Movie (recorded inputs) playback and recording. SDL has command lines to control it.
This commit is contained in:
		| @@ -43,11 +43,14 @@ | |||||||
| #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 | ||||||
|  |               << " [options] <filename>\n" | ||||||
|                  "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" |                  "-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n" | ||||||
|                  "-i, --install=FILE    Installs a specified CIA file\n" |                  "-i, --install=FILE    Installs a specified CIA file\n" | ||||||
|                  "-m, --multiplayer=nick:password@address:port" |                  "-m, --multiplayer=nick:password@address:port" | ||||||
|                  " Nickname, password, address and port for multiplayer\n" |                  " Nickname, password, address and port for multiplayer\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" |                  "-h, --help           Display this help and exit\n" | ||||||
|                  "-v, --version        Output version information and exit\n"; |                  "-v, --version        Output version information and exit\n"; | ||||||
| } | } | ||||||
| @@ -109,6 +112,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; | ||||||
| @@ -129,12 +135,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': | ||||||
| @@ -194,6 +201,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; | ||||||
| @@ -226,11 +239,17 @@ 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"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     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 | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -388,6 +388,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 | ||||||
|   | |||||||
| @@ -19,6 +19,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" | ||||||
| @@ -160,6 +161,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | |||||||
|     Service::Init(); |     Service::Init(); | ||||||
|     AudioCore::Init(); |     AudioCore::Init(); | ||||||
|     GDBStub::Init(); |     GDBStub::Init(); | ||||||
|  |     Movie::Init(); | ||||||
|  |  | ||||||
|     if (!VideoCore::Init(emu_window)) { |     if (!VideoCore::Init(emu_window)) { | ||||||
|         return ResultStatus::ErrorVideoCore; |         return ResultStatus::ErrorVideoCore; | ||||||
| @@ -185,6 +187,7 @@ void System::Shutdown() { | |||||||
|                          perf_results.frametime * 1000.0); |                          perf_results.frametime * 1000.0); | ||||||
|  |  | ||||||
|     // Shutdown emulation session |     // Shutdown emulation session | ||||||
|  |     Movie::Shutdown(); | ||||||
|     GDBStub::Shutdown(); |     GDBStub::Shutdown(); | ||||||
|     AudioCore::Shutdown(); |     AudioCore::Shutdown(); | ||||||
|     VideoCore::Shutdown(); |     VideoCore::Shutdown(); | ||||||
|   | |||||||
| @@ -19,6 +19,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 { | ||||||
| @@ -135,6 +137,9 @@ static void 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); | ||||||
|  |  | ||||||
|  |     Movie::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); | ||||||
| @@ -180,6 +185,8 @@ static void 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); | ||||||
|  |  | ||||||
|  |     Movie::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). | ||||||
| @@ -218,6 +225,8 @@ static void 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); | ||||||
|  |  | ||||||
|  |     Movie::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 | ||||||
| @@ -256,6 +265,8 @@ static void 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); | ||||||
|  |  | ||||||
|  |     Movie::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; | ||||||
|  |  | ||||||
|  |     Movie::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); | ||||||
|  |  | ||||||
|  |     Movie::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: | ||||||
|   | |||||||
							
								
								
									
										469
									
								
								src/core/movie.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										469
									
								
								src/core/movie.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,469 @@ | |||||||
|  | // 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 Movie { | ||||||
|  |  | ||||||
|  | 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) | ||||||
|  |  | ||||||
|  | static PlayMode play_mode = PlayMode::None; | ||||||
|  | static std::vector<u8> recorded_input; | ||||||
|  | static size_t current_byte = 0; | ||||||
|  |  | ||||||
|  | static bool IsPlayingInput() { | ||||||
|  |     return play_mode == PlayMode::Playing; | ||||||
|  | } | ||||||
|  | static bool IsRecordingInput() { | ||||||
|  |     return play_mode == PlayMode::Recording; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void CheckInputEnd() { | ||||||
|  |     if (current_byte + sizeof(ControllerState) > recorded_input.size()) { | ||||||
|  |         LOG_INFO(Movie, "Playback finished"); | ||||||
|  |         play_mode = PlayMode::None; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static bool 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; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void 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 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 Shutdown() { | ||||||
|  |     if (!IsRecordingInput()) { | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     SaveMovie(); | ||||||
|  |  | ||||||
|  |     play_mode = PlayMode::None; | ||||||
|  |     recorded_input.resize(0); | ||||||
|  |     current_byte = 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template <typename... Targs> | ||||||
|  | static void Handle(Targs&... Fargs) { | ||||||
|  |     if (IsPlayingInput()) { | ||||||
|  |         Play(Fargs...); | ||||||
|  |         CheckInputEnd(); | ||||||
|  |     } else if (IsRecordingInput()) { | ||||||
|  |         Record(Fargs...); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HandlePadAndCircleStatus(Service::HID::PadState& pad_state, s16& circle_pad_x, | ||||||
|  |                               s16& circle_pad_y) { | ||||||
|  |     Handle(pad_state, circle_pad_x, circle_pad_y); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HandleTouchStatus(Service::HID::TouchDataEntry& touch_data) { | ||||||
|  |     Handle(touch_data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HandleAccelerometerStatus(Service::HID::AccelerometerDataEntry& accelerometer_data) { | ||||||
|  |     Handle(accelerometer_data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HandleGyroscopeStatus(Service::HID::GyroscopeDataEntry& gyroscope_data) { | ||||||
|  |     Handle(gyroscope_data); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HandleIrRst(Service::IR::PadState& pad_state, s16& c_stick_x, s16& c_stick_y) { | ||||||
|  |     Handle(pad_state, c_stick_x, c_stick_y); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void HandleExtraHidResponse(Service::IR::ExtraHIDResponse& extra_hid_response) { | ||||||
|  |     Handle(extra_hid_response); | ||||||
|  | } | ||||||
|  | } | ||||||
							
								
								
									
										64
									
								
								src/core/movie.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/core/movie.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | // 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 Movie { | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  | } | ||||||
| @@ -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; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user