mirror of
https://github.com/sal063/AC6_recomp
synced 2026-08-21 23:00:53 -04:00
Fix the log file and path settings on non-ASCII install paths
Sweeps the config fix's defect class across the rest of the tree: the log file itself opened through the ANSI code page, the user_data_root/update_data_root/log_file settings, and both mod manifest loaders shared the same narrowing
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
@@ -14,6 +15,7 @@
|
||||
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/crypto/sha256.h>
|
||||
#include <rex/filesystem.h>
|
||||
#include <rex/logging.h>
|
||||
|
||||
REXCVAR_DEFINE_BOOL(
|
||||
@@ -334,10 +336,20 @@ void LoadManifestForRoot(NativeAssetRegistryState& state, RootDescriptor& root)
|
||||
|
||||
toml::table manifest;
|
||||
try {
|
||||
manifest = toml::parse_file(manifest_path.string());
|
||||
// Path-native stream + path_to_utf8 in the log line: parse_file(.string())
|
||||
// narrows through the ANSI code page and fails on non-ASCII install
|
||||
// paths (the same construct as the config-loader bug).
|
||||
std::ifstream manifest_file(manifest_path, std::ios::binary);
|
||||
if (!manifest_file) {
|
||||
++state.manifest_error_count;
|
||||
REXLOG_WARN("AC6 native assets: failed to open {}", rex::path_to_utf8(manifest_path));
|
||||
return;
|
||||
}
|
||||
manifest = toml::parse(manifest_file, rex::path_to_utf8(manifest_path));
|
||||
} catch (const toml::parse_error& err) {
|
||||
++state.manifest_error_count;
|
||||
REXLOG_WARN("AC6 native assets: failed to parse {}: {}", manifest_path.string(), err.description());
|
||||
REXLOG_WARN("AC6 native assets: failed to parse {}: {}", rex::path_to_utf8(manifest_path),
|
||||
err.description());
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -350,10 +350,20 @@ TextureSwapManifestCacheEntry LoadTextureSwapManifestCacheEntry(const std::files
|
||||
|
||||
toml::table manifest;
|
||||
try {
|
||||
manifest = toml::parse_file(manifest_path.string());
|
||||
// Path-native stream + path_to_utf8 in the log line: parse_file(.string())
|
||||
// narrows through the ANSI code page and fails on non-ASCII install
|
||||
// paths (the same construct as the config-loader bug).
|
||||
std::ifstream manifest_file(manifest_path, std::ios::binary);
|
||||
if (!manifest_file) {
|
||||
entry.parse_failed = true;
|
||||
REXLOG_WARN("Texture swap manifest {}: cannot open", rex::path_to_utf8(manifest_path));
|
||||
return entry;
|
||||
}
|
||||
manifest = toml::parse(manifest_file, rex::path_to_utf8(manifest_path));
|
||||
} catch (const toml::parse_error& err) {
|
||||
entry.parse_failed = true;
|
||||
REXLOG_WARN("Texture swap manifest {}: parse error: {}", manifest_path.string(), err.description());
|
||||
REXLOG_WARN("Texture swap manifest {}: parse error: {}", rex::path_to_utf8(manifest_path),
|
||||
err.description());
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -70,7 +70,9 @@ struct LogConfig {
|
||||
* the platform debug sink created by InitLoggingEarly(). */
|
||||
bool log_to_console = false;
|
||||
|
||||
/** Path to a log file, or nullptr for no file logging. */
|
||||
/** Path to a log file, or nullptr for no file logging. UTF-8 (the cvar/toml
|
||||
* encoding); InitLogging converts it with rex::to_path, never through the
|
||||
* ANSI code page. */
|
||||
const char* log_file = nullptr;
|
||||
|
||||
/** spdlog pattern string for the stdout console sink. */
|
||||
@@ -109,6 +111,8 @@ struct LogConfig {
|
||||
bool category_sinks_exclusive = false;
|
||||
|
||||
std::string app_name;
|
||||
/** Directory for auto-named sequential logs. UTF-8 (produce it with
|
||||
* rex::path_to_utf8); InitLogging converts back with rex::to_path. */
|
||||
std::string log_dir;
|
||||
};
|
||||
|
||||
|
||||
+59
-16
@@ -12,7 +12,9 @@
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -23,6 +25,7 @@
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/filesystem.h>
|
||||
#include <rex/logging.h>
|
||||
#include <rex/platform.h>
|
||||
|
||||
@@ -82,16 +85,29 @@ static void RemovePreviousLog(const std::filesystem::path& path, int max_files)
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(path, ec);
|
||||
const std::filesystem::path dir = path.parent_path();
|
||||
const std::string stem = path.stem().string();
|
||||
const std::string ext = path.extension().string();
|
||||
// path_to_utf8/to_path, not .string(): the log file name is user-chosen and
|
||||
// may contain characters outside the ANSI code page (MSVC's path::string()
|
||||
// throws for those).
|
||||
const std::string stem = rex::path_to_utf8(path.stem());
|
||||
const std::string ext = rex::path_to_utf8(path.extension());
|
||||
for (int i = 1; i <= max_files && i <= 64; ++i) {
|
||||
std::filesystem::remove(dir / fmt::format("{}.{}{}", stem, i, ext), ec);
|
||||
std::filesystem::remove(dir / rex::to_path(fmt::format("{}.{}{}", stem, i, ext)), ec);
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::path NextSequentialLogPath(const std::filesystem::path& logs_dir,
|
||||
std::string_view app_name) {
|
||||
std::filesystem::create_directories(logs_dir);
|
||||
// error_code overload: the throwing create_directories could propagate a
|
||||
// filesystem_error out of InitLogging during startup (e.g. an exe dir that
|
||||
// narrowed badly elsewhere). A failed log dir means "no file sink", never
|
||||
// a crash before the window exists.
|
||||
std::error_code create_ec;
|
||||
std::filesystem::create_directories(logs_dir, create_ec);
|
||||
if (create_ec) {
|
||||
std::fprintf(stderr, "logging: cannot create log directory %s: %s\n",
|
||||
rex::path_to_utf8(logs_dir).c_str(), create_ec.message().c_str());
|
||||
return {};
|
||||
}
|
||||
|
||||
int max_seq = 0;
|
||||
std::string prefix = std::string(app_name) + "_";
|
||||
@@ -99,7 +115,7 @@ std::filesystem::path NextSequentialLogPath(const std::filesystem::path& logs_di
|
||||
for (const auto& entry : std::filesystem::directory_iterator(logs_dir, ec)) {
|
||||
if (!entry.is_regular_file())
|
||||
continue;
|
||||
auto stem = entry.path().stem().string();
|
||||
auto stem = rex::path_to_utf8(entry.path().stem());
|
||||
if (stem.starts_with(prefix)) {
|
||||
auto num_str = stem.substr(prefix.size());
|
||||
int num = 0;
|
||||
@@ -235,10 +251,13 @@ void InitLogging(const LogConfig& config) {
|
||||
g_console_sink = sink;
|
||||
}
|
||||
|
||||
// File sink (rotating) with sequential naming fallback
|
||||
std::string resolved_path;
|
||||
// File sink (rotating) with sequential naming fallback. The path stays a
|
||||
// std::filesystem::path end-to-end; LogConfig's log_file/log_dir strings
|
||||
// are UTF-8 and are converted with rex::to_path, never the implicit
|
||||
// ANSI-code-page construction that mangled non-ASCII install paths.
|
||||
std::filesystem::path resolved_path;
|
||||
if (config.log_file) {
|
||||
resolved_path = config.log_file;
|
||||
resolved_path = rex::to_path(config.log_file);
|
||||
// Fresh single log each launch (QoL): delete the previous one first so a
|
||||
// fixed log_file is replaced, not appended-to, and files don't stack up.
|
||||
if (!resolved_path.empty() && REXCVAR_GET(log_new_file_per_launch)) {
|
||||
@@ -246,16 +265,32 @@ void InitLogging(const LogConfig& config) {
|
||||
}
|
||||
} else if (!config.app_name.empty()) {
|
||||
auto log_dir = config.log_dir.empty() ? std::filesystem::current_path() / "logs"
|
||||
: std::filesystem::path(config.log_dir);
|
||||
resolved_path = NextSequentialLogPath(log_dir, config.app_name).string();
|
||||
: rex::to_path(config.log_dir);
|
||||
resolved_path = NextSequentialLogPath(log_dir, config.app_name);
|
||||
}
|
||||
if (!resolved_path.empty()) {
|
||||
auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
resolved_path, static_cast<size_t>(REXCVAR_GET(log_max_file_size_mb)) * 1024 * 1024,
|
||||
try {
|
||||
#if defined(_WIN32) && defined(SPDLOG_WCHAR_FILENAMES)
|
||||
// Wide filename: spdlog opens via _wfsopen, so any Unicode path works.
|
||||
// Without SPDLOG_WCHAR_FILENAMES the narrow filename goes through the
|
||||
// ANSI code page in _fsopen and a Cyrillic/CJK exe dir gets no log at
|
||||
// all - the user loses their diagnostics exactly when they need them.
|
||||
spdlog::filename_t sink_filename = resolved_path.wstring();
|
||||
#else
|
||||
spdlog::filename_t sink_filename = resolved_path.string();
|
||||
#endif
|
||||
auto sink = std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
|
||||
sink_filename, static_cast<size_t>(REXCVAR_GET(log_max_file_size_mb)) * 1024 * 1024,
|
||||
static_cast<size_t>(REXCVAR_GET(log_max_files)), false);
|
||||
sink->set_level(spdlog::level::trace);
|
||||
sink->set_pattern(config.file_pattern);
|
||||
g_file_sink = sink;
|
||||
sink->set_level(spdlog::level::trace);
|
||||
sink->set_pattern(config.file_pattern);
|
||||
g_file_sink = sink;
|
||||
} catch (const spdlog::spdlog_ex& e) {
|
||||
// A file sink that cannot open must not take startup down with it.
|
||||
// (No REXLOG here: we hold g_mutex and the macros can re-enter it.)
|
||||
std::fprintf(stderr, "logging: cannot open log file %s: %s\n",
|
||||
rex::path_to_utf8(resolved_path).c_str(), e.what());
|
||||
}
|
||||
}
|
||||
|
||||
g_extra_sinks = config.extra_sinks;
|
||||
@@ -561,7 +596,15 @@ std::map<std::string, std::string> ParseCategoryLevelsFromConfig(
|
||||
return result;
|
||||
|
||||
try {
|
||||
auto config = toml::parse_file(config_path.string());
|
||||
// Path-native stream, not parse_file(path.string()): the narrow
|
||||
// conversion cannot represent (and on MSVC may throw for) characters
|
||||
// outside the ANSI code page - the same construct as the config
|
||||
// loader had, and this one ran before logging was even up.
|
||||
std::ifstream file(config_path, std::ios::binary);
|
||||
if (!file) {
|
||||
return result;
|
||||
}
|
||||
auto config = toml::parse(file, rex::path_to_utf8(config_path));
|
||||
auto* log_table = config["log"].as_table();
|
||||
if (!log_table)
|
||||
return result;
|
||||
|
||||
+11
-4
@@ -354,7 +354,9 @@ bool ReXApp::OnInitialize() {
|
||||
std::filesystem::path user_dir;
|
||||
std::string user_data_cvar = REXCVAR_GET(user_data_root);
|
||||
if (!user_data_cvar.empty()) {
|
||||
user_dir = user_data_cvar;
|
||||
// to_path: the toml string is UTF-8, not the ANSI codepage a bare path
|
||||
// construction would assume on Windows (same rule as game_iso/dlc_dir).
|
||||
user_dir = rex::to_path(user_data_cvar);
|
||||
} else {
|
||||
user_dir = rex::filesystem::GetUserFolder() / GetName();
|
||||
}
|
||||
@@ -363,7 +365,7 @@ bool ReXApp::OnInitialize() {
|
||||
std::filesystem::path update_dir;
|
||||
std::string update_data_cvar = REXCVAR_GET(update_data_root);
|
||||
if (!update_data_cvar.empty()) {
|
||||
update_dir = update_data_cvar;
|
||||
update_dir = rex::to_path(update_data_cvar);
|
||||
}
|
||||
|
||||
// Allow subclass to override path defaults
|
||||
@@ -399,7 +401,8 @@ bool ReXApp::OnInitialize() {
|
||||
crash_dir = exe_dir / "logs";
|
||||
} else {
|
||||
std::error_code crash_dir_ec;
|
||||
crash_dir = std::filesystem::absolute(std::filesystem::path(log_file_cvar), crash_dir_ec)
|
||||
// to_path: the cvar string is UTF-8 (see user_data_root above).
|
||||
crash_dir = std::filesystem::absolute(rex::to_path(log_file_cvar), crash_dir_ec)
|
||||
.parent_path();
|
||||
if (crash_dir.empty())
|
||||
crash_dir = std::filesystem::current_path();
|
||||
@@ -421,7 +424,11 @@ bool ReXApp::OnInitialize() {
|
||||
log_level_str, category_levels);
|
||||
if (log_file_cvar.empty()) {
|
||||
log_config.app_name = std::string(GetName());
|
||||
log_config.log_dir = (exe_dir / "logs").string();
|
||||
// path_to_utf8, not .string(): a non-ASCII exe dir narrowed through the
|
||||
// ANSI code page here made the log land in a mangled directory - or
|
||||
// threw during startup - exactly when a user on such a path most needs
|
||||
// their diagnostics. InitLogging converts back with rex::to_path.
|
||||
log_config.log_dir = rex::path_to_utf8(exe_dir / "logs");
|
||||
}
|
||||
rex::InitLogging(log_config);
|
||||
rex::RegisterLogLevelCallback();
|
||||
|
||||
@@ -167,6 +167,14 @@ set(SPDLOG_BUILD_BENCH OFF CACHE BOOL "" FORCE)
|
||||
set(SPDLOG_INSTALL ON CACHE BOOL "" FORCE)
|
||||
add_subdirectory(spdlog)
|
||||
target_compile_options(spdlog PRIVATE -w)
|
||||
if(WIN32)
|
||||
# Wide filenames: the log path derives from the exe directory, which is
|
||||
# user-chosen and can contain any Unicode. Narrow spdlog filenames go
|
||||
# through the ANSI code page in _fsopen, so a Cyrillic/CJK install path
|
||||
# produced no log file at all. PUBLIC so every
|
||||
# consumer's spdlog::filename_t agrees with the compiled lib.
|
||||
target_compile_definitions(spdlog PUBLIC SPDLOG_WCHAR_FILENAMES)
|
||||
endif()
|
||||
|
||||
#=============================================================================
|
||||
# snappy - Fast compression/decompression (GPU traces)
|
||||
|
||||
Reference in New Issue
Block a user