diff --git a/include/capture_structs.hpp b/include/capture_structs.hpp index b10aa09..930ba9b 100755 --- a/include/capture_structs.hpp +++ b/include/capture_structs.hpp @@ -23,11 +23,11 @@ #define OPTIMIZE_3DS_AUDIO_BUFFER_MAX_SIZE 0x200 -enum CaptureConnectionType { CAPTURE_CONN_FTD3, CAPTURE_CONN_USB, CAPTURE_CONN_FTD2, CAPTURE_CONN_IS_NITRO, CAPTURE_CONN_CYPRESS_NISETRO, CAPTURE_CONN_CYPRESS_OPTIMIZE }; +enum CaptureConnectionType { CAPTURE_CONN_FTD3, CAPTURE_CONN_USB, CAPTURE_CONN_FTD2, CAPTURE_CONN_IS_NITRO, CAPTURE_CONN_CYPRESS_NISETRO, CAPTURE_CONN_CYPRESS_OPTIMIZE, CAPTURE_CONN_PARTNER_CTR }; enum InputVideoDataType { VIDEO_DATA_RGB, VIDEO_DATA_BGR, VIDEO_DATA_RGB16, VIDEO_DATA_BGR16 }; enum CaptureScreensType { CAPTURE_SCREENS_BOTH, CAPTURE_SCREENS_TOP, CAPTURE_SCREENS_BOTTOM, CAPTURE_SCREENS_ENUM_END }; enum CaptureSpeedsType { CAPTURE_SPEEDS_FULL, CAPTURE_SPEEDS_HALF, CAPTURE_SPEEDS_THIRD, CAPTURE_SPEEDS_QUARTER, CAPTURE_SPEEDS_ENUM_END }; -enum PossibleCaptureDevices { CC_LOOPY_OLD_DS, CC_LOOPY_NEW_DS, CC_LOOPY_OLD_3DS, CC_LOOPY_NEW_N3DSXL, CC_IS_NITRO_EMULATOR, CC_IS_NITRO_CAPTURE, CC_IS_TWL_CAPTURE, CC_NISETRO_DS, CC_OPTIMIZE_O3DS, CC_OPTIMIZE_N3DS, CC_POSSIBLE_DEVICES_END }; +enum PossibleCaptureDevices { CC_LOOPY_OLD_DS, CC_LOOPY_NEW_DS, CC_LOOPY_OLD_3DS, CC_LOOPY_NEW_N3DSXL, CC_IS_NITRO_EMULATOR, CC_IS_NITRO_CAPTURE, CC_IS_TWL_CAPTURE, CC_NISETRO_DS, CC_OPTIMIZE_O3DS, CC_OPTIMIZE_N3DS, CC_PARTNER_CTR, CC_POSSIBLE_DEVICES_END }; // Readers are Audio and Video. So 2. // Use 6 extra buffers. 5 for async writing in case the other 2 are busy, @@ -222,6 +222,35 @@ struct ALIGNED(16) PACKED ISTWLCaptureReceived { ISTWLCaptureAudioReceived audio_capture_in[TWL_CAPTURE_MAX_SAMPLES_CHUNK_NUM]; }; +// Shared basic command structure for Kyoto MicroComputer Partner CTR Capture +struct ALIGNED(8) PACKED PartnerCTRCaptureCommand { + uint16_t magic; // 0xE007, little endian, like everything... + uint16_t command; + uint32_t payload_size; +}; + +// Input data, maybe...? Sent before every screen... Usually empty? +struct ALIGNED(8) PACKED PartnerCTRCaptureCommandHeader0F { + PartnerCTRCaptureCommand command; + uint16_t unk[4]; +}; + +// C4, C5 and C6. C4 is top screen. C5 second top screen. C6 bottom screen. +struct ALIGNED(8) PACKED PartnerCTRCaptureCommandHeaderCxScreen { + PartnerCTRCaptureCommand command; + uint8_t index_kind; // Reflects C4, C5 and C6, so either 04, 05 or 06 + uint8_t index; + uint16_t unk[3]; +}; + +// Sound data, maybe...? Sent every 3 screens in 2D... +// Every 4 screens in 3D...? +struct ALIGNED(8) PACKED PartnerCTRCaptureCommandHeaderC7 { + PartnerCTRCaptureCommand command; + uint32_t index; + uint32_t unk[5]; +}; + struct ALIGNED(16) PACKED USB5653DSOptimizeCaptureReceived { USB5653DSOptimizeInputColumnData columns_data[TOP_WIDTH_3DS]; USB5653DSOptimizePixelData bottom_only_column[HEIGHT_3DS][2]; @@ -317,6 +346,9 @@ struct CaptureDevice { int firmware_id = 0; uint32_t usb_speed = 0x200; bool is_rgb_888 = false; + bool is_horizontally_flipped = false; + bool is_vertically_flipped = false; + bool continuous_3d_screens = true; }; struct CaptureStatus { diff --git a/include/hw_defs.hpp b/include/hw_defs.hpp index 2d96fbb..19ba4be 100755 --- a/include/hw_defs.hpp +++ b/include/hw_defs.hpp @@ -77,7 +77,13 @@ #define MAX_IN_VIDEO_WIDTH IN_VIDEO_WIDTH_3DS_ROT #define MAX_IN_VIDEO_HEIGHT IN_VIDEO_HEIGHT_3DS_3D -#define MAX_IN_VIDEO_SIZE (MAX_IN_VIDEO_WIDTH * MAX_IN_VIDEO_HEIGHT) +// While this is technically correct, it is extremely wasteful. +//#define MAX_IN_VIDEO_SIZE (MAX_IN_VIDEO_WIDTH * MAX_IN_VIDEO_HEIGHT) +// The real MAX_IN_VIDEO_SIZE is the max between +// IN_VIDEO_SIZE_DS, IN_VIDEO_SIZE_3DS_3D and IN_VIDEO_SIZE_3DS_3D_ROT... +// As such, I am forcing this to be IN_VIDEO_SIZE_3DS_3D_ROT, +// as it is the greatest (due to the wasted space it has)... +#define MAX_IN_VIDEO_SIZE IN_VIDEO_SIZE_3DS_3D_ROT #define MAX_IN_VIDEO_BPP_SIZE IN_VIDEO_BPP_SIZE_3DS #define MAX_IN_VIDEO_WIDTH_TOP IN_VIDEO_WIDTH_3DS_ROT diff --git a/source/WindowScreen.cpp b/source/WindowScreen.cpp index 66ed392..6823a44 100755 --- a/source/WindowScreen.cpp +++ b/source/WindowScreen.cpp @@ -686,8 +686,12 @@ void WindowScreen::execute_single_update_texture(bool &manually_converted, bool bot_width = WIDTH_DS; bot_height = HEIGHT_DS; } - if(get_3d_enabled(this->capture_status)) - top_width *= 2; + if(get_3d_enabled(this->capture_status)) { + if(this->capture_status->device.continuous_3d_screens) + top_width *= 2; + else + top_height *= 2; + } if(is_vertically_rotated(this->capture_status->device.base_rotation)) { std::swap(top_width, top_height); @@ -967,6 +971,13 @@ void WindowScreen::post_texture_conversion_processing(sf::RectangleShape &rect_d sf::IntRect text_coords_rect = final_in_rect.getTextureRect(); text_coords_rect.position.x += this->curr_frame_texture_pos * MAX_IN_VIDEO_WIDTH; final_in_rect.setTextureRect(text_coords_rect); + float x_scale = 1; + float y_scale = 1; + if(capture_status->connected && capture_status->device.is_horizontally_flipped) + x_scale = -1; + if(capture_status->connected && capture_status->device.is_vertically_flipped) + y_scale = -1; + final_in_rect.setScale({x_scale, y_scale}); if(this->capture_status->connected && actually_draw) { bool use_default_shader = !(this->apply_shaders_to_input(rect_data, to_process_tex_data, backup_tex_data, final_in_rect, is_top)); if(use_default_shader) diff --git a/source/cc3dsfs.cpp b/source/cc3dsfs.cpp index d5928ef..f59bc46 100755 --- a/source/cc3dsfs.cpp +++ b/source/cc3dsfs.cpp @@ -467,10 +467,12 @@ static void soundCall(AudioData *audio_data, CaptureData* capture_data, volatile bool conversion_success = convertAudioToOutput(out_buf[audio_buf_counter], n_samples, last_buffer_index, endianness, data_buffer, &capture_data->status); if(!conversion_success) audio_data->signal_conversion_error(); - audio.samples.emplace(out_buf[audio_buf_counter], n_samples, out_time); - if(++audio_buf_counter >= NUM_CONCURRENT_AUDIO_BUFFERS) - audio_buf_counter = 0; - audio.samples_wait.unlock(); + if(n_samples > 0) { + audio.samples.emplace(out_buf[audio_buf_counter], n_samples, out_time); + if(++audio_buf_counter >= NUM_CONCURRENT_AUDIO_BUFFERS) + audio_buf_counter = 0; + audio.samples_wait.unlock(); + } } capture_data->data_buffers.ReleaseReaderBuffer(CAPTURE_READER_AUDIO); } diff --git a/source/conversions.cpp b/source/conversions.cpp index 9ab9653..388a9e0 100755 --- a/source/conversions.cpp +++ b/source/conversions.cpp @@ -786,6 +786,179 @@ static void usb_is_device_convertVideoToOutput(CaptureReceived *p_in, VideoOutpu } } +// To be moved to the appropriate header, later... +#define PARTNER_CTR_CAPTURE_BASE_COMMAND 0xE007 +#define PARTNER_CTR_CAPTURE_COMMAND_INPUT 0x000F +#define PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN 0x00C4 +#define PARTNER_CTR_CAPTURE_COMMAND_SECOND_TOP_SCREEN 0x00C5 +#define PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN 0x00C6 +#define PARTNER_CTR_CAPTURE_COMMAND_AUDIO 0x00C7 + +static PartnerCTRCaptureCommand read_partner_ctr_base_command(uint8_t* data) { + PartnerCTRCaptureCommand out_cmd; + // Important: Ensure the data after the buffers for a single frame + // is stopped by wrong magic value... + out_cmd.magic = read_le16(data, 0); + out_cmd.command = read_le16(data, 1); + out_cmd.payload_size = read_le32(data, 1); + return out_cmd; +} + +static size_t get_partner_ctr_size_command_header(PartnerCTRCaptureCommand read_command) { + switch (read_command.command) { + case PARTNER_CTR_CAPTURE_COMMAND_INPUT: + return sizeof(PartnerCTRCaptureCommandHeader0F); + case PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN: + return sizeof(PartnerCTRCaptureCommandHeaderCxScreen); + case PARTNER_CTR_CAPTURE_COMMAND_SECOND_TOP_SCREEN: + return sizeof(PartnerCTRCaptureCommandHeaderCxScreen); + case PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN: + return sizeof(PartnerCTRCaptureCommandHeaderCxScreen); + case PARTNER_CTR_CAPTURE_COMMAND_AUDIO: + return sizeof(PartnerCTRCaptureCommandHeaderC7); + default: + ActualConsoleOutTextError("Partner CTR Capture: Unknown command found! " + std::to_string(read_command.command)); + return sizeof(PartnerCTRCaptureCommandHeader0F); + } +} + +static uint8_t* get_ptr_next_command_partner_ctr(uint8_t* data) { + PartnerCTRCaptureCommand read_command = read_partner_ctr_base_command(data); + return data + get_partner_ctr_size_command_header(read_command) + read_command.payload_size; +} + +static uint8_t* find_partner_ctr_x_screen(uint8_t* data) { + PartnerCTRCaptureCommand read_command = read_partner_ctr_base_command(data); + if(read_command.magic != PARTNER_CTR_CAPTURE_BASE_COMMAND) + return NULL; + + if((read_command.command == PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN) || (read_command.command == PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN) || (read_command.command == PARTNER_CTR_CAPTURE_COMMAND_SECOND_TOP_SCREEN)) + return data; + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_AUDIO) + data = get_ptr_next_command_partner_ctr(data); + + read_command = read_partner_ctr_base_command(data); + if(read_command.magic != PARTNER_CTR_CAPTURE_BASE_COMMAND) + return NULL; + + if((read_command.command == PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN) || (read_command.command == PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN) || (read_command.command == PARTNER_CTR_CAPTURE_COMMAND_SECOND_TOP_SCREEN)) + return data; + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_INPUT) + data = get_ptr_next_command_partner_ctr(data); + + read_command = read_partner_ctr_base_command(data); + if(read_command.magic != PARTNER_CTR_CAPTURE_BASE_COMMAND) + return NULL; + + if((read_command.command == PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN) || (read_command.command == PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN) || (read_command.command == PARTNER_CTR_CAPTURE_COMMAND_SECOND_TOP_SCREEN)) + return data; + ActualConsoleOutTextError("Partner CTR Capture: Unknown command found! " + std::to_string(read_command.command)); + return NULL; +} + +static bool is_valid_frame_partner_ctr(uint8_t* data, bool enabled_3d, uint8_t** first_screen, uint8_t** second_screen, uint8_t** third_screen) { + bool has_top = false; + bool has_top_second = !enabled_3d; + bool has_bottom = false; + + *first_screen = find_partner_ctr_x_screen(data); + if(*first_screen == NULL) + return false; + PartnerCTRCaptureCommand read_command = read_partner_ctr_base_command(*first_screen); + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN) + has_top = true; + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN) + has_bottom = true; + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_SECOND_TOP_SCREEN) + has_top_second = true; + + *second_screen = find_partner_ctr_x_screen(get_ptr_next_command_partner_ctr(*first_screen)); + if(*second_screen == NULL) + return false; + + read_command = read_partner_ctr_base_command(*second_screen); + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN) + has_top = true; + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN) + has_bottom = true; + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_SECOND_TOP_SCREEN) + has_top_second = true; + + if(!enabled_3d) + return has_top && has_bottom; + + *third_screen = find_partner_ctr_x_screen(get_ptr_next_command_partner_ctr(*second_screen)); + if(*third_screen == NULL) + return false; + + read_command = read_partner_ctr_base_command(*third_screen); + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN) + has_top = true; + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN) + has_bottom = true; + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_SECOND_TOP_SCREEN) + has_top_second = true; + + return has_top && has_top_second && has_bottom; +} + +static void convert_partner_ctr_screen_x(uint8_t* screen_ptr, VideoOutputData *p_out) { + if(screen_ptr == NULL) + return; + + PartnerCTRCaptureCommand read_command = read_partner_ctr_base_command(screen_ptr); + VideoPixelRGB* out_screen_data = &p_out->rgb_video_output_data.screen_data[0]; + screen_ptr += get_partner_ctr_size_command_header(read_command); + + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_BOT_SCREEN) { + for(size_t i = 0; i < HEIGHT_3DS; i++) + memcpy(&out_screen_data[i * TOP_WIDTH_3DS], screen_ptr + (i * BOT_WIDTH_3DS * sizeof(VideoPixelRGB)), BOT_WIDTH_3DS * sizeof(VideoPixelRGB)); + return; + } + + if(read_command.command == PARTNER_CTR_CAPTURE_COMMAND_TOP_SCREEN) { + memcpy(&out_screen_data[TOP_WIDTH_3DS * HEIGHT_3DS], screen_ptr, TOP_WIDTH_3DS * HEIGHT_3DS * sizeof(VideoPixelRGB)); + return; + } + + memcpy(&out_screen_data[2 * TOP_WIDTH_3DS * HEIGHT_3DS], screen_ptr, TOP_WIDTH_3DS * HEIGHT_3DS * sizeof(VideoPixelRGB)); +} + +static void usb_partner_ctr_interleave_3d(VideoOutputData *p_out) { + VideoPixelRGB buffer_data[TOP_WIDTH_3DS * 2]; + + VideoPixelRGB* out_top_screen = &p_out->rgb_video_output_data.screen_data[TOP_WIDTH_3DS * HEIGHT_3DS]; + VideoPixelRGB* out_second_top_screen = &p_out->rgb_video_output_data.screen_data[2 * TOP_WIDTH_3DS * HEIGHT_3DS]; + for(size_t i = 0; i < HEIGHT_3DS; i++) { + for(size_t j = 0; j < TOP_WIDTH_3DS; j++) { + buffer_data[j * 2] = out_top_screen[(i * TOP_WIDTH_3DS) + j]; + buffer_data[(j * 2) + 1] = out_second_top_screen[(i * TOP_WIDTH_3DS) + j]; + } + memcpy(&out_top_screen[i * TOP_WIDTH_3DS], &buffer_data[0], TOP_WIDTH_3DS * sizeof(VideoPixelRGB)); + memcpy(&out_second_top_screen[i * TOP_WIDTH_3DS], &buffer_data[TOP_WIDTH_3DS], TOP_WIDTH_3DS * sizeof(VideoPixelRGB)); + } +} + +static void usb_partner_ctr_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, bool enabled_3d, bool interleaved_3d, bool requested_3d) { + uint8_t* data = (uint8_t*)p_in; + uint8_t* first_screen = NULL; + uint8_t* second_screen = NULL; + uint8_t* third_screen = NULL; + + if(!is_valid_frame_partner_ctr(data, enabled_3d, &first_screen, &second_screen, &third_screen)) + return; + + convert_partner_ctr_screen_x(first_screen, p_out); + convert_partner_ctr_screen_x(second_screen, p_out); + convert_partner_ctr_screen_x(third_screen, p_out); + + if(requested_3d && (!enabled_3d)) + memcpy(&p_out->rgb_video_output_data.screen_data[2 * TOP_WIDTH_3DS * HEIGHT_3DS], &p_out->rgb_video_output_data.screen_data[TOP_WIDTH_3DS * HEIGHT_3DS], TOP_WIDTH_3DS * HEIGHT_3DS * sizeof(VideoPixelRGB)); + + if(requested_3d && interleaved_3d) + usb_partner_ctr_interleave_3d(p_out); +} + bool convertVideoToOutput(VideoOutputData *p_out, const bool is_big_endian, CaptureDataSingleBuffer* data_buffer, CaptureStatus* status, bool interleaved_3d) { CaptureReceived* p_in = (CaptureReceived*)(((uint8_t*)&data_buffer->capture_buf) + data_buffer->unused_offset); bool converted = false; @@ -832,6 +1005,12 @@ bool convertVideoToOutput(VideoOutputData *p_out, const bool is_big_endian, Capt converted = true; } #endif + #ifdef USE_PARTNER_CTR + if(status->device.cc_type == CAPTURE_CONN_PARTNER_CTR) { + usb_partner_ctr_convertVideoToOutput(p_in, p_out, is_data_3d, interleaved_3d, is_3d_requested); + converted = true; + } + #endif return converted; } @@ -985,6 +1164,43 @@ static void copyAudioOptimize3DS3DForced2DBE(std::int16_t *p_out, uint64_t &n_sa n_samples = num_inserted * 2; } +static void usb_partner_ctr_copyBufferToAudio(uint8_t* buffer_ptr, std::int16_t *p_out, uint64_t &n_samples, const bool is_big_endian) { + PartnerCTRCaptureCommand read_command = read_partner_ctr_base_command(buffer_ptr); + if(read_command.magic != PARTNER_CTR_CAPTURE_BASE_COMMAND) + return; + if(read_command.command != PARTNER_CTR_CAPTURE_COMMAND_AUDIO) + return; + + buffer_ptr += get_partner_ctr_size_command_header(read_command); + + memcpy_data_u16le_origin((uint16_t*)p_out + n_samples, buffer_ptr, (size_t)read_command.payload_size / 2, is_big_endian); + + n_samples += read_command.payload_size / 2; +} + +static void usb_partner_ctr_copyBufferAfterCommandToAudio(uint8_t* buffer_ptr, std::int16_t *p_out, uint64_t &n_samples, const bool is_big_endian) { + if(buffer_ptr == NULL) + return; + + usb_partner_ctr_copyBufferToAudio(get_ptr_next_command_partner_ctr(buffer_ptr), p_out, n_samples, is_big_endian); +} + +static void usb_partner_ctr_convertAudioToOutput(CaptureReceived *p_in, std::int16_t *p_out, uint64_t &n_samples, bool enabled_3d, const bool is_big_endian) { + uint8_t* data = (uint8_t*)p_in; + uint8_t* first_screen = NULL; + uint8_t* second_screen = NULL; + uint8_t* third_screen = NULL; + n_samples = 0; + + if(!is_valid_frame_partner_ctr(data, enabled_3d, &first_screen, &second_screen, &third_screen)) + return; + + usb_partner_ctr_copyBufferToAudio(data, p_out, n_samples, is_big_endian); + usb_partner_ctr_copyBufferAfterCommandToAudio(first_screen, p_out, n_samples, is_big_endian); + usb_partner_ctr_copyBufferAfterCommandToAudio(second_screen, p_out, n_samples, is_big_endian); + usb_partner_ctr_copyBufferAfterCommandToAudio(third_screen, p_out, n_samples, is_big_endian); +} + bool convertAudioToOutput(std::int16_t *p_out, uint64_t &n_samples, uint16_t &last_buffer_index, const bool is_big_endian, CaptureDataSingleBuffer* data_buffer, CaptureStatus* status) { if(!status->device.has_audio) return true; @@ -1057,6 +1273,12 @@ bool convertAudioToOutput(std::int16_t *p_out, uint64_t &n_samples, uint16_t &la return true; } #endif + #ifdef USE_PARTNER_CTR + if(status->device.cc_type == CAPTURE_CONN_PARTNER_CTR) { + usb_partner_ctr_convertAudioToOutput(p_in, p_out, n_samples, is_data_3d, is_big_endian); + return true; + } + #endif if(base_ptr == NULL) return false; memcpy_data_u16le_origin((uint16_t*)p_out, base_ptr, (size_t)n_samples, is_big_endian);