2019-03-12 09:15:57 -04:00
|
|
|
#include "stdafx.h"
|
|
|
|
#include "../Utilities/FolderUtilities.h"
|
|
|
|
#include "../Utilities/ZipWriter.h"
|
|
|
|
#include "../Utilities/ZipReader.h"
|
2020-02-05 21:30:16 -05:00
|
|
|
#include "../Utilities/PNGHelper.h"
|
2019-03-12 09:15:57 -04:00
|
|
|
#include "SaveStateManager.h"
|
|
|
|
#include "MessageManager.h"
|
|
|
|
#include "Console.h"
|
|
|
|
#include "EmuSettings.h"
|
|
|
|
#include "VideoDecoder.h"
|
|
|
|
#include "BaseCartridge.h"
|
2019-08-09 16:25:59 -04:00
|
|
|
#include "MovieManager.h"
|
2019-10-06 19:06:18 -04:00
|
|
|
#include "EventType.h"
|
|
|
|
#include "Debugger.h"
|
2019-10-20 20:05:39 -04:00
|
|
|
#include "GameClient.h"
|
2020-02-05 21:30:16 -05:00
|
|
|
#include "Ppu.h"
|
|
|
|
#include "DefaultVideoFilter.h"
|
2019-03-12 09:15:57 -04:00
|
|
|
|
|
|
|
SaveStateManager::SaveStateManager(shared_ptr<Console> console)
|
|
|
|
{
|
|
|
|
_console = console;
|
|
|
|
_lastIndex = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
string SaveStateManager::GetStateFilepath(int stateIndex)
|
|
|
|
{
|
2019-03-12 12:28:41 -04:00
|
|
|
string romFile = _console->GetRomInfo().RomFile.GetFileName();
|
2019-03-12 09:15:57 -04:00
|
|
|
string folder = FolderUtilities::GetSaveStateFolder();
|
2019-03-31 15:58:59 -04:00
|
|
|
string filename = FolderUtilities::GetFilename(romFile, false) + "_" + std::to_string(stateIndex) + ".mss";
|
2019-03-12 09:15:57 -04:00
|
|
|
return FolderUtilities::CombinePath(folder, filename);
|
|
|
|
}
|
|
|
|
|
2019-12-26 14:11:33 -05:00
|
|
|
void SaveStateManager::SelectSaveSlot(int slotIndex)
|
|
|
|
{
|
|
|
|
_lastIndex = slotIndex;
|
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateSlotSelected", std::to_string(_lastIndex));
|
|
|
|
}
|
|
|
|
|
2019-03-12 09:15:57 -04:00
|
|
|
void SaveStateManager::MoveToNextSlot()
|
|
|
|
{
|
|
|
|
_lastIndex = (_lastIndex % MaxIndex) + 1;
|
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateSlotSelected", std::to_string(_lastIndex));
|
|
|
|
}
|
|
|
|
|
|
|
|
void SaveStateManager::MoveToPreviousSlot()
|
|
|
|
{
|
|
|
|
_lastIndex = (_lastIndex == 1 ? SaveStateManager::MaxIndex : (_lastIndex - 1));
|
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateSlotSelected", std::to_string(_lastIndex));
|
|
|
|
}
|
|
|
|
|
|
|
|
void SaveStateManager::SaveState()
|
|
|
|
{
|
|
|
|
SaveState(_lastIndex);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SaveStateManager::LoadState()
|
|
|
|
{
|
|
|
|
return LoadState(_lastIndex);
|
|
|
|
}
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
void SaveStateManager::GetSaveStateHeader(ostream &stream)
|
2019-03-12 09:15:57 -04:00
|
|
|
{
|
|
|
|
uint32_t emuVersion = _console->GetSettings()->GetVersion();
|
|
|
|
uint32_t formatVersion = SaveStateManager::FileFormatVersion;
|
2019-03-31 15:58:59 -04:00
|
|
|
stream.write("MSS", 3);
|
2019-03-12 09:15:57 -04:00
|
|
|
stream.write((char*)&emuVersion, sizeof(emuVersion));
|
|
|
|
stream.write((char*)&formatVersion, sizeof(uint32_t));
|
|
|
|
|
|
|
|
string sha1Hash = _console->GetCartridge()->GetSha1Hash();
|
|
|
|
stream.write(sha1Hash.c_str(), sha1Hash.size());
|
|
|
|
|
2020-06-18 00:58:22 -04:00
|
|
|
bool isGameboyMode = _console->GetSettings()->CheckFlag(EmulationFlags::GameboyMode);
|
|
|
|
stream.write((char*)&isGameboyMode, sizeof(bool));
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
#ifndef LIBRETRO
|
2020-02-05 21:30:16 -05:00
|
|
|
SaveScreenshotData(stream);
|
2021-03-10 11:13:28 -05:00
|
|
|
#endif
|
2020-02-05 21:30:16 -05:00
|
|
|
|
2019-03-12 09:15:57 -04:00
|
|
|
RomInfo romInfo = _console->GetCartridge()->GetRomInfo();
|
2019-03-12 12:28:41 -04:00
|
|
|
string romName = FolderUtilities::GetFilename(romInfo.RomFile.GetFileName(), true);
|
2019-03-12 09:15:57 -04:00
|
|
|
uint32_t nameLength = (uint32_t)romName.size();
|
|
|
|
stream.write((char*)&nameLength, sizeof(uint32_t));
|
|
|
|
stream.write(romName.c_str(), romName.size());
|
|
|
|
}
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
void SaveStateManager::SaveState(ostream &stream)
|
2019-03-12 09:15:57 -04:00
|
|
|
{
|
|
|
|
GetSaveStateHeader(stream);
|
|
|
|
_console->Serialize(stream);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SaveStateManager::SaveState(string filepath)
|
|
|
|
{
|
|
|
|
ofstream file(filepath, ios::out | ios::binary);
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
if(file) {
|
2019-10-06 19:06:18 -04:00
|
|
|
_console->Lock();
|
2019-03-12 09:15:57 -04:00
|
|
|
SaveState(file);
|
2019-10-06 19:06:18 -04:00
|
|
|
_console->Unlock();
|
2019-03-12 09:15:57 -04:00
|
|
|
file.close();
|
|
|
|
|
2019-10-06 19:06:18 -04:00
|
|
|
shared_ptr<Debugger> debugger = _console->GetDebugger(false);
|
2021-03-10 11:13:28 -05:00
|
|
|
if(debugger) {
|
2019-03-12 09:15:57 -04:00
|
|
|
debugger->ProcessEvent(EventType::StateSaved);
|
2019-10-06 19:06:18 -04:00
|
|
|
}
|
2019-03-12 09:15:57 -04:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SaveStateManager::SaveState(int stateIndex, bool displayMessage)
|
|
|
|
{
|
|
|
|
string filepath = SaveStateManager::GetStateFilepath(stateIndex);
|
2021-03-10 11:13:28 -05:00
|
|
|
if(SaveState(filepath)) {
|
|
|
|
if(displayMessage) {
|
2019-03-12 09:15:57 -04:00
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateSaved", std::to_string(stateIndex));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-05 21:30:16 -05:00
|
|
|
void SaveStateManager::SaveScreenshotData(ostream& stream)
|
|
|
|
{
|
|
|
|
bool isHighRes = _console->GetPpu()->IsHighResOutput();
|
|
|
|
uint32_t height = isHighRes ? 478 : 239;
|
|
|
|
uint32_t width = isHighRes ? 512 : 256;
|
|
|
|
|
|
|
|
stream.write((char*)&width, sizeof(uint32_t));
|
|
|
|
stream.write((char*)&height, sizeof(uint32_t));
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
unsigned long compressedSize = compressBound(512*478*2);
|
2020-02-05 21:30:16 -05:00
|
|
|
vector<uint8_t> compressedData(compressedSize, 0);
|
2021-03-10 11:13:28 -05:00
|
|
|
compress2(compressedData.data(), &compressedSize, (const unsigned char*)_console->GetPpu()->GetScreenBuffer(), width*height*2, MZ_DEFAULT_LEVEL);
|
2020-02-05 21:30:16 -05:00
|
|
|
|
|
|
|
uint32_t screenshotLength = (uint32_t)compressedSize;
|
|
|
|
stream.write((char*)&screenshotLength, sizeof(uint32_t));
|
|
|
|
stream.write((char*)compressedData.data(), screenshotLength);
|
|
|
|
}
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
bool SaveStateManager::GetScreenshotData(vector<uint8_t>& out, uint32_t &width, uint32_t &height, istream& stream)
|
2020-02-05 21:30:16 -05:00
|
|
|
{
|
|
|
|
stream.read((char*)&width, sizeof(uint32_t));
|
|
|
|
stream.read((char*)&height, sizeof(uint32_t));
|
|
|
|
|
|
|
|
uint32_t screenshotLength = 0;
|
|
|
|
stream.read((char*)&screenshotLength, sizeof(uint32_t));
|
|
|
|
|
|
|
|
vector<uint8_t> compressedData(screenshotLength, 0);
|
|
|
|
stream.read((char*)compressedData.data(), screenshotLength);
|
|
|
|
|
|
|
|
out = vector<uint8_t>(width * height * 2, 0);
|
|
|
|
unsigned long decompSize = width * height * 2;
|
2021-03-10 11:13:28 -05:00
|
|
|
if(uncompress(out.data(), &decompSize, compressedData.data(), (unsigned long)compressedData.size()) == MZ_OK) {
|
2020-02-05 21:30:16 -05:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired)
|
2019-03-12 09:15:57 -04:00
|
|
|
{
|
2021-03-10 11:13:28 -05:00
|
|
|
if(GameClient::Connected()) {
|
2019-10-20 20:05:39 -04:00
|
|
|
MessageManager::DisplayMessage("Netplay", "NetplayNotAllowed");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-03-12 09:15:57 -04:00
|
|
|
char header[3];
|
|
|
|
stream.read(header, 3);
|
2021-03-10 11:13:28 -05:00
|
|
|
if(memcmp(header, "MSS", 3) == 0) {
|
2019-03-12 09:15:57 -04:00
|
|
|
uint32_t emuVersion, fileFormatVersion;
|
|
|
|
|
|
|
|
stream.read((char*)&emuVersion, sizeof(emuVersion));
|
2021-03-10 11:13:28 -05:00
|
|
|
if(emuVersion > _console->GetSettings()->GetVersion()) {
|
2019-03-12 09:15:57 -04:00
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateNewerVersion");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion));
|
2021-03-10 11:13:28 -05:00
|
|
|
if(fileFormatVersion <= 5) {
|
2019-03-12 09:15:57 -04:00
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion");
|
|
|
|
return false;
|
2021-03-10 11:13:28 -05:00
|
|
|
} else {
|
2019-03-12 09:15:57 -04:00
|
|
|
char hash[41] = {};
|
|
|
|
stream.read(hash, 40);
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
if(fileFormatVersion >= 8) {
|
2020-06-18 00:58:22 -04:00
|
|
|
bool isGameboyMode = false;
|
|
|
|
stream.read((char*)&isGameboyMode, sizeof(bool));
|
2021-03-10 11:13:28 -05:00
|
|
|
if(isGameboyMode != _console->GetSettings()->CheckFlag(EmulationFlags::GameboyMode)) {
|
|
|
|
MessageManager::DisplayMessage("SaveStates", isGameboyMode ? "SaveStateWrongSystemGb" : "SaveStateWrongSystemSnes");
|
2020-06-18 00:58:22 -04:00
|
|
|
return false;
|
|
|
|
}
|
2021-03-10 11:13:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if(fileFormatVersion >= 7) {
|
|
|
|
#ifndef LIBRETRO
|
2020-02-05 21:30:16 -05:00
|
|
|
vector<uint8_t> frameData;
|
|
|
|
uint32_t width = 0;
|
|
|
|
uint32_t height = 0;
|
2021-03-10 11:13:28 -05:00
|
|
|
if(GetScreenshotData(frameData, width, height, stream)) {
|
2020-02-05 21:30:16 -05:00
|
|
|
_console->GetVideoDecoder()->UpdateFrameSync((uint16_t*)frameData.data(), width, height, 0, true);
|
|
|
|
}
|
2021-03-10 11:13:28 -05:00
|
|
|
#endif
|
2020-02-05 21:30:16 -05:00
|
|
|
}
|
|
|
|
|
2019-03-12 09:15:57 -04:00
|
|
|
uint32_t nameLength = 0;
|
|
|
|
stream.read((char*)&nameLength, sizeof(uint32_t));
|
2021-03-10 11:13:28 -05:00
|
|
|
|
2019-03-12 09:15:57 -04:00
|
|
|
vector<char> nameBuffer(nameLength);
|
|
|
|
stream.read(nameBuffer.data(), nameBuffer.size());
|
|
|
|
string romName(nameBuffer.data(), nameLength);
|
2021-03-10 11:13:28 -05:00
|
|
|
|
2019-03-31 15:58:59 -04:00
|
|
|
shared_ptr<BaseCartridge> cartridge = _console->GetCartridge();
|
2021-03-10 11:13:28 -05:00
|
|
|
if(!cartridge /*|| cartridge->GetSha1Hash() != string(hash)*/) {
|
2019-03-31 15:58:59 -04:00
|
|
|
//Game isn't loaded, or CRC doesn't match
|
|
|
|
//TODO: Try to find and load the game
|
|
|
|
return false;
|
|
|
|
}
|
2019-03-12 09:15:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
//Stop any movie that might have been playing/recording if a state is loaded
|
|
|
|
//(Note: Loading a state is disabled in the UI while a movie is playing/recording)
|
2019-10-16 20:22:45 -04:00
|
|
|
_console->GetMovieManager()->Stop();
|
2019-03-12 09:15:57 -04:00
|
|
|
|
|
|
|
_console->Deserialize(stream, fileFormatVersion);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateInvalidFile");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SaveStateManager::LoadState(string filepath, bool hashCheckRequired)
|
|
|
|
{
|
|
|
|
ifstream file(filepath, ios::in | ios::binary);
|
|
|
|
bool result = false;
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
if(file.good()) {
|
2019-10-06 19:06:18 -04:00
|
|
|
_console->Lock();
|
|
|
|
result = LoadState(file, hashCheckRequired);
|
|
|
|
_console->Unlock();
|
2019-03-12 09:15:57 -04:00
|
|
|
file.close();
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
if(result) {
|
2019-10-06 19:06:18 -04:00
|
|
|
shared_ptr<Debugger> debugger = _console->GetDebugger(false);
|
2021-03-10 11:13:28 -05:00
|
|
|
if(debugger) {
|
2019-10-06 19:06:18 -04:00
|
|
|
debugger->ProcessEvent(EventType::StateLoaded);
|
|
|
|
}
|
|
|
|
}
|
2021-03-10 11:13:28 -05:00
|
|
|
} else {
|
2019-03-12 09:15:57 -04:00
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateEmpty");
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool SaveStateManager::LoadState(int stateIndex)
|
|
|
|
{
|
|
|
|
string filepath = SaveStateManager::GetStateFilepath(stateIndex);
|
2021-03-10 11:13:28 -05:00
|
|
|
if(LoadState(filepath, false)) {
|
2019-03-12 09:15:57 -04:00
|
|
|
MessageManager::DisplayMessage("SaveStates", "SaveStateLoaded", std::to_string(stateIndex));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SaveStateManager::SaveRecentGame(string romName, string romPath, string patchPath)
|
|
|
|
{
|
2019-07-03 18:57:30 -04:00
|
|
|
#ifndef LIBRETRO
|
|
|
|
//Don't do this for libretro core
|
2019-03-14 18:07:25 -04:00
|
|
|
string filename = FolderUtilities::GetFilename(_console->GetRomInfo().RomFile.GetFileName(), false) + ".rgd";
|
|
|
|
ZipWriter writer;
|
|
|
|
writer.Initialize(FolderUtilities::CombinePath(FolderUtilities::GetRecentGamesFolder(), filename));
|
|
|
|
|
|
|
|
std::stringstream pngStream;
|
|
|
|
_console->GetVideoDecoder()->TakeScreenshot(pngStream);
|
|
|
|
writer.AddFile(pngStream, "Screenshot.png");
|
|
|
|
|
|
|
|
std::stringstream stateStream;
|
|
|
|
SaveStateManager::SaveState(stateStream);
|
2019-03-31 15:58:59 -04:00
|
|
|
writer.AddFile(stateStream, "Savestate.mss");
|
2019-03-14 18:07:25 -04:00
|
|
|
|
|
|
|
std::stringstream romInfoStream;
|
|
|
|
romInfoStream << romName << std::endl;
|
|
|
|
romInfoStream << romPath << std::endl;
|
|
|
|
romInfoStream << patchPath << std::endl;
|
|
|
|
writer.AddFile(romInfoStream, "RomInfo.txt");
|
|
|
|
writer.Save();
|
2019-07-03 18:57:30 -04:00
|
|
|
#endif
|
2019-03-12 09:15:57 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
void SaveStateManager::LoadRecentGame(string filename, bool resetGame)
|
|
|
|
{
|
|
|
|
ZipReader reader;
|
|
|
|
reader.LoadArchive(filename);
|
|
|
|
|
|
|
|
stringstream romInfoStream, stateStream;
|
|
|
|
reader.GetStream("RomInfo.txt", romInfoStream);
|
2019-03-31 15:58:59 -04:00
|
|
|
reader.GetStream("Savestate.mss", stateStream);
|
2019-03-12 09:15:57 -04:00
|
|
|
|
|
|
|
string romName, romPath, patchPath;
|
|
|
|
std::getline(romInfoStream, romName);
|
|
|
|
std::getline(romInfoStream, romPath);
|
|
|
|
std::getline(romInfoStream, patchPath);
|
|
|
|
|
2019-03-16 12:20:18 -04:00
|
|
|
_console->Lock();
|
2021-03-10 11:13:28 -05:00
|
|
|
try {
|
|
|
|
if(_console->LoadRom(romPath, patchPath)) {
|
|
|
|
if(!resetGame) {
|
2019-03-12 09:15:57 -04:00
|
|
|
SaveStateManager::LoadState(stateStream, false);
|
|
|
|
}
|
|
|
|
}
|
2021-03-10 11:13:28 -05:00
|
|
|
} catch(std::exception&) {
|
2019-03-14 18:07:25 -04:00
|
|
|
_console->Stop(true);
|
2019-03-12 09:15:57 -04:00
|
|
|
}
|
2019-03-16 12:20:18 -04:00
|
|
|
_console->Unlock();
|
2019-03-12 09:15:57 -04:00
|
|
|
}
|
2020-02-05 21:30:16 -05:00
|
|
|
|
|
|
|
int32_t SaveStateManager::GetSaveStatePreview(string saveStatePath, uint8_t* pngData)
|
|
|
|
{
|
|
|
|
ifstream stream(saveStatePath, ios::binary);
|
|
|
|
|
2021-03-10 11:13:28 -05:00
|
|
|
if(!stream) {
|
2020-02-05 21:30:16 -05:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
char header[3];
|
|
|
|
stream.read(header, 3);
|
2021-03-10 11:13:28 -05:00
|
|
|
if(memcmp(header, "MSS", 3) == 0) {
|
2020-02-05 21:30:16 -05:00
|
|
|
uint32_t emuVersion = 0;
|
|
|
|
|
|
|
|
stream.read((char*)&emuVersion, sizeof(emuVersion));
|
2021-03-10 11:13:28 -05:00
|
|
|
if(emuVersion > _console->GetSettings()->GetVersion()) {
|
2020-02-05 21:30:16 -05:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t fileFormatVersion = 0;
|
|
|
|
stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion));
|
2021-03-10 11:13:28 -05:00
|
|
|
if(fileFormatVersion <= 6) {
|
2020-02-05 21:30:16 -05:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//Skip some header fields
|
|
|
|
stream.seekg(40, ios::cur);
|
|
|
|
|
|
|
|
vector<uint8_t> frameData;
|
|
|
|
uint32_t width = 0;
|
|
|
|
uint32_t height = 0;
|
2021-03-10 11:13:28 -05:00
|
|
|
if(GetScreenshotData(frameData, width, height, stream)) {
|
2020-02-05 21:30:16 -05:00
|
|
|
FrameInfo baseFrameInfo;
|
|
|
|
baseFrameInfo.Width = width;
|
|
|
|
baseFrameInfo.Height = height;
|
2021-03-10 11:13:28 -05:00
|
|
|
|
2020-02-05 21:30:16 -05:00
|
|
|
DefaultVideoFilter filter(_console);
|
|
|
|
filter.SetBaseFrameInfo(baseFrameInfo);
|
|
|
|
FrameInfo frameInfo = filter.GetFrameInfo();
|
|
|
|
filter.SendFrame((uint16_t*)frameData.data(), 0);
|
|
|
|
|
|
|
|
std::stringstream pngStream;
|
|
|
|
PNGHelper::WritePNG(pngStream, filter.GetOutputBuffer(), frameInfo.Width, frameInfo.Height);
|
|
|
|
|
|
|
|
string data = pngStream.str();
|
|
|
|
memcpy(pngData, data.c_str(), data.size());
|
|
|
|
|
|
|
|
return (int32_t)frameData.size();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return -1;
|
2021-03-10 11:13:28 -05:00
|
|
|
}
|