From 718294ba0262c0fed67dd5c030076e5ca07ca32a Mon Sep 17 00:00:00 2001 From: Lorenzooone Date: Wed, 21 Aug 2024 14:02:22 +0200 Subject: [PATCH] Add really basic IS Nitro Emulator support --- CMakeLists.txt | 4 +- README.md | 2 + include/capture_structs.hpp | 11 +- include/frontend.hpp | 2 +- include/usb_ds_3ds_capture.hpp | 6 +- include/usb_generic.hpp | 11 + include/usb_is_nitro.hpp | 62 ++++++ include/usb_is_nitro_capture.hpp | 21 ++ include/utils.hpp | 4 + source/conversions.cpp | 7 +- source/devicecapture.cpp | 47 ++++- source/frontend.cpp | 14 +- source/usb_ds_3ds_capture.cpp | 67 +++--- source/usb_generic.cpp | 33 +++ source/usb_is_nitro.cpp | 299 +++++++++++++++++++++++++++ source/usb_is_nitro_capture.cpp | 344 +++++++++++++++++++++++++++++++ source/utils.cpp | 33 ++- usb_rules/51-isnitro.rules | 1 + 18 files changed, 901 insertions(+), 67 deletions(-) create mode 100644 include/usb_generic.hpp create mode 100644 include/usb_is_nitro.hpp create mode 100644 include/usb_is_nitro_capture.hpp create mode 100644 source/usb_generic.cpp create mode 100644 source/usb_is_nitro.cpp create mode 100644 source/usb_is_nitro_capture.cpp create mode 100644 usb_rules/51-isnitro.rules diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e32664..f3ca06e 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,7 +43,7 @@ endif() include(FetchContent) FetchContent_Declare(SFML - GIT_REPOSITORY https://github.com/Lorenzooone/SFML.git + GIT_REPOSITORY https://github.com/SFML/SFML.git GIT_TAG macos_fullscreen_v2.6) FetchContent_Declare(libusb1 @@ -301,7 +301,7 @@ execute_process(COMMAND ${CMAKE_COMMAND} --build ${TOOLS_DATA_DIR}) set(OUTPUT_NAME cc3dsfs) -add_executable(${OUTPUT_NAME} source/cc3dsfs.cpp source/utils.cpp source/audio_data.cpp source/audio.cpp source/frontend.cpp source/TextRectangle.cpp source/WindowScreen.cpp source/WindowScreen_Menu.cpp source/3dscapture_ftd3.cpp source/dscapture_ftd2.cpp source/usb_ds_3ds_capture.cpp source/devicecapture.cpp source/conversions.cpp source/ExtraButtons.cpp source/Menus/ConnectionMenu.cpp source/Menus/OptionSelectionMenu.cpp source/Menus/MainMenu.cpp source/Menus/VideoMenu.cpp source/Menus/CropMenu.cpp source/Menus/PARMenu.cpp source/Menus/RotationMenu.cpp source/Menus/OffsetMenu.cpp source/Menus/AudioMenu.cpp source/Menus/BFIMenu.cpp source/Menus/RelativePositionMenu.cpp source/Menus/ResolutionMenu.cpp source/Menus/FileConfigMenu.cpp source/Menus/ExtraSettingsMenu.cpp source/Menus/StatusMenu.cpp source/Menus/LicenseMenu.cpp source/WindowCommands.cpp source/Menus/ShortcutMenu.cpp source/Menus/ActionSelectionMenu.cpp source/Menus/ScalingRatioMenu.cpp ${TOOLS_DATA_DIR}/font_ttf.cpp) +add_executable(${OUTPUT_NAME} source/cc3dsfs.cpp source/utils.cpp source/audio_data.cpp source/audio.cpp source/frontend.cpp source/TextRectangle.cpp source/WindowScreen.cpp source/WindowScreen_Menu.cpp source/3dscapture_ftd3.cpp source/dscapture_ftd2.cpp source/usb_ds_3ds_capture.cpp source/devicecapture.cpp source/conversions.cpp source/ExtraButtons.cpp source/Menus/ConnectionMenu.cpp source/Menus/OptionSelectionMenu.cpp source/Menus/MainMenu.cpp source/Menus/VideoMenu.cpp source/Menus/CropMenu.cpp source/Menus/PARMenu.cpp source/Menus/RotationMenu.cpp source/Menus/OffsetMenu.cpp source/Menus/AudioMenu.cpp source/Menus/BFIMenu.cpp source/Menus/RelativePositionMenu.cpp source/Menus/ResolutionMenu.cpp source/Menus/FileConfigMenu.cpp source/Menus/ExtraSettingsMenu.cpp source/Menus/StatusMenu.cpp source/Menus/LicenseMenu.cpp source/WindowCommands.cpp source/Menus/ShortcutMenu.cpp source/Menus/ActionSelectionMenu.cpp source/Menus/ScalingRatioMenu.cpp source/usb_is_nitro.cpp source/usb_is_nitro_capture.cpp source/usb_generic.cpp ${TOOLS_DATA_DIR}/font_ttf.cpp) add_dependencies(${OUTPUT_NAME} FTD3XX_BUILD_PROJECT FTD2XX_BUILD_PROJECT) target_link_libraries(${OUTPUT_NAME} PRIVATE sfml-graphics sfml-audio sfml-window sfml-system usb-1.0 ${ftd3xx_BINARY_DIR}/${FTD3XX_SUBFOLDER}/${FTD3XX_LIB} ${ftd2xx_BINARY_DIR}/${FTD2XX_SUBFOLDER}/${FTD2XX_LIB} ${EXTRA_LIBRARIES}) target_link_directories(${OUTPUT_NAME} PRIVATE ${ftd3xx_BINARY_DIR}/${FTD3XX_SUBFOLDER} ${ftd2xx_BINARY_DIR}/${FTD2XX_SUBFOLDER}) diff --git a/README.md b/README.md index 9f7c011..075efbe 100755 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ cc3dsfs is a multi-platform capture and display program for [3dscapture's](https://3dscapture.com/) N3DSXL, 3DS and DS (old) capture boards written in C++. The main goal is to offer the ability to use the Capture Card with a TV, via fullscreen mode. +Tentative IS Nitro Emulator support (for newer revisions) is also present. Though results may vary (and the amount of video delay may be significantly higher). + ## Features - Performance-focused design, with low latency for both audio and video (measured to oscillate between 1 and 2 frames on 120hz displays). diff --git a/include/capture_structs.hpp b/include/capture_structs.hpp index 1c2988d..81ca681 100755 --- a/include/capture_structs.hpp +++ b/include/capture_structs.hpp @@ -18,7 +18,7 @@ #define EXTRA_DATA_BUFFER_USB_SIZE (1 << 9) #define EXTRA_DATA_BUFFER_FTD3XX_SIZE (1 << 10) -enum CaptureConnectionType { CAPTURE_CONN_FTD3, CAPTURE_CONN_USB, CAPTURE_CONN_FTD2 }; +enum CaptureConnectionType { CAPTURE_CONN_FTD3, CAPTURE_CONN_USB, CAPTURE_CONN_FTD2, CAPTURE_CONN_IS_NITRO }; #pragma pack(push, 1) @@ -44,6 +44,10 @@ struct PACKED USBOldDSVideoInputData { USBOldDSPixelData screen_data[IN_VIDEO_SIZE_DS]; }; +struct PACKED ISNitroEmulatorVideoInputData { + uint8_t screen_data[IN_VIDEO_SIZE_DS][3]; +}; + struct PACKED FTD3_3DSCaptureReceived { RGB83DSVideoInputData video_in; uint16_t audio_data[N3DSXL_SAMPLES_IN]; @@ -81,6 +85,10 @@ struct PACKED USBOldDSCaptureReceived { USBOldDSFrameInfo frameinfo; }; +struct PACKED ISNitroCaptureReceived { + ISNitroEmulatorVideoInputData video_in; +}; + #pragma pack(pop) union CaptureReceived { @@ -89,6 +97,7 @@ union CaptureReceived { USB3DSCaptureReceived usb_received_3ds; USB3DSCaptureReceived_3D usb_received_3ds_3d; USBOldDSCaptureReceived usb_received_old_ds; + ISNitroCaptureReceived is_nitro_capture_received; }; struct CaptureDevice { diff --git a/include/frontend.hpp b/include/frontend.hpp index ed86051..506cc1d 100755 --- a/include/frontend.hpp +++ b/include/frontend.hpp @@ -313,9 +313,9 @@ bool load_screen_info(std::string key, std::string value, std::string base, Scre std::string save_screen_info(std::string base, const ScreenInfo &info); void get_par_size(int &width, int &height, float multiplier_factor, const PARData *correction_factor); float get_par_mult_factor(float width, float height, float max_width, float max_height, const PARData *correction_factor, bool is_rotated); -void default_sleep(int wanted_ms = -1); void update_output(FrontendData* frontend_data, double frame_time = 0.0, VideoOutputData *out_buf = NULL); void update_connected_3ds_ds(FrontendData* frontend_data, const CaptureDevice &old_cc_device, const CaptureDevice &new_cc_device); void screen_display_thread(WindowScreen *screen); std::string get_name_non_int_mode(NonIntegerScalingModes input); +void default_sleep(int wanted_ms = -1); #endif diff --git a/include/usb_ds_3ds_capture.hpp b/include/usb_ds_3ds_capture.hpp index 7f84d19..4702d89 100755 --- a/include/usb_ds_3ds_capture.hpp +++ b/include/usb_ds_3ds_capture.hpp @@ -7,7 +7,7 @@ #include "capture_structs.hpp" #include "display_structs.hpp" -#define USE_USB +#define USE_DS_3DS_USB void list_devices_usb_ds_3ds(std::vector &devices_list); bool connect_usb(bool print_failed, CaptureData* capture_data, CaptureDevice* device); @@ -15,7 +15,7 @@ void usb_capture_main_loop(CaptureData* capture_data); void usb_capture_cleanup(CaptureData* capture_data); void usb_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureDevice* capture_device, bool enabled_3d); uint64_t usb_get_video_in_size(CaptureData* capture_data); -void usb_init(); -void usb_close(); +void usb_ds_3ds_init(); +void usb_ds_3ds_close(); #endif diff --git a/include/usb_generic.hpp b/include/usb_generic.hpp new file mode 100644 index 0000000..04c4ce0 --- /dev/null +++ b/include/usb_generic.hpp @@ -0,0 +1,11 @@ +#ifndef __USB_GENERIC_HPP +#define __USB_GENERIC_HPP + +#include + +void usb_init(); +void usb_close(); +bool usb_is_initialized(); +libusb_context* get_usb_ctx(); + +#endif diff --git a/include/usb_is_nitro.hpp b/include/usb_is_nitro.hpp new file mode 100644 index 0000000..2e8be91 --- /dev/null +++ b/include/usb_is_nitro.hpp @@ -0,0 +1,62 @@ +#ifndef __USB_IS_NITRO_HPP +#define __USB_IS_NITRO_HPP + +#include +#include +#include "utils.hpp" + +#define IS_NITRO_REAL_SERIAL_NUMBER_SIZE 10 + +enum is_nitro_forward_enable_values { + IS_NITRO_FORWARD_ENABLE_DISABLE = 0, + IS_NITRO_FORWARD_ENABLE_ENABLE = 1, + IS_NITRO_FORWARD_ENABLE_RESTART = 2, +}; + +enum is_nitro_forward_config_values { + IS_NITRO_FORWARD_CONFIG_COLOR_RGB24 = 0, + IS_NITRO_FORWARD_CONFIG_MODE_BOTH = 0, + IS_NITRO_FORWARD_CONFIG_MODE_TOP = 4, + IS_NITRO_FORWARD_CONFIG_MODE_BOTTOM = 8, + IS_NITRO_FORWARD_CONFIG_RATE_FULL = 0, + IS_NITRO_FORWARD_CONFIG_RATE_HALF = 1, + IS_NITRO_FORWARD_CONFIG_RATE_THIRD = 2, + IS_NITRO_FORWARD_CONFIG_RATE_QUARTER = 3, +}; + +struct is_nitro_usb_device { + int vid; + int pid; + int default_config; + int default_interface; + int bulk_timeout; + int ep2_in; + int ep1_out; + int product_id; + int manufacturer_id; +}; + +extern const is_nitro_usb_device usb_is_nitro_desc; + +/* +int SendReadCommand(libusb_device_handle *handle, uint16_t command, uint8_t* buf, int length); +int SendWriteCommand(libusb_device_handle *handle, uint16_t command, uint8_t* buf, int length); +int ReadNecMem(libusb_device_handle *handle, uint32_t address, uint8_t unit_size, uint8_t* buf, int count); +int ReadNecMemU16(libusb_device_handle *handle, uint32_t address, uint16_t* out); +int ReadNecMemU32(libusb_device_handle *handle, uint32_t address, uint32_t* out); +int WriteNecMem(libusb_device_handle *handle, uint32_t address, uint8_t unit_size, uint8_t* buf, int count); +int WriteNecMemU16(libusb_device_handle *handle, uint32_t address, uint16_t value); +int WriteNecMemU32(libusb_device_handle *handle, uint32_t address, uint32_t value); +*/ + +int DisableLca2(libusb_device_handle *handle); +int StartUsbCaptureDma(libusb_device_handle *handle); +int StopUsbCaptureDma(libusb_device_handle *handle); +int SetForwardFrameCount(libusb_device_handle *handle, uint16_t count); +int GetFrameCounter(libusb_device_handle *handle, uint16_t* out); +int GetDeviceSerial(libusb_device_handle *handle, uint8_t* buf); +int UpdateFrameForwardConfig(libusb_device_handle *handle, uint8_t value); +int UpdateFrameForwardEnable(libusb_device_handle *handle, uint8_t value); +int ReadFrame(libusb_device_handle *handle, uint8_t* buf, int length); + +#endif diff --git a/include/usb_is_nitro_capture.hpp b/include/usb_is_nitro_capture.hpp new file mode 100644 index 0000000..ddaedec --- /dev/null +++ b/include/usb_is_nitro_capture.hpp @@ -0,0 +1,21 @@ +#ifndef __USB_IS_NITRO_CAPTURE_HPP +#define __USB_IS_NITRO_CAPTURE_HPP + +#include +#include "utils.hpp" +#include "hw_defs.hpp" +#include "capture_structs.hpp" +#include "display_structs.hpp" + +#define USE_IS_NITRO_USB + +void list_devices_is_nitro(std::vector &devices_list); +bool is_nitro_connect_usb(bool print_failed, CaptureData* capture_data, CaptureDevice* device); +void is_nitro_capture_main_loop(CaptureData* capture_data); +void usb_is_nitro_capture_cleanup(CaptureData* capture_data); +void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureDevice* capture_device); +uint64_t usb_is_nitro_emulator_get_video_in_size(CaptureData* capture_data); +void usb_is_nitro_init(); +void usb_is_nitro_close(); + +#endif diff --git a/include/utils.hpp b/include/utils.hpp index e5658cd..adfc2d9 100755 --- a/include/utils.hpp +++ b/include/utils.hpp @@ -29,6 +29,10 @@ std::string LayoutNameGenerator(int index); std::string LayoutPathGenerator(int index); std::string load_layout_name(int index, bool &success); bool is_big_endian(void); +uint32_t to_le(uint32_t value); +uint32_t to_be(uint32_t value); +uint16_t to_le(uint16_t value); +uint16_t to_be(uint16_t value); std::string get_float_str_decimals(float value, int decimals); class ConsumerMutex { diff --git a/source/conversions.cpp b/source/conversions.cpp index 69029ae..0d8f8d8 100755 --- a/source/conversions.cpp +++ b/source/conversions.cpp @@ -3,6 +3,7 @@ #include "3dscapture_ftd3.hpp" #include "dscapture_ftd2.hpp" #include "usb_ds_3ds_capture.hpp" +#include "usb_is_nitro_capture.hpp" #include @@ -15,10 +16,14 @@ void convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, Capture if(capture_data->status.device.cc_type == CAPTURE_CONN_FTD2) ftd2_convertVideoToOutput(p_in, p_out, capture_data->status.enabled_3d); #endif - #ifdef USE_USB + #ifdef USE_DS_3DS_USB if(capture_data->status.device.cc_type == CAPTURE_CONN_USB) usb_convertVideoToOutput(p_in, p_out, &capture_data->status.device, capture_data->status.enabled_3d); #endif + #ifdef USE_IS_NITRO_USB + if(capture_data->status.device.cc_type == CAPTURE_CONN_IS_NITRO) + usb_is_nitro_convertVideoToOutput(p_in, p_out, &capture_data->status.device); + #endif } void convertAudioToOutput(CaptureReceived *p_in, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data) { diff --git a/source/devicecapture.cpp b/source/devicecapture.cpp index 93368ff..99d13ea 100755 --- a/source/devicecapture.cpp +++ b/source/devicecapture.cpp @@ -2,6 +2,7 @@ #include "3dscapture_ftd3.hpp" #include "dscapture_ftd2.hpp" #include "usb_ds_3ds_capture.hpp" +#include "usb_is_nitro_capture.hpp" #include #include @@ -16,9 +17,8 @@ static bool poll_connection_window_screen(WindowScreen *screen, int &chosen_inde chosen_index = screen->check_connection_menu_result(); return true; } - if(screen->close_capture()) { + if(screen->close_capture()) return true; - } return false; } @@ -76,9 +76,12 @@ bool connect(bool print_failed, CaptureData* capture_data, FrontendData* fronten #ifdef USE_FTD2 list_devices_ftd2(devices_list); #endif - #ifdef USE_USB + #ifdef USE_DS_3DS_USB list_devices_usb_ds_3ds(devices_list); #endif + #ifdef USE_IS_NITRO_USB + list_devices_is_nitro(devices_list); + #endif if(devices_list.size() <= 0) { capture_error_print(print_failed, capture_data, "No device was found"); @@ -100,10 +103,14 @@ bool connect(bool print_failed, CaptureData* capture_data, FrontendData* fronten if((devices_list[chosen_device].cc_type == CAPTURE_CONN_FTD2) && (!connect_ftd2(print_failed, capture_data, &devices_list[chosen_device]))) return false; #endif - #ifdef USE_USB + #ifdef USE_DS_3DS_USB if((devices_list[chosen_device].cc_type == CAPTURE_CONN_USB) && (!connect_usb(print_failed, capture_data, &devices_list[chosen_device]))) return false; #endif + #ifdef USE_IS_NITRO_USB + if((devices_list[chosen_device].cc_type == CAPTURE_CONN_IS_NITRO) && (!is_nitro_connect_usb(print_failed, capture_data, &devices_list[chosen_device]))) + return false; + #endif update_connected_3ds_ds(frontend_data, capture_data->status.device, devices_list[chosen_device]); capture_data->status.device = devices_list[chosen_device]; @@ -134,10 +141,14 @@ void captureCall(CaptureData* capture_data) { if(capture_data->status.device.cc_type == CAPTURE_CONN_FTD2) ftd2_capture_main_loop(capture_data); #endif - #ifdef USE_USB + #ifdef USE_DS_3DS_USB if(capture_data->status.device.cc_type == CAPTURE_CONN_USB) usb_capture_main_loop(capture_data); #endif + #ifdef USE_IS_NITRO_USB + if(capture_data->status.device.cc_type == CAPTURE_CONN_IS_NITRO) + is_nitro_capture_main_loop(capture_data); + #endif capture_data->status.close_success = false; capture_data->status.connected = false; @@ -156,10 +167,14 @@ void captureCall(CaptureData* capture_data) { if(capture_data->status.device.cc_type == CAPTURE_CONN_FTD2) ftd2_capture_cleanup(capture_data); #endif - #ifdef USE_USB + #ifdef USE_DS_3DS_USB if(capture_data->status.device.cc_type == CAPTURE_CONN_USB) usb_capture_cleanup(capture_data); #endif + #ifdef USE_IS_NITRO_USB + if(capture_data->status.device.cc_type == CAPTURE_CONN_IS_NITRO) + usb_is_nitro_capture_cleanup(capture_data); + #endif capture_data->status.close_success = false; capture_data->status.connected = false; @@ -186,21 +201,31 @@ uint64_t get_video_in_size(CaptureData* capture_data) { if(capture_data->status.device.cc_type == CAPTURE_CONN_FTD2) return ftd2_get_video_in_size(capture_data); #endif - #ifdef USE_USB + #ifdef USE_DS_3DS_USB if(capture_data->status.device.cc_type == CAPTURE_CONN_USB) return usb_get_video_in_size(capture_data); #endif + #ifdef USE_IS_NITRO_USB + if(capture_data->status.device.cc_type == CAPTURE_CONN_IS_NITRO) + return usb_is_nitro_emulator_get_video_in_size(capture_data); + #endif return 0; } void capture_init() { - #ifdef USE_USB - usb_init(); + #ifdef USE_DS_3DS_USB + usb_ds_3ds_init(); + #endif + #ifdef USE_IS_NITRO_USB + usb_is_nitro_init(); #endif } void capture_close() { - #ifdef USE_USB - usb_close(); + #ifdef USE_DS_3DS_USB + usb_ds_3ds_close(); + #endif + #ifdef USE_IS_NITRO_USB + usb_is_nitro_close(); #endif } diff --git a/source/frontend.cpp b/source/frontend.cpp index fecf535..da26c29 100755 --- a/source/frontend.cpp +++ b/source/frontend.cpp @@ -740,13 +740,6 @@ JoystickAction get_joystick_action(uint32_t joystickId, uint32_t joy_button) { return JOY_ACTION_NONE; } -void default_sleep(int wanted_ms) { - if(wanted_ms < 0) - sf::sleep(sf::milliseconds(1000/USB_CHECKS_PER_SECOND)); - else - sf::sleep(sf::milliseconds(wanted_ms)); -} - void update_output(FrontendData* frontend_data, double frame_time, VideoOutputData *out_buf) { if(frontend_data->reload) { frontend_data->top_screen->reload(); @@ -793,6 +786,13 @@ void update_connected_3ds_ds(FrontendData* frontend_data, const CaptureDevice &o } } +void default_sleep(int wanted_ms) { + if(wanted_ms < 0) + sf::sleep(sf::milliseconds(1000/USB_CHECKS_PER_SECOND)); + else + sf::sleep(sf::milliseconds(wanted_ms)); +} + void screen_display_thread(WindowScreen *screen) { screen->display_thread(); } diff --git a/source/usb_ds_3ds_capture.cpp b/source/usb_ds_3ds_capture.cpp index bf63032..70d0f11 100755 --- a/source/usb_ds_3ds_capture.cpp +++ b/source/usb_ds_3ds_capture.cpp @@ -1,5 +1,6 @@ #include "usb_ds_3ds_capture.hpp" #include "devicecapture.hpp" +#include "usb_generic.hpp" #include #include @@ -73,9 +74,6 @@ static const usb_device usb_old_ds_desc = { .i2caddr_3dsconfig = 0, .bitstream_3dscfg_ver = 0 }; -static bool usb_initialized = false; -static libusb_context *usb_ctx = NULL; // libusb session context - // Read vendor request from control endpoint. Returns bytes transferred (<0 = libusb error) static int vend_in(libusb_device_handle *handle, const usb_device* usb_device_desc, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, uint8_t *buf) { return libusb_control_transfer(handle, ((uint8_t)LIBUSB_REQUEST_TYPE_VENDOR | (uint8_t)LIBUSB_ENDPOINT_IN), bRequest, wValue, wIndex, buf, wLength, usb_device_desc->control_timeout); @@ -129,6 +127,18 @@ static const usb_device* get_usb_device_desc(CaptureData* capture_data) { return _get_usb_device_desc(&capture_data->status.device); } +static std::string get_serial(libusb_device_handle *handle, libusb_device_descriptor *usb_descriptor, int &curr_serial_extra_id) { + uint8_t data[SERIAL_NUMBER_SIZE]; + std::string serial_str = std::to_string(curr_serial_extra_id); + if(libusb_get_string_descriptor_ascii(handle, usb_descriptor->iSerialNumber, data, REAL_SERIAL_NUMBER_SIZE-1) >= 0) { + data[REAL_SERIAL_NUMBER_SIZE] = '\0'; + serial_str = std::string((const char*)data); + } + else + curr_serial_extra_id += 1; + return serial_str; +} + static bool insert_device(std::vector &devices_list, const usb_device* usb_device_desc, libusb_device *usb_device, libusb_device_descriptor *usb_descriptor, int &curr_serial_extra_id) { libusb_device_handle *handle = NULL; uint8_t data[SERIAL_NUMBER_SIZE]; @@ -137,13 +147,7 @@ static bool insert_device(std::vector &devices_list, const usb_de int result = libusb_open(usb_device, &handle); if(result || (handle == NULL)) return true; - std::string serial_str = std::to_string(curr_serial_extra_id); - if(libusb_get_string_descriptor_ascii(handle, usb_descriptor->iSerialNumber, data, REAL_SERIAL_NUMBER_SIZE-1) >= 0) { - data[REAL_SERIAL_NUMBER_SIZE] = '\0'; - serial_str = std::string((const char*)data); - } - else - curr_serial_extra_id += 1; + std::string serial_str = get_serial(handle, usb_descriptor, curr_serial_extra_id); if(usb_device_desc->is_3ds) devices_list.emplace_back(serial_str, "3DS", CAPTURE_CONN_USB, true, capture_get_has_3d(handle, usb_device_desc), true, HEIGHT_3DS, TOP_WIDTH_3DS + BOT_WIDTH_3DS, O3DS_SAMPLES_IN, usb_device_desc->bpp, 90, 0, 0, TOP_WIDTH_3DS, 0); else { @@ -160,10 +164,10 @@ static bool insert_device(std::vector &devices_list, const usb_de } static libusb_device_handle* usb_find_by_serial_number(const usb_device* usb_device_desc, std::string &serial_number) { - if(!usb_initialized) + if(!usb_is_initialized()) return NULL; libusb_device **usb_devices; - int num_devices = libusb_get_device_list(usb_ctx, &usb_devices); + int num_devices = libusb_get_device_list(get_usb_ctx(), &usb_devices); libusb_device_descriptor usb_descriptor{}; libusb_device_handle *final_handle = NULL; @@ -179,13 +183,7 @@ static libusb_device_handle* usb_find_by_serial_number(const usb_device* usb_dev result = libusb_open(usb_devices[i], &handle); if(result || (handle == NULL)) continue; - std::string device_serial_number = std::to_string(curr_serial_extra_id); - if(libusb_get_string_descriptor_ascii(handle, usb_descriptor.iSerialNumber, data, REAL_SERIAL_NUMBER_SIZE-1) >= 0) { - data[REAL_SERIAL_NUMBER_SIZE] = '\0'; - device_serial_number = std::string((const char*)data); - } - else - curr_serial_extra_id += 1; + std::string device_serial_number = get_serial(handle, &usb_descriptor, curr_serial_extra_id); if(serial_number == device_serial_number) { final_handle = handle; break; @@ -319,10 +317,10 @@ static void usb_3DSconvertVideoToOutput(USB3DSCaptureReceived *p_in, VideoOutput } void list_devices_usb_ds_3ds(std::vector &devices_list) { - if(!usb_initialized) + if(!usb_is_initialized()) return; libusb_device **usb_devices; - int num_devices = libusb_get_device_list(usb_ctx, &usb_devices); + int num_devices = libusb_get_device_list(get_usb_ctx(), &usb_devices); libusb_device_descriptor usb_descriptor{}; int curr_serial_extra_id_3ds = 0; @@ -342,7 +340,7 @@ void list_devices_usb_ds_3ds(std::vector &devices_list) { } bool connect_usb(bool print_failed, CaptureData* capture_data, CaptureDevice* device) { - if(!usb_initialized) + if(!usb_is_initialized()) return false; const usb_device* usb_device_desc = _get_usb_device_desc(device); libusb_device_handle *dev = usb_find_by_serial_number(usb_device_desc, device->serial_number); @@ -378,7 +376,7 @@ uint64_t usb_get_video_in_size(CaptureData* capture_data) { } void usb_capture_main_loop(CaptureData* capture_data) { - if(!usb_initialized) + if(!usb_is_initialized()) return; const usb_device* usb_device_desc = get_usb_device_desc(capture_data); int inner_curr_in = 0; @@ -429,14 +427,14 @@ void usb_capture_main_loop(CaptureData* capture_data) { } void usb_capture_cleanup(CaptureData* capture_data) { - if(!usb_initialized) + if(!usb_is_initialized()) return; const usb_device* usb_device_desc = get_usb_device_desc(capture_data); capture_end((libusb_device_handle*)capture_data->handle, usb_device_desc); } void usb_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureDevice* capture_device, bool enabled_3d) { - if(!usb_initialized) + if(!usb_is_initialized()) return; if(capture_device->is_3ds) { if(!enabled_3d) @@ -446,22 +444,11 @@ void usb_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, Cap usb_oldDSconvertVideoToOutput(&p_in->usb_received_old_ds, p_out); } -void usb_init() { - if(usb_initialized) - return; - int result = libusb_init(&usb_ctx); // open session - if (result < 0) { - usb_ctx = NULL; - usb_initialized = false; - return; - } - usb_initialized = true; +void usb_ds_3ds_init() { + return usb_init(); } -void usb_close() { - if(usb_initialized) - libusb_exit(usb_ctx); - usb_ctx = NULL; - usb_initialized = false; +void usb_ds_3ds_close() { + usb_close(); } diff --git a/source/usb_generic.cpp b/source/usb_generic.cpp new file mode 100644 index 0000000..675f866 --- /dev/null +++ b/source/usb_generic.cpp @@ -0,0 +1,33 @@ +#include "usb_generic.hpp" + +static bool usb_initialized = false; +static libusb_context* usb_ctx = NULL; // libusb session context + +void usb_init() { + if(usb_initialized) + return; + int result = libusb_init(&usb_ctx); // open session + if (result < 0) { + usb_ctx = NULL; + usb_initialized = false; + return; + } + usb_initialized = true; + return; +} + +void usb_close() { + if(usb_initialized) { + libusb_exit(usb_ctx); + } + usb_ctx = NULL; + usb_initialized = false; +} + +bool usb_is_initialized() { + return usb_initialized; +} + +libusb_context* get_usb_ctx() { + return usb_ctx; +} diff --git a/source/usb_is_nitro.cpp b/source/usb_is_nitro.cpp new file mode 100644 index 0000000..b632fb6 --- /dev/null +++ b/source/usb_is_nitro.cpp @@ -0,0 +1,299 @@ +#include "frontend.hpp" +#include "usb_is_nitro.hpp" + +#include +#include +#include +#include +#include + +// Code based off of Gericom's sample code. Distributed under the MIT License. Copyright (c) 2024 Gericom +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#define USB_PACKET_LIMIT 0x2000 + +#define REG_USB_DMA_CONTROL_2 0x0C000028 +#define REG_USB_BIU_CONTROL_2 0x0C0000A4 + +enum is_nitro_packet_dir { + IS_NITRO_PACKET_DIR_WRITE = 0x10, + IS_NITRO_PACKET_DIR_READ = 0x11 +}; + +enum is_nitro_packet_type { + IS_NITRO_PACKET_TYPE_COMMAND = 0, + IS_NITRO_PACKET_TYPE_EMU_MEMORY = 1, + IS_NITRO_PACKET_TYPE_AGB_SRAM = 2, + IS_NITRO_PACKET_TYPE_CAPTURE = 3, + IS_NITRO_PACKET_TYPE_AGB_CART_ROM = 4, + IS_NITRO_PACKET_TYPE_AGB_BUS2 = 5 +}; + +enum is_nitro_command { + IS_NITRO_CMD_GET_SERIAL = 0x13, + IS_NITRO_CMD_READ_NEC_MEM = 0x17, + IS_NITRO_CMD_WRITE_NEC_MEM = 0x24, + IS_NITRO_CMD_SET_READ_NEC_MEM = 0x25, + IS_NITRO_CMD_AD = 0xAD, +}; + +#pragma pack(push, 1) +struct PACKED is_nitro_nec_packet_header { + uint8_t command; + uint8_t unit_size; + uint16_t count; + uint32_t address; +}; + +struct PACKED is_nitro_packet_header { + uint16_t command; + uint8_t direction; + uint8_t type; + uint32_t address; + uint32_t length; + uint32_t padding; +}; +#pragma pack(pop) + +const is_nitro_usb_device usb_is_nitro_desc = { +.vid = 0x0f6e, .pid = 0x0404, +.default_config = 1, .default_interface = 0, +.bulk_timeout = 500, +.ep2_in = 2 | LIBUSB_ENDPOINT_IN, .ep1_out = 1 | LIBUSB_ENDPOINT_OUT, +.product_id = 2, .manufacturer_id = 1 +}; + +static void fix_endianness_header(is_nitro_packet_header* header) { + header->command = to_le(header->command); + header->address = to_le(header->address); + header->length = to_le(header->length); +} + +static void fix_endianness_header(is_nitro_nec_packet_header* header) { + header->count = to_le(header->count); + header->address = to_le(header->address); +} + +// Write to bulk endpoint. Returns libusb error code +static int bulk_out(libusb_device_handle *handle, const is_nitro_usb_device* usb_device_desc, uint8_t *buf, int length, int *transferred) { + return libusb_bulk_transfer(handle, usb_device_desc->ep1_out, buf, length, transferred, usb_device_desc->bulk_timeout); +} + +// Read from bulk endpoint. Returns libusb error code +static int bulk_in(libusb_device_handle *handle, const is_nitro_usb_device* usb_device_desc, uint8_t *buf, int length, int *transferred) { + return libusb_bulk_transfer(handle, usb_device_desc->ep2_in, buf, length, transferred, usb_device_desc->bulk_timeout); +} + +static int SendWritePacket(libusb_device_handle *handle, uint16_t command, is_nitro_packet_type type, uint32_t address, uint8_t* buf, int length) { + is_nitro_packet_header header; + uint8_t single_usb_packet[USB_PACKET_LIMIT]; + int single_packet_covered_size = USB_PACKET_LIMIT - sizeof(header); + int num_iters = (length + single_packet_covered_size - 1) / single_packet_covered_size; + if(!num_iters) + num_iters = 1; + for(int i = 0; i < num_iters; i++) { + int transfer_size = length - (i * single_packet_covered_size); + if(transfer_size > single_packet_covered_size) + transfer_size = single_packet_covered_size; + + header.command = command; + header.direction = IS_NITRO_PACKET_DIR_WRITE; + header.type = type; + header.address = address; + header.length = transfer_size; + header.padding = 0; + fix_endianness_header(&header); + for(int j = 0; j < sizeof(is_nitro_packet_header); j++) + single_usb_packet[j] = ((uint8_t*)&header)[j]; + for(int j = 0; j < transfer_size; j++) + single_usb_packet[sizeof(is_nitro_packet_header) + j] = buf[(i * single_packet_covered_size) + j]; + int num_bytes = 0; + int ret = bulk_out(handle, &usb_is_nitro_desc, single_usb_packet, transfer_size + sizeof(is_nitro_packet_header), &num_bytes); + if(ret < 0) + return ret; + if(num_bytes != (transfer_size + sizeof(is_nitro_packet_header))) + return LIBUSB_ERROR_INTERRUPTED; + } + return LIBUSB_SUCCESS; +} + +static int SendReadPacket(libusb_device_handle *handle, uint16_t command, is_nitro_packet_type type, uint32_t address, uint8_t* buf, int length) { + is_nitro_packet_header header; + int single_packet_covered_size = USB_PACKET_LIMIT; + int num_iters = (length + single_packet_covered_size - 1) / single_packet_covered_size; + for(int i = 0; i < num_iters; i++) { + int transfer_size = length - (i * single_packet_covered_size); + if(transfer_size > single_packet_covered_size) + transfer_size = single_packet_covered_size; + + header.command = command; + header.direction = IS_NITRO_PACKET_DIR_READ; + header.type = type; + header.address = address; + header.length = transfer_size; + header.padding = 0; + fix_endianness_header(&header); + int num_bytes = 0; + int ret = bulk_out(handle, &usb_is_nitro_desc, (uint8_t*)&header, sizeof(is_nitro_packet_header), &num_bytes); + if(ret < 0) + return ret; + if(num_bytes != sizeof(is_nitro_packet_header)) + return LIBUSB_ERROR_INTERRUPTED; + ret = bulk_in(handle, &usb_is_nitro_desc, buf + (i * single_packet_covered_size), transfer_size, &num_bytes); + if(ret < 0) + return ret; + if(num_bytes != transfer_size) + return LIBUSB_ERROR_INTERRUPTED; + } + return LIBUSB_SUCCESS; +} + +int SendReadCommand(libusb_device_handle *handle, uint16_t command, uint8_t* buf, int length) { + return SendReadPacket(handle, command, IS_NITRO_PACKET_TYPE_COMMAND, 0, buf, length); +} + +int SendWriteCommand(libusb_device_handle *handle, uint16_t command, uint8_t* buf, int length) { + return SendWritePacket(handle, command, IS_NITRO_PACKET_TYPE_COMMAND, 0, buf, length); +} + +int GetDeviceSerial(libusb_device_handle *handle, uint8_t* buf) { + return SendReadCommand(handle, IS_NITRO_CMD_GET_SERIAL, buf, IS_NITRO_REAL_SERIAL_NUMBER_SIZE); +} + +int ReadNecMem(libusb_device_handle *handle, uint32_t address, uint8_t unit_size, uint8_t* buf, int count) { + is_nitro_nec_packet_header header; + header.command = IS_NITRO_CMD_SET_READ_NEC_MEM; + header.unit_size = unit_size; + header.count = count; + header.address = address; + fix_endianness_header(&header); + int ret = SendWriteCommand(handle, header.command, (uint8_t*)&header, sizeof(is_nitro_nec_packet_header)); + if(ret < 0) + return ret; + return SendReadCommand(handle, IS_NITRO_CMD_READ_NEC_MEM, buf, count * unit_size); +} + +int ReadNecMemU16(libusb_device_handle *handle, uint32_t address, uint16_t* out) { + uint8_t buffer[2]; + int ret = ReadNecMem(handle, address, 2, buffer, 1); + if(ret < 0) + return ret; + *out = buffer[0] | (buffer[1] << 8); + return 0; +} + +int ReadNecMemU32(libusb_device_handle *handle, uint32_t address, uint32_t* out) { + uint8_t buffer[4]; + int ret = ReadNecMem(handle, address, 2, buffer, 2); + if(ret < 0) + return ret; + *out = buffer[0] | (buffer[1] << 8) | (buffer[2] << 16) | (buffer[3] << 24); + return 0; +} + +int WriteNecMem(libusb_device_handle *handle, uint32_t address, uint8_t unit_size, uint8_t* buf, int count) { + uint8_t* buffer = new uint8_t[(count * unit_size) + sizeof(is_nitro_nec_packet_header)]; + is_nitro_nec_packet_header header; + header.command = IS_NITRO_CMD_WRITE_NEC_MEM; + header.unit_size = unit_size; + header.count = count; + header.address = address; + fix_endianness_header(&header); + for(int i = 0; i < sizeof(is_nitro_nec_packet_header); i++) + buffer[i] = ((uint8_t*)&header)[i]; + for(int i = 0; i < count * unit_size; i++) + buffer[i + sizeof(is_nitro_nec_packet_header)] = buf[i]; + int ret = SendWriteCommand(handle, header.command, buffer, (count * unit_size) + sizeof(is_nitro_nec_packet_header)); + delete []buffer; + return ret; +} + +int WriteNecMemU16(libusb_device_handle *handle, uint32_t address, uint16_t value) { + uint8_t buffer[2]; + buffer[0] = value & 0xFF; + buffer[1] = (value >> 8) & 0xFF; + return WriteNecMem(handle, address, 2, buffer, 1); +} + +int WriteNecMemU32(libusb_device_handle *handle, uint32_t address, uint32_t value) { + uint8_t buffer[4]; + buffer[0] = value & 0xFF; + buffer[1] = (value >> 8) & 0xFF; + buffer[2] = (value >> 16) & 0xFF; + buffer[3] = (value >> 24) & 0xFF; + return WriteNecMem(handle, address, 2, buffer, 2); +} + +int DisableLca2(libusb_device_handle *handle) { + int ret = WriteNecMemU16(handle, 0x00805180, 0); + if(ret < 0) + return ret; + ret = WriteNecMemU16(handle, 0xF84000A, 1); + if(ret < 0) + return ret; + default_sleep(2000); + return WriteNecMemU16(handle, 0xF84000A, 0); +} + +int StartUsbCaptureDma(libusb_device_handle *handle) { + int ret = WriteNecMemU16(handle, REG_USB_DMA_CONTROL_2, 2); + if(ret < 0) + return ret; + return WriteNecMemU16(handle, REG_USB_BIU_CONTROL_2, 1); +} + +int StopUsbCaptureDma(libusb_device_handle *handle) { + int ret = WriteNecMemU16(handle, REG_USB_DMA_CONTROL_2, 0); + if(ret < 0) + return ret; + return WriteNecMemU16(handle, REG_USB_BIU_CONTROL_2, 0); +} + +int SetForwardFrameCount(libusb_device_handle *handle, uint16_t count) { + if(!count) + return LIBUSB_ERROR_INTERRUPTED; + count -= 1; + return WriteNecMemU32(handle, 0x800000C, (count >> 8) | ((count & 0xFF) << 16)); +} + +int GetFrameCounter(libusb_device_handle *handle, uint16_t* out) { + uint32_t counter = 0; + int ret = ReadNecMemU32(handle, 0x08000028, &counter); + if(ret < 0) + return ret; + *out = (counter & 0xFF) | ((counter & 0xFF0000) >> 8); + return ret; +} + +int UpdateFrameForwardConfig(libusb_device_handle *handle, uint8_t value) { + return WriteNecMemU16(handle, 0x0800000A, value); +} + +int UpdateFrameForwardEnable(libusb_device_handle *handle, uint8_t value) { + return WriteNecMemU16(handle, 0x08000008, value); +} + +int ReadFrame(libusb_device_handle *handle, uint8_t* buf, int length) { + int num_bytes = 0; + int ret = bulk_in(handle, &usb_is_nitro_desc, buf, length, &num_bytes); + if(num_bytes != length) + return LIBUSB_ERROR_INTERRUPTED; + return ret; +} + diff --git a/source/usb_is_nitro_capture.cpp b/source/usb_is_nitro_capture.cpp new file mode 100644 index 0000000..8fe2c25 --- /dev/null +++ b/source/usb_is_nitro_capture.cpp @@ -0,0 +1,344 @@ +#include "devicecapture.hpp" +#include "usb_is_nitro.hpp" +#include "usb_is_nitro_capture.hpp" +#include "usb_generic.hpp" + +#include +#include +#include +#include +#include + +// Code based off of Gericom's sample code. Distributed under the MIT License. Copyright (c) 2024 Gericom +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: + +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. + +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +#define SERIAL_NUMBER_SIZE (IS_NITRO_REAL_SERIAL_NUMBER_SIZE + 1) + +#define NUM_CONSECUTIVE_FRAMES 32 + +enum usb_capture_status { + USB_CAPTURE_SUCCESS = 0, + USB_CAPTURE_SKIP, + USB_CAPTURE_PIPE_ERROR, + USB_CAPTURE_FRAMEINFO_ERROR, + USB_CAPTURE_ERROR +}; + +static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames); +static int StartCapture(libusb_device_handle *handle, int *out_frame_count); +static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames); + +static std::string get_serial(const is_nitro_usb_device* usb_device_desc, libusb_device_handle *handle, int &curr_serial_extra_id) { + uint8_t data[SERIAL_NUMBER_SIZE]; + std::string serial_str = std::to_string(curr_serial_extra_id); + bool conn_success = true; + if(libusb_set_configuration(handle, usb_device_desc->default_config) != LIBUSB_SUCCESS) + conn_success = false; + //if(libusb_reset_device(handle) != LIBUSB_SUCCESS) + // conn_success = false; + if(conn_success && libusb_claim_interface(handle, usb_device_desc->default_interface) != LIBUSB_SUCCESS) + conn_success = false; + if(conn_success && EndCapture(handle, false, 0) != LIBUSB_SUCCESS) + conn_success = false; + if(conn_success && (GetDeviceSerial(handle, data) == LIBUSB_SUCCESS)) { + data[IS_NITRO_REAL_SERIAL_NUMBER_SIZE] = '\0'; + serial_str = std::string((const char*)data); + } + else + curr_serial_extra_id += 1; + if(conn_success) + libusb_release_interface(handle, usb_device_desc->default_interface); + return serial_str; +} + +static bool insert_device(std::vector &devices_list, const is_nitro_usb_device* usb_device_desc, libusb_device *usb_device, libusb_device_descriptor *usb_descriptor, int &curr_serial_extra_id) { + libusb_device_handle *handle = NULL; + if((usb_descriptor->idVendor != usb_device_desc->vid) || (usb_descriptor->idProduct != usb_device_desc->pid)) + return false; + if((usb_descriptor->iManufacturer != usb_device_desc->manufacturer_id) || (usb_descriptor->iProduct != usb_device_desc->product_id)) + return false; + int result = libusb_open(usb_device, &handle); + if(result || (handle == NULL)) + return true; + devices_list.emplace_back(get_serial(usb_device_desc, handle, curr_serial_extra_id), "IS Nitro Emulator", CAPTURE_CONN_IS_NITRO, false, false, false, WIDTH_DS, HEIGHT_DS + HEIGHT_DS, 0, 24, 0, 0, 0, 0, HEIGHT_DS); + libusb_close(handle); + return true; +} + +static libusb_device_handle* usb_find_by_serial_number(const is_nitro_usb_device* usb_device_desc, std::string &serial_number) { + if(!usb_is_initialized()) + return NULL; + libusb_device **usb_devices; + int num_devices = libusb_get_device_list(get_usb_ctx(), &usb_devices); + libusb_device_descriptor usb_descriptor{}; + libusb_device_handle *final_handle = NULL; + + int curr_serial_extra_id = 0; + for(int i = 0; i < num_devices; i++) { + libusb_device_handle *handle = NULL; + uint8_t data[SERIAL_NUMBER_SIZE]; + int result = libusb_get_device_descriptor(usb_devices[i], &usb_descriptor); + if(result < 0) + continue; + if((usb_descriptor.idVendor != usb_device_desc->vid) || (usb_descriptor.idProduct != usb_device_desc->pid)) + continue; + if((usb_descriptor.iManufacturer != usb_device_desc->manufacturer_id) || (usb_descriptor.iProduct != usb_device_desc->product_id)) + continue; + result = libusb_open(usb_devices[i], &handle); + if(result || (handle == NULL)) + continue; + std::string device_serial_number = get_serial(usb_device_desc, handle, curr_serial_extra_id); + if(serial_number == device_serial_number) { + final_handle = handle; + break; + } + libusb_close(handle); + } + + if(num_devices >= 0) + libusb_free_device_list(usb_devices, 1); + return final_handle; +} + +void list_devices_is_nitro(std::vector &devices_list) { + if(!usb_is_initialized()) + return; + libusb_device **usb_devices; + int num_devices = libusb_get_device_list(get_usb_ctx(), &usb_devices); + libusb_device_descriptor usb_descriptor{}; + + int curr_serial_extra_id_is_nitro_emulator = 0; + for(int i = 0; i < num_devices; i++) { + int result = libusb_get_device_descriptor(usb_devices[i], &usb_descriptor); + if(result < 0) + continue; + if(insert_device(devices_list, &usb_is_nitro_desc, usb_devices[i], &usb_descriptor, curr_serial_extra_id_is_nitro_emulator)) + continue; + } + + if(num_devices >= 0) + libusb_free_device_list(usb_devices, 1); +} + +static void is_nitro_connection_end(libusb_device_handle *dev, bool interface_claimed = true) { + if(interface_claimed) + libusb_release_interface(dev, usb_is_nitro_desc.default_interface); + libusb_close(dev); +} + +bool is_nitro_connect_usb(bool print_failed, CaptureData* capture_data, CaptureDevice* device) { + if(!usb_is_initialized()) + return false; + libusb_device_handle *dev = usb_find_by_serial_number(&usb_is_nitro_desc, device->serial_number); + if(!dev) { + capture_error_print(true, capture_data, "Device not found"); + return false; + } + if(libusb_set_configuration(dev, usb_is_nitro_desc.default_config) != LIBUSB_SUCCESS) { + capture_error_print(true, capture_data, "Configuration failed"); + is_nitro_connection_end(dev, false); + return false; + } + if(libusb_claim_interface(dev, usb_is_nitro_desc.default_interface) != LIBUSB_SUCCESS) { + capture_error_print(true, capture_data, "Interface claim failed"); + is_nitro_connection_end(dev, false); + return false; + } + capture_data->handle = (void*)dev; + + return true; +} + +static uint64_t _is_nitro_emulator_get_video_in_size() { + return sizeof(ISNitroEmulatorVideoInputData); +} + +static uint64_t get_capture_size() { + return sizeof(ISNitroCaptureReceived); +} + +uint64_t usb_is_nitro_emulator_get_video_in_size(CaptureData* capture_data) { + return _is_nitro_emulator_get_video_in_size(); +} + +static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames) { + ISNitroEmulatorVideoInputData video_in_buffer; + for (int i = start_frames; i < num_frames; i++) { + int ret = ReadFrame(handle, (uint8_t*)&video_in_buffer, _is_nitro_emulator_get_video_in_size()); + if(ret < 0) + return ret; + } + return LIBUSB_SUCCESS; +} + +static int StartCapture(libusb_device_handle *handle, int *out_frame_count) { + int ret = DisableLca2(handle); + if(ret < 0) + return ret; + ret = UpdateFrameForwardConfig(handle, IS_NITRO_FORWARD_CONFIG_COLOR_RGB24 | IS_NITRO_FORWARD_CONFIG_MODE_BOTH | IS_NITRO_FORWARD_CONFIG_RATE_FULL); + if(ret < 0) + return ret; + ret = SetForwardFrameCount(handle, NUM_CONSECUTIVE_FRAMES); + if(ret < 0) + return ret; + ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_DISABLE); + if(ret < 0) + return ret; + uint16_t oldFrameCount; + ret = GetFrameCounter(handle, &oldFrameCount); + if(ret < 0) + return ret; + ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_ENABLE | IS_NITRO_FORWARD_ENABLE_RESTART); + if(ret < 0) + return ret; + + uint16_t newFrameCount; + while (true) { + ret = GetFrameCounter(handle, &newFrameCount); + if(ret < 0) + return ret; + if (newFrameCount < 64 && newFrameCount != oldFrameCount && newFrameCount != oldFrameCount + 1) { + break; + } + } + + ret = StartUsbCaptureDma(handle); + if(ret < 0) + return ret; + ret = drain_frames(handle, newFrameCount, 0); + *out_frame_count = newFrameCount; + return ret; +} + +static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames) { + int ret = 0; + if(do_drain_frames) + ret = drain_frames(handle, NUM_CONSECUTIVE_FRAMES, start_frames); + if(ret < 0) + return ret; + ret = StopUsbCaptureDma(handle); + if(ret < 0) + return ret; + return UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_DISABLE); +} + +int reset_capture_frames(libusb_device_handle* handle, int &frame_counter, uint16_t &total_frame_counter) { + total_frame_counter += 1; + frame_counter += 1; + + if(frame_counter == NUM_CONSECUTIVE_FRAMES) { + int ret = StopUsbCaptureDma(handle); + if(ret < 0) + return ret; + + /* + uint16_t internalFrameCount = -1; + int diff; + do { + if (internalFrameCount != -1) + default_sleep(8000); + ret = GetFrameCounter(handle, &internalFrameCount); + if(ret < 0) + return ret; + + diff = internalFrameCount - total_frame_counter; + if(diff > 32768) + diff -= 1 << 16; + } while(diff <= 0); + */ + + ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_ENABLE); + if(ret < 0) + return ret; + frame_counter = 0; + ret = StartUsbCaptureDma(handle); + if(ret < 0) + return ret; + } + return LIBUSB_SUCCESS; +} + +void is_nitro_capture_main_loop(CaptureData* capture_data) { + if(!usb_is_initialized()) + return; + libusb_device_handle *handle = (libusb_device_handle*)capture_data->handle; + int frame_counter = 0; + int ret = StartCapture(handle, &frame_counter); + uint16_t total_frame_counter = frame_counter; + if(ret < 0) { + capture_error_print(true, capture_data, "Capture Start: Failed"); + return; + } + int inner_curr_in = 0; + auto clock_start = std::chrono::high_resolution_clock::now(); + + while(capture_data->status.connected && capture_data->status.running) { + ret = ReadFrame(handle, (uint8_t*)&capture_data->capture_buf[inner_curr_in], _is_nitro_emulator_get_video_in_size()); + if(ret < 0) { + capture_error_print(true, capture_data, "Disconnected: Read error"); + break; + } + + const auto curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - clock_start; + ret = reset_capture_frames(handle, frame_counter, total_frame_counter); + if(ret < 0) { + capture_error_print(true, capture_data, "Disconnected: Frame counter reset error"); + break; + } + clock_start = curr_time; + capture_data->time_in_buf[inner_curr_in] = diff.count(); + capture_data->read[inner_curr_in] = _is_nitro_emulator_get_video_in_size(); + + inner_curr_in = (inner_curr_in + 1) % NUM_CONCURRENT_DATA_BUFFERS; + if(capture_data->status.cooldown_curr_in) + capture_data->status.cooldown_curr_in = capture_data->status.cooldown_curr_in - 1; + capture_data->status.curr_in = inner_curr_in; + capture_data->status.video_wait.unlock(); + capture_data->status.audio_wait.unlock(); + } + EndCapture(handle, true, frame_counter); +} + +void usb_is_nitro_capture_cleanup(CaptureData* capture_data) { + if(!usb_is_initialized()) + return; + is_nitro_connection_end((libusb_device_handle*)capture_data->handle); +} + +void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureDevice* capture_device) { + if(!usb_is_initialized()) + return; + for(int i = 0; i < IN_VIDEO_HEIGHT_DS; i++) + for(int j = 0; j < IN_VIDEO_WIDTH_DS; j++) { + int pixel = (i * IN_VIDEO_WIDTH_DS) + j; + p_out->screen_data[pixel][0] = p_in->is_nitro_capture_received.video_in.screen_data[pixel][2]; + p_out->screen_data[pixel][1] = p_in->is_nitro_capture_received.video_in.screen_data[pixel][1]; + p_out->screen_data[pixel][2] = p_in->is_nitro_capture_received.video_in.screen_data[pixel][0]; + } +} + +void usb_is_nitro_init() { + return usb_init(); +} + +void usb_is_nitro_close() { + usb_close(); +} + diff --git a/source/utils.cpp b/source/utils.cpp index da54b6f..60ee412 100755 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -21,13 +21,44 @@ #define APP_VERSION_LETTER M #endif +static bool checked_be_once = false; +static bool _is_be = false; + bool is_big_endian(void) { + if(checked_be_once) + return _is_be; union { uint32_t i; char c[4]; } value = {0x01020304}; - return value.c[0] == 1; + checked_be_once = true; + _is_be = value.c[0] == 1; + return _is_be; +} + +uint32_t to_le(uint32_t value) { + if(is_big_endian()) + value = ((value & 0xFF) << 24) | ((value & 0xFF00) << 8) | ((value & 0xFF0000) >> 8) | ((value & 0xFF000000) >> 24); + return value; +} + +uint32_t to_be(uint32_t value) { + if(!is_big_endian()) + value = ((value & 0xFF) << 24) | ((value & 0xFF00) << 8) | ((value & 0xFF0000) >> 8) | ((value & 0xFF000000) >> 24); + return value; +} + +uint16_t to_le(uint16_t value) { + if(is_big_endian()) + value = ((value & 0xFF) << 8) | ((value & 0xFF00) >> 8); + return value; +} + +uint16_t to_be(uint16_t value) { + if(!is_big_endian()) + value = ((value & 0xFF) << 8) | ((value & 0xFF00) >> 8); + return value; } std::string get_version_string(bool get_letter) { diff --git a/usb_rules/51-isnitro.rules b/usb_rules/51-isnitro.rules new file mode 100644 index 0000000..8f81e24 --- /dev/null +++ b/usb_rules/51-isnitro.rules @@ -0,0 +1 @@ +SUBSYSTEM=="usb", ATTRS{idVendor}=="0f6e", ATTRS{idProduct}=="0404", MODE="0666"