diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index cb0e1dfb3e..eb043ed2bb 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -189,10 +189,12 @@ public: struct gem_color { - float r, g, b; + ENABLE_BITWISE_SERIALIZATION; + + f32 r, g, b; gem_color() : r(0.0f), g(0.0f), b(0.0f) {} - gem_color(float r_, float g_, float b_) + gem_color(f32 r_, f32 g_, f32 b_) { r = std::clamp(r_, 0.0f, 1.0f); g = std::clamp(g_, 0.0f, 1.0f); @@ -238,17 +240,16 @@ public: bool is_calibrating{false}; // Whether or not we are currently calibrating u64 calibration_start_us{0}; // The start timestamp of the calibration in microseconds + u64 calibration_status_flags = 0; // The calibration status flags static constexpr u64 calibration_time_us = 500000; // The calibration supposedly takes 0.5 seconds (500000 microseconds) - - ENABLE_BITWISE_SERIALIZATION; }; CellGemAttribute attribute = {}; CellGemVideoConvertAttribute vc_attribute = {}; s32 video_data_out_size = -1; std::vector video_data_in; - u64 status_flags = 0; + u64 runtime_status_flags = 0; // The runtime status flags bool enable_pitch_correction = false; u32 inertial_counter = 0; @@ -279,11 +280,9 @@ public: { gem.is_calibrating = false; gem.calibration_start_us = 0; + gem.calibration_status_flags = CELL_GEM_FLAG_CALIBRATION_SUCCEEDED | CELL_GEM_FLAG_CALIBRATION_OCCURRED; gem.calibrated_magnetometer = true; gem.enabled_tracking = true; - gem.hue = 1; - - status_flags = CELL_GEM_FLAG_CALIBRATION_SUCCEEDED | CELL_GEM_FLAG_CALIBRATION_OCCURRED; } } @@ -389,20 +388,11 @@ public: } } + void paint_spheres(CellGemVideoConvertFormatEnum output_format, u32 width, u32 height, u8* video_data_out, u32 video_data_out_size); + gem_config_data() { - if (!g_cfg_gem_real.load()) - { - cellGem.notice("Could not load real gem config. Using defaults."); - } - - if (!g_cfg_gem_fake.load()) - { - cellGem.notice("Could not load fake gem config. Using defaults."); - } - - cellGem.notice("Real gem config=\n", g_cfg_gem_real.to_string()); - cellGem.notice("Fake gem config=\n", g_cfg_gem_fake.to_string()); + load_configs(); }; SAVESTATE_INIT_POS(15); @@ -416,10 +406,36 @@ public: return; } - [[maybe_unused]] const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellGem); + const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellGem); - ar(attribute, vc_attribute, status_flags, enable_pitch_correction, inertial_counter, controllers - , connected_controllers, updating, camera_frame, memory_ptr, start_timestamp_us); + ar(attribute, vc_attribute, runtime_status_flags, enable_pitch_correction, inertial_counter); + + for (gem_controller& c : controllers) + { + ar(c.status, c.ext_status, c.ext_id, c.port, c.enabled_magnetometer, c.calibrated_magnetometer, c.enabled_filtering, c.enabled_tracking, c.enabled_LED, c.hue_set, c.rumble); + + // We need to add padding because we used bitwise serialization in version 1 + if (version < 2) + { + ar.add_padding(&gem_controller::rumble, &gem_controller::sphere_rgb); + } + + ar(c.sphere_rgb, c.hue, c.distance_mm, c.radius, c.radius_valid, c.is_calibrating); + + if (version < 2) + { + ar.add_padding(&gem_controller::is_calibrating, &gem_controller::calibration_start_us); + } + + ar(c.calibration_start_us); + + if (ar.is_writing() || version >= 2) + { + ar(c.calibration_status_flags); + } + } + + ar(connected_controllers, updating, camera_frame, memory_ptr, start_timestamp_us); } gem_config_data(utils::serial& ar) @@ -429,6 +445,11 @@ public: if (ar.is_writing()) return; + load_configs(); + } + + static void load_configs() + { if (!g_cfg_gem_real.load()) { cellGem.notice("Could not load real gem config. Using defaults."); @@ -447,7 +468,7 @@ public: extern std::pair get_video_resolution(const CellCameraInfoEx& info); extern u32 get_buffer_size_by_format(s32 format, s32 width, s32 height); -static inline int32_t cellGemGetVideoConvertSize(s32 output_format) +static inline s32 cellGemGetVideoConvertSize(s32 output_format) { switch (output_format) { @@ -474,6 +495,29 @@ static inline int32_t cellGemGetVideoConvertSize(s32 output_format) namespace gem { + struct gem_position + { + public: + void set_position(f32 x, f32 y) + { + std::lock_guard lock(m_mutex); + m_x = x; + m_y = y; + } + void get_position(f32& x, f32& y) + { + std::lock_guard lock(m_mutex); + x = m_x; + y = m_y; + } + private: + std::mutex m_mutex; + f32 m_x = 0.0f; + f32 m_y = 0.0f; + }; + + std::array positions {}; + bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format, const std::vector& video_data_in, u32 width, u32 height, u8* video_data_out, u32 video_data_out_size) @@ -524,15 +568,15 @@ namespace gem u8* dst0 = dst_row; u8* dst1 = dst_row + out_pitch; - for (uint32_t x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8) + for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8) { - const uint8_t b = src0[0]; - const uint8_t g0 = src0[1]; - const uint8_t g1 = src1[0]; - const uint8_t r = src1[1]; + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; - const uint8_t top[4] = { r, g0, b, 255 }; - const uint8_t bottom[4] = { r, g1, b, 255 }; + const u8 top[4] = { r, g0, b, 255 }; + const u8 bottom[4] = { r, g1, b, 255 }; // Top-Left std::memcpy(dst0, top, 4); @@ -602,6 +646,101 @@ namespace gem } } +void gem_config_data::paint_spheres(CellGemVideoConvertFormatEnum output_format, u32 width, u32 height, u8* video_data_out, u32 video_data_out_size) +{ + if (!width || !height || !video_data_out || !video_data_out_size) + { + return; + } + + struct sphere_information + { + f32 radius = 0.0f; + s16 x = 0; + s16 y = 0; + u8 r = 0; + u8 g = 0; + u8 b = 0; + }; + + std::vector sphere_info; + { + reader_lock lock(mtx); + + for (u32 gem_num = 0; gem_num < CELL_GEM_MAX_NUM; gem_num++) + { + const gem_config_data::gem_controller& controller = controllers[gem_num]; + if (!controller.radius_valid || controller.radius <= 0.0f) continue; + + f32 x, y; + ::at32(gem::positions, gem_num).get_position(x, y); + + const u8 r = static_cast(std::clamp(controller.sphere_rgb.r * 255.0f, 0.0f, 255.0f)); + const u8 g = static_cast(std::clamp(controller.sphere_rgb.g * 255.0f, 0.0f, 255.0f)); + const u8 b = static_cast(std::clamp(controller.sphere_rgb.b * 255.0f, 0.0f, 255.0f)); + + sphere_info.push_back({ controller.radius, static_cast(x), static_cast(y), r, g, b }); + } + } + + switch (output_format) + { + case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required + { + cellGem.trace("Painting spheres for CELL_GEM_RGBA_640x480"); + + const u32 out_pitch = width * 4; + + for (const sphere_information& info : sphere_info) + { + const s32 x_begin = std::max(0, static_cast(std::floor(info.x - info.radius))); + const s32 x_end = std::min(width, static_cast(std::ceil(info.x + info.radius))); + const s32 y_begin = std::max(0, static_cast(std::floor(info.y - info.radius))); + const s32 y_end = std::min(height, static_cast(std::ceil(info.y + info.radius))); + + for (s32 y = y_begin; y < y_end; y++) + { + u8* dst = video_data_out + y * out_pitch + x_begin * 4; + + for (s32 x = x_begin; x < x_end; x++, dst += 4) + { + const f32 distance = static_cast(std::sqrt(std::pow(info.x - x, 2) + std::pow(info.y - y, 2))); + if (distance > info.radius) continue; + + dst[0] = info.r; + dst[1] = info.g; + dst[2] = info.b; + dst[3] = 255; + } + } + } + + break; + } + case CELL_GEM_BAYER_RESTORED: // Bayer pattern output, 640x480, gamma and white balance applied, output buffer required + case CELL_GEM_RGBA_320x240: // RGBA output; 320*240*4-byte output buffer required + case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous) + case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous) + case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous) + case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B + case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters + { + cellGem.trace("Unimplemented: painting spheres for %s", output_format); + break; + } + case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output + { + cellGem.trace("Ignoring painting spheres for CELL_GEM_NO_VIDEO_OUTPUT"); + break; + } + default: + { + cellGem.trace("Ignoring painting spheres for %d", static_cast(output_format)); + break; + } + } +} + void gem_config_data::operator()() { cellGem.notice("Starting thread"); @@ -635,6 +774,11 @@ void gem_config_data::operator()() if (gem::convert_image_format(shared_data.format, vc.output_format, video_data_in, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size)) { cellGem.trace("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get()); + + if (g_cfg.io.paint_move_spheres) + { + paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size); + } } video_conversion_in_progress = false; @@ -710,7 +854,7 @@ public: { if (g_cfg.io.move != move_handler::real) { - return 1; // potentially true if less than 20 pixels have the hue + return true; // potentially true if less than 20 pixels have the hue } return hue < m_hues.size() && m_hues[hue] < 20; // potentially true if less than 20 pixels have the hue @@ -763,7 +907,7 @@ public: std::lock_guard lock(pad::g_pad_mutex); const auto handler = pad::get_current_handler(); auto& handlers = handler->get_handlers(); - if (auto it = handlers.find(pad_handler::move); it != handlers.end()) + if (auto it = handlers.find(pad_handler::move); it != handlers.end() && it->second) { for (auto& binding : it->second->bindings()) { @@ -774,12 +918,22 @@ public: if (gem_num < 0 || gem_num >= CELL_GEM_MAX_NUM) continue; - const cfg_ps_move* config = ::at32(g_cfg_move.move, gem_num); - binding.device->color_override_active = true; - binding.device->color_override.r = config->r.get(); - binding.device->color_override.g = config->g.get(); - binding.device->color_override.b = config->b.get(); + + if (g_cfg.io.allow_move_hue_set_by_game) + { + const auto& controller = gem.controllers[gem_num]; + binding.device->color_override.r = static_cast(std::clamp(controller.sphere_rgb.r * 255.0f, 0.0f, 255.0f)); + binding.device->color_override.g = static_cast(std::clamp(controller.sphere_rgb.g * 255.0f, 0.0f, 255.0f)); + binding.device->color_override.b = static_cast(std::clamp(controller.sphere_rgb.b * 255.0f, 0.0f, 255.0f)); + } + else + { + const cfg_ps_move* config = ::at32(g_cfg_move.move, gem_num); + binding.device->color_override.r = config->r.get(); + binding.device->color_override.g = config->g.get(); + binding.device->color_override.b = config->b.get(); + } } } } @@ -791,7 +945,7 @@ public: const cfg_ps_move* config = g_cfg_move.move[gem_num]; m_tracker.set_active(gem_num, controller.enabled_tracking && controller.status == CELL_GEM_STATUS_READY); - m_tracker.set_hue(gem_num, config->hue); + m_tracker.set_hue(gem_num, g_cfg.io.allow_move_hue_set_by_game ? controller.hue : config->hue); m_tracker.set_hue_threshold(gem_num, config->hue_threshold); m_tracker.set_saturation_threshold(gem_num, config->saturation_threshold); } @@ -902,6 +1056,11 @@ static inline void pos_to_gem_image_state(u32 gem_num, const gem_config::gem_con { draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max); } + + if (g_cfg.io.paint_move_spheres) + { + ::at32(gem::positions, gem_num).set_position(image_x, image_y); + } } static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, const ps_move_data& move_data) @@ -983,6 +1142,11 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con { draw_overlay_cursor(gem_num, controller, x_pos, y_pos, x_max, y_max); } + + if (g_cfg.io.paint_move_spheres) + { + ::at32(gem::positions, gem_num).set_position(image_x, image_y); + } } extern bool is_input_allowed(); @@ -1460,7 +1624,7 @@ error_code cellGemClearStatusFlags(u32 gem_num, u64 mask) return CELL_GEM_ERROR_INVALID_PARAMETER; } - gem.status_flags &= ~mask; + gem.controllers[gem_num].calibration_status_flags &= ~mask; return CELL_OK; } @@ -1657,7 +1821,7 @@ error_code cellGemFilterState(u32 gem_num, u32 enable) return CELL_OK; } -error_code cellGemForceRGB(u32 gem_num, float r, float g, float b) +error_code cellGemForceRGB(u32 gem_num, f32 r, f32 g, f32 b) { cellGem.todo("cellGemForceRGB(gem_num=%d, r=%f, g=%f, b=%f)", gem_num, r, g, b); @@ -1684,6 +1848,9 @@ error_code cellGemForceRGB(u32 gem_num, float r, float g, float b) gem.controllers[gem_num].sphere_rgb = gem_config::gem_color(r, g, b); gem.controllers[gem_num].enabled_tracking = false; + const auto [h, s, v] = ps_move_tracker::rgb_to_hsv(r, g, b); + gem.controllers[gem_num].hue = h; + return CELL_OK; } @@ -1753,9 +1920,9 @@ error_code cellGemGetCameraState(vm::ptr camera_state) // TODO: use correct camera settings camera_state->exposure = 0; camera_state->exposure_time = 1.0f / 60.0f; - camera_state->gain = 1.0; - camera_state->pitch_angle = 0.0; - camera_state->pitch_angle_estimate = 0.0; + camera_state->gain = 1.0f; + camera_state->pitch_angle = 0.0f; + camera_state->pitch_angle_estimate = 0.0f; return CELL_OK; } @@ -2060,7 +2227,7 @@ error_code cellGemGetMemorySize(s32 max_connect) return not_an_error(GemGetMemorySize(max_connect)); } -error_code cellGemGetRGB(u32 gem_num, vm::ptr r, vm::ptr g, vm::ptr b) +error_code cellGemGetRGB(u32 gem_num, vm::ptr r, vm::ptr g, vm::ptr b) { cellGem.todo("cellGemGetRGB(gem_num=%d, r=*0x%x, g=*0x%x, b=*0x%x)", gem_num, r, g, b); @@ -2246,7 +2413,7 @@ error_code cellGemGetStatusFlags(u32 gem_num, vm::ptr flags) return CELL_GEM_ERROR_INVALID_PARAMETER; } - *flags = gem.status_flags; + *flags = gem.runtime_status_flags | gem.controllers[gem_num].calibration_status_flags; return CELL_OK; } @@ -2375,7 +2542,7 @@ error_code cellGemInit(ppu_thread& ppu, vm::cptr attribute) gem.updating = false; gem.camera_frame = 0; - gem.status_flags = 0; + gem.runtime_status_flags = 0; gem.attribute = *attribute; for (int gem_num = 0; gem_num < CELL_GEM_MAX_NUM; gem_num++) @@ -2412,8 +2579,9 @@ error_code cellGemInvalidateCalibration(s32 gem_num) // TODO: does this really stop an ongoing calibration ? gem.controllers[gem_num].is_calibrating = false; gem.controllers[gem_num].calibration_start_us = 0; - - // TODO: gem.status_flags (probably not changed) + gem.controllers[gem_num].calibration_status_flags = 0; + gem.controllers[gem_num].hue_set = false; + gem.controllers[gem_num].enabled_tracking = false; return CELL_OK; } @@ -2641,6 +2809,25 @@ error_code cellGemSetRumble(u32 gem_num, u8 rumble) gem.controllers[gem_num].rumble = rumble; + // Set actual device rumble + if (g_cfg.io.move == move_handler::real) + { + std::lock_guard pad_lock(pad::g_pad_mutex); + const auto handler = pad::get_current_handler(); + auto& handlers = handler->get_handlers(); + if (auto it = handlers.find(pad_handler::move); it != handlers.end() && it->second) + { + const u32 pad_index = pad_num(gem_num); + for (const auto& binding : it->second->bindings()) + { + if (!binding.device || binding.device->player_id != pad_index) continue; + + handler->SetRumble(pad_index, rumble, rumble > 0); + break; + } + } + } + return CELL_OK; } @@ -2669,7 +2856,7 @@ error_code cellGemSetYaw(u32 gem_num, vm::ptr z_direction) error_code cellGemTrackHues(vm::cptr req_hues, vm::ptr res_hues) { - cellGem.todo("cellGemTrackHues(req_hues=*0x%x, res_hues=*0x%x)", req_hues, res_hues); + cellGem.todo("cellGemTrackHues(req_hues=%s, res_hues=*0x%x)", req_hues ? fmt::format("*0x%x [%d, %d, %d, %d]", req_hues, req_hues[0], req_hues[1], req_hues[2], req_hues[3]) : "*0x0", res_hues); auto& gem = g_fxo->get(); @@ -2693,8 +2880,6 @@ error_code cellGemTrackHues(vm::cptr req_hues, vm::ptr res_hues) gem.controllers[i].enabled_LED = true; gem.controllers[i].hue_set = true; - // TODO: set hue based on tracker data - switch (i) { default: @@ -2712,6 +2897,9 @@ error_code cellGemTrackHues(vm::cptr req_hues, vm::ptr res_hues) break; } + const auto [r, g, b] = ps_move_tracker::hsv_to_rgb(gem.controllers[i].hue, 1.0f, 1.0f); + gem.controllers[i].sphere_rgb = gem_config::gem_color(r / 255.0f, g / 255.0f, b / 255.0f); + if (res_hues) { res_hues[i] = gem.controllers[i].hue; @@ -2740,7 +2928,8 @@ error_code cellGemTrackHues(vm::cptr req_hues, vm::ptr res_hues) gem.controllers[i].hue_set = true; gem.controllers[i].hue = req_hues[i]; - // TODO: set hue of tracker + const auto [r, g, b] = ps_move_tracker::hsv_to_rgb(gem.controllers[i].hue, 1.0f, 1.0f); + gem.controllers[i].sphere_rgb = gem_config::gem_color(r / 255.0f, g / 255.0f, b / 255.0f); if (res_hues) { diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 14d47b2347..cfaf000996 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -3775,7 +3775,7 @@ namespace rsx m_invalidated_memory_range.invalidate(); } - void thread::renderctl(u32 request_code, void* args) + void thread::renderctl(u32 request_code, void* /*args*/) { switch (request_code) { diff --git a/rpcs3/Emu/savestate_utils.cpp b/rpcs3/Emu/savestate_utils.cpp index 89de9767b8..9f1be4016d 100644 --- a/rpcs3/Emu/savestate_utils.cpp +++ b/rpcs3/Emu/savestate_utils.cpp @@ -72,7 +72,7 @@ SERIALIZATION_VER(sceNp, 11) SERIALIZATION_VER(cellVdec, 12, 1) SERIALIZATION_VER(cellAudio, 13, 1) SERIALIZATION_VER(cellCamera, 14, 1) -SERIALIZATION_VER(cellGem, 15, 1) +SERIALIZATION_VER(cellGem, 15, 1, 2/*calibration_status_flags*/) SERIALIZATION_VER(sceNpTrophy, 16, 1) SERIALIZATION_VER(cellMusic, 17, 1) SERIALIZATION_VER(cellVoice, 18, 1) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index b476d62941..3cb3e39851 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -279,6 +279,8 @@ struct cfg_root : cfg::node cfg::uint<0, 100'000> pad_sleep{this, "Pad handler sleep (microseconds)", 1'000, true}; cfg::_bool background_input_enabled{this, "Background input enabled", true, true}; cfg::_bool show_move_cursor{this, "Show move cursor", false, true}; + cfg::_bool paint_move_spheres{this, "Paint move spheres", false, true}; + cfg::_bool allow_move_hue_set_by_game{this, "Allow move hue set by game", false, true}; cfg::_bool lock_overlay_input_to_player_one{this, "Lock overlay input to player one", false, true}; cfg::string midi_devices{this, "Emulated Midi devices", "ßßß@@@ßßß@@@ßßß@@@"}; cfg::_bool load_sdl_mappings{ this, "Load SDL GameController Mappings", true }; diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index 07c379a9ee..80742f0dc8 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -817,11 +817,9 @@ int ps_move_handler::send_output_report(ps_move_device* device) const auto elapsed = now - device->last_output_report_time; // Update LED at an interval or it will be disabled automatically - if (elapsed >= 4000ms) - { - device->new_output_data = true; - } - else + device->new_output_data |= elapsed >= 4000ms; + + if (!device->new_output_data) { // Use LED update rate of 120ms if (elapsed < 120ms) @@ -858,6 +856,7 @@ void ps_move_handler::apply_pad_data(const pad_ensemble& binding) const u8 speed_large = config->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0; + dev->new_output_data |= dev->large_motor != speed_large; dev->large_motor = speed_large; if (send_output_report(dev) >= 0) diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 486720c66a..12886b8664 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -2347,7 +2347,7 @@ void game_list_frame::BatchActionBySerials(progress_dialog* pdlg, const std::set const std::shared_ptr> periodic_func = std::make_shared>(); - *periodic_func = [=]() + *periodic_func = [=, this]() { if (should_wait_cb && should_wait_cb()) { diff --git a/rpcs3/util/serialization.hpp b/rpcs3/util/serialization.hpp index a231a22ee8..bcc0fe8d97 100644 --- a/rpcs3/util/serialization.hpp +++ b/rpcs3/util/serialization.hpp @@ -96,6 +96,29 @@ public: return m_is_writing; } + void add_padding(usz padding) + { + if (m_is_writing) return; + pos += padding; + } + + // Add padding needed between two members + template + void add_padding(T T2::*const first, T3 T2::*const second) + { + if (m_is_writing) return; + + const u32 offset1 = ::offset32(first) + sizeof(first); + const u32 offset2 = ::offset32(second); + + AUDIT(offset2 >= offset1); + + if (offset2 > offset1) + { + pos += offset2 - offset1; + } + } + void set_expect_little_data(bool value) { m_expect_little_data = value;