From ed572ececf376808f771c417376f2da3c491738c Mon Sep 17 00:00:00 2001 From: WerWolv Date: Fri, 27 Nov 2020 09:09:48 +0100 Subject: [PATCH] Added patching system and IPS/IPS32 patch exporting --- CMakeLists.txt | 3 +- include/patches.hpp | 15 +++ include/providers/file_provider.hpp | 3 + include/providers/provider.hpp | 22 +++- include/utils.hpp | 87 ++----------- include/views/view_hexeditor.hpp | 2 +- source/patches.cpp | 116 ++++++++++++++++++ .../{provider => providers}/file_provider.cpp | 27 +++- source/utils.cpp | 68 ++++++++++ source/views/view_hexeditor.cpp | 38 +++++- 10 files changed, 293 insertions(+), 88 deletions(-) create mode 100644 include/patches.hpp create mode 100644 source/patches.cpp rename source/{provider => providers}/file_provider.cpp (73%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 197eb8b2d..699a48cbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ add_executable(ImHex source/window.cpp source/utils.cpp source/crypto.cpp + source/patches.cpp source/lang/preprocessor.cpp source/lang/lexer.cpp @@ -37,7 +38,7 @@ add_executable(ImHex source/lang/validator.cpp source/lang/evaluator.cpp - source/provider/file_provider.cpp + source/providers/file_provider.cpp source/views/view_hexeditor.cpp source/views/view_pattern.cpp diff --git a/include/patches.hpp b/include/patches.hpp new file mode 100644 index 000000000..ea35f357b --- /dev/null +++ b/include/patches.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include + +#include +#include + +namespace hex { + + using Patches = std::map; + + std::vector generateIPSPatch(const Patches &patches); + std::vector generateIPS32Patch(const Patches &patches); + +} \ No newline at end of file diff --git a/include/providers/file_provider.hpp b/include/providers/file_provider.hpp index 08b700d65..877d87773 100644 --- a/include/providers/file_provider.hpp +++ b/include/providers/file_provider.hpp @@ -19,6 +19,9 @@ namespace hex::prv { void read(u64 offset, void *buffer, size_t size) override; void write(u64 offset, void *buffer, size_t size) override; + + void readRaw(u64 offset, void *buffer, size_t size) override; + void writeRaw(u64 offset, void *buffer, size_t size) override; size_t getActualSize() override; std::vector> getDataInformation() override; diff --git a/include/providers/provider.hpp b/include/providers/provider.hpp index 7e8599756..7b4f7d553 100644 --- a/include/providers/provider.hpp +++ b/include/providers/provider.hpp @@ -3,6 +3,8 @@ #include #include +#include +#include #include #include #include @@ -13,17 +15,29 @@ namespace hex::prv { public: constexpr static size_t PageSize = 0x1000'0000; - Provider() = default; + Provider() { + this->m_patches.emplace_back(); + } + virtual ~Provider() = default; virtual bool isAvailable() = 0; virtual bool isReadable() = 0; virtual bool isWritable() = 0; - virtual void read(u64 offset, void *buffer, size_t size) = 0; - virtual void write(u64 offset, void *buffer, size_t size) = 0; + virtual void read(u64 offset, void *buffer, size_t size) { this->readRaw(offset, buffer, size); } + virtual void write(u64 offset, void *buffer, size_t size) { this->writeRaw(offset, buffer, size); } + + virtual void readRaw(u64 offset, void *buffer, size_t size) = 0; + virtual void writeRaw(u64 offset, void *buffer, size_t size) = 0; virtual size_t getActualSize() = 0; + const std::map& getPatches() { return this->m_patches.back(); } + void applyPatches() { + for (auto &[patchAddress, patch] : this->m_patches.back()) + this->writeRaw(patchAddress, &patch, 1); + } + u32 getPageCount() { return std::ceil(this->getActualSize() / double(PageSize)); } u32 getCurrentPage() const { return this->m_currPage; } void setCurrentPage(u32 page) { if (page < getPageCount()) this->m_currPage = page; } @@ -49,6 +63,8 @@ namespace hex::prv { protected: u32 m_currPage = 0; + + std::vector> m_patches; }; } \ No newline at end of file diff --git a/include/utils.hpp b/include/utils.hpp index 37f8246b6..4e80877d0 100644 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -33,59 +33,34 @@ namespace hex { return std::string(buffer.data(), buffer.data() + size); } + [[nodiscard]] constexpr inline u64 extract(u8 from, u8 to, const u64 &value) { + u64 mask = (std::numeric_limits::max() >> (63 - (from - to))) << to; + return (value & mask) >> to; + } + [[nodiscard]] constexpr inline u64 signExtend(u64 value, u8 currWidth, u8 targetWidth) { u64 mask = 1LLU << (currWidth - 1); return (((value ^ mask) - mask) << (64 - targetWidth)) >> (64 - targetWidth); } - constexpr inline bool isUnsigned(const lang::Token::TypeToken::Type type) { + [[nodiscard]] constexpr inline bool isUnsigned(const lang::Token::TypeToken::Type type) { return (static_cast(type) & 0x0F) == 0x00; } - constexpr inline bool isSigned(const lang::Token::TypeToken::Type type) { + [[nodiscard]] constexpr inline bool isSigned(const lang::Token::TypeToken::Type type) { return (static_cast(type) & 0x0F) == 0x01; } - constexpr inline bool isFloatingPoint(const lang::Token::TypeToken::Type type) { + [[nodiscard]] constexpr inline bool isFloatingPoint(const lang::Token::TypeToken::Type type) { return (static_cast(type) & 0x0F) == 0x02; } - constexpr inline u32 getTypeSize(const lang::Token::TypeToken::Type type) { + [[nodiscard]] constexpr inline u32 getTypeSize(const lang::Token::TypeToken::Type type) { return static_cast(type) >> 4; } - inline std::string toByteString(u64 bytes) { - double value = bytes; - u8 unitIndex = 0; - - while (value > 1024) { - value /= 1024; - unitIndex++; - - if (unitIndex == 6) - break; - } - - std::string result = hex::format("%.2f", value); - - switch (unitIndex) { - case 0: result += " Bytes"; break; - case 1: result += " kB"; break; - case 2: result += " MB"; break; - case 3: result += " GB"; break; - case 4: result += " TB"; break; - case 5: result += " PB"; break; - case 6: result += " EB"; break; - default: result = "A lot!"; - } - - return result; - } - - [[nodiscard]] constexpr inline u64 extract(u8 from, u8 to, const u64 &value) { - u64 mask = (std::numeric_limits::max() >> (63 - (from - to))) << to; - return (value & mask) >> to; - } + std::string toByteString(u64 bytes); + std::string makePrintable(char c); template struct always_false : std::false_type {}; @@ -124,46 +99,6 @@ namespace hex { throw std::invalid_argument("Invalid value size!"); } - inline std::string makePrintable(char c) { - switch (c) { - case 0: return "NUL"; - case 1: return "SOH"; - case 2: return "STX"; - case 3: return "ETX"; - case 4: return "EOT"; - case 5: return "ENQ"; - case 6: return "ACK"; - case 7: return "BEL"; - case 8: return "BS"; - case 9: return "TAB"; - case 10: return "LF"; - case 11: return "VT"; - case 12: return "FF"; - case 13: return "CR"; - case 14: return "SO"; - case 15: return "SI"; - case 16: return "DLE"; - case 17: return "DC1"; - case 18: return "DC2"; - case 19: return "DC3"; - case 20: return "DC4"; - case 21: return "NAK"; - case 22: return "SYN"; - case 23: return "ETB"; - case 24: return "CAN"; - case 25: return "EM"; - case 26: return "SUB"; - case 27: return "ESC"; - case 28: return "FS"; - case 29: return "GS"; - case 30: return "RS"; - case 31: return "US"; - case 32: return "Space"; - case 127: return "DEL"; - default: return std::string() + c; - } - } - class ScopeExit { public: ScopeExit(std::function func) : m_func(func) {} diff --git a/include/views/view_hexeditor.hpp b/include/views/view_hexeditor.hpp index 7ed0eef42..2f034e6e2 100644 --- a/include/views/view_hexeditor.hpp +++ b/include/views/view_hexeditor.hpp @@ -45,7 +45,7 @@ namespace hex { s64 m_gotoAddress = 0; - std::vector m_importData; + std::vector m_dataToSave; void drawSearchPopup(); void drawGotoPopup(); diff --git a/source/patches.cpp b/source/patches.cpp new file mode 100644 index 000000000..708bc0a1d --- /dev/null +++ b/source/patches.cpp @@ -0,0 +1,116 @@ +#include "patches.hpp" + +#include +#include +#include +#include + +#include "utils.hpp" + +namespace hex { + + static void pushBytesBack(std::vector &buffer, const char* bytes) { + std::string_view string(bytes); + buffer.resize(buffer.size() + string.length()); + std::memcpy((&buffer.back() - string.length()) + 1, string.begin(), string.length()); + } + + template + static void pushBytesBack(std::vector &buffer, T bytes) { + buffer.resize(buffer.size() + sizeof(T)); + std::memcpy((&buffer.back() - sizeof(T)) + 1, &bytes, sizeof(T)); + } + + std::vector generateIPSPatch(const Patches &patches) { + std::vector result; + + pushBytesBack(result, "PATCH"); + + std::vector addresses; + std::vector values; + + for (const auto &[address, value] : patches) { + addresses.push_back(address); + values.push_back(value); + } + + std::optional startAddress; + std::vector bytes; + for (u32 i = 0; i < addresses.size(); i++) { + if (!startAddress.has_value()) + startAddress = addresses[i]; + + if (i != addresses.size() - 1 && addresses[i] == (addresses[i + 1] - 1)) { + bytes.push_back(values[i]); + } else { + bytes.push_back(values[i]); + + if (bytes.size() > 0xFFFF || startAddress > 0xFF'FFFF) + return { }; + + u32 address = startAddress.value(); + auto addressBytes = reinterpret_cast(&address); + + result.push_back(addressBytes[2]); result.push_back(addressBytes[1]); result.push_back(addressBytes[0]); + pushBytesBack(result, changeEndianess(bytes.size(), std::endian::big)); + + for (auto byte : bytes) + result.push_back(byte); + + bytes.clear(); + startAddress = { }; + } + } + + pushBytesBack(result, "EOF"); + + return result; + } + + std::vector generateIPS32Patch(const Patches &patches) { + std::vector result; + + pushBytesBack(result, "IPS32"); + + std::vector addresses; + std::vector values; + + for (const auto &[address, value] : patches) { + addresses.push_back(address); + values.push_back(value); + } + + std::optional startAddress; + std::vector bytes; + for (u32 i = 0; i < addresses.size(); i++) { + if (!startAddress.has_value()) + startAddress = addresses[i]; + + if (i != addresses.size() - 1 && addresses[i] == (addresses[i + 1] - 1)) { + bytes.push_back(values[i]); + } else { + bytes.push_back(values[i]); + + if (bytes.size() > 0xFFFF || startAddress > 0xFFFF'FFFF) + return { }; + + u32 address = startAddress.value(); + auto addressBytes = reinterpret_cast(&address); + + result.push_back(addressBytes[3]); result.push_back(addressBytes[2]); result.push_back(addressBytes[1]); result.push_back(addressBytes[0]); + pushBytesBack(result, changeEndianess(bytes.size(), std::endian::big)); + + for (auto byte : bytes) + result.push_back(byte); + + bytes.clear(); + startAddress = { }; + } + } + + pushBytesBack(result, "EEOF"); + + return result; + } + +} \ No newline at end of file diff --git a/source/provider/file_provider.cpp b/source/providers/file_provider.cpp similarity index 73% rename from source/provider/file_provider.cpp rename to source/providers/file_provider.cpp index 3020e923c..63f10c834 100644 --- a/source/provider/file_provider.cpp +++ b/source/providers/file_provider.cpp @@ -49,16 +49,39 @@ namespace hex::prv { fseeko64(this->m_file, this->getCurrentPage() * PageSize + offset, SEEK_SET); fread(buffer, 1, size, this->m_file); + + + + for (u64 i = 0; i < size; i++) + if (this->m_patches.back().contains(offset + i)) + reinterpret_cast(buffer)[i] = this->m_patches.back()[offset + i]; } void FileProvider::write(u64 offset, void *buffer, size_t size) { if (buffer == nullptr || size == 0) return; - fseeko64(this->m_file, this->getCurrentPage() * PageSize + offset, SEEK_SET); - fwrite(buffer, 1, size, this->m_file); + this->m_patches.push_back(this->m_patches.back()); + + for (u64 i = 0; i < size; i++) + this->m_patches.back()[offset + i] = reinterpret_cast(buffer)[i]; } + void FileProvider::readRaw(u64 offset, void *buffer, size_t size) { + if ((offset + size) > this->getSize() || buffer == nullptr || size == 0) + return; + + fseeko64(this->m_file, this->getCurrentPage() * PageSize + offset, SEEK_SET); + fread(buffer, 1, size, this->m_file); + } + + void FileProvider::writeRaw(u64 offset, void *buffer, size_t size) { + if (buffer == nullptr || size == 0) + return; + + fseeko64(this->m_file, offset, SEEK_SET); + fwrite(&buffer, 1, size, this->m_file); + } size_t FileProvider::getActualSize() { fseeko64(this->m_file, 0, SEEK_END); return ftello64(this->m_file); diff --git a/source/utils.cpp b/source/utils.cpp index 26a073298..83468946b 100644 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -5,4 +5,72 @@ namespace hex { + std::string toByteString(u64 bytes) { + double value = bytes; + u8 unitIndex = 0; + + while (value > 1024) { + value /= 1024; + unitIndex++; + + if (unitIndex == 6) + break; + } + + std::string result = hex::format("%.2f", value); + + switch (unitIndex) { + case 0: result += " Bytes"; break; + case 1: result += " kB"; break; + case 2: result += " MB"; break; + case 3: result += " GB"; break; + case 4: result += " TB"; break; + case 5: result += " PB"; break; + case 6: result += " EB"; break; + default: result = "A lot!"; + } + + return result; + } + + std::string makePrintable(char c) { + switch (c) { + case 0: return "NUL"; + case 1: return "SOH"; + case 2: return "STX"; + case 3: return "ETX"; + case 4: return "EOT"; + case 5: return "ENQ"; + case 6: return "ACK"; + case 7: return "BEL"; + case 8: return "BS"; + case 9: return "TAB"; + case 10: return "LF"; + case 11: return "VT"; + case 12: return "FF"; + case 13: return "CR"; + case 14: return "SO"; + case 15: return "SI"; + case 16: return "DLE"; + case 17: return "DC1"; + case 18: return "DC2"; + case 19: return "DC3"; + case 20: return "DC4"; + case 21: return "NAK"; + case 22: return "SYN"; + case 23: return "ETB"; + case 24: return "CAN"; + case 25: return "EM"; + case 26: return "SUB"; + case 27: return "ESC"; + case 28: return "FS"; + case 29: return "GS"; + case 30: return "RS"; + case 31: return "US"; + case 32: return "Space"; + case 127: return "DEL"; + default: return std::string() + c; + } + } + } \ No newline at end of file diff --git a/source/views/view_hexeditor.cpp b/source/views/view_hexeditor.cpp index 7af311f9c..ae36e80bb 100644 --- a/source/views/view_hexeditor.cpp +++ b/source/views/view_hexeditor.cpp @@ -6,6 +6,7 @@ #include #include "crypto.hpp" +#include "patches.hpp" #undef __STRICT_ANSI__ #include @@ -127,15 +128,14 @@ namespace hex { this->loadFromFile(this->m_fileBrowser.selected_path, base64); if (!base64.empty()) { - this->m_importData = decode64(base64); - ImGui::OpenPopup("Save File"); + this->m_dataToSave = decode64(base64); + ImGui::OpenPopup("Save Data"); } } - if (this->m_fileBrowser.showFileDialog("Save File", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE)) { - this->saveToFile(this->m_fileBrowser.selected_path, this->m_importData); - this->openFile(this->m_fileBrowser.selected_path); + if (this->m_fileBrowser.showFileDialog("Save Data", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE)) { + this->saveToFile(this->m_fileBrowser.selected_path, this->m_dataToSave); } } @@ -155,6 +155,33 @@ namespace hex { ImGui::EndMenu(); } + if (ImGui::BeginMenu("Export...")) { + if (ImGui::MenuItem("IPS Patch")) { + Patches patches = this->m_dataProvider->getPatches(); + if (!patches.contains(0x00454F45) && patches.contains(0x00454F46)) { + u8 value = 0; + this->m_dataProvider->read(0x00454F45, &value, sizeof(u8)); + patches[0x00454F45] = value; + } + + this->m_dataToSave = generateIPSPatch(patches); + View::doLater([]{ ImGui::OpenPopup("Save Data"); }); + } + if (ImGui::MenuItem("IPS32 Patch")) { + Patches patches = this->m_dataProvider->getPatches(); + if (!patches.contains(0x00454F45) && patches.contains(0x45454F46)) { + u8 value = 0; + this->m_dataProvider->read(0x45454F45, &value, sizeof(u8)); + patches[0x45454F45] = value; + } + + this->m_dataToSave = generateIPS32Patch(patches); + View::doLater([]{ ImGui::OpenPopup("Save Data"); }); + } + + ImGui::EndMenu(); + } + ImGui::Separator(); if (ImGui::MenuItem("Search", "CTRL + F")) { @@ -234,6 +261,7 @@ namespace hex { delete this->m_dataProvider; this->m_dataProvider = new prv::FileProvider(path); + this->m_memoryEditor.ReadOnly = !this->m_dataProvider->isWritable(); View::postEvent(Events::DataChanged); }