1 // Copyright (c) 2012- PPSSPP Project. 2 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU General Public License as published by 5 // the Free Software Foundation, version 2.0 or later versions. 6 7 // This program is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 // GNU General Public License 2.0 for more details. 11 12 // A copy of the GPL 2.0 should have been included with the program. 13 // If not, see http://www.gnu.org/licenses/ 14 15 // Official git repository and contact information can be found at 16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. 17 18 #include <algorithm> 19 #include <vector> 20 #include <thread> 21 #include <mutex> 22 23 #include "Common/Data/Text/I18n.h" 24 #include "Common/Thread/ThreadUtil.h" 25 #include "Common/Data/Text/Parsers.h" 26 27 #include "Common/File/FileUtil.h" 28 #include "Common/Serialize/Serializer.h" 29 #include "Common/Serialize/SerializeFuncs.h" 30 #include "Common/StringUtils.h" 31 #include "Common/TimeUtil.h" 32 33 #include "Core/SaveState.h" 34 #include "Core/Config.h" 35 #include "Core/Core.h" 36 #include "Core/CoreTiming.h" 37 #include "Core/Host.h" 38 #include "Core/Screenshot.h" 39 #include "Core/System.h" 40 #include "Core/FileSystems/MetaFileSystem.h" 41 #include "Core/ELF/ParamSFO.h" 42 #include "Core/HLE/HLE.h" 43 #include "Core/HLE/sceDisplay.h" 44 #include "Core/HLE/ReplaceTables.h" 45 #include "Core/HLE/sceKernel.h" 46 #include "Core/HLE/sceUtility.h" 47 #include "Core/MemMap.h" 48 #include "Core/MIPS/MIPS.h" 49 #include "Core/MIPS/JitCommon/JitBlockCache.h" 50 #include "HW/MemoryStick.h" 51 #include "GPU/GPUState.h" 52 53 #ifndef MOBILE_DEVICE 54 #include "Core/AVIDump.h" 55 #include "Core/HLE/__sceAudio.h" 56 #endif 57 58 // Slot number is visual only, -2 will display special message 59 constexpr int LOAD_UNDO_SLOT = -2; 60 61 namespace SaveState 62 { 63 struct SaveStart 64 { 65 void DoState(PointerWrap &p); 66 }; 67 68 enum OperationType 69 { 70 SAVESTATE_SAVE, 71 SAVESTATE_LOAD, 72 SAVESTATE_VERIFY, 73 SAVESTATE_REWIND, 74 SAVESTATE_SAVE_SCREENSHOT, 75 }; 76 77 struct Operation 78 { 79 // The slot number is for visual purposes only. Set to -1 for operations where we don't display a message for example. OperationSaveState::Operation80 Operation(OperationType t, const Path &f, int slot_, Callback cb, void *cbUserData_) 81 : type(t), filename(f), callback(cb), slot(slot_), cbUserData(cbUserData_) 82 { 83 } 84 85 OperationType type; 86 Path filename; 87 Callback callback; 88 int slot; 89 void *cbUserData; 90 }; 91 SaveToRam(std::vector<u8> & data)92 CChunkFileReader::Error SaveToRam(std::vector<u8> &data) { 93 SaveStart state; 94 size_t sz = CChunkFileReader::MeasurePtr(state); 95 if (data.size() < sz) 96 data.resize(sz); 97 return CChunkFileReader::SavePtr(&data[0], state, sz); 98 } 99 LoadFromRam(std::vector<u8> & data,std::string * errorString)100 CChunkFileReader::Error LoadFromRam(std::vector<u8> &data, std::string *errorString) { 101 SaveStart state; 102 return CChunkFileReader::LoadPtr(&data[0], state, errorString); 103 } 104 105 struct StateRingbuffer 106 { StateRingbufferSaveState::StateRingbuffer107 StateRingbuffer(int size) : first_(0), next_(0), size_(size), base_(-1) 108 { 109 states_.resize(size); 110 baseMapping_.resize(size); 111 } 112 SaveSaveState::StateRingbuffer113 CChunkFileReader::Error Save() 114 { 115 std::lock_guard<std::mutex> guard(lock_); 116 117 int n = next_++ % size_; 118 if ((next_ % size_) == first_) 119 ++first_; 120 121 static std::vector<u8> buffer; 122 std::vector<u8> *compressBuffer = &buffer; 123 CChunkFileReader::Error err; 124 125 if (base_ == -1 || ++baseUsage_ > BASE_USAGE_INTERVAL) 126 { 127 base_ = (base_ + 1) % ARRAY_SIZE(bases_); 128 baseUsage_ = 0; 129 err = SaveToRam(bases_[base_]); 130 // Let's not bother savestating twice. 131 compressBuffer = &bases_[base_]; 132 } 133 else 134 err = SaveToRam(buffer); 135 136 if (err == CChunkFileReader::ERROR_NONE) 137 ScheduleCompress(&states_[n], compressBuffer, &bases_[base_]); 138 else 139 states_[n].clear(); 140 baseMapping_[n] = base_; 141 return err; 142 } 143 RestoreSaveState::StateRingbuffer144 CChunkFileReader::Error Restore(std::string *errorString) 145 { 146 std::lock_guard<std::mutex> guard(lock_); 147 148 // No valid states left. 149 if (Empty()) 150 return CChunkFileReader::ERROR_BAD_FILE; 151 152 int n = (--next_ + size_) % size_; 153 if (states_[n].empty()) 154 return CChunkFileReader::ERROR_BAD_FILE; 155 156 static std::vector<u8> buffer; 157 LockedDecompress(buffer, states_[n], bases_[baseMapping_[n]]); 158 return LoadFromRam(buffer, errorString); 159 } 160 ScheduleCompressSaveState::StateRingbuffer161 void ScheduleCompress(std::vector<u8> *result, const std::vector<u8> *state, const std::vector<u8> *base) 162 { 163 if (compressThread_.joinable()) 164 compressThread_.join(); 165 compressThread_ = std::thread([=]{ 166 SetCurrentThreadName("SaveStateCompress"); 167 Compress(*result, *state, *base); 168 }); 169 } 170 CompressSaveState::StateRingbuffer171 void Compress(std::vector<u8> &result, const std::vector<u8> &state, const std::vector<u8> &base) 172 { 173 std::lock_guard<std::mutex> guard(lock_); 174 // Bail if we were cleared before locking. 175 if (first_ == 0 && next_ == 0) 176 return; 177 178 result.clear(); 179 for (size_t i = 0; i < state.size(); i += BLOCK_SIZE) 180 { 181 int blockSize = std::min(BLOCK_SIZE, (int)(state.size() - i)); 182 if (i + blockSize > base.size() || memcmp(&state[i], &base[i], blockSize) != 0) 183 { 184 result.push_back(1); 185 result.insert(result.end(), state.begin() + i, state.begin() +i + blockSize); 186 } 187 else 188 result.push_back(0); 189 } 190 } 191 LockedDecompressSaveState::StateRingbuffer192 void LockedDecompress(std::vector<u8> &result, const std::vector<u8> &compressed, const std::vector<u8> &base) 193 { 194 result.clear(); 195 result.reserve(base.size()); 196 auto basePos = base.begin(); 197 for (size_t i = 0; i < compressed.size(); ) 198 { 199 if (compressed[i] == 0) 200 { 201 ++i; 202 int blockSize = std::min(BLOCK_SIZE, (int)(base.size() - result.size())); 203 result.insert(result.end(), basePos, basePos + blockSize); 204 basePos += blockSize; 205 } 206 else 207 { 208 ++i; 209 int blockSize = std::min(BLOCK_SIZE, (int)(compressed.size() - i)); 210 result.insert(result.end(), compressed.begin() + i, compressed.begin() + i + blockSize); 211 i += blockSize; 212 basePos += blockSize; 213 } 214 } 215 } 216 ClearSaveState::StateRingbuffer217 void Clear() 218 { 219 if (compressThread_.joinable()) 220 compressThread_.join(); 221 222 // This lock is mainly for shutdown. 223 std::lock_guard<std::mutex> guard(lock_); 224 first_ = 0; 225 next_ = 0; 226 } 227 EmptySaveState::StateRingbuffer228 bool Empty() const 229 { 230 return next_ == first_; 231 } 232 233 static const int BLOCK_SIZE; 234 // TODO: Instead, based on size of compressed state? 235 static const int BASE_USAGE_INTERVAL; 236 237 typedef std::vector<u8> StateBuffer; 238 239 int first_; 240 int next_; 241 int size_; 242 243 std::vector<StateBuffer> states_; 244 StateBuffer bases_[2]; 245 std::vector<int> baseMapping_; 246 std::mutex lock_; 247 std::thread compressThread_; 248 249 int base_; 250 int baseUsage_; 251 }; 252 253 static bool needsProcess = false; 254 static bool needsRestart = false; 255 static std::vector<Operation> pending; 256 static std::mutex mutex; 257 static int screenshotFailures = 0; 258 static bool hasLoadedState = false; 259 static const int STALE_STATE_USES = 2; 260 // 4 hours of total gameplay since the virtual PSP started the game. 261 static const u64 STALE_STATE_TIME = 4 * 3600 * 1000000ULL; 262 static int saveStateGeneration = 0; 263 static int saveDataGeneration = 0; 264 static int lastSaveDataGeneration = 0; 265 static std::string saveStateInitialGitVersion = ""; 266 267 // TODO: Should this be configurable? 268 static const int REWIND_NUM_STATES = 20; 269 static const int SCREENSHOT_FAILURE_RETRIES = 15; 270 static StateRingbuffer rewindStates(REWIND_NUM_STATES); 271 // TODO: Any reason for this to be configurable? 272 const static float rewindMaxWallFrequency = 1.0f; 273 static double rewindLastTime = 0.0f; 274 const int StateRingbuffer::BLOCK_SIZE = 8192; 275 const int StateRingbuffer::BASE_USAGE_INTERVAL = 15; 276 DoState(PointerWrap & p)277 void SaveStart::DoState(PointerWrap &p) 278 { 279 auto s = p.Section("SaveStart", 1, 2); 280 if (!s) 281 return; 282 283 if (s >= 2) { 284 // This only increments on save, of course. 285 ++saveStateGeneration; 286 Do(p, saveStateGeneration); 287 // This saves the first git version to create this save state (or generation of save states.) 288 if (saveStateInitialGitVersion.empty()) 289 saveStateInitialGitVersion = PPSSPP_GIT_VERSION; 290 Do(p, saveStateInitialGitVersion); 291 } else { 292 saveStateGeneration = 1; 293 } 294 if (s >= 3) { 295 // Keep track of savedata (not save states) too. 296 Do(p, saveDataGeneration); 297 } else { 298 saveDataGeneration = 0; 299 } 300 301 // Gotta do CoreTiming first since we'll restore into it. 302 CoreTiming::DoState(p); 303 304 // Memory is a bit tricky when jit is enabled, since there's emuhacks in it. 305 auto savedReplacements = SaveAndClearReplacements(); 306 if (MIPSComp::jit && p.mode == p.MODE_WRITE) 307 { 308 std::vector<u32> savedBlocks; 309 savedBlocks = MIPSComp::jit->SaveAndClearEmuHackOps(); 310 Memory::DoState(p); 311 MIPSComp::jit->RestoreSavedEmuHackOps(savedBlocks); 312 } 313 else 314 Memory::DoState(p); 315 RestoreSavedReplacements(savedReplacements); 316 317 MemoryStick_DoState(p); 318 currentMIPS->DoState(p); 319 HLEDoState(p); 320 __KernelDoState(p); 321 // Kernel object destructors might close open files, so do the filesystem last. 322 pspFileSystem.DoState(p); 323 } 324 Enqueue(SaveState::Operation op)325 void Enqueue(SaveState::Operation op) 326 { 327 std::lock_guard<std::mutex> guard(mutex); 328 pending.push_back(op); 329 330 // Don't actually run it until next frame. 331 // It's possible there might be a duplicate but it won't hurt us. 332 needsProcess = true; 333 Core_UpdateSingleStep(); 334 } 335 Load(const Path & filename,int slot,Callback callback,void * cbUserData)336 void Load(const Path &filename, int slot, Callback callback, void *cbUserData) 337 { 338 if (coreState == CoreState::CORE_RUNTIME_ERROR) 339 Core_EnableStepping(true); 340 Enqueue(Operation(SAVESTATE_LOAD, filename, slot, callback, cbUserData)); 341 } 342 Save(const Path & filename,int slot,Callback callback,void * cbUserData)343 void Save(const Path &filename, int slot, Callback callback, void *cbUserData) 344 { 345 if (coreState == CoreState::CORE_RUNTIME_ERROR) 346 Core_EnableStepping(true); 347 Enqueue(Operation(SAVESTATE_SAVE, filename, slot, callback, cbUserData)); 348 } 349 Verify(Callback callback,void * cbUserData)350 void Verify(Callback callback, void *cbUserData) 351 { 352 Enqueue(Operation(SAVESTATE_VERIFY, Path(), -1, callback, cbUserData)); 353 } 354 Rewind(Callback callback,void * cbUserData)355 void Rewind(Callback callback, void *cbUserData) 356 { 357 if (coreState == CoreState::CORE_RUNTIME_ERROR) 358 Core_EnableStepping(true); 359 Enqueue(Operation(SAVESTATE_REWIND, Path(), -1, callback, cbUserData)); 360 } 361 SaveScreenshot(const Path & filename,Callback callback,void * cbUserData)362 void SaveScreenshot(const Path &filename, Callback callback, void *cbUserData) 363 { 364 Enqueue(Operation(SAVESTATE_SAVE_SCREENSHOT, filename, -1, callback, cbUserData)); 365 } 366 CanRewind()367 bool CanRewind() 368 { 369 return !rewindStates.Empty(); 370 } 371 372 // Slot utilities 373 AppendSlotTitle(const std::string & filename,const std::string & title)374 std::string AppendSlotTitle(const std::string &filename, const std::string &title) { 375 char slotChar = 0; 376 auto detectSlot = [&](const std::string &ext) { 377 if (!endsWith(filename, std::string(".") + ext)) { 378 return false; 379 } 380 381 // Usually these are slots, let's check the slot # after the last '_'. 382 size_t slotNumPos = filename.find_last_of('_'); 383 if (slotNumPos == filename.npos) { 384 return false; 385 } 386 387 const size_t extLength = ext.length() + 1; 388 // If we take out the extension, '_', etc. we should be left with only a single digit. 389 if (slotNumPos + 1 + extLength != filename.length() - 1) { 390 return false; 391 } 392 393 slotChar = filename[slotNumPos + 1]; 394 if (slotChar < '0' || slotChar > '8') { 395 return false; 396 } 397 398 // Change from zero indexed to human friendly. 399 slotChar++; 400 return true; 401 }; 402 403 if (detectSlot(STATE_EXTENSION)) { 404 return StringFromFormat("%s (%c)", title.c_str(), slotChar); 405 } 406 if (detectSlot(UNDO_STATE_EXTENSION)) { 407 auto sy = GetI18NCategory("System"); 408 // Allow the number to be positioned where it makes sense. 409 std::string undo = sy->T("undo %c"); 410 return title + " (" + StringFromFormat(undo.c_str(), slotChar) + ")"; 411 } 412 413 // Couldn't detect, use the filename. 414 return title + " (" + filename + ")"; 415 } 416 GetTitle(const Path & filename)417 std::string GetTitle(const Path &filename) { 418 std::string title; 419 if (CChunkFileReader::GetFileTitle(filename, &title) == CChunkFileReader::ERROR_NONE) { 420 if (title.empty()) { 421 return filename.GetFilename(); 422 } 423 424 return AppendSlotTitle(filename.GetFilename(), title); 425 } 426 427 // The file can't be loaded - let's note that. 428 auto sy = GetI18NCategory("System"); 429 return filename.GetFilename() + " " + sy->T("(broken)"); 430 } 431 GenerateFullDiscId(const Path & gameFilename)432 std::string GenerateFullDiscId(const Path &gameFilename) { 433 std::string discId = g_paramSFO.GetValueString("DISC_ID"); 434 std::string discVer = g_paramSFO.GetValueString("DISC_VERSION"); 435 if (discId.empty()) { 436 discId = g_paramSFO.GenerateFakeID(); 437 discVer = "1.00"; 438 } 439 return StringFromFormat("%s_%s", discId.c_str(), discVer.c_str()); 440 } 441 GenerateSaveSlotFilename(const Path & gameFilename,int slot,const char * extension)442 Path GenerateSaveSlotFilename(const Path &gameFilename, int slot, const char *extension) 443 { 444 std::string filename = StringFromFormat("%s_%d.%s", GenerateFullDiscId(gameFilename).c_str(), slot, extension); 445 return GetSysDirectory(DIRECTORY_SAVESTATE) / filename; 446 } 447 GetCurrentSlot()448 int GetCurrentSlot() 449 { 450 return g_Config.iCurrentStateSlot; 451 } 452 NextSlot()453 void NextSlot() 454 { 455 g_Config.iCurrentStateSlot = (g_Config.iCurrentStateSlot + 1) % NUM_SLOTS; 456 } 457 DeleteIfExists(const Path & fn)458 static void DeleteIfExists(const Path &fn) { 459 // Just avoiding error messages. 460 if (File::Exists(fn)) { 461 File::Delete(fn); 462 } 463 } 464 RenameIfExists(const Path & from,const Path & to)465 static void RenameIfExists(const Path &from, const Path &to) { 466 if (File::Exists(from)) { 467 File::Rename(from, to); 468 } 469 } 470 SwapIfExists(const Path & from,const Path & to)471 static void SwapIfExists(const Path &from, const Path &to) { 472 Path temp = from.WithExtraExtension(".tmp"); 473 if (File::Exists(from)) { 474 File::Rename(from, temp); 475 File::Rename(to, from); 476 File::Rename(temp, to); 477 } 478 } 479 LoadSlot(const Path & gameFilename,int slot,Callback callback,void * cbUserData)480 void LoadSlot(const Path &gameFilename, int slot, Callback callback, void *cbUserData) 481 { 482 Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION); 483 if (!fn.empty()) { 484 // This add only 1 extra state, should we just always enable it? 485 if (g_Config.bEnableStateUndo) { 486 Path backup = GetSysDirectory(DIRECTORY_SAVESTATE) / LOAD_UNDO_NAME; 487 488 auto saveCallback = [=](Status status, const std::string &message, void *data) { 489 if (status != Status::FAILURE) { 490 DeleteIfExists(backup); 491 File::Rename(backup.WithExtraExtension(".tmp"), backup); 492 g_Config.sStateLoadUndoGame = GenerateFullDiscId(gameFilename); 493 g_Config.Save("Saving config for savestate last load undo"); 494 } else { 495 ERROR_LOG(SAVESTATE, "Saving load undo state failed: %s", message.c_str()); 496 } 497 Load(fn, slot, callback, cbUserData); 498 }; 499 500 if (!backup.empty()) { 501 Save(backup.WithExtraExtension(".tmp"), LOAD_UNDO_SLOT, saveCallback, cbUserData); 502 } else { 503 ERROR_LOG(SAVESTATE, "Saving load undo state failed. Error in the file system."); 504 Load(fn, slot, callback, cbUserData); 505 } 506 } else { 507 Load(fn, slot, callback, cbUserData); 508 } 509 } else { 510 auto sy = GetI18NCategory("System"); 511 if (callback) 512 callback(Status::FAILURE, sy->T("Failed to load state. Error in the file system."), cbUserData); 513 } 514 } 515 UndoLoad(const Path & gameFilename,Callback callback,void * cbUserData)516 bool UndoLoad(const Path &gameFilename, Callback callback, void *cbUserData) 517 { 518 if (g_Config.sStateLoadUndoGame != GenerateFullDiscId(gameFilename)) { 519 auto sy = GetI18NCategory("System"); 520 if (callback) 521 callback(Status::FAILURE, sy->T("Error: load undo state is from a different game"), cbUserData); 522 return false; 523 } 524 525 Path fn = GetSysDirectory(DIRECTORY_SAVESTATE) / LOAD_UNDO_NAME; 526 if (!fn.empty()) { 527 Load(fn, LOAD_UNDO_SLOT, callback, cbUserData); 528 return true; 529 } else { 530 auto sy = GetI18NCategory("System"); 531 if (callback) 532 callback(Status::FAILURE, sy->T("Failed to load state for load undo. Error in the file system."), cbUserData); 533 return false; 534 } 535 } 536 SaveSlot(const Path & gameFilename,int slot,Callback callback,void * cbUserData)537 void SaveSlot(const Path &gameFilename, int slot, Callback callback, void *cbUserData) 538 { 539 Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION); 540 Path shot = GenerateSaveSlotFilename(gameFilename, slot, SCREENSHOT_EXTENSION); 541 Path fnUndo = GenerateSaveSlotFilename(gameFilename, slot, UNDO_STATE_EXTENSION); 542 Path shotUndo = GenerateSaveSlotFilename(gameFilename, slot, UNDO_SCREENSHOT_EXTENSION); 543 if (!fn.empty()) { 544 auto renameCallback = [=](Status status, const std::string &message, void *data) { 545 if (status != Status::FAILURE) { 546 if (g_Config.bEnableStateUndo) { 547 DeleteIfExists(fnUndo); 548 RenameIfExists(fn, fnUndo); 549 g_Config.sStateUndoLastSaveGame = GenerateFullDiscId(gameFilename); 550 g_Config.iStateUndoLastSaveSlot = slot; 551 g_Config.Save("Saving config for savestate last save undo"); 552 } else { 553 DeleteIfExists(fn); 554 } 555 File::Rename(fn.WithExtraExtension(".tmp"), fn); 556 } 557 if (callback) { 558 callback(status, message, data); 559 } 560 }; 561 // Let's also create a screenshot. 562 if (g_Config.bEnableStateUndo) { 563 DeleteIfExists(shotUndo); 564 RenameIfExists(shot, shotUndo); 565 } 566 SaveScreenshot(shot, Callback(), 0); 567 Save(fn.WithExtraExtension(".tmp"), slot, renameCallback, cbUserData); 568 } else { 569 auto sy = GetI18NCategory("System"); 570 if (callback) 571 callback(Status::FAILURE, sy->T("Failed to save state. Error in the file system."), cbUserData); 572 } 573 } 574 UndoSaveSlot(const Path & gameFilename,int slot)575 bool UndoSaveSlot(const Path &gameFilename, int slot) { 576 Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION); 577 Path shot = GenerateSaveSlotFilename(gameFilename, slot, SCREENSHOT_EXTENSION); 578 Path fnUndo = GenerateSaveSlotFilename(gameFilename, slot, UNDO_STATE_EXTENSION); 579 Path shotUndo = GenerateSaveSlotFilename(gameFilename, slot, UNDO_SCREENSHOT_EXTENSION); 580 581 // Do nothing if there's no undo. 582 if (File::Exists(fnUndo)) { 583 // Swap them so they can undo again to redo. Mistakes happen. 584 SwapIfExists(shotUndo, shot); 585 SwapIfExists(fnUndo, fn); 586 return true; 587 } 588 589 return false; 590 } 591 592 UndoLastSave(const Path & gameFilename)593 bool UndoLastSave(const Path &gameFilename) { 594 if (g_Config.sStateUndoLastSaveGame != GenerateFullDiscId(gameFilename)) 595 return false; 596 597 return UndoSaveSlot(gameFilename, g_Config.iStateUndoLastSaveSlot); 598 } 599 HasSaveInSlot(const Path & gameFilename,int slot)600 bool HasSaveInSlot(const Path &gameFilename, int slot) 601 { 602 Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION); 603 return File::Exists(fn); 604 } 605 HasUndoSaveInSlot(const Path & gameFilename,int slot)606 bool HasUndoSaveInSlot(const Path &gameFilename, int slot) 607 { 608 Path fn = GenerateSaveSlotFilename(gameFilename, slot, UNDO_STATE_EXTENSION); 609 return File::Exists(fn); 610 } 611 HasUndoLastSave(const Path & gameFilename)612 bool HasUndoLastSave(const Path &gameFilename) 613 { 614 if (g_Config.sStateUndoLastSaveGame != GenerateFullDiscId(gameFilename)) 615 return false; 616 617 return HasUndoSaveInSlot(gameFilename, g_Config.iStateUndoLastSaveSlot); 618 } 619 HasScreenshotInSlot(const Path & gameFilename,int slot)620 bool HasScreenshotInSlot(const Path &gameFilename, int slot) 621 { 622 Path fn = GenerateSaveSlotFilename(gameFilename, slot, SCREENSHOT_EXTENSION); 623 return File::Exists(fn); 624 } 625 HasUndoLoad(const Path & gameFilename)626 bool HasUndoLoad(const Path &gameFilename) 627 { 628 Path fn = GetSysDirectory(DIRECTORY_SAVESTATE) / LOAD_UNDO_NAME; 629 return File::Exists(fn) && g_Config.sStateLoadUndoGame == GenerateFullDiscId(gameFilename); 630 } 631 operator <(const tm & t1,const tm & t2)632 bool operator < (const tm &t1, const tm &t2) { 633 if (t1.tm_year < t2.tm_year) return true; 634 if (t1.tm_year > t2.tm_year) return false; 635 if (t1.tm_mon < t2.tm_mon) return true; 636 if (t1.tm_mon > t2.tm_mon) return false; 637 if (t1.tm_mday < t2.tm_mday) return true; 638 if (t1.tm_mday > t2.tm_mday) return false; 639 if (t1.tm_hour < t2.tm_hour) return true; 640 if (t1.tm_hour > t2.tm_hour) return false; 641 if (t1.tm_min < t2.tm_min) return true; 642 if (t1.tm_min > t2.tm_min) return false; 643 if (t1.tm_sec < t2.tm_sec) return true; 644 if (t1.tm_sec > t2.tm_sec) return false; 645 return false; 646 } 647 operator >(const tm & t1,const tm & t2)648 bool operator > (const tm &t1, const tm &t2) { 649 if (t1.tm_year > t2.tm_year) return true; 650 if (t1.tm_year < t2.tm_year) return false; 651 if (t1.tm_mon > t2.tm_mon) return true; 652 if (t1.tm_mon < t2.tm_mon) return false; 653 if (t1.tm_mday > t2.tm_mday) return true; 654 if (t1.tm_mday < t2.tm_mday) return false; 655 if (t1.tm_hour > t2.tm_hour) return true; 656 if (t1.tm_hour < t2.tm_hour) return false; 657 if (t1.tm_min > t2.tm_min) return true; 658 if (t1.tm_min < t2.tm_min) return false; 659 if (t1.tm_sec > t2.tm_sec) return true; 660 if (t1.tm_sec < t2.tm_sec) return false; 661 return false; 662 } 663 operator !(const tm & t1)664 bool operator ! (const tm &t1) { 665 if (t1.tm_year || t1.tm_mon || t1.tm_mday || t1.tm_hour || t1.tm_min || t1.tm_sec) return false; 666 return true; 667 } 668 GetNewestSlot(const Path & gameFilename)669 int GetNewestSlot(const Path &gameFilename) { 670 int newestSlot = -1; 671 tm newestDate = {0}; 672 for (int i = 0; i < NUM_SLOTS; i++) { 673 Path fn = GenerateSaveSlotFilename(gameFilename, i, STATE_EXTENSION); 674 if (File::Exists(fn)) { 675 tm time; 676 bool success = File::GetModifTime(fn, time); 677 if (success && newestDate < time) { 678 newestDate = time; 679 newestSlot = i; 680 } 681 } 682 } 683 return newestSlot; 684 } 685 GetOldestSlot(const Path & gameFilename)686 int GetOldestSlot(const Path &gameFilename) { 687 int oldestSlot = -1; 688 tm oldestDate = {0}; 689 for (int i = 0; i < NUM_SLOTS; i++) { 690 Path fn = GenerateSaveSlotFilename(gameFilename, i, STATE_EXTENSION); 691 if (File::Exists(fn)) { 692 tm time; 693 bool success = File::GetModifTime(fn, time); 694 if (success && (!oldestDate || oldestDate > time)) { 695 oldestDate = time; 696 oldestSlot = i; 697 } 698 } 699 } 700 return oldestSlot; 701 } 702 GetSlotDateAsString(const Path & gameFilename,int slot)703 std::string GetSlotDateAsString(const Path &gameFilename, int slot) { 704 Path fn = GenerateSaveSlotFilename(gameFilename, slot, STATE_EXTENSION); 705 if (File::Exists(fn)) { 706 tm time; 707 if (File::GetModifTime(fn, time)) { 708 char buf[256]; 709 // TODO: Use local time format? Americans and some others might not like ISO standard :) 710 switch (g_Config.iDateFormat) { 711 case PSP_SYSTEMPARAM_DATE_FORMAT_YYYYMMDD: 712 strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &time); 713 break; 714 case PSP_SYSTEMPARAM_DATE_FORMAT_MMDDYYYY: 715 strftime(buf, sizeof(buf), "%m-%d-%Y %H:%M:%S", &time); 716 break; 717 case PSP_SYSTEMPARAM_DATE_FORMAT_DDMMYYYY: 718 strftime(buf, sizeof(buf), "%d-%m-%Y %H:%M:%S", &time); 719 break; 720 default: // Should never happen 721 return ""; 722 } 723 return std::string(buf); 724 } 725 } 726 return ""; 727 } 728 Flush()729 std::vector<Operation> Flush() 730 { 731 std::lock_guard<std::mutex> guard(mutex); 732 std::vector<Operation> copy = pending; 733 pending.clear(); 734 735 return copy; 736 } 737 HandleLoadFailure()738 bool HandleLoadFailure() 739 { 740 // Okay, first, let's give the rewind state a shot - maybe we can at least not reset entirely. 741 // Even if this was a rewind, maybe we can still load a previous one. 742 CChunkFileReader::Error result; 743 do { 744 std::string errorString; 745 result = rewindStates.Restore(&errorString); 746 } while (result == CChunkFileReader::ERROR_BROKEN_STATE); 747 748 if (result == CChunkFileReader::ERROR_NONE) { 749 return true; 750 } 751 752 // We tried, our only remaining option is to reset the game. 753 needsRestart = true; 754 // Make sure we don't proceed to run anything yet. 755 coreState = CORE_NEXTFRAME; 756 return false; 757 } 758 CheckRewindState()759 static inline void CheckRewindState() 760 { 761 if (gpuStats.numFlips % g_Config.iRewindFlipFrequency != 0) 762 return; 763 764 // For fast-forwarding, otherwise they may be useless and too close. 765 double now = time_now_d(); 766 float diff = now - rewindLastTime; 767 if (diff < rewindMaxWallFrequency) 768 return; 769 770 rewindLastTime = now; 771 DEBUG_LOG(BOOT, "Saving rewind state"); 772 rewindStates.Save(); 773 } 774 HasLoadedState()775 bool HasLoadedState() { 776 return hasLoadedState; 777 } 778 IsStale()779 bool IsStale() { 780 if (saveStateGeneration >= STALE_STATE_USES) { 781 return CoreTiming::GetGlobalTimeUs() > STALE_STATE_TIME; 782 } 783 return false; 784 } 785 IsOldVersion()786 bool IsOldVersion() { 787 if (saveStateInitialGitVersion.empty()) 788 return false; 789 790 Version state(saveStateInitialGitVersion); 791 Version gitVer(PPSSPP_GIT_VERSION); 792 if (!state.IsValid() || !gitVer.IsValid()) 793 return false; 794 795 return state < gitVer; 796 } 797 TriggerLoadWarnings(std::string & callbackMessage)798 static Status TriggerLoadWarnings(std::string &callbackMessage) { 799 auto sc = GetI18NCategory("Screen"); 800 801 if (g_Config.bHideStateWarnings) 802 return Status::SUCCESS; 803 804 if (IsStale()) { 805 // For anyone wondering why (too long to put on the screen in an osm): 806 // Using save states instead of saves simulates many hour play sessions. 807 // Sometimes this exposes game bugs that were rarely seen on real devices, 808 // because few people played on a real PSP for 10 hours straight. 809 callbackMessage = sc->T("Loaded. Save in game, restart, and load for less bugs."); 810 return Status::WARNING; 811 } 812 if (IsOldVersion()) { 813 // Save states also preserve bugs from old PPSSPP versions, so warn. 814 callbackMessage = sc->T("Loaded. Save in game, restart, and load for less bugs."); 815 return Status::WARNING; 816 } 817 // If the loaded state (saveDataGeneration) is older, the game may prevent saving again. 818 // This can happen with newer too, but ignore to/from 0 as a common likely safe case. 819 if (saveDataGeneration != lastSaveDataGeneration && saveDataGeneration != 0 && lastSaveDataGeneration != 0) { 820 if (saveDataGeneration < lastSaveDataGeneration) 821 callbackMessage = sc->T("Loaded. Game may refuse to save over newer savedata."); 822 else 823 callbackMessage = sc->T("Loaded. Game may refuse to save over different savedata."); 824 return Status::WARNING; 825 } 826 return Status::SUCCESS; 827 } 828 Process()829 void Process() 830 { 831 if (g_Config.iRewindFlipFrequency != 0 && gpuStats.numFlips != 0) 832 CheckRewindState(); 833 834 if (!needsProcess) 835 return; 836 needsProcess = false; 837 838 if (!__KernelIsRunning()) 839 { 840 ERROR_LOG(SAVESTATE, "Savestate failure: Unable to load without kernel, this should never happen."); 841 return; 842 } 843 844 std::vector<Operation> operations = Flush(); 845 SaveStart state; 846 847 for (size_t i = 0, n = operations.size(); i < n; ++i) 848 { 849 Operation &op = operations[i]; 850 CChunkFileReader::Error result; 851 Status callbackResult; 852 bool tempResult; 853 std::string callbackMessage; 854 std::string title; 855 856 auto sc = GetI18NCategory("Screen"); 857 const char *i18nLoadFailure = sc->T("Load savestate failed", ""); 858 const char *i18nSaveFailure = sc->T("Save State Failed", ""); 859 if (strlen(i18nLoadFailure) == 0) 860 i18nLoadFailure = sc->T("Failed to load state"); 861 if (strlen(i18nSaveFailure) == 0) 862 i18nSaveFailure = sc->T("Failed to save state"); 863 864 std::string slot_prefix = op.slot >= 0 ? StringFromFormat("(%d) ", op.slot + 1) : ""; 865 std::string errorString; 866 867 switch (op.type) 868 { 869 case SAVESTATE_LOAD: 870 INFO_LOG(SAVESTATE, "Loading state from '%s'", op.filename.c_str()); 871 // Use the state's latest version as a guess for saveStateInitialGitVersion. 872 result = CChunkFileReader::Load(op.filename, &saveStateInitialGitVersion, state, &errorString); 873 if (result == CChunkFileReader::ERROR_NONE) { 874 callbackMessage = op.slot != LOAD_UNDO_SLOT ? sc->T("Loaded State") : sc->T("State load undone"); 875 callbackResult = TriggerLoadWarnings(callbackMessage); 876 hasLoadedState = true; 877 Core_ResetException(); 878 879 if (!slot_prefix.empty()) 880 callbackMessage = slot_prefix + callbackMessage; 881 882 #ifndef MOBILE_DEVICE 883 if (g_Config.bSaveLoadResetsAVdumping) { 884 if (g_Config.bDumpFrames) { 885 AVIDump::Stop(); 886 AVIDump::Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); 887 } 888 if (g_Config.bDumpAudio) { 889 WAVDump::Reset(); 890 } 891 } 892 #endif 893 } else if (result == CChunkFileReader::ERROR_BROKEN_STATE) { 894 HandleLoadFailure(); 895 callbackMessage = std::string(i18nLoadFailure) + ": " + errorString; 896 ERROR_LOG(SAVESTATE, "Load state failure: %s", errorString.c_str()); 897 callbackResult = Status::FAILURE; 898 } else { 899 callbackMessage = sc->T(errorString.c_str(), i18nLoadFailure); 900 callbackResult = Status::FAILURE; 901 } 902 break; 903 904 case SAVESTATE_SAVE: 905 INFO_LOG(SAVESTATE, "Saving state to %s", op.filename.c_str()); 906 title = g_paramSFO.GetValueString("TITLE"); 907 if (title.empty()) { 908 // Homebrew title 909 title = PSP_CoreParameter().fileToStart.ToVisualString(); 910 std::size_t lslash = title.find_last_of("/"); 911 title = title.substr(lslash + 1); 912 } 913 result = CChunkFileReader::Save(op.filename, title, PPSSPP_GIT_VERSION, state); 914 if (result == CChunkFileReader::ERROR_NONE) { 915 callbackMessage = slot_prefix + sc->T("Saved State"); 916 callbackResult = Status::SUCCESS; 917 #ifndef MOBILE_DEVICE 918 if (g_Config.bSaveLoadResetsAVdumping) { 919 if (g_Config.bDumpFrames) { 920 AVIDump::Stop(); 921 AVIDump::Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); 922 } 923 if (g_Config.bDumpAudio) { 924 WAVDump::Reset(); 925 } 926 } 927 #endif 928 } else if (result == CChunkFileReader::ERROR_BROKEN_STATE) { 929 // TODO: What else might we want to do here? This should be very unusual. 930 callbackMessage = i18nSaveFailure; 931 ERROR_LOG(SAVESTATE, "Save state failure"); 932 callbackResult = Status::FAILURE; 933 } else { 934 callbackMessage = i18nSaveFailure; 935 callbackResult = Status::FAILURE; 936 } 937 break; 938 939 case SAVESTATE_VERIFY: 940 tempResult = CChunkFileReader::Verify(state) == CChunkFileReader::ERROR_NONE; 941 callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE; 942 if (tempResult) { 943 INFO_LOG(SAVESTATE, "Verified save state system"); 944 } else { 945 ERROR_LOG(SAVESTATE, "Save state system verification failed"); 946 } 947 break; 948 949 case SAVESTATE_REWIND: 950 INFO_LOG(SAVESTATE, "Rewinding to recent savestate snapshot"); 951 result = rewindStates.Restore(&errorString); 952 if (result == CChunkFileReader::ERROR_NONE) { 953 callbackMessage = sc->T("Loaded State"); 954 callbackResult = Status::SUCCESS; 955 hasLoadedState = true; 956 Core_ResetException(); 957 } else if (result == CChunkFileReader::ERROR_BROKEN_STATE) { 958 // Cripes. Good news is, we might have more. Let's try those too, better than a reset. 959 if (HandleLoadFailure()) { 960 // Well, we did rewind, even if too much... 961 callbackMessage = sc->T("Loaded State"); 962 callbackResult = Status::SUCCESS; 963 hasLoadedState = true; 964 Core_ResetException(); 965 } else { 966 callbackMessage = std::string(i18nLoadFailure) + ": " + errorString; 967 callbackResult = Status::FAILURE; 968 } 969 } else { 970 callbackMessage = std::string(i18nLoadFailure) + ": " + errorString; 971 callbackResult = Status::FAILURE; 972 } 973 break; 974 975 case SAVESTATE_SAVE_SCREENSHOT: 976 { 977 int maxRes = g_Config.iInternalResolution > 2 ? 2 : -1; 978 tempResult = TakeGameScreenshot(op.filename, ScreenshotFormat::JPG, SCREENSHOT_DISPLAY, nullptr, nullptr, maxRes); 979 callbackResult = tempResult ? Status::SUCCESS : Status::FAILURE; 980 if (!tempResult) { 981 ERROR_LOG(SAVESTATE, "Failed to take a screenshot for the savestate! %s", op.filename.c_str()); 982 if (screenshotFailures++ < SCREENSHOT_FAILURE_RETRIES) { 983 // Requeue for next frame. 984 SaveScreenshot(op.filename, op.callback, op.cbUserData); 985 } 986 } else { 987 screenshotFailures = 0; 988 } 989 break; 990 } 991 default: 992 ERROR_LOG(SAVESTATE, "Savestate failure: unknown operation type %d", op.type); 993 callbackResult = Status::FAILURE; 994 break; 995 } 996 997 if (op.callback) 998 op.callback(callbackResult, callbackMessage, op.cbUserData); 999 } 1000 if (operations.size()) { 1001 // Avoid triggering frame skipping due to slowdown 1002 __DisplaySetWasPaused(); 1003 } 1004 } 1005 NotifySaveData()1006 void NotifySaveData() { 1007 saveDataGeneration++; 1008 lastSaveDataGeneration = saveDataGeneration; 1009 } 1010 Cleanup()1011 void Cleanup() { 1012 if (needsRestart) { 1013 PSP_Shutdown(); 1014 std::string resetError; 1015 if (!PSP_Init(PSP_CoreParameter(), &resetError)) { 1016 ERROR_LOG(BOOT, "Error resetting: %s", resetError.c_str()); 1017 // TODO: This probably doesn't clean up well enough. 1018 Core_Stop(); 1019 return; 1020 } 1021 host->BootDone(); 1022 host->UpdateDisassembly(); 1023 needsRestart = false; 1024 } 1025 } 1026 Init()1027 void Init() 1028 { 1029 // Make sure there's a directory for save slots 1030 File::CreateFullPath(GetSysDirectory(DIRECTORY_SAVESTATE)); 1031 1032 std::lock_guard<std::mutex> guard(mutex); 1033 rewindStates.Clear(); 1034 1035 hasLoadedState = false; 1036 saveStateGeneration = 0; 1037 saveDataGeneration = 0; 1038 lastSaveDataGeneration = 0; 1039 saveStateInitialGitVersion.clear(); 1040 } 1041 Shutdown()1042 void Shutdown() 1043 { 1044 std::lock_guard<std::mutex> guard(mutex); 1045 rewindStates.Clear(); 1046 } 1047 } 1048