diff --git a/CMakeLists.txt b/CMakeLists.txt index f3ca06e..1f6044d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ endif() include(FetchContent) FetchContent_Declare(SFML GIT_REPOSITORY https://github.com/SFML/SFML.git - GIT_TAG macos_fullscreen_v2.6) + GIT_TAG 2.6.x) FetchContent_Declare(libusb1 GIT_REPOSITORY https://github.com/libusb/libusb-cmake.git @@ -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 source/usb_is_nitro.cpp source/usb_is_nitro_capture.cpp source/usb_generic.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 source/Menus/ISNitroMenu.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 075efbe..336ccbc 100755 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ 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). +IS Nitro Emulator support (for newer revisions) is also present. Though results may vary (and the amount of video delay may be significantly higher based on the cable used). ## Features diff --git a/include/Menus/ISNitroMenu.hpp b/include/Menus/ISNitroMenu.hpp new file mode 100644 index 0000000..153e088 --- /dev/null +++ b/include/Menus/ISNitroMenu.hpp @@ -0,0 +1,39 @@ +#ifndef __ISNMENU_HPP +#define __ISNMENU_HPP + +#include "OptionSelectionMenu.hpp" +#include + +#include "TextRectangle.hpp" +#include "sfml_gfx_structs.hpp" +#include "display_structs.hpp" +#include "capture_structs.hpp" + +enum ISNitroMenuOutAction{ + ISN_MENU_NO_ACTION, + ISN_MENU_BACK, + ISN_MENU_DELAY, + ISN_MENU_TYPE_DEC, + ISN_MENU_TYPE_INC, +}; + +class ISNitroMenu : public OptionSelectionMenu { +public: + ISNitroMenu(bool font_load_success, sf::Font &text_font); + ~ISNitroMenu(); + void prepare(float scaling_factor, int view_size_x, int view_size_y, CaptureStatus* capture_status); + void insert_data(); + ISNitroMenuOutAction selected_index = ISNitroMenuOutAction::ISN_MENU_NO_ACTION; + void reset_output_option(); +protected: + bool is_option_selectable(int index, int action); + bool is_option_inc_dec(int index); + void set_output_option(int index, int action); + int get_num_options(); + std::string get_string_option(int index, int action); + void class_setup(); +private: + int *options_indexes; + int num_enabled_options; +}; +#endif diff --git a/include/Menus/MainMenu.hpp b/include/Menus/MainMenu.hpp index 5c3c431..0590e4b 100755 --- a/include/Menus/MainMenu.hpp +++ b/include/Menus/MainMenu.hpp @@ -7,6 +7,7 @@ #include "TextRectangle.hpp" #include "sfml_gfx_structs.hpp" #include "display_structs.hpp" +#include "capture_structs.hpp" enum MainMenuOutAction{ MAIN_MENU_NO_ACTION, @@ -24,6 +25,7 @@ enum MainMenuOutAction{ MAIN_MENU_EXTRA_SETTINGS, MAIN_MENU_SHUTDOWN, MAIN_MENU_SHORTCUT_SETTINGS, + MAIN_MENU_ISN_SETTINGS, }; class MainMenu : public OptionSelectionMenu { @@ -31,7 +33,7 @@ public: MainMenu(bool font_load_success, sf::Font &text_font); ~MainMenu(); void prepare(float scaling_factor, int view_size_x, int view_size_y, bool connected); - void insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_mode, bool enable_shortcuts); + void insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_mode, bool enable_shortcuts, CaptureConnectionType cc_type, bool connected); MainMenuOutAction selected_index = MainMenuOutAction::MAIN_MENU_NO_ACTION; void reset_output_option(); protected: diff --git a/include/capture_structs.hpp b/include/capture_structs.hpp index 81ca681..b2130b6 100755 --- a/include/capture_structs.hpp +++ b/include/capture_structs.hpp @@ -19,6 +19,7 @@ #define EXTRA_DATA_BUFFER_FTD3XX_SIZE (1 << 10) enum CaptureConnectionType { CAPTURE_CONN_FTD3, CAPTURE_CONN_USB, CAPTURE_CONN_FTD2, CAPTURE_CONN_IS_NITRO }; +enum CaptureScreensType { CAPTURE_SCREENS_BOTH, CAPTURE_SCREENS_TOP, CAPTURE_SCREENS_BOTTOM, CAPTURE_SCREENS_ENUM_END }; #pragma pack(push, 1) @@ -125,18 +126,21 @@ struct CaptureStatus { CaptureDevice device; std::string error_text; bool new_error_text; - bool enabled_3d = false; volatile int curr_in = 0; volatile int cooldown_curr_in = FIX_PARTIAL_FIRST_FRAME_NUM; volatile bool connected = false; volatile bool running = true; volatile bool close_success = true; + volatile int curr_delay = 0; + bool enabled_3d = false; + CaptureScreensType capture_type; ConsumerMutex video_wait; ConsumerMutex audio_wait; }; struct CaptureData { void* handle; + CaptureScreensType capture_type[NUM_CONCURRENT_DATA_BUFFERS]; uint64_t read[NUM_CONCURRENT_DATA_BUFFERS]; CaptureReceived capture_buf[NUM_CONCURRENT_DATA_BUFFERS]; double time_in_buf[NUM_CONCURRENT_DATA_BUFFERS]; diff --git a/include/conversions.hpp b/include/conversions.hpp index 0e3b98b..370f143 100755 --- a/include/conversions.hpp +++ b/include/conversions.hpp @@ -5,6 +5,6 @@ #include "capture_structs.hpp" #include "display_structs.hpp" -void convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureData* capture_data); -void convertAudioToOutput(CaptureReceived *p_in, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data); +void convertVideoToOutput(int index, VideoOutputData *p_out, CaptureData* capture_data); +void convertAudioToOutput(int index, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data); #endif diff --git a/include/display_structs.hpp b/include/display_structs.hpp index 20caaaf..d696fcc 100755 --- a/include/display_structs.hpp +++ b/include/display_structs.hpp @@ -7,7 +7,7 @@ enum ScreenType { TOP, BOTTOM, JOINT }; enum BottomRelativePosition { UNDER_TOP, LEFT_TOP, ABOVE_TOP, RIGHT_TOP, BOT_REL_POS_END }; enum NonIntegerScalingModes { SMALLER_PRIORITY, INVERSE_PROPORTIONAL_PRIORITY, EQUAL_PRIORITY, PROPORTIONAL_PRIORITY, BIGGER_PRIORITY, END_NONINT_SCALE_MODES }; -enum CurrMenuType { DEFAULT_MENU_TYPE, CONNECT_MENU_TYPE, MAIN_MENU_TYPE, VIDEO_MENU_TYPE, AUDIO_MENU_TYPE, CROP_MENU_TYPE, TOP_PAR_MENU_TYPE, BOTTOM_PAR_MENU_TYPE, ROTATION_MENU_TYPE, OFFSET_MENU_TYPE, BFI_MENU_TYPE, LOAD_MENU_TYPE, SAVE_MENU_TYPE, RESOLUTION_MENU_TYPE, EXTRA_MENU_TYPE, STATUS_MENU_TYPE, LICENSES_MENU_TYPE, RELATIVE_POS_MENU_TYPE, SHORTCUTS_MENU_TYPE, ACTION_SELECTION_MENU_TYPE, SCALING_RATIO_MENU_TYPE }; +enum CurrMenuType { DEFAULT_MENU_TYPE, CONNECT_MENU_TYPE, MAIN_MENU_TYPE, VIDEO_MENU_TYPE, AUDIO_MENU_TYPE, CROP_MENU_TYPE, TOP_PAR_MENU_TYPE, BOTTOM_PAR_MENU_TYPE, ROTATION_MENU_TYPE, OFFSET_MENU_TYPE, BFI_MENU_TYPE, LOAD_MENU_TYPE, SAVE_MENU_TYPE, RESOLUTION_MENU_TYPE, EXTRA_MENU_TYPE, STATUS_MENU_TYPE, LICENSES_MENU_TYPE, RELATIVE_POS_MENU_TYPE, SHORTCUTS_MENU_TYPE, ACTION_SELECTION_MENU_TYPE, SCALING_RATIO_MENU_TYPE, ISN_MENU_TYPE }; struct ScreenInfo { bool is_blurred; diff --git a/include/frontend.hpp b/include/frontend.hpp index 506cc1d..eba7bd6 100755 --- a/include/frontend.hpp +++ b/include/frontend.hpp @@ -30,6 +30,7 @@ #include "ShortcutMenu.hpp" #include "ActionSelectionMenu.hpp" #include "ScalingRatioMenu.hpp" +#include "ISNitroMenu.hpp" #include "display_structs.hpp" #include "WindowCommands.hpp" @@ -68,6 +69,7 @@ public: int check_connection_menu_result(); void end_connection_menu(); void update_ds_3ds_connection(bool changed_type); + void update_capture_specific_settings(); void update_save_menu(); void print_notification(std::string text, TextKind kind = TEXT_KIND_NORMAL); @@ -146,6 +148,7 @@ private: LicenseMenu *license_menu; ShortcutMenu *shortcut_menu; ActionSelectionMenu *action_selection_menu; + ISNitroMenu *is_nitro_menu; ScalingRatioMenu *scaling_ratio_menu; std::vector possible_crops; std::vector possible_crops_ds; @@ -219,6 +222,7 @@ private: void fast_poll_change(); void padding_change(); void game_crop_enable_change(); + void is_nitro_capture_type_change(bool positive); void crop_value_change(int new_crop_value, bool do_print_notification = true, bool do_cycle = true); void par_value_change(int new_par_value, bool is_top); void offset_change(float &value, float change); @@ -273,7 +277,7 @@ private: void setWinSize(bool is_main_thread); bool can_setup_menu(); void setup_no_menu(); - void setup_main_menu(bool reset_data = true); + void setup_main_menu(bool reset_data = true, bool skip_setup_check = false); void setup_video_menu(bool reset_data = true); void setup_crop_menu(bool reset_data = true); void setup_par_menu(bool is_top, bool reset_data = true); @@ -290,6 +294,7 @@ private: void setup_licenses_menu(bool reset_data = true); void setup_relative_pos_menu(bool reset_data = true); void setup_scaling_ratio_menu(bool reset_data = true); + void setup_is_nitro_menu(bool reset_data = true); void update_connection(); }; @@ -315,7 +320,8 @@ void get_par_size(int &width, int &height, float multiplier_factor, const PARDat float get_par_mult_factor(float width, float height, float max_width, float max_height, const PARData *correction_factor, bool is_rotated); 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 update_connected_specific_settings(FrontendData* frontend_data, const CaptureDevice &cc_device); void screen_display_thread(WindowScreen *screen); std::string get_name_non_int_mode(NonIntegerScalingModes input); -void default_sleep(int wanted_ms = -1); +void default_sleep(float wanted_ms = -1); #endif diff --git a/include/usb_is_nitro_capture.hpp b/include/usb_is_nitro_capture.hpp index ddaedec..dc1fb74 100644 --- a/include/usb_is_nitro_capture.hpp +++ b/include/usb_is_nitro_capture.hpp @@ -13,7 +13,7 @@ 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); +void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureScreensType capture_type); uint64_t usb_is_nitro_emulator_get_video_in_size(CaptureData* capture_data); void usb_is_nitro_init(); void usb_is_nitro_close(); diff --git a/source/Menus/ISNitroMenu.cpp b/source/Menus/ISNitroMenu.cpp new file mode 100644 index 0000000..bea14e3 --- /dev/null +++ b/source/Menus/ISNitroMenu.cpp @@ -0,0 +1,136 @@ +#include "ISNitroMenu.hpp" + +#define NUM_TOTAL_MENU_OPTIONS (sizeof(pollable_options)/sizeof(pollable_options[0])) + +struct ISNitroMenuOptionInfo { + const std::string base_name; + const std::string false_name; + const bool is_selectable; + const bool is_inc; + const std::string dec_str; + const std::string inc_str; + const ISNitroMenuOutAction inc_out_action; + const ISNitroMenuOutAction out_action; +}; + +static const ISNitroMenuOptionInfo is_nitro_delay_option = { +.base_name = "Delay", .false_name = "", .is_selectable = false, +.is_inc = false, .dec_str = "", .inc_str = "", .inc_out_action = ISN_MENU_NO_ACTION, +.out_action = ISN_MENU_DELAY}; + +static const ISNitroMenuOptionInfo is_nitro_type_option = { +.base_name = "Capture", .false_name = "", .is_selectable = true, +.is_inc = true, .dec_str = "<", .inc_str = ">", .inc_out_action = ISN_MENU_TYPE_INC, +.out_action = ISN_MENU_TYPE_DEC}; + +static const ISNitroMenuOptionInfo* pollable_options[] = { +&is_nitro_delay_option, +&is_nitro_type_option, +}; + +ISNitroMenu::ISNitroMenu(bool font_load_success, sf::Font &text_font) : OptionSelectionMenu(){ + this->options_indexes = new int[NUM_TOTAL_MENU_OPTIONS]; + this->initialize(font_load_success, text_font); + this->num_enabled_options = 0; +} + +ISNitroMenu::~ISNitroMenu() { + delete []this->options_indexes; +} + +void ISNitroMenu::class_setup() { + this->num_options_per_screen = 5; + this->min_elements_text_scaling_factor = num_options_per_screen + 2; + this->width_factor_menu = 16; + this->width_divisor_menu = 9; + this->base_height_factor_menu = 12; + this->base_height_divisor_menu = 6; + this->min_text_size = 0.3; + this->max_width_slack = 1.1; + this->menu_color = sf::Color(30, 30, 60, 192); + this->title = "IS Nitro Settings"; + this->show_back_x = true; + this->show_x = false; + this->show_title = true; +} + +void ISNitroMenu::insert_data() { + this->num_enabled_options = 0; + for(int i = 0; i < NUM_TOTAL_MENU_OPTIONS; i++) { + this->options_indexes[this->num_enabled_options] = i; + this->num_enabled_options++; + } + this->prepare_options(); +} + +void ISNitroMenu::reset_output_option() { + this->selected_index = ISNitroMenuOutAction::ISN_MENU_NO_ACTION; +} + +void ISNitroMenu::set_output_option(int index, int action) { + if(index == BACK_X_OUTPUT_OPTION) + this->selected_index = ISN_MENU_BACK; + else if((action == INC_ACTION) && this->is_option_inc_dec(index)) + this->selected_index = pollable_options[this->options_indexes[index]]->inc_out_action; + else + this->selected_index = pollable_options[this->options_indexes[index]]->out_action; +} + +int ISNitroMenu::get_num_options() { + return this->num_enabled_options; +} + +std::string ISNitroMenu::get_string_option(int index, int action) { + if((action == INC_ACTION) && this->is_option_inc_dec(index)) + return pollable_options[this->options_indexes[index]]->inc_str; + if((action == DEC_ACTION) && this->is_option_inc_dec(index)) + return pollable_options[this->options_indexes[index]]->dec_str; + if(action == FALSE_ACTION) + return pollable_options[this->options_indexes[index]]->false_name; + return pollable_options[this->options_indexes[index]]->base_name; +} + +bool ISNitroMenu::is_option_selectable(int index, int action) { + return pollable_options[this->options_indexes[index]]->is_selectable; +} + +bool ISNitroMenu::is_option_inc_dec(int index) { + return pollable_options[this->options_indexes[index]]->is_inc; +} + +static std::string get_capture_type_name(CaptureScreensType capture_type) { + switch(capture_type) { + case CAPTURE_SCREENS_TOP: + return "Top Screen"; + case CAPTURE_SCREENS_BOTTOM: + return "Bottom Screen"; + default: + return "Both Screens"; + } +} + +void ISNitroMenu::prepare(float menu_scaling_factor, int view_size_x, int view_size_y, CaptureStatus* capture_status) { + int num_pages = this->get_num_pages(); + if(this->future_data.page >= num_pages) + this->future_data.page = num_pages - 1; + int start = this->future_data.page * this->num_options_per_screen; + for(int i = 0; i < this->num_options_per_screen + 1; i++) { + int index = (i * this->single_option_multiplier) + this->elements_start_id; + if(!this->future_enabled_labels[index]) + continue; + int real_index = start + i; + int option_index = this->options_indexes[real_index]; + switch(pollable_options[option_index]->out_action) { + case ISN_MENU_DELAY: + this->labels[index]->setText(this->setTextOptionInt(real_index, capture_status->curr_delay)); + break; + case ISN_MENU_TYPE_DEC: + this->labels[index]->setText(this->setTextOptionString(real_index, get_capture_type_name(capture_status->capture_type))); + break; + default: + break; + } + } + + this->base_prepare(menu_scaling_factor, view_size_x, view_size_y); +} diff --git a/source/Menus/MainMenu.cpp b/source/Menus/MainMenu.cpp index 6166b2d..985cbc7 100755 --- a/source/Menus/MainMenu.cpp +++ b/source/Menus/MainMenu.cpp @@ -12,6 +12,7 @@ struct MainMenuOptionInfo { const bool active_bottom_screen; const bool enabled_normal_mode; const bool enabled_mono_mode; + const bool is_cc_specific; const MainMenuOutAction out_action; }; @@ -19,107 +20,114 @@ static const MainMenuOptionInfo connect_option = { .base_name = "Disconnect", .false_name = "Connect", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_OPEN}; static const MainMenuOptionInfo windowed_option = { .base_name = "Windowed Mode", .false_name = "", .active_fullscreen = true, .active_windowed_screen = false, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = false, +.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false, .out_action = MAIN_MENU_FULLSCREEN}; static const MainMenuOptionInfo fullscreen_option = { .base_name = "Fullscreen Mode", .false_name = "", .active_fullscreen = false, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = false, +.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false, .out_action = MAIN_MENU_FULLSCREEN}; static const MainMenuOptionInfo join_screens_option = { .base_name = "Join Screens", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = false, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = false, +.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false, .out_action = MAIN_MENU_SPLIT}; static const MainMenuOptionInfo split_screens_option = { .base_name = "Split Screens", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = false, .active_bottom_screen = false, -.enabled_normal_mode = true, .enabled_mono_mode = false, +.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false, .out_action = MAIN_MENU_SPLIT}; static const MainMenuOptionInfo video_settings_option = { .base_name = "Video Settings", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_VIDEO_SETTINGS}; static const MainMenuOptionInfo quit_option = { .base_name = "Quit Application", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = false, +.enabled_normal_mode = true, .enabled_mono_mode = false, .is_cc_specific = false, .out_action = MAIN_MENU_QUIT_APPLICATION}; static const MainMenuOptionInfo audio_settings_option = { .base_name = "Audio Settings", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_AUDIO_SETTINGS}; static const MainMenuOptionInfo save_profiles_option = { .base_name = "Save Profile", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_SAVE_PROFILES}; static const MainMenuOptionInfo load_profiles_option = { .base_name = "Load Profile", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_LOAD_PROFILES}; static const MainMenuOptionInfo status_option = { .base_name = "Status", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_STATUS}; static const MainMenuOptionInfo licenses_option = { .base_name = "Licenses", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_LICENSES}; static const MainMenuOptionInfo extra_settings_option = { .base_name = "Extra Settings", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = false, .enabled_mono_mode = true, +.enabled_normal_mode = false, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_EXTRA_SETTINGS}; static const MainMenuOptionInfo shortcut_option = { .base_name = "Shortcuts", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = true, .enabled_mono_mode = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_SHORTCUT_SETTINGS}; static const MainMenuOptionInfo shutdown_option = { .base_name = "Shutdown", .false_name = "", .active_fullscreen = true, .active_windowed_screen = true, .active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, -.enabled_normal_mode = false, .enabled_mono_mode = true, +.enabled_normal_mode = false, .enabled_mono_mode = true, .is_cc_specific = false, .out_action = MAIN_MENU_SHUTDOWN}; +static const MainMenuOptionInfo isn_settings_option = { +.base_name = "Is Nitro Settings", .false_name = "", +.active_fullscreen = true, .active_windowed_screen = true, +.active_joint_screen = true, .active_top_screen = true, .active_bottom_screen = true, +.enabled_normal_mode = true, .enabled_mono_mode = true, .is_cc_specific = true, +.out_action = MAIN_MENU_ISN_SETTINGS}; + static const MainMenuOptionInfo* pollable_options[] = { &connect_option, &windowed_option, @@ -132,6 +140,7 @@ static const MainMenuOptionInfo* pollable_options[] = { &load_profiles_option, &shortcut_option, &status_option, +&isn_settings_option, &licenses_option, &extra_settings_option, &quit_option, @@ -164,7 +173,13 @@ void MainMenu::class_setup() { this->show_title = true; } -void MainMenu::insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_mode, bool enable_shortcut) { +static bool check_cc_specific_option(const MainMenuOptionInfo* option, CaptureConnectionType cc_type) { + if((option->out_action == MAIN_MENU_ISN_SETTINGS) && (cc_type == CAPTURE_CONN_IS_NITRO)) + return true; + return false; +} + +void MainMenu::insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_mode, bool enable_shortcut, CaptureConnectionType cc_type, bool connected) { this->num_enabled_options = 0; for(int i = 0; i < NUM_TOTAL_MENU_OPTIONS; i++) { bool valid = true; @@ -182,6 +197,8 @@ void MainMenu::insert_data(ScreenType s_type, bool is_fullscreen, bool mono_app_ valid = valid && pollable_options[i]->enabled_mono_mode; else valid = valid && pollable_options[i]->enabled_normal_mode; + if(pollable_options[i]->is_cc_specific) + valid = valid && connected && check_cc_specific_option(pollable_options[i], cc_type); if((pollable_options[i]->out_action == MAIN_MENU_SHORTCUT_SETTINGS) && (!enable_shortcut)) valid = false; if(valid) { diff --git a/source/WindowScreen_Menu.cpp b/source/WindowScreen_Menu.cpp index 85cc2a5..7e7f1e2 100755 --- a/source/WindowScreen_Menu.cpp +++ b/source/WindowScreen_Menu.cpp @@ -109,6 +109,7 @@ void WindowScreen::init_menus() { this->shortcut_menu = new ShortcutMenu(this->font_load_success, this->text_font); this->action_selection_menu = new ActionSelectionMenu(this->font_load_success, this->text_font); this->scaling_ratio_menu = new ScalingRatioMenu(this->font_load_success, this->text_font); + this->is_nitro_menu = new ISNitroMenu(this->font_load_success, this->text_font); } void WindowScreen::destroy_menus() { @@ -130,6 +131,7 @@ void WindowScreen::destroy_menus() { delete this->shortcut_menu; delete this->action_selection_menu; delete this->scaling_ratio_menu; + delete this->is_nitro_menu; } void WindowScreen::set_close(int ret_val) { @@ -174,6 +176,15 @@ void WindowScreen::fast_poll_change() { this->print_notification_on_off("Slow Poll", this->display_data->fast_poll); } +void WindowScreen::is_nitro_capture_type_change(bool positive) { + int new_value = (int)(this->capture_status->capture_type); + if(positive) + new_value += 1; + else + new_value += CAPTURE_SCREENS_ENUM_END - 1; + this->capture_status->capture_type = static_cast(new_value % CAPTURE_SCREENS_ENUM_END); +} + void WindowScreen::padding_change() { if(this->m_info.is_fullscreen) return; @@ -615,14 +626,14 @@ void WindowScreen::setup_no_menu() { this->last_menu_change_time = std::chrono::high_resolution_clock::now(); } -void WindowScreen::setup_main_menu(bool reset_data) { - if(!this->can_setup_menu()) +void WindowScreen::setup_main_menu(bool reset_data, bool skip_setup_check) { + if((!skip_setup_check) && (!this->can_setup_menu())) return; if(this->curr_menu != MAIN_MENU_TYPE) { this->curr_menu = MAIN_MENU_TYPE; if(reset_data) this->main_menu->reset_data(); - this->main_menu->insert_data(this->m_stype, this->m_info.is_fullscreen, this->display_data->mono_app_mode, is_shortcut_valid()); + this->main_menu->insert_data(this->m_stype, this->m_info.is_fullscreen, this->display_data->mono_app_mode, is_shortcut_valid(), this->capture_status->device.cc_type, this->capture_status->connected); this->last_menu_change_time = std::chrono::high_resolution_clock::now(); } } @@ -900,6 +911,18 @@ void WindowScreen::setup_scaling_ratio_menu(bool reset_data) { } } +void WindowScreen::setup_is_nitro_menu(bool reset_data) { + if(!this->can_setup_menu()) + return; + if(this->curr_menu != ISN_MENU_TYPE) { + this->curr_menu = ISN_MENU_TYPE; + if(reset_data) + this->is_nitro_menu->reset_data(); + this->is_nitro_menu->insert_data(); + this->last_menu_change_time = std::chrono::high_resolution_clock::now(); + } +} + void WindowScreen::update_save_menu() { if(this->curr_menu == SAVE_MENU_TYPE) { this->curr_menu = DEFAULT_MENU_TYPE; @@ -1234,6 +1257,10 @@ void WindowScreen::poll(bool do_everything) { this->setup_shortcuts_menu(); done = true; break; + case MAIN_MENU_ISN_SETTINGS: + this->setup_is_nitro_menu(); + done = true; + break; case MAIN_MENU_SHUTDOWN: this->set_close(1); this->setup_no_menu(); @@ -1771,6 +1798,30 @@ void WindowScreen::poll(bool do_everything) { continue; } break; + case ISN_MENU_TYPE: + if(this->is_nitro_menu->poll(event_data)) { + switch(this->is_nitro_menu->selected_index) { + case ISN_MENU_BACK: + this->setup_main_menu(false); + done = true; + break; + case ISN_MENU_NO_ACTION: + break; + case ISN_MENU_DELAY: + break; + case ISN_MENU_TYPE_DEC: + this->is_nitro_capture_type_change(false); + break; + case ISN_MENU_TYPE_INC: + this->is_nitro_capture_type_change(true); + break; + default: + break; + } + this->is_nitro_menu->reset_output_option(); + continue; + } + break; default: break; } @@ -1800,6 +1851,15 @@ void WindowScreen::update_ds_3ds_connection(bool changed_type) { this->future_operations.call_crop = true; } +void WindowScreen::update_capture_specific_settings() { + if(this->curr_menu == MAIN_MENU_TYPE) { + this->setup_no_menu(); + this->setup_main_menu(true, true); + } + if(this->curr_menu == ISN_MENU_TYPE) + this->setup_no_menu(); +} + int WindowScreen::load_data() { int ret_val = this->m_prepare_load; this->m_prepare_load = 0; @@ -1985,6 +2045,9 @@ void WindowScreen::prepare_menu_draws(int view_size_x, int view_size_y) { case SCALING_RATIO_MENU_TYPE: this->scaling_ratio_menu->prepare(this->loaded_info.menu_scaling_factor, view_size_x, view_size_y, &this->loaded_info); break; + case ISN_MENU_TYPE: + this->is_nitro_menu->prepare(this->loaded_info.menu_scaling_factor, view_size_x, view_size_y, this->capture_status); + break; default: break; } @@ -2052,6 +2115,9 @@ void WindowScreen::execute_menu_draws() { case SCALING_RATIO_MENU_TYPE: this->scaling_ratio_menu->draw(this->loaded_info.menu_scaling_factor, this->m_win); break; + case ISN_MENU_TYPE: + this->is_nitro_menu->draw(this->loaded_info.menu_scaling_factor, this->m_win); + break; default: break; } diff --git a/source/cc3dsfs.cpp b/source/cc3dsfs.cpp index faa07ef..0780aff 100755 --- a/source/cc3dsfs.cpp +++ b/source/cc3dsfs.cpp @@ -57,7 +57,7 @@ static void SuccessConnectionOutTextGenerator(OutTextData &out_text_data, Captur } } -static bool load(const std::string path, const std::string name, ScreenInfo &top_info, ScreenInfo &bottom_info, ScreenInfo &joint_info, DisplayData &display_data, AudioData *audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts) { +static bool load(const std::string path, const std::string name, ScreenInfo &top_info, ScreenInfo &bottom_info, ScreenInfo &joint_info, DisplayData &display_data, AudioData *audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status) { std::ifstream file(path + name); std::string line; @@ -110,6 +110,11 @@ static bool load(const std::string path, const std::string name, ScreenInfo &top continue; } + if(key == "is_screen_capture_type") { + capture_status->capture_type = static_cast(std::stoi(value) % CaptureScreensType::CAPTURE_SCREENS_ENUM_END); + continue; + } + if(audio_data->load_audio_data(key, value)) continue; } @@ -125,7 +130,9 @@ static bool load(const std::string path, const std::string name, ScreenInfo &top return result; } -static void defaults_reload(FrontendData *frontend_data, AudioData* audio_data, ExtraButtonShortcuts* extra_button_shortcuts) { +static void defaults_reload(FrontendData *frontend_data, AudioData* audio_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status) { + capture_status->enabled_3d = false; + capture_status->capture_type = CAPTURE_SCREENS_BOTH; reset_screen_info(frontend_data->top_screen->m_info); reset_screen_info(frontend_data->bot_screen->m_info); reset_screen_info(frontend_data->joint_screen->m_info); @@ -138,11 +145,11 @@ static void defaults_reload(FrontendData *frontend_data, AudioData* audio_data, frontend_data->reload = true; } -static void load_layout_file(int load_index, FrontendData *frontend_data, AudioData* audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, bool skip_io, bool do_print) { +static void load_layout_file(int load_index, FrontendData *frontend_data, AudioData* audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status, bool skip_io, bool do_print) { if(skip_io) return; - defaults_reload(frontend_data, audio_data, extra_button_shortcuts); + defaults_reload(frontend_data, audio_data, extra_button_shortcuts, capture_status); if(load_index == SIMPLE_RESET_DATA_INDEX) { UpdateOutText(out_text_data, "Reset detected. Defaults re-loaded", "Reset detected\nDefaults re-loaded", TEXT_KIND_WARNING); @@ -152,16 +159,16 @@ static void load_layout_file(int load_index, FrontendData *frontend_data, AudioD bool name_load_success = false; std::string layout_name = LayoutNameGenerator(load_index); std::string layout_path = LayoutPathGenerator(load_index); - bool op_success = load(layout_path, layout_name, frontend_data->top_screen->m_info, frontend_data->bot_screen->m_info, frontend_data->joint_screen->m_info, frontend_data->display_data, audio_data, out_text_data, extra_button_shortcuts); + bool op_success = load(layout_path, layout_name, frontend_data->top_screen->m_info, frontend_data->bot_screen->m_info, frontend_data->joint_screen->m_info, frontend_data->display_data, audio_data, out_text_data, extra_button_shortcuts, capture_status); if(do_print && op_success) { std::string load_name = load_layout_name(load_index, name_load_success); UpdateOutText(out_text_data, "Layout loaded from: " + layout_path + layout_name, "Layout " + load_name + " loaded", TEXT_KIND_SUCCESS); } else if(!op_success) - defaults_reload(frontend_data, audio_data, extra_button_shortcuts); + defaults_reload(frontend_data, audio_data, extra_button_shortcuts, capture_status); } -static bool save(const std::string path, const std::string name, const std::string save_name, const ScreenInfo &top_info, const ScreenInfo &bottom_info, const ScreenInfo &joint_info, DisplayData &display_data, AudioData *audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts) { +static bool save(const std::string path, const std::string name, const std::string save_name, const ScreenInfo &top_info, const ScreenInfo &bottom_info, const ScreenInfo &joint_info, DisplayData &display_data, AudioData *audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status) { #if (!defined(_MSC_VER)) || (_MSC_VER > 1916) std::filesystem::create_directories(path); #else @@ -183,13 +190,14 @@ static bool save(const std::string path, const std::string name, const std::stri file << "last_connected_ds=" << display_data.last_connected_ds << std::endl; file << "extra_button_enter_short=" << extra_button_shortcuts->enter_shortcut->cmd << std::endl; file << "extra_button_page_up_short=" << extra_button_shortcuts->page_up_shortcut->cmd << std::endl; + file << "is_screen_capture_type=" << capture_status->capture_type << std::endl; file << audio_data->save_audio_data(); file.close(); return true; } -static void save_layout_file(int save_index, FrontendData *frontend_data, AudioData* audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, bool skip_io, bool do_print) { +static void save_layout_file(int save_index, FrontendData *frontend_data, AudioData* audio_data, OutTextData &out_text_data, ExtraButtonShortcuts* extra_button_shortcuts, CaptureStatus* capture_status, bool skip_io, bool do_print) { if(skip_io) return; @@ -200,7 +208,7 @@ static void save_layout_file(int save_index, FrontendData *frontend_data, AudioD std::string save_name = load_layout_name(save_index, name_load_success); std::string layout_name = LayoutNameGenerator(save_index); std::string layout_path = LayoutPathGenerator(save_index); - bool op_success = save(layout_path, layout_name, save_name, frontend_data->top_screen->m_info, frontend_data->bot_screen->m_info, frontend_data->joint_screen->m_info, frontend_data->display_data, audio_data, out_text_data, extra_button_shortcuts); + bool op_success = save(layout_path, layout_name, save_name, frontend_data->top_screen->m_info, frontend_data->bot_screen->m_info, frontend_data->joint_screen->m_info, frontend_data->display_data, audio_data, out_text_data, extra_button_shortcuts, capture_status); if(do_print && op_success) { UpdateOutText(out_text_data, "Layout saved to: " + layout_path + layout_name, "Layout " + save_name + " saved", TEXT_KIND_SUCCESS); } @@ -223,7 +231,7 @@ static void soundCall(AudioData *audio_data, CaptureData* capture_data) { if((capture_data->read[curr_out] > get_video_in_size(capture_data)) && (loaded_samples < MAX_MAX_AUDIO_LATENCY)) { int n_samples = get_audio_n_samples(capture_data, capture_data->read[curr_out]); double out_time = capture_data->time_in_buf[curr_out]; - convertAudioToOutput(&capture_data->capture_buf[curr_out], out_buf[audio_buf_counter], n_samples, endianness, capture_data); + convertAudioToOutput(curr_out, out_buf[audio_buf_counter], n_samples, endianness, capture_data); audio.samples.emplace(out_buf[audio_buf_counter], n_samples, out_time); if(++audio_buf_counter == NUM_CONCURRENT_AUDIO_BUFFERS) { audio_buf_counter = 0; @@ -307,7 +315,7 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data, frontend_data.bot_screen = bot_screen; frontend_data.joint_screen = joint_screen; - load_layout_file(STARTUP_FILE_INDEX, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, skip_io, false); + load_layout_file(STARTUP_FILE_INDEX, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, &capture_data->status, skip_io, false); // Due to the risk for seizures, at the start of the program, set BFI to false! top_screen->m_info.bfi = false; bot_screen->m_info.bfi = false; @@ -322,6 +330,7 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data, std::thread joint_thread(screen_display_thread, joint_screen); capture_data->status.connected = connect(true, capture_data, &frontend_data); + bool last_connected = capture_data->status.connected; SuccessConnectionOutTextGenerator(out_text_data, capture_data); while(capture_data->status.running) { @@ -337,13 +346,16 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data, VideoOutputData *chosen_buf = out_buf; bool blank_out = false; if(capture_data->status.connected) { + if(!last_connected) + update_connected_specific_settings(&frontend_data, capture_data->status.device); + last_connected = true; bool timed_out = !capture_data->status.video_wait.timed_lock(); curr_out = (capture_data->status.curr_in - 1 + NUM_CONCURRENT_DATA_BUFFERS) % NUM_CONCURRENT_DATA_BUFFERS; if((!capture_data->status.cooldown_curr_in) && (curr_out != prev_out)) { last_frame_time = capture_data->time_in_buf[curr_out]; if(capture_data->read[curr_out] >= get_video_in_size(capture_data)) { - convertVideoToOutput(&capture_data->capture_buf[curr_out], out_buf, capture_data); + convertVideoToOutput(curr_out, out_buf, capture_data); num_allowed_blanks = MAX_ALLOWED_BLANKS; } else { @@ -355,11 +367,14 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data, } } } - else if(capture_data->status.cooldown_curr_in) + else if((capture_data->status.cooldown_curr_in) || timed_out) blank_out = true; prev_out = curr_out; } else { + if(last_connected) + update_connected_specific_settings(&frontend_data, capture_data->status.device); + last_connected = false; default_sleep(); blank_out = true; } @@ -392,12 +407,12 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data, if((load_index = top_screen->load_data()) || (load_index = bot_screen->load_data()) || (load_index = joint_screen->load_data())) { // This value should only be loaded when starting the program... bool previous_last_connected_ds = frontend_data.display_data.last_connected_ds; - load_layout_file(load_index, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, skip_io, true); + load_layout_file(load_index, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, &capture_data->status, skip_io, true); frontend_data.display_data.last_connected_ds = previous_last_connected_ds; } if((save_index = top_screen->save_data()) || (save_index = bot_screen->save_data()) || (save_index = joint_screen->save_data())) { - save_layout_file(save_index, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, skip_io, true); + save_layout_file(save_index, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, &capture_data->status, skip_io, true); top_screen->update_save_menu(); bot_screen->update_save_menu(); joint_screen->update_save_menu(); @@ -433,7 +448,7 @@ static int mainVideoOutputCall(AudioData* audio_data, CaptureData* capture_data, bot_screen->after_thread_join(); joint_screen->after_thread_join(); - save_layout_file(STARTUP_FILE_INDEX, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, skip_io, false); + save_layout_file(STARTUP_FILE_INDEX, &frontend_data, audio_data, out_text_data, &extra_button_shortcuts, &capture_data->status, skip_io, false); if(!out_text_data.consumed) { ConsoleOutText(out_text_data.full_text); diff --git a/source/conversions.cpp b/source/conversions.cpp index 0d8f8d8..3798f82 100755 --- a/source/conversions.cpp +++ b/source/conversions.cpp @@ -7,7 +7,8 @@ #include -void convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureData* capture_data) { +void convertVideoToOutput(int index, VideoOutputData *p_out, CaptureData* capture_data) { + CaptureReceived *p_in = &capture_data->capture_buf[index]; #ifdef USE_FTD3 if(capture_data->status.device.cc_type == CAPTURE_CONN_FTD3) ftd3_convertVideoToOutput(p_in, p_out, capture_data->status.enabled_3d); @@ -22,13 +23,14 @@ void convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, Capture #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); + usb_is_nitro_convertVideoToOutput(p_in, p_out, capture_data->capture_type[index]); #endif } -void convertAudioToOutput(CaptureReceived *p_in, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data) { +void convertAudioToOutput(int index, sf::Int16 *p_out, uint64_t n_samples, const bool is_big_endian, CaptureData* capture_data) { if(!capture_data->status.device.has_audio) return; + CaptureReceived *p_in = &capture_data->capture_buf[index]; uint8_t* base_ptr = NULL; #ifdef USE_FTD3 if(capture_data->status.device.cc_type == CAPTURE_CONN_FTD3) { diff --git a/source/frontend.cpp b/source/frontend.cpp index da26c29..565283a 100755 --- a/source/frontend.cpp +++ b/source/frontend.cpp @@ -786,9 +786,17 @@ void update_connected_3ds_ds(FrontendData* frontend_data, const CaptureDevice &o } } -void default_sleep(int wanted_ms) { +void update_connected_specific_settings(FrontendData* frontend_data, const CaptureDevice &cc_device) { + if(cc_device.cc_type == CAPTURE_CONN_IS_NITRO) { + frontend_data->top_screen->update_capture_specific_settings(); + frontend_data->bot_screen->update_capture_specific_settings(); + frontend_data->joint_screen->update_capture_specific_settings(); + } +} + +void default_sleep(float wanted_ms) { if(wanted_ms < 0) - sf::sleep(sf::milliseconds(1000/USB_CHECKS_PER_SECOND)); + sf::sleep(sf::milliseconds(1000.0/USB_CHECKS_PER_SECOND)); else sf::sleep(sf::milliseconds(wanted_ms)); } diff --git a/source/usb_is_nitro.cpp b/source/usb_is_nitro.cpp index b632fb6..6cb22e6 100644 --- a/source/usb_is_nitro.cpp +++ b/source/usb_is_nitro.cpp @@ -247,7 +247,7 @@ int DisableLca2(libusb_device_handle *handle) { ret = WriteNecMemU16(handle, 0xF84000A, 1); if(ret < 0) return ret; - default_sleep(2000); + //default_sleep(2000); return WriteNecMemU16(handle, 0xF84000A, 0); } @@ -290,6 +290,7 @@ int UpdateFrameForwardEnable(libusb_device_handle *handle, uint8_t value) { } int ReadFrame(libusb_device_handle *handle, uint8_t* buf, int length) { + // Maybe making this async would be better for lower end hardware... int num_bytes = 0; int ret = bulk_in(handle, &usb_is_nitro_desc, buf, length, &num_bytes); if(num_bytes != length) diff --git a/source/usb_is_nitro_capture.cpp b/source/usb_is_nitro_capture.cpp index 8fe2c25..6447272 100644 --- a/source/usb_is_nitro_capture.cpp +++ b/source/usb_is_nitro_capture.cpp @@ -30,7 +30,7 @@ #define SERIAL_NUMBER_SIZE (IS_NITRO_REAL_SERIAL_NUMBER_SIZE + 1) -#define NUM_CONSECUTIVE_FRAMES 32 +#define FRAME_BUFFER_SIZE 32 enum usb_capture_status { USB_CAPTURE_SUCCESS = 0, @@ -40,9 +40,9 @@ enum usb_capture_status { 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 int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames, CaptureScreensType capture_type); +static int StartCapture(libusb_device_handle *handle, uint16_t *out_frame_count, float* single_frame_time, CaptureScreensType capture_type); +static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames, CaptureScreensType capture_type); 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]; @@ -50,13 +50,18 @@ static std::string get_serial(const is_nitro_usb_device* usb_device_desc, libusb 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) + if(conn_success && EndCapture(handle, false, 0, CAPTURE_SCREENS_BOTH) != LIBUSB_SUCCESS) conn_success = false; - if(conn_success && (GetDeviceSerial(handle, data) == LIBUSB_SUCCESS)) { + if(conn_success && (GetDeviceSerial(handle, data) != LIBUSB_SUCCESS)) { + int ret = 0; + while(ret >= 0) + ret = drain_frames(handle, FRAME_BUFFER_SIZE * 2, 0, CAPTURE_SCREENS_TOP); + if((GetDeviceSerial(handle, data) != LIBUSB_SUCCESS)) + conn_success = false; + } + if(conn_success) { data[IS_NITRO_REAL_SERIAL_NUMBER_SIZE] = '\0'; serial_str = std::string((const char*)data); } @@ -165,71 +170,120 @@ bool is_nitro_connect_usb(bool print_failed, CaptureData* capture_data, CaptureD return true; } -static uint64_t _is_nitro_emulator_get_video_in_size() { +static uint64_t _is_nitro_emulator_get_video_in_size(CaptureScreensType capture_type) { + if((capture_type == CAPTURE_SCREENS_TOP) || (capture_type == CAPTURE_SCREENS_BOTTOM)) + return sizeof(ISNitroEmulatorVideoInputData) / 2; 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(); + return _is_nitro_emulator_get_video_in_size(capture_data->status.capture_type); } -static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames) { +static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames, CaptureScreensType capture_type) { 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()); + int ret = ReadFrame(handle, (uint8_t*)&video_in_buffer, _is_nitro_emulator_get_video_in_size(capture_type)); if(ret < 0) return ret; } return LIBUSB_SUCCESS; } -static int StartCapture(libusb_device_handle *handle, int *out_frame_count) { +static int set_capture_mode(libusb_device_handle *handle, CaptureScreensType capture_type) { + uint8_t capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_BOTH; + if(capture_type == CAPTURE_SCREENS_TOP) + capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_TOP; + if(capture_type == CAPTURE_SCREENS_BOTTOM) + capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_BOTTOM; + return UpdateFrameForwardConfig(handle, IS_NITRO_FORWARD_CONFIG_COLOR_RGB24 | capture_mode_flag | IS_NITRO_FORWARD_CONFIG_RATE_FULL); +} + +static int StartCapture(libusb_device_handle *handle, uint16_t &out_frame_count, float &single_frame_time, CaptureScreensType capture_type) { 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); + ret = set_capture_mode(handle, capture_type); if(ret < 0) return ret; - ret = SetForwardFrameCount(handle, NUM_CONSECUTIVE_FRAMES); + ret = SetForwardFrameCount(handle, FRAME_BUFFER_SIZE); if(ret < 0) return ret; - ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_DISABLE); + // Reset this in case it's high. At around 0xFFFF, reading from the USB DMA seems to fail... + ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_RESTART | IS_NITRO_FORWARD_ENABLE_ENABLE); if(ret < 0) return ret; + ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_ENABLE); + if(ret < 0) + return ret; + + // Get to the closest next frame + auto clock_start = std::chrono::high_resolution_clock::now(); uint16_t oldFrameCount; + uint16_t newFrameCount; 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) { + newFrameCount = oldFrameCount; + while(newFrameCount == oldFrameCount) { ret = GetFrameCounter(handle, &newFrameCount); if(ret < 0) return ret; - if (newFrameCount < 64 && newFrameCount != oldFrameCount && newFrameCount != oldFrameCount + 1) { + const auto curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - clock_start; + // If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up + if(diff.count() > 0.2) break; - } } - ret = StartUsbCaptureDma(handle); + // Get to the next modulo 32 frame. + // We also do this to measure the time that is needed for each frame... + // To do so, a minimum of 4 frames is required (FRAME_BUFFER_SIZE - 1 + 4) + clock_start = std::chrono::high_resolution_clock::now(); + ret = GetFrameCounter(handle, &oldFrameCount); if(ret < 0) return ret; - ret = drain_frames(handle, newFrameCount, 0); - *out_frame_count = newFrameCount; + uint16_t targetFrameCount = (newFrameCount + FRAME_BUFFER_SIZE + 3) & (~(FRAME_BUFFER_SIZE - 1)); + while(oldFrameCount != targetFrameCount) { + ret = GetFrameCounter(handle, &oldFrameCount); + if(ret < 0) + return ret; + // Placing a sleep of some kind here would be much better... + // Though this is only executed for a small time when first connecting... + const auto curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - clock_start; + // If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up + if(diff.count() > 1.0) + break; + } + const auto curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - clock_start; + // Sometimes the upper 8 bits aren't updated... Use only the lower 8 bits. + newFrameCount &= 0xFF; + oldFrameCount &= 0xFF; + int frame_diff = ((int)oldFrameCount) - ((int)newFrameCount); + if(frame_diff < 0) + frame_diff += 1 << 8; + out_frame_count = oldFrameCount; + // Determine how much time a single frame takes. We'll use it for sleeps + if(frame_diff == 0) + single_frame_time = 0; + else + single_frame_time = diff.count() / frame_diff; + + // Start the actual DMA + if(single_frame_time > 0) { + ret = StartUsbCaptureDma(handle); + if(ret < 0) + return ret; + } return ret; } -static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames) { +static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames, CaptureScreensType capture_type) { int ret = 0; if(do_drain_frames) - ret = drain_frames(handle, NUM_CONSECUTIVE_FRAMES, start_frames); + ret = drain_frames(handle, FRAME_BUFFER_SIZE, start_frames, capture_type); if(ret < 0) return ret; ret = StopUsbCaptureDma(handle); @@ -238,38 +292,98 @@ static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int st 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; +static void frame_wait(float single_frame_time, std::chrono::time_point clock_last_reset, int curr_frame_counter, int last_frame_counter) { + if(curr_frame_counter == 0) + return; + auto curr_time = std::chrono::high_resolution_clock::now(); + std::chrono::duration diff = curr_time - clock_last_reset; + float expected_time = single_frame_time * curr_frame_counter; + // If the current time is too low, sleep a bit to make sure we don't overrun the framerate counter + // Don't do it regardless of the situation, and only in small increments... + // Otherwise there is the risk of sleeping too much + while((diff.count() < expected_time) && ((expected_time - diff.count()) > (single_frame_time / 4)) && (!(last_frame_counter & (FRAME_BUFFER_SIZE - 1)))) { + default_sleep((expected_time - diff.count()) / 4); + curr_time = std::chrono::high_resolution_clock::now(); + diff = curr_time - clock_last_reset; + } +} - if(frame_counter == NUM_CONSECUTIVE_FRAMES) { +int reset_capture_frames(libusb_device_handle* handle, uint16_t &curr_frame_counter, uint16_t &last_frame_counter, float &single_frame_time, std::chrono::time_point &clock_last_reset, CaptureScreensType &curr_capture_type, CaptureScreensType wanted_capture_type, int multiplier) { + curr_frame_counter += 1; + + if(curr_frame_counter == FRAME_BUFFER_SIZE) { 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 the user requests a mode change, accomodate them. + // Though it may lag for a bit... + if(wanted_capture_type != curr_capture_type) { + curr_capture_type = wanted_capture_type; + ret = set_capture_mode(handle, curr_capture_type); if(ret < 0) return ret; + } - diff = internalFrameCount - total_frame_counter; - if(diff > 32768) - diff -= 1 << 16; - } while(diff <= 0); - */ + uint16_t internalFrameCount = 0; + uint16_t full_internalFrameCount = 0; + int frame_diff = 0; + int diff_target = FRAME_BUFFER_SIZE * multiplier; + do { + // Check how many frames have passed... + ret = GetFrameCounter(handle, &internalFrameCount); + full_internalFrameCount = internalFrameCount; + // Sometimes the upper 8 bits aren't updated... Use only the lower 8 bits. + internalFrameCount &= 0xFF; + if(ret < 0) + return ret; + frame_diff = internalFrameCount - last_frame_counter; + if(frame_diff < 0) + frame_diff += 1 << 8; + // If the frames haven't advanced, the DS is either turned off or sleeping. If so, avoid locking up + if(frame_diff == 0) + break; + const auto curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - clock_last_reset; + // If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up + if(diff.count() > (1.0 * multiplier)) { + frame_diff = 0; + break; + } + // Exit if enough frames have passed, or if there currently is some delay. + // Exiting early makes it possible to catch up to the DMA, if we're behind. + } while((frame_diff < diff_target) && (!(last_frame_counter & (FRAME_BUFFER_SIZE - 1)))); + // Determine how much time a single frame takes. We'll use it for sleeps + const auto curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - clock_last_reset; + if(frame_diff == 0) + single_frame_time = 0; + else + single_frame_time = diff.count() / (frame_diff / ((float)multiplier)); + clock_last_reset = curr_time; + // Save the current frame counter's 8 LSB + last_frame_counter = internalFrameCount; + + // If we're nearing 0xFFFF for the frame counter, reset it. + // It's a problematic value for DMA reading + if(frame_diff && (full_internalFrameCount >= 0xF000)) { + ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_RESTART | IS_NITRO_FORWARD_ENABLE_ENABLE); + if(ret < 0) + return ret; + clock_last_reset = std::chrono::high_resolution_clock::now(); + } ret = UpdateFrameForwardEnable(handle, IS_NITRO_FORWARD_ENABLE_ENABLE); if(ret < 0) return ret; - frame_counter = 0; - ret = StartUsbCaptureDma(handle); - if(ret < 0) - return ret; + curr_frame_counter = 0; + + // Start the actual DMA + if(single_frame_time > 0) { + ret = StartUsbCaptureDma(handle); + if(ret < 0) + return ret; + } } return LIBUSB_SUCCESS; } @@ -278,42 +392,55 @@ 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; + uint16_t last_frame_counter = 0; + float single_frame_time = 0; + uint16_t curr_frame_counter = 0; + CaptureScreensType curr_capture_type = capture_data->status.capture_type; + int ret = StartCapture(handle, last_frame_counter, single_frame_time, curr_capture_type); 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(); + auto clock_last_reset = 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; - } + frame_wait(single_frame_time, clock_last_reset, curr_frame_counter, last_frame_counter); - 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(single_frame_time > 0) { + ret = ReadFrame(handle, (uint8_t*)&capture_data->capture_buf[inner_curr_in], _is_nitro_emulator_get_video_in_size(curr_capture_type)); + if(ret < 0) { + capture_error_print(true, capture_data, "Disconnected: Read error"); + break; + } + // Output to the other threads... + const auto curr_time = std::chrono::high_resolution_clock::now(); + const std::chrono::duration diff = curr_time - clock_start; + 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(curr_capture_type); + capture_data->capture_type[inner_curr_in] = curr_capture_type; + + 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(); + } + else { + capture_data->status.cooldown_curr_in = FIX_PARTIAL_FIRST_FRAME_NUM; + default_sleep(20); + } + capture_data->status.curr_delay = last_frame_counter % FRAME_BUFFER_SIZE; + ret = reset_capture_frames(handle, curr_frame_counter, last_frame_counter, single_frame_time, clock_last_reset, curr_capture_type, capture_data->status.capture_type, 1); 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); + EndCapture(handle, true, curr_frame_counter, curr_capture_type); } void usb_is_nitro_capture_cleanup(CaptureData* capture_data) { @@ -322,16 +449,23 @@ void usb_is_nitro_capture_cleanup(CaptureData* capture_data) { is_nitro_connection_end((libusb_device_handle*)capture_data->handle); } -void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureDevice* capture_device) { +void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureScreensType capture_type) { 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]; - } + int num_pixels = _is_nitro_emulator_get_video_in_size(capture_type) / 3; + int out_start_pos = 0; + int out_clear_pos = num_pixels; + if(capture_type == CAPTURE_SCREENS_BOTTOM) { + out_start_pos = num_pixels; + out_clear_pos = 0; + } + if((capture_type == CAPTURE_SCREENS_BOTTOM) || (capture_type == CAPTURE_SCREENS_TOP)) + memset(p_out->screen_data[out_clear_pos], 0, num_pixels * 3); + for(int i = 0; i < num_pixels; i++) { + p_out->screen_data[i + out_start_pos][0] = p_in->is_nitro_capture_received.video_in.screen_data[i][2]; + p_out->screen_data[i + out_start_pos][1] = p_in->is_nitro_capture_received.video_in.screen_data[i][1]; + p_out->screen_data[i + out_start_pos][2] = p_in->is_nitro_capture_received.video_in.screen_data[i][0]; + } } void usb_is_nitro_init() {