From b61b3f2a404f994c029f12588581661ad608e163 Mon Sep 17 00:00:00 2001 From: Augusto Domingos Date: Sat, 19 Sep 2026 14:12:35 +0200 Subject: [PATCH] handle UTF-8 command-line paths --- src/common/file.cpp | 29 +++++++++++-------- src/common/stringUtils.h | 17 +++++++++++ src/kernel/fileSystem.cpp | 12 ++++---- src/main.cpp | 59 ++++++++++++++++++++++++++++++++------- 4 files changed, 90 insertions(+), 27 deletions(-) diff --git a/src/common/file.cpp b/src/common/file.cpp index 086effbd1..90bc6db6c 100644 --- a/src/common/file.cpp +++ b/src/common/file.cpp @@ -245,28 +245,33 @@ void File::Printf(const char* format, ...) { Write(s.data(), static_cast(s.size())); } +static std::filesystem::path WithoutTrailingSeparator( + const std::filesystem::path& path) { + if (!path.empty() && !path.has_filename() && + path != path.root_path()) { + return path.parent_path(); + } + + return path; +} + + bool File::IsDirectoryExisting(const std::filesystem::path& path) { - auto path_str = PathToGenericString(path); - return SysFileIsDirectoryExisting(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1) - : path_str); + return SysFileIsDirectoryExisting(WithoutTrailingSeparator(path)); } bool File::IsFileExisting(const std::filesystem::path& name) { return SysFileIsFileExisting(name); } -bool File::CreateDirectory( - const std::filesystem::path& path) // @suppress("Member declaration not found") -{ - auto path_str = PathToGenericString(path); - return SysFileCreateDirectory(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1) - : path_str); +bool File::CreateDirectory(const std::filesystem::path& path) { + return SysFileCreateDirectory( + WithoutTrailingSeparator(path)); } bool File::DeleteDirectory(const std::filesystem::path& path) { - auto path_str = PathToGenericString(path); - return SysFileDeleteDirectory(path_str.ends_with("/") ? Common::RemoveLast(path_str, 1) - : path_str); + return SysFileDeleteDirectory( + WithoutTrailingSeparator(path)); } bool File::CreateDirectories(const std::filesystem::path& path) { diff --git a/src/common/stringUtils.h b/src/common/stringUtils.h index 3e8386170..3f071e9ee 100644 --- a/src/common/stringUtils.h +++ b/src/common/stringUtils.h @@ -34,6 +34,23 @@ inline std::string PathToGenericString(const std::filesystem::path& path) { #endif } +inline std::filesystem::path PathFromUtf8(std::string_view text) { +#if defined(__cpp_char8_t) + std::u8string utf8; + utf8.reserve(text.size()); + + for (const char ch: text) { + utf8.push_back( + static_cast(static_cast(ch)) + ); + } + + return std::filesystem::path(utf8); +#else + return std::filesystem::u8path(text.begin(), text.end()); +#endif +} + inline bool EqualNoCase(std::string_view lhs, std::string_view rhs) { if (lhs.size() != rhs.size()) { return false; diff --git a/src/kernel/fileSystem.cpp b/src/kernel/fileSystem.cpp index 9f2d5aab5..e0a446b80 100644 --- a/src/kernel/fileSystem.cpp +++ b/src/kernel/fileSystem.cpp @@ -251,16 +251,15 @@ void FileDescriptors::CloseAll() { void MountPoints::Mount(const std::filesystem::path& folder, const std::string& point) { Common::LockGuard lock(m_mutex); - auto folder_str = Common::FixDirectorySlash(Common::PathToGenericString(folder)); auto point_str = Common::FixDirectorySlash(point); Umount(point_str); MountPair p; - p.dir = folder_str; + p.dir = folder; p.point = point_str; - m_mount_pairs.push_back(p); + m_mount_pairs.push_back(std::move(p)); } void MountPoints::Umount(const std::string& folder_or_point) { @@ -343,14 +342,17 @@ std::filesystem::path MountPoints::ResolvePath(const std::string& mounted_name) while (rel_path.starts_with('/')) { rel_path = Common::RemoveFirst(rel_path, 1); } + + const auto native_rel_path = Common::PathFromUtf8(rel_path); + #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS if (HasWindowsForbiddenFilenameCharacter(rel_path)) { ::printf("FileSystem: Windows-incompatible guest filename: %s\n", mounted_name.c_str()); } - return p.dir / rel_path; + return p.dir / native_rel_path; #else - return ResolvePathIgnoringCase(p.dir / rel_path); + return ResolvePathIgnoringCase(p.dir / native_rel_path); #endif } diff --git a/src/main.cpp b/src/main.cpp index 81f379805..34af5582a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,8 @@ #include #include +#include +#include #include #include @@ -207,15 +209,19 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he } value = Common::FixFilenameSlash(value); - if (Common::File::IsDirectoryExisting(value)) { - options.app0_dir = value; + const auto path = Common::PathFromUtf8(value); + + if (Common::File::IsDirectoryExisting(path)) { + options.app0_dir = path; options.elf = "/app0/eboot.bin"; - } else if (Common::File::IsFileExisting(value)) { - options.app0_dir = Common::DirectoryWithoutFilename(value); + } else if (Common::File::IsFileExisting(path)) { + options.app0_dir = path.parent_path(); + if (options.app0_dir.empty()) { options.app0_dir = "."; } - options.elf = "/app0/" + Common::FilenameWithoutDirectory(value); + + options.elf = std::filesystem::path("/app0") / path.filename(); } else { ::printf("--game must point to an existing directory or ELF: %s\n", value.c_str()); return false; @@ -226,7 +232,9 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he return false; } value = Common::FixFilenameSlash(value); - if (!Common::File::IsFileExisting(value)) { + const auto path = Common::PathFromUtf8(value); + + if (!Common::File::IsFileExisting(path)) { ::printf("--game-patch must point to an existing file: %s\n", value.c_str()); return false; } @@ -291,14 +299,14 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he return false; } } else if (arg == "--shader-log-folder") { - options.config.shader_log_folder = value; + options.config.shader_log_folder = Common::PathFromUtf8(value); } else if (arg == "--command-buffer-dump") { if (!ParseBool(value, options.config.command_buffer_dump_enabled)) { ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); return false; } } else if (arg == "--command-buffer-dump-folder") { - options.config.command_buffer_dump_folder = value; + options.config.command_buffer_dump_folder = Common::PathFromUtf8(value); } else if (arg == "--graphics-debug-dump") { if (!ParseBool(value, options.config.graphics_debug_dump_enabled)) { ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); @@ -310,7 +318,7 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he return false; } } else if (arg == "--printf-output-file") { - options.config.printf_output_file = value; + options.config.printf_output_file = Common::PathFromUtf8(value); } else if (arg == "--spirv-debug-printf") { if (!ParseBool(value, options.config.spirv_debug_printf_enabled)) { ::printf("invalid boolean for %s: %s\n", arg.c_str(), value.c_str()); @@ -341,7 +349,7 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he return show_help || (!options.app0_dir.empty() && !options.elf.empty()); } -int main(int argc, char* argv[]) { +static int Main(int argc, char* argv[]) { VirtualMemory::Init(); InitializeThreads(); @@ -367,3 +375,34 @@ int main(int argc, char* argv[]) { return 0; } + +#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS + +int wmain(int argc, wchar_t* argv[]) { + std::vector utf8_args; + utf8_args.reserve(static_cast(argc)); + + for (int index = 0; index < argc; index++) { + const std::wstring_view wide(argv[index]); + const std::u16string utf16(wide.begin(), wide.end()); + + utf8_args.push_back(Common::Utf16ToUtf8(utf16)); + } + + std::vector utf8_argv; + utf8_argv.reserve(utf8_args.size()); + + for (auto& argument: utf8_args) { + utf8_argv.push_back(argument.data()); + } + + return Main(argc, utf8_argv.data()); +} + +#else + +int main(int argc, char* argv[]) { + return Main(argc, argv); +} + +#endif