diff --git a/CMakeLists.txt b/CMakeLists.txt index 3346f0c..d369cd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Data/Logger.h include/Data/StdString.h include/Data/StdMap.h + include/Data/EntityList.h include/Views/ViewToolbar.h include/Views/ViewEntityDB.h include/Views/ViewParticleDB.h @@ -77,6 +78,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Views/ViewJournalPage.h include/Views/ViewThreads.h include/Views/ViewStdMap.h + include/Views/ViewEntityList.h include/QtHelpers/StyledItemDelegateHTML.h include/QtHelpers/StyledItemDelegateColorPicker.h include/QtHelpers/TreeViewMemoryFields.h @@ -131,6 +133,7 @@ x64dbg_plugin(${PROJECT_NAME} src/Views/ViewStdMap.cpp src/Views/ViewJournalPage.cpp src/Views/ViewThreads.cpp + src/Views/ViewEntityList.cpp src/QtHelpers/StyledItemDelegateHTML.cpp src/QtHelpers/TreeViewMemoryFields.cpp src/QtHelpers/WidgetMemoryView.cpp diff --git a/include/Configuration.h b/include/Configuration.h index 09c1354..31e9443 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -129,6 +129,7 @@ namespace S2Plugin Double, Array, Matrix, + EntityList, }; struct VirtualFunction @@ -159,8 +160,8 @@ namespace S2Plugin size_t get_size() const; union { - // length, size of array etc. - size_t numberOfElements{0}; + // length, size of array etc. + size_t numberOfElements{0}; // row count for matrix size_t rows; }; @@ -249,7 +250,7 @@ namespace S2Plugin const std::vector>& refTitlesOfField(const std::string& fieldName) const; size_t getTypeSize(const std::string& typeName, bool entitySubclass = false); - const EntityList& entityList() const + const EntityNamesList& entityList() const { return entityNames; }; @@ -295,7 +296,7 @@ namespace S2Plugin void processRoomCodesJSON(nlohmann::ordered_json& json); MemoryField populateMemoryField(const nlohmann::ordered_json& field, const std::string& struct_name); - EntityList entityNames; + EntityNamesList entityNames; ParticleEmittersList particleEmitters; Configuration(); diff --git a/include/Data/EntityList.h b/include/Data/EntityList.h new file mode 100644 index 0000000..3498e3c --- /dev/null +++ b/include/Data/EntityList.h @@ -0,0 +1,159 @@ +#pragma once + +#include "Entity.h" +#include "pluginmain.h" +#include "read_helpers.h" +#include +#include + +namespace S2Plugin +{ + + class EntityList + { + public: + EntityList(uintptr_t _address) : address(_address){}; + uintptr_t entities() const + { + return Script::Memory::ReadQword(address); + } + + uintptr_t uids() const + { + return Script::Memory::ReadQword(address + sizeof(uintptr_t)); + } + uint32_t capacity() const + { + return Script::Memory::ReadDword(address + sizeof(uintptr_t) * 2); + } + uint32_t size() const + { + return Script::Memory::ReadDword(address + sizeof(uintptr_t) * 2 + sizeof(uint32_t)); + } + struct Iterator + { + // Iterator(){}; + Iterator(const EntityList entityList, uint32_t index) noexcept : Iterator(entityList.begin()) + { + advance(index); + } + void advance(int count) noexcept // should probably be int64 ? + { + addr.first += count * sizeof(uintptr_t); + addr.second += count * sizeof(uint32_t); + } + Iterator& operator++() noexcept + { + advance(1); + return *this; + } + Iterator operator++(int) noexcept + { + auto copy = *this; + advance(1); + return copy; + } + Iterator& operator--() noexcept + { + advance(-1); + } + Iterator operator--(int) noexcept + { + auto copy = *this; + advance(-1); + return copy; + } + std::pair operator*() const + { + return {addr.first, Script::Memory::ReadDword(addr.second)}; + } + bool operator==(const Iterator& other) const noexcept + { + return addr.first == other.addr.first; + } + bool operator!=(const Iterator& other) const noexcept + { + return addr.first != other.addr.first; + } + uintptr_t entityRaw() const + { + return Script::Memory::ReadQword(addr.first); + } + Entity entity() const + { + return Script::Memory::ReadQword(addr.first); + } + uint32_t uid() const + { + return Script::Memory::ReadDword(addr.second); + } + + private: + Iterator(uintptr_t entitiesAddress, uintptr_t uidsAddress) : addr(entitiesAddress, uidsAddress){}; + std::pair addr; + friend class EntityList; + }; + + Iterator begin() const + { + uintptr_t pointers[2] = {0, 0}; + // slightly faster then reading both thru ReadQword + Script::Memory::Read(address, &pointers, sizeof(uintptr_t) * 2, nullptr); + return {pointers[0], pointers[1]}; + } + Iterator end() const + { + auto full = getFullStruct(); + uintptr_t entitiesEnd = full.entities + full.size * sizeof(uintptr_t); + uintptr_t uidsEnd = full.uids + full.size * sizeof(uint32_t); + return {entitiesEnd, uidsEnd}; + } + const Iterator cbegin() const + { + return begin(); + } + const Iterator cend() const + { + return end(); + } + Iterator find(uint32_t uid) const + { + auto endIterator = end(); + for (auto it = begin(); it != endIterator; ++it) + { + if (it.uid() == uid) + return it; + } + return endIterator; + } + Iterator findEntity(uintptr_t addr) const + { + auto endIterator = end(); + for (auto it = begin(); it != endIterator; ++it) + { + if (it.entityRaw() == addr) + return it; + } + return endIterator; + } + + private: + uintptr_t address; + struct TrueEntityList + { + uintptr_t entities{0}; + uintptr_t uids{0}; + uint32_t cap{0}; + uint32_t size{0}; + + private: + TrueEntityList() = delete; + // TrueEntityList(const TrueEntityList&) = delete; + // TrueEntityList& operator=(const TrueEntityList&) = delete; + }; + TrueEntityList getFullStruct() const + { + return Read(address); + } + }; +}; // namespace S2Plugin diff --git a/include/Data/IDNameList.h b/include/Data/IDNameList.h index 73cd0d0..f56805e 100644 --- a/include/Data/IDNameList.h +++ b/include/Data/IDNameList.h @@ -54,12 +54,12 @@ namespace S2Plugin ParticleEmittersList(const ParticleEmittersList&) = delete; ParticleEmittersList& operator=(const ParticleEmittersList&) = delete; }; - class EntityList : public IDNameList + class EntityNamesList : public IDNameList { public: - explicit EntityList(); + explicit EntityNamesList(); - EntityList(const EntityList&) = delete; - EntityList& operator=(const EntityList&) = delete; + EntityNamesList(const EntityNamesList&) = delete; + EntityNamesList& operator=(const EntityNamesList&) = delete; }; } // namespace S2Plugin diff --git a/include/Views/ViewEntityList.h b/include/Views/ViewEntityList.h new file mode 100644 index 0000000..0617b53 --- /dev/null +++ b/include/Views/ViewEntityList.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +namespace S2Plugin +{ + class TreeViewMemoryFields; + + class ViewEntityList : public QWidget + { + Q_OBJECT + public: + ViewEntityList(uintptr_t address, QWidget* parent = nullptr); + + protected: + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + + private slots: + void refreshEntityListContents(); + + private: + uintptr_t mEntityListAddress; + + TreeViewMemoryFields* mMainTreeView; + }; +} // namespace S2Plugin diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index aa36b7e..d5a586d 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -28,6 +28,7 @@ namespace S2Plugin void showLevelGen(uintptr_t address); void showArray(uintptr_t address, std::string name, std::string arrayTypeName, size_t length); void showMatrix(uintptr_t address, std::string name, std::string arrayTypeName, size_t rows, size_t columns); + void showEntityList(uintptr_t address); public slots: ViewEntityDB* showEntityDB(); diff --git a/src/Configuration.cpp b/src/Configuration.cpp index cc7428f..53b3229 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -145,6 +145,7 @@ namespace S2Plugin {MemoryFieldType::IPv4Address, "IPv4Address", "uint32_t", "IPv4Address", 4, false}, {MemoryFieldType::Array, "Array", "", "Array", 0, false}, {MemoryFieldType::Matrix, "Matrix", "", "Matrix", 0, false}, + {MemoryFieldType::EntityList, "EntityList", "EntityList*", "EntityList", 24, false}, // Other //{MemoryFieldType::EntitySubclass, "", "", "", 0}, //{MemoryFieldType::DefaultStructType, "", "", "", 0}, diff --git a/src/Data/IDNameList.cpp b/src/Data/IDNameList.cpp index 4ac6196..a8b60f7 100644 --- a/src/Data/IDNameList.cpp +++ b/src/Data/IDNameList.cpp @@ -58,7 +58,7 @@ std::string S2Plugin::IDNameList::nameForID(uint32_t id) const static const std::regex regexEntityLine("^([0-9]+): ENT_TYPE_(.*?)$", std::regex_constants::ECMAScript); -S2Plugin::EntityList::EntityList() : IDNameList("plugins/Spelunky2Entities.txt", regexEntityLine) {} +S2Plugin::EntityNamesList::EntityNamesList() : IDNameList("plugins/Spelunky2Entities.txt", regexEntityLine) {} static const std::regex regexParticleLine("^([0-9]+): PARTICLEEMITTER_(.*?)$", std::regex_constants::ECMAScript); diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 64bf322..5ac9996 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -1880,7 +1880,12 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional 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); + uintptr_t beginPointer = Script::Memory::ReadQword(valueMemoryOffset); + if (beginPointer == value.value()) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); + // maybe show hex as the begin pointer ? } @@ -1888,10 +1893,14 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { std::optional comparisonValue; auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x8; - comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, false, false, highlightColor); if (comparisonValue.has_value()) { - itemComparisonValue->setData("Show contents", Qt::DisplayRole); + uintptr_t beginPointer = Script::Memory::ReadQword(valueComparisonMemoryOffset); + if (beginPointer == comparisonValue.value()) + itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemComparisonValue->setData("Show contents", Qt::DisplayRole); } itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1914,7 +1923,10 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional 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); + if (value.value() == 0) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); // maybe show hex as the pointer ? } @@ -1922,10 +1934,13 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { std::optional comparisonValue; auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x8; - comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, false, false, highlightColor); if (comparisonValue.has_value()) { - itemComparisonValue->setData("Show contents", Qt::DisplayRole); + if (comparisonValue.value() == 0) + itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemComparisonValue->setData("Show contents", Qt::DisplayRole); } // maybe it should be based on the pointer not size? itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1941,6 +1956,44 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::EntityList: + { + std::optional value; + // we use the size to check if it was changed + value = updateField(itemField, valueMemoryOffset == 0 ? 0 : valueMemoryOffset + 0x14, itemValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); + if (value.has_value()) + { + if (value.value() == 0) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); + } + + if (comparisonActive) + { + std::optional comparisonValue; + auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x14; + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, false, highlightColor); + if (comparisonValue.has_value()) + { + if (comparisonValue.value() == 0) + 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::Skip: { // TODO when setting for skip is done @@ -2429,6 +2482,15 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } break; } + case MemoryFieldType::EntityList: + { + auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (offset != 0) + { + getToolbar()->showEntityList(offset); + } + break; + } } emit memoryFieldValueUpdated(index.row(), clickedItem->parent()); } diff --git a/src/Views/ViewEntityList.cpp b/src/Views/ViewEntityList.cpp new file mode 100644 index 0000000..f90b722 --- /dev/null +++ b/src/Views/ViewEntityList.cpp @@ -0,0 +1,69 @@ +#include "Views/ViewEntityList.h" + +#include "Configuration.h" +#include "Data/EntityList.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetAutorefresh.h" +#include "QtPlugin.h" +#include +#include +#include + +S2Plugin::ViewEntityList::ViewEntityList(uintptr_t address, QWidget* parent) : mEntityListAddress(address), QWidget(parent) +{ + setWindowIcon(getCavemanIcon()); + setWindowTitle("EntityList"); + + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto refreshLayout = new QHBoxLayout(); + mainLayout->addLayout(refreshLayout); + + auto refreshVectorButton = new QPushButton("Refresh list", this); + QObject::connect(refreshVectorButton, &QPushButton::clicked, this, &ViewEntityList::refreshEntityListContents); + refreshLayout->addWidget(refreshVectorButton); + + auto autoRefresh = new WidgetAutorefresh(300, this); + refreshLayout->addWidget(autoRefresh); + + mMainTreeView = new TreeViewMemoryFields(this); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, mMainTreeView, static_cast(&TreeViewMemoryFields::updateTree)); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment).disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress); + mainLayout->addWidget(mMainTreeView); + autoRefresh->toggleAutoRefresh(true); + refreshEntityListContents(); +} + +void S2Plugin::ViewEntityList::refreshEntityListContents() +{ + mMainTreeView->clear(); + + EntityList entityList{mEntityListAddress}; + + for (auto entity : entityList) + { + MemoryField entityField; + entityField.name = "uid_" + std::to_string(entity.second); + entityField.isPointer = true; + entityField.type = MemoryFieldType::EntityPointer; + mMainTreeView->addMemoryField(entityField, {}, entity.first, 0); + } + + 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); +} + +QSize S2Plugin::ViewEntityList::sizeHint() const +{ + return QSize(750, 550); +} + +QSize S2Plugin::ViewEntityList::minimumSizeHint() const +{ + return QSize(150, 150); +} diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index 9ab970e..974e75e 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -6,6 +6,7 @@ #include "Views/ViewEntities.h" #include "Views/ViewEntity.h" #include "Views/ViewEntityDB.h" +#include "Views/ViewEntityList.h" #include "Views/ViewJournalPage.h" #include "Views/ViewLevelGen.h" #include "Views/ViewLogger.h" @@ -200,6 +201,14 @@ void S2Plugin::ViewToolbar::showMatrix(uintptr_t address, std::string name, std: win->setAttribute(Qt::WA_DeleteOnClose); } +void S2Plugin::ViewToolbar::showEntityList(uintptr_t address) +{ + auto w = new ViewEntityList(address); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + // // slots: //