|
|
|
@ -3,18 +3,15 @@
|
|
|
|
|
// Refer to the license.txt file included.
|
|
|
|
|
|
|
|
|
|
#include <chrono>
|
|
|
|
|
#include <boost/serialization/binary_object.hpp>
|
|
|
|
|
#include <cryptopp/hex.h>
|
|
|
|
|
#include "common/archives.h"
|
|
|
|
|
#include "common/logging/log.h"
|
|
|
|
|
#include "common/scm_rev.h"
|
|
|
|
|
#include "common/zstd_compression.h"
|
|
|
|
|
#include "core/cheats/cheats.h"
|
|
|
|
|
#include "core/core.h"
|
|
|
|
|
#include "core/movie.h"
|
|
|
|
|
#include "core/savestate.h"
|
|
|
|
|
#include "network/network.h"
|
|
|
|
|
#include "video_core/video_core.h"
|
|
|
|
|
|
|
|
|
|
namespace Core {
|
|
|
|
|
|
|
|
|
@ -25,19 +22,14 @@ struct CSTHeader {
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
std::array<u8, 216> reserved{}; /// Make heading 256 bytes so it has consistent size
|
|
|
|
|
};
|
|
|
|
|
static_assert(sizeof(CSTHeader) == 256, "CSTHeader should be 256 bytes");
|
|
|
|
|
#pragma pack(pop)
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
if (movie_id) {
|
|
|
|
|
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> result;
|
|
|
|
|
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);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (header.filetype != header_magic_bytes) {
|
|
|
|
|
LOG_WARNING(Core, "Invalid save state file {}", path);
|
|
|
|
|
if (!ValidateSaveState(header, info, program_id, slot)) {
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
@ -121,7 +123,7 @@ void System::SaveState(u32 slot) const {
|
|
|
|
|
header.filetype = header_magic_bytes;
|
|
|
|
|
header.program_id = title_id;
|
|
|
|
|
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)));
|
|
|
|
|
std::memcpy(header.revision.data(), rev_bytes.data(), sizeof(header.revision));
|
|
|
|
|
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));
|
|
|
|
|
|
|
|
|
|
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()) {
|
|
|
|
|
throw std::runtime_error("Could not read from file at " + path);
|
|
|
|
|
}
|
|
|
|
|