From 57efbecb0fdf2b4f23b1d58871698ab10ac0caef Mon Sep 17 00:00:00 2001 From: aldelaro5 Date: Sun, 6 Aug 2017 17:59:58 -0400 Subject: [PATCH] Initial project Adds all the source files of the first version of the program. This first versions includes: - A fully functional Dolphin accessor for Windows and Linux - A fully functional memory scanner supporting every common types in 4 number base - A fully functionnal memory watcher with file saving support All this in a very clunky for now GUI and works in runtime as Dolphin runs a game. --- .gitignore | 14 + Source/.clang-format | 16 + Source/CMakeLists.txt | 37 ++ Source/Common/CommonTypes.h | 13 + Source/Common/CommonUtils.h | 51 ++ Source/Common/MemoryCommon.cpp | 474 ++++++++++++++++ Source/Common/MemoryCommon.h | 56 ++ Source/DolphinProcess/DolphinAccessor.cpp | 129 +++++ Source/DolphinProcess/DolphinAccessor.h | 37 ++ Source/DolphinProcess/IDolphinProcess.h | 36 ++ .../Linux/LinuxDolphinProcess.cpp | 190 +++++++ .../Linux/LinuxDolphinProcess.h | 26 + .../Windows/WindowsDolphinProcess.cpp | 151 +++++ .../Windows/WindowsDolphinProcess.h | 28 + Source/GUI/GUICommon.cpp | 51 ++ Source/GUI/GUICommon.h | 16 + Source/GUI/MainWindow.cpp | 251 +++++++++ Source/GUI/MainWindow.h | 54 ++ Source/GUI/MemScanner/MemScanWidget.cpp | 319 +++++++++++ Source/GUI/MemScanner/MemScanWidget.h | 61 ++ Source/GUI/MemScanner/ResultsListModel.cpp | 89 +++ Source/GUI/MemScanner/ResultsListModel.h | 35 ++ Source/GUI/MemWatcher/DlgAddWatchEntry.cpp | 331 +++++++++++ Source/GUI/MemWatcher/DlgAddWatchEntry.h | 47 ++ Source/GUI/MemWatcher/DlgChangeType.cpp | 80 +++ Source/GUI/MemWatcher/DlgChangeType.h | 24 + Source/GUI/MemWatcher/MemWatchDelegate.cpp | 33 ++ Source/GUI/MemWatcher/MemWatchDelegate.h | 11 + Source/GUI/MemWatcher/MemWatchModel.cpp | 499 +++++++++++++++++ Source/GUI/MemWatcher/MemWatchModel.h | 67 +++ Source/GUI/MemWatcher/MemWatchTreeNode.cpp | 222 ++++++++ Source/GUI/MemWatcher/MemWatchTreeNode.h | 44 ++ Source/GUI/MemWatcher/MemWatchWidget.cpp | 444 +++++++++++++++ Source/GUI/MemWatcher/MemWatchWidget.h | 46 ++ Source/MemoryScanner/MemoryScanner.cpp | 524 ++++++++++++++++++ Source/MemoryScanner/MemoryScanner.h | 158 ++++++ Source/MemoryWatch/MemoryWatch.cpp | 302 ++++++++++ Source/MemoryWatch/MemoryWatch.h | 67 +++ Source/main.cpp | 11 + Tools/format.sh | 2 + 40 files changed, 5046 insertions(+) create mode 100644 .gitignore create mode 100755 Source/.clang-format create mode 100755 Source/CMakeLists.txt create mode 100755 Source/Common/CommonTypes.h create mode 100755 Source/Common/CommonUtils.h create mode 100644 Source/Common/MemoryCommon.cpp create mode 100644 Source/Common/MemoryCommon.h create mode 100644 Source/DolphinProcess/DolphinAccessor.cpp create mode 100644 Source/DolphinProcess/DolphinAccessor.h create mode 100644 Source/DolphinProcess/IDolphinProcess.h create mode 100644 Source/DolphinProcess/Linux/LinuxDolphinProcess.cpp create mode 100644 Source/DolphinProcess/Linux/LinuxDolphinProcess.h create mode 100644 Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp create mode 100644 Source/DolphinProcess/Windows/WindowsDolphinProcess.h create mode 100755 Source/GUI/GUICommon.cpp create mode 100755 Source/GUI/GUICommon.h create mode 100644 Source/GUI/MainWindow.cpp create mode 100644 Source/GUI/MainWindow.h create mode 100644 Source/GUI/MemScanner/MemScanWidget.cpp create mode 100644 Source/GUI/MemScanner/MemScanWidget.h create mode 100644 Source/GUI/MemScanner/ResultsListModel.cpp create mode 100644 Source/GUI/MemScanner/ResultsListModel.h create mode 100644 Source/GUI/MemWatcher/DlgAddWatchEntry.cpp create mode 100644 Source/GUI/MemWatcher/DlgAddWatchEntry.h create mode 100644 Source/GUI/MemWatcher/DlgChangeType.cpp create mode 100644 Source/GUI/MemWatcher/DlgChangeType.h create mode 100644 Source/GUI/MemWatcher/MemWatchDelegate.cpp create mode 100644 Source/GUI/MemWatcher/MemWatchDelegate.h create mode 100644 Source/GUI/MemWatcher/MemWatchModel.cpp create mode 100644 Source/GUI/MemWatcher/MemWatchModel.h create mode 100644 Source/GUI/MemWatcher/MemWatchTreeNode.cpp create mode 100644 Source/GUI/MemWatcher/MemWatchTreeNode.h create mode 100644 Source/GUI/MemWatcher/MemWatchWidget.cpp create mode 100644 Source/GUI/MemWatcher/MemWatchWidget.h create mode 100644 Source/MemoryScanner/MemoryScanner.cpp create mode 100644 Source/MemoryScanner/MemoryScanner.h create mode 100644 Source/MemoryWatch/MemoryWatch.cpp create mode 100644 Source/MemoryWatch/MemoryWatch.h create mode 100644 Source/main.cpp create mode 100755 Tools/format.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..09a6c703 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +Source/[Bb]uild*/ +Source/[Bb]inary/ +Source/obj/ +Source/*.ipch +Source/*.opensdf +Source/*.sdf +Source/*.suo +Source/*.vcxproj.user +Source/*.obj +Source/*.tlog +Source/*.VC.opendb +Source/*.VC.db +Source/.vs/ +Source/.vscode/ diff --git a/Source/.clang-format b/Source/.clang-format new file mode 100755 index 00000000..1a9a233e --- /dev/null +++ b/Source/.clang-format @@ -0,0 +1,16 @@ +--- +AlignAfterOpenBracket: 'true' +AllowAllParametersOfDeclarationOnNextLine: 'true' +AllowShortFunctionsOnASingleLine: None +AlwaysBreakBeforeMultilineStrings: 'true' +AlwaysBreakTemplateDeclarations: 'true' +BreakBeforeBraces: Allman +BreakStringLiterals: 'true' +ColumnLimit: '100' +Cpp11BracedListStyle: 'true' +Language: Cpp +PointerAlignment: Left +Standard: Cpp11 +UseTab: Never + +... diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt new file mode 100755 index 00000000..29214535 --- /dev/null +++ b/Source/CMakeLists.txt @@ -0,0 +1,37 @@ +cmake_minimum_required(VERSION 3.5.0) +project(Dolphin-memory-engine) + +if(WIN32) + set(DolphinProcessSrc DolphinProcess/Windows/WindowsDolphinProcess.cpp) +endif(WIN32) + +if(UNIX) + set(DolphinProcessSrc DolphinProcess/Linux/LinuxDolphinProcess.cpp) +endif(UNIX) + +set(SRCS ${DolphinProcessSrc} + DolphinProcess/DolphinAccessor.cpp + Common/MemoryCommon.cpp + MemoryWatch/MemoryWatch.cpp + MemoryScanner/MemoryScanner.cpp + GUI/GUICommon.cpp + GUI/MemWatcher/MemWatchTreeNode.cpp + GUI/MemWatcher/MemWatchDelegate.cpp + GUI/MemWatcher/MemWatchModel.cpp + GUI/MemWatcher/DlgChangeType.cpp + GUI/MemWatcher/DlgAddWatchEntry.cpp + GUI/MemWatcher/MemWatchWidget.cpp + GUI/MemScanner/ResultsListModel.cpp + GUI/MemScanner/MemScanWidget.cpp + GUI/MainWindow.cpp + main.cpp) + +set(CMAKE_INCLUE_CURRENT_DIR ON) + +find_package(Qt5Widgets REQUIRED) + +set(CMAKE_AUTOMOC ON) + +add_executable(Dolphin-memory-engine ${SRCS}) + +target_link_libraries(Dolphin-memory-engine Qt5::Widgets) \ No newline at end of file diff --git a/Source/Common/CommonTypes.h b/Source/Common/CommonTypes.h new file mode 100755 index 00000000..e3909baf --- /dev/null +++ b/Source/Common/CommonTypes.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; + +typedef int64_t s64; +typedef int32_t s32; +typedef int16_t s16; +typedef int8_t s8; \ No newline at end of file diff --git a/Source/Common/CommonUtils.h b/Source/Common/CommonUtils.h new file mode 100755 index 00000000..3a6cca69 --- /dev/null +++ b/Source/Common/CommonUtils.h @@ -0,0 +1,51 @@ +#pragma once + +#ifdef __linux__ +#include +#elif _WIN32 +#include +#endif + +#include "CommonTypes.h" + +namespace Common +{ +#ifdef _WIN32 +inline u16 bSwap16(u16 data) +{ + return _byteswap_ushort(data); +} +inline u32 bSwap32(u32 data) +{ + return _byteswap_ulong(data); +} +inline u64 bSwap64(u64 data) +{ + return _byteswap_uint64(data); +} + +#elif __linux__ +inline u16 bSwap16(u16 data) +{ + return bswap_16(data); +} +inline u32 bSwap32(u32 data) +{ + return bswap_32(data); +} +inline u64 bSwap64(u64 data) +{ + return bswap_64(data); +} +#endif + +inline u32 dolphinAddrToOffset(u32 addr) +{ + return addr &= 0x7FFFFFFF; +} + +inline u32 offsetToDolphinAddr(u32 offset) +{ + return offset |= 0x80000000; +} +} diff --git a/Source/Common/MemoryCommon.cpp b/Source/Common/MemoryCommon.cpp new file mode 100644 index 00000000..73db42bb --- /dev/null +++ b/Source/Common/MemoryCommon.cpp @@ -0,0 +1,474 @@ +#include "MemoryCommon.h" + +#include +#include +#include +#include +#include + +#include "../Common/CommonTypes.h" +#include "../Common/CommonUtils.h" + +namespace Common +{ +size_t getSizeForType(const MemType type, const size_t length) +{ + switch (type) + { + case MemType::type_byte: + return sizeof(u8); + case MemType::type_halfword: + return sizeof(u16); + case MemType::type_word: + return sizeof(u32); + case MemType::type_float: + return sizeof(float); + case MemType::type_double: + return sizeof(double); + case MemType::type_string: + return length; + case MemType::type_byteArray: + return length; + default: + return 0; + } +} + +bool shouldBeBSwappedForType(const MemType type) +{ + switch (type) + { + case MemType::type_byte: + return false; + case MemType::type_halfword: + return true; + case MemType::type_word: + return true; + case MemType::type_float: + return true; + case MemType::type_double: + return true; + case MemType::type_string: + return false; + case MemType::type_byteArray: + return false; + default: + return false; + } +} + +char* formatStringToMemory(MemOperationReturnCode& returnCode, size_t& actualLength, + const std::string inputString, const MemBase base, const MemType type, + const size_t length) +{ + std::stringstream ss(inputString); + switch (base) + { + case MemBase::base_octal: + { + ss >> std::oct; + break; + } + case MemBase::base_decimal: + { + ss >> std::dec; + break; + } + case MemBase::base_hexadecimal: + { + ss >> std::hex; + break; + } + } + + char* buffer = new char[getSizeForType(type, length)]; + + switch (type) + { + case MemType::type_byte: + { + u8 theByte = 0; + if (base == MemBase::base_binary) + { + unsigned long long input = 0; + try + { + input = std::bitset(inputString).to_ullong(); + } + catch (std::invalid_argument) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + theByte = static_cast(input); + } + else + { + int theByteInt = 0; + ss >> theByteInt; + if (ss.fail()) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + theByte = static_cast(theByteInt); + } + + std::memcpy(buffer, &theByte, sizeof(u8)); + actualLength = sizeof(u8); + break; + } + + case MemType::type_halfword: + { + u16 theHalfword = 0; + if (base == MemBase::base_binary) + { + unsigned long long input = 0; + try + { + input = std::bitset(inputString).to_ullong(); + } + catch (std::invalid_argument) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + theHalfword = static_cast(input); + } + else + { + ss >> theHalfword; + if (ss.fail()) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + } + + std::memcpy(buffer, &theHalfword, sizeof(u16)); + actualLength = sizeof(u16); + break; + } + + case MemType::type_word: + { + u32 theWord = 0; + if (base == MemBase::base_binary) + { + unsigned long long input = 0; + try + { + input = std::bitset(inputString).to_ullong(); + } + catch (std::invalid_argument) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + theWord = static_cast(input); + } + else + { + ss >> theWord; + if (ss.fail()) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + } + + std::memcpy(buffer, &theWord, sizeof(u32)); + actualLength = sizeof(u32); + break; + } + + case MemType::type_float: + { + float theFloat = 0.0f; + // 9 digits is the max number of digits in a flaot that can recover any binary format + ss >> std::setprecision(9) >> theFloat; + if (ss.fail()) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + std::memcpy(buffer, &theFloat, sizeof(float)); + actualLength = sizeof(float); + break; + } + + case MemType::type_double: + { + double theDouble = 0.0; + // 17 digits is the max number of digits in a double that can recover any binary format + ss >> std::setprecision(17) >> theDouble; + if (ss.fail()) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + std::memcpy(buffer, &theDouble, sizeof(double)); + actualLength = sizeof(double); + break; + } + + case MemType::type_string: + { + if (inputString.length() > length) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::inputTooLong; + return buffer; + } + std::memcpy(buffer, inputString.c_str(), length); + actualLength = length; + break; + } + + case MemType::type_byteArray: + { + std::vector bytes; + std::string next; + for (auto i : inputString) + { + if (i == ' ') + { + if (!next.empty()) + { + bytes.push_back(next); + next.clear(); + } + } + else + { + next += i; + } + } + if (!next.empty()) + { + bytes.push_back(next); + next.clear(); + } + + if (bytes.size() > length) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::inputTooLong; + return buffer; + } + + int index = 0; + for (auto i : bytes) + { + std::stringstream byteStream(i); + ss >> std::hex; + u8 theByte = 0; + int theByteInt = 0; + ss >> theByteInt; + if (ss.fail()) + { + delete[] buffer; + buffer = nullptr; + returnCode = MemOperationReturnCode::invalidInput; + return buffer; + } + theByte = static_cast(theByteInt); + std::memcpy(&(buffer[index]), &theByte, sizeof(u8)); + index++; + } + actualLength = bytes.size(); + } + } + return buffer; +} + +std::string formatMemoryToString(const char* memory, const MemType type, const size_t length, + const MemBase base, const bool isUnsigned, const bool withBSwap) +{ + std::stringstream ss; + switch (base) + { + case Common::MemBase::base_octal: + { + ss << std::oct; + break; + } + case Common::MemBase::base_decimal: + { + ss << std::dec; + break; + } + case Common::MemBase::base_hexadecimal: + { + ss << std::hex << std::uppercase; + break; + } + } + + switch (type) + { + case Common::MemType::type_byte: + { + if ((isUnsigned && base == Common::MemBase::base_decimal) || + base == Common::MemBase::base_binary) + { + u8 unsignedByte = 0; + std::memcpy(&unsignedByte, memory, sizeof(u8)); + if (base == Common::MemBase::base_binary) + { + return std::bitset(unsignedByte).to_string(); + } + ss << static_cast(unsignedByte); + return ss.str(); + } + s8 aByte = 0; + std::memcpy(&aByte, memory, sizeof(s8)); + ss << static_cast(aByte); + return ss.str(); + } + case Common::MemType::type_halfword: + { + char* memoryCopy = new char[sizeof(u16)]; + std::memcpy(memoryCopy, memory, sizeof(u16)); + if (withBSwap) + { + u16 halfword = 0; + std::memcpy(&halfword, memoryCopy, sizeof(u16)); + halfword = Common::bSwap16(halfword); + std::memcpy(memoryCopy, &halfword, sizeof(u16)); + } + + if ((isUnsigned && base == Common::MemBase::base_decimal) || + base == Common::MemBase::base_binary) + { + u16 unsignedHalfword = 0; + std::memcpy(&unsignedHalfword, memoryCopy, sizeof(u16)); + if (base == Common::MemBase::base_binary) + { + delete[] memoryCopy; + return std::bitset(unsignedHalfword).to_string(); + } + ss << unsignedHalfword; + delete[] memoryCopy; + return ss.str(); + } + s16 aHalfword = 0; + std::memcpy(&aHalfword, memoryCopy, sizeof(s16)); + ss << aHalfword; + delete[] memoryCopy; + return ss.str(); + } + case Common::MemType::type_word: + { + char* memoryCopy = new char[sizeof(u32)]; + std::memcpy(memoryCopy, memory, sizeof(u32)); + if (withBSwap) + { + u32 word = 0; + std::memcpy(&word, memoryCopy, sizeof(u32)); + word = Common::bSwap32(word); + std::memcpy(memoryCopy, &word, sizeof(u32)); + } + + if ((isUnsigned && base == Common::MemBase::base_decimal) || + base == Common::MemBase::base_binary) + { + u32 unsignedWord = 0; + std::memcpy(&unsignedWord, memoryCopy, sizeof(u32)); + if (base == Common::MemBase::base_binary) + { + delete[] memoryCopy; + return std::bitset(unsignedWord).to_string(); + } + ss << unsignedWord; + delete[] memoryCopy; + return ss.str(); + } + s32 aWord = 0; + std::memcpy(&aWord, memoryCopy, sizeof(s32)); + ss << aWord; + delete[] memoryCopy; + return ss.str(); + } + case Common::MemType::type_float: + { + char* memoryCopy = new char[sizeof(u32)]; + std::memcpy(memoryCopy, memory, sizeof(u32)); + if (withBSwap) + { + u32 word = 0; + std::memcpy(&word, memoryCopy, sizeof(u32)); + word = Common::bSwap32(word); + std::memcpy(memoryCopy, &word, sizeof(u32)); + } + + float aFloat = 0.0f; + std::memcpy(&aFloat, memoryCopy, sizeof(float)); + // With 9 digits of precision, it is possible to convert a float back and forth to its binary + // representation without any loss + ss << std::setprecision(9) << aFloat; + delete[] memoryCopy; + return ss.str(); + } + case Common::MemType::type_double: + { + char* memoryCopy = new char[sizeof(u64)]; + std::memcpy(memoryCopy, memory, sizeof(u64)); + if (withBSwap) + { + u64 doubleword = 0; + std::memcpy(&doubleword, memoryCopy, sizeof(u64)); + doubleword = Common::bSwap64(doubleword); + std::memcpy(memoryCopy, &doubleword, sizeof(u64)); + } + + double aDouble = 0.0; + std::memcpy(&aDouble, memoryCopy, sizeof(double)); + // With 17 digits of precision, it is possible to convert a double back and forth to its binary + // representation without any loss + ss << std::setprecision(17) << aDouble; + delete[] memoryCopy; + return ss.str(); + } + case Common::MemType::type_string: + { + return std::string(memory, length); + } + case Common::MemType::type_byteArray: + { + // Force Hexadecimal, no matter the base + ss << std::hex << std::uppercase; + for (int i = 0; i < length; ++i) + { + u8 aByte = 0; + std::memcpy(&aByte, memory + i, sizeof(u8)); + ss << std::setfill('0') << std::setw(2) << static_cast(aByte) << " "; + } + std::string test = ss.str(); + return ss.str(); + } + default: + return ""; + break; + } +} +} diff --git a/Source/Common/MemoryCommon.h b/Source/Common/MemoryCommon.h new file mode 100644 index 00000000..39d6ac23 --- /dev/null +++ b/Source/Common/MemoryCommon.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include "CommonTypes.h" + +namespace Common +{ +const u32 MEM1_SIZE = 0x1800000; +const u32 MEM1_START = 0x80000000; +const u32 MEM1_END = 0x81800000; + +const u32 MEM2_SIZE = 0x4000000; +const u32 MEM2_START = 0x90000000; +const u32 MEM2_END = 0x94000000; + +enum class MemType +{ + type_byte = 0, + type_halfword, + type_word, + type_float, + type_double, + type_string, + type_byteArray, + type_num +}; + +enum class MemBase +{ + base_decimal = 0, + base_hexadecimal, + base_octal, + base_binary, + base_none // Placeholder when the base doesn't matter (ie. string) +}; + +enum class MemOperationReturnCode +{ + invalidInput, + operationFailed, + inputTooLong, + invalidPointer, + OK +}; + +size_t getSizeForType(const MemType type, const size_t length); +bool shouldBeBSwappedForType(const MemType type); +char* formatStringToMemory(MemOperationReturnCode& returnCode, size_t& actualLength, + const std::string inputString, const MemBase base, const MemType type, + const size_t length); +std::string formatMemoryToString(const char* memory, const MemType type, const size_t length, + const MemBase base, const bool isUsigned, + const bool withBSwap = false); +} diff --git a/Source/DolphinProcess/DolphinAccessor.cpp b/Source/DolphinProcess/DolphinAccessor.cpp new file mode 100644 index 00000000..c7650d53 --- /dev/null +++ b/Source/DolphinProcess/DolphinAccessor.cpp @@ -0,0 +1,129 @@ +#include "DolphinAccessor.h" +#ifdef linux +#include "Linux/LinuxDolphinProcess.h" +#elif _WIN32 +#include "Windows/WindowsDolphinProcess.h" +#endif + +#include "../Common/MemoryCommon.h" + +namespace DolphinComm +{ +IDolphinProcess* DolphinAccessor::m_instance = nullptr; +DolphinAccessor::DolphinStatus DolphinAccessor::m_status = DolphinStatus::unHooked; +bool DolphinAccessor::m_mem2Enabled = false; + +void DolphinAccessor::hook() +{ + if (m_instance == nullptr) + { +#ifdef __linux__ + m_instance = new LinuxDolphinProcess(); +#elif _WIN32 + m_instance = new WindowsDolphinProcess(); +#endif + } + + if (!m_instance->findPID()) + { + m_status = DolphinStatus::notRunning; + } + else if (!m_instance->findEmuRAMStartAddress()) + { + m_status = DolphinStatus::noEmu; + } + else + { + m_status = DolphinStatus::hooked; + } +} + +void DolphinAccessor::unHook() +{ + if (m_instance != nullptr) + delete m_instance; + m_instance = nullptr; + m_status = DolphinStatus::unHooked; +} + +DolphinAccessor::DolphinStatus DolphinAccessor::getStatus() +{ + return m_status; +} + +bool DolphinAccessor::readFromRAM(const u32 offset, char* buffer, const size_t size, + const bool withBSwap) +{ + return m_instance->readFromRAM(offset, buffer, size, withBSwap); +} + +bool DolphinAccessor::writeToRAM(const u32 offset, const char* buffer, const size_t size, + const bool withBSwap) +{ + return m_instance->writeToRAM(offset, buffer, size, withBSwap); +} + +int DolphinAccessor::getPID() +{ + return m_instance->getPID(); +} + +u64 DolphinAccessor::getEmuRAMAddressStart() +{ + return m_instance->getEmuRAMAddressStart(); +} + +void DolphinAccessor::enableMem2(const bool doEnable) +{ + m_mem2Enabled = doEnable; +} + +bool DolphinAccessor::isMem2Enabled() +{ + return m_mem2Enabled; +} + +void DolphinAccessor::autoDetectMem2() +{ + if (getStatus() == DolphinStatus::hooked) + { + char gameIdFirstChar = ' '; + if (readFromRAM(0, &gameIdFirstChar, 1, false)) + { + switch (gameIdFirstChar) + { + case 'G': // Gamecube disc + case 'P': // Promotional games, guaranteed to be Gamecube + m_mem2Enabled = false; + break; + + case 'R': // Wii disc, can be prototypes, but unlikely + case 'S': // Later Wii disc + m_mem2Enabled = true; + break; + + // If there's no ID, likely to be a Wiiware, but this isn't guaranteed, could also be D, but + // this one is present on both, so let's just leave MEM2 enabled for these by default, the + // user can be the judge here, these are extremely unlikely cases anyway + default: + m_mem2Enabled = true; + break; + } + } + else + { + unHook(); + } + } +} + +bool DolphinAccessor::isValidConsoleAddress(const u32 address) +{ + bool isMem1Address = address >= Common::MEM1_START && address < Common::MEM1_END; + if (m_mem2Enabled) + { + return isMem1Address || (address >= Common::MEM2_START && address < Common::MEM2_END); + } + return isMem1Address; +} +} diff --git a/Source/DolphinProcess/DolphinAccessor.h b/Source/DolphinProcess/DolphinAccessor.h new file mode 100644 index 00000000..c83fe551 --- /dev/null +++ b/Source/DolphinProcess/DolphinAccessor.h @@ -0,0 +1,37 @@ +// Wrapper around IDolphinProcess +#pragma once + +#include "IDolphinProcess.h" + +namespace DolphinComm +{ +class DolphinAccessor +{ +public: + enum class DolphinStatus + { + hooked, + notRunning, + noEmu, + unHooked + }; + + static void hook(); + static void unHook(); + static bool readFromRAM(const u32 offset, char* buffer, const size_t size, const bool withBSwap); + static bool writeToRAM(const u32 offset, const char* buffer, const size_t size, + const bool withBSwap); + static int getPID(); + static u64 getEmuRAMAddressStart(); + static DolphinStatus getStatus(); + static void enableMem2(const bool doEnable); + static bool isMem2Enabled(); + static void autoDetectMem2(); + static bool isValidConsoleAddress(const u32 address); + +private: + static IDolphinProcess* m_instance; + static DolphinStatus m_status; + static bool m_mem2Enabled; +}; +} \ No newline at end of file diff --git a/Source/DolphinProcess/IDolphinProcess.h b/Source/DolphinProcess/IDolphinProcess.h new file mode 100644 index 00000000..e272623a --- /dev/null +++ b/Source/DolphinProcess/IDolphinProcess.h @@ -0,0 +1,36 @@ +// Interface to perform operations in Dolphin's process +#pragma once + +#include + +#include "../Common/CommonTypes.h" + +namespace DolphinComm +{ +class IDolphinProcess +{ +public: + virtual ~IDolphinProcess() + { + } + virtual bool findPID() = 0; + virtual bool findEmuRAMStartAddress() = 0; + virtual bool readFromRAM(const u32 offset, char* buffer, const size_t size, + const bool withBSwap) = 0; + virtual bool writeToRAM(const u32 offset, const char* buffer, const size_t size, + const bool withBSwap) = 0; + + int getPID() const + { + return m_PID; + }; + u64 getEmuRAMAddressStart() const + { + return m_emuRAMAddressStart; + }; + +protected: + int m_PID = -1; + u64 m_emuRAMAddressStart = 0; +}; +} \ No newline at end of file diff --git a/Source/DolphinProcess/Linux/LinuxDolphinProcess.cpp b/Source/DolphinProcess/Linux/LinuxDolphinProcess.cpp new file mode 100644 index 00000000..c7d3df63 --- /dev/null +++ b/Source/DolphinProcess/Linux/LinuxDolphinProcess.cpp @@ -0,0 +1,190 @@ +#ifdef __linux__ + +#include "LinuxDolphinProcess.h" +#include "../../Common/CommonUtils.h" + +#include +#include +#include +#include +#include +#include + +namespace DolphinComm +{ +bool LinuxDolphinProcess::findEmuRAMStartAddress() +{ + std::ifstream theMapsFile("/proc/" + std::to_string(m_PID) + "/maps"); + std::string line; + while (getline(theMapsFile, line)) + { + if (line.length() > 73) + { + if (line.substr(73, 19) == "/dev/shm/dolphinmem") + { + u64 firstAddress = 0; + u64 SecondAddress = 0; + std::string firstAddressStr("0x" + line.substr(0, 12)); + std::string secondAddressStr("0x" + line.substr(13, 12)); + + firstAddress = std::stoul(firstAddressStr, nullptr, 16); + SecondAddress = std::stoul(secondAddressStr, nullptr, 16); + + u64 test = SecondAddress - firstAddress; + + if (SecondAddress - firstAddress == 0x2000000) + { + m_emuRAMAddressStart = firstAddress; + break; + } + } + } + } + + if (m_emuRAMAddressStart != 0) + return true; + + // Here, Dolphin appears to be running, but the emulator isn't started + return false; +} + +bool LinuxDolphinProcess::findPID() +{ + DIR* directoryPointer = opendir("/proc/"); + if (directoryPointer == nullptr) + { + return false; + } + struct dirent* directoryEntry = nullptr; + while (m_PID == -1 && (directoryEntry = readdir(directoryPointer))) + { + std::istringstream conversionStream(directoryEntry->d_name); + int aPID = 0; + if (!(conversionStream >> aPID)) + continue; + std::ifstream aCmdLineFile; + std::string line; + aCmdLineFile.open("/proc/" + std::string(directoryEntry->d_name) + "/comm"); + getline(aCmdLineFile, line); + if (line == "dolphin-emu") + { + m_PID = aPID; + } + aCmdLineFile.close(); + } + closedir(directoryPointer); + + if (m_PID == -1) + { + // Here, Dolphin apparently isn't running on the system + return false; + } + return true; +} + +bool LinuxDolphinProcess::readFromRAM(const u32 offset, char* buffer, const size_t size, + const bool withBSwap) +{ + struct iovec local; + struct iovec remote; + size_t nread; + u64 RAMAddress = m_emuRAMAddressStart + offset; + + local.iov_base = buffer; + local.iov_len = size; + remote.iov_base = (void*)RAMAddress; + remote.iov_len = size; + + nread = process_vm_readv(m_PID, &local, 1, &remote, 1, 0); + if (nread != size) + return false; + + if (withBSwap) + { + switch (size) + { + case 2: + { + u16 halfword = 0; + std::memcpy(&halfword, buffer, sizeof(u16)); + halfword = Common::bSwap16(halfword); + std::memcpy(buffer, &halfword, sizeof(u16)); + break; + } + case 4: + { + u32 word = 0; + std::memcpy(&word, buffer, sizeof(u32)); + word = Common::bSwap32(word); + std::memcpy(buffer, &word, sizeof(u32)); + break; + } + case 8: + { + u64 doubleword = 0; + std::memcpy(&doubleword, buffer, sizeof(u64)); + doubleword = Common::bSwap64(doubleword); + std::memcpy(buffer, &doubleword, sizeof(u64)); + break; + } + } + } + + return true; +} + +bool LinuxDolphinProcess::writeToRAM(const u32 offset, const char* buffer, const size_t size, + const bool withBSwap) +{ + struct iovec local; + struct iovec remote; + size_t nwrote; + u64 RAMAddress = m_emuRAMAddressStart + offset; + char* bufferCopy = new char[size]; + std::memcpy(bufferCopy, buffer, size); + + local.iov_base = bufferCopy; + local.iov_len = size; + remote.iov_base = (void*)RAMAddress; + remote.iov_len = size; + + if (withBSwap) + { + switch (size) + { + case 2: + { + u16 halfword = 0; + std::memcpy(&halfword, bufferCopy, sizeof(u16)); + halfword = Common::bSwap16(halfword); + std::memcpy(bufferCopy, &halfword, sizeof(u16)); + break; + } + case 4: + { + u32 word = 0; + std::memcpy(&word, bufferCopy, sizeof(u32)); + word = Common::bSwap32(word); + std::memcpy(bufferCopy, &word, sizeof(u32)); + break; + } + case 8: + { + u64 doubleword = 0; + std::memcpy(&doubleword, bufferCopy, sizeof(u64)); + doubleword = Common::bSwap64(doubleword); + std::memcpy(bufferCopy, &doubleword, sizeof(u64)); + break; + } + } + } + + nwrote = process_vm_writev(m_PID, &local, 1, &remote, 1, 0); + delete[] bufferCopy; + if (nwrote != size) + return false; + + return true; +} +} +#endif diff --git a/Source/DolphinProcess/Linux/LinuxDolphinProcess.h b/Source/DolphinProcess/Linux/LinuxDolphinProcess.h new file mode 100644 index 00000000..6a4ee215 --- /dev/null +++ b/Source/DolphinProcess/Linux/LinuxDolphinProcess.h @@ -0,0 +1,26 @@ +#ifdef __linux__ + +#pragma once + +#include +#include +#include + +#include "../IDolphinProcess.h" + +namespace DolphinComm +{ +class LinuxDolphinProcess : public IDolphinProcess +{ +public: + LinuxDolphinProcess() + { + } + bool findPID() override; + bool findEmuRAMStartAddress() override; + bool readFromRAM(const u32 offset, char* buffer, size_t size, const bool withBSwap) override; + bool writeToRAM(const u32 offset, const char* buffer, const size_t size, + const bool withBSwap) override; +}; +} +#endif \ No newline at end of file diff --git a/Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp b/Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp new file mode 100644 index 00000000..4b635d35 --- /dev/null +++ b/Source/DolphinProcess/Windows/WindowsDolphinProcess.cpp @@ -0,0 +1,151 @@ +#ifdef _WIN32 + +#include "WindowsDolphinProcess.h" +#include "../../Common/CommonUtils.h" + +#include +#include + +namespace DolphinComm +{ +bool WindowsDolphinProcess::findPID() +{ + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); + + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + if (Process32First(snapshot, &entry) == TRUE) + { + do + { + if (std::string(entry.szExeFile) == "Dolphin.exe") + { + m_PID = entry.th32ProcessID; + break; + } + } while (Process32Next(snapshot, &entry) == TRUE); + } + + CloseHandle(snapshot); + if (m_PID == -1) + { + // Here, Dolphin doesn't appear to be running on the system + return false; + } + + // Get the handle if Dolphin is running since it's required on Windows to read or write into the + // RAM of the process and to query the RAM mapping information + m_hDolphin = m_hDolphin = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | + PROCESS_VM_READ | PROCESS_VM_WRITE, + FALSE, m_PID); + return true; +} + +bool WindowsDolphinProcess::findEmuRAMStartAddress() +{ + MEMORY_BASIC_INFORMATION info; + for (unsigned char* p = nullptr; + VirtualQueryEx(m_hDolphin, p, &info, sizeof(info)) == sizeof(info); p += info.RegionSize) + { + if (info.RegionSize == 0x2000000) + { + std::memcpy(&m_emuRAMAddressStart, &(info.BaseAddress), sizeof(info.BaseAddress)); + break; + } + } + if (m_emuRAMAddressStart == 0) + { + // Here, Dolphin is running, but the emulation hasn't started + return false; + } + return true; +} + +bool WindowsDolphinProcess::readFromRAM(const u32 offset, char* buffer, const size_t size, + const bool withBSwap) +{ + u64 RAMAddress = m_emuRAMAddressStart + offset; + SIZE_T nread = 0; + bool bResult = ReadProcessMemory(m_hDolphin, (void*)RAMAddress, buffer, size, &nread); + if (bResult && nread == size) + { + if (withBSwap) + { + switch (size) + { + case 2: + { + u16 halfword = 0; + std::memcpy(&halfword, buffer, sizeof(u16)); + halfword = Common::bSwap16(halfword); + std::memcpy(buffer, &halfword, sizeof(u16)); + break; + } + case 4: + { + u32 word = 0; + std::memcpy(&word, buffer, sizeof(u32)); + word = Common::bSwap32(word); + std::memcpy(buffer, &word, sizeof(u32)); + break; + } + case 8: + { + u64 doubleword = 0; + std::memcpy(&doubleword, buffer, sizeof(u64)); + doubleword = Common::bSwap64(doubleword); + std::memcpy(buffer, &doubleword, sizeof(u64)); + break; + } + } + } + return true; + } + return false; +} + +bool WindowsDolphinProcess::writeToRAM(const u32 offset, const char* buffer, const size_t size, + const bool withBSwap) +{ + u64 RAMAddress = m_emuRAMAddressStart + offset; + SIZE_T nread = 0; + char* bufferCopy = new char[size]; + std::memcpy(bufferCopy, buffer, size); + if (withBSwap) + { + switch (size) + { + case 2: + { + u16 halfword = 0; + std::memcpy(&halfword, bufferCopy, sizeof(u16)); + halfword = Common::bSwap16(halfword); + std::memcpy(bufferCopy, &halfword, sizeof(u16)); + break; + } + case 4: + { + u32 word = 0; + std::memcpy(&word, bufferCopy, sizeof(u32)); + word = Common::bSwap32(word); + std::memcpy(bufferCopy, &word, sizeof(u32)); + break; + } + case 8: + { + u64 doubleword = 0; + std::memcpy(&doubleword, bufferCopy, sizeof(u64)); + doubleword = Common::bSwap64(doubleword); + std::memcpy(bufferCopy, &doubleword, sizeof(u64)); + break; + } + } + } + + bool bResult = WriteProcessMemory(m_hDolphin, (void*)RAMAddress, bufferCopy, size, &nread); + delete[] bufferCopy; + return (bResult && nread == size); +} +} +#endif diff --git a/Source/DolphinProcess/Windows/WindowsDolphinProcess.h b/Source/DolphinProcess/Windows/WindowsDolphinProcess.h new file mode 100644 index 00000000..00b02348 --- /dev/null +++ b/Source/DolphinProcess/Windows/WindowsDolphinProcess.h @@ -0,0 +1,28 @@ +#ifdef _WIN32 + +#pragma once + +#include + +#include "../IDolphinProcess.h" + +namespace DolphinComm +{ +class WindowsDolphinProcess : public IDolphinProcess +{ +public: + WindowsDolphinProcess() + { + } + bool findPID() override; + bool findEmuRAMStartAddress() override; + bool readFromRAM(const u32 offset, char* buffer, const size_t size, + const bool withBSwap) override; + bool writeToRAM(const u32 offset, const char* buffer, const size_t size, + const bool withBSwap) override; + +private: + HANDLE m_hDolphin; +}; +} +#endif diff --git a/Source/GUI/GUICommon.cpp b/Source/GUI/GUICommon.cpp new file mode 100755 index 00000000..bd8c50c4 --- /dev/null +++ b/Source/GUI/GUICommon.cpp @@ -0,0 +1,51 @@ +#include "GUICommon.h" + +#include + +namespace GUICommon +{ +QStringList g_memTypeNames = QStringList({"Byte", "2 bytes (Halfword)", "4 bytes (Word)", "Float", + "Double", "String", "Array of bytes"}); + +QStringList g_memBaseNames = QStringList({"Decimal", "Hexadecimal", "Octal", "Binary"}); + +QStringList g_memScanFilter = QStringList({"Exact value", "Increased by", "Decreased by", "Between", + "Bigger than", "Smaller than", "Increased", "Decreased", + "Changed", "Unchanged", "Unknown initial value"}); + +QString getStringFromType(const Common::MemType type, const size_t length) +{ + switch (type) + { + case Common::MemType::type_byte: + case Common::MemType::type_halfword: + case Common::MemType::type_word: + case Common::MemType::type_float: + case Common::MemType::type_double: + return GUICommon::g_memTypeNames.at(static_cast(type)); + case Common::MemType::type_string: + return QString::fromStdString("string[" + std::to_string(length) + "]"); + case Common::MemType::type_byteArray: + return QString::fromStdString("array of bytes[" + std::to_string(length) + "]"); + default: + return QString(""); + } +} + +QString getNameFromBase(const Common::MemBase base) +{ + switch (base) + { + case Common::MemBase::base_binary: + return QString("binary"); + case Common::MemBase::base_octal: + return QString("octal"); + case Common::MemBase::base_decimal: + return QString("decimal"); + case Common::MemBase::base_hexadecimal: + return QString("hexadecimal"); + default: + return QString(""); + } +} +} \ No newline at end of file diff --git a/Source/GUI/GUICommon.h b/Source/GUI/GUICommon.h new file mode 100755 index 00000000..de5ba46a --- /dev/null +++ b/Source/GUI/GUICommon.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#include "../Common/MemoryCommon.h" + +namespace GUICommon +{ +extern QStringList g_memTypeNames; +extern QStringList g_memScanFilter; +extern QStringList g_memBaseNames; + +QString getStringFromType(const Common::MemType type, const size_t length = 0); +QString getNameFromBase(const Common::MemBase base); +} \ No newline at end of file diff --git a/Source/GUI/MainWindow.cpp b/Source/GUI/MainWindow.cpp new file mode 100644 index 00000000..997373dc --- /dev/null +++ b/Source/GUI/MainWindow.cpp @@ -0,0 +1,251 @@ +#include "MainWindow.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "../DolphinProcess/DolphinAccessor.h" +#include "../MemoryWatch/MemoryWatch.h" + +MainWindow::MainWindow() +{ + m_scanner = new MemScanWidget(this); + connect(m_scanner, + static_cast( + &MemScanWidget::requestAddWatchEntry), + this, static_cast( + &MainWindow::addWatchRequested)); + m_watcher = new MemWatchWidget(this); + + m_btnAttempHook = new QPushButton("Hook"); + m_btnUnhook = new QPushButton("Unhook"); + connect(m_btnAttempHook, static_cast(&QPushButton::clicked), this, + &MainWindow::onHookAttempt); + connect(m_btnUnhook, static_cast(&QPushButton::clicked), this, + &MainWindow::onUnhook); + + QHBoxLayout* dolphinHookButtons_layout = new QHBoxLayout(); + dolphinHookButtons_layout->addWidget(m_btnAttempHook); + dolphinHookButtons_layout->addWidget(m_btnUnhook); + + m_lblDolphinStatus = new QLabel(""); + + m_lblMem2Status = new QLabel("MEM2 is disabled"); + m_btnMem2AutoDetect = new QPushButton("Auto detect"); + m_btnToggleMem2 = new QPushButton("Toggle MEM2"); + connect(m_btnMem2AutoDetect, static_cast(&QPushButton::clicked), + this, &MainWindow::onAutoDetectMem2); + connect(m_btnToggleMem2, static_cast(&QPushButton::clicked), this, + &MainWindow::onToggleMem2); + + QHBoxLayout* mem2Buttons_layout = new QHBoxLayout(); + mem2Buttons_layout->addWidget(m_btnMem2AutoDetect); + mem2Buttons_layout->addWidget(m_btnToggleMem2); + + QVBoxLayout* mem2Status_layout = new QVBoxLayout(); + mem2Status_layout->addWidget(m_lblMem2Status); + mem2Status_layout->addLayout(mem2Buttons_layout); + + m_mem2StatusWidget = new QWidget(); + m_mem2StatusWidget->setLayout(mem2Status_layout); + + QVBoxLayout* main_layout = new QVBoxLayout; + main_layout->addWidget(m_lblDolphinStatus); + main_layout->addLayout(dolphinHookButtons_layout); + main_layout->addWidget(m_mem2StatusWidget); + main_layout->addWidget(m_scanner); + main_layout->addWidget(m_watcher); + + QWidget* main_widget = new QWidget(this); + main_widget->setLayout(main_layout); + setCentralWidget(main_widget); + + m_actOpenWatchList = new QAction("&Open...", this); + m_actSaveWatchList = new QAction("&Save", this); + m_actSaveAsWatchList = new QAction("&Save as...", this); + + m_actQuit = new QAction("&Quit", this); + m_actAbout = new QAction("&About", this); + connect(m_actOpenWatchList, &QAction::triggered, this, &MainWindow::onOpenWatchFile); + connect(m_actSaveWatchList, &QAction::triggered, this, &MainWindow::onSaveWatchFile); + connect(m_actSaveAsWatchList, &QAction::triggered, this, &MainWindow::onSaveAsWatchFile); + connect(m_actQuit, &QAction::triggered, this, &MainWindow::onQuit); + connect(m_actAbout, &QAction::triggered, this, &MainWindow::onAbout); + + m_menuFile = menuBar()->addMenu("&File"); + m_menuFile->addAction(m_actOpenWatchList); + m_menuFile->addAction(m_actSaveWatchList); + m_menuFile->addAction(m_actSaveAsWatchList); + m_menuFile->addAction(m_actQuit); + m_menuHelp = menuBar()->addMenu("&Help"); + m_menuHelp->addAction(m_actAbout); + + connect(m_scanner, &MemScanWidget::mustUnhook, this, &MainWindow::onUnhook); + connect(m_watcher, &MemWatchWidget::mustUnhook, this, &MainWindow::onUnhook); + + // First attempt to hook + onHookAttempt(); + if (DolphinComm::DolphinAccessor::getStatus() == + DolphinComm::DolphinAccessor::DolphinStatus::hooked) + { + DolphinComm::DolphinAccessor::autoDetectMem2(); + updateMem2Status(); + } +} + +void MainWindow::addWatchRequested(u32 address, Common::MemType type, size_t length, + bool isUnsigned, Common::MemBase base) +{ + MemWatchEntry* newEntry = new MemWatchEntry("No label", address, type, base, isUnsigned, length); + m_watcher->addWatchEntry(newEntry); +} + +void MainWindow::onAutoDetectMem2() +{ + DolphinComm::DolphinAccessor::autoDetectMem2(); + updateDolphinHookingStatus(); + if (DolphinComm::DolphinAccessor::getStatus() == + DolphinComm::DolphinAccessor::DolphinStatus::hooked) + updateMem2Status(); + else + onUnhook(); +} + +void MainWindow::onToggleMem2() +{ + if (DolphinComm::DolphinAccessor::isMem2Enabled()) + DolphinComm::DolphinAccessor::enableMem2(false); + else + DolphinComm::DolphinAccessor::enableMem2(true); + updateMem2Status(); +} + +void MainWindow::updateMem2Status() +{ + if (DolphinComm::DolphinAccessor::isMem2Enabled()) + m_lblMem2Status->setText("MEM2 is enabled"); + else + m_lblMem2Status->setText("MEM2 is disabled"); +} + +void MainWindow::updateDolphinHookingStatus() +{ + switch (DolphinComm::DolphinAccessor::getStatus()) + { + case DolphinComm::DolphinAccessor::DolphinStatus::hooked: + { + m_lblDolphinStatus->setText( + "Hooked sucessfully to Dolphin, current start address: " + + QString::number(DolphinComm::DolphinAccessor::getEmuRAMAddressStart(), 16).toUpper()); + m_scanner->setEnabled(true); + m_watcher->setEnabled(true); + m_btnAttempHook->hide(); + m_btnUnhook->show(); + break; + } + case DolphinComm::DolphinAccessor::DolphinStatus::notRunning: + { + m_lblDolphinStatus->setText("Cannot hook to Dolphin, the process is not running"); + m_scanner->setDisabled(true); + m_watcher->setDisabled(true); + m_btnAttempHook->show(); + m_btnUnhook->hide(); + break; + } + case DolphinComm::DolphinAccessor::DolphinStatus::noEmu: + { + m_lblDolphinStatus->setText( + "Cannot hook to Dolphin, the process is running, but no emulation has been started"); + m_scanner->setDisabled(true); + m_watcher->setDisabled(true); + m_btnAttempHook->show(); + m_btnUnhook->hide(); + break; + } + case DolphinComm::DolphinAccessor::DolphinStatus::unHooked: + { + m_lblDolphinStatus->setText("Unhooked, press \"Hook\" to hook to Dolphin again"); + m_scanner->setDisabled(true); + m_watcher->setDisabled(true); + m_btnAttempHook->show(); + m_btnUnhook->hide(); + break; + } + } +} + +void MainWindow::onHookAttempt() +{ + DolphinComm::DolphinAccessor::hook(); + updateDolphinHookingStatus(); + if (DolphinComm::DolphinAccessor::getStatus() == + DolphinComm::DolphinAccessor::DolphinStatus::hooked) + { + m_scanner->getUpdateTimer()->start(100); + m_watcher->getUpdateTimer()->start(10); + m_watcher->getFreezeTimer()->start(10); + m_mem2StatusWidget->setEnabled(true); + } + else + { + m_mem2StatusWidget->setDisabled(true); + } +} + +void MainWindow::onUnhook() +{ + m_scanner->getUpdateTimer()->stop(); + m_watcher->getUpdateTimer()->stop(); + m_watcher->getFreezeTimer()->stop(); + m_mem2StatusWidget->setDisabled(true); + DolphinComm::DolphinAccessor::unHook(); + updateDolphinHookingStatus(); +} + +void MainWindow::onOpenWatchFile() +{ + if (m_watcher->warnIfUnsavedChanges()) + m_watcher->openWatchFile(); +} + +void MainWindow::onSaveWatchFile() +{ + m_watcher->saveWatchFile(); +} + +void MainWindow::onSaveAsWatchFile() +{ + m_watcher->saveAsWatchFile(); +} + +void MainWindow::onAbout() +{ + QMessageBox::about(this, "About Dolphin memory engine", + "Preview version 0.1.0\n\nA RAM search made to facilitate research and " + "reverse engineering of Gamecube and Wii games using the Dolphin " + "emulator.\n\nThis program is licensed under the MIT license. You " + "should have received a copy of the MIT license along with this program"); +} + +void MainWindow::onQuit() +{ + close(); +} + +void MainWindow::closeEvent(QCloseEvent* event) +{ + if (m_watcher->warnIfUnsavedChanges()) + { + event->accept(); + } + else + { + event->ignore(); + } +} \ No newline at end of file diff --git a/Source/GUI/MainWindow.h b/Source/GUI/MainWindow.h new file mode 100644 index 00000000..97073255 --- /dev/null +++ b/Source/GUI/MainWindow.h @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +#include "../Common/CommonTypes.h" +#include "../Common/MemoryCommon.h" +#include "MemScanner/MemScanWidget.h" +#include "MemWatcher/MemWatchWidget.h" + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + void closeEvent(QCloseEvent* event) override; + void addWatchRequested(u32 address, Common::MemType type, size_t length, bool isUnsigned, + Common::MemBase base); + void updateDolphinHookingStatus(); + void onHookAttempt(); + void onUnhook(); + void onAutoDetectMem2(); + void onToggleMem2(); + void updateMem2Status(); + + void onOpenWatchFile(); + void onSaveWatchFile(); + void onSaveAsWatchFile(); + void onAbout(); + void onQuit(); + +private: + MemWatchWidget* m_watcher; + MemScanWidget* m_scanner; + + QLabel* m_lblDolphinStatus; + QPushButton* m_btnAttempHook; + QPushButton* m_btnUnhook; + QLabel* m_lblMem2Status; + QPushButton* m_btnToggleMem2; + QPushButton* m_btnMem2AutoDetect; + QWidget* m_mem2StatusWidget; + + QMenu* m_menuFile; + QMenu* m_menuHelp; + QAction* m_actOpenWatchList; + QAction* m_actSaveWatchList; + QAction* m_actSaveAsWatchList; + QAction* m_actQuit; + QAction* m_actAbout; +}; \ No newline at end of file diff --git a/Source/GUI/MemScanner/MemScanWidget.cpp b/Source/GUI/MemScanner/MemScanWidget.cpp new file mode 100644 index 00000000..9f32f08e --- /dev/null +++ b/Source/GUI/MemScanner/MemScanWidget.cpp @@ -0,0 +1,319 @@ +#include "MemScanWidget.h" + +#include +#include +#include +#include +#include + +#include "../GUICommon.h" + +MemScanWidget::MemScanWidget(QWidget* parent) : QWidget(parent) +{ + m_memScanner = new MemScanner(); + + m_resultsListModel = new ResultsListModel(this, m_memScanner); + + m_lblResultCount = new QLabel(""); + m_tblResulstList = new QTableView(); + m_tblResulstList->setModel(m_resultsListModel); + m_tblResulstList->setSelectionBehavior(QAbstractItemView::SelectRows); + m_tblResulstList->setSelectionMode(QAbstractItemView::ExtendedSelection); + connect(m_tblResulstList, static_cast( + &QAbstractItemView::doubleClicked), + this, static_cast( + &MemScanWidget::onResultListDoubleClicked)); + + QVBoxLayout* results_layout = new QVBoxLayout(); + results_layout->addWidget(m_lblResultCount); + results_layout->addWidget(m_tblResulstList); + + m_btnFirstScan = new QPushButton("First scan"); + m_btnNextScan = new QPushButton("Next scan"); + m_btnNextScan->hide(); + m_btnResetScan = new QPushButton("Reset scan"); + m_btnResetScan->hide(); + + connect(m_btnFirstScan, static_cast(&QPushButton::clicked), this, + &MemScanWidget::onFirstScan); + connect(m_btnNextScan, static_cast(&QPushButton::clicked), this, + &MemScanWidget::onNextScan); + connect(m_btnResetScan, static_cast(&QPushButton::clicked), this, + &MemScanWidget::onResetScan); + + QHBoxLayout* buttons_layout = new QHBoxLayout(); + buttons_layout->addWidget(m_btnFirstScan); + buttons_layout->addWidget(m_btnNextScan); + buttons_layout->addWidget(m_btnResetScan); + + m_txbSearchTerm1 = new QLineEdit(); + QLabel* lblAnd = new QLabel("and"); + m_txbSearchTerm2 = new QLineEdit(); + + QHBoxLayout* searchTerm2_layout = new QHBoxLayout(); + searchTerm2_layout->addWidget(lblAnd); + searchTerm2_layout->addWidget(m_txbSearchTerm2); + + m_searchTerm2Widget = new QWidget(); + m_searchTerm2Widget->setLayout(searchTerm2_layout); + + QHBoxLayout* searchTerms_layout = new QHBoxLayout(); + searchTerms_layout->addWidget(m_txbSearchTerm1); + searchTerms_layout->addWidget(m_searchTerm2Widget); + m_searchTerm2Widget->hide(); + + m_cmbScanType = new QComboBox(); + m_cmbScanType->addItems(GUICommon::g_memTypeNames); + m_cmbScanType->setCurrentIndex(0); + connect(m_cmbScanType, static_cast(&QComboBox::currentIndexChanged), + this, &MemScanWidget::onScanMemTypeChanged); + m_variableLengthType = false; + + m_cmbScanFilter = new QComboBox(); + updateScanFilterChoices(); + connect(m_cmbScanFilter, static_cast(&QComboBox::currentIndexChanged), + this, &MemScanWidget::onScanFilterChanged); + + QRadioButton* rdbBaseDecimal = new QRadioButton("Decimal"); + QRadioButton* rdbBaseHexadecimal = new QRadioButton("Hexadecimal"); + QRadioButton* rdbBaseOctal = new QRadioButton("Octal"); + QRadioButton* rdbBaseBinary = new QRadioButton("Binary"); + + m_btnGroupScanBase = new QButtonGroup(); + m_btnGroupScanBase->addButton(rdbBaseDecimal, 0); + m_btnGroupScanBase->addButton(rdbBaseHexadecimal, 1); + m_btnGroupScanBase->addButton(rdbBaseOctal, 2); + m_btnGroupScanBase->addButton(rdbBaseBinary, 3); + rdbBaseDecimal->setChecked(true); + + QHBoxLayout* layout_buttonsBase = new QHBoxLayout(); + layout_buttonsBase->addWidget(rdbBaseDecimal); + layout_buttonsBase->addWidget(rdbBaseHexadecimal); + layout_buttonsBase->addWidget(rdbBaseOctal); + layout_buttonsBase->addWidget(rdbBaseBinary); + + m_groupScanBase = new QGroupBox("Base to use"); + m_groupScanBase->setLayout(layout_buttonsBase); + + m_chkSignedScan = new QCheckBox("Signed value scan"); + m_chkSignedScan->setChecked(false); + + QVBoxLayout* scanner_layout = new QVBoxLayout(); + scanner_layout->addLayout(buttons_layout); + scanner_layout->addWidget(m_cmbScanType); + scanner_layout->addWidget(m_cmbScanFilter); + scanner_layout->addLayout(searchTerms_layout); + scanner_layout->addWidget(m_groupScanBase); + scanner_layout->addWidget(m_chkSignedScan); + + QHBoxLayout* main_layout = new QHBoxLayout(); + main_layout->addLayout(results_layout); + main_layout->addLayout(scanner_layout); + + setLayout(main_layout); + + m_currentValuesUpdateTimer = new QTimer(this); + connect(m_currentValuesUpdateTimer, &QTimer::timeout, this, + &MemScanWidget::onCurrentValuesUpdateTimer); +} + +MemScanWidget::~MemScanWidget() +{ + delete m_memScanner; +} + +MemScanner::ScanFiter MemScanWidget::getSelectedFilter() const +{ + int index = + GUICommon::g_memScanFilter.indexOf(QRegExp("^" + m_cmbScanFilter->currentText() + "$")); + return static_cast(index); +} + +void MemScanWidget::updateScanFilterChoices() +{ + Common::MemType newType = static_cast(m_cmbScanType->currentIndex()); + m_cmbScanFilter->clear(); + if (newType == Common::MemType::type_byteArray || newType == Common::MemType::type_string) + { + m_cmbScanFilter->addItem( + GUICommon::g_memScanFilter.at(static_cast(MemScanner::ScanFiter::exact))); + } + else if (m_memScanner->hasScanStarted()) + { + m_cmbScanFilter->addItems(GUICommon::g_memScanFilter); + m_cmbScanFilter->removeItem(static_cast(MemScanner::ScanFiter::unknownInitial)); + } + else + { + m_cmbScanFilter->addItem( + GUICommon::g_memScanFilter.at(static_cast(MemScanner::ScanFiter::exact))); + m_cmbScanFilter->addItem( + GUICommon::g_memScanFilter.at(static_cast(MemScanner::ScanFiter::between))); + m_cmbScanFilter->addItem( + GUICommon::g_memScanFilter.at(static_cast(MemScanner::ScanFiter::biggerThan))); + m_cmbScanFilter->addItem( + GUICommon::g_memScanFilter.at(static_cast(MemScanner::ScanFiter::smallerThan))); + m_cmbScanFilter->addItem( + GUICommon::g_memScanFilter.at(static_cast(MemScanner::ScanFiter::unknownInitial))); + } + m_cmbScanFilter->setCurrentIndex(0); +} + +void MemScanWidget::updateTypeAdditionalOptions() +{ + if (m_memScanner->typeSupportsAdditionalOptions( + static_cast(m_cmbScanType->currentIndex()))) + { + m_chkSignedScan->show(); + m_groupScanBase->show(); + } + else + { + m_chkSignedScan->hide(); + m_groupScanBase->hide(); + } +} + +void MemScanWidget::onScanFilterChanged() +{ + MemScanner::ScanFiter theFilter = getSelectedFilter(); + int numTerms = m_memScanner->getTermsNumForFilter(theFilter); + switch (numTerms) + { + case 0: + m_txbSearchTerm1->hide(); + m_searchTerm2Widget->hide(); + m_chkSignedScan->hide(); + m_groupScanBase->hide(); + break; + case 1: + m_txbSearchTerm1->show(); + m_searchTerm2Widget->hide(); + updateTypeAdditionalOptions(); + break; + case 2: + m_txbSearchTerm1->show(); + m_searchTerm2Widget->show(); + updateTypeAdditionalOptions(); + break; + } +} + +void MemScanWidget::onScanMemTypeChanged() +{ + Common::MemType newType = static_cast(m_cmbScanType->currentIndex()); + if (!m_variableLengthType && + (newType == Common::MemType::type_string || newType == Common::MemType::type_byteArray)) + { + updateScanFilterChoices(); + m_variableLengthType = true; + } + else if (m_variableLengthType && newType != Common::MemType::type_string && + newType != Common::MemType::type_byteArray) + { + updateScanFilterChoices(); + m_variableLengthType = false; + } + + updateTypeAdditionalOptions(); +} + +void MemScanWidget::onFirstScan() +{ + m_memScanner->setType(static_cast(m_cmbScanType->currentIndex())); + m_memScanner->setIsSigned(m_chkSignedScan->isChecked()); + m_memScanner->setBase(static_cast(m_btnGroupScanBase->checkedId())); + Common::MemOperationReturnCode scannerReturn = + m_memScanner->firstScan(getSelectedFilter(), m_txbSearchTerm1->text().toStdString(), + m_txbSearchTerm2->text().toStdString()); + if (scannerReturn != Common::MemOperationReturnCode::OK) + { + handleScannerErrors(scannerReturn); + } + else + { + m_lblResultCount->setText( + QString::number(m_memScanner->getResultCount()).append(" result(s) found")); + m_btnFirstScan->hide(); + m_btnNextScan->show(); + m_btnResetScan->show(); + m_cmbScanType->setDisabled(true); + m_chkSignedScan->setDisabled(true); + m_groupScanBase->setDisabled(true); + updateScanFilterChoices(); + } +} + +void MemScanWidget::onNextScan() +{ + Common::MemOperationReturnCode scannerReturn = + m_memScanner->nextScan(getSelectedFilter(), m_txbSearchTerm1->text().toStdString(), + m_txbSearchTerm2->text().toStdString()); + if (scannerReturn != Common::MemOperationReturnCode::OK) + { + handleScannerErrors(scannerReturn); + } + else + { + m_lblResultCount->setText( + QString::number(m_memScanner->getResultCount()).append(" result(s) found")); + } +} + +void MemScanWidget::onResetScan() +{ + m_memScanner->reset(); + m_lblResultCount->setText(""); + m_btnFirstScan->show(); + m_btnNextScan->hide(); + m_btnResetScan->hide(); + m_cmbScanType->setEnabled(true); + m_chkSignedScan->setEnabled(true); + m_groupScanBase->setEnabled(true); + m_resultsListModel->updateAfterScannerReset(); + updateScanFilterChoices(); +} + +void MemScanWidget::handleScannerErrors(const Common::MemOperationReturnCode errorCode) +{ + if (errorCode == Common::MemOperationReturnCode::invalidInput) + { + QMessageBox* errorBox = + new QMessageBox(QMessageBox::Critical, "Invalid term(s)", + QString("The search term(s) you entered for the type " + + m_cmbScanType->currentText() + " is/are invalid"), + QMessageBox::Ok, this); + errorBox->exec(); + } + else if (errorCode == Common::MemOperationReturnCode::operationFailed) + { + emit mustUnhook(); + } +} + +void MemScanWidget::onCurrentValuesUpdateTimer() +{ + if (m_memScanner->getResultCount() > 0 && m_memScanner->getResultCount() <= 1000) + { + Common::MemOperationReturnCode updateReturn = m_resultsListModel->updateScannerCurrentCache(); + if (updateReturn != Common::MemOperationReturnCode::OK) + { + handleScannerErrors(updateReturn); + } + } +} + +QTimer* MemScanWidget::getUpdateTimer() const +{ + return m_currentValuesUpdateTimer; +} + +void MemScanWidget::onResultListDoubleClicked(const QModelIndex& index) +{ + if (index != QVariant()) + { + emit requestAddWatchEntry(m_resultsListModel->getResultAddress(index.row()), + m_memScanner->getType(), m_memScanner->getLength(), + m_memScanner->getIsUnsigned(), m_memScanner->getBase()); + } +} \ No newline at end of file diff --git a/Source/GUI/MemScanner/MemScanWidget.h b/Source/GUI/MemScanner/MemScanWidget.h new file mode 100644 index 00000000..bf355d23 --- /dev/null +++ b/Source/GUI/MemScanner/MemScanWidget.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ResultsListModel.h" + +class MemScanWidget : public QWidget +{ + Q_OBJECT + +public: + MemScanWidget(QWidget* parent); + ~MemScanWidget(); + + void onScanFilterChanged(); + void onScanMemTypeChanged(); + void onCurrentValuesUpdateTimer(); + void onResultListDoubleClicked(const QModelIndex& index); + void handleScannerErrors(const Common::MemOperationReturnCode errorCode); + void onFirstScan(); + void onNextScan(); + void onResetScan(); + QTimer* getUpdateTimer() const; + +signals: + void requestAddWatchEntry(u32 address, Common::MemType type, size_t length, bool isUnsigned, + Common::MemBase base); + void mustUnhook(); + +private: + MemScanner::ScanFiter getSelectedFilter() const; + void updateScanFilterChoices(); + void updateTypeAdditionalOptions(); + + MemScanner* m_memScanner; + ResultsListModel* m_resultsListModel; + QPushButton* m_btnFirstScan; + QPushButton* m_btnNextScan; + QPushButton* m_btnResetScan; + QLineEdit* m_txbSearchTerm1; + QLineEdit* m_txbSearchTerm2; + QWidget* m_searchTerm2Widget; + QTimer* m_currentValuesUpdateTimer; + QComboBox* m_cmbScanFilter; + QComboBox* m_cmbScanType; + QLabel* m_lblResultCount; + QCheckBox* m_chkSignedScan; + QButtonGroup* m_btnGroupScanBase; + QGroupBox* m_groupScanBase; + QTableView* m_tblResulstList; + bool m_variableLengthType; +}; \ No newline at end of file diff --git a/Source/GUI/MemScanner/ResultsListModel.cpp b/Source/GUI/MemScanner/ResultsListModel.cpp new file mode 100644 index 00000000..7aebc184 --- /dev/null +++ b/Source/GUI/MemScanner/ResultsListModel.cpp @@ -0,0 +1,89 @@ +#include "ResultsListModel.h" + +ResultsListModel::ResultsListModel(QObject* parent, MemScanner* scanner) + : QAbstractTableModel(parent), m_scanner(scanner) +{ +} + +ResultsListModel::~ResultsListModel() +{ +} + +int ResultsListModel::columnCount(const QModelIndex& parent) const +{ + return RESULT_COL_NUM; +} + +int ResultsListModel::rowCount(const QModelIndex& parent) const +{ + if (m_scanner->getResultCount() > 1000) + return 0; + return static_cast(m_scanner->getResultCount()); +} + +QVariant ResultsListModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + if (role == Qt::DisplayRole) + { + switch (index.column()) + { + case RESULT_COL_ADDRESS: + { + return QString::number(m_scanner->getResultsConsoleAddr().at(index.row()), 16).toUpper(); + break; + } + case RESULT_COL_SCANNED: + { + return QString::fromStdString(m_scanner->getFormattedScannedValueAt(index.row())); + break; + } + case RESULT_COL_CURRENT: + { + return QString::fromStdString(m_scanner->getFormattedCurrentValueAt(index.row())); + break; + } + } + } + return QVariant(); +} + +u32 ResultsListModel::getResultAddress(const int row) const +{ + return m_scanner->getResultsConsoleAddr().at(row); +} + +QVariant ResultsListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case RESULT_COL_ADDRESS: + return QString("Address"); + break; + case RESULT_COL_SCANNED: + return QString("Scanned"); + break; + case RESULT_COL_CURRENT: + return QString("Current"); + break; + } + } + return QVariant(); +} + +Common::MemOperationReturnCode ResultsListModel::updateScannerCurrentCache() +{ + Common::MemOperationReturnCode updateReturn = m_scanner->updateCurrentRAMCache(); + if (updateReturn == Common::MemOperationReturnCode::OK) + emit layoutChanged(); + return updateReturn; +} + +void ResultsListModel::updateAfterScannerReset() +{ + emit layoutChanged(); +} \ No newline at end of file diff --git a/Source/GUI/MemScanner/ResultsListModel.h b/Source/GUI/MemScanner/ResultsListModel.h new file mode 100644 index 00000000..b09349fc --- /dev/null +++ b/Source/GUI/MemScanner/ResultsListModel.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include "../../MemoryScanner/MemoryScanner.h" + +class ResultsListModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + enum + { + RESULT_COL_ADDRESS = 0, + RESULT_COL_SCANNED, + RESULT_COL_CURRENT, + RESULT_COL_NUM + }; + + ResultsListModel(QObject* parent, MemScanner* scanner); + ~ResultsListModel(); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + u32 getResultAddress(const int row) const; + Common::MemOperationReturnCode updateScannerCurrentCache(); + void updateAfterScannerReset(); + +private: + MemScanner* m_scanner; +}; \ No newline at end of file diff --git a/Source/GUI/MemWatcher/DlgAddWatchEntry.cpp b/Source/GUI/MemWatcher/DlgAddWatchEntry.cpp new file mode 100644 index 00000000..a39770b3 --- /dev/null +++ b/Source/GUI/MemWatcher/DlgAddWatchEntry.cpp @@ -0,0 +1,331 @@ +#include "DlgAddWatchEntry.h" + +#include +#include +#include +#include +#include + +#include "../../DolphinProcess/DolphinAccessor.h" +#include "../GUICommon.h" + +DlgAddWatchEntry::DlgAddWatchEntry(MemWatchEntry* entry) : m_entry(entry) +{ + m_chkBoundToPointer = new QCheckBox("This is a pointer", this); + connect(m_chkBoundToPointer, static_cast(&QCheckBox::stateChanged), + this, &DlgAddWatchEntry::onIsPointerChanged); + + QLabel* lblPreview = new QLabel("Value preview: "); + m_lblValuePreview = new QLabel("", this); + QHBoxLayout* preview_layout = new QHBoxLayout; + preview_layout->addWidget(lblPreview); + preview_layout->addWidget(m_lblValuePreview); + QWidget* preview_widget = new QWidget(); + preview_widget->setLayout(preview_layout); + + QLabel* lblAddress = new QLabel("Address: ", this); + m_txbAddress = new QLineEdit(this); + QHBoxLayout* layout_addressAndPreview = new QHBoxLayout; + layout_addressAndPreview->addWidget(lblAddress); + layout_addressAndPreview->addWidget(m_txbAddress); + QWidget* addressWidget = new QWidget(this); + addressWidget->setLayout(layout_addressAndPreview); + connect(m_txbAddress, static_cast(&QLineEdit::textEdited), + this, &DlgAddWatchEntry::onAddressChanged); + + m_offsetsLayout = new QFormLayout; + m_offsetsWidgets = QVector(); + QWidget* offsetsWidget = new QWidget(); + offsetsWidget->setLayout(m_offsetsLayout); + + QLabel* lblOffset = new QLabel("Offsets (in hex): ", this); + + m_btnAddOffset = new QPushButton("Add offset"); + m_btnRemoveOffset = new QPushButton("Remove offset"); + QHBoxLayout* pointerButtons_layout = new QHBoxLayout; + pointerButtons_layout->addWidget(m_btnAddOffset); + pointerButtons_layout->addWidget(m_btnRemoveOffset); + QWidget* pointerButtons_widget = new QWidget(); + pointerButtons_widget->setLayout(pointerButtons_layout); + connect(m_btnAddOffset, static_cast(&QPushButton::clicked), this, + &DlgAddWatchEntry::addPointerOffset); + connect(m_btnRemoveOffset, static_cast(&QPushButton::clicked), this, + &DlgAddWatchEntry::removePointerOffset); + + QVBoxLayout* pointerOffset_layout = new QVBoxLayout; + pointerOffset_layout->addWidget(lblOffset); + pointerOffset_layout->addWidget(offsetsWidget); + pointerOffset_layout->addWidget(pointerButtons_widget); + m_pointerWidget = new QWidget(this); + m_pointerWidget->setLayout(pointerOffset_layout); + + QLabel* lblLabel = new QLabel("Label: ", this); + m_txbLabel = new QLineEdit(this); + QHBoxLayout* layout_label = new QHBoxLayout; + layout_label->addWidget(lblLabel); + layout_label->addWidget(m_txbLabel); + QWidget* labelWidget = new QWidget(this); + labelWidget->setLayout(layout_label); + + QLabel* lblType = new QLabel("Type: ", this); + m_cmbTypes = new QComboBox(this); + m_cmbTypes->addItems(GUICommon::g_memTypeNames); + connect(m_cmbTypes, static_cast(&QComboBox::currentIndexChanged), this, + &DlgAddWatchEntry::onTypeChange); + QHBoxLayout* layout_type = new QHBoxLayout; + layout_type->addWidget(lblType); + layout_type->addWidget(m_cmbTypes); + QWidget* typeWidget = new QWidget(this); + typeWidget->setLayout(layout_type); + + QLabel* lblLength = new QLabel(QString("Length: "), this); + m_spnLength = new QSpinBox(this); + m_spnLength->setMinimum(1); + QHBoxLayout* layout_length = new QHBoxLayout; + layout_length->addWidget(lblLength); + layout_length->addWidget(m_spnLength); + m_lengtWidget = new QWidget; + m_lengtWidget->setLayout(layout_length); + connect(m_spnLength, static_cast(&QSpinBox::valueChanged), this, + &DlgAddWatchEntry::onLengthChanged); + + QDialogButtonBox* buttonBox = + new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + QVBoxLayout* main_layout = new QVBoxLayout; + main_layout->addWidget(preview_widget); + main_layout->addWidget(m_chkBoundToPointer); + main_layout->addWidget(addressWidget); + main_layout->addWidget(m_pointerWidget); + main_layout->addWidget(labelWidget); + main_layout->addWidget(typeWidget); + main_layout->addWidget(m_lengtWidget); + main_layout->addWidget(buttonBox); + setLayout(main_layout); + + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgAddWatchEntry::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgAddWatchEntry::reject); + + if (entry == nullptr) + { + m_entry = new MemWatchEntry(); + m_entry->setLength(1); + + m_cmbTypes->setCurrentIndex(0); + m_spnLength->setValue(1); + m_lengtWidget->hide(); + m_lblValuePreview->setText("???"); + m_chkBoundToPointer->setChecked(false); + m_pointerWidget->hide(); + + addPointerOffset(); + } + else + { + m_cmbTypes->setCurrentIndex(static_cast(m_entry->getType())); + m_spnLength->setValue(static_cast(m_entry->getLength())); + if (m_entry->getType() == Common::MemType::type_string || + m_entry->getType() == Common::MemType::type_byteArray) + m_lengtWidget->show(); + else + m_lengtWidget->hide(); + m_txbLabel->setText(QString::fromStdString(m_entry->getLabel())); + std::stringstream ssAddress; + ssAddress << std::hex << std::uppercase << m_entry->getConsoleAddress(); + m_txbAddress->setText(QString::fromStdString(ssAddress.str())); + m_chkBoundToPointer->setChecked(m_entry->isBoundToPointer()); + if (entry->isBoundToPointer()) + { + m_pointerWidget->show(); + for (int i = 0; i < m_entry->getPointerLevel(); ++i) + { + std::stringstream ssOffset; + ssOffset << std::hex << std::uppercase << m_entry->getPointerOffset(i); + QLineEdit* lineEdit = new QLineEdit(); + lineEdit->setText(QString::fromStdString(ssOffset.str())); + m_offsetsWidgets.append(lineEdit); + QLabel* label = new QLabel(QString::fromStdString("Level " + std::to_string(i + 1) + ":")); + m_offsetsLayout->addRow(label, lineEdit); + } + } + else + { + m_pointerWidget->hide(); + addPointerOffset(); + } + updateValuePreview(); + } +} + +void DlgAddWatchEntry::addPointerOffset() +{ + QLineEdit* lineEdit = new QLineEdit(); + lineEdit->setText(0); + m_offsetsWidgets.append(lineEdit); + int level = m_offsetsLayout->rowCount(); + QLabel* label = new QLabel(QString::fromStdString("Level " + std::to_string(level + 1) + ":")); + m_offsetsLayout->addRow(label, lineEdit); + m_entry->addOffset(0); + connect(lineEdit, static_cast(&QLineEdit::textEdited), this, + &DlgAddWatchEntry::onOffsetChanged); + if (m_offsetsLayout->rowCount() > 1) + m_btnRemoveOffset->setEnabled(true); + else + m_btnRemoveOffset->setDisabled(true); +} + +void DlgAddWatchEntry::removePointerOffset() +{ + m_offsetsLayout->removeRow(m_offsetsLayout->rowCount() - 1); + m_offsetsWidgets.removeLast(); + m_entry->removeOffset(); + updateValuePreview(); + if (m_offsetsLayout->rowCount() == 1) + m_btnRemoveOffset->setDisabled(true); +} + +void DlgAddWatchEntry::onOffsetChanged() +{ + QLineEdit* theLineEdit = static_cast(sender()); + int index = 0; + QFormLayout::ItemRole itemRole; + m_offsetsLayout->getWidgetPosition(theLineEdit, &index, &itemRole); + if (validateAndSetOffset(index)) + { + updateValuePreview(); + } +} + +void DlgAddWatchEntry::onTypeChange(int index) +{ + Common::MemType theType = static_cast(index); + if (theType == Common::MemType::type_string || theType == Common::MemType::type_byteArray) + m_lengtWidget->show(); + else + m_lengtWidget->hide(); + m_entry->setType(theType); + if (validateAndSetAddress()) + updateValuePreview(); +} + +void DlgAddWatchEntry::accept() +{ + if (!validateAndSetAddress()) + { + QString errorMsg = QString( + "The address you entered is invalid, make sure it is an " + "hexadecimal number between 0x80000000 and 0x817FFFFF"); + if (DolphinComm::DolphinAccessor::isMem2Enabled()) + { + errorMsg.append(" or between 0x90000000 and 0x93FFFFFF"); + } + QMessageBox* errorBox = new QMessageBox(QMessageBox::Critical, QString("Invalid address"), + errorMsg, QMessageBox::Ok, this); + errorBox->exec(); + } + else + { + if (m_chkBoundToPointer->isChecked()) + { + bool allOffsetsValid = true; + int i = 0; + for (i; i < m_offsetsWidgets.count(); ++i) + { + allOffsetsValid = validateAndSetOffset(i); + if (!allOffsetsValid) + break; + } + if (!allOffsetsValid) + { + QMessageBox* errorBox = new QMessageBox( + QMessageBox::Critical, "Invalid offset", + QString::fromStdString("The offset you entered for the level " + std::to_string(i + 1) + + " is invalid, make sure it is an " + "hexadecimal number"), + QMessageBox::Ok, this); + errorBox->exec(); + return; + } + } + if (m_txbLabel->text() == "") + m_entry->setLabel("No label"); + else + m_entry->setLabel(m_txbLabel->text().toStdString()); + + setResult(QDialog::Accepted); + hide(); + } +} + +bool DlgAddWatchEntry::validateAndSetAddress() +{ + std::string addressStr = m_txbAddress->text().toStdString(); + std::stringstream ss(addressStr); + ss >> std::hex; + u32 address = 0; + ss >> address; + if (!ss.fail()) + { + if (DolphinComm::DolphinAccessor::isValidConsoleAddress(address)) + { + m_entry->setConsoleAddress(address); + return true; + } + } + return false; +} + +bool DlgAddWatchEntry::validateAndSetOffset(int index) +{ + std::string offsetStr = m_offsetsWidgets[index]->text().toStdString(); + std::stringstream ss(offsetStr); + ss >> std::hex; + int offset = 0; + ss >> offset; + if (!ss.fail()) + { + m_entry->setPointerOffset(offset, index); + return true; + } + return false; +} + +void DlgAddWatchEntry::onAddressChanged() +{ + if (validateAndSetAddress()) + { + updateValuePreview(); + } + else + { + m_lblValuePreview->setText("???"); + } +} + +void DlgAddWatchEntry::updateValuePreview() +{ + m_entry->readMemoryFromRAM(); + m_lblValuePreview->setText(QString::fromStdString(m_entry->getStringFromMemory())); +} + +void DlgAddWatchEntry::onLengthChanged() +{ + m_entry->setLength(m_spnLength->value()); + if (validateAndSetAddress()) + updateValuePreview(); +} + +void DlgAddWatchEntry::onIsPointerChanged() +{ + if (m_chkBoundToPointer->isChecked()) + m_pointerWidget->show(); + else + m_pointerWidget->hide(); + m_entry->setBoundToPointer(m_chkBoundToPointer->isChecked()); + updateValuePreview(); +} + +MemWatchEntry* DlgAddWatchEntry::getEntry() const +{ + return m_entry; +} \ No newline at end of file diff --git a/Source/GUI/MemWatcher/DlgAddWatchEntry.h b/Source/GUI/MemWatcher/DlgAddWatchEntry.h new file mode 100644 index 00000000..6324a773 --- /dev/null +++ b/Source/GUI/MemWatcher/DlgAddWatchEntry.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../MemoryWatch/MemoryWatch.h" + +class DlgAddWatchEntry : public QDialog +{ +public: + DlgAddWatchEntry(MemWatchEntry* entry); + void onTypeChange(int index); + void accept(); + void onAddressChanged(); + void onIsPointerChanged(); + void onLengthChanged(); + void onOffsetChanged(); + MemWatchEntry* getEntry() const; + +private: + void updateValuePreview(); + bool validateAndSetAddress(); + bool validateAndSetOffset(int index); + void addPointerOffset(); + void removePointerOffset(); + + MemWatchEntry* m_entry; + QLineEdit* m_txbAddress; + QVector m_offsetsWidgets; + QFormLayout* m_offsetsLayout; + QCheckBox* m_chkBoundToPointer; + QLabel* m_lblValuePreview; + QLineEdit* m_txbLabel; + QComboBox* m_cmbTypes; + QSpinBox* m_spnLength; + QWidget* m_lengtWidget; + QWidget* m_pointerWidget; + QPushButton* m_btnAddOffset; + QPushButton* m_btnRemoveOffset; +}; \ No newline at end of file diff --git a/Source/GUI/MemWatcher/DlgChangeType.cpp b/Source/GUI/MemWatcher/DlgChangeType.cpp new file mode 100644 index 00000000..7b032108 --- /dev/null +++ b/Source/GUI/MemWatcher/DlgChangeType.cpp @@ -0,0 +1,80 @@ +#include "DlgChangeType.h" + +#include +#include +#include +#include + +#include "../GUICommon.h" + +DlgChangeType::DlgChangeType(QWidget* parent, const int typeIndex, const size_t length) + : QDialog(parent), m_typeIndex(typeIndex), m_length(length) +{ + QLabel* lblType = new QLabel(QString("New type: "), this); + m_cmbTypes = new QComboBox(this); + m_cmbTypes->addItems(GUICommon::g_memTypeNames); + m_cmbTypes->setCurrentIndex(typeIndex); + + QLabel* lblLength = new QLabel(QString("Length: "), this); + m_spnLength = new QSpinBox(this); + m_spnLength->setMinimum(1); + m_spnLength->setValue(static_cast(length)); + + QWidget* typeSelection = new QWidget(this); + QHBoxLayout* typeSelectionLayout = new QHBoxLayout; + typeSelectionLayout->addWidget(lblType); + typeSelectionLayout->addWidget(m_cmbTypes); + typeSelection->setLayout(typeSelectionLayout); + + m_lengthSelection = new QWidget; + QHBoxLayout* lengthSelectionLayout = new QHBoxLayout; + lengthSelectionLayout->addWidget(lblLength); + lengthSelectionLayout->addWidget(m_spnLength); + m_lengthSelection->setLayout(lengthSelectionLayout); + + QDialogButtonBox* buttonBox = + new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + QVBoxLayout* main_layout = new QVBoxLayout; + + main_layout->addWidget(typeSelection); + main_layout->addWidget(m_lengthSelection); + main_layout->addWidget(buttonBox); + setLayout(main_layout); + + Common::MemType theType = static_cast(typeIndex); + if (theType != Common::MemType::type_string && theType != Common::MemType::type_byteArray) + m_lengthSelection->hide(); + + connect(m_cmbTypes, static_cast(&QComboBox::currentIndexChanged), this, + &DlgChangeType::onTypeChange); + connect(buttonBox, &QDialogButtonBox::accepted, this, &DlgChangeType::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &DlgChangeType::reject); +} + +int DlgChangeType::getTypeIndex() const +{ + return m_typeIndex; +} + +size_t DlgChangeType::getLength() const +{ + return m_length; +} + +void DlgChangeType::accept() +{ + m_typeIndex = m_cmbTypes->currentIndex(); + m_length = m_spnLength->value(); + setResult(QDialog::Accepted); + hide(); +} + +void DlgChangeType::onTypeChange(int index) +{ + Common::MemType theType = static_cast(index); + if (theType == Common::MemType::type_string || theType == Common::MemType::type_byteArray) + m_lengthSelection->show(); + else + m_lengthSelection->hide(); +} \ No newline at end of file diff --git a/Source/GUI/MemWatcher/DlgChangeType.h b/Source/GUI/MemWatcher/DlgChangeType.h new file mode 100644 index 00000000..3ed93e60 --- /dev/null +++ b/Source/GUI/MemWatcher/DlgChangeType.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +class DlgChangeType : public QDialog +{ + Q_OBJECT + +public: + DlgChangeType(QWidget* parent, const int typeIndex, const size_t length); + int getTypeIndex() const; + size_t getLength() const; + void accept(); + void onTypeChange(int index); + +private: + int m_typeIndex; + size_t m_length; + QComboBox* m_cmbTypes; + QSpinBox* m_spnLength; + QWidget* m_lengthSelection; +}; \ No newline at end of file diff --git a/Source/GUI/MemWatcher/MemWatchDelegate.cpp b/Source/GUI/MemWatcher/MemWatchDelegate.cpp new file mode 100644 index 00000000..9d99bc97 --- /dev/null +++ b/Source/GUI/MemWatcher/MemWatchDelegate.cpp @@ -0,0 +1,33 @@ +#include "MemWatchDelegate.h" + +#include + +#include "MemWatchModel.h" +#include "MemWatchTreeNode.h" + +void MemWatchDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + MemWatchTreeNode* node = static_cast(index.internalPointer()); + if (index.column() == MemWatchModel::WATCH_COL_VALUE && !node->isGroup()) + { + node->setValueEditing(true); + } + else if (node->isGroup() && index.column() == MemWatchModel::WATCH_COL_LABEL) + { + QLineEdit* lineEditor = static_cast(editor); + lineEditor->setText(node->getGroupName()); + } + else + { + QStyledItemDelegate::setEditorData(editor, index); + } +} + +void MemWatchDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, + const QModelIndex& index) const +{ + QStyledItemDelegate::setModelData(editor, model, index); + MemWatchTreeNode* node = static_cast(index.internalPointer()); + if (index.column() == MemWatchModel::WATCH_COL_VALUE && !node->isGroup()) + node->setValueEditing(false); +} \ No newline at end of file diff --git a/Source/GUI/MemWatcher/MemWatchDelegate.h b/Source/GUI/MemWatcher/MemWatchDelegate.h new file mode 100644 index 00000000..1a310d09 --- /dev/null +++ b/Source/GUI/MemWatcher/MemWatchDelegate.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class MemWatchDelegate : public QStyledItemDelegate +{ +public: + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData(QWidget* editor, QAbstractItemModel* model, + const QModelIndex& index) const override; +}; \ No newline at end of file diff --git a/Source/GUI/MemWatcher/MemWatchModel.cpp b/Source/GUI/MemWatcher/MemWatchModel.cpp new file mode 100644 index 00000000..61b60fdf --- /dev/null +++ b/Source/GUI/MemWatcher/MemWatchModel.cpp @@ -0,0 +1,499 @@ +#include "MemWatchModel.h" + +#include +#include +#include +#include + +#include "../GUICommon.h" + +MemWatchModel::MemWatchModel(QObject* parent) : QAbstractItemModel(parent) +{ + m_rootNode = new MemWatchTreeNode(nullptr); +} + +MemWatchModel::~MemWatchModel() +{ + delete m_rootNode; +} + +void MemWatchModel::onUpdateTimer() +{ + if (!updateNodeValueRecursive(m_rootNode)) + { + emit readFailed(); + } +} + +void MemWatchModel::onFreezeTimer() +{ + if (!freezeNodeValueRecursive(m_rootNode)) + { + emit writeFailed(QModelIndex(), Common::MemOperationReturnCode::operationFailed); + } +} + +bool MemWatchModel::updateNodeValueRecursive(MemWatchTreeNode* node, const QModelIndex& parent, + bool readSucess) +{ + QVector children = node->getChildren(); + if (children.count() != 0) + { + for (auto i : children) + { + QModelIndex theIndex = index(i->getRow(), WATCH_COL_VALUE, parent); + readSucess = updateNodeValueRecursive(i, theIndex, readSucess); + if (!readSucess) + return false; + if (!i->isValueEditing()) + emit dataChanged(theIndex, theIndex); + } + } + + MemWatchEntry* entry = node->getEntry(); + if (entry != nullptr) + if (entry->readMemoryFromRAM() == Common::MemOperationReturnCode::operationFailed) + return false; + return true; +} + +bool MemWatchModel::freezeNodeValueRecursive(MemWatchTreeNode* node, const QModelIndex& parent, + bool writeSucess) +{ + QVector children = node->getChildren(); + if (children.count() != 0) + { + for (auto i : children) + { + QModelIndex theIndex = index(i->getRow(), WATCH_COL_VALUE, parent); + writeSucess = freezeNodeValueRecursive(i, theIndex, writeSucess); + if (!writeSucess) + return false; + } + } + + MemWatchEntry* entry = node->getEntry(); + if (entry != nullptr) + { + if (entry->isLocked()) + { + Common::MemOperationReturnCode writeReturn = entry->freeze(); + // Here we want to not care about invalid pointers, it won't write anyway + if (writeReturn == Common::MemOperationReturnCode::OK || + writeReturn == Common::MemOperationReturnCode::invalidPointer) + return true; + } + } + return true; +} + +void MemWatchModel::changeType(const QModelIndex& index, Common::MemType type, size_t length) +{ + MemWatchEntry* entry = getEntryFromIndex(index); + entry->setType(type); + entry->setLength(length); + emit dataChanged(index, index); +} + +MemWatchEntry* MemWatchModel::getEntryFromIndex(const QModelIndex& index) const +{ + MemWatchTreeNode* node = static_cast(index.internalPointer()); + return node->getEntry(); +} + +void MemWatchModel::addGroup(const QString& name) +{ + const QModelIndex rootIndex = index(0, 0); + MemWatchTreeNode* node = new MemWatchTreeNode(nullptr, m_rootNode, true, name); + beginInsertRows(rootIndex, rowCount(rootIndex), rowCount(rootIndex)); + m_rootNode->appendChild(node); + endInsertRows(); + emit layoutChanged(); +} + +void MemWatchModel::addEntry(MemWatchEntry* entry) +{ + MemWatchTreeNode* newNode = new MemWatchTreeNode(entry); + QModelIndex idx = index(0, 0); + beginInsertRows(idx, rowCount(QModelIndex()), rowCount(QModelIndex())); + m_rootNode->appendChild(newNode); + endInsertRows(); + emit layoutChanged(); +} + +void MemWatchModel::editEntry(MemWatchEntry* entry, const QModelIndex& index) +{ + MemWatchTreeNode* node = static_cast(index.internalPointer()); + node->setEntry(entry); + emit layoutChanged(); +} + +void MemWatchModel::removeNode(const QModelIndex& index) +{ + if (index.isValid()) + { + MemWatchTreeNode* toDelete = static_cast(index.internalPointer()); + MemWatchTreeNode* parent = toDelete->getParent(); + + int toDeleteRow = toDelete->getRow(); + + const QModelIndex idx = createIndex(toDeleteRow, 0, parent); + if (parent == m_rootNode) + beginRemoveRows(QModelIndex(), toDeleteRow, toDeleteRow); + else + beginRemoveRows(idx.parent(), toDeleteRow, toDeleteRow); + bool removeChildren = (toDelete->isGroup() && toDelete->hasChildren()); + if (removeChildren) + beginRemoveRows(index, 0, toDelete->childrenCount()); + toDelete->getParent()->removeChild(toDeleteRow); + delete toDelete; + if (removeChildren) + endRemoveRows(); + endRemoveRows(); + } +} + +int MemWatchModel::columnCount(const QModelIndex& parent) const +{ + return WATCH_COL_NUM; +} + +int MemWatchModel::rowCount(const QModelIndex& parent) const +{ + MemWatchTreeNode* parentItem; + if (parent.column() > 0) + return 0; + + if (!parent.isValid()) + parentItem = m_rootNode; + else + parentItem = static_cast(parent.internalPointer()); + + return parentItem->getChildren().size(); +} + +QVariant MemWatchModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + MemWatchTreeNode* item = static_cast(index.internalPointer()); + + if (!item->isGroup()) + { + if (role == Qt::EditRole && index.column() == WATCH_COL_TYPE) + { + return QVariant(static_cast(item->getEntry()->getType())); + } + + MemWatchEntry* entry = item->getEntry(); + if (role == Qt::DisplayRole || role == Qt::EditRole) + { + switch (index.column()) + { + case WATCH_COL_LABEL: + { + return QString::fromStdString(entry->getLabel()); + break; + } + case WATCH_COL_TYPE: + { + Common::MemType type = entry->getType(); + size_t length = entry->getLength(); + return GUICommon::getStringFromType(type, length); + break; + } + case WATCH_COL_ADDRESS: + { + u32 address = entry->getConsoleAddress(); + bool isPointer = entry->isBoundToPointer(); + return getAddressString(address, isPointer); + break; + } + case WATCH_COL_VALUE: + { + return QString::fromStdString(entry->getStringFromMemory()); + break; + } + } + } + else if (role == Qt::CheckStateRole && index.column() == WATCH_COL_LOCK) + { + if (entry->isLocked()) + return Qt::Checked; + return Qt::Unchecked; + } + } + else + { + if (index.column() == 0 && role == Qt::DisplayRole) + return item->getGroupName(); + } + return QVariant(); +} + +bool MemWatchModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + if (!index.isValid()) + return false; + + MemWatchTreeNode* node = static_cast(index.internalPointer()); + if (node == m_rootNode) + return false; + + if (!node->isGroup()) + { + MemWatchEntry* entry = node->getEntry(); + if (role == Qt::EditRole) + { + switch (index.column()) + { + case WATCH_COL_LABEL: + { + entry->setLabel((value.toString().toStdString())); + emit dataChanged(index, index); + return true; + break; + } + case WATCH_COL_VALUE: + { + Common::MemOperationReturnCode returnCode = + entry->writeMemoryFromString((value.toString().toStdString())); + if (returnCode != Common::MemOperationReturnCode::OK) + { + emit writeFailed(index, returnCode); + return false; + } + emit dataChanged(index, index); + return true; + break; + } + default: + return false; + } + } + else if (role == Qt::CheckStateRole && index.column() == WATCH_COL_LOCK) + { + value == Qt::Checked ? entry->setLock(true) : entry->setLock(false); + emit dataChanged(index, index); + return true; + } + else + { + return false; + } + } + else + { + node->setGroupName(value.toString()); + return true; + } +} + +Qt::ItemFlags MemWatchModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::ItemIsDropEnabled; + + MemWatchTreeNode* node = static_cast(index.internalPointer()); + if (node == m_rootNode) + return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + + // These flags are common to every node + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsDragEnabled | Qt::ItemIsEnabled; + + if (node->isGroup()) + { + flags |= Qt::ItemIsDropEnabled; + if (index.column() == WATCH_COL_LABEL) + flags |= Qt::ItemIsEditable; + return flags; + } + + if (index.column() == WATCH_COL_LOCK) + return flags |= Qt::ItemIsUserCheckable; + + if (index.column() != WATCH_COL_ADDRESS && index.column() != WATCH_COL_TYPE) + flags |= Qt::ItemIsEditable; + return flags; +} + +QModelIndex MemWatchModel::index(int row, int column, const QModelIndex& parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + MemWatchTreeNode* parentNode; + + if (!parent.isValid()) + parentNode = m_rootNode; + else + parentNode = static_cast(parent.internalPointer()); + + MemWatchTreeNode* childNode = parentNode->getChildren().at(row); + if (childNode) + return createIndex(row, column, childNode); + else + return QModelIndex(); +} + +QModelIndex MemWatchModel::parent(const QModelIndex& index) const +{ + if (!index.isValid()) + return QModelIndex(); + + MemWatchTreeNode* childNode = static_cast(index.internalPointer()); + MemWatchTreeNode* parentNode = childNode->getParent(); + + if (parentNode == m_rootNode) + return QModelIndex(); + + return createIndex(parentNode->getRow(), 0, parentNode); +} + +QVariant MemWatchModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) + { + switch (section) + { + case WATCH_COL_LABEL: + return QString("Name"); + break; + case WATCH_COL_TYPE: + return QString("Type"); + break; + case WATCH_COL_ADDRESS: + return QString("Address"); + break; + case WATCH_COL_VALUE: + return QString("Value"); + break; + case WATCH_COL_LOCK: + return QString("Lock"); + break; + } + } + return QVariant(); +} + +Qt::DropActions MemWatchModel::supportedDropActions() const +{ + return Qt::MoveAction; +} + +Qt::DropActions MemWatchModel::supportedDragActions() const +{ + return Qt::MoveAction; +} + +QStringList MemWatchModel::mimeTypes() const +{ + return QStringList() << "application/x-memwatchtreenode"; +} + +QMimeData* MemWatchModel::mimeData(const QModelIndexList& indexes) const +{ + QMimeData* mimeData = new QMimeData; + QByteArray data; + + QDataStream stream(&data, QIODevice::WriteOnly); + QList nodes; + + foreach (const QModelIndex& index, indexes) + { + MemWatchTreeNode* node = static_cast(index.internalPointer()); + if (!nodes.contains(node)) + nodes << node; + } + stream << nodes.count(); + foreach (MemWatchTreeNode* node, nodes) + { + qulonglong pointer = 0; + std::memcpy(&pointer, &node, sizeof(node)); + stream << pointer; + } + mimeData->setData("application/x-memwatchtreenode", data); + return mimeData; +} + +bool MemWatchModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, + const QModelIndex& parent) +{ + if (!data->hasFormat("application/x-memwatchtreenode")) + { + return false; + } + QByteArray bytes = data->data("application/x-memwatchtreenode"); + QDataStream stream(&bytes, QIODevice::ReadOnly); + MemWatchTreeNode* destParentNode = nullptr; + if (!parent.isValid()) + destParentNode = m_rootNode; + else + destParentNode = static_cast(parent.internalPointer()); + int count; + stream >> count; + if (row == -1) + { + if (parent.isValid() && destParentNode->isGroup()) + row = rowCount(parent); + else if (!parent.isValid()) + return false; + } + for (int i = 0; i < count; ++i) + { + qlonglong nodePtr; + stream >> nodePtr; + MemWatchTreeNode* srcNode = nullptr; + std::memcpy(&srcNode, &nodePtr, sizeof(nodePtr)); + + if (srcNode->getRow() < row && destParentNode == srcNode->getParent()) + --row; + + const int srcNodeRow = srcNode->getRow(); + + const QModelIndex idx = createIndex(srcNodeRow, 0, destParentNode); + if (destParentNode == m_rootNode) + beginRemoveRows(QModelIndex(), srcNodeRow, srcNodeRow); + else + beginRemoveRows(idx.parent(), srcNodeRow, srcNodeRow); + srcNode->getParent()->removeChild(srcNodeRow); + endRemoveRows(); + + beginInsertRows(parent, row, row); + destParentNode->insertChild(row, srcNode); + endInsertRows(); + + ++row; + } + emit dropSucceeded(); + return true; +} + +QString MemWatchModel::getAddressString(u32 address, bool isPointer) const +{ + std::stringstream ss; + if (isPointer) + { + ss << "P->" << std::hex << std::uppercase << address; + return QString::fromStdString(ss.str()); + } + ss << std::hex << std::uppercase << address; + return QString::fromStdString(ss.str()); +} + +void MemWatchModel::loadRootFromJsonRecursive(const QJsonObject& json) +{ + m_rootNode->readFromJson(json); + emit layoutChanged(); +} + +void MemWatchModel::writeRootToJsonRecursive(QJsonObject& json) const +{ + m_rootNode->writeToJson(json); +} + +bool MemWatchModel::hasAnyNodes() const +{ + return m_rootNode->hasChildren(); +} \ No newline at end of file diff --git a/Source/GUI/MemWatcher/MemWatchModel.h b/Source/GUI/MemWatcher/MemWatchModel.h new file mode 100644 index 00000000..01e7e671 --- /dev/null +++ b/Source/GUI/MemWatcher/MemWatchModel.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include "../../MemoryWatch/MemoryWatch.h" +#include "MemWatchTreeNode.h" + +class MemWatchModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum + { + WATCH_COL_LABEL = 0, + WATCH_COL_TYPE, + WATCH_COL_ADDRESS, + WATCH_COL_VALUE, + WATCH_COL_LOCK, + WATCH_COL_NUM + }; + + MemWatchModel(QObject* parent); + ~MemWatchModel(); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex& index) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + Qt::DropActions supportedDropActions() const override; + Qt::DropActions supportedDragActions() const override; + QStringList mimeTypes() const override; + QMimeData* mimeData(const QModelIndexList& indexes) const override; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, + const QModelIndex& parent) override; + + void changeType(const QModelIndex& index, const Common::MemType type, const size_t length); + MemWatchEntry* getEntryFromIndex(const QModelIndex& index) const; + void addGroup(const QString& name); + void addEntry(MemWatchEntry* entry); + void editEntry(MemWatchEntry* entry, const QModelIndex& index); + void removeNode(const QModelIndex& index); + void onUpdateTimer(); + void onFreezeTimer(); + void loadRootFromJsonRecursive(const QJsonObject& json); + void writeRootToJsonRecursive(QJsonObject& json) const; + bool hasAnyNodes() const; +signals: + void writeFailed(const QModelIndex& index, Common::MemOperationReturnCode writeReturn); + void readFailed(); + void dropSucceeded(); + +private: + bool updateNodeValueRecursive(MemWatchTreeNode* node, const QModelIndex& parent = QModelIndex(), + const bool readSucess = true); + bool freezeNodeValueRecursive(MemWatchTreeNode* node, const QModelIndex& parent = QModelIndex(), + const bool writeSucess = true); + QString getAddressString(u32 address, bool isPointer) const; + + MemWatchTreeNode* m_rootNode; +}; \ No newline at end of file diff --git a/Source/GUI/MemWatcher/MemWatchTreeNode.cpp b/Source/GUI/MemWatcher/MemWatchTreeNode.cpp new file mode 100644 index 00000000..e4daa3c7 --- /dev/null +++ b/Source/GUI/MemWatcher/MemWatchTreeNode.cpp @@ -0,0 +1,222 @@ +#include "MemWatchTreeNode.h" + +#include + +#include + +MemWatchTreeNode::MemWatchTreeNode(MemWatchEntry* entry, MemWatchTreeNode* parent, + const bool isGroup, const QString& groupName) + : m_entry(entry), m_parent(parent), m_isGroup(isGroup), m_groupName(groupName) +{ +} + +MemWatchTreeNode::~MemWatchTreeNode() +{ + qDeleteAll(m_children); +} + +bool MemWatchTreeNode::isGroup() const +{ + return m_isGroup; +} + +bool MemWatchTreeNode::hasChildren() const +{ + return (m_children.count() >= 1); +} + +int MemWatchTreeNode::childrenCount() const +{ + return m_children.count(); +} + +void MemWatchTreeNode::deleteAllChildren() +{ + qDeleteAll(m_children); +} + +bool MemWatchTreeNode::isValueEditing() const +{ + return m_isValueEditing; +} + +void MemWatchTreeNode::setValueEditing(const bool valueEditing) +{ + m_isValueEditing = valueEditing; +} + +QString MemWatchTreeNode::getGroupName() const +{ + return m_groupName; +} + +void MemWatchTreeNode::setGroupName(const QString& groupName) +{ + m_groupName = groupName; +} + +MemWatchEntry* MemWatchTreeNode::getEntry() const +{ + return m_entry; +} + +void MemWatchTreeNode::setEntry(MemWatchEntry* entry) +{ + delete m_entry; + m_entry = entry; +} + +QVector MemWatchTreeNode::getChildren() const +{ + return m_children; +} + +MemWatchTreeNode* MemWatchTreeNode::getParent() const +{ + return m_parent; +} + +int MemWatchTreeNode::getRow() const +{ + if (m_parent) + return m_parent->m_children.indexOf(const_cast(this)); + + return 0; +} + +void MemWatchTreeNode::appendChild(MemWatchTreeNode* node) +{ + m_children.append(node); + node->m_parent = this; +} + +void MemWatchTreeNode::insertChild(const int row, MemWatchTreeNode* node) +{ + m_children.insert(row, node); + node->m_parent = this; +} + +void MemWatchTreeNode::removeChild(const int row) +{ + m_children.remove(row); +} + +void MemWatchTreeNode::readFromJson(const QJsonObject& json, MemWatchTreeNode* parent) +{ + m_parent = parent; + if (json["watchList"] != QJsonValue::Undefined) + { + m_isGroup = false; + QJsonArray watchList = json["watchList"].toArray(); + for (auto i : watchList) + { + QJsonObject node = i.toObject(); + MemWatchTreeNode* childNode = new MemWatchTreeNode(nullptr); + childNode->readFromJson(node, this); + m_children.append(childNode); + } + } + else if (json["groupName"] != QJsonValue::Undefined) + { + m_isGroup = true; + m_groupName = json["groupName"].toString(); + QJsonArray groupEntries = json["groupEntries"].toArray(); + for (auto i : groupEntries) + { + QJsonObject node = i.toObject(); + MemWatchTreeNode* childNode = new MemWatchTreeNode(nullptr); + childNode->readFromJson(node, this); + m_children.append(childNode); + } + } + else + { + m_isGroup = false; + m_entry = new MemWatchEntry(); + m_entry->setLabel(json["label"].toString().toStdString()); + std::stringstream ss(json["address"].toString().toStdString()); + u32 address = 0; + ss >> std::hex >> std::uppercase >> address; + m_entry->setConsoleAddress(address); + m_entry->setType(static_cast(json["typeIndex"].toInt())); + m_entry->setSignedUnsigned(json["unsigned"].toBool()); + if (m_entry->getType() == Common::MemType::type_string || + m_entry->getType() == Common::MemType::type_byteArray) + { + m_entry->setLength(static_cast(json["length"].toDouble())); + } + m_entry->setBase(static_cast(json["baseIndex"].toInt())); + if (json["pointerOffsets"] != QJsonValue::Undefined) + { + m_entry->setBoundToPointer(true); + QJsonArray pointerOffsets = json["pointerOffsets"].toArray(); + for (auto i : pointerOffsets) + { + std::stringstream ssOffset(i.toString().toStdString()); + int offset = 0; + ssOffset >> std::hex >> std::uppercase >> offset; + m_entry->addOffset(offset); + } + } + else + { + m_entry->setBoundToPointer(false); + } + } +} + +void MemWatchTreeNode::writeToJson(QJsonObject& json) const +{ + if (isGroup()) + { + json["groupName"] = m_groupName; + QJsonArray entries; + for (auto i : m_children) + { + QJsonObject theNode; + i->writeToJson(theNode); + entries.append(theNode); + } + json["groupEntries"] = entries; + } + else + { + if (m_parent == nullptr) + { + QJsonArray watchList; + for (auto i : m_children) + { + QJsonObject theNode; + i->writeToJson(theNode); + watchList.append(theNode); + } + json["watchList"] = watchList; + } + else + { + json["label"] = QString::fromStdString(m_entry->getLabel()); + std::stringstream ss; + ss << std::hex << std::uppercase << m_entry->getConsoleAddress(); + json["address"] = QString::fromStdString(ss.str()); + json["typeIndex"] = static_cast(m_entry->getType()); + json["unsigned"] = m_entry->isUnsigned(); + if (m_entry->getType() == Common::MemType::type_string || + m_entry->getType() == Common::MemType::type_byteArray) + { + json["length"] = static_cast(m_entry->getLength()); + } + json["baseIndex"] = static_cast(m_entry->getBase()); + if (m_entry->isBoundToPointer()) + { + QJsonArray offsets; + for (int i = 0; i < m_entry->getPointerOffsets().size(); ++i) + { + std::stringstream ssOffset; + ssOffset << std::hex << std::uppercase << m_entry->getPointerOffset(i); + offsets.append(QString::fromStdString(ssOffset.str())); + } + json["pointerOffsets"] = offsets; + } + } + } +} diff --git a/Source/GUI/MemWatcher/MemWatchTreeNode.h b/Source/GUI/MemWatcher/MemWatchTreeNode.h new file mode 100644 index 00000000..6720cc12 --- /dev/null +++ b/Source/GUI/MemWatcher/MemWatchTreeNode.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#include "../../MemoryWatch/MemoryWatch.h" + +class MemWatchTreeNode +{ +public: + MemWatchTreeNode(MemWatchEntry* entry, MemWatchTreeNode* parent = nullptr, + const bool isGroup = false, const QString& groupName = ""); + ~MemWatchTreeNode(); + + bool isGroup() const; + QString getGroupName() const; + void setGroupName(const QString& groupName); + MemWatchEntry* getEntry() const; + void setEntry(MemWatchEntry* entry); + QVector getChildren() const; + MemWatchTreeNode* getParent() const; + int getRow() const; + bool isValueEditing() const; + bool hasChildren() const; + int childrenCount() const; + void setValueEditing(const bool valueEditing); + + void appendChild(MemWatchTreeNode* node); + void insertChild(const int row, MemWatchTreeNode* node); + void removeChild(const int row); + void deleteAllChildren(); + + void readFromJson(const QJsonObject& json, MemWatchTreeNode* parent = nullptr); + void writeToJson(QJsonObject& json) const; + +private: + bool m_isGroup; + bool m_isValueEditing = false; + QString m_groupName; + MemWatchEntry* m_entry; + QVector m_children; + MemWatchTreeNode* m_parent; +}; \ No newline at end of file diff --git a/Source/GUI/MemWatcher/MemWatchWidget.cpp b/Source/GUI/MemWatcher/MemWatchWidget.cpp new file mode 100644 index 00000000..5546ad3c --- /dev/null +++ b/Source/GUI/MemWatcher/MemWatchWidget.cpp @@ -0,0 +1,444 @@ +#include "MemWatchWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../Common/MemoryCommon.h" +#include "../../MemoryWatch/MemoryWatch.h" +#include "../GUICommon.h" +#include "DlgAddWatchEntry.h" +#include "DlgChangeType.h" + +MemWatchWidget::MemWatchWidget(QWidget* parent) : QWidget(parent) +{ + m_watchModel = new MemWatchModel(this); + m_watchDelegate = new MemWatchDelegate(); + + m_watchView = new QTreeView; + m_watchView->setDragEnabled(true); + m_watchView->setAcceptDrops(true); + m_watchView->setDragDropMode(QAbstractItemView::InternalMove); + m_watchView->setDropIndicatorShown(true); + m_watchView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_watchView->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_watchView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_watchView, + static_cast(&QWidget::customContextMenuRequested), this, + static_cast( + &MemWatchWidget::customContextMenuRequested)); + connect(m_watchModel, + static_cast( + &MemWatchModel::writeFailed), + this, + static_cast( + &MemWatchWidget::onValueWriteError)); + connect(m_watchModel, &MemWatchModel::dropSucceeded, this, &MemWatchWidget::onDropSucceeded); + connect(m_watchView, static_cast( + &QAbstractItemView::doubleClicked), + this, static_cast( + &MemWatchWidget::onWatchDoubleClicked)); + m_watchView->setItemDelegate(m_watchDelegate); + m_watchView->setModel(m_watchModel); + QShortcut* shortcut = new QShortcut(QKeySequence::Delete, m_watchView); + connect(shortcut, &QShortcut::activated, this, &MemWatchWidget::onDeleteNode); + + QWidget* buttons = new QWidget; + QHBoxLayout* buttons_layout = new QHBoxLayout; + m_btnAddGroup = new QPushButton("Add group", this); + m_btnAddWatchEntry = new QPushButton("Add watch", this); + + buttons_layout->addWidget(m_btnAddGroup); + buttons_layout->addWidget(m_btnAddWatchEntry); + buttons->setLayout(buttons_layout); + connect(m_btnAddGroup, static_cast(&QPushButton::clicked), this, + &MemWatchWidget::onAddGroup); + connect(m_btnAddWatchEntry, static_cast(&QPushButton::clicked), this, + &MemWatchWidget::onAddWatchEntry); + + QVBoxLayout* layout = new QVBoxLayout; + + layout->addWidget(buttons); + layout->addWidget(m_watchView); + + setLayout(layout); + + connect(m_watchModel, &MemWatchModel::readFailed, this, &MemWatchWidget::mustUnhook); + + m_updateTimer = new QTimer(this); + connect(m_updateTimer, &QTimer::timeout, m_watchModel, &MemWatchModel::onUpdateTimer); + + m_freezeTimer = new QTimer(this); + connect(m_freezeTimer, &QTimer::timeout, m_watchModel, &MemWatchModel::onFreezeTimer); +} + +void MemWatchWidget::onMemWatchContextMenuRequested(QPoint pos) +{ + QModelIndex index = m_watchView->indexAt(pos); + if (index != QModelIndex()) + { + MemWatchTreeNode* node = static_cast(index.internalPointer()); + if (!node->isGroup()) + { + MemWatchEntry* entry = m_watchModel->getEntryFromIndex(index); + int typeIndex = static_cast(entry->getType()); + Common::MemType theType = static_cast(typeIndex); + if (theType != Common::MemType::type_string) + { + QMenu* contextMenu = new QMenu(this); + + QAction* viewDec = new QAction("View as Decimal", this); + QAction* viewHex = new QAction("View as Hexadecimal", this); + QAction* viewOct = new QAction("View as Octal", this); + QAction* viewBin = new QAction("View as Binary", this); + + connect(viewDec, &QAction::triggered, m_watchModel, [=] { + entry->setBase(Common::MemBase::base_decimal); + m_hasUnsavedChanges = true; + }); + connect(viewHex, &QAction::triggered, m_watchModel, [=] { + entry->setBase(Common::MemBase::base_hexadecimal); + m_hasUnsavedChanges = true; + }); + connect(viewOct, &QAction::triggered, m_watchModel, [=] { + entry->setBase(Common::MemBase::base_octal); + m_hasUnsavedChanges = true; + }); + connect(viewBin, &QAction::triggered, m_watchModel, [=] { + entry->setBase(Common::MemBase::base_binary); + m_hasUnsavedChanges = true; + }); + + contextMenu->addAction(viewDec); + contextMenu->addAction(viewHex); + contextMenu->addAction(viewOct); + contextMenu->addAction(viewBin); + + int baseIndex = static_cast(entry->getBase()); + Common::MemBase theBase = static_cast(baseIndex); + contextMenu->actions().at(baseIndex)->setEnabled(false); + + if (theBase == Common::MemBase::base_decimal) + { + QAction* viewSigned = new QAction("View as Signed", this); + QAction* viewUnsigned = new QAction("View as Unsigned", this); + + connect(viewSigned, &QAction::triggered, m_watchModel, [=] { + entry->setSignedUnsigned(false); + m_hasUnsavedChanges = true; + }); + connect(viewUnsigned, &QAction::triggered, m_watchModel, [=] { + entry->setSignedUnsigned(true); + m_hasUnsavedChanges = true; + }); + + contextMenu->addSeparator(); + contextMenu->addAction(viewSigned); + contextMenu->addAction(viewUnsigned); + + if (entry->isUnsigned()) + contextMenu->actions().at(6)->setEnabled(false); + else + contextMenu->actions().at(5)->setEnabled(false); + } + + contextMenu->popup(m_watchView->viewport()->mapToGlobal(pos)); + } + } + } +} + +void MemWatchWidget::onWatchDoubleClicked(const QModelIndex& index) +{ + if (index != QVariant()) + { + MemWatchTreeNode* node = static_cast(index.internalPointer()); + if (index.column() == MemWatchModel::WATCH_COL_TYPE && !node->isGroup()) + { + MemWatchEntry* entry = node->getEntry(); + int typeIndex = static_cast(entry->getType()); + DlgChangeType* dlg = new DlgChangeType(this, typeIndex, entry->getLength()); + if (dlg->exec() == QDialog::Accepted) + { + Common::MemType theType = static_cast(dlg->getTypeIndex()); + m_watchModel->changeType(index, theType, dlg->getLength()); + m_hasUnsavedChanges = true; + } + } + else if (index.column() == MemWatchModel::WATCH_COL_ADDRESS && !node->isGroup()) + { + MemWatchEntry* entryCopy = new MemWatchEntry(node->getEntry()); + DlgAddWatchEntry* dlg = new DlgAddWatchEntry(entryCopy); + if (dlg->exec() == QDialog::Accepted) + { + m_watchModel->editEntry(entryCopy, index); + m_hasUnsavedChanges = true; + } + } + } +} + +void MemWatchWidget::onValueWriteError(const QModelIndex& index, + Common::MemOperationReturnCode writeReturn) +{ + switch (writeReturn) + { + case Common::MemOperationReturnCode::invalidInput: + { + MemWatchTreeNode* node = static_cast(index.internalPointer()); + MemWatchEntry* entry = node->getEntry(); + int typeIndex = static_cast(entry->getType()); + int baseIndex = static_cast(entry->getBase()); + + QString errorMsg("The value you entered for the type " + + GUICommon::g_memTypeNames.at(typeIndex)); + if (entry->getType() == Common::MemType::type_byteArray) + errorMsg += + (" is invalid, you must enter the bytes in hexadecimal with one space between each byte"); + else + errorMsg += (" in the base " + GUICommon::g_memBaseNames.at(baseIndex) + " is invalid"); + QMessageBox* errorBox = new QMessageBox(QMessageBox::Critical, QString("Invalid value"), + errorMsg, QMessageBox::Ok, this); + errorBox->exec(); + break; + } + case Common::MemOperationReturnCode::inputTooLong: + { + MemWatchTreeNode* node = static_cast(index.internalPointer()); + MemWatchEntry* entry = node->getEntry(); + int typeIndex = static_cast(entry->getType()); + + QString errorMsg("The value you entered for the type " + + GUICommon::g_memTypeNames.at(typeIndex) + " for the length " + + QString::fromStdString(std::to_string(entry->getLength())) + + " is too long for the given length"); + if (entry->getType() == Common::MemType::type_byteArray) + errorMsg += (", you must enter the bytes in hexadecimal with one space between each byte"); + QMessageBox* errorBox = new QMessageBox(QMessageBox::Critical, QString("Invalid value"), + errorMsg, QMessageBox::Ok, this); + errorBox->exec(); + break; + } + case Common::MemOperationReturnCode::invalidPointer: + { + QMessageBox* errorBox = new QMessageBox(QMessageBox::Critical, QString("Invalid pointer"), + "This pointer points to invalid memory, therefore, you " + "cannot write to the value of this watch", + QMessageBox::Ok, this); + errorBox->exec(); + break; + } + case Common::MemOperationReturnCode::operationFailed: + { + emit mustUnhook(); + break; + } + } +} + +void MemWatchWidget::onAddGroup() +{ + bool ok = false; + QString text = QInputDialog::getText(this, "Add a group", + "Enter the group name:", QLineEdit::Normal, "", &ok); + if (ok && !text.isEmpty()) + { + m_watchModel->addGroup(text); + m_hasUnsavedChanges = true; + } +} + +void MemWatchWidget::onAddWatchEntry() +{ + DlgAddWatchEntry* dlg = new DlgAddWatchEntry(nullptr); + if (dlg->exec() == QDialog::Accepted) + { + addWatchEntry(dlg->getEntry()); + } +} + +void MemWatchWidget::addWatchEntry(MemWatchEntry* entry) +{ + m_watchModel->addEntry(entry); + m_hasUnsavedChanges = true; +} + +void MemWatchWidget::onDeleteNode() +{ + QModelIndexList selection = m_watchView->selectionModel()->selectedRows(); + bool hasGroupWithChild = false; + for (int i = 0; i < selection.count(); i++) + { + const QModelIndex index = selection.at(i); + MemWatchTreeNode* node = static_cast(index.internalPointer()); + if (node->isGroup() && node->hasChildren()) + { + hasGroupWithChild = true; + } + } + + QString confirmationMsg = "Are you sure you want to delete these watches and/or groups?"; + if (hasGroupWithChild) + confirmationMsg += + "\n\nThe current selection contains one or more groups with watches in them, deleting the " + "groups will also delete their watches, if you want to avoid this, move the watches out of " + "the groups."; + QMessageBox* confirmationBox = + new QMessageBox(QMessageBox::Question, QString("Deleting confirmation"), confirmationMsg, + QMessageBox::Yes | QMessageBox::No, this); + if (confirmationBox->exec() == QMessageBox::Yes) + { + // First, discard all indexes whose parent is selected already + QModelIndexList* toDeleteList = new QModelIndexList(); + for (int i = 0; i < selection.count(); ++i) + { + const QModelIndex index = selection.at(i); + if (!m_watchView->selectionModel()->isSelected(index.parent())) + { + toDeleteList->append(index); + } + } + + // Then, delete the rest (the children will be deleted too) + for (auto i : *toDeleteList) + { + m_watchModel->removeNode(i); + } + m_hasUnsavedChanges = true; + } +} + +void MemWatchWidget::onDropSucceeded() +{ + m_hasUnsavedChanges = true; +} + +QTimer* MemWatchWidget::getUpdateTimer() const +{ + return m_updateTimer; +} + +QTimer* MemWatchWidget::getFreezeTimer() const +{ + return m_freezeTimer; +} + +void MemWatchWidget::openWatchFile() +{ + QString fileName = QFileDialog::getOpenFileName(this, "Open watch list", m_watchListFile, + "Dolphin memory watches file (*.dmw)"); + if (fileName != "") + { + QFile watchFile(fileName); + if (!watchFile.exists()) + { + QMessageBox* errorBox = new QMessageBox( + QMessageBox::Critical, QString("Error while opening file"), + QString("The watch list file " + fileName + " does not exist"), QMessageBox::Ok, this); + errorBox->exec(); + return; + } + if (!watchFile.open(QIODevice::ReadOnly)) + { + QMessageBox* errorBox = new QMessageBox( + QMessageBox::Critical, "Error while opening file", + "An error occured while opening the watch list file for reading", QMessageBox::Ok, this); + errorBox->exec(); + return; + } + QByteArray bytes = watchFile.readAll(); + watchFile.close(); + QJsonDocument loadDoc(QJsonDocument::fromJson(bytes)); + m_watchModel->loadRootFromJsonRecursive(loadDoc.object()); + m_watchListFile = fileName; + m_hasUnsavedChanges = false; + } +} + +void MemWatchWidget::saveWatchFile() +{ + if (QFile::exists(m_watchListFile)) + { + QFile watchFile(m_watchListFile); + if (!watchFile.open(QIODevice::WriteOnly)) + { + QMessageBox* errorBox = new QMessageBox( + QMessageBox::Critical, "Error while creating file", + "An error occured while creating and opening the watch list file for writting", + QMessageBox::Ok, this); + errorBox->exec(); + return; + } + QJsonObject root; + m_watchModel->writeRootToJsonRecursive(root); + QJsonDocument saveDoc(root); + watchFile.write(saveDoc.toJson()); + watchFile.close(); + m_hasUnsavedChanges = false; + } + else + { + saveAsWatchFile(); + } +} + +void MemWatchWidget::saveAsWatchFile() +{ + QString fileName = QFileDialog::getSaveFileName(this, "Save watch list", m_watchListFile, + "Dolphin memory watches file (*.dmw)"); + if (fileName != "") + { + QFile watchFile(fileName); + if (!watchFile.open(QIODevice::WriteOnly)) + { + QMessageBox* errorBox = new QMessageBox( + QMessageBox::Critical, "Error while creating file", + "An error occured while creating and opening the watch list file for writting", + QMessageBox::Ok, this); + errorBox->exec(); + return; + } + QJsonObject root; + m_watchModel->writeRootToJsonRecursive(root); + QJsonDocument saveDoc(root); + watchFile.write(saveDoc.toJson()); + watchFile.close(); + m_watchListFile = fileName; + m_hasUnsavedChanges = false; + } +} + +bool MemWatchWidget::warnIfUnsavedChanges() +{ + if (m_hasUnsavedChanges) + { + QMessageBox* questionBox = new QMessageBox( + QMessageBox::Question, "Unsaved changes", + "You have unsaved changes in the current watch list, do you want to save them?", + QMessageBox::Cancel | QMessageBox::No | QMessageBox::Yes); + switch (questionBox->exec()) + { + case QMessageBox::Cancel: + return false; + case QMessageBox::No: + return true; + case QMessageBox::Yes: + saveWatchFile(); + return true; + } + } + return true; +} \ No newline at end of file diff --git a/Source/GUI/MemWatcher/MemWatchWidget.h b/Source/GUI/MemWatcher/MemWatchWidget.h new file mode 100644 index 00000000..b7bd6766 --- /dev/null +++ b/Source/GUI/MemWatcher/MemWatchWidget.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +#include "MemWatchDelegate.h" +#include "MemWatchModel.h" + +class MemWatchWidget : public QWidget +{ + Q_OBJECT + +public: + MemWatchWidget(QWidget* parent); + + void onMemWatchContextMenuRequested(QPoint pos); + void onValueWriteError(const QModelIndex& index, Common::MemOperationReturnCode writeReturn); + void onWatchDoubleClicked(const QModelIndex& index); + void onAddGroup(); + void onAddWatchEntry(); + void addWatchEntry(MemWatchEntry* entry); + void onDeleteNode(); + void onDropSucceeded(); + void openWatchFile(); + void saveWatchFile(); + void saveAsWatchFile(); + QTimer* getUpdateTimer() const; + QTimer* getFreezeTimer() const; + bool warnIfUnsavedChanges(); + +signals: + void mustUnhook(); + +private: + QTreeView* m_watchView; + MemWatchModel* m_watchModel; + MemWatchDelegate* m_watchDelegate; + QPushButton* m_btnAddGroup; + QPushButton* m_btnAddWatchEntry; + QTimer* m_updateTimer; + QTimer* m_freezeTimer; + + QString m_watchListFile = ""; + bool m_hasUnsavedChanges = false; +}; \ No newline at end of file diff --git a/Source/MemoryScanner/MemoryScanner.cpp b/Source/MemoryScanner/MemoryScanner.cpp new file mode 100644 index 00000000..0ea3f073 --- /dev/null +++ b/Source/MemoryScanner/MemoryScanner.cpp @@ -0,0 +1,524 @@ +#include "MemoryScanner.h" + +#include "../Common/CommonUtils.h" +#include "../DolphinProcess/DolphinAccessor.h" + +MemScanner::MemScanner() : m_resultsConsoleAddr(std::vector()) +{ +} + +MemScanner::~MemScanner() +{ +} + +Common::MemOperationReturnCode MemScanner::firstScan(const MemScanner::ScanFiter filter, + const std::string& searchTerm1, + const std::string& searchTerm2) +{ + m_scanRAMCache = nullptr; + m_updatedRAMCache = nullptr; + u32 ramSize = 0; + if (DolphinComm::DolphinAccessor::isMem2Enabled()) + { + ramSize = Common::MEM1_SIZE + Common::MEM2_SIZE; + m_scanRAMCache = new char[ramSize - 1]; + if (!DolphinComm::DolphinAccessor::readFromRAM(Common::dolphinAddrToOffset(Common::MEM2_START), + m_scanRAMCache + Common::MEM1_SIZE, + Common::MEM2_SIZE - 1, false)) + { + delete[] m_scanRAMCache; + return Common::MemOperationReturnCode::operationFailed; + } + } + else + { + ramSize = Common::MEM1_SIZE; + m_scanRAMCache = new char[ramSize - 1]; + } + + if (!DolphinComm::DolphinAccessor::readFromRAM(Common::dolphinAddrToOffset(Common::MEM1_START), + m_scanRAMCache, Common::MEM1_SIZE - 1, false)) + { + delete[] m_scanRAMCache; + return Common::MemOperationReturnCode::operationFailed; + } + + if (filter == ScanFiter::unknownInitial) + { + m_resultCount = (ramSize - Common::getSizeForType(m_memType, static_cast(1))); + m_wasUnknownInitialValue = true; + m_memSize = 1; + m_scanStarted = true; + m_updatedRAMCache = new char[ramSize - 1]; + return Common::MemOperationReturnCode::OK; + } + + bool m_wasUnknownInitialValue = false; + Common::MemOperationReturnCode scanReturn = Common::MemOperationReturnCode::OK; + size_t termActualLength = 0; + size_t termMaxLength = 0; + if (m_memType == Common::MemType::type_string) + { + // This is just to have the string formatted with the appropriate length, byte arrays don't need + // this because they get copied byte per byte + termMaxLength = searchTerm1.length(); + } + else + { + // Have no restriction on the length for the rest + termMaxLength = ramSize; + } + char* memoryToCompare1 = Common::formatStringToMemory(scanReturn, termActualLength, searchTerm1, + m_memBase, m_memType, termMaxLength); + if (scanReturn != Common::MemOperationReturnCode::OK) + return scanReturn; + + char* memoryToCompare2 = nullptr; + if (filter == ScanFiter::between) + { + memoryToCompare2 = Common::formatStringToMemory(scanReturn, termActualLength, searchTerm2, + m_memBase, m_memType, ramSize); + if (scanReturn != Common::MemOperationReturnCode::OK) + return scanReturn; + } + + m_updatedRAMCache = new char[ramSize - 1]; + + bool withBSwap = Common::shouldBeBSwappedForType(m_memType); + + m_memSize = Common::getSizeForType(m_memType, termActualLength); + + char* noOffset = new char[m_memSize]; + std::memset(noOffset, 0, m_memSize); + + for (u32 i = 0; i < (ramSize - m_memSize); ++i) + { + char* memoryCandidate = &m_scanRAMCache[i]; + bool isResult = false; + switch (filter) + { + case ScanFiter::exact: + { + if (m_memType == Common::MemType::type_string || m_memType == Common::MemType::type_byteArray) + isResult = (std::memcmp(memoryCandidate, memoryToCompare1, m_memSize) == 0); + else + isResult = (compareMemoryAsNumbers(memoryCandidate, memoryToCompare1, noOffset, false, + false, m_memSize) == MemScanner::CompareResult::equal); + break; + } + case ScanFiter::between: + { + MemScanner::CompareResult result1 = compareMemoryAsNumbers(memoryCandidate, memoryToCompare1, + noOffset, false, false, m_memSize); + MemScanner::CompareResult result2 = compareMemoryAsNumbers(memoryCandidate, memoryToCompare2, + noOffset, false, false, m_memSize); + isResult = ((result1 == MemScanner::CompareResult::bigger || + result1 == MemScanner::CompareResult::equal) && + (result2 == MemScanner::CompareResult::smaller || + result2 == MemScanner::CompareResult::equal)); + break; + } + case ScanFiter::biggerThan: + { + isResult = (compareMemoryAsNumbers(memoryCandidate, memoryToCompare1, noOffset, false, false, + m_memSize) == MemScanner::CompareResult::bigger); + break; + } + case ScanFiter::smallerThan: + { + isResult = (compareMemoryAsNumbers(memoryCandidate, memoryToCompare1, noOffset, false, false, + m_memSize) == MemScanner::CompareResult::smaller); + break; + } + } + + if (isResult) + { + u32 consoleOffset = 0; + if (i >= Common::MEM1_SIZE) + consoleOffset = i + (Common::MEM2_START - Common::MEM1_END); + else + consoleOffset = i; + m_resultsConsoleAddr.push_back(Common::offsetToDolphinAddr(consoleOffset)); + } + } + delete[] noOffset; + m_resultCount = m_resultsConsoleAddr.size(); + m_scanStarted = true; + return Common::MemOperationReturnCode::OK; +} + +Common::MemOperationReturnCode MemScanner::nextScan(const MemScanner::ScanFiter filter, + const std::string& searchTerm1, + const std::string& searchTerm2) +{ + u32 ramSize = 0; + char* newerRAMCache = nullptr; + if (DolphinComm::DolphinAccessor::isMem2Enabled()) + { + ramSize = Common::MEM1_SIZE + Common::MEM2_SIZE; + newerRAMCache = new char[ramSize - 1]; + if (!DolphinComm::DolphinAccessor::readFromRAM(Common::dolphinAddrToOffset(Common::MEM2_START), + newerRAMCache + Common::MEM1_SIZE, + Common::MEM2_SIZE - 1, false)) + { + delete[] m_scanRAMCache; + delete[] newerRAMCache; + return Common::MemOperationReturnCode::operationFailed; + } + } + else + { + ramSize = Common::MEM1_SIZE; + newerRAMCache = new char[ramSize - 1]; + } + + if (!DolphinComm::DolphinAccessor::readFromRAM(Common::dolphinAddrToOffset(Common::MEM1_START), + newerRAMCache, Common::MEM1_SIZE - 1, false)) + { + delete[] m_scanRAMCache; + delete[] newerRAMCache; + return Common::MemOperationReturnCode::operationFailed; + } + + Common::MemOperationReturnCode scanReturn = Common::MemOperationReturnCode::OK; + size_t termActualLength = 0; + size_t termMaxLength = 0; + if (m_memType == Common::MemType::type_string) + { + // This is just to have the string formatted with the appropriate length, byte arrays don't need + // this because they get copied byte per byte + termMaxLength = searchTerm1.length(); + } + else + { + // Have no restriction on the length for the rest + termMaxLength = ramSize; + } + char* memoryToCompare1 = nullptr; + if (filter != ScanFiter::increased && filter != ScanFiter::decreased && + filter != ScanFiter::changed && filter != ScanFiter::unchanged) + { + memoryToCompare1 = Common::formatStringToMemory(scanReturn, termActualLength, searchTerm1, + m_memBase, m_memType, termMaxLength); + if (scanReturn != Common::MemOperationReturnCode::OK) + return scanReturn; + } + + char* memoryToCompare2 = nullptr; + if (filter == ScanFiter::between) + { + memoryToCompare2 = Common::formatStringToMemory(scanReturn, termActualLength, searchTerm2, + m_memBase, m_memType, ramSize); + if (scanReturn != Common::MemOperationReturnCode::OK) + return scanReturn; + } + + bool withBSwap = Common::shouldBeBSwappedForType(m_memType); + + m_memSize = Common::getSizeForType(m_memType, termActualLength); + + char* noOffset = new char[m_memSize]; + std::memset(noOffset, 0, m_memSize); + + std::vector newerResults = std::vector(); + + if (m_wasUnknownInitialValue) + { + m_wasUnknownInitialValue = false; + for (u32 i = 0; i < (ramSize - m_memSize); ++i) + { + u32 consoleOffset = 0; + if (i >= Common::MEM1_SIZE) + consoleOffset = i + (Common::MEM2_START - Common::MEM1_END); + else + consoleOffset = i; + if (isHitNextScan(filter, memoryToCompare1, memoryToCompare2, noOffset, newerRAMCache, + m_memSize, i)) + { + newerResults.push_back(Common::offsetToDolphinAddr(consoleOffset)); + } + } + } + else + { + for (auto i : m_resultsConsoleAddr) + { + u32 ramIndex = 0; + if (Common::dolphinAddrToOffset(i) >= Common::MEM1_SIZE) + ramIndex = Common::dolphinAddrToOffset(i) - (Common::MEM2_START - Common::MEM1_END); + else + ramIndex = Common::dolphinAddrToOffset(i); + if (isHitNextScan(filter, memoryToCompare1, memoryToCompare2, noOffset, newerRAMCache, + m_memSize, ramIndex)) + { + newerResults.push_back(i); + } + } + } + + delete[] noOffset; + m_resultsConsoleAddr.clear(); + std::swap(m_resultsConsoleAddr, newerResults); + delete[] m_scanRAMCache; + m_scanRAMCache = newerRAMCache; + m_resultCount = m_resultsConsoleAddr.size(); + return Common::MemOperationReturnCode::OK; +} + +void MemScanner::reset() +{ + m_resultsConsoleAddr.clear(); + m_wasUnknownInitialValue = false; + if (m_scanRAMCache != nullptr) + delete[] m_scanRAMCache; + if (m_updatedRAMCache != nullptr) + delete[] m_updatedRAMCache; + m_resultCount = 0; + m_scanStarted = false; +} + +inline bool MemScanner::isHitNextScan(const MemScanner::ScanFiter filter, + const char* memoryToCompare1, const char* memoryToCompare2, + const char* noOffset, const char* newerRAMCache, + const size_t realSize, const u32 consoleOffset) const +{ + char* olderMemory = &m_scanRAMCache[consoleOffset]; + const char* newerMemory = &newerRAMCache[consoleOffset]; + + switch (filter) + { + case ScanFiter::exact: + { + if (m_memType == Common::MemType::type_string || m_memType == Common::MemType::type_byteArray) + return (std::memcmp(newerMemory, memoryToCompare1, realSize) == 0); + else + return (compareMemoryAsNumbers(newerMemory, memoryToCompare1, noOffset, false, false, + realSize) == MemScanner::CompareResult::equal); + break; + } + case ScanFiter::between: + { + MemScanner::CompareResult result1 = + compareMemoryAsNumbers(newerMemory, memoryToCompare1, noOffset, false, false, realSize); + MemScanner::CompareResult result2 = + compareMemoryAsNumbers(newerMemory, memoryToCompare2, noOffset, false, false, realSize); + return ((result1 == MemScanner::CompareResult::bigger || + result1 == MemScanner::CompareResult::equal) && + (result2 == MemScanner::CompareResult::smaller || + result2 == MemScanner::CompareResult::equal)); + break; + } + case ScanFiter::biggerThan: + { + return (compareMemoryAsNumbers(newerMemory, memoryToCompare1, noOffset, false, false, + realSize) == MemScanner::CompareResult::bigger); + break; + } + case ScanFiter::smallerThan: + { + return (compareMemoryAsNumbers(newerMemory, memoryToCompare1, noOffset, false, false, + realSize) == MemScanner::CompareResult::smaller); + break; + } + case ScanFiter::increasedBy: + { + return (compareMemoryAsNumbers(newerMemory, olderMemory, memoryToCompare1, false, true, + realSize) == MemScanner::CompareResult::equal); + break; + } + case ScanFiter::decreasedBy: + { + return (compareMemoryAsNumbers(newerMemory, olderMemory, memoryToCompare1, true, true, + realSize) == MemScanner::CompareResult::equal); + break; + } + case ScanFiter::increased: + { + return (compareMemoryAsNumbers(newerMemory, olderMemory, noOffset, false, true, realSize) == + MemScanner::CompareResult::bigger); + break; + } + case ScanFiter::decreased: + { + return (compareMemoryAsNumbers(newerMemory, olderMemory, noOffset, false, true, realSize) == + MemScanner::CompareResult::smaller); + break; + } + case ScanFiter::changed: + { + MemScanner::CompareResult result = + compareMemoryAsNumbers(newerMemory, olderMemory, noOffset, false, true, realSize); + return (result == MemScanner::CompareResult::bigger || + result == MemScanner::CompareResult::smaller); + break; + } + case ScanFiter::unchanged: + { + return (compareMemoryAsNumbers(newerMemory, olderMemory, noOffset, false, true, realSize) == + MemScanner::CompareResult::equal); + break; + } + default: + { + return false; + } + } +} + +inline MemScanner::CompareResult +MemScanner::compareMemoryAsNumbers(const char* first, const char* second, const char* offset, + bool offsetInvert, bool bswapSecond, size_t length) const +{ + switch (m_memType) + { + case Common::MemType::type_byte: + { + if (m_memIsSigned) + { + return compareMemoryAsNumbersWithType(first, second, offset, offsetInvert, bswapSecond); + } + return compareMemoryAsNumbersWithType(first, second, offset, offsetInvert, bswapSecond); + break; + } + case Common::MemType::type_halfword: + { + if (m_memIsSigned) + { + return compareMemoryAsNumbersWithType(first, second, offset, offsetInvert, bswapSecond); + } + return compareMemoryAsNumbersWithType(first, second, offset, offsetInvert, bswapSecond); + break; + } + case Common::MemType::type_word: + { + if (m_memIsSigned) + { + return compareMemoryAsNumbersWithType(first, second, offset, offsetInvert, bswapSecond); + } + return compareMemoryAsNumbersWithType(first, second, offset, offsetInvert, bswapSecond); + break; + } + case Common::MemType::type_float: + { + return compareMemoryAsNumbersWithType(first, second, offset, offsetInvert, bswapSecond); + break; + } + case Common::MemType::type_double: + { + return compareMemoryAsNumbersWithType(first, second, offset, offsetInvert, bswapSecond); + break; + } + default: + { + return MemScanner::CompareResult::nan; + } + } +} + +void MemScanner::setType(const Common::MemType type) +{ + m_memType = type; +} + +void MemScanner::setBase(const Common::MemBase base) +{ + m_memBase = base; +} + +void MemScanner::setIsSigned(const bool isSigned) +{ + m_memIsSigned = isSigned; +} + +int MemScanner::getTermsNumForFilter(const MemScanner::ScanFiter filter) const +{ + if (filter == MemScanner::ScanFiter::between) + return 2; + else if (filter == MemScanner::ScanFiter::exact || filter == MemScanner::ScanFiter::increasedBy || + filter == MemScanner::ScanFiter::decreasedBy || + filter == MemScanner::ScanFiter::biggerThan || + filter == MemScanner::ScanFiter::smallerThan) + return 1; + return 0; +} + +bool MemScanner::typeSupportsAdditionalOptions(const Common::MemType type) const +{ + return (type == Common::MemType::type_byte || type == Common::MemType::type_halfword || + type == Common::MemType::type_word); +} + +std::vector MemScanner::getResultsConsoleAddr() const +{ + return m_resultsConsoleAddr; +} + +std::string MemScanner::getFormattedScannedValueAt(const int index) const +{ + u32 offset = Common::dolphinAddrToOffset(m_resultsConsoleAddr.at(index)); + u32 ramIndex = 0; + if (offset >= Common::MEM1_SIZE) + ramIndex = offset - (Common::MEM2_START - Common::MEM1_END); + else + ramIndex = offset; + return Common::formatMemoryToString(&m_scanRAMCache[ramIndex], m_memType, m_memSize, m_memBase, + !m_memIsSigned, Common::shouldBeBSwappedForType(m_memType)); +} + +Common::MemOperationReturnCode MemScanner::updateCurrentRAMCache() +{ + if (DolphinComm::DolphinAccessor::isMem2Enabled()) + { + if (!DolphinComm::DolphinAccessor::readFromRAM(Common::dolphinAddrToOffset(Common::MEM2_START), + m_updatedRAMCache + Common::MEM1_SIZE, + Common::MEM2_SIZE - 1, false)) + return Common::MemOperationReturnCode::operationFailed; + } + if (!DolphinComm::DolphinAccessor::readFromRAM(0, m_updatedRAMCache, Common::MEM1_SIZE - 1, + false)) + return Common::MemOperationReturnCode::operationFailed; + return Common::MemOperationReturnCode::OK; +} + +std::string MemScanner::getFormattedCurrentValueAt(const int index) const +{ + u32 offset = Common::dolphinAddrToOffset(m_resultsConsoleAddr.at(index)); + u32 ramIndex = 0; + if (offset >= Common::MEM1_SIZE) + ramIndex = offset - (Common::MEM2_START - Common::MEM1_END); + else + ramIndex = offset; + return Common::formatMemoryToString(&m_updatedRAMCache[ramIndex], m_memType, m_memSize, m_memBase, + !m_memIsSigned, Common::shouldBeBSwappedForType(m_memType)); +} + +size_t MemScanner::getResultCount() const +{ + return m_resultCount; +} + +bool MemScanner::hasScanStarted() const +{ + return m_scanStarted; +} + +Common::MemType MemScanner::getType() const +{ + return m_memType; +} + +Common::MemBase MemScanner::getBase() const +{ + return m_memBase; +} + +size_t MemScanner::getLength() const +{ + return m_memSize; +} + +bool MemScanner::getIsUnsigned() const +{ + return !m_memIsSigned; +} \ No newline at end of file diff --git a/Source/MemoryScanner/MemoryScanner.h b/Source/MemoryScanner/MemoryScanner.h new file mode 100644 index 00000000..8073cdd4 --- /dev/null +++ b/Source/MemoryScanner/MemoryScanner.h @@ -0,0 +1,158 @@ +#pragma once + +#include +#include +#include + +#include "../Common/CommonTypes.h" +#include "../Common/CommonUtils.h" +#include "../Common/MemoryCommon.h" + +class MemScanner +{ +public: + enum class ScanFiter + { + exact = 0, + increasedBy, + decreasedBy, + between, + biggerThan, + smallerThan, + increased, + decreased, + changed, + unchanged, + unknownInitial + }; + + enum class CompareResult + { + bigger, + smaller, + equal, + nan + }; + + MemScanner(); + ~MemScanner(); + Common::MemOperationReturnCode firstScan(const ScanFiter filter, const std::string& searchTerm1, + const std::string& searchTerm2); + Common::MemOperationReturnCode nextScan(const ScanFiter filter, const std::string& searchTerm1, + const std::string& searchTerm2); + void reset(); + inline CompareResult compareMemoryAsNumbers(const char* first, const char* second, + const char* offset, bool offsetInvert, + bool bswapSecond, size_t length) const; + + template + inline T convertMemoryToType(const char* memory, bool invert) const + { + T theType; + std::memcpy(&theType, memory, sizeof(T)); + if (invert) + theType *= -1; + return theType; + } + + template + inline CompareResult compareMemoryAsNumbersWithType(const char* first, const char* second, + const char* offset, bool offsetInvert, + bool bswapSecond) const + { + T firstByte; + T secondByte; + std::memcpy(&firstByte, first, sizeof(T)); + std::memcpy(&secondByte, second, sizeof(T)); + size_t size = sizeof(T); + switch (size) + { + case 2: + { + u16 firstHalfword = 0; + std::memcpy(&firstHalfword, &firstByte, sizeof(u16)); + firstHalfword = Common::bSwap16(firstHalfword); + std::memcpy(&firstByte, &firstHalfword, sizeof(u16)); + if (bswapSecond) + { + std::memcpy(&firstHalfword, &secondByte, sizeof(u16)); + firstHalfword = Common::bSwap16(firstHalfword); + std::memcpy(&secondByte, &firstHalfword, sizeof(u16)); + } + break; + } + case 4: + { + u32 firstWord = 0; + std::memcpy(&firstWord, &firstByte, sizeof(u32)); + firstWord = Common::bSwap32(firstWord); + std::memcpy(&firstByte, &firstWord, sizeof(u32)); + if (bswapSecond) + { + std::memcpy(&firstWord, &secondByte, sizeof(u32)); + firstWord = Common::bSwap32(firstWord); + std::memcpy(&secondByte, &firstWord, sizeof(u32)); + } + break; + } + case 8: + { + u64 firstDoubleword = 0; + std::memcpy(&firstDoubleword, &firstByte, sizeof(u64)); + firstDoubleword = Common::bSwap64(firstDoubleword); + std::memcpy(&firstByte, &firstDoubleword, sizeof(u64)); + if (bswapSecond) + { + std::memcpy(&firstDoubleword, &secondByte, sizeof(u64)); + firstDoubleword = Common::bSwap64(firstDoubleword); + std::memcpy(&secondByte, &firstDoubleword, sizeof(u64)); + } + break; + } + } + + if (firstByte != firstByte) + return CompareResult::nan; + + if (firstByte < (secondByte + convertMemoryToType(offset, offsetInvert))) + return CompareResult::smaller; + else if (firstByte > (secondByte + convertMemoryToType(offset, offsetInvert))) + return CompareResult::bigger; + else + return CompareResult::equal; + } + + void setType(const Common::MemType type); + void setBase(const Common::MemBase base); + void setIsSigned(const bool isSigned); + + std::vector getResultsConsoleAddr() const; + size_t getResultCount() const; + int getTermsNumForFilter(const ScanFiter filter) const; + Common::MemType getType() const; + Common::MemBase getBase() const; + size_t getLength() const; + bool getIsUnsigned() const; + std::string getFormattedScannedValueAt(const int index) const; + Common::MemOperationReturnCode updateCurrentRAMCache(); + std::string getFormattedCurrentValueAt(int index) const; + bool typeSupportsAdditionalOptions(const Common::MemType type) const; + bool hasScanStarted() const; + +private: + inline bool isHitNextScan(const ScanFiter filter, const char* memoryToCompare1, + const char* memoryToCompare2, const char* noOffset, + const char* newerRAMCache, const size_t realSize, + const u32 consoleOffset) const; + + Common::MemType m_memType = Common::MemType::type_byte; + Common::MemBase m_memBase = Common::MemBase::base_decimal; + size_t m_memSize; + bool m_memIsSigned = false; + std::vector m_resultsConsoleAddr; + bool m_wasUnknownInitialValue = false; + size_t m_resultCount = 0; + char* m_scanRAMCache = nullptr; + char* m_updatedRAMCache = nullptr; + bool m_scanStarted = false; +}; \ No newline at end of file diff --git a/Source/MemoryWatch/MemoryWatch.cpp b/Source/MemoryWatch/MemoryWatch.cpp new file mode 100644 index 00000000..8f7429a9 --- /dev/null +++ b/Source/MemoryWatch/MemoryWatch.cpp @@ -0,0 +1,302 @@ +#include "MemoryWatch.h" + +#include +#include +#include +#include +#include +#include + +#include "../Common/CommonUtils.h" +#include "../DolphinProcess/DolphinAccessor.h" + +MemWatchEntry::MemWatchEntry(const std::string label, const u32 consoleAddress, + const Common::MemType type, const Common::MemBase base, + const bool isUnsigned, const size_t length, + const bool isBoundToPointer) + : m_label(label), m_consoleAddress(consoleAddress), m_type(type), m_isUnsigned(isUnsigned), + m_base(base), m_boundToPointer(isBoundToPointer), m_length(length) +{ + m_memory = new char[getSizeForType(m_type, m_length)]; +} + +MemWatchEntry::MemWatchEntry() +{ + m_type = Common::MemType::type_byte; + m_memory = new char[sizeof(u8)]; + m_consoleAddress = 0x80000000; +} + +MemWatchEntry::MemWatchEntry(MemWatchEntry* entry) + : m_label(entry->m_label), m_consoleAddress(entry->m_consoleAddress), m_type(entry->m_type), + m_isUnsigned(entry->m_isUnsigned), m_base(entry->m_base), + m_boundToPointer(entry->m_boundToPointer), m_pointerOffsets(entry->m_pointerOffsets), + m_length(entry->m_length), m_isValidPointer(entry->m_isValidPointer) +{ + m_memory = new char[getSizeForType(entry->getType(), entry->getLength())]; + std::memcpy(m_memory, entry->getMemory(), getSizeForType(entry->getType(), entry->getLength())); +} + +MemWatchEntry::~MemWatchEntry() +{ + if (m_memory != nullptr) + delete[] m_memory; +} + +std::string MemWatchEntry::getLabel() const +{ + return m_label; +} + +size_t MemWatchEntry::getLength() const +{ + return m_length; +} + +Common::MemType MemWatchEntry::getType() const +{ + return m_type; +} + +u32 MemWatchEntry::getConsoleAddress() const +{ + return m_consoleAddress; +} + +bool MemWatchEntry::isLocked() const +{ + return m_lock; +} + +bool MemWatchEntry::isBoundToPointer() const +{ + return m_boundToPointer; +} + +Common::MemBase MemWatchEntry::getBase() const +{ + return m_base; +} + +int MemWatchEntry::getPointerOffset(const int index) const +{ + return m_pointerOffsets.at(index); +} + +std::vector MemWatchEntry::getPointerOffsets() const +{ + return m_pointerOffsets; +} + +size_t MemWatchEntry::getPointerLevel() const +{ + return m_pointerOffsets.size(); +} + +char* MemWatchEntry::getMemory() const +{ + return m_memory; +} + +bool MemWatchEntry::isUnsigned() const +{ + return m_isUnsigned; +} + +void MemWatchEntry::setLabel(const std::string& label) +{ + m_label = label; +} + +void MemWatchEntry::setConsoleAddress(const u32 address) +{ + m_consoleAddress = address; +} + +void MemWatchEntry::setType(const Common::MemType type) +{ + size_t oldSize = getSizeForType(m_type, m_length); + m_type = type; + size_t newSize = getSizeForType(m_type, m_length); + if (oldSize != newSize) + { + delete[] m_memory; + m_memory = new char[newSize]; + } +} + +void MemWatchEntry::setBase(const Common::MemBase base) +{ + m_base = base; +} + +void MemWatchEntry::setLock(const bool doLock) +{ + m_lock = doLock; + if (m_lock) + { + if (readMemoryFromRAM() == Common::MemOperationReturnCode::OK) + { + m_freezeMemSize = getSizeForType(m_type, m_length); + m_freezeMemory = new char[m_freezeMemSize]; + std::memcpy(m_freezeMemory, m_memory, m_freezeMemSize); + } + } + else if (m_freezeMemory != nullptr) + { + m_freezeMemSize = 0; + delete[] m_freezeMemory; + } +} + +void MemWatchEntry::setLength(const size_t length) +{ + m_length = length; +} + +void MemWatchEntry::setSignedUnsigned(const bool isUnsigned) +{ + m_isUnsigned = isUnsigned; +} + +void MemWatchEntry::setBoundToPointer(const bool boundToPointer) +{ + m_boundToPointer = boundToPointer; +} + +void MemWatchEntry::setPointerOffset(const int pointerOffset, int index) +{ + m_pointerOffsets[index] = pointerOffset; +} + +void MemWatchEntry::removeOffset() +{ + m_pointerOffsets.pop_back(); +} + +void MemWatchEntry::addOffset(const int offset) +{ + m_pointerOffsets.push_back(offset); +} + +Common::MemOperationReturnCode MemWatchEntry::freeze() +{ + Common::MemOperationReturnCode writeCode = writeMemoryToRAM(m_freezeMemory, m_freezeMemSize); + return writeCode; +} + +Common::MemOperationReturnCode MemWatchEntry::readMemoryFromRAM() +{ + u32 realConsoleAddress = m_consoleAddress; + if (m_boundToPointer) + { + char realConsoleAddressBuffer[sizeof(u32)] = {0}; + for (int offset : m_pointerOffsets) + { + if (DolphinComm::DolphinAccessor::readFromRAM(Common::dolphinAddrToOffset(realConsoleAddress), + realConsoleAddressBuffer, sizeof(u32), true)) + { + std::memcpy(&realConsoleAddress, realConsoleAddressBuffer, sizeof(u32)); + if (DolphinComm::DolphinAccessor::isValidConsoleAddress(realConsoleAddress)) + { + realConsoleAddress += offset; + } + else + { + m_isValidPointer = false; + return Common::MemOperationReturnCode::invalidPointer; + } + } + else + { + return Common::MemOperationReturnCode::operationFailed; + } + } + // Resolve sucessful + m_isValidPointer = true; + } + if (DolphinComm::DolphinAccessor::readFromRAM(Common::dolphinAddrToOffset(realConsoleAddress), + m_memory, getSizeForType(m_type, m_length), + shouldBeBSwappedForType(m_type))) + { + return Common::MemOperationReturnCode::OK; + } + else + { + return Common::MemOperationReturnCode::operationFailed; + } +} + +Common::MemOperationReturnCode MemWatchEntry::writeMemoryToRAM(const char* memory, + const size_t size) +{ + u32 realConsoleAddress = m_consoleAddress; + if (m_boundToPointer) + { + char realConsoleAddressBuffer[sizeof(u32)] = {0}; + for (int offset : m_pointerOffsets) + { + if (DolphinComm::DolphinAccessor::readFromRAM(Common::dolphinAddrToOffset(realConsoleAddress), + realConsoleAddressBuffer, sizeof(u32), true)) + { + std::memcpy(&realConsoleAddress, realConsoleAddressBuffer, sizeof(u32)); + if (DolphinComm::DolphinAccessor::isValidConsoleAddress(realConsoleAddress)) + { + realConsoleAddress += offset; + } + else + { + m_isValidPointer = false; + return Common::MemOperationReturnCode::invalidPointer; + } + } + else + { + return Common::MemOperationReturnCode::operationFailed; + } + } + // Resolve sucessful + m_isValidPointer = true; + } + + if (DolphinComm::DolphinAccessor::writeToRAM(Common::dolphinAddrToOffset(realConsoleAddress), + memory, size, shouldBeBSwappedForType(m_type))) + { + return Common::MemOperationReturnCode::OK; + } + else + { + return Common::MemOperationReturnCode::operationFailed; + } +} + +std::string MemWatchEntry::getStringFromMemory() const +{ + if (m_boundToPointer && !m_isValidPointer) + return "???"; + return Common::formatMemoryToString(m_memory, m_type, m_length, m_base, m_isUnsigned); +} + +Common::MemOperationReturnCode MemWatchEntry::writeMemoryFromString(const std::string& inputString) +{ + Common::MemOperationReturnCode writeReturn = Common::MemOperationReturnCode::OK; + size_t sizeToWrite = 0; + char* buffer = + Common::formatStringToMemory(writeReturn, sizeToWrite, inputString, m_base, m_type, m_length); + if (writeReturn != Common::MemOperationReturnCode::OK) + return writeReturn; + + writeReturn = writeMemoryToRAM(buffer, sizeToWrite); + if (writeReturn == Common::MemOperationReturnCode::OK) + { + if (m_lock) + std::memcpy(m_freezeMemory, buffer, m_freezeMemSize); + delete[] buffer; + return writeReturn; + } + else + { + delete[] buffer; + return writeReturn; + } +} \ No newline at end of file diff --git a/Source/MemoryWatch/MemoryWatch.h b/Source/MemoryWatch/MemoryWatch.h new file mode 100644 index 00000000..0189ce2b --- /dev/null +++ b/Source/MemoryWatch/MemoryWatch.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include "../Common/CommonTypes.h" +#include "../Common/MemoryCommon.h" + +class MemWatchEntry +{ +public: + MemWatchEntry(); + MemWatchEntry(const std::string label, const u32 consoleAddress, const Common::MemType type, + const Common::MemBase = Common::MemBase::base_decimal, + const bool m_isUnsigned = false, const size_t length = 1, + const bool isBoundToPointer = false); + MemWatchEntry(MemWatchEntry* entry); + ~MemWatchEntry(); + + std::string getLabel() const; + Common::MemType getType() const; + u32 getConsoleAddress() const; + bool isLocked() const; + bool isBoundToPointer() const; + Common::MemBase getBase() const; + size_t getLength() const; + char* getMemory() const; + bool isUnsigned() const; + int getPointerOffset(const int index) const; + std::vector getPointerOffsets() const; + size_t getPointerLevel() const; + void setLabel(const std::string& label); + void setConsoleAddress(const u32 address); + void setType(const Common::MemType type); + void setBase(const Common::MemBase base); + void setLock(const bool doLock); + void setLength(const size_t length); + void setSignedUnsigned(const bool isUnsigned); + void setBoundToPointer(const bool boundToPointer); + void setPointerOffset(const int pointerOffset, const int index); + void addOffset(const int offset); + void removeOffset(); + + Common::MemOperationReturnCode freeze(); + + Common::MemOperationReturnCode readMemoryFromRAM(); + + std::string getStringFromMemory() const; + Common::MemOperationReturnCode writeMemoryFromString(const std::string& inputString); + +private: + Common::MemOperationReturnCode writeMemoryToRAM(const char* memory, const size_t size); + + std::string m_label; + u32 m_consoleAddress; + bool m_lock = false; + Common::MemType m_type; + Common::MemBase m_base; + bool m_isUnsigned; + bool m_boundToPointer = false; + std::vector m_pointerOffsets; + bool m_isValidPointer = false; + char* m_memory; + char* m_freezeMemory = nullptr; + size_t m_freezeMemSize = 0; + size_t m_length = 1; +}; \ No newline at end of file diff --git a/Source/main.cpp b/Source/main.cpp new file mode 100644 index 00000000..2db327c7 --- /dev/null +++ b/Source/main.cpp @@ -0,0 +1,11 @@ +#include + +#include "GUI/MainWindow.h" + +int main(int argc, char** argv) +{ + QApplication app(argc, argv); + MainWindow window; + window.show(); + return app.exec(); +} \ No newline at end of file diff --git a/Tools/format.sh b/Tools/format.sh new file mode 100755 index 00000000..7b1fc084 --- /dev/null +++ b/Tools/format.sh @@ -0,0 +1,2 @@ +#!/bin/bash +find ../Source -iname *.h -o -iname *.cpp | xargs clang-format -i