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:
Vitor K 2023-08-11 00:55:22 -03:00 committed by GitHub
parent af78268dd5
commit eb8d2941c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 1470 additions and 11 deletions

View File

@ -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 =
savestate.status == Core::SaveStateInfo::ValidationStatus::RevisionDismatch &&
!savestate.build_name.empty();
const auto text =
tr("Slot %1 - %2 %3")
.arg(savestate.slot) .arg(savestate.slot)
.arg(QDateTime::fromSecsSinceEpoch(savestate.time) .arg(QDateTime::fromSecsSinceEpoch(savestate.time)
.toString(QStringLiteral("yyyy-MM-dd hh:mm:ss"))); .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);

View File

@ -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

View File

@ -15,6 +15,7 @@
#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 {
@ -25,8 +26,10 @@ struct CSTHeader {
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 {
if (!build_name.empty()) {
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, LOG_WARNING(Core, "Save state file {} created from a different revision {}", path,
revision); 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()) {

View File

@ -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

File diff suppressed because it is too large Load Diff