1 #include "stdafx.h"
2 #include "../Utilities/FolderUtilities.h"
3 #include "../Utilities/ZipWriter.h"
4 #include "../Utilities/ZipReader.h"
5 #include "SaveStateManager.h"
6 #include "MessageManager.h"
7 #include "Console.h"
8 #include "EmulationSettings.h"
9 #include "VideoDecoder.h"
10 #include "Debugger.h"
11 #include "MovieManager.h"
12 #include "RomData.h"
13 
SaveStateManager(shared_ptr<Console> console)14 SaveStateManager::SaveStateManager(shared_ptr<Console> console)
15 {
16 	_console = console;
17 	_lastIndex = 1;
18 }
19 
GetStateFilepath(int stateIndex)20 string SaveStateManager::GetStateFilepath(int stateIndex)
21 {
22 	string folder = FolderUtilities::GetSaveStateFolder();
23 	string filename = FolderUtilities::GetFilename(_console->GetRomInfo().RomName, false) + "_" + std::to_string(stateIndex) + ".mst";
24 	return FolderUtilities::CombinePath(folder, filename);
25 }
26 
GetStateInfo(int stateIndex)27 uint64_t SaveStateManager::GetStateInfo(int stateIndex)
28 {
29 	string filepath = SaveStateManager::GetStateFilepath(stateIndex);
30 	ifstream file(filepath, ios::in | ios::binary);
31 
32 	if(file) {
33 		file.close();
34 		return FolderUtilities::GetFileModificationTime(filepath);
35 	}
36 	return 0;
37 }
38 
MoveToNextSlot()39 void SaveStateManager::MoveToNextSlot()
40 {
41 	_lastIndex = (_lastIndex % MaxIndex) + 1;
42 	MessageManager::DisplayMessage("SaveStates", "SaveStateSlotSelected", std::to_string(_lastIndex));
43 }
44 
MoveToPreviousSlot()45 void SaveStateManager::MoveToPreviousSlot()
46 {
47 	_lastIndex = (_lastIndex == 1 ? SaveStateManager::MaxIndex : (_lastIndex - 1));
48 	MessageManager::DisplayMessage("SaveStates", "SaveStateSlotSelected", std::to_string(_lastIndex));
49 }
50 
SaveState()51 void SaveStateManager::SaveState()
52 {
53 	SaveState(_lastIndex);
54 }
55 
LoadState()56 bool SaveStateManager::LoadState()
57 {
58 	return LoadState(_lastIndex);
59 }
60 
GetSaveStateHeader(ostream & stream)61 void SaveStateManager::GetSaveStateHeader(ostream &stream)
62 {
63 	uint32_t emuVersion = EmulationSettings::GetMesenVersion();
64 	uint32_t formatVersion = SaveStateManager::FileFormatVersion;
65 	stream.write("MST", 3);
66 	stream.write((char*)&emuVersion, sizeof(emuVersion));
67 	stream.write((char*)&formatVersion, sizeof(uint32_t));
68 
69 	RomInfo romInfo = _console->GetRomInfo();
70 	stream.write((char*)&romInfo.MapperID, sizeof(uint16_t));
71 	stream.write((char*)&romInfo.SubMapperID, sizeof(uint8_t));
72 
73 	string sha1Hash = romInfo.Hash.Sha1;
74 	stream.write(sha1Hash.c_str(), sha1Hash.size());
75 
76 	string romName = romInfo.RomName;
77 	uint32_t nameLength = (uint32_t)romName.size();
78 	stream.write((char*)&nameLength, sizeof(uint32_t));
79 	stream.write(romName.c_str(), romName.size());
80 }
81 
SaveState(ostream & stream)82 void SaveStateManager::SaveState(ostream &stream)
83 {
84 	GetSaveStateHeader(stream);
85 	_console->SaveState(stream);
86 }
87 
SaveState(string filepath)88 bool SaveStateManager::SaveState(string filepath)
89 {
90 	ofstream file(filepath, ios::out | ios::binary);
91 
92 	if(file) {
93 		_console->Pause();
94 		SaveState(file);
95 		file.close();
96 
97 		shared_ptr<Debugger> debugger = _console->GetDebugger(false);
98 		if(debugger) {
99 			debugger->ProcessEvent(EventType::StateSaved);
100 		}
101 
102 		_console->Resume();
103 		return true;
104 	}
105 	return false;
106 }
107 
SaveState(int stateIndex,bool displayMessage)108 void SaveStateManager::SaveState(int stateIndex, bool displayMessage)
109 {
110 	string filepath = SaveStateManager::GetStateFilepath(stateIndex);
111 	if(SaveState(filepath)) {
112 		if(displayMessage) {
113 			MessageManager::DisplayMessage("SaveStates", "SaveStateSaved", std::to_string(stateIndex));
114 		}
115 	}
116 }
117 
LoadState(istream & stream,bool hashCheckRequired)118 bool SaveStateManager::LoadState(istream &stream, bool hashCheckRequired)
119 {
120 	char header[3];
121 	stream.read(header, 3);
122 	if(memcmp(header, "MST", 3) == 0) {
123 		uint32_t emuVersion, fileFormatVersion;
124 
125 		stream.read((char*)&emuVersion, sizeof(emuVersion));
126 		if(emuVersion > EmulationSettings::GetMesenVersion()) {
127 			MessageManager::DisplayMessage("SaveStates", "SaveStateNewerVersion");
128 			return false;
129 		}
130 
131 		stream.read((char*)&fileFormatVersion, sizeof(fileFormatVersion));
132 		if(fileFormatVersion <= 10) {
133 			MessageManager::DisplayMessage("SaveStates", "SaveStateIncompatibleVersion");
134 			return false;
135 		} else {
136 			int32_t mapperId = -1;
137 			int32_t subMapperId = -1;
138 			uint16_t id;
139 			uint8_t sid;
140 			stream.read((char*)&id, sizeof(uint16_t));
141 			stream.read((char*)&sid, sizeof(uint8_t));
142 			mapperId = id;
143 			subMapperId = sid;
144 
145 			char hash[41] = {};
146 			stream.read(hash, 40);
147 
148 			uint32_t nameLength = 0;
149 			stream.read((char*)&nameLength, sizeof(uint32_t));
150 
151 			vector<char> nameBuffer(nameLength);
152 			stream.read(nameBuffer.data(), nameBuffer.size());
153 			string romName(nameBuffer.data(), nameLength);
154 
155 			RomInfo romInfo = _console->GetRomInfo();
156 			bool gameLoaded = !romInfo.Hash.Sha1.empty();
157 			if(romInfo.Hash.Sha1 != string(hash)) {
158 				//CRC doesn't match
159 				if(!_console->GetSettings()->CheckFlag(EmulationFlags::AllowMismatchingSaveState) || !gameLoaded ||
160 					romInfo.MapperID != mapperId || romInfo.SubMapperID != subMapperId)
161 				{
162 					//If mismatching states aren't allowed, or a game isn't loaded, or the mapper types don't match, try to find and load the matching ROM
163 					HashInfo info;
164 					info.Sha1 = hash;
165 					if(!_console->LoadMatchingRom(romName, info)) {
166 						MessageManager::DisplayMessage("SaveStates", "SaveStateMissingRom", romName);
167 						return false;
168 					}
169 				}
170 			}
171 		}
172 
173 		//Stop any movie that might have been playing/recording if a state is loaded
174 		//(Note: Loading a state is disabled in the UI while a movie is playing/recording)
175 		MovieManager::Stop();
176 
177 		_console->LoadState(stream, fileFormatVersion);
178 
179 		return true;
180 	}
181 	MessageManager::DisplayMessage("SaveStates", "SaveStateInvalidFile");
182 	return false;
183 }
184 
LoadState(string filepath,bool hashCheckRequired)185 bool SaveStateManager::LoadState(string filepath, bool hashCheckRequired)
186 {
187 	ifstream file(filepath, ios::in | ios::binary);
188 	bool result = false;
189 
190 	if(file.good()) {
191 		_console->Pause();
192 		if(LoadState(file, hashCheckRequired)) {
193 			result = true;
194 		}
195 		file.close();
196 		shared_ptr<Debugger> debugger = _console->GetDebugger(false);
197 		if(debugger) {
198 			debugger->ProcessEvent(EventType::StateLoaded);
199 		}
200 		_console->Resume();
201 	} else {
202 		MessageManager::DisplayMessage("SaveStates", "SaveStateEmpty");
203 	}
204 
205 	return result;
206 }
207 
LoadState(int stateIndex)208 bool SaveStateManager::LoadState(int stateIndex)
209 {
210 	string filepath = SaveStateManager::GetStateFilepath(stateIndex);
211 	if(LoadState(filepath, false)) {
212 		MessageManager::DisplayMessage("SaveStates", "SaveStateLoaded", std::to_string(stateIndex));
213 		return true;
214 	}
215 	return false;
216 }
217 
SaveRecentGame(string romName,string romPath,string patchPath)218 void SaveStateManager::SaveRecentGame(string romName, string romPath, string patchPath)
219 {
220 	if(!_console->GetSettings()->CheckFlag(EmulationFlags::ConsoleMode) && !_console->GetSettings()->CheckFlag(EmulationFlags::DisableGameSelectionScreen) && _console->GetRomInfo().Format != RomFormat::Nsf) {
221 		string filename = FolderUtilities::GetFilename(_console->GetRomInfo().RomName, false) + ".rgd";
222 		ZipWriter writer;
223 		writer.Initialize(FolderUtilities::CombinePath(FolderUtilities::GetRecentGamesFolder(), filename));
224 
225 		std::stringstream pngStream;
226 		_console->GetVideoDecoder()->TakeScreenshot(pngStream);
227 		writer.AddFile(pngStream, "Screenshot.png");
228 
229 		std::stringstream stateStream;
230 		SaveStateManager::SaveState(stateStream);
231 		writer.AddFile(stateStream, "Savestate.mst");
232 
233 		std::stringstream romInfoStream;
234 		romInfoStream << romName << std::endl;
235 		romInfoStream << romPath << std::endl;
236 		romInfoStream << patchPath << std::endl;
237 		writer.AddFile(romInfoStream, "RomInfo.txt");
238 		writer.Save();
239 	}
240 }
241 
LoadRecentGame(string filename,bool resetGame)242 void SaveStateManager::LoadRecentGame(string filename, bool resetGame)
243 {
244 	ZipReader reader;
245 	reader.LoadArchive(filename);
246 
247 	stringstream romInfoStream, stateStream;
248 	reader.GetStream("RomInfo.txt", romInfoStream);
249 	reader.GetStream("Savestate.mst", stateStream);
250 
251 	string romName, romPath, patchPath;
252 	std::getline(romInfoStream, romName);
253 	std::getline(romInfoStream, romPath);
254 	std::getline(romInfoStream, patchPath);
255 
256 	_console->Pause();
257 	try {
258 		if(_console->Initialize(romPath, patchPath)) {
259 			if(!resetGame) {
260 				SaveStateManager::LoadState(stateStream, false);
261 			}
262 		}
263 	} catch(std::exception ex) {
264 		_console->Stop();
265 	}
266 	_console->Resume();
267 }