mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-30 05:10:03 +00:00 
			
		
		
		
	Merge pull request #4267 from zhaowenlan1779/movie
movie: Add clock init time to CTM header
This commit is contained in:
		
						commit
						2a90426cb8
					
				
					 5 changed files with 96 additions and 20 deletions
				
			
		|  | @ -273,6 +273,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; | ||||||
|  |  | ||||||
|  | @ -748,6 +748,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; | ||||||
| 
 | 
 | ||||||
|  | @ -1271,6 +1275,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)")); | ||||||
|  | @ -1332,6 +1345,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)")); | ||||||
|  | @ -1363,6 +1386,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> | ||||||
|  |  | ||||||
|  | @ -42,9 +42,19 @@ 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; | ||||||
| }; | }; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue