Consolidate the host's UTF-8/wide conversions into util/utf8.hpp
Five files each carried their own copy of the WideCharToMultiByte / MultiByteToWideChar UTF-8 conversion (injection_panel, audio_overrides, imgui_layer's to_utf8, main's harness widen, and vk_layer_setup's inline form). Replace them all with coop::narrow / coop::widen from one header. audio_panel's image_basename dropped its lossy `c & 0x7F` ASCII mask for the proper narrow(), so a non-ASCII game exe name is no longer mangled in log lines.
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include <mmreg.h> // WAVE_FORMAT_* (needs windows.h first)
|
||||
|
||||
#include "coop/tool_paths.hpp"
|
||||
#include "util/utf8.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
@@ -22,32 +23,8 @@ std::wstring to_lower(std::wstring s)
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// UTF-8 round-trip so a non-ASCII image name (e.g. a CJK game exe) survives persist/reload and
|
||||
// can't collide with another name; for ASCII names (the common case) UTF-8 is byte-identical.
|
||||
std::string narrow(const std::wstring& w)
|
||||
{
|
||||
if (w.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), nullptr, 0, nullptr, nullptr);
|
||||
std::string s(static_cast<std::size_t>(n), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), s.data(), n, nullptr, nullptr);
|
||||
return s;
|
||||
}
|
||||
|
||||
std::wstring widen(const std::string& s)
|
||||
{
|
||||
if (s.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), nullptr, 0);
|
||||
std::wstring w(static_cast<std::size_t>(n), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), w.data(), n);
|
||||
return w;
|
||||
}
|
||||
// A non-ASCII image name (e.g. a CJK game exe) round-trips through UTF-8 so it survives
|
||||
// persist/reload; ASCII names (the common case) are byte-identical. See util/utf8.hpp.
|
||||
} // namespace
|
||||
|
||||
AudioOverrideStore::AudioOverrideStore(std::wstring path) : path_(std::move(path))
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mmreg.h>
|
||||
|
||||
#include "ui/app_chrome.hpp"
|
||||
#include "util/utf8.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
@@ -69,18 +70,11 @@ ImVec4 audio_format_state_color(std::uint32_t state)
|
||||
}
|
||||
}
|
||||
|
||||
// Basename of an image path, narrowed to ASCII for a log line.
|
||||
// Basename of an image path (UTF-8), for a log line.
|
||||
std::string image_basename(const std::wstring& image_path)
|
||||
{
|
||||
const std::size_t slash = image_path.find_last_of(L"\\/");
|
||||
const std::wstring w = slash == std::wstring::npos ? image_path : image_path.substr(slash + 1);
|
||||
std::string out;
|
||||
out.reserve(w.size());
|
||||
for (wchar_t c : w)
|
||||
{
|
||||
out.push_back(static_cast<char>(c & 0x7F));
|
||||
}
|
||||
return out;
|
||||
return narrow(slash == std::wstring::npos ? image_path : image_path.substr(slash + 1));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -9,27 +9,11 @@
|
||||
#include "coop/dpi.hpp"
|
||||
#include "coop/tool_paths.hpp"
|
||||
#include "ui/app_chrome.hpp"
|
||||
#include "util/utf8.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
// ImGui's file IO treats IniFilename as UTF-8 (it converts to wide for _wfopen), so
|
||||
// the path must be UTF-8 rather than the system ANSI code page.
|
||||
std::string to_utf8(const std::wstring& w)
|
||||
{
|
||||
if (w.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int n = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), nullptr, 0, nullptr, nullptr);
|
||||
std::string s(static_cast<std::size_t>(n), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), s.data(), n, nullptr, nullptr);
|
||||
return s;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ImGuiLayer::~ImGuiLayer()
|
||||
{
|
||||
if (initialized_)
|
||||
@@ -52,7 +36,9 @@ bool ImGuiLayer::init(HWND hwnd, ID3D11Device* device, ID3D11DeviceContext* cont
|
||||
// computed default on startup (which would clobber the restored positions).
|
||||
const std::wstring ini_w = exe_directory() + L"coop_layout.ini";
|
||||
const bool had_layout = GetFileAttributesW(ini_w.c_str()) != INVALID_FILE_ATTRIBUTES;
|
||||
ini_path_ = to_utf8(ini_w);
|
||||
// ImGui treats IniFilename as UTF-8 (it converts to wide for _wfopen), so narrow to UTF-8, not
|
||||
// the system ANSI code page.
|
||||
ini_path_ = narrow(ini_w);
|
||||
io.IniFilename = ini_path_.c_str();
|
||||
set_layout_persisted(had_layout);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "inject/injector.hpp"
|
||||
#include "ui/app_chrome.hpp"
|
||||
#include "ui/text_match.hpp"
|
||||
#include "util/utf8.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
@@ -21,18 +22,6 @@ const ImVec4 kGreen(0.4f, 1.0f, 0.4f, 1.0f);
|
||||
const ImVec4 kRed(1.0f, 0.45f, 0.4f, 1.0f);
|
||||
const ImVec4 kGrey(0.7f, 0.7f, 0.7f, 1.0f);
|
||||
|
||||
std::string narrow(const std::wstring& w)
|
||||
{
|
||||
if (w.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int len = WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), nullptr, 0, nullptr, nullptr);
|
||||
std::string out(static_cast<std::size_t>(len), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), out.data(), len, nullptr, nullptr);
|
||||
return out;
|
||||
}
|
||||
|
||||
// Case-insensitive equality of two image names (e.g. "game.exe").
|
||||
bool iequals_name(const std::wstring& a, const std::wstring& b)
|
||||
{
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include "log_panel.hpp"
|
||||
#include "test_harness.hpp"
|
||||
#include "ui/app_chrome.hpp"
|
||||
#include "util/utf8.hpp"
|
||||
#include "vk_layer_setup.hpp"
|
||||
|
||||
namespace
|
||||
@@ -91,17 +92,7 @@ void draw_screenshot_toast(double seconds_since, const std::string& name)
|
||||
}
|
||||
|
||||
#ifdef COOP_TEST_HARNESS
|
||||
std::wstring widen(const std::string& s)
|
||||
{
|
||||
if (s.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), nullptr, 0);
|
||||
std::wstring w(static_cast<std::size_t>(n), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), w.data(), n);
|
||||
return w;
|
||||
}
|
||||
using coop::widen;
|
||||
|
||||
// Run a test-harness command on the main thread, hitting the same code the UI buttons do.
|
||||
// Returns a one-line response the driver reads back.
|
||||
|
||||
39
host/src/util/utf8.hpp
Normal file
39
host/src/util/utf8.hpp
Normal file
@@ -0,0 +1,39 @@
|
||||
// UTF-8 <-> wide-string conversion, shared by the host code that bridges Win32's UTF-16
|
||||
// (image names, window titles, paths) and the UTF-8 the ImGui overlay / .ini files use.
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace coop
|
||||
{
|
||||
|
||||
// UTF-16 -> UTF-8.
|
||||
inline std::string narrow(const std::wstring& w)
|
||||
{
|
||||
if (w.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int n =
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), nullptr, 0, nullptr, nullptr);
|
||||
std::string s(static_cast<std::size_t>(n), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, w.c_str(), static_cast<int>(w.size()), s.data(), n, nullptr, nullptr);
|
||||
return s;
|
||||
}
|
||||
|
||||
// UTF-8 -> UTF-16.
|
||||
inline std::wstring widen(const std::string& s)
|
||||
{
|
||||
if (s.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
const int n = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), nullptr, 0);
|
||||
std::wstring w(static_cast<std::size_t>(n), L'\0');
|
||||
MultiByteToWideChar(CP_UTF8, 0, s.c_str(), static_cast<int>(s.size()), w.data(), n);
|
||||
return w;
|
||||
}
|
||||
|
||||
} // namespace coop
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include "coop/tool_paths.hpp"
|
||||
#include "util/utf8.hpp"
|
||||
|
||||
namespace coop
|
||||
{
|
||||
@@ -33,11 +34,7 @@ bool register_vk_layer(const std::wstring& target_image)
|
||||
{
|
||||
const std::size_t slash = target_image.find_last_of(L"\\/");
|
||||
const std::wstring base = slash == std::wstring::npos ? target_image : target_image.substr(slash + 1);
|
||||
const int n = WideCharToMultiByte(CP_UTF8, 0, base.c_str(), static_cast<int>(base.size()), nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
std::string utf8(static_cast<std::size_t>(n), '\0');
|
||||
WideCharToMultiByte(CP_UTF8, 0, base.c_str(), static_cast<int>(base.size()), utf8.data(), n, nullptr,
|
||||
nullptr);
|
||||
const std::string utf8 = narrow(base);
|
||||
HANDLE f = CreateFileW(sf.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (f != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user