diff --git a/Utilities/File.h b/Utilities/File.h index cee48f95d9..14ac900a8b 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -71,6 +71,10 @@ namespace fs s64 atime; s64 mtime; s64 ctime; + + using enable_bitcopy = std::true_type; + + constexpr bool operator==(const stat_t&) const = default; }; // Helper, layout is equal to iovec struct @@ -105,6 +109,8 @@ namespace fs : stat_t{} { } + + using enable_bitcopy = std::false_type; }; // Directory handle base @@ -681,9 +687,10 @@ namespace fs T obj; u64 pos; - container_stream(T&& obj) + container_stream(T&& obj, const stat_t& init_stat = {}) : obj(std::forward(obj)) , pos(0) + , m_stat(init_stat) { } @@ -694,6 +701,7 @@ namespace fs bool trunc(u64 length) override { obj.resize(length); + update_time(true); return true; } @@ -708,6 +716,7 @@ namespace fs { std::copy(obj.cbegin() + pos, obj.cbegin() + pos + max, static_cast(buffer)); pos = pos + max; + update_time(); return max; } } @@ -743,6 +752,7 @@ namespace fs obj.insert(obj.end(), src + overlap, src + size); pos += size; + if (size) update_time(true); return size; } @@ -767,13 +777,33 @@ namespace fs { return obj.size(); } + + stat_t stat() override + { + return m_stat; + } + + private: + stat_t m_stat{}; + + void update_time(bool write = false) + { + // TODO: Accurate timestamps + m_stat.atime++; + + if (write) + { + m_stat.mtime = std::max(m_stat.atime, ++m_stat.mtime); + m_stat.ctime = m_stat.mtime; + } + } }; template - file make_stream(T&& container = T{}) + file make_stream(T&& container = T{}, const stat_t& stat = stat_t{}) { file result; - result.reset(std::make_unique>(std::forward(container))); + result.reset(std::make_unique>(std::forward(container), stat)); return result; } diff --git a/rpcs3/Crypto/unedat.h b/rpcs3/Crypto/unedat.h index 0ea7cc7176..80e6f12837 100644 --- a/rpcs3/Crypto/unedat.h +++ b/rpcs3/Crypto/unedat.h @@ -26,11 +26,17 @@ struct loaded_npdrm_keys } // TODO: Check if correct for ELF files usage - u128 last_key() const + u128 last_key(usz backwards = 0) const { + backwards++; const usz pos = dec_keys_pos; - return pos ? dec_keys[(pos - 1) % std::size(dec_keys)].load() : u128{}; + return pos >= backwards ? dec_keys[(pos - backwards) % std::size(dec_keys)].load() : u128{}; } + + SAVESTATE_INIT_POS(2); + loaded_npdrm_keys() = default; + loaded_npdrm_keys(utils::serial& ar); + void save(utils::serial& ar); }; struct NPD_HEADER diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index caa594da88..5b04f9dd90 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -49,6 +49,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case cpu_flag::pause: return "p"; case cpu_flag::suspend: return "s"; case cpu_flag::ret: return "ret"; + case cpu_flag::again: return "a"; case cpu_flag::signal: return "sig"; case cpu_flag::memory: return "mem"; case cpu_flag::pending: return "pend"; @@ -720,7 +721,7 @@ bool cpu_thread::check_state() noexcept } // Atomically clean wait flag and escape - if (!(flags & (cpu_flag::exit + cpu_flag::ret + cpu_flag::stop))) + if (!is_stopped(flags) && flags.none_of(cpu_flag::ret)) { // Check pause flags which hold thread inside check_state (ignore suspend/debug flags on cpu_flag::temp) if (flags & (cpu_flag::pause + cpu_flag::memory) || (cpu_can_stop && flags & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause + cpu_flag::suspend))) diff --git a/rpcs3/Emu/CPU/CPUThread.h b/rpcs3/Emu/CPU/CPUThread.h index b3912b8cbc..39cb3a3eca 100644 --- a/rpcs3/Emu/CPU/CPUThread.h +++ b/rpcs3/Emu/CPU/CPUThread.h @@ -19,6 +19,7 @@ enum class cpu_flag : u32 pause, // Thread suspended by suspend_all technique suspend, // Thread suspended ret, // Callback return requested + again, // Thread must complete the syscall after deserialization signal, // Thread received a signal (HLE) memory, // Thread must unlock memory mutex pending, // Thread has postponed work @@ -34,7 +35,7 @@ enum class cpu_flag : u32 // Test stopped state constexpr bool is_stopped(bs_t state) { - return !!(state & (cpu_flag::stop + cpu_flag::exit)); + return !!(state & (cpu_flag::stop + cpu_flag::exit + cpu_flag::again)); } // Test paused state diff --git a/rpcs3/Emu/Cell/MFC.h b/rpcs3/Emu/Cell/MFC.h index 736a53c6c6..a5f0a72f39 100644 --- a/rpcs3/Emu/Cell/MFC.h +++ b/rpcs3/Emu/Cell/MFC.h @@ -88,6 +88,8 @@ enum : u32 struct alignas(16) spu_mfc_cmd { + ENABLE_BITWISE_SERIALIZATION; + MFC cmd; u8 tag; u16 size; diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index 780b45f5d2..fdce713adf 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -342,6 +342,52 @@ void audio_port::tag(s32 offset) prev_touched_tag_nr = -1; } +cell_audio_thread::cell_audio_thread(utils::serial& ar) + : cell_audio_thread() +{ + ar(init); + + if (!init) + { + return; + } + + ar(key_count, event_period); + + keys.resize(ar); + + for (key_info& k : keys) + { + ar(k.start_period, k.flags, k.source); + k.port = lv2_event_queue::load_ptr(ar, k.port); + } + + ar(ports); +} + +void cell_audio_thread::save(utils::serial& ar) +{ + ar(init); + + if (!init) + { + return; + } + + USING_SERIALIZATION_VERSION(cellAudio); + + ar(key_count, event_period); + ar(keys.size()); + + for (const key_info& k : keys) + { + ar(k.start_period, k.flags, k.source); + lv2_event_queue::save_ptr(ar, k.port.get()); + } + + ar(ports); +} + std::tuple cell_audio_thread::count_port_buffer_tags() { AUDIT(cfg.buffering_enabled); @@ -615,6 +661,11 @@ void cell_audio_thread::operator()() thread_ctrl::scoped_priority high_prio(+1); + while (Emu.IsPaused()) + { + thread_ctrl::wait_for(5000); + } + u32 untouched_expected = 0; // Main cellAudio loop diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.h b/rpcs3/Emu/Cell/Modules/cellAudio.h index ac5342db72..5e1c47798e 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.h +++ b/rpcs3/Emu/Cell/Modules/cellAudio.h @@ -189,6 +189,16 @@ struct audio_port f32 last_tag_value[PORT_BUFFER_TAG_COUNT] = { 0 }; void tag(s32 offset = 0); + + audio_port() = default; + + // Handle copy ctor of atomic var + audio_port(const audio_port& r) + { + std::memcpy(this, &r, sizeof(r)); + } + + ENABLE_BITWISE_SERIALIZATION; }; struct cell_audio_config @@ -366,7 +376,7 @@ public: atomic_t m_update_configuration = audio_backend_update::NONE; shared_mutex mutex{}; - atomic_t init = 0; + atomic_t init = 0; u32 key_count = 0; u8 event_period = 0; @@ -390,10 +400,14 @@ public: bool m_backend_failed = false; bool m_audio_should_restart = false; - cell_audio_thread(); - void operator()(); + SAVESTATE_INIT_POS(9); + + cell_audio_thread(); + cell_audio_thread(utils::serial& ar); + void save(utils::serial& ar); + audio_port* open_port(); static constexpr auto thread_name = "cellAudio Thread"sv; diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.cpp b/rpcs3/Emu/Cell/Modules/cellCamera.cpp index 969124579f..32ab185702 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.cpp +++ b/rpcs3/Emu/Cell/Modules/cellCamera.cpp @@ -131,6 +131,25 @@ static const char* get_camera_attr_name(s32 value) return nullptr; } +camera_context::camera_context(utils::serial& ar) +{ + save(ar); +} + +void camera_context::save(utils::serial& ar) +{ + ar(init); + + if (!init) + { + return; + } + + USING_SERIALIZATION_VERSION_COND(ar.is_writing(), cellCamera); + + ar(notify_data_map, start_timestamp, read_mode, is_streaming, is_attached, is_open, info, attr, frame_num); +} + static bool check_dev_num(s32 dev_num) { return dev_num == 0; diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.h b/rpcs3/Emu/Cell/Modules/cellCamera.h index 7a9ec60c7c..10c7910d7e 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.h +++ b/rpcs3/Emu/Cell/Modules/cellCamera.h @@ -375,6 +375,8 @@ struct CellCameraInfoEx be_t container; be_t read_mode; vm::bptr pbuf[2]; + + ENABLE_BITWISE_SERIALIZATION; }; struct CellCameraReadEx @@ -392,6 +394,8 @@ class camera_context { u64 source; u64 flag; + + ENABLE_BITWISE_SERIALIZATION; }; public: @@ -433,14 +437,23 @@ public: struct attr_t { u32 v1, v2; + + ENABLE_BITWISE_SERIALIZATION; }; + attr_t attr[500]{}; atomic_t has_new_frame = false; atomic_t frame_num = 0; atomic_t frame_timestamp = 0; atomic_t bytes_read = 0; - atomic_t init = 0; + atomic_t init = 0; + + SAVESTATE_INIT_POS(16); + + camera_context() = default; + camera_context(utils::serial& ar); + void save(utils::serial& ar); static constexpr auto thread_name = "Camera Thread"sv; diff --git a/rpcs3/Emu/Cell/Modules/cellDmux.cpp b/rpcs3/Emu/Cell/Modules/cellDmux.cpp index 17c9960998..f50fee1585 100644 --- a/rpcs3/Emu/Cell/Modules/cellDmux.cpp +++ b/rpcs3/Emu/Cell/Modules/cellDmux.cpp @@ -165,6 +165,7 @@ public: static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 1023; + SAVESTATE_INIT_POS(34); ElementaryStream(Demuxer* dmux, u32 addr, u32 size, u32 fidMajor, u32 fidMinor, u32 sup1, u32 sup2, vm::ptr cbFunc, u32 cbArg, u32 spec); diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index bda8c011aa..ea86d8c286 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -94,7 +94,7 @@ public: static constexpr auto thread_name = "Gem Thread"sv; - atomic_t state = 0; + atomic_t state = 0; struct gem_color { @@ -131,6 +131,8 @@ public: u64 calibration_start_us{0}; // The start timestamp of the calibration in microseconds static constexpr u64 calibration_time_us = 500000; // The calibration supposedly takes 0.5 seconds (500000 microseconds) + + ENABLE_BITWISE_SERIALIZATION; }; CellGemAttribute attribute = {}; @@ -202,6 +204,30 @@ public: controllers[gem_num].port = 7u - gem_num; } } + + gem_config_data() = default; + + SAVESTATE_INIT_POS(15); + + void save(utils::serial& ar) + { + ar(state); + + if (!state) + { + return; + } + + USING_SERIALIZATION_VERSION_COND(ar.is_writing(), cellGem); + + ar(attribute, vc_attribute, status_flags, enable_pitch_correction, inertial_counter, controllers + , connected_controllers, update_started, camera_frame, memory_ptr, start_timestamp); + } + + gem_config_data(utils::serial& ar) + { + save(ar); + } }; static inline int32_t cellGemGetVideoConvertSize(s32 output_format) @@ -854,7 +880,7 @@ error_code cellGemEnd(ppu_thread& ppu) if (gem.state.compare_and_swap_test(1, 0)) { - if (u32 addr = gem.memory_ptr) + if (u32 addr = std::exchange(gem.memory_ptr, 0)) { sys_memory_free(ppu, addr); } diff --git a/rpcs3/Emu/Cell/Modules/cellGem.h b/rpcs3/Emu/Cell/Modules/cellGem.h index 9867ec9f8e..f518ae2d90 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.h +++ b/rpcs3/Emu/Cell/Modules/cellGem.h @@ -171,6 +171,8 @@ struct CellGemAttribute be_t memory_ptr; be_t spurs_addr; u8 spu_priorities[8]; + + ENABLE_BITWISE_SERIALIZATION; }; struct CellGemCameraState @@ -180,6 +182,8 @@ struct CellGemCameraState be_t gain; be_t pitch_angle; be_t pitch_angle_estimate; + + ENABLE_BITWISE_SERIALIZATION; }; struct CellGemExtPortData @@ -270,4 +274,6 @@ struct CellGemVideoConvertAttribute vm::bptr buffer_memory; vm::bptr video_data_out; u8 alpha; + + ENABLE_BITWISE_SERIALIZATION; }; diff --git a/rpcs3/Emu/Cell/Modules/cellJpgDec.h b/rpcs3/Emu/Cell/Modules/cellJpgDec.h index f00b181d03..d916a38a3b 100644 --- a/rpcs3/Emu/Cell/Modules/cellJpgDec.h +++ b/rpcs3/Emu/Cell/Modules/cellJpgDec.h @@ -110,6 +110,7 @@ struct CellJpgDecSubHandle static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 1023; + SAVESTATE_INIT_POS(35); u32 fd; u64 fileSize; diff --git a/rpcs3/Emu/Cell/Modules/cellKb.cpp b/rpcs3/Emu/Cell/Modules/cellKb.cpp index 99e62b96ff..5dcc5cb1cb 100644 --- a/rpcs3/Emu/Cell/Modules/cellKb.cpp +++ b/rpcs3/Emu/Cell/Modules/cellKb.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "Emu/IdManager.h" +#include "Emu/System.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Io/KeyboardHandler.h" @@ -31,6 +32,33 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } + +KeyboardHandlerBase::KeyboardHandlerBase(utils::serial* ar) +{ + if (!ar) + { + return; + } + + (*ar)(m_info.max_connect); + + if (m_info.max_connect) + { + Emu.DeferDeserialization([this]() + { + Init(m_info.max_connect); + init.init(); + }); + } +} + +void KeyboardHandlerBase::save(utils::serial& ar) +{ + const auto inited = init.access(); + + ar(inited ? m_info.max_connect : 0); +} + error_code cellKbInit(u32 max_connect) { sys_io.warning("cellKbInit(max_connect=%d)", max_connect); diff --git a/rpcs3/Emu/Cell/Modules/cellMic.h b/rpcs3/Emu/Cell/Modules/cellMic.h index 483f8fdff5..1565d788dc 100644 --- a/rpcs3/Emu/Cell/Modules/cellMic.h +++ b/rpcs3/Emu/Cell/Modules/cellMic.h @@ -356,7 +356,7 @@ public: std::unordered_map mic_list; shared_mutex mutex; - atomic_t init = 0; + atomic_t init = 0; static constexpr auto thread_name = "Microphone Thread"sv; diff --git a/rpcs3/Emu/Cell/Modules/cellMouse.cpp b/rpcs3/Emu/Cell/Modules/cellMouse.cpp index c3d54d9a64..8cc2d366c7 100644 --- a/rpcs3/Emu/Cell/Modules/cellMouse.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMouse.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "Emu/IdManager.h" +#include "Emu/System.h" #include "Emu/Cell/PPUModule.h" #include "Emu/Io/MouseHandler.h" @@ -32,6 +33,32 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +MouseHandlerBase::MouseHandlerBase(utils::serial* ar) +{ + if (!ar) + { + return; + } + + (*ar)(m_info.max_connect); + + if (m_info.max_connect) + { + Emu.DeferDeserialization([this]() + { + Init(m_info.max_connect); + init.init(); + }); + } +} + +void MouseHandlerBase::save(utils::serial& ar) +{ + const auto inited = init.access(); + + ar(inited ? m_info.max_connect : 0); +} + error_code cellMouseInit(u32 max_connect) { sys_io.warning("cellMouseInit(max_connect=%d)", max_connect); diff --git a/rpcs3/Emu/Cell/Modules/cellMusic.cpp b/rpcs3/Emu/Cell/Modules/cellMusic.cpp index 42fa511078..e9edc9d2e0 100644 --- a/rpcs3/Emu/Cell/Modules/cellMusic.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMusic.cpp @@ -74,10 +74,32 @@ struct music_state std::shared_ptr handler; music_selection_context current_selection_context; + SAVESTATE_INIT_POS(16); + music_state() { handler = Emu.GetCallbacks().get_music_handler(); } + + music_state(utils::serial& ar) + : music_state() + { + save(ar); + } + + void save(utils::serial& ar) + { + ar(func); + + if (!func) + { + return; + } + + USING_SERIALIZATION_VERSION_COND(ar.is_writing(), cellMusic); + + ar(userData); + } }; error_code cell_music_select_contents() diff --git a/rpcs3/Emu/Cell/Modules/cellPad.cpp b/rpcs3/Emu/Cell/Modules/cellPad.cpp index ced267288a..5174e29006 100644 --- a/rpcs3/Emu/Cell/Modules/cellPad.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPad.cpp @@ -50,6 +50,18 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } +pad_info::pad_info(utils::serial& ar) + : max_connect(ar) + , port_setting(ar) +{ +} + +void pad_info::save(utils::serial& ar) +{ + ar(max_connect, port_setting); +} + + error_code cellPadInit(u32 max_connect) { sys_io.warning("cellPadInit(max_connect=%d)", max_connect); diff --git a/rpcs3/Emu/Cell/Modules/cellPad.h b/rpcs3/Emu/Cell/Modules/cellPad.h index 404caadc08..f7d61f627c 100644 --- a/rpcs3/Emu/Cell/Modules/cellPad.h +++ b/rpcs3/Emu/Cell/Modules/cellPad.h @@ -3,6 +3,7 @@ #include "Emu/Io/pad_types.h" #include +#include "util/types.hpp" enum CellPadError : u32 { @@ -197,6 +198,12 @@ struct pad_info { atomic_t max_connect = 0; std::array port_setting{ 0 }; + + SAVESTATE_INIT_POS(11); + + pad_info() = default; + pad_info(utils::serial& ar); + void save(utils::serial& ar); }; error_code cellPadGetData(u32 port_no, vm::ptr data); diff --git a/rpcs3/Emu/Cell/Modules/cellSearch.cpp b/rpcs3/Emu/Cell/Modules/cellSearch.cpp index 6efaec94ad..2fc1e16db1 100644 --- a/rpcs3/Emu/Cell/Modules/cellSearch.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSearch.cpp @@ -81,6 +81,8 @@ struct search_content_t CellSearchVideoListInfo video_list; CellSearchVideoSceneInfo scene; } data; + + ENABLE_BITWISE_SERIALIZATION; }; using content_id_type = std::pair>; @@ -90,6 +92,8 @@ struct content_id_map std::unordered_map> map; shared_mutex mutex; + + SAVESTATE_INIT_POS(36); }; struct search_object_t @@ -98,6 +102,7 @@ struct search_object_t static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 1024; // TODO + SAVESTATE_INIT_POS(36.1); std::vector content_ids; }; diff --git a/rpcs3/Emu/Cell/Modules/cellSysutil.cpp b/rpcs3/Emu/Cell/Modules/cellSysutil.cpp index 9f23325f64..bfe19004d8 100644 --- a/rpcs3/Emu/Cell/Modules/cellSysutil.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSysutil.cpp @@ -68,6 +68,8 @@ struct sysutil_cb_manager struct alignas(8) registered_cb { + ENABLE_BITWISE_SERIALIZATION; + vm::ptr callback; vm::ptr user_data; }; @@ -78,6 +80,21 @@ struct sysutil_cb_manager atomic_t draw_cb_started{}; atomic_t read_counter{0}; + + SAVESTATE_INIT_POS(13); + + sysutil_cb_manager() = default; + + sysutil_cb_manager(utils::serial& ar) + { + ar(callbacks); + } + + void save(utils::serial& ar) + { + ensure(!registered); + ar(callbacks); + } }; extern void sysutil_register_cb(std::function&& cb) diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.cpp b/rpcs3/Emu/Cell/Modules/cellVdec.cpp index c3b0b42dc0..5393b46a8c 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVdec.cpp @@ -7,6 +7,7 @@ #include "Emu/Cell/lv2/sys_process.h" #include "sysPrxForUser.h" #include "util/media_utils.h" +#include "util/init_mutex.hpp" #ifdef _MSC_VER #pragma warning(push, 0) @@ -173,6 +174,7 @@ struct vdec_context final static const u32 id_base = 0xf0000000; static const u32 id_step = 0x00000100; static const u32 id_count = 1024; + SAVESTATE_INIT_POS(24); u32 handle = 0; @@ -627,7 +629,43 @@ struct vdec_context final } }; -static void vdecEntry(ppu_thread& ppu, u32 vid) +struct vdec_creation_lock +{ + stx::init_mutex locked; + + vdec_creation_lock() + { + locked.init(); + } +}; + +extern bool try_lock_vdec_context_creation() +{ + bool exist = false; + + auto& lock = g_fxo->get(); + auto reset = lock.locked.reset(); + + if (reset) + { + bool context_exists = false; + + idm::select([&](u32, vdec_context&) + { + context_exists = true; + }); + + if (context_exists) + { + reset.set_init(); + return false; + } + } + + return true; +} + +extern void vdecEntry(ppu_thread& ppu, u32 vid) { idm::get(vid)->exec(ppu, vid); @@ -856,9 +894,20 @@ static error_code vdecOpen(ppu_thread& ppu, T type, U res, vm::cptr } // Create decoder context - const u32 vid = idm::make(type->codecType, type->profileLevel, res->memAddr, res->memSize, cb->cbFunc, cb->cbArg); + std::shared_ptr vdec; + + if (auto access = g_fxo->get().locked.access(); access) + { + vdec = idm::make_ptr(type->codecType, type->profileLevel, res->memAddr, res->memSize, cb->cbFunc, cb->cbArg); + } + else + { + ppu.state += cpu_flag::again; + return {}; + } + + const u32 vid = idm::last_id(); - auto vdec = idm::get(vid); ensure(vdec); vdec->handle = vid; diff --git a/rpcs3/Emu/Cell/Modules/cellVoice.cpp b/rpcs3/Emu/Cell/Modules/cellVoice.cpp index 3d74687565..5472361056 100644 --- a/rpcs3/Emu/Cell/Modules/cellVoice.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVoice.cpp @@ -46,6 +46,12 @@ void voice_manager::reset() queue_keys.clear(); } +void voice_manager::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION_COND(ar.is_writing(), cellVoice); + ar(id_ctr, port_source, ports, queue_keys, voice_service_started); +} + error_code cellVoiceConnectIPortToOPort(u32 ips, u32 ops) { cellVoice.todo("cellVoiceConnectIPortToOPort(ips=%d, ops=%d)", ips, ops); diff --git a/rpcs3/Emu/Cell/Modules/cellVoice.h b/rpcs3/Emu/Cell/Modules/cellVoice.h index 6fc6037096..80fac88c29 100644 --- a/rpcs3/Emu/Cell/Modules/cellVoice.h +++ b/rpcs3/Emu/Cell/Modules/cellVoice.h @@ -182,6 +182,8 @@ struct voice_manager { s32 state = CELLVOICE_PORTSTATE_NULL; CellVoicePortParam info; + + ENABLE_BITWISE_SERIALIZATION; }; // See cellVoiceCreatePort @@ -210,4 +212,10 @@ struct voice_manager void reset(); shared_mutex mtx; atomic_t is_init{ false }; + + SAVESTATE_INIT_POS(17); + + voice_manager() = default; + voice_manager(utils::serial& ar) { save(ar); } + void save(utils::serial& ar); }; diff --git a/rpcs3/Emu/Cell/Modules/cellVpost.h b/rpcs3/Emu/Cell/Modules/cellVpost.h index 00b14adeb8..6e7919158e 100644 --- a/rpcs3/Emu/Cell/Modules/cellVpost.h +++ b/rpcs3/Emu/Cell/Modules/cellVpost.h @@ -340,6 +340,7 @@ public: static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 1023; + SAVESTATE_INIT_POS(23); const bool to_rgba; diff --git a/rpcs3/Emu/Cell/Modules/sceNpSns.h b/rpcs3/Emu/Cell/Modules/sceNpSns.h index cba77db26a..dc0106c763 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpSns.h +++ b/rpcs3/Emu/Cell/Modules/sceNpSns.h @@ -47,6 +47,11 @@ struct sns_fb_handle_t static const u32 id_step = 1; static const u32 id_count = SCE_NP_SNS_FB_HANDLE_SLOT_MAX + 1; static const u32 invalid = SCE_NP_SNS_FB_INVALID_HANDLE; + + SAVESTATE_INIT_POS(20); + sns_fb_handle_t() = default; + sns_fb_handle_t(utils::serial&){} + void save(utils::serial&){} }; // Initialization parameters for functionalities coordinated with Facebook diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp index 6bafa8ab9a..9439922fa8 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp @@ -33,10 +33,51 @@ struct trophy_context_t static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 4; + SAVESTATE_INIT_POS(42); std::string trp_name; std::unique_ptr tropusr; bool read_only = false; + + trophy_context_t() = default; + + trophy_context_t(utils::serial& ar) + : trp_name(ar.operator std::string()) + { + std::string trophy_path = vfs::get(Emu.GetDir() + "TROPDIR/" + trp_name + "/TROPHY.TRP"); + fs::file trp_stream(trophy_path); + + if (!trp_stream) + { + // Fallback + trophy_path = vfs::get("/dev_bdvd/PS3_GAME/TROPDIR/" + trp_name + "/TROPHY.TRP"); + trp_stream.open(trophy_path); + } + + if (!ar.operator bool()) + { + ar(read_only); + return; + } + + ar(read_only); + + if (!trp_stream && g_cfg.savestate.state_inspection_mode) + { + return; + } + + const std::string trophyPath = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + trp_name; + tropusr = std::make_unique(); + const std::string trophyUsrPath = trophyPath + "/TROPUSR.DAT"; + const std::string trophyConfPath = trophyPath + "/TROPCONF.SFM"; + ensure(tropusr->Load(trophyUsrPath, trophyConfPath).success); + } + + void save(utils::serial& ar) + { + ar(trp_name, tropusr.operator bool(), read_only); + } }; struct trophy_handle_t @@ -44,8 +85,21 @@ struct trophy_handle_t static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 4; + SAVESTATE_INIT_POS(43); bool is_aborted = false; + + trophy_handle_t() = default; + + trophy_handle_t(utils::serial& ar) + : is_aborted(ar) + { + } + + void save(utils::serial& ar) + { + ar(is_aborted); + } }; struct sce_np_trophy_manager @@ -103,6 +157,25 @@ struct sce_np_trophy_manager return res; } + + SAVESTATE_INIT_POS(12); + + sce_np_trophy_manager() = default; + + sce_np_trophy_manager(utils::serial& ar) + : is_initialized(ar) + { + } + + void save(utils::serial& ar) + { + ar(is_initialized); + + if (is_initialized) + { + USING_SERIALIZATION_VERSION(sceNpTrophy); + } + } }; template<> diff --git a/rpcs3/Emu/Cell/Modules/sys_heap.cpp b/rpcs3/Emu/Cell/Modules/sys_heap.cpp index 69f04ea42a..a502e26486 100644 --- a/rpcs3/Emu/Cell/Modules/sys_heap.cpp +++ b/rpcs3/Emu/Cell/Modules/sys_heap.cpp @@ -11,6 +11,7 @@ struct HeapInfo static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 1023; + SAVESTATE_INIT_POS(22); const std::string name; diff --git a/rpcs3/Emu/Cell/Modules/sys_mempool.cpp b/rpcs3/Emu/Cell/Modules/sys_mempool.cpp index d4aecc94e1..ad70cc173e 100644 --- a/rpcs3/Emu/Cell/Modules/sys_mempool.cpp +++ b/rpcs3/Emu/Cell/Modules/sys_mempool.cpp @@ -18,6 +18,7 @@ struct memory_pool_t static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 1023; + SAVESTATE_INIT_POS(21); u32 mutexid; u32 condid; diff --git a/rpcs3/Emu/Cell/PPUFunction.cpp b/rpcs3/Emu/Cell/PPUFunction.cpp index 3ae10c69db..5ac681d6af 100644 --- a/rpcs3/Emu/Cell/PPUFunction.cpp +++ b/rpcs3/Emu/Cell/PPUFunction.cpp @@ -1,8 +1,10 @@ #include "stdafx.h" #include "PPUFunction.h" #include "Utilities/JIT.h" +#include "util/serialization.hpp" #include "PPUModule.h" +#include "PPUInterpreter.h" // Get function name by FNID extern std::string ppu_get_function_name(const std::string& _module, u32 fnid) @@ -1943,6 +1945,16 @@ auto gen_ghc_cpp_trampoline(ppu_intrp_func_t fn_target) #error "Not implemented!" #endif +ppu_function_manager::ppu_function_manager(utils::serial& ar) + : addr(ar) +{ +} + +void ppu_function_manager::save(utils::serial& ar) +{ + ar(addr); +} + std::vector& ppu_function_manager::access(bool ghc) { static std::vector list diff --git a/rpcs3/Emu/Cell/PPUFunction.h b/rpcs3/Emu/Cell/PPUFunction.h index 5b4569a02b..6f8829b353 100644 --- a/rpcs3/Emu/Cell/PPUFunction.h +++ b/rpcs3/Emu/Cell/PPUFunction.h @@ -115,6 +115,7 @@ namespace ppu_func_detail static FORCE_INLINE void put_result(ppu_thread& ppu, const T& result) { + if (ppu.state & cpu_flag::again) return; ppu.gpr[3] = ppu_gpr_cast(result); } }; @@ -126,6 +127,7 @@ namespace ppu_func_detail static FORCE_INLINE void put_result(ppu_thread& ppu, const T& result) { + if (ppu.state & cpu_flag::again) return; ppu.fpr[1] = static_cast(result); } }; @@ -137,6 +139,7 @@ namespace ppu_func_detail static FORCE_INLINE void put_result(ppu_thread& ppu, const T& result) { + if (ppu.state & cpu_flag::again) return; ppu.vr[2] = result; } }; @@ -299,6 +302,9 @@ public: // Allocation address u32 addr = 0; + + void save(utils::serial& ar); + ppu_function_manager(utils::serial& ar); }; template diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index 9d6f697849..2b0a6f5f86 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -4,6 +4,7 @@ #include "Utilities/bin_patch.h" #include "Utilities/StrUtil.h" #include "Utilities/address_range.h" +#include "util/serialization.hpp" #include "Crypto/sha1.h" #include "Crypto/unself.h" #include "Loader/ELF.h" @@ -152,7 +153,7 @@ struct ppu_linkage_info }; // Initialize static modules. -static void ppu_initialize_modules(ppu_linkage_info* link) +static void ppu_initialize_modules(ppu_linkage_info* link, utils::serial* ar = nullptr) { if (!link->modules.empty()) { @@ -280,7 +281,10 @@ static void ppu_initialize_modules(ppu_linkage_info* link) u32& hle_funcs_addr = g_fxo->get().addr; // Allocate memory for the array (must be called after fixed allocations) - hle_funcs_addr = vm::alloc(::size32(hle_funcs) * 8, vm::main); + if (!hle_funcs_addr) + hle_funcs_addr = vm::alloc(::size32(hle_funcs) * 8, vm::main); + else + vm::page_protect(hle_funcs_addr, utils::align(::size32(hle_funcs) * 8, 0x1000), 0, vm::page_writable); // Initialize as PPU executable code ppu_register_range(hle_funcs_addr, ::size32(hle_funcs) * 8); @@ -319,6 +323,71 @@ static void ppu_initialize_modules(ppu_linkage_info* link) ppu_loader.trace("Registered static module: %s", _module->name); } + struct hle_vars_save + { + hle_vars_save() = default; + + hle_vars_save(const hle_vars_save&) = delete; + + hle_vars_save& operator =(const hle_vars_save&) = delete; + + hle_vars_save(utils::serial& ar) + { + auto& manager = ppu_module_manager::get(); + + while (true) + { + const std::string name = ar.operator std::string(); + + if (name.empty()) + { + // Null termination + break; + } + + const auto _module = manager.at(name); + + auto& variable = _module->variables; + + for (u32 i = 0, end = ar.operator usz(); i < end; i++) + { + auto* ptr = &variable.at(ar.operator u32()); + ptr->addr = ar.operator u32(); + ensure(!!ptr->var); + } + } + } + + void save(utils::serial& ar) + { + for (auto& pair : ppu_module_manager::get()) + { + const auto _module = pair.second; + + ar(_module->name); + + ar(_module->variables.size()); + + for (auto& variable : _module->variables) + { + ar(variable.first, variable.second.addr); + } + } + + // Null terminator + ar(std::string{}); + } + }; + + if (ar) + { + g_fxo->init(*ar); + } + else + { + g_fxo->init(); + } + for (auto& pair : ppu_module_manager::get()) { const auto _module = pair.second; @@ -345,7 +414,11 @@ static void ppu_initialize_modules(ppu_linkage_info* link) ppu_loader.trace("** &0x%08X: %s (size=0x%x, align=0x%x)", variable.first, variable.second.name, variable.second.size, variable.second.align); // Allocate HLE variable - if (variable.second.size >= 0x10000 || variable.second.align >= 0x10000) + if (ar) + { + // Already loaded + } + else if (variable.second.size >= 0x10000 || variable.second.align >= 0x10000) { variable.second.addr = vm::alloc(variable.second.size, vm::main, std::max(variable.second.align, 0x10000)); } @@ -790,6 +863,49 @@ void ppu_manual_load_imports_exports(u32 imports_start, u32 imports_size, u32 ex ppu_load_imports(_main.relocs, &link, imports_start, imports_start + imports_size); } +// For savestates +extern bool is_memory_read_only_of_executable(u32 addr) +{ + if (g_cfg.savestate.state_inspection_mode) + { + return false; + } + + const auto _main = g_fxo->try_get(); + ensure(_main); + + for (const auto& seg : _main->segs) + { + if (!seg.addr || (seg.flags & 0x2) /* W */) + continue; + + if (addr >= seg.addr && addr < (seg.addr + seg.size)) + return true; + } + + return false; +} + +void init_ppu_functions(utils::serial* ar, bool full = false) +{ + g_fxo->need(); + + if (ar) + { + ensure(vm::check_addr(g_fxo->init(*ar)->addr)); + } + else + g_fxo->init(); + + if (full) + { + ensure(ar); + + // Initialize HLE modules + ppu_initialize_modules(&g_fxo->get(), ar); + } +} + static void ppu_check_patch_spu_images(const ppu_segment& seg) { const std::string_view seg_view{vm::get_super_ptr(seg.addr), seg.size}; @@ -894,7 +1010,7 @@ static void ppu_check_patch_spu_images(const ppu_segment& seg) void try_spawn_ppu_if_exclusive_program(const ppu_module& m) { // If only PRX/OVL has been loaded at Emu.BootGame(), launch a single PPU thread so its memory can be viewed - if (Emu.IsReady() && g_fxo->get().segs.empty()) + if (Emu.IsReady() && g_fxo->get().segs.empty() && !Emu.DeserialManager()) { ppu_thread_params p { @@ -911,7 +1027,7 @@ void try_spawn_ppu_if_exclusive_program(const ppu_module& m) } } -std::shared_ptr ppu_load_prx(const ppu_prx_object& elf, const std::string& path, s64 file_offset) +std::shared_ptr ppu_load_prx(const ppu_prx_object& elf, const std::string& path, s64 file_offset, utils::serial* ar) { if (elf != elf_error::ok) { @@ -919,7 +1035,7 @@ std::shared_ptr ppu_load_prx(const ppu_prx_object& elf, const std::stri } // Create new PRX object - const auto prx = idm::make_ptr(); + const auto prx = !ar ? idm::make_ptr() : std::make_shared(); // Access linkage information object auto& link = g_fxo->get(); @@ -957,15 +1073,16 @@ std::shared_ptr ppu_load_prx(const ppu_prx_object& elf, const std::stri //const u32 init_addr = ::narrow(prog.p_vaddr); // Alloc segment memory - const u32 addr = vm::alloc(mem_size, vm::main); + // Or use saved address + const u32 addr = !ar ? vm::alloc(mem_size, vm::main) : ar->operator u32(); - if (!addr) + if (!vm::check_addr(addr)) { fmt::throw_exception("vm::alloc() failed (size=0x%x)", mem_size); } // Copy segment data - std::memcpy(vm::base(addr), prog.bin.data(), file_size); + if (!ar) std::memcpy(vm::base(addr), prog.bin.data(), file_size); ppu_loader.warning("**** Loaded to 0x%x...0x%x (size=0x%x)", addr, addr + mem_size - 1, mem_size); // Hash segment @@ -1068,6 +1185,11 @@ std::shared_ptr ppu_load_prx(const ppu_prx_object& elf, const std::stri const u64 rdata = _rel.data = data_base + rel.ptr.addr(); prx->relocs.emplace_back(_rel); + if (ar) + { + break; + } + switch (rtype) { case 1: // R_PPC64_ADDR32 @@ -1293,7 +1415,7 @@ void ppu_unload_prx(const lv2_prx& prx) } } -bool ppu_load_exec(const ppu_exec_object& elf) +bool ppu_load_exec(const ppu_exec_object& elf, utils::serial* ar) { if (elf != elf_error::ok) { @@ -1316,8 +1438,7 @@ bool ppu_load_exec(const ppu_exec_object& elf) } } - g_fxo->need(); - g_fxo->need(); + init_ppu_functions(ar, false); // Set for delayed initialization in ppu_initialize() auto& _main = g_fxo->get(); @@ -1390,7 +1511,17 @@ bool ppu_load_exec(const ppu_exec_object& elf) return false; } - if (!vm::falloc(addr, size, vm::main)) + const bool already_loaded = ar && (_seg.flags & 0x2); + + if (already_loaded) + { + if (!vm::check_addr(addr, vm::page_readable, size)) + { + ppu_loader.fatal("ppu_load_exec(): Archived PPU executable memory has not been found! (addr=0x%x, memsz=0x%x)", addr, size); + return false; + } + } + else if (!vm::falloc(addr, size, vm::main)) { ppu_loader.error("vm::falloc(vm::main) failed (addr=0x%x, memsz=0x%x)", addr, size); // TODO @@ -1402,7 +1533,18 @@ bool ppu_load_exec(const ppu_exec_object& elf) } // Copy segment data, hash it - std::memcpy(vm::base(addr), prog.bin.data(), prog.bin.size()); + if (!already_loaded) + { + std::memcpy(vm::base(addr), prog.bin.data(), prog.bin.size()); + } + else + { + // For backwards compatibility: already loaded memory will always be writable + const u32 size0 = utils::align(size + addr % 0x10000, 0x10000); + const u32 addr0 = addr & -0x10000; + vm::page_protect(addr0, size0, 0, vm::page_writable | vm::page_readable, vm::page_executable); + } + sha1_update(&sha, reinterpret_cast(&prog.p_vaddr), sizeof(prog.p_vaddr)); sha1_update(&sha, reinterpret_cast(&prog.p_memsz), sizeof(prog.p_memsz)); sha1_update(&sha, prog.bin.data(), prog.bin.size()); @@ -1476,7 +1618,7 @@ bool ppu_load_exec(const ppu_exec_object& elf) } // Initialize HLE modules - ppu_initialize_modules(&link); + ppu_initialize_modules(&link, ar); // Embedded SPU elf patching for (const auto& seg : _main.segs) @@ -1641,6 +1783,46 @@ bool ppu_load_exec(const ppu_exec_object& elf) } } + // Initialize memory stats (according to sdk version) + u32 mem_size; + if (g_ps3_process_info.get_cellos_appname() == "vsh.self"sv) + { + // Because vsh.self comes before any generic application, more memory is available to it + mem_size = 0xF000000; + } + else if (sdk_version > 0x0021FFFF) + { + mem_size = 0xD500000; + } + else if (sdk_version > 0x00192FFF) + { + mem_size = 0xD300000; + } + else if (sdk_version > 0x0018FFFF) + { + mem_size = 0xD100000; + } + else if (sdk_version > 0x0017FFFF) + { + mem_size = 0xD000000; + } + else if (sdk_version > 0x00154FFF) + { + mem_size = 0xCC00000; + } + else + { + mem_size = 0xC800000; + } + + if (g_cfg.core.debug_console_mode) + { + // TODO: Check for all sdk versions + mem_size += 0xC000000; + } + + if (!ar) g_fxo->init(mem_size); + // Initialize process std::vector> loaded_modules; @@ -1658,9 +1840,9 @@ bool ppu_load_exec(const ppu_exec_object& elf) load_libs.emplace("libsysmodule.sprx"); } - if (g_ps3_process_info.get_cellos_appname() == "vsh.self"sv) + if (ar || g_ps3_process_info.get_cellos_appname() == "vsh.self"sv) { - // Cannot be used with vsh.self (it self-manages itself) + // Cannot be used with vsh.self or savestates (they self-manage itself) load_libs.clear(); } @@ -1676,6 +1858,25 @@ bool ppu_load_exec(const ppu_exec_object& elf) // Program entry u32 entry = 0; + // Set path (TODO) + _main.name.clear(); + _main.path = vfs::get(Emu.argv[0]); + + // Analyse executable (TODO) + _main.analyse(0, static_cast(elf.header.e_entry), end, applied); + + // Validate analyser results (not required) + _main.validate(0); + + // Set SDK version + g_ps3_process_info.sdk_ver = sdk_version; + + // Set ppc fixed allocations segment permission + g_ps3_process_info.ppc_seg = ppc_seg; + + void init_fxo_for_exec(utils::serial* ar, bool full); + init_fxo_for_exec(ar, false); + if (!load_libs.empty()) { for (const auto& name : load_libs) @@ -1686,7 +1887,7 @@ bool ppu_load_exec(const ppu_exec_object& elf) { ppu_loader.warning("Loading library: %s", name); - auto prx = ppu_load_prx(obj, lle_dir + name, 0); + auto prx = ppu_load_prx(obj, lle_dir + name, 0, nullptr); if (prx->funcs.empty()) { @@ -1715,21 +1916,11 @@ bool ppu_load_exec(const ppu_exec_object& elf) } } - // Set path (TODO) - _main.name.clear(); - _main.path = vfs::get(Emu.argv[0]); - - // Analyse executable (TODO) - _main.analyse(0, static_cast(elf.header.e_entry), end, applied); - - // Validate analyser results (not required) - _main.validate(0); - - // Set SDK version - g_ps3_process_info.sdk_ver = sdk_version; - - // Set ppc fixed allocations segment permission - g_ps3_process_info.ppc_seg = ppc_seg; + if (ar) + { + error_handler.errored = false; + return true; + } if (ppc_seg != 0x0) { @@ -1804,44 +1995,6 @@ bool ppu_load_exec(const ppu_exec_object& elf) ppu->gpr[1] -= Emu.data.size(); } - // Initialize memory stats (according to sdk version) - u32 mem_size; - if (g_ps3_process_info.get_cellos_appname() == "vsh.self"sv) - { - // Because vsh.self comes before any generic application, more memory is available to it - mem_size = 0xF000000; - } - else if (sdk_version > 0x0021FFFF) - { - mem_size = 0xD500000; - } - else if (sdk_version > 0x00192FFF) - { - mem_size = 0xD300000; - } - else if (sdk_version > 0x0018FFFF) - { - mem_size = 0xD100000; - } - else if (sdk_version > 0x0017FFFF) - { - mem_size = 0xD000000; - } - else if (sdk_version > 0x00154FFF) - { - mem_size = 0xCC00000; - } - else - { - mem_size = 0xC800000; - } - - if (g_cfg.core.debug_console_mode) - { - // TODO: Check for all sdk versions - mem_size += 0xC000000; - } - if (Emu.init_mem_containers) { // Refer to sys_process_exit2 for explanation @@ -1913,7 +2066,7 @@ bool ppu_load_exec(const ppu_exec_object& elf) return true; } -std::pair, CellError> ppu_load_overlay(const ppu_exec_object& elf, const std::string& path, s64 file_offset) +std::pair, CellError> ppu_load_overlay(const ppu_exec_object& elf, const std::string& path, s64 file_offset, utils::serial* ar) { if (elf != elf_error::ok) { @@ -1975,7 +2128,17 @@ std::pair, CellError> ppu_load_overlay(const ppu_ex if (prog.bin.size() > size || prog.bin.size() != prog.p_filesz) fmt::throw_exception("Invalid binary size (0x%llx, memsz=0x%x)", prog.bin.size(), size); - if (!vm::get(vm::any, 0x30000000)->falloc(addr, size)) + const bool already_loaded = ar /*&& !!(_seg.flags & 0x2)*/; + + if (already_loaded) + { + if (!vm::check_addr(addr, vm::page_readable, size)) + { + ppu_loader.fatal("ppu_load_overlay(): Archived PPU overlay memory has not been found! (addr=0x%x, memsz=0x%x)", addr, size); + return {nullptr, CELL_EABORT}; + } + } + else if (!vm::get(vm::any, 0x30000000)->falloc(addr, size)) { ppu_loader.error("ppu_load_overlay(): vm::falloc() failed (addr=0x%x, memsz=0x%x)", addr, size); @@ -1990,7 +2153,7 @@ std::pair, CellError> ppu_load_overlay(const ppu_ex } // Copy segment data, hash it - std::memcpy(vm::base(addr), prog.bin.data(), prog.bin.size()); + if (!already_loaded) std::memcpy(vm::base(addr), prog.bin.data(), prog.bin.size()); sha1_update(&sha, reinterpret_cast(&prog.p_vaddr), sizeof(prog.p_vaddr)); sha1_update(&sha, reinterpret_cast(&prog.p_memsz), sizeof(prog.p_memsz)); sha1_update(&sha, prog.bin.data(), prog.bin.size()); @@ -2158,9 +2321,11 @@ std::pair, CellError> ppu_load_overlay(const ppu_ex // Validate analyser results (not required) ovlm->validate(0); - idm::import_existing(ovlm); - - try_spawn_ppu_if_exclusive_program(*ovlm); + if (!ar) + { + idm::import_existing(ovlm); + try_spawn_ppu_if_exclusive_program(*ovlm); + } return {std::move(ovlm), {}}; } diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index 80c368eb38..91a5c0acba 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "Utilities/JIT.h" #include "Utilities/StrUtil.h" +#include "util/serialization.hpp" #include "Crypto/sha1.h" #include "Crypto/unself.h" #include "Loader/ELF.h" @@ -138,13 +139,28 @@ void fmt_class_string::format(std::string& extern const ppu_decoder g_ppu_itype{}; extern const ppu_decoder g_ppu_iname{}; +template <> +bool serialize(utils::serial& ar, typename ppu_thread::cr_bits& o) +{ + if (ar.is_writing()) + { + ar(o.pack()); + } + else + { + o.unpack(ar); + } + + return true; +} + extern void ppu_initialize(); extern void ppu_finalize(const ppu_module& info); extern bool ppu_initialize(const ppu_module& info, bool = false); static void ppu_initialize2(class jit_compiler& jit, const ppu_module& module_part, const std::string& cache_path, const std::string& obj_name); -extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 file_offset); +extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 file_offset, utils::serial* = nullptr); extern void ppu_unload_prx(const lv2_prx&); -extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, const std::string&, s64 file_offset); +extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, const std::string&, s64 file_offset, utils::serial* = nullptr); extern void ppu_execute_syscall(ppu_thread& ppu, u64 code); static void ppu_break(ppu_thread&, ppu_opcode_t, be_t*, ppu_intrp_func*); @@ -1351,6 +1367,12 @@ void ppu_thread::cpu_task() cmd_pop(1), func(*this, {}, vm::_ptr(cia - 4), &ppu_ret); break; } + case ppu_cmd::cia_call: + { + loaded_from_savestate = true; + cmd_pop(), fast_call(std::exchange(cia, 0), gpr[2]); + break; + } case ppu_cmd::initialize: { #ifdef __APPLE__ @@ -1358,19 +1380,8 @@ void ppu_thread::cpu_task() #endif cmd_pop(); - while (!g_fxo->get().is_inited && !is_stopped()) - { - // Wait for RSX to be initialized - thread_ctrl::wait_on(g_fxo->get().is_inited, false); - } - ppu_initialize(), spu_cache::initialize(); - // Wait until the progress dialog is closed. - // We don't want to open a cell dialog while a native progress dialog is still open. - thread_ctrl::wait_on(g_progr_ptotal, 0); - g_fxo->get().skip_the_progress_dialog = true; - #ifdef __APPLE__ pthread_jit_write_protect_np(true); #endif @@ -1380,6 +1391,24 @@ void ppu_thread::cpu_task() asm("DSB ISH"); #endif + // Wait until the progress dialog is closed. + // We don't want to open a cell dialog while a native progress dialog is still open. + thread_ctrl::wait_on(g_progr_ptotal, 0); + g_fxo->get().skip_the_progress_dialog = true; + + // Sadly we can't postpone initializing guest time because we need ti run PPU threads + // (the farther it's postponed, the less accuracy of guest time has been lost) + Emu.FixGuestTime(); + + // Check if this is the only PPU left to initialize (savestates related) + if (lv2_obj::is_scheduler_ready()) + { + if (Emu.IsStarting()) + { + Emu.FinalizeRunRequest(); + } + } + break; } case ppu_cmd::sleep: @@ -1484,6 +1513,7 @@ ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u3 , joiner(detached != 0 ? ppu_join_status::detached : ppu_join_status::joinable) , entry_func(param.entry) , start_time(get_guest_system_time()) + , is_interrupt_thread(detached < 0) , ppu_tname(make_single(name)) { gpr[1] = stack_addr + stack_size - ppu_stack_start_offset; @@ -1520,6 +1550,195 @@ ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u3 #endif } +struct disable_precomp_t +{ + atomic_t disable = false; +}; + +void vdecEntry(ppu_thread& ppu, u32 vid); + +bool ppu_thread::savable() const +{ + if (joiner == ppu_join_status::exited) + { + return false; + } + + if (cia == g_fxo->get().func_addr(FIND_FUNC(vdecEntry))) + { + // Do not attempt to save the state of HLE VDEC threads + return false; + } + + return true; +} + +void ppu_thread::serialize_common(utils::serial& ar) +{ + ar(gpr, fpr, cr, fpscr.bits, lr, ctr, vrsave, cia, xer, sat, nj, prio, optional_syscall_state); + + for (v128& reg : vr) + ar(reg._bytes); +} + +ppu_thread::ppu_thread(utils::serial& ar) + : cpu_thread(idm::last_id()) // last_id() is showed to constructor on serialization + , stack_size(ar) + , stack_addr(ar) + , joiner(ar.operator ppu_join_status()) + , entry_func(std::bit_cast(ar)) + , is_interrupt_thread(ar) +{ + struct init_pushed + { + bool pushed = false; + atomic_t inited = false; + }; + + serialize_common(ar); + + // Restore jm_mask + jm_mask = nj ? 0x7F800000 : 0x7fff'ffff; + + auto queue_intr_entry = [&]() + { + if (is_interrupt_thread) + { + void ppu_interrupt_thread_entry(ppu_thread&, ppu_opcode_t, be_t*, struct ppu_intrp_func*); + + cmd_list + ({ + { ppu_cmd::ptr_call, 0 }, + std::bit_cast(&ppu_interrupt_thread_entry) + }); + } + }; + + switch (const u32 status = ar.operator u32()) + { + case PPU_THREAD_STATUS_IDLE: + { + stop_flag_removal_protection = true; + break; + } + case PPU_THREAD_STATUS_RUNNABLE: + case PPU_THREAD_STATUS_ONPROC: + { + lv2_obj::awake(this); + [[fallthrough]]; + } + case PPU_THREAD_STATUS_SLEEP: + { + if (std::exchange(g_fxo->get().pushed, true)) + { + cmd_list + ({ + {ppu_cmd::ptr_call, 0}, +[](ppu_thread& ppu) -> bool + { + while (!Emu.IsStopped() && !g_fxo->get().inited) + { + thread_ctrl::wait_on(g_fxo->get().inited, false); + } + return false; + } + }); + } + else + { + g_fxo->init(); + g_fxo->get().disable = true; + + cmd_push({ppu_cmd::initialize, 0}); + cmd_list + ({ + {ppu_cmd::ptr_call, 0}, +[](ppu_thread&) -> bool + { + auto& inited = g_fxo->get().inited; + inited = true; + inited.notify_all(); + return true; + } + }); + } + + if (status == PPU_THREAD_STATUS_SLEEP) + { + cmd_list + ({ + {ppu_cmd::ptr_call, 0}, + + +[](ppu_thread& ppu) -> bool + { + ppu.loaded_from_savestate = true; + ppu_execute_syscall(ppu, ppu.gpr[11]); + ppu.loaded_from_savestate = false; + return true; + } + }); + + lv2_obj::set_future_sleep(this); + } + + queue_intr_entry(); + cmd_push({ppu_cmd::cia_call, 0}); + break; + } + case PPU_THREAD_STATUS_ZOMBIE: + { + state += cpu_flag::exit; + break; + } + case PPU_THREAD_STATUS_STOP: + { + queue_intr_entry(); + break; + } + } + + // Trigger the scheduler + state += cpu_flag::suspend; + + if (!g_use_rtm) + { + state += cpu_flag::memory; + } + + ppu_tname = make_single(ar.operator std::string()); +} + +void ppu_thread::save(utils::serial& ar) +{ + const u64 entry = std::bit_cast(entry_func); + + ppu_join_status _joiner = joiner; + if (_joiner >= ppu_join_status::max) + { + // Joining thread should recover this member properly + _joiner = ppu_join_status::joinable; + } + + if (state & cpu_flag::again) + { + std::memcpy(&gpr[3], syscall_args, sizeof(syscall_args)); + cia -= 4; + } + + ar(stack_size, stack_addr, _joiner, entry, is_interrupt_thread); + serialize_common(ar); + + ppu_thread_status status = lv2_obj::ppu_state(this, false); + + if (status == PPU_THREAD_STATUS_SLEEP && cpu_flag::again - state) + { + // Hack for sys_fs + status = PPU_THREAD_STATUS_RUNNABLE; + } + + ar(status); + + ar(*ppu_tname.load()); +} + ppu_thread::thread_name_t::operator std::string() const { std::string thread_name = fmt::format("PPU[0x%x]", _this->id); @@ -1596,7 +1815,7 @@ be_t* ppu_thread::get_stack_arg(s32 i, u64 align) return vm::_ptr(vm::cast((gpr[1] + 0x30 + 0x8 * (i - 1)) & (0 - align))); } -void ppu_thread::fast_call(u32 addr, u32 rtoc) +void ppu_thread::fast_call(u32 addr, u64 rtoc) { const auto old_cia = cia; const auto old_rtoc = gpr[2]; @@ -1604,11 +1823,17 @@ void ppu_thread::fast_call(u32 addr, u32 rtoc) const auto old_func = current_function; const auto old_fmt = g_tls_log_prefix; + interrupt_thread_executing = true; cia = addr; gpr[2] = rtoc; lr = g_fxo->get().func_addr(1) + 4; // HLE stop address current_function = nullptr; + if (std::exchange(loaded_from_savestate, false)) + { + lr = old_lr; + } + g_tls_log_prefix = [] { const auto _this = static_cast(get_current_cpu_thread()); @@ -1643,15 +1868,21 @@ void ppu_thread::fast_call(u32 addr, u32 rtoc) cpu_on_stop(); current_function = old_func; } - else + else if (old_cia) { - state -= cpu_flag::ret; + if (state & cpu_flag::exit) + { + ppu_log.error("HLE callstack savestate is not implemented!"); + } + cia = old_cia; gpr[2] = old_rtoc; lr = old_lr; - current_function = old_func; - g_tls_log_prefix = old_fmt; } + + current_function = old_func; + g_tls_log_prefix = old_fmt; + state -= cpu_flag::ret; }; exec_task(); @@ -2466,6 +2697,13 @@ namespace }; } +extern fs::file make_file_view(fs::file&& _file, u64 offset) +{ + fs::file file; + file.reset(std::make_unique(std::move(_file), offset)); + return file; +} + extern void ppu_finalize(const ppu_module& info) { // Get cache path for this executable @@ -2503,13 +2741,18 @@ extern void ppu_finalize(const ppu_module& info) #endif } -extern void ppu_precompile(std::vector& dir_queue, std::vector* loaded_prx) +extern void ppu_precompile(std::vector& dir_queue, std::vector* loaded_modules) { if (g_cfg.core.ppu_decoder != ppu_decoder_type::llvm) { return; } + if (auto dis = g_fxo->try_get(); dis && dis->disable) + { + return; + } + // Make sure we only have one '/' at the end and remove duplicates. for (std::string& dir : dir_queue) { @@ -2560,53 +2803,48 @@ extern void ppu_precompile(std::vector& dir_queue, std::vector bool + { + if (dir_queue[i] != firmware_sprx_path) + { + return false; + } + + if (loaded_modules) + { + if (std::any_of(loaded_modules->begin(), loaded_modules->end(), [&](ppu_module* obj) + { + return obj->name == entry.name; + })) + { + return true; + } + } + + if (g_cfg.core.libraries_control.get_set().count(entry.name + ":lle")) + { + // Force LLE + return false; + } + else if (g_cfg.core.libraries_control.get_set().count(entry.name + ":hle")) + { + // Force HLE + return true; + } + + extern const std::map g_prx_list; + + // Use list + return g_prx_list.count(entry.name) && g_prx_list.at(entry.name) != 0; + }; + // Check .sprx filename if (upper.ends_with(".SPRX") && entry.name != "libfs_utility_init.sprx"sv) { - // Skip already loaded modules or HLEd ones - if (dir_queue[i] == firmware_sprx_path) + if (is_ignored(0)) { - bool ignore = false; - - if (loaded_prx) - { - for (auto* obj : *loaded_prx) - { - if (obj->name == entry.name) - { - ignore = true; - break; - } - } - - if (ignore) - { - continue; - } - } - - if (g_cfg.core.libraries_control.get_set().count(entry.name + ":lle")) - { - // Force LLE - ignore = false; - } - else if (g_cfg.core.libraries_control.get_set().count(entry.name + ":hle")) - { - // Force HLE - ignore = true; - } - else - { - extern const std::map g_prx_list; - - // Use list - ignore = g_prx_list.count(entry.name) && g_prx_list.at(entry.name) != 0; - } - - if (ignore) - { - continue; - } + continue; } // Get full path @@ -2800,8 +3038,6 @@ extern void ppu_precompile(std::vector& dir_queue, std::vectorget(); - if (!g_fxo->is_init()) { return; @@ -2812,6 +3048,8 @@ extern void ppu_initialize() return; } + auto& _main = g_fxo->get(); + scoped_progress_dialog progr = "Scanning PPU modules..."; bool compile_main = false; @@ -2822,20 +3060,39 @@ extern void ppu_initialize() compile_main = ppu_initialize(_main, true); } - std::vector prx_list; + std::vector module_list; - idm::select([&](u32, lv2_prx& prx) + const std::string firmware_sprx_path = vfs::get("/dev_flash/sys/external/"); + + // If empty we have no indication for firmware cache state, check everything + bool compile_fw = true; + + idm::select([&](u32, lv2_prx& _module) { - prx_list.emplace_back(&prx); + if (_module.path.starts_with(firmware_sprx_path)) + { + // Postpone testing + compile_fw = false; + } + + module_list.emplace_back(&_module); }); - // If empty we have no indication for cache state, check everything - bool compile_fw = prx_list.empty(); + idm::select([&](u32, lv2_overlay& _module) + { + module_list.emplace_back(&_module); + }); // Check preloaded libraries cache - for (auto ptr : prx_list) + if (!compile_fw) { - compile_fw |= ppu_initialize(*ptr, true); + for (auto ptr : module_list) + { + if (ptr->path.starts_with(firmware_sprx_path)) + { + compile_fw |= ppu_initialize(*ptr, true); + } + } } std::vector dir_queue; @@ -2871,7 +3128,7 @@ extern void ppu_initialize() dir_queue.insert(std::end(dir_queue), std::begin(dirs), std::end(dirs)); } - ppu_precompile(dir_queue, &prx_list); + ppu_precompile(dir_queue, &module_list); if (Emu.IsStopped()) { @@ -2885,7 +3142,7 @@ extern void ppu_initialize() } // Initialize preloaded libraries - for (auto ptr : prx_list) + for (auto ptr : module_list) { if (Emu.IsStopped()) { diff --git a/rpcs3/Emu/Cell/PPUThread.h b/rpcs3/Emu/Cell/PPUThread.h index 0dafe99919..bb9d1e5ffe 100644 --- a/rpcs3/Emu/Cell/PPUThread.h +++ b/rpcs3/Emu/Cell/PPUThread.h @@ -20,6 +20,7 @@ enum class ppu_cmd : u32 hle_call, // Execute function by index (arg) ptr_call, // Execute function by pointer opd_call, // Execute function by provided rtoc and address (unlike lle_call, does not read memory) + cia_call, // Execute from current CIA, mo GPR modification applied initialize, // ppu_initialize() sleep, reset_stack, // resets stack address @@ -140,10 +141,15 @@ public: virtual void cpu_on_stop() override; virtual ~ppu_thread() override; - ppu_thread(const ppu_thread_params&, std::string_view name, u32 prio, int detached = 0); + SAVESTATE_INIT_POS(3); + ppu_thread(const ppu_thread_params&, std::string_view name, u32 prio, int detached = 0); + ppu_thread(utils::serial& ar); ppu_thread(const ppu_thread&) = delete; ppu_thread& operator=(const ppu_thread&) = delete; + bool savable() const; + void serialize_common(utils::serial& ar); + void save(utils::serial& ar); using cpu_thread::operator=; @@ -180,8 +186,8 @@ public: { for (u8& b : bits) { - b = value & 0x1; - value >>= 1; + b = !!(value & (1u << 31)); + value <<= 1; } } }; @@ -215,6 +221,8 @@ public: // Fixed-Point Exception Register (abstract representation) struct { + ENABLE_BITWISE_SERIALIZATION; + bool so{}; // Summary Overflow bool ov{}; // Overflow bool ca{}; // Carry @@ -266,6 +274,8 @@ public: 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 bool is_interrupt_thread; // True for interrupts-handler threads + // Thread name atomic_ptr ppu_tname; @@ -307,9 +317,15 @@ public: operator std::string() const; } thread_name{ this }; + // For savestates + bool stop_flag_removal_protection = false; // If set, Emulator::Run won't remove stop flag + bool loaded_from_savestate = false; // Indicates the thread had just started straight from savestate load + u64 optional_syscall_state{}; + bool interrupt_thread_executing = false; + be_t* get_stack_arg(s32 i, u64 align = alignof(u64)); void exec_task(); - void fast_call(u32 addr, u32 rtoc); + void fast_call(u32 addr, u64 rtoc); static std::pair stack_push(u32 size, u32 align_v); static void stack_pop_verbose(u32 addr, u32 size) noexcept; diff --git a/rpcs3/Emu/Cell/SPURecompiler.cpp b/rpcs3/Emu/Cell/SPURecompiler.cpp index c0a4bcd793..25e26c54d9 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.cpp +++ b/rpcs3/Emu/Cell/SPURecompiler.cpp @@ -5544,11 +5544,7 @@ public: spu_runtime::g_escape(_spu); } - if (_spu->test_stopped()) - { - _spu->pc += 4; - spu_runtime::g_escape(_spu); - } + static_cast(_spu->test_stopped()); } void STOP(spu_opcode_t op) // @@ -5591,12 +5587,7 @@ public: spu_runtime::g_escape(_spu); } - if (_spu->test_stopped()) - { - _spu->pc += 4; - spu_runtime::g_escape(_spu); - } - + static_cast(_spu->test_stopped()); return static_cast(result & 0xffffffff); } @@ -5614,12 +5605,7 @@ public: { _spu->state += cpu_flag::wait; std::this_thread::yield(); - - if (_spu->test_stopped()) - { - _spu->pc += 4; - spu_runtime::g_escape(_spu); - } + static_cast(_spu->test_stopped()); } return res; @@ -5655,6 +5641,8 @@ public: val0 = m_ir->CreateTrunc(val0, get_type()); m_ir->CreateCondBr(cond, done, wait); m_ir->SetInsertPoint(wait); + update_pc(); + m_block->store.fill(nullptr); const auto val1 = call("spu_read_channel", &exec_rdch, m_thread, m_ir->getInt32(op.ra)); m_ir->CreateBr(done); m_ir->SetInsertPoint(done); @@ -5685,6 +5673,7 @@ public: case SPU_RdInMbox: { update_pc(); + m_block->store.fill(nullptr); res.value = call("spu_read_in_mbox", &exec_read_in_mbox, m_thread); break; } @@ -5731,6 +5720,7 @@ public: case SPU_RdEventStat: { update_pc(); + m_block->store.fill(nullptr); res.value = call("spu_read_events", &exec_read_events, m_thread); break; } @@ -5744,6 +5734,7 @@ public: default: { update_pc(); + m_block->store.fill(nullptr); res.value = call("spu_read_channel", &exec_rdch, m_thread, m_ir->getInt32(op.ra)); break; } @@ -5954,6 +5945,7 @@ public: m_ir->CreateCondBr(m_ir->CreateICmpNE(m_ir->CreateLoad(spu_ptr(&spu_thread::ch_tag_upd)), m_ir->getInt32(MFC_TAG_UPDATE_IMMEDIATE)), _mfc, next); m_ir->SetInsertPoint(_mfc); update_pc(); + m_block->store.fill(nullptr); call("spu_write_channel", &exec_wrch, m_thread, m_ir->getInt32(op.ra), val.value); m_ir->CreateBr(next); m_ir->SetInsertPoint(next); @@ -6106,6 +6098,7 @@ public: m_ir->SetInsertPoint(next); m_ir->CreateStore(ci, spu_ptr(&spu_thread::ch_mfc_cmd, &spu_mfc_cmd::cmd)); update_pc(); + m_block->store.fill(nullptr); call("spu_exec_mfc_cmd", &exec_mfc_cmd, m_thread); return; } @@ -6373,6 +6366,7 @@ public: m_ir->CreateCondBr(m_ir->CreateICmpNE(_old, _new), _mfc, next); m_ir->SetInsertPoint(_mfc); update_pc(); + m_block->store.fill(nullptr); call("spu_list_unstall", &exec_list_unstall, m_thread, eval(val & 0x1f).value); m_ir->CreateBr(next); m_ir->SetInsertPoint(next); @@ -6396,6 +6390,7 @@ public: } update_pc(); + m_block->store.fill(nullptr); call("spu_write_channel", &exec_wrch, m_thread, m_ir->getInt32(op.ra), val.value); } @@ -6416,6 +6411,7 @@ public: { m_block->block_end = m_ir->GetInsertBlock(); update_pc(m_pos + 4); + m_block->store.fill(nullptr); tail_chunk(m_dispatch); } } diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 46f19ac28f..1743070e60 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -27,11 +27,13 @@ #include #include #include +#include #include "util/vm.hpp" #include "util/asm.hpp" #include "util/v128.hpp" #include "util/simd.hpp" #include "util/sysinfo.hpp" +#include "util/serialization.hpp" using spu_rdata_t = decltype(spu_thread::rdata); @@ -1623,63 +1625,28 @@ spu_thread::~spu_thread() shm->unmap(ls + SPU_LS_SIZE); shm->unmap(ls); shm->unmap(ls - SPU_LS_SIZE); + utils::memory_release(ls - SPU_LS_SIZE * 2, SPU_LS_SIZE * 5); perf_log.notice("Perf stats for transactions: success %u, failure %u", stx, ftx); perf_log.notice("Perf stats for PUTLLC reload: successs %u, failure %u", last_succ, last_fail); } +u8* spu_thread::map_ls(utils::shm& shm) +{ + vm::writer_lock mlock; + + const auto ls = static_cast(ensure(utils::memory_reserve(SPU_LS_SIZE * 5, nullptr, true))) + SPU_LS_SIZE * 2; + ensure(shm.map_critical(ls - SPU_LS_SIZE).first && shm.map_critical(ls).first && shm.map_critical(ls + SPU_LS_SIZE).first); + return ls; +} + spu_thread::spu_thread(lv2_spu_group* group, u32 index, std::string_view name, u32 lv2_id, bool is_isolated, u32 option) : cpu_thread(idm::last_id()) , group(group) , index(index) - , shm(std::make_shared(SPU_LS_SIZE)) - , ls([&]() - { - if (!group) - { - ensure(vm::get(vm::spu)->falloc(vm_offset(), SPU_LS_SIZE, &shm, vm::page_size_64k)); - } - else - { - // alloc_hidden indicates falloc to allocate page with no access rights in base memory - ensure(vm::get(vm::spu)->falloc(vm_offset(), SPU_LS_SIZE, &shm, static_cast(vm::page_size_64k) | static_cast(vm::alloc_hidden))); - } - - // Try to guess free area - const auto start = vm::g_free_addr + SPU_LS_SIZE * (cpu_thread::id & 0xffffff) * 12; - - u32 total = 0; - - // Map LS and its mirrors - for (u64 addr = reinterpret_cast(start); addr < 0x8000'0000'0000;) - { - if (auto ptr = shm->try_map(reinterpret_cast(addr))) - { - if (++total == 3) - { - // Use the middle mirror - return ptr - SPU_LS_SIZE; - } - - addr += SPU_LS_SIZE; - } - else - { - // Reset, cleanup and start again - for (u32 i = 1; i <= total; i++) - { - shm->unmap(reinterpret_cast(addr - i * SPU_LS_SIZE)); - } - - total = 0; - - addr += 0x10000; - } - } - - fmt::throw_exception("Failed to map SPU LS memory"); - }()) , thread_type(group ? spu_type::threaded : is_isolated ? spu_type::isolated : spu_type::raw) + , shm(std::make_shared(SPU_LS_SIZE)) + , ls(map_ls(*this->shm)) , option(option) , lv2_id(lv2_id) , spu_tname(make_single(name)) @@ -1688,12 +1655,26 @@ spu_thread::spu_thread(lv2_spu_group* group, u32 index, std::string_view name, u { jit = spu_recompiler_base::make_asmjit_recompiler(); } - - if (g_cfg.core.spu_decoder == spu_decoder_type::llvm) + else if (g_cfg.core.spu_decoder == spu_decoder_type::llvm) { jit = spu_recompiler_base::make_fast_llvm_recompiler(); } + if (g_cfg.core.mfc_debug) + { + utils::memory_commit(vm::g_stat_addr + vm_offset(), SPU_LS_SIZE); + } + + if (!group) + { + ensure(vm::get(vm::spu)->falloc(vm_offset(), SPU_LS_SIZE, &shm, vm::page_size_64k)); + } + else + { + // alloc_hidden indicates falloc to allocate page with no access rights in base memory + ensure(vm::get(vm::spu)->falloc(vm_offset(), SPU_LS_SIZE, &shm, static_cast(vm::page_size_64k) | static_cast(vm::alloc_hidden))); + } + if (g_cfg.core.spu_decoder == spu_decoder_type::asmjit || g_cfg.core.spu_decoder == spu_decoder_type::llvm) { if (g_cfg.core.spu_block_size != spu_block_size_type::safe) @@ -1716,6 +1697,165 @@ spu_thread::spu_thread(lv2_spu_group* group, u32 index, std::string_view name, u range_lock = vm::alloc_range_lock(); } +void spu_thread::serialize_common(utils::serial& ar) +{ + for (v128& reg : gpr) + ar(reg._bytes); + + ar(pc, ch_mfc_cmd, mfc_size, mfc_barrier, mfc_fence, mfc_prxy_cmd, mfc_prxy_mask, mfc_prxy_write_state.all + , srr0 + , ch_tag_upd + , ch_tag_mask + , ch_tag_stat.data + , ch_stall_mask + , ch_stall_stat.data + , ch_atomic_stat.data + , ch_out_mbox.data + , ch_out_intr_mbox.data + , snr_config + + , ch_snr1.data + , ch_snr2.data + , ch_events.raw().all + , interrupts_enabled + , run_ctrl + , exit_status.data + , status_npc.raw().status); + + std::for_each_n(mfc_queue, mfc_size, [&](spu_mfc_cmd& cmd) { ar(cmd); }); +} + +spu_thread::spu_thread(utils::serial& ar, lv2_spu_group* group) + : cpu_thread(idm::last_id()) + , group(group) + , index(ar) + , thread_type(group ? spu_type::threaded : ar.operator u8() ? spu_type::isolated : spu_type::raw) + , shm(ensure(vm::get(vm::spu)->peek(vm_offset()).second)) + , ls(map_ls(*this->shm)) + , option(ar) + , lv2_id(ar) + , spu_tname(make_single(ar.operator std::string())) +{ + if (g_cfg.core.spu_decoder == spu_decoder_type::asmjit) + { + jit = spu_recompiler_base::make_asmjit_recompiler(); + } + else if (g_cfg.core.spu_decoder == spu_decoder_type::llvm) + { + jit = spu_recompiler_base::make_fast_llvm_recompiler(); + } + + if (g_cfg.core.mfc_debug) + { + utils::memory_commit(vm::g_stat_addr + vm_offset(), SPU_LS_SIZE); + } + + if (g_cfg.core.spu_decoder != spu_decoder_type::_static && g_cfg.core.spu_decoder != spu_decoder_type::dynamic) + { + if (g_cfg.core.spu_block_size != spu_block_size_type::safe) + { + // Initialize stack mirror + std::memset(stack_mirror.data(), 0xff, sizeof(stack_mirror)); + } + } + + if (get_type() >= spu_type::raw) + { + cpu_init(); + } + + range_lock = vm::alloc_range_lock(); + + serialize_common(ar); + + { + u32 vals[4]{}; + const u8 count = ar; + ar(std::span(vals, count)); + ch_in_mbox.set_values(count, vals[0], vals[1], vals[2], vals[3]); + } + + status_npc.raw().npc = pc | u8{interrupts_enabled}; + + if (get_type() == spu_type::threaded) + { + for (auto& pair : spuq) + { + ar(pair.first); + pair.second = idm::get_unlocked(ar.operator u32()); + } + + for (auto& q : spup) + { + q = idm::get_unlocked(ar.operator u32()); + } + } + else + { + for (spu_int_ctrl_t& ctrl : int_ctrl) + { + ar(ctrl.mask, ctrl.stat); + ctrl.tag = idm::get_unlocked(ar.operator u32()); + } + + g_raw_spu_ctr++; + g_raw_spu_id[index] = id; + } + + ar(stop_flag_removal_protection); +} + +void spu_thread::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(spu); + + if (raddr) + { + // Lose reservation at savestate load with an event if one existed at savestate save + set_events(SPU_EVENT_LR); + } + + ar(index); + + if (get_type() != spu_type::threaded) + { + ar(u8{get_type() == spu_type::isolated}); + } + + ar(option, lv2_id, *spu_tname.load()); + + serialize_common(ar); + + { + u32 vals[4]{}; + const u8 count = ch_in_mbox.try_read(vals); + ar(count, std::span(vals, count)); + } + + if (get_type() == spu_type::threaded) + { + for (const auto& [key, q] : spuq) + { + ar(key); + ar(lv2_obj::check(q) ? q->id : 0); + } + + for (auto& p : spup) + { + ar(lv2_obj::check(p) ? p->id : 0); + } + } + else + { + for (const spu_int_ctrl_t& ctrl : int_ctrl) + { + ar(ctrl.mask, ctrl.stat, lv2_obj::check(ctrl.tag) ? ctrl.tag->id : 0); + } + } + + ar(!!(state & cpu_flag::stop)); +} + void spu_thread::push_snr(u32 number, u32 value) { // Get channel @@ -3408,7 +3548,8 @@ bool spu_thread::process_mfc_cmd() std::memcpy(dump.data, _ptr(ch_mfc_cmd.lsa & 0x3ff80), 128); } - return !test_stopped(); + static_cast(test_stopped()); + return true; } case MFC_PUTLLUC_CMD: { @@ -3422,7 +3563,8 @@ bool spu_thread::process_mfc_cmd() do_putlluc(ch_mfc_cmd); ch_atomic_stat.set_value(MFC_PUTLLUC_SUCCESS); - return !test_stopped(); + static_cast(test_stopped()); + return true; } case MFC_PUTQLLUC_CMD: { @@ -4161,6 +4303,11 @@ bool spu_thread::set_ch_value(u32 ch, u32 value) spu_log.warning("sys_spu_thread_send_event(spup=%d, data0=0x%x, data1=0x%x): error (%s)", spup, (value & 0x00ffffff), data, res); } + if (res == CELL_EAGAIN) + { + return false; + } + ch_in_mbox.set_values(1, res); return true; } @@ -4183,6 +4330,12 @@ bool spu_thread::set_ch_value(u32 ch, u32 value) // TODO: check passing spup value if (auto res = queue ? queue->send(SYS_SPU_THREAD_EVENT_USER_KEY, lv2_id, (u64{spup} << 32) | (value & 0x00ffffff), data) : CELL_ENOTCONN) { + if (res == CELL_EAGAIN) + { + ch_out_mbox.set_value(data); + return false; + } + spu_log.warning("sys_spu_thread_throw_event(spup=%d, data0=0x%x, data1=0x%x) failed (error=%s)", spup, (value & 0x00ffffff), data, res); } @@ -4207,6 +4360,12 @@ bool spu_thread::set_ch_value(u32 ch, u32 value) // Use the syscall to set flag const auto res = ch_in_mbox.get_count() ? CELL_EBUSY : 0u + sys_event_flag_set(*this, data, 1ull << flag); + if (res == CELL_EAGAIN) + { + ch_out_mbox.set_value(data); + return false; + } + if (res == CELL_EBUSY) { spu_log.warning("sys_event_flag_set_bit(value=0x%x (flag=%d)): In_MBox is not empty (%d)", value, flag, ch_in_mbox.get_count()); @@ -4230,7 +4389,12 @@ bool spu_thread::set_ch_value(u32 ch, u32 value) spu_log.trace("sys_event_flag_set_bit_impatient(id=%d, value=0x%x (flag=%d))", data, value, flag); // Use the syscall to set flag - sys_event_flag_set(*this, data, 1ull << flag); + if (sys_event_flag_set(*this, data, 1ull << flag) + 0u == CELL_EAGAIN) + { + ch_out_mbox.set_value(data); + return false; + } + return true; } else @@ -4570,7 +4734,7 @@ bool spu_thread::stop_and_signal(u32 code) u32 spuq = 0; - if (!ch_out_mbox.try_pop(spuq)) + if (!ch_out_mbox.try_read(spuq)) { fmt::throw_exception("sys_spu_thread_receive_event(): Out_MBox is empty"); } @@ -4581,6 +4745,20 @@ bool spu_thread::stop_and_signal(u32 code) return ch_in_mbox.set_values(1, CELL_EBUSY), true; } + struct clear_mbox + { + spu_thread& _this; + + ~clear_mbox() noexcept + { + if (cpu_flag::again - _this.state) + { + u32 val = 0; + _this.ch_out_mbox.try_pop(val); + } + } + } clear{*this}; + spu_log.trace("sys_spu_thread_receive_event(spuq=0x%x)", spuq); if (!group->has_scheduler_context /*|| group->type & 0xf00*/) @@ -4606,6 +4784,7 @@ bool spu_thread::stop_and_signal(u32 code) if (is_stopped(old)) { + state += cpu_flag::again; return false; } @@ -4624,6 +4803,7 @@ bool spu_thread::stop_and_signal(u32 code) if (is_stopped()) { + state += cpu_flag::again; return false; } @@ -4675,16 +4855,26 @@ bool spu_thread::stop_and_signal(u32 code) while (auto old = state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(old)) - { - return false; - } - if (old & cpu_flag::signal) { break; } + if (is_stopped(old)) + { + std::lock_guard qlock(queue->mutex); + + old = state.fetch_sub(cpu_flag::signal); + + if (old & cpu_flag::signal) + { + break; + } + + state += cpu_flag::again; + return false; + } + thread_ctrl::wait_on(state, old); } @@ -4797,6 +4987,7 @@ bool spu_thread::stop_and_signal(u32 code) if (is_stopped(old)) { + ch_out_mbox.set_value(value); return false; } diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index 8d005c520d..e64104ab27 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -699,6 +699,13 @@ public: using cpu_thread::operator=; + SAVESTATE_INIT_POS(5); + + spu_thread(utils::serial& ar, lv2_spu_group* group = nullptr); + void serialize_common(utils::serial& ar); + void save(utils::serial& ar); + bool savable() const { return get_type() != spu_type::threaded; } // Threaded SPUs are saved as part of the SPU group + u32 pc = 0; u32 dbg_step_pc = 0; @@ -802,9 +809,9 @@ public: atomic_t last_exit_status; // Value to be written in exit_status after checking group termination lv2_spu_group* const group; // SPU Thread Group (access by the spu threads in the group only! From other threads obtain a shared pointer to group using group ID) const u32 index; // SPU index + const spu_type thread_type; std::shared_ptr shm; // SPU memory const std::add_pointer_t ls; // SPU LS pointer - const spu_type thread_type; const u32 option; // sys_spu_thread_initialize option const u32 lv2_id; // The actual id that is used by syscalls @@ -847,6 +854,7 @@ public: std::array, SPU_LS_SIZE / 4> local_breakpoints{}; atomic_t has_active_local_bps = false; u32 current_bp_pc = umax; + bool stop_flag_removal_protection = false; void push_snr(u32 number, u32 value); static void do_dma_transfer(spu_thread* _this, const spu_mfc_cmd& args, u8* ls); @@ -898,6 +906,8 @@ public: return group ? SPU_FAKE_BASE_ADDR + SPU_LS_SIZE * (id & 0xffffff) : RAW_SPU_BASE_ADDR + RAW_SPU_OFFSET * index; } + static u8* map_ls(utils::shm& shm); + // Returns true if reservation existed but was just discovered to be lost // It is safe to use on any address, even if not directly accessed by SPU (so it's slower) bool reservation_check(u32 addr, const decltype(rdata)& data) const; diff --git a/rpcs3/Emu/Cell/lv2/lv2.cpp b/rpcs3/Emu/Cell/lv2/lv2.cpp index 4add1f5b87..59e1d4b29f 100644 --- a/rpcs3/Emu/Cell/lv2/lv2.cpp +++ b/rpcs3/Emu/Cell/lv2/lv2.cpp @@ -1190,6 +1190,7 @@ DECLARE(lv2_obj::g_mutex); DECLARE(lv2_obj::g_ppu); DECLARE(lv2_obj::g_pending); DECLARE(lv2_obj::g_waiting); +DECLARE(lv2_obj::g_to_sleep); thread_local DECLARE(lv2_obj::g_to_awake); @@ -1256,6 +1257,32 @@ void lv2_obj::sleep_unlocked(cpu_thread& thread, u64 timeout) // Find and remove the thread if (!unqueue(g_ppu, ppu)) { + if (unqueue(g_to_sleep, ppu)) + { + ppu->start_time = start_time; + + std::string out = fmt::format("Threads (%d):", g_to_sleep.size()); + for (auto thread : g_to_sleep) + { + fmt::append(out, " 0x%x,", thread->id); + } + + ppu_log.warning("%s", out); + + if (g_to_sleep.empty()) + { + // All threads are ready, wake threads + Emu.CallFromMainThread([] + { + if (Emu.IsStarting()) + { + // It uses lv2_obj::g_mutex, run it on main thread + Emu.FinalizeRunRequest(); + } + }); + } + } + // Already sleeping ppu_log.trace("sleep(): called on already sleeping thread."); return; @@ -1438,11 +1465,12 @@ void lv2_obj::cleanup() g_ppu.clear(); g_pending.clear(); g_waiting.clear(); + g_to_sleep.clear(); } void lv2_obj::schedule_all() { - if (g_pending.empty()) + if (g_pending.empty() && g_to_sleep.empty()) { // Wake up threads for (usz i = 0, x = std::min(g_cfg.core.ppu_threads, g_ppu.size()); i < x; i++) @@ -1486,7 +1514,7 @@ ppu_thread_status lv2_obj::ppu_state(ppu_thread* ppu, bool lock_idm, bool lock_l opt_lock[0].emplace(id_manager::g_mutex); } - if (ppu->state & cpu_flag::stop) + if (!Emu.IsReady() ? ppu->state.all_of(cpu_flag::stop) : ppu->stop_flag_removal_protection) { return PPU_THREAD_STATUS_IDLE; } @@ -1507,6 +1535,11 @@ ppu_thread_status lv2_obj::ppu_state(ppu_thread* ppu, bool lock_idm, bool lock_l if (it == g_ppu.end()) { + if (!ppu->interrupt_thread_executing) + { + return PPU_THREAD_STATUS_STOP; + } + return PPU_THREAD_STATUS_SLEEP; } @@ -1517,3 +1550,14 @@ ppu_thread_status lv2_obj::ppu_state(ppu_thread* ppu, bool lock_idm, bool lock_l return PPU_THREAD_STATUS_ONPROC; } + +void lv2_obj::set_future_sleep(ppu_thread* ppu) +{ + g_to_sleep.emplace_back(ppu); +} + +bool lv2_obj::is_scheduler_ready() +{ + reader_lock lock(g_mutex); + return g_to_sleep.empty(); +} diff --git a/rpcs3/Emu/Cell/lv2/sys_cond.cpp b/rpcs3/Emu/Cell/lv2/sys_cond.cpp index d148436767..d3f48358e4 100644 --- a/rpcs3/Emu/Cell/lv2/sys_cond.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_cond.cpp @@ -1,14 +1,72 @@ #include "stdafx.h" #include "sys_cond.h" +#include "util/serialization.hpp" #include "Emu/IdManager.h" #include "Emu/IPC.h" +#include "Emu/System.h" #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" LOG_CHANNEL(sys_cond); +lv2_cond::lv2_cond(utils::serial& ar) + : key(ar) + , name(ar) + , mtx_id(ar) + , mutex(idm::get_unlocked(mtx_id)) // May be nullptr +{ +} + +CellError lv2_cond::on_id_create() +{ + exists++; + + static auto do_it = [](lv2_cond* _this) -> CellError + { + if (lv2_obj::check(_this->mutex)) + { + _this->mutex->cond_count++; + return {}; + } + + // Mutex has been destroyed, cannot create conditional variable + return CELL_ESRCH; + }; + + if (mutex) + { + return do_it(this); + } + + ensure(!!Emu.DeserialManager()); + + Emu.DeferDeserialization([this]() + { + if (!mutex) + { + mutex = ensure(idm::get_unlocked(mtx_id)); + } + + // Defer function + ensure(CellError{} == do_it(this)); + }); + + return {}; +} + +std::shared_ptr lv2_cond::load(utils::serial& ar) +{ + auto cond = std::make_shared(ar); + return lv2_obj::load(cond->key, cond); +} + +void lv2_cond::save(utils::serial& ar) +{ + ar(key, name, mtx_id); +} + error_code sys_cond_create(ppu_thread& ppu, vm::ptr cond_id, u32 mutex_id, vm::ptr attr) { ppu.state += cpu_flag::wait; @@ -81,7 +139,7 @@ error_code sys_cond_signal(ppu_thread& ppu, u32 cond_id) sys_cond.trace("sys_cond_signal(cond_id=0x%x)", cond_id); - const auto cond = idm::check(cond_id, [](lv2_cond& cond) + const auto cond = idm::check(cond_id, [&](lv2_cond& cond) { if (cond.waiters) { @@ -89,6 +147,12 @@ error_code sys_cond_signal(ppu_thread& ppu, u32 cond_id) if (const auto cpu = cond.schedule(cond.sq, cond.mutex->protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return; + } + // TODO: Is EBUSY returned after reqeueing, on sys_cond_destroy? cond.waiters--; @@ -114,12 +178,21 @@ error_code sys_cond_signal_all(ppu_thread& ppu, u32 cond_id) sys_cond.trace("sys_cond_signal_all(cond_id=0x%x)", cond_id); - const auto cond = idm::check(cond_id, [](lv2_cond& cond) + const auto cond = idm::check(cond_id, [&](lv2_cond& cond) { if (cond.waiters) { std::lock_guard lock(cond.mutex->mutex); + for (auto cpu : cond.sq) + { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return; + } + } + cpu_thread* result = nullptr; cond.waiters -= ::size32(cond.sq); @@ -167,6 +240,12 @@ error_code sys_cond_signal_to(ppu_thread& ppu, u32 cond_id, u32 thread_id) { if (cpu->id == thread_id) { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return 0; + } + ensure(cond.unqueue(cond.sq, cpu)); cond.waiters--; @@ -208,19 +287,33 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout) const auto cond = idm::get(cond_id, [&](lv2_cond& cond) -> s64 { - if (cond.mutex->owner >> 1 != ppu.id) + if (!ppu.loaded_from_savestate && cond.mutex->owner >> 1 != ppu.id) { return -1; } std::lock_guard lock(cond.mutex->mutex); - // Register waiter - cond.sq.emplace_back(&ppu); - cond.waiters++; + if (ppu.loaded_from_savestate && ppu.optional_syscall_state & 1) + { + // Mutex sleep + ensure(!cond.mutex->try_own(ppu, ppu.id)); + } + else + { + // Register waiter + cond.sq.emplace_back(&ppu); + cond.waiters++; + } + + if (ppu.loaded_from_savestate) + { + cond.sleep(ppu, timeout); + return static_cast(ppu.optional_syscall_state >> 32); + } // Unlock the mutex - const auto count = cond.mutex->lock_count.exchange(0); + const u32 count = cond.mutex->lock_count.exchange(0); if (const auto cpu = cond.mutex->reown()) { @@ -246,16 +339,28 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout) while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(state)) - { - return {}; - } - if (state & cpu_flag::signal) { break; } + if (is_stopped(state)) + { + std::lock_guard lock(cond->mutex->mutex); + + const bool cond_sleep = std::find(cond->sq.begin(), cond->sq.end(), &ppu) != cond->sq.end(); + const bool mutex_sleep = std::find(cond->mutex->sq.begin(), cond->mutex->sq.end(), &ppu) != cond->mutex->sq.end(); + + if (!cond_sleep && !mutex_sleep) + { + break; + } + + ppu.optional_syscall_state = u32{mutex_sleep} | (u64{static_cast(cond.ret)} << 32); + ppu.state += cpu_flag::again; + return {}; + } + if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) @@ -263,7 +368,7 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout) // Wait for rescheduling if (ppu.check_state()) { - return {}; + continue; } std::lock_guard lock(cond->mutex->mutex); diff --git a/rpcs3/Emu/Cell/lv2/sys_cond.h b/rpcs3/Emu/Cell/lv2/sys_cond.h index 7baef27841..91fb35ec7c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_cond.h +++ b/rpcs3/Emu/Cell/lv2/sys_cond.h @@ -38,18 +38,11 @@ struct lv2_cond final : lv2_obj { } - CellError on_id_create() - { - if (mutex->exists) - { - mutex->cond_count++; - exists++; - return {}; - } + lv2_cond(utils::serial& ar); + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); - // Mutex has been destroyed, cannot create conditional variable - return CELL_ESRCH; - } + CellError on_id_create(); }; class ppu_thread; diff --git a/rpcs3/Emu/Cell/lv2/sys_config.h b/rpcs3/Emu/Cell/lv2/sys_config.h index 8c3027c4bf..d1a138f1b7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_config.h +++ b/rpcs3/Emu/Cell/lv2/sys_config.h @@ -169,6 +169,7 @@ public: static const u32 id_base = 0x41000000; static const u32 id_step = 0x100; static const u32 id_count = 2048; + SAVESTATE_INIT_POS(37); private: u32 idm_id; @@ -219,6 +220,7 @@ public: static const u32 id_base = 0x43000000; static const u32 id_step = 0x100; static const u32 id_count = 2048; + SAVESTATE_INIT_POS(38); private: // IDM data @@ -283,6 +285,7 @@ public: static const u32 id_base = 0x42000000; static const u32 id_step = 0x100; static const u32 id_count = 2048; + SAVESTATE_INIT_POS(39); private: // IDM data diff --git a/rpcs3/Emu/Cell/lv2/sys_event.cpp b/rpcs3/Emu/Cell/lv2/sys_event.cpp index 4470c813e3..45839b032d 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_event.cpp @@ -3,6 +3,7 @@ #include "Emu/IdManager.h" #include "Emu/IPC.h" +#include "Emu/System.h" #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" @@ -21,6 +22,78 @@ lv2_event_queue::lv2_event_queue(u32 protocol, s32 type, s32 size, u64 name, u64 { } +lv2_event_queue::lv2_event_queue(utils::serial& ar) noexcept + : id(idm::last_id()) + , protocol(ar) + , type(ar) + , size(ar) + , name(ar) + , key(ar) +{ + ar(events); +} + +std::shared_ptr lv2_event_queue::load(utils::serial& ar) +{ + auto queue = std::make_shared(ar); + return lv2_obj::load(queue->key, queue); +} + +void lv2_event_queue::save(utils::serial& ar) +{ + ar(protocol, type, size, name, key, events); +} + +void lv2_event_queue::save_ptr(utils::serial& ar, lv2_event_queue* q) +{ + if (!lv2_obj::check(q)) + { + ar(u32{0}); + return; + } + + ar(q->id); +} + +std::shared_ptr lv2_event_queue::load_ptr(utils::serial& ar, std::shared_ptr& queue) +{ + const u32 id = ar.operator u32(); + + if (!id) + { + return nullptr; + } + + if (auto q = idm::get_unlocked(id)) + { + // Already initialized + return q; + } + + Emu.DeferDeserialization([id, &queue]() + { + // Defer resolving + queue = ensure(idm::get_unlocked(id)); + }); + + // Null until resolved + return nullptr; +} + +lv2_event_port::lv2_event_port(utils::serial& ar) + : type(ar) + , name(ar) + , queue(lv2_event_queue::load_ptr(ar, queue)) +{ +} + +void lv2_event_port::save(utils::serial& ar) +{ + ar(type, name); + + lv2_event_queue::save_ptr(ar, queue.get()); +} + std::shared_ptr lv2_event_queue::find(u64 ipc_key) { if (ipc_key == SYS_EVENT_QUEUE_LOCAL) @@ -60,6 +133,20 @@ CellError lv2_event_queue::send(lv2_event event) // Store event in registers auto& ppu = static_cast(*schedule(sq, protocol)); + if (ppu.state & cpu_flag::again) + { + if (auto cpu = get_current_cpu_thread()) + { + cpu->state += cpu_flag::again; + cpu->state += cpu_flag::exit; + } + + sys_event.warning("Ignored event!"); + + // Fake error for abort + return CELL_EAGAIN; + } + std::tie(ppu.gpr[4], ppu.gpr[5], ppu.gpr[6], ppu.gpr[7]) = event; awake(&ppu); @@ -68,6 +155,19 @@ CellError lv2_event_queue::send(lv2_event event) { // Store event in In_MBox auto& spu = static_cast(*schedule(sq, protocol)); + + if (spu.state & cpu_flag::again) + { + if (auto cpu = get_current_cpu_thread()) + { + cpu->state += cpu_flag::exit + cpu_flag::again; + } + + sys_event.warning("Ignored event!"); + + // Fake error for abort + return CELL_EAGAIN; + } const u32 data1 = static_cast(std::get<1>(event)); const u32 data2 = static_cast(std::get<2>(event)); @@ -331,11 +431,18 @@ error_code sys_event_queue_receive(ppu_thread& ppu, u32 equeue_id, vm::ptrmutex); @@ -559,6 +666,11 @@ error_code sys_event_port_send(u32 eport_id, u64 data1, u64 data2, u64 data3) if (port.ret) { + if (port.ret == CELL_EAGAIN) + { + return CELL_OK; + } + if (port.ret == CELL_EBUSY) { return not_an_error(CELL_EBUSY); diff --git a/rpcs3/Emu/Cell/lv2/sys_event.h b/rpcs3/Emu/Cell/lv2/sys_event.h index edab584cec..f51adf9fc6 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event.h +++ b/rpcs3/Emu/Cell/lv2/sys_event.h @@ -93,6 +93,12 @@ struct lv2_event_queue final : public lv2_obj lv2_event_queue(u32 protocol, s32 type, s32 size, u64 name, u64 ipc_key) noexcept; + lv2_event_queue(utils::serial& ar) noexcept; + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); + static void save_ptr(utils::serial&, lv2_event_queue*); + static std::shared_ptr load_ptr(utils::serial& ar, std::shared_ptr& queue); + CellError send(lv2_event); CellError send(u64 source, u64 d1, u64 d2, u64 d3) @@ -102,10 +108,6 @@ struct lv2_event_queue final : public lv2_obj // Get event queue by its global key static std::shared_ptr find(u64 ipc_key); - - // Check queue ptr validity (use 'exists' member) - static bool check(const std::weak_ptr&); - static bool check(const std::shared_ptr&); }; struct lv2_event_port final : lv2_obj @@ -122,6 +124,9 @@ struct lv2_event_port final : lv2_obj , name(name) { } + + lv2_event_port(utils::serial& ar); + void save(utils::serial& ar); }; class ppu_thread; diff --git a/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp b/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp index aaca1761e0..2803ee1575 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_event_flag.cpp @@ -11,6 +11,26 @@ LOG_CHANNEL(sys_event_flag); +lv2_event_flag::lv2_event_flag(utils::serial& ar) + : protocol(ar) + , key(ar) + , type(ar) + , name(ar) +{ + ar(pattern); +} + +std::shared_ptr lv2_event_flag::load(utils::serial& ar) +{ + auto eflag = std::make_shared(ar); + return lv2_obj::load(eflag->key, eflag); +} + +void lv2_event_flag::save(utils::serial& ar) +{ + ar(protocol, key, type, name, pattern); +} + error_code sys_event_flag_create(ppu_thread& ppu, vm::ptr id, vm::ptr attr, u64 init) { ppu.state += cpu_flag::wait; @@ -171,16 +191,24 @@ error_code sys_event_flag_wait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(state)) - { - return {}; - } - if (state & cpu_flag::signal) { break; } + if (is_stopped(state)) + { + std::lock_guard lock(flag->mutex); + + if (std::find(flag->sq.begin(), flag->sq.end(), &ppu) == flag->sq.end()) + { + break; + } + + ppu.state += cpu_flag::again; + return {}; + } + if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) @@ -188,7 +216,7 @@ error_code sys_event_flag_wait(ppu_thread& ppu, u32 id, u64 bitptn, u32 mode, vm // Wait for rescheduling if (ppu.check_state()) { - return {}; + continue; } std::lock_guard lock(flag->mutex); @@ -275,6 +303,17 @@ error_code sys_event_flag_set(cpu_thread& cpu, u32 id, u64 bitptn) { std::lock_guard lock(flag->mutex); + for (auto ppu : flag->sq) + { + if (ppu->state & cpu_flag::again) + { + cpu.state += cpu_flag::again; + + // Fake error for abort + return not_an_error(CELL_EAGAIN); + } + } + // Sort sleep queue in required order if (flag->protocol != SYS_SYNC_FIFO) { @@ -379,6 +418,15 @@ error_code sys_event_flag_cancel(ppu_thread& ppu, u32 id, vm::ptr num) { std::lock_guard lock(flag->mutex); + for (auto cpu : flag->sq) + { + if (cpu->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return {}; + } + } + // Get current pattern const u64 pattern = flag->pattern; @@ -403,10 +451,7 @@ error_code sys_event_flag_cancel(ppu_thread& ppu, u32 id, vm::ptr num) } } - if (ppu.test_stopped()) - { - return 0; - } + static_cast(ppu.test_stopped()); if (num) *num = value; return CELL_OK; diff --git a/rpcs3/Emu/Cell/lv2/sys_event_flag.h b/rpcs3/Emu/Cell/lv2/sys_event_flag.h index 2fccc61f43..3f761a3ddc 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event_flag.h +++ b/rpcs3/Emu/Cell/lv2/sys_event_flag.h @@ -54,6 +54,10 @@ struct lv2_event_flag final : lv2_obj { } + lv2_event_flag(utils::serial& ar); + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); + // Check mode arg static bool check_mode(u32 mode) { diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index aade7011c6..937a970de9 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -14,6 +14,7 @@ #include "Utilities/StrUtil.h" #include +#include LOG_CHANNEL(sys_fs); @@ -199,6 +200,12 @@ lv2_fs_mount_point* lv2_fs_object::get_mp(std::string_view filename) return &g_mp_sys_dev_root; } +lv2_fs_object::lv2_fs_object(utils::serial& ar, bool) + : name(ar) + , mp(get_mp(name.data())) +{ +} + u64 lv2_file::op_read(const fs::file& file, vm::ptr buf, u64 size) { // Copy data from intermediate buffer (avoid passing vm pointer to a native API) @@ -246,6 +253,136 @@ u64 lv2_file::op_write(const fs::file& file, vm::cptr buf, u64 size) return result; } +lv2_file::lv2_file(utils::serial& ar) + : lv2_fs_object(ar, false) + , mode(ar) + , flags(ar) + , type(ar) +{ + ar(lock); + + be_t arg = 0; + u64 size = 0; + + switch (type) + { + case lv2_file_type::regular: break; + case lv2_file_type::sdata: arg = 0x18000000010, size = 8; break; // TODO: Fix + case lv2_file_type::edata: arg = 0x2, size = 8; break; + } + + const std::string retrieve_real = ar; + + open_result_t res = lv2_file::open(retrieve_real, flags & CELL_FS_O_ACCMODE, mode, size ? &arg : nullptr, size); + file = std::move(res.file); + real_path = std::move(res.real_path); + + g_fxo->get().npdrm_fds.raw() += type != lv2_file_type::regular; + + if (ar.operator bool()) // see lv2_file::save in_mem + { + std::vector buf = ar; + const fs::stat_t stat = ar; + file = fs::make_stream>(std::move(buf), stat); + } + + if (!file) + { + sys_fs.error("Failed to load %s for savestates", name.data()); + ar.pos += sizeof(u64); + ensure(!!g_cfg.savestate.state_inspection_mode); + return; + } + + file.seek(ar); +} + +void lv2_file::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_fs); + ar(name, mode, flags, type, lock, vfs::retrieve(real_path)); + + if (!(mp->flags & lv2_mp_flag::read_only) && flags & CELL_FS_O_ACCMODE) + { + // Ensure accurate timestamps and content on disk + file.sync(); + } + + // UNIX allows deletion of files while descriptors are still opened + // descriptpors shall keep the data in memory in this case + const bool in_mem = [&]() + { + if (mp->flags & lv2_mp_flag::read_only) + { + return false; + } + + fs::file test{real_path}; + + if (!test) return true; + + return test.stat() != file.stat(); + }(); + + ar(in_mem); + + if (in_mem) + { + ar(file.to_vector()); + ar(file.stat()); + } + + ar(file.pos()); +} + +lv2_dir::lv2_dir(utils::serial& ar) + : lv2_fs_object(ar, false) + , entries([&] + { + std::vector entries; + + u64 size = 0; + ar.deserialize_vle(size); + entries.resize(size); + + for (auto& entry : entries) + { + ar(entry.name, static_cast(entry)); + } + + return entries; + }()) + , pos(ar) +{ +} + +void lv2_dir::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_fs); + + ar(name); + + ar.serialize_vle(entries.size()); + + for (auto& entry : entries) + { + ar(entry.name, static_cast(entry)); + } + + ar(pos); +} + +loaded_npdrm_keys::loaded_npdrm_keys(utils::serial& ar) +{ + save(ar); +} + +void loaded_npdrm_keys::save(utils::serial& ar) +{ + ar(dec_keys_pos); + ar(std::span(dec_keys, std::min(std::size(dec_keys), dec_keys_pos))); +} + struct lv2_file::file_view : fs::file_base { const std::shared_ptr m_file; @@ -2281,10 +2418,7 @@ error_code sys_fs_fget_block_size(ppu_thread& ppu, u32 fd, vm::ptr sector_s return CELL_EBADF; } - if (ppu.is_stopped()) - { - return {}; - } + static_cast(ppu.test_stopped()); // TODO *sector_size = file->mp->sector_size; @@ -2335,10 +2469,7 @@ error_code sys_fs_get_block_size(ppu_thread& ppu, vm::cptr path, vm::ptr(ppu.test_stopped()); // TODO *sector_size = mp->sector_size; diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.h b/rpcs3/Emu/Cell/lv2/sys_fs.h index 0a2b5950ff..6347dde5dc 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.h +++ b/rpcs3/Emu/Cell/lv2/sys_fs.h @@ -163,20 +163,23 @@ struct lv2_fs_object static const u32 id_base = 3; static const u32 id_step = 1; static const u32 id_count = 255 - id_base; - - // Mount Point - const std::add_pointer_t mp; + SAVESTATE_INIT_POS(40); // File Name (max 1055) const std::array name; + // Mount Point + const std::add_pointer_t mp; + protected: lv2_fs_object(lv2_fs_mount_point* mp, std::string_view filename) - : mp(mp) - , name(get_name(filename)) + : name(get_name(filename)) + , mp(mp) { } + lv2_fs_object(utils::serial& ar, bool dummy); + public: lv2_fs_object(const lv2_fs_object&) = delete; @@ -197,10 +200,14 @@ public: name[filename.size()] = 0; return name; } + + void save(utils::serial&) {} }; struct lv2_file final : lv2_fs_object { + static constexpr u32 id_type = 1; + fs::file file; const s32 mode; const s32 flags; @@ -241,6 +248,9 @@ struct lv2_file final : lv2_fs_object { } + lv2_file(utils::serial& ar); + void save(utils::serial& ar); + struct open_raw_result_t { CellError error; @@ -285,6 +295,8 @@ struct lv2_file final : lv2_fs_object struct lv2_dir final : lv2_fs_object { + static constexpr u32 id_type = 2; + const std::vector entries; // Current reading position @@ -296,6 +308,9 @@ struct lv2_dir final : lv2_fs_object { } + lv2_dir(utils::serial& ar); + void save(utils::serial& ar); + // Read next const fs::dir_entry* dir_read() { diff --git a/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp b/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp index 86660f924d..00dd50f98e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_interrupt.cpp @@ -2,6 +2,7 @@ #include "sys_interrupt.h" #include "Emu/IdManager.h" +#include "Emu/System.h" #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" @@ -11,18 +12,59 @@ LOG_CHANNEL(sys_interrupt); lv2_int_tag::lv2_int_tag() noexcept - : id(idm::last_id()) + : lv2_obj{1} + , id(idm::last_id()) { - exists.release(1); +} + +lv2_int_tag::lv2_int_tag(utils::serial& ar) noexcept + : lv2_obj{1} + , id(idm::last_id()) + , handler([&]() + { + const u32 id = ar; + + auto ptr = idm::get_unlocked(id); + + if (!ptr && id) + { + Emu.DeferDeserialization([id, &handler = this->handler]() + { + handler = ensure(idm::get_unlocked(id)); + }); + } + + return ptr; + }()) +{ +} + +void lv2_int_tag::save(utils::serial& ar) +{ + ar(lv2_obj::check(handler) ? handler->id : 0); } lv2_int_serv::lv2_int_serv(const std::shared_ptr>& thread, u64 arg1, u64 arg2) noexcept - : id(idm::last_id()) + : lv2_obj{1} + , id(idm::last_id()) , thread(thread) , arg1(arg1) , arg2(arg2) { - exists.release(1); +} + +lv2_int_serv::lv2_int_serv(utils::serial& ar) noexcept + : lv2_obj{1} + , id(idm::last_id()) + , thread(idm::get_unlocked>(ar)) + , arg1(ar) + , arg2(ar) +{ +} + +void lv2_int_serv::save(utils::serial& ar) +{ + ar(thread && idm::check_unlocked>(thread->id) ? thread->id : 0, arg1, arg2); } void ppu_interrupt_thread_entry(ppu_thread&, ppu_opcode_t, be_t*, struct ppu_intrp_func*); @@ -197,6 +239,8 @@ void sys_interrupt_thread_eoi(ppu_thread& ppu) ppu.state += cpu_flag::ret; lv2_obj::sleep(ppu); + + ppu.interrupt_thread_executing = false; } void ppu_interrupt_thread_entry(ppu_thread& ppu, ppu_opcode_t, be_t*, struct ppu_intrp_func*) diff --git a/rpcs3/Emu/Cell/lv2/sys_interrupt.h b/rpcs3/Emu/Cell/lv2/sys_interrupt.h index f28af0b6e5..9fe4dc9d39 100644 --- a/rpcs3/Emu/Cell/lv2/sys_interrupt.h +++ b/rpcs3/Emu/Cell/lv2/sys_interrupt.h @@ -6,7 +6,7 @@ class ppu_thread; -struct lv2_int_tag final : lv2_obj +struct lv2_int_tag final : public lv2_obj { static const u32 id_base = 0x0a000000; @@ -14,9 +14,11 @@ struct lv2_int_tag final : lv2_obj std::shared_ptr handler; lv2_int_tag() noexcept; + lv2_int_tag(utils::serial& ar) noexcept; + void save(utils::serial& ar); }; -struct lv2_int_serv final : lv2_obj +struct lv2_int_serv final : public lv2_obj { static const u32 id_base = 0x0b000000; @@ -26,6 +28,8 @@ struct lv2_int_serv final : lv2_obj const u64 arg2; lv2_int_serv(const std::shared_ptr>& thread, u64 arg1, u64 arg2) noexcept; + lv2_int_serv(utils::serial& ar) noexcept; + void save(utils::serial& ar); void exec() const; void join() const; diff --git a/rpcs3/Emu/Cell/lv2/sys_io.h b/rpcs3/Emu/Cell/lv2/sys_io.h index 1d2491c53a..27c8b99857 100644 --- a/rpcs3/Emu/Cell/lv2/sys_io.h +++ b/rpcs3/Emu/Cell/lv2/sys_io.h @@ -4,10 +4,10 @@ struct lv2_io_buf { - using id_type = lv2_io_buf; static const u32 id_base = 0x44000000; static const u32 id_step = 1; static const u32 id_count = 2048; + SAVESTATE_INIT_POS(41); const u32 block_count; const u32 block_size; diff --git a/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp b/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp index ab1da88567..dd4b866c68 100644 --- a/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp @@ -9,6 +9,20 @@ LOG_CHANNEL(sys_lwcond); +lv2_lwcond::lv2_lwcond(utils::serial& ar) + : name(ar.operator be_t()) + , lwid(ar) + , protocol(ar) + , control(ar.operator decltype(control)()) +{ +} + +void lv2_lwcond::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_sync); + ar(name, lwid, protocol, control); +} + error_code _sys_lwcond_create(ppu_thread& ppu, vm::ptr lwcond_id, u32 lwmutex_id, vm::ptr control, u64 name) { ppu.state += cpu_flag::wait; @@ -115,12 +129,25 @@ error_code _sys_lwcond_signal(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u6 { std::lock_guard lock(cond.mutex); + if (cpu) + { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return 0; + } + } + auto result = cpu ? cond.unqueue(cond.sq, cpu) : cond.schedule(cond.sq, cond.protocol); if (result) { - cond.waiters--; + if (static_cast(result)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return 0; + } if (mode == 2) { @@ -137,6 +164,12 @@ error_code _sys_lwcond_signal(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u6 // Respect ordering of the sleep queue mutex->sq.emplace_back(result); result = mutex->schedule(mutex->sq, mutex->protocol); + + if (static_cast(result)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return 0; + } } else if (mode == 1) { @@ -145,6 +178,8 @@ error_code _sys_lwcond_signal(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id, u6 } } + cond.waiters--; + if (result) { cond.awake(result); @@ -218,6 +253,15 @@ error_code _sys_lwcond_signal_all(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id u32 result = 0; + for (auto cpu : cond.sq) + { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return 0; + } + } + while (const auto cpu = cond.schedule(cond.sq, cond.protocol)) { cond.waiters--; @@ -291,16 +335,32 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id std::lock_guard lock(cond.mutex); - // Add a waiter - cond.waiters++; - cond.sq.emplace_back(&ppu); + if (ppu.loaded_from_savestate && ppu.optional_syscall_state) + { + // Special: loading state from the point of waiting on lwmutex sleep queue + std::lock_guard lock2(mutex->mutex); + mutex->sq.emplace_back(&ppu); + } + else + { + // Add a waiter + cond.waiters++; + cond.sq.emplace_back(&ppu); + } + if (!ppu.loaded_from_savestate) { std::lock_guard lock2(mutex->mutex); // Process lwmutex sleep queue if (const auto cpu = mutex->schedule(mutex->sq, mutex->protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return; + } + cond.append(cpu); } else @@ -318,18 +378,36 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id return CELL_ESRCH; } + if (ppu.state & cpu_flag::again) + { + return CELL_OK; + } + while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(state)) - { - return {}; - } - if (state & cpu_flag::signal) { break; } + if (is_stopped(state)) + { + reader_lock lock(cond->mutex); + reader_lock lock2(mutex->mutex); + + const bool cond_sleep = std::find(cond->sq.begin(), cond->sq.end(), &ppu) != cond->sq.end(); + const bool mutex_sleep = std::find(mutex->sq.begin(), mutex->sq.end(), &ppu) != mutex->sq.end(); + + if (!cond_sleep && !mutex_sleep) + { + break; + } + + ppu.optional_syscall_state = +mutex_sleep; + ppu.state += cpu_flag::again; + break; + } + if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) @@ -337,7 +415,7 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id // Wait for rescheduling if (ppu.check_state()) { - return {}; + continue; } std::lock_guard lock(cond->mutex); diff --git a/rpcs3/Emu/Cell/lv2/sys_lwcond.h b/rpcs3/Emu/Cell/lv2/sys_lwcond.h index d4cf18755e..d600c15825 100644 --- a/rpcs3/Emu/Cell/lv2/sys_lwcond.h +++ b/rpcs3/Emu/Cell/lv2/sys_lwcond.h @@ -41,6 +41,9 @@ struct lv2_lwcond final : lv2_obj , control(control) { } + + lv2_lwcond(utils::serial& ar); + void save(utils::serial& ar); }; // Aux diff --git a/rpcs3/Emu/Cell/lv2/sys_lwmutex.cpp b/rpcs3/Emu/Cell/lv2/sys_lwmutex.cpp index 0427b9c3d5..5354c8a1af 100644 --- a/rpcs3/Emu/Cell/lv2/sys_lwmutex.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_lwmutex.cpp @@ -8,6 +8,19 @@ LOG_CHANNEL(sys_lwmutex); +lv2_lwmutex::lv2_lwmutex(utils::serial& ar) + : protocol(ar) + , control(ar.operator decltype(control)()) + , name(ar.operator be_t()) + , signaled(ar) +{ +} + +void lv2_lwmutex::save(utils::serial& ar) +{ + ar(protocol, control, name, signaled); +} + error_code _sys_lwmutex_create(ppu_thread& ppu, vm::ptr lwmutex_id, u32 protocol, vm::ptr control, s32 has_name, u64 name) { ppu.state += cpu_flag::wait; @@ -102,6 +115,7 @@ error_code _sys_lwmutex_destroy(ppu_thread& ppu, u32 lwmutex_id) if (ppu.is_stopped()) { + ppu.state += cpu_flag::again; return {}; } @@ -170,16 +184,24 @@ error_code _sys_lwmutex_lock(ppu_thread& ppu, u32 lwmutex_id, u64 timeout) while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(state)) - { - return {}; - } - if (state & cpu_flag::signal) { break; } + if (is_stopped(state)) + { + std::lock_guard lock(mutex->mutex); + + if (std::find(mutex->sq.begin(), mutex->sq.end(), &ppu) == mutex->sq.end()) + { + break; + } + + ppu.state += cpu_flag::again; + return {}; + } + if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) @@ -187,7 +209,7 @@ error_code _sys_lwmutex_lock(ppu_thread& ppu, u32 lwmutex_id, u64 timeout) // Wait for rescheduling if (ppu.check_state()) { - return {}; + continue; } std::lock_guard lock(mutex->mutex); @@ -257,6 +279,12 @@ error_code _sys_lwmutex_unlock(ppu_thread& ppu, u32 lwmutex_id) if (const auto cpu = mutex.schedule(mutex.sq, mutex.protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return; + } + mutex.awake(cpu); return; } @@ -284,6 +312,12 @@ error_code _sys_lwmutex_unlock2(ppu_thread& ppu, u32 lwmutex_id) if (const auto cpu = mutex.schedule(mutex.sq, mutex.protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return; + } + static_cast(cpu)->gpr[3] = CELL_EBUSY; mutex.awake(cpu); return; diff --git a/rpcs3/Emu/Cell/lv2/sys_lwmutex.h b/rpcs3/Emu/Cell/lv2/sys_lwmutex.h index cf853eb00e..8aec9241d5 100644 --- a/rpcs3/Emu/Cell/lv2/sys_lwmutex.h +++ b/rpcs3/Emu/Cell/lv2/sys_lwmutex.h @@ -71,6 +71,9 @@ struct lv2_lwmutex final : lv2_obj { } + lv2_lwmutex(utils::serial& ar); + void save(utils::serial& ar); + // Add a waiter void add_waiter(cpu_thread* cpu) { diff --git a/rpcs3/Emu/Cell/lv2/sys_memory.cpp b/rpcs3/Emu/Cell/lv2/sys_memory.cpp index ab4863d0fb..ca4240ff12 100644 --- a/rpcs3/Emu/Cell/lv2/sys_memory.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_memory.cpp @@ -12,18 +12,77 @@ LOG_CHANNEL(sys_memory); +// +static shared_mutex s_memstats_mtx; + lv2_memory_container::lv2_memory_container(u32 size, bool from_idm) noexcept : size(size) , id{from_idm ? idm::last_id() : SYS_MEMORY_CONTAINER_ID_INVALID} { } -// -static shared_mutex s_memstats_mtx; +lv2_memory_container::lv2_memory_container(utils::serial& ar, bool from_idm) noexcept + : size(ar) + , id{from_idm ? idm::last_id() : SYS_MEMORY_CONTAINER_ID_INVALID} + , used(ar) +{ +} + +std::shared_ptr lv2_memory_container::load(utils::serial& ar) +{ + // Use idm::last_id() only for the instances at IDM + return std::make_shared(stx::exact_t(ar), true); +} + +void lv2_memory_container::save(utils::serial& ar) +{ + ar(size, used); +} + +lv2_memory_container* lv2_memory_container::search(u32 id) +{ + if (id != SYS_MEMORY_CONTAINER_ID_INVALID) + { + return idm::check(id); + } + + return &g_fxo->get(); +} struct sys_memory_address_table { atomic_t addrs[65536]{}; + + sys_memory_address_table() = default; + + SAVESTATE_INIT_POS(id_manager::id_map::savestate_init_pos + 0.1); + + sys_memory_address_table(utils::serial& ar) + { + // First: address, second: conatiner ID (SYS_MEMORY_CONTAINER_ID_INVALID for global FXO memory container) + std::unordered_map mm; + ar(mm); + + for (const auto& [addr, id] : mm) + { + addrs[addr] = ensure(lv2_memory_container::search(id)); + } + } + + void save(utils::serial& ar) + { + std::unordered_map mm; + + for (auto& ctr : addrs) + { + if (const auto ptr = +ctr) + { + mm[static_cast(&ctr - addrs)] = ptr->id; + } + } + + ar(mm); + } }; // Todo: fix order of error checks diff --git a/rpcs3/Emu/Cell/lv2/sys_memory.h b/rpcs3/Emu/Cell/lv2/sys_memory.h index b903f90ecc..36827ec736 100644 --- a/rpcs3/Emu/Cell/lv2/sys_memory.h +++ b/rpcs3/Emu/Cell/lv2/sys_memory.h @@ -70,7 +70,13 @@ struct lv2_memory_container const lv2_mem_container_id id; // ID of the container in if placed at IDM, otherwise SYS_MEMORY_CONTAINER_ID_INVALID atomic_t used{}; // Amount of "physical" memory currently used + SAVESTATE_INIT_POS(1); + lv2_memory_container(u32 size, bool from_idm = false) noexcept; + lv2_memory_container(utils::serial& ar, bool from_idm = false) noexcept; + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); + static lv2_memory_container* search(u32 id); // Try to get specified amount of "physical" memory // Values greater than UINT32_MAX will fail diff --git a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp index d2d5308c4a..b992a42be3 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mmapper.cpp @@ -8,6 +8,8 @@ #include "sys_sync.h" #include "sys_process.h" +#include + #include "util/vm.hpp" LOG_CHANNEL(sys_mmapper); @@ -42,6 +44,32 @@ lv2_memory::lv2_memory(u32 size, u32 align, u64 flags, u64 key, bool pshared, lv #endif } +lv2_memory::lv2_memory(utils::serial& ar) + : size(ar) + , align(ar) + , flags(ar) + , key(ar) + , pshared(ar) + , ct(lv2_memory_container::search(ar.operator u32())) + , shm([&](u32 addr) + { + if (addr) + { + return ensure(vm::get(vm::any, addr)->peek(addr).second); + } + + const auto _shm = std::make_shared(size, 1); + ar(std::span(_shm->map_self(), size)); + return _shm; + }(ar.operator u32())) + , counter(ar) +{ +#ifndef _WIN32 + // Optimization that's useless on Windows :puke: + utils::memory_lock(shm->map_self(), size); +#endif +} + CellError lv2_memory::on_id_create() { if (!exists && !ct->take(size)) @@ -53,6 +81,40 @@ CellError lv2_memory::on_id_create() return {}; } +std::shared_ptr lv2_memory::load(utils::serial& ar) +{ + auto mem = std::make_shared(ar); + mem->exists++; // Disable on_id_create() + std::shared_ptr ptr = lv2_obj::load(mem->key, mem, +mem->pshared); + mem->exists--; + return ptr; +} + +void lv2_memory::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_memory); + + ar(size, align, flags, key, pshared, ct->id); + ar(counter ? vm::get_shm_addr(shm) : 0); + + if (!counter) + { + ar(std::span(shm->map_self(), size)); + } + + ar(counter); +} + +page_fault_notification_entries::page_fault_notification_entries(utils::serial& ar) +{ + ar(entries); +} + +void page_fault_notification_entries::save(utils::serial& ar) +{ + ar(entries); +} + template error_code create_lv2_shm(bool pshared, u64 ipc_key, u64 size, u32 align, u64 flags, lv2_memory_container* ct) { diff --git a/rpcs3/Emu/Cell/lv2/sys_mmapper.h b/rpcs3/Emu/Cell/lv2/sys_mmapper.h index a34119f784..57f2c33dd5 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mmapper.h +++ b/rpcs3/Emu/Cell/lv2/sys_mmapper.h @@ -30,6 +30,10 @@ struct lv2_memory : lv2_obj lv2_memory(u32 size, u32 align, u64 flags, u64 key, bool pshared, lv2_memory_container* ct); + lv2_memory(utils::serial& ar); + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); + CellError on_id_create(); }; @@ -54,6 +58,8 @@ enum : u64 struct page_fault_notification_entry { + ENABLE_BITWISE_SERIALIZATION; + u32 start_addr; // Starting address of region to monitor. u32 event_queue_id; // Queue to be notified. u32 port_id; // Port used to notify the queue. @@ -64,6 +70,10 @@ struct page_fault_notification_entries { std::vector entries; shared_mutex mutex; + + page_fault_notification_entries() = default; + page_fault_notification_entries(utils::serial& ar); + void save(utils::serial& ar); }; struct page_fault_event_entries diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp index f4dfd002ac..e4f79e4f2e 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp @@ -9,6 +9,27 @@ LOG_CHANNEL(sys_mutex); +lv2_mutex::lv2_mutex(utils::serial& ar) + : protocol(ar) + , recursive(ar) + , adaptive(ar) + , key(ar) + , name(ar) +{ + ar(lock_count, owner); +} + +std::shared_ptr lv2_mutex::load(utils::serial& ar) +{ + auto mtx = std::make_shared(ar); + return lv2_obj::load(mtx->key, mtx); +} + +void lv2_mutex::save(utils::serial& ar) +{ + ar(protocol, recursive, adaptive, key, name, lock_count, owner & -2); +} + error_code sys_mutex_create(ppu_thread& ppu, vm::ptr mutex_id, vm::ptr attr) { ppu.state += cpu_flag::wait; @@ -154,11 +175,24 @@ error_code sys_mutex_lock(ppu_thread& ppu, u32 mutex_id, u64 timeout) while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(state) || state & cpu_flag::signal) + if (state & cpu_flag::signal) { break; } + if (is_stopped(state)) + { + std::lock_guard lock(mutex->mutex); + + if (std::find(mutex->sq.begin(), mutex->sq.end(), &ppu) == mutex->sq.end()) + { + break; + } + + ppu.state += cpu_flag::again; + return {}; + } + if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) @@ -166,7 +200,7 @@ error_code sys_mutex_lock(ppu_thread& ppu, u32 mutex_id, u64 timeout) // Wait for rescheduling if (ppu.check_state()) { - return {}; + continue; } std::lock_guard lock(mutex->mutex); diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.h b/rpcs3/Emu/Cell/lv2/sys_mutex.h index d7b9e5a06a..78ff9725cb 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.h +++ b/rpcs3/Emu/Cell/lv2/sys_mutex.h @@ -46,6 +46,10 @@ struct lv2_mutex final : lv2_obj { } + lv2_mutex(utils::serial& ar); + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); + CellError try_lock(u32 id) { const u32 value = owner; diff --git a/rpcs3/Emu/Cell/lv2/sys_net.cpp b/rpcs3/Emu/Cell/lv2/sys_net.cpp index 05c6fdb7ea..f84ef00a0f 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net.cpp @@ -240,6 +240,89 @@ void fmt_class_string::format(std::string& out, u64 arg) fmt::append(out, "%u.%u.%u.%u", data[0], data[1], data[2], data[3]); } +lv2_socket::lv2_socket(utils::serial& ar, lv2_socket_type _type) + : family(ar) + , type(_type) + , protocol(ar) + , so_nbio(ar) + , so_error(ar) + , so_tcp_maxseg(ar) +#ifdef _WIN32 + , so_reuseaddr(ar) + , so_reuseport(ar) +{ +#else +{ + // Try to match structure between different platforms + ar.pos += 8; +#endif + lv2_id = idm::last_id(); + + ar(last_bound_addr); +} + +std::shared_ptr lv2_socket::load(utils::serial& ar) +{ + const lv2_socket_type type{ar}; + + std::shared_ptr sock_lv2; + + switch (type) + { + case SYS_NET_SOCK_STREAM: + case SYS_NET_SOCK_DGRAM: + { + auto lv2_native = std::make_shared(ar, type); + ensure(lv2_native->create_socket() >= 0); + sock_lv2 = std::move(lv2_native); + break; + } + case SYS_NET_SOCK_RAW: sock_lv2 = std::make_shared(ar, type); break; + case SYS_NET_SOCK_DGRAM_P2P: sock_lv2 = std::make_shared(ar, type); break; + case SYS_NET_SOCK_STREAM_P2P: sock_lv2 = std::make_shared(ar, type); break; + } + + if (std::memcmp(&sock_lv2->last_bound_addr, std::array{}.data(), 16)) + { + // NOTE: It is allowed fail + sock_lv2->bind(sock_lv2->last_bound_addr); + } + + return sock_lv2; +} + +void lv2_socket::save(utils::serial& ar, bool save_only_this_class) +{ + USING_SERIALIZATION_VERSION(lv2_net); + + if (save_only_this_class) + { + ar(family, protocol, so_nbio, so_error, so_tcp_maxseg); +#ifdef _WIN32 + ar(so_reuseaddr, so_reuseport); +#else + ar(std::array{}); +#endif + ar(last_bound_addr); + return; + } + + ar(type); + + switch (type) + { + case SYS_NET_SOCK_STREAM: + case SYS_NET_SOCK_DGRAM: + { + static_cast(this)->save(ar); + break; + } + case SYS_NET_SOCK_RAW: static_cast(this)->save(ar); break; + case SYS_NET_SOCK_DGRAM_P2P: static_cast(this)->save(ar); break; + case SYS_NET_SOCK_STREAM_P2P: static_cast(this)->save(ar); break; + } +} + void sys_net_dump_data(std::string_view desc, const u8* data, s32 len) { if (sys_net_dump.enabled == logs::level::trace) @@ -360,10 +443,7 @@ error_code sys_net_bnet_accept(ppu_thread& ppu, s32 s, vm::ptr } } - if (ppu.is_stopped()) - { - return {}; - } + static_cast(ppu.test_stopped()); if (addr) { @@ -402,7 +482,7 @@ error_code sys_net_bnet_bind(ppu_thread& ppu, s32 s, vm::cptr const auto sock = idm::check(s, [&](lv2_socket& sock) -> s32 { - return sock.bind(sn_addr, s); + return sock.bind(sn_addr); }); if (!sock) @@ -779,10 +859,7 @@ error_code sys_net_bnet_recvfrom(ppu_thread& ppu, s32 s, vm::ptr buf, u32 } } - if (ppu.is_stopped()) - { - return {}; - } + static_cast(ppu.test_stopped()); if (result == -SYS_NET_EWOULDBLOCK) { diff --git a/rpcs3/Emu/Cell/lv2/sys_net.h b/rpcs3/Emu/Cell/lv2/sys_net.h index 0411954787..55e6cb80a8 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net.h +++ b/rpcs3/Emu/Cell/lv2/sys_net.h @@ -269,6 +269,8 @@ struct sys_net_pollfd // sockaddr prefixed with sys_net_ struct sys_net_sockaddr { + ENABLE_BITWISE_SERIALIZATION; + u8 sa_len; u8 sa_family; char sa_data[14]; @@ -277,6 +279,8 @@ struct sys_net_sockaddr // sockaddr_dl prefixed with sys_net_ struct sys_net_sockaddr_dl { + ENABLE_BITWISE_SERIALIZATION; + u8 sdl_len; u8 sdl_family; be_t sdl_index; @@ -290,6 +294,8 @@ struct sys_net_sockaddr_dl // sockaddr_in prefixed with sys_net_ struct sys_net_sockaddr_in { + ENABLE_BITWISE_SERIALIZATION; + u8 sin_len; u8 sin_family; be_t sin_port; @@ -300,6 +306,8 @@ struct sys_net_sockaddr_in // sockaddr_in_p2p prefixed with sys_net_ struct sys_net_sockaddr_in_p2p { + ENABLE_BITWISE_SERIALIZATION; + u8 sin_len; u8 sin_family; be_t sin_port; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h index 9b2a1aea66..d4431c5352 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.h @@ -49,7 +49,13 @@ public: }; public: + SAVESTATE_INIT_POS(7); // Dependency on RPCN + lv2_socket(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); + lv2_socket(utils::serial&){} + lv2_socket(utils::serial&, lv2_socket_type type); + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial&, bool save_only_this_class = false); virtual ~lv2_socket() = default; std::unique_lock lock(); @@ -73,7 +79,7 @@ public: public: virtual std::tuple, sys_net_sockaddr> accept(bool is_lock = true) = 0; - virtual s32 bind(const sys_net_sockaddr& addr, s32 ps3_id) = 0; + virtual s32 bind(const sys_net_sockaddr& addr) = 0; virtual std::optional connect(const sys_net_sockaddr& addr) = 0; virtual s32 connect_followup() = 0; @@ -102,6 +108,8 @@ public: static const u32 id_count = 1000; protected: + lv2_socket(utils::serial&, bool); + shared_mutex mutex; u32 lv2_id = 0; @@ -130,4 +138,6 @@ protected: // Tracks connect for WSAPoll workaround bool connecting = false; #endif + + sys_net_sockaddr last_bound_addr{}; }; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp index 248b6a56bf..25a2e86e93 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.cpp @@ -13,6 +13,32 @@ lv2_socket_native::lv2_socket_native(lv2_socket_family family, lv2_socket_type t { } +lv2_socket_native::lv2_socket_native(utils::serial& ar, lv2_socket_type type) + : lv2_socket(ar, type) +{ +#ifdef _WIN32 + ar(so_reuseaddr, so_reuseport); +#else + std::array dummy{}; + ar(dummy); + + if (dummy != std::array{}) + { + sys_net.error("[Native] Savestate tried to load Win32 specific data, compatibility may be affected"); + } +#endif +} + +void lv2_socket_native::save(utils::serial& ar) +{ + static_cast(this)->save(ar, true); +#ifdef _WIN32 + ar(so_reuseaddr, so_reuseport); +#else + ar(std::array{}); +#endif +} + lv2_socket_native::~lv2_socket_native() { std::lock_guard lock(mutex); @@ -94,7 +120,7 @@ std::tuple, sys_net_sockaddr> lv2_socket_ return {false, {}, {}, {}}; } -s32 lv2_socket_native::bind(const sys_net_sockaddr& addr, [[maybe_unused]] s32 ps3_id) +s32 lv2_socket_native::bind(const sys_net_sockaddr& addr) { std::lock_guard lock(mutex); @@ -110,6 +136,7 @@ s32 lv2_socket_native::bind(const sys_net_sockaddr& addr, [[maybe_unused]] s32 p if (::bind(socket, reinterpret_cast(&native_addr), native_addr_len) == 0) { + last_bound_addr = addr; return CELL_OK; } return -get_last_error(false); diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h index 35199e199e..ecc5f59fbe 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_native.h @@ -31,11 +31,13 @@ class lv2_socket_native final : public lv2_socket { public: lv2_socket_native(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); + lv2_socket_native(utils::serial& ar, lv2_socket_type type); + void save(utils::serial& ar); ~lv2_socket_native(); s32 create_socket(); std::tuple, sys_net_sockaddr> accept(bool is_lock = true) override; - s32 bind(const sys_net_sockaddr& addr, s32 ps3_id) override; + s32 bind(const sys_net_sockaddr& addr) override; std::optional connect(const sys_net_sockaddr& addr) override; s32 connect_followup() override; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp index c7c513be4f..7d9d5e85ee 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.cpp @@ -11,6 +11,36 @@ lv2_socket_p2p::lv2_socket_p2p(lv2_socket_family family, lv2_socket_type type, l { } +lv2_socket_p2p::lv2_socket_p2p(utils::serial& ar, lv2_socket_type type) + : lv2_socket(ar, type) +{ + ar(port, vport); + + std::deque>> data_dequeue{ar}; + + for (; !data_dequeue.empty(); data_dequeue.pop_front()) + { + data.push(data_dequeue.front()); + } + + ar(data_dequeue); +} + +void lv2_socket_p2p::save(utils::serial& ar) +{ + static_cast(this)->save(ar, true); + ar(port, vport); + + std::deque>> data_dequeue; + + for (; !data.empty(); data.pop()) + { + data_dequeue.push_back(data.front()); + } + + ar(data_dequeue); +} + void lv2_socket_p2p::handle_new_data(sys_net_sockaddr_in_p2p p2p_addr, std::vector p2p_data) { std::lock_guard lock(mutex); @@ -69,7 +99,7 @@ s32 lv2_socket_p2p::listen([[maybe_unused]] s32 backlog) return {}; } -s32 lv2_socket_p2p::bind(const sys_net_sockaddr& addr, s32 ps3_id) +s32 lv2_socket_p2p::bind(const sys_net_sockaddr& addr) { const auto* psa_in_p2p = reinterpret_cast(&addr); u16 p2p_port = psa_in_p2p->sin_port; @@ -118,7 +148,7 @@ s32 lv2_socket_p2p::bind(const sys_net_sockaddr& addr, s32 ps3_id) } } - pport.bound_p2p_vports.insert(std::make_pair(p2p_vport, ps3_id)); + pport.bound_p2p_vports.insert(std::make_pair(p2p_vport, lv2_id)); } } @@ -127,6 +157,7 @@ s32 lv2_socket_p2p::bind(const sys_net_sockaddr& addr, s32 ps3_id) port = p2p_port; vport = p2p_vport; socket = real_socket; + last_bound_addr = addr; } return CELL_OK; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h index cfe04dda54..3b9c17e642 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2p.h @@ -6,9 +6,11 @@ class lv2_socket_p2p : public lv2_socket { public: lv2_socket_p2p(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); + lv2_socket_p2p(utils::serial& ar, lv2_socket_type type); + void save(utils::serial& ar); std::tuple, sys_net_sockaddr> accept(bool is_lock = true) override; - s32 bind(const sys_net_sockaddr& addr, s32 ps3_id) override; + s32 bind(const sys_net_sockaddr& addr) override; std::optional connect(const sys_net_sockaddr& addr) override; s32 connect_followup() override; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp index 99a74044d7..b7e314cebe 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.cpp @@ -238,6 +238,18 @@ lv2_socket_p2ps::lv2_socket_p2ps(socket_type socket, u16 port, u16 vport, u32 op status = p2ps_stream_status::stream_connected; } +lv2_socket_p2ps::lv2_socket_p2ps(utils::serial& ar, lv2_socket_type type) + : lv2_socket_p2p(ar, type) +{ + ar(status, max_backlog, backlog, op_port, op_vport, op_addr, data_beg_seq, received_data, cur_seq); +} + +void lv2_socket_p2ps::save(utils::serial& ar) +{ + static_cast(this)->save(ar); + ar(status, max_backlog, backlog, op_port, op_vport, op_addr, data_beg_seq, received_data, cur_seq); +} + bool lv2_socket_p2ps::handle_connected(p2ps_encapsulated_tcp* tcp_header, u8* data, ::sockaddr_storage* op_addr) { std::lock_guard lock(mutex); @@ -414,7 +426,7 @@ bool lv2_socket_p2ps::handle_listening(p2ps_encapsulated_tcp* tcp_header, [[mayb send_u2s_packet(std::move(packet), reinterpret_cast<::sockaddr_in*>(op_addr), send_hdr.seq, true); } - backlog.push(new_sock_id); + backlog.push_back(new_sock_id); if (events.test_and_reset(lv2_socket::poll_t::read)) { bs_t read_event = lv2_socket::poll_t::read; @@ -501,7 +513,7 @@ std::tuple, sys_net_sockaddr> lv2_socket_ } auto p2ps_client = backlog.front(); - backlog.pop(); + backlog.pop_front(); sys_net_sockaddr ps3_addr{}; auto* paddr = reinterpret_cast(&ps3_addr); @@ -519,7 +531,7 @@ std::tuple, sys_net_sockaddr> lv2_socket_ return {true, p2ps_client, {}, ps3_addr}; } -s32 lv2_socket_p2ps::bind(const sys_net_sockaddr& addr, s32 ps3_id) +s32 lv2_socket_p2ps::bind(const sys_net_sockaddr& addr) { const auto* psa_in_p2p = reinterpret_cast(&addr); @@ -563,7 +575,7 @@ s32 lv2_socket_p2ps::bind(const sys_net_sockaddr& addr, s32 ps3_id) { p2p_vport++; } - pport.bound_p2p_streams.emplace((static_cast(p2p_vport) << 32), ps3_id); + pport.bound_p2p_streams.emplace((static_cast(p2p_vport) << 32), lv2_id); } else { @@ -572,12 +584,13 @@ s32 lv2_socket_p2ps::bind(const sys_net_sockaddr& addr, s32 ps3_id) { return -SYS_NET_EADDRINUSE; } - pport.bound_p2p_streams.emplace(key, ps3_id); + pport.bound_p2p_streams.emplace(key, lv2_id); } port = p2p_port; vport = p2p_vport; socket = real_socket; + last_bound_addr = addr; } } diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h index cba0182ebb..740e369252 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_p2ps.h @@ -59,6 +59,8 @@ class lv2_socket_p2ps final : public lv2_socket_p2p public: lv2_socket_p2ps(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); lv2_socket_p2ps(socket_type socket, u16 port, u16 vport, u32 op_addr, u16 op_port, u16 op_vport, u64 cur_seq, u64 data_beg_seq); + lv2_socket_p2ps(utils::serial& ar, lv2_socket_type type); + void save(utils::serial& ar); p2ps_stream_status get_status() const; void set_status(p2ps_stream_status new_status); @@ -67,7 +69,7 @@ public: void send_u2s_packet(std::vector data, const ::sockaddr_in* dst, u32 seq, bool require_ack); std::tuple, sys_net_sockaddr> accept(bool is_lock = true) override; - s32 bind(const sys_net_sockaddr& addr, s32 ps3_id) override; + s32 bind(const sys_net_sockaddr& addr) override; std::optional connect(const sys_net_sockaddr& addr) override; @@ -90,7 +92,7 @@ protected: p2ps_stream_status status = p2ps_stream_status::stream_closed; usz max_backlog = 0; // set on listen - std::queue backlog; + std::deque backlog; u16 op_port = 0, op_vport = 0; u32 op_addr = 0; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp index 96b10b5ffa..2ee070eb19 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.cpp @@ -8,13 +8,23 @@ lv2_socket_raw::lv2_socket_raw(lv2_socket_family family, lv2_socket_type type, l { } +lv2_socket_raw::lv2_socket_raw(utils::serial& ar, lv2_socket_type type) + : lv2_socket(ar, type) +{ +} + +void lv2_socket_raw::save(utils::serial& ar) +{ + static_cast(this)->save(ar, true); +} + std::tuple, sys_net_sockaddr> lv2_socket_raw::accept([[maybe_unused]] bool is_lock) { sys_net.todo("lv2_socket_raw::accept"); return {}; } -s32 lv2_socket_raw::bind([[maybe_unused]] const sys_net_sockaddr& addr, [[maybe_unused]] s32 ps3_id) +s32 lv2_socket_raw::bind([[maybe_unused]] const sys_net_sockaddr& addr) { sys_net.todo("lv2_socket_raw::bind"); return {}; diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h index 63ca3ab830..6886f23e45 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket_raw.h @@ -6,9 +6,11 @@ class lv2_socket_raw final : public lv2_socket { public: lv2_socket_raw(lv2_socket_family family, lv2_socket_type type, lv2_ip_protocol protocol); + lv2_socket_raw(utils::serial& ar, lv2_socket_type type); + void save(utils::serial& ar); std::tuple, sys_net_sockaddr> accept(bool is_lock = true) override; - s32 bind(const sys_net_sockaddr& addr, s32 ps3_id) override; + s32 bind(const sys_net_sockaddr& addr) override; std::optional connect(const sys_net_sockaddr& addr) override; s32 connect_followup() override; diff --git a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp index 787620ade3..a0e5245955 100644 --- a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp @@ -12,7 +12,7 @@ #include "sys_overlay.h" #include "sys_fs.h" -extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 file_offset); +extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 file_offset, utils::serial* ar = nullptr); extern bool ppu_initialize(const ppu_module&, bool = false); extern void ppu_finalize(const ppu_module&); @@ -61,6 +61,38 @@ static error_code overlay_load_module(vm::ptr ovlmid, const std::string& vp return CELL_OK; } +fs::file make_file_view(fs::file&&, u64); + +std::shared_ptr lv2_overlay::load(utils::serial& ar) +{ + const std::string path = vfs::get(ar.operator std::string()); + const s64 offset = ar; + + std::shared_ptr ovlm; + + fs::file file{path.substr(0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))}; + + if (file) + { + u128 klic = g_fxo->get().last_key(); + file = make_file_view(std::move(file), offset); + ovlm = ppu_load_overlay(ppu_exec_object{ decrypt_self(std::move(file), reinterpret_cast(&klic)) }, path, 0, &ar).first; + ensure(ovlm); + } + else + { + ensure(g_cfg.savestate.state_inspection_mode.get()); + } + + return std::move(ovlm); +} + +void lv2_overlay::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_prx_overlay); + ar(vfs::retrieve(path), offset); +} + error_code sys_overlay_load_module(vm::ptr ovlmid, vm::cptr path, u64 flags, vm::ptr entry) { sys_overlay.warning("sys_overlay_load_module(ovlmid=*0x%x, path=%s, flags=0x%x, entry=*0x%x)", ovlmid, path, flags, entry); diff --git a/rpcs3/Emu/Cell/lv2/sys_overlay.h b/rpcs3/Emu/Cell/lv2/sys_overlay.h index c7f133c367..965a91809f 100644 --- a/rpcs3/Emu/Cell/lv2/sys_overlay.h +++ b/rpcs3/Emu/Cell/lv2/sys_overlay.h @@ -8,6 +8,11 @@ struct lv2_overlay final : lv2_obj, ppu_module static const u32 id_base = 0x25000000; u32 entry; + + lv2_overlay() = default; + lv2_overlay(utils::serial& ar){} + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); }; error_code sys_overlay_load_module(vm::ptr ovlmid, vm::cptr path, u64 flags, vm::ptr entry); diff --git a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp index 49023e473f..354dd207cc 100644 --- a/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_ppu_thread.cpp @@ -192,10 +192,12 @@ error_code sys_ppu_thread_join(ppu_thread& ppu, u32 thread_id, vm::ptr vptr if (thread->joiner != ppu_join_status::exited) { // Thread aborted, log it later - ppu.state += cpu_flag::exit; + ppu.state += cpu_flag::again; return {}; } + static_cast(ppu.test_stopped()); + // Get the exit status from the register const u64 vret = thread->gpr[3]; diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp index 5d000217c3..e1c1795e34 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_prx.cpp @@ -15,7 +15,7 @@ #include "sys_memory.h" #include -extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, const std::string&, s64); +extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, const std::string&, s64, utils::serial* = nullptr); extern void ppu_unload_prx(const lv2_prx& prx); extern bool ppu_initialize(const ppu_module&, bool = false); extern void ppu_finalize(const ppu_module&); @@ -291,6 +291,76 @@ static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr lv2_prx::load(utils::serial& ar) +{ + const std::string path = vfs::get(ar.operator std::string()); + const s64 offset = ar; + const u32 state = ar; + + usz seg_count = 0; + ar.deserialize_vle(seg_count); + + std::shared_ptr prx; + + auto hle_load = [&]() + { + prx = std::make_shared(); + prx->path = path; + prx->name = path.substr(path.find_last_of(fs::delim) + 1); + }; + + if (seg_count) + { + fs::file file{path.substr(0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))}; + + if (file) + { + u128 klic = g_fxo->get().last_key(); + file = make_file_view(std::move(file), offset); + prx = ppu_load_prx(ppu_prx_object{ decrypt_self(std::move(file), reinterpret_cast(&klic)) }, path, 0, &ar); + ensure(prx); + } + else + { + ensure(g_cfg.savestate.state_inspection_mode.get()); + + hle_load(); + + // Partially recover information + for (usz i = 0; i < seg_count; i++) + { + auto& seg = prx->segs.emplace_back(); + seg.addr = ar; + seg.size = 1; // TODO + } + } + } + else + { + hle_load(); + } + + prx->state = state; + return std::move(prx); +} + +void lv2_prx::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_prx_overlay); + + ar(vfs::retrieve(path), offset, state); + + // Save segments count + ar.serialize_vle(segs.size()); + + for (const ppu_segment& seg : segs) + { + if (seg.type == 0x1u && seg.size) ar(seg.addr); + } +} + error_code sys_prx_get_ppu_guid(ppu_thread& ppu) { ppu.state += cpu_flag::wait; diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.h b/rpcs3/Emu/Cell/lv2/sys_prx.h index cedfda76ed..2df2328f62 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.h +++ b/rpcs3/Emu/Cell/lv2/sys_prx.h @@ -189,6 +189,11 @@ struct lv2_prx final : lv2_obj, ppu_module char module_info_name[28]; u8 module_info_version[2]; be_t module_info_attributes; + + lv2_prx() noexcept = default; + lv2_prx(utils::serial&) {} + static std::shared_ptr load(utils::serial&); + void save(utils::serial& ar); }; enum : u64 diff --git a/rpcs3/Emu/Cell/lv2/sys_rsx.cpp b/rpcs3/Emu/Cell/lv2/sys_rsx.cpp index d5bab1f0b5..43132c5e56 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rsx.cpp @@ -76,12 +76,37 @@ void rsx::thread::send_event(u64 data1, u64 event_flags, u64 data3) const error = sys_event_port_send(rsx_event_port, data1, event_flags, data3); } - if (error && error + 0u != CELL_ENOTCONN) + if (!Emu.IsPaused() && error && error + 0u != CELL_ENOTCONN) { fmt::throw_exception("rsx::thread::send_event() Failed to send event! (error=%x)", +error); } } +// Returns true on success of receiving the event +void signal_gcm_intr_thread_offline(lv2_event_queue& q) +{ + const auto render = rsx::get_current_renderer(); + const auto cpu = cpu_thread::get_current(); + + static shared_mutex s_dummy; + + std::scoped_lock lock_rsx(render ? render->sys_rsx_mtx : s_dummy, q.mutex); + + if (std::find(q.sq.begin(), q.sq.end(), cpu) == q.sq.end()) + { + return; + } + + cpu->state += cpu_flag::again; + + if (!vm::check_addr(render->driver_info) || vm::_ref(render->driver_info).handler_queue != q.id) + { + return; + } + + render->gcm_intr_thread_offline = true; +} + error_code sys_rsx_device_open(cpu_thread& cpu) { cpu.state += cpu_flag::wait; @@ -487,7 +512,16 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 } } - render->request_emu_flip(flip_idx); + if (!render->request_emu_flip(flip_idx)) + { + if (auto cpu = get_current_cpu_thread()) + { + cpu->state += cpu_flag::exit; + cpu->state += cpu_flag::again; + } + + return {}; + } } break; @@ -745,6 +779,19 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 break; case 0xFEC: // hack: flip event notification + { + reader_lock lock(render->sys_rsx_mtx); + + if (render->gcm_intr_thread_offline) + { + if (auto cpu = get_current_cpu_thread()) + { + cpu->state += cpu_flag::exit; + cpu->state += cpu_flag::again; + } + + break; + } // we only ever use head 1 for now driverInfo.head[1].flipFlags |= 0x80000000; @@ -757,6 +804,7 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 render->send_event(0, SYS_RSX_EVENT_FLIP_BASE << 1, 0); break; + } case 0xFED: // hack: vblank command { @@ -769,6 +817,13 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 // NOTE: There currently seem to only be 2 active heads on PS3 ensure(a3 < 2); + reader_lock lock(render->sys_rsx_mtx); + + if (render->gcm_intr_thread_offline) + { + break; + } + // todo: this is wrong and should be 'second' vblank handler and freq, but since currently everything is reported as being 59.94, this should be fine driverInfo.head[a3].lastSecondVTime.atomic_op([&](be_t& time) { @@ -796,12 +851,27 @@ error_code sys_rsx_context_attribute(u32 context_id, u32 package_id, u64 a3, u64 } case 0xFEF: // hack: user command + { + reader_lock lock(render->sys_rsx_mtx); + + if (render->gcm_intr_thread_offline) + { + if (auto cpu = get_current_cpu_thread()) + { + cpu->state += cpu_flag::exit; + cpu->state += cpu_flag::again; + } + + break; + } + // 'custom' invalid package id for now // as i think we need custom lv1 interrupts to handle this accurately // this also should probly be set by rsxthread driverInfo.userCmdParam = static_cast(a4); render->send_event(0, SYS_RSX_EVENT_USER_CMD, 0); break; + } default: return CELL_EINVAL; diff --git a/rpcs3/Emu/Cell/lv2/sys_rsx.h b/rpcs3/Emu/Cell/lv2/sys_rsx.h index 2e574e2272..34d9f8f936 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rsx.h +++ b/rpcs3/Emu/Cell/lv2/sys_rsx.h @@ -120,6 +120,8 @@ struct RsxDisplayInfo be_t width{0}; be_t height{0}; + ENABLE_BITWISE_SERIALIZATION; + bool valid() const { return height != 0u && width != 0u; diff --git a/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp b/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp index 9bf8c1d61e..c420374e64 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_rwlock.cpp @@ -9,6 +9,26 @@ LOG_CHANNEL(sys_rwlock); +lv2_rwlock::lv2_rwlock(utils::serial& ar) + : protocol(ar) + , key(ar) + , name(ar) +{ + ar(owner); +} + +std::shared_ptr lv2_rwlock::load(utils::serial& ar) +{ + auto rwlock = std::make_shared(ar); + return lv2_obj::load(rwlock->key, rwlock); +} + +void lv2_rwlock::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_sync); + ar(protocol, key, name, owner); +} + error_code sys_rwlock_create(ppu_thread& ppu, vm::ptr rw_lock_id, vm::ptr attr) { ppu.state += cpu_flag::wait; @@ -130,9 +150,22 @@ error_code sys_rwlock_rlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout) while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { + if (state & cpu_flag::signal) + { + break; + } + if (is_stopped(state)) { - return {}; + std::lock_guard lock(rwlock->mutex); + + if (std::find(rwlock->rq.begin(), rwlock->rq.end(), &ppu) == rwlock->rq.end()) + { + break; + } + + ppu.state += cpu_flag::again; + break; } if (state & cpu_flag::signal) @@ -147,7 +180,7 @@ error_code sys_rwlock_rlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout) // Wait for rescheduling if (ppu.check_state()) { - return {}; + continue; } std::lock_guard lock(rwlock->mutex); @@ -257,6 +290,12 @@ error_code sys_rwlock_runlock(ppu_thread& ppu, u32 rw_lock_id) { if (const auto cpu = rwlock->schedule(rwlock->wq, rwlock->protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return {}; + } + rwlock->owner = cpu->id << 1 | !rwlock->wq.empty() | !rwlock->rq.empty(); rwlock->awake(cpu); @@ -337,16 +376,24 @@ error_code sys_rwlock_wlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout) while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(state)) - { - return {}; - } - if (state & cpu_flag::signal) { break; } + if (is_stopped(state)) + { + std::lock_guard lock(rwlock->mutex); + + if (std::find(rwlock->wq.begin(), rwlock->wq.end(), &ppu) == rwlock->wq.end()) + { + break; + } + + ppu.state += cpu_flag::again; + break; + } + if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) @@ -354,7 +401,7 @@ error_code sys_rwlock_wlock(ppu_thread& ppu, u32 rw_lock_id, u64 timeout) // Wait for rescheduling if (ppu.check_state()) { - return {}; + continue; } std::lock_guard lock(rwlock->mutex); @@ -461,6 +508,12 @@ error_code sys_rwlock_wunlock(ppu_thread& ppu, u32 rw_lock_id) if (auto cpu = rwlock->schedule(rwlock->wq, rwlock->protocol)) { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return {}; + } + rwlock->owner = cpu->id << 1 | !rwlock->wq.empty() | !rwlock->rq.empty(); rwlock->awake(cpu); diff --git a/rpcs3/Emu/Cell/lv2/sys_rwlock.h b/rpcs3/Emu/Cell/lv2/sys_rwlock.h index 97f762c01c..63ef36d79a 100644 --- a/rpcs3/Emu/Cell/lv2/sys_rwlock.h +++ b/rpcs3/Emu/Cell/lv2/sys_rwlock.h @@ -38,6 +38,10 @@ struct lv2_rwlock final : lv2_obj , name(name) { } + + lv2_rwlock(utils::serial& ar); + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); }; // Aux diff --git a/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp b/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp index 4a3e291547..2a7e44d11b 100644 --- a/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_semaphore.cpp @@ -9,6 +9,27 @@ LOG_CHANNEL(sys_semaphore); +lv2_sema::lv2_sema(utils::serial& ar) + : protocol(ar) + , key(ar) + , name(ar) + , max(ar) +{ + ar(val); +} + +std::shared_ptr lv2_sema::load(utils::serial& ar) +{ + auto sema = std::make_shared(ar); + return lv2_obj::load(sema->key, sema); +} + +void lv2_sema::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_sync); + ar(protocol, key, name, max, std::max(+val, 0)); +} + error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr sem_id, vm::ptr attr, s32 initial_val, s32 max_val) { ppu.state += cpu_flag::wait; @@ -46,10 +67,7 @@ error_code sys_semaphore_create(ppu_thread& ppu, vm::ptr sem_id, vm::ptr(ppu.test_stopped()); *sem_id = idm::last_id(); return CELL_OK; @@ -129,16 +147,24 @@ error_code sys_semaphore_wait(ppu_thread& ppu, u32 sem_id, u64 timeout) while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(state)) - { - return {}; - } - if (state & cpu_flag::signal) { break; } + if (is_stopped(state)) + { + std::lock_guard lock(sem->mutex); + + if (std::find(sem->sq.begin(), sem->sq.end(), &ppu) == sem->sq.end()) + { + break; + } + + ppu.state += cpu_flag::again; + return {}; + } + if (timeout) { if (lv2_obj::wait_timeout(timeout, &ppu)) @@ -146,7 +172,7 @@ error_code sys_semaphore_wait(ppu_thread& ppu, u32 sem_id, u64 timeout) // Wait for rescheduling if (ppu.check_state()) { - return {}; + continue; } std::lock_guard lock(sem->mutex); @@ -240,6 +266,15 @@ error_code sys_semaphore_post(ppu_thread& ppu, u32 sem_id, s32 count) { std::lock_guard lock(sem->mutex); + for (auto cpu : sem->sq) + { + if (static_cast(cpu)->state & cpu_flag::again) + { + ppu.state += cpu_flag::again; + return {}; + } + } + const auto [val, ok] = sem->val.fetch_op([&](s32& val) { if (count + 0u <= sem->max + 0u - val) @@ -294,10 +329,7 @@ error_code sys_semaphore_get_value(ppu_thread& ppu, u32 sem_id, vm::ptr cou return CELL_EFAULT; } - if (ppu.test_stopped()) - { - return {}; - } + static_cast(ppu.test_stopped()); *count = sema.ret; return CELL_OK; diff --git a/rpcs3/Emu/Cell/lv2/sys_semaphore.h b/rpcs3/Emu/Cell/lv2/sys_semaphore.h index 78dc0643a5..9c2b39b877 100644 --- a/rpcs3/Emu/Cell/lv2/sys_semaphore.h +++ b/rpcs3/Emu/Cell/lv2/sys_semaphore.h @@ -40,6 +40,10 @@ struct lv2_sema final : lv2_obj , val(value) { } + + lv2_sema(utils::serial& ar); + static std::shared_ptr load(utils::serial& ar); + void save(utils::serial& ar); }; // Aux diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.cpp b/rpcs3/Emu/Cell/lv2/sys_spu.cpp index 990d1c24b3..01e95ffa4d 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_spu.cpp @@ -194,6 +194,131 @@ void sys_spu_image::deploy(u8* loc, std::span segs) spu_log.notice("Loaded SPU image: %s (<- %u)%s", hash, applied.size(), dump); } +lv2_spu_group::lv2_spu_group(utils::serial& ar) noexcept + : name(ar.operator std::string()) + , id(idm::last_id()) + , max_num(ar) + , mem_size(ar) + , type(ar) // SPU Thread Group Type + , ct(lv2_memory_container::search(ar)) + , has_scheduler_context(ar.operator u8()) + , max_run(ar) + , init(ar) + , prio(ar) + , run_state(ar.operator spu_group_status()) + , exit_status(ar) +{ + for (auto& thread : threads) + { + if (ar.operator u8()) + { + ar(id_manager::g_id); + thread = std::make_shared>(ar, this); + idm::import_existing>(thread, idm::last_id()); + running += !thread->stop_flag_removal_protection; + } + } + + ar(threads_map); + ar(imgs); + ar(args); + + for (auto ep : {&ep_run, &ep_exception, &ep_sysmodule}) + { + *ep = idm::get_unlocked(ar.operator u32()); + } + + switch (run_state) + { + // Commented stuff are handled by different means currently + //case SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED: + //case SPU_THREAD_GROUP_STATUS_INITIALIZED: + //case SPU_THREAD_GROUP_STATUS_READY: + //case SPU_THREAD_GROUP_STATUS_WAITING: + case SPU_THREAD_GROUP_STATUS_SUSPENDED: + { + // Suspend all SPU threads + for (const auto& thread : threads) + { + if (thread) + { + thread->state += cpu_flag::suspend; + } + } + + break; + } + //case SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED: + //case SPU_THREAD_GROUP_STATUS_RUNNING: + //case SPU_THREAD_GROUP_STATUS_STOPPED: + //case SPU_THREAD_GROUP_STATUS_UNKNOWN: + default: + { + break; + } + } +} + +void lv2_spu_group::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(spu); + + spu_group_status _run_state = run_state; + + switch (_run_state) + { + // Commented stuff are handled by different means currently + //case SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED: + //case SPU_THREAD_GROUP_STATUS_INITIALIZED: + //case SPU_THREAD_GROUP_STATUS_READY: + + // Waiting SPU should recover this + case SPU_THREAD_GROUP_STATUS_WAITING: _run_state = SPU_THREAD_GROUP_STATUS_RUNNING; break; + case SPU_THREAD_GROUP_STATUS_WAITING_AND_SUSPENDED: _run_state = SPU_THREAD_GROUP_STATUS_SUSPENDED; break; + //case SPU_THREAD_GROUP_STATUS_RUNNING: + //case SPU_THREAD_GROUP_STATUS_STOPPED: + //case SPU_THREAD_GROUP_STATUS_UNKNOWN: + default: + { + break; + } + } + + ar(name, max_num, mem_size, type, ct->id, has_scheduler_context, max_run, init, prio, _run_state, exit_status); + + for (const auto& thread : threads) + { + ar(u8{thread.operator bool()}); + + if (thread) + { + ar(thread->id); + thread->save(ar); + } + } + + ar(threads_map); + ar(imgs); + ar(args); + + for (auto ep : {&ep_run, &ep_exception, &ep_sysmodule}) + { + ar(lv2_obj::check(*ep) ? (*ep)->id : 0); + } +} + +lv2_spu_image::lv2_spu_image(utils::serial& ar) + : e_entry(ar) + , segs(ar.operator decltype(segs)()) + , nsegs(ar) +{ +} + +void lv2_spu_image::save(utils::serial& ar) +{ + ar(e_entry, segs, nsegs); +} + // Get spu thread ptr, returns group ptr as well for refcounting std::pair*, std::shared_ptr> lv2_spu_group::get_thread(u32 id) { @@ -1270,11 +1395,24 @@ error_code sys_spu_thread_group_join(ppu_thread& ppu, u32 id, vm::ptr cause { const auto state = ppu.state.fetch_sub(cpu_flag::signal); - if (is_stopped(state) || state & cpu_flag::signal) + if (state & cpu_flag::signal) { break; } + if (is_stopped(state)) + { + std::lock_guard lock(group->mutex); + + if (!group->waiter) + { + break; + } + + ppu.state += cpu_flag::again; + break; + } + thread_ctrl::wait_on(ppu.state, state); } } diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.h b/rpcs3/Emu/Cell/lv2/sys_spu.h index ceae6a9b22..e5f12108db 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.h +++ b/rpcs3/Emu/Cell/lv2/sys_spu.h @@ -114,6 +114,8 @@ struct sys_spu_thread_argument struct sys_spu_segment { + ENABLE_BITWISE_SERIALIZATION; + be_t type; // copy, fill, info be_t ls; // local storage address be_t size; @@ -248,6 +250,9 @@ struct lv2_spu_image : lv2_obj , nsegs(nsegs) { } + + lv2_spu_image(utils::serial& ar); + void save(utils::serial& ar); }; struct sys_spu_thread_group_syscall_253_info @@ -283,8 +288,8 @@ struct lv2_spu_group atomic_t run_state; // SPU Thread Group State atomic_t exit_status; // SPU Thread Group Exit Status atomic_t join_state; // flags used to detect exit cause and signal - atomic_t running; // Number of running threads - atomic_t stop_count; + atomic_t running = 0; // Number of running threads + atomic_t stop_count = 0; class ppu_thread* waiter = nullptr; bool set_terminate = false; @@ -297,7 +302,7 @@ struct lv2_spu_group std::shared_ptr ep_exception; // TODO: SYS_SPU_THREAD_GROUP_EVENT_EXCEPTION std::shared_ptr ep_sysmodule; // TODO: SYS_SPU_THREAD_GROUP_EVENT_SYSTEM_MODULE - lv2_spu_group(std::string name, u32 num, s32 prio, s32 type, lv2_memory_container* ct, bool uses_scheduler, u32 mem_size) + lv2_spu_group(std::string name, u32 num, s32 prio, s32 type, lv2_memory_container* ct, bool uses_scheduler, u32 mem_size) noexcept : name(std::move(name)) , id(idm::last_id()) , max_num(num) @@ -311,13 +316,16 @@ struct lv2_spu_group , run_state(SPU_THREAD_GROUP_STATUS_NOT_INITIALIZED) , exit_status(0) , join_state(0) - , running(0) - , stop_count(0) // TODO: args() { threads_map.fill(-1); } + SAVESTATE_INIT_POS(8); // Dependency on SPUs + + lv2_spu_group(utils::serial& ar) noexcept; + void save(utils::serial& ar); + CellError send_run_event(u64 data1, u64 data2, u64 data3) const { return ep_run ? ep_run->send(SYS_SPU_THREAD_GROUP_EVENT_RUN_KEY, data1, data2, data3) : CELL_ENOTCONN; diff --git a/rpcs3/Emu/Cell/lv2/sys_storage.h b/rpcs3/Emu/Cell/lv2/sys_storage.h index 5177247933..9916f2e58a 100644 --- a/rpcs3/Emu/Cell/lv2/sys_storage.h +++ b/rpcs3/Emu/Cell/lv2/sys_storage.h @@ -26,6 +26,7 @@ struct lv2_storage static const u32 id_base = 0x45000000; static const u32 id_step = 1; static const u32 id_count = 2048; + SAVESTATE_INIT_POS(45); const u64 device_id; const fs::file file; diff --git a/rpcs3/Emu/Cell/lv2/sys_sync.h b/rpcs3/Emu/Cell/lv2/sys_sync.h index a26f4ad71c..b62072fe33 100644 --- a/rpcs3/Emu/Cell/lv2/sys_sync.h +++ b/rpcs3/Emu/Cell/lv2/sys_sync.h @@ -84,6 +84,7 @@ private: } public: + SAVESTATE_INIT_POS(4); // Dependency on PPUs // Existence validation (workaround for shared-ptr ref-counting) atomic_t exists = 0; @@ -133,7 +134,10 @@ public: if (protocol == SYS_SYNC_FIFO) { const auto res = queue.front(); - queue.pop_front(); + + if (res->state.none_of(cpu_flag::again)) + queue.pop_front(); + return res; } @@ -152,7 +156,10 @@ public: } const auto res = *it; - queue.erase(it); + + if (res->state.none_of(cpu_flag::again)) + queue.erase(it); + return res; } @@ -192,6 +199,10 @@ public: g_to_awake.emplace_back(thread); } + // Serialization related + static void set_future_sleep(ppu_thread* ppu); + static bool is_scheduler_ready(); + static void cleanup(); template @@ -314,6 +325,24 @@ public: } } + template + static std::shared_ptr load(u64 ipc_key, std::shared_ptr make, u64 pshared = -1) + { + if (pshared == umax ? ipc_key != 0 : pshared != 0) + { + g_fxo->need>(); + + make = g_fxo->get>().add(ipc_key, [&]() + { + return make; + }, true).second; + } + + // Ensure no error + ensure(!make->on_id_create()); + return make; + } + template static bool wait_timeout(u64 usec, cpu_thread* const cpu = {}) { @@ -420,5 +449,8 @@ private: // Scheduler queue for timeouts (wait until -> thread) static std::deque> g_waiting; + // Threads which must call lv2_obj::sleep before the scheduler starts + static std::deque g_to_sleep; + static void schedule_all(); }; diff --git a/rpcs3/Emu/Cell/lv2/sys_time.cpp b/rpcs3/Emu/Cell/lv2/sys_time.cpp index 6b72a9dbee..71af7982c7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_time.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_time.cpp @@ -166,11 +166,24 @@ u64 get_timebased_time() } // Add an offset to get_timebased_time to avoid leaking PC's uptime into the game -void initalize_timebased_time() +// As if PS3 starts at value 0 (base time) when the game boots +// If none-zero arg is specified it will become the base time (for savestates) +void initialize_timebased_time(u64 timebased_init, bool reset) { timebase_offset = 0; - timebase_offset = get_timebased_time(); - systemtime_offset = timebase_offset / (g_timebase_freq / 1000000); + + if (reset) + { + // We simply want to zero-out these values + systemtime_offset = 0; + return; + } + + const u64 current = get_timebased_time(); + timebased_init = get_timebased_time() - timebased_init; + + timebase_offset = timebased_init; + systemtime_offset = timebased_init / (g_timebase_freq / 1000000); } // Returns some relative time in microseconds, don't change this fact @@ -201,7 +214,6 @@ u64 get_system_time() u64 get_guest_system_time(u64 time) { const u64 result = (time != umax ? time : get_system_time()) * g_cfg.core.clocks_scale / 100; - ensure(result >= systemtime_offset); return result - systemtime_offset; } diff --git a/rpcs3/Emu/Cell/lv2/sys_timer.cpp b/rpcs3/Emu/Cell/lv2/sys_timer.cpp index 20a1496998..dadda6c5e0 100644 --- a/rpcs3/Emu/Cell/lv2/sys_timer.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_timer.cpp @@ -6,6 +6,7 @@ #include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUThread.h" #include "Emu/Cell/timers.hpp" +#include "Emu/System.h" #include "sys_event.h" #include "sys_process.h" @@ -23,6 +24,24 @@ struct lv2_timer_thread static constexpr auto thread_name = "Timer Thread"sv; }; +lv2_timer::lv2_timer(utils::serial& ar) + : lv2_obj{1} + , state(ar) + , port(lv2_event_queue::load_ptr(ar, port)) + , source(ar) + , data1(ar) + , data2(ar) + , expire(ar) + , period(ar) +{ +} + +void lv2_timer::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_sync); + ar(state), lv2_event_queue::save_ptr(ar, port.get()), ar(source, data1, data2, expire, period); +} + u64 lv2_timer::check() { while (thread_ctrl::state() != thread_state::aborting) @@ -34,6 +53,7 @@ u64 lv2_timer::check() const u64 _now = get_guest_system_time(); u64 next = expire; + // If aborting, perform the last accurate check for event if (_now >= next) { std::lock_guard lock(mutex); @@ -73,7 +93,19 @@ u64 lv2_timer::check() void lv2_timer_thread::operator()() { - u64 sleep_time = umax; + { + decltype(timers) vec; + + idm::select([&vec](u32 id, lv2_timer&) + { + vec.emplace_back(idm::get_unlocked(id)); + }); + + std::lock_guard lock(mutex); + timers = std::move(vec); + } + + u64 sleep_time = 0; while (thread_ctrl::state() != thread_state::aborting) { @@ -87,6 +119,12 @@ void lv2_timer_thread::operator()() sleep_time = umax; + if (Emu.IsPaused()) + { + sleep_time = 10000; + continue; + } + reader_lock lock(mutex); for (const auto& timer : timers) @@ -115,6 +153,7 @@ error_code sys_timer_create(ppu_thread& ppu, vm::ptr timer_id) auto& thread = g_fxo->get>(); { std::lock_guard lock(thread.mutex); + lv2_obj::unqueue(thread.timers, ptr); thread.timers.emplace_back(std::move(ptr)); } @@ -344,7 +383,7 @@ error_code sys_timer_sleep(ppu_thread& ppu, u32 sleep_time) { ppu.state += cpu_flag::wait; - sys_timer.trace("sys_timer_sleep(sleep_time=%d) -> sys_timer_usleep()", sleep_time); + sys_timer.warning("sys_timer_sleep(sleep_time=%d)", sleep_time); return sys_timer_usleep(ppu, sleep_time * u64{1000000}); } @@ -359,7 +398,10 @@ error_code sys_timer_usleep(ppu_thread& ppu, u64 sleep_time) { lv2_obj::sleep(ppu, sleep_time); - lv2_obj::wait_timeout(sleep_time); + if (!lv2_obj::wait_timeout(sleep_time)) + { + ppu.state += cpu_flag::again; + } } else { diff --git a/rpcs3/Emu/Cell/lv2/sys_timer.h b/rpcs3/Emu/Cell/lv2/sys_timer.h index e8a85b5dd3..3b976ed519 100644 --- a/rpcs3/Emu/Cell/lv2/sys_timer.h +++ b/rpcs3/Emu/Cell/lv2/sys_timer.h @@ -60,6 +60,9 @@ struct lv2_timer : lv2_obj info.period = 0; } } + + lv2_timer(utils::serial& ar); + void save(utils::serial& ar); }; class ppu_thread; diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index 630a468e7a..c302bfd147 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -63,6 +63,18 @@ public: usb_handler_thread(); ~usb_handler_thread(); + SAVESTATE_INIT_POS(14); + + usb_handler_thread(utils::serial& ar) : usb_handler_thread() + { + is_init = !!ar.operator u8(); + } + + void save(utils::serial& ar) + { + ar(u8{is_init.load()}); + } + // Thread loop void operator()(); @@ -850,18 +862,26 @@ error_code sys_usbd_receive_event(ppu_thread& ppu, u32 handle, vm::ptr arg1 while (auto state = ppu.state.fetch_sub(cpu_flag::signal)) { - if (is_stopped(state)) - { - sys_usbd.trace("sys_usbd_receive_event: aborting"); - return {}; - } - if (state & cpu_flag::signal) { sys_usbd.trace("Received event(queued): arg1=0x%x arg2=0x%x arg3=0x%x", ppu.gpr[4], ppu.gpr[5], ppu.gpr[6]); break; } + if (is_stopped(state)) + { + std::lock_guard lock(usbh.mutex); + + if (std::find(usbh.sq.begin(), usbh.sq.end(), &ppu) == usbh.sq.end()) + { + break; + } + + ppu.state += cpu_flag::again; + sys_usbd.trace("sys_usbd_receive_event: aborting"); + return {}; + } + thread_ctrl::wait_on(ppu.state, state); } diff --git a/rpcs3/Emu/Cell/lv2/sys_vm.cpp b/rpcs3/Emu/Cell/lv2/sys_vm.cpp index 9d69472a2f..46c2b00b81 100644 --- a/rpcs3/Emu/Cell/lv2/sys_vm.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_vm.cpp @@ -17,6 +17,12 @@ sys_vm_t::sys_vm_t(u32 _addr, u32 vsize, lv2_memory_container* ct, u32 psize) g_ids[addr >> 28].release(idm::last_id()); } +void sys_vm_t::save(utils::serial& ar) +{ + USING_SERIALIZATION_VERSION(lv2_vm); + ar(ct->id, addr, size, psize); +} + sys_vm_t::~sys_vm_t() { // Free ID @@ -30,6 +36,17 @@ struct sys_vm_global_t atomic_t total_vsize = 0; }; +sys_vm_t::sys_vm_t(utils::serial& ar) + : ct(lv2_memory_container::search(ar)) + , addr(ar) + , size(ar) + , psize(ar) +{ + g_ids[addr >> 28].release(idm::last_id()); + g_fxo->need(); + g_fxo->get().total_vsize += size; +} + error_code sys_vm_memory_map(ppu_thread& ppu, u32 vsize, u32 psize, u32 cid, u64 flag, u64 policy, vm::ptr addr) { ppu.state += cpu_flag::wait; diff --git a/rpcs3/Emu/Cell/lv2/sys_vm.h b/rpcs3/Emu/Cell/lv2/sys_vm.h index c058baeb5d..f92c44564c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_vm.h +++ b/rpcs3/Emu/Cell/lv2/sys_vm.h @@ -41,6 +41,11 @@ struct sys_vm_t sys_vm_t(u32 addr, u32 vsize, lv2_memory_container* ct, u32 psize); ~sys_vm_t(); + SAVESTATE_INIT_POS(10); + + sys_vm_t(utils::serial& ar); + void save(utils::serial& ar); + static std::array, id_count> g_ids; static u32 find_id(u32 addr) diff --git a/rpcs3/Emu/Cell/timers.hpp b/rpcs3/Emu/Cell/timers.hpp index 7656f9f953..eb0ab767d7 100644 --- a/rpcs3/Emu/Cell/timers.hpp +++ b/rpcs3/Emu/Cell/timers.hpp @@ -4,6 +4,5 @@ u64 convert_to_timebased_time(u64 time); u64 get_timebased_time(); -void initalize_timebased_time(); u64 get_system_time(); u64 get_guest_system_time(u64 time = umax); diff --git a/rpcs3/Emu/IdManager.cpp b/rpcs3/Emu/IdManager.cpp index 54862d7716..9c60543fe5 100644 --- a/rpcs3/Emu/IdManager.cpp +++ b/rpcs3/Emu/IdManager.cpp @@ -4,15 +4,40 @@ shared_mutex id_manager::g_mutex; -thread_local DECLARE(idm::g_id); - -idm::map_data* idm::allocate_id(std::vector& vec, u32 type_id, u32 base, u32 step, u32 count, std::pair invl_range) +namespace id_manager { + thread_local u32 g_id = 0; +} + +std::vector>& id_manager::get_typeinfo_map() +{ + // Magic static + static std::vector> s_map; + return s_map; +} + +idm::map_data* idm::allocate_id(std::vector& vec, u32 type_id, u32 dst_id, u32 base, u32 step, u32 count, std::pair invl_range) +{ + if (const u32 index = id_manager::get_index(dst_id, base, step, count, invl_range); index < count) + { + // Fixed position construction + ensure(index < vec.size()); + + if (vec[index].second) + { + return nullptr; + } + + id_manager::g_id = dst_id; + vec[index] = {id_manager::id_key(dst_id, type_id), nullptr}; + return &vec[index]; + } + if (vec.size() < count) { // Try to emplace back const u32 _next = base + step * ::size32(vec); - g_id = _next; + id_manager::g_id = _next; vec.emplace_back(id_manager::id_key(_next, type_id), nullptr); return &vec.back(); } @@ -27,7 +52,7 @@ idm::map_data* idm::allocate_id(std::vector& vec, u32 type_id, u32 ba { // Incremenet ID invalidation counter const u32 id = next | ((ptr->first + (1u << invl_range.first)) & (invl_range.second ? (((1u << invl_range.second) - 1) << invl_range.first) : 0)); - g_id = id; + id_manager::g_id = id; ptr->first = id_manager::id_key(id, type_id); return ptr; } diff --git a/rpcs3/Emu/IdManager.h b/rpcs3/Emu/IdManager.h index 4216fc9622..d2fb0c3198 100644 --- a/rpcs3/Emu/IdManager.h +++ b/rpcs3/Emu/IdManager.h @@ -5,7 +5,10 @@ #include #include +#include +#include +#include "util/serialization.hpp" #include "util/fixed_typemap.hpp" extern stx::manual_typemap g_fixed_typemap; @@ -35,6 +38,9 @@ namespace id_manager template concept IdmCompatible = requires () { T::id_base, T::id_step, T::id_count; }; + // Last allocated ID for constructors + extern thread_local u32 g_id; + // ID traits template struct id_traits @@ -57,53 +63,155 @@ namespace id_manager static_assert(!invl_range.second || (u64{invl_range.second} + invl_range.first <= 32 /*....*/ )); }; - class typeinfo + static constexpr u32 get_index(u32 id, u32 base, u32 step, u32 count, std::pair invl_range) { - // Global variable for each registered type - template - struct registered - { - static const u32 index; - }; + u32 mask_out = ((1u << invl_range.second) - 1) << invl_range.first; - // Increment type counter - static u32 add_type(u32 i) - { - static atomic_t g_next{0}; + // Note: if id is lower than base, diff / step will be higher than count + u32 diff = (id & ~mask_out) - base; - return g_next.fetch_add(i); + if (diff % step) + { + // id is invalid, return invalid index + return count; } + // Get actual index + return diff / step; + } + + // ID traits + template + struct id_traits_load_func + { + static constexpr std::shared_ptr(*load)(utils::serial&) = [](utils::serial& ar) -> std::shared_ptr { return std::make_shared(stx::exact_t(ar)); }; + }; + + template + struct id_traits_load_func> + { + static constexpr std::shared_ptr(*load)(utils::serial&) = &T::load; + }; + + template + struct id_traits_savable_func + { + static constexpr bool(*savable)(void*) = [](void*) -> bool { return true; }; + }; + + template + struct id_traits_savable_func> + { + static constexpr bool(*savable)(void* ptr) = [](void* ptr) -> bool { return static_cast(ptr)->savable(); }; + }; + + struct dummy_construct + { + dummy_construct() {} + dummy_construct(utils::serial&){} + void save(utils::serial&) {} + + static constexpr u32 id_base = 1, id_step = 1, id_count = 1; + static constexpr double savestate_init_pos = 0; + }; + + struct typeinfo; + + // Use a vector instead of map to reduce header dependencies in this commonly used header + std::vector>& get_typeinfo_map(); + + struct typeinfo + { public: + std::shared_ptr(*load)(utils::serial&); + void(*save)(utils::serial&, void*); + bool(*savable)(void* ptr); + + u32 base; + u32 step; + u32 count; + std::pair invl_range; + // Get type index template static inline u32 get_index() { - return registered::index; + return stx::typeindex(); } - // Get type count - static inline u32 get_count() + // Unique type ID within the same container: we use id_base if nothing else was specified + template + static consteval u32 get_type() { - return add_type(0); + return T::id_base; + } + + // Specified type ID for containers which their types may be sharing an overlapping IDs range + template requires requires () { u32{T::id_type}; } + static consteval u32 get_type() + { + return T::id_type; + } + + template + static typeinfo make_typeinfo() + { + typeinfo info{}; + + using C = std::conditional_t && std::is_constructible_v>, T, dummy_construct>; + using Type = std::conditional_t, T, dummy_construct>; + + if constexpr (std::is_same_v) + { + info = + { + +id_traits_load_func::load, + +[](utils::serial& ar, void* obj) { static_cast(obj)->save(ar); }, + +id_traits_savable_func::savable, + id_traits::base, id_traits::step, id_traits::count, id_traits::invl_range, + }; + + const u128 key = u128{get_type()} << 64 | std::bit_cast(C::savestate_init_pos); + + for (const auto& tinfo : get_typeinfo_map()) + { + if (!(tinfo.first ^ key)) + { + ensure(!std::memcmp(&info, &tinfo.second, sizeof(info))); + return info; + } + } + + // id_base must be unique within all the objects with the same initialization posistion by definition of id_map with multiple types + get_typeinfo_map().emplace_back(key, info); + } + else + { + info = + { + nullptr, + nullptr, + nullptr, + id_traits::base, id_traits::step, id_traits::count, id_traits::invl_range, + }; + } + + return info; } }; - template - const u32 typeinfo::registered::index = typeinfo::add_type(1); - // ID value with additional type stored class id_key { - u32 m_value; // ID value - u32 m_type; // True object type + u32 m_value; // ID value + u32 m_base; // ID base (must be unique for each type in the same container) public: id_key() = default; id_key(u32 value, u32 type) : m_value(value) - , m_type(type) + , m_base(type) { } @@ -114,7 +222,7 @@ namespace id_manager u32 type() const { - return m_type; + return m_base; } operator u32() const @@ -135,6 +243,86 @@ namespace id_manager vec.reserve(T::id_count); } + // Order it directly afterward the source type's position + static constexpr double savestate_init_pos = std::bit_cast(std::bit_cast(T::savestate_init_pos) + 1); + + id_map(utils::serial& ar) + { + vec.resize(T::id_count); + + u32 i = ar.operator u32(); + + ensure(i <= T::id_count); + + while (--i != umax) + { + // ID, type hash + const u32 id = ar; + + const u128 type_init_pos = u128{u32{ar}} << 64 | std::bit_cast(T::savestate_init_pos); + const typeinfo* info = nullptr; + + // Search load functions for the one of this type (see make_typeinfo() for explenation about key composition reasoning) + for (const auto& typeinfo : get_typeinfo_map()) + { + if (!(typeinfo.first ^ type_init_pos)) + { + info = std::addressof(typeinfo.second); + } + } + + ensure(info); + + // Construct each object from information collected + + // Simulate construction semantics (idm::last_id() value) + g_id = id; + + auto& obj = vec[get_index(id, info->base, info->step, info->count, info->invl_range)]; + ensure(!obj.second); + + obj.first = id_key(id, static_cast(static_cast(type_init_pos >> 64))); + obj.second = info->load(ar); + } + } + + void save(utils::serial& ar) + { + u32 obj_count = 0; + usz obj_count_offs = ar.data.size(); + + // To be patched at the end of the function + ar(obj_count); + + for (const auto& p : vec) + { + if (!p.second) continue; + + const u128 type_init_pos = u128{p.first.type()} << 64 | std::bit_cast(T::savestate_init_pos); + const typeinfo* info = nullptr; + + // Search load functions for the one of this type (see make_typeinfo() for explenation about key composition reasoning) + for (const auto& typeinfo : get_typeinfo_map()) + { + if (!(typeinfo.first ^ type_init_pos)) + { + ensure(!std::exchange(info, std::addressof(typeinfo.second))); + } + } + + // Save each object with needed information + if (info && info->savable(p.second.get())) + { + ar(p.first.value(), p.first.type()); + info->save(ar, p.second.get()); + obj_count++; + } + } + + // Patch object count + std::memcpy(ar.data.data() + obj_count_offs, &obj_count, sizeof(obj_count)); + } + template requires (std::is_assignable_v) id_map& operator=(thread_state state) { @@ -163,33 +351,12 @@ namespace id_manager // Object manager for emulated process. Multiple objects of specified arbitrary type are given unique IDs. class idm { - // Last allocated ID for constructors - static thread_local u32 g_id; - - template - static inline u32 get_type() - { - return id_manager::typeinfo::get_index(); - } - template static constexpr u32 get_index(u32 id) { using traits = id_manager::id_traits; - constexpr u32 mask_out = ((1u << traits::invl_range.second) - 1) << traits::invl_range.first; - - // Note: if id is lower than base, diff / step will be higher than count - u32 diff = (id & ~mask_out) - traits::base; - - if (diff % traits::step) - { - // id is invalid, return invalid index - return traits::count; - } - - // Get actual index - return diff / traits::step; + return id_manager::get_index(id, traits::base, traits::step, traits::count, traits::invl_range); } // Helper @@ -259,7 +426,7 @@ class idm using map_data = std::pair>; // Prepare new ID (returns nullptr if out of resources) - static map_data* allocate_id(std::vector& vec, u32 type_id, u32 base, u32 step, u32 count, std::pair invl_range); + static map_data* allocate_id(std::vector& vec, u32 type_id, u32 dst_id, u32 base, u32 step, u32 count, std::pair invl_range); // Find ID (additionally check type if types are not equal) template @@ -297,21 +464,24 @@ class idm return nullptr; } - // Allocate new ID and assign the object from the provider() + // Allocate new ID (or use fixed ID) and assign the object from the provider() template - static map_data* create_id(F&& provider) + static map_data* create_id(F&& provider, u32 id = id_manager::id_traits::invalid) { static_assert(PtrSame, "Invalid ID type combination"); // ID traits using traits = id_manager::id_traits; + // Ensure make_typeinfo() is used for this type + stx::typedata(); + // Allocate new id std::lock_guard lock(id_manager::g_mutex); auto& map = g_fxo->get>(); - if (auto* place = allocate_id(map.vec, get_type(), traits::base, traits::step, traits::count, traits::invl_range)) + if (auto* place = allocate_id(map.vec, get_type(), id, traits::base, traits::step, traits::count, traits::invl_range)) { // Get object, store it place->second = provider(); @@ -338,7 +508,14 @@ public: // Get last ID (updated in create_id/allocate_id) static inline u32 last_id() { - return g_id; + return id_manager::g_id; + } + + // Get type ID that is meant to be unique within the same container + template + static consteval u32 get_type() + { + return id_manager::typeinfo::get_type(); } // Add a new ID of specified type with specified constructor arguments (returns object or nullptr) @@ -367,9 +544,9 @@ public: // Add a new ID for an object returned by provider() template requires (std::is_invocable_v) - static inline u32 import(F&& provider) + static inline u32 import(F&& provider, u32 id = id_manager::id_traits::invalid) { - if (auto pair = create_id(std::forward(provider))) + if (auto pair = create_id(std::forward(provider), id)) { return pair->first; } @@ -379,9 +556,9 @@ public: // Add a new ID for an existing object provided (returns new id) template - static inline u32 import_existing(std::shared_ptr ptr) + static inline u32 import_existing(std::shared_ptr ptr, u32 id = id_manager::id_traits::invalid) { - return import([&] { return std::move(ptr); }); + return import([&] { return std::move(ptr); }, id); } // Access the ID record without locking (unsafe) diff --git a/rpcs3/Emu/Io/KeyboardHandler.h b/rpcs3/Emu/Io/KeyboardHandler.h index fc3d0c5b15..179578c0ad 100644 --- a/rpcs3/Emu/Io/KeyboardHandler.h +++ b/rpcs3/Emu/Io/KeyboardHandler.h @@ -77,6 +77,11 @@ public: virtual void Init(const u32 max_connect) = 0; virtual ~KeyboardHandlerBase() = default; + KeyboardHandlerBase(utils::serial* ar); + KeyboardHandlerBase(utils::serial& ar) : KeyboardHandlerBase(&ar) {} + void save(utils::serial& ar); + + SAVESTATE_INIT_POS(19); void Key(u32 code, bool pressed); void SetIntercepted(bool intercepted); diff --git a/rpcs3/Emu/Io/MouseHandler.h b/rpcs3/Emu/Io/MouseHandler.h index ec7a41c300..ad61ad4ef3 100644 --- a/rpcs3/Emu/Io/MouseHandler.h +++ b/rpcs3/Emu/Io/MouseHandler.h @@ -129,6 +129,13 @@ public: virtual void Init(const u32 max_connect) = 0; virtual ~MouseHandlerBase() = default; + SAVESTATE_INIT_POS(18); + + MouseHandlerBase(const MouseHandlerBase&) = delete; + MouseHandlerBase(utils::serial* ar); + MouseHandlerBase(utils::serial& ar) : MouseHandlerBase(&ar) {} + void save(utils::serial& ar); + void Button(u8 button, bool pressed) { std::lock_guard lock(mutex); diff --git a/rpcs3/Emu/Io/Null/NullKeyboardHandler.h b/rpcs3/Emu/Io/Null/NullKeyboardHandler.h index 9bdb8bba2d..953b3ae17c 100644 --- a/rpcs3/Emu/Io/Null/NullKeyboardHandler.h +++ b/rpcs3/Emu/Io/Null/NullKeyboardHandler.h @@ -4,6 +4,8 @@ class NullKeyboardHandler final : public KeyboardHandlerBase { + using KeyboardHandlerBase::KeyboardHandlerBase; + public: void Init(const u32 max_connect) override { diff --git a/rpcs3/Emu/Io/Null/NullMouseHandler.h b/rpcs3/Emu/Io/Null/NullMouseHandler.h index 357d956d30..4d2bae2a2a 100644 --- a/rpcs3/Emu/Io/Null/NullMouseHandler.h +++ b/rpcs3/Emu/Io/Null/NullMouseHandler.h @@ -4,6 +4,8 @@ class NullMouseHandler final : public MouseHandlerBase { + using MouseHandlerBase::MouseHandlerBase; + public: void Init(const u32 max_connect) override { diff --git a/rpcs3/Emu/Memory/vm.cpp b/rpcs3/Emu/Memory/vm.cpp index e59bc3fda0..996eef129b 100644 --- a/rpcs3/Emu/Memory/vm.cpp +++ b/rpcs3/Emu/Memory/vm.cpp @@ -13,13 +13,17 @@ #include "Emu/Cell/SPURecompiler.h" #include "Emu/perf_meter.hpp" #include +#include #include "util/vm.hpp" #include "util/asm.hpp" +#include "util/simd.hpp" +#include "util/serialization.hpp" LOG_CHANNEL(vm_log, "VM"); void ppu_remove_hle_instructions(u32 addr, u32 size); +extern bool is_memory_read_only_of_executable(u32 addr); namespace vm { @@ -883,7 +887,7 @@ namespace vm // Notify rsx to invalidate range // Note: This must be done *before* memory gets unmapped while holding the vm lock, otherwise // the RSX might try to call VirtualProtect on memory that is already unmapped - if (auto& rsxthr = g_fxo->get(); g_fxo->is_init()) + if (auto& rsxthr = g_fxo->get(); !Emu.IsPaused() && g_fxo->is_init()) { rsxthr.on_notify_memory_unmapped(addr, size); } @@ -1145,8 +1149,14 @@ namespace vm return flags; } + static u64 init_block_id() + { + static atomic_t s_id = 1; + return s_id++; + } + block_t::block_t(u32 addr, u32 size, u64 flags) - : m_id([](){ static atomic_t s_id = 1; return s_id++; }()) + : m_id(init_block_id()) , addr(addr) , size(size) , flags(process_block_flags(flags)) @@ -1450,6 +1460,210 @@ namespace vm return imp_used(lock); } + void block_t::get_shared_memory(std::vector>& shared) + { + auto& m_map = (m.*block_map)(); + + if (!(flags & preallocated)) + { + shared.reserve(shared.size() + m_map.size()); + + for (const auto& [addr, shm] : m_map) + { + shared.emplace_back(shm.second.get(), addr); + } + } + } + + u32 block_t::get_shm_addr(const std::shared_ptr& shared) + { + auto& m_map = (m.*block_map)(); + + if (!(flags & preallocated)) + { + for (auto& [addr, pair] : m_map) + { + if (pair.second == shared) + { + return addr; + } + } + } + + return 0; + } + + static bool check_cache_line_zero(const void* ptr) + { + const auto p = reinterpret_cast(ptr); + const v128 _1 = p[0] | p[1]; + const v128 _2 = p[2] | p[3]; + const v128 _3 = p[4] | p[5]; + const v128 _4 = p[6] | p[7]; + const v128 _5 = _1 | _2; + const v128 _6 = _3 | _4; + const v128 _7 = _5 | _6; + return _7 == v128{}; + } + + static void save_memory_bytes(utils::serial& ar, const u8* ptr, usz size) + { + AUDIT(ar.is_writing() && !(size % 1024)); + + for (; size; ptr += 128 * 8, size -= 128 * 8) + { + ar(u8{}); // bitmap of 1024 bytes (bit is 128-byte) + u8 bitmap = 0, count = 0; + + for (usz i = 0, end = std::min(size, 128 * 8); i < end; i += 128) + { + if (!check_cache_line_zero(ptr + i)) + { + bitmap |= 1u << (i / 128); + count++; + ar(std::span(ptr + i, 128)); + } + } + + // Patch bitmap with correct value + *std::prev(&ar.data.back(), count * 128) = bitmap; + } + } + + static void load_memory_bytes(utils::serial& ar, u8* ptr, usz size) + { + AUDIT(ar.is_writing() && !(size % 128)); + + for (; size; ptr += 128 * 8, size -= 128 * 8) + { + const u8 bitmap{ar}; + + for (usz i = 0, end = std::min(size, 128 * 8); i < end; i += 128) + { + if (bitmap & (1u << (i / 128))) + { + ar(std::span(ptr + i, 128)); + } + } + } + } + + void block_t::save(utils::serial& ar, std::map& shared) + { + auto& m_map = (m.*block_map)(); + + ar(addr, size, flags); + + for (const auto& [addr, shm] : m_map) + { + // Assume first page flags represent all the map + ar(g_pages[addr / 4096 + !!(flags & stack_guarded)]); + + ar(addr); + ar(shm.first); + + if (flags & preallocated) + { + // Do not save read-only memory which comes from the executable + // Because it couldn't have changed + if (!(ar.data.back() & page_writable) && is_memory_read_only_of_executable(addr)) + { + // Revert changes + ar.data.resize(ar.data.size() - (sizeof(u32) * 2 + sizeof(memory_page))); + vm_log.success("Removed read-only memory block of the executable from savestate. (addr=0x%x, size=0x%x)", addr, shm.first); + continue; + } + + // Save raw binary image + const u32 guard_size = flags & stack_guarded ? 0x1000 : 0; + save_memory_bytes(ar, vm::get_super_ptr(addr + guard_size), shm.first - guard_size * 2); + } + else + { + // Save index of shm + ar(shared[shm.second.get()]); + } + } + + // Terminator + ar(u8{0}); + } + + block_t::block_t(utils::serial& ar, std::vector>& shared) + : m_id(init_block_id()) + , addr(ar) + , size(ar) + , flags(ar) + { + if (flags & preallocated) + { + m_common = std::make_shared(size); + m_common->map_critical(vm::base(addr), utils::protection::no); + m_common->map_critical(vm::get_super_ptr(addr)); + lock_sudo(addr, size); + } + + auto& m_map = (m.*block_map)(); + + std::shared_ptr null_shm; + + while (true) + { + const u8 flags0 = ar; + + if (!(flags0 & page_allocated)) + { + // Terminator found + break; + } + + const u32 addr0 = ar; + const u32 size0 = ar; + + u64 pflags = 0; + + if (flags0 & page_executable) + { + pflags |= alloc_executable; + } + + if (~flags0 & page_writable) + { + pflags |= alloc_unwritable; + } + + if (~flags0 & page_readable) + { + pflags |= alloc_hidden; + } + + if ((flags & page_size_64k) == page_size_64k) + { + pflags |= page_64k_size; + } + else if (!(flags & (page_size_mask & ~page_size_1m))) + { + pflags |= page_1m_size; + } + + // Map the memory through the same method as alloc() and falloc() + // Copy the shared handle unconditionally + ensure(try_alloc(addr0, pflags, size0, ::as_rvalue(flags & preallocated ? null_shm : shared[ar.operator usz()]))); + + if (flags & preallocated) + { + // Load binary image + const u32 guard_size = flags & stack_guarded ? 0x1000 : 0; + load_memory_bytes(ar, vm::get_super_ptr(addr0 + guard_size), size0 - guard_size * 2); + } + } + } + + bool _unmap_block(const std::shared_ptr& block) + { + return block->unmap(); + } + static bool _test_map(u32 addr, u32 size) { const auto range = utils::address_range::start_length(addr, size); @@ -1623,7 +1837,7 @@ namespace vm result.first = std::move(*it); g_locations.erase(it); - ensure(result.first->unmap()); + ensure(_unmap_block(result.first)); result.second = true; return result; } @@ -1764,7 +1978,7 @@ namespace vm for (auto& block : g_locations) { - if (block) block->unmap(); + if (block) _unmap_block(block); } g_locations.clear(); @@ -1783,6 +1997,106 @@ namespace vm std::memset(g_range_lock_set, 0, sizeof(g_range_lock_set)); g_range_lock_bits = 0; } + + void save(utils::serial& ar) + { + // Shared memory lookup, sample address is saved for easy memory copy + // Just need one address for this optimization + std::vector> shared; + + for (auto& loc : g_locations) + { + if (loc) loc->get_shared_memory(shared); + } + + shared.erase(std::unique(shared.begin(), shared.end(), [](auto& a, auto& b) { return a.first == b.first; }), shared.end()); + + std::map shared_map; + + for (auto& p : shared) + { + shared_map.emplace(p.first, &p - shared.data()); + } + + // TODO: proper serialization of std::map + ar(static_cast(shared_map.size())); + + for (const auto& [shm, addr] : shared) + { + // Save shared memory + ar(shm->flags()); + + // TODO: string_view serialization (even with load function, so the loaded address points to a position of the stream's buffer) + ar(shm->size()); + save_memory_bytes(ar, vm::get_super_ptr(addr), shm->size()); + } + + // TODO: Serialize std::vector direcly + ar(g_locations.size()); + + for (auto& loc : g_locations) + { + const u8 has = loc.operator bool(); + ar(has); + + if (loc) + { + loc->save(ar, shared_map); + } + } + } + + void load(utils::serial& ar) + { + std::vector> shared; + shared.resize(ar.operator usz()); + + for (auto& shm : shared) + { + // Load shared memory + + const u32 flags = ar; + const u64 size = ar; + shm = std::make_shared(size, flags); + + // Load binary image + // elad335: I'm not proud about it as well.. (ideal situation is to not call map_self()) + load_memory_bytes(ar, shm->map_self(), shm->size()); + } + + for (auto& block : g_locations) + { + if (block) _unmap_block(block); + } + + g_locations.clear(); + g_locations.resize(ar.operator usz()); + + for (auto& loc : g_locations) + { + const u8 has = ar; + + if (has) + { + loc = std::make_shared(ar, shared); + } + } + + g_range_lock = 0; + } + + u32 get_shm_addr(const std::shared_ptr& shared) + { + for (auto& loc : g_locations) + { + if (u32 addr = loc ? loc->get_shm_addr(shared) : 0) + { + return addr; + } + } + + return 0; + } } void fmt_class_string>::format(std::string& out, u64 arg) diff --git a/rpcs3/Emu/Memory/vm.h b/rpcs3/Emu/Memory/vm.h index a40a9eb075..34e3bbe032 100644 --- a/rpcs3/Emu/Memory/vm.h +++ b/rpcs3/Emu/Memory/vm.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "util/types.hpp" #include "util/atomic.hpp" #include "util/auto_typemap.hpp" @@ -11,6 +12,7 @@ namespace utils { class shm; + class address_range; } namespace vm @@ -129,6 +131,7 @@ namespace vm // Unmap block bool unmap(); + friend bool _unmap_block(const std::shared_ptr&); public: block_t(u32 addr, u32 size, u64 flags); @@ -167,8 +170,15 @@ namespace vm return m_id; } - friend std::pair, bool> unmap(u32, bool, const std::shared_ptr*); - friend void close(); + // Serialization helper for shared memory + void get_shared_memory(std::vector>& shared); + + // Returns sample address for shared memory, 0 on failure + u32 get_shm_addr(const std::shared_ptr& shared); + + // Serialization + void save(utils::serial& ar, std::map& shared); + block_t(utils::serial& ar, std::vector>& shared); }; // Create new memory block with specified parameters and return it @@ -336,6 +346,12 @@ namespace vm void close(); + void load(utils::serial& ar); + void save(utils::serial& ar); + + // Returns sample address for shared memory, 0 on failure (wraps block_t::get_shm_addr) + u32 get_shm_addr(const std::shared_ptr& shared); + template class _ptr_base; diff --git a/rpcs3/Emu/Memory/vm_ptr.h b/rpcs3/Emu/Memory/vm_ptr.h index 761f784df8..93b97852ab 100644 --- a/rpcs3/Emu/Memory/vm_ptr.h +++ b/rpcs3/Emu/Memory/vm_ptr.h @@ -29,7 +29,7 @@ namespace vm using type = T; using addr_type = std::remove_cv_t; - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; _ptr_base() = default; @@ -240,7 +240,7 @@ namespace vm public: using addr_type = std::remove_cv_t; - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; _ptr_base() = default; diff --git a/rpcs3/Emu/NP/np_allocator.h b/rpcs3/Emu/NP/np_allocator.h index 80808fb3e8..f523d9301e 100644 --- a/rpcs3/Emu/NP/np_allocator.h +++ b/rpcs3/Emu/NP/np_allocator.h @@ -15,9 +15,12 @@ namespace np { public: memory_allocator() = default; + memory_allocator(utils::serial& ar) noexcept { save(ar); } memory_allocator(const memory_allocator&) = delete; memory_allocator& operator=(const memory_allocator&) = delete; + void save(utils::serial& ar); + void setup(vm::ptr ptr_pool, u32 size) { std::lock_guard lock(m_mutex); diff --git a/rpcs3/Emu/NP/np_contexts.h b/rpcs3/Emu/NP/np_contexts.h index 7a04cd8a89..dce086675f 100644 --- a/rpcs3/Emu/NP/np_contexts.h +++ b/rpcs3/Emu/NP/np_contexts.h @@ -13,6 +13,7 @@ struct score_ctx static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 32; + SAVESTATE_INIT_POS(25); SceNpCommunicationId communicationId{}; SceNpCommunicationPassphrase passphrase{}; @@ -27,6 +28,7 @@ struct score_transaction_ctx static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 32; + SAVESTATE_INIT_POS(26); s32 score_context_id = 0; }; @@ -41,6 +43,7 @@ struct match2_ctx static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 255; + SAVESTATE_INIT_POS(27); SceNpCommunicationId communicationId{}; SceNpCommunicationPassphrase passphrase{}; @@ -62,6 +65,7 @@ struct lookup_title_ctx static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 32; + SAVESTATE_INIT_POS(28); SceNpCommunicationId communicationId{}; SceNpCommunicationPassphrase passphrase{}; @@ -76,6 +80,7 @@ struct lookup_transaction_ctx static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 32; + SAVESTATE_INIT_POS(29); s32 lt_ctx = 0; }; @@ -89,6 +94,7 @@ struct commerce2_ctx static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 32; + SAVESTATE_INIT_POS(30); u32 version{}; SceNpId npid{}; @@ -106,6 +112,7 @@ struct signaling_ctx static const u32 id_base = 1; static const u32 id_step = 1; static const u32 id_count = 32; + SAVESTATE_INIT_POS(31); SceNpId npid{}; vm::ptr handler{}; diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 5da0b71d70..2d375796a5 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -42,6 +42,8 @@ #include "util/asm.hpp" +#include + LOG_CHANNEL(sys_net); LOG_CHANNEL(sceNp2); LOG_CHANNEL(sceNp); @@ -372,6 +374,52 @@ namespace np } } + np_handler::np_handler(utils::serial& ar) + : np_handler() + { + ar(is_netctl_init, is_NP_init); + + if (!is_NP_init) + { + return; + } + + ar(is_NP_Lookup_init, is_NP_Score_init, is_NP2_init, is_NP2_Match2_init, is_NP_Auth_init + , manager_cb, manager_cb_arg, std::as_bytes(std::span(&basic_handler, 1)), is_connected, is_psn_active + , hostname, ether_address, local_ip_addr, public_ip_addr, dns_ip); + + // Call init func if needed (np_memory is unaffected when an empty pool is provided) + init_NP(0, vm::null); + + np_memory.save(ar); + + // TODO: IDM-tied objects are not yet saved + } + + void np_handler::save(utils::serial& ar) + { + // TODO: See ctor + ar(is_netctl_init, is_NP_init); + + if (!is_NP_init) + { + return; + } + + USING_SERIALIZATION_VERSION(sceNp); + + ar(is_NP_Lookup_init, is_NP_Score_init, is_NP2_init, is_NP2_Match2_init, is_NP_Auth_init + , manager_cb, manager_cb_arg, std::as_bytes(std::span(&basic_handler, 1)), is_connected, is_psn_active + , hostname, ether_address, local_ip_addr, public_ip_addr, dns_ip); + + np_memory.save(ar); + } + + void memory_allocator::save(utils::serial& ar) + { + ar(m_pool, m_size, m_allocs, m_avail); + } + void np_handler::discover_ip_address() { hostname.clear(); @@ -553,8 +601,11 @@ namespace np void np_handler::init_NP(u32 poolsize, vm::ptr poolptr) { - // Init memory pool - np_memory.setup(poolptr, poolsize); + if (poolsize) + { + // Init memory pool (zero arg is reserved for savestate's use) + np_memory.setup(poolptr, poolsize); + } memset(&npid, 0, sizeof(npid)); memset(&online_name, 0, sizeof(online_name)); diff --git a/rpcs3/Emu/NP/np_handler.h b/rpcs3/Emu/NP/np_handler.h index 90a30378b9..204e2efe19 100644 --- a/rpcs3/Emu/NP/np_handler.h +++ b/rpcs3/Emu/NP/np_handler.h @@ -66,7 +66,11 @@ namespace np class np_handler { public: + SAVESTATE_INIT_POS(5); + np_handler(); + np_handler(utils::serial& ar); + void save(utils::serial& ar); const std::array& get_ether_addr() const; const std::string& get_hostname() const; diff --git a/rpcs3/Emu/RSX/Capture/rsx_replay.h b/rpcs3/Emu/RSX/Capture/rsx_replay.h index 8fe5053c66..2e564b22c9 100644 --- a/rpcs3/Emu/RSX/Capture/rsx_replay.h +++ b/rpcs3/Emu/RSX/Capture/rsx_replay.h @@ -24,7 +24,7 @@ namespace rsx // simple block to hold ps3 address and data struct memory_block { - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; u32 offset; // Offset in rsx address space u32 location; // rsx memory location of the block @@ -41,7 +41,7 @@ namespace rsx struct tile_info { - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; u32 tile; u32 limit; @@ -51,7 +51,7 @@ namespace rsx struct zcull_info { - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; u32 region; u32 size; @@ -64,7 +64,7 @@ namespace rsx // bleh, may need to break these out, might be unnecessary to do both always struct tile_state { - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; tile_info tiles[15]{}; zcull_info zculls[8]{}; @@ -72,7 +72,7 @@ namespace rsx struct buffer_state { - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; u32 width{0}; u32 height{0}; @@ -82,7 +82,7 @@ namespace rsx struct display_buffers_state { - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; std::array buffers{}; u32 count{0}; diff --git a/rpcs3/Emu/RSX/GCM.h b/rpcs3/Emu/RSX/GCM.h index 004dbe5a48..80633e1651 100644 --- a/rpcs3/Emu/RSX/GCM.h +++ b/rpcs3/Emu/RSX/GCM.h @@ -98,6 +98,8 @@ struct CellGcmTileInfo struct GcmZcullInfo { + ENABLE_BITWISE_SERIALIZATION; + u32 offset; u32 width; u32 height; @@ -128,6 +130,8 @@ struct GcmZcullInfo struct GcmTileInfo { + ENABLE_BITWISE_SERIALIZATION; + u32 location; u32 offset; u32 size; diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 803bd273f0..37f62418d5 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -13,7 +13,7 @@ u64 GLGSRender::get_cycles() return thread_ctrl::get_cycles(static_cast&>(*this)); } -GLGSRender::GLGSRender() : GSRender() +GLGSRender::GLGSRender(utils::serial* ar) noexcept : GSRender(ar) { m_shaders_cache = std::make_unique(m_prog_buffer, "opengl", "v1.93"); diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index 98c4dd217e..068a7aacb5 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -70,8 +70,6 @@ namespace gl class GLGSRender : public GSRender, public ::rsx::reports::ZCULL_control { -private: - gl::sampler_state m_fs_sampler_states[rsx::limits::fragment_textures_count]; // Fragment textures gl::sampler_state m_fs_sampler_mirror_states[rsx::limits::fragment_textures_count]; // Alternate views of fragment textures with different format (e.g Depth vs Stencil for D24S8) gl::sampler_state m_vs_sampler_states[rsx::limits::vertex_textures_count]; // Vertex textures @@ -146,7 +144,9 @@ private: public: u64 get_cycles() final; - GLGSRender(); + + GLGSRender(utils::serial* ar) noexcept; + GLGSRender() noexcept : GLGSRender(nullptr) {} private: diff --git a/rpcs3/Emu/RSX/GSRender.cpp b/rpcs3/Emu/RSX/GSRender.cpp index 1ee2c5ad2d..e79aafe747 100644 --- a/rpcs3/Emu/RSX/GSRender.cpp +++ b/rpcs3/Emu/RSX/GSRender.cpp @@ -2,8 +2,7 @@ #include "GSRender.h" -GSRender::GSRender() - : m_context(nullptr) +GSRender::GSRender(utils::serial* ar) noexcept : rsx::thread(ar) { if (auto gs_frame = Emu.GetCallbacks().get_gs_frame()) { @@ -36,13 +35,13 @@ void GSRender::on_init_thread() void GSRender::on_exit() { + rsx::thread::on_exit(); + if (m_frame) { m_frame->delete_context(m_context); m_context = nullptr; } - - rsx::thread::on_exit(); } void GSRender::flip(const rsx::display_flip_info_t&) diff --git a/rpcs3/Emu/RSX/GSRender.h b/rpcs3/Emu/RSX/GSRender.h index 0d99decabd..d939c48ccb 100644 --- a/rpcs3/Emu/RSX/GSRender.h +++ b/rpcs3/Emu/RSX/GSRender.h @@ -20,12 +20,13 @@ class GSRender : public rsx::thread { protected: GSFrameBase* m_frame; - draw_context_t m_context; + draw_context_t m_context = nullptr; public: - GSRender(); ~GSRender() override; + GSRender(utils::serial* ar) noexcept; + void on_init_thread() override; void on_exit() override; diff --git a/rpcs3/Emu/RSX/Null/NullGSRender.cpp b/rpcs3/Emu/RSX/Null/NullGSRender.cpp index 43eefb9499..38d1fd46da 100644 --- a/rpcs3/Emu/RSX/Null/NullGSRender.cpp +++ b/rpcs3/Emu/RSX/Null/NullGSRender.cpp @@ -6,7 +6,7 @@ u64 NullGSRender::get_cycles() return thread_ctrl::get_cycles(static_cast&>(*this)); } -NullGSRender::NullGSRender() : GSRender() +NullGSRender::NullGSRender(utils::serial* ar) noexcept : GSRender(ar) { } diff --git a/rpcs3/Emu/RSX/Null/NullGSRender.h b/rpcs3/Emu/RSX/Null/NullGSRender.h index 4a34121058..16a2512e75 100644 --- a/rpcs3/Emu/RSX/Null/NullGSRender.h +++ b/rpcs3/Emu/RSX/Null/NullGSRender.h @@ -5,7 +5,9 @@ class NullGSRender : public GSRender { public: u64 get_cycles() final; - NullGSRender(); + + NullGSRender(utils::serial* ar) noexcept; + NullGSRender() noexcept : NullGSRender(nullptr) {} private: void end() override; diff --git a/rpcs3/Emu/RSX/RSXFIFO.cpp b/rpcs3/Emu/RSX/RSXFIFO.cpp index ad837ad792..dff8a598bf 100644 --- a/rpcs3/Emu/RSX/RSXFIFO.cpp +++ b/rpcs3/Emu/RSX/RSXFIFO.cpp @@ -810,6 +810,9 @@ namespace rsx } while (fifo_ctrl->read_unsafe(command)); - fifo_ctrl->sync_get(); + if (cpu_flag::again - state) + { + fifo_ctrl->sync_get(); + } } } diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 37b1c5e638..755af8403f 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -45,7 +45,7 @@ extern thread_local std::string(*g_tls_log_prefix)(); template <> bool serialize(utils::serial& ar, rsx::rsx_state& o) { - return ar(o.transform_program, /*o.transform_constants,*/ o.registers); + return ar(o.transform_program, o.transform_constants, o.registers); } template <> @@ -73,6 +73,29 @@ bool serialize(utils::serial& ar, rsx:: return ar(o.rsx_command, o.memory_state, o.tile_state, o.display_buffer_state); } +template <> +bool serialize(utils::serial& ar, rsx::rsx_iomap_table& o) +{ + // We do not need more than that + ar(std::span(o.ea.data(), 512)); + + if (!ar.is_writing()) + { + // Populate o.io + for (const atomic_t& ea_addr : o.ea) + { + const u32& addr = ea_addr.raw(); + + if (addr != umax) + { + o.io[addr >> 20].raw() = static_cast(&ea_addr - o.ea.data()) << 20; + } + } + } + + return true; +} + namespace rsx { std::function g_access_violation_handler; @@ -413,7 +436,26 @@ namespace rsx g_access_violation_handler = nullptr; } - thread::thread() + void thread::save(utils::serial& ar) + { + USING_SERIALIZATION_VERSION_COND(ar.is_writing(), rsx); + + ar(rsx::method_registers); + + for (auto& v : vertex_push_buffers) + { + ar(v.attr, v.size, v.type, v.vertex_count, v.dword_count, v.data); + } + + ar(element_push_buffer, fifo_ret_addr, saved_fifo_ret, zcull_surface_active, m_surface_info, m_depth_surface_info, m_framebuffer_layout); + ar(dma_address, iomap_table, restore_point, tiles, zculls, display_buffers, display_buffers_count, current_display_buffer); + ar(enable_second_vhandler, requested_vsync); + ar(device_addr, label_addr, main_mem_size, local_mem_size, rsx_event_port, driver_info); + ar(in_begin_end, zcull_stats_enabled, zcull_rendering_enabled, zcull_pixel_cnt_enabled); + ar(display_buffers, display_buffers_count, current_display_buffer); + } + + thread::thread(utils::serial* _ar) : cpu_thread(0x5555'5555) { g_access_violation_handler = [this](u32 address, bool is_writing) @@ -435,6 +477,35 @@ namespace rsx } state -= cpu_flag::stop + cpu_flag::wait; // TODO: Remove workaround + + if (!_ar) + { + return; + } + + serialized = true; + save(*_ar); + + if (dma_address) + { + ctrl = vm::_ptr(dma_address); + m_rsx_thread_exiting = false; + } + + if (g_cfg.savestate.start_paused) + { + m_pause_on_first_flip = true; + } + } + + avconf::avconf(utils::serial& ar) + { + ar(*this); + } + + void avconf::save(utils::serial& ar) + { + ar(*this); } void thread::capture_frame(const std::string &name) @@ -628,7 +699,7 @@ namespace rsx return fmt::format("RSX [0x%07x]", rsx->ctrl ? +rsx->ctrl->get : 0); }; - method_registers.init(); + if (!serialized) method_registers.init(); rsx::overlays::reset_performance_overlay(); @@ -646,8 +717,10 @@ namespace rsx performance_counters.state = FIFO_state::empty; + Emu.CallFromMainThread([]{ Emu.RunPPU(); }); + // Wait for startup (TODO) - while (m_rsx_thread_exiting) + while (m_rsx_thread_exiting || Emu.IsPaused()) { // Wait for external pause events if (external_interrupt_lock) @@ -669,9 +742,15 @@ namespace rsx thread_ctrl::wait_for(1000); } + if (is_stopped()) + { + return; + } + performance_counters.state = FIFO_state::running; fifo_ctrl = std::make_unique<::rsx::FIFO::FIFO_control>(this); + fifo_ctrl->set_get(ctrl->get); last_guest_flip_timestamp = rsx::uclock() - 1000000; @@ -796,6 +875,11 @@ namespace rsx void thread::on_exit() { + if (zcull_ctrl) + { + zcull_ctrl->sync(this); + } + // Deregister violation handler g_access_violation_handler = nullptr; @@ -803,7 +887,6 @@ namespace rsx std::this_thread::sleep_for(10ms); do_local_task(rsx::FIFO_state::lock_wait); - m_rsx_thread_exiting = true; g_fxo->get().join(); state += cpu_flag::exit; } @@ -1088,6 +1171,14 @@ namespace rsx { sync(); } + + if (is_stopped()) + { + std::lock_guard lock(m_mtx_task); + + m_invalidated_memory_range = utils::address_range::start_end(0x2 << 28, constants::local_mem_base + local_mem_size - 1); + handle_invalidated_memory_range(); + } } std::array thread::get_color_surface_addresses() const @@ -2519,6 +2610,12 @@ namespace rsx if (info.emu_flip) { performance_counters.sampled_frames++; + + if (m_pause_on_first_flip) + { + Emu.Pause(); + m_pause_on_first_flip = false; + } } last_host_flip_timestamp = rsx::uclock(); @@ -3007,6 +3104,12 @@ namespace rsx if (!m_invalidated_memory_range.valid()) return; + if (is_stopped()) + { + on_invalidate_memory_range(m_invalidated_memory_range, rsx::invalidation_cause::read); + on_invalidate_memory_range(m_invalidated_memory_range, rsx::invalidation_cause::write); + } + on_invalidate_memory_range(m_invalidated_memory_range, rsx::invalidation_cause::unmap); m_invalidated_memory_range.invalidate(); } @@ -3172,7 +3275,7 @@ namespace rsx m_profiler.enabled = !!g_cfg.video.overlay; } - void thread::request_emu_flip(u32 buffer) + bool thread::request_emu_flip(u32 buffer) { if (is_current_thread()) // requested through command buffer { @@ -3184,13 +3287,22 @@ namespace rsx if (async_flip_requested & flip_request::emu_requested) { // ignore multiple requests until previous happens - return; + return true; } async_flip_buffer = buffer; async_flip_requested |= flip_request::emu_requested; + m_eng_interrupt_mask |= rsx::display_interrupt; + + if (state & cpu_flag::exit) + { + // Resubmit possibly-ignored flip on savestate load + return false; + } } + + return true; } void thread::handle_emu_flip(u32 buffer) @@ -3245,12 +3357,6 @@ namespace rsx { const auto delay_us = target_rsx_flip_time - time; lv2_obj::wait_timeout(delay_us); - - if (thread_ctrl::state() == thread_state::aborting) - { - return; - } - performance_counters.idle_time += delay_us; } } diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index f767880058..5050ddba67 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -361,6 +361,8 @@ namespace rsx struct framebuffer_layout { + ENABLE_BITWISE_SERIALIZATION; + u16 width; u16 height; std::array color_addresses; @@ -497,6 +499,9 @@ namespace rsx rsx::profiling_timer m_profiler; frame_statistics_t m_frame_stats; + // Savestates vrelated + bool m_pause_on_first_flip = false; + public: RsxDmaControl* ctrl = nullptr; u32 dma_address{0}; @@ -560,6 +565,7 @@ namespace rsx // I hate this flag, but until hle is closer to lle, its needed bool isHLE{ false }; + bool serialized = false; u32 flip_status; int debug_level; @@ -578,6 +584,7 @@ namespace rsx u32 local_mem_size{0}; u32 rsx_event_port{0}; u32 driver_info{0}; + bool gcm_intr_thread_offline = false; // Hack for savestates void send_event(u64, u64, u64) const; @@ -675,7 +682,10 @@ namespace rsx static constexpr auto thread_name = "rsx::thread"sv; protected: - thread(); + thread(utils::serial* ar); + + thread() : thread(static_cast(nullptr)) {} + virtual void on_task(); virtual void on_exit(); @@ -691,6 +701,7 @@ namespace rsx public: thread(const thread&) = delete; thread& operator=(const thread&) = delete; + void save(utils::serial& ar); virtual void clear_surface(u32 /*arg*/) {} virtual void begin(); @@ -819,7 +830,7 @@ namespace rsx void init(u32 ctrlAddress); // Emu App/Game flip, only immediately flips when called from rsxthread - void request_emu_flip(u32 buffer); + bool request_emu_flip(u32 buffer); void pause(); void unpause(); diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index a8deec91fd..c503ff21d0 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -332,7 +332,7 @@ u64 VKGSRender::get_cycles() return thread_ctrl::get_cycles(static_cast&>(*this)); } -VKGSRender::VKGSRender() : GSRender() +VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) { if (m_instance.create("RPCS3")) { diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index 8b245d521c..b8138f15e8 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -197,9 +197,11 @@ private: public: u64 get_cycles() final; - VKGSRender(); ~VKGSRender() override; + VKGSRender(utils::serial* ar) noexcept; + VKGSRender() noexcept : VKGSRender(nullptr) {} + private: void prepare_rtts(rsx::framebuffer_creation_context context); diff --git a/rpcs3/Emu/RSX/rsx_utils.h b/rpcs3/Emu/RSX/rsx_utils.h index 34d68df413..02e82c1e9d 100644 --- a/rpcs3/Emu/RSX/rsx_utils.h +++ b/rpcs3/Emu/RSX/rsx_utils.h @@ -128,6 +128,8 @@ namespace rsx gcm_framebuffer_info() = default; + ENABLE_BITWISE_SERIALIZATION; + void calculate_memory_range(u32 aa_factor_u, u32 aa_factor_v) { // Account for the last line of the block not reaching the end @@ -161,8 +163,13 @@ namespace rsx u32 resolution_y = 720; // Y RES atomic_t state = 0; // 1 after cellVideoOutConfigure was called + ENABLE_BITWISE_SERIALIZATION; + SAVESTATE_INIT_POS(12); + avconf() noexcept; ~avconf() = default; + avconf(utils::serial& ar); + void save(utils::serial& ar); u32 get_compatible_gcm_format() const; u8 get_bpp() const; diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 672ce4a982..0d31454184 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -27,6 +27,7 @@ #include "Emu/RSX/Capture/rsx_replay.h" #include "Loader/PSF.h" +#include "Loader/TAR.h" #include "Loader/ELF.h" #include "Loader/disc.h" @@ -40,6 +41,7 @@ #include #include #include +#include #include "Utilities/JIT.h" @@ -62,29 +64,95 @@ bool g_use_rtm = false; u64 g_rtm_tx_limit1 = 0; u64 g_rtm_tx_limit2 = 0; +struct serial_ver_t +{ + bool used = false; + u32 current_version = 0; + std::set compatible_versions; +}; + +static std::array s_serial_versions; + +#define SERIALIZATION_VER(name, identifier, ver) \ +\ + const bool s_##name##_serialization_fill = []() { ::s_serial_versions[identifier].compatible_versions = ver; return true; }();\ +\ + extern void using_##name##_serialization()\ + {\ + ensure(Emu.IsStopped());\ + ::s_serial_versions[identifier].used = true;\ + }\ +\ + extern u32 get_##name##_serialization_version()\ + {\ + return ::s_serial_versions[identifier].current_version;\ + } + +SERIALIZATION_VER(global_version, 0, {9}) // For stuff not listed here +SERIALIZATION_VER(ppu, 1, {1}) +SERIALIZATION_VER(spu, 2, {1}) +SERIALIZATION_VER(lv2_sync, 3, {1}) +SERIALIZATION_VER(lv2_vm, 4, {1}) +SERIALIZATION_VER(lv2_net, 5, {1}) +SERIALIZATION_VER(lv2_fs, 6, {1}) +SERIALIZATION_VER(lv2_prx_overlay, 7, {1}) +SERIALIZATION_VER(lv2_memory, 8, {1}) +SERIALIZATION_VER(lv2_config, 9, {1}) + +namespace rsx +{ + SERIALIZATION_VER(rsx, 10, {1}) +} + +namespace np +{ + SERIALIZATION_VER(sceNp, 11, {1}) +} + +#ifdef _MSC_VER +// Compiler bug, lambda function body does seem to inherit used namespace atleast for function decleration +SERIALIZATION_VER(rsx, 10, {1}) +SERIALIZATION_VER(sceNp, 11, {1}) +#endif + +SERIALIZATION_VER(cellVdec, 12, {1}) +SERIALIZATION_VER(cellAudio, 13, {1}) +SERIALIZATION_VER(cellCamera, 14, {1}) +SERIALIZATION_VER(cellGem, 15, {1}) +SERIALIZATION_VER(sceNpTrophy, 16, {1}) +SERIALIZATION_VER(cellMusic, 17, {1}) +SERIALIZATION_VER(cellVoice, 15, {1}) + +#undef SERIALIZATION_VER + std::string g_cfg_defaults; atomic_t g_watchdog_hold_ctr{0}; -extern bool ppu_load_exec(const ppu_exec_object&); +extern bool ppu_load_exec(const ppu_exec_object&, utils::serial* = nullptr); extern void spu_load_exec(const spu_exec_object&); extern void spu_load_rel_exec(const spu_rel_object&); -extern void ppu_precompile(std::vector& dir_queue, std::vector* loaded_prx); +extern void ppu_precompile(std::vector& dir_queue, std::vector* loaded_prx); extern bool ppu_initialize(const ppu_module&, bool = false); extern void ppu_finalize(const ppu_module&); extern void ppu_unload_prx(const lv2_prx&); -extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, const std::string&, s64 = 0); -extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 = 0); +extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, const std::string&, s64 = 0, utils::serial* = nullptr); +extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 = 0, utils::serial* = nullptr); extern bool ppu_load_rel_exec(const ppu_rel_object&); fs::file g_tty; atomic_t g_tty_size{0}; std::array, 16> g_tty_input; std::mutex g_tty_mutex; +thread_local std::string_view g_tls_serialize_name; + +extern thread_local std::string(*g_tls_log_prefix)(); // Report error and call std::abort(), defined in main.cpp [[noreturn]] void report_fatal_error(std::string_view); +void initialize_timebased_time(u64 timebased_init, bool reset = false); + namespace atomic_wait { extern void parse_hashtable(bool(*cb)(u64 id, u32 refs, u64 ptr, u32 max_coll)); @@ -107,6 +175,8 @@ void fmt_class_string::format(std::string& out, u64 arg) case game_boot_result::file_creation_error: return "Could not create important files"; case game_boot_result::firmware_missing: return "Firmware is missing"; case game_boot_result::unsupported_disc_type: return "This disc type is not supported yet"; + case game_boot_result::savestate_corrupted: return "Savestate data is corrupted or it's not an RPCS3 savestate"; + case game_boot_result::savestate_version_unsupported: return "Savestate versioning data differes from your RPCS3 build"; } return unknown; }); @@ -161,6 +231,29 @@ void Emulator::BlockingCallFromMainThread(std::function&& func) const } } +// This function ensures constant initialization order between different compilers and builds +void init_fxo_for_exec(utils::serial* ar, bool full = false) +{ + g_fxo->init(); + + void init_ppu_functions(utils::serial* ar, bool full); + + if (full) + { + init_ppu_functions(ar, true); + } + + Emu.ConfigurePPUCache(); + + g_fxo->init(false, ar); + + Emu.GetCallbacks().init_gs_render(ar); + Emu.GetCallbacks().init_pad_handler(Emu.GetTitleID()); + Emu.GetCallbacks().init_kb_handler(); + Emu.GetCallbacks().init_mouse_handler(); + if (ar) Emu.ExecDeserializationRemnants(); +} + void Emulator::Init(bool add_only) { jit_runtime::initialize(); @@ -335,6 +428,7 @@ void Emulator::Init(bool add_only) make_path_verbose(fs::get_cache_dir() + "shaderlog/"); make_path_verbose(fs::get_cache_dir() + "spu_progs/"); + make_path_verbose(fs::get_cache_dir() + "/savestates/"); make_path_verbose(fs::get_config_dir() + "captures/"); make_path_verbose(fs::get_config_dir() + "sounds/"); make_path_verbose(patch_engine::get_patches_path()); @@ -518,10 +612,12 @@ bool Emulator::BootRsxCapture(const std::string& path) } std::unique_ptr frame = std::make_unique(); - utils::serial load_manager; - load_manager.set_reading_state(in_file.to_vector()); + utils::serial load; + load.set_reading_state(); + in_file.read(load.data, in_file.size()); + load.data.shrink_to_fit(); - load_manager(*frame); + load(*frame); in_file.close(); if (frame->magic != rsx::c_fc_magic) @@ -562,7 +658,7 @@ bool Emulator::BootRsxCapture(const std::string& path) m_state = system_state::ready; GetCallbacks().on_ready(); - GetCallbacks().init_gs_render(); + GetCallbacks().init_gs_render(nullptr); GetCallbacks().init_pad_handler(""); GetCallbacks().on_run(false); @@ -616,10 +712,32 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string& m_config_mode = config_mode; m_config_path = config_path; - if (direct || fs::is_file(path)) + if (fs::file save{path, fs::isfile + fs::read}; save && save.size() >= 8 && save.read() == "RPCS3SAV"_u64) + { + m_ar = std::make_shared(); + m_ar->set_reading_state(); + save.seek(0); + save.read(m_ar->data, save.size()); + m_ar->data.shrink_to_fit(); + } + + if (direct || m_ar || fs::is_file(path)) { m_path = path; - return Load(title_id, add_only); + + auto error = Load(title_id, add_only); + + if (is_error(error)) + { + m_ar.reset(); + } + + if (g_cfg.savestate.suspend_emu && m_ar) + { + fs::remove_file(path); + } + + return error; } game_boot_result result = game_boot_result::nothing_to_boot; @@ -666,8 +784,6 @@ void Emulator::SetForceBoot(bool force_boot) game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool is_disc_patch) { - const std::string resolved_path = GetCallbacks().resolve_path(m_path); - if (m_config_mode == cfg_mode::continuous) { // The program is being booted from another running program @@ -727,8 +843,175 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool games.reset(); } + m_state_inspection_savestate = g_cfg.savestate.state_inspection_mode.get(); + + if (m_ar) + { + struct file_header + { + ENABLE_BITWISE_SERIALIZATION; + + nse_t magic; + bool LE_format; + bool state_inspection_support; + nse_t offset; + }; + + if (m_ar->data.size() <= sizeof(file_header)) + { + return game_boot_result::savestate_corrupted; + } + + const file_header header = m_ar->operator file_header(); + + if (header.magic != "RPCS3SAV"_u64) + { + return game_boot_result::savestate_corrupted; + } + + if (header.LE_format != (std::endian::native == std::endian::little)) + { + return game_boot_result::savestate_corrupted; + } + + g_cfg.savestate.state_inspection_mode.set(header.state_inspection_support); + + // Emulate seek operation (please avoid using in other places) + m_ar->pos = header.offset; + const std::vector> versions_data = *m_ar; + m_ar->pos = sizeof(file_header); // Restore position + + if (versions_data.empty()) + { + return game_boot_result::savestate_corrupted; + } + + bool ok = true; + + for (auto [identifier, version] : versions_data) + { + if (identifier >= s_serial_versions.size()) + { + sys_log.error("Savestate version identider is unknown! (category=%u, version=%u)", identifier, version); + ok = false; // Log all mismatches + } + else if (!s_serial_versions[identifier].compatible_versions.count(version)) + { + sys_log.error("Savestate version is not supported. (category=%u, version=%u)", identifier, version); + ok = false; + } + else + { + s_serial_versions[identifier].current_version = version; + } + } + + if (!ok) + { + return game_boot_result::savestate_version_unsupported; + } + + argv.clear(); + + std::string bdvd_by_title_id; + (*m_ar)(argv.emplace_back(), bdvd_by_title_id); + + klic.clear(); + + if (u128 key = m_ar->operator u128()) + { + klic.emplace_back(key); + } + + if (!bdvd_by_title_id.empty()) + { + m_title_id = bdvd_by_title_id; + + // Load /dev_bdvd/ from game list if available + if (auto node = games[m_title_id]) + { + disc = node.Scalar(); + } + else + { + sys_log.fatal("Disc directory not found. Savestate cannot be loaded. ('%s')", m_title_id); + return game_boot_result::invalid_file_or_folder; + } + } + + hdd1 = m_ar->operator std::string(); + + auto load_tar = [&](const std::string& path) + { + const usz size = *m_ar; + + if (size) + { + fs::remove_all(path, false); + ensure(tar_object(fs::file(&m_ar->data[m_ar->pos], size)).extract(path)); + m_ar->pos += size; + } + }; + + if (!hdd1.empty()) + { + hdd1 = rpcs3::utils::get_hdd1_dir() + "caches/" + hdd1 + "/"; + load_tar(hdd1); + } + + for (const std::string hdd0_game = rpcs3::utils::get_hdd0_dir() + "game/";;) + { + const std::string game_data = m_ar->operator std::string(); + + if (game_data.empty()) + { + break; + } + + load_tar(hdd0_game + game_data); + } + + if (argv[0].starts_with("/dev_hdd0"sv)) + { + m_path = rpcs3::utils::get_hdd0_dir(); + m_path += std::string_view(argv[0]).substr(9); + } + else if (argv[0].starts_with("/dev_flash"sv)) + { + m_path = g_cfg_vfs.get_dev_flash(); + m_path += std::string_view(argv[0]).substr(10); + } + else if (argv[0].starts_with("/dev_bdvd"sv)) + { + m_path = disc; + m_path += std::string_view(argv[0]).substr(9); + } + else if (argv[0].starts_with("/host_root"sv)) + { + sys_log.error("Host root has been used in savestates!"); + m_path = argv[0].substr(9); + } + else if (argv[0].starts_with("/dev_hdd1"sv)) + { + sys_log.error("HDD1 has been used to store executable in savestates!"); + m_path = rpcs3::utils::get_hdd1_dir(); + m_path += std::string_view(argv[0]).substr(9); + } + else + { + sys_log.error("Unknown source for savestates: %s", argv[0]); + } + + argv.clear(); + } + + const std::string resolved_path = GetCallbacks().resolve_path(m_path); + const std::string elf_dir = fs::get_parent_dir(m_path); + // Mount /app_home + vfs::mount("/app_home", g_cfg_vfs.app_home.to_string().empty() ? elf_dir + '/' : g_cfg_vfs.get(g_cfg_vfs.app_home, rpcs3::utils::get_emu_dir())); + // Load PARAM.SFO (TODO) psf::registry _psf; if (fs::file sfov{elf_dir + "/sce_sys/param.sfo"}) @@ -856,8 +1139,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool } } - initalize_timebased_time(); - // Set RTM usage g_use_rtm = utils::has_rtm() && (((utils::has_mpx() && !utils::has_tsx_force_abort()) && g_cfg.core.enable_TSX == tsx_usage::enabled) || g_cfg.core.enable_TSX == tsx_usage::forced); @@ -1324,7 +1605,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool // Check game updates const std::string hdd0_boot = hdd0_game + m_title_id + "/USRDIR/EBOOT.BIN"; - if (disc.empty() && !bdvd_dir.empty() && GetCallbacks().resolve_path(m_path) != GetCallbacks().resolve_path(hdd0_boot) && fs::is_file(hdd0_boot)) + if (!m_ar && disc.empty() && !bdvd_dir.empty() && GetCallbacks().resolve_path(m_path) != GetCallbacks().resolve_path(hdd0_boot) && fs::is_file(hdd0_boot)) { // Booting game update sys_log.success("Updates found at /dev_hdd0/game/%s/", m_title_id); @@ -1365,11 +1646,38 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool elf_path = vfs::get(argv[0]); } + if (m_ar) + { + g_tls_log_prefix = []() + { + return fmt::format("Emu State Load Thread: '%s'", g_tls_serialize_name); + }; + } + fs::file elf_file(elf_path); if (!elf_file) { sys_log.error("Failed to open executable: %s", elf_path); + + if (m_ar) + { + sys_log.warning("State Inspection Savestate Mode!"); + + vm::init(); + vm::load(*m_ar); + + if (!hdd1.empty()) + { + vfs::mount("/dev_hdd1", hdd1); + sys_log.notice("Hdd1: %s", hdd1); + } + + init_fxo_for_exec(DeserialManager(), true); + + return game_boot_result::no_errors; + } + return game_boot_result::invalid_file_or_folder; } @@ -1416,7 +1724,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool } m_state = system_state::ready; - vm::init(); ppu_exec_object ppu_exec; ppu_prx_object ppu_prx; @@ -1424,6 +1731,19 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool spu_exec_object spu_exec; spu_rel_object spu_rel; + vm::init(); + + if (m_ar) + { + vm::load(*m_ar); + } + + if (!hdd1.empty()) + { + vfs::mount("/dev_hdd1", hdd1); + sys_log.notice("Hdd1: %s", vfs::get("/dev_hdd1")); + } + if (ppu_exec.open(elf_file) == elf_error::ok) { // PS3 executable @@ -1496,18 +1816,9 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool } g_fxo->init(); - g_fxo->init>(); - g_fxo->init>>(); - if (ppu_load_exec(ppu_exec)) + if (ppu_load_exec(ppu_exec, DeserialManager())) { - ConfigurePPUCache(); - - g_fxo->init(false); - GetCallbacks().init_gs_render(); - GetCallbacks().init_pad_handler(m_title_id); - GetCallbacks().init_kb_handler(); - GetCallbacks().init_mouse_handler(); } // Overlay (OVL) executable (only load it) else if (vm::map(0x3000'0000, 0x1000'0000, 0x200); !ppu_load_overlay(ppu_exec, m_path).first) @@ -1621,13 +1932,25 @@ void Emulator::Run(bool start_playtime) m_pause_start_time = 0; m_pause_amend_time = 0; + rpcs3::utils::configure_logs(); - m_state = system_state::running; + m_state = system_state::starting; + + if (g_cfg.misc.prevent_display_sleep) + { + disable_display_sleep(); + } +} + +void Emulator::RunPPU() +{ + ensure(IsStarting()); // Run main thread - idm::check>(ppu_thread::id_base, [](named_thread& cpu) + idm::select>([](u32, named_thread& cpu) { + if (cpu.stop_flag_removal_protection) return; ensure(cpu.state.test_and_reset(cpu_flag::stop)); cpu.state.notify_one(cpu_flag::stop); }); @@ -1637,11 +1960,44 @@ void Emulator::Run(bool start_playtime) thr->state -= cpu_flag::stop; thr->state.notify_one(cpu_flag::stop); } +} - if (g_cfg.misc.prevent_display_sleep) +void Emulator::FixGuestTime() +{ + if (m_ar) { - disable_display_sleep(); + initialize_timebased_time(m_ar->operator u64()); + + g_cfg.savestate.state_inspection_mode.set(m_state_inspection_savestate); + m_ar.reset(); + + CallFromMainThread([this] + { + g_tls_log_prefix = []() + { + return std::string(); + }; + }); } + else + { + initialize_timebased_time(0); + } +} +void Emulator::FinalizeRunRequest() +{ + auto on_select = [] (u32, spu_thread& cpu) + { + if (cpu.stop_flag_removal_protection) return; + ensure(cpu.state.test_and_reset(cpu_flag::stop)); + cpu.state.notify_one(cpu_flag::stop); + }; + + idm::select>(on_select); + + lv2_obj::awake_all(); + + m_state.compare_and_swap_test(system_state::starting, system_state::running); } bool Emulator::Pause(bool freeze_emulation) @@ -1795,7 +2151,7 @@ u64 get_sysutil_cb_manager_read_count(); void process_qt_events(); -void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op) +void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savestate) { const auto old_state = m_state.load(); @@ -1811,10 +2167,10 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op) const u64 read_counter = get_sysutil_cb_manager_read_count(); - if (old_state == system_state::frozen || !sysutil_send_system_cmd(0x0101 /* CELL_SYSUTIL_REQUEST_EXITGAME */, 0)) + if (old_state == system_state::frozen || savestate || !sysutil_send_system_cmd(0x0101 /* CELL_SYSUTIL_REQUEST_EXITGAME */, 0)) { // The callback has been rudely ignored, we have no other option but to force termination - Kill(allow_autoexit); + Kill(allow_autoexit && !savestate, savestate); return; } @@ -1857,8 +2213,23 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op) } } -void Emulator::Kill(bool allow_autoexit) +extern bool try_lock_vdec_context_creation(); + +void Emulator::Kill(bool allow_autoexit, bool savestate) { + if (!try_lock_vdec_context_creation()) + { + sys_log.error("Failed to savestate: HLE VDEC (video decoder) context(s) exist." + "\nLLE libvdec.sprx by selecting it in Adavcned tab -> Firmware Libraries." + "\nYou need to close the game for 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."); + } + + g_tls_log_prefix = []() + { + return std::string(); + }; + if (m_state.exchange(system_state::stopped) == system_state::stopped) { // Ensure clean state @@ -1887,6 +2258,13 @@ void Emulator::Kill(bool allow_autoexit) } } + m_ar.reset(); + + if (savestate) + { + m_ar = std::make_unique(); + } + named_thread stop_watchdog("Stop Watchdog", [&]() { for (uint i = 0; thread_ctrl::state() != thread_state::aborting;) @@ -1963,14 +2341,121 @@ void Emulator::Kill(bool allow_autoexit) } } - cpu_thread::cleanup(); + // Save it first for maximum timing accuracy + const u64 timestamp = get_timebased_time(); - g_fxo->reset(); + stop_watchdog = thread_state::aborting; sys_log.notice("All threads have been stopped."); + if (savestate) + { + // Savestate thread + named_thread emu_state_cap_thread("Emu State Capture Thread", [&]() + { + g_tls_log_prefix = []() + { + return fmt::format("Emu State Capture Thread: '%s'", g_tls_serialize_name); + }; + + auto& ar = *m_ar; + + for (serial_ver_t& ver : s_serial_versions) + { + ver.used = false; + } + + using_global_version_serialization(); + using_ppu_serialization(); + + // Avoid duplicating TAR object memory because it can be very large + auto save_tar = [&](const std::string& path) + { + ar(usz{}); // Reserve memory to be patched later with correct size + const usz old_size = ar.data.size(); + ar.data = tar_object::save_directory(path, std::move(ar.data)); + const usz tar_size = ar.data.size() - old_size; + std::memcpy(ar.data.data() + old_size - sizeof(usz), &tar_size, sizeof(usz)); + sys_log.success("Saved the contents of directory '%s' (size=0x%x)", path, tar_size); + }; + + auto save_hdd1 = [&]() + { + const std::string _path = vfs::get("/dev_hdd1"); + std::string_view path = _path; + + path = path.substr(0, path.find_last_not_of(fs::delim) + 1); + + ar(std::string(path.substr(path.find_last_of(fs::delim) + 1))); + + if (!_path.empty()) + { + if (!g_cfg.savestate.suspend_emu) + { + save_tar(_path); + } + else + { + ar(usz{}); + } + } + }; + + auto save_hdd0 = [&]() + { + if (!g_cfg.savestate.suspend_emu && g_cfg.savestate.save_disc_game_data) + { + const std::string path = vfs::get("/dev_hdd0/game/"); + + for (auto& entry : fs::dir(path)) + { + if (entry.is_directory && entry.name != "." && entry.name != "..") + { + if (auto res = psf::load(path + entry.name + "/PARAM.SFO"); res && /*!m_title_id.empty() &&*/ psf::get_string(res.sfo, "TITLE_ID") == m_title_id && psf::get_string(res.sfo, "CATEGORY") == "GD") + { + ar(entry.name); + save_tar(path + entry.name); + } + } + } + } + + ar(std::string{}); + }; + + ar("RPCS3SAV"_u64); + ar(std::endian::native == std::endian::little); + ar(g_cfg.savestate.state_inspection_mode.get()); + ar(usz{0}); // Offset of versioning data, to be overwritten at the end of saving + ar(argv[0]); + ar(!m_title_id.empty() && !vfs::get("/dev_bdvd").empty() ? m_title_id : std::string()); + ar(klic.empty() ? std::array{} : std::bit_cast>(klic[0])); + save_hdd1(); + save_hdd0(); + vm::save(ar); + g_fxo->save(ar); + ar(timestamp); + }); + + // Join it + emu_state_cap_thread(); + + if (emu_state_cap_thread == thread_state::errored) + { + sys_log.error("Saving savestate failed due to fatal error!"); + m_ar.reset(); + savestate = false; + } + } + + cpu_thread::cleanup(); + + initialize_timebased_time(0, true); + lv2_obj::cleanup(); + g_fxo->reset(); + sys_log.notice("Objects cleared..."); vm::close(); @@ -2002,12 +2487,46 @@ void Emulator::Kill(bool allow_autoexit) sys_log.notice("Atomic wait hashtable stats: [in_use=%u, used=%u, max_collision_weight=%u, total_collisions=%u]", aw_refs, aw_used, aw_colm, aw_colc); - stop_watchdog = thread_state::aborting; - m_stop_ctr++; m_stop_ctr.notify_all(); - // Boot arg cleanup + if (savestate) + { + const std::string path = fs::get_cache_dir() + "/savestates/" + (m_title_id.empty() ? m_path.substr(m_path.find_last_of(fs::delim) + 1) : m_title_id) + ".SAVESTAT"; + + fs::pending_file file(path); + + // Identifer -> version + std::vector> used_serial; + used_serial.reserve(s_serial_versions.size()); + + for (const serial_ver_t& ver : s_serial_versions) + { + if (ver.used) + { + used_serial.emplace_back(&ver - s_serial_versions.data(), *ver.compatible_versions.rbegin()); + } + } + + auto& ar = *m_ar; + const usz pos = ar.data.size(); + std::memcpy(&ar.data[10], &pos, 8);// Set offset + ar(used_serial); + + if (!file.file || (file.file.write(ar.data), !file.commit())) + { + sys_log.error("Failed to write savestate to file! (path='%s', %s)", path, fs::g_tls_error); + } + else + { + sys_log.success("Saved savestate! path='%s'", path); + } + + ar.pos = 0; + } + + // Boot arg cleanup (preserved in the case restarting) + m_ar.reset(); argv.clear(); envp.clear(); data.clear(); @@ -2030,7 +2549,7 @@ void Emulator::Kill(bool allow_autoexit) } } -game_boot_result Emulator::Restart() +game_boot_result Emulator::Restart(bool savestate) { if (m_state == system_state::stopped) { @@ -2039,16 +2558,33 @@ game_boot_result Emulator::Restart() auto save_args = std::make_tuple(argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_mode); - GracefulShutdown(false, false); + GracefulShutdown(false, false, savestate); std::tie(argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_mode) = std::move(save_args); + if (savestate) + { + if (!m_ar) + { + return game_boot_result::generic_error; + } + + if (g_cfg.savestate.suspend_emu) + { + return game_boot_result::no_errors; + } + } + // Reload with prior configs. if (const auto error = Load(m_title_id); error != game_boot_result::no_errors) { sys_log.error("Restart failed: %s", error); return error; } + else + { + m_ar.reset(); + } return game_boot_result::no_errors; } @@ -2420,4 +2956,10 @@ game_boot_result Emulator::InsertDisc(const std::string& path) return game_boot_result::no_errors; } +utils::serial* Emulator::DeserialManager() const +{ + ensure(!m_ar || !m_ar->is_writing()); + return m_ar.get(); +} + Emulator Emu; diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index 07aabad43c..8445c7fc69 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -10,6 +10,8 @@ #include "Emu/Cell/timers.hpp" +void init_fxo_for_exec(utils::serial*, bool); + struct progress_dialog_workaround { // WORKAROUND: @@ -28,6 +30,7 @@ enum class system_state : u32 paused, frozen, // paused but cannot resume ready, + starting, }; enum class game_boot_result : u32 @@ -41,9 +44,16 @@ enum class game_boot_result : u32 decryption_error, file_creation_error, firmware_missing, - unsupported_disc_type + unsupported_disc_type, + savestate_corrupted, + savestate_version_unsupported, }; +constexpr bool is_error(game_boot_result res) +{ + return res != game_boot_result::no_errors; +} + enum class cfg_mode { custom, // Prefer regular custom config. Fall back to global config. @@ -71,9 +81,9 @@ struct EmuCallbacks std::function init_mouse_handler; std::function init_pad_handler; std::function()> get_gs_frame; - std::function init_gs_render; std::function()> get_camera_handler; std::function()> get_music_handler; + std::function init_gs_render; std::function()> get_audio; std::function()> get_msg_dialog; std::function()> get_osk_dialog; @@ -84,7 +94,12 @@ struct EmuCallbacks std::function get_localized_string; std::function get_localized_u32string; std::function play_sound; - std::string(*resolve_path)(std::string_view) = nullptr; // Resolve path using Qt + std::string(*resolve_path)(std::string_view) = [](std::string_view arg){ return std::string{arg}; }; // Resolve path using Qt +}; + +namespace utils +{ + struct serial; }; class Emulator final @@ -114,6 +129,7 @@ class Emulator final std::string m_game_dir{"PS3_GAME"}; std::string m_usr{"00000001"}; u32 m_usrid{1}; + std::shared_ptr m_ar; // This flag should be adjusted before each Kill() or each BootGame() and similar because: // 1. It forces an application to boot immediately by calling Run() in Load(). @@ -122,6 +138,18 @@ class Emulator final bool m_has_gui = true; + bool m_state_inspection_savestate = false; + + std::vector> deferred_deserialization; + + void ExecDeserializationRemnants() + { + for (auto&& func : ::as_rvalue(std::move(deferred_deserialization))) + { + func(); + } + } + public: Emulator() = default; @@ -153,6 +181,11 @@ public: CallFromMainThread(std::move(func), nullptr, true, static_cast(counter)); } + void DeferDeserialization(std::function&& func) + { + deferred_deserialization.emplace_back(std::move(func)); + } + /** Set emulator mode to running unconditionnaly. * Required to execute various part (PPUInterpreter, memory manager...) outside of rpcs3. */ @@ -230,6 +263,9 @@ public: return m_usr; } + // Get deserialization manager + utils::serial* DeserialManager() const; + // u32 for cell. u32 GetUsrId() const { @@ -257,18 +293,23 @@ public: game_boot_result Load(const std::string& title_id = "", bool add_only = false, bool is_disc_patch = false); void Run(bool start_playtime); + void RunPPU(); + void FixGuestTime(); + void FinalizeRunRequest(); + bool Pause(bool freeze_emulation = false); void Resume(); - void GracefulShutdown(bool allow_autoexit = true, bool async_op = false); - void Kill(bool allow_autoexit = true); - game_boot_result Restart(); + void GracefulShutdown(bool allow_autoexit = true, bool async_op = false, bool savestate = false); + void Kill(bool allow_autoexit = true, bool savestate = false); + game_boot_result Restart(bool savestate = false); bool Quit(bool force_quit); static void CleanUp(); bool IsRunning() const { return m_state == system_state::running; } - bool IsPaused() const { return m_state >= system_state::paused; } // ready is also considered paused by this function + bool IsPaused() const { return m_state >= system_state::paused; } // ready/starting are also considered paused by this function bool IsStopped() const { return m_state == system_state::stopped; } bool IsReady() const { return m_state == system_state::ready; } + bool IsStarting() const { return m_state == system_state::starting; } auto GetStatus() const { system_state state = m_state; return state == system_state::frozen ? system_state::paused : state; } bool HasGui() const { return m_has_gui; } @@ -291,6 +332,7 @@ public: static game_boot_result GetElfPathFromDir(std::string& elf_path, const std::string& path); static void GetBdvdDir(std::string& bdvd_dir, std::string& sfb_dir, std::string& game_dir, const std::string& elf_dir); + friend void init_fxo_for_exec(utils::serial*, bool); }; extern Emulator Emu; diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index 916d49136f..fff1b72a88 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -68,8 +68,9 @@ bool vfs::mount(std::string_view vpath, std::string_view path) if (pos == umax) { // Mounting completed - list.back()->path = path; - vfs_log.notice("Mounted path \"%s\" to \"%s\"", vpath_backup, path); + list.back()->path = Emu.GetCallbacks().resolve_path(path); + list.back()->path += '/'; + vfs_log.notice("Mounted path \"%s\" to \"%s\"", vpath_backup, list.back()->path); return true; } @@ -357,6 +358,100 @@ using char2 = char8_t; using char2 = char; #endif +std::string vfs::retrieve(std::string_view path, const vfs_directory* node, std::vector* mount_path) +{ + auto& table = g_fxo->get(); + + if (!node) + { + if (path.starts_with(".")) + { + return {}; + } + + const std::string rpath = Emu.GetCallbacks().resolve_path(path); + + if (rpath.empty()) + { + return {}; + } + + reader_lock lock(table.mutex); + + std::vector mount_path_empty; + + return vfs::retrieve(rpath, &table.root, &mount_path_empty); + } + + mount_path->emplace_back(); + + // Try to extract host root mount point name (if exists) + std::string_view host_root_name; + + for (const auto& [name, dir] : node->dirs) + { + mount_path->back() = name; + + if (std::string res = vfs::retrieve(path, &dir, mount_path); !res.empty()) + { + return res; + } + + if (dir.path == "/"sv) + { + host_root_name = name; + } + } + + mount_path->pop_back(); + + if (node->path.size() > 1 && path.starts_with(node->path)) + { + auto unescape_path = [](std::string_view path) + { + // Unescape from host FS + std::vector escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); + std::vector result; + for (auto& sv : escaped) + result.emplace_back(vfs::unescape(sv)); + + return fmt::merge(result, "/"); + }; + + std::string result{"/"}; + + for (const auto& name : *mount_path) + { + result += name; + result += '/'; + } + + result += unescape_path(path.substr(node->path.size())); + return result; + } + + if (!host_root_name.empty()) + { + // If failed to find mount point for path and /host_root is mounted + // Prepend "/host_root" to path and return the constructed string + std::string result{"/"}; + + for (const auto& name : *mount_path) + { + result += name; + result += '/'; + } + + result += host_root_name; + result += '/'; + + result += path; + return result; + } + + return {}; +} + std::string vfs::escape(std::string_view name, bool escape_slash) { std::string result; diff --git a/rpcs3/Emu/VFS.h b/rpcs3/Emu/VFS.h index 5753ad69b2..bab790ca08 100644 --- a/rpcs3/Emu/VFS.h +++ b/rpcs3/Emu/VFS.h @@ -5,6 +5,7 @@ #include struct lv2_fs_mount_point; +struct vfs_directory; namespace vfs { @@ -17,6 +18,9 @@ namespace vfs // Convert VFS path to fs path, optionally listing directories mounted in it std::string get(std::string_view vpath, std::vector* out_dir = nullptr, std::string* out_path = nullptr); + // Convert fs path to VFS path + std::string retrieve(std::string_view path, const vfs_directory* node = nullptr, std::vector* mount_path = nullptr); + // Escape VFS name by replacing non-portable characters with surrogates std::string escape(std::string_view name, bool escape_slash = false); diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index c2e4c1b741..b0de3df0c7 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -305,6 +305,16 @@ struct cfg_root : cfg::node cfg::_enum psn_status{this, "PSN status", np_psn_status::disabled}; } net{this}; + struct node_savestate : cfg::node + { + node_savestate(cfg::node* _this) : cfg::node(_this, "Savestate") {} + + cfg::_bool start_paused{ this, "Start Paused" }; // Pause on first frame + cfg::_bool suspend_emu{ this, "Suspend Emulation Savestate Mode", false }; // Close emulation when saving, delete save after loading + cfg::_bool state_inspection_mode{ this, "Inspection Mode Savestates" }; // Save memory stored in executable files, thus allowing to view state without any files (for debugging) + cfg::_bool save_disc_game_data{ this, "Save Disc Game Data", false }; + } savestate{this}; + struct node_misc : cfg::node { node_misc(cfg::node* _this) : cfg::node(_this, "Miscellaneous") {} diff --git a/rpcs3/Input/basic_keyboard_handler.cpp b/rpcs3/Input/basic_keyboard_handler.cpp index 1a23e336eb..80cf662503 100644 --- a/rpcs3/Input/basic_keyboard_handler.cpp +++ b/rpcs3/Input/basic_keyboard_handler.cpp @@ -33,10 +33,6 @@ void basic_keyboard_handler::Init(const u32 max_connect) m_info.status[0] = CELL_KB_STATUS_CONNECTED; // (TODO: Support for more keyboards) } -basic_keyboard_handler::basic_keyboard_handler() : QObject() -{ -} - /* Sets the target window for the event handler, and also installs an event filter on the target. */ void basic_keyboard_handler::SetTargetWindow(QWindow* target) { diff --git a/rpcs3/Input/basic_keyboard_handler.h b/rpcs3/Input/basic_keyboard_handler.h index 52d0608bce..d148065711 100644 --- a/rpcs3/Input/basic_keyboard_handler.h +++ b/rpcs3/Input/basic_keyboard_handler.h @@ -8,11 +8,11 @@ class basic_keyboard_handler final : public KeyboardHandlerBase, public QObject { + using KeyboardHandlerBase::KeyboardHandlerBase; + public: void Init(const u32 max_connect) override; - explicit basic_keyboard_handler(); - void SetTargetWindow(QWindow* target); bool eventFilter(QObject* watched, QEvent* event) override; void keyPressEvent(QKeyEvent* event); diff --git a/rpcs3/Input/basic_mouse_handler.cpp b/rpcs3/Input/basic_mouse_handler.cpp index 0ea7e02c2f..6bc4e3c248 100644 --- a/rpcs3/Input/basic_mouse_handler.cpp +++ b/rpcs3/Input/basic_mouse_handler.cpp @@ -28,9 +28,6 @@ void basic_mouse_handler::Init(const u32 max_connect) m_info.product_id[0] = 0x1234; } -basic_mouse_handler::basic_mouse_handler() : QObject() -{} - /* Sets the target window for the event handler, and also installs an event filter on the target. */ void basic_mouse_handler::SetTargetWindow(QWindow* target) { diff --git a/rpcs3/Input/basic_mouse_handler.h b/rpcs3/Input/basic_mouse_handler.h index 9dbd169a04..b308bb5412 100644 --- a/rpcs3/Input/basic_mouse_handler.h +++ b/rpcs3/Input/basic_mouse_handler.h @@ -9,11 +9,11 @@ class basic_mouse_handler final : public MouseHandlerBase, public QObject { + using MouseHandlerBase::MouseHandlerBase; + public: void Init(const u32 max_connect) override; - basic_mouse_handler(); - void SetTargetWindow(QWindow* target); void MouseButtonDown(QMouseEvent* event); void MouseButtonUp(QMouseEvent* event); diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index 7ee7dab989..8c2c83a663 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -42,6 +42,7 @@ pad_thread::pad_thread(void *_curthread, void *_curwindow, std::string_view titl { pad::g_title_id = title_id; pad::g_current = this; + pad::g_reset = true; } pad_thread::~pad_thread() @@ -212,7 +213,8 @@ void pad_thread::SetIntercepted(bool intercepted) void pad_thread::operator()() { - pad::g_reset = true; + Init(); + pad::g_reset = false; atomic_t pad_mode{g_cfg.io.pad_mode.get()}; std::vector>>> threads; diff --git a/rpcs3/Loader/TAR.cpp b/rpcs3/Loader/TAR.cpp index df69d017df..9ec683f434 100644 --- a/rpcs3/Loader/TAR.cpp +++ b/rpcs3/Loader/TAR.cpp @@ -150,7 +150,7 @@ fs::file tar_object::get_file(const std::string& path) } } -bool tar_object::extract(std::string vfs_mp) +bool tar_object::extract(std::string prefix_path, bool is_vfs) { if (!m_file) return false; @@ -163,21 +163,26 @@ bool tar_object::extract(std::string vfs_mp) std::string result = name; - if (!vfs_mp.empty()) + if (!prefix_path.empty()) { - result = fmt::format("/%s/%s", vfs_mp, result); + result = prefix_path + '/' + result; } else { + // Must be VFS here + is_vfs = true; result.insert(result.begin(), '/'); } - result = vfs::get(result); - - if (result.empty()) + if (is_vfs) { - tar_log.error("Path of entry is not mounted: '%s' (vfs_mp='%s')", name, vfs_mp); - return false; + result = vfs::get(result); + + if (result.empty()) + { + tar_log.error("Path of entry is not mounted: '%s' (prefix_path='%s')", name, prefix_path); + return false; + } } u64 mtime = octal_text_to_u64({header.mtime, std::size(header.mtime)}); @@ -197,8 +202,8 @@ bool tar_object::extract(std::string vfs_mp) case '\0': case '0': { - // Create the directories which should have been mount points if vfs_mp is not empty - if (!vfs_mp.empty() && !fs::create_path(fs::get_parent_dir(result))) + // Create the directories which should have been mount points if prefix_path is not empty + if (!prefix_path.empty() && !fs::create_path(fs::get_parent_dir(result))) { tar_log.error("TAR Loader: failed to create directory for file %s (%s)", name, fs::g_tls_error); return false; @@ -400,7 +405,7 @@ bool extract_tar(const std::string& file_path, const std::string& dir_path, fs:: tar_object tar(vec.empty() ? file : vec[2]); - const bool ok = tar.extract("/tar_extract"); + const bool ok = tar.extract("/tar_extract", true); if (ok) { diff --git a/rpcs3/Loader/TAR.h b/rpcs3/Loader/TAR.h index 3e06e1f630..6a60734297 100644 --- a/rpcs3/Loader/TAR.h +++ b/rpcs3/Loader/TAR.h @@ -40,9 +40,9 @@ public: using process_func = std::function&&)>; - // Extract all files in archive to destination as VFS + // Extract all files in archive to destination (as VFS if is_vfs is true) // Allow to optionally specify explicit mount point (which may be directory meant for extraction) - bool extract(std::string vfs_mp = {}); + bool extract(std::string prefix_path = {}, bool is_vfs = false); static std::vector save_directory(const std::string& src_dir, std::vector&& init = std::vector{}, const process_func& func = {}, std::string append_path = {}); }; diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index e1e3a23de3..c1c2f50e6b 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -63,13 +63,13 @@ void headless_application::InitializeCallbacks() RequestCallFromMainThread(std::move(func), wake_up); }; - callbacks.init_gs_render = []() + callbacks.init_gs_render = [](utils::serial* ar) { switch (const video_renderer type = g_cfg.video.renderer) { case video_renderer::null: { - g_fxo->init>(); + g_fxo->init>(ar); break; } case video_renderer::opengl: diff --git a/rpcs3/main.cpp b/rpcs3/main.cpp index 038b8e8d81..e2ee51176f 100644 --- a/rpcs3/main.cpp +++ b/rpcs3/main.cpp @@ -81,6 +81,7 @@ static atomic_t s_no_gui = false; static atomic_t s_argv0; extern thread_local std::string(*g_tls_log_prefix)(); +extern thread_local std::string_view g_tls_serialize_name; #ifndef _WIN32 extern char **environ; @@ -103,6 +104,17 @@ LOG_CHANNEL(q_debug, "QDEBUG"); fmt::append(buf, "\n\nThread id = %s.", std::this_thread::get_id()); } + if (!g_tls_serialize_name.empty()) + { + // Copy only when needed + if (!buf.empty()) + { + buf = std::string(_text); + } + + fmt::append(buf, "\nSerialized Object: %s", g_tls_serialize_name); + } + std::string_view text = buf.empty() ? _text : buf; if (s_headless) @@ -263,6 +275,7 @@ constexpr auto arg_updating = "updating"; constexpr auto arg_user_id = "user-id"; constexpr auto arg_installfw = "installfw"; constexpr auto arg_installpkg = "installpkg"; +constexpr auto arg_savestate = "savestate"; constexpr auto arg_timer = "high-res-timer"; constexpr auto arg_verbose_curl = "verbose-curl"; constexpr auto arg_any_location = "allow-any-location"; @@ -388,7 +401,6 @@ void fmt_class_stringexec(); } diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index ff61055cb2..b5b0034873 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -27,6 +27,7 @@ #endif #include // This shouldn't be outside rpcs3qt... +#include LOG_CHANNEL(sys_log, "SYS"); @@ -54,12 +55,12 @@ EmuCallbacks main_application::CreateCallbacks() { case keyboard_handler::null: { - g_fxo->init(); + g_fxo->init(Emu.DeserialManager()); break; } case keyboard_handler::basic: { - basic_keyboard_handler* ret = g_fxo->init(); + basic_keyboard_handler* ret = g_fxo->init(Emu.DeserialManager()); ret->moveToThread(get_thread()); ret->SetTargetWindow(m_game_window); break; @@ -75,18 +76,18 @@ EmuCallbacks main_application::CreateCallbacks() { if (g_cfg.io.move == move_handler::mouse) { - basic_mouse_handler* ret = g_fxo->init(); + basic_mouse_handler* ret = g_fxo->init(Emu.DeserialManager()); ret->moveToThread(get_thread()); ret->SetTargetWindow(m_game_window); } else - g_fxo->init(); + g_fxo->init(Emu.DeserialManager()); break; } case mouse_handler::basic: { - basic_mouse_handler* ret = g_fxo->init(); + basic_mouse_handler* ret = g_fxo->init(Emu.DeserialManager()); ret->moveToThread(get_thread()); ret->SetTargetWindow(m_game_window); break; @@ -96,7 +97,8 @@ EmuCallbacks main_application::CreateCallbacks() callbacks.init_pad_handler = [this](std::string_view title_id) { - g_fxo->init>(get_thread(), m_game_window, title_id); + ensure(g_fxo->init>(get_thread(), m_game_window, title_id)); + while (pad::g_reset) std::this_thread::yield(); }; callbacks.get_audio = []() -> std::shared_ptr diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index bc4ee6cafb..41e30ad10e 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -971,6 +971,18 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) }); } + if (const std::string sstate = fs::get_cache_dir() + "/savestates/" + current_game.serial + ".SAVESTAT"; fs::is_file(sstate)) + { + QAction* boot_state = menu.addAction(is_current_running_game + ? tr("&Reboot with savestate") + : tr("&Boot with savestate")); + connect(boot_state, &QAction::triggered, [this, gameinfo, sstate] + { + sys_log.notice("Booting savestate from gamelist per context menu..."); + Q_EMIT RequestBoot(gameinfo, cfg_mode::custom, "", sstate); + }); + } + menu.addSeparator(); QAction* configure = menu.addAction(gameinfo->hasCustomConfig diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 99cddd0200..3dd1f93d8f 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -85,7 +85,7 @@ private Q_SLOTS: Q_SIGNALS: void GameListFrameClosed(); void NotifyGameSelection(const game_info& game); - void RequestBoot(const game_info& game, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = ""); + void RequestBoot(const game_info& game, cfg_mode config_mode = cfg_mode::custom, const std::string& config_path = "", const std::string& savestate = ""); void RequestIconSizeChange(const int& val); void NotifyEmuSettingsChange(); protected: diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 62f2ab9132..1c0127e1b2 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -245,6 +245,13 @@ void gs_frame::keyPressEvent(QKeyEvent *keyEvent) return; } break; + case Qt::Key_S: + if (keyEvent->modifiers() == Qt::ControlModifier && !m_disable_kb_hotkeys) + { + Emu.Restart(true); + return; + } + break; case Qt::Key_R: if (keyEvent->modifiers() == Qt::ControlModifier && !m_disable_kb_hotkeys) { diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index dd8ac2fcd9..78deee696a 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -343,26 +343,26 @@ void gui_application::InitializeCallbacks() RequestCallFromMainThread(std::move(func), wake_up); }; - callbacks.init_gs_render = []() + callbacks.init_gs_render = [](utils::serial* ar) { switch (g_cfg.video.renderer.get()) { case video_renderer::null: { - g_fxo->init>(); + g_fxo->init>(ar); break; } case video_renderer::opengl: { #if not defined(__APPLE__) - g_fxo->init>(); + g_fxo->init>(ar); #endif break; } case video_renderer::vulkan: { #if defined(HAVE_VULKAN) - g_fxo->init>(); + g_fxo->init>(ar); #endif break; } diff --git a/rpcs3/rpcs3qt/kernel_explorer.cpp b/rpcs3/rpcs3qt/kernel_explorer.cpp index 21ad392125..b8b93d8b15 100644 --- a/rpcs3/rpcs3qt/kernel_explorer.cpp +++ b/rpcs3/rpcs3qt/kernel_explorer.cpp @@ -768,7 +768,7 @@ void kernel_explorer::update() decltype(rsx->display_buffers) dbs; decltype(rsx->zculls) zcs; { - std::lock_guard lock(rsx->sys_rsx_mtx); + reader_lock lock(rsx->sys_rsx_mtx); std::memcpy(&table, &rsx->iomap_table, sizeof(table)); std::memcpy(&dbs, rsx->display_buffers, sizeof(dbs)); std::memcpy(&zcs, &rsx->zculls, sizeof(zcs)); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 4ef4ffaf7e..018fd35961 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -357,6 +357,7 @@ void main_window::OnPlayOrPause() return; } + case system_state::starting: break; default: fmt::throw_exception("Unreachable"); } } @@ -387,6 +388,12 @@ void main_window::show_boot_error(game_boot_result status) case game_boot_result::unsupported_disc_type: message = tr("This disc type is not supported yet."); break; + case game_boot_result::savestate_corrupted: + message = tr("Savestate data is corrupted or it's not an RPCS3 savestate."); + break; + case game_boot_result::savestate_version_unsupported: + message = tr("Savestate versioning data differes from your RPCS3 build."); + break; case game_boot_result::firmware_missing: // Handled elsewhere case game_boot_result::no_errors: return; @@ -493,8 +500,7 @@ void main_window::BootTest() const QString file_path = QFileDialog::getOpenFileName(this, tr("Select (S)ELF To Boot"), path_tests, tr( "(S)ELF files (*.elf *.self);;" "ELF files (*.elf);;" - "SELF files (*.self);;" - "All files (*.*)"), + "SELF files (*.self);;"), Q_NULLPTR, QFileDialog::DontResolveSymlinks); if (file_path.isEmpty()) @@ -512,6 +518,36 @@ void main_window::BootTest() Boot(path, "", true); } +void main_window::BootSavestate() +{ + bool stopped = false; + + if (Emu.IsRunning()) + { + Emu.Pause(); + stopped = true; + } + + const QString file_path = QFileDialog::getOpenFileName(this, tr("Select Savestate To Boot"), qstr(fs::get_cache_dir() + "/savestates/"), tr( + "Savestate files (*.SAVESTAT);;" + "All files (*.*)"), + Q_NULLPTR, QFileDialog::DontResolveSymlinks); + + if (file_path.isEmpty()) + { + if (stopped) + { + Emu.Resume(); + } + return; + } + + const std::string path = sstr(QFileInfo(file_path).absoluteFilePath()); + + gui_log.notice("Booting from BootSavestate..."); + Boot(path, "", true); +} + void main_window::BootGame() { bool stopped = false; @@ -1153,7 +1189,7 @@ void main_window::HandlePupInstallation(const QString& file_path, const QString& return; } - if (!update_files.extract("/pup_extract")) + if (!update_files.extract("/pup_extract", true)) { gui_log.error("Error while installing firmware: TAR contents are invalid."); critical(tr("Firmware installation failed: Firmware contents could not be extracted.")); @@ -1413,7 +1449,7 @@ void main_window::RepaintThumbnailIcons() m_icon_thumb_stop = icon(":/Icons/stop.png"); m_icon_thumb_restart = icon(":/Icons/restart.png"); - m_thumb_playPause->setIcon(Emu.IsRunning() ? m_icon_thumb_pause : m_icon_thumb_play); + m_thumb_playPause->setIcon(Emu.IsRunning() || Emu.IsStarting() ? m_icon_thumb_pause : m_icon_thumb_play); m_thumb_stop->setIcon(m_icon_thumb_stop); m_thumb_restart->setIcon(m_icon_thumb_restart); #endif @@ -1983,6 +2019,8 @@ void main_window::CreateConnects() g_user_asked_for_frame_capture = true; }); + connect(ui->bootSavestateAct, &QAction::triggered, this, &main_window::BootSavestate); + connect(ui->addGamesAct, &QAction::triggered, this, [this]() { if (!m_gui_settings->GetBootConfirmation(this)) @@ -2577,9 +2615,9 @@ void main_window::CreateDockWindows() m_selected_game = game; }); - connect(m_game_list_frame, &game_list_frame::RequestBoot, this, [this](const game_info& game, cfg_mode config_mode, const std::string& config_path) + connect(m_game_list_frame, &game_list_frame::RequestBoot, this, [this](const game_info& game, cfg_mode config_mode, const std::string& config_path, const std::string& savestate) { - Boot(game->info.path, game->info.serial, false, false, config_mode, config_path); + Boot(savestate.empty() ? game->info.path : savestate, game->info.serial, false, false, config_mode, config_path); }); connect(m_game_list_frame, &game_list_frame::NotifyEmuSettingsChange, this, &main_window::NotifyEmuSettingsChange); diff --git a/rpcs3/rpcs3qt/main_window.h b/rpcs3/rpcs3qt/main_window.h index 1dbaca6d96..16b15b8978 100644 --- a/rpcs3/rpcs3qt/main_window.h +++ b/rpcs3/rpcs3qt/main_window.h @@ -113,6 +113,7 @@ private Q_SLOTS: void BootTest(); void BootGame(); void BootVSH(); + void BootSavestate(); void BootRsxCapture(std::string path = ""); void DecryptSPRXLibraries(); static void show_boot_error(game_boot_result status); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 14b25a5a8f..598e9398a3 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -202,6 +202,7 @@ + @@ -426,6 +427,11 @@ Boot Game + + + Boot Savestate + + Install Packages/Raps/Edats diff --git a/rpcs3/rpcs3qt/memory_string_searcher.h b/rpcs3/rpcs3qt/memory_string_searcher.h index e499d0119f..c30d6d6572 100644 --- a/rpcs3/rpcs3qt/memory_string_searcher.h +++ b/rpcs3/rpcs3qt/memory_string_searcher.h @@ -42,6 +42,7 @@ struct memory_searcher_handle static constexpr u32 id_base = 1; static constexpr u32 id_step = 1; static constexpr u32 id_count = 2048; + SAVESTATE_INIT_POS(32); // Of course not really used template requires (std::is_constructible_v) memory_searcher_handle(Args&&... args) diff --git a/rpcs3/rpcs3qt/memory_viewer_panel.h b/rpcs3/rpcs3qt/memory_viewer_panel.h index 403ea463e2..c9eef2f452 100644 --- a/rpcs3/rpcs3qt/memory_viewer_panel.h +++ b/rpcs3/rpcs3qt/memory_viewer_panel.h @@ -86,6 +86,7 @@ struct memory_viewer_handle static constexpr u32 id_base = 1; static constexpr u32 id_step = 1; static constexpr u32 id_count = 2048; + SAVESTATE_INIT_POS(33); // Of course not really used template requires (std::is_constructible_v) memory_viewer_handle(Args&&... args) diff --git a/rpcs3/util/atomic.hpp b/rpcs3/util/atomic.hpp index 711b669ae6..2047cd77fa 100644 --- a/rpcs3/util/atomic.hpp +++ b/rpcs3/util/atomic.hpp @@ -1214,7 +1214,7 @@ protected: public: static constexpr usz align = Align; - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; atomic_t() noexcept = default; diff --git a/rpcs3/util/endian.hpp b/rpcs3/util/endian.hpp index fab5fdc78a..b9d1b19d0c 100644 --- a/rpcs3/util/endian.hpp +++ b/rpcs3/util/endian.hpp @@ -180,7 +180,7 @@ namespace stx using under = decltype(int_or_enum()); public: - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; se_t() noexcept = default; diff --git a/rpcs3/util/fixed_typemap.hpp b/rpcs3/util/fixed_typemap.hpp index 850868f134..28f8e9ca04 100644 --- a/rpcs3/util/fixed_typemap.hpp +++ b/rpcs3/util/fixed_typemap.hpp @@ -7,9 +7,12 @@ #include #include +#include enum class thread_state : u32; +extern thread_local std::string_view g_tls_serialize_name; + namespace stx { // Simplified typemap with exactly one object of each used type, non-moveable. Initialized on init(). Destroyed on clear(). @@ -58,14 +61,32 @@ namespace stx // Save default constructor and destructor and optional joining operation struct typeinfo { - bool(*create)(uchar* ptr, manual_typemap&) noexcept = nullptr; + bool(*create)(uchar* ptr, manual_typemap&, utils::serial*, std::string_view) noexcept = nullptr; void(*stop)(void* ptr, thread_state) noexcept = nullptr; + void(*save)(void* ptr, utils::serial&) noexcept = nullptr; void(*destroy)(void* ptr) noexcept = nullptr; - std::string_view name{}; + std::string_view name; template - static bool call_ctor(uchar* ptr, manual_typemap& _this) noexcept + static bool call_ctor(uchar* ptr, manual_typemap& _this, utils::serial* ar, std::string_view name) noexcept { + if (ar) + { + if constexpr (std::is_constructible_v>) + { + g_tls_serialize_name = name; + new (ptr) T(_this, exact_t(*ar)); + return true; + } + + if constexpr (std::is_constructible_v>) + { + g_tls_serialize_name = name; + new (ptr) T(exact_t(*ar)); + return true; + } + } + // Allow passing reference to "this" if constexpr (std::is_constructible_v) { @@ -96,6 +117,19 @@ namespace stx *std::launder(static_cast(ptr)) = state; } +#ifdef _MSC_VER + template + static void call_save(void*, utils::serial&) noexcept + { + } +#endif + + template requires requires (T& a) { a.save(std::declval>()); } + static void call_save(void* ptr, utils::serial& ar) noexcept + { + std::launder(static_cast(ptr))->save(stx::exact_t(ar)); + } + template static typeinfo make_typeinfo() { @@ -110,6 +144,13 @@ namespace stx r.stop = &call_stop; } + // TODO: Unconnement and remove call_save overload when MSVC implements it +#ifndef _MSC_VER + if constexpr (!!(requires (T& a) { a.save(std::declval>()); })) +#endif + { + r.save = &call_save; + } #ifdef _MSC_VER constexpr std::string_view name = parse_type(__FUNCSIG__); #else @@ -181,15 +222,31 @@ namespace stx *m_info++ = nullptr; } - void init(bool reset = true) + void init(bool reset = true, utils::serial* ar = nullptr) { if (reset) { this->reset(); } + // Use unique_ptr to reduce header dependencies in this commonly used header + const auto order = std::make_unique*>[]>(stx::typelist().count()); + + usz pos = 0; for (const auto& type : stx::typelist()) { + order[pos++] = {type.init_pos(), std::addressof(type)}; + } + + std::stable_sort(order.get(), order.get() + stx::typelist().count(), [](auto a, auto b) + { + return a.first < b.first; + }); + + for (pos = 0; pos < stx::typelist().count(); pos++) + { + const auto& type = *order[pos].second; + const u32 id = type.index(); uchar* data = (Size ? +m_data : m_list) + type.pos(); @@ -199,13 +256,15 @@ namespace stx continue; } - if (type.create(data, *this)) + if (type.create(data, *this, ar, type.name)) { *m_order++ = data; *m_info++ = &type; m_init[id] = true; } } + + g_tls_serialize_name = {}; } void clear() @@ -262,6 +321,32 @@ namespace stx } } + void save(utils::serial& ar) + { + if (!m_init) + { + return; + } + + // Get actual number of created objects + u32 _max = 0; + + for (const auto& type : stx::typelist()) + { + if (m_init[type.index()]) + { + // Skip object if not created + _max++; + } + } + + // Save data in forward order + for (u32 i = _max; i; i--) + { + if (auto save = (*std::prev(m_info, i))->save) save(*std::prev(m_order, i), ar); + } + } + // Check if object is not initialized but shall be initialized first (to use in initializing other objects) template void need() noexcept @@ -294,6 +379,8 @@ namespace stx As* obj = nullptr; + g_tls_serialize_name = get_name(); + if constexpr (Size != 0) { obj = new (m_data + stx::typeoffset>()) std::decay_t(std::forward(args)...); @@ -303,6 +390,8 @@ namespace stx obj = new (m_list + stx::typeoffset>()) std::decay_t(std::forward(args)...); } + g_tls_serialize_name = {}; + *m_order++ = obj; *m_info++ = &stx::typedata, std::decay_t>(); return obj; @@ -337,6 +426,12 @@ namespace stx } } + template + static std::string_view get_name() noexcept + { + return stx::typedata, std::decay_t>().name; + } + // Obtain object pointer if initialized template T* try_get() const noexcept diff --git a/rpcs3/util/serialization.hpp b/rpcs3/util/serialization.hpp index 12623db633..d6f48aeda3 100644 --- a/rpcs3/util/serialization.hpp +++ b/rpcs3/util/serialization.hpp @@ -3,21 +3,6 @@ #include "util/types.hpp" #include -namespace stx -{ - template - struct exact_t - { - T obj; - - exact_t(T&& _obj) : obj(std::forward(_obj)) {} - - // TODO: More conversions - template requires (std::is_same_v) - operator U&() const { return obj; }; - }; -} - namespace utils { template @@ -52,6 +37,10 @@ namespace utils std::vector data; usz pos = umax; + serial() = default; + serial(const serial&) = delete; + ~serial() = default; + // Checks if this instance is currently used for serialization bool is_writing() const { @@ -299,7 +288,7 @@ namespace utils } // Convert serialization manager to deserializion manager (can't go the other way) - // If no arg provided reuse saved buffer + // If no arg is provided reuse saved buffer void set_reading_state(std::vector&& _data = std::vector{}) { if (!_data.empty()) diff --git a/rpcs3/util/typeindices.hpp b/rpcs3/util/typeindices.hpp index 7dcac5950b..4f8e907362 100644 --- a/rpcs3/util/typeindices.hpp +++ b/rpcs3/util/typeindices.hpp @@ -3,6 +3,8 @@ #include "util/types.hpp" #include "util/shared_ptr.hpp" +#include + #ifndef _MSC_VER #define ATTR_PURE __attribute__((pure)) #else @@ -24,6 +26,7 @@ namespace stx u32 size = 1; u32 align = 1; u32 begin = 0; + double order; // Next typeinfo in linked list type_info* next = nullptr; @@ -31,7 +34,7 @@ namespace stx // Auxiliary pointer to base type const type_info* base = nullptr; - type_info(Info info, u32 size, u32 align, const type_info* base = nullptr) noexcept; + type_info(Info info, u32 size, u32 align, double order, const type_info* base = nullptr) noexcept; friend type_counter; @@ -55,6 +58,11 @@ namespace stx { return begin + size; } + + ATTR_PURE double init_pos() const + { + return order; + } }; // Class for automatic type registration for given Info type @@ -177,14 +185,25 @@ namespace stx return typelist_v; } + template requires requires () { T::savestate_init_pos + 0.; } + constexpr double get_savestate_init_pos() + { + return T::savestate_init_pos; + } + template requires (!(requires () { T::savestate_init_pos + 0.; })) + constexpr double get_savestate_init_pos() + { + return {}; + } + template template - const type_info type_counter::type{Info::template make_typeinfo(), sizeof(T), alignof(T)}; + const type_info type_counter::type{Info::template make_typeinfo(), sizeof(T), alignof(T), get_savestate_init_pos()}; template template - const type_info type_counter::dyn_type{Info::template make_typeinfo(), sizeof(As), alignof(As), &type_counter::template type}; + const type_info type_counter::dyn_type{Info::template make_typeinfo(), sizeof(As), alignof(As), get_savestate_init_pos(), &type_counter::template type}; template - type_info::type_info(Info info, u32 _size, u32 _align, const type_info* cbase) noexcept + type_info::type_info(Info info, u32 _size, u32 _align, double order, const type_info* cbase) noexcept : Info(info) { auto& tl = typelist(); @@ -193,6 +212,7 @@ namespace stx this->size = _size > this->size ? _size : this->size; this->align = _align > this->align ? _align : this->align; this->base = cbase; + this->order = order; // Update global max alignment tl.first.align = _align > tl.first.align ? _align : tl.first.align; diff --git a/rpcs3/util/types.hpp b/rpcs3/util/types.hpp index dd2e270c7f..5052970e18 100644 --- a/rpcs3/util/types.hpp +++ b/rpcs3/util/types.hpp @@ -1081,6 +1081,21 @@ constexpr bool is_same_ptr(const volatile Y* ptr) template concept PtrSame = (is_same_ptr()); +namespace stx +{ + template + struct exact_t + { + T obj; + + exact_t(T&& _obj) : obj(std::forward(_obj)) {} + + // TODO: More conversions + template requires (std::is_same_v) + operator U&() const { return obj; }; + }; +} + namespace utils { struct serial; @@ -1088,3 +1103,24 @@ namespace utils template extern bool serialize(utils::serial& ar, T& obj); + +#define USING_SERIALIZATION_VERSION(name) []()\ +{\ + extern void using_##name##_serialization();\ + using_##name##_serialization();\ +}() + +#define USING_SERIALIZATION_VERSION_COND(cond, name) [&]()\ +{\ + extern void using_##name##_serialization();\ + if (static_cast(cond)) using_##name##_serialization();\ +}() + +#define GET_SERIALIZATION_VERSION(name) []()\ +{\ + extern u32 get_##name##_serialization_version();\ + return get_##name##_serialization_version();\ +}() + +#define ENABLE_BITWISE_SERIALIZATION using enable_bitcopy = std::true_type; +#define SAVESTATE_INIT_POS(x) static constexpr double savestate_init_pos = (x) diff --git a/rpcs3/util/v128.hpp b/rpcs3/util/v128.hpp index 8ea3b335b0..75036e80f1 100644 --- a/rpcs3/util/v128.hpp +++ b/rpcs3/util/v128.hpp @@ -79,7 +79,7 @@ union alignas(16) v128 return std::bit_cast(*this); } - using enable_bitcopy = std::true_type; + ENABLE_BITWISE_SERIALIZATION; static v128 from64(u64 _0, u64 _1 = 0) { diff --git a/rpcs3/util/vm.hpp b/rpcs3/util/vm.hpp index ac00126ba2..3c636fc8e9 100644 --- a/rpcs3/util/vm.hpp +++ b/rpcs3/util/vm.hpp @@ -73,7 +73,7 @@ namespace utils atomic_t m_ptr{nullptr}; public: - explicit shm(u32 size, u32 flags = 0); + explicit shm(u64 size, u32 flags = 0); // Construct with specified path as sparse file storage shm(u64 size, const std::string& storage); diff --git a/rpcs3/util/vm_native.cpp b/rpcs3/util/vm_native.cpp index c08a9397a4..55677b59c6 100644 --- a/rpcs3/util/vm_native.cpp +++ b/rpcs3/util/vm_native.cpp @@ -395,8 +395,8 @@ namespace utils void memory_release(void* pointer, usz size) { #ifdef _WIN32 - ensure(::VirtualFree(pointer, 0, MEM_RELEASE)); unmap_mappping_memory(reinterpret_cast(pointer), size); + ensure(::VirtualFree(pointer, 0, MEM_RELEASE)); #else ensure(::munmap(pointer, size) != -1); #endif @@ -457,7 +457,7 @@ namespace utils #endif } - shm::shm(u32 size, u32 flags) + shm::shm(u64 size, u32 flags) : m_flags(flags) , m_size(utils::align(size, 0x10000)) {