diff --git a/include/dusk/io.hpp b/include/dusk/io.hpp index 6de60e3449..2efc4a8d3b 100644 --- a/include/dusk/io.hpp +++ b/include/dusk/io.hpp @@ -1,14 +1,13 @@ #ifndef DUSK_IO_HPP #define DUSK_IO_HPP -#include #include +#include // I can't believe it's 2026 and neither SDL (no error codes) nor // C++ (no error codes) have a file system API functional enough for me to use. // Here you go, this one's inspired by C#. I only wrote the functions I need. - namespace dusk::io { /** @@ -83,9 +82,7 @@ public: /** * Get direct access to the underlying FILE* handle. */ - [[nodiscard]] void* GetFileHandle() const noexcept { - return file; - } + [[nodiscard]] void* GetFileHandle() const noexcept { return file; } /** * Write data to the file. @@ -95,7 +92,14 @@ public: FILE* ToInner(); }; +/** + * Converts a std::filesystem::path to a std::string, UTF-8, without exploding on Windows. + */ +inline std::string fs_path_to_string(const std::filesystem::path& path) { + const auto u8str = path.u8string(); + return {reinterpret_cast(u8str.c_str())}; } +} // namespace dusk::io #endif // DUSK_IO_HPP diff --git a/src/dusk/logging.cpp b/src/dusk/logging.cpp index 67723af4df..1c0a6ecbdb 100644 --- a/src/dusk/logging.cpp +++ b/src/dusk/logging.cpp @@ -208,7 +208,7 @@ void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraL std::filesystem::create_directories(logsDir, ec); if (ec) { std::fprintf(stderr, "[WARNING | dusk] Failed to create log directory '%s': %s\n", - logsDir.string().c_str(), ec.message().c_str()); + io::fs_path_to_string(logsDir).c_str(), ec.message().c_str()); return; } @@ -216,7 +216,7 @@ void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraL g_logState.file = io::FileStream::Create(logPath).ToInner(); if (g_logState.file == nullptr) { std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n", - logPath.string().c_str()); + io::fs_path_to_string(logPath).c_str()); return; } diff --git a/src/dusk/main.cpp b/src/dusk/main.cpp index e1b2fd0b6e..d88594248a 100644 --- a/src/dusk/main.cpp +++ b/src/dusk/main.cpp @@ -6,6 +6,7 @@ #include #include "dusk/main.h" +#include "dusk/io.hpp" #include #include @@ -91,7 +92,7 @@ bool RestartProcess(int argc, char* argv[]) { std::vector args; args.reserve(static_cast(std::max(argc, 1))); - args.push_back(executablePath.string()); + args.push_back(dusk::io::fs_path_to_string(executablePath)); for (int i = 1; i < argc; ++i) { args.emplace_back(argv[i] != nullptr ? argv[i] : ""); } diff --git a/src/dusk/ui/ui.cpp b/src/dusk/ui/ui.cpp index 0ee59e7938..5ef15a6c41 100644 --- a/src/dusk/ui/ui.cpp +++ b/src/dusk/ui/ui.cpp @@ -11,6 +11,7 @@ #include #include "aurora/lib/window.hpp" +#include "dusk/io.hpp" #include "input.hpp" #include "prelaunch.hpp" #include "window.hpp" @@ -19,7 +20,7 @@ namespace dusk::ui { namespace { void load_font(const char* filename, bool fallback = false) { - Rml::LoadFontFace(resource_path(filename).string(), fallback); + Rml::LoadFontFace(io::fs_path_to_string(resource_path(filename)), fallback); } bool sInitialized = false; diff --git a/src/m_Do/m_Do_main.cpp b/src/m_Do/m_Do_main.cpp index 98269c0817..6db94cc317 100644 --- a/src/m_Do/m_Do_main.cpp +++ b/src/m_Do/m_Do_main.cpp @@ -71,6 +71,7 @@ #include #include "SDL3/SDL_filesystem.h" +#include "SDL3/SDL_iostream.h" #include "SDL3/SDL_misc.h" #include "cxxopts.hpp" #include "d/actor/d_a_movie_player.h" @@ -78,6 +79,7 @@ #include "dusk/audio/DuskDsp.hpp" #include "dusk/config.hpp" #include "dusk/settings.h" +#include "dusk/io.hpp" #include "dusk/version.hpp" #include "dusk/discord_presence.hpp" #include "tracy/Tracy.hpp" @@ -124,7 +126,7 @@ bool dusk::OpenDataFolder() { std::filesystem::path path = std::filesystem::absolute(ConfigPath, ec); if (ec) { DuskLog.warn("Failed to resolve absolute data folder path '{}': {}", - ConfigPath.string(), ec.message()); + io::fs_path_to_string(ConfigPath), ec.message()); path = ConfigPath; } @@ -134,7 +136,8 @@ bool dusk::OpenDataFolder() { const std::string url = "file://" + path.generic_string(); #endif if (!SDL_OpenURL(url.c_str())) { - DuskLog.warn("Failed to open data folder '{}': {}", path.string(), SDL_GetError()); + DuskLog.warn( + "Failed to open data folder '{}': {}", io::fs_path_to_string(path), SDL_GetError()); return false; } return true; @@ -504,16 +507,21 @@ static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) { return; } - const char* basePath = SDL_GetBasePath(); - if (basePath == nullptr) { - DuskLog.warn("Unable to resolve base path while seeding pipeline cache: {}", SDL_GetError()); - return; - } + std::string sourcePathString; + SDL_IOStream* source = nullptr; - const std::filesystem::path initialPipelineCachePath = - std::filesystem::path(basePath) / "initial_pipeline_cache.db"; - if (!std::filesystem::exists(initialPipelineCachePath)) { - DuskLog.info("No bundled initial pipeline cache found at '{}'", initialPipelineCachePath.string()); + const char* basePath = SDL_GetBasePath(); + if (basePath != nullptr) { + sourcePathString = dusk::io::fs_path_to_string( + std::filesystem::path(basePath) / "initial_pipeline_cache.db"); + source = SDL_IOFromFile(sourcePathString.c_str(), "rb"); + } + if (source == nullptr) { + sourcePathString = "initial_pipeline_cache.db"; + source = SDL_IOFromFile(sourcePathString.c_str(), "rb"); + } + if (source == nullptr) { + DuskLog.info("No bundled initial pipeline cache found"); return; } @@ -521,18 +529,68 @@ static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) { std::filesystem::create_directories(configDir, ec); if (ec) { DuskLog.warn("Failed to create config directory '{}' for pipeline cache: {}", - configDir.string(), ec.message()); + dusk::io::fs_path_to_string(configDir), ec.message()); + SDL_CloseIO(source); return; } - std::filesystem::copy_file(initialPipelineCachePath, pipelineCachePath, std::filesystem::copy_options::none, ec); - if (ec) { - DuskLog.warn("Failed to seed pipeline cache from '{}' to '{}': {}", - initialPipelineCachePath.string(), pipelineCachePath.string(), ec.message()); + const auto pipelineCacheString = dusk::io::fs_path_to_string(pipelineCachePath); + SDL_IOStream* destination = SDL_IOFromFile(pipelineCacheString.c_str(), "wb"); + if (destination == nullptr) { + DuskLog.warn("Failed to open '{}' for seeded pipeline cache: {}", pipelineCacheString, + SDL_GetError()); + SDL_CloseIO(source); return; } - DuskLog.info("Seeded pipeline cache from '{}'", initialPipelineCachePath.string()); + bool copied = true; + std::array buffer{}; + while (true) { + const size_t bytesRead = SDL_ReadIO(source, buffer.data(), buffer.size()); + if (bytesRead > 0) { + size_t bytesWritten = 0; + while (bytesWritten < bytesRead) { + const size_t written = SDL_WriteIO( + destination, buffer.data() + bytesWritten, bytesRead - bytesWritten); + if (written == 0) { + DuskLog.warn("Failed to write seeded pipeline cache '{}': {}", + pipelineCacheString, SDL_GetError()); + copied = false; + break; + } + bytesWritten += written; + } + } + + if (!copied) { + break; + } + + if (bytesRead < buffer.size()) { + if (SDL_GetIOStatus(source) == SDL_IO_STATUS_EOF) { + break; + } + + DuskLog.warn( + "Failed to read bundled pipeline cache '{}': {}", sourcePathString, SDL_GetError()); + copied = false; + break; + } + } + + if (!SDL_CloseIO(destination)) { + DuskLog.warn("Failed to close seeded pipeline cache '{}': {}", + dusk::io::fs_path_to_string(pipelineCachePath), SDL_GetError()); + copied = false; + } + SDL_CloseIO(source); + + if (!copied) { + std::filesystem::remove(pipelineCachePath, ec); + return; + } + + DuskLog.info("Seeded pipeline cache from '{}'", sourcePathString); } static constexpr PADDefaultMapping defaultPadMapping = {