#include #include #include #include #include #include #include #include #include #include #if defined(OS_WEB) #include #endif #include #include #include #include namespace hex { namespace ContentRegistry::Settings { [[maybe_unused]] constexpr auto SettingsFile = "settings.json"; namespace impl { struct OnChange { u32 id; OnChangeCallback callback; }; static AutoReset>>> s_onChangeCallbacks; static AutoReset s_settings; const nlohmann::json& getSettingsData() { return s_settings; } nlohmann::json& getSetting(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &defaultValue) { auto &settings = *s_settings; if (!settings.contains(unlocalizedCategory)) settings[unlocalizedCategory] = {}; if (!settings[unlocalizedCategory].contains(unlocalizedName)) settings[unlocalizedCategory][unlocalizedName] = defaultValue; if (settings[unlocalizedCategory][unlocalizedName].is_null()) settings[unlocalizedCategory][unlocalizedName] = defaultValue; return settings[unlocalizedCategory][unlocalizedName]; } #if defined(OS_WEB) void load() { char *data = (char *) MAIN_THREAD_EM_ASM_INT({ let data = localStorage.getItem("config"); return data ? stringToNewUTF8(data) : null; }); if (data == nullptr) { store(); } else { s_settings = nlohmann::json::parse(data); } for (const auto &[category, rest] : *impl::s_onChangeCallbacks) { for (const auto &[name, callbacks] : rest) { for (const auto &[id, callback] : callbacks) { try { callback(getSetting(category, name, {})); } catch (const std::exception &e) { log::error("Failed to load setting [{}/{}]: {}", category, name, e.what()); } } } } } void store() { auto data = s_settings->dump(); MAIN_THREAD_EM_ASM({ localStorage.setItem("config", UTF8ToString($0)); }, data.c_str()); } void clear() { MAIN_THREAD_EM_ASM({ localStorage.removeItem("config"); }); } #else void load() { bool loaded = false; for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { wolv::io::File file(dir / SettingsFile, wolv::io::File::Mode::Read); if (file.isValid()) { s_settings = nlohmann::json::parse(file.readString()); loaded = true; break; } } if (!loaded) store(); for (const auto &[category, rest] : *impl::s_onChangeCallbacks) { for (const auto &[name, callbacks] : rest) { for (const auto &[id, callback] : callbacks) { try { callback(getSetting(category, name, {})); } catch (const std::exception &e) { log::error("Failed to load setting [{}/{}]: {}", category, name, e.what()); } } } } } void store() { if (!s_settings.isValid()) return; const auto &settingsData = *s_settings; // During a crash settings can be empty, causing them to be overwritten. if (settingsData.empty()) { return; } const auto result = settingsData.dump(4); if (result.empty()) { return; } for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { wolv::io::File file(dir / SettingsFile, wolv::io::File::Mode::Create); if (file.isValid()) { file.writeString(result); break; } } } void clear() { for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Config)) { wolv::io::fs::remove(dir / SettingsFile); } } #endif template static T* insertOrGetEntry(std::vector &vector, const UnlocalizedString &unlocalizedName) { T *foundEntry = nullptr; for (auto &entry : vector) { if (entry.unlocalizedName == unlocalizedName) { foundEntry = &entry; break; } } if (foundEntry == nullptr) { if (unlocalizedName.empty()) foundEntry = &*vector.emplace(vector.begin(), unlocalizedName); else foundEntry = &vector.emplace_back(unlocalizedName); } return foundEntry; } static AutoReset> s_categories; const std::vector& getSettings() { return *s_categories; } Widgets::Widget* add(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedSubCategory, const UnlocalizedString &unlocalizedName, std::unique_ptr &&widget) { const auto category = insertOrGetEntry(*s_categories, unlocalizedCategory); const auto subCategory = insertOrGetEntry(category->subCategories, unlocalizedSubCategory); const auto entry = insertOrGetEntry(subCategory->entries, unlocalizedName); entry->widget = std::move(widget); return entry->widget.get(); } void printSettingReadError(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json::exception& e) { hex::log::error("Failed to read setting {}/{}: {}", unlocalizedCategory.get(), unlocalizedName.get(), e.what()); } void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value) { if (auto categoryIt = s_onChangeCallbacks->find(unlocalizedCategory); categoryIt != s_onChangeCallbacks->end()) { if (auto nameIt = categoryIt->second.find(unlocalizedName); nameIt != categoryIt->second.end()) { for (const auto &[id, callback] : nameIt->second) { try { callback(value); } catch (const nlohmann::json::exception &e) { log::error("Failed to run onChange handler for setting {}/{}: {}", unlocalizedCategory.get(), unlocalizedName.get(), e.what()); } } } } } } void setCategoryDescription(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedDescription) { const auto category = insertOrGetEntry(*impl::s_categories, unlocalizedCategory); category->unlocalizedDescription = unlocalizedDescription; } u64 onChange(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const OnChangeCallback &callback) { static u64 id = 1; (*impl::s_onChangeCallbacks)[unlocalizedCategory][unlocalizedName].emplace_back(id, callback); auto result = id; id += 1; return result; } void removeOnChangeHandler(u64 id) { bool done = false; auto categoryIt = impl::s_onChangeCallbacks->begin(); for (; categoryIt != impl::s_onChangeCallbacks->end(); ++categoryIt) { auto nameIt = categoryIt->second.begin(); for (; nameIt != categoryIt->second.end(); ++nameIt) { done = std::erase_if(nameIt->second, [id](const impl::OnChange &entry) { return entry.id == id; }) > 0; if (done) break; } if (done) { if (nameIt->second.empty()) categoryIt->second.erase(nameIt); break; } } if (done) { if (categoryIt->second.empty()) impl::s_onChangeCallbacks->erase(categoryIt); } } namespace Widgets { bool Checkbox::draw(const std::string &name) { return ImGui::Checkbox(name.c_str(), &m_value); } void Checkbox::load(const nlohmann::json &data) { if (data.is_number()) { m_value = data.get() != 0; } else if (data.is_boolean()) { m_value = data.get(); } else { log::warn("Invalid data type loaded from settings for checkbox!"); } } nlohmann::json Checkbox::store() { return m_value; } bool SliderInteger::draw(const std::string &name) { return ImGui::SliderInt(name.c_str(), &m_value, m_min, m_max); } void SliderInteger::load(const nlohmann::json &data) { if (data.is_number_integer()) { m_value = data.get(); } else { log::warn("Invalid data type loaded from settings for slider!"); } } nlohmann::json SliderInteger::store() { return m_value; } bool SliderFloat::draw(const std::string &name) { return ImGui::SliderFloat(name.c_str(), &m_value, m_min, m_max); } void SliderFloat::load(const nlohmann::json &data) { if (data.is_number()) { m_value = data.get(); } else { log::warn("Invalid data type loaded from settings for slider!"); } } nlohmann::json SliderFloat::store() { return m_value; } ColorPicker::ColorPicker(ImColor defaultColor) { m_value = { defaultColor.Value.x, defaultColor.Value.y, defaultColor.Value.z, defaultColor.Value.w }; } bool ColorPicker::draw(const std::string &name) { return ImGui::ColorEdit4(name.c_str(), m_value.data(), ImGuiColorEditFlags_NoInputs); } void ColorPicker::load(const nlohmann::json &data) { if (data.is_number()) { const ImColor color(data.get()); m_value = { color.Value.x, color.Value.y, color.Value.z, color.Value.w }; } else { log::warn("Invalid data type loaded from settings for color picker!"); } } nlohmann::json ColorPicker::store() { const ImColor color(m_value[0], m_value[1], m_value[2], m_value[3]); return static_cast(color); } ImColor ColorPicker::getColor() const { return { m_value[0], m_value[1], m_value[2], m_value[3] }; } bool DropDown::draw(const std::string &name) { auto preview = ""; if (static_cast(m_value) < m_items.size()) preview = m_items[m_value].c_str(); bool changed = false; if (ImGui::BeginCombo(name.c_str(), Lang(preview))) { int index = 0; for (const auto &item : m_items) { const bool selected = index == m_value; if (ImGui::Selectable(Lang(item), selected)) { m_value = index; changed = true; } if (selected) ImGui::SetItemDefaultFocus(); index += 1; } ImGui::EndCombo(); } return changed; } void DropDown::load(const nlohmann::json &data) { m_value = 0; int defaultItemIndex = 0; int index = 0; for (const auto &item : m_settingsValues) { if (item == m_defaultItem) defaultItemIndex = index; if (item == data) { m_value = index; return; } index += 1; } m_value = defaultItemIndex; } nlohmann::json DropDown::store() { if (m_value == -1) return m_defaultItem; if (static_cast(m_value) >= m_items.size()) return m_defaultItem; return m_settingsValues[m_value]; } const nlohmann::json& DropDown::getValue() const { return m_settingsValues[m_value]; } bool TextBox::draw(const std::string &name) { return ImGui::InputText(name.c_str(), m_value); } void TextBox::load(const nlohmann::json &data) { if (data.is_string()) { m_value = data.get(); } else { log::warn("Invalid data type loaded from settings for text box!"); } } nlohmann::json TextBox::store() { return m_value; } bool FilePicker::draw(const std::string &name) { bool changed = false; auto pathString = wolv::util::toUTF8String(m_path); if (ImGui::InputText("##font_path", pathString)) { changed = true; } ImGui::SameLine(); if (ImGuiExt::IconButton("...", ImGui::GetStyleColorVec4(ImGuiCol_Text))) { changed = fs::openFileBrowser(fs::DialogMode::Open, { { "TTF Font", "ttf" }, { "OTF Font", "otf" } }, [&](const std::fs::path &path) { pathString = wolv::util::toUTF8String(path); }); } ImGui::SameLine(); ImGuiExt::TextFormatted("{}", name); if (changed) { m_path = pathString; } return changed; } void FilePicker::load(const nlohmann::json &data) { if (data.is_string()) { m_path = data.get(); } else { log::warn("Invalid data type loaded from settings for file picker!"); } } nlohmann::json FilePicker::store() { return m_path; } bool Label::draw(const std::string& name) { ImGui::NewLine(); ImGui::TextUnformatted(name.c_str()); return false; } } } namespace ContentRegistry::CommandPaletteCommands { namespace impl { static AutoReset> s_entries; const std::vector& getEntries() { return *s_entries; } static AutoReset> s_handlers; const std::vector& getHandlers() { return *s_handlers; } } void add(Type type, const std::string &command, const UnlocalizedString &unlocalizedDescription, const impl::DisplayCallback &displayCallback, const impl::ExecuteCallback &executeCallback) { log::debug("Registered new command palette command: {}", command); impl::s_entries->push_back(impl::Entry { type, command, unlocalizedDescription, displayCallback, executeCallback }); } void addHandler(Type type, const std::string &command, const impl::QueryCallback &queryCallback, const impl::DisplayCallback &displayCallback) { log::debug("Registered new command palette command handler: {}", command); impl::s_handlers->push_back(impl::Handler { type, command, queryCallback, displayCallback }); } } namespace ContentRegistry::PatternLanguage { namespace impl { static AutoReset> s_visualizers; const std::map& getVisualizers() { return *s_visualizers; } static AutoReset> s_inlineVisualizers; const std::map& getInlineVisualizers() { return *s_inlineVisualizers; } static AutoReset> s_pragmas; const std::map& getPragmas() { return *s_pragmas; } static AutoReset> s_functions; const std::vector& getFunctions() { return *s_functions; } } static std::string getFunctionName(const pl::api::Namespace &ns, const std::string &name) { std::string functionName; for (auto &scope : ns) functionName += scope + "::"; functionName += name; return functionName; } pl::PatternLanguage& getRuntime() { static PerProvider runtime; AT_FIRST_TIME { runtime.setOnCreateCallback([](prv::Provider *provider, pl::PatternLanguage &runtime) { configureRuntime(runtime, provider); }); }; return *runtime; } std::mutex& getRuntimeLock() { static std::mutex runtimeLock; return runtimeLock; } void configureRuntime(pl::PatternLanguage &runtime, prv::Provider *provider) { runtime.reset(); if (provider != nullptr) { runtime.setDataSource(provider->getBaseAddress(), provider->getActualSize(), [provider](u64 offset, u8 *buffer, size_t size) { provider->read(offset, buffer, size); }, [provider](u64 offset, const u8 *buffer, size_t size) { if (provider->isWritable()) provider->write(offset, buffer, size); } ); } runtime.setIncludePaths(fs::getDefaultPaths(fs::ImHexPath::PatternsInclude) | fs::getDefaultPaths(fs::ImHexPath::Patterns)); for (const auto &[ns, name, paramCount, callback, dangerous] : impl::getFunctions()) { if (dangerous) runtime.addDangerousFunction(ns, name, paramCount, callback); else runtime.addFunction(ns, name, paramCount, callback); } for (const auto &[name, callback] : impl::getPragmas()) { runtime.addPragma(name, callback); } runtime.addDefine("__IMHEX__"); runtime.addDefine("__IMHEX_VERSION__", ImHexApi::System::getImHexVersion()); } void addPragma(const std::string &name, const pl::api::PragmaHandler &handler) { log::debug("Registered new pattern language pragma: {}", name); (*impl::s_pragmas)[name] = handler; } void addFunction(const pl::api::Namespace &ns, const std::string &name, pl::api::FunctionParameterCount parameterCount, const pl::api::FunctionCallback &func) { log::debug("Registered new pattern language function: {}", getFunctionName(ns, name)); impl::s_functions->push_back({ ns, name, parameterCount, func, false }); } void addDangerousFunction(const pl::api::Namespace &ns, const std::string &name, pl::api::FunctionParameterCount parameterCount, const pl::api::FunctionCallback &func) { log::debug("Registered new dangerous pattern language function: {}", getFunctionName(ns, name)); impl::s_functions->push_back({ ns, name, parameterCount, func, true }); } void addVisualizer(const std::string &name, const impl::VisualizerFunctionCallback &function, pl::api::FunctionParameterCount parameterCount) { log::debug("Registered new pattern visualizer function: {}", name); (*impl::s_visualizers)[name] = impl::Visualizer { parameterCount, function }; } void addInlineVisualizer(const std::string &name, const impl::VisualizerFunctionCallback &function, pl::api::FunctionParameterCount parameterCount) { log::debug("Registered new inline pattern visualizer function: {}", name); (*impl::s_inlineVisualizers)[name] = impl::Visualizer { parameterCount, function }; } } namespace ContentRegistry::Views { namespace impl { static AutoReset>> s_views; const std::map>& getEntries() { return *s_views; } void add(std::unique_ptr &&view) { log::debug("Registered new view: {}", view->getUnlocalizedName().get()); s_views->insert({ view->getUnlocalizedName(), std::move(view) }); } } View* getViewByName(const UnlocalizedString &unlocalizedName) { auto &views = *impl::s_views; if (views.contains(unlocalizedName)) return views[unlocalizedName].get(); else return nullptr; } } namespace ContentRegistry::Tools { namespace impl { static AutoReset> s_tools; const std::vector& getEntries() { return *s_tools; } } void add(const UnlocalizedString &unlocalizedName, const impl::Callback &function) { log::debug("Registered new tool: {}", unlocalizedName.get()); impl::s_tools->emplace_back(impl::Entry { unlocalizedName, function }); } } namespace ContentRegistry::DataInspector { namespace impl { static AutoReset> s_entries; const std::vector& getEntries() { return *s_entries; } } void add(const UnlocalizedString &unlocalizedName, size_t requiredSize, impl::GeneratorFunction displayGeneratorFunction, std::optional editingFunction) { log::debug("Registered new data inspector format: {}", unlocalizedName.get()); impl::s_entries->push_back({ unlocalizedName, requiredSize, requiredSize, std::move(displayGeneratorFunction), std::move(editingFunction) }); } void add(const UnlocalizedString &unlocalizedName, size_t requiredSize, size_t maxSize, impl::GeneratorFunction displayGeneratorFunction, std::optional editingFunction) { log::debug("Registered new data inspector format: {}", unlocalizedName.get()); impl::s_entries->push_back({ unlocalizedName, requiredSize, maxSize, std::move(displayGeneratorFunction), std::move(editingFunction) }); } } namespace ContentRegistry::DataProcessorNode { namespace impl { static AutoReset> s_nodes; const std::vector& getEntries() { return *s_nodes; } void add(const Entry &entry) { log::debug("Registered new data processor node type: [{}]: {}", entry.unlocalizedCategory.get(), entry.unlocalizedName.get()); s_nodes->push_back(entry); } } void addSeparator() { impl::s_nodes->push_back({ "", "", [] { return nullptr; } }); } } namespace ContentRegistry::Language { namespace impl { static AutoReset> s_languages; const std::map& getLanguages() { return *s_languages; } static AutoReset>> s_definitions; const std::map>& getLanguageDefinitions() { return *s_definitions; } } void addLocalization(const nlohmann::json &data) { if (!data.is_object()) return; if (!data.contains("code") || !data.contains("country") || !data.contains("language") || !data.contains("translations")) { log::error("Localization data is missing required fields!"); return; } const auto &code = data["code"]; const auto &country = data["country"]; const auto &language = data["language"]; const auto &translations = data["translations"]; if (!code.is_string() || !country.is_string() || !language.is_string() || !translations.is_object()) { log::error("Localization data has invalid fields!"); return; } if (data.contains("fallback")) { const auto &fallback = data["fallback"]; if (fallback.is_boolean() && fallback.get()) LocalizationManager::impl::setFallbackLanguage(code.get()); } impl::s_languages->insert({ code.get(), hex::format("{} ({})", language.get(), country.get()) }); std::map translationDefinitions; for (auto &[key, value] : translations.items()) { if (!value.is_string()) { log::error("Localization data has invalid fields!"); continue; } translationDefinitions[key] = value.get(); } (*impl::s_definitions)[code.get()].emplace_back(std::move(translationDefinitions)); } } namespace ContentRegistry::Interface { namespace impl { static AutoReset> s_mainMenuItems; const std::multimap& getMainMenuItems() { return *s_mainMenuItems; } static AutoReset> s_menuItems; const std::multimap& getMenuItems() { return *s_menuItems; } std::multimap& getMenuItemsMutable() { return *s_menuItems; } static AutoReset> s_welcomeScreenEntries; const std::vector& getWelcomeScreenEntries() { return *s_welcomeScreenEntries; } static AutoReset> s_footerItems; const std::vector& getFooterItems() { return *s_footerItems; } static AutoReset> s_toolbarItems; const std::vector& getToolbarItems() { return *s_toolbarItems; } static AutoReset> s_sidebarItems; const std::vector& getSidebarItems() { return *s_sidebarItems; } static AutoReset> s_titlebarButtons; const std::vector& getTitlebarButtons() { return *s_titlebarButtons; } } void registerMainMenuItem(const UnlocalizedString &unlocalizedName, u32 priority) { log::debug("Registered new main menu item: {}", unlocalizedName.get()); impl::s_mainMenuItems->insert({ priority, { unlocalizedName } }); } void addMenuItem(const std::vector &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, const impl::SelectedCallback &selectedCallback, View *view) { addMenuItem(unlocalizedMainMenuNames, "", priority, shortcut, function, enabledCallback, selectedCallback, view); } void addMenuItem(const std::vector &unlocalizedMainMenuNames, const Icon &icon, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, View *view) { addMenuItem(unlocalizedMainMenuNames, icon, priority, shortcut, function, enabledCallback, []{ return false; }, view); } void addMenuItem(const std::vector &unlocalizedMainMenuNames, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, View *view) { addMenuItem(unlocalizedMainMenuNames, "", priority, shortcut, function, enabledCallback, []{ return false; }, view); } void addMenuItem(const std::vector &unlocalizedMainMenuNames, const Icon &icon, u32 priority, const Shortcut &shortcut, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback, const impl::SelectedCallback &selectedCallback, View *view) { log::debug("Added new menu item to menu {} with priority {}", unlocalizedMainMenuNames[0].get(), priority); Icon coloredIcon = icon; if (coloredIcon.color == 0x00) coloredIcon.color = ImGuiCustomCol_ToolbarGray; impl::s_menuItems->insert({ priority, impl::MenuItem { unlocalizedMainMenuNames, coloredIcon, std::make_unique(shortcut), view, function, enabledCallback, selectedCallback, -1 } }); if (shortcut != Shortcut::None) { if (shortcut.isLocal() && view != nullptr) ShortcutManager::addShortcut(view, shortcut, unlocalizedMainMenuNames.back(), function); else ShortcutManager::addGlobalShortcut(shortcut, unlocalizedMainMenuNames.back(), function); } } void addMenuItemSubMenu(std::vector unlocalizedMainMenuNames, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) { addMenuItemSubMenu(std::move(unlocalizedMainMenuNames), "", priority, function, enabledCallback); } void addMenuItemSubMenu(std::vector unlocalizedMainMenuNames, const char *icon, u32 priority, const impl::MenuCallback &function, const impl::EnabledCallback& enabledCallback) { log::debug("Added new menu item sub menu to menu {} with priority {}", unlocalizedMainMenuNames[0].get(), priority); unlocalizedMainMenuNames.emplace_back(impl::SubMenuValue); impl::s_menuItems->insert({ priority, impl::MenuItem { unlocalizedMainMenuNames, icon, std::make_unique(), nullptr, function, enabledCallback, []{ return false; }, -1 } }); } void addMenuItemSeparator(std::vector unlocalizedMainMenuNames, u32 priority) { unlocalizedMainMenuNames.emplace_back(impl::SeparatorValue); impl::s_menuItems->insert({ priority, impl::MenuItem { unlocalizedMainMenuNames, "", std::make_unique(), nullptr, []{}, []{ return true; }, []{ return false; }, -1 } }); } void addWelcomeScreenEntry(const impl::DrawCallback &function) { impl::s_welcomeScreenEntries->push_back(function); } void addFooterItem(const impl::DrawCallback &function) { impl::s_footerItems->push_back(function); } void addToolbarItem(const impl::DrawCallback &function) { impl::s_toolbarItems->push_back(function); } void addMenuItemToToolbar(const UnlocalizedString& unlocalizedName, ImGuiCustomCol color) { const auto maxIndex = std::ranges::max_element(impl::getMenuItems(), [](const auto &a, const auto &b) { return a.second.toolbarIndex < b.second.toolbarIndex; })->second.toolbarIndex; for (auto &[priority, menuItem] : *impl::s_menuItems) { if (menuItem.unlocalizedNames.back() == unlocalizedName) { menuItem.toolbarIndex = maxIndex + 1; menuItem.icon.color = color; break; } } } void addSidebarItem(const std::string &icon, const impl::DrawCallback &function, const impl::EnabledCallback &enabledCallback) { impl::s_sidebarItems->push_back({ icon, function, enabledCallback }); } void addTitleBarButton(const std::string &icon, const UnlocalizedString &unlocalizedTooltip, const impl::ClickCallback &function) { impl::s_titlebarButtons->push_back({ icon, unlocalizedTooltip, function }); } } namespace ContentRegistry::Provider { namespace impl { void add(const std::string &typeName, ProviderCreationFunction creationFunction) { (void)RequestCreateProvider::subscribe([expectedName = typeName, creationFunction](const std::string &name, bool skipLoadInterface, bool selectProvider, prv::Provider **provider) { if (name != expectedName) return; auto newProvider = creationFunction(); if (provider != nullptr) { *provider = newProvider.get(); ImHexApi::Provider::add(std::move(newProvider), skipLoadInterface, selectProvider); } }); } static AutoReset> s_providerNames; const std::vector& getEntries() { return *s_providerNames; } void addProviderName(const UnlocalizedString &unlocalizedName) { log::debug("Registered new provider: {}", unlocalizedName.get()); s_providerNames->push_back(unlocalizedName); } } } namespace ContentRegistry::DataFormatter { namespace impl { static AutoReset> s_entries; const std::vector& getEntries() { return *s_entries; } } void add(const UnlocalizedString &unlocalizedName, const impl::Callback &callback) { log::debug("Registered new data formatter: {}", unlocalizedName.get()); impl::s_entries->push_back({ unlocalizedName, callback }); } } namespace ContentRegistry::FileHandler { namespace impl { static AutoReset> s_entries; const std::vector& getEntries() { return *s_entries; } } void add(const std::vector &extensions, const impl::Callback &callback) { for (const auto &extension : extensions) log::debug("Registered new data handler for extensions: {}", extension); impl::s_entries->push_back({ extensions, callback }); } } namespace ContentRegistry::HexEditor { const int DataVisualizer::TextInputFlags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_AlwaysOverwrite; bool DataVisualizer::drawDefaultScalarEditingTextBox(u64 address, const char *format, ImGuiDataType dataType, u8 *data, ImGuiInputTextFlags flags) const { struct UserData { u8 *data; i32 maxChars; bool editingDone; }; UserData userData = { .data = data, .maxChars = this->getMaxCharsPerCell(), .editingDone = false }; ImGui::PushID(reinterpret_cast(address)); ImGuiExt::InputScalarCallback("##editing_input", dataType, data, format, flags | TextInputFlags | ImGuiInputTextFlags_CallbackEdit, [](ImGuiInputTextCallbackData *data) -> int { auto &userData = *static_cast(data->UserData); if (data->CursorPos >= userData.maxChars) userData.editingDone = true; data->Buf[userData.maxChars] = 0x00; return 0; }, &userData); ImGui::PopID(); return userData.editingDone || ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_Escape); } bool DataVisualizer::drawDefaultTextEditingTextBox(u64 address, std::string &data, ImGuiInputTextFlags flags) const { struct UserData { std::string *data; i32 maxChars; bool editingDone; }; UserData userData = { .data = &data, .maxChars = this->getMaxCharsPerCell(), .editingDone = false }; ImGui::PushID(reinterpret_cast(address)); ImGui::InputText("##editing_input", data.data(), data.size() + 1, flags | TextInputFlags | ImGuiInputTextFlags_CallbackEdit, [](ImGuiInputTextCallbackData *data) -> int { auto &userData = *static_cast(data->UserData); userData.data->resize(data->BufSize); if (data->BufTextLen >= userData.maxChars) userData.editingDone = true; return 0; }, &userData); ImGui::PopID(); return userData.editingDone || ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::IsKeyPressed(ImGuiKey_Escape); } namespace impl { static AutoReset>> s_visualizers; const std::vector>& getVisualizers() { return *s_visualizers; } static AutoReset>> s_miniMapVisualizers; const std::vector>& getMiniMapVisualizers() { return *s_miniMapVisualizers; } void addDataVisualizer(std::shared_ptr &&visualizer) { s_visualizers->emplace_back(std::move(visualizer)); } } std::shared_ptr getVisualizerByName(const UnlocalizedString &unlocalizedName) { for (const auto &visualizer : impl::getVisualizers()) { if (visualizer->getUnlocalizedName() == unlocalizedName) return visualizer; } return nullptr; } void addMiniMapVisualizer(UnlocalizedString unlocalizedName, MiniMapVisualizer::Callback callback) { impl::s_miniMapVisualizers->emplace_back(std::make_shared(std::move(unlocalizedName), std::move(callback))); } } namespace ContentRegistry::Diffing { namespace impl { static AutoReset>> s_algorithms; const std::vector>& getAlgorithms() { return *s_algorithms; } void addAlgorithm(std::unique_ptr &&hash) { s_algorithms->emplace_back(std::move(hash)); } } } namespace ContentRegistry::Hashes { namespace impl { static AutoReset>> s_hashes; const std::vector>& getHashes() { return *s_hashes; } void add(std::unique_ptr &&hash) { s_hashes->emplace_back(std::move(hash)); } } } namespace ContentRegistry::BackgroundServices { namespace impl { class Service { public: Service(std::string name, std::jthread thread) : m_name(std::move(name)), m_thread(std::move(thread)) { } Service(const Service&) = delete; Service(Service &&) = default; ~Service() { m_thread.request_stop(); if (m_thread.joinable()) m_thread.join(); } Service& operator=(const Service&) = delete; Service& operator=(Service &&) = default; [[nodiscard]] const std::string& getName() const { return m_name; } [[nodiscard]] const std::jthread& getThread() const { return m_thread; } private: std::string m_name; std::jthread m_thread; }; static AutoReset> s_services; const std::vector& getServices() { return *s_services; } void stopServices() { s_services->clear(); } } void registerService(const UnlocalizedString &unlocalizedName, const impl::Callback &callback) { log::debug("Registered new background service: {}", unlocalizedName.get()); impl::s_services->emplace_back( unlocalizedName, std::jthread([=](const std::stop_token &stopToken){ TaskManager::setCurrentThreadName(Lang(unlocalizedName)); while (!stopToken.stop_requested()) { callback(); std::this_thread::sleep_for(std::chrono::milliseconds(50)); } }) ); } } namespace ContentRegistry::CommunicationInterface { namespace impl { static AutoReset> s_endpoints; const std::map& getNetworkEndpoints() { return *s_endpoints; } } void registerNetworkEndpoint(const std::string &endpoint, const impl::NetworkCallback &callback) { log::debug("Registered new network endpoint: {}", endpoint); impl::s_endpoints->insert({ endpoint, callback }); } } namespace ContentRegistry::Experiments { namespace impl { static AutoReset> s_experiments; const std::map& getExperiments() { return *s_experiments; } } void addExperiment(const std::string &experimentName, const UnlocalizedString &unlocalizedName, const UnlocalizedString &unlocalizedDescription) { auto &experiments = *impl::s_experiments; if (experiments.contains(experimentName)) { log::error("Experiment with name '{}' already exists!", experimentName); return; } experiments[experimentName] = impl::Experiment { .unlocalizedName = unlocalizedName, .unlocalizedDescription = unlocalizedDescription, .enabled = false }; } void enableExperiement(const std::string &experimentName, bool enabled) { auto &experiments = *impl::s_experiments; if (!experiments.contains(experimentName)) { log::error("Experiment with name '{}' does not exist!", experimentName); return; } experiments[experimentName].enabled = enabled; } [[nodiscard]] bool isExperimentEnabled(const std::string &experimentName) { auto &experiments = *impl::s_experiments; if (!experiments.contains(experimentName)) { log::error("Experiment with name '{}' does not exist!", experimentName); return false; } return experiments[experimentName].enabled; } } namespace ContentRegistry::Reports { namespace impl { static AutoReset> s_generators; const std::vector& getGenerators() { return *s_generators; } } void addReportProvider(impl::Callback callback) { impl::s_generators->push_back(impl::ReportGenerator { std::move(callback ) }); } } namespace ContentRegistry::DataInformation { namespace impl { static AutoReset> s_informationSectionConstructors; const std::vector& getInformationSectionConstructors() { return *s_informationSectionConstructors; } void addInformationSectionCreator(const CreateCallback &callback) { s_informationSectionConstructors->emplace_back(callback); } } } }