savestates: save the build name to be displayed when there's a version mismatch (#6493)
* savestates: add a build_name field to the header * savestates: display build name on save/load menu * savestates: add zero member to header just in case of UB from an older save state * savestates: add legacy hash lookup * savestate_data: update hash database
This commit is contained in:
parent
af78268dd5
commit
eb8d2941c9
5 changed files with 1470 additions and 11 deletions
|
@ -1422,10 +1422,17 @@ void GMainWindow::UpdateSaveStates() {
|
||||||
actions_save_state[i]->setText(tr("Slot %1").arg(i + 1));
|
actions_save_state[i]->setText(tr("Slot %1").arg(i + 1));
|
||||||
}
|
}
|
||||||
for (const auto& savestate : savestates) {
|
for (const auto& savestate : savestates) {
|
||||||
const auto text = tr("Slot %1 - %2")
|
const bool display_name =
|
||||||
.arg(savestate.slot)
|
savestate.status == Core::SaveStateInfo::ValidationStatus::RevisionDismatch &&
|
||||||
.arg(QDateTime::fromSecsSinceEpoch(savestate.time)
|
!savestate.build_name.empty();
|
||||||
.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")));
|
const auto text =
|
||||||
|
tr("Slot %1 - %2 %3")
|
||||||
|
.arg(savestate.slot)
|
||||||
|
.arg(QDateTime::fromSecsSinceEpoch(savestate.time)
|
||||||
|
.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss")))
|
||||||
|
.arg(display_name ? QString::fromStdString(savestate.build_name) : QLatin1String())
|
||||||
|
.trimmed();
|
||||||
|
|
||||||
actions_load_state[savestate.slot - 1]->setEnabled(true);
|
actions_load_state[savestate.slot - 1]->setEnabled(true);
|
||||||
actions_load_state[savestate.slot - 1]->setText(text);
|
actions_load_state[savestate.slot - 1]->setText(text);
|
||||||
actions_save_state[savestate.slot - 1]->setText(text);
|
actions_save_state[savestate.slot - 1]->setText(text);
|
||||||
|
|
|
@ -463,6 +463,7 @@ add_library(citra_core STATIC
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
savestate.cpp
|
savestate.cpp
|
||||||
savestate.h
|
savestate.h
|
||||||
|
savestate_data.h
|
||||||
system_titles.cpp
|
system_titles.cpp
|
||||||
system_titles.h
|
system_titles.h
|
||||||
telemetry_session.cpp
|
telemetry_session.cpp
|
||||||
|
|
|
@ -15,18 +15,21 @@
|
||||||
#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 "core/savestate_data.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
#pragma pack(push, 1)
|
||||||
struct CSTHeader {
|
struct CSTHeader {
|
||||||
std::array<u8, 4> filetype; /// Unique Identifier to check the file type (always "CST"0x1B)
|
std::array<u8, 4> filetype; /// Unique Identifier to check the file type (always "CST"0x1B)
|
||||||
u64_le program_id; /// ID of the ROM being executed. Also called title_id
|
u64_le program_id; /// ID of the ROM being executed. Also called title_id
|
||||||
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, 20> build_name; /// The build name (Canary/Nightly) with the version number
|
||||||
|
u32_le zero = 0; /// Should be zero, just in case.
|
||||||
|
|
||||||
std::array<u8, 216> reserved{}; /// Make heading 256 bytes so it has consistent size
|
std::array<u8, 192> reserved{}; /// Make heading 256 bytes so it has consistent size
|
||||||
};
|
};
|
||||||
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)
|
||||||
|
@ -58,11 +61,26 @@ static bool ValidateSaveState(const CSTHeader& header, SaveStateInfo& info, u64
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const std::string revision = fmt::format("{:02x}", fmt::join(header.revision, ""));
|
const std::string revision = fmt::format("{:02x}", fmt::join(header.revision, ""));
|
||||||
|
const std::string build_name =
|
||||||
|
header.zero == 0 ? reinterpret_cast<const char*>(header.build_name.data()) : "";
|
||||||
|
|
||||||
if (revision == Common::g_scm_rev) {
|
if (revision == Common::g_scm_rev) {
|
||||||
info.status = SaveStateInfo::ValidationStatus::OK;
|
info.status = SaveStateInfo::ValidationStatus::OK;
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING(Core, "Save state file {} created from a different revision {}", path,
|
if (!build_name.empty()) {
|
||||||
revision);
|
info.build_name = build_name;
|
||||||
|
} else if (hash_to_version.find(revision) != hash_to_version.end()) {
|
||||||
|
info.build_name = hash_to_version.at(revision);
|
||||||
|
}
|
||||||
|
if (info.build_name.empty()) {
|
||||||
|
LOG_WARNING(Core, "Save state file {} created from a different revision {}", path,
|
||||||
|
revision);
|
||||||
|
} else {
|
||||||
|
LOG_WARNING(Core,
|
||||||
|
"Save state file {} created from a different build {} with revision {}",
|
||||||
|
path, info.build_name, revision);
|
||||||
|
}
|
||||||
|
|
||||||
info.status = SaveStateInfo::ValidationStatus::RevisionDismatch;
|
info.status = SaveStateInfo::ValidationStatus::RevisionDismatch;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
@ -134,6 +152,10 @@ void System::SaveState(u32 slot) const {
|
||||||
header.time = std::chrono::duration_cast<std::chrono::seconds>(
|
header.time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
std::chrono::system_clock::now().time_since_epoch())
|
std::chrono::system_clock::now().time_since_epoch())
|
||||||
.count();
|
.count();
|
||||||
|
const std::string build_fullname = Common::g_build_fullname;
|
||||||
|
std::memset(header.build_name.data(), 0, sizeof(header.build_name));
|
||||||
|
std::memcpy(header.build_name.data(), build_fullname.c_str(),
|
||||||
|
std::min(build_fullname.length(), sizeof(header.build_name) - 1));
|
||||||
|
|
||||||
if (file.WriteBytes(&header, sizeof(header)) != sizeof(header) ||
|
if (file.WriteBytes(&header, sizeof(header)) != sizeof(header) ||
|
||||||
file.WriteBytes(buffer.data(), buffer.size()) != buffer.size()) {
|
file.WriteBytes(buffer.data(), buffer.size()) != buffer.size()) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
@ -16,6 +17,7 @@ struct SaveStateInfo {
|
||||||
OK,
|
OK,
|
||||||
RevisionDismatch,
|
RevisionDismatch,
|
||||||
} status;
|
} status;
|
||||||
|
std::string build_name;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr u32 SaveStateSlotCount = 10; // Maximum count of savestate slots
|
constexpr u32 SaveStateSlotCount = 10; // Maximum count of savestate slots
|
||||||
|
|
1427
src/core/savestate_data.h
Normal file
1427
src/core/savestate_data.h
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue