#include "content/views/view_store.hpp" #include "hex/api/theme_manager.hpp" #include #include #include #define IMGUI_DEFINE_MATH_OPERATORS #include #include #include #include #include #include #include #include #include #include #include namespace hex::plugin::builtin { using namespace std::literals::string_literals; using namespace std::literals::chrono_literals; ViewStore::ViewStore() : View("hex.builtin.view.store.name") { 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; }); this->m_httpRequest.setTimeout(30'0000); } void ViewStore::drawStore() { ImGui::Header("hex.builtin.view.store.desc"_lang, true); bool reloading = false; if (this->m_apiRequest.valid()) { if (this->m_apiRequest.wait_for(0s) != std::future_status::ready) reloading = true; else this->parseResponse(); } ImGui::BeginDisabled(reloading); if (ImGui::Button("hex.builtin.view.store.reload"_lang)) { this->refresh(); } ImGui::EndDisabled(); if (reloading) { ImGui::SameLine(); ImGui::TextSpinner("hex.builtin.view.store.loading"_lang); } auto drawTab = [this](auto title, fs::ImHexPath pathType, auto &content, const std::function &downloadDoneCallback = []{}) { if (ImGui::BeginTabItem(title)) { if (ImGui::BeginTable("##pattern_language", 3, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_RowBg)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("hex.builtin.view.store.row.name"_lang, ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("hex.builtin.view.store.row.description"_lang, ImGuiTableColumnFlags_None); ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); ImGui::TableHeadersRow(); u32 id = 1; for (auto &entry : content) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted(entry.name.c_str()); ImGui::TableNextColumn(); ImGui::TextUnformatted(entry.description.c_str()); ImGui::TableNextColumn(); const auto buttonSize = ImVec2(100_scaled, ImGui::GetTextLineHeightWithSpacing()); ImGui::PushID(id); ImGui::BeginDisabled(this->m_download.valid() && this->m_download.wait_for(0s) != std::future_status::ready); { if (entry.downloading) { ImGui::ProgressBar(this->m_httpRequest.getProgress(), buttonSize, ""); if (this->m_download.valid() && this->m_download.wait_for(0s) == std::future_status::ready) { entry.downloading = false; auto response = this->m_download.get(); if (response.isSuccess()) { entry.installed = true; entry.hasUpdate = false; if (entry.isFolder) { Tar tar(this->m_downloadPath, Tar::Mode::Read); tar.extractAll(this->m_downloadPath.parent_path() / this->m_downloadPath.stem()); EventManager::post(this->m_downloadPath.parent_path() / this->m_downloadPath.stem()); } else { EventManager::post(this->m_downloadPath); } downloadDoneCallback(); } else log::error("Download failed! HTTP Code {}", response.getStatusCode()); this->m_download = {}; } } else { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); if (entry.hasUpdate) { if (ImGui::Button("hex.builtin.view.store.update"_lang, buttonSize)) { entry.downloading = this->download(pathType, entry.fileName, entry.link, true); } } else if (!entry.installed) { if (ImGui::Button("hex.builtin.view.store.download"_lang, buttonSize)) { entry.downloading = this->download(pathType, entry.fileName, entry.link, false); } } else { if (ImGui::Button("hex.builtin.view.store.remove"_lang, buttonSize)) { entry.installed = !this->remove(pathType, entry.fileName); } } ImGui::PopStyleVar(); } } ImGui::EndDisabled(); ImGui::PopID(); id++; } ImGui::EndTable(); } ImGui::EndTabItem(); } }; if (ImGui::BeginTabBar("storeTabs")) { drawTab("hex.builtin.view.store.tab.patterns"_lang, fs::ImHexPath::Patterns, this->m_patterns); drawTab("hex.builtin.view.store.tab.includes"_lang, fs::ImHexPath::PatternsInclude, this->m_includes); drawTab("hex.builtin.view.store.tab.magic"_lang, fs::ImHexPath::Magic, this->m_magics, []{ magic::compile(); }); drawTab("hex.builtin.view.store.tab.nodes"_lang, fs::ImHexPath::Nodes, this->m_nodes); drawTab("hex.builtin.view.store.tab.encodings"_lang, fs::ImHexPath::Encodings, this->m_encodings); drawTab("hex.builtin.view.store.tab.constants"_lang, fs::ImHexPath::Constants, this->m_constants); drawTab("hex.builtin.view.store.tab.themes"_lang, fs::ImHexPath::Themes, this->m_themes, [this]{ auto themeFile = wolv::io::File(this->m_downloadPath, wolv::io::File::Mode::Read); ThemeManager::addTheme(themeFile.readString()); }); drawTab("hex.builtin.view.store.tab.yara"_lang, fs::ImHexPath::Yara, this->m_yara); ImGui::EndTabBar(); } } void ViewStore::refresh() { // do not refresh if a refresh is already in progress if (this->m_requestStatus == RequestStatus::InProgress) return; this->m_requestStatus = RequestStatus::InProgress; this->m_patterns.clear(); this->m_includes.clear(); this->m_magics.clear(); this->m_constants.clear(); this->m_yara.clear(); this->m_encodings.clear(); this->m_nodes.clear(); this->m_themes.clear(); this->m_httpRequest.setUrl(ImHexApiURL + "/store"s); this->m_apiRequest = this->m_httpRequest.execute(); } void ViewStore::parseResponse() { auto response = this->m_apiRequest.get(); this->m_requestStatus = response.isSuccess() ? RequestStatus::Succeeded : RequestStatus::Failed; if (this->m_requestStatus == RequestStatus::Succeeded) { auto json = nlohmann::json::parse(response.getData()); auto parseStoreEntries = [](auto storeJson, const std::string &name, fs::ImHexPath pathType, std::vector &results) { // Check if the response handles the type of files if (storeJson.contains(name)) { for (auto &entry : storeJson[name]) { // Check if entry is valid if (entry.contains("name") && entry.contains("desc") && entry.contains("file") && entry.contains("url") && entry.contains("hash") && entry.contains("folder")) { // Parse entry StoreEntry storeEntry = { entry["name"], entry["desc"], entry["file"], entry["url"], entry["hash"], entry["folder"], false, false, false }; // Check if file is installed already or has an update available for (const auto &folder : fs::getDefaultPaths(pathType)) { auto path = folder / std::fs::path(storeEntry.fileName); if (wolv::io::fs::exists(path) && fs::isPathWritable(folder)) { storeEntry.installed = true; wolv::io::File file(path, wolv::io::File::Mode::Read); auto bytes = file.readVector(); auto fileHash = crypt::sha256(bytes); // Compare installed file hash with hash of repo file if (std::vector(fileHash.begin(), fileHash.end()) != crypt::decode16(storeEntry.hash)) storeEntry.hasUpdate = true; } } results.push_back(storeEntry); } } } std::sort(results.begin(), results.end(), [](const auto &lhs, const auto &rhs) { return lhs.name < rhs.name; }); }; parseStoreEntries(json, "patterns", fs::ImHexPath::Patterns, this->m_patterns); parseStoreEntries(json, "includes", fs::ImHexPath::PatternsInclude, this->m_includes); parseStoreEntries(json, "magic", fs::ImHexPath::Magic, this->m_magics); parseStoreEntries(json, "constants", fs::ImHexPath::Constants, this->m_constants); parseStoreEntries(json, "yara", fs::ImHexPath::Yara, this->m_yara); parseStoreEntries(json, "encodings", fs::ImHexPath::Encodings, this->m_encodings); parseStoreEntries(json, "nodes", fs::ImHexPath::Nodes, this->m_nodes); parseStoreEntries(json, "themes", fs::ImHexPath::Themes, this->m_themes); } this->m_apiRequest = {}; } void ViewStore::drawContent() { if (ImGui::BeginPopupModal(View::toWindowName("hex.builtin.view.store.name").c_str(), &this->getWindowOpenState())) { if (this->m_requestStatus == RequestStatus::Failed) ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed), "hex.builtin.view.store.netfailed"_lang); this->drawStore(); ImGui::EndPopup(); } else { this->getWindowOpenState() = false; } } bool ViewStore::download(fs::ImHexPath pathType, const std::string &fileName, const std::string &url, bool update) { bool downloading = false; for (const auto &path : fs::getDefaultPaths(pathType)) { if (!fs::isPathWritable(path)) continue; auto fullPath = path / std::fs::path(fileName); if (!update || wolv::io::fs::exists(fullPath)) { downloading = true; this->m_downloadPath = fullPath; this->m_httpRequest.setUrl(url); this->m_download = this->m_httpRequest.downloadFile(fullPath); break; } } if (!downloading) { PopupError::open("hex.builtin.view.store.download_error"_lang); return false; } return true; } bool ViewStore::remove(fs::ImHexPath pathType, const std::string &fileName) { bool removed = true; for (const auto &path : fs::getDefaultPaths(pathType)) { const auto filePath = path / fileName; const auto folderPath = (path / std::fs::path(fileName).stem()); wolv::io::fs::remove(filePath); wolv::io::fs::removeAll(folderPath); removed = removed && !wolv::io::fs::exists(filePath) && !wolv::io::fs::exists(folderPath); EventManager::post(filePath); } return removed; } }