diff --git a/CMakeLists.txt b/CMakeLists.txt index d369cd3..4bea818 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Data/StdString.h include/Data/StdMap.h include/Data/EntityList.h + include/Data/OldStdList.h include/Views/ViewToolbar.h include/Views/ViewEntityDB.h include/Views/ViewParticleDB.h @@ -101,7 +102,7 @@ x64dbg_plugin(${PROJECT_NAME} include/QtHelpers/WidgetSampling.h include/QtHelpers/WidgetSamplesPlot.h include/QtHelpers/ItemModelLoggerSamples.h - include/QtHelpers/WidgetDatabaseView.h + include/QtHelpers/AbstractDatabaseView.h include/QtHelpers/WidgetAutorefresh.h include/QtHelpers/LongLongSpinBox.h src/Spelunky2.cpp @@ -151,7 +152,7 @@ x64dbg_plugin(${PROJECT_NAME} src/QtHelpers/ItemModelLoggerFields.cpp src/QtHelpers/WidgetSamplesPlot.cpp src/QtHelpers/ItemModelLoggerSamples.cpp - src/QtHelpers/WidgetDatabaseView.cpp + src/QtHelpers/AbstractDatabaseView.cpp src/QtHelpers/WidgetAutorefresh.cpp ${CMAKE_CURRENT_BINARY_DIR}/include/pluginconfig.h resources/spelunky2.qrc diff --git a/include/Configuration.h b/include/Configuration.h index 31e9443..be2e17d 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -130,6 +130,7 @@ namespace S2Plugin Array, Matrix, EntityList, + OldStdList, }; struct VirtualFunction diff --git a/include/Data/OldStdList.h b/include/Data/OldStdList.h new file mode 100644 index 0000000..2bd2a84 --- /dev/null +++ b/include/Data/OldStdList.h @@ -0,0 +1,101 @@ +#pragma once + +#include "pluginmain.h" +#include + +namespace S2Plugin +{ + // this is the pre C++ 11 version standard + class OldStdList + { + public: + OldStdList(uintptr_t address) : _end(address){}; + + struct Node + { + Node(uintptr_t address) : nodeAddress(address){}; + + Node prev() const + { + return Script::Memory::ReadQword(nodeAddress); + } + Node next() const + { + return Script::Memory::ReadQword(nodeAddress + sizeof(uintptr_t)); + } + uintptr_t value_ptr() const + { + return nodeAddress + 2 * sizeof(uintptr_t); + } + uintptr_t operator*() const + { + return value_ptr(); + } + Node operator++() + { + nodeAddress = next().nodeAddress; + return *this; + } + Node operator--(int) + { + auto tmp = *this; + nodeAddress = prev().nodeAddress; + return tmp; + } + Node operator++(int) + { + auto tmp = *this; + nodeAddress = next().nodeAddress; + return tmp; + } + Node operator--() + { + nodeAddress = prev().nodeAddress; + return *this; + } + bool operator==(const Node& other) const + { + return nodeAddress == other.nodeAddress; + } + bool operator!=(const Node& other) const + { + return nodeAddress != other.nodeAddress; + } + + private: + uintptr_t nodeAddress; + }; + + Node begin() const + { + return _end.next(); + } + Node end() const + { + return _end; + } + bool empty() const + { + return begin() == end(); + } + Node cbegin() const + { + return begin(); + } + Node cend() const + { + return end(); + } + Node back() const + { + return _end.prev(); + } + Node front() const + { + return _end.next(); + } + + private: + Node _end; + }; +}; // namespace S2Plugin diff --git a/include/QtHelpers/WidgetDatabaseView.h b/include/QtHelpers/AbstractDatabaseView.h similarity index 95% rename from include/QtHelpers/WidgetDatabaseView.h rename to include/QtHelpers/AbstractDatabaseView.h index 36dadae..6eb462e 100644 --- a/include/QtHelpers/WidgetDatabaseView.h +++ b/include/QtHelpers/AbstractDatabaseView.h @@ -33,11 +33,11 @@ namespace S2Plugin struct MemoryField; using ID_type = uint32_t; - class WidgetDatabaseView : public QWidget + class AbstractDatabaseView : public QWidget { Q_OBJECT public: - WidgetDatabaseView(MemoryFieldType type, QWidget* parent = nullptr); + AbstractDatabaseView(MemoryFieldType type, QWidget* parent = nullptr); virtual void showID(ID_type id) = 0; protected: diff --git a/include/Views/ViewCharacterDB.h b/include/Views/ViewCharacterDB.h index afc8623..1fc0d99 100644 --- a/include/Views/ViewCharacterDB.h +++ b/include/Views/ViewCharacterDB.h @@ -1,12 +1,12 @@ #pragma once -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewCharacterDB : public WidgetDatabaseView + class ViewCharacterDB : public AbstractDatabaseView { Q_OBJECT public: diff --git a/include/Views/ViewEntityDB.h b/include/Views/ViewEntityDB.h index 6ddea74..8274e69 100644 --- a/include/Views/ViewEntityDB.h +++ b/include/Views/ViewEntityDB.h @@ -1,12 +1,12 @@ #pragma once -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewEntityDB : public WidgetDatabaseView + class ViewEntityDB : public AbstractDatabaseView { Q_OBJECT public: diff --git a/include/Views/ViewParticleDB.h b/include/Views/ViewParticleDB.h index 5dbc25e..dd988d9 100644 --- a/include/Views/ViewParticleDB.h +++ b/include/Views/ViewParticleDB.h @@ -1,12 +1,12 @@ #pragma once -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewParticleDB : public WidgetDatabaseView + class ViewParticleDB : public AbstractDatabaseView { Q_OBJECT public: diff --git a/include/Views/ViewTextureDB.h b/include/Views/ViewTextureDB.h index 8a03de1..8ed98bb 100644 --- a/include/Views/ViewTextureDB.h +++ b/include/Views/ViewTextureDB.h @@ -1,12 +1,12 @@ #pragma once -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewTextureDB : public WidgetDatabaseView + class ViewTextureDB : public AbstractDatabaseView { Q_OBJECT public: diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 09bfb98..16d6a01 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -1,6 +1,6 @@ { // list of structs that are always used as pointers - // leftover of previous version but also required if type is used as secondary type (vectortype, map type etc.) + // leftover of previous version but also required if type is used as secondary type (vector, map, array etc.) // normal pointers can be defined at the specific field level with ("pointer": true) // structs not listed here and not defined with pointer value will be treated as inline sturct "pointer_types": [ @@ -1398,7 +1398,7 @@ { "field": "unknown11", "type": "StdVector", - "vectortype": "DataPointer" + "valuetype": "DataPointer" }, { "field": "unknown12", "type": "DataPointer" }, { "field": "unknown13", "type": "CodePointer" }, @@ -1467,12 +1467,12 @@ { "field": "pages", "type": "StdVector", - "vectortype": "JournalPagePointer" + "valuetype": "JournalPagePointer" }, { "field": "pages_tmp", "type": "StdVector", - "vectortype": "JournalPagePointer", + "valuetype": "JournalPagePointer", "comment": "pages are constructed, saved here and later moved to the pages vector from where they are rendered" }, { "field": "current_page", "type": "UnsignedDword" }, @@ -1637,7 +1637,7 @@ { "field": "text_lines", "type": "StdVector", - "vectortype": "TextRenderingInfoPointer" + "valuetype": "TextRenderingInfoPointer" }, { "field": "x", "type": "Float" }, { "field": "y", "type": "Float" } @@ -1917,7 +1917,7 @@ { "field": "vector", "type": "StdVector", - "vectortype": "ScreenMenuOption" + "valuetype": "ScreenMenuOption" } ], "ScreenMenuOption": [ @@ -1996,12 +1996,12 @@ { "field": "menu_tree", "type": "StdVector", - "vectortype": "ScreenMenuVector" + "valuetype": "ScreenMenuVector" }, { "field": "menu_index_order", "type": "StdVector", - "vectortype": "UnsignedDword" + "valuetype": "UnsignedDword" }, { "field": "controls", "type": "ScreenControls" }, { "field": "selected_menu_index", "type": "UnsignedDword" }, @@ -2045,12 +2045,12 @@ { "field": "menu_tree", "type": "StdVector", - "vectortype": "ScreenMenuVector" + "valuetype": "ScreenMenuVector" }, { "field": "menu_index_order", "type": "StdVector", - "vectortype": "UnsignedDword" + "valuetype": "UnsignedDword" }, { "field": "DOWN", @@ -2269,7 +2269,7 @@ { "field": "unknown50", "type": "StdVector", - "vectortype": "UnsignedQword" + "valuetype": "UnsignedQword" }, { "field": "unknown51", "type": "UnsignedByte" }, { "field": "padding_probably10", "type": "UnsignedByte" }, @@ -2278,7 +2278,7 @@ { "field": "tooltip_text", "type": "StdVector", - "vectortype": "StringsTableID" + "valuetype": "StringsTableID" }, { "field": "disable_controls", "type": "Bool" }, { "field": "padding_probably13", "type": "UnsignedByte" }, @@ -2771,7 +2771,7 @@ { "field": "option_captions", "type": "StdVector", - "vectortype": "ArenaRulesStringsEntry" + "valuetype": "ArenaRulesStringsEntry" }, { "field": "unknown48", "type": "Bool" }, { "field": "unknown49", "type": "UnsignedByte" }, @@ -3352,7 +3352,7 @@ { "field": "entities_switching_layer", "type": "StdVector", - "vectortype": "BackLayerRelated" + "valuetype": "BackLayerRelated" }, { "field": "layer_transition_timer", "type": "UnsignedDword" }, { "field": "transition_to_layer", "type": "UnsignedByte" }, @@ -3605,7 +3605,7 @@ { "field": "shop_owners", "type": "StdVector", - "vectortype": "ShopOwnerDetails" + "valuetype": "ShopOwnerDetails" } ], "ItemOwnerDetails": [ @@ -4125,7 +4125,7 @@ "field": "activefloors", "type": "PointerToPushBlocksMap" }, - { "field": "impostors", "type": "StdVector", "vectortype": "LiquidLake" }, + { "field": "impostors", "type": "StdVector", "valuetype": "LiquidLake" }, { "field": "total_liquid_spawned", "type": "UnsignedDword", @@ -4317,7 +4317,7 @@ { "field": "unknown13", "type": "StdVector", - "vectortype": "PointerToEntityList", + "valuetype": "PointerToEntityList", "comment": "probably not actual vector" } ], @@ -4462,7 +4462,7 @@ { "field": "torches", "type": "StdVector", - "vectortype": "EntityPointer" + "valuetype": "EntityPointer" }, { "field": "start_countdown", "type": "UnsignedByte" }, { "field": "unknown9", "type": "UnsignedByte" }, @@ -4502,7 +4502,7 @@ { "field": "magman_spawns", "type": "StdVector", - "vectortype": "MagmamanSpawnData" + "valuetype": "MagmamanSpawnData" } ], "LogicUnderwaterBubbles": [ @@ -6299,7 +6299,7 @@ { "field": "exit_doors_locations", "type": "StdVector", - "vectortype": "XY" + "valuetype": "XY" }, { "field": "flags", @@ -6546,17 +6546,17 @@ { "field": "unknown47", "type": "StdVector", - "vectortype": "UnsignedQword" + "valuetype": "UnsignedQword" }, { "field": "unknown48", "type": "StdVector", - "vectortype": "UnsignedQword" + "valuetype": "UnsignedQword" }, { "field": "unknown49", "type": "StdVector", - "vectortype": "UnsignedQword" + "valuetype": "UnsignedQword" } ], "OnlineServer": [ @@ -6803,7 +6803,7 @@ { "field": "_Vec", "type": "StdVector", - "vectortype": "UnorderedMapBucketIterator", + "valuetype": "UnorderedMapBucketIterator", "comment": "iterators for buckets, if both are pointing to end, the bucket is empty" }, { @@ -6888,14 +6888,14 @@ { "field": "vector", "type": "StdVector", - "vectortype": "ParticleEmitterInfoPointer" + "valuetype": "ParticleEmitterInfoPointer" } ], "StdVectorPointerIllumination": [ { "field": "vector", "type": "StdVector", - "vectortype": "IlluminationPointer" + "valuetype": "IlluminationPointer" }, { "field": "unknown1", "type": "UnsignedDword" }, { "field": "unknown2", "type": "UnsignedDword" } @@ -7020,7 +7020,7 @@ { "field": "unknown3", "type": "StdVector", - "vectortype": "EntityPointer" + "valuetype": "EntityPointer" }, { "field": "unknown6", "type": "PointerToFloatSet" }, { "field": "expired_entities", "type": "EntityList" }, @@ -7786,7 +7786,7 @@ { "field": "unknown2", "type": "Dword", "comment": "padding probably" }, { "field": "sound_name", "type": "StdString" } ], - "StdList": [ + "OldStdList": [ { "field": "back", "type": "StdListIteratorPointer" }, { "field": "front", "type": "StdListIteratorPointer" } ], @@ -7831,7 +7831,7 @@ "type": "UnsignedDword", "comment": "padding probably" }, - { "field": "unk1", "type": "StdList" }, + { "field": "unk1", "type": "OldStdList" }, { "field": "resize_value", "type": "UnsignedDword", @@ -7842,10 +7842,10 @@ "type": "UnsignedDword", "comment": "padding probably" }, - { "field": "liquid_ids", "type": "StdList" }, + { "field": "liquid_ids", "type": "OldStdList" }, { "field": "unknown44", - "type": "StdList", + "type": "OldStdList", "comment": "all of them are -1" }, { @@ -8078,7 +8078,10 @@ "comment": "array of entities? removed ones?" }, { "field": "unknown10", "type": "DataPointer", "comment": "map?" }, - { "field": "unknown11", "type": "Qword" } + { "field": "unknown11", "type": "Qword" }, + { "field": "skip", "type": "Skip", "offset": 102472 }, + { "field": "unknown12", "type": "Array", "length": 46, "arraytype": "UnsignedQword" }, + { "field": "unknown13", "type": "Qword", "comment": "the end?" } ], "MysteryLiquidPointer2": [ { "field": "last_spawn_liquid", "type": "EntityPointer" }, diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 53b3229..bc6ee50 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -112,6 +112,7 @@ namespace S2Plugin {MemoryFieldType::StdMap, "StdMap", "std::map", "StdMap", 16, false}, {MemoryFieldType::StdString, "StdString", "std::string", "StdString", 32, false}, {MemoryFieldType::StdWstring, "StdWstring", "std::wstring", "StdWstring", 32, false}, + {MemoryFieldType::OldStdList, "OldStdList", "std::pair", "OldStdList", 16, false}, // can't use std::list representation since the standard was changed // Game Main structs {MemoryFieldType::GameManager, "GameManager", "", "GameManager", 0, false}, {MemoryFieldType::State, "State", "", "State", 0, false}, @@ -331,14 +332,27 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman } case MemoryFieldType::StdVector: { - if (field.contains("vectortype")) + if (field.contains("valuetype")) { - memField.firstParameterType = field["vectortype"].get(); + memField.firstParameterType = field["valuetype"].get(); } else { memField.firstParameterType = "UnsignedQword"; - dprintf("no vectortype specified for StdVector (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); + dprintf("no valuetype specified for StdVector (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); + } + break; + } + case MemoryFieldType::OldStdList: + { + if (field.contains("valuetype")) + { + memField.firstParameterType = field["valuetype"].get(); + } + else + { + memField.firstParameterType = "UnsignedQword"; + dprintf("no valuetype specified for OldStdList (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); } break; } @@ -810,6 +824,8 @@ int S2Plugin::Configuration::getAlingment(const std::string& typeName) const case MemoryFieldType::Qword: case MemoryFieldType::UnsignedQword: case MemoryFieldType::Double: + case MemoryFieldType::OldStdList: + case MemoryFieldType::EntityList: return sizeof(uintptr_t); } } diff --git a/src/QtHelpers/WidgetDatabaseView.cpp b/src/QtHelpers/AbstractDatabaseView.cpp similarity index 90% rename from src/QtHelpers/WidgetDatabaseView.cpp rename to src/QtHelpers/AbstractDatabaseView.cpp index 34ffdad..46bce7c 100644 --- a/src/QtHelpers/WidgetDatabaseView.cpp +++ b/src/QtHelpers/AbstractDatabaseView.cpp @@ -1,4 +1,4 @@ -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" #include "Configuration.h" #include "QtHelpers/StyledItemDelegateHTML.h" @@ -37,7 +37,7 @@ struct ComparisonField }; Q_DECLARE_METATYPE(ComparisonField) -S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* parent) : QWidget(parent) +S2Plugin::AbstractDatabaseView::AbstractDatabaseView(MemoryFieldType type, QWidget* parent) : QWidget(parent) { setWindowIcon(getCavemanIcon()); auto mainLayout = new QVBoxLayout(this); @@ -68,10 +68,10 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mSearchLineEdit = new QLineEdit(); mSearchLineEdit->setPlaceholderText("Search"); topLayout->addWidget(mSearchLineEdit); - QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &WidgetDatabaseView::searchFieldReturnPressed); + QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &AbstractDatabaseView::searchFieldReturnPressed); auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &WidgetDatabaseView::label); + QObject::connect(labelButton, &QPushButton::clicked, this, &AbstractDatabaseView::label); topLayout->addWidget(labelButton); qobject_cast(tabLookup->layout())->addLayout(topLayout); @@ -79,8 +79,8 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* 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); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &AbstractDatabaseView::fieldUpdated); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &AbstractDatabaseView::fieldExpanded); tabLookup->layout()->addWidget(mMainTreeView); mMainTreeView->setColumnWidth(gsColField, 125); mMainTreeView->setColumnWidth(gsColValue, 250); @@ -99,16 +99,16 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mCompareFieldComboBox->addItem(QString::fromStdString("")); populateComparisonCombobox(config->typeFields(type)); - QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &WidgetDatabaseView::comparisonFieldChosen); + QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &AbstractDatabaseView::comparisonFieldChosen); topLayout->addWidget(mCompareFieldComboBox); mCompareFlagComboBox = new QComboBox(); - QObject::connect(mCompareFlagComboBox, &QComboBox::currentTextChanged, this, &WidgetDatabaseView::comparisonFlagChosen); + QObject::connect(mCompareFlagComboBox, &QComboBox::currentTextChanged, this, &AbstractDatabaseView::comparisonFlagChosen); mCompareFlagComboBox->hide(); topLayout->addWidget(mCompareFlagComboBox); auto groupCheckbox = new QCheckBox("Group by value", this); - QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &WidgetDatabaseView::compareGroupByCheckBoxClicked); + QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &AbstractDatabaseView::compareGroupByCheckBoxClicked); topLayout->addWidget(groupCheckbox); qobject_cast(tabCompare->layout())->addLayout(topLayout); @@ -128,14 +128,14 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mCompareTableWidget->setColumnWidth(2, 150); auto HTMLDelegate = new StyledItemDelegateHTML(this); mCompareTableWidget->setItemDelegate(HTMLDelegate); - QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &WidgetDatabaseView::comparisonCellClicked); + QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &AbstractDatabaseView::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); + QObject::connect(mCompareTreeWidget, &QTreeWidget::itemClicked, this, &AbstractDatabaseView::groupedComparisonItemClicked); tabCompare->layout()->addWidget(mCompareTableWidget); tabCompare->layout()->addWidget(mCompareTreeWidget); @@ -144,12 +144,12 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mSearchLineEdit->setFocus(); } -QSize S2Plugin::WidgetDatabaseView::minimumSizeHint() const +QSize S2Plugin::AbstractDatabaseView::minimumSizeHint() const { return QSize(150, 150); } -void S2Plugin::WidgetDatabaseView::searchFieldReturnPressed() +void S2Plugin::AbstractDatabaseView::searchFieldReturnPressed() { auto text = mSearchLineEdit->text(); bool isNumeric = false; @@ -169,7 +169,7 @@ void S2Plugin::WidgetDatabaseView::searchFieldReturnPressed() } } -void S2Plugin::WidgetDatabaseView::fieldUpdated(int row, QStandardItem* parrent) +void S2Plugin::AbstractDatabaseView::fieldUpdated(int row, QStandardItem* parrent) { if (parrent != nullptr) // special case: for flag field need to update it's parrent, not the flag field { @@ -185,19 +185,19 @@ void S2Plugin::WidgetDatabaseView::fieldUpdated(int row, QStandardItem* parrent) mMainTreeView->updateRow(row, std::nullopt, std::nullopt, parrent, true); } -void S2Plugin::WidgetDatabaseView::fieldExpanded(const QModelIndex& index) +void S2Plugin::AbstractDatabaseView::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) +void S2Plugin::AbstractDatabaseView::compareGroupByCheckBoxClicked(int state) { mCompareTableWidget->setHidden(state == Qt::Checked); mCompareTreeWidget->setHidden(state == Qt::Unchecked); } -void S2Plugin::WidgetDatabaseView::comparisonFieldChosen() +void S2Plugin::AbstractDatabaseView::comparisonFieldChosen() { mFieldChoosen = true; mCompareTableWidget->clearContents(); @@ -245,7 +245,7 @@ void S2Plugin::WidgetDatabaseView::comparisonFieldChosen() mFieldChoosen = false; } -void S2Plugin::WidgetDatabaseView::comparisonFlagChosen(const QString& text) +void S2Plugin::AbstractDatabaseView::comparisonFlagChosen(const QString& text) { // protect againts infinite loops since this slot is called when adding firts element to combo box if (mFieldChoosen) @@ -282,7 +282,7 @@ void S2Plugin::WidgetDatabaseView::comparisonFlagChosen(const QString& text) populateComparisonTreeWidget(newData); } -void S2Plugin::WidgetDatabaseView::populateComparisonTableWidget(const QVariant& fieldData) +void S2Plugin::AbstractDatabaseView::populateComparisonTableWidget(const QVariant& fieldData) { mCompareTableWidget->setSortingEnabled(false); @@ -309,7 +309,7 @@ void S2Plugin::WidgetDatabaseView::populateComparisonTableWidget(const QVariant& mCompareTableWidget->sortItems(0); } -void S2Plugin::WidgetDatabaseView::populateComparisonTreeWidget(const QVariant& fieldData) +void S2Plugin::AbstractDatabaseView::populateComparisonTreeWidget(const QVariant& fieldData) { mCompareTreeWidget->setSortingEnabled(false); @@ -352,7 +352,7 @@ void S2Plugin::WidgetDatabaseView::populateComparisonTreeWidget(const QVariant& mCompareTreeWidget->sortItems(0, Qt::AscendingOrder); } -void S2Plugin::WidgetDatabaseView::comparisonCellClicked(int row, int column) +void S2Plugin::AbstractDatabaseView::comparisonCellClicked(int row, int column) { if (column == 1) { @@ -362,7 +362,7 @@ void S2Plugin::WidgetDatabaseView::comparisonCellClicked(int row, int column) } } -void S2Plugin::WidgetDatabaseView::groupedComparisonItemClicked(QTreeWidgetItem* item) +void S2Plugin::AbstractDatabaseView::groupedComparisonItemClicked(QTreeWidgetItem* item) { if (item->childCount() == 0) { @@ -371,7 +371,7 @@ void S2Plugin::WidgetDatabaseView::groupedComparisonItemClicked(QTreeWidgetItem* } } -size_t S2Plugin::WidgetDatabaseView::populateComparisonCombobox(const std::vector& fields, size_t offset, std::string prefix) +size_t S2Plugin::AbstractDatabaseView::populateComparisonCombobox(const std::vector& fields, size_t offset, std::string prefix) { for (const auto& field : fields) { @@ -416,7 +416,7 @@ size_t S2Plugin::WidgetDatabaseView::populateComparisonCombobox(const std::vecto return offset; } -std::pair S2Plugin::WidgetDatabaseView::valueForField(const QVariant& data, uintptr_t addr) +std::pair S2Plugin::AbstractDatabaseView::valueForField(const QVariant& data, uintptr_t addr) { ComparisonField compData = qvariant_cast(data); diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 5ac9996..b5e0c64 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -4,6 +4,7 @@ #include "Data/CharacterDB.h" #include "Data/Entity.h" #include "Data/EntityDB.h" +#include "Data/OldStdList.h" #include "Data/ParticleDB.h" #include "Data/State.h" #include "Data/StdString.h" @@ -1956,6 +1957,80 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::OldStdList: + { + std::optional> value; + if (valueMemoryOffset == 0) + { + itemValue->setData({}, Qt::DisplayRole); + if (!isPointer) + itemValueHex->setData({}, Qt::DisplayRole); + + itemValue->setData({}, S2Plugin::gsRoleRawValue); + itemField->setBackground(Qt::transparent); + } + else + { + value = {0, 0}; + Script::Memory::Read(valueMemoryOffset, &value.value(), 2 * sizeof(uintptr_t), nullptr); + auto dataOld = itemValue->data(S2Plugin::gsRoleRawValue); + auto valueOld = dataOld.value>(); + if (!dataOld.isValid() || value.value() != valueOld) + { + itemField->setBackground(highlightColor); + itemValue->setData(QVariant::fromValue(value.value()), S2Plugin::gsRoleRawValue); + + OldStdList list{valueMemoryOffset}; + if (list.empty()) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); + } + else if (!isPointer) + itemField->setBackground(Qt::transparent); + } + + if (comparisonActive) + { + std::optional> comparisonValue; + if (valueComparisonMemoryOffset == 0) + { + itemComparisonValue->setData({}, Qt::DisplayRole); + if (!isPointer) + itemComparisonValueHex->setData({}, Qt::DisplayRole); + + itemComparisonValue->setData({}, S2Plugin::gsRoleRawValue); + } + else + { + comparisonValue = {0, 0}; + Script::Memory::Read(valueComparisonMemoryOffset, &comparisonValue.value(), 2 * sizeof(uintptr_t), nullptr); + auto dataOld = itemComparisonValue->data(S2Plugin::gsRoleRawValue); + auto valueOld = dataOld.value>(); + if (!dataOld.isValid() || comparisonValue.value() != valueOld) + { + itemComparisonValue->setData(QVariant::fromValue(comparisonValue.value()), S2Plugin::gsRoleRawValue); + + OldStdList list{valueComparisonMemoryOffset}; + if (list.empty()) + itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemComparisonValue->setData("Show contents", Qt::DisplayRole); + } + } + 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) + updateRow(x, addr, comparisonAddr, itemField); + } + break; + } case MemoryFieldType::EntityList: { std::optional value; @@ -2429,65 +2504,75 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } case MemoryFieldType::UTF16Char: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) { 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); + auto dialog = new DialogEditString(fieldName, c, address, 1, dataType, this); dialog->exec(); } break; } case MemoryFieldType::UTF16StringFixedSize: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 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); + Script::Memory::Read(address, stringData.get(), size, nullptr); auto s = QString::fromUtf16(stringData.get()); - auto dialog = new DialogEditString(fieldName, s, offset, size / 2 - 1, dataType, this); + auto dialog = new DialogEditString(fieldName, s, address, size / 2 - 1, dataType, this); dialog->exec(); } break; } case MemoryFieldType::ConstCharPointer: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) { auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); - auto s = QString::fromStdString(ReadConstString(offset)); + auto s = QString::fromStdString(ReadConstString(address)); // [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); + auto dialog = new DialogEditString(fieldName, s, address, s.length(), dataType, this); dialog->exec(); } break; } case MemoryFieldType::UTF8StringFixedSize: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 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); + Script::Memory::Read(address, stringData.get(), size, nullptr); auto s = QString::fromUtf8(stringData.get()); - auto dialog = new DialogEditString(fieldName, s, offset, size - 1, dataType, this); + auto dialog = new DialogEditString(fieldName, s, address, size - 1, dataType, this); dialog->exec(); } break; } case MemoryFieldType::EntityList: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) + { + getToolbar()->showEntityList(address); + } + break; + } + case MemoryFieldType::OldStdList: + { + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) { - getToolbar()->showEntityList(offset); + auto typeName = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerFirstParameterType)); + //getToolbar()->showStdList(address, typeName, true); } break; } diff --git a/src/Views/ViewCharacterDB.cpp b/src/Views/ViewCharacterDB.cpp index 9a8d747..34d0411 100644 --- a/src/Views/ViewCharacterDB.cpp +++ b/src/Views/ViewCharacterDB.cpp @@ -5,7 +5,7 @@ #include "Spelunky2.h" #include -S2Plugin::ViewCharacterDB::ViewCharacterDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::CharacterDB, parent) +S2Plugin::ViewCharacterDB::ViewCharacterDB(QWidget* parent) : AbstractDatabaseView(MemoryFieldType::CharacterDB, parent) { setWindowTitle("Character DB"); auto& charDB = Spelunky2::get()->get_CharacterDB(); diff --git a/src/Views/ViewEntityDB.cpp b/src/Views/ViewEntityDB.cpp index d9825b6..cb2c7e0 100644 --- a/src/Views/ViewEntityDB.cpp +++ b/src/Views/ViewEntityDB.cpp @@ -7,7 +7,7 @@ #include "Spelunky2.h" #include -S2Plugin::ViewEntityDB::ViewEntityDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::EntityDB, parent) +S2Plugin::ViewEntityDB::ViewEntityDB(QWidget* parent) : AbstractDatabaseView(MemoryFieldType::EntityDB, parent) { auto config = Configuration::get(); setWindowTitle(QString("Entity DB (%1 entities)").arg(config->entityList().count())); diff --git a/src/Views/ViewParticleDB.cpp b/src/Views/ViewParticleDB.cpp index 08d1f04..cc66ba2 100644 --- a/src/Views/ViewParticleDB.cpp +++ b/src/Views/ViewParticleDB.cpp @@ -5,7 +5,7 @@ #include "Spelunky2.h" #include -S2Plugin::ViewParticleDB::ViewParticleDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::ParticleDB, parent) +S2Plugin::ViewParticleDB::ViewParticleDB(QWidget* parent) : AbstractDatabaseView(MemoryFieldType::ParticleDB, parent) { auto& particleEmitters = Configuration::get()->particleEmittersList(); setWindowTitle(QString("Particle DB (%1 particles)").arg(particleEmitters.count())); diff --git a/src/Views/ViewTextureDB.cpp b/src/Views/ViewTextureDB.cpp index 3973ac3..67991f9 100644 --- a/src/Views/ViewTextureDB.cpp +++ b/src/Views/ViewTextureDB.cpp @@ -6,7 +6,7 @@ #include "Spelunky2.h" #include -S2Plugin::ViewTextureDB::ViewTextureDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::TextureDB, parent) +S2Plugin::ViewTextureDB::ViewTextureDB(QWidget* parent) : AbstractDatabaseView(MemoryFieldType::TextureDB, parent) { auto& textureDB = Spelunky2::get()->get_TextureDB(); setWindowTitle(QString("Texture DB (%1 textures)").arg(textureDB.count()));