core: Add read-only mode and separate savestate slots for movies

The read-only mode switch affects how movies interact with savestates after you start a movie playback and load a savestate. When you are in read-only mode, the movie will resume playing from the loaded savestate. When you are in read+write mode however, your input will be recorded over the original movie ('rerecording'). If you wish to start rerecording immediately, you should switch to R+W mode, save a state and then load it.

To make this more user-friendly, I also added a unique ID to the movies, which allows each movie to have an individual set of savestate slots (plus another set for when not doing any movies). This is as recommended by staff at TASVideos.
This commit is contained in:
zhupengfei 2020-06-29 22:32:24 +08:00
parent 2e3834f880
commit ebaa225bcb
No known key found for this signature in database
GPG key ID: DD129E108BD09378
3 changed files with 102 additions and 13 deletions

View file

@ -3,10 +3,12 @@
// Refer to the license.txt file included.
#include <cstring>
#include <stdexcept>
#include <string>
#include <vector>
#include <boost/optional.hpp>
#include <cryptopp/hex.h>
#include <cryptopp/osrng.h>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/file_util.h"
@ -117,12 +119,61 @@ struct CTMHeader {
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 movie was created with
u64_le clock_init_time; /// The init time of the system clock
// Unique identifier of the movie, used to support separate savestate slots for TASing
u64_le id;
std::array<u8, 216> reserved; /// Make heading 256 bytes so it has consistent size
std::array<u8, 208> reserved; /// Make heading 256 bytes so it has consistent size
};
static_assert(sizeof(CTMHeader) == 256, "CTMHeader should be 256 bytes");
#pragma pack(pop)
template <class Archive>
void Movie::serialize(Archive& ar, const unsigned int file_version) {
// Only serialize what's needed to make savestates useful for TAS:
u64 _current_byte = static_cast<u64>(current_byte);
ar& _current_byte;
current_byte = static_cast<std::size_t>(_current_byte);
std::vector<u8> recorded_input_ = recorded_input;
ar& recorded_input_;
ar& init_time;
if (file_version > 0) {
if (Archive::is_loading::value) {
u64 savestate_movie_id;
ar& savestate_movie_id;
if (id != savestate_movie_id) {
if (savestate_movie_id == 0) {
throw std::runtime_error("You must close your movie to load this state");
} else {
throw std::runtime_error("You must load the same movie to load this state");
}
}
} else {
ar& id;
}
}
if (Archive::is_loading::value && id != 0) {
if (read_only) { // Do not replace the previously recorded input.
if (play_mode == PlayMode::Recording) {
SaveMovie();
}
if (current_byte >= recorded_input.size()) {
throw std::runtime_error(
"This savestate was created at a later point and must be loaded in R+W mode");
}
play_mode = PlayMode::Playing;
} else {
recorded_input = std::move(recorded_input_);
play_mode = PlayMode::Recording;
}
}
}
SERIALIZE_IMPL(Movie)
bool Movie::IsPlayingInput() const {
return play_mode == PlayMode::Playing;
}
@ -135,6 +186,7 @@ void Movie::CheckInputEnd() {
LOG_INFO(Movie, "Playback finished");
play_mode = PlayMode::None;
init_time = 0;
id = 0;
playback_completion_callback();
}
}
@ -394,6 +446,7 @@ void Movie::SaveMovie() {
CTMHeader header = {};
header.filetype = header_magic_bytes;
header.clock_init_time = init_time;
header.id = id;
Core::System::GetInstance().GetAppLoader().ReadProgramId(header.program_id);
@ -421,10 +474,14 @@ void Movie::StartPlayback(const std::string& movie_file,
save_record.ReadArray(&header, 1);
if (ValidateHeader(header) != ValidationResult::Invalid) {
play_mode = PlayMode::Playing;
record_movie_file = movie_file;
recorded_input.resize(size - sizeof(CTMHeader));
save_record.ReadArray(recorded_input.data(), recorded_input.size());
current_byte = 0;
id = header.id;
playback_completion_callback = completion_callback;
LOG_INFO(Movie, "Loaded Movie, ID: {:016X}", id);
}
} else {
LOG_ERROR(Movie, "Failed to playback movie: Unable to open '{}'", movie_file);
@ -432,9 +489,18 @@ void Movie::StartPlayback(const std::string& movie_file,
}
void Movie::StartRecording(const std::string& movie_file) {
LOG_INFO(Movie, "Enabling Movie recording");
play_mode = PlayMode::Recording;
record_movie_file = movie_file;
// Generate a random ID
CryptoPP::AutoSeededRandomPool rng;
rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&id), sizeof(id));
LOG_INFO(Movie, "Enabling Movie recording, ID: {:016X}", id);
}
void Movie::SetReadOnly(bool read_only_) {
read_only = read_only_;
}
static boost::optional<CTMHeader> ReadHeader(const std::string& movie_file) {
@ -496,6 +562,7 @@ void Movie::Shutdown() {
record_movie_file.clear();
current_byte = 0;
init_time = 0;
id = 0;
}
template <typename... Targs>