diff --git a/src/gui/gui.cc b/src/gui/gui.cc index 189a5ff62..aa5cfea7d 100644 --- a/src/gui/gui.cc +++ b/src/gui/gui.cc @@ -786,47 +786,32 @@ void PCSX::GUI::init(std::function applyArguments) { m_hwrEditor.title = l_("Hardware Registers"); m_biosEditor.title = l_("BIOS"); m_vramEditor.title = l_("VRAM"); - m_vramEditor.editor.WriteFn = [](uint8_t* data, size_t offset, uint8_t writtenByte) { - constexpr size_t vramWidth = 1024; - constexpr size_t stride = vramWidth * sizeof(uint16_t); // Number of bytes per line of VRAM - - // x and y coordinates of pixel - const auto x = (offset % stride) / sizeof(uint16_t); - const auto y = offset / stride; - const bool offsetIsOdd = (offset & 1) == 1; - const auto maskedOffset = offset & ~1; - uint16_t newPixel; - - if (offsetIsOdd) { - newPixel = (writtenByte << 8) | data[maskedOffset]; - } else { - newPixel = writtenByte | (data[maskedOffset] << 8); - } - - g_emulator->m_gpu->partialUpdateVRAM(x, y, 1, 1, &newPixel); - }; - - auto exportFn = [this](ImU8* data, size_t len, size_t base_addr, std::string postfixName) { - std::filesystem::path writeFilepath = - g_system->getPersistentDir() / (getSaveStatePrefix(true) + "mem_" + postfixName + ".bin"); - IO f(new PosixFile(writeFilepath.string(), FileOps::TRUNCATE)); - if (!f->failed()) { - f->write(data, len); - f->close(); - g_system->log(LogClass::UI, "Memory exported to: %s\n", writeFilepath.string().c_str()); - } else { - g_system->log(LogClass::UI, "Failed to export memory to: %s\n", writeFilepath.string().c_str()); - } + auto makeExportFn = [this](MemoryEditorWrapper &wrapper, std::string postfixName) { + return [this, &wrapper, postfixName](size_t len, size_t base_addr) { + std::filesystem::path writeFilepath = + g_system->getPersistentDir() / (getSaveStatePrefix(true) + "mem_" + postfixName + ".bin"); + IO out(new PosixFile(writeFilepath.string(), FileOps::TRUNCATE)); + if (!out->failed()) { + std::vector buf(len); + if (wrapper.editor.Cache.BulkReadFn) { + wrapper.editor.Cache.BulkReadFn(buf.data(), 0, len); + } + out->write(buf.data(), len); + out->close(); + g_system->log(LogClass::UI, "Memory exported to: %s\n", writeFilepath.string().c_str()); + } else { + g_system->log(LogClass::UI, "Failed to export memory to: %s\n", writeFilepath.string().c_str()); + } + }; }; -#define EXPORT_FUNC(name) [=](ImU8* data, size_t len, size_t base_addr) { exportFn(data, len, base_addr, name); } for (auto& editor : m_mainMemEditors) { - editor.editor.ExportFn = EXPORT_FUNC("wram"); + editor.editor.ExportFn = makeExportFn(editor, "wram"); } - m_parallelPortEditor.editor.ExportFn = EXPORT_FUNC("parallel"); - m_scratchPadEditor.editor.ExportFn = EXPORT_FUNC("scratch"); - m_hwrEditor.editor.ExportFn = EXPORT_FUNC("hwr"); - m_biosEditor.editor.ExportFn = EXPORT_FUNC("bios"); - m_vramEditor.editor.ExportFn = EXPORT_FUNC("vram"); + m_parallelPortEditor.editor.ExportFn = makeExportFn(m_parallelPortEditor, "parallel"); + m_scratchPadEditor.editor.ExportFn = makeExportFn(m_scratchPadEditor, "scratch"); + m_hwrEditor.editor.ExportFn = makeExportFn(m_hwrEditor, "hwr"); + m_biosEditor.editor.ExportFn = makeExportFn(m_biosEditor, "bios"); + m_vramEditor.editor.ExportFn = makeExportFn(m_vramEditor, "vram"); m_offscreenShaderEditor.init(); m_outputShaderEditor.init(); @@ -1616,47 +1601,70 @@ in Configuration->Emulation, restart PCSX-Redux, then try again.)")); } { + IO memFile = g_emulator->m_mem->getMemoryAsFile(); unsigned counter = 0; for (auto& editor : m_mainMemEditors) { if (editor.m_show) { ImGui::SetNextWindowPos(ImVec2(520, 30 + 10 * counter), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(484, 480), ImGuiCond_FirstUseEver); - editor.draw(g_emulator->m_mem->m_wram, 8 * 1024 * 1024); + editor.draw(memFile, 8 * 1024 * 1024); } counter++; } if (m_parallelPortEditor.m_show) { ImGui::SetNextWindowPos(ImVec2(520, 30 + 10 * counter), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(484, 480), ImGuiCond_FirstUseEver); - m_parallelPortEditor.draw(g_emulator->m_mem->m_exp1, 512 * 1024); + m_parallelPortEditor.draw(memFile, 512 * 1024); } counter++; if (m_scratchPadEditor.m_show) { ImGui::SetNextWindowPos(ImVec2(520, 30 + 10 * counter), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(484, 480), ImGuiCond_FirstUseEver); - m_scratchPadEditor.draw(g_emulator->m_mem->m_hard, 1024); + m_scratchPadEditor.draw(memFile, 1024); } counter++; if (m_hwrEditor.m_show) { ImGui::SetNextWindowPos(ImVec2(520, 30 + 10 * counter), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(484, 480), ImGuiCond_FirstUseEver); - m_hwrEditor.draw(g_emulator->m_mem->m_hard + 4 * 1024, 8 * 1024); + m_hwrEditor.draw(memFile, 8 * 1024); } counter++; if (m_biosEditor.m_show) { ImGui::SetNextWindowPos(ImVec2(520, 30 + 10 * counter), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(484, 480), ImGuiCond_FirstUseEver); - m_biosEditor.draw(g_emulator->m_mem->m_bios, 512 * 1024); + m_biosEditor.draw(memFile, 512 * 1024); } counter++; if (m_vramEditor.m_show) { ImGui::SetNextWindowPos(ImVec2(520, 30 + 10 * counter), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(484, 480), ImGuiCond_FirstUseEver); - // This const_cast is disgusting but we only use it to satisfy the type system - // The slice data is indeed treated as read-only const Slice vram = g_emulator->m_gpu->getVRAM(); - m_vramEditor.draw(const_cast(vram.data()), vram.size()); + auto* vramData = (const ImU8*)vram.data(); + size_t vramSize = vram.size(); + m_vramEditor.editor.ReadFn = [vramData](size_t off) -> ImU8 { return vramData[off]; }; + m_vramEditor.editor.WriteFn = [vramData](size_t off, ImU8 writtenByte) { + constexpr size_t vramWidth = 1024; + constexpr size_t stride = vramWidth * sizeof(uint16_t); + + const auto x = (off % stride) / sizeof(uint16_t); + const auto y = off / stride; + const bool offsetIsOdd = (off & 1) == 1; + const auto maskedOffset = off & ~(size_t)1; + uint16_t newPixel; + + if (offsetIsOdd) { + newPixel = (writtenByte << 8) | vramData[maskedOffset]; + } else { + newPixel = writtenByte | (vramData[maskedOffset + 1] << 8); + } + + g_emulator->m_gpu->partialUpdateVRAM(x, y, 1, 1, &newPixel); + }; + m_vramEditor.editor.Cache.BulkReadFn = [vramData](void* dest, size_t off, size_t len) { + memcpy(dest, vramData + off, len); + }; + m_vramEditor.editor.DrawWindow(m_vramEditor.title(), vramSize); } } diff --git a/src/gui/gui.h b/src/gui/gui.h index 88b39ebcf..dc3218766 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -340,7 +340,27 @@ class GUI final : public UI { std::function title; void MenuItem() { ImGui::MenuItem(title(), nullptr, &m_show); } - void draw(void *mem, size_t size) { editor.DrawWindow(title(), mem, size); } + void draw(void *mem, size_t size) { + editor.ReadFn = [mem](size_t off) -> ImU8 { return ((ImU8 *)mem)[off]; }; + editor.WriteFn = [mem](size_t off, ImU8 d) { ((ImU8 *)mem)[off] = d; }; + editor.Cache.BulkReadFn = [mem](void *dest, size_t off, size_t len) { + memcpy(dest, (ImU8 *)mem + off, len); + }; + editor.DrawWindow(title(), size); + } + void draw(IO file, size_t size) { + IO sub(new SubFile(file, m_baseAddr, size, FileOps::READWRITE)); + editor.ReadFn = [sub](size_t off) mutable -> ImU8 { + ImU8 b; + sub->readAt(&b, 1, off); + return b; + }; + editor.WriteFn = [sub](size_t off, ImU8 d) mutable { sub->writeAt(&d, 1, off); }; + editor.Cache.BulkReadFn = [sub](void *dest, size_t off, size_t len) mutable { + sub->readAt(dest, len, off); + }; + editor.DrawWindow(title(), size); + } }; std::string m_stringHolder; const size_t wramBaseAddr = 0x80000000; diff --git a/src/support/file.cc b/src/support/file.cc index 4720ee794..8ef0b7b7a 100644 --- a/src/support/file.cc +++ b/src/support/file.cc @@ -312,6 +312,43 @@ ssize_t PCSX::SubFile::readAt(void *dest, size_t size, size_t ptr) { return m_file->readAt(dest, size, ptr + m_start); } +ssize_t PCSX::SubFile::wSeek(ssize_t pos, int wheel) { + switch (wheel) { + case SEEK_SET: + m_ptrW = pos; + break; + case SEEK_END: + m_ptrW = m_size - pos; + break; + case SEEK_CUR: + m_ptrW += pos; + break; + } + m_ptrW = std::max(std::min(m_ptrW, m_size), size_t(0)); + return m_ptrW; +} + +ssize_t PCSX::SubFile::write(const void *src, size_t size) { + ssize_t ret = writeAt(src, size, m_ptrW); + if (ret < 0) return ret; + m_ptrW += ret; + if ((m_ptrW < 0) || (m_ptrW > m_size)) { + throw std::runtime_error("SubFile write pointer got out of bound - shouldn't happen"); + } + return ret; +} + +ssize_t PCSX::SubFile::writeAt(const void *src, size_t size, size_t ptr) { + ssize_t excess = size + ptr - m_size; + if (excess > 0) { + if (excess > size) { + return -1; + } + size -= excess; + } + return m_file->writeAt(src, size, ptr + m_start); +} + ssize_t PCSX::Fifo::read(void *dest_, size_t size) { if (size == 0) return 0; uint8_t *dest = static_cast(dest_); diff --git a/src/support/file.h b/src/support/file.h index 2cd4139b8..e3b989a5c 100644 --- a/src/support/file.h +++ b/src/support/file.h @@ -457,11 +457,20 @@ class SubFile : public File { m_file(file), m_start(start), m_size(size < 0 ? file->size() - start : size) {} + SubFile(IO file, size_t start, ssize_t size, FileOps::ReadWrite) + : File(file->seekable() ? RW_SEEKABLE : RW_STREAM), + m_file(file), + m_start(start), + m_size(size < 0 ? file->size() - start : size) {} virtual ssize_t rSeek(ssize_t pos, int wheel) final override; virtual ssize_t rTell() final override { return m_ptrR; } + virtual ssize_t wSeek(ssize_t pos, int wheel) final override; + virtual ssize_t wTell() final override { return m_ptrW; } virtual size_t size() final override { return m_size; } virtual ssize_t read(void* dest, size_t size) final override; virtual ssize_t readAt(void* dest, size_t size, size_t ptr) final override; + virtual ssize_t write(const void* src, size_t size) final override; + virtual ssize_t writeAt(const void* src, size_t size, size_t ptr) final override; virtual bool eof() final override { return m_ptrR == m_size; } virtual File* dup() final override { return new SubFile(m_file, m_start, m_size); } virtual bool failed() final override { return m_file->failed(); } @@ -469,6 +478,7 @@ class SubFile : public File { private: IO m_file; size_t m_ptrR = 0; + size_t m_ptrW = 0; const size_t m_start = 0; const size_t m_size = 0; }; diff --git a/third_party/imgui_memory_editor/imgui_memory_editor.cpp b/third_party/imgui_memory_editor/imgui_memory_editor.cpp index d06ff2226..14b036ce2 100644 --- a/third_party/imgui_memory_editor/imgui_memory_editor.cpp +++ b/third_party/imgui_memory_editor/imgui_memory_editor.cpp @@ -34,8 +34,6 @@ MemoryEditor::MemoryEditor(bool& show, size_t base_addr, size_t &goto_addr) : Op OptAddrDigitsCount = 0; OptFooterExtraHeight = 0.0f; HighlightColor = IM_COL32(255, 255, 255, 50); - ReadFn = NULL; - WriteFn = NULL; HighlightFn = NULL; // State/Internals ContentsWidthChanged = false; @@ -82,8 +80,15 @@ void MemoryEditor::CalcSizes(Sizes& s, size_t mem_size) if (PushMonoFont) ImGui::PopFont(); } +ImU8 MemoryEditor::ReadByte(size_t addr) const +{ + if (Cache.BulkReadFn) return Cache.read(addr); + if (ReadFn) return ReadFn(addr); + return 0; +} + // Standalone Memory Editor window -void MemoryEditor::DrawWindow(const char* title, void* mem_data, size_t mem_size) +void MemoryEditor::DrawWindow(const char* title, size_t mem_size) { Sizes s; CalcSizes(s, mem_size); @@ -94,7 +99,7 @@ void MemoryEditor::DrawWindow(const char* title, void* mem_data, size_t mem_size { if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right)) ImGui::OpenPopup("context"); - DrawContents(mem_data, mem_size); + DrawContents(mem_size); if (ContentsWidthChanged) { CalcSizes(s, mem_size); @@ -105,13 +110,15 @@ void MemoryEditor::DrawWindow(const char* title, void* mem_data, size_t mem_size } // Memory Editor contents only -void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) +void MemoryEditor::DrawContents(size_t mem_size) { if (Cols < 1) Cols = 1; + // Invalidate cache at the start of each frame + Cache.setSize(mem_size); + Cache.invalidate(); - ImU8* mem_data = (ImU8*)mem_data_void; Sizes s; CalcSizes(s, mem_size); if (PushMonoFont) PushMonoFont(); @@ -143,7 +150,7 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) bool data_next = false; - if (ReadOnly || DataEditingAddr >= mem_size) + if (ReadOnly || !WriteFn || DataEditingAddr >= mem_size) DataEditingAddr = (size_t)-1; if (DataPreviewAddr >= mem_size) DataPreviewAddr = (size_t)-1; @@ -164,7 +171,6 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: "; - const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x"; const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x"; const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; @@ -191,9 +197,9 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) ImGui::SameLine(byte_pos_x); // Draw highlight - size_t DataPreviewHighlightBase = DataPreviewAddr & ~(preview_data_type_size - 1); + size_t DataPreviewHighlightBase = DataPreviewAddr & ~(preview_data_type_size - 1); bool is_highlight_from_user_range = (HighlightMin && addr >= HighlightMin && addr < HighlightMax); - bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); + bool is_highlight_from_user_func = (HighlightFn && HighlightFn(addr)); bool is_highlight_from_preview = (addr >= DataPreviewHighlightBase && addr < DataPreviewHighlightBase + preview_data_type_size); if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) { @@ -211,7 +217,7 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) { ImGui::SetKeyboardFocusHere(0); AddrInputBuf = formatter_variable_hex(s.AddrDigitsCount, BaseAddr + addr); - auto byte = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + auto byte = ReadByte(addr); DataInputBuf = formatter_byte(byte); } struct UserData @@ -239,7 +245,7 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) }; UserData user_data; user_data.CursorPos = -1; - sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); + sprintf(user_data.CurrentBufOverwrite, format_byte, ReadByte(addr)); ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways; #if IMGUI_VERSION_NUM >= 18104 flags |= ImGuiInputTextFlags_AlwaysOverwrite; @@ -259,17 +265,17 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) unsigned int data_input_value = 0; if (data_write && sscanf(DataInputBuf.c_str(), "%X", &data_input_value) == 1) { - if (WriteFn) - WriteFn(mem_data, addr, (ImU8)data_input_value); - else - mem_data[addr] = (ImU8)data_input_value; + if (WriteFn) { + WriteFn(addr, (ImU8)data_input_value); + Cache.invalidate(); + } } ImGui::PopID(); } else { // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. - ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + ImU8 b = ReadByte(addr); if (OptShowHexII) { @@ -289,7 +295,7 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) else ImGui::Text(format_byte_space, b); } - if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) + if (!ReadOnly && WriteFn && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) { DataEditingTakeFocus = true; data_editing_addr_next = addr; @@ -306,8 +312,11 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) ImGui::PushID(line_i); if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) { - DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); - DataEditingTakeFocus = true; + DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); + if (!ReadOnly && WriteFn) { + DataEditingAddr = DataPreviewAddr; + DataEditingTakeFocus = true; + } } ImGui::PopID(); for (int n = 0; n < Cols && addr < mem_size; n++, addr++) @@ -317,7 +326,7 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); } - unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; + unsigned char c = ReadByte(addr); char display_c = (c < 32 || c >= 128) ? '.' : c; draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1); pos.x += s.GlyphWidth; @@ -351,19 +360,18 @@ void MemoryEditor::DrawContents(void* mem_data_void, size_t mem_size) if (OptShowOptions) { ImGui::Separator(); - DrawOptionsLine(s, mem_data, mem_size); + DrawOptionsLine(s, mem_size); } if (lock_show_data_preview) { ImGui::Separator(); - DrawPreviewLine(s, mem_data, mem_size); + DrawPreviewLine(s, mem_size); } } -void MemoryEditor::DrawOptionsLine(const Sizes& s, void* mem_data_void, size_t mem_size) +void MemoryEditor::DrawOptionsLine(const Sizes& s, size_t mem_size) { - ImU8* mem_data = (ImU8*)mem_data_void; ImGuiStyle& style = ImGui::GetStyle(); const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; @@ -430,14 +438,13 @@ void MemoryEditor::DrawOptionsLine(const Sizes& s, void* mem_data_void, size_t m if (ExportFn) { ImGui::SameLine(); if (ImGui::Button("Export Memory")) { - ExportFn(mem_data, mem_size, BaseAddr); + ExportFn(mem_size, BaseAddr); } } } -void MemoryEditor::DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size) +void MemoryEditor::DrawPreviewLine(const Sizes& s, size_t mem_size) { - ImU8* mem_data = (ImU8*)mem_data_void; ImGuiStyle& style = ImGui::GetStyle(); ImGui::AlignTextToFramePadding(); ImGui::Text("Preview as:"); @@ -456,15 +463,15 @@ void MemoryEditor::DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t m char buf[128] = ""; float x = s.GlyphWidth * 6.0f; - bool has_value = DataPreviewAddr != (size_t)-1; + bool has_value = DataPreviewAddr != (size_t)-1 && DataPreviewAddr < mem_size; if (has_value) - DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf)); + DrawPreviewData(DataPreviewAddr, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf)); ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); if (has_value) - DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf)); + DrawPreviewData(DataPreviewAddr, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf)); ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); if (has_value) - DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf)); + DrawPreviewData(DataPreviewAddr, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf)); buf[IM_ARRAYSIZE(buf) - 1] = 0; ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); } @@ -557,16 +564,14 @@ const char* MemoryEditor::FormatBinary(const uint8_t* buf, int width) const } // [Internal] -void MemoryEditor::DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const +void MemoryEditor::DrawPreviewData(size_t addr, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const { + if (addr >= mem_size) { out_buf[0] = 0; return; } uint8_t buf[8]; size_t elem_size = DataTypeGetSize(data_type); size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; - if (ReadFn) - for (int i = 0, n = (int)size; i < n; ++i) - buf[i] = ReadFn(mem_data, addr + i); - else - memcpy(buf, mem_data + addr, size); + for (int i = 0, n = (int)size; i < n; ++i) + buf[i] = ReadByte(addr + i); if (data_format == DataFormat_Bin) { diff --git a/third_party/imgui_memory_editor/imgui_memory_editor.h b/third_party/imgui_memory_editor/imgui_memory_editor.h index 4078bd9d5..a2daaceca 100644 --- a/third_party/imgui_memory_editor/imgui_memory_editor.h +++ b/third_party/imgui_memory_editor/imgui_memory_editor.h @@ -8,16 +8,21 @@ // // Usage: // // Create a window and draw memory editor inside it: -// static MemoryEditor mem_edit_1; -// static char data[0x10000]; -// size_t data_size = 0x10000; -// mem_edit_1.DrawWindow("Memory Editor", data, data_size); +// static bool show = true; +// static size_t goto_addr = 0; +// static MemoryEditor mem_edit_1(show, 0, goto_addr); +// mem_edit_1.ReadFn = [](size_t off) -> ImU8 { return my_data[off]; }; +// mem_edit_1.WriteFn = [](size_t off, ImU8 d) { my_data[off] = d; }; +// mem_edit_1.DrawWindow("Memory Editor", data_size); // // Usage: // // If you already have a window, use DrawContents() instead: -// static MemoryEditor mem_edit_2; +// static bool show = true; +// static size_t goto_addr = 0; +// static MemoryEditor mem_edit_2(show, 0, goto_addr); +// mem_edit_2.ReadFn = [](size_t off) -> ImU8 { return ((uint8_t*)ptr)[off]; }; // ImGui::Begin("MyWindow") -// mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this); +// mem_edit_2.DrawContents(mem_size); // ImGui::End(); // // Changelog: @@ -51,8 +56,10 @@ #include #include +#include #include #include +#include #include "imgui.h" @@ -80,12 +87,52 @@ struct MemoryEditor int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets ImU32 HighlightColor; // // background color of highlighted bytes. - ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes. - void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes. - bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting). + bool (*HighlightFn)(size_t off); // = 0 // optional handler to return Highlight property (to support non-contiguous highlighting). std::function PushMonoFont = nullptr; size_t& OffsetAddr; // referenced from PCSX-Redux Settings - std::function ExportFn = nullptr; // optional handler to export all memory data. + + // Data source callbacks - these replace the old void* data pointer approach. + // ReadFn is required; WriteFn is optional (if null, editor is read-only for writes). + // ExportFn is optional, provides an "Export Memory" button. + std::function ReadFn = nullptr; + std::function WriteFn = nullptr; + std::function ExportFn = nullptr; + + // Read-ahead cache for performance. Batches individual byte reads into bulk reads. + struct ReadCache { + static constexpr size_t kPageSize = 4096; + std::function BulkReadFn = nullptr; + + ImU8 read(size_t off) { + if (!BulkReadFn || off >= m_totalSize) return 0; + if (off < m_base || off >= m_base + m_len) fill(off); + return m_buf[off - m_base]; + } + + void invalidate() { m_base = SIZE_MAX; m_len = 0; } + + void setSize(size_t total) { m_totalSize = total; } + + private: + void fill(size_t off) { + m_base = off & ~(kPageSize - 1); + if (m_base >= m_totalSize) { + m_len = 0; + m_buf.clear(); + return; + } + m_len = std::min(kPageSize, m_totalSize - m_base); + m_buf.resize(m_len); + BulkReadFn(m_buf.data(), m_base, m_len); + } + + std::vector m_buf; + size_t m_base = SIZE_MAX; + size_t m_len = 0; + size_t m_totalSize = 0; + }; + + mutable ReadCache Cache; private: // [Internal State] @@ -103,7 +150,7 @@ struct MemoryEditor public: ImGuiDataType PreviewDataType; - + MemoryEditor(bool& show, size_t base_addr, size_t &goto_addr); void GotoAddrAndHighlight(size_t addr_min, size_t addr_max); @@ -127,11 +174,11 @@ struct MemoryEditor void CalcSizes(Sizes& s, size_t mem_size); // Standalone Memory Editor window - void DrawWindow(const char* title, void* mem_data, size_t mem_size); + void DrawWindow(const char* title, size_t mem_size); // Memory Editor contents only - void DrawContents(void* mem_data_void, size_t mem_size); - void DrawOptionsLine(const Sizes& s, void* mem_data_void, size_t mem_size); - void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size); + void DrawContents(size_t mem_size); + void DrawOptionsLine(const Sizes& s, size_t mem_size); + void DrawPreviewLine(const Sizes& s, size_t mem_size); // Utilities for Data Preview const char* DataTypeGetDesc(ImGuiDataType data_type) const; size_t DataTypeGetSize(ImGuiDataType data_type) const; @@ -144,5 +191,6 @@ struct MemoryEditor private: // [Internal] - void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const; + ImU8 ReadByte(size_t addr) const; + void DrawPreviewData(size_t addr, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const; };