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:
parent
e5f36ca08d
commit
7e660450ed
2
lib/external/libwolv
vendored
2
lib/external/libwolv
vendored
@ -1 +1 @@
|
||||
Subproject commit 7e4a95cb4a909d80c0bd5777bf89325f240f9d54
|
||||
Subproject commit 86faee9f3e0ebdf7542c51fb5233134c376e4d3d
|
2
lib/external/pattern_language
vendored
2
lib/external/pattern_language
vendored
@ -1 +1 @@
|
||||
Subproject commit 914d5357624e63b5930a46ffb7334fa82bea6dd8
|
||||
Subproject commit ae180a117aa4513285ca6ef6072ac7de18447e61
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
@ -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;
|
||||
|
@ -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; }
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
57
lib/libimhex/include/hex/providers/undo_redo/stack.hpp
Normal file
57
lib/libimhex/include/hex/providers/undo_redo/stack.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
@ -141,6 +141,7 @@ namespace hex {
|
||||
|
||||
void draw() final {
|
||||
if (this->shouldDraw()) {
|
||||
ImGui::SetNextWindowSizeConstraints(this->getMinSize(), this->getMaxSize());
|
||||
this->drawContent();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
131
lib/libimhex/source/providers/undo/stack.cpp
Normal file
131
lib/libimhex/source/providers/undo/stack.cpp
Normal 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
2
lib/third_party/nativefiledialog
vendored
2
lib/third_party/nativefiledialog
vendored
@ -1 +1 @@
|
||||
Subproject commit 5786fabceeaee4d892f3c7a16b243796244cdddc
|
||||
Subproject commit 800f58283fbc1f3950abd881357fb44c22f3f44e
|
@ -57,7 +57,7 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
[[nodiscard]] ImVec2 getMaxSize() const override {
|
||||
return scaled({ 600, 300 });
|
||||
return scaled({ 600, 600 });
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
@ -16,7 +16,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
private:
|
||||
u64 m_selectedPatch = 0x00;
|
||||
PerProvider<u32> m_numPatches;
|
||||
PerProvider<u32> m_numOperations;
|
||||
};
|
||||
|
||||
}
|
@ -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",
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user