core/movie: Movie refactor, add a completion callback

This commit is contained in:
zhupengfei 2018-07-07 22:30:37 +08:00 committed by fearlessTobi
parent 6cb9a45154
commit 0f44f7b481
5 changed files with 77 additions and 47 deletions

View file

@ -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;
} }

View file

@ -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();

View file

@ -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;
} }

View file

@ -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

View file

@ -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;