mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-04-01 21:17:44 -05:00
feat: Implement better and more complete undo/redo stack (#1433)
This PR aims to implement a more complete undo/redo stack that, unlike the old one, also supports undoing insertions, deletions and resize operations
This commit is contained in:
@@ -107,24 +107,24 @@ namespace hex::plugin::builtin {
|
||||
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
||||
auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readVector();
|
||||
auto patch = hex::loadIPSPatch(patchData);
|
||||
auto patch = Patches::fromIPSPatch(patchData);
|
||||
if (!patch.has_value()) {
|
||||
handleIPSError(patch.error());
|
||||
return;
|
||||
}
|
||||
|
||||
task.setMaxValue(patch->size());
|
||||
task.setMaxValue(patch->get().size());
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
u64 progress = 0;
|
||||
for (auto &[address, value] : *patch) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
u64 count = 0;
|
||||
for (auto &[address, value] : patch->get()) {
|
||||
provider->write(address, &value, sizeof(value));
|
||||
count += 1;
|
||||
task.update(count);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
provider->getUndoStack().groupOperations(count, "hex.builtin.undo_operation.patches");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -133,24 +133,24 @@ namespace hex::plugin::builtin {
|
||||
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
||||
auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readVector();
|
||||
auto patch = hex::loadIPS32Patch(patchData);
|
||||
auto patch = Patches::fromIPS32Patch(patchData);
|
||||
if (!patch.has_value()) {
|
||||
handleIPSError(patch.error());
|
||||
return;
|
||||
}
|
||||
|
||||
task.setMaxValue(patch->size());
|
||||
task.setMaxValue(patch->get().size());
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
u64 progress = 0;
|
||||
for (auto &[address, value] : *patch) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
u64 count = 0;
|
||||
for (auto &[address, value] : patch->get()) {
|
||||
provider->write(address, &value, sizeof(value));
|
||||
count += 1;
|
||||
task.update(count);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
provider->getUndoStack().groupOperations(count, "hex.builtin.undo_operation.patches");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -179,14 +179,14 @@ namespace hex::plugin::builtin {
|
||||
|
||||
task.setMaxValue(patches.size());
|
||||
|
||||
u64 progress = 0;
|
||||
u64 count = 0;
|
||||
for (auto &[address, value] : patches) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
provider->write(address, &value, sizeof(value));
|
||||
count += 1;
|
||||
task.update(count);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
provider->getUndoStack().groupOperations(count, "hex.builtin.undo_operation.patches");
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -281,17 +281,21 @@ namespace hex::plugin::builtin {
|
||||
void exportIPSPatch() {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
Patches patches = provider->getPatches();
|
||||
auto patches = Patches::fromProvider(provider);
|
||||
if (!patches.has_value()) {
|
||||
handleIPSError(patches.error());
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure there's no patch at address 0x00454F46 because that would cause the patch to contain the sequence "EOF" which signals the end of the patch
|
||||
if (!patches.contains(0x00454F45) && patches.contains(0x00454F46)) {
|
||||
if (!patches->get().contains(0x00454F45) && patches->get().contains(0x00454F46)) {
|
||||
u8 value = 0;
|
||||
provider->read(0x00454F45, &value, sizeof(u8));
|
||||
patches[0x00454F45] = value;
|
||||
patches->get().at(0x00454F45) = value;
|
||||
}
|
||||
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) {
|
||||
auto data = generateIPSPatch(patches);
|
||||
auto data = patches->toIPSPatch();
|
||||
|
||||
TaskManager::doLater([data] {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) {
|
||||
@@ -316,17 +320,21 @@ namespace hex::plugin::builtin {
|
||||
void exportIPS32Patch() {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
Patches patches = provider->getPatches();
|
||||
auto patches = Patches::fromProvider(provider);
|
||||
if (!patches.has_value()) {
|
||||
handleIPSError(patches.error());
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure there's no patch at address 0x45454F46 because that would cause the patch to contain the sequence "*EOF" which signals the end of the patch
|
||||
if (!patches.contains(0x45454F45) && patches.contains(0x45454F46)) {
|
||||
if (!patches->get().contains(0x45454F45) && patches->get().contains(0x45454F46)) {
|
||||
u8 value = 0;
|
||||
provider->read(0x45454F45, &value, sizeof(u8));
|
||||
patches[0x45454F45] = value;
|
||||
patches->get().at(0x45454F45) = value;
|
||||
}
|
||||
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) {
|
||||
auto data = generateIPS32Patch(patches);
|
||||
auto data = patches->toIPS32Patch();
|
||||
|
||||
TaskManager::doLater([data] {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) {
|
||||
|
||||
@@ -39,28 +39,7 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
bool FileProvider::isSavable() const {
|
||||
return !this->getPatches().empty();
|
||||
}
|
||||
|
||||
|
||||
void FileProvider::read(u64 offset, void *buffer, size_t size, bool overlays) {
|
||||
this->readRaw(offset - this->getBaseAddress(), buffer, size);
|
||||
|
||||
if (overlays) [[likely]] {
|
||||
for (const auto&[patchOffset, patchData] : getPatches()) {
|
||||
if (patchOffset >= offset && patchOffset < (offset + size))
|
||||
static_cast<u8 *>(buffer)[patchOffset - offset] = patchData;
|
||||
}
|
||||
|
||||
this->applyOverlays(offset, buffer, size);
|
||||
}
|
||||
}
|
||||
|
||||
void FileProvider::write(u64 offset, const void *buffer, size_t size) {
|
||||
if ((offset - this->getBaseAddress()) > (this->getActualSize() - size) || buffer == nullptr || size == 0)
|
||||
return;
|
||||
|
||||
addPatch(offset, buffer, size, true);
|
||||
return this->m_undoRedoStack.canUndo();
|
||||
}
|
||||
|
||||
void FileProvider::readRaw(u64 offset, void *buffer, size_t size) {
|
||||
@@ -79,8 +58,6 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void FileProvider::save() {
|
||||
this->applyPatches();
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
FILETIME ft;
|
||||
SYSTEMTIME st;
|
||||
@@ -105,7 +82,7 @@ namespace hex::plugin::builtin {
|
||||
Provider::saveAs(path);
|
||||
}
|
||||
|
||||
void FileProvider::resize(size_t newSize) {
|
||||
void FileProvider::resizeRaw(size_t newSize) {
|
||||
this->close();
|
||||
|
||||
{
|
||||
@@ -117,9 +94,9 @@ namespace hex::plugin::builtin {
|
||||
(void)this->open();
|
||||
}
|
||||
|
||||
void FileProvider::insert(u64 offset, size_t size) {
|
||||
void FileProvider::insertRaw(u64 offset, size_t size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
this->resize(oldSize + size);
|
||||
this->resizeRaw(oldSize + size);
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
const std::vector<u8> zeroBuffer(0x1000);
|
||||
@@ -134,11 +111,9 @@ namespace hex::plugin::builtin {
|
||||
this->writeRaw(position, zeroBuffer.data(), readSize);
|
||||
this->writeRaw(position + size, buffer.data(), readSize);
|
||||
}
|
||||
|
||||
Provider::insert(offset, size);
|
||||
}
|
||||
|
||||
void FileProvider::remove(u64 offset, size_t size) {
|
||||
void FileProvider::removeRaw(u64 offset, size_t size) {
|
||||
if (offset > this->getActualSize() || size == 0)
|
||||
return;
|
||||
|
||||
@@ -160,10 +135,7 @@ namespace hex::plugin::builtin {
|
||||
position += readSize;
|
||||
}
|
||||
|
||||
this->resize(newSize);
|
||||
|
||||
Provider::insert(offset, size);
|
||||
Provider::remove(offset, size);
|
||||
this->resizeRaw(newSize);
|
||||
}
|
||||
|
||||
size_t FileProvider::getActualSize() const {
|
||||
|
||||
@@ -184,10 +184,6 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
if (overlays) {
|
||||
for (u64 i = 0; i < size; i++)
|
||||
if (getPatches().contains(offset + i))
|
||||
static_cast<u8 *>(buffer)[i] = getPatches()[offset + this->getPageSize() * this->m_currPage + i];
|
||||
|
||||
this->applyOverlays(offset, buffer, size);
|
||||
}
|
||||
}
|
||||
@@ -218,7 +214,6 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void GDBProvider::save() {
|
||||
this->applyPatches();
|
||||
Provider::save();
|
||||
}
|
||||
|
||||
|
||||
@@ -64,15 +64,13 @@ namespace hex::plugin::builtin {
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryFileProvider::resize(size_t newSize) {
|
||||
void MemoryFileProvider::resizeRaw(size_t newSize) {
|
||||
this->m_data.resize(newSize);
|
||||
|
||||
Provider::resize(newSize);
|
||||
}
|
||||
|
||||
void MemoryFileProvider::insert(u64 offset, size_t size) {
|
||||
void MemoryFileProvider::insertRaw(u64 offset, size_t size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
this->resize(oldSize + size);
|
||||
this->resizeRaw(oldSize + size);
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
const std::vector<u8> zeroBuffer(0x1000);
|
||||
@@ -87,14 +85,10 @@ namespace hex::plugin::builtin {
|
||||
this->writeRaw(position, zeroBuffer.data(), readSize);
|
||||
this->writeRaw(position + size, buffer.data(), readSize);
|
||||
}
|
||||
|
||||
Provider::insert(offset, size);
|
||||
}
|
||||
|
||||
void MemoryFileProvider::remove(u64 offset, size_t size) {
|
||||
void MemoryFileProvider::removeRaw(u64 offset, size_t size) {
|
||||
auto oldSize = this->getActualSize();
|
||||
this->resize(oldSize + size);
|
||||
|
||||
std::vector<u8> buffer(0x1000);
|
||||
|
||||
const auto newSize = oldSize - size;
|
||||
@@ -108,10 +102,7 @@ namespace hex::plugin::builtin {
|
||||
position += readSize;
|
||||
}
|
||||
|
||||
this->resize(newSize);
|
||||
|
||||
Provider::insert(offset, size);
|
||||
Provider::remove(offset, size);
|
||||
this->resizeRaw(oldSize - size);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string MemoryFileProvider::getName() const {
|
||||
|
||||
@@ -15,12 +15,14 @@
|
||||
#include <wolv/io/file.hpp>
|
||||
#include <wolv/utils/guards.hpp>
|
||||
|
||||
#include <content/providers/undo_operations/operation_bookmark.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
ViewBookmarks::ViewBookmarks() : View::Window("hex.builtin.view.bookmarks.name") {
|
||||
|
||||
// Handle bookmark add requests sent by the API
|
||||
EventManager::subscribe<RequestAddBookmark>(this, [this](Region region, std::string name, std::string comment, color_t color) {
|
||||
EventManager::subscribe<RequestAddBookmark>(this, [this](Region region, std::string name, std::string comment, color_t color, u64 *id) {
|
||||
if (name.empty()) {
|
||||
name = hex::format("hex.builtin.view.bookmarks.default_title"_lang, region.address, region.address + region.size - 1);
|
||||
}
|
||||
@@ -28,13 +30,21 @@ namespace hex::plugin::builtin {
|
||||
if (color == 0x00)
|
||||
color = ImGui::GetColorU32(ImGuiCol_Header);
|
||||
|
||||
this->m_bookmarks->push_back({
|
||||
this->m_currBookmarkId += 1;
|
||||
u64 bookmarkId = this->m_currBookmarkId;
|
||||
if (id != nullptr)
|
||||
*id = bookmarkId;
|
||||
|
||||
auto bookmark = ImHexApi::Bookmarks::Entry{
|
||||
region,
|
||||
name,
|
||||
std::move(comment),
|
||||
color,
|
||||
false
|
||||
});
|
||||
false,
|
||||
bookmarkId
|
||||
};
|
||||
|
||||
this->m_bookmarks->push_back(std::move(bookmark));
|
||||
|
||||
ImHexApi::Provider::markDirty();
|
||||
|
||||
@@ -42,6 +52,12 @@ namespace hex::plugin::builtin {
|
||||
EventManager::post<EventHighlightingChanged>();
|
||||
});
|
||||
|
||||
EventManager::subscribe<RequestRemoveBookmark>([this](u64 id) {
|
||||
std::erase_if(this->m_bookmarks.get(), [id](const auto &bookmark) {
|
||||
return bookmark.id == id;
|
||||
});
|
||||
});
|
||||
|
||||
// Draw hex editor background highlights for bookmarks
|
||||
ImHexApi::HexEditor::addBackgroundHighlightingProvider([this](u64 address, const u8* data, size_t size, bool) -> std::optional<color_t> {
|
||||
hex::unused(data);
|
||||
@@ -244,7 +260,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
// Draw all bookmarks
|
||||
for (auto iter = this->m_bookmarks->begin(); iter != this->m_bookmarks->end(); iter++) {
|
||||
auto &[region, name, comment, color, locked] = *iter;
|
||||
auto &[region, name, comment, color, locked, bookmarkId] = *iter;
|
||||
|
||||
// Apply filter
|
||||
if (!this->m_currFilter.empty()) {
|
||||
@@ -424,12 +440,18 @@ namespace hex::plugin::builtin {
|
||||
continue;
|
||||
|
||||
this->m_bookmarks.get(provider).push_back({
|
||||
.region = { region["address"], region["size"] },
|
||||
.name = bookmark["name"],
|
||||
.comment = bookmark["comment"],
|
||||
.color = bookmark["color"],
|
||||
.locked = bookmark["locked"]
|
||||
.region = { region["address"], region["size"] },
|
||||
.name = bookmark["name"],
|
||||
.comment = bookmark["comment"],
|
||||
.color = bookmark["color"],
|
||||
.locked = bookmark["locked"],
|
||||
.id = bookmark.contains("id") ? bookmark["id"].get<u64>() : *this->m_currBookmarkId
|
||||
});
|
||||
|
||||
if (bookmark.contains("id"))
|
||||
this->m_currBookmarkId = std::max<u64>(this->m_currBookmarkId, bookmark["id"].get<i64>() + 1);
|
||||
else
|
||||
this->m_currBookmarkId += 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -440,15 +462,16 @@ namespace hex::plugin::builtin {
|
||||
size_t index = 0;
|
||||
for (const auto &bookmark : this->m_bookmarks.get(provider)) {
|
||||
json["bookmarks"][index] = {
|
||||
{ "name", bookmark.name },
|
||||
{ "comment", bookmark.comment },
|
||||
{ "color", bookmark.color },
|
||||
{ "name", bookmark.name },
|
||||
{ "comment", bookmark.comment },
|
||||
{ "color", bookmark.color },
|
||||
{ "region", {
|
||||
{ "address", bookmark.region.address },
|
||||
{ "size", bookmark.region.size }
|
||||
{ "address", bookmark.region.address },
|
||||
{ "size", bookmark.region.size }
|
||||
}
|
||||
},
|
||||
{ "locked", bookmark.locked }
|
||||
{ "locked", bookmark.locked },
|
||||
{ "id", bookmark.id }
|
||||
};
|
||||
index++;
|
||||
}
|
||||
|
||||
@@ -535,10 +535,13 @@ namespace hex::plugin::builtin {
|
||||
return;
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
u32 patchCount = 0;
|
||||
for (u64 i = 0; i < size; i += bytes.size()) {
|
||||
auto remainingSize = std::min<size_t>(size - i, bytes.size());
|
||||
provider->write(provider->getBaseAddress() + address + i, bytes.data(), remainingSize);
|
||||
patchCount += 1;
|
||||
}
|
||||
provider->getUndoStack().groupOperations(patchCount, "hex.builtin.undo_operation.fill");
|
||||
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.fill.name");
|
||||
}
|
||||
|
||||
@@ -5,6 +5,11 @@
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <content/providers/undo_operations/operation_write.hpp>
|
||||
#include <content/providers/undo_operations/operation_insert.hpp>
|
||||
#include <content/providers/undo_operations/operation_remove.hpp>
|
||||
|
||||
#include <ranges>
|
||||
#include <string>
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
@@ -18,14 +23,17 @@ namespace hex::plugin::builtin {
|
||||
.required = false,
|
||||
.load = [](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) {
|
||||
auto json = nlohmann::json::parse(tar.readString(basePath));
|
||||
provider->getPatches() = json.at("patches").get<std::map<u64, u8>>();
|
||||
auto patches = json.at("patches").get<std::map<u64, u8>>();
|
||||
|
||||
for (const auto &[address, value] : patches) {
|
||||
provider->write(address, &value, sizeof(value));
|
||||
}
|
||||
|
||||
provider->getUndoStack().groupOperations(patches.size(), "hex.builtin.undo_operation.patches");
|
||||
|
||||
return true;
|
||||
},
|
||||
.store = [](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) {
|
||||
nlohmann::json json;
|
||||
json["patches"] = provider->getPatches();
|
||||
tar.writeString(basePath, json.dump(4));
|
||||
|
||||
.store = [](prv::Provider *, const std::fs::path &, Tar &) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -38,19 +46,43 @@ namespace hex::plugin::builtin {
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
u8 byte = 0x00;
|
||||
provider->read(offset, &byte, sizeof(u8), false);
|
||||
offset -= provider->getBaseAddress();
|
||||
|
||||
const auto &patches = provider->getPatches();
|
||||
if (patches.contains(offset) && patches.at(offset) != byte)
|
||||
return ImGuiExt::GetCustomColorU32(ImGuiCustomCol_Patches);
|
||||
else
|
||||
return std::nullopt;
|
||||
const auto &undoStack = provider->getUndoStack();
|
||||
for (const auto &operation : undoStack.getAppliedOperations()) {
|
||||
if (!operation->shouldHighlight())
|
||||
continue;
|
||||
|
||||
if (operation->getRegion().overlaps(Region { offset, 1}))
|
||||
return ImGuiExt::GetCustomColorU32(ImGuiCustomCol_Patches);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
EventManager::subscribe<EventProviderSaved>([](auto *) {
|
||||
EventManager::post<EventHighlightingChanged>();
|
||||
});
|
||||
|
||||
EventManager::subscribe<EventProviderDataModified>(this, [](prv::Provider *provider, u64 offset, u64 size, const u8 *data) {
|
||||
offset -= provider->getBaseAddress();
|
||||
|
||||
std::vector<u8> oldData(size, 0x00);
|
||||
provider->read(offset, oldData.data(), size);
|
||||
provider->getUndoStack().add<undo::OperationWrite>(offset, size, oldData.data(), data);
|
||||
});
|
||||
|
||||
EventManager::subscribe<EventProviderDataInserted>(this, [](prv::Provider *provider, u64 offset, u64 size) {
|
||||
offset -= provider->getBaseAddress();
|
||||
|
||||
provider->getUndoStack().add<undo::OperationInsert>(offset, size);
|
||||
});
|
||||
|
||||
EventManager::subscribe<EventProviderDataRemoved>(this, [](prv::Provider *provider, u64 offset, u64 size) {
|
||||
offset -= provider->getBaseAddress();
|
||||
|
||||
provider->getUndoStack().add<undo::OperationRemove>(offset, size);
|
||||
});
|
||||
}
|
||||
|
||||
void ViewPatches::drawContent() {
|
||||
@@ -60,57 +92,76 @@ namespace hex::plugin::builtin {
|
||||
|
||||
if (ImGui::BeginTable("##patchesTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) {
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableSetupColumn("hex.builtin.view.patches.offset"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.view.patches.orig"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.view.patches.patch"_lang);
|
||||
ImGui::TableSetupColumn("##PatchID", ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoResize);
|
||||
ImGui::TableSetupColumn("hex.builtin.view.patches.offset"_lang, ImGuiTableColumnFlags_WidthFixed);
|
||||
ImGui::TableSetupColumn("hex.builtin.view.patches.patch"_lang, ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
auto &patches = provider->getPatches();
|
||||
u32 index = 0;
|
||||
const auto &undoRedoStack = provider->getUndoStack();
|
||||
std::vector<prv::undo::Operation*> operations;
|
||||
for (const auto &operation : undoRedoStack.getUndoneOperations())
|
||||
operations.push_back(operation.get());
|
||||
for (const auto &operation : undoRedoStack.getAppliedOperations() | std::views::reverse)
|
||||
operations.push_back(operation.get());
|
||||
|
||||
u32 index = 0;
|
||||
|
||||
ImGuiListClipper clipper;
|
||||
|
||||
clipper.Begin(patches.size());
|
||||
clipper.Begin(operations.size());
|
||||
while (clipper.Step()) {
|
||||
auto iter = patches.begin();
|
||||
auto iter = operations.begin();
|
||||
for (auto i = 0; i < clipper.DisplayStart; i++)
|
||||
++iter;
|
||||
|
||||
auto undoneOperationsCount = undoRedoStack.getUndoneOperations().size();
|
||||
for (auto i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
|
||||
const auto &[address, patch] = *iter;
|
||||
const auto &operation = *iter;
|
||||
|
||||
const auto [address, size] = operation->getRegion();
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::Selectable(("##patchLine" + std::to_string(index)).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
ImHexApi::HexEditor::setSelection(address, 1);
|
||||
ImGui::BeginDisabled(size_t(i) < undoneOperationsCount);
|
||||
|
||||
if (ImGui::Selectable(hex::format("{} {}", index == undoneOperationsCount ? ICON_VS_ARROW_SMALL_RIGHT : " ", index).c_str(), false, ImGuiSelectableFlags_SpanAllColumns)) {
|
||||
ImHexApi::HexEditor::setSelection(address, size);
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
const auto content = operation->formatContent();
|
||||
if (!content.empty()) {
|
||||
if (ImGui::BeginTooltip()) {
|
||||
if (ImGui::BeginTable("##content_table", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders)) {
|
||||
for (const auto &entry : content) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ImGuiExt::TextFormatted("{}", entry);
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ImGui::IsMouseReleased(1) && ImGui::IsItemHovered()) {
|
||||
ImGui::OpenPopup("PatchContextMenu");
|
||||
this->m_selectedPatch = address;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGuiExt::TextFormatted("0x{0:08X}", address);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
u8 previousValue = 0x00;
|
||||
provider->readRaw(address, &previousValue, sizeof(u8));
|
||||
ImGuiExt::TextFormatted("0x{0:02X}", previousValue);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGuiExt::TextFormatted("0x{0:02X}", patch);
|
||||
ImGuiExt::TextFormatted("{}", operation->format());
|
||||
index += 1;
|
||||
|
||||
iter++;
|
||||
}
|
||||
}
|
||||
++iter;
|
||||
|
||||
if (ImGui::BeginPopup("PatchContextMenu")) {
|
||||
if (ImGui::MenuItem("hex.builtin.view.patches.remove"_lang)) {
|
||||
patches.erase(this->m_selectedPatch);
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
@@ -120,9 +171,9 @@ namespace hex::plugin::builtin {
|
||||
|
||||
void ViewPatches::drawAlwaysVisibleContent() {
|
||||
if (auto provider = ImHexApi::Provider::get(); provider != nullptr) {
|
||||
const auto &patches = provider->getPatches();
|
||||
if (this->m_numPatches.get(provider) != patches.size()) {
|
||||
this->m_numPatches.get(provider) = patches.size();
|
||||
const auto &operations = provider->getUndoStack().getAppliedOperations();
|
||||
if (this->m_numOperations.get(provider) != operations.size()) {
|
||||
this->m_numOperations.get(provider) = operations.size();
|
||||
EventManager::post<EventHighlightingChanged>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,8 +165,9 @@ namespace hex::plugin::builtin::ui {
|
||||
|
||||
if (this->m_editingBytes.size() < size) {
|
||||
this->m_editingBytes.resize(size);
|
||||
std::memcpy(this->m_editingBytes.data(), data, size);
|
||||
}
|
||||
|
||||
std::memcpy(this->m_editingBytes.data(), data, size);
|
||||
}
|
||||
|
||||
if (this->m_editingAddress != address || this->m_editingCellType != cellType) {
|
||||
@@ -217,9 +218,23 @@ namespace hex::plugin::builtin::ui {
|
||||
}
|
||||
|
||||
if (shouldExitEditingMode || this->m_shouldModifyValue) {
|
||||
this->m_provider->write(*this->m_editingAddress, this->m_editingBytes.data(), this->m_editingBytes.size());
|
||||
{
|
||||
std::vector<u8> oldData(this->m_editingBytes.size());
|
||||
this->m_provider->read(*this->m_editingAddress, oldData.data(), oldData.size());
|
||||
|
||||
if (!this->m_selectionChanged && !ImGui::IsMouseDown(ImGuiMouseButton_Left) && !ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
size_t writtenBytes = 0;
|
||||
for (size_t i = 0; i < this->m_editingBytes.size(); i += 1) {
|
||||
if (this->m_editingBytes[i] != oldData[i]) {
|
||||
this->m_provider->write(*this->m_editingAddress, &this->m_editingBytes[i], 1);
|
||||
writtenBytes += 1;
|
||||
}
|
||||
}
|
||||
|
||||
this->m_provider->getUndoStack().groupOperations(writtenBytes, "hex.builtin.undo_operation.modification");
|
||||
}
|
||||
|
||||
|
||||
if (!this->m_selectionChanged && !ImGui::IsMouseDown(ImGuiMouseButton_Left) && !ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGui::IsKeyDown(ImGuiKey_Escape)) {
|
||||
auto nextEditingAddress = *this->m_editingAddress + this->m_currDataVisualizer->getBytesPerCell();
|
||||
this->setSelection(nextEditingAddress, nextEditingAddress);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user