Switch homebrew port (#173)

* Initial port

* Switch specific video modes

* Handle clean exit

* Hide option to disable gamepads, force it enabled

* Match buttons layout with Switch controllers

* Hide player name setting (to avoid getting stuck)

* Switch specific VFS setup instead of env bootstrap

* Clean up, get rid of warnings and a few ifdefs

* Add Switch specific build script and assets

* Make vfs setup mount packages

* Re-enable packaging on Switch

* Transpile shaders to es and install them

* Add applet warning and shader deps to README

* Remove script; instead using meson build scripts

* Strict prototypes

* Build script compat with project minimum meson ver

Refactor of pack.py exclude option

* Uniformise header inclusion on arch_switch.c

* Allow input for any dev; hide the option on Switch

* Silence unsused function warnings
This commit is contained in:
Samuel P 2019-08-02 20:38:33 +02:00 committed by Andrei Alexeyev
parent c6c83a7c03
commit 469d6e2f48
24 changed files with 477 additions and 46 deletions

View file

@ -117,7 +117,7 @@ if sm_check.stderr() != ''
warning('Submodule check completed with errors:\n@0@'.format(sm_check.stderr()))
endif
static = get_option('static') or (host_machine.system() == 'emscripten')
static = get_option('static') or ['emscripten', 'nx'].contains(host_machine.system())
dep_freetype = dependency('freetype2', required : true, static : static, fallback : ['freetype', 'freetype_dep'])
dep_opusfile = dependency('opusfile', required : false, static : static, fallback : ['opusfile', 'opusfile_dep'])
@ -197,6 +197,7 @@ prefer_relpath_systems = [
force_relpath_systems = [
'emscripten',
'nx'
]
if macos_app_bundle
@ -268,7 +269,7 @@ config.set('TAISEI_BUILDCONF_LOG_FATAL_MSGBOX', (
))
config.set('TAISEI_BUILDCONF_DEBUG_OPENGL', get_option('debug_opengl'))
install_docs = get_option('docs') and host_machine.system() != 'emscripten'
install_docs = get_option('docs') and not ['emscripten', 'nx'].contains(host_machine.system())
if host_machine.system() == 'windows'
if install_docs
@ -310,6 +311,7 @@ bindist_deps = []
subdir('misc')
subdir('emscripten')
subdir('switch')
subdir('external')
subdir('resources')
subdir('doc')
@ -363,7 +365,7 @@ Summary:
', '.join(enabled_audio_backends),
get_option('a_default'),
', '.join(enabled_renderers),
get_option('r_default'),
default_renderer,
get_option('shader_transpiler'),
enable_zip,
package_data,

View file

@ -140,7 +140,7 @@ spvc_vert_args = [
'--stage', 'vert',
]
if host_machine.system() == 'emscripten'
if ['emscripten', 'nx'].contains(host_machine.system())
validate_glsl = 'true'
transpile_glsl = true
else

View file

@ -81,6 +81,7 @@ foreach pkg : packages
pkg_path,
'@OUTPUT@',
'--depfile', '@DEPFILE@',
'--exclude', '**/meson.build',
],
output : pkg_zip,
depfile : '@0@.d'.format(pkg_zip),
@ -94,6 +95,31 @@ foreach pkg : packages
endif
endforeach
if host_machine.system() == 'nx'
# Package shaders that were transpiled
shader_pkg_zip = '01-es-shaders.zip'
shader_pkg_path = join_paths(shaders_build_dir, '..')
if package_data
bindist_deps += custom_target(shader_pkg_zip,
command : [pack_command,
shader_pkg_path,
'@OUTPUT@',
'--depfile', '@DEPFILE@',
'--exclude', '**/*.spv',
'--exclude', '**/meson.build',
],
output : shader_pkg_zip,
depfile : '@0@.d'.format(shader_pkg_zip),
install : true,
install_dir : data_path,
)
else
glob_result = run_command(glob_command, shaders_build_dir, '**/*.spv', '**/meson.build')
assert(glob_result.returncode() == 0, 'Glob script failed')
install_subdir(shaders_build_dir, install_dir : data_path, exclude_files : glob_result.stdout().split('\n'))
endif
endif
if host_machine.system() == 'emscripten'
# First add some stuff that isn't sourced from resources/

View file

@ -24,7 +24,6 @@ from taiseilib.common import (
write_depfile,
)
def pack(args):
nocompress_file = args.directory / '.nocompress'
@ -40,7 +39,7 @@ def pack(args):
with ZipFile(str(args.output), 'w', ZIP_DEFLATED, **zkwargs) as zf:
for path in sorted(args.directory.glob('**/*')):
if path.name[0] == '.' or path.name == 'meson.build':
if path.name[0] == '.' or any(path.match(x) for x in args.exclude):
continue
relpath = path.relative_to(args.directory)
@ -82,6 +81,12 @@ def main(args):
help='the output archive path'
)
parser.add_argument('--exclude',
action='append',
default=[],
help='file exclusion pattern'
)
add_common_args(parser, depfile=True)
args = parser.parse_args(args[1:])

107
src/arch_switch.c Normal file
View file

@ -0,0 +1,107 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2019, p-sam <p-sam@d3vs.net>.
*/
#include "taisei.h"
#include "arch_switch.h"
#include <switch/runtime/devices/socket.h>
#include <switch/runtime/nxlink.h>
#include <switch/services/applet.h>
#include <switch/services/fs.h>
#define NX_LOG_FMT(fmt, ...) tsfprintf(stdout, "[NX] " fmt "\n", ##__VA_ARGS__)
#define NX_LOG(str) NX_LOG_FMT("%s", str)
#define NX_SETENV(name, val) NX_LOG_FMT("Setting env var %s to %s", name, val);env_set_string(name, val, true)
static nxAtExitFn g_nxAtExitFn = NULL;
static char g_programDir[FS_MAX_PATH] = {0};
static AppletHookCookie g_hookCookie;
static void onAppletHook(AppletHookType hook, void *param) {
switch (hook) {
case AppletHookType_OnExitRequest:
NX_LOG("Got AppletHook OnExitRequest, exiting.\n");
taisei_quit();
break;
default:
break;
}
}
attr_used
void userAppInit(void) {
socketInitializeDefault();
appletLockExit();
appletHook(&g_hookCookie, onAppletHook, NULL);
#ifdef DEBUG
dup2(1, 2);
NX_LOG("stderr -> stdout");
nxlinkStdio();
NX_LOG("nxlink enabled");
NX_SETENV("TAISEI_NOASYNC", "1");
#endif
appletInitializeGamePlayRecording();
appletSetGamePlayRecordingState(1);
getcwd(g_programDir, FS_MAX_PATH);
#if defined(DEBUG) && defined(TAISEI_BUILDCONF_DEBUG_OPENGL)
// enable Mesa logging:
NX_SETENV("EGL_LOG_LEVEL", "debug");
NX_SETENV("MESA_VERBOSE", "all");
NX_SETENV("MESA_DEBUG", "1");
NX_SETENV("NOUVEAU_MESA_DEBUG", "1");
// enable shader debugging in Nouveau:
NX_SETENV("NV50_PROG_OPTIMIZE", "0");
NX_SETENV("NV50_PROG_DEBUG", "1");
NX_SETENV("NV50_PROG_CHIPSET", "0x120");
#else
// disable error checking and save CPU time
NX_SETENV("MESA_NO_ERROR", "1");
#endif
}
attr_used
void userAppExit(void) {
if(g_nxAtExitFn != NULL) {
NX_LOG("calling exit callback");
g_nxAtExitFn();
g_nxAtExitFn = NULL;
}
socketExit();
appletUnlockExit();
}
int nxAtExit(nxAtExitFn fn) {
if(g_nxAtExitFn == NULL) {
NX_LOG("got exit callback");
g_nxAtExitFn = fn;
return 0;
}
return -1;
}
void __attribute__((weak)) noreturn __libnx_exit(int rc);
void noreturn nxExit(int rc) {
__libnx_exit(rc);
}
void noreturn nxAbort(void) {
/* Using abort would not give us correct offsets in crash reports,
* nor code region name, so we use __builtin_trap instead */
__builtin_trap();
}
const char* nxGetProgramDir(void) {
return g_programDir;
}

22
src/arch_switch.h Normal file
View file

@ -0,0 +1,22 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2019, p-sam <p-sam@d3vs.net>.
*/
#ifndef IGUARD_arch_switch_h
#define IGUARD_arch_switch_h
#include "taisei.h"
typedef void (*nxAtExitFn)(void);
void userAppInit(void);
void userAppExit(void);
int nxAtExit(nxAtExitFn fn);
void noreturn nxExit(int rc);
void noreturn nxAbort(void);
const char* nxGetProgramDir(void);
#endif // IGUARD_arch_switch_h

View file

@ -476,4 +476,9 @@ void config_load(void) {
// set config version to the latest
config_set_int(CONFIG_VERSION, sizeof(config_upgrades) / sizeof(ConfigUpgradeFunc));
#ifdef __SWITCH__
config_set_int(CONFIG_GAMEPAD_ENABLED, true);
config_set_str(CONFIG_GAMEPAD_DEVICE, "any");
#endif
}

View file

@ -44,10 +44,17 @@ typedef enum GamepadEmulatedButton {
typedef enum GamepadButton {
// must match SDL_GameControllerButton
GAMEPAD_BUTTON_INVALID = -1,
#ifdef __SWITCH__
GAMEPAD_BUTTON_B,
GAMEPAD_BUTTON_A,
GAMEPAD_BUTTON_Y,
GAMEPAD_BUTTON_X,
#else
GAMEPAD_BUTTON_A,
GAMEPAD_BUTTON_B,
GAMEPAD_BUTTON_X,
GAMEPAD_BUTTON_Y,
#endif
GAMEPAD_BUTTON_BACK,
GAMEPAD_BUTTON_GUIDE,
GAMEPAD_BUTTON_START,

View file

@ -50,8 +50,13 @@
enum {
// defaults
#ifdef __SWITCH__
RESX = 1280,
RESY = 720,
#else
RESX = 800,
RESY = 600,
#endif
VIEWPORT_X = 40,
VIEWPORT_Y = 20,

View file

@ -91,8 +91,9 @@ noreturn static void log_abort(const char *msg) {
}
#endif
// abort() doesn't clean up, but it lets us get a backtrace, which is more useful
log_shutdown();
// abort() doesn't clean up, but it lets us get a backtrace, which is more useful
abort();
}

View file

@ -164,6 +164,7 @@ static int bind_gpdev_set(OptionBinding *b, int v) {
return b->selected;
}
#ifndef __SWITCH__
// BT_GamepadDevice: dynamic device list
static OptionBinding* bind_gpdevice(int cfgentry) {
OptionBinding *bind = bind_new();
@ -191,6 +192,7 @@ static OptionBinding* bind_stroption(ConfigIndex cfgentry) {
return bind;
}
#endif
// BT_Resolution: super-special binding type for the resolution setting
static void bind_resolution_update(OptionBinding *bind) {
@ -590,9 +592,11 @@ static void bind_setvaluerange_fancy(OptionBinding *b, int ma) {
}
}
#ifndef __SWITCH__
static bool gamepad_enabled_depencence(void) {
return config_get_int(CONFIG_GAMEPAD_ENABLED);
}
#endif
static MenuData* create_options_menu_gamepad_controls(MenuData *parent) {
MenuData *m = create_options_menu_base("Gamepad Controls");
@ -662,6 +666,7 @@ static MenuData* create_options_menu_gamepad(MenuData *parent) {
OptionBinding *b;
#ifndef __SWITCH__
add_menu_entry(m, "Enable Gamepad/Joystick support", do_nothing,
b = bind_option(CONFIG_GAMEPAD_ENABLED, bind_common_onoff_get, bind_common_onoff_set)
); bind_onoff(b);
@ -669,6 +674,7 @@ static MenuData* create_options_menu_gamepad(MenuData *parent) {
add_menu_entry(m, "Device", do_nothing,
b = bind_gpdevice(CONFIG_GAMEPAD_DEVICE)
); b->dependence = gamepad_enabled_depencence;
#endif
add_menu_separator(m);
add_menu_entry(m, "Customize controls…", enter_options_menu_gamepad_controls, NULL);
@ -811,11 +817,13 @@ MenuData* create_options_menu(void) {
MenuData *m = create_options_menu_base("Options");
OptionBinding *b;
#ifndef __SWITCH__
add_menu_entry(m, "Player name", do_nothing,
b = bind_stroption(CONFIG_PLAYERNAME)
);
add_menu_separator(m);
#endif
add_menu_entry(m, "Save replays", do_nothing,
b = bind_option(CONFIG_SAVE_RPY, bind_common_onoffplus_get, bind_common_onoffplus_set)

View file

@ -97,6 +97,12 @@ else
)
endif
if host_machine.system() == 'nx'
taisei_src += files(
'arch_switch.c',
)
endif
sse42_src = []
subdir('audio')
@ -242,6 +248,53 @@ if host_machine.system() == 'emscripten'
install : true,
install_dir : bindir,
)
elif host_machine.system() == 'nx'
taisei_elf_name = '@0@.elf'.format(taisei_basename)
taisei_elf = executable(taisei_elf_name, taisei_src, version_deps,
dependencies : taisei_deps,
c_args : taisei_c_args,
c_pch : 'pch/taisei_pch.h',
install : true,
install_dir : bindir,
override_options: ['strip=false'],
)
bindist_deps += taisei_elf
taisei_nacp_name = '@0@.nacp'.format(taisei_basename)
taisei_nacp = custom_target(taisei_nacp_name,
command : [
find_program('nacptool'),
'--create',
nx_app_title,
nx_app_author,
taisei_version_string,
'@OUTPUT@',
],
build_by_default : true,
install : false,
output : taisei_nacp_name,
)
taisei_nro_name = '@0@.nro'.format(taisei_basename)
taisei_nro = custom_target(taisei_nro_name,
# NOTE: Unfortunately we can't just put 'taisei_elf' directly into the command array.
# Meson then makes an invalid assumption that we are going to execute it ("use as a generator"),
# and aborts because there's no exe wrapper in the cross file (which wouldn't make sense to have).
command : [
find_program('elf2nro'),
taisei_elf.full_path(), # workaround for the above issue
'@OUTPUT@',
'--nacp=@0@'.format(taisei_nacp.full_path()), # if we could pass the path in a standalone argument, we could have meson generate an implicit dependency here...
'--icon=@0@'.format(nx_icon_path),
],
build_by_default : true,
depends : [taisei_elf, taisei_nacp],
install : true,
install_dir : bindir,
output : taisei_nro_name,
)
bindist_deps += taisei_nro
else
taisei = executable(taisei_basename, taisei_src, version_deps,
dependencies : taisei_deps,

View file

@ -1,8 +1,22 @@
modules = [
'gl33',
'gles20',
'gles30',
'null',
]
default_backend = get_option('r_default')
if host_machine.system() == 'nx'
has_forced_renderer = true
forced_renderer = 'gles30'
else
has_forced_renderer = false
forced_renderer = ''
endif
if not get_option('r_@0@'.format(default_backend))
error('Default renderer \'@0@\' is not enabled. Enable it with -Dr_@0@=true, or set r_default to something else.'.format(default_backend))
default_renderer = has_forced_renderer ? forced_renderer : get_option('r_default')
if not has_forced_renderer and not get_option('r_@0@'.format(default_renderer))
error('Default renderer \'@0@\' is not enabled. Enable it with -Dr_@0@=true, or set r_default to something else.'.format(default_renderer))
endif
renderer_src = files(
@ -21,19 +35,13 @@ subdir('glescommon')
subdir('gles20')
subdir('gles30')
modules = [
'gl33',
'gles20',
'gles30',
'null',
]
included_deps = []
needed_deps = ['common']
r_macro = []
foreach m : modules
if get_option('r_@0@'.format(m))
should_include = has_forced_renderer ? m == forced_renderer : get_option('r_@0@'.format(m))
if should_include
renderer_src += get_variable('r_@0@_src'.format(m))
r_macro += ['R(@0@)'.format(m)]
enabled_renderers += [m]
@ -53,4 +61,4 @@ endforeach
r_macro = ' '.join(r_macro)
config.set('TAISEI_BUILDCONF_RENDERER_BACKENDS', r_macro)
config.set_quoted('TAISEI_BUILDCONF_RENDERER_DEFAULT', default_backend)
config.set_quoted('TAISEI_BUILDCONF_RENDERER_DEFAULT', default_renderer)

View file

@ -278,4 +278,11 @@ typedef complex max_align_t;
#define CASTPTR_ASSUME_ALIGNED(expr, type) ((type*)ASSUME_ALIGNED((expr), alignof(type)))
#ifdef __SWITCH__
#include "../arch_switch.h"
#define atexit nxAtExit
#define exit nxExit
#define abort nxAbort
#endif
#endif // IGUARD_util_compat_h

View file

@ -37,6 +37,11 @@ if host_machine.system() == 'emscripten'
'setup_emscripten.c',
'sync_emscripten.c',
)
elif host_machine.system() == 'nx'
vfs_src += files(
'setup_switch.c',
'sync_noop.c',
)
else
vfs_src += files(
'setup_generic.c',

View file

@ -4,6 +4,7 @@
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
* Copyright (c) 2019, p-sam <p-sam@d3vs.net>.
*/
#ifndef IGUARD_vfs_setup_h
@ -12,7 +13,50 @@
#include "taisei.h"
#include "public.h"
#include "loadpacks.h"
typedef struct VfsSetupFixedPaths {
const char* res_path;
const char* storage_path;
const char* cache_path;
} VfsSetupFixedPaths;
void vfs_setup(CallChain onready);
static inline void vfs_setup_fixedpaths_onsync(CallChainResult ccr, VfsSetupFixedPaths* paths) {
assume(paths != NULL);
assume(paths->res_path != NULL);
assume(paths->storage_path != NULL);
assume(paths->cache_path != NULL);
log_info("Resource path: %s", paths->res_path);
log_info("Storage path: %s", paths->storage_path);
log_info("Cache path: %s", paths->cache_path);
vfs_create_union_mountpoint("/res");
if(!vfs_mount_syspath("/res-dir", paths->res_path, VFS_SYSPATH_MOUNT_READONLY)) {
log_fatal("Failed to mount '%s': %s", paths->res_path, vfs_get_error());
}
if(!vfs_mount_syspath("/storage", paths->storage_path, VFS_SYSPATH_MOUNT_MKDIR)) {
log_fatal("Failed to mount '%s': %s", paths->storage_path, vfs_get_error());
}
if(!vfs_mount_syspath("/cache", paths->cache_path, VFS_SYSPATH_MOUNT_MKDIR)) {
log_fatal("Failed to mount '%s': %s", paths->cache_path, vfs_get_error());
}
vfs_load_packages("/res-dir", "/res");
vfs_mount_alias("/res", "/res-dir");
vfs_unmount("/res-dir");
vfs_mkdir_required("storage/replays");
vfs_mkdir_required("storage/screenshots");
CallChain *next = ccr.ctx;
run_call_chain(next, NULL);
free(next);
}
#endif // IGUARD_vfs_setup_h

View file

@ -4,6 +4,7 @@
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
* Copyright (c) 2019, p-sam <p-sam@d3vs.net>.
*/
#include "taisei.h"
@ -13,32 +14,13 @@
#include "util.h"
static void vfs_setup_onsync(CallChainResult ccr) {
const char *res_path = "/" TAISEI_BUILDCONF_DATA_PATH;
const char *storage_path = "/persistent/storage";
const char *cache_path = "/persistent/cache";
VfsSetupFixedPaths paths = {
.res_path = "/" TAISEI_BUILDCONF_DATA_PATH,
.storage_path = "/persistent/storage",
.cache_path ="/persistent/cache",
};
log_info("Resource path: %s", res_path);
log_info("Storage path: %s", storage_path);
log_info("Cache path: %s", cache_path);
if(!vfs_mount_syspath("/res", res_path, VFS_SYSPATH_MOUNT_READONLY)) {
log_fatal("Failed to mount '%s': %s", res_path, vfs_get_error());
}
if(!vfs_mount_syspath("/storage", storage_path, VFS_SYSPATH_MOUNT_MKDIR)) {
log_fatal("Failed to mount '%s': %s", storage_path, vfs_get_error());
}
if(!vfs_mount_syspath("/cache", cache_path, VFS_SYSPATH_MOUNT_MKDIR)) {
log_fatal("Failed to mount '%s': %s", cache_path, vfs_get_error());
}
vfs_mkdir_required("storage/replays");
vfs_mkdir_required("storage/screenshots");
CallChain *next = ccr.ctx;
run_call_chain(next, NULL);
free(next);
vfs_setup_fixedpaths_onsync(ccr, &paths);
}
void vfs_setup(CallChain next) {

39
src/vfs/setup_switch.c Normal file
View file

@ -0,0 +1,39 @@
/*
* This software is licensed under the terms of the MIT-License
* See COPYING for further information.
* ---
* Copyright (c) 2011-2019, Lukas Weber <laochailan@web.de>.
* Copyright (c) 2012-2019, Andrei Alexeyev <akari@taisei-project.org>.
* Copyright (c) 2019, p-sam <p-sam@d3vs.net>.
*/
#include "taisei.h"
#include "public.h"
#include "setup.h"
#include "util.h"
static void vfs_setup_onsync(CallChainResult ccr) {
const char *program_dir = nxGetProgramDir();
char *res_path = strfmt("%s/%s", program_dir, TAISEI_BUILDCONF_DATA_PATH);
char *storage_path = strfmt("%s/storage", program_dir);
char *cache_path = strfmt("%s/cache", program_dir);
VfsSetupFixedPaths paths = {
.res_path = res_path,
.storage_path = storage_path,
.cache_path = cache_path,
};
vfs_setup_fixedpaths_onsync(ccr, &paths);
free(res_path);
free(storage_path);
free(cache_path);
}
void vfs_setup(CallChain next) {
vfs_init();
CallChain *cc = memdup(&next, sizeof(next));
vfs_sync(VFS_SYNC_LOAD, CALLCHAIN(vfs_setup_onsync, cc));
}

View file

@ -66,6 +66,7 @@ static VideoCapabilityState video_query_capability_alwaysfullscreen(VideoCapabil
UNREACHABLE;
}
#ifndef __SWITCH__
static VideoCapabilityState video_query_capability_webcanvas(VideoCapability cap) {
switch(cap) {
case VIDEO_CAP_EXTERNAL_RESIZE:
@ -78,6 +79,7 @@ static VideoCapabilityState video_query_capability_webcanvas(VideoCapability cap
return video_query_capability_generic(cap);
}
}
#endif
static void video_add_mode(int width, int height) {
if(video.modes) {
@ -622,6 +624,10 @@ void video_init(void) {
const char *driver = SDL_GetCurrentVideoDriver();
log_info("Using driver '%s'", driver);
#ifdef __SWITCH__
video.backend = VIDEO_BACKEND_SWITCH;
video_query_capability = video_query_capability_alwaysfullscreen;
#else
video_query_capability = video_query_capability_generic;
if(!strcmp(driver, "x11")) {
@ -642,11 +648,12 @@ void video_init(void) {
} else {
video.backend = VIDEO_BACKEND_OTHER;
}
#endif
r_init();
// Register all resolutions that are available in fullscreen
#ifndef __SWITCH__
for(int s = 0; s < video_num_displays(); ++s) {
log_info("Found display #%i: %s", s, video_display_name(s));
for(int i = 0; i < SDL_GetNumDisplayModes(s); ++i) {
@ -660,6 +667,7 @@ void video_init(void) {
}
}
}
#endif
if(!fullscreen_available) {
log_warn("No available fullscreen modes");
@ -669,6 +677,11 @@ void video_init(void) {
// Then, add some common 4:3 modes for the windowed mode if they are not there yet.
// This is required for some multihead setups.
VideoMode common_modes[] = {
#ifdef __SWITCH__
{640, 480},
{1280, 720},
{1920, 1080},
#else
{RESX, RESY},
{SCREEN_W, SCREEN_H},
@ -679,7 +692,7 @@ void video_init(void) {
{1152, 864},
{1400, 1050},
{1440, 1080},
#endif
{0, 0},
};

View file

@ -41,6 +41,7 @@ typedef enum VideoBackend {
VIDEO_BACKEND_EMSCRIPTEN,
VIDEO_BACKEND_KMSDRM,
VIDEO_BACKEND_RPI,
VIDEO_BACKEND_SWITCH,
} VideoBackend;
typedef struct {

43
switch/README.md Normal file
View file

@ -0,0 +1,43 @@
Taisei Switch Port
==================
<p align="center"><img src="icon.jpg"></p>
## Installation
### Grabbing Binaries
Download the latest release,
and extract the archive in the `/switch` folder on your SD Card.
Then, run the game from the [hbmenu](https://github.com/switchbrew/nx-hbmenu)
using [hbl](https://github.com/switchbrew/nx-hbloader).
**WARNING:** This will crash if executed from an applet such as the Photo/Library applet,
be sure to launch it from hbmenu on top of the game of your choice,
which can be done by holding R over any installed title on latest Atmosphère, with default settings.
### Build dependencies
For building, you need the devkitA64 from devkitPro setup, along with switch portlibs and libnx.
Documentation to setup that can be found [here](https://switchbrew.org/wiki/Setting_up_Development_Environment).
Other dependencies common to the main targets include:
* meson >= 0.45.0 (build system; >=0.48.0 recommended)
* Python >= 3.5
* ninja
* glslc
* spirv-cross
### Compiling from source
Run one of the following commands from the project root:
```
mkdir -p ./build/nx
./switch/crossfile.sh > ./build/nx/crossfile.txt
meson --cross-file="./build/nx/crossfile.txt" . ./build/nx
ninja -C ./build/nx
```
**Note:** You can optionally set a custom prefix and `ninja install` NRO and assets into that folder.

44
switch/crossfile.sh Executable file
View file

@ -0,0 +1,44 @@
#!/bin/bash
set -eo pipefail
source "$DEVKITPRO/switchvars.sh"
function meson_arg_list() {
while (( "$#" )); do
echo -n "'$1'";
if [ $# -gt 1 ]; then
echo -n ",";
fi
shift
done
}
function bin_path() {
which "${TOOL_PREFIX}$1"
}
ADDITIONAL_LINK_FLAGS="-specs=$DEVKITPRO/libnx/switch.specs"
cat <<DOCEND
[binaries]
c = '$(bin_path gcc)'
cpp = '$(bin_path g++)'
ar = '$(bin_path gcc-ar)'
strip = '$(bin_path strip)'
pkgconfig = '$(bin_path pkg-config)'
elf2nro = '$(which elf2nro)'
nacptool = '$(which nacptool)'
[properties]
c_args = [$(meson_arg_list $CPPFLAGS $CFLAGS)]
c_link_args = [$(meson_arg_list $LDFLAGS $LIBS $ADDITIONAL_LINK_FLAGS)]
cpp_args = [$(meson_arg_list $CPPFLAGS $CXXFLAGS)]
cpp_link_args = [$(meson_arg_list $LDFLAGS $LIBS $ADDITIONAL_LINK_FLAGS)]
[host_machine]
system = 'nx'
cpu_family = 'aarch64'
cpu = 'cortex-a57'
endian = 'little'
DOCEND

BIN
switch/icon.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

4
switch/meson.build Normal file
View file

@ -0,0 +1,4 @@
nx_app_title = 'Taisei Project'
nx_app_author = 'Taisei Project Team, port by p-sam'
nx_icon_path = join_paths(meson.current_source_dir(), 'icon.jpg')