diff --git a/Makefile.common b/Makefile.common index 125afe8532..2743726ef0 100644 --- a/Makefile.common +++ b/Makefile.common @@ -525,6 +525,10 @@ ifeq ($(TARGET), retroarch_3ds) OBJ += gfx/drivers_font/ctr_font.o endif +ifeq ($(HAVE_LIBNX), 1) + OBJ += gfx/drivers_font/switch_font.o +endif + ifeq ($(HAVE_OSS), 1) OBJ += audio/drivers/oss.o else ifeq ($(HAVE_OSS_BSD), 1) @@ -708,7 +712,6 @@ endif ifeq ($(HAVE_STRIPES), 1) OBJ += menu/drivers/stripes.o DEFINES += -DHAVE_STRIPES - HAVE_MENU_COMMON = 1 endif ifeq ($(HAVE_LAKKA), 1) @@ -821,10 +824,20 @@ ifeq ($(TARGET), retroarch_3ds) endif ifeq ($(TARGET), retroarch_switch) - OBJ += gfx/drivers/switch_gfx.o \ - input/drivers/switch_input.o \ - input/drivers_joypad/switch_joypad.o \ - audio/drivers/switch_audio.o + ifeq ($(HAVE_LIBNX), 1) + OBJ += input/drivers_joypad/switch_joypad.o \ + input/drivers/switch_input.o \ + menu/drivers_display/menu_display_switch.o \ + gfx/drivers/switch_nx_gfx.o \ + audio/drivers/switch_audio.o \ + audio/drivers/switch_nx_thread_audio.o \ + frontend/drivers/platform_switch.o + else + OBJ += gfx/drivers/switch_gfx.o \ + input/drivers/switch_input.o \ + input/drivers_joypad/switch_joypad.o \ + audio/drivers/switch_audio.o + endif endif ifeq ($(HAVE_WAYLAND), 1) diff --git a/Makefile.libnx b/Makefile.libnx new file mode 100644 index 0000000000..13c7e52ab1 --- /dev/null +++ b/Makefile.libnx @@ -0,0 +1,222 @@ +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- + +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=/devkitpro") +endif + +TOPDIR ?= $(CURDIR) +include $(DEVKITPRO)/libnx/switch_rules + +TARGET := retroarch_switch + +DEBUG ?= 0 +WHOLE_ARCHIVE_LINK = 0 +GRIFFIN_BUILD = 0 + +OBJ := + +# For threading we need to overwrite some vars with global defines because devkitPro's includes +# make it hard for us. This works for the pthread wrapper +DEFINES_THREAD := -Dpthread_t=Thread -Dpthread_mutex_t=Mutex -Dpthread_mutexattr_t='void*' -Dpthread_attr_t=int -Dpthread_cond_t=CondVar -Dpthread_condattr_t='int' +DEFINES := -U__linux__ -U__linux -DRARCH_INTERNAL -DGLOBAL_CONFIG_DIR='"/switch"' + +HAVE_CC_RESAMPLER = 1 +HAVE_MENU_COMMON = 1 +HAVE_RTGA = 1 +HAVE_RPNG = 1 +HAVE_RJPEG = 1 +HAVE_RBMP = 1 +HAVE_RGUI = 1 +HAVE_ZLIB = 1 +HAVE_BUILTINZLIB = 1 +HAVE_LIBRETRODB = 1 +HAVE_ZARCH = 0 +HAVE_MATERIALUI = 0 +HAVE_XMB = 0 +HAVE_STATIC_VIDEO_FILTERS = 1 +HAVE_STATIC_AUDIO_FILTERS = 1 +HAVE_MENU = 1 +HAVE_RUNAHEAD = 1 + +# RetroArch libnx useful flags +HAVE_OVERLAY = 0 +HAVE_THREADS = 0 +HAVE_PTHREADS = 0 +HAVE_FREETYPE = 0 +HAVE_SWITCH = 1 +HAVE_LIBNX = 1 + +include Makefile.common +BLACKLIST := +#BLACKLIST += input/input_overlay.o +#BLACKLIST += tasks/task_overlay.o + +OBJ := $(filter-out $(BLACKLIST),$(OBJ)) + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# DATA is a list of directories containing data files +# INCLUDES is a list of directories containing header files +# EXEFS_SRC is the optional input directory containing data copied into exefs, if anything this normally should only contain "main.npdm". +# ROMFS is the directory containing data to be added to RomFS, relative to the Makefile (Optional) +# +# NO_ICON: if set to anything, do not use icon. +# NO_NACP: if set to anything, no .nacp file is generated. +# APP_TITLE is the name of the app stored in the .nacp file (Optional) +# APP_AUTHOR is the author of the app stored in the .nacp file (Optional) +# APP_VERSION is the version of the app stored in the .nacp file (Optional) +# APP_TITLEID is the titleID of the app stored in the .nacp file (Optional) +# ICON is the filename of the icon (.jpg), relative to the project folder. +# If not set, it attempts to use one of the following (in this order): +# - .jpg +# - icon.jpg +# - /default_icon.jpg +#--------------------------------------------------------------------------------- +BUILD := build +SOURCES := $(CURDIR)/source +DATA := data +INCLUDES := include +EXEFS_SRC := exefs_src +#ROMFS := switch/romfs + +APP_TITLE := RetroArch +APP_VERSION := 1.0.0 +APP_AUTHOR := libretro Team +NO_ICON := 1 + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- +ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE -mcpu=cortex-a57+crc+fp+simd + +CFLAGS := -g -Wall -O3 -ffast-math -ffunction-sections \ + $(ARCH) $(DEFINES) -Ideps -Ideps/libz -Ilibretro-common/include -Ideps/stb -I$(LIBNX)/include -include $(LIBNX)/include/switch.h #$(shell $(DEVKITPRO)/portlibs/switch/bin/freetype-config --cflags) + +CFLAGS += $(INCLUDE) -DSWITCH=1 -DHAVE_LIBNX=1 -DNXLINK=1 + +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11 + +ASFLAGS := -g $(ARCH) +LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs $(ARCH) -Wl,--allow-multiple-definition -Wl,-Map,$(notdir $*.map) + +LIBS := -lnx -lm -lstdc++ -lbz2 -lpng -lz + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(PORTLIBS) $(LIBNX) + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- + +export OUTPUT := $(TARGET) +export TOPDIR := $(CURDIR) + +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) + +export DEPSDIR := $(CURDIR)/ + +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) +#--------------------------------------------------------------------------------- + export LD := $(CC) +#--------------------------------------------------------------------------------- +else +#--------------------------------------------------------------------------------- + export LD := $(CXX) +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- + +export OFILES := $(OBJ) libretro_switch.a +export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES))) + +export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + -I$(CURDIR)/$(BUILD) + +export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib) + +export BUILD_EXEFS_SRC := $(TOPDIR)/$(EXEFS_SRC) + +ifeq ($(strip $(ICON)),) + icons := $(wildcard *.jpg) + ifneq (,$(findstring $(TARGET).jpg,$(icons))) + export APP_ICON := $(TOPDIR)/$(TARGET).jpg + else + ifneq (,$(findstring icon.jpg,$(icons))) + export APP_ICON := $(TOPDIR)/icon.jpg + endif + endif +else + export APP_ICON := $(TOPDIR)/$(ICON) +endif + +ifeq ($(strip $(NO_ICON)),) + export NROFLAGS += --icon=$(APP_ICON) +endif + +ifeq ($(strip $(NO_NACP)),) + export NROFLAGS += --nacp=$(CURDIR)/$(TARGET).nacp +endif + +ifneq ($(APP_TITLEID),) + export NACPFLAGS += --titleid=$(APP_TITLEID) +endif + +ifneq ($(ROMFS),) + export NROFLAGS += --romfsdir=$(CURDIR)/$(ROMFS) +endif + +DEPENDS := $(OFILES:.o=.d) + +.PHONY: clean all + +#--------------------------------------------------------------------------------- +# main targets +#--------------------------------------------------------------------------------- +all : $(OUTPUT).pfs0 $(OUTPUT).nro + +$(OUTPUT).pfs0 : $(OUTPUT).nso + +$(OUTPUT).nso : $(OUTPUT).elf + +ifeq ($(strip $(NO_NACP)),) +$(OUTPUT).nro : $(OUTPUT).elf $(OUTPUT).nacp +else +$(OUTPUT).nro : $(OUTPUT).elf +endif + +$(OUTPUT).elf : $(OBJ) + +clean: + rm -f $(OBJ) $(OUTPUT).pfs0 $(OUTPUT).nro $(OUTPUT).elf + +#--------------------------------------------------------------------------------- +# you need a rule like this for each extension you use as binary data +#--------------------------------------------------------------------------------- +%.bin.o %_bin.h : %.bin +#--------------------------------------------------------------------------------- + @echo $(notdir $<) + @$(bin2o) + +#--------------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------------- diff --git a/frontend/drivers/platform_switch.c b/frontend/drivers/platform_switch.c new file mode 100644 index 0000000000..ea19d4010e --- /dev/null +++ b/frontend/drivers/platform_switch.c @@ -0,0 +1,739 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#ifdef HAVE_CONFIG_H +#include "../../config.h" +#endif + +#ifndef IS_SALAMANDER +#include +#endif + +#include "../frontend_driver.h" +#include "../../verbosity.h" +#include "../../defaults.h" +#include "../../paths.h" +#include "../../retroarch.h" +#include "../../file_path_special.h" +#include "../../audio/audio_driver.h" + +#ifndef IS_SALAMANDER +#ifdef HAVE_MENU +#include "../../menu/menu_driver.h" +#endif +#endif + +static enum frontend_fork switch_fork_mode = FRONTEND_FORK_NONE; +static const char *elf_path_cst = "/switch/retroarch_switch.nro"; + +static uint64_t frontend_switch_get_mem_used(void); + +// Splash +static uint32_t *splashData = NULL; + +// switch_gfx.c protypes, we really need a header +extern void gfx_slow_swizzling_blit(uint32_t *buffer, uint32_t *image, int w, int h, int tx, int ty, bool blend); + +static void get_first_valid_core(char *path_return) +{ + DIR *dir; + struct dirent *ent; + const char *extension = ".nro"; + + path_return[0] = '\0'; + + dir = opendir("/retroarch/cores"); + if (dir != NULL) + { + while (ent = readdir(dir)) + { + if (ent == NULL) + break; + if (strlen(ent->d_name) > strlen(extension) && !strcmp(ent->d_name + strlen(ent->d_name) - strlen(extension), extension)) + { + strcpy(path_return, "/retroarch/cores"); + strcat(path_return, "/"); + strcat(path_return, ent->d_name); + break; + } + } + closedir(dir); + } +} + +static void frontend_switch_get_environment_settings(int *argc, char *argv[], void *args, void *params_data) +{ + (void)args; + +#ifndef IS_SALAMANDER +#if defined(HAVE_LOGGER) + logger_init(); +#elif defined(HAVE_FILE_LOGGER) + retro_main_log_file_init("/retroarch-log.txt"); +#endif +#endif + + fill_pathname_basedir(g_defaults.dirs[DEFAULT_DIR_PORT], "/retroarch/retroarch_switch.nro", sizeof(g_defaults.dirs[DEFAULT_DIR_PORT])); + RARCH_LOG("port dir: [%s]\n", g_defaults.dirs[DEFAULT_DIR_PORT]); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS], g_defaults.dirs[DEFAULT_DIR_PORT], + "downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_ASSETS], g_defaults.dirs[DEFAULT_DIR_PORT], + "media", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], g_defaults.dirs[DEFAULT_DIR_PORT], + "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_INFO], g_defaults.dirs[DEFAULT_DIR_CORE], + "info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SAVESTATE], g_defaults.dirs[DEFAULT_DIR_CORE], + "savestates", sizeof(g_defaults.dirs[DEFAULT_DIR_SAVESTATE])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SRAM], g_defaults.dirs[DEFAULT_DIR_CORE], + "savefiles", sizeof(g_defaults.dirs[DEFAULT_DIR_SRAM])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SYSTEM], g_defaults.dirs[DEFAULT_DIR_CORE], + "system", sizeof(g_defaults.dirs[DEFAULT_DIR_SYSTEM])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_PLAYLIST], g_defaults.dirs[DEFAULT_DIR_CORE], + "playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_PLAYLIST])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], g_defaults.dirs[DEFAULT_DIR_PORT], + "config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_REMAP], g_defaults.dirs[DEFAULT_DIR_PORT], + "config/remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER], g_defaults.dirs[DEFAULT_DIR_PORT], + "filters", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_DATABASE], g_defaults.dirs[DEFAULT_DIR_PORT], + "database/rdb", sizeof(g_defaults.dirs[DEFAULT_DIR_DATABASE])); + + fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CURSOR], g_defaults.dirs[DEFAULT_DIR_PORT], + "database/cursors", sizeof(g_defaults.dirs[DEFAULT_DIR_CURSOR])); + + fill_pathname_join(g_defaults.path.config, g_defaults.dirs[DEFAULT_DIR_PORT], + file_path_str(FILE_PATH_MAIN_CONFIG), sizeof(g_defaults.path.config)); +} + +static void frontend_switch_deinit(void *data) +{ + (void)data; + +#if defined(HAVE_LIBNX) && defined(NXLINK) // Else freeze on exit + socketExit(); +#endif + + // Splash + if (splashData) + { + free(splashData); + splashData = NULL; + } + + gfxExit(); +} + +static void frontend_switch_exec(const char *path, bool should_load_game) +{ + char game_path[PATH_MAX]; + const char *arg_data[3]; + char error_string[200 + PATH_MAX]; + int args = 0; + int error = 0; + + game_path[0] = NULL; + arg_data[0] = NULL; + + arg_data[args] = elf_path_cst; + arg_data[args + 1] = NULL; + args++; + + RARCH_LOG("Attempt to load core: [%s].\n", path); +#ifndef IS_SALAMANDER + if (should_load_game && !path_is_empty(RARCH_PATH_CONTENT)) + { + strcpy(game_path, path_get(RARCH_PATH_CONTENT)); + arg_data[args] = game_path; + arg_data[args + 1] = NULL; + args++; + RARCH_LOG("content path: [%s].\n", path_get(RARCH_PATH_CONTENT)); + } +#endif + + if (path && path[0]) + { +#ifdef IS_SALAMANDER + struct stat sbuff; + bool file_exists; + + file_exists = stat(path, &sbuff) == 0; + if (!file_exists) + { + char core_path[PATH_MAX]; + + /* find first valid core and load it if the target core doesnt exist */ + get_first_valid_core(&core_path[0]); + + if (core_path[0] == '\0') + { + /*errorInit(&error_dialog, ERROR_TEXT, CFG_LANGUAGE_EN); + errorText(&error_dialog, "There are no cores installed, install a core to continue."); + errorDisp(&error_dialog);*/ + svcExitProcess(); + } + } +#endif + char *argBuffer = (char *)malloc(PATH_MAX); + if (should_load_game) + { + snprintf(argBuffer, PATH_MAX, "%s \"%s\"", path, game_path); + } + else + { + snprintf(argBuffer, PATH_MAX, "%s", path); + } + + envSetNextLoad(path, argBuffer); + } +} + +#ifndef IS_SALAMANDER +static bool frontend_switch_set_fork(enum frontend_fork fork_mode) +{ + switch (fork_mode) + { + case FRONTEND_FORK_CORE: + RARCH_LOG("FRONTEND_FORK_CORE\n"); + switch_fork_mode = fork_mode; + break; + case FRONTEND_FORK_CORE_WITH_ARGS: + RARCH_LOG("FRONTEND_FORK_CORE_WITH_ARGS\n"); + switch_fork_mode = fork_mode; + break; + case FRONTEND_FORK_RESTART: + RARCH_LOG("FRONTEND_FORK_RESTART\n"); + /* NOTE: We don't implement Salamander, so just turn + this into FRONTEND_FORK_CORE. */ + switch_fork_mode = FRONTEND_FORK_CORE; + break; + case FRONTEND_FORK_NONE: + default: + return false; + } + + return true; +} +#endif + +static void frontend_switch_exitspawn(char *s, size_t len) +{ + bool should_load_game = false; +#ifndef IS_SALAMANDER + if (switch_fork_mode == FRONTEND_FORK_NONE) + return; + + switch (switch_fork_mode) + { + case FRONTEND_FORK_CORE_WITH_ARGS: + should_load_game = true; + break; + default: + break; + } +#endif + frontend_switch_exec(s, should_load_game); +} + +static void frontend_switch_shutdown(bool unused) +{ + (void)unused; +} + +void argb_to_rgba8(uint32_t *buff, uint32_t height, uint32_t width) +{ + // Convert + for (uint32_t h = 0; h < height; h++) + { + for (uint32_t w = 0; w < width; w++) + { + uint32_t offset = (h * width) + w; + uint32_t c = buff[offset]; + + uint32_t a = (uint32_t)((c & 0xff000000) >> 24); + uint32_t r = (uint32_t)((c & 0x00ff0000) >> 16); + uint32_t g = (uint32_t)((c & 0x0000ff00) >> 8); + uint32_t b = (uint32_t)(c & 0x000000ff); + + buff[offset] = RGBA8(r, g, b, a); + } + } +} + +void frontend_switch_showsplash() +{ + printf("[Splash] Showing splashScreen\n"); + + if (splashData) + { + uint32_t width, height; + width = height = 0; + + uint32_t *frambuffer = (uint32_t *)gfxGetFramebuffer(&width, &height); + + gfx_slow_swizzling_blit(frambuffer, splashData, width, height, 0, 0, false); + + gfxFlushBuffers(); + gfxSwapBuffers(); + gfxWaitForVsync(); + } +} + +// From rpng_test.c +bool rpng_load_image_argb(const char *path, uint32_t **data, unsigned *width, unsigned *height) +{ + int retval; + size_t file_len; + bool ret = true; + rpng_t *rpng = NULL; + void *ptr = NULL; + + struct nbio_t *handle = (struct nbio_t *)nbio_open(path, NBIO_READ); + + if (!handle) + goto end; + + nbio_begin_read(handle); + + while (!nbio_iterate(handle)) + svcSleepThread(3); + + ptr = nbio_get_ptr(handle, &file_len); + + if (!ptr) + { + ret = false; + goto end; + } + + rpng = rpng_alloc(); + + if (!rpng) + { + ret = false; + goto end; + } + + if (!rpng_set_buf_ptr(rpng, (uint8_t *)ptr)) + { + ret = false; + goto end; + } + + if (!rpng_start(rpng)) + { + ret = false; + goto end; + } + + while (rpng_iterate_image(rpng)) + svcSleepThread(3); + + if (!rpng_is_valid(rpng)) + { + ret = false; + goto end; + } + + do + { + retval = rpng_process_image(rpng, (void **)data, file_len, width, height); + svcSleepThread(3); + } while (retval == IMAGE_PROCESS_NEXT); + + if (retval == IMAGE_PROCESS_ERROR || retval == IMAGE_PROCESS_ERROR_END) + ret = false; + +end: + if (handle) + nbio_free(handle); + + if (rpng) + rpng_free(rpng); + + rpng = NULL; + + if (!ret) + free(*data); + + return ret; +} + +int nanosleep(const struct timespec *rqtp, struct timespec *rmtp) +{ + svcSleepThread(rqtp->tv_nsec + (rqtp->tv_sec * 1000000000)); + return 0; +} + +long sysconf(int name) +{ + switch (name) + { + case 8: + return 0x1000; + } + return -1; +} + +ssize_t readlink(const char *restrict path, char *restrict buf, size_t bufsize) +{ + return -1; +} + +// Taken from glibc +char *realpath(const char *name, char *resolved) +{ + char *rpath, *dest, *extra_buf = NULL; + const char *start, *end, *rpath_limit; + long int path_max; + int num_links = 0; + + if (name == NULL) + { + /* As per Single Unix Specification V2 we must return an error if + either parameter is a null pointer. We extend this to allow + the RESOLVED parameter to be NULL in case the we are expected to + allocate the room for the return value. */ + return NULL; + } + + if (name[0] == '\0') + { + /* As per Single Unix Specification V2 we must return an error if + the name argument points to an empty string. */ + return NULL; + } + +#ifdef PATH_MAX + path_max = PATH_MAX; +#else + path_max = pathconf(name, _PC_PATH_MAX); + if (path_max <= 0) + path_max = 1024; +#endif + + if (resolved == NULL) + { + rpath = malloc(path_max); + if (rpath == NULL) + return NULL; + } + else + rpath = resolved; + rpath_limit = rpath + path_max; + + if (name[0] != '/') + { + if (!getcwd(rpath, path_max)) + { + rpath[0] = '\0'; + goto error; + } + dest = memchr(rpath, '\0', path_max); + } + else + { + rpath[0] = '/'; + dest = rpath + 1; + } + + for (start = end = name; *start; start = end) + { + int n; + + /* Skip sequence of multiple path-separators. */ + while (*start == '/') + ++start; + + /* Find end of path component. */ + for (end = start; *end && *end != '/'; ++end) + /* Nothing. */; + + if (end - start == 0) + break; + else if (end - start == 1 && start[0] == '.') + /* nothing */; + else if (end - start == 2 && start[0] == '.' && start[1] == '.') + { + /* Back up to previous component, ignore if at root already. */ + if (dest > rpath + 1) + while ((--dest)[-1] != '/') + ; + } + else + { + size_t new_size; + + if (dest[-1] != '/') + *dest++ = '/'; + + if (dest + (end - start) >= rpath_limit) + { + ptrdiff_t dest_offset = dest - rpath; + char *new_rpath; + + if (resolved) + { + if (dest > rpath + 1) + dest--; + *dest = '\0'; + goto error; + } + new_size = rpath_limit - rpath; + if (end - start + 1 > path_max) + new_size += end - start + 1; + else + new_size += path_max; + new_rpath = (char *)realloc(rpath, new_size); + if (new_rpath == NULL) + goto error; + rpath = new_rpath; + rpath_limit = rpath + new_size; + + dest = rpath + dest_offset; + } + + dest = memcpy(dest, start, end - start); + *dest = '\0'; + } + } + if (dest > rpath + 1 && dest[-1] == '/') + --dest; + *dest = '\0'; + + return rpath; + +error: + if (resolved == NULL) + free(rpath); + return NULL; +} + +// runloop_get_system_info isnt initialized that early.. +extern void retro_get_system_info(struct retro_system_info *info); + +static void frontend_switch_init(void *data) +{ + (void)data; + + // Init Resolution before initDefault + gfxInitResolution(1280, 720); + + gfxInitDefault(); + gfxSetMode(GfxMode_TiledDouble); + + // Needed, else its flipped and mirrored + gfxSetDrawFlip(false); + gfxConfigureTransform(0); + +#if defined(HAVE_LIBNX) && defined(NXLINK) + socketInitializeDefault(); + nxlinkStdio(); +#ifndef IS_SALAMANDER + verbosity_enable(); +#endif +#endif + + rarch_system_info_t *sys_info = runloop_get_system_info(); + retro_get_system_info(sys_info); + + const char *core_name = NULL; + + printf("[Video]: Video initialized\n"); + + uint32_t width, height; + width = height = 0; + + // Load splash + if (!splashData) + { + if (sys_info) + { + core_name = sys_info->info.library_name; + char *full_core_splash_path = (char *)malloc(PATH_MAX); + snprintf(full_core_splash_path, PATH_MAX, "/retroarch/rgui/splash/%s.png", core_name); + + rpng_load_image_argb((const char *)full_core_splash_path, &splashData, &width, &height); + if (splashData) + { + argb_to_rgba8(splashData, height, width); + frontend_switch_showsplash(); + } + else + { + rpng_load_image_argb("/retroarch/rgui/splash/RetroArch.png", &splashData, &width, &height); + if (splashData) + { + argb_to_rgba8(splashData, height, width); + frontend_switch_showsplash(); + } + } + + free(full_core_splash_path); + } + else + { + rpng_load_image_argb("/retroarch/rgui/splash/RetroArch.png", &splashData, &width, &height); + if (splashData) + { + argb_to_rgba8(splashData, height, width); + frontend_switch_showsplash(); + } + } + } + else + { + frontend_switch_showsplash(); + } +} + +static int frontend_switch_get_rating(void) +{ + return 1000; +} + +enum frontend_architecture frontend_switch_get_architecture(void) +{ + return FRONTEND_ARCH_ARMV8; +} + +static int frontend_switch_parse_drive_list(void *data, bool load_content) +{ +#ifndef IS_SALAMANDER + file_list_t *list = (file_list_t *)data; + enum msg_hash_enums enum_idx = load_content ? MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR : MSG_UNKNOWN; + + if (!list) + return -1; + + menu_entries_append_enum(list, "/", msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR), + enum_idx, + FILE_TYPE_DIRECTORY, 0, 0); +#endif + + return 0; +} + +static uint64_t frontend_switch_get_mem_total(void) +{ + uint64_t memoryTotal = 0; + svcGetInfo(&memoryTotal, 6, CUR_PROCESS_HANDLE, 0); // avaiable + memoryTotal += frontend_switch_get_mem_used(); + + return memoryTotal; +} + +static uint64_t frontend_switch_get_mem_used(void) +{ + uint64_t memoryUsed = 0; + svcGetInfo(&memoryUsed, 7, CUR_PROCESS_HANDLE, 0); // used + + return memoryUsed; +} + +static enum frontend_powerstate frontend_switch_get_powerstate(int *seconds, int *percent) +{ + // This is fine monkaS + return FRONTEND_POWERSTATE_CHARGED; +} + +static void frontend_switch_get_os(char *s, size_t len, int *major, int *minor) +{ + strlcpy(s, "Horizon OS", len); + + // There is pretty sure a better way, but this will do just fine + if (kernelAbove500()) + { + *major = 5; + *minor = 0; + } + else if (kernelAbove400()) + { + *major = 4; + *minor = 0; + } + else if (kernelAbove300()) + { + *major = 3; + *minor = 0; + } + else if (kernelAbove200()) + { + *major = 2; + *minor = 0; + } + else + { + // either 1.0 or > 5.x + *major = 1; + *minor = 0; + } +} + +static void frontend_switch_get_name(char *s, size_t len) +{ + // TODO: Add Mariko at some point + strlcpy(s, "Nintendo Switch", len); +} + +frontend_ctx_driver_t frontend_ctx_switch = + { + frontend_switch_get_environment_settings, + frontend_switch_init, + frontend_switch_deinit, + frontend_switch_exitspawn, + NULL, /* process_args */ + frontend_switch_exec, +#ifdef IS_SALAMANDER + NULL, +#else + frontend_switch_set_fork, +#endif + frontend_switch_shutdown, + frontend_switch_get_name, + frontend_switch_get_os, + frontend_switch_get_rating, + NULL, /* load_content */ + frontend_switch_get_architecture, + frontend_switch_get_powerstate, + frontend_switch_parse_drive_list, + frontend_switch_get_mem_total, + frontend_switch_get_mem_used, + NULL, /* install_signal_handler */ + NULL, /* get_signal_handler_state */ + NULL, /* set_signal_handler_state */ + NULL, /* destroy_signal_handler_state */ + NULL, /* attach_console */ + NULL, /* detach_console */ + NULL, /* watch_path_for_changes */ + NULL, /* check_for_path_changes */ + NULL, /* set_sustained_performance_mode */ + "switch", +}; diff --git a/frontend/frontend_driver.c b/frontend/frontend_driver.c index eea6e057cb..81262eb7de 100644 --- a/frontend/frontend_driver.c +++ b/frontend/frontend_driver.c @@ -59,6 +59,9 @@ static frontend_ctx_driver_t *frontend_ctx_drivers[] = { #if defined(_3DS) &frontend_ctx_ctr, #endif +#if defined(SWITCH) && defined(HAVE_LIBNX) + &frontend_ctx_switch, +#endif #if defined(_WIN32) && !defined(_XBOX) &frontend_ctx_win32, #endif diff --git a/frontend/frontend_driver.h b/frontend/frontend_driver.h index b4aa568f72..63fc0ae750 100644 --- a/frontend/frontend_driver.h +++ b/frontend/frontend_driver.h @@ -121,6 +121,7 @@ extern frontend_ctx_driver_t frontend_ctx_darwin; extern frontend_ctx_driver_t frontend_ctx_unix; extern frontend_ctx_driver_t frontend_ctx_psp; extern frontend_ctx_driver_t frontend_ctx_ctr; +extern frontend_ctx_driver_t frontend_ctx_switch; extern frontend_ctx_driver_t frontend_ctx_win32; extern frontend_ctx_driver_t frontend_ctx_xenon; extern frontend_ctx_driver_t frontend_ctx_emscripten; diff --git a/gfx/common/switch_common.h b/gfx/common/switch_common.h new file mode 100644 index 0000000000..d3858cb975 --- /dev/null +++ b/gfx/common/switch_common.h @@ -0,0 +1,59 @@ +#ifndef SWITCH_COMMON_H__ +#define SWITCH_COMMON_H__ + +#include +#include + +typedef struct +{ + bool vsync; + bool rgb32; + bool smooth; // bilinear + unsigned width, height; + unsigned rotation; + struct video_viewport vp; + struct texture_image *overlay; + bool overlay_enabled; + bool in_menu; + struct + { + bool enable; + bool fullscreen; + + uint32_t *pixels; + + uint32_t width; + uint32_t height; + + unsigned tgtw; + unsigned tgth; + + struct scaler_ctx scaler; + } menu_texture; + + struct + { + uint32_t width; + uint32_t height; + uint32_t x_offset; + } hw_scale; + + uint32_t image[1280 * 720]; + uint32_t tmp_image[1280 * 720]; + u32 cnt; + struct scaler_ctx scaler; + uint32_t last_width; + uint32_t last_height; + bool keep_aspect; + bool should_resize; + bool need_clear; + bool is_threaded; + + bool o_size; + uint32_t o_height; + uint32_t o_width; +} switch_video_t; + +void gfx_slow_swizzling_blit(uint32_t *buffer, uint32_t *image, int w, int h, int tx, int ty, bool blend); + +#endif diff --git a/griffin/griffin.c b/griffin/griffin.c index 118c307fc6..e4e05c0096 100644 --- a/griffin/griffin.c +++ b/griffin/griffin.c @@ -940,6 +940,8 @@ FRONTEND #include "../frontend/drivers/platform_psp.c" #elif defined(_3DS) #include "../frontend/drivers/platform_ctr.c" +#elif defined(SWITCH) && defined(HAVE_LIBNX) +#include "../frontend/drivers/platform_switch.c" #elif defined(XENON) #include "../frontend/drivers/platform_xenon.c" #elif defined(__QNX__) diff --git a/input/drivers_joypad/switch_joypad.c b/input/drivers_joypad/switch_joypad.c index 1f67283ccc..62aafe4db7 100644 --- a/input/drivers_joypad/switch_joypad.c +++ b/input/drivers_joypad/switch_joypad.c @@ -161,7 +161,8 @@ static void switch_joypad_poll(void) hidScanInput(); - if (settings->bools.split_joycon && !hidGetHandheldMode()) + // TODO: Options via menu + /*if (settings->bools.split_joycon && !hidGetHandheldMode()) { if (lastMode != 1) { @@ -180,7 +181,8 @@ static void switch_joypad_poll(void) hidSetNpadJoyAssignmentModeDual(CONTROLLER_PLAYER_2); lastMode = 0; } - } + }*/ + for (int i = 0; i < MAX_PADS; i++) { HidControllerID target = (i == 0) ? CONTROLLER_P1_AUTO : i;