Quick save/load support

This commit is contained in:
Souryo 2014-06-25 21:52:37 -04:00
parent 37f1d94cff
commit 9ec756fae2
21 changed files with 406 additions and 13 deletions

View file

@ -3,6 +3,7 @@
#include "stdafx.h"
#include "APU.h"
#include "CPU.h"
#include "Nes_Apu\apu_snapshot.h"
APU* APU::Instance = nullptr;
IAudioDevice* APU::AudioDevice = nullptr;
@ -79,3 +80,59 @@ bool APU::Exec(uint32_t executedCycles)
}
return false;
}
void APU::StreamState(bool saving)
{
apu_snapshot_t snapshot;
if(saving) {
_apu.save_snapshot(&snapshot);
}
StreamArray<uint8_t>(snapshot.w40xx, 0x14);
Stream<uint8_t>(snapshot.w4015);
Stream<uint8_t>(snapshot.w4017);
Stream<uint16_t>(snapshot.delay);
Stream<uint8_t>(snapshot.step);
Stream<uint8_t>(snapshot.irq_flag);
Stream<uint16_t>(snapshot.square1.delay);
StreamArray<uint8_t>(snapshot.square1.env, 3);
Stream<uint8_t>(snapshot.square1.length);
Stream<uint8_t>(snapshot.square1.phase);
Stream<uint8_t>(snapshot.square1.swp_delay);
Stream<uint8_t>(snapshot.square1.swp_reset);
StreamArray<uint8_t>(snapshot.square1.unused, 1);
Stream<uint16_t>(snapshot.square2.delay);
StreamArray<uint8_t>(snapshot.square2.env, 3);
Stream<uint8_t>(snapshot.square2.length);
Stream<uint8_t>(snapshot.square2.phase);
Stream<uint8_t>(snapshot.square2.swp_delay);
Stream<uint8_t>(snapshot.square2.swp_reset);
StreamArray<uint8_t>(snapshot.square2.unused, 1);
Stream<uint16_t>(snapshot.triangle.delay);
Stream<uint8_t>(snapshot.triangle.length);
Stream<uint8_t>(snapshot.triangle.phase);
Stream<uint8_t>(snapshot.triangle.linear_counter);
Stream<uint8_t>(snapshot.triangle.linear_mode);
Stream<uint16_t>(snapshot.noise.delay);
StreamArray<uint8_t>(snapshot.noise.env, 3);
Stream<uint8_t>(snapshot.noise.length);
Stream<uint16_t>(snapshot.noise.shift_reg);
Stream<uint16_t>(snapshot.dmc.delay);
Stream<uint16_t>(snapshot.dmc.remain);
Stream<uint16_t>(snapshot.dmc.addr);
Stream<uint8_t>(snapshot.dmc.buf);
Stream<uint8_t>(snapshot.dmc.bits_remain);
Stream<uint8_t>(snapshot.dmc.bits);
Stream<uint8_t>(snapshot.dmc.buf_empty);
Stream<uint8_t>(snapshot.dmc.silence);
Stream<uint8_t>(snapshot.dmc.irq_flag);
if(!saving) {
_apu.load_snapshot(snapshot);
}
}

View file

@ -4,9 +4,10 @@
#include "MemoryManager.h"
#include "IMemoryHandler.h"
#include "IAudioDevice.h"
#include "Snapshotable.h"
#include "Nes_Apu/Nes_Apu.h"
class APU : public IMemoryHandler
class APU : public IMemoryHandler, public Snapshotable
{
private:
static IAudioDevice* AudioDevice;
@ -23,6 +24,9 @@ class APU : public IMemoryHandler
static int DMCRead(void*, cpu_addr_t addr);
static void IRQChanged(void* data);
protected:
void StreamState(bool saving);
public:
static const uint32_t SampleRate = 44100;
static const uint32_t SamplesPerFrame = 44100 / 60;

View file

@ -1,10 +1,11 @@
#pragma once
#include "stdafx.h"
#include "Snapshotable.h"
#include "IMemoryHandler.h"
#include "ROMLoader.h"
class BaseMapper : public IMemoryHandler
class BaseMapper : public IMemoryHandler, public Snapshotable
{
protected:
uint8_t* _prgRAM;
@ -12,6 +13,7 @@ class BaseMapper : public IMemoryHandler
uint32_t _prgSize;
uint32_t _chrSize;
bool _hasCHRRAM;
bool _hasBattery;
wstring _romFilename;
@ -20,6 +22,9 @@ class BaseMapper : public IMemoryHandler
vector<uint8_t*> _prgPages;
vector<uint8_t*> _chrPages;
uint32_t* _prgSlotPages;
uint32_t* _chrSlotPages;
uint32_t _chrShift = -1;
uint32_t _prgShift = -1;
@ -38,12 +43,14 @@ class BaseMapper : public IMemoryHandler
{
//std::cout << std::dec << "PRG Slot " << (short)slot << ": " << (short)page << std::endl;
_prgPages[slot] = &_prgRAM[(page & (GetPRGPageCount() - 1)) * GetPRGPageSize()];
_prgSlotPages[slot] = page;
}
void SelectCHRPage(uint32_t slot, uint32_t page)
{
//std::cout << std::dec << "CHR Slot " << (short)slot << ": " << (short)page << std::endl;
_chrPages[slot] = &_chrRAM[(page & (GetCHRPageCount() - 1)) * GetCHRPageSize()];
_chrSlotPages[slot] = page;
}
uint32_t GetPRGSlotCount()
@ -107,6 +114,28 @@ class BaseMapper : public IMemoryHandler
return filename;
}
protected:
virtual void StreamState(bool saving)
{
StreamArray<uint32_t>(_prgSlotPages, GetPRGSlotCount());
StreamArray<uint32_t>(_chrSlotPages, GetCHRSlotCount());
Stream<bool>(_hasCHRRAM);
if(_hasCHRRAM) {
StreamArray<uint8_t>(_chrRAM, BaseMapper::CHRSize);
}
if(!saving) {
for(int i = GetPRGSlotCount() - 1; i >= 0; i--) {
SelectPRGPage(i, _prgSlotPages[i]);
}
for(int i = GetCHRSlotCount() - 1; i >= 0; i--) {
SelectCHRPage(i, _chrSlotPages[i]);
}
}
}
public:
void Initialize(ROMLoader &romLoader)
{
@ -119,6 +148,7 @@ class BaseMapper : public IMemoryHandler
_romFilename = romLoader.GetFilename();
if(_chrSize == 0) {
_hasCHRRAM = true;
_chrRAM = new uint8_t[BaseMapper::CHRSize];
_chrSize = BaseMapper::CHRSize;
}
@ -131,6 +161,9 @@ class BaseMapper : public IMemoryHandler
_chrPages.push_back(nullptr);
}
_prgSlotPages = new uint32_t[GetPRGSlotCount()];
_chrSlotPages = new uint32_t[GetCHRSlotCount()];
InitMapper();
}

View file

@ -135,3 +135,19 @@ uint32_t CPU::Exec()
CPU::CycleCount += executedCycles;
return executedCycles + GetCyclePenalty();
}
void CPU::StreamState(bool saving)
{
Stream<uint16_t>(_state.PC);
Stream<uint8_t>(_state.SP);
Stream<uint8_t>(_state.A);
Stream<uint8_t>(_state.X);
Stream<uint8_t>(_state.Y);
Stream<uint64_t>(CPU::CycleCount);
Stream<bool>(CPU::NMIFlag);
Stream<uint32_t>(CPU::IRQFlag);
Stream<bool>(_runNMI);
Stream<bool>(_runIRQ);
}

View file

@ -2,6 +2,7 @@
#include "stdafx.h"
#include "MemoryManager.h"
#include "Snapshotable.h"
namespace PSFlags
{
@ -35,7 +36,7 @@ struct State
uint8_t PS;
};
class CPU
class CPU : public Snapshotable
{
private:
const uint16_t NMIVector = 0xFFFA;
@ -638,6 +639,9 @@ private:
}
#pragma endregion
protected:
void StreamState(bool saving);
public:
static const uint32_t ClockRate = 1789773;

View file

@ -94,10 +94,15 @@ void Console::Run()
uint32_t frameCount = _ppu->GetFrameCount();
Console::CurrentFPS = (int)((frameCount - lastFrameCount) / (fpsTimer.GetElapsedMS() / 1000));
lastFrameCount = frameCount;
//std::cout << Console::CurrentFPS << std::endl;
fpsTimer.Reset();
}
if(!_saveStateFilename.empty()) {
SaveState();
} else if(!_loadStateFilename.empty()) {
LoadState();
}
if(_stop) {
_stop = false;
break;
@ -114,6 +119,48 @@ void Console::Run()
}
}
void Console::SaveState(wstring filename)
{
_saveStateFilename = filename;
}
void Console::SaveState()
{
ofstream file(_saveStateFilename, ios::out | ios::binary);
if(file) {
_cpu->SaveSnapshot(&file);
_ppu->SaveSnapshot(&file);
_memoryManager->SaveSnapshot(&file);
_mapper->SaveSnapshot(&file);
_apu->SaveSnapshot(&file);
file.close();
}
_saveStateFilename.clear();
}
void Console::LoadState(wstring filename)
{
_loadStateFilename = filename;
}
void Console::LoadState()
{
ifstream file(_loadStateFilename, ios::out | ios::binary);
if(file) {
_cpu->LoadSnapshot(&file);
_ppu->LoadSnapshot(&file);
_memoryManager->LoadSnapshot(&file);
_mapper->LoadSnapshot(&file);
_apu->LoadSnapshot(&file);
file.close();
}
_loadStateFilename.clear();
}
bool Console::RunTest(uint8_t *expectedResult)
{
Timer timer;

View file

@ -31,6 +31,12 @@ class Console
bool _stop = false;
bool _reset = false;
wstring _loadStateFilename;
wstring _saveStateFilename;
void SaveState();
void LoadState();
void ResetComponents(bool softReset);
public:
@ -43,6 +49,9 @@ class Console
bool RunTest(uint8_t* expectedResult);
void SaveTestResult();
void SaveState(wstring filename);
void LoadState(wstring filename);
static bool CheckFlag(int flag);
static void SetFlags(int flags);
static void ClearFlags(int flags);

View file

@ -95,6 +95,7 @@
<ClInclude Include="IControlDevice.h" />
<ClInclude Include="IMemoryHandler.h" />
<ClInclude Include="Console.h" />
<ClInclude Include="Snapshotable.h" />
<ClInclude Include="IVideoDevice.h" />
<ClInclude Include="MapperFactory.h" />
<ClInclude Include="MMC1.h" />

View file

@ -117,6 +117,9 @@
<ClInclude Include="MMC3.h">
<Filter>Header Files\Mappers</Filter>
</ClInclude>
<ClInclude Include="Snapshotable.h">
<Filter>Header Files\Interfaces</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="CPU.cpp">

View file

@ -129,6 +129,28 @@ class MMC1 : public BaseMapper
}
protected:
void StreamState(bool saving)
{
Stream<uint8_t>(_state.Reg8000);
Stream<uint8_t>(_state.RegA000);
Stream<uint8_t>(_state.RegC000);
Stream<uint8_t>(_state.RegE000);
Stream<uint8_t>(_writeBuffer);
Stream<uint8_t>(_shiftCount);
Stream<bool>(_wramDisable);
Stream<ChrMode>(_chrMode);
Stream<PrgMode>(_prgMode);
Stream<SlotSelect>(_slotSelect);
Stream<uint8_t>(_chrReg0);
Stream<uint8_t>(_chrReg1);
Stream<uint8_t>(_prgReg);
BaseMapper::StreamState(saving);
}
virtual uint32_t GetPRGPageSize() { return 0x4000; }
virtual uint32_t GetCHRPageSize() { return 0x1000; }

View file

@ -26,7 +26,8 @@ class MMC3 : public BaseMapper
bool _irqReload;
bool _irqEnabled;
int32_t _lastPPUCycle;
uint32_t _lastCycle;
uint32_t _cyclesDown;
bool _wramEnabled;
bool _wramWriteProtected;
@ -51,7 +52,8 @@ class MMC3 : public BaseMapper
_irqReloadValue = 0;
_irqReload = false;
_irqEnabled = false;
_lastPPUCycle = 0xFFFF;
_lastCycle = 0xFFFF;
_cyclesDown = 0;
_wramEnabled = false;
_wramWriteProtected = false;
@ -107,6 +109,32 @@ class MMC3 : public BaseMapper
}
protected:
void StreamState(bool saving)
{
Stream<uint8_t>(_state.Reg8000);
Stream<uint8_t>(_state.RegA000);
Stream<uint8_t>(_state.RegA001);
Stream<uint8_t>(_currentRegister);
StreamArray<uint8_t>(_registers, 8);
Stream<uint8_t>(_chrMode);
Stream<uint8_t>(_prgMode);
Stream<uint8_t>(_irqReloadValue);
Stream<uint8_t>(_irqCounter);
Stream<bool>(_irqReload);
Stream<bool>(_irqEnabled);
Stream<uint32_t>(_lastCycle);
Stream<uint32_t>(_cyclesDown);
Stream<bool>(_wramEnabled);
Stream<bool>(_wramWriteProtected);
BaseMapper::StreamState(saving);
}
virtual uint32_t GetPRGPageSize() { return 0x2000; }
virtual uint32_t GetCHRPageSize() { return 0x0400; }
@ -168,8 +196,6 @@ class MMC3 : public BaseMapper
}
}
uint32_t _lastCycle = 0xFFFF;
uint32_t _cyclesDown = 0;
virtual void NotifyVRAMAddressChange(uint16_t addr)
{
uint16_t cycle = PPU::GetCurrentCycle();

View file

@ -8,7 +8,7 @@ MemoryManager::MemoryManager(shared_ptr<BaseMapper> mapper)
_internalRAM = new uint8_t[InternalRAMSize];
_SRAM = new uint8_t[SRAMSize];
_videoRAM = new uint8_t[VRAMSize];
_expansionRAM = new uint8_t[0x2000];
_expansionRAM = new uint8_t[ExpansionRAMSize];
_ramReadHandlers = new IMemoryHandler*[RAMSize];
_ramWriteHandlers = new IMemoryHandler*[RAMSize];
@ -18,7 +18,7 @@ MemoryManager::MemoryManager(shared_ptr<BaseMapper> mapper)
memset(_internalRAM, 0, InternalRAMSize);
memset(_SRAM, 0, SRAMSize);
memset(_videoRAM, 0, VRAMSize);
memset(_expansionRAM, 0, 0x2000);
memset(_expansionRAM, 0, ExpansionRAMSize);
memset(_ramReadHandlers, 0, RAMSize * sizeof(IMemoryHandler*));
memset(_ramWriteHandlers, 0, RAMSize * sizeof(IMemoryHandler*));
@ -213,4 +213,12 @@ void MemoryManager::WriteVRAM(uint16_t addr, uint8_t value)
throw exception("Not implemented yet");
}
}
}
void MemoryManager::StreamState(bool saving)
{
StreamArray<uint8_t>(_internalRAM, MemoryManager::InternalRAMSize);
StreamArray<uint8_t>(_expansionRAM, MemoryManager::ExpansionRAMSize);
StreamArray<uint8_t>(_SRAM, MemoryManager::SRAMSize);
StreamArray<uint8_t>(_videoRAM, MemoryManager::VRAMSize);
}

View file

@ -4,12 +4,14 @@
#include "IMemoryHandler.h"
#include "ROMLoader.h"
#include "BaseMapper.h"
#include "Snapshotable.h"
class MemoryManager
class MemoryManager: public Snapshotable
{
private:
const int RAMSize = 0x10000;
const int InternalRAMSize = 0x800;
const int ExpansionRAMSize = 0x2000;
const int SRAMSize = 0x2000;
const int VRAMSize = 0x4000;
@ -31,6 +33,9 @@ class MemoryManager
uint8_t ReadMappedVRAM(uint16_t addr);
void WriteMappedVRAM(uint16_t addr, uint8_t value);
protected:
void StreamState(bool saving);
public:
MemoryManager(shared_ptr<BaseMapper> mapper);
~MemoryManager();

View file

@ -644,4 +644,57 @@ void PPU::Exec()
gap--;
}
}
void PPU::StreamState(bool saving)
{
Stream<uint8_t>(_state.Control);
Stream<uint8_t>(_state.Mask);
Stream<uint8_t>(_state.Status);
Stream<uint32_t>(_state.SpriteRamAddr);
Stream<uint16_t>(_state.VideoRamAddr);
Stream<uint8_t>(_state.XScroll);
Stream<uint16_t>(_state.TmpVideoRamAddr);
Stream<bool>(_state.WriteToggle);
Stream<uint16_t>(_state.HighBitShift);
Stream<uint16_t>(_state.LowBitShift);
Stream<int32_t>(_scanline);
Stream<uint32_t>(_cycle);
Stream<uint32_t>(_frameCount);
Stream<uint64_t>(_cycleCount);
Stream<uint8_t>(_memoryReadBuffer);
StreamArray<uint8_t>(_paletteRAM, 0x100);
StreamArray<uint8_t>(_spriteRAM, 0x100);
StreamArray<uint8_t>(_secondarySpriteRAM, 0x20);
Stream<uint8_t>(_currentTile.LowByte);
Stream<uint8_t>(_currentTile.HighByte);
Stream<uint32_t>(_currentTile.PaletteOffset);
Stream<uint8_t>(_nextTile.LowByte);
Stream<uint8_t>(_nextTile.HighByte);
Stream<uint32_t>(_nextTile.PaletteOffset);
Stream<uint8_t>(_previousTile.LowByte);
Stream<uint8_t>(_previousTile.HighByte);
Stream<uint32_t>(_previousTile.PaletteOffset);
StreamArray<int32_t>(_spriteX, 0x8);
for(int i = 0; i < 8; i++) {
Stream<uint8_t>(_spriteTiles[i].LowByte);
Stream<uint8_t>(_spriteTiles[i].HighByte);
Stream<uint32_t>(_spriteTiles[i].PaletteOffset);
Stream<bool>(_spriteTiles[i].HorizontalMirror);
Stream<bool>(_spriteTiles[i].BackgroundPriority);
}
Stream<uint32_t>(_spriteCount);
Stream<uint32_t>(_secondaryOAMAddr);
Stream<bool>(_sprite0Visible);
if(!saving) {
SetControlRegister(_state.Control);
SetMaskRegister(_state.Mask);
}
}

View file

@ -1,6 +1,7 @@
#pragma once
#include "stdafx.h"
#include "Snapshotable.h"
#include "MemoryManager.h"
#include "IVideoDevice.h"
@ -73,7 +74,7 @@ struct SpriteInfo
bool BackgroundPriority;
};
class PPU : public IMemoryHandler
class PPU : public IMemoryHandler, public Snapshotable
{
private:
static PPU* Instance;
@ -157,6 +158,9 @@ class PPU : public IMemoryHandler
}
}
protected:
void StreamState(bool saving);
public:
PPU(MemoryManager *memoryManager);
~PPU();

71
Core/Snapshotable.h Normal file
View file

@ -0,0 +1,71 @@
#pragma once
#include "stdafx.h"
class Snapshotable
{
uint8_t* _stream;
uint32_t _position;
bool _saving;
protected:
virtual void StreamState(bool saving) = 0;
template<typename T>
void Stream(T &value)
{
if(_saving) {
uint8_t* bytes = (uint8_t*)&value;
int typeSize = sizeof(T);
for(int i = 0; i < typeSize; i++) {
_stream[_position++] = bytes[i];
}
} else {
value = *((T*)(_stream + _position));
_position += sizeof(T);
}
}
template<typename T>
void StreamArray(T* value, uint32_t length)
{
uint32_t typeSize = sizeof(*value);
if(_saving) {
uint8_t* bytes = (uint8_t*)value;
for(uint32_t i = 0, len = length*typeSize; i < len; i++) {
_stream[_position++] = bytes[i];
}
} else {
for(uint32_t i = 0; i < length*typeSize; i++) {
((uint8_t*)value)[i] = _stream[_position];
_position++;
}
}
}
public:
void SaveSnapshot(ofstream* file)
{
_stream = new uint8_t[0xFFFF];
memset((char*)_stream, 0, 0xFFFF);
_position = 0;
_saving = true;
StreamState(_saving);
file->write((char*)_stream, 0xFFFF);
delete[] _stream;
}
void LoadSnapshot(ifstream* file)
{
_stream = new uint8_t[0xFFFF];
_position = 0;
_saving = false;
file->read((char*)_stream, 0xFFFF);
StreamState(_saving);
delete[] _stream;
}
};

Binary file not shown.

View file

@ -195,6 +195,10 @@ namespace NES {
SetMenuEnabled(ID_NES_STOP, true);
SetMenuEnabled(ID_NES_RESUME, false);
SetMenuEnabled(ID_FILE_QUICKLOAD, true);
SetMenuEnabled(ID_FILE_QUICKSAVE, true);
_renderer->ClearFlags(UIFlags::ShowPauseScreen);
if(IsMenuChecked(ID_OPTIONS_SHOWFPS)) {
_renderer->SetFlags(UIFlags::ShowFPS);
@ -223,6 +227,8 @@ namespace NES {
SetMenuEnabled(ID_NES_PAUSE, false);
SetMenuEnabled(ID_NES_RESET, !powerOff);
SetMenuEnabled(ID_NES_STOP, !powerOff);
SetMenuEnabled(ID_FILE_QUICKLOAD, !powerOff);
SetMenuEnabled(ID_FILE_QUICKSAVE, !powerOff);
SetMenuEnabled(ID_NES_RESUME, true);
}
@ -360,6 +366,14 @@ namespace NES {
mainWindow->Start(filename);
}
break;
case ID_FILE_QUICKLOAD:
mainWindow->_console->LoadState(mainWindow->_currentROM + L".svs");
mainWindow->_renderer->DisplayMessage(L"State loaded.", 3000);
break;
case ID_FILE_QUICKSAVE:
mainWindow->_console->SaveState(mainWindow->_currentROM + L".svs");
mainWindow->_renderer->DisplayMessage(L"State saved.", 3000);
break;
case ID_FILE_EXIT:
DestroyWindow(hWnd);
break;

View file

@ -234,6 +234,12 @@ namespace NES
return shaderResourceView;
}
void Renderer::DisplayMessage(wstring text, uint32_t duration)
{
_displayMessage = text;
_displayTimestamp = timeGetTime() + duration;
}
void Renderer::DrawNESScreen()
{
RECT sourceRect;
@ -309,7 +315,12 @@ namespace NES
//Draw FPS counter
if(CheckFlag(UIFlags::ShowFPS)) {
_font->DrawString(_spriteBatch.get(), (wstring(L"FPS: ") + std::to_wstring(Console::GetFPS())).c_str(), XMFLOAT2(256 * 4 - 149, 13), Colors::Black, 0.0f, XMFLOAT2(0, 0), 1.0f);
_font->DrawString(_spriteBatch.get(), (wstring(L"FPS: ") + std::to_wstring(Console::GetFPS())).c_str(), XMFLOAT2(256 * 4 - 150, 11), Colors::Yellow, 0.0f, XMFLOAT2(0, 0), 1.0f);
_font->DrawString(_spriteBatch.get(), (wstring(L"FPS: ") + std::to_wstring(Console::GetFPS())).c_str(), XMFLOAT2(256 * 4 - 150, 11), Colors::AntiqueWhite, 0.0f, XMFLOAT2(0, 0), 1.0f);
}
if(!_displayMessage.empty() && _displayTimestamp > timeGetTime()) {
_font->DrawString(_spriteBatch.get(), _displayMessage.c_str(), XMFLOAT2(12, 13), Colors::Black, 0.0f, XMFLOAT2(0, 0), 1.0f);
_font->DrawString(_spriteBatch.get(), _displayMessage.c_str(), XMFLOAT2(11, 11), Colors::AntiqueWhite, 0.0f, XMFLOAT2(0, 0), 1.0f);
}
if(CheckFlag(UIFlags::ShowPauseScreen)) {

View file

@ -39,6 +39,9 @@ namespace NES {
uint32_t _flags = 0;
wstring _displayMessage = L"";
uint32_t _displayTimestamp = 0;
HRESULT InitDevice();
void CleanupDevice();
@ -51,6 +54,8 @@ namespace NES {
~Renderer();
void Render();
void Renderer::DisplayMessage(wstring text, uint32_t duration);
void SetFlags(uint32_t flags)
{

Binary file not shown.