Merge pull request #19953 from kotcrab/struct-viewer-improvements

Struct viewer - Add watch edit and show in window menu
This commit is contained in:
Henrik Rydgård authored and GitHub committed 2025-02-06 09:28:50 -06:00
commit b73536f820
5 files changed
+176 -106

No files matched your search

+10 -10
View File
@@ -6,7 +6,7 @@
using namespace json;
static GhidraTypeKind ResolveTypeKind(const std::string& kind) {
static GhidraTypeKind ResolveTypeKind(const std::string &kind) {
if (kind == "ENUM") return ENUM;
if (kind == "TYPEDEF") return TYPEDEF;
if (kind == "POINTER") return POINTER;
@@ -18,9 +18,9 @@ static GhidraTypeKind ResolveTypeKind(const std::string& kind) {
return UNKNOWN;
}
static bool IsBitfieldEnum(const std::vector<GhidraEnumMember>& enumMembers) {
static bool IsBitfieldEnum(const std::vector<GhidraEnumMember> &enumMembers) {
u64 previousValues = 0;
for (const auto& member : enumMembers) {
for (const auto &member : enumMembers) {
if (previousValues & member.value) {
return false;
}
@@ -35,7 +35,7 @@ GhidraClient::~GhidraClient() {
}
}
void GhidraClient::FetchAll(const std::string& host, const int port) {
void GhidraClient::FetchAll(const std::string &host, const int port) {
std::lock_guard<std::mutex> lock(mutex_);
if (status_ != Status::Idle) {
return;
@@ -47,7 +47,7 @@ void GhidraClient::FetchAll(const std::string& host, const int port) {
});
}
bool GhidraClient::FetchAllDo(const std::string& host, const int port) {
bool GhidraClient::FetchAllDo(const std::string &host, const int port) {
std::lock_guard<std::mutex> lock(mutex_);
host_ = host;
port_ = port;
@@ -129,12 +129,12 @@ bool GhidraClient::FetchTypes() {
switch (type.kind) {
case ENUM: {
const JsonNode* enumEntries = entry.getArray("members");
const JsonNode *enumEntries = entry.getArray("members");
if (!enumEntries) {
pendingResult_.error = "missing enum members";
return false;
}
for (const JsonNode* pEnumEntry : enumEntries->value) {
for (const JsonNode *pEnumEntry : enumEntries->value) {
JsonGet enumEntry = pEnumEntry->value;
GhidraEnumMember member;
member.name = enumEntry.getStringOr("name", "");
@@ -159,12 +159,12 @@ bool GhidraClient::FetchTypes() {
break;
case STRUCTURE:
case UNION: {
const JsonNode* compositeEntries = entry.getArray("members");
const JsonNode *compositeEntries = entry.getArray("members");
if (!compositeEntries) {
pendingResult_.error = "missing composite members";
return false;
}
for (const JsonNode* pCompositeEntry : compositeEntries->value) {
for (const JsonNode *pCompositeEntry : compositeEntries->value) {
JsonGet compositeEntry = pCompositeEntry->value;
GhidraCompositeMember member;
member.fieldName = compositeEntry.getStringOr("fieldName", "");
@@ -192,7 +192,7 @@ bool GhidraClient::FetchTypes() {
return true;
}
bool GhidraClient::FetchResource(const std::string& path, std::string& outResult) {
bool GhidraClient::FetchResource(const std::string &path, std::string &outResult) {
http::Client http;
if (!http.Resolve(host_.c_str(), port_)) {
pendingResult_.error = "can't resolve host";
+3 -3
View File
@@ -108,7 +108,7 @@ public:
~GhidraClient();
// If client is idle then asynchronously starts fetching data from Ghidra.
void FetchAll(const std::string& host, int port);
void FetchAll(const std::string &host, int port);
// If client is ready then updates the result field with newly fetched data.
// This must be called from the thread using the result.
@@ -128,11 +128,11 @@ private:
std::string host_;
int port_ = 0;
bool FetchAllDo(const std::string& host, int port);
bool FetchAllDo(const std::string &host, int port);
bool FetchSymbols();
bool FetchTypes();
bool FetchResource(const std::string& path, std::string& outResult);
bool FetchResource(const std::string &path, std::string &outResult);
};
+1 -1
View File
@@ -1408,7 +1408,7 @@ void ImDebugger::Frame(MIPSDebugInterface *mipsDebug, GPUDebugInterface *gpuDebu
}
if (cfg_.structViewerOpen) {
structViewer_.Draw(mipsDebug, &cfg_.structViewerOpen);
structViewer_.Draw(cfg_, control, mipsDebug);
}
if (cfg_.geDebuggerOpen) {
+138 -79
View File
@@ -12,6 +12,7 @@
#include "Core/MIPS/MIPSDebugInterface.h"
#include "UI/ImDebugger/ImStructViewer.h"
#include "UI/ImDebugger/ImDebugger.h"
static auto COLOR_GRAY = ImVec4(0.45f, 0.45f, 0.45f, 1);
static auto COLOR_RED = ImVec4(1, 0, 0, 1);
@@ -31,7 +32,7 @@ enum class BuiltInType {
struct BuiltIn {
BuiltInType type;
ImGuiDataType imGuiType;
const char* hexFormat;
const char *hexFormat;
};
static const std::unordered_map<std::string, BuiltIn> knownBuiltIns = {
@@ -72,7 +73,7 @@ static const std::unordered_map<std::string, BuiltIn> knownBuiltIns = {
{"/void", {BuiltInType::Void, -1, nullptr}},
};
static void DrawBuiltInEditPopup(const BuiltIn& builtIn, const u32 address) {
static void DrawBuiltInEditPopup(const BuiltIn &builtIn, const u32 address) {
if (builtIn.imGuiType == -1) {
return;
}
@@ -101,7 +102,7 @@ static void DrawBuiltInEditPopup(const BuiltIn& builtIn, const u32 address) {
break;
}
}
void* data = Memory::GetPointerWriteUnchecked(address);
void *data = Memory::GetPointerWriteUnchecked(address);
if (builtIn.hexFormat) {
ImGui::DragScalar("Value (hex)", builtIn.imGuiType, data, 0.2f, nullptr, nullptr, builtIn.hexFormat);
}
@@ -129,7 +130,7 @@ static void DrawIntBuiltInEditPopup(const u32 address, const u32 length) {
}
}
static void DrawBuiltInContent(const BuiltIn& builtIn, const u32 address) {
static void DrawBuiltInContent(const BuiltIn &builtIn, const u32 address) {
switch (builtIn.type) {
case BuiltInType::Bool:
ImGui::Text("= %s", Memory::Read_U8(address) ? "true" : "false");
@@ -188,6 +189,32 @@ static u64 ReadMemoryInt(const u32 address, const u32 length) {
}
}
void ImStructViewer::WatchForm::Clear() {
memset(name, 0, sizeof(name));
memset(expression, 0, sizeof(expression));
dynamic = false;
error = "";
typeFilter.Clear();
// Not clearing the actual selected type on purpose here, user will have to reselect one anyway and
// maybe there is a chance they will reuse the current one
}
void ImStructViewer::WatchForm::SetFrom(const std::unordered_map<std::string, GhidraType> &types, const Watch &watch) {
if (!types.count(watch.typePathName)) {
return;
}
snprintf(name, sizeof(name), "%s", watch.name.c_str());
typeDisplayName = types.at(watch.typePathName).displayName;
typePathName = watch.typePathName;
if (watch.expression.empty()) {
snprintf(expression, sizeof(expression), "0x%x", watch.address);
} else {
snprintf(expression, sizeof(expression), "%s", watch.expression.c_str());
}
dynamic = !watch.expression.empty();
error = "";
}
static constexpr int COLUMN_NAME = 0;
static constexpr int COLUMN_TYPE = 1;
static constexpr int COLUMN_CONTENT = 2;
@@ -196,10 +223,11 @@ static constexpr int COLUMN_CONTENT = 2;
static constexpr int MAX_POINTER_ELEMENTS = 0x100000;
static constexpr u32 INDEXED_MEMBERS_CHUNK_SIZE = 0x1000;
void ImStructViewer::Draw(MIPSDebugInterface* mipsDebug, bool* open) {
void ImStructViewer::Draw(ImConfig &cfg, ImControl &control, MIPSDebugInterface *mipsDebug) {
control_ = &control;
mipsDebug_ = mipsDebug;
ImGui::SetNextWindowSize(ImVec2(750, 550), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Struct viewer", open) || !mipsDebug->isAlive() || !Memory::IsActive()) {
if (!ImGui::Begin("Struct viewer", &cfg.structViewerOpen) || !mipsDebug->isAlive() || !Memory::IsActive()) {
ImGui::End();
return;
}
@@ -271,8 +299,11 @@ void ImStructViewer::DrawStructViewer() {
addWatch_ = Watch();
}
if (removeWatchId_ != -1) {
auto pred = [&](const Watch& watch) { return watch.id == removeWatchId_; };
auto pred = [&](const Watch &watch) { return watch.id == removeWatchId_; };
watches_.erase(std::remove_if(watches_.begin(), watches_.end(), pred), watches_.end());
if (editWatchId_ == removeWatchId_) {
ClearWatchForm();
}
removeWatchId_ = -1;
}
@@ -300,7 +331,7 @@ void ImStructViewer::DrawGlobals() {
ImGui::TableSetupColumn("Content");
ImGui::TableHeadersRow();
for (const auto& symbol : ghidraClient_.result.symbols) {
for (const auto &symbol : ghidraClient_.result.symbols) {
if (!symbol.label || !symbol.userDefined || symbol.dataTypePathName.empty()) {
continue;
}
@@ -315,7 +346,7 @@ void ImStructViewer::DrawGlobals() {
}
void ImStructViewer::DrawWatch() {
DrawNewWatchEntry();
DrawWatchForm();
ImGui::Dummy(ImVec2(1, 6));
watchFilter_.Draw();
@@ -328,7 +359,7 @@ void ImStructViewer::DrawWatch() {
ImGui::TableSetupColumn("Content");
ImGui::TableHeadersRow();
for (const auto& watch : watches_) {
for (const auto &watch : watches_) {
if (!watchFilter_.PassFilter(watch.name.c_str())) {
continue;
}
@@ -349,23 +380,23 @@ void ImStructViewer::DrawWatch() {
}
}
void ImStructViewer::DrawNewWatchEntry() {
void ImStructViewer::DrawWatchForm() {
ImGui::PushItemWidth(150);
ImGui::InputText("Name", newWatch_.name, IM_ARRAYSIZE(newWatch_.name));
ImGui::InputText("Name", watchForm_.name, IM_ARRAYSIZE(watchForm_.name));
ImGui::SameLine();
if (ImGui::BeginCombo("Type", newWatch_.typeDisplayName.c_str())) {
if (ImGui::BeginCombo("Type", watchForm_.typeDisplayName.c_str())) {
if (ImGui::IsWindowAppearing()) {
ImGui::SetKeyboardFocusHere(0);
}
newWatch_.typeFilter.Draw();
for (const auto& entry : ghidraClient_.result.types) {
const auto& type = entry.second;
if (newWatch_.typeFilter.PassFilter(type.displayName.c_str())) {
watchForm_.typeFilter.Draw();
for (const auto &entry : ghidraClient_.result.types) {
const auto &type = entry.second;
if (watchForm_.typeFilter.PassFilter(type.displayName.c_str())) {
ImGui::PushID(type.pathName.c_str());
if (ImGui::Selectable(type.displayName.c_str(), newWatch_.typePathName == type.pathName)) {
newWatch_.typePathName = type.pathName;
newWatch_.typeDisplayName = type.displayName;
if (ImGui::Selectable(type.displayName.c_str(), watchForm_.typePathName == type.pathName)) {
watchForm_.typePathName = type.pathName;
watchForm_.typeDisplayName = type.displayName;
}
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip) && ImGui::BeginTooltip()) {
ImGui::Text("%s (%s)", type.displayName.c_str(), type.pathName.c_str());
@@ -379,9 +410,9 @@ void ImStructViewer::DrawNewWatchEntry() {
}
ImGui::SameLine();
ImGui::InputText("Expression", newWatch_.expression, IM_ARRAYSIZE(newWatch_.expression));
ImGui::InputText("Expression", watchForm_.expression, IM_ARRAYSIZE(watchForm_.expression));
ImGui::SameLine();
ImGui::Checkbox("Dynamic", &newWatch_.dynamic);
ImGui::Checkbox("Dynamic", &watchForm_.dynamic);
if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip | ImGuiHoveredFlags_DelayNormal)) {
ImGui::SetTooltip("When checked the expression will be\nre-evaluated on each frame.");
}
@@ -389,45 +420,65 @@ void ImStructViewer::DrawNewWatchEntry() {
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImGui::Button("Add watch")) {
if (editWatchId_ == -1 ? ImGui::Button("Add watch") : ImGui::Button("Edit watch")) {
u32 val;
PostfixExpression postfix;
if (newWatch_.typePathName.empty()) {
newWatch_.error = "type can't be empty";
} else if (!initExpression(mipsDebug_, newWatch_.expression, postfix)
if (watchForm_.typePathName.empty()) {
watchForm_.error = "type can't be empty";
} else if (!initExpression(mipsDebug_, watchForm_.expression, postfix)
|| !parseExpression(mipsDebug_, postfix, val)) {
newWatch_.error = "invalid expression";
watchForm_.error = "invalid expression";
} else {
std::string watchName = newWatch_.name;
std::string watchName = watchForm_.name;
if (watchName.empty()) {
watchName = "<watch>";
}
watches_.emplace_back(Watch{
nextWatchId_++,
newWatch_.dynamic ? newWatch_.expression : "",
newWatch_.dynamic ? 0 : val,
newWatch_.typePathName,
newWatch_.dynamic ? watchName + " (" + newWatch_.expression + ")" : watchName
});
memset(newWatch_.name, 0, sizeof(newWatch_.name));
memset(newWatch_.expression, 0, sizeof(newWatch_.name));
newWatch_.dynamic = false;
newWatch_.error = "";
newWatch_.typeFilter.Clear();
// Not clearing the actual selected type on purpose here, user will have to reselect one anyway and
// maybe there is a chance they will reuse the current one
if (watchForm_.dynamic) {
watchName = watchName + " (" + watchForm_.expression + ")";
}
if (editWatchId_ == -1) {
watches_.emplace_back(Watch{
nextWatchId_++,
watchForm_.dynamic ? watchForm_.expression : "",
watchForm_.dynamic ? 0 : val,
watchForm_.typePathName,
watchName
});
} else {
for (auto &watch : watches_) {
if (watch.id == editWatchId_) {
watch.expression = watchForm_.dynamic ? watchForm_.expression : "";
watch.address = watchForm_.dynamic ? 0 : val;
watch.typePathName = watchForm_.typePathName;
watch.name = watchName;
break;
}
}
}
ClearWatchForm();
}
}
if (!newWatch_.error.empty()) {
if (editWatchId_ != -1) {
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
ClearWatchForm();
}
}
if (!watchForm_.error.empty()) {
ImGui::PushStyleColor(ImGuiCol_Text, COLOR_RED);
ImGui::TextWrapped("Error: %s", newWatch_.error.c_str());
ImGui::TextWrapped("Error: %s", watchForm_.error.c_str());
ImGui::PopStyleColor();
}
}
void ImStructViewer::ClearWatchForm() {
watchForm_.Clear();
editWatchId_ = -1;
}
static void DrawTypeColumn(
const std::string& format,
const std::string& typeDisplayName,
const std::string &format,
const std::string &typeDisplayName,
const u32 base,
const u32 offset
) {
@@ -444,17 +495,17 @@ static void DrawTypeColumn(
}
static void DrawArrayContent(
const std::unordered_map<std::string, GhidraType>& types,
const GhidraType& type,
const std::unordered_map<std::string, GhidraType> &types,
const GhidraType &type,
const u32 address
) {
if (type.arrayElementLength != 1 || !types.count(type.arrayTypePathName)) {
return;
}
const auto& arrayType = types.at(type.arrayTypePathName);
const auto &arrayType = types.at(type.arrayTypePathName);
bool charElement = false;
if (arrayType.kind == TYPEDEF && types.count(arrayType.typedefBaseTypePathName)) {
const auto& baseArrayType = types.at(arrayType.typedefBaseTypePathName);
const auto &baseArrayType = types.at(arrayType.typedefBaseTypePathName);
charElement = baseArrayType.pathName == "/char";
} else {
charElement = arrayType.pathName == "/char";
@@ -462,7 +513,7 @@ static void DrawArrayContent(
if (!charElement) {
return;
}
const char* charPointer = Memory::GetCharPointerUnchecked(address);
const char *charPointer = Memory::GetCharPointerUnchecked(address);
std::string text(charPointer, charPointer + type.arrayElementCount);
text = std::regex_replace(text, std::regex("\n"), "\\n");
ImGui::Text("= \"%s\"", text.c_str());
@@ -483,18 +534,18 @@ static void DrawPointerText(const u32 value) {
}
static void DrawPointerContent(
const std::unordered_map<std::string, GhidraType>& types,
const GhidraType& type,
const std::unordered_map<std::string, GhidraType> &types,
const GhidraType &type,
const u32 value
) {
if (!types.count(type.pointerTypePathName)) {
DrawPointerText(value);
return;
}
const auto& pointedType = types.at(type.pointerTypePathName);
const auto &pointedType = types.at(type.pointerTypePathName);
bool charPointerElement = false;
if (pointedType.kind == TYPEDEF && types.count(pointedType.typedefBaseTypePathName)) {
const auto& basePointedType = types.at(pointedType.typedefBaseTypePathName);
const auto &basePointedType = types.at(pointedType.typedefBaseTypePathName);
charPointerElement = basePointedType.pathName == "/char";
} else {
charPointerElement = pointedType.pathName == "/char";
@@ -503,7 +554,7 @@ static void DrawPointerContent(
DrawPointerText(value);
return;
}
const char* charPointer = Memory::GetCharPointerUnchecked(value);
const char *charPointer = Memory::GetCharPointerUnchecked(value);
std::string text(charPointer);
text = std::regex_replace(text, std::regex("\n"), "\\n");
ImGui::Text("= \"%s\"", text.c_str());
@@ -512,7 +563,7 @@ static void DrawPointerContent(
// If enum is a bitfield then format as it would look in code with 'or' operator (e.g. "ALIGN_TOP | ALIGN_LEFT")
// otherwise just try to find exact matching member
static std::string FormatEnumValue(
const std::vector<GhidraEnumMember>& enumMembers,
const std::vector<GhidraEnumMember> &enumMembers,
const u64 value,
const bool bitfield
) {
@@ -522,7 +573,7 @@ static std::string FormatEnumValue(
// The value for the yet unknown enum members, it will be non-zero if the enum definition is incomplete
u64 remainderValue = value;
for (const auto& member : enumMembers) {
for (const auto &member : enumMembers) {
remainderValue &= ~member.value;
}
if (remainderValue != 0) {
@@ -530,7 +581,7 @@ static std::string FormatEnumValue(
hasPrevious = true;
}
for (const auto& member : enumMembers) {
for (const auto &member : enumMembers) {
if ((value & member.value) != 0 || (value == 0 && member.value == 0)) {
if (hasPrevious) {
ss << " | ";
@@ -542,7 +593,7 @@ static std::string FormatEnumValue(
return ss.str();
}
for (const auto& member : enumMembers) {
for (const auto &member : enumMembers) {
if (value == member.value) {
return member.name;
}
@@ -553,13 +604,13 @@ static std::string FormatEnumValue(
void ImStructViewer::DrawType(
const u32 base,
const u32 offset,
const std::string& typePathName,
const char* typeDisplayNameOverride,
const char* name,
const std::string &typePathName,
const char *typeDisplayNameOverride,
const char *name,
const int watchId,
const ImGuiTreeNodeFlags extraTreeNodeFlags
) {
const auto& types = ghidraClient_.result.types;
const auto &types = ghidraClient_.result.types;
// Generic pointer is not included in the type listing, need to resolve it manually to void*
if (typePathName == "/pointer") {
DrawType(base, offset, "/void *", "pointer", name, watchId);
@@ -575,7 +626,7 @@ void ImStructViewer::DrawType(
// Resolve typedefs as early as possible
if (hasType) {
const auto& type = types.at(typePathName);
const auto &type = types.at(typePathName);
if (type.kind == TYPEDEF) {
DrawType(base, offset, type.typedefBaseTypePathName, type.displayName.c_str(), name, watchId);
return;
@@ -607,7 +658,7 @@ void ImStructViewer::DrawType(
return;
}
const auto& type = types.at(typePathName);
const auto &type = types.at(typePathName);
const std::string typeDisplayName =
typeDisplayNameOverride == nullptr ? type.displayName : typeDisplayNameOverride;
@@ -663,10 +714,8 @@ void ImStructViewer::DrawType(
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(COLUMN_NAME);
int inputElementCount = pointerElementCount;
constexpr int step = 1;
constexpr int stepFast = 10;
ImGui::PushItemWidth(110);
ImGui::InputScalar("Elements", ImGuiDataType_S32, &inputElementCount, &step, &stepFast, "%x");
ImGui::InputScalar("Elements", ImGuiDataType_S32, &inputElementCount, NULL, NULL, "%x");
if (ImGui::IsItemDeactivatedAfterEdit()) {
const int newValue = std::clamp(inputElementCount, 1, MAX_POINTER_ELEMENTS);
ImGui::GetStateStorage()->SetInt(countStateId, newValue);
@@ -705,7 +754,7 @@ void ImStructViewer::DrawType(
DrawTypeColumn("%s", typeDisplayName, base, offset);
if (nodeOpen) {
for (const auto& member : type.compositeMembers) {
for (const auto &member : type.compositeMembers) {
DrawType(base, offset + member.offset, member.typePathName, nullptr,
member.fieldName.c_str(), -1);
}
@@ -719,7 +768,7 @@ void ImStructViewer::DrawType(
DrawTypeColumn("%s", typeDisplayName, base, offset);
ImGui::TableSetColumnIndex(COLUMN_CONTENT);
ImGui::Text("<function definition>"); // TODO could be go to in disassembler here
ImGui::Text("<function definition>");
break;
case BUILT_IN: {
ImGui::TreeNodeEx("Field", leafFlags, "%s", name);
@@ -758,8 +807,8 @@ void ImStructViewer::DrawType(
void ImStructViewer::DrawIndexedMembers(
const u32 base,
const u32 offset,
const std::string& typePathName,
const char* name,
const std::string &typePathName,
const char *name,
const u32 elementCount,
const int elementLength,
const bool openFirst
@@ -810,10 +859,10 @@ void ImStructViewer::DrawContextMenu(
const u32 base,
const u32 offset,
const int length,
const std::string& typePathName,
const char* name,
const std::string &typePathName,
const char *name,
const int watchId,
const u64* value
const u64 *value
) {
ImGui::OpenPopupOnItemClick("context", ImGuiPopupFlags_MouseButtonRight);
if (ImGui::BeginPopup("context")) {
@@ -825,6 +874,7 @@ void ImStructViewer::DrawContextMenu(
if (value && ImGui::MenuItem("Copy value")) {
CopyHexNumberToClipboard(*value);
}
ImGui::Separator();
// This might be called when iterating over existing watches so can't modify the watch vector directly here
if (watchId < 0) {
@@ -838,11 +888,20 @@ void ImStructViewer::DrawContextMenu(
if (ImGui::MenuItem("Remove watch")) {
removeWatchId_ = watchId;
}
if (ImGui::MenuItem("Edit watch")) {
for (const auto &watch : watches_) {
if (watch.id == watchId) {
editWatchId_ = watchId;
watchForm_.SetFrom(ghidraClient_.result.types, watch);
break;
}
}
}
}
// if (ImGui::MenuItem("Go to in memory view")) {
// TODO imgui memory view not yet implemented
// }
ImGui::Separator();
ShowInWindowMenuItems(address, *control_);
ImGui::Separator();
// Memory breakpoints are only possible for sized types
if (length > 0) {
+24 -13
View File
@@ -5,6 +5,9 @@
#include "Common/GhidraClient.h"
#include "Core/MIPS/MIPSDebugInterface.h"
struct ImConfig;
struct ImControl;
// Struct viewer visualizes objects data in game memory using types and symbols fetched from a Ghidra project.
// It also allows to set memory breakpoints and edit field values which is helpful when reverse engineering unknown
// types.
@@ -24,7 +27,7 @@ class ImStructViewer {
std::string name;
};
struct NewWatch {
struct WatchForm {
char name[256] = {};
std::string typeDisplayName;
std::string typePathName;
@@ -32,13 +35,18 @@ class ImStructViewer {
bool dynamic = false;
std::string error;
ImGuiTextFilter typeFilter;
void Clear();
void SetFrom(const std::unordered_map<std::string, GhidraType> &types, const Watch &watch);
};
public:
void Draw(MIPSDebugInterface* mipsDebug, bool* open);
void Draw(ImConfig &cfg, ImControl &control, MIPSDebugInterface *mipsDebug);
private:
MIPSDebugInterface* mipsDebug_ = nullptr;
ImControl *control_ = nullptr;
MIPSDebugInterface *mipsDebug_ = nullptr;
ImGuiTextFilter globalFilter_;
ImGuiTextFilter watchFilter_;
@@ -51,8 +59,9 @@ private:
std::vector<Watch> watches_;
int nextWatchId_ = 0; // ID value to use when creating new watch entry
int removeWatchId_ = -1; // Watch entry id to be removed on next draw
int editWatchId_ = -1; // Watch entry id currently being edited
Watch addWatch_; // Temporary variable to store watch entry added from the Globals tab
NewWatch newWatch_; // State for the new watch entry UI
WatchForm watchForm_; // State for the new and edit watch form UI
void DrawConnectionSetup();
@@ -62,22 +71,24 @@ private:
void DrawWatch();
void DrawNewWatchEntry();
void DrawWatchForm();
void ClearWatchForm();
void DrawType(
u32 base,
u32 offset,
const std::string& typePathName,
const char* typeDisplayNameOverride,
const char* name,
const std::string &typePathName,
const char *typeDisplayNameOverride,
const char *name,
int watchId,
ImGuiTreeNodeFlags extraTreeNodeFlags = 0);
void DrawIndexedMembers(
u32 base,
u32 offset,
const std::string& typePathName,
const char* name,
const std::string &typePathName,
const char *name,
u32 elementCount,
int elementLength,
bool openFirst);
@@ -86,8 +97,8 @@ private:
u32 base,
u32 offset,
int length,
const std::string& typePathName,
const char* name,
const std::string &typePathName,
const char *name,
int watchId,
const u64* value);
const u64 *value);
};