core/movie: Movie refactor, add a completion callback
This commit is contained in:
		| @@ -39,6 +39,7 @@ | ||||
| #include "core/gdbstub/gdbstub.h" | ||||
| #include "core/hle/service/am/am.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/movie.h" | ||||
| #include "core/settings.h" | ||||
| #include "network/network.h" | ||||
|  | ||||
| @@ -268,8 +269,6 @@ int main(int argc, char** argv) { | ||||
|     // Apply the command line arguments | ||||
|     Settings::values.gdbstub_port = gdb_port; | ||||
|     Settings::values.use_gdbstub = use_gdbstub; | ||||
|     Settings::values.movie_play = std::move(movie_play); | ||||
|     Settings::values.movie_record = std::move(movie_record); | ||||
|     Settings::Apply(); | ||||
|  | ||||
|     // 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()) { | ||||
|         system.RunLoop(); | ||||
|     } | ||||
|  | ||||
|     Core::Movie::GetInstance().Shutdown(); | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|   | ||||
| @@ -176,7 +176,6 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | ||||
|     Kernel::Init(system_mode); | ||||
|     Service::Init(service_manager); | ||||
|     GDBStub::Init(); | ||||
|     Movie::GetInstance().Init(); | ||||
|  | ||||
|     if (!VideoCore::Init(emu_window)) { | ||||
|         return ResultStatus::ErrorVideoCore; | ||||
| @@ -214,7 +213,6 @@ void System::Shutdown() { | ||||
|                          perf_results.frametime * 1000.0); | ||||
|  | ||||
|     // Shutdown emulation session | ||||
|     Movie::GetInstance().Shutdown(); | ||||
|     GDBStub::Shutdown(); | ||||
|     VideoCore::Shutdown(); | ||||
|     Service::Shutdown(); | ||||
|   | ||||
| @@ -118,10 +118,10 @@ struct CTMHeader { | ||||
| static_assert(sizeof(CTMHeader) == 256, "CTMHeader should be 256 bytes"); | ||||
| #pragma pack(pop) | ||||
|  | ||||
| bool Movie::IsPlayingInput() { | ||||
| bool Movie::IsPlayingInput() const { | ||||
|     return play_mode == PlayMode::Playing; | ||||
| } | ||||
| bool Movie::IsRecordingInput() { | ||||
| bool Movie::IsRecordingInput() const { | ||||
|     return play_mode == PlayMode::Recording; | ||||
| } | ||||
|  | ||||
| @@ -129,6 +129,7 @@ void Movie::CheckInputEnd() { | ||||
|     if (current_byte + sizeof(ControllerState) > recorded_input.size()) { | ||||
|         LOG_INFO(Movie, "Playback finished"); | ||||
|         play_mode = PlayMode::None; | ||||
|         playback_completion_callback(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -343,33 +344,35 @@ void Movie::Record(const Service::IR::ExtraHIDResponse& extra_hid_response) { | ||||
|     Record(s); | ||||
| } | ||||
|  | ||||
| bool Movie::ValidateHeader(const CTMHeader& header) { | ||||
| Movie::ValidationResult Movie::ValidateHeader(const CTMHeader& header) const { | ||||
|     if (header_magic_bytes != header.filetype) { | ||||
|         LOG_ERROR(Movie, "Playback file does not have valid header"); | ||||
|         return false; | ||||
|         return ValidationResult::Invalid; | ||||
|     } | ||||
|  | ||||
|     std::string revision = | ||||
|         Common::ArrayToString(header.revision.data(), header.revision.size(), 21, false); | ||||
|     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; | ||||
|     Core::System::GetInstance().GetAppLoader().ReadProgramId(program_id); | ||||
|     if (program_id != header.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() { | ||||
|     LOG_INFO(Movie, "Saving movie"); | ||||
|     FileUtil::IOFile save_record(Settings::values.movie_record, "wb"); | ||||
|     LOG_INFO(Movie, "Saving recorded movie to '{}'", record_movie_file); | ||||
|     FileUtil::IOFile save_record(record_movie_file, "wb"); | ||||
|  | ||||
|     if (!save_record.IsGood()) { | ||||
|         LOG_ERROR(Movie, "Unable to open file to save movie"); | ||||
| @@ -394,31 +397,45 @@ void Movie::SaveMovie() { | ||||
|     } | ||||
| } | ||||
|  | ||||
| void Movie::Init() { | ||||
|     if (!Settings::values.movie_play.empty()) { | ||||
|         LOG_INFO(Movie, "Loading Movie for playback"); | ||||
|         FileUtil::IOFile save_record(Settings::values.movie_play, "rb"); | ||||
|         u64 size = save_record.GetSize(); | ||||
| void Movie::StartPlayback(const std::string& movie_file, | ||||
|                           std::function<void()> completion_callback) { | ||||
|     LOG_INFO(Movie, "Loading Movie for playback"); | ||||
|     FileUtil::IOFile save_record(movie_file, "rb"); | ||||
|     const u64 size = save_record.GetSize(); | ||||
|  | ||||
|         if (save_record.IsGood() && size > sizeof(CTMHeader)) { | ||||
|             CTMHeader header; | ||||
|             save_record.ReadArray(&header, 1); | ||||
|             if (ValidateHeader(header)) { | ||||
|                 play_mode = PlayMode::Playing; | ||||
|                 recorded_input.resize(size - sizeof(CTMHeader)); | ||||
|                 save_record.ReadArray(recorded_input.data(), recorded_input.size()); | ||||
|                 current_byte = 0; | ||||
|             } | ||||
|         } else { | ||||
|             LOG_ERROR(Movie, "Failed to playback movie: Unable to open '{}'", | ||||
|                       Settings::values.movie_play); | ||||
|     if (save_record.IsGood() && size > sizeof(CTMHeader)) { | ||||
|         CTMHeader header; | ||||
|         save_record.ReadArray(&header, 1); | ||||
|         if (ValidateHeader(header) != ValidationResult::Invalid) { | ||||
|             play_mode = PlayMode::Playing; | ||||
|             recorded_input.resize(size - sizeof(CTMHeader)); | ||||
|             save_record.ReadArray(recorded_input.data(), recorded_input.size()); | ||||
|             current_byte = 0; | ||||
|             playback_completion_callback = completion_callback; | ||||
|         } | ||||
|     } else { | ||||
|         LOG_ERROR(Movie, "Failed to playback movie: Unable to open '{}'", 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; | ||||
| } | ||||
|  | ||||
| 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; | ||||
|     } | ||||
|  | ||||
|     if (!Settings::values.movie_record.empty()) { | ||||
|         LOG_INFO(Movie, "Enabling Movie recording"); | ||||
|         play_mode = PlayMode::Recording; | ||||
|     } | ||||
|     CTMHeader header; | ||||
|     save_record.ReadArray(&header, 1); | ||||
|     return ValidateHeader(header); | ||||
| } | ||||
|  | ||||
| void Movie::Shutdown() { | ||||
| @@ -428,6 +445,7 @@ void Movie::Shutdown() { | ||||
|  | ||||
|     play_mode = PlayMode::None; | ||||
|     recorded_input.resize(0); | ||||
|     record_movie_file.clear(); | ||||
|     current_byte = 0; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,6 +4,7 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <functional> | ||||
| #include "common/common_types.h" | ||||
|  | ||||
| namespace Service { | ||||
| @@ -26,6 +27,12 @@ enum class PlayMode; | ||||
|  | ||||
| class Movie { | ||||
| public: | ||||
|     enum class ValidationResult { | ||||
|         OK, | ||||
|         RevisionDismatch, | ||||
|         GameDismatch, | ||||
|         Invalid, | ||||
|     }; | ||||
|     /** | ||||
|      * Gets 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; | ||||
|     } | ||||
|  | ||||
|     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(); | ||||
|  | ||||
| @@ -74,14 +84,12 @@ public: | ||||
|      * When playing: Replaces the given input states with the ones stored in the playback file | ||||
|      */ | ||||
|     void HandleExtraHidResponse(Service::IR::ExtraHIDResponse& extra_hid_response); | ||||
|     bool IsPlayingInput() const; | ||||
|     bool IsRecordingInput() const; | ||||
|  | ||||
| private: | ||||
|     static Movie s_instance; | ||||
|  | ||||
|     bool IsPlayingInput(); | ||||
|  | ||||
|     bool IsRecordingInput(); | ||||
|  | ||||
|     void CheckInputEnd(); | ||||
|  | ||||
|     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::ExtraHIDResponse& extra_hid_response); | ||||
|  | ||||
|     bool ValidateHeader(const CTMHeader& header); | ||||
|     ValidationResult ValidateHeader(const CTMHeader& header) const; | ||||
|  | ||||
|     void SaveMovie(); | ||||
|  | ||||
|     PlayMode play_mode; | ||||
|     std::string record_movie_file; | ||||
|     std::vector<u8> recorded_input; | ||||
|     std::function<void()> playback_completion_callback; | ||||
|     size_t current_byte = 0; | ||||
| }; | ||||
| } // namespace Core | ||||
| @@ -156,10 +156,6 @@ struct Values { | ||||
|     std::string log_filter; | ||||
|     std::unordered_map<std::string, bool> lle_modules; | ||||
|  | ||||
|     // Movie | ||||
|     std::string movie_play; | ||||
|     std::string movie_record; | ||||
|  | ||||
|     // WebService | ||||
|     bool enable_telemetry; | ||||
|     std::string telemetry_endpoint_url; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user