Files
RetroArch/frontend/drivers/platform_switch.c
T
LibretroAdmin b9be38fd55 strl: copy string literals without scanning them at runtime
strlcpy() begins with strlen() of its source. It lives in another
translation unit, so with no LTO that scan happens at runtime even when
the source is a literal whose length the compiler already measured, and
the call itself cannot be folded away either.

strlcpy_lit() passes the length over as sizeof(). When the destination
size is also a constant -- sizeof(buf), the usual case -- nothing is
left but the stores memcpy() compiles to:

  strlcpy      "core_options"  sizeof(buf)   7.01 ns
  strlcpy_lit  "core_options"  sizeof(buf)   0.42 ns
  strlcpy      41-char literal sizeof(buf)   7.74 ns
  strlcpy_lit  41-char literal sizeof(buf)   0.71 ns
  strlcpy      "core_options"  runtime len   7.11 ns
  strlcpy_lit  "core_options"  runtime len   0.70 ns

The destination bound is kept. When the literal does not fit it defers
to strlcpy(), so the copy truncates as before rather than overrunning;
a form that took only the literal would have turned every one of these
sites into an overflow waiting for a small buffer. The literal is
concatenated with "" so a non-literal source is a compile error rather
than a sizeof() that silently measures a pointer.

818 sites across 104 files were converted by parsing arguments rather
than by pattern-matching text: only a three-argument call whose second
argument is a bare string literal is rewritten, so a macro that happens
to expand to a literal is left alone. Line endings are preserved, so
the CRLF files carry only their own changed lines.

test/compat/test_strl.c compares the two against each other over every
destination size from 0 past the literal's length, for literals that
are empty, single-character, path-shaped, adjacently concatenated and
escape-bearing, checking the return, the terminator and the whole
buffer so an overrun past the terminator is caught. ASan+UBSan+LSan and
TSan both 4/4. The full menu_displaylist_build_list walk over every
displaylist type is byte-identical to before the change
(md5 a2a081bcac09cdc8d72d67efd14ac40e).
2026-08-30 02:10:20 +02:00

839 lines
23 KiB
C

/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch 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 Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <boolean.h>
#include <sys/stat.h>
#include <dirent.h>
#include <formats/image.h>
#ifdef HAVE_LIBNX
#include <switch.h>
#include "../../switch_performance_profiles.h"
#include "../../configuration.h"
#include <unistd.h>
#include <malloc.h>
#else
#include <libtransistor/nx.h>
#include <libtransistor/ipc_helpers.h>
#endif
#include <file/file_path.h>
#ifdef HAVE_CONFIG_H
#include "../../config.h"
#endif
#ifndef IS_SALAMANDER
#include <lists/file_list.h>
#endif
#include <string/stdstring.h>
#include "../frontend_driver.h"
#include "../../defaults.h"
#include "../../file_path_special.h"
#include "../../paths.h"
#include "../../retroarch.h"
#include "../../verbosity.h"
#ifndef IS_SALAMANDER
#ifdef HAVE_MENU
#include "../../menu/menu_driver.h"
#endif
#if defined(HAVE_LIBNX) && defined(HAVE_NETWORKING)
#include "../../network/netplay/netplay.h"
#endif
#endif
#ifdef HAVE_LIBNX
#define SD_PREFIX
#include "../../gfx/common/switch_defines.h"
#else
#define SD_PREFIX "/sd"
#endif
static enum frontend_fork switch_fork_mode = FRONTEND_FORK_NONE;
bool platform_switch_has_focus = true;
#ifdef HAVE_LIBNX
char *SWITCH_CPU_PROFILES[] = {
"Maximum Performance",
"High Performance",
"Boost Performance",
"Stock Performance",
"Powersaving Mode 1",
"Powersaving Mode 2",
"Powersaving Mode 3",
NULL
};
char *SWITCH_CPU_SPEEDS[] = {
"1785 MHz",
"1581 MHz",
"1224 MHz",
"1020 MHz",
"918 MHz",
"816 MHz",
"714 MHz",
NULL
};
unsigned SWITCH_CPU_SPEEDS_VALUES[] = {
1785000000,
1581000000,
1224000000,
1020000000,
918000000,
816000000,
714000000,
0
};
static bool psmInitialized = false;
static AppletHookCookie applet_hook_cookie;
#ifdef NXLINK
extern bool nxlink_connected;
#endif
void libnx_apply_overclock(void)
{
const size_t profiles_count = sizeof(SWITCH_CPU_PROFILES)
/ sizeof(SWITCH_CPU_PROFILES[1]) - 1;
settings_t *settings = config_get_ptr();
unsigned libnx_overclock = settings->uints.libnx_overclock;
if (libnx_overclock >= 0 && libnx_overclock <= profiles_count)
{
if (hosversionBefore(8, 0, 0))
{
pcvSetClockRate(PcvModule_CpuBus, SWITCH_CPU_SPEEDS_VALUES[
libnx_overclock]);
}
else
{
ClkrstSession session = {0};
clkrstOpenSession(&session, PcvModuleId_CpuBus, 3);
clkrstSetClockRate(&session, SWITCH_CPU_SPEEDS_VALUES[libnx_overclock]);
clkrstCloseSession(&session);
}
}
}
static void on_applet_hook(AppletHookType hook, void *param)
{
AppletFocusState focus_state;
/* Exit request */
switch (hook)
{
case AppletHookType_OnExitRequest:
retroarch_main_quit();
break;
/* Focus state*/
case AppletHookType_OnFocusState:
focus_state = appletGetFocusState();
platform_switch_has_focus = focus_state == AppletFocusState_InFocus;
if (!platform_switch_has_focus)
{
if (hosversionBefore(8, 0, 0))
{
pcvSetClockRate(PcvModule_CpuBus, 1020000000);
}
else
{
ClkrstSession session = {0};
clkrstOpenSession(&session, PcvModuleId_CpuBus, 3);
clkrstSetClockRate(&session, 1020000000);
clkrstCloseSession(&session);
}
}
else
libnx_apply_overclock();
break;
/* Performance mode */
case AppletHookType_OnPerformanceMode:
libnx_apply_overclock();
break;
default:
break;
}
}
#endif /* HAVE_LIBNX */
#ifdef IS_SALAMANDER
static void get_first_valid_core(char *path_return, size_t len)
{
DIR *dir;
struct dirent *ent;
const char *extension = ".nro";
path_return[0] = '\0';
if ((dir = opendir(SD_PREFIX "/retroarch/cores")))
{
size_t ext_len = strlen(extension);
while ((ent = readdir(dir)))
{
size_t name_len;
if (!ent)
break;
name_len = strlen(ent->d_name);
if ( name_len > ext_len
&& !strcmp(ent->d_name + name_len - ext_len, extension))
{
size_t _len = strlcpy(path_return, SD_PREFIX "/retroarch/cores", len);
_len += strlcpy_lit(path_return + _len,
"/",
len - _len);
strlcpy(path_return + _len, ent->d_name, len - _len);
break;
}
}
closedir(dir);
}
}
#endif
static void frontend_switch_get_env(
int *argc, char *argv[], void *args, void *params_data)
{
unsigned i;
#ifndef IS_SALAMANDER
#if defined(HAVE_LOGGER)
logger_init();
#elif defined(HAVE_FILE_LOGGER)
retro_main_log_file_init(SD_PREFIX "/retroarch-log.txt");
#endif
#endif
fill_pathname_basedir(g_defaults.dirs[DEFAULT_DIR_PORT], SD_PREFIX "/retroarch/retroarch_switch.nro", sizeof(g_defaults.dirs[DEFAULT_DIR_PORT]));
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_PORT],
"info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG], g_defaults.dirs[DEFAULT_DIR_PORT],
"autoconfig", sizeof(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_ASSETS], g_defaults.dirs[DEFAULT_DIR_PORT],
"assets", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS]));
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_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_MENU_CONFIG],
"remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_PLAYLIST], g_defaults.dirs[DEFAULT_DIR_PORT],
"playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_PLAYLIST]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG], g_defaults.dirs[DEFAULT_DIR_PORT],
"records_config", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT], g_defaults.dirs[DEFAULT_DIR_PORT],
"records", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT]));
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_VIDEO_FILTER], g_defaults.dirs[DEFAULT_DIR_PORT],
"filters", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER], g_defaults.dirs[DEFAULT_DIR_PORT],
"shaders", sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CHEATS], g_defaults.dirs[DEFAULT_DIR_PORT],
"cheats", sizeof(g_defaults.dirs[DEFAULT_DIR_CHEATS]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_OVERLAY], g_defaults.dirs[DEFAULT_DIR_PORT],
"overlays", sizeof(g_defaults.dirs[DEFAULT_DIR_OVERLAY]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_OSK_OVERLAY], g_defaults.dirs[DEFAULT_DIR_PORT],
"overlays/keyboards", sizeof(g_defaults.dirs[DEFAULT_DIR_OSK_OVERLAY]));
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_SCREENSHOT], g_defaults.dirs[DEFAULT_DIR_PORT],
"screenshots", sizeof(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS], g_defaults.dirs[DEFAULT_DIR_PORT],
"thumbnails", sizeof(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_LOGS], g_defaults.dirs[DEFAULT_DIR_PORT],
"logs", sizeof(g_defaults.dirs[DEFAULT_DIR_LOGS]));
fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CACHE], g_defaults.dirs[DEFAULT_DIR_PORT],
"temp", sizeof(g_defaults.dirs[DEFAULT_DIR_CACHE]));
for (i = 0; i < DEFAULT_DIR_LAST; i++)
{
const char *dir_path = g_defaults.dirs[i];
if (dir_path && *dir_path)
path_mkdir(dir_path);
}
fill_pathname_join(g_defaults.path_config,
g_defaults.dirs[DEFAULT_DIR_PORT],
FILE_PATH_MAIN_CONFIG,
sizeof(g_defaults.path_config));
#ifndef IS_SALAMANDER
dir_check_defaults("custom.ini");
#endif
}
static void frontend_switch_deinit(void *data)
{
(void)data;
#ifdef HAVE_LIBNX
nifmExit();
if (hosversionBefore(8, 0, 0))
{
pcvSetClockRate(PcvModule_CpuBus, 1020000000);
pcvExit();
}
else
{
ClkrstSession session = {0};
clkrstOpenSession(&session, PcvModuleId_CpuBus, 3);
clkrstSetClockRate(&session, 1020000000);
clkrstCloseSession(&session);
clkrstExit();
}
#if defined(SWITCH) && defined(NXLINK)
socketExit();
#endif
if (psmInitialized)
psmExit();
appletUnlockExit();
#endif
}
#ifdef HAVE_LIBNX
static void frontend_switch_exec(const char *path, bool should_load_game)
{
if (path && *path)
{
char args[PATH_MAX];
strlcpy(args, path, sizeof(args));
#ifndef IS_SALAMANDER
if (should_load_game)
{
const char *content = path_get(RARCH_PATH_CONTENT);
#ifdef HAVE_NETWORKING
char *arg_data[NETPLAY_FORK_MAX_ARGS];
if (netplay_driver_ctl(RARCH_NETPLAY_CTL_GET_FORK_ARGS,
(void*)arg_data))
{
char buf[PATH_MAX];
char **arg = arg_data;
do
{
snprintf(buf, sizeof(buf), " \"%s\"", *arg);
strlcat(args, buf, sizeof(args));
} while (*(++arg));
}
else
#endif
if (content && *content)
snprintf(args, sizeof(args), "%s \"%s\"", path, content);
}
#else
{
struct stat sbuff;
if (stat(path, &sbuff))
{
char core_path[PATH_MAX];
get_first_valid_core(core_path, sizeof(core_path));
if (!core_path || !*core_path)
svcExitProcess();
}
}
#endif
envSetNextLoad(path, args);
}
}
#ifndef IS_SALAMANDER
static bool frontend_switch_set_fork(enum frontend_fork fork_mode)
{
switch (fork_mode)
{
case FRONTEND_FORK_CORE:
switch_fork_mode = fork_mode;
break;
case FRONTEND_FORK_CORE_WITH_ARGS:
switch_fork_mode = fork_mode;
break;
case FRONTEND_FORK_RESTART:
/* 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, char *args)
{
bool should_load_content = false;
#ifndef IS_SALAMANDER
if (switch_fork_mode == FRONTEND_FORK_NONE)
return;
switch (switch_fork_mode)
{
case FRONTEND_FORK_CORE_WITH_ARGS:
should_load_content = true;
break;
default:
break;
}
#endif
frontend_switch_exec(s, should_load_content);
}
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp)
{
svcSleepThread(rqtp->tv_nsec + (rqtp->tv_sec * 1000000000));
return 0;
}
long sysconf(int name)
{
if (name == 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 = NULL;
const char *start, *end, *rpath_limit;
long int path_max;
/* 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. */
if (!name)
return NULL;
/* As per Single Unix Specification V2 we must return an error if
the name argument points to an empty string. */
if (name[0] == '\0')
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)
{
rpath = (char*)malloc(path_max);
if (!rpath)
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)
{
/* 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)
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)
free(rpath);
return NULL;
}
#endif /* HAVE_LIBNX */
static void frontend_switch_shutdown(bool unused)
{
(void)unused;
}
/* 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)
{
#ifdef HAVE_LIBNX
Result rc;
bool recording_supported = false;
nifmInitialize(NifmServiceType_User);
if (hosversionBefore(8, 0, 0))
pcvInitialize();
else
clkrstInitialize();
appletLockExit();
appletHook(&applet_hook_cookie, on_applet_hook, NULL);
appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend);
appletIsGamePlayRecordingSupported(&recording_supported);
if (recording_supported)
appletInitializeGamePlayRecording();
#ifdef NXLINK
socketInitializeDefault();
nxlink_connected = nxlinkStdio() != -1;
#ifndef IS_SALAMANDER
verbosity_enable();
#endif /* IS_SALAMANDER */
#endif /* NXLINK */
rc = psmInitialize();
if (R_SUCCEEDED(rc))
psmInitialized = true;
else
RARCH_WARN("Error initializing psm.\n");
#endif /* HAVE_LIBNX (splash) */
}
enum frontend_architecture frontend_switch_get_arch(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
: MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY;
if (!list)
return -1;
menu_entries_append(list,
"/", msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR),
enum_idx,
FILE_TYPE_DIRECTORY, 0, 0, NULL);
#endif
return 0;
}
static enum frontend_powerstate
frontend_switch_get_powerstate(int *seconds, int *percent)
{
uint32_t pct;
PsmChargerType ct;
Result rc;
if (!psmInitialized)
return FRONTEND_POWERSTATE_NONE;
rc = psmGetBatteryChargePercentage(&pct);
if (!R_SUCCEEDED(rc))
return FRONTEND_POWERSTATE_NONE;
rc = psmGetChargerType(&ct);
if (!R_SUCCEEDED(rc))
return FRONTEND_POWERSTATE_NONE;
*percent = (int)pct;
if (*percent >= 100)
return FRONTEND_POWERSTATE_CHARGED;
switch (ct)
{
case PsmChargerType_EnoughPower:
case PsmChargerType_LowPower:
return FRONTEND_POWERSTATE_CHARGING;
default:
break;
}
return FRONTEND_POWERSTATE_NO_SOURCE;
}
static size_t frontend_switch_get_os(
char *s, size_t len, int *major, int *minor)
{
size_t _len;
#ifdef HAVE_LIBNX
u32 hosVersion;
#else
int patch;
char firmware_version[0x100];
result_t r; /* used by LIB_ASSERT_OK macros */
ipc_object_t set_sys;
ipc_request_t rq;
#endif
_len = strlcpy_lit(s, "Horizon OS", len);
#ifdef HAVE_LIBNX
*major = 0;
*minor = 0;
hosVersion = hosversionGet();
*major = HOSVER_MAJOR(hosVersion);
*minor = HOSVER_MINOR(hosVersion);
#else
/* defaults in case we error out */
*major = 0;
*minor = 0;
LIB_ASSERT_OK(fail, sm_init());
LIB_ASSERT_OK(fail_sm, sm_get_service(&set_sys, "set:sys"));
rq = ipc_make_request(3);
ipc_buffer_t buffers[] = {
ipc_make_buffer(firmware_version, 0x100, 0x1a),
};
ipc_msg_set_buffers(rq, buffers, buffer_ptrs);
LIB_ASSERT_OK(fail_object, ipc_send(set_sys, &rq, &ipc_default_response_fmt));
/* Parse "<major>.<minor>.<patch>" without sscanf.
* Matches prior best-effort semantics: a field that fails to
* parse leaves itself and subsequent fields at their current
* values. */
{
const char *p = firmware_version + 0x68;
char *endp;
long v;
v = strtol(p, &endp, 10);
if (endp != p)
{
*major = (int)v;
if (*endp == '.')
{
p = endp + 1;
v = strtol(p, &endp, 10);
if (endp != p)
{
*minor = (int)v;
if (*endp == '.')
{
p = endp + 1;
v = strtol(p, &endp, 10);
if (endp != p)
patch = (int)v;
}
}
}
}
}
fail_object:
ipc_close(set_sys);
fail_sm:
sm_finalize();
fail:
#endif
return _len;
}
static void frontend_switch_get_name(char *s, size_t len)
{
/* TODO/FIXME: Add Mariko at some point */
strlcpy_lit(s, "Nintendo Switch", len);
}
void frontend_switch_process_args(int *argc, char *argv[])
{
/* When launched through hbloader (hbmenu, a forwarder, or a
* fork via envSetNextLoad), argv[0] is the path of the NRO
* this process is running and is the authoritative core
* identity: RARCH_PATH_CORE may otherwise carry a stale value
* read from the salamander config at startup, naming a core
* other than the one statically linked into this binary.
* Menu-triggered content loads pass a synthesised argv whose
* argv[0] is the literal "retroarch", so only accept values
* that actually name an NRO. */
if ( (*argc >= 1)
&& string_is_equal_noncase(path_get_extension(argv[0]), "nro"))
path_set(RARCH_PATH_CORE, argv[0]);
}
frontend_ctx_driver_t frontend_ctx_switch =
{
frontend_switch_get_env,
frontend_switch_init,
frontend_switch_deinit,
#ifdef HAVE_LIBNX
frontend_switch_exitspawn,
frontend_switch_process_args,
frontend_switch_exec,
#ifdef IS_SALAMANDER
NULL,
#else
frontend_switch_set_fork,
#endif
#else /* HAVE_LIBNX */
NULL,
NULL,
NULL,
NULL,
#endif /* HAVE_LIBNX */
frontend_switch_shutdown,
frontend_switch_get_name,
frontend_switch_get_os,
NULL, /* content_loaded */
frontend_switch_get_arch, /* get_architecture */
frontend_switch_get_powerstate, /* get_powerstate */
frontend_switch_parse_drive_list, /* parse_drive_list */
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, /* get_lakka_version */
NULL, /* set_screen_brightness */
NULL, /* set_sustained_performance_mode */
NULL, /* get_cpu_model_name */
NULL, /* get_user_language */
NULL, /* is_narrator_running */
NULL, /* accessibility_speak */
NULL, /* set_gamemode */
NULL, /* get_display_type */
"switch", /* ident */
NULL /* get_video_driver */
};