#include "content/views/view_patches.hpp" #include #include #include #include #include #include #include #include using namespace std::literals::string_literals; namespace hex::plugin::builtin { ViewPatches::ViewPatches() : View::Window("hex.builtin.view.patches.name", ICON_VS_GIT_PULL_REQUEST_NEW_CHANGES) { ProjectFile::registerPerProviderHandler({ .basePath = "patches.json", .required = false, .load = [](prv::Provider *provider, const std::fs::path &basePath, const Tar &tar) { auto json = nlohmann::json::parse(tar.readString(basePath)); auto patches = json.at("patches").get>(); 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 *, const std::fs::path &, Tar &) { return true; } }); ImHexApi::HexEditor::addForegroundHighlightingProvider([](u64 offset, const u8* buffer, size_t, bool) -> std::optional { hex::unused(buffer); if (!ImHexApi::Provider::isValid()) return std::nullopt; auto provider = ImHexApi::Provider::get(); offset -= provider->getBaseAddress(); 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; }); EventProviderSaved::subscribe([](auto *) { EventHighlightingChanged::post(); }); EventProviderDataModified::subscribe(this, [](prv::Provider *provider, u64 offset, u64 size, const u8 *data) { if (size == 0) return; offset -= provider->getBaseAddress(); std::vector oldData(size, 0x00); provider->read(offset, oldData.data(), size); provider->getUndoStack().add(offset, size, oldData.data(), data); }); EventProviderDataInserted::subscribe(this, [](prv::Provider *provider, u64 offset, u64 size) { offset -= provider->getBaseAddress(); provider->getUndoStack().add(offset, size); }); EventProviderDataRemoved::subscribe(this, [](prv::Provider *provider, u64 offset, u64 size) { offset -= provider->getBaseAddress(); provider->getUndoStack().add(offset, size); }); } void ViewPatches::drawContent() { auto provider = ImHexApi::Provider::get(); if (ImHexApi::Provider::isValid() && provider->isReadable()) { if (ImGui::BeginTable("##patchesTable", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY)) { ImGui::TableSetupScrollFreeze(0, 1); 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(); const auto &undoRedoStack = provider->getUndoStack(); std::vector 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(operations.size()); while (clipper.Step()) { 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 &operation = *iter; const auto [address, size] = operation->getRegion(); ImGui::TableNextRow(); ImGui::TableNextColumn(); 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"); m_selectedPatch = address; } ImGui::TableNextColumn(); ImGuiExt::TextFormatted("0x{0:08X}", address); ImGui::TableNextColumn(); ImGuiExt::TextFormatted("{}", operation->format()); index += 1; ++iter; ImGui::EndDisabled(); } } ImGui::EndTable(); } } } void ViewPatches::drawAlwaysVisibleContent() { if (auto provider = ImHexApi::Provider::get(); provider != nullptr) { const auto &operations = provider->getUndoStack().getAppliedOperations(); if (m_numOperations.get(provider) != operations.size()) { m_numOperations.get(provider) = operations.size(); EventHighlightingChanged::post(); } } } }