diff --git a/common/port.h b/common/port.h index 435de115..cde6ef62 100644 --- a/common/port.h +++ b/common/port.h @@ -39,7 +39,12 @@ GNU General Public License for more details. #if XASH_POSIX #include - #include + #ifdef XASH_NSWITCH + #define SOLDER_LIBDL_COMPAT + #include + #else + #include + #endif #define PATH_SPLITTER "/" #define HAVE_DUP diff --git a/engine/common/build.c b/engine/common/build.c index c4ddaeeb..a74199ca 100644 --- a/engine/common/build.c +++ b/engine/common/build.c @@ -95,6 +95,8 @@ const char *Q_buildos( void ) osname = "DOS4GW"; #elif XASH_HAIKU osname = "haiku"; +#elif XASH_NSWITCH + osname = "nswitch"; #else #error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" #endif diff --git a/engine/common/host.c b/engine/common/host.c index 07fbfd55..eff5b335 100644 --- a/engine/common/host.c +++ b/engine/common/host.c @@ -990,6 +990,10 @@ void Host_InitCommon( int argc, char **argv, const char *progname, qboolean bCha #if TARGET_OS_IOS const char *IOS_GetDocsDir(); Q_strncpy( host.rootdir, IOS_GetDocsDir(), sizeof(host.rootdir) ); +#elif XASH_NSWITCH + // we can't NOT be in the same directory as the NRO + host.rootdir[0] = '.'; + host.rootdir[1] = '\0'; #elif XASH_SDL == 2 char *szBasePath; diff --git a/engine/common/imagelib/img_png.c b/engine/common/imagelib/img_png.c index 9569a241..3fbacad4 100644 --- a/engine/common/imagelib/img_png.c +++ b/engine/common/imagelib/img_png.c @@ -21,6 +21,9 @@ GNU General Public License for more details. #if defined(XASH_NO_NETWORK) #include "platform/stub/net_stub.h" +#elif XASH_NSWITCH + // our ntohl is here + #include #elif !XASH_WIN32 #include #endif diff --git a/engine/common/system.c b/engine/common/system.c index b2a0a659..e21dc1a6 100644 --- a/engine/common/system.c +++ b/engine/common/system.c @@ -25,7 +25,12 @@ GNU General Public License for more details. #if XASH_POSIX #include #include +#if XASH_NSWITCH +#define SOLDER_LIBDL_COMPAT +#include +#else #include +#endif #if !XASH_ANDROID #include @@ -126,7 +131,7 @@ const char *Sys_GetCurrentUser( void ) if( GetUserName( s_userName, &size )) return s_userName; -#elif XASH_POSIX && !XASH_ANDROID +#elif XASH_POSIX && !XASH_ANDROID && !XASH_NSWITCH uid_t uid = geteuid(); struct passwd *pw = getpwuid( uid ); @@ -405,6 +410,9 @@ void Sys_Error( const char *error, ... ) { va_list argptr; char text[MAX_PRINT_MSG]; +#if XASH_NSWITCH + FILE *fout; +#endif DEBUG_BREAK; @@ -429,6 +437,16 @@ void Sys_Error( const char *error, ... ) #endif } +#if XASH_NSWITCH + fprintf( stderr, "Fatal error: %s\n", text ); + fout = fopen( "fatal.log", "w" ); + if (fout) + { + fprintf( fout, "Fatal error: %s\n", text ); + fclose( fout ); + } +#endif + if( host_developer.value ) { #if XASH_WIN32 diff --git a/engine/platform/nswitch/sys_nswitch.c b/engine/platform/nswitch/sys_nswitch.c new file mode 100644 index 00000000..9d3aab89 --- /dev/null +++ b/engine/platform/nswitch/sys_nswitch.c @@ -0,0 +1,61 @@ +/* +switch.c - switch backend +Copyright (C) 2021 fgsfds + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#include "platform/platform.h" +#include +#include +#include +#include +#include +#include + +static int nxlink_sock = -1; + +// HACKHACK: force these lads to link in +const solder_export_t solder_extra_exports[] = +{ + SOLDER_EXPORT_SYMBOL(vsprintf), + SOLDER_EXPORT_SYMBOL(isalpha), + SOLDER_EXPORT_SYMBOL(isalnum), + SOLDER_EXPORT_SYMBOL(tolower), + SOLDER_EXPORT_SYMBOL(toupper), +}; + +void Platform_ShellExecute( const char *path, const char *parms ) +{ + Con_Reportf( S_WARN "Tried to shell execute `%s` -- not supported\n", path ); +} + +void NSwitch_Init( void ) +{ + socketInitializeDefault(); + nxlink_sock = nxlinkStdio(); + if ( solder_init(0) < 0 ) + { + fprintf( stderr, "solder_init() failed: %s\n", solder_dlerror() ); + fflush( stderr ); + exit(1); + } + printf("NSwitch_Init\n"); +} + +void NSwitch_Shutdown( void ) +{ + printf("NSwitch_Shutdown\n"); + solder_quit(); + if (nxlink_sock >= 0) + close(nxlink_sock); + socketExit(); +} diff --git a/engine/platform/platform.h b/engine/platform/platform.h index c25ffe29..9a7d34eb 100644 --- a/engine/platform/platform.h +++ b/engine/platform/platform.h @@ -45,6 +45,11 @@ const char *Android_LoadID( void ); void Android_SaveID( const char *id ); #endif +#if XASH_NSWITCH +void NSwitch_Init( void ); +void NSwitch_Shutdown( void ); +#endif + /* ============================================================================== diff --git a/engine/platform/posix/sys_posix.c b/engine/platform/posix/sys_posix.c index 328d11db..903e3a69 100644 --- a/engine/platform/posix/sys_posix.c +++ b/engine/platform/posix/sys_posix.c @@ -67,7 +67,7 @@ static qboolean Sys_FindExecutable( const char *baseName, char *buf, size_t size return false; } -#if !XASH_ANDROID +#if !XASH_ANDROID && !XASH_NSWITCH void Platform_ShellExecute( const char *path, const char *parms ) { char xdgOpen[128]; @@ -145,7 +145,7 @@ void Posix_Daemonize( void ) } -#if !XASH_SDL && !XASH_ANDROID +#if !XASH_SDL && !XASH_ANDROID && !XASH_NSWITCH void Platform_Init( void ) { diff --git a/engine/platform/sdl/sys_sdl.c b/engine/platform/sdl/sys_sdl.c index b1ad5838..7adb54ac 100644 --- a/engine/platform/sdl/sys_sdl.c +++ b/engine/platform/sdl/sys_sdl.c @@ -61,12 +61,15 @@ void Platform_Init( void ) SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); SDL_StopTextInput(); #endif // XASH_SDL == 2 -#if XASH_POSIX +#if XASH_POSIX && !XASH_NSWITCH Posix_Daemonize(); #endif #ifdef XASH_WIN32 Wcon_CreateConsole(); // system console used by dedicated server or show fatal errors #endif +#ifdef XASH_NSWITCH + NSwitch_Init(); +#endif } void Platform_Shutdown( void ) @@ -74,4 +77,7 @@ void Platform_Shutdown( void ) #ifdef XASH_WIN32 Wcon_DestroyConsole(); #endif +#ifdef XASH_NSWITCH + NSwitch_Shutdown(); +#endif } \ No newline at end of file diff --git a/engine/server/sv_game.c b/engine/server/sv_game.c index c3f25eb0..9933322b 100644 --- a/engine/server/sv_game.c +++ b/engine/server/sv_game.c @@ -3071,7 +3071,7 @@ void SV_SetStringArrayMode( qboolean dynamic ) } #ifdef XASH_64BIT -#if !XASH_WIN32 +#if !XASH_WIN32 && !XASH_NSWITCH #define USE_MMAP #include #endif diff --git a/engine/wscript b/engine/wscript index c1479c45..9d99be50 100644 --- a/engine/wscript +++ b/engine/wscript @@ -51,6 +51,19 @@ def configure(conf): elif conf.env.DEST_OS == 'android': # Android doesn't need SDL2 for i in ['log']: conf.check_cc(lib = i) + if conf.env.DEST_OS == 'nswitch': + # our pkg-config script for SDL2 tactically forgets to add -lstdc++ to LDFLAGS + conf.options.SDL2_SANITY_CHECK = False + conf.load('sdl2') + if not conf.env.HAVE_SDL2: + conf.fatal('SDL2 not availiable! Install switch-sdl2!') + conf.define('XASH_SDL', 2) + # find our dynamic linker + # conf.check_cfg(package='solder', args='--cflags --libs', uselib_store='SOLDER') + conf.env.append_unique('LDFLAGS', '-lsolderd') + # disallow undefined symbols + conf.env.append_unique('CXXFLAGS', '-Wl,--no-undefined') + conf.env.append_unique('CFLAGS', '-Wl,--no-undefined') elif conf.options.FBDEV_SW: # unused, XASH_LINUX without XASH_SDL gives fbdev & alsa support # conf.define('XASH_FBDEV', 1) @@ -159,6 +172,11 @@ def build(bld): libs += ['LOG'] source += bld.path.ant_glob(['platform/android/*.cpp', 'platform/android/*.c', 'platform/linux/*.c']) + if bld.env.DEST_OS == 'nswitch': + libs += ['SOLDER'] + source += bld.path.ant_glob(['platform/nswitch/*.c']) + is_cxx_link = True # switch-mesa requires libstdc++ + # add client files if not bld.env.DEDICATED: source += bld.path.ant_glob([ diff --git a/game_launch/game.cpp b/game_launch/game.cpp index 7b637989..361d2c6a 100644 --- a/game_launch/game.cpp +++ b/game_launch/game.cpp @@ -20,7 +20,7 @@ GNU General Public License for more details. #include #include -#if defined(__APPLE__) || defined(__unix__) || defined(__HAIKU__) +#if defined(__APPLE__) || defined(__unix__) || defined(__HAIKU__) || defined(__SWITCH__) #define XASHLIB "libxash." OS_LIB_EXT #elif _WIN32 #if !__MINGW32__ && _MSC_VER >= 1200 diff --git a/public/build.h b/public/build.h index 6e1f326d..3f882c0d 100644 --- a/public/build.h +++ b/public/build.h @@ -77,6 +77,7 @@ For more information, please refer to #undef XASH_WIN32 #undef XASH_WIN64 #undef XASH_X86 +#undef XASH_NSWITCH //================================================================ // @@ -124,12 +125,16 @@ For more information, please refer to #define XASH_LITTLE_ENDIAN 1 #elif defined __HAIKU__ #define XASH_HAIKU 1 + #define XASH_LITTLE_ENDIAN +#elif defined __SWITCH__ + #define XASH_NSWITCH 1 + #define XASH_LITTLE_ENDIAN #define XASH_POSIX 1 #else #error "Place your operating system name here! If this is a mistake, try to fix conditions above and report a bug" #endif -#if defined XASH_ANDROID || defined XASH_IOS +#if defined XASH_ANDROID || defined XASH_IOS || defined XASH_NSWITCH #define XASH_MOBILE_PLATFORM 1 #endif diff --git a/scripts/waifulib/xcompile.py b/scripts/waifulib/xcompile.py index 0abf53f1..34dde074 100644 --- a/scripts/waifulib/xcompile.py +++ b/scripts/waifulib/xcompile.py @@ -30,6 +30,8 @@ ANDROID_NDK_API_MIN = { 10: 3, 19: 16, 20: 16, 23: 16 } # minimal API level ndk ANDROID_STPCPY_API_MIN = 21 # stpcpy() introduced in SDK 21 ANDROID_64BIT_API_MIN = 21 # minimal API level that supports 64-bit targets +NSWITCH_ENVVARS = ['DEVKITPRO'] + # This class does support ONLY r10e and r19c/r20 NDK class Android: ctx = None # waf context @@ -348,6 +350,83 @@ class Android: ldflags += ['-march=armv5te'] return ldflags +class NintendoSwitch: + ctx = None # waf context + arch = "aarch64" + dkp_dir = None + portlibs_dir = None + dka64_dir = None + libnx_dir = None + + def __init__(self, ctx): + self.ctx = ctx + + for i in NSWITCH_ENVVARS: + self.dkp_dir = os.getenv(i) + if self.dkp_dir != None: + break + else: + ctx.fatal('Set %s environment variable pointing to the DEVKITPRO home!' % + ' or '.join(ANDROID_NDK_ENVVARS)) + + self.dkp_dir = os.path.abspath(self.dkp_dir) + + self.dka64_dir = os.path.join(self.dkp_dir, 'devkitA64') + if not os.path.exists(self.dka64_dir): + ctx.fatal('devkitA64 not found in `%s`. Install devkitA64!' % self.dka64_dir) + + self.libnx_dir = os.path.join(self.dkp_dir, 'libnx') + if not os.path.exists(self.libnx_dir): + ctx.fatal('libnx not found in `%s`. Install libnx!' % self.libnx_dir) + + self.portlibs_dir = os.path.join(self.dkp_dir, 'portlibs', 'switch') + if not os.path.exists(self.portlibs_dir): + ctx.fatal('No Switch libraries found in `%s`!' % self.portlibs_dir) + + def cc(self): + return 'aarch64-none-elf-gcc' + + def cxx(self): + return 'aarch64-none-elf-g++' + + def strip(self): + return 'aarch64-none-elf-strip' + + def pkgconfig(self): + return 'aarch64-none-elf-pkg-config' + + def cflags(self, cxx = False): + cflags = [] + # arch flags + cflags += ['-D__SWITCH__', '-march=armv8-a+crc+crypto', '-mtune=cortex-a57', '-mtp=soft', '-ftls-model=local-exec', '-fPIE'] + # help the linker out + cflags += ['-ffunction-sections', '-fdata-sections'] + # base include dirs + cflags += ['-isystem "%s/include"' % self.libnx_dir, '-I"%s/include"' % self.portlibs_dir] + if cxx: + # while these are supported, they could fuck up my crappy dynamic linker + cflags += ['-fno-exceptions', '-fno-rtti'] + # the game wants GNU extensions + cflags += ['-std=gnu++17', '-D_GNU_SOURCE'] + else: + cflags += ['-std=gnu11', '-D_GNU_SOURCE'] + # we know we have neon + cflags += ['-DHAVE_EFFICIENT_UNALIGNED_ACCESS', '-DVECTORIZE_SINCOS'] + return cflags + + # they go before object list + def linkflags(self): + linkflags = ['-fPIE', '-specs=%s/switch.specs' % self.libnx_dir] + # libsolder only supports sysv hashes and we need to build everything with -rdynamoc + linkflags += ['-rdynamic'] + # avoid pulling in and exposing mesa's internals, that crashes it for some god forsaken reason + linkflags += ['-Wl,--exclude-libs=libglapi.a', '-Wl,--exclude-libs=libdrm_nouveau.a'] + return linkflags + + def ldflags(self): + ldflags = [] + return ldflags + def options(opt): android = opt.add_option_group('Android options') android.add_option('--android', action='store', dest='ANDROID_OPTS', default=None, @@ -357,6 +436,10 @@ def options(opt): magx.add_option('--enable-magx', action = 'store_true', dest = 'MAGX', default = False, help = 'enable targetting for MotoMAGX phones [default: %default]') + switch = opt.add_option_group('Nintendo Switch options') + switch.add_option('--nswitch', action='store_true', dest='NSWITCH', default = False, + help ='enable building for Nintendo Switch [default: %default]') + def configure(conf): if conf.options.ANDROID_OPTS: values = conf.options.ANDROID_OPTS.split(',') @@ -399,8 +482,25 @@ def configure(conf): conf.env.LIBPATH_MAGX = [toolchain_path + i for i in ['ezx-z6/lib', 'qt-2.3.8/lib']] conf.env.LINKFLAGS_MAGX = ['-Wl,-rpath-link=' + i for i in conf.env.LIBPATH_MAGX] + for lib in ['qte-mt', 'ezxappbase', 'ezxpm', 'log_util']: + conf.check_cc(lib=lib, use='MAGX', uselib_store='MAGX') + elif conf.options.NSWITCH: + conf.nswitch = nswitch = NintendoSwitch(conf) + conf.environ['CC'] = nswitch.cc() + conf.environ['CXX'] = nswitch.cxx() + conf.environ['STRIP'] = nswitch.strip() + conf.env.PKGCONFIG = nswitch.pkgconfig() + conf.env.CFLAGS += nswitch.cflags() + conf.env.CXXFLAGS += nswitch.cflags(True) + conf.env.LINKFLAGS += nswitch.linkflags() + conf.env.LDFLAGS += nswitch.ldflags() + conf.env.HAVE_M = True + conf.env.LIB_M = ['m'] + conf.env.PREFIX = '' + conf.env.DEST_OS = 'nswitch' + conf.env.MAGX = conf.options.MAGX - MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android' }) + MACRO_TO_DESTOS = OrderedDict({ '__ANDROID__' : 'android', '__SWITCH__' : 'nswitch' }) for k in c_config.MACRO_TO_DESTOS: MACRO_TO_DESTOS[k] = c_config.MACRO_TO_DESTOS[k] # ordering is important c_config.MACRO_TO_DESTOS = MACRO_TO_DESTOS diff --git a/wscript b/wscript index 2254efa4..aaeed804 100644 --- a/wscript +++ b/wscript @@ -155,6 +155,12 @@ def configure(conf): conf.options.NANOGL = True conf.options.GLWES = True conf.options.GL = False + elif conf.env.DEST_OS == 'nswitch': + conf.options.NO_VGUI = True + conf.options.GL = True + conf.options.SINGLE_BINARY = True + conf.options.NO_ASYNC_RESOLVE = True + conf.options.USE_STBTT = True elif conf.env.MAGX: conf.options.USE_SELECT = True conf.options.SDL12 = True @@ -227,6 +233,14 @@ def configure(conf): cflags, linkflags = conf.get_optimization_flags() + # on the Switch, allow undefined symbols by default, which is needed for libsolder to work + # we'll specifically disallow for the engine executable + # additionally, shared libs are linked without libc + if conf.env.DEST_OS == 'nswitch': + linkflags.remove('-Wl,--no-undefined') + conf.env.append_unique('LINKFLAGS_cshlib', ['-nostdlib', '-nostartfiles']) + conf.env.append_unique('LINKFLAGS_cxxshlib', ['-nostdlib', '-nostartfiles']) + # And here C++ flags starts to be treated separately cxxflags = list(cflags) if conf.env.COMPILER_CC != 'msvc' and not conf.options.DISABLE_WERROR: @@ -322,7 +336,7 @@ def configure(conf): conf.env.LIBDIR = conf.env.BINDIR = '${PREFIX}/lib/xash3d' conf.env.SHAREDIR = '${PREFIX}/share/xash3d' else: - if sys.platform != 'win32' and not conf.env.DEST_OS == 'android': + if sys.platform != 'win32' and conf.env.DEST_OS not in ['android', 'nswitch']: conf.env.PREFIX = '/' conf.env.SHAREDIR = conf.env.LIBDIR = conf.env.BINDIR = conf.env.PREFIX