Save states: Fixed incorrect timestamp in menu

Also changed save states to contain the raw framebuffer rather than a PNG, which allows displaying the screenshot in the emulator window when loading a state while paused
This commit is contained in:
Sour 2020-01-28 23:33:37 -05:00
parent d6728ee306
commit 06ccf0bdb0
9 changed files with 62 additions and 53 deletions

View file

@ -2,6 +2,8 @@
#include "../Utilities/FolderUtilities.h"
#include "../Utilities/ZipWriter.h"
#include "../Utilities/ZipReader.h"
#include "../Utilities/PNGHelper.h"
#include "../Utilities/miniz.h"
#include "SaveStateManager.h"
#include "MessageManager.h"
#include "Console.h"
@ -10,6 +12,8 @@
#include "Debugger.h"
#include "MovieManager.h"
#include "RomData.h"
#include "DefaultVideoFilter.h"
#include "PPU.h"
SaveStateManager::SaveStateManager(shared_ptr<Console> console)
{
@ -24,18 +28,6 @@ string SaveStateManager::GetStateFilepath(int stateIndex)
return FolderUtilities::CombinePath(folder, filename);
}
uint64_t SaveStateManager::GetStateInfo(int stateIndex)
{
string filepath = SaveStateManager::GetStateFilepath(stateIndex);
ifstream file(filepath, ios::in | ios::binary);
if(file) {
file.close();
return FolderUtilities::GetFileModificationTime(filepath);
}
return 0;
}
void SaveStateManager::SelectSaveSlot(int slotIndex)
{
_lastIndex = slotIndex;
@ -79,11 +71,7 @@ void SaveStateManager::GetSaveStateHeader(ostream &stream)
string sha1Hash = romInfo.Hash.Sha1;
stream.write(sha1Hash.c_str(), sha1Hash.size());
std::stringstream screenshotStream;
_console->GetVideoDecoder()->TakeScreenshot(screenshotStream, true);
uint32_t screenshotLength = (uint32_t)screenshotStream.tellp();
stream.write((char*)&screenshotLength, sizeof(uint32_t));
stream.write(screenshotStream.str().c_str(), screenshotLength);
SaveScreenshotData(stream);
string romName = romInfo.RomName;
uint32_t nameLength = (uint32_t)romName.size();
@ -127,6 +115,33 @@ void SaveStateManager::SaveState(int stateIndex, bool displayMessage)
}
}
void SaveStateManager::SaveScreenshotData(ostream &stream)
{
unsigned long compressedSize = compressBound(PPU::PixelCount * 2);
vector<uint8_t> compressedData(compressedSize, 0);
compress2(compressedData.data(), &compressedSize, (const unsigned char*)_console->GetPpu()->GetScreenBuffer(true), PPU::PixelCount * 2, MZ_DEFAULT_LEVEL);
uint32_t screenshotLength = (uint32_t)compressedSize;
stream.write((char*)&screenshotLength, sizeof(uint32_t));
stream.write((char*)compressedData.data(), screenshotLength);
}
bool SaveStateManager::GetScreenshotData(vector<uint8_t>& out, istream& stream)
{
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>(PPU::PixelCount * 2, 0);
unsigned long decompSize = PPU::PixelCount * 2;
if(uncompress(out.data(), &decompSize, compressedData.data(), (unsigned long)compressedData.size()) == MZ_OK) {
return true;
}
return false;
}
bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired)
{
char header[3];
@ -158,10 +173,10 @@ bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired)
stream.read(hash, 40);
if(fileFormatVersion >= 13) {
//Skip screenshot data
uint32_t screenshotLength = 0;
stream.read((char*)&screenshotLength, sizeof(uint32_t));
stream.seekg(screenshotLength, std::ios::cur);
vector<uint8_t> frameData;
if(GetScreenshotData(frameData, stream)) {
_console->GetVideoDecoder()->UpdateFrameSync(frameData.data());
}
}
uint32_t nameLength = 0;
@ -312,16 +327,20 @@ int32_t SaveStateManager::GetSaveStatePreview(string saveStatePath, uint8_t* png
//Skip some header fields
stream.seekg(43, ios::cur);
uint32_t screenshotLength = 0;
stream.read((char*)&screenshotLength, sizeof(screenshotLength));
vector<uint8_t> frameData;
if(GetScreenshotData(frameData, stream)) {
DefaultVideoFilter filter(_console);
FrameInfo frameInfo = filter.GetFrameInfo();
filter.SendFrame((uint16_t*)frameData.data(), 0);
if(screenshotLength > 0) {
stream.read((char*)pngData, screenshotLength);
return screenshotLength;
std::stringstream pngStream;
PNGHelper::WritePNG(pngStream, filter.GetOutputBuffer(), frameInfo.Width, frameInfo.Height);
string data = pngStream.str();
memcpy(pngData, data.c_str(), data.size());
return frameData.size();
}
return -1;
}
return -1;
}

View file

@ -12,14 +12,14 @@ private:
shared_ptr<Console> _console;
string GetStateFilepath(int stateIndex);
void SaveScreenshotData(ostream& stream);
bool GetScreenshotData(vector<uint8_t>& out, istream& stream);
public:
static constexpr uint32_t FileFormatVersion = 13;
SaveStateManager(shared_ptr<Console> console);
uint64_t GetStateInfo(int stateIndex);
void SaveState();
bool LoadState();

View file

@ -116,8 +116,8 @@ namespace Mesen.GUI.Controls
InteropEmu.LoadRecentGame(_recentGame.FileName, ConfigManager.Config.PreferenceInfo.GameSelectionScreenResetGame);
} else {
switch(this.Mode) {
case GameScreenMode.LoadState: InteropEmu.LoadStateFile(_recentGame.FileName); break;
case GameScreenMode.SaveState: InteropEmu.SaveStateFile(_recentGame.FileName); break;
case GameScreenMode.LoadState: InteropEmu.LoadState(_recentGame.SaveSlot); break;
case GameScreenMode.SaveState: InteropEmu.SaveState(_recentGame.SaveSlot); break;
}
}
OnRecentGameLoaded?.Invoke(_recentGame);

View file

@ -163,7 +163,7 @@ namespace Mesen.GUI.Controls
string romName = InteropEmu.GetRomInfo().GetRomName();
for(int i = 0; i < 11; i++) {
_recentGames.Add(new RecentGameInfo() { FileName = Path.Combine(ConfigManager.SaveStateFolder, romName + "_" + (i + 1) + ".mst"), Name = i == 10 ? ResourceHelper.GetMessage("AutoSave") : ResourceHelper.GetMessage("SlotNumber", i+1) });
_recentGames.Add(new RecentGameInfo() { FileName = Path.Combine(ConfigManager.SaveStateFolder, romName + "_" + (i + 1) + ".mst"), Name = i == 10 ? ResourceHelper.GetMessage("AutoSave") : ResourceHelper.GetMessage("SlotNumber", i+1), SaveSlot = (uint)i+1 });
}
_recentGames.Add(new RecentGameInfo() { FileName = Path.Combine(ConfigManager.RecentGamesFolder, romName + ".rgd"), Name = ResourceHelper.GetMessage("LastSession") });
}
@ -245,7 +245,9 @@ namespace Mesen.GUI.Controls
private void RecentGameLoaded(RecentGameInfo gameInfo)
{
OnRecentGameLoaded?.Invoke(gameInfo);
if(this.Mode == GameScreenMode.RecentGames) {
OnRecentGameLoaded?.Invoke(gameInfo);
}
if(this._needResume) {
InteropEmu.Resume();
}
@ -327,6 +329,7 @@ namespace Mesen.GUI.Controls
{
public string FileName { get; set; }
public string Name { get; set; }
public uint SaveSlot { get; set; }
public ResourcePath RomPath { get; set; }
}

View file

@ -20,15 +20,15 @@ namespace Mesen.GUI.Forms
this.BeginInvoke((MethodInvoker)(() => this.UpdateStateMenu(menu, forSave)));
} else {
for(uint i = 1; i <= frmMain.NumberOfSaveSlots + (forSave ? 0 : 1); i++) {
Int64 fileTime = InteropEmu.GetStateInfo(i);
string statePath = Path.Combine(ConfigManager.SaveStateFolder, InteropEmu.GetRomInfo().GetRomName() + "_" + i + ".mst");
string label;
bool isAutoSaveSlot = i == NumberOfSaveSlots + 1;
string slotName = isAutoSaveSlot ? "Auto" : i.ToString();
if(fileTime == 0) {
if(!File.Exists(statePath)) {
label = slotName + ". " + ResourceHelper.GetMessage("EmptyState");
} else {
DateTime dateTime = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(fileTime).ToLocalTime();
DateTime dateTime = new FileInfo(statePath).LastWriteTime;
label = slotName + ". " + dateTime.ToShortDateString() + " " + dateTime.ToShortTimeString();
}

View file

@ -147,12 +147,12 @@ namespace Mesen.GUI
[DllImport(DLLPath)] public static extern void LoadState(UInt32 stateIndex);
[DllImport(DLLPath)] public static extern void SaveStateFile([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filepath);
[DllImport(DLLPath)] public static extern void LoadStateFile([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string filepath);
[DllImport(DLLPath)] public static extern Int64 GetStateInfo(UInt32 stateIndex);
[DllImport(DLLPath, EntryPoint = "GetSaveStatePreview")] private static extern Int32 GetSaveStatePreviewWrapper([MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]string saveStatePath, [Out]byte[] imgData);
public static Image GetSaveStatePreview(string saveStatePath)
{
if(File.Exists(saveStatePath)) {
byte[] buffer = new byte[new FileInfo(saveStatePath).Length];
byte[] buffer = new byte[128000];
Int32 size = InteropEmu.GetSaveStatePreviewWrapper(saveStatePath, buffer);
if(size > 0) {
Array.Resize(ref buffer, size);

View file

@ -545,7 +545,6 @@ namespace InteropEmu {
DllExport void __stdcall LoadState(uint32_t stateIndex) { _console->GetSaveStateManager()->LoadState(stateIndex); }
DllExport void __stdcall SaveStateFile(char* filepath) { _console->GetSaveStateManager()->SaveState(filepath); }
DllExport void __stdcall LoadStateFile(char* filepath) { _console->GetSaveStateManager()->LoadState(filepath); }
DllExport int64_t __stdcall GetStateInfo(uint32_t stateIndex) { return _console->GetSaveStateManager()->GetStateInfo(stateIndex); }
DllExport int32_t __stdcall GetSaveStatePreview(char* saveStatePath, uint8_t* pngData) { return _console->GetSaveStateManager()->GetSaveStatePreview(saveStatePath, pngData); }

View file

@ -213,12 +213,6 @@ string FolderUtilities::CombinePath(string folder, string filename)
return folder + filename;
}
}
int64_t FolderUtilities::GetFileModificationTime(string filepath)
{
std::error_code errorCode;
return fs::last_write_time(fs::u8path(filepath), errorCode).time_since_epoch() / std::chrono::seconds(1);
}
#else
//Libretro: Avoid using filesystem API.
@ -267,8 +261,4 @@ string FolderUtilities::CombinePath(string folder, string filename)
return folder + filename;
}
int64_t FolderUtilities::GetFileModificationTime(string filepath)
{
return 0;
}
#endif

View file

@ -36,7 +36,5 @@ public:
static void CreateFolder(string folder);
static int64_t GetFileModificationTime(string filepath);
static string CombinePath(string folder, string filename);
};