#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace hex::plugin::builtin { static ImGui::Texture s_bannerTexture, s_backdropTexture; static std::fs::path s_safetyBackupPath; static std::string s_tipOfTheDay; struct RecentProvider { std::string displayName; std::string type; std::fs::path filePath; nlohmann::json data; bool operator==(const RecentProvider &other) const { return displayName == other.displayName && type == other.type && data == other.data; } }; static std::vector s_recentProviders; static void updateRecentProviders() { s_recentProviders.clear(); // Query all recent providers std::vector recentFiles; for (const auto &folder : fs::getDefaultPaths(fs::ImHexPath::Recent)) { for (const auto &entry : std::fs::directory_iterator(folder)) { if (entry.is_regular_file()) recentFiles.push_back(entry.path()); } } // Sort recent provider files by last modified time std::sort(recentFiles.begin(), recentFiles.end(), [](const auto &a, const auto &b) { return std::fs::last_write_time(a) > std::fs::last_write_time(b); }); for (u32 i = 0; i < recentFiles.size() && s_recentProviders.size() < 5; i++) { auto &path = recentFiles[i]; try { auto jsonData = nlohmann::json::parse(fs::File(path, fs::File::Mode::Read).readString()); s_recentProviders.push_back(RecentProvider { .displayName = jsonData["displayName"], .type = jsonData["type"], .filePath = path, .data = jsonData }); } catch (...) { } } // De-duplicate recent providers s_recentProviders.erase(std::unique(s_recentProviders.begin(), s_recentProviders.end()), s_recentProviders.end()); } static void loadRecentProvider(const RecentProvider &recentProvider) { auto *provider = ImHexApi::Provider::createProvider(recentProvider.type, true); if (provider != nullptr) { provider->loadSettings(recentProvider.data); if (!provider->open() || !provider->isAvailable()) { View::showErrorPopup("hex.builtin.popup.error.open"_lang); ImHexApi::Tasks::doLater([provider] { ImHexApi::Provider::remove(provider); }); return; } updateRecentProviders(); } } static void loadDefaultLayout() { auto layouts = ContentRegistry::Interface::getLayouts(); if (!layouts.empty()) { for (auto &[viewName, view] : ContentRegistry::Views::getEntries()) { view->getWindowOpenState() = false; } auto dockId = ImHexApi::System::getMainDockSpaceId(); ImGui::DockBuilderRemoveNode(dockId); ImGui::DockBuilderAddNode(dockId); layouts.front().callback(dockId); ImGui::DockBuilderFinish(dockId); } } static bool isAnyViewOpen() { const auto &views = ContentRegistry::Views::getEntries(); return std::any_of(views.begin(), views.end(), [](const std::pair &entry) { return entry.second->getWindowOpenState(); }); } static void drawPopups() { ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F)); ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size / 3, ImGuiCond_Appearing); if (ImGui::BeginPopup("hex.builtin.welcome.tip_of_the_day"_lang)) { ImGui::Header("hex.builtin.welcome.tip_of_the_day"_lang, true); ImGui::TextFormattedWrapped("{}", s_tipOfTheDay.c_str()); ImGui::NewLine(); static bool dontShowAgain = false; if (ImGui::Checkbox("hex.builtin.common.dont_show_again"_lang, &dontShowAgain)) { ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.show_tips", !dontShowAgain); } ImGui::SameLine((ImGui::GetMainViewport()->Size / 3 - ImGui::CalcTextSize("hex.builtin.common.close"_lang) - ImGui::GetStyle().FramePadding).x); if (ImGui::Button("hex.builtin.common.close"_lang)) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } // Popup for if there is a safety backup present because ImHex crashed ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F)); if (ImGui::BeginPopupModal("hex.builtin.welcome.safety_backup.title"_lang, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { ImGui::TextUnformatted("hex.builtin.welcome.safety_backup.desc"_lang); ImGui::NewLine(); auto width = ImGui::GetWindowWidth(); ImGui::SetCursorPosX(width / 9); if (ImGui::Button("hex.builtin.welcome.safety_backup.restore"_lang, ImVec2(width / 3, 0))) { ProjectFile::load(s_safetyBackupPath); fs::remove(s_safetyBackupPath); ImGui::CloseCurrentPopup(); } ImGui::SameLine(); ImGui::SetCursorPosX(width / 9 * 5); if (ImGui::Button("hex.builtin.welcome.safety_backup.delete"_lang, ImVec2(width / 3, 0))) { fs::remove(s_safetyBackupPath); ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } } static void drawWelcomeScreenContent() { const auto availableSpace = ImGui::GetContentRegionAvail(); ImGui::Image(s_bannerTexture, s_bannerTexture.size() / (2 * (1.0F / ImHexApi::System::getGlobalScale()))); ImGui::Indent(); if (ImGui::BeginTable("Welcome Left", 1, ImGuiTableFlags_NoBordersInBody, ImVec2(availableSpace.x / 2, 0))) { ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 3); ImGui::TableNextColumn(); ImGui::TextFormattedWrapped("A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM."); ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 6); ImGui::TableNextColumn(); ImGui::UnderlinedText("hex.builtin.welcome.header.start"_lang); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5_scaled); { if (ImGui::IconHyperlink(ICON_VS_NEW_FILE, "hex.builtin.welcome.start.create_file"_lang)) EventManager::post("Create File"); if (ImGui::IconHyperlink(ICON_VS_GO_TO_FILE, "hex.builtin.welcome.start.open_file"_lang)) EventManager::post("Open File"); if (ImGui::IconHyperlink(ICON_VS_NOTEBOOK, "hex.builtin.welcome.start.open_project"_lang)) EventManager::post("Open Project"); if (ImGui::IconHyperlink(ICON_VS_TELESCOPE, "hex.builtin.welcome.start.open_other"_lang)) ImGui::OpenPopup("hex.builtin.welcome.start.popup.open_other"_lang); } ImGui::SetNextWindowPos(ImGui::GetWindowPos() + ImGui::GetCursorPos()); if (ImGui::BeginPopup("hex.builtin.welcome.start.popup.open_other"_lang)) { for (const auto &unlocalizedProviderName : ContentRegistry::Provider::getEntries()) { if (ImGui::Hyperlink(LangEntry(unlocalizedProviderName))) { ImHexApi::Provider::createProvider(unlocalizedProviderName); ImGui::CloseCurrentPopup(); } } ImGui::EndPopup(); } ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 9); ImGui::TableNextColumn(); ImGui::UnderlinedText("hex.builtin.welcome.start.recent"_lang); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5_scaled); { for (const auto &recentProvider : s_recentProviders) { ImGui::PushID(&recentProvider); ON_SCOPE_EXIT { ImGui::PopID(); }; if (ImGui::BulletHyperlink(recentProvider.displayName.c_str())) { loadRecentProvider(recentProvider); break; } } } if (ImHexApi::System::getInitArguments().contains("update-available")) { ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 5); ImGui::TableNextColumn(); ImGui::UnderlinedText("hex.builtin.welcome.header.update"_lang); { if (ImGui::DescriptionButton("hex.builtin.welcome.update.title"_lang, hex::format("hex.builtin.welcome.update.desc"_lang, ImHexApi::System::getInitArguments()["update-available"]).c_str(), ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) hex::openWebpage("hex.builtin.welcome.update.link"_lang); } } ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 6); ImGui::TableNextColumn(); ImGui::UnderlinedText("hex.builtin.welcome.header.help"_lang); ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5_scaled); { if (ImGui::IconHyperlink(ICON_VS_GITHUB, "hex.builtin.welcome.help.repo"_lang)) hex::openWebpage("hex.builtin.welcome.help.repo.link"_lang); if (ImGui::IconHyperlink(ICON_VS_ORGANIZATION, "hex.builtin.welcome.help.gethelp"_lang)) hex::openWebpage("hex.builtin.welcome.help.gethelp.link"_lang); if (ImGui::IconHyperlink(ICON_VS_COMMENT_DISCUSSION, "hex.builtin.welcome.help.discord"_lang)) hex::openWebpage("hex.builtin.welcome.help.discord.link"_lang); } ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 5); ImGui::TableNextColumn(); ImGui::UnderlinedText("hex.builtin.welcome.header.plugins"_lang); { const auto &plugins = PluginManager::getPlugins(); if (!plugins.empty()) { if (ImGui::BeginTable("plugins", 4, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit, ImVec2((ImGui::GetContentRegionAvail().x * 5) / 6, ImGui::GetTextLineHeightWithSpacing() * 5))) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("hex.builtin.welcome.plugins.plugin"_lang, ImGuiTableColumnFlags_WidthStretch, 0.2); ImGui::TableSetupColumn("hex.builtin.welcome.plugins.author"_lang, ImGuiTableColumnFlags_WidthStretch, 0.2); ImGui::TableSetupColumn("hex.builtin.welcome.plugins.desc"_lang, ImGuiTableColumnFlags_WidthStretch, 0.6); ImGui::TableSetupColumn("##loaded", ImGuiTableColumnFlags_WidthFixed, ImGui::GetTextLineHeight()); ImGui::TableHeadersRow(); ImGuiListClipper clipper; clipper.Begin(plugins.size()); while (clipper.Step()) { for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { const auto &plugin = plugins[i]; ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted(plugin.getPluginName().c_str()); ImGui::TableNextColumn(); ImGui::TextUnformatted(plugin.getPluginAuthor().c_str()); ImGui::TableNextColumn(); ImGui::TextUnformatted(plugin.getPluginDescription().c_str()); ImGui::TableNextColumn(); ImGui::TextUnformatted(plugin.isLoaded() ? ICON_VS_CHECK : ICON_VS_CLOSE); } } clipper.End(); ImGui::EndTable(); } } } ImGui::EndTable(); } ImGui::SameLine(); if (ImGui::BeginTable("Welcome Right", 1, ImGuiTableFlags_NoBordersInBody, ImVec2(availableSpace.x / 2, 0))) { ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 5); ImGui::TableNextColumn(); ImGui::UnderlinedText("hex.builtin.welcome.header.customize"_lang); { if (ImGui::DescriptionButton("hex.builtin.welcome.customize.settings.title"_lang, "hex.builtin.welcome.customize.settings.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) EventManager::post("Settings"); } ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 5); ImGui::TableNextColumn(); ImGui::UnderlinedText("hex.builtin.welcome.header.learn"_lang); { if (ImGui::DescriptionButton("hex.builtin.welcome.learn.latest.title"_lang, "hex.builtin.welcome.learn.latest.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) hex::openWebpage("hex.builtin.welcome.learn.latest.link"_lang); if (ImGui::DescriptionButton("hex.builtin.welcome.learn.pattern.title"_lang, "hex.builtin.welcome.learn.pattern.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) hex::openWebpage("hex.builtin.welcome.learn.pattern.link"_lang); if (ImGui::DescriptionButton("hex.builtin.welcome.learn.plugins.title"_lang, "hex.builtin.welcome.learn.plugins.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) hex::openWebpage("hex.builtin.welcome.learn.plugins.link"_lang); } auto extraWelcomeScreenEntries = ContentRegistry::Interface::getWelcomeScreenEntries(); if (!extraWelcomeScreenEntries.empty()) { ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetTextLineHeightWithSpacing() * 5); ImGui::TableNextColumn(); ImGui::UnderlinedText("hex.builtin.welcome.header.various"_lang); { for (const auto &callback : extraWelcomeScreenEntries) callback(); } } ImGui::EndTable(); } ImGui::SetCursorPos(ImVec2(ImGui::GetContentRegionAvail().x, 0)); if (ImGui::Hyperlink("X")) { auto provider = ImHexApi::Provider::createProvider("hex.builtin.provider.null"); if (provider != nullptr) (void)provider->open(); } } static void drawWelcomeScreen() { if (ImGui::Begin("ImHexDockSpace")) { if (!ImHexApi::Provider::isValid()) { static char title[256]; ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", ImGui::GetCurrentWindow()->Name, ImGui::GetID("ImHexMainDock")); if (ImGui::Begin(title)) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10_scaled, 10_scaled)); if (ImGui::BeginChild("Welcome Screen", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoScrollWithMouse)) { drawWelcomeScreenContent(); } ImGui::EndChild(); ImGui::PopStyleVar(); } ImGui::End(); } } ImGui::End(); drawPopups(); } static void drawNoViewsBackground() { if (isAnyViewOpen() && ImHexApi::Provider::isValid()) return; if (ImGui::Begin("ImHexDockSpace")) { static char title[256]; ImFormatString(title, IM_ARRAYSIZE(title), "%s/DockSpace_%08X", ImGui::GetCurrentWindow()->Name, ImGui::GetID("ImHexMainDock")); if (ImGui::Begin(title)) { ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10_scaled, 10_scaled)); if (ImGui::BeginChild("NoViewsBackground", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysUseWindowPadding | ImGuiWindowFlags_NoScrollWithMouse)) { auto imageSize = scaled(ImVec2(350, 350)); auto pos = (ImGui::GetContentRegionAvail() - imageSize) / 2; ImGui::SetCursorPos(pos); ImGui::Image(s_backdropTexture, imageSize); } ImGui::EndChild(); ImGui::PopStyleVar(); } ImGui::End(); } ImGui::End(); } void createWelcomeScreen() { updateRecentProviders(); (void)EventManager::subscribe(drawWelcomeScreen); (void)EventManager::subscribe(drawNoViewsBackground); (void)EventManager::subscribe([]() { { auto theme = ContentRegistry::Settings::getSetting("hex.builtin.setting.interface", "hex.builtin.setting.interface.color"); if (theme.is_number()) { static int lastTheme = 0; if (const int thisTheme = theme.get(); thisTheme != lastTheme) { EventManager::post(thisTheme); lastTheme = thisTheme; } } } { auto language = ContentRegistry::Settings::getSetting("hex.builtin.setting.interface", "hex.builtin.setting.interface.language"); if (language.is_string()) { LangEntry::loadLanguage(static_cast(language)); } else { // If no language is specified, fall back to English. LangEntry::loadLanguage("en-US"); } } { auto targetFps = ContentRegistry::Settings::getSetting("hex.builtin.setting.interface", "hex.builtin.setting.interface.fps"); if (targetFps.is_number()) ImHexApi::System::setTargetFPS(targetFps); } }); (void)EventManager::subscribe([](u32 theme) { auto changeTexture = [&](const std::string &path, const ImGui::Texture &texture) { auto textureData = romfs::get(path); auto oldTexture = texture; auto newTexture = ImGui::LoadImageFromMemory(reinterpret_cast(textureData.data()), textureData.size()); if (oldTexture.valid()) { ImGui::UnloadImage(oldTexture); } return newTexture; }; switch (theme) { default: case 1: /* Dark theme */ { ImGui::StyleColorsDark(); ImGui::StyleCustomColorsDark(); ImPlot::StyleColorsDark(); s_bannerTexture = changeTexture("banner_dark.png", s_bannerTexture); s_backdropTexture = changeTexture("backdrop_dark.png", s_backdropTexture); break; } case 2: /* Light theme */ { ImGui::StyleColorsLight(); ImGui::StyleCustomColorsLight(); ImPlot::StyleColorsLight(); s_bannerTexture = changeTexture("banner_light.png", s_bannerTexture); s_backdropTexture = changeTexture("backdrop_light.png", s_backdropTexture); break; } case 3: /* Classic theme */ { ImGui::StyleColorsClassic(); ImGui::StyleCustomColorsClassic(); ImPlot::StyleColorsClassic(); s_bannerTexture = changeTexture("banner_dark.png", s_bannerTexture); s_backdropTexture = changeTexture("backdrop_dark.png", s_backdropTexture); break; } } ImGui::GetStyle().Colors[ImGuiCol_DockingEmptyBg] = ImGui::GetStyle().Colors[ImGuiCol_WindowBg]; ImGui::GetStyle().Colors[ImGuiCol_TitleBg] = ImGui::GetStyle().Colors[ImGuiCol_MenuBarBg]; ImGui::GetStyle().Colors[ImGuiCol_TitleBgActive] = ImGui::GetStyle().Colors[ImGuiCol_MenuBarBg]; ImGui::GetStyle().Colors[ImGuiCol_TitleBgCollapsed] = ImGui::GetStyle().Colors[ImGuiCol_MenuBarBg]; if (!s_bannerTexture.valid()) { log::error("Failed to load banner texture!"); } }); (void)EventManager::subscribe([](prv::Provider *provider) { { auto recentPath = fs::getDefaultPaths(fs::ImHexPath::Recent).front(); auto fileName = hex::format("{:%y%m%d_%H%M%S}.json", fmt::gmtime(std::chrono::system_clock::now())); fs::File recentFile(recentPath / fileName, fs::File::Mode::Create); recentFile.write(provider->storeSettings().dump(4)); } updateRecentProviders(); }); EventManager::subscribe([](auto) { if (!isAnyViewOpen()) loadDefaultLayout(); }); ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 1075, [&] { if (ImGui::BeginMenu("hex.builtin.menu.file.open_recent"_lang, !s_recentProviders.empty())) { // Copy to avoid changing list while iteration auto recentProviders = s_recentProviders; for (auto &recentProvider : recentProviders) { if (ImGui::MenuItem(recentProvider.displayName.c_str())) { loadRecentProvider(recentProvider); } } ImGui::Separator(); if (ImGui::MenuItem("hex.builtin.menu.file.clear_recent"_lang)) { s_recentProviders.clear(); // Remove all recent files for (const auto &recentPath : fs::getDefaultPaths(fs::ImHexPath::Recent)) for (const auto &entry : std::fs::directory_iterator(recentPath)) std::fs::remove(entry.path()); } ImGui::EndMenu(); } }); constexpr auto CrashBackupFileName = "crash_backup.hexproj"; for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Config)) { if (auto filePath = std::fs::path(path) / CrashBackupFileName; fs::exists(filePath)) { s_safetyBackupPath = filePath; ImHexApi::Tasks::doLater([] { ImGui::OpenPopup("hex.builtin.welcome.safety_backup.title"_lang); }); } } if (ImHexApi::System::getInitArguments().contains("tip-of-the-day")) { s_tipOfTheDay = ImHexApi::System::getInitArguments()["tip-of-the-day"]; bool showTipOfTheDay = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.show_tips", 1); if (showTipOfTheDay) ImHexApi::Tasks::doLater([] { ImGui::OpenPopup("hex.builtin.welcome.tip_of_the_day"_lang); }); } } }