core/movie: Movie refactor, add a completion callback
This commit is contained in:
		| @@ -39,6 +39,7 @@ | |||||||
| #include "core/gdbstub/gdbstub.h" | #include "core/gdbstub/gdbstub.h" | ||||||
| #include "core/hle/service/am/am.h" | #include "core/hle/service/am/am.h" | ||||||
| #include "core/loader/loader.h" | #include "core/loader/loader.h" | ||||||
|  | #include "core/movie.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| #include "network/network.h" | #include "network/network.h" | ||||||
|  |  | ||||||
| @@ -268,8 +269,6 @@ int main(int argc, char** argv) { | |||||||
|     // 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; | ||||||
|     Settings::values.movie_play = std::move(movie_play); |  | ||||||
|     Settings::values.movie_record = std::move(movie_record); |  | ||||||
|     Settings::Apply(); |     Settings::Apply(); | ||||||
|  |  | ||||||
|     // Register frontend applets |     // Register frontend applets | ||||||
| @@ -327,9 +326,18 @@ int main(int argc, char** argv) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (!movie_play.empty()) { | ||||||
|  |         Core::Movie::GetInstance().StartPlayback(movie_play); | ||||||
|  |     } | ||||||
|  |     if (!movie_record.empty()) { | ||||||
|  |         Core::Movie::GetInstance().StartRecording(movie_record); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     while (emu_window->IsOpen()) { |     while (emu_window->IsOpen()) { | ||||||
|         system.RunLoop(); |         system.RunLoop(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     Core::Movie::GetInstance().Shutdown(); | ||||||
|  |  | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -176,7 +176,6 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | |||||||
|     Kernel::Init(system_mode); |     Kernel::Init(system_mode); | ||||||
|     Service::Init(service_manager); |     Service::Init(service_manager); | ||||||
|     GDBStub::Init(); |     GDBStub::Init(); | ||||||
|     Movie::GetInstance().Init(); |  | ||||||
|  |  | ||||||
|     if (!VideoCore::Init(emu_window)) { |     if (!VideoCore::Init(emu_window)) { | ||||||
|         return ResultStatus::ErrorVideoCore; |         return ResultStatus::ErrorVideoCore; | ||||||
| @@ -214,7 +213,6 @@ void System::Shutdown() { | |||||||
|                          perf_results.frametime * 1000.0); |                          perf_results.frametime * 1000.0); | ||||||
|  |  | ||||||
|     // Shutdown emulation session |     // Shutdown emulation session | ||||||
|     Movie::GetInstance().Shutdown(); |  | ||||||
|     GDBStub::Shutdown(); |     GDBStub::Shutdown(); | ||||||
|     VideoCore::Shutdown(); |     VideoCore::Shutdown(); | ||||||
|     Service::Shutdown(); |     Service::Shutdown(); | ||||||
|   | |||||||
| @@ -118,10 +118,10 @@ struct CTMHeader { | |||||||
| 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) | ||||||
|  |  | ||||||
| bool Movie::IsPlayingInput() { | bool Movie::IsPlayingInput() const { | ||||||
|     return play_mode == PlayMode::Playing; |     return play_mode == PlayMode::Playing; | ||||||
| } | } | ||||||
| bool Movie::IsRecordingInput() { | bool Movie::IsRecordingInput() const { | ||||||
|     return play_mode == PlayMode::Recording; |     return play_mode == PlayMode::Recording; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -129,6 +129,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; | ||||||
|  |         playback_completion_callback(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -343,33 +344,35 @@ void Movie::Record(const Service::IR::ExtraHIDResponse& extra_hid_response) { | |||||||
|     Record(s); |     Record(s); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Movie::ValidateHeader(const CTMHeader& header) { | Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header) 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"); | ||||||
|         return false; |         return ValidationResult::Invalid; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     std::string revision = |     std::string revision = | ||||||
|         Common::ArrayToString(header.revision.data(), header.revision.size(), 21, false); |         Common::ArrayToString(header.revision.data(), header.revision.size(), 21, false); | ||||||
|     revision = Common::ToLower(revision); |     revision = Common::ToLower(revision); | ||||||
|  |  | ||||||
|     if (revision != Common::g_scm_rev) { |  | ||||||
|         LOG_WARNING(Movie, |  | ||||||
|                     "This movie was created on a different version of Citra, playback may desync"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     u64 program_id; |     u64 program_id; | ||||||
|     Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id); |     Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id); | ||||||
|     if (program_id != header.program_id) { |     if (program_id != header.program_id) { | ||||||
|         LOG_WARNING(Movie, "This movie was recorded using a ROM with a different program id"); |         LOG_WARNING(Movie, "This movie was recorded using a ROM with a different program id"); | ||||||
|  |         return ValidationResult::GameDismatch; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return true; |     if (revision != Common::g_scm_rev) { | ||||||
|  |         LOG_WARNING(Movie, | ||||||
|  |                     "This movie was created on a different version of Citra, playback may desync"); | ||||||
|  |         return ValidationResult::RevisionDismatch; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return ValidationResult::OK; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Movie::SaveMovie() { | void Movie::SaveMovie() { | ||||||
|     LOG_INFO(Movie, "Saving movie"); |     LOG_INFO(Movie, "Saving recorded movie to '{}'", record_movie_file); | ||||||
|     FileUtil::IOFile save_record(Settings::values.movie_record, "wb"); |     FileUtil::IOFile save_record(record_movie_file, "wb"); | ||||||
|  |  | ||||||
|     if (!save_record.IsGood()) { |     if (!save_record.IsGood()) { | ||||||
|         LOG_ERROR(Movie, "Unable to open file to save movie"); |         LOG_ERROR(Movie, "Unable to open file to save movie"); | ||||||
| @@ -394,31 +397,45 @@ void Movie::SaveMovie() { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void Movie::Init() { | void Movie::StartPlayback(const std::string& movie_file, | ||||||
|     if (!Settings::values.movie_play.empty()) { |                           std::function<void()> completion_callback) { | ||||||
|     LOG_INFO(Movie, "Loading Movie for playback"); |     LOG_INFO(Movie, "Loading Movie for playback"); | ||||||
|         FileUtil::IOFile save_record(Settings::values.movie_play, "rb"); |     FileUtil::IOFile save_record(movie_file, "rb"); | ||||||
|         u64 size = save_record.GetSize(); |     const u64 size = save_record.GetSize(); | ||||||
|  |  | ||||||
|     if (save_record.IsGood() && size > sizeof(CTMHeader)) { |     if (save_record.IsGood() && size > sizeof(CTMHeader)) { | ||||||
|         CTMHeader header; |         CTMHeader header; | ||||||
|         save_record.ReadArray(&header, 1); |         save_record.ReadArray(&header, 1); | ||||||
|             if (ValidateHeader(header)) { |         if (ValidateHeader(header) != ValidationResult::Invalid) { | ||||||
|             play_mode = PlayMode::Playing; |             play_mode = PlayMode::Playing; | ||||||
|             recorded_input.resize(size - sizeof(CTMHeader)); |             recorded_input.resize(size - sizeof(CTMHeader)); | ||||||
|             save_record.ReadArray(recorded_input.data(), recorded_input.size()); |             save_record.ReadArray(recorded_input.data(), recorded_input.size()); | ||||||
|             current_byte = 0; |             current_byte = 0; | ||||||
|  |             playback_completion_callback = completion_callback; | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|             LOG_ERROR(Movie, "Failed to playback movie: Unable to open '{}'", |         LOG_ERROR(Movie, "Failed to playback movie: Unable to open '{}'", movie_file); | ||||||
|                       Settings::values.movie_play); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|     if (!Settings::values.movie_record.empty()) { | void Movie::StartRecording(const std::string& movie_file) { | ||||||
|     LOG_INFO(Movie, "Enabling Movie recording"); |     LOG_INFO(Movie, "Enabling Movie recording"); | ||||||
|     play_mode = PlayMode::Recording; |     play_mode = PlayMode::Recording; | ||||||
|  |     record_movie_file = movie_file; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Movie::ValidationResult Movie::ValidateMovie(const std::string& movie_file) const { | ||||||
|  |     LOG_INFO(Movie, "Validating Movie file '{}'", 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); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Movie::Shutdown() { | void Movie::Shutdown() { | ||||||
| @@ -428,6 +445,7 @@ void Movie::Shutdown() { | |||||||
|  |  | ||||||
|     play_mode = PlayMode::None; |     play_mode = PlayMode::None; | ||||||
|     recorded_input.resize(0); |     recorded_input.resize(0); | ||||||
|  |     record_movie_file.clear(); | ||||||
|     current_byte = 0; |     current_byte = 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ | |||||||
|  |  | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include <functional> | ||||||
| #include "common/common_types.h" | #include "common/common_types.h" | ||||||
|  |  | ||||||
| namespace Service { | namespace Service { | ||||||
| @@ -26,6 +27,12 @@ enum class PlayMode; | |||||||
|  |  | ||||||
| class Movie { | class Movie { | ||||||
| public: | public: | ||||||
|  |     enum class ValidationResult { | ||||||
|  |         OK, | ||||||
|  |         RevisionDismatch, | ||||||
|  |         GameDismatch, | ||||||
|  |         Invalid, | ||||||
|  |     }; | ||||||
|     /** |     /** | ||||||
|      * Gets the instance of the Movie singleton class. |      * Gets the instance of the Movie singleton class. | ||||||
|      * @returns Reference to the instance of the Movie singleton class. |      * @returns Reference to the instance of the Movie singleton class. | ||||||
| @@ -34,7 +41,10 @@ public: | |||||||
|         return s_instance; |         return s_instance; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void Init(); |     void StartPlayback(const std::string& movie_file, | ||||||
|  |                        std::function<void()> completion_callback = {}); | ||||||
|  |     void StartRecording(const std::string& movie_file); | ||||||
|  |     ValidationResult ValidateMovie(const std::string& movie_file) const; | ||||||
|  |  | ||||||
|     void Shutdown(); |     void Shutdown(); | ||||||
|  |  | ||||||
| @@ -74,14 +84,12 @@ public: | |||||||
|      * When playing: Replaces the given input states with the ones stored in the playback file |      * When playing: Replaces the given input states with the ones stored in the playback file | ||||||
|      */ |      */ | ||||||
|     void HandleExtraHidResponse(Service::IR::ExtraHIDResponse& extra_hid_response); |     void HandleExtraHidResponse(Service::IR::ExtraHIDResponse& extra_hid_response); | ||||||
|  |     bool IsPlayingInput() const; | ||||||
|  |     bool IsRecordingInput() const; | ||||||
|  |  | ||||||
| private: | private: | ||||||
|     static Movie s_instance; |     static Movie s_instance; | ||||||
|  |  | ||||||
|     bool IsPlayingInput(); |  | ||||||
|  |  | ||||||
|     bool IsRecordingInput(); |  | ||||||
|  |  | ||||||
|     void CheckInputEnd(); |     void CheckInputEnd(); | ||||||
|  |  | ||||||
|     template <typename... Targs> |     template <typename... Targs> | ||||||
| @@ -103,12 +111,14 @@ private: | |||||||
|     void Record(const Service::IR::PadState& pad_state, const s16& c_stick_x, const s16& c_stick_y); |     void Record(const Service::IR::PadState& pad_state, const s16& c_stick_x, const s16& c_stick_y); | ||||||
|     void Record(const Service::IR::ExtraHIDResponse& extra_hid_response); |     void Record(const Service::IR::ExtraHIDResponse& extra_hid_response); | ||||||
|  |  | ||||||
|     bool ValidateHeader(const CTMHeader& header); |     ValidationResult ValidateHeader(const CTMHeader& header) const; | ||||||
|  |  | ||||||
|     void SaveMovie(); |     void SaveMovie(); | ||||||
|  |  | ||||||
|     PlayMode play_mode; |     PlayMode play_mode; | ||||||
|  |     std::string record_movie_file; | ||||||
|     std::vector<u8> recorded_input; |     std::vector<u8> recorded_input; | ||||||
|  |     std::function<void()> playback_completion_callback; | ||||||
|     size_t current_byte = 0; |     size_t current_byte = 0; | ||||||
| }; | }; | ||||||
| } // namespace Core | } // namespace Core | ||||||
| @@ -156,10 +156,6 @@ struct Values { | |||||||
|     std::string log_filter; |     std::string log_filter; | ||||||
|     std::unordered_map<std::string, bool> lle_modules; |     std::unordered_map<std::string, bool> lle_modules; | ||||||
|  |  | ||||||
|     // Movie |  | ||||||
|     std::string movie_play; |  | ||||||
|     std::string movie_record; |  | ||||||
|  |  | ||||||
|     // WebService |     // WebService | ||||||
|     bool enable_telemetry; |     bool enable_telemetry; | ||||||
|     std::string telemetry_endpoint_url; |     std::string telemetry_endpoint_url; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user