mirror of
https://github.com/RPCSX/rpcsx.git
synced 2026-04-04 14:08:37 +00:00
Savestates Support For PS3 Emulation (#10478)
This commit is contained in:
parent
969b9eb89d
commit
fcd297ffb2
154 changed files with 4948 additions and 635 deletions
|
|
@ -13,13 +13,17 @@
|
|||
#include "Emu/Cell/SPURecompiler.h"
|
||||
#include "Emu/perf_meter.hpp"
|
||||
#include <deque>
|
||||
#include <span>
|
||||
|
||||
#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<rsx::thread>(); g_fxo->is_init<rsx::thread>())
|
||||
if (auto& rsxthr = g_fxo->get<rsx::thread>(); !Emu.IsPaused() && g_fxo->is_init<rsx::thread>())
|
||||
{
|
||||
rsxthr.on_notify_memory_unmapped(addr, size);
|
||||
}
|
||||
|
|
@ -1145,8 +1149,14 @@ namespace vm
|
|||
return flags;
|
||||
}
|
||||
|
||||
static u64 init_block_id()
|
||||
{
|
||||
static atomic_t<u64> s_id = 1;
|
||||
return s_id++;
|
||||
}
|
||||
|
||||
block_t::block_t(u32 addr, u32 size, u64 flags)
|
||||
: m_id([](){ static atomic_t<u64> 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<std::pair<utils::shm*, u32>>& 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<utils::shm>& 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<const v128*>(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<usz>(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<usz>(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<utils::shm*, usz>& 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<const u8>(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<std::shared_ptr<utils::shm>>& shared)
|
||||
: m_id(init_block_id())
|
||||
, addr(ar)
|
||||
, size(ar)
|
||||
, flags(ar)
|
||||
{
|
||||
if (flags & preallocated)
|
||||
{
|
||||
m_common = std::make_shared<utils::shm>(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<utils::shm> 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<u8>(addr0 + guard_size), size0 - guard_size * 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _unmap_block(const std::shared_ptr<block_t>& 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<std::pair<utils::shm*, u32>> 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<utils::shm*, usz> shared_map;
|
||||
|
||||
for (auto& p : shared)
|
||||
{
|
||||
shared_map.emplace(p.first, &p - shared.data());
|
||||
}
|
||||
|
||||
// TODO: proper serialization of std::map
|
||||
ar(static_cast<usz>(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<u8>(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<std::shared_ptr<utils::shm>> 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<utils::shm>(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<block_t>(ar, shared);
|
||||
}
|
||||
}
|
||||
|
||||
g_range_lock = 0;
|
||||
}
|
||||
|
||||
u32 get_shm_addr(const std::shared_ptr<utils::shm>& shared)
|
||||
{
|
||||
for (auto& loc : g_locations)
|
||||
{
|
||||
if (u32 addr = loc ? loc->get_shm_addr(shared) : 0)
|
||||
{
|
||||
return addr;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void fmt_class_string<vm::_ptr_base<const void, u32>>::format(std::string& out, u64 arg)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue