core/movie: Movie refactor, add a completion callback
This commit is contained in:
parent
6cb9a45154
commit
0f44f7b481
5 changed files with 77 additions and 47 deletions
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue