1
0
mirror of synced 2025-01-18 00:56:49 +01: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:
Nik 2023-11-25 12:43:48 +01:00 committed by GitHub
parent e5f36ca08d
commit 7e660450ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 904 additions and 325 deletions

@ -1 +1 @@
Subproject commit 7e4a95cb4a909d80c0bd5777bf89325f240f9d54
Subproject commit 86faee9f3e0ebdf7542c51fb5233134c376e4d3d

@ -1 +1 @@
Subproject commit 914d5357624e63b5930a46ffb7334fa82bea6dd8
Subproject commit ae180a117aa4513285ca6ef6072ac7de18447e61

View File

@ -38,6 +38,7 @@ set(LIBIMHEX_SOURCES
source/helpers/debugging.cpp
source/providers/provider.cpp
source/providers/undo/stack.cpp
source/ui/imgui_imhex_extensions.cpp
source/ui/view.cpp

View File

@ -243,6 +243,10 @@ namespace hex {
EVENT_DEF(EventAchievementUnlocked, const Achievement&);
EVENT_DEF(EventSearchBoxClicked);
EVENT_DEF(EventProviderDataModified, prv::Provider *, u64, u64, const u8*);
EVENT_DEF(EventProviderDataInserted, prv::Provider *, u64, u64);
EVENT_DEF(EventProviderDataRemoved, prv::Provider *, u64, u64);
/**
* @brief Called when a project has been loaded
*/
@ -254,7 +258,8 @@ namespace hex {
EVENT_DEF(RequestOpenWindow, std::string);
EVENT_DEF(RequestSelectionChange, Region);
EVENT_DEF(RequestAddBookmark, Region, std::string, std::string, color_t);
EVENT_DEF(RequestAddBookmark, Region, std::string, std::string, color_t, u64*);
EVENT_DEF(RequestRemoveBookmark, u64);
EVENT_DEF(RequestSetPatternLanguageCode, std::string);
EVENT_DEF(RequestLoadPatternLanguageFile, std::fs::path);
EVENT_DEF(RequestSavePatternLanguageFile, std::fs::path);

View File

@ -211,6 +211,7 @@ namespace hex {
std::string comment;
u32 color;
bool locked;
u64 id;
};
/**
@ -220,8 +221,25 @@ namespace hex {
* @param name The name of the bookmark
* @param comment The comment of the bookmark
* @param color The color of the bookmark or 0x00 for the default color
* @return Bookmark ID
*/
void add(u64 address, size_t size, const std::string &name, const std::string &comment, color_t color = 0x00000000);
u64 add(u64 address, size_t size, const std::string &name, const std::string &comment, color_t color = 0x00000000);
/**
* @brief Adds a new bookmark
* @param region The region of the bookmark
* @param name The name of the bookmark
* @param comment The comment of the bookmark
* @param color The color of the bookmark or 0x00 for the default color
* @return Bookmark ID
*/
u64 add(Region region, const std::string &name, const std::string &comment, color_t color = 0x00000000);
/**
* @brief Removes a bookmark
* @param id The ID of the bookmark to remove
*/
void remove(u64 id);
}

View File

@ -9,7 +9,9 @@
namespace hex {
using Patches = std::map<u64, u8>;
namespace prv {
class Provider;
}
enum class IPSError {
AddressOutOfRange,
@ -19,9 +21,22 @@ namespace hex {
MissingEOF
};
wolv::util::Expected<std::vector<u8>, IPSError> generateIPSPatch(const Patches &patches);
wolv::util::Expected<std::vector<u8>, IPSError> generateIPS32Patch(const Patches &patches);
class Patches {
public:
Patches() = default;
Patches(std::map<u64, u8> &&patches) : m_patches(std::move(patches)) {}
wolv::util::Expected<Patches, IPSError> loadIPSPatch(const std::vector<u8> &ipsPatch);
wolv::util::Expected<Patches, IPSError> loadIPS32Patch(const std::vector<u8> &ipsPatch);
static wolv::util::Expected<Patches, IPSError> fromProvider(hex::prv::Provider *provider);
static wolv::util::Expected<Patches, IPSError> fromIPSPatch(const std::vector<u8> &ipsPatch);
static wolv::util::Expected<Patches, IPSError> fromIPS32Patch(const std::vector<u8> &ipsPatch);
wolv::util::Expected<std::vector<u8>, IPSError> toIPSPatch() const;
wolv::util::Expected<std::vector<u8>, IPSError> toIPS32Patch() const;
const auto& get() const { return this->m_patches; }
auto& get() { return this->m_patches; }
private:
std::map<u64, u8> m_patches;
};
}

View File

@ -14,6 +14,8 @@
#include <nlohmann/json.hpp>
#include <hex/providers/undo_redo/stack.hpp>
namespace hex::prv {
/**
@ -154,24 +156,20 @@ namespace hex::prv {
*/
[[nodiscard]] virtual std::string getName() const = 0;
void resize(size_t newSize);
void insert(u64 offset, size_t size);
void remove(u64 offset, size_t size);
virtual void resize(size_t newSize);
virtual void insert(u64 offset, size_t size);
virtual void remove(u64 offset, size_t size);
virtual void resizeRaw(size_t newSize) { hex::unused(newSize); }
virtual void insertRaw(u64 offset, size_t size) { hex::unused(offset, size); }
virtual void removeRaw(u64 offset, size_t size) { hex::unused(offset, size); }
virtual void save();
virtual void saveAs(const std::fs::path &path);
void applyOverlays(u64 offset, void *buffer, size_t size) const;
[[nodiscard]] std::map<u64, u8> &getPatches();
[[nodiscard]] const std::map<u64, u8> &getPatches() const;
void applyPatches();
[[nodiscard]] Overlay *newOverlay();
void deleteOverlay(Overlay *overlay);
void applyOverlays(u64 offset, void *buffer, size_t size) const;
[[nodiscard]] const std::list<std::unique_ptr<Overlay>> &getOverlays() const;
[[nodiscard]] size_t getPageSize() const;
@ -190,9 +188,6 @@ namespace hex::prv {
[[nodiscard]] virtual std::vector<Description> getDataDescription() const;
[[nodiscard]] virtual std::variant<std::string, i128> queryInformation(const std::string &category, const std::string &argument);
void addPatch(u64 offset, const void *buffer, size_t size, bool createUndo = false);
void createUndoPoint();
void undo();
void redo();
@ -226,12 +221,19 @@ namespace hex::prv {
void setErrorMessage(const std::string &errorMessage) { this->m_errorMessage = errorMessage; }
[[nodiscard]] const std::string& getErrorMessage() const { return this->m_errorMessage; }
template<std::derived_from<undo::Operation> T>
bool addUndoableOperation(auto && ... args) {
return this->m_undoRedoStack.add<T>(std::forward<decltype(args)...>(args)...);
}
[[nodiscard]] undo::Stack& getUndoStack() { return this->m_undoRedoStack; }
protected:
u32 m_currPage = 0;
u64 m_baseAddress = 0;
std::list<std::map<u64, u8>> m_patches;
decltype(m_patches)::iterator m_currPatches;
undo::Stack m_undoRedoStack;
std::list<std::unique_ptr<Overlay>> m_overlays;
u32 m_id;

View File

@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <vector>
#include <hex/helpers/concepts.hpp>
namespace hex::prv {
class Provider;
}
namespace hex::prv::undo {
class Operation : public ICloneable<Operation> {
public:
virtual ~Operation() = default;
virtual void undo(Provider *provider) = 0;
virtual void redo(Provider *provider) = 0;
[[nodiscard]] virtual Region getRegion() const = 0;
[[nodiscard]] virtual std::string format() const = 0;
[[nodiscard]] virtual std::vector<std::string> formatContent() const {
return { };
}
[[nodiscard]] virtual bool shouldHighlight() const { return true; }
};
}

View File

@ -0,0 +1,70 @@
#pragma once
#include <hex/providers/undo_redo/operations/operation.hpp>
#include <hex/api/localization_manager.hpp>
#include <hex/helpers/fmt.hpp>
#include <hex/helpers/utils.hpp>
namespace hex::prv::undo {
class OperationGroup : public Operation {
public:
explicit OperationGroup(std::string unlocalizedName) : m_unlocalizedName(std::move(unlocalizedName)) {}
OperationGroup(const OperationGroup &other) {
for (const auto &operation : other.m_operations)
this->m_operations.emplace_back(operation->clone());
}
void undo(Provider *provider) override {
for (auto &operation : this->m_operations)
operation->undo(provider);
}
void redo(Provider *provider) override {
for (auto &operation : this->m_operations)
operation->redo(provider);
}
void addOperation(std::unique_ptr<Operation> &&newOperation) {
auto newRegion = newOperation->getRegion();
if (newRegion.getStartAddress() < this->m_startAddress)
this->m_startAddress = newRegion.getStartAddress();
if (newRegion.getEndAddress() > this->m_endAddress)
this->m_endAddress = newRegion.getEndAddress();
if (this->m_formattedContent.size() <= 10)
this->m_formattedContent.emplace_back(newOperation->format());
else
this->m_formattedContent.back() = hex::format("[{}x] ...", (this->m_operations.size() - 10) + 1);
this->m_operations.emplace_back(std::move(newOperation));
}
[[nodiscard]] std::string format() const override {
return hex::format("{}", Lang(this->m_unlocalizedName));
}
[[nodiscard]] Region getRegion() const override {
return Region { this->m_startAddress, (this->m_endAddress - this->m_startAddress) + 1 };
}
std::unique_ptr<Operation> clone() const override {
return std::make_unique<OperationGroup>(*this);
}
std::vector<std::string> formatContent() const override {
return this->m_formattedContent;
}
private:
std::string m_unlocalizedName;
std::vector<std::unique_ptr<Operation>> m_operations;
u64 m_startAddress = std::numeric_limits<u64>::max();
u64 m_endAddress = std::numeric_limits<u64>::min();
std::vector<std::string> m_formattedContent;
};
}

View File

@ -0,0 +1,57 @@
#pragma once
#include <hex.hpp>
#include <hex/providers/undo_redo/operations/operation.hpp>
#include <atomic>
#include <map>
#include <memory>
#include <mutex>
#include <vector>
namespace hex::prv {
class Provider;
}
namespace hex::prv::undo {
using Patches = std::map<u64, u8>;
class Stack {
public:
explicit Stack(Provider *provider);
void undo(u32 count = 1);
void redo(u32 count = 1);
void groupOperations(u32 count, const std::string &unlocalizedName);
void apply(const Stack &otherStack);
[[nodiscard]] bool canUndo() const;
[[nodiscard]] bool canRedo() const;
template<std::derived_from<Operation> T>
bool add(auto && ... args) {
return this->add(std::make_unique<T>(std::forward<decltype(args)>(args)...));
}
bool add(std::unique_ptr<Operation> &&operation);
const std::vector<std::unique_ptr<Operation>> &getAppliedOperations() const {
return this->m_undoStack;
}
const std::vector<std::unique_ptr<Operation>> &getUndoneOperations() const {
return this->m_redoStack;
}
private:
[[nodiscard]] Operation* getLastOperation() const {
return this->m_undoStack.back().get();
}
private:
std::vector<std::unique_ptr<Operation>> m_undoStack, m_redoStack;
Provider *m_provider;
};
}

View File

@ -141,6 +141,7 @@ namespace hex {
void draw() final {
if (this->shouldDraw()) {
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
this->drawContent();
}
}

View File

@ -205,12 +205,19 @@ namespace hex {
namespace ImHexApi::Bookmarks {
void add(Region region, const std::string &name, const std::string &comment, u32 color) {
EventManager::post<RequestAddBookmark>(region, name, comment, color);
u64 add(Region region, const std::string &name, const std::string &comment, u32 color) {
u64 id = 0;
EventManager::post<RequestAddBookmark>(region, name, comment, color, &id);
return id;
}
void add(u64 address, size_t size, const std::string &name, const std::string &comment, u32 color) {
add(Region { address, size }, name, comment, color);
u64 add(u64 address, size_t size, const std::string &name, const std::string &comment, u32 color) {
return add(Region { address, size }, name, comment, color);
}
void remove(u64 id) {
EventManager::post<RequestRemoveBookmark>(id);
}
}

View File

@ -2,22 +2,108 @@
#include <hex/helpers/utils.hpp>
#include <hex/providers/provider.hpp>
#include <cstring>
#include <string_view>
namespace hex {
static void pushStringBack(std::vector<u8> &buffer, const std::string &string) {
std::copy(string.begin(), string.end(), std::back_inserter(buffer));
namespace {
class PatchesGenerator : public hex::prv::Provider {
public:
explicit PatchesGenerator() = default;
~PatchesGenerator() override = default;
[[nodiscard]] bool isAvailable() const override { return true; }
[[nodiscard]] bool isReadable() const override { return true; }
[[nodiscard]] bool isWritable() const override { return true; }
[[nodiscard]] bool isResizable() const override { return true; }
[[nodiscard]] bool isSavable() const override { return false; }
[[nodiscard]] bool isSavableAsRecent() const override { return false; }
[[nodiscard]] bool open() override { return true; }
void close() override { }
void readRaw(u64 offset, void *buffer, size_t size) override {
hex::unused(offset, buffer, size);
}
void writeRaw(u64 offset, const void *buffer, size_t size) override {
for (u64 i = 0; i < size; i += 1)
this->m_patches[offset] = static_cast<const u8*>(buffer)[i];
}
[[nodiscard]] size_t getActualSize() const override {
if (this->m_patches.empty())
return 0;
else
return this->m_patches.rbegin()->first;
}
void resizeRaw(size_t newSize) override {
hex::unused(newSize);
}
void insertRaw(u64 offset, size_t size) override {
std::vector<std::pair<u64, u8>> patchesToMove;
for (auto &[address, value] : this->m_patches) {
if (address > offset)
patchesToMove.emplace_back(address, value);
}
for (const auto &[address, value] : patchesToMove)
this->m_patches.erase(address);
for (const auto &[address, value] : patchesToMove)
this->m_patches.insert({ address + size, value });
}
void removeRaw(u64 offset, size_t size) override {
std::vector<std::pair<u64, u8>> patchesToMove;
for (auto &[address, value] : this->m_patches) {
if (address > offset)
patchesToMove.emplace_back(address, value);
}
for (const auto &[address, value] : patchesToMove)
this->m_patches.erase(address);
for (const auto &[address, value] : patchesToMove)
this->m_patches.insert({ address - size, value });
}
[[nodiscard]] std::string getName() const override {
return "";
}
[[nodiscard]] std::string getTypeName() const override { return ""; }
const std::map<u64, u8>& getPatches() const {
return this->m_patches;
}
private:
std::map<u64, u8> m_patches;
};
void pushStringBack(std::vector<u8> &buffer, const std::string &string) {
std::copy(string.begin(), string.end(), std::back_inserter(buffer));
}
template<typename T>
void pushBytesBack(std::vector<u8> &buffer, T bytes) {
buffer.resize(buffer.size() + sizeof(T));
std::memcpy((&buffer.back() - sizeof(T)) + 1, &bytes, sizeof(T));
}
}
template<typename T>
static void pushBytesBack(std::vector<u8> &buffer, T bytes) {
buffer.resize(buffer.size() + sizeof(T));
std::memcpy((&buffer.back() - sizeof(T)) + 1, &bytes, sizeof(T));
}
wolv::util::Expected<std::vector<u8>, IPSError> generateIPSPatch(const Patches &patches) {
wolv::util::Expected<std::vector<u8>, IPSError> Patches::toIPSPatch() const {
std::vector<u8> result;
pushStringBack(result, "PATCH");
@ -25,7 +111,7 @@ namespace hex {
std::vector<u64> addresses;
std::vector<u8> values;
for (const auto &[address, value] : patches) {
for (const auto &[address, value] : this->m_patches) {
addresses.push_back(address);
values.push_back(value);
}
@ -67,7 +153,7 @@ namespace hex {
return result;
}
wolv::util::Expected<std::vector<u8>, IPSError> generateIPS32Patch(const Patches &patches) {
wolv::util::Expected<std::vector<u8>, IPSError> Patches::toIPS32Patch() const {
std::vector<u8> result;
pushStringBack(result, "IPS32");
@ -75,7 +161,7 @@ namespace hex {
std::vector<u64> addresses;
std::vector<u8> values;
for (const auto &[address, value] : patches) {
for (const auto &[address, value] : this->m_patches) {
addresses.push_back(address);
values.push_back(value);
}
@ -118,7 +204,21 @@ namespace hex {
return result;
}
wolv::util::Expected<Patches, IPSError> loadIPSPatch(const std::vector<u8> &ipsPatch) {
wolv::util::Expected<Patches, IPSError> Patches::fromProvider(hex::prv::Provider* provider) {
PatchesGenerator generator;
generator.getUndoStack().apply(provider->getUndoStack());
if (generator.getActualSize() > 0xFFFF'FFFF)
return wolv::util::Unexpected(IPSError::PatchTooLarge);
auto patches = generator.getPatches();
return Patches(std::move(patches));
}
wolv::util::Expected<Patches, IPSError> Patches::fromIPSPatch(const std::vector<u8> &ipsPatch) {
if (ipsPatch.size() < (5 + 3))
return wolv::util::Unexpected(IPSError::InvalidPatchHeader);
@ -142,7 +242,7 @@ namespace hex {
return wolv::util::Unexpected(IPSError::InvalidPatchFormat);
for (u16 i = 0; i < size; i++)
result[offset + i] = ipsPatch[ipsOffset + i];
result.get()[offset + i] = ipsPatch[ipsOffset + i];
ipsOffset += size;
}
// Handle RLE record
@ -155,7 +255,7 @@ namespace hex {
ipsOffset += 2;
for (u16 i = 0; i < rleSize; i++)
result[offset + i] = ipsPatch[ipsOffset + 0];
result.get()[offset + i] = ipsPatch[ipsOffset + 0];
ipsOffset += 1;
}
@ -171,7 +271,7 @@ namespace hex {
return wolv::util::Unexpected(IPSError::MissingEOF);
}
wolv::util::Expected<Patches, IPSError> loadIPS32Patch(const std::vector<u8> &ipsPatch) {
wolv::util::Expected<Patches, IPSError> Patches::fromIPS32Patch(const std::vector<u8> &ipsPatch) {
if (ipsPatch.size() < (5 + 4))
return wolv::util::Unexpected(IPSError::InvalidPatchHeader);
@ -195,7 +295,7 @@ namespace hex {
return wolv::util::Unexpected(IPSError::InvalidPatchFormat);
for (u16 i = 0; i < size; i++)
result[offset + i] = ipsPatch[ipsOffset + i];
result.get()[offset + i] = ipsPatch[ipsOffset + i];
ipsOffset += size;
}
// Handle RLE record
@ -208,7 +308,7 @@ namespace hex {
ipsOffset += 2;
for (u16 i = 0; i < rleSize; i++)
result[offset + i] = ipsPatch[ipsOffset + 0];
result.get()[offset + i] = ipsPatch[ipsOffset + 0];
ipsOffset += 1;
}

View File

@ -5,7 +5,6 @@
#include <cmath>
#include <cstring>
#include <map>
#include <optional>
#include <hex/helpers/magic.hpp>
@ -20,9 +19,8 @@ namespace hex::prv {
}
Provider::Provider() : m_id(s_idCounter++) {
this->m_patches.emplace_back();
this->m_currPatches = this->m_patches.begin();
Provider::Provider() : m_undoRedoStack(this), m_id(s_idCounter++) {
}
Provider::~Provider() {
@ -39,7 +37,7 @@ namespace hex::prv {
}
void Provider::write(u64 offset, const void *buffer, size_t size) {
this->writeRaw(offset - this->getBaseAddress(), buffer, size);
EventManager::post<EventProviderDataModified>(this, offset, size, static_cast<const u8*>(buffer));
this->markDirty();
}
@ -68,53 +66,29 @@ namespace hex::prv {
file.writeBuffer(buffer.data(), bufferSize);
}
for (auto &[patchAddress, patch] : getPatches()) {
file.seek(patchAddress - this->getBaseAddress());
file.writeBuffer(&patch, 1);
}
EventManager::post<EventProviderSaved>(this);
}
}
void Provider::resize(size_t newSize) {
hex::unused(newSize);
i64 difference = newSize - this->getActualSize();
if (difference > 0)
EventManager::post<EventProviderDataInserted>(this, this->getActualSize(), difference);
else if (difference < 0)
EventManager::post<EventProviderDataRemoved>(this, this->getActualSize(), -difference);
this->markDirty();
}
void Provider::insert(u64 offset, size_t size) {
auto &patches = getPatches();
std::vector<std::pair<u64, u8>> patchesToMove;
for (auto &[address, value] : patches) {
if (address > offset)
patchesToMove.emplace_back(address, value);
}
for (const auto &[address, value] : patchesToMove)
patches.erase(address);
for (const auto &[address, value] : patchesToMove)
patches.insert({ address + size, value });
EventManager::post<EventProviderDataInserted>(this, offset, size);
this->markDirty();
}
void Provider::remove(u64 offset, size_t size) {
auto &patches = getPatches();
std::vector<std::pair<u64, u8>> patchesToMove;
for (auto &[address, value] : patches) {
if (address > offset)
patchesToMove.emplace_back(address, value);
}
for (const auto &[address, value] : patchesToMove)
patches.erase(address);
for (const auto &[address, value] : patchesToMove)
patches.insert({ address - size, value });
EventManager::post<EventProviderDataRemoved>(this, offset, size);
this->markDirty();
}
@ -131,38 +105,6 @@ namespace hex::prv {
}
}
std::map<u64, u8> &Provider::getPatches() {
return *this->m_currPatches;
}
const std::map<u64, u8> &Provider::getPatches() const {
return *this->m_currPatches;
}
void Provider::applyPatches() {
if (!this->isWritable())
return;
this->m_patches.emplace_back();
for (auto &[patchAddress, patch] : getPatches()) {
u8 value = 0x00;
this->readRaw(patchAddress - this->getBaseAddress(), &value, 1);
this->m_patches.back().insert({ patchAddress, value });
}
for (auto &[patchAddress, patch] : getPatches()) {
this->writeRaw(patchAddress - this->getBaseAddress(), &patch, 1);
}
this->markDirty();
this->m_patches.emplace_back();
this->m_currPatches = std::prev(this->m_patches.end());
}
Overlay *Provider::newOverlay() {
return this->m_overlays.emplace_back(std::make_unique<Overlay>()).get();
}
@ -235,54 +177,20 @@ namespace hex::prv {
return { };
}
void Provider::addPatch(u64 offset, const void *buffer, size_t size, bool createUndo) {
if (createUndo) {
// Delete all patches after the current one if a modification is made while
// the current patch list is not at the end of the undo stack
if (std::next(this->m_currPatches) != this->m_patches.end())
this->m_patches.erase(std::next(this->m_currPatches), this->m_patches.end());
createUndoPoint();
}
for (u64 i = 0; i < size; i++) {
u8 patch = static_cast<const u8 *>(buffer)[i];
u8 originalValue = 0x00;
this->readRaw((offset + i) - this->getBaseAddress(), &originalValue, sizeof(u8));
if (patch == originalValue)
getPatches().erase(offset + i);
else
getPatches()[offset + i] = patch;
EventManager::post<EventPatchCreated>(offset, originalValue, patch);
}
this->markDirty();
}
void Provider::createUndoPoint() {
this->m_patches.push_back(getPatches());
this->m_currPatches = std::prev(this->m_patches.end());
}
void Provider::undo() {
if (canUndo())
--this->m_currPatches;
this->m_undoRedoStack.undo();
}
void Provider::redo() {
if (canRedo())
++this->m_currPatches;
this->m_undoRedoStack.redo();
}
bool Provider::canUndo() const {
return this->m_currPatches != this->m_patches.begin();
return this->m_undoRedoStack.canUndo();
}
bool Provider::canRedo() const {
return std::next(this->m_currPatches) != this->m_patches.end();
return this->m_undoRedoStack.canRedo();
}
bool Provider::hasFilePicker() const {
@ -341,14 +249,6 @@ namespace hex::prv {
}
}
for (const auto &[patchAddress, value] : this->m_patches.back()) {
if (!nextRegionAddress.has_value() || patchAddress < nextRegionAddress)
nextRegionAddress = patchAddress;
if (address == patchAddress)
insideValidRegion = true;
}
if (!nextRegionAddress.has_value())
return { Region::Invalid(), false };
else

View File

@ -0,0 +1,131 @@
#include <hex/providers/undo_redo/stack.hpp>
#include <hex/providers/undo_redo/operations/operation_group.hpp>
#include <hex/providers/provider.hpp>
#include <wolv/utils/guards.hpp>
namespace hex::prv::undo {
namespace {
std::atomic_bool s_locked;
std::mutex s_mutex;
}
Stack::Stack(Provider *provider) : m_provider(provider) {
}
void Stack::undo(u32 count) {
std::scoped_lock lock(s_mutex);
s_locked = true;
ON_SCOPE_EXIT { s_locked = false; };
// If there are no operations, we can't undo anything.
if (this->m_undoStack.empty())
return;
for (u32 i = 0; i < count; i += 1) {
// If we reached the start of the list, we can't undo anymore.
if (!this->canUndo()) {
return;
}
// Move last element from the undo stack to the redo stack
this->m_redoStack.emplace_back(std::move(this->m_undoStack.back()));
this->m_redoStack.back()->undo(this->m_provider);
this->m_undoStack.pop_back();
}
}
void Stack::redo(u32 count) {
std::scoped_lock lock(s_mutex);
s_locked = true;
ON_SCOPE_EXIT { s_locked = false; };
// If there are no operations, we can't redo anything.
if (this->m_redoStack.empty())
return;
for (u32 i = 0; i < count; i += 1) {
// If we reached the end of the list, we can't redo anymore.
if (!this->canRedo()) {
return;
}
// Move last element from the undo stack to the redo stack
this->m_undoStack.emplace_back(std::move(this->m_redoStack.back()));
this->m_undoStack.back()->redo(this->m_provider);
this->m_redoStack.pop_back();
}
}
void Stack::groupOperations(u32 count, const std::string &unlocalizedName) {
if (count <= 1)
return;
auto operation = std::make_unique<OperationGroup>(unlocalizedName);
i64 startIndex = std::max<i64>(0, this->m_undoStack.size() - count);
// Move operations from our stack to the group in the same order they were added
for (u32 i = 0; i < count; i += 1) {
i64 index = startIndex + i;
operation->addOperation(std::move(this->m_undoStack[index]));
}
// Remove the empty operations from the stack
this->m_undoStack.resize(startIndex);
this->add(std::move(operation));
}
void Stack::apply(const Stack &otherStack) {
for (const auto &operation : otherStack.m_undoStack) {
this->add(operation->clone());
}
}
bool Stack::add(std::unique_ptr<Operation> &&operation) {
// If we're already inside of an undo/redo operation, ignore new operations being added
if (s_locked)
return false;
s_locked = true;
ON_SCOPE_EXIT { s_locked = false; };
std::scoped_lock lock(s_mutex);
// Clear the redo stack
this->m_redoStack.clear();
// Insert the new operation at the end of the list
this->m_undoStack.emplace_back(std::move(operation));
// Do the operation
this->getLastOperation()->redo(this->m_provider);
return true;
}
bool Stack::canUndo() const {
return !this->m_undoStack.empty();
}
bool Stack::canRedo() const {
return !this->m_redoStack.empty();
}
}

@ -1 +1 @@
Subproject commit 5786fabceeaee4d892f3c7a16b243796244cdddc
Subproject commit 800f58283fbc1f3950abd881357fb44c22f3f44e

View File

@ -57,7 +57,7 @@ namespace hex::plugin::builtin {
}
[[nodiscard]] ImVec2 getMaxSize() const override {
return scaled({ 600, 300 });
return scaled({ 600, 600 });
}
private:

View File

@ -20,12 +20,9 @@ namespace hex::plugin::builtin {
[[nodiscard]] bool isResizable() const override;
[[nodiscard]] bool isSavable() const override;
void read(u64 offset, void *buffer, size_t size, bool overlays) override;
void write(u64 offset, const void *buffer, size_t size) override;
void resize(size_t newSize) override;
void insert(u64 offset, size_t size) override;
void remove(u64 offset, size_t size) override;
void resizeRaw(size_t newSize) override;
void insertRaw(u64 offset, size_t size) override;
void removeRaw(u64 offset, size_t size) override;
void readRaw(u64 offset, void *buffer, size_t size) override;
void writeRaw(u64 offset, const void *buffer, size_t size) override;

View File

@ -24,9 +24,9 @@ namespace hex::plugin::builtin {
void writeRaw(u64 offset, const void *buffer, size_t size) override;
[[nodiscard]] size_t getActualSize() const override { return this->m_data.size(); }
void resize(size_t newSize) override;
void insert(u64 offset, size_t size) override;
void remove(u64 offset, size_t size) override;
void resizeRaw(size_t newSize) override;
void insertRaw(u64 offset, size_t size) override;
void removeRaw(u64 offset, size_t size) override;
void save() override;

View File

@ -0,0 +1,46 @@
#pragma once
#include <hex/providers/undo_redo/operations/operation.hpp>
#include <hex/helpers/fmt.hpp>
namespace hex::plugin::builtin::undo {
class OperationBookmark : public prv::undo::Operation {
public:
explicit OperationBookmark(ImHexApi::Bookmarks::Entry entry) :
m_entry(std::move(entry)) { }
void undo(prv::Provider *provider) override {
hex::unused(provider);
ImHexApi::Bookmarks::remove(this->m_entry.id);
}
void redo(prv::Provider *provider) override {
hex::unused(provider);
auto &[region, name, comment, color, locked, id] = this->m_entry;
id = ImHexApi::Bookmarks::add(region, name, comment, color);
}
[[nodiscard]] std::string format() const override {
return hex::format("Bookmark {} created", this->m_entry.name);
}
std::unique_ptr<Operation> clone() const override {
return std::make_unique<OperationBookmark>(*this);
}
[[nodiscard]] Region getRegion() const override {
return this->m_entry.region;
}
bool shouldHighlight() const override { return false; }
private:
ImHexApi::Bookmarks::Entry m_entry;
};
}

View File

@ -0,0 +1,40 @@
#pragma once
#include <hex/providers/undo_redo/operations/operation.hpp>
#include <hex/helpers/fmt.hpp>
#include <hex/helpers/utils.hpp>
namespace hex::plugin::builtin::undo {
class OperationInsert : public prv::undo::Operation {
public:
OperationInsert(u64 offset, u64 size) :
m_offset(offset), m_size(size) { }
void undo(prv::Provider *provider) override {
provider->removeRaw(this->m_offset, this->m_size);
}
void redo(prv::Provider *provider) override {
provider->insertRaw(this->m_offset, this->m_size);
}
[[nodiscard]] std::string format() const override {
return hex::format("hex.builtin.undo_operation.insert"_lang, hex::toByteString(this->m_size), this->m_offset);
}
std::unique_ptr<Operation> clone() const override {
return std::make_unique<OperationInsert>(*this);
}
[[nodiscard]] Region getRegion() const override {
return { this->m_offset, this->m_size };
}
private:
u64 m_offset;
u64 m_size;
};
}

View File

@ -0,0 +1,48 @@
#pragma once
#include <hex/providers/undo_redo/operations/operation.hpp>
#include <hex/helpers/fmt.hpp>
#include <hex/helpers/utils.hpp>
namespace hex::plugin::builtin::undo {
class OperationRemove : public prv::undo::Operation {
public:
OperationRemove(u64 offset, u64 size) :
m_offset(offset), m_size(size) { }
void undo(prv::Provider *provider) override {
provider->insertRaw(this->m_offset, this->m_size);
provider->writeRaw(this->m_offset, this->m_removedData.data(), this->m_removedData.size());
}
void redo(prv::Provider *provider) override {
this->m_removedData.resize(this->m_size);
provider->readRaw(this->m_offset, this->m_removedData.data(), this->m_removedData.size());
provider->removeRaw(this->m_offset, this->m_size);
}
[[nodiscard]] std::string format() const override {
return hex::format("hex.builtin.undo_operation.remove"_lang, hex::toByteString(this->m_size), this->m_offset);
}
std::unique_ptr<Operation> clone() const override {
return std::make_unique<OperationRemove>(*this);
}
[[nodiscard]] Region getRegion() const override {
return { this->m_offset, this->m_size };
}
bool shouldHighlight() const override { return false; }
private:
u64 m_offset;
u64 m_size;
std::vector<u8> m_removedData;
};
}

View File

@ -0,0 +1,49 @@
#pragma once
#include <hex/helpers/crypto.hpp>
#include <hex/providers/undo_redo/operations/operation.hpp>
#include <hex/helpers/fmt.hpp>
#include <hex/helpers/utils.hpp>
namespace hex::plugin::builtin::undo {
class OperationWrite : public prv::undo::Operation {
public:
OperationWrite(u64 offset, u64 size, const u8 *oldData, const u8 *newData) :
m_offset(offset),
m_oldData(oldData, oldData + size),
m_newData(newData, newData + size) { }
void undo(prv::Provider *provider) override {
provider->writeRaw(this->m_offset, this->m_oldData.data(), this->m_oldData.size());
}
void redo(prv::Provider *provider) override {
provider->writeRaw(this->m_offset, this->m_newData.data(), this->m_newData.size());
}
[[nodiscard]] std::string format() const override {
return hex::format("hex.builtin.undo_operation.write"_lang, hex::toByteString(this->m_newData.size()), this->m_offset);
}
std::vector<std::string> formatContent() const override {
return {
hex::format("{} {} {}", hex::crypt::encode16(this->m_oldData), ICON_VS_ARROW_RIGHT, hex::crypt::encode16(this->m_newData)),
};
}
std::unique_ptr<Operation> clone() const override {
return std::make_unique<OperationWrite>(*this);
}
[[nodiscard]] Region getRegion() const override {
return { this->m_offset, this->m_oldData.size() };
}
private:
u64 m_offset;
std::vector<u8> m_oldData, m_newData;
};
}

View File

@ -53,10 +53,10 @@ namespace hex::plugin::builtin {
[[nodiscard]] bool open() override { return true; }
void close() override { }
void resize(size_t newSize) override {
void resizeRaw(size_t newSize) override {
this->m_size = newSize;
}
void insert(u64 offset, size_t size) override {
void insertRaw(u64 offset, size_t size) override {
if (this->m_provider == nullptr)
return;
@ -64,7 +64,7 @@ namespace hex::plugin::builtin {
this->m_provider->insert(offset + this->m_startAddress, size);
}
void remove(u64 offset, size_t size) override {
void removeRaw(u64 offset, size_t size) override {
if (this->m_provider == nullptr)
return;

View File

@ -23,6 +23,7 @@ namespace hex::plugin::builtin {
std::list<ImHexApi::Bookmarks::Entry>::iterator m_dragStartIterator;
PerProvider<std::list<ImHexApi::Bookmarks::Entry>> m_bookmarks;
PerProvider<u64> m_currBookmarkId;
};
}

View File

@ -16,7 +16,7 @@ namespace hex::plugin::builtin {
private:
u64 m_selectedPatch = 0x00;
PerProvider<u32> m_numPatches;
PerProvider<u32> m_numOperations;
};
}

View File

@ -699,6 +699,12 @@
"hex.builtin.tools.wiki_explain.invalid_response": "Invalid response from Wikipedia!",
"hex.builtin.tools.wiki_explain.results": "Results",
"hex.builtin.tools.wiki_explain.search": "Search",
"hex.builtin.undo_operation.insert": "Inserted {0}",
"hex.builtin.undo_operation.remove": "Removed {0}",
"hex.builtin.undo_operation.write": "Wrote {0}",
"hex.builtin.undo_operation.patches": "Applied patch",
"hex.builtin.undo_operation.fill": "Filled region",
"hex.builtin.undo_operation.modification": "Modified bytes",
"hex.builtin.view.achievements.name": "Achievements",
"hex.builtin.view.achievements.unlocked": "Achievement Unlocked!",
"hex.builtin.view.achievements.unlocked_count": "Unlocked",

View File

@ -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) {

View File

@ -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 {

View File

@ -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();
}

View File

@ -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 {

View File

@ -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++;
}

View File

@ -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");
}

View File

@ -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>();
}
}

View File

@ -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);

View File

@ -51,8 +51,7 @@ TEST_SEQUENCE("TestProvider_write") {
u8 data[1024] = { 0xde, 0xad, 0xbe, 0xef, 0x42, 0x2a, 0x00, 0xff };
std::fill(std::begin(buff), std::end(buff), 22);
provider2->write(1, data, 4);
provider2->applyPatches();
provider2->writeRaw(1, data, 4);
TEST_ASSERT(buff[0] == 22); // should be unchanged
TEST_ASSERT(buff[1] == 0xde);
TEST_ASSERT(buff[2] == 0xad);
@ -61,22 +60,19 @@ TEST_SEQUENCE("TestProvider_write") {
TEST_ASSERT(buff[5] == 22); // should be unchanged
std::fill(std::begin(buff), std::end(buff), 22);
provider2->write(0, data + 6, 2);
provider2->applyPatches();
provider2->writeRaw(0, data + 6, 2);
TEST_ASSERT(buff[0] == 0x00);
TEST_ASSERT(buff[1] == 0xff);
TEST_ASSERT(buff[2] == 22); // should be unchanged
std::fill(std::begin(buff), std::end(buff), 22);
provider2->write(6, data, 2);
provider2->applyPatches();
provider2->writeRaw(6, data, 2);
TEST_ASSERT(buff[5] == 22); // should be unchanged
TEST_ASSERT(buff[6] == 0xde);
TEST_ASSERT(buff[7] == 0xad);
std::fill(std::begin(buff), std::end(buff), 22);
provider2->write(7, data, 2);
provider2->applyPatches();
provider2->writeRaw(7, data, 2);
TEST_ASSERT(std::count(std::begin(buff), std::end(buff), 22) == std::size(buff)); // buff should be unchanged
TEST_SUCCESS();