citra-emu
/
citra
Archived
1
0
Fork 0

Merge pull request #6419 from vitor-k/states

Update zstd and improve savestates logging
This commit is contained in:
SachinVin 2023-04-15 10:11:40 +05:30 committed by GitHub
commit 5f81940e63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 49 additions and 35 deletions

2
externals/zstd vendored

@ -1 +1 @@
Subproject commit e47e674cd09583ff0503f0f6defd6d23d8b718d3 Subproject commit 63779c798237346c2b245c546c40b72a5a5913fe

View File

@ -113,9 +113,10 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
case Signal::Shutdown: case Signal::Shutdown:
return ResultStatus::ShutdownRequested; return ResultStatus::ShutdownRequested;
case Signal::Load: { case Signal::Load: {
LOG_INFO(Core, "Begin load"); const u32 slot = param;
LOG_INFO(Core, "Begin load of slot {}", slot);
try { try {
System::LoadState(param); System::LoadState(slot);
LOG_INFO(Core, "Load completed"); LOG_INFO(Core, "Load completed");
} catch (const std::exception& e) { } catch (const std::exception& e) {
LOG_ERROR(Core, "Error loading: {}", e.what()); LOG_ERROR(Core, "Error loading: {}", e.what());
@ -126,9 +127,10 @@ System::ResultStatus System::RunLoop(bool tight_loop) {
return ResultStatus::Success; return ResultStatus::Success;
} }
case Signal::Save: { case Signal::Save: {
LOG_INFO(Core, "Begin save"); const u32 slot = param;
LOG_INFO(Core, "Begin save to slot {}", slot);
try { try {
System::SaveState(param); System::SaveState(slot);
LOG_INFO(Core, "Save completed"); LOG_INFO(Core, "Save completed");
} catch (const std::exception& e) { } catch (const std::exception& e) {
LOG_ERROR(Core, "Error saving: {}", e.what()); LOG_ERROR(Core, "Error saving: {}", e.what());

View File

@ -3,18 +3,15 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <chrono> #include <chrono>
#include <boost/serialization/binary_object.hpp>
#include <cryptopp/hex.h> #include <cryptopp/hex.h>
#include "common/archives.h" #include "common/archives.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scm_rev.h" #include "common/scm_rev.h"
#include "common/zstd_compression.h" #include "common/zstd_compression.h"
#include "core/cheats/cheats.h"
#include "core/core.h" #include "core/core.h"
#include "core/movie.h" #include "core/movie.h"
#include "core/savestate.h" #include "core/savestate.h"
#include "network/network.h" #include "network/network.h"
#include "video_core/video_core.h"
namespace Core { namespace Core {
@ -25,19 +22,14 @@ struct CSTHeader {
std::array<u8, 20> revision; /// Git hash of the revision this savestate was created with std::array<u8, 20> revision; /// Git hash of the revision this savestate was created with
u64_le time; /// The time when this save state was created u64_le time; /// The time when this save state was created
std::array<u8, 216> reserved; /// Make heading 256 bytes so it has consistent size std::array<u8, 216> reserved{}; /// Make heading 256 bytes so it has consistent size
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& boost::serialization::binary_object(this, sizeof(CSTHeader));
}
}; };
static_assert(sizeof(CSTHeader) == 256, "CSTHeader should be 256 bytes"); static_assert(sizeof(CSTHeader) == 256, "CSTHeader should be 256 bytes");
#pragma pack(pop) #pragma pack(pop)
constexpr std::array<u8, 4> header_magic_bytes{{'C', 'S', 'T', 0x1B}}; constexpr std::array<u8, 4> header_magic_bytes{{'C', 'S', 'T', 0x1B}};
std::string GetSaveStatePath(u64 program_id, u32 slot) { static std::string GetSaveStatePath(u64 program_id, u32 slot) {
const u64 movie_id = Movie::GetInstance().GetCurrentMovieID(); const u64 movie_id = Movie::GetInstance().GetCurrentMovieID();
if (movie_id) { if (movie_id) {
return fmt::format("{}{:016X}.movie{:016X}.{:02d}.cst", return fmt::format("{}{:016X}.movie{:016X}.{:02d}.cst",
@ -49,6 +41,30 @@ std::string GetSaveStatePath(u64 program_id, u32 slot) {
} }
} }
static bool ValidateSaveState(const CSTHeader& header, SaveStateInfo& info, u64 program_id,
u32 slot) {
const auto path = GetSaveStatePath(program_id, slot);
if (header.filetype != header_magic_bytes) {
LOG_WARNING(Core, "Invalid save state file {}", path);
return false;
}
info.time = header.time;
if (header.program_id != program_id) {
LOG_WARNING(Core, "Save state file isn't for the current game {}", path);
return false;
}
const std::string revision = fmt::format("{:02x}", fmt::join(header.revision, ""));
if (revision == Common::g_scm_rev) {
info.status = SaveStateInfo::ValidationStatus::OK;
} else {
LOG_WARNING(Core, "Save state file {} created from a different revision {}", path,
revision);
info.status = SaveStateInfo::ValidationStatus::RevisionDismatch;
}
return true;
}
std::vector<SaveStateInfo> ListSaveStates(u64 program_id) { std::vector<SaveStateInfo> ListSaveStates(u64 program_id) {
std::vector<SaveStateInfo> result; std::vector<SaveStateInfo> result;
for (u32 slot = 1; slot <= SaveStateSlotCount; ++slot) { for (u32 slot = 1; slot <= SaveStateSlotCount; ++slot) {
@ -74,24 +90,10 @@ std::vector<SaveStateInfo> ListSaveStates(u64 program_id) {
LOG_ERROR(Core, "Could not read from file {}", path); LOG_ERROR(Core, "Could not read from file {}", path);
continue; continue;
} }
if (header.filetype != header_magic_bytes) { if (!ValidateSaveState(header, info, program_id, slot)) {
LOG_WARNING(Core, "Invalid save state file {}", path);
continue; continue;
} }
info.time = header.time;
if (header.program_id != program_id) {
LOG_WARNING(Core, "Save state file isn't for the current game {}", path);
continue;
}
const std::string revision = fmt::format("{:02x}", fmt::join(header.revision, ""));
if (revision == Common::g_scm_rev) {
info.status = SaveStateInfo::ValidationStatus::OK;
} else {
LOG_WARNING(Core, "Save state file {} created from a different revision {}", path,
revision);
info.status = SaveStateInfo::ValidationStatus::RevisionDismatch;
}
result.emplace_back(std::move(info)); result.emplace_back(std::move(info));
} }
return result; return result;
@ -121,7 +123,7 @@ void System::SaveState(u32 slot) const {
header.filetype = header_magic_bytes; header.filetype = header_magic_bytes;
header.program_id = title_id; header.program_id = title_id;
std::string rev_bytes; std::string rev_bytes;
CryptoPP::StringSource(Common::g_scm_rev, true, CryptoPP::StringSource ss(Common::g_scm_rev, true,
new CryptoPP::HexDecoder(new CryptoPP::StringSink(rev_bytes))); new CryptoPP::HexDecoder(new CryptoPP::StringSink(rev_bytes)));
std::memcpy(header.revision.data(), rev_bytes.data(), sizeof(header.revision)); std::memcpy(header.revision.data(), rev_bytes.data(), sizeof(header.revision));
header.time = std::chrono::duration_cast<std::chrono::seconds>( header.time = std::chrono::duration_cast<std::chrono::seconds>(
@ -146,7 +148,19 @@ void System::LoadState(u32 slot) {
std::vector<u8> buffer(FileUtil::GetSize(path) - sizeof(CSTHeader)); std::vector<u8> buffer(FileUtil::GetSize(path) - sizeof(CSTHeader));
FileUtil::IOFile file(path, "rb"); FileUtil::IOFile file(path, "rb");
file.Seek(sizeof(CSTHeader), SEEK_SET); // Skip header
// load header
CSTHeader header;
if (file.ReadBytes(&header, sizeof(header)) != sizeof(header)) {
throw std::runtime_error("Could not read from file at " + path);
}
// validate header
SaveStateInfo info;
if (!ValidateSaveState(header, info, title_id, slot)) {
throw std::runtime_error("Invalid savestate");
}
if (file.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) { if (file.ReadBytes(buffer.data(), buffer.size()) != buffer.size()) {
throw std::runtime_error("Could not read from file at " + path); throw std::runtime_error("Could not read from file at " + path);
} }

View File

@ -9,8 +9,6 @@
namespace Core { namespace Core {
struct CSTHeader;
struct SaveStateInfo { struct SaveStateInfo {
u32 slot; u32 slot;
u64 time; u64 time;