mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-08-31 09:45:24 +02:00
The block list in the memory windows was navigation-only - click to jump there, and that was it. Right-clicking a block now offers the two things you actually want once you've found one: - Copy info to clipboard. More than the status bar shows (range, size, the PC that allocated it, ticks, flags, allocated state), since the reason to copy it is to keep it - for a bug report or to compare two runs. - Add memory breakpoint, covering the whole block rather than a single address. That's the point of doing it from this list: you want to catch anything touching the allocation, not one byte of it. Read and write, pause and log. It then opens and focuses the Breakpoints window, via a new SHOW_IN_BREAKPOINTS command so window activation stays with the other ImCmds rather than poking at the config from here. Only on the blocks themselves, not on the synthetic "(start)" and "(end)" entries, which have no block info behind them to copy or watch. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GZq8ZtJmFY7bkX5FVkr3P9
1227 lines
35 KiB
C++
1227 lines
35 KiB
C++
#include <cctype>
|
|
#include <cmath>
|
|
#include <iomanip>
|
|
#include <cstdio>
|
|
#include <sstream>
|
|
|
|
#include "ext/imgui/imgui.h"
|
|
#include "ext/imgui/imgui_internal.h"
|
|
#include "ext/imgui/imgui_impl_thin3d.h"
|
|
|
|
#include "ext/xxhash.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/File/FileUtil.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/MemMap.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/RetroAchievements.h"
|
|
#include "Common/System/Display.h"
|
|
#include "Common/System/System.h"
|
|
#include "Core/Debugger/Breakpoints.h"
|
|
|
|
#include "Core/System.h"
|
|
#include "UI/ImDebugger/ImDebugger.h"
|
|
#include "UI/ImDebugger/ImMemView.h"
|
|
|
|
static const char* searchtypes[] = {"u8", "u16", "u32", "u64", "float", "string", "string16", "bytesequence"};
|
|
|
|
ImMemView::ImMemView() {
|
|
windowStart_ = curAddress_;
|
|
selectRangeStart_ = curAddress_;
|
|
selectRangeEnd_ = curAddress_ + 1;
|
|
lastSelectReset_ = curAddress_;
|
|
}
|
|
|
|
ImMemView::~ImMemView() {}
|
|
|
|
static uint32_t pickTagColor(std::string_view tag) {
|
|
uint32_t colors[6] = { 0xFF301010, 0xFF103030, 0xFF403010, 0xFF103000, 0xFF301030, 0xFF101030 };
|
|
int which = XXH3_64bits(tag.data(), tag.length()) % ARRAY_SIZE(colors);
|
|
return colors[which];
|
|
}
|
|
|
|
void ImMemView::Draw(ImDrawList *drawList) {
|
|
if (!debugger_->isAlive()) {
|
|
return;
|
|
}
|
|
|
|
ImGui_PushFixedFont();
|
|
|
|
rowHeight_ = ImGui::GetTextLineHeightWithSpacing();
|
|
charWidth_ = ImGui::CalcTextSize("W", nullptr, false, -1.0f).x;
|
|
charHeight_ = ImGui::GetTextLineHeight();
|
|
|
|
offsetPositionY_ = offsetLine * rowHeight_;
|
|
|
|
addressStartX_ = charWidth_;
|
|
hexStartX_ = addressStartX_ + 9 * charWidth_;
|
|
asciiStartX_ = hexStartX_ + (rowSize_ * 3 + 1) * charWidth_;
|
|
|
|
const ImVec2 canvas_p0 = ImGui::GetCursorScreenPos(); // ImDrawList API uses screen coordinates!
|
|
const ImVec2 canvas_sz = ImGui::GetContentRegionAvail(); // Resize canvas to what's available
|
|
const ImVec2 canvas_p1 = ImVec2(canvas_p0.x + canvas_sz.x, canvas_p0.y + canvas_sz.y);
|
|
|
|
// This will catch our interactions
|
|
bool pressed = ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
|
|
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
|
|
const bool is_active = ImGui::IsItemActive(); // Held
|
|
|
|
if (pressed) {
|
|
// INFO_LOG(Log::System, "Pressed");
|
|
}
|
|
ImGui::SetItemKeyOwner(ImGuiKey_MouseWheelY);
|
|
|
|
visibleRows_ = canvas_sz.y / rowHeight_;
|
|
|
|
if (displayOffsetScale_) {
|
|
// visibleRows_ is calculated based on the size of the control, but X rows have already been used for the offsets and are no longer usable
|
|
visibleRows_ -= offsetSpace;
|
|
}
|
|
|
|
Bounds bounds;
|
|
bounds.x = canvas_p0.x;
|
|
bounds.y = canvas_p0.y;
|
|
bounds.w = canvas_p1.x - canvas_p0.x;
|
|
bounds.h = canvas_p1.y - canvas_p0.y;
|
|
|
|
drawList->PushClipRect(canvas_p0, canvas_p1, true);
|
|
drawList->AddRectFilled(canvas_p0, canvas_p1, IM_COL32(25, 25, 25, 255));
|
|
|
|
if (displayOffsetScale_)
|
|
drawOffsetScale(drawList);
|
|
|
|
std::vector<MemBlockInfo> memRangeInfo = FindMemInfoByFlag(highlightFlags_, windowStart_, (visibleRows_ + 1) * rowSize_);
|
|
|
|
_assert_msg_(((windowStart_ | rowSize_) & 3) == 0, "readMemory() can't handle unaligned reads");
|
|
|
|
// draw one extra row that may be partially visible
|
|
for (int i = 0; i < visibleRows_ + 1; i++) {
|
|
int rowY = rowHeight_ * i;
|
|
// Skip the first X rows to make space for the offsets.
|
|
if (displayOffsetScale_)
|
|
rowY += rowHeight_ * offsetSpace;
|
|
|
|
char temp[32];
|
|
uint32_t address = windowStart_ + i * rowSize_;
|
|
snprintf(temp, sizeof(temp), "%08x", address);
|
|
drawList->AddText(ImVec2(canvas_p0.x + addressStartX_, canvas_p0.y + rowY), IM_COL32(0xE0, 0xE0, 0xE0, 0xFF), temp);
|
|
|
|
union {
|
|
uint32_t words[4];
|
|
uint8_t bytes[16];
|
|
} memory;
|
|
int valid = debugger_ != nullptr && debugger_->isAlive() ? Memory::ClampValidSizeAt(address, 16) / 4 : 0;
|
|
for (int i = 0; i < valid; ++i) {
|
|
memory.words[i] = debugger_->readMemory(address + i * 4);
|
|
}
|
|
|
|
for (int j = 0; j < rowSize_; j++) {
|
|
const uint32_t byteAddress = (address + j) & ~0xC0000000;
|
|
std::string_view tag;
|
|
bool tagContinues = false;
|
|
for (const auto &info : memRangeInfo) {
|
|
if (info.start <= byteAddress && info.start + info.size > byteAddress) {
|
|
tag = info.tag; // doesn't take ownership!
|
|
tagContinues = byteAddress + 1 < info.start + info.size;
|
|
}
|
|
}
|
|
|
|
int hexX = hexStartX_ + j * 3 * charWidth_;
|
|
int hexLen = 2;
|
|
int asciiX = asciiStartX_ + j * (charWidth_ + 2);
|
|
|
|
char c;
|
|
if (valid) {
|
|
snprintf(temp, sizeof(temp), "%02x ", memory.bytes[j]);
|
|
c = (char)memory.bytes[j];
|
|
if (memory.bytes[j] < 32 || memory.bytes[j] >= 128)
|
|
c = '.';
|
|
} else {
|
|
truncate_cpy(temp, "??");
|
|
c = '.';
|
|
}
|
|
|
|
ImColor standardBG = 0xFF000000;
|
|
bool continueRect = false;
|
|
|
|
ImColor hexBGCol = 0;
|
|
ImColor hexTextCol = 0xFFFFFFFF;
|
|
ImColor asciiBGCol = 0;
|
|
ImColor asciiTextCol = 0xFFFFFFFF;
|
|
int underline = -1;
|
|
|
|
const ImColor primarySelFg = 0xFFFFFFFF;
|
|
const ImColor primarySelBg = 0xFFFF9933;
|
|
const ImColor secondarySelFg = 0xFFFFFFFF;
|
|
const ImColor secondarySelBg = 0xFF808080;
|
|
|
|
const ImColor zeroColor = 0x888888ff;
|
|
const ImColor searchResBg = 0xFF108010;
|
|
|
|
if (address + j >= selectRangeStart_ && address + j < selectRangeEnd_) {
|
|
if (asciiSelected_) {
|
|
hexBGCol = secondarySelBg;
|
|
hexTextCol = secondarySelFg;
|
|
asciiBGCol = primarySelBg;
|
|
asciiTextCol = primarySelFg;
|
|
} else {
|
|
hexBGCol = primarySelBg;
|
|
hexTextCol = primarySelFg;
|
|
asciiBGCol = secondarySelBg;
|
|
asciiTextCol = secondarySelFg;
|
|
if (address + j == curAddress_)
|
|
underline = selectedNibble_;
|
|
}
|
|
if (!tag.empty() && tagContinues) {
|
|
continueRect = true;
|
|
}
|
|
} else if (!tag.empty()) {
|
|
if (memSearch_.status == SEARCH_OK && address+j >= memSearch_.matchAddress && address +j < memSearch_.matchAddress + memSearch_.data.size()) {
|
|
hexBGCol = searchResBg;
|
|
} else {
|
|
hexBGCol = pickTagColor(tag);
|
|
}
|
|
continueRect = tagContinues;
|
|
asciiBGCol = hexBGCol;
|
|
}
|
|
|
|
ImColor fg = hexTextCol;
|
|
ImColor bg = hexBGCol;
|
|
// SelectObject(hdc, underline == 0 ? (HGDIOBJ)underlineFont : (HGDIOBJ)font);
|
|
if (bg != 0) {
|
|
int bgWidth = 2; // continueRect ? 3 : 2;
|
|
drawList->AddRectFilled(ImVec2(canvas_p0.x + hexX - 1, canvas_p0.y + rowY), ImVec2(canvas_p0.x + hexX + charWidth_ * bgWidth, canvas_p0.y + rowY + charHeight_), bg);
|
|
}
|
|
if (drawZeroDark_ && temp[0] == '0' && temp[1] == '0') {
|
|
// if the byte is all zero make it darker
|
|
fg = zeroColor;
|
|
}
|
|
drawList->AddText(ImVec2(canvas_p0.x + hexX, canvas_p0.y + rowY), fg, &temp[0], &temp[2]);
|
|
if (underline >= 0) {
|
|
float x = canvas_p0.x + hexX + underline * charWidth_;
|
|
drawList->AddRectFilled(ImVec2(x, canvas_p0.y + rowY + charHeight_ - 2), ImVec2(x + charWidth_, canvas_p0.y + rowY + charHeight_), IM_COL32(0xFF, 0xFF, 0xFF, 0xFF));
|
|
}
|
|
|
|
fg = asciiTextCol;
|
|
bg = asciiBGCol;
|
|
if (bg) {
|
|
drawList->AddRectFilled(ImVec2(canvas_p0.x + asciiX, canvas_p0.y + rowY), ImVec2(canvas_p0.x + asciiX + charWidth_, canvas_p0.y + rowY + charHeight_), bg);
|
|
}
|
|
drawList->AddText(ImVec2(canvas_p0.x + asciiX, canvas_p0.y + rowY), fg, &c, &c + 1);
|
|
}
|
|
}
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
ImVec2 mousePos = ImVec2(io.MousePos.x - canvas_p0.x, io.MousePos.y - canvas_p0.y);
|
|
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
|
// INFO_LOG(Log::System, "Mousedown %f,%f active:%d hover:%d", mousePos.x, mousePos.y, is_active, is_hovered);
|
|
onMouseDown(mousePos.x, mousePos.y, 1);
|
|
}
|
|
if (is_hovered && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) {
|
|
// INFO_LOG(Log::CPU, "Mousedown %f,%f active:%d hover:%d", mousePos.x, mousePos.y, is_active, is_hovered);
|
|
onMouseDown(mousePos.x, mousePos.y, 2);
|
|
}
|
|
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
|
// INFO_LOG(Log::System, "Mouseup %f,%f active:%d hover:%d", mousePos.x, mousePos.y, is_active, is_hovered);
|
|
if (is_hovered) {
|
|
onMouseUp(mousePos.x, mousePos.y, 1);
|
|
}
|
|
}
|
|
if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) {
|
|
// INFO_LOG(Log::System, "Mousedrag %f,%f active:%d hover:%d", mousePos.x, mousePos.y, is_active, is_hovered);
|
|
if (is_hovered) {
|
|
onMouseMove(mousePos.x, mousePos.y, 1);
|
|
}
|
|
}
|
|
|
|
if (is_hovered) {
|
|
if (io.MouseWheel > 0.0f) { // TODO: Scale steps by the value.
|
|
windowStart_ -= rowSize_ * 4;
|
|
} else if (io.MouseWheel < 0.0f) {
|
|
windowStart_ += rowSize_ * 4;
|
|
}
|
|
}
|
|
|
|
ProcessKeyboardShortcuts(ImGui::IsItemFocused());
|
|
|
|
ImGui_PopFont();
|
|
|
|
ImGui::OpenPopupOnItemClick("memcontext", ImGuiPopupFlags_MouseButtonRight);
|
|
PopupMenu();
|
|
|
|
drawList->PopClipRect();
|
|
}
|
|
|
|
// We don't have a scroll bar - though maybe we should build one.
|
|
/*
|
|
void ImMemView::onVScroll(WPARAM wParam, LPARAM lParam) {
|
|
switch (wParam & 0xFFFF) {
|
|
case SB_LINEDOWN:
|
|
ScrollWindow(1, GotoModeFromModifiers(false));
|
|
break;
|
|
case SB_LINEUP:
|
|
ScrollWindow(-1, GotoModeFromModifiers(false));
|
|
break;
|
|
case SB_PAGEDOWN:
|
|
ScrollWindow(visibleRows_, GotoModeFromModifiers(false));
|
|
break;
|
|
case SB_PAGEUP:
|
|
ScrollWindow(-visibleRows_, GotoModeFromModifiers(false));
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
}
|
|
*/
|
|
|
|
void ImMemView::ProcessKeyboardShortcuts(bool focused) {
|
|
if (!focused) {
|
|
return;
|
|
}
|
|
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
if (io.KeyMods & ImGuiMod_Ctrl) {
|
|
if (ImGui::IsKeyPressed(ImGuiKey_G)) {
|
|
/*
|
|
u32 addr;
|
|
if (executeExpressionWindow(wnd, debugger_, addr) == false)
|
|
return;
|
|
gotoAddr(addr);
|
|
return;
|
|
*/
|
|
}
|
|
|
|
if (ImGui::IsKeyPressed(ImGuiKey_P)) {
|
|
PasteFromByteClipboard();
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_C)) {
|
|
CopyToByteClipboard();
|
|
return;
|
|
}
|
|
} else {
|
|
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
|
|
ScrollCursor(rowSize_, GotoModeFromModifiers(false));
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow)) {
|
|
ScrollCursor(-rowSize_, GotoModeFromModifiers(false));
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow)) {
|
|
ScrollCursor(-1, GotoModeFromModifiers(false));
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_RightArrow)) {
|
|
ScrollCursor(1, GotoModeFromModifiers(false));
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_PageDown)) {
|
|
ScrollWindow(visibleRows_, GotoModeFromModifiers(false));
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_PageUp)) {
|
|
ScrollWindow(-visibleRows_, GotoModeFromModifiers(false));
|
|
return;
|
|
}
|
|
|
|
if (editableMemory_) {
|
|
if (!asciiSelected_) {
|
|
for (int i = ImGuiKey_0; i != ImGuiKey_G; i++) {
|
|
if (ImGui::IsKeyPressed(static_cast<ImGuiKey>(i))) {
|
|
EditMemory(i - ImGuiKey_0);
|
|
return;
|
|
}
|
|
}
|
|
for (int i = ImGuiKey_Keypad0; i != ImGuiKey_KeypadDecimal; i++) {
|
|
if (ImGui::IsKeyPressed(static_cast<ImGuiKey>(i))) {
|
|
EditMemory(i - ImGuiKey_Keypad0);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImMemView::EditMemory(int newNibble) {
|
|
uint8_t og = Memory::ReadUnchecked_U8(curAddress_);
|
|
u8 out;
|
|
if (selectedNibble_) {
|
|
out = (og & 0xF0) | newNibble;
|
|
} else {
|
|
out = (og & 0x0F) | (newNibble << 4);
|
|
}
|
|
Memory::WriteUnchecked_U8((u8)out, curAddress_);
|
|
ScrollCursor(1, GotoMode::RESET);
|
|
}
|
|
|
|
void ImMemView::CopyToByteClipboard() {
|
|
byteClipboard_.clear();
|
|
uint32_t size = selectRangeEnd_ - selectRangeStart_;
|
|
byteClipboard_.resize(size);
|
|
Memory::MemcpyUnchecked(byteClipboard_.data(), curAddress_, size);
|
|
}
|
|
|
|
void ImMemView::PasteFromByteClipboard() {
|
|
if (editableMemory_)
|
|
Memory::MemcpyUnchecked(curAddress_, byteClipboard_.data(), (u32)byteClipboard_.size());
|
|
}
|
|
|
|
void ImMemView::toggleEditableMemory(bool toggle) {
|
|
editableMemory_ = toggle;
|
|
}
|
|
|
|
|
|
void ImMemView::onChar(int c) {
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
if (io.KeyMods & ImGuiMod_Ctrl) {
|
|
return;
|
|
}
|
|
|
|
if (!Memory::IsValidAddress(curAddress_)) {
|
|
ScrollCursor(1, GotoMode::RESET);
|
|
return;
|
|
}
|
|
|
|
if (asciiSelected_) {
|
|
Memory::WriteUnchecked_U8((u8)c, curAddress_);
|
|
ScrollCursor(1, GotoMode::RESET);
|
|
} else {
|
|
c = tolower(c);
|
|
int inputValue = -1;
|
|
|
|
if (c >= '0' && c <= '9') inputValue = c - '0';
|
|
if (c >= 'a' && c <= 'f') inputValue = c - 'a' + 10;
|
|
|
|
if (inputValue >= 0) {
|
|
int shiftAmount = (1 - selectedNibble_) * 4;
|
|
|
|
u8 oldValue = Memory::ReadUnchecked_U8(curAddress_);
|
|
oldValue &= ~(0xF << shiftAmount);
|
|
u8 newValue = oldValue | (inputValue << shiftAmount);
|
|
Memory::WriteUnchecked_U8(newValue, curAddress_);
|
|
ScrollCursor(1, GotoMode::RESET);
|
|
}
|
|
}
|
|
|
|
Reporting::NotifyDebugger();
|
|
}
|
|
|
|
ImMemView::GotoMode ImMemView::GotoModeFromModifiers(bool isRightClick) {
|
|
GotoMode mode = GotoMode::RESET;
|
|
auto &io = ImGui::GetIO();
|
|
if (isRightClick) {
|
|
mode = GotoMode::RESET_IF_OUTSIDE;
|
|
} else if (io.KeyMods & ImGuiMod_Shift) {
|
|
if (io.KeyMods & ImGuiMod_Ctrl)
|
|
mode = GotoMode::EXTEND;
|
|
else
|
|
mode = GotoMode::FROM_CUR;
|
|
}
|
|
return mode;
|
|
}
|
|
|
|
void ImMemView::onMouseDown(float x, float y, int button) {
|
|
if (Achievements::HardcoreModeActive())
|
|
return;
|
|
|
|
GotoPoint(x, y, GotoModeFromModifiers(button == 2));
|
|
}
|
|
|
|
void ImMemView::PopupMenu() {
|
|
if (ImGui::BeginPopup("memcontext")) {
|
|
|
|
int32_t selectedSize = selectRangeEnd_ - selectRangeStart_;
|
|
bool enable16 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 1) == 0);
|
|
bool enable32 = !asciiSelected_ && (selectedSize == 1 || (selectedSize & 3) == 0);
|
|
/*
|
|
if (ImGui::MenuItem("Dump memory")) {
|
|
DumpMemoryWindow dump(wnd, debugger_);
|
|
dump.exec();
|
|
break;
|
|
}
|
|
*/
|
|
ImGui::SeparatorText("Internal clipboard");
|
|
if (ImGui::MenuItem("Copy bytes")) {
|
|
CopyToByteClipboard();
|
|
}
|
|
if (ImGui::MenuItem("Paste bytes")) {
|
|
PasteFromByteClipboard();
|
|
}
|
|
ImGui::SeparatorText("System clipboard");
|
|
if (ImGui::MenuItem("Copy value (8-bit)")) {
|
|
size_t tempSize = 3 * selectedSize + 1;
|
|
char *temp = new char[tempSize];
|
|
memset(temp, 0, tempSize);
|
|
|
|
// it's admittedly not really useful like this
|
|
if (asciiSelected_) {
|
|
for (uint32_t p = selectRangeStart_; p != selectRangeEnd_; ++p) {
|
|
uint8_t c = Memory::IsValidAddress(p) ? Memory::ReadUnchecked_U8(p) : '.';
|
|
if (c < 32 || c >= 128)
|
|
c = '.';
|
|
temp[p - selectRangeStart_] = c;
|
|
}
|
|
} else {
|
|
char *pos = temp;
|
|
for (uint32_t p = selectRangeStart_; p != selectRangeEnd_; ++p) {
|
|
uint8_t c = Memory::IsValidAddress(p) ? Memory::ReadUnchecked_U8(p) : 0xFF;
|
|
pos += snprintf(pos, tempSize - (pos - temp + 1), "%02X ", c);
|
|
}
|
|
// Clear the last space.
|
|
if (pos > temp)
|
|
*(pos - 1) = '\0';
|
|
}
|
|
|
|
System_CopyStringToClipboard(temp);
|
|
delete[] temp;
|
|
}
|
|
|
|
if (ImGui::MenuItem("Copy value (16-bit)")) {
|
|
size_t tempSize = 5 * ((selectedSize + 1) / 2) + 1;
|
|
char *temp = new char[tempSize];
|
|
memset(temp, 0, tempSize);
|
|
|
|
char *pos = temp;
|
|
for (uint32_t p = selectRangeStart_; p < selectRangeEnd_; p += 2) {
|
|
uint16_t c = Memory::IsValidRange(p, 2) ? Memory::ReadUnchecked_U16(p) : 0xFFFF;
|
|
pos += snprintf(pos, tempSize - (pos - temp + 1), "%04X ", c);
|
|
}
|
|
// Clear the last space.
|
|
if (pos > temp)
|
|
*(pos - 1) = '\0';
|
|
|
|
System_CopyStringToClipboard(temp);
|
|
delete[] temp;
|
|
}
|
|
|
|
if (ImGui::MenuItem("Copy value (32-bit)")) {
|
|
size_t tempSize = 9 * ((selectedSize + 3) / 4) + 1;
|
|
char *temp = new char[tempSize];
|
|
memset(temp, 0, tempSize);
|
|
|
|
char *pos = temp;
|
|
for (uint32_t p = selectRangeStart_; p < selectRangeEnd_; p += 4) {
|
|
uint32_t c = Memory::IsValidRange(p, 4) ? Memory::ReadUnchecked_U32(p) : 0xFFFFFFFF;
|
|
pos += snprintf(pos, tempSize - (pos - temp + 1), "%08X ", c);
|
|
}
|
|
// Clear the last space.
|
|
if (pos > temp)
|
|
*(pos - 1) = '\0';
|
|
|
|
System_CopyStringToClipboard(temp);
|
|
delete[] temp;
|
|
}
|
|
|
|
if (ImGui::MenuItem("Copy value (float32)")) {
|
|
char temp[64];
|
|
snprintf(temp, sizeof(temp), "%f", Memory::IsValid4AlignedAddress(curAddress_) ? Memory::ReadUnchecked_Float(curAddress_) : NAN);
|
|
System_CopyStringToClipboard(temp);
|
|
}
|
|
/*
|
|
case ID_MEMVIEW_EXTENTBEGIN:
|
|
{
|
|
std::vector<MemBlockInfo> memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1);
|
|
uint32_t addr = curAddress_;
|
|
for (MemBlockInfo info : memRangeInfo) {
|
|
addr = info.start;
|
|
}
|
|
gotoAddr(addr);
|
|
break;
|
|
}
|
|
|
|
case ID_MEMVIEW_EXTENTEND:
|
|
{
|
|
std::vector<MemBlockInfo> memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1);
|
|
uint32_t addr = curAddress_;
|
|
for (MemBlockInfo info : memRangeInfo) {
|
|
addr = info.start + info.size - 1;
|
|
}
|
|
gotoAddr(addr);
|
|
break;
|
|
}
|
|
*/
|
|
if (ImGui::MenuItem("Copy address")) {
|
|
char temp[24];
|
|
snprintf(temp, sizeof(temp), "0x%08X", curAddress_);
|
|
System_CopyStringToClipboard(temp);
|
|
}
|
|
ImGui::SeparatorText("Misc");
|
|
if (ImGui::MenuItem("Goto in disasm")) {
|
|
/*
|
|
if (disasmWindow) {
|
|
disasmWindow->Goto(curAddress_);
|
|
disasmWindow->Show(true);
|
|
}
|
|
*/
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
}
|
|
|
|
void ImMemView::onMouseUp(float x, float y, int button) {
|
|
if (button == 2) {
|
|
|
|
// HMENU menu = GetContextMenu(ContextMenuID::MEMVIEW);
|
|
// EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_16, enable16 ? MF_ENABLED : MF_GRAYED);
|
|
// EnableMenuItem(menu, ID_MEMVIEW_COPYVALUE_32, enable32 ? MF_ENABLED : MF_GRAYED);
|
|
// EnableMenuItem(menu, ID_MEMVIEW_COPYFLOAT_32, enable32 ? MF_ENABLED : MF_GRAYED);
|
|
|
|
}
|
|
|
|
/*
|
|
int x = LOWORD(lParam);
|
|
int y = HIWORD(lParam);
|
|
ReleaseCapture();
|
|
*/
|
|
GotoPoint(x, y, GotoModeFromModifiers(button == 2));
|
|
}
|
|
|
|
void ImMemView::onMouseMove(float x, float y, int button) {
|
|
if (Achievements::HardcoreModeActive())
|
|
return;
|
|
|
|
if (button & 1) {
|
|
GotoPoint(x, y, GotoModeFromModifiers(button == 2));
|
|
}
|
|
}
|
|
|
|
// Everything the block list knows about a block, in a form worth pasting into a bug report.
|
|
// Deliberately more than the status bar shows - the whole reason to copy it is to keep it.
|
|
static std::string DescribeMemBlockInfo(const MemBlockInfo &info) {
|
|
std::string flags;
|
|
auto addFlag = [&flags, &info](MemBlockFlags flag, const char *name) {
|
|
if ((uint32_t)(info.flags & flag) == 0)
|
|
return;
|
|
if (!flags.empty())
|
|
flags += "|";
|
|
flags += name;
|
|
};
|
|
addFlag(MemBlockFlags::ALLOC, "ALLOC");
|
|
addFlag(MemBlockFlags::FREE, "FREE");
|
|
addFlag(MemBlockFlags::SUB_ALLOC, "SUB_ALLOC");
|
|
addFlag(MemBlockFlags::SUB_FREE, "SUB_FREE");
|
|
addFlag(MemBlockFlags::WRITE, "WRITE");
|
|
addFlag(MemBlockFlags::READ, "READ");
|
|
addFlag(MemBlockFlags::TEXTURE, "TEXTURE");
|
|
if (flags.empty())
|
|
flags = "none";
|
|
|
|
return StringFromFormat(
|
|
"%s\n%08x-%08x (%d bytes)\nPC: %08x\nTicks: %lld\nFlags: %s\nAllocated: %s\n",
|
|
info.tag.c_str(), info.start, info.start + info.size, (int)info.size, info.pc,
|
|
(long long)info.ticks, flags.c_str(), info.allocated ? "yes" : "no");
|
|
}
|
|
|
|
void ImMemView::updateStatusBarText() {
|
|
std::vector<MemBlockInfo> memRangeInfo = FindMemInfoByFlag(highlightFlags_, curAddress_, 1);
|
|
char text[512];
|
|
snprintf(text, sizeof(text), "%08x", curAddress_);
|
|
// There should only be one.
|
|
for (MemBlockInfo info : memRangeInfo) {
|
|
snprintf(text, sizeof(text), "%08x - %s %08x-%08x (PC %08x / %lld ticks)", curAddress_, info.tag.c_str(), info.start, info.start + info.size, info.pc, (long long)info.ticks);
|
|
}
|
|
statusMessage_ = text;
|
|
}
|
|
|
|
void ImMemView::UpdateSelectRange(uint32_t target, GotoMode mode) {
|
|
if (mode == GotoMode::FROM_CUR && lastSelectReset_ == 0) {
|
|
lastSelectReset_ = curAddress_;
|
|
}
|
|
|
|
switch (mode) {
|
|
case GotoMode::RESET:
|
|
selectRangeStart_ = target;
|
|
selectRangeEnd_ = target + 1;
|
|
lastSelectReset_ = target;
|
|
break;
|
|
|
|
case GotoMode::RESET_IF_OUTSIDE:
|
|
if (target < selectRangeStart_ || target >= selectRangeEnd_) {
|
|
selectRangeStart_ = target;
|
|
selectRangeEnd_ = target + 1;
|
|
lastSelectReset_ = target;
|
|
}
|
|
break;
|
|
|
|
case GotoMode::FROM_CUR:
|
|
selectRangeStart_ = lastSelectReset_ > target ? target : lastSelectReset_;
|
|
selectRangeEnd_ = selectRangeStart_ == lastSelectReset_ ? target + 1 : lastSelectReset_ + 1;
|
|
break;
|
|
|
|
case GotoMode::EXTEND:
|
|
if (target < selectRangeStart_)
|
|
selectRangeStart_ = target;
|
|
if (target > selectRangeEnd_)
|
|
selectRangeEnd_ = target;
|
|
break;
|
|
}
|
|
curAddress_ = target;
|
|
}
|
|
|
|
void ImMemView::GotoPoint(int x, int y, GotoMode mode) {
|
|
int line = y / rowHeight_;
|
|
int lineAddress = windowStart_ + line * rowSize_;
|
|
|
|
if (displayOffsetScale_) {
|
|
// ignore clicks on the offset space
|
|
if (line < offsetSpace) {
|
|
updateStatusBarText();
|
|
return;
|
|
}
|
|
// since each row has been written X rows down from where the window expected it to be written the target of the clicks must be adjusted
|
|
lineAddress -= rowSize_ * offsetSpace;
|
|
}
|
|
|
|
uint32_t target = curAddress_;
|
|
uint32_t targetNibble = selectedNibble_;
|
|
bool targetAscii = asciiSelected_;
|
|
if (x >= asciiStartX_) {
|
|
int col = (x - asciiStartX_) / (charWidth_ + 2);
|
|
if (col >= rowSize_)
|
|
return;
|
|
|
|
targetAscii = true;
|
|
target = lineAddress + col;
|
|
targetNibble = 0;
|
|
} else if (x >= hexStartX_) {
|
|
int col = (x - hexStartX_) / charWidth_;
|
|
if ((col / 3) >= rowSize_)
|
|
return;
|
|
|
|
switch (col % 3) {
|
|
case 0: targetNibble = 0; break;
|
|
case 1: targetNibble = 1; break;
|
|
// case 2: return; // don't change position when clicking on the space
|
|
case 2: targetNibble = 0; break; // TODO: split the difference? Anyway, this feels better.
|
|
}
|
|
|
|
targetAscii = false;
|
|
target = lineAddress + col / 3;
|
|
}
|
|
|
|
if (target != curAddress_ || targetNibble != selectedNibble_ || targetAscii != asciiSelected_) {
|
|
selectedNibble_ = targetNibble;
|
|
asciiSelected_ = targetAscii;
|
|
UpdateSelectRange(target, mode);
|
|
updateStatusBarText();
|
|
}
|
|
}
|
|
|
|
void ImMemView::gotoAddr(unsigned int addr) {
|
|
int lines = visibleRows_;
|
|
u32 windowEnd = windowStart_ + lines * rowSize_;
|
|
|
|
curAddress_ = addr;
|
|
lastSelectReset_ = curAddress_;
|
|
selectRangeStart_ = curAddress_;
|
|
selectRangeEnd_ = curAddress_ + 1;
|
|
selectedNibble_ = 0;
|
|
|
|
if (curAddress_ < windowStart_ || curAddress_ >= windowEnd) {
|
|
windowStart_ = curAddress_ & ~15;
|
|
}
|
|
|
|
updateStatusBarText();
|
|
}
|
|
|
|
void ImMemView::ScrollWindow(int lines, GotoMode mode) {
|
|
windowStart_ += lines * rowSize_;
|
|
|
|
UpdateSelectRange(curAddress_ + lines * rowSize_, mode);
|
|
|
|
updateStatusBarText();
|
|
}
|
|
|
|
void ImMemView::ScrollCursor(int bytes, GotoMode mode) {
|
|
if (!asciiSelected_ && bytes == 1) {
|
|
if (selectedNibble_ == 0) {
|
|
selectedNibble_ = 1;
|
|
bytes = 0;
|
|
} else {
|
|
selectedNibble_ = 0;
|
|
}
|
|
} else if (!asciiSelected_ && bytes == -1) {
|
|
if (selectedNibble_ == 0) {
|
|
selectedNibble_ = 1;
|
|
} else {
|
|
selectedNibble_ = 0;
|
|
bytes = 0;
|
|
}
|
|
}
|
|
|
|
UpdateSelectRange(curAddress_ + bytes, mode);
|
|
|
|
u32 windowEnd = windowStart_ + visibleRows_ * rowSize_;
|
|
if (curAddress_ < windowStart_) {
|
|
windowStart_ = curAddress_ & ~15;
|
|
} else if (curAddress_ >= windowEnd) {
|
|
windowStart_ = (curAddress_ - (visibleRows_ - 1) * rowSize_) & ~15;
|
|
}
|
|
|
|
updateStatusBarText();
|
|
}
|
|
|
|
|
|
bool ImMemView::ParseSearchString(const char* query, MemorySearchType mode) {
|
|
memSearch_.data.clear();
|
|
switch (mode) {
|
|
case FLOAT_32:{
|
|
float flt = std::strtof(query, nullptr);
|
|
uint32_t* tmp = (uint32_t*) &flt;
|
|
for (int i = 0; i < 4; i++) {
|
|
memSearch_.data.push_back((uint8_t)(*tmp & 0xff));
|
|
*tmp >>= 8;
|
|
}
|
|
}
|
|
break;
|
|
case BITS_8:
|
|
case BITS_16:
|
|
case BITS_32:
|
|
case BITS_64:{
|
|
long long ll = std::strtoll(query, nullptr, 0);
|
|
int bytes = 1 << mode;
|
|
for (int i = 0; i < bytes; i++) {
|
|
memSearch_.data.push_back((uint8_t)(ll & 0xff));
|
|
ll >>= 8;
|
|
}
|
|
}
|
|
break;
|
|
case STRING:{
|
|
while(*query != 0) {
|
|
memSearch_.data.push_back(*query);
|
|
query++;
|
|
}
|
|
}
|
|
break;
|
|
case STRING_16:{
|
|
// for now limited to ascii.
|
|
while(*query != 0) {
|
|
char c = *query;
|
|
if (c > 0x1f && c < 0x7f) {
|
|
memSearch_.data.push_back(*query);
|
|
memSearch_.data.push_back(0);
|
|
}
|
|
query++;
|
|
}
|
|
}break;
|
|
case BYTE_SEQ:{
|
|
char* s = strdup(query);
|
|
size_t len = strlen(s);
|
|
for (size_t index = 0; index < len; ) {
|
|
if (isspace(s[index])) {
|
|
index++;
|
|
continue;
|
|
}
|
|
u8 value = 0;
|
|
for (int i = 0; i < 2 && index < len; i++) {
|
|
char c = tolower(s[index++]);
|
|
if (c >= 'a' && c <= 'f') {
|
|
value |= (c - 'a' + 10) << (1 - i) * 4;
|
|
} else if (c >= '0' && c <= '9') {
|
|
value |= (c - '0') << (1 - i) * 4;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
memSearch_.data.push_back(value);
|
|
}
|
|
free(s);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
/*
|
|
std::vector<u32> ImMemView::searchString(const std::string &searchQuery) {
|
|
std::vector<u32> searchResAddrs;
|
|
|
|
std::vector<u8> searchData;
|
|
if (!ParseSearchString(searchQuery, false, searchData))
|
|
return searchResAddrs;
|
|
|
|
if (searchData.empty())
|
|
return searchResAddrs;
|
|
|
|
std::vector<std::pair<u32, u32>> memoryAreas;
|
|
memoryAreas.emplace_back(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd());
|
|
// Ignore the video memory mirrors.
|
|
memoryAreas.emplace_back(PSP_GetVidMemBase(), 0x04200000);
|
|
memoryAreas.emplace_back(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd());
|
|
|
|
for (const auto &area : memoryAreas) {
|
|
const u32 segmentStart = area.first;
|
|
const u32 segmentEnd = area.second - (u32)searchData.size();
|
|
|
|
for (u32 pos = segmentStart; pos < segmentEnd; pos++) {
|
|
if ((pos % 256) == 0 && ImGui::IsKeyDown(ImGuiKey_Escape)) {
|
|
return searchResAddrs;
|
|
}
|
|
|
|
const u8 *ptr = Memory::GetPointerUnchecked(pos);
|
|
if (memcmp(ptr, searchData.data(), searchData.size()) == 0) {
|
|
searchResAddrs.push_back(pos);
|
|
}
|
|
}
|
|
}
|
|
|
|
return searchResAddrs;
|
|
};
|
|
*/
|
|
MemorySearchStatus ImMemView::search(bool continueSearch) {
|
|
if (!PSP_IsInited())
|
|
return SEARCH_PSP_NOT_INIT;
|
|
if (continueSearch == false /*|| searchQuery_.empty()*/) {
|
|
memSearch_.searchAddress = curAddress_ + 1;
|
|
} else {
|
|
memSearch_.searchAddress = memSearch_.matchAddress + 1;
|
|
}
|
|
|
|
std::vector<std::pair<u32, u32>> memoryAreas;
|
|
// Ignore the video memory mirrors.
|
|
memoryAreas.emplace_back(PSP_GetVidMemBase(), 0x04200000);
|
|
memoryAreas.emplace_back(PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd());
|
|
memoryAreas.emplace_back(PSP_GetScratchpadMemoryBase(), PSP_GetScratchpadMemoryEnd());
|
|
|
|
// NOTE:
|
|
// this currently stops at the first found value
|
|
// we could look for all matches
|
|
// and put them in a matches list
|
|
// and display as;
|
|
// <match idx><memory region><addr><what matched>
|
|
const u8 *data = memSearch_.data.data();
|
|
const size_t size = memSearch_.data.size();
|
|
|
|
for (size_t i = 0; i < memoryAreas.size(); i++) {
|
|
memSearch_.segmentStart = memoryAreas[i].first;
|
|
memSearch_.segmentEnd = memoryAreas[i].second;
|
|
|
|
// better safe than sorry, I guess
|
|
if (!Memory::IsValidAddress(memSearch_.segmentStart))
|
|
continue;
|
|
const u8 *dataPointer = Memory::GetPointerUnchecked(memSearch_.segmentStart);
|
|
|
|
if (memSearch_.searchAddress < memSearch_.segmentStart)
|
|
memSearch_.searchAddress = memSearch_.segmentStart;
|
|
if (memSearch_.searchAddress >= memSearch_.segmentEnd)
|
|
continue;
|
|
|
|
int index = memSearch_.searchAddress - memSearch_.segmentStart;
|
|
int endIndex = memSearch_.segmentEnd - memSearch_.segmentStart - (int)size;
|
|
while (index < endIndex) {
|
|
if (memcmp(&dataPointer[index], data, size) == 0) {
|
|
memSearch_.matchAddress = index + memSearch_.segmentStart;
|
|
gotoAddr(memSearch_.matchAddress);
|
|
return SEARCH_OK;
|
|
}
|
|
index++;
|
|
}
|
|
}
|
|
|
|
statusMessage_ = "Not found";
|
|
return SEARCH_NOTFOUND;
|
|
}
|
|
|
|
// for now type as int
|
|
void ImMemView::initSearch(const char* str, MemorySearchType type) {
|
|
memSearch_.status = SEARCH_INITIAL;
|
|
ParseSearchString(str, type);
|
|
memSearch_.status = search(false);
|
|
}
|
|
void ImMemView::continueSearch() {
|
|
memSearch_.status = SEARCH_INITIAL;
|
|
memSearch_.status = search(true);
|
|
|
|
}
|
|
void ImMemView::drawOffsetScale(ImDrawList *drawList) {
|
|
int currentX = addressStartX_;
|
|
|
|
drawList->AddText(ImVec2(currentX, offsetPositionY_), 0xFF600000, "Offset");
|
|
// SetTextColor(hdc, 0x600000);
|
|
// TextOutA(hdc, currentX, offsetPositionY_, "Offset", 6);
|
|
|
|
// the start offset, the size of the hex addresses and one space
|
|
currentX = addressStartX_ + ((8 + 1) * charWidth_);
|
|
|
|
char temp[64];
|
|
for (int i = 0; i < 16; i++) {
|
|
snprintf(temp, sizeof(temp), "%02X", i);
|
|
drawList->AddText(ImVec2(currentX, offsetPositionY_), 0xFF600000, temp);
|
|
currentX += 3 * charWidth_; // hex and space
|
|
}
|
|
}
|
|
|
|
void ImMemView::toggleOffsetScale(CommonToggles toggle) {
|
|
if (toggle == On)
|
|
displayOffsetScale_ = true;
|
|
else if (toggle == Off)
|
|
displayOffsetScale_ = false;
|
|
|
|
updateStatusBarText();
|
|
}
|
|
|
|
void ImMemView::setHighlightType(MemBlockFlags flags) {
|
|
if (highlightFlags_ != flags) {
|
|
highlightFlags_ = flags;
|
|
updateStatusBarText();
|
|
}
|
|
}
|
|
|
|
void ImMemView::toggleDrawZeroDark(bool toggle) {
|
|
drawZeroDark_ = toggle;
|
|
}
|
|
|
|
MemorySearchStatus ImMemView::SearchStatus() {
|
|
return memSearch_.status;
|
|
}
|
|
uint32_t ImMemView::SearchMatchAddress() {
|
|
return memSearch_.matchAddress;
|
|
}
|
|
bool ImMemView::SearchEmpty() {
|
|
return memSearch_.data.empty();
|
|
}
|
|
|
|
void ImMemDumpWindow::Draw(ImConfig &cfg, MIPSDebugInterface *debug) {
|
|
ImGui::SetNextWindowSize(ImVec2(200, 300), ImGuiCond_FirstUseEver);
|
|
|
|
if (!ImGui::Begin(Title(), &cfg.memDumpOpen)) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
if (ImGui::Button("User RAM (0x08800000)")) {
|
|
address_ = 0x08800000;
|
|
size_ = 0x01800000; // 24MB
|
|
}
|
|
|
|
ImGui::InputScalar("Starting address", ImGuiDataType_U32, &address_, NULL, NULL, "%08X");
|
|
ImGui::InputScalar("Size", ImGuiDataType_U32, &size_, NULL, NULL, "%08X");
|
|
|
|
ImGui::InputText("Filename", filename_, ARRAY_SIZE(filename_));
|
|
|
|
const char* modes[] = { "Raw", "Disassembly" };
|
|
int modeIndex = static_cast<int>(mode_);
|
|
if (ImGui::Combo("Memory Dump Mode", &modeIndex, modes, IM_ARRAYSIZE(modes))) {
|
|
// Update the current mode if the user selects a new one
|
|
mode_ = static_cast<MemDumpMode>(modeIndex);
|
|
}
|
|
|
|
if (ImGui::Button(mode_ == MemDumpMode::Raw ? "Dump to file" : "Disassemble to file")) {
|
|
uint32_t validSize = Memory::ClampValidSizeAt(address_, size_);
|
|
if (validSize != size_) {
|
|
errorMsg_ = "Address range out of bounds";
|
|
if (Memory::IsValidAddress(address_)) {
|
|
size_ = validSize;
|
|
}
|
|
} else if (strlen(filename_) == 0) {
|
|
errorMsg_ = "Please specify a valid filename";
|
|
} else {
|
|
FILE *file = File::OpenCFile(Path(filename_), "wb");
|
|
if (!file) {
|
|
errorMsg_ = "Couldn't open file for writing";
|
|
} else {
|
|
if (mode_ == MemDumpMode::Raw) {
|
|
const uint8_t *ptr = Memory::GetPointerUnchecked(address_);
|
|
fwrite(ptr, 1, size_, file);
|
|
} else {
|
|
std::string disassembly = DisassembleRange(address_, size_, true, debug);
|
|
fprintf(file, "%s", disassembly.c_str());
|
|
}
|
|
errorMsg_.clear();
|
|
fclose(file);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!errorMsg_.empty()) {
|
|
ImGui::TextUnformatted(errorMsg_.data(), errorMsg_.data() + errorMsg_.size());
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
#define OPEN_SEARCH_FORM() \
|
|
if (!(searchFormFlags_ & ImGuiTreeNodeFlags_DefaultOpen)) \
|
|
searchFormFlags_ ^= ImGuiTreeNodeFlags_DefaultOpen; \
|
|
focusSearchValueInput_ = true
|
|
|
|
void ImMemWindow::ProcessKeyboardShortcuts() {
|
|
if (ImGui::IsKeyPressed(ImGuiKey_F)) {
|
|
OPEN_SEARCH_FORM();
|
|
selectedSearchType_ = STRING;
|
|
return;
|
|
}
|
|
|
|
if (ImGui::IsKeyPressed(ImGuiKey_B)) {
|
|
OPEN_SEARCH_FORM();
|
|
selectedSearchType_ = BITS_8;
|
|
// XXX: ctrl+b already restarts the emulation
|
|
// SDL/SDLMain.cpp
|
|
return;
|
|
}
|
|
|
|
if (ImGui::IsKeyPressed(ImGuiKey_H)) {
|
|
OPEN_SEARCH_FORM();
|
|
selectedSearchType_ = BITS_16;
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_U)) {
|
|
OPEN_SEARCH_FORM();
|
|
selectedSearchType_ = BITS_32;
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_X)) {
|
|
OPEN_SEARCH_FORM();
|
|
selectedSearchType_ = BYTE_SEQ;
|
|
return;
|
|
}
|
|
if (ImGui::IsKeyPressed(ImGuiKey_N)) {
|
|
memView_.continueSearch();
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ImMemWindow::Draw(MIPSDebugInterface *mipsDebug, ImConfig &cfg, ImControl &control, int index) {
|
|
ImGui::SetNextWindowSize(ImVec2(520, 600), ImGuiCond_FirstUseEver);
|
|
if (!ImGui::Begin(Title(index), &cfg.memViewOpen[index])) {
|
|
ImGui::End();
|
|
return;
|
|
}
|
|
|
|
// Toolbars
|
|
|
|
ImGui::InputScalar("Go to addr: ", ImGuiDataType_U32, &gotoAddr_, NULL, NULL, "%08X");
|
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
|
memView_.gotoAddr(gotoAddr_);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Go")) {
|
|
memView_.gotoAddr(gotoAddr_);
|
|
}
|
|
ImGui::SameLine();
|
|
if (ImGui::SmallButton("Settings")) {
|
|
ImGui::OpenPopup("disSettings");
|
|
}
|
|
if (ImGui::BeginPopup("disSettings")) {
|
|
if (ImGui::Checkbox("Darken Zeros", &drawZeroDark_)) {
|
|
memView_.toggleDrawZeroDark(drawZeroDark_);
|
|
}
|
|
if (ImGui::Checkbox("Edit Memory", &editableMemory_)) {
|
|
memView_.toggleEditableMemory(editableMemory_);
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
ImGui::Separator();
|
|
if (ImGui::CollapsingHeader("Memory Search", searchFormFlags_)) {
|
|
ImGui::Combo("type", reinterpret_cast<int*>(&selectedSearchType_), searchtypes, IM_ARRAYSIZE(searchtypes));
|
|
|
|
if (focusSearchValueInput_) {
|
|
ImGui::SetKeyboardFocusHere(0);
|
|
focusSearchValueInput_ = false;
|
|
}
|
|
if (ImGui::InputText("data", searchStr_, IM_ARRAYSIZE(searchStr_), ImGuiInputTextFlags_EnterReturnsTrue) || ImGui::Button("Search")) {
|
|
memView_.initSearch(searchStr_, selectedSearchType_);
|
|
// TODO: transfer focus on the memview.
|
|
}
|
|
ImGui::SameLine();
|
|
|
|
bool isEmpty = memView_.SearchEmpty();
|
|
if (isEmpty)
|
|
ImGui::BeginDisabled(true);
|
|
if (ImGui::Button("Next")) {
|
|
memView_.continueSearch();
|
|
}
|
|
if (isEmpty)
|
|
ImGui::EndDisabled();
|
|
int searchStatus = memView_.SearchStatus();
|
|
if (searchStatus != 0)
|
|
ImGui::SameLine();
|
|
switch (searchStatus) {
|
|
case SEARCH_OK:
|
|
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "match found 0x%x", memView_.SearchMatchAddress());
|
|
break;
|
|
case SEARCH_NOTFOUND:
|
|
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "no match found");
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
ImGui::Separator();
|
|
|
|
ImVec2 size(0, -ImGui::GetFrameHeightWithSpacing());
|
|
|
|
auto node = [&](const char *title, uint32_t start, uint32_t len) {
|
|
if (ImGui::TreeNode(title)) {
|
|
if (ImGui::Selectable("(start)", cfg.selectedMemoryBlock == start)) {
|
|
cfg.selectedMemoryBlock = start;
|
|
GotoAddr(start);
|
|
}
|
|
const std::vector<MemBlockInfo> info = FindMemInfo(start, len);
|
|
for (auto &iter : info) {
|
|
ImGui::PushID(iter.start);
|
|
if (ImGui::Selectable(iter.tag.c_str(), cfg.selectedMemoryBlock == iter.start)) {
|
|
cfg.selectedMemoryBlock = iter.start;
|
|
GotoAddr(iter.start);
|
|
}
|
|
if (ImGui::BeginPopupContextItem("blockinfo")) {
|
|
ImGui::TextUnformatted(iter.tag.c_str());
|
|
ImGui::Separator();
|
|
if (ImGui::MenuItem("Copy info to clipboard")) {
|
|
System_CopyStringToClipboard(DescribeMemBlockInfo(iter));
|
|
}
|
|
if (ImGui::MenuItem("Add memory breakpoint")) {
|
|
// Covers the whole block rather than a single address - the point of doing
|
|
// this from the block list is to catch anything touching the allocation.
|
|
g_breakpoints.AddMemCheck(iter.start, iter.start + iter.size, MEMCHECK_READWRITE, BREAK_ACTION_PAUSE | BREAK_ACTION_LOG);
|
|
control.command = { ImCmd::SHOW_IN_BREAKPOINTS };
|
|
}
|
|
ImGui::EndPopup();
|
|
}
|
|
ImGui::PopID();
|
|
}
|
|
const u32 end = start + len;
|
|
if (ImGui::Selectable("(end)", cfg.selectedMemoryBlock == end)) {
|
|
cfg.selectedMemoryBlock = end;
|
|
GotoAddr(end);
|
|
}
|
|
ImGui::TreePop();
|
|
}
|
|
};
|
|
|
|
// Main views - list of interesting addresses to the left, memory view to the right.
|
|
if (ImGui::BeginChild("addr_list", ImVec2(200.0f, size.y), ImGuiChildFlags_ResizeX)) {
|
|
node("Scratch", 0x00010000, 0x00004000);
|
|
node("Kernel RAM", 0x08000000, 0x00800000);
|
|
node("User RAM", 0x08800000, 0x01800000);
|
|
node("VRAM", 0x04000000, 0x00200000);
|
|
}
|
|
|
|
ImGui::EndChild();
|
|
|
|
ImGui::SameLine();
|
|
|
|
if (ImGui::BeginChild("memview", size)) {
|
|
memView_.Draw(ImGui::GetWindowDrawList());
|
|
}
|
|
ImGui::EndChild();
|
|
|
|
StatusBar(memView_.StatusMessage());
|
|
|
|
ImGui::End();
|
|
if (!ImGui::GetIO().WantTextInput) {
|
|
ProcessKeyboardShortcuts();
|
|
}
|
|
}
|
|
|
|
const char *ImMemWindow::Title(int index) {
|
|
static const char *const titles[4] = { "Memory 1", "Memory 2", "Memory 3", "Memory 4" };
|
|
return titles[index];
|
|
}
|