mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-20 02:47:02 +02:00
Merge PR #716: Fix Unicode paths on Windows
Preserve native Windows paths for filesystem operations, command-line options, and game icons. Complete Unicode handling in recursive file listings and file logging, restore unmounting by host directory, and extend regression coverage. Original implementation by Augusto Domingos (@AghastyGD); retain all five of his PR commits and include the reviewed follow-up fixes. Closes #714 Co-authored-by: Augusto Domingos <augustodomingosvasco@gmail.com>
This commit is contained in:
Vendored
+3
@@ -86,6 +86,9 @@ set(SPDLOG_BUILD_WARNINGS OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_INSTALL OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_FMT_EXTERNAL ON CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_NO_EXCEPTIONS ON CACHE BOOL "" FORCE)
|
||||
if(WIN32)
|
||||
set(SPDLOG_WCHAR_FILENAMES ON CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
|
||||
|
||||
if (NOT TARGET FFmpeg::ffmpeg)
|
||||
|
||||
+49
-65
@@ -245,88 +245,78 @@ void File::Printf(const char* format, ...) {
|
||||
Write(s.data(), static_cast<uint32_t>(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) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
std::string p;
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
if (IsDirectoryExisting(current_path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (si != 0 || real_path.starts_with("/")) {
|
||||
p += "/";
|
||||
}
|
||||
if (!CreateDirectory(current_path)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
if (IsDirectoryExisting(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
const auto normalized_path =
|
||||
WithoutTrailingSeparator(path);
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
std::vector<std::filesystem::path> directories;
|
||||
auto current_path = normalized_path.root_path();
|
||||
|
||||
std::string p;
|
||||
std::vector<std::string> list2;
|
||||
for (const auto& component:
|
||||
normalized_path.relative_path()) {
|
||||
current_path /= component;
|
||||
directories.push_back(current_path);
|
||||
}
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
for (auto it = directories.rbegin();
|
||||
it != directories.rend(); ++it) {
|
||||
if (!DeleteDirectory(*it)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (si != 0 || real_path.starts_with("/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
list2.push_back(p);
|
||||
}
|
||||
|
||||
uint32_t num = list2.size();
|
||||
|
||||
for (uint32_t si = num - 1; si < num; si--) {
|
||||
if (!DeleteDirectory(list2[si])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteFile(
|
||||
@@ -460,13 +450,7 @@ std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
|
||||
SysFileFindFiles(path, files);
|
||||
|
||||
auto path_str = PathToGenericString(path);
|
||||
uint32_t len = static_cast<uint32_t>(path_str.size());
|
||||
|
||||
if (!path_str.ends_with("/") && !path_str.ends_with("\\")) {
|
||||
len++;
|
||||
}
|
||||
|
||||
const auto normalized_path = path.lexically_normal();
|
||||
std::vector<File::FindInfo> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
@@ -474,7 +458,7 @@ std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
File::FindInfo r {};
|
||||
|
||||
r.path_with_name = f.path_with_name;
|
||||
r.rel_path_with_name = Common::Mid(PathToGenericString(f.path_with_name), len);
|
||||
r.rel_path_with_name = f.path_with_name.lexically_normal().lexically_relative(normalized_path);
|
||||
r.size = f.size;
|
||||
|
||||
SysTimeStruct at {};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
@@ -47,7 +46,7 @@ std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name
|
||||
}
|
||||
|
||||
auto sink =
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(path.native(), true);
|
||||
return MakeLogger(std::move(name), std::move(sink));
|
||||
}
|
||||
|
||||
|
||||
@@ -477,18 +477,10 @@ bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!real_path.ends_with("/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
const auto pattern = path / L"*";
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
WIN32_FIND_DATAW data {};
|
||||
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
@@ -496,18 +488,19 @@ void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_fi
|
||||
|
||||
do {
|
||||
std::filesystem::path file_name(data.cFileName);
|
||||
auto file_name_str = Common::PathToString(file_name);
|
||||
|
||||
if (file_name_str == "." || file_name_str == "..") {
|
||||
if (file_name == L"." || file_name == L"..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto full_path = path / file_name;
|
||||
|
||||
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
|
||||
SysFileFindFiles(std::filesystem::path(real_path) / file_name, out);
|
||||
SysFileFindFiles(full_path, out);
|
||||
} else {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = std::filesystem::path(real_path) / file_name;
|
||||
r.path_with_name = full_path;
|
||||
r.size = (static_cast<uint64_t>(data.nFileSizeHigh) << 32u) +
|
||||
static_cast<uint64_t>(data.nFileSizeLow);
|
||||
r.last_access_time.is_invalid = false;
|
||||
@@ -515,7 +508,7 @@ void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_fi
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = data.ftLastWriteTime;
|
||||
|
||||
out.push_back(r);
|
||||
out.push_back(std::move(r));
|
||||
}
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
@@ -524,18 +517,10 @@ void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_fi
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!real_path.ends_with("/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
const auto pattern = path / L"*";
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
WIN32_FIND_DATAW data {};
|
||||
HANDLE h = FindFirstFileW(pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
@@ -549,7 +534,7 @@ void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entr
|
||||
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
r.name = Common::PathToString(file_name);
|
||||
|
||||
out.push_back(r);
|
||||
out.push_back(std::move(r));
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
|
||||
@@ -34,6 +34,14 @@ inline std::string PathToGenericString(const std::filesystem::path& path) {
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::filesystem::path PathFromUtf8(std::string_view text) {
|
||||
#if defined(__cpp_char8_t)
|
||||
return std::filesystem::path(std::u8string(text.begin(), text.end()));
|
||||
#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;
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/timer.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
@@ -45,6 +46,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <vulkan/vk_platform.h>
|
||||
#include <filesystem>
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#define STBI_NO_SIMD
|
||||
@@ -945,10 +947,10 @@ struct WindowIcon {
|
||||
}
|
||||
};
|
||||
|
||||
static void WindowLoadPngIcon(const std::string& path, WindowIcon* icon) {
|
||||
static void WindowLoadPngIcon(const std::filesystem::path& path, WindowIcon* icon) {
|
||||
Common::File f;
|
||||
if (!f.Open(path, Common::File::Mode::Read)) {
|
||||
EXIT("Can't open icon file %s\n", path.c_str());
|
||||
EXIT("Can't open icon file %s\n", Common::PathToString(path).c_str());
|
||||
}
|
||||
|
||||
int width = 0;
|
||||
@@ -974,7 +976,7 @@ void WindowContext::UpdateIcon() {
|
||||
static bool icon_loaded = false;
|
||||
|
||||
if (!icon_loaded) {
|
||||
std::string icon_path;
|
||||
std::filesystem::path icon_path;
|
||||
if (Loader::SystemContentGetIconPath(&icon_path)) {
|
||||
WindowLoadPngIcon(icon_path, &icon);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -270,7 +269,7 @@ void MountPoints::Umount(const std::string& folder_or_point) {
|
||||
|
||||
const auto it = std::find_if(
|
||||
m_mount_pairs.begin(), m_mount_pairs.end(), [&folder_or_point_str](const MountPair& p) {
|
||||
return Common::PathToGenericString(p.dir) == folder_or_point_str ||
|
||||
return Common::FixDirectorySlash(Common::PathToGenericString(p.dir)) == folder_or_point_str ||
|
||||
p.point == folder_or_point_str;
|
||||
});
|
||||
if (it != m_mount_pairs.end()) {
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -647,20 +647,20 @@ uint64_t SystemContentGetFlexibleMemorySize() {
|
||||
return sc->psf.GetFlexibleMemorySize();
|
||||
}
|
||||
|
||||
bool SystemContentGetIconPath(std::string* path) {
|
||||
if (path == nullptr) {
|
||||
return false;
|
||||
}
|
||||
bool SystemContentGetIconPath(std::filesystem::path* path) {
|
||||
if (path == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
auto* sc = Common::Singleton<SystemContent>::Instance();
|
||||
|
||||
if (sc->icon_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (sc->icon_path.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*path = Common::PathToString(sc->icon_path);
|
||||
*path = sc->icon_path;
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SystemContentGetChunksNum(uint32_t* num) {
|
||||
|
||||
@@ -15,7 +15,7 @@ bool SystemContentParamSfoGetInt(const char* name, int32_t* value);
|
||||
bool SystemContentParamSfoGetString(const char* name, std::string* value);
|
||||
bool SystemContentParamSfoGetString(const char* name, char* value, size_t value_size);
|
||||
uint64_t SystemContentGetFlexibleMemorySize();
|
||||
bool SystemContentGetIconPath(std::string* path);
|
||||
bool SystemContentGetIconPath(std::filesystem::path* path);
|
||||
bool SystemContentGetChunksNum(uint32_t* num);
|
||||
|
||||
} // namespace Loader
|
||||
|
||||
+50
-11
@@ -10,6 +10,8 @@
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdio>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <fmt/format.h>
|
||||
#include <magic_enum.hpp>
|
||||
|
||||
@@ -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,11 +232,13 @@ 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;
|
||||
}
|
||||
options.game_patch = value;
|
||||
options.game_patch = path;
|
||||
} else if (arg == "--screen-width") {
|
||||
options.config.screen_width = static_cast<uint32_t>(Common::ToInt32(value));
|
||||
} else if (arg == "--screen-height") {
|
||||
@@ -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<std::string> utf8_args;
|
||||
utf8_args.reserve(static_cast<size_t>(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<char*> 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
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
#include "common/logging/log.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "common/threads.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/presentation/window/windowInternal.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/network.h"
|
||||
#include "loader/symbolDatabase.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include <chrono>
|
||||
@@ -153,6 +155,94 @@ void CheckMountRoot(const std::filesystem::path &root) {
|
||||
}
|
||||
}
|
||||
FileSystem::Umount("/app0");
|
||||
Check(FileSystem::GetRealFilename("/app0/rpf.cache") == "/app0/rpf.cache",
|
||||
"unmount by guest path");
|
||||
for (const auto &folder : {root, root / ""}) {
|
||||
for (const auto &host : {root, root / ""}) {
|
||||
FileSystem::Mount(folder, "/app0");
|
||||
FileSystem::Umount(Common::PathToGenericString(host));
|
||||
Check(FileSystem::GetRealFilename("/app0/rpf.cache") == "/app0/rpf.cache",
|
||||
"unmount by host path with or without trailing separator");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CheckUnicodePaths(const std::filesystem::path &root) {
|
||||
constexpr std::string_view HostDirectory =
|
||||
"Test\xc3\xa9-\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e";
|
||||
constexpr std::string_view GuestFilename =
|
||||
"asset-\xc3\xa9-\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e.bin";
|
||||
|
||||
const auto unicode_root =
|
||||
root / Common::PathFromUtf8(HostDirectory);
|
||||
const auto nested_root = unicode_root / "nested";
|
||||
|
||||
Check(Common::File::CreateDirectories(nested_root),
|
||||
"create nested Unicode host directory");
|
||||
|
||||
CheckMountRoot(nested_root);
|
||||
|
||||
const auto native_file =
|
||||
nested_root / Common::PathFromUtf8(GuestFilename);
|
||||
|
||||
Common::File fixture;
|
||||
Check(fixture.Create(native_file), "create Unicode filename");
|
||||
fixture.Close();
|
||||
|
||||
for (const auto &directory : {unicode_root, std::filesystem::relative(unicode_root)}) {
|
||||
// A Windows temporary directory may be on a different drive from the working directory.
|
||||
if (directory.empty()) {
|
||||
continue;
|
||||
}
|
||||
const auto files = Common::File::FindFiles(directory);
|
||||
const auto relative_file =
|
||||
std::filesystem::path(u8"nested/asset-\u00e9-\u65e5\u672c\u8a9e.bin");
|
||||
Check(std::any_of(files.begin(), files.end(), [&](const auto &file) {
|
||||
return file.path_with_name.lexically_normal() ==
|
||||
(directory / relative_file).lexically_normal() &&
|
||||
file.rel_path_with_name == relative_file;
|
||||
}), "recursive enumeration preserves native and relative Unicode paths");
|
||||
}
|
||||
const auto entries = Common::File::GetDirEntries(nested_root);
|
||||
Check(std::any_of(entries.begin(), entries.end(), [&](const auto &entry) {
|
||||
return entry.is_file && entry.name == GuestFilename;
|
||||
}), "directory enumeration returns UTF-8 filenames");
|
||||
|
||||
FileSystem::Mount(nested_root, "/app0");
|
||||
|
||||
const auto guest_file =
|
||||
std::string("/app0/") + std::string(GuestFilename);
|
||||
|
||||
Check(FileSystem::GetRealFilename(guest_file) == native_file,
|
||||
"resolve Unicode guest path");
|
||||
|
||||
const int fd = FileSystem::KernelOpen(guest_file.c_str(), 0, 0);
|
||||
Check(fd >= 3, "open Unicode guest path");
|
||||
Check(FileSystem::KernelClose(fd) == OK,
|
||||
"close Unicode guest path");
|
||||
|
||||
FileSystem::Umount("/app0");
|
||||
}
|
||||
|
||||
void CheckUnicodeLogPath(const std::filesystem::path &root) {
|
||||
Config::ConfigOptions options;
|
||||
options.printf_direction = Config::LogDirection::File;
|
||||
options.printf_output_file = root / u8"logs-\u65e5\u672c\u8a9e-\U0001f600" / u8"log-\u00e9.txt";
|
||||
Config::Load(options);
|
||||
Log::Initialize();
|
||||
constexpr std::string_view Payload = "Unicode log path\n";
|
||||
Log::Write(Payload);
|
||||
Log::Shutdown();
|
||||
|
||||
Common::File result(options.printf_output_file, Common::File::Mode::Read);
|
||||
Check(!result.IsInvalid(), "open Unicode log file");
|
||||
const auto data = result.ReadWholeBuffer();
|
||||
Check(data.size() == Payload.size() &&
|
||||
std::memcmp(data.data(), Payload.data(), data.size()) == 0,
|
||||
"Unicode log file contains output");
|
||||
options.printf_direction = Config::LogDirection::Silent;
|
||||
Config::Load(options);
|
||||
Log::Initialize();
|
||||
}
|
||||
|
||||
void CheckDirectoryStream(const std::filesystem::path &root) {
|
||||
@@ -414,6 +504,8 @@ int main() {
|
||||
TempDirectory temporary;
|
||||
FileSystem::Initialize();
|
||||
CheckMountRoot(temporary.Path());
|
||||
CheckUnicodePaths(temporary.Path());
|
||||
CheckUnicodeLogPath(temporary.Path());
|
||||
CheckDirectoryStream(temporary.Path());
|
||||
CheckAprPaths(temporary.Path());
|
||||
FileSystem::Mount(temporary.Path(), "/savedata0");
|
||||
|
||||
Reference in New Issue
Block a user