Skip to content

Commit

Permalink
Replace Threads view with SaveStates (#6)
Browse files Browse the repository at this point in the history
* replace Threads view with SaveStates
  • Loading branch information
Mr-Auto authored Jul 4, 2024
1 parent 7a57fcb commit 781038a
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 192 deletions.
4 changes: 2 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ x64dbg_plugin(${PROJECT_NAME}
include/Views/ViewVirtualFunctions.h
include/Views/ViewStdVector.h
include/Views/ViewJournalPage.h
include/Views/ViewThreads.h
include/Views/ViewSaveStates.h
include/Views/ViewStdMap.h
include/Views/ViewStdUnorderedMap.h
include/Views/ViewStdList.h
Expand Down Expand Up @@ -141,7 +141,7 @@ x64dbg_plugin(${PROJECT_NAME}
src/Views/ViewStdUnorderedMap.cpp
src/Views/ViewStdList.cpp
src/Views/ViewJournalPage.cpp
src/Views/ViewThreads.cpp
src/Views/ViewSaveStates.cpp
src/Views/ViewEntityList.cpp
src/QtHelpers/StyledItemDelegateHTML.cpp
src/QtHelpers/TreeViewMemoryFields.cpp
Expand Down
31 changes: 16 additions & 15 deletions include/Spelunky2.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,31 @@ namespace S2Plugin
uintptr_t get_OnlinePtr();
uintptr_t get_GameAPIPtr();
uintptr_t get_HudPtr();
uintptr_t get_SaveStatesPtr();
uintptr_t get_StatePtr() const
{
if (heapBaseAddr == 0)
return 0;
if (auto base = get_HeapBase(); base != 0)
return base + GAME_OFFSET::STATE;

return heapBaseAddr + GAME_OFFSET::STATE;
return 0;
};
uintptr_t get_LevelGenPtr() const
{
if (heapBaseAddr == 0)
return 0;

return heapBaseAddr + GAME_OFFSET::LEVEL_GEN;
if (auto base = get_HeapBase(); base != 0)
return base + GAME_OFFSET::LEVEL_GEN;

return 0;
};
uintptr_t get_LiquidEnginePtr() const
{
if (heapBaseAddr == 0)
return 0;
if (auto base = get_HeapBase(); base == 0)
return base + GAME_OFFSET::LIQUID_ENGINE;

return heapBaseAddr + GAME_OFFSET::LIQUID_ENGINE;
return 0;
}
uintptr_t get_HeapBase() const
{
return heapBaseAddr;
};
uintptr_t get_HeapBase() const;

const TextureDB& get_TextureDB();
const CharacterDB& get_CharacterDB();
const ParticleDB& get_ParticleDB();
Expand All @@ -78,12 +78,13 @@ namespace S2Plugin
size_t codeSectionSize{0};
uintptr_t afterBundle{0};
size_t afterBundleSize{0};
uintptr_t heapBaseAddr{0};
uintptr_t heapBasePtr{0};

uintptr_t mGameManagerPtr{0};
uintptr_t mOnlinePtr{0};
uintptr_t mGameAPIPtr{0};
uintptr_t mHudPtr{0};
uintptr_t mSaveStatesPtr{0};

EntityDB mEntityDB;
ParticleDB mParticleDB;
Expand All @@ -103,13 +104,13 @@ namespace S2Plugin
{
UNKNOWN1 = 0x8, // - ?
MALLOC = 0x20, // - malloc base
UNKNOWN2 = 0x3B4, // - ?
ILLUMINATION_SYNC = 0x3D0, // - illumination sync timer
PRNG = 0x3F0, // - PRNG
STATE = 0x4A0, // - State Memory
LEVEL_GEN = 0xD7B30, // - level gen
LIQUID_ENGINE = 0xD8650, // - liquid physics
UNKNOWN3 = 0x108420, // - some vector?
};
friend class ViewSaveStates;
};
} // namespace S2Plugin
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@

namespace S2Plugin
{
class ViewThreads : public QWidget
class ViewSaveStates : public QWidget
{
Q_OBJECT
public:
ViewThreads(QWidget* parent = nullptr);
ViewSaveStates(QWidget* parent = nullptr);

protected:
QSize sizeHint() const override;
QSize minimumSizeHint() const override;

private slots:
void cellClicked(int row, int column);
void refreshThreads();
void refreshSlots();

private:
QTableWidget* mMainTable;
Expand Down
5 changes: 3 additions & 2 deletions include/Views/ViewToolbar.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ namespace S2Plugin
void showMatrix(uintptr_t address, std::string name, std::string arrayTypeName, size_t rows, size_t columns);
void showEntityList(uintptr_t address);
void showStdList(uintptr_t address, std::string typeName, bool oldType = false);
void showSaveGame(uintptr_t address);

public slots:
ViewEntityDB* showEntityDB();
Expand All @@ -45,10 +46,10 @@ namespace S2Plugin
void showMainThreadLiquidPhysics();
void showEntities();
ViewVirtualTable* showVirtualTableLookup();
void showSaveGame();
void showMainThreadSaveGame();
void showLogger();
void showOnline();
void showThreads();
void showSaveStates();
void showGameAPI();
void showHud();
void showEntityFactory();
Expand Down
21 changes: 21 additions & 0 deletions src/Data/Lookup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,24 @@ uintptr_t S2Plugin::Spelunky2::get_HudPtr()
}
return mHudPtr;
}

uintptr_t S2Plugin::Spelunky2::get_SaveStatesPtr()
{
if (mSaveStatesPtr != 0)
return mSaveStatesPtr;

auto instructionAddress = Script::Pattern::FindMem(afterBundle, afterBundleSize, "90 83 C1 FF");
if (instructionAddress == 0)
{
displayError("Lookup error: unable to find SaveStates (1)");
return mSaveStatesPtr;
}
auto relativeOffset = Script::Memory::ReadDword(instructionAddress + 6);
mSaveStatesPtr = instructionAddress + 10 + relativeOffset;
if (!Script::Memory::IsValidPtr(mSaveStatesPtr))
{
displayError("Lookup error: unable to find SaveStates (2)");
mSaveStatesPtr = 0;
}
return mSaveStatesPtr;
}
22 changes: 17 additions & 5 deletions src/Spelunky2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@ S2Plugin::Spelunky2* S2Plugin::Spelunky2::get()

THREADLIST threadList;
uintptr_t heapBase{0};
uintptr_t heapBasePtr{0};
DbgGetThreadList(&threadList);
if (threadList.count == 0)
{
displayError("Could not retrieve thread list\nYou might be too fast, wait for the info in bottom left corner to change to \"Running\"");
return false;
}
for (auto x = 0; x < threadList.count; ++x)
{
auto threadAllInfo = threadList.list[x];
Expand All @@ -70,13 +76,14 @@ S2Plugin::Spelunky2* S2Plugin::Spelunky2::get()
auto tebAddress = DbgGetTebAddress(threadAllInfo.BasicInfo.ThreadId);
auto tebAddress11Ptr = Script::Memory::ReadQword(tebAddress + (11 * sizeof(uintptr_t)));
auto tebAddress11Value = Script::Memory::ReadQword(tebAddress11Ptr);
heapBase = Script::Memory::ReadQword(tebAddress11Value + TEB_offset);
heapBasePtr = tebAddress11Value + TEB_offset;
heapBase = Script::Memory::ReadQword(heapBasePtr);
break;
}
}
if (heapBase == 0)
if (!Script::Memory::IsValidPtr(heapBasePtr) || !Script::Memory::IsValidPtr(heapBase))
{
displayError("Could not retrieve heap base of the main thread!");
displayError("Could not retrieve heap base of the main thread!\nYou might be too fast, wait for the info in bottom left corner to change to \"Running\"");
return false;
}

Expand All @@ -85,7 +92,7 @@ S2Plugin::Spelunky2* S2Plugin::Spelunky2::get()
addr->codeSectionSize = Spelunky2CodeSectionSize;
addr->afterBundle = Spelunky2AfterBundle;
addr->afterBundleSize = Spelunky2CodeSectionStart + Spelunky2CodeSectionSize - Spelunky2AfterBundle;
addr->heapBaseAddr = heapBase;
addr->heapBasePtr = heapBasePtr;
ptr = addr;
}
return ptr;
Expand Down Expand Up @@ -135,7 +142,7 @@ uintptr_t S2Plugin::Spelunky2::get_SaveDataPtr()
if (heapOffsetSaveGame == 0)
return 0;

return heapBaseAddr + heapOffsetSaveGame;
return get_HeapBase() + heapOffsetSaveGame;
}

const QString& S2Plugin::Spelunky2::themeNameOfOffset(uintptr_t offset) const
Expand All @@ -156,3 +163,8 @@ const QString& S2Plugin::Spelunky2::themeNameOfOffset(uintptr_t offset) const
static const QString unknown{"UNKNOWN THEME"};
return unknown;
}

uintptr_t S2Plugin::Spelunky2::get_HeapBase() const
{
return Script::Memory::ReadQword(heapBasePtr);
};
167 changes: 167 additions & 0 deletions src/Views/ViewSaveStates.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#include "Views/ViewSaveStates.h"

#include "QtHelpers/StyledItemDelegateHTML.h"
#include "QtHelpers/WidgetAutorefresh.h"
#include "QtPlugin.h"
#include "Spelunky2.h"
#include "Views/ViewToolbar.h"
#include "pluginmain.h"
#include <QHeaderView>
#include <QPushButton>
#include <QString>
#include <QVBoxLayout>
#include <cstdint>

constexpr uint8_t gsSaveStates = 5;
enum Columns
{
InUse,
HeapBase,
State,
LevelGen,
LiquidPhysics,
SaveGame
};

S2Plugin::ViewSaveStates::ViewSaveStates(QWidget* parent) : QWidget(parent)
{
setWindowIcon(getCavemanIcon());
setWindowTitle("Save States");

auto mainLayout = new QVBoxLayout(this);
auto horLayout = new QHBoxLayout();
auto autoRefresh = new WidgetAutorefresh(100, this);
horLayout->addWidget(autoRefresh);
QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, this, &ViewSaveStates::refreshSlots);
horLayout->addStretch();
mainLayout->addLayout(horLayout);

mMainTable = new QTableWidget(this);
mMainTable->setAlternatingRowColors(true);
mMainTable->verticalHeader()->hide();
mMainTable->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
mMainTable->verticalHeader()->setDefaultSectionSize(30);
mMainTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
mMainTable->horizontalHeader()->setStretchLastSection(true);
mMainTable->setSelectionBehavior(QAbstractItemView::SelectRows);
mMainTable->setSelectionMode(QAbstractItemView::SingleSelection);
mMainTable->setColumnCount(6);
mMainTable->setRowCount(gsSaveStates);
auto HTMLDelegate = new StyledItemDelegateHTML(this);
mMainTable->setItemDelegate(HTMLDelegate);
mMainTable->setHorizontalHeaderLabels({"In Use", "Heap Base", "State", "LevelGen", "Liquid Phisics", "SaveGame"});
mMainTable->setColumnWidth(Columns::InUse, 60);
mMainTable->setColumnWidth(Columns::HeapBase, 130);
mMainTable->setColumnWidth(Columns::State, 130);
mMainTable->setColumnWidth(Columns::LevelGen, 130);
mMainTable->setColumnWidth(Columns::LiquidPhysics, 130);
mMainTable->setColumnWidth(Columns::SaveGame, 130);
HTMLDelegate->setCenterVertically(true);
QObject::connect(mMainTable, &QTableWidget::cellClicked, this, &ViewSaveStates::cellClicked);

mainLayout->addWidget(mMainTable);
refreshSlots();
autoRefresh->toggleAutoRefresh(true);
}

constexpr uint32_t gsRoleMemoryAddress = Qt::UserRole + 1;

void S2Plugin::ViewSaveStates::refreshSlots()
{
auto updateItem = [&](int row, int column, uintptr_t address)
{
QString text;
if (address != 0)
text = QString::asprintf("<font color='blue'><u>0x%016llX</u></font>", address);

auto item = mMainTable->item(row, column);
if (item == nullptr)
{
item = new QTableWidgetItem(text);
mMainTable->setItem(row, column, item);
}
else
item->setText(text);

item->setData(gsRoleMemoryAddress, address);
};

uintptr_t heapOffsetSaveGame = 0;
auto gm = Spelunky2::get()->get_GameManagerPtr();
if (gm != 0)
heapOffsetSaveGame = Script::Memory::ReadQword(Script::Memory::ReadQword(gm + 8));

auto saveStatePtr = Spelunky2::get()->get_SaveStatesPtr();
uint8_t emptySlots = Script::Memory::ReadByte(saveStatePtr);
saveStatePtr += 0x10;
for (uint8_t i = 0; i < gsSaveStates; ++i)
{
auto statePtr = Script::Memory::ReadQword(saveStatePtr + i * sizeof(uintptr_t));
if (i >= emptySlots)
mMainTable->setItem(i, Columns::InUse, new QTableWidgetItem("<font color='green'><b>Yes</b></font>"));
else
mMainTable->setItem(i, Columns::InUse, new QTableWidgetItem("<font color='#aaa'>No</font>"));

updateItem(i, Columns::HeapBase, statePtr);
updateItem(i, Columns::State, statePtr + Spelunky2::GAME_OFFSET::STATE);
updateItem(i, Columns::LevelGen, statePtr + Spelunky2::GAME_OFFSET::LEVEL_GEN);
updateItem(i, Columns::LiquidPhysics, statePtr + Spelunky2::GAME_OFFSET::LIQUID_ENGINE);
updateItem(i, Columns::SaveGame, heapOffsetSaveGame == 0 ? 0 : statePtr + heapOffsetSaveGame);
}
}

QSize S2Plugin::ViewSaveStates::sizeHint() const
{
return QSize(750, 375);
}

QSize S2Plugin::ViewSaveStates::minimumSizeHint() const
{
return QSize(150, 150);
}

void S2Plugin::ViewSaveStates::cellClicked(int row, int column)
{
auto clickedItem = mMainTable->item(row, column);
switch (column)
{
case Columns::HeapBase:
{
auto addr = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (addr != 0)
{
GuiDumpAt(addr);
GuiShowCpu();
}
break;
}
case Columns::State:
{
auto statePtr = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (statePtr != 0)
getToolbar()->showState(statePtr);
break;
}
case Columns::LevelGen:
{
auto levelGenPtr = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (levelGenPtr != 0)
getToolbar()->showLevelGen(levelGenPtr);
break;
}
case Columns::LiquidPhysics:
{
auto liquidPhisicsPtr = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (liquidPhisicsPtr != 0)
getToolbar()->showLiquidPhysics(liquidPhisicsPtr);
break;
}
case Columns::SaveGame:
{
auto saveGamePtr = clickedItem->data(gsRoleMemoryAddress).toULongLong();
if (saveGamePtr != 0)
getToolbar()->showSaveGame(saveGamePtr);
break;
}
}
}
Loading

0 comments on commit 781038a

Please sign in to comment.