1 // Copyright 2008 Dolphin Emulator Project
2 // Licensed under GPLv2+
3 // Refer to the license.txt file included.
4 
5 #include "Core/State.h"
6 
7 #include <lzo/lzo1x.h>
8 #include <map>
9 #include <mutex>
10 #include <string>
11 #include <thread>
12 #include <utility>
13 #include <vector>
14 
15 #include <fmt/format.h>
16 
17 #include "Common/ChunkFile.h"
18 #include "Common/CommonTypes.h"
19 #include "Common/Event.h"
20 #include "Common/File.h"
21 #include "Common/FileUtil.h"
22 #include "Common/MsgHandler.h"
23 #include "Common/ScopeGuard.h"
24 #include "Common/Thread.h"
25 #include "Common/Timer.h"
26 #include "Common/Version.h"
27 
28 #include "Core/ConfigManager.h"
29 #include "Core/Core.h"
30 #include "Core/CoreTiming.h"
31 #include "Core/GeckoCode.h"
32 #include "Core/HW/HW.h"
33 #include "Core/HW/Memmap.h"
34 #include "Core/HW/Wiimote.h"
35 #include "Core/Host.h"
36 #include "Core/Movie.h"
37 #include "Core/NetPlayClient.h"
38 #include "Core/PowerPC/PowerPC.h"
39 
40 #include "VideoCommon/FrameDump.h"
41 #include "VideoCommon/OnScreenDisplay.h"
42 #include "VideoCommon/VideoBackendBase.h"
43 
44 namespace State
45 {
46 #if defined(__LZO_STRICT_16BIT)
47 static const u32 IN_LEN = 8 * 1024u;
48 #elif defined(LZO_ARCH_I086) && !defined(LZO_HAVE_MM_HUGE_ARRAY)
49 static const u32 IN_LEN = 60 * 1024u;
50 #else
51 static const u32 IN_LEN = 128 * 1024u;
52 #endif
53 
54 static const u32 OUT_LEN = IN_LEN + (IN_LEN / 16) + 64 + 3;
55 
56 static unsigned char __LZO_MMODEL out[OUT_LEN];
57 
58 #define HEAP_ALLOC(var, size)                                                                      \
59   lzo_align_t __LZO_MMODEL var[((size) + (sizeof(lzo_align_t) - 1)) / sizeof(lzo_align_t)]
60 
61 static HEAP_ALLOC(wrkmem, LZO1X_1_MEM_COMPRESS);
62 
63 static AfterLoadCallbackFunc s_on_after_load_callback;
64 
65 // Temporary undo state buffer
66 static std::vector<u8> g_undo_load_buffer;
67 static std::vector<u8> g_current_buffer;
68 static bool s_load_or_save_in_progress;
69 
70 static std::mutex g_cs_undo_load_buffer;
71 static std::mutex g_cs_current_buffer;
72 static Common::Event g_compressAndDumpStateSyncEvent;
73 
74 static std::thread g_save_thread;
75 
76 // Don't forget to increase this after doing changes on the savestate system
77 constexpr u32 STATE_VERSION = 124;  // Last changed in PR 9097
78 
79 // Maps savestate versions to Dolphin versions.
80 // Versions after 42 don't need to be added to this list,
81 // because they save the exact Dolphin version to savestates.
82 static const std::map<u32, std::pair<std::string, std::string>> s_old_versions = {
83     // The 16 -> 17 change modified the size of StateHeader,
84     // so versions older than that can't even be decompressed anymore
85     {17, {"3.5-1311", "3.5-1364"}}, {18, {"3.5-1366", "3.5-1371"}}, {19, {"3.5-1372", "3.5-1408"}},
86     {20, {"3.5-1409", "4.0-704"}},  {21, {"4.0-705", "4.0-889"}},   {22, {"4.0-905", "4.0-1871"}},
87     {23, {"4.0-1873", "4.0-1900"}}, {24, {"4.0-1902", "4.0-1919"}}, {25, {"4.0-1921", "4.0-1936"}},
88     {26, {"4.0-1939", "4.0-1959"}}, {27, {"4.0-1961", "4.0-2018"}}, {28, {"4.0-2020", "4.0-2291"}},
89     {29, {"4.0-2293", "4.0-2360"}}, {30, {"4.0-2362", "4.0-2628"}}, {31, {"4.0-2632", "4.0-3331"}},
90     {32, {"4.0-3334", "4.0-3340"}}, {33, {"4.0-3342", "4.0-3373"}}, {34, {"4.0-3376", "4.0-3402"}},
91     {35, {"4.0-3409", "4.0-3603"}}, {36, {"4.0-3610", "4.0-4480"}}, {37, {"4.0-4484", "4.0-4943"}},
92     {38, {"4.0-4963", "4.0-5267"}}, {39, {"4.0-5279", "4.0-5525"}}, {40, {"4.0-5531", "4.0-5809"}},
93     {41, {"4.0-5811", "4.0-5923"}}, {42, {"4.0-5925", "4.0-5946"}}};
94 
95 enum
96 {
97   STATE_NONE = 0,
98   STATE_SAVE = 1,
99   STATE_LOAD = 2,
100 };
101 
102 static bool g_use_compression = true;
103 
EnableCompression(bool compression)104 void EnableCompression(bool compression)
105 {
106   g_use_compression = compression;
107 }
108 
109 // Returns true if state version matches current Dolphin state version, false otherwise.
DoStateVersion(PointerWrap & p,std::string * version_created_by)110 static bool DoStateVersion(PointerWrap& p, std::string* version_created_by)
111 {
112   u32 version = STATE_VERSION;
113   {
114     static const u32 COOKIE_BASE = 0xBAADBABE;
115     u32 cookie = version + COOKIE_BASE;
116     p.Do(cookie);
117     version = cookie - COOKIE_BASE;
118   }
119 
120   *version_created_by = Common::scm_rev_str;
121   if (version > 42)
122     p.Do(*version_created_by);
123   else
124     version_created_by->clear();
125 
126   if (version != STATE_VERSION)
127   {
128     if (version_created_by->empty() && s_old_versions.count(version))
129     {
130       // The savestate is from an old version that doesn't
131       // save the Dolphin version number to savestates, but
132       // by looking up the savestate version number, it is possible
133       // to know approximately which Dolphin version was used.
134 
135       std::pair<std::string, std::string> version_range = s_old_versions.find(version)->second;
136       std::string oldest_version = version_range.first;
137       std::string newest_version = version_range.second;
138 
139       *version_created_by = "Dolphin " + oldest_version + " - " + newest_version;
140     }
141 
142     return false;
143   }
144 
145   p.DoMarker("Version");
146   return true;
147 }
148 
DoState(PointerWrap & p)149 static void DoState(PointerWrap& p)
150 {
151   std::string version_created_by;
152   if (!DoStateVersion(p, &version_created_by))
153   {
154     const std::string message =
155         version_created_by.empty() ?
156             "This savestate was created using an incompatible version of Dolphin" :
157             "This savestate was created using the incompatible version " + version_created_by;
158     Core::DisplayMessage(message, OSD::Duration::NORMAL);
159     p.SetMode(PointerWrap::MODE_MEASURE);
160     return;
161   }
162 
163   bool is_wii = SConfig::GetInstance().bWii || SConfig::GetInstance().m_is_mios;
164   const bool is_wii_currently = is_wii;
165   p.Do(is_wii);
166   if (is_wii != is_wii_currently)
167   {
168     OSD::AddMessage(fmt::format("Cannot load a savestate created under {} mode in {} mode",
169                                 is_wii ? "Wii" : "GC", is_wii_currently ? "Wii" : "GC"),
170                     OSD::Duration::NORMAL, OSD::Color::RED);
171     p.SetMode(PointerWrap::MODE_MEASURE);
172     return;
173   }
174 
175   // Check to make sure the emulated memory sizes are the same as the savestate
176   u32 state_mem1_size = Memory::GetRamSizeReal();
177   u32 state_mem2_size = Memory::GetExRamSizeReal();
178   p.Do(state_mem1_size);
179   p.Do(state_mem2_size);
180   if (state_mem1_size != Memory::GetRamSizeReal() || state_mem2_size != Memory::GetExRamSizeReal())
181   {
182     OSD::AddMessage(fmt::format("Memory size mismatch!\n"
183                                 "Current | MEM1 {:08X} ({:3}MB)    MEM2 {:08X} ({:3}MB)\n"
184                                 "State   | MEM1 {:08X} ({:3}MB)    MEM2 {:08X} ({:3}MB)",
185                                 Memory::GetRamSizeReal(), Memory::GetRamSizeReal() / 0x100000U,
186                                 Memory::GetExRamSizeReal(), Memory::GetExRamSizeReal() / 0x100000U,
187                                 state_mem1_size, state_mem1_size / 0x100000U, state_mem2_size,
188                                 state_mem2_size / 0x100000U));
189     p.SetMode(PointerWrap::MODE_MEASURE);
190     return;
191   }
192 
193   // Movie must be done before the video backend, because the window is redrawn in the video backend
194   // state load, and the frame number must be up-to-date.
195   Movie::DoState(p);
196   p.DoMarker("Movie");
197 
198   // Begin with video backend, so that it gets a chance to clear its caches and writeback modified
199   // things to RAM
200   g_video_backend->DoState(p);
201   p.DoMarker("video_backend");
202 
203   PowerPC::DoState(p);
204   p.DoMarker("PowerPC");
205   // CoreTiming needs to be restored before restoring Hardware because
206   // the controller code might need to schedule an event if the controller has changed.
207   CoreTiming::DoState(p);
208   p.DoMarker("CoreTiming");
209   HW::DoState(p);
210   p.DoMarker("HW");
211   if (SConfig::GetInstance().bWii)
212     Wiimote::DoState(p);
213   p.DoMarker("Wiimote");
214   Gecko::DoState(p);
215   p.DoMarker("Gecko");
216 
217 #if defined(HAVE_FFMPEG)
218   FrameDump::DoState();
219 #endif
220 }
221 
LoadFromBuffer(std::vector<u8> & buffer)222 void LoadFromBuffer(std::vector<u8>& buffer)
223 {
224   if (NetPlay::IsNetPlayRunning())
225   {
226     OSD::AddMessage("Loading savestates is disabled in Netplay to prevent desyncs");
227     return;
228   }
229 
230   Core::RunOnCPUThread(
231       [&] {
232         u8* ptr = &buffer[0];
233         PointerWrap p(&ptr, PointerWrap::MODE_READ);
234         DoState(p);
235       },
236       true);
237 }
238 
SaveToBuffer(std::vector<u8> & buffer)239 void SaveToBuffer(std::vector<u8>& buffer)
240 {
241   Core::RunOnCPUThread(
242       [&] {
243         u8* ptr = nullptr;
244         PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
245 
246         DoState(p);
247         const size_t buffer_size = reinterpret_cast<size_t>(ptr);
248         buffer.resize(buffer_size);
249 
250         ptr = &buffer[0];
251         p.SetMode(PointerWrap::MODE_WRITE);
252         DoState(p);
253       },
254       true);
255 }
256 
257 // return state number not in map
GetEmptySlot(std::map<double,int> m)258 static int GetEmptySlot(std::map<double, int> m)
259 {
260   for (int i = 1; i <= (int)NUM_STATES; i++)
261   {
262     bool found = false;
263     for (auto& p : m)
264     {
265       if (p.second == i)
266       {
267         found = true;
268         break;
269       }
270     }
271     if (!found)
272       return i;
273   }
274   return -1;
275 }
276 
277 static std::string MakeStateFilename(int number);
278 
279 // read state timestamps
GetSavedStates()280 static std::map<double, int> GetSavedStates()
281 {
282   StateHeader header;
283   std::map<double, int> m;
284   for (int i = 1; i <= (int)NUM_STATES; i++)
285   {
286     std::string filename = MakeStateFilename(i);
287     if (File::Exists(filename))
288     {
289       if (ReadHeader(filename, header))
290       {
291         double d = Common::Timer::GetDoubleTime() - header.time;
292 
293         // increase time until unique value is obtained
294         while (m.find(d) != m.end())
295           d += .001;
296 
297         m.emplace(d, i);
298       }
299     }
300   }
301   return m;
302 }
303 
304 struct CompressAndDumpState_args
305 {
306   std::vector<u8>* buffer_vector;
307   std::mutex* buffer_mutex;
308   std::string filename;
309   bool wait;
310 };
311 
CompressAndDumpState(CompressAndDumpState_args save_args)312 static void CompressAndDumpState(CompressAndDumpState_args save_args)
313 {
314   std::lock_guard<std::mutex> lk(*save_args.buffer_mutex);
315 
316   // ScopeGuard is used here to ensure that g_compressAndDumpStateSyncEvent.Set()
317   // will be called and that it will happen after the IOFile is closed.
318   // Both ScopeGuard's and IOFile's finalization occur at respective object destruction time.
319   // As Local (stack) objects are destructed in the reverse order of construction and "ScopeGuard
320   // on_exit"
321   // is created before the "IOFile f", it is guaranteed that the file will be finalized before
322   // the ScopeGuard's finalization (i.e. "g_compressAndDumpStateSyncEvent.Set()" call).
323   Common::ScopeGuard on_exit([]() { g_compressAndDumpStateSyncEvent.Set(); });
324   // If it is not required to wait, we call finalizer early (and it won't be called again at
325   // destruction).
326   if (!save_args.wait)
327     on_exit.Exit();
328 
329   const u8* const buffer_data = &(*(save_args.buffer_vector))[0];
330   const size_t buffer_size = (save_args.buffer_vector)->size();
331   std::string& filename = save_args.filename;
332 
333   // For easy debugging
334   Common::SetCurrentThreadName("SaveState thread");
335 
336   // Moving to last overwritten save-state
337   if (File::Exists(filename))
338   {
339     if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
340       File::Delete((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"));
341     if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm"))
342       File::Delete((File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm"));
343 
344     if (!File::Rename(filename, File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav"))
345       Core::DisplayMessage("Failed to move previous state to state undo backup", 1000);
346     else if (File::Exists(filename + ".dtm"))
347       File::Rename(filename + ".dtm", File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav.dtm");
348   }
349 
350   if ((Movie::IsMovieActive()) && !Movie::IsJustStartingRecordingInputFromSaveState())
351     Movie::SaveRecording(filename + ".dtm");
352   else if (!Movie::IsMovieActive())
353     File::Delete(filename + ".dtm");
354 
355   File::IOFile f(filename, "wb");
356   if (!f)
357   {
358     Core::DisplayMessage("Could not save state", 2000);
359     return;
360   }
361 
362   // Setting up the header
363   StateHeader header{};
364   SConfig::GetInstance().GetGameID().copy(header.gameID, std::size(header.gameID));
365   header.size = g_use_compression ? (u32)buffer_size : 0;
366   header.time = Common::Timer::GetDoubleTime();
367 
368   f.WriteArray(&header, 1);
369 
370   if (header.size != 0)  // non-zero header size means the state is compressed
371   {
372     lzo_uint i = 0;
373     while (true)
374     {
375       lzo_uint32 cur_len = 0;
376       lzo_uint out_len = 0;
377 
378       if ((i + IN_LEN) >= buffer_size)
379       {
380         cur_len = (lzo_uint32)(buffer_size - i);
381       }
382       else
383       {
384         cur_len = IN_LEN;
385       }
386 
387       if (lzo1x_1_compress(buffer_data + i, cur_len, out, &out_len, wrkmem) != LZO_E_OK)
388         PanicAlertT("Internal LZO Error - compression failed");
389 
390       // The size of the data to write is 'out_len'
391       f.WriteArray((lzo_uint32*)&out_len, 1);
392       f.WriteBytes(out, out_len);
393 
394       if (cur_len != IN_LEN)
395         break;
396 
397       i += cur_len;
398     }
399   }
400   else  // uncompressed
401   {
402     f.WriteBytes(buffer_data, buffer_size);
403   }
404 
405   Core::DisplayMessage(fmt::format("Saved State to {}", filename), 2000);
406   Host_UpdateMainFrame();
407 }
408 
SaveAs(const std::string & filename,bool wait)409 void SaveAs(const std::string& filename, bool wait)
410 {
411   if (s_load_or_save_in_progress)
412     return;
413 
414   s_load_or_save_in_progress = true;
415 
416   Core::RunOnCPUThread(
417       [&] {
418         // Measure the size of the buffer.
419         u8* ptr = nullptr;
420         PointerWrap p(&ptr, PointerWrap::MODE_MEASURE);
421         DoState(p);
422         const size_t buffer_size = reinterpret_cast<size_t>(ptr);
423 
424         // Then actually do the write.
425         {
426           std::lock_guard<std::mutex> lk(g_cs_current_buffer);
427           g_current_buffer.resize(buffer_size);
428           ptr = &g_current_buffer[0];
429           p.SetMode(PointerWrap::MODE_WRITE);
430           DoState(p);
431         }
432 
433         if (p.GetMode() == PointerWrap::MODE_WRITE)
434         {
435           Core::DisplayMessage("Saving State...", 1000);
436 
437           CompressAndDumpState_args save_args;
438           save_args.buffer_vector = &g_current_buffer;
439           save_args.buffer_mutex = &g_cs_current_buffer;
440           save_args.filename = filename;
441           save_args.wait = wait;
442 
443           Flush();
444           g_save_thread = std::thread(CompressAndDumpState, save_args);
445           g_compressAndDumpStateSyncEvent.Wait();
446         }
447         else
448         {
449           // someone aborted the save by changing the mode?
450           Core::DisplayMessage("Unable to save: Internal DoState Error", 4000);
451         }
452       },
453       true);
454 
455   s_load_or_save_in_progress = false;
456 }
457 
ReadHeader(const std::string & filename,StateHeader & header)458 bool ReadHeader(const std::string& filename, StateHeader& header)
459 {
460   Flush();
461   File::IOFile f(filename, "rb");
462   if (!f)
463   {
464     Core::DisplayMessage("State not found", 2000);
465     return false;
466   }
467 
468   f.ReadArray(&header, 1);
469   return true;
470 }
471 
GetInfoStringOfSlot(int slot,bool translate)472 std::string GetInfoStringOfSlot(int slot, bool translate)
473 {
474   std::string filename = MakeStateFilename(slot);
475   if (!File::Exists(filename))
476     return translate ? Common::GetStringT("Empty") : "Empty";
477 
478   State::StateHeader header;
479   if (!ReadHeader(filename, header))
480     return translate ? Common::GetStringT("Unknown") : "Unknown";
481 
482   return Common::Timer::GetDateTimeFormatted(header.time);
483 }
484 
LoadFileStateData(const std::string & filename,std::vector<u8> & ret_data)485 static void LoadFileStateData(const std::string& filename, std::vector<u8>& ret_data)
486 {
487   Flush();
488   File::IOFile f(filename, "rb");
489   if (!f)
490   {
491     Core::DisplayMessage("State not found", 2000);
492     return;
493   }
494 
495   StateHeader header;
496   f.ReadArray(&header, 1);
497 
498   if (strncmp(SConfig::GetInstance().GetGameID().c_str(), header.gameID, 6))
499   {
500     Core::DisplayMessage(fmt::format("State belongs to a different game (ID {})",
501                                      std::string_view{header.gameID, std::size(header.gameID)}),
502                          2000);
503     return;
504   }
505 
506   std::vector<u8> buffer;
507 
508   if (header.size != 0)  // non-zero size means the state is compressed
509   {
510     Core::DisplayMessage("Decompressing State...", 500);
511 
512     buffer.resize(header.size);
513 
514     lzo_uint i = 0;
515     while (true)
516     {
517       lzo_uint32 cur_len = 0;  // number of bytes to read
518       lzo_uint new_len = 0;    // number of bytes to write
519 
520       if (!f.ReadArray(&cur_len, 1))
521         break;
522 
523       f.ReadBytes(out, cur_len);
524       const int res = lzo1x_decompress(out, cur_len, &buffer[i], &new_len, nullptr);
525       if (res != LZO_E_OK)
526       {
527         // This doesn't seem to happen anymore.
528         PanicAlertT("Internal LZO Error - decompression failed (%d) (%li, %li) \n"
529                     "Try loading the state again",
530                     res, i, new_len);
531         return;
532       }
533 
534       i += new_len;
535     }
536   }
537   else  // uncompressed
538   {
539     const size_t size = (size_t)(f.GetSize() - sizeof(StateHeader));
540     buffer.resize(size);
541 
542     if (!f.ReadBytes(&buffer[0], size))
543     {
544       PanicAlert("wtf? reading bytes: %zu", size);
545       return;
546     }
547   }
548 
549   // all good
550   ret_data.swap(buffer);
551 }
552 
LoadAs(const std::string & filename)553 void LoadAs(const std::string& filename)
554 {
555   if (!Core::IsRunning() || s_load_or_save_in_progress)
556   {
557     return;
558   }
559   else if (NetPlay::IsNetPlayRunning())
560   {
561     OSD::AddMessage("Loading savestates is disabled in Netplay to prevent desyncs");
562     return;
563   }
564 
565   s_load_or_save_in_progress = true;
566 
567   Core::RunOnCPUThread(
568       [&] {
569         // Save temp buffer for undo load state
570         if (!Movie::IsJustStartingRecordingInputFromSaveState())
571         {
572           std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
573           SaveToBuffer(g_undo_load_buffer);
574           if (Movie::IsMovieActive())
575             Movie::SaveRecording(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
576           else if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm"))
577             File::Delete(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
578         }
579 
580         bool loaded = false;
581         bool loadedSuccessfully = false;
582 
583         // brackets here are so buffer gets freed ASAP
584         {
585           std::vector<u8> buffer;
586           LoadFileStateData(filename, buffer);
587 
588           if (!buffer.empty())
589           {
590             u8* ptr = &buffer[0];
591             PointerWrap p(&ptr, PointerWrap::MODE_READ);
592             DoState(p);
593             loaded = true;
594             loadedSuccessfully = (p.GetMode() == PointerWrap::MODE_READ);
595           }
596         }
597 
598         if (loaded)
599         {
600           if (loadedSuccessfully)
601           {
602             Core::DisplayMessage(fmt::format("Loaded state from {}", filename), 2000);
603             if (File::Exists(filename + ".dtm"))
604               Movie::LoadInput(filename + ".dtm");
605             else if (!Movie::IsJustStartingRecordingInputFromSaveState() &&
606                      !Movie::IsJustStartingPlayingInputFromSaveState())
607               Movie::EndPlayInput(false);
608           }
609           else
610           {
611             Core::DisplayMessage("The savestate could not be loaded", OSD::Duration::NORMAL);
612 
613             // since we could be in an inconsistent state now (and might crash or whatever), undo.
614             UndoLoadState();
615           }
616         }
617 
618         if (s_on_after_load_callback)
619           s_on_after_load_callback();
620       },
621       true);
622 
623   s_load_or_save_in_progress = false;
624 }
625 
SetOnAfterLoadCallback(AfterLoadCallbackFunc callback)626 void SetOnAfterLoadCallback(AfterLoadCallbackFunc callback)
627 {
628   s_on_after_load_callback = std::move(callback);
629 }
630 
Init()631 void Init()
632 {
633   if (lzo_init() != LZO_E_OK)
634     PanicAlertT("Internal LZO Error - lzo_init() failed");
635 }
636 
Shutdown()637 void Shutdown()
638 {
639   Flush();
640 
641   // swapping with an empty vector, rather than clear()ing
642   // this gives a better guarantee to free the allocated memory right NOW (as opposed to, actually,
643   // never)
644   {
645     std::lock_guard<std::mutex> lk(g_cs_current_buffer);
646     std::vector<u8>().swap(g_current_buffer);
647   }
648 
649   {
650     std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
651     std::vector<u8>().swap(g_undo_load_buffer);
652   }
653 }
654 
MakeStateFilename(int number)655 static std::string MakeStateFilename(int number)
656 {
657   return fmt::format("{}{}.s{:02d}", File::GetUserPath(D_STATESAVES_IDX),
658                      SConfig::GetInstance().GetGameID(), number);
659 }
660 
Save(int slot,bool wait)661 void Save(int slot, bool wait)
662 {
663   SaveAs(MakeStateFilename(slot), wait);
664 }
665 
Load(int slot)666 void Load(int slot)
667 {
668   LoadAs(MakeStateFilename(slot));
669 }
670 
LoadLastSaved(int i)671 void LoadLastSaved(int i)
672 {
673   std::map<double, int> savedStates = GetSavedStates();
674 
675   if (i > (int)savedStates.size())
676     Core::DisplayMessage("State doesn't exist", 2000);
677   else
678   {
679     std::map<double, int>::iterator it = savedStates.begin();
680     std::advance(it, i - 1);
681     Load(it->second);
682   }
683 }
684 
685 // must wait for state to be written because it must know if all slots are taken
SaveFirstSaved()686 void SaveFirstSaved()
687 {
688   std::map<double, int> savedStates = GetSavedStates();
689 
690   // save to an empty slot
691   if (savedStates.size() < NUM_STATES)
692     Save(GetEmptySlot(savedStates), true);
693   // overwrite the oldest state
694   else
695   {
696     std::map<double, int>::iterator it = savedStates.begin();
697     std::advance(it, savedStates.size() - 1);
698     Save(it->second, true);
699   }
700 }
701 
Flush()702 void Flush()
703 {
704   // If already saving state, wait for it to finish
705   if (g_save_thread.joinable())
706     g_save_thread.join();
707 }
708 
709 // Load the last state before loading the state
UndoLoadState()710 void UndoLoadState()
711 {
712   std::lock_guard<std::mutex> lk(g_cs_undo_load_buffer);
713   if (!g_undo_load_buffer.empty())
714   {
715     if (File::Exists(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm") || (!Movie::IsMovieActive()))
716     {
717       LoadFromBuffer(g_undo_load_buffer);
718       if (Movie::IsMovieActive())
719         Movie::LoadInput(File::GetUserPath(D_STATESAVES_IDX) + "undo.dtm");
720     }
721     else
722     {
723       PanicAlertT("No undo.dtm found, aborting undo load state to prevent movie desyncs");
724     }
725   }
726   else
727   {
728     PanicAlertT("There is nothing to undo!");
729   }
730 }
731 
732 // Load the state that the last save state overwritten on
UndoSaveState()733 void UndoSaveState()
734 {
735   LoadAs(File::GetUserPath(D_STATESAVES_IDX) + "lastState.sav");
736 }
737 
738 }  // namespace State
739