Savestates: safe saving while cellSaveData is active

This commit is contained in:
Eladash 2024-03-27 14:44:33 +02:00 committed by Elad Ashkenazi
parent 9c9ece3d95
commit 7468d96c51
8 changed files with 172 additions and 40 deletions

View File

@ -3,6 +3,7 @@
#include "Emu/VFS.h"
#include "Emu/IdManager.h"
#include "Emu/localized_string.h"
#include "Emu/savestate_utils.hpp"
#include "Emu/Cell/lv2/sys_fs.h"
#include "Emu/Cell/lv2/sys_sync.h"
#include "Emu/Cell/lv2/sys_process.h"
@ -272,6 +273,14 @@ static std::vector<SaveDataEntry> get_save_entries(const std::string& base_dir,
static error_code select_and_delete(ppu_thread& ppu)
{
std::unique_lock hle_lock(g_fxo->get<hle_locks_t>(), std::try_to_lock);
if (!hle_lock)
{
ppu.state += cpu_flag::again;
return {};
}
std::unique_lock lock(g_fxo->get<savedata_manager>().mutex, std::try_to_lock);
if (!lock)
@ -699,6 +708,14 @@ static NEVER_INLINE error_code savedata_op(ppu_thread& ppu, u32 operation, u32 v
return {CELL_SAVEDATA_ERROR_PARAM, " (error %d)", ecode};
}
std::unique_lock hle_lock(g_fxo->get<hle_locks_t>(), std::try_to_lock);
if (!hle_lock)
{
ppu.state += cpu_flag::again;
return {};
}
std::unique_lock lock(g_fxo->get<savedata_manager>().mutex, std::try_to_lock);
if (!lock)

View File

@ -5,9 +5,9 @@
#include "Emu/Cell/lv2/sys_sync.h"
#include "Emu/Cell/lv2/sys_ppu_thread.h"
#include "Emu/Cell/lv2/sys_process.h"
#include "Emu/savestate_utils.hpp"
#include "sysPrxForUser.h"
#include "util/media_utils.h"
#include "util/init_mutex.hpp"
#ifdef _MSC_VER
#pragma warning(push, 0)
@ -625,38 +625,16 @@ struct vdec_context final
}
};
struct vdec_creation_lock
extern bool check_if_vdec_contexts_exist()
{
stx::init_mutex locked;
bool context_exists = false;
vdec_creation_lock()
idm::select<vdec_context>([&](u32, vdec_context&)
{
auto lk = locked.init();
}
};
context_exists = true;
});
extern bool try_lock_vdec_context_creation()
{
auto& lock = g_fxo->get<vdec_creation_lock>();
auto reset = lock.locked.reset();
if (reset)
{
bool context_exists = false;
idm::select<vdec_context>([&](u32, vdec_context&)
{
context_exists = true;
});
if (context_exists)
{
reset.set_init();
return false;
}
}
return true;
return context_exists;
}
extern void vdecEntry(ppu_thread& ppu, u32 vid)
@ -890,7 +868,7 @@ static error_code vdecOpen(ppu_thread& ppu, T type, U res, vm::cptr<CellVdecCb>
// Create decoder context
std::shared_ptr<vdec_context> vdec;
if (auto access = g_fxo->get<vdec_creation_lock>().locked.access(); access)
if (std::unique_lock lock{g_fxo->get<hle_locks_t>(), std::try_to_lock})
{
vdec = idm::make_ptr<vdec_context>(type->codecType, type->profileLevel, res->memAddr, res->memSize, cb->cbFunc, cb->cbArg);
}
@ -943,6 +921,14 @@ error_code cellVdecClose(ppu_thread& ppu, u32 handle)
{
cellVdec.warning("cellVdecClose(handle=0x%x)", handle);
std::unique_lock lock_hle{g_fxo->get<hle_locks_t>(), std::try_to_lock};
if (!lock_hle)
{
ppu.state += cpu_flag::again;
return {};
}
auto vdec = idm::get<vdec_context>(handle);
if (!vdec)

View File

@ -289,9 +289,9 @@ inline RT ppu_execute(ppu_thread& ppu, Args... args)
return func(ppu, args...);
}
#define BIND_FUNC_WITH_BLR(func) BIND_FUNC(func, if (cpu_flag::again - ppu.state) ppu.cia = static_cast<u32>(ppu.lr) & ~3)
#define BIND_FUNC_WITH_BLR(func, _module) BIND_FUNC(func, if (cpu_flag::again - ppu.state) ppu.cia = static_cast<u32>(ppu.lr) & ~3; else ppu.current_module = _module)
#define REG_FNID(_module, nid, func) ppu_module_manager::register_static_function<&func>(#_module, ppu_select_name(#func, nid), BIND_FUNC_WITH_BLR(func), ppu_generate_id(nid))
#define REG_FNID(_module, nid, func) ppu_module_manager::register_static_function<&func>(#_module, ppu_select_name(#func, nid), BIND_FUNC_WITH_BLR(func, #_module), ppu_generate_id(nid))
#define REG_FUNC(_module, func) REG_FNID(_module, #func, func)
@ -299,7 +299,7 @@ inline RT ppu_execute(ppu_thread& ppu, Args... args)
#define REG_VAR(_module, var) REG_VNID(_module, #var, var)
#define REG_HIDDEN_FUNC(func) ppu_function_manager::register_function<decltype(&func), &func>(BIND_FUNC_WITH_BLR(func))
#define REG_HIDDEN_FUNC(func) ppu_function_manager::register_function<decltype(&func), &func>(BIND_FUNC_WITH_BLR(func, ""))
#define REG_HIDDEN_FUNC_PURE(func) ppu_function_manager::register_function<decltype(&func), &func>(func)

View File

@ -2421,6 +2421,14 @@ ppu_thread::ppu_thread(utils::serial& ar)
ar(lv2_obj::g_priority_order_tag);
}
if (version >= 3)
{
// Function and module for HLE function relocation
// TODO: Use it
ar.pop<std::string>();
ar.pop<std::string>();
}
serialize_common(ar);
// Restore jm_mask
@ -2595,6 +2603,17 @@ void ppu_thread::save(utils::serial& ar)
ar(lv2_obj::g_priority_order_tag);
}
if (current_module && current_module[0])
{
ar(std::string{current_module});
ar(std::string{last_function});
}
else
{
ar(std::string{});
ar(std::string{});
}
serialize_common(ar);
auto [status, order] = lv2_obj::ppu_state(this, false);

View File

@ -295,6 +295,7 @@ public:
u64 syscall_args[8]{0}; // Last syscall arguments stored
const char* current_function{}; // Current function name for diagnosis, optimized for speed.
const char* last_function{}; // Sticky copy of current_function, is not cleared on function return
const char* current_module{}; // Current module name, for savestates.
const bool is_interrupt_thread; // True for interrupts-handler threads

View File

@ -2819,7 +2819,7 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta
}
}
extern bool try_lock_vdec_context_creation();
extern bool check_if_vdec_contexts_exist();
extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool revert_lock = false);
void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_stage)
@ -2861,14 +2861,35 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
return;
}
if (!IsStopped() && !try_lock_vdec_context_creation())
bool savedata_error = false;
bool vdec_error = false;
if (!g_fxo->get<hle_locks_t>().try_finalize([&]()
{
// List of conditions required for emulation to save properly
vdec_error = check_if_vdec_contexts_exist();
return !vdec_error;
}))
{
// Unlock SPUs
try_lock_spu_threads_in_a_state_compatible_with_savestates(true);
sys_log.error("Failed to savestate: HLE VDEC (video decoder) context(s) exist."
"\nLLE libvdec.sprx by selecting it in Advanced tab -> Firmware Libraries."
"\nYou need to close the game for it to take effect."
"\nIf you cannot close the game due to losing important progress, your best chance is to skip the current cutscenes if any are played and retry.");
savedata_error = !vdec_error; // For now it is implied a savedata error
if (vdec_error)
{
sys_log.error("Failed to savestate: HLE VDEC (video decoder) context(s) exist."
"\nLLE libvdec.sprx by selecting it in Advanced tab -> Firmware Libraries."
"\nYou need to close the game for it to take effect."
"\nIf you cannot close the game due to losing important progress, your best chance is to skip the current cutscenes if any are played and retry.");
}
if (savedata_error)
{
sys_log.error("Failed to savestate: Savedata operation is active."
"\nYour best chance is to wait for the current game saving operation to finish and retry."
"\nThe game is probably displaying a saving cicrle or other gesture to indicate that it is saving.");
}
m_emu_state_close_pending = false;

View File

@ -42,7 +42,7 @@ static std::array<serial_ver_t, 26> s_serial_versions;
}
SERIALIZATION_VER(global_version, 0, 16) // For stuff not listed here
SERIALIZATION_VER(ppu, 1, 1, 2/*PPU sleep order*/)
SERIALIZATION_VER(ppu, 1, 1, 2/*PPU sleep order*/, 3/*PPU FNID and module*/)
SERIALIZATION_VER(spu, 2, 1)
SERIALIZATION_VER(lv2_sync, 3, 1)
SERIALIZATION_VER(lv2_vm, 4, 1)
@ -340,3 +340,74 @@ extern u16 serial_breathe_and_tag(utils::serial& ar, std::string_view name, bool
{
return ::stx::serial_breathe_and_tag(ar, name, tag_bit);
}
[[noreturn]] void hle_locks_t::lock()
{
// Unreachable
ensure(false);
}
bool hle_locks_t::try_lock()
{
while (true)
{
auto [old, success] = lock_val.fetch_op([](s64& value)
{
if (value >= 0)
{
value++;
return true;
}
return false;
});
if (success)
{
return true;
}
if (old == finalized)
{
break;
}
lock_val.wait(old);
}
return false;
}
void hle_locks_t::unlock()
{
lock_val--;
}
bool hle_locks_t::try_finalize(std::function<bool()> test)
{
if (!test())
{
return false;
}
if (!lock_val.compare_and_swap_test(0, waiting_for_evaluation))
{
return false;
}
if (!test())
{
// Failed
ensure(lock_val.compare_and_swap_test(waiting_for_evaluation, 0));
return false;
}
ensure(lock_val.compare_and_swap_test(waiting_for_evaluation, finalized));
// Sanity check when debugging (the result is not expected to change after finalization)
//ensure(test());
lock_val.notify_all();
return true;
}

View File

@ -10,6 +10,23 @@ struct version_entry
ENABLE_BITWISE_SERIALIZATION;
};
struct hle_locks_t
{
atomic_t<s64> lock_val{0};
enum states : s64
{
waiting_for_evaluation = -1,
finalized = -2,
};
void lock();
bool try_lock();
void unlock();
bool try_finalize(std::function<bool()> test);
};
bool load_and_check_reserved(utils::serial& ar, usz size);
bool is_savestate_version_compatible(const std::vector<version_entry>& data, bool is_boot_check);
std::vector<version_entry> get_savestate_versioning_data(fs::file&& file, std::string_view filepath);