mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-02-21 00:39:53 +00:00
Savestates: safe saving while cellSaveData is active
This commit is contained in:
parent
9c9ece3d95
commit
7468d96c51
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user