mirror of
https://github.com/Mr-Wiseguy/Zelda64Recomp.git
synced 2024-12-22 11:01:54 +01:00
Use file backup system for config files (#335)
* Use file backup system for config files * Add -fexceptions to windows cxxflags in workflow
This commit is contained in:
parent
3e5efc935e
commit
ce406e9c5d
2
.github/workflows/validate.yml
vendored
2
.github/workflows/validate.yml
vendored
@ -158,7 +158,7 @@ jobs:
|
||||
# enable ccache
|
||||
set $env:PATH="$env:USERPROFILE/.cargo/bin;$env:PATH"
|
||||
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build -DCMAKE_CXX_FLAGS="-Xclang -fcxx-exceptions"
|
||||
cmake -DCMAKE_BUILD_TYPE=${{ matrix.type }} -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER=clang-cl -DCMAKE_MAKE_PROGRAM=ninja -G Ninja -S . -B cmake-build -DCMAKE_CXX_FLAGS="-Xclang -fexceptions -Xclang -fcxx-exceptions"
|
||||
cmake --build cmake-build --config ${{ matrix.type }} --target Zelda64Recompiled -j 8
|
||||
- name: Prepare Archive
|
||||
run: |
|
||||
|
@ -130,6 +130,7 @@ set (SOURCES
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/dp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/eep.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/euc-jp.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/files.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/flash.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/math_routines.cpp
|
||||
${CMAKE_SOURCE_DIR}/src/recomp/overlays.cpp
|
||||
|
14
include/recomp_files.h
Normal file
14
include/recomp_files.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef __RECOMP_FILES_H__
|
||||
#define __RECOMP_FILES_H__
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace recomp {
|
||||
std::ifstream open_input_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::in);
|
||||
std::ifstream open_input_backup_file(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::in);
|
||||
std::ofstream open_output_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode = std::ios_base::out);
|
||||
bool finalize_output_file_with_backup(const std::filesystem::path& filepath);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,6 +1,7 @@
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_input.h"
|
||||
#include "recomp_sound.h"
|
||||
#include "recomp_files.h"
|
||||
#include "../../ultramodern/config.hpp"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@ -153,9 +154,48 @@ std::filesystem::path recomp::get_app_folder_path() {
|
||||
return recomp_dir;
|
||||
}
|
||||
|
||||
void save_general_config(const std::filesystem::path& path) {
|
||||
std::ofstream config_file{path};
|
||||
|
||||
bool read_json(std::ifstream input_file, nlohmann::json& json_out) {
|
||||
if (!input_file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
input_file >> json_out;
|
||||
}
|
||||
catch (nlohmann::json::parse_error&) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_json_with_backups(const std::filesystem::path& path, nlohmann::json& json_out) {
|
||||
// Try reading and parsing the base file.
|
||||
if (read_json(std::ifstream{path}, json_out)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Try reading and parsing the backup file.
|
||||
if (read_json(recomp::open_input_backup_file(path), json_out)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Both reads failed.
|
||||
return false;
|
||||
}
|
||||
|
||||
bool save_json_with_backups(const std::filesystem::path& path, const nlohmann::json& json_data) {
|
||||
{
|
||||
std::ofstream output_file = recomp::open_output_file_with_backup(path);
|
||||
if (!output_file.good()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
output_file << std::setw(4) << json_data;
|
||||
}
|
||||
return recomp::finalize_output_file_with_backup(path);
|
||||
}
|
||||
|
||||
bool save_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
recomp::to_json(config_json["targeting_mode"], recomp::get_targeting_mode());
|
||||
@ -169,7 +209,8 @@ void save_general_config(const std::filesystem::path& path) {
|
||||
config_json["analog_cam_mode"] = recomp::get_analog_cam_mode();
|
||||
config_json["analog_camera_invert_mode"] = recomp::get_analog_camera_invert_mode();
|
||||
config_json["debug_mode"] = recomp::get_debug_mode_enabled();
|
||||
config_file << std::setw(4) << config_json;
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void set_general_settings_from_json(const nlohmann::json& config_json) {
|
||||
@ -186,13 +227,14 @@ void set_general_settings_from_json(const nlohmann::json& config_json) {
|
||||
recomp::set_debug_mode_enabled(from_or_default(config_json, "debug_mode", false));
|
||||
}
|
||||
|
||||
void load_general_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
bool load_general_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_file >> config_json;
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_general_settings_from_json(config_json);
|
||||
return true;
|
||||
}
|
||||
|
||||
void assign_mapping(recomp::InputDevice device, recomp::GameInput input, const std::vector<recomp::InputField>& value) {
|
||||
@ -263,23 +305,22 @@ void reset_graphics_options() {
|
||||
ultramodern::set_graphics_config(new_config);
|
||||
}
|
||||
|
||||
void save_graphics_config(const std::filesystem::path& path) {
|
||||
std::ofstream config_file{path};
|
||||
|
||||
bool save_graphics_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
ultramodern::to_json(config_json, ultramodern::get_graphics_config());
|
||||
config_file << std::setw(4) << config_json;
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void load_graphics_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
bool load_graphics_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_file >> config_json;
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ultramodern::GraphicsConfig new_config{};
|
||||
ultramodern::from_json(config_json, new_config);
|
||||
ultramodern::set_graphics_config(new_config);
|
||||
return true;
|
||||
}
|
||||
|
||||
void add_input_bindings(nlohmann::json& out, recomp::GameInput input, recomp::InputDevice device) {
|
||||
@ -291,7 +332,7 @@ void add_input_bindings(nlohmann::json& out, recomp::GameInput input, recomp::In
|
||||
}
|
||||
};
|
||||
|
||||
void save_controls_config(const std::filesystem::path& path) {
|
||||
bool save_controls_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_json["keyboard"] = {};
|
||||
@ -304,8 +345,7 @@ void save_controls_config(const std::filesystem::path& path) {
|
||||
add_input_bindings(config_json["controller"], cur_input, recomp::InputDevice::Controller);
|
||||
}
|
||||
|
||||
std::ofstream config_file{path};
|
||||
config_file << std::setw(4) << config_json;
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
bool load_input_device_from_json(const nlohmann::json& config_json, recomp::InputDevice device, const std::string& key) {
|
||||
@ -349,11 +389,11 @@ bool load_input_device_from_json(const nlohmann::json& config_json, recomp::Inpu
|
||||
return true;
|
||||
}
|
||||
|
||||
void load_controls_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
bool load_controls_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_file >> config_json;
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!load_input_device_from_json(config_json, recomp::InputDevice::Keyboard, "keyboard")) {
|
||||
assign_all_mappings(recomp::InputDevice::Keyboard, recomp::default_n64_keyboard_mappings);
|
||||
@ -362,29 +402,30 @@ void load_controls_config(const std::filesystem::path& path) {
|
||||
if (!load_input_device_from_json(config_json, recomp::InputDevice::Controller, "controller")) {
|
||||
assign_all_mappings(recomp::InputDevice::Controller, recomp::default_n64_controller_mappings);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void save_sound_config(const std::filesystem::path& path) {
|
||||
bool save_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_json["main_volume"] = recomp::get_main_volume();
|
||||
config_json["bgm_volume"] = recomp::get_bgm_volume();
|
||||
config_json["low_health_beeps"] = recomp::get_low_health_beeps_enabled();
|
||||
|
||||
std::ofstream config_file{path};
|
||||
config_file << std::setw(4) << config_json;
|
||||
|
||||
return save_json_with_backups(path, config_json);
|
||||
}
|
||||
|
||||
void load_sound_config(const std::filesystem::path& path) {
|
||||
std::ifstream config_file{path};
|
||||
bool load_sound_config(const std::filesystem::path& path) {
|
||||
nlohmann::json config_json{};
|
||||
|
||||
config_file >> config_json;
|
||||
if (!read_json_with_backups(path, config_json)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recomp::reset_sound_settings();
|
||||
call_if_key_exists(recomp::set_main_volume, config_json, "main_volume");
|
||||
call_if_key_exists(recomp::set_bgm_volume, config_json, "bgm_volume");
|
||||
call_if_key_exists(recomp::set_low_health_beeps_enabled, config_json, "low_health_beeps");
|
||||
return true;
|
||||
}
|
||||
|
||||
void recomp::load_config() {
|
||||
@ -400,35 +441,25 @@ void recomp::load_config() {
|
||||
std::filesystem::create_directories(recomp_dir);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(general_path)) {
|
||||
load_general_config(general_path);
|
||||
}
|
||||
else {
|
||||
// TODO error handling for failing to save config files after resetting them.
|
||||
|
||||
if (!load_general_config(general_path)) {
|
||||
// Set the general settings from an empty json to use defaults.
|
||||
set_general_settings_from_json({});
|
||||
save_general_config(general_path);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(graphics_path)) {
|
||||
load_graphics_config(graphics_path);
|
||||
}
|
||||
else {
|
||||
if (!load_graphics_config(graphics_path)) {
|
||||
reset_graphics_options();
|
||||
save_graphics_config(graphics_path);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(controls_path)) {
|
||||
load_controls_config(controls_path);
|
||||
}
|
||||
else {
|
||||
if (!load_controls_config(controls_path)) {
|
||||
recomp::reset_input_bindings();
|
||||
save_controls_config(controls_path);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(sound_path)) {
|
||||
load_sound_config(sound_path);
|
||||
}
|
||||
else {
|
||||
if (!load_sound_config(sound_path)) {
|
||||
recomp::reset_sound_settings();
|
||||
save_sound_config(sound_path);
|
||||
}
|
||||
@ -442,6 +473,8 @@ void recomp::save_config() {
|
||||
}
|
||||
|
||||
std::filesystem::create_directories(recomp_dir);
|
||||
|
||||
// TODO error handling for failing to save config files.
|
||||
|
||||
save_general_config(recomp_dir / general_filename);
|
||||
save_graphics_config(recomp_dir / graphics_filename);
|
||||
|
51
src/recomp/files.cpp
Normal file
51
src/recomp/files.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include "recomp_files.h"
|
||||
|
||||
constexpr std::u8string_view backup_suffix = u8".bak";
|
||||
constexpr std::u8string_view temp_suffix = u8".temp";
|
||||
|
||||
std::ifstream recomp::open_input_backup_file(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::filesystem::path backup_path{filepath};
|
||||
backup_path += backup_suffix;
|
||||
return std::ifstream{backup_path, mode};
|
||||
}
|
||||
|
||||
std::ifstream recomp::open_input_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::ifstream ret{filepath, mode};
|
||||
|
||||
// Check if the file failed to open and open the corresponding backup file instead if so.
|
||||
if (!ret.good()) {
|
||||
return open_input_backup_file(filepath, mode);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::ofstream recomp::open_output_file_with_backup(const std::filesystem::path& filepath, std::ios_base::openmode mode) {
|
||||
std::filesystem::path temp_path{filepath};
|
||||
temp_path += temp_suffix;
|
||||
std::ofstream temp_file_out{ temp_path, mode };
|
||||
|
||||
return temp_file_out;
|
||||
}
|
||||
|
||||
bool recomp::finalize_output_file_with_backup(const std::filesystem::path& filepath) {
|
||||
std::filesystem::path backup_path{filepath};
|
||||
backup_path += backup_suffix;
|
||||
|
||||
std::filesystem::path temp_path{filepath};
|
||||
temp_path += temp_suffix;
|
||||
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(filepath, ec)) {
|
||||
std::filesystem::copy_file(filepath, backup_path, std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::filesystem::copy_file(temp_path, filepath, std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
return false;
|
||||
}
|
||||
std::filesystem::remove(temp_path, ec);
|
||||
return true;
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
#include "recomp.h"
|
||||
#include "recomp_game.h"
|
||||
#include "recomp_config.h"
|
||||
#include "recomp_files.h"
|
||||
#include "../ultramodern/ultra64.h"
|
||||
#include "../ultramodern/ultramodern.hpp"
|
||||
|
||||
@ -96,42 +97,28 @@ struct {
|
||||
|
||||
const std::u8string save_folder = u8"saves";
|
||||
const std::u8string save_filename = std::u8string{recomp::mm_game_id} + u8".bin";
|
||||
const std::u8string save_filename_temp = std::u8string{recomp::mm_game_id} + u8".bin.temp";
|
||||
const std::u8string save_filename_backup = std::u8string{recomp::mm_game_id} + u8".bin.bak";
|
||||
|
||||
std::filesystem::path get_save_file_path() {
|
||||
return recomp::get_app_folder_path() / save_folder / save_filename;
|
||||
}
|
||||
|
||||
std::filesystem::path get_save_file_path_temp() {
|
||||
return recomp::get_app_folder_path() / save_folder / save_filename_temp;
|
||||
}
|
||||
|
||||
std::filesystem::path get_save_file_path_backup() {
|
||||
return recomp::get_app_folder_path() / save_folder / save_filename_backup;
|
||||
}
|
||||
|
||||
void update_save_file() {
|
||||
bool saving_failed = false;
|
||||
{
|
||||
std::ofstream save_file{ get_save_file_path_temp(), std::ios_base::binary };
|
||||
std::ofstream save_file = recomp::open_output_file_with_backup(get_save_file_path(), std::ios_base::binary);
|
||||
|
||||
if (save_file.good()) {
|
||||
std::lock_guard lock{ save_context.save_buffer_mutex };
|
||||
save_file.write(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
}
|
||||
else {
|
||||
recomp::message_box("Failed to write to the save file. Check your file permissions. If you have moved your appdata folder to Dropbox or similar, this can cause issues.");
|
||||
saving_failed = false;
|
||||
}
|
||||
}
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(get_save_file_path(), ec)) {
|
||||
std::filesystem::copy_file(get_save_file_path(), get_save_file_path_backup(), std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
printf("[ERROR] Failed to copy save file backup\n");
|
||||
}
|
||||
if (!saving_failed) {
|
||||
saving_failed = recomp::finalize_output_file_with_backup(get_save_file_path());
|
||||
}
|
||||
std::filesystem::copy_file(get_save_file_path_temp(), get_save_file_path(), std::filesystem::copy_options::overwrite_existing, ec);
|
||||
if (ec) {
|
||||
if (saving_failed) {
|
||||
recomp::message_box("Failed to write to the save file. Check your file permissions. If you have moved your appdata folder to Dropbox or similar, this can cause issues.");
|
||||
}
|
||||
}
|
||||
@ -199,24 +186,18 @@ void save_clear(uint32_t start, uint32_t size, char value) {
|
||||
|
||||
void ultramodern::init_saving(RDRAM_ARG1) {
|
||||
std::filesystem::path save_file_path = get_save_file_path();
|
||||
std::filesystem::path save_file_path_backup = get_save_file_path_backup();
|
||||
|
||||
// Ensure the save file directory exists.
|
||||
std::filesystem::create_directories(save_file_path.parent_path());
|
||||
|
||||
// Read the save file if it exists.
|
||||
std::ifstream save_file{ save_file_path, std::ios_base::binary };
|
||||
std::ifstream save_file = recomp::open_input_file_with_backup(save_file_path, std::ios_base::binary);
|
||||
if (save_file.good()) {
|
||||
save_file.read(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
} else {
|
||||
// Reading the save file faield, so try to read the backup save file.
|
||||
std::ifstream save_file_backup{ save_file_path_backup, std::ios_base::binary };
|
||||
if (save_file_backup.good()) {
|
||||
save_file_backup.read(save_context.save_buffer.data(), save_context.save_buffer.size());
|
||||
} else {
|
||||
// Otherwise clear the save file to all zeroes.
|
||||
save_context.save_buffer.fill(0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Otherwise clear the save file to all zeroes.
|
||||
save_context.save_buffer.fill(0);
|
||||
}
|
||||
|
||||
save_context.saving_thread = std::thread{saving_thread_func, PASS_RDRAM};
|
||||
|
Loading…
Reference in New Issue
Block a user