mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Movie (recorded inputs) playback and recording. SDL has command lines to control it.
This commit is contained in:
		
							parent
							
								
									6e2a4ba665
								
							
						
					
					
						commit
						04541150b1
					
				
					 13 changed files with 625 additions and 41 deletions
				
			
		|  | @ -388,6 +388,8 @@ add_library(core STATIC | |||
|     memory.h | ||||
|     memory_setup.h | ||||
|     mmio.h | ||||
|     movie.cpp | ||||
|     movie.h | ||||
|     perf_stats.cpp | ||||
|     perf_stats.h | ||||
|     settings.cpp | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ | |||
| #include "core/hw/hw.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/memory_setup.h" | ||||
| #include "core/movie.h" | ||||
| #include "core/settings.h" | ||||
| #include "network/network.h" | ||||
| #include "video_core/video_core.h" | ||||
|  | @ -160,6 +161,7 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | |||
|     Service::Init(); | ||||
|     AudioCore::Init(); | ||||
|     GDBStub::Init(); | ||||
|     Movie::Init(); | ||||
| 
 | ||||
|     if (!VideoCore::Init(emu_window)) { | ||||
|         return ResultStatus::ErrorVideoCore; | ||||
|  | @ -185,6 +187,7 @@ void System::Shutdown() { | |||
|                          perf_results.frametime * 1000.0); | ||||
| 
 | ||||
|     // Shutdown emulation session
 | ||||
|     Movie::Shutdown(); | ||||
|     GDBStub::Shutdown(); | ||||
|     AudioCore::Shutdown(); | ||||
|     VideoCore::Shutdown(); | ||||
|  |  | |||
|  | @ -19,6 +19,8 @@ | |||
| #include "core/hle/service/hid/hid_spvr.h" | ||||
| #include "core/hle/service/hid/hid_user.h" | ||||
| #include "core/hle/service/service.h" | ||||
| #include "core/movie.h" | ||||
| #include "video_core/video_core.h" | ||||
| 
 | ||||
| namespace Service { | ||||
| 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
 | ||||
|     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); | ||||
| 
 | ||||
|     Movie::HandlePadAndCircleStatus(state, circle_pad_x, circle_pad_y); | ||||
| 
 | ||||
|     const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); | ||||
|     state.circle_up.Assign(direction.up); | ||||
|     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.valid.Assign(pressed ? 1 : 0); | ||||
| 
 | ||||
|     Movie::HandleTouchStatus(touch_entry); | ||||
| 
 | ||||
|     // 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
 | ||||
|     // 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.z = static_cast<s16>(accel.z); | ||||
| 
 | ||||
|     Movie::HandleAccelerometerStatus(accelerometer_entry); | ||||
| 
 | ||||
|     // Make up "raw" entry
 | ||||
|     // TODO(wwylele):
 | ||||
|     // 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.z = static_cast<s16>(gyro.z); | ||||
| 
 | ||||
|     Movie::HandleGyroscopeStatus(gyroscope_entry); | ||||
| 
 | ||||
|     // Make up "raw" entry
 | ||||
|     mem->gyroscope.raw_entry.x = gyroscope_entry.x; | ||||
|     mem->gyroscope.raw_entry.z = -gyroscope_entry.y; | ||||
|  |  | |||
|  | @ -3,10 +3,10 @@ | |||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/alignment.h" | ||||
| #include "common/bit_field.h" | ||||
| #include "common/string_util.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/service/ir/extra_hid.h" | ||||
| #include "core/movie.h" | ||||
| #include "core/settings.h" | ||||
| 
 | ||||
| namespace Service { | ||||
|  | @ -176,22 +176,6 @@ void ExtraHID::SendHIDStatus() { | |||
|     if (is_device_reload_pending.exchange(false)) | ||||
|         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; | ||||
|     // 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.
 | ||||
|  | @ -200,6 +184,7 @@ void ExtraHID::SendHIDStatus() { | |||
|     float x, y; | ||||
|     std::tie(x, y) = c_stick->GetStatus(); | ||||
| 
 | ||||
|     ExtraHIDResponse response; | ||||
|     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_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.unknown = 0; | ||||
| 
 | ||||
|     Movie::HandleExtraHidResponse(response); | ||||
| 
 | ||||
|     std::vector<u8> response_buffer(sizeof(response)); | ||||
|     memcpy(response_buffer.data(), &response, sizeof(response)); | ||||
|     Send(response_buffer); | ||||
|  |  | |||
|  | @ -6,6 +6,8 @@ | |||
| 
 | ||||
| #include <array> | ||||
| #include <atomic> | ||||
| #include "common/bit_field.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/frontend/input.h" | ||||
| #include "core/hle/service/ir/ir_user.h" | ||||
| 
 | ||||
|  | @ -16,6 +18,22 @@ struct EventType; | |||
| namespace Service { | ||||
| 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. | ||||
|  * 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
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include "common/bit_field.h" | ||||
| #include "core/core_timing.h" | ||||
| #include "core/hle/ipc_helpers.h" | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/kernel/shared_memory.h" | ||||
| #include "core/hle/service/hid/hid.h" | ||||
| #include "core/hle/service/ir/ir_rst.h" | ||||
| #include "core/movie.h" | ||||
| #include "core/settings.h" | ||||
| 
 | ||||
| namespace Service { | ||||
| 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 { | ||||
|     PadState current_state; | ||||
|     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; | ||||
|     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
 | ||||
|     const 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_x = static_cast<s16>(c_stick_x_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) { | ||||
|         const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); | ||||
|  |  | |||
|  | @ -6,6 +6,9 @@ | |||
| 
 | ||||
| #include <atomic> | ||||
| #include <memory> | ||||
| #include "common/bit_field.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/frontend/input.h" | ||||
| #include "core/hle/kernel/kernel.h" | ||||
| #include "core/hle/service/service.h" | ||||
|  | @ -22,6 +25,18 @@ class EventType; | |||
| namespace Service { | ||||
| 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
 | ||||
| class IR_RST final : public ServiceFramework<IR_RST> { | ||||
| 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; | ||||
|     u16 gdbstub_port; | ||||
| 
 | ||||
|     // Movie
 | ||||
|     std::string movie_play; | ||||
|     std::string movie_record; | ||||
| 
 | ||||
|     // WebService
 | ||||
|     bool enable_telemetry; | ||||
|     std::string telemetry_endpoint_url; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue