mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-01-24 21:49:21 -06:00
sys: Completely revamped main menu item system
This commit is contained in:
@@ -277,9 +277,11 @@ namespace hex {
|
||||
|
||||
namespace impl {
|
||||
|
||||
using DrawCallback = std::function<void()>;
|
||||
using LayoutFunction = std::function<void(u32)>;
|
||||
using ClickCallback = std::function<void()>;
|
||||
using DrawCallback = std::function<void()>;
|
||||
using MenuCallback = std::function<void()>;
|
||||
using EnabledCallback = std::function<bool()>;
|
||||
using LayoutFunction = std::function<void(u32)>;
|
||||
using ClickCallback = std::function<void()>;
|
||||
|
||||
struct Layout {
|
||||
std::string unlocalizedName;
|
||||
@@ -291,8 +293,10 @@ namespace hex {
|
||||
};
|
||||
|
||||
struct MenuItem {
|
||||
std::string unlocalizedName;
|
||||
DrawCallback callback;
|
||||
std::vector<std::string> unlocalizedNames;
|
||||
Shortcut shortcut;
|
||||
MenuCallback callback;
|
||||
EnabledCallback enabledCallback;
|
||||
};
|
||||
|
||||
struct SidebarItem {
|
||||
@@ -306,10 +310,16 @@ namespace hex {
|
||||
ClickCallback callback;
|
||||
};
|
||||
|
||||
constexpr static auto SeparatorValue = "$SEPARATOR$";
|
||||
constexpr static auto SubMenuValue = "$SUBMENU$";
|
||||
|
||||
}
|
||||
|
||||
void registerMainMenuItem(const std::string &unlocalizedName, u32 priority);
|
||||
void addMenuItem(const std::string &unlocalizedMainMenuName, u32 priority, const impl::DrawCallback &function);
|
||||
void addMenuItem(const std::string &unlocalizedMainMenuNames, u32 priority, const impl::DrawCallback &function);
|
||||
void addMenuItem(const std::vector<std::string> &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback = []{ return true; });
|
||||
void addMenuItemSubMenu(std::vector<std::string> unlocalizedMainMenuNames, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback = []{ return true; });
|
||||
void addMenuItemSeparator(std::vector<std::string> unlocalizedMainMenuNames, u32 priority);
|
||||
|
||||
void addWelcomeScreenEntry(const impl::DrawCallback &function);
|
||||
void addFooterItem(const impl::DrawCallback &function);
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
struct ImGuiWindow;
|
||||
|
||||
@@ -136,15 +137,30 @@ namespace hex {
|
||||
|
||||
auto operator<=>(const Key &) const = default;
|
||||
|
||||
[[nodiscard]] constexpr u32 getKeyCode() const { return this->m_key; }
|
||||
private:
|
||||
u32 m_key;
|
||||
};
|
||||
|
||||
|
||||
constexpr static auto CTRL = Key(static_cast<Keys>(0x1000'0000));
|
||||
constexpr static auto ALT = Key(static_cast<Keys>(0x2000'0000));
|
||||
constexpr static auto SHIFT = Key(static_cast<Keys>(0x4000'0000));
|
||||
constexpr static auto SUPER = Key(static_cast<Keys>(0x8000'0000));
|
||||
|
||||
#if defined (OS_MACOS)
|
||||
constexpr static auto CTRLCMD = SUPER;
|
||||
#else
|
||||
constexpr static auto CTRLCMD = CTRL;
|
||||
#endif
|
||||
|
||||
class Shortcut {
|
||||
public:
|
||||
Shortcut() = default;
|
||||
Shortcut(Keys key) : m_keys({ key }) { }
|
||||
|
||||
const static inline auto None = Keys(0);
|
||||
|
||||
Shortcut operator+(const Key &other) const {
|
||||
Shortcut result = *this;
|
||||
result.m_keys.insert(other);
|
||||
@@ -166,6 +182,168 @@ namespace hex {
|
||||
return this->m_keys == other.m_keys;
|
||||
}
|
||||
|
||||
std::string toString() const {
|
||||
std::string result;
|
||||
|
||||
#if defined(OS_MACOS)
|
||||
constexpr static auto CTRL_NAME = "CTRL";
|
||||
constexpr static auto ALT_NAME = "OPT";
|
||||
constexpr static auto SHIFT_NAME = "SHIFT";
|
||||
constexpr static auto SUPER_NAME = "CMD";
|
||||
#else
|
||||
constexpr static auto CTRL_NAME = "CTRL";
|
||||
constexpr static auto ALT_NAME = "ALT";
|
||||
constexpr static auto SHIFT_NAME = "SHIFT";
|
||||
constexpr static auto SUPER_NAME = "SUPER";
|
||||
#endif
|
||||
|
||||
constexpr static auto Concatination = " + ";
|
||||
|
||||
auto keys = this->m_keys;
|
||||
if (keys.erase(CTRL) > 0) {
|
||||
result += CTRL_NAME;
|
||||
result += Concatination;
|
||||
}
|
||||
if (keys.erase(ALT) > 0) {
|
||||
result += ALT_NAME;
|
||||
result += Concatination;
|
||||
}
|
||||
if (keys.erase(SHIFT) > 0) {
|
||||
result += SHIFT_NAME;
|
||||
result += Concatination;
|
||||
}
|
||||
if (keys.erase(SUPER) > 0) {
|
||||
result += SUPER_NAME;
|
||||
result += Concatination;
|
||||
}
|
||||
|
||||
for (const auto &key : keys) {
|
||||
switch (Keys(key.getKeyCode())) {
|
||||
case Keys::Space: result += "SPACE"; break;
|
||||
case Keys::Apostrophe: result += "'"; break;
|
||||
case Keys::Comma: result += ","; break;
|
||||
case Keys::Minus: result += "-"; break;
|
||||
case Keys::Period: result += "."; break;
|
||||
case Keys::Slash: result += "/"; break;
|
||||
case Keys::Num0: result += "0"; break;
|
||||
case Keys::Num1: result += "1"; break;
|
||||
case Keys::Num2: result += "2"; break;
|
||||
case Keys::Num3: result += "3"; break;
|
||||
case Keys::Num4: result += "4"; break;
|
||||
case Keys::Num5: result += "5"; break;
|
||||
case Keys::Num6: result += "6"; break;
|
||||
case Keys::Num7: result += "7"; break;
|
||||
case Keys::Num8: result += "8"; break;
|
||||
case Keys::Num9: result += "9"; break;
|
||||
case Keys::Semicolon: result += ";"; break;
|
||||
case Keys::Equals: result += "="; break;
|
||||
case Keys::A: result += "A"; break;
|
||||
case Keys::B: result += "B"; break;
|
||||
case Keys::C: result += "C"; break;
|
||||
case Keys::D: result += "D"; break;
|
||||
case Keys::E: result += "E"; break;
|
||||
case Keys::F: result += "F"; break;
|
||||
case Keys::G: result += "G"; break;
|
||||
case Keys::H: result += "H"; break;
|
||||
case Keys::I: result += "I"; break;
|
||||
case Keys::J: result += "J"; break;
|
||||
case Keys::K: result += "K"; break;
|
||||
case Keys::L: result += "L"; break;
|
||||
case Keys::M: result += "M"; break;
|
||||
case Keys::N: result += "N"; break;
|
||||
case Keys::O: result += "O"; break;
|
||||
case Keys::P: result += "P"; break;
|
||||
case Keys::Q: result += "Q"; break;
|
||||
case Keys::R: result += "R"; break;
|
||||
case Keys::S: result += "S"; break;
|
||||
case Keys::T: result += "T"; break;
|
||||
case Keys::U: result += "U"; break;
|
||||
case Keys::V: result += "V"; break;
|
||||
case Keys::W: result += "W"; break;
|
||||
case Keys::X: result += "X"; break;
|
||||
case Keys::Y: result += "Y"; break;
|
||||
case Keys::Z: result += "Z"; break;
|
||||
case Keys::LeftBracket: result += "["; break;
|
||||
case Keys::Backslash: result += "\\"; break;
|
||||
case Keys::RightBracket: result += "]"; break;
|
||||
case Keys::GraveAccent: result += "`"; break;
|
||||
case Keys::World1: result += "WORLD1"; break;
|
||||
case Keys::World2: result += "WORLD2"; break;
|
||||
case Keys::Escape: result += "ESC"; break;
|
||||
case Keys::Enter: result += "ENTER"; break;
|
||||
case Keys::Tab: result += "TAB"; break;
|
||||
case Keys::Backspace: result += "BACKSPACE"; break;
|
||||
case Keys::Insert: result += "INSERT"; break;
|
||||
case Keys::Delete: result += "DELETE"; break;
|
||||
case Keys::Right: result += "RIGHT"; break;
|
||||
case Keys::Left: result += "LEFT"; break;
|
||||
case Keys::Down: result += "DOWN"; break;
|
||||
case Keys::Up: result += "UP"; break;
|
||||
case Keys::PageUp: result += "PAGEUP"; break;
|
||||
case Keys::PageDown: result += "PAGEDOWN"; break;
|
||||
case Keys::Home: result += "HOME"; break;
|
||||
case Keys::End: result += "END"; break;
|
||||
case Keys::CapsLock: result += "CAPSLOCK"; break;
|
||||
case Keys::ScrollLock: result += "SCROLLLOCK"; break;
|
||||
case Keys::NumLock: result += "NUMLOCK"; break;
|
||||
case Keys::PrintScreen: result += "PRINTSCREEN"; break;
|
||||
case Keys::Pause: result += "PAUSE"; break;
|
||||
case Keys::F1: result += "F1"; break;
|
||||
case Keys::F2: result += "F2"; break;
|
||||
case Keys::F3: result += "F3"; break;
|
||||
case Keys::F4: result += "F4"; break;
|
||||
case Keys::F5: result += "F5"; break;
|
||||
case Keys::F6: result += "F6"; break;
|
||||
case Keys::F7: result += "F7"; break;
|
||||
case Keys::F8: result += "F8"; break;
|
||||
case Keys::F9: result += "F9"; break;
|
||||
case Keys::F10: result += "F10"; break;
|
||||
case Keys::F11: result += "F11"; break;
|
||||
case Keys::F12: result += "F12"; break;
|
||||
case Keys::F13: result += "F13"; break;
|
||||
case Keys::F14: result += "F14"; break;
|
||||
case Keys::F15: result += "F15"; break;
|
||||
case Keys::F16: result += "F16"; break;
|
||||
case Keys::F17: result += "F17"; break;
|
||||
case Keys::F18: result += "F18"; break;
|
||||
case Keys::F19: result += "F19"; break;
|
||||
case Keys::F20: result += "F20"; break;
|
||||
case Keys::F21: result += "F21"; break;
|
||||
case Keys::F22: result += "F22"; break;
|
||||
case Keys::F23: result += "F23"; break;
|
||||
case Keys::F24: result += "F24"; break;
|
||||
case Keys::F25: result += "F25"; break;
|
||||
case Keys::KeyPad0: result += "KP0"; break;
|
||||
case Keys::KeyPad1: result += "KP1"; break;
|
||||
case Keys::KeyPad2: result += "KP2"; break;
|
||||
case Keys::KeyPad3: result += "KP3"; break;
|
||||
case Keys::KeyPad4: result += "KP4"; break;
|
||||
case Keys::KeyPad5: result += "KP5"; break;
|
||||
case Keys::KeyPad6: result += "KP6"; break;
|
||||
case Keys::KeyPad7: result += "KP7"; break;
|
||||
case Keys::KeyPad8: result += "KP8"; break;
|
||||
case Keys::KeyPad9: result += "KP9"; break;
|
||||
case Keys::KeyPadDecimal: result += "KPDECIMAL"; break;
|
||||
case Keys::KeyPadDivide: result += "KPDIVIDE"; break;
|
||||
case Keys::KeyPadMultiply: result += "KPMULTIPLY"; break;
|
||||
case Keys::KeyPadSubtract: result += "KPSUBTRACT"; break;
|
||||
case Keys::KeyPadAdd: result += "KPADD"; break;
|
||||
case Keys::KeyPadEnter: result += "KPENTER"; break;
|
||||
case Keys::KeyPadEqual: result += "KPEQUAL"; break;
|
||||
case Keys::Menu: result += "MENU"; break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
result += " + ";
|
||||
}
|
||||
|
||||
if (result.ends_with(" + "))
|
||||
result = result.substr(0, result.size() - 3);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
friend Shortcut operator+(const Key &lhs, const Key &rhs);
|
||||
|
||||
@@ -179,29 +357,6 @@ namespace hex {
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr static auto CTRL = Key(static_cast<Keys>(0x1000'0000));
|
||||
constexpr static auto ALT = Key(static_cast<Keys>(0x2000'0000));
|
||||
constexpr static auto SHIFT = Key(static_cast<Keys>(0x4000'0000));
|
||||
constexpr static auto SUPER = Key(static_cast<Keys>(0x8000'0000));
|
||||
|
||||
#if defined(OS_MACOS)
|
||||
constexpr static auto CTRLCMD = SUPER;
|
||||
|
||||
constexpr static auto CTRL_NAME = "CTRL";
|
||||
constexpr static auto ALT_NAME = "OPT";
|
||||
constexpr static auto SHIFT_NAME = "SHIFT";
|
||||
constexpr static auto SUPER_NAME = "CMD";
|
||||
constexpr static auto CTRLCMD_NAME = SUPER_NAME;
|
||||
#else
|
||||
constexpr static auto CTRLCMD = CTRL;
|
||||
|
||||
constexpr static auto CTRL_NAME = "CTRL";
|
||||
constexpr static auto ALT_NAME = "ALT";
|
||||
constexpr static auto SHIFT_NAME = "SHIFT";
|
||||
constexpr static auto SUPER_NAME = "SUPER";
|
||||
constexpr static auto CTRLCMD_NAME = CTRL_NAME;
|
||||
#endif
|
||||
|
||||
class ShortcutManager {
|
||||
public:
|
||||
static void addGlobalShortcut(const Shortcut &shortcut, const std::function<void()> &callback);
|
||||
|
||||
@@ -474,11 +474,29 @@ namespace hex {
|
||||
getMainMenuItems().insert({ priority, { unlocalizedName } });
|
||||
}
|
||||
|
||||
void addMenuItem(const std::string &unlocalizedMainMenuName, u32 priority, const impl::DrawCallback &function) {
|
||||
log::debug("Added new menu item to menu {} with priority {}", unlocalizedMainMenuName, priority);
|
||||
void addMenuItem(const std::vector<std::string> &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) {
|
||||
log::debug("Added new menu item to menu {} with priority {}", wolv::util::combineStrings(unlocalizedMainMenuNames, " -> "), priority);
|
||||
|
||||
getMenuItems().insert({
|
||||
priority, {unlocalizedMainMenuName, function}
|
||||
priority, { unlocalizedMainMenuNames, shortcut, function, enabledCallback }
|
||||
});
|
||||
|
||||
ShortcutManager::addGlobalShortcut(shortcut, function);
|
||||
}
|
||||
|
||||
void addMenuItemSubMenu(std::vector<std::string> unlocalizedMainMenuNames, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) {
|
||||
log::debug("Added new menu item sub menu to menu {} with priority {}", wolv::util::combineStrings(unlocalizedMainMenuNames, " -> "), priority);
|
||||
|
||||
unlocalizedMainMenuNames.emplace_back(impl::SubMenuValue);
|
||||
getMenuItems().insert({
|
||||
priority, { unlocalizedMainMenuNames, {}, function, enabledCallback }
|
||||
});
|
||||
}
|
||||
|
||||
void addMenuItemSeparator(std::vector<std::string> unlocalizedMainMenuNames, u32 priority) {
|
||||
unlocalizedMainMenuNames.emplace_back(impl::SeparatorValue);
|
||||
getMenuItems().insert({
|
||||
priority, { unlocalizedMainMenuNames, {}, []{}, []{ return true; } }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -272,6 +272,27 @@ namespace hex {
|
||||
}
|
||||
}
|
||||
|
||||
static void createNestedMenu(std::span<const std::string> menuItems, const Shortcut &shortcut, const std::function<void()> &callback, const std::function<bool()> &enabledCallback) {
|
||||
const auto &name = menuItems.front();
|
||||
|
||||
if (name == ContentRegistry::Interface::impl::SeparatorValue) {
|
||||
ImGui::Separator();
|
||||
return;
|
||||
}
|
||||
|
||||
if (name == ContentRegistry::Interface::impl::SubMenuValue) {
|
||||
callback();
|
||||
} else if (menuItems.size() == 1) {
|
||||
if (ImGui::MenuItem(LangEntry(name), shortcut.toString().c_str(), false, enabledCallback()))
|
||||
callback();
|
||||
} else {
|
||||
if (ImGui::BeginMenu(LangEntry(name))) {
|
||||
createNestedMenu({ menuItems.begin() + 1, menuItems.end() }, shortcut, callback, enabledCallback);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Window::frameBegin() {
|
||||
// Start new ImGui Frame
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
@@ -389,16 +410,9 @@ namespace hex {
|
||||
}
|
||||
}
|
||||
|
||||
std::set<std::string> encounteredMenus;
|
||||
for (auto &[priority, menuItem] : ContentRegistry::Interface::getMenuItems()) {
|
||||
if (ImGui::BeginMenu(LangEntry(menuItem.unlocalizedName))) {
|
||||
auto [iter, inserted] = encounteredMenus.insert(menuItem.unlocalizedName);
|
||||
if (!inserted)
|
||||
ImGui::Separator();
|
||||
|
||||
menuItem.callback();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
const auto &[unlocalizedNames, shortcut, callback, enabledCallback] = menuItem;
|
||||
createNestedMenu(unlocalizedNames, shortcut, callback, enabledCallback);
|
||||
}
|
||||
|
||||
this->drawTitleBar();
|
||||
@@ -509,6 +523,16 @@ namespace hex {
|
||||
// Run all deferred calls
|
||||
TaskManager::runDeferredCalls();
|
||||
|
||||
// Draw main menu popups
|
||||
for (auto &[priority, menuItem] : ContentRegistry::Interface::getMenuItems()) {
|
||||
const auto &[unlocalizedNames, shortcut, callback, enabledCallback] = menuItem;
|
||||
|
||||
if (ImGui::BeginPopup(unlocalizedNames.front().c_str())) {
|
||||
createNestedMenu({ unlocalizedNames.begin() + 1, unlocalizedNames.end() }, shortcut, callback, enabledCallback);
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
EventManager::post<EventFrameBegin>();
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,7 @@
|
||||
"hex.builtin.menu.file.open_project",
|
||||
"hex.builtin.menu.file.open_recent",
|
||||
"hex.builtin.menu.file.quit",
|
||||
"hex.builtin.menu.file.reload_file",
|
||||
"hex.builtin.menu.file.reload_provider",
|
||||
"hex.builtin.menu.file.save_project",
|
||||
"hex.builtin.menu.file.save_project_as",
|
||||
"hex.builtin.menu.help",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"hex.builtin.menu.file.open_project": "Projekt öffnen...",
|
||||
"hex.builtin.menu.file.open_recent": "Zuletzt geöffnete Dateien",
|
||||
"hex.builtin.menu.file.quit": "ImHex beenden",
|
||||
"hex.builtin.menu.file.reload_file": "Datei neu laden",
|
||||
"hex.builtin.menu.file.reload_provider": "Provider neu laden",
|
||||
"hex.builtin.menu.file.save_project": "Projekt speichern",
|
||||
"hex.builtin.menu.file.save_project_as": "Projekt speichern unter...",
|
||||
"hex.builtin.menu.help": "Hilfe",
|
||||
|
||||
@@ -150,6 +150,9 @@
|
||||
"hex.builtin.menu.file.export.ips.popup.missing_eof_error": "Missing IPS EOF record!",
|
||||
"hex.builtin.menu.file.export.ips": "IPS Patch",
|
||||
"hex.builtin.menu.file.export.ips32": "IPS32 Patch",
|
||||
"hex.builtin.menu.file.export.bookmark": "Bookmark",
|
||||
"hex.builtin.menu.file.export.pattern": "Pattern File",
|
||||
"hex.builtin.menu.file.export.data_processor": "Data Processor Workspace",
|
||||
"hex.builtin.menu.file.export.popup.create": "Cannot export data. Failed to create file!",
|
||||
"hex.builtin.menu.file.export.title": "Export File",
|
||||
"hex.builtin.menu.file.import": "Import...",
|
||||
@@ -159,14 +162,19 @@
|
||||
"hex.builtin.menu.file.import.ips": "IPS Patch",
|
||||
"hex.builtin.menu.file.import.ips32": "IPS32 Patch",
|
||||
"hex.builtin.menu.file.import.modified_file": "Modified File",
|
||||
"hex.builtin.menu.file.import.bookmark": "Bookmark",
|
||||
"hex.builtin.menu.file.import.pattern": "Pattern File",
|
||||
"hex.builtin.menu.file.import.data_processor": "Data Processor Workspace",
|
||||
"hex.builtin.menu.file.import.custom_encoding": "Custom Encoding File",
|
||||
"hex.builtin.menu.file.open_file": "Open File...",
|
||||
"hex.builtin.menu.file.open_other": "Open Other...",
|
||||
"hex.builtin.menu.file.open_project": "Open Project...",
|
||||
"hex.builtin.menu.file.project": "Project",
|
||||
"hex.builtin.menu.file.project.open": "Open Project...",
|
||||
"hex.builtin.menu.file.project.save": "Save Project",
|
||||
"hex.builtin.menu.file.project.save_as": "Save Project As...",
|
||||
"hex.builtin.menu.file.open_recent": "Open Recent",
|
||||
"hex.builtin.menu.file.quit": "Quit ImHex",
|
||||
"hex.builtin.menu.file.reload_file": "Reload File",
|
||||
"hex.builtin.menu.file.save_project": "Save Project",
|
||||
"hex.builtin.menu.file.save_project_as": "Save Project As...",
|
||||
"hex.builtin.menu.file.reload_provider": "Reload Provider",
|
||||
"hex.builtin.menu.help": "Help",
|
||||
"hex.builtin.menu.layout": "Layout",
|
||||
"hex.builtin.menu.view": "View",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"hex.builtin.menu.file.open_project": "Apri un Progetto...",
|
||||
"hex.builtin.menu.file.open_recent": "File recenti",
|
||||
"hex.builtin.menu.file.quit": "Uscita ImHex",
|
||||
"hex.builtin.menu.file.reload_file": "",
|
||||
"hex.builtin.menu.file.reload_provider": "",
|
||||
"hex.builtin.menu.file.save_project": "Salva Progetto",
|
||||
"hex.builtin.menu.file.save_project_as": "",
|
||||
"hex.builtin.menu.help": "Aiuto",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"hex.builtin.menu.file.open_project": "プロジェクトを開く…",
|
||||
"hex.builtin.menu.file.open_recent": "最近使用したファイル",
|
||||
"hex.builtin.menu.file.quit": "ImHexを終了",
|
||||
"hex.builtin.menu.file.reload_file": "",
|
||||
"hex.builtin.menu.file.reload_provider": "",
|
||||
"hex.builtin.menu.file.save_project": "プロジェクトを保存",
|
||||
"hex.builtin.menu.file.save_project_as": "",
|
||||
"hex.builtin.menu.help": "ヘルプ",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"hex.builtin.menu.file.open_project": "프로젝트 열기...",
|
||||
"hex.builtin.menu.file.open_recent": "최근 파일",
|
||||
"hex.builtin.menu.file.quit": "ImHex 종료하기",
|
||||
"hex.builtin.menu.file.reload_file": "",
|
||||
"hex.builtin.menu.file.reload_provider": "",
|
||||
"hex.builtin.menu.file.save_project": "프로젝트 저장",
|
||||
"hex.builtin.menu.file.save_project_as": "",
|
||||
"hex.builtin.menu.help": "도움말",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"hex.builtin.menu.file.open_project": "Abrir Projeto...",
|
||||
"hex.builtin.menu.file.open_recent": "Abrir Recentes",
|
||||
"hex.builtin.menu.file.quit": "Sair do ImHex",
|
||||
"hex.builtin.menu.file.reload_file": "",
|
||||
"hex.builtin.menu.file.reload_provider": "",
|
||||
"hex.builtin.menu.file.save_project": "Salvar Projeto",
|
||||
"hex.builtin.menu.file.save_project_as": "",
|
||||
"hex.builtin.menu.help": "Ajuda",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"hex.builtin.menu.file.open_project": "打开项目...",
|
||||
"hex.builtin.menu.file.open_recent": "最近打开",
|
||||
"hex.builtin.menu.file.quit": "退出 ImHex",
|
||||
"hex.builtin.menu.file.reload_file": "重新加载文件",
|
||||
"hex.builtin.menu.file.reload_file": "",
|
||||
"hex.builtin.menu.file.save_project": "保存项目",
|
||||
"hex.builtin.menu.file.save_project_as": "另存项目为...",
|
||||
"hex.builtin.menu.help": "帮助",
|
||||
|
||||
@@ -159,7 +159,7 @@
|
||||
"hex.builtin.menu.file.open_project": "開啟專案...",
|
||||
"hex.builtin.menu.file.open_recent": "開啟近期",
|
||||
"hex.builtin.menu.file.quit": "退出 ImHex",
|
||||
"hex.builtin.menu.file.reload_file": "重新載入檔案",
|
||||
"hex.builtin.menu.file.reload_file": "",
|
||||
"hex.builtin.menu.file.save_project": "儲存專案",
|
||||
"hex.builtin.menu.file.save_project_as": "另存專案為...",
|
||||
"hex.builtin.menu.help": "幫助",
|
||||
|
||||
@@ -20,368 +20,419 @@ namespace hex::plugin::builtin {
|
||||
|
||||
static bool g_demoWindowOpen = false;
|
||||
|
||||
void handleIPSError(IPSError error) {
|
||||
TaskManager::doLater([error]{
|
||||
switch (error) {
|
||||
case IPSError::InvalidPatchHeader:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_header_error"_lang);
|
||||
break;
|
||||
case IPSError::AddressOutOfRange:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.address_out_of_range_error"_lang);
|
||||
break;
|
||||
case IPSError::PatchTooLarge:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.patch_too_large_error"_lang);
|
||||
break;
|
||||
case IPSError::InvalidPatchFormat:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_format_error"_lang);
|
||||
break;
|
||||
case IPSError::MissingEOF:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.missing_eof_error"_lang);
|
||||
break;
|
||||
}
|
||||
});
|
||||
namespace {
|
||||
|
||||
bool noRunningTasks() {
|
||||
return TaskManager::getRunningTaskCount() == 0;
|
||||
}
|
||||
|
||||
bool noRunningTaskAndValidProvider() {
|
||||
return noRunningTasks() && ImHexApi::Provider::isValid();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void handleIPSError(IPSError error) {
|
||||
TaskManager::doLater([error]{
|
||||
switch (error) {
|
||||
case IPSError::InvalidPatchHeader:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_header_error"_lang);
|
||||
break;
|
||||
case IPSError::AddressOutOfRange:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.address_out_of_range_error"_lang);
|
||||
break;
|
||||
case IPSError::PatchTooLarge:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.patch_too_large_error"_lang);
|
||||
break;
|
||||
case IPSError::InvalidPatchFormat:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.invalid_patch_format_error"_lang);
|
||||
break;
|
||||
case IPSError::MissingEOF:
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.missing_eof_error"_lang);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Import
|
||||
namespace {
|
||||
|
||||
void importBase64() {
|
||||
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
||||
wolv::io::File inputFile(path, wolv::io::File::Mode::Read);
|
||||
if (!inputFile.isValid()) {
|
||||
View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.open_error"_lang);
|
||||
return;
|
||||
}
|
||||
|
||||
auto base64 = inputFile.readBytes();
|
||||
|
||||
if (!base64.empty()) {
|
||||
auto data = crypt::decode64(base64);
|
||||
|
||||
if (data.empty())
|
||||
View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.import_error"_lang);
|
||||
else {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const std::fs::path &path) {
|
||||
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
|
||||
|
||||
if (!outputFile.isValid())
|
||||
View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.import_error"_lang);
|
||||
|
||||
outputFile.write(data);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
View::showErrorPopup("hex.builtin.popup.file_open_error"_lang);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void importIPSPatch() {
|
||||
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).readBytes();
|
||||
auto patch = hex::loadIPSPatch(patchData);
|
||||
if (!patch.has_value()) {
|
||||
handleIPSError(patch.error());
|
||||
return;
|
||||
}
|
||||
|
||||
task.setMaxValue(patch->size());
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
u64 progress = 0;
|
||||
for (auto &[address, value] : *patch) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void importIPS32Patch() {
|
||||
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).readBytes();
|
||||
auto patch = hex::loadIPS32Patch(patchData);
|
||||
if (!patch.has_value()) {
|
||||
handleIPSError(patch.error());
|
||||
return;
|
||||
}
|
||||
|
||||
task.setMaxValue(patch->size());
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
u64 progress = 0;
|
||||
for (auto &[address, value] : *patch) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void importModifiedFile() {
|
||||
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readBytes();
|
||||
|
||||
if (patchData.size() != provider->getActualSize()) {
|
||||
View::showErrorPopup("hex.builtin.menu.file.import.modified_file.popup.invalid_size"_lang);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto baseAddress = provider->getBaseAddress();
|
||||
|
||||
std::map<u64, u8> patches;
|
||||
for (u64 i = 0; i < patchData.size(); i++) {
|
||||
u8 value = 0;
|
||||
provider->read(baseAddress + i, &value, 1);
|
||||
|
||||
if (value != patchData[i])
|
||||
patches[baseAddress + i] = patchData[i];
|
||||
}
|
||||
|
||||
task.setMaxValue(patches.size());
|
||||
|
||||
u64 progress = 0;
|
||||
for (auto &[address, value] : patches) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Export
|
||||
namespace {
|
||||
|
||||
void exportBase64() {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [](const auto &path) {
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &) {
|
||||
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
|
||||
if (!outputFile.isValid()) {
|
||||
TaskManager::doLater([] {
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.base64.popup.export_error"_lang);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
std::vector<u8> bytes(3000);
|
||||
for (u64 address = 0; address < provider->getActualSize(); address += 3000) {
|
||||
bytes.resize(std::min<u64>(3000, provider->getActualSize() - address));
|
||||
provider->read(provider->getBaseAddress() + address, bytes.data(), bytes.size());
|
||||
|
||||
outputFile.write(crypt::encode64(bytes));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void exportIPSPatch() {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
Patches patches = provider->getPatches();
|
||||
|
||||
// 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)) {
|
||||
u8 value = 0;
|
||||
provider->read(0x00454F45, &value, sizeof(u8));
|
||||
patches[0x00454F45] = value;
|
||||
}
|
||||
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) {
|
||||
auto data = generateIPSPatch(patches);
|
||||
|
||||
TaskManager::doLater([data] {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) {
|
||||
auto file = wolv::io::File(path, wolv::io::File::Mode::Create);
|
||||
if (!file.isValid()) {
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.has_value())
|
||||
file.write(data.value());
|
||||
else {
|
||||
handleIPSError(data.error());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void exportIPS32Patch() {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
Patches patches = provider->getPatches();
|
||||
|
||||
// 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)) {
|
||||
u8 value = 0;
|
||||
provider->read(0x45454F45, &value, sizeof(u8));
|
||||
patches[0x45454F45] = value;
|
||||
}
|
||||
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) {
|
||||
auto data = generateIPS32Patch(patches);
|
||||
|
||||
TaskManager::doLater([data] {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) {
|
||||
auto file = wolv::io::File(path, wolv::io::File::Mode::Create);
|
||||
if (!file.isValid()) {
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.has_value())
|
||||
file.write(data.value());
|
||||
else
|
||||
handleIPSError(data.error());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void createFileMenu() {
|
||||
|
||||
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.file", 1000);
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1050, [&] {
|
||||
bool taskRunning = TaskManager::getRunningTaskCount() > 0;
|
||||
/* Create File */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.create_file" }, 1050, CTRLCMD + Keys::N, [] {
|
||||
auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true);
|
||||
if (newProvider != nullptr && !newProvider->open())
|
||||
hex::ImHexApi::Provider::remove(newProvider);
|
||||
else
|
||||
EventManager::post<EventProviderOpened>(newProvider);
|
||||
}, noRunningTasks);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.create_file"_lang, (CTRLCMD_NAME + " + N"s).c_str(), false, !taskRunning)) {
|
||||
auto newProvider = hex::ImHexApi::Provider::createProvider("hex.builtin.provider.mem_file", true);
|
||||
if (newProvider != nullptr && !newProvider->open())
|
||||
hex::ImHexApi::Provider::remove(newProvider);
|
||||
else
|
||||
EventManager::post<EventProviderOpened>(newProvider);
|
||||
/* Open File */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_file" }, 1100, CTRLCMD + Keys::O, [] {
|
||||
EventManager::post<RequestOpenWindow>("Open File");
|
||||
}, noRunningTasks);
|
||||
|
||||
/* Open Other */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.open_other"}, 1150, Shortcut::None, [] {
|
||||
for (const auto &unlocalizedProviderName : ContentRegistry::Provider::getEntries()) {
|
||||
if (ImGui::MenuItem(LangEntry(unlocalizedProviderName)))
|
||||
ImHexApi::Provider::createProvider(unlocalizedProviderName);
|
||||
}
|
||||
}, noRunningTasks);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.open_file"_lang, (CTRLCMD_NAME + " + O"s).c_str(), false, !taskRunning)) {
|
||||
EventManager::post<RequestOpenWindow>("Open File");
|
||||
}
|
||||
/* Reload Provider */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.reload_provider"}, 1250, CTRLCMD + Keys::R, [] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
if (ImGui::BeginMenu("hex.builtin.menu.file.open_other"_lang, !taskRunning)) {
|
||||
provider->close();
|
||||
if (!provider->open())
|
||||
ImHexApi::Provider::remove(provider, true);
|
||||
}, noRunningTaskAndValidProvider);
|
||||
|
||||
for (const auto &unlocalizedProviderName : ContentRegistry::Provider::getEntries()) {
|
||||
if (ImGui::MenuItem(LangEntry(unlocalizedProviderName))) {
|
||||
ImHexApi::Provider::createProvider(unlocalizedProviderName);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.reload_file"_lang, (CTRLCMD_NAME + " + R"s).c_str(), false, !taskRunning && ImHexApi::Provider::isValid())) {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
provider->close();
|
||||
if (!provider->open())
|
||||
ImHexApi::Provider::remove(provider, true);
|
||||
}
|
||||
});
|
||||
|
||||
/* File close, quit imhex */
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 5000, [&] {
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
bool taskRunning = TaskManager::getRunningTaskCount() > 0;
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.close"_lang, (CTRLCMD_NAME + " + W"s).c_str(), false, providerValid && !taskRunning)) {
|
||||
ImHexApi::Provider::remove(ImHexApi::Provider::get());
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.quit"_lang, "Alt + F4")) {
|
||||
ImHexApi::Common::closeImHex();
|
||||
}
|
||||
});
|
||||
|
||||
/* Project open / save */
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1150, [&] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
bool taskRunning = TaskManager::getRunningTaskCount() > 0;
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.open" }, 1400,
|
||||
ALT + Keys::O,
|
||||
openProject, noRunningTasks);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.open_project"_lang, "", false, !taskRunning)) {
|
||||
openProject();
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.save" }, 1450,
|
||||
ALT + Keys::S,
|
||||
saveProject, [&] { return noRunningTaskAndValidProvider() && ProjectFile::hasPath(); });
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.save_project"_lang, (ALT_NAME + " + S"s).c_str(), false, providerValid && ProjectFile::hasPath())) {
|
||||
saveProject();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.save_project_as"_lang, (ALT_NAME + " + "s + SHIFT_NAME + " + S"s).c_str(), false, providerValid && provider->isWritable())) {
|
||||
saveProjectAs();
|
||||
}
|
||||
});
|
||||
|
||||
/* Import / Export */
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1300, [&] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
bool taskRunning = TaskManager::getRunningTaskCount() > 0;
|
||||
|
||||
/* Import */
|
||||
if (ImGui::BeginMenu("hex.builtin.menu.file.import"_lang, !taskRunning)) {
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.import.base64"_lang)) {
|
||||
|
||||
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
||||
wolv::io::File inputFile(path, wolv::io::File::Mode::Read);
|
||||
if (!inputFile.isValid()) {
|
||||
View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.open_error"_lang);
|
||||
return;
|
||||
}
|
||||
|
||||
auto base64 = inputFile.readBytes();
|
||||
|
||||
if (!base64.empty()) {
|
||||
auto data = crypt::decode64(base64);
|
||||
|
||||
if (data.empty())
|
||||
View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.import_error"_lang);
|
||||
else {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const std::fs::path &path) {
|
||||
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
|
||||
|
||||
if (!outputFile.isValid())
|
||||
View::showErrorPopup("hex.builtin.menu.file.import.base64.popup.import_error"_lang);
|
||||
|
||||
outputFile.write(data);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
View::showErrorPopup("hex.builtin.popup.file_open_error"_lang);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.import.ips"_lang, nullptr, false)) {
|
||||
|
||||
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).readBytes();
|
||||
auto patch = hex::loadIPSPatch(patchData);
|
||||
if (!patch.has_value()) {
|
||||
handleIPSError(patch.error());
|
||||
return;
|
||||
}
|
||||
|
||||
task.setMaxValue(patch->size());
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
u64 progress = 0;
|
||||
for (auto &[address, value] : *patch) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.import.ips32"_lang, nullptr, false)) {
|
||||
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).readBytes();
|
||||
auto patch = hex::loadIPS32Patch(patchData);
|
||||
if (!patch.has_value()) {
|
||||
handleIPSError(patch.error());
|
||||
return;
|
||||
}
|
||||
|
||||
task.setMaxValue(patch->size());
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
u64 progress = 0;
|
||||
for (auto &[address, value] : *patch) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.import.modified_file"_lang, nullptr, false)) {
|
||||
fs::openFileBrowser(fs::DialogMode::Open, {}, [](const auto &path) {
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &task) {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
auto patchData = wolv::io::File(path, wolv::io::File::Mode::Read).readBytes();
|
||||
|
||||
if (patchData.size() != provider->getActualSize()) {
|
||||
View::showErrorPopup("hex.builtin.menu.file.import.modified_file.popup.invalid_size"_lang);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto baseAddress = provider->getBaseAddress();
|
||||
|
||||
std::map<u64, u8> patches;
|
||||
for (u64 i = 0; i < patchData.size(); i++) {
|
||||
u8 value = 0;
|
||||
provider->read(baseAddress + i, &value, 1);
|
||||
|
||||
if (value != patchData[i])
|
||||
patches[baseAddress + i] = patchData[i];
|
||||
}
|
||||
|
||||
task.setMaxValue(patches.size());
|
||||
|
||||
u64 progress = 0;
|
||||
for (auto &[address, value] : patches) {
|
||||
provider->addPatch(address, &value, 1);
|
||||
progress++;
|
||||
task.update(progress);
|
||||
}
|
||||
|
||||
provider->createUndoPoint();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.project", "hex.builtin.menu.file.project.save_as" }, 1500,
|
||||
ALT + SHIFT + Keys::S,
|
||||
saveProjectAs, [&] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isWritable(); });
|
||||
|
||||
|
||||
/* Export */
|
||||
if (ImGui::BeginMenu("hex.builtin.menu.file.export"_lang, providerValid && provider->isWritable())) {
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.export.base64"_lang)) {
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 2000);
|
||||
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [](const auto &path) {
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [path](auto &) {
|
||||
wolv::io::File outputFile(path, wolv::io::File::Mode::Create);
|
||||
if (!outputFile.isValid()) {
|
||||
TaskManager::doLater([] {
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.base64.popup.export_error"_lang);
|
||||
});
|
||||
return;
|
||||
}
|
||||
/* Import */
|
||||
{
|
||||
/* Base 64 */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.base64" }, 2050,
|
||||
Shortcut::None,
|
||||
importBase64,
|
||||
noRunningTasks);
|
||||
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
std::vector<u8> bytes(3000);
|
||||
for (u64 address = 0; address < provider->getActualSize(); address += 3000) {
|
||||
bytes.resize(std::min<u64>(3000, provider->getActualSize() - address));
|
||||
provider->read(provider->getBaseAddress() + address, bytes.data(), bytes.size());
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.import" }, 2100);
|
||||
|
||||
outputFile.write(crypt::encode64(bytes));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/* IPS */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.ips"}, 2150,
|
||||
Shortcut::None,
|
||||
importIPSPatch,
|
||||
ImHexApi::Provider::isValid);
|
||||
|
||||
ImGui::Separator();
|
||||
/* IPS32 */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.ips32"}, 2200,
|
||||
Shortcut::None,
|
||||
importIPS32Patch,
|
||||
ImHexApi::Provider::isValid);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.export.ips"_lang, nullptr, false)) {
|
||||
Patches patches = provider->getPatches();
|
||||
/* Modified File */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.modified_file" }, 2300,
|
||||
Shortcut::None,
|
||||
importModifiedFile,
|
||||
[&] { return noRunningTaskAndValidProvider() && ImHexApi::Provider::get()->isWritable(); });
|
||||
}
|
||||
|
||||
// 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)) {
|
||||
u8 value = 0;
|
||||
provider->read(0x00454F45, &value, sizeof(u8));
|
||||
patches[0x00454F45] = value;
|
||||
}
|
||||
/* Export */
|
||||
{
|
||||
/* Base 64 */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.base64" }, 6000,
|
||||
Shortcut::None,
|
||||
exportBase64,
|
||||
ImHexApi::Provider::isValid);
|
||||
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) {
|
||||
auto data = generateIPSPatch(patches);
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.export" }, 6050);
|
||||
|
||||
TaskManager::doLater([data] {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) {
|
||||
auto file = wolv::io::File(path, wolv::io::File::Mode::Create);
|
||||
if (!file.isValid()) {
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang);
|
||||
return;
|
||||
}
|
||||
/* IPS */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.ips" }, 6100,
|
||||
Shortcut::None,
|
||||
exportIPSPatch,
|
||||
ImHexApi::Provider::isValid);
|
||||
|
||||
if (data.has_value())
|
||||
file.write(data.value());
|
||||
else {
|
||||
handleIPSError(data.error());
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
/* IPS32 */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.ips32" }, 6150,
|
||||
Shortcut::None,
|
||||
exportIPS32Patch,
|
||||
ImHexApi::Provider::isValid);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.export.ips32"_lang, nullptr, false)) {
|
||||
Patches patches = provider->getPatches();
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 10000);
|
||||
|
||||
// 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)) {
|
||||
u8 value = 0;
|
||||
provider->read(0x45454F45, &value, sizeof(u8));
|
||||
patches[0x45454F45] = value;
|
||||
}
|
||||
/* Close Provider */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.close"}, 10050, CTRLCMD + Keys::W, [] {
|
||||
ImHexApi::Provider::remove(ImHexApi::Provider::get());
|
||||
}, noRunningTaskAndValidProvider);
|
||||
|
||||
TaskManager::createTask("hex.builtin.common.processing", TaskManager::NoProgress, [patches](auto &) {
|
||||
auto data = generateIPS32Patch(patches);
|
||||
|
||||
TaskManager::doLater([data] {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, {}, [&data](const auto &path) {
|
||||
auto file = wolv::io::File(path, wolv::io::File::Mode::Create);
|
||||
if (!file.isValid()) {
|
||||
View::showErrorPopup("hex.builtin.menu.file.export.ips.popup.export_error"_lang);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.has_value())
|
||||
file.write(data.value());
|
||||
else
|
||||
handleIPSError(data.error());
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
/* Quit ImHex */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.quit"}, 10100, ALT + Keys::F4, [] {
|
||||
ImHexApi::Common::closeImHex();
|
||||
});
|
||||
}
|
||||
|
||||
static void createEditMenu() {
|
||||
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.edit", 2000);
|
||||
|
||||
/* Provider Undo / Redo */
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1000, [&] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.edit.undo"_lang, (CTRLCMD_NAME + " + Z"s).c_str(), false, providerValid && provider->canUndo()))
|
||||
/* Undo */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.menu.edit.undo" }, 1000, CTRLCMD + Keys::Z, [] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
provider->undo();
|
||||
if (ImGui::MenuItem("hex.builtin.menu.edit.redo"_lang, (CTRLCMD_NAME + " + Y"s).c_str(), false, providerValid && provider->canRedo()))
|
||||
}, [&] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->canUndo(); });
|
||||
|
||||
/* Redo */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.menu.edit.redo" }, 1050, CTRLCMD + Keys::Y, [] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
provider->redo();
|
||||
});
|
||||
}, [&] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->canRedo(); });
|
||||
|
||||
}
|
||||
|
||||
static void createViewMenu() {
|
||||
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.view", 3000);
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.view", 1000, [] {
|
||||
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.view" }, 1000, [] {
|
||||
for (auto &[name, view] : ContentRegistry::Views::getEntries()) {
|
||||
if (view->hasViewMenuItemEntry())
|
||||
ImGui::MenuItem(LangEntry(view->getUnlocalizedName()), "", &view->getWindowOpenState());
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
#if defined(DEBUG)
|
||||
ImGui::MenuItem("hex.builtin.menu.view.demo"_lang, "", &g_demoWindowOpen);
|
||||
#endif
|
||||
});
|
||||
|
||||
#if defined(DEBUG)
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.view", 2000, [] {
|
||||
ImGui::MenuItem("hex.builtin.menu.view.demo"_lang, "", &g_demoWindowOpen);
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
static void createLayoutMenu() {
|
||||
ContentRegistry::Interface::registerMainMenuItem("hex.builtin.menu.layout", 4000);
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.layout", 1000, [] {
|
||||
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.layout" }, 1000, [] {
|
||||
for (auto &[layoutName, func] : ContentRegistry::Interface::getLayouts()) {
|
||||
if (ImGui::MenuItem(LangEntry(layoutName), "", false, ImHexApi::Provider::isValid())) {
|
||||
auto dock = ImHexApi::System::getMainDockSpaceId();
|
||||
|
||||
@@ -12,16 +12,14 @@ namespace hex::plugin::builtin {
|
||||
|
||||
ViewAbout::ViewAbout() : View("hex.builtin.view.help.about.name") {
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 1000, [&, this] {
|
||||
if (ImGui::MenuItem("hex.builtin.view.help.about.name"_lang, "")) {
|
||||
TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.help.about.name").c_str()); });
|
||||
this->m_aboutWindowOpen = true;
|
||||
this->getWindowOpenState() = true;
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.help.about.name" }, 1000, Shortcut::None, [this] {
|
||||
TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.help.about.name").c_str()); });
|
||||
this->m_aboutWindowOpen = true;
|
||||
this->getWindowOpenState() = true;
|
||||
});
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.help.documentation"_lang, "")) {
|
||||
hex::openWebpage("https://imhex.werwolv.net/docs");
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.help.documentation" }, 1050, Shortcut::None, [] {
|
||||
hex::openWebpage("https://imhex.werwolv.net/docs");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -376,33 +376,37 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void ViewBookmarks::registerMenuItems() {
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1050, [&] {
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
/* Create bookmark */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.menu.edit.bookmark.create" }, 1900, CTRL + Keys::B, [&] {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
ImHexApi::Bookmarks::add(selection->getStartAddress(), selection->getSize(), {}, {});
|
||||
}, []{ return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid(); });
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.edit.bookmark.create"_lang, nullptr, false, selection.has_value() && providerValid)) {
|
||||
ImHexApi::Bookmarks::add(selection->getStartAddress(), selection->getSize(), {}, {});
|
||||
}
|
||||
});
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 4000, [&] {
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.import" }, 3000);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.bookmark.import"_lang, nullptr, false, providerValid)) {
|
||||
fs::openFileBrowser(fs::DialogMode::Open, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) {
|
||||
try {
|
||||
importBookmarks(ImHexApi::Provider::get(), nlohmann::json::parse(wolv::io::File(path, wolv::io::File::Mode::Read).readString()));
|
||||
} catch (...) { }
|
||||
});
|
||||
}
|
||||
if (ImGui::MenuItem("hex.builtin.menu.file.bookmark.export"_lang, nullptr, false, providerValid && !ProviderExtraData::getCurrent().bookmarks.empty())) {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) {
|
||||
nlohmann::json json;
|
||||
exportBookmarks(ImHexApi::Provider::get(), json);
|
||||
/* Import bookmarks */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.bookmark" }, 3050, Shortcut::None, []{
|
||||
fs::openFileBrowser(fs::DialogMode::Open, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) {
|
||||
try {
|
||||
importBookmarks(ImHexApi::Provider::get(), nlohmann::json::parse(wolv::io::File(path, wolv::io::File::Mode::Read).readString()));
|
||||
} catch (...) { }
|
||||
});
|
||||
}, ImHexApi::Provider::isValid);
|
||||
|
||||
wolv::io::File(path, wolv::io::File::Mode::Create).write(json.dump(4));
|
||||
});
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file", "hex.builtin.menu.file.export" }, 6200);
|
||||
|
||||
|
||||
/* Export bookmarks */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.bookmark" }, 6250, Shortcut::None, []{
|
||||
fs::openFileBrowser(fs::DialogMode::Save, { { "Bookmarks File", "hexbm"} }, [&](const std::fs::path &path) {
|
||||
nlohmann::json json;
|
||||
exportBookmarks(ImHexApi::Provider::get(), json);
|
||||
|
||||
wolv::io::File(path, wolv::io::File::Mode::Create).write(json.dump(4));
|
||||
});
|
||||
}, []{
|
||||
return ImHexApi::Provider::isValid() && !ProviderExtraData::getCurrent().bookmarks.empty();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ namespace hex::plugin::builtin {
|
||||
if (!formatWriteFunction.empty()) {
|
||||
editingFunction = [formatWriteFunction, &pattern](const std::string &value, std::endian) -> std::vector<u8> {
|
||||
pattern->setValue(value);
|
||||
|
||||
return { };
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -66,30 +66,33 @@ namespace hex::plugin::builtin {
|
||||
ViewDataProcessor::processNodes(workspace);
|
||||
});
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 3000, [&] {
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
|
||||
/* Import bookmarks */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.data_processor" }, 4050, Shortcut::None, [this]{
|
||||
auto &data = ProviderExtraData::getCurrent().dataProcessor;
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.load_processor"_lang, nullptr, false, providerValid)) {
|
||||
fs::openFileBrowser(fs::DialogMode::Open, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } },
|
||||
[&](const std::fs::path &path) {
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Read);
|
||||
if (file.isValid()) {
|
||||
ViewDataProcessor::loadNodes(data.mainWorkspace, file.readString());
|
||||
this->m_updateNodePositions = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
fs::openFileBrowser(fs::DialogMode::Open, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } },
|
||||
[&](const std::fs::path &path) {
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Read);
|
||||
if (file.isValid()) {
|
||||
ViewDataProcessor::loadNodes(data.mainWorkspace, file.readString());
|
||||
this->m_updateNodePositions = true;
|
||||
}
|
||||
});
|
||||
}, ImHexApi::Provider::isValid);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.data_processor.menu.file.save_processor"_lang, nullptr, false, !data.workspaceStack.empty() && !data.workspaceStack.back()->nodes.empty() && providerValid)) {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } },
|
||||
[&](const std::fs::path &path) {
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
||||
if (file.isValid())
|
||||
file.write(ViewDataProcessor::saveNodes(data.mainWorkspace).dump(4));
|
||||
});
|
||||
}
|
||||
/* Export bookmarks */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.data_processor" }, 8050, Shortcut::None, []{
|
||||
auto &data = ProviderExtraData::getCurrent().dataProcessor;
|
||||
|
||||
fs::openFileBrowser(fs::DialogMode::Save, { {"hex.builtin.view.data_processor.name"_lang, "hexnode" } },
|
||||
[&](const std::fs::path &path) {
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
||||
if (file.isValid())
|
||||
file.write(ViewDataProcessor::saveNodes(data.mainWorkspace).dump(4));
|
||||
});
|
||||
}, []{
|
||||
auto &data = ProviderExtraData::getCurrent().dataProcessor;
|
||||
return !data.workspaceStack.empty() && !data.workspaceStack.back()->nodes.empty() && ImHexApi::Provider::isValid();
|
||||
});
|
||||
|
||||
ContentRegistry::FileHandler::add({ ".hexnode" }, [this](const auto &path) {
|
||||
|
||||
@@ -560,23 +560,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
// Right click menu
|
||||
if (ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows))
|
||||
ImGui::OpenPopup("hex.builtin.menu.edit"_lang);
|
||||
|
||||
if (ImGui::BeginPopup("hex.builtin.menu.edit"_lang)) {
|
||||
bool needsSeparator = false;
|
||||
for (auto &[priority, menuItem] : ContentRegistry::Interface::getMenuItems()) {
|
||||
if (menuItem.unlocalizedName != "hex.builtin.menu.edit")
|
||||
continue;
|
||||
|
||||
if (needsSeparator)
|
||||
ImGui::Separator();
|
||||
|
||||
menuItem.callback();
|
||||
needsSeparator = true;
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
EventManager::post<RequestOpenPopup>("hex.builtin.menu.edit");
|
||||
}
|
||||
|
||||
void ViewHexEditor::drawContent() {
|
||||
@@ -667,6 +651,24 @@ namespace hex::plugin::builtin {
|
||||
ImGui::SetClipboardText(buffer.c_str());
|
||||
}
|
||||
|
||||
static void copyCustomEncoding(const EncodingFile &customEncoding, const Region &selection) {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
std::vector<u8> buffer(customEncoding.getLongestSequence(), 0x00);
|
||||
std::string string;
|
||||
|
||||
u64 offset = selection.getStartAddress();
|
||||
while (offset < selection.getEndAddress()) {
|
||||
provider->read(offset, buffer.data(), std::min<size_t>(buffer.size(), selection.size - (offset - selection.getStartAddress())));
|
||||
auto [result, size] = customEncoding.getEncodingFor(buffer);
|
||||
|
||||
string += result;
|
||||
offset += size;
|
||||
};
|
||||
|
||||
ImGui::SetClipboardText(string.c_str());
|
||||
}
|
||||
|
||||
void ViewHexEditor::registerShortcuts() {
|
||||
// Save operations
|
||||
ShortcutManager::addShortcut(this, CTRLCMD + Keys::S, [] {
|
||||
@@ -953,159 +955,235 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void ViewHexEditor::registerMenuItems() {
|
||||
// Basic operations
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1100, [&] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.save"_lang, (CTRLCMD_NAME + " + S"s).c_str(), false, providerValid && provider->isWritable())) {
|
||||
save();
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 1300);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.save_as"_lang, (CTRLCMD_NAME + " + "s + SHIFT_NAME + " + S"s).c_str(), false, providerValid && provider->isWritable())) {
|
||||
saveAs();
|
||||
}
|
||||
});
|
||||
/* Save */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.save"_lang }, 1350,
|
||||
CTRL + Keys::S,
|
||||
save,
|
||||
[] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
|
||||
return providerValid && provider->isWritable();
|
||||
});
|
||||
|
||||
/* Save As */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.save_as"_lang }, 1375,
|
||||
CTRL + SHIFT + Keys::S,
|
||||
saveAs,
|
||||
[] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
|
||||
return providerValid && provider->isWritable();
|
||||
});
|
||||
|
||||
/* Load Encoding File */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.custom_encoding" }, 5050,
|
||||
Shortcut::None,
|
||||
[this]{
|
||||
std::vector<std::fs::path> paths;
|
||||
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Encodings)) {
|
||||
std::error_code error;
|
||||
for (const auto &entry : std::fs::recursive_directory_iterator(path, error)) {
|
||||
if (!entry.is_regular_file()) continue;
|
||||
|
||||
paths.push_back(entry);
|
||||
}
|
||||
}
|
||||
|
||||
View::showFileChooserPopup(paths, { {"Thingy Table File", "tbl"} }, false,
|
||||
[this](const auto &path) {
|
||||
this->m_hexEditor.setCustomEncoding(EncodingFile(EncodingFile::Type::Thingy, path));
|
||||
});
|
||||
},
|
||||
ImHexApi::Provider::isValid);
|
||||
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.file" }, 1500);
|
||||
|
||||
/* Search */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.search" }, 1550,
|
||||
CTRLCMD + Keys::F,
|
||||
[this] {
|
||||
this->openPopup<PopupFind>();
|
||||
},
|
||||
ImHexApi::Provider::isValid);
|
||||
|
||||
/* Goto */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.goto" }, 1600,
|
||||
CTRLCMD + Keys::G,
|
||||
[this] {
|
||||
this->openPopup<PopupGoto>();
|
||||
},
|
||||
ImHexApi::Provider::isValid);
|
||||
|
||||
/* Select */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.view.hex_editor.menu.file.select" }, 1650,
|
||||
CTRLCMD + SHIFT + Keys::A,
|
||||
[this] {
|
||||
this->openPopup<PopupSelect>();
|
||||
},
|
||||
ImHexApi::Provider::isValid);
|
||||
|
||||
|
||||
// Metadata save/load
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1200, [&, this] {
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.load_encoding_file"_lang, nullptr, false, providerValid)) {
|
||||
std::vector<std::fs::path> paths;
|
||||
for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Encodings)) {
|
||||
std::error_code error;
|
||||
for (const auto &entry : std::fs::recursive_directory_iterator(path, error)) {
|
||||
if (!entry.is_regular_file()) continue;
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1100);
|
||||
|
||||
paths.push_back(entry);
|
||||
}
|
||||
}
|
||||
/* Copy */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy" }, 1150,
|
||||
CTRLCMD + Keys::C,
|
||||
[] {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
if (selection.has_value() && selection != Region::Invalid())
|
||||
copyBytes(*selection);
|
||||
},
|
||||
ImHexApi::HexEditor::isSelectionValid);
|
||||
|
||||
View::showFileChooserPopup(paths, { {"Thingy Table File", "tbl"} }, false,
|
||||
[this](const auto &path) {
|
||||
this->m_hexEditor.setCustomEncoding(EncodingFile(EncodingFile::Type::Thingy, path));
|
||||
});
|
||||
}
|
||||
});
|
||||
/* Copy As */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as", "hex.builtin.view.hex_editor.copy.ascii" }, 1200,
|
||||
CTRLCMD + SHIFT + Keys::C,
|
||||
[] {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
if (selection.has_value() && selection != Region::Invalid())
|
||||
copyString(*selection);
|
||||
},
|
||||
ImHexApi::HexEditor::isSelectionValid);
|
||||
|
||||
/* Copy address */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as", "hex.builtin.view.hex_editor.copy.address" }, 1250,
|
||||
Shortcut::None,
|
||||
[] {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
if (selection.has_value() && selection != Region::Invalid())
|
||||
ImGui::SetClipboardText(hex::format("0x{:08X}", selection->getStartAddress()).c_str());
|
||||
},
|
||||
ImHexApi::HexEditor::isSelectionValid);
|
||||
|
||||
// Search / Goto
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1400, [&, this] {
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
/* Copy custom encoding */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as", "hex.builtin.view.hex_editor.copy.custom_encoding" }, 1300,
|
||||
Shortcut::None,
|
||||
[this] {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
auto customEncoding = this->m_hexEditor.getCustomEncoding();
|
||||
if (customEncoding.has_value() && selection.has_value() && selection != Region::Invalid())
|
||||
copyCustomEncoding(*customEncoding, *selection);
|
||||
},
|
||||
[this] {
|
||||
return ImHexApi::HexEditor::isSelectionValid() && this->m_hexEditor.getCustomEncoding().has_value();
|
||||
});
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.search"_lang, (CTRLCMD_NAME + " + F"s).c_str(), false, providerValid)) {
|
||||
this->openPopup<PopupFind>();
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as" }, 1350);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.goto"_lang, (CTRLCMD_NAME + " + G"s).c_str(), false, providerValid)) {
|
||||
this->openPopup<PopupGoto>();
|
||||
}
|
||||
/* Copy as... */
|
||||
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.copy_as" }, 1400, []{
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.file.select"_lang, (CTRLCMD_NAME + " + "s + SHIFT_NAME + " + A"s).c_str(), false, providerValid)) {
|
||||
this->openPopup<PopupSelect>();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Copy / Paste
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1100, [&] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.copy"_lang, (CTRLCMD_NAME + " + C"s).c_str(), false, selection.has_value()))
|
||||
copyBytes(*selection);
|
||||
|
||||
if (ImGui::BeginMenu("hex.builtin.view.hex_editor.menu.edit.copy_as"_lang, selection.has_value() && providerValid)) {
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.copy.ascii"_lang, (CTRLCMD_NAME + " + "s + SHIFT_NAME + " + C"s).c_str()))
|
||||
copyString(*selection);
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.copy.address"_lang))
|
||||
ImGui::SetClipboardText(hex::format("0x{:08X}", selection->getStartAddress()).c_str());
|
||||
|
||||
auto &customEncoding = this->m_hexEditor.getCustomEncoding();
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.copy.custom_encoding"_lang, "", false, customEncoding.has_value())) {
|
||||
std::vector<u8> buffer(customEncoding->getLongestSequence(), 0x00);
|
||||
std::string string;
|
||||
|
||||
u64 offset = selection->getStartAddress();
|
||||
while (offset < selection->getEndAddress()) {
|
||||
provider->read(offset, buffer.data(), std::min<size_t>(buffer.size(), selection->size - (offset - selection->getStartAddress())));
|
||||
auto [result, size] = customEncoding->getEncodingFor(buffer);
|
||||
|
||||
string += result;
|
||||
offset += size;
|
||||
};
|
||||
|
||||
ImGui::SetClipboardText(string.c_str());
|
||||
}
|
||||
ImGui::Separator();
|
||||
|
||||
for (const auto &[unlocalizedName, callback] : ContentRegistry::DataFormatter::getEntries()) {
|
||||
if (ImGui::MenuItem(LangEntry(unlocalizedName))) {
|
||||
ImGui::SetClipboardText(
|
||||
for (const auto &[unlocalizedName, callback] : ContentRegistry::DataFormatter::getEntries()) {
|
||||
if (ImGui::MenuItem(LangEntry(unlocalizedName))) {
|
||||
ImGui::SetClipboardText(
|
||||
callback(
|
||||
provider,
|
||||
selection->getStartAddress() + provider->getBaseAddress() + provider->getCurrentPageAddress(),
|
||||
selection->size
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.paste"_lang, (CTRLCMD_NAME + " + V"s).c_str(), false, selection.has_value()))
|
||||
pasteBytes(*selection, true);
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.paste_all"_lang, (CTRLCMD_NAME + " + "s + SHIFT_NAME + " + V"s).c_str(), false, selection.has_value()))
|
||||
pasteBytes(*selection, false);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.select_all"_lang, (CTRLCMD_NAME + " + A"s).c_str(), false, selection.has_value() && providerValid))
|
||||
ImHexApi::HexEditor::setSelection(provider->getBaseAddress(), provider->getActualSize());
|
||||
});
|
||||
|
||||
// Popups
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 1200, [&] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.set_base"_lang, nullptr, false, providerValid && provider->isReadable())) {
|
||||
this->openPopup<PopupBaseAddress>(provider->getBaseAddress());
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.resize"_lang, nullptr, false, providerValid && provider->isResizable())) {
|
||||
this->openPopup<PopupResize>(provider->getActualSize());
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.insert"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value())) {
|
||||
this->openPopup<PopupInsert>(selection->getStartAddress(), 0x00);
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.remove"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value())) {
|
||||
this->openPopup<PopupRemove>(selection->getStartAddress(), selection->getSize());
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.jump_to"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value() && selection->getSize() <= sizeof(u64))) {
|
||||
u64 value = 0;
|
||||
provider->read(selection->getStartAddress(), &value, selection->getSize());
|
||||
|
||||
if (value < provider->getBaseAddress() + provider->getActualSize()) {
|
||||
ImHexApi::HexEditor::setSelection(value, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.hex_editor.menu.edit.open_in_new_provider"_lang, nullptr, false, providerValid && provider->isResizable() && selection.has_value())) {
|
||||
auto newProvider = ImHexApi::Provider::createProvider("hex.builtin.provider.view", true);
|
||||
if (auto *viewProvider = dynamic_cast<ViewProvider*>(newProvider); viewProvider != nullptr) {
|
||||
viewProvider->setProvider(selection->getStartAddress(), selection->getSize(), selection->getProvider());
|
||||
if (viewProvider->open())
|
||||
EventManager::post<EventProviderOpened>(viewProvider);
|
||||
provider,
|
||||
selection->getStartAddress() + provider->getBaseAddress() + provider->getCurrentPageAddress(),
|
||||
selection->size
|
||||
).c_str()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* Paste */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.paste" }, 1450, CTRL + Keys::V,
|
||||
[] {
|
||||
pasteBytes(*ImHexApi::HexEditor::getSelection(), true);
|
||||
},
|
||||
ImHexApi::HexEditor::isSelectionValid);
|
||||
|
||||
/* Paste All */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.paste_all" }, 1500, CTRL + SHIFT + Keys::V,
|
||||
[] {
|
||||
pasteBytes(*ImHexApi::HexEditor::getSelection(), false);
|
||||
},
|
||||
ImHexApi::HexEditor::isSelectionValid);
|
||||
|
||||
/* Select All */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.select_all" }, 1550, CTRL + Keys::A,
|
||||
[] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
ImHexApi::HexEditor::setSelection(provider->getBaseAddress(), provider->getActualSize());
|
||||
},
|
||||
ImHexApi::HexEditor::isSelectionValid);
|
||||
|
||||
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1600);
|
||||
|
||||
/* Set Base Address */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.set_base" }, 1650, Shortcut::None,
|
||||
[this] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
this->openPopup<PopupBaseAddress>(provider->getBaseAddress());
|
||||
},
|
||||
[] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isReadable(); });
|
||||
|
||||
/* Resize */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.resize" }, 1700, Shortcut::None,
|
||||
[this] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
this->openPopup<PopupResize>(provider->getBaseAddress());
|
||||
},
|
||||
[] { return ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isResizable(); });
|
||||
|
||||
/* Insert */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.insert" }, 1750, Shortcut::None,
|
||||
[this] {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
this->openPopup<PopupInsert>(selection->getStartAddress(), 0x00);
|
||||
},
|
||||
[] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isResizable(); });
|
||||
|
||||
/* Remove */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.remove" }, 1800, Shortcut::None,
|
||||
[this] {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
this->openPopup<PopupRemove>(selection->getStartAddress(), selection->getSize());
|
||||
},
|
||||
[] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid() && ImHexApi::Provider::get()->isResizable(); });
|
||||
|
||||
/* Jump to */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.jump_to" }, 1850, Shortcut::None,
|
||||
[] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
u64 value = 0;
|
||||
provider->read(selection->getStartAddress(), &value, selection->getSize());
|
||||
|
||||
if (value < provider->getBaseAddress() + provider->getActualSize()) {
|
||||
ImHexApi::HexEditor::setSelection(value, 1);
|
||||
}
|
||||
},
|
||||
[] { return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && ImHexApi::HexEditor::getSelection()->getSize() <= sizeof(u64); });
|
||||
// Popups
|
||||
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.edit" }, 1900);
|
||||
|
||||
/* Open in new provider */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.edit", "hex.builtin.view.hex_editor.menu.edit.open_in_new_provider" }, 1950, Shortcut::None,
|
||||
[] {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
auto newProvider = ImHexApi::Provider::createProvider("hex.builtin.provider.view", true);
|
||||
if (auto *viewProvider = dynamic_cast<ViewProvider*>(newProvider); viewProvider != nullptr) {
|
||||
viewProvider->setProvider(selection->getStartAddress(), selection->getSize(), selection->getProvider());
|
||||
if (viewProvider->open())
|
||||
EventManager::post<EventProviderOpened>(viewProvider);
|
||||
}
|
||||
},
|
||||
[] { return ImHexApi::HexEditor::isSelectionValid() && ImHexApi::Provider::isValid(); });
|
||||
}
|
||||
|
||||
}
|
||||
@@ -866,108 +866,109 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void ViewPatternEditor::registerMenuItems() {
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 2000, [&, this] {
|
||||
bool providerValid = ImHexApi::Provider::isValid();
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
/* Import Pattern */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.import", "hex.builtin.menu.file.import.pattern" }, 4050, Shortcut::None,
|
||||
[this] {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
std::vector<std::fs::path> paths;
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.file.load_pattern"_lang, nullptr, false, providerValid)) {
|
||||
std::vector<std::fs::path> paths;
|
||||
for (const auto &imhexPath : fs::getDefaultPaths(fs::ImHexPath::Patterns)) {
|
||||
if (!wolv::io::fs::exists(imhexPath)) continue;
|
||||
|
||||
for (const auto &imhexPath : fs::getDefaultPaths(fs::ImHexPath::Patterns)) {
|
||||
if (!wolv::io::fs::exists(imhexPath)) continue;
|
||||
std::error_code error;
|
||||
for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) {
|
||||
if (entry.is_regular_file() && entry.path().extension() == ".hexpat") {
|
||||
paths.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::error_code error;
|
||||
for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) {
|
||||
if (entry.is_regular_file() && entry.path().extension() == ".hexpat") {
|
||||
paths.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
View::showFileChooserPopup(paths, { { "Pattern File", "hexpat" } }, false,
|
||||
[this, provider](const std::fs::path &path) {
|
||||
this->loadPatternFile(path, provider);
|
||||
});
|
||||
}, ImHexApi::Provider::isValid);
|
||||
|
||||
View::showFileChooserPopup(paths, { { "Pattern File", "hexpat" } }, false,
|
||||
[this, provider](const std::fs::path &path) {
|
||||
this->loadPatternFile(path, provider);
|
||||
});
|
||||
}
|
||||
/* Export Pattern */
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.file", "hex.builtin.menu.file.export", "hex.builtin.menu.file.export.pattern" }, 7050, Shortcut::None,
|
||||
[this] {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, { {"Pattern", "hexpat"} },
|
||||
[this](const auto &path) {
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
||||
|
||||
if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.file.save_pattern"_lang, nullptr, false, providerValid)) {
|
||||
fs::openFileBrowser(fs::DialogMode::Save, { {"Pattern", "hexpat"} },
|
||||
[this](const auto &path) {
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
||||
file.write(wolv::util::trim(this->m_textEditor.GetText()));
|
||||
});
|
||||
}, ImHexApi::Provider::isValid);
|
||||
|
||||
file.write(wolv::util::trim(this->m_textEditor.GetText()));
|
||||
});
|
||||
}
|
||||
});
|
||||
const auto appendEditorText = [this](const std::string &text){
|
||||
this->m_textEditor.SetCursorPosition(TextEditor::Coordinates { this->m_textEditor.GetTotalLines(), 0 });
|
||||
this->m_textEditor.InsertText(hex::format("\n{0}", text));
|
||||
this->m_hasUnevaluatedChanges = true;
|
||||
};
|
||||
|
||||
// Place Pattern Type...
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 3000, [this] {
|
||||
bool available = ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && this->m_runningParsers == 0;
|
||||
const auto appendVariable = [&](const std::string &type) {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
const auto &types = this->m_parserRuntime->getInternals().parser->getTypes();
|
||||
appendEditorText(hex::format("{0} {0}_at_0x{1:02X} @ 0x{1:02X};", type, selection->getStartAddress()));
|
||||
};
|
||||
|
||||
const auto appendEditorText = [this](const std::string &text){
|
||||
this->m_textEditor.SetCursorPosition(TextEditor::Coordinates { this->m_textEditor.GetTotalLines(), 0 });
|
||||
this->m_textEditor.InsertText(hex::format("\n{0}", text));
|
||||
this->m_hasUnevaluatedChanges = true;
|
||||
};
|
||||
const auto appendArray = [&](const std::string &type, size_t size) {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
const auto appendVariable = [&](const std::string &type) {
|
||||
appendEditorText(hex::format("{0} {0}_at_0x{1:02X} @ 0x{1:02X};", type, selection->getStartAddress()));
|
||||
};
|
||||
appendEditorText(hex::format("{0} {0}_array_at_0x{1:02X}[0x{2:02X}] @ 0x{1:02X};", type, selection->getStartAddress(), (selection->getSize() + (size - 1)) / size));
|
||||
};
|
||||
|
||||
const auto appendArray = [&](const std::string &type, size_t size) {
|
||||
appendEditorText(hex::format("{0} {0}_array_at_0x{1:02X}[0x{2:02X}] @ 0x{1:02X};", type, selection->getStartAddress(), (selection->getSize() + (size - 1)) / size));
|
||||
};
|
||||
constexpr static std::array<std::pair<const char *, size_t>, 21> Types = {{
|
||||
{ "u8", 1 }, { "u16", 2 }, { "u24", 3 }, { "u32", 4 }, { "u48", 6 }, { "u64", 8 }, { "u96", 12 }, { "u128", 16 },
|
||||
{ "s8", 1 }, { "s16", 2 }, { "s24", 3 }, { "s32", 4 }, { "s48", 6 }, { "s64", 8 }, { "s96", 12 }, { "s128", 16 },
|
||||
{ "float", 4 }, { "double", 8 },
|
||||
{ "bool", 1 }, { "char", 1 }, { "char16", 2 }
|
||||
}};
|
||||
|
||||
if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern"_lang, available)) {
|
||||
if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin"_lang)) {
|
||||
constexpr static std::array<std::pair<const char *, size_t>, 21> Types = {{
|
||||
{ "u8", 1 }, { "u16", 2 }, { "u24", 3 }, { "u32", 4 }, { "u48", 6 }, { "u64", 8 }, { "u96", 12 }, { "u128", 16 },
|
||||
{ "s8", 1 }, { "s16", 2 }, { "s24", 3 }, { "s32", 4 }, { "s48", 6 }, { "s64", 8 }, { "s96", 12 }, { "s128", 16 },
|
||||
{ "float", 4 }, { "double", 8 },
|
||||
{ "bool", 1 }, { "char", 1 }, { "char16", 2 }
|
||||
}};
|
||||
/* Place pattern... */
|
||||
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.place_pattern", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin" }, 3000,
|
||||
[&]{
|
||||
if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.single"_lang)) {
|
||||
for (const auto &[type, size] : Types)
|
||||
if (ImGui::MenuItem(type))
|
||||
appendVariable(type);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.single"_lang)) {
|
||||
for (const auto &[type, size] : Types)
|
||||
if (ImGui::MenuItem(type))
|
||||
appendVariable(type);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array"_lang)) {
|
||||
for (const auto &[type, size] : Types)
|
||||
if (ImGui::MenuItem(type))
|
||||
appendArray(type, size);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}, [this] {
|
||||
return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && this->m_runningParsers == 0;
|
||||
});
|
||||
|
||||
if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array"_lang)) {
|
||||
for (const auto &[type, size] : Types)
|
||||
if (ImGui::MenuItem(type))
|
||||
appendArray(type, size);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.edit", "hex.builtin.view.pattern_editor.menu.edit.place_pattern", "hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom" }, 3050,
|
||||
[&]{
|
||||
const auto &types = this->m_parserRuntime->getInternals().parser->getTypes();
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
for (const auto &[typeName, type] : types) {
|
||||
if (type->isTemplateType())
|
||||
continue;
|
||||
|
||||
bool hasPlaceableTypes = std::any_of(types.begin(), types.end(), [](const auto &type) { return !type.second->isTemplateType(); });
|
||||
if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom"_lang, hasPlaceableTypes)) {
|
||||
for (const auto &[typeName, type] : types) {
|
||||
if (type->isTemplateType())
|
||||
continue;
|
||||
createNestedMenu(hex::splitString(typeName, "::"), [&] {
|
||||
std::string variableName;
|
||||
for (char &c : hex::replaceStrings(typeName, "::", "_"))
|
||||
variableName += static_cast<char>(std::tolower(c));
|
||||
variableName += hex::format("_at_0x{:02X}", selection->getStartAddress());
|
||||
|
||||
createNestedMenu(hex::splitString(typeName, "::"), [&] {
|
||||
std::string variableName;
|
||||
for (char &c : hex::replaceStrings(typeName, "::", "_"))
|
||||
variableName += static_cast<char>(std::tolower(c));
|
||||
variableName += hex::format("_at_0x{:02X}", selection->getStartAddress());
|
||||
appendEditorText(hex::format("{0} {1} @ 0x{2:02X};", typeName, variableName, selection->getStartAddress()));
|
||||
});
|
||||
}
|
||||
}, [this] {
|
||||
const auto &types = this->m_parserRuntime->getInternals().parser->getTypes();
|
||||
bool hasPlaceableTypes = std::any_of(types.begin(), types.end(), [](const auto &type) { return !type.second->isTemplateType(); });
|
||||
|
||||
appendEditorText(hex::format("{0} {1} @ 0x{2:02X};", typeName, variableName, selection->getStartAddress()));
|
||||
});
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
});
|
||||
return ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && this->m_runningParsers == 0 && hasPlaceableTypes;
|
||||
});
|
||||
}
|
||||
|
||||
void ViewPatternEditor::registerHandlers() {
|
||||
|
||||
@@ -18,11 +18,11 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
});
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 2000, [&, this] {
|
||||
if (ImGui::MenuItem("hex.builtin.view.settings.name"_lang)) {
|
||||
TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.settings.name").c_str()); });
|
||||
this->getWindowOpenState() = true;
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 4000);
|
||||
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.settings.name"_lang }, 4050, Shortcut::None, [&, this] {
|
||||
TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.settings.name").c_str()); });
|
||||
this->getWindowOpenState() = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -26,13 +26,13 @@ namespace hex::plugin::builtin {
|
||||
using namespace std::literals::chrono_literals;
|
||||
|
||||
ViewStore::ViewStore() : View("hex.builtin.view.store.name") {
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 3000, [&, this] {
|
||||
if (ImGui::MenuItem("hex.builtin.view.store.name"_lang)) {
|
||||
if (this->m_requestStatus == RequestStatus::NotAttempted)
|
||||
this->refresh();
|
||||
TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.store.name").c_str()); });
|
||||
this->getWindowOpenState() = true;
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 2000);
|
||||
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.store.name" }, 2050, Shortcut::None, [&, this] {
|
||||
if (this->m_requestStatus == RequestStatus::NotAttempted)
|
||||
this->refresh();
|
||||
TaskManager::doLater([] { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.store.name").c_str()); });
|
||||
this->getWindowOpenState() = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,9 @@
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
ViewThemeManager::ViewThemeManager() : View("hex.builtin.view.theme_manager.name") {
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.help", 1200, [&, this] {
|
||||
if (ImGui::MenuItem("hex.builtin.view.theme_manager.name"_lang, "")) {
|
||||
this->m_viewOpen = true;
|
||||
this->getWindowOpenState() = true;
|
||||
}
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.theme_manager.name" }, 3000, Shortcut::None, [&, this] {
|
||||
this->m_viewOpen = true;
|
||||
this->getWindowOpenState() = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -543,7 +543,7 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
});
|
||||
|
||||
ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1075, [&] {
|
||||
ContentRegistry::Interface::addMenuItemSubMenu({ "hex.builtin.menu.file" }, 1200, [] {
|
||||
if (ImGui::BeginMenu("hex.builtin.menu.file.open_recent"_lang, !s_recentProvidersUpdating && !s_recentProviders.empty())) {
|
||||
// Copy to avoid changing list while iteration
|
||||
auto recentProviders = s_recentProviders;
|
||||
|
||||
Reference in New Issue
Block a user