From 4878f70e586179a09070d44175f9c711a41c4b5c Mon Sep 17 00:00:00 2001 From: WerWolv Date: Mon, 30 Nov 2020 00:03:12 +0100 Subject: [PATCH] Added project files --- CMakeLists.txt | 6 +- include/helpers/event.hpp | 6 +- include/helpers/project_file_handler.hpp | 46 +++++++++++++ include/helpers/utils.hpp | 7 ++ include/views/view_bookmarks.hpp | 7 -- source/helpers/project_file_handler.cpp | 85 ++++++++++++++++++++++++ source/providers/file_provider.cpp | 4 ++ source/views/view_bookmarks.cpp | 15 ++++- source/views/view_hexeditor.cpp | 69 ++++++++++++++++++- source/views/view_patches.cpp | 13 +++- source/views/view_pattern.cpp | 20 +++++- source/window.cpp | 16 +++-- 12 files changed, 274 insertions(+), 20 deletions(-) create mode 100644 include/helpers/project_file_handler.hpp create mode 100644 source/helpers/project_file_handler.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 147d26a67..f4f20aea6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ pkg_search_module(CRYPTO REQUIRED libcrypto) pkg_search_module(CAPSTONE REQUIRED capstone) find_package(OpenGL REQUIRED) find_package(LLVM REQUIRED CONFIG) +find_package(nlohmann_json REQUIRED) llvm_map_components_to_libnames(demangler) @@ -32,6 +33,7 @@ add_executable(ImHex source/helpers/crypto.cpp source/helpers/patches.cpp source/helpers/math_evaluator.cpp + source/helpers/project_file_handler.cpp source/lang/preprocessor.cpp source/lang/lexer.cpp @@ -69,9 +71,9 @@ add_executable(ImHex ) if (WIN32) - target_link_libraries(ImHex libglfw3.a libgcc.a libstdc++.a libmagic.a libgnurx.a libtre.a libintl.a libiconv.a shlwapi.lib libcrypto.a libwinpthread.a libcapstone.a libLLVMDemangle.a) + target_link_libraries(ImHex libglfw3.a libgcc.a libstdc++.a libmagic.a libgnurx.a libtre.a libintl.a libiconv.a shlwapi.lib libcrypto.a libwinpthread.a libcapstone.a libLLVMDemangle.a nlohmann_json::nlohmann_json) endif (WIN32) if (UNIX) - target_link_libraries(ImHex libglfw.so libmagic.so libcrypto.so libdl.so libcapstone.so libLLVMDemangle.so) + target_link_libraries(ImHex libglfw.so libmagic.so libcrypto.so libdl.so libcapstone.so libLLVMDemangle.so nlohmann_json::nlohmann_json) endif (UNIX) \ No newline at end of file diff --git a/include/helpers/event.hpp b/include/helpers/event.hpp index a4a6dc69f..d1cdb95f1 100644 --- a/include/helpers/event.hpp +++ b/include/helpers/event.hpp @@ -10,11 +10,15 @@ namespace hex { DataChanged, PatternChanged, FileDropped, + WindowClosing, RegionSelected, SelectionChangeRequest, - AddBookmark + AddBookmark, + + ProjectFileStore, + ProjectFileLoad }; struct EventHandler { diff --git a/include/helpers/project_file_handler.hpp b/include/helpers/project_file_handler.hpp new file mode 100644 index 000000000..88da24116 --- /dev/null +++ b/include/helpers/project_file_handler.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +#include "patches.hpp" +#include "utils.hpp" + +namespace hex { + + class ProjectFile { + public: + ProjectFile() = delete; + + static bool load(std::string_view filePath); + static bool store(std::string_view filePath = ""); + + [[nodiscard]] static bool hasUnsavedChanges() { return ProjectFile::s_hasUnsavedChanged; } + static void markDirty() { if (!ProjectFile::s_currProjectFilePath.empty()) ProjectFile::s_hasUnsavedChanged = true; } + + [[nodiscard]] static std::string getProjectFilePath() { return ProjectFile::s_currProjectFilePath; } + + [[nodiscard]] static std::string getFilePath() { return ProjectFile::s_filePath; } + static void setFilePath(std::string_view filePath) { ProjectFile::s_hasUnsavedChanged = true; ProjectFile::s_filePath = filePath; } + + [[nodiscard]] static std::string getPattern() { return ProjectFile::s_pattern; } + static void setPattern(std::string_view pattern) { ProjectFile::s_hasUnsavedChanged = true; ProjectFile::s_pattern = pattern; } + + [[nodiscard]] static const Patches& getPatches() { return ProjectFile::s_patches; } + static void setPatches(const Patches &patches) { ProjectFile::s_hasUnsavedChanged = true; ProjectFile::s_patches = patches; } + + [[nodiscard]] static const std::list& getBookmarks() { return ProjectFile::s_bookmarks; } + static void setBookmarks(const std::list &bookmarks) { ProjectFile::s_hasUnsavedChanged = true; ProjectFile::s_bookmarks = bookmarks; } + + private: + static inline std::string s_currProjectFilePath; + static inline bool s_hasUnsavedChanged = false; + + static inline std::string s_filePath; + static inline std::string s_pattern; + static inline Patches s_patches; + static inline std::list s_bookmarks; + }; + +} \ No newline at end of file diff --git a/include/helpers/utils.hpp b/include/helpers/utils.hpp index cd2aba949..5df2cc35f 100644 --- a/include/helpers/utils.hpp +++ b/include/helpers/utils.hpp @@ -118,4 +118,11 @@ namespace hex { u64 address; size_t size; }; + + struct Bookmark { + Region region; + + std::vector name; + std::vector comment; + }; } \ No newline at end of file diff --git a/include/views/view_bookmarks.hpp b/include/views/view_bookmarks.hpp index c1ac6864b..34d9a0ee1 100644 --- a/include/views/view_bookmarks.hpp +++ b/include/views/view_bookmarks.hpp @@ -11,13 +11,6 @@ namespace hex { namespace prv { class Provider; } - struct Bookmark { - Region region; - - std::vector name; - std::vector comment; - }; - class ViewBookmarks : public View { public: explicit ViewBookmarks(prv::Provider* &dataProvider); diff --git a/source/helpers/project_file_handler.cpp b/source/helpers/project_file_handler.cpp new file mode 100644 index 000000000..6aa6afced --- /dev/null +++ b/source/helpers/project_file_handler.cpp @@ -0,0 +1,85 @@ +#include "helpers/project_file_handler.hpp" + +#include +#include + +using json = nlohmann::json; + +namespace hex { + + void to_json(json& j, const hex::Bookmark& b) { + j = json{ { "address", b.region.address }, { "size", b.region.size }, { "name", b.name.data() }, { "comment", b.comment.data() } }; + } + + void from_json(const json& j, hex::Bookmark& b) { + std::string name, comment; + + j.at("address").get_to(b.region.address); + j.at("size").get_to(b.region.size); + j.at("name").get_to(name); + j.at("comment").get_to(comment); + + std::copy(name.begin(), name.end(), std::back_inserter(b.name)); + std::copy(comment.begin(), comment.end(), std::back_inserter(b.comment)); + } + + + bool ProjectFile::load(std::string_view filePath) { + ProjectFile::s_hasUnsavedChanged = false; + + json projectFileData; + + try { + std::ifstream projectFile(filePath.data()); + projectFile >> projectFileData; + + ProjectFile::s_filePath = projectFileData["filePath"]; + ProjectFile::s_pattern = projectFileData["pattern"]; + ProjectFile::s_patches = projectFileData["patches"].get(); + + for (auto &element : projectFileData["bookmarks"].items()) { + ProjectFile::s_bookmarks.push_back(element.value().get()); + } + + } catch (json::exception &e) { + return false; + } catch (std::ofstream::failure &e) { + return false; + } + + ProjectFile::s_currProjectFilePath = filePath; + + return true; + } + + bool ProjectFile::store(std::string_view filePath) { + ProjectFile::s_hasUnsavedChanged = false; + + json projectFileData; + + if (filePath.empty()) + filePath = ProjectFile::s_currProjectFilePath; + + try { + projectFileData["filePath"] = ProjectFile::s_filePath; + projectFileData["pattern"] = ProjectFile::s_pattern; + projectFileData["patches"] = ProjectFile::s_patches; + + for (auto &bookmark : ProjectFile::s_bookmarks) { + projectFileData["bookmarks"].push_back(bookmark); + } + + std::ofstream projectFile(filePath.data(), std::fstream::trunc); + projectFile << projectFileData; + } catch (json::exception &e) { + return false; + } catch (std::ifstream::failure &e) { + return false; + } + + ProjectFile::s_currProjectFilePath = filePath; + + return true; + } + +} \ No newline at end of file diff --git a/source/providers/file_provider.cpp b/source/providers/file_provider.cpp index 2e5839c9e..a6deb2a0d 100644 --- a/source/providers/file_provider.cpp +++ b/source/providers/file_provider.cpp @@ -7,6 +7,7 @@ #include #include "helpers/utils.hpp" +#include "helpers/project_file_handler.hpp" namespace hex::prv { @@ -22,6 +23,9 @@ namespace hex::prv { this->m_file = fopen(path.data(), "rb"); this->m_writable = false; } + + if (this->m_file != nullptr) + ProjectFile::setFilePath(path); } FileProvider::~FileProvider() { diff --git a/source/views/view_bookmarks.cpp b/source/views/view_bookmarks.cpp index ca98f714e..030c944a3 100644 --- a/source/views/view_bookmarks.cpp +++ b/source/views/view_bookmarks.cpp @@ -1,6 +1,7 @@ #include "views/view_bookmarks.hpp" #include "providers/provider.hpp" +#include "helpers/project_file_handler.hpp" #include @@ -19,11 +20,21 @@ namespace hex { std::strcpy(bookmark.name.data(), ("Bookmark " + std::to_string(this->m_bookmarks.size() + 1)).c_str()); this->m_bookmarks.push_back(bookmark); + ProjectFile::markDirty(); + }); + + View::subscribeEvent(Events::ProjectFileLoad, [this](const void*) { + this->m_bookmarks = ProjectFile::getBookmarks(); + }); + View::subscribeEvent(Events::ProjectFileStore, [this](const void*) { + ProjectFile::setBookmarks(this->m_bookmarks); }); } ViewBookmarks::~ViewBookmarks() { View::unsubscribeEvent(Events::AddBookmark); + View::unsubscribeEvent(Events::ProjectFileLoad); + View::unsubscribeEvent(Events::ProjectFileStore); } void ViewBookmarks::createView() { @@ -83,8 +94,10 @@ namespace hex { } } - if (bookmarkToRemove != this->m_bookmarks.end()) + if (bookmarkToRemove != this->m_bookmarks.end()) { this->m_bookmarks.erase(bookmarkToRemove); + ProjectFile::markDirty(); + } ImGui::EndChild(); } diff --git a/source/views/view_hexeditor.cpp b/source/views/view_hexeditor.cpp index 1c3c36a32..a30317e51 100644 --- a/source/views/view_hexeditor.cpp +++ b/source/views/view_hexeditor.cpp @@ -7,6 +7,7 @@ #include "helpers/crypto.hpp" #include "helpers/patches.hpp" +#include "helpers/project_file_handler.hpp" #undef __STRICT_ANSI__ #include @@ -36,6 +37,7 @@ namespace hex { _this->m_dataProvider->write(off, &d, sizeof(ImU8)); _this->postEvent(Events::DataChanged); + ProjectFile::markDirty(); }; this->m_memoryEditor.HighlightFn = [](const ImU8 *data, size_t off, bool next) -> bool { @@ -76,6 +78,19 @@ namespace hex { this->m_memoryEditor.DataPreviewAddrEnd = region.address + region.size - 1; View::postEvent(Events::RegionSelected, ®ion); }); + + View::subscribeEvent(Events::ProjectFileLoad, [this](const void *userData) { + this->openFile(ProjectFile::getFilePath()); + }); + + View::subscribeEvent(Events::WindowClosing, [this](const void *userData) { + auto window = const_cast(static_cast(userData)); + + if (ProjectFile::hasUnsavedChanges()) { + glfwSetWindowShouldClose(window, GLFW_FALSE); + View::doLater([] { ImGui::OpenPopup("Save Changes"); }); + } + }); } ViewHexEditor::~ViewHexEditor() { @@ -118,6 +133,24 @@ namespace hex { } + if (ImGui::BeginPopupModal("Save Changes", nullptr, ImGuiWindowFlags_NoResize)) { + constexpr auto Message = "You have unsaved changes made to your Project. Are you sure you want to exit?"; + ImGui::NewLine(); + if (ImGui::BeginChild("##scrolling", ImVec2(300, 50))) { + ImGui::SetCursorPosX(10); + ImGui::TextWrapped("%s", Message); + ImGui::EndChild(); + } + ImGui::SetCursorPosX(40); + if (ImGui::Button("Yes", ImVec2(100, 20))) + std::exit(0); + ImGui::SameLine(); + ImGui::SetCursorPosX(160); + if (ImGui::Button("No", ImVec2(100, 20))) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + if (this->m_fileBrowser.showFileDialog("Open File", imgui_addons::ImGuiFileBrowser::DialogMode::OPEN)) { this->openFile(this->m_fileBrowser.selected_path); @@ -138,6 +171,17 @@ namespace hex { } + + if (this->m_fileBrowser.showFileDialog("Open Project", imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, ImVec2(0, 0), ".hexproj")) { + ProjectFile::load(this->m_fileBrowser.selected_path); + View::postEvent(Events::ProjectFileLoad); + } + + if (this->m_fileBrowser.showFileDialog("Save Project", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE, ImVec2(0, 0), ".hexproj")) { + ProjectFile::store(this->m_fileBrowser.selected_path); + } + + if (this->m_fileBrowser.showFileDialog("Export File", imgui_addons::ImGuiFileBrowser::DialogMode::SAVE)) { this->saveToFile(this->m_fileBrowser.selected_path, this->m_dataToSave); } @@ -199,6 +243,24 @@ namespace hex { View::doLater([]{ ImGui::OpenPopup("Save As"); }); } + ImGui::Separator(); + + if (ImGui::MenuItem("Open Project", "")) { + this->getWindowOpenState() = true; + View::doLater([]{ ImGui::OpenPopup("Open Project"); }); + } + + if (ImGui::MenuItem("Save Project", "", false, this->m_dataProvider != nullptr && this->m_dataProvider->isWritable())) { + View::postEvent(Events::ProjectFileStore); + + if (ProjectFile::getProjectFilePath() == "") + View::doLater([] { ImGui::OpenPopup("Save Project"); }); + else + ProjectFile::store(); + } + + ImGui::Separator(); + if (ImGui::BeginMenu("Import...")) { if (ImGui::MenuItem("Base64 File")) { this->getWindowOpenState() = true; @@ -315,7 +377,7 @@ namespace hex { for (const auto &[address, value] : this->m_dataProvider->getPatches()) this->m_dataProvider->writeRaw(address, &value, sizeof(u8)); return true; - } else if (mods == GLFW_MOD_CONTROL | GLFW_MOD_SHIFT && key == GLFW_KEY_S) { + } else if (mods == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT) && key == GLFW_KEY_S) { ImGui::OpenPopup("Save As"); return true; } else if (mods == GLFW_MOD_CONTROL && key == GLFW_KEY_F) { @@ -345,8 +407,13 @@ namespace hex { this->m_dataProvider = new prv::FileProvider(path); this->m_memoryEditor.ReadOnly = !this->m_dataProvider->isWritable(); + + if (this->m_dataProvider->isAvailable()) + ProjectFile::setFilePath(path); + View::postEvent(Events::FileLoaded); View::postEvent(Events::DataChanged); + ProjectFile::markDirty(); } bool ViewHexEditor::saveToFile(std::string path, const std::vector& data) { diff --git a/source/views/view_patches.cpp b/source/views/view_patches.cpp index 510736f1d..754e9ae72 100644 --- a/source/views/view_patches.cpp +++ b/source/views/view_patches.cpp @@ -3,6 +3,7 @@ #include "providers/provider.hpp" #include "helpers/utils.hpp" +#include "helpers/project_file_handler.hpp" #include @@ -11,11 +12,20 @@ using namespace std::literals::string_literals; namespace hex { ViewPatches::ViewPatches(prv::Provider* &dataProvider) : View("Patches"), m_dataProvider(dataProvider) { + View::subscribeEvent(Events::ProjectFileStore, [this](const void*) { + if (this->m_dataProvider != nullptr) + ProjectFile::setPatches(this->m_dataProvider->getPatches()); + }); + View::subscribeEvent(Events::ProjectFileLoad, [this](const void*) { + if (this->m_dataProvider != nullptr) + this->m_dataProvider->getPatches() = ProjectFile::getPatches(); + }); } ViewPatches::~ViewPatches() { - + View::unsubscribeEvent(Events::ProjectFileStore); + View::unsubscribeEvent(Events::ProjectFileLoad); } void ViewPatches::createView() { @@ -62,6 +72,7 @@ namespace hex { if (ImGui::BeginPopup("PatchContextMenu")) { if (ImGui::MenuItem("Remove")) { patches.erase(this->m_selectedPatch); + ProjectFile::markDirty(); } ImGui::EndPopup(); } diff --git a/source/views/view_pattern.cpp b/source/views/view_pattern.cpp index acf73b612..d9b9bbe50 100644 --- a/source/views/view_pattern.cpp +++ b/source/views/view_pattern.cpp @@ -5,6 +5,8 @@ #include "lang/lexer.hpp" #include "lang/validator.hpp" #include "lang/evaluator.hpp" + +#include "helpers/project_file_handler.hpp" #include "helpers/utils.hpp" #include @@ -76,9 +78,20 @@ namespace hex { this->m_textEditor.SetLanguageDefinition(PatternLanguage()); this->m_textEditor.SetShowWhitespaces(false); - View::subscribeEvent(Events::FileLoaded, [this](const void* userData) { - lang::Preprocessor preprocessor; + View::subscribeEvent(Events::ProjectFileStore, [this](const void*) { + ProjectFile::setPattern(this->m_textEditor.GetText()); + }); + View::subscribeEvent(Events::ProjectFileLoad, [this](const void*) { + this->m_textEditor.SetText(ProjectFile::getPattern()); + this->parsePattern(this->m_textEditor.GetText().data()); + }); + + View::subscribeEvent(Events::FileLoaded, [this](const void* userData) { + if (!this->m_textEditor.GetText().empty()) + return; + + lang::Preprocessor preprocessor; std::string magicFiles; std::error_code error; @@ -140,7 +153,8 @@ namespace hex { } ViewPattern::~ViewPattern() { - + View::unsubscribeEvent(Events::ProjectFileStore); + View::unsubscribeEvent(Events::ProjectFileLoad); } void ViewPattern::createMenu() { diff --git a/source/window.cpp b/source/window.cpp index a57168ba8..35a08a140 100644 --- a/source/window.cpp +++ b/source/window.cpp @@ -107,15 +107,17 @@ namespace hex { ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); - ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking; ImGuiViewport* viewport = ImGui::GetMainViewport(); ImGui::SetNextWindowPos(viewport->GetWorkPos()); ImGui::SetNextWindowSize(viewport->GetWorkSize()); ImGui::SetNextWindowViewport(viewport->ID); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); - windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize; - windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus; + + ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking + | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse + | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBringToFrontOnFocus; if (ImGui::Begin("DockSpace", nullptr, windowFlags)) { ImGui::PopStyleVar(2); @@ -208,6 +210,7 @@ namespace hex { glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + this->m_window = glfwCreateWindow(1280, 720, "ImHex", nullptr, nullptr); @@ -229,7 +232,12 @@ namespace hex { View::postEvent(Events::FileDropped, paths[0]); }); - glfwSetWindowSizeLimits(this->m_window, 720, 480, GLFW_DONT_CARE, GLFW_DONT_CARE); + glfwSetWindowCloseCallback(this->m_window, [](GLFWwindow *window) { + View::postEvent(Events::WindowClosing, window); + }); + + + glfwSetWindowSizeLimits(this->m_window, 720, 480, GLFW_DONT_CARE, GLFW_DONT_CARE); if (gladLoadGL() == 0) throw std::runtime_error("Failed to initialize OpenGL loader!");