mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
Add watch edit and show in window menu
This commit is contained in:
parent
032b8bfae3
commit
6cc58b19ce
3 changed files with 117 additions and 47 deletions
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -273,6 +301,9 @@ void ImStructViewer::DrawStructViewer() {
|
|||
if (removeWatchId_ != -1) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -315,7 +346,7 @@ void ImStructViewer::DrawGlobals() {
|
|||
}
|
||||
|
||||
void ImStructViewer::DrawWatch() {
|
||||
DrawNewWatchEntry();
|
||||
DrawWatchForm();
|
||||
ImGui::Dummy(ImVec2(1, 6));
|
||||
|
||||
watchFilter_.Draw();
|
||||
|
@ -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();
|
||||
watchForm_.typeFilter.Draw();
|
||||
for (const auto& entry : ghidraClient_.result.types) {
|
||||
const auto& type = entry.second;
|
||||
if (newWatch_.typeFilter.PassFilter(type.displayName.c_str())) {
|
||||
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,42 +420,62 @@ 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,
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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,12 +35,17 @@ 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:
|
||||
ImControl* control_ = nullptr;
|
||||
MIPSDebugInterface* mipsDebug_ = nullptr;
|
||||
|
||||
ImGuiTextFilter globalFilter_;
|
||||
|
@ -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,7 +71,9 @@ private:
|
|||
|
||||
void DrawWatch();
|
||||
|
||||
void DrawNewWatchEntry();
|
||||
void DrawWatchForm();
|
||||
|
||||
void ClearWatchForm();
|
||||
|
||||
void DrawType(
|
||||
u32 base,
|
||||
|
|
Loading…
Add table
Reference in a new issue