Merge a277d66c50
into 2c83a256ae
This commit is contained in:
commit
52401d6808
|
@ -22,6 +22,7 @@ struct WindowsMemoryFunctions
|
|||
void* m_address_UnmapViewOfFileEx = nullptr;
|
||||
void* m_address_VirtualAlloc2 = nullptr;
|
||||
void* m_address_MapViewOfFile3 = nullptr;
|
||||
void* m_address_VirtualProtect = nullptr;
|
||||
};
|
||||
#endif
|
||||
|
||||
|
@ -111,6 +112,15 @@ public:
|
|||
///
|
||||
void UnmapFromMemoryRegion(void* view, size_t size);
|
||||
|
||||
///
|
||||
/// Virtual protect a section from the memory region previously mapped by CreateView.
|
||||
///
|
||||
/// @param data Pointer to data to protect.
|
||||
/// @param size Size of the protection.
|
||||
/// @param flag What new permission to protect with.
|
||||
///
|
||||
bool VirtualProtectMemoryRegion(u8* data, size_t size, u64 flag);
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
WindowsMemoryRegion* EnsureSplitRegionForMapping(void* address, size_t size);
|
||||
|
|
|
@ -34,6 +34,9 @@ using PMapViewOfFile3 = PVOID(WINAPI*)(HANDLE FileMapping, HANDLE Process, PVOID
|
|||
|
||||
using PUnmapViewOfFileEx = BOOL(WINAPI*)(PVOID BaseAddress, ULONG UnmapFlags);
|
||||
|
||||
using PVirtualProtect = BOOL(WINAPI*)(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect,
|
||||
PDWORD lpflOldProtect);
|
||||
|
||||
using PIsApiSetImplemented = BOOL(APIENTRY*)(PCSTR Contract);
|
||||
|
||||
namespace Common
|
||||
|
@ -78,11 +81,14 @@ static bool InitWindowsMemoryFunctions(WindowsMemoryFunctions* functions)
|
|||
functions->m_api_ms_win_core_memory_l1_1_6_handle.GetSymbolAddress("MapViewOfFile3FromApp");
|
||||
void* const address_UnmapViewOfFileEx =
|
||||
functions->m_kernel32_handle.GetSymbolAddress("UnmapViewOfFileEx");
|
||||
void* const address_VirtualProtect =
|
||||
functions->m_kernel32_handle.GetSymbolAddress("VirtualProtect");
|
||||
if (address_VirtualAlloc2 && address_MapViewOfFile3 && address_UnmapViewOfFileEx)
|
||||
{
|
||||
functions->m_address_VirtualAlloc2 = address_VirtualAlloc2;
|
||||
functions->m_address_MapViewOfFile3 = address_MapViewOfFile3;
|
||||
functions->m_address_UnmapViewOfFileEx = address_UnmapViewOfFileEx;
|
||||
functions->m_address_VirtualProtect = address_VirtualProtect;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -209,6 +215,13 @@ void MemArena::ReleaseMemoryRegion()
|
|||
}
|
||||
}
|
||||
|
||||
bool MemArena::VirtualProtectMemoryRegion(u8* data, size_t size, u64 flag)
|
||||
{
|
||||
DWORD lpflOldProtect = 0;
|
||||
return static_cast<PVirtualProtect>(m_memory_functions.m_address_VirtualProtect)(
|
||||
data, size, flag, &lpflOldProtect);
|
||||
}
|
||||
|
||||
WindowsMemoryRegion* MemArena::EnsureSplitRegionForMapping(void* start_address, size_t size)
|
||||
{
|
||||
u8* const address = static_cast<u8*>(start_address);
|
||||
|
|
|
@ -289,4 +289,15 @@ size_t MemPhysical()
|
|||
#endif
|
||||
}
|
||||
|
||||
size_t PageSize()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SYSTEM_INFO sysInfo;
|
||||
GetSystemInfo(&sysInfo);
|
||||
return sysInfo.dwPageSize;
|
||||
#else
|
||||
return sysconf(_SC_PAGESIZE);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -34,5 +34,6 @@ bool ReadProtectMemory(void* ptr, size_t size);
|
|||
bool WriteProtectMemory(void* ptr, size_t size, bool executable = false);
|
||||
bool UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute = false);
|
||||
size_t MemPhysical();
|
||||
size_t PageSize();
|
||||
|
||||
} // namespace Common
|
||||
|
|
|
@ -75,6 +75,7 @@
|
|||
#include "Core/State.h"
|
||||
#include "Core/System.h"
|
||||
#include "Core/WiiRoot.h"
|
||||
#include "Core/HW/Memmap.h"
|
||||
|
||||
#ifdef USE_MEMORYWATCHER
|
||||
#include "Core/MemoryWatcher.h"
|
||||
|
@ -385,8 +386,9 @@ static void CpuThread(Core::System& system, const std::optional<std::string>& sa
|
|||
// The JIT need to be able to intercept faults, both for fastmem and for the BLR optimization.
|
||||
const bool exception_handler = EMM::IsExceptionHandlerSupported();
|
||||
if (exception_handler)
|
||||
{
|
||||
EMM::InstallExceptionHandler();
|
||||
|
||||
}
|
||||
#ifdef USE_MEMORYWATCHER
|
||||
s_memory_watcher = std::make_unique<MemoryWatcher>();
|
||||
#endif
|
||||
|
@ -689,6 +691,9 @@ static void EmuThread(Core::System& system, std::unique_ptr<BootParameters> boot
|
|||
cpuThreadFunc(system, savestate_path, delete_savestate);
|
||||
}
|
||||
|
||||
if (cpuThreadFunc == CpuThread)
|
||||
system.GetMemory().InitDirtyPages();
|
||||
|
||||
INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "Stopping GDB ..."));
|
||||
GDBStub::Deinit();
|
||||
INFO_LOG_FMT(CONSOLE, "{}", StopMessage(true, "GDB stopped."));
|
||||
|
|
|
@ -82,9 +82,9 @@ void Shutdown(Core::System& system)
|
|||
system.GetCoreTiming().Shutdown();
|
||||
}
|
||||
|
||||
void DoState(Core::System& system, PointerWrap& p)
|
||||
void DoState(Core::System& system, PointerWrap& p, bool delta)
|
||||
{
|
||||
system.GetMemory().DoState(p);
|
||||
system.GetMemory().DoState(p, delta);
|
||||
p.DoMarker("Memory");
|
||||
system.GetMemoryInterface().DoState(p);
|
||||
p.DoMarker("MemoryInterface");
|
||||
|
|
|
@ -14,5 +14,5 @@ namespace HW
|
|||
{
|
||||
void Init(Core::System& system, const Sram* override_sram);
|
||||
void Shutdown(Core::System& system);
|
||||
void DoState(Core::System& system, PointerWrap& p);
|
||||
void DoState(Core::System& system, PointerWrap& p, bool delta);
|
||||
} // namespace HW
|
||||
|
|
|
@ -47,6 +47,57 @@ MemoryManager::MemoryManager(Core::System& system) : m_system(system)
|
|||
|
||||
MemoryManager::~MemoryManager() = default;
|
||||
|
||||
u64 MemoryManager::GetDirtyPageIndexFromAddress(u64 address)
|
||||
{
|
||||
const size_t page_size = Common::PageSize();
|
||||
const size_t page_mask = page_size - 1;
|
||||
return address & ~page_mask;
|
||||
}
|
||||
|
||||
bool MemoryManager::HandleFault(uintptr_t fault_address)
|
||||
{
|
||||
u8* fault_address_bytes = reinterpret_cast<u8*>(fault_address);
|
||||
if (!IsAddressInEmulatedMemory(fault_address_bytes) || IsPageDirty(fault_address))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SetPageDirtyBit(fault_address, 0x1, true);
|
||||
bool change_protection =
|
||||
m_arena.VirtualProtectMemoryRegion(fault_address_bytes, 0x1, PAGE_READWRITE);
|
||||
|
||||
if (!change_protection)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MemoryManager::WriteProtectPhysicalMemoryRegions()
|
||||
{
|
||||
for (const PhysicalMemoryRegion& region : m_physical_regions)
|
||||
{
|
||||
if (!region.active || !region.track)
|
||||
continue;
|
||||
|
||||
bool change_protection =
|
||||
m_arena.VirtualProtectMemoryRegion((*region.out_pointer), region.size, PAGE_READONLY);
|
||||
|
||||
if (!change_protection)
|
||||
{
|
||||
PanicAlertFmt("Memory::WriteProtectPhysicalMemoryRegions(): Failed to write protect for "
|
||||
"this block of memory at 0x{:08X}.",
|
||||
reinterpret_cast<u64>(*region.out_pointer));
|
||||
}
|
||||
const size_t page_size = Common::PageSize();
|
||||
const intptr_t out_pointer = reinterpret_cast<intptr_t>(*region.out_pointer);
|
||||
for (size_t i = out_pointer; i < region.size; i += page_size)
|
||||
{
|
||||
m_dirty_pages[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryManager::InitMMIO(bool is_wii)
|
||||
{
|
||||
m_mmio_mapping = std::make_unique<MMIO::Mapping>();
|
||||
|
@ -71,6 +122,11 @@ void MemoryManager::InitMMIO(bool is_wii)
|
|||
}
|
||||
}
|
||||
|
||||
void MemoryManager::InitDirtyPages()
|
||||
{
|
||||
WriteProtectPhysicalMemoryRegions();
|
||||
}
|
||||
|
||||
void MemoryManager::Init()
|
||||
{
|
||||
const auto get_mem1_size = [] {
|
||||
|
@ -95,13 +151,14 @@ void MemoryManager::Init()
|
|||
m_exram_mask = GetExRamSize() - 1;
|
||||
|
||||
m_physical_regions[0] = PhysicalMemoryRegion{
|
||||
&m_ram, 0x00000000, GetRamSize(), PhysicalMemoryRegion::ALWAYS, 0, false};
|
||||
&m_ram, 0x00000000, GetRamSize(), PhysicalMemoryRegion::ALWAYS, 0, false, true};
|
||||
m_physical_regions[1] = PhysicalMemoryRegion{
|
||||
&m_l1_cache, 0xE0000000, GetL1CacheSize(), PhysicalMemoryRegion::ALWAYS, 0, false};
|
||||
&m_l1_cache, 0xE0000000, GetL1CacheSize(), PhysicalMemoryRegion::ALWAYS, 0, false, false};
|
||||
m_physical_regions[2] = PhysicalMemoryRegion{
|
||||
&m_fake_vmem, 0x7E000000, GetFakeVMemSize(), PhysicalMemoryRegion::FAKE_VMEM, 0, false};
|
||||
&m_fake_vmem, 0x7E000000, GetFakeVMemSize(), PhysicalMemoryRegion::FAKE_VMEM, 0,
|
||||
false, false};
|
||||
m_physical_regions[3] = PhysicalMemoryRegion{
|
||||
&m_exram, 0x10000000, GetExRamSize(), PhysicalMemoryRegion::WII_ONLY, 0, false};
|
||||
&m_exram, 0x10000000, GetExRamSize(), PhysicalMemoryRegion::WII_ONLY, 0, false, true};
|
||||
|
||||
const bool wii = m_system.IsWii();
|
||||
const bool mmu = m_system.IsMMUMode();
|
||||
|
@ -164,6 +221,21 @@ bool MemoryManager::IsAddressInFastmemArea(const u8* address) const
|
|||
return address >= m_fastmem_arena && address < m_fastmem_arena + m_fastmem_arena_size;
|
||||
}
|
||||
|
||||
bool MemoryManager::IsAddressInEmulatedMemory(const u8* address) const
|
||||
{
|
||||
for (const PhysicalMemoryRegion& region : m_physical_regions)
|
||||
{
|
||||
if (!region.active || !region.track)
|
||||
continue;
|
||||
|
||||
if (address >= *region.out_pointer && address < *region.out_pointer + region.size)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MemoryManager::InitFastmemArena()
|
||||
{
|
||||
// Here we set up memory mappings for fastmem. The basic idea of fastmem is that we reserve 4 GiB
|
||||
|
@ -290,7 +362,7 @@ void MemoryManager::UpdateLogicalMemory(const PowerPC::BatTable& dbat_table)
|
|||
}
|
||||
}
|
||||
|
||||
void MemoryManager::DoState(PointerWrap& p)
|
||||
void MemoryManager::DoState(PointerWrap& p, bool delta)
|
||||
{
|
||||
const u32 current_ram_size = GetRamSize();
|
||||
const u32 current_l1_cache_size = GetL1CacheSize();
|
||||
|
@ -327,28 +399,60 @@ void MemoryManager::DoState(PointerWrap& p)
|
|||
p.SetVerifyMode();
|
||||
return;
|
||||
}
|
||||
|
||||
p.DoArray(m_ram, current_ram_size);
|
||||
p.DoArray(m_l1_cache, current_l1_cache_size);
|
||||
p.DoMarker("Memory RAM");
|
||||
if (current_have_fake_vmem)
|
||||
p.DoArray(m_fake_vmem, current_fake_vmem_size);
|
||||
p.DoMarker("Memory FakeVMEM");
|
||||
if (current_have_exram)
|
||||
p.DoArray(m_exram, current_exram_size);
|
||||
p.DoMarker("Memory EXRAM");
|
||||
if (delta)
|
||||
{
|
||||
const u32 page_size = static_cast<u32>(Common::PageSize());
|
||||
p.Do(m_dirty_pages);
|
||||
for (size_t i = 0; i < current_ram_size; i += page_size)
|
||||
{
|
||||
if (IsPageDirty(reinterpret_cast<uintptr_t>(&m_ram[i])))
|
||||
{
|
||||
p.DoArray(m_ram + i, page_size);
|
||||
}
|
||||
}
|
||||
p.DoArray(m_l1_cache, current_l1_cache_size);
|
||||
p.DoMarker("Memory RAM");
|
||||
if (current_have_fake_vmem)
|
||||
{
|
||||
p.DoArray(m_fake_vmem, current_fake_vmem_size);
|
||||
}
|
||||
p.DoMarker("Memory FakeVMEM");
|
||||
if (current_have_exram)
|
||||
{
|
||||
for (size_t i = 0; i < current_exram_size; i += page_size)
|
||||
{
|
||||
if (IsPageDirty(reinterpret_cast<uintptr_t>(&m_exram[i])))
|
||||
{
|
||||
p.DoArray(m_exram + i, page_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
p.DoMarker("Memory EXRAM");
|
||||
}
|
||||
else
|
||||
{
|
||||
p.DoArray(m_ram, current_ram_size);
|
||||
p.DoArray(m_l1_cache, current_l1_cache_size);
|
||||
p.DoMarker("Memory RAM");
|
||||
if (current_have_fake_vmem)
|
||||
p.DoArray(m_fake_vmem, current_fake_vmem_size);
|
||||
p.DoMarker("Memory FakeVMEM");
|
||||
if (current_have_exram)
|
||||
p.DoArray(m_exram, current_exram_size);
|
||||
p.DoMarker("Memory EXRAM");
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryManager::Shutdown()
|
||||
{
|
||||
ShutdownFastmemArena();
|
||||
m_dirty_pages.clear();
|
||||
|
||||
m_is_initialized = false;
|
||||
for (const PhysicalMemoryRegion& region : m_physical_regions)
|
||||
{
|
||||
if (!region.active)
|
||||
continue;
|
||||
|
||||
m_arena.ReleaseView(*region.out_pointer, region.size);
|
||||
*region.out_pointer = nullptr;
|
||||
}
|
||||
|
@ -573,4 +677,22 @@ void MemoryManager::Write_U64_Swap(u64 value, u32 address)
|
|||
CopyToEmu(address, &value, sizeof(value));
|
||||
}
|
||||
|
||||
bool MemoryManager::IsPageDirty(uintptr_t address)
|
||||
{
|
||||
return m_dirty_pages[GetDirtyPageIndexFromAddress(address)];
|
||||
}
|
||||
|
||||
void MemoryManager::SetPageDirtyBit(uintptr_t address, size_t size, bool dirty)
|
||||
{
|
||||
for (size_t i = 0; i < size; i++)
|
||||
{
|
||||
m_dirty_pages[GetDirtyPageIndexFromAddress(address + i)] = dirty;
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryManager::ResetDirtyPages()
|
||||
{
|
||||
WriteProtectPhysicalMemoryRegions();
|
||||
}
|
||||
|
||||
} // namespace Memory
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/MathUtil.h"
|
||||
|
@ -48,6 +49,7 @@ struct PhysicalMemoryRegion
|
|||
} flags;
|
||||
u32 shm_position;
|
||||
bool active;
|
||||
bool track;
|
||||
};
|
||||
|
||||
struct LogicalMemoryView
|
||||
|
@ -78,6 +80,7 @@ public:
|
|||
u32 GetExRamMask() const { return m_exram_mask; }
|
||||
|
||||
bool IsAddressInFastmemArea(const u8* address) const;
|
||||
bool IsAddressInEmulatedMemory(const u8* address) const;
|
||||
u8* GetPhysicalBase() const { return m_physical_base; }
|
||||
u8* GetLogicalBase() const { return m_logical_base; }
|
||||
u8* GetPhysicalPageMappingsBase() const { return m_physical_page_mappings_base; }
|
||||
|
@ -94,10 +97,11 @@ public:
|
|||
// Init and Shutdown
|
||||
bool IsInitialized() const { return m_is_initialized; }
|
||||
void Init();
|
||||
void InitDirtyPages();
|
||||
void Shutdown();
|
||||
bool InitFastmemArena();
|
||||
void ShutdownFastmemArena();
|
||||
void DoState(PointerWrap& p);
|
||||
void DoState(PointerWrap& p, bool delta);
|
||||
|
||||
void UpdateLogicalMemory(const PowerPC::BatTable& dbat_table);
|
||||
|
||||
|
@ -130,6 +134,13 @@ public:
|
|||
void Write_U32_Swap(u32 var, u32 address);
|
||||
void Write_U64_Swap(u64 var, u32 address);
|
||||
|
||||
bool IsPageDirty(uintptr_t address);
|
||||
void SetPageDirtyBit(uintptr_t address, size_t size, bool dirty);
|
||||
void ResetDirtyPages();
|
||||
bool HandleFault(uintptr_t fault_address);
|
||||
|
||||
std::map<u64, u8>& GetDirtyPages() { return m_dirty_pages; }
|
||||
|
||||
// Templated functions for byteswapped copies.
|
||||
template <typename T>
|
||||
void CopyFromEmuSwapped(T* data, u32 address, size_t size) const
|
||||
|
@ -254,6 +265,11 @@ private:
|
|||
|
||||
Core::System& m_system;
|
||||
|
||||
std::map<u64, u8> m_dirty_pages;
|
||||
|
||||
u64 GetDirtyPageIndexFromAddress(u64 address);
|
||||
void WriteProtectPhysicalMemoryRegions();
|
||||
|
||||
void InitMMIO(bool is_wii);
|
||||
};
|
||||
} // namespace Memory
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "Common/MsgHandler.h"
|
||||
#include "Common/Thread.h"
|
||||
|
||||
#include "Core/HW/Memmap.h"
|
||||
#include "Core/MachineContext.h"
|
||||
#include "Core/PowerPC/JitInterface.h"
|
||||
#include "Core/System.h"
|
||||
|
@ -24,6 +25,7 @@
|
|||
#ifndef _WIN32
|
||||
#include <unistd.h> // Needed for _POSIX_VERSION
|
||||
#endif
|
||||
#include <Common/MemoryUtil.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#ifdef _M_X86_64
|
||||
|
@ -60,8 +62,12 @@ static LONG NTAPI Handler(PEXCEPTION_POINTERS pPtrs)
|
|||
// virtual address of the inaccessible data
|
||||
uintptr_t fault_address = (uintptr_t)pPtrs->ExceptionRecord->ExceptionInformation[1];
|
||||
SContext* ctx = pPtrs->ContextRecord;
|
||||
|
||||
if (Core::System::GetInstance().GetJitInterface().HandleFault(fault_address, ctx))
|
||||
Core::System& system = Core::System::GetInstance();
|
||||
if (system.GetMemory().HandleFault(fault_address))
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
else if (system.GetJitInterface().HandleFault(fault_address, ctx))
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ void EnableCompression(bool compression)
|
|||
s_use_compression = compression;
|
||||
}
|
||||
|
||||
static void DoState(Core::System& system, PointerWrap& p)
|
||||
static void DoState(Core::System& system, PointerWrap& p, bool delta)
|
||||
{
|
||||
bool is_wii = system.IsWii() || system.IsMIOS();
|
||||
const bool is_wii_currently = is_wii;
|
||||
|
@ -188,7 +188,7 @@ static void DoState(Core::System& system, PointerWrap& p)
|
|||
p.DoMarker("CoreTiming");
|
||||
|
||||
// HW needs to be restored before PowerPC because the data cache might need to be flushed.
|
||||
HW::DoState(system, p);
|
||||
HW::DoState(system, p, delta);
|
||||
p.DoMarker("HW");
|
||||
|
||||
system.GetPowerPC().DoState(p);
|
||||
|
@ -224,7 +224,7 @@ void LoadFromBuffer(Core::System& system, std::vector<u8>& buffer)
|
|||
[&] {
|
||||
u8* ptr = buffer.data();
|
||||
PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Read);
|
||||
DoState(system, p);
|
||||
DoState(system, p, false);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
@ -237,13 +237,13 @@ void SaveToBuffer(Core::System& system, std::vector<u8>& buffer)
|
|||
u8* ptr = nullptr;
|
||||
PointerWrap p_measure(&ptr, 0, PointerWrap::Mode::Measure);
|
||||
|
||||
DoState(system, p_measure);
|
||||
DoState(system, p_measure, false);
|
||||
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
|
||||
buffer.resize(buffer_size);
|
||||
|
||||
ptr = buffer.data();
|
||||
PointerWrap p(&ptr, buffer_size, PointerWrap::Mode::Write);
|
||||
DoState(system, p);
|
||||
DoState(system, p, false);
|
||||
},
|
||||
true);
|
||||
}
|
||||
|
@ -486,7 +486,7 @@ void SaveAs(Core::System& system, const std::string& filename, bool wait)
|
|||
// Measure the size of the buffer.
|
||||
u8* ptr = nullptr;
|
||||
PointerWrap p_measure(&ptr, 0, PointerWrap::Mode::Measure);
|
||||
DoState(system, p_measure);
|
||||
DoState(system, p_measure, false);
|
||||
const size_t buffer_size = reinterpret_cast<size_t>(ptr);
|
||||
|
||||
// Then actually do the write.
|
||||
|
@ -494,7 +494,7 @@ void SaveAs(Core::System& system, const std::string& filename, bool wait)
|
|||
current_buffer.resize(buffer_size);
|
||||
ptr = current_buffer.data();
|
||||
PointerWrap p(&ptr, buffer_size, PointerWrap::Mode::Write);
|
||||
DoState(system, p);
|
||||
DoState(system, p, true);
|
||||
|
||||
if (p.IsWriteMode())
|
||||
{
|
||||
|
@ -900,7 +900,7 @@ void LoadAs(Core::System& system, const std::string& filename)
|
|||
{
|
||||
u8* ptr = buffer.data();
|
||||
PointerWrap p(&ptr, buffer.size(), PointerWrap::Mode::Read);
|
||||
DoState(system, p);
|
||||
DoState(system, p, true);
|
||||
loaded = true;
|
||||
loadedSuccessfully = p.IsReadMode();
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue