Files
PS5_RetroArch/tooling/dolphin/ps5-libc-shims.cpp
T
Mihawk 8a457a133f Seven more cores: PS1, N64, Saturn, C64, MAME, DS and 3DS
Beetle PSX HW, Mupen64Plus-Next, Beetle Saturn, VICE x64sc, MAME 0.289,
DeSmuME and Azahar, each built from my fork of it at a pinned revision
(tools/core-fork.sh, tools/build-<core>.sh), with the PS5 defaults at the
most each offers that holds full speed.

The loader grew to fit them. A core's file and image come from direct memory,
up to 1 GiB (MAME's image is 413 MB), and each image is zeroed when it is
reserved: direct memory is not, and a reloaded core's .bss held its
predecessor's bytes, which faulted every core on a reload. Each image's
.eh_frame is registered with the title's unwinder, for the cores that throw.
Initializers are capped at 65,536 (Azahar has 2,476). A load that misses imports
lists all of them. Aligned operator new comes from the core heap, a core's
atexit belongs to the core, and the imports the new cores need are bound to the
platform layer's getcwd, tmpfile, mkstemp and the rest (SDK fork ebd0fe2).
2026-09-28 15:06:47 -04:00

172 lines
5.0 KiB
C++

/*
* PS5 RetroArch - libc entry points Dolphin's core calls and the console lacks.
*
* Copyright (C) 2026 Mihawk
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Each function here is referenced by Dolphin or by a library it vendors, and
* none can come from the title's import table: the payload SDK exports no
* symbol for it, or (the resolver and isatty, as the PPSSPP port found) its
* stub lists one the console does not provide at runtime. They are defined in
* the core so the reference resolves where it is made.
*
* atexit, which VideoCommon/Spirv.cpp calls for glslang's finalizer, comes from
* the destructor registry every core links (tooling/native/core_cxx_runtime.cpp).
*
* Real implementations, because Dolphin depends on the answer:
*
* __cxa_thread_atexit
* thread_local destructors (Common, glslang). A thread that
* outlives the core (the frontend's main thread) would call them
* after the unmap, so they are not registered: the objects of a
* thread are leaked when it exits, never run from freed code.
* newlocale, uselocale
* Common/StringUtil.cpp formats and parses numbers in the C
* locale. The console's libc has no locale objects and is always
* in the C locale, so a stand-in handle is returned and switching
* to it changes nothing.
* ___mb_cur_max libiconv's MB_CUR_MAX: 1 byte, the C locale's value.
* timegm implot's time axis; a proleptic Gregorian UTC conversion.
*
* Refusals the callers already handle, for features a console core never uses:
*
* fork, execvp, waitpid imgui's "open in shell"
* mkdtemp Common's temporary directories (NetPlay,
* movie export)
* gethostbyname, getaddrinfo, freeaddrinfo, gai_strerror, getnameinfo,
* __res_init, __res_state name resolution (the Wii network stack,
* enet, curl, SFML), unused offline
* isatty terminal detection for log colour: the
* title's output is a log file
*/
#include <cerrno>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <ctime>
#include <netdb.h>
#include <sys/types.h>
namespace
{
/* Days from 1970-01-01 to the given civil date (Howard Hinnant's algorithm). */
long long days_from_civil(long long year, unsigned month, unsigned day)
{
year -= month <= 2;
const long long era = (year >= 0 ? year : year - 399) / 400;
const unsigned year_of_era = static_cast<unsigned>(year - era * 400);
const unsigned day_of_year = (153 * (month + (month > 2 ? -3 : 9)) + 2) / 5 + day - 1;
const unsigned day_of_era = year_of_era * 365 + year_of_era / 4 - year_of_era / 100 + day_of_year;
return era * 146097 + static_cast<long long>(day_of_era) - 719468;
}
int c_locale_tag;
/* The resolver's state; __res_state returns a zeroed block no caller reads after
* __res_init has failed. */
alignas(16) unsigned char resolver_state[1024];
} // namespace
extern "C"
{
int __cxa_thread_atexit(void (*)(void *), void *, void *)
{
return 0;
}
void *newlocale(int, const char *, void *)
{
return &c_locale_tag;
}
void *uselocale(void *)
{
return &c_locale_tag;
}
int ___mb_cur_max()
{
return 1;
}
time_t timegm(struct tm *time)
{
/* Normalise the month into 0-11 first, as mktime would. */
long long year = 1900LL + time->tm_year + time->tm_mon / 12;
int month = time->tm_mon % 12;
if (month < 0)
{
month += 12;
--year;
}
const long long days = days_from_civil(year, static_cast<unsigned>(month + 1), 1) + time->tm_mday - 1;
return static_cast<time_t>(((days * 24 + time->tm_hour) * 60 + time->tm_min) * 60 + time->tm_sec);
}
pid_t fork()
{
errno = ENOSYS;
return -1;
}
int execvp(const char *, char *const[])
{
errno = ENOSYS;
return -1;
}
pid_t waitpid(pid_t, int *, int)
{
errno = ECHILD;
return -1;
}
char *mkdtemp(char *)
{
errno = ENOSYS;
return nullptr;
}
struct hostent *gethostbyname(const char *)
{
return nullptr;
}
int getaddrinfo(const char *, const char *, const struct addrinfo *, struct addrinfo **result)
{
if (result)
*result = nullptr;
return EAI_FAIL;
}
void freeaddrinfo(struct addrinfo *)
{
}
const char *gai_strerror(int)
{
return "name resolution is not available in this core";
}
int getnameinfo(const struct sockaddr *, socklen_t, char *, size_t, char *, size_t, int)
{
return EAI_FAIL;
}
void *__res_state()
{
return resolver_state;
}
int __res_init()
{
return -1;
}
int isatty(int)
{
return 0;
}
}