2525#include < ImGuiAddons.h>
2626#include < Modules/Resources.h>
2727#include < Utils/FontLoader.h>
28+ #include < Utils/TextUtils.h>
2829
2930// gMod GitHub release shape, parsed from the releases API. Kept at namespace scope
3031// (external linkage) because glaze reflection can't bind types from an anonymous
@@ -107,6 +108,15 @@ namespace {
107108 constexpr const char * INI_PACK_PATH = " Pack" ;
108109 constexpr const char * INI_PACK_ENABLED = " PackEnabled" ;
109110
111+ // path::string() would narrow to the ANSI codepage and lose non-ASCII characters
112+ // (e.g. a "µ" in a pack name). Encode as UTF-8 instead, which round-trips through
113+ // the INI and renders correctly in ImGui. Pair with TextUtils::StringToWString to
114+ // decode back to a path.
115+ std::string PathToUtf8 (const std::filesystem::path& p)
116+ {
117+ return TextUtils::WStringToString (p.wstring ());
118+ }
119+
110120 // =========================================================================
111121 // gMod TextureClient helpers
112122 // =========================================================================
@@ -181,7 +191,7 @@ namespace {
181191 it->loaded = true ;
182192 continue ;
183193 }
184- packs.push_back ({p, p.stem (). string ( ), /* loaded=*/ true , /* external=*/ true });
194+ packs.push_back ({p, PathToUtf8 ( p.stem ()), /* loaded=*/ true , /* external=*/ true });
185195 }
186196 if (clear_missing) {
187197 for (auto & pack : packs) {
@@ -537,7 +547,7 @@ namespace {
537547 });
538548 if (it == packs.end ()) {
539549 if (create_if_not_found) {
540- packs.push_back ({path, path.stem (). string ( ), false , false });
550+ packs.push_back ({path, PathToUtf8 ( path.stem ()), false , false });
541551 return &packs.back ();
542552 }
543553 return nullptr ;
@@ -548,11 +558,11 @@ namespace {
548558 bool LoadTexturePack (const std::filesystem::path& path)
549559 {
550560 if (!std::filesystem::exists (path)) {
551- statusMessage = " File not found: " + path. string ( );
561+ statusMessage = " File not found: " + PathToUtf8 (path );
552562 return false ;
553563 }
554564 auto pack = FindPack (path, true );
555- const auto filename = pack->path .filename (). string ( );
565+ const auto filename = PathToUtf8 ( pack->path .filename ());
556566 pack->loaded = true ;
557567 if (!gmodReady) {
558568 // First pack configured but gMod isn't loaded yet: fetch/download it now.
@@ -733,7 +743,11 @@ namespace {
733743 void OnTexmodFileChosen (const char * path)
734744 {
735745 if (!path) return ;
736- std::filesystem::path p = path;
746+ // The file dialog hands back the path as UTF-8. Building a filesystem::path
747+ // straight from a char* would decode it in the ANSI codepage instead, mangling
748+ // non-ASCII names (e.g. a "µ" in the filename) into a path that doesn't exist.
749+ // Decode the UTF-8 to a wide string first so the full Unicode path survives.
750+ const std::filesystem::path p = TextUtils::StringToWString (path);
737751 if (!std::filesystem::exists (p)) return ;
738752 LoadTexturePack (p);
739753 }
@@ -849,7 +863,7 @@ namespace {
849863 ImGui::TextDisabled (" %s" , pack.label .c_str ());
850864
851865 ImGui::TableSetColumnIndex (2 );
852- const std::string pathStr = pack.path . string ( );
866+ const std::string pathStr = PathToUtf8 ( pack.path );
853867 ImGui::TextDisabled (" %s" , pathStr.c_str ());
854868 if (ImGui::IsItemHovered ()) ImGui::SetTooltip (" %s" , pathStr.c_str ());
855869
@@ -1205,7 +1219,7 @@ void TexmodModule::DrawSettingsInternal()
12051219 ImGui::Separator ();
12061220 DrawPackList ();
12071221 ImGui::Separator ();
1208- if (ImGui::Button (" Add texture pack" )) Resources::OpenFileDialog (OnTexmodFileChosen, " tpf,zip,dds" , Resources::GetSettingsFolderPath (). string ( ).c_str ());
1222+ if (ImGui::Button (" Add texture pack" )) Resources::OpenFileDialog (OnTexmodFileChosen, " tpf,zip,dds" , PathToUtf8 ( Resources::GetSettingsFolderPath ()).c_str ());
12091223 ImGui::TextDisabled (" Supported: .tpf, .zip (texmod format), .dds" );
12101224 ImGui::Separator ();
12111225 DrawTextureGallery ();
@@ -1220,10 +1234,12 @@ void TexmodModule::LoadSettings(ToolboxIni* ini)
12201234 const std::string key = std::string (INI_PACK_PATH ) + std::to_string (i);
12211235 const char * val = ini->GetValue (INI_SECTION , key.c_str (), nullptr );
12221236 if (!val || !*val) continue ;
1223- std::filesystem::path p = val;
1237+ // Stored as UTF-8 (see SaveSettings); decode to a wide path so non-ASCII names
1238+ // survive. Falls back to the ANSI codepage for any pre-existing legacy value.
1239+ std::filesystem::path p = TextUtils::StringToWString (val);
12241240 const std::string enabled_key = std::string (INI_PACK_ENABLED ) + std::to_string (i);
12251241 const bool enabled = ini->GetBoolValue (INI_SECTION , enabled_key.c_str (), false );
1226- packs.push_back ({p, p.stem (). string ( ), /* loaded=*/ enabled});
1242+ packs.push_back ({p, PathToUtf8 ( p.stem ()), /* loaded=*/ enabled});
12271243 }
12281244 // gMod is not ready yet (no device); the restored enabled flags are pushed to
12291245 // it later by RestoreLoadedPacks(). This call is a no-op until then.
@@ -1242,7 +1258,7 @@ void TexmodModule::SaveSettings(ToolboxIni* ini)
12421258 ini->SetLongValue (INI_SECTION , INI_PACK_COUNT , static_cast <long >(packs.size ()));
12431259 for (size_t i = 0 ; i < packs.size (); i++) {
12441260 const std::string key = std::string (INI_PACK_PATH ) + std::to_string (i);
1245- ini->SetValue (INI_SECTION , key.c_str (), packs[i].path . string ( ).c_str ());
1261+ ini->SetValue (INI_SECTION , key.c_str (), PathToUtf8 ( packs[i].path ).c_str ());
12461262 const std::string enabled_key = std::string (INI_PACK_ENABLED ) + std::to_string (i);
12471263 ini->SetBoolValue (INI_SECTION , enabled_key.c_str (), packs[i].loaded );
12481264 }
0 commit comments