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 <mmreg.h> // WAVE_FORMAT_* (needs windows.h first)
|
||||||
|
|
||||||
#include "coop/tool_paths.hpp"
|
#include "coop/tool_paths.hpp"
|
||||||
|
#include "util/utf8.hpp"
|
||||||
|
|
||||||
namespace coop
|
namespace coop
|
||||||
{
|
{
|
||||||
@@ -22,32 +23,8 @@ std::wstring to_lower(std::wstring s)
|
|||||||
}
|
}
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
// A non-ASCII image name (e.g. a CJK game exe) round-trips through UTF-8 so it survives
|
||||||
// UTF-8 round-trip so a non-ASCII image name (e.g. a CJK game exe) survives persist/reload and
|
// persist/reload; ASCII names (the common case) are byte-identical. See util/utf8.hpp.
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
AudioOverrideStore::AudioOverrideStore(std::wstring path) : path_(std::move(path))
|
AudioOverrideStore::AudioOverrideStore(std::wstring path) : path_(std::move(path))
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <mmreg.h>
|
#include <mmreg.h>
|
||||||
|
|
||||||
#include "ui/app_chrome.hpp"
|
#include "ui/app_chrome.hpp"
|
||||||
|
#include "util/utf8.hpp"
|
||||||
|
|
||||||
namespace coop
|
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)
|
std::string image_basename(const std::wstring& image_path)
|
||||||
{
|
{
|
||||||
const std::size_t slash = image_path.find_last_of(L"\\/");
|
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);
|
return narrow(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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
@@ -9,27 +9,11 @@
|
|||||||
#include "coop/dpi.hpp"
|
#include "coop/dpi.hpp"
|
||||||
#include "coop/tool_paths.hpp"
|
#include "coop/tool_paths.hpp"
|
||||||
#include "ui/app_chrome.hpp"
|
#include "ui/app_chrome.hpp"
|
||||||
|
#include "util/utf8.hpp"
|
||||||
|
|
||||||
namespace coop
|
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()
|
ImGuiLayer::~ImGuiLayer()
|
||||||
{
|
{
|
||||||
if (initialized_)
|
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).
|
// computed default on startup (which would clobber the restored positions).
|
||||||
const std::wstring ini_w = exe_directory() + L"coop_layout.ini";
|
const std::wstring ini_w = exe_directory() + L"coop_layout.ini";
|
||||||
const bool had_layout = GetFileAttributesW(ini_w.c_str()) != INVALID_FILE_ATTRIBUTES;
|
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();
|
io.IniFilename = ini_path_.c_str();
|
||||||
set_layout_persisted(had_layout);
|
set_layout_persisted(had_layout);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include "inject/injector.hpp"
|
#include "inject/injector.hpp"
|
||||||
#include "ui/app_chrome.hpp"
|
#include "ui/app_chrome.hpp"
|
||||||
#include "ui/text_match.hpp"
|
#include "ui/text_match.hpp"
|
||||||
|
#include "util/utf8.hpp"
|
||||||
|
|
||||||
namespace coop
|
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 kRed(1.0f, 0.45f, 0.4f, 1.0f);
|
||||||
const ImVec4 kGrey(0.7f, 0.7f, 0.7f, 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").
|
// Case-insensitive equality of two image names (e.g. "game.exe").
|
||||||
bool iequals_name(const std::wstring& a, const std::wstring& b)
|
bool iequals_name(const std::wstring& a, const std::wstring& b)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -35,6 +35,7 @@
|
|||||||
#include "log_panel.hpp"
|
#include "log_panel.hpp"
|
||||||
#include "test_harness.hpp"
|
#include "test_harness.hpp"
|
||||||
#include "ui/app_chrome.hpp"
|
#include "ui/app_chrome.hpp"
|
||||||
|
#include "util/utf8.hpp"
|
||||||
#include "vk_layer_setup.hpp"
|
#include "vk_layer_setup.hpp"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -91,17 +92,7 @@ void draw_screenshot_toast(double seconds_since, const std::string& name)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef COOP_TEST_HARNESS
|
#ifdef COOP_TEST_HARNESS
|
||||||
std::wstring widen(const std::string& s)
|
using coop::widen;
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run a test-harness command on the main thread, hitting the same code the UI buttons do.
|
// 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.
|
// 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 <windows.h>
|
||||||
|
|
||||||
#include "coop/tool_paths.hpp"
|
#include "coop/tool_paths.hpp"
|
||||||
|
#include "util/utf8.hpp"
|
||||||
|
|
||||||
namespace coop
|
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::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 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,
|
const std::string utf8 = narrow(base);
|
||||||
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);
|
|
||||||
HANDLE f = CreateFileW(sf.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
HANDLE f = CreateFileW(sf.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||||
if (f != INVALID_HANDLE_VALUE)
|
if (f != INVALID_HANDLE_VALUE)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user