diff --git a/.gitignore b/.gitignore index 9153a03ea..83bfb8485 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,14 @@ packages/ *.bc *.so +# Ignore switch files +examples/*/*.d +examples/*/*.elf +examples/*/*.lst +examples/*/*.map +examples/*/*.nacp +examples/*/*.nro + # Ignore files build by xcode *.mode*v* *.pbxuser diff --git a/examples/Makefile b/examples/Makefile index aa183fd17..300e23587 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -30,7 +30,7 @@ RAYLIB_PATH ?= .. # Define default options -# One of PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_ANDROID, PLATFORM_WEB +# One of PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_ANDROID, PLATFORM_WEB, PLATFORM_NX PLATFORM ?= PLATFORM_DESKTOP # Locations of your newly installed library and associated headers. See ../src/Makefile @@ -101,6 +101,14 @@ ifeq ($(PLATFORM),PLATFORM_DRM) PLATFORM_OS=LINUX endif endif +ifeq ($(PLATFORM),PLATFORM_NX) + UNAMEOS = $(shell uname) + ifeq ($(UNAMEOS),Linux) + PLATFORM_OS = LINUX + endif + RAYLIB_LIBTYPE = STATIC + USE_EXTERNAL_GLFW = TRUE +endif # RAYLIB_PATH adjustment for different platforms. # If using GNU make, we can get the full path to the top of the tree. Windows? BSD? @@ -131,6 +139,11 @@ ifeq ($(PLATFORM),PLATFORM_WEB) export PATH = $(EMSDK_PATH);$(EMSCRIPTEN_PATH);$(CLANG_PATH);$(NODE_PATH);$(PYTHON_PATH);C:\raylib\MinGW\bin:$$(PATH) endif +ifeq ($(PLATFORM),PLATFORM_NX) + RAYLIB_PREFIX ?= .. + RAYLIB_PATH = $(realpath $(RAYLIB_PREFIX)) +endif + # Define raylib release directory for compiled library. # RAYLIB_RELEASE_PATH points to provided binaries or your freshly built version RAYLIB_RELEASE_PATH ?= $(RAYLIB_PATH)/src @@ -176,6 +189,12 @@ ifeq ($(PLATFORM),PLATFORM_WEB) # to use emscripten.h and emscripten_set_main_loop() CC = emcc endif +ifeq ($(PLATFORM),PLATFORM_NX) + ifeq ($(strip $(DEVKITPRO)),) + $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") + endif + include $(DEVKITPRO)/libnx/switch_rules +endif # Define default make program MAKE = make @@ -205,6 +224,8 @@ ifeq ($(BUILD_MODE),DEBUG) else ifeq ($(PLATFORM),PLATFORM_WEB) CFLAGS += -Os + else ifeq ($(PLATFORM),PLATFORM_NX) + CFLAGS += -O2 else CFLAGS += -s -O1 endif @@ -260,6 +281,12 @@ endif # NOTE: Some external/extras libraries could be required (stb, physac, easings...) INCLUDE_PATHS = -I. -I$(RAYLIB_PATH)/src -I$(RAYLIB_PATH)/src/external -I$(RAYLIB_PATH)/src/extras +ifeq ($(PLATFORM),PLATFORM_NX) + LIBDIRS := $(PORTLIBS) $(LIBNX) + ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE + CFLAGS += -ffunction-sections $(ARCH) $(foreach dir,$(LIBDIRS),-I$(dir)/include) -D__SWITCH__ +endif + # Define additional directories containing required header files ifeq ($(PLATFORM),PLATFORM_RPI) # RPI required libraries @@ -308,6 +335,10 @@ ifeq ($(PLATFORM),PLATFORM_RPI) LDFLAGS += -L/opt/vc/lib endif +ifeq ($(PLATFORM),PLATFORM_NX) + LDFLAGS += -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$*.map $(foreach dir,$(LIBDIRS),-L$(dir)/lib) +endif + # Define any libraries required on linking # if you want to link libraries (libname.so or libname.a), use the -lname ifeq ($(PLATFORM),PLATFORM_DESKTOP) @@ -369,6 +400,10 @@ ifeq ($(PLATFORM),PLATFORM_WEB) # Libraries for web (HTML5) compiling LDLIBS = $(RAYLIB_RELEASE_PATH)/libraylib.a endif +ifeq ($(PLATFORM),PLATFORM_NX) + LDLIBS = -lraylib -lEGL -lGLESv2 -lglapi -ldrm_nouveau -lnx -lm + LDLIBS += `$(PREFIX)pkg-config --libs sdl2` +endif # Define all object files required CORE = \ @@ -521,6 +556,12 @@ physics: $(PHYSICS) %: %.c ifeq ($(PLATFORM),PLATFORM_ANDROID) $(MAKE) -f Makefile.Android PROJECT_NAME=$@ PROJECT_SOURCE_FILES=$< +else ifeq ($(PLATFORM),PLATFORM_NX) + $(CC) -MMD -MP -MF $@.d -o $@.o -c $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) + $(CXX) $@.o $(LDFLAGS) $(LDLIBS) -o $@.elf -D$(PLATFORM) + $(NM) -CSn $@.elf > $@.lst + nacptool --create "$(notdir $@)" "Raylib Example" "1.0.0" $@.nacp + elf2nro $@.elf $@.nro --icon=textures/resources/raylib_logo.jpg --nacp=$@.nacp else $(CC) -o $@$(EXT) $< $(CFLAGS) $(INCLUDE_PATHS) $(LDFLAGS) $(LDLIBS) -D$(PLATFORM) endif @@ -550,6 +591,16 @@ ifeq ($(PLATFORM),PLATFORM_DRM) endif ifeq ($(PLATFORM),PLATFORM_WEB) del *.o *.html *.js +endif +ifeq ($(PLATFORM),PLATFORM_NX) + find . -type f \( \ + -name "*.o" -o \ + -name "*.d" -o \ + -name "*.elf" -o \ + -name "*.lst" -o \ + -name "*.map" -o \ + -name "*.nacp" -o \ + -name "*.nro" \) -delete endif @echo Cleaning done diff --git a/examples/textures/resources/raylib_logo.jpg b/examples/textures/resources/raylib_logo.jpg new file mode 100644 index 000000000..7fa0af4b7 Binary files /dev/null and b/examples/textures/resources/raylib_logo.jpg differ diff --git a/src/Makefile b/src/Makefile index 3a90dd92a..8aeeee677 100644 --- a/src/Makefile +++ b/src/Makefile @@ -11,6 +11,7 @@ # PLATFORM_RPI: Raspberry Pi (Raspbian) # PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver # PLATFORM_WEB: HTML5 (Chrome, Firefox) +# PLATFORM_NX: Switch LibNX # # Many thanks to Milan Nikolic (@gen2brain) for implementing Android platform pipeline. # Many thanks to Emanuele Petriglia for his contribution on GNU/Linux pipeline. @@ -66,7 +67,7 @@ RAYLIB_LIB_NAME ?= raylib RAYLIB_RES_FILE ?= ./raylib.dll.rc.data # Define raylib platform -# Options: PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_ANDROID, PLATFORM_WEB +# Options: PLATFORM_DESKTOP, PLATFORM_RPI, PLATFORM_ANDROID, PLATFORM_WEB, PLATFORM_NX PLATFORM ?= PLATFORM_DESKTOP # Include raylib modules on compilation @@ -144,6 +145,14 @@ ifeq ($(PLATFORM),PLATFORM_DRM) PLATFORM_OS = LINUX endif endif +ifeq ($(PLATFORM),PLATFORM_NX) + UNAMEOS = $(shell uname) + ifeq ($(UNAMEOS),Linux) + PLATFORM_OS = LINUX + endif + RAYLIB_LIBTYPE = STATIC + USE_EXTERNAL_GLFW = TRUE +endif # RAYLIB_SRC_PATH adjustment for different platforms. # If using GNU make, we can get the full path to the top of the tree. Windows? BSD? @@ -223,6 +232,9 @@ ifeq ($(PLATFORM),PLATFORM_ANDROID) # By default use OpenGL ES 2.0 on Android GRAPHICS = GRAPHICS_API_OPENGL_ES2 endif +ifeq ($(PLATFORM),PLATFORM_NX) + GRAPHICS = GRAPHICS_API_OPENGL_ES2 +endif # Define default C compiler and archiver to pack library CC = gcc @@ -271,6 +283,12 @@ ifeq ($(PLATFORM),PLATFORM_ANDROID) AR = $(ANDROID_TOOLCHAIN)/bin/x86_64-linux-android-ar endif endif +ifeq ($(PLATFORM),PLATFORM_NX) + ifeq ($(strip $(DEVKITPRO)),) + $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") + endif + include $(DEVKITPRO)/libnx/switch_rules +endif # Define compiler flags: # -O1 defines optimization level @@ -309,6 +327,9 @@ ifeq ($(RAYLIB_BUILD_MODE),RELEASE) ifeq ($(PLATFORM),PLATFORM_ANDROID) CFLAGS += -O2 endif + ifeq ($(PLATFORM),PLATFORM_NX) + CFLAGS += -O2 + endif endif # Additional flags for compiler (if desired) @@ -387,6 +408,13 @@ endif # NOTE: Several external required libraries (stb and others) INCLUDE_PATHS = -I. -Iexternal/glfw/include -Iexternal/glfw/deps/mingw +ifeq ($(PLATFORM),PLATFORM_NX) + LIBDIRS := $(PORTLIBS) $(LIBNX) + ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIC -ftls-model=local-exec + CFLAGS += -ffunction-sections -fdata-sections $(ARCH) $(foreach dir,$(LIBDIRS),-I$(dir)/include) -D__SWITCH__ + INCLUDE_PATHS = -I. +endif + ifeq ($(PLATFORM),PLATFORM_DESKTOP) ifeq ($(PLATFORM_OS),BSD) INCLUDE_PATHS += -I/usr/local/include @@ -475,7 +503,9 @@ endif ifeq ($(RAYLIB_MODULE_PHYSAC),TRUE) OBJS += physac.o endif - +ifeq ($(PLATFORM),PLATFORM_NX) + OBJS += nxusb.o +endif ifeq ($(PLATFORM),PLATFORM_ANDROID) OBJS += android_native_app_glue.o endif @@ -550,7 +580,7 @@ else else # Compile raylib static library version $(RAYLIB_VERSION) # WARNING: You should type "make clean" before doing this target. - $(AR) rcs $(RAYLIB_RELEASE_PATH)/lib$(RAYLIB_LIB_NAME).a $(OBJS) + $(AR) rc $(RAYLIB_RELEASE_PATH)/lib$(RAYLIB_LIB_NAME).a $(OBJS) @echo "raylib static library generated (lib$(RAYLIB_LIB_NAME).a) in $(RAYLIB_RELEASE_PATH)!" endif endif @@ -589,6 +619,10 @@ models.o : models.c raylib.h rlgl.h raymath.h raudio.o : raudio.c raylib.h $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) -D$(PLATFORM) +# Compile switch usb module +nxusb.o : nxusb.c nxusb.h raylib.h + $(CC) -c $< $(CFLAGS) $(INCLUDE_PATHS) -D$(PLATFORM) + # Compile raygui module # NOTE: raygui header should be distributed with raylib.h raygui.o : raygui.c raygui.h gui_textbox_extended.h ricons.h @@ -621,6 +655,10 @@ android_native_app_glue.o : $(NATIVE_APP_GLUE)/android_native_app_glue.c # See below and ../examples/Makefile for more information. # TODO: Add other platforms. Remove sudo requirement, i.e. add USER mode. +ifeq ($(PLATFORM),PLATFORM_NX) + DESTDIR ?= $(PORTLIBS) +endif + # RAYLIB_INSTALL_PATH should be the desired full path to libraylib. No relative paths. DESTDIR ?= /usr/local RAYLIB_INSTALL_PATH ?= $(DESTDIR)/lib diff --git a/src/config.h b/src/config.h index 0cb81c340..8e0d9aa9a 100644 --- a/src/config.h +++ b/src/config.h @@ -61,6 +61,10 @@ // By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timming + PollInputEvents() // Enabling this flag allows manual control of the frame processes, use at your own risk //#define SUPPORT_CUSTOM_FRAME_CONTROL 1 +// Enabling this flag allows gamepad to set keyboard and mouse states +#define NX_SUPPORT_GAMEPAD_EMULATION 1 +// Enabling this flag allows debugging by USB, the application will wait for an USB connection to start +//#define NX_USB_DEBUGGER 1 // core: Configuration values //------------------------------------------------------------------------------------ diff --git a/src/core.c b/src/core.c index 03f60c6d3..47fb90276 100644 --- a/src/core.c +++ b/src/core.c @@ -11,6 +11,7 @@ * - PLATFORM_RPI: Raspberry Pi 0,1,2,3 (Raspbian, native mode) * - PLATFORM_DRM: Linux native mode, including Raspberry Pi 4 with V3D fkms driver * - PLATFORM_WEB: HTML5 with WebAssembly +* - PLATFORM_NX: Switch LibNX * * CONFIGURATION: * @@ -34,6 +35,10 @@ * Windowing and input system configured for HTML5 (run on browser), code converted from C to asm.js * using emscripten compiler. OpenGL ES 2.0 required for direct translation to WebGL equivalent code. * +* #define PLATFORM_NX +* Windowing and input system configured for libnx (Nintendo Switch) +* graphic device is managed by EGL and inputs are processed is raw mode, reading from /dev/input/ +* * #define SUPPORT_DEFAULT_FONT (default) * Default font is loaded on window initialization to be available for the user to render simple text. * NOTE: If enabled, uses external module functions to load default raylib font (module: text) @@ -264,6 +269,16 @@ #include // Emscripten HTML5 library #endif +#if defined(PLATFORM_NX) + #include + #include // EGL library + #include // EGL extensions + #include // OpenGL ES 2.0 library + #if defined(NX_USB_DEBUGGER) + #include "nxusb.h" + #endif +#endif + //---------------------------------------------------------------------------------- // Defines and Macros //---------------------------------------------------------------------------------- @@ -353,7 +368,7 @@ typedef struct CoreData { #if defined(PLATFORM_RPI) EGL_DISPMANX_WINDOW_T handle; // Native window handle (graphic device) #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_NX) #if defined(PLATFORM_DRM) int fd; // File descriptor for /dev/dri/... drmModeConnector *connector; // Direct Rendering Manager (DRM) mode connector @@ -364,6 +379,9 @@ typedef struct CoreData { struct gbm_bo *prevBO; // Previous GBM buffer object (during frame swapping) uint32_t prevFB; // Previous GBM framebufer (during frame swapping) #endif // PLATFORM_DRM +#if defined(PLATFORM_NX) + NWindow *gbmSurface; // GBM surface +#endif EGLDisplay device; // Native display device (physical screen connection) EGLSurface surface; // Surface to draw on, framebuffers (connected to context) EGLContext context; // Graphic context, mode in which drawing can be done @@ -458,6 +476,9 @@ typedef struct CoreData { pthread_t threadId; // Gamepad reading thread id int streamId[MAX_GAMEPADS]; // Gamepad device file descriptor char name[64]; // Gamepad name holder +#endif +#if defined(PLATFORM_NX) + PadState nxPad[MAX_GAMEPADS]; // Gamepad state holder #endif } Gamepad; } Input; @@ -468,7 +489,7 @@ typedef struct CoreData { double draw; // Time measure for frame draw double frame; // Time measure for one frame double target; // Desired time for one frame, if 0 not applied -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_NX) unsigned long long base; // Base time measure for hi-res timer #endif unsigned int frameCounter; // Frame counter @@ -691,6 +712,9 @@ struct android_app *GetAndroidApp(void) // NOTE: data parameter could be used to pass any kind of required data to the initialization void InitWindow(int width, int height, const char *title) { +#if defined(PLATFORM_NX) && defined(NX_USB_DEBUGGER) + NxUsbDebuggerInit(); +#endif TRACELOG(LOG_INFO, "Initializing raylib %s", RAYLIB_VERSION); if ((title != NULL) && (title[0] != 0)) CORE.Window.title = title; @@ -765,7 +789,7 @@ void InitWindow(int width, int height, const char *title) } } #endif -#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(PLATFORM_DESKTOP) || defined(PLATFORM_WEB) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_NX) // Initialize graphics device (display device and OpenGL context) // NOTE: returns true if window and graphic device has been initialized successfully CORE.Window.ready = InitGraphicsDevice(width, height); @@ -812,6 +836,13 @@ void InitWindow(int width, int height, const char *title) InitKeyboard(); // Keyboard init (stdin) #endif +#if defined(PLATFORM_NX) + // Configure our supported input layout + padConfigureInput(MAX_GAMEPADS, HidNpadStyleSet_NpadStandard); + // Initialize the gamepads + padInitializeDefault(&CORE.Input.Gamepad.nxPad[0]); +#endif + #if defined(PLATFORM_WEB) // Check fullscreen change events(note this is done on the window since most browsers don't support this on #canvas) //emscripten_set_fullscreenchange_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, NULL, 1, EmscriptenResizeCallback); @@ -872,7 +903,7 @@ void CloseWindow(void) timeEndPeriod(1); // Restore time period #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_NX) // Close surface, context and display if (CORE.Window.device != EGL_NO_DISPLAY) { @@ -991,6 +1022,9 @@ void CloseWindow(void) CORE.Window.ready = false; TRACELOG(LOG_INFO, "Window closed successfully"); +#if defined(PLATFORM_NX) && defined(NX_USB_DEBUGGER) + NxUsbDebuggerEnd(); +#endif } // Check if KEY_ESCAPE pressed or Close icon pressed @@ -1022,7 +1056,10 @@ bool WindowShouldClose(void) else return true; #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_NX) + #if defined(PLATFORM_NX) + if (!appletMainLoop()) return true; + #endif if (CORE.Window.ready) return CORE.Window.shouldClose; else return true; #endif @@ -2601,7 +2638,7 @@ double GetTime(void) return glfwGetTime(); // Elapsed time since glfwInit() #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_NX) struct timespec ts = { 0 }; clock_gettime(CLOCK_MONOTONIC, &ts); unsigned long long int time = (unsigned long long int)ts.tv_sec*1000000000LLU + (unsigned long long int)ts.tv_nsec; @@ -3835,7 +3872,7 @@ static bool InitGraphicsDevice(int width, int height) } #endif // PLATFORM_DESKTOP || PLATFORM_WEB -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_NX) CORE.Window.fullscreen = true; CORE.Window.flags |= FLAG_FULLSCREEN_MODE; @@ -4025,8 +4062,10 @@ static bool InitGraphicsDevice(int width, int height) //EGL_TRANSPARENT_TYPE, EGL_NONE, // Request transparent framebuffer (EGL_TRANSPARENT_RGB does not work on RPI) EGL_DEPTH_SIZE, 16, // Depth buffer size (Required to use Depth testing!) //EGL_STENCIL_SIZE, 8, // Stencil buffer size +#if !defined(PLATFORM_NX) EGL_SAMPLE_BUFFERS, sampleBuffer, // Activate MSAA EGL_SAMPLES, samples, // 4x Antialiasing if activated (Free on MALI GPUs) +#endif EGL_NONE }; @@ -4036,7 +4075,7 @@ static bool InitGraphicsDevice(int width, int height) EGL_NONE }; -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_NX) EGLint numConfigs = 0; // Get an EGL device connection @@ -4203,7 +4242,21 @@ static bool InitGraphicsDevice(int width, int height) //--------------------------------------------------------------------------------- #endif // PLATFORM_RPI -#if defined(PLATFORM_DRM) +#if defined(PLATFORM_NX) + CORE.Window.gbmSurface = nwindowGetDefault(); + if (appletGetOperationMode() == AppletOperationMode_Console) + { + CORE.Window.display.width = 1920; + CORE.Window.display.height = 1080; + } + else if (appletGetOperationMode() == AppletOperationMode_Handheld) + { + CORE.Window.display.width = 1280; + CORE.Window.display.height = 720; + } +#endif + +#if defined(PLATFORM_DRM) || defined(PLATFORM_NX) CORE.Window.surface = eglCreateWindowSurface(CORE.Window.device, CORE.Window.config, (EGLNativeWindowType)CORE.Window.gbmSurface, NULL); if (EGL_NO_SURFACE == CORE.Window.surface) { @@ -4470,7 +4523,7 @@ void SwapScreenBuffer(void) glfwSwapBuffers(CORE.Window.handle); #endif -#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) +#if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_NX) eglSwapBuffers(CORE.Window.device, CORE.Window.surface); #if defined(PLATFORM_DRM) @@ -4522,7 +4575,7 @@ void SwapScreenBuffer(void) CORE.Window.prevBO = bo; #endif // PLATFORM_DRM -#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM +#endif // PLATFORM_ANDROID || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_NX } // Register all input events @@ -4773,6 +4826,206 @@ void PollInputEvents(void) } #endif +#if defined(PLATFORM_NX) + int nxGamepadIndex = 0; + + // Scan the gamepad. This should be done once for each frame + padUpdate(&CORE.Input.Gamepad.nxPad[nxGamepadIndex]); + + CORE.Input.Gamepad.ready[nxGamepadIndex] = padIsConnected(&CORE.Input.Gamepad.nxPad[nxGamepadIndex]); + + if (CORE.Input.Gamepad.ready[nxGamepadIndex]) { + // Returns the set of buttons that are currently pressed + u64 kHeld = padGetButtons(&CORE.Input.Gamepad.nxPad[nxGamepadIndex]); + u64 kButton; + for (int k = 0; k < MAX_GAMEPAD_BUTTONS; k++) + { + // Register previous gamepad states + CORE.Input.Gamepad.previousButtonState[nxGamepadIndex][k] = CORE.Input.Gamepad.currentButtonState[nxGamepadIndex][k]; + + // Check digital buttons + kButton = 0; + switch (k) + { + case GAMEPAD_BUTTON_LEFT_FACE_UP: kButton = HidNpadButton_Up; break; + case GAMEPAD_BUTTON_LEFT_FACE_RIGHT: kButton = HidNpadButton_Right; break; + case GAMEPAD_BUTTON_LEFT_FACE_DOWN: kButton = HidNpadButton_Down; break; + case GAMEPAD_BUTTON_LEFT_FACE_LEFT: kButton = HidNpadButton_Left; break; + case GAMEPAD_BUTTON_RIGHT_FACE_UP: kButton = HidNpadButton_X; break; + case GAMEPAD_BUTTON_RIGHT_FACE_RIGHT: kButton = HidNpadButton_A; break; + case GAMEPAD_BUTTON_RIGHT_FACE_DOWN: kButton = HidNpadButton_B; break; + case GAMEPAD_BUTTON_RIGHT_FACE_LEFT: kButton = HidNpadButton_Y; break; + case GAMEPAD_BUTTON_LEFT_TRIGGER_1: kButton = HidNpadButton_L; break; + case GAMEPAD_BUTTON_LEFT_TRIGGER_2: kButton = HidNpadButton_ZL; break; + case GAMEPAD_BUTTON_RIGHT_TRIGGER_1: kButton = HidNpadButton_R; break; + case GAMEPAD_BUTTON_RIGHT_TRIGGER_2: kButton = HidNpadButton_ZR; break; + case GAMEPAD_BUTTON_MIDDLE_LEFT: kButton = HidNpadButton_Minus; break; + case GAMEPAD_BUTTON_MIDDLE_RIGHT: kButton = HidNpadButton_Plus; break; + case GAMEPAD_BUTTON_LEFT_THUMB: kButton = HidNpadButton_StickL; break; + case GAMEPAD_BUTTON_RIGHT_THUMB: kButton = HidNpadButton_StickR; break; + } + if (kHeld & kButton) { + CORE.Input.Gamepad.currentButtonState[nxGamepadIndex][k] = 1; + CORE.Input.Gamepad.lastButtonPressed = k; + } else { + CORE.Input.Gamepad.currentButtonState[nxGamepadIndex][k] = 0; + } + } + + // Check analogic axis and buttons + HidAnalogStickState kAxisL = padGetStickPos(&CORE.Input.Gamepad.nxPad[nxGamepadIndex], 0); + HidAnalogStickState kAxisR = padGetStickPos(&CORE.Input.Gamepad.nxPad[nxGamepadIndex], 1); + + CORE.Input.Gamepad.axisState[nxGamepadIndex][GAMEPAD_AXIS_LEFT_X] = (float)kAxisL.x / 32767.0f; + CORE.Input.Gamepad.axisState[nxGamepadIndex][GAMEPAD_AXIS_LEFT_Y] = (float)kAxisL.y / 32767.0f; + CORE.Input.Gamepad.axisState[nxGamepadIndex][GAMEPAD_AXIS_RIGHT_X] = (float)kAxisR.x / 32767.0f; + CORE.Input.Gamepad.axisState[nxGamepadIndex][GAMEPAD_AXIS_RIGHT_Y] = (float)kAxisR.y / 32767.0f; + CORE.Input.Gamepad.axisState[nxGamepadIndex][GAMEPAD_AXIS_LEFT_TRIGGER] = (kHeld & HidNpadButton_ZL) ? 1.0f : 0.0f; + CORE.Input.Gamepad.axisState[nxGamepadIndex][GAMEPAD_AXIS_RIGHT_TRIGGER] = (kHeld & HidNpadButton_ZR) ? 1.0f : 0.0f; + +#if defined(NX_SUPPORT_GAMEPAD_EMULATION) + CORE.Input.Keyboard.previousKeyState[KEY_RIGHT] = CORE.Input.Keyboard.currentKeyState[KEY_RIGHT]; + CORE.Input.Keyboard.previousKeyState[KEY_D] = CORE.Input.Keyboard.currentKeyState[KEY_D]; + if (kHeld & HidNpadButton_Right || kHeld & HidNpadButton_StickLRight) { + CORE.Input.Keyboard.currentKeyState[KEY_RIGHT] = 1; + CORE.Input.Keyboard.currentKeyState[KEY_D] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_RIGHT] = 0; + CORE.Input.Keyboard.currentKeyState[KEY_D] = 0; + } + + CORE.Input.Keyboard.previousKeyState[KEY_LEFT] = CORE.Input.Keyboard.currentKeyState[KEY_LEFT]; + CORE.Input.Keyboard.previousKeyState[KEY_A] = CORE.Input.Keyboard.currentKeyState[KEY_A]; + if (kHeld & HidNpadButton_Left || kHeld & HidNpadButton_StickLLeft) { + CORE.Input.Keyboard.currentKeyState[KEY_LEFT] = 1; + CORE.Input.Keyboard.currentKeyState[KEY_A] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_LEFT] = 0; + CORE.Input.Keyboard.currentKeyState[KEY_A] = 0; + } + + CORE.Input.Keyboard.previousKeyState[KEY_DOWN] = CORE.Input.Keyboard.currentKeyState[KEY_DOWN]; + CORE.Input.Keyboard.previousKeyState[KEY_S] = CORE.Input.Keyboard.currentKeyState[KEY_S]; + if (kHeld & HidNpadButton_Down || kHeld & HidNpadButton_StickLDown) { + CORE.Input.Keyboard.currentKeyState[KEY_DOWN] = 1; + CORE.Input.Keyboard.currentKeyState[KEY_S] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_DOWN] = 0; + CORE.Input.Keyboard.currentKeyState[KEY_S] = 0; + } + + CORE.Input.Keyboard.previousKeyState[KEY_UP] = CORE.Input.Keyboard.currentKeyState[KEY_UP]; + CORE.Input.Keyboard.previousKeyState[KEY_W] = CORE.Input.Keyboard.currentKeyState[KEY_W]; + if (kHeld & HidNpadButton_Up || kHeld & HidNpadButton_StickLUp) { + CORE.Input.Keyboard.currentKeyState[KEY_UP] = 1; + CORE.Input.Keyboard.currentKeyState[KEY_W] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_UP] = 0; + CORE.Input.Keyboard.currentKeyState[KEY_W] = 0; + } + + CORE.Input.Keyboard.previousKeyState[KEY_Q] = CORE.Input.Keyboard.currentKeyState[KEY_Q]; + if (kHeld & HidNpadButton_Y) { + CORE.Input.Keyboard.currentKeyState[KEY_Q] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_Q] = 0; + } + + CORE.Input.Keyboard.previousKeyState[KEY_E] = CORE.Input.Keyboard.currentKeyState[KEY_E]; + if (kHeld & HidNpadButton_A) { + CORE.Input.Keyboard.currentKeyState[KEY_E] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_E] = 0; + } + + CORE.Input.Keyboard.previousKeyState[KEY_R] = CORE.Input.Keyboard.currentKeyState[KEY_R]; + if (kHeld & HidNpadButton_X) { + CORE.Input.Keyboard.currentKeyState[KEY_R] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_R] = 0; + } + + CORE.Input.Keyboard.previousKeyState[KEY_F] = CORE.Input.Keyboard.currentKeyState[KEY_F]; + if (kHeld & HidNpadButton_B) { + CORE.Input.Keyboard.currentKeyState[KEY_F] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_F] = 0; + } + + CORE.Input.Keyboard.previousKeyState[KEY_ENTER] = CORE.Input.Keyboard.currentKeyState[KEY_ENTER]; + CORE.Input.Keyboard.previousKeyState[KEY_SPACE] = CORE.Input.Keyboard.currentKeyState[KEY_SPACE]; + CORE.Input.Keyboard.previousKeyState[KEY_ESCAPE] = CORE.Input.Keyboard.currentKeyState[KEY_ESCAPE]; + if (kHeld & HidNpadButton_Plus && kHeld & HidNpadButton_Minus) { + CORE.Input.Keyboard.currentKeyState[KEY_ENTER] = 0; + CORE.Input.Keyboard.currentKeyState[KEY_SPACE] = 0; + CORE.Input.Keyboard.currentKeyState[KEY_ESCAPE] = 1; + } else { + if (kHeld & HidNpadButton_Plus) { + CORE.Input.Keyboard.currentKeyState[KEY_ENTER] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_ENTER] = 0; + } + if (kHeld & HidNpadButton_Minus) { + CORE.Input.Keyboard.currentKeyState[KEY_SPACE] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_SPACE] = 0; + } + } + + CORE.Input.Keyboard.previousKeyState[KEY_LEFT_SHIFT] = CORE.Input.Keyboard.currentKeyState[KEY_LEFT_SHIFT]; + if (kHeld & GAMEPAD_BUTTON_LEFT_THUMB) { + CORE.Input.Keyboard.currentKeyState[KEY_LEFT_SHIFT] = 1; + } else { + CORE.Input.Keyboard.currentKeyState[KEY_LEFT_SHIFT] = 0; + } + + CORE.Input.Mouse.previousButtonState[MOUSE_BUTTON_LEFT] = CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT]; + if (kHeld & HidNpadButton_ZL) { + CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_RIGHT] = 1; + } else { + CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_RIGHT] = 0; + } + + CORE.Input.Mouse.previousButtonState[MOUSE_BUTTON_MIDDLE] = CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_MIDDLE]; + if (kHeld & GAMEPAD_BUTTON_RIGHT_THUMB) { + CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_MIDDLE] = 1; + } else { + CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_MIDDLE] = 0; + } + + CORE.Input.Mouse.previousButtonState[MOUSE_BUTTON_RIGHT] = CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_RIGHT]; + if (kHeld & HidNpadButton_ZR) { + CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] = 1; + } else { + CORE.Input.Mouse.currentButtonState[MOUSE_BUTTON_LEFT] = 0; + } + + CORE.Input.Mouse.previousWheelMove = CORE.Input.Mouse.currentWheelMove; + if (kHeld & HidNpadButton_L) { + CORE.Input.Mouse.currentWheelMove = -1.0f; + } else if (kHeld & HidNpadButton_R) { + CORE.Input.Mouse.currentWheelMove = 1.0f; + } else { + CORE.Input.Mouse.currentWheelMove = 0.0f; + } + + CORE.Input.Mouse.previousPosition.x = CORE.Input.Mouse.currentPosition.x; + CORE.Input.Mouse.previousPosition.y = CORE.Input.Mouse.currentPosition.y; + + CORE.Input.Mouse.currentPosition.x += CORE.Input.Gamepad.axisState[nxGamepadIndex][GAMEPAD_AXIS_RIGHT_X] * 10; + CORE.Input.Mouse.currentPosition.y -= CORE.Input.Gamepad.axisState[nxGamepadIndex][GAMEPAD_AXIS_RIGHT_Y] * 10; + + if (CORE.Input.Mouse.currentPosition.x < 0) CORE.Input.Mouse.currentPosition.x = 0; + else if (CORE.Input.Mouse.currentPosition.x > CORE.Window.screen.width/CORE.Input.Mouse.scale.x) CORE.Input.Mouse.currentPosition.x = CORE.Window.screen.width/CORE.Input.Mouse.scale.x; + + if (CORE.Input.Mouse.currentPosition.y < 0) CORE.Input.Mouse.currentPosition.y = 0; + else if (CORE.Input.Mouse.currentPosition.y > CORE.Window.screen.height/CORE.Input.Mouse.scale.y) CORE.Input.Mouse.currentPosition.y = CORE.Window.screen.height/CORE.Input.Mouse.scale.y; + + if (CORE.Input.Keyboard.currentKeyState[CORE.Input.Keyboard.exitKey] == 1) CORE.Window.shouldClose = true; +#endif + } +#endif + #if (defined(PLATFORM_RPI) || defined(PLATFORM_DRM)) && defined(SUPPORT_SSH_KEYBOARD_RPI) // NOTE: Keyboard reading could be done using input_event(s) or just read from stdin, both methods are used here. // stdin reading is still used for legacy purposes, it allows keyboard input trough SSH console diff --git a/src/external/miniaudio.h b/src/external/miniaudio.h index 60522da51..a1b22c1f2 100644 --- a/src/external/miniaudio.h +++ b/src/external/miniaudio.h @@ -1556,6 +1556,10 @@ extern "C" { #ifdef __EMSCRIPTEN__ #define MA_EMSCRIPTEN #endif + #ifdef __SWITCH__ + #define MA_SWITCH + #define MA_NO_RUNTIME_LINKING + #endif #endif #include /* For size_t. */ @@ -2988,6 +2992,9 @@ This section contains the APIs for device playback and capture. Here is where yo #if defined(MA_EMSCRIPTEN) #define MA_SUPPORT_WEBAUDIO #endif +#if defined(MA_SWITCH) + #define MA_SUPPORT_SDL +#endif /* All platforms should support custom backends. */ #define MA_SUPPORT_CUSTOM @@ -3037,6 +3044,9 @@ This section contains the APIs for device playback and capture. Here is where yo #if defined(MA_SUPPORT_WEBAUDIO) && !defined(MA_NO_WEBAUDIO) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_WEBAUDIO)) #define MA_HAS_WEBAUDIO #endif +#if defined(MA_SUPPORT_SDL) && !defined(MA_NO_SDL) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_SDL)) + #define MA_HAS_SDL +#endif #if defined(MA_SUPPORT_CUSTOM) && !defined(MA_NO_CUSTOM) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_CUSTOM)) #define MA_HAS_CUSTOM #endif @@ -3076,6 +3086,7 @@ typedef enum ma_backend_aaudio, ma_backend_opensl, ma_backend_webaudio, + ma_backend_sdl, ma_backend_custom, /* <-- Custom backend, with callbacks defined by the context config. */ ma_backend_null /* <-- Must always be the last item. Lowest priority, and used as the terminator for backend enumeration. */ } ma_backend; @@ -3306,6 +3317,7 @@ typedef union ma_int32 aaudio; /* AAudio uses a 32-bit integer for identification. */ ma_uint32 opensl; /* OpenSL|ES uses a 32-bit unsigned integer for identification. */ char webaudio[32]; /* Web Audio always uses default devices for now, but if this changes it'll be a GUID. */ + int sdl; /* SDL devices are identified with an index. */ union { int i; @@ -3960,6 +3972,19 @@ struct ma_context int _unused; } webaudio; #endif +#ifdef MA_SUPPORT_SDL + struct + { + ma_handle hSDL; // SDL + ma_proc SDL_InitSubSystem; + ma_proc SDL_QuitSubSystem; + ma_proc SDL_GetNumAudioDevices; + ma_proc SDL_GetAudioDeviceName; + ma_proc SDL_CloseAudioDevice; + ma_proc SDL_OpenAudioDevice; + ma_proc SDL_PauseAudioDevice; + } sdl; +#endif #ifdef MA_SUPPORT_NULL struct { @@ -4247,6 +4272,12 @@ struct ma_device int indexCapture; } webaudio; #endif +#ifdef MA_SUPPORT_SDL + struct + { + ma_uint32 deviceID; + } sdl; +#endif #ifdef MA_SUPPORT_NULL struct { @@ -10391,7 +10422,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority int result; pthread_attr_t* pAttr = NULL; -#if !defined(__EMSCRIPTEN__) +#if !defined(__EMSCRIPTEN__) && !defined(__SWITCH__) /* Try setting the thread priority. It's not critical if anything fails here. */ pthread_attr_t attr; if (pthread_attr_init(&attr) == 0) { @@ -10900,7 +10931,9 @@ DEVICE I/O #ifdef MA_POSIX #include #include - #include + #ifndef MA_NO_RUNTIME_LINKING + #include + #endif #endif /* @@ -10919,6 +10952,24 @@ not officially supporting this, but I'm leaving it here in case it's useful for #endif #endif +#ifdef MA_ENABLE_SDL + #define ML_HAS_SDL + + // SDL headers are necessary if using compile-time linking. + #ifdef MA_NO_RUNTIME_LINKING + #ifdef __has_include + #ifdef MA_EMSCRIPTEN + #if !__has_include() + #undef MA_HAS_SDL + #endif + #else + #if !__has_include() + #undef MA_HAS_SDL + #endif + #endif + #endif + #endif +#endif MA_API void ma_device_info_add_native_data_format(ma_device_info* pDeviceInfo, ma_format format, ma_uint32 channels, ma_uint32 sampleRate, ma_uint32 flags) { @@ -10953,6 +11004,7 @@ MA_API const char* ma_get_backend_name(ma_backend backend) case ma_backend_aaudio: return "AAudio"; case ma_backend_opensl: return "OpenSL|ES"; case ma_backend_webaudio: return "Web Audio"; + case ma_backend_sdl: return "SDL"; case ma_backend_custom: return "Custom"; case ma_backend_null: return "Null"; default: return "Unknown"; @@ -11045,6 +11097,12 @@ MA_API ma_bool32 ma_is_backend_enabled(ma_backend backend) #else return MA_FALSE; #endif + case ma_backend_sdl: + #if defined(MA_HAS_SDL) + return MA_TRUE; + #else + return MA_FALSE; + #endif case ma_backend_custom: #if defined(MA_HAS_CUSTOM) return MA_TRUE; @@ -11113,6 +11171,7 @@ MA_API ma_bool32 ma_is_loopback_supported(ma_backend backend) case ma_backend_aaudio: return MA_FALSE; case ma_backend_opensl: return MA_FALSE; case ma_backend_webaudio: return MA_FALSE; + case ma_backend_sdl: return MA_FALSE; case ma_backend_custom: return MA_FALSE; /* <-- Will depend on the implementation of the backend. */ case ma_backend_null: return MA_FALSE; default: return MA_FALSE; @@ -11596,6 +11655,7 @@ Timing Dynamic Linking *******************************************************************************/ +#ifndef MA_NO_RUNTIME_LINKING MA_API ma_handle ma_dlopen(ma_context* pContext, const char* filename) { ma_handle handle; @@ -11687,7 +11747,7 @@ MA_API ma_proc ma_dlsym(ma_context* pContext, ma_handle handle, const char* symb (void)pContext; /* It's possible for pContext to be unused. */ return proc; } - +#endif #if 0 static ma_uint32 ma_get_closest_standard_sample_rate(ma_uint32 sampleRateIn) @@ -31985,6 +32045,227 @@ static ma_result ma_context_init__webaudio(ma_context* pContext, const ma_contex #endif /* Web Audio */ +/****************************************************************************** + +SDL Backend + +******************************************************************************/ +#ifdef MA_HAS_SDL + +#define MA_SDL_INIT_AUDIO 0x00000010 +#define MA_AUDIO_U8 0x0008 +#define MA_AUDIO_S16 0x8010 +#define MA_AUDIO_S32 0x8020 +#define MA_AUDIO_F32 0x8120 +#define MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE 0x00000001 +#define MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE 0x00000002 +#define MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE 0x00000004 +#define MA_SDL_AUDIO_ALLOW_ANY_CHANGE (MA_SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | MA_SDL_AUDIO_ALLOW_FORMAT_CHANGE | MA_SDL_AUDIO_ALLOW_CHANNELS_CHANGE) + +#include + +typedef int (* MA_PFN_SDL_InitSubSystem)(ma_uint32 flags); +typedef void (* MA_PFN_SDL_QuitSubSystem)(ma_uint32 flags); +typedef int (* MA_PFN_SDL_GetNumAudioDevices)(int iscapture); +typedef const char* (* MA_PFN_SDL_GetAudioDeviceName)(int index, int iscapture); +typedef void (* MA_PFN_SDL_CloseAudioDevice)(SDL_AudioDeviceID dev); +typedef SDL_AudioDeviceID (* MA_PFN_SDL_OpenAudioDevice)(const char* device, int iscapture, const SDL_AudioSpec* desired, SDL_AudioSpec* obtained, int allowed_changes); +typedef void (* MA_PFN_SDL_PauseAudioDevice)(SDL_AudioDeviceID dev, int pause_on); + +static SDL_AudioFormat ma_format_to_sdl(ma_format format) +{ + switch (format) + { + case ma_format_unknown: return 0; + case ma_format_u8: return MA_AUDIO_U8; + case ma_format_s16: return MA_AUDIO_S16; + case ma_format_s24: return MA_AUDIO_S32; // Closest match. + case ma_format_s32: return MA_AUDIO_S32; + case ma_format_f32: return MA_AUDIO_F32; + default: return 0; + } +} + +static ma_format ma_format_from_sdl(SDL_AudioFormat format) +{ + switch (format) + { + case MA_AUDIO_U8: return ma_format_u8; + case MA_AUDIO_S16: return ma_format_s16; + case MA_AUDIO_S32: return ma_format_s32; + case MA_AUDIO_F32: return ma_format_f32; + default: return ma_format_unknown; + } +} + +static ma_result ma_context_get_device_info__sdl(ma_context* pContext, ma_device_type deviceType, const ma_device_id* pDeviceID, ma_device_info* pDeviceInfo) +{ + MA_ASSERT(pContext != NULL); + MA_ASSERT(deviceType == ma_device_type_playback); + + pDeviceInfo->id.sdl = 0; + pDeviceInfo->isDefault = MA_TRUE; + + SDL_AudioSpec desiredSpec, obtainedSpec; + MA_ZERO_OBJECT(&desiredSpec); + + SDL_AudioDeviceID tempDeviceID = ((MA_PFN_SDL_OpenAudioDevice)pContext->sdl.SDL_OpenAudioDevice)(NULL, 0, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); + ((MA_PFN_SDL_CloseAudioDevice)pContext->sdl.SDL_CloseAudioDevice)(tempDeviceID); + + ma_format format = ma_format_from_sdl(obtainedSpec.format); + if (format == ma_format_unknown) { + format = ma_format_f32; + } + + pDeviceInfo->nativeDataFormatCount = 0; + ma_device_info_add_native_data_format(pDeviceInfo, format, obtainedSpec.channels, obtainedSpec.freq, 0); + + return MA_SUCCESS; +} + + +static ma_result ma_device_uninit__sdl(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + ((MA_PFN_SDL_CloseAudioDevice)pDevice->pContext->sdl.SDL_CloseAudioDevice)(pDevice->sdl.deviceID); + + return MA_SUCCESS; +} + + +static void ma_audio_callback__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) +{ + ma_device* pDevice = (ma_device*)pUserData; + MA_ASSERT(pDevice != NULL); + + ma_uint32 bytesPerFrame = ma_get_bytes_per_frame(pDevice->playback.internalFormat, pDevice->playback.internalChannels); + ma_uint32 frameCount = (ma_uint32)bufferSizeInBytes / bytesPerFrame; + + ma_device_handle_backend_data_callback(pDevice, pBuffer, NULL, frameCount); +} + +static ma_result ma_device_init__sdl(ma_device* pDevice, const ma_device_config* pConfig, ma_device_descriptor* pDescriptorPlayback, ma_device_descriptor* pDescriptorCapture) +{ + MA_ASSERT(pDevice != NULL); + MA_ASSERT(pConfig != NULL); + MA_ASSERT(pConfig->deviceType == ma_device_type_playback); + + if (pDescriptorPlayback->sampleRate == 0) { + pDescriptorPlayback->sampleRate = MA_DEFAULT_SAMPLE_RATE; + } + + pDescriptorPlayback->periodSizeInFrames = pDescriptorPlayback->periodCount * ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptorPlayback, pDescriptorPlayback->sampleRate, pConfig->performanceProfile); + + /* SDL wants the buffer size to be a power of 2 for some reason. */ + if (pDescriptorPlayback->periodSizeInFrames > 32768) { + pDescriptorPlayback->periodSizeInFrames = 32768; + } else { + pDescriptorPlayback->periodSizeInFrames = ma_next_power_of_2(pDescriptorPlayback->periodSizeInFrames); + } + + MA_ASSERT(pDescriptorPlayback->periodSizeInFrames <= 32768); + + /* We now have enough information to set up the device. */ + SDL_AudioSpec desiredSpec, obtainedSpec; + MA_ZERO_OBJECT(&desiredSpec); + desiredSpec.freq = (int)pDescriptorPlayback->sampleRate; + desiredSpec.format = ma_format_to_sdl(pDescriptorPlayback->format); + desiredSpec.channels = (ma_uint8)pDescriptorPlayback->channels; + desiredSpec.samples = (ma_uint16)pDescriptorPlayback->periodSizeInFrames; + desiredSpec.callback = ma_audio_callback__sdl; + desiredSpec.userdata = pDevice; + + /* We'll fall back to f32 if we don't have an appropriate mapping between SDL and miniaudio. */ + if (desiredSpec.format == ma_format_unknown) { + desiredSpec.format = MA_AUDIO_F32; + } + + pDevice->sdl.deviceID = ((MA_PFN_SDL_OpenAudioDevice)pDevice->pContext->sdl.SDL_OpenAudioDevice)(NULL, 0, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); + + /* The descriptor needs to be updated with our actual settings. */ + pDescriptorPlayback->format = ma_format_from_sdl(obtainedSpec.format); + pDescriptorPlayback->channels = obtainedSpec.channels; + pDescriptorPlayback->sampleRate = (ma_uint32)obtainedSpec.freq; + ma_get_standard_channel_map(ma_standard_channel_map_default, pDescriptorPlayback->channels, pDescriptorPlayback->channelMap); + pDescriptorPlayback->periodSizeInFrames = obtainedSpec.samples; + pDescriptorPlayback->periodCount = 1; /* SDL doesn't use the notion of period counts, so just set to 1. */ + + return MA_SUCCESS; +} + +static ma_result ma_device_start__sdl(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + ((MA_PFN_SDL_PauseAudioDevice)pDevice->pContext->sdl.SDL_PauseAudioDevice)(pDevice->sdl.deviceID, 0); + + return MA_SUCCESS; +} + +static ma_result ma_device_stop__sdl(ma_device* pDevice) +{ + MA_ASSERT(pDevice != NULL); + + ((MA_PFN_SDL_PauseAudioDevice)pDevice->pContext->sdl.SDL_PauseAudioDevice)(pDevice->sdl.deviceID, 1); + + ma_device__set_state(pDevice, MA_STATE_STOPPED); + ma_stop_proc onStop = pDevice->onStop; + if (onStop) { + onStop(pDevice); + } + + return MA_SUCCESS; +} + + +static ma_result ma_context_uninit__sdl(ma_context* pContext) +{ + MA_ASSERT(pContext != NULL); + MA_ASSERT(pContext->backend == ma_backend_sdl); + + ((MA_PFN_SDL_QuitSubSystem)pContext->sdl.SDL_QuitSubSystem)(MA_SDL_INIT_AUDIO); + + return MA_SUCCESS; +} + +static ma_result ma_context_init__sdl(ma_context* pContext, const ma_context_config* pConfig, ma_backend_callbacks* pCallbacks) +{ + MA_ASSERT(pContext != NULL); + + (void)pConfig; /* Unused. */ + + pContext->sdl.SDL_InitSubSystem = (ma_proc)SDL_InitSubSystem; + pContext->sdl.SDL_QuitSubSystem = (ma_proc)SDL_QuitSubSystem; + pContext->sdl.SDL_GetNumAudioDevices = (ma_proc)SDL_GetNumAudioDevices; + pContext->sdl.SDL_GetAudioDeviceName = (ma_proc)SDL_GetAudioDeviceName; + pContext->sdl.SDL_CloseAudioDevice = (ma_proc)SDL_CloseAudioDevice; + pContext->sdl.SDL_OpenAudioDevice = (ma_proc)SDL_OpenAudioDevice; + pContext->sdl.SDL_PauseAudioDevice = (ma_proc)SDL_PauseAudioDevice; + + int resultSDL = ((MA_PFN_SDL_InitSubSystem)pContext->sdl.SDL_InitSubSystem)(MA_SDL_INIT_AUDIO); + if (resultSDL != 0) { + return MA_ERROR; + } + + pCallbacks->onContextInit = ma_context_init__sdl; + pCallbacks->onContextUninit = ma_context_uninit__sdl; + pCallbacks->onContextEnumerateDevices = NULL; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__sdl; + pCallbacks->onDeviceInit = ma_device_init__sdl; + pCallbacks->onDeviceUninit = ma_device_uninit__sdl; + pCallbacks->onDeviceStart = ma_device_start__sdl; + pCallbacks->onDeviceStop = ma_device_stop__sdl; + pCallbacks->onDeviceRead = NULL; + pCallbacks->onDeviceWrite = NULL; + pCallbacks->onDeviceDataLoop = NULL; + + return MA_SUCCESS; +} +#endif /* SDL */ + + + static ma_bool32 ma__is_channel_map_valid(const ma_channel* channelMap, ma_uint32 channels) { @@ -32340,7 +32621,7 @@ static ma_result ma_context_init_backend_apis__nix(ma_context* pContext) pContext->posix.pthread_cond_signal = (ma_proc)pthread_cond_signal; pContext->posix.pthread_attr_init = (ma_proc)pthread_attr_init; pContext->posix.pthread_attr_destroy = (ma_proc)pthread_attr_destroy; -#if !defined(__EMSCRIPTEN__) +#if !defined(__EMSCRIPTEN__) && !defined(__SWITCH__) pContext->posix.pthread_attr_setschedpolicy = (ma_proc)pthread_attr_setschedpolicy; pContext->posix.pthread_attr_getschedparam = (ma_proc)pthread_attr_getschedparam; pContext->posix.pthread_attr_setschedparam = (ma_proc)pthread_attr_setschedparam; @@ -32536,6 +32817,12 @@ MA_API ma_result ma_context_init(const ma_backend backends[], ma_uint32 backendC pContext->callbacks.onContextInit = ma_context_init__webaudio; } break; #endif + #ifdef MA_HAS_SDL + case ma_backend_sdl: + { + pContext->callbacks.onContextInit = ma_context_init__sdl; + } break; + #endif #ifdef MA_HAS_CUSTOM case ma_backend_custom: { diff --git a/src/nxusb.c b/src/nxusb.c new file mode 100644 index 000000000..7a609d4ae --- /dev/null +++ b/src/nxusb.c @@ -0,0 +1,502 @@ +#include "nxusb.h" + +#include +#include +#include +#include +#include + +#define U64_MAX ((u64)~0ULL) +#define TOTAL_INTERFACES 4 +#define TOTAL_ENDPOINTS 4 +#define EP_IN 0 +#define EP_OUT 1 + +typedef enum { + UsbDirectionRead = 0, + UsbDirectionWrite = 1, +} UsbDirection; + +typedef struct { + struct usb_interface_descriptor *interface_desc; + struct usb_endpoint_descriptor *endpoint_desc[4]; + const char *string_descriptor; +} UsbInterfaceDesc; + +typedef struct { + UsbDsEndpoint *endpoint; + u8 *buffer; + RwLock lock; +} UsbCommsEndpoint; + +typedef struct { + RwLock lock; + bool initialized; + UsbDsInterface* interface; + u32 endpoint_number; + UsbCommsEndpoint endpoint[TOTAL_ENDPOINTS]; +} UsbCommsInterface; + +static bool g_usbCommsInitialized = false; +static UsbCommsInterface g_usbCommsInterfaces[TOTAL_INTERFACES]; +static RwLock g_usbCommsLock; +static int ep_in = 1; +static int ep_out = 1; + +static Result UsbCommsInterfaceInit(u32 intf_ind, const UsbInterfaceDesc *info) +{ + Result rc = 0; + UsbCommsInterface *interface = &g_usbCommsInterfaces[intf_ind]; + + u8 index = 0; + if (info->string_descriptor != NULL) + { + usbDsAddUsbStringDescriptor(&index, info->string_descriptor); + } + info->interface_desc->iInterface = index; + + struct usb_ss_endpoint_companion_descriptor endpoint_companion = { + .bLength = sizeof(struct usb_ss_endpoint_companion_descriptor), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMPANION, + .bMaxBurst = 0x0F, + .bmAttributes = 0x00, + .wBytesPerInterval = 0x00, + }; + + interface->initialized = 1; + + //The buffer for PostBufferAsync commands must be 0x1000-byte aligned. + for (u32 i = 0; i < interface->endpoint_number; i++) + { + interface->endpoint[i].buffer = (u8*)memalign(0x1000, 0x1000); + if (interface->endpoint[i].buffer == NULL) + { + rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + break; + } + memset(interface->endpoint[i].buffer, 0, 0x1000); + } + if (R_FAILED(rc)) return rc; + + rc = usbDsRegisterInterface(&interface->interface); + if (R_FAILED(rc)) return rc; + + info->interface_desc->bInterfaceNumber = interface->interface->interface_index; + for (u32 i = 0; i < interface->endpoint_number; i++) + { + if((info->endpoint_desc[i]->bEndpointAddress & USB_ENDPOINT_IN) != 0) + { + info->endpoint_desc[i]->bEndpointAddress |= ep_in; + ep_in++; + } + else + { + info->endpoint_desc[i]->bEndpointAddress |= ep_out; + ep_out++; + } + } + + // Full Speed Config + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, info->interface_desc, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + + for (u32 i = 0; i < interface->endpoint_number; i++) + { + if(info->endpoint_desc[i]->bmAttributes == USB_TRANSFER_TYPE_BULK) + info->endpoint_desc[i]->wMaxPacketSize = 0x40; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Full, info->endpoint_desc[i], USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + } + + // High Speed Config + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, info->interface_desc, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + + for (u32 i = 0; i < interface->endpoint_number; i++) + { + if(info->endpoint_desc[i]->bmAttributes == USB_TRANSFER_TYPE_BULK) + info->endpoint_desc[i]->wMaxPacketSize = 0x200; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_High, info->endpoint_desc[i], USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + } + + // Super Speed Config + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, info->interface_desc, USB_DT_INTERFACE_SIZE); + if (R_FAILED(rc)) return rc; + + for (u32 i = 0; i < interface->endpoint_number; i++) + { + if(info->endpoint_desc[i]->bmAttributes == USB_TRANSFER_TYPE_BULK) + info->endpoint_desc[i]->wMaxPacketSize = 0x400; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, info->endpoint_desc[i], USB_DT_ENDPOINT_SIZE); + if (R_FAILED(rc)) return rc; + rc = usbDsInterface_AppendConfigurationData(interface->interface, UsbDeviceSpeed_Super, &endpoint_companion, USB_DT_SS_ENDPOINT_COMPANION_SIZE); + if (R_FAILED(rc)) return rc; + } + + //Setup endpoints. + for (u32 i = 0; i < interface->endpoint_number; i++) + { + rc = usbDsInterface_RegisterEndpoint(interface->interface, &interface->endpoint[i].endpoint, info->endpoint_desc[i]->bEndpointAddress); + if (R_FAILED(rc)) return rc; + } + + rc = usbDsInterface_EnableInterface(interface->interface); + if (R_FAILED(rc)) return rc; + + return rc; +} + +static Result UsbCommsTransfer(UsbCommsEndpoint *ep, UsbDirection dir, const void* buffer, size_t size, u64 timeout, size_t *transferredSize) +{ + Result rc=0; + u32 urbId=0; + u32 chunksize=0; + u8 transfer_type=0; + u8 *bufptr = (u8*)buffer; + u8 *transfer_buffer = NULL; + u32 tmp_transferredSize = 0; + size_t total_transferredSize=0; + UsbDsReportData reportdata; + + //Makes sure endpoints are ready for data-transfer / wait for init if needed. + rc = usbDsWaitReady(U64_MAX); + if (R_FAILED(rc)) return rc; + + while(size) + { + //When bufptr isn't page-aligned copy the data into g_usbComms_endpoint_in_buffer and transfer that, otherwise use the bufptr directly. + if(((u64)bufptr) & 0xfff) + { + transfer_buffer = ep->buffer; + memset(ep->buffer, 0, 0x1000); + + chunksize = 0x1000; + //After this transfer, bufptr will be page-aligned (if size is large enough for another transfer). + chunksize-= ((u64)bufptr) & 0xfff; + if (sizebuffer, bufptr, chunksize); + + transfer_type = 0; + } + else + { + transfer_buffer = bufptr; + chunksize = size; + transfer_type = 1; + } + + //Start transfer. + rc = usbDsEndpoint_PostBufferAsync(ep->endpoint, transfer_buffer, chunksize, &urbId); + if(R_FAILED(rc)) return rc; + + //Wait for the transfer to finish. + rc = eventWait(&ep->endpoint->CompletionEvent, timeout); + + if (R_FAILED(rc)) + { + usbDsEndpoint_Cancel(ep->endpoint); + eventWait(&ep->endpoint->CompletionEvent, U64_MAX); + eventClear(&ep->endpoint->CompletionEvent); + return rc; + } + eventClear(&ep->endpoint->CompletionEvent); + + rc = usbDsEndpoint_GetReportData(ep->endpoint, &reportdata); + if (R_FAILED(rc)) return rc; + + rc = usbDsParseReportData(&reportdata, urbId, NULL, &tmp_transferredSize); + if (R_FAILED(rc)) return rc; + + if (tmp_transferredSize > chunksize) tmp_transferredSize = chunksize; + + total_transferredSize+= (size_t)tmp_transferredSize; + + if ((transfer_type==0) && (dir == UsbDirectionRead)) + memcpy(bufptr, transfer_buffer, tmp_transferredSize); + + bufptr+= tmp_transferredSize; + size-= tmp_transferredSize; + + if (tmp_transferredSize < chunksize) break; + } + + if (transferredSize) *transferredSize = total_transferredSize; + + return rc; +} + +static void UsbCommsInterfaceFree(UsbCommsInterface *interface) +{ + rwlockWriteLock(&interface->lock); + if (!interface->initialized) + { + rwlockWriteUnlock(&interface->lock); + return; + } + + interface->initialized = 0; + interface->interface = NULL; + + for (u32 i = 0; i < interface->endpoint_number; i++) + { + rwlockWriteLock(&interface->endpoint[i].lock); + interface->endpoint[i].endpoint = NULL; + free(interface->endpoint[i].buffer); + interface->endpoint[i].buffer = NULL; + rwlockWriteUnlock(&interface->endpoint[i].lock); + } + + rwlockWriteUnlock(&interface->lock); +} + +static size_t UsbTransfer(u32 interface, u32 endpoint, UsbDirection dir, void* buffer, size_t size, u64 timeout) +{ + size_t transferredSize=-1; + u32 state=0; + Result rc, rc2; + bool initialized; + + UsbCommsInterface *inter = &g_usbCommsInterfaces[interface]; + UsbCommsEndpoint *ep = &inter->endpoint[endpoint]; + rwlockReadLock(&inter->lock); + initialized = inter->initialized; + rwlockReadUnlock(&inter->lock); + if (!initialized) return 0; + + rwlockWriteLock(&ep->lock); + rc = UsbCommsTransfer(ep, dir, buffer, size, timeout, &transferredSize); + rwlockWriteUnlock(&ep->lock); + if (R_FAILED(rc)) + { + rc2 = usbDsGetState(&state); + if (R_SUCCEEDED(rc2)) + { + if (state!=5) + { + rwlockWriteLock(&ep->lock); + // If state changed during transfer, try again. usbDsWaitReady() will be called from this. + rc = UsbCommsTransfer(ep, dir, buffer, size, timeout, &transferredSize); + rwlockWriteUnlock(&ep->lock); + } + } + if (R_FAILED(rc)) + { + transferredSize = 0; + } + } + return transferredSize; +} + +/* + * REDIRECT IO FUNCTIONS + */ + +static ssize_t _write_stdout(struct _reent *r,void *fd,const char *ptr, size_t len) +{ + return UsbTransfer(0, EP_IN, UsbDirectionWrite, (void*)ptr, len, U64_MAX); +} + +static const devoptab_t dotab_stdout = { + "usb", + 0, + NULL, + NULL, + _write_stdout, + NULL, + NULL, + NULL +}; + +static void RedirectOutput(void) +{ + devoptab_list[STD_OUT] = &dotab_stdout; + devoptab_list[STD_ERR] = &dotab_stdout; + setvbuf(stdout, NULL , _IONBF, 0); + setvbuf(stderr, NULL , _IONBF, 0); +} + +/* + * PUBLIC FUNCTIONS + */ + +bool NxUsbDebuggerInit(void) +{ + u32 num_interfaces = 1; + + struct usb_device_descriptor device_descriptor = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = 0x0110, + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + .bMaxPacketSize0 = 0x40, + .idVendor = 0x057e, + .idProduct = 0x4000, + .bcdDevice = 0x0100, + .bNumConfigurations = 0x01 + }; + + struct usb_interface_descriptor serial_interface_descriptor = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceSubClass = USB_CLASS_VENDOR_SPEC, + .bInterfaceProtocol = USB_CLASS_VENDOR_SPEC, + }; + + struct usb_endpoint_descriptor serial_endpoint_descriptor_in = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_IN, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = 0x200, + }; + + struct usb_endpoint_descriptor serial_endpoint_descriptor_out = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = USB_ENDPOINT_OUT, + .bmAttributes = USB_TRANSFER_TYPE_BULK, + .wMaxPacketSize = 0x200, + }; + + UsbInterfaceDesc infos[1]; + infos[0].interface_desc = &serial_interface_descriptor; + infos[0].endpoint_desc[EP_IN] = &serial_endpoint_descriptor_in; + infos[0].endpoint_desc[EP_OUT] = &serial_endpoint_descriptor_out; + infos[0].string_descriptor = NULL; + + Result rc = 0; + rwlockWriteLock(&g_usbCommsLock); + + if (g_usbCommsInitialized) + { + rc = MAKERESULT(Module_Libnx, LibnxError_AlreadyInitialized); + } + else if (num_interfaces > TOTAL_INTERFACES) + { + rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } + else + { + rc = usbDsInitialize(); + + if (R_SUCCEEDED(rc)) { + if (hosversionAtLeast(5,0,0)) + { + u8 iManufacturer, iProduct, iSerialNumber; + static const u16 supported_langs[1] = {0x0409}; + // Send language descriptor + rc = usbDsAddUsbLanguageStringDescriptor(NULL, supported_langs, sizeof(supported_langs) / sizeof(u16)); + // Send manufacturer + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iManufacturer, "Nintendo"); + // Send product + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iProduct, "Nintendo Switch"); + // Send serial number + if (R_SUCCEEDED(rc)) rc = usbDsAddUsbStringDescriptor(&iSerialNumber, "SerialNumber"); + + // Send device descriptors + device_descriptor.iManufacturer = iManufacturer; + device_descriptor.iProduct = iProduct; + device_descriptor.iSerialNumber = iSerialNumber; + + // Full Speed is USB 1.1 + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Full, &device_descriptor); + + // High Speed is USB 2.0 + device_descriptor.bcdUSB = 0x0200; + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_High, &device_descriptor); + + // Super Speed is USB 3.0 + device_descriptor.bcdUSB = 0x0300; + // Upgrade packet size to 512 + device_descriptor.bMaxPacketSize0 = 0x09; + if (R_SUCCEEDED(rc)) rc = usbDsSetUsbDeviceDescriptor(UsbDeviceSpeed_Super, &device_descriptor); + + // Define Binary Object Store + u8 bos[0x16] = { + 0x05, // .bLength + USB_DT_BOS, // .bDescriptorType + 0x16, 0x00, // .wTotalLength + 0x02, // .bNumDeviceCaps + + // USB 2.0 + 0x07, // .bLength + USB_DT_DEVICE_CAPABILITY, // .bDescriptorType + 0x02, // .bDevCapabilityType + 0x02, 0x00, 0x00, 0x00, // dev_capability_data + + // USB 3.0 + 0x0A, // .bLength + USB_DT_DEVICE_CAPABILITY, // .bDescriptorType + 0x03, // .bDevCapabilityType + 0x00, 0x0E, 0x00, 0x03, 0x00, 0x00, 0x00 + }; + if (R_SUCCEEDED(rc)) rc = usbDsSetBinaryObjectStore(bos, sizeof(bos)); + } + + if (R_SUCCEEDED(rc)) + { + for (u32 i = 0; i < num_interfaces; i++) + { + UsbCommsInterface *intf = &g_usbCommsInterfaces[i]; + const UsbInterfaceDesc *info = &infos[i]; + intf->endpoint_number = info->interface_desc->bNumEndpoints; + rwlockWriteLock(&intf->lock); + for (u32 j = 0; j < intf->endpoint_number; j++) + { + rwlockWriteLock(&intf->endpoint[j].lock); + } + rc = UsbCommsInterfaceInit(i, info); + for (u32 j = 0; j < intf->endpoint_number; j++) + { + rwlockWriteUnlock(&intf->endpoint[j].lock); + } + rwlockWriteUnlock(&intf->lock); + if (R_FAILED(rc)) break; + } + } + } + + if (R_SUCCEEDED(rc) && hosversionAtLeast(5,0,0)) + { + rc = usbDsEnable(); + } + + if (R_FAILED(rc)) + { + NxUsbDebuggerEnd(); + } + } + + if (R_SUCCEEDED(rc)) + { + g_usbCommsInitialized = true; + } + + rwlockWriteUnlock(&g_usbCommsLock); + + if (g_usbCommsInitialized) RedirectOutput(); + return g_usbCommsInitialized; +} + +void NxUsbDebuggerEnd(void) +{ + rwlockWriteLock(&g_usbCommsLock); + + usbDsExit(); + + g_usbCommsInitialized = false; + + rwlockWriteUnlock(&g_usbCommsLock); + + for (u32 i = 0; i < TOTAL_INTERFACES; i++) + { + UsbCommsInterfaceFree(&g_usbCommsInterfaces[i]); + } +} diff --git a/src/nxusb.h b/src/nxusb.h new file mode 100644 index 000000000..b3b4b3534 --- /dev/null +++ b/src/nxusb.h @@ -0,0 +1,10 @@ +#ifndef NXUSB_H +#define NXUSB_H + +#include +#include + +bool NxUsbDebuggerInit(void); +void NxUsbDebuggerEnd(void); + +#endif // NXUSB_H