diff --git a/lib/external/libwolv b/lib/external/libwolv index 1063613e87100..25907ccf64596 160000 --- a/lib/external/libwolv +++ b/lib/external/libwolv @@ -1 +1 @@ -Subproject commit 1063613e87100910dfae254ae9af0111203c768b +Subproject commit 25907ccf6459658b1f610eec0aaee155730d1459 diff --git a/lib/external/pattern_language b/lib/external/pattern_language index 9adb36901a6b2..eadb17038c88c 160000 --- a/lib/external/pattern_language +++ b/lib/external/pattern_language @@ -1 +1 @@ -Subproject commit 9adb36901a6b292f7f097bc79d1eab69c5144f21 +Subproject commit eadb17038c88c0603f7b77f7c5d520911b338eb1 diff --git a/lib/libimhex/include/hex/api/content_registry/settings.hpp b/lib/libimhex/include/hex/api/content_registry/settings.hpp index 2bca5775c70f9..a590da7f9e727 100644 --- a/lib/libimhex/include/hex/api/content_registry/settings.hpp +++ b/lib/libimhex/include/hex/api/content_registry/settings.hpp @@ -278,8 +278,6 @@ EXPORT_MODULE namespace hex { Widgets::Widget* add(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedSubCategory, const UnlocalizedString &unlocalizedName, std::unique_ptr &&widget); void printSettingReadError(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json::exception &e); - - void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value); } template T> @@ -316,6 +314,8 @@ EXPORT_MODULE namespace hex { nlohmann::json m_value; }; + void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value); + template requires (!(std::is_reference_v || std::is_const_v)) [[nodiscard]] T read(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, T defaultValue) { auto setting = impl::getSetting(unlocalizedCategory, unlocalizedName, defaultValue); @@ -337,7 +337,7 @@ EXPORT_MODULE namespace hex { template requires (!(std::is_reference_v || std::is_const_v)) void write(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, T value) { impl::getSetting(unlocalizedCategory, unlocalizedName, value) = value; - impl::runOnChangeHandlers(unlocalizedCategory, unlocalizedName, value); + runOnChangeHandlers(unlocalizedCategory, unlocalizedName, value); impl::store(); } diff --git a/lib/libimhex/include/hex/api/localization_manager.hpp b/lib/libimhex/include/hex/api/localization_manager.hpp index be8c40b7685ca..5b231939fc84d 100644 --- a/lib/libimhex/include/hex/api/localization_manager.hpp +++ b/lib/libimhex/include/hex/api/localization_manager.hpp @@ -11,6 +11,11 @@ #include #include +// Forwards +namespace wolv::util { + class Locale; +} + EXPORT_MODULE namespace hex { struct UnlocalizedString; @@ -35,6 +40,8 @@ EXPORT_MODULE namespace hex { void addLanguages(const std::string_view &languageList, std::function callback); void setLanguage(const LanguageId &languageId); [[nodiscard]] const LanguageId& getSelectedLanguageId(); + [[nodiscard]] wolv::util::Locale getSelectedLocale(); + void setSelectedLocale(const LanguageId &languageId, bool longDate = false); [[nodiscard]] const std::string& get(const LanguageId& languageId, const UnlocalizedString &unlocalizedString); [[nodiscard]] const std::map& getLanguageDefinitions(); [[nodiscard]] const LanguageDefinition& getLanguageDefinition(const LanguageId &languageId); diff --git a/lib/libimhex/source/api/content_registry.cpp b/lib/libimhex/source/api/content_registry.cpp index 10696655572a0..ef68f40fd7f82 100644 --- a/lib/libimhex/source/api/content_registry.cpp +++ b/lib/libimhex/source/api/content_registry.cpp @@ -83,20 +83,6 @@ namespace hex { } } - 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()); - } - } - } - } - } - static AutoReset s_settings; const nlohmann::json& getSettingsData() { return s_settings; @@ -288,6 +274,20 @@ namespace hex { category->unlocalizedDescription = unlocalizedDescription; } + void runOnChangeHandlers(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const nlohmann::json &value) { + if (auto categoryIt = impl::s_onChangeCallbacks->find(unlocalizedCategory); categoryIt != impl::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()); + } + } + } + } + } + u64 onChange(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName, const OnChangeCallback &callback) { static u64 id = 1; (*impl::s_onChangeCallbacks)[unlocalizedCategory][unlocalizedName].emplace_back(id, callback); @@ -698,6 +698,13 @@ namespace hex { runtime.setOnCreateCallback([](prv::Provider *provider, pl::PatternLanguage &runtime) { configureRuntime(runtime, provider); }); + + // Handle language change + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", [](const ContentRegistry::Settings::SettingsValue&) { + for (auto &pl : runtime.all()) { + pl.setLocale(hex::LocalizationManager::getSelectedLocale()); + } + }); }; return *runtime; @@ -712,6 +719,8 @@ namespace hex { void configureRuntime(pl::PatternLanguage &runtime, prv::Provider *provider) { runtime.reset(); + runtime.setLocale(hex::LocalizationManager::getSelectedLocale()); + if (provider != nullptr) { runtime.setDataSource(provider->getBaseAddress(), provider->getActualSize(), [provider](u64 offset, u8 *buffer, size_t size) { @@ -724,6 +733,7 @@ namespace hex { ); } + runtime.setIncludePaths(paths::PatternsInclude.read() | paths::Patterns.read()); for (const auto &[ns, name, paramCount, callback, dangerous] : impl::getFunctions()) { diff --git a/lib/libimhex/source/api/localization_manager.cpp b/lib/libimhex/source/api/localization_manager.cpp index 06c65cf30c3c2..8d050c51303f8 100644 --- a/lib/libimhex/source/api/localization_manager.cpp +++ b/lib/libimhex/source/api/localization_manager.cpp @@ -7,7 +7,9 @@ #include #include +#include #include +#include namespace hex { @@ -15,6 +17,11 @@ namespace hex { constexpr static auto FallbackLanguageId = "en-US"; + namespace { + std::shared_mutex s_localeLock; + wolv::util::Locale s_locale; + } + namespace { AutoReset> s_languageDefinitions; @@ -133,7 +140,8 @@ namespace hex { void setLanguage(const LanguageId &languageId) { if (languageId == "native") { setLanguage(hex::getOSLanguage().value_or(FallbackLanguageId)); - s_selectedLanguageId = languageId; + // s_selectedLanguageId = languageId; + // /\-- Was this a bug, or have I made one? return; } @@ -150,6 +158,16 @@ namespace hex { return *s_selectedLanguageId; } + [[nodiscard]] wolv::util::Locale getSelectedLocale() { + std::shared_lock lock(s_localeLock); + return s_locale; + } + + void setSelectedLocale(const LanguageId &languageId, bool longDate) { + std::unique_lock lock(s_localeLock); + s_locale.set(languageId, longDate); + } + [[nodiscard]] const std::string& get(const LanguageId &languageId, const UnlocalizedString &unlocalizedString) { static AutoReset currentLanguageId; static AutoReset> loadedLocalization; diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index de0f16aeee484..658dec0bbc2ec 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -552,6 +552,8 @@ "hex.builtin.setting.interface.fps.unlocked": "Unlocked", "hex.builtin.setting.interface.fps.native": "Native", "hex.builtin.setting.interface.language": "Language", + "hex.builtin.setting.interface.locale": "Locale", + "hex.builtin.setting.interface.longdate": "Use long dates", "hex.builtin.setting.interface.multi_windows": "Enable Multi Window support", "hex.builtin.setting.interface.randomize_window_title": "Use a randomized Window Title", "hex.builtin.setting.interface.scaling_factor": "Scaling", diff --git a/plugins/builtin/source/content/data_inspector.cpp b/plugins/builtin/source/content/data_inspector.cpp index a7711865a4c0c..b17035081726d 100644 --- a/plugins/builtin/source/content/data_inspector.cpp +++ b/plugins/builtin/source/content/data_inspector.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -21,6 +22,8 @@ #include #include #include +#include +#include namespace hex::plugin::builtin { @@ -33,6 +36,9 @@ namespace hex::plugin::builtin { u8 data4[8]; }; + const std::string s_invalid = "Invalid"; + const std::string s_canNotFormat = "Can not format"; + template static ContentRegistry::DataInspector::impl::EditingFunction stringToUnsigned() requires(sizeof(T) <= sizeof(u64)) { return ContentRegistry::DataInspector::EditWidget::TextInput([](const std::string &value, std::endian endian) -> std::vector { @@ -754,82 +760,56 @@ namespace hex::plugin::builtin { }; }); -#if defined(OS_WINDOWS) - - ContentRegistry::DataInspector::add("hex.builtin.inspector.time32", sizeof(u32), [](auto buffer, auto endian, auto style) { + ContentRegistry::DataInspector::add("hex.builtin.inspector.time32", sizeof(i32), [](auto buffer, auto endian, auto style) { std::ignore = style; - time_t endianAdjustedTime = hex::changeEndianness(*reinterpret_cast(buffer.data()), endian); - - std::string value; - try { - auto time = std::gmtime(&endianAdjustedTime); - if (time == nullptr) { - value = "Invalid"; - } else { - value = fmt::format("{0:%a, %d.%m.%Y %H:%M:%S}", *time); - } - } catch (fmt::format_error &) { - value = "Invalid"; - } - - return [value] { ImGui::TextUnformatted(value.c_str()); return value; }; - }); + time_t endianAdjustedTime = hex::changeEndianness(*reinterpret_cast(buffer.data()), endian); - ContentRegistry::DataInspector::add("hex.builtin.inspector.time64", sizeof(u64), [](auto buffer, auto endian, auto style) { - std::ignore = style; - - time_t endianAdjustedTime = hex::changeEndianness(*reinterpret_cast(buffer.data()), endian); + const wolv::util::Locale &lc = LocalizationManager::getSelectedLocale(); std::string value; - try { - auto time = std::gmtime(&endianAdjustedTime); - if (time == nullptr) { - value = "Invalid"; - } else { - value = fmt::format("{0:%a, %d.%m.%Y %H:%M:%S}", *time); - } - } catch (fmt::format_error &) { - value = "Invalid"; + using wolv::util::DTOpts; + auto optval = wolv::util::formatTT(lc, endianAdjustedTime, DTOpts::TT32 | DTOpts::DandT); + if (optval) { + value = optval.value(); + } + else { + value = s_canNotFormat; } return [value] { ImGui::TextUnformatted(value.c_str()); return value; }; }); -#else - ContentRegistry::DataInspector::add("hex.builtin.inspector.time", sizeof(time_t), [](auto buffer, auto endian, auto style) { std::ignore = style; time_t endianAdjustedTime = hex::changeEndianness(*reinterpret_cast(buffer.data()), endian); + const wolv::util::Locale &lc = LocalizationManager::getSelectedLocale(); + std::string value; - try { - auto time = std::gmtime(&endianAdjustedTime); - if (time == nullptr) { - value = "Invalid"; - } else { - value = fmt::format("{0:%a, %d.%m.%Y %H:%M:%S}", *time); - } - } catch (const fmt::format_error &e) { - value = "Invalid"; + using wolv::util::DTOpts; + auto optval = wolv::util::formatTT(lc, endianAdjustedTime, DTOpts::TT64 | DTOpts::DandT); + if (optval) { + value = optval.value(); + } + else { + value = s_canNotFormat; } return [value] { ImGui::TextUnformatted(value.c_str()); return value; }; }); -#endif - struct DOSDate { - unsigned day : 5; - unsigned month : 4; - unsigned year : 7; + u16 day : 5; + u16 month : 4; + u16 year : 7; }; struct DOSTime { - unsigned seconds : 5; - unsigned minutes : 6; - unsigned hours : 5; + u16 seconds : 5; + u16 minutes : 6; + u16 hours : 5; }; ContentRegistry::DataInspector::add("hex.builtin.inspector.dos_date", sizeof(DOSDate), [](auto buffer, auto endian, auto style) { @@ -839,7 +819,33 @@ namespace hex::plugin::builtin { std::memcpy(&date, buffer.data(), sizeof(DOSDate)); date = hex::changeEndianness(date, endian); - auto value = fmt::format("{}/{}/{}", u8(date.day), u8(date.month), u8(date.year) + 1980); + bool ok = false; + std::string value; + if ( (date.day>=1 && date.day<=31) && (date.month>=1 && date.month<=12) ) { + std::tm tm{}; + tm.tm_year = date.year + 80; + tm.tm_mon = date.month - 1; + tm.tm_mday = date.day; + +#if defined(OS_WINDOWS) + time_t tt = _mkgmtime(&tm); +#else + time_t tt = timegm(&tm); +#endif + if (tt != -1) { + const wolv::util::Locale &lc = LocalizationManager::getSelectedLocale(); + + using wolv::util::DTOpts; + auto optval = wolv::util::formatTT(lc, tt, DTOpts::TT64 | DTOpts::D); + if (optval) { + value = optval.value(); + ok = true; + } + } + } + if (!ok) { + value = s_invalid; + } return [value] { ImGui::TextUnformatted(value.c_str()); return value; }; }); @@ -851,7 +857,26 @@ namespace hex::plugin::builtin { std::memcpy(&time, buffer.data(), sizeof(DOSTime)); time = hex::changeEndianness(time, endian); - auto value = fmt::format("{:02}:{:02}:{:02}", u8(time.hours), u8(time.minutes), u8(time.seconds) * 2); + bool ok = false; + std::string value; + if ( (time.hours>=0 && time.hours<=23) && + (time.minutes>=0 && time.minutes<=59) && + (time.seconds>=0 && time.seconds<=29) ) + { + time_t tt = time.hours*60*60 + time.minutes*60 + time.seconds*2; + + const wolv::util::Locale &lc = LocalizationManager::getSelectedLocale(); + + using wolv::util::DTOpts; + auto optval = wolv::util::formatTT(lc, tt, DTOpts::TT64 | DTOpts::T); + if (optval) { + value = optval.value(); + ok = true; + } + } + if (!ok) { + value = s_invalid; + } return [value] { ImGui::TextUnformatted(value.c_str()); return value; }; }); diff --git a/plugins/builtin/source/content/settings_entries.cpp b/plugins/builtin/source/content/settings_entries.cpp index 9822b938e6513..eb4f5d2941e51 100644 --- a/plugins/builtin/source/content/settings_entries.cpp +++ b/plugins/builtin/source/content/settings_entries.cpp @@ -20,6 +20,7 @@ #include #include +#include #include @@ -868,10 +869,48 @@ for (const auto &path : m_paths) { languageCodes.emplace_back(languageCode); } - ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "hex.builtin.setting.interface.language", languageNames, languageCodes, "en-US"); + auto &itfLang = ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "hex.builtin.setting.interface.language", languageNames, languageCodes, "en-US"); + + auto installedLocales = wolv::util::enumLocales(); + std::vector localeNames; + for (const std::string &lc : installedLocales) { + wolv::util::LocaleName name(lc); + localeNames.push_back(fmt::format("{} - {}", name.displayName(), lc)); + } + std::vector installedLocalesJSON(installedLocales.begin(), installedLocales.end()); + ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "hex.builtin.setting.interface.locale", localeNames, installedLocalesJSON, "en-US"); + + itfLang.setChangedCallback([](auto &) { + static bool firstTime = true; + if (firstTime) { + firstTime = false; + return; + } + + auto val = ContentRegistry::Settings::read( + "hex.builtin.setting.interface", + "hex.builtin.setting.interface.language", + "en-US" + ); + + if (val== "native") { + val = getOSLanguage().value_or("en-US"); + } + + ContentRegistry::Settings::write( + "hex.builtin.setting.interface", + "hex.builtin.setting.interface.locale", + val + ); + }); } +#if defined(OS_WINDOWS) + ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "hex.builtin.setting.interface.longdate", false); +#endif + ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.language", "hex.builtin.setting.interface.wiki_explain_language", "en"); + ContentRegistry::Settings::add("hex.builtin.setting.interface", "hex.builtin.setting.interface.window", "hex.builtin.setting.interface.fps"); #if defined (OS_LINUX) diff --git a/plugins/builtin/source/content/views/view_data_inspector.cpp b/plugins/builtin/source/content/views/view_data_inspector.cpp index c09281e876d65..0b8a527101fe7 100644 --- a/plugins/builtin/source/content/views/view_data_inspector.cpp +++ b/plugins/builtin/source/content/views/view_data_inspector.cpp @@ -30,6 +30,12 @@ namespace hex::plugin::builtin { using NumberDisplayStyle = ContentRegistry::DataInspector::NumberDisplayStyle; ViewDataInspector::ViewDataInspector() : View::Window("hex.builtin.view.data_inspector.name", ICON_VS_INSPECT) { + // Handle language change + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.locale", [this](const ContentRegistry::Settings::SettingsValue&) { + // Invalidate inspector rows + m_shouldInvalidate = true; + }); + // Handle region selection EventRegionSelected::subscribe(this, [this](const auto ®ion) { // Save current selection diff --git a/plugins/builtin/source/content/welcome_screen.cpp b/plugins/builtin/source/content/welcome_screen.cpp index 334722ef8f2c6..0f2f0a00a6fb2 100644 --- a/plugins/builtin/source/content/welcome_screen.cpp +++ b/plugins/builtin/source/content/welcome_screen.cpp @@ -546,7 +546,7 @@ namespace hex::plugin::builtin { if (ImGuiExt::DimmedIconToggle(onIcon.c_str(), offIcon.c_str(), &state)) { ContentRegistry::Settings::write("hex.builtin.settings.quick_settings", unlocalizedTooltip, state); } - if (id % 5 > 0) + if (id % 5 == 0) ImGui::SameLine(); ImGui::SetItemTooltip("%s", Lang(unlocalizedTooltip).get()); ImGui::PopID(); @@ -657,6 +657,34 @@ namespace hex::plugin::builtin { if (language != LocalizationManager::getSelectedLanguageId()) LocalizationManager::setLanguage(language); }); + + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.locale", [](const ContentRegistry::Settings::SettingsValue &value) { + auto language = value.get("en-US"); + + bool longDate = false; +#if defined(OS_WINDOWS) + longDate = ContentRegistry::Settings::read( + "hex.builtin.setting.interface", + "hex.builtin.setting.interface.longdate", + "en-US" + ); +#endif + + LocalizationManager::setSelectedLocale(language, longDate); + }); + +#if defined(OS_WINDOWS) + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.longdate", [](const ContentRegistry::Settings::SettingsValue &) { + // We consider this part of the locale + auto loc = ContentRegistry::Settings::read( + "hex.builtin.setting.interface", + "hex.builtin.setting.interface.locale", + "en-US" + ); + ContentRegistry::Settings::runOnChangeHandlers("hex.builtin.setting.interface", "hex.builtin.setting.interface.locale", loc); + }); +#endif + ContentRegistry::Settings::onChange("hex.builtin.setting.interface", "hex.builtin.setting.interface.fps", [](const ContentRegistry::Settings::SettingsValue &value) { ImHexApi::System::setTargetFPS(static_cast(value.get(14))); });