mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-08-21 21:40:32 -04:00
Migrate only user files
This commit is contained in:
Vendored
+1
-1
Submodule extern/aurora updated: f1f6bc5b28...3643a369ad
@@ -10,6 +10,7 @@ extern bool IsShuttingDown;
|
||||
extern bool IsGameLaunched;
|
||||
extern bool RestartRequested;
|
||||
extern std::filesystem::path ConfigPath;
|
||||
extern std::filesystem::path CachePath;
|
||||
|
||||
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
|
||||
(defined(TARGET_OS_TV) && TARGET_OS_TV)
|
||||
|
||||
@@ -61,7 +61,7 @@ std::string release_name() {
|
||||
}
|
||||
|
||||
std::filesystem::path sentry_database_path() {
|
||||
return dusk::ConfigPath / "sentry";
|
||||
return dusk::CachePath / "sentry";
|
||||
}
|
||||
|
||||
std::filesystem::path log_attachment_path() {
|
||||
|
||||
+188
-79
@@ -6,6 +6,7 @@
|
||||
#include "dusk/main.h"
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
@@ -30,6 +31,21 @@ constexpr auto kLocationDescriptorName = "data_location.json";
|
||||
constexpr auto kPipelineCacheName = "pipeline_cache.db";
|
||||
constexpr auto kInitialPipelineCacheName = "initial_pipeline_cache.db";
|
||||
|
||||
constexpr std::array<std::string_view, 4> kUserDataDirectories = {
|
||||
"texture_replacements",
|
||||
"USA",
|
||||
"EUR",
|
||||
"JAP",
|
||||
};
|
||||
constexpr std::array<std::string_view, 6> kUserDataFiles = {
|
||||
"achievements.json",
|
||||
"config.json",
|
||||
"controller_ports.dat",
|
||||
"imgui.ini",
|
||||
"keyboard_bindings.dat",
|
||||
"states.json",
|
||||
};
|
||||
|
||||
enum class LocationMode {
|
||||
Default,
|
||||
Portable,
|
||||
@@ -249,6 +265,62 @@ std::filesystem::path absolute_path(const std::filesystem::path& path) {
|
||||
return absolute.lexically_normal();
|
||||
}
|
||||
|
||||
void rename_legacy_pref_path(
|
||||
const std::filesystem::path& legacyPath, const std::filesystem::path& prefPath) {
|
||||
if (legacyPath.empty() || prefPath.empty() ||
|
||||
normalized_path(legacyPath) == normalized_path(prefPath))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::exists(legacyPath, ec)) {
|
||||
if (ec) {
|
||||
Log.warn("Failed to inspect legacy data directory '{}': {}",
|
||||
io::fs_path_to_string(legacyPath), ec.message());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const bool prefExists = std::filesystem::exists(prefPath, ec);
|
||||
if (ec) {
|
||||
Log.warn("Failed to inspect data directory '{}': {}", io::fs_path_to_string(prefPath),
|
||||
ec.message());
|
||||
return;
|
||||
}
|
||||
if (prefExists) {
|
||||
if (!std::filesystem::is_directory(prefPath, ec) ||
|
||||
!std::filesystem::is_empty(prefPath, ec))
|
||||
{
|
||||
if (ec) {
|
||||
Log.warn("Failed to inspect data directory '{}': {}",
|
||||
io::fs_path_to_string(prefPath), ec.message());
|
||||
} else {
|
||||
Log.info("Skipping legacy data directory rename because '{}' is not empty",
|
||||
io::fs_path_to_string(prefPath));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::remove(prefPath, ec);
|
||||
if (ec) {
|
||||
Log.warn("Failed to remove empty data directory '{}' before legacy rename: {}",
|
||||
io::fs_path_to_string(prefPath), ec.message());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::filesystem::rename(legacyPath, prefPath, ec);
|
||||
if (ec) {
|
||||
Log.warn("Failed to rename legacy data directory '{}' to '{}': {}",
|
||||
io::fs_path_to_string(legacyPath), io::fs_path_to_string(prefPath), ec.message());
|
||||
return;
|
||||
}
|
||||
|
||||
Log.info("Renamed legacy data directory '{}' to '{}'", io::fs_path_to_string(legacyPath),
|
||||
io::fs_path_to_string(prefPath));
|
||||
}
|
||||
|
||||
bool is_same_or_inside(const std::filesystem::path& root, const std::filesystem::path& path) {
|
||||
const auto normalizedRoot = normalized_path(root);
|
||||
const auto normalizedPath = normalized_path(path);
|
||||
@@ -283,78 +355,39 @@ bool should_skip_migration_path(const std::filesystem::path& path,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool has_location_descriptor(const std::filesystem::path& path) {
|
||||
std::error_code ec;
|
||||
return std::filesystem::exists(path / kLocationDescriptorName, ec);
|
||||
bool matches_name(std::string_view name, const auto& names) {
|
||||
return std::ranges::find(names, name) != names.end();
|
||||
}
|
||||
|
||||
bool remove_empty_destination_for_rename(const std::filesystem::path& path) {
|
||||
std::error_code ec;
|
||||
const bool exists = std::filesystem::exists(path, ec);
|
||||
if (ec) {
|
||||
Log.debug("Could not inspect migration destination '{}': {}", io::fs_path_to_string(path),
|
||||
ec.message());
|
||||
bool should_migrate_user_data_path(
|
||||
const std::filesystem::path& sourcePath, const std::filesystem::path& from) {
|
||||
const auto relativePath = sourcePath.lexically_relative(from);
|
||||
if (relativePath.empty() || relativePath.is_absolute()) {
|
||||
return false;
|
||||
}
|
||||
if (!exists) {
|
||||
|
||||
auto it = relativePath.begin();
|
||||
if (it == relativePath.end() || *it == "..") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto first = io::fs_path_to_string(*it);
|
||||
if (matches_name(first, kUserDataDirectories)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool canRemove = std::filesystem::is_directory(path, ec) &&
|
||||
std::filesystem::is_empty(path, ec) && !has_location_descriptor(path);
|
||||
if (ec || !canRemove) {
|
||||
if (ec) {
|
||||
Log.debug("Could not inspect migration destination '{}': {}",
|
||||
io::fs_path_to_string(path), ec.message());
|
||||
}
|
||||
++it;
|
||||
if (it != relativePath.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::remove(path, ec);
|
||||
if (ec) {
|
||||
Log.debug("Could not remove empty migration destination '{}': {}",
|
||||
io::fs_path_to_string(path), ec.message());
|
||||
return false;
|
||||
const auto filename = io::fs_path_to_string(relativePath.filename());
|
||||
if (matches_name(filename, kUserDataFiles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool try_rename_directory_migration(
|
||||
const std::filesystem::path& from, const std::filesystem::path& to) {
|
||||
std::error_code ec;
|
||||
if (!std::filesystem::is_directory(from, ec)) {
|
||||
return false;
|
||||
}
|
||||
if (ec) {
|
||||
Log.debug("Could not inspect migration source '{}': {}", io::fs_path_to_string(from),
|
||||
ec.message());
|
||||
return false;
|
||||
}
|
||||
if (has_location_descriptor(from)) {
|
||||
return false;
|
||||
}
|
||||
if (!remove_empty_destination_for_rename(to)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::create_directories(to.parent_path(), ec);
|
||||
if (ec) {
|
||||
Log.debug("Could not create migration destination parent '{}': {}",
|
||||
io::fs_path_to_string(to.parent_path()), ec.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::rename(from, to, ec);
|
||||
if (ec) {
|
||||
Log.debug("Could not rename data directory '{}' to '{}': {}", io::fs_path_to_string(from),
|
||||
io::fs_path_to_string(to), ec.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.info("Renamed data directory '{}' to '{}'", io::fs_path_to_string(from),
|
||||
io::fs_path_to_string(to));
|
||||
return true;
|
||||
return relativePath.extension() == ".controller" || relativePath.extension() == ".gci" ||
|
||||
(filename.starts_with("MemoryCard") && filename.ends_with(".raw"));
|
||||
}
|
||||
|
||||
std::filesystem::path current_data_path() {
|
||||
@@ -439,6 +472,61 @@ bool write_location_descriptor(LocationMode mode, const std::filesystem::path& t
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_error(std::string* errorOut, std::string error) {
|
||||
if (errorOut != nullptr) {
|
||||
*errorOut = std::move(error);
|
||||
}
|
||||
}
|
||||
|
||||
bool validate_writable_data_path(const std::filesystem::path& path, std::string* errorOut) {
|
||||
if (path.empty()) {
|
||||
set_error(errorOut, "Choose a folder.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(path, ec);
|
||||
if (ec) {
|
||||
set_error(errorOut, fmt::format("{} could not create the selected folder.", AppName));
|
||||
Log.warn("Failed to create custom data folder '{}': {}", io::fs_path_to_string(path),
|
||||
ec.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!std::filesystem::is_directory(path, ec)) {
|
||||
set_error(errorOut, "The selected path is not a folder.");
|
||||
if (ec) {
|
||||
Log.warn("Failed to inspect custom data folder '{}': {}", io::fs_path_to_string(path),
|
||||
ec.message());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto probePath = path / fmt::format(".write-probe-{}.tmp",
|
||||
std::chrono::steady_clock::now().time_since_epoch().count());
|
||||
try {
|
||||
io::FileStream::WriteAllText(probePath, "dusk");
|
||||
} catch (const std::exception& e) {
|
||||
set_error(errorOut, fmt::format("{} could not write to the selected folder.", AppName));
|
||||
Log.warn("Failed write probe for custom data folder '{}': {}", io::fs_path_to_string(path),
|
||||
e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::filesystem::remove(probePath, ec);
|
||||
if (ec) {
|
||||
set_error(
|
||||
errorOut, fmt::format("{} could write to the selected folder, but could not remove "
|
||||
"the test file it created.",
|
||||
AppName));
|
||||
Log.warn("Failed to remove custom data folder write probe '{}': {}",
|
||||
io::fs_path_to_string(probePath), ec.message());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::uintmax_t remove_empty_directories(const std::filesystem::path& root, bool includeRoot) {
|
||||
std::error_code ec;
|
||||
std::vector<std::filesystem::path> directories;
|
||||
@@ -647,14 +735,6 @@ void migrate_directory(const std::filesystem::path& from, const std::filesystem:
|
||||
return;
|
||||
}
|
||||
|
||||
if (try_rename_directory_migration(from, to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (try_rename_directory_migration(from, to)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::create_directories(to, ec);
|
||||
if (ec) {
|
||||
++stats.failures;
|
||||
@@ -700,6 +780,16 @@ void migrate_directory(const std::filesystem::path& from, const std::filesystem:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!should_migrate_user_data_path(sourcePath, from)) {
|
||||
++stats.skippedUnsupportedEntries;
|
||||
if (std::filesystem::is_directory(status)) {
|
||||
it.disable_recursion_pending();
|
||||
}
|
||||
ec.clear();
|
||||
it.increment(ec);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto relativePath = sourcePath.lexically_relative(from);
|
||||
if (relativePath.empty() || relativePath.is_absolute()) {
|
||||
++stats.failures;
|
||||
@@ -761,8 +851,6 @@ void migrate_data(const std::filesystem::path& prefPath, const std::filesystem::
|
||||
const LocationDescriptor* descriptor) {
|
||||
if (descriptor && !descriptor->previousPath.empty()) {
|
||||
migrate_directory(descriptor->previousPath, dataPath, prefPath);
|
||||
} else if (const auto legacyPath = get_legacy_path(); !legacyPath.empty()) {
|
||||
migrate_directory(legacyPath, dataPath, prefPath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -899,17 +987,25 @@ bool open_data_path() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool set_custom_data_path(const std::filesystem::path& path) {
|
||||
if (path.empty()) {
|
||||
Log.warn("Ignoring empty custom data path");
|
||||
bool set_custom_data_path(const std::filesystem::path& path, std::string* errorOut) {
|
||||
if (!validate_writable_data_path(path, errorOut)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return write_location_descriptor(LocationMode::Custom, path);
|
||||
if (!write_location_descriptor(LocationMode::Custom, path)) {
|
||||
set_error(errorOut, fmt::format("{} could not save the data folder setting.", AppName));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_custom_data_path(const char* path) {
|
||||
return set_custom_data_path(path_from_utf8(path));
|
||||
bool set_custom_data_path(const char* path, std::string* errorOut) {
|
||||
if (path == nullptr) {
|
||||
set_error(errorOut, "Choose a folder.");
|
||||
return false;
|
||||
}
|
||||
return set_custom_data_path(path_from_utf8(path), errorOut);
|
||||
}
|
||||
|
||||
bool set_portable_data_path() {
|
||||
@@ -941,6 +1037,13 @@ std::filesystem::path configured_data_path() {
|
||||
return *sConfiguredDataPath;
|
||||
}
|
||||
|
||||
std::filesystem::path cache_path() {
|
||||
if (!CachePath.empty()) {
|
||||
return CachePath;
|
||||
}
|
||||
return get_pref_path();
|
||||
}
|
||||
|
||||
bool is_data_path_restart_pending() {
|
||||
if (ConfigPath.empty()) {
|
||||
return false;
|
||||
@@ -949,8 +1052,10 @@ bool is_data_path_restart_pending() {
|
||||
return normalized_path(ConfigPath) != normalized_path(configured_data_path());
|
||||
}
|
||||
|
||||
std::filesystem::path initialize_data() {
|
||||
Paths initialize_data() {
|
||||
const auto prefPath = get_pref_path();
|
||||
rename_legacy_pref_path(get_legacy_path(), prefPath);
|
||||
|
||||
const auto descriptor = read_location_descriptor(prefPath);
|
||||
if (descriptor) {
|
||||
sActiveDescriptorPath = descriptor->path;
|
||||
@@ -963,9 +1068,13 @@ std::filesystem::path initialize_data() {
|
||||
|
||||
migrate_data(prefPath, dataPath, descriptor ? &descriptor->descriptor : nullptr);
|
||||
ensure_data_directory(dataPath);
|
||||
ensure_initial_pipeline_cache(dataPath);
|
||||
ensure_data_directory(prefPath);
|
||||
ensure_initial_pipeline_cache(prefPath);
|
||||
|
||||
return dataPath;
|
||||
return Paths{
|
||||
.userPath = dataPath,
|
||||
.cachePath = prefPath,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace dusk::data
|
||||
|
||||
+11
-4
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
@@ -14,7 +15,7 @@
|
||||
#define DUSK_CAN_OPEN_DATA_FOLDER 0
|
||||
#endif
|
||||
|
||||
#if defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST
|
||||
#if (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST) || defined(__ANDROID__)
|
||||
#define DUSK_CAN_CHANGE_DATA_FOLDER 0
|
||||
#else
|
||||
#define DUSK_CAN_CHANGE_DATA_FOLDER 1
|
||||
@@ -22,11 +23,17 @@
|
||||
|
||||
namespace dusk::data {
|
||||
|
||||
std::filesystem::path initialize_data();
|
||||
struct Paths {
|
||||
std::filesystem::path userPath;
|
||||
std::filesystem::path cachePath;
|
||||
};
|
||||
|
||||
Paths initialize_data();
|
||||
std::filesystem::path configured_data_path();
|
||||
std::filesystem::path cache_path();
|
||||
bool open_data_path();
|
||||
bool set_custom_data_path(const char* path);
|
||||
bool set_custom_data_path(const std::filesystem::path& path);
|
||||
bool set_custom_data_path(const char* path, std::string* errorOut);
|
||||
bool set_custom_data_path(const std::filesystem::path& path, std::string* errorOut);
|
||||
bool set_portable_data_path();
|
||||
bool reset_data_path();
|
||||
bool is_default_data_path();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "aurora/gfx.h"
|
||||
#include "bool_button.hpp"
|
||||
#include "controller_config.hpp"
|
||||
#include "dusk/app_info.hpp"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/audio/DuskDsp.hpp"
|
||||
#include "dusk/config.hpp"
|
||||
@@ -17,6 +18,7 @@
|
||||
#include "graphics_tuner.hpp"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "menu_bar.hpp"
|
||||
#include "modal.hpp"
|
||||
#include "number_button.hpp"
|
||||
#include "menu_bar.hpp"
|
||||
#include "pane.hpp"
|
||||
@@ -25,6 +27,7 @@
|
||||
|
||||
#include <aurora/lib/window.hpp>
|
||||
#include <SDL3/SDL_filesystem.h>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#if DUSK_ENABLE_SENTRY_NATIVE
|
||||
#include "dusk/crash_reporting.h"
|
||||
@@ -297,13 +300,49 @@ private:
|
||||
Rml::String mCurrentRml;
|
||||
};
|
||||
|
||||
void show_data_folder_error_modal(std::string_view message) {
|
||||
auto dismiss = [](Modal& modal) {
|
||||
mDoAud_seStartMenu(kSoundWindowClose);
|
||||
modal.pop();
|
||||
};
|
||||
push_document(std::make_unique<Modal>(Modal::Props{
|
||||
.title = "Data Folder Not Changed",
|
||||
.bodyRml = escape(message),
|
||||
.actions =
|
||||
{
|
||||
ModalAction{
|
||||
.label = "OK",
|
||||
.onPressed = dismiss,
|
||||
},
|
||||
},
|
||||
.onDismiss = dismiss,
|
||||
.icon = "warning",
|
||||
}));
|
||||
if (auto* doc = top_document()) {
|
||||
doc->focus();
|
||||
}
|
||||
}
|
||||
|
||||
void data_folder_dialog_callback(void*, const char* path, const char* error) {
|
||||
if (error != nullptr || path == nullptr) {
|
||||
if (error != nullptr) {
|
||||
show_data_folder_error_modal(error);
|
||||
return;
|
||||
}
|
||||
if (data::set_custom_data_path(path)) {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
if (path == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string dataPathError;
|
||||
if (data::set_custom_data_path(path, &dataPathError)) {
|
||||
mDoAud_seStartMenu(kSoundItemChange);
|
||||
return;
|
||||
}
|
||||
|
||||
if (dataPathError.empty()) {
|
||||
dataPathError =
|
||||
fmt::format("{} could not use the selected folder as its data folder.", AppName);
|
||||
}
|
||||
show_data_folder_error_modal(dataPathError);
|
||||
}
|
||||
|
||||
const Rml::String kInternalResolutionHelpText =
|
||||
|
||||
@@ -119,6 +119,7 @@ bool dusk::IsShuttingDown = false;
|
||||
bool dusk::IsGameLaunched = false;
|
||||
bool dusk::RestartRequested = false;
|
||||
std::filesystem::path dusk::ConfigPath;
|
||||
std::filesystem::path dusk::CachePath;
|
||||
#endif
|
||||
|
||||
void dusk::RequestRestart() noexcept {
|
||||
@@ -513,8 +514,10 @@ int game_main(int argc, char* argv[]) {
|
||||
|
||||
const auto startupLogLevel =
|
||||
static_cast<AuroraLogLevel>(parsed_arg_options["log-level"].as<uint8_t>());
|
||||
dusk::ConfigPath = dusk::data::initialize_data();
|
||||
dusk::InitializeFileLogging(dusk::ConfigPath, startupLogLevel);
|
||||
const auto dataPaths = dusk::data::initialize_data();
|
||||
dusk::ConfigPath = dataPaths.userPath;
|
||||
dusk::CachePath = dataPaths.cachePath;
|
||||
dusk::InitializeFileLogging(dusk::CachePath, startupLogLevel);
|
||||
|
||||
log_build_info();
|
||||
|
||||
@@ -536,10 +539,12 @@ int game_main(int argc, char* argv[]) {
|
||||
SDL_SetAppMetadata("Dusklight", DUSK_VERSION_STRING, "dev.twilitrealm.dusk");
|
||||
|
||||
{
|
||||
const auto configPathString = dusk::ConfigPath.u8string();
|
||||
const auto userPathString = dusk::ConfigPath.u8string();
|
||||
const auto cachePathString = dusk::CachePath.u8string();
|
||||
AuroraConfig config{};
|
||||
config.appName = dusk::AppName;
|
||||
config.configPath = reinterpret_cast<const char*>(configPathString.c_str());
|
||||
config.userPath = reinterpret_cast<const char*>(userPathString.c_str());
|
||||
config.cachePath = reinterpret_cast<const char*>(cachePathString.c_str());
|
||||
config.vsync = dusk::getSettings().video.enableVsync;
|
||||
config.startFullscreen = dusk::getSettings().video.enableFullscreen;
|
||||
config.windowPosX = -1;
|
||||
|
||||
Reference in New Issue
Block a user