diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index 98970ec1..a6540c5a 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: jobs: - prettier: + build: name: Automated prettier runs-on: ubuntu-latest @@ -17,7 +17,8 @@ jobs: prettier_options: --write resources/Spelunky2.json commit_message: "Automated prettier changes" - build: + notify: + needs: build name: Autobuild runs-on: windows-latest @@ -33,7 +34,7 @@ jobs: uses: actions/cache/restore@v3 with: path: C:\Qt - key: ${{ runner.os }}-Qt563 # don't think QT version will change? + key: ${{ runner.os }}-Qt563 - name: Download Qt if Needed if: steps.cache-qt.outputs.cache-hit != 'true' @@ -48,7 +49,7 @@ jobs: uses: actions/cache/save@v3 with: path: C:\Qt - key: ${{ runner.os }}-Qt563 # don't think QT version will change? + key: ${{ runner.os }}-Qt563 - name: Build run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 71aaa67a..3346f0cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ include(CPM.cmake) CPMAddPackage( NAME x64dbg - SOURCE_DIR "${CMAKE_HOME_DIRECTORY}/3rdParty/x64dbg-src" + SYSTEM SOURCE_DIR "${CMAKE_HOME_DIRECTORY}/3rdParty/x64dbg-src" ) set(Qt5Core_DIR "${Qt5ROOT}/lib/cmake/Qt5Core") @@ -67,15 +67,12 @@ x64dbg_plugin(${PROJECT_NAME} include/Views/ViewTextureDB.h include/Views/ViewEntity.h include/Views/ViewEntities.h - include/Views/ViewState.h - include/Views/ViewSaveGame.h - include/Views/ViewGameManager.h + include/Views/ViewStruct.h include/Views/ViewLevelGen.h include/Views/ViewVirtualTable.h include/Views/ViewStringsTable.h include/Views/ViewLogger.h include/Views/ViewVirtualFunctions.h - include/Views/ViewOnline.h include/Views/ViewStdVector.h include/Views/ViewJournalPage.h include/Views/ViewThreads.h @@ -88,6 +85,7 @@ x64dbg_plugin(${PROJECT_NAME} include/QtHelpers/WidgetSpelunkyRooms.h include/QtHelpers/DialogEditSimpleValue.h include/QtHelpers/DialogEditState.h + include/QtHelpers/DialogEditString.h include/QtHelpers/TableWidgetItemNumeric.h include/QtHelpers/TreeWidgetItemNumeric.h include/QtHelpers/ItemModelVirtualTable.h @@ -101,7 +99,9 @@ x64dbg_plugin(${PROJECT_NAME} include/QtHelpers/WidgetSampling.h include/QtHelpers/WidgetSamplesPlot.h include/QtHelpers/ItemModelLoggerSamples.h - include/QtHelpers/DatabaseHelper.h + include/QtHelpers/WidgetDatabaseView.h + include/QtHelpers/WidgetAutorefresh.h + include/QtHelpers/LongLongSpinBox.h src/Spelunky2.cpp src/Configuration.cpp src/Data/EntityDB.cpp @@ -121,41 +121,35 @@ x64dbg_plugin(${PROJECT_NAME} src/Views/ViewTextureDB.cpp src/Views/ViewEntity.cpp src/Views/ViewEntities.cpp - src/Views/ViewState.cpp - src/Views/ViewSaveGame.cpp - src/Views/ViewGameManager.cpp + src/Views/ViewStruct.cpp src/Views/ViewLevelGen.cpp src/Views/ViewVirtualTable.cpp src/Views/ViewStringsTable.cpp src/Views/ViewLogger.cpp src/Views/ViewVirtualFunctions.cpp - src/Views/ViewOnline.cpp src/Views/ViewStdVector.cpp src/Views/ViewStdMap.cpp src/Views/ViewJournalPage.cpp src/Views/ViewThreads.cpp src/QtHelpers/StyledItemDelegateHTML.cpp - src/QtHelpers/StyledItemDelegateColorPicker.cpp src/QtHelpers/TreeViewMemoryFields.cpp src/QtHelpers/WidgetMemoryView.cpp src/QtHelpers/WidgetSpelunkyLevel.cpp src/QtHelpers/WidgetSpelunkyRooms.cpp src/QtHelpers/DialogEditSimpleValue.cpp src/QtHelpers/DialogEditState.cpp - src/QtHelpers/TableWidgetItemNumeric.cpp - src/QtHelpers/TreeWidgetItemNumeric.cpp + src/QtHelpers/DialogEditString.cpp src/QtHelpers/ItemModelVirtualTable.cpp src/QtHelpers/ItemModelVirtualFunctions.cpp src/QtHelpers/SortFilterProxyModelStringsTable.cpp - src/QtHelpers/ItemModelStates.cpp src/QtHelpers/ItemModelGatherVirtualData.cpp src/QtHelpers/CPPSyntaxHighlighter.cpp src/QtHelpers/TableViewLogger.cpp src/QtHelpers/ItemModelLoggerFields.cpp - src/QtHelpers/WidgetSampling.cpp src/QtHelpers/WidgetSamplesPlot.cpp src/QtHelpers/ItemModelLoggerSamples.cpp - src/QtHelpers/DatabaseHelper.cpp + src/QtHelpers/WidgetDatabaseView.cpp + src/QtHelpers/WidgetAutorefresh.cpp ${CMAKE_CURRENT_BINARY_DIR}/include/pluginconfig.h resources/spelunky2.qrc ) @@ -171,7 +165,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core # Set the plugin as the startup project set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) -target_compile_options(Spelunky2 PRIVATE /W4) +target_compile_options(${CMAKE_PROJECT_NAME} PRIVATE /W4) # Copy the plugin to the x64dbg plugins folder diff --git a/include/Configuration.h b/include/Configuration.h index c43d14ff..6cd000a2 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -27,7 +26,7 @@ namespace S2Plugin * [[ Roles explanation: ]] * The first 5 roles are all saved to the name field * those are used as information about the row - * memory address in the name field are used just for row update and should not be used for anything else + * memory address in the name field are used just for row update and shouldn't really be used for anything else * * value, comparison value, memoryaddress and delta fields all should contain the `gsRoleRawValue` data * (may differ with some special types) @@ -53,10 +52,6 @@ namespace S2Plugin constexpr uint16_t gsRoleSize = Qt::UserRole + 10; constexpr uint16_t gsRoleEntityAddress = Qt::UserRole + 11; // for entity uid to not look for the uid twice - constexpr char* gsJSONDragDropMemoryField_UID = "uid"; - constexpr char* gsJSONDragDropMemoryField_Address = "addr"; - constexpr char* gsJSONDragDropMemoryField_Type = "type"; - // new types need to be added to // - the MemoryFieldType enum // - gsMemoryFieldType in Configuration.cpp @@ -116,7 +111,7 @@ namespace S2Plugin EntitySubclass, // a subclass of an entity defined in json DefaultStructType, // a struct defined in json UndeterminedThemeInfoPointer, // used to look up the theme pointer in the levelgen and show the correct theme name - ThemeInfoName, // same as above, but does not add struct tree + ThemeInfoPointer, // same as above, but does not add struct tree LevelGenRoomsPointer, // used to make the level gen rooms title clickable LevelGenRoomsMetaPointer, // used to make the level gen rooms title clickable JournalPagePointer, // used to make journal page in vector clickable @@ -156,6 +151,7 @@ namespace S2Plugin std::string firstParameterType; std::string secondParameterType; std::string comment; + // size in bytes size_t get_size() const; // For checking duplicate names @@ -177,8 +173,8 @@ namespace S2Plugin RoomCode(uint16_t _id, std::string _name, QColor _color) : id(_id), name(_name), color(_color){}; }; - Q_DECLARE_METATYPE(S2Plugin::MemoryFieldType) - Q_DECLARE_METATYPE(std::string) + Q_DECLARE_METATYPE(S2Plugin::MemoryFieldType); + Q_DECLARE_METATYPE(std::string); class Configuration { @@ -221,7 +217,7 @@ namespace S2Plugin int getAlingment(const std::string& type) const; bool isPermanentPointer(const std::string& type) const { - return mPointerTypes.find(type) != mPointerTypes.end(); + return std::find(mPointerTypes.begin(), mPointerTypes.end(), type) != mPointerTypes.end(); } bool isJsonStruct(const std::string type) const { @@ -246,6 +242,11 @@ namespace S2Plugin RoomCode roomCodeForID(uint16_t code) const; std::string getEntityName(uint32_t type) const; + const std::vector& getJournalPageNames() + { + return mJournalPages; + } + private: static Configuration* ptr; bool initialisedCorrectly = false; @@ -257,7 +258,9 @@ namespace S2Plugin std::unordered_map> mTypeFieldsMain; std::unordered_map> mTypeFieldsEntitySubclasses; std::unordered_map> mTypeFieldsStructs; - std::unordered_set mPointerTypes; // pointers defined in pointer_types in json + std::vector mPointerTypes; // pointers defined in pointer_types in json + + std::vector mJournalPages; std::unordered_map mTypeFieldsStructsSizes; diff --git a/include/Data/Entity.h b/include/Data/Entity.h index 242e7453..cb9d09d9 100644 --- a/include/Data/Entity.h +++ b/include/Data/Entity.h @@ -6,6 +6,9 @@ namespace S2Plugin { + constexpr size_t gSmallEntityBucket = 0xD0; + constexpr size_t gBigEntityBucket = 0x188; + class Entity { public: diff --git a/include/Data/IDNameList.h b/include/Data/IDNameList.h index 9f85de98..73cd0d0a 100644 --- a/include/Data/IDNameList.h +++ b/include/Data/IDNameList.h @@ -10,9 +10,10 @@ namespace S2Plugin { class IDNameList { - public: + protected: IDNameList(const std::string& relFilePath, const std::regex& regex); + public: uint32_t idForName(const std::string& name) const; std::string nameForID(uint32_t id) const; uint32_t highestID() const noexcept @@ -23,7 +24,7 @@ namespace S2Plugin { return mEntries.size(); } - QStringList names() const noexcept + const QStringList& names() const noexcept { return mNames; } diff --git a/include/Data/Logger.h b/include/Data/Logger.h index 95b520da..a5c485bb 100644 --- a/include/Data/Logger.h +++ b/include/Data/Logger.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -43,7 +44,7 @@ namespace S2Plugin } void addField(const LoggerField& field); - void removeFieldAt(size_t fieldIndex); + void removeFieldAt(int fieldIndex); void updateFieldColor(size_t fieldIndex, const QColor& newColor) { mFields.at(fieldIndex).color = newColor; @@ -63,7 +64,7 @@ namespace S2Plugin } std::pair sampleBounds(const LoggerField& field) const; - void start(size_t samplePeriod, size_t duration); + void start(int samplePeriod, int duration); signals: void samplingEnded(); diff --git a/include/Data/State.h b/include/Data/State.h index 4003717f..6706aec0 100644 --- a/include/Data/State.h +++ b/include/Data/State.h @@ -1,8 +1,6 @@ #pragma once #include -#include -#include namespace S2Plugin { diff --git a/include/Data/StdMap.h b/include/Data/StdMap.h index 5c14f05b..96853f6c 100644 --- a/include/Data/StdMap.h +++ b/include/Data/StdMap.h @@ -16,7 +16,7 @@ namespace S2Plugin struct StdMap { // only for the template - StdMap(size_t addr) : address(addr) + StdMap(uintptr_t addr) : address(addr) { keytype_size = sizeof(Key); valuetype_size = sizeof(Value); @@ -24,13 +24,13 @@ namespace S2Plugin }; // value size only needed for value() function - StdMap(size_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize) : address(addr) + StdMap(uintptr_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize) : address(addr) { keytype_size = keySize; valuetype_size = sizeof(Value); set_offsets(keyAlignment, valueAlignment); }; - StdMap(size_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize, size_t valueSize) : address(addr) + StdMap(uintptr_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize, size_t valueSize) : address(addr) { keytype_size = keySize; valuetype_size = valueSize; @@ -72,11 +72,11 @@ namespace S2Plugin } return (Value)Script::Memory::ReadQword(value_address); } - size_t key_ptr() const + uintptr_t key_ptr() const { return node_ptr + parent_map->key_offset; } - size_t value_ptr() const + uintptr_t value_ptr() const { return node_ptr + parent_map->value_offset; } @@ -103,6 +103,11 @@ namespace S2Plugin { return (bool)Script::Memory::ReadByte(node_ptr + 0x19); } + // returning value ptr instead of value itself since it's more usefull for us + std::pair operator*() + { + return {key(), value_ptr()}; + } Node operator++() { if (is_nil()) @@ -159,9 +164,10 @@ namespace S2Plugin { return other.node_ptr != node_ptr; } - size_t node_ptr; private: + uintptr_t node_ptr; + // need reference to the map object so we can get offsets and alignments const StdMap* parent_map; }; diff --git a/include/Data/StdString.h b/include/Data/StdString.h index 75777843..4a57d94d 100644 --- a/include/Data/StdString.h +++ b/include/Data/StdString.h @@ -2,7 +2,6 @@ #include "pluginmain.h" #include -#include #include namespace S2Plugin @@ -77,5 +76,5 @@ namespace S2Plugin }; using StdString = StdBasicString; - using StdWstring = StdBasicString; + using StdWstring = StdBasicString; } // namespace S2Plugin diff --git a/include/Data/StringsTable.h b/include/Data/StringsTable.h index f74e1013..27fd0c3e 100644 --- a/include/Data/StringsTable.h +++ b/include/Data/StringsTable.h @@ -20,7 +20,7 @@ namespace S2Plugin { return ptr + idx * sizeof(uintptr_t); } - uintptr_t stringaddressOfIndex(uint32_t idx) const; + uintptr_t stringAddressOfIndex(uint32_t idx) const; size_t count() const { return size; diff --git a/include/Data/TextureDB.h b/include/Data/TextureDB.h index 3979f483..36951c73 100644 --- a/include/Data/TextureDB.h +++ b/include/Data/TextureDB.h @@ -47,11 +47,16 @@ namespace S2Plugin { return mTextures; } + size_t highestID() const + { + return mHighestID; + } private: - size_t ptr{0}; + uintptr_t ptr{0}; std::unordered_map> mTextures; // id -> {name, address} QStringList mTextureNamesStringList; + size_t mHighestID; TextureDB() = default; ~TextureDB(){}; diff --git a/include/QtHelpers/CPPSyntaxHighlighter.h b/include/QtHelpers/CPPSyntaxHighlighter.h index 215792ba..e1cc012b 100644 --- a/include/QtHelpers/CPPSyntaxHighlighter.h +++ b/include/QtHelpers/CPPSyntaxHighlighter.h @@ -1,8 +1,11 @@ #pragma once #include +#include +#include #include #include +#include #include namespace S2Plugin diff --git a/include/QtHelpers/DatabaseHelper.h b/include/QtHelpers/DatabaseHelper.h deleted file mode 100644 index 7ea6fecc..00000000 --- a/include/QtHelpers/DatabaseHelper.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "Configuration.h" -#include -#include -#include -#include - -namespace S2Plugin -{ - namespace DB - { - size_t populateComparisonCombobox(QComboBox* CompareFieldComboBox, const std::vector& fields, size_t offset = 0, std::string prefix = ""); - std::pair valueForField(const QVariant& data, uintptr_t addr); - } // namespace DB -} // namespace S2Plugin diff --git a/include/QtHelpers/DialogEditSimpleValue.h b/include/QtHelpers/DialogEditSimpleValue.h index 366950e2..ff7d92b1 100644 --- a/include/QtHelpers/DialogEditSimpleValue.h +++ b/include/QtHelpers/DialogEditSimpleValue.h @@ -2,6 +2,11 @@ #include #include +#include +#include +#include +#include +#include namespace S2Plugin { @@ -27,7 +32,7 @@ namespace S2Plugin uintptr_t mMemoryAddress; MemoryFieldType mFieldType; - QLineEdit* mLineEditDecValue; + QAbstractSpinBox* mSpinBox; QLineEdit* mLineEditHexValue; }; } // namespace S2Plugin diff --git a/include/QtHelpers/DialogEditState.h b/include/QtHelpers/DialogEditState.h index ab26acf0..9fd5fa55 100644 --- a/include/QtHelpers/DialogEditState.h +++ b/include/QtHelpers/DialogEditState.h @@ -3,6 +3,11 @@ #include #include #include +#include +#include +#include +#include +#include namespace S2Plugin { @@ -24,7 +29,7 @@ namespace S2Plugin private slots: void cancelBtnClicked(); void changeBtnClicked(); - void stateComboBoxChanged(int index); + void stateComboBoxChanged(); private: uintptr_t mMemoryAddress; diff --git a/include/QtHelpers/DialogEditString.h b/include/QtHelpers/DialogEditString.h new file mode 100644 index 00000000..59d93912 --- /dev/null +++ b/include/QtHelpers/DialogEditString.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace S2Plugin +{ + enum class MemoryFieldType; + + class DialogEditString : public QDialog + { + Q_OBJECT + + public: + // size without the last char (null termination) + DialogEditString(const QString& fieldName, QString initialValue, uintptr_t memoryAddress, int size, MemoryFieldType type, QWidget* parent = nullptr); + + protected: + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + + private slots: + void cancelBtnClicked(); + void changeBtnClicked(); + + private: + QLineEdit* mLineEdit; + uintptr_t mMemoryAddress; + MemoryFieldType mFieldType; + }; +} // namespace S2Plugin diff --git a/include/QtHelpers/ItemModelGatherVirtualData.h b/include/QtHelpers/ItemModelGatherVirtualData.h index dd5f1a57..ad54192d 100644 --- a/include/QtHelpers/ItemModelGatherVirtualData.h +++ b/include/QtHelpers/ItemModelGatherVirtualData.h @@ -1,9 +1,12 @@ #pragma once #include +#include #include +#include #include #include +#include namespace S2Plugin { @@ -37,14 +40,32 @@ namespace S2Plugin Q_OBJECT public: - ItemModelGatherVirtualData(QObject* parent = nullptr); + ItemModelGatherVirtualData(QObject* parent = nullptr) : QAbstractItemModel(parent) + { + parseJSON(); + }; - Qt::ItemFlags flags(const QModelIndex& index) const override; + Qt::ItemFlags flags(const QModelIndex&) const override + { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; + int rowCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return static_cast(mEntries.size()); + } + int columnCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return 10; + } + QModelIndex index(int row, int column, [[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return createIndex(row, column); + } + QModelIndex parent(const QModelIndex&) const override + { + return QModelIndex(); + } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void gatherEntities(); @@ -54,7 +75,10 @@ namespace S2Plugin std::string dumpJSON() const; std::string dumpVirtTable() const; std::string dumpCppEnum() const; - bool isEntryCompleted(size_t index) const; + bool isEntryCompleted(size_t index) const + { + return mEntries.at(index).virtualTableOffset != 0; + } private: std::vector mEntries; @@ -67,10 +91,22 @@ namespace S2Plugin Q_OBJECT public: - SortFilterProxyModelGatherVirtualData(QObject* parent = nullptr); + SortFilterProxyModelGatherVirtualData(QObject* parent = nullptr) : QSortFilterProxyModel(parent){}; - bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; - void setHideCompleted(bool b); + bool filterAcceptsRow(int sourceRow, const QModelIndex&) const override + { + if (mHideCompleted && dynamic_cast(sourceModel())->isEntryCompleted(sourceRow)) + { + return false; + } + return true; + } + void setHideCompleted(bool b) + { + beginResetModel(); + mHideCompleted = b; + endResetModel(); + } private: bool mHideCompleted = false; diff --git a/include/QtHelpers/ItemModelLoggerFields.h b/include/QtHelpers/ItemModelLoggerFields.h index d04af34a..2a614572 100644 --- a/include/QtHelpers/ItemModelLoggerFields.h +++ b/include/QtHelpers/ItemModelLoggerFields.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace S2Plugin { @@ -10,19 +12,45 @@ namespace S2Plugin { Q_OBJECT public: - ItemModelLoggerFields(Logger* logger, QObject* parent = nullptr); + ItemModelLoggerFields(Logger* logger, QObject* parent = nullptr) : QAbstractItemModel(parent), mLogger(logger){}; - void removeRow(size_t index); - void removeRowEnd(); - void appendRow(); - void appendRowEnd(); + void removeRow(int index) + { + beginRemoveRows(QModelIndex(), index, index); + } + void removeRowEnd() + { + endRemoveRows(); + } + void appendRow() + { + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + } + void appendRowEnd() + { + endInsertRows(); + } + + Qt::ItemFlags flags(const QModelIndex&) const override + { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } - Qt::ItemFlags flags(const QModelIndex& index) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; + int columnCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return 4; + } + QModelIndex index(int row, int column, [[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return createIndex(row, column); + } + + QModelIndex parent(const QModelIndex&) const override + { + return QModelIndex(); + } QVariant headerData(int section, Qt::Orientation orientation, int role) const override; private: diff --git a/include/QtHelpers/ItemModelLoggerSamples.h b/include/QtHelpers/ItemModelLoggerSamples.h index 1ce55e06..ed3d77ac 100644 --- a/include/QtHelpers/ItemModelLoggerSamples.h +++ b/include/QtHelpers/ItemModelLoggerSamples.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include namespace S2Plugin { @@ -10,16 +12,29 @@ namespace S2Plugin { Q_OBJECT public: - ItemModelLoggerSamples(Logger* logger, QObject* parent = nullptr); + ItemModelLoggerSamples(Logger* logger, QObject* parent = nullptr) : QAbstractItemModel(parent), mLogger(logger){}; - void reset(); + void reset() + { + beginResetModel(); + endResetModel(); + } - Qt::ItemFlags flags(const QModelIndex& index) const override; + Qt::ItemFlags flags(const QModelIndex&) const override + { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; + QModelIndex index(int row, int column, [[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return createIndex(row, column); + } + QModelIndex parent(const QModelIndex&) const override + { + return QModelIndex(); + } QVariant headerData(int section, Qt::Orientation orientation, int role) const override; private: diff --git a/include/QtHelpers/ItemModelStates.h b/include/QtHelpers/ItemModelStates.h index 62b20cc8..6272fd5c 100644 --- a/include/QtHelpers/ItemModelStates.h +++ b/include/QtHelpers/ItemModelStates.h @@ -1,7 +1,9 @@ #pragma once #include +#include #include +#include #include #include #include @@ -12,14 +14,41 @@ namespace S2Plugin { Q_OBJECT public: - ItemModelStates(const std::vector>& states, QObject* parent = nullptr); + ItemModelStates(const std::vector>& states, QObject* parent = nullptr) : QAbstractItemModel(parent), mStates(states){}; - Qt::ItemFlags flags(const QModelIndex& index) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; + Qt::ItemFlags flags(const QModelIndex&) const override + { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override + { + auto& [stateID, state] = mStates.at(index.row()); + if (role == Qt::DisplayRole) + { + return QString("%1: %2").arg(stateID).arg(QString::fromStdString(state)); + } + else if (role == Qt::UserRole) + { + return stateID; + } + return QVariant(); + } + int rowCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return static_cast(mStates.size()); + } + int columnCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return 1; + } + QModelIndex index(int row, int column, [[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return createIndex(row, column); + } + QModelIndex parent(const QModelIndex&) const override + { + return QModelIndex(); + } private: std::vector> mStates; @@ -30,10 +59,18 @@ namespace S2Plugin Q_OBJECT public: - SortFilterProxyModelStates(const std::vector>& states, QObject* parent = nullptr); + SortFilterProxyModelStates(const std::vector>& states, QObject* parent = nullptr) : QSortFilterProxyModel(parent), mStates(states) + { + setSortRole(Qt::UserRole); + } protected: - bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const; + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const + { + auto leftValue = sourceModel()->data(source_left, Qt::UserRole).toLongLong(); + auto rightValue = sourceModel()->data(source_right, Qt::UserRole).toLongLong(); + return leftValue < rightValue; + } private: std::vector> mStates; diff --git a/include/QtHelpers/ItemModelVirtualFunctions.h b/include/QtHelpers/ItemModelVirtualFunctions.h index 28b3ce03..9ff6863f 100644 --- a/include/QtHelpers/ItemModelVirtualFunctions.h +++ b/include/QtHelpers/ItemModelVirtualFunctions.h @@ -1,14 +1,14 @@ #pragma once #include +#include #include +#include #include #include namespace S2Plugin { - class ViewToolbar; - static const uint8_t gsColFunctionIndex = 0; static const uint8_t gsColFunctionTableAddress = 1; static const uint8_t gsColFunctionFunctionAddress = 2; @@ -22,20 +22,31 @@ namespace S2Plugin { Q_OBJECT public: - ItemModelVirtualFunctions(const std::string& typeName, uintptr_t memoryAddress, ViewToolbar* toolbar, QObject* parent = nullptr); + ItemModelVirtualFunctions(const std::string& typeName, uintptr_t memoryAddress, QObject* parent = nullptr) : QAbstractItemModel(parent), mTypeName(typeName), mMemoryAddress(memoryAddress){}; - Qt::ItemFlags flags(const QModelIndex& index) const override; + Qt::ItemFlags flags(const QModelIndex&) const override + { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; + int columnCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return 4; + } + QModelIndex index(int row, int column, [[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return createIndex(row, column); + } + QModelIndex parent(const QModelIndex&) const override + { + return QModelIndex(); + } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; private: std::string mTypeName; uintptr_t mMemoryAddress; - ViewToolbar* mToolbar; }; class SortFilterProxyModelVirtualFunctions : public QSortFilterProxyModel @@ -43,14 +54,18 @@ namespace S2Plugin Q_OBJECT public: - SortFilterProxyModelVirtualFunctions(const std::string& typeName, ViewToolbar* toolbar, QObject* parent = nullptr); + SortFilterProxyModelVirtualFunctions(QObject* parent = nullptr) : QSortFilterProxyModel(parent) + { + setSortRole(gsRoleFunctionIndex); + } protected: - bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const; - - private: - std::string mTypeName; - ViewToolbar* mToolbar; + bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const + { + auto leftValue = sourceModel()->data(source_left, gsRoleFunctionIndex).toLongLong(); + auto rightValue = sourceModel()->data(source_right, gsRoleFunctionIndex).toLongLong(); + return leftValue < rightValue; + } }; } // namespace S2Plugin diff --git a/include/QtHelpers/ItemModelVirtualTable.h b/include/QtHelpers/ItemModelVirtualTable.h index 8b08b493..d5835c7a 100644 --- a/include/QtHelpers/ItemModelVirtualTable.h +++ b/include/QtHelpers/ItemModelVirtualTable.h @@ -1,13 +1,14 @@ #pragma once #include -#include +#include #include +#include +#include +#include namespace S2Plugin { - class ViewToolbar; - static const uint8_t gsColTableOffset = 0; static const uint8_t gsColCodeAddress = 1; static const uint8_t gsColTableAddress = 2; @@ -20,12 +21,24 @@ namespace S2Plugin public: ItemModelVirtualTable(QObject* parent = nullptr); - Qt::ItemFlags flags(const QModelIndex& index) const override; + Qt::ItemFlags flags(const QModelIndex&) const override + { + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; + } QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; - int columnCount(const QModelIndex& parent = QModelIndex()) const override; - QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override; - QModelIndex parent(const QModelIndex& index) const override; + int columnCount([[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return 4; + } + QModelIndex index(int row, int column, [[maybe_unused]] const QModelIndex& parent = QModelIndex()) const override + { + return createIndex(row, column); + } + QModelIndex parent(const QModelIndex&) const override + { + return QModelIndex(); + } QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void detectEntities(); @@ -43,12 +56,31 @@ namespace S2Plugin SortFilterProxyModelVirtualTable(QObject* parent = nullptr); bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; - void setShowImportedSymbols(bool b); - void setShowNonAddressEntries(bool b); - void setShowSymbollessEntries(bool b); - void setFilterString(const QString& f); + void setShowImportedSymbols(bool b) + { + mShowImportedSymbols = b; + invalidateFilter(); + } + void setShowNonAddressEntries(bool b) + { + mShowNonAddressEntries = b; + invalidateFilter(); + } + void setShowSymbollessEntries(bool b) + { + mShowSymbollessEntries = b; + invalidateFilter(); + } + void setFilterString(const QString& f) + { + mFilterString = f; + invalidateFilter(); + } - bool symbollessEntriesShown() const noexcept; + bool symbollessEntriesShown() const noexcept + { + return mShowSymbollessEntries; + } private: bool mShowImportedSymbols = true; diff --git a/include/QtHelpers/LongLongSpinBox.h b/include/QtHelpers/LongLongSpinBox.h new file mode 100644 index 00000000..8c1e42f1 --- /dev/null +++ b/include/QtHelpers/LongLongSpinBox.h @@ -0,0 +1,272 @@ +#pragma once +#include +#include +#include +#include +#include +#include + +namespace S2Plugin +{ + class LongLongSpinBox : public QAbstractSpinBox + { + Q_OBJECT + + using type = long long; + + type m_minimum; + type m_maximum; + type m_value; + + public: + explicit LongLongSpinBox(QWidget* parent = nullptr) : QAbstractSpinBox(parent) + { + setRange(std::numeric_limits::min(), std::numeric_limits::max()); + QObject::connect(lineEdit(), &QLineEdit::textEdited, this, &LongLongSpinBox::onEditFinished); + }; + ~LongLongSpinBox(){}; + + type value() const + { + return m_value; + }; + + type minimum() const + { + return m_minimum; + }; + + void setMinimum(type min) + { + m_minimum = min; + } + + type maximum() const + { + return m_maximum; + }; + + void setMaximum(type max) + { + m_maximum = max; + } + + void setRange(type min, type max) + { + setMinimum(min); + setMaximum(max); + } + + void stepBy(int steps) override + { + type new_value; + + if (steps > 0 && m_value > std::numeric_limits::max() - steps) // overflow + new_value = m_maximum; + else if (steps < 0 && m_value < std::numeric_limits::min() - steps) // underflow + new_value = m_minimum; + else if ((m_value + steps) < m_minimum) + new_value = m_minimum; + else if ((m_value + steps) > m_maximum) + new_value = m_maximum; + else + new_value = m_value + steps; + + setValue(new_value); + } + + protected: + // bool event(QEvent *event); + QValidator::State validate(QString& input, int&) const override + { + if (input.isEmpty()) + return QValidator::Acceptable; + + // technically should check if the range allows positive / negative values + if (input.length() == 1 && (input[0] == '+' || input[0] == '-')) + return QValidator::Acceptable; + + bool ok; + type val = input.toLongLong(&ok); + if (!ok) + return QValidator::Invalid; + + if (val < m_minimum || val > m_maximum) + return QValidator::Invalid; + + return QValidator::Acceptable; + } + + virtual type valueFromText(const QString& text) const + { + return text.toLongLong(); + } + + virtual QString textFromValue(type val) const + { + return QString::number(val); + } + // virtual void fixup(QString &str) const; + + virtual QAbstractSpinBox::StepEnabled stepEnabled() const + { + return StepUpEnabled | StepDownEnabled; + } + + public Q_SLOTS: + void setValue(type val) + { + if (m_value != val) + { + auto new_text = textFromValue(val); + lineEdit()->setText(new_text); + m_value = val; + // emit valueChanged(val); + emit valueChanged(new_text); + } + } + + void onEditFinished() + { + auto new_text = text(); + m_value = valueFromText(new_text); + // emit valueChanged(m_value); + emit valueChanged(new_text); + } + + Q_SIGNALS: + // void valueChanged(type v); + void valueChanged(const QString& v); + }; + + class ULongLongSpinBox : public QAbstractSpinBox + { + Q_OBJECT + + using type = unsigned long long; + + type m_minimum; + type m_maximum; + type m_value; + + public: + explicit ULongLongSpinBox(QWidget* parent = nullptr) : QAbstractSpinBox(parent) + { + setRange(std::numeric_limits::min(), std::numeric_limits::max()); + QObject::connect(lineEdit(), &QLineEdit::textEdited, this, &ULongLongSpinBox::onEditFinished); + }; + ~ULongLongSpinBox(){}; + + type value() const + { + return m_value; + }; + + type minimum() const + { + return m_minimum; + }; + + void setMinimum(type min) + { + m_minimum = min; + } + + type maximum() const + { + return m_maximum; + }; + + void setMaximum(type max) + { + m_maximum = max; + } + + void setRange(type min, type max) + { + setMinimum(min); + setMaximum(max); + } + + void stepBy(int steps) override + { + type new_value; + + if (steps > 0 && m_value > std::numeric_limits::max() - steps) // overflow + new_value = m_maximum; + else if (steps < 0 && m_value < std::numeric_limits::min() - steps) // underflow + new_value = m_minimum; + else if ((m_value + steps) < m_minimum) + new_value = m_minimum; + else if ((m_value + steps) > m_maximum) + new_value = m_maximum; + else + new_value = m_value + steps; + + setValue(new_value); + } + + protected: + // bool event(QEvent *event); + QValidator::State validate(QString& input, int&) const override + { + if (input.isEmpty()) + return QValidator::Acceptable; + + // technically should to be more complicated, chacking if the range allows positive / negative values + if (input.length() == 1 && input[0] == '+') + return QValidator::Acceptable; + + bool ok; + type val = input.toULongLong(&ok); + if (!ok) + return QValidator::Invalid; + + if (val < m_minimum || val > m_maximum) + return QValidator::Invalid; + + return QValidator::Acceptable; + } + + virtual type valueFromText(const QString& text) const + { + return text.toULongLong(); + } + + virtual QString textFromValue(type val) const + { + return QString::number(val); + } + // virtual void fixup(QString &str) const; + + virtual QAbstractSpinBox::StepEnabled stepEnabled() const + { + return StepUpEnabled | StepDownEnabled; + } + + public Q_SLOTS: + void setValue(type val) + { + if (m_value != val) + { + auto new_text = textFromValue(val); + lineEdit()->setText(new_text); + m_value = val; + // emit valueChanged(val); + emit valueChanged(new_text); + } + } + + void onEditFinished() + { + auto new_text = text(); + m_value = valueFromText(new_text); + // emit valueChanged(m_value); + emit valueChanged(new_text); + } + + Q_SIGNALS: + // void valueChanged(type v); + void valueChanged(const QString& v); + }; +} // namespace S2Plugin diff --git a/include/QtHelpers/SortFilterProxyModelStringsTable.h b/include/QtHelpers/SortFilterProxyModelStringsTable.h index e81f0d61..60b0a67b 100644 --- a/include/QtHelpers/SortFilterProxyModelStringsTable.h +++ b/include/QtHelpers/SortFilterProxyModelStringsTable.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -10,10 +11,14 @@ namespace S2Plugin Q_OBJECT public: - SortFilterProxyModelStringsTable(QObject* parent = nullptr); + using QSortFilterProxyModel::QSortFilterProxyModel; bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; - void setFilterString(const QString& f); + void setFilterString(const QString& f) + { + mFilterString = f; + invalidateFilter(); + } private: QString mFilterString = ""; diff --git a/include/QtHelpers/StyledItemDelegateColorPicker.h b/include/QtHelpers/StyledItemDelegateColorPicker.h index 8ccc3592..951c9ea8 100644 --- a/include/QtHelpers/StyledItemDelegateColorPicker.h +++ b/include/QtHelpers/StyledItemDelegateColorPicker.h @@ -1,12 +1,31 @@ #pragma once +#include +#include +#include #include namespace S2Plugin { class StyledItemDelegateColorPicker : public QStyledItemDelegate { + using QStyledItemDelegate::QStyledItemDelegate; + protected: - void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const + { + QStyleOptionViewItemV4 options = option; + initStyleOption(&options, index); + + painter->save(); + + auto adjusted = options.rect.adjusted(5, 5, -5, -5); + auto color = index.data(Qt::DisplayRole).value(); + painter->fillRect(adjusted, QBrush(color)); + painter->setPen(Qt::darkGray); + painter->drawRect(adjusted); + + painter->restore(); + } }; } // namespace S2Plugin diff --git a/include/QtHelpers/StyledItemDelegateHTML.h b/include/QtHelpers/StyledItemDelegateHTML.h index 164b419f..7a5d1f44 100644 --- a/include/QtHelpers/StyledItemDelegateHTML.h +++ b/include/QtHelpers/StyledItemDelegateHTML.h @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include namespace S2Plugin @@ -7,7 +10,11 @@ namespace S2Plugin class StyledItemDelegateHTML : public QStyledItemDelegate { public: - void setCenterVertically(bool b); + using QStyledItemDelegate::QStyledItemDelegate; + void setCenterVertically(bool b) + { + mCenterVertically = b; + } protected: void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; diff --git a/include/QtHelpers/TableViewLogger.h b/include/QtHelpers/TableViewLogger.h index 45a0a42a..fc081fdc 100644 --- a/include/QtHelpers/TableViewLogger.h +++ b/include/QtHelpers/TableViewLogger.h @@ -4,14 +4,15 @@ #include #include #include +#include #include #include -#include +#include +#include namespace S2Plugin { class Logger; - class StyledItemDelegateColorPicker; static constexpr uint8_t gsLogFieldColColor = 0; static constexpr uint8_t gsLogFieldColMemoryOffset = 1; @@ -35,7 +36,6 @@ namespace S2Plugin void cellClicked(const QModelIndex& index); private: - std::unique_ptr mColorPickerDelegate; Logger* mLogger; }; } // namespace S2Plugin diff --git a/include/QtHelpers/TableWidgetItemNumeric.h b/include/QtHelpers/TableWidgetItemNumeric.h index 513949b1..e1608c57 100644 --- a/include/QtHelpers/TableWidgetItemNumeric.h +++ b/include/QtHelpers/TableWidgetItemNumeric.h @@ -7,8 +7,11 @@ namespace S2Plugin class TableWidgetItemNumeric : public QTableWidgetItem { public: - TableWidgetItemNumeric(const QString& s); - bool operator<(const QTableWidgetItem& other) const; + using QTableWidgetItem::QTableWidgetItem; + bool operator<(const QTableWidgetItem& other) const + { + return data(Qt::UserRole) < other.data(Qt::UserRole); + } }; } // namespace S2Plugin diff --git a/include/QtHelpers/TreeViewMemoryFields.h b/include/QtHelpers/TreeViewMemoryFields.h index f401dfd2..c4b0fa32 100644 --- a/include/QtHelpers/TreeViewMemoryFields.h +++ b/include/QtHelpers/TreeViewMemoryFields.h @@ -1,10 +1,14 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" +#include +#include +#include +#include #include #include +#include #include -#include +#include #include #include #include @@ -13,9 +17,14 @@ namespace S2Plugin { - class ViewToolbar; struct MemoryField; + constexpr char* gsDragDropMemoryField_UID = "uid"; + constexpr char* gsDragDropMemoryField_Address = "addr"; + constexpr char* gsDragDropMemoryField_Type = "type"; + constexpr char* gsDragDropMemoryField_IsPointer = "pointer"; + constexpr char* gsDragDropMemoryField_RefName = "ref"; + struct ColumnFilter { constexpr ColumnFilter& enable(uint8_t h) @@ -41,22 +50,36 @@ namespace S2Plugin { Q_OBJECT public: - TreeViewMemoryFields(ViewToolbar* toolbar, QWidget* parent = nullptr); + TreeViewMemoryFields(QWidget* parent = nullptr); - void addMemoryFields(const std::vector& fields, const std::string& mainName, uintptr_t structAddr, size_t initialDelta = 0, QStandardItem* parent = nullptr); - QStandardItem* addMemoryField(const MemoryField& field, const std::string& fieldNameOverride, uintptr_t memoryAddress, size_t delta, QStandardItem* parent = nullptr); + void addMemoryFields(const std::vector& fields, const std::string& mainName, uintptr_t structAddr, size_t initialDelta = 0, uint8_t deltaPrefixCount = 0, + QStandardItem* parent = nullptr); + QStandardItem* addMemoryField(const MemoryField& field, const std::string& fieldNameOverride, uintptr_t memoryAddress, size_t delta, uint8_t deltaPrefixCount = 0, + QStandardItem* parent = nullptr); void clear(); void updateTableHeader(bool restoreColumnWidths = true); - void setEnableChangeHighlighting(bool b) noexcept; + void setEnableChangeHighlighting(bool b) noexcept + { + mEnableChangeHighlighting = b; + } - void expandItem(QStandardItem* item); - void updateTree(uintptr_t newAddr = 0, uintptr_t newComparisonAddr = 0, bool initial = false); + void updateTree(uintptr_t newAddr, uintptr_t newComparisonAddr = 0, bool initial = false); void updateRow(int row, std::optional newAddr = std::nullopt, std::optional newAddrComparison = std::nullopt, QStandardItem* parent = nullptr, bool disableChangeHighlightingForField = false); - void labelAll(std::string_view prefix = {}); - ColumnFilter activeColumns; + void labelAll(std::string_view prefix); + void expandLast(); + + public slots: + void labelAll() // for the slots so we don't corrupt the parameters + { + labelAll({}); + } + void updateTree() + { + updateTree(0, 0, false); + } protected: void dragEnterEvent(QDragEnterEvent* event) override; @@ -65,17 +88,15 @@ namespace S2Plugin void startDrag(Qt::DropActions supportedActions) override; signals: - void memoryFieldValueUpdated(const QString& fieldName); + void memoryFieldValueUpdated(int row, QStandardItem* parrent); void levelGenRoomsPointerClicked(); - void entityOffsetDropped(size_t offset); + void offsetDropped(uintptr_t offset); private slots: void cellClicked(const QModelIndex& index); private: - ViewToolbar* mToolbar; QStandardItemModel* mModel; - StyledItemDelegateHTML mHTMLDelegate; std::array mSavedColumnWidths = {0}; bool mEnableChangeHighlighting = true; }; diff --git a/include/QtHelpers/TreeWidgetItemNumeric.h b/include/QtHelpers/TreeWidgetItemNumeric.h index 5c692577..f0d9e153 100644 --- a/include/QtHelpers/TreeWidgetItemNumeric.h +++ b/include/QtHelpers/TreeWidgetItemNumeric.h @@ -7,8 +7,11 @@ namespace S2Plugin class TreeWidgetItemNumeric : public QTreeWidgetItem { public: - TreeWidgetItemNumeric(QTreeWidgetItem* parent, const QString& caption); - bool operator<(const QTreeWidgetItem& other) const; + using QTreeWidgetItem::QTreeWidgetItem; + bool operator<(const QTreeWidgetItem& other) const + { + return data(0, Qt::UserRole) < other.data(0, Qt::UserRole); + } }; } // namespace S2Plugin diff --git a/include/QtHelpers/WidgetAutorefresh.h b/include/QtHelpers/WidgetAutorefresh.h new file mode 100644 index 00000000..ea4c014c --- /dev/null +++ b/include/QtHelpers/WidgetAutorefresh.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace S2Plugin +{ + class WidgetAutorefresh : public QWidget + { + Q_OBJECT + public: + WidgetAutorefresh(int initialInterval, QWidget* parrent = nullptr); + signals: + void refresh(); + + public slots: + void toggleAutoRefresh(bool checked); + protected slots: + void autoRefreshIntervalChanged(int val); + + private: + QPushButton* mRefreshButton; + QCheckBox* mAutoRefreshCheckBox; + QTimer* mAutoRefreshTimer; + QSpinBox* mAutoRefreshIntervalQSpinBox; + }; +} // namespace S2Plugin diff --git a/include/QtHelpers/WidgetDatabaseView.h b/include/QtHelpers/WidgetDatabaseView.h new file mode 100644 index 00000000..36dadae9 --- /dev/null +++ b/include/QtHelpers/WidgetDatabaseView.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Things to implement: + * setWindowTitle + * Set up completer + * update number of rows in mCompareTableWidget + * sizeHint override + * call show id on open + * override pure virtuals + */ + +namespace S2Plugin +{ + class TreeViewMemoryFields; + enum class MemoryFieldType; + struct MemoryField; + using ID_type = uint32_t; + + class WidgetDatabaseView : public QWidget + { + Q_OBJECT + public: + WidgetDatabaseView(MemoryFieldType type, QWidget* parent = nullptr); + virtual void showID(ID_type id) = 0; + + protected: + QSize minimumSizeHint() const override; + + virtual ID_type highestRecordID() const = 0; + // already capped with highestRecordID, only for structs with gaps in between id's + virtual bool isValidRecordID(ID_type ID) const = 0; + virtual std::optional getIDForName(QString name) const = 0; + // valid id guaranteed via highestRecordID and isValidRecordID + virtual QString recordNameForID(ID_type id) const = 0; + // valid id guaranteed via highestRecordID and isValidRecordID + virtual uintptr_t addressOfRecordID(ID_type id) const = 0; + + void switchToLookupTab() + { + mMainTabWidget->setCurrentIndex(0); + } + + protected slots: + virtual void label() const = 0; + void searchFieldReturnPressed(); + + private slots: + void fieldUpdated(int row, QStandardItem* parrent); + void fieldExpanded(const QModelIndex& index); + void comparisonFieldChosen(); + void comparisonFlagChosen(const QString& text); + void compareGroupByCheckBoxClicked(int state); + void comparisonCellClicked(int row, int column); + void groupedComparisonItemClicked(QTreeWidgetItem* item); + + protected: + QLineEdit* mSearchLineEdit; + TreeViewMemoryFields* mMainTreeView; + QTableWidget* mCompareTableWidget; + bool mFieldChoosen{false}; + + private: + QTabWidget* mMainTabWidget; + QComboBox* mCompareFieldComboBox; + QComboBox* mCompareFlagComboBox; + QTreeWidget* mCompareTreeWidget; + + void populateComparisonTableWidget(const QVariant& fieldData); + void populateComparisonTreeWidget(const QVariant& fieldData); + size_t populateComparisonCombobox(const std::vector& fields, size_t offset = 0, std::string prefix = {}); + static std::pair valueForField(const QVariant& data, uintptr_t addr); + }; +} // namespace S2Plugin diff --git a/include/QtHelpers/WidgetMemoryView.h b/include/QtHelpers/WidgetMemoryView.h index 2ec3fc50..5897d0ae 100644 --- a/include/QtHelpers/WidgetMemoryView.h +++ b/include/QtHelpers/WidgetMemoryView.h @@ -1,5 +1,12 @@ #pragma once +#include "Data/Entity.h" +#include +#include +#include +#include +#include +#include #include #include #include @@ -13,9 +20,9 @@ namespace S2Plugin { std::string tooltip; size_t offset; - size_t size; + int size; QColor color; - HighlightedField(std::string _tooltip, size_t _offset, size_t _size, QColor _color) : tooltip(_tooltip), offset(_offset), size(_size), color(_color){}; + HighlightedField(std::string _tooltip, size_t _offset, int _size, QColor _color) : tooltip(_tooltip), offset(_offset), size(_size), color(_color){}; }; struct ToolTipRect @@ -37,20 +44,23 @@ namespace S2Plugin void setOffsetAndSize(size_t offset, size_t size); void clearHighlights(); - void addHighlightedField(std::string tooltip, size_t offset, size_t size, QColor color); + void addHighlightedField(std::string tooltip, size_t offset, int size, QColor color); + + void updateMemory(); protected: void paintEvent(QPaintEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; private: - size_t mOffset = 0; - size_t mSize = 0; + uintptr_t mAddress{0}; + size_t mSize{0}; QFont mFont; QSize mTextAdvance; uint8_t mSpaceAdvance; std::vector mHighlightedFields; std::vector mToolTipRects; + uint8_t mMemoryData[gBigEntityBucket] = {}; }; } // namespace S2Plugin diff --git a/include/QtHelpers/WidgetSamplesPlot.h b/include/QtHelpers/WidgetSamplesPlot.h index 1a57969b..67545f0a 100644 --- a/include/QtHelpers/WidgetSamplesPlot.h +++ b/include/QtHelpers/WidgetSamplesPlot.h @@ -1,7 +1,10 @@ #pragma once +#include #include #include +#include +#include #include namespace S2Plugin @@ -12,15 +15,29 @@ namespace S2Plugin { Q_OBJECT public: - WidgetSamplesPlot(Logger* logger, QWidget* parent = nullptr); + WidgetSamplesPlot(Logger* logger, QWidget* parent = nullptr) : QWidget(parent), mLogger(logger) + { + setMouseTracking(true); + setCursor(Qt::CrossCursor); + } QSize minimumSizeHint() const override; - QSize sizeHint() const override; + QSize sizeHint() const override + { + return minimumSizeHint(); + } protected: void paintEvent(QPaintEvent* event) override; - void mouseMoveEvent(QMouseEvent* event) override; - void leaveEvent(QEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override + { + mCurrentMousePos = event->pos(); + update(); + } + void leaveEvent(QEvent*) override + { + update(); + } private: Logger* mLogger; diff --git a/include/QtHelpers/WidgetSampling.h b/include/QtHelpers/WidgetSampling.h index f8d1765a..5fda24ed 100644 --- a/include/QtHelpers/WidgetSampling.h +++ b/include/QtHelpers/WidgetSampling.h @@ -1,6 +1,12 @@ #pragma once +#include +#include #include +#include +#include +#include +#include #include namespace S2Plugin @@ -9,12 +15,36 @@ namespace S2Plugin { Q_OBJECT public: - explicit WidgetSampling(QWidget* parent = nullptr); + using QWidget::QWidget; - QSize minimumSizeHint() const override; - QSize sizeHint() const override; + QSize minimumSizeHint() const override + { + return QSize(400, 400); + } + QSize sizeHint() const override + { + return minimumSizeHint(); + } protected: - void paintEvent(QPaintEvent* event) override; + void paintEvent(QPaintEvent*) override + { + auto painter = QPainter(this); + painter.save(); + + painter.fillRect(rect(), Qt::white); + painter.setPen(Qt::darkGray); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + + static const auto caption = QString("Sampling..."); + static const auto font = QFont("Arial", 16); + static const auto captionSize = QFontMetrics(font).size(Qt::TextSingleLine, caption); + + painter.setFont(font); + painter.setPen(Qt::lightGray); + painter.drawText(QPointF((width() / 2.) - (captionSize.width() / 2.), (height() / 2.) - (captionSize.height() / 2.)), caption); + + painter.restore(); + } }; } // namespace S2Plugin diff --git a/include/QtHelpers/WidgetSpelunkyLevel.h b/include/QtHelpers/WidgetSpelunkyLevel.h index 78e5a8f9..b4d0e596 100644 --- a/include/QtHelpers/WidgetSpelunkyLevel.h +++ b/include/QtHelpers/WidgetSpelunkyLevel.h @@ -1,14 +1,31 @@ #pragma once #include "Data/Entity.h" +#include +#include +#include #include #include +#include #include #include #include namespace S2Plugin { + class EntityToPaint + { + public: + EntityToPaint() = default; + EntityToPaint(uintptr_t addr, QBrush col) : ent(addr), color(col){}; + + protected: + Entity ent; + QBrush color; + std::pair pos{0, 0}; + friend class WidgetSpelunkyLevel; + }; + class WidgetSpelunkyLevel : public QWidget { Q_OBJECT @@ -25,6 +42,7 @@ namespace S2Plugin void paintFloor(const QColor& color); void clearAllPaintedEntities(); void clearPaintedEntity(uintptr_t addr); + void updateLevel(); protected: void paintEvent(QPaintEvent* event) override; @@ -32,23 +50,26 @@ namespace S2Plugin private: uintptr_t mMainEntityAddr{0}; - bool mPaintFloors{false}; QBrush mFloorColor; - uint mLevelWidth{0}; - uint mLevelHeight{0}; + bool mPaintFloors{false}; + // uint8_t mLevelWidth{0}; + // uint8_t mLevelHeight{0}; std::pair mMaskMapAddr; std::pair mGridEntitiesAddr; uint32_t mEntityMasksToPaint{0}; std::array mEntityMaskColors; - std::vector> mEntitiesToPaint; + std::array>, 15> mEntitiesMaskCoordinates; + std::vector mEntitiesToPaint; static constexpr uint8_t msLevelMaxHeight = 125; static constexpr uint8_t msLevelMaxWidth = 85; static constexpr uint8_t msMarginVer = 1; static constexpr uint8_t msMarginHor = 1; static constexpr uint8_t msScaleFactor = 7; + + uintptr_t mLevelFloors[msLevelMaxHeight + 1][msLevelMaxWidth + 1] = {}; }; } // namespace S2Plugin diff --git a/include/QtHelpers/WidgetSpelunkyRooms.h b/include/QtHelpers/WidgetSpelunkyRooms.h index 593dcc39..2d1a7d6a 100644 --- a/include/QtHelpers/WidgetSpelunkyRooms.h +++ b/include/QtHelpers/WidgetSpelunkyRooms.h @@ -1,7 +1,15 @@ #pragma once +#include +#include +#include +#include +#include #include +#include +#include #include +#include namespace S2Plugin { diff --git a/include/QtPlugin.h b/include/QtPlugin.h index f3287c98..994b2134 100644 --- a/include/QtPlugin.h +++ b/include/QtPlugin.h @@ -1,5 +1,7 @@ #pragma once +#include + namespace QtPlugin { void Init(); @@ -12,3 +14,11 @@ namespace QtPlugin void MenuEntry(int hMenu); void Detach(); } // namespace QtPlugin + +namespace S2Plugin +{ + class ViewToolbar; + + QIcon getCavemanIcon(); + ViewToolbar* getToolbar(); +} // namespace S2Plugin diff --git a/include/Spelunky2.h b/include/Spelunky2.h index cd32d5a9..66284de9 100644 --- a/include/Spelunky2.h +++ b/include/Spelunky2.h @@ -3,19 +3,17 @@ #include "Data/CharacterDB.h" #include "Data/EntityDB.h" #include "Data/ParticleDB.h" +#include "Data/State.h" #include "Data/StringsTable.h" #include "Data/TextureDB.h" #include "Data/VirtualTableLookup.h" +#include #include -#include -#include namespace S2Plugin { constexpr uint32_t TEB_offset = 0x120; - class EntityDB; - struct Spelunky2 { static Spelunky2* get(); @@ -47,12 +45,16 @@ namespace S2Plugin const EntityDB& get_EntityDB(); const StringsTable& get_StringsTable(); const VirtualTableLookup& get_VirtualTableLookup(); + State get_State() const + { + return State{get_StatePtr()}; + } // uintptr_t find(const char* pattern, uintptr_t start = 0, size_t size = 0) const; uintptr_t find_between(const char* pattern, uintptr_t start = 0, uintptr_t end = 0) const; - std::string themeNameOfOffset(uintptr_t offset) const; + const QString& themeNameOfOffset(uintptr_t offset) const; private: static Spelunky2* ptr; diff --git a/include/Views/ViewCharacterDB.h b/include/Views/ViewCharacterDB.h index fb8ddb76..afc8623a 100644 --- a/include/Views/ViewCharacterDB.h +++ b/include/Views/ViewCharacterDB.h @@ -1,66 +1,31 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "QtHelpers/WidgetDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewToolbar; - class ViewCharacterDB : public QWidget + class ViewCharacterDB : public WidgetDatabaseView { Q_OBJECT public: - ViewCharacterDB(ViewToolbar* toolbar, uint8_t index = 0, QWidget* parent = nullptr); - void showIndex(uint8_t index); + ViewCharacterDB(QWidget* parent = nullptr); + void showID(ID_type id) override; protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; - QSize minimumSizeHint() const override; + void label() const override; + ID_type highestRecordID() const override; + bool isValidRecordID(ID_type) const override + { + return true; + } + std::optional getIDForName(QString name) const override; + QString recordNameForID(ID_type id) const override; + uintptr_t addressOfRecordID(ID_type id) const override; private slots: - void searchFieldReturnPressed(); - void searchFieldCompleterActivated(const QString& text); - void label(); - void fieldUpdated(const QString& fieldName); - void fieldExpanded(const QModelIndex& index); - void comparisonFieldChosen(const QString& fieldName); - void compareGroupByCheckBoxClicked(int state); - void comparisonCellClicked(int row, int column); - void groupedComparisonItemClicked(QTreeWidgetItem* item, int column); - - private: - StyledItemDelegateHTML mHTMLDelegate; - - QTabWidget* mMainTabWidget; - QWidget* mTabLookup; - QWidget* mTabCompare; - - // LOOKUP - TreeViewMemoryFields* mMainTreeView; - QLineEdit* mSearchLineEdit; - - // COMPARE - QComboBox* mCompareFieldComboBox; - QTableWidget* mCompareTableWidget; - QTreeWidget* mCompareTreeWidget; - - void initializeUI(); - void updateFieldValues(); - void populateComparisonTableWidget(); - void populateComparisonTreeWidget(); + void searchFieldCompleterActivated(); }; } // namespace S2Plugin diff --git a/include/Views/ViewEntities.h b/include/Views/ViewEntities.h index 06d588f0..4b65c057 100644 --- a/include/Views/ViewEntities.h +++ b/include/Views/ViewEntities.h @@ -2,14 +2,13 @@ #include #include -#include +#include #include -#include +#include #include namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; enum class MASK : uint32_t @@ -34,10 +33,9 @@ namespace S2Plugin { Q_OBJECT public: - ViewEntities(ViewToolbar* toolbar, QWidget* parent = nullptr); + ViewEntities(QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -51,7 +49,6 @@ namespace S2Plugin const MASK mask; const QString name; }; - QVBoxLayout* mMainLayout; TreeViewMemoryFields* mMainTreeView; QCheckBox* mCheckboxLayer0; @@ -77,12 +74,8 @@ namespace S2Plugin QLineEdit* mFilterLineEdit; - ViewToolbar* mToolbar; uintptr_t mLayer0Offset = 0; uintptr_t mLayer1Offset = 0; uintptr_t mLayerMapOffset = 0; - - void initializeTreeView(); - void initializeRefreshAndFilter(); }; } // namespace S2Plugin diff --git a/include/Views/ViewEntity.h b/include/Views/ViewEntity.h index bf9c3e99..2e386fd8 100644 --- a/include/Views/ViewEntity.h +++ b/include/Views/ViewEntity.h @@ -1,21 +1,16 @@ #pragma once -#include #include -#include -#include #include -#include +#include +#include #include #include -#include -#include -#include -#include +#include +#include namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; class WidgetMemoryView; class CPPSyntaxHighlighter; @@ -25,42 +20,27 @@ namespace S2Plugin { Q_OBJECT public: - ViewEntity(size_t entityOffset, ViewToolbar* toolbar, QWidget* parent = nullptr); + ViewEntity(size_t entityOffset, QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; private slots: void refreshEntity(); - void toggleAutoRefresh(int newState); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); void interpretAsChanged(const QString& text); void label(); - void entityOffsetDropped(size_t entityOffset); - void tabChanged(int index); + void entityOffsetDropped(uintptr_t entityOffset); + void tabChanged(); private: - QVBoxLayout* mMainLayout; - QHBoxLayout* mTopLayout; QTabWidget* mMainTabWidget; - QWidget* mTabFields; - QWidget* mTabMemory; - QWidget* mTabLevel; - QWidget* mTabCPP; uintptr_t mEntityPtr; uintptr_t mComparisonEntityPtr{0}; size_t mEntitySize{0}; - ViewToolbar* mToolbar; // TOP LAYOUT - QPushButton* mRefreshButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; QComboBox* mInterpretAsComboBox; // TAB FIELDS @@ -69,6 +49,7 @@ namespace S2Plugin // TAB MEMORY WidgetMemoryView* mMemoryView; WidgetMemoryView* mMemoryComparisonView; + QScrollArea* mMemoryScrollArea; QScrollArea* mMemoryComparisonScrollArea; // TAB LEVEL diff --git a/include/Views/ViewEntityDB.h b/include/Views/ViewEntityDB.h index 513e08ab..6ddea749 100644 --- a/include/Views/ViewEntityDB.h +++ b/include/Views/ViewEntityDB.h @@ -1,66 +1,29 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "QtHelpers/WidgetDatabaseView.h" namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; - class ViewEntityDB : public QWidget + class ViewEntityDB : public WidgetDatabaseView { Q_OBJECT public: - ViewEntityDB(ViewToolbar* toolbar, uint32_t id = 1, QWidget* parent = nullptr); - void showID(uint32_t id); + ViewEntityDB(QWidget* parent = nullptr); + void showID(ID_type id) override; + void showRAW(uintptr_t address); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; - QSize minimumSizeHint() const override; + void label() const override; + ID_type highestRecordID() const override; + bool isValidRecordID(ID_type id) const override; + std::optional getIDForName(QString name) const override; + QString recordNameForID(ID_type id) const override; + uintptr_t addressOfRecordID(ID_type id) const override; private slots: - void searchFieldReturnPressed(); - void searchFieldCompleterActivated(const QString& text); - void label(); - void fieldUpdated(const QString& fieldName); - void fieldExpanded(const QModelIndex& index); - void comparisonFieldChosen(const QString& fieldName); - void compareGroupByCheckBoxClicked(int state); - void comparisonCellClicked(int row, int column); - void groupedComparisonItemClicked(QTreeWidgetItem* item, int column); - - private: - StyledItemDelegateHTML mHTMLDelegate; - - QTabWidget* mMainTabWidget; - QWidget* mTabLookup; - QWidget* mTabCompare; - - // LOOKUP - TreeViewMemoryFields* mMainTreeView; - QLineEdit* mSearchLineEdit; - - // COMPARE - QComboBox* mCompareFieldComboBox; - QTableWidget* mCompareTableWidget; - QTreeWidget* mCompareTreeWidget; - - void initializeUI(); - void updateFieldValues(); - void populateComparisonTableWidget(); - void populateComparisonTreeWidget(); + void searchFieldCompleterActivated(); }; } // namespace S2Plugin diff --git a/include/Views/ViewGameManager.h b/include/Views/ViewGameManager.h deleted file mode 100644 index cbc704cb..00000000 --- a/include/Views/ViewGameManager.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace S2Plugin -{ - class ViewToolbar; - class TreeViewMemoryFields; - - class ViewGameManager : public QWidget - { - Q_OBJECT - public: - ViewGameManager(ViewToolbar* toolbar, QWidget* parent = nullptr); - - protected: - void closeEvent(QCloseEvent* event) override; - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - - private slots: - void refreshGameManager(); - void toggleAutoRefresh(int newState); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); - void label(); - - private: - QVBoxLayout* mMainLayout; - QHBoxLayout* mRefreshLayout; - TreeViewMemoryFields* mMainTreeView; - QPushButton* mRefreshButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; - - ViewToolbar* mToolbar; - - void initializeUI(); - }; -} // namespace S2Plugin diff --git a/include/Views/ViewJournalPage.h b/include/Views/ViewJournalPage.h index da2a59fe..b11623fb 100644 --- a/include/Views/ViewJournalPage.h +++ b/include/Views/ViewJournalPage.h @@ -1,55 +1,31 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; - class JournalPage; class ViewJournalPage : public QWidget { Q_OBJECT public: - ViewJournalPage(ViewToolbar* toolbar, uintptr_t offset, const std::string& pageType, QWidget* parent = nullptr); + ViewJournalPage(uintptr_t address, QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; private slots: void refreshJournalPage(); - void toggleAutoRefresh(int newState); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); void label(); void interpretAsChanged(const QString& text); private: - uintptr_t mOffset; - std::string mPageType; - ViewToolbar* mToolbar; - - QVBoxLayout* mMainLayout; - QHBoxLayout* mRefreshLayout; + uintptr_t mPageAddress; TreeViewMemoryFields* mMainTreeView; - QPushButton* mRefreshButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; - QComboBox* mInterpretAsComboBox; - - void initializeUI(); }; } // namespace S2Plugin diff --git a/include/Views/ViewLevelGen.h b/include/Views/ViewLevelGen.h index 07eab463..efd0638f 100644 --- a/include/Views/ViewLevelGen.h +++ b/include/Views/ViewLevelGen.h @@ -1,19 +1,14 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; class WidgetSpelunkyRooms; @@ -21,42 +16,25 @@ namespace S2Plugin { Q_OBJECT public: - ViewLevelGen(ViewToolbar* toolbar, QWidget* parent = nullptr); + ViewLevelGen(uintptr_t address, QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; private slots: void refreshLevelGen(); - void toggleAutoRefresh(int newLevelGen); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); void label(); void levelGenRoomsPointerClicked(); private: - QVBoxLayout* mMainLayout; QTabWidget* mMainTabWidget; - QWidget* mTabData; - QWidget* mTabRooms; - - // TOP REFRESH LAYOUT - QHBoxLayout* mRefreshLayout; - QPushButton* mRefreshButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; // TAB DATA TreeViewMemoryFields* mMainTreeView; + uintptr_t mLevelGenPtr; // TAB LEVEL std::unordered_map mRoomsWidgets; // field_name -> widget* - - ViewToolbar* mToolbar; - - void initializeUI(); }; } // namespace S2Plugin diff --git a/include/Views/ViewLogger.h b/include/Views/ViewLogger.h index 8dff3e1c..1fec00d8 100644 --- a/include/Views/ViewLogger.h +++ b/include/Views/ViewLogger.h @@ -2,20 +2,17 @@ #include #include -#include -#include -#include +#include +#include +#include #include -#include namespace S2Plugin { class Logger; class TableViewLogger; - class ItemModelLoggerFields; class WidgetSampling; class ItemModelLoggerSamples; - class WidgetSamplesPlot; class ViewLogger : public QWidget { @@ -25,7 +22,6 @@ namespace S2Plugin explicit ViewLogger(QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -34,35 +30,24 @@ namespace S2Plugin void fieldsChanged(); private: - QVBoxLayout* mMainLayout; - std::unique_ptr mLogger; + Logger* mLogger; // TOP LAYOUT - QHBoxLayout* mTopLayout; QLineEdit* mSamplePeriodLineEdit; QLineEdit* mDurationLineEdit; QPushButton* mStartButton; // TABS QTabWidget* mMainTabWidget; - QWidget* mTabFields; - QWidget* mTabSamples; - QWidget* mTabPlot; // TABLE TableViewLogger* mFieldsTableView; - ItemModelLoggerFields* mFieldsTableModel; WidgetSampling* mSamplingWidget; // SAMPLES QTableView* mSamplesTableView; ItemModelLoggerSamples* mSamplesTableModel; - // PLOT - QScrollArea* mSamplesPlotScroll; - WidgetSamplesPlot* mSamplesPlotWidget; - - void initializeUI(); void startLogging(); }; } // namespace S2Plugin diff --git a/include/Views/ViewOnline.h b/include/Views/ViewOnline.h deleted file mode 100644 index 9a1275c7..00000000 --- a/include/Views/ViewOnline.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace S2Plugin -{ - class ViewToolbar; - class TreeViewMemoryFields; - - class ViewOnline : public QWidget - { - Q_OBJECT - public: - ViewOnline(ViewToolbar* toolbar, QWidget* parent = nullptr); - - protected: - void closeEvent(QCloseEvent* event) override; - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - - private slots: - void refreshOnline(); - void toggleAutoRefresh(int newState); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); - void label(); - - private: - QVBoxLayout* mMainLayout; - QHBoxLayout* mRefreshLayout; - TreeViewMemoryFields* mMainTreeView; - QPushButton* mRefreshButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; - - ViewToolbar* mToolbar; - - void initializeUI(); - }; -} // namespace S2Plugin diff --git a/include/Views/ViewParticleDB.h b/include/Views/ViewParticleDB.h index badab7f3..5dbc25e5 100644 --- a/include/Views/ViewParticleDB.h +++ b/include/Views/ViewParticleDB.h @@ -1,66 +1,29 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "QtHelpers/WidgetDatabaseView.h" namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; - class ViewParticleDB : public QWidget + class ViewParticleDB : public WidgetDatabaseView { Q_OBJECT public: - ViewParticleDB(ViewToolbar* toolbar, uint32_t id = 1, QWidget* parent = nullptr); - void showID(uint32_t id); + ViewParticleDB(QWidget* parent = nullptr); + void showID(ID_type id) override; + void showRAW(uintptr_t address); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; - QSize minimumSizeHint() const override; + void label() const override; + ID_type highestRecordID() const override; + bool isValidRecordID(ID_type id) const override; + std::optional getIDForName(QString name) const override; + QString recordNameForID(ID_type id) const override; + uintptr_t addressOfRecordID(ID_type id) const override; private slots: - void searchFieldReturnPressed(); - void searchFieldCompleterActivated(const QString& text); - void label(); - void fieldUpdated(const QString& fieldName); - void fieldExpanded(const QModelIndex& index); - void comparisonFieldChosen(const QString& fieldName); - void compareGroupByCheckBoxClicked(int state); - void comparisonCellClicked(int row, int column); - void groupedComparisonItemClicked(QTreeWidgetItem* item, int column); - - private: - StyledItemDelegateHTML mHTMLDelegate; - - QTabWidget* mMainTabWidget; - QWidget* mTabLookup; - QWidget* mTabCompare; - - // LOOKUP - TreeViewMemoryFields* mMainTreeView; - QLineEdit* mSearchLineEdit; - - // COMPARE - QComboBox* mCompareFieldComboBox; - QTableWidget* mCompareTableWidget; - QTreeWidget* mCompareTreeWidget; - - void initializeUI(); - void updateFieldValues(); - void populateComparisonTableWidget(); - void populateComparisonTreeWidget(); + void searchFieldCompleterActivated(); }; } // namespace S2Plugin diff --git a/include/Views/ViewSaveGame.h b/include/Views/ViewSaveGame.h deleted file mode 100644 index 62036595..00000000 --- a/include/Views/ViewSaveGame.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace S2Plugin -{ - class ViewToolbar; - class TreeViewMemoryFields; - - class ViewSaveGame : public QWidget - { - Q_OBJECT - public: - ViewSaveGame(ViewToolbar* toolbar, QWidget* parent = nullptr); - - protected: - void closeEvent(QCloseEvent* event) override; - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - - private slots: - void refreshSaveGame(); - void toggleAutoRefresh(int newState); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); - void label(); - - private: - QVBoxLayout* mMainLayout; - QHBoxLayout* mRefreshLayout; - TreeViewMemoryFields* mMainTreeView; - QPushButton* mRefreshButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; - - ViewToolbar* mToolbar; - - void initializeUI(); - }; -} // namespace S2Plugin diff --git a/include/Views/ViewState.h b/include/Views/ViewState.h deleted file mode 100644 index 3cc7886f..00000000 --- a/include/Views/ViewState.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace S2Plugin -{ - class ViewToolbar; - class TreeViewMemoryFields; - - class ViewState : public QWidget - { - Q_OBJECT - public: - ViewState(ViewToolbar* toolbar, uintptr_t state, QWidget* parent = nullptr); - - protected: - void closeEvent(QCloseEvent* event) override; - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - - private slots: - void refreshState(); - void toggleAutoRefresh(int newState); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); - void label(); - - private: - ViewToolbar* mToolbar; - uintptr_t mState; - - QVBoxLayout* mMainLayout; - QHBoxLayout* mRefreshLayout; - TreeViewMemoryFields* mMainTreeView; - QPushButton* mRefreshButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; - - void initializeUI(); - }; -} // namespace S2Plugin diff --git a/include/Views/ViewStdMap.h b/include/Views/ViewStdMap.h index 5bdc81a0..38f95d25 100644 --- a/include/Views/ViewStdMap.h +++ b/include/Views/ViewStdMap.h @@ -1,52 +1,37 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; class ViewStdMap : public QWidget { Q_OBJECT public: - ViewStdMap(ViewToolbar* toolbar, const std::string& keytypeName, const std::string& valuetypeName, uintptr_t mapOffset, QWidget* parent = nullptr); + ViewStdMap(const std::string& keytypeName, const std::string& valuetypeName, uintptr_t address, QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; private slots: void refreshMapContents(); void refreshData(); - void toggleAutoRefresh(int newState); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); private: std::string mMapKeyType; std::string mMapValueType; - uintptr_t mmapOffset; + uintptr_t mMapAddress; size_t mMapKeyTypeSize; size_t mMapValueTypeSize; uint8_t mMapKeyAlignment; uint8_t mMapValueAlignment; - QVBoxLayout* mMainLayout; TreeViewMemoryFields* mMainTreeView; - QPushButton* mRefreshDataButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; - - void initializeRefreshLayout(); }; } // namespace S2Plugin diff --git a/include/Views/ViewStdVector.h b/include/Views/ViewStdVector.h index 23dd56a1..67de2ac3 100644 --- a/include/Views/ViewStdVector.h +++ b/include/Views/ViewStdVector.h @@ -1,47 +1,33 @@ #pragma once -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; class ViewStdVector : public QWidget { Q_OBJECT public: - ViewStdVector(ViewToolbar* toolbar, const std::string& vectorType, uintptr_t vectorOffset, QWidget* parent = nullptr); + ViewStdVector(const std::string& vectorType, uintptr_t vectorOffset, QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; private slots: void refreshVectorContents(); void refreshData(); - void toggleAutoRefresh(int newState); - void autoRefreshTimerTrigger(); - void autoRefreshIntervalChanged(const QString& text); private: std::string mVectorType; uintptr_t mVectorOffset; size_t mVectorTypeSize; - QVBoxLayout* mMainLayout; TreeViewMemoryFields* mMainTreeView; - QPushButton* mRefreshDataButton; - QCheckBox* mAutoRefreshCheckBox; - QLineEdit* mAutoRefreshIntervalLineEdit; - std::unique_ptr mAutoRefreshTimer; - - void initializeRefreshLayout(); }; } // namespace S2Plugin diff --git a/include/Views/ViewStringsTable.h b/include/Views/ViewStringsTable.h index b0ddc8ec..9486257d 100644 --- a/include/Views/ViewStringsTable.h +++ b/include/Views/ViewStringsTable.h @@ -1,7 +1,9 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" #include +#include +#include +#include #include #include #include @@ -22,20 +24,17 @@ namespace S2Plugin ViewStringsTable(QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; + + protected slots: void cellClicked(const QModelIndex& index); void filterTextChanged(const QString& text); void reload(); private: - QLineEdit* mFilterLineEdit; QTableView* mMainTableView; SortFilterProxyModelStringsTable* mModelProxy; - StyledItemDelegateHTML mHTMLDelegate; - - void initializeUI(); }; } // namespace S2Plugin diff --git a/include/Views/ViewStruct.h b/include/Views/ViewStruct.h new file mode 100644 index 00000000..b864287f --- /dev/null +++ b/include/Views/ViewStruct.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + +namespace S2Plugin +{ + class TreeViewMemoryFields; + struct MemoryField; + + class ViewStruct : public QWidget + { + Q_OBJECT + public: + ViewStruct(uintptr_t address, const std::vector& fields, const std::string name, QWidget* parent = nullptr); + + protected: + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + + private: + TreeViewMemoryFields* mMainTreeView; + }; +} // namespace S2Plugin diff --git a/include/Views/ViewTextureDB.h b/include/Views/ViewTextureDB.h index 120f6784..8a03de1a 100644 --- a/include/Views/ViewTextureDB.h +++ b/include/Views/ViewTextureDB.h @@ -1,66 +1,29 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "QtHelpers/WidgetDatabaseView.h" namespace S2Plugin { - class ViewToolbar; class TreeViewMemoryFields; - class ViewTextureDB : public QWidget + class ViewTextureDB : public WidgetDatabaseView { Q_OBJECT public: - ViewTextureDB(ViewToolbar* toolbar, size_t index = 1, QWidget* parent = nullptr); - void showID(uint32_t id); + ViewTextureDB(QWidget* parent = nullptr); + void showID(ID_type id) override; + void showRAW(uintptr_t address); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; - QSize minimumSizeHint() const override; + void label() const override; + ID_type highestRecordID() const override; + bool isValidRecordID(ID_type id) const override; + std::optional getIDForName(QString name) const override; + QString recordNameForID(ID_type id) const override; + uintptr_t addressOfRecordID(ID_type id) const override; private slots: - void searchFieldReturnPressed(); void searchFieldCompleterActivated(const QString& text); - void label(); - void fieldUpdated(const QString& fieldName); - void fieldExpanded(const QModelIndex& index); - void comparisonFieldChosen(const QString& fieldName); - void compareGroupByCheckBoxClicked(int state); - void comparisonCellClicked(int row, int column); - void groupedComparisonItemClicked(QTreeWidgetItem* item, int column); - - private: - StyledItemDelegateHTML mHTMLDelegate; - - QTabWidget* mMainTabWidget; - QWidget* mTabLookup; - QWidget* mTabCompare; - - // LOOKUP - TreeViewMemoryFields* mMainTreeView; - QLineEdit* mSearchLineEdit; - - // COMPARE - QComboBox* mCompareFieldComboBox; - QTableWidget* mCompareTableWidget; - QTreeWidget* mCompareTreeWidget; - - void initializeUI(); - void updateFieldValues(); - void populateComparisonTableWidget(); - void populateComparisonTreeWidget(); }; } // namespace S2Plugin diff --git a/include/Views/ViewThreads.h b/include/Views/ViewThreads.h index 32ddcfe4..59d2c111 100644 --- a/include/Views/ViewThreads.h +++ b/include/Views/ViewThreads.h @@ -1,24 +1,18 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" +#include #include -#include #include -#include -#include namespace S2Plugin { - class ViewToolbar; - class ViewThreads : public QWidget { Q_OBJECT public: - ViewThreads(ViewToolbar* toolbar); + ViewThreads(QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -27,12 +21,6 @@ namespace S2Plugin void refreshThreads(); private: - ViewToolbar* mToolbar; - StyledItemDelegateHTML mHTMLDelegate; - - QVBoxLayout* mMainLayout; QTableWidget* mMainTable; - - void initializeUI(); }; } // namespace S2Plugin diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index c95b773d..19ff85a1 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include @@ -19,34 +19,37 @@ namespace S2Plugin Q_OBJECT public: ViewToolbar(QMdiArea* mdiArea, QWidget* parent = nullptr); - void showEntity(uintptr_t offset); + void showEntity(uintptr_t address); void showState(uintptr_t statePtr); - void resetSpelunky2Data(); + void showStdVector(uintptr_t address, const std::string& typeName); + void showStdMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName); + void showVirtualFunctions(uintptr_t address, const std::string& typeName); + void showJournalPage(uintptr_t address); + void showLevelGen(uintptr_t address); public slots: ViewEntityDB* showEntityDB(); ViewParticleDB* showParticleDB(); ViewTextureDB* showTextureDB(); + void showStringsTable(); + ViewCharacterDB* showCharacterDB(); + void showMainThreadState(); void showGameManager(); - void showLevelGen(); + void showMainThreadLevelGen(); void showEntities(); ViewVirtualTable* showVirtualTableLookup(); - void showStringsTable(); - ViewCharacterDB* showCharacterDB(); void showSaveGame(); void showLogger(); - void showVirtualFunctions(size_t offset, const std::string& typeName); void showOnline(); - void showStdVector(size_t offset, const std::string& typeName); - void showStdMap(size_t offset, const std::string& keytypeName, const std::string& valuetypeName); - void showJournalPage(size_t offset, const std::string& pageType); void showThreads(); + + private slots: void clearLabels(); void reloadConfig(); private: QMdiArea* mMDIArea; - QVBoxLayout* mMainLayout; + friend struct QtPluginStruct; }; } // namespace S2Plugin diff --git a/include/Views/ViewVirtualFunctions.h b/include/Views/ViewVirtualFunctions.h index 66052483..79d2f693 100644 --- a/include/Views/ViewVirtualFunctions.h +++ b/include/Views/ViewVirtualFunctions.h @@ -1,46 +1,32 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" #include +#include +#include #include -#include -#include +#include +#include namespace S2Plugin { - class ViewToolbar; - class ItemModelVirtualFunctions; - class SortFilterProxyModelVirtualFunctions; - class ViewVirtualFunctions : public QWidget { Q_OBJECT public: - ViewVirtualFunctions(const std::string& typeName, size_t offset, ViewToolbar* toolbar, QWidget* parent = nullptr); + ViewVirtualFunctions(const std::string& typeName, uintptr_t address, QWidget* parent = nullptr); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; private slots: void tableEntryClicked(const QModelIndex& index); - void jumpToFunction(bool b); + void jumpToFunction(); private: - std::string mTypeName; - size_t mMemoryOffset; - ViewToolbar* mToolbar; - QVBoxLayout* mMainLayout; + uintptr_t mMemoryAddress; - QHBoxLayout* mTopLayout; QLineEdit* mJumpToLineEdit; - QTableView* mFunctionsTable; - StyledItemDelegateHTML mHTMLDelegate; - std::unique_ptr mModel; - std::unique_ptr mSortFilterProxy; - - void initializeUI(); }; } // namespace S2Plugin diff --git a/include/Views/ViewVirtualTable.h b/include/Views/ViewVirtualTable.h index d2f5258a..f9517f4c 100644 --- a/include/Views/ViewVirtualTable.h +++ b/include/Views/ViewVirtualTable.h @@ -1,12 +1,14 @@ #pragma once -#include "QtHelpers/StyledItemDelegateHTML.h" -#include #include +#include +#include +#include +#include +#include #include #include -#include -#include +#include namespace S2Plugin { @@ -24,7 +26,6 @@ namespace S2Plugin void updateGatherProgress(); protected: - void closeEvent(QCloseEvent* event) override; QSize sizeHint() const override; QSize minimumSizeHint() const override; @@ -45,29 +46,21 @@ namespace S2Plugin void showGatherHideCompletedCheckBoxStateChanged(int state); private: - QVBoxLayout* mMainLayout; - QTabWidget* mMainTabWidget; - QWidget* mTabData; - QWidget* mTabLookup; - QWidget* mTabGather; // DATA QTableView* mDataTable; - std::unique_ptr mModel; - std::unique_ptr mSortFilterProxy; - StyledItemDelegateHTML mHTMLDelegate; + ItemModelVirtualTable* mModel; + SortFilterProxyModelVirtualTable* mSortFilterProxy; // LOOKUP QLineEdit* mLookupAddressLineEdit; QTableWidget* mLookupResultsTable; // GATHER - QTableView* mGatherTable; - std::unique_ptr mGatherModel; - std::unique_ptr mGatherSortFilterProxy; + ItemModelGatherVirtualData* mGatherModel; + SortFilterProxyModelGatherVirtualData* mGatherSortFilterProxy; QLabel* mGatherProgressLabel; - QCheckBox* mHideCompletedCheckbox; void initializeUI(); void lookupAddress(size_t address); diff --git a/include/read_helpers.h b/include/read_helpers.h index 0cd4a80e..83eb1981 100644 --- a/include/read_helpers.h +++ b/include/read_helpers.h @@ -20,6 +20,7 @@ namespace S2Plugin if (addr == 0) return {}; // reads thru the characters twice but avoids static buffers or resizing string by adding characters one by one + // which is apparently what the basic string constructor does when constructing from const char* pointer constexpr auto char_size = sizeof(T); size_t size = 0; diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 60418e31..eb7d11d5 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -108,6 +108,23 @@ "EntityList": 8, "ItemOwnerDetails": 4 }, + "journal_pages": [ + "JournalPage", + "JournalPageProgress", + "JournalPageJournalMenu", + "JournalPagePlaces", + "JournalPagePeople", + "JournalPageBestiary", + "JournalPageItems", + "JournalPageTraps", + "JournalPageStory", + "JournalPageFeats", + "JournalPageDeathCause", + "JournalPageDeathMenu", + "JournalPageRecap", + "JournalPagePlayerProfile", + "JournalPageLastGamePlayed" + ], "refs": { "screen_states": { "-1": "Game startup", @@ -1549,7 +1566,6 @@ { "field": "unknown28", "type": "UnsignedDword" }, { "field": "unknown29", "type": "DataPointer" } ], - "JournalPagePointer": [], "JournalPage": [ { "field": "__vftable", @@ -1558,46 +1574,23 @@ "0": { "name": "~JournalPage", "params": "", "return": "" }, "1": { "name": "v_unknown1", "params": "", "return": "" }, "2": { "name": "v_unknown2", "params": "", "return": "" }, - "3": { "name": "v_unknown3", "params": "", "return": "" }, + "3": { "name": "v_unknown3", "params": "", "return": "bool" }, "4": { "name": "render", "params": "", "return": "" } } }, { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" } + { "field": "page_number", "type": "UnsignedDword" }, + { "field": "padding", "type": "UnsignedDword" } ], "JournalPageProgress": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "coffeestain_top", "type": "TextureRenderingInfo" } ], "JournalPageJournalMenu": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "menu", "type": "MenuInsert" }, { "field": "journal_text_info", "type": "TextRenderingInfoPointer" }, { "field": "completion_badge", "type": "TextureRenderingInfo" } ], "JournalPagePlaces": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "show_main_image", "type": "Bool", @@ -1624,14 +1617,6 @@ { "field": "unknown14", "type": "UnsignedDword" } ], "JournalPagePeople": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "show_main_image", "type": "Bool", @@ -1659,14 +1644,6 @@ { "field": "character_drawing", "type": "TextureRenderingInfo" } ], "JournalPageBestiary": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "show_main_image", "type": "Bool", @@ -1724,14 +1701,6 @@ { "field": "y", "type": "Float" } ], "JournalPageItems": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "show_main_image", "type": "Bool", @@ -1760,14 +1729,6 @@ { "field": "unknown15", "type": "UnsignedDword" } ], "JournalPageTraps": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "show_main_image", "type": "Bool", @@ -1800,24 +1761,8 @@ { "field": "unknown19", "type": "UnsignedDword" }, { "field": "unknown20", "type": "UnsignedDword" } ], - "JournalPageStory": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" } - ], + "JournalPageStory": [], "JournalPageFeats": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "chapter_title_text_info", "type": "TextRenderingInfoPointer" @@ -1825,25 +1770,9 @@ { "field": "feat_icons", "type": "TextureRenderingInfo" } ], "JournalPageDeathCause": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "death_cause_text_info", "type": "TextRenderingInfoPointer" } ], "JournalPageDeathMenu": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "menu", "type": "MenuInsert" }, { "field": "game_over_text_info", "type": "TextRenderingInfoPointer" }, { "field": "level_text_info", "type": "TextRenderingInfoPointer" }, @@ -1853,24 +1782,8 @@ { "field": "time_text_info", "type": "TextRenderingInfoPointer" }, { "field": "time_value_text_info", "type": "TextRenderingInfoPointer" } ], - "JournalPageRecap": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" } - ], + "JournalPageRecap": [], "JournalPagePlayerProfile": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "player_icon", "type": "TextureRenderingInfo" }, { "field": "player_icon_id", "type": "UnsignedDword" }, { "field": "unknown4", "type": "Float" }, @@ -1930,14 +1843,6 @@ } ], "JournalPageLastGamePlayed": [ - { - "field": "__vftable", - "type": "DataPointer", - "comment": "see JournalPagePointer" - }, - { "field": "background", "type": "TextureRenderingInfo" }, - { "field": "page_number", "type": "UnsignedDword" }, - { "field": "-", "type": "Skip", "offset": 4 }, { "field": "main_image", "type": "TextureRenderingInfo" }, { "field": "last_game_played_text_info", @@ -8230,48 +8135,7 @@ { "field": "unknown14", "type": "Bool" }, { "field": "unknown15", "type": "UnsignedByte" }, { "field": "unknown16", "type": "UnsignedByte" }, - { "field": "unknown17", "type": "UnsignedDword" }, - { "field": "unknown18", "type": "UnsignedDword" }, - { "field": "unknown19", "type": "UnsignedDword" }, - { - "field": "entity_items_begin", - "type": "DataPointer", - "comment": "begin of the memory that holds the items of entities" - }, - { "field": "unknown21", "type": "DataPointer" }, - { "field": "unknown22", "type": "DataPointer" }, - { "field": "unknown23", "type": "Bool" }, - { - "field": "layer_freeze?", - "type": "Bool", - "comment": "locking mechanism?" - }, - { "field": "unknown25", "type": "UnsignedByte" }, - { "field": "unknown26", "type": "UnsignedByte" }, - { "field": "unknown27", "type": "UnsignedDword" }, - { "field": "unknown28", "type": "UnsignedQword" }, - { "field": "unknown29", "type": "UnsignedQword" }, - { "field": "unknown30", "type": "UnsignedQword" }, - { "field": "unknown31", "type": "UnsignedQword" }, - { "field": "unknown32", "type": "UnsignedQword" }, - { "field": "unknown33", "type": "UnsignedDword" }, - { "field": "unknown34", "type": "UnsignedDword" }, - { "field": "unknown35", "type": "DataPointer" }, - { "field": "unknown36", "type": "DataPointer" }, - { "field": "unknown37", "type": "DataPointer" }, - { "field": "unknown38", "type": "Bool" }, - { "field": "unknown39", "type": "Bool" }, - { "field": "unknown40", "type": "UnsignedByte" }, - { "field": "unknown41", "type": "UnsignedByte" }, - { "field": "unknown42", "type": "UnsignedDword" }, - { "field": "unknown43", "type": "UnsignedQword" }, - { "field": "unknown44", "type": "UnsignedQword" }, - { "field": "unknown45", "type": "UnsignedQword" }, - { - "field": "unknown46", - "type": "UnsignedQword", - "comment": "next layer below" - } + { "field": "unknown17", "type": "UnsignedDword" } ], "PointerToPointerToEntityList": [ { @@ -9032,7 +8896,7 @@ { "field": "padding3?", "type": "UnsignedDword" }, { "field": "sub_theme", - "type": "ThemeInfoName", + "type": "ThemeInfoPointer", "comment": "for cosmic ocean" }, { "field": "unknown3", "type": "UnsignedDword" }, diff --git a/resources/spelunky2.qrc b/resources/spelunky2.qrc index c21da040..37c9d783 100644 --- a/resources/spelunky2.qrc +++ b/resources/spelunky2.qrc @@ -1,5 +1,20 @@ - - caveman.png - + + caveman.png + + + stylesheet-vline.png + + + stylesheet-branch-more.png + + + stylesheet-branch-end.png + + + stylesheet-branch-closed.png + + + stylesheet-branch-open.png + \ No newline at end of file diff --git a/resources/stylesheet-branch-closed.png b/resources/stylesheet-branch-closed.png new file mode 100644 index 00000000..3203da14 Binary files /dev/null and b/resources/stylesheet-branch-closed.png differ diff --git a/resources/stylesheet-branch-end.png b/resources/stylesheet-branch-end.png new file mode 100644 index 00000000..54915b3b Binary files /dev/null and b/resources/stylesheet-branch-end.png differ diff --git a/resources/stylesheet-branch-more.png b/resources/stylesheet-branch-more.png new file mode 100644 index 00000000..664ad447 Binary files /dev/null and b/resources/stylesheet-branch-more.png differ diff --git a/resources/stylesheet-branch-open.png b/resources/stylesheet-branch-open.png new file mode 100644 index 00000000..60470061 Binary files /dev/null and b/resources/stylesheet-branch-open.png differ diff --git a/resources/stylesheet-vline.png b/resources/stylesheet-vline.png new file mode 100644 index 00000000..8f0c336f Binary files /dev/null and b/resources/stylesheet-vline.png differ diff --git a/src/Configuration.cpp b/src/Configuration.cpp index ba0d6725..b21cc7dc 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -1,5 +1,5 @@ #include "Configuration.h" -#include "Spelunky2.h" + #include "pluginmain.h" #include #include @@ -134,7 +134,7 @@ namespace S2Plugin {MemoryFieldType::ConstCharPointer, "Const char*", "const char*", "ConstCharPointer", 8, true}, {MemoryFieldType::ConstCharPointerPointer, "Const char**", "const char**", "ConstCharPointerPointer", 8, true}, // there is more then just pointer to pointer? {MemoryFieldType::UndeterminedThemeInfoPointer, "UndeterminedThemeInfoPointer", "ThemeInfo*", "UndeterminedThemeInfoPointer", 8, true}, // display theme name and add ThemeInfo fields - {MemoryFieldType::ThemeInfoName, "ThemeInfoName", "ThemeInfo*", "ThemeInfoName", 8, true}, // just theme name + {MemoryFieldType::ThemeInfoPointer, "ThemeInfoPointer", "ThemeInfo*", "ThemeInfoPointer", 8, true}, // just theme name {MemoryFieldType::LevelGenRoomsPointer, "LevelGenRoomsPointer", "LevelGenRooms*", "LevelGenRoomsPointer", 8, true}, {MemoryFieldType::LevelGenRoomsMetaPointer, "LevelGenRoomsMetaPointer", "LevelGenRoomsMeta*", "LevelGenRoomsMetaPointer", 8, true}, {MemoryFieldType::JournalPagePointer, "JournalPagePointer", "JournalPage*", "JournalPagePointer", 8, true}, @@ -264,9 +264,7 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman memField.type = MemoryFieldType::DefaultStructType; // just initial std::string fieldTypeStr = field["type"].get(); - bool knownPointer = mPointerTypes.find(fieldTypeStr) != mPointerTypes.end(); - - if (knownPointer || value_or(field, "pointer", false)) + if (isPermanentPointer(fieldTypeStr) || value_or(field, "pointer", false)) { memField.isPointer = true; memField.size = sizeof(uintptr_t); @@ -476,9 +474,11 @@ void S2Plugin::Configuration::processEntitiesJSON(ordered_json& j) void S2Plugin::Configuration::processJSON(ordered_json& j) { for (const auto& t : j["pointer_types"]) - { - mPointerTypes.emplace(t.get()); - } + mPointerTypes.emplace_back(t.get()); + + for (const auto& t : j["journal_pages"]) + mJournalPages.emplace_back(t.get()); + for (const auto& [key, jsonValue] : j["struct_alignments"].items()) { uint8_t val = jsonValue.get(); @@ -497,7 +497,6 @@ void S2Plugin::Configuration::processJSON(ordered_json& j) } mRefs[key] = std::move(vec); } - for (const auto& [key, jsonArray] : j["fields"].items()) { std::vector vec; diff --git a/src/Data/Logger.cpp b/src/Data/Logger.cpp index 28d3e5b8..2abc6c01 100644 --- a/src/Data/Logger.cpp +++ b/src/Data/Logger.cpp @@ -15,7 +15,7 @@ void S2Plugin::Logger::addField(const LoggerField& field) } } -void S2Plugin::Logger::removeFieldAt(size_t fieldIndex) +void S2Plugin::Logger::removeFieldAt(int fieldIndex) { if (mTableModel != nullptr) { @@ -27,7 +27,7 @@ void S2Plugin::Logger::removeFieldAt(size_t fieldIndex) } } -void S2Plugin::Logger::start(size_t samplePeriod, size_t duration) +void S2Plugin::Logger::start(int samplePeriod, int duration) { mSamples.clear(); diff --git a/src/Data/Lookup.cpp b/src/Data/Lookup.cpp index 6f7dab43..6b067b50 100644 --- a/src/Data/Lookup.cpp +++ b/src/Data/Lookup.cpp @@ -1,5 +1,3 @@ -#pragma once - #include "Spelunky2.h" #include "pluginmain.h" #include "read_helpers.h" @@ -72,6 +70,7 @@ const S2Plugin::TextureDB& S2Plugin::Spelunky2::get_TextureDB() { uintptr_t offset = mTextureDB.ptr + textureSize * x; auto textureID = Script::Memory::ReadQword(offset); + mTextureDB.mHighestID = std::max(mTextureDB.mHighestID, textureID); auto nameOffset = offset + 0x8; diff --git a/src/Data/State.cpp b/src/Data/State.cpp index d8c0dbe4..86bb1b67 100644 --- a/src/Data/State.cpp +++ b/src/Data/State.cpp @@ -14,11 +14,11 @@ static constexpr uint32_t lowbias32(uint32_t x) // Just for refrence // struct RobinHoodTableEntry -//{ +// { // uint32_t uid_plus_one; // uint32_t padding; // Entity* entity; -//}; +// }; uintptr_t S2Plugin::State::findEntitybyUID(uint32_t uid) const { @@ -28,6 +28,7 @@ uintptr_t S2Plugin::State::findEntitybyUID(uint32_t uid) const return 0; } + // [Known Issue]: Static value, have to restart programm for size to update static size_t mask_offset = Configuration::get()->offsetForField(MemoryFieldType::State, "uid_to_entity_mask"); const uint32_t mask = Script::Memory::ReadDword(mStatePtr + mask_offset); const uint32_t target_uid_plus_one = lowbias32(uid + 1u); diff --git a/src/Data/StringsTable.cpp b/src/Data/StringsTable.cpp index 5c17c9a4..b7ac27b5 100644 --- a/src/Data/StringsTable.cpp +++ b/src/Data/StringsTable.cpp @@ -5,15 +5,15 @@ QString S2Plugin::StringsTable::stringForIndex(uint32_t idx) const { - if (count() < idx) + if (count() <= idx) { return QString("INVALID OR NOT APPLICABLE"); } - auto str = ReadConstBasicString(stringaddressOfIndex(idx)); - return QString::fromUtf16(str.c_str(), str.size()); + auto str = ReadConstBasicString(stringAddressOfIndex(idx)); + return QString::fromUtf16(str.c_str(), static_cast(str.size())); } -uintptr_t S2Plugin::StringsTable::stringaddressOfIndex(uint32_t idx) const +uintptr_t S2Plugin::StringsTable::stringAddressOfIndex(uint32_t idx) const { return Script::Memory::ReadQword(addressOfIndex(idx)); } diff --git a/src/Data/VirtualTableLookup.cpp b/src/Data/VirtualTableLookup.cpp index 0c9417dc..ba442abc 100644 --- a/src/Data/VirtualTableLookup.cpp +++ b/src/Data/VirtualTableLookup.cpp @@ -32,7 +32,5 @@ S2Plugin::VirtualTableEntry S2Plugin::VirtualTableLookup::findPrecedingEntryWith } counter--; } - - VirtualTableEntry dummy; - return dummy; + return {}; } diff --git a/src/QtHelpers/CPPSyntaxHighlighter.cpp b/src/QtHelpers/CPPSyntaxHighlighter.cpp index 491c6cf0..7e871b88 100644 --- a/src/QtHelpers/CPPSyntaxHighlighter.cpp +++ b/src/QtHelpers/CPPSyntaxHighlighter.cpp @@ -1,5 +1,4 @@ #include "QtHelpers/CPPSyntaxHighlighter.h" -#include "pluginmain.h" S2Plugin::CPPSyntaxHighlighter::CPPSyntaxHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) { diff --git a/src/QtHelpers/DatabaseHelper.cpp b/src/QtHelpers/DatabaseHelper.cpp deleted file mode 100644 index 1b8c8f50..00000000 --- a/src/QtHelpers/DatabaseHelper.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "QtHelpers/DatabaseHelper.h" - -#include "Configuration.h" -#include "pluginmain.h" -#include - -// TODO: instead of adding all the flags as separate options, add flags and when selected, show another combo box with the flags - -struct ComparisonField -{ - S2Plugin::MemoryFieldType type{S2Plugin::MemoryFieldType::None}; - size_t offset; - uint8_t flag_index; -}; -Q_DECLARE_METATYPE(ComparisonField) - -size_t S2Plugin::DB::populateComparisonCombobox(QComboBox* CompareFieldComboBox, const std::vector& fields, size_t offset, std::string prefix) -{ - for (const auto& field : fields) - { - if (field.isPointer) - { - // we don't do pointers as i don't think there are any important ones for comparison in databases - - offset += sizeof(uintptr_t); - continue; - } - switch (field.type) - { - case MemoryFieldType::Skip: - break; - case MemoryFieldType::Flags32: - case MemoryFieldType::Flags16: - case MemoryFieldType::Flags8: - { - ComparisonField parrentFlag; - parrentFlag.type = field.type; - parrentFlag.offset = offset; - CompareFieldComboBox->addItem(QString::fromStdString(prefix + field.name), QVariant::fromValue(parrentFlag)); - uint8_t flagCount = (field.type == MemoryFieldType::Flags16 ? 16 : (field.type == MemoryFieldType::Flags8 ? 8 : 32)); - for (uint8_t x = 1; x <= flagCount; ++x) - { - ComparisonField flag; - flag.type = MemoryFieldType::Flag; - flag.offset = offset; - flag.flag_index = x - 1; - // TODO: use flag name? - CompareFieldComboBox->addItem(QString::fromStdString(prefix + field.name + ".flag_" + std::to_string(x)), QVariant::fromValue(flag)); - } - break; - } - case MemoryFieldType::DefaultStructType: - { - offset = populateComparisonCombobox(CompareFieldComboBox, Configuration::get()->typeFieldsOfDefaultStruct(field.jsonName), offset, prefix + field.name + "."); - continue; - } - default: - { - ComparisonField tmp; - tmp.offset = offset; - tmp.type = field.type; - CompareFieldComboBox->addItem(QString::fromStdString(prefix + field.name), QVariant::fromValue(tmp)); - break; - } - } - offset += field.get_size(); - } - return offset; -} - -std::pair S2Plugin::DB::valueForField(const QVariant& data, uintptr_t addr) -{ - ComparisonField compData = qvariant_cast(data); - - auto offset = addr + compData.offset; - switch (compData.type) - { - // we only handle types that are occur in the db's - case MemoryFieldType::Byte: - case MemoryFieldType::State8: - { - int8_t value = Script::Memory::ReadByte(offset); - return std::make_pair(QString::asprintf("%d", value), QVariant::fromValue(value)); - } - case MemoryFieldType::CharacterDBID: - case MemoryFieldType::UnsignedByte: - case MemoryFieldType::Flags8: - { - uint8_t value = Script::Memory::ReadByte(offset); - return std::make_pair(QString::asprintf("%u", value), QVariant::fromValue(value)); - } - case MemoryFieldType::Word: - case MemoryFieldType::State16: - { - int16_t value = Script::Memory::ReadWord(offset); - return std::make_pair(QString::asprintf("%d", value), QVariant::fromValue(value)); - } - case MemoryFieldType::UnsignedWord: - case MemoryFieldType::Flags16: - { - uint16_t value = Script::Memory::ReadWord(offset); - return std::make_pair(QString::asprintf("%u", value), QVariant::fromValue(value)); - } - case MemoryFieldType::TextureDBID: - case MemoryFieldType::Dword: - case MemoryFieldType::State32: - { - int32_t value = Script::Memory::ReadDword(offset); - return std::make_pair(QString::asprintf("%ld", value), QVariant::fromValue(value)); - } - case MemoryFieldType::ParticleDBID: - case MemoryFieldType::EntityDBID: - case MemoryFieldType::StringsTableID: - case MemoryFieldType::UnsignedDword: - case MemoryFieldType::Flags32: - { - uint32_t value = Script::Memory::ReadDword(offset); - return std::make_pair(QString::asprintf("%lu", value), QVariant::fromValue(value)); - } - case MemoryFieldType::Qword: - { - int64_t value = Script::Memory::ReadQword(offset); - return std::make_pair(QString::asprintf("%lld", value), QVariant::fromValue(value)); - } - case MemoryFieldType::UnsignedQword: - { - uint64_t value = Script::Memory::ReadQword(offset); - return std::make_pair(QString::asprintf("%llu", value), QVariant::fromValue(value)); - } - case MemoryFieldType::Float: - { - uint32_t dword = Script::Memory::ReadDword(offset); - float value = reinterpret_cast(dword); - return std::make_pair(QString::asprintf("%f", value), QVariant::fromValue(value)); - } - case MemoryFieldType::Double: - { - size_t qword = Script::Memory::ReadQword(offset); - double value = reinterpret_cast(qword); - return std::make_pair(QString::asprintf("%lf", value), QVariant::fromValue(value)); - } - case MemoryFieldType::Bool: - { - auto b = Script::Memory::ReadByte(offset); - bool value = reinterpret_cast(b); - return std::make_pair(value ? "True" : "False", QVariant::fromValue(b)); - } - case MemoryFieldType::Flag: - { - uint8_t flagToCheck = compData.flag_index; - bool isFlagSet = false; - if (flagToCheck > 15) - isFlagSet = ((Script::Memory::ReadDword(offset) & (1 << flagToCheck)) != 0); - else if (flagToCheck > 7) - isFlagSet = ((Script::Memory::ReadWord(offset) & (1 << flagToCheck)) != 0); - else - isFlagSet = ((Script::Memory::ReadByte(offset) & (1 << flagToCheck)) != 0); - - bool value = reinterpret_cast(isFlagSet); - return std::make_pair(value ? "True" : "False", QVariant::fromValue(isFlagSet)); - } - } - return std::make_pair("unknown", 0); -} diff --git a/src/QtHelpers/DialogEditSimpleValue.cpp b/src/QtHelpers/DialogEditSimpleValue.cpp index 3ba9fd79..444d07eb 100644 --- a/src/QtHelpers/DialogEditSimpleValue.cpp +++ b/src/QtHelpers/DialogEditSimpleValue.cpp @@ -1,11 +1,10 @@ #include "QtHelpers/DialogEditSimpleValue.h" #include "Configuration.h" -#include "pluginmain.h" -#include +#include "QtHelpers/LongLongSpinBox.h" +#include "QtPlugin.h" +#include "read_helpers.h" #include #include -#include -#include #include #include #include @@ -17,7 +16,7 @@ S2Plugin::DialogEditSimpleValue::DialogEditSimpleValue(const QString& fieldName, { setModal(true); setWindowTitle("Change value"); - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); auto layout = new QVBoxLayout(this); // FIELDS @@ -28,89 +27,108 @@ S2Plugin::DialogEditSimpleValue::DialogEditSimpleValue(const QString& fieldName, gridLayout->addWidget(new QLabel("New value (dec):", this), 1, 0); gridLayout->addWidget(new QLabel("New value (hex):", this), 2, 0); - mLineEditDecValue = new QLineEdit(this); mLineEditHexValue = new QLineEdit(this); mLineEditHexValue->setDisabled(true); - QObject::connect(mLineEditDecValue, &QLineEdit::textChanged, this, &DialogEditSimpleValue::decValueChanged); switch (mFieldType) { case MemoryFieldType::Byte: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - int8_t v = Script::Memory::ReadByte(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::UnsignedByte: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - uint8_t v = Script::Memory::ReadByte(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::Word: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - int16_t v = Script::Memory::ReadWord(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } - case MemoryFieldType::UTF16Char: case MemoryFieldType::UnsignedWord: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - uint16_t v = Script::Memory::ReadWord(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::Dword: { - mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - int32_t v = Script::Memory::ReadDword(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new QSpinBox(this); + QObject::connect(spinBox, static_cast(&QSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::UnsignedDword: case MemoryFieldType::StringsTableID: { - // mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - uint32_t v = Script::Memory::ReadDword(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + // no point of making SpinBox for uint32_t type, we can just use bigger type + auto spinBox = new ULongLongSpinBox(this); + QObject::connect(spinBox, &ULongLongSpinBox::valueChanged, this, &DialogEditSimpleValue::decValueChanged); + spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::Qword: { - // mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - int64_t v = Script::Memory::ReadQword(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new LongLongSpinBox(this); + QObject::connect(spinBox, &LongLongSpinBox::valueChanged, this, &DialogEditSimpleValue::decValueChanged); + // spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::UnsignedQword: { - // mLineEditDecValue->setValidator(new QIntValidator((std::numeric_limits::min)(), (std::numeric_limits::max)(), this)); - uint64_t v = Script::Memory::ReadQword(mMemoryAddress); - mLineEditDecValue->setText(QString("%1").arg(v)); + auto spinBox = new ULongLongSpinBox(this); + QObject::connect(spinBox, &ULongLongSpinBox::valueChanged, this, &DialogEditSimpleValue::decValueChanged); + // spinBox->setRange(std::numeric_limits::min(), std::numeric_limits::max()); + auto v = Read(mMemoryAddress); + spinBox->setValue(v); + mSpinBox = spinBox; break; } case MemoryFieldType::Float: - { - mLineEditDecValue->setValidator(new QDoubleValidator((std::numeric_limits::max)() * -1, (std::numeric_limits::max)(), 1000, this)); - uint32_t tmp = Script::Memory::ReadDword(mMemoryAddress); - float v = reinterpret_cast(tmp); - mLineEditDecValue->setText(QString("%1").arg(v)); - break; - } case MemoryFieldType::Double: { - mLineEditDecValue->setValidator(new QDoubleValidator((std::numeric_limits::max)() * -1, (std::numeric_limits::max)(), 1000, this)); + auto spinbox = new QDoubleSpinBox(this); + mSpinBox = spinbox; + QObject::connect(spinbox, static_cast(&QDoubleSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); size_t tmp = Script::Memory::ReadQword(mMemoryAddress); double v = reinterpret_cast(tmp); - mLineEditDecValue->setText(QString("%1").arg(v)); + spinbox->setValue(v); break; } } - gridLayout->addWidget(mLineEditDecValue, 1, 1); + decValueChanged(mSpinBox->text()); + + gridLayout->addWidget(mSpinBox, 1, 1); gridLayout->addWidget(mLineEditHexValue, 2, 1); // BUTTONS @@ -131,9 +149,7 @@ S2Plugin::DialogEditSimpleValue::DialogEditSimpleValue(const QString& fieldName, layout->addStretch(); layout->addLayout(buttonLayout); - setLayout(layout); - mLineEditDecValue->setFocus(); - mLineEditDecValue->selectAll(); + mSpinBox->setFocus(); } QSize S2Plugin::DialogEditSimpleValue::minimumSizeHint() const @@ -154,70 +170,90 @@ void S2Plugin::DialogEditSimpleValue::cancelBtnClicked() void S2Plugin::DialogEditSimpleValue::changeBtnClicked() { + switch (mFieldType) { case MemoryFieldType::Byte: - { - int8_t v = mLineEditDecValue->text().toInt(); - Script::Memory::WriteByte(mMemoryAddress, v); - break; - } case MemoryFieldType::UnsignedByte: { - uint8_t v = mLineEditDecValue->text().toInt(); - Script::Memory::WriteByte(mMemoryAddress, v); + int v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteByte(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::Word: - { - int16_t v = mLineEditDecValue->text().toShort(); - Script::Memory::WriteWord(mMemoryAddress, v); - break; - } case MemoryFieldType::UnsignedWord: - case MemoryFieldType::UTF16Char: { - uint16_t v = mLineEditDecValue->text().toUShort(); - Script::Memory::WriteWord(mMemoryAddress, v); + int v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteByte(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::Dword: { - int32_t v = mLineEditDecValue->text().toLong(); - Script::Memory::WriteDword(mMemoryAddress, v); + int v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteByte(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::UnsignedDword: case MemoryFieldType::StringsTableID: { - uint32_t v = mLineEditDecValue->text().toULong(); + int v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + Script::Memory::WriteDword(mMemoryAddress, v); break; } case MemoryFieldType::Qword: { - int64_t v = mLineEditDecValue->text().toLongLong(); + int64_t v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + Script::Memory::WriteQword(mMemoryAddress, v); break; } case MemoryFieldType::UnsignedQword: { - uint64_t v = mLineEditDecValue->text().toULongLong(); + uint64_t v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + Script::Memory::WriteQword(mMemoryAddress, v); break; } case MemoryFieldType::Float: { - float v = mLineEditDecValue->text().toFloat(); - uint32_t tmp = reinterpret_cast(v); - Script::Memory::WriteDword(mMemoryAddress, tmp); + float v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteDword(mMemoryAddress, reinterpret_cast(v)); break; } case MemoryFieldType::Double: { - double v = mLineEditDecValue->text().toDouble(); - size_t tmp = reinterpret_cast(v); - Script::Memory::WriteQword(mMemoryAddress, tmp); + double v = 0; + auto obj = qobject_cast(mSpinBox); + if (obj) // probably not needed but just in case + v = obj->value(); + + Script::Memory::WriteQword(mMemoryAddress, reinterpret_cast(v)); break; } } @@ -231,64 +267,63 @@ void S2Plugin::DialogEditSimpleValue::decValueChanged(const QString& text) { case MemoryFieldType::Byte: { - int8_t v = mLineEditDecValue->text().toInt(); + int8_t v = text.toInt(); ss << QString::asprintf("0x%02x", static_cast(v)).toStdString(); break; } case MemoryFieldType::UnsignedByte: { - uint8_t v = mLineEditDecValue->text().toInt(); + uint8_t v = text.toInt(); ss << "0x" << std::hex << std::setw(2) << std::setfill('0') << static_cast(v); break; } case MemoryFieldType::Word: { - int16_t v = mLineEditDecValue->text().toShort(); + int16_t v = text.toShort(); ss << "0x" << std::hex << std::setw(4) << std::setfill('0') << v; break; } case MemoryFieldType::UnsignedWord: - case MemoryFieldType::UTF16Char: { - uint16_t v = mLineEditDecValue->text().toUShort(); + uint16_t v = text.toUShort(); ss << "0x" << std::hex << std::setw(4) << std::setfill('0') << v; break; } case MemoryFieldType::Dword: { - int32_t v = mLineEditDecValue->text().toLong(); + int32_t v = text.toLong(); ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << v; break; } case MemoryFieldType::UnsignedDword: case MemoryFieldType::StringsTableID: { - uint32_t v = mLineEditDecValue->text().toULong(); + uint32_t v = text.toULong(); ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << v; break; } case MemoryFieldType::Qword: { - int64_t v = mLineEditDecValue->text().toLongLong(); + int64_t v = text.toLongLong(); ss << "0x" << std::hex << std::setw(16) << std::setfill('0') << v; break; } case MemoryFieldType::UnsignedQword: { - uint64_t v = mLineEditDecValue->text().toULongLong(); + uint64_t v = text.toULongLong(); ss << "0x" << std::hex << std::setw(16) << std::setfill('0') << v; break; } case MemoryFieldType::Float: { - float v = mLineEditDecValue->text().toFloat(); + float v = text.toFloat(); uint32_t tmp = reinterpret_cast(v); ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << tmp; break; } case MemoryFieldType::Double: { - double v = mLineEditDecValue->text().toDouble(); + double v = text.toDouble(); size_t tmp = reinterpret_cast(v); ss << "0x" << std::hex << std::setw(16) << std::setfill('0') << tmp; break; diff --git a/src/QtHelpers/DialogEditState.cpp b/src/QtHelpers/DialogEditState.cpp index 75424372..fd40fed2 100644 --- a/src/QtHelpers/DialogEditState.cpp +++ b/src/QtHelpers/DialogEditState.cpp @@ -1,6 +1,7 @@ #include "QtHelpers/DialogEditState.h" #include "Configuration.h" #include "QtHelpers/ItemModelStates.h" +#include "QtPlugin.h" #include "Spelunky2.h" #include "pluginmain.h" #include @@ -15,7 +16,7 @@ S2Plugin::DialogEditState::DialogEditState(const QString& fieldName, const std:: { setModal(true); setWindowTitle("Change value"); - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); auto layout = new QVBoxLayout(this); // STATES @@ -83,8 +84,6 @@ S2Plugin::DialogEditState::DialogEditState(const QString& fieldName, const std:: layout->addLayout(gridLayout); layout->addStretch(); layout->addLayout(buttonLayout); - - setLayout(layout); } QSize S2Plugin::DialogEditState::minimumSizeHint() const @@ -141,7 +140,7 @@ void S2Plugin::DialogEditState::changeBtnClicked() accept(); } -void S2Plugin::DialogEditState::stateComboBoxChanged(int index) +void S2Plugin::DialogEditState::stateComboBoxChanged() { auto currentState = mStatesComboBox->currentData().toString(); mStateLineEdit->setText(currentState); diff --git a/src/QtHelpers/DialogEditString.cpp b/src/QtHelpers/DialogEditString.cpp new file mode 100644 index 00000000..440277fd --- /dev/null +++ b/src/QtHelpers/DialogEditString.cpp @@ -0,0 +1,94 @@ +#include "QtHelpers/DialogEditString.h" +#include "Configuration.h" +#include "QtPlugin.h" +#include "pluginmain.h" +#include +#include +#include +#include +#include +#include + +S2Plugin::DialogEditString::DialogEditString(const QString& fieldName, QString value, uintptr_t memoryAddress, int size, MemoryFieldType type, QWidget* parent) + : QDialog(parent, Qt::WindowCloseButtonHint | Qt::WindowTitleHint), mMemoryAddress(memoryAddress), mFieldType(type) +{ + setModal(true); + setWindowTitle("Change value"); + setWindowIcon(getCavemanIcon()); + auto layout = new QVBoxLayout(this); + + // FIELDS + auto gridLayout = new QGridLayout(); + + gridLayout->addWidget(new QLabel(QString("Change value of %1").arg(fieldName), this), 0, 0, 1, 2); + gridLayout->addWidget(new QLabel("New text:", this), 1, 0); + + mLineEdit = new QLineEdit(this); + mLineEdit->setMaxLength(size); + mLineEdit->setText(value); + gridLayout->addWidget(mLineEdit, 1, 1); + + // BUTTONS + auto buttonLayout = new QHBoxLayout(); + + auto cancelBtn = new QPushButton("Cancel", this); + QObject::connect(cancelBtn, &QPushButton::clicked, this, &DialogEditString::cancelBtnClicked); + cancelBtn->setAutoDefault(false); + auto changeBtn = new QPushButton("Change", this); + QObject::connect(changeBtn, &QPushButton::clicked, this, &DialogEditString::changeBtnClicked); + changeBtn->setAutoDefault(true); + + buttonLayout->addStretch(); + buttonLayout->addWidget(cancelBtn); + buttonLayout->addWidget(changeBtn); + + layout->addLayout(gridLayout); + layout->addStretch(); + layout->addLayout(buttonLayout); + + mLineEdit->setFocus(); + mLineEdit->selectAll(); +} + +QSize S2Plugin::DialogEditString::minimumSizeHint() const +{ + + return QSize(350, 150); +} + +QSize S2Plugin::DialogEditString::sizeHint() const +{ + return minimumSizeHint(); +} + +void S2Plugin::DialogEditString::cancelBtnClicked() +{ + reject(); +} + +void S2Plugin::DialogEditString::changeBtnClicked() +{ + switch (mFieldType) + { + case MemoryFieldType::UTF16Char: + { + ushort v = mLineEdit->text().isEmpty() ? 0 : mLineEdit->text()[0].unicode(); + Script::Memory::WriteWord(mMemoryAddress, v); + break; + } + case MemoryFieldType::UTF16StringFixedSize: + { + auto v = mLineEdit->text().toStdWString(); + Script::Memory::Write(mMemoryAddress, v.data(), (v.size() + 1) * 2, nullptr); // +1 to include null character + break; + } + case MemoryFieldType::ConstCharPointer: + case MemoryFieldType::UTF8StringFixedSize: + { + auto v = mLineEdit->text().toStdString(); + Script::Memory::Write(mMemoryAddress, v.data(), v.size() + 1, nullptr); // +1 to include null character + break; + } + } + accept(); +} diff --git a/src/QtHelpers/ItemModelGatherVirtualData.cpp b/src/QtHelpers/ItemModelGatherVirtualData.cpp index 4566a16e..3cac4f83 100644 --- a/src/QtHelpers/ItemModelGatherVirtualData.cpp +++ b/src/QtHelpers/ItemModelGatherVirtualData.cpp @@ -1,4 +1,5 @@ #include "QtHelpers/ItemModelGatherVirtualData.h" + #include "Configuration.h" #include "Spelunky2.h" #include "pluginmain.h" @@ -21,16 +22,6 @@ enum VIRT_FUNC : uint32_t MOVABLE_DAMAGE = 48, }; -S2Plugin::ItemModelGatherVirtualData::ItemModelGatherVirtualData(QObject* parent) : QAbstractItemModel(parent) -{ - parseJSON(); -} - -Qt::ItemFlags S2Plugin::ItemModelGatherVirtualData::flags(const QModelIndex& index) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; -} - QVariant S2Plugin::ItemModelGatherVirtualData::data(const QModelIndex& index, int role) const { if (role == Qt::DisplayRole) @@ -63,26 +54,6 @@ QVariant S2Plugin::ItemModelGatherVirtualData::data(const QModelIndex& index, in return QVariant(); } -int S2Plugin::ItemModelGatherVirtualData::rowCount(const QModelIndex& parent) const -{ - return mEntries.size(); -} - -int S2Plugin::ItemModelGatherVirtualData::columnCount(const QModelIndex& parent) const -{ - return 10; -} - -QModelIndex S2Plugin::ItemModelGatherVirtualData::index(int row, int column, const QModelIndex& parent) const -{ - return createIndex(row, column); -} - -QModelIndex S2Plugin::ItemModelGatherVirtualData::parent(const QModelIndex& index) const -{ - return QModelIndex(); -} - QVariant S2Plugin::ItemModelGatherVirtualData::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Orientation::Horizontal && role == Qt::DisplayRole) @@ -545,11 +516,6 @@ std::string S2Plugin::ItemModelGatherVirtualData::dumpCppEnum() const return ss.str(); } -bool S2Plugin::ItemModelGatherVirtualData::isEntryCompleted(size_t index) const -{ - return mEntries.at(index).virtualTableOffset != 0; -} - void S2Plugin::ItemModelGatherVirtualData::gatherAvailableVirtuals() { auto& vtl = Spelunky2::get()->get_VirtualTableLookup(); @@ -611,21 +577,3 @@ void S2Plugin::ItemModelGatherVirtualData::gatherAvailableVirtuals() } endResetModel(); } - -S2Plugin::SortFilterProxyModelGatherVirtualData::SortFilterProxyModelGatherVirtualData(QObject* parent) : QSortFilterProxyModel(parent) {} - -bool S2Plugin::SortFilterProxyModelGatherVirtualData::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const -{ - if (mHideCompleted && dynamic_cast(sourceModel())->isEntryCompleted(sourceRow)) - { - return false; - } - return true; -} - -void S2Plugin::SortFilterProxyModelGatherVirtualData::setHideCompleted(bool b) -{ - beginResetModel(); - mHideCompleted = b; - endResetModel(); -} diff --git a/src/QtHelpers/ItemModelLoggerFields.cpp b/src/QtHelpers/ItemModelLoggerFields.cpp index ab7d02b4..46270910 100644 --- a/src/QtHelpers/ItemModelLoggerFields.cpp +++ b/src/QtHelpers/ItemModelLoggerFields.cpp @@ -3,13 +3,6 @@ #include "Data/Logger.h" #include "QtHelpers/TableViewLogger.h" -S2Plugin::ItemModelLoggerFields::ItemModelLoggerFields(Logger* logger, QObject* parent) : QAbstractItemModel(parent), mLogger(logger) {} - -Qt::ItemFlags S2Plugin::ItemModelLoggerFields::flags(const QModelIndex& index) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; -} - QVariant S2Plugin::ItemModelLoggerFields::data(const QModelIndex& index, int role) const { auto& field = mLogger->fieldAt(index.row()); @@ -32,31 +25,16 @@ QVariant S2Plugin::ItemModelLoggerFields::data(const QModelIndex& index, int rol case gsLogFieldColFieldType: { auto str = Configuration::getTypeDisplayName(field.type); - return QString::fromUtf8(str.data(), str.size()); + return QString::fromUtf8(str.data(), static_cast(str.size())); } } } return QVariant(); } -int S2Plugin::ItemModelLoggerFields::rowCount(const QModelIndex& parent) const -{ - return mLogger->fieldCount(); -} - -int S2Plugin::ItemModelLoggerFields::columnCount(const QModelIndex& parent) const -{ - return 4; -} - -QModelIndex S2Plugin::ItemModelLoggerFields::index(int row, int column, const QModelIndex& parent) const -{ - return createIndex(row, column); -} - -QModelIndex S2Plugin::ItemModelLoggerFields::parent(const QModelIndex& index) const +int S2Plugin::ItemModelLoggerFields::rowCount(const QModelIndex&) const { - return QModelIndex(); + return static_cast(mLogger->fieldCount()); } QVariant S2Plugin::ItemModelLoggerFields::headerData(int section, Qt::Orientation orientation, int role) const @@ -77,23 +55,3 @@ QVariant S2Plugin::ItemModelLoggerFields::headerData(int section, Qt::Orientatio } return QVariant(); } - -void S2Plugin::ItemModelLoggerFields::removeRow(size_t index) -{ - beginRemoveRows(QModelIndex(), index, index); -} - -void S2Plugin::ItemModelLoggerFields::removeRowEnd() -{ - endRemoveRows(); -} - -void S2Plugin::ItemModelLoggerFields::appendRow() -{ - beginInsertRows(QModelIndex(), rowCount(), rowCount()); -} - -void S2Plugin::ItemModelLoggerFields::appendRowEnd() -{ - endInsertRows(); -} diff --git a/src/QtHelpers/ItemModelLoggerSamples.cpp b/src/QtHelpers/ItemModelLoggerSamples.cpp index 42afe320..06ff97bf 100644 --- a/src/QtHelpers/ItemModelLoggerSamples.cpp +++ b/src/QtHelpers/ItemModelLoggerSamples.cpp @@ -3,13 +3,6 @@ #include "Data/Logger.h" #include "QtHelpers/TableViewLogger.h" -S2Plugin::ItemModelLoggerSamples::ItemModelLoggerSamples(Logger* logger, QObject* parent) : QAbstractItemModel(parent), mLogger(logger) {} - -Qt::ItemFlags S2Plugin::ItemModelLoggerSamples::flags(const QModelIndex& index) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; -} - QVariant S2Plugin::ItemModelLoggerSamples::data(const QModelIndex& index, int role) const { if (role == Qt::DisplayRole) @@ -83,24 +76,14 @@ QVariant S2Plugin::ItemModelLoggerSamples::data(const QModelIndex& index, int ro return QVariant(); } -int S2Plugin::ItemModelLoggerSamples::rowCount(const QModelIndex& parent) const -{ - return mLogger->sampleCount(); -} - -int S2Plugin::ItemModelLoggerSamples::columnCount(const QModelIndex& parent) const +int S2Plugin::ItemModelLoggerSamples::rowCount(const QModelIndex&) const { - return mLogger->fieldCount() + 1; + return static_cast(mLogger->sampleCount()); } -QModelIndex S2Plugin::ItemModelLoggerSamples::index(int row, int column, const QModelIndex& parent) const +int S2Plugin::ItemModelLoggerSamples::columnCount(const QModelIndex&) const { - return createIndex(row, column); -} - -QModelIndex S2Plugin::ItemModelLoggerSamples::parent(const QModelIndex& index) const -{ - return QModelIndex(); + return static_cast(mLogger->fieldCount() + 1); } QVariant S2Plugin::ItemModelLoggerSamples::headerData(int section, Qt::Orientation orientation, int role) const @@ -117,9 +100,3 @@ QVariant S2Plugin::ItemModelLoggerSamples::headerData(int section, Qt::Orientati } return QVariant(); } - -void S2Plugin::ItemModelLoggerSamples::reset() -{ - beginResetModel(); - endResetModel(); -} diff --git a/src/QtHelpers/ItemModelStates.cpp b/src/QtHelpers/ItemModelStates.cpp deleted file mode 100644 index b58ee47a..00000000 --- a/src/QtHelpers/ItemModelStates.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "QtHelpers/ItemModelStates.h" - -S2Plugin::ItemModelStates::ItemModelStates(const std::vector>& states, QObject* parent) : QAbstractItemModel(parent) -{ - mStates = states; -} - -Qt::ItemFlags S2Plugin::ItemModelStates::flags(const QModelIndex& index) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; -} - -QVariant S2Plugin::ItemModelStates::data(const QModelIndex& index, int role) const -{ - auto& [stateID, state] = mStates.at(index.row()); - if (role == Qt::DisplayRole) - { - return QString("%1: %2").arg(stateID).arg(QString::fromStdString(state)); - } - else if (role == Qt::UserRole) - { - return stateID; - } - return QVariant(); -} - -int S2Plugin::ItemModelStates::rowCount(const QModelIndex& parent) const -{ - return mStates.size(); -} - -int S2Plugin::ItemModelStates::columnCount(const QModelIndex& parent) const -{ - return 1; -} - -QModelIndex S2Plugin::ItemModelStates::index(int row, int column, const QModelIndex& parent) const -{ - return createIndex(row, column); -} - -QModelIndex S2Plugin::ItemModelStates::parent(const QModelIndex& index) const -{ - return QModelIndex(); -} - -S2Plugin::SortFilterProxyModelStates::SortFilterProxyModelStates(const std::vector>& states, QObject* parent) : QSortFilterProxyModel(parent) -{ - mStates = states; - setSortRole(Qt::UserRole); -} - -bool S2Plugin::SortFilterProxyModelStates::lessThan(const QModelIndex& left, const QModelIndex& right) const -{ - auto leftValue = sourceModel()->data(left, Qt::UserRole).toLongLong(); - auto rightValue = sourceModel()->data(right, Qt::UserRole).toLongLong(); - return leftValue < rightValue; -} diff --git a/src/QtHelpers/ItemModelVirtualFunctions.cpp b/src/QtHelpers/ItemModelVirtualFunctions.cpp index 04ce63f2..2d2c5b08 100644 --- a/src/QtHelpers/ItemModelVirtualFunctions.cpp +++ b/src/QtHelpers/ItemModelVirtualFunctions.cpp @@ -1,19 +1,7 @@ #include "QtHelpers/ItemModelVirtualFunctions.h" #include "Configuration.h" -#include "Spelunky2.h" -#include "Views/ViewToolbar.h" #include "pluginmain.h" -S2Plugin::ItemModelVirtualFunctions::ItemModelVirtualFunctions(const std::string& typeName, uintptr_t memoryAddress, ViewToolbar* toolbar, QObject* parent) - : QAbstractItemModel(parent), mTypeName(typeName), mMemoryAddress(memoryAddress), mToolbar(toolbar) -{ -} - -Qt::ItemFlags S2Plugin::ItemModelVirtualFunctions::flags(const QModelIndex& index) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; -} - QVariant S2Plugin::ItemModelVirtualFunctions::data(const QModelIndex& index, int role) const { const VirtualFunction entry = Configuration::get()->virtualFunctionsOfType(mTypeName).at(index.row()); @@ -54,24 +42,9 @@ QVariant S2Plugin::ItemModelVirtualFunctions::data(const QModelIndex& index, int return QVariant(); } -int S2Plugin::ItemModelVirtualFunctions::rowCount(const QModelIndex& parent) const +int S2Plugin::ItemModelVirtualFunctions::rowCount(const QModelIndex&) const { - return Configuration::get()->virtualFunctionsOfType(mTypeName).size(); -} - -int S2Plugin::ItemModelVirtualFunctions::columnCount(const QModelIndex& parent) const -{ - return 4; -} - -QModelIndex S2Plugin::ItemModelVirtualFunctions::index(int row, int column, const QModelIndex& parent) const -{ - return createIndex(row, column); -} - -QModelIndex S2Plugin::ItemModelVirtualFunctions::parent(const QModelIndex& index) const -{ - return QModelIndex(); + return static_cast(Configuration::get()->virtualFunctionsOfType(mTypeName).size()); } QVariant S2Plugin::ItemModelVirtualFunctions::headerData(int section, Qt::Orientation orientation, int role) const @@ -92,16 +65,3 @@ QVariant S2Plugin::ItemModelVirtualFunctions::headerData(int section, Qt::Orient } return QVariant(); } - -S2Plugin::SortFilterProxyModelVirtualFunctions::SortFilterProxyModelVirtualFunctions(const std::string& typeName, ViewToolbar* toolbar, QObject* parent) - : QSortFilterProxyModel(parent), mTypeName(typeName), mToolbar(toolbar) -{ - setSortRole(gsRoleFunctionIndex); -} - -bool S2Plugin::SortFilterProxyModelVirtualFunctions::lessThan(const QModelIndex& left, const QModelIndex& right) const -{ - auto leftValue = sourceModel()->data(left, gsRoleFunctionIndex).toLongLong(); - auto rightValue = sourceModel()->data(right, gsRoleFunctionIndex).toLongLong(); - return leftValue < rightValue; -} diff --git a/src/QtHelpers/ItemModelVirtualTable.cpp b/src/QtHelpers/ItemModelVirtualTable.cpp index ce660724..fb4b1345 100644 --- a/src/QtHelpers/ItemModelVirtualTable.cpp +++ b/src/QtHelpers/ItemModelVirtualTable.cpp @@ -4,7 +4,6 @@ #include "Data/State.h" #include "Data/VirtualTableLookup.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" #include "pluginmain.h" S2Plugin::ItemModelVirtualTable::ItemModelVirtualTable(QObject* parent) : QAbstractItemModel(parent) @@ -14,11 +13,6 @@ S2Plugin::ItemModelVirtualTable::ItemModelVirtualTable(QObject* parent) : QAbstr mLayer1Offset = config->offsetForField(config->typeFields(MemoryFieldType::State), "layer1", Spelunky2::get()->get_StatePtr()); } -Qt::ItemFlags S2Plugin::ItemModelVirtualTable::flags(const QModelIndex& index) const -{ - return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; -} - QVariant S2Plugin::ItemModelVirtualTable::data(const QModelIndex& index, int role) const { if (role == Qt::DisplayRole) @@ -53,24 +47,9 @@ QVariant S2Plugin::ItemModelVirtualTable::data(const QModelIndex& index, int rol return QVariant(); } -int S2Plugin::ItemModelVirtualTable::rowCount(const QModelIndex& parent) const -{ - return Spelunky2::get()->get_VirtualTableLookup().count(); -} - -int S2Plugin::ItemModelVirtualTable::columnCount(const QModelIndex& parent) const -{ - return 4; -} - -QModelIndex S2Plugin::ItemModelVirtualTable::index(int row, int column, const QModelIndex& parent) const +int S2Plugin::ItemModelVirtualTable::rowCount(const QModelIndex&) const { - return createIndex(row, column); -} - -QModelIndex S2Plugin::ItemModelVirtualTable::parent(const QModelIndex& index) const -{ - return QModelIndex(); + return static_cast(Spelunky2::get()->get_VirtualTableLookup().count()); } QVariant S2Plugin::ItemModelVirtualTable::headerData(int section, Qt::Orientation orientation, int role) const @@ -123,7 +102,7 @@ void S2Plugin::ItemModelVirtualTable::detectEntities() S2Plugin::SortFilterProxyModelVirtualTable::SortFilterProxyModelVirtualTable(QObject* parent) : QSortFilterProxyModel(parent) {} -bool S2Plugin::SortFilterProxyModelVirtualTable::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +bool S2Plugin::SortFilterProxyModelVirtualTable::filterAcceptsRow(int sourceRow, const QModelIndex&) const { const auto& entry = Spelunky2::get()->get_VirtualTableLookup().entryForOffset(sourceRow); @@ -146,48 +125,13 @@ bool S2Plugin::SortFilterProxyModelVirtualTable::filterAcceptsRow(int sourceRow, } if (!mShowImportedSymbols && entry.isAutoSymbol) - { return false; - } if (!mShowNonAddressEntries && !entry.isValidAddress) - { return false; - } if (!mShowSymbollessEntries && entry.symbols.size() == 0) - { return false; - } return true; } - -void S2Plugin::SortFilterProxyModelVirtualTable::setShowImportedSymbols(bool b) -{ - mShowImportedSymbols = b; - invalidateFilter(); -} - -void S2Plugin::SortFilterProxyModelVirtualTable::setShowNonAddressEntries(bool b) -{ - mShowNonAddressEntries = b; - invalidateFilter(); -} - -void S2Plugin::SortFilterProxyModelVirtualTable::setShowSymbollessEntries(bool b) -{ - mShowSymbollessEntries = b; - invalidateFilter(); -} - -void S2Plugin::SortFilterProxyModelVirtualTable::setFilterString(const QString& f) -{ - mFilterString = f; - invalidateFilter(); -} - -bool S2Plugin::SortFilterProxyModelVirtualTable::symbollessEntriesShown() const noexcept -{ - return mShowSymbollessEntries; -} diff --git a/src/QtHelpers/SortFilterProxyModelStringsTable.cpp b/src/QtHelpers/SortFilterProxyModelStringsTable.cpp index 670a6733..6badff84 100644 --- a/src/QtHelpers/SortFilterProxyModelStringsTable.cpp +++ b/src/QtHelpers/SortFilterProxyModelStringsTable.cpp @@ -2,9 +2,7 @@ #include "Views/ViewStringsTable.h" // just for gsColStringValue #include -S2Plugin::SortFilterProxyModelStringsTable::SortFilterProxyModelStringsTable(QObject* parent) : QSortFilterProxyModel(parent) {} - -bool S2Plugin::SortFilterProxyModelStringsTable::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +bool S2Plugin::SortFilterProxyModelStringsTable::filterAcceptsRow(int sourceRow, const QModelIndex&) const { if (mFilterString.isEmpty()) { @@ -29,9 +27,3 @@ bool S2Plugin::SortFilterProxyModelStringsTable::filterAcceptsRow(int sourceRow, return str.contains(mFilterString, Qt::CaseInsensitive); } } - -void S2Plugin::SortFilterProxyModelStringsTable::setFilterString(const QString& f) -{ - mFilterString = f; - invalidateFilter(); -} diff --git a/src/QtHelpers/StyledItemDelegateColorPicker.cpp b/src/QtHelpers/StyledItemDelegateColorPicker.cpp deleted file mode 100644 index eaa5db32..00000000 --- a/src/QtHelpers/StyledItemDelegateColorPicker.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "QtHelpers/StyledItemDelegateColorPicker.h" -#include -#include - -void S2Plugin::StyledItemDelegateColorPicker::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const -{ - QStyleOptionViewItemV4 options = option; - initStyleOption(&options, index); - - painter->save(); - - auto adjusted = options.rect.adjusted(5, 5, -5, -5); - auto color = index.data(Qt::DisplayRole).value(); - painter->fillRect(adjusted, QBrush(color)); - painter->setPen(Qt::darkGray); - painter->drawRect(adjusted); - - painter->restore(); -} diff --git a/src/QtHelpers/StyledItemDelegateHTML.cpp b/src/QtHelpers/StyledItemDelegateHTML.cpp index 0d1d964c..d29596c9 100644 --- a/src/QtHelpers/StyledItemDelegateHTML.cpp +++ b/src/QtHelpers/StyledItemDelegateHTML.cpp @@ -1,5 +1,7 @@ #include "QtHelpers/StyledItemDelegateHTML.h" +#include #include +#include #include void S2Plugin::StyledItemDelegateHTML::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const @@ -14,7 +16,7 @@ void S2Plugin::StyledItemDelegateHTML::paint(QPainter* painter, const QStyleOpti options.text = ""; options.widget->style()->drawControl(QStyle::CE_ItemViewItem, &options, painter); - + QSize iconSize = options.icon.actualSize(options.rect.size()); if (mCenterVertically) { doc.setTextWidth(options.rect.width()); @@ -23,10 +25,19 @@ void S2Plugin::StyledItemDelegateHTML::paint(QPainter* painter, const QStyleOpti } else { - painter->translate(options.rect.left(), options.rect.top() - 2); + painter->translate(options.rect.left() + iconSize.width(), options.rect.top() - 2); } - QRect clip(0, 0, options.rect.width(), options.rect.height()); - doc.drawContents(painter, clip); + QRect clip(0, 0, options.rect.width() + iconSize.width(), options.rect.height()); + // doc.drawContents(painter, clip); + + painter->setClipRect(clip); + QAbstractTextDocumentLayout::PaintContext ctx; + auto newColor = index.data(Qt::TextColorRole); + if (!newColor.isNull()) + ctx.palette.setColor(QPalette::Text, newColor.value()); + + ctx.clip = clip; + doc.documentLayout()->draw(painter, ctx); painter->restore(); } @@ -42,8 +53,3 @@ QSize S2Plugin::StyledItemDelegateHTML::sizeHint(const QStyleOptionViewItem& opt doc.setDocumentMargin(2); return QSize(doc.idealWidth(), doc.size().height()); } - -void S2Plugin::StyledItemDelegateHTML::setCenterVertically(bool b) -{ - mCenterVertically = b; -} diff --git a/src/QtHelpers/TableViewLogger.cpp b/src/QtHelpers/TableViewLogger.cpp index f816584f..b531499d 100644 --- a/src/QtHelpers/TableViewLogger.cpp +++ b/src/QtHelpers/TableViewLogger.cpp @@ -3,7 +3,8 @@ #include "Data/Logger.h" #include "QtHelpers/ItemModelLoggerFields.h" #include "QtHelpers/StyledItemDelegateColorPicker.h" -#include "pluginmain.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "QtPlugin.h" #include #include #include @@ -13,7 +14,6 @@ #include #include #include -#include S2Plugin::TableViewLogger::TableViewLogger(Logger* logger, QWidget* parent) : QTableView(parent), mLogger(logger) { @@ -23,26 +23,21 @@ S2Plugin::TableViewLogger::TableViewLogger(Logger* logger, QWidget* parent) : QT setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionMode(QAbstractItemView::SingleSelection); - mColorPickerDelegate = std::make_unique(); - setItemDelegateForColumn(gsLogFieldColColor, mColorPickerDelegate.get()); + setItemDelegateForColumn(gsLogFieldColColor, new StyledItemDelegateColorPicker(this)); QObject::connect(this, static_cast(&QTableView::clicked), this, &TableViewLogger::cellClicked); } void S2Plugin::TableViewLogger::dragEnterEvent(QDragEnterEvent* event) { - if (event->mimeData()->hasFormat("spelunky/memoryfield")) - { + if (event->mimeData()->property(gsDragDropMemoryField_UID).isValid()) event->acceptProposedAction(); - } } void S2Plugin::TableViewLogger::dragMoveEvent(QDragMoveEvent* event) { - if (event->mimeData()->hasFormat("spelunky/memoryfield")) - { + if (event->mimeData()->property(gsDragDropMemoryField_UID).isValid()) event->accept(); - } } void S2Plugin::TableViewLogger::dropEvent(QDropEvent* event) @@ -51,14 +46,8 @@ void S2Plugin::TableViewLogger::dropEvent(QDropEvent* event) auto fieldsModel = qobject_cast(model()); - auto dropData = event->mimeData()->data("spelunky/memoryfield"); - auto codec = QTextCodec::codecForName("UTF-8"); - auto str = codec->toUnicode(dropData); - - auto j = nlohmann::json::parse(str.toStdString()); - LoggerField field; - field.type = static_cast(j[gsJSONDragDropMemoryField_Type].get()); + field.type = event->mimeData()->property(gsDragDropMemoryField_Type).value(); switch (field.type) { // List allowed types @@ -90,7 +79,7 @@ void S2Plugin::TableViewLogger::dropEvent(QDropEvent* event) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Critical); - msgBox.setWindowIcon(QIcon(":/icons/caveman.png")); + msgBox.setWindowIcon(getCavemanIcon()); msgBox.setText("This field type is not supported for logging"); msgBox.setWindowTitle("Spelunky2"); msgBox.exec(); @@ -98,8 +87,8 @@ void S2Plugin::TableViewLogger::dropEvent(QDropEvent* event) } } field.uuid = QUuid::createUuid().toString().toStdString(); - field.memoryAddr = j[gsJSONDragDropMemoryField_Address].get(); - field.name = j[gsJSONDragDropMemoryField_UID].get(); + field.memoryAddr = event->mimeData()->property(gsDragDropMemoryField_Address).toULongLong(); + field.name = event->mimeData()->property(gsDragDropMemoryField_UID).toString().toStdString(); field.color = defaultColors[fieldsModel->rowCount() % defaultColors.size()]; mLogger->addField(field); diff --git a/src/QtHelpers/TableWidgetItemNumeric.cpp b/src/QtHelpers/TableWidgetItemNumeric.cpp deleted file mode 100644 index f916a8c4..00000000 --- a/src/QtHelpers/TableWidgetItemNumeric.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "QtHelpers/TableWidgetItemNumeric.h" -#include "pluginmain.h" - -S2Plugin::TableWidgetItemNumeric::TableWidgetItemNumeric(const QString& s) : QTableWidgetItem(s, 0) {} - -bool S2Plugin::TableWidgetItemNumeric::operator<(const QTableWidgetItem& other) const -{ - return data(Qt::UserRole) < other.data(Qt::UserRole); -} diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 71e6fc3f..355cdb0f 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -11,6 +11,9 @@ #include "Data/TextureDB.h" #include "QtHelpers/DialogEditSimpleValue.h" #include "QtHelpers/DialogEditState.h" +#include "QtHelpers/DialogEditString.h" +#include "QtHelpers/StyledItemDelegateHTML.h" +#include "QtPlugin.h" #include "Spelunky2.h" #include "Views/ViewCharacterDB.h" #include "Views/ViewEntity.h" @@ -30,32 +33,52 @@ #include #include #include -#include #include #include -S2Plugin::TreeViewMemoryFields::TreeViewMemoryFields(ViewToolbar* toolbar, QWidget* parent) : QTreeView(parent), mToolbar(toolbar) +S2Plugin::TreeViewMemoryFields::TreeViewMemoryFields(QWidget* parent) : QTreeView(parent) { - setItemDelegate(&mHTMLDelegate); + setItemDelegate(new StyledItemDelegateHTML(this)); setAlternatingRowColors(true); mModel = new QStandardItemModel(this); setModel(mModel); - setDragDropMode(QAbstractItemView::DragDropMode::DragOnly); + setDragDropMode(QAbstractItemView::DragDropMode::DragDrop); setDragEnabled(true); - setAcceptDrops(false); + setAcceptDrops(true); + + setStyleSheet("QTreeView::branch:has-siblings:!adjoins-item {\ + border-image: url(:/images/vline.png) 0;\ +}\ +QTreeView::branch:has-siblings:adjoins-item {\ + border-image: url(:/images/branch-more.png) 0;\ +}\ +QTreeView::branch:!has-children:!has-siblings:adjoins-item {\ + border-image: url(:/images/branch-end.png) 0;\ +}\ +QTreeView::branch:has-children:!has-siblings:closed,\ +QTreeView::branch:closed:has-children:has-siblings {\ + border-image: none;\ + image: url(:/images/branch-closed.png);\ +}\ +QTreeView::branch:open:has-children:!has-siblings,\ +QTreeView::branch:open:has-children:has-siblings {\ + border-image: none;\ + image: url(:/images/branch-open.png);\ +}"); QObject::connect(this, &QTreeView::clicked, this, &TreeViewMemoryFields::cellClicked); } -void S2Plugin::TreeViewMemoryFields::addMemoryFields(const std::vector& fields, const std::string& mainName, uintptr_t structAddr, size_t initialDelta, QStandardItem* parent) +void S2Plugin::TreeViewMemoryFields::addMemoryFields(const std::vector& fields, const std::string& mainName, uintptr_t structAddr, size_t initialDelta, uint8_t deltaPrefixCount, + QStandardItem* parent) { size_t currentOffset = structAddr; size_t currentDelta = initialDelta; for (auto& field : fields) { - addMemoryField(field, mainName + "." + field.name, currentOffset, currentDelta, parent); + addMemoryField(field, mainName + "." + field.name, currentOffset, currentDelta, deltaPrefixCount, parent); auto size = field.get_size(); currentDelta += size; if (structAddr != 0) @@ -63,9 +86,11 @@ void S2Plugin::TreeViewMemoryFields::addMemoryFields(const std::vector QStandardItem* + auto createAndInsertItem = [&delta, &deltaPrefixCount](const MemoryField& field, const std::string& fieldNameUID, QStandardItem* itemParent, uintptr_t memAddr, + bool showDelta = true) -> QStandardItem* { auto itemFieldName = new QStandardItem(); itemFieldName->setEditable(false); @@ -79,42 +104,40 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& itemFieldValue->setEditable(false); if (field.isPointer == false) // if it's pointer, we set it on first update itemFieldValue->setData(memAddr, gsRoleMemoryAddress); - itemFieldValue->setData("", Qt::DisplayRole); auto itemFieldValueHex = new QStandardItem(); itemFieldValueHex->setEditable(false); - itemFieldValueHex->setData("", Qt::DisplayRole); auto itemFieldComparisonValue = new QStandardItem(); itemFieldComparisonValue->setEditable(false); - itemFieldComparisonValue->setData("", Qt::DisplayRole); auto itemFieldComparisonValueHex = new QStandardItem(); itemFieldComparisonValueHex->setEditable(false); - itemFieldComparisonValueHex->setData("", Qt::DisplayRole); auto itemFieldMemoryOffset = new QStandardItem(); itemFieldMemoryOffset->setEditable(false); - if (memAddr == 0) - itemFieldMemoryOffset->setData("", Qt::DisplayRole); - else + if (memAddr != 0) { itemFieldMemoryOffset->setData(QString::asprintf("0x%016llX", memAddr), Qt::DisplayRole); - // for click event. I could just use the itemFieldName(gsRoleMemoryAddress) for it, but doing it this way for potential itemComparisonFieldMemoryOffset in future + // for click event. we could just use the itemFieldName(gsRoleMemoryAddress), but doing it this way for potential itemComparisonFieldMemoryOffset in the future itemFieldMemoryOffset->setData(memAddr, gsRoleRawValue); } auto itemFieldMemoryOffsetDelta = new QStandardItem(); itemFieldMemoryOffsetDelta->setEditable(false); - if (showDelta) + if (showDelta) // this should only ever be false for flag field { - itemFieldMemoryOffsetDelta->setData(QString::asprintf("+0x%llX", delta), Qt::DisplayRole); + QString text; + if (deltaPrefixCount > 0) + { + text = QString(deltaPrefixCount - 1, QChar(0x2502)); // '│' + text += QChar(0x2514); // '└' + text += QChar(0x2192); // '→' + } + text += QString::asprintf("+0x%llX", delta); + itemFieldMemoryOffsetDelta->setData(text, Qt::DisplayRole); itemFieldMemoryOffsetDelta->setData(delta, gsRoleRawValue); } - else - { - itemFieldMemoryOffsetDelta->setData("", Qt::DisplayRole); // this should only ever happen for flag field - } auto itemFieldComment = new QStandardItem(); itemFieldComment->setEditable(false); @@ -123,11 +146,11 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& auto itemFieldType = new QStandardItem(); itemFieldType->setEditable(false); - QString typeName = field.isPointer ? "P: " : ""; // TODO: add color? + QString typeName = field.isPointer ? "P: " : ""; // add color? if (field.type == MemoryFieldType::EntitySubclass || field.type == MemoryFieldType::DefaultStructType) typeName += QString::fromStdString(field.jsonName); else if (auto str = Configuration::getTypeDisplayName(field.type); !str.empty()) - typeName += QString::fromUtf8(str.data(), str.size()); + typeName += QString::fromUtf8(str.data(), static_cast(str.size())); else typeName += "Unknown field type"; @@ -140,10 +163,9 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& }; if (parent == nullptr) - { parent = mModel->invisibleRootItem(); - } + uint8_t flags = 0; QStandardItem* returnField = nullptr; switch (field.type) { @@ -182,7 +204,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& case MemoryFieldType::LevelGenRoomsPointer: case MemoryFieldType::LevelGenRoomsMetaPointer: case MemoryFieldType::JournalPagePointer: - case MemoryFieldType::ThemeInfoName: + case MemoryFieldType::ThemeInfoPointer: case MemoryFieldType::UTF16Char: case MemoryFieldType::IPv4Address: { @@ -211,10 +233,19 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& break; } case MemoryFieldType::Flags32: + flags = 32; + [[fallthrough]]; + case MemoryFieldType::Flags16: + if (flags == 0) + flags = 16; + [[fallthrough]]; + case MemoryFieldType::Flags8: { + if (flags == 0) + flags = 8; + auto flagsParent = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); - flagsParent->setData(QVariant::fromValue(field.firstParameterType), gsRoleRefName); - for (uint8_t x = 1; x <= 32; ++x) + for (uint8_t x = 1; x <= flags; ++x) { MemoryField flagField; flagField.name = "flag_" + std::to_string(x); @@ -227,39 +258,10 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& } auto flagFieldItem = createAndInsertItem(flagField, fieldNameOverride + "." + flagField.name, flagsParent, 0, showDelta); flagFieldItem->setData(x, gsRoleFlagIndex); - } - returnField = flagsParent; - break; - } - case MemoryFieldType::Flags16: - { - auto flagsParent = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress, delta); - flagsParent->setData(QVariant::fromValue(field.firstParameterType), gsRoleRefName); - for (uint8_t x = 1; x <= 16; ++x) - { - MemoryField flagField; - flagField.name = "flag_" + std::to_string(x); - flagField.type = MemoryFieldType::Flag; - bool showDelta = x == 1 || x == 9; - delta += x == 9 ? 1 : 0; - auto flagFieldItem = createAndInsertItem(flagField, fieldNameOverride + "." + flagField.name, flagsParent, 0, showDelta); - flagFieldItem->setData(x, gsRoleFlagIndex); - } - returnField = flagsParent; - break; - } - case MemoryFieldType::Flags8: - { - auto flagsParent = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress, delta); - flagsParent->setData(QVariant::fromValue(field.firstParameterType), gsRoleRefName); - for (uint8_t x = 1; x <= 8; ++x) - { - MemoryField flagField; - flagField.name = "flag_" + std::to_string(x); - flagField.type = MemoryFieldType::Flag; - bool showDelta = x == 1; // for 8 bit flag we only show the first - auto flagFieldItem = createAndInsertItem(flagField, fieldNameOverride + "." + flagField.name, flagsParent, 0, showDelta); - flagFieldItem->setData(x, gsRoleFlagIndex); + auto flagName = Configuration::get()->flagTitle(field.firstParameterType, x); + QString realFlagName = QString::fromStdString(flagName.empty() ? Configuration::get()->flagTitle("unknown", x) : flagName); // TODO: don't show unknown unless it was chosen in settings + + flagsParent->child(x - 1, gsColValue)->setData(realFlagName, Qt::DisplayRole); } returnField = flagsParent; break; @@ -267,7 +269,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& case MemoryFieldType::UndeterminedThemeInfoPointer: { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); - addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct("ThemeInfoPointer"), fieldNameOverride, 0, 0, returnField); + addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct("ThemeInfoPointer"), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); break; } case MemoryFieldType::StdVector: @@ -275,9 +277,9 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); returnField->setData(QVariant::fromValue(field.firstParameterType), gsRoleStdContainerFirstParameterType); if (field.isPointer) - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, returnField); + addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); else - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, returnField); + addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } @@ -287,25 +289,26 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& returnField->setData(QVariant::fromValue(field.firstParameterType), gsRoleStdContainerFirstParameterType); returnField->setData(QVariant::fromValue(field.secondParameterType), gsRoleStdContainerSecondParameterType); if (field.isPointer) - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, returnField); + addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); else - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, returnField); + addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } case MemoryFieldType::EntitySubclass: { returnField = createAndInsertItem(field, fieldNameOverride, parent, 0); - addMemoryFields(Configuration::get()->typeFieldsOfEntitySubclass(field.jsonName), fieldNameOverride, memoryAddress, delta, returnField); + returnField->setData(memoryAddress, gsRoleMemoryAddress); + addMemoryFields(Configuration::get()->typeFieldsOfEntitySubclass(field.jsonName), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } case MemoryFieldType::DefaultStructType: { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); if (field.isPointer) - addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct(field.jsonName), fieldNameOverride, 0, 0, returnField); + addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct(field.jsonName), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); else - addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct(field.jsonName), fieldNameOverride, memoryAddress, delta, returnField); + addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct(field.jsonName), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } @@ -313,9 +316,9 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); if (field.isPointer) - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, returnField); + addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); else - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, returnField); + addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } @@ -390,16 +393,16 @@ void S2Plugin::TreeViewMemoryFields::updateTree(uintptr_t newAddr, uintptr_t new // hope that the compiler can inline and optimise all of this 🙏 template inline std::optional updateField(QStandardItem* itemField, uintptr_t memoryAddress, QStandardItem* itemValue, const char* valueFormat, QStandardItem* itemValueHex, bool isPointer, - const char* hexFormat, bool updateBackground, bool resetBackgroundToTransparent, QColor& background) + const char* hexFormat, bool updateBackground, bool resetBackgroundToTransparent, const QColor& background) { std::optional value; if (memoryAddress == 0) { - itemValue->setData("", Qt::DisplayRole); + itemValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemValueHex->setData("", Qt::DisplayRole); + itemValueHex->setData({}, Qt::DisplayRole); - itemValue->setData(QVariant{}, S2Plugin::gsRoleRawValue); + itemValue->setData({}, S2Plugin::gsRoleRawValue); itemField->setBackground(Qt::transparent); } else @@ -408,7 +411,7 @@ inline std::optional updateField(QStandardItem* itemField, uintptr_t memoryAd value = valueTmp; auto data = itemValue->data(S2Plugin::gsRoleRawValue); T valueOld = data.value(); - if (data.isNull() || value.value() != valueOld) + if (!data.isValid() || value.value() != valueOld) { if (updateBackground) itemField->setBackground(background); @@ -469,7 +472,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional const auto comparisonDifferenceColor = QColor::fromRgb(255, 221, 184); QColor highlightColor = (mEnableChangeHighlighting && !disableChangeHighlighting) ? QColor::fromRgb(255, 184, 184) : Qt::transparent; // updating memory offset - if (newAddr.has_value()) // if (fieldType != MemoryFieldType::Flag && fieldType != MemoryFieldType::EntitySubclass) // there should never be a situation when they get the memoryoffset updated + if (newAddr.has_value() && fieldType != MemoryFieldType::Flag) // if (fieldType != MemoryFieldType::EntitySubclass) // there should never be a situation when they get the memoryoffset updated { QStandardItem* itemMemoryOffset = parent->child(row, gsColMemoryAddress); QStandardItem* itemMemoryOffsetDelta = parent->child(row, gsColMemoryAddressDelta); @@ -501,15 +504,15 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional bool pointerUpdate = false; bool comparisonPointerUpdate = false; - bool comparisonPointerDifference = false; bool comparisonActive = activeColumns.test(gsColComparisonValue) || activeColumns.test(gsColComparisonValueHex); - uintptr_t newPointer = 0; - uintptr_t newComparisonPointer = 0; uintptr_t valueMemoryOffset = memoryOffset; // 0, memory offset or pointer value (no bad values) uintptr_t valueComparisonMemoryOffset = comparisonMemoryOffset; // 0, memory offset or pointer value (no bad values) if (isPointer) { + uintptr_t newPointer = 0; + uintptr_t newComparisonPointer = 0; + bool comparisonPointerDifference = false; // dealing with itemValueHex and itemComparisonValueHex for all pointers and check if the pointer changed auto checkAndUpdatePointer = [fieldType](uintptr_t& pointerValue, QStandardItem* valueHexField) -> bool { @@ -527,13 +530,14 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional pointerValue = 0; } else - newHexValue = QString::asprintf("0x%016llX", pointerValue); - - // TODO: if we could set the color separately, we could avoid this check, (we would also then not need the newPointer and newComparisonPointer to have scope on the whole function?) - if (fieldType != MemoryFieldType::CodePointer) // for Code pointer we paint it green instead { - valueHexField->setData(newHexValue, Qt::DisplayRole); + if (fieldType == MemoryFieldType::CodePointer) + newHexValue = QString::asprintf("0x%016llX", pointerValue); + else + newHexValue = QString::asprintf("0x%016llX", pointerValue); } + + valueHexField->setData(newHexValue, Qt::DisplayRole); valueHexField->setData(pointertmp, gsRoleRawValue); return true; } @@ -602,40 +606,6 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional switch (fieldType) { case MemoryFieldType::CodePointer: - { - if (pointerUpdate) - { - QString newValue; - if (newPointer == 0) - newValue = "nullptr"; - else if (valueMemoryOffset == 0) // trick to not use Script::Memory::IsValidPtr(newPointer) again - newValue = "bad ptr"; - else - newValue = QString::asprintf("0x%016llX", newPointer); - - itemValue->setData(newValue, Qt::DisplayRole); - itemValueHex->setData(newValue, Qt::DisplayRole); - } - - if (comparisonActive) - { - if (comparisonPointerUpdate) - { - QString newComparisonValue; - if (newComparisonPointer == 0) - newComparisonValue = "nullptr"; - else if (valueComparisonMemoryOffset == 0) - newComparisonValue = "bad ptr"; - else - newComparisonValue = QString::asprintf("0x%016llX", newComparisonPointer); - - itemComparisonValue->setData(newComparisonValue, Qt::DisplayRole); - itemComparisonValueHex->setData(newComparisonValue, Qt::DisplayRole); - } - itemComparisonValue->setBackground(itemComparisonValueHex->background()); - } - break; - } case MemoryFieldType::DataPointer: { if (pointerUpdate) @@ -674,7 +644,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (comparisonActive) { std::optional comparisonValue; - comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "%d", itemComparisonValueHex, isPointer, "0x%02X", false, false, highlightColor); + comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "%u", itemComparisonValueHex, isPointer, "0x%02X", false, false, highlightColor); itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); if (isPointer == false) @@ -706,7 +676,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (comparisonActive) { std::optional comparisonValue; - comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "%d", itemComparisonValueHex, isPointer, "0x%04X", false, false, highlightColor); + comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "%u", itemComparisonValueHex, isPointer, "0x%04X", false, false, highlightColor); itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); if (isPointer == false) @@ -733,12 +703,12 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional case MemoryFieldType::UnsignedDword: { std::optional value; - value = updateField(itemField, valueMemoryOffset, itemValue, "%u", itemValueHex, isPointer, "0x%08X", true, !pointerUpdate, highlightColor); + value = updateField(itemField, valueMemoryOffset, itemValue, "%lu", itemValueHex, isPointer, "0x%08X", true, !pointerUpdate, highlightColor); if (comparisonActive) { std::optional comparisonValue; - comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "%u", itemComparisonValueHex, isPointer, "0x%08X", false, false, highlightColor); + comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "%lu", itemComparisonValueHex, isPointer, "0x%08X", false, false, highlightColor); itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); if (isPointer == false) @@ -782,13 +752,18 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } case MemoryFieldType::Float: { - std::optional value; - value = updateField(itemField, valueMemoryOffset, itemValue, "%f", itemValueHex, isPointer, "0x%08X", true, !pointerUpdate, highlightColor); + std::optional value; + value = updateField(itemField, valueMemoryOffset, itemValue, nullptr, itemValueHex, isPointer, "0x%08X", true, !pointerUpdate, highlightColor); + if (value.has_value()) + itemValue->setData(QString::asprintf("%f", reinterpret_cast(value.value())), Qt::DisplayRole); if (comparisonActive) { - std::optional comparisonValue; - comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "%f", itemComparisonValueHex, isPointer, "0x%08X", false, false, highlightColor); + std::optional comparisonValue; + comparisonValue = + updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, nullptr, itemComparisonValueHex, isPointer, "0x%08X", false, false, highlightColor); + if (comparisonValue.has_value()) + itemComparisonValue->setData(QString::asprintf("%f", reinterpret_cast(comparisonValue.value())), Qt::DisplayRole); itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); if (isPointer == false) @@ -798,13 +773,18 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } case MemoryFieldType::Double: { - std::optional value; - value = updateField(itemField, valueMemoryOffset, itemValue, "%lf", itemValueHex, isPointer, "0x%016llX", true, !pointerUpdate, highlightColor); + std::optional value; + value = updateField(itemField, valueMemoryOffset, itemValue, nullptr, itemValueHex, isPointer, "0x%016llX", true, !pointerUpdate, highlightColor); + if (value.has_value()) + itemValue->setData(QString::asprintf("%lf", reinterpret_cast(value.value())), Qt::DisplayRole); if (comparisonActive) { - std::optional comparisonValue; - comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "%lf", itemComparisonValueHex, isPointer, "0x%016llX", false, false, highlightColor); + std::optional comparisonValue; + comparisonValue = + updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, nullptr, itemComparisonValueHex, isPointer, "0x%016llX", false, false, highlightColor); + if (comparisonValue.has_value()) + itemComparisonValue->setData(QString::asprintf("%lf", reinterpret_cast(comparisonValue.value())), Qt::DisplayRole); itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); if (isPointer == false) @@ -817,14 +797,14 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional std::optional value; value = updateField(itemField, valueMemoryOffset, itemValue, nullptr, itemValueHex, isPointer, "0x%02X", true, !pointerUpdate, highlightColor); if (value.has_value()) - itemValue->setData(value.value() ? "True" : "False", Qt::DisplayRole); // maybe color them green/red as well? + itemValue->setData(value.value() ? "True" : "False", Qt::DisplayRole); if (comparisonActive) { std::optional comparisonValue; comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, nullptr, itemComparisonValueHex, isPointer, "0x%02X", false, false, highlightColor); if (comparisonValue.has_value()) - itemComparisonValue->setData(comparisonValue.value() ? "True" : "False", Qt::DisplayRole); + itemComparisonValue->setData(comparisonValue.value() ? "True" : "False", Qt::DisplayRole); itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); if (isPointer == false) @@ -917,20 +897,14 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } case MemoryFieldType::Flag: // can't be pointer, always have parent { - constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role) - { - auto mod = idx.model(); - auto parentIndex = idx.parent(); - return mod->data(mod->index(idx.row(), col, parentIndex), role); - }; + constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role) { return idx.sibling(idx.row(), col).data(role); }; - auto flagIndex = itemField->data(gsRoleFlagIndex).toUInt(); - uint mask = (1 << (flagIndex - 1)); - auto flagRef = qvariant_cast(itemField->parent()->data(gsRoleRefName)); - auto flagName = Configuration::get()->flagTitle(flagRef, flagIndex); + // [Known Issue]: null memory address is not handled + const auto flagIndex = itemField->data(gsRoleFlagIndex).toUInt(); + const uint mask = (1 << (flagIndex - 1)); auto value = getDataFrom(itemField->parent()->index(), gsColValue, gsRoleRawValue).toUInt(); - auto flagSet = ((value & mask) == mask); + bool flagSet = (value & mask) == mask; if (itemValue->data(gsRoleRawValue).toBool() != flagSet) { itemValue->setData(flagSet, gsRoleRawValue); @@ -939,19 +913,17 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional else itemField->setBackground(Qt::transparent); - auto flagTitle = QString::fromStdString(flagName.empty() ? Configuration::get()->flagTitle("unknown", flagIndex) : flagName); // TODO: don't show empty unless it was chosen in settings - // TODO: would love to instead get the names and save them in addMemoryField and then just use itemValue->setForeground or itemValue->setData(Qt::TextColorRole) for the color - // but it doesn't work with HTML delagate, and i don't know how to edit it to make it work - auto caption = QString("%2").arg(flagSet ? "green" : "red", flagTitle); - itemValue->setData(caption, Qt::DisplayRole); + itemValue->setData(flagSet ? QColor("green") : QColor("red"), Qt::TextColorRole); - auto comparisonValue = getDataFrom(itemField->parent()->index(), gsColComparisonValue, gsRoleRawValue).toUInt(); - auto comparisonFlagSet = ((comparisonValue & mask) == mask); - auto comparisonTitle = QString("%2").arg(comparisonFlagSet ? "green" : "red", flagTitle); - itemComparisonValue->setData(comparisonTitle, Qt::DisplayRole); + if (comparisonActive) + { + auto comparisonValue = getDataFrom(itemField->parent()->index(), gsColComparisonValue, gsRoleRawValue).toUInt(); + bool comparisonFlagSet = (comparisonValue & mask) == mask; + itemComparisonValue->setData(comparisonFlagSet ? QColor("green") : QColor("red"), Qt::TextColorRole); - itemComparisonValue->setBackground(flagSet != comparisonFlagSet ? comparisonDifferenceColor : Qt::transparent); - itemComparisonValueHex->setBackground(flagSet != comparisonFlagSet ? comparisonDifferenceColor : Qt::transparent); + itemComparisonValue->setBackground(flagSet != comparisonFlagSet ? comparisonDifferenceColor : Qt::transparent); + itemComparisonValueHex->setBackground(flagSet != comparisonFlagSet ? comparisonDifferenceColor : Qt::transparent); + } break; } case MemoryFieldType::State8: @@ -974,7 +946,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (comparisonValue.has_value()) { auto stateTitle = QString::fromStdString(std::to_string(comparisonValue.value()) + ": " + config->stateTitle(stateRef, comparisonValue.value())); - itemValue->setData(stateTitle, Qt::DisplayRole); + itemComparisonValue->setData(stateTitle, Qt::DisplayRole); } itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1003,7 +975,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (comparisonValue.has_value()) { auto stateTitle = QString::fromStdString(std::to_string(comparisonValue.value()) + ": " + config->stateTitle(stateRef, comparisonValue.value())); - itemValue->setData(stateTitle, Qt::DisplayRole); + itemComparisonValue->setData(stateTitle, Qt::DisplayRole); } itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1032,7 +1004,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (comparisonValue.has_value()) { auto stateTitle = QString::fromStdString(std::to_string(comparisonValue.value()) + ": " + config->stateTitle(stateRef, comparisonValue.value())); - itemValue->setData(stateTitle, Qt::DisplayRole); + itemComparisonValue->setData(stateTitle, Qt::DisplayRole); } itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1046,7 +1018,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional std::optional value; value = updateField(itemField, valueMemoryOffset, itemValue, nullptr, itemValueHex, isPointer, "0x%04X", true, !pointerUpdate, highlightColor); if (value.has_value()) - itemValue->setData(QString("'%1' (%2)").arg(QChar(value.value())).arg(value.value()), Qt::DisplayRole); + itemValue->setData(QString("'%1' (%2)").arg(QString(QChar(value.value())).toHtmlEscaped()).arg(value.value()), Qt::DisplayRole); if (comparisonActive) { @@ -1054,7 +1026,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, nullptr, itemComparisonValueHex, isPointer, "0x%04X", false, false, highlightColor); if (comparisonValue.has_value()) - itemComparisonValue->setData(QString("'%1' (%2)").arg(QChar(comparisonValue.value())).arg(comparisonValue.value()), Qt::DisplayRole); + itemComparisonValue->setData(QString("'%1' (%2)").arg(QString(QChar(comparisonValue.value())).toHtmlEscaped()).arg(comparisonValue.value()), Qt::DisplayRole); itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); if (isPointer == false) @@ -1066,22 +1038,25 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { // size in bytes auto size = itemField->data(gsRoleSize).toULongLong(); - char buffer[1024] = {0}; + auto lenght = size / 2; + std::optional value; if (valueMemoryOffset == 0) { itemField->setBackground(Qt::transparent); - itemValue->setData("", Qt::DisplayRole); + itemValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemValueHex->setData("", Qt::DisplayRole); + itemValueHex->setData({}, Qt::DisplayRole); } else { - Script::Memory::Read(valueMemoryOffset, buffer, size, nullptr); - auto buffer_w = reinterpret_cast(buffer); - auto valueString = QString::fromUtf16(buffer_w); + value = std::wstring(); + value->resize(lenght); + Script::Memory::Read(valueMemoryOffset, value->data(), size, nullptr); + auto buffer_w = reinterpret_cast(value->c_str()); + auto valueString = ('\"' + QString::fromUtf16(buffer_w) + '\"').toHtmlEscaped(); - QString valueOld = itemValue->data(Qt::DisplayRole).toString(); // no need for gsRoleRawValue - if (valueString != valueOld) + auto valueOld = itemValue->data(Qt::DisplayRole); // no need for gsRoleRawValue + if (valueOld.isNull() || valueString != valueOld.toString()) { itemField->setBackground(highlightColor); itemValue->setData(valueString, Qt::DisplayRole); @@ -1089,8 +1064,8 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { std::stringstream ss; ss << "0x" << std::hex << std::setfill('0'); - for (int i = 0; i < std::min(size / 2, 10ull) && buffer_w[i] != 0; ++i) - ss << std::setw(4) << static_cast(buffer_w[i]); + for (int i = 0; i < std::min(lenght, 10ull); ++i) + ss << std::setw(4) << reinterpret_cast(buffer_w[i]); itemValueHex->setData(QString::fromStdString(ss.str()), Qt::DisplayRole); } @@ -1100,57 +1075,62 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } if (comparisonActive) { + std::optional comparisonValue; if (valueComparisonMemoryOffset == 0) { - itemComparisonValue->setData("", Qt::DisplayRole); + itemComparisonValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemComparisonValueHex->setData("", Qt::DisplayRole); + itemComparisonValueHex->setData({}, Qt::DisplayRole); } else { - Script::Memory::Read(valueComparisonMemoryOffset, buffer, size, nullptr); - auto buffer_w = reinterpret_cast(buffer); - auto valueString = QString::fromUtf16(buffer_w); + comparisonValue = std::wstring(); + comparisonValue->resize(lenght); + Script::Memory::Read(valueComparisonMemoryOffset, comparisonValue->data(), size, nullptr); + auto buffer_w = reinterpret_cast(comparisonValue->c_str()); + auto valueString = ('\"' + QString::fromUtf16(buffer_w) + '\"').toHtmlEscaped(); - QString valueOld = itemComparisonValue->data(Qt::DisplayRole).toString(); // no need for gsRoleRawValue - if (valueString != valueOld) + auto valueOld = itemComparisonValue->data(Qt::DisplayRole); // no need for gsRoleRawValue + if (valueOld.isNull() || valueString != valueOld.toString()) { - itemField->setBackground(highlightColor); itemComparisonValue->setData(valueString, Qt::DisplayRole); if (!isPointer) { std::stringstream ss; ss << "0x" << std::hex << std::setfill('0'); - for (int i = 0; i < std::min(size / 2, 10ull) && buffer_w[i] != 0; ++i) - ss << std::setw(4) << static_cast(buffer_w[i]); + for (int i = 0; i < std::min(lenght, 10ull) && buffer_w[i] != 0; ++i) + ss << std::setw(4) << reinterpret_cast(buffer_w[i]); itemComparisonValueHex->setData(QString::fromStdString(ss.str()), Qt::DisplayRole); } } - else if (!pointerUpdate) - itemField->setBackground(Qt::transparent); } + itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + if (isPointer == false) + itemComparisonValueHex->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); } break; } case MemoryFieldType::UTF8StringFixedSize: { auto size = itemField->data(gsRoleSize).toULongLong(); - char buffer[1024] = {0}; + std::optional value; if (valueMemoryOffset == 0) { itemField->setBackground(Qt::transparent); - itemValue->setData("", Qt::DisplayRole); + itemValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemValueHex->setData("", Qt::DisplayRole); + itemValueHex->setData({}, Qt::DisplayRole); } else { - Script::Memory::Read(valueMemoryOffset, buffer, size, nullptr); - auto valueString = QString::fromUtf8(buffer); + value = std::string(); + value->resize(size); + Script::Memory::Read(valueMemoryOffset, value->data(), size, nullptr); + auto valueString = ('\"' + QString::fromUtf8(value->c_str()) + '\"').toHtmlEscaped(); // using c_str and not fromStdString to ignore characters after null terminator - QString valueOld = itemValue->data(Qt::DisplayRole).toString(); // no need for gsRoleRawValue - if (valueString != valueOld) + auto valueOld = itemValue->data(Qt::DisplayRole); // no need for gsRoleRawValue + if (valueOld.isNull() || valueString != valueOld.toString()) { itemField->setBackground(highlightColor); itemValue->setData(valueString, Qt::DisplayRole); @@ -1158,8 +1138,8 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { std::stringstream ss; ss << "0x" << std::hex << std::setfill('0'); - for (int i = 0; i < std::min(size, 10ull) && buffer[i] != 0; ++i) - ss << std::setw(2) << static_cast(buffer[i]); + for (int i = 0; i < std::min(size, 10ull); ++i) + ss << std::setw(2) << reinterpret_cast(value->data()[i]); itemValueHex->setData(QString::fromStdString(ss.str()), Qt::DisplayRole); } @@ -1169,35 +1149,38 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } if (comparisonActive) { + std::optional comparisonValue; if (valueComparisonMemoryOffset == 0) { - itemComparisonValue->setData("", Qt::DisplayRole); + itemComparisonValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemComparisonValueHex->setData("", Qt::DisplayRole); + itemComparisonValueHex->setData({}, Qt::DisplayRole); } else { - Script::Memory::Read(valueComparisonMemoryOffset, buffer, size, nullptr); - auto valueString = QString::fromUtf8(buffer); + comparisonValue = std::string(); + comparisonValue->resize(size); + Script::Memory::Read(valueComparisonMemoryOffset, comparisonValue->data(), size, nullptr); + auto valueString = ('\"' + QString::fromUtf8(comparisonValue->c_str()) + '\"').toHtmlEscaped(); - QString valueOld = itemComparisonValue->data(Qt::DisplayRole).toString(); // no need for gsRoleRawValue - if (valueString != valueOld) + auto valueOld = itemComparisonValue->data(Qt::DisplayRole); // no need for gsRoleRawValue + if (valueOld.isNull() || valueString != valueOld.toString()) { - itemField->setBackground(highlightColor); itemComparisonValue->setData(valueString, Qt::DisplayRole); if (!isPointer) { std::stringstream ss; ss << "0x" << std::hex << std::setfill('0'); - for (int i = 0; i < std::min(size, 10ull) && buffer[i] != 0; ++i) - ss << std::setw(2) << static_cast(buffer[i]); + for (int i = 0; i < std::min(size, 10ull); ++i) + ss << std::setw(2) << reinterpret_cast(comparisonValue->data()[i]); itemComparisonValueHex->setData(QString::fromStdString(ss.str()), Qt::DisplayRole); } } - else if (!pointerUpdate) - itemField->setBackground(Qt::transparent); } + itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + if (isPointer == false) + itemComparisonValueHex->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); } break; } @@ -1242,6 +1225,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { if (value.value() < 0) { + // TODO: write better explanation itemValue->setData(QString::asprintf("%d (dynamically applied in ThemeInfo->get_dynamic_floor_texture_id())", value.value()), Qt::DisplayRole); } else @@ -1280,7 +1264,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional std::optional value; value = updateField(itemField, valueMemoryOffset, itemValue, nullptr, itemValueHex, isPointer, "0x%08X", true, !pointerUpdate, highlightColor); if (value.has_value()) - itemValue->setData(QString("%1: %2").arg(value.value()).arg(Spelunky2::get()->get_StringsTable().stringForIndex(value.value())), Qt::DisplayRole); + itemValue->setData(QString("%1: %2").arg(value.value()).arg(Spelunky2::get()->get_StringsTable().stringForIndex(value.value())).toHtmlEscaped(), Qt::DisplayRole); if (comparisonActive) { @@ -1289,7 +1273,8 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, nullptr, itemComparisonValueHex, isPointer, "0x%08X", false, false, highlightColor); if (comparisonValue.has_value()) { - itemComparisonValue->setData(QString("%1: %2").arg(comparisonValue.value()).arg(Spelunky2::get()->get_StringsTable().stringForIndex(comparisonValue.value())), Qt::DisplayRole); + itemComparisonValue->setData(QString("%1: %2").arg(comparisonValue.value()).arg(Spelunky2::get()->get_StringsTable().stringForIndex(comparisonValue.value())).toHtmlEscaped(), + Qt::DisplayRole); } itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1418,7 +1403,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional itemValue->setData(itemValueHex->data(Qt::DisplayRole), Qt::DisplayRole); else { - auto id = Script::Memory::ReadDword(valueMemoryOffset + 20); // TODO hex offset + auto id = Script::Memory::ReadDword(valueMemoryOffset + 0x14); auto entityName = Configuration::get()->entityList().nameForID(id); itemValue->setData(QString::asprintf("EntityDB %d %s", id, entityName.c_str()), Qt::DisplayRole); } @@ -1557,6 +1542,9 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (valueMemoryOffset != 0) valueMemoryOffset = Script::Memory::ReadQword(valueMemoryOffset); + if (valueComparisonMemoryOffset != 0) + valueComparisonMemoryOffset = Script::Memory::ReadQword(valueComparisonMemoryOffset); + [[fallthrough]]; } case MemoryFieldType::ConstCharPointer: @@ -1567,8 +1555,8 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } else { - std::string str = ReadConstString(valueMemoryOffset); - itemValue->setData(QString::fromStdString(str), Qt::DisplayRole); + std::string str = '\"' + ReadConstString(valueMemoryOffset) + '\"'; + itemValue->setData(QString::fromStdString(str).toHtmlEscaped(), Qt::DisplayRole); } if (comparisonActive) @@ -1579,8 +1567,8 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } else { - std::string comparisonStr = ReadConstString(valueComparisonMemoryOffset); - itemComparisonValue->setData(QString::fromStdString(comparisonStr), Qt::DisplayRole); + std::string comparisonStr = '\"' + ReadConstString(valueComparisonMemoryOffset) + '\"'; + itemComparisonValue->setData(QString::fromStdString(comparisonStr).toHtmlEscaped(), Qt::DisplayRole); } // pointer compare itemComparisonValue->setBackground(itemComparisonValueHex->background()); @@ -1590,41 +1578,41 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional case MemoryFieldType::StdString: { std::optional stringValue; - std::optional comparisonStringValue; if (valueMemoryOffset == 0) { itemField->setBackground(Qt::transparent); - itemValue->setData("", Qt::DisplayRole); + itemValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemValueHex->setData("", Qt::DisplayRole); + itemValueHex->setData({}, Qt::DisplayRole); } else { StdString string{valueMemoryOffset}; - // i don't think we will have pointer to std::string, but note just in case: this would override the pointer value in hex + // [Known Issue]: i don't think we will have pointer to std::string, but note just in case: this would override the pointer value in hex auto ptr = string.string_ptr(); itemValueHex->setData(QString::asprintf("0x%016llX", ptr), Qt::DisplayRole); itemValueHex->setData(ptr, gsRoleRawValue); - stringValue = string.get_string(); - auto displayValue = QString::fromStdString(stringValue.value()); + stringValue = '\"' + string.get_string() + '\"'; + auto displayValue = QString::fromStdString(stringValue.value()).toHtmlEscaped(); itemField->setBackground(itemValue->data(Qt::DisplayRole).toString() == displayValue ? Qt::transparent : highlightColor); itemValue->setData(displayValue, Qt::DisplayRole); } if (comparisonActive) { + std::optional comparisonStringValue; if (valueComparisonMemoryOffset == 0) { - itemComparisonValue->setData("", Qt::DisplayRole); + itemComparisonValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemComparisonValueHex->setData("", Qt::DisplayRole); + itemComparisonValueHex->setData({}, Qt::DisplayRole); } else { StdString comparisonString{valueComparisonMemoryOffset}; - comparisonStringValue = comparisonString.get_string(); - itemComparisonValue->setData(QString::fromStdString(comparisonStringValue.value()), Qt::DisplayRole); + comparisonStringValue = '\"' + comparisonString.get_string() + '\"'; + itemComparisonValue->setData(QString::fromStdString(comparisonStringValue.value()).toHtmlEscaped(), Qt::DisplayRole); auto ptr = comparisonString.string_ptr(); itemComparisonValueHex->setData(QString::asprintf("0x%016llX", ptr), Qt::DisplayRole); @@ -1638,25 +1626,24 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } if (shouldUpdateChildren) { + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); for (uint8_t x = 0; x < itemField->rowCount(); ++x) - { - std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); - std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); updateRow(x, addr, comparisonAddr, itemField); - } } break; } case MemoryFieldType::StdWstring: { - std::optional stringValue; - std::optional comparisonStringValue; + constexpr ushort quotationMark = static_cast(u'\"'); + std::optional> stringValue; + std::optional> comparisonStringValue; if (valueMemoryOffset == 0) { itemField->setBackground(Qt::transparent); - itemValue->setData("", Qt::DisplayRole); + itemValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemValueHex->setData("", Qt::DisplayRole); + itemValueHex->setData({}, Qt::DisplayRole); } else { @@ -1666,25 +1653,26 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional itemValueHex->setData(QString::asprintf("0x%016llX", ptr), Qt::DisplayRole); itemValueHex->setData(ptr, gsRoleRawValue); - stringValue = string.get_string(); - // auto displayValue = QString::fromStdU16String(stringValue.value()); - // itemField->setBackground(itemValue->data(Qt::DisplayRole).toString() == displayValue ? Qt::transparent : highlightColor); - // itemValue->setData(displayValue, Qt::DisplayRole); + stringValue = quotationMark + string.get_string() + quotationMark; + auto displayValue = QString::fromUtf16(stringValue->c_str(), static_cast(stringValue->size())).toHtmlEscaped(); + itemField->setBackground(itemValue->data(Qt::DisplayRole).toString() == displayValue ? Qt::transparent : highlightColor); + itemValue->setData(displayValue, Qt::DisplayRole); } if (comparisonActive) { if (valueComparisonMemoryOffset == 0) { - itemComparisonValue->setData("", Qt::DisplayRole); + itemComparisonValue->setData({}, Qt::DisplayRole); if (!isPointer) - itemComparisonValueHex->setData("", Qt::DisplayRole); + itemComparisonValueHex->setData({}, Qt::DisplayRole); } else { StdWstring comparisonString{valueComparisonMemoryOffset}; - comparisonStringValue = comparisonString.get_string(); - // itemComparisonValue->setData(QString::fromStdU16String(comparisonStringValue.value()), Qt::DisplayRole); + comparisonStringValue = quotationMark + comparisonString.get_string() + quotationMark; + auto displayValue = QString::fromUtf16(comparisonStringValue->data(), static_cast(comparisonStringValue->size())).toHtmlEscaped(); + itemComparisonValue->setData(displayValue, Qt::DisplayRole); auto ptr = comparisonString.string_ptr(); itemComparisonValueHex->setData(QString::asprintf("0x%016llX", ptr), Qt::DisplayRole); @@ -1697,41 +1685,37 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } if (shouldUpdateChildren) { + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); for (uint8_t x = 0; x < itemField->rowCount(); ++x) - { - std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); - std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); updateRow(x, addr, comparisonAddr, itemField); - } } break; } - case MemoryFieldType::ThemeInfoName: + case MemoryFieldType::ThemeInfoPointer: case MemoryFieldType::UndeterminedThemeInfoPointer: { if (valueMemoryOffset == 0) itemValue->setData("n/a", Qt::DisplayRole); else - itemValue->setData(QString::fromStdString(Spelunky2::get()->themeNameOfOffset(valueMemoryOffset)), Qt::DisplayRole); + itemValue->setData(Spelunky2::get()->themeNameOfOffset(valueMemoryOffset), Qt::DisplayRole); if (comparisonActive) { if (valueComparisonMemoryOffset == 0) itemComparisonValue->setData("n/a", Qt::DisplayRole); else - itemComparisonValue->setData(QString::fromStdString(Spelunky2::get()->themeNameOfOffset(valueMemoryOffset)), Qt::DisplayRole); + itemComparisonValue->setData(Spelunky2::get()->themeNameOfOffset(valueMemoryOffset), Qt::DisplayRole); itemComparisonValue->setBackground(itemComparisonValueHex->background()); } if (shouldUpdateChildren && fieldType == MemoryFieldType::UndeterminedThemeInfoPointer) { + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); for (uint8_t x = 0; x < itemField->rowCount(); ++x) - { - std::optional addr = pointerUpdate ? std::optional(valueMemoryOffset) : std::nullopt; - std::optional comparisonAddr = comparisonPointerUpdate ? std::optional(valueComparisonMemoryOffset) : std::nullopt; - updateRow(x, valueMemoryOffset, valueComparisonMemoryOffset, itemField); - } + updateRow(x, addr, comparisonAddr, itemField); } break; } @@ -1746,8 +1730,8 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (comparisonActive) { // we can't show comparison version since it's a tab, not a new window - // TODO: maybe add comparison in the show rooms tab? - itemComparisonValue->setData("", Qt::DisplayRole); + // maybe add comparison in the show rooms tab? + itemComparisonValue->setData({}, Qt::DisplayRole); itemComparisonValue->setData(0, gsRoleMemoryAddress); itemComparisonValue->setBackground(itemComparisonValueHex->background()); @@ -1821,17 +1805,17 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (value.has_value()) { itemValue->setData("Show contents", Qt::DisplayRole); - // TODO maybe show hex as the begin pointer ? + // maybe show hex as the begin pointer ? } if (comparisonActive) { std::optional comparisonValue; auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x8; - value = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); - if (value.has_value()) + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); + if (comparisonValue.has_value()) { - itemValue->setData("Show contents", Qt::DisplayRole); + itemComparisonValue->setData("Show contents", Qt::DisplayRole); } itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1840,12 +1824,10 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } if (shouldUpdateChildren) { + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); for (uint8_t x = 0; x < itemField->rowCount(); ++x) - { - std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); - std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); updateRow(x, addr, comparisonAddr, itemField); - } } break; } @@ -1853,83 +1835,91 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { std::optional value; // we use the size to check if it was changed - // TODO: don't use updateField, make struct with two size_t values for comparison and hold (will also be needed for std::list) value = updateField(itemField, valueMemoryOffset == 0 ? 0 : valueMemoryOffset + 0x8, itemValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); if (value.has_value()) { itemValue->setData("Show contents", Qt::DisplayRole); - // TODO maybe show hex as the pointer ? + // maybe show hex as the pointer ? } if (comparisonActive) { std::optional comparisonValue; auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x8; - value = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); - if (value.has_value()) + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); + if (comparisonValue.has_value()) { - itemValue->setData("Show contents", Qt::DisplayRole); + itemComparisonValue->setData("Show contents", Qt::DisplayRole); } - // TODO maybe it should be based on the pointer not size? + // maybe it should be based on the pointer not size? itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); if (isPointer == false) itemComparisonValueHex->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); } if (shouldUpdateChildren) { + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); for (uint8_t x = 0; x < itemField->rowCount(); ++x) - { - std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); - std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); updateRow(x, addr, comparisonAddr, itemField); - } } break; } case MemoryFieldType::Skip: { - // TODO + // TODO when setting for skip is done break; } case MemoryFieldType::EntitySubclass: { - // can't be a pointer, nothing to do here + // can't be a pointer + if (isExpanded(itemField->index())) + itemValue->setData("[Collapse]", Qt::DisplayRole); + else + itemValue->setData("[Expand]", Qt::DisplayRole); + + if (comparisonActive) + itemComparisonValue->setData(itemValue->data(Qt::DisplayRole), Qt::DisplayRole); + if (shouldUpdateChildren) { - for (uint8_t x = 0; x < itemField->rowCount(); ++x) - { + for (int x = 0; x < itemField->rowCount(); ++x) updateRow(x, newAddr, newAddrComparison, itemField); - } } break; } case MemoryFieldType::DefaultStructType: { - if (isPointer) - { - itemValue->setData(itemValueHex->data(Qt::DisplayRole), Qt::DisplayRole); - if (comparisonActive) - { - itemComparisonValue->setData(itemComparisonValueHex->data(Qt::DisplayRole), Qt::DisplayRole); - itemComparisonValue->setBackground(itemComparisonValueHex->background()); - } - } + if (isExpanded(itemField->index())) + itemValue->setData("[Collapse]", Qt::DisplayRole); + else + itemValue->setData("[Expand]", Qt::DisplayRole); + + if (comparisonActive) + itemComparisonValue->setData(itemValue->data(Qt::DisplayRole), Qt::DisplayRole); + if (shouldUpdateChildren) { - for (uint8_t x = 0; x < itemField->rowCount(); ++x) - { - std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); - std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); + for (int x = 0; x < itemField->rowCount(); ++x) updateRow(x, addr, comparisonAddr, itemField); - } } break; } case MemoryFieldType::Dummy: { + if (isExpanded(itemField->index())) + itemValue->setData("[Collapse]", Qt::DisplayRole); + else + itemValue->setData("[Expand]", Qt::DisplayRole); + + // if (comparisonActive) + // itemComparisonValue->setData(itemValue->data(Qt::DisplayRole), Qt::DisplayRole); + // probably won't be involved in comparisons if (shouldUpdateChildren) { - for (uint8_t x = 0; x < itemField->rowCount(); ++x) + for (int x = 0; x < itemField->rowCount(); ++x) updateRow(x, newAddr, newAddrComparison, itemField); } break; @@ -1940,7 +1930,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional dprintf("WARNING: type %d not handled in TreeViewMemoryFields::updateRow ('%s' row: %d)\n", fieldType, itemField->data(gsRoleUID).toString().toStdString().c_str(), row); if (shouldUpdateChildren) { - for (uint8_t x = 0; x < itemField->rowCount(); ++x) + for (int x = 0; x < itemField->rowCount(); ++x) { std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); @@ -1955,12 +1945,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) { auto clickedItem = mModel->itemFromIndex(index); - constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role) - { - auto mod = idx.model(); - auto parentIndex = idx.parent(); - return mod->data(mod->index(idx.row(), col, parentIndex), role); - }; + constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role) { return idx.sibling(idx.row(), col).data(role); }; switch (index.column()) { @@ -2014,10 +1999,10 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } case MemoryFieldType::EntityPointer: { - auto rawValue = clickedItem->data(gsRoleRawValue).toULongLong(); // TODO check if valid ptr here or in update - if (rawValue != 0) + auto rawValue = clickedItem->data(gsRoleRawValue).toULongLong(); + if (Script::Memory::IsValidPtr(rawValue)) { - mToolbar->showEntity(rawValue); + getToolbar()->showEntity(rawValue); } break; } @@ -2026,7 +2011,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto offset = clickedItem->data(gsRoleEntityAddress).toULongLong(); if (offset != 0) { - mToolbar->showEntity(offset); + getToolbar()->showEntity(offset); } break; } @@ -2035,7 +2020,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto id = clickedItem->data(gsRoleRawValue).toUInt(); if (id != 0) { - auto view = mToolbar->showEntityDB(); + auto view = getToolbar()->showEntityDB(); if (view != nullptr) { view->showID(id); @@ -2048,10 +2033,10 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto id = clickedItem->data(gsRoleRawValue); if (!id.isNull()) { - auto view = mToolbar->showCharacterDB(); + auto view = getToolbar()->showCharacterDB(); if (view != nullptr) { - view->showIndex(id.toUInt()); + view->showID(id.toUInt()); } } break; @@ -2061,7 +2046,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto id = clickedItem->data(gsRoleRawValue); if (!id.isNull()) { - auto view = mToolbar->showTextureDB(); + auto view = getToolbar()->showTextureDB(); if (view != nullptr) { view->showID(id.toUInt()); @@ -2074,7 +2059,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto id = clickedItem->data(gsRoleRawValue); if (!id.isNull() && id.toUInt() != -1) { - auto view = mToolbar->showParticleDB(); + auto view = getToolbar()->showParticleDB(); if (view != nullptr) { view->showID(id.toUInt()); @@ -2087,11 +2072,10 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); if (rawValue != 0) { - auto id = Script::Memory::ReadDword(rawValue + 20); - auto view = mToolbar->showEntityDB(); + auto view = getToolbar()->showEntityDB(); if (view != nullptr) { - view->showID(id); // TODO: use pointer, not ID + view->showRAW(rawValue); } } break; @@ -2101,11 +2085,10 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); if (rawValue != 0) { - auto id = Script::Memory::ReadQword(rawValue); - auto view = mToolbar->showTextureDB(); + auto view = getToolbar()->showTextureDB(); if (view != nullptr) { - view->showID(id); + view->showRAW(rawValue); } } break; @@ -2129,7 +2112,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); if (rawValue != 0) { - mToolbar->showLevelGen(); // TODO: use pointer + getToolbar()->showLevelGen(rawValue); } break; } @@ -2139,7 +2122,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto fieldType = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerFirstParameterType)); if (addr != 0) { - mToolbar->showStdVector(addr, fieldType); + getToolbar()->showStdVector(addr, fieldType); } break; } @@ -2150,7 +2133,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto fieldvalueType = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerSecondParameterType)); if (addr != 0) { - mToolbar->showStdMap(addr, fieldkeyType, fieldvalueType); + getToolbar()->showStdMap(addr, fieldkeyType, fieldvalueType); } break; } @@ -2159,11 +2142,10 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); if (rawValue != 0) { - auto id = Script::Memory::ReadDword(rawValue); // TODO: use pointer - auto view = mToolbar->showParticleDB(); + auto view = getToolbar()->showParticleDB(); if (view != nullptr) { - view->showID(id); + view->showRAW(rawValue); } } break; @@ -2173,7 +2155,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); if (rawValue != 0) { - // TODO: maybe use the "entity interpret as" for vtable? (it's doable) + // maybe use the "entity interpret as" for vtable? (it's doable) auto vftType = qvariant_cast(getDataFrom(index, gsColField, gsRoleRefName)); if (vftType == "Entity") // in case of Entity, we have to see what the entity is interpreted as, and show those functions { @@ -2184,11 +2166,11 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) else ent = getDataFrom(index, gsColField, gsRoleMemoryAddress).toULongLong(); - mToolbar->showVirtualFunctions(rawValue, Entity{ent}.entityClassName()); + getToolbar()->showVirtualFunctions(rawValue, Entity{ent}.entityClassName()); } else { - mToolbar->showVirtualFunctions(rawValue, vftType); + getToolbar()->showVirtualFunctions(rawValue, vftType); } } break; @@ -2224,7 +2206,6 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) case MemoryFieldType::UnsignedQword: case MemoryFieldType::Float: case MemoryFieldType::Double: - case MemoryFieldType::UTF16Char: case MemoryFieldType::StringsTableID: { auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); @@ -2239,6 +2220,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) case MemoryFieldType::LevelGenRoomsPointer: case MemoryFieldType::LevelGenRoomsMetaPointer: { + // available only in the level gen auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); if (rawValue != 0) { @@ -2251,25 +2233,79 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); if (rawValue != 0) { - mToolbar->showJournalPage(rawValue, "JournalPage"); + getToolbar()->showJournalPage(rawValue); } break; } case MemoryFieldType::DefaultStructType: + case MemoryFieldType::EntitySubclass: + case MemoryFieldType::Dummy: + { + auto fieldIndex = index.sibling(index.row(), gsColField); + if (isExpanded(fieldIndex)) + collapse(fieldIndex); + else + expand(index.sibling(index.row(), gsColField)); + + break; + } + case MemoryFieldType::UTF16Char: { - bool isPointer = getDataFrom(index, gsColField, gsRoleIsPointer).toBool(); - if (isPointer) + auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (offset != 0) { - auto addr = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (addr != 0) - { - GuiDumpAt(addr); - GuiShowCpu(); - } + auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); + QChar c = static_cast(clickedItem->data(gsRoleRawValue).toUInt()); + auto dialog = new DialogEditString(fieldName, c, offset, 1, dataType, this); + dialog->exec(); } + break; + } + case MemoryFieldType::UTF16StringFixedSize: + { + auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (offset != 0) + { + int size = getDataFrom(index, gsColField, gsRoleSize).toInt(); + auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); + auto stringData = std::make_unique(size); + Script::Memory::Read(offset, stringData.get(), size, nullptr); + auto s = QString::fromUtf16(stringData.get()); + auto dialog = new DialogEditString(fieldName, s, offset, size / 2 - 1, dataType, this); + dialog->exec(); + } + break; + } + case MemoryFieldType::ConstCharPointer: + { + auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (offset != 0) + { + auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); + auto s = QString::fromStdString(ReadConstString(offset)); + // [Known Issue]: Now way to safely determinate allowed lenght, so we just allow as much characters as there is already + auto dialog = new DialogEditString(fieldName, s, offset, s.length(), dataType, this); + dialog->exec(); + } + break; + } + case MemoryFieldType::UTF8StringFixedSize: + { + auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (offset != 0) + { + int size = getDataFrom(index, gsColField, gsRoleSize).toInt(); + auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); + auto stringData = std::make_unique(size); + Script::Memory::Read(offset, stringData.get(), size, nullptr); + auto s = QString::fromUtf8(stringData.get()); + auto dialog = new DialogEditString(fieldName, s, offset, size - 1, dataType, this); + dialog->exec(); + } + break; } } - emit memoryFieldValueUpdated(getDataFrom(index, gsColField, gsRoleUID).toString()); + emit memoryFieldValueUpdated(index.row(), clickedItem->parent()); } } } @@ -2286,27 +2322,20 @@ void S2Plugin::TreeViewMemoryFields::clear() mModel->clear(); } -void S2Plugin::TreeViewMemoryFields::expandItem(QStandardItem* item) -{ - expand(mModel->indexFromItem(item)); -} - -void S2Plugin::TreeViewMemoryFields::setEnableChangeHighlighting(bool b) noexcept -{ - mEnableChangeHighlighting = b; -} - void S2Plugin::TreeViewMemoryFields::dragEnterEvent(QDragEnterEvent* event) { - if (event->mimeData()->hasFormat("spelunky/entityoffset")) + if (event->mimeData()->property(gsDragDropMemoryField_Address).isValid()) { + if (qobject_cast(event->source()) == this) + return; + event->acceptProposedAction(); } } void S2Plugin::TreeViewMemoryFields::dragMoveEvent(QDragMoveEvent* event) { - if (event->mimeData()->hasFormat("spelunky/entityoffset")) + if (event->mimeData()->property(gsDragDropMemoryField_Address).isValid()) { event->accept(); } @@ -2314,52 +2343,285 @@ void S2Plugin::TreeViewMemoryFields::dragMoveEvent(QDragMoveEvent* event) void S2Plugin::TreeViewMemoryFields::dropEvent(QDropEvent* event) { - auto dropData = event->mimeData()->data("spelunky/entityoffset"); - emit entityOffsetDropped(dropData.toULongLong()); - event->acceptProposedAction(); + if (qobject_cast(event->source()) == this) + return; + + auto dropData = event->mimeData()->property(gsDragDropMemoryField_Address); + if (auto addr = dropData.toULongLong(); addr != 0) + { + uintptr_t dataAddr = addr; // just for convenience + if (event->mimeData()->property(gsDragDropMemoryField_IsPointer).toBool()) + { + dataAddr = Script::Memory::ReadQword(addr); + if (!Script::Memory::IsValidPtr(dataAddr)) + return; + } + auto rootItem = mModel->invisibleRootItem(); + auto getThisTypeName = [rootItem]() -> QString + { + auto parrent = rootItem; + for (int idx = 0; idx < parrent->rowCount(); ++idx) + { + auto item = parrent->child(idx, gsColField); + if (item == nullptr) + break; + + auto data = item->data(gsRoleUID); + if (data.isValid()) + { + auto str = data.toString(); + auto dot = str.indexOf('.'); + if (dot != -1) + return str.left(dot); + } + if (item->hasChildren()) + { + parrent = item; + idx = 0; + } + } + return {}; + }; + + switch (event->mimeData()->property(gsDragDropMemoryField_Type).value()) + { + case MemoryFieldType::DataPointer: + { + // allow all + addr = dataAddr; + break; + } + case MemoryFieldType::EntityPointer: + { + if (getThisTypeName() != "Entity") + return; + + addr = dataAddr; + break; + } + case MemoryFieldType::EntityUID: + { + if (getThisTypeName() != "Entity") + return; + + auto uid = Script::Memory::ReadDword(dataAddr); + auto entityPtr = Spelunky2::get()->get_State().findEntitybyUID(uid); + if (entityPtr == 0) + return; + + addr = entityPtr; + break; + } + case MemoryFieldType::EntitySubclass: + { + if (getThisTypeName() != "Entity") + return; + + break; + } + case MemoryFieldType::EntityDBPointer: + { + if (getThisTypeName() != "EntityDB") + return; + + addr = dataAddr; + break; + } + case MemoryFieldType::EntityDBID: + { + if (getThisTypeName() != "EntityDB") + return; + + auto id = Script::Memory::ReadDword(dataAddr); + addr = Spelunky2::get()->get_EntityDB().addressOfIndex(id); + break; + } + case MemoryFieldType::ParticleDBPointer: + { + if (getThisTypeName() != "ParticleDB") + return; + + addr = dataAddr; + break; + } + case MemoryFieldType::ParticleDBID: + { + if (getThisTypeName() != "ParticleDB") + return; + + auto id = Script::Memory::ReadDword(dataAddr); + addr = Spelunky2::get()->get_ParticleDB().addressOfIndex(id); + break; + } + case MemoryFieldType::TextureDBPointer: + { + if (getThisTypeName() != "TextureDB") + return; + + addr = dataAddr; + break; + } + case MemoryFieldType::TextureDBID: + { + if (getThisTypeName() != "TextureDB") + return; + + auto id = Script::Memory::ReadDword(dataAddr); + addr = Spelunky2::get()->get_TextureDB().addressOfID(id); + break; + } + case MemoryFieldType::CharacterDBID: + { + if (getThisTypeName() != "CharacterDB") + return; + + auto id = Script::Memory::ReadByte(dataAddr); + addr = Spelunky2::get()->get_CharacterDB().addressOfIndex(id); + break; + } + case MemoryFieldType::VirtualFunctionTable: + { + // since it's always the first item in struct, we allow using it like an anchor to the whole struct + auto refName = event->mimeData()->property(gsDragDropMemoryField_RefName).value(); + if (getThisTypeName() != QString::fromStdString(refName)) + return; + break; + } + case MemoryFieldType::DefaultStructType: + { + if (getThisTypeName() != event->mimeData()->property(gsDragDropMemoryField_RefName).toString()) + return; + + addr = dataAddr; + break; + } + case MemoryFieldType::JournalPagePointer: + { + if (getThisTypeName() != "JournalPage") + return; + + addr = dataAddr; + break; + } + case MemoryFieldType::LevelGenPointer: + { + if (getThisTypeName() != "LevelGen") + return; + + addr = dataAddr; + break; + } + case MemoryFieldType::LevelGenRoomsMetaPointer: + case MemoryFieldType::LevelGenRoomsPointer: + { + if (getThisTypeName() != "LevelGen") + return; + + break; + } + case MemoryFieldType::ThemeInfoPointer: + case MemoryFieldType::UndeterminedThemeInfoPointer: + { + if (getThisTypeName() != "ThemeInfo") + return; + + addr = dataAddr; + break; + } + default: + return; + } + if (addr == 0) // just in case some bad id's + return; + + activeColumns.enable(gsColComparisonValue).enable(gsColComparisonValueHex); + setColumnHidden(gsColComparisonValue, false); + setColumnHidden(gsColComparisonValueHex, false); + updateTree(0, addr); + emit offsetDropped(addr); + event->acceptProposedAction(); + } } -void S2Plugin::TreeViewMemoryFields::startDrag(Qt::DropActions supportedActions) +void S2Plugin::TreeViewMemoryFields::startDrag(Qt::DropActions) { - auto ix = selectedIndexes(); - if (ix.count() == 0) - { + QModelIndex index = currentIndex(); + if (!index.isValid()) return; - } - constexpr auto getDataFrom = [](const QModelIndex& idx, int col, int role) - { - auto mod = idx.model(); - auto parentIndex = idx.parent(); - return mod->data(mod->index(idx.row(), col, parentIndex), role); - }; - QDrag* drag = new QDrag(this); - auto mimeData = new QMimeData(); + if (index.column() != gsColField) + index = index.sibling(index.row(), gsColField); - auto& index = ix.at(0); + auto mimeData = new QMimeData(); + mimeData->setProperty(gsDragDropMemoryField_UID, index.data(gsRoleUID)); + mimeData->setProperty(gsDragDropMemoryField_Address, index.data(gsRoleMemoryAddress)); - // for spelunky/entityoffset: dragging an entity from ViewEntities on top of ViewEntity for comparison - auto entityItem = mModel->item(index.row(), gsColMemoryAddress); // TODO: maybe not needed? try to use spelunky/memoryfield data, also allow only entity pointer? - if (entityItem != nullptr) + if (currentIndex().column() == gsColMemoryAddress) // if you grab memory address treat it like data pointer { - auto entityData = entityItem->data(gsRoleRawValue); - if (entityData.isValid()) - { - mimeData->setData("spelunky/entityoffset", QByteArray().setNum(Script::Memory::ReadQword(entityData.toULongLong()))); - } + mimeData->setProperty(gsDragDropMemoryField_Type, QVariant::fromValue(MemoryFieldType::DataPointer)); + // set as not pointer, so it does not read the memory first + mimeData->setProperty(gsDragDropMemoryField_IsPointer, false); } + else + { + mimeData->setProperty(gsDragDropMemoryField_Type, index.data(gsRoleType)); + mimeData->setProperty(gsDragDropMemoryField_IsPointer, index.data(gsRoleIsPointer)); + + switch (index.data(gsRoleType).value()) + { + case MemoryFieldType::VirtualFunctionTable: + mimeData->setProperty(gsDragDropMemoryField_RefName, index.data(gsRoleRefName)); + break; + + case MemoryFieldType::LevelGenRoomsMetaPointer: + case MemoryFieldType::LevelGenRoomsPointer: + { + auto indexDelta = index.sibling(index.row(), gsColMemoryAddressDelta); + if (!indexDelta.isValid()) + return; - // for spelunky/memoryfield: dragging any memoryfield onto ViewLogger + // note that we set the address to be of whole level gen, not the pointer + // if in future we need this pointer, will have to use different solution (adding delta value property instead) + mimeData->setProperty(gsDragDropMemoryField_Address, index.data(gsRoleMemoryAddress).toULongLong() - indexDelta.data(gsRoleRawValue).toULongLong()); + mimeData->setProperty(gsDragDropMemoryField_IsPointer, false); + break; + } + case MemoryFieldType::DefaultStructType: + { + auto indexType = index.sibling(index.row(), gsColType); + if (!indexType.isValid()) + return; - nlohmann::json o; - o[gsJSONDragDropMemoryField_UID] = getDataFrom(index, gsColField, gsRoleUID).toString().toStdString(); - o[gsJSONDragDropMemoryField_Address] = getDataFrom(index, gsColField, gsRoleMemoryAddress).toULongLong(); - o[gsJSONDragDropMemoryField_Type] = getDataFrom(index, gsColField, gsRoleType).value(); - auto json = QString::fromStdString(o.dump()); + auto typeName = indexType.data(Qt::DisplayRole).toString(); + auto colon = typeName.indexOf(':'); // to get rid of the "P: " + if (colon == -1) + mimeData->setProperty(gsDragDropMemoryField_RefName, typeName); + else + mimeData->setProperty(gsDragDropMemoryField_RefName, typeName.mid(colon + 2)); - auto codec = QTextCodec::codecForName("UTF-8"); - mimeData->setData("spelunky/memoryfield", codec->fromUnicode(json)); + break; + } + case MemoryFieldType::EntitySubclass: + { + auto indexType = index.sibling(index.row(), gsColType); + if (!indexType.isValid()) + return; + auto typeName = indexType.data(Qt::DisplayRole).toString(); + if (typeName != "Entity") + { + auto indexDelta = index.sibling(index.row(), gsColMemoryAddressDelta); + mimeData->setProperty(gsDragDropMemoryField_Address, index.data(gsRoleMemoryAddress).toULongLong() - indexDelta.data(gsRoleRawValue).toULongLong()); + } + + break; + } + case MemoryFieldType::None: + case MemoryFieldType::Dummy: + return; + } + } + QDrag* drag = new QDrag(this); drag->setMimeData(mimeData); drag->exec(); } @@ -2390,7 +2652,6 @@ static void labelChildren(QStandardItem* parrent, std::string_view prefix) name.reserve(prefix.length() + 1u + qstr_name.length()); name.append(prefix); name += '.'; - // TODO: use 'toUtf8' instead of toStdString to skip creating new object - struggling with stupid linker error name.append(qstr_name.toStdString()); } @@ -2461,3 +2722,11 @@ void S2Plugin::TreeViewMemoryFields::labelAll(std::string_view prefix) auto parrent = mModel->invisibleRootItem(); labelChildren(parrent, prefix); } + +void S2Plugin::TreeViewMemoryFields::expandLast() +{ + auto mod = model(); + auto rows = mod->rowCount(); + if (rows != 0) + expand(mod->index(mod->rowCount() - 1, 0)); +} diff --git a/src/QtHelpers/TreeWidgetItemNumeric.cpp b/src/QtHelpers/TreeWidgetItemNumeric.cpp deleted file mode 100644 index 1f942348..00000000 --- a/src/QtHelpers/TreeWidgetItemNumeric.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "QtHelpers/TreeWidgetItemNumeric.h" -#include "pluginmain.h" - -S2Plugin::TreeWidgetItemNumeric::TreeWidgetItemNumeric(QTreeWidgetItem* parent, const QString& caption) : QTreeWidgetItem(parent, QStringList(caption)) {} - -bool S2Plugin::TreeWidgetItemNumeric::operator<(const QTreeWidgetItem& other) const -{ - return data(0, Qt::UserRole) < other.data(0, Qt::UserRole); -} diff --git a/src/QtHelpers/WidgetAutorefresh.cpp b/src/QtHelpers/WidgetAutorefresh.cpp new file mode 100644 index 00000000..a0adfa91 --- /dev/null +++ b/src/QtHelpers/WidgetAutorefresh.cpp @@ -0,0 +1,53 @@ +#include "QtHelpers/WidgetAutorefresh.h" +#include +#include + +S2Plugin::WidgetAutorefresh::WidgetAutorefresh(int initialInterval, QWidget* parrent) : QWidget(parrent) +{ + auto refreshLayout = new QHBoxLayout(this); + refreshLayout->setMargin(0); + mRefreshButton = new QPushButton("Refresh", this); + refreshLayout->addWidget(mRefreshButton); + QObject::connect(mRefreshButton, &QPushButton::clicked, this, &WidgetAutorefresh::refresh); + + mAutoRefreshTimer = new QTimer(this); + QObject::connect(mAutoRefreshTimer, &QTimer::timeout, this, &WidgetAutorefresh::refresh); + + mAutoRefreshCheckBox = new QCheckBox("Auto-refresh every", this); + mAutoRefreshCheckBox->setCheckState(Qt::Checked); + refreshLayout->addWidget(mAutoRefreshCheckBox); + QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &WidgetAutorefresh::toggleAutoRefresh); + + mAutoRefreshIntervalQSpinBox = new QSpinBox(this); + mAutoRefreshIntervalQSpinBox->setFixedWidth(50); + mAutoRefreshIntervalQSpinBox->setRange(10, 5000); + mAutoRefreshIntervalQSpinBox->setValue(initialInterval); + refreshLayout->addWidget(mAutoRefreshIntervalQSpinBox); + QObject::connect(mAutoRefreshIntervalQSpinBox, static_cast(&QSpinBox::valueChanged), this, &WidgetAutorefresh::autoRefreshIntervalChanged); + + refreshLayout->addWidget(new QLabel("milliseconds", this)); + refreshLayout->addStretch(); +} + +void S2Plugin::WidgetAutorefresh::toggleAutoRefresh(bool checked) +{ + if (!checked) + { + mAutoRefreshTimer->stop(); + mRefreshButton->setEnabled(true); + } + else + { + mAutoRefreshTimer->setInterval(mAutoRefreshIntervalQSpinBox->value()); + mAutoRefreshTimer->start(); + mRefreshButton->setEnabled(false); + } +} + +void S2Plugin::WidgetAutorefresh::autoRefreshIntervalChanged(int val) +{ + if (mAutoRefreshCheckBox->checkState() == Qt::Checked) + { + mAutoRefreshTimer->setInterval(val); + } +} diff --git a/src/QtHelpers/WidgetDatabaseView.cpp b/src/QtHelpers/WidgetDatabaseView.cpp new file mode 100644 index 00000000..34ffdad2 --- /dev/null +++ b/src/QtHelpers/WidgetDatabaseView.cpp @@ -0,0 +1,511 @@ +#include "QtHelpers/WidgetDatabaseView.h" + +#include "Configuration.h" +#include "QtHelpers/StyledItemDelegateHTML.h" +#include "QtHelpers/TableWidgetItemNumeric.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/TreeWidgetItemNumeric.h" +#include "QtPlugin.h" +#include "Spelunky2.h" +#include "pluginmain.h" +#include +#include +#include +#include +#include +#include +#include + +namespace std +{ + template <> // to be able to use QString as a key in unordered map + struct hash + { + std::size_t operator()(const QString& s) const noexcept + { + return qHash(s); + } + }; +} // namespace std + +struct ComparisonField +{ + S2Plugin::MemoryFieldType type{S2Plugin::MemoryFieldType::None}; + size_t offset{0}; + std::string refName; // for flags + uint8_t flag_index; +}; +Q_DECLARE_METATYPE(ComparisonField) + +S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* parent) : QWidget(parent) +{ + setWindowIcon(getCavemanIcon()); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + + mMainTabWidget = new QTabWidget(); + mMainTabWidget->setDocumentMode(false); + mainLayout->addWidget(mMainTabWidget); + + auto tabLookup = new QWidget(); + auto tabCompare = new QWidget(); + tabLookup->setLayout(new QVBoxLayout()); + tabLookup->layout()->setMargin(10); + tabLookup->setObjectName("lookupwidget"); + tabLookup->setStyleSheet("QWidget#lookupwidget {border: 1px solid #999;}"); + tabCompare->setLayout(new QVBoxLayout()); + tabCompare->layout()->setMargin(10); + tabCompare->setObjectName("comparewidget"); + tabCompare->setStyleSheet("QWidget#comparewidget {border: 1px solid #999;}"); + + mMainTabWidget->addTab(tabLookup, "Lookup"); + mMainTabWidget->addTab(tabCompare, "Compare"); + auto config = Configuration::get(); + // LOOKUP + { + auto topLayout = new QHBoxLayout(); + + mSearchLineEdit = new QLineEdit(); + mSearchLineEdit->setPlaceholderText("Search"); + topLayout->addWidget(mSearchLineEdit); + QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &WidgetDatabaseView::searchFieldReturnPressed); + + auto labelButton = new QPushButton("Label", this); + QObject::connect(labelButton, &QPushButton::clicked, this, &WidgetDatabaseView::label); + topLayout->addWidget(labelButton); + + qobject_cast(tabLookup->layout())->addLayout(topLayout); + + mMainTreeView = new TreeViewMemoryFields(this); + mMainTreeView->setEnableChangeHighlighting(false); + mMainTreeView->addMemoryFields(config->typeFields(type), std::string(config->getTypeDisplayName(type)), 0); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &WidgetDatabaseView::fieldUpdated); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &WidgetDatabaseView::fieldExpanded); + tabLookup->layout()->addWidget(mMainTreeView); + mMainTreeView->setColumnWidth(gsColField, 125); + mMainTreeView->setColumnWidth(gsColValue, 250); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); + mMainTreeView->updateTableHeader(); + } + + // COMPARE + { + auto topLayout = new QHBoxLayout(); + mCompareFieldComboBox = new QComboBox(); + mCompareFieldComboBox->addItem(QString::fromStdString("")); + populateComparisonCombobox(config->typeFields(type)); + + QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &WidgetDatabaseView::comparisonFieldChosen); + topLayout->addWidget(mCompareFieldComboBox); + + mCompareFlagComboBox = new QComboBox(); + QObject::connect(mCompareFlagComboBox, &QComboBox::currentTextChanged, this, &WidgetDatabaseView::comparisonFlagChosen); + mCompareFlagComboBox->hide(); + topLayout->addWidget(mCompareFlagComboBox); + + auto groupCheckbox = new QCheckBox("Group by value", this); + QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &WidgetDatabaseView::compareGroupByCheckBoxClicked); + topLayout->addWidget(groupCheckbox); + + qobject_cast(tabCompare->layout())->addLayout(topLayout); + + mCompareTableWidget = new QTableWidget(0, 3, this); // need to set the row count in subclass + mCompareTableWidget->setAlternatingRowColors(true); + mCompareTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); + mCompareTableWidget->setHorizontalHeaderLabels(QStringList() << "ID" + << "Name" + << "Value"); + mCompareTableWidget->verticalHeader()->setVisible(false); + mCompareTableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + mCompareTableWidget->verticalHeader()->setDefaultSectionSize(20); + mCompareTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + mCompareTableWidget->setColumnWidth(0, 40); + mCompareTableWidget->setColumnWidth(1, 325); + mCompareTableWidget->setColumnWidth(2, 150); + auto HTMLDelegate = new StyledItemDelegateHTML(this); + mCompareTableWidget->setItemDelegate(HTMLDelegate); + QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &WidgetDatabaseView::comparisonCellClicked); + + mCompareTreeWidget = new QTreeWidget(this); + mCompareTreeWidget->setAlternatingRowColors(true); + mCompareTreeWidget->headerItem()->setHidden(true); + mCompareTreeWidget->setHidden(true); + mCompareTreeWidget->setItemDelegate(HTMLDelegate); + QObject::connect(mCompareTreeWidget, &QTreeWidget::itemClicked, this, &WidgetDatabaseView::groupedComparisonItemClicked); + + tabCompare->layout()->addWidget(mCompareTableWidget); + tabCompare->layout()->addWidget(mCompareTreeWidget); + } + + mSearchLineEdit->setFocus(); +} + +QSize S2Plugin::WidgetDatabaseView::minimumSizeHint() const +{ + return QSize(150, 150); +} + +void S2Plugin::WidgetDatabaseView::searchFieldReturnPressed() +{ + auto text = mSearchLineEdit->text(); + bool isNumeric = false; + auto enteredID = text.toUInt(&isNumeric); + if (isNumeric) + { + if (enteredID > highestRecordID()) // exceptions can be dealt in showID + return; + + showID(enteredID); + } + else // maybe should not do this else part at all and just handle it in searchFieldCompleterActivated + { + auto cID = getIDForName(std::move(text)); + if (cID.has_value()) + showID(cID.value()); + } +} + +void S2Plugin::WidgetDatabaseView::fieldUpdated(int row, QStandardItem* parrent) +{ + if (parrent != nullptr) // special case: for flag field need to update it's parrent, not the flag field + { + auto model = qobject_cast(mMainTreeView->model()); + auto parrentIndex = parrent->index(); + auto index = model->index(row, gsColField, parrentIndex); + if (model->data(index, gsRoleType).value() == MemoryFieldType::Flag) + { + mMainTreeView->updateRow(parrentIndex.row(), std::nullopt, std::nullopt, parrent->parent(), true); + return; + } + } + mMainTreeView->updateRow(row, std::nullopt, std::nullopt, parrent, true); +} + +void S2Plugin::WidgetDatabaseView::fieldExpanded(const QModelIndex& index) +{ + auto model = qobject_cast(mMainTreeView->model()); + mMainTreeView->updateRow(index.row(), std::nullopt, std::nullopt, model->itemFromIndex(index.parent()), true); +} + +void S2Plugin::WidgetDatabaseView::compareGroupByCheckBoxClicked(int state) +{ + mCompareTableWidget->setHidden(state == Qt::Checked); + mCompareTreeWidget->setHidden(state == Qt::Unchecked); +} + +void S2Plugin::WidgetDatabaseView::comparisonFieldChosen() +{ + mFieldChoosen = true; + mCompareTableWidget->clearContents(); + mCompareTreeWidget->clear(); + + auto comboIndex = mCompareFieldComboBox->currentIndex(); + if (comboIndex == 0) + return; + + auto comboboxData = mCompareFieldComboBox->currentData(); + if (!comboboxData.isValid()) + return; + + auto type = comboboxData.value().type; + uint8_t flagsCount = 0; + if (type == MemoryFieldType::Flags8) + flagsCount = 8; + else if (type == MemoryFieldType::Flags16) + flagsCount = 16; + else if (type == MemoryFieldType::Flags32) + flagsCount = 32; + + if (flagsCount != 0) + { + mCompareFlagComboBox->clear(); + mCompareFlagComboBox->addItem(""); + std::string refName = comboboxData.value().refName; + for (uint8_t x = 1; x <= flagsCount; ++x) + { + auto flagName = Configuration::get()->flagTitle(refName, x); + // TODO: don't show unknown unless it was chosen in settings + QString realFlagName = QString("%1: ").arg(x) + QString::fromStdString(flagName.empty() ? Configuration::get()->flagTitle("unknown", x) : flagName); + mCompareFlagComboBox->addItem(realFlagName, x - 1); + } + mCompareFlagComboBox->show(); + } + else + { + mCompareFlagComboBox->clear(); + mCompareFlagComboBox->hide(); + } + + populateComparisonTableWidget(comboboxData); + populateComparisonTreeWidget(comboboxData); + mFieldChoosen = false; +} + +void S2Plugin::WidgetDatabaseView::comparisonFlagChosen(const QString& text) +{ + // protect againts infinite loops since this slot is called when adding firts element to combo box + if (mFieldChoosen) + return; + + mCompareTableWidget->clearContents(); + mCompareTreeWidget->clear(); + + if (text.isEmpty()) + { + auto comboboxData = mCompareFieldComboBox->currentData(); + if (!comboboxData.isValid()) + return; + + populateComparisonTableWidget(comboboxData); + populateComparisonTreeWidget(comboboxData); + + return; + } + + auto fieldData = mCompareFieldComboBox->currentData(); + if (!fieldData.isValid()) + return; + auto flagData = mCompareFlagComboBox->currentData(); + if (!flagData.isValid()) + return; + + ComparisonField comparisonData = fieldData.value(); + comparisonData.type = MemoryFieldType::Flag; + comparisonData.flag_index = flagData.value(); + auto newData = QVariant::fromValue(comparisonData); + + populateComparisonTableWidget(newData); + populateComparisonTreeWidget(newData); +} + +void S2Plugin::WidgetDatabaseView::populateComparisonTableWidget(const QVariant& fieldData) +{ + mCompareTableWidget->setSortingEnabled(false); + + int row = 0; + for (ID_type x = 0; x <= highestRecordID(); ++x) + { + if (!isValidRecordID(x)) + continue; + + auto item0 = new QTableWidgetItem(QString::asprintf("%03d", x)); + item0->setTextAlignment(Qt::AlignCenter); + mCompareTableWidget->setItem(row, 0, item0); + const auto name = recordNameForID(x); + mCompareTableWidget->setItem(row, 1, new QTableWidgetItem(QString("%1").arg(name))); + + auto [caption, value] = valueForField(fieldData, addressOfRecordID(x)); + auto item = new TableWidgetItemNumeric(caption); + item->setData(Qt::UserRole, value); + mCompareTableWidget->setItem(row, 2, item); + + row++; + } + mCompareTableWidget->setSortingEnabled(true); + mCompareTableWidget->sortItems(0); +} + +void S2Plugin::WidgetDatabaseView::populateComparisonTreeWidget(const QVariant& fieldData) +{ + mCompareTreeWidget->setSortingEnabled(false); + + std::unordered_map rootValues; + std::unordered_map> groupedValues; // valueString -> vector + for (ID_type x = 0; x <= highestRecordID(); ++x) + { + if (!isValidRecordID(x)) + continue; + + auto [caption, value] = valueForField(fieldData, addressOfRecordID(x)); + rootValues[caption] = value; + + if (auto it = groupedValues.find(caption); it != groupedValues.end()) + { + it->second.push_back(x); + } + else + { + groupedValues[caption] = {x}; + } + } + + for (const auto& [groupString, IDList] : groupedValues) + { + auto rootItem = new TreeWidgetItemNumeric(mCompareTreeWidget, QStringList(groupString)); + rootItem->setData(0, Qt::UserRole, rootValues.at(groupString)); + mCompareTreeWidget->insertTopLevelItem(0, rootItem); + for (const auto& recordID : IDList) + { + auto name = recordNameForID(recordID); + auto caption = QString("%1").arg(name); + auto childItem = new QTreeWidgetItem(rootItem, QStringList(caption)); + childItem->setData(0, Qt::UserRole, recordID); + mCompareTreeWidget->insertTopLevelItem(0, childItem); + } + } + + mCompareTreeWidget->setSortingEnabled(true); + mCompareTreeWidget->sortItems(0, Qt::AscendingOrder); +} + +void S2Plugin::WidgetDatabaseView::comparisonCellClicked(int row, int column) +{ + if (column == 1) + { + mSearchLineEdit->clear(); + auto clickedID = mCompareTableWidget->item(row, 0)->data(Qt::DisplayRole).toUInt(); + showID(clickedID); + } +} + +void S2Plugin::WidgetDatabaseView::groupedComparisonItemClicked(QTreeWidgetItem* item) +{ + if (item->childCount() == 0) + { + mSearchLineEdit->clear(); + showID(item->data(0, Qt::UserRole).toUInt()); + } +} + +size_t S2Plugin::WidgetDatabaseView::populateComparisonCombobox(const std::vector& fields, size_t offset, std::string prefix) +{ + for (const auto& field : fields) + { + if (field.isPointer) + { + // we don't do pointers as i don't think there are any important ones for comparison in databases + + offset += sizeof(uintptr_t); + continue; + } + switch (field.type) + { + case MemoryFieldType::Skip: + break; + case MemoryFieldType::Flags32: + case MemoryFieldType::Flags16: + case MemoryFieldType::Flags8: + { + ComparisonField parrentFlag; + parrentFlag.type = field.type; + parrentFlag.offset = offset; + parrentFlag.refName = field.firstParameterType; + mCompareFieldComboBox->addItem(QString::fromStdString(prefix + field.name), QVariant::fromValue(parrentFlag)); + break; + } + case MemoryFieldType::DefaultStructType: + { + offset = populateComparisonCombobox(Configuration::get()->typeFieldsOfDefaultStruct(field.jsonName), offset, prefix + field.name + "."); + continue; + } + default: + { + ComparisonField tmp; + tmp.offset = offset; + tmp.type = field.type; + mCompareFieldComboBox->addItem(QString::fromStdString(prefix + field.name), QVariant::fromValue(tmp)); + break; + } + } + offset += field.get_size(); + } + return offset; +} + +std::pair S2Plugin::WidgetDatabaseView::valueForField(const QVariant& data, uintptr_t addr) +{ + ComparisonField compData = qvariant_cast(data); + + auto offset = addr + compData.offset; + switch (compData.type) + { + // we only handle types that occur in the db's + case MemoryFieldType::Byte: + case MemoryFieldType::State8: + { + int8_t value = Script::Memory::ReadByte(offset); + return std::make_pair(QString::asprintf("%d", value), QVariant::fromValue(value)); + } + case MemoryFieldType::CharacterDBID: + case MemoryFieldType::UnsignedByte: + case MemoryFieldType::Flags8: + { + uint8_t value = Script::Memory::ReadByte(offset); + return std::make_pair(QString::asprintf("%u", value), QVariant::fromValue(value)); + } + case MemoryFieldType::Word: + case MemoryFieldType::State16: + { + int16_t value = Script::Memory::ReadWord(offset); + return std::make_pair(QString::asprintf("%d", value), QVariant::fromValue(value)); + } + case MemoryFieldType::UnsignedWord: + case MemoryFieldType::Flags16: + { + uint16_t value = Script::Memory::ReadWord(offset); + return std::make_pair(QString::asprintf("%u", value), QVariant::fromValue(value)); + } + case MemoryFieldType::TextureDBID: + case MemoryFieldType::Dword: + case MemoryFieldType::State32: + { + int32_t value = Script::Memory::ReadDword(offset); + return std::make_pair(QString::asprintf("%ld", value), QVariant::fromValue(value)); + } + case MemoryFieldType::ParticleDBID: + case MemoryFieldType::EntityDBID: + case MemoryFieldType::StringsTableID: + case MemoryFieldType::UnsignedDword: + case MemoryFieldType::Flags32: + { + uint32_t value = Script::Memory::ReadDword(offset); + return std::make_pair(QString::asprintf("%lu", value), QVariant::fromValue(value)); + } + case MemoryFieldType::Qword: + { + int64_t value = Script::Memory::ReadQword(offset); + return std::make_pair(QString::asprintf("%lld", value), QVariant::fromValue(value)); + } + case MemoryFieldType::UnsignedQword: + { + uint64_t value = Script::Memory::ReadQword(offset); + return std::make_pair(QString::asprintf("%llu", value), QVariant::fromValue(value)); + } + case MemoryFieldType::Float: + { + uint32_t dword = Script::Memory::ReadDword(offset); + float value = reinterpret_cast(dword); + return std::make_pair(QString::asprintf("%f", value), QVariant::fromValue(value)); + } + case MemoryFieldType::Double: + { + size_t qword = Script::Memory::ReadQword(offset); + double value = reinterpret_cast(qword); + return std::make_pair(QString::asprintf("%lf", value), QVariant::fromValue(value)); + } + case MemoryFieldType::Bool: + { + auto b = Script::Memory::ReadByte(offset); + bool value = b != 0; + return std::make_pair(value ? "True" : "False", QVariant::fromValue(b)); + } + case MemoryFieldType::Flag: + { + uint8_t flagToCheck = compData.flag_index; + bool isFlagSet = false; + if (flagToCheck > 15) + isFlagSet = ((Script::Memory::ReadDword(offset) & (1 << flagToCheck)) != 0); + else if (flagToCheck > 7) + isFlagSet = ((Script::Memory::ReadWord(offset) & (1 << flagToCheck)) != 0); + else + isFlagSet = ((Script::Memory::ReadByte(offset) & (1 << flagToCheck)) != 0); + + return std::make_pair(isFlagSet ? "True" : "False", QVariant::fromValue(isFlagSet)); + } + } + return std::make_pair("unknown", 0); +} diff --git a/src/QtHelpers/WidgetMemoryView.cpp b/src/QtHelpers/WidgetMemoryView.cpp index 33c6be2f..d5a3a5c3 100644 --- a/src/QtHelpers/WidgetMemoryView.cpp +++ b/src/QtHelpers/WidgetMemoryView.cpp @@ -1,10 +1,12 @@ #include "QtHelpers/WidgetMemoryView.h" + #include "pluginmain.h" #include #include #include #include #include +#include static constexpr uint32_t gsMarginHor = 10; static constexpr uint32_t gsMarginVer = 5; @@ -17,9 +19,9 @@ S2Plugin::WidgetMemoryView::WidgetMemoryView(QWidget* parent) : QWidget(parent) setMouseTracking(true); } -void S2Plugin::WidgetMemoryView::paintEvent(QPaintEvent* event) +void S2Plugin::WidgetMemoryView::paintEvent(QPaintEvent*) { - if (mOffset != 0 && mSize != 0) + if (mAddress != 0 && mSize != 0) { QPainter painter(this); painter.setRenderHint(QPainter::HighQualityAntialiasing, true); @@ -31,7 +33,7 @@ void S2Plugin::WidgetMemoryView::paintEvent(QPaintEvent* event) uint32_t y = gsMarginVer + mTextAdvance.height(); uint32_t index = 0; bool addToolTips = mToolTipRects.empty(); - for (size_t opCounter = mOffset; opCounter < (mOffset + mSize); ++opCounter) + for (uintptr_t opCounter = mAddress; opCounter < (mAddress + mSize); ++opCounter) { // paint highlighted fields painter.setPen(Qt::transparent); @@ -49,8 +51,7 @@ void S2Plugin::WidgetMemoryView::paintEvent(QPaintEvent* event) // paint hex values painter.setPen(QPen(Qt::SolidLine)); - // TODO: don't read on refresh rate - auto str = QString("%1").arg(Script::Memory::ReadByte(opCounter), 2, 16, QChar('0')); + auto str = QString("%1").arg(mMemoryData[opCounter - mAddress], 2, 16, QChar('0')); painter.drawText(x, y, str); x += mTextAdvance.width() + mSpaceAdvance; index++; @@ -77,7 +78,7 @@ QSize S2Plugin::WidgetMemoryView::minimumSizeHint() const void S2Plugin::WidgetMemoryView::setOffsetAndSize(size_t offset, size_t size) { - mOffset = offset; + mAddress = offset; mSize = size; update(); updateGeometry(); @@ -91,7 +92,7 @@ void S2Plugin::WidgetMemoryView::clearHighlights() update(); } -void S2Plugin::WidgetMemoryView::addHighlightedField(std::string tooltip, size_t offset, size_t size, QColor color) +void S2Plugin::WidgetMemoryView::addHighlightedField(std::string tooltip, size_t offset, int size, QColor color) { mHighlightedFields.emplace_back(std::move(tooltip), offset, size, std::move(color)); } @@ -108,3 +109,9 @@ void S2Plugin::WidgetMemoryView::mouseMoveEvent(QMouseEvent* event) } } } + +void S2Plugin::WidgetMemoryView::updateMemory() +{ + Script::Memory::Read(mAddress, &mMemoryData, gBigEntityBucket, nullptr); + update(); +} diff --git a/src/QtHelpers/WidgetSamplesPlot.cpp b/src/QtHelpers/WidgetSamplesPlot.cpp index 9a35ae57..e7b8a533 100644 --- a/src/QtHelpers/WidgetSamplesPlot.cpp +++ b/src/QtHelpers/WidgetSamplesPlot.cpp @@ -1,7 +1,6 @@ #include "QtHelpers/WidgetSamplesPlot.h" #include "Configuration.h" #include "Data/Logger.h" -#include "pluginmain.h" #include #include #include @@ -9,13 +8,7 @@ static const uint8_t gsPlotMargin = 5; -S2Plugin::WidgetSamplesPlot::WidgetSamplesPlot(Logger* logger, QWidget* parent) : QWidget(parent), mLogger(logger) -{ - setMouseTracking(true); - setCursor(Qt::CrossCursor); -} - -void S2Plugin::WidgetSamplesPlot::paintEvent(QPaintEvent* event) +void S2Plugin::WidgetSamplesPlot::paintEvent(QPaintEvent*) { auto painter = QPainter(this); @@ -236,23 +229,7 @@ void S2Plugin::WidgetSamplesPlot::paintEvent(QPaintEvent* event) } } -void S2Plugin::WidgetSamplesPlot::mouseMoveEvent(QMouseEvent* event) -{ - mCurrentMousePos = event->pos(); - update(); -} - -void S2Plugin::WidgetSamplesPlot::leaveEvent(QEvent* event) -{ - update(); -} - QSize S2Plugin::WidgetSamplesPlot::minimumSizeHint() const { - return QSize(mLogger->sampleCount() + (gsPlotMargin * 2) + 100, 50); -} - -QSize S2Plugin::WidgetSamplesPlot::sizeHint() const -{ - return minimumSizeHint(); + return QSize(static_cast(mLogger->sampleCount() + (gsPlotMargin * 2) + 100), 50); } diff --git a/src/QtHelpers/WidgetSampling.cpp b/src/QtHelpers/WidgetSampling.cpp deleted file mode 100644 index 7084f03c..00000000 --- a/src/QtHelpers/WidgetSampling.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include "QtHelpers/WidgetSampling.h" -#include - -S2Plugin::WidgetSampling::WidgetSampling(QWidget* parent) : QWidget(parent) {} - -void S2Plugin::WidgetSampling::paintEvent(QPaintEvent* event) -{ - auto painter = QPainter(this); - painter.save(); - - painter.fillRect(rect(), Qt::white); - painter.setPen(Qt::darkGray); - painter.drawRect(rect().adjusted(0, 0, -1, -1)); - - static const auto caption = QString("Sampling..."); - static const auto font = QFont("Arial", 16); - static const auto captionSize = QFontMetrics(font).size(Qt::TextSingleLine, caption); - - painter.setFont(font); - painter.setPen(Qt::lightGray); - painter.drawText(QPointF((width() / 2.) - (captionSize.width() / 2.), (height() / 2.) - (captionSize.height() / 2.)), caption); - - painter.restore(); -} - -QSize S2Plugin::WidgetSampling::minimumSizeHint() const -{ - return QSize(400, 400); -} - -QSize S2Plugin::WidgetSampling::sizeHint() const -{ - return minimumSizeHint(); -} diff --git a/src/QtHelpers/WidgetSpelunkyLevel.cpp b/src/QtHelpers/WidgetSpelunkyLevel.cpp index 4f66436e..979814b3 100644 --- a/src/QtHelpers/WidgetSpelunkyLevel.cpp +++ b/src/QtHelpers/WidgetSpelunkyLevel.cpp @@ -1,4 +1,5 @@ #include "QtHelpers/WidgetSpelunkyLevel.h" + #include "Configuration.h" #include "Data/StdMap.h" #include "Spelunky2.h" @@ -13,24 +14,30 @@ S2Plugin::WidgetSpelunkyLevel::WidgetSpelunkyLevel(uintptr_t main_entity, QWidge mGridEntitiesAddr.first = Configuration::get()->offsetForField(MemoryFieldType::State, "layer0.grid_entities_begin", stateptr); mGridEntitiesAddr.second = Configuration::get()->offsetForField(MemoryFieldType::State, "layer1.grid_entities_begin", stateptr); - auto offset = Configuration::get()->offsetForField(MemoryFieldType::State, "level_width_rooms", stateptr); - mLevelWidth = Script::Memory::ReadDword(offset) * 10; - mLevelHeight = Script::Memory::ReadDword(offset + 4) * 8; - - if (mLevelWidth == 0) - { - mLevelWidth = msLevelMaxWidth; - mLevelHeight = msLevelMaxHeight; - } - else - { - // add border size - mLevelWidth += 5; - mLevelHeight += 5; - } + // auto offset = Configuration::get()->offsetForField(MemoryFieldType::State, "level_width_rooms", stateptr); + // mLevelWidth = Script::Memory::ReadDword(offset) * 10; + // mLevelHeight = Script::Memory::ReadDword(offset + 4) * 8; + + // if (mLevelWidth == 0) + //{ + // mLevelWidth = msLevelMaxWidth; + // mLevelHeight = msLevelMaxHeight; + // } + // else + //{ + // // add border size + // mLevelWidth += 5; + // mLevelHeight += 5; + + // // limit just in case + // if (mLevelWidth > msLevelMaxWidth) + // mLevelWidth = msLevelMaxWidth; + // if (mLevelHeight > msLevelMaxHeight) + // mLevelHeight = msLevelMaxHeight; + //} } -void S2Plugin::WidgetSpelunkyLevel::paintEvent(QPaintEvent* event) +void S2Plugin::WidgetSpelunkyLevel::paintEvent(QPaintEvent*) { auto painter = QPainter(this); painter.setRenderHint(QPainter::HighQualityAntialiasing, true); @@ -45,20 +52,14 @@ void S2Plugin::WidgetSpelunkyLevel::paintEvent(QPaintEvent* event) painter.setPen(Qt::transparent); if (mPaintFloors) { - // painting floors is quite expensive, any optimisations are always welcome (like maybe use provided event to not draw obstructed parts of the level?) - // TODO: don't read on refresh rate + // painting floors is quite expensive, any optimisations are always welcome (like maybe use provided event to not draw obstructed parts of the level view?) painter.setBrush(mFloorColor); - auto gridAddr = layerToDraw == 0 ? mGridEntitiesAddr.first : mGridEntitiesAddr.second; // y: 0-125, x: 0-85 // note: the y = 125 is at the top of a level and the level is build from the top - for (uint8_t y = msLevelMaxHeight - mLevelHeight; y < 126; ++y) - { - for (uint8_t x = 0; x <= mLevelWidth; ++x) - { - if (Script::Memory::ReadQword(gridAddr + y * (86 * sizeof(uintptr_t)) + x * sizeof(uintptr_t)) != 0) + for (uint8_t y = 0; y < msLevelMaxHeight + 1; ++y) + for (uint8_t x = 0; x <= msLevelMaxWidth + 1; ++x) + if (mLevelFloors[y][x] != 0) painter.drawRect(QRectF(msMarginHor + x, msMarginVer + msLevelMaxHeight - y, 1.0, 1.0)); - } - } } if (mEntityMasksToPaint != 0) { @@ -67,29 +68,17 @@ void S2Plugin::WidgetSpelunkyLevel::paintEvent(QPaintEvent* event) { if ((mEntityMasksToPaint >> bit_number) & 1) { - auto itr = maskMap.find(1u << bit_number); - if (itr != maskMap.end()) - { - painter.setBrush(mEntityMaskColors[bit_number]); - // TODO: change to proper struct when done - auto ent_list = itr.value_ptr(); - auto pointers = Script::Memory::ReadQword(ent_list); - auto list_count = Script::Memory::ReadDword(ent_list + 20); - for (uint idx = 0; idx < list_count; idx++) - { - Entity ent{Script::Memory::ReadQword(pointers + idx * sizeof(uintptr_t))}; - auto [entityX, entityY] = ent.abs_position(); - painter.drawRect(QRectF(msMarginHor + entityX, msMarginVer + msLevelMaxHeight - entityY, 1.0, 1.0)); - } - } + painter.setBrush(mEntityMaskColors[bit_number]); + for (auto& [posX, posY] : mEntitiesMaskCoordinates[bit_number]) + painter.drawRect(QRectF(msMarginHor + posX, msMarginVer + msLevelMaxHeight - posY, 1.0, 1.0)); } } } - for (auto& [entity, color] : mEntitiesToPaint) + for (auto& entity : mEntitiesToPaint) { - auto [entityX, entityY] = entity.abs_position(); - painter.setBrush(color); + const auto& [entityX, entityY] = entity.pos; + painter.setBrush(entity.color); painter.drawRect(QRectF(msMarginHor + entityX, msMarginVer + msLevelMaxHeight - entityY, 1.0, 1.0)); } @@ -126,6 +115,9 @@ void S2Plugin::WidgetSpelunkyLevel::clearAllPaintedEntities() mEntitiesToPaint.clear(); mEntityMasksToPaint = 0; mPaintFloors = false; + for (uint8_t bit_number = 0; bit_number < mEntityMaskColors.size(); ++bit_number) + mEntitiesMaskCoordinates[bit_number].clear(); + update(); } @@ -133,7 +125,7 @@ void S2Plugin::WidgetSpelunkyLevel::clearPaintedEntity(uintptr_t addr) { for (auto cur = mEntitiesToPaint.begin(); cur < mEntitiesToPaint.end(); ++cur) { - if (cur->first.ptr() == addr) + if (cur->ent.ptr() == addr) { mEntitiesToPaint.erase(cur); break; @@ -152,3 +144,48 @@ QSize S2Plugin::WidgetSpelunkyLevel::sizeHint() const { return minimumSizeHint(); } + +void S2Plugin::WidgetSpelunkyLevel::updateLevel() +{ + uint8_t layerToDraw = Entity{mMainEntityAddr}.layer(); + if (mPaintFloors) + { + auto gridAddr = layerToDraw == 0 ? mGridEntitiesAddr.first : mGridEntitiesAddr.second; + // Maybe don't read the whole array? + constexpr auto dataSize = (msLevelMaxHeight + 1) * ((msLevelMaxWidth + 1) * sizeof(uintptr_t)); + Script::Memory::Read(gridAddr, &mLevelFloors, dataSize, nullptr); + } + for (auto& entity : mEntitiesToPaint) + entity.pos = entity.ent.abs_position(); + + if (mEntityMasksToPaint != 0) + { + StdMap maskMap{layerToDraw == 0 ? mMaskMapAddr.first : mMaskMapAddr.second}; + for (auto [key, value_ptr] : maskMap) + { + uint8_t bit_number = std::log2(key); + mEntitiesMaskCoordinates[bit_number].clear(); + if ((mEntityMasksToPaint & key) != 0) + { + // TODO: change to proper struct when done + auto pointers = Script::Memory::ReadQword(value_ptr); + auto list_count = Script::Memory::ReadDword(value_ptr + 20); + + mEntitiesMaskCoordinates[bit_number].reserve(list_count); + std::vector entities; + entities.resize(list_count); + Script::Memory::Read(pointers, entities.data(), list_count * sizeof(uintptr_t), nullptr); + + for (auto entityAddr : entities) + { + if (entityAddr == 0) + continue; + + Entity ent{entityAddr}; + mEntitiesMaskCoordinates[bit_number].emplace_back(ent.abs_position()); + } + } + } + } + update(); +} diff --git a/src/QtHelpers/WidgetSpelunkyRooms.cpp b/src/QtHelpers/WidgetSpelunkyRooms.cpp index 09b8e683..87dceefd 100644 --- a/src/QtHelpers/WidgetSpelunkyRooms.cpp +++ b/src/QtHelpers/WidgetSpelunkyRooms.cpp @@ -3,7 +3,6 @@ #include "Data/State.h" #include "QtHelpers/WidgetMemoryView.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" #include "pluginmain.h" #include #include @@ -26,7 +25,7 @@ S2Plugin::WidgetSpelunkyRooms::WidgetSpelunkyRooms(const std::string& fieldName, setSizePolicy(QSizePolicy(QSizePolicy::Policy::Fixed, QSizePolicy::Policy::Fixed)); } -void S2Plugin::WidgetSpelunkyRooms::paintEvent(QPaintEvent* event) +void S2Plugin::WidgetSpelunkyRooms::paintEvent(QPaintEvent*) { auto config = Configuration::get(); QPainter painter(this); diff --git a/src/QtPlugin.cpp b/src/QtPlugin.cpp index 7b1e5d89..9f64bca0 100644 --- a/src/QtPlugin.cpp +++ b/src/QtPlugin.cpp @@ -1,4 +1,5 @@ #include "QtPlugin.h" +#include "Spelunky2.h" #include "Views/ViewToolbar.h" #include "Views/ViewVirtualTable.h" #include "pluginmain.h" @@ -10,6 +11,7 @@ QMainWindow* gsSpelunky2MainWindow; QMdiArea* gsMDIArea; S2Plugin::ViewToolbar* gsViewToolbar; +QPixmap gsCavemanIcon; static HANDLE hSetupEvent; static HANDLE hStopEvent; @@ -43,8 +45,9 @@ void QtPlugin::Setup() { QWidget* parent = getParent(); + gsCavemanIcon = QPixmap(":/icons/caveman.png"); gsSpelunky2MainWindow = new QMainWindow(); - gsSpelunky2MainWindow->setWindowIcon(QIcon(":/icons/caveman.png")); + gsSpelunky2MainWindow->setWindowIcon(QIcon(gsCavemanIcon)); gsMDIArea = new QMdiArea(); gsSpelunky2MainWindow->setCentralWidget(gsMDIArea); gsSpelunky2MainWindow->setWindowTitle("Spelunky 2"); @@ -55,8 +58,8 @@ void QtPlugin::Setup() GuiAddQWidgetTab(gsSpelunky2MainWindow); auto cavemanBytes = getResourceBytes(":/icons/caveman.png"); - ICONDATA cavemanIcon{cavemanBytes.data(), (duint)(cavemanBytes.size())}; - _plugin_menuseticon(S2Plugin::hMenuDisasm, &cavemanIcon); + ICONDATA icon{cavemanBytes.data(), (duint)(cavemanBytes.size())}; + _plugin_menuseticon(S2Plugin::hMenuDisasm, &icon); _plugin_menuaddentry(S2Plugin::hMenuDisasm, MENU_DISASM_LOOKUP_IN_VIRTUAL_TABLE, "Lookup in virtual table"); SetEvent(hSetupEvent); @@ -85,12 +88,21 @@ void QtPlugin::ShowTab() GuiShowQWidgetTab(gsSpelunky2MainWindow); } +struct S2Plugin::QtPluginStruct +{ + void static inline resetSpelunky2Data() + { + gsViewToolbar->mMDIArea->closeAllSubWindows(); + S2Plugin::Spelunky2::reset(); + }; +}; + void QtPlugin::Detach() { - gsViewToolbar->resetSpelunky2Data(); + S2Plugin::QtPluginStruct::resetSpelunky2Data(); } -void QtPlugin::MenuPrepare(int hMenu) {} +void QtPlugin::MenuPrepare([[maybe_unused]] int hMenu) {} void QtPlugin::MenuEntry(int hEntry) { @@ -135,3 +147,13 @@ void QtPlugin::MenuEntry(int hEntry) break; } } + +QIcon S2Plugin::getCavemanIcon() +{ + return QIcon(gsCavemanIcon); +} + +S2Plugin::ViewToolbar* S2Plugin::getToolbar() +{ + return gsViewToolbar; +} diff --git a/src/Spelunky2.cpp b/src/Spelunky2.cpp index 8f04d894..c0cd8d8e 100644 --- a/src/Spelunky2.cpp +++ b/src/Spelunky2.cpp @@ -138,12 +138,12 @@ uintptr_t S2Plugin::Spelunky2::get_SaveDataPtr() return heapBaseAddr + heapOffsetSaveGame; } -std::string S2Plugin::Spelunky2::themeNameOfOffset(uintptr_t offset) const // TODO use QString? +const QString& S2Plugin::Spelunky2::themeNameOfOffset(uintptr_t offset) const { auto config = Configuration::get(); uintptr_t firstThemeOffset = config->offsetForField(MemoryFieldType::LevelGen, "theme_dwelling", get_LevelGenPtr()); - const static auto themeNames = { + static const QStringList themeNames = { "DWELLING", "JUNGLE", "VOLCANA", "OLMEC", "TIDE POOL", "TEMPLE", "ICE CAVES", "NEO BABYLON", "SUNKEN CITY", "COSMIC OCEAN", "CITY OF GOLD", "DUAT", "ABZU", "TIAMAT", "EGGPLANT WORLD", "HUNDUN", "BASE CAMP", "ARENA", }; @@ -151,8 +151,8 @@ std::string S2Plugin::Spelunky2::themeNameOfOffset(uintptr_t offset) const // TO { uintptr_t testPtr = Script::Memory::ReadQword(firstThemeOffset + idx * 0x8ull); if (testPtr == offset) - return *(themeNames.begin() + idx); + return themeNames.at(idx); } - - return "UNKNOWN THEME"; + static const QString unknown{"UNKNOWN THEME"}; + return unknown; } diff --git a/src/Views/ViewCharacterDB.cpp b/src/Views/ViewCharacterDB.cpp index 612ba587..9a8d7474 100644 --- a/src/Views/ViewCharacterDB.cpp +++ b/src/Views/ViewCharacterDB.cpp @@ -1,137 +1,21 @@ #include "Views/ViewCharacterDB.h" + #include "Configuration.h" -#include "QtHelpers/DatabaseHelper.h" -#include "QtHelpers/TableWidgetItemNumeric.h" #include "QtHelpers/TreeViewMemoryFields.h" -#include "QtHelpers/TreeWidgetItemNumeric.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include #include -#include -#include -#include -S2Plugin::ViewCharacterDB::ViewCharacterDB(ViewToolbar* toolbar, uint8_t index, QWidget* parent) : QWidget(parent) +S2Plugin::ViewCharacterDB::ViewCharacterDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::CharacterDB, parent) { - mMainTreeView = new TreeViewMemoryFields(toolbar, this); - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); setWindowTitle("Character DB"); - showIndex(index); -} - -void S2Plugin::ViewCharacterDB::initializeUI() -{ - auto mainLayout = new QVBoxLayout(); - mainLayout->setMargin(5); - setLayout(mainLayout); - - mMainTabWidget = new QTabWidget(this); - mMainTabWidget->setDocumentMode(false); - mainLayout->addWidget(mMainTabWidget); - - mTabLookup = new QWidget(); - mTabCompare = new QWidget(); - mTabLookup->setLayout(new QVBoxLayout(mTabLookup)); - mTabLookup->layout()->setMargin(10); - mTabLookup->setObjectName("lookupwidget"); - mTabLookup->setStyleSheet("QWidget#lookupwidget {border: 1px solid #999;}"); - mTabCompare->setLayout(new QVBoxLayout(mTabCompare)); - mTabCompare->layout()->setMargin(10); - mTabCompare->setObjectName("comparewidget"); - mTabCompare->setStyleSheet("QWidget#comparewidget {border: 1px solid #999;}"); - - mMainTabWidget->addTab(mTabLookup, "Lookup"); - mMainTabWidget->addTab(mTabCompare, "Compare"); - auto config = Configuration::get(); - // LOOKUP - { - auto topLayout = new QHBoxLayout(); - - mSearchLineEdit = new QLineEdit(); // TODO: maybe change into ComboBox since there is only 19 of them? - mSearchLineEdit->setPlaceholderText("Search id"); - topLayout->addWidget(mSearchLineEdit); - QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &ViewCharacterDB::searchFieldReturnPressed); - mSearchLineEdit->setVisible(false); - auto characterNameCompleter = new QCompleter(Spelunky2::get()->get_CharacterDB().characterNamesStringList(), this); - characterNameCompleter->setCaseSensitivity(Qt::CaseInsensitive); - characterNameCompleter->setFilterMode(Qt::MatchContains); - QObject::connect(characterNameCompleter, static_cast(&QCompleter::activated), this, &ViewCharacterDB::searchFieldCompleterActivated); - mSearchLineEdit->setCompleter(characterNameCompleter); - - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewCharacterDB::label); - topLayout->addWidget(labelButton); - - dynamic_cast(mTabLookup->layout())->addLayout(topLayout); - - mMainTreeView->setEnableChangeHighlighting(false); - mMainTreeView->addMemoryFields(config->typeFields(MemoryFieldType::CharacterDB), "CharacterDB", 0); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &ViewCharacterDB::fieldUpdated); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &ViewCharacterDB::fieldExpanded); - mTabLookup->layout()->addWidget(mMainTreeView); - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainTreeView->updateTableHeader(); - } - - // COMPARE - { - auto topLayout = new QHBoxLayout(); - mCompareFieldComboBox = new QComboBox(this); - mCompareFieldComboBox->addItem(QString::fromStdString(""), QVariant::fromValue(QString::fromStdString(""))); - DB::populateComparisonCombobox(mCompareFieldComboBox, config->typeFields(MemoryFieldType::CharacterDB)); - - QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &ViewCharacterDB::comparisonFieldChosen); - topLayout->addWidget(mCompareFieldComboBox); - - auto groupCheckbox = new QCheckBox("Group by value", this); - QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &ViewCharacterDB::compareGroupByCheckBoxClicked); - topLayout->addWidget(groupCheckbox); - - dynamic_cast(mTabCompare->layout())->addLayout(topLayout); - - mCompareTableWidget = new QTableWidget(Spelunky2::get()->get_CharacterDB().charactersCount(), 3, this); - mCompareTableWidget->setAlternatingRowColors(true); - mCompareTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - mCompareTableWidget->setHorizontalHeaderLabels(QStringList() << "ID" - << "Name" - << "Value"); - mCompareTableWidget->verticalHeader()->setVisible(false); - mCompareTableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - mCompareTableWidget->verticalHeader()->setDefaultSectionSize(20); - mCompareTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - mCompareTableWidget->setColumnWidth(0, 40); - mCompareTableWidget->setColumnWidth(1, 325); - mCompareTableWidget->setColumnWidth(2, 150); - mCompareTableWidget->setItemDelegate(&mHTMLDelegate); - QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &ViewCharacterDB::comparisonCellClicked); - - mCompareTreeWidget = new QTreeWidget(this); - mCompareTreeWidget->setAlternatingRowColors(true); - mCompareTreeWidget->headerItem()->setHidden(true); - mCompareTreeWidget->setHidden(true); - mCompareTreeWidget->setItemDelegate(&mHTMLDelegate); - QObject::connect(mCompareTreeWidget, &QTreeWidget::itemClicked, this, &ViewCharacterDB::groupedComparisonItemClicked); - - mTabCompare->layout()->addWidget(mCompareTableWidget); - mTabCompare->layout()->addWidget(mCompareTreeWidget); - } - - mSearchLineEdit->setVisible(true); - mSearchLineEdit->setFocus(); - mMainTreeView->setVisible(true); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); -} - -void S2Plugin::ViewCharacterDB::closeEvent(QCloseEvent* event) -{ - delete this; + auto& charDB = Spelunky2::get()->get_CharacterDB(); + auto characterNameCompleter = new QCompleter(charDB.characterNamesStringList(), this); + characterNameCompleter->setCaseSensitivity(Qt::CaseInsensitive); + characterNameCompleter->setFilterMode(Qt::MatchContains); + QObject::connect(characterNameCompleter, static_cast(&QCompleter::activated), this, &ViewCharacterDB::searchFieldCompleterActivated); + mSearchLineEdit->setCompleter(characterNameCompleter); + mCompareTableWidget->setRowCount(charDB.charactersCount()); + showID(0); } QSize S2Plugin::ViewCharacterDB::sizeHint() const @@ -139,55 +23,30 @@ QSize S2Plugin::ViewCharacterDB::sizeHint() const return QSize(750, 650); } -QSize S2Plugin::ViewCharacterDB::minimumSizeHint() const -{ - return QSize(150, 150); -} - -void S2Plugin::ViewCharacterDB::searchFieldReturnPressed() -{ - auto text = mSearchLineEdit->text(); - bool isNumeric = false; - auto enteredID = text.toUInt(&isNumeric); - auto& characterDB = Spelunky2::get()->get_CharacterDB(); - if (isNumeric && enteredID < characterDB.charactersCount()) - { - showIndex(enteredID); - } - else - { - uint8_t cID = 0; - for (const auto& cName : characterDB.characterNamesStringList()) - { - if (text == cName) - { - showIndex(cID); - } - cID++; - } - } -} - -void S2Plugin::ViewCharacterDB::searchFieldCompleterActivated(const QString& text) +void S2Plugin::ViewCharacterDB::searchFieldCompleterActivated() { searchFieldReturnPressed(); } -void S2Plugin::ViewCharacterDB::showIndex(uint8_t index) +void S2Plugin::ViewCharacterDB::showID(ID_type id) { - mMainTabWidget->setCurrentWidget(mTabLookup); - auto offset = Spelunky2::get()->get_CharacterDB().addressOfIndex(index); + auto& charDB = Spelunky2::get()->get_CharacterDB(); + if (id >= charDB.charactersCount()) + return; + + switchToLookupTab(); + auto offset = charDB.addressOfIndex(id); mMainTreeView->updateTree(offset); } -void S2Plugin::ViewCharacterDB::label() +void S2Plugin::ViewCharacterDB::label() const { auto model = mMainTreeView->model(); std::string name = "[UNKNOWN_CHARACTER]"; auto& characterDB = Spelunky2::get()->get_CharacterDB(); auto offset = characterDB.addressOfIndex(0); // ptr uintptr_t indexOffset = model->data(model->index(0, gsColField), gsRoleMemoryAddress).toULongLong(); - size_t index = (indexOffset - offset) / characterDB.characterSize(); + uint8_t index = static_cast((indexOffset - offset) / characterDB.characterSize()); if (index < characterDB.charactersCount()) { name = '[' + characterDB.characterNamesStringList()[index].toStdString() + ']'; @@ -195,128 +54,30 @@ void S2Plugin::ViewCharacterDB::label() mMainTreeView->labelAll(name); } -void S2Plugin::ViewCharacterDB::fieldUpdated(const QString& fieldName) +std::optional S2Plugin::ViewCharacterDB::getIDForName(QString name) const { - updateFieldValues(); -} - -void S2Plugin::ViewCharacterDB::fieldExpanded(const QModelIndex& index) -{ - updateFieldValues(); -} - -void S2Plugin::ViewCharacterDB::updateFieldValues() -{ - mMainTreeView->updateTree(); -} - -void S2Plugin::ViewCharacterDB::compareGroupByCheckBoxClicked(int state) -{ - mCompareTableWidget->setHidden(state == Qt::Checked); - mCompareTreeWidget->setHidden(state == Qt::Unchecked); -} - -void S2Plugin::ViewCharacterDB::comparisonFieldChosen(const QString& fieldName) -{ - mCompareTableWidget->clearContents(); - mCompareTreeWidget->clear(); - - auto comboIndex = mCompareFieldComboBox->currentIndex(); - if (comboIndex == 0) - { - return; - } - - populateComparisonTableWidget(); - populateComparisonTreeWidget(); -} - -void S2Plugin::ViewCharacterDB::populateComparisonTableWidget() -{ - mCompareTableWidget->setSortingEnabled(false); - - auto comboboxData = mCompareFieldComboBox->currentData(); - auto& characterDB = Spelunky2::get()->get_CharacterDB(); - - size_t row = 0; - for (auto x = 0; x < characterDB.charactersCount(); ++x) + ID_type count = 0; + for (auto& characterName : Spelunky2::get()->get_CharacterDB().characterNamesStringList()) { - auto item0 = new QTableWidgetItem(QString::asprintf("%03d", x)); - item0->setTextAlignment(Qt::AlignCenter); - mCompareTableWidget->setItem(row, 0, item0); - const auto& name = characterDB.characterNamesStringList().at(x); - mCompareTableWidget->setItem(row, 1, new QTableWidgetItem(QString("%1").arg(name))); - - auto [caption, value] = DB::valueForField(comboboxData, characterDB.addressOfIndex(x)); - auto item = new TableWidgetItemNumeric(caption); - item->setData(Qt::UserRole, value); - mCompareTableWidget->setItem(row, 2, item); + if (characterName == name) + return count; - row++; + ++count; } - mCompareTableWidget->setSortingEnabled(true); - mCompareTableWidget->sortItems(0); + return std::nullopt; } -void S2Plugin::ViewCharacterDB::populateComparisonTreeWidget() +S2Plugin::ID_type S2Plugin::ViewCharacterDB::highestRecordID() const { - mCompareTreeWidget->setSortingEnabled(false); - - auto comboboxData = mCompareFieldComboBox->currentData(); - auto& characterDB = Spelunky2::get()->get_CharacterDB(); - - std::unordered_map rootValues; - std::unordered_map> groupedValues; // valueString -> set - for (uint32_t x = 0; x < characterDB.charactersCount(); ++x) - { - auto [caption, value] = DB::valueForField(comboboxData, characterDB.addressOfIndex(x)); - auto captionStr = caption.toStdString(); - rootValues[captionStr] = value; - - if (groupedValues.count(captionStr) == 0) - { - groupedValues[captionStr] = {x}; - } - else - { - groupedValues[captionStr].insert(x); - } - } - - for (const auto& [groupString, characterIDs] : groupedValues) - { - auto rootItem = new TreeWidgetItemNumeric(nullptr, QString::fromStdString(groupString)); - rootItem->setData(0, Qt::UserRole, rootValues.at(groupString)); - mCompareTreeWidget->insertTopLevelItem(0, rootItem); - for (const auto& characterId : characterIDs) - { - const auto& characterName = characterDB.characterNamesStringList().at(characterId); - auto caption = QString("%1").arg(characterName); - auto childItem = new QTreeWidgetItem(rootItem, QStringList(caption)); - childItem->setData(0, Qt::UserRole, characterId); - mCompareTreeWidget->insertTopLevelItem(0, childItem); - } - } - - mCompareTreeWidget->setSortingEnabled(true); - mCompareTreeWidget->sortItems(0, Qt::AscendingOrder); + return Spelunky2::get()->get_CharacterDB().charactersCount() - 1; } -void S2Plugin::ViewCharacterDB::comparisonCellClicked(int row, int column) +QString S2Plugin::ViewCharacterDB::recordNameForID(ID_type id) const { - if (column == 1) - { - mSearchLineEdit->clear(); - auto clickedID = mCompareTableWidget->item(row, 0)->data(Qt::DisplayRole).toUInt(); - showIndex(clickedID); - } + return Spelunky2::get()->get_CharacterDB().characterNamesStringList()[id]; } -void S2Plugin::ViewCharacterDB::groupedComparisonItemClicked(QTreeWidgetItem* item, int column) +uintptr_t S2Plugin::ViewCharacterDB::addressOfRecordID(ID_type id) const { - if (item->childCount() == 0) - { - mSearchLineEdit->clear(); - showIndex(item->data(0, Qt::UserRole).toUInt()); - } + return Spelunky2::get()->get_CharacterDB().addressOfIndex(id); } diff --git a/src/Views/ViewEntities.cpp b/src/Views/ViewEntities.cpp index f5370a45..5e01e8b5 100644 --- a/src/Views/ViewEntities.cpp +++ b/src/Views/ViewEntities.cpp @@ -1,101 +1,82 @@ -#include +#include "Views/ViewEntities.h" #include "Configuration.h" #include "Data/Entity.h" -#include "Data/EntityDB.h" -#include "Data/State.h" #include "Data/StdMap.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtPlugin.h" #include "Spelunky2.h" -#include "Views/ViewEntities.h" -#include "Views/ViewToolbar.h" #include "pluginmain.h" -#include #include #include -#include +#include +#include -S2Plugin::ViewEntities::ViewEntities(ViewToolbar* toolbar, QWidget* parent) : QWidget(parent), mToolbar(toolbar) +S2Plugin::ViewEntities::ViewEntities(QWidget* parent) : QWidget(parent) { - mMainLayout = new QVBoxLayout(this); + setWindowIcon(getCavemanIcon()); + setWindowTitle("Entities"); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); auto config = Configuration::get(); mLayer0Offset = config->offsetForField(MemoryFieldType::State, "layer0", Spelunky2::get()->get_StatePtr()); mLayer1Offset = config->offsetForField(MemoryFieldType::State, "layer1", Spelunky2::get()->get_StatePtr()); mLayerMapOffset = config->offsetForField(config->typeFieldsOfDefaultStruct("LayerPointer"), "entities_by_mask"); - initializeRefreshAndFilter(); - initializeTreeView(); - setWindowIcon(QIcon(":/icons/caveman.png")); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); - - setWindowTitle("Entities"); - mMainTreeView->setVisible(true); - - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress).disable(gsColComment); - refreshEntities(); - mFilterLineEdit->setFocus(); -} - -void S2Plugin::ViewEntities::initializeTreeView() -{ - mMainTreeView = new TreeViewMemoryFields(mToolbar, this); - mMainTreeView->setEnableChangeHighlighting(false); - - mMainLayout->addWidget(mMainTreeView); -} - -void S2Plugin::ViewEntities::initializeRefreshAndFilter() -{ - auto filterLayout = new QGridLayout(this); + // initializeRefreshAndFilter + { + auto filterLayout = new QGridLayout(); - auto refreshButton = new QPushButton("Refresh", this); - QObject::connect(refreshButton, &QPushButton::clicked, this, &ViewEntities::refreshEntities); - filterLayout->addWidget(refreshButton, 0, 0); + auto refreshButton = new QPushButton("Refresh", this); + QObject::connect(refreshButton, &QPushButton::clicked, this, &ViewEntities::refreshEntities); + filterLayout->addWidget(refreshButton, 0, 0); - auto label = new QLabel("Filter:", this); - filterLayout->addWidget(label, 0, 1); + auto label = new QLabel("Filter:", this); + filterLayout->addWidget(label, 0, 1); - mFilterLineEdit = new QLineEdit(this); - mFilterLineEdit->setPlaceholderText("Search for UID (dec or hex starting with 0x) or (part of) the entity name"); - QObject::connect(mFilterLineEdit, &QLineEdit::textChanged, this, &ViewEntities::refreshEntities); - filterLayout->addWidget(mFilterLineEdit, 0, 2, 1, 6); + mFilterLineEdit = new QLineEdit(this); + mFilterLineEdit->setPlaceholderText("Search for UID (dec or hex starting with 0x) or (part of) the entity name"); + QObject::connect(mFilterLineEdit, &QLineEdit::textChanged, this, &ViewEntities::refreshEntities); + filterLayout->addWidget(mFilterLineEdit, 0, 2, 1, 6); - mCheckboxLayer0 = new QCheckBox("Front layer (0)", this); - mCheckboxLayer0->setChecked(true); - QObject::connect(mCheckboxLayer0, &QCheckBox::stateChanged, this, &ViewEntities::refreshEntities); - filterLayout->addWidget(mCheckboxLayer0, 2, 2); + mCheckboxLayer0 = new QCheckBox("Front layer (0)", this); + mCheckboxLayer0->setChecked(true); + QObject::connect(mCheckboxLayer0, &QCheckBox::stateChanged, this, &ViewEntities::refreshEntities); + filterLayout->addWidget(mCheckboxLayer0, 2, 2); - mCheckboxLayer1 = new QCheckBox("Back layer (0)", this); - QObject::connect(mCheckboxLayer1, &QCheckBox::stateChanged, this, &ViewEntities::refreshEntities); - filterLayout->addWidget(mCheckboxLayer1, 2, 3); + mCheckboxLayer1 = new QCheckBox("Back layer (0)", this); + QObject::connect(mCheckboxLayer1, &QCheckBox::stateChanged, this, &ViewEntities::refreshEntities); + filterLayout->addWidget(mCheckboxLayer1, 2, 3); - int row = 3; - int col = 2; - for (auto& checkbox : mCheckbox) - { - checkbox.mCheckbox = new QCheckBox(checkbox.name + " (0)", this); - QObject::connect(checkbox.mCheckbox, &QCheckBox::stateChanged, this, &ViewEntities::refreshEntities); - filterLayout->addWidget(checkbox.mCheckbox, row, col); - ++col; - if (col == 9) + int row = 3; + int col = 2; + for (auto& checkbox : mCheckbox) { - col = 2; - ++row; + checkbox.mCheckbox = new QCheckBox(checkbox.name + " (0)", this); + QObject::connect(checkbox.mCheckbox, &QCheckBox::stateChanged, this, &ViewEntities::refreshEntities); + filterLayout->addWidget(checkbox.mCheckbox, row, col); + ++col; + if (col == 9) + { + col = 2; + ++row; + } } + + auto horLayout = new QHBoxLayout(); + horLayout->addLayout(filterLayout); + horLayout->addStretch(); + mainLayout->addLayout(horLayout); } + mMainTreeView = new TreeViewMemoryFields(this); + mMainTreeView->setEnableChangeHighlighting(false); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress).disable(gsColComment); - auto horLayout = new QHBoxLayout(this); - horLayout->addLayout(filterLayout); - horLayout->addStretch(); - mMainLayout->addLayout(horLayout); -} + mainLayout->addWidget(mMainTreeView); -void S2Plugin::ViewEntities::closeEvent(QCloseEvent* event) -{ - delete this; + refreshEntities(); + mFilterLineEdit->setFocus(); } void S2Plugin::ViewEntities::refreshEntities() @@ -221,11 +202,9 @@ void S2Plugin::ViewEntities::refreshEntities() checkbox.mCheckbox->setText(QString(checkbox.name + " (%1)").arg(field_count)); } setWindowTitle(QString("%1 Entities").arg(totalEntities)); - mMainTreeView->updateTableHeader(); mMainTreeView->setColumnWidth(gsColField, 145); mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); mMainTreeView->setColumnWidth(gsColType, 100); mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(); diff --git a/src/Views/ViewEntity.cpp b/src/Views/ViewEntity.cpp index 749d58a6..ccba2aa6 100644 --- a/src/Views/ViewEntity.cpp +++ b/src/Views/ViewEntity.cpp @@ -1,42 +1,45 @@ #include "Views/ViewEntity.h" + #include "Configuration.h" #include "Data/CPPGenerator.h" #include "Data/Entity.h" #include "QtHelpers/CPPSyntaxHighlighter.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetAutorefresh.h" #include "QtHelpers/WidgetMemoryView.h" #include "QtHelpers/WidgetSpelunkyLevel.h" -#include "Views/ViewToolbar.h" +#include "QtPlugin.h" #include "pluginmain.h" -#include +#include +#include #include +#include +#include #include +#include +#include +#include +#include #include +#include -S2Plugin::ViewEntity::ViewEntity(size_t entityOffset, ViewToolbar* toolbar, QWidget* parent) : QWidget(parent), mToolbar(toolbar), mEntityPtr(entityOffset) +enum TABS { - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); + FIELDS = 0, + MEMORY = 1, + LEVEL = 2, + CPP = 3, +}; +S2Plugin::ViewEntity::ViewEntity(size_t entityOffset, QWidget* parent) : QWidget(parent), mEntityPtr(entityOffset) +{ + setWindowIcon(getCavemanIcon()); setWindowTitle(QString::asprintf("Entity %s 0x%016llX", Entity{mEntityPtr}.entityTypeName().c_str(), entityOffset)); - mMainTreeView->setVisible(true); - mMainTreeView->setColumnWidth(gsColField, 175); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); + initializeUI(); updateMemoryViewOffsetAndSize(); - mSpelunkyLevel->paintFloor(QColor(160, 160, 160)); - mSpelunkyLevel->paintEntity(mEntityPtr, QColor(222, 52, 235)); - mSpelunkyLevel->update(); - toggleAutoRefresh(Qt::Checked); auto entityClassName = Entity{mEntityPtr}.entityClassName(); - // the combobox is set as Entity by default, so we have to manually call interpretAsChanged if (entityClassName == "Entity") interpretAsChanged(QString::fromStdString(entityClassName)); @@ -46,64 +49,25 @@ S2Plugin::ViewEntity::ViewEntity(size_t entityOffset, ViewToolbar* toolbar, QWid void S2Plugin::ViewEntity::initializeUI() { - mMainLayout = new QVBoxLayout(); - mTopLayout = new QHBoxLayout(); - mMainLayout->addLayout(mTopLayout); - - mMainTabWidget = new QTabWidget(this); - mMainTabWidget->setDocumentMode(false); - QObject::connect(mMainTabWidget, &QTabWidget::currentChanged, this, &ViewEntity::tabChanged); - mMainLayout->addWidget(mMainTabWidget); - - mTabFields = new QWidget(); - mTabMemory = new QWidget(); - mTabLevel = new QWidget(); - mTabCPP = new QWidget(); - mTabFields->setLayout(new QVBoxLayout(mTabFields)); - mTabFields->layout()->setMargin(0); - mTabMemory->setLayout(new QHBoxLayout(mTabMemory)); - mTabMemory->layout()->setMargin(0); - mTabLevel->setLayout(new QVBoxLayout(mTabLevel)); - mTabLevel->layout()->setMargin(0); - mTabCPP->setLayout(new QVBoxLayout(mTabCPP)); - mTabCPP->layout()->setMargin(0); - - mMainTabWidget->addTab(mTabFields, "Fields"); - mMainTabWidget->addTab(mTabMemory, "Memory"); - mMainTabWidget->addTab(mTabLevel, "Level"); - mMainTabWidget->addTab(mTabCPP, "C++"); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto topLayout = new QHBoxLayout(); + mainLayout->addLayout(topLayout); // TOP LAYOUT - mRefreshButton = new QPushButton("Refresh", this); - mTopLayout->addWidget(mRefreshButton); - QObject::connect(mRefreshButton, &QPushButton::clicked, this, &ViewEntity::refreshEntity); - - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewEntity::autoRefreshTimerTrigger); - - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - mTopLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewEntity::toggleAutoRefresh); + auto autoRefresh = new WidgetAutorefresh(100, this); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, this, &ViewEntity::refreshEntity); + topLayout->addWidget(autoRefresh); - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("100"); - mTopLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewEntity::autoRefreshIntervalChanged); - - mTopLayout->addWidget(new QLabel("milliseconds", this)); - - mTopLayout->addStretch(); + topLayout->addStretch(); auto labelButton = new QPushButton("Label", this); QObject::connect(labelButton, &QPushButton::clicked, this, &ViewEntity::label); - mTopLayout->addWidget(labelButton); + topLayout->addWidget(labelButton); - mTopLayout->addWidget(new QLabel("Interpret as:", this)); + topLayout->addWidget(new QLabel("Interpret as:", this)); mInterpretAsComboBox = new QComboBox(this); - // mInterpretAsComboBox->addItem(""); + mInterpretAsComboBox->addItem("Reset"); mInterpretAsComboBox->addItem("Entity"); mInterpretAsComboBox->insertSeparator(2); std::vector classNames; @@ -117,109 +81,104 @@ void S2Plugin::ViewEntity::initializeUI() mInterpretAsComboBox->addItem(QString::fromStdString(className)); } QObject::connect(mInterpretAsComboBox, &QComboBox::currentTextChanged, this, &ViewEntity::interpretAsChanged); - mTopLayout->addWidget(mInterpretAsComboBox); + topLayout->addWidget(mInterpretAsComboBox); - // TAB FIELDS - mMainTreeView = new TreeViewMemoryFields(mToolbar, this); - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->setVisible(false); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainTreeView->updateTableHeader(); - mMainTreeView->setDragDropMode(QAbstractItemView::DragDropMode::DragDrop); - mMainTreeView->setAcceptDrops(true); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::entityOffsetDropped, this, &ViewEntity::entityOffsetDropped); - mTabFields->layout()->addWidget(mMainTreeView); - - // TAB MEMORY - auto scroll = new QScrollArea(mTabMemory); - mMemoryView = new WidgetMemoryView(scroll); - scroll->setStyleSheet("background-color: #fff;"); - scroll->setWidget(mMemoryView); - scroll->setVisible(true); - mTabMemory->layout()->addWidget(scroll); - - mMemoryComparisonScrollArea = new QScrollArea(mTabMemory); - mMemoryComparisonView = new WidgetMemoryView(mMemoryComparisonScrollArea); - mMemoryComparisonScrollArea->setStyleSheet("background-color: #fff;"); - mMemoryComparisonScrollArea->setWidget(mMemoryComparisonView); - mMemoryComparisonScrollArea->setVisible(true); - mTabMemory->layout()->addWidget(mMemoryComparisonScrollArea); - mMemoryComparisonScrollArea->setVisible(false); + // TABS + mMainTabWidget = new QTabWidget(this); + mMainTabWidget->setDocumentMode(false); + QObject::connect(mMainTabWidget, &QTabWidget::currentChanged, this, &ViewEntity::tabChanged); + mainLayout->addWidget(mMainTabWidget); - // TAB LEVEL - scroll = new QScrollArea(mTabLevel); - mSpelunkyLevel = new WidgetSpelunkyLevel(mEntityPtr, scroll); - scroll->setStyleSheet("background-color: #fff;"); - scroll->setWidget(mSpelunkyLevel); - scroll->setVisible(true); - mTabLevel->layout()->addWidget(scroll); + mMainTreeView = new TreeViewMemoryFields(); + auto tabMemory = new QWidget(); + auto tabLevel = new QScrollArea(); + mCPPTextEdit = new QTextEdit(); - // TAB CPP - mCPPTextEdit = new QTextEdit(this); - mCPPTextEdit->setReadOnly(true); - auto font = QFont("Courier", 10); - font.setFixedPitch(true); - font.setStyleHint(QFont::Monospace); - auto fontMetrics = QFontMetrics(font); - mCPPTextEdit->setFont(font); - mCPPTextEdit->setTabStopWidth(4 * fontMetrics.width(' ')); - mCPPTextEdit->setLineWrapMode(QTextEdit::LineWrapMode::NoWrap); - QPalette palette = mCPPTextEdit->palette(); - palette.setColor(QPalette::Base, QColor("#1E1E1E")); - palette.setColor(QPalette::Text, QColor("#D4D4D4")); - mCPPTextEdit->setPalette(palette); - mCPPTextEdit->document()->setDocumentMargin(10); - mCPPSyntaxHighlighter = new CPPSyntaxHighlighter(mCPPTextEdit->document()); - - mTabCPP->layout()->addWidget(mCPPTextEdit); -} + tabMemory->setLayout(new QHBoxLayout()); + tabMemory->layout()->setMargin(0); -void S2Plugin::ViewEntity::closeEvent(QCloseEvent* event) -{ - delete this; -} + mMainTabWidget->addTab(mMainTreeView, "Fields"); + mMainTabWidget->addTab(tabMemory, "Memory"); + mMainTabWidget->addTab(tabLevel, "Level"); + mMainTabWidget->addTab(mCPPTextEdit, "C++"); -void S2Plugin::ViewEntity::refreshEntity() -{ - mMainTreeView->updateTree(); - if (mMainTabWidget->currentWidget() == mTabMemory) + // TAB FIELDS { - mMemoryView->update(); - mMemoryComparisonView->update(); - updateComparedMemoryViewHighlights(); + mMainTreeView->setColumnWidth(gsColValue, 250); + mMainTreeView->setColumnWidth(gsColField, 175); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); + mMainTreeView->updateTableHeader(); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::offsetDropped, this, &ViewEntity::entityOffsetDropped); } - else if (mMainTabWidget->currentWidget() == mTabLevel) + // TAB MEMORY { - mSpelunkyLevel->update(); + mMemoryScrollArea = new QScrollArea(tabMemory); + mMemoryView = new WidgetMemoryView(mMemoryScrollArea); + mMemoryScrollArea->setStyleSheet("background-color: #fff;"); + mMemoryScrollArea->setWidget(mMemoryView); + tabMemory->layout()->addWidget(mMemoryScrollArea); + + mMemoryComparisonScrollArea = new QScrollArea(tabMemory); + mMemoryComparisonView = new WidgetMemoryView(mMemoryComparisonScrollArea); + mMemoryComparisonScrollArea->setStyleSheet("background-color: #fff;"); + mMemoryComparisonScrollArea->setWidget(mMemoryComparisonView); + tabMemory->layout()->addWidget(mMemoryComparisonScrollArea); + mMemoryComparisonScrollArea->setVisible(false); + + connect(mMemoryScrollArea->horizontalScrollBar(), &QScrollBar::valueChanged, mMemoryComparisonScrollArea->horizontalScrollBar(), &QScrollBar::setValue); + connect(mMemoryScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, mMemoryComparisonScrollArea->verticalScrollBar(), &QScrollBar::setValue); + connect(mMemoryComparisonScrollArea->horizontalScrollBar(), &QScrollBar::valueChanged, mMemoryScrollArea->horizontalScrollBar(), &QScrollBar::setValue); + connect(mMemoryComparisonScrollArea->verticalScrollBar(), &QScrollBar::valueChanged, mMemoryScrollArea->verticalScrollBar(), &QScrollBar::setValue); } -} - -void S2Plugin::ViewEntity::toggleAutoRefresh(int newState) -{ - if (newState == Qt::Unchecked) + // TAB LEVEL { - mAutoRefreshTimer->stop(); - mRefreshButton->setEnabled(true); + mSpelunkyLevel = new WidgetSpelunkyLevel(mEntityPtr, tabLevel); + mSpelunkyLevel->paintFloor(QColor(160, 160, 160)); + mSpelunkyLevel->paintEntity(mEntityPtr, QColor(222, 52, 235)); + // to many entities + // mSpelunkyLevel->paintEntityMask(0x4000, QColor(255, 87, 6)); // lava + // mSpelunkyLevel->paintEntityMask(0x2000, QColor(6, 213, 249)); // water + mSpelunkyLevel->paintEntityMask(0x80, QColor(85, 170, 170)); // active floors + tabLevel->setStyleSheet("background-color: #fff;"); + tabLevel->setWidget(mSpelunkyLevel); } - else + // TAB CPP { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshButton->setEnabled(false); + mCPPTextEdit->setReadOnly(true); + auto font = QFont("Courier", 10); + font.setFixedPitch(true); + font.setStyleHint(QFont::Monospace); + auto fontMetrics = QFontMetrics(font); + mCPPTextEdit->setFont(font); + mCPPTextEdit->setTabStopWidth(4 * fontMetrics.width(' ')); + mCPPTextEdit->setLineWrapMode(QTextEdit::LineWrapMode::NoWrap); + QPalette palette = mCPPTextEdit->palette(); + palette.setColor(QPalette::Base, QColor("#1E1E1E")); + palette.setColor(QPalette::Text, QColor("#D4D4D4")); + mCPPTextEdit->setPalette(palette); + mCPPTextEdit->document()->setDocumentMargin(10); + mCPPSyntaxHighlighter = new CPPSyntaxHighlighter(mCPPTextEdit->document()); } + autoRefresh->toggleAutoRefresh(true); } -void S2Plugin::ViewEntity::autoRefreshIntervalChanged(const QString& text) +void S2Plugin::ViewEntity::refreshEntity() { - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) + mMainTreeView->updateTree(); + if (mMainTabWidget->currentIndex() == TABS::MEMORY) { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); + mMemoryView->updateMemory(); + mMemoryComparisonView->updateMemory(); + updateComparedMemoryViewHighlights(); + } + else if (mMainTabWidget->currentIndex() == TABS::LEVEL) + { + mSpelunkyLevel->updateLevel(); } -} - -void S2Plugin::ViewEntity::autoRefreshTimerTrigger() -{ - refreshEntity(); } QSize S2Plugin::ViewEntity::sizeHint() const @@ -236,17 +195,20 @@ void S2Plugin::ViewEntity::interpretAsChanged(const QString& classType) { if (!classType.isEmpty()) { - auto textStr = classType.toStdString(); + Entity entity{mEntityPtr}; + if (classType == "Reset") + { + mInterpretAsComboBox->setCurrentText(QString::fromStdString(entity.entityClassName())); + return; + } + static const auto colors = {QColor(255, 214, 222), QColor(232, 206, 227), QColor(199, 186, 225), QColor(187, 211, 236), QColor(236, 228, 197), QColor(193, 219, 204)}; auto config = Configuration::get(); - - Entity entity{mEntityPtr}; auto hierarchy = Entity::classHierarchy(classType.toStdString()); mMainTreeView->clear(); mMemoryView->clearHighlights(); mMainTreeView->updateTableHeader(); - uint8_t counter = 0; size_t delta = 0; uint8_t colorIndex = 0; auto recursiveHighlight = [&](std::string prefix, const std::vector& fields, auto&& self) -> void @@ -268,7 +230,7 @@ void S2Plugin::ViewEntity::interpretAsChanged(const QString& classType) } } - auto size = field.get_size(); + int size = static_cast(field.get_size()); if (size == 0) continue; mMemoryView->addHighlightedField(prefix + field.name, mEntityPtr + delta, size, *(colors.begin() + colorIndex)); @@ -285,17 +247,13 @@ void S2Plugin::ViewEntity::interpretAsChanged(const QString& classType) headerField.name = "" + *it + ""; headerField.type = MemoryFieldType::EntitySubclass; headerField.jsonName = *it; - auto item = mMainTreeView->addMemoryField(headerField, *it, mEntityPtr + delta, delta); - if (++counter == hierarchy.size()) // expand last subclass - { - mMainTreeView->expandItem(item); - } + mMainTreeView->addMemoryField(headerField, *it, mEntityPtr + delta, delta); // highlights fields in memory view, also updates delta recursiveHighlight(*it + ".", config->typeFieldsOfEntitySubclass(*it), recursiveHighlight); } - // mInterpretAsComboBox->setCurrentText(""); mMainTreeView->updateTree(0, 0, true); + mMainTreeView->expandLast(); mEntitySize = delta; updateMemoryViewOffsetAndSize(); } @@ -303,10 +261,7 @@ void S2Plugin::ViewEntity::interpretAsChanged(const QString& classType) void S2Plugin::ViewEntity::updateMemoryViewOffsetAndSize() { - constexpr size_t smallEntityBucket = 0xD0; - constexpr size_t bigEntityBucket = 0x188; - - size_t bytesShown = mEntitySize > smallEntityBucket ? bigEntityBucket : smallEntityBucket; + size_t bytesShown = mEntitySize > gSmallEntityBucket ? gBigEntityBucket : gSmallEntityBucket; mMemoryView->setOffsetAndSize(mEntityPtr, bytesShown); mMemoryComparisonView->setOffsetAndSize(mComparisonEntityPtr, bytesShown); @@ -328,7 +283,7 @@ void S2Plugin::ViewEntity::updateComparedMemoryViewHighlights() auto highlightFields = [&](QStandardItem* parrent, auto&& self) -> void { - for (size_t idx = 0; idx < parrent->rowCount(); ++idx) + for (int idx = 0; idx < parrent->rowCount(); ++idx) { auto field = parrent->child(idx, gsColField); type = field->data(gsRoleType).value(); @@ -342,7 +297,7 @@ void S2Plugin::ViewEntity::updateComparedMemoryViewHighlights() size_t delta = deltaField->data(gsRoleRawValue).toULongLong(); // get the size by the difference in offset delta // [Known Issue]: this will fail in getting the correct size if there is a skip element between fields - size_t size = delta - offset; + int size = static_cast(delta - offset); if (size != 0) { mMemoryComparisonView->addHighlightedField(std::move(fieldName), mComparisonEntityPtr + offset, size, std::move(color)); @@ -353,18 +308,18 @@ void S2Plugin::ViewEntity::updateComparedMemoryViewHighlights() } }; - for (size_t idx = 0; idx < root->rowCount(); ++idx) + for (int idx = 0; idx < root->rowCount(); ++idx) { QStandardItem* currentClass = root->child(idx, gsColField); if (currentClass->data(gsRoleType).value() != MemoryFieldType::EntitySubclass) { - displayError("Error in `updateComparedMemoryViewHighlights`, found non EntitySubclass member in main tree"); + dprintf("Error in `updateComparedMemoryViewHighlights`, found non EntitySubclass member in main tree"); return; } highlightFields(currentClass, highlightFields); } // update last element - size_t size = Configuration::getBuiltInTypeSize(type); + int size = static_cast(Configuration::getBuiltInTypeSize(type)); if (size != 0) { mMemoryComparisonView->addHighlightedField(std::move(fieldName), mComparisonEntityPtr + offset, size, std::move(color)); @@ -378,7 +333,7 @@ void S2Plugin::ViewEntity::label() mMainTreeView->labelAll(ss.str()); } -void S2Plugin::ViewEntity::entityOffsetDropped(size_t entityOffset) +void S2Plugin::ViewEntity::entityOffsetDropped(uintptr_t entityOffset) { if (mComparisonEntityPtr != 0) { @@ -386,17 +341,15 @@ void S2Plugin::ViewEntity::entityOffsetDropped(size_t entityOffset) } mComparisonEntityPtr = entityOffset; - mMainTreeView->activeColumns.enable(gsColComparisonValue).enable(gsColComparisonValueHex); - mMainTreeView->setColumnHidden(gsColComparisonValue, false); - mMainTreeView->setColumnHidden(gsColComparisonValueHex, false); + mSpelunkyLevel->paintEntity(entityOffset, QColor(232, 134, 30)); mMemoryComparisonScrollArea->setVisible(true); - mMainTreeView->updateTree(0, mComparisonEntityPtr); updateMemoryViewOffsetAndSize(); + mMemoryScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); } -void S2Plugin::ViewEntity::tabChanged(int index) +void S2Plugin::ViewEntity::tabChanged() { - if (mMainTabWidget->currentWidget() == mTabCPP) + if (mMainTabWidget->currentIndex() == TABS::CPP) { mCPPSyntaxHighlighter->clearRules(); CPPGenerator g{}; diff --git a/src/Views/ViewEntityDB.cpp b/src/Views/ViewEntityDB.cpp index 5df54062..d9825b69 100644 --- a/src/Views/ViewEntityDB.cpp +++ b/src/Views/ViewEntityDB.cpp @@ -1,140 +1,23 @@ #include "Views/ViewEntityDB.h" + #include "Configuration.h" #include "Data/EntityDB.h" -#include "QtHelpers/DatabaseHelper.h" -#include "QtHelpers/TableWidgetItemNumeric.h" #include "QtHelpers/TreeViewMemoryFields.h" -#include "QtHelpers/TreeWidgetItemNumeric.h" +#include "QtPlugin.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include #include -#include -#include -#include - -S2Plugin::ViewEntityDB::ViewEntityDB(ViewToolbar* toolbar, uint32_t id, QWidget* parent) : QWidget(parent) -{ - mMainTreeView = new TreeViewMemoryFields(toolbar, this); - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); - setWindowTitle(QString("Entity DB (%1 entities)").arg(Configuration::get()->entityList().highestID())); - showID(id); -} -void S2Plugin::ViewEntityDB::initializeUI() +S2Plugin::ViewEntityDB::ViewEntityDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::EntityDB, parent) { - auto mainLayout = new QVBoxLayout(); - mainLayout->setMargin(5); - setLayout(mainLayout); - - mMainTabWidget = new QTabWidget(this); - mMainTabWidget->setDocumentMode(false); - mainLayout->addWidget(mMainTabWidget); - - mTabLookup = new QWidget(); - mTabCompare = new QWidget(); - mTabLookup->setLayout(new QVBoxLayout()); - mTabLookup->layout()->setMargin(10); - mTabLookup->setObjectName("lookupwidget"); - mTabLookup->setStyleSheet("QWidget#lookupwidget {border: 1px solid #999;}"); - mTabCompare->setLayout(new QVBoxLayout()); - mTabCompare->layout()->setMargin(10); - mTabCompare->setObjectName("comparewidget"); - mTabCompare->setStyleSheet("QWidget#comparewidget {border: 1px solid #999;}"); - - mMainTabWidget->addTab(mTabLookup, "Lookup"); - mMainTabWidget->addTab(mTabCompare, "Compare"); - auto config = Configuration::get(); - - // LOOKUP - { - auto topLayout = new QHBoxLayout(); - - mSearchLineEdit = new QLineEdit(this); - mSearchLineEdit->setPlaceholderText("Search"); - topLayout->addWidget(mSearchLineEdit); - QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &ViewEntityDB::searchFieldReturnPressed); - mSearchLineEdit->setVisible(false); - auto entityNameCompleter = new QCompleter(config->entityList().names(), this); - entityNameCompleter->setCaseSensitivity(Qt::CaseInsensitive); - entityNameCompleter->setFilterMode(Qt::MatchContains); - QObject::connect(entityNameCompleter, static_cast(&QCompleter::activated), this, &ViewEntityDB::searchFieldCompleterActivated); - mSearchLineEdit->setCompleter(entityNameCompleter); - - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewEntityDB::label); - topLayout->addWidget(labelButton); - - dynamic_cast(mTabLookup->layout())->addLayout(topLayout); - - mMainTreeView->setEnableChangeHighlighting(false); - mMainTreeView->addMemoryFields(config->typeFields(MemoryFieldType::EntityDB), "EntityDB", 0); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &ViewEntityDB::fieldUpdated); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &ViewEntityDB::fieldExpanded); - mTabLookup->layout()->addWidget(mMainTreeView); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainTreeView->updateTableHeader(); - } - - // COMPARE - { - auto topLayout = new QHBoxLayout(); - mCompareFieldComboBox = new QComboBox(this); - mCompareFieldComboBox->addItem(QString::fromStdString(""), QVariant::fromValue(QString::fromStdString(""))); - DB::populateComparisonCombobox(mCompareFieldComboBox, config->typeFields(MemoryFieldType::EntityDB)); - - QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &ViewEntityDB::comparisonFieldChosen); - topLayout->addWidget(mCompareFieldComboBox); - - auto groupCheckbox = new QCheckBox("Group by value", this); - QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &ViewEntityDB::compareGroupByCheckBoxClicked); - topLayout->addWidget(groupCheckbox); - - dynamic_cast(mTabCompare->layout())->addLayout(topLayout); - - mCompareTableWidget = new QTableWidget(config->entityList().count(), 3, this); - mCompareTableWidget->setAlternatingRowColors(true); - mCompareTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - mCompareTableWidget->setHorizontalHeaderLabels(QStringList() << "ID" - << "Name" - << "Value"); - mCompareTableWidget->verticalHeader()->setVisible(false); - mCompareTableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - mCompareTableWidget->verticalHeader()->setDefaultSectionSize(20); - mCompareTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - mCompareTableWidget->setColumnWidth(0, 40); - mCompareTableWidget->setColumnWidth(1, 325); - mCompareTableWidget->setColumnWidth(2, 150); - mCompareTableWidget->setItemDelegate(&mHTMLDelegate); - QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &ViewEntityDB::comparisonCellClicked); - - mCompareTreeWidget = new QTreeWidget(this); - mCompareTreeWidget->setAlternatingRowColors(true); - mCompareTreeWidget->headerItem()->setHidden(true); - mCompareTreeWidget->setHidden(true); - mCompareTreeWidget->setItemDelegate(&mHTMLDelegate); - QObject::connect(mCompareTreeWidget, &QTreeWidget::itemClicked, this, &ViewEntityDB::groupedComparisonItemClicked); - - mTabCompare->layout()->addWidget(mCompareTableWidget); - mTabCompare->layout()->addWidget(mCompareTreeWidget); - } - - mSearchLineEdit->setVisible(true); - mSearchLineEdit->setFocus(); - mMainTreeView->setVisible(true); -} - -void S2Plugin::ViewEntityDB::closeEvent(QCloseEvent* event) -{ - delete this; + setWindowTitle(QString("Entity DB (%1 entities)").arg(config->entityList().count())); + auto entityNameCompleter = new QCompleter(config->entityList().names(), this); + entityNameCompleter->setCaseSensitivity(Qt::CaseInsensitive); + entityNameCompleter->setFilterMode(Qt::MatchContains); + QObject::connect(entityNameCompleter, static_cast(&QCompleter::activated), this, &ViewEntityDB::searchFieldCompleterActivated); + mSearchLineEdit->setCompleter(entityNameCompleter); + mCompareTableWidget->setRowCount(static_cast(config->entityList().count())); + showID(1); } QSize S2Plugin::ViewEntityDB::sizeHint() const @@ -142,188 +25,63 @@ QSize S2Plugin::ViewEntityDB::sizeHint() const return QSize(750, 1050); } -QSize S2Plugin::ViewEntityDB::minimumSizeHint() const +void S2Plugin::ViewEntityDB::searchFieldCompleterActivated() { - return QSize(150, 150); + searchFieldReturnPressed(); } -void S2Plugin::ViewEntityDB::searchFieldReturnPressed() +void S2Plugin::ViewEntityDB::showRAW(uintptr_t address) { - auto text = mSearchLineEdit->text(); - bool isNumeric = false; - auto enteredID = text.toUInt(&isNumeric); - auto& entityList = Configuration::get()->entityList(); - - if (isNumeric && enteredID <= entityList.highestID()) - { - showID(enteredID); - } - else - { - auto entityID = entityList.idForName(text.toStdString()); - if (entityID != 0) - { - showID(entityID); - } - } + switchToLookupTab(); + mMainTreeView->updateTree(address); } -void S2Plugin::ViewEntityDB::searchFieldCompleterActivated(const QString& text) +void S2Plugin::ViewEntityDB::showID(ID_type id) { - searchFieldReturnPressed(); -} + if (id > Configuration::get()->entityList().highestID()) + return; -void S2Plugin::ViewEntityDB::showID(uint32_t id) -{ - mMainTabWidget->setCurrentWidget(mTabLookup); + switchToLookupTab(); // id == 0 is valid, but not used as of right now - mMainTreeView->updateTree(Spelunky2::get()->get_EntityDB().addressOfIndex(id), 0); + mMainTreeView->updateTree(Spelunky2::get()->get_EntityDB().addressOfIndex(id)); } -void S2Plugin::ViewEntityDB::label() +void S2Plugin::ViewEntityDB::label() const { auto model = mMainTreeView->model(); auto& entityDB = Spelunky2::get()->get_EntityDB(); auto offset = entityDB.addressOfIndex(0); // ptr uintptr_t indexOffset = model->data(model->index(0, gsColField), gsRoleMemoryAddress).toULongLong(); - size_t index = (indexOffset - offset) / entityDB.entitySize(); + uint32_t index = static_cast((indexOffset - offset) / entityDB.entitySize()); std::string name = '[' + Configuration::get()->entityList().nameForID(index) + ']'; mMainTreeView->labelAll(name); } -void S2Plugin::ViewEntityDB::fieldUpdated(const QString& fieldName) -{ - updateFieldValues(); -} - -void S2Plugin::ViewEntityDB::fieldExpanded(const QModelIndex& index) -{ - updateFieldValues(); -} - -void S2Plugin::ViewEntityDB::updateFieldValues() +S2Plugin::ID_type S2Plugin::ViewEntityDB::highestRecordID() const { - mMainTreeView->updateTree(); -} - -void S2Plugin::ViewEntityDB::compareGroupByCheckBoxClicked(int state) -{ - mCompareTableWidget->setHidden(state == Qt::Checked); - mCompareTreeWidget->setHidden(state == Qt::Unchecked); -} - -void S2Plugin::ViewEntityDB::comparisonFieldChosen(const QString& fieldName) -{ - mCompareTableWidget->clearContents(); - mCompareTreeWidget->clear(); - - auto comboIndex = mCompareFieldComboBox->currentIndex(); - if (comboIndex == 0) - { - return; - } - - populateComparisonTableWidget(); - populateComparisonTreeWidget(); + return Configuration::get()->entityList().highestID(); } -void S2Plugin::ViewEntityDB::populateComparisonTableWidget() +bool S2Plugin::ViewEntityDB::isValidRecordID(ID_type id) const { - mCompareTableWidget->setSortingEnabled(false); - - auto comboboxData = mCompareFieldComboBox->currentData(); - auto& entityDB = Spelunky2::get()->get_EntityDB(); - auto& entityList = Configuration::get()->entityList(); - - size_t row = 0; - for (uint x = 1; x <= entityList.highestID(); ++x) - { - if (!entityList.isValidID(x)) - { - continue; - } - - auto item0 = new QTableWidgetItem(QString::asprintf("%03d", x)); - item0->setTextAlignment(Qt::AlignCenter); - mCompareTableWidget->setItem(row, 0, item0); - mCompareTableWidget->setItem(row, 1, new QTableWidgetItem(QString("%1").arg(QString::fromStdString(entityList.nameForID(x))))); - - auto [caption, value] = DB::valueForField(comboboxData, entityDB.addressOfIndex(x)); - auto item = new TableWidgetItemNumeric(caption); - item->setData(Qt::UserRole, value); - mCompareTableWidget->setItem(row, 2, item); - - row++; - } - mCompareTableWidget->setSortingEnabled(true); - mCompareTableWidget->sortItems(0); + return Configuration::get()->entityList().isValidID(id); } -void S2Plugin::ViewEntityDB::populateComparisonTreeWidget() +std::optional S2Plugin::ViewEntityDB::getIDForName(QString name) const { - mCompareTreeWidget->setSortingEnabled(false); - - auto comboboxData = mCompareFieldComboBox->currentData(); - auto& entityDB = Spelunky2::get()->get_EntityDB(); - auto& entityList = Configuration::get()->entityList(); - - std::unordered_map rootValues; - std::unordered_map> groupedValues; // valueString -> set - for (uint32_t x = 1; x <= entityList.highestID(); ++x) - { - if (!entityList.isValidID(x)) - { - continue; - } - - auto [caption, value] = DB::valueForField(comboboxData, entityDB.addressOfIndex(x)); - auto captionStr = caption.toStdString(); - rootValues[captionStr] = value; - - if (groupedValues.count(captionStr) == 0) - { - groupedValues[captionStr] = {x}; - } - else - { - groupedValues[captionStr].insert(x); - } - } - - for (const auto& [groupString, entityIds] : groupedValues) - { - auto rootItem = new TreeWidgetItemNumeric(nullptr, QString::fromStdString(groupString)); - rootItem->setData(0, Qt::UserRole, rootValues.at(groupString)); - mCompareTreeWidget->insertTopLevelItem(0, rootItem); - for (const auto& entityId : entityIds) - { - auto entityName = entityList.nameForID(entityId); - auto caption = QString("%1").arg(QString::fromStdString(entityName)); - auto childItem = new QTreeWidgetItem(rootItem, QStringList(caption)); - childItem->setData(0, Qt::UserRole, entityId); - mCompareTreeWidget->insertTopLevelItem(0, childItem); - } - } + auto id = Configuration::get()->entityList().idForName(name.toStdString()); + if (id == 0) + return std::nullopt; - mCompareTreeWidget->setSortingEnabled(true); - mCompareTreeWidget->sortItems(0, Qt::AscendingOrder); + return id; } -void S2Plugin::ViewEntityDB::comparisonCellClicked(int row, int column) +QString S2Plugin::ViewEntityDB::recordNameForID(ID_type id) const { - if (column == 1) - { - mSearchLineEdit->clear(); - auto clickedID = mCompareTableWidget->item(row, 0)->data(Qt::DisplayRole).toULongLong(); - showID(clickedID); - } + return QString::fromStdString(Configuration::get()->entityList().nameForID(id)); } -void S2Plugin::ViewEntityDB::groupedComparisonItemClicked(QTreeWidgetItem* item, int column) +uintptr_t S2Plugin::ViewEntityDB::addressOfRecordID(ID_type id) const { - if (item->childCount() == 0) - { - mSearchLineEdit->clear(); - showID(item->data(0, Qt::UserRole).toUInt()); - } + return Spelunky2::get()->get_EntityDB().addressOfIndex(id); } diff --git a/src/Views/ViewGameManager.cpp b/src/Views/ViewGameManager.cpp deleted file mode 100644 index b9d14397..00000000 --- a/src/Views/ViewGameManager.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "Views/ViewGameManager.h" -#include "Configuration.h" -#include "QtHelpers/TreeViewMemoryFields.h" -#include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include "pluginmain.h" -#include -#include -#include - -S2Plugin::ViewGameManager::ViewGameManager(ViewToolbar* toolbar, QWidget* parent) : QWidget(parent), mToolbar(toolbar) -{ - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); - setWindowTitle("GameManager"); - refreshGameManager(); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); - toggleAutoRefresh(Qt::Checked); -} - -void S2Plugin::ViewGameManager::initializeUI() -{ - mMainLayout = new QVBoxLayout(this); - mRefreshLayout = new QHBoxLayout(this); - mMainLayout->addLayout(mRefreshLayout); - - mRefreshButton = new QPushButton("Refresh", this); - mRefreshLayout->addWidget(mRefreshButton); - QObject::connect(mRefreshButton, &QPushButton::clicked, this, &ViewGameManager::refreshGameManager); - - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewGameManager::autoRefreshTimerTrigger); - - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - mRefreshLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewGameManager::toggleAutoRefresh); - - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("100"); - mRefreshLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewGameManager::autoRefreshIntervalChanged); - - mRefreshLayout->addWidget(new QLabel("milliseconds", this)); - - mRefreshLayout->addStretch(); - - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewGameManager::label); - mRefreshLayout->addWidget(labelButton); - - mMainTreeView = new TreeViewMemoryFields(mToolbar, this); - mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::GameManager), "GameManager", Spelunky2::get()->get_GameManagerPtr()); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainLayout->addWidget(mMainTreeView); - - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->updateTableHeader(); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); - mMainTreeView->setVisible(true); -} - -void S2Plugin::ViewGameManager::closeEvent(QCloseEvent* event) -{ - delete this; -} - -void S2Plugin::ViewGameManager::refreshGameManager() -{ - mMainTreeView->updateTree(); -} - -void S2Plugin::ViewGameManager::toggleAutoRefresh(int newState) -{ - if (newState == Qt::Unchecked) - { - mAutoRefreshTimer->stop(); - mRefreshButton->setEnabled(true); - } - else - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshButton->setEnabled(false); - } -} - -void S2Plugin::ViewGameManager::autoRefreshIntervalChanged(const QString& text) -{ - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - } -} - -void S2Plugin::ViewGameManager::autoRefreshTimerTrigger() -{ - refreshGameManager(); -} - -QSize S2Plugin::ViewGameManager::sizeHint() const -{ - return QSize(750, 1050); -} - -QSize S2Plugin::ViewGameManager::minimumSizeHint() const -{ - return QSize(150, 150); -} - -void S2Plugin::ViewGameManager::label() -{ - mMainTreeView->labelAll(); -} diff --git a/src/Views/ViewJournalPage.cpp b/src/Views/ViewJournalPage.cpp index 3f319d17..655f914a 100644 --- a/src/Views/ViewJournalPage.cpp +++ b/src/Views/ViewJournalPage.cpp @@ -1,101 +1,58 @@ #include "Views/ViewJournalPage.h" + #include "Configuration.h" #include "QtHelpers/TreeViewMemoryFields.h" -#include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include "pluginmain.h" -#include +#include "QtHelpers/WidgetAutorefresh.h" +#include "QtPlugin.h" +#include #include #include +#include +#include -S2Plugin::ViewJournalPage::ViewJournalPage(ViewToolbar* toolbar, uintptr_t offset, const std::string& pageType, QWidget* parent) - : QWidget(parent), mOffset(offset), mPageType(pageType), mToolbar(toolbar) +S2Plugin::ViewJournalPage::ViewJournalPage(uintptr_t address, QWidget* parent) : QWidget(parent), mPageAddress(address) { - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); setWindowTitle("JournalPage"); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); - toggleAutoRefresh(Qt::Checked); -} - -void S2Plugin::ViewJournalPage::initializeUI() -{ - mMainLayout = new QVBoxLayout(this); - mRefreshLayout = new QHBoxLayout(this); - mMainLayout->addLayout(mRefreshLayout); - - mRefreshButton = new QPushButton("Refresh", this); - mRefreshLayout->addWidget(mRefreshButton); - QObject::connect(mRefreshButton, &QPushButton::clicked, this, &ViewJournalPage::refreshJournalPage); - - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewJournalPage::autoRefreshTimerTrigger); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto refreshLayout = new QHBoxLayout(); + mainLayout->addLayout(refreshLayout); - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - mRefreshLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewJournalPage::toggleAutoRefresh); + auto autoRefresh = new WidgetAutorefresh(100, this); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, this, &ViewJournalPage::refreshJournalPage); + refreshLayout->addWidget(autoRefresh); - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("100"); - mRefreshLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewJournalPage::autoRefreshIntervalChanged); - - mRefreshLayout->addWidget(new QLabel("milliseconds", this)); - - mRefreshLayout->addStretch(); + refreshLayout->addStretch(); + auto labelButton = new QPushButton("Label", this); + QObject::connect(labelButton, &QPushButton::clicked, this, &ViewJournalPage::label); + refreshLayout->addWidget(labelButton); + refreshLayout->addWidget(new QLabel("Interpret as:", this)); - mRefreshLayout->addWidget(new QLabel("Interpret as:", this)); - mInterpretAsComboBox = new QComboBox(this); - // TODO get from json - // also, guess page by the vtable - mInterpretAsComboBox->addItem("JournalPage"); - mInterpretAsComboBox->addItem("JournalPageProgress"); - mInterpretAsComboBox->addItem("JournalPageJournalMenu"); - mInterpretAsComboBox->addItem("JournalPagePlaces"); - mInterpretAsComboBox->addItem("JournalPagePeople"); - mInterpretAsComboBox->addItem("JournalPageBestiary"); - mInterpretAsComboBox->addItem("JournalPageItems"); - mInterpretAsComboBox->addItem("JournalPageTraps"); - mInterpretAsComboBox->addItem("JournalPageStory"); - mInterpretAsComboBox->addItem("JournalPageFeats"); - mInterpretAsComboBox->addItem("JournalPageDeathCause"); - mInterpretAsComboBox->addItem("JournalPageDeathMenu"); - mInterpretAsComboBox->addItem("JournalPageRecap"); - mInterpretAsComboBox->addItem("JournalPagePlayerProfile"); - mInterpretAsComboBox->addItem("JournalPageLastGamePlayed"); + auto interpretAsComboBox = new QComboBox(this); - QObject::connect(mInterpretAsComboBox, &QComboBox::currentTextChanged, this, &ViewJournalPage::interpretAsChanged); - mRefreshLayout->addWidget(mInterpretAsComboBox); - mRefreshLayout->addStretch(); + auto config = Configuration::get(); + for (auto& pageName : config->getJournalPageNames()) + interpretAsComboBox->addItem(QString::fromStdString(pageName)); - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewJournalPage::label); - mRefreshLayout->addWidget(labelButton); + QObject::connect(interpretAsComboBox, &QComboBox::currentTextChanged, this, &ViewJournalPage::interpretAsChanged); + refreshLayout->addWidget(interpretAsComboBox); - mMainTreeView = new TreeViewMemoryFields(mToolbar, this); - mMainTreeView->addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct(mPageType), mPageType, mOffset); + mMainTreeView = new TreeViewMemoryFields(this); + mMainTreeView->addMemoryFields(config->typeFieldsOfDefaultStruct("JournalPage"), "JournalPage", mPageAddress); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainLayout->addWidget(mMainTreeView); + mainLayout->addWidget(mMainTreeView); mMainTreeView->setColumnWidth(gsColValue, 250); + mMainTreeView->setColumnWidth(gsColField, 125); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); + mMainTreeView->setColumnWidth(gsColType, 100); mMainTreeView->updateTableHeader(); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); - mMainTreeView->setVisible(true); -} - -void S2Plugin::ViewJournalPage::closeEvent(QCloseEvent* event) -{ - delete this; + mMainTreeView->updateTree(0, 0, true); + autoRefresh->toggleAutoRefresh(true); } void S2Plugin::ViewJournalPage::refreshJournalPage() @@ -103,34 +60,6 @@ void S2Plugin::ViewJournalPage::refreshJournalPage() mMainTreeView->updateTree(); } -void S2Plugin::ViewJournalPage::toggleAutoRefresh(int newState) -{ - if (newState == Qt::Unchecked) - { - mAutoRefreshTimer->stop(); - mRefreshButton->setEnabled(true); - } - else - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshButton->setEnabled(false); - } -} - -void S2Plugin::ViewJournalPage::autoRefreshIntervalChanged(const QString& text) -{ - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - } -} - -void S2Plugin::ViewJournalPage::autoRefreshTimerTrigger() -{ - refreshJournalPage(); -} - QSize S2Plugin::ViewJournalPage::sizeHint() const { return QSize(750, 750); @@ -150,12 +79,30 @@ void S2Plugin::ViewJournalPage::interpretAsChanged(const QString& text) { if (!text.isEmpty()) { - mPageType = text.toStdString(); + auto pageType = text.toStdString(); mMainTreeView->clear(); - mMainTreeView->addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct(mPageType), mPageType, mOffset); + static std::vector structs; + if (structs.empty()) + { + structs.resize(2); + structs[0].type = MemoryFieldType::DefaultStructType; + structs[0].jsonName = "JournalPage"; + structs[0].name = "JournalPage"; + structs[1].type = MemoryFieldType::DefaultStructType; + } + if (text == "JournalPage") + { + mMainTreeView->addMemoryFields({structs[0]}, pageType, mPageAddress); + } + else + { + structs[1].name = pageType; + structs[1].jsonName = pageType; + mMainTreeView->addMemoryFields(structs, pageType, mPageAddress); + } + mMainTreeView->expandLast(); mMainTreeView->setColumnWidth(gsColValue, 250); mMainTreeView->updateTableHeader(); mMainTreeView->updateTree(0, 0, true); - // mInterpretAsComboBox->setCurrentText(""); } } diff --git a/src/Views/ViewLevelGen.cpp b/src/Views/ViewLevelGen.cpp index 51970f34..48c2aab6 100644 --- a/src/Views/ViewLevelGen.cpp +++ b/src/Views/ViewLevelGen.cpp @@ -1,87 +1,59 @@ #include "Views/ViewLevelGen.h" + #include "Configuration.h" -#include "Data/EntityDB.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetAutorefresh.h" #include "QtHelpers/WidgetSpelunkyRooms.h" -#include "Spelunky2.h" -#include "Views/ViewToolbar.h" +#include "QtPlugin.h" #include "pluginmain.h" -#include +#include #include #include +#include #include +#include -S2Plugin::ViewLevelGen::ViewLevelGen(ViewToolbar* toolbar, QWidget* parent) : QWidget(parent), mToolbar(toolbar) +S2Plugin::ViewLevelGen::ViewLevelGen(uintptr_t address, QWidget* parent) : QWidget(parent), mLevelGenPtr(address) { - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); setWindowTitle("LevelGen"); - mMainTreeView->updateTree(0, 0, true); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); - toggleAutoRefresh(Qt::Checked); -} - -void S2Plugin::ViewLevelGen::initializeUI() -{ - mMainLayout = new QVBoxLayout(); - mRefreshLayout = new QHBoxLayout(); - mMainLayout->addLayout(mRefreshLayout); - - mMainTabWidget = new QTabWidget(this); - mMainTabWidget->setDocumentMode(false); - mMainLayout->addWidget(mMainTabWidget); - - // TOP REFRESH LAYOUT - mRefreshButton = new QPushButton("Refresh", this); - mRefreshLayout->addWidget(mRefreshButton); - QObject::connect(mRefreshButton, &QPushButton::clicked, this, &ViewLevelGen::refreshLevelGen); - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewLevelGen::autoRefreshTimerTrigger); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto refreshLayout = new QHBoxLayout(); + mainLayout->addLayout(refreshLayout); - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - mRefreshLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewLevelGen::toggleAutoRefresh); + auto autoRefresh = new WidgetAutorefresh(500, this); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, this, &ViewLevelGen::refreshLevelGen); + refreshLayout->addWidget(autoRefresh); - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("500"); - mRefreshLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewLevelGen::autoRefreshIntervalChanged); - - mRefreshLayout->addWidget(new QLabel("milliseconds", this)); - - mRefreshLayout->addStretch(); + refreshLayout->addStretch(); auto labelButton = new QPushButton("Label", this); QObject::connect(labelButton, &QPushButton::clicked, this, &ViewLevelGen::label); - mRefreshLayout->addWidget(labelButton); + refreshLayout->addWidget(labelButton); + + mMainTabWidget = new QTabWidget(this); + mMainTabWidget->setDocumentMode(false); + mainLayout->addWidget(mMainTabWidget); // TABS - mTabData = new QWidget(); - mTabRooms = new QWidget(); - mTabData->setLayout(new QVBoxLayout(mTabData)); - mTabData->layout()->setMargin(0); - mTabData->setObjectName("datawidget"); - mTabRooms->setLayout(new QVBoxLayout(mTabRooms)); - mTabRooms->layout()->setMargin(0); + mMainTreeView = new TreeViewMemoryFields(this); + auto tabRooms = new QScrollArea(this); - mMainTabWidget->addTab(mTabData, "Data"); - mMainTabWidget->addTab(mTabRooms, "Rooms"); + mMainTabWidget->addTab(mMainTreeView, "Data"); + mMainTabWidget->addTab(tabRooms, "Rooms"); // TAB DATA { - mMainTreeView = new TreeViewMemoryFields(mToolbar, this); - mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::LevelGen), "LevelGen", Spelunky2::get()->get_LevelGenPtr()); - mTabData->layout()->addWidget(mMainTreeView); + mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::LevelGen), "LevelGen", mLevelGenPtr); mMainTreeView->setColumnWidth(gsColValue, 250); + mMainTreeView->setColumnWidth(gsColField, 125); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); + mMainTreeView->setColumnWidth(gsColType, 100); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); mMainTreeView->updateTableHeader(); QObject::connect(mMainTreeView, &TreeViewMemoryFields::levelGenRoomsPointerClicked, this, &ViewLevelGen::levelGenRoomsPointerClicked); @@ -89,10 +61,9 @@ void S2Plugin::ViewLevelGen::initializeUI() // TAB ROOMS { - auto scroll = new QScrollArea(this); - scroll->setWidgetResizable(true); + tabRooms->setWidgetResizable(true); auto containerWidget = new QWidget(this); - scroll->setWidget(containerWidget); + tabRooms->setWidget(containerWidget); auto containerLayout = new QVBoxLayout(containerWidget); for (const auto& field : Configuration::get()->typeFields(MemoryFieldType::LevelGen)) @@ -108,26 +79,18 @@ void S2Plugin::ViewLevelGen::initializeUI() containerLayout->addWidget(roomWidget); } } - dynamic_cast(mTabRooms->layout())->addWidget(scroll); } - - mMainLayout->setMargin(5); - setLayout(mMainLayout); - mMainTreeView->setVisible(true); -} - -void S2Plugin::ViewLevelGen::closeEvent(QCloseEvent* event) -{ - delete this; + autoRefresh->toggleAutoRefresh(true); + mMainTreeView->updateTree(0, 0, true); } void S2Plugin::ViewLevelGen::refreshLevelGen() { mMainTreeView->updateTree(); - if (mMainTabWidget->currentWidget() == mTabRooms) + if (mMainTabWidget->currentIndex() == 0) { - auto offset = Spelunky2::get()->get_LevelGenPtr(); // TODO: save ptr to sturct on view open + auto offset = mLevelGenPtr; for (const auto& field : Configuration::get()->typeFields(MemoryFieldType::LevelGen)) { if (field.type == MemoryFieldType::LevelGenRoomsPointer || field.type == MemoryFieldType::LevelGenRoomsMetaPointer) @@ -140,34 +103,6 @@ void S2Plugin::ViewLevelGen::refreshLevelGen() } } -void S2Plugin::ViewLevelGen::toggleAutoRefresh(int newLevelGen) -{ - if (newLevelGen == Qt::Unchecked) - { - mAutoRefreshTimer->stop(); - mRefreshButton->setEnabled(true); - } - else - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshButton->setEnabled(false); - } -} - -void S2Plugin::ViewLevelGen::autoRefreshIntervalChanged(const QString& text) -{ - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - } -} - -void S2Plugin::ViewLevelGen::autoRefreshTimerTrigger() -{ - refreshLevelGen(); -} - QSize S2Plugin::ViewLevelGen::sizeHint() const { return QSize(750, 1050); @@ -185,5 +120,5 @@ void S2Plugin::ViewLevelGen::label() void S2Plugin::ViewLevelGen::levelGenRoomsPointerClicked() { - mMainTabWidget->setCurrentWidget(mTabRooms); + mMainTabWidget->setCurrentIndex(1); } diff --git a/src/Views/ViewLogger.cpp b/src/Views/ViewLogger.cpp index 604f1c92..fa37477d 100644 --- a/src/Views/ViewLogger.cpp +++ b/src/Views/ViewLogger.cpp @@ -1,101 +1,83 @@ #include "Views/ViewLogger.h" + #include "Data/Logger.h" #include "QtHelpers/ItemModelLoggerFields.h" #include "QtHelpers/ItemModelLoggerSamples.h" #include "QtHelpers/TableViewLogger.h" #include "QtHelpers/WidgetSamplesPlot.h" #include "QtHelpers/WidgetSampling.h" -#include +#include "QtPlugin.h" #include -#include #include #include +#include +#include S2Plugin::ViewLogger::ViewLogger(QWidget* parent) : QWidget(parent) { - mLogger = std::make_unique(); + mLogger = new Logger(this); - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); setWindowTitle("Logger"); -} - -void S2Plugin::ViewLogger::closeEvent(QCloseEvent* event) -{ - delete this; -} - -QSize S2Plugin::ViewLogger::sizeHint() const -{ - return QSize(650, 450); -} - -QSize S2Plugin::ViewLogger::minimumSizeHint() const -{ - return QSize(150, 150); -} -void S2Plugin::ViewLogger::initializeUI() -{ - mMainLayout = new QVBoxLayout(this); - mMainLayout->setMargin(5); - setLayout(mMainLayout); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); - mTopLayout = new QHBoxLayout(this); - mTopLayout->addWidget(new QLabel("Sample period:", this)); + auto topLayout = new QHBoxLayout(this); + topLayout->addWidget(new QLabel("Sample period:", this)); mSamplePeriodLineEdit = new QLineEdit("8", this); mSamplePeriodLineEdit->setFixedWidth(50); mSamplePeriodLineEdit->setValidator(new QIntValidator(5, 5000, this)); - mTopLayout->addWidget(mSamplePeriodLineEdit); - mTopLayout->addWidget(new QLabel("milliseconds", this)); + topLayout->addWidget(mSamplePeriodLineEdit); + topLayout->addWidget(new QLabel("milliseconds", this)); - mTopLayout->addStretch(); + topLayout->addStretch(); - mTopLayout->addWidget(new QLabel("Duration:", this)); + topLayout->addWidget(new QLabel("Duration:", this)); mDurationLineEdit = new QLineEdit("5", this); - mTopLayout->addWidget(mDurationLineEdit); + topLayout->addWidget(mDurationLineEdit); mDurationLineEdit->setFixedWidth(50); mDurationLineEdit->setValidator(new QIntValidator(1, 500, this)); - mTopLayout->addWidget(new QLabel("seconds", this)); + topLayout->addWidget(new QLabel("seconds", this)); - mTopLayout->addStretch(); + topLayout->addStretch(); mStartButton = new QPushButton(this); mStartButton->setText("Start"); - mTopLayout->addWidget(mStartButton); + topLayout->addWidget(mStartButton); QObject::connect(mStartButton, &QPushButton::clicked, this, &ViewLogger::startLogging); - mMainLayout->addLayout(mTopLayout); + mainLayout->addLayout(topLayout); // TABS mMainTabWidget = new QTabWidget(this); mMainTabWidget->setDocumentMode(false); - mMainLayout->addWidget(mMainTabWidget); - - mTabFields = new QWidget(); - mTabSamples = new QWidget(); - mTabPlot = new QWidget(); - mTabFields->setLayout(new QVBoxLayout(mTabFields)); - mTabFields->layout()->setMargin(0); - mTabSamples->setLayout(new QVBoxLayout(mTabSamples)); - mTabSamples->layout()->setMargin(0); - mTabPlot->setLayout(new QVBoxLayout(mTabPlot)); - mTabPlot->layout()->setMargin(0); - - mMainTabWidget->addTab(mTabFields, "Fields"); - mMainTabWidget->addTab(mTabSamples, "Samples"); - mMainTabWidget->addTab(mTabPlot, "Plot"); + mainLayout->addWidget(mMainTabWidget); + + auto tabFields = new QWidget(); + auto tabSamples = new QWidget(); + auto tabPlot = new QWidget(); + tabFields->setLayout(new QVBoxLayout()); + tabFields->layout()->setMargin(0); + tabSamples->setLayout(new QVBoxLayout()); + tabSamples->layout()->setMargin(0); + tabPlot->setLayout(new QVBoxLayout()); + tabPlot->layout()->setMargin(0); + + mMainTabWidget->addTab(tabFields, "Fields"); + mMainTabWidget->addTab(tabSamples, "Samples"); + mMainTabWidget->addTab(tabPlot, "Plot"); // TAB Fields { - mFieldsTableView = new TableViewLogger(mLogger.get(), this); + mFieldsTableView = new TableViewLogger(mLogger, this); mFieldsTableView->setDragDropMode(QAbstractItemView::DragDropMode::DropOnly); mFieldsTableView->setAcceptDrops(true); - mTabFields->layout()->addWidget(mFieldsTableView); + tabFields->layout()->addWidget(mFieldsTableView); - mFieldsTableModel = new ItemModelLoggerFields(mLogger.get(), mFieldsTableView); - mLogger->setTableModel(mFieldsTableModel); - mFieldsTableView->setModel(mFieldsTableModel); + auto fieldsTableModel = new ItemModelLoggerFields(mLogger, mFieldsTableView); + mLogger->setTableModel(fieldsTableModel); + mFieldsTableView->setModel(fieldsTableModel); mFieldsTableView->setColumnWidth(gsLogFieldColColor, 45); mFieldsTableView->setColumnWidth(gsLogFieldColMemoryOffset, 125); mFieldsTableView->setColumnWidth(gsLogFieldColFieldType, 150); @@ -104,27 +86,37 @@ void S2Plugin::ViewLogger::initializeUI() // TAB Samples { mSamplesTableView = new QTableView(this); - mTabSamples->layout()->addWidget(mSamplesTableView); - mSamplesTableModel = new ItemModelLoggerSamples(mLogger.get(), mSamplesTableView); + tabSamples->layout()->addWidget(mSamplesTableView); + mSamplesTableModel = new ItemModelLoggerSamples(mLogger, mSamplesTableView); mSamplesTableView->setModel(mSamplesTableModel); } // TAB Plot { - mSamplesPlotScroll = new QScrollArea(this); - mSamplesPlotWidget = new WidgetSamplesPlot(mLogger.get(), this); - mSamplesPlotScroll->setBackgroundRole(QPalette::Dark); - mSamplesPlotScroll->setWidget(mSamplesPlotWidget); - mSamplesPlotScroll->setWidgetResizable(true); - mTabPlot->layout()->addWidget(mSamplesPlotScroll); + auto samplesPlotScroll = new QScrollArea(this); + auto samplesPlotWidget = new WidgetSamplesPlot(mLogger, this); + samplesPlotScroll->setBackgroundRole(QPalette::Dark); + samplesPlotScroll->setWidget(samplesPlotWidget); + samplesPlotScroll->setWidgetResizable(true); + tabPlot->layout()->addWidget(samplesPlotScroll); } - QObject::connect(mLogger.get(), &Logger::samplingEnded, this, &ViewLogger::samplingEnded); - QObject::connect(mLogger.get(), &Logger::fieldsChanged, this, &ViewLogger::fieldsChanged); + QObject::connect(mLogger, &Logger::samplingEnded, this, &ViewLogger::samplingEnded); + QObject::connect(mLogger, &Logger::fieldsChanged, this, &ViewLogger::fieldsChanged); mSamplingWidget = new WidgetSampling(this); mSamplingWidget->setHidden(true); - mMainLayout->addWidget(mSamplingWidget); + mainLayout->addWidget(mSamplingWidget); +} + +QSize S2Plugin::ViewLogger::sizeHint() const +{ + return QSize(650, 450); +} + +QSize S2Plugin::ViewLogger::minimumSizeHint() const +{ + return QSize(150, 150); } void S2Plugin::ViewLogger::startLogging() @@ -136,13 +128,13 @@ void S2Plugin::ViewLogger::startLogging() mStartButton->setEnabled(false); mMainTabWidget->setHidden(true); mSamplingWidget->setHidden(false); - mLogger->start(mSamplePeriodLineEdit->text().toULongLong(), mDurationLineEdit->text().toULongLong()); + mLogger->start(mSamplePeriodLineEdit->text().toInt(), mDurationLineEdit->text().toInt()); } else { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Warning); - msgBox.setWindowIcon(QIcon(":/icons/caveman.png")); + msgBox.setWindowIcon(getCavemanIcon()); msgBox.setText("Please specify one or more fields to log"); msgBox.setWindowTitle("Spelunky2"); msgBox.exec(); diff --git a/src/Views/ViewOnline.cpp b/src/Views/ViewOnline.cpp deleted file mode 100644 index 900a015e..00000000 --- a/src/Views/ViewOnline.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "Views/ViewOnline.h" -#include "Configuration.h" -#include "Data/State.h" -#include "QtHelpers/TreeViewMemoryFields.h" -#include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include "pluginmain.h" -#include -#include -#include - -S2Plugin::ViewOnline::ViewOnline(ViewToolbar* toolbar, QWidget* parent) : QWidget(parent), mToolbar(toolbar) -{ - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); - setWindowTitle("Online"); - refreshOnline(); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); - toggleAutoRefresh(Qt::Checked); -} - -void S2Plugin::ViewOnline::initializeUI() -{ - mMainLayout = new QVBoxLayout(this); - mRefreshLayout = new QHBoxLayout(this); - mMainLayout->addLayout(mRefreshLayout); - - mRefreshButton = new QPushButton("Refresh", this); - mRefreshLayout->addWidget(mRefreshButton); - QObject::connect(mRefreshButton, &QPushButton::clicked, this, &ViewOnline::refreshOnline); - - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewOnline::autoRefreshTimerTrigger); - - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - mRefreshLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewOnline::toggleAutoRefresh); - - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("100"); - mRefreshLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewOnline::autoRefreshIntervalChanged); - - mRefreshLayout->addWidget(new QLabel("milliseconds", this)); - - mRefreshLayout->addStretch(); - - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewOnline::label); - mRefreshLayout->addWidget(labelButton); - - mMainTreeView = new TreeViewMemoryFields(mToolbar, this); - mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::Online), "Online", Spelunky2::get()->get_OnlinePtr()); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainLayout->addWidget(mMainTreeView); - - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->updateTableHeader(); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); - mMainTreeView->setVisible(true); -} - -void S2Plugin::ViewOnline::closeEvent(QCloseEvent* event) -{ - delete this; -} - -void S2Plugin::ViewOnline::refreshOnline() -{ - mMainTreeView->updateTree(); -} - -void S2Plugin::ViewOnline::toggleAutoRefresh(int newState) -{ - if (newState == Qt::Unchecked) - { - mAutoRefreshTimer->stop(); - mRefreshButton->setEnabled(true); - } - else - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshButton->setEnabled(false); - } -} - -void S2Plugin::ViewOnline::autoRefreshIntervalChanged(const QString& text) -{ - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - } -} - -void S2Plugin::ViewOnline::autoRefreshTimerTrigger() -{ - refreshOnline(); -} - -QSize S2Plugin::ViewOnline::sizeHint() const -{ - return QSize(750, 500); -} - -QSize S2Plugin::ViewOnline::minimumSizeHint() const -{ - return QSize(150, 150); -} - -void S2Plugin::ViewOnline::label() -{ - mMainTreeView->labelAll(); -} diff --git a/src/Views/ViewParticleDB.cpp b/src/Views/ViewParticleDB.cpp index 7a69b060..08d1f049 100644 --- a/src/Views/ViewParticleDB.cpp +++ b/src/Views/ViewParticleDB.cpp @@ -1,138 +1,21 @@ #include "Views/ViewParticleDB.h" + #include "Configuration.h" -#include "QtHelpers/DatabaseHelper.h" -#include "QtHelpers/TableWidgetItemNumeric.h" #include "QtHelpers/TreeViewMemoryFields.h" -#include "QtHelpers/TreeWidgetItemNumeric.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include #include -#include -#include -#include - -S2Plugin::ViewParticleDB::ViewParticleDB(ViewToolbar* toolbar, uint32_t id, QWidget* parent) : QWidget(parent) -{ - mMainTreeView = new TreeViewMemoryFields(toolbar, this); - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); - setWindowTitle(QString("Particle DB (%1 particles)").arg(Configuration::get()->particleEmittersList().count())); - showID(id); -} - -void S2Plugin::ViewParticleDB::initializeUI() -{ - auto mainLayout = new QVBoxLayout(); - mainLayout->setMargin(5); - setLayout(mainLayout); - - mMainTabWidget = new QTabWidget(); - mMainTabWidget->setDocumentMode(false); - mainLayout->addWidget(mMainTabWidget); - - mTabLookup = new QWidget(); - mTabCompare = new QWidget(); - mTabLookup->setLayout(new QVBoxLayout()); - mTabLookup->layout()->setMargin(10); - mTabLookup->setObjectName("lookupwidget"); - mTabLookup->setStyleSheet("QWidget#lookupwidget {border: 1px solid #999;}"); - mTabCompare->setLayout(new QVBoxLayout()); - mTabCompare->layout()->setMargin(10); - mTabCompare->setObjectName("comparewidget"); - mTabCompare->setStyleSheet("QWidget#comparewidget {border: 1px solid #999;}"); - - mMainTabWidget->addTab(mTabLookup, "Lookup"); - mMainTabWidget->addTab(mTabCompare, "Compare"); - auto config = Configuration::get(); - // LOOKUP - { - auto topLayout = new QHBoxLayout(); - - mSearchLineEdit = new QLineEdit(); - mSearchLineEdit->setPlaceholderText("Search id"); - topLayout->addWidget(mSearchLineEdit); - QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &ViewParticleDB::searchFieldReturnPressed); - mSearchLineEdit->setVisible(false); - auto particleNameCompleter = new QCompleter(config->particleEmittersList().names(), this); - particleNameCompleter->setCaseSensitivity(Qt::CaseInsensitive); - particleNameCompleter->setFilterMode(Qt::MatchContains); - QObject::connect(particleNameCompleter, static_cast(&QCompleter::activated), this, &ViewParticleDB::searchFieldCompleterActivated); - mSearchLineEdit->setCompleter(particleNameCompleter); - - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewParticleDB::label); - topLayout->addWidget(labelButton); - - dynamic_cast(mTabLookup->layout())->addLayout(topLayout); - - mMainTreeView->setEnableChangeHighlighting(false); - mMainTreeView->addMemoryFields(config->typeFields(MemoryFieldType::ParticleDB), "ParticleDB", 0); - - QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &ViewParticleDB::fieldUpdated); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &ViewParticleDB::fieldExpanded); - mTabLookup->layout()->addWidget(mMainTreeView); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainTreeView->updateTableHeader(); - } - // COMPARE - { - auto topLayout = new QHBoxLayout(); - mCompareFieldComboBox = new QComboBox(this); - mCompareFieldComboBox->addItem(QString::fromStdString(""), QVariant{}); - DB::populateComparisonCombobox(mCompareFieldComboBox, config->typeFields(MemoryFieldType::ParticleDB)); - - QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &ViewParticleDB::comparisonFieldChosen); - topLayout->addWidget(mCompareFieldComboBox); - - auto groupCheckbox = new QCheckBox("Group by value", this); - QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &ViewParticleDB::compareGroupByCheckBoxClicked); - topLayout->addWidget(groupCheckbox); - - dynamic_cast(mTabCompare->layout())->addLayout(topLayout); - - mCompareTableWidget = new QTableWidget(config->particleEmittersList().count(), 3, this); - mCompareTableWidget->setAlternatingRowColors(true); - mCompareTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - mCompareTableWidget->setHorizontalHeaderLabels(QStringList() << "ID" - << "Name" - << "Value"); - mCompareTableWidget->verticalHeader()->setVisible(false); - mCompareTableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - mCompareTableWidget->verticalHeader()->setDefaultSectionSize(20); - mCompareTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - mCompareTableWidget->setColumnWidth(0, 40); - mCompareTableWidget->setColumnWidth(1, 325); - mCompareTableWidget->setColumnWidth(2, 150); - mCompareTableWidget->setItemDelegate(&mHTMLDelegate); - QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &ViewParticleDB::comparisonCellClicked); - - mCompareTreeWidget = new QTreeWidget(this); - mCompareTreeWidget->setAlternatingRowColors(true); - mCompareTreeWidget->headerItem()->setHidden(true); - mCompareTreeWidget->setHidden(true); - mCompareTreeWidget->setItemDelegate(&mHTMLDelegate); - QObject::connect(mCompareTreeWidget, &QTreeWidget::itemClicked, this, &ViewParticleDB::groupedComparisonItemClicked); - - mTabCompare->layout()->addWidget(mCompareTableWidget); - mTabCompare->layout()->addWidget(mCompareTreeWidget); - } - - mSearchLineEdit->setVisible(true); - mSearchLineEdit->setFocus(); - mMainTreeView->setVisible(true); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); -} - -void S2Plugin::ViewParticleDB::closeEvent(QCloseEvent* event) +S2Plugin::ViewParticleDB::ViewParticleDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::ParticleDB, parent) { - delete this; + auto& particleEmitters = Configuration::get()->particleEmittersList(); + setWindowTitle(QString("Particle DB (%1 particles)").arg(particleEmitters.count())); + auto particleNameCompleter = new QCompleter(particleEmitters.names(), this); + particleNameCompleter->setCaseSensitivity(Qt::CaseInsensitive); + particleNameCompleter->setFilterMode(Qt::MatchContains); + QObject::connect(particleNameCompleter, static_cast(&QCompleter::activated), this, &ViewParticleDB::searchFieldCompleterActivated); + mSearchLineEdit->setCompleter(particleNameCompleter); + mCompareTableWidget->setRowCount(static_cast(particleEmitters.count())); + showID(1); } QSize S2Plugin::ViewParticleDB::sizeHint() const @@ -140,179 +23,62 @@ QSize S2Plugin::ViewParticleDB::sizeHint() const return QSize(750, 1050); } -QSize S2Plugin::ViewParticleDB::minimumSizeHint() const +void S2Plugin::ViewParticleDB::searchFieldCompleterActivated() { - return QSize(150, 150); + searchFieldReturnPressed(); } -void S2Plugin::ViewParticleDB::searchFieldReturnPressed() +void S2Plugin::ViewParticleDB::showRAW(uintptr_t address) { - auto text = mSearchLineEdit->text(); - bool isNumeric = false; - auto enteredID = text.toUInt(&isNumeric); - auto& particleEmittersList = Configuration::get()->particleEmittersList(); - - if (isNumeric && enteredID <= particleEmittersList.highestID()) - { - showID(enteredID); - } - else - { - auto entityID = particleEmittersList.idForName(text.toStdString()); - if (entityID != 0) - { - showID(entityID); - } - } + mMainTreeView->updateTree(address); } -void S2Plugin::ViewParticleDB::searchFieldCompleterActivated(const QString& text) +void S2Plugin::ViewParticleDB::showID(ID_type id) { - searchFieldReturnPressed(); -} + // ids start from 1 + if (id == 0 || id > Configuration::get()->particleEmittersList().highestID()) + return; -void S2Plugin::ViewParticleDB::showID(uint32_t id) -{ - mMainTabWidget->setCurrentWidget(mTabLookup); - uint32_t index = id == 0 ? 0 : id - 1; - mMainTreeView->updateTree(Spelunky2::get()->get_ParticleDB().addressOfIndex(index)); + switchToLookupTab(); + mMainTreeView->updateTree(Spelunky2::get()->get_ParticleDB().addressOfIndex(id - 1)); } -void S2Plugin::ViewParticleDB::label() +void S2Plugin::ViewParticleDB::label() const { auto model = mMainTreeView->model(); auto& particleDB = Spelunky2::get()->get_ParticleDB(); auto offset = particleDB.addressOfIndex(0); // ptr uintptr_t indexOffset = model->data(model->index(0, gsColField), gsRoleMemoryAddress).toULongLong(); - size_t index = (indexOffset - offset) / particleDB.particleSize(); + uint32_t index = static_cast((indexOffset - offset) / particleDB.particleSize()); std::string name = '[' + Configuration::get()->particleEmittersList().nameForID(index + 1) + ']'; mMainTreeView->labelAll(name); } -void S2Plugin::ViewParticleDB::fieldUpdated(const QString& fieldName) -{ - updateFieldValues(); -} - -void S2Plugin::ViewParticleDB::fieldExpanded(const QModelIndex& index) -{ - updateFieldValues(); -} - -void S2Plugin::ViewParticleDB::updateFieldValues() -{ - mMainTreeView->updateTree(); -} - -void S2Plugin::ViewParticleDB::compareGroupByCheckBoxClicked(int state) -{ - mCompareTableWidget->setHidden(state == Qt::Checked); - mCompareTreeWidget->setHidden(state == Qt::Unchecked); -} - -void S2Plugin::ViewParticleDB::comparisonFieldChosen(const QString& fieldName) +S2Plugin::ID_type S2Plugin::ViewParticleDB::highestRecordID() const { - mCompareTableWidget->clearContents(); - mCompareTreeWidget->clear(); - - auto comboIndex = mCompareFieldComboBox->currentIndex(); - if (comboIndex == 0) - { - return; - } - - populateComparisonTableWidget(); - populateComparisonTreeWidget(); + return Configuration::get()->particleEmittersList().highestID(); } -void S2Plugin::ViewParticleDB::populateComparisonTableWidget() +bool S2Plugin::ViewParticleDB::isValidRecordID(ID_type id) const { - mCompareTableWidget->setSortingEnabled(false); - - auto comboboxData = mCompareFieldComboBox->currentData(); - auto& particleList = Configuration::get()->particleEmittersList(); - auto& particleDB = Spelunky2::get()->get_ParticleDB(); - - size_t row = 0; - for (auto x = 1; x <= particleList.count(); ++x) - { - auto item0 = new QTableWidgetItem(QString::asprintf("%03d", x)); - item0->setTextAlignment(Qt::AlignCenter); - mCompareTableWidget->setItem(row, 0, item0); - auto name = QString::fromStdString(particleList.nameForID(x)); - mCompareTableWidget->setItem(row, 1, new QTableWidgetItem(QString("%1").arg(name))); - - auto [caption, value] = DB::valueForField(comboboxData, particleDB.addressOfIndex(x - 1)); // id:1 == index:0 - auto item = new TableWidgetItemNumeric(caption); - item->setData(Qt::UserRole, value); - mCompareTableWidget->setItem(row, 2, item); - - row++; - } - mCompareTableWidget->setSortingEnabled(true); - mCompareTableWidget->sortItems(0); + return id != 0; } -void S2Plugin::ViewParticleDB::populateComparisonTreeWidget() +std::optional S2Plugin::ViewParticleDB::getIDForName(QString name) const { - mCompareTreeWidget->setSortingEnabled(false); - - auto comboboxData = mCompareFieldComboBox->currentData(); - auto& particleDB = Spelunky2::get()->get_ParticleDB(); - auto& particleEmitters = Configuration::get()->particleEmittersList(); - - std::unordered_map rootValues; - std::unordered_map> groupedValues; // valueString -> set - for (uint32_t x = 1; x <= particleEmitters.count(); ++x) - { - auto [caption, value] = DB::valueForField(comboboxData, particleDB.addressOfIndex(x - 1)); // id:1 == index:0 - auto captionStr = caption.toStdString(); - rootValues[captionStr] = value; - - if (groupedValues.count(captionStr) == 0) - { - groupedValues[captionStr] = {x}; - } - else - { - groupedValues[captionStr].insert(x); - } - } - - for (const auto& [groupString, particleIds] : groupedValues) - { - auto rootItem = new TreeWidgetItemNumeric(nullptr, QString::fromStdString(groupString)); - rootItem->setData(0, Qt::UserRole, rootValues.at(groupString)); - mCompareTreeWidget->insertTopLevelItem(0, rootItem); - for (const auto& particleId : particleIds) - { - auto particleName = particleEmitters.nameForID(particleId); - auto caption = QString("%1").arg(QString::fromStdString(particleName)); - auto childItem = new QTreeWidgetItem(rootItem, QStringList(caption)); - childItem->setData(0, Qt::UserRole, particleId); - mCompareTreeWidget->insertTopLevelItem(0, childItem); - } - } + auto id = Configuration::get()->particleEmittersList().idForName(name.toStdString()); + if (id == 0) + return std::nullopt; - mCompareTreeWidget->setSortingEnabled(true); - mCompareTreeWidget->sortItems(0, Qt::AscendingOrder); + return id; } -void S2Plugin::ViewParticleDB::comparisonCellClicked(int row, int column) +QString S2Plugin::ViewParticleDB::recordNameForID(ID_type id) const { - if (column == 1) - { - mSearchLineEdit->clear(); - auto clickedID = mCompareTableWidget->item(row, 0)->data(Qt::DisplayRole).toUInt(); - showID(clickedID); - } + return QString::fromStdString(Configuration::get()->particleEmittersList().nameForID(id)); } -void S2Plugin::ViewParticleDB::groupedComparisonItemClicked(QTreeWidgetItem* item, int column) +uintptr_t S2Plugin::ViewParticleDB::addressOfRecordID(ID_type id) const { - if (item->childCount() == 0) - { - mSearchLineEdit->clear(); - showID(item->data(0, Qt::UserRole).toUInt()); - } + return Spelunky2::get()->get_ParticleDB().addressOfIndex(id); } diff --git a/src/Views/ViewSaveGame.cpp b/src/Views/ViewSaveGame.cpp deleted file mode 100644 index f886f412..00000000 --- a/src/Views/ViewSaveGame.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "Views/ViewSaveGame.h" -#include "Configuration.h" -#include "QtHelpers/TreeViewMemoryFields.h" -#include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include "pluginmain.h" -#include -#include -#include - -S2Plugin::ViewSaveGame::ViewSaveGame(ViewToolbar* toolbar, QWidget* parent) : QWidget(parent), mToolbar(toolbar) -{ - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); - setWindowTitle("SaveGame"); - refreshSaveGame(); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); - toggleAutoRefresh(Qt::Checked); -} - -void S2Plugin::ViewSaveGame::initializeUI() -{ - mMainLayout = new QVBoxLayout(this); - mRefreshLayout = new QHBoxLayout(this); - mMainLayout->addLayout(mRefreshLayout); - - mRefreshButton = new QPushButton("Refresh", this); - mRefreshLayout->addWidget(mRefreshButton); - QObject::connect(mRefreshButton, &QPushButton::clicked, this, &ViewSaveGame::refreshSaveGame); - - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewSaveGame::autoRefreshTimerTrigger); - - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - mRefreshLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewSaveGame::toggleAutoRefresh); - - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("100"); - mRefreshLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewSaveGame::autoRefreshIntervalChanged); - - mRefreshLayout->addWidget(new QLabel("milliseconds", this)); - - mRefreshLayout->addStretch(); - - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewSaveGame::label); - mRefreshLayout->addWidget(labelButton); - - mMainTreeView = new TreeViewMemoryFields(mToolbar, this); - mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::SaveGame), "SaveGame", Spelunky2::get()->get_SaveDataPtr()); - - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainLayout->addWidget(mMainTreeView); - - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->updateTableHeader(); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); - mMainTreeView->setVisible(true); -} - -void S2Plugin::ViewSaveGame::closeEvent(QCloseEvent* event) -{ - delete this; -} - -void S2Plugin::ViewSaveGame::refreshSaveGame() -{ - mMainTreeView->updateTree(); -} - -void S2Plugin::ViewSaveGame::toggleAutoRefresh(int newState) -{ - if (newState == Qt::Unchecked) - { - mAutoRefreshTimer->stop(); - mRefreshButton->setEnabled(true); - } - else - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshButton->setEnabled(false); - } -} - -void S2Plugin::ViewSaveGame::autoRefreshIntervalChanged(const QString& text) -{ - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - } -} - -void S2Plugin::ViewSaveGame::autoRefreshTimerTrigger() -{ - refreshSaveGame(); -} - -QSize S2Plugin::ViewSaveGame::sizeHint() const -{ - return QSize(750, 1050); -} - -QSize S2Plugin::ViewSaveGame::minimumSizeHint() const -{ - return QSize(150, 150); -} - -void S2Plugin::ViewSaveGame::label() -{ - mMainTreeView->labelAll(); -} diff --git a/src/Views/ViewState.cpp b/src/Views/ViewState.cpp deleted file mode 100644 index 9b28265f..00000000 --- a/src/Views/ViewState.cpp +++ /dev/null @@ -1,124 +0,0 @@ -#include "Views/ViewState.h" -#include "Configuration.h" -#include "Data/EntityDB.h" -#include "Data/State.h" -#include "QtHelpers/TreeViewMemoryFields.h" -#include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include "pluginmain.h" -#include -#include -#include - -S2Plugin::ViewState::ViewState(ViewToolbar* toolbar, uintptr_t state, QWidget* parent) : QWidget(parent), mToolbar(toolbar), mState(state) -{ - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); - setWindowTitle("State"); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); - toggleAutoRefresh(Qt::Checked); -} - -void S2Plugin::ViewState::initializeUI() -{ - mMainLayout = new QVBoxLayout(); - mRefreshLayout = new QHBoxLayout(); - mMainLayout->addLayout(mRefreshLayout); - - mRefreshButton = new QPushButton("Refresh", this); - mRefreshLayout->addWidget(mRefreshButton); - QObject::connect(mRefreshButton, &QPushButton::clicked, this, &ViewState::refreshState); - - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewState::autoRefreshTimerTrigger); - - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - mRefreshLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewState::toggleAutoRefresh); - - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("100"); - mRefreshLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewState::autoRefreshIntervalChanged); - - mRefreshLayout->addWidget(new QLabel("milliseconds", this)); - - mRefreshLayout->addStretch(); - - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewState::label); - mRefreshLayout->addWidget(labelButton); - - mMainTreeView = new TreeViewMemoryFields(mToolbar, this); - mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::State), "State", mState); - - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainLayout->addWidget(mMainTreeView); - - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->updateTableHeader(); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); - mMainTreeView->setVisible(true); -} - -void S2Plugin::ViewState::closeEvent(QCloseEvent* event) -{ - delete this; -} - -void S2Plugin::ViewState::refreshState() -{ - mMainTreeView->updateTree(); -} - -void S2Plugin::ViewState::autoRefreshTimerTrigger() -{ - mMainTreeView->updateTree(); -} - -void S2Plugin::ViewState::toggleAutoRefresh(int newState) -{ - if (newState == Qt::Unchecked) - { - mAutoRefreshTimer->stop(); - mRefreshButton->setEnabled(true); - } - else - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshButton->setEnabled(false); - } -} - -void S2Plugin::ViewState::autoRefreshIntervalChanged(const QString& text) -{ - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - } -} - -QSize S2Plugin::ViewState::sizeHint() const -{ - return QSize(750, 1050); -} - -QSize S2Plugin::ViewState::minimumSizeHint() const -{ - return QSize(150, 150); -} - -void S2Plugin::ViewState::label() -{ - mMainTreeView->labelAll(); -} diff --git a/src/Views/ViewStdMap.cpp b/src/Views/ViewStdMap.cpp index 13705160..9c9e73b5 100644 --- a/src/Views/ViewStdMap.cpp +++ b/src/Views/ViewStdMap.cpp @@ -3,23 +3,17 @@ #include "Configuration.h" #include "Data/StdMap.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetAutorefresh.h" +#include "QtPlugin.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" #include "pluginmain.h" -#include -#include -#include -#include -#include #include -#include -#include +#include +#include -S2Plugin::ViewStdMap::ViewStdMap(ViewToolbar* toolbar, const std::string& keytypeName, const std::string& valuetypeName, uintptr_t mapOffset, QWidget* parent) - : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mmapOffset(mapOffset), QWidget(parent) +S2Plugin::ViewStdMap::ViewStdMap(const std::string& keytypeName, const std::string& valuetypeName, uintptr_t address, QWidget* parent) + : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mMapAddress(address), QWidget(parent) { - mMainLayout = new QVBoxLayout(this); - auto config = Configuration::get(); mMapKeyTypeSize = config->getTypeSize(keytypeName); mMapValueTypeSize = config->getTypeSize(valuetypeName); @@ -27,70 +21,40 @@ S2Plugin::ViewStdMap::ViewStdMap(ViewToolbar* toolbar, const std::string& keytyp mMapKeyAlignment = config->getAlingment(keytypeName); mMapValueAlignment = config->getAlingment(valuetypeName); - initializeRefreshLayout(); - mMainTreeView = new TreeViewMemoryFields(toolbar, this); - mMainTreeView->setEnableChangeHighlighting(false); - - mMainLayout->addWidget(mMainTreeView); - setWindowIcon(QIcon(":/icons/caveman.png")); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); + setWindowIcon(getCavemanIcon()); if (mMapValueTypeSize == 0) setWindowTitle(QString("std::set<%1>").arg(QString::fromStdString(keytypeName))); else setWindowTitle(QString("std::map<%1, %2>").arg(QString::fromStdString(keytypeName), QString::fromStdString(valuetypeName))); - mMainTreeView->setVisible(true); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColComment); - refreshMapContents(); - toggleAutoRefresh(Qt::Checked); -} + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto refreshLayout = new QHBoxLayout(); + mainLayout->addLayout(refreshLayout); -void S2Plugin::ViewStdMap::initializeRefreshLayout() -{ - auto refreshLayout = new QHBoxLayout(this); - mMainLayout->addLayout(refreshLayout); - - auto refreshVectorButton = new QPushButton("Refresh map", this); - QObject::connect(refreshVectorButton, &QPushButton::clicked, this, &ViewStdMap::refreshMapContents); - refreshLayout->addWidget(refreshVectorButton); - - mRefreshDataButton = new QPushButton("Refresh data", this); - refreshLayout->addWidget(mRefreshDataButton); - QObject::connect(mRefreshDataButton, &QPushButton::clicked, this, &ViewStdMap::refreshData); - - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewStdMap::autoRefreshTimerTrigger); - - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh data every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - refreshLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewStdMap::toggleAutoRefresh); - - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("3000"); - refreshLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewStdMap::autoRefreshIntervalChanged); - - refreshLayout->addWidget(new QLabel("milliseconds", this)); - refreshLayout->addStretch(); -} + auto refreshMapButton = new QPushButton("Refresh map", this); + QObject::connect(refreshMapButton, &QPushButton::clicked, this, &ViewStdMap::refreshMapContents); + refreshLayout->addWidget(refreshMapButton); -void S2Plugin::ViewStdMap::closeEvent(QCloseEvent* event) -{ - delete this; + auto autoRefresh = new WidgetAutorefresh(100, this); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, this, &ViewStdMap::refreshData); + refreshLayout->addWidget(autoRefresh); + + mMainTreeView = new TreeViewMemoryFields(this); + mMainTreeView->setEnableChangeHighlighting(false); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColComment); + mainLayout->addWidget(mMainTreeView); + autoRefresh->toggleAutoRefresh(true); + refreshMapContents(); } void S2Plugin::ViewStdMap::refreshMapContents() { - if (!Script::Memory::IsValidPtr(Script::Memory::ReadQword(mmapOffset))) + if (!Script::Memory::IsValidPtr(Script::Memory::ReadQword(mMapAddress))) return; - StdMap the_map{mmapOffset, mMapKeyAlignment, mMapValueAlignment, mMapKeyTypeSize}; + StdMap the_map{mMapAddress, mMapKeyAlignment, mMapValueAlignment, mMapKeyTypeSize}; auto config = Configuration::get(); mMainTreeView->clear(); @@ -157,21 +121,20 @@ void S2Plugin::ViewStdMap::refreshMapContents() parent_field.name = "obj_" + std::to_string(x); parent = mMainTreeView->addMemoryField(parent_field, parent_field.name, 0, 0); - mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0, parent); + mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0, 0, parent); if (mMapValueTypeSize == 0) // StdSet continue; - mMainTreeView->addMemoryField(value_field, value_field.name, _cur.value_ptr(), 0, parent); + mMainTreeView->addMemoryField(value_field, value_field.name, _cur.value_ptr(), 0, 0, parent); } - refreshData(); - mMainTreeView->updateTableHeader(); mMainTreeView->setColumnWidth(gsColField, 145); mMainTreeView->setColumnWidth(gsColValueHex, 125); mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); mMainTreeView->setColumnWidth(gsColType, 100); mMainTreeView->setColumnWidth(gsColValue, 300); + mMainTreeView->updateTree(0, 0, true); } void S2Plugin::ViewStdMap::refreshData() @@ -188,31 +151,3 @@ QSize S2Plugin::ViewStdMap::minimumSizeHint() const { return QSize(150, 150); } - -void S2Plugin::ViewStdMap::toggleAutoRefresh(int newState) -{ - if (newState == Qt::Unchecked) - { - mAutoRefreshTimer->stop(); - mRefreshDataButton->setEnabled(true); - } - else - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshDataButton->setEnabled(false); - } -} - -void S2Plugin::ViewStdMap::autoRefreshIntervalChanged(const QString& text) -{ - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - } -} - -void S2Plugin::ViewStdMap::autoRefreshTimerTrigger() -{ - refreshData(); -} diff --git a/src/Views/ViewStdVector.cpp b/src/Views/ViewStdVector.cpp index 2769727c..b8d5f4ec 100644 --- a/src/Views/ViewStdVector.cpp +++ b/src/Views/ViewStdVector.cpp @@ -1,73 +1,40 @@ #include "Views/ViewStdVector.h" + #include "Configuration.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetAutorefresh.h" +#include "QtPlugin.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" #include "pluginmain.h" -#include -#include -#include -#include -#include - -S2Plugin::ViewStdVector::ViewStdVector(ViewToolbar* toolbar, const std::string& vectorType, uintptr_t vectorOffset, QWidget* parent) - : mVectorType(vectorType), mVectorOffset(vectorOffset), QWidget(parent) -{ - mMainLayout = new QVBoxLayout(); +#include +#include +#include +S2Plugin::ViewStdVector::ViewStdVector(const std::string& vectorType, uintptr_t vectorOffset, QWidget* parent) : mVectorType(vectorType), mVectorOffset(vectorOffset), QWidget(parent) +{ mVectorTypeSize = Configuration::get()->getTypeSize(mVectorType); - initializeRefreshLayout(); - mMainTreeView = new TreeViewMemoryFields(toolbar, this); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment); - mMainLayout->addWidget(mMainTreeView); - setWindowIcon(QIcon(":/icons/caveman.png")); - - mMainLayout->setMargin(5); - setLayout(mMainLayout); - + setWindowIcon(getCavemanIcon()); setWindowTitle(QString("std::vector<%1>").arg(QString::fromStdString(vectorType))); - mMainTreeView->setVisible(true); - - refreshVectorContents(); - toggleAutoRefresh(Qt::Checked); -} -void S2Plugin::ViewStdVector::initializeRefreshLayout() -{ + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); auto refreshLayout = new QHBoxLayout(); - mMainLayout->addLayout(refreshLayout); + mainLayout->addLayout(refreshLayout); auto refreshVectorButton = new QPushButton("Refresh vector", this); QObject::connect(refreshVectorButton, &QPushButton::clicked, this, &ViewStdVector::refreshVectorContents); refreshLayout->addWidget(refreshVectorButton); - mRefreshDataButton = new QPushButton("Refresh data", this); - refreshLayout->addWidget(mRefreshDataButton); - QObject::connect(mRefreshDataButton, &QPushButton::clicked, this, &ViewStdVector::refreshData); - - mAutoRefreshTimer = std::make_unique(this); - QObject::connect(mAutoRefreshTimer.get(), &QTimer::timeout, this, &ViewStdVector::autoRefreshTimerTrigger); - - mAutoRefreshCheckBox = new QCheckBox("Auto-refresh data every", this); - mAutoRefreshCheckBox->setCheckState(Qt::Checked); - refreshLayout->addWidget(mAutoRefreshCheckBox); - QObject::connect(mAutoRefreshCheckBox, &QCheckBox::clicked, this, &ViewStdVector::toggleAutoRefresh); + auto autoRefresh = new WidgetAutorefresh(100, this); + refreshLayout->addWidget(autoRefresh); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, this, &ViewStdVector::refreshData); - mAutoRefreshIntervalLineEdit = new QLineEdit(this); - mAutoRefreshIntervalLineEdit->setFixedWidth(50); - mAutoRefreshIntervalLineEdit->setValidator(new QIntValidator(100, 5000, this)); - mAutoRefreshIntervalLineEdit->setText("100"); - refreshLayout->addWidget(mAutoRefreshIntervalLineEdit); - QObject::connect(mAutoRefreshIntervalLineEdit, &QLineEdit::textChanged, this, &ViewStdVector::autoRefreshIntervalChanged); - - refreshLayout->addWidget(new QLabel("milliseconds", this)); - refreshLayout->addStretch(); -} - -void S2Plugin::ViewStdVector::closeEvent(QCloseEvent* event) -{ - delete this; + mMainTreeView = new TreeViewMemoryFields(this); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment); + mainLayout->addWidget(mMainTreeView); + autoRefresh->toggleAutoRefresh(true); + refreshVectorContents(); } void S2Plugin::ViewStdVector::refreshVectorContents() @@ -118,14 +85,13 @@ void S2Plugin::ViewStdVector::refreshVectorContents() field.name = "obj_" + std::to_string(x); mMainTreeView->addMemoryField(field, field.name, vectorBegin + x * mVectorTypeSize, x * mVectorTypeSize); } - refreshData(); - mMainTreeView->updateTableHeader(); mMainTreeView->setColumnWidth(gsColField, 145); mMainTreeView->setColumnWidth(gsColValueHex, 125); mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); mMainTreeView->setColumnWidth(gsColType, 100); mMainTreeView->setColumnWidth(gsColValue, 300); + mMainTreeView->updateTree(0, 0, true); } void S2Plugin::ViewStdVector::refreshData() @@ -142,31 +108,3 @@ QSize S2Plugin::ViewStdVector::minimumSizeHint() const { return QSize(150, 150); } - -void S2Plugin::ViewStdVector::toggleAutoRefresh(int newState) -{ - if (newState == Qt::Unchecked) - { - mAutoRefreshTimer->stop(); - mRefreshDataButton->setEnabled(true); - } - else - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - mAutoRefreshTimer->start(); - mRefreshDataButton->setEnabled(false); - } -} - -void S2Plugin::ViewStdVector::autoRefreshIntervalChanged(const QString& text) -{ - if (mAutoRefreshCheckBox->checkState() == Qt::Checked) - { - mAutoRefreshTimer->setInterval(mAutoRefreshIntervalLineEdit->text().toUInt()); - } -} - -void S2Plugin::ViewStdVector::autoRefreshTimerTrigger() -{ - refreshData(); -} diff --git a/src/Views/ViewStringsTable.cpp b/src/Views/ViewStringsTable.cpp index cd0b3f76..92d15491 100644 --- a/src/Views/ViewStringsTable.cpp +++ b/src/Views/ViewStringsTable.cpp @@ -1,38 +1,35 @@ #include "Views/ViewStringsTable.h" + #include "Data/StringsTable.h" #include "QtHelpers/SortFilterProxyModelStringsTable.h" +#include "QtHelpers/StyledItemDelegateHTML.h" +#include "QtPlugin.h" #include "Spelunky2.h" #include "pluginmain.h" #include -#include +#include #include -#include #include constexpr uint32_t gsRoleRawValue = 1; S2Plugin::ViewStringsTable::ViewStringsTable(QWidget* parent) : QWidget(parent) { - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); setWindowTitle(QString("Strings table (%1 strings)").arg(Spelunky2::get()->get_StringsTable().count())); - initializeUI(); -} - -void S2Plugin::ViewStringsTable::initializeUI() -{ - auto mainLayout = new QVBoxLayout(); - setLayout(mainLayout); + auto mainLayout = new QVBoxLayout(this); auto topLayout = new QHBoxLayout(); + mainLayout->addLayout(topLayout); + auto reloadButton = new QPushButton("Reload", this); topLayout->addWidget(reloadButton); QObject::connect(reloadButton, &QPushButton::clicked, this, &ViewStringsTable::reload); - mFilterLineEdit = new QLineEdit(this); - mFilterLineEdit->setPlaceholderText("Search id or text"); - QObject::connect(mFilterLineEdit, &QLineEdit::textChanged, this, &ViewStringsTable::filterTextChanged); - topLayout->addWidget(mFilterLineEdit); - mainLayout->addLayout(topLayout); + auto filterLineEdit = new QLineEdit(this); + filterLineEdit->setPlaceholderText("Search id or text"); + QObject::connect(filterLineEdit, &QLineEdit::textChanged, this, &ViewStringsTable::filterTextChanged); + topLayout->addWidget(filterLineEdit); mMainTableView = new QTableView(this); mMainTableView->setAlternatingRowColors(true); @@ -42,9 +39,10 @@ void S2Plugin::ViewStringsTable::initializeUI() mMainTableView->setEditTriggers(QAbstractItemView::NoEditTriggers); mMainTableView->horizontalHeader()->setStretchLastSection(true); mMainTableView->setSelectionBehavior(QAbstractItemView::SelectRows); - mHTMLDelegate.setCenterVertically(true); - mMainTableView->setItemDelegateForColumn(gsColStringTableOffset, &mHTMLDelegate); - mMainTableView->setItemDelegateForColumn(gsColStringMemoryOffset, &mHTMLDelegate); + auto HTMLDelegate = new StyledItemDelegateHTML(this); + HTMLDelegate->setCenterVertically(true); + mMainTableView->setItemDelegateForColumn(gsColStringTableOffset, HTMLDelegate); + mMainTableView->setItemDelegateForColumn(gsColStringMemoryOffset, HTMLDelegate); QObject::connect(mMainTableView, &QTableView::clicked, this, &ViewStringsTable::cellClicked); mainLayout->addWidget(mMainTableView); @@ -60,6 +58,7 @@ void S2Plugin::ViewStringsTable::initializeUI() mMainTableView->setColumnWidth(gsColStringMemoryOffset, 130); mMainTableView->setWordWrap(true); } + void S2Plugin::ViewStringsTable::reload() { auto& stringTable = Spelunky2::get()->get_StringsTable(); @@ -67,7 +66,7 @@ void S2Plugin::ViewStringsTable::reload() stringTable.modelCache()->setHorizontalHeaderLabels({"ID", "Table offset", "Memory offset", "Value"}); auto parrent = stringTable.modelCache()->invisibleRootItem(); - for (size_t idx = 0; idx < stringTable.count(); ++idx) + for (uint32_t idx = 0; idx < stringTable.count(); ++idx) { QStandardItem* fieldID = new QStandardItem(QString::number(idx)); auto offset = stringTable.addressOfIndex(idx); @@ -98,11 +97,6 @@ QSize S2Plugin::ViewStringsTable::minimumSizeHint() const return QSize(150, 150); } -void S2Plugin::ViewStringsTable::closeEvent(QCloseEvent* event) -{ - delete this; -} - void S2Plugin::ViewStringsTable::cellClicked(const QModelIndex& index) { if (index.column() == gsColStringTableOffset || index.column() == gsColStringMemoryOffset) diff --git a/src/Views/ViewStruct.cpp b/src/Views/ViewStruct.cpp new file mode 100644 index 00000000..588d2466 --- /dev/null +++ b/src/Views/ViewStruct.cpp @@ -0,0 +1,55 @@ +#include "Views/ViewStruct.h" + +#include "Configuration.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetAutorefresh.h" +#include "QtPlugin.h" +#include +#include +#include +#include + +S2Plugin::ViewStruct::ViewStruct(uintptr_t address, const std::vector& fields, const std::string name, QWidget* parent) : QWidget(parent) +{ + setWindowIcon(getCavemanIcon()); + setWindowTitle(QString::fromStdString(name)); + mMainTreeView = new TreeViewMemoryFields(this); + + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto refreshLayout = new QHBoxLayout(); + mainLayout->addLayout(refreshLayout); + + auto autoRefresh = new WidgetAutorefresh(100, this); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, mMainTreeView, static_cast(&TreeViewMemoryFields::updateTree)); + refreshLayout->addWidget(autoRefresh); + + refreshLayout->addStretch(); + + auto labelButton = new QPushButton("Label", this); + QObject::connect(labelButton, &QPushButton::clicked, mMainTreeView, static_cast(&TreeViewMemoryFields::labelAll)); + refreshLayout->addWidget(labelButton); + + mainLayout->addWidget(mMainTreeView); + mMainTreeView->addMemoryFields(fields, name, address); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); + mMainTreeView->setColumnWidth(gsColValue, 250); + mMainTreeView->setColumnWidth(gsColField, 125); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->updateTableHeader(); + mMainTreeView->updateTree(0, 0, true); + autoRefresh->toggleAutoRefresh(true); +} + +QSize S2Plugin::ViewStruct::sizeHint() const +{ + return QSize(750, 1050); +} + +QSize S2Plugin::ViewStruct::minimumSizeHint() const +{ + return QSize(150, 150); +} diff --git a/src/Views/ViewTextureDB.cpp b/src/Views/ViewTextureDB.cpp index cda52a01..3973ac35 100644 --- a/src/Views/ViewTextureDB.cpp +++ b/src/Views/ViewTextureDB.cpp @@ -1,142 +1,22 @@ #include "Views/ViewTextureDB.h" + #include "Configuration.h" #include "Data/TextureDB.h" -#include "QtHelpers/DatabaseHelper.h" -#include "QtHelpers/StyledItemDelegateHTML.h" -#include "QtHelpers/TableWidgetItemNumeric.h" #include "QtHelpers/TreeViewMemoryFields.h" -#include "QtHelpers/TreeWidgetItemNumeric.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" -#include "pluginmain.h" -#include #include -#include -#include -#include -#include - -S2Plugin::ViewTextureDB::ViewTextureDB(ViewToolbar* toolbar, size_t index, QWidget* parent) : QWidget(parent) -{ - mMainTreeView = new TreeViewMemoryFields(toolbar, this); - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); - setWindowTitle(QString("Texture DB (%1 textures)").arg(Spelunky2::get()->get_TextureDB().count())); - showID(index); -} - -void S2Plugin::ViewTextureDB::initializeUI() -{ - auto mainLayout = new QVBoxLayout(); - mainLayout->setMargin(5); - setLayout(mainLayout); - - mMainTabWidget = new QTabWidget(this); - mMainTabWidget->setDocumentMode(false); - mainLayout->addWidget(mMainTabWidget); - - mTabLookup = new QWidget(); // ownership passed on via addTab - mTabCompare = new QWidget(); // ovnership passed on via addTab - mTabLookup->setLayout(new QVBoxLayout()); - mTabLookup->layout()->setMargin(10); - mTabLookup->setObjectName("lookupwidget"); - mTabLookup->setStyleSheet("QWidget#lookupwidget {border: 1px solid #999;}"); - mTabCompare->setLayout(new QVBoxLayout()); - mTabCompare->layout()->setMargin(10); - mTabCompare->setObjectName("comparewidget"); - mTabCompare->setStyleSheet("QWidget#comparewidget {border: 1px solid #999;}"); - - mMainTabWidget->addTab(mTabLookup, "Lookup"); - mMainTabWidget->addTab(mTabCompare, "Compare"); - - // LOOKUP - { - auto topLayout = new QHBoxLayout(this); - - mSearchLineEdit = new QLineEdit(); - mSearchLineEdit->setPlaceholderText("Search id"); - topLayout->addWidget(mSearchLineEdit); - QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &ViewTextureDB::searchFieldReturnPressed); - mSearchLineEdit->setVisible(false); - auto textureNameCompleter = new QCompleter(Spelunky2::get()->get_TextureDB().namesStringList(), this); - textureNameCompleter->setCaseSensitivity(Qt::CaseInsensitive); - textureNameCompleter->setFilterMode(Qt::MatchContains); - QObject::connect(textureNameCompleter, static_cast(&QCompleter::activated), this, &ViewTextureDB::searchFieldCompleterActivated); - mSearchLineEdit->setCompleter(textureNameCompleter); - - auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &ViewTextureDB::label); - topLayout->addWidget(labelButton); - - dynamic_cast(mTabLookup->layout())->addLayout(topLayout); - - mMainTreeView->setEnableChangeHighlighting(false); - mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::TextureDB), "TextureDB", 0); - - QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &ViewTextureDB::fieldUpdated); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &ViewTextureDB::fieldExpanded); - mTabLookup->layout()->addWidget(mMainTreeView); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainTreeView->updateTableHeader(); - } - - // COMPARE - { - auto topLayout = new QHBoxLayout(); - mCompareFieldComboBox = new QComboBox(this); - mCompareFieldComboBox->addItem(QString::fromStdString(""), QVariant::fromValue(QString::fromStdString(""))); - DB::populateComparisonCombobox(mCompareFieldComboBox, Configuration::get()->typeFields(MemoryFieldType::TextureDB)); - - QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &ViewTextureDB::comparisonFieldChosen); - topLayout->addWidget(mCompareFieldComboBox); - - auto groupCheckbox = new QCheckBox("Group by value", this); - QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &ViewTextureDB::compareGroupByCheckBoxClicked); - topLayout->addWidget(groupCheckbox); - dynamic_cast(mTabCompare->layout())->addLayout(topLayout); - - mCompareTableWidget = new QTableWidget(Spelunky2::get()->get_TextureDB().count(), 3, this); - mCompareTableWidget->setAlternatingRowColors(true); - mCompareTableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); - mCompareTableWidget->setHorizontalHeaderLabels(QStringList() << "ID" - << "Name" - << "Value"); - mCompareTableWidget->verticalHeader()->setVisible(false); - mCompareTableWidget->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - mCompareTableWidget->verticalHeader()->setDefaultSectionSize(20); - mCompareTableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - mCompareTableWidget->setColumnWidth(0, 40); - mCompareTableWidget->setColumnWidth(1, 325); - mCompareTableWidget->setColumnWidth(2, 150); - mCompareTableWidget->setItemDelegate(&mHTMLDelegate); - QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &ViewTextureDB::comparisonCellClicked); - - mCompareTreeWidget = new QTreeWidget(this); - mCompareTreeWidget->setAlternatingRowColors(true); - mCompareTreeWidget->headerItem()->setHidden(true); - mCompareTreeWidget->setHidden(true); - mCompareTreeWidget->setItemDelegate(&mHTMLDelegate); - QObject::connect(mCompareTreeWidget, &QTreeWidget::itemClicked, this, &ViewTextureDB::groupedComparisonItemClicked); - - mTabCompare->layout()->addWidget(mCompareTableWidget); - mTabCompare->layout()->addWidget(mCompareTreeWidget); - } - - mSearchLineEdit->setVisible(true); - mSearchLineEdit->setFocus(); - mMainTreeView->setVisible(true); - mMainTreeView->setColumnWidth(gsColField, 125); - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); - mMainTreeView->setColumnWidth(gsColType, 100); -} - -void S2Plugin::ViewTextureDB::closeEvent(QCloseEvent* event) +S2Plugin::ViewTextureDB::ViewTextureDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::TextureDB, parent) { - delete this; + auto& textureDB = Spelunky2::get()->get_TextureDB(); + setWindowTitle(QString("Texture DB (%1 textures)").arg(textureDB.count())); + auto textureNameCompleter = new QCompleter(textureDB.namesStringList(), this); + textureNameCompleter->setCaseSensitivity(Qt::CaseInsensitive); + textureNameCompleter->setFilterMode(Qt::MatchContains); + QObject::connect(textureNameCompleter, static_cast(&QCompleter::activated), this, &ViewTextureDB::searchFieldCompleterActivated); + mSearchLineEdit->setCompleter(textureNameCompleter); + mCompareTableWidget->setRowCount(static_cast(textureDB.count())); + showID(0); } QSize S2Plugin::ViewTextureDB::sizeHint() const @@ -144,50 +24,36 @@ QSize S2Plugin::ViewTextureDB::sizeHint() const return QSize(750, 375); } -QSize S2Plugin::ViewTextureDB::minimumSizeHint() const -{ - return QSize(150, 150); -} - -void S2Plugin::ViewTextureDB::searchFieldReturnPressed() +void S2Plugin::ViewTextureDB::searchFieldCompleterActivated(const QString& text) { - auto text = mSearchLineEdit->text(); - bool isNumeric = false; - auto enteredID = text.toUInt(&isNumeric); - if (isNumeric && Spelunky2::get()->get_TextureDB().isValidID(enteredID)) - { - showID(enteredID); - } - else + auto id = getIDForName(text); + if (id.has_value()) { - static const QRegularExpression r("^Texture ([0-9]+)"); - auto m = r.match(text); - if (m.isValid()) - { - auto textureID = m.captured(1).toUInt(); - showID(textureID); - } + showID(id.value()); } } -void S2Plugin::ViewTextureDB::searchFieldCompleterActivated(const QString& text) +void S2Plugin::ViewTextureDB::showRAW(uintptr_t address) { - searchFieldReturnPressed(); + mMainTreeView->updateTree(address); } -void S2Plugin::ViewTextureDB::showID(uint32_t id) +void S2Plugin::ViewTextureDB::showID(ID_type id) { - mMainTabWidget->setCurrentWidget(mTabLookup); + if (!isValidRecordID(id)) + return; + + switchToLookupTab(); auto offset = Spelunky2::get()->get_TextureDB().addressOfID(id); mMainTreeView->updateTree(offset); } -void S2Plugin::ViewTextureDB::label() +void S2Plugin::ViewTextureDB::label() const { auto model = mMainTreeView->model(); std::string name; auto& textureDB = Spelunky2::get()->get_TextureDB(); - for (uint idx = 0; idx < model->rowCount(); ++idx) + for (int idx = 0; idx < model->rowCount(); ++idx) { if (model->data(model->index(idx, gsColField), Qt::DisplayRole).toString() == "id") { @@ -199,128 +65,32 @@ void S2Plugin::ViewTextureDB::label() mMainTreeView->labelAll(name); } -void S2Plugin::ViewTextureDB::fieldUpdated(const QString& fieldName) +S2Plugin::ID_type S2Plugin::ViewTextureDB::highestRecordID() const { - updateFieldValues(); + return static_cast(Spelunky2::get()->get_TextureDB().highestID()); } -void S2Plugin::ViewTextureDB::fieldExpanded(const QModelIndex& index) +bool S2Plugin::ViewTextureDB::isValidRecordID(ID_type id) const { - updateFieldValues(); + return Spelunky2::get()->get_TextureDB().isValidID(id); } -void S2Plugin::ViewTextureDB::updateFieldValues() +std::optional S2Plugin::ViewTextureDB::getIDForName(QString name) const { - mMainTreeView->updateTree(); -} + static const QRegularExpression r("^Texture ([0-9]+)"); + auto m = r.match(name); + if (m.isValid()) + return m.captured(1).toUInt(); -void S2Plugin::ViewTextureDB::compareGroupByCheckBoxClicked(int state) -{ - mCompareTableWidget->setHidden(state == Qt::Checked); - mCompareTreeWidget->setHidden(state == Qt::Unchecked); + return std::nullopt; } -void S2Plugin::ViewTextureDB::comparisonFieldChosen(const QString& fieldName) +QString S2Plugin::ViewTextureDB::recordNameForID(ID_type id) const { - mCompareTableWidget->clearContents(); - mCompareTreeWidget->clear(); - - auto comboIndex = mCompareFieldComboBox->currentIndex(); - if (comboIndex == 0) - { - return; - } - - populateComparisonTableWidget(); - populateComparisonTreeWidget(); + return QString::fromStdString(Spelunky2::get()->get_TextureDB().nameForID(id)); } -void S2Plugin::ViewTextureDB::populateComparisonTableWidget() +uintptr_t S2Plugin::ViewTextureDB::addressOfRecordID(ID_type id) const { - mCompareTableWidget->setSortingEnabled(false); - - auto comboboxData = mCompareFieldComboBox->currentData(); - auto& textureDB = Spelunky2::get()->get_TextureDB(); - - size_t row = 0; - for (auto& [textureID, textureData] : textureDB.textures()) - { - auto item0 = new QTableWidgetItem(QString::asprintf("%03d", textureID)); - item0->setTextAlignment(Qt::AlignCenter); - mCompareTableWidget->setItem(row, 0, item0); - auto name = QString("Texture %1 (%2)").arg(textureID).arg(QString::fromStdString(textureData.first)); - mCompareTableWidget->setItem(row, 1, new QTableWidgetItem(QString("%1").arg(name))); - - auto [caption, value] = DB::valueForField(comboboxData, textureData.second); - auto item = new TableWidgetItemNumeric(caption); - item->setData(Qt::UserRole, value); - mCompareTableWidget->setItem(row, 2, item); - - row++; - } - mCompareTableWidget->setSortingEnabled(true); - mCompareTableWidget->sortItems(0); -} - -void S2Plugin::ViewTextureDB::populateComparisonTreeWidget() -{ - mCompareTreeWidget->setSortingEnabled(false); - - auto comboboxData = mCompareFieldComboBox->currentData(); - auto& textureDB = Spelunky2::get()->get_TextureDB(); - - std::unordered_map rootValues; - std::unordered_map> groupedValues; // valueString -> set - for (auto& [textureID, textureData] : textureDB.textures()) - { - auto [caption, value] = DB::valueForField(comboboxData, textureData.second); - auto captionStr = caption.toStdString(); - rootValues[captionStr] = value; - - if (groupedValues.count(captionStr) == 0) - { - groupedValues[captionStr] = {textureID}; - } - else - { - groupedValues[captionStr].insert(textureID); - } - } - - for (const auto& [groupString, textureIds] : groupedValues) - { - auto rootItem = new TreeWidgetItemNumeric(nullptr, QString::fromStdString(groupString)); - rootItem->setData(0, Qt::UserRole, rootValues.at(groupString)); - mCompareTreeWidget->insertTopLevelItem(0, rootItem); - for (const auto& textureId : textureIds) - { - auto textureName = QString("Texture %1 (%2)").arg(textureId).arg(QString::fromStdString(textureDB.nameForID(textureId))); - auto caption = QString("%1").arg(textureName); - auto childItem = new QTreeWidgetItem(rootItem, QStringList(caption)); - childItem->setData(0, Qt::UserRole, textureId); - mCompareTreeWidget->insertTopLevelItem(0, childItem); - } - } - - mCompareTreeWidget->setSortingEnabled(true); - mCompareTreeWidget->sortItems(0, Qt::AscendingOrder); -} - -void S2Plugin::ViewTextureDB::comparisonCellClicked(int row, int column) -{ - if (column == 1) - { - mSearchLineEdit->clear(); - auto clickedID = mCompareTableWidget->item(row, 0)->data(Qt::DisplayRole).toULongLong(); - showID(clickedID); - } -} - -void S2Plugin::ViewTextureDB::groupedComparisonItemClicked(QTreeWidgetItem* item, int column) -{ - if (item->childCount() == 0) - { - mSearchLineEdit->clear(); - showID(item->data(0, Qt::UserRole).toUInt()); - } + return Spelunky2::get()->get_TextureDB().addressOfID(id); } diff --git a/src/Views/ViewThreads.cpp b/src/Views/ViewThreads.cpp index 6873a418..50332207 100644 --- a/src/Views/ViewThreads.cpp +++ b/src/Views/ViewThreads.cpp @@ -1,11 +1,14 @@ #include "Views/ViewThreads.h" -#include "Configuration.h" -#include "Data/State.h" + +#include "QtHelpers/StyledItemDelegateHTML.h" +#include "QtPlugin.h" #include "Spelunky2.h" #include "Views/ViewToolbar.h" #include "pluginmain.h" #include #include +#include +#include static const uint32_t gsColThreadName = 0; static const uint32_t gsColTEBAddress = 1; @@ -13,28 +16,18 @@ static const uint32_t gsColStateAddress = 2; static const uint32_t gsRoleMemoryAddress = Qt::UserRole + 1; -// TODO review (crashes) - -// TODO: check for null in click event, add more columns, add spel2 function to get ptr based on the different heapbase - -S2Plugin::ViewThreads::ViewThreads(ViewToolbar* toolbar) : QWidget(toolbar), mToolbar(toolbar) +S2Plugin::ViewThreads::ViewThreads(QWidget* parent) : QWidget(parent) { - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); setWindowTitle("Threads"); - refreshThreads(); -} - -void S2Plugin::ViewThreads::initializeUI() -{ - mMainLayout = new QVBoxLayout(this); - auto horLayout = new QHBoxLayout(this); + auto mainLayout = new QVBoxLayout(this); + auto horLayout = new QHBoxLayout(); auto refreshButton = new QPushButton("Refresh", this); horLayout->addWidget(refreshButton); QObject::connect(refreshButton, &QPushButton::clicked, this, &ViewThreads::refreshThreads); horLayout->addStretch(); - mMainLayout->addLayout(horLayout); + mainLayout->addLayout(horLayout); mMainTable = new QTableWidget(this); mMainTable->setColumnCount(3); @@ -46,11 +39,13 @@ void S2Plugin::ViewThreads::initializeUI() mMainTable->horizontalHeader()->setStretchLastSection(true); mMainTable->setSelectionBehavior(QAbstractItemView::SelectRows); mMainTable->setSelectionMode(QAbstractItemView::SingleSelection); - mMainTable->setItemDelegate(&mHTMLDelegate); - mHTMLDelegate.setCenterVertically(true); + auto HTMLDelegate = new StyledItemDelegateHTML(this); + mMainTable->setItemDelegate(HTMLDelegate); + HTMLDelegate->setCenterVertically(true); QObject::connect(mMainTable, &QTableWidget::cellClicked, this, &ViewThreads::cellClicked); - mMainLayout->addWidget(mMainTable); + mainLayout->addWidget(mMainTable); + refreshThreads(); } void S2Plugin::ViewThreads::refreshThreads() @@ -127,11 +122,6 @@ void S2Plugin::ViewThreads::refreshThreads() } } -void S2Plugin::ViewThreads::closeEvent(QCloseEvent* event) -{ - delete this; -} - QSize S2Plugin::ViewThreads::sizeHint() const { return QSize(550, 375); @@ -147,12 +137,17 @@ void S2Plugin::ViewThreads::cellClicked(int row, int column) auto clickedItem = mMainTable->item(row, column); if (column == gsColTEBAddress) { - GuiDumpAt(clickedItem->data(gsRoleMemoryAddress).toULongLong()); - GuiShowCpu(); + auto addr = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (addr != 0) + { + GuiDumpAt(addr); + GuiShowCpu(); + } } else if (column == gsColStateAddress) { auto statePtr = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - mToolbar->showState(statePtr); + if (statePtr != 0) + getToolbar()->showState(statePtr); } } diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index a155c588..b4972599 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -1,21 +1,19 @@ #include "Views/ViewToolbar.h" + #include "Configuration.h" #include "Spelunky2.h" #include "Views/ViewCharacterDB.h" #include "Views/ViewEntities.h" #include "Views/ViewEntity.h" #include "Views/ViewEntityDB.h" -#include "Views/ViewGameManager.h" #include "Views/ViewJournalPage.h" #include "Views/ViewLevelGen.h" #include "Views/ViewLogger.h" -#include "Views/ViewOnline.h" #include "Views/ViewParticleDB.h" -#include "Views/ViewSaveGame.h" -#include "Views/ViewState.h" #include "Views/ViewStdMap.h" #include "Views/ViewStdVector.h" #include "Views/ViewStringsTable.h" +#include "Views/ViewStruct.h" #include "Views/ViewTextureDB.h" #include "Views/ViewThreads.h" #include "Views/ViewVirtualFunctions.h" @@ -24,118 +22,180 @@ #include #include #include +#include S2Plugin::ViewToolbar::ViewToolbar(QMdiArea* mdiArea, QWidget* parent) : QDockWidget(parent, Qt::WindowFlags()), mMDIArea(mdiArea) { setFeatures(QDockWidget::NoDockWidgetFeatures); - mMainLayout = new QVBoxLayout(this); + auto mainLayout = new QVBoxLayout(); auto container = new QWidget(this); - container->setLayout(mMainLayout); + container->setLayout(mainLayout); setWidget(container); setTitleBarWidget(new QWidget(this)); auto btnEntityDB = new QPushButton(this); btnEntityDB->setText("Entity DB"); - mMainLayout->addWidget(btnEntityDB); + mainLayout->addWidget(btnEntityDB); QObject::connect(btnEntityDB, &QPushButton::clicked, this, &ViewToolbar::showEntityDB); auto btnTextureDB = new QPushButton(this); btnTextureDB->setText("Texture DB"); - mMainLayout->addWidget(btnTextureDB); + mainLayout->addWidget(btnTextureDB); QObject::connect(btnTextureDB, &QPushButton::clicked, this, &ViewToolbar::showTextureDB); auto btnParticleDB = new QPushButton(this); btnParticleDB->setText("Particle DB"); - mMainLayout->addWidget(btnParticleDB); + mainLayout->addWidget(btnParticleDB); QObject::connect(btnParticleDB, &QPushButton::clicked, this, &ViewToolbar::showParticleDB); auto btnStringsTable = new QPushButton(this); btnStringsTable->setText("Strings DB"); - mMainLayout->addWidget(btnStringsTable); + mainLayout->addWidget(btnStringsTable); QObject::connect(btnStringsTable, &QPushButton::clicked, this, &ViewToolbar::showStringsTable); auto btnCharacterDB = new QPushButton(this); btnCharacterDB->setText("Character DB"); - mMainLayout->addWidget(btnCharacterDB); + mainLayout->addWidget(btnCharacterDB); QObject::connect(btnCharacterDB, &QPushButton::clicked, this, &ViewToolbar::showCharacterDB); auto divider = new QFrame(this); divider->setFrameShape(QFrame::HLine); divider->setFrameShadow(QFrame::Sunken); - mMainLayout->addWidget(divider); + mainLayout->addWidget(divider); auto btnState = new QPushButton(this); btnState->setText("State"); - mMainLayout->addWidget(btnState); + mainLayout->addWidget(btnState); QObject::connect(btnState, &QPushButton::clicked, this, &ViewToolbar::showMainThreadState); auto btnEntities = new QPushButton(this); btnEntities->setText("Entities"); - mMainLayout->addWidget(btnEntities); + mainLayout->addWidget(btnEntities); QObject::connect(btnEntities, &QPushButton::clicked, this, &ViewToolbar::showEntities); auto btnLevelGen = new QPushButton(this); btnLevelGen->setText("LevelGen"); - mMainLayout->addWidget(btnLevelGen); - QObject::connect(btnLevelGen, &QPushButton::clicked, this, &ViewToolbar::showLevelGen); + mainLayout->addWidget(btnLevelGen); + QObject::connect(btnLevelGen, &QPushButton::clicked, this, &ViewToolbar::showMainThreadLevelGen); auto btnGameManager = new QPushButton(this); btnGameManager->setText("GameManager"); - mMainLayout->addWidget(btnGameManager); + mainLayout->addWidget(btnGameManager); QObject::connect(btnGameManager, &QPushButton::clicked, this, &ViewToolbar::showGameManager); auto btnSaveGame = new QPushButton(this); btnSaveGame->setText("SaveGame"); - mMainLayout->addWidget(btnSaveGame); + mainLayout->addWidget(btnSaveGame); QObject::connect(btnSaveGame, &QPushButton::clicked, this, &ViewToolbar::showSaveGame); auto btnOnline = new QPushButton(this); btnOnline->setText("Online"); - mMainLayout->addWidget(btnOnline); + mainLayout->addWidget(btnOnline); QObject::connect(btnOnline, &QPushButton::clicked, this, &ViewToolbar::showOnline); auto btnVirtualTable = new QPushButton(this); btnVirtualTable->setText("Virtual Table"); - mMainLayout->addWidget(btnVirtualTable); + mainLayout->addWidget(btnVirtualTable); QObject::connect(btnVirtualTable, &QPushButton::clicked, this, &ViewToolbar::showVirtualTableLookup); auto divider2 = new QFrame(this); divider2->setFrameShape(QFrame::HLine); divider2->setFrameShadow(QFrame::Sunken); - mMainLayout->addWidget(divider2); + mainLayout->addWidget(divider2); auto btnLogger = new QPushButton(this); btnLogger->setText("Logger"); - mMainLayout->addWidget(btnLogger); + mainLayout->addWidget(btnLogger); QObject::connect(btnLogger, &QPushButton::clicked, this, &ViewToolbar::showLogger); auto btnThreads = new QPushButton(this); btnThreads->setText("Threads"); - mMainLayout->addWidget(btnThreads); + mainLayout->addWidget(btnThreads); QObject::connect(btnThreads, &QPushButton::clicked, this, &ViewToolbar::showThreads); - mMainLayout->addStretch(); + mainLayout->addStretch(); auto btnClearLabels = new QPushButton(this); btnClearLabels->setText("Clear labels"); - mMainLayout->addWidget(btnClearLabels); + mainLayout->addWidget(btnClearLabels); QObject::connect(btnClearLabels, &QPushButton::clicked, this, &ViewToolbar::clearLabels); auto btnReloadConfig = new QPushButton(this); btnReloadConfig->setText("Reload JSON"); - mMainLayout->addWidget(btnReloadConfig); + mainLayout->addWidget(btnReloadConfig); QObject::connect(btnReloadConfig, &QPushButton::clicked, this, &ViewToolbar::reloadConfig); } +void S2Plugin::ViewToolbar::showVirtualFunctions(uintptr_t address, const std::string& typeName) +{ + auto w = new ViewVirtualFunctions(typeName, address); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +void S2Plugin::ViewToolbar::showStdVector(uintptr_t address, const std::string& typeName) +{ + auto w = new ViewStdVector(typeName, address); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +void S2Plugin::ViewToolbar::showStdMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName) +{ + auto w = new ViewStdMap(keytypeName, valuetypeName, address); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +void S2Plugin::ViewToolbar::showJournalPage(uintptr_t address) +{ + auto w = new ViewJournalPage(address); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +void S2Plugin::ViewToolbar::showState(uintptr_t address) +{ + auto w = new ViewStruct(address, Configuration::get()->typeFields(MemoryFieldType::State), "State"); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +void S2Plugin::ViewToolbar::showLevelGen(uintptr_t address) +{ + auto w = new ViewLevelGen(address); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +void S2Plugin::ViewToolbar::showEntity(uintptr_t address) +{ + auto w = new ViewEntity(address); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +// +// slots: +// + S2Plugin::ViewEntityDB* S2Plugin::ViewToolbar::showEntityDB() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_EntityDB().isValid()) { - auto w = new ViewEntityDB(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewEntityDB(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); return w; } return nullptr; @@ -145,9 +205,10 @@ S2Plugin::ViewParticleDB* S2Plugin::ViewToolbar::showParticleDB() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_ParticleDB().isValid()) { - auto w = new ViewParticleDB(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewParticleDB(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); return w; } return nullptr; @@ -157,9 +218,10 @@ S2Plugin::ViewTextureDB* S2Plugin::ViewToolbar::showTextureDB() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_TextureDB().isValid()) { - auto w = new ViewTextureDB(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewTextureDB(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); return w; } return nullptr; @@ -169,9 +231,10 @@ S2Plugin::ViewCharacterDB* S2Plugin::ViewToolbar::showCharacterDB() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_StringsTable().isValid() && Spelunky2::get()->get_CharacterDB().isValid()) { - auto w = new ViewCharacterDB(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewCharacterDB(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); return w; } return nullptr; @@ -179,7 +242,7 @@ S2Plugin::ViewCharacterDB* S2Plugin::ViewToolbar::showCharacterDB() void S2Plugin::ViewToolbar::showMainThreadState() { - if (Spelunky2::is_loaded()) + if (Spelunky2::is_loaded() && Configuration::is_loaded()) { auto statePtr = Spelunky2::get()->get_StatePtr(); if (statePtr != 0) @@ -187,13 +250,13 @@ void S2Plugin::ViewToolbar::showMainThreadState() } } -void S2Plugin::ViewToolbar::showState(uintptr_t state) +void S2Plugin::ViewToolbar::showMainThreadLevelGen() { if (Spelunky2::is_loaded() && Configuration::is_loaded()) { - auto w = new ViewState(this, state); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto levelGenPtr = Spelunky2::get()->get_LevelGenPtr(); + if (levelGenPtr != 0) + showLevelGen(levelGenPtr); } } @@ -201,19 +264,10 @@ void S2Plugin::ViewToolbar::showGameManager() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_GameManagerPtr() != 0) { - auto w = new ViewGameManager(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); - } -} - -void S2Plugin::ViewToolbar::showLevelGen() -{ - if (Spelunky2::is_loaded() && Configuration::is_loaded()) - { - auto w = new ViewLevelGen(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewStruct(Spelunky2::get()->get_GameManagerPtr(), Configuration::get()->typeFields(MemoryFieldType::GameManager), "GameManager"); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); } } @@ -221,9 +275,10 @@ S2Plugin::ViewVirtualTable* S2Plugin::ViewToolbar::showVirtualTableLookup() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_VirtualTableLookup().isValid()) { - auto w = new ViewVirtualTable(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewVirtualTable(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); return w; } return nullptr; @@ -233,9 +288,10 @@ void S2Plugin::ViewToolbar::showStringsTable() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_StringsTable().isValid()) { - auto w = new ViewStringsTable(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewStringsTable(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); } } @@ -243,26 +299,21 @@ void S2Plugin::ViewToolbar::showOnline() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_OnlinePtr() != 0) { - auto w = new ViewOnline(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewStruct(Spelunky2::get()->get_OnlinePtr(), Configuration::get()->typeFields(MemoryFieldType::Online), "Online"); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); } } -void S2Plugin::ViewToolbar::showEntity(uintptr_t offset) -{ - auto w = new ViewEntity(offset, this); - mMDIArea->addSubWindow(w); - w->setVisible(true); -} - void S2Plugin::ViewToolbar::showEntities() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_EntityDB().isValid()) { - auto w = new ViewEntities(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewEntities(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); } } @@ -270,60 +321,35 @@ void S2Plugin::ViewToolbar::showSaveGame() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_SaveDataPtr() != 0) { - auto w = new ViewSaveGame(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewStruct(Spelunky2::get()->get_SaveDataPtr(), Configuration::get()->typeFields(MemoryFieldType::SaveGame), "SaveGame"); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); } } void S2Plugin::ViewToolbar::showLogger() { - auto w = new ViewLogger(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); -} - -void S2Plugin::ViewToolbar::showVirtualFunctions(size_t offset, const std::string& typeName) -{ - auto w = new ViewVirtualFunctions(typeName, offset, this, this); - mMDIArea->addSubWindow(w); - w->setVisible(true); -} - -void S2Plugin::ViewToolbar::showStdVector(size_t offset, const std::string& typeName) -{ - auto w = new ViewStdVector(this, typeName, offset, this); - mMDIArea->addSubWindow(w); - w->setVisible(true); -} - -void S2Plugin::ViewToolbar::showStdMap(size_t offset, const std::string& keytypeName, const std::string& valuetypeName) -{ - auto w = new ViewStdMap(this, keytypeName, valuetypeName, offset, this); - mMDIArea->addSubWindow(w); - w->setVisible(true); -} - -void S2Plugin::ViewToolbar::showJournalPage(size_t offset, const std::string& pageType) -{ - auto w = new ViewJournalPage(this, offset, pageType, this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewLogger(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); } void S2Plugin::ViewToolbar::showThreads() { if (Spelunky2::is_loaded() && Configuration::is_loaded()) { - auto w = new ViewThreads(this); - mMDIArea->addSubWindow(w); - w->setVisible(true); + auto w = new ViewThreads(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); } } void S2Plugin::ViewToolbar::clearLabels() { - // -1 since the full max value causes some overflow? and removes all labels, not only the automatic ones + // (-1) since the full max value causes some overflow(?) and removes all labels, not only the automatic ones DbgClearAutoLabelRange(0, std::numeric_limits::max() - 1); } @@ -339,9 +365,3 @@ void S2Plugin::ViewToolbar::reloadConfig() } Configuration::reload(); } - -void S2Plugin::ViewToolbar::resetSpelunky2Data() -{ - mMDIArea->closeAllSubWindows(); - Spelunky2::reset(); -} diff --git a/src/Views/ViewVirtualFunctions.cpp b/src/Views/ViewVirtualFunctions.cpp index e34f5be8..0328c90d 100644 --- a/src/Views/ViewVirtualFunctions.cpp +++ b/src/Views/ViewVirtualFunctions.cpp @@ -1,67 +1,58 @@ #include "Views/ViewVirtualFunctions.h" + #include "QtHelpers/ItemModelVirtualFunctions.h" #include "QtHelpers/StyledItemDelegateHTML.h" -#include "Views/ViewToolbar.h" +#include "QtPlugin.h" #include "pluginmain.h" #include #include #include +#include -S2Plugin::ViewVirtualFunctions::ViewVirtualFunctions(const std::string& typeName, size_t offset, ViewToolbar* toolbar, QWidget* parent) - : QWidget(parent), mTypeName(typeName), mMemoryOffset(offset), mToolbar(toolbar) +S2Plugin::ViewVirtualFunctions::ViewVirtualFunctions(const std::string& typeName, uintptr_t address, QWidget* parent) : QWidget(parent), mMemoryAddress(address) { - mModel = std::make_unique(typeName, offset, toolbar, this); - mSortFilterProxy = std::make_unique(typeName, toolbar, this); - - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); setWindowTitle(QString("Virtual Functions of %1").arg(QString::fromStdString(typeName))); -} - -void S2Plugin::ViewVirtualFunctions::initializeUI() -{ - mMainLayout = new QVBoxLayout(this); - mMainLayout->setMargin(5); - setLayout(mMainLayout); - mTopLayout = new QHBoxLayout(this); - mMainLayout->addLayout(mTopLayout); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto topLayout = new QHBoxLayout(); + mainLayout->addLayout(topLayout); - mTopLayout->addWidget(new QLabel("Jump to function at index:", this)); + topLayout->addWidget(new QLabel("Jump to function at index:", this)); mJumpToLineEdit = new QLineEdit(this); mJumpToLineEdit->setValidator(new QIntValidator(0, 5000, this)); mJumpToLineEdit->setFixedWidth(100); - mTopLayout->addWidget(mJumpToLineEdit); + topLayout->addWidget(mJumpToLineEdit); auto jumpBtn = new QPushButton("Jump", this); QObject::connect(jumpBtn, &QPushButton::clicked, this, &ViewVirtualFunctions::jumpToFunction); - mTopLayout->addWidget(jumpBtn); - mTopLayout->addStretch(); + topLayout->addWidget(jumpBtn); + topLayout->addStretch(); - mHTMLDelegate.setCenterVertically(true); + auto HTMLDelegate = new StyledItemDelegateHTML(this); + HTMLDelegate->setCenterVertically(true); + + auto model = new ItemModelVirtualFunctions(typeName, mMemoryAddress, this); + auto sortFilterProxy = new SortFilterProxyModelVirtualFunctions(this); + sortFilterProxy->setSourceModel(model); mFunctionsTable = new QTableView(this); - mSortFilterProxy->setSourceModel(mModel.get()); - mFunctionsTable->setModel(mSortFilterProxy.get()); + mFunctionsTable->setModel(sortFilterProxy); mFunctionsTable->setAlternatingRowColors(true); mFunctionsTable->setSelectionBehavior(QAbstractItemView::SelectRows); mFunctionsTable->horizontalHeader()->setStretchLastSection(true); - mFunctionsTable->setItemDelegate(&mHTMLDelegate); + mFunctionsTable->setItemDelegate(HTMLDelegate); mFunctionsTable->setColumnWidth(gsColFunctionIndex, 50); mFunctionsTable->setColumnWidth(gsColFunctionTableAddress, 130); mFunctionsTable->setColumnWidth(gsColFunctionFunctionAddress, 130); - mSortFilterProxy->sort(0); - mMainLayout->addWidget(mFunctionsTable); + sortFilterProxy->sort(0); + mainLayout->addWidget(mFunctionsTable); QObject::connect(mFunctionsTable, &QTableView::clicked, this, &ViewVirtualFunctions::tableEntryClicked); } -void S2Plugin::ViewVirtualFunctions::closeEvent(QCloseEvent* event) -{ - delete this; -} - QSize S2Plugin::ViewVirtualFunctions::sizeHint() const { return QSize(650, 450); @@ -74,10 +65,8 @@ QSize S2Plugin::ViewVirtualFunctions::minimumSizeHint() const void S2Plugin::ViewVirtualFunctions::tableEntryClicked(const QModelIndex& index) { - auto mappedIndex = mSortFilterProxy->mapToSource(index); - - auto column = mappedIndex.column(); - switch (column) + auto model = mFunctionsTable->model(); + switch (index.column()) { case gsColFunctionIndex: case gsColFunctionSignature: @@ -85,14 +74,14 @@ void S2Plugin::ViewVirtualFunctions::tableEntryClicked(const QModelIndex& index) break; case gsColFunctionFunctionAddress: { - auto address = mModel->data(mappedIndex, gsRoleFunctionFunctionAddress).value(); + auto address = model->data(index, gsRoleFunctionFunctionAddress).value(); GuiDisasmAt(address, GetContextData(UE_CIP)); GuiShowCpu(); break; } case gsColFunctionTableAddress: { - auto address = mModel->data(mappedIndex, gsRoleFunctionTableAddress).value(); + auto address = model->data(index, gsRoleFunctionTableAddress).value(); GuiDumpAt(address); GuiShowCpu(); break; @@ -100,9 +89,9 @@ void S2Plugin::ViewVirtualFunctions::tableEntryClicked(const QModelIndex& index) } } -void S2Plugin::ViewVirtualFunctions::jumpToFunction(bool b) +void S2Plugin::ViewVirtualFunctions::jumpToFunction() { - auto address = Script::Memory::ReadQword(mMemoryOffset + (mJumpToLineEdit->text().toUInt() * 8ull)); + auto address = Script::Memory::ReadQword(mMemoryAddress + (mJumpToLineEdit->text().toUInt() * 8ull)); GuiDisasmAt(address, GetContextData(UE_CIP)); GuiShowCpu(); } diff --git a/src/Views/ViewVirtualTable.cpp b/src/Views/ViewVirtualTable.cpp index 666073f8..56c34980 100644 --- a/src/Views/ViewVirtualTable.cpp +++ b/src/Views/ViewVirtualTable.cpp @@ -1,65 +1,73 @@ #include "Views/ViewVirtualTable.h" + #include "Data/VirtualTableLookup.h" #include "QtHelpers/ItemModelGatherVirtualData.h" #include "QtHelpers/ItemModelVirtualTable.h" +#include "QtHelpers/StyledItemDelegateHTML.h" #include "QtHelpers/TableWidgetItemNumeric.h" +#include "QtPlugin.h" #include "Spelunky2.h" -#include "Views/ViewToolbar.h" #include "pluginmain.h" #include #include #include #include #include -#include #include #include +#include #include S2Plugin::ViewVirtualTable::ViewVirtualTable(QWidget* parent) : QWidget(parent) { - mModel = std::make_unique(this); - mSortFilterProxy = std::make_unique(this); - mGatherModel = std::make_unique(this); - mGatherSortFilterProxy = std::make_unique(this); + mModel = new ItemModelVirtualTable(this); + mSortFilterProxy = new SortFilterProxyModelVirtualTable(this); + mGatherModel = new ItemModelGatherVirtualData(this); + mGatherSortFilterProxy = new SortFilterProxyModelGatherVirtualData(this); mGatherSortFilterProxy->sort(gsColGatherID); - initializeUI(); - setWindowIcon(QIcon(":/icons/caveman.png")); + setWindowIcon(getCavemanIcon()); setWindowTitle("Virtual Table"); + initializeUI(); } +enum TABS +{ + DATA = 0, + LOOKUP = 1, + GATHER = 2, +}; + void S2Plugin::ViewVirtualTable::initializeUI() { - mMainLayout = new QVBoxLayout(this); - mMainLayout->setMargin(5); - setLayout(mMainLayout); + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); - mMainTabWidget = new QTabWidget(this); + mMainTabWidget = new QTabWidget(); mMainTabWidget->setDocumentMode(false); - mMainLayout->addWidget(mMainTabWidget); - - mTabData = new QWidget(); - mTabData->setLayout(new QVBoxLayout(mTabData)); - mTabData->layout()->setMargin(10); - mTabData->setObjectName("datawidget"); - mTabData->setStyleSheet("QWidget#datawidget {border: 1px solid #999;}"); - - mTabLookup = new QWidget(); - mTabLookup->setLayout(new QVBoxLayout(mTabLookup)); - mTabLookup->layout()->setMargin(10); - mTabLookup->setObjectName("lookupwidget"); - mTabLookup->setStyleSheet("QWidget#lookupwidget {border: 1px solid #999;}"); - - mTabGather = new QWidget(); - mTabGather->setLayout(new QVBoxLayout(mTabGather)); - mTabGather->layout()->setMargin(10); - mTabGather->setObjectName("gatherwidget"); - mTabGather->setStyleSheet("QWidget#gatherwidget {border: 1px solid #999;}"); - - mMainTabWidget->addTab(mTabData, "Data"); - mMainTabWidget->addTab(mTabLookup, "Lookup"); - mMainTabWidget->addTab(mTabGather, "Gather"); + mainLayout->addWidget(mMainTabWidget); + + auto tabData = new QWidget(); + tabData->setLayout(new QVBoxLayout()); + tabData->layout()->setMargin(10); + tabData->setObjectName("datawidget"); + tabData->setStyleSheet("QWidget#datawidget {border: 1px solid #999;}"); + + auto tabLookup = new QWidget(); + tabLookup->setLayout(new QVBoxLayout()); + tabLookup->layout()->setMargin(10); + tabLookup->setObjectName("lookupwidget"); + tabLookup->setStyleSheet("QWidget#lookupwidget {border: 1px solid #999;}"); + + auto tabGather = new QWidget(); + tabGather->setLayout(new QVBoxLayout()); + tabGather->layout()->setMargin(10); + tabGather->setObjectName("gatherwidget"); + tabGather->setStyleSheet("QWidget#gatherwidget {border: 1px solid #999;}"); + + mMainTabWidget->addTab(tabData, "Data"); + mMainTabWidget->addTab(tabLookup, "Lookup"); + mMainTabWidget->addTab(tabGather, "Gather"); // TAB DATA { @@ -92,17 +100,17 @@ void S2Plugin::ViewVirtualTable::initializeUI() auto tmpLayout = new QHBoxLayout(this); tmpLayout->addLayout(topLayout); tmpLayout->addStretch(); - dynamic_cast(mTabData->layout())->addLayout(tmpLayout); + dynamic_cast(tabData->layout())->addLayout(tmpLayout); mDataTable = new QTableView(this); - mSortFilterProxy->setSourceModel(mModel.get()); - mDataTable->setModel(mSortFilterProxy.get()); + mSortFilterProxy->setSourceModel(mModel); + mDataTable->setModel(mSortFilterProxy); mDataTable->setAlternatingRowColors(true); mDataTable->setSelectionBehavior(QAbstractItemView::SelectRows); mDataTable->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); mDataTable->verticalHeader()->setDefaultSectionSize(19); mDataTable->verticalHeader()->setVisible(false); - mDataTable->setItemDelegate(&mHTMLDelegate); + mDataTable->setItemDelegate(new StyledItemDelegateHTML(this)); mDataTable->setColumnWidth(gsColTableOffset, 100); mDataTable->setColumnWidth(gsColCodeAddress, 125); mDataTable->setColumnWidth(gsColTableAddress, 125); @@ -110,7 +118,7 @@ void S2Plugin::ViewVirtualTable::initializeUI() QObject::connect(mDataTable, &QTableView::clicked, this, &ViewVirtualTable::tableEntryClicked); - dynamic_cast(mTabData->layout())->addWidget(mDataTable); + dynamic_cast(tabData->layout())->addWidget(mDataTable); } // TAB LOOKUP @@ -126,7 +134,7 @@ void S2Plugin::ViewVirtualTable::initializeUI() topLayout->addStretch(); - dynamic_cast(mTabLookup->layout())->addLayout(topLayout); + dynamic_cast(tabLookup->layout())->addLayout(topLayout); mLookupResultsTable = new QTableWidget(this); mLookupResultsTable->setAlternatingRowColors(true); @@ -140,7 +148,7 @@ void S2Plugin::ViewVirtualTable::initializeUI() mLookupResultsTable->setColumnWidth(1, 325); mLookupResultsTable->setEditTriggers(QAbstractItemView::NoEditTriggers); - dynamic_cast(mTabLookup->layout())->addWidget(mLookupResultsTable); + dynamic_cast(tabLookup->layout())->addWidget(mLookupResultsTable); } // TAB GATHER @@ -162,9 +170,9 @@ void S2Plugin::ViewVirtualTable::initializeUI() mGatherProgressLabel = new QLabel("", this); topLayout->addWidget(mGatherProgressLabel); - mHideCompletedCheckbox = new QCheckBox("Hide completed", this); - QObject::connect(mHideCompletedCheckbox, &QCheckBox::stateChanged, this, &ViewVirtualTable::showGatherHideCompletedCheckBoxStateChanged); - topLayout->addWidget(mHideCompletedCheckbox); + auto hideCompletedCheckbox = new QCheckBox("Hide completed", this); + QObject::connect(hideCompletedCheckbox, &QCheckBox::stateChanged, this, &ViewVirtualTable::showGatherHideCompletedCheckBoxStateChanged); + topLayout->addWidget(hideCompletedCheckbox); topLayout->addStretch(); @@ -180,34 +188,29 @@ void S2Plugin::ViewVirtualTable::initializeUI() QObject::connect(exportCppEnumBtn, &QPushButton::clicked, this, &ViewVirtualTable::exportCppEnum); topLayout->addWidget(exportCppEnumBtn); - dynamic_cast(mTabGather->layout())->addLayout(topLayout); - - mGatherTable = new QTableView(this); - mGatherSortFilterProxy->setSourceModel(mGatherModel.get()); - mGatherTable->setModel(mGatherSortFilterProxy.get()); - mGatherTable->setAlternatingRowColors(true); - mGatherTable->setSelectionBehavior(QAbstractItemView::SelectRows); - mGatherTable->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); - mGatherTable->verticalHeader()->setDefaultSectionSize(19); - mGatherTable->verticalHeader()->setVisible(false); - mGatherTable->setItemDelegate(&mHTMLDelegate); - mGatherTable->setColumnWidth(gsColGatherID, 50); - mGatherTable->setColumnWidth(gsColGatherName, 200); - mGatherTable->setColumnWidth(gsColGatherVirtualTableOffset, 125); - mGatherTable->setColumnWidth(gsColGatherCollision1Present, 75); - mGatherTable->setColumnWidth(gsColGatherCollision2Present, 75); - mGatherTable->horizontalHeader()->setStretchLastSection(true); - - dynamic_cast(mTabGather->layout())->addWidget(mGatherTable); + dynamic_cast(tabGather->layout())->addLayout(topLayout); + + auto gatherTable = new QTableView(this); + mGatherSortFilterProxy->setSourceModel(mGatherModel); + gatherTable->setModel(mGatherSortFilterProxy); + gatherTable->setAlternatingRowColors(true); + gatherTable->setSelectionBehavior(QAbstractItemView::SelectRows); + gatherTable->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + gatherTable->verticalHeader()->setDefaultSectionSize(19); + gatherTable->verticalHeader()->setVisible(false); + gatherTable->setItemDelegate(new StyledItemDelegateHTML(this)); + gatherTable->setColumnWidth(gsColGatherID, 50); + gatherTable->setColumnWidth(gsColGatherName, 200); + gatherTable->setColumnWidth(gsColGatherVirtualTableOffset, 125); + gatherTable->setColumnWidth(gsColGatherCollision1Present, 75); + gatherTable->setColumnWidth(gsColGatherCollision2Present, 75); + gatherTable->horizontalHeader()->setStretchLastSection(true); + + dynamic_cast(tabGather->layout())->addWidget(gatherTable); updateGatherProgress(); } } -void S2Plugin::ViewVirtualTable::closeEvent(QCloseEvent* event) -{ - delete this; -} - QSize S2Plugin::ViewVirtualTable::sizeHint() const { return QSize(800, 650); @@ -294,7 +297,7 @@ void S2Plugin::ViewVirtualTable::processLookupAddressText() void S2Plugin::ViewVirtualTable::showLookupAddress(size_t address) { - mMainTabWidget->setCurrentWidget(mTabLookup); + mMainTabWidget->setCurrentIndex(TABS::LOOKUP); mLookupAddressLineEdit->setText(QString::asprintf("%016llX", address)); lookupAddress(address); } @@ -334,7 +337,7 @@ void S2Plugin::ViewVirtualTable::lookupAddress(size_t address) if (items.size() == 0) { - mLookupResultsTable->setRowCount(tableOffsets.size()); + mLookupResultsTable->setRowCount(static_cast(tableOffsets.size())); auto counter = 0; for (const auto& tableOffset : tableOffsets) { @@ -344,7 +347,7 @@ void S2Plugin::ViewVirtualTable::lookupAddress(size_t address) } else { - mLookupResultsTable->setRowCount(items.size()); + mLookupResultsTable->setRowCount(static_cast(items.size())); auto counter = 0; for (const auto& item : items) { @@ -395,7 +398,7 @@ void S2Plugin::ViewVirtualTable::exportGatheredData() { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Critical); - msgBox.setWindowIcon(QIcon(":/icons/caveman.png")); + msgBox.setWindowIcon(getCavemanIcon()); msgBox.setText("The file could not be written"); msgBox.setWindowTitle("Spelunky2"); msgBox.exec(); @@ -411,7 +414,7 @@ void S2Plugin::ViewVirtualTable::exportVirtTable() QMessageBox msgBox; msgBox.setIcon(QMessageBox::Information); - msgBox.setWindowIcon(QIcon(":/icons/caveman.png")); + msgBox.setWindowIcon(getCavemanIcon()); msgBox.setText("The table was copied to the clipboard"); msgBox.setWindowTitle("Spelunky2"); msgBox.exec(); @@ -425,7 +428,7 @@ void S2Plugin::ViewVirtualTable::exportCppEnum() QMessageBox msgBox; msgBox.setIcon(QMessageBox::Information); - msgBox.setWindowIcon(QIcon(":/icons/caveman.png")); + msgBox.setWindowIcon(getCavemanIcon()); msgBox.setText("The C++ enum was copied to the clipboard"); msgBox.setWindowTitle("Spelunky2"); msgBox.exec(); diff --git a/src/pluginmain.cpp b/src/pluginmain.cpp index b4cdf6ab..0e41205e 100644 --- a/src/pluginmain.cpp +++ b/src/pluginmain.cpp @@ -40,22 +40,22 @@ PLUG_EXPORT void plugsetup(PLUG_SETUPSTRUCT* setupStruct) QtPlugin::WaitForSetup(); } -PLUG_EXPORT void CBDETACH(CBTYPE cbType, PLUG_CB_DETACH* info) +PLUG_EXPORT void CBDETACH([[maybe_unused]] CBTYPE cbType, [[maybe_unused]] PLUG_CB_DETACH* info) { GuiExecuteOnGuiThread(QtPlugin::Detach); } -PLUG_EXPORT void CBEXITPROCESS(CBTYPE cbType, PLUG_CB_EXITPROCESS* info) +PLUG_EXPORT void CBEXITPROCESS([[maybe_unused]] CBTYPE cbType, [[maybe_unused]] PLUG_CB_EXITPROCESS* info) { GuiExecuteOnGuiThread(QtPlugin::Detach); } -PLUG_EXPORT void CBMENUPREPARE(CBTYPE, PLUG_CB_MENUPREPARE* info) +PLUG_EXPORT void CBMENUPREPARE([[maybe_unused]] CBTYPE cbType, PLUG_CB_MENUPREPARE* info) { QtPlugin::MenuPrepare(info->hMenu); } -PLUG_EXPORT void CBMENUENTRY(CBTYPE, PLUG_CB_MENUENTRY* info) +PLUG_EXPORT void CBMENUENTRY([[maybe_unused]] CBTYPE cbType, PLUG_CB_MENUENTRY* info) { QtPlugin::MenuEntry(info->hEntry); } @@ -71,7 +71,7 @@ void displayError(const char* fmt, ...) QMessageBox msgBox; msgBox.setIcon(QMessageBox::Critical); - msgBox.setWindowIcon(QIcon(":/icons/caveman.png")); + msgBox.setWindowIcon(S2Plugin::getCavemanIcon()); msgBox.setText(buffer); msgBox.setWindowTitle("Spelunky2"); msgBox.exec(); @@ -82,7 +82,7 @@ void displayError(std::string message) { QMessageBox msgBox; msgBox.setIcon(QMessageBox::Critical); - msgBox.setWindowIcon(QIcon(":/icons/caveman.png")); + msgBox.setWindowIcon(S2Plugin::getCavemanIcon()); msgBox.setText(message.c_str()); msgBox.setWindowTitle("Spelunky2"); msgBox.exec();