diff --git a/.vs/launch.vs.json b/.vs/launch.vs.json index d45ad42fc..a9d422894 100644 --- a/.vs/launch.vs.json +++ b/.vs/launch.vs.json @@ -1,151 +1,180 @@ { - "version" : "0.2.1", "defaults" : {}, "configurations" : [ + "version": "0.2.1", + "defaults": {}, + "configurations": [ { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)", - "name" : "Tests - Unit-Tests - Summary", - "args" : ["--gtest_brief=1"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", + "name": "Tests - Unit-Tests - Summary", + "args": ["--gtest_brief=1"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)", - "name" : "Tests - Unit-Tests - Verbose", - "args" : ["--gtest_brief=0"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", + "name": "Tests - Unit-Tests - Verbose", + "args": ["--gtest_brief=0"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)", - "name" : "Tests - Draft Tests - Verbose", - "args" : ["--gtest_brief=0", "--gtest_filter=\"*Draft*\""] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", + "name": "Tests - Draft Tests - Verbose", + "args": ["--gtest_brief=0", "--gtest_filter=\"*Draft*\""] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)", - "name" : "Tests - TypeConsistency - Verbose", - "args" : ["--gtest_brief=0", "--gtest_filter=\"*TypeConsistency*\""] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", + "name": "Tests - TypeConsistency - Verbose", + "args": ["--gtest_brief=0", "--gtest_filter=\"*TypeConsistency*\""] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "goalc-test.exe (bin\\goalc-test.exe)", - "name" : "Tests - WithGameTests - Verbose", - "args" : ["--gtest_brief=0", "--gtest_filter=\"*WithGameTests*\""] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc-test.exe (bin\\goalc-test.exe)", + "name": "Tests - WithGameTests - Verbose", + "args": ["--gtest_brief=0", "--gtest_filter=\"*WithGameTests*\""] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "offline-test.exe (bin\\offline-test.exe)", - "name" : "Tests - Offline Tests", - "args" : ["${workspaceRoot}/iso_data/jak1"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "offline-test.exe (bin\\offline-test.exe)", + "name": "Tests - Offline Tests", + "args": ["${workspaceRoot}/iso_data/jak1"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "gk.exe (bin\\gk.exe)", - "name" : "Run - Runtime (no kernel)", - "args" : [ "-fakeiso", "-debug", "-nokernel", "-v", "-nodisplay" ] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "gk.exe (bin\\gk.exe)", + "name": "Run - Runtime (no kernel)", + "args": ["-fakeiso", "-debug", "-nokernel", "-v", "-nodisplay"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "gk.exe (bin\\gk.exe)", - "name" : "Run - Runtime (with kernel)", - "args" : [ "-fakeiso", "-debug", "-v" ] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "gk.exe (bin\\gk.exe)", + "name": "Run - Runtime (with kernel)", + "args": ["-fakeiso", "-debug", "-v"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "gk.exe (bin\\gk.exe)", - "name" : "Run - Runtime (boot)", - "args" : [ "-boot", "-fakeiso", "-debug", "-v" ] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "gk.exe (bin\\gk.exe)", + "name": "Run - Runtime (boot)", + "args": ["-boot", "-fakeiso", "-debug", "-v"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "gk.exe (bin\\gk.exe)", - "name" : "Run - Runtime (boot no debug)", - "args" : [ "-boot", "-fakeiso", "-v" ] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "gk.exe (bin\\gk.exe)", + "name": "Run - Runtime (boot no debug)", + "args": ["-boot", "-fakeiso", "-v"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "goalc.exe (bin\\goalc.exe)", - "name" : "Run - REPL", - "args" : [ "--user-auto" ] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc.exe (bin\\goalc.exe)", + "name": "Run - REPL", + "args": ["--user-auto"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "goalc.exe (bin\\goalc.exe)", - "name" : "Run - REPL - Auto Listen", - "args" : [ "--user-auto", "--auto-lt" ] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "goalc.exe (bin\\goalc.exe)", + "name": "Run - REPL - Auto Listen", + "args": ["--user-auto", "--auto-lt"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "decompiler.exe (bin\\decompiler.exe)", - "name" : "Run - Decompiler - Jak 1", - "args" : [ "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "decompiler.exe (bin\\decompiler.exe)", + "name": "Run - Decompiler - Jak 1", + "args": [ + "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", + "${workspaceRoot}/iso_data", + "${workspaceRoot}/decompiler_out" + ] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "decompiler.exe (bin\\decompiler.exe)", - "name" : "Run - Decompiler - Jak 1 - Data Only", - "args" : [ "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out", "decompile_code=false"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "decompiler.exe (bin\\decompiler.exe)", + "name": "Run - Decompiler - Jak 1 - Data Only", + "args": [ + "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", + "${workspaceRoot}/iso_data", + "${workspaceRoot}/decompiler_out", + "decompile_code=false" + ] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "decompiler.exe (bin\\decompiler.exe)", - "name" : "Run - Decompiler - Jak 1 PAL", - "args" : [ "${workspaceRoot}/decompiler/config/jak1_pal.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "decompiler.exe (bin\\decompiler.exe)", + "name": "Run - Decompiler - Jak 1 PAL", + "args": [ + "${workspaceRoot}/decompiler/config/jak1_pal.jsonc", + "${workspaceRoot}/iso_data", + "${workspaceRoot}/decompiler_out" + ] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "decompiler.exe (bin\\decompiler.exe)", - "name" : "Run - Disassembler - Jak 1", - "args" : [ "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "decompiler.exe (bin\\decompiler.exe)", + "name": "Run - Disassembler - Jak 1", + "args": [ + "${workspaceRoot}/decompiler/config/jak1_ntsc_black_label.jsonc", + "${workspaceRoot}/iso_data", + "${workspaceRoot}/decompiler_out" + ] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "decompiler.exe (bin\\decompiler.exe)", - "name" : "Run - Decompiler - Jak 2", - "args" : [ "${workspaceRoot}/decompiler/config/jak2_ntsc_v1.jsonc", "${workspaceRoot}/iso_data", "${workspaceRoot}/decompiler_out"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "decompiler.exe (bin\\decompiler.exe)", + "name": "Run - Decompiler - Jak 2", + "args": [ + "${workspaceRoot}/decompiler/config/jak2_ntsc_v1.jsonc", + "${workspaceRoot}/iso_data", + "${workspaceRoot}/decompiler_out" + ] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "memory_dump_tool.exe (bin\\memory_dump_tool.exe)", - "name" : "Run - EE Memory Analyze", - "args" : [ "${workspaceRoot}/eeMemory.bin", "${workspaceRoot}"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)", + "name": "Run - EE Memory Analyze", + "args": ["${workspaceRoot}/eeMemory.bin", "${workspaceRoot}"] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "memory_dump_tool.exe (bin\\memory_dump_tool.exe)", - "name" : "Run - EE Memory Analyze - Test", - "args" : [ "\"C:\\Users\\xtvas\\Repositories\\pcsx2\\128mb\\sstates\\SCUS-97124 (1B3976AB).00.p2s\"", "${workspaceRoot}"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "memory_dump_tool.exe (bin\\memory_dump_tool.exe)", + "name": "Run - EE Memory Analyze - Test", + "args": [ + "\"C:\\Users\\xtvas\\Repositories\\pcsx2\\128mb\\sstates\\SCUS-97124 (1B3976AB).00.p2s\"", + "${workspaceRoot}" + ] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "dgo_unpacker.exe (bin\\dgo_unpacker.exe)", - "name" : "Run - DGO Unpacker (test)", - "args" : [ "C:\\GameData\\Jak1\\Backup\\DGO-PAL\\GAME", "C:\\GameData\\Jak1\\Backup\\DISC-PAL\\CGO\\GAME.CGO"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "dgo_unpacker.exe (bin\\dgo_unpacker.exe)", + "name": "Run - DGO Unpacker (test)", + "args": [ + "C:\\GameData\\Jak1\\Backup\\DGO-PAL\\GAME", + "C:\\GameData\\Jak1\\Backup\\DISC-PAL\\CGO\\GAME.CGO" + ] }, { - "type" : "default", - "project" : "CMakeLists.txt", - "projectTarget" : "extractor.exe (bin\\extractor.exe)", - "name" : "Run - Extractor - Extract", - "args" : ["E:\\ISOs\\Jak\\Jak 1.iso"] + "type": "default", + "project": "CMakeLists.txt", + "projectTarget": "extractor.exe (bin\\extractor.exe)", + "name": "Run - Extractor - Extract", + "args": ["\"E:\\ISOs\\Jak\\дWTF平仮名WTF\\Jak 1.iso\""] } ] } diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 5fde7b5a5..7de2ece8b 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -48,7 +48,7 @@ add_library(common util/os.cpp util/print_float.cpp util/FontUtils.cpp - util/FrameLimiter.cpp) + util/FrameLimiter.cpp "util/unicode_util.h" "util/unicode_util.cpp") target_link_libraries(common fmt lzokay replxx libzstd_static) diff --git a/common/audio/audio_formats.cpp b/common/audio/audio_formats.cpp index e540d6681..62398aa4f 100644 --- a/common/audio/audio_formats.cpp +++ b/common/audio/audio_formats.cpp @@ -7,9 +7,7 @@ /*! * Write a wave file from a vector of samples. */ -void write_wave_file_mono(const std::vector& samples, - s32 sample_rate, - const std::filesystem::path& name) { +void write_wave_file_mono(const std::vector& samples, s32 sample_rate, const fs::path& name) { WaveFileHeader header; memcpy(header.chunk_id, "RIFF", 4); header.chunk_size = 36 + samples.size() * sizeof(s16); diff --git a/common/audio/audio_formats.h b/common/audio/audio_formats.h index ed9888976..6ec0e4ed2 100644 --- a/common/audio/audio_formats.h +++ b/common/audio/audio_formats.h @@ -1,11 +1,11 @@ #pragma once -#include #include #include #include "common/common_types.h" #include "common/util/BinaryReader.h" +#include "common/util/FileUtil.h" // The header data for a simple wave file struct WaveFileHeader { @@ -29,10 +29,8 @@ struct WaveFileHeader { s32 subchunk2_size; }; -void write_wave_file_mono(const std::vector& samples, - s32 sample_rate, - const std::filesystem::path& name); +void write_wave_file_mono(const std::vector& samples, s32 sample_rate, const fs::path& name); std::vector decode_adpcm(BinaryReader& reader); -std::vector encode_adpcm(const std::vector& samples); \ No newline at end of file +std::vector encode_adpcm(const std::vector& samples); diff --git a/common/goos/Interpreter.cpp b/common/goos/Interpreter.cpp index 092c1b1b4..6ee88742b 100644 --- a/common/goos/Interpreter.cpp +++ b/common/goos/Interpreter.cpp @@ -1051,7 +1051,7 @@ Object Interpreter::eval_try_load_file(const Object& form, vararg_check(form, args, {ObjectType::STRING}, {}); auto path = {args.unnamed.at(0).as_string()->data}; - if (!std::filesystem::exists(file_util::get_file_path(path))) { + if (!fs::exists(file_util::get_file_path(path))) { return SymbolObject::make_new(reader.symbolTable, "#f"); } diff --git a/common/goos/Reader.cpp b/common/goos/Reader.cpp index a601443ac..4cf177955 100644 --- a/common/goos/Reader.cpp +++ b/common/goos/Reader.cpp @@ -11,8 +11,6 @@ #include "Reader.h" -#include - #include "ReplUtils.h" #include "common/util/FileUtil.h" diff --git a/common/goos/ReplUtils.cpp b/common/goos/ReplUtils.cpp index 03242bc2f..67d9bad30 100644 --- a/common/goos/ReplUtils.cpp +++ b/common/goos/ReplUtils.cpp @@ -57,14 +57,14 @@ void ReplWrapper::add_to_history(const std::string& line) { } void ReplWrapper::save_history() { - std::filesystem::path path = file_util::get_user_config_dir() / ".opengoal.repl.history"; + fs::path path = file_util::get_user_config_dir() / ".opengoal.repl.history"; file_util::create_dir_if_needed_for_file(path.string()); repl.history_save(path.string()); } void ReplWrapper::load_history() { - std::filesystem::path path = file_util::get_user_config_dir() / ".opengoal.repl.history"; - if (std::filesystem::exists(path)) { + fs::path path = file_util::get_user_config_dir() / ".opengoal.repl.history"; + if (fs::exists(path)) { repl.history_load(path.string()); } else { fmt::print("Couldn't locate REPL history file at '{}'\n", path.string()); diff --git a/common/log/log.cpp b/common/log/log.cpp index a0eebfd49..2fd546fed 100644 --- a/common/log/log.cpp +++ b/common/log/log.cpp @@ -83,7 +83,7 @@ void log_message(level log_level, LogTime& now, const char* message) { void set_file(const std::string& filename) { ASSERT(!gLogger.fp); file_util::create_dir_if_needed_for_file(filename); - gLogger.fp = fopen(filename.c_str(), "w"); + gLogger.fp = file_util::open_file(filename.c_str(), "w"); ASSERT(gLogger.fp); } diff --git a/common/serialization/subtitles/subtitles_deser.cpp b/common/serialization/subtitles/subtitles_deser.cpp index 0cad667f9..c0b7d52e4 100644 --- a/common/serialization/subtitles/subtitles_deser.cpp +++ b/common/serialization/subtitles/subtitles_deser.cpp @@ -117,8 +117,7 @@ bool write_subtitle_db_to_files(const GameSubtitleDB& db) { } // Commit it to the file - std::string full_path = - (file_util::get_jak_project_dir() / std::filesystem::path(bank->file_path)).string(); + std::string full_path = (file_util::get_jak_project_dir() / fs::path(bank->file_path)).string(); file_util::write_text_file(full_path, file_contents); } diff --git a/common/util/BinaryWriter.h b/common/util/BinaryWriter.h index 739492d03..c3ced57c2 100644 --- a/common/util/BinaryWriter.h +++ b/common/util/BinaryWriter.h @@ -7,11 +7,11 @@ #include #include -#include #include #include #include "common/util/Assert.h" +#include "common/util/FileUtil.h" struct BinaryWriterRef { size_t offset; @@ -64,8 +64,8 @@ class BinaryWriter { void* get_data() { return data.data(); } - void write_to_file(const std::filesystem::path& filename) { - auto fp = fopen(filename.string().c_str(), "wb"); + void write_to_file(const fs::path& filename) { + auto fp = file_util::open_file(filename.string().c_str(), "wb"); if (!fp) throw std::runtime_error("failed to open " + filename.string()); if (fwrite(get_data(), get_size(), 1, fp) != 1) diff --git a/common/util/DgoWriter.h b/common/util/DgoWriter.h index 1c4c753d0..9bba5f177 100644 --- a/common/util/DgoWriter.h +++ b/common/util/DgoWriter.h @@ -5,7 +5,6 @@ * Create a DGO from existing files. */ -#include #include #include diff --git a/common/util/FileUtil.cpp b/common/util/FileUtil.cpp index 392eb6523..55123e6ea 100644 --- a/common/util/FileUtil.cpp +++ b/common/util/FileUtil.cpp @@ -7,7 +7,6 @@ #include /* defines FILENAME_MAX */ #include -#include #include #include #include @@ -37,22 +36,22 @@ #include namespace file_util { -std::filesystem::path get_user_home_dir() { +fs::path get_user_home_dir() { #ifdef _WIN32 // NOTE - on older systems, this may case issues if it cannot be found! std::string home_dir = std::getenv("USERPROFILE"); - return std::filesystem::path(home_dir); + return fs::path(home_dir); #else std::string home_dir = std::getenv("HOME"); - return std::filesystem::path(home_dir); + return fs::path(home_dir); #endif } -std::filesystem::path get_user_config_dir() { - std::filesystem::path config_base_path; +fs::path get_user_config_dir() { + fs::path config_base_path; #ifdef _WIN32 auto config_base_dir = std::getenv("APPDATA"); - config_base_path = std::filesystem::path(std::string(config_base_dir)); + config_base_path = fs::path(std::string(config_base_dir)); #elif __linux // Docs - https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html // Prefer XDG_CONFIG_HOME if available @@ -66,19 +65,19 @@ std::filesystem::path get_user_config_dir() { return config_base_path / "OpenGOAL"; } -std::filesystem::path get_user_settings_dir() { +fs::path get_user_settings_dir() { // TODO - jak2 return get_user_config_dir() / "jak1" / "settings"; } -std::filesystem::path get_user_memcard_dir() { +fs::path get_user_memcard_dir() { // TODO - jak2 return get_user_config_dir() / "jak1" / "saves"; } struct { bool initialized = false; - std::filesystem::path path_to_data; + fs::path path_to_data; } gFilePathInfo; /*! @@ -120,17 +119,17 @@ std::optional try_get_jak_project_path() { 0, pos + 11)); // + 12 to include "/jak-project" in the returned filepath } -std::optional try_get_data_dir() { - std::filesystem::path my_path = get_current_executable_path(); +std::optional try_get_data_dir() { + fs::path my_path = get_current_executable_path(); auto data_dir = my_path.parent_path() / "data"; - if (std::filesystem::exists(data_dir) && std::filesystem::is_directory(data_dir)) { + if (fs::exists(data_dir) && fs::is_directory(data_dir)) { return std::make_optional(data_dir); } else { return {}; } } -bool setup_project_path(std::optional project_path_override) { +bool setup_project_path(std::optional project_path_override) { if (gFilePathInfo.initialized) { return true; } @@ -162,7 +161,7 @@ bool setup_project_path(std::optional project_path_overri return false; } -std::filesystem::path get_jak_project_dir() { +fs::path get_jak_project_dir() { ASSERT(gFilePathInfo.initialized); return gFilePathInfo.path_to_data; } @@ -172,7 +171,7 @@ std::string get_file_path(const std::vector& input) { // the project path should be explicitly provided by whatever if needed // TEMP HACK // - if the provided path is absolute, don't add the project path - if (input.size() == 1 && std::filesystem::path(input.at(0)).is_absolute()) { + if (input.size() == 1 && fs::path(input.at(0)).is_absolute()) { return input.at(0); } @@ -184,24 +183,24 @@ std::string get_file_path(const std::vector& input) { return current_path.string(); } -bool create_dir_if_needed(const std::filesystem::path& path) { - if (!std::filesystem::is_directory(path)) { - std::filesystem::create_directories(path); +bool create_dir_if_needed(const fs::path& path) { + if (!fs::is_directory(path)) { + fs::create_directories(path); return true; } return false; } bool create_dir_if_needed_for_file(const std::string& path) { - return create_dir_if_needed_for_file(std::filesystem::path(path)); + return create_dir_if_needed_for_file(fs::path(path)); } -bool create_dir_if_needed_for_file(const std::filesystem::path& path) { - return std::filesystem::create_directories(path.parent_path()); +bool create_dir_if_needed_for_file(const fs::path& path) { + return fs::create_directories(path.parent_path()); } -void write_binary_file(const std::filesystem::path& name, const void* data, size_t size) { - FILE* fp = fopen(name.string().c_str(), "wb"); +void write_binary_file(const fs::path& name, const void* data, size_t size) { + FILE* fp = file_util::open_file(name.string().c_str(), "wb"); if (!fp) { throw std::runtime_error("couldn't open file " + name.string()); } @@ -215,10 +214,10 @@ void write_binary_file(const std::filesystem::path& name, const void* data, size } void write_binary_file(const std::string& name, const void* data, size_t size) { - write_binary_file(std::filesystem::path(name), data, size); + write_binary_file(fs::path(name), data, size); } -void write_rgba_png(const std::filesystem::path& name, void* data, int w, int h) { +void write_rgba_png(const fs::path& name, void* data, int w, int h) { auto flags = 0; auto ok = fpng::fpng_encode_image_to_file(name.string().c_str(), data, w, h, 4, flags); @@ -229,11 +228,11 @@ void write_rgba_png(const std::filesystem::path& name, void* data, int w, int h) } void write_text_file(const std::string& file_name, const std::string& text) { - write_text_file(std::filesystem::path(file_name), text); + write_text_file(fs::path(file_name), text); } -void write_text_file(const std::filesystem::path& file_name, const std::string& text) { - FILE* fp = fopen(file_name.string().c_str(), "w"); +void write_text_file(const fs::path& file_name, const std::string& text) { + FILE* fp = file_util::open_file(file_name.string().c_str(), "w"); if (!fp) { lg::error("Failed to fopen {}\n", file_name.string()); throw std::runtime_error("Failed to open file"); @@ -242,26 +241,25 @@ void write_text_file(const std::filesystem::path& file_name, const std::string& fclose(fp); } std::vector read_binary_file(const std::string& filename) { - return read_binary_file(std::filesystem::path(filename)); + return read_binary_file(fs::path(filename)); } -std::vector read_binary_file(const std::filesystem::path& path) { +std::vector read_binary_file(const fs::path& path) { // make sure file exists and isn't a directory - auto status = std::filesystem::status(path); + auto status = fs::status(path); - if (!std::filesystem::exists(status)) { + if (!fs::exists(status)) { throw std::runtime_error( fmt::format("File {} cannot be opened: does not exist.", path.string())); } - if (status.type() != std::filesystem::file_type::regular && - status.type() != std::filesystem::file_type::symlink) { + if (status.type() != fs::file_type::regular && status.type() != fs::file_type::symlink) { throw std::runtime_error( fmt::format("File {} cannot be opened: not a regular file or symlink.", path.string())); } - auto fp = fopen(path.string().c_str(), "rb"); + auto fp = file_util::open_file(path.string().c_str(), "rb"); if (!fp) throw std::runtime_error("File " + path.string() + " cannot be opened: " + std::string(strerror(errno))); @@ -281,7 +279,7 @@ std::vector read_binary_file(const std::filesystem::path& path) { return data; } -std::string read_text_file(const std::filesystem::path& path) { +std::string read_text_file(const fs::path& path) { std::ifstream file(path.string()); if (!file.good()) { throw std::runtime_error("couldn't open " + path.string()); @@ -292,7 +290,7 @@ std::string read_text_file(const std::filesystem::path& path) { } std::string read_text_file(const std::string& path) { - return read_text_file(std::filesystem::path(path)); + return read_text_file(fs::path(path)); } bool is_printable_char(char c) { @@ -304,7 +302,7 @@ std::string combine_path(const std::string& parent, const std::string& child) { } bool file_exists(const std::string& path) { - return std::filesystem::exists(path); + return fs::exists(path); } std::string base_name(const std::string& filename) { @@ -448,7 +446,7 @@ void MakeISOName(char* dst, const char* src) { } void assert_file_exists(const char* path, const char* error_message) { - if (!std::filesystem::exists(path)) { + if (!fs::exists(path)) { ASSERT_MSG(false, fmt::format("File {} was not found: {}", path, error_message)); } } @@ -512,4 +510,12 @@ std::vector decompress_dgo(const std::vector& data_in) { return decompressed_data; } +FILE* open_file(const fs::path& path, std::string mode) { +#ifdef _WIN32 + return _wfopen(path.wstring().c_str(), std::wstring(mode.begin(), mode.end()).c_str()); +#else + return fopen(path.string().c_str(), mode.c_str()); +#endif +} + } // namespace file_util diff --git a/common/util/FileUtil.h b/common/util/FileUtil.h index 765c7762b..90f5410ee 100644 --- a/common/util/FileUtil.h +++ b/common/util/FileUtil.h @@ -5,36 +5,46 @@ * Utility functions for reading and writing files. */ -#include +#ifdef _WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#endif + +#include "third-party/filesystem.hpp" + +#ifdef _WIN32 +#undef FALSE +#endif + #include #include #include #include "common/common_types.h" -namespace fs = std::filesystem; +namespace fs = ghc::filesystem; namespace file_util { -std::filesystem::path get_user_home_dir(); -std::filesystem::path get_user_config_dir(); -std::filesystem::path get_user_settings_dir(); -std::filesystem::path get_user_memcard_dir(); -std::filesystem::path get_jak_project_dir(); +fs::path get_user_home_dir(); +fs::path get_user_config_dir(); +fs::path get_user_settings_dir(); +fs::path get_user_memcard_dir(); +fs::path get_jak_project_dir(); -bool create_dir_if_needed(const std::filesystem::path& path); +bool create_dir_if_needed(const fs::path& path); bool create_dir_if_needed_for_file(const std::string& path); -bool create_dir_if_needed_for_file(const std::filesystem::path& path); -bool setup_project_path(std::optional project_path_override); +bool create_dir_if_needed_for_file(const fs::path& path); +bool setup_project_path(std::optional project_path_override); std::string get_file_path(const std::vector& path); void write_binary_file(const std::string& name, const void* data, size_t size); -void write_binary_file(const std::filesystem::path& name, const void* data, size_t size); -void write_rgba_png(const std::filesystem::path& name, void* data, int w, int h); +void write_binary_file(const fs::path& name, const void* data, size_t size); +void write_rgba_png(const fs::path& name, void* data, int w, int h); void write_text_file(const std::string& file_name, const std::string& text); -void write_text_file(const std::filesystem::path& file_name, const std::string& text); +void write_text_file(const fs::path& file_name, const std::string& text); std::vector read_binary_file(const std::string& filename); -std::vector read_binary_file(const std::filesystem::path& filename); +std::vector read_binary_file(const fs::path& filename); std::string read_text_file(const std::string& path); -std::string read_text_file(const std::filesystem::path& path); +std::string read_text_file(const fs::path& path); bool is_printable_char(char c); std::string combine_path(const std::string& parent, const std::string& child); bool file_exists(const std::string& path); @@ -44,4 +54,6 @@ void ISONameFromAnimationName(char* dst, const char* src); void assert_file_exists(const char* path, const char* error_message); bool dgo_header_is_compressed(const std::vector& data); std::vector decompress_dgo(const std::vector& data_in); + +FILE* open_file(const fs::path& path, std::string mode); } // namespace file_util diff --git a/common/util/read_iso_file.cpp b/common/util/read_iso_file.cpp index 74b836c37..14ac1cc2d 100644 --- a/common/util/read_iso_file.cpp +++ b/common/util/read_iso_file.cpp @@ -80,11 +80,11 @@ void add_from_dir(FILE* fp, u32 sector, u32 size, IsoFile::Entry* parent) { void unpack_entry(FILE* fp, IsoFile& iso, const IsoFile::Entry& entry, - const std::filesystem::path& dest, + const fs::path& dest, bool print_progress) { - std::filesystem::path path_to_entry = dest / entry.name; + fs::path path_to_entry = dest / entry.name; if (entry.is_dir) { - std::filesystem::create_directory(path_to_entry); + fs::create_directory(path_to_entry); for (const auto& child : entry.children) { unpack_entry(fp, iso, child, path_to_entry, print_progress); } @@ -118,15 +118,12 @@ IsoFile find_files_in_iso(FILE* fp) { return result; } -void unpack_iso_files(FILE* fp, - IsoFile& layout, - const std::filesystem::path& dest, - bool print_progress) { +void unpack_iso_files(FILE* fp, IsoFile& layout, const fs::path& dest, bool print_progress) { unpack_entry(fp, layout, layout.root, dest, print_progress); } IsoFile unpack_iso_files(FILE* fp, - const std::filesystem::path& dest, + const fs::path& dest, bool print_progress, const bool hashFiles) { auto file = find_files_in_iso(fp); diff --git a/common/util/read_iso_file.h b/common/util/read_iso_file.h index ef9fae8b7..a25fd0a40 100644 --- a/common/util/read_iso_file.h +++ b/common/util/read_iso_file.h @@ -1,9 +1,10 @@ #pragma once -#include #include #include +#include "common/util/FileUtil.h" + #include "third-party/xxhash.hpp" struct IsoFile { @@ -34,8 +35,8 @@ struct IsoFile { }; IsoFile find_files_in_iso(FILE* fp); -void unpack_iso_files(FILE* fp, IsoFile& layout, const std::filesystem::path& dest); +void unpack_iso_files(FILE* fp, IsoFile& layout, const fs::path& dest); IsoFile unpack_iso_files(FILE* fp, - const std::filesystem::path& dest, + const fs::path& dest, bool print_progress, const bool hashFiles = false); diff --git a/common/util/unicode_util.cpp b/common/util/unicode_util.cpp new file mode 100644 index 000000000..11f281ba0 --- /dev/null +++ b/common/util/unicode_util.cpp @@ -0,0 +1,61 @@ +#include "unicode_util.h" + +// clang-format off +#ifdef _WIN32 +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#undef FALSE +#endif +// clang-format on + +std::string utf8_from_utf16(const wchar_t* utf16_string) { +#ifdef _WIN32 + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, nullptr, + 0, nullptr, nullptr); + if (target_length == 0) { + return std::string(); + } + std::string utf8_string; + utf8_string.resize(target_length); + int converted_length = WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, -1, + utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +#else + return "don't call this on linux"; +#endif +} + +std::vector get_widechar_cli_args() { +#ifdef _WIN32 + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argv == nullptr) { + return {}; + } + + std::vector command_line_arguments; + + for (int i = 0; i < argc; i++) { + command_line_arguments.push_back(utf8_from_utf16(argv[i])); + } + + LocalFree(argv); + + return command_line_arguments; +#else + return {"don't call this on linux"}; +#endif +} diff --git a/common/util/unicode_util.h b/common/util/unicode_util.h new file mode 100644 index 000000000..cd79df1fe --- /dev/null +++ b/common/util/unicode_util.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +std::string utf8_from_utf16(const wchar_t* utf16_string); +std::vector get_widechar_cli_args(); diff --git a/decompiler/CMakeLists.txt b/decompiler/CMakeLists.txt index d28c1b9a5..8bfadabdc 100644 --- a/decompiler/CMakeLists.txt +++ b/decompiler/CMakeLists.txt @@ -100,7 +100,7 @@ target_link_libraries(decompiler add_executable(extractor - extractor/main.cpp) + extractor/main.cpp extractor/extractor_util.hpp ) target_link_libraries(extractor decomp diff --git a/decompiler/ObjectFile/ObjectFileDB.cpp b/decompiler/ObjectFile/ObjectFileDB.cpp index 1d6319820..17e129b08 100644 --- a/decompiler/ObjectFile/ObjectFileDB.cpp +++ b/decompiler/ObjectFile/ObjectFileDB.cpp @@ -113,10 +113,10 @@ const ObjectFileData& ObjectFileDB::lookup_record(const ObjectFileRecord& rec) c /*! * Build an object file DB for the given list of DGOs. */ -ObjectFileDB::ObjectFileDB(const std::vector& _dgos, - const std::filesystem::path& obj_file_name_map_file, - const std::vector& object_files, - const std::vector& str_files, +ObjectFileDB::ObjectFileDB(const std::vector& _dgos, + const fs::path& obj_file_name_map_file, + const std::vector& object_files, + const std::vector& str_files, const Config& config) { Timer timer; @@ -258,7 +258,7 @@ void ObjectFileDB::load_map_file(const std::string& map_data) { /*! * Load the objects stored in the given DGO into the ObjectFileDB */ -void ObjectFileDB::get_objs_from_dgo(const std::filesystem::path& filename, const Config& config) { +void ObjectFileDB::get_objs_from_dgo(const fs::path& filename, const Config& config) { auto dgo_data = file_util::read_binary_file(filename); stats.total_dgo_bytes += dgo_data.size(); @@ -516,7 +516,7 @@ void ObjectFileDB::process_labels() { /*! * Dump object files and their linking data to text files for debugging */ -void ObjectFileDB::write_object_file_words(const std::filesystem::path& output_dir, +void ObjectFileDB::write_object_file_words(const fs::path& output_dir, bool dump_data, bool dump_code) { lg::info("- Writing object file dumps (code? {} data? {})...", dump_code, dump_data); @@ -545,7 +545,7 @@ void ObjectFileDB::write_object_file_words(const std::filesystem::path& output_d /*! * Dump disassembly for object files containing code. Data zones will also be dumped. */ -void ObjectFileDB::write_disassembly(const std::filesystem::path& output_dir, +void ObjectFileDB::write_disassembly(const fs::path& output_dir, bool disassemble_data, bool disassemble_code, bool print_hex) { @@ -623,7 +623,7 @@ void ObjectFileDB::find_code(const Config& config) { * Finds and writes all scripts into a file named all_scripts.lisp. * Doesn't change any state in ObjectFileDB. */ -void ObjectFileDB::find_and_write_scripts(const std::filesystem::path& output_dir) { +void ObjectFileDB::find_and_write_scripts(const fs::path& output_dir) { lg::info("- Finding scripts in object files..."); Timer timer; std::string all_scripts; @@ -645,8 +645,7 @@ void ObjectFileDB::find_and_write_scripts(const std::filesystem::path& output_di lg::info(" Total {:.3f} ms\n", timer.getMs()); } -std::string ObjectFileDB::process_tpages(TextureDB& tex_db, - const std::filesystem::path& output_path) { +std::string ObjectFileDB::process_tpages(TextureDB& tex_db, const fs::path& output_path) { lg::info("- Finding textures in tpages..."); std::string tpage_string = "tpage-"; int total = 0, success = 0; @@ -796,7 +795,7 @@ void ObjectFileDB::extract_art_info() { /*! * Write out the art group information. */ -void ObjectFileDB::dump_art_info(const std::filesystem::path& output_dir) { +void ObjectFileDB::dump_art_info(const fs::path& output_dir) { lg::info("Writing art group info..."); Timer timer; @@ -820,7 +819,7 @@ void ObjectFileDB::dump_art_info(const std::filesystem::path& output_dir) { lg::info("Written art group info: in {:.2f} ms\n", timer.getMs()); } -void ObjectFileDB::dump_raw_objects(const std::filesystem::path& output_dir) { +void ObjectFileDB::dump_raw_objects(const fs::path& output_dir) { for_each_obj([&](ObjectFileData& data) { auto dest = output_dir / data.to_unique_name(); if (data.obj_version != 3) { diff --git a/decompiler/ObjectFile/ObjectFileDB.h b/decompiler/ObjectFile/ObjectFileDB.h index e2497b2e0..df7f16a5c 100644 --- a/decompiler/ObjectFile/ObjectFileDB.h +++ b/decompiler/ObjectFile/ObjectFileDB.h @@ -15,6 +15,7 @@ #include "common/common_types.h" #include "common/util/Assert.h" +#include "common/util/FileUtil.h" #include "decompiler/analysis/symbol_def_map.h" #include "decompiler/data/TextureDB.h" @@ -144,30 +145,28 @@ struct LetRewriteStats { class ObjectFileDB { public: - ObjectFileDB(const std::vector& _dgos, - const std::filesystem::path& obj_file_name_map_file, - const std::vector& object_files, - const std::vector& str_files, + ObjectFileDB(const std::vector& _dgos, + const fs::path& obj_file_name_map_file, + const std::vector& object_files, + const std::vector& str_files, const Config& config); std::string generate_dgo_listing(); std::string generate_obj_listing(const std::unordered_set& merged_objs); void process_link_data(const Config& config); void process_labels(); void find_code(const Config& config); - void find_and_write_scripts(const std::filesystem::path& output_dir); + void find_and_write_scripts(const fs::path& output_dir); void extract_art_info(); - void dump_art_info(const std::filesystem::path& output_dir); - void dump_raw_objects(const std::filesystem::path& output_dir); - void write_object_file_words(const std::filesystem::path& output_dir, - bool dump_data, - bool dump_code); - void write_disassembly(const std::filesystem::path& output_dir, + void dump_art_info(const fs::path& output_dir); + void dump_raw_objects(const fs::path& output_dir); + void write_object_file_words(const fs::path& output_dir, bool dump_data, bool dump_code); + void write_disassembly(const fs::path& output_dir, bool disassemble_data, bool disassemble_code, bool print_hex); void analyze_functions_ir2( - const std::filesystem::path& output_dir, + const fs::path& output_dir, const Config& config, const std::unordered_set& skip_functions, const std::unordered_map>& skip_states = {}); @@ -185,7 +184,7 @@ class ObjectFileDB { void ir2_rewrite_inline_asm_instructions(int seg, ObjectFileData& data); void ir2_insert_anonymous_functions(int seg, ObjectFileData& data); void ir2_symbol_definition_map(ObjectFileData& data); - void ir2_write_results(const std::filesystem::path& output_dir, + void ir2_write_results(const fs::path& output_dir, const Config& config, const std::vector& imports, ObjectFileData& data); @@ -193,7 +192,7 @@ class ObjectFileDB { void ir2_do_segment_analysis_phase2(int seg, const Config& config, ObjectFileData& data); void ir2_setup_labels(const Config& config, ObjectFileData& data); void ir2_run_mips2c(const Config& config, ObjectFileData& data); - void ir2_analyze_all_types(const std::filesystem::path& output_file, + void ir2_analyze_all_types(const fs::path& output_file, const std::optional& previous_game_types, const std::unordered_set& bad_types); std::string ir2_to_file(ObjectFileData& data, const Config& config); @@ -202,7 +201,7 @@ class ObjectFileDB { const std::vector& imports, const std::unordered_set& skip_functions); - std::string process_tpages(TextureDB& tex_db, const std::filesystem::path& output_path); + std::string process_tpages(TextureDB& tex_db, const fs::path& output_path); std::string process_game_count_file(); std::string process_game_text_files(const Config& cfg); @@ -216,7 +215,7 @@ class ObjectFileDB { public: void load_map_file(const std::string& map_data); - void get_objs_from_dgo(const std::filesystem::path& filename, const Config& config); + void get_objs_from_dgo(const fs::path& filename, const Config& config); void add_obj_from_dgo(const std::string& obj_name, const std::string& name_in_dgo, const uint8_t* obj_data, diff --git a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp index 80e0c3d47..a1679c9d2 100644 --- a/decompiler/ObjectFile/ObjectFileDB_IR2.cpp +++ b/decompiler/ObjectFile/ObjectFileDB_IR2.cpp @@ -38,7 +38,7 @@ namespace decompiler { * functions, but nothing else. */ void ObjectFileDB::analyze_functions_ir2( - const std::filesystem::path& output_dir, + const fs::path& output_dir, const Config& config, const std::unordered_set& skip_functions, const std::unordered_map>& skip_states) { @@ -300,7 +300,7 @@ void ObjectFileDB::ir2_top_level_pass(const Config& config) { lg::info("{:4d} logins {:.2f}%\n", total_top_levels, 100.f * total_top_levels / total_functions); } -void ObjectFileDB::ir2_analyze_all_types(const std::filesystem::path& output_file, +void ObjectFileDB::ir2_analyze_all_types(const fs::path& output_file, const std::optional& previous_game_types, const std::unordered_set& bad_types) { struct PerObject { @@ -690,7 +690,7 @@ void ObjectFileDB::ir2_insert_anonymous_functions(int seg, ObjectFileData& data) }); } -void ObjectFileDB::ir2_write_results(const std::filesystem::path& output_dir, +void ObjectFileDB::ir2_write_results(const fs::path& output_dir, const Config& config, const std::vector& imports, ObjectFileData& obj) { diff --git a/decompiler/data/StrFileReader.cpp b/decompiler/data/StrFileReader.cpp index 5f8874fe9..9a4320b70 100644 --- a/decompiler/data/StrFileReader.cpp +++ b/decompiler/data/StrFileReader.cpp @@ -14,7 +14,7 @@ #include "game/common/str_rpc_types.h" namespace decompiler { -StrFileReader::StrFileReader(const std::filesystem::path& file_path) { +StrFileReader::StrFileReader(const fs::path& file_path) { auto data = file_util::read_binary_file(file_path); ASSERT(data.size() >= SECTOR_SIZE); // must have at least the header sector ASSERT(data.size() % SECTOR_SIZE == 0); // should be multiple of the sector size. diff --git a/decompiler/data/StrFileReader.h b/decompiler/data/StrFileReader.h index 03d00d8e2..2ff542d08 100644 --- a/decompiler/data/StrFileReader.h +++ b/decompiler/data/StrFileReader.h @@ -5,16 +5,16 @@ * Utility class to read a .STR file and extract the full file name. */ -#include #include #include #include "common/common_types.h" +#include "common/util/FileUtil.h" namespace decompiler { class StrFileReader { public: - explicit StrFileReader(const std::filesystem::path& file_path); + explicit StrFileReader(const fs::path& file_path); int chunk_count() const; const std::vector& get_chunk(int idx) const; std::string get_full_name(const std::string& short_name) const; @@ -22,4 +22,4 @@ class StrFileReader { private: std::vector> m_chunks; }; -} // namespace decompiler \ No newline at end of file +} // namespace decompiler diff --git a/decompiler/data/TextureDB.cpp b/decompiler/data/TextureDB.cpp index 97d2a0db6..ec90e2bd4 100644 --- a/decompiler/data/TextureDB.cpp +++ b/decompiler/data/TextureDB.cpp @@ -1,7 +1,5 @@ #include "TextureDB.h" -#include - #include "common/util/Assert.h" #include "third-party/fmt/core.h" @@ -45,12 +43,11 @@ void TextureDB::add_texture(u32 tpage, } } -void TextureDB::replace_textures(const std::filesystem::path& path) { - std::filesystem::path base_path(path); +void TextureDB::replace_textures(const fs::path& path) { + fs::path base_path(path); for (auto& tex : textures) { - std::filesystem::path full_path = - base_path / tpage_names.at(tex.second.page) / (tex.second.name + ".png"); - if (std::filesystem::exists(full_path)) { + fs::path full_path = base_path / tpage_names.at(tex.second.page) / (tex.second.name + ".png"); + if (fs::exists(full_path)) { fmt::print("Replacing {}\n", full_path.string().c_str()); int w, h; auto data = stbi_load(full_path.string().c_str(), &w, &h, 0, 4); // rgba channels diff --git a/decompiler/data/TextureDB.h b/decompiler/data/TextureDB.h index 9e2280ace..48178f9a3 100644 --- a/decompiler/data/TextureDB.h +++ b/decompiler/data/TextureDB.h @@ -1,12 +1,12 @@ #pragma once -#include #include #include #include #include #include "common/common_types.h" +#include "common/util/FileUtil.h" namespace decompiler { struct TextureDB { @@ -30,6 +30,6 @@ struct TextureDB { const std::string& tpage_name, const std::vector& level_names); - void replace_textures(const std::filesystem::path& path); + void replace_textures(const fs::path& path); }; } // namespace decompiler diff --git a/decompiler/data/streamed_audio.cpp b/decompiler/data/streamed_audio.cpp index 59c297d99..466626a0f 100644 --- a/decompiler/data/streamed_audio.cpp +++ b/decompiler/data/streamed_audio.cpp @@ -89,7 +89,7 @@ struct VagFileHeader { /*! * Read the DIR file into an AudioDir */ -AudioDir read_audio_dir(const std::filesystem::path& path) { +AudioDir read_audio_dir(const fs::path& path) { // matches the format in file. struct DirEntry { char name[8]; @@ -145,7 +145,7 @@ struct AudioFileInfo { double length_seconds; }; -AudioFileInfo process_audio_file(const std::filesystem::path& output_folder, +AudioFileInfo process_audio_file(const fs::path& output_folder, const std::vector& data, const std::string& name, const std::string& suffix) { @@ -182,8 +182,8 @@ AudioFileInfo process_audio_file(const std::filesystem::path& output_folder, return {vag_filename, (double)decoded_samples.size() / header.sample_rate}; } -void process_streamed_audio(const std::filesystem::path& output_path, - const std::filesystem::path& input_dir, +void process_streamed_audio(const fs::path& output_path, + const fs::path& input_dir, const std::vector& audio_files) { auto dir_data = read_audio_dir(input_dir / "VAG" / "VAGDIR.AYB"); double audio_len = 0.f; @@ -201,7 +201,7 @@ void process_streamed_audio(const std::filesystem::path& output_path, for (size_t lang_id = 0; lang_id < audio_files.size(); lang_id++) { auto& file = audio_files[lang_id]; auto wad_data = file_util::read_binary_file(input_dir / "VAG" / file); - auto suffix = std::filesystem::path(file).extension().u8string().substr(1); + auto suffix = fs::path(file).extension().u8string().substr(1); langs.push_back(suffix); dir_data.set_file_size(wad_data.size()); for (int i = 0; i < dir_data.entry_count(); i++) { diff --git a/decompiler/data/streamed_audio.h b/decompiler/data/streamed_audio.h index bbdedd383..b1f5ecbb4 100644 --- a/decompiler/data/streamed_audio.h +++ b/decompiler/data/streamed_audio.h @@ -1,11 +1,12 @@ #pragma once -#include #include #include +#include "common/util/FileUtil.h" + namespace decompiler { -void process_streamed_audio(const std::filesystem::path& output_path, - const std::filesystem::path& input_dir, +void process_streamed_audio(const fs::path& output_path, + const fs::path& input_dir, const std::vector& audio_files); -} \ No newline at end of file +} diff --git a/decompiler/data/tpage.cpp b/decompiler/data/tpage.cpp index cf4ec7be6..a6610485b 100644 --- a/decompiler/data/tpage.cpp +++ b/decompiler/data/tpage.cpp @@ -421,7 +421,7 @@ TexturePage read_texture_page(ObjectFileData& data, */ TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db, - const std::filesystem::path& output_path) { + const fs::path& output_path) { TPageResultStats stats; auto& words = data.linked_data.words_by_seg.at(0); const auto& level_names = data.dgo_names; diff --git a/decompiler/data/tpage.h b/decompiler/data/tpage.h index 164330273..b442743dd 100644 --- a/decompiler/data/tpage.h +++ b/decompiler/data/tpage.h @@ -1,7 +1,5 @@ #pragma once -#include - #include "decompiler/data/TextureDB.h" namespace decompiler { @@ -15,5 +13,5 @@ struct TPageResultStats { TPageResultStats process_tpage(ObjectFileData& data, TextureDB& texture_db, - const std::filesystem::path& output_path); -} // namespace decompiler \ No newline at end of file + const fs::path& output_path); +} // namespace decompiler diff --git a/decompiler/extractor/extractor_util.hpp b/decompiler/extractor/extractor_util.hpp new file mode 100644 index 000000000..0aea716a9 --- /dev/null +++ b/decompiler/extractor/extractor_util.hpp @@ -0,0 +1,251 @@ +#pragma once + +#include +#include +#include + +#include "common/log/log.h" +#include "common/util/FileUtil.h" +#include +#include + +#include + +#include "third-party/xxhash.hpp" + +enum class ExtractorErrorCode { + SUCCESS = 0, + INVALID_CLI_INPUT = 3990, + VALIDATION_CANT_LOCATE_ELF = 4000, + VALIDATION_SERIAL_MISSING_FROM_DB = 4001, + VALIDATION_ELF_MISSING_FROM_DB = 4002, + VALIDATION_BAD_ISO_CONTENTS = 4010, + VALIDATION_INCORRECT_EXTRACTION_COUNT = 4011, + VALIDATION_FILE_CONTENTS_UNEXPECTED = 4012, + VALIDATION_BAD_EXTRACTION = 4020, + DECOMPILATION_GENERIC_ERROR = 4030, + EXTRACTION_INVALID_ISO_PATH = 4040, + EXTRACTION_ISO_UNEXPECTED_SIZE = 4041, + COMPILATION_BAD_PROJECT_PATH = 4050, +}; + +enum GameIsoFlags { FLAG_JAK1_BLACK_LABEL = (1 << 0) }; + +static const std::unordered_map sGameIsoFlagNames = { + {"jak1-black-label", FLAG_JAK1_BLACK_LABEL}}; + +// used for - decompiler_out/ and iso_data/ +std::unordered_map data_subfolders = {{"jak1", "jak1"}}; + +struct ISOMetadata { + std::string canonical_name; + std::string region; + int num_files; + xxh::hash64_t contents_hash; + std::string decomp_config; + std::string game_name; + std::vector flags; +}; + +// This is all we need to re-fetch info from the database +// - if this changes such that we have a collision in the future, +// then the database isn't adequate and everything must change +struct BuildInfo { + std::string serial = ""; + xxh::hash64_t elf_hash = 0; +}; + +void to_json(nlohmann::json& j, const BuildInfo& info) { + j = nlohmann::json{{"serial", info.serial}, {"elf_hash", info.elf_hash}}; +} + +void from_json(const nlohmann::json& j, BuildInfo& info) { + j[0].at("serial").get_to(info.serial); + j[0].at("elf_hash").get_to(info.elf_hash); +} + +std::optional get_buildinfo_from_path(fs::path iso_data_path) { + if (!fs::exists(iso_data_path / "buildinfo.json")) { + return {}; + } + auto buildinfo_path = (iso_data_path / "buildinfo.json").string(); + try { + return parse_commented_json(file_util::read_text_file(buildinfo_path), buildinfo_path) + .get(); + } catch (std::exception& e) { + lg::error("JSON parsing error on buildinfo.json - {}", e.what()); + return {}; + } +} + +static const ISOMetadata jak1_ntsc_black_label_info = { + "Jak & Daxter™: The Precursor Legacy (Black Label)", + "NTSC-U", + 337, + 11363853835861842434U, + "jak1_ntsc_black_label", + "jak1", + {"jak1-black-label"}}; + +// { SERIAL : { ELF_HASH : ISOMetadataDatabase } } +static const std::unordered_map> + isoDatabase{{"SCUS-97124", + {{7280758013604870207U, jak1_ntsc_black_label_info}, + {744661860962747854, + {"Jak & Daxter™: The Precursor Legacy", + "NTSC-U", + 338, + 8538304367812415885U, + "jak1_jp", + "jak1", + {}}}}}, + {"SCES-50361", + {{12150718117852276522U, + {"Jak & Daxter™: The Precursor Legacy", + "PAL", + 338, + 16850370297611763875U, + "jak1_pal", + "jak1", + {}}}}}, + {"SCPS-15021", + {{16909372048085114219U, + {"ジャックXダクスター ~ 旧世界の遺産", + "NTSC-J", + 338, + 1262350561338887717, + "jak1_jp", + "jak1", + {}}}}}}; + +std::optional get_version_info_from_build_info(const BuildInfo& build_info) { + if (build_info.serial.empty() || build_info.elf_hash == 0) { + return {}; + } + auto dbEntry = isoDatabase.find(build_info.serial); + if (dbEntry == isoDatabase.end()) { + return {}; + } + + auto& metaMap = dbEntry->second; + auto meta_entry = metaMap.find(build_info.elf_hash); + if (meta_entry == metaMap.end()) { + return {}; + } + return std::make_optional(meta_entry->second); +} + +ISOMetadata get_version_info_or_default(const fs::path& iso_data_path) { + ISOMetadata version_info = jak1_ntsc_black_label_info; + const auto build_info = get_buildinfo_from_path(iso_data_path); + if (!build_info) { + lg::warn( + "unable locate buildinfo.json file in iso data path, defaulting to Jak 1 - NTSC " + "Black Label"); + } else { + auto maybe_version_info = get_version_info_from_build_info(build_info.value()); + if (!maybe_version_info) { + lg::warn( + "unable to determine game version from buildinfo.json file, defaulting to Jak 1 - NTSC " + "Black Label"); + } else { + version_info = maybe_version_info.value(); + } + } + return version_info; +} + +std::tuple, std::optional> findElfFile( + const fs::path& extracted_iso_path) { + std::optional serial = std::nullopt; + std::optional elf_hash = std::nullopt; + for (const auto& entry : fs::directory_iterator(extracted_iso_path)) { + auto as_str = entry.path().filename().string(); + if (std::regex_match(as_str, std::regex(".{4}_.{3}\\..{2}"))) { + serial = std::make_optional( + fmt::format("{}-{}", as_str.substr(0, 4), as_str.substr(5, 3) + as_str.substr(9, 2))); + // We already found the path, so hash it while we're here + auto fp = file_util::open_file(entry.path().string().c_str(), "rb"); + fseek(fp, 0, SEEK_END); + size_t size = ftell(fp); + std::vector buffer(size); + rewind(fp); + fread(&buffer[0], sizeof(std::vector::value_type), buffer.size(), fp); + elf_hash = std::make_optional(xxh::xxhash<64>(buffer)); + fclose(fp); + break; + } + } + return {serial, elf_hash}; +} + +void log_potential_new_db_entry(ExtractorErrorCode error_code, + const std::string& serial, + const xxh::hash64_t elf_hash, + const int files_extracted, + const xxh::hash64_t contents_hash) { + // Finally, return the result + // Generate the map entry to make things simple, just convienance + if (error_code == ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB) { + lg::info( + "If this is a new release or version that should be supported, consider adding the " + "following serial entry to the database:"); + lg::info( + "\t'{{\"{}\", {{{{{}U, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, " + "\"DECOMP_CONFIG_FILENAME_NO_EXTENSION\", \"jak1|jak2|jak3|jakx\", {}}}}}}}}}'", + serial, elf_hash, files_extracted, contents_hash); + } else if (error_code == ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB) { + lg::info( + "If this is a new release or version that should be supported, consider adding the " + "following ELF entry to the database under the '{}' serial:", + serial); + lg::info( + "\t'{{{}, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, " + "\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\", \"jak1|jak2|jak3|jakx\", {}}}}}'", + elf_hash, files_extracted, contents_hash); + } +} + +std::tuple is_iso_file(fs::path path_to_supposed_iso) { + // it's a file, normalize extension case and verify it's an ISO file + std::string ext = path_to_supposed_iso.extension().string(); + if (!std::regex_match(ext, std::regex("\\.(iso|ISO)"))) { + lg::error("Provided game data path contains a file that isn't a .ISO!"); + return {false, ExtractorErrorCode::EXTRACTION_INVALID_ISO_PATH}; + } + + // make sure the .iso is greater than 1GB in size + // to-do: verify game header data as well + if (fs::file_size(path_to_supposed_iso) < 1000000000) { + lg::error("Provided game data file appears to be too small or corrupted! Size is: {}", + fs::file_size(path_to_supposed_iso)); + return {false, ExtractorErrorCode::EXTRACTION_ISO_UNEXPECTED_SIZE}; + } + return {true, ExtractorErrorCode::SUCCESS}; +} + +std::tuple calculate_extraction_hash(const IsoFile& iso_file) { + // - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka + // files) irrelevant + xxh::hash64_t combined_hash = 0; + for (const auto& hash : iso_file.hashes) { + combined_hash ^= hash; + } + return {xxh::xxhash<64>({combined_hash}), iso_file.hashes.size()}; +} + +std::tuple calculate_extraction_hash(const fs::path& extracted_iso_path) { + // - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka + // files) irrelevant + xxh::hash64_t combined_hash = 0; + int filec = 0; + for (auto const& dir_entry : fs::recursive_directory_iterator(extracted_iso_path)) { + if (dir_entry.is_regular_file()) { + auto buffer = file_util::read_binary_file(dir_entry.path().string()); + auto hash = xxh::xxhash<64>(buffer); + combined_hash ^= hash; + filec++; + } + } + return {xxh::xxhash<64>({combined_hash}), filec}; +} diff --git a/decompiler/extractor/main.cpp b/decompiler/extractor/main.cpp index 9c5804ba4..b06c7aecb 100644 --- a/decompiler/extractor/main.cpp +++ b/decompiler/extractor/main.cpp @@ -2,10 +2,13 @@ #include #include +#include "extractor_util.hpp" + #include "common/log/log.h" #include "common/util/FileUtil.h" #include "common/util/json_util.h" #include "common/util/read_iso_file.h" +#include #include "decompiler/Disasm/OpcodeInfo.h" #include "decompiler/ObjectFile/ObjectFileDB.h" @@ -15,369 +18,111 @@ #include "third-party/CLI11.hpp" -enum class ExtractorErrorCode { - SUCCESS = 0, - VALIDATION_CANT_LOCATE_ELF = 4000, - VALIDATION_SERIAL_MISSING_FROM_DB = 4001, - VALIDATION_ELF_MISSING_FROM_DB = 4002, - VALIDATION_BAD_ISO_CONTENTS = 4010, - VALIDATION_INCORRECT_EXTRACTION_COUNT = 4011, - VALIDATION_BAD_EXTRACTION = 4020, - DECOMPILATION_GENERIC_ERROR = 4030, - EXTRACTION_INVALID_ISO_PATH = 4040, - EXTRACTION_ISO_UNEXPECTED_SIZE = 4041 -}; - -enum GameIsoFlags { FLAG_JAK1_BLACK_LABEL = (1 << 0) }; - -static const std::unordered_map sGameIsoFlagNames = { - {"jak1-black-label", FLAG_JAK1_BLACK_LABEL}}; - -struct ISOMetadata { - std::string canonical_name; - std::string region; - int num_files; - xxh::hash64_t contents_hash; - std::string decomp_config; - GameIsoFlags flags = (GameIsoFlags)0; -}; - -// TODO - when we support jak2 and beyond, add which game it's for as well -// this will let the installer reject (or gracefully handle) jak2 isos on the jak1 page, etc. - -// { SERIAL : { ELF_HASH : ISOMetadataDatabase } } -static const std::map> isoDatabase{ - {"SCUS-97124", - {{7280758013604870207U, - {"Jak & Daxter™: The Precursor Legacy (Black Label)", "NTSC-U", 337, 11363853835861842434U, - "jak1_ntsc_black_label", FLAG_JAK1_BLACK_LABEL}}, - {744661860962747854, - {"Jak & Daxter™: The Precursor Legacy", "NTSC-U", 338, 8538304367812415885U, "jak1_jp"}}}}, - {"SCES-50361", - {{12150718117852276522U, - {"Jak & Daxter™: The Precursor Legacy", "PAL", 338, 16850370297611763875U, "jak1_pal"}}}}, - {"SCPS-15021", - {{16909372048085114219U, - {"ジャックXダクスター ~ 旧世界の遺産", "NTSC-J", 338, 1262350561338887717, - "jak1_jp"}}}}}; - -void setup_global_decompiler_stuff(std::optional project_path_override) { - decompiler::init_opcode_info(); - file_util::setup_project_path(project_path_override); -} - -IsoFile extract_files(std::filesystem::path data_dir_path, - std::filesystem::path extracted_iso_path) { - fmt::print( +IsoFile extract_files(fs::path input_file_path, fs::path extracted_iso_path) { + lg::info( "Note: Provided game data path '{}' points to a file, not a directory. Assuming it's an ISO " - "file and attempting to extract!\n", - data_dir_path.string()); + "file and attempting to extract!", + input_file_path.string()); - std::filesystem::create_directories(extracted_iso_path); + fs::create_directories(extracted_iso_path); - auto fp = fopen(data_dir_path.string().c_str(), "rb"); - ASSERT_MSG(fp, "failed to open input ISO file\n"); + auto fp = file_util::open_file(input_file_path, "rb"); + ASSERT_MSG(fp, "failed to open input ISO file"); IsoFile iso = unpack_iso_files(fp, extracted_iso_path, true, true); fclose(fp); return iso; } -std::pair, std::optional> findElfFile( - const std::filesystem::path& extracted_iso_path) { - std::optional serial = std::nullopt; - std::optional elf_hash = std::nullopt; - for (const auto& entry : fs::directory_iterator(extracted_iso_path)) { - auto as_str = entry.path().filename().string(); - if (std::regex_match(as_str, std::regex(".{4}_.{3}\\..{2}"))) { - serial = std::make_optional( - fmt::format("{}-{}", as_str.substr(0, 4), as_str.substr(5, 3) + as_str.substr(9, 2))); - // We already found the path, so hash it while we're here - auto fp = fopen(entry.path().string().c_str(), "rb"); - fseek(fp, 0, SEEK_END); - size_t size = ftell(fp); - std::vector buffer(size); - rewind(fp); - fread(&buffer[0], sizeof(std::vector::value_type), buffer.size(), fp); - elf_hash = std::make_optional(xxh::xxhash<64>(buffer)); - break; - } - } - return {serial, elf_hash}; -} - -std::pair> validate( - const std::filesystem::path& extracted_iso_path) { - if (!std::filesystem::exists(extracted_iso_path / "DGO")) { - fmt::print(stderr, "ERROR: input folder doesn't have a DGO folder. Is this the right input?\n"); - return {ExtractorErrorCode::VALIDATION_BAD_EXTRACTION, std::nullopt}; +std::tuple, ExtractorErrorCode> validate( + const fs::path& extracted_iso_path, + const xxh::hash64_t expected_hash, + const int expected_num_files) { + if (!fs::exists(extracted_iso_path / "DGO")) { + lg::error("input folder doesn't have a DGO folder. Is this the right input?"); + return {std::nullopt, ExtractorErrorCode::VALIDATION_BAD_EXTRACTION}; } std::optional error_code; - std::optional serial = std::nullopt; - std::optional elf_hash = std::nullopt; - std::tie(serial, elf_hash) = findElfFile(extracted_iso_path); - - // - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka - // files) irrelevant - xxh::hash64_t combined_hash = 0; - int filec = 0; - for (auto const& dir_entry : std::filesystem::recursive_directory_iterator(extracted_iso_path)) { - if (dir_entry.is_regular_file()) { - auto buffer = file_util::read_binary_file(dir_entry.path().string()); - auto hash = xxh::xxhash<64>(buffer); - combined_hash ^= hash; - filec++; - } - } - xxh::hash64_t contents_hash = xxh::xxhash<64>({combined_hash}); + const auto [serial, elf_hash] = findElfFile(extracted_iso_path); if (!serial || !elf_hash) { - fmt::print(stderr, "ERROR: Unable to locate a Serial/ELF file!\n"); - if (!error_code.has_value()) { - error_code = std::make_optional(ExtractorErrorCode::VALIDATION_CANT_LOCATE_ELF); - } + lg::error("Unable to locate a Serial/ELF file!"); // No point in continuing here - return {*error_code, std::nullopt}; - } - - // Find the game in our tracking database - std::optional meta_res = std::nullopt; - if (auto dbEntry = isoDatabase.find(serial.value()); dbEntry == isoDatabase.end()) { - fmt::print(stderr, "ERROR: Serial '{}' not found in the validation database\n", serial.value()); - if (!error_code.has_value()) { - error_code = std::make_optional(ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB); - } - } else { - auto& metaMap = dbEntry->second; - auto meta_entry = metaMap.find(elf_hash.value()); - if (meta_entry == metaMap.end()) { - fmt::print(stderr, - "ERROR: ELF Hash '{}' not found in the validation database, is this a new or " - "modified version of the same game?\n", - elf_hash.value()); - if (!error_code.has_value()) { - error_code = std::make_optional(ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB); - } - } else { - meta_res = std::make_optional(meta_entry->second); - const auto& meta = *meta_res; - // Print out some information - fmt::print("Detected Game Metadata:\n"); - fmt::print("\tDetected - {}\n", meta.canonical_name); - fmt::print("\tRegion - {}\n", meta.region); - fmt::print("\tSerial - {}\n", dbEntry->first); - fmt::print("\tUses Decompiler Config - {}\n", meta.decomp_config); - - // - Number of Files - if (meta.num_files != filec) { - fmt::print(stderr, - "ERROR: Extracted an unexpected number of files. Expected '{}', Actual '{}'\n", - meta.num_files, filec); - if (!error_code.has_value()) { - error_code = - std::make_optional(ExtractorErrorCode::VALIDATION_INCORRECT_EXTRACTION_COUNT); - } - } - // Check the ISO Hash - if (meta.contents_hash != contents_hash) { - fmt::print(stderr, - "ERROR: Overall ISO content's hash does not match. Expected '{}', Actual '{}'\n", - meta.contents_hash, contents_hash); - } - } - } - - // Finally, return the result - if (error_code.has_value()) { - // Generate the map entry to make things simple, just convienance - if (error_code.value() == ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB) { - fmt::print( - "If this is a new release or version that should be supported, consider adding the " - "following serial entry to the database:\n"); - fmt::print( - "\t'{{\"{}\", {{{{{}U, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, " - "\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}}}}}'\n", - serial.value(), elf_hash.value(), filec, contents_hash); - } else if (error_code.value() == ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB) { - fmt::print( - "If this is a new release or version that should be supported, consider adding the " - "following ELF entry to the database under the '{}' serial:\n", - serial.value()); - fmt::print( - "\t'{{{}, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, " - "\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}'\n", - elf_hash.value(), filec, contents_hash); - } else { - fmt::print(stderr, - "Validation has failed to match with expected values, see the above errors for " - "specifics. This may be an error in the validation database!\n"); - } - return {*error_code, std::nullopt}; - } - - return {ExtractorErrorCode::SUCCESS, meta_res}; -} - -std::pair> validate( - const IsoFile& iso_file, - const std::filesystem::path& extracted_iso_path) { - if (!std::filesystem::exists(extracted_iso_path / "DGO")) { - fmt::print(stderr, "ERROR: input folder doesn't have a DGO folder. Is this the right input?\n"); - return {ExtractorErrorCode::VALIDATION_BAD_EXTRACTION, std::nullopt}; - } - - std::optional error_code; - std::optional serial = std::nullopt; - std::optional elf_hash = std::nullopt; - std::tie(serial, elf_hash) = findElfFile(extracted_iso_path); - - // - XOR all hashes together and hash the result. This makes the ordering of the hashes (aka - // files) irrelevant - xxh::hash64_t combined_hash = 0; - for (const auto& hash : iso_file.hashes) { - combined_hash ^= hash; - } - xxh::hash64_t contents_hash = xxh::xxhash<64>({combined_hash}); - - if (!serial || !elf_hash) { - fmt::print(stderr, "ERROR: Unable to locate a Serial/ELF file!\n"); - if (!error_code.has_value()) { - error_code = std::make_optional(ExtractorErrorCode::VALIDATION_CANT_LOCATE_ELF); - } - // No point in continuing here - return {*error_code, std::nullopt}; - } - - // Find the game in our tracking database - std::optional meta_res = std::nullopt; - if (auto dbEntry = isoDatabase.find(serial.value()); dbEntry == isoDatabase.end()) { - fmt::print(stderr, "ERROR: Serial '{}' not found in the validation database\n", serial.value()); - if (!error_code.has_value()) { - error_code = std::make_optional(ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB); - } - } else { - auto& metaMap = dbEntry->second; - auto meta_entry = metaMap.find(elf_hash.value()); - if (meta_entry == metaMap.end()) { - fmt::print(stderr, - "ERROR: ELF Hash '{}' not found in the validation database, is this a new or " - "modified version of the same game?\n", - elf_hash.value()); - if (!error_code.has_value()) { - error_code = std::make_optional(ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB); - } - } else { - meta_res = std::make_optional(meta_entry->second); - const auto& meta = *meta_res; - // Print out some information - fmt::print("Detected Game Metadata:\n"); - fmt::print("\tDetected - {}\n", meta.canonical_name); - fmt::print("\tRegion - {}\n", meta.region); - fmt::print("\tSerial - {}\n", dbEntry->first); - fmt::print("\tUses Decompiler Config - {}\n", meta.decomp_config); - - // - Number of Files - if (meta.num_files != iso_file.files_extracted) { - fmt::print(stderr, - "ERROR: Extracted an unexpected number of files. Expected '{}', Actual '{}'\n", - meta.num_files, iso_file.files_extracted); - if (!error_code.has_value()) { - error_code = - std::make_optional(ExtractorErrorCode::VALIDATION_INCORRECT_EXTRACTION_COUNT); - } - } - // Check the ISO Hash - if (meta.contents_hash != contents_hash) { - fmt::print(stderr, - "ERROR: Overall ISO content's hash does not match. Expected '{}', Actual '{}'\n", - meta.contents_hash, contents_hash); - } - } - } - - // Finally, return the result - if (error_code.has_value()) { - // Generate the map entry to make things simple, just convienance - if (error_code.value() == ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB) { - fmt::print( - "If this is a new release or version that should be supported, consider adding the " - "following serial entry to the database:\n"); - fmt::print( - "\t'{{\"{}\", {{{{{}U, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, " - "\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}}}}}'\n", - serial.value(), elf_hash.value(), iso_file.files_extracted, contents_hash); - } else if (error_code.value() == ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB) { - fmt::print( - "If this is a new release or version that should be supported, consider adding the " - "following ELF entry to the database under the '{}' serial:\n", - serial.value()); - fmt::print( - "\t'{{{}, {{\"GAME_TITLE\", \"NTSC-U/PAL/NTSC-J\", {}, {}U, " - "\"DECOMP_CONFIF_FILENAME_NO_EXTENSION\"}}}}'\n", - elf_hash.value(), iso_file.files_extracted, contents_hash); - } else { - fmt::print(stderr, - "Validation has failed to match with expected values, see the above errors for " - "specifics. This may be an error in the validation database!\n"); - } - return {*error_code, std::nullopt}; - } - - return {ExtractorErrorCode::SUCCESS, meta_res}; -} - -std::optional determineRelease(const std::filesystem::path& jak1_input_files) { - std::optional serial = std::nullopt; - std::optional elf_hash = std::nullopt; - std::tie(serial, elf_hash) = findElfFile(jak1_input_files); - - if (!serial || !elf_hash) { - return std::nullopt; + return {std::nullopt, ExtractorErrorCode::VALIDATION_CANT_LOCATE_ELF}; } // Find the game in our tracking database auto dbEntry = isoDatabase.find(serial.value()); if (dbEntry == isoDatabase.end()) { - return std::nullopt; - } else { - auto& metaMap = dbEntry->second; - auto meta_entry = metaMap.find(elf_hash.value()); - if (meta_entry == metaMap.end()) { - return std::nullopt; - } else { - return std::make_optional(meta_entry->second); - } + lg::error("Serial '{}' not found in the validation database", serial.value()); + log_potential_new_db_entry(ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB, + serial.value(), elf_hash.value(), expected_num_files, expected_hash); + return {std::nullopt, ExtractorErrorCode::VALIDATION_SERIAL_MISSING_FROM_DB}; } + + auto& metaMap = dbEntry->second; + auto meta_entry = metaMap.find(elf_hash.value()); + if (meta_entry == metaMap.end()) { + lg::error( + "ELF Hash '{}' not found in the validation database, is this a new or " + "modified version of the same game?", + elf_hash.value()); + log_potential_new_db_entry(ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB, serial.value(), + elf_hash.value(), expected_num_files, expected_hash); + return {std::nullopt, ExtractorErrorCode::VALIDATION_ELF_MISSING_FROM_DB}; + } + + auto version_info = meta_entry->second; + // Print out some information + lg::info("Detected Game Metadata:"); + lg::info("\tDetected - {}", version_info.canonical_name); + lg::info("\tRegion - {}", version_info.region); + lg::info("\tSerial - {}", dbEntry->first); + lg::info("\tUses Decompiler Config - {}", version_info.decomp_config); + + // - Number of Files + if (version_info.num_files != expected_num_files) { + lg::error("Extracted an unexpected number of files. Expected '{}', Actual '{}'", + version_info.num_files, expected_num_files); + return {std::nullopt, ExtractorErrorCode::VALIDATION_INCORRECT_EXTRACTION_COUNT}; + } + // Check the ISO Hash + if (version_info.contents_hash != expected_hash) { + lg::error("Overall ISO content's hash does not match. Expected '{}', Actual '{}'", + version_info.contents_hash, expected_hash); + return {std::nullopt, ExtractorErrorCode::VALIDATION_FILE_CONTENTS_UNEXPECTED}; + } + + return { + std::make_optional(version_info), + ExtractorErrorCode::SUCCESS, + }; } -void decompile(std::filesystem::path jak1_input_files) { +void decompile(const fs::path& iso_data_path, const std::string& data_subfolder) { using namespace decompiler; // Determine which config to use from the database - auto meta = determineRelease(jak1_input_files); - std::string decomp_config = "jak1_ntsc_black_label"; - if (meta.has_value()) { - decomp_config = meta.value().decomp_config; - fmt::print("INFO: Automatically detected decompiler config, using - {}\n", decomp_config); - } + const auto version_info = get_version_info_or_default(iso_data_path); Config config = read_config_file((file_util::get_jak_project_dir() / "decompiler" / "config" / - fmt::format("{}.jsonc", decomp_config)) + fmt::format("{}.jsonc", version_info.decomp_config)) .string(), {}); - std::vector dgos, objs; + std::vector dgos, objs; // grab all DGOS we need (level + common) + // TODO - Jak 2 - jak 1 specific code? for (const auto& dgo_name : config.dgo_names) { std::string common_name = "GAME.CGO"; if (dgo_name.length() > 3 && dgo_name.substr(dgo_name.length() - 3) == "DGO") { // ends in DGO, it's a level - dgos.push_back(jak1_input_files / dgo_name); + dgos.push_back(iso_data_path / dgo_name); } else if (dgo_name.length() >= common_name.length() && dgo_name.substr(dgo_name.length() - common_name.length()) == common_name) { // it's COMMON.CGO, we need that too. - dgos.push_back(jak1_input_files / dgo_name); + dgos.push_back(iso_data_path / dgo_name); } } @@ -385,15 +130,15 @@ void decompile(std::filesystem::path jak1_input_files) { for (const auto& obj_name : config.object_file_names) { if (obj_name.length() > 3 && obj_name.substr(obj_name.length() - 3) == "TXT") { // ends in TXT - objs.push_back(jak1_input_files / obj_name); + objs.push_back(iso_data_path / obj_name); } } // set up objects - ObjectFileDB db(dgos, std::filesystem::path(config.obj_file_name_map_file), objs, {}, config); + ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, {}, config); // save object files - auto out_folder = file_util::get_jak_project_dir() / "decompiler_out" / "jak1"; + auto out_folder = file_util::get_jak_project_dir() / "decompiler_out" / data_subfolder; auto raw_obj_folder = out_folder / "raw_obj"; file_util::create_dir_if_needed(raw_obj_folder); db.dump_raw_objects(raw_obj_folder); @@ -422,7 +167,7 @@ void decompile(std::filesystem::path jak1_input_files) { db.process_tpages(tex_db, textures_out)); // texture replacements auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements"; - if (std::filesystem::exists(replacements_path)) { + if (fs::exists(replacements_path)) { tex_db.replace_textures(replacements_path); } @@ -444,27 +189,34 @@ void decompile(std::filesystem::path jak1_input_files) { } } -void compile(std::filesystem::path extracted_iso_path) { +ExtractorErrorCode compile(const fs::path& iso_data_path, const std::string& data_subfolder) { + // Determine which config to use from the database + const auto version_info = get_version_info_or_default(iso_data_path); + Compiler compiler; - compiler.make_system().set_constant("*iso-data*", absolute(extracted_iso_path).string()); + compiler.make_system().set_constant("*iso-data*", absolute(iso_data_path).string()); compiler.make_system().set_constant("*use-iso-data-path*", true); - auto buildinfo_path = (extracted_iso_path / "buildinfo.json").string(); - auto bi = parse_commented_json(file_util::read_text_file(buildinfo_path), buildinfo_path); - auto all_flags = bi.at("flags").get>(); int flags = 0; - for (const auto& n : all_flags) { - if (auto it = sGameIsoFlagNames.find(n); it != sGameIsoFlagNames.end()) { + for (const auto& flag : version_info.flags) { + if (auto it = sGameIsoFlagNames.find(flag); it != sGameIsoFlagNames.end()) { flags |= it->second; } } - compiler.make_system().set_constant("*jak1-full-game*", !(flags & FLAG_JAK1_BLACK_LABEL)); - // TODO - jak2 - BAD! - // TODO - if this directory is failing, very bad (non-existant) error message - compiler.make_system().load_project_file( - (file_util::get_jak_project_dir() / "goal_src" / "jak1" / "game.gp").string()); + if (version_info.game_name == "jak1") { + compiler.make_system().set_constant("*jak1-full-game*", !(flags & FLAG_JAK1_BLACK_LABEL)); + } + + auto project_path = file_util::get_jak_project_dir() / "goal_src" / data_subfolder / "game.gp"; + if (!fs::exists(project_path)) { + return ExtractorErrorCode::COMPILATION_BAD_PROJECT_PATH; + } + + compiler.make_system().load_project_file(project_path.string()); compiler.run_front_end_on_string("(mi)"); + + return ExtractorErrorCode::SUCCESS; } void launch_game() { @@ -472,8 +224,8 @@ void launch_game() { } int main(int argc, char** argv) { - std::filesystem::path data_dir_path; - std::filesystem::path project_path_override; + fs::path input_file_path; + fs::path project_path_override; bool flag_runall = false; bool flag_extract = false; bool flag_fail_on_validation = false; @@ -481,20 +233,30 @@ int main(int argc, char** argv) { bool flag_compile = false; bool flag_play = false; bool flag_folder = false; + std::string game_name = "jak1"; + +#ifdef _WIN32 + auto args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif lg::initialize(); CLI::App app{"OpenGOAL Level Extraction Tool"}; - app.add_option("game-files-path", data_dir_path, + app.add_option("game-files-path", input_file_path, "The path to the folder with the ISO extracted or the ISO itself") - ->check(CLI::ExistingPath) ->required(); app.add_option("--proj-path", project_path_override, - "Explicitly set the location of the 'data/' folder") - ->check(CLI::ExistingPath); + "Explicitly set the location of the 'data/' folder"); + app.add_flag("-g,--game", game_name, "Specify the game name, defaults to 'jak1'"); app.add_flag("-a,--all", flag_runall, "Run all steps, from extraction to playing the game"); app.add_flag("-e,--extract", flag_extract, "Extract the ISO"); - app.add_flag("-v,--validate", flag_fail_on_validation, "Fail on Validation Errors"); + app.add_flag("-v,--validate", flag_fail_on_validation, + "Fail on validation errors during extraction"); app.add_flag("-d,--decompile", flag_decompile, "Decompile the game data"); app.add_flag("-c,--compile", flag_compile, "Compile the game"); app.add_flag("-p,--play", flag_play, "Play the game"); @@ -502,7 +264,7 @@ int main(int argc, char** argv) { app.validate_positionals(); CLI11_PARSE(app, argc, argv); - fmt::print("Working Directory - {}\n", std::filesystem::current_path().string()); + lg::info("Working Directory - {}", fs::current_path().string()); // If no flag is set, we default to running everything if (!flag_extract && !flag_decompile && !flag_compile && !flag_play) { @@ -516,90 +278,111 @@ int main(int argc, char** argv) { flag_play = true; } - // todo: print revision here. + // - SETUP + decompiler::init_opcode_info(); if (!project_path_override.empty()) { - setup_global_decompiler_stuff(std::make_optional(project_path_override)); + if (!fs::exists(project_path_override)) { + lg::error("Error: project path override '{}' does not exist", project_path_override.string()); + return static_cast(ExtractorErrorCode::INVALID_CLI_INPUT); + } + file_util::setup_project_path(project_path_override); } else { - setup_global_decompiler_stuff(std::nullopt); + file_util::setup_project_path({}); } - std::filesystem::path path_to_iso_files = file_util::get_jak_project_dir() / "iso_data" / "_temp"; + fs::path iso_data_path; - // make sure the input looks right - if (!std::filesystem::exists(data_dir_path)) { - fmt::print("Error: input data path {} does not exist\n", data_dir_path.string()); - return 1; + // - INPUT VALIDATION + if (!fs::exists(input_file_path)) { + lg::error("Error: input game file path '{}' does not exist", input_file_path.string()); + return static_cast(ExtractorErrorCode::INVALID_CLI_INPUT); } + if (data_subfolders.count(game_name) == 0) { + lg::error("Error: input game name '{}' is not valid", game_name); + return static_cast(ExtractorErrorCode::INVALID_CLI_INPUT); + } + std::string data_subfolder = data_subfolders[game_name]; if (flag_extract) { - if (data_dir_path != path_to_iso_files) { + // we extract to a temporary location because we don't know what we're extracting yet! + fs::path temp_iso_extract_location = file_util::get_jak_project_dir() / "iso_data" / "_temp"; + if (input_file_path != temp_iso_extract_location) { // in case input is also output, don't just wipe everything (weird) - std::filesystem::remove_all(path_to_iso_files); + fs::remove_all(temp_iso_extract_location); } - std::filesystem::create_directories(path_to_iso_files); + fs::create_directories(temp_iso_extract_location); - int flags = 0; - if (std::filesystem::is_regular_file(data_dir_path)) { - // it's a file, normalize extension case and verify it's an ISO file - std::string ext = data_dir_path.extension().string(); - std::transform(ext.begin(), ext.end(), ext.begin(), - [](unsigned char c) { return std::tolower(c); }); - - if (ext != ".iso") { - fmt::print(stderr, "ERROR: Provided game data path contains a file that isn't a .ISO!"); - return static_cast(ExtractorErrorCode::EXTRACTION_INVALID_ISO_PATH); + if (fs::is_regular_file(input_file_path)) { + // If it's a file, then it better be an iso file + const auto [iso_ok, iso_code] = is_iso_file(input_file_path); + if (!iso_ok) { + return static_cast(iso_code); } - // make sure the .iso is greater than 1GB in size - // to-do: verify game header data as well - if (std::filesystem::file_size(data_dir_path) < 1000000000) { - fmt::print( - stderr, - "ERROR: Provided game data file appears to be too small or corrupted! Size is {}", - std::filesystem::file_size(data_dir_path)); - return static_cast(ExtractorErrorCode::EXTRACTION_ISO_UNEXPECTED_SIZE); + // Extract to the temporary location + const auto iso_file = extract_files(input_file_path, temp_iso_extract_location); + // Get hash and file count + const auto [hash, file_count] = calculate_extraction_hash(iso_file); + // Validate the result to determine the release + const auto [version_info, validate_code] = + validate(temp_iso_extract_location, hash, file_count); + if (validate_code == ExtractorErrorCode::VALIDATION_BAD_EXTRACTION || + (flag_fail_on_validation && validate_code != ExtractorErrorCode::SUCCESS)) { + return static_cast(validate_code); } + // Finalize the folder name now that we know where it should go + if (!version_info) { + lg::error("could not verify release, so not finalizing iso_data, leaving in '_temp'"); + iso_data_path = temp_iso_extract_location; + } else { + // We know the version since we just extracted it, so the user didn't need to provide this + // explicitly + data_subfolder = data_subfolders[version_info->game_name]; + iso_data_path = file_util::get_jak_project_dir() / "iso_data" / data_subfolder; + if (fs::exists(iso_data_path)) { + fs::remove_all(iso_data_path); + } - auto iso_file = extract_files(data_dir_path, path_to_iso_files); - auto validation_res = validate(iso_file, path_to_iso_files); - flags = validation_res.second->flags; - if (validation_res.first == ExtractorErrorCode::VALIDATION_BAD_EXTRACTION) { - // We fail here regardless of the flag - return static_cast(validation_res.first); - } else if (flag_fail_on_validation && validation_res.first != ExtractorErrorCode::SUCCESS) { - return static_cast(validation_res.first); + // std::filesystem doesn't have a rename for dirs... + fs::copy(temp_iso_extract_location, iso_data_path, fs::copy_options::recursive); + fs::remove_all(temp_iso_extract_location); } - } else if (std::filesystem::is_directory(data_dir_path)) { + } else if (fs::is_directory(input_file_path)) { if (!flag_folder) { // if we didn't request a folder explicitly, but we got one, assume something went wrong. - fmt::print("Error: got a folder, but didn't get folder flag\n"); - return static_cast(ExtractorErrorCode::VALIDATION_BAD_ISO_CONTENTS); + lg::error("got a folder, but didn't get folder flag"); + return static_cast(ExtractorErrorCode::INVALID_CLI_INPUT); } - path_to_iso_files = data_dir_path; - if (std::filesystem::exists(path_to_iso_files / "buildinfo.json")) { - std::filesystem::remove(path_to_iso_files / "buildinfo.json"); - } - auto validation_res = validate(path_to_iso_files); - flags = validation_res.second->flags; + iso_data_path = input_file_path; + // Get hash and file count + const auto [hash, file_count] = calculate_extraction_hash(iso_data_path); + // Validate + auto [version_info, code] = validate(iso_data_path, hash, file_count); } // write out a json file with some metadata for the game - nlohmann::json buildinfo_json; - auto flags_json = nlohmann::json::array(); - for (const auto& [n, f] : sGameIsoFlagNames) { - if (flags & f) { - flags_json.push_back(n); - } + if (fs::exists(iso_data_path / "buildinfo.json")) { + fs::remove(iso_data_path / "buildinfo.json"); } - buildinfo_json["flags"] = flags_json; - // something tells me a ps2 game is unlikely to have a json file in root - file_util::write_text_file((path_to_iso_files / "buildinfo.json").string(), - buildinfo_json.dump(2)); + const auto [serial, elf_hash] = findElfFile(iso_data_path); + BuildInfo build_info; + if (serial.has_value()) { + build_info.serial = serial.value(); + } + if (elf_hash.has_value()) { + build_info.elf_hash = elf_hash.value(); + } + const nlohmann::json json_data{build_info}; + file_util::write_text_file((iso_data_path / "buildinfo.json").string(), json_data.dump(2)); + } else { + // If we did not extract, we have no clue what game the user is trying to decompile / compile + // this is why the user has to specify this! + iso_data_path = file_util::get_jak_project_dir() / "iso_data" / data_subfolder; } if (flag_decompile) { try { - decompile(path_to_iso_files); + decompile(iso_data_path, data_subfolder); } catch (std::exception& e) { lg::error("Error during decompile: {}", e.what()); return static_cast(ExtractorErrorCode::DECOMPILATION_GENERIC_ERROR); @@ -607,7 +390,7 @@ int main(int argc, char** argv) { } if (flag_compile) { - compile(path_to_iso_files); + compile(iso_data_path, data_subfolder); } if (flag_play) { diff --git a/decompiler/level_extractor/extract_level.cpp b/decompiler/level_extractor/extract_level.cpp index 05917531c..a17e47ad3 100644 --- a/decompiler/level_extractor/extract_level.cpp +++ b/decompiler/level_extractor/extract_level.cpp @@ -203,7 +203,7 @@ void extract_common(const ObjectFileDB& db, const TextureDB& tex_db, const std::string& dgo_name, bool dump_levels, - const std::filesystem::path& output_folder) { + const fs::path& output_folder) { if (db.obj_files_by_dgo.count(dgo_name) == 0) { lg::warn("Skipping common extract for {} because the DGO was not part of the input", dgo_name); return; @@ -239,7 +239,7 @@ void extract_from_level(const ObjectFileDB& db, const DecompileHacks& hacks, bool dump_level, bool extract_collision, - const std::filesystem::path& output_folder) { + const fs::path& output_folder) { if (db.obj_files_by_dgo.count(dgo_name) == 0) { lg::warn("Skipping extract for {} because the DGO was not part of the input", dgo_name); return; @@ -272,7 +272,7 @@ void extract_all_levels(const ObjectFileDB& db, const DecompileHacks& hacks, bool debug_dump_level, bool extract_collision, - const std::filesystem::path& output_path) { + const fs::path& output_path) { extract_common(db, tex_db, common_name, debug_dump_level, output_path); SimpleThreadGroup threads; threads.run( diff --git a/decompiler/level_extractor/extract_level.h b/decompiler/level_extractor/extract_level.h index b36cb2deb..169aa760d 100644 --- a/decompiler/level_extractor/extract_level.h +++ b/decompiler/level_extractor/extract_level.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include "common/math/Vector.h" @@ -17,5 +16,5 @@ void extract_all_levels(const ObjectFileDB& db, const DecompileHacks& hacks, bool debug_dump_level, bool extract_collision, - const std::filesystem::path& path); + const fs::path& path); } // namespace decompiler diff --git a/decompiler/main.cpp b/decompiler/main.cpp index e790f2f60..642ea0858 100644 --- a/decompiler/main.cpp +++ b/decompiler/main.cpp @@ -10,6 +10,7 @@ #include "common/util/diff.h" #include "common/util/os.h" #include "common/versions.h" +#include #include "ObjectFile/ObjectFileDB.h" #include "decompiler/data/TextureDB.h" @@ -17,6 +18,15 @@ #include "decompiler/level_extractor/extract_level.h" int main(int argc, char** argv) { +#ifdef _WIN32 + auto args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif + Timer decomp_timer; fmt::print("[Mem] Top of main: {} MB\n", get_peak_rss() / (1024 * 1024)); @@ -87,8 +97,8 @@ int main(int argc, char** argv) { } // std::string in_folder = file_util::combine_path(argv[2], config.game_name); - std::filesystem::path in_folder = std::filesystem::path(argv[2]) / config.game_name; - std::filesystem::path out_folder = std::filesystem::path(argv[3]) / config.game_name; + fs::path in_folder = fs::path(argv[2]) / config.game_name; + fs::path out_folder = fs::path(argv[3]) / config.game_name; // Verify the in_folder is correct if (!exists(in_folder)) { @@ -105,7 +115,7 @@ int main(int argc, char** argv) { in_folder.string(), config.expected_elf_name); } - std::vector dgos, objs, strs; + std::vector dgos, objs, strs; for (const auto& dgo_name : config.dgo_names) { dgos.push_back(in_folder / dgo_name); } @@ -129,7 +139,7 @@ int main(int argc, char** argv) { // build file database lg::info("Setting up object file DB..."); - ObjectFileDB db(dgos, std::filesystem::path(config.obj_file_name_map_file), objs, strs, config); + ObjectFileDB db(dgos, fs::path(config.obj_file_name_map_file), objs, strs, config); fmt::print("[Mem] After DB setup: {} MB\n", get_peak_rss() / (1024 * 1024)); @@ -220,7 +230,7 @@ int main(int argc, char** argv) { fmt::print("[Mem] After textures: {} MB\n", get_peak_rss() / (1024 * 1024)); auto replacements_path = file_util::get_jak_project_dir() / "texture_replacements"; - if (std::filesystem::exists(replacements_path)) { + if (fs::exists(replacements_path)) { tex_db.replace_textures(replacements_path); } diff --git a/game/graphics/gfx.cpp b/game/graphics/gfx.cpp index c4eefc1dc..f9427e2fe 100644 --- a/game/graphics/gfx.cpp +++ b/game/graphics/gfx.cpp @@ -6,7 +6,6 @@ #include "gfx.h" #include -#include #include #include "display.h" @@ -57,9 +56,9 @@ GfxSettings g_settings; // TODO serialize void LoadSettings() { const auto filename = file_util::get_file_path({GAME_CONFIG_DIR_NAME, SETTINGS_GFX_FILE_NAME}); - if (std::filesystem::exists(filename)) { + if (fs::exists(filename)) { // this is just wrong LOL - FILE* fp = fopen(filename.c_str(), "rb"); + FILE* fp = file_util::open_file(filename.c_str(), "rb"); lg::info("Found graphics configuration file. Checking version."); u64 version; fread(&version, sizeof(u64), 1, fp); @@ -78,7 +77,7 @@ void LoadSettings() { void SaveSettings() { const auto filename = file_util::get_file_path({GAME_CONFIG_DIR_NAME, SETTINGS_GFX_FILE_NAME}); file_util::create_dir_if_needed(file_util::get_file_path({GAME_CONFIG_DIR_NAME})); - FILE* fp = fopen(filename.c_str(), "wb"); + FILE* fp = file_util::open_file(filename.c_str(), "wb"); fwrite(&g_settings, sizeof(GfxSettings), 1, fp); fclose(fp); lg::info("Saved graphics configuration file."); diff --git a/game/graphics/opengl_renderer/loader/Loader.cpp b/game/graphics/opengl_renderer/loader/Loader.cpp index 8212c2626..e07c967ce 100644 --- a/game/graphics/opengl_renderer/loader/Loader.cpp +++ b/game/graphics/opengl_renderer/loader/Loader.cpp @@ -16,7 +16,7 @@ std::string uppercase_string(const std::string& s) { } } // namespace -Loader::Loader(const std::filesystem::path& base_path) : m_base_path(base_path) { +Loader::Loader(const fs::path& base_path) : m_base_path(base_path) { m_loader_thread = std::thread(&Loader::loader_thread, this); m_loader_stages = make_loader_stages(); } @@ -408,4 +408,4 @@ std::optional Loader::get_merc_model(const char* model_name) { } else { return std::nullopt; } -} \ No newline at end of file +} diff --git a/game/graphics/opengl_renderer/loader/Loader.h b/game/graphics/opengl_renderer/loader/Loader.h index 5dd103a7c..f31d4d5fd 100644 --- a/game/graphics/opengl_renderer/loader/Loader.h +++ b/game/graphics/opengl_renderer/loader/Loader.h @@ -1,11 +1,11 @@ #pragma once #include -#include #include #include #include "common/custom_data/Tfrag3Data.h" +#include "common/util/FileUtil.h" #include "common/util/Timer.h" #include "game/graphics/opengl_renderer/loader/common.h" @@ -16,7 +16,7 @@ class Loader { public: static constexpr float TIE_LOAD_BUDGET = 1.5f; static constexpr float SHARED_TEXTURE_LOAD_BUDGET = 3.f; - Loader(const std::filesystem::path& base_path); + Loader(const fs::path& base_path); ~Loader(); void update(TexturePool& tex_pool); void update_blocking(TexturePool& tex_pool); @@ -52,5 +52,5 @@ class Loader { std::vector m_desired_levels; std::vector> m_loader_stages; - std::filesystem::path m_base_path; + fs::path m_base_path; }; diff --git a/game/kernel/common/kmachine.cpp b/game/kernel/common/kmachine.cpp index 5e916a724..4a13bc359 100644 --- a/game/kernel/common/kmachine.cpp +++ b/game/kernel/common/kmachine.cpp @@ -411,7 +411,7 @@ void mkdir_path(u32 filepath) { u64 filepath_exists(u32 filepath) { auto filepath_str = std::string(Ptr(filepath).c()->data()); - if (std::filesystem::exists(filepath_str)) { + if (fs::exists(filepath_str)) { return s7.offset + true_symbol_offset(g_game_version); } return s7.offset; @@ -470,4 +470,4 @@ void vif_interrupt_callback() { */ u32 offset_of_s7() { return s7.offset; -} \ No newline at end of file +} diff --git a/game/kernel/common/kmemcard.cpp b/game/kernel/common/kmemcard.cpp index 425b7bea9..39d84092f 100644 --- a/game/kernel/common/kmemcard.cpp +++ b/game/kernel/common/kmemcard.cpp @@ -117,8 +117,7 @@ u32 mc_checksum(Ptr data, s32 size) { */ bool file_is_present(int id, int bank = 0) { auto bankname = file_util::get_user_memcard_dir() / filename[4 + id * 2 + bank]; - if (!std::filesystem::exists(bankname) || - std::filesystem::file_size(bankname) < BANK_TOTAL_SIZE) { + if (!fs::exists(bankname) || fs::file_size(bankname) < BANK_TOTAL_SIZE) { // file doesn't exist, or size is bad. we do not want to open files that will crash on read! return false; } @@ -130,7 +129,7 @@ bool file_is_present(int id, int bank = 0) { // file exists. but let's see if it's an empty one. // this prevents the game from reading a bank but classifying it as corrupt data. // which a file full of zeros logically is. - auto fp = fopen(bankname.c_str(), "rb"); + auto fp = file_util::open_file(bankname.c_str(), "rb"); // we can actually just check if the save count is over zero... u32 savecount = 0; @@ -214,7 +213,7 @@ void pc_game_save_synch() { mc_print("open {} for saving", filename[op.param2 * 2 + 4 + p4]); auto save_path = file_util::get_user_memcard_dir() / filename[op.param2 * 2 + 4 + p4]; file_util::create_dir_if_needed_for_file(save_path.string()); - auto fd = fopen(save_path.string().c_str(), "wb"); + auto fd = file_util::open_file(save_path.string().c_str(), "wb"); mc_print("synchronous save file open took {:.2f}ms\n", mc_timer.getMs()); if (fd) { // cb_openedsave // @@ -282,12 +281,12 @@ void pc_game_load_open_file(FILE* fd) { if (fclose(fd) == 0) { // cb_closedload // // added : check if aux bank exists - if (p2 < 1 && std::filesystem::exists(file_util::get_user_memcard_dir() / - filename[op.param2 * 2 + 4 + p2 + 1])) { + if (p2 < 1 && + fs::exists(file_util::get_user_memcard_dir() / filename[op.param2 * 2 + 4 + p2 + 1])) { p2++; mc_print("reading next save bank {}", filename[op.param2 * 2 + 4 + p2]); auto new_bankname = file_util::get_user_memcard_dir() / filename[op.param2 * 2 + 4 + p2]; - auto new_fd = fopen(new_bankname.string().c_str(), "rb"); + auto new_fd = file_util::open_file(new_bankname.string().c_str(), "rb"); pc_game_load_open_file(new_fd); } else { // let's verify the data. @@ -404,7 +403,7 @@ void pc_game_load_synch() { mc_print("opening save file {}", filename[op.param2 * 2 + 4]); auto path = file_util::get_user_memcard_dir() / filename[op.param2 * 2 + 4]; - auto fd = fopen(path.string().c_str(), "rb"); + auto fd = file_util::open_file(path.string().c_str(), "rb"); pc_game_load_open_file(fd); mc_print("synchronous load took {:.2f}ms\n", mc_timer.getMs()); diff --git a/game/kernel/jak1/kmachine.cpp b/game/kernel/jak1/kmachine.cpp index b9e292f79..453f821dd 100644 --- a/game/kernel/jak1/kmachine.cpp +++ b/game/kernel/jak1/kmachine.cpp @@ -8,7 +8,6 @@ #include "kmachine.h" -#include #include #include "common/log/log.h" diff --git a/game/main.cpp b/game/main.cpp index d689fde7a..8a63b6577 100644 --- a/game/main.cpp +++ b/game/main.cpp @@ -11,6 +11,7 @@ #include "common/util/FileUtil.h" #include "common/util/os.h" #include "common/versions.h" +#include #include "game/discord.h" @@ -39,6 +40,15 @@ void setup_logging(bool verbose) { * Entry point for the game. */ int main(int argc, char** argv) { +#ifdef _WIN32 + auto args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif + // Figure out if the CPU has AVX2 to enable higher performance AVX2 versions of functions. setup_cpu_info(); // If the CPU doesn't have AVX, GOAL code won't work and we exit. @@ -50,7 +60,7 @@ int main(int argc, char** argv) { // parse arguments bool verbose = false; bool disable_avx2 = false; - std::optional project_path_override = std::nullopt; + std::optional project_path_override = std::nullopt; for (int i = 1; i < argc; i++) { if (std::string("-v") == argv[i]) { verbose = true; @@ -62,7 +72,7 @@ int main(int argc, char** argv) { } if (std::string("-proj-path") == argv[i] && i + 1 < argc) { - project_path_override = std::make_optional(std::filesystem::path(argv[i + 1])); + project_path_override = std::make_optional(fs::path(argv[i + 1])); } } diff --git a/game/overlord/fake_iso.cpp b/game/overlord/fake_iso.cpp index d3883199c..288724d79 100644 --- a/game/overlord/fake_iso.cpp +++ b/game/overlord/fake_iso.cpp @@ -20,7 +20,6 @@ #include "fake_iso.h" #include -#include #include "isocommon.h" #include "overlord.h" @@ -98,8 +97,8 @@ void fake_iso_init_globals() { int FS_Init(u8* buffer) { (void)buffer; - for (const auto& f : std::filesystem::directory_iterator( - file_util::get_jak_project_dir() / "out" / game_version_names[g_game_version] / "iso")) { + for (const auto& f : fs::directory_iterator(file_util::get_jak_project_dir() / "out" / + game_version_names[g_game_version] / "iso")) { if (f.is_regular_file()) { ASSERT(fake_iso_entry_count < MAX_ISO_FILES); FakeIsoEntry* e = &fake_iso_entries[fake_iso_entry_count]; @@ -175,7 +174,7 @@ static const char* get_file_path(FileRecord* fr) { uint32_t FS_GetLength(FileRecord* fr) { const char* path = get_file_path(fr); file_util::assert_file_exists(path, "fake_iso FS_GetLength"); - FILE* fp = fopen(path, "rb"); + FILE* fp = file_util::open_file(path, "rb"); ASSERT(fp); fseek(fp, 0, SEEK_END); uint32_t len = ftell(fp); @@ -265,7 +264,7 @@ uint32_t FS_BeginRead(LoadStackEntry* fd, void* buffer, int32_t len) { u32 offset_into_file = SECTOR_SIZE * fd->location; const char* path = get_file_path(fd->fr); - FILE* fp = fopen(path, "rb"); + FILE* fp = file_util::open_file(path, "rb"); if (!fp) { lg::error("[OVERLORD] fake iso could not open the file \"{}\"", path); } @@ -354,7 +353,7 @@ uint32_t FS_LoadSoundBank(char* name, void* buffer) { return 0; } - auto fp = fopen(get_file_path(file), "rb"); + auto fp = file_util::open_file(get_file_path(file), "rb"); fread(buffer, offset, 1, fp); fclose(fp); @@ -370,7 +369,7 @@ void LoadMusicTweaks() { MakeISOName(tweakname, "TWEAKVAL.MUS"); auto file = FS_FindIN(tweakname); if (file) { - auto fp = fopen(get_file_path(file), "rb"); + auto fp = file_util::open_file(get_file_path(file), "rb"); fread(&gMusicTweakInfo, sizeof(gMusicTweakInfo), 1, fp); fclose(fp); } else { diff --git a/game/sce/sif_ee.cpp b/game/sce/sif_ee.cpp index 52cdc0440..948eb5f3b 100644 --- a/game/sce/sif_ee.cpp +++ b/game/sce/sif_ee.cpp @@ -105,11 +105,11 @@ s32 sceOpen(const char* filename, s32 flag) { auto name = file_util::get_file_path({filename}); switch (flag) { case SCE_RDONLY: { - fp = fopen(name.c_str(), "rb"); + fp = file_util::open_file(name.c_str(), "rb"); } break; default: { - fp = fopen(name.c_str(), "w"); + fp = file_util::open_file(name.c_str(), "w"); } break; } if (!fp) { diff --git a/game/sce/sif_ee_memcard.cpp b/game/sce/sif_ee_memcard.cpp index 9c5d87916..2f87e42d3 100644 --- a/game/sce/sif_ee_memcard.cpp +++ b/game/sce/sif_ee_memcard.cpp @@ -300,7 +300,7 @@ void flush_memory_card_to_file() { } void read_memory_card_from_file() { - if (std::filesystem::exists(get_memory_card_path())) { + if (fs::exists(get_memory_card_path())) { g_mc_state.data.load_from_file(get_memory_card_path()); } } diff --git a/game/sound/989snd/loader.h b/game/sound/989snd/loader.h index 6fefe9cc2..435d9c1dc 100644 --- a/game/sound/989snd/loader.h +++ b/game/sound/989snd/loader.h @@ -1,7 +1,7 @@ // Copyright: 2021 - 2022, Ziemas // SPDX-License-Identifier: ISC #pragma once -#include + #include #include diff --git a/game/sound/989snd/player.cpp b/game/sound/989snd/player.cpp index b42705653..d35e0f0d7 100644 --- a/game/sound/989snd/player.cpp +++ b/game/sound/989snd/player.cpp @@ -7,6 +7,7 @@ #include #ifdef _WIN32 +#include #include #endif @@ -193,7 +194,7 @@ void player::set_master_volume(u32 group, s32 volume) { } } -u32 player::load_bank(std::filesystem::path& filepath, size_t offset) { +u32 player::load_bank(fs::path& filepath, size_t offset) { std::scoped_lock lock(m_ticklock); fmt::print("Loading bank {}\n", filepath.string()); std::fstream in(filepath, std::fstream::binary | std::fstream::in); diff --git a/game/sound/989snd/player.h b/game/sound/989snd/player.h index e2a6ffcce..491fdc0c2 100644 --- a/game/sound/989snd/player.h +++ b/game/sound/989snd/player.h @@ -1,7 +1,7 @@ // Copyright: 2021 - 2022, Ziemas // SPDX-License-Identifier: ISC #pragma once -#include + #include #include #include @@ -14,6 +14,7 @@ #include "sound_handler.h" #include "common/common_types.h" +#include "common/util/FileUtil.h" #include "../common/synth.h" #include "game/sound/989snd/vagvoice.h" @@ -32,7 +33,7 @@ class player { // player(player&& other) noexcept = default; // player& operator=(player&& other) noexcept = default; - u32 load_bank(std::filesystem::path& path, size_t offset); + u32 load_bank(fs::path& path, size_t offset); u32 play_sound(u32 bank, u32 sound, s32 vol, s32 pan, s32 pm, s32 pb); void set_midi_reg(u32 sound_id, u8 reg, u8 value); diff --git a/game/sound/989snd/sndplay.cpp b/game/sound/989snd/sndplay.cpp index b0d5f2cdf..a39b2d869 100644 --- a/game/sound/989snd/sndplay.cpp +++ b/game/sound/989snd/sndplay.cpp @@ -1,12 +1,10 @@ -#include - #include "player.h" int main(int argc, char* argv[]) { snd::player player; unsigned bankid = 0; - std::filesystem::path file = argv[1]; + fs::path file = argv[1]; if (argc > 2) { bankid = player.load_bank(file, 0); diff --git a/game/sound/sndshim.cpp b/game/sound/sndshim.cpp index e01e0cc68..f3cc9d537 100644 --- a/game/sound/sndshim.cpp +++ b/game/sound/sndshim.cpp @@ -178,7 +178,7 @@ void snd_AutoPitchBend(s32, s32, s32, s32) { s32 snd_BankLoadEx(const char* filename, s32 offset, s32, s32) { // printf("snd_BankLoadEx\n"); if (player) { - std::filesystem::path path = filename; + fs::path path = filename; return player->load_bank(path, offset); } else { return 0; diff --git a/game/system/IOP_Kernel.cpp b/game/system/IOP_Kernel.cpp index cd6dd0572..53b6436f7 100644 --- a/game/system/IOP_Kernel.cpp +++ b/game/system/IOP_Kernel.cpp @@ -3,6 +3,7 @@ #include #include "common/util/Assert.h" +#include #include "game/sce/iop.h" @@ -299,7 +300,7 @@ void IOP_Kernel::rpc_loop(iop::sceSifQueueData* qd) { void IOP_Kernel::read_disc_sectors(u32 sector, u32 sectors, void* buffer) { if (!iso_disc_file) { - iso_disc_file = fopen("./disc.iso", "rb"); + iso_disc_file = file_util::open_file("./disc.iso", "rb"); } ASSERT(iso_disc_file); diff --git a/goalc/build_level/build_level.cpp b/goalc/build_level/build_level.cpp index 0a1ee3eae..d26722672 100644 --- a/goalc/build_level/build_level.cpp +++ b/goalc/build_level/build_level.cpp @@ -16,7 +16,7 @@ void save_pc_data(const std::string& nickname, tfrag3::Level& data, - const std::filesystem::path& fr3_output_dir) { + const fs::path& fr3_output_dir) { Serializer ser; data.serialize(ser); auto compressed = @@ -55,7 +55,7 @@ bool run_build_level(const std::string& input_file, gltf_mesh_extract::extract(mesh_extract_in, mesh_extract_out); // add stuff to the GOAL level structure - file.info = make_file_info_for_level(std::filesystem::path(input_file).filename().string()); + file.info = make_file_info_for_level(fs::path(input_file).filename().string()); // all vis // drawable trees // pat diff --git a/goalc/build_level/build_level.h b/goalc/build_level/build_level.h index 24931528b..32e80a26a 100644 --- a/goalc/build_level/build_level.h +++ b/goalc/build_level/build_level.h @@ -1,10 +1,9 @@ #pragma once -#include #include #include bool run_build_level(const std::string& input_file, const std::string& bsp_output_file, const std::string& output_prefix); -std::vector get_build_level_deps(const std::string& input_file); \ No newline at end of file +std::vector get_build_level_deps(const std::string& input_file); diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index f4573138a..3d196c0c3 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -37,9 +37,8 @@ Compiler::Compiler(const std::string& user_profile, std::unique_ptr compile_object_file("goal-lib", library_code, false); // user profile stuff - if (user_profile != "#f" && - std::filesystem::exists(file_util::get_jak_project_dir() / "goal_src" / "user" / - user_profile / "user.gc")) { + if (user_profile != "#f" && fs::exists(file_util::get_jak_project_dir() / "goal_src" / "user" / + user_profile / "user.gc")) { try { Object user_code = m_goos.reader.read_from_file({"goal_src", "user", user_profile, "user.gc"}); diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index e897c6ae3..d67b46342 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -3,7 +3,6 @@ * Compiler implementation for forms which actually control the compiler. */ -#include #include #include @@ -308,7 +307,7 @@ Val* Compiler::compile_build_dgo(const goos::Object& form, const goos::Object& r desc.entries.push_back(o); } else { // allow data objects to be missing. - if (std::filesystem::exists(file_util::get_file_path( + if (fs::exists(file_util::get_file_path( {"out", m_make.compiler_output_prefix(), "obj", o.file_name}))) { desc.entries.push_back(o); } diff --git a/goalc/main.cpp b/goalc/main.cpp index 8498d3c25..4d85a173a 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -87,6 +87,7 @@ int main(int argc, char** argv) { // the compiler may throw an exception if it fails to load its standard library. try { std::unique_ptr compiler; + // TODO - allow passing in an iso_data override std::mutex compiler_mutex; // if a command is provided on the command line, no REPL just run the compiler on it if (!cmd.empty()) { diff --git a/goalc/make/MakeSystem.cpp b/goalc/make/MakeSystem.cpp index 2b9894f77..efda451b6 100644 --- a/goalc/make/MakeSystem.cpp +++ b/goalc/make/MakeSystem.cpp @@ -1,7 +1,5 @@ #include "MakeSystem.h" -#include - #include "common/goos/ParseHelpers.h" #include "common/util/FileUtil.h" #include "common/util/Timer.h" @@ -179,7 +177,7 @@ goos::Object MakeSystem::handle_basename(const goos::Object& form, const std::shared_ptr& env) { m_goos.eval_args(&args, env); va_check(form, args, {goos::ObjectType::STRING}, {}); - std::filesystem::path input(args.unnamed.at(0).as_string()->data); + fs::path input(args.unnamed.at(0).as_string()->data); return goos::StringObject::make_new(input.filename().u8string()); } @@ -189,7 +187,7 @@ goos::Object MakeSystem::handle_stem(const goos::Object& form, const std::shared_ptr& env) { m_goos.eval_args(&args, env); va_check(form, args, {goos::ObjectType::STRING}, {}); - std::filesystem::path input(args.unnamed.at(0).as_string()->data); + fs::path input(args.unnamed.at(0).as_string()->data); return goos::StringObject::make_new(input.stem().u8string()); } diff --git a/goalc/make/Tool.cpp b/goalc/make/Tool.cpp index 69f1f2a2b..da340e607 100644 --- a/goalc/make/Tool.cpp +++ b/goalc/make/Tool.cpp @@ -2,7 +2,6 @@ #include "Tool.h" #include -#include #include "common/util/FileUtil.h" @@ -14,17 +13,17 @@ bool Tool::needs_run(const ToolInput& task, const PathMap& path_map) { // for this to return false, all outputs need to be newer than all inputs. for (auto& in : task.input) { - auto in_file = std::filesystem::path(file_util::get_file_path({in})); + auto in_file = fs::path(file_util::get_file_path({in})); - if (!std::filesystem::exists(in_file)) { + if (!fs::exists(in_file)) { throw std::runtime_error(fmt::format("Input file {} does not exist.", in)); } - auto newest_input = std::filesystem::last_write_time(in_file); + auto newest_input = fs::last_write_time(in_file); for (auto& dep : task.deps) { - auto dep_path = std::filesystem::path(file_util::get_file_path({dep})); - if (std::filesystem::exists(dep_path)) { - auto dep_time = std::filesystem::last_write_time(dep_path); + auto dep_path = fs::path(file_util::get_file_path({dep})); + if (fs::exists(dep_path)) { + auto dep_time = fs::last_write_time(dep_path); if (dep_time > newest_input) { newest_input = dep_time; } @@ -34,9 +33,9 @@ bool Tool::needs_run(const ToolInput& task, const PathMap& path_map) { } for (auto& dep : get_additional_dependencies(task, path_map)) { - auto dep_path = std::filesystem::path(file_util::get_file_path({dep})); - if (std::filesystem::exists(dep_path)) { - auto dep_time = std::filesystem::last_write_time(dep_path); + auto dep_path = fs::path(file_util::get_file_path({dep})); + if (fs::exists(dep_path)) { + auto dep_time = fs::last_write_time(dep_path); if (dep_time > newest_input) { newest_input = dep_time; } @@ -46,9 +45,9 @@ bool Tool::needs_run(const ToolInput& task, const PathMap& path_map) { } for (auto& out : task.output) { - auto out_path = std::filesystem::path(file_util::get_file_path({out})); - if (std::filesystem::exists(out_path)) { - auto out_time = std::filesystem::last_write_time(out_path); + auto out_path = fs::path(file_util::get_file_path({out})); + if (fs::exists(out_path)) { + auto out_time = fs::last_write_time(out_path); if (out_time < newest_input) { return true; } @@ -87,4 +86,4 @@ std::string PathMap::apply_remaps(const std::string& input) const { } else { return input; } -} \ No newline at end of file +} diff --git a/goalc/make/Tools.cpp b/goalc/make/Tools.cpp index 0f90f26ad..2b0657e33 100644 --- a/goalc/make/Tools.cpp +++ b/goalc/make/Tools.cpp @@ -1,7 +1,5 @@ #include "Tools.h" -#include - #include "common/goos/ParseHelpers.h" #include "common/util/DgoWriter.h" #include "common/util/FileUtil.h" @@ -21,7 +19,7 @@ bool CompilerTool::needs_run(const ToolInput& task, const PathMap& path_map) { throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name())); } - if (!m_compiler->knows_object_file(std::filesystem::path(task.input.at(0)).stem().u8string())) { + if (!m_compiler->knows_object_file(fs::path(task.input.at(0)).stem().u8string())) { return true; } return Tool::needs_run(task, path_map); @@ -113,9 +111,8 @@ bool CopyTool::run(const ToolInput& task, const PathMap& /*path_map*/) { throw std::runtime_error(fmt::format("Invalid amount of inputs to {} tool", name())); } for (auto& out : task.output) { - std::filesystem::copy(std::filesystem::path(file_util::get_file_path({task.input.at(0)})), - std::filesystem::path(file_util::get_file_path({out})), - std::filesystem::copy_options::overwrite_existing); + fs::copy(fs::path(file_util::get_file_path({task.input.at(0)})), + fs::path(file_util::get_file_path({out})), fs::copy_options::overwrite_existing); } return true; } diff --git a/test/goalc/framework/test_runner.cpp b/test/goalc/framework/test_runner.cpp index c05270d5d..af631d954 100644 --- a/test/goalc/framework/test_runner.cpp +++ b/test/goalc/framework/test_runner.cpp @@ -1,7 +1,6 @@ #include "test_runner.h" -#include #include #include "inja.hpp" @@ -118,8 +117,8 @@ void runtime_with_kernel_no_debug_segment() { } void createDirIfAbsent(const std::string& path) { - if (!std::filesystem::is_directory(path) || !std::filesystem::exists(path)) { - std::filesystem::create_directory(path); + if (!fs::is_directory(path) || !fs::exists(path)) { + fs::create_directory(path); } } std::string getTemplateDir(const std::string& category) { diff --git a/test/goalc/test_collections.cpp b/test/goalc/test_collections.cpp index 416303970..9a7b2123f 100644 --- a/test/goalc/test_collections.cpp +++ b/test/goalc/test_collections.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include diff --git a/test/goalc/test_control_statements.cpp b/test/goalc/test_control_statements.cpp index f267f7e55..496a92f23 100644 --- a/test/goalc/test_control_statements.cpp +++ b/test/goalc/test_control_statements.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include diff --git a/test/goalc/test_variables.cpp b/test/goalc/test_variables.cpp index d04cd4bee..7aa1271cf 100644 --- a/test/goalc/test_variables.cpp +++ b/test/goalc/test_variables.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include diff --git a/test/goalc/test_with_game.cpp b/test/goalc/test_with_game.cpp index c2f817445..ed9bcc683 100644 --- a/test/goalc/test_with_game.cpp +++ b/test/goalc/test_with_game.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -380,8 +379,8 @@ TEST_F(WithGameTests, GameCount) { shared_compiler->runner.run_static_test(env, testCategory, "test-game-count.gc", get_test_pass_string("game-count", 4)); // don't leave behind a weird version of the game-count file. - std::filesystem::remove(file_util::get_file_path({"out", "jak1", "iso", "ENGINE.CGO"})); - std::filesystem::remove(file_util::get_file_path({"out", "jak1", "obj", "game-cnt.go"})); + fs::remove(file_util::get_file_path({"out", "jak1", "iso", "ENGINE.CGO"})); + fs::remove(file_util::get_file_path({"out", "jak1", "obj", "game-cnt.go"})); } TEST_F(WithGameTests, BitFieldAccess) { diff --git a/test/offline/offline_test_main.cpp b/test/offline/offline_test_main.cpp index 137267518..df4301250 100644 --- a/test/offline/offline_test_main.cpp +++ b/test/offline/offline_test_main.cpp @@ -9,13 +9,14 @@ #include "common/util/Timer.h" #include "common/util/diff.h" #include "common/util/json_util.h" +#include #include "decompiler/ObjectFile/ObjectFileDB.h" #include "goalc/compiler/Compiler.h" #include "third-party/fmt/format.h" -namespace fs = std::filesystem; +namespace fs = fs; // command line arguments struct OfflineTestArgs { @@ -82,7 +83,7 @@ OfflineTestConfig parse_config() { } struct DecompilerFile { - std::filesystem::path path; + fs::path path; std::string name_in_dgo; std::string unique_name; std::string reference; @@ -264,20 +265,20 @@ Decompiler setup_decompiler(const std::vector& files, // don't try to do this because we can't write the file dc.config->generate_symbol_definition_map = false; - std::vector dgo_paths; + std::vector dgo_paths; if (args.iso_data_path.empty()) { for (auto& x : offline_config.dgos) { dgo_paths.push_back(file_util::get_jak_project_dir() / "iso_data" / "jak1" / x); } } else { for (auto& x : offline_config.dgos) { - dgo_paths.push_back(std::filesystem::path(args.iso_data_path) / x); + dgo_paths.push_back(fs::path(args.iso_data_path) / x); } } - dc.db = std::make_unique( - dgo_paths, dc.config->obj_file_name_map_file, std::vector{}, - std::vector{}, *dc.config); + dc.db = std::make_unique(dgo_paths, dc.config->obj_file_name_map_file, + std::vector{}, + std::vector{}, *dc.config); std::unordered_set db_files; for (auto& files_by_name : dc.db->obj_files_by_name) { @@ -420,6 +421,15 @@ bool compile(Decompiler& dc, } int main(int argc, char* argv[]) { +#ifdef _WIN32 + auto utf8_args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : utf8_args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif + fmt::print("Offline Decompiler Test 2\n"); lg::initialize(); if (!file_util::setup_project_path(std::nullopt)) { diff --git a/test/test_main.cpp b/test/test_main.cpp index ed083c6be..2029e30df 100644 --- a/test/test_main.cpp +++ b/test/test_main.cpp @@ -1,8 +1,9 @@ -#include + #include "common/log/log.h" #include "common/util/FileUtil.h" #include "common/util/os.h" +#include #include "gtest/gtest.h" @@ -18,6 +19,15 @@ // to make it easier to test a subset of tests int main(int argc, char** argv) { +#ifdef _WIN32 + auto args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif + // hopefully get a debug print on github actions setup_cpu_info(); file_util::setup_project_path(std::nullopt); @@ -27,10 +37,10 @@ int main(int argc, char** argv) { // Re-init failed folder std::string failedFolder = file_util::get_file_path({"test/goalc/source_generated/failed/"}); - if (std::filesystem::exists(failedFolder)) { - std::filesystem::remove_all(failedFolder); + if (fs::exists(failedFolder)) { + fs::remove_all(failedFolder); } - std::filesystem::create_directory(failedFolder); + fs::create_directory(failedFolder); return RUN_ALL_TESTS(); } diff --git a/third-party/filesystem.hpp b/third-party/filesystem.hpp new file mode 100644 index 000000000..9540b4405 --- /dev/null +++ b/third-party/filesystem.hpp @@ -0,0 +1,6049 @@ +//--------------------------------------------------------------------------------------- +// +// ghc::filesystem - A C++17-like filesystem implementation for C++11/C++14/C++17/C++20 +// +//--------------------------------------------------------------------------------------- +// +// Copyright (c) 2018, Steffen Schümann +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +//--------------------------------------------------------------------------------------- +// +// To dynamically select std::filesystem where available on most platforms, +// you could use: +// +// #if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +// #if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +// #define GHC_USE_STD_FS +// #include +// namespace fs = std::filesystem; +// #endif +// #endif +// #ifndef GHC_USE_STD_FS +// #include +// namespace fs = ghc::filesystem; +// #endif +// +//--------------------------------------------------------------------------------------- +#ifndef GHC_FILESYSTEM_H +#define GHC_FILESYSTEM_H + +// #define BSD manifest constant only in +// sys/param.h +#ifndef _WIN32 +#include +#endif + +#ifndef GHC_OS_DETECTED +#if defined(__APPLE__) && defined(__MACH__) +#define GHC_OS_MACOS +#elif defined(__linux__) +#define GHC_OS_LINUX +#if defined(__ANDROID__) +#define GHC_OS_ANDROID +#endif +#elif defined(_WIN64) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN64 +#elif defined(_WIN32) +#define GHC_OS_WINDOWS +#define GHC_OS_WIN32 +#elif defined(__CYGWIN__) +#define GHC_OS_CYGWIN +#elif defined(__sun) && defined(__SVR4) +#define GHC_OS_SOLARIS +#elif defined(__svr4__) +#define GHC_OS_SYS5R4 +#elif defined(BSD) +#define GHC_OS_BSD +#elif defined(__EMSCRIPTEN__) +#define GHC_OS_WEB +#include +#elif defined(__QNX__) +#define GHC_OS_QNX +#else +#error "Operating system currently not supported!" +#endif +#define GHC_OS_DETECTED +#if (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#if _MSVC_LANG == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif +#elif (defined(__cplusplus) && __cplusplus >= 201703L) +#if __cplusplus == 201703L +#define GHC_FILESYSTEM_RUNNING_CPP17 +#else +#define GHC_FILESYSTEM_RUNNING_CPP20 +#endif +#endif +#endif + +#if defined(GHC_FILESYSTEM_IMPLEMENTATION) +#define GHC_EXPAND_IMPL +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#ifndef GHC_FS_API +#define GHC_FS_API +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#else +#ifndef GHC_FS_API +#define GHC_FS_API __attribute__((visibility("default"))) +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS __attribute__((visibility("default"))) +#endif +#endif +#elif defined(GHC_FILESYSTEM_FWD) +#define GHC_INLINE +#ifdef GHC_OS_WINDOWS +#ifndef GHC_FS_API +#define GHC_FS_API extern +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#else +#ifndef GHC_FS_API +#define GHC_FS_API extern +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#endif +#else +#define GHC_EXPAND_IMPL +#define GHC_INLINE inline +#ifndef GHC_FS_API +#define GHC_FS_API +#endif +#ifndef GHC_FS_API_CLASS +#define GHC_FS_API_CLASS +#endif +#endif + +#ifdef GHC_EXPAND_IMPL + +#ifdef GHC_OS_WINDOWS +#include +// additional includes +#include +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_ANDROID +#include +#if __ANDROID_API__ < 12 +#include +#endif +#include +#define statvfs statfs +#else +#include +#endif +#ifdef GHC_OS_CYGWIN +#include +#endif +#if !defined(__ANDROID__) || __ANDROID_API__ >= 26 +#include +#endif +#endif +#ifdef GHC_OS_MACOS +#include +#endif + +#if defined(__cpp_impl_three_way_comparison) && defined(__has_include) +#if __has_include() +#define GHC_HAS_THREEWAY_COMP +#include +#endif +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#else // GHC_EXPAND_IMPL + +#if defined(__cpp_impl_three_way_comparison) && defined(__has_include) +#if __has_include() +#define GHC_HAS_THREEWAY_COMP +#include +#endif +#endif +#include +#include +#include +#include +#include +#include +#include +#ifdef GHC_OS_WINDOWS +#include +#endif +#endif // GHC_EXPAND_IMPL + +// After standard library includes. +// Standard library support for std::string_view. +#if defined(__cpp_lib_string_view) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 4000) && (__cplusplus >= 201402) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE >= 7) && (__cplusplus >= 201703) +#define GHC_HAS_STD_STRING_VIEW +#elif defined(_MSC_VER) && (_MSC_VER >= 1910 && _MSVC_LANG >= 201703) +#define GHC_HAS_STD_STRING_VIEW +#endif + +// Standard library support for std::experimental::string_view. +#if defined(_LIBCPP_VERSION) && (_LIBCPP_VERSION >= 3700 && _LIBCPP_VERSION < 7000) && (__cplusplus >= 201402) +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#elif defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)) || (__GNUC__ > 4)) && (__cplusplus >= 201402) +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#elif defined(__GLIBCXX__) && defined(_GLIBCXX_USE_DUAL_ABI) && (__cplusplus >= 201402) +// macro _GLIBCXX_USE_DUAL_ABI is always defined in libstdc++ from gcc-5 and newer +#define GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW +#endif + +#if defined(GHC_HAS_STD_STRING_VIEW) +#include +#elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) +#include +#endif + +#if !defined(GHC_OS_WINDOWS) && !defined(PATH_MAX) +#define PATH_MAX 4096 +#endif + +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Behaviour Switches (see README.md, should match the config in test/filesystem_test.cpp): +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Enforce C++17 API where possible when compiling for C++20, handles the following cases: +// * fs::path::u8string() returns std::string instead of std::u8string +// #define GHC_FILESYSTEM_ENFORCE_CPP17_API +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2682 disables the since then invalid use of the copy option create_symlinks on directories +// configure LWG conformance () +#define LWG_2682_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2395 makes crate_directory/create_directories not emit an error if there is a regular +// file with that name, it is superseded by P1164R1, so only activate if really needed +// #define LWG_2935_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2936 enables new element wise (more expensive) path comparison +// * if this->root_name().native().compare(p.root_name().native()) != 0 return result +// * if this->has_root_directory() and !p.has_root_directory() return -1 +// * if !this->has_root_directory() and p.has_root_directory() return -1 +// * else result of element wise comparison of path iteration where first comparison is != 0 or 0 +// if all comparisons are 0 (on Windows this implementation does case-insensitive root_name() +// comparison) +#define LWG_2936_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// LWG #2937 enforces that fs::equivalent emits an error, if !fs::exists(p1)||!exists(p2) +#define LWG_2937_BEHAVIOUR +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// UTF8-Everywhere is the original behaviour of ghc::filesystem. But since v1.5 the Windows +// version defaults to std::wstring storage backend. Still all std::string will be interpreted +// as UTF-8 encoded. With this define you can enforce the old behavior on Windows, using +// std::string as backend and for fs::path::native() and char for fs::path::c_str(). This +// needs more conversions, so it is (and was before v1.5) slower, bot might help keeping source +// homogeneous in a multi-platform project. +// #define GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Raise errors/exceptions when invalid unicode codepoints or UTF-8 sequences are found, +// instead of replacing them with the unicode replacement character (U+FFFD). +// #define GHC_RAISE_UNICODE_ERRORS +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// Automatic prefix windows path with "\\?\" if they would break the MAX_PATH length. +// instead of replacing them with the unicode replacement character (U+FFFD). +#ifndef GHC_WIN_DISABLE_AUTO_PREFIXES +#define GHC_WIN_AUTO_PREFIX_LONG_PATH +#endif // GHC_WIN_DISABLE_AUTO_PREFIXES +//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// ghc::filesystem version in decimal (major * 10000 + minor * 100 + patch) +#define GHC_FILESYSTEM_VERSION 10512L + +#if !defined(GHC_WITH_EXCEPTIONS) && (defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)) +#define GHC_WITH_EXCEPTIONS +#endif +#if !defined(GHC_WITH_EXCEPTIONS) && defined(GHC_RAISE_UNICODE_ERRORS) +#error "Can't raise unicode errors with exception support disabled" +#endif + +namespace ghc { +namespace filesystem { + +#if defined(GHC_HAS_CUSTOM_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +#elif defined(GHC_HAS_STD_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +using std::basic_string_view; +#elif defined(GHC_HAS_STD_EXPERIMENTAL_STRING_VIEW) +#define GHC_WITH_STRING_VIEW +using std::experimental::basic_string_view; +#endif + +// temporary existing exception type for yet unimplemented parts +class GHC_FS_API_CLASS not_implemented_exception : public std::logic_error +{ +public: + not_implemented_exception() + : std::logic_error("function not implemented yet.") + { + } +}; + +template +class path_helper_base +{ +public: + using value_type = char_type; +#ifdef GHC_OS_WINDOWS + static constexpr value_type preferred_separator = '\\'; +#else + static constexpr value_type preferred_separator = '/'; +#endif +}; + +#if __cplusplus < 201703L +template +constexpr char_type path_helper_base::preferred_separator; +#endif + +#ifdef GHC_OS_WINDOWS +class path; +namespace detail { +bool has_executable_extension(const path& p); +} +#endif + +// [fs.class.path] class path +class GHC_FS_API_CLASS path +#if defined(GHC_OS_WINDOWS) && !defined(GHC_WIN_DISABLE_WSTRING_STORAGE_TYPE) +#define GHC_USE_WCHAR_T +#define GHC_NATIVEWP(p) p.c_str() +#define GHC_PLATFORM_LITERAL(str) L##str + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#else +#define GHC_NATIVEWP(p) p.wstring().c_str() +#define GHC_PLATFORM_LITERAL(str) str + : private path_helper_base +{ +public: + using path_helper_base::value_type; +#endif + using string_type = std::basic_string; + using path_helper_base::preferred_separator; + + // [fs.enum.path.format] enumeration format + /// The path format in which the constructor argument is given. + enum format { + generic_format, ///< The generic format, internally used by + ///< ghc::filesystem::path with slashes + native_format, ///< The format native to the current platform this code + ///< is build for + auto_format, ///< Try to auto-detect the format, fallback to native + }; + + template + struct _is_basic_string : std::false_type + { + }; + template + struct _is_basic_string> : std::true_type + { + }; + template + struct _is_basic_string, std::allocator>> : std::true_type + { + }; +#ifdef GHC_WITH_STRING_VIEW + template + struct _is_basic_string> : std::true_type + { + }; + template + struct _is_basic_string>> : std::true_type + { + }; +#endif + + template + using path_type = typename std::enable_if::value, path>::type; + template +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + using path_from_string = + typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value, + path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; +#else + using path_from_string = + typename std::enable_if<_is_basic_string::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value || + std::is_same::type>::value || std::is_same::type>::value || std::is_same::type>::value, + path>::type; + template + using path_type_EcharT = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, path>::type; +#endif + // [fs.path.construct] constructors and destructor + path() noexcept; + path(const path& p); + path(path&& p) noexcept; + path(string_type&& source, format fmt = auto_format); + template > + path(const Source& source, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, format fmt = auto_format); +#ifdef GHC_WITH_EXCEPTIONS + template > + path(const Source& source, const std::locale& loc, format fmt = auto_format); + template + path(InputIterator first, InputIterator last, const std::locale& loc, format fmt = auto_format); +#endif + ~path(); + + // [fs.path.assign] assignments + path& operator=(const path& p); + path& operator=(path&& p) noexcept; + path& operator=(string_type&& source); + path& assign(string_type&& source); + template + path& operator=(const Source& source); + template + path& assign(const Source& source); + template + path& assign(InputIterator first, InputIterator last); + + // [fs.path.append] appends + path& operator/=(const path& p); + template + path& operator/=(const Source& source); + template + path& append(const Source& source); + template + path& append(InputIterator first, InputIterator last); + + // [fs.path.concat] concatenation + path& operator+=(const path& x); + path& operator+=(const string_type& x); +#ifdef GHC_WITH_STRING_VIEW + path& operator+=(basic_string_view x); +#endif + path& operator+=(const value_type* x); + path& operator+=(value_type x); + template + path_from_string& operator+=(const Source& x); + template + path_type_EcharT& operator+=(EcharT x); + template + path& concat(const Source& x); + template + path& concat(InputIterator first, InputIterator last); + + // [fs.path.modifiers] modifiers + void clear() noexcept; + path& make_preferred(); + path& remove_filename(); + path& replace_filename(const path& replacement); + path& replace_extension(const path& replacement = path()); + void swap(path& rhs) noexcept; + + // [fs.path.native.obs] native format observers + const string_type& native() const noexcept; + const value_type* c_str() const noexcept; + operator string_type() const; + template , class Allocator = std::allocator> + std::basic_string string(const Allocator& a = Allocator()) const; + std::string string() const; + std::wstring wstring() const; +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + std::u8string u8string() const; +#else + std::string u8string() const; +#endif + std::u16string u16string() const; + std::u32string u32string() const; + + // [fs.path.generic.obs] generic format observers + template , class Allocator = std::allocator> + std::basic_string generic_string(const Allocator& a = Allocator()) const; + std::string generic_string() const; + std::wstring generic_wstring() const; +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) + std::u8string generic_u8string() const; +#else + std::string generic_u8string() const; +#endif + std::u16string generic_u16string() const; + std::u32string generic_u32string() const; + + // [fs.path.compare] compare + int compare(const path& p) const noexcept; + int compare(const string_type& s) const; +#ifdef GHC_WITH_STRING_VIEW + int compare(basic_string_view s) const; +#endif + int compare(const value_type* s) const; + + // [fs.path.decompose] decomposition + path root_name() const; + path root_directory() const; + path root_path() const; + path relative_path() const; + path parent_path() const; + path filename() const; + path stem() const; + path extension() const; + + // [fs.path.query] query + bool empty() const noexcept; + bool has_root_name() const; + bool has_root_directory() const; + bool has_root_path() const; + bool has_relative_path() const; + bool has_parent_path() const; + bool has_filename() const; + bool has_stem() const; + bool has_extension() const; + bool is_absolute() const; + bool is_relative() const; + + // [fs.path.gen] generation + path lexically_normal() const; + path lexically_relative(const path& base) const; + path lexically_proximate(const path& base) const; + + // [fs.path.itr] iterators + class iterator; + using const_iterator = iterator; + iterator begin() const; + iterator end() const; + +private: + using impl_value_type = value_type; + using impl_string_type = std::basic_string; + friend class directory_iterator; + void append_name(const value_type* name); + static constexpr impl_value_type generic_separator = '/'; + template + class input_iterator_range + { + public: + typedef InputIterator iterator; + typedef InputIterator const_iterator; + typedef typename InputIterator::difference_type difference_type; + + input_iterator_range(const InputIterator& first, const InputIterator& last) + : _first(first) + , _last(last) + { + } + + InputIterator begin() const { return _first; } + InputIterator end() const { return _last; } + + private: + InputIterator _first; + InputIterator _last; + }; + friend void swap(path& lhs, path& rhs) noexcept; + friend size_t hash_value(const path& p) noexcept; + friend path canonical(const path& p, std::error_code& ec); + friend bool create_directories(const path& p, std::error_code& ec) noexcept; + string_type::size_type root_name_length() const noexcept; + void postprocess_path_with_format(format fmt); + void check_long_path(); + impl_string_type _path; +#ifdef GHC_OS_WINDOWS + void handle_prefixes(); + friend bool detail::has_executable_extension(const path& p); +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + string_type::size_type _prefixLength{0}; +#else // GHC_WIN_AUTO_PREFIX_LONG_PATH + static const string_type::size_type _prefixLength{0}; +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +#else + static const string_type::size_type _prefixLength{0}; +#endif +}; + +// [fs.path.nonmember] path non-member functions +GHC_FS_API void swap(path& lhs, path& rhs) noexcept; +GHC_FS_API size_t hash_value(const path& p) noexcept; +#ifdef GHC_HAS_THREEWAY_COMP +GHC_FS_API std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept; +#endif +GHC_FS_API bool operator==(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator!=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator<=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>(const path& lhs, const path& rhs) noexcept; +GHC_FS_API bool operator>=(const path& lhs, const path& rhs) noexcept; +GHC_FS_API path operator/(const path& lhs, const path& rhs); + +// [fs.path.io] path inserter and extractor +template +std::basic_ostream& operator<<(std::basic_ostream& os, const path& p); +template +std::basic_istream& operator>>(std::basic_istream& is, path& p); + +// [pfs.path.factory] path factory functions +template > +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] +#endif +path u8path(const Source& source); +template +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +[[deprecated("use ghc::filesystem::path::path() with std::u8string instead")]] +#endif +path u8path(InputIterator first, InputIterator last); + +// [fs.class.filesystem_error] class filesystem_error +class GHC_FS_API_CLASS filesystem_error : public std::system_error +{ +public: + filesystem_error(const std::string& what_arg, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec); + filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec); + const path& path1() const noexcept; + const path& path2() const noexcept; + const char* what() const noexcept override; + +private: + std::string _what_arg; + std::error_code _ec; + path _p1, _p2; +}; + +class GHC_FS_API_CLASS path::iterator +{ +public: + using value_type = const path; + using difference_type = std::ptrdiff_t; + using pointer = const path*; + using reference = const path&; + using iterator_category = std::bidirectional_iterator_tag; + + iterator(); + iterator(const path& p, const impl_string_type::const_iterator& pos); + iterator& operator++(); + iterator operator++(int); + iterator& operator--(); + iterator operator--(int); + bool operator==(const iterator& other) const; + bool operator!=(const iterator& other) const; + reference operator*() const; + pointer operator->() const; + +private: + friend class path; + impl_string_type::const_iterator increment(const impl_string_type::const_iterator& pos) const; + impl_string_type::const_iterator decrement(const impl_string_type::const_iterator& pos) const; + void updateCurrent(); + impl_string_type::const_iterator _first; + impl_string_type::const_iterator _last; + impl_string_type::const_iterator _prefix; + impl_string_type::const_iterator _root; + impl_string_type::const_iterator _iter; + path _current; +}; + +struct space_info +{ + uintmax_t capacity; + uintmax_t free; + uintmax_t available; +}; + +// [fs.enum] enumerations +// [fs.enum.file_type] +enum class file_type { + none, + not_found, + regular, + directory, + symlink, + block, + character, + fifo, + socket, + unknown, +}; + +// [fs.enum.perms] +enum class perms : uint16_t { + none = 0, + + owner_read = 0400, + owner_write = 0200, + owner_exec = 0100, + owner_all = 0700, + + group_read = 040, + group_write = 020, + group_exec = 010, + group_all = 070, + + others_read = 04, + others_write = 02, + others_exec = 01, + others_all = 07, + + all = 0777, + set_uid = 04000, + set_gid = 02000, + sticky_bit = 01000, + + mask = 07777, + unknown = 0xffff +}; + +// [fs.enum.perm.opts] +enum class perm_options : uint16_t { + replace = 3, + add = 1, + remove = 2, + nofollow = 4, +}; + +// [fs.enum.copy.opts] +enum class copy_options : uint16_t { + none = 0, + + skip_existing = 1, + overwrite_existing = 2, + update_existing = 4, + + recursive = 8, + + copy_symlinks = 0x10, + skip_symlinks = 0x20, + + directories_only = 0x40, + create_symlinks = 0x80, +#ifndef GHC_OS_WEB + create_hard_links = 0x100 +#endif +}; + +// [fs.enum.dir.opts] +enum class directory_options : uint16_t { + none = 0, + follow_directory_symlink = 1, + skip_permission_denied = 2, +}; + +// [fs.class.file_status] class file_status +class GHC_FS_API_CLASS file_status +{ +public: + // [fs.file_status.cons] constructors and destructor + file_status() noexcept; + explicit file_status(file_type ft, perms prms = perms::unknown) noexcept; + file_status(const file_status&) noexcept; + file_status(file_status&&) noexcept; + ~file_status(); + // assignments: + file_status& operator=(const file_status&) noexcept; + file_status& operator=(file_status&&) noexcept; + // [fs.file_status.mods] modifiers + void type(file_type ft) noexcept; + void permissions(perms prms) noexcept; + // [fs.file_status.obs] observers + file_type type() const noexcept; + perms permissions() const noexcept; + friend bool operator==(const file_status& lhs, const file_status& rhs) noexcept { return lhs.type() == rhs.type() && lhs.permissions() == rhs.permissions(); } + +private: + file_type _type; + perms _perms; +}; + +using file_time_type = std::chrono::time_point; + +// [fs.class.directory_entry] Class directory_entry +class GHC_FS_API_CLASS directory_entry +{ +public: + // [fs.dir.entry.cons] constructors and destructor + directory_entry() noexcept = default; + directory_entry(const directory_entry&) = default; + directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_entry(const path& p); +#endif + directory_entry(const path& p, std::error_code& ec); + ~directory_entry(); + + // assignments: + directory_entry& operator=(const directory_entry&) = default; + directory_entry& operator=(directory_entry&&) noexcept = default; + + // [fs.dir.entry.mods] modifiers +#ifdef GHC_WITH_EXCEPTIONS + void assign(const path& p); + void replace_filename(const path& p); + void refresh(); +#endif + void assign(const path& p, std::error_code& ec); + void replace_filename(const path& p, std::error_code& ec); + void refresh(std::error_code& ec) noexcept; + + // [fs.dir.entry.obs] observers + const filesystem::path& path() const noexcept; + operator const filesystem::path&() const noexcept; +#ifdef GHC_WITH_EXCEPTIONS + bool exists() const; + bool is_block_file() const; + bool is_character_file() const; + bool is_directory() const; + bool is_fifo() const; + bool is_other() const; + bool is_regular_file() const; + bool is_socket() const; + bool is_symlink() const; + uintmax_t file_size() const; + file_time_type last_write_time() const; + file_status status() const; + file_status symlink_status() const; +#endif + bool exists(std::error_code& ec) const noexcept; + bool is_block_file(std::error_code& ec) const noexcept; + bool is_character_file(std::error_code& ec) const noexcept; + bool is_directory(std::error_code& ec) const noexcept; + bool is_fifo(std::error_code& ec) const noexcept; + bool is_other(std::error_code& ec) const noexcept; + bool is_regular_file(std::error_code& ec) const noexcept; + bool is_socket(std::error_code& ec) const noexcept; + bool is_symlink(std::error_code& ec) const noexcept; + uintmax_t file_size(std::error_code& ec) const noexcept; + file_time_type last_write_time(std::error_code& ec) const noexcept; + file_status status(std::error_code& ec) const noexcept; + file_status symlink_status(std::error_code& ec) const noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS + uintmax_t hard_link_count() const; +#endif + uintmax_t hard_link_count(std::error_code& ec) const noexcept; +#endif + +#ifdef GHC_HAS_THREEWAY_COMP + std::strong_ordering operator<=>(const directory_entry& rhs) const noexcept; +#endif + bool operator<(const directory_entry& rhs) const noexcept; + bool operator==(const directory_entry& rhs) const noexcept; + bool operator!=(const directory_entry& rhs) const noexcept; + bool operator<=(const directory_entry& rhs) const noexcept; + bool operator>(const directory_entry& rhs) const noexcept; + bool operator>=(const directory_entry& rhs) const noexcept; + +private: + friend class directory_iterator; +#ifdef GHC_WITH_EXCEPTIONS + file_type status_file_type() const; +#endif + file_type status_file_type(std::error_code& ec) const noexcept; + filesystem::path _path; + file_status _status; + file_status _symlink_status; + uintmax_t _file_size = static_cast(-1); +#ifndef GHC_OS_WINDOWS + uintmax_t _hard_link_count = static_cast(-1); +#endif + time_t _last_write_time = 0; +}; + +// [fs.class.directory.iterator] Class directory_iterator +class GHC_FS_API_CLASS directory_iterator +{ +public: + class GHC_FS_API_CLASS proxy + { + public: + const directory_entry& operator*() const& noexcept { return _dir_entry; } + directory_entry operator*() && noexcept { return std::move(_dir_entry); } + + private: + explicit proxy(const directory_entry& dir_entry) + : _dir_entry(dir_entry) + { + } + friend class directory_iterator; + friend class recursive_directory_iterator; + directory_entry _dir_entry; + }; + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // [fs.dir.itr.members] member functions + directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit directory_iterator(const path& p); + directory_iterator(const path& p, directory_options options); +#endif + directory_iterator(const path& p, std::error_code& ec) noexcept; + directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + directory_iterator(const directory_iterator& rhs); + directory_iterator(directory_iterator&& rhs) noexcept; + ~directory_iterator(); + directory_iterator& operator=(const directory_iterator& rhs); + directory_iterator& operator=(directory_iterator&& rhs) noexcept; + const directory_entry& operator*() const; + const directory_entry* operator->() const; +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator& operator++(); +#endif + directory_iterator& increment(std::error_code& ec) noexcept; + + // other members as required by [input.iterators] +#ifdef GHC_WITH_EXCEPTIONS + proxy operator++(int) + { + proxy p{**this}; + ++*this; + return p; + } +#endif + bool operator==(const directory_iterator& rhs) const; + bool operator!=(const directory_iterator& rhs) const; + +private: + friend class recursive_directory_iterator; + class impl; + std::shared_ptr _impl; +}; + +// [fs.dir.itr.nonmembers] directory_iterator non-member functions +GHC_FS_API directory_iterator begin(directory_iterator iter) noexcept; +GHC_FS_API directory_iterator end(const directory_iterator&) noexcept; + +// [fs.class.re.dir.itr] class recursive_directory_iterator +class GHC_FS_API_CLASS recursive_directory_iterator +{ +public: + using iterator_category = std::input_iterator_tag; + using value_type = directory_entry; + using difference_type = std::ptrdiff_t; + using pointer = const directory_entry*; + using reference = const directory_entry&; + + // [fs.rec.dir.itr.members] constructors and destructor + recursive_directory_iterator() noexcept; +#ifdef GHC_WITH_EXCEPTIONS + explicit recursive_directory_iterator(const path& p); + recursive_directory_iterator(const path& p, directory_options options); +#endif + recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept; + recursive_directory_iterator(const path& p, std::error_code& ec) noexcept; + recursive_directory_iterator(const recursive_directory_iterator& rhs); + recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept; + ~recursive_directory_iterator(); + + // [fs.rec.dir.itr.members] observers + directory_options options() const; + int depth() const; + bool recursion_pending() const; + + const directory_entry& operator*() const; + const directory_entry* operator->() const; + + // [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& + recursive_directory_iterator& operator=(const recursive_directory_iterator& rhs); + recursive_directory_iterator& operator=(recursive_directory_iterator&& rhs) noexcept; +#ifdef GHC_WITH_EXCEPTIONS + recursive_directory_iterator& operator++(); +#endif + recursive_directory_iterator& increment(std::error_code& ec) noexcept; + +#ifdef GHC_WITH_EXCEPTIONS + void pop(); +#endif + void pop(std::error_code& ec); + void disable_recursion_pending(); + + // other members as required by [input.iterators] +#ifdef GHC_WITH_EXCEPTIONS + directory_iterator::proxy operator++(int) + { + directory_iterator::proxy proxy{**this}; + ++*this; + return proxy; + } +#endif + bool operator==(const recursive_directory_iterator& rhs) const; + bool operator!=(const recursive_directory_iterator& rhs) const; + +private: + struct recursive_directory_iterator_impl + { + directory_options _options; + bool _recursion_pending; + std::stack _dir_iter_stack; + recursive_directory_iterator_impl(directory_options options, bool recursion_pending) + : _options(options) + , _recursion_pending(recursion_pending) + { + } + }; + std::shared_ptr _impl; +}; + +// [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions +GHC_FS_API recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept; +GHC_FS_API recursive_directory_iterator end(const recursive_directory_iterator&) noexcept; + +// [fs.op.funcs] filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API path absolute(const path& p); +GHC_FS_API path canonical(const path& p); +GHC_FS_API void copy(const path& from, const path& to); +GHC_FS_API void copy(const path& from, const path& to, copy_options options); +GHC_FS_API bool copy_file(const path& from, const path& to); +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option); +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink); +GHC_FS_API bool create_directories(const path& p); +GHC_FS_API bool create_directory(const path& p); +GHC_FS_API bool create_directory(const path& p, const path& attributes); +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink); +GHC_FS_API void create_symlink(const path& to, const path& new_symlink); +GHC_FS_API path current_path(); +GHC_FS_API void current_path(const path& p); +GHC_FS_API bool exists(const path& p); +GHC_FS_API bool equivalent(const path& p1, const path& p2); +GHC_FS_API uintmax_t file_size(const path& p); +GHC_FS_API bool is_block_file(const path& p); +GHC_FS_API bool is_character_file(const path& p); +GHC_FS_API bool is_directory(const path& p); +GHC_FS_API bool is_empty(const path& p); +GHC_FS_API bool is_fifo(const path& p); +GHC_FS_API bool is_other(const path& p); +GHC_FS_API bool is_regular_file(const path& p); +GHC_FS_API bool is_socket(const path& p); +GHC_FS_API bool is_symlink(const path& p); +GHC_FS_API file_time_type last_write_time(const path& p); +GHC_FS_API void last_write_time(const path& p, file_time_type new_time); +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts = perm_options::replace); +GHC_FS_API path proximate(const path& p, const path& base = current_path()); +GHC_FS_API path read_symlink(const path& p); +GHC_FS_API path relative(const path& p, const path& base = current_path()); +GHC_FS_API bool remove(const path& p); +GHC_FS_API uintmax_t remove_all(const path& p); +GHC_FS_API void rename(const path& from, const path& to); +GHC_FS_API void resize_file(const path& p, uintmax_t size); +GHC_FS_API space_info space(const path& p); +GHC_FS_API file_status status(const path& p); +GHC_FS_API file_status symlink_status(const path& p); +GHC_FS_API path temp_directory_path(); +GHC_FS_API path weakly_canonical(const path& p); +#endif +GHC_FS_API path absolute(const path& p, std::error_code& ec); +GHC_FS_API path canonical(const path& p, std::error_code& ec); +GHC_FS_API void copy(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept; +GHC_FS_API bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API bool copy_file(const path& from, const path& to, copy_options option, std::error_code& ec) noexcept; +GHC_FS_API void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API bool create_directories(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool create_directory(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept; +GHC_FS_API void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept; +GHC_FS_API path current_path(std::error_code& ec); +GHC_FS_API void current_path(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool exists(file_status s) noexcept; +GHC_FS_API bool exists(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t file_size(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_block_file(file_status s) noexcept; +GHC_FS_API bool is_block_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_character_file(file_status s) noexcept; +GHC_FS_API bool is_character_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_directory(file_status s) noexcept; +GHC_FS_API bool is_directory(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_empty(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_fifo(file_status s) noexcept; +GHC_FS_API bool is_fifo(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_other(file_status s) noexcept; +GHC_FS_API bool is_other(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_regular_file(file_status s) noexcept; +GHC_FS_API bool is_regular_file(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_socket(file_status s) noexcept; +GHC_FS_API bool is_socket(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool is_symlink(file_status s) noexcept; +GHC_FS_API bool is_symlink(const path& p, std::error_code& ec) noexcept; +GHC_FS_API file_time_type last_write_time(const path& p, std::error_code& ec) noexcept; +GHC_FS_API void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, std::error_code& ec) noexcept; +GHC_FS_API void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept; +GHC_FS_API path proximate(const path& p, std::error_code& ec); +GHC_FS_API path proximate(const path& p, const path& base, std::error_code& ec); +GHC_FS_API path read_symlink(const path& p, std::error_code& ec); +GHC_FS_API path relative(const path& p, std::error_code& ec); +GHC_FS_API path relative(const path& p, const path& base, std::error_code& ec); +GHC_FS_API bool remove(const path& p, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t remove_all(const path& p, std::error_code& ec) noexcept; +GHC_FS_API void rename(const path& from, const path& to, std::error_code& ec) noexcept; +GHC_FS_API void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept; +GHC_FS_API space_info space(const path& p, std::error_code& ec) noexcept; +GHC_FS_API file_status status(const path& p, std::error_code& ec) noexcept; +GHC_FS_API bool status_known(file_status s) noexcept; +GHC_FS_API file_status symlink_status(const path& p, std::error_code& ec) noexcept; +GHC_FS_API path temp_directory_path(std::error_code& ec) noexcept; +GHC_FS_API path weakly_canonical(const path& p, std::error_code& ec) noexcept; + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link); +GHC_FS_API uintmax_t hard_link_count(const path& p); +#endif +GHC_FS_API void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept; +GHC_FS_API uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept; +#endif + +// Non-C++17 add-on std::fstream wrappers with path +template > +class basic_filebuf : public std::basic_filebuf +{ +public: + basic_filebuf() {} + ~basic_filebuf() override {} + basic_filebuf(const basic_filebuf&) = delete; + const basic_filebuf& operator=(const basic_filebuf&) = delete; + basic_filebuf* open(const path& p, std::ios_base::openmode mode) + { +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + return std::basic_filebuf::open(p.wstring().c_str(), mode) ? this : 0; +#else + return std::basic_filebuf::open(p.string().c_str(), mode) ? this : 0; +#endif + } +}; + +template > +class basic_ifstream : public std::basic_ifstream +{ +public: + basic_ifstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ifstream(const path& p, std::ios_base::openmode mode = std::ios_base::in) + : std::basic_ifstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in) { std::basic_ifstream::open(p.string().c_str(), mode); } +#endif + basic_ifstream(const basic_ifstream&) = delete; + const basic_ifstream& operator=(const basic_ifstream&) = delete; + ~basic_ifstream() override {} +}; + +template > +class basic_ofstream : public std::basic_ofstream +{ +public: + basic_ofstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_ofstream(const path& p, std::ios_base::openmode mode = std::ios_base::out) + : std::basic_ofstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::out) { std::basic_ofstream::open(p.string().c_str(), mode); } +#endif + basic_ofstream(const basic_ofstream&) = delete; + const basic_ofstream& operator=(const basic_ofstream&) = delete; + ~basic_ofstream() override {} +}; + +template > +class basic_fstream : public std::basic_fstream +{ +public: + basic_fstream() {} +#if defined(GHC_OS_WINDOWS) && !defined(__GLIBCXX__) + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.wstring().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.wstring().c_str(), mode); } +#else + explicit basic_fstream(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) + : std::basic_fstream(p.string().c_str(), mode) + { + } + void open(const path& p, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { std::basic_fstream::open(p.string().c_str(), mode); } +#endif + basic_fstream(const basic_fstream&) = delete; + const basic_fstream& operator=(const basic_fstream&) = delete; + ~basic_fstream() override {} +}; + +typedef basic_filebuf filebuf; +typedef basic_filebuf wfilebuf; +typedef basic_ifstream ifstream; +typedef basic_ifstream wifstream; +typedef basic_ofstream ofstream; +typedef basic_ofstream wofstream; +typedef basic_fstream fstream; +typedef basic_fstream wfstream; + +class GHC_FS_API_CLASS u8arguments +{ +public: + u8arguments(int& argc, char**& argv); + ~u8arguments() + { + _refargc = _argc; + _refargv = _argv; + } + + bool valid() const { return _isvalid; } + +private: + int _argc; + char** _argv; + int& _refargc; + char**& _refargv; + bool _isvalid; +#ifdef GHC_OS_WINDOWS + std::vector _args; + std::vector _argp; +#endif +}; + +//------------------------------------------------------------------------------------------------- +// Implementation +//------------------------------------------------------------------------------------------------- + +namespace detail { +enum utf8_states_t { S_STRT = 0, S_RJCT = 8 }; +GHC_FS_API void appendUTF8(std::string& str, uint32_t unicode); +GHC_FS_API bool is_surrogate(uint32_t c); +GHC_FS_API bool is_high_surrogate(uint32_t c); +GHC_FS_API bool is_low_surrogate(uint32_t c); +GHC_FS_API unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint); +enum class portable_error { + none = 0, + exists, + not_found, + not_supported, + not_implemented, + invalid_argument, + is_a_directory, +}; +GHC_FS_API std::error_code make_error_code(portable_error err); +#ifdef GHC_OS_WINDOWS +GHC_FS_API std::error_code make_system_error(uint32_t err = 0); +#else +GHC_FS_API std::error_code make_system_error(int err = 0); + +template +struct has_d_type : std::false_type{}; + +template +struct has_d_type : std::true_type {}; + +template +GHC_INLINE file_type file_type_from_dirent_impl(const T&, std::false_type) +{ + return file_type::none; +} + +template +GHC_INLINE file_type file_type_from_dirent_impl(const T& t, std::true_type) +{ + switch (t.d_type) { +#ifdef DT_BLK + case DT_BLK: + return file_type::block; +#endif +#ifdef DT_CHR + case DT_CHR: + return file_type::character; +#endif +#ifdef DT_DIR + case DT_DIR: + return file_type::directory; +#endif +#ifdef DT_FIFO + case DT_FIFO: + return file_type::fifo; +#endif +#ifdef DT_LNK + case DT_LNK: + return file_type::symlink; +#endif +#ifdef DT_REG + case DT_REG: + return file_type::regular; +#endif +#ifdef DT_SOCK + case DT_SOCK: + return file_type::socket; +#endif +#ifdef DT_UNKNOWN + case DT_UNKNOWN: + return file_type::none; +#endif + default: + return file_type::unknown; + } +} + +template +GHC_INLINE file_type file_type_from_dirent(const T& t) +{ + return file_type_from_dirent_impl(t, has_d_type{}); +} +#endif +} // namespace detail + +namespace detail { + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::error_code make_error_code(portable_error err) +{ +#ifdef GHC_OS_WINDOWS + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(ERROR_ALREADY_EXISTS, std::system_category()); + case portable_error::not_found: + return std::error_code(ERROR_PATH_NOT_FOUND, std::system_category()); + case portable_error::not_supported: + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ERROR_CALL_NOT_IMPLEMENTED, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(ERROR_INVALID_PARAMETER, std::system_category()); + case portable_error::is_a_directory: +#ifdef ERROR_DIRECTORY_NOT_SUPPORTED + return std::error_code(ERROR_DIRECTORY_NOT_SUPPORTED, std::system_category()); +#else + return std::error_code(ERROR_NOT_SUPPORTED, std::system_category()); +#endif + } +#else + switch (err) { + case portable_error::none: + return std::error_code(); + case portable_error::exists: + return std::error_code(EEXIST, std::system_category()); + case portable_error::not_found: + return std::error_code(ENOENT, std::system_category()); + case portable_error::not_supported: + return std::error_code(ENOTSUP, std::system_category()); + case portable_error::not_implemented: + return std::error_code(ENOSYS, std::system_category()); + case portable_error::invalid_argument: + return std::error_code(EINVAL, std::system_category()); + case portable_error::is_a_directory: + return std::error_code(EISDIR, std::system_category()); + } +#endif + return std::error_code(); +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE std::error_code make_system_error(uint32_t err) +{ + return std::error_code(err ? static_cast(err) : static_cast(::GetLastError()), std::system_category()); +} +#else +GHC_INLINE std::error_code make_system_error(int err) +{ + return std::error_code(err ? err : errno, std::system_category()); +} +#endif + +#endif // GHC_EXPAND_IMPL + +template +using EnableBitmask = typename std::enable_if::value || std::is_same::value || std::is_same::value || std::is_same::value, Enum>::type; +} // namespace detail + +template +constexpr detail::EnableBitmask operator&(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) & static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator|(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) | static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator^(Enum X, Enum Y) +{ + using underlying = typename std::underlying_type::type; + return static_cast(static_cast(X) ^ static_cast(Y)); +} + +template +constexpr detail::EnableBitmask operator~(Enum X) +{ + using underlying = typename std::underlying_type::type; + return static_cast(~static_cast(X)); +} + +template +detail::EnableBitmask& operator&=(Enum& X, Enum Y) +{ + X = X & Y; + return X; +} + +template +detail::EnableBitmask& operator|=(Enum& X, Enum Y) +{ + X = X | Y; + return X; +} + +template +detail::EnableBitmask& operator^=(Enum& X, Enum Y) +{ + X = X ^ Y; + return X; +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool in_range(uint32_t c, uint32_t lo, uint32_t hi) +{ + return (static_cast(c - lo) < (hi - lo + 1)); +} + +GHC_INLINE bool is_surrogate(uint32_t c) +{ + return in_range(c, 0xd800, 0xdfff); +} + +GHC_INLINE bool is_high_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xd800; +} + +GHC_INLINE bool is_low_surrogate(uint32_t c) +{ + return (c & 0xfffffc00) == 0xdc00; +} + +GHC_INLINE void appendUTF8(std::string& str, uint32_t unicode) +{ + if (unicode <= 0x7f) { + str.push_back(static_cast(unicode)); + } + else if (unicode >= 0x80 && unicode <= 0x7ff) { + str.push_back(static_cast((unicode >> 6) + 192)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if ((unicode >= 0x800 && unicode <= 0xd7ff) || (unicode >= 0xe000 && unicode <= 0xffff)) { + str.push_back(static_cast((unicode >> 12) + 224)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else if (unicode >= 0x10000 && unicode <= 0x10ffff) { + str.push_back(static_cast((unicode >> 18) + 240)); + str.push_back(static_cast(((unicode & 0x3ffff) >> 12) + 128)); + str.push_back(static_cast(((unicode & 0xfff) >> 6) + 128)); + str.push_back(static_cast((unicode & 0x3f) + 128)); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", str, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(str, 0xfffd); +#endif + } +} + +// Thanks to Bjoern Hoehrmann (https://bjoern.hoehrmann.de/utf-8/decoder/dfa/) +// and Taylor R Campbell for the ideas to this DFA approach of UTF-8 decoding; +// Generating debugging and shrinking my own DFA from scratch was a day of fun! +GHC_INLINE unsigned consumeUtf8Fragment(const unsigned state, const uint8_t fragment, uint32_t& codepoint) +{ + static const uint32_t utf8_state_info[] = { + // encoded states + 0x11111111u, 0x11111111u, 0x77777777u, 0x77777777u, 0x88888888u, 0x88888888u, 0x88888888u, 0x88888888u, 0x22222299u, 0x22222222u, 0x22222222u, 0x22222222u, 0x3333333au, 0x33433333u, 0x9995666bu, 0x99999999u, + 0x88888880u, 0x22818108u, 0x88888881u, 0x88888882u, 0x88888884u, 0x88888887u, 0x88888886u, 0x82218108u, 0x82281108u, 0x88888888u, 0x88888883u, 0x88888885u, 0u, 0u, 0u, 0u, + }; + uint8_t category = fragment < 128 ? 0 : (utf8_state_info[(fragment >> 3) & 0xf] >> ((fragment & 7) << 2)) & 0xf; + codepoint = (state ? (codepoint << 6) | (fragment & 0x3fu) : (0xffu >> category) & fragment); + return state == S_RJCT ? static_cast(S_RJCT) : static_cast((utf8_state_info[category + 16] >> (state << 2)) & 0xf); +} + +GHC_INLINE bool validUtf8(const std::string& utf8String) +{ + std::string::const_iterator iter = utf8String.begin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.end()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_RJCT) { + return false; + } + } + if (utf8_state) { + return false; + } + return true; +} + +} // namespace detail + +#endif + +namespace detail { + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 1)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(utf8String.begin(), utf8String.end(), alloc); +} + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 2)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + auto iter = utf8String.cbegin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.cend()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + if (codepoint <= 0xffff) { + result += static_cast(codepoint); + } + else { + codepoint -= 0x10000; + result += static_cast((codepoint >> 10) + 0xd800); + result += static_cast((codepoint & 0x3ff) + 0xdc00); + } + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template ::value && (sizeof(typename Utf8String::value_type) == 1) && (sizeof(typename StringType::value_type) == 4)>::type* = nullptr> +inline StringType fromUtf8(const Utf8String& utf8String, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + StringType result(alloc); + result.reserve(utf8String.length()); + auto iter = utf8String.cbegin(); + unsigned utf8_state = S_STRT; + std::uint32_t codepoint = 0; + while (iter < utf8String.cend()) { + if ((utf8_state = consumeUtf8Fragment(utf8_state, static_cast(*iter++), codepoint)) == S_STRT) { + result += static_cast(codepoint); + codepoint = 0; + } + else if (utf8_state == S_RJCT) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); + utf8_state = S_STRT; + codepoint = 0; +#endif + } + } + if (utf8_state) { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal byte sequence for unicode character.", utf8String, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + result += static_cast(0xfffd); +#endif + } + return result; +} + +template +inline StringType fromUtf8(const charT (&utf8String)[N]) +{ +#ifdef GHC_WITH_STRING_VIEW + return fromUtf8(basic_string_view(utf8String, N - 1)); +#else + return fromUtf8(std::basic_string(utf8String, N - 1)); +#endif +} + +template ::value && (sizeof(typename strT::value_type) == 1), int>::type size = 1> +inline std::string toUtf8(const strT& unicodeString) +{ + return std::string(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 2), int>::type size = 2> +inline std::string toUtf8(const strT& unicodeString) +{ + std::string result; + for (auto iter = unicodeString.begin(); iter != unicodeString.end(); ++iter) { + char32_t c = *iter; + if (is_surrogate(c)) { + ++iter; + if (iter != unicodeString.end() && is_high_surrogate(c) && is_low_surrogate(*iter)) { + appendUTF8(result, (char32_t(c) << 10) + *iter - 0x35fdc00); + } + else { +#ifdef GHC_RAISE_UNICODE_ERRORS + throw filesystem_error("Illegal code point for unicode character.", result, std::make_error_code(std::errc::illegal_byte_sequence)); +#else + appendUTF8(result, 0xfffd); + if (iter == unicodeString.end()) { + break; + } +#endif + } + } + else { + appendUTF8(result, c); + } + } + return result; +} + +template ::value && (sizeof(typename strT::value_type) == 4), int>::type size = 4> +inline std::string toUtf8(const strT& unicodeString) +{ + std::string result; + for (auto c : unicodeString) { + appendUTF8(result, static_cast(c)); + } + return result; +} + +template +inline std::string toUtf8(const charT* unicodeString) +{ +#ifdef GHC_WITH_STRING_VIEW + return toUtf8(basic_string_view>(unicodeString)); +#else + return toUtf8(std::basic_string>(unicodeString)); +#endif +} + +#ifdef GHC_USE_WCHAR_T +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 1), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return StringType(temp.begin(), temp.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 2), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + return StringType(wString.begin(), wString.end(), alloc); +} + +template ::value && (sizeof(typename WString::value_type) == 2) && (sizeof(typename StringType::value_type) == 4), bool>::type = false> +inline StringType fromWChar(const WString& wString, const typename StringType::allocator_type& alloc = typename StringType::allocator_type()) +{ + auto temp = toUtf8(wString); + return fromUtf8(temp, alloc); +} + +template ::value && (sizeof(typename strT::value_type) == 1), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return fromUtf8(unicodeString); +} + +template ::value && (sizeof(typename strT::value_type) == 2), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + return std::wstring(unicodeString.begin(), unicodeString.end()); +} + +template ::value && (sizeof(typename strT::value_type) == 4), bool>::type = false> +inline std::wstring toWChar(const strT& unicodeString) +{ + auto temp = toUtf8(unicodeString); + return fromUtf8(temp); +} + +template +inline std::wstring toWChar(const charT* unicodeString) +{ +#ifdef GHC_WITH_STRING_VIEW + return toWChar(basic_string_view>(unicodeString)); +#else + return toWChar(std::basic_string>(unicodeString)); +#endif +} +#endif // GHC_USE_WCHAR_T + +} // namespace detail + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +template ::value, bool>::type = true> +GHC_INLINE bool startsWith(const strT& what, const strT& with) +{ + return with.length() <= what.length() && equal(with.begin(), with.end(), what.begin()); +} + +template ::value, bool>::type = true> +GHC_INLINE bool endsWith(const strT& what, const strT& with) +{ + return with.length() <= what.length() && what.compare(what.length() - with.length(), with.size(), with) == 0; +} + +} // namespace detail + +GHC_INLINE void path::check_long_path() +{ +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + postprocess_path_with_format(native_format); + } +#endif +} + +GHC_INLINE void path::postprocess_path_with_format(path::format fmt) +{ +#ifdef GHC_RAISE_UNICODE_ERRORS + if (!detail::validUtf8(_path)) { + path t; + t._path = _path; + throw filesystem_error("Illegal byte sequence for unicode character.", t, std::make_error_code(std::errc::illegal_byte_sequence)); + } +#endif + switch (fmt) { +#ifdef GHC_OS_WINDOWS + case path::native_format: + case path::auto_format: + case path::generic_format: + for (auto& c : _path) { + if (c == generic_separator) { + c = preferred_separator; + } + } +#ifdef GHC_WIN_AUTO_PREFIX_LONG_PATH + if (is_absolute() && _path.length() >= MAX_PATH - 12 && !detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\")))) { + _path = GHC_PLATFORM_LITERAL("\\\\?\\") + _path; + } +#endif + handle_prefixes(); + break; +#else + case path::auto_format: + case path::native_format: + case path::generic_format: + // nothing to do + break; +#endif + } + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator) { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength) + 2, _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); + } + else { + impl_string_type::iterator new_end = std::unique(_path.begin() + static_cast(_prefixLength), _path.end(), [](path::value_type lhs, path::value_type rhs) { return lhs == rhs && lhs == preferred_separator; }); + _path.erase(new_end, _path.end()); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path(const Source& source, format fmt) +#ifdef GHC_USE_WCHAR_T + : _path(detail::toWChar(source)) +#else + : _path(detail::toUtf8(source)) +#endif +{ + postprocess_path_with_format(fmt); +} + +template +inline path u8path(const Source& source) +{ + return path(source); +} +template +inline path u8path(InputIterator first, InputIterator last) +{ + return path(first, last); +} + +template +inline path::path(InputIterator first, InputIterator last, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + // delegated +} + +#ifdef GHC_EXPAND_IMPL + +namespace detail { + +GHC_INLINE bool equals_simple_insensitive(const path::value_type* str1, const path::value_type* str2) +{ +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + while (::tolower((unsigned char)*str1) == ::tolower((unsigned char)*str2++)) { + if (*str1++ == 0) + return true; + } + return false; +#else // __GNUC__ +#ifdef GHC_USE_WCHAR_T + return 0 == ::_wcsicmp(str1, str2); +#else // GHC_USE_WCHAR_T + return 0 == ::_stricmp(str1, str2); +#endif // GHC_USE_WCHAR_T +#endif // __GNUC__ +#else // GHC_OS_WINDOWS + return 0 == ::strcasecmp(str1, str2); +#endif // GHC_OS_WINDOWS +} + +GHC_INLINE int compare_simple_insensitive(const path::value_type* str1, size_t len1, const path::value_type* str2, size_t len2) +{ + while (len1 > 0 && len2 > 0 && ::tolower(static_cast(*str1)) == ::tolower(static_cast(*str2))) { + --len1; + --len2; + ++str1; + ++str2; + } + if (len1 && len2) { + return *str1 < *str2 ? -1 : 1; + } + if (len1 == 0 && len2 == 0) { + return 0; + } + return len1 == 0 ? -1 : 1; +} + +GHC_INLINE const char* strerror_adapter(char* gnu, char*) +{ + return gnu; +} + +GHC_INLINE const char* strerror_adapter(int posix, char* buffer) +{ + if (posix) { + return "Error in strerror_r!"; + } + return buffer; +} + +template +GHC_INLINE std::string systemErrorText(ErrorNumber code = 0) +{ +#if defined(GHC_OS_WINDOWS) + LPVOID msgBuf; + DWORD dw = code ? static_cast(code) : ::GetLastError(); + FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&msgBuf, 0, NULL); + std::string msg = toUtf8(std::wstring((LPWSTR)msgBuf)); + LocalFree(msgBuf); + return msg; +#else + char buffer[512]; + return strerror_adapter(strerror_r(code ? code : errno, buffer, sizeof(buffer)), buffer); +#endif +} + +#ifdef GHC_OS_WINDOWS +using CreateSymbolicLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, DWORD); +using CreateHardLinkW_fp = BOOLEAN(WINAPI*)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES); + +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool to_directory, std::error_code& ec) +{ + std::error_code tec; + auto fs = status(target_name, tec); + if ((fs.type() == file_type::directory && !to_directory) || (fs.type() == file_type::regular && to_directory)) { + ec = detail::make_error_code(detail::portable_error::not_supported); + return; + } +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + static CreateSymbolicLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateSymbolicLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + if (api_call) { + if (api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 1 : 0) == 0) { + auto result = ::GetLastError(); + if (result == ERROR_PRIVILEGE_NOT_HELD && api_call(GHC_NATIVEWP(new_symlink), GHC_NATIVEWP(target_name), to_directory ? 3 : 2) != 0) { + return; + } + ec = detail::make_system_error(result); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + static CreateHardLinkW_fp api_call = reinterpret_cast(GetProcAddress(GetModuleHandleW(L"kernel32.dll"), "CreateHardLinkW")); +#if defined(__GNUC__) && __GNUC__ >= 8 +#pragma GCC diagnostic pop +#endif + if (api_call) { + if (api_call(GHC_NATIVEWP(new_hardlink), GHC_NATIVEWP(target_name), NULL) == 0) { + ec = detail::make_system_error(); + } + } + else { + ec = detail::make_system_error(ERROR_NOT_SUPPORTED); + } +} + +GHC_INLINE path getFullPathName(const wchar_t* p, std::error_code& ec) +{ + ULONG size = ::GetFullPathNameW(p, 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(p, size, buf.data(), nullptr); + if (s2 && s2 < size) { + return path(std::wstring(buf.data(), s2)); + } + } + ec = detail::make_system_error(); + return path(); +} + +#else +GHC_INLINE void create_symlink(const path& target_name, const path& new_symlink, bool, std::error_code& ec) +{ + if (::symlink(target_name.c_str(), new_symlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} + +#ifndef GHC_OS_WEB +GHC_INLINE void create_hardlink(const path& target_name, const path& new_hardlink, std::error_code& ec) +{ + if (::link(target_name.c_str(), new_hardlink.c_str()) != 0) { + ec = detail::make_system_error(); + } +} +#endif +#endif + +template +GHC_INLINE file_status file_status_from_st_mode(T mode) +{ +#ifdef GHC_OS_WINDOWS + file_type ft = file_type::unknown; + if ((mode & _S_IFDIR) == _S_IFDIR) { + ft = file_type::directory; + } + else if ((mode & _S_IFREG) == _S_IFREG) { + ft = file_type::regular; + } + else if ((mode & _S_IFCHR) == _S_IFCHR) { + ft = file_type::character; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#else + file_type ft = file_type::unknown; + if (S_ISDIR(mode)) { + ft = file_type::directory; + } + else if (S_ISREG(mode)) { + ft = file_type::regular; + } + else if (S_ISCHR(mode)) { + ft = file_type::character; + } + else if (S_ISBLK(mode)) { + ft = file_type::block; + } + else if (S_ISFIFO(mode)) { + ft = file_type::fifo; + } + else if (S_ISLNK(mode)) { + ft = file_type::symlink; + } + else if (S_ISSOCK(mode)) { + ft = file_type::socket; + } + perms prms = static_cast(mode & 0xfff); + return file_status(ft, prms); +#endif +} + +#ifdef GHC_OS_WINDOWS + +class unique_handle +{ +public: + typedef HANDLE element_type; + + unique_handle() noexcept + : _handle(INVALID_HANDLE_VALUE) + { + } + explicit unique_handle(element_type h) noexcept + : _handle(h) + { + } + unique_handle(unique_handle&& u) noexcept + : _handle(u.release()) + { + } + ~unique_handle() { reset(); } + unique_handle& operator=(unique_handle&& u) noexcept + { + reset(u.release()); + return *this; + } + element_type get() const noexcept { return _handle; } + explicit operator bool() const noexcept { return _handle != INVALID_HANDLE_VALUE; } + element_type release() noexcept + { + element_type tmp = _handle; + _handle = INVALID_HANDLE_VALUE; + return tmp; + } + void reset(element_type h = INVALID_HANDLE_VALUE) noexcept + { + element_type tmp = _handle; + _handle = h; + if (tmp != INVALID_HANDLE_VALUE) { + CloseHandle(tmp); + } + } + void swap(unique_handle& u) noexcept { std::swap(_handle, u._handle); } + +private: + element_type _handle; +}; + +#ifndef REPARSE_DATA_BUFFER_HEADER_SIZE +typedef struct _REPARSE_DATA_BUFFER +{ + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + } DUMMYUNIONNAME; +} REPARSE_DATA_BUFFER; +#ifndef MAXIMUM_REPARSE_DATA_BUFFER_SIZE +#define MAXIMUM_REPARSE_DATA_BUFFER_SIZE (16 * 1024) +#endif +#endif + +template +struct free_deleter +{ + void operator()(T* p) const { std::free(p); } +}; + +GHC_INLINE std::unique_ptr> getReparseData(const path& p, std::error_code& ec) +{ + unique_handle file(CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0)); + if (!file) { + ec = detail::make_system_error(); + return nullptr; + } + + std::unique_ptr> reparseData(reinterpret_cast(std::calloc(1, MAXIMUM_REPARSE_DATA_BUFFER_SIZE))); + ULONG bufferUsed; + if (DeviceIoControl(file.get(), FSCTL_GET_REPARSE_POINT, 0, 0, reparseData.get(), MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bufferUsed, 0)) { + return reparseData; + } + else { + ec = detail::make_system_error(); + } + return nullptr; +} +#endif + +GHC_INLINE path resolveSymlink(const path& p, std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS + path result; + auto reparseData = detail::getReparseData(p, ec); + if (!ec) { + if (reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag)) { + switch (reparseData->ReparseTag) { + case IO_REPARSE_TAG_SYMLINK: { + auto printName = std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.PrintNameLength / sizeof(WCHAR)); + auto substituteName = + std::wstring(&reparseData->SymbolicLinkReparseBuffer.PathBuffer[reparseData->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + if (detail::endsWith(substituteName, printName) && detail::startsWith(substituteName, std::wstring(L"\\??\\"))) { + result = printName; + } + else { + result = substituteName; + } + if (reparseData->SymbolicLinkReparseBuffer.Flags & 0x1 /*SYMLINK_FLAG_RELATIVE*/) { + result = p.parent_path() / result; + } + break; + } + case IO_REPARSE_TAG_MOUNT_POINT: + result = detail::getFullPathName(GHC_NATIVEWP(p), ec); + // result = std::wstring(&reparseData->MountPointReparseBuffer.PathBuffer[reparseData->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)], reparseData->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + break; + default: + break; + } + } + } + return result; +#else + size_t bufferSize = 256; + while (true) { + std::vector buffer(bufferSize, static_cast(0)); + auto rc = ::readlink(p.c_str(), buffer.data(), buffer.size()); + if (rc < 0) { + ec = detail::make_system_error(); + return path(); + } + else if (rc < static_cast(bufferSize)) { + return path(std::string(buffer.data(), static_cast(rc))); + } + bufferSize *= 2; + } + return path(); +#endif +} + +#ifdef GHC_OS_WINDOWS +GHC_INLINE time_t timeFromFILETIME(const FILETIME& ft) +{ + ULARGE_INTEGER ull; + ull.LowPart = ft.dwLowDateTime; + ull.HighPart = ft.dwHighDateTime; + return static_cast(ull.QuadPart / 10000000ULL - 11644473600ULL); +} + +GHC_INLINE void timeToFILETIME(time_t t, FILETIME& ft) +{ + LONGLONG ll; + ll = Int32x32To64(t, 10000000) + 116444736000000000; + ft.dwLowDateTime = static_cast(ll); + ft.dwHighDateTime = static_cast(ll >> 32); +} + +template +GHC_INLINE uintmax_t hard_links_from_INFO(const INFO* info) +{ + return static_cast(-1); +} + +template <> +GHC_INLINE uintmax_t hard_links_from_INFO(const BY_HANDLE_FILE_INFORMATION* info) +{ + return info->nNumberOfLinks; +} + +template +GHC_INLINE DWORD reparse_tag_from_INFO(const INFO*) +{ + return 0; +} + +template <> +GHC_INLINE DWORD reparse_tag_from_INFO(const WIN32_FIND_DATAW* info) +{ + return info->dwReserved0; +} + +template +GHC_INLINE file_status status_from_INFO(const path& p, const INFO* info, std::error_code& ec, uintmax_t* sz = nullptr, time_t* lwt = nullptr) +{ + file_type ft = file_type::unknown; + if (sizeof(INFO) == sizeof(WIN32_FIND_DATAW)) { + if (detail::reparse_tag_from_INFO(info) == IO_REPARSE_TAG_SYMLINK) { + ft = file_type::symlink; + } + } + else { + if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) { + auto reparseData = detail::getReparseData(p, ec); + if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + ft = file_type::symlink; + } + } + } + if (ft == file_type::unknown) { + if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) { + ft = file_type::directory; + } + else { + ft = file_type::regular; + } + } + perms prms = perms::owner_read | perms::group_read | perms::others_read; + if (!(info->dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { + prms = prms | perms::owner_write | perms::group_write | perms::others_write; + } + if (has_executable_extension(p)) { + prms = prms | perms::owner_exec | perms::group_exec | perms::others_exec; + } + if (sz) { + *sz = static_cast(info->nFileSizeHigh) << (sizeof(info->nFileSizeHigh) * 8) | info->nFileSizeLow; + } + if (lwt) { + *lwt = detail::timeFromFILETIME(info->ftLastWriteTime); + } + return file_status(ft, prms); +} + +#endif + +GHC_INLINE bool is_not_found_error(std::error_code& ec) +{ +#ifdef GHC_OS_WINDOWS + return ec.value() == ERROR_FILE_NOT_FOUND || ec.value() == ERROR_PATH_NOT_FOUND || ec.value() == ERROR_INVALID_NAME; +#else + return ec.value() == ENOENT || ec.value() == ENOTDIR; +#endif +} + +GHC_INLINE file_status symlink_status_ex(const path& p, std::error_code& ec, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr) noexcept +{ +#ifdef GHC_OS_WINDOWS + file_status fs; + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else { + ec.clear(); + fs = detail::status_from_INFO(p, &attr, ec, sz, lwt); + if (nhl) { + *nhl = 0; + } + } + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return ec ? file_status(file_type::none) : fs; +#else + (void)sz; + (void)nhl; + (void)lwt; + struct ::stat fs; + auto result = ::lstat(p.c_str(), &fs); + if (result == 0) { + ec.clear(); + file_status f_s = detail::file_status_from_st_mode(fs.st_mode); + return f_s; + } + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); +#endif +} + +GHC_INLINE file_status status_ex(const path& p, std::error_code& ec, file_status* sls = nullptr, uintmax_t* sz = nullptr, uintmax_t* nhl = nullptr, time_t* lwt = nullptr, int recurse_count = 0) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (recurse_count > 16) { + ec = detail::make_system_error(0x2A9 /*ERROR_STOPPED_ON_SYMLINK*/); + return file_status(file_type::unknown); + } + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!::GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + } + else if (attr.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + auto reparseData = detail::getReparseData(p, ec); + if (!ec && reparseData && IsReparseTagMicrosoft(reparseData->ReparseTag) && reparseData->ReparseTag == IO_REPARSE_TAG_SYMLINK) { + path target = resolveSymlink(p, ec); + file_status result; + if (!ec && !target.empty()) { + if (sls) { + *sls = status_from_INFO(p, &attr, ec); + } + return detail::status_ex(target, ec, nullptr, sz, nhl, lwt, recurse_count + 1); + } + return file_status(file_type::unknown); + } + } + if (ec) { + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found); + } + return file_status(file_type::none); + } + if (nhl) { + *nhl = 0; + } + return detail::status_from_INFO(p, &attr, ec, sz, lwt); +#else + (void)recurse_count; + struct ::stat st; + auto result = ::lstat(p.c_str(), &st); + if (result == 0) { + ec.clear(); + file_status fs = detail::file_status_from_st_mode(st.st_mode); + if (sls) { + *sls = fs; + } + if (fs.type() == file_type::symlink) { + result = ::stat(p.c_str(), &st); + if (result == 0) { + fs = detail::file_status_from_st_mode(st.st_mode); + } + else { + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); + } + } + if (sz) { + *sz = static_cast(st.st_size); + } + if (nhl) { + *nhl = st.st_nlink; + } + if (lwt) { + *lwt = st.st_mtime; + } + return fs; + } + else { + ec = detail::make_system_error(); + if (detail::is_not_found_error(ec)) { + return file_status(file_type::not_found, perms::unknown); + } + return file_status(file_type::none); + } +#endif +} + +} // namespace detail + +GHC_INLINE u8arguments::u8arguments(int& argc, char**& argv) + : _argc(argc) + , _argv(argv) + , _refargc(argc) + , _refargv(argv) + , _isvalid(false) +{ +#ifdef GHC_OS_WINDOWS + LPWSTR* p; + p = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + _args.reserve(static_cast(argc)); + _argp.reserve(static_cast(argc)); + for (size_t i = 0; i < static_cast(argc); ++i) { + _args.push_back(detail::toUtf8(std::wstring(p[i]))); + _argp.push_back((char*)_args[i].data()); + } + argv = _argp.data(); + ::LocalFree(p); + _isvalid = true; +#else + std::setlocale(LC_ALL, ""); +#if defined(__ANDROID__) && __ANDROID_API__ < 26 + _isvalid = true; +#else + if (detail::equals_simple_insensitive(::nl_langinfo(CODESET), "UTF-8")) { + _isvalid = true; + } +#endif +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.construct] constructors and destructor + +GHC_INLINE path::path() noexcept {} + +GHC_INLINE path::path(const path& p) + : _path(p._path) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif +{ +} + +GHC_INLINE path::path(path&& p) noexcept + : _path(std::move(p._path)) +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + , _prefixLength(p._prefixLength) +#endif +{ +} + +GHC_INLINE path::path(string_type&& source, format fmt) + : _path(std::move(source)) +{ + postprocess_path_with_format(fmt); +} + +#endif // GHC_EXPAND_IMPL + +#ifdef GHC_WITH_EXCEPTIONS +template +inline path::path(const Source& source, const std::locale& loc, format fmt) + : path(source, fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} + +template +inline path::path(InputIterator first, InputIterator last, const std::locale& loc, format fmt) + : path(std::basic_string::value_type>(first, last), fmt) +{ + std::string locName = loc.name(); + if (!(locName.length() >= 5 && (locName.substr(locName.length() - 5) == "UTF-8" || locName.substr(locName.length() - 5) == "utf-8"))) { + throw filesystem_error("This implementation only supports UTF-8 locales!", path(_path), detail::make_error_code(detail::portable_error::not_supported)); + } +} +#endif + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE path::~path() {} + +//----------------------------------------------------------------------------- +// [fs.path.assign] assignments + +GHC_INLINE path& path::operator=(const path& p) +{ + _path = p._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif + return *this; +} + +GHC_INLINE path& path::operator=(path&& p) noexcept +{ + _path = std::move(p._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = p._prefixLength; +#endif + return *this; +} + +GHC_INLINE path& path::operator=(path::string_type&& source) +{ + return assign(source); +} + +GHC_INLINE path& path::assign(path::string_type&& source) +{ + _path = std::move(source); + postprocess_path_with_format(native_format); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator=(const Source& source) +{ + return assign(source); +} + +template +inline path& path::assign(const Source& source) +{ +#ifdef GHC_USE_WCHAR_T + _path.assign(detail::toWChar(source)); +#else + _path.assign(detail::toUtf8(source)); +#endif + postprocess_path_with_format(native_format); + return *this; +} + +template <> +inline path& path::assign(const path& source) +{ + _path = source._path; +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = source._prefixLength; +#endif + return *this; +} + +template +inline path& path::assign(InputIterator first, InputIterator last) +{ + _path.assign(first, last); + postprocess_path_with_format(native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.append] appends + +GHC_INLINE path& path::operator/=(const path& p) +{ + if (p.empty()) { + // was: if ((!has_root_directory() && is_absolute()) || has_filename()) + if (!_path.empty() && _path[_path.length() - 1] != preferred_separator && _path[_path.length() - 1] != ':') { + _path += preferred_separator; + } + return *this; + } + if ((p.is_absolute() && (_path != root_name()._path || p._path != "/")) || (p.has_root_name() && p.root_name() != root_name())) { + assign(p); + return *this; + } + if (p.has_root_directory()) { + assign(root_name()); + } + else if ((!has_root_directory() && is_absolute()) || has_filename()) { + _path += preferred_separator; + } + auto iter = p.begin(); + bool first = true; + if (p.has_root_name()) { + ++iter; + } + while (iter != p.end()) { + if (!first && !(!_path.empty() && _path[_path.length() - 1] == preferred_separator)) { + _path += preferred_separator; + } + first = false; + _path += (*iter++).native(); + } + check_long_path(); + return *this; +} + +GHC_INLINE void path::append_name(const value_type* name) +{ + if (_path.empty()) { + this->operator/=(path(name)); + } + else { + if (_path.back() != path::preferred_separator) { + _path.push_back(path::preferred_separator); + } + _path += name; + check_long_path(); + } +} + +#endif // GHC_EXPAND_IMPL + +template +inline path& path::operator/=(const Source& source) +{ + return append(source); +} + +template +inline path& path::append(const Source& source) +{ + return this->operator/=(path(source)); +} + +template <> +inline path& path::append(const path& p) +{ + return this->operator/=(p); +} + +template +inline path& path::append(InputIterator first, InputIterator last) +{ + std::basic_string::value_type> part(first, last); + return append(part); +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.concat] concatenation + +GHC_INLINE path& path::operator+=(const path& x) +{ + return concat(x._path); +} + +GHC_INLINE path& path::operator+=(const string_type& x) +{ + return concat(x); +} + +#ifdef GHC_WITH_STRING_VIEW +GHC_INLINE path& path::operator+=(basic_string_view x) +{ + return concat(x); +} +#endif + +GHC_INLINE path& path::operator+=(const value_type* x) +{ +#ifdef GHC_WITH_STRING_VIEW + basic_string_view part(x); +#else + string_type part(x); +#endif + return concat(part); +} + +GHC_INLINE path& path::operator+=(value_type x) +{ +#ifdef GHC_OS_WINDOWS + if (x == generic_separator) { + x = preferred_separator; + } +#endif + if (_path.empty() || _path.back() != preferred_separator) { + _path += x; + } + check_long_path(); + return *this; +} + +#endif // GHC_EXPAND_IMPL + +template +inline path::path_from_string& path::operator+=(const Source& x) +{ + return concat(x); +} + +template +inline path::path_type_EcharT& path::operator+=(EcharT x) +{ +#ifdef GHC_WITH_STRING_VIEW + basic_string_view part(&x, 1); +#else + std::basic_string part(1, x); +#endif + concat(part); + return *this; +} + +template +inline path& path::concat(const Source& x) +{ + path p(x); + _path += p._path; + postprocess_path_with_format(native_format); + return *this; +} +template +inline path& path::concat(InputIterator first, InputIterator last) +{ + _path.append(first, last); + postprocess_path_with_format(native_format); + return *this; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.modifiers] modifiers +GHC_INLINE void path::clear() noexcept +{ + _path.clear(); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; +#endif +} + +GHC_INLINE path& path::make_preferred() +{ + // as this filesystem implementation only uses generic_format + // internally, this must be a no-op + return *this; +} + +GHC_INLINE path& path::remove_filename() +{ + if (has_filename()) { + _path.erase(_path.size() - filename()._path.size()); + } + return *this; +} + +GHC_INLINE path& path::replace_filename(const path& replacement) +{ + remove_filename(); + return append(replacement); +} + +GHC_INLINE path& path::replace_extension(const path& replacement) +{ + if (has_extension()) { + _path.erase(_path.size() - extension()._path.size()); + } + if (!replacement.empty() && replacement._path[0] != '.') { + _path += '.'; + } + return concat(replacement); +} + +GHC_INLINE void path::swap(path& rhs) noexcept +{ + _path.swap(rhs._path); +#if defined(GHC_OS_WINDOWS) && defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + std::swap(_prefixLength, rhs._prefixLength); +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.native.obs] native format observers +GHC_INLINE const path::string_type& path::native() const noexcept +{ + return _path; +} + +GHC_INLINE const path::value_type* path::c_str() const noexcept +{ + return native().c_str(); +} + +GHC_INLINE path::operator path::string_type() const +{ + return native(); +} + +#endif // GHC_EXPAND_IMPL + +template +inline std::basic_string path::string(const Allocator& a) const +{ +#ifdef GHC_USE_WCHAR_T + return detail::fromWChar>(_path, a); +#else + return detail::fromUtf8>(_path, a); +#endif +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::string() const +{ +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif +} + +GHC_INLINE std::wstring path::wstring() const +{ +#ifdef GHC_USE_WCHAR_T + return native(); +#else + return detail::fromUtf8(native()); +#endif +} + +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +GHC_INLINE std::u8string path::u8string() const +{ +#ifdef GHC_USE_WCHAR_T + return std::u8string(reinterpret_cast(detail::toUtf8(native()).c_str())); +#else + return std::u8string(reinterpret_cast(c_str())); +#endif +} +#else +GHC_INLINE std::string path::u8string() const +{ +#ifdef GHC_USE_WCHAR_T + return detail::toUtf8(native()); +#else + return native(); +#endif +} +#endif + +GHC_INLINE std::u16string path::u16string() const +{ + // TODO: optimize + return detail::fromUtf8(string()); +} + +GHC_INLINE std::u32string path::u32string() const +{ + // TODO: optimize + return detail::fromUtf8(string()); +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.generic.obs] generic format observers +template +inline std::basic_string path::generic_string(const Allocator& a) const +{ +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto result = detail::fromWChar, path::string_type>(_path, a); +#else + auto result = detail::fromUtf8>(_path, a); +#endif + for (auto& c : result) { + if (c == preferred_separator) { + c = generic_separator; + } + } + return result; +#else + return detail::fromUtf8>(_path, a); +#endif +} + +#ifdef GHC_EXPAND_IMPL + +GHC_INLINE std::string path::generic_string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return _path; +#endif +} + +GHC_INLINE std::wstring path::generic_wstring() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} // namespace filesystem + +#if defined(__cpp_lib_char8_t) && !defined(GHC_FILESYSTEM_ENFORCE_CPP17_API) +GHC_INLINE std::u8string path::generic_u8string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return std::u8string(reinterpret_cast(_path.c_str())); +#endif +} +#else +GHC_INLINE std::string path::generic_u8string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return _path; +#endif +} +#endif + +GHC_INLINE std::u16string path::generic_u16string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} + +GHC_INLINE std::u32string path::generic_u32string() const +{ +#ifdef GHC_OS_WINDOWS + return generic_string(); +#else + return detail::fromUtf8(_path); +#endif +} + +//----------------------------------------------------------------------------- +// [fs.path.compare] compare +GHC_INLINE int path::compare(const path& p) const noexcept +{ +#ifdef LWG_2936_BEHAVIOUR + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); +#ifdef GHC_OS_WINDOWS + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); +#else + auto rnc = _path.compare(0, rnl1, p._path, 0, (std::min(rnl1, rnl2))); +#endif + if (rnc) { + return rnc; + } + bool hrd1 = has_root_directory(), hrd2 = p.has_root_directory(); + if (hrd1 != hrd2) { + return hrd1 ? 1 : -1; + } + if (hrd1) { + ++rnl1; + ++rnl2; + } + auto iter1 = _path.begin() + static_cast(rnl1); + auto iter2 = p._path.begin() + static_cast(rnl2); + while (iter1 != _path.end() && iter2 != p._path.end() && *iter1 == *iter2) { + ++iter1; + ++iter2; + } + if (iter1 == _path.end()) { + return iter2 == p._path.end() ? 0 : -1; + } + if (iter2 == p._path.end()) { + return 1; + } + if (*iter1 == preferred_separator) { + return -1; + } + if (*iter2 == preferred_separator) { + return 1; + } + return *iter1 < *iter2 ? -1 : 1; +#else // LWG_2936_BEHAVIOUR +#ifdef GHC_OS_WINDOWS + auto rnl1 = root_name_length(); + auto rnl2 = p.root_name_length(); + auto rnc = detail::compare_simple_insensitive(_path.c_str(), rnl1, p._path.c_str(), rnl2); + if (rnc) { + return rnc; + } + return _path.compare(rnl1, std::string::npos, p._path, rnl2, std::string::npos); +#else + return _path.compare(p._path); +#endif +#endif +} + +GHC_INLINE int path::compare(const string_type& s) const +{ + return compare(path(s)); +} + +#ifdef GHC_WITH_STRING_VIEW +GHC_INLINE int path::compare(basic_string_view s) const +{ + return compare(path(s)); +} +#endif + +GHC_INLINE int path::compare(const value_type* s) const +{ + return compare(path(s)); +} + +//----------------------------------------------------------------------------- +// [fs.path.decompose] decomposition +#ifdef GHC_OS_WINDOWS +GHC_INLINE void path::handle_prefixes() +{ +#if defined(GHC_WIN_AUTO_PREFIX_LONG_PATH) + _prefixLength = 0; + if (_path.length() >= 6 && _path[2] == '?' && std::toupper(static_cast(_path[4])) >= 'A' && std::toupper(static_cast(_path[4])) <= 'Z' && _path[5] == ':') { + if (detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\\\?\\"))) || detail::startsWith(_path, impl_string_type(GHC_PLATFORM_LITERAL("\\??\\")))) { + _prefixLength = 4; + } + } +#endif // GHC_WIN_AUTO_PREFIX_LONG_PATH +} +#endif + +GHC_INLINE path::string_type::size_type path::root_name_length() const noexcept +{ +#ifdef GHC_OS_WINDOWS + if (_path.length() >= _prefixLength + 2 && std::toupper(static_cast(_path[_prefixLength])) >= 'A' && std::toupper(static_cast(_path[_prefixLength])) <= 'Z' && _path[_prefixLength + 1] == ':') { + return 2; + } +#endif + if (_path.length() > _prefixLength + 2 && _path[_prefixLength] == preferred_separator && _path[_prefixLength + 1] == preferred_separator && _path[_prefixLength + 2] != preferred_separator && std::isprint(_path[_prefixLength + 2])) { + impl_string_type::size_type pos = _path.find(preferred_separator, _prefixLength + 3); + if (pos == impl_string_type::npos) { + return _path.length(); + } + else { + return pos; + } + } + return 0; +} + +GHC_INLINE path path::root_name() const +{ + return path(_path.substr(_prefixLength, root_name_length()), native_format); +} + +GHC_INLINE path path::root_directory() const +{ + if (has_root_directory()) { + static const path _root_dir(std::string(1, preferred_separator), native_format); + return _root_dir; + } + return path(); +} + +GHC_INLINE path path::root_path() const +{ + return path(root_name().string() + root_directory().string(), native_format); +} + +GHC_INLINE path path::relative_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + return path(_path.substr((std::min)(rootPathLen, _path.length())), generic_format); +} + +GHC_INLINE path path::parent_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + if (rootPathLen < _path.length()) { + if (empty()) { + return path(); + } + else { + auto piter = end(); + auto iter = piter.decrement(_path.end()); + if (iter > _path.begin() + static_cast(rootPathLen) && *iter != preferred_separator) { + --iter; + } + return path(_path.begin(), iter, native_format); + } + } + else { + return *this; + } +} + +GHC_INLINE path path::filename() const +{ + return !has_relative_path() ? path() : path(*--end()); +} + +GHC_INLINE path path::stem() const +{ + impl_string_type fn = filename().native(); + if (fn != "." && fn != "..") { + impl_string_type::size_type pos = fn.rfind('.'); + if (pos != impl_string_type::npos && pos > 0) { + return path{fn.substr(0, pos), native_format}; + } + } + return path{fn, native_format}; +} + +GHC_INLINE path path::extension() const +{ + if (has_relative_path()) { + auto iter = end(); + const auto& fn = *--iter; + impl_string_type::size_type pos = fn._path.rfind('.'); + if (pos != std::string::npos && pos > 0) { + return path(fn._path.substr(pos), native_format); + } + } + return path(); +} + +#ifdef GHC_OS_WINDOWS +namespace detail { +GHC_INLINE bool has_executable_extension(const path& p) +{ + if (p.has_relative_path()) { + auto iter = p.end(); + const auto& fn = *--iter; + auto pos = fn._path.find_last_of('.'); + if (pos == std::string::npos || pos == 0 || fn._path.length() - pos != 3) { + return false; + } + const path::value_type* ext = fn._path.c_str() + pos + 1; + if (detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("exe")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("cmd")) || detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("bat")) || + detail::equals_simple_insensitive(ext, GHC_PLATFORM_LITERAL("com"))) { + return true; + } + } + return false; +} +} // namespace detail +#endif + +//----------------------------------------------------------------------------- +// [fs.path.query] query +GHC_INLINE bool path::empty() const noexcept +{ + return _path.empty(); +} + +GHC_INLINE bool path::has_root_name() const +{ + return root_name_length() > 0; +} + +GHC_INLINE bool path::has_root_directory() const +{ + auto rootLen = _prefixLength + root_name_length(); + return (_path.length() > rootLen && _path[rootLen] == preferred_separator); +} + +GHC_INLINE bool path::has_root_path() const +{ + return has_root_name() || has_root_directory(); +} + +GHC_INLINE bool path::has_relative_path() const +{ + auto rootPathLen = _prefixLength + root_name_length() + (has_root_directory() ? 1 : 0); + return rootPathLen < _path.length(); +} + +GHC_INLINE bool path::has_parent_path() const +{ + return !parent_path().empty(); +} + +GHC_INLINE bool path::has_filename() const +{ + return has_relative_path() && !filename().empty(); +} + +GHC_INLINE bool path::has_stem() const +{ + return !stem().empty(); +} + +GHC_INLINE bool path::has_extension() const +{ + return !extension().empty(); +} + +GHC_INLINE bool path::is_absolute() const +{ +#ifdef GHC_OS_WINDOWS + return has_root_name() && has_root_directory(); +#else + return has_root_directory(); +#endif +} + +GHC_INLINE bool path::is_relative() const +{ + return !is_absolute(); +} + +//----------------------------------------------------------------------------- +// [fs.path.gen] generation +GHC_INLINE path path::lexically_normal() const +{ + path dest; + bool lastDotDot = false; + for (string_type s : *this) { + if (s == ".") { + dest /= ""; + continue; + } + else if (s == ".." && !dest.empty()) { + auto root = root_path(); + if (dest == root) { + continue; + } + else if (*(--dest.end()) != "..") { + if (dest._path.back() == preferred_separator) { + dest._path.pop_back(); + } + dest.remove_filename(); + continue; + } + } + if (!(s.empty() && lastDotDot)) { + dest /= s; + } + lastDotDot = s == ".."; + } + if (dest.empty()) { + dest = "."; + } + return dest; +} + +GHC_INLINE path path::lexically_relative(const path& base) const +{ + if (root_name() != base.root_name() || is_absolute() != base.is_absolute() || (!has_root_directory() && base.has_root_directory())) { + return path(); + } + const_iterator a = begin(), b = base.begin(); + while (a != end() && b != base.end() && *a == *b) { + ++a; + ++b; + } + if (a == end() && b == base.end()) { + return path("."); + } + int count = 0; + for (const auto& element : input_iterator_range(b, base.end())) { + if (element != "." && element != "" && element != "..") { + ++count; + } + else if (element == "..") { + --count; + } + } + if (count < 0) { + return path(); + } + path result; + for (int i = 0; i < count; ++i) { + result /= ".."; + } + for (const auto& element : input_iterator_range(a, end())) { + result /= element; + } + return result; +} + +GHC_INLINE path path::lexically_proximate(const path& base) const +{ + path result = lexically_relative(base); + return result.empty() ? *this : result; +} + +//----------------------------------------------------------------------------- +// [fs.path.itr] iterators +GHC_INLINE path::iterator::iterator() {} + +GHC_INLINE path::iterator::iterator(const path& p, const impl_string_type::const_iterator& pos) + : _first(p._path.begin()) + , _last(p._path.end()) + , _prefix(_first + static_cast(p._prefixLength)) + , _root(p.has_root_directory() ? _first + static_cast(p._prefixLength + p.root_name_length()) : _last) + , _iter(pos) +{ + if (pos != _last) { + updateCurrent(); + } +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::increment(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + bool fromStart = i == _first || i == _prefix; + if (i != _last) { + if (fromStart && i == _first && _prefix > _first) { + i = _prefix; + } + else if (*i++ == preferred_separator) { + // we can only sit on a slash if it is a network name or a root + if (i != _last && *i == preferred_separator) { + if (fromStart && !(i + 1 != _last && *(i + 1) == preferred_separator)) { + // leadind double slashes detected, treat this and the + // following until a slash as one unit + i = std::find(++i, _last, preferred_separator); + } + else { + // skip redundant slashes + while (i != _last && *i == preferred_separator) { + ++i; + } + } + } + } + else { + if (fromStart && i != _last && *i == ':') { + ++i; + } + else { + i = std::find(i, _last, preferred_separator); + } + } + } + return i; +} + +GHC_INLINE path::impl_string_type::const_iterator path::iterator::decrement(const path::impl_string_type::const_iterator& pos) const +{ + path::impl_string_type::const_iterator i = pos; + if (i != _first) { + --i; + // if this is now the root slash or the trailing slash, we are done, + // else check for network name + if (i != _root && (pos != _last || *i != preferred_separator)) { +#ifdef GHC_OS_WINDOWS + static const impl_string_type seps = GHC_PLATFORM_LITERAL("\\:"); + i = std::find_first_of(std::reverse_iterator(i), std::reverse_iterator(_first), seps.begin(), seps.end()).base(); + if (i > _first && *i == ':') { + i++; + } +#else + i = std::find(std::reverse_iterator(i), std::reverse_iterator(_first), preferred_separator).base(); +#endif + // Now we have to check if this is a network name + if (i - _first == 2 && *_first == preferred_separator && *(_first + 1) == preferred_separator) { + i -= 2; + } + } + } + return i; +} + +GHC_INLINE void path::iterator::updateCurrent() +{ + if ((_iter == _last) || (_iter != _first && _iter != _last && (*_iter == preferred_separator && _iter != _root) && (_iter + 1 == _last))) { + _current.clear(); + } + else { + _current.assign(_iter, increment(_iter)); + } +} + +GHC_INLINE path::iterator& path::iterator::operator++() +{ + _iter = increment(_iter); + while (_iter != _last && // we didn't reach the end + _iter != _root && // this is not a root position + *_iter == preferred_separator && // we are on a separator + (_iter + 1) != _last // the slash is not the last char + ) { + ++_iter; + } + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator++(int) +{ + path::iterator i{*this}; + ++(*this); + return i; +} + +GHC_INLINE path::iterator& path::iterator::operator--() +{ + _iter = decrement(_iter); + updateCurrent(); + return *this; +} + +GHC_INLINE path::iterator path::iterator::operator--(int) +{ + auto i = *this; + --(*this); + return i; +} + +GHC_INLINE bool path::iterator::operator==(const path::iterator& other) const +{ + return _iter == other._iter; +} + +GHC_INLINE bool path::iterator::operator!=(const path::iterator& other) const +{ + return _iter != other._iter; +} + +GHC_INLINE path::iterator::reference path::iterator::operator*() const +{ + return _current; +} + +GHC_INLINE path::iterator::pointer path::iterator::operator->() const +{ + return &_current; +} + +GHC_INLINE path::iterator path::begin() const +{ + return iterator(*this, _path.begin()); +} + +GHC_INLINE path::iterator path::end() const +{ + return iterator(*this, _path.end()); +} + +//----------------------------------------------------------------------------- +// [fs.path.nonmember] path non-member functions +GHC_INLINE void swap(path& lhs, path& rhs) noexcept +{ + swap(lhs._path, rhs._path); +} + +GHC_INLINE size_t hash_value(const path& p) noexcept +{ + return std::hash()(p.generic_string()); +} + +#ifdef GHC_HAS_THREEWAY_COMP +GHC_INLINE std::strong_ordering operator<=>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <=> 0; +} +#endif + +GHC_INLINE bool operator==(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) == 0; +} + +GHC_INLINE bool operator!=(const path& lhs, const path& rhs) noexcept +{ + return !(lhs == rhs); +} + +GHC_INLINE bool operator<(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) < 0; +} + +GHC_INLINE bool operator<=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) <= 0; +} + +GHC_INLINE bool operator>(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) > 0; +} + +GHC_INLINE bool operator>=(const path& lhs, const path& rhs) noexcept +{ + return lhs.compare(rhs) >= 0; +} + +GHC_INLINE path operator/(const path& lhs, const path& rhs) +{ + path result(lhs); + result /= rhs; + return result; +} + +#endif // GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.path.io] path inserter and extractor +template +inline std::basic_ostream& operator<<(std::basic_ostream& os, const path& p) +{ + os << "\""; + auto ps = p.string(); + for (auto c : ps) { + if (c == '"' || c == '\\') { + os << '\\'; + } + os << c; + } + os << "\""; + return os; +} + +template +inline std::basic_istream& operator>>(std::basic_istream& is, path& p) +{ + std::basic_string tmp; + charT c; + is >> c; + if (c == '"') { + auto sf = is.flags(); + is >> std::noskipws; + while (is) { + auto c2 = is.get(); + if (is) { + if (c2 == '\\') { + c2 = is.get(); + if (is) { + tmp += static_cast(c2); + } + } + else if (c2 == '"') { + break; + } + else { + tmp += static_cast(c2); + } + } + } + if ((sf & std::ios_base::skipws) == std::ios_base::skipws) { + is >> std::skipws; + } + p = path(tmp); + } + else { + is >> tmp; + p = path(static_cast(c) + tmp); + } + return is; +} + +#ifdef GHC_EXPAND_IMPL + +//----------------------------------------------------------------------------- +// [fs.class.filesystem_error] Class filesystem_error +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) +{ +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.string() + "'"; + } +} + +GHC_INLINE filesystem_error::filesystem_error(const std::string& what_arg, const path& p1, const path& p2, std::error_code ec) + : std::system_error(ec, what_arg) + , _what_arg(what_arg) + , _ec(ec) + , _p1(p1) + , _p2(p2) +{ + if (!_p1.empty()) { + _what_arg += ": '" + _p1.string() + "'"; + } + if (!_p2.empty()) { + _what_arg += ", '" + _p2.string() + "'"; + } +} + +GHC_INLINE const path& filesystem_error::path1() const noexcept +{ + return _p1; +} + +GHC_INLINE const path& filesystem_error::path2() const noexcept +{ + return _p2; +} + +GHC_INLINE const char* filesystem_error::what() const noexcept +{ + return _what_arg.c_str(); +} + +//----------------------------------------------------------------------------- +// [fs.op.funcs] filesystem operations +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path absolute(const path& p) +{ + std::error_code ec; + path result = absolute(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path absolute(const path& p, std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (p.empty()) { + return absolute(current_path(ec), ec) / ""; + } + ULONG size = ::GetFullPathNameW(GHC_NATIVEWP(p), 0, 0, 0); + if (size) { + std::vector buf(size, 0); + ULONG s2 = GetFullPathNameW(GHC_NATIVEWP(p), size, buf.data(), nullptr); + if (s2 && s2 < size) { + path result = path(std::wstring(buf.data(), s2)); + if (p.filename() == ".") { + result /= "."; + } + return result; + } + } + ec = detail::make_system_error(); + return path(); +#else + path base = current_path(ec); + if (!ec) { + if (p.empty()) { + return base / p; + } + if (p.has_root_name()) { + if (p.has_root_directory()) { + return p; + } + else { + return p.root_name() / base.root_directory() / base.relative_path() / p.relative_path(); + } + } + else { + if (p.has_root_directory()) { + return base.root_name() / p; + } + else { + return base / p; + } + } + } + ec = detail::make_system_error(); + return path(); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path canonical(const path& p) +{ + std::error_code ec; + auto result = canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path canonical(const path& p, std::error_code& ec) +{ + if (p.empty()) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + path work = p.is_absolute() ? p : absolute(p, ec); + path result; + + auto fs = status(work, ec); + if (ec) { + return path(); + } + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + return path(); + } + bool redo; + do { + auto rootPathLen = work._prefixLength + work.root_name_length() + (work.has_root_directory() ? 1 : 0); + redo = false; + result.clear(); + for (auto pe : work) { + if (pe.empty() || pe == ".") { + continue; + } + else if (pe == "..") { + result = result.parent_path(); + continue; + } + else if ((result / pe).string().length() <= rootPathLen) { + result /= pe; + continue; + } + auto sls = symlink_status(result / pe, ec); + if (ec) { + return path(); + } + if (is_symlink(sls)) { + redo = true; + auto target = read_symlink(result / pe, ec); + if (ec) { + return path(); + } + if (target.is_absolute()) { + result = target; + continue; + } + else { + result /= target; + continue; + } + } + else { + result /= pe; + } + } + work = result; + } while (redo); + ec.clear(); + return result; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy(const path& from, const path& to) +{ + copy(from, to, copy_options::none); +} + +GHC_INLINE void copy(const path& from, const path& to, copy_options options) +{ + std::error_code ec; + copy(from, to, options, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void copy(const path& from, const path& to, std::error_code& ec) noexcept +{ + copy(from, to, copy_options::none, ec); +} + +GHC_INLINE void copy(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tec; + file_status fs_from, fs_to; + ec.clear(); + if ((options & (copy_options::skip_symlinks | copy_options::copy_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_from = symlink_status(from, ec); + } + else { + fs_from = status(from, ec); + } + if (!exists(fs_from)) { + if (!ec) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return; + } + if ((options & (copy_options::skip_symlinks | copy_options::create_symlinks)) != copy_options::none) { + fs_to = symlink_status(to, tec); + } + else { + fs_to = status(to, tec); + } + if (is_other(fs_from) || is_other(fs_to) || (is_directory(fs_from) && is_regular_file(fs_to)) || (exists(fs_to) && equivalent(from, to, ec))) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + else if (is_symlink(fs_from)) { + if ((options & copy_options::skip_symlinks) == copy_options::none) { + if (!exists(fs_to) && (options & copy_options::copy_symlinks) != copy_options::none) { + copy_symlink(from, to, ec); + } + else { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + } + } + } + else if (is_regular_file(fs_from)) { + if ((options & copy_options::directories_only) == copy_options::none) { + if ((options & copy_options::create_symlinks) != copy_options::none) { + create_symlink(from.is_absolute() ? from : canonical(from, ec), to, ec); + } +#ifndef GHC_OS_WEB + else if ((options & copy_options::create_hard_links) != copy_options::none) { + create_hard_link(from, to, ec); + } +#endif + else if (is_directory(fs_to)) { + copy_file(from, to / from.filename(), options, ec); + } + else { + copy_file(from, to, options, ec); + } + } + } +#ifdef LWG_2682_BEHAVIOUR + else if (is_directory(fs_from) && (options & copy_options::create_symlinks) != copy_options::none) { + ec = detail::make_error_code(detail::portable_error::is_a_directory); + } +#endif + else if (is_directory(fs_from) && (options == copy_options::none || (options & copy_options::recursive) != copy_options::none)) { + if (!exists(fs_to)) { + create_directory(to, from, ec); + if (ec) { + return; + } + } + for (auto iter = directory_iterator(from, ec); iter != directory_iterator(); iter.increment(ec)) { + if (!ec) { + copy(iter->path(), to / iter->path().filename(), options | static_cast(0x8000), ec); + } + if (ec) { + return; + } + } + } + return; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool copy_file(const path& from, const path& to) +{ + return copy_file(from, to, copy_options::none); +} + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options option) +{ + std::error_code ec; + auto result = copy_file(from, to, option, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } + return result; +} +#endif + +GHC_INLINE bool copy_file(const path& from, const path& to, std::error_code& ec) noexcept +{ + return copy_file(from, to, copy_options::none, ec); +} + +GHC_INLINE bool copy_file(const path& from, const path& to, copy_options options, std::error_code& ec) noexcept +{ + std::error_code tecf, tect; + auto sf = status(from, tecf); + auto st = status(to, tect); + bool overwrite = false; + ec.clear(); + if (!is_regular_file(sf)) { + ec = tecf; + return false; + } + if (exists(st) && (!is_regular_file(st) || equivalent(from, to, ec) || (options & (copy_options::skip_existing | copy_options::overwrite_existing | copy_options::update_existing)) == copy_options::none)) { + ec = tect ? tect : detail::make_error_code(detail::portable_error::exists); + return false; + } + if (exists(st)) { + if ((options & copy_options::update_existing) == copy_options::update_existing) { + auto from_time = last_write_time(from, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + auto to_time = last_write_time(to, ec); + if (ec) { + ec = detail::make_system_error(); + return false; + } + if (from_time <= to_time) { + return false; + } + } + overwrite = true; + } +#ifdef GHC_OS_WINDOWS + if (!::CopyFileW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), !overwrite)) { + ec = detail::make_system_error(); + return false; + } + return true; +#else + std::vector buffer(16384, '\0'); + int in = -1, out = -1; + if ((in = ::open(from.c_str(), O_RDONLY)) < 0) { + ec = detail::make_system_error(); + return false; + } + int mode = O_CREAT | O_WRONLY | O_TRUNC; + if (!overwrite) { + mode |= O_EXCL; + } + if ((out = ::open(to.c_str(), mode, static_cast(sf.permissions() & perms::all))) < 0) { + ec = detail::make_system_error(); + ::close(in); + return false; + } + ssize_t br, bw; + while ((br = ::read(in, buffer.data(), buffer.size())) > 0) { + ssize_t offset = 0; + do { + if ((bw = ::write(out, buffer.data() + offset, static_cast(br))) > 0) { + br -= bw; + offset += bw; + } + else if (bw < 0) { + ec = detail::make_system_error(); + ::close(in); + ::close(out); + return false; + } + } while (br); + } + ::close(in); + ::close(out); + return true; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink) +{ + std::error_code ec; + copy_symlink(existing_symlink, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), existing_symlink, new_symlink, ec); + } +} +#endif + +GHC_INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, std::error_code& ec) noexcept +{ + ec.clear(); + auto to = read_symlink(existing_symlink, ec); + if (!ec) { + if (exists(to, ec) && is_directory(to, ec)) { + create_directory_symlink(to, new_symlink, ec); + } + else { + create_symlink(to, new_symlink, ec); + } + } +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directories(const path& p) +{ + std::error_code ec; + auto result = create_directories(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directories(const path& p, std::error_code& ec) noexcept +{ + path current; + ec.clear(); + bool didCreate = false; + auto rootPathLen = p._prefixLength + p.root_name_length() + (p.has_root_directory() ? 1 : 0); + current = p.native().substr(0, rootPathLen); + path folders(p._path.substr(rootPathLen)); + for (path::string_type part : folders) { + current /= part; + std::error_code tec; + auto fs = status(current, tec); + if (tec && fs.type() != file_type::not_found) { + ec = tec; + return false; + } + if (!exists(fs)) { + create_directory(current, ec); + if (ec) { + std::error_code tmp_ec; + if (is_directory(current, tmp_ec)) { + ec.clear(); + } + else { + return false; + } + } + didCreate = true; + } +#ifndef LWG_2935_BEHAVIOUR + else if (!is_directory(fs)) { + ec = detail::make_error_code(detail::portable_error::exists); + return false; + } +#endif + } + return didCreate; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p) +{ + std::error_code ec; + auto result = create_directory(p, path(), ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, std::error_code& ec) noexcept +{ + return create_directory(p, path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool create_directory(const path& p, const path& attributes) +{ + std::error_code ec; + auto result = create_directory(p, attributes, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool create_directory(const path& p, const path& attributes, std::error_code& ec) noexcept +{ + std::error_code tec; + ec.clear(); + auto fs = status(p, tec); +#ifdef LWG_2935_BEHAVIOUR + if (status_known(fs) && exists(fs)) { + return false; + } +#else + if (status_known(fs) && exists(fs) && is_directory(fs)) { + return false; + } +#endif +#ifdef GHC_OS_WINDOWS + if (!attributes.empty()) { + if (!::CreateDirectoryExW(GHC_NATIVEWP(attributes), GHC_NATIVEWP(p), NULL)) { + ec = detail::make_system_error(); + return false; + } + } + else if (!::CreateDirectoryW(GHC_NATIVEWP(p), NULL)) { + ec = detail::make_system_error(); + return false; + } +#else + ::mode_t attribs = static_cast(perms::all); + if (!attributes.empty()) { + struct ::stat fileStat; + if (::stat(attributes.c_str(), &fileStat) != 0) { + ec = detail::make_system_error(); + return false; + } + attribs = fileStat.st_mode; + } + if (::mkdir(p.c_str(), attribs) != 0) { + ec = detail::make_system_error(); + return false; + } +#endif + return true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_directory_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_directory_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, true, ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link) +{ + std::error_code ec; + create_hard_link(to, new_hard_link, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_hard_link, ec); + } +} +#endif + +GHC_INLINE void create_hard_link(const path& to, const path& new_hard_link, std::error_code& ec) noexcept +{ + detail::create_hardlink(to, new_hard_link, ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void create_symlink(const path& to, const path& new_symlink) +{ + std::error_code ec; + create_symlink(to, new_symlink, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), to, new_symlink, ec); + } +} +#endif + +GHC_INLINE void create_symlink(const path& to, const path& new_symlink, std::error_code& ec) noexcept +{ + detail::create_symlink(to, new_symlink, false, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path current_path() +{ + std::error_code ec; + auto result = current_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path current_path(std::error_code& ec) +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + DWORD pathlen = ::GetCurrentDirectoryW(0, 0); + std::unique_ptr buffer(new wchar_t[size_t(pathlen) + 1]); + if (::GetCurrentDirectoryW(pathlen, buffer.get()) == 0) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer.get()), path::native_format); +#else + size_t pathlen = static_cast(std::max(int(::pathconf(".", _PC_PATH_MAX)), int(PATH_MAX))); + std::unique_ptr buffer(new char[pathlen + 1]); + if (::getcwd(buffer.get(), pathlen) == nullptr) { + ec = detail::make_system_error(); + return path(); + } + return path(buffer.get()); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void current_path(const path& p) +{ + std::error_code ec; + current_path(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void current_path(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (!::SetCurrentDirectoryW(GHC_NATIVEWP(p))) { + ec = detail::make_system_error(); + } +#else + if (::chdir(p.string().c_str()) == -1) { + ec = detail::make_system_error(); + } +#endif +} + +GHC_INLINE bool exists(file_status s) noexcept +{ + return status_known(s) && s.type() != file_type::not_found; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool exists(const path& p) +{ + return exists(status(p)); +} +#endif + +GHC_INLINE bool exists(const path& p, std::error_code& ec) noexcept +{ + file_status s = status(p, ec); + if (status_known(s)) { + ec.clear(); + } + return exists(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool equivalent(const path& p1, const path& p2) +{ + std::error_code ec; + bool result = equivalent(p1, p2, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p1, p2, ec); + } + return result; +} +#endif + +GHC_INLINE bool equivalent(const path& p1, const path& p2, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + detail::unique_handle file1(::CreateFileW(GHC_NATIVEWP(p1), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + auto e1 = ::GetLastError(); + detail::unique_handle file2(::CreateFileW(GHC_NATIVEWP(p2), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + if (!file1 || !file2) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); +#else + if (file1 == file2) { + ec = detail::make_system_error(e1 ? e1 : ::GetLastError()); + } +#endif + return false; + } + BY_HANDLE_FILE_INFORMATION inf1, inf2; + if (!::GetFileInformationByHandle(file1.get(), &inf1)) { + ec = detail::make_system_error(); + return false; + } + if (!::GetFileInformationByHandle(file2.get(), &inf2)) { + ec = detail::make_system_error(); + return false; + } + return inf1.ftLastWriteTime.dwLowDateTime == inf2.ftLastWriteTime.dwLowDateTime && inf1.ftLastWriteTime.dwHighDateTime == inf2.ftLastWriteTime.dwHighDateTime && inf1.nFileIndexHigh == inf2.nFileIndexHigh && inf1.nFileIndexLow == inf2.nFileIndexLow && + inf1.nFileSizeHigh == inf2.nFileSizeHigh && inf1.nFileSizeLow == inf2.nFileSizeLow && inf1.dwVolumeSerialNumber == inf2.dwVolumeSerialNumber; +#else + struct ::stat s1, s2; + auto rc1 = ::stat(p1.c_str(), &s1); + auto e1 = errno; + auto rc2 = ::stat(p2.c_str(), &s2); + if (rc1 || rc2) { +#ifdef LWG_2937_BEHAVIOUR + ec = detail::make_system_error(e1 ? e1 : errno); +#else + if (rc1 && rc2) { + ec = detail::make_system_error(e1 ? e1 : errno); + } +#endif + return false; + } + return s1.st_dev == s2.st_dev && s1.st_ino == s2.st_ino && s1.st_size == s2.st_size && s1.st_mtime == s2.st_mtime; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t file_size(const path& p) +{ + std::error_code ec; + auto result = file_size(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t file_size(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + WIN32_FILE_ATTRIBUTE_DATA attr; + if (!GetFileAttributesExW(GHC_NATIVEWP(p), GetFileExInfoStandard, &attr)) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; +#else + struct ::stat fileStat; + if (::stat(p.c_str(), &fileStat) == -1) { + ec = detail::make_system_error(); + return static_cast(-1); + } + return static_cast(fileStat.st_size); +#endif +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t hard_link_count(const path& p) +{ + std::error_code ec; + auto result = hard_link_count(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t hard_link_count(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + uintmax_t result = static_cast(-1); + detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0)); + BY_HANDLE_FILE_INFORMATION inf; + if (!file) { + ec = detail::make_system_error(); + } + else { + if (!::GetFileInformationByHandle(file.get(), &inf)) { + ec = detail::make_system_error(); + } + else { + result = inf.nNumberOfLinks; + } + } + return result; +#else + uintmax_t result = 0; + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, &result, nullptr); + if (fs.type() == file_type::not_found) { + ec = detail::make_error_code(detail::portable_error::not_found); + } + return ec ? static_cast(-1) : result; +#endif +} +#endif + +GHC_INLINE bool is_block_file(file_status s) noexcept +{ + return s.type() == file_type::block; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_block_file(const path& p) +{ + return is_block_file(status(p)); +} +#endif + +GHC_INLINE bool is_block_file(const path& p, std::error_code& ec) noexcept +{ + return is_block_file(status(p, ec)); +} + +GHC_INLINE bool is_character_file(file_status s) noexcept +{ + return s.type() == file_type::character; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_character_file(const path& p) +{ + return is_character_file(status(p)); +} +#endif + +GHC_INLINE bool is_character_file(const path& p, std::error_code& ec) noexcept +{ + return is_character_file(status(p, ec)); +} + +GHC_INLINE bool is_directory(file_status s) noexcept +{ + return s.type() == file_type::directory; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_directory(const path& p) +{ + return is_directory(status(p)); +} +#endif + +GHC_INLINE bool is_directory(const path& p, std::error_code& ec) noexcept +{ + return is_directory(status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_empty(const path& p) +{ + if (is_directory(p)) { + return directory_iterator(p) == directory_iterator(); + } + else { + return file_size(p) == 0; + } +} +#endif + +GHC_INLINE bool is_empty(const path& p, std::error_code& ec) noexcept +{ + auto fs = status(p, ec); + if (ec) { + return false; + } + if (is_directory(fs)) { + directory_iterator iter(p, ec); + if (ec) { + return false; + } + return iter == directory_iterator(); + } + else { + auto sz = file_size(p, ec); + if (ec) { + return false; + } + return sz == 0; + } +} + +GHC_INLINE bool is_fifo(file_status s) noexcept +{ + return s.type() == file_type::fifo; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_fifo(const path& p) +{ + return is_fifo(status(p)); +} +#endif + +GHC_INLINE bool is_fifo(const path& p, std::error_code& ec) noexcept +{ + return is_fifo(status(p, ec)); +} + +GHC_INLINE bool is_other(file_status s) noexcept +{ + return exists(s) && !is_regular_file(s) && !is_directory(s) && !is_symlink(s); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_other(const path& p) +{ + return is_other(status(p)); +} +#endif + +GHC_INLINE bool is_other(const path& p, std::error_code& ec) noexcept +{ + return is_other(status(p, ec)); +} + +GHC_INLINE bool is_regular_file(file_status s) noexcept +{ + return s.type() == file_type::regular; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_regular_file(const path& p) +{ + return is_regular_file(status(p)); +} +#endif + +GHC_INLINE bool is_regular_file(const path& p, std::error_code& ec) noexcept +{ + return is_regular_file(status(p, ec)); +} + +GHC_INLINE bool is_socket(file_status s) noexcept +{ + return s.type() == file_type::socket; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_socket(const path& p) +{ + return is_socket(status(p)); +} +#endif + +GHC_INLINE bool is_socket(const path& p, std::error_code& ec) noexcept +{ + return is_socket(status(p, ec)); +} + +GHC_INLINE bool is_symlink(file_status s) noexcept +{ + return s.type() == file_type::symlink; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool is_symlink(const path& p) +{ + return is_symlink(symlink_status(p)); +} +#endif + +GHC_INLINE bool is_symlink(const path& p, std::error_code& ec) noexcept +{ + return is_symlink(symlink_status(p, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type last_write_time(const path& p) +{ + std::error_code ec; + auto result = last_write_time(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_time_type last_write_time(const path& p, std::error_code& ec) noexcept +{ + time_t result = 0; + ec.clear(); + file_status fs = detail::status_ex(p, ec, nullptr, nullptr, nullptr, &result); + return ec ? (file_time_type::min)() : std::chrono::system_clock::from_time_t(result); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void last_write_time(const path& p, file_time_type new_time) +{ + std::error_code ec; + last_write_time(p, new_time, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void last_write_time(const path& p, file_time_type new_time, std::error_code& ec) noexcept +{ + ec.clear(); + auto d = new_time.time_since_epoch(); +#ifdef GHC_OS_WINDOWS + detail::unique_handle file(::CreateFileW(GHC_NATIVEWP(p), FILE_WRITE_ATTRIBUTES, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL)); + FILETIME ft; + auto tt = std::chrono::duration_cast(d).count() * 10 + 116444736000000000; + ft.dwLowDateTime = static_cast(tt); + ft.dwHighDateTime = static_cast(tt >> 32); + if (!::SetFileTime(file.get(), 0, 0, &ft)) { + ec = detail::make_system_error(); + } +#elif defined(GHC_OS_MACOS) +#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED +#if __MAC_OS_X_VERSION_MIN_REQUIRED < 101300 + struct ::stat fs; + if (::stat(p.c_str(), &fs) == 0) { + struct ::timeval tv[2]; + tv[0].tv_sec = fs.st_atimespec.tv_sec; + tv[0].tv_usec = static_cast(fs.st_atimespec.tv_nsec / 1000); + tv[1].tv_sec = std::chrono::duration_cast(d).count(); + tv[1].tv_usec = static_cast(std::chrono::duration_cast(d).count() % 1000000); + if (::utimes(p.c_str(), tv) == 0) { + return; + } + } + ec = detail::make_system_error(); + return; +#else + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = std::chrono::duration_cast(d).count(); + times[1].tv_nsec = 0; // std::chrono::duration_cast(d).count() % 1000000000; + if (::utimensat(AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { + ec = detail::make_system_error(); + } + return; +#endif +#endif +#else +#ifndef UTIME_OMIT +#define UTIME_OMIT ((1l << 30) - 2l) +#endif + struct ::timespec times[2]; + times[0].tv_sec = 0; + times[0].tv_nsec = UTIME_OMIT; + times[1].tv_sec = static_cast(std::chrono::duration_cast(d).count()); + times[1].tv_nsec = static_cast(std::chrono::duration_cast(d).count() % 1000000000); +#if defined(__ANDROID_API__) && __ANDROID_API__ < 12 + if (syscall(__NR_utimensat, AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#else + if (::utimensat((int)AT_FDCWD, p.c_str(), times, AT_SYMLINK_NOFOLLOW) != 0) { +#endif + ec = detail::make_system_error(); + } + return; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts) +{ + std::error_code ec; + permissions(p, prms, opts, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void permissions(const path& p, perms prms, std::error_code& ec) noexcept +{ + permissions(p, prms, perm_options::replace, ec); +} + +GHC_INLINE void permissions(const path& p, perms prms, perm_options opts, std::error_code& ec) noexcept +{ + if (static_cast(opts & (perm_options::replace | perm_options::add | perm_options::remove)) == 0) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return; + } + auto fs = symlink_status(p, ec); + if ((opts & perm_options::replace) != perm_options::replace) { + if ((opts & perm_options::add) == perm_options::add) { + prms = fs.permissions() | prms; + } + else { + prms = fs.permissions() & ~prms; + } + } +#ifdef GHC_OS_WINDOWS +#ifdef __GNUC__ + auto oldAttr = GetFileAttributesW(GHC_NATIVEWP(p)); + if (oldAttr != INVALID_FILE_ATTRIBUTES) { + DWORD newAttr = ((prms & perms::owner_write) == perms::owner_write) ? oldAttr & ~(static_cast(FILE_ATTRIBUTE_READONLY)) : oldAttr | FILE_ATTRIBUTE_READONLY; + if (oldAttr == newAttr || SetFileAttributesW(GHC_NATIVEWP(p), newAttr)) { + return; + } + } + ec = detail::make_system_error(); +#else + int mode = 0; + if ((prms & perms::owner_read) == perms::owner_read) { + mode |= _S_IREAD; + } + if ((prms & perms::owner_write) == perms::owner_write) { + mode |= _S_IWRITE; + } + if (::_wchmod(p.wstring().c_str(), mode) != 0) { + ec = detail::make_system_error(); + } +#endif +#else + if ((opts & perm_options::nofollow) != perm_options::nofollow) { + if (::chmod(p.c_str(), static_cast(prms)) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, std::error_code& ec) +{ + auto cp = current_path(ec); + if (!ec) { + return proximate(p, cp, ec); + } + return path(); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path proximate(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_proximate(weakly_canonical(base)); +} +#endif + +GHC_INLINE path proximate(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_proximate(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path read_symlink(const path& p) +{ + std::error_code ec; + auto result = read_symlink(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path read_symlink(const path& p, std::error_code& ec) +{ + file_status fs = symlink_status(p, ec); + if (fs.type() != file_type::symlink) { + ec = detail::make_error_code(detail::portable_error::invalid_argument); + return path(); + } + auto result = detail::resolveSymlink(p, ec); + return ec ? path() : result; +} + +GHC_INLINE path relative(const path& p, std::error_code& ec) +{ + return relative(p, current_path(ec), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path relative(const path& p, const path& base) +{ + return weakly_canonical(p).lexically_relative(weakly_canonical(base)); +} +#endif + +GHC_INLINE path relative(const path& p, const path& base, std::error_code& ec) +{ + return weakly_canonical(p, ec).lexically_relative(weakly_canonical(base, ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool remove(const path& p) +{ + std::error_code ec; + auto result = remove(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE bool remove(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS +#ifdef GHC_USE_WCHAR_T + auto cstr = p.c_str(); +#else + std::wstring np = detail::fromUtf8(p.u8string()); + auto cstr = np.c_str(); +#endif + DWORD attr = GetFileAttributesW(cstr); + if (attr == INVALID_FILE_ATTRIBUTES) { + auto error = ::GetLastError(); + if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) { + return false; + } + ec = detail::make_system_error(error); + } + else if (attr & FILE_ATTRIBUTE_READONLY) { + auto new_attr = attr & ~static_cast(FILE_ATTRIBUTE_READONLY); + if (!SetFileAttributesW(cstr, new_attr)) { + auto error = ::GetLastError(); + ec = detail::make_system_error(error); + } + } + if (!ec) { + if (attr & FILE_ATTRIBUTE_DIRECTORY) { + if (!RemoveDirectoryW(cstr)) { + ec = detail::make_system_error(); + } + } + else { + if (!DeleteFileW(cstr)) { + ec = detail::make_system_error(); + } + } + } +#else + if (::remove(p.c_str()) == -1) { + auto error = errno; + if (error == ENOENT) { + return false; + } + ec = detail::make_system_error(); + } +#endif + return ec ? false : true; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t remove_all(const path& p) +{ + std::error_code ec; + auto result = remove_all(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE uintmax_t remove_all(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); + uintmax_t count = 0; + if (p == "/") { + ec = detail::make_error_code(detail::portable_error::not_supported); + return static_cast(-1); + } + std::error_code tec; + auto fs = symlink_status(p, tec); + if (exists(fs) && is_directory(fs)) { + for (auto iter = directory_iterator(p, ec); iter != directory_iterator(); iter.increment(ec)) { + if (ec && !detail::is_not_found_error(ec)) { + break; + } + bool is_symlink_result = iter->is_symlink(ec); + if (ec) + return static_cast(-1); + if (!is_symlink_result && iter->is_directory(ec)) { + count += remove_all(iter->path(), ec); + if (ec) { + return static_cast(-1); + } + } + else { + if (!ec) { + remove(iter->path(), ec); + } + if (ec) { + return static_cast(-1); + } + ++count; + } + } + } + if (!ec) { + if (remove(p, ec)) { + ++count; + } + } + if (ec) { + return static_cast(-1); + } + return count; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void rename(const path& from, const path& to) +{ + std::error_code ec; + rename(from, to, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), from, to, ec); + } +} +#endif + +GHC_INLINE void rename(const path& from, const path& to, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + if (from != to) { + if (!MoveFileExW(GHC_NATIVEWP(from), GHC_NATIVEWP(to), (DWORD)MOVEFILE_REPLACE_EXISTING)) { + ec = detail::make_system_error(); + } + } +#else + if (from != to) { + if (::rename(from.c_str(), to.c_str()) != 0) { + ec = detail::make_system_error(); + } + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void resize_file(const path& p, uintmax_t size) +{ + std::error_code ec; + resize_file(p, size, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } +} +#endif + +GHC_INLINE void resize_file(const path& p, uintmax_t size, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + LARGE_INTEGER lisize; + lisize.QuadPart = static_cast(size); + if (lisize.QuadPart < 0) { +#ifdef ERROR_FILE_TOO_LARGE + ec = detail::make_system_error(ERROR_FILE_TOO_LARGE); +#else + ec = detail::make_system_error(223); +#endif + return; + } + detail::unique_handle file(CreateFileW(GHC_NATIVEWP(p), GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL)); + if (!file) { + ec = detail::make_system_error(); + } + else if (SetFilePointerEx(file.get(), lisize, NULL, FILE_BEGIN) == 0 || SetEndOfFile(file.get()) == 0) { + ec = detail::make_system_error(); + } +#else + if (::truncate(p.c_str(), static_cast(size)) != 0) { + ec = detail::make_system_error(); + } +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE space_info space(const path& p) +{ + std::error_code ec; + auto result = space(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE space_info space(const path& p, std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + ULARGE_INTEGER freeBytesAvailableToCaller = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfBytes = {{ 0, 0 }}; + ULARGE_INTEGER totalNumberOfFreeBytes = {{ 0, 0 }}; + if (!GetDiskFreeSpaceExW(GHC_NATIVEWP(p), &freeBytesAvailableToCaller, &totalNumberOfBytes, &totalNumberOfFreeBytes)) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(totalNumberOfBytes.QuadPart), static_cast(totalNumberOfFreeBytes.QuadPart), static_cast(freeBytesAvailableToCaller.QuadPart)}; +#else + struct ::statvfs sfs; + if (::statvfs(p.c_str(), &sfs) != 0) { + ec = detail::make_system_error(); + return {static_cast(-1), static_cast(-1), static_cast(-1)}; + } + return {static_cast(sfs.f_blocks) * static_cast(sfs.f_frsize), static_cast(sfs.f_bfree) * static_cast(sfs.f_frsize), static_cast(sfs.f_bavail) * static_cast(sfs.f_frsize)}; +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status status(const path& p) +{ + std::error_code ec; + auto result = status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE file_status status(const path& p, std::error_code& ec) noexcept +{ + return detail::status_ex(p, ec); +} + +GHC_INLINE bool status_known(file_status s) noexcept +{ + return s.type() != file_type::none; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status symlink_status(const path& p) +{ + std::error_code ec; + auto result = symlink_status(p, ec); + if (result.type() == file_type::none) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE file_status symlink_status(const path& p, std::error_code& ec) noexcept +{ + return detail::symlink_status_ex(p, ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path temp_directory_path() +{ + std::error_code ec; + path result = temp_directory_path(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), ec); + } + return result; +} +#endif + +GHC_INLINE path temp_directory_path(std::error_code& ec) noexcept +{ + ec.clear(); +#ifdef GHC_OS_WINDOWS + wchar_t buffer[512]; + auto rc = GetTempPathW(511, buffer); + if (!rc || rc > 511) { + ec = detail::make_system_error(); + return path(); + } + return path(std::wstring(buffer)); +#else + static const char* temp_vars[] = {"TMPDIR", "TMP", "TEMP", "TEMPDIR", nullptr}; + const char* temp_path = nullptr; + for (auto temp_name = temp_vars; *temp_name != nullptr; ++temp_name) { + temp_path = std::getenv(*temp_name); + if (temp_path) { + return path(temp_path); + } + } + return path("/tmp"); +#endif +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE path weakly_canonical(const path& p) +{ + std::error_code ec; + auto result = weakly_canonical(p, ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), p, ec); + } + return result; +} +#endif + +GHC_INLINE path weakly_canonical(const path& p, std::error_code& ec) noexcept +{ + path result; + ec.clear(); + bool scan = true; + for (auto pe : p) { + if (scan) { + std::error_code tec; + if (exists(result / pe, tec)) { + result /= pe; + } + else { + if (ec) { + return path(); + } + scan = false; + if (!result.empty()) { + result = canonical(result, ec) / pe; + if (ec) { + break; + } + } + else { + result /= pe; + } + } + } + else { + result /= pe; + } + } + if (scan) { + if (!result.empty()) { + result = canonical(result, ec); + } + } + return ec ? path() : result.lexically_normal(); +} + +//----------------------------------------------------------------------------- +// [fs.class.file_status] class file_status +// [fs.file_status.cons] constructors and destructor +GHC_INLINE file_status::file_status() noexcept + : file_status(file_type::none) +{ +} + +GHC_INLINE file_status::file_status(file_type ft, perms prms) noexcept + : _type(ft) + , _perms(prms) +{ +} + +GHC_INLINE file_status::file_status(const file_status& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::file_status(file_status&& other) noexcept + : _type(other._type) + , _perms(other._perms) +{ +} + +GHC_INLINE file_status::~file_status() {} + +// assignments: +GHC_INLINE file_status& file_status::operator=(const file_status& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +GHC_INLINE file_status& file_status::operator=(file_status&& rhs) noexcept +{ + _type = rhs._type; + _perms = rhs._perms; + return *this; +} + +// [fs.file_status.mods] modifiers +GHC_INLINE void file_status::type(file_type ft) noexcept +{ + _type = ft; +} + +GHC_INLINE void file_status::permissions(perms prms) noexcept +{ + _perms = prms; +} + +// [fs.file_status.obs] observers +GHC_INLINE file_type file_status::type() const noexcept +{ + return _type; +} + +GHC_INLINE perms file_status::permissions() const noexcept +{ + return _perms; +} + +//----------------------------------------------------------------------------- +// [fs.class.directory_entry] class directory_entry +// [fs.dir.entry.cons] constructors and destructor +// directory_entry::directory_entry() noexcept = default; +// directory_entry::directory_entry(const directory_entry&) = default; +// directory_entry::directory_entry(directory_entry&&) noexcept = default; +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p) + : _path(p) + , _file_size(static_cast(-1)) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(static_cast(-1)) +#endif + , _last_write_time(0) +{ + refresh(); +} +#endif + +GHC_INLINE directory_entry::directory_entry(const filesystem::path& p, std::error_code& ec) + : _path(p) + , _file_size(static_cast(-1)) +#ifndef GHC_OS_WINDOWS + , _hard_link_count(static_cast(-1)) +#endif + , _last_write_time(0) +{ + refresh(ec); +} + +GHC_INLINE directory_entry::~directory_entry() {} + +// assignments: +// directory_entry& directory_entry::operator=(const directory_entry&) = default; +// directory_entry& directory_entry::operator=(directory_entry&&) noexcept = default; + +// [fs.dir.entry.mods] directory_entry modifiers +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::assign(const filesystem::path& p) +{ + _path = p; + refresh(); +} +#endif + +GHC_INLINE void directory_entry::assign(const filesystem::path& p, std::error_code& ec) +{ + _path = p; + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p) +{ + _path.replace_filename(p); + refresh(); +} +#endif + +GHC_INLINE void directory_entry::replace_filename(const filesystem::path& p, std::error_code& ec) +{ + _path.replace_filename(p); + refresh(ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void directory_entry::refresh() +{ + std::error_code ec; + refresh(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _path, ec); + } +} +#endif + +GHC_INLINE void directory_entry::refresh(std::error_code& ec) noexcept +{ +#ifdef GHC_OS_WINDOWS + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, nullptr, &_last_write_time); +#else + _status = detail::status_ex(_path, ec, &_symlink_status, &_file_size, &_hard_link_count, &_last_write_time); +#endif +} + +// [fs.dir.entry.obs] directory_entry observers +GHC_INLINE const filesystem::path& directory_entry::path() const noexcept +{ + return _path; +} + +GHC_INLINE directory_entry::operator const filesystem::path&() const noexcept +{ + return _path; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_type directory_entry::status_file_type() const +{ + return _status.type() != file_type::none ? _status.type() : filesystem::status(path()).type(); +} +#endif + +GHC_INLINE file_type directory_entry::status_file_type(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none) { + ec.clear(); + return _status.type(); + } + return filesystem::status(path(), ec).type(); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::exists() const +{ + return status_file_type() != file_type::not_found; +} +#endif + +GHC_INLINE bool directory_entry::exists(std::error_code& ec) const noexcept +{ + return status_file_type(ec) != file_type::not_found; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_block_file() const +{ + return status_file_type() == file_type::block; +} +#endif +GHC_INLINE bool directory_entry::is_block_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::block; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_character_file() const +{ + return status_file_type() == file_type::character; +} +#endif + +GHC_INLINE bool directory_entry::is_character_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::character; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_directory() const +{ + return status_file_type() == file_type::directory; +} +#endif + +GHC_INLINE bool directory_entry::is_directory(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::directory; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_fifo() const +{ + return status_file_type() == file_type::fifo; +} +#endif + +GHC_INLINE bool directory_entry::is_fifo(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::fifo; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_other() const +{ + auto ft = status_file_type(); + return ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(); +} +#endif + +GHC_INLINE bool directory_entry::is_other(std::error_code& ec) const noexcept +{ + auto ft = status_file_type(ec); + bool other = ft != file_type::none && ft != file_type::not_found && ft != file_type::regular && ft != file_type::directory && !is_symlink(ec); + return !ec && other; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_regular_file() const +{ + return status_file_type() == file_type::regular; +} +#endif + +GHC_INLINE bool directory_entry::is_regular_file(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::regular; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_socket() const +{ + return status_file_type() == file_type::socket; +} +#endif + +GHC_INLINE bool directory_entry::is_socket(std::error_code& ec) const noexcept +{ + return status_file_type(ec) == file_type::socket; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE bool directory_entry::is_symlink() const +{ + return _symlink_status.type() != file_type::none ? _symlink_status.type() == file_type::symlink : filesystem::is_symlink(symlink_status()); +} +#endif + +GHC_INLINE bool directory_entry::is_symlink(std::error_code& ec) const noexcept +{ + if (_symlink_status.type() != file_type::none) { + ec.clear(); + return _symlink_status.type() == file_type::symlink; + } + return filesystem::is_symlink(symlink_status(ec)); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::file_size() const +{ + if (_file_size != static_cast(-1)) { + return _file_size; + } + return filesystem::file_size(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::file_size(std::error_code& ec) const noexcept +{ + if (_file_size != static_cast(-1)) { + ec.clear(); + return _file_size; + } + return filesystem::file_size(path(), ec); +} + +#ifndef GHC_OS_WEB +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE uintmax_t directory_entry::hard_link_count() const +{ +#ifndef GHC_OS_WINDOWS + if (_hard_link_count != static_cast(-1)) { + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path()); +} +#endif + +GHC_INLINE uintmax_t directory_entry::hard_link_count(std::error_code& ec) const noexcept +{ +#ifndef GHC_OS_WINDOWS + if (_hard_link_count != static_cast(-1)) { + ec.clear(); + return _hard_link_count; + } +#endif + return filesystem::hard_link_count(path(), ec); +} +#endif + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_time_type directory_entry::last_write_time() const +{ + if (_last_write_time != 0) { + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path()); +} +#endif + +GHC_INLINE file_time_type directory_entry::last_write_time(std::error_code& ec) const noexcept +{ + if (_last_write_time != 0) { + ec.clear(); + return std::chrono::system_clock::from_time_t(_last_write_time); + } + return filesystem::last_write_time(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::status() const +{ + if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { + return _status; + } + return filesystem::status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::status(std::error_code& ec) const noexcept +{ + if (_status.type() != file_type::none && _status.permissions() != perms::unknown) { + ec.clear(); + return _status; + } + return filesystem::status(path(), ec); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE file_status directory_entry::symlink_status() const +{ + if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { + return _symlink_status; + } + return filesystem::symlink_status(path()); +} +#endif + +GHC_INLINE file_status directory_entry::symlink_status(std::error_code& ec) const noexcept +{ + if (_symlink_status.type() != file_type::none && _symlink_status.permissions() != perms::unknown) { + ec.clear(); + return _symlink_status; + } + return filesystem::symlink_status(path(), ec); +} + +#ifdef GHC_HAS_THREEWAY_COMP +GHC_INLINE std::strong_ordering directory_entry::operator<=>(const directory_entry& rhs) const noexcept +{ + return _path <=> rhs._path; +} +#endif + +GHC_INLINE bool directory_entry::operator<(const directory_entry& rhs) const noexcept +{ + return _path < rhs._path; +} + +GHC_INLINE bool directory_entry::operator==(const directory_entry& rhs) const noexcept +{ + return _path == rhs._path; +} + +GHC_INLINE bool directory_entry::operator!=(const directory_entry& rhs) const noexcept +{ + return _path != rhs._path; +} + +GHC_INLINE bool directory_entry::operator<=(const directory_entry& rhs) const noexcept +{ + return _path <= rhs._path; +} + +GHC_INLINE bool directory_entry::operator>(const directory_entry& rhs) const noexcept +{ + return _path > rhs._path; +} + +GHC_INLINE bool directory_entry::operator>=(const directory_entry& rhs) const noexcept +{ + return _path >= rhs._path; +} + +//----------------------------------------------------------------------------- +// [fs.class.directory_iterator] class directory_iterator + +#ifdef GHC_OS_WINDOWS +class directory_iterator::impl +{ +public: + impl(const path& p, directory_options options) + : _base(p) + , _options(options) + , _dirHandle(INVALID_HANDLE_VALUE) + { + if (!_base.empty()) { + ZeroMemory(&_findData, sizeof(WIN32_FIND_DATAW)); + if ((_dirHandle = FindFirstFileW(GHC_NATIVEWP((_base / "*")), &_findData)) != INVALID_HANDLE_VALUE) { + if (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L"..") { + increment(_ec); + } + else { + _dir_entry._path = _base / std::wstring(_findData.cFileName); + copyToDirEntry(_ec); + } + } + else { + auto error = ::GetLastError(); + _base = filesystem::path(); + if (error != ERROR_ACCESS_DENIED || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + } + } + void increment(std::error_code& ec) + { + if (_dirHandle != INVALID_HANDLE_VALUE) { + do { + if (FindNextFileW(_dirHandle, &_findData)) { + _dir_entry._path = _base; +#ifdef GHC_USE_WCHAR_T + _dir_entry._path.append_name(_findData.cFileName); +#else +#ifdef GHC_RAISE_UNICODE_ERRORS + try { + _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); + } + catch (filesystem_error& fe) { + ec = fe.code(); + return; + } +#else + _dir_entry._path.append_name(detail::toUtf8(_findData.cFileName).c_str()); +#endif +#endif + copyToDirEntry(ec); + } + else { + auto err = ::GetLastError(); + if (err != ERROR_NO_MORE_FILES) { + _ec = ec = detail::make_system_error(err); + } + FindClose(_dirHandle); + _dirHandle = INVALID_HANDLE_VALUE; + _dir_entry._path.clear(); + break; + } + } while (std::wstring(_findData.cFileName) == L"." || std::wstring(_findData.cFileName) == L".."); + } + else { + ec = _ec; + } + } + void copyToDirEntry(std::error_code& ec) + { + if (_findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + _dir_entry._status = detail::status_ex(_dir_entry._path, ec, &_dir_entry._symlink_status, &_dir_entry._file_size, nullptr, &_dir_entry._last_write_time); + } + else { + _dir_entry._status = detail::status_from_INFO(_dir_entry._path, &_findData, ec, &_dir_entry._file_size, &_dir_entry._last_write_time); + _dir_entry._symlink_status = _dir_entry._status; + } + if (ec) { + if (_dir_entry._status.type() != file_type::none && _dir_entry._symlink_status.type() != file_type::none) { + ec.clear(); + } + else { + _dir_entry._file_size = static_cast(-1); + _dir_entry._last_write_time = 0; + } + } + } + path _base; + directory_options _options; + WIN32_FIND_DATAW _findData; + HANDLE _dirHandle; + directory_entry _dir_entry; + std::error_code _ec; +}; +#else +// POSIX implementation +class directory_iterator::impl +{ +public: + impl(const path& path, directory_options options) + : _base(path) + , _options(options) + , _dir(nullptr) + , _entry(nullptr) + { + if (!path.empty()) { + _dir = ::opendir(path.native().c_str()); + if (!_dir) { + auto error = errno; + _base = filesystem::path(); + if ((error != EACCES && error != EPERM) || (options & directory_options::skip_permission_denied) == directory_options::none) { + _ec = detail::make_system_error(); + } + } + else { + increment(_ec); + } + } + } + impl(const impl& other) = delete; + ~impl() + { + if (_dir) { + ::closedir(_dir); + } + } + void increment(std::error_code& ec) + { + if (_dir) { + bool skip; + do { + skip = false; + errno = 0; + _entry = ::readdir(_dir); + if (_entry) { + _dir_entry._path = _base; + _dir_entry._path.append_name(_entry->d_name); + copyToDirEntry(); + if (ec && (ec.value() == EACCES || ec.value() == EPERM) && (_options & directory_options::skip_permission_denied) == directory_options::skip_permission_denied) { + ec.clear(); + skip = true; + } + } + else { + ::closedir(_dir); + _dir = nullptr; + _dir_entry._path.clear(); + if (errno) { + ec = detail::make_system_error(); + } + break; + } + } while (skip || std::strcmp(_entry->d_name, ".") == 0 || std::strcmp(_entry->d_name, "..") == 0); + } + } + + void copyToDirEntry() + { + _dir_entry._symlink_status.permissions(perms::unknown); + auto ft = detail::file_type_from_dirent(*_entry); + _dir_entry._symlink_status.type(ft); + if (ft != file_type::symlink) { + _dir_entry._status = _dir_entry._symlink_status; + } + else { + _dir_entry._status.type(file_type::none); + _dir_entry._status.permissions(perms::unknown); + } + _dir_entry._file_size = static_cast(-1); + _dir_entry._hard_link_count = static_cast(-1); + _dir_entry._last_write_time = 0; + } + path _base; + directory_options _options; + DIR* _dir; + struct ::dirent* _entry; + directory_entry _dir_entry; + std::error_code _ec; +}; +#endif + +// [fs.dir.itr.members] member functions +GHC_INLINE directory_iterator::directory_iterator() noexcept + : _impl(new impl(path(), directory_options::none)) +{ +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator::directory_iterator(const path& p) + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } + _impl->_ec.clear(); +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options) + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + throw filesystem_error(detail::systemErrorText(_impl->_ec.value()), p, _impl->_ec); + } +} +#endif + +GHC_INLINE directory_iterator::directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new impl(p, directory_options::none)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new impl(p, options)) +{ + if (_impl->_ec) { + ec = _impl->_ec; + } +} + +GHC_INLINE directory_iterator::directory_iterator(const directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE directory_iterator::directory_iterator(directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE directory_iterator::~directory_iterator() {} + +GHC_INLINE directory_iterator& directory_iterator::operator=(const directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE directory_iterator& directory_iterator::operator=(directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +GHC_INLINE const directory_entry& directory_iterator::operator*() const +{ + return _impl->_dir_entry; +} + +GHC_INLINE const directory_entry* directory_iterator::operator->() const +{ + return &_impl->_dir_entry; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE directory_iterator& directory_iterator::operator++() +{ + std::error_code ec; + _impl->increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_entry._path, ec); + } + return *this; +} +#endif + +GHC_INLINE directory_iterator& directory_iterator::increment(std::error_code& ec) noexcept +{ + _impl->increment(ec); + return *this; +} + +GHC_INLINE bool directory_iterator::operator==(const directory_iterator& rhs) const +{ + return _impl->_dir_entry._path == rhs._impl->_dir_entry._path; +} + +GHC_INLINE bool directory_iterator::operator!=(const directory_iterator& rhs) const +{ + return _impl->_dir_entry._path != rhs._impl->_dir_entry._path; +} + +// [fs.dir.itr.nonmembers] directory_iterator non-member functions + +GHC_INLINE directory_iterator begin(directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE directory_iterator end(const directory_iterator&) noexcept +{ + return directory_iterator(); +} + +//----------------------------------------------------------------------------- +// [fs.class.rec.dir.itr] class recursive_directory_iterator + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator() noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator()); +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p) + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options) + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options)); +} +#endif + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, directory_options options, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(options, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, options, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const path& p, std::error_code& ec) noexcept + : _impl(new recursive_directory_iterator_impl(directory_options::none, true)) +{ + _impl->_dir_iter_stack.push(directory_iterator(p, ec)); +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(const recursive_directory_iterator& rhs) + : _impl(rhs._impl) +{ +} + +GHC_INLINE recursive_directory_iterator::recursive_directory_iterator(recursive_directory_iterator&& rhs) noexcept + : _impl(std::move(rhs._impl)) +{ +} + +GHC_INLINE recursive_directory_iterator::~recursive_directory_iterator() {} + +// [fs.rec.dir.itr.members] observers +GHC_INLINE directory_options recursive_directory_iterator::options() const +{ + return _impl->_options; +} + +GHC_INLINE int recursive_directory_iterator::depth() const +{ + return static_cast(_impl->_dir_iter_stack.size() - 1); +} + +GHC_INLINE bool recursive_directory_iterator::recursion_pending() const +{ + return _impl->_recursion_pending; +} + +GHC_INLINE const directory_entry& recursive_directory_iterator::operator*() const +{ + return *(_impl->_dir_iter_stack.top()); +} + +GHC_INLINE const directory_entry* recursive_directory_iterator::operator->() const +{ + return &(*(_impl->_dir_iter_stack.top())); +} + +// [fs.rec.dir.itr.members] modifiers recursive_directory_iterator& +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(const recursive_directory_iterator& rhs) +{ + _impl = rhs._impl; + return *this; +} + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator=(recursive_directory_iterator&& rhs) noexcept +{ + _impl = std::move(rhs._impl); + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::operator++() +{ + std::error_code ec; + increment(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } + return *this; +} +#endif + +GHC_INLINE recursive_directory_iterator& recursive_directory_iterator::increment(std::error_code& ec) noexcept +{ + bool isSymLink = (*this)->is_symlink(ec); + bool isDir = !ec && (*this)->is_directory(ec); + if (isSymLink && detail::is_not_found_error(ec)) { + ec.clear(); + } + if (!ec) { + if (recursion_pending() && isDir && (!isSymLink || (options() & directory_options::follow_directory_symlink) != directory_options::none)) { + _impl->_dir_iter_stack.push(directory_iterator((*this)->path(), _impl->_options, ec)); + } + else { + _impl->_dir_iter_stack.top().increment(ec); + } + if (!ec) { + while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()) { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } + } + else if (!_impl->_dir_iter_stack.empty()) { + _impl->_dir_iter_stack.pop(); + } + _impl->_recursion_pending = true; + } + return *this; +} + +#ifdef GHC_WITH_EXCEPTIONS +GHC_INLINE void recursive_directory_iterator::pop() +{ + std::error_code ec; + pop(ec); + if (ec) { + throw filesystem_error(detail::systemErrorText(ec.value()), _impl->_dir_iter_stack.empty() ? path() : _impl->_dir_iter_stack.top()->path(), ec); + } +} +#endif + +GHC_INLINE void recursive_directory_iterator::pop(std::error_code& ec) +{ + if (depth() == 0) { + *this = recursive_directory_iterator(); + } + else { + do { + _impl->_dir_iter_stack.pop(); + _impl->_dir_iter_stack.top().increment(ec); + } while (depth() && _impl->_dir_iter_stack.top() == directory_iterator()); + } +} + +GHC_INLINE void recursive_directory_iterator::disable_recursion_pending() +{ + _impl->_recursion_pending = false; +} + +// other members as required by [input.iterators] +GHC_INLINE bool recursive_directory_iterator::operator==(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() == rhs._impl->_dir_iter_stack.top(); +} + +GHC_INLINE bool recursive_directory_iterator::operator!=(const recursive_directory_iterator& rhs) const +{ + return _impl->_dir_iter_stack.top() != rhs._impl->_dir_iter_stack.top(); +} + +// [fs.rec.dir.itr.nonmembers] directory_iterator non-member functions +GHC_INLINE recursive_directory_iterator begin(recursive_directory_iterator iter) noexcept +{ + return iter; +} + +GHC_INLINE recursive_directory_iterator end(const recursive_directory_iterator&) noexcept +{ + return recursive_directory_iterator(); +} + +#endif // GHC_EXPAND_IMPL + +} // namespace filesystem +} // namespace ghc + +// cleanup some macros +#undef GHC_INLINE +#undef GHC_EXPAND_IMPL + +#endif // GHC_FILESYSTEM_H diff --git a/tools/MemoryDumpTool/main.cpp b/tools/MemoryDumpTool/main.cpp index 83223cae1..fe6bed9b9 100644 --- a/tools/MemoryDumpTool/main.cpp +++ b/tools/MemoryDumpTool/main.cpp @@ -12,8 +12,9 @@ #include "decompiler/util/DecompilerTypeSystem.h" #include "common/util/Assert.h" +#include -namespace fs = std::filesystem; +namespace fs = fs; struct Ram { const u8* data = nullptr; @@ -554,6 +555,15 @@ void inspect_symbols(const Ram& ram, } int main(int argc, char** argv) { +#ifdef _WIN32 + auto args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif + fmt::print("MemoryDumpTool\n"); if (argc != 2 && argc != 3) { diff --git a/tools/dgo_packer.cpp b/tools/dgo_packer.cpp index 3b282bdb1..797c2723e 100644 --- a/tools/dgo_packer.cpp +++ b/tools/dgo_packer.cpp @@ -3,8 +3,18 @@ #include "common/util/FileUtil.h" #include "common/util/BinaryWriter.h" #include "third-party/json.hpp" +#include int main(int argc, char** argv) { +#ifdef _WIN32 + auto args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif + printf("OpenGOAL version %d.%d\n", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR); printf("DGO Packing Tool\n"); diff --git a/tools/dgo_unpacker.cpp b/tools/dgo_unpacker.cpp index 8ff91403e..8d8a0d99b 100644 --- a/tools/dgo_unpacker.cpp +++ b/tools/dgo_unpacker.cpp @@ -3,6 +3,7 @@ #include "common/versions.h" #include "common/util/FileUtil.h" #include "common/util/DgoReader.h" +#include namespace { int run(int argc, char** argv) { @@ -48,6 +49,15 @@ int run(int argc, char** argv) { } // namespace int main(int argc, char** argv) { +#ifdef _WIN32 + auto args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif + try { return run(argc, argv); } catch (const std::exception& e) { diff --git a/tools/level_tools/level_dump/main.cpp b/tools/level_tools/level_dump/main.cpp index 0b06d4ae6..bbb1ab345 100644 --- a/tools/level_tools/level_dump/main.cpp +++ b/tools/level_tools/level_dump/main.cpp @@ -8,6 +8,7 @@ #include "decompiler/level_extractor/BspHeader.h" #include "common/util/Assert.h" +#include constexpr GameVersion kGameVersion = GameVersion::Jak1; @@ -55,6 +56,15 @@ bool is_valid_bsp(const decompiler::LinkedObjectFile& file) { } int main(int argc, char** argv) { +#ifdef _WIN32 + auto args = get_widechar_cli_args(); + std::vector string_ptrs; + for (auto& str : args) { + string_ptrs.push_back(str.data()); + } + argv = string_ptrs.data(); +#endif + try { fmt::print("Level Dump Tool\n"); @@ -92,4 +102,4 @@ int main(int argc, char** argv) { } return 0; -} \ No newline at end of file +}