movie: Add clock init time to CTM header
This adds a clock init time field to the CTM header. The clock settings would be overridden when playing a movie. And when recording a movie, if the clock is set to System Time, it would be set to fixed init time at the current moment as well. In this way this keeps consistency with the RNG even if the user does just no setting.
This commit is contained in:
parent
20e42592ff
commit
ae5c658997
|
@ -271,6 +271,13 @@ int main(int argc, char** argv) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!movie_record.empty()) {
|
||||||
|
Core::Movie::GetInstance().PrepareForRecording();
|
||||||
|
}
|
||||||
|
if (!movie_play.empty()) {
|
||||||
|
Core::Movie::GetInstance().PrepareForPlayback(movie_play);
|
||||||
|
}
|
||||||
|
|
||||||
// Apply the command line arguments
|
// Apply the command line arguments
|
||||||
Settings::values.gdbstub_port = gdb_port;
|
Settings::values.gdbstub_port = gdb_port;
|
||||||
Settings::values.use_gdbstub = use_gdbstub;
|
Settings::values.use_gdbstub = use_gdbstub;
|
||||||
|
@ -332,7 +339,7 @@ int main(int argc, char** argv) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!movie_play.empty()) {
|
if (!movie_play.empty()) {
|
||||||
Core::Movie::GetInstance().StartPlayback(movie_play);
|
Core::Movie::GetInstance().StartPlayback(movie_play, [] {});
|
||||||
}
|
}
|
||||||
if (!movie_record.empty()) {
|
if (!movie_record.empty()) {
|
||||||
Core::Movie::GetInstance().StartRecording(movie_record);
|
Core::Movie::GetInstance().StartRecording(movie_record);
|
||||||
|
|
|
@ -746,6 +746,10 @@ void GMainWindow::BootGame(const QString& filename) {
|
||||||
LOG_INFO(Frontend, "Citra starting...");
|
LOG_INFO(Frontend, "Citra starting...");
|
||||||
StoreRecentFile(filename); // Put the filename on top of the list
|
StoreRecentFile(filename); // Put the filename on top of the list
|
||||||
|
|
||||||
|
if (movie_record_on_start) {
|
||||||
|
Core::Movie::GetInstance().PrepareForRecording();
|
||||||
|
}
|
||||||
|
|
||||||
if (!LoadROM(filename))
|
if (!LoadROM(filename))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1261,6 +1265,15 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnRecordMovie() {
|
void GMainWindow::OnRecordMovie() {
|
||||||
|
if (emulation_running) {
|
||||||
|
QMessageBox::StandardButton answer = QMessageBox::warning(
|
||||||
|
this, tr("Record Movie"),
|
||||||
|
tr("To keep consistency with the RNG, it is recommended to record the movie from game "
|
||||||
|
"start.<br>Are you sure you still want to record movies now?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (answer == QMessageBox::No)
|
||||||
|
return;
|
||||||
|
}
|
||||||
const QString path =
|
const QString path =
|
||||||
QFileDialog::getSaveFileName(this, tr("Record Movie"), UISettings::values.movie_record_path,
|
QFileDialog::getSaveFileName(this, tr("Record Movie"), UISettings::values.movie_record_path,
|
||||||
tr("Citra TAS Movie (*.ctm)"));
|
tr("Citra TAS Movie (*.ctm)"));
|
||||||
|
@ -1322,6 +1335,16 @@ bool GMainWindow::ValidateMovie(const QString& path, u64 program_id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::OnPlayMovie() {
|
void GMainWindow::OnPlayMovie() {
|
||||||
|
if (emulation_running) {
|
||||||
|
QMessageBox::StandardButton answer = QMessageBox::warning(
|
||||||
|
this, tr("Play Movie"),
|
||||||
|
tr("To keep consistency with the RNG, it is recommended to play the movie from game "
|
||||||
|
"start.<br>Are you sure you still want to play movies now?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (answer == QMessageBox::No)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const QString path =
|
const QString path =
|
||||||
QFileDialog::getOpenFileName(this, tr("Play Movie"), UISettings::values.movie_playback_path,
|
QFileDialog::getOpenFileName(this, tr("Play Movie"), UISettings::values.movie_playback_path,
|
||||||
tr("Citra TAS Movie (*.ctm)"));
|
tr("Citra TAS Movie (*.ctm)"));
|
||||||
|
@ -1353,6 +1376,7 @@ void GMainWindow::OnPlayMovie() {
|
||||||
}
|
}
|
||||||
if (!ValidateMovie(path, program_id))
|
if (!ValidateMovie(path, program_id))
|
||||||
return;
|
return;
|
||||||
|
Core::Movie::GetInstance().PrepareForPlayback(path.toStdString());
|
||||||
BootGame(game_path);
|
BootGame(game_path);
|
||||||
}
|
}
|
||||||
Core::Movie::GetInstance().StartPlayback(path.toStdString(), [this] {
|
Core::Movie::GetInstance().StartPlayback(path.toStdString(), [this] {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/hle/service/ptm/ptm.h"
|
#include "core/hle/service/ptm/ptm.h"
|
||||||
#include "core/hle/shared_page.h"
|
#include "core/hle/shared_page.h"
|
||||||
|
#include "core/movie.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -14,6 +15,12 @@
|
||||||
namespace SharedPage {
|
namespace SharedPage {
|
||||||
|
|
||||||
static std::chrono::seconds GetInitTime() {
|
static std::chrono::seconds GetInitTime() {
|
||||||
|
u64 override_init_time = Core::Movie::GetInstance().GetOverrideInitTime();
|
||||||
|
if (override_init_time) {
|
||||||
|
// Override the clock init time with the one in the movie
|
||||||
|
return std::chrono::seconds(override_init_time);
|
||||||
|
}
|
||||||
|
|
||||||
switch (Settings::values.init_clock) {
|
switch (Settings::values.init_clock) {
|
||||||
case Settings::InitClock::SystemTime: {
|
case Settings::InitClock::SystemTime: {
|
||||||
auto now = std::chrono::system_clock::now();
|
auto now = std::chrono::system_clock::now();
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <boost/optional.hpp>
|
||||||
#include <cryptopp/hex.h>
|
#include <cryptopp/hex.h>
|
||||||
#include "common/bit_field.h"
|
#include "common/bit_field.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
@ -13,6 +14,7 @@
|
||||||
#include "common/scm_rev.h"
|
#include "common/scm_rev.h"
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
|
#include "common/timer.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/service/hid/hid.h"
|
#include "core/hle/service/hid/hid.h"
|
||||||
#include "core/hle/service/ir/extra_hid.h"
|
#include "core/hle/service/ir/extra_hid.h"
|
||||||
|
@ -112,8 +114,9 @@ struct CTMHeader {
|
||||||
std::array<u8, 4> filetype; /// Unique Identifier to check the file type (always "CTM"0x1B)
|
std::array<u8, 4> filetype; /// Unique Identifier to check the file type (always "CTM"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 movie was created with
|
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
|
||||||
|
|
||||||
std::array<u8, 224> reserved; /// Make heading 256 bytes so it has consistent size
|
std::array<u8, 216> reserved; /// Make heading 256 bytes so it has consistent size
|
||||||
};
|
};
|
||||||
static_assert(sizeof(CTMHeader) == 256, "CTMHeader should be 256 bytes");
|
static_assert(sizeof(CTMHeader) == 256, "CTMHeader should be 256 bytes");
|
||||||
#pragma pack(pop)
|
#pragma pack(pop)
|
||||||
|
@ -129,6 +132,7 @@ void Movie::CheckInputEnd() {
|
||||||
if (current_byte + sizeof(ControllerState) > recorded_input.size()) {
|
if (current_byte + sizeof(ControllerState) > recorded_input.size()) {
|
||||||
LOG_INFO(Movie, "Playback finished");
|
LOG_INFO(Movie, "Playback finished");
|
||||||
play_mode = PlayMode::None;
|
play_mode = PlayMode::None;
|
||||||
|
init_time = 0;
|
||||||
playback_completion_callback();
|
playback_completion_callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -344,6 +348,10 @@ void Movie::Record(const Service::IR::ExtraHIDResponse& extra_hid_response) {
|
||||||
Record(s);
|
Record(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 Movie::GetOverrideInitTime() const {
|
||||||
|
return init_time;
|
||||||
|
}
|
||||||
|
|
||||||
Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header, u64 program_id) const {
|
Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header, u64 program_id) const {
|
||||||
if (header_magic_bytes != header.filetype) {
|
if (header_magic_bytes != header.filetype) {
|
||||||
LOG_ERROR(Movie, "Playback file does not have valid header");
|
LOG_ERROR(Movie, "Playback file does not have valid header");
|
||||||
|
@ -381,6 +389,7 @@ void Movie::SaveMovie() {
|
||||||
|
|
||||||
CTMHeader header = {};
|
CTMHeader header = {};
|
||||||
header.filetype = header_magic_bytes;
|
header.filetype = header_magic_bytes;
|
||||||
|
header.clock_init_time = init_time;
|
||||||
|
|
||||||
Core::System::GetInstance().GetAppLoader().ReadProgramId(header.program_id);
|
Core::System::GetInstance().GetAppLoader().ReadProgramId(header.program_id);
|
||||||
|
|
||||||
|
@ -424,36 +433,53 @@ void Movie::StartRecording(const std::string& movie_file) {
|
||||||
record_movie_file = movie_file;
|
record_movie_file = movie_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
Movie::ValidationResult Movie::ValidateMovie(const std::string& movie_file, u64 program_id) const {
|
static boost::optional<CTMHeader> ReadHeader(const std::string& movie_file) {
|
||||||
LOG_INFO(Movie, "Validating Movie file '{}'", movie_file);
|
|
||||||
FileUtil::IOFile save_record(movie_file, "rb");
|
FileUtil::IOFile save_record(movie_file, "rb");
|
||||||
const u64 size = save_record.GetSize();
|
const u64 size = save_record.GetSize();
|
||||||
|
|
||||||
if (!save_record || size <= sizeof(CTMHeader)) {
|
if (!save_record || size <= sizeof(CTMHeader)) {
|
||||||
return ValidationResult::Invalid;
|
return boost::none;
|
||||||
}
|
|
||||||
|
|
||||||
CTMHeader header;
|
|
||||||
save_record.ReadArray(&header, 1);
|
|
||||||
return ValidateHeader(header, program_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
u64 Movie::GetMovieProgramID(const std::string& movie_file) const {
|
|
||||||
FileUtil::IOFile save_record(movie_file, "rb");
|
|
||||||
const u64 size = save_record.GetSize();
|
|
||||||
|
|
||||||
if (!save_record || size <= sizeof(CTMHeader)) {
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CTMHeader header;
|
CTMHeader header;
|
||||||
save_record.ReadArray(&header, 1);
|
save_record.ReadArray(&header, 1);
|
||||||
|
|
||||||
if (header_magic_bytes != header.filetype) {
|
if (header_magic_bytes != header.filetype) {
|
||||||
return 0;
|
return boost::none;
|
||||||
}
|
}
|
||||||
|
|
||||||
return static_cast<u64>(header.program_id);
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::PrepareForPlayback(const std::string& movie_file) {
|
||||||
|
auto header = ReadHeader(movie_file);
|
||||||
|
if (header != boost::none)
|
||||||
|
return;
|
||||||
|
|
||||||
|
init_time = header.value().clock_init_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::PrepareForRecording() {
|
||||||
|
init_time = (Settings::values.init_clock == Settings::InitClock::SystemTime
|
||||||
|
? Common::Timer::GetTimeSinceJan1970().count()
|
||||||
|
: Settings::values.init_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
Movie::ValidationResult Movie::ValidateMovie(const std::string& movie_file, u64 program_id) const {
|
||||||
|
LOG_INFO(Movie, "Validating Movie file '{}'", movie_file);
|
||||||
|
auto header = ReadHeader(movie_file);
|
||||||
|
if (header != boost::none)
|
||||||
|
return ValidationResult::Invalid;
|
||||||
|
|
||||||
|
return ValidateHeader(header.value(), program_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 Movie::GetMovieProgramID(const std::string& movie_file) const {
|
||||||
|
auto header = ReadHeader(movie_file);
|
||||||
|
if (header != boost::none)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return static_cast<u64>(header.value().program_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Movie::Shutdown() {
|
void Movie::Shutdown() {
|
||||||
|
@ -465,6 +491,7 @@ void Movie::Shutdown() {
|
||||||
recorded_input.resize(0);
|
recorded_input.resize(0);
|
||||||
record_movie_file.clear();
|
record_movie_file.clear();
|
||||||
current_byte = 0;
|
current_byte = 0;
|
||||||
|
init_time = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename... Targs>
|
template <typename... Targs>
|
||||||
|
|
|
@ -44,7 +44,17 @@ public:
|
||||||
void StartPlayback(const std::string& movie_file,
|
void StartPlayback(const std::string& movie_file,
|
||||||
std::function<void()> completion_callback = {});
|
std::function<void()> completion_callback = {});
|
||||||
void StartRecording(const std::string& movie_file);
|
void StartRecording(const std::string& movie_file);
|
||||||
|
|
||||||
|
/// Prepare to override the clock before playing back movies
|
||||||
|
void PrepareForPlayback(const std::string& movie_file);
|
||||||
|
|
||||||
|
/// Prepare to override the clock before recording movies
|
||||||
|
void PrepareForRecording();
|
||||||
|
|
||||||
ValidationResult ValidateMovie(const std::string& movie_file, u64 program_id = 0) const;
|
ValidationResult ValidateMovie(const std::string& movie_file, u64 program_id = 0) const;
|
||||||
|
|
||||||
|
/// Get the init time that would override the one in the settings
|
||||||
|
u64 GetOverrideInitTime() const;
|
||||||
u64 GetMovieProgramID(const std::string& movie_file) const;
|
u64 GetMovieProgramID(const std::string& movie_file) const;
|
||||||
|
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
@ -119,6 +129,7 @@ private:
|
||||||
PlayMode play_mode;
|
PlayMode play_mode;
|
||||||
std::string record_movie_file;
|
std::string record_movie_file;
|
||||||
std::vector<u8> recorded_input;
|
std::vector<u8> recorded_input;
|
||||||
|
u64 init_time;
|
||||||
std::function<void()> playback_completion_callback;
|
std::function<void()> playback_completion_callback;
|
||||||
std::size_t current_byte = 0;
|
std::size_t current_byte = 0;
|
||||||
};
|
};
|
||||||
|
|
Reference in New Issue