fix(input): read the input file from paths with non-ASCII characters

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AlxcBTWandClaude Opus 5.5 authored and Adrià Franch committed 2026-10-14 18:52:30 +02:00
1 parent e34c479210
commit b8ed78902f
3 files changed
+70 -8

No files matched your search

@@ -214,13 +214,32 @@ std::uint8_t stickByte(std::int32_t axis) {
return static_cast<std::uint8_t>(((axis + 32768) * 255 + 32767) / 65535);
}
std::filesystem::path pathFromUtf8(std::string_view text) {
return std::filesystem::path(std::u8string(reinterpret_cast<const char8_t*>(text.data()), text.size()));
}
std::string pathToUtf8(const std::filesystem::path& path) {
const auto text = path.u8string();
return std::string(reinterpret_cast<const char*>(text.data()), text.size());
}
std::filesystem::path configuredPath() {
#ifdef _WIN32
const wchar_t* configured = _wgetenv(L"ANYPS5_INPUT_CONFIG");
return configured != nullptr ? std::filesystem::path(configured) : std::filesystem::path{};
#else
const char* configured = std::getenv("ANYPS5_INPUT_CONFIG");
return configured != nullptr ? pathFromUtf8(configured) : std::filesystem::path{};
#endif
}
[[noreturn]] void invalidLine(const std::filesystem::path& path, std::size_t line, const std::string& reason) {
throw std::runtime_error("Pad: invalid input mapping " + path.string() + ":" + std::to_string(line) + ": " + reason);
throw std::runtime_error("Pad: invalid input mapping " + pathToUtf8(path) + ":" + std::to_string(line) + ": " + reason);
}
std::filesystem::path defaultConfigPath() {
std::unique_ptr<char, decltype(&SDL_free)> basePath(SDL_GetBasePath(), SDL_free);
if (basePath) return std::filesystem::path(basePath.get()) / "anyps5-input.ini";
if (basePath) return pathFromUtf8(basePath.get()) / "anyps5-input.ini";
return "anyps5-input.ini";
}
@@ -244,13 +263,13 @@ std::array<std::uint8_t, 2> Pad::StickWithDeadzone(std::int16_t x, std::int16_t
Pad::InputConfiguration Pad::LoadInputMapping() {
InputConfiguration configuration{{InputMapping.begin(), InputMapping.end()}, {}};
auto& bindings = configuration.bindings;
const char* configuredPath = std::getenv("ANYPS5_INPUT_CONFIG");
const bool explicitPath = configuredPath != nullptr && configuredPath[0] != '\0';
const std::filesystem::path path = explicitPath ? configuredPath : defaultConfigPath();
const auto configured = configuredPath();
const bool explicitPath = !configured.empty();
const std::filesystem::path path = explicitPath ? configured : defaultConfigPath();
std::ifstream file(path);
if (!file) {
if (explicitPath || std::filesystem::exists(path)) {
throw std::runtime_error("Pad: cannot read input mapping '" + path.string() + "'");
throw std::runtime_error("Pad: cannot read input mapping '" + pathToUtf8(path) + "'");
}
return configuration;
}
@@ -301,7 +320,7 @@ Pad::InputConfiguration Pad::LoadInputMapping() {
invalidLine(path, lineNumber, error.what());
}
}
if (file.bad()) throw std::runtime_error("Pad: cannot read input mapping '" + path.string() + "'");
if (file.bad()) throw std::runtime_error("Pad: cannot read input mapping '" + pathToUtf8(path) + "'");
return configuration;
}
@@ -105,6 +105,48 @@ void mouseLookReject() {
require(rejects("MouseDecayMs = 20\nmousedecayms = 30\n"), "a mouse setting given twice is rejected");
}
void setConfigPath(const std::filesystem::path& path) {
#ifdef _WIN32
require(_wputenv_s(L"ANYPS5_INPUT_CONFIG", path.wstring().c_str()) == 0, "set ANYPS5_INPUT_CONFIG");
#else
require(::setenv("ANYPS5_INPUT_CONFIG", path.string().c_str(), 1) == 0, "set ANYPS5_INPUT_CONFIG");
#endif
}
void nonAsciiPaths() {
const std::u8string name = u8"anyps5-input-\u00fc-\u65e5\u672c-" + std::u8string(root.filename().u8string());
const auto directory = root / std::filesystem::path(name);
std::filesystem::create_directories(directory);
const auto file = directory / "anyps5-input.ini";
{
std::ofstream stream(file, std::ios::binary | std::ios::trunc);
stream << "MouseSensitivity = 3\n";
require(stream.good(), "write the input mapping in a non-ASCII directory");
}
setConfigPath(file);
require(Pad::LoadInputMapping().settings.mouseSensitivity == 3.0, "ANYPS5_INPUT_CONFIG may name a path with non-ASCII characters");
{
std::ofstream stream(file, std::ios::binary | std::ios::trunc);
stream << "MouseSensitivity = fast\n";
}
std::string message;
try {
Pad::LoadInputMapping();
} catch (const std::runtime_error& error) {
message = error.what();
}
const auto utf8Name = std::string(reinterpret_cast<const char*>(name.data()), name.size());
require(message.find(utf8Name) != std::string::npos, "an error names the path in UTF-8");
setConfigPath(directory / "missing.ini");
message.clear();
try {
Pad::LoadInputMapping();
} catch (const std::runtime_error& error) {
message = error.what();
}
require(message.find(utf8Name) != std::string::npos, "a missing file names the path in UTF-8");
}
void deadzoneOffMatchesRawSticks() {
for (const std::int16_t axis : {std::int16_t{-32768}, std::int16_t{-20000}, std::int16_t{-1}, std::int16_t{0}, std::int16_t{1}, std::int16_t{1234}, std::int16_t{32767}}) {
const auto stick = Pad::StickWithDeadzone(axis, axis, 0);
@@ -141,6 +183,7 @@ int main() try {
mouseLookDefault();
mouseLookParse();
mouseLookReject();
nonAsciiPaths();
deadzoneOffMatchesRawSticks();
deadzoneShapesSticks();
std::filesystem::remove_all(root);
+1 -1
View File
@@ -1,6 +1,6 @@
# Controller, keyboard and mouse input
AnyPS5 accepts SDL-mapped game controllers as well as its built-in keyboard and mouse bindings. Xbox-style controllers and PlayStation controllers recognized by SDL use the standard PS button layout; sticks and analog triggers are passed through, and controllers can be connected or disconnected while the game is running. The first recognized controller is used. AnyPS5 uses its built-in keyboard and mouse bindings when no configuration file is present. To change selected keyboard and mouse bindings, create `anyps5-input.ini` beside the generated game executable. Set `ANYPS5_INPUT_CONFIG` to use a file at another path.
AnyPS5 accepts SDL-mapped game controllers as well as its built-in keyboard and mouse bindings. Xbox-style controllers and PlayStation controllers recognized by SDL use the standard PS button layout; sticks and analog triggers are passed through, and controllers can be connected or disconnected while the game is running. The first recognized controller is used. AnyPS5 uses its built-in keyboard and mouse bindings when no configuration file is present. To change selected keyboard and mouse bindings, create `anyps5-input.ini` beside the generated game executable. Set `ANYPS5_INPUT_CONFIG` to use a file at another path. Both the game directory and that path may contain any characters, such as `ü` or `日本`.
For an Xbox-layout controller, A/B/X/Y map to Cross/Circle/Square/Triangle, LB/RB map to L1/R1, and LT/RT map to analog L2/R2. Start maps to Options. D-pad and stick-click buttons are supported. On a PlayStation controller, the touchpad click button is forwarded; finger touch coordinates are not implemented.