mirror of
https://github.com/citra-emu/citra.git
synced 2024-12-27 04:30:05 +00:00
Merge pull request #2882 from danzel/movie-squash
Movie (Game Inputs) recording and playback
This commit is contained in:
commit
44d07574b1
src
citra
common/logging
core
@ -41,13 +41,16 @@
|
|||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
|
|
||||||
static void PrintHelp(const char* argv0) {
|
static void PrintHelp(const char* argv0) {
|
||||||
std::cout << "Usage: " << argv0 << " [options] <filename>\n"
|
std::cout << "Usage: " << argv0
|
||||||
"-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
|
<< " [options] <filename>\n"
|
||||||
"-i, --install=FILE Installs a specified CIA file\n"
|
"-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
|
||||||
"-m, --multiplayer=nick:password@address:port"
|
"-i, --install=FILE Installs a specified CIA file\n"
|
||||||
" Nickname, password, address and port for multiplayer\n"
|
"-m, --multiplayer=nick:password@address:port"
|
||||||
"-h, --help Display this help and exit\n"
|
" Nickname, password, address and port for multiplayer\n"
|
||||||
"-v, --version Output version information and exit\n";
|
"-r, --movie-record=[file] Record a movie (game inputs) to the given file\n"
|
||||||
|
"-p, --movie-play=[file] Playback the movie (game inputs) from the given file\n"
|
||||||
|
"-h, --help Display this help and exit\n"
|
||||||
|
"-v, --version Output version information and exit\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PrintVersion() {
|
static void PrintVersion() {
|
||||||
@ -107,6 +110,9 @@ int main(int argc, char** argv) {
|
|||||||
int option_index = 0;
|
int option_index = 0;
|
||||||
bool use_gdbstub = Settings::values.use_gdbstub;
|
bool use_gdbstub = Settings::values.use_gdbstub;
|
||||||
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
|
u32 gdb_port = static_cast<u32>(Settings::values.gdbstub_port);
|
||||||
|
std::string movie_record;
|
||||||
|
std::string movie_play;
|
||||||
|
|
||||||
char* endarg;
|
char* endarg;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
int argc_w;
|
int argc_w;
|
||||||
@ -127,12 +133,13 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
static struct option long_options[] = {
|
static struct option long_options[] = {
|
||||||
{"gdbport", required_argument, 0, 'g'}, {"install", required_argument, 0, 'i'},
|
{"gdbport", required_argument, 0, 'g'}, {"install", required_argument, 0, 'i'},
|
||||||
{"multiplayer", required_argument, 0, 'm'}, {"help", no_argument, 0, 'h'},
|
{"multiplayer", required_argument, 0, 'm'}, {"movie-record", required_argument, 0, 'r'},
|
||||||
|
{"movie-play", required_argument, 0, 'p'}, {"help", no_argument, 0, 'h'},
|
||||||
{"version", no_argument, 0, 'v'}, {0, 0, 0, 0},
|
{"version", no_argument, 0, 'v'}, {0, 0, 0, 0},
|
||||||
};
|
};
|
||||||
|
|
||||||
while (optind < argc) {
|
while (optind < argc) {
|
||||||
char arg = getopt_long(argc, argv, "g:i:m:hv", long_options, &option_index);
|
char arg = getopt_long(argc, argv, "g:i:m:r:p:hv", long_options, &option_index);
|
||||||
if (arg != -1) {
|
if (arg != -1) {
|
||||||
switch (arg) {
|
switch (arg) {
|
||||||
case 'g':
|
case 'g':
|
||||||
@ -189,6 +196,12 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'r':
|
||||||
|
movie_record = optarg;
|
||||||
|
break;
|
||||||
|
case 'p':
|
||||||
|
movie_play = optarg;
|
||||||
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
PrintHelp(argv[0]);
|
PrintHelp(argv[0]);
|
||||||
return 0;
|
return 0;
|
||||||
@ -221,11 +234,18 @@ int main(int argc, char** argv) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!movie_record.empty() && !movie_play.empty()) {
|
||||||
|
LOG_CRITICAL(Frontend, "Cannot both play and record a movie");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
log_filter.ParseFilterString(Settings::values.log_filter);
|
log_filter.ParseFilterString(Settings::values.log_filter);
|
||||||
|
|
||||||
// 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();
|
||||||
|
|
||||||
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>()};
|
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>()};
|
||||||
|
@ -74,6 +74,7 @@ namespace Log {
|
|||||||
SUB(Audio, Sink) \
|
SUB(Audio, Sink) \
|
||||||
CLS(Input) \
|
CLS(Input) \
|
||||||
CLS(Network) \
|
CLS(Network) \
|
||||||
|
CLS(Movie) \
|
||||||
CLS(Loader) \
|
CLS(Loader) \
|
||||||
CLS(WebService)
|
CLS(WebService)
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ enum class Class : ClassType {
|
|||||||
Loader, ///< ROM loader
|
Loader, ///< ROM loader
|
||||||
Input, ///< Input emulation
|
Input, ///< Input emulation
|
||||||
Network, ///< Network emulation
|
Network, ///< Network emulation
|
||||||
|
Movie, ///< Movie (Input Recording) Playback
|
||||||
WebService, ///< Interface to Citra Web Services
|
WebService, ///< Interface to Citra Web Services
|
||||||
Count ///< Total number of logging classes
|
Count ///< Total number of logging classes
|
||||||
};
|
};
|
||||||
|
@ -386,6 +386,8 @@ add_library(core STATIC
|
|||||||
memory.h
|
memory.h
|
||||||
memory_setup.h
|
memory_setup.h
|
||||||
mmio.h
|
mmio.h
|
||||||
|
movie.cpp
|
||||||
|
movie.h
|
||||||
perf_stats.cpp
|
perf_stats.cpp
|
||||||
perf_stats.h
|
perf_stats.h
|
||||||
settings.cpp
|
settings.cpp
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
#include "core/hw/hw.h"
|
#include "core/hw/hw.h"
|
||||||
#include "core/loader/loader.h"
|
#include "core/loader/loader.h"
|
||||||
#include "core/memory_setup.h"
|
#include "core/memory_setup.h"
|
||||||
|
#include "core/movie.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
@ -167,6 +168,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
|||||||
Service::Init();
|
Service::Init();
|
||||||
AudioCore::Init();
|
AudioCore::Init();
|
||||||
GDBStub::Init();
|
GDBStub::Init();
|
||||||
|
Movie::GetInstance().Init();
|
||||||
|
|
||||||
if (!VideoCore::Init(emu_window)) {
|
if (!VideoCore::Init(emu_window)) {
|
||||||
return ResultStatus::ErrorVideoCore;
|
return ResultStatus::ErrorVideoCore;
|
||||||
@ -192,6 +194,7 @@ 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();
|
||||||
AudioCore::Shutdown();
|
AudioCore::Shutdown();
|
||||||
VideoCore::Shutdown();
|
VideoCore::Shutdown();
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
#include "core/hle/service/hid/hid_spvr.h"
|
#include "core/hle/service/hid/hid_spvr.h"
|
||||||
#include "core/hle/service/hid/hid_user.h"
|
#include "core/hle/service/hid/hid_user.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
#include "core/movie.h"
|
||||||
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace HID {
|
namespace HID {
|
||||||
@ -96,6 +98,9 @@ void Module::UpdatePadCallback(u64 userdata, int cycles_late) {
|
|||||||
constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
|
constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
|
||||||
s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS);
|
s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS);
|
||||||
s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS);
|
s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS);
|
||||||
|
|
||||||
|
Core::Movie::GetInstance().HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y);
|
||||||
|
|
||||||
const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y);
|
const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y);
|
||||||
state.circle_up.Assign(direction.up);
|
state.circle_up.Assign(direction.up);
|
||||||
state.circle_down.Assign(direction.down);
|
state.circle_down.Assign(direction.down);
|
||||||
@ -141,6 +146,8 @@ void Module::UpdatePadCallback(u64 userdata, int cycles_late) {
|
|||||||
touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
|
touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
|
||||||
touch_entry.valid.Assign(pressed ? 1 : 0);
|
touch_entry.valid.Assign(pressed ? 1 : 0);
|
||||||
|
|
||||||
|
Core::Movie::GetInstance().HandleTouchStatus(touch_entry);
|
||||||
|
|
||||||
// TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which
|
// TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which
|
||||||
// supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being
|
// supposedly is "Touch-screen entry, which contains the raw coordinate data prior to being
|
||||||
// converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8).
|
// converted to pixel coordinates." (http://3dbrew.org/wiki/HID_Shared_Memory#Offset_0xA8).
|
||||||
@ -179,6 +186,8 @@ void Module::UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
|
|||||||
accelerometer_entry.y = static_cast<s16>(accel.y);
|
accelerometer_entry.y = static_cast<s16>(accel.y);
|
||||||
accelerometer_entry.z = static_cast<s16>(accel.z);
|
accelerometer_entry.z = static_cast<s16>(accel.z);
|
||||||
|
|
||||||
|
Core::Movie::GetInstance().HandleAccelerometerStatus(accelerometer_entry);
|
||||||
|
|
||||||
// Make up "raw" entry
|
// Make up "raw" entry
|
||||||
// TODO(wwylele):
|
// TODO(wwylele):
|
||||||
// From hardware testing, the raw_entry values are approximately, but not exactly, as twice as
|
// From hardware testing, the raw_entry values are approximately, but not exactly, as twice as
|
||||||
@ -217,6 +226,8 @@ void Module::UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
|
|||||||
gyroscope_entry.y = static_cast<s16>(gyro.y);
|
gyroscope_entry.y = static_cast<s16>(gyro.y);
|
||||||
gyroscope_entry.z = static_cast<s16>(gyro.z);
|
gyroscope_entry.z = static_cast<s16>(gyro.z);
|
||||||
|
|
||||||
|
Core::Movie::GetInstance().HandleGyroscopeStatus(gyroscope_entry);
|
||||||
|
|
||||||
// Make up "raw" entry
|
// Make up "raw" entry
|
||||||
mem->gyroscope.raw_entry.x = gyroscope_entry.x;
|
mem->gyroscope.raw_entry.x = gyroscope_entry.x;
|
||||||
mem->gyroscope.raw_entry.z = -gyroscope_entry.y;
|
mem->gyroscope.raw_entry.z = -gyroscope_entry.y;
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/bit_field.h"
|
|
||||||
#include "common/string_util.h"
|
#include "common/string_util.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/hle/service/ir/extra_hid.h"
|
#include "core/hle/service/ir/extra_hid.h"
|
||||||
|
#include "core/movie.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
@ -176,22 +176,6 @@ void ExtraHID::SendHIDStatus() {
|
|||||||
if (is_device_reload_pending.exchange(false))
|
if (is_device_reload_pending.exchange(false))
|
||||||
LoadInputDevices();
|
LoadInputDevices();
|
||||||
|
|
||||||
struct {
|
|
||||||
union {
|
|
||||||
BitField<0, 8, u32_le> header;
|
|
||||||
BitField<8, 12, u32_le> c_stick_x;
|
|
||||||
BitField<20, 12, u32_le> c_stick_y;
|
|
||||||
} c_stick;
|
|
||||||
union {
|
|
||||||
BitField<0, 5, u8> battery_level;
|
|
||||||
BitField<5, 1, u8> zl_not_held;
|
|
||||||
BitField<6, 1, u8> zr_not_held;
|
|
||||||
BitField<7, 1, u8> r_not_held;
|
|
||||||
} buttons;
|
|
||||||
u8 unknown;
|
|
||||||
} response;
|
|
||||||
static_assert(sizeof(response) == 6, "HID status response has wrong size!");
|
|
||||||
|
|
||||||
constexpr int C_STICK_CENTER = 0x800;
|
constexpr int C_STICK_CENTER = 0x800;
|
||||||
// TODO(wwylele): this value is not accurately measured. We currently assume that the axis can
|
// TODO(wwylele): this value is not accurately measured. We currently assume that the axis can
|
||||||
// take values in the whole range of a 12-bit integer.
|
// take values in the whole range of a 12-bit integer.
|
||||||
@ -200,6 +184,7 @@ void ExtraHID::SendHIDStatus() {
|
|||||||
float x, y;
|
float x, y;
|
||||||
std::tie(x, y) = c_stick->GetStatus();
|
std::tie(x, y) = c_stick->GetStatus();
|
||||||
|
|
||||||
|
ExtraHIDResponse response;
|
||||||
response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
|
response.c_stick.header.Assign(static_cast<u8>(ResponseID::PollHID));
|
||||||
response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
|
response.c_stick.c_stick_x.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * x));
|
||||||
response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
|
response.c_stick.c_stick_y.Assign(static_cast<u32>(C_STICK_CENTER + C_STICK_RADIUS * y));
|
||||||
@ -209,6 +194,8 @@ void ExtraHID::SendHIDStatus() {
|
|||||||
response.buttons.r_not_held.Assign(1);
|
response.buttons.r_not_held.Assign(1);
|
||||||
response.unknown = 0;
|
response.unknown = 0;
|
||||||
|
|
||||||
|
Core::Movie::GetInstance().HandleExtraHidResponse(response);
|
||||||
|
|
||||||
std::vector<u8> response_buffer(sizeof(response));
|
std::vector<u8> response_buffer(sizeof(response));
|
||||||
memcpy(response_buffer.data(), &response, sizeof(response));
|
memcpy(response_buffer.data(), &response, sizeof(response));
|
||||||
Send(response_buffer);
|
Send(response_buffer);
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "common/swap.h"
|
||||||
#include "core/frontend/input.h"
|
#include "core/frontend/input.h"
|
||||||
#include "core/hle/service/ir/ir_user.h"
|
#include "core/hle/service/ir/ir_user.h"
|
||||||
|
|
||||||
@ -16,6 +18,22 @@ struct EventType;
|
|||||||
namespace Service {
|
namespace Service {
|
||||||
namespace IR {
|
namespace IR {
|
||||||
|
|
||||||
|
struct ExtraHIDResponse {
|
||||||
|
union {
|
||||||
|
BitField<0, 8, u32_le> header;
|
||||||
|
BitField<8, 12, u32_le> c_stick_x;
|
||||||
|
BitField<20, 12, u32_le> c_stick_y;
|
||||||
|
} c_stick;
|
||||||
|
union {
|
||||||
|
BitField<0, 5, u8> battery_level;
|
||||||
|
BitField<5, 1, u8> zl_not_held;
|
||||||
|
BitField<6, 1, u8> zr_not_held;
|
||||||
|
BitField<7, 1, u8> r_not_held;
|
||||||
|
} buttons;
|
||||||
|
u8 unknown;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ExtraHIDResponse) == 6, "HID status response has wrong size!");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An IRDevice emulating Circle Pad Pro or New 3DS additional HID hardware.
|
* An IRDevice emulating Circle Pad Pro or New 3DS additional HID hardware.
|
||||||
* This device sends periodic udates at a rate configured by the 3DS, and sends calibration data if
|
* This device sends periodic udates at a rate configured by the 3DS, and sends calibration data if
|
||||||
|
@ -2,30 +2,18 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
#include "common/bit_field.h"
|
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
#include "core/hle/kernel/event.h"
|
#include "core/hle/kernel/event.h"
|
||||||
#include "core/hle/kernel/shared_memory.h"
|
#include "core/hle/kernel/shared_memory.h"
|
||||||
#include "core/hle/service/hid/hid.h"
|
#include "core/hle/service/hid/hid.h"
|
||||||
#include "core/hle/service/ir/ir_rst.h"
|
#include "core/hle/service/ir/ir_rst.h"
|
||||||
|
#include "core/movie.h"
|
||||||
#include "core/settings.h"
|
#include "core/settings.h"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
namespace IR {
|
namespace IR {
|
||||||
|
|
||||||
union PadState {
|
|
||||||
u32_le hex{};
|
|
||||||
|
|
||||||
BitField<14, 1, u32_le> zl;
|
|
||||||
BitField<15, 1, u32_le> zr;
|
|
||||||
|
|
||||||
BitField<24, 1, u32_le> c_stick_right;
|
|
||||||
BitField<25, 1, u32_le> c_stick_left;
|
|
||||||
BitField<26, 1, u32_le> c_stick_up;
|
|
||||||
BitField<27, 1, u32_le> c_stick_down;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PadDataEntry {
|
struct PadDataEntry {
|
||||||
PadState current_state;
|
PadState current_state;
|
||||||
PadState delta_additions;
|
PadState delta_additions;
|
||||||
@ -74,8 +62,10 @@ void IR_RST::UpdateCallback(u64 userdata, int cycles_late) {
|
|||||||
float c_stick_x_f, c_stick_y_f;
|
float c_stick_x_f, c_stick_y_f;
|
||||||
std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus();
|
std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus();
|
||||||
constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius
|
constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius
|
||||||
const s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS);
|
s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS);
|
||||||
const s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS);
|
s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS);
|
||||||
|
|
||||||
|
Core::Movie::GetInstance().HandleIrRst(state, c_stick_x, c_stick_y);
|
||||||
|
|
||||||
if (!raw_c_stick) {
|
if (!raw_c_stick) {
|
||||||
const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y);
|
const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y);
|
||||||
|
@ -6,6 +6,9 @@
|
|||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/swap.h"
|
||||||
#include "core/frontend/input.h"
|
#include "core/frontend/input.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
@ -22,6 +25,18 @@ class EventType;
|
|||||||
namespace Service {
|
namespace Service {
|
||||||
namespace IR {
|
namespace IR {
|
||||||
|
|
||||||
|
union PadState {
|
||||||
|
u32_le hex{};
|
||||||
|
|
||||||
|
BitField<14, 1, u32_le> zl;
|
||||||
|
BitField<15, 1, u32_le> zr;
|
||||||
|
|
||||||
|
BitField<24, 1, u32_le> c_stick_right;
|
||||||
|
BitField<25, 1, u32_le> c_stick_left;
|
||||||
|
BitField<26, 1, u32_le> c_stick_up;
|
||||||
|
BitField<27, 1, u32_le> c_stick_down;
|
||||||
|
};
|
||||||
|
|
||||||
/// Interface to "ir:rst" service
|
/// Interface to "ir:rst" service
|
||||||
class IR_RST final : public ServiceFramework<IR_RST> {
|
class IR_RST final : public ServiceFramework<IR_RST> {
|
||||||
public:
|
public:
|
||||||
|
466
src/core/movie.cpp
Normal file
466
src/core/movie.cpp
Normal file
@ -0,0 +1,466 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <cryptopp/hex.h>
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scm_rev.h"
|
||||||
|
#include "common/string_util.h"
|
||||||
|
#include "common/swap.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/hle/service/hid/hid.h"
|
||||||
|
#include "core/hle/service/ir/extra_hid.h"
|
||||||
|
#include "core/hle/service/ir/ir_rst.h"
|
||||||
|
#include "core/movie.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
|
||||||
|
/*static*/ Movie Movie::s_instance;
|
||||||
|
|
||||||
|
enum class PlayMode { None, Recording, Playing };
|
||||||
|
|
||||||
|
enum class ControllerStateType : u8 {
|
||||||
|
PadAndCircle,
|
||||||
|
Touch,
|
||||||
|
Accelerometer,
|
||||||
|
Gyroscope,
|
||||||
|
IrRst,
|
||||||
|
ExtraHidResponse
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct ControllerState {
|
||||||
|
ControllerStateType type;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
union {
|
||||||
|
u16_le hex;
|
||||||
|
|
||||||
|
BitField<0, 1, u16_le> a;
|
||||||
|
BitField<1, 1, u16_le> b;
|
||||||
|
BitField<2, 1, u16_le> select;
|
||||||
|
BitField<3, 1, u16_le> start;
|
||||||
|
BitField<4, 1, u16_le> right;
|
||||||
|
BitField<5, 1, u16_le> left;
|
||||||
|
BitField<6, 1, u16_le> up;
|
||||||
|
BitField<7, 1, u16_le> down;
|
||||||
|
BitField<8, 1, u16_le> r;
|
||||||
|
BitField<9, 1, u16_le> l;
|
||||||
|
BitField<10, 1, u16_le> x;
|
||||||
|
BitField<11, 1, u16_le> y;
|
||||||
|
// Bits 12-15 are currently unused
|
||||||
|
};
|
||||||
|
s16_le circle_pad_x;
|
||||||
|
s16_le circle_pad_y;
|
||||||
|
} pad_and_circle;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u16_le x;
|
||||||
|
u16_le y;
|
||||||
|
// This is a bool, u8 for platform compatibility
|
||||||
|
u8 valid;
|
||||||
|
} touch;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
s16_le x;
|
||||||
|
s16_le y;
|
||||||
|
s16_le z;
|
||||||
|
} accelerometer;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
s16_le x;
|
||||||
|
s16_le y;
|
||||||
|
s16_le z;
|
||||||
|
} gyroscope;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
s16_le x;
|
||||||
|
s16_le y;
|
||||||
|
// These are bool, u8 for platform compatibility
|
||||||
|
u8 zl;
|
||||||
|
u8 zr;
|
||||||
|
} ir_rst;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
union {
|
||||||
|
u32_le hex;
|
||||||
|
|
||||||
|
BitField<0, 5, u32_le> battery_level;
|
||||||
|
BitField<5, 1, u32_le> zl_not_held;
|
||||||
|
BitField<6, 1, u32_le> zr_not_held;
|
||||||
|
BitField<7, 1, u32_le> r_not_held;
|
||||||
|
BitField<8, 12, u32_le> c_stick_x;
|
||||||
|
BitField<20, 12, u32_le> c_stick_y;
|
||||||
|
};
|
||||||
|
} extra_hid_response;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(ControllerState) == 7, "ControllerState should be 7 bytes");
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
constexpr std::array<u8, 4> header_magic_bytes{{'C', 'T', 'M', 0x1B}};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct CTMHeader {
|
||||||
|
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
|
||||||
|
std::array<u8, 20> revision; /// Git hash of the revision this movie was created with
|
||||||
|
|
||||||
|
std::array<u8, 224> reserved; /// Make heading 256 bytes so it has consistent size
|
||||||
|
};
|
||||||
|
static_assert(sizeof(CTMHeader) == 256, "CTMHeader should be 256 bytes");
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
bool Movie::IsPlayingInput() {
|
||||||
|
return play_mode == PlayMode::Playing;
|
||||||
|
}
|
||||||
|
bool Movie::IsRecordingInput() {
|
||||||
|
return play_mode == PlayMode::Recording;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::CheckInputEnd() {
|
||||||
|
if (current_byte + sizeof(ControllerState) > recorded_input.size()) {
|
||||||
|
LOG_INFO(Movie, "Playback finished");
|
||||||
|
play_mode = PlayMode::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Play(Service::HID::PadState& pad_state, s16& circle_pad_x, s16& circle_pad_y) {
|
||||||
|
ControllerState s;
|
||||||
|
std::memcpy(&s, &recorded_input[current_byte], sizeof(ControllerState));
|
||||||
|
current_byte += sizeof(ControllerState);
|
||||||
|
|
||||||
|
if (s.type != ControllerStateType::PadAndCircle) {
|
||||||
|
LOG_ERROR(Movie,
|
||||||
|
"Expected to read type %d, but found %d. Your playback will be out of sync",
|
||||||
|
ControllerStateType::PadAndCircle, s.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pad_state.a.Assign(s.pad_and_circle.a);
|
||||||
|
pad_state.b.Assign(s.pad_and_circle.b);
|
||||||
|
pad_state.select.Assign(s.pad_and_circle.select);
|
||||||
|
pad_state.start.Assign(s.pad_and_circle.start);
|
||||||
|
pad_state.right.Assign(s.pad_and_circle.right);
|
||||||
|
pad_state.left.Assign(s.pad_and_circle.left);
|
||||||
|
pad_state.up.Assign(s.pad_and_circle.up);
|
||||||
|
pad_state.down.Assign(s.pad_and_circle.down);
|
||||||
|
pad_state.r.Assign(s.pad_and_circle.r);
|
||||||
|
pad_state.l.Assign(s.pad_and_circle.l);
|
||||||
|
pad_state.x.Assign(s.pad_and_circle.x);
|
||||||
|
pad_state.y.Assign(s.pad_and_circle.y);
|
||||||
|
|
||||||
|
circle_pad_x = s.pad_and_circle.circle_pad_x;
|
||||||
|
circle_pad_y = s.pad_and_circle.circle_pad_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Play(Service::HID::TouchDataEntry& touch_data) {
|
||||||
|
ControllerState s;
|
||||||
|
std::memcpy(&s, &recorded_input[current_byte], sizeof(ControllerState));
|
||||||
|
current_byte += sizeof(ControllerState);
|
||||||
|
|
||||||
|
if (s.type != ControllerStateType::Touch) {
|
||||||
|
LOG_ERROR(Movie,
|
||||||
|
"Expected to read type %d, but found %d. Your playback will be out of sync",
|
||||||
|
ControllerStateType::Touch, s.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
touch_data.x = s.touch.x;
|
||||||
|
touch_data.y = s.touch.y;
|
||||||
|
touch_data.valid.Assign(s.touch.valid);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Play(Service::HID::AccelerometerDataEntry& accelerometer_data) {
|
||||||
|
ControllerState s;
|
||||||
|
std::memcpy(&s, &recorded_input[current_byte], sizeof(ControllerState));
|
||||||
|
current_byte += sizeof(ControllerState);
|
||||||
|
|
||||||
|
if (s.type != ControllerStateType::Accelerometer) {
|
||||||
|
LOG_ERROR(Movie,
|
||||||
|
"Expected to read type %d, but found %d. Your playback will be out of sync",
|
||||||
|
ControllerStateType::Accelerometer, s.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
accelerometer_data.x = s.accelerometer.x;
|
||||||
|
accelerometer_data.y = s.accelerometer.y;
|
||||||
|
accelerometer_data.z = s.accelerometer.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Play(Service::HID::GyroscopeDataEntry& gyroscope_data) {
|
||||||
|
ControllerState s;
|
||||||
|
std::memcpy(&s, &recorded_input[current_byte], sizeof(ControllerState));
|
||||||
|
current_byte += sizeof(ControllerState);
|
||||||
|
|
||||||
|
if (s.type != ControllerStateType::Gyroscope) {
|
||||||
|
LOG_ERROR(Movie,
|
||||||
|
"Expected to read type %d, but found %d. Your playback will be out of sync",
|
||||||
|
ControllerStateType::Gyroscope, s.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gyroscope_data.x = s.gyroscope.x;
|
||||||
|
gyroscope_data.y = s.gyroscope.y;
|
||||||
|
gyroscope_data.z = s.gyroscope.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Play(Service::IR::PadState& pad_state, s16& c_stick_x, s16& c_stick_y) {
|
||||||
|
ControllerState s;
|
||||||
|
std::memcpy(&s, &recorded_input[current_byte], sizeof(ControllerState));
|
||||||
|
current_byte += sizeof(ControllerState);
|
||||||
|
|
||||||
|
if (s.type != ControllerStateType::IrRst) {
|
||||||
|
LOG_ERROR(Movie,
|
||||||
|
"Expected to read type %d, but found %d. Your playback will be out of sync",
|
||||||
|
ControllerStateType::IrRst, s.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
c_stick_x = s.ir_rst.x;
|
||||||
|
c_stick_y = s.ir_rst.y;
|
||||||
|
pad_state.zl.Assign(s.ir_rst.zl);
|
||||||
|
pad_state.zr.Assign(s.ir_rst.zr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Play(Service::IR::ExtraHIDResponse& extra_hid_response) {
|
||||||
|
ControllerState s;
|
||||||
|
std::memcpy(&s, &recorded_input[current_byte], sizeof(ControllerState));
|
||||||
|
current_byte += sizeof(ControllerState);
|
||||||
|
|
||||||
|
if (s.type != ControllerStateType::ExtraHidResponse) {
|
||||||
|
LOG_ERROR(Movie,
|
||||||
|
"Expected to read type %d, but found %d. Your playback will be out of sync",
|
||||||
|
ControllerStateType::ExtraHidResponse, s.type);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
extra_hid_response.buttons.battery_level.Assign(s.extra_hid_response.battery_level);
|
||||||
|
extra_hid_response.c_stick.c_stick_x.Assign(s.extra_hid_response.c_stick_x);
|
||||||
|
extra_hid_response.c_stick.c_stick_y.Assign(s.extra_hid_response.c_stick_y);
|
||||||
|
extra_hid_response.buttons.r_not_held.Assign(s.extra_hid_response.r_not_held);
|
||||||
|
extra_hid_response.buttons.zl_not_held.Assign(s.extra_hid_response.zl_not_held);
|
||||||
|
extra_hid_response.buttons.zr_not_held.Assign(s.extra_hid_response.zr_not_held);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Record(const ControllerState& controller_state) {
|
||||||
|
recorded_input.resize(current_byte + sizeof(ControllerState));
|
||||||
|
std::memcpy(&recorded_input[current_byte], &controller_state, sizeof(ControllerState));
|
||||||
|
current_byte += sizeof(ControllerState);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Record(const Service::HID::PadState& pad_state, const s16& circle_pad_x,
|
||||||
|
const s16& circle_pad_y) {
|
||||||
|
ControllerState s;
|
||||||
|
s.type = ControllerStateType::PadAndCircle;
|
||||||
|
|
||||||
|
s.pad_and_circle.a.Assign(static_cast<u16>(pad_state.a));
|
||||||
|
s.pad_and_circle.b.Assign(static_cast<u16>(pad_state.b));
|
||||||
|
s.pad_and_circle.select.Assign(static_cast<u16>(pad_state.select));
|
||||||
|
s.pad_and_circle.start.Assign(static_cast<u16>(pad_state.start));
|
||||||
|
s.pad_and_circle.right.Assign(static_cast<u16>(pad_state.right));
|
||||||
|
s.pad_and_circle.left.Assign(static_cast<u16>(pad_state.left));
|
||||||
|
s.pad_and_circle.up.Assign(static_cast<u16>(pad_state.up));
|
||||||
|
s.pad_and_circle.down.Assign(static_cast<u16>(pad_state.down));
|
||||||
|
s.pad_and_circle.r.Assign(static_cast<u16>(pad_state.r));
|
||||||
|
s.pad_and_circle.l.Assign(static_cast<u16>(pad_state.l));
|
||||||
|
s.pad_and_circle.x.Assign(static_cast<u16>(pad_state.x));
|
||||||
|
s.pad_and_circle.y.Assign(static_cast<u16>(pad_state.y));
|
||||||
|
|
||||||
|
s.pad_and_circle.circle_pad_x = circle_pad_x;
|
||||||
|
s.pad_and_circle.circle_pad_y = circle_pad_y;
|
||||||
|
|
||||||
|
Record(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Record(const Service::HID::TouchDataEntry& touch_data) {
|
||||||
|
ControllerState s;
|
||||||
|
s.type = ControllerStateType::Touch;
|
||||||
|
|
||||||
|
s.touch.x = touch_data.x;
|
||||||
|
s.touch.y = touch_data.y;
|
||||||
|
s.touch.valid = static_cast<u8>(touch_data.valid);
|
||||||
|
|
||||||
|
Record(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Record(const Service::HID::AccelerometerDataEntry& accelerometer_data) {
|
||||||
|
ControllerState s;
|
||||||
|
s.type = ControllerStateType::Accelerometer;
|
||||||
|
|
||||||
|
s.accelerometer.x = accelerometer_data.x;
|
||||||
|
s.accelerometer.y = accelerometer_data.y;
|
||||||
|
s.accelerometer.z = accelerometer_data.z;
|
||||||
|
|
||||||
|
Record(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Record(const Service::HID::GyroscopeDataEntry& gyroscope_data) {
|
||||||
|
ControllerState s;
|
||||||
|
s.type = ControllerStateType::Gyroscope;
|
||||||
|
|
||||||
|
s.gyroscope.x = gyroscope_data.x;
|
||||||
|
s.gyroscope.y = gyroscope_data.y;
|
||||||
|
s.gyroscope.z = gyroscope_data.z;
|
||||||
|
|
||||||
|
Record(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Record(const Service::IR::PadState& pad_state, const s16& c_stick_x,
|
||||||
|
const s16& c_stick_y) {
|
||||||
|
ControllerState s;
|
||||||
|
s.type = ControllerStateType::IrRst;
|
||||||
|
|
||||||
|
s.ir_rst.x = c_stick_x;
|
||||||
|
s.ir_rst.y = c_stick_y;
|
||||||
|
s.ir_rst.zl = static_cast<u8>(pad_state.zl);
|
||||||
|
s.ir_rst.zr = static_cast<u8>(pad_state.zr);
|
||||||
|
|
||||||
|
Record(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Record(const Service::IR::ExtraHIDResponse& extra_hid_response) {
|
||||||
|
ControllerState s;
|
||||||
|
s.type = ControllerStateType::ExtraHidResponse;
|
||||||
|
|
||||||
|
s.extra_hid_response.battery_level.Assign(extra_hid_response.buttons.battery_level);
|
||||||
|
s.extra_hid_response.c_stick_x.Assign(extra_hid_response.c_stick.c_stick_x);
|
||||||
|
s.extra_hid_response.c_stick_y.Assign(extra_hid_response.c_stick.c_stick_y);
|
||||||
|
s.extra_hid_response.r_not_held.Assign(extra_hid_response.buttons.r_not_held);
|
||||||
|
s.extra_hid_response.zl_not_held.Assign(extra_hid_response.buttons.zl_not_held);
|
||||||
|
s.extra_hid_response.zr_not_held.Assign(extra_hid_response.buttons.zr_not_held);
|
||||||
|
|
||||||
|
Record(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Movie::ValidateHeader(const CTMHeader& header) {
|
||||||
|
if (header_magic_bytes != header.filetype) {
|
||||||
|
LOG_ERROR(Movie, "Playback file does not have valid header");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::SaveMovie() {
|
||||||
|
LOG_INFO(Movie, "Saving movie");
|
||||||
|
FileUtil::IOFile save_record(Settings::values.movie_record, "wb");
|
||||||
|
|
||||||
|
if (!save_record.IsGood()) {
|
||||||
|
LOG_ERROR(Movie, "Unable to open file to save movie");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CTMHeader header = {};
|
||||||
|
header.filetype = header_magic_bytes;
|
||||||
|
|
||||||
|
Core::System::GetInstance().GetAppLoader().ReadProgramId(header.program_id);
|
||||||
|
|
||||||
|
std::string rev_bytes;
|
||||||
|
CryptoPP::StringSource(Common::g_scm_rev, true,
|
||||||
|
new CryptoPP::HexDecoder(new CryptoPP::StringSink(rev_bytes)));
|
||||||
|
std::memcpy(header.revision.data(), rev_bytes.data(), sizeof(CTMHeader::revision));
|
||||||
|
|
||||||
|
save_record.WriteBytes(&header, sizeof(CTMHeader));
|
||||||
|
save_record.WriteBytes(recorded_input.data(), recorded_input.size());
|
||||||
|
|
||||||
|
if (!save_record.IsGood()) {
|
||||||
|
LOG_ERROR(Movie, "Error saving movie");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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 '%s'",
|
||||||
|
Settings::values.movie_play.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Settings::values.movie_record.empty()) {
|
||||||
|
LOG_INFO(Movie, "Enabling Movie recording");
|
||||||
|
play_mode = PlayMode::Recording;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::Shutdown() {
|
||||||
|
if (IsRecordingInput()) {
|
||||||
|
SaveMovie();
|
||||||
|
}
|
||||||
|
|
||||||
|
play_mode = PlayMode::None;
|
||||||
|
recorded_input.resize(0);
|
||||||
|
current_byte = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename... Targs>
|
||||||
|
void Movie::Handle(Targs&... Fargs) {
|
||||||
|
if (IsPlayingInput()) {
|
||||||
|
ASSERT(current_byte + sizeof(ControllerState) <= recorded_input.size());
|
||||||
|
Play(Fargs...);
|
||||||
|
CheckInputEnd();
|
||||||
|
} else if (IsRecordingInput()) {
|
||||||
|
Record(Fargs...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::HandlePadAndCircleStatus(Service::HID::PadState& pad_state, s16& circle_pad_x,
|
||||||
|
s16& circle_pad_y) {
|
||||||
|
Handle(pad_state, circle_pad_x, circle_pad_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::HandleTouchStatus(Service::HID::TouchDataEntry& touch_data) {
|
||||||
|
Handle(touch_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::HandleAccelerometerStatus(Service::HID::AccelerometerDataEntry& accelerometer_data) {
|
||||||
|
Handle(accelerometer_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::HandleGyroscopeStatus(Service::HID::GyroscopeDataEntry& gyroscope_data) {
|
||||||
|
Handle(gyroscope_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::HandleIrRst(Service::IR::PadState& pad_state, s16& c_stick_x, s16& c_stick_y) {
|
||||||
|
Handle(pad_state, c_stick_x, c_stick_y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Movie::HandleExtraHidResponse(Service::IR::ExtraHIDResponse& extra_hid_response) {
|
||||||
|
Handle(extra_hid_response);
|
||||||
|
}
|
||||||
|
}; // namespace Core
|
114
src/core/movie.h
Normal file
114
src/core/movie.h
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
// Copyright 2017 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Service {
|
||||||
|
namespace HID {
|
||||||
|
struct AccelerometerDataEntry;
|
||||||
|
struct GyroscopeDataEntry;
|
||||||
|
struct PadState;
|
||||||
|
struct TouchDataEntry;
|
||||||
|
}
|
||||||
|
namespace IR {
|
||||||
|
struct ExtraHIDResponse;
|
||||||
|
union PadState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
struct CTMHeader;
|
||||||
|
struct ControllerState;
|
||||||
|
enum class PlayMode;
|
||||||
|
|
||||||
|
class Movie {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Gets the instance of the Movie singleton class.
|
||||||
|
* @returns Reference to the instance of the Movie singleton class.
|
||||||
|
*/
|
||||||
|
static Movie& GetInstance() {
|
||||||
|
return s_instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Init();
|
||||||
|
|
||||||
|
void Shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When recording: Takes a copy of the given input states so they can be used for playback
|
||||||
|
* When playing: Replaces the given input states with the ones stored in the playback file
|
||||||
|
*/
|
||||||
|
void HandlePadAndCircleStatus(Service::HID::PadState& pad_state, s16& circle_pad_x,
|
||||||
|
s16& circle_pad_y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When recording: Takes a copy of the given input states so they can be used for playback
|
||||||
|
* When playing: Replaces the given input states with the ones stored in the playback file
|
||||||
|
*/
|
||||||
|
void HandleTouchStatus(Service::HID::TouchDataEntry& touch_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When recording: Takes a copy of the given input states so they can be used for playback
|
||||||
|
* When playing: Replaces the given input states with the ones stored in the playback file
|
||||||
|
*/
|
||||||
|
void HandleAccelerometerStatus(Service::HID::AccelerometerDataEntry& accelerometer_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When recording: Takes a copy of the given input states so they can be used for playback
|
||||||
|
* When playing: Replaces the given input states with the ones stored in the playback file
|
||||||
|
*/
|
||||||
|
void HandleGyroscopeStatus(Service::HID::GyroscopeDataEntry& gyroscope_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When recording: Takes a copy of the given input states so they can be used for playback
|
||||||
|
* When playing: Replaces the given input states with the ones stored in the playback file
|
||||||
|
*/
|
||||||
|
void HandleIrRst(Service::IR::PadState& pad_state, s16& c_stick_x, s16& c_stick_y);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When recording: Takes a copy of the given input states so they can be used for playback
|
||||||
|
* When playing: Replaces the given input states with the ones stored in the playback file
|
||||||
|
*/
|
||||||
|
void HandleExtraHidResponse(Service::IR::ExtraHIDResponse& extra_hid_response);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Movie s_instance;
|
||||||
|
|
||||||
|
bool IsPlayingInput();
|
||||||
|
|
||||||
|
bool IsRecordingInput();
|
||||||
|
|
||||||
|
void CheckInputEnd();
|
||||||
|
|
||||||
|
template <typename... Targs>
|
||||||
|
void Handle(Targs&... Fargs);
|
||||||
|
|
||||||
|
void Play(Service::HID::PadState& pad_state, s16& circle_pad_x, s16& circle_pad_y);
|
||||||
|
void Play(Service::HID::TouchDataEntry& touch_data);
|
||||||
|
void Play(Service::HID::AccelerometerDataEntry& accelerometer_data);
|
||||||
|
void Play(Service::HID::GyroscopeDataEntry& gyroscope_data);
|
||||||
|
void Play(Service::IR::PadState& pad_state, s16& c_stick_x, s16& c_stick_y);
|
||||||
|
void Play(Service::IR::ExtraHIDResponse& extra_hid_response);
|
||||||
|
|
||||||
|
void Record(const ControllerState& controller_state);
|
||||||
|
void Record(const Service::HID::PadState& pad_state, const s16& circle_pad_x,
|
||||||
|
const s16& circle_pad_y);
|
||||||
|
void Record(const Service::HID::TouchDataEntry& touch_data);
|
||||||
|
void Record(const Service::HID::AccelerometerDataEntry& accelerometer_data);
|
||||||
|
void Record(const Service::HID::GyroscopeDataEntry& gyroscope_data);
|
||||||
|
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);
|
||||||
|
|
||||||
|
void SaveMovie();
|
||||||
|
|
||||||
|
PlayMode play_mode;
|
||||||
|
std::vector<u8> recorded_input;
|
||||||
|
size_t current_byte = 0;
|
||||||
|
};
|
||||||
|
} // namespace Core
|
@ -130,6 +130,10 @@ struct Values {
|
|||||||
bool use_gdbstub;
|
bool use_gdbstub;
|
||||||
u16 gdbstub_port;
|
u16 gdbstub_port;
|
||||||
|
|
||||||
|
// 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 New Issue
Block a user