Skip to content

Commit 3805243

Browse files
committed
Texmod: handle non-ASCII texture pack paths (UTF-8)
The file dialog returns paths as UTF-8, but the chosen-file callback built a std::filesystem::path straight from the char*, which on Windows decodes in the ANSI codepage. A pack whose name has non-ASCII characters (e.g. "@µ's_clear_line_UI_v4.8.200.tpf") was mangled into a path that didn't exist, so it silently failed to load. Decode the dialog's UTF-8 output via TextUtils::StringToWString before building the path. Also store pack paths in the INI as UTF-8 (and decode them back) and use UTF-8 for the labels/paths shown in ImGui, so non-ASCII names round-trip across restarts and render correctly instead of being lost to the ANSI codepage by path::string().
1 parent db6445f commit 3805243

1 file changed

Lines changed: 26 additions & 10 deletions

File tree

GWToolboxdll/Modules/TexmodModule.cpp

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
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

Comments
 (0)