diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp index be2067fcf..71693f21f 100644 --- a/src/citra/citra.cpp +++ b/src/citra/citra.cpp @@ -273,6 +273,13 @@ int main(int argc, char** argv) { 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 Settings::values.gdbstub_port = gdb_port; Settings::values.use_gdbstub = use_gdbstub; diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index d475a8539..5ff9b7119 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -748,6 +748,10 @@ void GMainWindow::BootGame(const QString& filename) { LOG_INFO(Frontend, "Citra starting..."); StoreRecentFile(filename); // Put the filename on top of the list + if (movie_record_on_start) { + Core::Movie::GetInstance().PrepareForRecording(); + } + if (!LoadROM(filename)) return; @@ -1271,6 +1275,15 @@ void GMainWindow::OnCreateGraphicsSurfaceViewer() { } 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.
Are you sure you still want to record movies now?"), + QMessageBox::Yes | QMessageBox::No); + if (answer == QMessageBox::No) + return; + } const QString path = QFileDialog::getSaveFileName(this, tr("Record Movie"), UISettings::values.movie_record_path, tr("Citra TAS Movie (*.ctm)")); @@ -1332,6 +1345,16 @@ bool GMainWindow::ValidateMovie(const QString& path, u64 program_id) { } 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.
Are you sure you still want to play movies now?"), + QMessageBox::Yes | QMessageBox::No); + if (answer == QMessageBox::No) + return; + } + const QString path = QFileDialog::getOpenFileName(this, tr("Play Movie"), UISettings::values.movie_playback_path, tr("Citra TAS Movie (*.ctm)")); @@ -1363,6 +1386,7 @@ void GMainWindow::OnPlayMovie() { } if (!ValidateMovie(path, program_id)) return; + Core::Movie::GetInstance().PrepareForPlayback(path.toStdString()); BootGame(game_path); } Core::Movie::GetInstance().StartPlayback(path.toStdString(), [this] { diff --git a/src/core/hle/shared_page.cpp b/src/core/hle/shared_page.cpp index ec813546e..962293609 100644 --- a/src/core/hle/shared_page.cpp +++ b/src/core/hle/shared_page.cpp @@ -7,6 +7,7 @@ #include "core/core_timing.h" #include "core/hle/service/ptm/ptm.h" #include "core/hle/shared_page.h" +#include "core/movie.h" #include "core/settings.h" //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -14,6 +15,12 @@ namespace SharedPage { 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) { case Settings::InitClock::SystemTime: { auto now = std::chrono::system_clock::now(); diff --git a/src/core/movie.cpp b/src/core/movie.cpp index 04b470782..3b05f77d0 100644 --- a/src/core/movie.cpp +++ b/src/core/movie.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "common/bit_field.h" #include "common/common_types.h" @@ -13,6 +14,7 @@ #include "common/scm_rev.h" #include "common/string_util.h" #include "common/swap.h" +#include "common/timer.h" #include "core/core.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/extra_hid.h" @@ -112,8 +114,9 @@ struct CTMHeader { std::array 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 std::array 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 reserved; /// Make heading 256 bytes so it has consistent size + std::array reserved; /// Make heading 256 bytes so it has consistent size }; static_assert(sizeof(CTMHeader) == 256, "CTMHeader should be 256 bytes"); #pragma pack(pop) @@ -129,6 +132,7 @@ void Movie::CheckInputEnd() { if (current_byte + sizeof(ControllerState) > recorded_input.size()) { LOG_INFO(Movie, "Playback finished"); play_mode = PlayMode::None; + init_time = 0; playback_completion_callback(); } } @@ -344,6 +348,10 @@ void Movie::Record(const Service::IR::ExtraHIDResponse& extra_hid_response) { Record(s); } +u64 Movie::GetOverrideInitTime() const { + return init_time; +} + Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header, u64 program_id) const { if (header_magic_bytes != header.filetype) { LOG_ERROR(Movie, "Playback file does not have valid header"); @@ -381,6 +389,7 @@ void Movie::SaveMovie() { CTMHeader header = {}; header.filetype = header_magic_bytes; + header.clock_init_time = init_time; 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; } -Movie::ValidationResult Movie::ValidateMovie(const std::string& movie_file, u64 program_id) const { - LOG_INFO(Movie, "Validating Movie file '{}'", movie_file); +static boost::optional ReadHeader(const std::string& movie_file) { FileUtil::IOFile save_record(movie_file, "rb"); const u64 size = save_record.GetSize(); if (!save_record || size <= sizeof(CTMHeader)) { - return ValidationResult::Invalid; - } - - 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; + return boost::none; } CTMHeader header; save_record.ReadArray(&header, 1); if (header_magic_bytes != header.filetype) { - return 0; + return boost::none; } - return static_cast(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(header.value().program_id); } void Movie::Shutdown() { @@ -465,6 +491,7 @@ void Movie::Shutdown() { recorded_input.resize(0); record_movie_file.clear(); current_byte = 0; + init_time = 0; } template diff --git a/src/core/movie.h b/src/core/movie.h index 20ab5a06e..f1be86946 100644 --- a/src/core/movie.h +++ b/src/core/movie.h @@ -42,9 +42,19 @@ public: } void StartPlayback(const std::string& movie_file, - std::function completion_callback = {}); + std::function completion_callback = [] {}); 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; + + /// Get the init time that would override the one in the settings + u64 GetOverrideInitTime() const; u64 GetMovieProgramID(const std::string& movie_file) const; void Shutdown(); @@ -119,6 +129,7 @@ private: PlayMode play_mode; std::string record_movie_file; std::vector recorded_input; + u64 init_time; std::function playback_completion_callback; std::size_t current_byte = 0; };