diff --git a/.github/workflows/build-coop.yaml b/.github/workflows/build-coop.yaml index ae4ce8a8c..de78e7fce 100644 --- a/.github/workflows/build-coop.yaml +++ b/.github/workflows/build-coop.yaml @@ -3,7 +3,7 @@ name: Build coop on: workflow_dispatch: push: - branches: [ dev ] + branches: [ dev, nx-support ] jobs: build-linux: @@ -241,3 +241,28 @@ jobs: with: name: sm64coopdx-macos-intel path: ./build/us_pc/sm64coopdx_macOS_Intel.zip + + build-switch: + if: ${{ contains(github.event.head_commit.message, '[build]') || github.event_name == 'workflow_dispatch' }} + runs-on: ubuntu-latest + container: devkitpro/devkita64:latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install dependencies + run: | + sudo apt update + sudo apt install -y build-essential bsdmainutils git python3 + + - name: Build the game + run: | + export DEVKITA64=${DEVKITPRO}/devkitA64 + export PATH=${DEVKITPRO}/tools/bin:${DEVKITA64}/bin:$PATH + make TARGET_NX=1 BUILD_NRO=1 CONTROLLER_API=SWITCH -j$(nproc) + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: sm64coopdx-switch + path: ./build/us_nx/sm64coopdx.nro diff --git a/Makefile b/Makefile index 3739f7b3c..e17e49264 100644 --- a/Makefile +++ b/Makefile @@ -31,6 +31,9 @@ TARGET_RPI ?= 0 # Build and optimize for RK3588 processor TARGET_RK3588 ?= 0 +# Build and optimize for the Nintendo Switch +TARGET_NX ?= 0 + # Makeflag to enable OSX fixes OSX_BUILD ?= 0 @@ -71,7 +74,7 @@ else MIN_MACOS_VERSION ?= 10.15 endif # Make some small adjustments for handheld devices -HANDHELD ?= 0 +HANDHELD ?= $(TARGET_NX) # Various workarounds for weird toolchains NO_BZERO_BCOPY ?= 0 @@ -85,7 +88,7 @@ RENDER_API ?= GL WINDOW_API ?= SDL2 # Audio backends: SDL1, SDL2, DUMMY AUDIO_API ?= SDL2 -# Controller backends (can have multiple, space separated): SDL2, SDL1 +# Controller backends (can have multiple, space separated): SDL2, SDL1, SWITCH CONTROLLER_API ?= SDL2 # Automatic settings for PC port(s) @@ -94,6 +97,11 @@ WINDOWS_BUILD ?= 0 WINDOWS_AUTO_BUILDER ?= 0 +# Switch Only Settings +ifeq ($(TARGET_NX), 1) +BUILD_NRO ?= 0 +endif + # Setup extra cflags EXTRA_CFLAGS ?= EXTRA_CPP_FLAGS ?= @@ -119,7 +127,9 @@ else endif ifeq ($(HOST_OS),Windows) - WINDOWS_BUILD := 1 + ifneq ($(TARGET_NX),1) + WINDOWS_BUILD := 1 + endif endif ifeq ($(HOST_OS),Darwin) @@ -208,6 +218,7 @@ $(eval $(call validate-option,VERSION,us)) # Graphics microcode used GRUCODE ?= f3dex2e + ifeq ($(VERSION),jp) DEFINES += VERSION_JP=1 #GRUCODE ?= f3d_old @@ -286,8 +297,8 @@ else endif ifeq ($(TARGET_RPI),1) - $(info Compiling for Raspberry Pi) - DISCORD_SDK := 0 + $(info Compiling for Raspberry Pi) + DISCORD_SDK := 0 # Raspberry Pi B+, Zero, etc ifneq (,$(findstring armv6l,$(machine))) @@ -296,7 +307,7 @@ ifeq ($(TARGET_RPI),1) # Raspberry Pi 2 and 3 in ARM 32bit mode ifneq (,$(findstring armv7l,$(machine))) - $(info ARM 32bit mode) + $(info ARM 32bit mode) model = $(shell sh -c 'cat /sys/firmware/devicetree/base/model 2>/dev/null || echo unknown') ifneq (,$(findstring 3,$(model))) OPT_FLAGS := -march=armv8-a+crc -mtune=cortex-a53 -mfpu=neon-fp-armv8 -O3 @@ -308,7 +319,7 @@ ifeq ($(TARGET_RPI),1) # RPi3 or RPi4, in ARM64 (aarch64) mode. NEEDS TESTING 32BIT. # DO NOT pass -mfpu stuff here, thats for 32bit ARM only and will fail for 64bit ARM. ifneq (,$(findstring aarch64,$(machine))) - $(info ARM64 mode) + $(info ARM64 mode) model = $(shell sh -c 'cat /sys/firmware/devicetree/base/model 2>/dev/null || echo unknown') ifneq (,$(findstring 3,$(model))) OPT_FLAGS := -march=armv8-a+crc -mtune=cortex-a53 -O3 @@ -316,9 +327,43 @@ ifeq ($(TARGET_RPI),1) OPT_FLAGS := -march=armv8-a+crc+simd -mtune=cortex-a72 -O3 endif endif -endif +else ifeq ($(TARGET_NX),1) # Nintendo Switch + $(info Compiling for Nintendo Switch) + RENDER_API := GL + WINDOW_API := SDL2 + AUDIO_API := SDL2 + DISCORD_SDK := 0 -ifeq ($(TARGET_RK3588),1) + ifeq ($(strip $(DEVKITPRO)),) + $(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") + endif + + export PATH := $(DEVKITPRO)/tools/bin:$(DEVKITPRO)/devkitA64/bin:$(PATH) + include $(DEVKITPRO)/devkitA64/base_tools + NXPATH := $(DEVKITPRO)/portlibs/switch/bin + PORTLIBS := $(DEVKITPRO)/portlibs/switch + LIBNX ?= $(DEVKITPRO)/libnx + CROSS ?= aarch64-none-elf- + SDLCROSS := $(NXPATH)/ + CC := $(CROSS)gcc + CXX := $(CROSS)g++ + STRIP := $(CROSS)strip + + OPT_FLAGS := -ffunction-sections -fdata-sections -march=armv8-a+crc+crypto+simd -mtune=cortex-a57 -mtp=soft -ftls-model=local-exec -fwrapv -fPIC + DEFINES += __SWITCH__=1 __CONSOLE__=1 USE_GLES=1 + + ifeq ($(BUILD_NRO),1) + DEFINES += BUILD_NRO=1 + endif + + APP_TITLE := SM64 Coop DX + APP_AUTHOR := The Coop DX Team + APP_VERSION := 1.0.0 + APP_ICON := res/icon_AmericanEnglish.jpg + APP_TITLEID := 0100534d36344350 + APP_JSON := res/npdm.json + ICON := res/icon_AmericanEnglish.dat +else ifeq ($(TARGET_RK3588),1) $(info Compiling for RK3588) DISCORD_SDK := 0 COOPNET := 0 @@ -330,7 +375,9 @@ ifeq ($(TARGET_RK3588),1) endif # Set BITS (32/64) to compile for -OPT_FLAGS += $(BITS) +ifeq ($(TARGET_NX),0) + OPT_FLAGS += $(BITS) +endif TARGET := sm64.$(VERSION) @@ -486,23 +533,35 @@ _ := $(shell $(PYTHON) $(TOOLS_DIR)/copy_extended_sounds.py) BUILD_DIR_BASE := build # BUILD_DIR is the location where all build artifacts are placed -BUILD_DIR := $(BUILD_DIR_BASE)/$(VERSION)_pc -ifeq ($(WINDOWS_BUILD),1) - EXE := $(BUILD_DIR)/sm64coopdx.exe -else # Linux builds/binary namer - ifeq ($(TARGET_RPI),1) - EXE := $(BUILD_DIR)/sm64coopdx.arm - else - EXE := $(BUILD_DIR)/sm64coopdx - endif + +ifeq ($(TARGET_RPI),1) + BUILD_DIR := $(BUILD_DIR_BASE)/$(VERSION)_rpi + EXE := $(BUILD_DIR)/sm64coopdx.arm +else ifeq ($(TARGET_NX),1) # Nintendo Switch + BUILD_DIR := $(BUILD_DIR_BASE)/$(VERSION)_nx + ELF := $(BUILD_DIR)/sm64coopdx.elf + ifeq ($(BUILD_NRO),1) + EXE := $(BUILD_DIR)/sm64coopdx.nro + else + EXE := $(BUILD_DIR)/sm64coopdx.nsp + endif +else ifeq ($(TARGET_RK3588),1) + EXE := $(BUILD_DIR)/sm64coopdx.arm +else ifeq ($(TARGET_N64),1) # Nintendo 64 (Unused) + BUILD_DIR := $(BUILD_DIR_BASE)/$(VERSION) + ELF := $(BUILD_DIR)/sm64coopdx.elf + EXE := $(BUILD_DIR)/sm64coopdx.n64 +else ifeq ($(WINDOWS_BUILD),1) + BUILD_DIR := $(BUILD_DIR_BASE)/$(VERSION)_pc + EXE := $(BUILD_DIR)/sm64coopdx.exe +else + BUILD_DIR := $(BUILD_DIR_BASE)/$(VERSION)_pc + EXE := $(BUILD_DIR)/sm64coopdx endif -ifeq ($(TARGET_RK3588),1) - EXE := $(BUILD_DIR)/sm64coopdx.arm -endif +MAP := $(BUILD_DIR)/sm64coopdx.map -ELF := $(BUILD_DIR)/$(TARGET).elf LIBULTRA := $(BUILD_DIR)/libultra.a LD_SCRIPT := sm64.ld MIO0_DIR := $(BUILD_DIR)/bin @@ -617,6 +676,16 @@ ifeq ($(DISCORD_SDK), 1) endif endif + +ifeq ($(TARGET_NX),1) + +ROMFS_DIR := romfs + +# Remove old romfs dir +_ := $(shell rm -rf ./$(BUILD_DIR)/$(ROMFS_DIR)) + +endif + LANG_DIR := lang # Remove old lang dir @@ -654,10 +723,14 @@ endif # Compiler Options # #==============================================================================# -AS := $(CROSS)as +SDLCROSS ?= $(CROSS) ifeq ($(OSX_BUILD),1) AS := i686-w64-mingw32-as +else ifeq ($(TARGET_NX),1) # Nintendo Switch + AS := aarch64-none-elf-as +else + AS := $(CROSS)as endif ifeq ($(WINDOWS_AUTO_BUILDER),1) @@ -690,11 +763,7 @@ else endif endif -ifeq ($(WINDOWS_BUILD),1) # fixes compilation in MXE on Linux and WSL - CPP := cpp -P - OBJCOPY := objcopy - OBJDUMP := $(CROSS)objdump -else ifeq ($(OSX_BUILD),1) +ifeq ($(OSX_BUILD),1) OSX_GCC_VER = $(shell find $(BREW_PREFIX)/bin/gcc* | grep -oE '[[:digit:]]+' | sort -n | uniq | tail -1) # if we couldn't find a gcc ver, default to 9 ifeq ($(OSX_GCC_VER),) @@ -707,6 +776,10 @@ else ifeq ($(TARGET_N64),0) # Linux & other builds CPP := $(CROSS)cpp -P OBJCOPY := $(CROSS)objcopy OBJDUMP := $(CROSS)objdump +else ifeq ($(WINDOWS_BUILD),1) # fixes compilation in MXE on Linux and WSL + CPP := cpp -P + OBJCOPY := objcopy + OBJDUMP := $(CROSS)objdump else # Prefer gcc's cpp if installed on the system ifneq (,$(call find-command,cpp-10)) @@ -755,11 +828,14 @@ ifeq ($(TARGET_N64),1) INCLUDE_DIRS += include/libc else INCLUDE_DIRS += sound lib/lua/include lib/coopnet/include $(EXTRA_INCLUDES) + ifeq ($(TARGET_NX),1) + INCLUDE_DIRS += ${PORTLIBS}/include system ${DEVKITPRO}/libnx/include + endif endif # Connfigure backend flags -SDLCONFIG := $(CROSS)sdl2-config +SDLCONFIG := $(SDLCROSS)sdl2-config BACKEND_CFLAGS := -DRAPI_$(RENDER_API)=1 -DWAPI_$(WINDOW_API)=1 -DAAPI_$(AUDIO_API)=1 # can have multiple controller APIs @@ -781,12 +857,15 @@ else ifeq ($(findstring SDL,$(WINDOW_API)),SDL) BACKEND_LDFLAGS += -lGLESv2 else ifeq ($(TARGET_RK3588),1) BACKEND_LDFLAGS += -lGLESv2 + else ifeq ($(TARGET_NX),1) # Nintendo Switch + BACKEND_LDFLAGS += -lGLESv2 + EXTRA_CPP_FLAGS += -std=gnu++17 -fsanitize=builtin -fstack-protector else ifeq ($(OSX_BUILD),1) BACKEND_LDFLAGS += -framework OpenGL `pkg-config --libs glew` -mmacosx-version-min=$(MIN_MACOS_VERSION) EXTRA_CPP_FLAGS += -stdlib=libc++ -std=c++17 -mmacosx-version-min=$(MIN_MACOS_VERSION) else BACKEND_LDFLAGS += -lGL - endif + endif endif ifeq ($(WINDOW_API),DUMMY) @@ -810,10 +889,10 @@ endif # SDL can be used by different systems, so we consolidate all of that shit into this ifeq ($(SDL2_USED),1) - SDLCONFIG := $(CROSS)sdl2-config + SDLCONFIG := $(SDLCROSS)sdl2-config BACKEND_CFLAGS += -DHAVE_SDL2=1 else ifeq ($(SDL1_USED),1) - SDLCONFIG := $(CROSS)sdl-config + SDLCONFIG := $(SDLCROSS)sdl-config BACKEND_CFLAGS += -DHAVE_SDL1=1 endif @@ -826,7 +905,9 @@ ifneq ($(SDL1_USED)$(SDL2_USED),00) BACKEND_CFLAGS += `$(SDLCONFIG) --cflags` endif - ifeq ($(WINDOWS_BUILD),1) + ifeq ($(TARGET_NX),1) # Nintendo Switch + BACKEND_LDFLAGS += `$(SDLCONFIG) --libs` + else ifeq ($(WINDOWS_BUILD),1) BACKEND_LDFLAGS += `$(SDLCONFIG) --static-libs` -lsetupapi -luser32 -limm32 -lole32 -loleaut32 -lshell32 -lshlwapi -lwinmm -lversion else BACKEND_LDFLAGS += `$(SDLCONFIG) --libs` @@ -839,17 +920,7 @@ DEF_INC_CFLAGS := $(foreach i,$(INCLUDE_DIRS),-I$(i)) $(C_DEFINES) # Check code syntax with host compiler CC_CHECK := $(CC) -ifeq ($(WINDOWS_BUILD),1) - CC_CHECK_CFLAGS := -fsyntax-only -fsigned-char $(BACKEND_CFLAGS) $(DEF_INC_CFLAGS) -Wall -Wextra $(TARGET_CFLAGS) -DWINSOCK - CFLAGS := $(OPT_FLAGS) $(DEF_INC_CFLAGS) $(BACKEND_CFLAGS) $(TARGET_CFLAGS) -fno-strict-aliasing -fwrapv -DWINSOCK - - ifeq ($(TARGET_BITS), 32) - BACKEND_LDFLAGS += -ldbghelp - endif -else ifeq ($(TARGET_N64),0) # Linux / Other builds below - CC_CHECK_CFLAGS := -fsyntax-only -fsigned-char $(BACKEND_CFLAGS) $(DEF_INC_CFLAGS) -Wall -Wextra $(TARGET_CFLAGS) - CFLAGS := $(OPT_FLAGS) $(DEF_INC_CFLAGS) $(BACKEND_CFLAGS) $(TARGET_CFLAGS) -fno-strict-aliasing -fwrapv -else # C compiler options for N64 +ifeq ($(TARGET_N64),1) # C compiler options for N64 CC_CHECK_CFLAGS := -fsyntax-only -fsigned-char $(CC_CFLAGS) $(TARGET_CFLAGS) -std=gnu90 -Wall -Wextra -Wno-main -DNON_MATCHING -DAVOID_UB $(DEF_INC_CFLAGS) CFLAGS = -G 0 $(OPT_FLAGS) $(TARGET_CFLAGS) $(MIPSISET) $(DEF_INC_CFLAGS) ifeq ($(COMPILER),gcc) @@ -857,6 +928,19 @@ else # C compiler options for N64 else CFLAGS += -non_shared -Wab,-r4300_mul -Xcpluscomm -Xfullwarn -signed -32 endif +else ifeq ($(TARGET_NX),1) # Nintendo Switch + CC_CHECK_CFLAGS := -fsyntax-only -fsigned-char $(BACKEND_CFLAGS) $(DEF_INC_CFLAGS) -Wall -Wextra $(TARGET_CFLAGS) + CFLAGS := $(OPT_FLAGS) $(DEF_INC_CFLAGS) $(BACKEND_CFLAGS) $(TARGET_CFLAGS) -fno-strict-aliasing -fwrapv -std=gnu17 +else ifeq ($(WINDOWS_BUILD),1) + CC_CHECK_CFLAGS := -fsyntax-only -fsigned-char $(BACKEND_CFLAGS) $(DEF_INC_CFLAGS) -Wall -Wextra $(TARGET_CFLAGS) -DWINSOCK + CFLAGS := $(OPT_FLAGS) $(DEF_INC_CFLAGS) $(BACKEND_CFLAGS) $(TARGET_CFLAGS) -fno-strict-aliasing -fwrapv -DWINSOCK + + ifeq ($(TARGET_BITS), 32) + BACKEND_LDFLAGS += -ldbghelp + endif +else # Linux / Other builds below + CC_CHECK_CFLAGS := -fsyntax-only -fsigned-char $(BACKEND_CFLAGS) $(DEF_INC_CFLAGS) -Wall -Wextra $(TARGET_CFLAGS) + CFLAGS := $(OPT_FLAGS) $(DEF_INC_CFLAGS) $(BACKEND_CFLAGS) $(TARGET_CFLAGS) -fno-strict-aliasing -fwrapv endif ifeq ($(TARGET_N64),1) @@ -887,9 +971,11 @@ ifeq ($(WINDOWS_BUILD),1) endif LDFLAGS += -T windows.ld else ifeq ($(TARGET_RPI),1) - LDFLAGS := $(OPT_FLAGS) -lm $(BACKEND_LDFLAGS) -no-pie + LDFLAGS := $(OPT_FLAGS) $(BACKEND_LDFLAGS) -no-pie else ifeq ($(TARGET_RK3588),1) LDFLAGS := $(OPT_FLAGS) -lm $(BACKEND_LDFLAGS) -no-pie +else ifeq ($(TARGET_NX),1) # Nintendo Switch + LDFLAGS := -specs=$(LIBNX)/switch.specs $(OPT_FLAGS) -L$(LIBNX)/lib -L$(PORTLIBS)/lib $(BACKEND_LDFLAGS) else ifeq ($(OSX_BUILD),1) LDFLAGS := -lm $(BACKEND_LDFLAGS) -lpthread else @@ -898,7 +984,11 @@ endif # used by crash handler and loading screen on linux ifeq ($(WINDOWS_BUILD),0) - LDFLAGS += -rdynamic -ldl -pthread + ifeq ($(TARGET_NX),1) + LDFLAGS += -pthread + else + LDFLAGS += -rdynamic -ldl -pthread + endif endif # icon @@ -954,13 +1044,15 @@ else ifeq ($(OSX_BUILD),1) LDFLAGS += -L./lib/lua/mac_intel/ -l lua53 endif else ifeq ($(TARGET_RPI),1) - ifneq (,$(findstring aarch64,$(machine))) + ifneq (,$(findstring aarch64,$(machine))) LDFLAGS += -Llib/lua/linux -l:liblua53-arm64.a else LDFLAGS += -Llib/lua/linux -l:liblua53-arm.a endif else ifeq ($(TARGET_RK3588),1) LDFLAGS += -Llib/lua/linux -l:liblua53-arm64.a +else ifeq ($(TARGET_NX),1) + LDFLAGS += -Llib/lua/nx -l:liblua53.a else LDFLAGS += -Llib/lua/linux -l:liblua53.a -ldl endif @@ -992,6 +1084,8 @@ ifeq ($(COOPNET),1) endif else ifeq ($(TARGET_RK3588),1) LDFLAGS += -Llib/coopnet/linux -l:libcoopnet-arm64.a -l:libjuice.a + else ifeq ($(TARGET_NX),1) + LDFLAGS += -Llib/coopnet/nx -l:libcoopnet.a -l:libjuice.a else LDFLAGS += -Llib/coopnet/linux -l:libcoopnet.a -l:libjuice.a endif @@ -1026,11 +1120,18 @@ endif # Enable ASLR CFLAGS += -fPIE +# Generate a map file +ifeq ($(OSX_BUILD),0) + LDFLAGS += -Wl,-Map $(MAP) +endif + # Prevent a crash with -sopt export LANG := C -ifeq ($(OSX_BUILD),0) - LDFLAGS += -latomic +ifeq ($(TARGET_NX),0) + ifeq ($(OSX_BUILD),0) + LDFLAGS += -latomic + endif endif #==============================================================================# @@ -1217,14 +1318,25 @@ $(BUILD_DIR)/$(DISCORD_SDK_LIBS): $(BUILD_DIR)/$(COOPNET_LIBS): @$(CP) -f $(COOPNET_LIBS) $(BUILD_DIR) +ifeq ($(TARGET_NX),1) +$(BUILD_DIR)/$(ROMFS_DIR): + @mkdir -p $(BUILD_DIR)/$(ROMFS_DIR) + +$(BUILD_DIR)/$(LANG_DIR): $(BUILD_DIR)/$(ROMFS_DIR) + @$(CP) -f -r $(LANG_DIR) $(BUILD_DIR)/$(ROMFS_DIR)/$(LANG_DIR)/ + +$(BUILD_DIR)/$(PALETTES_DIR): $(BUILD_DIR)/$(ROMFS_DIR) + @$(CP) -f -r $(PALETTES_DIR) $(BUILD_DIR)/$(ROMFS_DIR)/$(PALETTES_DIR)/ +else $(BUILD_DIR)/$(LANG_DIR): @$(CP) -f -r $(LANG_DIR) $(BUILD_DIR) -$(BUILD_DIR)/$(MOD_DIR): - $(CP) -f -r $(MOD_DIR) $(BUILD_DIR) - $(BUILD_DIR)/$(PALETTES_DIR): @$(CP) -f -r $(PALETTES_DIR) $(BUILD_DIR) +endif + +$(BUILD_DIR)/$(MOD_DIR): + $(CP) -f -r $(MOD_DIR) $(BUILD_DIR) # Extra object file dependencies @@ -1342,6 +1454,57 @@ $(BUILD_DIR)/%.mio0.o: $(BUILD_DIR)/%.mio0 endif +#==============================================================================# +# Nintendo Switch App Generation # +#==============================================================================# + +ifeq ($(TARGET_NX),1) + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(APP_ICON),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(BUILD_DIR)/$(ROMFS_DIR) +endif + +define make_pfs0 + $(V)mkdir -p $(BUILD_DIR)/exefs + $(V)[ $(BUILD_EXEFS_SRC) ] && [ -d $(BUILD_EXEFS_SRC) ] && cp -R $(BUILD_EXEFS_SRC)/* $(BUILD_DIR)/exefs || echo > /dev/null + $(V)cp $*.nso $(BUILD_DIR)/exefs/main + $(V)[ $(APP_JSON) ] && cp $*.npdm $(BUILD_DIR)/exefs/main.npdm || echo > /dev/null + $(V)build_pfs0 $(BUILD_DIR)/exefs $@ +endef + +%.nacp: $(MAKEFILE_LIST) + $(V)nacptool --create "$(APP_TITLE)" "$(APP_AUTHOR)" "$(APP_VERSION)" $@ $(NACPFLAGS) + @$(PRINT) "$(GREEN)Built NACP: $(BLUE)$(notdir $@) $(NO_COL)\n" + +%.nro: %.elf %.nacp + $(V)elf2nro $< $@ --nacp=$(BUILD_DIR)/sm64coopdx.nacp $(NROFLAGS) + @$(PRINT) "$(GREEN)Built NRO: $(BLUE)$(notdir $@) $(NO_COL)\n" + +%.nso: %.elf + $(V)elf2nso $< $@ + @$(PRINT) "$(GREEN)Built NSO: $(BLUE)$(notdir $@) $(NO_COL)\n" + +%.npdm: $(APP_JSON) + $(V)npdmtool $< $@ + @$(PRINT) "$(GREEN)Built NPDM: $(BLUE)$(notdir $@) $(NO_COL)\n" + +%.pfs0: %.nso %.npdm + $(make_pfs0) + @$(PRINT) "$(GREEN)Built PFS0: $(BLUE)$(notdir $@) $(NO_COL)\n" + +%.nsp: %.nso %.npdm %.nacp + $(make_pfs0) + @$(PRINT) "$(GREEN)Built NSP: $(BLUE)$(notdir $@) $(NO_COL)\n" + +endif #==============================================================================# # Sound File Generation # @@ -1564,6 +1727,10 @@ ifeq ($(TARGET_N64),1) $(BUILD_DIR)/$(TARGET).objdump: $(ELF) $(OBJDUMP) -D $< > $@ +else ifeq ($(TARGET_NX),1) # Nintendo Switch + $(ELF): $(O_FILES) $(MIO0_FILES:.mio0=.o) $(ULTRA_O_FILES) $(GODDARD_O_FILES) $(BUILD_DIR)/$(RPC_LIBS) $(BUILD_DIR)/$(DISCORD_SDK_LIBS) $(BUILD_DIR)/$(COOPNET_LIBS) $(BUILD_DIR)/$(LANG_DIR) $(BUILD_DIR)/$(MOD_DIR) $(BUILD_DIR)/$(PALETTES_DIR) + @$(PRINT) "$(GREEN)Linking executable: $(BLUE)$@ $(NO_COL)\n" + $(V)$(LD) $(PROF_FLAGS) -L $(BUILD_DIR) -o $@ $(O_FILES) $(ULTRA_O_FILES) $(GODDARD_O_FILES) $(LDFLAGS) -lnx -lm else $(EXE): $(O_FILES) $(MIO0_FILES:.mio0=.o) $(ULTRA_O_FILES) $(GODDARD_O_FILES) $(BUILD_DIR)/$(RPC_LIBS) $(BUILD_DIR)/$(DISCORD_SDK_LIBS) $(BUILD_DIR)/$(COOPNET_LIBS) $(BUILD_DIR)/$(LANG_DIR) $(BUILD_DIR)/$(MOD_DIR) $(BUILD_DIR)/$(PALETTES_DIR) @$(PRINT) "$(GREEN)Linking executable: $(BLUE)$@ $(NO_COL)\n" diff --git a/data/dynos.cpp.h b/data/dynos.cpp.h index d3bfa3261..7d03a3548 100644 --- a/data/dynos.cpp.h +++ b/data/dynos.cpp.h @@ -735,23 +735,23 @@ void Print(const char *aFmt, Args... aArgs) { } template -void PrintConsole(enum ConsoleMessageLevel level, const char *aFmt, Args... aArgs) { +void DynOS_PrintConsole(enum ConsoleMessageLevel level, const char *aFmt, Args... aArgs) { snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, aFmt, aArgs...); sys_swap_backslashes(gDjuiConsoleTmpBuffer); djui_console_message_create(gDjuiConsoleTmpBuffer, level); } template -void PrintError(const char *aFmt, Args... aArgs) { +void DynOS_PrintError(const char *aFmt, Args... aArgs) { printf(aFmt, aArgs...); printf("\r\n"); fflush(stdout); - PrintConsole(CONSOLE_MESSAGE_ERROR, aFmt, aArgs...); + DynOS_PrintConsole(CONSOLE_MESSAGE_ERROR, aFmt, aArgs...); } -#define PrintDataError(...) { \ +#define DynOS_PrintDataError(...) { \ if (aGfxData->mErrorCount == 0) Print(" ERROR!"); \ Print(__VA_ARGS__); \ - PrintConsole(CONSOLE_MESSAGE_ERROR, __VA_ARGS__); \ + DynOS_PrintConsole(CONSOLE_MESSAGE_ERROR, __VA_ARGS__); \ aGfxData->mErrorCount++; \ } diff --git a/data/dynos_bin_actor.cpp b/data/dynos_bin_actor.cpp index 60d62bcf1..ea360c296 100644 --- a/data/dynos_bin_actor.cpp +++ b/data/dynos_bin_actor.cpp @@ -16,7 +16,7 @@ void ClearGfxDataNodes(DataNodes &aDataNodes) { static bool DynOS_Actor_WriteBinary(const SysPath &aOutputFilename, GfxData *aGfxData) { BinFile *_File = BinFile::OpenW(aOutputFilename.c_str()); if (!_File) { - PrintDataError(" ERROR: Unable to create file \"%s\"", aOutputFilename.c_str()); + DynOS_PrintDataError(" ERROR: Unable to create file \"%s\"", aOutputFilename.c_str()); return false; } @@ -188,7 +188,7 @@ static void DynOS_Actor_Generate(const SysPath &aPackFolder, ArraymModelIdentifier); - PrintConsole(CONSOLE_MESSAGE_INFO, "%s.bin: Model identifier: %X - Processing... ", _GeoRootName.begin(), _GfxData->mModelIdentifier); + DynOS_PrintConsole(CONSOLE_MESSAGE_INFO, "%s.bin: Model identifier: %X - Processing... ", _GeoRootName.begin(), _GfxData->mModelIdentifier); DynOS_Geo_Parse(_GfxData, _GeoNode, true); // Init animation data @@ -222,7 +222,7 @@ static void DynOS_Actor_Generate(const SysPath &aPackFolder, ArraymErrorCount == 0) { DynOS_Actor_WriteBinary(_BinFilename, _GfxData); } else { - PrintError(" %u error(s): Unable to parse data", _GfxData->mErrorCount); + DynOS_PrintError(" %u error(s): Unable to parse data", _GfxData->mErrorCount); } // Clear data pointers ClearGfxDataNodes(_GfxData->mLights); diff --git a/data/dynos_bin_ambient_t.cpp b/data/dynos_bin_ambient_t.cpp index abd5f56bb..f0d48fc70 100644 --- a/data/dynos_bin_ambient_t.cpp +++ b/data/dynos_bin_ambient_t.cpp @@ -9,7 +9,7 @@ DataNode* DynOS_AmbientT_Parse(GfxData* aGfxData, DataNode // Check tokens count if (aNode->mTokens.Count() < 8) { - PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); + DynOS_PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); return aNode; } diff --git a/data/dynos_bin_animation.cpp b/data/dynos_bin_animation.cpp index 787b11ad1..a277c1520 100644 --- a/data/dynos_bin_animation.cpp +++ b/data/dynos_bin_animation.cpp @@ -7,7 +7,7 @@ static void ScanAnimationDataFile(GfxData *aGfxData, const SysPath &aFilename) { FILE *_File = fopen(aFilename.c_str(), "rb"); if (!_File) { - PrintDataError(" ERROR: Unable to open file \"%s\"", aFilename.c_str()); + DynOS_PrintDataError(" ERROR: Unable to open file \"%s\"", aFilename.c_str()); } // Load file into a buffer while removing all comments @@ -70,7 +70,7 @@ static void ScanAnimationDataFile(GfxData *aGfxData, const SysPath &aFilename) { case DATA_TYPE_ANIMATION: { if (_Data.Count() < 10) { - PrintDataError(" ERROR: %s: Not enough data", _DataName.begin()); + DynOS_PrintDataError(" ERROR: %s: Not enough data", _DataName.begin()); break; } @@ -104,7 +104,7 @@ static void ScanAnimationDataFile(GfxData *aGfxData, const SysPath &aFilename) { static void ScanAnimationTableFile(GfxData *aGfxData, const SysPath &aFilename) { FILE *_File = fopen(aFilename.c_str(), "rb"); if (!_File) { - PrintDataError(" ERROR: Unable to open file \"%s\"", aFilename.c_str()); + DynOS_PrintDataError(" ERROR: Unable to open file \"%s\"", aFilename.c_str()); } // Load file into a buffer while removing all comments diff --git a/data/dynos_bin_behavior.cpp b/data/dynos_bin_behavior.cpp index bc266c763..900b52381 100644 --- a/data/dynos_bin_behavior.cpp +++ b/data/dynos_bin_behavior.cpp @@ -1985,7 +1985,7 @@ static BehaviorScript ParseBehaviorScriptSymbolArg(GfxData *aGfxData, DataNodemTokens[aTokenIndex - 1]; - PrintDataError(" ERROR: Unknown bhv arg: %s", _Arg.begin()); + DynOS_PrintDataError(" ERROR: Unknown bhv arg: %s", _Arg.begin()); } return value; } @@ -2274,7 +2274,7 @@ static void ParseBehaviorScriptSymbol(GfxData *aGfxData, DataNode *DynOS_Bhv_Parse(GfxData *aGfxData, DataNode *aNode, bool aDisplayPercent) { @@ -2478,7 +2478,7 @@ static void DynOS_Bhv_Write(BinFile* aFile, GfxData* aGfxData, DataNode *DynOS_Bhv_Load(BinFile *aFile, GfxData *aGfxDat // Sanity check the files size. The minimum valid size is 9 bytes. // 1 byte for the type, 1 bytes for the name length, 3 bytes for the version, And 4 bytes for the behaviors size. if (aFile->Size() < 9) { - PrintDataError(" ERROR: Behavior file is smaller then it should be, Rejecting '%s'.", aFile->GetFilename()); + DynOS_PrintDataError(" ERROR: Behavior file is smaller then it should be, Rejecting '%s'.", aFile->GetFilename()); // We have nothing to return, So return NULL. return NULL; } @@ -2524,7 +2524,7 @@ static DataNode *DynOS_Bhv_Load(BinFile *aFile, GfxData *aGfxDat // we can't read it no matter what. If it's just minor or patch. We might have // code to support it. if (majorVersion != BEHAVIOR_MIN_MAJOR_VER || (minorVersion < BEHAVIOR_MIN_MINOR_VER || patchVersion < BEHAVIOR_MIN_PATCH_VER)) { - PrintDataError(" ERROR: Behavior file is version %u.%u.%u, which is not supported! Rejecting '%s'.", majorVersion, minorVersion, patchVersion, aFile->GetFilename()); + DynOS_PrintDataError(" ERROR: Behavior file is version %u.%u.%u, which is not supported! Rejecting '%s'.", majorVersion, minorVersion, patchVersion, aFile->GetFilename()); // We don't return this since we failed to read the behavior. Delete(_Node); // We have nothing to return, So return NULL. @@ -2535,7 +2535,7 @@ static DataNode *DynOS_Bhv_Load(BinFile *aFile, GfxData *aGfxDat // We also check if the specified behavior size is valid for the file. u32 dataSize = aFile->Read(); if (dataSize == 0 || (dataSize > (aFile->Size() - aFile->Offset()))) { - PrintDataError(" ERROR: Behavior file has a invalid behavior in it! Rejecting '%s'.", aFile->GetFilename()); + DynOS_PrintDataError(" ERROR: Behavior file has a invalid behavior in it! Rejecting '%s'.", aFile->GetFilename()); // We don't return this since we failed to read the behavior. Delete(_Node); // We have nothing to return, So return NULL. @@ -2549,7 +2549,7 @@ static DataNode *DynOS_Bhv_Load(BinFile *aFile, GfxData *aGfxDat // Read it for (u32 i = 0; i != _Node->mSize; ++i) { if (aFile->EoF()) { - PrintDataError(" ERROR: Reached EOF when reading file! Expected %llx bytes!", _Node->mSize * sizeof(u32)); + DynOS_PrintDataError(" ERROR: Reached EOF when reading file! Expected %llx bytes!", _Node->mSize * sizeof(u32)); break; } u32 _Value = aFile->Read(); @@ -2638,14 +2638,14 @@ static void DynOS_Bhv_Generate(const SysPath &aPackFolder, ArraymModelIdentifier); - PrintConsole(CONSOLE_MESSAGE_INFO, "%s.bhv: Behavior identifier: %X - Processing... ", _BhvRootName.begin(), _GfxData->mModelIdentifier); + DynOS_PrintConsole(CONSOLE_MESSAGE_INFO, "%s.bhv: Behavior identifier: %X - Processing... ", _BhvRootName.begin(), _GfxData->mModelIdentifier); DynOS_Bhv_Parse(_GfxData, _BhvNode, true); // Write if no error if (_GfxData->mErrorCount == 0) { DynOS_Bhv_WriteBinary(_BinFilename, _GfxData); } else { - PrintError(" %u error(s): Unable to parse data", _GfxData->mErrorCount); + DynOS_PrintError(" %u error(s): Unable to parse data", _GfxData->mErrorCount); } // Clear data pointers diff --git a/data/dynos_bin_col.cpp b/data/dynos_bin_col.cpp index 5501fc7be..4233efefa 100644 --- a/data/dynos_bin_col.cpp +++ b/data/dynos_bin_col.cpp @@ -42,21 +42,21 @@ struct CollisionValidationData { static void ValidateColSectionChange(GfxData* aGfxData, struct CollisionValidationData& aColValData, u8 section) { if (aColValData.section == COL_SECTION_END) { - PrintDataError("Found new col section after COL_END"); + DynOS_PrintDataError("Found new col section after COL_END"); } if (aColValData.section != section) { if (aColValData.vtxAlloc != aColValData.vtxCount) { - PrintDataError("Improper vtx count found in section. Allocated: %u, Defined: %u", aColValData.vtxAlloc, aColValData.vtxCount); + DynOS_PrintDataError("Improper vtx count found in section. Allocated: %u, Defined: %u", aColValData.vtxAlloc, aColValData.vtxCount); } if (aColValData.triAlloc != aColValData.triCount) { - PrintDataError("Improper triangle count found in section. Allocated: %u, Defined: %u", aColValData.triAlloc, aColValData.triCount); + DynOS_PrintDataError("Improper triangle count found in section. Allocated: %u, Defined: %u", aColValData.triAlloc, aColValData.triCount); } if (aColValData.specialAlloc != aColValData.specialCount) { - PrintDataError("Improper special count found in section. Allocated: %u, Defined: %u", aColValData.triAlloc, aColValData.triCount); + DynOS_PrintDataError("Improper special count found in section. Allocated: %u, Defined: %u", aColValData.triAlloc, aColValData.triCount); } if (aColValData.waterBoxAlloc != aColValData.waterBoxCount) { - PrintDataError("Improper water box count found in section. Allocated: %u, Defined: %u", aColValData.waterBoxAlloc, aColValData.waterBoxCount); + DynOS_PrintDataError("Improper water box count found in section. Allocated: %u, Defined: %u", aColValData.waterBoxAlloc, aColValData.waterBoxCount); } } @@ -65,17 +65,17 @@ static void ValidateColSectionChange(GfxData* aGfxData, struct CollisionValidati static void ValidateColInit(GfxData* aGfxData, struct CollisionValidationData& aColValData) { if (aColValData.tokenIndex != 0) { - PrintDataError("COL_INIT found after the first token"); + DynOS_PrintDataError("COL_INIT found after the first token"); } ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_VTX); } static void ValidateColVertexInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0) { if (strcmp(aColValData.lastSymbol, "COL_INIT") != 0) { - PrintDataError("COL_VERTEX_INIT found outside of vertex section"); + DynOS_PrintDataError("COL_VERTEX_INIT found outside of vertex section"); } if (arg0 < 0) { - PrintDataError("COL_VERTEX_INIT with a negative count: %d", arg0); + DynOS_PrintDataError("COL_VERTEX_INIT with a negative count: %d", arg0); } aColValData.vtxAlloc = arg0; aColValData.vtxCount = 0; @@ -83,14 +83,14 @@ static void ValidateColVertexInit(GfxData* aGfxData, struct CollisionValidationD static void ValidateColVertex(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2) { if (aColValData.section != COL_SECTION_VTX) { - PrintDataError("COL_VERTEX found outside of vertex section"); + DynOS_PrintDataError("COL_VERTEX found outside of vertex section"); } aColValData.vtxCount++; } static void ValidateColTriInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1) { if (arg1 < 0) { - PrintDataError("COL_TRI_INIT with a negative count: %d", arg1); + DynOS_PrintDataError("COL_TRI_INIT with a negative count: %d", arg1); } ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_TRI); aColValData.triAlloc = arg1; @@ -99,16 +99,16 @@ static void ValidateColTriInit(GfxData* aGfxData, struct CollisionValidationData static void ValidateColTri(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2) { if (aColValData.section != COL_SECTION_TRI) { - PrintDataError("COL_TRI found outside of triangle section"); + DynOS_PrintDataError("COL_TRI found outside of triangle section"); } if (arg0 < 0 || arg0 > aColValData.vtxCount) { - PrintDataError("COL_TRI used vertex outside of known range for first param: %d", arg0); + DynOS_PrintDataError("COL_TRI used vertex outside of known range for first param: %d", arg0); } if (arg1 < 0 || arg1 > aColValData.vtxCount) { - PrintDataError("COL_TRI used vertex outside of known range for second param: %d", arg1); + DynOS_PrintDataError("COL_TRI used vertex outside of known range for second param: %d", arg1); } if (arg2 < 0 || arg2 > aColValData.vtxCount) { - PrintDataError("COL_TRI used vertex outside of known range for third param: %d", arg2); + DynOS_PrintDataError("COL_TRI used vertex outside of known range for third param: %d", arg2); } aColValData.triCount++; } @@ -127,7 +127,7 @@ static void ValidateColEnd(GfxData* aGfxData, struct CollisionValidationData& aC static void ValidateColSpecialInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0) { if (arg0 < 0) { - PrintDataError("COL_SPECIAL_INIT with a negative count: %d", arg0); + DynOS_PrintDataError("COL_SPECIAL_INIT with a negative count: %d", arg0); } ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_SPECIAL); aColValData.specialAlloc = arg0; @@ -136,7 +136,7 @@ static void ValidateColSpecialInit(GfxData* aGfxData, struct CollisionValidation static void ValidateColWaterBoxInit(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0) { if (arg0 < 0) { - PrintDataError("COL_WATER_BOX_INIT with a negative count: %d", arg0); + DynOS_PrintDataError("COL_WATER_BOX_INIT with a negative count: %d", arg0); } ValidateColSectionChange(aGfxData, aColValData, COL_SECTION_WATER_BOX); aColValData.waterBoxAlloc = arg0; @@ -145,28 +145,28 @@ static void ValidateColWaterBoxInit(GfxData* aGfxData, struct CollisionValidatio static void ValidateColWaterBox(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3, s16 arg4, s16 arg5) { if (aColValData.section != COL_SECTION_WATER_BOX) { - PrintDataError("COL_WATER_BOX found outside of water box section"); + DynOS_PrintDataError("COL_WATER_BOX found outside of water box section"); } aColValData.waterBoxCount++; } static void ValidateColSpecialObject(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3) { if (aColValData.section != COL_SECTION_SPECIAL) { - PrintDataError("SPECIAL_OBJECT found outside of special section"); + DynOS_PrintDataError("SPECIAL_OBJECT found outside of special section"); } aColValData.specialCount++; } static void ValidateColSpecialObjectWithYaw(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3, s16 arg4) { if (aColValData.section != COL_SECTION_SPECIAL) { - PrintDataError("SPECIAL_OBJECT_WITH_YAW found outside of special section"); + DynOS_PrintDataError("SPECIAL_OBJECT_WITH_YAW found outside of special section"); } aColValData.specialCount++; } static void ValidateColSpecialObjectWithYawAndParam(GfxData* aGfxData, struct CollisionValidationData& aColValData, s16 arg0, s16 arg1, s16 arg2, s16 arg3, s16 arg4, s16 arg5) { if (aColValData.section != COL_SECTION_SPECIAL) { - PrintDataError("SPECIAL_OBJECT_WITH_YAW_AND_PARAM found outside of special section"); + DynOS_PrintDataError("SPECIAL_OBJECT_WITH_YAW_AND_PARAM found outside of special section"); } aColValData.specialCount++; } @@ -458,7 +458,7 @@ static s16 ParseColSymbolArg(GfxData* aGfxData, DataNode* aNode, u64& } // Unknown - PrintDataError(" ERROR: Unknown col arg: %s", _Arg.begin()); + DynOS_PrintDataError(" ERROR: Unknown col arg: %s", _Arg.begin()); return 0; } @@ -575,7 +575,7 @@ static void ParseCollisionSymbol(GfxData* aGfxData, DataNode* aNode, col_symbol_6(SPECIAL_OBJECT_WITH_YAW_AND_PARAM, ValidateColSpecialObjectWithYawAndParam); // Unknown - PrintDataError(" ERROR: Unknown col symbol: %s", _Symbol.begin()); + DynOS_PrintDataError(" ERROR: Unknown col symbol: %s", _Symbol.begin()); } DataNode* DynOS_Col_Parse(GfxData* aGfxData, DataNode* aNode, bool aDisplayPercent) { @@ -595,7 +595,7 @@ DataNode* DynOS_Col_Parse(GfxData* aGfxData, DataNode* aNo } if (colValData.section != COL_SECTION_END) { - PrintDataError("Collision did not end with COL_END"); + DynOS_PrintDataError("Collision did not end with COL_END"); } if (aDisplayPercent && aGfxData->mErrorCount == 0) { Print("100%%"); } @@ -603,7 +603,7 @@ DataNode* DynOS_Col_Parse(GfxData* aGfxData, DataNode* aNo aNode->mLoadIndex = aGfxData->mLoadIndex++; if (aGfxData->mErrorCount > 0) { - PrintDataError("Failed to parse collision: '%s'", aNode->mName.begin()); + DynOS_PrintDataError("Failed to parse collision: '%s'", aNode->mName.begin()); } return aNode; @@ -630,7 +630,7 @@ void DynOS_Col_Write(BinFile* aFile, GfxData* aGfxData, DataNode *aNo static bool DynOS_Col_WriteBinary(const SysPath &aOutputFilename, GfxData *aGfxData, DataNode* _Node) { BinFile *_File = BinFile::OpenW(aOutputFilename.c_str()); if (!_File) { - PrintDataError(" ERROR: Unable to create file \"%s\"", aOutputFilename.c_str()); + DynOS_PrintDataError(" ERROR: Unable to create file \"%s\"", aOutputFilename.c_str()); return false; } @@ -706,14 +706,14 @@ void DynOS_Col_Generate(const SysPath &aPackFolder, Array> _Ac // Parse data PrintNoNewLine("%s.col: Collision identifier: %X - Processing... ", _ColRootName.begin(), _GfxData->mModelIdentifier); - PrintConsole(CONSOLE_MESSAGE_INFO, "%s.col: Collision identifier: %X - Processing... ", _ColRootName.begin(), _GfxData->mModelIdentifier); + DynOS_PrintConsole(CONSOLE_MESSAGE_INFO, "%s.col: Collision identifier: %X - Processing... ", _ColRootName.begin(), _GfxData->mModelIdentifier); DynOS_Col_Parse(_GfxData, _ColNode, true); // Write if no error if (_GfxData->mErrorCount == 0) { DynOS_Col_WriteBinary(_ColFilename, _GfxData, _ColNode); } else { - PrintError(" %u error(s): Unable to parse data", _GfxData->mErrorCount); + DynOS_PrintError(" %u error(s): Unable to parse data", _GfxData->mErrorCount); } // Clear data pointers diff --git a/data/dynos_bin_compress.cpp b/data/dynos_bin_compress.cpp index 02a3546a5..fde3218ea 100644 --- a/data/dynos_bin_compress.cpp +++ b/data/dynos_bin_compress.cpp @@ -29,7 +29,7 @@ static inline void DynOS_Bin_Compress_Free() { static inline bool DynOS_Bin_Compress_Check(bool condition, const char *function, const char *filename, const char *message) { if (!condition) { - PrintError("ERROR: %s: File \"%s\": %s", function, filename, message); + DynOS_PrintError("ERROR: %s: File \"%s\": %s", function, filename, message); DynOS_Bin_Compress_Free(); return false; } @@ -223,7 +223,7 @@ BinFile *DynOS_Bin_Decompress(const SysPath &aFilename) { uncompressRc == Z_OK, __FUNCTION__, aFilename.c_str(), "Cannot uncompress data" )) { - PrintError("ERROR: uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader); + DynOS_PrintError("ERROR: uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader); return NULL; } Print("uncompress rc: %d, length uncompressed: %lu, length compressed: %lu, length header: %lu", uncompressRc, sLengthUncompressed, sLengthCompressed, _LengthHeader); diff --git a/data/dynos_bin_geo.cpp b/data/dynos_bin_geo.cpp index 25a824d4d..3062103cb 100644 --- a/data/dynos_bin_geo.cpp +++ b/data/dynos_bin_geo.cpp @@ -148,7 +148,7 @@ static s64 ParseGeoSymbolArg(GfxData* aGfxData, DataNode* aNode, u64& } // Unknown - PrintDataError(" ERROR: Unknown geo arg: %s", _Arg.begin()); + DynOS_PrintDataError(" ERROR: Unknown geo arg: %s", _Arg.begin()); return 0; } @@ -433,7 +433,7 @@ static void ParseGeoSymbol(GfxData* aGfxData, DataNode* aNode, GeoLay } // Unknown - PrintDataError(" ERROR: Unknown geo symbol: %s", _Symbol.begin()); + DynOS_PrintDataError(" ERROR: Unknown geo symbol: %s", _Symbol.begin()); } DataNode* DynOS_Geo_Parse(GfxData* aGfxData, DataNode* aNode, bool aDisplayPercent) { diff --git a/data/dynos_bin_gfx.cpp b/data/dynos_bin_gfx.cpp index e7ac4f846..5a6690d76 100644 --- a/data/dynos_bin_gfx.cpp +++ b/data/dynos_bin_gfx.cpp @@ -12,18 +12,18 @@ static std::map> sGfxCommandCache; static char *sGfxCommandErrorMsg = NULL; static u32 sGfxCommandErrorSize = 0; -#define PrintDataErrorGfx(...) { \ +#define DynOS_PrintDataErrorGfx(...) { \ if (sGfxCommandErrorMsg) { \ snprintf(sGfxCommandErrorMsg, sGfxCommandErrorSize, __VA_ARGS__); \ aGfxData->mErrorCount++; \ } else { \ - PrintDataError(__VA_ARGS__); \ + DynOS_PrintDataError(__VA_ARGS__); \ } \ } #define CHECK_TOKEN_INDEX(tokenIndex, returnValue) { \ if (tokenIndex >= aNode->mTokens.Count()) { \ - PrintDataErrorGfx(" ERROR: Invalid token index: %llu, tokens count is: %d", tokenIndex, aNode->mTokens.Count()); \ + DynOS_PrintDataErrorGfx(" ERROR: Invalid token index: %llu, tokens count is: %d", tokenIndex, aNode->mTokens.Count()); \ return returnValue; \ } \ } @@ -570,7 +570,7 @@ static s64 ParseGfxSymbolArg(GfxData* aGfxData, DataNode* aNode, u64* pToke } // Unknown - PrintDataErrorGfx(" ERROR: Unknown gfx arg: %s", _Arg.begin()); + DynOS_PrintDataErrorGfx(" ERROR: Unknown gfx arg: %s", _Arg.begin()); return 0; } @@ -764,7 +764,7 @@ static String ConvertSetCombineModeArgToString(GfxData *aGfxData, const String& gfx_set_combine_mode_arg(G_CC_HILITERGBA2); gfx_set_combine_mode_arg(G_CC_HILITERGBDECALA2); gfx_set_combine_mode_arg(G_CC_HILITERGBPASSA2); - PrintDataErrorGfx(" ERROR: Unknown gfx gsDPSetCombineMode arg: %s", _Arg.begin()); + DynOS_PrintDataErrorGfx(" ERROR: Unknown gfx gsDPSetCombineMode arg: %s", _Arg.begin()); return ""; } @@ -785,7 +785,7 @@ static Array ParseGfxSetCombineMode(GfxData* aGfxData, DataNode* aNode } } if (_Args.Count() < 8) { - PrintDataErrorGfx(" ERROR: gsDPSetCombineMode %s: Not enough arguments", _Buffer.begin()); + DynOS_PrintDataErrorGfx(" ERROR: gsDPSetCombineMode %s: Not enough arguments", _Buffer.begin()); } return _Args; } @@ -1115,7 +1115,7 @@ static void ParseGfxSymbol(GfxData* aGfxData, DataNode* aNode, Gfx*& aHead, } // Unknown - PrintDataErrorGfx(" ERROR: Unknown gfx symbol: %s", _Symbol.begin()); + DynOS_PrintDataErrorGfx(" ERROR: Unknown gfx symbol: %s", _Symbol.begin()); } DataNode* DynOS_Gfx_Parse(GfxData* aGfxData, DataNode* aNode) { @@ -1217,7 +1217,7 @@ template static String ConvertParam(lua_State *L, GfxData *aGfxData, u32 paramIndex, const char *typeName, const SmluaToFunc &smluaToFunc, const ReturnFunc &returnFunc) { T value = smluaToFunc(L, paramIndex); if (!gSmLuaConvertSuccess) { - PrintDataErrorGfx(" ERROR: Failed to convert parameter %u to %s", paramIndex, typeName); + DynOS_PrintDataErrorGfx(" ERROR: Failed to convert parameter %u to %s", paramIndex, typeName); return ""; } return returnFunc(value); @@ -1266,7 +1266,7 @@ static String ResolveParam(lua_State *L, GfxData *aGfxData, u32 paramIndex, char [&aGfxData] (Gfx *gfx) { return CreateRawPointerDataNode(aGfxData, gfx); } ); } - PrintDataErrorGfx(" ERROR: Unknown parameter type: '%c'", paramType); + DynOS_PrintDataErrorGfx(" ERROR: Unknown parameter type: '%c'", paramType); return ""; } @@ -1353,7 +1353,7 @@ static bool CheckGfxLength(GfxData *aGfxData, Gfx *gfx, u32 lengthToWrite) { if (lengthToWrite > 1) { u32 gfxLength = gfx_get_length(gfx); if (gfxLength < lengthToWrite) { - PrintDataErrorGfx(" ERROR: Cannot write %u commands to display list of length: %u", lengthToWrite, gfxLength); + DynOS_PrintDataErrorGfx(" ERROR: Cannot write %u commands to display list of length: %u", lengthToWrite, gfxLength); return false; } } diff --git a/data/dynos_bin_light0.cpp b/data/dynos_bin_light0.cpp index d26548aef..b4fb3b49c 100644 --- a/data/dynos_bin_light0.cpp +++ b/data/dynos_bin_light0.cpp @@ -9,13 +9,13 @@ DataNode* DynOS_Light0_Parse(GfxData* aGfxData, DataNode* aNod // Check tokens count if (aNode->mTokens.Count() < 4) { - PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); + DynOS_PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); return aNode; } // Parse def token if (aNode->mTokens[0] != "gdSPDefLights0") { - PrintDataError(" ERROR: Invalid def token: should be gdSPDefLights0, is %s", aNode->mTokens[0].begin()); + DynOS_PrintDataError(" ERROR: Invalid def token: should be gdSPDefLights0, is %s", aNode->mTokens[0].begin()); return aNode; } diff --git a/data/dynos_bin_light_t.cpp b/data/dynos_bin_light_t.cpp index b342fa06e..0a5f994cf 100644 --- a/data/dynos_bin_light_t.cpp +++ b/data/dynos_bin_light_t.cpp @@ -9,7 +9,7 @@ DataNode* DynOS_LightT_Parse(GfxData* aGfxData, DataNode* aNod // Check tokens count if (aNode->mTokens.Count() < 12) { - PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); + DynOS_PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); return aNode; } diff --git a/data/dynos_bin_lights.cpp b/data/dynos_bin_lights.cpp index 0be628fb7..7871d0764 100644 --- a/data/dynos_bin_lights.cpp +++ b/data/dynos_bin_lights.cpp @@ -9,13 +9,13 @@ DataNode* DynOS_Lights_Parse(GfxData* aGfxData, DataNode* aNod // Check tokens count if (aNode->mTokens.Count() < 10) { - PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); + DynOS_PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); return aNode; } // Parse def token if (aNode->mTokens[0] != "gdSPDefLights1") { - PrintDataError(" ERROR: Invalid def token: should be gdSPDefLights1, is %s", aNode->mTokens[0].begin()); + DynOS_PrintDataError(" ERROR: Invalid def token: should be gdSPDefLights1, is %s", aNode->mTokens[0].begin()); return aNode; } diff --git a/data/dynos_bin_lvl.cpp b/data/dynos_bin_lvl.cpp index 53aaa529d..90d72ee32 100644 --- a/data/dynos_bin_lvl.cpp +++ b/data/dynos_bin_lvl.cpp @@ -529,7 +529,7 @@ static LevelScript ParseLevelScriptSymbolArg(GfxData* aGfxData, DataNodemTokens[aTokenIndex - 1]; - PrintDataError(" ERROR: Unknown lvl arg: %s", _Arg.begin()); + DynOS_PrintDataError(" ERROR: Unknown lvl arg: %s", _Arg.begin()); } return value; } @@ -881,7 +881,7 @@ static void ParseLevelScriptSymbol(GfxData* aGfxData, DataNode* aNo } // Unknown - PrintDataError(" ERROR: Unknown lvl symbol: %s", _Symbol.begin()); + DynOS_PrintDataError(" ERROR: Unknown lvl symbol: %s", _Symbol.begin()); } DataNode* DynOS_Lvl_Parse(GfxData* aGfxData, DataNode* aNode, bool aDisplayPercent) { @@ -938,7 +938,7 @@ static void DynOS_Lvl_Write(BinFile* aFile, GfxData* aGfxData, DataNode* DynOS_Lvl_Load(BinFile *aFile, GfxData *aGfxData) void *_Ptr = DynOS_Pointer_Load(aFile, aGfxData, _Value, &_Node->mFlags); if (_Ptr) { if (!requirePointer) { - PrintError("Didn't expect a pointer while reading level script: %s, %u", _Node->mName.begin(), _Value); + DynOS_PrintError("Didn't expect a pointer while reading level script: %s, %u", _Node->mName.begin(), _Value); } _Node->mData[i] = (uintptr_t) _Ptr; } else { if (requirePointer) { - PrintError("Expected a pointer while reading level script: %s, %u", _Node->mName.begin(), _Value); + DynOS_PrintError("Expected a pointer while reading level script: %s, %u", _Node->mName.begin(), _Value); _Node->mData[i] = 0; } else { _Node->mData[i] = (uintptr_t) _Value; @@ -1151,7 +1151,7 @@ static bool DynOS_Lvl_GeneratePack_Internal(const SysPath &aPackFolder, ArraymModelIdentifier); - PrintConsole(CONSOLE_MESSAGE_INFO, "%s.lvl: Level identifier: %X - Processing... ", _LvlRootName.begin(), _GfxData->mModelIdentifier); + DynOS_PrintConsole(CONSOLE_MESSAGE_INFO, "%s.lvl: Level identifier: %X - Processing... ", _LvlRootName.begin(), _GfxData->mModelIdentifier); DynOS_Lvl_Parse(_GfxData, _LvlRoot, true); // Force all of the movtexs, collisions, and trajectories into the compiled lvl @@ -1176,7 +1176,7 @@ static bool DynOS_Lvl_GeneratePack_Internal(const SysPath &aPackFolder, ArraymErrorCount == 0) { DynOS_Lvl_WriteBinary(_LvlFilename, _GfxData); } else { - PrintError(" %u error(s): Unable to parse data", _GfxData->mErrorCount); + DynOS_PrintError(" %u error(s): Unable to parse data", _GfxData->mErrorCount); } // Clear data pointers diff --git a/data/dynos_bin_macro_object.cpp b/data/dynos_bin_macro_object.cpp index 5090abe5b..fc060f7dc 100644 --- a/data/dynos_bin_macro_object.cpp +++ b/data/dynos_bin_macro_object.cpp @@ -404,7 +404,7 @@ static s64 ParseMacroObjectSymbolArg(GfxData* aGfxData, DataNode* a } // Unknown - PrintDataError(" ERROR: Unknown macro object arg: %s", _Arg.begin()); + DynOS_PrintDataError(" ERROR: Unknown macro object arg: %s", _Arg.begin()); return 0; } @@ -451,7 +451,7 @@ static void ParseMacroObjectSymbol(GfxData* aGfxData, DataNode* aNo macro_object_symbol_0(MACRO_OBJECT_END); // Unknown - PrintDataError(" ERROR: Unknown macro object symbol: %s", _Symbol.begin()); + DynOS_PrintDataError(" ERROR: Unknown macro object symbol: %s", _Symbol.begin()); } DataNode* DynOS_MacroObject_Parse(GfxData* aGfxData, DataNode* aNode, bool aDisplayPercent) { diff --git a/data/dynos_bin_movtex.cpp b/data/dynos_bin_movtex.cpp index a7f993e44..4c497892f 100644 --- a/data/dynos_bin_movtex.cpp +++ b/data/dynos_bin_movtex.cpp @@ -40,7 +40,7 @@ static s64 ParseMovtexSymbolArg(GfxData* aGfxData, DataNode* aNode, u64& movtex_constant(NULL); // Unknown - PrintDataError(" ERROR: Unknown movtex arg: %s", _Arg.begin()); + DynOS_PrintDataError(" ERROR: Unknown movtex arg: %s", _Arg.begin()); return 0; } @@ -143,7 +143,7 @@ static void ParseMovtexSymbol(GfxData* aGfxData, DataNode* aNode, Movtex } // Unknown - PrintDataError(" ERROR: Unknown movtex symbol: %s", _Symbol.begin()); + DynOS_PrintDataError(" ERROR: Unknown movtex symbol: %s", _Symbol.begin()); } DataNode* DynOS_Movtex_Parse(GfxData* aGfxData, DataNode* aNode, bool aDisplayPercent) { diff --git a/data/dynos_bin_movtexqc.cpp b/data/dynos_bin_movtexqc.cpp index ab7e36a77..d3c825f24 100644 --- a/data/dynos_bin_movtexqc.cpp +++ b/data/dynos_bin_movtexqc.cpp @@ -27,7 +27,7 @@ static Movtex* ParseMovtexQCSymbolArg(GfxData* aGfxData, DataNode* aNo } // Unknown - PrintDataError(" ERROR: Unknown movtexqc arg: %s", _Arg.begin()); + DynOS_PrintDataError(" ERROR: Unknown movtexqc arg: %s", _Arg.begin()); return NULL; } diff --git a/data/dynos_bin_pointer.cpp b/data/dynos_bin_pointer.cpp index b23d475e8..d73b950ff 100644 --- a/data/dynos_bin_pointer.cpp +++ b/data/dynos_bin_pointer.cpp @@ -199,7 +199,7 @@ static PointerData GetDataFromPointer(const void* aPtr, GfxData* aGfxData) { } } - PrintDataError("Unable to find pointer %x!", aPtr); + DynOS_PrintDataError("Unable to find pointer %x!", aPtr); return { "", 0 }; } diff --git a/data/dynos_bin_read.cpp b/data/dynos_bin_read.cpp index d6e3c72fe..7235675fe 100644 --- a/data/dynos_bin_read.cpp +++ b/data/dynos_bin_read.cpp @@ -226,7 +226,7 @@ void DynOS_Read_Source(GfxData *aGfxData, const SysPath &aFilename) { } else if (_Buffer == "BehaviorScript") { _DataType = DATA_TYPE_BEHAVIOR_SCRIPT; } else { - PrintDataError(" ERROR: Unknown type name: %s", _Buffer.begin()); + DynOS_PrintDataError(" ERROR: Unknown type name: %s", _Buffer.begin()); } _Buffer.Clear(); } @@ -277,7 +277,7 @@ void DynOS_Read_Source(GfxData *aGfxData, const SysPath &aFilename) { if (*c == '=') { pDataStart = c + 1; } else if (*c == ';') { - PrintDataError(" ERROR: %s: Unexpected end of data", pDataName->begin()); + DynOS_PrintDataError(" ERROR: %s: Unexpected end of data", pDataName->begin()); } } diff --git a/data/dynos_bin_tex.cpp b/data/dynos_bin_tex.cpp index eb52cea15..0f213dc6b 100644 --- a/data/dynos_bin_tex.cpp +++ b/data/dynos_bin_tex.cpp @@ -52,7 +52,7 @@ static TexData* LoadTextureFromFile(GfxData *aGfxData, const char* aFile) { // The file does not exist in either spot! if (!_File) { - PrintDataError(" ERROR: Unable to open file at \"%s\" or \"%s\"", _Filename.c_str(), _ActorFilename.c_str()); + DynOS_PrintDataError(" ERROR: Unable to open file at \"%s\" or \"%s\"", _Filename.c_str(), _ActorFilename.c_str()); return NULL; } } @@ -72,7 +72,7 @@ void DynOS_Tex_ConvertTextureDataToPng(GfxData *aGfxData, TexData* aTexture) { const u8 *_Palette = (aGfxData->mGfxContext.mCurrentPalette ? aGfxData->mGfxContext.mCurrentPalette->mData->mRawData.begin() : NULL); u8 *_Buffer = DynOS_Tex_ConvertToRGBA32(aTexture->mRawData.begin(), aTexture->mRawData.Count(), aTexture->mRawFormat, aTexture->mRawSize, _Palette); if (_Buffer == NULL) { - PrintDataError(" ERROR: Unknown texture format"); + DynOS_PrintDataError(" ERROR: Unknown texture format"); return; } @@ -80,7 +80,7 @@ void DynOS_Tex_ConvertTextureDataToPng(GfxData *aGfxData, TexData* aTexture) { s32 _PngLength = 0; u8 *_PngData = stbi_write_png_to_mem(_Buffer, 0, aTexture->mRawWidth, aTexture->mRawHeight, 4, &_PngLength); if (!_PngData || !_PngLength) { - PrintDataError(" ERROR: Cannot convert texture to PNG"); + DynOS_PrintDataError(" ERROR: Cannot convert texture to PNG"); return; } @@ -97,7 +97,7 @@ DataNode* DynOS_Tex_Parse(GfxData* aGfxData, DataNode* aNode) // Check tokens Count if (aNode->mTokens.Count() < 1) { - PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); + DynOS_PrintDataError(" ERROR: %s: not enough data", aNode->mName.begin()); return aNode; } @@ -107,7 +107,7 @@ DataNode* DynOS_Tex_Parse(GfxData* aGfxData, DataNode* aNode) s32 i1 = aNode->mTokens[0].Find(".inc.c"); if (i1 == -1) { if (strstr(aNode->mName.begin(), "_pal_") == NULL) { - PrintDataError(" ERROR: %s: missing .inc.c in String %s", aNode->mName.begin(), aNode->mTokens[0].begin()); + DynOS_PrintDataError(" ERROR: %s: missing .inc.c in String %s", aNode->mName.begin(), aNode->mTokens[0].begin()); } else { // hack for pal textures to be "found" TexData* _Texture = New(); @@ -129,7 +129,7 @@ DataNode* DynOS_Tex_Parse(GfxData* aGfxData, DataNode* aNode) if (dq0 != -1) { s32 dq1 = aNode->mTokens[0].Find('\"', dq0 + 1); if (dq1 == -1) { - PrintDataError(" ERROR: %s: missing second quote in String %s", aNode->mName.begin(), aNode->mTokens[0].begin()); + DynOS_PrintDataError(" ERROR: %s: missing second quote in String %s", aNode->mName.begin(), aNode->mTokens[0].begin()); return aNode; } @@ -188,7 +188,7 @@ void DynOS_Tex_Write(BinFile* aFile, GfxData* aGfxData, DataNode *aNode static bool DynOS_Tex_WriteBinary(GfxData* aGfxData, const SysPath &aOutputFilename, String& aName, TexData* aTexData, bool aRawTexture) { BinFile *_File = BinFile::OpenW(aOutputFilename.c_str()); if (!_File) { - PrintDataError(" ERROR: Unable to create file \"%s\"", aOutputFilename.c_str()); + DynOS_PrintDataError(" ERROR: Unable to create file \"%s\"", aOutputFilename.c_str()); return false; } @@ -429,7 +429,7 @@ static void DynOS_Tex_GeneratePack_Recursive(const SysPath &aPackFolder, SysPath aGfxData->mModelIdentifier++; TexData* _TexData = LoadTextureFromFile(aGfxData, _Path.c_str()); if (_TexData == NULL) { - PrintDataError("Error reading texture from file: %s", _Path.c_str()); + DynOS_PrintDataError("Error reading texture from file: %s", _Path.c_str()); continue; } diff --git a/data/dynos_bin_texlist.cpp b/data/dynos_bin_texlist.cpp index d0e0bac90..a52166fe2 100644 --- a/data/dynos_bin_texlist.cpp +++ b/data/dynos_bin_texlist.cpp @@ -13,7 +13,7 @@ static TexData* ParseTexListSymbol(GfxData* aGfxData, DataNode* aNode, } // Unknown - PrintDataError(" ERROR: Unknown texlist arg: %s", aToken.begin()); + DynOS_PrintDataError(" ERROR: Unknown texlist arg: %s", aToken.begin()); return NULL; } @@ -56,7 +56,7 @@ void DynOS_TexList_Write(BinFile* aFile, GfxData* aGfxData, DataNode * } } if (!found) { - PrintDataError("Could not write texture in texlist"); + DynOS_PrintDataError("Could not write texture in texlist"); } } } @@ -78,7 +78,7 @@ DataNode* DynOS_TexList_Load(BinFile *aFile, GfxData *aGfxData) { u32 _Value = aFile->Read(); void *_Ptr = DynOS_Pointer_Load(aFile, aGfxData, _Value, &_Node->mFlags); if (_Ptr == NULL) { - PrintDataError("Could not read texture in texlist"); + DynOS_PrintDataError("Could not read texture in texlist"); } else { _Node->mData[i] = ((DataNode*)_Ptr)->mData; } diff --git a/data/dynos_bin_trajectory.cpp b/data/dynos_bin_trajectory.cpp index ddd7cbec5..80f1db976 100644 --- a/data/dynos_bin_trajectory.cpp +++ b/data/dynos_bin_trajectory.cpp @@ -28,7 +28,7 @@ static s64 ParseTrajectorySymbolArg(GfxData* aGfxData, DataNode* aNo trajectory_constant(NULL); // Unknown - PrintDataError(" ERROR: Unknown trajectory arg: %s", _Arg.begin()); + DynOS_PrintDataError(" ERROR: Unknown trajectory arg: %s", _Arg.begin()); return 0; } @@ -59,7 +59,7 @@ static void ParseTrajectorySymbol(GfxData* aGfxData, DataNode* aNode trajectory_symbol_0(TRAJECTORY_END); // Unknown - PrintDataError(" ERROR: Unknown trajectory symbol: %s", _Symbol.begin()); + DynOS_PrintDataError(" ERROR: Unknown trajectory symbol: %s", _Symbol.begin()); } DataNode* DynOS_Trajectory_Parse(GfxData* aGfxData, DataNode* aNode, bool aDisplayPercent) { diff --git a/data/dynos_mgr_actor.cpp b/data/dynos_mgr_actor.cpp index d80adb44f..02bc1f36d 100644 --- a/data/dynos_mgr_actor.cpp +++ b/data/dynos_mgr_actor.cpp @@ -40,7 +40,7 @@ void DynOS_Actor_AddCustom(s32 aModIndex, const SysPath &aFilename, const char * GfxData *_GfxData = DynOS_Actor_LoadFromBinary(aFilename, actorName, aFilename, false); if (!_GfxData) { - PrintError(" ERROR: Couldn't load Actor Binary \"%s\" from \"%s\"", actorName, aFilename.c_str()); + DynOS_PrintError(" ERROR: Couldn't load Actor Binary \"%s\" from \"%s\"", actorName, aFilename.c_str()); free(actorName); return; } @@ -48,7 +48,7 @@ void DynOS_Actor_AddCustom(s32 aModIndex, const SysPath &aFilename, const char * void* geoLayout = (*(_GfxData->mGeoLayouts.end() - 1))->mData; if (!geoLayout) { - PrintError(" ERROR: Couldn't load geo layout for \"%s\"", actorName); + DynOS_PrintError(" ERROR: Couldn't load geo layout for \"%s\"", actorName); free(actorName); return; } @@ -60,7 +60,7 @@ void DynOS_Actor_AddCustom(s32 aModIndex, const SysPath &aFilename, const char * actorGfx.mPackIndex = MOD_PACK_INDEX; actorGfx.mGraphNode = (GraphNode *) DynOS_Model_LoadGeo(&id, MODEL_POOL_SESSION, geoLayout, true); if (!actorGfx.mGraphNode) { - PrintError(" ERROR: Couldn't load graph node for \"%s\"", actorName); + DynOS_PrintError(" ERROR: Couldn't load graph node for \"%s\"", actorName); free(actorName); return; } diff --git a/data/dynos_mgr_bhv.cpp b/data/dynos_mgr_bhv.cpp index 51d3b2731..7d5734f30 100644 --- a/data/dynos_mgr_bhv.cpp +++ b/data/dynos_mgr_bhv.cpp @@ -105,7 +105,7 @@ void DynOS_Bhv_HookAllCustomBehaviors() { // Theres currently no better place but to do this here. if (smlua_hook_custom_bhv(script, scriptName) == 0) { - PrintDataError(" ERROR: Failed to add custom behavior '%s'!", scriptName); + DynOS_PrintDataError(" ERROR: Failed to add custom behavior '%s'!", scriptName); } } } \ No newline at end of file diff --git a/data/dynos_mgr_lvl.cpp b/data/dynos_mgr_lvl.cpp index 56f1b7e96..5f3f424aa 100644 --- a/data/dynos_mgr_lvl.cpp +++ b/data/dynos_mgr_lvl.cpp @@ -85,7 +85,7 @@ void DynOS_Lvl_Activate(s32 modIndex, const SysPath &aFilename, const char *aLev // Override vanilla script auto& newScripts = _Node->mLevelScripts; if (newScripts.Count() <= 0) { - PrintError("Could not find level scripts: '%s'", aLevelName); + DynOS_PrintError("Could not find level scripts: '%s'", aLevelName); return; } @@ -161,7 +161,7 @@ void DynOS_Lvl_LoadBackground(void *aPtr) { double_break: if (foundList == NULL) { - PrintError("Could not find custom background"); + DynOS_PrintError("Could not find custom background"); return; } diff --git a/data/dynos_mgr_tex.cpp b/data/dynos_mgr_tex.cpp index 5531c0710..ceac66017 100644 --- a/data/dynos_mgr_tex.cpp +++ b/data/dynos_mgr_tex.cpp @@ -474,7 +474,7 @@ bool DynOS_Tex_Get(const char* aTexName, struct TextureInfo* aOutTexInfo) { u8 *_RawData = stbi_load_from_memory(_Data->mPngData.begin(), _Data->mPngData.Count(), &_Data->mRawWidth, &_Data->mRawHeight, NULL, 4); // texture data is corrupted if (_RawData == NULL) { - PrintError("Attempted to load corrupted tex file: %s", aTexName); + DynOS_PrintError("Attempted to load corrupted tex file: %s", aTexName); return false; } _Data->mRawFormat = G_IM_FMT_RGBA; diff --git a/include/PR/ultratypes.h b/include/PR/ultratypes.h index 1ebde7756..4b29c936b 100644 --- a/include/PR/ultratypes.h +++ b/include/PR/ultratypes.h @@ -8,6 +8,9 @@ #define TRUE 1 #define FALSE 0 +#ifdef __SWITCH__ +#include +#else typedef signed char s8; typedef unsigned char u8; typedef signed short int s16; @@ -25,6 +28,7 @@ typedef volatile s8 vs8; typedef volatile s16 vs16; typedef volatile s32 vs32; typedef volatile s64 vs64; +#endif typedef float f32; typedef double f64; diff --git a/include/types.h b/include/types.h index 2ac0825ad..984aaa088 100644 --- a/include/types.h +++ b/include/types.h @@ -19,6 +19,10 @@ #define BAD_RETURN(cmd) cmd #endif +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + struct Controller { // For optimization reasons, See MarioState diff --git a/lib/coopnet/nx/libcoopnet.a b/lib/coopnet/nx/libcoopnet.a new file mode 100644 index 000000000..42f36be8e Binary files /dev/null and b/lib/coopnet/nx/libcoopnet.a differ diff --git a/lib/coopnet/nx/libjuice.a b/lib/coopnet/nx/libjuice.a new file mode 100644 index 000000000..d122a48ef Binary files /dev/null and b/lib/coopnet/nx/libjuice.a differ diff --git a/lib/lua/include/luaconf.h b/lib/lua/include/luaconf.h index f95555689..12ad923ae 100644 --- a/lib/lua/include/luaconf.h +++ b/lib/lua/include/luaconf.h @@ -47,10 +47,13 @@ /* ** By default, Lua on Windows use (some) specific Windows features */ -#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) +#if !defined(LUA_USE_C89) && defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SWITCH__) #define LUA_USE_WINDOWS /* enable goodies for regular Windows */ #endif +#if defined(__SWITCH__) +#define LUA_USE_C89 +#endif #if defined(LUA_USE_WINDOWS) #define LUA_DL_DLL /* enable support for DLL */ diff --git a/lib/lua/nx/liblua53.a b/lib/lua/nx/liblua53.a new file mode 100644 index 000000000..68792d6f9 Binary files /dev/null and b/lib/lua/nx/liblua53.a differ diff --git a/res/icon_AmericanEnglish.dat b/res/icon_AmericanEnglish.dat new file mode 100644 index 000000000..835931081 Binary files /dev/null and b/res/icon_AmericanEnglish.dat differ diff --git a/res/icon_AmericanEnglish.jpg b/res/icon_AmericanEnglish.jpg new file mode 100644 index 000000000..835931081 Binary files /dev/null and b/res/icon_AmericanEnglish.jpg differ diff --git a/res/npdm.json b/res/npdm.json new file mode 100644 index 000000000..47c773083 --- /dev/null +++ b/res/npdm.json @@ -0,0 +1,167 @@ +{ + "name": "SM64 Coop DX", + "program_id": "0x0100534d36344350", + "program_id_range_min": "0x0100534d36344350", + "program_id_range_max": "0x0100534d36344350", + "main_thread_stack_size": "0x100000", + "main_thread_priority": 44, + "default_cpu_id": 0, + "process_category": 0, + "pool_partition": 0, + "is_64_bit": true, + "address_space_type": 1, + "optimize_memory_allocation": true, + "disable_device_address_space_merge": false, + "prevent_code_reads": false, + "signature_key_generation": 1, + "is_retail": true, + "filesystem_access": { + "permissions": "0xFFFFFFFFFFFFFFFF" + }, + "service_host": [ + "*" + ], + "service_access": [ + "*" + ], + "kernel_capabilities": [ + { + "type": "kernel_flags", + "value": { + "highest_thread_priority": 59, + "lowest_thread_priority": 28, + "highest_cpu_id": 2, + "lowest_cpu_id": 0 + } + }, + { + "type": "syscalls", + "value": { + "svcUnknown00": "0x00", + "svcSetHeapSize": "0x01", + "svcSetMemoryPermission": "0x02", + "svcSetMemoryAttribute": "0x03", + "svcMapMemory": "0x04", + "svcUnmapMemory": "0x05", + "svcQueryMemory": "0x06", + "svcExitProcess": "0x07", + "svcCreateThread": "0x08", + "svcStartThread": "0x09", + "svcExitThread": "0x0A", + "svcSleepThread": "0x0B", + "svcGetThreadPriority": "0x0C", + "svcSetThreadPriority": "0x0D", + "svcGetThreadCoreMask": "0x0E", + "svcSetThreadCoreMask": "0x0F", + "svcGetCurrentProcessorNumber": "0x10", + "svcSignalEvent": "0x11", + "svcClearEvent": "0x12", + "svcMapSharedMemory": "0x13", + "svcUnmapSharedMemory": "0x14", + "svcCreateTransferMemory": "0x15", + "svcCloseHandle": "0x16", + "svcResetSignal": "0x17", + "svcWaitSynchronization": "0x18", + "svcCancelSynchronization": "0x19", + "svcArbitrateLock": "0x1A", + "svcArbitrateUnlock": "0x1B", + "svcWaitProcessWideKeyAtomic": "0x1C", + "svcSignalProcessWideKey": "0x1D", + "svcGetSystemTick": "0x1E", + "svcConnectToNamedPort": "0x1F", + "svcSendSyncRequestLight": "0x20", + "svcSendSyncRequest": "0x21", + "svcSendSyncRequestWithUserBuffer": "0x22", + "svcSendAsyncRequestWithUserBuffer": "0x23", + "svcGetProcessId": "0x24", + "svcGetThreadId": "0x25", + "svcBreak": "0x26", + "svcOutputDebugString": "0x27", + "svcReturnFromException": "0x28", + "svcGetInfo": "0x29", + "svcFlushEntireDataCache": "0x2A", + "svcFlushDataCache": "0x2B", + "svcMapPhysicalMemory": "0x2C", + "svcUnmapPhysicalMemory": "0x2D", + "svcGetFutureThreadInfo": "0x2E", + "svcGetLastThreadInfo": "0x2F", + "svcGetResourceLimitLimitValue": "0x30", + "svcGetResourceLimitCurrentValue": "0x31", + "svcSetThreadActivity": "0x32", + "svcGetThreadContext3": "0x33", + "svcWaitForAddress": "0x34", + "svcSignalToAddress": "0x35", + "svcUnknown36": "0x36", + "svcUnknown37": "0x37", + "svcUnknown38": "0x38", + "svcUnknown39": "0x39", + "svcUnknown3a": "0x3A", + "svcUnknown3b": "0x3B", + "svcDumpInfo": "0x3C", + "svcDumpInfoNew": "0x3D", + "svcUnknown3e": "0x3E", + "svcUnknown3f": "0x3F", + "svcCreateSession": "0x40", + "svcAcceptSession": "0x41", + "svcReplyAndReceiveLight": "0x42", + "svcReplyAndReceive": "0x43", + "svcReplyAndReceiveWithUserBuffer": "0x44", + "svcCreateEvent": "0x45", + "svcUnknown46": "0x46", + "svcUnknown47": "0x47", + "svcMapPhysicalMemoryUnsafe": "0x48", + "svcUnmapPhysicalMemoryUnsafe": "0x49", + "svcSetUnsafeLimit": "0x4A", + "svcCreateCodeMemory": "0x4B", + "svcControlCodeMemory": "0x4C", + "svcSleepSystem": "0x4D", + "svcReadWriteRegister": "0x4E", + "svcSetProcessActivity": "0x4F", + "svcCreateSharedMemory": "0x50", + "svcMapTransferMemory": "0x51", + "svcUnmapTransferMemory": "0x52", + "svcDebugActiveProcess": "0x60", + "svcBreakDebugProcess": "0x61", + "svcTerminateDebugProcess": "0x62", + "svcGetDebugEvent": "0x63", + "svcContinueDebugEvent": "0x64", + "svcGetProcessList": "0x65", + "svcGetThreadList": "0x66", + "svcGetDebugThreadContext": "0x67", + "svcSetDebugThreadContext": "0x68", + "svcQueryDebugProcessMemory": "0x69", + "svcReadDebugProcessMemory": "0x6A", + "svcWriteDebugProcessMemory": "0x6B", + "svcSetHardwareBreakPoint": "0x6C", + "svcGetDebugThreadParam": "0x6D", + "svcConnectToPort": "0x72", + "svcSetProcessMemoryPermission": "0x73", + "svcMapProcessMemory": "0x74", + "svcUnmapProcessMemory": "0x75", + "svcQueryProcessMemory": "0x76", + "svcMapProcessCodeMemory": "0x77", + "svcUnmapProcessCodeMemory": "0x78" + } + }, + { + "type": "application_type", + "value": 1 + }, + { + "type": "min_kernel_version", + "value": "0x30" + }, + { + "type": "handle_table_size", + "value": 512 + }, + { + "type": "debug_flags", + "value": { + "allow_debug": false, + "force_debug_prod": false, + "force_debug": true + } + } + ] +} \ No newline at end of file diff --git a/src/game/behaviors/texscroll.inc.c b/src/game/behaviors/texscroll.inc.c index a8a8df046..ead0dc7b9 100644 --- a/src/game/behaviors/texscroll.inc.c +++ b/src/game/behaviors/texscroll.inc.c @@ -8,7 +8,7 @@ #include #include "engine/math_util.h" #include "game/scroll_targets.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/utils/misc.h" static inline void shift_UV_JUMP(struct ScrollTarget *scroll, u16 vertcount, s16 speed, u16 bhv, u16 cycle) { diff --git a/src/game/hardcoded.c b/src/game/hardcoded.c index 5724197d1..3fba2b8cc 100644 --- a/src/game/hardcoded.c +++ b/src/game/hardcoded.c @@ -34,7 +34,7 @@ #include "levels/wf/header.h" #include "levels/wmotr/header.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" extern Trajectory sThiHugeMetalBallTraj[]; extern Trajectory sThiTinyMetalBallTraj[]; diff --git a/src/game/level_update.c b/src/game/level_update.c index 26ffb2ac3..e04c60fe0 100644 --- a/src/game/level_update.c +++ b/src/game/level_update.c @@ -36,7 +36,7 @@ #include "game/interaction.h" #include "menu/intro_geo.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/cliopts.h" #include "pc/configfile.h" #include "pc/network/network.h" diff --git a/src/game/mario_actions_cutscene.c b/src/game/mario_actions_cutscene.c index eaa2e4579..73f093668 100644 --- a/src/game/mario_actions_cutscene.c +++ b/src/game/mario_actions_cutscene.c @@ -31,7 +31,7 @@ #include "hardcoded.h" #include "libc/stdlib.h" #include "pc/debuglog.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/configfile.h" #include "pc/network/network.h" #include "pc/lua/smlua.h" diff --git a/src/pc/cliopts.c b/src/pc/cliopts.c index f8bab9c2f..3a355da40 100644 --- a/src/pc/cliopts.c +++ b/src/pc/cliopts.c @@ -1,6 +1,6 @@ #include "cliopts.h" #include "configfile.h" -#include "pc_main.h" +#include "game_main.h" #include "platform.h" #include "macros.h" @@ -10,6 +10,8 @@ #include #include +#include "string_utils.h" + struct CLIOptions gCLIOpts; static void print_help(void) { diff --git a/src/pc/configfile.c b/src/pc/configfile.c index ee832d58b..c0dc8b5db 100644 --- a/src/pc/configfile.c +++ b/src/pc/configfile.c @@ -21,7 +21,8 @@ #include "djui/djui_hud_utils.h" #include "game/save_file.h" #include "pc/network/network_player.h" -#include "pc/pc_main.h" +#include "string_utils.h" +#include "pc/game_main.h" #define ARRAY_LEN(arr) (sizeof(arr) / sizeof(arr[0])) @@ -84,7 +85,11 @@ ConfigStick configStick = { 0 }; // display settings unsigned int configFiltering = 2; // 0 = Nearest, 1 = Bilinear, 2 = Trilinear bool configShowFPS = false; +#if defined(__SWITCH__) +bool configUncappedFramerate = false; +#else bool configUncappedFramerate = true; +#endif unsigned int configFrameLimit = 60; unsigned int configInterpolationMode = 1; unsigned int configDrawDistance = 4; diff --git a/src/pc/controller/controller_bind_mapping.c b/src/pc/controller/controller_bind_mapping.c index 8c56299f2..dd63769e2 100644 --- a/src/pc/controller/controller_bind_mapping.c +++ b/src/pc/controller/controller_bind_mapping.c @@ -12,7 +12,7 @@ #include "controller_api.h" #include "controller_sdl.h" -#if defined(CAPI_SDL1) || defined(CAPI_SDL2) +#if defined(CAPI_SDL1) || defined(CAPI_SDL2) || defined(CAPI_SWITCH) static int inverted_scancode_table[512]; static SDL_Scancode bind_to_sdl_scancode[512] = { 0 }; diff --git a/src/pc/controller/controller_bind_mapping.h b/src/pc/controller/controller_bind_mapping.h index 211172e0a..f762d2688 100644 --- a/src/pc/controller/controller_bind_mapping.h +++ b/src/pc/controller/controller_bind_mapping.h @@ -1,7 +1,7 @@ #ifndef CONTROLLER_BIND_MAPPING_H #define CONTROLLER_BIND_MAPPING_H -#if defined(CAPI_SDL1) || defined(CAPI_SDL2) +#if defined(CAPI_SDL1) || defined(CAPI_SDL2) || defined(CAPI_SWITCH) void controller_bind_init(void); int translate_sdl_scancode(int scancode); const char* translate_bind_to_name(int bind); diff --git a/src/pc/controller/controller_entry_point.c b/src/pc/controller/controller_entry_point.c index 4524b83f8..d5fd8de86 100644 --- a/src/pc/controller/controller_entry_point.c +++ b/src/pc/controller/controller_entry_point.c @@ -9,6 +9,7 @@ #include "controller_keyboard.h" #include "controller_sdl.h" #include "controller_system.h" +#include "controller_switch.h" // Analog camera movement by Pathétique (github.com/vrmiguel), y0shin and Mors // Contribute or communicate bugs at github.com/vrmiguel/sm64-analog-camera @@ -16,10 +17,14 @@ // moved these from sdl controller implementations static struct ControllerAPI *controller_implementations[] = { -#if defined(CAPI_SDL2) || defined(CAPI_SDL1) - &controller_sdl, +#if defined(CAPI_SWITCH) + &controller_switch, +#else + #if defined(CAPI_SDL2) || defined(CAPI_SDL1) + &controller_sdl, + #endif + &controller_keyboard, #endif - &controller_keyboard, }; s32 osContInit(UNUSED OSMesgQueue *mq, u8 *controllerBits, UNUSED OSContStatus *status) { diff --git a/src/pc/controller/controller_keyboard.c b/src/pc/controller/controller_keyboard.c index 18a3db407..473b08974 100644 --- a/src/pc/controller/controller_keyboard.c +++ b/src/pc/controller/controller_keyboard.c @@ -9,7 +9,7 @@ #include "controller_keyboard.h" #include "pc/gfx/gfx_window_manager_api.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "engine/math_util.h" #include "menu/file_select.h" #include "pc/djui/djui.h" diff --git a/src/pc/controller/controller_sdl2.c b/src/pc/controller/controller_sdl2.c index d9b64586c..f5ee8be86 100644 --- a/src/pc/controller/controller_sdl2.c +++ b/src/pc/controller/controller_sdl2.c @@ -17,7 +17,7 @@ #include "controller_sdl.h" #include "controller_mouse.h" #include "controller_system.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/configfile.h" #include "pc/platform.h" #include "pc/fs/fs.h" @@ -122,6 +122,7 @@ static void controller_sdl_init(void) { haptics_enabled = (SDL_InitSubSystem(SDL_INIT_HAPTIC) == 0); +#ifndef __SWITCH__ // try loading an external gamecontroller mapping file uint64_t gcsize = 0; void *gcdata = fs_load_file("gamecontrollerdb.txt", &gcsize); @@ -134,6 +135,7 @@ static void controller_sdl_init(void) { } free(gcdata); } +#endif if (gNewCamera.isMouse) { controller_mouse_enter_relative(); } controller_mouse_read_relative(); @@ -182,7 +184,7 @@ extern s16 gMenuMode; static void controller_sdl_read(OSContPad *pad) { if (!init_ok) { return; } - if ((gNewCamera.isMouse || get_first_person_enabled() || gDjuiHudLockMouse) && !is_game_paused() && !gDjuiPanelPauseCreated && !gDjuiInMainMenu && !gDjuiChatBoxFocus && !gDjuiConsoleFocus && WAPI.has_focus()) { + if ((gNewCamera.isMouse || get_first_person_enabled() || gDjuiHudLockMouse) && !is_game_paused() && !gDjuiPanelPauseCreated && !gDjuiInMainMenu && !gDjuiChatBoxFocus && !gDjuiConsoleFocus && wm_api->has_focus()) { controller_mouse_enter_relative(); } else { controller_mouse_leave_relative(); diff --git a/src/pc/controller/controller_switch.c b/src/pc/controller/controller_switch.c new file mode 100644 index 000000000..0c702311b --- /dev/null +++ b/src/pc/controller/controller_switch.c @@ -0,0 +1,298 @@ +#ifdef CAPI_SWITCH + +#include +#include +#include + +#include +#include "../../game/level_update.h" + +#include "controller_switch.h" +#include "pc/djui/djui.h" +#include "pc/djui/djui_base.h" +#include "pc/djui/djui_interactable.h" +#include "pc/djui/djui_panel_pause.h" + +// Buttons & Sticks +static HidAnalogStickState stickStates[2] = { 0 }; +static PadState padState = { 0 }; +static u64 padBtnsPressed = 0; +static u64 padBtnsDown = 0; +static u64 padBtnsUp = 0; + +// Motion Controls +static bool isSixAxis = false; +static HidSixAxisSensorHandle sixAxisHandles[4] = { 0 }; + +// Rumble +static HidVibrationDeviceHandle VibrationDeviceHandles[2][2] = { 0 }; + +// Switch Keyboard +static bool kbdInited = false; +static bool kbdShown = false; +static SwkbdInline kbd = { 0 }; +static SwkbdAppearArg kbdAppearArg = { 0 }; + +static void enter_cb(const char *str, SwkbdDecidedEnterArg *arg) { + djui_interactable_on_text_input((char *)str); + djui_interactable_set_input_focus(NULL); +} + +static void cancel_cb(void) { + djui_interactable_set_input_focus(NULL); +} + +static void changed_string(const char *str, SwkbdChangedStringArg *arg) { + djui_interactable_on_text_editing((char *)str, arg->cursorPos); +} + +static void moved_cursor(const char *str, SwkbdMovedCursorArg *arg) { + djui_interactable_on_text_editing((char *)str, arg->cursorPos); +} + +static void start_swkb(void) { + if (kbdInited) { return; } + + Result rc = swkbdInlineCreate(&kbd); + if (!R_SUCCEEDED(rc)) { return; } + + rc = swkbdInlineLaunchForLibraryApplet(&kbd, SwkbdInlineMode_AppletDisplay, 0); + if (!R_SUCCEEDED(rc)) { return; } + + swkbdInlineSetDecidedEnterCallback(&kbd, enter_cb); + swkbdInlineSetDecidedCancelCallback(&kbd, cancel_cb); + swkbdInlineSetChangedStringCallback(&kbd, changed_string); + swkbdInlineSetMovedCursorCallback(&kbd, moved_cursor); + swkbdInlineMakeAppearArg(&kbdAppearArg, SwkbdType_All); + swkbdInlineAppearArgSetOkButtonText(&kbdAppearArg, "Submit"); + kbdAppearArg.dicFlag = 1; + kbdAppearArg.returnButtonFlag = 1; + kbdInited = true; +} + +void show_swkb(char *text) { + if (!kbdInited) { return; } + + swkbdInlineSetInputText(&kbd, text); + swkbdInlineSetCursorPos(&kbd, strlen(text)); + swkbdInlineAppear(&kbd, &kbdAppearArg); + kbdShown = true; + + djui_interactable_on_text_editing("\0", 0); +} + +void hide_swkb(void) { + if (!kbdInited) { return; } + + swkbdInlineDisappear(&kbd); + kbdShown = false; +} + +void poll_swkb(void) { + if (!kbdInited) { return; } + swkbdInlineUpdate(&kbd, NULL); +} + +static void quit_swkb(void) { + if (!kbdInited) { return; } + + swkbdInlineClose(&kbd); + kbdInited = false; +} + +static void start_six_axis() { + isSixAxis = true; + for (int i = 0; i < 4; i++) { + hidStartSixAxisSensor(sixAxisHandles[i]); + } +} + +static void stop_six_axis() { + isSixAxis = false; + for (int i = 0; i < 4; i++) { + hidStopSixAxisSensor(sixAxisHandles[i]); + } +} + +static s32 gyro_to_stick(float f) { + s32 tmp = f * -255; + if (tmp < -80) tmp = -80; + else if (tmp > 80) tmp = 80; + return tmp; +} + +static void update_button_djui(u8 flag, u64 hid) { + bool down = padBtnsDown & hid; + bool up = padBtnsUp & hid; + bool held = (padBtnsPressed & hid) && (!down && !up); + + if (down) { + djui_interactable_on_button_down(flag); + } + if (up) { + djui_interactable_on_button_up(flag); + } + if (held) { + djui_interactable_on_button_held(flag); + } +} + +static void update_button_game(OSContPad *pad, u32 button, u64 hid) { + if (padBtnsPressed & hid) { + pad->button |= button; + } +} + +static void update_button(OSContPad *pad, u32 button, u8 flag, u64 hid) { + update_button_djui(flag, hid); + update_button_game(pad, button, hid); +} + +static void update_buttons(OSContPad *pad) { + update_button_game(pad, START_BUTTON, HidNpadButton_Plus); + update_button_djui(DJUI_BTN_SELECT, HidNpadButton_Minus); + + update_button_game(pad, B_BUTTON, HidNpadButton_B); + update_button_game(pad, A_BUTTON, HidNpadButton_A); + update_button_game(pad, Y_BUTTON, HidNpadButton_Y); + update_button_game(pad, X_BUTTON, HidNpadButton_X); + + update_button_game(pad, L_TRIG, HidNpadButton_L); + update_button_game(pad, R_TRIG, HidNpadButton_ZL); + update_button_game(pad, R_TRIG, HidNpadButton_R); + update_button_game(pad, Z_TRIG, HidNpadButton_ZR); + + update_button_game(pad, U_JPAD, HidNpadButton_Up); + update_button_game(pad, L_JPAD, HidNpadButton_Left); + update_button_game(pad, D_JPAD, HidNpadButton_Down); + update_button_game(pad, R_JPAD, HidNpadButton_Right); + + // Bind the C stick to the C buttons. + update_button(pad, R_CBUTTONS, DJUI_BTN_CRIGHT, HidNpadButton_StickRRight); + update_button(pad, L_CBUTTONS, DJUI_BTN_CLEFT, HidNpadButton_StickRLeft); + update_button(pad, U_CBUTTONS, DJUI_BTN_CUP, HidNpadButton_StickRUp); + update_button(pad, D_CBUTTONS, DJUI_BTN_CDOWN, HidNpadButton_StickRDown); +} + +static void update_sticks(OSContPad *pad) { + s32 leftX = stickStates[0].x / 409; + s32 leftY = stickStates[0].y / 409; + s32 rightX = stickStates[1].x / 409; + s32 rightY = stickStates[1].y / 409; + + if (isSixAxis && abs(leftX) < 20 && abs(leftY) < 20) { + HidSixAxisSensorState sixAxis; + + u64 style_set = padGetStyleSet(&padState); + if (style_set & HidNpadStyleTag_NpadHandheld) { + hidGetSixAxisSensorStates(sixAxisHandles[0], &sixAxis, 1); + } else if (style_set & HidNpadStyleTag_NpadFullKey) { + hidGetSixAxisSensorStates(sixAxisHandles[1], &sixAxis, 1); + } else if (style_set & HidNpadStyleTag_NpadJoyDual) { + // For JoyDual, read from either the Left or Right Joy-Con depending on which is/are connected + u64 attrib = padGetAttributes(&padState); + if (attrib & HidNpadAttribute_IsRightConnected) { + hidGetSixAxisSensorStates(sixAxisHandles[3], &sixAxis, 1); + } else if (attrib & HidNpadAttribute_IsLeftConnected) { + hidGetSixAxisSensorStates(sixAxisHandles[2], &sixAxis, 1); + } + } + + leftX = gyro_to_stick(sixAxis.angular_velocity.y); //Gyroscope Y-Axis (Controller X) + leftY = gyro_to_stick(sixAxis.angular_velocity.x); //Gyroscope X-Axis (Controller Y) + } + + pad->stick_x = leftX; + pad->stick_y = leftY; + pad->ext_stick_x = rightX; + pad->ext_stick_y = rightY; +} + +static void controller_switch_nx_init(void) { + padConfigureInput(1, HidNpadStyleSet_NpadFullCtrl); + padInitializeDefault(&padState); + + hidInitializeVibrationDevices(VibrationDeviceHandles[0], 2, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld); + hidInitializeVibrationDevices(VibrationDeviceHandles[1], 2, HidNpadIdType_No1, HidNpadStyleTag_NpadJoyDual); + //Initiating the SixAxis, needs to be done for all options; Obviously no GC Controller. + hidGetSixAxisSensorHandles(&sixAxisHandles[0], 1, HidNpadIdType_Handheld, HidNpadStyleTag_NpadHandheld); + hidGetSixAxisSensorHandles(&sixAxisHandles[1], 1, HidNpadIdType_No1, HidNpadStyleTag_NpadFullKey); + hidGetSixAxisSensorHandles(&sixAxisHandles[2], 2, HidNpadIdType_No1, HidNpadStyleTag_NpadJoyDual); + + djui_interactable_set_buttons_only(true); + start_swkb(); +} + +static void controller_switch_nx_read(OSContPad *pad) { + switch (gMarioState->action) { + case ACT_IN_CANNON: + case ACT_FIRST_PERSON: + if (!isSixAxis) start_six_axis(); + break; + default: + if (isSixAxis) stop_six_axis(); + } + + padUpdate(&padState); + + padBtnsPressed = padGetButtons(&padState); + padBtnsDown = padGetButtonsDown(&padState); + padBtnsUp = padGetButtonsUp(&padState); + stickStates[0] = padGetStickPos(&padState, 0); + stickStates[1] = padGetStickPos(&padState, 1); + + update_buttons(pad); + update_sticks(pad); + poll_swkb(); +} + +static u32 controller_switch_nx_rawkey(void) { + return VK_INVALID; +} + +static void controller_switch_nx_rumble_play(f32 strength, UNUSED f32 length) { + HidVibrationValue VibrationValues[2]; + HidVibrationValue VibrationValue = {0}; + + VibrationValue.freq_high = VibrationValue.freq_low = strength * 1.26f; + VibrationValue.amp_high = VibrationValue.amp_low = 1.0f; + + memcpy(&VibrationValues[0], &VibrationValue, sizeof(HidVibrationValue)); + memcpy(&VibrationValues[1], &VibrationValue, sizeof(HidVibrationValue)); + + padUpdate(&padState); + u32 target_device = padIsHandheld(&padState) ? 0 : 1; + hidSendVibrationValues(VibrationDeviceHandles[target_device], VibrationValues, 2); +} + +static void controller_switch_nx_rumble_stop(void) { + HidVibrationValue VibrationValues[2]; + HidVibrationValue VibrationValue_stop = {0}; + + VibrationValue_stop.freq_low = 160.0f; + VibrationValue_stop.freq_high = 320.0f; + + memcpy(&VibrationValues[0], &VibrationValue_stop, sizeof(HidVibrationValue)); + memcpy(&VibrationValues[1], &VibrationValue_stop, sizeof(HidVibrationValue)); + + hidSendVibrationValues(VibrationDeviceHandles[0], VibrationValues, 2); + hidSendVibrationValues(VibrationDeviceHandles[1], VibrationValues, 2); +} + +static void controller_switch_nx_shutdown(void) { + djui_interactable_set_buttons_only(false); + quit_swkb(); +} + +struct ControllerAPI controller_switch = { + VK_INVALID, + controller_switch_nx_init, + controller_switch_nx_read, + controller_switch_nx_rawkey, + controller_switch_nx_rumble_play, + controller_switch_nx_rumble_stop, + NULL, // no rebinding + controller_switch_nx_shutdown +}; + +#endif diff --git a/src/pc/controller/controller_switch.h b/src/pc/controller/controller_switch.h new file mode 100644 index 000000000..61505f3c8 --- /dev/null +++ b/src/pc/controller/controller_switch.h @@ -0,0 +1,12 @@ +#ifndef CONTROLLER_SWITCH_H +#define CONTROLLER_SWITCH_H + +#include "controller_api.h" + +extern struct ControllerAPI controller_switch; + +void show_swkb(char *text); +void hide_swkb(void); +void poll_swkb(void); + +#endif diff --git a/src/pc/crash_handler.c b/src/pc/crash_handler.c index 23301f0f2..c59ce7261 100644 --- a/src/pc/crash_handler.c +++ b/src/pc/crash_handler.c @@ -27,10 +27,14 @@ char gLastRemoteBhv[256] = ""; #include "pc/gfx/gfx_rendering_api.h" #include "pc/mods/mods.h" #include "pc/debuglog.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "controller/controller_keyboard.h" #include "controller/controller_mouse.h" +#ifndef __SWITCH__ +#include "pc/pc_main.h" +#endif + typedef struct { s32 x, y; u8 r, g, b; diff --git a/src/pc/discord/discord_activity.c b/src/pc/discord/discord_activity.c index 510ac440e..0bff47bf0 100644 --- a/src/pc/discord/discord_activity.c +++ b/src/pc/discord/discord_activity.c @@ -1,5 +1,5 @@ #include "discord.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/djui/djui.h" #include "pc/mods/mods.h" #include "pc/debuglog.h" diff --git a/src/pc/djui/djui_console.c b/src/pc/djui/djui_console.c index 3f6b1372a..fc7e34094 100644 --- a/src/pc/djui/djui_console.c +++ b/src/pc/djui/djui_console.c @@ -2,8 +2,9 @@ #include #include "djui.h" #include "djui_console.h" -#include "pc/pc_main.h" #include "engine/math_util.h" +#include "pc/game_main.h" +#include "pc/string_utils.h" #define MAX_CONSOLE_MESSAGES 500 diff --git a/src/pc/djui/djui_ctx_display.c b/src/pc/djui/djui_ctx_display.c index 08359ec32..f276a124f 100644 --- a/src/pc/djui/djui_ctx_display.c +++ b/src/pc/djui/djui_ctx_display.c @@ -1,7 +1,7 @@ #include "djui_ctx_display.h" #include "djui.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/debug_context.h" #ifdef DEVELOPMENT diff --git a/src/pc/djui/djui_cursor.c b/src/pc/djui/djui_cursor.c index e5d6b9f49..1726d4332 100644 --- a/src/pc/djui/djui_cursor.c +++ b/src/pc/djui/djui_cursor.c @@ -2,7 +2,7 @@ #include "djui_panel.h" #include "pc/controller/controller_mouse.h" #include "pc/gfx/gfx_window_manager_api.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" extern ALIGNED8 u8 gd_texture_hand_open[]; extern ALIGNED8 u8 gd_texture_hand_closed[]; @@ -112,7 +112,7 @@ void djui_cursor_move(s8 xDir, s8 yDir) { } void djui_cursor_update(void) { -#if defined(CAPI_SDL2) || defined(CAPI_SDL1) +#if defined(CAPI_SDL2) || defined(CAPI_SDL1) || defined(CAPI_SWITCH) if (djui_interactable_is_binding()) { return; } if (sMouseCursor == NULL) { return; } if (!djui_panel_is_active()) { return; } diff --git a/src/pc/djui/djui_fps_display.c b/src/pc/djui/djui_fps_display.c index bd1b351fa..e85b837f5 100644 --- a/src/pc/djui/djui_fps_display.c +++ b/src/pc/djui/djui_fps_display.c @@ -1,5 +1,5 @@ #include "djui.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" struct DjuiFpsDisplay { struct DjuiText *text; diff --git a/src/pc/djui/djui_gfx.c b/src/pc/djui/djui_gfx.c index 06f0c787b..da2454dd1 100644 --- a/src/pc/djui/djui_gfx.c +++ b/src/pc/djui/djui_gfx.c @@ -3,7 +3,7 @@ #include "djui.h" #include "game/ingame_menu.h" #include "game/segment2.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/gfx/gfx_window_manager_api.h" #include "gfx_dimensions.h" #include "djui_gfx.h" diff --git a/src/pc/djui/djui_hud_utils.c b/src/pc/djui/djui_hud_utils.c index d8247416c..01dd904d6 100644 --- a/src/pc/djui/djui_hud_utils.c +++ b/src/pc/djui/djui_hud_utils.c @@ -6,7 +6,7 @@ #include "pc/controller/controller_mouse.h" #include "pc/gfx/gfx_pc.h" #include "pc/gfx/gfx_window_manager_api.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/utils/misc.h" #include "djui_gfx.h" diff --git a/src/pc/djui/djui_inputbox.c b/src/pc/djui/djui_inputbox.c index 878a3a94d..dea25aec7 100644 --- a/src/pc/djui/djui_inputbox.c +++ b/src/pc/djui/djui_inputbox.c @@ -3,10 +3,13 @@ #include "djui.h" #include "djui_unicode.h" #include "djui_hud_utils.h" -#include "pc/gfx/gfx_window_manager_api.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "game/segment2.h" +#include "engine/math_util.h" #include "pc/controller/controller_keyboard.h" +#include "pc/controller/controller_switch.h" +#include "pc/game_main.h" +#include "pc/gfx/gfx_window_manager_api.h" #define DJUI_INPUTBOX_YOFF (-3) #define DJUI_INPUTBOX_MAX_BLINK 50 @@ -326,23 +329,58 @@ void djui_inputbox_on_key_up(UNUSED struct DjuiBase *base, int scancode) { } void djui_inputbox_on_focus_begin(UNUSED struct DjuiBase* base) { +#ifndef __SWITCH__ gDjuiInputHeldShift = 0; gDjuiInputHeldControl = 0; gDjuiInputHeldAlt = 0; wm_api->start_text_input(); +#else + struct DjuiInputbox *inputbox = (struct DjuiInputbox *)base; + show_swkb(inputbox->buffer); +#endif } void djui_inputbox_on_focus_end(UNUSED struct DjuiBase* base) { +#ifndef __SWITCH__ wm_api->stop_text_input(); +#else + hide_swkb(); +#endif } void djui_inputbox_on_text_input(struct DjuiBase *base, char* text) { struct DjuiInputbox *inputbox = (struct DjuiInputbox *) base; - char* msg = inputbox->buffer; - int msgLen = strlen(msg); + char *msg = inputbox->buffer; int textLen = strlen(text); - // make sure we're not just printing garbage characters +#ifdef __SWITCH__ + // Sanitize + char *t = text; + while (*t != '\0') { + if (*t == '\n') { *t = ' '; } + else if (*t == '\r') { *t = ' '; } + else if (djui_unicode_valid_char(t)) { ; } + + t = djui_unicode_next_char(t); + } + + // Truncate + if (textLen >= inputbox->bufferSize) { + text[inputbox->bufferSize] = '\0'; + textLen = inputbox->bufferSize - 1; + } + + snprintf(msg, textLen + 1, "%s", text); + msg[textLen] = '\0'; + djui_unicode_cleanup_end(msg); + + // Adjust cursor + inputbox->selection[0] = djui_unicode_len(msg); + inputbox->selection[1] = inputbox->selection[0]; + sCursorBlink = 0; + djui_inputbox_on_change(inputbox); +#else + // Make sure we're not just printing garbage characters bool containsValidAscii = false; char* tinput = text; while (*tinput != '\0') { @@ -355,21 +393,18 @@ void djui_inputbox_on_text_input(struct DjuiBase *base, char* text) { if (!containsValidAscii) { return; } - - // truncate + + int msgLen = strlen(msg); + + // Truncate if (textLen + msgLen >= inputbox->bufferSize) { int space = (inputbox->bufferSize - msgLen); if (space <= 1) { return; } text[space - 1] = '\0'; textLen = space - 1; } - - // erase selection - if (inputbox->selection[0] != inputbox->selection[1]) { - djui_inputbox_delete_selection(inputbox); - } - - // sanitize + + // Sanitize char *t = text; while (*t != '\0') { if (*t == '\n') { *t = ' '; } @@ -378,25 +413,26 @@ void djui_inputbox_on_text_input(struct DjuiBase *base, char* text) { t = djui_unicode_next_char(t); } - - // back up current message + + // Back up current message char* sMsg = calloc(inputbox->bufferSize, sizeof(char)); memcpy(sMsg, msg, inputbox->bufferSize); - // insert text + // Insert text size_t sel = djui_unicode_at_index(inputbox->buffer, inputbox->selection[0]) - inputbox->buffer; snprintf(&msg[sel], (inputbox->bufferSize - sel), "%s%s", text, &sMsg[sel]); free(sMsg); djui_unicode_cleanup_end(msg); - - // adjust cursor + + // Adjust cursor inputbox->selection[0] += djui_unicode_len(text); s32 ulen = djui_unicode_len(msg); if (inputbox->selection[0] > ulen) { inputbox->selection[0] = ulen; } inputbox->selection[1] = inputbox->selection[0]; sCursorBlink = 0; djui_inputbox_on_change(inputbox); +#endif inputbox->imePos = 0; if (inputbox->imeBuffer != NULL) { @@ -413,14 +449,20 @@ void djui_inputbox_on_text_editing(struct DjuiBase *base, char* text, int cursor if (*text == '\0') { inputbox->imeBuffer = NULL; - } - else { + } else { size_t size = strlen(text); char* copy = malloc(size + 1); - strcpy(copy,text); + strcpy(copy, text); inputbox->imeBuffer = copy; + +#ifdef __SWITCH__ + // Adjust cursor + inputbox->selection[0] = djui_unicode_len(text); + inputbox->selection[1] = inputbox->selection[0]; + sCursorBlink = 0; +#endif } - + djui_inputbox_on_change(inputbox); } @@ -455,6 +497,38 @@ static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) { selection[0] = fmin(inputbox->selection[0], inputbox->selection[1]); selection[1] = fmax(inputbox->selection[0], inputbox->selection[1]); +#ifdef __SWITCH__ + sCursorBlink = (sCursorBlink + 1) % DJUI_INPUTBOX_MAX_BLINK; + f32 x = 0; + f32 width = 0; + f32 renderX = 0; + + if (inputbox->imeBuffer != NULL) { + char *ime = inputbox->imeBuffer; + s32 imeBufferSize = strlen(ime); + for (s32 i = 0; i < imeBufferSize; i++) { + char *dc = inputbox->passwordChar[0] ? inputbox->passwordChar : ime; + if (i < selection[1]) { + x += font->char_width(dc); + } else { + width += font->char_width(dc); + } + ime = djui_unicode_next_char(ime); + } + } else { + char *c = inputbox->buffer; + for (u16 i = 0; i < selection[1]; i++) { + char *dc = inputbox->passwordChar[0] ? inputbox->passwordChar : c; + if (i < selection[0]) { + x += font->char_width(dc); + } else { + width += font->char_width(dc); + } + c = djui_unicode_next_char(c); + } + } + renderX = x; +#else char* c = inputbox->buffer; f32 x = 0; f32 width = 0; @@ -480,6 +554,7 @@ static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) { ime = djui_unicode_next_char(ime); } } +#endif // render only cursor when there is no selection width if (selection[0] == selection[1]) { @@ -525,23 +600,39 @@ static void djui_inputbox_render_selection(struct DjuiInputbox* inputbox) { static void djui_inputbox_keep_selection_in_view(struct DjuiInputbox* inputbox) { const struct DjuiFont* font = gDjuiFonts[configDjuiThemeFont == 0 ? FONT_NORMAL : FONT_ALIASED]; - - // calculate where our cursor is - f32 cursorX = inputbox->viewX; - char* c = inputbox->buffer; + + // Calculate where our cursor is. +#ifdef __SWITCH__ + char *c = inputbox->imeBuffer != NULL ? inputbox->imeBuffer : inputbox->buffer; +#else + char *c = inputbox->buffer; +#endif + f32 selSize = 0; for (u16 i = 0; i < inputbox->selection[0]; i++) { if (*c == '\0') { break; } char* dc = inputbox->passwordChar[0] ? inputbox->passwordChar : c; - cursorX += font->char_width(dc) * font->defaultFontScale; + selSize += font->char_width(dc) * font->defaultFontScale; c = djui_unicode_next_char(c); } + + f32 cursorX = inputbox->viewX + selSize; - // shift viewing window + // Shift viewing window +#ifdef __SWITCH__ + if (selSize > 0 && selSize <= inputbox->base.comp.width) { + inputbox->viewX = 0; + } else if (cursorX > inputbox->base.comp.width) { + inputbox->viewX -= cursorX - inputbox->base.comp.width; + } else if (cursorX < 0) { + inputbox->viewX -= cursorX; + } +#else if (cursorX > inputbox->base.comp.width) { inputbox->viewX -= cursorX - inputbox->base.comp.width; } else if (cursorX < 0) { inputbox->viewX -= cursorX; } +#endif } static bool djui_inputbox_render(struct DjuiBase* base) { @@ -584,6 +675,33 @@ static bool djui_inputbox_render(struct DjuiBase* base) { selection[0] = fmin(inputbox->selection[0], inputbox->selection[1]); selection[1] = fmax(inputbox->selection[0], inputbox->selection[1]); +#ifdef __SWITCH__ + // On Nintendo Switch, We need to handle this differently. + // We need to render only the ime or the original text. + // But not both. + + f32 drawX = inputbox->viewX; + f32 additionalShift = 0; + bool wasInsideSelection = false; + char *c = inputbox->imeBuffer != NULL ? inputbox->imeBuffer : inputbox->buffer; + s32 bufferSize = inputbox->imeBuffer != NULL ? strlen(c) : inputbox->bufferSize; + for (s32 i = 0; i < bufferSize; i++) { + if (*c == '\0') { break; } + // Deal with seleciton color + if (selection[0] != selection[1]) { + bool insideSelection = (i >= selection[0]) && (i < selection[1]); + if (insideSelection && !wasInsideSelection) { + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); + } else if (!insideSelection && wasInsideSelection) { + gDPSetEnvColor(gDisplayListHead++, inputbox->textColor.r, inputbox->textColor.g, inputbox->textColor.b, inputbox->textColor.a); + } + wasInsideSelection = insideSelection; + } + + djui_inputbox_render_char(inputbox, c, &drawX, &additionalShift); + c = djui_unicode_next_char(c); + } +#else // render text char* c = inputbox->buffer; f32 drawX = inputbox->viewX; @@ -617,6 +735,7 @@ static bool djui_inputbox_render(struct DjuiBase* base) { djui_inputbox_render_char(inputbox, c, &drawX, &additionalShift); c = djui_unicode_next_char(c); } +#endif gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); gSPDisplayList(gDisplayListHead++, dl_ia_text_end); diff --git a/src/pc/djui/djui_interactable.c b/src/pc/djui/djui_interactable.c index 822600969..00f2ef155 100644 --- a/src/pc/djui/djui_interactable.c +++ b/src/pc/djui/djui_interactable.c @@ -28,6 +28,7 @@ struct DjuiBase* gDjuiCursorDownOn = NULL; struct DjuiBase* gInteractableFocus = NULL; struct DjuiBase* gInteractableBinding = NULL; struct DjuiBase* gInteractableMouseDown = NULL; +bool gIsUsingButtonsOnly = false; bool gInteractableOverridePad = false; OSContPad gInteractablePad = { 0 }; static OSContPad sLastInteractablePad = { 0 }; @@ -193,6 +194,14 @@ bool djui_interactable_is_input_focus(struct DjuiBase* base) { return gInteractableFocus == base; } +void djui_interactable_set_buttons_only(bool enabled) { + gIsUsingButtonsOnly = enabled; +} + +bool djui_interactable_is_buttons_only(void) { + return gIsUsingButtonsOnly; +} + bool djui_interactable_on_key_down(int scancode) { if (gInteractableBinding != NULL) { return true; @@ -311,7 +320,7 @@ void djui_interactable_on_key_up(int scancode) { return; } - OSContPad* pad = &gInteractablePad; + OSContPad *pad = &gInteractablePad; switch (scancode) { case SCANCODE_UP: if (sKeyboardHoldDirection == PAD_HOLD_DIR_UP) { sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; pad->stick_y = 0; } break; case SCANCODE_DOWN: if (sKeyboardHoldDirection == PAD_HOLD_DIR_DOWN) { sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; pad->stick_y = 0; } break; @@ -321,6 +330,50 @@ void djui_interactable_on_key_up(int scancode) { } } +bool djui_interactable_on_button_down(u8 code) { + if (gInteractableBinding != NULL) { + return true; + } + + bool keyFocused = (gInteractableFocus != NULL) && (gInteractableFocus->interactable != NULL); + + if (keyFocused) { + sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; + sKeyboardButtons = 0; + return true; + } + + if (code == DJUI_BTN_SELECT && djui_panel_is_active()) { + // Pressed select on controller. + djui_panel_back(); + return true; + } + + return false; +} + +void djui_interactable_on_button_up(u8 code) { + OSContPad *pad = &gInteractablePad; + switch (code) { + case DJUI_BTN_UP: if (sKeyboardHoldDirection == PAD_HOLD_DIR_UP) { sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; pad->stick_y = 0; } break; + case DJUI_BTN_DOWN: if (sKeyboardHoldDirection == PAD_HOLD_DIR_DOWN) { sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; pad->stick_y = 0; } break; + case DJUI_BTN_LEFT: if (sKeyboardHoldDirection == PAD_HOLD_DIR_LEFT) { sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; pad->stick_x = 0; } break; + case DJUI_BTN_RIGHT: if (sKeyboardHoldDirection == PAD_HOLD_DIR_RIGHT) { sKeyboardHoldDirection = PAD_HOLD_DIR_NONE; pad->stick_x = 0; } break; + } +} + +void djui_interactable_on_button_held(u8 code) { + OSContPad *pad = &gInteractablePad; + if (gDjuiChatBoxFocus || djui_panel_is_active()) { + switch (code) { + case DJUI_BTN_UP: sKeyboardHoldDirection = PAD_HOLD_DIR_UP; break; + case DJUI_BTN_DOWN: sKeyboardHoldDirection = PAD_HOLD_DIR_DOWN; break; + case DJUI_BTN_LEFT: sKeyboardHoldDirection = PAD_HOLD_DIR_LEFT; break; + case DJUI_BTN_RIGHT: sKeyboardHoldDirection = PAD_HOLD_DIR_RIGHT; break; + } + } +} + void djui_interactable_on_text_input(char* text) { if (gInteractableFocus == NULL) { return; } if (gInteractableFocus->interactable == NULL) { return; } @@ -369,7 +422,7 @@ void djui_interactable_update_pad(void) { case PAD_HOLD_DIR_DOWN: pad->stick_x = 0; pad->stick_y = 64; break; case PAD_HOLD_DIR_LEFT: pad->stick_x = -64; pad->stick_y = 0; break; case PAD_HOLD_DIR_RIGHT: pad->stick_x = 64; pad->stick_y = 0; break; - default: break; + default: pad->stick_x = 0; pad->stick_y = 0; break; } } else if (pad->stick_x == 0 && pad->stick_y == 0) { padHoldDirection = PAD_HOLD_DIR_NONE; @@ -427,7 +480,7 @@ void djui_interactable_update(void) { if (!gDjuiChatBoxFocus) { djui_interactable_set_input_focus(NULL); } - } else if ((padButtons & mainButtons) && !(sLastInteractablePad.button & mainButtons)) { + } else if (!gIsUsingButtonsOnly && (padButtons & mainButtons) && !(sLastInteractablePad.button & mainButtons)) { // pressed main face button if (!gDjuiChatBoxFocus) { djui_interactable_set_input_focus(NULL); @@ -436,7 +489,7 @@ void djui_interactable_update(void) { djui_interactable_on_focus(gInteractableFocus); } } else if ((padButtons & PAD_BUTTON_B) && !(sLastInteractablePad.button & PAD_BUTTON_B)) { - // pressed back button on controller + // pressed back button on controller djui_panel_back(); sLastInteractablePad = gInteractablePad; @@ -449,7 +502,7 @@ void djui_interactable_update(void) { if (gInteractableBinding != NULL) { djui_interactable_on_bind(gInteractableBinding); - } else if ((padButtons & PAD_BUTTON_A) || (mouseButtons & MOUSE_BUTTON_1)) { + } else if (!(gIsUsingButtonsOnly && gInteractableFocus) && ((padButtons & PAD_BUTTON_A) || (mouseButtons & MOUSE_BUTTON_1))) { // cursor down events if (gDjuiHovered != NULL) { gInteractableMouseDown = gDjuiHovered; diff --git a/src/pc/djui/djui_interactable.h b/src/pc/djui/djui_interactable.h index 4a4dff258..099c8952f 100644 --- a/src/pc/djui/djui_interactable.h +++ b/src/pc/djui/djui_interactable.h @@ -23,6 +23,30 @@ #define SCANCODE_LSHIFT 42 #define SCANCODE_RSHIFT 54 +#define DJUI_BTN_BAD 0 +#define DJUI_BTN_A 1 +#define DJUI_BTN_B 2 +#define DJUI_BTN_X 3 +#define DJUI_BTN_Y 4 +#define DJUI_BTN_L 5 +#define DJUI_BTN_R 6 +#define DJUI_BTN_ZL 7 +#define DJUI_BTN_ZR 8 +#define DJUI_BTN_PADUP 9 +#define DJUI_BTN_PADLEFT 10 +#define DJUI_BTN_PADDOWN 11 +#define DJUI_BTN_PADRIGHT 12 +#define DJUI_BTN_UP 13 +#define DJUI_BTN_LEFT 14 +#define DJUI_BTN_DOWN 15 +#define DJUI_BTN_RIGHT 16 +#define DJUI_BTN_CUP 17 +#define DJUI_BTN_CLEFT 18 +#define DJUI_BTN_CDOWN 19 +#define DJUI_BTN_CRIGHT 20 +#define DJUI_BTN_START 21 +#define DJUI_BTN_SELECT 22 + struct DjuiInteractable { bool enabled; void (*update_style)(struct DjuiBase*); @@ -59,8 +83,13 @@ bool djui_interactable_is_binding(void); void djui_interactable_set_binding(struct DjuiBase* base); void djui_interactable_set_input_focus(struct DjuiBase* base); bool djui_interactable_is_input_focus(struct DjuiBase* base); +void djui_interactable_set_buttons_only(bool enabled); +bool djui_interactable_is_buttons_only(void); bool djui_interactable_on_key_down(int scancode); void djui_interactable_on_key_up(int scancode); +bool djui_interactable_on_button_down(u8 code); +void djui_interactable_on_button_up(u8 code); +void djui_interactable_on_button_held(u8 code); void djui_interactable_on_text_input(char *text); void djui_interactable_on_text_editing(char* text, int cursorPos); void djui_interactable_on_scroll(float x, float y); diff --git a/src/pc/djui/djui_lobby_entry.c b/src/pc/djui/djui_lobby_entry.c index 96abda0fe..9d531c3bb 100644 --- a/src/pc/djui/djui_lobby_entry.c +++ b/src/pc/djui/djui_lobby_entry.c @@ -2,6 +2,7 @@ #include #include "djui.h" #include "djui_lobby_entry.h" +#include "pc/string_utils.h" #define VK_ESCAPE 1 diff --git a/src/pc/djui/djui_lua_profiler.c b/src/pc/djui/djui_lua_profiler.c index 57728e6a7..68914b598 100644 --- a/src/pc/djui/djui_lua_profiler.c +++ b/src/pc/djui/djui_lua_profiler.c @@ -1,7 +1,7 @@ #include "djui_lua_profiler.h" #include "djui.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/mods/mod.h" #include "pc/mods/mods.h" diff --git a/src/pc/djui/djui_panel.c b/src/pc/djui/djui_panel.c index 58d218398..ea80db8ba 100644 --- a/src/pc/djui/djui_panel.c +++ b/src/pc/djui/djui_panel.c @@ -132,6 +132,11 @@ void djui_panel_back(void) { // play a sound play_sound(SOUND_MENU_CLICK_FILE_SELECT, NULL); + +#if defined(CAPI_SWITCH) + // Reselect the default element. + djui_cursor_input_controlled_center(sPanelList->defaultElementBase); +#endif gDjuiPanelJoinMessageVisible = false; } diff --git a/src/pc/djui/djui_panel_controls.c b/src/pc/djui/djui_panel_controls.c index e7e42857d..93309555f 100644 --- a/src/pc/djui/djui_panel_controls.c +++ b/src/pc/djui/djui_panel_controls.c @@ -8,6 +8,7 @@ #include "pc/controller/controller_api.h" #include "pc/controller/controller_sdl.h" #include "pc/controller/controller_system.h" +#include "pc/string_utils.h" void djui_panel_controls_value_change(UNUSED struct DjuiBase* caller) { controller_reconfigure(); @@ -34,14 +35,19 @@ void djui_panel_controls_create(struct DjuiBase* caller) { struct DjuiThreePanel* panel = djui_panel_menu_create(DLANG(CONTROLS, CONTROLS), false); struct DjuiBase* body = djui_three_panel_get_body(panel); { - djui_button_create(body, DLANG(CONTROLS, N64_BINDS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_controls_n64_create); - djui_button_create(body, DLANG(CONTROLS, EXTRA_BINDS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_controls_extra_create); - djui_button_create(body, DLANG(CONTROLS, ANALOG_STICK_OPTIONS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_controls_analog_stick_options_create); - djui_checkbox_create(body, DLANG(CONTROLS, BACKGROUND_GAMEPAD), &configBackgroundGamepad, NULL); -#ifndef HANDHELD - djui_checkbox_create(body, DLANG(CONTROLS, DISABLE_GAMEPADS), &configDisableGamepads, NULL); + struct DjuiButton *binds = djui_button_create(body, DLANG(CONTROLS, N64_BINDS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_controls_n64_create); + struct DjuiButton *extra_binds = djui_button_create(body, DLANG(CONTROLS, EXTRA_BINDS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_controls_extra_create); + struct DjuiButton *analog_stick_controls = djui_button_create(body, DLANG(CONTROLS, ANALOG_STICK_OPTIONS), DJUI_BUTTON_STYLE_NORMAL, djui_panel_controls_analog_stick_options_create); + struct DjuiCheckbox *background_gamepad = djui_checkbox_create(body, DLANG(CONTROLS, BACKGROUND_GAMEPAD), &configBackgroundGamepad, NULL); + struct DjuiCheckbox *disable_gamepads = djui_checkbox_create(body, DLANG(CONTROLS, DISABLE_GAMEPADS), &configDisableGamepads, NULL); + struct DjuiCheckbox *chat_keybinds = djui_checkbox_create(body, DLANG(MISC, USE_STANDARD_KEY_BINDINGS_CHAT), &configUseStandardKeyBindingsChat, NULL); + +#ifdef __SWITCH__ + djui_base_set_enabled(&binds->base, false); + djui_base_set_enabled(&extra_binds->base, false); + djui_base_set_enabled(&background_gamepad->base, false); + djui_base_set_enabled(&chat_keybinds->base, false); #endif - djui_checkbox_create(body, DLANG(MISC, USE_STANDARD_KEY_BINDINGS_CHAT), &configUseStandardKeyBindingsChat, NULL); #ifdef HAVE_SDL2 controller_update_gamepad_choices(); diff --git a/src/pc/djui/djui_panel_display.c b/src/pc/djui/djui_panel_display.c index 36be26435..876b3685c 100644 --- a/src/pc/djui/djui_panel_display.c +++ b/src/pc/djui/djui_panel_display.c @@ -2,7 +2,7 @@ #include "djui_panel.h" #include "djui_panel_menu.h" #include "pc/gfx/gfx_window_manager_api.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/utils/misc.h" #include "pc/configfile.h" @@ -23,6 +23,16 @@ static void djui_panel_display_uncapped_change(UNUSED struct DjuiBase* caller) { djui_base_set_enabled(&sInterpolationSelectionBox->base, (configFrameLimit > 30 || (configFrameLimit <= 30 && configUncappedFramerate))); } +#if defined(__SWITCH__) +static u32 sFramerateSelection = 0; + +static void djui_panel_display_frame_limit_change(UNUSED struct DjuiBase* caller) { + switch (sFramerateSelection) { + case 1: configFrameLimit = 60; break; + default: configFrameLimit = 30; break; + } +} +#else static void djui_panel_display_frame_limit_text_change(struct DjuiBase* caller) { struct DjuiInputbox* inputbox1 = (struct DjuiInputbox*)caller; s32 frameLimit = atoi(inputbox1->buffer); @@ -34,6 +44,7 @@ static void djui_panel_display_frame_limit_text_change(struct DjuiBase* caller) } djui_base_set_enabled(&sInterpolationSelectionBox->base, (configFrameLimit > 30 || (configFrameLimit <= 30 && configUncappedFramerate))); } +#endif static void djui_panel_display_msaa_change(UNUSED struct DjuiBase* caller) { switch (sMsaaSelection) { @@ -60,16 +71,22 @@ void djui_panel_display_create(struct DjuiBase* caller) { if (sMsaaOriginal == MSAA_ORIGINAL_UNSET) { sMsaaOriginal = configWindow.msaa; } { - djui_checkbox_create(body, DLANG(DISPLAY, FULLSCREEN), &configWindow.fullscreen, djui_panel_display_apply); - djui_checkbox_create(body, DLANG(DISPLAY, FORCE_4BY3), &configForce4By3, djui_panel_display_apply); djui_checkbox_create(body, DLANG(DISPLAY, SHOW_FPS), &configShowFPS, NULL); + djui_checkbox_create(body, DLANG(DISPLAY, FORCE_4BY3), &configForce4By3, djui_panel_display_apply); +#if !defined(__SWITCH__) + djui_checkbox_create(body, DLANG(DISPLAY, FULLSCREEN), &configWindow.fullscreen, djui_panel_display_apply); djui_checkbox_create(body, DLANG(DISPLAY, VSYNC), &configWindow.vsync, djui_panel_display_apply); djui_checkbox_create(body, DLANG(DISPLAY, UNCAPPED_FRAMERATE), &configUncappedFramerate, djui_panel_display_uncapped_change); +#endif struct DjuiRect* rect1 = djui_rect_container_create(body, 32); { if (configFrameLimit < 30) { configFrameLimit = 30; } if (configFrameLimit > 3000) { configFrameLimit = 3000; } +#if defined(__SWITCH__) + char *framerateChoices[3] = { "30", "60", NULL }; + struct DjuiSelectionbox *framerate = djui_selectionbox_create(body, DLANG(DISPLAY, FRAME_LIMIT), framerateChoices, 2, &sFramerateSelection, djui_panel_display_frame_limit_change); +#else struct DjuiText* text1 = djui_text_create(&rect1->base, DLANG(DISPLAY, FRAME_LIMIT)); djui_base_set_size_type(&text1->base, DJUI_SVT_RELATIVE, DJUI_SVT_ABSOLUTE); djui_base_set_color(&text1->base, 220, 220, 220, 255); @@ -87,6 +104,7 @@ void djui_panel_display_create(struct DjuiBase* caller) { djui_interactable_hook_value_change(&inputbox1->base, djui_panel_display_frame_limit_text_change); djui_base_set_enabled(&inputbox1->base, !configUncappedFramerate); sFrameLimitInput = inputbox1; +#endif } char* interpChoices[2] = { DLANG(DISPLAY, FAST), DLANG(DISPLAY, ACCURATE) }; @@ -94,7 +112,7 @@ void djui_panel_display_create(struct DjuiBase* caller) { djui_base_set_enabled(&selectionbox1->base, (configFrameLimit > 30 || (configFrameLimit <= 30 && configUncappedFramerate))); sInterpolationSelectionBox = selectionbox1; - char* filterChoices[3] = { DLANG(DISPLAY, NEAREST), DLANG(DISPLAY, LINEAR), DLANG(DISPLAY, TRIPOINT) }; + char* filterChoices[4] = { DLANG(DISPLAY, NEAREST), DLANG(DISPLAY, LINEAR), DLANG(DISPLAY, TRIPOINT), NULL }; djui_selectionbox_create(body, DLANG(DISPLAY, FILTERING), filterChoices, 3, &configFiltering, NULL); int maxMsaa = wm_api->get_max_msaa(); @@ -110,7 +128,7 @@ void djui_panel_display_create(struct DjuiBase* caller) { else if (maxMsaa >= 8) { choiceCount = 4; } else if (maxMsaa >= 4) { choiceCount = 3; } - char* msaaChoices[5] = { DLANG(DISPLAY, OFF), "2x", "4x", "8x", "16x" }; + char* msaaChoices[6] = { DLANG(DISPLAY, OFF), "2x", "4x", "8x", "16x", NULL }; msaa = djui_selectionbox_create(body, DLANG(DISPLAY, ANTIALIASING), msaaChoices, choiceCount, &sMsaaSelection, djui_panel_display_msaa_change); } diff --git a/src/pc/djui/djui_panel_join_lobbies.c b/src/pc/djui/djui_panel_join_lobbies.c index bc0d7514f..c3a598fc3 100644 --- a/src/pc/djui/djui_panel_join_lobbies.c +++ b/src/pc/djui/djui_panel_join_lobbies.c @@ -12,6 +12,7 @@ #include "pc/configfile.h" #include "pc/debuglog.h" #include "macros.h" +#include "pc/string_utils.h" #ifdef COOPNET diff --git a/src/pc/djui/djui_panel_language.c b/src/pc/djui/djui_panel_language.c index b70a56c3e..d5308754c 100644 --- a/src/pc/djui/djui_panel_language.c +++ b/src/pc/djui/djui_panel_language.c @@ -62,6 +62,8 @@ static void djui_panel_language_destroy(UNUSED struct DjuiBase* caller) { djui_panel_playerlist_create(NULL); djui_panel_modlist_create(NULL); djui_panel_main_create(NULL); + djui_panel_options_create(NULL); + djui_panel_misc_create(NULL); } else if (gDjuiInMainMenu) { djui_panel_shutdown(); gDjuiInMainMenu = true; diff --git a/src/pc/djui/djui_panel_main.c b/src/pc/djui/djui_panel_main.c index 08b730d95..3ae588041 100644 --- a/src/pc/djui/djui_panel_main.c +++ b/src/pc/djui/djui_panel_main.c @@ -6,7 +6,7 @@ #include "djui_panel_menu.h" #include "djui_panel_confirm.h" #include "pc/controller/controller_sdl.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/update_checker.h" extern ALIGNED8 u8 texture_coopdx_logo[]; diff --git a/src/pc/djui/djui_panel_mod_menu.c b/src/pc/djui/djui_panel_mod_menu.c index 6cf7f9623..b451cf134 100644 --- a/src/pc/djui/djui_panel_mod_menu.c +++ b/src/pc/djui/djui_panel_mod_menu.c @@ -4,6 +4,7 @@ #include "djui_panel_menu.h" #include "pc/lua/smlua_hooks.h" #include "pc/mods/mods.h" +#include "pc/string_utils.h" static char* to_uppercase(char* str) { char* buffer = strdup(str); diff --git a/src/pc/djui/djui_panel_options.c b/src/pc/djui/djui_panel_options.c index 37e8ea1bd..e9bb2717a 100644 --- a/src/pc/djui/djui_panel_options.c +++ b/src/pc/djui/djui_panel_options.c @@ -10,7 +10,7 @@ #include "djui_panel_dynos.h" #include "pc/network/network.h" #include "pc/utils/misc.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" static void djui_panel_options_back(struct DjuiBase* caller) { configfile_save(configfile_name()); diff --git a/src/pc/djui/djui_panel_pause.c b/src/pc/djui/djui_panel_pause.c index 9c993053d..f1b323187 100644 --- a/src/pc/djui/djui_panel_pause.c +++ b/src/pc/djui/djui_panel_pause.c @@ -8,7 +8,7 @@ #include "djui_panel_menu.h" #include "djui_panel_confirm.h" #include "djui_panel_mod_menu.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/network/network.h" #include "pc/lua/smlua_hooks.h" #include "game/object_helpers.h" diff --git a/src/pc/djui/djui_root.c b/src/pc/djui/djui_root.c index 5653e658a..3458c1f40 100644 --- a/src/pc/djui/djui_root.c +++ b/src/pc/djui/djui_root.c @@ -1,5 +1,5 @@ #include "djui.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/gfx/gfx_window_manager_api.h" static bool djui_root_render(struct DjuiBase* base) { diff --git a/src/pc/fs/fs.c b/src/pc/fs/fs.c index 91514fa14..584ad066b 100644 --- a/src/pc/fs/fs.c +++ b/src/pc/fs/fs.c @@ -53,8 +53,9 @@ bool fs_init(const char *writepath) { #endif // we shall not progress any further if the path is inaccessible - if (('\0' == fs_writepath[0]) || !fs_sys_dir_exists(fs_writepath)) { - sys_fatal("Could not access the User Preferences directory."); + if (('\0' == fs_writepath[0]) || (!fs_sys_dir_exists(fs_writepath) && !fs_sys_mkdir(fs_writepath))) { + printf("FS: Could not access the User Preferences directory."); + return false; } fs_mount(fs_writepath); diff --git a/src/pc/game_main.c b/src/pc/game_main.c new file mode 100644 index 000000000..b5ea2f93f --- /dev/null +++ b/src/pc/game_main.c @@ -0,0 +1,421 @@ +#include +#include +#include +#include +#include +#include + +#include "sm64.h" + +#include "pc/lua/smlua.h" +#include "pc/lua/utils/smlua_text_utils.h" +#include "game/memory.h" +#include "audio/data.h" +#include "audio/external.h" + +#include "network/network.h" +#include "lua/smlua.h" + +#include "audio/audio_api.h" +#include "audio/audio_sdl.h" +#include "audio/audio_null.h" + +#include "rom_assets.h" +#include "rom_checker.h" +#include "game_main.h" +#ifdef __SWITCH__ +#include "nx_main.h" +#else +#include "pc_main.h" +#endif +#include "loading.h" +#include "configfile.h" +#include "thread.h" +#include "controller/controller_api.h" +#include "controller/controller_keyboard.h" +#include "fs/fs.h" + +#include "game/display.h" // for gGlobalTimer +#include "game/game_init.h" +#include "game/main.h" +#include "game/rumble_init.h" + +#include "pc/lua/utils/smlua_audio_utils.h" + +#include "pc/network/version.h" +#include "pc/network/socket/socket.h" +#include "pc/network/network_player.h" +#include "pc/update_checker.h" +#include "pc/djui/djui.h" +#include "pc/djui/djui_unicode.h" +#include "pc/djui/djui_panel.h" +#include "pc/djui/djui_panel_modlist.h" +#include "pc/djui/djui_ctx_display.h" +#include "pc/djui/djui_fps_display.h" +#include "pc/djui/djui_lua_profiler.h" +#include "pc/debuglog.h" +#include "pc/gfx/gfx_dummy.h" +#include "pc/utils/misc.h" + +#include "pc/mods/mods.h" + +#include "debug_context.h" +#include "menu/intro_geo.h" + +#include "gfx_dimensions.h" +#include "game/segment2.h" + +#ifndef __SWITCH__ +#include "pc/mumble/mumble.h" +#endif + +extern Vp D_8032CF00; + +OSMesg D_80339BEC; +OSMesgQueue gSIEventMesgQueue; + +s8 gResetTimer; +s8 D_8032C648; +s8 gDebugLevelSelect; +s8 gShowProfiler; +s8 gShowDebugText; + +s32 gRumblePakPfs; +u32 gNumVblanks = 0; + +u8 gRenderingInterpolated = 0; +f32 gRenderingDelta = 0; + +#define FRAMERATE 30 +static const f64 sFrameTime = (1.0 / ((double)FRAMERATE)); +static f64 sFpsTimeLast = 0; +static f64 sFrameTimeStart = 0; +static u32 sDrawnFrames = 0; + +bool gGameInited = false; +bool gGfxInited = false; + +f32 gMasterVolume = 100.0f; + +u8 gLuaVolumeMaster = 127; +u8 gLuaVolumeLevel = 127; +u8 gLuaVolumeSfx = 127; +u8 gLuaVolumeEnv = 127; + +extern struct AudioAPI *audio_api; +extern struct GfxWindowManagerAPI *wm_api; + +extern void gfx_run(Gfx *commands); +extern void thread5_game_loop(void *arg); +extern void create_next_audio_buffer(s16 *samples, u32 num_samples); +void game_loop_one_iteration(void); + +void dispatch_audio_sptask(UNUSED struct SPTask *spTask) {} +void set_vblank_handler(UNUSED s32 index, UNUSED struct VblankHandler *handler, UNUSED OSMesgQueue *queue, UNUSED OSMesg *msg) {} + +void send_display_list(struct SPTask *spTask) { + if (!gGameInited) { return; } + gfx_run((Gfx *)spTask->task.t.data_ptr); +} + +#ifdef VERSION_EU +#define SAMPLES_HIGH 560 // gAudioBufferParameters.maxAiBufferLength +#define SAMPLES_LOW 528 // gAudioBufferParameters.minAiBufferLength +#else +#define SAMPLES_HIGH 544 +#define SAMPLES_LOW 528 +#endif + +extern void patch_mtx_before(void); +extern void patch_screen_transition_before(void); +extern void patch_title_screen_before(void); +extern void patch_dialog_before(void); +extern void patch_hud_before(void); +extern void patch_paintings_before(void); +extern void patch_bubble_particles_before(void); +extern void patch_snow_particles_before(void); +extern void patch_djui_before(void); +extern void patch_djui_hud_before(void); +extern void patch_scroll_targets_before(void); + +extern void patch_mtx_interpolated(f32 delta); +extern void patch_screen_transition_interpolated(f32 delta); +extern void patch_title_screen_interpolated(f32 delta); +extern void patch_dialog_interpolated(f32 delta); +extern void patch_hud_interpolated(f32 delta); +extern void patch_paintings_interpolated(f32 delta); +extern void patch_bubble_particles_interpolated(f32 delta); +extern void patch_snow_particles_interpolated(f32 delta); +extern void patch_djui_interpolated(f32 delta); +extern void patch_djui_hud(f32 delta); +extern void patch_scroll_targets_interpolated(f32 delta); + +static void patch_interpolations_before(void) { + patch_mtx_before(); + patch_screen_transition_before(); + patch_title_screen_before(); + patch_dialog_before(); + patch_hud_before(); + patch_paintings_before(); + patch_bubble_particles_before(); + patch_snow_particles_before(); + patch_djui_before(); + patch_djui_hud_before(); + patch_scroll_targets_before(); +} + +static inline void patch_interpolations(f32 delta) { + patch_mtx_interpolated(delta); + patch_screen_transition_interpolated(delta); + patch_title_screen_interpolated(delta); + patch_dialog_interpolated(delta); + patch_hud_interpolated(delta); + patch_paintings_interpolated(delta); + patch_bubble_particles_interpolated(delta); + patch_snow_particles_interpolated(delta); + patch_djui_interpolated(delta); + patch_djui_hud(delta); + patch_scroll_targets_interpolated(delta); +} + +static void compute_fps(f64 curTime) { + u32 fps = round((f64) sDrawnFrames / MAX(0.001, curTime - sFpsTimeLast)); + djui_fps_display_update(fps); + sFpsTimeLast = curTime; + sDrawnFrames = 0; +} + +static s32 get_num_frames_to_draw(f64 t) { + if (configFrameLimit % FRAMERATE == 0) { + return configFrameLimit / FRAMERATE; + } + s64 numFramesCurr = (s64) (t * (f64) configFrameLimit); + s64 numFramesNext = (s64) ((t + sFrameTime) * (f64) configFrameLimit); + return (s32) MAX(1, numFramesNext - numFramesCurr); +} + +void produce_interpolation_frames_and_delay(void) { + bool is30Fps = (!configUncappedFramerate && configFrameLimit == FRAMERATE); + + gRenderingInterpolated = true; + + f64 curTime = clock_elapsed_f64(); + f64 targetTime = sFrameTimeStart + sFrameTime; + s32 numFramesToDraw = get_num_frames_to_draw(sFrameTimeStart); + + f64 loopStartTime = curTime; + f64 expectedTime = 0; + + // interpolate and render + // make sure to draw at least one frame to prevent the game from freezing completely + // (including inputs and window events) if the game update duration is greater than 33ms + do { + f32 delta = ( + is30Fps ? + 1.0f : + MIN(MAX((curTime - sFrameTimeStart) / sFrameTime, 0.f), 1.f) + ); + gRenderingDelta = delta; + + gfx_start_frame(); + if (!gSkipInterpolationTitleScreen) { patch_interpolations(delta); } + send_display_list(gGfxSPTask); + gfx_end_frame(); + + sDrawnFrames++; + + if (!is30Fps && configUncappedFramerate) { continue; } + + // delay if our framerate is capped + f64 now = clock_elapsed_f64(); + f64 elapsedTime = now - loopStartTime; + expectedTime += (targetTime - curTime) / (f64) numFramesToDraw; + f64 delay = (expectedTime - elapsedTime) * 1000.0; + if (delay > 0.0) { + wm_api->delay((u32)delay); + } + numFramesToDraw--; + } while ((curTime = clock_elapsed_f64()) < targetTime && numFramesToDraw > 0); + + // compute and update the frame rate every second + if ((curTime = clock_elapsed_f64()) >= sFpsTimeLast + 1.0) { + compute_fps(curTime); + } + + // advance frame start time + if (curTime > sFrameTimeStart + 2 * sFrameTime) { + sFrameTimeStart = curTime; + } else { + sFrameTimeStart += sFrameTime; + } + + gRenderingInterpolated = false; +} + +void produce_one_frame(void) { + CTX_EXTENT(CTX_NETWORK, network_update); + + CTX_EXTENT(CTX_INTERP, patch_interpolations_before); + + CTX_EXTENT(CTX_GAME_LOOP, game_loop_one_iteration); + + CTX_EXTENT(CTX_SMLUA, smlua_update); + + // If we aren't threaded + if (gAudioThread.state == INVALID) { + CTX_EXTENT(CTX_AUDIO, buffer_audio); + } + + CTX_EXTENT(CTX_RENDER, produce_interpolation_frames_and_delay); +} + +// used for rendering 2D scenes fullscreen like the loading or crash screens +void produce_one_dummy_frame(void (*callback)(), u8 clearColorR, u8 clearColorG, u8 clearColorB) { + // start frame + gfx_start_frame(); + config_gfx_pool(); + init_render_image(); + create_dl_ortho_matrix(); + djui_gfx_displaylist_begin(); + + // fix scaling issues + gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(&D_8032CF00)); + gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - BORDER_HEIGHT); + + // clear screen + create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(0), 240.f, 0.f); + create_dl_scale_matrix(MENU_MTX_NOPUSH, (GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT) / 130.f, 3.f, 1.f); + gDPSetEnvColor(gDisplayListHead++, clearColorR, clearColorG, clearColorB, 0xFF); + gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + + // call the callback + callback(); + + // render frame + djui_gfx_displaylist_end(); + end_master_display_list(); + alloc_display_list(0); + gfx_run((Gfx*) gGfxSPTask->task.t.data_ptr); // send_display_list + display_and_vsync(); + gfx_end_frame(); +} + +// It's just better to have this off the stack, Because the size isn't small. +// It also may help static analysis and bug catching. +static s16 sAudioBuffer[SAMPLES_HIGH * 2 * 2] = { 0 }; + +void buffer_audio(void) { + bool shouldMute = (configMuteFocusLoss && !wm_api->has_focus()) || (gMasterVolume == 0); + if (!shouldMute) { + set_sequence_player_volume(SEQ_PLAYER_LEVEL, (f32)configMusicVolume / 127.0f * (f32)gLuaVolumeLevel / 127.0f); + set_sequence_player_volume(SEQ_PLAYER_SFX, (f32)configSfxVolume / 127.0f * (f32)gLuaVolumeSfx / 127.0f); + set_sequence_player_volume(SEQ_PLAYER_ENV, (f32)configEnvVolume / 127.0f * (f32)gLuaVolumeEnv / 127.0f); + } + + int samplesLeft = audio_api->buffered(); + u32 numAudioSamples = samplesLeft < audio_api->get_desired_buffered() ? SAMPLES_HIGH : SAMPLES_LOW; + for (s32 i = 0; i < 2; i++) { + create_next_audio_buffer(sAudioBuffer + i * (numAudioSamples * 2), numAudioSamples); + } + + if (!shouldMute) { + for (u16 i = 0; i < ARRAY_COUNT(sAudioBuffer); i++) { + sAudioBuffer[i] *= gMasterVolume; + } + audio_api->play((u8 *)sAudioBuffer, 2 * numAudioSamples * 4); + } +} + +void *audio_thread(UNUSED void *arg) { + // As long as we have an audio api and that we're threaded, Loop. + while (audio_api) { + f64 curTime = clock_elapsed_f64(); + + // Buffer the audio. + lock_mutex(&gAudioThread); + buffer_audio(); + unlock_mutex(&gAudioThread); + + // Delay till the next frame for smooth audio at the correct speed. + // delay + f64 targetDelta = 1.0 / (f64)FRAMERATE; + f64 now = clock_elapsed_f64(); + f64 actualDelta = now - curTime; + if (actualDelta < targetDelta) { + f64 delay = ((targetDelta - actualDelta) * 1000.0); + wm_api->delay((u32)delay); + } + } + + // Exit the thread if our loop breaks. + exit_thread(); + + return NULL; +} + +void audio_shutdown(void) { + audio_custom_shutdown(); + if (audio_api) { + if (audio_api->shutdown) audio_api->shutdown(); + audio_api = NULL; + } +} + +void game_deinit(void) { + if (gGameInited) { configfile_save(configfile_name()); } + controller_shutdown(); + audio_custom_shutdown(); + audio_shutdown(); + network_shutdown(true, true, false, false); + smlua_text_utils_shutdown(); + smlua_shutdown(); + smlua_audio_custom_deinit(); + mods_shutdown(); + djui_shutdown(); + gfx_shutdown(); +#ifdef __SWITCH__ + nx_cleanup(); +#endif + gGameInited = false; +} + +void game_exit(void) { + LOG_INFO("exiting cleanly"); + game_deinit(); + exit(0); +} + +void* main_game_init(UNUSED void* dummy) { + // load language + if (!djui_language_init(configLanguage)) { snprintf(configLanguage, MAX_CONFIG_STRING, "%s", ""); } + + LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Loading")); + dynos_gfx_init(); + enable_queued_dynos_packs(); + sync_objects_init_system(); + + if (gCLIOpts.network != NT_SERVER && !gCLIOpts.skipUpdateCheck) { + check_for_updates(); + } + + LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Loading ROM Assets")); + rom_assets_load(); + smlua_text_utils_init(); + + mods_init(); + enable_queued_mods(); + LOADING_SCREEN_MUTEX( + gCurrLoadingSegment.percentage = 0; + loading_screen_set_segment_text("Starting Game"); + ); + + audio_init(); + sound_init(); + network_player_init(); +#ifndef __SWITCH__ + mumble_init(); +#endif + + gGameInited = true; +} \ No newline at end of file diff --git a/src/pc/game_main.h b/src/pc/game_main.h new file mode 100644 index 000000000..2c084bb20 --- /dev/null +++ b/src/pc/game_main.h @@ -0,0 +1,46 @@ +#ifndef _GAME_MAIN_H +#define _GAME_MAIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sm64.h" +#include "audio/audio_api.h" +#include "gfx/gfx_dummy.h" + +#define AT_STARTUP __attribute__((constructor)) + +#ifdef GIT_HASH +#define TITLE ({ char title[96] = ""; snprintf(title, 96, "%s %s, [%s]", WINDOW_NAME, get_version(), GIT_HASH); title; }) +#else +#define TITLE ({ char title[96] = ""; snprintf(title, 96, "%s %s", WINDOW_NAME, get_version()); title; }) +#endif + +extern bool gGameInited; +extern bool gGfxInited; + +extern f32 gMasterVolume; + +extern u8 gLuaVolumeMaster; +extern u8 gLuaVolumeLevel; +extern u8 gLuaVolumeSfx; +extern u8 gLuaVolumeEnv; + +void buffer_audio(void); + +void produce_one_frame(void); +void produce_one_dummy_frame(void (*callback)(), u8 clearColorR, u8 clearColorG, u8 clearColorB); + +void game_deinit(void); +void game_exit(void); +void *main_game_init(UNUSED void *dummy); + +extern struct AudioAPI *audio_api; +extern struct GfxWindowManagerAPI *wm_api; + +#ifdef __cplusplus +} +#endif + +#endif // _GAME_MAIN_H \ No newline at end of file diff --git a/src/pc/gfx/gfx_dummy.c b/src/pc/gfx/gfx_dummy.c index 135ca2e3d..83c561857 100644 --- a/src/pc/gfx/gfx_dummy.c +++ b/src/pc/gfx/gfx_dummy.c @@ -14,7 +14,7 @@ #include "gfx_window_manager_api.h" #include "gfx_rendering_api.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/utils/misc.h" #include "pc/debuglog.h" @@ -29,7 +29,7 @@ static void sleep_ms(int milliseconds) { // cross-platform sleep function // from StackOverflow user Bernardo Ramos: https://stackoverflow.com/a/28827188 #ifdef WIN32 Sleep(milliseconds); -#elif _POSIX_C_SOURCE >= 199309L +#elif _POSIX_C_SOURCE >= 199309L || defined(__SWITCH__) struct timespec ts; ts.tv_sec = milliseconds / 1000; ts.tv_nsec = (milliseconds % 1000) * 1000000; diff --git a/src/pc/gfx/gfx_dxgi.cpp b/src/pc/gfx/gfx_dxgi.cpp index 8dcfdad4a..9a74c4bc5 100644 --- a/src/pc/gfx/gfx_dxgi.cpp +++ b/src/pc/gfx/gfx_dxgi.cpp @@ -31,7 +31,7 @@ extern "C" { #include "pc/configfile.h" } -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "gfx_window_manager_api.h" #include "gfx_rendering_api.h" diff --git a/src/pc/gfx/gfx_opengl.c b/src/pc/gfx/gfx_opengl.c index 46481fbf0..2f35c0c09 100644 --- a/src/pc/gfx/gfx_opengl.c +++ b/src/pc/gfx/gfx_opengl.c @@ -3,12 +3,16 @@ #include #include +#ifdef __SWITCH__ +#include +#endif + #ifndef _LANGUAGE_C # define _LANGUAGE_C #endif #include -#ifdef __MINGW32__ +#if defined(__MINGW32__) && !defined(__SWITCH__) # define FOR_WINDOWS 1 #else # define FOR_WINDOWS 0 @@ -23,13 +27,16 @@ #ifdef WAPI_SDL2 # include -# ifdef USE_GLES +# if defined(USE_GLES) +# define USE_FRAMEBUFFER 0 # include # else +# define USE_FRAMEBUFFER 0 # include # endif #elif defined(WAPI_SDL1) # include +# define USE_FRAMEBUFFER 0 # ifndef GLEW_STATIC # include # endif @@ -41,8 +48,37 @@ #include "gfx_rendering_api.h" #include "gfx_pc.h" +#define FRAMEBUFFER_WIDTH 320 +#define FRAMEBUFFER_HEIGHT 240 #define TEX_CACHE_STEP 512 +static const char *rt_vertex_shader = +#ifdef USE_GLES + "#version 100\n" +#else + "#version 120\n" +#endif + "attribute vec2 a_position;\n" + "attribute vec2 a_uv;\n" + "varying vec2 v_uv;\n" + "void main() {\n" + " gl_Position = vec4(a_position.x, a_position.y, 0.0, 1.0);\n" + " v_uv = a_uv;\n" + "}\n"; + +static const char *rt_fragment_shader = +#ifdef USE_GLES + "#version 100\n" + "precision mediump float;\n" +#else + "#version 120\n" +#endif + "varying vec2 v_uv;\n" + "uniform sampler2D u_texture;" + "void main() {\n" + " gl_FragColor = vec4(texture2D(u_texture, v_uv).rgb, 1);\n" + "}\n"; + struct ShaderProgram { uint64_t hash; GLuint opengl_program_id; @@ -63,7 +99,30 @@ struct GLTexture { bool filter; }; +struct RenderTarget { + GLuint framebuffer_id; + GLuint color_texture_id; + GLuint depth_renderbuffer_id; + + uint32_t width; + uint32_t height; +}; + +static struct { + int32_t viewport_x, viewport_y, viewport_width, viewport_height; + int32_t scissor_x, scissor_y, scissor_width, scissor_height; + int8_t depth_test, depth_mask; + int8_t zmode_decal; + + uint8_t active_texture; + GLuint bound_framebuffer; +} gl_state = { 0 }; + +static struct RenderTarget main_rt; +static struct RenderTarget framebuffer_rt; + static struct ShaderProgram shader_program_pool[CC_MAX_SHADERS]; +static struct ShaderProgram *current_shader_program = NULL; static uint8_t shader_program_pool_size = 0; static uint8_t shader_program_pool_index = 0; static GLuint opengl_vbo; @@ -73,12 +132,155 @@ static int tex_cache_size = 0; static int num_textures = 0; static struct GLTexture *tex_cache = NULL; -static struct ShaderProgram *opengl_prg = NULL; +static struct ShaderProgram rt_shader_program; static struct GLTexture *opengl_tex[2]; static int opengl_curtex = 0; +static uint32_t current_width; +static uint32_t current_height; + +static int8_t current_depth_test, current_depth_mask; +static int8_t current_zmode_decal; + +static int32_t current_viewport_x, current_viewport_y, current_viewport_width, current_viewport_height; +static int32_t current_scissor_x, current_scissor_y, current_scissor_width, current_scissor_height; + static uint32_t frame_count; +static void gfx_opengl_set_depth_test(bool depth_test); +static void gfx_opengl_set_depth_mask(bool z_upd); +static void gfx_opengl_set_zmode_decal(bool zmode_decal); +static void gfx_opengl_set_viewport(int32_t x, int32_t y, int32_t width, int32_t height); +static void gfx_opengl_set_scissor(int32_t x, int32_t y, int32_t width, int32_t height); + +static void gfx_opengl_set_active_texture(uint8_t active_texture) { + if (gl_state.active_texture == active_texture) { + return; + } + + gl_state.active_texture = active_texture; + + glActiveTexture(GL_TEXTURE0 + active_texture); +} + +static void gfx_opengl_set_vertex_buffer(float buffer[], size_t buffer_length) { + glBufferData(GL_ARRAY_BUFFER, buffer_length, buffer, GL_STREAM_DRAW); +} + +static void gfx_opengl_bind_render_target(const struct RenderTarget *render_target) { + GLuint id = render_target == NULL ? 0 : render_target->framebuffer_id; + + if (gl_state.bound_framebuffer != id) { + gl_state.bound_framebuffer = id; + glBindFramebuffer(GL_FRAMEBUFFER, id); + } +} + +static void gfx_opengl_create_render_target(uint32_t width, uint32_t height, bool is_resizing, bool has_depth_buffer, struct RenderTarget *render_target) { + // Create color texture and buffers + + if (!is_resizing) { + glGenTextures(1, &render_target->color_texture_id); + if (has_depth_buffer) { + glGenRenderbuffers(1, &render_target->depth_renderbuffer_id); + } + glGenFramebuffers(1, &render_target->framebuffer_id); + } + + // Configure color texture + + glBindTexture(GL_TEXTURE_2D, render_target->color_texture_id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Configure the depth buffer + + if (has_depth_buffer) { + glBindRenderbuffer(GL_RENDERBUFFER, render_target->depth_renderbuffer_id); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, width, height); + } + + // Bind color and depth to the framebuffer + + if (!is_resizing) { + gfx_opengl_bind_render_target(render_target); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, render_target->color_texture_id, 0); + if (has_depth_buffer) { + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, render_target->depth_renderbuffer_id); + } + } + + render_target->width = width; + render_target->height = height; +} + +// function only used when USE_FRAMEBUFFER is defined +static void gfx_opengl_draw_render_target(const struct RenderTarget *dst_render_target, const struct RenderTarget *src_render_target, bool clear_before_drawing) { + // Set render target + + uint32_t dst_width, dst_height; + + gfx_opengl_bind_render_target(dst_render_target); + + if (dst_render_target == NULL) { + dst_width = current_width; + dst_height = current_height; + } else { + dst_width = dst_render_target->width; + dst_height = dst_render_target->height; + } + + // Set some states and clear after that + + gfx_opengl_set_depth_test(false); + gfx_opengl_set_depth_mask(false); + gfx_opengl_set_zmode_decal(false); + gfx_opengl_set_viewport(0, 0, dst_width, dst_height); + gfx_opengl_set_scissor(0, 0, dst_width, dst_height); + + if (clear_before_drawing) { + glClear(GL_COLOR_BUFFER_BIT); + } + + // Set color texture + + gfx_opengl_set_active_texture(0); + glBindTexture(GL_TEXTURE_2D, src_render_target->color_texture_id); + + // Set vertex buffer data + + float dst_aspect = (float) dst_width / (float) dst_height; + float src_aspect = (float) src_render_target->width / (float) src_render_target->height; + float w = src_aspect / dst_aspect; + + float buf_vbo[] = { + -w, +1.0, 0.0, 1.0, + -w, -1.0, 0.0, 0.0, + +w, +1.0, 1.0, 1.0, + +w, -1.0, 1.0, 0.0 + }; + + uint32_t stride = 2 * 2 * sizeof(float); + gfx_opengl_set_vertex_buffer(buf_vbo, 4 * stride); + + // Draw the quad + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); +} + +static void gfx_opengl_create_render_target_views(bool is_resize) { + if (!is_resize) { + // Initialize the framebuffer only the first time. + gfx_opengl_create_render_target(FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, false, false, &framebuffer_rt); + } + + // Create the main render target where contents will be rendered. + gfx_opengl_create_render_target(current_width, current_height, is_resize, true, &main_rt); +} + static bool gfx_opengl_z_is_from_0_to_1(void) { return false; } @@ -107,19 +309,71 @@ static inline void gfx_opengl_set_texture_uniforms(struct ShaderProgram *prg, co } } +static GLuint gfx_opengl_compile_shader(const char *vertex_shader_raw, const char *fragment_shader_raw) { + GLint success; + + GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); + if (!vertex_shader) { + fprintf(stderr, "Failed to create vertex shader!\n"); + return -1; + } + glShaderSource(vertex_shader, 1, &vertex_shader_raw, NULL); + glCompileShader(vertex_shader); + glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); + if (!success) { + GLint max_length = 0; + glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &max_length); + char error_log[1024]; + fprintf(stderr, "Vertex shader compilation failed\n"); + glGetShaderInfoLog(vertex_shader, max_length, &max_length, &error_log[0]); + fprintf(stderr, "%s\n", &error_log[0]); + sys_fatal("vertex shader compilation failed (see terminal)"); + } + + GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); + if (!fragment_shader) { + fprintf(stderr, "Failed to create fragment shader!\n"); + return -1; + } + glShaderSource(fragment_shader, 1, &fragment_shader_raw, NULL); + glCompileShader(fragment_shader); + glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); + if (!success) { + GLint max_length = 0; + glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &max_length); + char error_log[1024]; + fprintf(stderr, "Fragment shader compilation failed\n"); + glGetShaderInfoLog(fragment_shader, max_length, &max_length, &error_log[0]); + fprintf(stderr, "%s\n", &error_log[0]); + sys_fatal("fragment shader compilation failed (see terminal)"); + } + + GLuint shader_program = glCreateProgram(); + glAttachShader(shader_program, vertex_shader); + glAttachShader(shader_program, fragment_shader); + glLinkProgram(shader_program); + + glDetachShader(shader_program, vertex_shader); + glDetachShader(shader_program, fragment_shader); + glDeleteShader(vertex_shader); + glDeleteShader(fragment_shader); + + return shader_program; +} + static void gfx_opengl_unload_shader(struct ShaderProgram *old_prg) { if (old_prg != NULL) { for (int i = 0; i < old_prg->num_attribs; i++) glDisableVertexAttribArray(old_prg->attrib_locations[i]); - if (old_prg == opengl_prg) - opengl_prg = NULL; + if (old_prg == current_shader_program) + current_shader_program = NULL; } else { - opengl_prg = NULL; + current_shader_program = NULL; } } static void gfx_opengl_load_shader(struct ShaderProgram *new_prg) { - opengl_prg = new_prg; + current_shader_program = new_prg; glUseProgram(new_prg->opengl_program_id); gfx_opengl_vertex_array_set_attribs(new_prg); gfx_opengl_set_shader_uniforms(new_prg); @@ -255,7 +509,7 @@ static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(struct ColorC bool opt_2cycle = cc->cm.use_2cycle; bool opt_light_map = cc->cm.light_map; -#ifdef USE_GLES +#if defined(USE_GLES) bool opt_dither = false; #else bool opt_dither = cc->cm.use_dither; @@ -268,7 +522,7 @@ static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(struct ColorC size_t num_floats = 4; // Vertex shader -#ifdef USE_GLES +#if defined(USE_GLES) append_line(vs_buf, &vs_len, "#version 100"); #else append_line(vs_buf, &vs_len, "#version 120"); @@ -311,7 +565,7 @@ static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(struct ColorC append_line(vs_buf, &vs_len, "}"); // Fragment shader -#ifdef USE_GLES +#if defined(USE_GLES) append_line(fs_buf, &fs_len, "#version 100"); append_line(fs_buf, &fs_len, "precision mediump float;"); #else @@ -367,7 +621,11 @@ static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(struct ColorC append_line(fs_buf, &fs_len, "float random(in vec3 value) {"); append_line(fs_buf, &fs_len, " float random = dot(sin(value), vec3(12.9898, 78.233, 37.719));"); +#ifdef USE_GLES + append_line(fs_buf, &fs_len, " return fract(sin(random) * 143.7585453);"); +#else append_line(fs_buf, &fs_len, " return fract(sin(random) * 143758.5453);"); +#endif append_line(fs_buf, &fs_len, "}"); } @@ -449,42 +707,7 @@ static struct ShaderProgram *gfx_opengl_create_and_load_new_shader(struct ColorC puts(fs_buf); puts("End");*/ - const GLchar *sources[2] = { vs_buf, fs_buf }; - const GLint lengths[2] = { vs_len, fs_len }; - GLint success; - - GLuint vertex_shader = glCreateShader(GL_VERTEX_SHADER); - glShaderSource(vertex_shader, 1, &sources[0], &lengths[0]); - glCompileShader(vertex_shader); - glGetShaderiv(vertex_shader, GL_COMPILE_STATUS, &success); - if (!success) { - GLint max_length = 0; - glGetShaderiv(vertex_shader, GL_INFO_LOG_LENGTH, &max_length); - char error_log[1024]; - fprintf(stderr, "Vertex shader compilation failed\n"); - glGetShaderInfoLog(vertex_shader, max_length, &max_length, &error_log[0]); - fprintf(stderr, "%s\n", &error_log[0]); - sys_fatal("vertex shader compilation failed (see terminal)"); - } - - GLuint fragment_shader = glCreateShader(GL_FRAGMENT_SHADER); - glShaderSource(fragment_shader, 1, &sources[1], &lengths[1]); - glCompileShader(fragment_shader); - glGetShaderiv(fragment_shader, GL_COMPILE_STATUS, &success); - if (!success) { - GLint max_length = 0; - glGetShaderiv(fragment_shader, GL_INFO_LOG_LENGTH, &max_length); - char error_log[1024]; - fprintf(stderr, "Fragment shader compilation failed\n"); - glGetShaderInfoLog(fragment_shader, max_length, &max_length, &error_log[0]); - fprintf(stderr, "%s\n", &error_log[0]); - sys_fatal("fragment shader compilation failed (see terminal)"); - } - - GLuint shader_program = glCreateProgram(); - glAttachShader(shader_program, vertex_shader); - glAttachShader(shader_program, fragment_shader); - glLinkProgram(shader_program); + GLuint shader_program = gfx_opengl_compile_shader(vs_buf, fs_buf); size_t cnt = 0; @@ -597,7 +820,7 @@ static void gfx_opengl_select_texture(int tile, GLuint texture_id) { opengl_curtex = tile; glActiveTexture(GL_TEXTURE0 + tile); glBindTexture(GL_TEXTURE_2D, opengl_tex[tile]->gltex); - gfx_opengl_set_texture_uniforms(opengl_prg, tile); + gfx_opengl_set_texture_uniforms(current_shader_program, tile); } static void gfx_opengl_upload_texture(const uint8_t *rgba32_buf, int width, int height) { @@ -623,11 +846,17 @@ static void gfx_opengl_set_sampler_parameters(int tile, bool linear_filter, uint opengl_curtex = tile; if (opengl_tex[tile]) { opengl_tex[tile]->filter = linear_filter; - gfx_opengl_set_texture_uniforms(opengl_prg, tile); + gfx_opengl_set_texture_uniforms(current_shader_program, tile); } } static void gfx_opengl_set_depth_test(bool depth_test) { + if (gl_state.depth_test == depth_test) { + return; + } + + gl_state.depth_test = depth_test; + if (depth_test) { glEnable(GL_DEPTH_TEST); } else { @@ -636,10 +865,22 @@ static void gfx_opengl_set_depth_test(bool depth_test) { } static void gfx_opengl_set_depth_mask(bool z_upd) { + if (gl_state.depth_mask == z_upd) { + return; + } + + gl_state.depth_mask = z_upd; + glDepthMask(z_upd ? GL_TRUE : GL_FALSE); } static void gfx_opengl_set_zmode_decal(bool zmode_decal) { + if (gl_state.zmode_decal == zmode_decal) { + return; + } + + gl_state.zmode_decal = zmode_decal; + if (zmode_decal) { glPolygonOffset(-2, -2); glEnable(GL_POLYGON_OFFSET_FILL); @@ -648,12 +889,29 @@ static void gfx_opengl_set_zmode_decal(bool zmode_decal) { glDisable(GL_POLYGON_OFFSET_FILL); } } +static void gfx_opengl_set_viewport(int32_t x, int32_t y, int32_t width, int32_t height) { + if (gl_state.viewport_x == x && gl_state.viewport_y == y && gl_state.viewport_width == width && gl_state.viewport_height == height) { + return; + } + + gl_state.viewport_x = x; + gl_state.viewport_y = y; + gl_state.viewport_width = width; + gl_state.viewport_height = height; -static void gfx_opengl_set_viewport(int x, int y, int width, int height) { glViewport(x, y, width, height); } -static void gfx_opengl_set_scissor(int x, int y, int width, int height) { +static void gfx_opengl_set_scissor(int32_t x, int32_t y, int32_t width, int32_t height) { + if (gl_state.scissor_x == x && gl_state.scissor_y == y && gl_state.scissor_width == width && gl_state.scissor_height == height) { + return; + } + + gl_state.scissor_x = x; + gl_state.scissor_y = y; + gl_state.scissor_width = width; + gl_state.scissor_height = height; + glScissor(x, y, width, height); } @@ -666,14 +924,29 @@ static void gfx_opengl_set_use_alpha(bool use_alpha) { } static void gfx_opengl_draw_triangles(float buf_vbo[], size_t buf_vbo_len, size_t buf_vbo_num_tris) { - //printf("flushing %d tris\n", buf_vbo_num_tris); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * buf_vbo_len, buf_vbo, GL_STREAM_DRAW); + // Depth + + gfx_opengl_set_depth_test(current_depth_test); + gfx_opengl_set_depth_mask(current_depth_mask); + gfx_opengl_set_zmode_decal(current_zmode_decal); + + // Viewport and Scissor + + gfx_opengl_set_viewport(current_viewport_x, current_viewport_y, current_viewport_width, current_viewport_height); + gfx_opengl_set_scissor(current_scissor_x, current_scissor_y, current_scissor_width, current_scissor_height); + + // Draw vertex buffer + + gfx_opengl_set_vertex_buffer(buf_vbo, buf_vbo_len * sizeof(float)); glDrawArrays(GL_TRIANGLES, 0, 3 * buf_vbo_num_tris); } static inline bool gl_get_version(int *major, int *minor, bool *is_es) { const char *vstr = (const char *)glGetString(GL_VERSION); - if (!vstr || !vstr[0]) return false; + if (!vstr || !vstr[0]) { + + return false; + } if (!strncmp(vstr, "OpenGL ES ", 10)) { vstr += 10; @@ -686,34 +959,103 @@ static inline bool gl_get_version(int *major, int *minor, bool *is_es) { return (sscanf(vstr, "%d.%d", major, minor) == 2); } +static void gfx_opengl_get_framebuffer(uint16_t *buffer) { + if (USE_FRAMEBUFFER) { + gfx_opengl_bind_render_target(&framebuffer_rt); + + uint8_t pixels[FRAMEBUFFER_WIDTH * FRAMEBUFFER_HEIGHT * 4]; + glReadPixels(0, 0, FRAMEBUFFER_WIDTH, FRAMEBUFFER_HEIGHT, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + uint32_t bi = 0; + for (int32_t y = FRAMEBUFFER_HEIGHT - 1; y >= 0; y--) { + for (int32_t x = 0; x < FRAMEBUFFER_WIDTH; x++) { + uint32_t fb_pixel = (y * FRAMEBUFFER_WIDTH + x) * 4; + + uint8_t r = pixels[fb_pixel + 0] >> 3; + uint8_t g = pixels[fb_pixel + 1] >> 3; + uint8_t b = pixels[fb_pixel + 2] >> 3; + uint8_t a = 1; //pixels[fb_pixel + 3] / 255; + + buffer[bi] = (r << 11) | (g << 6) | (b << 1) | a; + bi++; + } + } + } +} + static void gfx_opengl_init(void) { #if FOR_WINDOWS || defined(OSX_BUILD) GLenum err; if ((err = glewInit()) != GLEW_OK) sys_fatal("could not init GLEW:\n%s", glewGetErrorString(err)); #endif - tex_cache_size = TEX_CACHE_STEP; tex_cache = calloc(tex_cache_size, sizeof(struct GLTexture)); - if (!tex_cache) sys_fatal("out of memory allocating texture cache"); + if (!tex_cache) { + printf("Ran out of memory allocating texture cache!"); + return; + } - // check GL version + // Check GL version int vmajor = 0; int vminor = 0; bool is_es = false; gl_get_version(&vmajor, &vminor, &is_es); - if (vmajor < 2 && vminor < 1 && !is_es) - sys_fatal("OpenGL 2.1+ is required.\nReported version: %s%d.%d", is_es ? "ES" : "", vmajor, vminor); + if (vmajor < 2 && vminor < 1 && !is_es) { + printf("GL: OpenGL 2.1+ is required.\nGL: Reported version: %s%d.%d\n", is_es ? "ES" : "", vmajor, vminor); + return; + } + + printf("GL: OpenGL loaded:\n"); + printf("Vendor: %s\n", glGetString(GL_VENDOR)); + printf("Renderer: %s\n", glGetString(GL_RENDERER)); + printf("Version: %s\n", glGetString(GL_VERSION)); + printf("Using: OpenGL %s%d.%d\n", is_es ? "ES " : " ", vmajor, vminor); + + // Initialize resolution before drawing first frame + + if (current_width != gfx_current_dimensions.width || current_height != gfx_current_dimensions.height) { + current_width = gfx_current_dimensions.width; + current_height = gfx_current_dimensions.height; + } + + // Initialize render targets + + if (USE_FRAMEBUFFER) { + gfx_opengl_create_render_target_views(false); + + // Create the render target shader, used to draw into fullscreen quads + + rt_shader_program.opengl_program_id = gfx_opengl_compile_shader(rt_vertex_shader, rt_fragment_shader); + rt_shader_program.attrib_locations[0] = glGetAttribLocation(rt_shader_program.opengl_program_id, "a_position"); + rt_shader_program.attrib_sizes[0] = 2; + rt_shader_program.attrib_locations[1] = glGetAttribLocation(rt_shader_program.opengl_program_id, "a_uv"); + rt_shader_program.attrib_sizes[1] = 2; + rt_shader_program.num_attribs = 2; + rt_shader_program.num_floats = 4; + rt_shader_program.used_textures[0] = true; + rt_shader_program.used_textures[1] = false; + rt_shader_program.num_inputs = 0; // Unused in this case + rt_shader_program.used_noise = false; // Unused in this case + rt_shader_program.used_lightmap = false; // Unused in this case + + glUseProgram(rt_shader_program.opengl_program_id); + GLint sampler_location = glGetUniformLocation(rt_shader_program.opengl_program_id, "u_texture"); + glUniform1i(sampler_location, 0); + } glGenBuffers(1, &opengl_vbo); - glBindBuffer(GL_ARRAY_BUFFER, opengl_vbo); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); +#if !defined(__SWITCH__) if (vmajor >= 3 && !is_es) { glGenVertexArrays(1, &opengl_vao); glBindVertexArray(opengl_vao); } +#endif + glDepthFunc(GL_LEQUAL); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } @@ -723,6 +1065,16 @@ static void gfx_opengl_on_resize(void) { static void gfx_opengl_start_frame(void) { frame_count++; + + if (USE_FRAMEBUFFER) { + if (current_width != gfx_current_dimensions.width || current_height != gfx_current_dimensions.height) { + current_width = gfx_current_dimensions.width; + current_height = gfx_current_dimensions.height; + gfx_opengl_create_render_target_views(true); + } + } + + gfx_opengl_bind_render_target(&main_rt); glDisable(GL_SCISSOR_TEST); glDepthMask(GL_TRUE); // Must be set to clear Z-buffer @@ -732,6 +1084,23 @@ static void gfx_opengl_start_frame(void) { } static void gfx_opengl_end_frame(void) { + if (USE_FRAMEBUFFER) { + // Set the shader and vertex attribs for quad rendering + + glUseProgram(rt_shader_program.opengl_program_id); + gfx_opengl_vertex_array_set_attribs(&rt_shader_program); + + // Draw quad with main render target into the other render targets + + gfx_opengl_draw_render_target(NULL, &main_rt, false); + gfx_opengl_draw_render_target(&framebuffer_rt, &main_rt, true); + + // Set again the last shader used before drawing render targets. + // Not doing so can lead to rendering issues on the first drawcalls + // of the next frame, if they use the same shader as the ones before. + + gfx_opengl_load_shader(current_shader_program); + } } static void gfx_opengl_finish_render(void) { @@ -740,6 +1109,32 @@ static void gfx_opengl_finish_render(void) { static void gfx_opengl_shutdown(void) { } +static void gfx_opengl_set_current_depth_test(bool depth_test) { + current_depth_test = depth_test; +} + +static void gfx_opengl_set_current_depth_mask(bool z_upd) { + current_depth_mask = z_upd; +} + +static void gfx_opengl_set_current_zmode_decal(bool zmode_decal) { + current_zmode_decal = zmode_decal; +} + +static void gfx_opengl_set_current_viewport(int x, int y, int width, int height) { + current_viewport_x = x; + current_viewport_y = y; + current_viewport_width = width; + current_viewport_height = height; +} + +static void gfx_opengl_set_current_scissor(int x, int y, int width, int height) { + current_scissor_x = x; + current_scissor_y = y; + current_scissor_width = width; + current_scissor_height = height; +} + struct GfxRenderingAPI gfx_opengl_api = { gfx_opengl_z_is_from_0_to_1, gfx_opengl_unload_shader, @@ -751,11 +1146,11 @@ struct GfxRenderingAPI gfx_opengl_api = { gfx_opengl_select_texture, gfx_opengl_upload_texture, gfx_opengl_set_sampler_parameters, - gfx_opengl_set_depth_test, - gfx_opengl_set_depth_mask, - gfx_opengl_set_zmode_decal, - gfx_opengl_set_viewport, - gfx_opengl_set_scissor, + gfx_opengl_set_current_depth_test, + gfx_opengl_set_current_depth_mask, + gfx_opengl_set_current_zmode_decal, + gfx_opengl_set_current_viewport, + gfx_opengl_set_current_scissor, gfx_opengl_set_use_alpha, gfx_opengl_draw_triangles, gfx_opengl_init, diff --git a/src/pc/gfx/gfx_pc.c b/src/pc/gfx/gfx_pc.c index 05cc7ac68..bec3a0f10 100644 --- a/src/pc/gfx/gfx_pc.c +++ b/src/pc/gfx/gfx_pc.c @@ -28,6 +28,7 @@ #include "pc/configfile.h" #include "pc/debug_context.h" +#include "pc/game_main.h" #include "pc/pc_main.h" #include "pc/platform.h" diff --git a/src/pc/gfx/gfx_sdl1.c b/src/pc/gfx/gfx_sdl1.c index cf6b4a272..08cfe2af9 100644 --- a/src/pc/gfx/gfx_sdl1.c +++ b/src/pc/gfx/gfx_sdl1.c @@ -17,7 +17,7 @@ #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" -#include "../pc_main.h" +#include "../game_main.h" #include "../configfile.h" #include "../cliopts.h" #include "../platform.h" diff --git a/src/pc/gfx/gfx_sdl2.c b/src/pc/gfx/gfx_sdl2.c index 4d99d9e8b..0cf3103c9 100644 --- a/src/pc/gfx/gfx_sdl2.c +++ b/src/pc/gfx/gfx_sdl2.c @@ -33,7 +33,7 @@ #include "gfx_window_manager_api.h" #include "gfx_screen_config.h" -#include "../pc_main.h" +#include "../game_main.h" #include "../configfile.h" #include "../cliopts.h" @@ -56,7 +56,7 @@ # define FRAMERATE 30 #endif -static SDL_Window *wnd; +static SDL_Window *wnd = NULL; static SDL_GLContext ctx = NULL; static kb_callback_t kb_key_down = NULL; @@ -74,6 +74,10 @@ static inline void gfx_sdl_set_vsync(const bool enabled) { } static void gfx_sdl_set_fullscreen(void) { +#ifdef __SWITCH__ + SDL_SetWindowFullscreen(wnd, 0); + SDL_ShowCursor(SDL_ENABLE); +#else if (configWindow.reset) configWindow.fullscreen = false; if (configWindow.fullscreen == IS_FULLSCREEN()) @@ -82,17 +86,37 @@ static void gfx_sdl_set_fullscreen(void) { SDL_SetWindowFullscreen(wnd, SDL_WINDOW_FULLSCREEN_DESKTOP); } else { SDL_SetWindowFullscreen(wnd, 0); - SDL_ShowCursor(1); + SDL_ShowCursor(SDL_ENABLE); configWindow.exiting_fullscreen = true; } +#endif } static void gfx_sdl_reset_dimension_and_pos(void) { if (configWindow.exiting_fullscreen) { configWindow.exiting_fullscreen = false; - SDL_ShowCursor(0); + SDL_ShowCursor(SDL_DISABLE); } +#ifdef __SWITCH__ + if (configWindow.reset) { + configWindow.x = 0; + configWindow.y = 0; + if (appletGetOperationMode() == 1) { + configWindow.w = 1920; + configWindow.h = 1080; + } else { + configWindow.w = 1280; + configWindow.h = 720; + } + configWindow.reset = false; + } else if (!configWindow.settings_changed) { + return; + } + + SDL_SetWindowSize(wnd, configWindow.w, configWindow.h); + SDL_SetWindowPosition(wnd, configWindow.x, configWindow.y); +#else if (configWindow.reset) { configWindow.x = WAPI_WIN_CENTERPOS; configWindow.y = WAPI_WIN_CENTERPOS; @@ -108,18 +132,22 @@ static void gfx_sdl_reset_dimension_and_pos(void) { SDL_SetWindowSize(wnd, configWindow.w, configWindow.h); SDL_SetWindowPosition(wnd, xpos, ypos); + // in case vsync changed gfx_sdl_set_vsync(configWindow.vsync); +#endif } -static void gfx_sdl_init(const char *window_title) { +static void gfx_sdl_init(const char *window_title) { #if defined(_WIN32) || defined(_WIN64) SetProcessDPIAware(); #endif SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); SDL_Init(SDL_INIT_VIDEO); +#ifndef __SWITCH__ SDL_StartTextInput(); +#endif if (configWindow.msaa > 0) { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); @@ -127,11 +155,46 @@ static void gfx_sdl_init(const char *window_title) { } else { SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0); } - + + // Request a depth buffer SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); -#ifdef USE_GLES +#ifdef __SWITCH__ + // Request an OpenGL 3 context + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + + if (appletGetOperationMode() == 1) { + configWindow.w = 1920; + configWindow.h = 1080; + } else { + configWindow.w = 1280; + configWindow.h = 720; + } + configWindow.x = 0; + configWindow.y = 0; + + printf("Creating SDL Window with size: %d %d\n", configWindow.w, configWindow.h); + wnd = SDL_CreateWindow("sdl2_gles2", configWindow.w, configWindow.h, configWindow.w, configWindow.h, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN); + if (!wnd) { + printf("SDL2: Failed to create window with error: %s\n", SDL_GetError()); + return; + } + + ctx = SDL_GL_CreateContext(wnd); + if (!ctx) { + printf("SDL2: Failed to create context with error: %s\n", SDL_GetError()); + return; + } + + gfx_sdl_set_vsync(2); + gfx_sdl_set_fullscreen(); +#else + +#if defined(USE_GLES) + // Request an OpenGL 2.1 context SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); // These attributes allow for hardware acceleration on RPis. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); @@ -139,13 +202,17 @@ static void gfx_sdl_init(const char *window_title) { int xpos = (configWindow.x == WAPI_WIN_CENTERPOS) ? SDL_WINDOWPOS_CENTERED : configWindow.x; int ypos = (configWindow.y == WAPI_WIN_CENTERPOS) ? SDL_WINDOWPOS_CENTERED : configWindow.y; - + wnd = SDL_CreateWindow( window_title, xpos, ypos, configWindow.w, configWindow.h, SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE ); ctx = SDL_GL_CreateContext(wnd); + if (!ctx) { + printf("SDL2: Failed to create context with error: %s", SDL_GetError()); + return; + } gfx_sdl_set_vsync(configWindow.vsync); @@ -153,6 +220,7 @@ static void gfx_sdl_init(const char *window_title) { if (configWindow.fullscreen) { SDL_ShowCursor(SDL_DISABLE); } +#endif controller_bind_init(); } @@ -311,7 +379,7 @@ static void gfx_sdl_set_window_title(const char* title) { } static void gfx_sdl_reset_window_title(void) { - SDL_SetWindowTitle(wnd, TITLE); + SDL_SetWindowTitle(wnd, "SM64 Coop DX"); } static void gfx_sdl_shutdown(void) { diff --git a/src/pc/gfx/gfx_window_manager_api.h b/src/pc/gfx/gfx_window_manager_api.h index f1e7760ef..d5836210e 100644 --- a/src/pc/gfx/gfx_window_manager_api.h +++ b/src/pc/gfx/gfx_window_manager_api.h @@ -14,7 +14,7 @@ typedef bool (*kb_callback_t)(int code); struct GfxWindowManagerAPI { void (*init)(const char *window_title); void (*set_keyboard_callbacks)(kb_callback_t on_key_down, kb_callback_t on_key_up, void (*on_all_keys_up)(void), - void (*on_text_input)(char*), void (*on_text_editing)(char*, int)); + void (*on_text_input)(char*), void (*on_text_editing)(char*, int)); void (*set_scroll_callback)(void (*on_scroll)(float, float)); void (*main_loop)(void (*run_one_game_iter)(void)); void (*get_dimensions)(uint32_t *width, uint32_t *height); @@ -26,7 +26,7 @@ struct GfxWindowManagerAPI { void (*shutdown)(void); void (*start_text_input)(void); void (*stop_text_input)(void); - char* (*get_clipboard_text)(void); + char *(*get_clipboard_text)(void); void (*set_clipboard_text)(const char*); void (*set_cursor_visible)(bool); void (*delay)(unsigned int ms); diff --git a/src/pc/loading.c b/src/pc/loading.c index dc3adcc03..5fda9e182 100644 --- a/src/pc/loading.c +++ b/src/pc/loading.c @@ -7,7 +7,7 @@ #include "djui/djui.h" #include "pc/djui/djui_unicode.h" -#include "pc_main.h" +#include "game_main.h" #include "pc/utils/misc.h" #include "pc/cliopts.h" #include "rom_checker.h" @@ -179,7 +179,7 @@ void render_loading_screen(void) { // loading screen loop while (!gGameInited) { - WAPI.main_loop(loading_screen_produce_one_frame); + wm_api->main_loop(loading_screen_produce_one_frame); } int err = join_thread(&gLoadingThread); @@ -192,7 +192,7 @@ void render_rom_setup_screen(void) { loading_screen_set_segment_text("No rom detected, drag & drop Super Mario 64 (U) [!].z64 on to this screen"); while (!gRomIsValid) { - WAPI.main_loop(loading_screen_produce_one_frame); + wm_api->main_loop(loading_screen_produce_one_frame); } } diff --git a/src/pc/lua/smlua.h b/src/pc/lua/smlua.h index 3704460b1..faa7c723b 100644 --- a/src/pc/lua/smlua.h +++ b/src/pc/lua/smlua.h @@ -18,6 +18,7 @@ #include "pc/debuglog.h" #include "pc/djui/djui_console.h" +#include "pc/string_utils.h" #define LOG_LUA(...) { if (!gSmLuaSuppressErrors) { printf("[LUA] "), printf(__VA_ARGS__), printf("\n"), smlua_mod_error(), snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__), sys_swap_backslashes(gDjuiConsoleTmpBuffer), djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_ERROR); } } #define LOG_LUA_LINE(...) { if (!gSmLuaSuppressErrors) { printf("[LUA] "), printf(__VA_ARGS__), printf("\n"), smlua_mod_error(); snprintf(gDjuiConsoleTmpBuffer, CONSOLE_MAX_TMP_BUFFER, __VA_ARGS__), sys_swap_backslashes(gDjuiConsoleTmpBuffer), djui_console_message_create(gDjuiConsoleTmpBuffer, CONSOLE_MESSAGE_ERROR), smlua_logline(); } } diff --git a/src/pc/lua/smlua_hooks.c b/src/pc/lua/smlua_hooks.c index fc14780e6..d67db25c8 100644 --- a/src/pc/lua/smlua_hooks.c +++ b/src/pc/lua/smlua_hooks.c @@ -14,12 +14,13 @@ #include "pc/network/network_player.h" #include "pc/network/socket/socket.h" #include "pc/chat_commands.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/djui/djui_lua_profiler.h" #include "pc/djui/djui_panel.h" #include "pc/configfile.h" #include "pc/utils/misc.h" #include "pc/lua/utils/smlua_model_utils.h" +#include "pc/string_utils.h" #include "../mods/mods.h" #include "game/print.h" diff --git a/src/pc/lua/utils/smlua_anim_utils.c b/src/pc/lua/utils/smlua_anim_utils.c index 84a09fbc2..9bb58be5f 100644 --- a/src/pc/lua/utils/smlua_anim_utils.c +++ b/src/pc/lua/utils/smlua_anim_utils.c @@ -3,6 +3,7 @@ #include "pc/lua/smlua.h" #include "smlua_anim_utils.h" #include "pc/debuglog.h" +#include "pc/string_utils.h" // models #include "actors/common0.h" diff --git a/src/pc/lua/utils/smlua_audio_utils.c b/src/pc/lua/utils/smlua_audio_utils.c index 2345b0878..63324f623 100644 --- a/src/pc/lua/utils/smlua_audio_utils.c +++ b/src/pc/lua/utils/smlua_audio_utils.c @@ -1,5 +1,3 @@ -#define MINIAUDIO_IMPLEMENTATION // required by miniaudio - // enable Vorbis decoding (provides ogg audio decoding support) for miniaudio #define STB_VORBIS_HEADER_ONLY #include "pc/utils/stb_vorbis.c" @@ -15,7 +13,7 @@ #include "pc/mods/mods_utils.h" #include "pc/utils/misc.h" #include "pc/debuglog.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/fs/fmem.h" struct AudioOverride { @@ -158,19 +156,15 @@ void smlua_audio_utils_replace_sequence(u8 sequenceId, u8 bankId, u8 defaultVolu // mod audio // /////////////// -// Optimization: disable spatialization for everything as it's not used -#define MA_SOUND_STREAM_FLAGS (MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_STREAM) -#define MA_SOUND_SAMPLE_FLAGS (MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_DECODE) // No pitch, pre-decode audio samples - static ma_engine sModAudioEngine; static struct DynamicPool *sModAudioPool; static void smlua_audio_custom_init(void) { sModAudioPool = dynamic_pool_init(); - ma_result result = ma_engine_init(NULL, &sModAudioEngine); + ma_result result = ma_initalize(NULL, &sModAudioEngine); if (result != MA_SUCCESS) { - LOG_ERROR("failed to init Miniaudio: %d", result); + LOG_ERROR("Failed to init Miniaudio: %d", result); } } @@ -291,23 +285,11 @@ struct ModAudio* audio_load_internal(const char* filename, bool isStream) { } f_close(f); f_delete(f); - - // decode the audio buffer - ma_result result = ma_decoder_init_memory(buffer, size, NULL, &audio->decoder); + + ma_result result = ma_sound_from_buffer(&sModAudioEngine, &audio->sound, &audio->decoder, buffer, size, isStream); if (result != MA_SUCCESS) { free(buffer); - LOG_ERROR("failed to load audio file '%s': failed to decode raw audio: %d", filename, result); - return NULL; - } - - result = ma_sound_init_from_data_source( - &sModAudioEngine, &audio->decoder, - isStream ? MA_SOUND_STREAM_FLAGS : MA_SOUND_SAMPLE_FLAGS, - NULL, &audio->sound - ); - if (result != MA_SUCCESS) { - free(buffer); - LOG_ERROR("failed to load audio file '%s': %d", filename, result); + LOG_ERROR("Failed to load audio file '%s': Failed to decode raw audio: %d", filename, result); return NULL; } @@ -331,8 +313,8 @@ void audio_stream_destroy(struct ModAudio* audio) { void audio_stream_play(struct ModAudio* audio, bool restart, f32 volume) { if (!audio_sanity_check(audio, true, "play")) { return; } - - if (configMuteFocusLoss && !WAPI.has_focus()) { + + if (configMuteFocusLoss && !wm_api->has_focus()) { ma_sound_set_volume(&audio->sound, 0); } else { f32 musicVolume = (f32)configMusicVolume / 127.0f * (f32)gLuaVolumeLevel / 127.0f; @@ -359,7 +341,7 @@ void audio_stream_stop(struct ModAudio* audio) { f32 audio_stream_get_position(struct ModAudio* audio) { if (!audio_sanity_check(audio, true, "get stream position from")) { return 0; } - u64 cursor; ma_data_source_get_cursor_in_pcm_frames(&audio->decoder, &cursor); + ma_uint64 cursor; ma_data_source_get_cursor_in_pcm_frames(&audio->decoder, &cursor); return (f32)cursor / ma_engine_get_sample_rate(&sModAudioEngine); } @@ -384,7 +366,7 @@ void audio_stream_set_looping(struct ModAudio* audio, bool looping) { void audio_stream_set_loop_points(struct ModAudio* audio, s64 loopStart, s64 loopEnd) { if (!audio_sanity_check(audio, true, "set stream loop points for")) { return; } - u64 length; ma_data_source_get_length_in_pcm_frames(&audio->decoder, &length); + ma_uint64 length; ma_data_source_get_length_in_pcm_frames(&audio->decoder, &length); if (loopStart < 0) loopStart += length; if (loopEnd <= 0) loopEnd += length; @@ -423,9 +405,9 @@ f32 audio_stream_get_volume(struct ModAudio* audio) { } void audio_stream_set_volume(struct ModAudio* audio, f32 volume) { - if (!audio_sanity_check(audio, true, "set stream volume for")) { return; } - - if (configMuteFocusLoss && !WAPI.has_focus()) { + if (!audio_sanity_check(audio, true, "set stream volume")) { return; } + + if (configMuteFocusLoss && !wm_api->has_focus()) { ma_sound_set_volume(&audio->sound, 0); } else { f32 musicVolume = (f32)configMusicVolume / 127.0f * (f32)gLuaVolumeLevel / 127.0f; @@ -531,9 +513,7 @@ void audio_sample_play(struct ModAudio* audio, Vec3f position, f32 volume) { ma_sound *sound = &audio->sound; if (ma_sound_is_playing(sound)) { struct ModAudioSampleCopies* copy = calloc(1, sizeof(struct ModAudioSampleCopies)); - ma_result result = ma_decoder_init_memory(audio->buffer, audio->bufferSize, NULL, ©->decoder); - if (result != MA_SUCCESS) { return; } - result = ma_sound_init_from_data_source(&sModAudioEngine, ©->decoder, MA_SOUND_SAMPLE_FLAGS, NULL, ©->sound); + ma_result result = ma_sound_from_buffer(&sModAudioEngine, ©->sound, ©->decoder, audio->buffer, audio->bufferSize, false); if (result != MA_SUCCESS) { return; } ma_sound_set_end_callback(©->sound, audio_sample_copy_end_callback, copy); copy->parent = audio; @@ -563,7 +543,7 @@ void audio_sample_play(struct ModAudio* audio, Vec3f position, f32 volume) { pan = (get_sound_pan(mtx[3][0] * factor, mtx[3][2] * factor) - 0.5f) * 2.0f; } - if (configMuteFocusLoss && !WAPI.has_focus()) { + if (configMuteFocusLoss && !wm_api->has_focus()) { ma_sound_set_volume(sound, 0); } else { f32 intensity = sound_get_level_intensity(dist); @@ -584,7 +564,7 @@ void audio_custom_update_volume(void) { while (node) { struct DynamicPoolNode* prev = node->prev; struct ModAudio* audio = node->ptr; - if (configMuteFocusLoss && !WAPI.has_focus()) { + if (configMuteFocusLoss && !wm_api->has_focus()) { ma_sound_set_volume(&audio->sound, 0); } else if (audio->isStream) { ma_sound_set_volume(&audio->sound, gMasterVolume * musicVolume * audio->baseVolume); @@ -615,7 +595,7 @@ void smlua_audio_custom_deinit(void) { if (sModAudioPool) { audio_custom_shutdown(); free(sModAudioPool); - ma_engine_uninit(&sModAudioEngine); + ma_uninitalize(&sModAudioEngine); sModAudioPool = NULL; } } diff --git a/src/pc/lua/utils/smlua_audio_utils.h b/src/pc/lua/utils/smlua_audio_utils.h index 4fc276548..a3337218b 100644 --- a/src/pc/lua/utils/smlua_audio_utils.h +++ b/src/pc/lua/utils/smlua_audio_utils.h @@ -1,7 +1,7 @@ #ifndef SMLUA_AUDIO_UTILS_H #define SMLUA_AUDIO_UTILS_H -#include "pc/utils/miniaudio.h" +#include "pc/utils/miniaudio_api.h" /* |description|Resets all custom sequences back to vanilla|descriptionEnd| */ void smlua_audio_utils_reset_all(void); diff --git a/src/pc/lua/utils/smlua_misc_utils.c b/src/pc/lua/utils/smlua_misc_utils.c index d9b11800e..643e8832e 100644 --- a/src/pc/lua/utils/smlua_misc_utils.c +++ b/src/pc/lua/utils/smlua_misc_utils.c @@ -14,7 +14,7 @@ #include "pc/mods/mod.h" #include "pc/mods/mods.h" #include "pc/mods/mods_utils.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "game/object_list_processor.h" #include "game/rendering_graph_node.h" #include "game/level_update.h" @@ -539,11 +539,11 @@ struct Mod* get_active_mod(void) { /// void set_window_title(const char* title) { - WAPI.set_window_title(title); + wm_api->set_window_title(title); } void reset_window_title(void) { - WAPI.reset_window_title(); + wm_api->reset_window_title(); } /// diff --git a/src/pc/lua/utils/smlua_text_utils.c b/src/pc/lua/utils/smlua_text_utils.c index 573c9b0b3..6ccf14f27 100644 --- a/src/pc/lua/utils/smlua_text_utils.c +++ b/src/pc/lua/utils/smlua_text_utils.c @@ -6,7 +6,7 @@ #include "game/save_file.h" #include "game/segment2.h" #include "game/level_info.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "../smlua.h" #include "smlua_level_utils.h" #include "smlua_text_utils.h" diff --git a/src/pc/mods/mod.c b/src/pc/mods/mod.c index dbc97c93e..2c0a96cff 100644 --- a/src/pc/mods/mod.c +++ b/src/pc/mods/mod.c @@ -6,6 +6,7 @@ #include "pc/utils/misc.h" #include "pc/utils/md5.h" #include "pc/debuglog.h" +#include "pc/string_utils.h" #include "pc/fs/fmem.h" size_t mod_get_lua_size(struct Mod* mod) { diff --git a/src/pc/mods/mod_cache.c b/src/pc/mods/mod_cache.c index fa1a662d1..a38a96fcb 100644 --- a/src/pc/mods/mod_cache.c +++ b/src/pc/mods/mod_cache.c @@ -9,6 +9,7 @@ #include "mods_utils.h" #include "pc/utils/md5.h" #include "pc/lua/smlua_hooks.h" +#include "pc/string_utils.h" #include "pc/loading.h" #define MOD_CACHE_FILENAME "mod.cache" diff --git a/src/pc/mods/mods.c b/src/pc/mods/mods.c index 9af7ab2b3..f5c370386 100644 --- a/src/pc/mods/mods.c +++ b/src/pc/mods/mods.c @@ -6,7 +6,8 @@ #include "pc/debuglog.h" #include "pc/loading.h" #include "pc/fs/fmem.h" -#include "pc/pc_main.h" +#include "pc/string_utils.h" +#include "pc/game_main.h" #include "pc/utils/misc.h" #if defined(_WIN32) || defined(_WIN64) diff --git a/src/pc/mods/mods_utils.c b/src/pc/mods/mods_utils.c index 734442656..257fb2e68 100644 --- a/src/pc/mods/mods_utils.c +++ b/src/pc/mods/mods_utils.c @@ -3,6 +3,7 @@ #include "mods.h" #include "mods_utils.h" #include "pc/debuglog.h" +#include "pc/string_utils.h" #if defined(_WIN32) || defined(_WIN64) #include @@ -26,6 +27,9 @@ void mods_size_enforce(struct Mods* mods) { } static bool mods_incompatible_match(struct Mod* a, struct Mod* b) { +#ifdef __SWITCH__ + return false; +#else if (a->incompatible == NULL || b->incompatible == NULL) { return false; } @@ -52,6 +56,7 @@ static bool mods_incompatible_match(struct Mod* a, struct Mod* b) { free(bi); return false; +#endif } void mods_update_selectable(void) { diff --git a/src/pc/mumble/mumble.c b/src/pc/mumble/mumble.c index dfe814601..eecd6154e 100644 --- a/src/pc/mumble/mumble.c +++ b/src/pc/mumble/mumble.c @@ -1,3 +1,5 @@ +#ifndef __CONSOLE__ + // adapted from https://www.mumble.info/documentation/developer/positional-audio/link-plugin/ #include "mumble.h" @@ -196,3 +198,5 @@ bool should_update_context() { return true; } + +#endif // __CONSOLE__ \ No newline at end of file diff --git a/src/pc/mumble/mumble.h b/src/pc/mumble/mumble.h index e31c2bb47..427ce0c1f 100644 --- a/src/pc/mumble/mumble.h +++ b/src/pc/mumble/mumble.h @@ -1,3 +1,5 @@ +#ifndef __CONSOLE__ + #ifndef MUMBLE_H #define MUMBLE_H @@ -31,4 +33,6 @@ void mumble_update_menu(void); bool should_update_context(void); -#endif /* MUMBLE_H */ \ No newline at end of file +#endif /* MUMBLE_H */ + +#endif // __CONSOLE__ \ No newline at end of file diff --git a/src/pc/network/ban_list.c b/src/pc/network/ban_list.c index e136b94c9..98e372fc2 100644 --- a/src/pc/network/ban_list.c +++ b/src/pc/network/ban_list.c @@ -3,6 +3,7 @@ #include #include "ban_list.h" #include "pc/debuglog.h" +#include "pc/string_utils.h" char** gBanAddresses = NULL; bool* gBanPerm = NULL; diff --git a/src/pc/network/moderator_list.c b/src/pc/network/moderator_list.c index c8be0d860..4e39c2dd2 100644 --- a/src/pc/network/moderator_list.c +++ b/src/pc/network/moderator_list.c @@ -3,6 +3,7 @@ #include #include "moderator_list.h" #include "pc/debuglog.h" +#include "pc/string_utils.h" char** gModeratorAddresses = NULL; bool* gModerator = NULL; diff --git a/src/pc/network/network.c b/src/pc/network/network.c index 5c652eefc..988c546c4 100644 --- a/src/pc/network/network.c +++ b/src/pc/network/network.c @@ -19,7 +19,7 @@ #include "pc/mods/mods.h" #include "pc/crash_handler.h" #include "pc/debuglog.h" -#include "pc/pc_main.h" +#include "pc/game_main.h" #include "pc/gfx/gfx_pc.h" #include "pc/fs/fmem.h" #include "game/hardcoded.h" diff --git a/src/pc/network/packets/packet_download.c b/src/pc/network/packets/packet_download.c index ab5aa4917..fcc68b330 100644 --- a/src/pc/network/packets/packet_download.c +++ b/src/pc/network/packets/packet_download.c @@ -10,6 +10,7 @@ //#define DISABLE_MODULE_LOG 1 #include "pc/debuglog.h" #include "pc/fs/fmem.h" +#include "pc/string_utils.h" #define CHUNK_SIZE 800 #define OFFSET_COUNT 50 diff --git a/src/pc/network/packets/packet_mod_list.c b/src/pc/network/packets/packet_mod_list.c index 2a4a7baf8..136d5e51e 100644 --- a/src/pc/network/packets/packet_mod_list.c +++ b/src/pc/network/packets/packet_mod_list.c @@ -6,6 +6,7 @@ #include "pc/djui/djui_panel_join_message.h" #include "pc/debuglog.h" #include "pc/mods/mod_cache.h" +#include "pc/string_utils.h" void network_send_mod_list_request(void) { SOFT_ASSERT(gNetworkType == NT_CLIENT); diff --git a/src/pc/network/socket/socket.h b/src/pc/network/socket/socket.h index 045f3c3a4..07e1e2154 100644 --- a/src/pc/network/socket/socket.h +++ b/src/pc/network/socket/socket.h @@ -1,19 +1,9 @@ #ifndef SOCKET_H #define SOCKET_H -#ifdef WINSOCK -#include "socket_windows.h" +#ifdef __SWITCH__ +#include "socket_nx.h" #else -#include "socket_linux.h" -#endif - -#include "../network.h" - -extern struct NetworkSystem gNetworkSystemSocket; - -extern char gGetHostName[]; - -SOCKET socket_initialize(void); -void socket_shutdown(SOCKET socket); - -#endif +#include "socket_pc.h" +#endif // __SWITCH__ +#endif // SOCKET_H diff --git a/src/pc/network/socket/socket_linux.c b/src/pc/network/socket/socket_linux.c index a1903b078..f3c3ece0f 100644 --- a/src/pc/network/socket/socket_linux.c +++ b/src/pc/network/socket/socket_linux.c @@ -1,4 +1,4 @@ -#ifndef WINSOCK +#if !defined(WINSOCK) && !defined(__SWITCH__) #include "socket_linux.h" #include "../network.h" #include "pc/debuglog.h" @@ -38,4 +38,4 @@ void socket_shutdown(SOCKET socket) { } } -#endif +#endif \ No newline at end of file diff --git a/src/pc/network/socket/socket_linux.h b/src/pc/network/socket/socket_linux.h index 7cdb7f9c4..4c4d27bec 100644 --- a/src/pc/network/socket/socket_linux.h +++ b/src/pc/network/socket/socket_linux.h @@ -2,7 +2,7 @@ #define SOCKET_LINUX_H #include -#include +#include "netinet/in.h" #include #include #include diff --git a/src/pc/network/socket/socket_nx.c b/src/pc/network/socket/socket_nx.c new file mode 100644 index 000000000..72a4adae8 --- /dev/null +++ b/src/pc/network/socket/socket_nx.c @@ -0,0 +1,316 @@ +#ifdef __SWITCH__ +#include "socket_nx.h" +#include "../network.h" +#include "pc/configfile.h" +#include "pc/debuglog.h" +#include "pc/djui/djui.h" + +#include + +struct sockaddr_in; +struct sin_addr; + +static SOCKET sCurSocket = INVALID_SOCKET; +static struct sockaddr_in sAddr[MAX_PLAYERS] = { 0 }; +struct addrinfo hints; +struct addrinfo *result, *i; + +char gGetHostName[MAX_CONFIG_STRING] = ""; + +// Resolves a hostname to an IP address. Current limitation: It still only gets the first address it sees and returns. +// getaddrinfo() is smart enough to prioritize IPv4 if the user is not in an IPv6 enabled network, so this shouldn't be a problem for now. +// TODO: Store all found addresses somewhere and make the game try to connect to each of them if one fails. +void resolve_domain(struct sockaddr_in *addr) { + // non zero value if getaddrinfo throws an error. + int error; + + // set hints + memset(&hints, 0, sizeof(hints)); + hints.ai_socktype = SOCK_DGRAM; + + // sanity check: remove square brackets from configJoinIp. getaddrinfo doesn't like those, at least on Linux. + if (configJoinIp[0] == '[') { + LOG_INFO("sanity check: found opening square bracket on configJoinIp, removing it."); + for (int i = 0; i < MAX_CONFIG_STRING; i++) { + if (configJoinIp[i] == '\0') { break; } + if (configJoinIp[i] == ']') { + configJoinIp[i] = '\0'; + memcpy(&configJoinIp, &configJoinIp[1], MAX_CONFIG_STRING-1); + break; + } + } + } + + // Get host addresses + error = getaddrinfo(configJoinIp, NULL, &hints, &result); + + // If it wasn't successful + if (error != 0) { + LOG_ERROR("getaddrinfo() failed with error code %i: %s", error, gai_strerror(error)); + } + + // If it was successful: + + // Iterate through results + for (i = result; i != NULL; i = i->ai_next) { + // Buffer for IPv4 address + char str[INET_ADDRSTRLEN]; + + // Sadly since we can't claim the IPv6 port on Switch. + // We can't use any of the IPv6 addresses. + if (i->ai_addr->sa_family != AF_INET) { continue; } + + // IPv4 address: + struct sockaddr_in *p = (struct sockaddr_in *)i->ai_addr; + // copy address to sockaddr_in struct + memcpy(&addr->sin_addr, &p->sin_addr, sizeof(struct in_addr)); + // set new join IP for config file + snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", inet_ntop(AF_INET, &p->sin_addr, str, sizeof(str))); + // Free results from memory and return + freeaddrinfo(result); + return; + } +} + +static int socket_bind(SOCKET socket, unsigned int port) { + struct sockaddr_in rxAddr; + rxAddr.sin_family = AF_INET; + rxAddr.sin_port = htons(port); + rxAddr.sin_addr.s_addr = htonl(INADDR_ANY); + int rc = bind(socket, (SOCKADDR *)&rxAddr, sizeof(rxAddr)); + if (rc != 0) { + LOG_ERROR("bind failed with error %d", SOCKET_LAST_ERROR); + } + + return rc; +} + +static int socket_send(SOCKET socket, struct sockaddr_in *addr, u8 *buffer, u16 bufferLength) { + int addrSize = sizeof(struct sockaddr_in); + int rc = sendto(socket, (char *)buffer, bufferLength, 0, (struct sockaddr *)addr, addrSize); + if (rc != SOCKET_ERROR) { return NO_ERROR; } + + int error = SOCKET_LAST_ERROR; + if (error == SOCKET_EWOULDBLOCK) { return NO_ERROR; } + + LOG_ERROR("sendto failed with error: %d", error); + return rc; +} + +static int socket_receive(SOCKET socket, struct sockaddr_in *rxAddr, u8 *buffer, u16 bufferLength, u16 *receiveLength, u8 *localIndex) { + *receiveLength = 0; + + RX_ADDR_SIZE_TYPE rxAddrSize = sizeof(struct sockaddr_in); + int rc = recvfrom(socket, (char*)buffer, bufferLength, 0, (struct sockaddr*)rxAddr, &rxAddrSize); + + for (int i = 1; i < MAX_PLAYERS; i++) { + if (memcmp(rxAddr, &sAddr[i], sizeof(struct sockaddr_in)) == 0) { + *localIndex = i; + break; + } + } + + if (rc == SOCKET_ERROR) { + int error = SOCKET_LAST_ERROR; + if (error != SOCKET_EWOULDBLOCK && error != SOCKET_ECONNRESET) { + LOG_ERROR("recvfrom failed with error %d", SOCKET_LAST_ERROR); + } + return SOCKET_ERROR; + } + + *receiveLength = rc; + return NO_ERROR; +} + +SOCKET socket_initialize(void) { + // initialize socket + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) { + LOG_ERROR("socket failed with error %d", SOCKET_LAST_ERROR); + return INVALID_SOCKET; + } + + // set non-blocking mode + int rc = fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); + if (rc == (int)INVALID_SOCKET) { + LOG_ERROR("fcntl failed with error: %d", rc); + return INVALID_SOCKET; + } + + // The Nintendo Switch does support IPv6. But that socket is already claimed! + // We aren't able to retrieve an already opened socket so we have to settle for IPv4. + // It could be worse I suppose. + + LOG_INFO("Socket initialized."); + + return sock; +} + +void socket_shutdown(SOCKET socket) { + if (socket == INVALID_SOCKET) { return; } + int rc = closesocket(socket); + if (rc == (int)SOCKET_ERROR) { + LOG_ERROR("closesocket failed with error %d\n", SOCKET_LAST_ERROR); + } +} + +static bool ns_socket_initialize(enum NetworkType networkType, UNUSED bool reconnecting) { + // sanity check port + unsigned int port = (networkType == NT_CLIENT) ? configJoinPort : configHostPort; + if (port == 0) { port = DEFAULT_PORT; } + + // create a receiver socket to receive datagrams + sCurSocket = socket_initialize(); + if (sCurSocket == INVALID_SOCKET) { return false; } + + // connect + if (networkType == NT_SERVER) { + int reuse = 1; + if (setsockopt(sCurSocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&reuse, sizeof(reuse)) < 0) { + LOG_ERROR("setsockopt(SO_REUSEADDR) failed"); + } + +#ifdef SO_REUSEPORT + if (setsockopt(sCurSocket, SOL_SOCKET, SO_REUSEPORT, (const char*)&reuse, sizeof(reuse)) < 0) { + LOG_ERROR("setsockopt(SO_REUSEPORT) failed"); + } +#endif + // bind the socket to any address and the specified port. + int rc = socket_bind(sCurSocket, port); + if (rc != NO_ERROR) { + LOG_ERROR("bind returned an error."); + return false; + } + LOG_INFO("Bound to port %u", port); + } else if (networkType == NT_CLIENT) { + struct sockaddr_in addr; + // set and clean struct to prevent garbage data + memset(&addr, 0, sizeof(struct sockaddr_in)); + // save the port to send to + sAddr[0].sin_family = AF_INET; + sAddr[0].sin_port = htons(port); + // resolve and get address list to connect + resolve_domain(&addr); + sAddr[0].sin_addr = addr.sin_addr; + LOG_INFO("Connecting to %s, port %u", configJoinIp, port); + // copy hostname to be saved to config file + snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", gGetHostName); + + // kick off first packet + char joinText[128] = { 0 }; + snprintf(joinText, 63, "%s %d", configJoinIp, configJoinPort); + djui_connect_menu_open(); + + gNetworkType = NT_CLIENT; + } + + LOG_INFO("Initialized"); + + if (networkType == NT_CLIENT) { + network_send_mod_list_request(); + } + + // success + return true; +} + +static s64 ns_socket_get_id(UNUSED u8 localId) { + return 0; +} + +static char* ns_socket_get_id_str(u8 localId) { + if (localId == UNKNOWN_LOCAL_INDEX) { localId = 0; } + static char id_str[INET_ADDRSTRLEN] = { 0 }; + snprintf(id_str, INET_ADDRSTRLEN, "%s", inet_ntop(AF_INET, &sAddr[localId].sin_addr, id_str, sizeof(id_str))); + return id_str; +} + +static void ns_socket_save_id(u8 localId, UNUSED s64 networkId) { + SOFT_ASSERT(localId > 0); + SOFT_ASSERT(localId < MAX_PLAYERS); + sAddr[localId] = sAddr[0]; + LOG_INFO("saved addr for id %d", localId); +} + +static void ns_socket_clear_id(u8 localId) { + if (localId == 0) { return; } + SOFT_ASSERT(localId < MAX_PLAYERS); + memset(&sAddr[localId], 0, sizeof(struct sockaddr_in)); + LOG_INFO("cleared addr for id %d", localId); +} + +static void* ns_socket_dup_addr(u8 localIndex) { + void* address = malloc(sizeof(struct sockaddr_in)); + memcpy(address, &sAddr[localIndex], sizeof(struct sockaddr_in)); + return address; +} + +static bool ns_socket_match_addr(void* addr1, void* addr2) { + return !memcmp(addr1, addr2, sizeof(struct sockaddr_in)); +} + +static void ns_socket_update(void) { + if (gNetworkType == NT_NONE) { return; } + do { + // receive packet + u8 data[PACKET_LENGTH + 1]; + u16 dataLength = 0; + u8 localIndex = UNKNOWN_LOCAL_INDEX; + int rc = socket_receive(sCurSocket, &sAddr[0], data, PACKET_LENGTH + 1, &dataLength, &localIndex); + SOFT_ASSERT(dataLength < PACKET_LENGTH); + if (rc != NO_ERROR) { break; } + network_receive(localIndex, &sAddr[0], data, dataLength); + } while (true); +} + +static int ns_socket_send(u8 localIndex, void* address, u8* data, u16 dataLength) { + if (localIndex != 0) { + if (gNetworkType == NT_SERVER && gNetworkPlayers[localIndex].type != NPT_CLIENT) { return SOCKET_ERROR; } + if (gNetworkType == NT_CLIENT && gNetworkPlayers[localIndex].type != NPT_SERVER) { return SOCKET_ERROR; } + } + + struct sockaddr_in *userAddr = &sAddr[localIndex]; + if (localIndex == 0 && address != NULL) { userAddr = (struct sockaddr_in *)address; } + + int rc = socket_send(sCurSocket, userAddr, data, dataLength); + if (rc) { + LOG_ERROR(" localIndex: %d, packetType: %d, dataLength: %d", localIndex, data[0], dataLength); + } + return rc; +} + +static void ns_socket_get_lobby_id(char* destination, u32 destLength) { + snprintf(destination, destLength, "%s", ""); // TODO: we can probably hook this up +} + +static void ns_socket_get_lobby_secret(char* destination, u32 destLength) { + snprintf(destination, destLength, "%s", ""); // TODO: we can probably hook this up +} + +static void ns_socket_shutdown(UNUSED bool reconnecting) { + socket_shutdown(sCurSocket); + sCurSocket = INVALID_SOCKET; + for (u16 i = 0; i < MAX_PLAYERS; i++) { + memset(&sAddr[i], 0, sizeof(struct sockaddr_in)); + } + LOG_INFO("shutdown"); +} + +struct NetworkSystem gNetworkSystemSocket = { + .initialize = ns_socket_initialize, + .get_id = ns_socket_get_id, + .get_id_str = ns_socket_get_id_str, + .save_id = ns_socket_save_id, + .clear_id = ns_socket_clear_id, + .dup_addr = ns_socket_dup_addr, + .match_addr = ns_socket_match_addr, + .update = ns_socket_update, + .send = ns_socket_send, + .get_lobby_id = ns_socket_get_lobby_id, + .get_lobby_secret = ns_socket_get_lobby_secret, + .shutdown = ns_socket_shutdown, + .requireServerBroadcast = true, + .name = "Socket", +}; + +#endif \ No newline at end of file diff --git a/src/pc/network/socket/socket_nx.h b/src/pc/network/socket/socket_nx.h new file mode 100644 index 000000000..6b7681684 --- /dev/null +++ b/src/pc/network/socket/socket_nx.h @@ -0,0 +1,29 @@ +#ifndef SOCKET_NX_H +#define SOCKET_NX_H + +#include +#include "netinet/in.h" +#include +#include +#include +#include +#include +#include +#include "../network.h" + +#define SOCKET unsigned int +#define INVALID_SOCKET (unsigned int)(-1) +#define SOCKET_LAST_ERROR errno +#define NO_ERROR (0) +#define SOCKADDR struct sockaddr +#define SOCKET_ERROR (-1) +#define closesocket(fd) close(fd) +#define SOCKET_EWOULDBLOCK EWOULDBLOCK +#define SOCKET_ECONNRESET ECONNRESET +#define RX_ADDR_SIZE_TYPE unsigned int + +extern struct NetworkSystem gNetworkSystemSocket; + +extern char gGetHostName[]; + +#endif // SOCKET_NX_H \ No newline at end of file diff --git a/src/pc/network/socket/socket.c b/src/pc/network/socket/socket_pc.c similarity index 98% rename from src/pc/network/socket/socket.c rename to src/pc/network/socket/socket_pc.c index edf179202..b32ac6dec 100644 --- a/src/pc/network/socket/socket.c +++ b/src/pc/network/socket/socket_pc.c @@ -1,9 +1,14 @@ +#ifndef __SWITCH__ + #include "socket.h" #include #include "pc/configfile.h" #include "pc/debuglog.h" #include "pc/djui/djui.h" +struct sockaddr_in6; +struct sin6_addr; + static SOCKET sCurSocket = INVALID_SOCKET; static struct sockaddr_in6 sAddr[MAX_PLAYERS] = { 0 }; struct addrinfo hints; @@ -88,8 +93,7 @@ static int socket_bind(SOCKET socket, unsigned int port) { rxAddr.sin6_port = htons(port); rxAddr.sin6_addr = in6addr_any; - int rc = bind(socket, (SOCKADDR *)&rxAddr, sizeof(rxAddr)); - + int rc = bind(socket, (SOCKADDR *)&rxAddr, sizeof(struct sockaddr_in6)); if (rc != 0) { LOG_ERROR("bind failed with error %d", SOCKET_LAST_ERROR); } @@ -292,3 +296,5 @@ struct NetworkSystem gNetworkSystemSocket = { .requireServerBroadcast = true, .name = "Socket", }; + +#endif \ No newline at end of file diff --git a/src/pc/network/socket/socket_pc.h b/src/pc/network/socket/socket_pc.h new file mode 100644 index 000000000..9c5ad12dd --- /dev/null +++ b/src/pc/network/socket/socket_pc.h @@ -0,0 +1,19 @@ +#ifndef SOCKET_PC_H +#define SOCKET_PC_H + +#ifdef WINSOCK +#include "socket_windows.h" +#else +#include "socket_linux.h" +#endif + +#include "../network.h" + +extern struct NetworkSystem gNetworkSystemSocket; + +extern char gGetHostName[]; + +SOCKET socket_initialize(void); +void socket_shutdown(SOCKET socket); + +#endif diff --git a/src/pc/network/socket/socket_windows.c b/src/pc/network/socket/socket_windows.c index 4435848df..602e4c53c 100644 --- a/src/pc/network/socket/socket_windows.c +++ b/src/pc/network/socket/socket_windows.c @@ -1,4 +1,4 @@ -#ifdef WINSOCK +#if defined(WINSOCK) && !defined(__SWITCH__) #include "socket_windows.h" #include #include "pc/debuglog.h" diff --git a/src/pc/network/socket/socket_windows.h b/src/pc/network/socket/socket_windows.h index 91000bcae..86470f4c3 100644 --- a/src/pc/network/socket/socket_windows.h +++ b/src/pc/network/socket/socket_windows.h @@ -9,4 +9,4 @@ #define SOCKET_ECONNRESET WSAECONNRESET #define RX_ADDR_SIZE_TYPE int -#endif +#endif // SOCKET_WINDOWS_H \ No newline at end of file diff --git a/src/pc/nx_main.c b/src/pc/nx_main.c new file mode 100644 index 000000000..7a5650ed6 --- /dev/null +++ b/src/pc/nx_main.c @@ -0,0 +1,237 @@ +#ifdef __SWITCH__ +#include + +#include +#include +#include +#include +#include +#include + +#include "sm64.h" + +#include "pc/lua/smlua.h" +#include "pc/lua/utils/smlua_text_utils.h" +#include "game/memory.h" +#include "audio/data.h" +#include "audio/external.h" + +#include "network/network.h" +#include "lua/smlua.h" + +#include "audio/audio_api.h" +#include "audio/audio_sdl.h" +#include "audio/audio_null.h" + +#include "rom_assets.h" +#include "rom_checker.h" +#include "nx_main.h" +#include "loading.h" +#include "cliopts.h" +#include "configfile.h" +#include "thread.h" +#include "controller/controller_api.h" +#include "controller/controller_keyboard.h" +#include "controller/controller_switch.h" +#include "fs/fs.h" + +#include "game/display.h" // for gGlobalTimer +#include "game/game_init.h" +#include "game/main.h" +#include "game/rumble_init.h" + +#include "pc/lua/utils/smlua_audio_utils.h" + +#include "pc/network/version.h" +#include "pc/network/socket/socket.h" +#include "pc/network/network_player.h" +#include "pc/update_checker.h" +#include "pc/djui/djui.h" +#include "pc/djui/djui_unicode.h" +#include "pc/djui/djui_panel.h" +#include "pc/djui/djui_panel_modlist.h" +#include "pc/djui/djui_ctx_display.h" +#include "pc/djui/djui_fps_display.h" +#include "pc/djui/djui_lua_profiler.h" +#include "pc/debuglog.h" +#include "pc/utils/misc.h" + +#include "pc/mods/mods.h" + +#include "debug_context.h" +#include "menu/intro_geo.h" + +#include "gfx_dimensions.h" +#include "game/segment2.h" + +struct AudioAPI *audio_api = NULL; +struct GfxWindowManagerAPI *wm_api = &WAPI; + +static bool gNxInitialized = false; + +void nx_init(void) { + accountInitialize(AccountServiceType_Application); + socketInitializeDefault(); + nifmInitialize(NifmServiceType_User); + plInitialize(PlServiceType_User); + Result rc = romfsInit(); + if (R_FAILED(rc)) { + printf("romfsInit: %08X\n", rc); + } + nxlinkStdio(); + + gNxInitialized = true; +} + +void nx_cleanup(void) { + if (!gNxInitialized) { return; } + + accountExit(); + socketExit(); + nifmExit(); + plExit(); + romfsExit(); + + gNxInitialized = false; +} + +int main(int argc, char *argv[]) { + nx_init(); + + // Initialize our filesystem, If it fails. + // We can't continue. + if (!fs_init("sdmc:/switch/sm64coopdx")) { + fflush(stdout); + nx_cleanup(); + return 0; + } + + printf("Loading game config.\n"); + + // Load our config. + configfile_load(); + + + printf("Loading gfx for game.\n"); + + // Create the window as soon as possible. + if (!gGfxInited) { + gfx_init(&WAPI, &RAPI, "Super Mario 64 Coop Deluxe"); + } + + printf("Checking for game ROM.\n"); + + // Render the ROM setup screen + if (!main_rom_handler()) { +#ifdef LOADING_SCREEN_SUPPORTED + if (!gCLIOpts.hideLoadingScreen) { + render_rom_setup_screen(); // holds the game load until a valid rom is provided + } else +#endif + { + printf("ERROR: Could not find a valid vanilla US SM64 rom in the game user folder.\n"); + fflush(stdout); + nx_cleanup(); + return 0; + } + } + + printf("Loading game data.\n"); + + // Start the thread for setting up the game +#ifdef LOADING_SCREEN_SUPPORTED + bool threadSuccess = false; + if (!gCLIOpts.hideLoadingScreen && !gCLIOpts.headless) { + if (init_thread_handle(&gLoadingThread, main_game_init, NULL, NULL, 0) == 0) { + render_loading_screen(); // render the loading screen while the game is setup + threadSuccess = true; + destroy_mutex(&gLoadingThread); + } + } + if (!threadSuccess) +#endif + { + main_game_init(NULL); // failsafe incase threading doesn't work + } + + // initialize sm64 data and controllers + thread5_game_loop(NULL); + + printf("Initializing audio.\n"); + + // initialize sound outside threads + if (audio_sdl.init()) audio_api = &audio_sdl; + if (!audio_api) audio_api = &audio_null; + + // Initialize the audio thread if possible. + // init_thread_handle(&gAudioThread, audio_thread, NULL, NULL, 0); + +#ifdef LOADING_SCREEN_SUPPORTED + loading_screen_reset(); +#endif + + printf("Initializing DJUI.\n"); + + // initialize djui + djui_init(); + djui_unicode_init(); + djui_init_late(); + djui_console_message_dequeue(); + + show_update_popup(); + + printf("Initializing networking.\n"); + + // initialize network + if (gCLIOpts.network == NT_CLIENT) { + network_set_system(NS_SOCKET); + snprintf(gGetHostName, MAX_CONFIG_STRING, "%s", gCLIOpts.joinIp); + snprintf(configJoinIp, MAX_CONFIG_STRING, "%s", gCLIOpts.joinIp); + configJoinPort = gCLIOpts.networkPort; + network_init(NT_CLIENT, false); + } else if (gCLIOpts.network == NT_SERVER || gCLIOpts.coopnet) { + if (gCLIOpts.network == NT_SERVER) { + configNetworkSystem = NS_SOCKET; + configHostPort = gCLIOpts.networkPort; + } else { + configNetworkSystem = NS_COOPNET; + snprintf(configPassword, MAX_CONFIG_STRING, "%s", gCLIOpts.coopnetPassword); + } + + // horrible, hacky fix for mods that access marioObj straight away + // best fix: host with the standard main menu method + static struct Object sHackyObject = { 0 }; + gMarioStates[0].marioObj = &sHackyObject; + + extern void djui_panel_do_host(bool reconnecting, bool playSound); + djui_panel_do_host(NULL, false); + } else { + network_init(NT_NONE, false); + } + + printf("Starting main loop.\n"); + + // main loop + while (appletMainLoop()) { + debug_context_reset(); + + CTX_BEGIN(CTX_TOTAL); + WAPI.main_loop(produce_one_frame); + CTX_END(CTX_TOTAL); + +#ifdef DEBUG + fflush(stdout); + fflush(stderr); +#endif + +#ifdef DEVELOPMENT + djui_ctx_display_update(); +#endif + djui_lua_profiler_update(); + } + + nx_cleanup(); + return 0; +} + +#endif // __SWITCH__ \ No newline at end of file diff --git a/src/pc/nx_main.h b/src/pc/nx_main.h new file mode 100644 index 000000000..9fc131a43 --- /dev/null +++ b/src/pc/nx_main.h @@ -0,0 +1,36 @@ +#ifdef __SWITCH__ + +#ifndef _NX_MAIN_H +#define _NX_MAIN_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "pc/game_main.h" + +#include "gfx/gfx_pc.h" +#include "gfx/gfx_opengl.h" +#include "gfx/gfx_sdl.h" + +#define WAPI gfx_sdl +#ifndef WAPI_SDL2 +#define WAPI_SDL2 +#endif + +#define RAPI gfx_opengl_api +#define RAPI_NAME "OpenGL ES" + +extern struct AudioAPI *audio_api; +extern struct GfxWindowManagerAPI *wm_api; + +void nx_init(void); +void nx_cleanup(void); + +#ifdef __cplusplus +} +#endif + +#endif // _NX_MAIN_H + +#endif // __SWITCH__ \ No newline at end of file diff --git a/src/pc/nx_utils.cpp b/src/pc/nx_utils.cpp new file mode 100644 index 000000000..f65febd7a --- /dev/null +++ b/src/pc/nx_utils.cpp @@ -0,0 +1,46 @@ +#ifdef __SWITCH__ + +#include "nx_utils.h" + +AccountUid get_current_user() { + AccountUid uid; + uid.uid[0] = 0; + uid.uid[1] = 0; + accountGetPreselectedUser(&uid); + + return uid; +} + +int ensure_save_data(AccountUid uid) { + return serviceDispatchIn(appletGetServiceSession_Functions(), 20, uid); +} + +int ensure_current_user_save() { + return ensure_save_data(get_current_user()); +} + +int mount_save_data() { + FsFileSystem fileSystem; + + if(fsOpen_SaveData(&fileSystem, FS_SAVEDATA_CURRENT_APPLICATIONID, get_current_user())) { + return 1; + } + + if(fsdevMountDevice("sv", fileSystem) == -1) { + fsdevUnmountDevice("sv"); + return 2; + } + + return 0; +} + +int unmount_save_data() { + if(fsdevUnmountDevice("sv") == -1) { return 1; } + return 0; +} + +int commit_save() { + return fsdevCommitDevice("sv"); +} + +#endif // __SWITCH__ \ No newline at end of file diff --git a/src/pc/nx_utils.h b/src/pc/nx_utils.h new file mode 100644 index 000000000..dbfafded6 --- /dev/null +++ b/src/pc/nx_utils.h @@ -0,0 +1,27 @@ +#ifdef __SWITCH__ + +#ifndef _NX_UTILS_H +#define _NX_UTILS_H + +#ifdef __cplusplus + +#include + +AccountUid get_current_user(void); +int ensure_save_data(AccountUid uid); + +extern "C" { +#endif + +int ensure_current_user_save(void); +int mount_save_data(void); +int unmount_save_data(void); +int commit_save(void); + +#ifdef __cplusplus +} +#endif + +#endif // _NX_UTILS_H + +#endif // __SWITCH__ \ No newline at end of file diff --git a/src/pc/pc_main.c b/src/pc/pc_main.c index aa6745e06..766df8d6e 100644 --- a/src/pc/pc_main.c +++ b/src/pc/pc_main.c @@ -1,3 +1,5 @@ +#ifndef __CONSOLE__ + #include #include #include @@ -71,357 +73,9 @@ #include #endif -#include "game/local_multiplayer.h" - -extern Vp D_8032CF00; - -OSMesg D_80339BEC; -OSMesgQueue gSIEventMesgQueue; - -s8 gResetTimer; -s8 D_8032C648; -s8 gDebugLevelSelect; -s8 gShowProfiler; -s8 gShowDebugText; - -s32 gRumblePakPfs; -u32 gNumVblanks = 0; - -u8 gRenderingInterpolated = 0; -f32 gRenderingDelta = 0; - -#define FRAMERATE 30 -static const f64 sFrameTime = (1.0 / ((double)FRAMERATE)); -static f64 sFpsTimeLast = 0; -static f64 sFrameTimeStart = 0; -static u32 sDrawnFrames = 0; - -bool gGameInited = false; -bool gGfxInited = false; - -f32 gMasterVolume; - -u8 gLuaVolumeMaster = 127; -u8 gLuaVolumeLevel = 127; -u8 gLuaVolumeSfx = 127; -u8 gLuaVolumeEnv = 127; - -static struct AudioAPI *audio_api; +struct AudioAPI *audio_api = NULL; struct GfxWindowManagerAPI *wm_api = &WAPI; -extern void gfx_run(Gfx *commands); -extern void thread5_game_loop(void *arg); -extern void create_next_audio_buffer(s16 *samples, u32 num_samples); -void game_loop_one_iteration(void); - -void dispatch_audio_sptask(UNUSED struct SPTask *spTask) {} -void set_vblank_handler(UNUSED s32 index, UNUSED struct VblankHandler *handler, UNUSED OSMesgQueue *queue, UNUSED OSMesg *msg) {} - -void send_display_list(struct SPTask *spTask) { - if (!gGameInited) { return; } - gfx_run((Gfx *)spTask->task.t.data_ptr); -} - -#ifdef VERSION_EU -#define SAMPLES_HIGH 560 // gAudioBufferParameters.maxAiBufferLength -#define SAMPLES_LOW 528 // gAudioBufferParameters.minAiBufferLength -#else -#define SAMPLES_HIGH 544 -#define SAMPLES_LOW 528 -#endif - -extern void patch_mtx_before(void); -extern void patch_screen_transition_before(void); -extern void patch_title_screen_before(void); -extern void patch_dialog_before(void); -extern void patch_hud_before(void); -extern void patch_paintings_before(void); -extern void patch_bubble_particles_before(void); -extern void patch_snow_particles_before(void); -extern void patch_djui_before(void); -extern void patch_djui_hud_before(void); -extern void patch_scroll_targets_before(void); - -extern void patch_mtx_interpolated(f32 delta); -extern void patch_screen_transition_interpolated(f32 delta); -extern void patch_title_screen_interpolated(f32 delta); -extern void patch_dialog_interpolated(f32 delta); -extern void patch_hud_interpolated(f32 delta); -extern void patch_paintings_interpolated(f32 delta); -extern void patch_bubble_particles_interpolated(f32 delta); -extern void patch_snow_particles_interpolated(f32 delta); -extern void patch_djui_interpolated(f32 delta); -extern void patch_djui_hud(f32 delta); -extern void patch_scroll_targets_interpolated(f32 delta); - -static void patch_interpolations_before(void) { - patch_mtx_before(); - patch_screen_transition_before(); - patch_title_screen_before(); - patch_dialog_before(); - patch_hud_before(); - patch_paintings_before(); - patch_bubble_particles_before(); - patch_snow_particles_before(); - patch_djui_before(); - patch_djui_hud_before(); - patch_scroll_targets_before(); -} - -static inline void patch_interpolations(f32 delta) { - patch_mtx_interpolated(delta); - patch_screen_transition_interpolated(delta); - patch_title_screen_interpolated(delta); - patch_dialog_interpolated(delta); - patch_hud_interpolated(delta); - patch_paintings_interpolated(delta); - patch_bubble_particles_interpolated(delta); - patch_snow_particles_interpolated(delta); - patch_djui_interpolated(delta); - patch_djui_hud(delta); - patch_scroll_targets_interpolated(delta); -} - -static void compute_fps(f64 curTime) { - u32 fps = round((f64) sDrawnFrames / MAX(0.001, curTime - sFpsTimeLast)); - djui_fps_display_update(fps); - sFpsTimeLast = curTime; - sDrawnFrames = 0; -} - -static s32 get_num_frames_to_draw(f64 t) { - if (configFrameLimit % FRAMERATE == 0) { - return configFrameLimit / FRAMERATE; - } - s64 numFramesCurr = (s64) (t * (f64) configFrameLimit); - s64 numFramesNext = (s64) ((t + sFrameTime) * (f64) configFrameLimit); - return (s32) MAX(1, numFramesNext - numFramesCurr); -} - -void produce_interpolation_frames_and_delay(void) { - bool is30Fps = (!configUncappedFramerate && configFrameLimit == FRAMERATE); - - gRenderingInterpolated = true; - - f64 curTime = clock_elapsed_f64(); - f64 targetTime = sFrameTimeStart + sFrameTime; - s32 numFramesToDraw = get_num_frames_to_draw(sFrameTimeStart); - - f64 loopStartTime = curTime; - f64 expectedTime = 0; - - // interpolate and render - // make sure to draw at least one frame to prevent the game from freezing completely - // (including inputs and window events) if the game update duration is greater than 33ms - do { - f32 delta = ( - is30Fps ? - 1.0f : - MIN(MAX((curTime - sFrameTimeStart) / sFrameTime, 0.f), 1.f) - ); - gRenderingDelta = delta; - - gfx_start_frame(); - for (u8 i = 0; i < gNumPlayersLocal; i++) { - set_local_player(i); - if (!gSkipInterpolationTitleScreen) { patch_interpolations(delta); } - } - send_display_list(gGfxSPTask); - gfx_end_frame(); - - sDrawnFrames++; - - if (!is30Fps && configUncappedFramerate) { continue; } - - // delay if our framerate is capped - f64 now = clock_elapsed_f64(); - f64 elapsedTime = now - loopStartTime; - expectedTime += (targetTime - curTime) / (f64) numFramesToDraw; - f64 delay = (expectedTime - elapsedTime) * 1000.0; - if (delay > 0.0) { - WAPI.delay((u32)delay); - } - numFramesToDraw--; - } while ((curTime = clock_elapsed_f64()) < targetTime && numFramesToDraw > 0); - - // compute and update the frame rate every second - if ((curTime = clock_elapsed_f64()) >= sFpsTimeLast + 1.0) { - compute_fps(curTime); - } - - // advance frame start time - if (curTime > sFrameTimeStart + 2 * sFrameTime) { - sFrameTimeStart = curTime; - } else { - sFrameTimeStart += sFrameTime; - } - - gRenderingInterpolated = false; -} - -// It's just better to have this off the stack, Because the size isn't small. -// It also may help static analysis and bug catching. -static s16 sAudioBuffer[SAMPLES_HIGH * 2 * 2] = { 0 }; - -inline static void buffer_audio(void) { - bool shouldMute = (configMuteFocusLoss && !WAPI.has_focus()) || (gMasterVolume == 0); - if (!shouldMute) { - set_sequence_player_volume(SEQ_PLAYER_LEVEL, (f32)configMusicVolume / 127.0f * (f32)gLuaVolumeLevel / 127.0f); - set_sequence_player_volume(SEQ_PLAYER_SFX, (f32)configSfxVolume / 127.0f * (f32)gLuaVolumeSfx / 127.0f); - set_sequence_player_volume(SEQ_PLAYER_ENV, (f32)configEnvVolume / 127.0f * (f32)gLuaVolumeEnv / 127.0f); - } - - int samplesLeft = audio_api->buffered(); - u32 numAudioSamples = samplesLeft < audio_api->get_desired_buffered() ? SAMPLES_HIGH : SAMPLES_LOW; - for (s32 i = 0; i < 2; i++) { - create_next_audio_buffer(sAudioBuffer + i * (numAudioSamples * 2), numAudioSamples); - } - - if (!shouldMute) { - for (u16 i=0; i < ARRAY_COUNT(sAudioBuffer); i++) { - sAudioBuffer[i] *= gMasterVolume; - } - audio_api->play((u8 *)sAudioBuffer, 2 * numAudioSamples * 4); - } -} - -void *audio_thread(UNUSED void *arg) { - // As long as we have an audio api and that we're threaded, Loop. - while (audio_api) { - f64 curTime = clock_elapsed_f64(); - - // Buffer the audio. - lock_mutex(&gAudioThread); - buffer_audio(); - unlock_mutex(&gAudioThread); - - // Delay till the next frame for smooth audio at the correct speed. - // delay - f64 targetDelta = 1.0 / (f64)FRAMERATE; - f64 now = clock_elapsed_f64(); - f64 actualDelta = now - curTime; - if (actualDelta < targetDelta) { - f64 delay = ((targetDelta - actualDelta) * 1000.0); - WAPI.delay((u32)delay); - } - } - - // Exit the thread if our loop breaks. - exit_thread(); - - return NULL; -} - -void produce_one_frame(void) { - CTX_EXTENT(CTX_NETWORK, network_update); - - CTX_EXTENT(CTX_INTERP, patch_interpolations_before); - - CTX_EXTENT(CTX_GAME_LOOP, game_loop_one_iteration); - - CTX_EXTENT(CTX_SMLUA, smlua_update); - - // If we aren't threaded - if (gAudioThread.state == INVALID) { - CTX_EXTENT(CTX_AUDIO, buffer_audio); - } - - CTX_EXTENT(CTX_RENDER, produce_interpolation_frames_and_delay); -} - -// used for rendering 2D scenes fullscreen like the loading or crash screens -void produce_one_dummy_frame(void (*callback)(), u8 clearColorR, u8 clearColorG, u8 clearColorB) { - // start frame - gfx_start_frame(); - config_gfx_pool(); - init_render_image(); - create_dl_ortho_matrix(); - djui_gfx_displaylist_begin(); - - // fix scaling issues - gSPViewport(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(&D_8032CF00)); - gDPSetScissor(gDisplayListHead++, G_SC_NON_INTERLACE, 0, BORDER_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT - BORDER_HEIGHT); - - // clear screen - create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(0), 240.f, 0.f); - create_dl_scale_matrix(MENU_MTX_NOPUSH, (GFX_DIMENSIONS_ASPECT_RATIO * SCREEN_HEIGHT) / 130.f, 3.f, 1.f); - gDPSetEnvColor(gDisplayListHead++, clearColorR, clearColorG, clearColorB, 0xFF); - gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box); - gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); - - // call the callback - callback(); - - // render frame - djui_gfx_displaylist_end(); - end_master_display_list(); - alloc_display_list(0); - gfx_run((Gfx*) gGfxSPTask->task.t.data_ptr); // send_display_list - display_and_vsync(); - gfx_end_frame(); -} - -void audio_shutdown(void) { - audio_custom_shutdown(); - if (audio_api) { - if (audio_api->shutdown) audio_api->shutdown(); - audio_api = NULL; - } -} - -void game_deinit(void) { - if (gGameInited) { configfile_save(configfile_name()); } - controller_shutdown(); - audio_custom_shutdown(); - audio_shutdown(); - network_shutdown(true, true, false, false); - smlua_text_utils_shutdown(); - smlua_shutdown(); - smlua_audio_custom_deinit(); - mods_shutdown(); - djui_shutdown(); - gfx_shutdown(); - gGameInited = false; -} - -void game_exit(void) { - LOG_INFO("exiting cleanly"); - game_deinit(); - exit(0); -} - -void* main_game_init(UNUSED void* dummy) { - // load language - if (!djui_language_init(configLanguage)) { snprintf(configLanguage, MAX_CONFIG_STRING, "%s", ""); } - - LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Loading")); - dynos_gfx_init(); - enable_queued_dynos_packs(); - sync_objects_init_system(); - - if (gCLIOpts.network != NT_SERVER && !gCLIOpts.skipUpdateCheck) { - check_for_updates(); - } - - LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Loading ROM Assets")); - rom_assets_load(); - smlua_text_utils_init(); - - mods_init(); - enable_queued_mods(); - LOADING_SCREEN_MUTEX( - gCurrLoadingSegment.percentage = 0; - loading_screen_set_segment_text("Starting Game"); - ); - - audio_init(); - sound_init(); - network_player_init(); - mumble_init(); - - gGameInited = true; -} - int main(int argc, char *argv[]) { // handle terminal arguments if (!parse_cli_opts(argc, argv)) { return 0; } @@ -575,3 +229,5 @@ int main(int argc, char *argv[]) { return 0; } + +#endif // __CONSOLE__ \ No newline at end of file diff --git a/src/pc/pc_main.h b/src/pc/pc_main.h index 384bcc8dc..2c81129bd 100644 --- a/src/pc/pc_main.h +++ b/src/pc/pc_main.h @@ -1,3 +1,5 @@ +#ifndef __CONSOLE__ + #ifndef _PC_MAIN_H #define _PC_MAIN_H @@ -5,6 +7,8 @@ extern "C" { #endif +#include "pc/game_main.h" + #include "gfx/gfx_pc.h" #include "gfx/gfx_opengl.h" @@ -12,8 +16,6 @@ extern "C" { #include "gfx/gfx_dxgi.h" #include "gfx/gfx_sdl.h" -#include "gfx/gfx_dummy.h" - #if defined(WAPI_SDL1) || defined(WAPI_SDL2) # define WAPI gfx_sdl #elif defined(WAPI_DXGI) @@ -53,31 +55,10 @@ extern "C" { #define RAPI_NAME "Dummy" #endif -#ifdef GIT_HASH -#define TITLE ({ char title[96] = ""; snprintf(title, 96, "%s %s, [%s]", WINDOW_NAME, get_version(), GIT_HASH); title; }) -#else -#define TITLE ({ char title[96] = ""; snprintf(title, 96, "%s %s", WINDOW_NAME, get_version()); title; }) -#endif - -#define AT_STARTUP __attribute__((constructor)) - -extern bool gGameInited; -extern bool gGfxInited; - -extern f32 gMasterVolume; - -extern u8 gLuaVolumeMaster; -extern u8 gLuaVolumeLevel; -extern u8 gLuaVolumeSfx; -extern u8 gLuaVolumeEnv; - -extern struct GfxWindowManagerAPI* wm_api; -void produce_one_dummy_frame(void (*callback)(), u8 clearColorR, u8 clearColorG, u8 clearColorB); -void game_deinit(void); -void game_exit(void); - #ifdef __cplusplus } #endif #endif // _PC_MAIN_H + +#endif // __CONSOLE__ \ No newline at end of file diff --git a/src/pc/platform.c b/src/pc/platform.c index 331a7c075..785513a32 100644 --- a/src/pc/platform.c +++ b/src/pc/platform.c @@ -330,8 +330,7 @@ const char *sys_user_path(void) { return path; } -const char *sys_resource_path(void) -{ +const char *sys_resource_path(void) { #ifdef __APPLE__ // Kinda lazy, but I don't know how to add CoreFoundation.framework static char path[SYS_MAX_PATH]; if ('\0' != path[0]) { return path; } @@ -344,9 +343,14 @@ const char *sys_resource_path(void) strncpy(path, exeDir, count); return strncat(path, folder, sizeof(path) - 1 - count); } -#endif - + return sys_exe_path_dir(); +#elif defined(__SWITCH__) + static const char path[] = "sdmc:/switch/sm64coopdx"; + return path; +#else + return sys_exe_path_dir(); +#endif } const char *sys_exe_path_dir(void) { @@ -370,7 +374,8 @@ const char *sys_exe_path_file(void) { #if defined(__APPLE__) uint32_t bufsize = SYS_MAX_PATH; int res = _NSGetExecutablePath(path, &bufsize); - +#elif defined(__SWITCH__) + int res = -1; #else char procPath[SYS_MAX_PATH]; snprintf(procPath, SYS_MAX_PATH, "/proc/%d/exe", getpid()); diff --git a/src/pc/rom_checker.cpp b/src/pc/rom_checker.cpp index 2024dd0de..da8313092 100644 --- a/src/pc/rom_checker.cpp +++ b/src/pc/rom_checker.cpp @@ -95,7 +95,9 @@ void legacy_folder_handler(void) { bool main_rom_handler(void) { if (scan_path_for_rom(fs_get_write_path(""))) { return true; } +#if !defined(__SWITCH__) scan_path_for_rom(sys_exe_path_dir()); +#endif return gRomIsValid; } diff --git a/src/pc/string_utils.c b/src/pc/string_utils.c new file mode 100644 index 000000000..41a038275 --- /dev/null +++ b/src/pc/string_utils.c @@ -0,0 +1,15 @@ +#include "string_utils.h" +#include +#include + +#ifdef __SWITCH__ + +char *strdup(const char *str) { + size_t size = strlen(str); + char *nstr = malloc((size + 1) * sizeof(char)); + strcpy(nstr, str); + nstr[size] = 0; + return nstr; +} + +#endif \ No newline at end of file diff --git a/src/pc/string_utils.h b/src/pc/string_utils.h new file mode 100644 index 000000000..b82b0be30 --- /dev/null +++ b/src/pc/string_utils.h @@ -0,0 +1,18 @@ +#ifndef _STRING_UTILS_H +#define _STRING_UTILS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sm64.h" + +#ifdef __SWITCH__ +char *strdup(const char *str); +#endif + +#ifdef __cplusplus +} +#endif + +#endif // _STRING_UTILS_H \ No newline at end of file diff --git a/src/pc/ultra_reimplementation.c b/src/pc/ultra_reimplementation.c index 230f7e747..4e1b40e62 100644 --- a/src/pc/ultra_reimplementation.c +++ b/src/pc/ultra_reimplementation.c @@ -5,6 +5,10 @@ #include "platform.h" #include "fs/fs.h" +#ifdef __SWITCH__ +#include "nx_utils.h" +#endif + u8* gOverrideEeprom = NULL; extern OSMgrArgs piMgrArgs; @@ -132,7 +136,10 @@ s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) u8 content[512]; s32 ret = -1; - + +#if defined(__SWITCH__) && !defined(BUILD_NRO) + mount_save_data(); +#endif fs_file_t *fp = fs_open(SAVE_FILENAME); if (fp == NULL) { return -1; @@ -142,6 +149,9 @@ s32 osEepromLongRead(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes) ret = 0; } fs_close(fp); +#if defined(__SWITCH__) && !defined(BUILD_NRO) + unmount_save_data(); +#endif return ret; } @@ -158,12 +168,19 @@ s32 osEepromLongWrite(UNUSED OSMesgQueue *mq, u8 address, u8 *buffer, int nbytes } memcpy(content + address * 8, buffer, nbytes); +#if defined(__SWITCH__) && !defined(BUILD_NRO) + mount_save_data(); +#endif FILE *fp = fopen(fs_get_write_path(SAVE_FILENAME), "wb"); if (fp == NULL) { return -1; } s32 ret = fwrite(content, 1, 512, fp) == 512 ? 0 : -1; fclose(fp); +#if defined(__SWITCH__) && !defined(BUILD_NRO) + commit_save(); + unmount_save_data(); +#endif return ret; } diff --git a/src/pc/update_checker.c b/src/pc/update_checker.c index e83414106..dc973a1ca 100644 --- a/src/pc/update_checker.c +++ b/src/pc/update_checker.c @@ -128,7 +128,7 @@ void get_version_remote(void) { void check_for_updates(void) { LOADING_SCREEN_MUTEX(loading_screen_set_segment_text("Checking For Updates")); - + get_version_remote(); if (sRemoteVersion[0] != '\0' && strcmp(sRemoteVersion, get_version())) { snprintf( diff --git a/src/pc/utils/miniaudio.h b/src/pc/utils/miniaudio.h index a928c29fd..18e17780e 100644 --- a/src/pc/utils/miniaudio.h +++ b/src/pc/utils/miniaudio.h @@ -16106,7 +16106,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority pAttr = &attr; /* We need to set the scheduler policy. Only do this if the OS supports pthread_attr_setschedpolicy() */ - #if !defined(MA_BEOS) + #if !defined(MA_BEOS) && defined(_POSIX_THREAD_PRIORITY_SCHEDULING) { if (priority == ma_thread_priority_idle) { #ifdef SCHED_IDLE @@ -16131,7 +16131,8 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority if (stackSize > 0) { pthread_attr_setstacksize(&attr, stackSize); } - + + #if defined(_POSIX_THREAD_PRIORITY_SCHEDULING) if (scheduler != -1) { int priorityMin = sched_get_priority_min(scheduler); int priorityMax = sched_get_priority_max(scheduler); @@ -16157,6 +16158,7 @@ static ma_result ma_thread_create__posix(ma_thread* pThread, ma_thread_priority pthread_attr_setschedparam(&attr, &sched); } } + #endif } #else /* It's the emscripten build. We'll have a few unused parameters. */ @@ -28112,6 +28114,13 @@ static ma_result ma_device_stop__alsa(ma_device* pDevice) static ma_result ma_device_wait__alsa(ma_device* pDevice, ma_snd_pcm_t* pPCM, struct pollfd* pPollDescriptors, int pollDescriptorCount, short requiredEvent) { +#ifdef __SWITCH__ + if (pollDescriptorCount >= RLIMIT_NOFILE) { + ma_log_post(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[ALSA] poll() failed. Too many poll descriptors.\n"); + return MA_INVALID_ARGS; + } +#endif + for (;;) { unsigned short revents; int resultALSA; diff --git a/src/pc/utils/miniaudio_api.c b/src/pc/utils/miniaudio_api.c new file mode 100644 index 000000000..b3c4766d7 --- /dev/null +++ b/src/pc/utils/miniaudio_api.c @@ -0,0 +1,119 @@ +#define MINIAUDIO_IMPLEMENTATION // required by miniaudio +#define MA_NO_SDL // We don't use this because it conflicts with audio_sdl2. + +#include "types.h" +#include "pc/utils/miniaudio_api.h" +#include "pc/utils/miniaudio_nx.inl" +#include "pc/utils/miniaudio_sdl2.inl" + +typedef struct { + union { + ma_context context; + ma_context_sdl sdl_context; + }; +} ma_context_ex; + +typedef struct { + union { + ma_device device; + ma_device_sdl sdl_device; + }; +} ma_device_ex; + +void data_callback(ma_device* pDevice, void* pOutput, const void* pInput, ma_uint32 frameCount) +{ + if (pDevice->type == ma_device_type_playback) { + ma_waveform_read_pcm_frames((ma_waveform*)pDevice->pUserData, pOutput, frameCount, NULL); + } + + if (pDevice->type == ma_device_type_duplex) { + ma_copy_pcm_frames(pOutput, pInput, frameCount, pDevice->playback.format, pDevice->playback.channels); + } +} + +/* +This is our custom backend "loader". All this does is attempts to initialize our custom backends in the order they are listed. The first +one to successfully initialize is the one that's chosen. In this example we're just listing them statically, but you can use whatever logic +you want to handle backend selection. + +This is used as the onContextInit() callback in the context config. +*/ +static ma_result ma_context_init__custom_loader(ma_context *pContext, const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks) { + ma_result result = MA_NO_BACKEND; + + /* Silence some unused parameter warnings just in case no custom backends are enabled. */ + (void)pContext; + (void)pCallbacks; + + /* NX. */ +#if !defined(MA_NO_NX) + if (result != MA_SUCCESS) { + result = ma_context_init__nx(pContext, pConfig, pCallbacks); + } +#endif + + /* SDL. */ +#if !defined(MA_NO_SDL) + if (result != MA_SUCCESS) { + result = ma_context_init__sdl(pContext, pConfig, pCallbacks); + } +#endif + + /* If we have a success result we have initialized a backend. Otherwise we need to tell miniaudio about the error so it can skip over our custom backends. */ + return result; +} + +ma_context_ex global_context = { 0 }; +ma_backend global_backends[] = { + ma_backend_wasapi, + ma_backend_dsound, + ma_backend_winmm, + ma_backend_coreaudio, + ma_backend_sndio, + ma_backend_audio4, + ma_backend_oss, + ma_backend_pulseaudio, + ma_backend_alsa, + ma_backend_jack, + ma_backend_aaudio, + ma_backend_opensl, + ma_backend_webaudio, + ma_backend_custom, + ma_backend_null +}; + +ma_result ma_initalize(const ma_engine_config *pConfig, ma_engine *pEngine) { + ma_result result = MA_SUCCESS; + ma_engine_config engineConfig; + + /* The config is allowed to be NULL in which case we use defaults for everything. */ + if (pConfig != NULL) { + engineConfig = *pConfig; + } else { + engineConfig = ma_engine_config_init(); + } + engineConfig.pContext = (ma_context *)&global_context; + + ma_context_config contextConfig = ma_context_config_init(); + contextConfig.custom.onContextInit = ma_context_init__custom_loader; + + result = ma_context_init(global_backends, sizeof(global_backends) / sizeof(global_backends[0]), &contextConfig, (ma_context *)&global_context); + if (result != MA_SUCCESS) { return result; } + + result = ma_engine_init(&engineConfig, pEngine); + return result; +} + +ma_result ma_uninitalize(ma_engine *pEngine) { + ma_engine_uninit(pEngine); + ma_context_uninit((ma_context *)&global_context); +} + +ma_result ma_sound_from_buffer(ma_engine *engine, ma_sound *sound, ma_decoder *decoder, void *buffer, u32 size, bool stream) { + // Decode the audio buffer. + ma_result result = ma_decoder_init_memory(buffer, size, NULL, decoder); + if (result != MA_SUCCESS) { return result; } + + result = ma_sound_init_from_data_source(engine, decoder, stream ? MA_SOUND_STREAM_FLAGS : MA_SOUND_SAMPLE_FLAGS, NULL, sound); + return result; +} \ No newline at end of file diff --git a/src/pc/utils/miniaudio_api.h b/src/pc/utils/miniaudio_api.h new file mode 100644 index 000000000..1248ed872 --- /dev/null +++ b/src/pc/utils/miniaudio_api.h @@ -0,0 +1,20 @@ +#ifndef miniaudio_api_h +#define miniaudio_api_h + +#ifdef __SWITCH__ +#define MA_NO_RUNTIME_LINKING 1 +#define MA_DEBUG_OUTPUT 1 +#endif + +#include "pc/utils/miniaudio.h" + +// Optimization: disable spatialization for everything as it's not used +#define MA_SOUND_STREAM_FLAGS (MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_STREAM) +#define MA_SOUND_SAMPLE_FLAGS (MA_SOUND_FLAG_NO_SPATIALIZATION | MA_SOUND_FLAG_NO_PITCH | MA_SOUND_FLAG_DECODE) // No pitch, pre-decode audio samples + +ma_result ma_initalize(const ma_engine_config *pConfig, ma_engine *pEngine); +ma_result ma_uninitalize(ma_engine *pEngine); + +ma_result ma_sound_from_buffer(ma_engine *engine, ma_sound *sound, ma_decoder *decoder, void *buffer, u32 size, bool stream); + +#endif // miniaudio_api_h \ No newline at end of file diff --git a/src/pc/utils/miniaudio_conf.h b/src/pc/utils/miniaudio_conf.h new file mode 100644 index 000000000..f8b739316 --- /dev/null +++ b/src/pc/utils/miniaudio_conf.h @@ -0,0 +1,9 @@ +#ifndef miniaudio_conf_h +#define miniaudio_conf_h + +#ifdef __SWITCH__ +#define MA_NO_RUNTIME_LINKING 1 +#define MA_DEBUG_OUTPUT 1 +#endif + +#endif // miniaudio_conf_h \ No newline at end of file diff --git a/src/pc/utils/miniaudio_nx.inl b/src/pc/utils/miniaudio_nx.inl new file mode 100644 index 000000000..b7597e764 --- /dev/null +++ b/src/pc/utils/miniaudio_nx.inl @@ -0,0 +1,199 @@ +#ifndef miniaudio_nx_inl +#define miniaudio_nx_inl + +#include +#include + +/* Only enable MA_SUPPORT_NX on Nintendo Switch */ +#ifdef __SWITCH__ +#define MA_SUPPORT_NX +#endif + +/* +Only enable NX if it's hasn't been explicitly disabled (MA_NO_NX) or enabled (MA_ENABLE_NX with +MA_ENABLE_ONLY_SPECIFIC_BACKENDS) and it's supported at compile time (MA_SUPPORT_NX). +*/ +#if defined(MA_SUPPORT_NX) && !defined(MA_NO_NX) && (!defined(MA_ENABLE_ONLY_SPECIFIC_BACKENDS) || defined(MA_ENABLE_NX)) + #define MA_HAS_NX +#endif + +#if defined(MA_HAS_NX) + +ma_result ma_context_init__nx(ma_context *pContext, const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks); +ma_result ma_context_uninit__nx(ma_context *pContext); +ma_result ma_device_init__nx(ma_device *pDevice, const ma_device_config *pConfig, ma_device_descriptor *pDescriptorPlayback, ma_device_descriptor *pDescriptorCapture); +ma_result ma_device_uninit__nx(ma_device *pDevice); +ma_result ma_device_start__nx(ma_device *pDevice); +ma_result ma_device_stop__nx(ma_device *pDevice); + +ma_result ma_context_enumerate_devices__nx(ma_context *pContext, ma_enum_devices_callback_proc callback, void *pUserData); +ma_result ma_context_get_device_info__nx(ma_context *pContext, ma_device_type deviceType, const ma_device_id *pDeviceID, ma_device_info *pDeviceInfo); + +#endif // MA_HAS_NX + +#if defined(MA_HAS_NX) + +#include + +AudioOutBuffer audout_buf = { 0 }; +AudioOutBuffer *released_out_buffer = NULL; +u32 released_out_count = 0; + +PcmFormat ma_format_to_nx(ma_format format) { + switch (format) + { + case ma_format_u8: return PcmFormat_Int8; + case ma_format_s16: return PcmFormat_Int16; + case ma_format_s24: return PcmFormat_Int24; + case ma_format_s32: return PcmFormat_Int32; + case ma_format_f32: return PcmFormat_Float; + default: return PcmFormat_Invalid; + } +} + +ma_format ma_format_from_nx(PcmFormat format) { + switch (format) + { + case PcmFormat_Int8: return ma_format_u8; + case PcmFormat_Int16: return ma_format_s16; + case PcmFormat_Int24: return ma_format_s24; + case PcmFormat_Int32: return ma_format_s32; + case PcmFormat_Float: return ma_format_f32; + default: return ma_format_unknown; + } +} + +ma_result ma_context_enumerate_devices__nx(UNUSED ma_context *pContext, UNUSED ma_enum_devices_callback_proc callback, UNUSED void *pUserData) { + return MA_SUCCESS; +} + +ma_result ma_context_get_device_info__nx(UNUSED ma_context *pContext, UNUSED ma_device_type deviceType, UNUSED const ma_device_id *pDeviceID, ma_device_info *pDeviceInfo) { + pDeviceInfo->id.custom.i = 0; + pDeviceInfo->isDefault = MA_TRUE; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + + pDeviceInfo->nativeDataFormatCount = 1; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].channels = 2; + pDeviceInfo->nativeDataFormats[0].sampleRate = 48000; + pDeviceInfo->nativeDataFormats[0].flags = 0; + + /* If miniaudio does not support the format, just use f32 as the native format (SDL will do the necessary conversions for us). */ + if (pDeviceInfo->nativeDataFormats[0].format == ma_format_unknown) { + pDeviceInfo->nativeDataFormats[0].format = ma_format_f32; + } + + return MA_SUCCESS; +} + +ma_result ma_device_init__nx(ma_device *pDevice, const ma_device_config *pConfig, UNUSED ma_device_descriptor *pDescriptorPlayback, UNUSED ma_device_descriptor *pDescriptorCapture) { + /* NX does not support loopback mode, so must return MA_DEVICE_TYPE_NOT_SUPPORTED if it's requested. */ + if (pConfig->deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + // Initialize the default audio output device. + Result rc = audoutInitialize(); + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] audoutInitialize() returned 0x%x", rc); + return MA_ERROR; + } + + // Make sure the sample buffer size is aligned to 0x1000 bytes. + u32 data_size = ((audoutGetSampleRate() / 30) * audoutGetChannelCount() * 2); + u32 buffer_size = (data_size + 0xfff) & ~0xfff; + + // Allocate the buffers. + u8 *out_buf_data = memalign(0x1000, buffer_size); + + // Ensure buffers were properly allocated. + if (out_buf_data == NULL) { + rc = MAKERESULT(Module_Libnx, LibnxError_OutOfMemory); + } + + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] ma_device_init__nx() failed to allocate buffer with error 0x%x", rc); + audoutExit(); + free(out_buf_data); + return MA_ERROR; + } + + memset(out_buf_data, 0, buffer_size); + + // Prepare the output buffer. + audout_buf.next = NULL; + audout_buf.buffer = out_buf_data; + audout_buf.buffer_size = buffer_size; + audout_buf.data_size = data_size; + audout_buf.data_offset = 0; + + // Prepare pointers and counters for released buffers. + released_out_buffer = NULL; + released_out_count = 0; + + // Append the initial output buffer. + rc = audoutAppendAudioOutBuffer(&audout_buf); + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] audoutAppendAudioOutBuffer() returned 0x%x", rc); + audoutExit(); + free(out_buf_data); + return MA_ERROR; + } + + return MA_SUCCESS; +} + +ma_result ma_device_uninit__nx(UNUSED ma_device *pDevice) { + audoutExit(); + + if (audout_buf.buffer) { free(audout_buf.buffer); } + memset(&audout_buf, 0, sizeof(audout_buf)); + released_out_buffer = NULL; + released_out_count = 0; + return MA_SUCCESS; +} + +ma_result ma_device_start__nx(ma_device *pDevice) { + Result rc = audoutStartAudioOut(); + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] audoutStartAudioOut() returned 0x%x", rc); + return MA_ERROR; + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__nx(ma_device *pDevice) { + Result rc = audoutStopAudioOut(); + if (!R_SUCCEEDED(rc)) { + ma_log_postf(ma_device_get_log(pDevice), MA_LOG_LEVEL_ERROR, "[NX] audoutStopAudioOut() returned 0x%x", rc); + return MA_ERROR; + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__nx(UNUSED ma_context *pContext) { + return MA_SUCCESS; +} + +ma_result ma_context_init__nx(UNUSED ma_context *pContext, UNUSED const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks) { + /* + The last step is to make sure the callbacks are set properly in pCallbacks. Internally, miniaudio will copy these callbacks into the + context object and then use them for then on for calling into our custom backend. + */ + pCallbacks->onContextInit = ma_context_init__nx; + pCallbacks->onContextUninit = ma_context_uninit__nx; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__nx; + pCallbacks->onContextGetDeviceInfo = ma_context_get_device_info__nx; + pCallbacks->onDeviceInit = ma_device_init__nx; + pCallbacks->onDeviceUninit = ma_device_uninit__nx; + pCallbacks->onDeviceStart = ma_device_start__nx; + pCallbacks->onDeviceStop = ma_device_stop__nx; + + return MA_SUCCESS; +} + +#endif // MA_HAS_NX + +#endif // miniaudio_nx_inl \ No newline at end of file diff --git a/src/pc/utils/miniaudio_sdl2.inl b/src/pc/utils/miniaudio_sdl2.inl new file mode 100644 index 000000000..c6602a153 --- /dev/null +++ b/src/pc/utils/miniaudio_sdl2.inl @@ -0,0 +1,535 @@ +#ifndef miniaudio_sdl_inl +#define miniaudio_sdl_inl + +/* Support SDL on everything. */ +#define MA_SUPPORT_SDL + +/* +Only enable SDL if it's hasn't been explicitly disabled (MA_NO_SDL) or enabled (MA_ENABLE_SDL with +MA_ENABLE_ONLY_SPECIFIC_BACKENDS) and it's supported at compile time (MA_SUPPORT_SDL). +*/ +#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_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 + +typedef struct { + ma_context context; /* Make this the first member so we can cast between ma_context and ma_context_sdl. */ +#if defined(MA_SUPPORT_SDL) + struct { + ma_handle hSDL; /* A handle to the SDL2 shared object. We dynamically load function pointers at runtime so we can avoid linking. */ + 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 +} ma_context_sdl; + +typedef struct { + ma_device device; /* Make this the first member so we can cast between ma_device and ma_device_sdl. */ +#if defined(MA_SUPPORT_SDL) + struct { + int deviceIDPlayback; + int deviceIDCapture; + } sdl; +#endif +} ma_device_sdl; + +#if defined(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) + +ma_result ma_context_init__sdl(ma_context *pContext, const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks); +ma_result ma_context_uninit__sdl(ma_context *pContext); +ma_result ma_device_init__sdl(ma_device *pDevice, const ma_device_config *pConfig, ma_device_descriptor *pDescriptorPlayback, ma_device_descriptor *pDescriptorCapture); +ma_result ma_device_uninit__sdl(ma_device *pDevice); +ma_result ma_device_start__sdl(ma_device *pDevice); +ma_result ma_device_stop__sdl(ma_device *pDevice); + +ma_result ma_context_enumerate_devices__sdl(ma_context *pContext, ma_enum_devices_callback_proc callback, void *pUserData); +ma_result ma_context_get_device_info__sdl(ma_context *pContext, ma_device_type deviceType, const ma_device_id *pDeviceID, ma_device_info *pDeviceInfo); + +#endif // MA_HAS_SDL + +#if defined(MA_HAS_SDL) + +/* If we are linking at compile time we'll just #include SDL.h. Otherwise we can just redeclare some stuff to avoid the need for development packages to be installed. */ +#ifdef MA_NO_RUNTIME_LINKING + #define SDL_MAIN_HANDLED + #ifdef MA_EMSCRIPTEN + #include + #else + #include + #endif + + typedef SDL_AudioCallback MA_SDL_AudioCallback; + typedef SDL_AudioSpec MA_SDL_AudioSpec; + typedef SDL_AudioFormat MA_SDL_AudioFormat; + typedef SDL_AudioDeviceID MA_SDL_AudioDeviceID; +#else + typedef void (* MA_SDL_AudioCallback)(void* userdata, ma_uint8* stream, int len); + typedef ma_uint16 MA_SDL_AudioFormat; + typedef ma_uint32 MA_SDL_AudioDeviceID; + + typedef struct MA_SDL_AudioSpec + { + int freq; + MA_SDL_AudioFormat format; + ma_uint8 channels; + ma_uint8 silence; + ma_uint16 samples; + ma_uint16 padding; + ma_uint32 size; + MA_SDL_AudioCallback callback; + void* userdata; + } MA_SDL_AudioSpec; +#endif + +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)(MA_SDL_AudioDeviceID dev); +typedef MA_SDL_AudioDeviceID (* MA_PFN_SDL_OpenAudioDevice)(const char* device, int iscapture, const MA_SDL_AudioSpec* desired, MA_SDL_AudioSpec* obtained, int allowed_changes); +typedef void (* MA_PFN_SDL_PauseAudioDevice)(MA_SDL_AudioDeviceID dev, int pause_on); + +MA_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; + } +} + +ma_format ma_format_from_sdl(MA_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; + } +} + +ma_result ma_context_enumerate_devices__sdl(ma_context *pContext, ma_enum_devices_callback_proc callback, void* pUserData) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pContext; + ma_bool32 isTerminated = MA_FALSE; + + /* Playback */ + if (!isTerminated) { + int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextEx->sdl.SDL_GetNumAudioDevices)(0); + for (int iDevice = 0; iDevice < deviceCount; ++iDevice) { + ma_device_info deviceInfo; + MA_ZERO_OBJECT(&deviceInfo); + + deviceInfo.id.custom.i = iDevice; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(iDevice, 0), (size_t)-1); + + if (iDevice == 0) { + deviceInfo.isDefault = MA_TRUE; + } + + ma_bool32 cbResult = callback(pContext, ma_device_type_playback, &deviceInfo, pUserData); + if (cbResult == MA_FALSE) { + isTerminated = MA_TRUE; + break; + } + } + } + + /* Capture */ + if (!isTerminated) { + int deviceCount = ((MA_PFN_SDL_GetNumAudioDevices)pContextEx->sdl.SDL_GetNumAudioDevices)(1); + for (int iDevice = 0; iDevice < deviceCount; ++iDevice) { + ma_device_info deviceInfo; + MA_ZERO_OBJECT(&deviceInfo); + + deviceInfo.id.custom.i = iDevice; + ma_strncpy_s(deviceInfo.name, sizeof(deviceInfo.name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(iDevice, 1), (size_t)-1); + + if (iDevice == 0) { + deviceInfo.isDefault = MA_TRUE; + } + + ma_bool32 cbResult = callback(pContext, ma_device_type_capture, &deviceInfo, pUserData); + if (cbResult == MA_FALSE) { + isTerminated = MA_TRUE; + break; + } + } + } + + return MA_SUCCESS; +} + +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_context_sdl *pContextEx = (ma_context_sdl *)pContext; + +#if !defined(__EMSCRIPTEN__) + MA_SDL_AudioSpec desiredSpec; + MA_SDL_AudioSpec obtainedSpec; + MA_SDL_AudioDeviceID tempDeviceID; + const char* pDeviceName; +#endif + + if (pDeviceID == NULL) { + if (deviceType == ma_device_type_playback) { + pDeviceInfo->id.custom.i = 0; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_PLAYBACK_DEVICE_NAME, (size_t)-1); + } else { + pDeviceInfo->id.custom.i = 0; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), MA_DEFAULT_CAPTURE_DEVICE_NAME, (size_t)-1); + } + } else { + pDeviceInfo->id.custom.i = pDeviceID->custom.i; + ma_strncpy_s(pDeviceInfo->name, sizeof(pDeviceInfo->name), ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1), (size_t)-1); + } + + if (pDeviceInfo->id.custom.i == 0) { + pDeviceInfo->isDefault = MA_TRUE; + } + + /* + To get an accurate idea on the backend's native format we need to open the device. Not ideal, but it's the only way. An + alternative to this is to report all channel counts, sample rates and formats, but that doesn't offer a good representation + of the device's _actual_ ideal format. + + Note: With Emscripten, it looks like non-zero values need to be specified for desiredSpec. Whatever is specified in + desiredSpec will be used by SDL since it uses it just does its own format conversion internally. Therefore, from what + I can tell, there's no real way to know the device's actual format which means I'm just going to fall back to the full + range of channels and sample rates on Emscripten builds. + */ +#if defined(__EMSCRIPTEN__) + /* Good practice to prioritize the best format first so that the application can use the first data format as their chosen one if desired. */ + pDeviceInfo->nativeDataFormatCount = 3; + pDeviceInfo->nativeDataFormats[0].format = ma_format_s16; + pDeviceInfo->nativeDataFormats[0].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[0].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[0].flags = 0; + pDeviceInfo->nativeDataFormats[1].format = ma_format_s32; + pDeviceInfo->nativeDataFormats[1].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[1].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[1].flags = 0; + pDeviceInfo->nativeDataFormats[2].format = ma_format_u8; + pDeviceInfo->nativeDataFormats[2].channels = 0; /* All channel counts supported. */ + pDeviceInfo->nativeDataFormats[2].sampleRate = 0; /* All sample rates supported. */ + pDeviceInfo->nativeDataFormats[2].flags = 0; +#else + MA_ZERO_MEMORY(&desiredSpec, sizeof(desiredSpec)); + + pDeviceName = NULL; + if (pDeviceID != NULL) { + pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDeviceID->custom.i, (deviceType == ma_device_type_playback) ? 0 : 1); + } + + tempDeviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextEx->sdl.SDL_OpenAudioDevice)(pDeviceName, (deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); + if (tempDeviceID == 0) { + ma_log_postf(ma_context_get_log(pContext), MA_LOG_LEVEL_ERROR, "Failed to open SDL device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(tempDeviceID); + + /* Only reporting a single native data format. It'll be whatever SDL decides is the best. */ + pDeviceInfo->nativeDataFormatCount = 1; + pDeviceInfo->nativeDataFormats[0].format = ma_format_from_sdl(obtainedSpec.format); + pDeviceInfo->nativeDataFormats[0].channels = obtainedSpec.channels; + pDeviceInfo->nativeDataFormats[0].sampleRate = obtainedSpec.freq; + pDeviceInfo->nativeDataFormats[0].flags = 0; + + /* If miniaudio does not support the format, just use f32 as the native format (SDL will do the necessary conversions for us). */ + if (pDeviceInfo->nativeDataFormats[0].format == ma_format_unknown) { + pDeviceInfo->nativeDataFormats[0].format = ma_format_f32; + } +#endif /* __EMSCRIPTEN__ */ + + return MA_SUCCESS; +} + +void ma_audio_callback_capture__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pUserData; + ma_device_handle_backend_data_callback((ma_device *)pDeviceEx, NULL, pBuffer, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceEx->device.capture.internalFormat, pDeviceEx->device.capture.internalChannels)); +} + +void ma_audio_callback_playback__sdl(void* pUserData, ma_uint8* pBuffer, int bufferSizeInBytes) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pUserData; + ma_device_handle_backend_data_callback((ma_device *)pDeviceEx, pBuffer, NULL, (ma_uint32)bufferSizeInBytes / ma_get_bytes_per_frame(pDeviceEx->device.playback.internalFormat, pDeviceEx->device.playback.internalChannels)); +} + +static ma_result ma_device_init_internal__sdl(ma_device_sdl *pDeviceEx, const ma_device_config *pConfig, ma_device_descriptor *pDescriptor) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pDeviceEx->device.pContext; + + /* + SDL is a little bit awkward with specifying the buffer size, You need to specify the size of the buffer in frames, but since we may + have requested a period size in milliseconds we'll need to convert, which depends on the sample rate. But there's a possibility that + the sample rate just set to 0, which indicates that the native sample rate should be used. There's no practical way to calculate this + that I can think of right now so I'm just using MA_DEFAULT_SAMPLE_RATE. + */ + if (pDescriptor->sampleRate == 0) { + pDescriptor->sampleRate = MA_DEFAULT_SAMPLE_RATE; + } + + /* + When determining the period size, you need to take defaults into account. This is how the size of the period should be determined. + + 1) If periodSizeInFrames is not 0, use periodSizeInFrames; else + 2) If periodSizeInMilliseconds is not 0, use periodSizeInMilliseconds; else + 3) If both periodSizeInFrames and periodSizeInMilliseconds is 0, use the backend's default. If the backend does not allow a default + buffer size, use a default value of MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_LOW_LATENCY or + MA_DEFAULT_PERIOD_SIZE_IN_MILLISECONDS_CONSERVATIVE depending on the value of pConfig->performanceProfile. + + Note that options 2 and 3 require knowledge of the sample rate in order to convert it to a frame count. You should try to keep the + calculation of the period size as accurate as possible, but sometimes it's just not practical so just use whatever you can. + + A helper function called ma_calculate_buffer_size_in_frames_from_descriptor() is available to do all of this for you which is what + we'll be using here. + */ + pDescriptor->periodSizeInFrames = ma_calculate_buffer_size_in_frames_from_descriptor(pDescriptor, pDescriptor->sampleRate, pConfig->performanceProfile); + + /* SDL wants the buffer size to be a power of 2 for some reason. */ + if (pDescriptor->periodSizeInFrames > 32768) { + pDescriptor->periodSizeInFrames = 32768; + } else { + pDescriptor->periodSizeInFrames = ma_next_power_of_2(pDescriptor->periodSizeInFrames); + } + + + /* We now have enough information to set up the device. */ + MA_SDL_AudioSpec desiredSpec; + MA_ZERO_OBJECT(&desiredSpec); + desiredSpec.freq = (int)pDescriptor->sampleRate; + desiredSpec.format = ma_format_to_sdl(pDescriptor->format); + desiredSpec.channels = (ma_uint8)pDescriptor->channels; + desiredSpec.samples = (ma_uint16)pDescriptor->periodSizeInFrames; + desiredSpec.callback = (pConfig->deviceType == ma_device_type_capture) ? ma_audio_callback_capture__sdl : ma_audio_callback_playback__sdl; + desiredSpec.userdata = pDeviceEx; + + /* We'll fall back to f32 if we don't have an appropriate mapping between SDL and miniaudio. */ + if (desiredSpec.format == 0) { + desiredSpec.format = MA_AUDIO_F32; + } + + const char *pDeviceName = NULL; + if (pDescriptor->pDeviceID != NULL) { + pDeviceName = ((MA_PFN_SDL_GetAudioDeviceName)pContextEx->sdl.SDL_GetAudioDeviceName)(pDescriptor->pDeviceID->custom.i, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1); + } + + MA_SDL_AudioSpec obtainedSpec; + int deviceID = ((MA_PFN_SDL_OpenAudioDevice)pContextEx->sdl.SDL_OpenAudioDevice)(pDeviceName, (pConfig->deviceType == ma_device_type_playback) ? 0 : 1, &desiredSpec, &obtainedSpec, MA_SDL_AUDIO_ALLOW_ANY_CHANGE); + if (deviceID == 0) { + ma_log_postf(ma_device_get_log((ma_device*)pDeviceEx), MA_LOG_LEVEL_ERROR, "Failed to open SDL2 device."); + return MA_FAILED_TO_OPEN_BACKEND_DEVICE; + } + + if (pConfig->deviceType == ma_device_type_playback) { + pDeviceEx->sdl.deviceIDPlayback = deviceID; + } else { + pDeviceEx->sdl.deviceIDCapture = deviceID; + } + + /* The descriptor needs to be updated with our actual settings. */ + pDescriptor->format = ma_format_from_sdl(obtainedSpec.format); + pDescriptor->channels = obtainedSpec.channels; + pDescriptor->sampleRate = (ma_uint32)obtainedSpec.freq; + ma_channel_map_init_standard(ma_standard_channel_map_default, pDescriptor->channelMap, ma_countof(pDescriptor->channelMap), pDescriptor->channels); + pDescriptor->periodSizeInFrames = obtainedSpec.samples; + pDescriptor->periodCount = 1; /* SDL doesn't use the notion of period counts, so just set to 1. */ + + return MA_SUCCESS; +} + +ma_result ma_device_init__sdl(ma_device *pDevice, const ma_device_config *pConfig, ma_device_descriptor *pDescriptorPlayback, ma_device_descriptor *pDescriptorCapture) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pDevice; + ma_context_sdl *pContextEx = (ma_context_sdl *)pDevice->pContext; + ma_result result; + + /* SDL does not support loopback mode, so must return MA_DEVICE_TYPE_NOT_SUPPORTED if it's requested. */ + if (pConfig->deviceType == ma_device_type_loopback) { + return MA_DEVICE_TYPE_NOT_SUPPORTED; + } + + if (pConfig->deviceType == ma_device_type_capture || pConfig->deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__sdl(pDeviceEx, pConfig, pDescriptorCapture); + if (result != MA_SUCCESS) { + return result; + } + } + + if (pConfig->deviceType == ma_device_type_playback || pConfig->deviceType == ma_device_type_duplex) { + result = ma_device_init_internal__sdl(pDeviceEx, pConfig, pDescriptorPlayback); + if (result != MA_SUCCESS) { + if (pConfig->deviceType == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); + } + + return result; + } + } + + return MA_SUCCESS; +} + +ma_result ma_device_uninit__sdl(ma_device *pDevice) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pDevice; + ma_context_sdl *pContextEx = (ma_context_sdl *)pDevice->pContext; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_CloseAudioDevice)pContextEx->sdl.SDL_CloseAudioDevice)(pDeviceEx->sdl.deviceIDCapture); + } + + return MA_SUCCESS; +} + +ma_result ma_device_start__sdl(ma_device *pDevice) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pDevice; + ma_context_sdl *pContextEx = (ma_context_sdl *)pDevice->pContext; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDCapture, 0); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDPlayback, 0); + } + + return MA_SUCCESS; +} + +ma_result ma_device_stop__sdl(ma_device *pDevice) { + ma_device_sdl *pDeviceEx = (ma_device_sdl *)pDevice; + ma_context_sdl *pContextEx = (ma_context_sdl *)pDevice->pContext; + + if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDCapture, 1); + } + + if (pDevice->type == ma_device_type_playback || pDevice->type == ma_device_type_duplex) { + ((MA_PFN_SDL_PauseAudioDevice)pContextEx->sdl.SDL_PauseAudioDevice)(pDeviceEx->sdl.deviceIDPlayback, 1); + } + + return MA_SUCCESS; +} + +ma_result ma_context_uninit__sdl(ma_context *pContext) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pContext; + + ((MA_PFN_SDL_QuitSubSystem)pContextEx->sdl.SDL_QuitSubSystem)(MA_SDL_INIT_AUDIO); + + /* Close the handle to the SDL shared object last. */ +#ifndef MA_NO_RUNTIME_LINKING + ma_dlclose(ma_context_get_log(pContext), pContextEx->sdl.hSDL); +#endif + pContextEx->sdl.hSDL = NULL; + + return MA_SUCCESS; +} + +ma_result ma_context_init__sdl(ma_context *pContext, const ma_context_config *pConfig, ma_backend_callbacks *pCallbacks) { + ma_context_sdl *pContextEx = (ma_context_sdl *)pContext; + +#ifndef MA_NO_RUNTIME_LINKING + /* We'll use a list of possible shared object names for easier extensibility. */ + const char* pSDLNames[] = { +#if defined(_WIN32) + "SDL2.dll" +#elif defined(__APPLE__) + "SDL2.framework/SDL2" +#else + "libSDL2-2.0.so.0" +#endif + }; + + (void)pConfig; + + /* Check if we have SDL2 installed somewhere. If not it's not usable and we need to abort. */ + for (size_t iName = 0; iName < ma_countof(pSDLNames); iName += 1) { + pContextEx->sdl.hSDL = ma_dlopen(ma_context_get_log(pContext), pSDLNames[iName]); + if (pContextEx->sdl.hSDL != NULL) { + break; + } + } + + if (pContextEx->sdl.hSDL == NULL) { + return MA_NO_BACKEND; /* SDL2 could not be loaded. */ + } + + /* Now that we have the handle to the shared object we can go ahead and load some function pointers. */ + pContextEx->sdl.SDL_InitSubSystem = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_InitSubSystem"); + pContextEx->sdl.SDL_QuitSubSystem = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_QuitSubSystem"); + pContextEx->sdl.SDL_GetNumAudioDevices = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_GetNumAudioDevices"); + pContextEx->sdl.SDL_GetAudioDeviceName = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_GetAudioDeviceName"); + pContextEx->sdl.SDL_CloseAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_CloseAudioDevice"); + pContextEx->sdl.SDL_OpenAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_OpenAudioDevice"); + pContextEx->sdl.SDL_PauseAudioDevice = ma_dlsym(ma_context_get_log(pContext), pContextEx->sdl.hSDL, "SDL_PauseAudioDevice"); +#else + pContextEx->sdl.SDL_InitSubSystem = (ma_proc)SDL_InitSubSystem; + pContextEx->sdl.SDL_QuitSubSystem = (ma_proc)SDL_QuitSubSystem; + pContextEx->sdl.SDL_GetNumAudioDevices = (ma_proc)SDL_GetNumAudioDevices; + pContextEx->sdl.SDL_GetAudioDeviceName = (ma_proc)SDL_GetAudioDeviceName; + pContextEx->sdl.SDL_CloseAudioDevice = (ma_proc)SDL_CloseAudioDevice; + pContextEx->sdl.SDL_OpenAudioDevice = (ma_proc)SDL_OpenAudioDevice; + pContextEx->sdl.SDL_PauseAudioDevice = (ma_proc)SDL_PauseAudioDevice; +#endif /* MA_NO_RUNTIME_LINKING */ + + int resultSDL = ((MA_PFN_SDL_InitSubSystem)pContextEx->sdl.SDL_InitSubSystem)(MA_SDL_INIT_AUDIO); + if (resultSDL != 0) { + ma_dlclose(ma_context_get_log(pContext), pContextEx->sdl.hSDL); + return MA_ERROR; + } + + /* + The last step is to make sure the callbacks are set properly in pCallbacks. Internally, miniaudio will copy these callbacks into the + context object and then use them for then on for calling into our custom backend. + */ + pCallbacks->onContextInit = ma_context_init__sdl; + pCallbacks->onContextUninit = ma_context_uninit__sdl; + pCallbacks->onContextEnumerateDevices = ma_context_enumerate_devices__sdl; + 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; + + return MA_SUCCESS; +} + +#endif /* MA_HAS_SDL */ + +#endif // miniaudio_sdl_inl \ No newline at end of file diff --git a/src/pc/utils/misc.c b/src/pc/utils/misc.c index df98e8f17..46baab981 100644 --- a/src/pc/utils/misc.c +++ b/src/pc/utils/misc.c @@ -13,6 +13,7 @@ #include "game/save_file.h" #include "engine/math_util.h" #include "pc/configfile.h" +#include "pc/string_utils.h" float smooth_step(float edge0, float edge1, float x) { float t = (x - edge0) / (edge1 - edge0);