Qt: Add new cheat manager
This commit is contained in:
parent
9f0f24a5e5
commit
b694577c38
|
@ -13,6 +13,7 @@ A "BIOS" ROM image is required to to start the emulator and to play games. You c
|
|||
|
||||
## Latest News
|
||||
|
||||
- 2020/10/20: New cheat manager with memory scanning added. More features will be added over time.
|
||||
- 2020/10/05: CD-ROM read speedup enhancement added.
|
||||
- 2020/09/30: CPU overclocking is now supported. Use with caution as it will break games and increase system requirements. It can be set globally or per-game.
|
||||
- 2020/09/25: Cheat support added for libretro core.
|
||||
|
|
|
@ -1,10 +1,14 @@
|
|||
#include "cheats.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/file_system.h"
|
||||
#include "common/log.h"
|
||||
#include "common/string.h"
|
||||
#include "common/string_util.h"
|
||||
#include "cpu_core.h"
|
||||
#include "host_interface.h"
|
||||
#include <cctype>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
Log_SetChannel(Cheats);
|
||||
|
||||
using KeyValuePairVector = std::vector<std::pair<std::string, std::string>>;
|
||||
|
@ -37,6 +41,7 @@ bool CheatList::LoadFromPCSXRFile(const char* filename)
|
|||
|
||||
char line[1024];
|
||||
CheatCode current_code;
|
||||
current_code.group = "Ungrouped";
|
||||
while (std::fgets(line, sizeof(line), fp.get()))
|
||||
{
|
||||
char* start = line;
|
||||
|
@ -67,8 +72,9 @@ bool CheatList::LoadFromPCSXRFile(const char* filename)
|
|||
if (current_code.Valid())
|
||||
m_codes.push_back(std::move(current_code));
|
||||
|
||||
current_code = {};
|
||||
current_code.enabled = false;
|
||||
current_code = CheatCode();
|
||||
current_code.group = "Ungrouped";
|
||||
|
||||
if (*start == '*')
|
||||
{
|
||||
current_code.enabled = true;
|
||||
|
@ -191,6 +197,7 @@ bool CheatList::LoadFromLibretroFile(const char* filename)
|
|||
}
|
||||
|
||||
CheatCode cc;
|
||||
cc.group = "Ungrouped";
|
||||
cc.description = *desc;
|
||||
cc.enabled = StringUtil::FromChars<bool>(*enable).value_or(false);
|
||||
if (ParseLibretroCheat(&cc, code->c_str()))
|
||||
|
@ -357,6 +364,20 @@ u32 CheatList::GetEnabledCodeCount() const
|
|||
return count;
|
||||
}
|
||||
|
||||
std::vector<std::string> CheatList::GetCodeGroups() const
|
||||
{
|
||||
std::vector<std::string> groups;
|
||||
for (const CheatCode& cc : m_codes)
|
||||
{
|
||||
if (std::any_of(groups.begin(), groups.end(), [cc](const std::string& group) { return (group == cc.group); }))
|
||||
continue;
|
||||
|
||||
groups.emplace_back(cc.group);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
void CheatList::SetCodeEnabled(u32 index, bool state)
|
||||
{
|
||||
if (index >= m_codes.size())
|
||||
|
@ -383,6 +404,73 @@ void CheatList::ApplyCode(u32 index)
|
|||
m_codes[index].Apply();
|
||||
}
|
||||
|
||||
std::string CheatCode::GetInstructionsAsString() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
for (const Instruction& inst : instructions)
|
||||
{
|
||||
ss << std::hex << std::uppercase << std::setw(8) << std::setfill('0') << inst.first;
|
||||
ss << " ";
|
||||
ss << std::hex << std::uppercase << std::setw(8) << std::setfill('0') << inst.second;
|
||||
ss << '\n';
|
||||
}
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool CheatCode::SetInstructionsFromString(const std::string& str)
|
||||
{
|
||||
std::vector<Instruction> new_instructions;
|
||||
std::istringstream ss(str);
|
||||
|
||||
for (std::string line; std::getline(ss, line);)
|
||||
{
|
||||
char* start = line.data();
|
||||
while (*start != '\0' && std::isspace(*start))
|
||||
start++;
|
||||
|
||||
// skip empty lines
|
||||
if (*start == '\0')
|
||||
continue;
|
||||
|
||||
char* end = start + std::strlen(start) - 1;
|
||||
while (end > start && std::isspace(*end))
|
||||
{
|
||||
*end = '\0';
|
||||
end--;
|
||||
}
|
||||
|
||||
// skip comments and empty line
|
||||
if (*start == '#' || *start == ';' || *start == '/' || *start == '\"')
|
||||
continue;
|
||||
|
||||
while (!IsHexCharacter(*start) && start != end)
|
||||
start++;
|
||||
if (start == end)
|
||||
continue;
|
||||
|
||||
char* end_ptr;
|
||||
CheatCode::Instruction inst;
|
||||
inst.first = static_cast<u32>(std::strtoul(start, &end_ptr, 16));
|
||||
inst.second = 0;
|
||||
if (end_ptr)
|
||||
{
|
||||
while (!IsHexCharacter(*end_ptr) && end_ptr != end)
|
||||
end_ptr++;
|
||||
if (end_ptr != end)
|
||||
inst.second = static_cast<u32>(std::strtoul(end_ptr, nullptr, 16));
|
||||
}
|
||||
new_instructions.push_back(inst);
|
||||
}
|
||||
|
||||
if (new_instructions.empty())
|
||||
return false;
|
||||
|
||||
instructions = std::move(new_instructions);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CheatCode::Apply() const
|
||||
{
|
||||
const u32 count = static_cast<u32>(instructions.size());
|
||||
|
@ -622,3 +710,476 @@ void CheatCode::Apply() const
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::array<const char*, 1> s_cheat_code_type_names = {{"Gameshark"}};
|
||||
static std::array<const char*, 1> s_cheat_code_type_display_names{{TRANSLATABLE("Cheats", "Gameshark")}};
|
||||
|
||||
const char* CheatCode::GetTypeName(Type type)
|
||||
{
|
||||
return s_cheat_code_type_names[static_cast<u32>(type)];
|
||||
}
|
||||
|
||||
const char* CheatCode::GetTypeDisplayName(Type type)
|
||||
{
|
||||
return s_cheat_code_type_display_names[static_cast<u32>(type)];
|
||||
}
|
||||
|
||||
std::optional<CheatCode::Type> CheatCode::ParseTypeName(const char* str)
|
||||
{
|
||||
for (u32 i = 0; i < static_cast<u32>(s_cheat_code_type_names.size()); i++)
|
||||
{
|
||||
if (std::strcmp(s_cheat_code_type_names[i], str) == 0)
|
||||
return static_cast<Type>(i);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static std::array<const char*, 2> s_cheat_code_activation_names = {{"Manual", "EndFrame"}};
|
||||
static std::array<const char*, 2> s_cheat_code_activation_display_names{
|
||||
{TRANSLATABLE("Cheats", "Manual"), TRANSLATABLE("Cheats", "Automatic (Frame End)")}};
|
||||
|
||||
const char* CheatCode::GetActivationName(Activation activation)
|
||||
{
|
||||
return s_cheat_code_activation_names[static_cast<u32>(activation)];
|
||||
}
|
||||
|
||||
const char* CheatCode::GetActivationDisplayName(Activation activation)
|
||||
{
|
||||
return s_cheat_code_activation_display_names[static_cast<u32>(activation)];
|
||||
}
|
||||
|
||||
std::optional<CheatCode::Activation> CheatCode::ParseActivationName(const char* str)
|
||||
{
|
||||
for (u32 i = 0; i < static_cast<u32>(s_cheat_code_activation_names.size()); i++)
|
||||
{
|
||||
if (std::strcmp(s_cheat_code_activation_names[i], str) == 0)
|
||||
return static_cast<Activation>(i);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
u8 bvalue = 0;
|
||||
CPU::SafeReadMemoryByte(address, &bvalue);
|
||||
|
||||
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)
|
||||
{
|
||||
u16 bvalue = 0;
|
||||
CPU::SafeReadMemoryHalfWord(address, &bvalue);
|
||||
|
||||
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)
|
||||
{
|
||||
Result res;
|
||||
res.address = address;
|
||||
CPU::SafeReadMemoryWord(address, &res.value);
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
CPU::SafeReadMemoryByte(address, &bvalue);
|
||||
value = is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
|
||||
}
|
||||
break;
|
||||
|
||||
case MemoryAccessSize::HalfWord:
|
||||
{
|
||||
u16 bvalue = 0;
|
||||
CPU::SafeReadMemoryHalfWord(address, &bvalue);
|
||||
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;
|
||||
CPU::SafeReadMemoryByte(entry->address, &bvalue);
|
||||
entry->value = entry->is_signed ? SignExtend32(bvalue) : ZeroExtend32(bvalue);
|
||||
}
|
||||
break;
|
||||
|
||||
case MemoryAccessSize::HalfWord:
|
||||
{
|
||||
u16 bvalue = 0;
|
||||
CPU::SafeReadMemoryHalfWord(entry->address, &bvalue);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,19 @@
|
|||
|
||||
struct CheatCode
|
||||
{
|
||||
enum class Type : u8
|
||||
{
|
||||
Gameshark,
|
||||
Count
|
||||
};
|
||||
|
||||
enum class Activation : u8
|
||||
{
|
||||
Manual,
|
||||
EndFrame,
|
||||
Count,
|
||||
};
|
||||
|
||||
enum class InstructionCode : u8
|
||||
{
|
||||
Nop = 0x00,
|
||||
|
@ -45,13 +58,28 @@ struct CheatCode
|
|||
BitField<u64, u8, 0, 8> value8;
|
||||
};
|
||||
|
||||
std::string group;
|
||||
std::string description;
|
||||
std::vector<Instruction> instructions;
|
||||
bool enabled;
|
||||
Type type = Type::Gameshark;
|
||||
Activation activation = Activation::EndFrame;
|
||||
bool enabled = false;
|
||||
|
||||
ALWAYS_INLINE bool Valid() const { return !instructions.empty() && !description.empty(); }
|
||||
ALWAYS_INLINE bool IsManuallyActivated() const { return (activation == Activation::Manual); }
|
||||
|
||||
std::string GetInstructionsAsString() const;
|
||||
bool SetInstructionsFromString(const std::string& str);
|
||||
|
||||
void Apply() 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);
|
||||
};
|
||||
|
||||
class CheatList final
|
||||
|
@ -78,6 +106,7 @@ public:
|
|||
void RemoveCode(u32 i);
|
||||
|
||||
u32 GetEnabledCodeCount() const;
|
||||
std::vector<std::string> GetCodeGroups() const;
|
||||
void EnableCode(u32 index);
|
||||
void DisableCode(u32 index);
|
||||
void SetCodeEnabled(u32 index, bool state);
|
||||
|
@ -98,3 +127,121 @@ public:
|
|||
private:
|
||||
std::vector<CheatCode> m_codes;
|
||||
};
|
||||
|
||||
class MemoryScan
|
||||
{
|
||||
public:
|
||||
enum class Operator
|
||||
{
|
||||
Equal,
|
||||
NotEqual,
|
||||
GreaterThan,
|
||||
GreaterEqual,
|
||||
LessThan,
|
||||
LessEqual,
|
||||
IncreasedBy,
|
||||
DecreasedBy,
|
||||
ChangedBy,
|
||||
EqualLast,
|
||||
NotEqualLast,
|
||||
GreaterThanLast,
|
||||
GreaterEqualLast,
|
||||
LessThanLast,
|
||||
LessEqualLast
|
||||
};
|
||||
|
||||
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::Word;
|
||||
Operator m_operator = Operator::Equal;
|
||||
PhysicalMemoryAddress m_start_address = 0;
|
||||
PhysicalMemoryAddress m_end_address = 0x200000;
|
||||
ResultVector m_results;
|
||||
bool m_signed = true;
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
@ -18,6 +18,12 @@ set(SRCS
|
|||
biossettingswidget.cpp
|
||||
biossettingswidget.h
|
||||
biossettingswidget.ui
|
||||
cheatmanagerdialog.cpp
|
||||
cheatmanagerdialog.h
|
||||
cheatmanagerdialog.ui
|
||||
cheatcodeeditordialog.cpp
|
||||
cheatcodeeditordialog.h
|
||||
cheatcodeeditordialog.ui
|
||||
consolesettingswidget.cpp
|
||||
consolesettingswidget.h
|
||||
consolesettingswidget.ui
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
#include "cheatcodeeditordialog.h"
|
||||
#include <QtWidgets/QMessageBox>
|
||||
|
||||
CheatCodeEditorDialog::CheatCodeEditorDialog(CheatList* list, CheatCode* code, QWidget* parent)
|
||||
: m_code(code), QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
setupAdditionalUi(list);
|
||||
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(CheatList* list)
|
||||
{
|
||||
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))));
|
||||
}
|
||||
|
||||
const auto groups = list->GetCodeGroups();
|
||||
if (!groups.empty())
|
||||
{
|
||||
for (const std::string& group_name : groups)
|
||||
m_ui.group->addItem(QString::fromStdString(group_name));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui.group->addItem(QStringLiteral("Ungrouped"));
|
||||
}
|
||||
}
|
||||
|
||||
void CheatCodeEditorDialog::fillUi()
|
||||
{
|
||||
m_ui.description->setText(QString::fromStdString(m_code->description));
|
||||
|
||||
int index = m_ui.group->findText(QString::fromStdString(m_code->group));
|
||||
if (index >= 0)
|
||||
m_ui.group->setCurrentIndex(index);
|
||||
else
|
||||
m_ui.group->setCurrentIndex(0);
|
||||
|
||||
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.save, &QPushButton::clicked, this, &CheatCodeEditorDialog::saveClicked);
|
||||
connect(m_ui.cancel, &QPushButton::clicked, this, &CheatCodeEditorDialog::cancelClicked);
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
#pragma once
|
||||
#include "core/cheats.h"
|
||||
#include "ui_cheatcodeeditordialog.h"
|
||||
|
||||
class CheatCodeEditorDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CheatCodeEditorDialog(CheatList* list, CheatCode* code, QWidget* parent);
|
||||
~CheatCodeEditorDialog();
|
||||
|
||||
private Q_SLOTS:
|
||||
void saveClicked();
|
||||
void cancelClicked();
|
||||
|
||||
private:
|
||||
void setupAdditionalUi(CheatList* list);
|
||||
void fillUi();
|
||||
void connectUi();
|
||||
|
||||
CheatCode* m_code;
|
||||
|
||||
Ui::CheatCodeEditorDialog m_ui;
|
||||
};
|
|
@ -0,0 +1,101 @@
|
|||
<?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">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<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>
|
||||
<widget class="QPushButton" name="save">
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="cancel">
|
||||
<property name="text">
|
||||
<string>Cancel</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -0,0 +1,689 @@
|
|||
#include "cheatmanagerdialog.h"
|
||||
#include "cheatcodeeditordialog.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/system.h"
|
||||
#include "qthostinterface.h"
|
||||
#include "qtutils.h"
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtGui/QColor>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QTreeWidgetItemIterator>
|
||||
#include <array>
|
||||
|
||||
static QString formatHexValue(u32 value)
|
||||
{
|
||||
return QStringLiteral("0x%1").arg(static_cast<uint>(value), 8, 16, QChar('0'));
|
||||
}
|
||||
|
||||
static QString formatValue(u32 value, bool is_signed)
|
||||
{
|
||||
if (is_signed)
|
||||
return QStringLiteral("%1").arg(static_cast<int>(value));
|
||||
else
|
||||
return QStringLiteral("%1").arg(static_cast<uint>(value));
|
||||
}
|
||||
|
||||
CheatManagerDialog::CheatManagerDialog(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
setupAdditionalUi();
|
||||
connectUi();
|
||||
|
||||
updateCheatList();
|
||||
}
|
||||
|
||||
CheatManagerDialog::~CheatManagerDialog() = default;
|
||||
|
||||
void CheatManagerDialog::setupAdditionalUi()
|
||||
{
|
||||
m_ui.scanStartAddress->setText(formatHexValue(m_scanner.GetStartAddress()));
|
||||
m_ui.scanEndAddress->setText(formatHexValue(m_scanner.GetEndAddress()));
|
||||
}
|
||||
|
||||
void CheatManagerDialog::connectUi()
|
||||
{
|
||||
connect(m_ui.tabWidget, &QTabWidget::currentChanged, [this](int index) {
|
||||
resizeColumns();
|
||||
setUpdateTimerEnabled(index == 1);
|
||||
});
|
||||
connect(m_ui.cheatList, &QTreeWidget::currentItemChanged, this, &CheatManagerDialog::cheatListCurrentItemChanged);
|
||||
connect(m_ui.cheatList, &QTreeWidget::itemActivated, this, &CheatManagerDialog::cheatListItemActivated);
|
||||
connect(m_ui.cheatList, &QTreeWidget::itemChanged, this, &CheatManagerDialog::cheatListItemChanged);
|
||||
connect(m_ui.cheatListNewCategory, &QPushButton::clicked, this, &CheatManagerDialog::newCategoryClicked);
|
||||
connect(m_ui.cheatListAdd, &QPushButton::clicked, this, &CheatManagerDialog::addCodeClicked);
|
||||
connect(m_ui.cheatListEdit, &QPushButton::clicked, this, &CheatManagerDialog::editCodeClicked);
|
||||
connect(m_ui.cheatListRemove, &QPushButton::clicked, this, &CheatManagerDialog::deleteCodeClicked);
|
||||
connect(m_ui.cheatListActivate, &QPushButton::clicked, this, &CheatManagerDialog::activateCodeClicked);
|
||||
connect(m_ui.cheatListImport, &QPushButton::clicked, this, &CheatManagerDialog::importClicked);
|
||||
connect(m_ui.cheatListExport, &QPushButton::clicked, this, &CheatManagerDialog::exportClicked);
|
||||
|
||||
connect(m_ui.scanValue, &QLineEdit::textChanged, this, &CheatManagerDialog::updateScanValue);
|
||||
connect(m_ui.scanValueBase, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
[this](int index) { updateScanValue(); });
|
||||
connect(m_ui.scanSize, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
m_scanner.SetSize(static_cast<MemoryAccessSize>(index));
|
||||
m_scanner.ResetSearch();
|
||||
updateResults();
|
||||
});
|
||||
connect(m_ui.scanValueSigned, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
|
||||
m_scanner.SetValueSigned(index == 0);
|
||||
m_scanner.ResetSearch();
|
||||
updateResults();
|
||||
});
|
||||
connect(m_ui.scanOperator, QOverload<int>::of(&QComboBox::currentIndexChanged),
|
||||
[this](int index) { m_scanner.SetOperator(static_cast<MemoryScan::Operator>(index)); });
|
||||
connect(m_ui.scanNewSearch, &QPushButton::clicked, [this]() {
|
||||
m_scanner.Search();
|
||||
updateResults();
|
||||
});
|
||||
connect(m_ui.scanSearchAgain, &QPushButton::clicked, [this]() {
|
||||
m_scanner.SearchAgain();
|
||||
updateResults();
|
||||
});
|
||||
connect(m_ui.scanResetSearch, &QPushButton::clicked, [this]() {
|
||||
m_scanner.ResetSearch();
|
||||
updateResults();
|
||||
});
|
||||
connect(m_ui.scanAddWatch, &QPushButton::clicked, this, &CheatManagerDialog::addToWatchClicked);
|
||||
connect(m_ui.scanRemoveWatch, &QPushButton::clicked, this, &CheatManagerDialog::removeWatchClicked);
|
||||
connect(m_ui.scanTable, &QTableWidget::currentItemChanged, this, &CheatManagerDialog::scanCurrentItemChanged);
|
||||
connect(m_ui.watchTable, &QTableWidget::currentItemChanged, this, &CheatManagerDialog::watchCurrentItemChanged);
|
||||
connect(m_ui.scanTable, &QTableWidget::itemChanged, this, &CheatManagerDialog::scanItemChanged);
|
||||
connect(m_ui.watchTable, &QTableWidget::itemChanged, this, &CheatManagerDialog::watchItemChanged);
|
||||
}
|
||||
|
||||
void CheatManagerDialog::showEvent(QShowEvent* event)
|
||||
{
|
||||
QDialog::showEvent(event);
|
||||
resizeColumns();
|
||||
}
|
||||
|
||||
void CheatManagerDialog::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QDialog::resizeEvent(event);
|
||||
resizeColumns();
|
||||
}
|
||||
|
||||
void CheatManagerDialog::resizeColumns()
|
||||
{
|
||||
QtUtils::ResizeColumnsForTableView(m_ui.scanTable, {-1, 100, 100});
|
||||
QtUtils::ResizeColumnsForTableView(m_ui.watchTable, {50, -1, 100, 150, 100});
|
||||
QtUtils::ResizeColumnsForTreeView(m_ui.cheatList, {-1, 100, 150, 100});
|
||||
}
|
||||
|
||||
void CheatManagerDialog::setUpdateTimerEnabled(bool enabled)
|
||||
{
|
||||
if ((!m_update_timer && !enabled) && m_update_timer->isActive() == enabled)
|
||||
return;
|
||||
|
||||
if (!m_update_timer)
|
||||
{
|
||||
m_update_timer = new QTimer(this);
|
||||
connect(m_update_timer, &QTimer::timeout, this, &CheatManagerDialog::updateScanUi);
|
||||
}
|
||||
|
||||
if (enabled)
|
||||
m_update_timer->start(100);
|
||||
else
|
||||
m_update_timer->stop();
|
||||
}
|
||||
|
||||
int CheatManagerDialog::getSelectedResultIndex() const
|
||||
{
|
||||
QList<QTableWidgetSelectionRange> sel = m_ui.scanTable->selectedRanges();
|
||||
if (sel.isEmpty())
|
||||
return -1;
|
||||
|
||||
return sel.front().topRow();
|
||||
}
|
||||
|
||||
int CheatManagerDialog::getSelectedWatchIndex() const
|
||||
{
|
||||
QList<QTableWidgetSelectionRange> sel = m_ui.watchTable->selectedRanges();
|
||||
if (sel.isEmpty())
|
||||
return -1;
|
||||
|
||||
return sel.front().topRow();
|
||||
}
|
||||
|
||||
QTreeWidgetItem* CheatManagerDialog::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;
|
||||
}
|
||||
|
||||
static u32 getCheatIndexFromItem(QTreeWidgetItem* item)
|
||||
{
|
||||
return item->data(0, Qt::UserRole).toUInt();
|
||||
}
|
||||
|
||||
int CheatManagerDialog::getSelectedCheatIndex() const
|
||||
{
|
||||
QList<QTreeWidgetItem*> sel = m_ui.cheatList->selectedItems();
|
||||
if (sel.isEmpty())
|
||||
return -1;
|
||||
|
||||
return static_cast<int>(getCheatIndexFromItem(sel.first()));
|
||||
}
|
||||
|
||||
CheatList* CheatManagerDialog::getCheatList() const
|
||||
{
|
||||
Assert(System::IsValid());
|
||||
|
||||
CheatList* list = System::GetCheatList();
|
||||
if (!list)
|
||||
QtHostInterface::GetInstance()->LoadCheatListFromGameTitle();
|
||||
if (!list)
|
||||
{
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
[]() { System::SetCheatList(std::make_unique<CheatList>()); }, true);
|
||||
list = System::GetCheatList();
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void CheatManagerDialog::updateCheatList()
|
||||
{
|
||||
QSignalBlocker sb(m_ui.cheatList);
|
||||
|
||||
CheatList* list = getCheatList();
|
||||
while (m_ui.cheatList->topLevelItemCount() > 0)
|
||||
delete m_ui.cheatList->takeTopLevelItem(0);
|
||||
|
||||
const std::vector<std::string> groups = list->GetCodeGroups();
|
||||
for (const std::string& group_name : groups)
|
||||
{
|
||||
QTreeWidgetItem* group = new QTreeWidgetItem();
|
||||
group->setFlags(group->flags() | Qt::ItemIsUserCheckable);
|
||||
group->setText(0, QString::fromStdString(group_name));
|
||||
m_ui.cheatList->addTopLevelItem(group);
|
||||
|
||||
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);
|
||||
item->setData(0, Qt::UserRole, QVariant(static_cast<uint>(i)));
|
||||
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())));
|
||||
|
||||
all_enabled &= code.enabled;
|
||||
}
|
||||
|
||||
group->setCheckState(0, all_enabled ? Qt::Checked : Qt::Unchecked);
|
||||
group->setExpanded(true);
|
||||
}
|
||||
|
||||
m_ui.cheatListEdit->setEnabled(false);
|
||||
m_ui.cheatListRemove->setEnabled(false);
|
||||
m_ui.cheatListActivate->setText(tr("Activate"));
|
||||
m_ui.cheatListActivate->setEnabled(false);
|
||||
m_ui.cheatListExport->setEnabled(list->GetCodeCount() > 0);
|
||||
}
|
||||
|
||||
void CheatManagerDialog::saveCheatList()
|
||||
{
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([]() { QtHostInterface::GetInstance()->SaveCheatList(); });
|
||||
}
|
||||
|
||||
void CheatManagerDialog::cheatListCurrentItemChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous)
|
||||
{
|
||||
const bool has_current = (current != nullptr);
|
||||
m_ui.cheatListEdit->setEnabled(has_current);
|
||||
m_ui.cheatListRemove->setEnabled(has_current);
|
||||
m_ui.cheatListActivate->setEnabled(has_current);
|
||||
|
||||
if (!current)
|
||||
{
|
||||
m_ui.cheatListActivate->setText(tr("Activate"));
|
||||
}
|
||||
else
|
||||
{
|
||||
const bool manual_activation = getCheatList()->GetCode(getCheatIndexFromItem(current)).IsManuallyActivated();
|
||||
m_ui.cheatListActivate->setText(manual_activation ? tr("Activate") : tr("Toggle"));
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::cheatListItemActivated(QTreeWidgetItem* item)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
const u32 index = getCheatIndexFromItem(item);
|
||||
activateCheat(index);
|
||||
}
|
||||
|
||||
void CheatManagerDialog::cheatListItemChanged(QTreeWidgetItem* item, int column)
|
||||
{
|
||||
if (!item || column != 0)
|
||||
return;
|
||||
|
||||
const u32 index = getCheatIndexFromItem(item);
|
||||
CheatList* list = getCheatList();
|
||||
if (index >= list->GetCodeCount())
|
||||
return;
|
||||
|
||||
CheatCode& cc = list->GetCode(index);
|
||||
if (cc.IsManuallyActivated())
|
||||
return;
|
||||
|
||||
const bool new_enabled = (item->checkState(0) == Qt::Checked);
|
||||
if (cc.enabled == new_enabled)
|
||||
return;
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([index, new_enabled]() {
|
||||
System::GetCheatList()->SetCodeEnabled(index, new_enabled);
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
});
|
||||
}
|
||||
|
||||
void CheatManagerDialog::activateCheat(u32 index)
|
||||
{
|
||||
CheatList* list = getCheatList();
|
||||
if (index >= list->GetCodeCount())
|
||||
return;
|
||||
|
||||
CheatCode& cc = list->GetCode(index);
|
||||
if (cc.IsManuallyActivated())
|
||||
{
|
||||
QtHostInterface::GetInstance()->applyCheat(index);
|
||||
return;
|
||||
}
|
||||
|
||||
const bool new_enabled = !cc.enabled;
|
||||
QTreeWidgetItem* item = getItemForCheatIndex(index);
|
||||
if (item)
|
||||
{
|
||||
QSignalBlocker sb(m_ui.cheatList);
|
||||
item->setCheckState(0, new_enabled ? Qt::Checked : Qt::Unchecked);
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread([index, new_enabled]() {
|
||||
System::GetCheatList()->SetCodeEnabled(index, new_enabled);
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
});
|
||||
}
|
||||
|
||||
void CheatManagerDialog::newCategoryClicked()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
void CheatManagerDialog::addCodeClicked()
|
||||
{
|
||||
CheatList* list = getCheatList();
|
||||
|
||||
CheatCode new_code;
|
||||
CheatCodeEditorDialog editor(list, &new_code, this);
|
||||
if (editor.exec() > 0)
|
||||
{
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
[this, &new_code]() {
|
||||
System::GetCheatList()->AddCode(std::move(new_code));
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
},
|
||||
true);
|
||||
updateCheatList();
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::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(list, &new_code, this);
|
||||
if (editor.exec() > 0)
|
||||
{
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
[index, &new_code]() {
|
||||
System::GetCheatList()->SetCode(static_cast<u32>(index), std::move(new_code));
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
},
|
||||
true);
|
||||
updateCheatList();
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::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;
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
[index]() {
|
||||
System::GetCheatList()->RemoveCode(static_cast<u32>(index));
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
},
|
||||
true);
|
||||
updateCheatList();
|
||||
}
|
||||
|
||||
void CheatManagerDialog::activateCodeClicked()
|
||||
{
|
||||
int index = getSelectedCheatIndex();
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
activateCheat(static_cast<u32>(index));
|
||||
}
|
||||
|
||||
void CheatManagerDialog::importClicked()
|
||||
{
|
||||
const QString filter(tr("PCSXR/Libretro Cheat Files (*.cht *.txt);;All Files (*.*)"));
|
||||
const QString filename(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;
|
||||
}
|
||||
|
||||
QtHostInterface::GetInstance()->executeOnEmulationThread(
|
||||
[&new_cheats]() {
|
||||
DebugAssert(System::HasCheatList());
|
||||
CheatList* list = System::GetCheatList();
|
||||
for (u32 i = 0; i < new_cheats.GetCodeCount(); i++)
|
||||
list->AddCode(new_cheats.GetCode(i));
|
||||
|
||||
QtHostInterface::GetInstance()->SaveCheatList();
|
||||
},
|
||||
true);
|
||||
updateCheatList();
|
||||
}
|
||||
|
||||
void CheatManagerDialog::exportClicked()
|
||||
{
|
||||
const QString filter(tr("PCSXR Cheat Files (*.cht);;All Files (*.*)"));
|
||||
const QString filename(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 CheatManagerDialog::addToWatchClicked()
|
||||
{
|
||||
const int index = getSelectedResultIndex();
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
const MemoryScan::Result& res = m_scanner.GetResults()[static_cast<u32>(index)];
|
||||
m_watch.AddEntry(StringUtil::StdStringFromFormat("0x%08x", res.address), res.address, m_scanner.GetSize(),
|
||||
m_scanner.GetValueSigned(), false);
|
||||
updateWatch();
|
||||
}
|
||||
|
||||
void CheatManagerDialog::removeWatchClicked()
|
||||
{
|
||||
const int index = getSelectedWatchIndex();
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
m_watch.RemoveEntry(static_cast<u32>(index));
|
||||
updateWatch();
|
||||
}
|
||||
|
||||
void CheatManagerDialog::scanCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous)
|
||||
{
|
||||
m_ui.scanAddWatch->setEnabled((current != nullptr));
|
||||
}
|
||||
|
||||
void CheatManagerDialog::watchCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous)
|
||||
{
|
||||
m_ui.scanRemoveWatch->setEnabled((current != nullptr));
|
||||
}
|
||||
|
||||
void CheatManagerDialog::scanItemChanged(QTableWidgetItem* item)
|
||||
{
|
||||
const u32 index = static_cast<u32>(item->row());
|
||||
switch (item->column())
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
bool value_ok = false;
|
||||
if (m_scanner.GetValueSigned())
|
||||
{
|
||||
int value = item->text().toInt(&value_ok);
|
||||
if (value_ok)
|
||||
m_scanner.SetResultValue(index, static_cast<u32>(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint value = item->text().toUInt(&value_ok);
|
||||
if (value_ok)
|
||||
m_scanner.SetResultValue(index, static_cast<u32>(value));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::watchItemChanged(QTableWidgetItem* item)
|
||||
{
|
||||
const u32 index = static_cast<u32>(item->row());
|
||||
if (index >= m_watch.GetEntryCount())
|
||||
return;
|
||||
|
||||
switch (item->column())
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
m_watch.SetEntryFreeze(index, (item->checkState() == Qt::Checked));
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
{
|
||||
m_watch.SetEntryDescription(index, item->text().toStdString());
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
{
|
||||
const MemoryWatchList::Entry& entry = m_watch.GetEntry(index);
|
||||
bool value_ok = false;
|
||||
if (entry.is_signed)
|
||||
{
|
||||
int value = item->text().toInt(&value_ok);
|
||||
if (value_ok)
|
||||
m_watch.SetEntryValue(index, static_cast<u32>(value));
|
||||
}
|
||||
else
|
||||
{
|
||||
uint value = item->text().toUInt(&value_ok);
|
||||
if (value_ok)
|
||||
m_watch.SetEntryValue(index, static_cast<u32>(value));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::updateScanValue()
|
||||
{
|
||||
QString value = m_ui.scanValue->text();
|
||||
if (value.startsWith(QStringLiteral("0x")))
|
||||
value.remove(0, 2);
|
||||
|
||||
bool ok = false;
|
||||
uint uint_value = value.toUInt(&ok, (m_ui.scanValueBase->currentIndex() > 0) ? 16 : 10);
|
||||
if (ok)
|
||||
m_scanner.SetValue(uint_value);
|
||||
}
|
||||
|
||||
void CheatManagerDialog::updateResults()
|
||||
{
|
||||
QSignalBlocker sb(m_ui.scanTable);
|
||||
m_ui.scanTable->setRowCount(0);
|
||||
|
||||
const MemoryScan::ResultVector& results = m_scanner.GetResults();
|
||||
if (!results.empty())
|
||||
{
|
||||
int row = 0;
|
||||
for (const MemoryScan::Result& res : m_scanner.GetResults())
|
||||
{
|
||||
m_ui.scanTable->insertRow(row);
|
||||
|
||||
QTableWidgetItem* address_item = new QTableWidgetItem(formatHexValue(res.address));
|
||||
address_item->setFlags(address_item->flags() & ~(Qt::ItemIsEditable));
|
||||
m_ui.scanTable->setItem(row, 0, address_item);
|
||||
|
||||
QTableWidgetItem* value_item = new QTableWidgetItem(formatValue(res.value, m_scanner.GetValueSigned()));
|
||||
m_ui.scanTable->setItem(row, 1, value_item);
|
||||
|
||||
QTableWidgetItem* previous_item = new QTableWidgetItem(formatValue(res.last_value, m_scanner.GetValueSigned()));
|
||||
previous_item->setFlags(address_item->flags() & ~(Qt::ItemIsEditable));
|
||||
m_ui.scanTable->setItem(row, 2, previous_item);
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
m_ui.scanResetSearch->setEnabled(!results.empty());
|
||||
m_ui.scanSearchAgain->setEnabled(!results.empty());
|
||||
m_ui.scanAddWatch->setEnabled(false);
|
||||
}
|
||||
|
||||
void CheatManagerDialog::updateResultsValues()
|
||||
{
|
||||
QSignalBlocker sb(m_ui.scanTable);
|
||||
|
||||
int row = 0;
|
||||
for (const MemoryScan::Result& res : m_scanner.GetResults())
|
||||
{
|
||||
if (res.value_changed)
|
||||
{
|
||||
QTableWidgetItem* item = m_ui.scanTable->item(row, 1);
|
||||
item->setText(formatValue(res.value, m_scanner.GetValueSigned()));
|
||||
item->setForeground(Qt::red);
|
||||
}
|
||||
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::updateWatch()
|
||||
{
|
||||
static constexpr std::array<const char*, 6> size_strings = {
|
||||
{QT_TR_NOOP("Byte"), QT_TR_NOOP("Halfword"), QT_TR_NOOP("Word"), QT_TR_NOOP("Signed Byte"),
|
||||
QT_TR_NOOP("Signed Halfword"), QT_TR_NOOP("Signed Word")}};
|
||||
|
||||
m_watch.UpdateValues();
|
||||
|
||||
QSignalBlocker sb(m_ui.watchTable);
|
||||
m_ui.watchTable->setRowCount(0);
|
||||
|
||||
const MemoryWatchList::EntryVector& entries = m_watch.GetEntries();
|
||||
if (!entries.empty())
|
||||
{
|
||||
int row = 0;
|
||||
for (const MemoryWatchList::Entry& res : entries)
|
||||
{
|
||||
m_ui.watchTable->insertRow(row);
|
||||
|
||||
QTableWidgetItem* freeze_item = new QTableWidgetItem();
|
||||
freeze_item->setFlags(freeze_item->flags() | (Qt::ItemIsEditable | Qt::ItemIsUserCheckable));
|
||||
freeze_item->setCheckState(res.freeze ? Qt::Checked : Qt::Unchecked);
|
||||
m_ui.watchTable->setItem(row, 0, freeze_item);
|
||||
|
||||
QTableWidgetItem* description_item = new QTableWidgetItem(QString::fromStdString(res.description));
|
||||
m_ui.watchTable->setItem(row, 1, description_item);
|
||||
|
||||
QTableWidgetItem* address_item = new QTableWidgetItem(formatHexValue(res.address));
|
||||
address_item->setFlags(address_item->flags() & ~(Qt::ItemIsEditable));
|
||||
m_ui.watchTable->setItem(row, 2, address_item);
|
||||
|
||||
QTableWidgetItem* size_item =
|
||||
new QTableWidgetItem(tr(size_strings[static_cast<u32>(res.size) + (res.is_signed ? 3 : 0)]));
|
||||
size_item->setFlags(address_item->flags() & ~(Qt::ItemIsEditable));
|
||||
m_ui.watchTable->setItem(row, 3, size_item);
|
||||
|
||||
QTableWidgetItem* value_item = new QTableWidgetItem(formatValue(res.value, res.is_signed));
|
||||
m_ui.watchTable->setItem(row, 4, value_item);
|
||||
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
m_ui.scanSaveWatch->setEnabled(!entries.empty());
|
||||
m_ui.scanRemoveWatch->setEnabled(false);
|
||||
}
|
||||
|
||||
void CheatManagerDialog::updateWatchValues()
|
||||
{
|
||||
QSignalBlocker sb(m_ui.watchTable);
|
||||
int row = 0;
|
||||
for (const MemoryWatchList::Entry& res : m_watch.GetEntries())
|
||||
{
|
||||
if (res.changed)
|
||||
m_ui.watchTable->item(row, 4)->setText(formatValue(res.value, res.is_signed));
|
||||
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
void CheatManagerDialog::updateScanUi()
|
||||
{
|
||||
m_scanner.UpdateResultsValues();
|
||||
m_watch.UpdateValues();
|
||||
|
||||
updateResultsValues();
|
||||
updateWatchValues();
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
#pragma once
|
||||
#include "core/cheats.h"
|
||||
#include "ui_cheatmanagerdialog.h"
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QTableWidget>
|
||||
#include <optional>
|
||||
|
||||
class CheatManagerDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
CheatManagerDialog(QWidget* parent);
|
||||
~CheatManagerDialog();
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent* event);
|
||||
void resizeEvent(QResizeEvent* event);
|
||||
|
||||
private Q_SLOTS:
|
||||
void resizeColumns();
|
||||
|
||||
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 newCategoryClicked();
|
||||
void addCodeClicked();
|
||||
void editCodeClicked();
|
||||
void deleteCodeClicked();
|
||||
void activateCodeClicked();
|
||||
void importClicked();
|
||||
void exportClicked();
|
||||
|
||||
void addToWatchClicked();
|
||||
void removeWatchClicked();
|
||||
void scanCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous);
|
||||
void watchCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous);
|
||||
void scanItemChanged(QTableWidgetItem* item);
|
||||
void watchItemChanged(QTableWidgetItem* item);
|
||||
void updateScanValue();
|
||||
void updateScanUi();
|
||||
|
||||
private:
|
||||
void setupAdditionalUi();
|
||||
void connectUi();
|
||||
void setUpdateTimerEnabled(bool enabled);
|
||||
void updateResults();
|
||||
void updateResultsValues();
|
||||
void updateWatch();
|
||||
void updateWatchValues();
|
||||
|
||||
QTreeWidgetItem* getItemForCheatIndex(u32 index) const;
|
||||
int getSelectedCheatIndex() const;
|
||||
int getSelectedResultIndex() const;
|
||||
int getSelectedWatchIndex() const;
|
||||
|
||||
Ui::CheatManagerDialog m_ui;
|
||||
|
||||
MemoryScan m_scanner;
|
||||
MemoryWatchList m_watch;
|
||||
|
||||
QTimer* m_update_timer = nullptr;
|
||||
};
|
|
@ -0,0 +1,550 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>CheatManagerDialog</class>
|
||||
<widget class="QDialog" name="CheatManagerDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>864</width>
|
||||
<height>599</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Cheat Manager</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Cheat List</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="cheatListNewCategory">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>&New Category...</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>
|
||||
<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>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeWidget" name="cheatList">
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::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>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Memory Scanner</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="scanTable">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Address</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Value</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Previous Value</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Search Parameters</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Value:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="scanValue"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="scanValueSigned">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Signed</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Unsigned</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="scanValueBase">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Decimal</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Hex</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Data Size:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="scanSize">
|
||||
<property name="currentIndex">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Byte (1 byte)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Halfword (2 bytes)</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Word (4 bytes)</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Operator:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="scanOperator">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Equal to...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Not Equal to...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Greater Than...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Greater or Equal...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Less Than...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Less or Equal...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Increased By...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Decreased By...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Changed By...</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Equal to Previous</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Not Equal to Previous</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Greater Than Previous</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Greater or Equal to Previous</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Less Than Previous</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Less or Equal to Previous</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Start Address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QLineEdit" name="scanStartAddress"/>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>End Address:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QLineEdit" name="scanEndAddress"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanNewSearch">
|
||||
<property name="text">
|
||||
<string>New Search</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanSearchAgain">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Search Again</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanResetSearch">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Clear Results</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTableWidget" name="watchTable">
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::SingleSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
<property name="showGrid">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderHighlightSections">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Freeze</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Address</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Value</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanAddWatch">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add To Watch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanAddManualAddress">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add Manual Address</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanRemoveWatch">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Remove Watch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanLoadWatch">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Load Watch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="scanSaveWatch">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save Watch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -56,6 +56,8 @@
|
|||
<ClCompile Include="audiosettingswidget.cpp" />
|
||||
<ClCompile Include="autoupdaterdialog.cpp" />
|
||||
<ClCompile Include="biossettingswidget.cpp" />
|
||||
<ClCompile Include="cheatmanagerdialog.cpp" />
|
||||
<ClCompile Include="cheatcodeeditordialog.cpp" />
|
||||
<ClCompile Include="consolesettingswidget.cpp" />
|
||||
<ClCompile Include="enhancementsettingswidget.cpp" />
|
||||
<ClCompile Include="gamelistmodel.cpp" />
|
||||
|
@ -86,6 +88,8 @@
|
|||
<QtMoc Include="aboutdialog.h" />
|
||||
<QtMoc Include="audiosettingswidget.h" />
|
||||
<QtMoc Include="biossettingswidget.h" />
|
||||
<QtMoc Include="cheatmanagerdialog.h" />
|
||||
<QtMoc Include="cheatcodeeditordialog.h" />
|
||||
<QtMoc Include="controllersettingswidget.h" />
|
||||
<QtMoc Include="enhancementsettingswidget.h" />
|
||||
<QtMoc Include="memorycardsettingswidget.h" />
|
||||
|
@ -181,6 +185,12 @@
|
|||
<QtUi Include="memorycardeditordialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="cheatmanagerdialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
<QtUi Include="cheatcodeeditordialog.ui">
|
||||
<FileType>Document</FileType>
|
||||
</QtUi>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<QtResource Include="resources\resources.qrc">
|
||||
|
@ -193,6 +203,8 @@
|
|||
<ClCompile Include="$(IntDir)moc_autoupdaterdialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_advancedsettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_biossettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_cheatmanagerdialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_cheatcodeeditordialog.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_consolesettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_controllersettingswidget.cpp" />
|
||||
<ClCompile Include="$(IntDir)moc_enhancementsettingswidget.cpp" />
|
||||
|
|
|
@ -11,6 +11,7 @@ int main(int argc, char* argv[])
|
|||
{
|
||||
// Register any standard types we need elsewhere
|
||||
qRegisterMetaType<std::optional<bool>>();
|
||||
qRegisterMetaType<std::function<void()>>();
|
||||
|
||||
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "mainwindow.h"
|
||||
#include "aboutdialog.h"
|
||||
#include "autoupdaterdialog.h"
|
||||
#include "cheatmanagerdialog.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/host_display.h"
|
||||
#include "core/settings.h"
|
||||
|
@ -233,6 +234,12 @@ void MainWindow::onEmulationStopped()
|
|||
m_emulation_running = false;
|
||||
updateEmulationActions(false, false);
|
||||
switchToGameListView();
|
||||
|
||||
if (m_cheat_manager_dialog)
|
||||
{
|
||||
delete m_cheat_manager_dialog;
|
||||
m_cheat_manager_dialog = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onEmulationPaused(bool paused)
|
||||
|
@ -316,6 +323,8 @@ void MainWindow::onChangeDiscFromPlaylistMenuAboutToHide()
|
|||
void MainWindow::onCheatsMenuAboutToShow()
|
||||
{
|
||||
m_ui.menuCheats->clear();
|
||||
connect(m_ui.menuCheats->addAction(tr("Cheat Manager")), &QAction::triggered, this, &MainWindow::onToolsCheatManagerTriggered);
|
||||
m_ui.menuCheats->addSeparator();
|
||||
m_host_interface->populateCheatsMenu(m_ui.menuCheats);
|
||||
}
|
||||
|
||||
|
@ -645,6 +654,7 @@ void MainWindow::updateEmulationActions(bool starting, bool running)
|
|||
m_ui.actionViewSystemDisplay->setEnabled(starting || running);
|
||||
m_ui.menuChangeDisc->setDisabled(starting || !running);
|
||||
m_ui.menuCheats->setDisabled(starting || !running);
|
||||
m_ui.actionCheatManager->setDisabled(starting || !running);
|
||||
|
||||
m_ui.actionSaveState->setDisabled(starting || !running);
|
||||
m_ui.menuSaveState->setDisabled(starting || !running);
|
||||
|
@ -779,6 +789,7 @@ void MainWindow::connectSignals()
|
|||
connect(m_ui.actionAbout, &QAction::triggered, this, &MainWindow::onAboutActionTriggered);
|
||||
connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
|
||||
connect(m_ui.actionMemory_Card_Editor, &QAction::triggered, this, &MainWindow::onToolsMemoryCardEditorTriggered);
|
||||
connect(m_ui.actionCheatManager, &QAction::triggered, this, &MainWindow::onToolsCheatManagerTriggered);
|
||||
connect(m_ui.actionOpenDataDirectory, &QAction::triggered, this, &MainWindow::onToolsOpenDataDirectoryTriggered);
|
||||
connect(m_ui.actionGridViewShowTitles, &QAction::triggered, m_game_list_widget, &GameListWidget::setShowCoverTitles);
|
||||
connect(m_ui.actionGridViewZoomIn, &QAction::triggered, m_game_list_widget, [this]() {
|
||||
|
@ -1167,6 +1178,15 @@ void MainWindow::onToolsMemoryCardEditorTriggered()
|
|||
m_memory_card_editor_dialog->show();
|
||||
}
|
||||
|
||||
void MainWindow::onToolsCheatManagerTriggered()
|
||||
{
|
||||
if (!m_cheat_manager_dialog)
|
||||
m_cheat_manager_dialog = new CheatManagerDialog(this);
|
||||
|
||||
m_cheat_manager_dialog->setModal(false);
|
||||
m_cheat_manager_dialog->show();
|
||||
}
|
||||
|
||||
void MainWindow::onToolsOpenDataDirectoryTriggered()
|
||||
{
|
||||
QtUtils::OpenURL(this, QUrl::fromLocalFile(m_host_interface->getUserDirectoryRelativePath(QString())));
|
||||
|
|
|
@ -15,6 +15,7 @@ class QtHostInterface;
|
|||
class QtDisplayWidget;
|
||||
class AutoUpdaterDialog;
|
||||
class MemoryCardEditorDialog;
|
||||
class CheatManagerDialog;
|
||||
|
||||
class HostDisplay;
|
||||
struct GameListEntry;
|
||||
|
@ -76,6 +77,7 @@ private Q_SLOTS:
|
|||
void onAboutActionTriggered();
|
||||
void onCheckForUpdatesActionTriggered();
|
||||
void onToolsMemoryCardEditorTriggered();
|
||||
void onToolsCheatManagerTriggered();
|
||||
void onToolsOpenDataDirectoryTriggered();
|
||||
|
||||
void onGameListEntrySelected(const GameListEntry* entry);
|
||||
|
@ -127,6 +129,7 @@ private:
|
|||
SettingsDialog* m_settings_dialog = nullptr;
|
||||
AutoUpdaterDialog* m_auto_updater_dialog = nullptr;
|
||||
MemoryCardEditorDialog* m_memory_card_editor_dialog = nullptr;
|
||||
CheatManagerDialog* m_cheat_manager_dialog = nullptr;
|
||||
|
||||
bool m_emulation_running = false;
|
||||
};
|
||||
|
|
|
@ -205,6 +205,7 @@
|
|||
<string>&Tools</string>
|
||||
</property>
|
||||
<addaction name="actionMemory_Card_Editor"/>
|
||||
<addaction name="actionCheatManager"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="actionOpenDataDirectory"/>
|
||||
</widget>
|
||||
|
@ -712,6 +713,11 @@
|
|||
<string>Memory &Card Editor</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionCheatManager">
|
||||
<property name="text">
|
||||
<string>C&heat Manager</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionViewGameGrid">
|
||||
<property name="text">
|
||||
<string>Game &Grid</string>
|
||||
|
|
|
@ -971,7 +971,7 @@ void QtHostInterface::populateCheatsMenu(QMenu* menu)
|
|||
QAction* action = menu->addAction(tr("&Load Cheats..."));
|
||||
connect(action, &QAction::triggered, [this]() {
|
||||
QString filename = QFileDialog::getOpenFileName(m_main_window, tr("Select Cheat File"), QString(),
|
||||
tr("PCSXR/Libretro Cheat Files (*.cht);;All Files (*.*)"));
|
||||
tr("PCSXR/Libretro Cheat Files (*.cht *.txt);;All Files (*.*)"));
|
||||
if (!filename.isEmpty())
|
||||
loadCheatList(filename);
|
||||
});
|
||||
|
@ -980,7 +980,7 @@ void QtHostInterface::populateCheatsMenu(QMenu* menu)
|
|||
action->setEnabled(has_cheat_list);
|
||||
connect(action, &QAction::triggered, [this]() {
|
||||
QString filename = QFileDialog::getSaveFileName(m_main_window, tr("Select Cheat File"), QString(),
|
||||
tr("PCSXR/Libretro Cheat Files (*.cht);;All Files (*.*)"));
|
||||
tr("PCSXR Cheat Files (*.cht);;All Files (*.*)"));
|
||||
if (!filename.isEmpty())
|
||||
SaveCheatList(filename.toUtf8().constData());
|
||||
});
|
||||
|
@ -1052,6 +1052,28 @@ void QtHostInterface::reloadPostProcessingShaders()
|
|||
ReloadPostProcessingShaders();
|
||||
}
|
||||
|
||||
void QtHostInterface::executeOnEmulationThread(std::function<void()> callback, bool wait)
|
||||
{
|
||||
if (isOnWorkerThread())
|
||||
{
|
||||
callback();
|
||||
if (wait)
|
||||
m_worker_thread_sync_execute_done.Signal();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QMetaObject::invokeMethod(this, "executeOnEmulationThread", Qt::QueuedConnection,
|
||||
Q_ARG(std::function<void()>, callback), Q_ARG(bool, wait));
|
||||
if (wait)
|
||||
{
|
||||
// don't deadlock
|
||||
while (!m_worker_thread_sync_execute_done.TryWait(10))
|
||||
qApp->processEvents(QEventLoop::ExcludeSocketNotifiers);
|
||||
m_worker_thread_sync_execute_done.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void QtHostInterface::loadState(const QString& filename)
|
||||
{
|
||||
if (!isOnWorkerThread())
|
||||
|
|
|
@ -167,6 +167,7 @@ public Q_SLOTS:
|
|||
void setCheatEnabled(quint32 index, bool enabled);
|
||||
void applyCheat(quint32 index);
|
||||
void reloadPostProcessingShaders();
|
||||
void executeOnEmulationThread(std::function<void()> callback, bool wait = false);
|
||||
|
||||
private Q_SLOTS:
|
||||
void doStopThread();
|
||||
|
@ -254,6 +255,7 @@ private:
|
|||
QThread* m_original_thread = nullptr;
|
||||
Thread* m_worker_thread = nullptr;
|
||||
QEventLoop* m_worker_thread_event_loop = nullptr;
|
||||
Common::Event m_worker_thread_sync_execute_done;
|
||||
|
||||
std::atomic_bool m_shutdown_flag{false};
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <QtWidgets/QScrollBar>
|
||||
#include <QtWidgets/QStyle>
|
||||
#include <QtWidgets/QTableView>
|
||||
#include <QtWidgets/QTreeView>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <map>
|
||||
|
@ -44,10 +45,17 @@ QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog)
|
|||
return widget;
|
||||
}
|
||||
|
||||
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths)
|
||||
template<typename T>
|
||||
ALWAYS_INLINE_RELEASE static void ResizeColumnsForView(T* view, const std::initializer_list<int>& widths)
|
||||
{
|
||||
const int min_column_width = view->horizontalHeader()->minimumSectionSize();
|
||||
const int max_column_width = view->horizontalHeader()->maximumSectionSize();
|
||||
QHeaderView* header;
|
||||
if constexpr (std::is_same_v<T, QTableView>)
|
||||
header = view->horizontalHeader();
|
||||
else
|
||||
header = view->header();
|
||||
|
||||
const int min_column_width = header->minimumSectionSize();
|
||||
const int max_column_width = header->maximumSectionSize();
|
||||
const int total_width =
|
||||
std::accumulate(widths.begin(), widths.end(), 0, [&min_column_width, &max_column_width](int a, int b) {
|
||||
return a + ((b < 0) ? 0 : std::clamp(b, min_column_width, max_column_width));
|
||||
|
@ -74,6 +82,16 @@ void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int
|
|||
}
|
||||
}
|
||||
|
||||
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths)
|
||||
{
|
||||
ResizeColumnsForView(view, widths);
|
||||
}
|
||||
|
||||
void ResizeColumnsForTreeView(QTreeView* view, const std::initializer_list<int>& widths)
|
||||
{
|
||||
ResizeColumnsForView(view, widths);
|
||||
}
|
||||
|
||||
static const std::map<int, QString> s_qt_key_names = {
|
||||
{Qt::Key_Escape, QStringLiteral("Escape")},
|
||||
{Qt::Key_Tab, QStringLiteral("Tab")},
|
||||
|
|
|
@ -2,10 +2,12 @@
|
|||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QMetaType>
|
||||
#include <QtCore/QString>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <optional>
|
||||
|
||||
Q_DECLARE_METATYPE(std::optional<bool>);
|
||||
Q_DECLARE_METATYPE(std::function<void()>);
|
||||
|
||||
class ByteStream;
|
||||
|
||||
|
@ -13,6 +15,7 @@ class QComboBox;
|
|||
class QFrame;
|
||||
class QKeyEvent;
|
||||
class QTableView;
|
||||
class QTreeView;
|
||||
class QWidget;
|
||||
class QUrl;
|
||||
|
||||
|
@ -27,6 +30,7 @@ QWidget* GetRootWidget(QWidget* widget, bool stop_at_window_or_dialog = true);
|
|||
/// Resizes columns of the table view to at the specified widths. A negative width will stretch the column to use the
|
||||
/// remaining space.
|
||||
void ResizeColumnsForTableView(QTableView* view, const std::initializer_list<int>& widths);
|
||||
void ResizeColumnsForTreeView(QTreeView* view, const std::initializer_list<int>& widths);
|
||||
|
||||
/// Returns a string identifier for a Qt key ID.
|
||||
QString GetKeyIdentifier(int key);
|
||||
|
|
|
@ -2366,6 +2366,23 @@ bool CommonHostInterface::LoadCheatListFromGameTitle()
|
|||
return LoadCheatList(filename.c_str());
|
||||
}
|
||||
|
||||
bool CommonHostInterface::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()))
|
||||
{
|
||||
AddFormattedOSDMessage(15.0f, TranslateString("OSDMessage", "Failed to save cheat list to '%s'"), filename.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CommonHostInterface::SaveCheatList(const char* filename)
|
||||
{
|
||||
if (!System::IsValid() || !System::HasCheatList())
|
||||
|
@ -2404,17 +2421,7 @@ void CommonHostInterface::SetCheatCodeState(u32 index, bool enabled, bool save_t
|
|||
}
|
||||
|
||||
if (save_to_file)
|
||||
{
|
||||
const std::string filename(GetCheatFileName());
|
||||
if (!filename.empty())
|
||||
{
|
||||
if (!cl->SaveToPCSXRFile(filename.c_str()))
|
||||
{
|
||||
AddFormattedOSDMessage(15.0f, TranslateString("OSDMessage", "Failed to save cheat list to '%s'"),
|
||||
filename.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
SaveCheatList();
|
||||
}
|
||||
|
||||
void CommonHostInterface::ApplyCheatCode(u32 index)
|
||||
|
|
|
@ -159,6 +159,9 @@ public:
|
|||
/// Loads the cheat list for the current game title from the user directory.
|
||||
bool LoadCheatListFromGameTitle();
|
||||
|
||||
/// Saves the current cheat list to the game title's file.
|
||||
bool SaveCheatList();
|
||||
|
||||
/// Saves the current cheat list to the specified file.
|
||||
bool SaveCheatList(const char* filename);
|
||||
|
||||
|
|
Loading…
Reference in New Issue