Compare commits

...

24 Commits

Author SHA1 Message Date
lizzie 237fb283f8 prepare 2026-08-12 16:07:00 +00:00
lizzie a098e17e5c build adjustments 2026-08-12 16:07:00 +00:00
lizzie b3e4033ce9 ok no broken make 2026-08-12 16:07:00 +00:00
lizzie 26cb153f14 boost asio fix in android 2026-08-12 16:07:00 +00:00
lizzie ce8ec956bc it loads the fucker :) 2026-08-12 16:07:00 +00:00
lizzie 56ea54c380 BUT PROPERLY SHOW THE LOGS for fucks sake 2026-08-12 16:07:00 +00:00
lizzie e0facbe8a7 force services into guest process and singlecore option 2026-08-12 16:06:59 +00:00
lizzie 9c395241ca add filter options 2026-08-12 16:06:51 +00:00
lizzie e00e9faf12 --null-renderer 2026-08-12 16:06:35 +00:00
lizzie 526a367ffa emscripten friendly sdl loop 2026-08-12 16:06:34 +00:00
lizzie 7bd119a5b1 fixup make program 2026-08-12 16:06:03 +00:00
lizzie cc3f8cc4ca fixup caveats + add miniserver 2026-08-12 16:06:03 +00:00
lizzie b6fb3300b0 10gb of ram 2026-08-12 16:04:46 +00:00
lizzie 2d8ee311dc small writeup of wasm 2026-08-12 16:04:46 +00:00
lizzie 2b7fac48cd fix openssl 2026-08-12 16:04:46 +00:00
lizzie 411809818b proper jthread support 2026-08-12 16:04:46 +00:00
lizzie 67b3730059 fix wasm flags 2026-08-12 16:04:46 +00:00
lizzie 4a96cc9694 disable JIT service 2026-08-12 16:04:46 +00:00
lizzie b3bc1d8a40 emscripten can't memfd 2026-08-12 16:04:46 +00:00
lizzie 2da1418d77 emscripten doesnt have arc4random but wasi does? 2026-08-12 16:04:46 +00:00
lizzie a74d16a8fa fix non existant pthread funcs 2026-08-12 16:04:46 +00:00
lizzie 60943e8b9e initial wasm support 2026-08-12 16:04:46 +00:00
lizzie 911bab04f7 ok fixup but better with bool returns 2026-08-12 16:04:46 +00:00
lizzie cc04e1c536 Use managarm special fastmem fallback 2026-08-12 16:04:46 +00:00
35 changed files with 758 additions and 92 deletions
+92
View File
@@ -0,0 +1,92 @@
#!/bin/sh -ex
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
NUM_JOBS=$(nproc 2>/dev/null || getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)
: "${CCACHE:=false}"
RETURN=0
usage() {
cat <<EOF
Usage: $0 [-b|--build-type BUILD_TYPE] [-o|--outdir OUTPUT_DIRECTORY]
Build script for Emscripten (using wasm64).
Options:
--build-type Set the CMake build type (Release|RelWithDebInfo|MinSizeRel|Debug)
Default: Release
--outdir Set the output directory
Default: build
EOF
exit "$RETURN"
}
die() {
echo "-- ! $*" >&2
RETURN=1 usage
}
type() {
[ -z "$1" ] && die "You must specify a valid type."
TYPE="$1"
}
outdir() {
[ -z "$1" ] && die "You must specify a valid output directory."
OUTDIR="$1"
}
while true; do
case "$1" in
-r|--release) DEVEL=false ;;
-b|--build-type) type "$2"; shift ;;
-o|--outdir) outdir "$2"; shift ;;
-h|--help) usage ;;
*) break ;;
esac
shift
done
: "${TYPE:=Release}"
: "${DEVEL:=true}"
: "${OUTDIR:=build}"
# -sMEMORY64 must be specified twice, see below
# The CMake toolchain file will match against MEMORY64 but will fail to match if:
# - it's either -sMEMORY64
# - or it's either -sMEMORY64=1
# The line in question:
# if (CMAKE_C_FLAGS MATCHES "MEMORY64")
# However why need to specify -sMEMORY64=1 then? Oh that's because if you didn't set
# the =1, it would assume you meant =0, which equates to not specifying it at all
# This seems to be fixed in later versions but occurs atleast on 4.0.3-git and below.
emcmake cmake -B "$OUTDIR" -G "Unix Makefiles" \
-DCMAKE_BUILD_TYPE=${TYPE} \
-DENABLE_OPENGL=OFF \
-DENABLE_LTO=OFF \
-DENABLE_QT=OFF \
-DENABLE_UNITY_BUILD=OFF \
-DENABLE_QT_TRANSLATION=OFF \
-DENABLE_CUBEB=OFF \
-DENABLE_LIBUSB=OFF \
-DENABLE_UPDATE_CHECKER=OFF \
-DENABLE_WEB_SERVICE=OFF \
-DUSE_DISCORD_PRESENCE=OFF \
-DENABLE_WIFI_SCAN=OFF \
-DUSE_FASTER_LINKER=ON \
-DYUZU_STATIC_BUILD=ON \
-DYUZU_USE_BUNDLED_OPENSSL=OFF \
-DYUZU_USE_EXTERNAL_FFMPEG=ON \
-Dzstd_FORCE_BUNDLED=ON \
-DOpenSSL_FORCE_BUNDLED=ON \
-DEMSCRIPTEN_SYSTEM_PROCESSOR=wasm \
-DCMAKE_C_FLAGS="-s MEMORY64 -m64 -pipe -sMEMORY64=1" \
-DCMAKE_CXX_FLAGS="-s MEMORY64 -m64 -pipe -sMEMORY64=1" \
-DCMAKE_EXE_LINKER_FLAGS="-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1" \
-DCMAKE_C_LINK_FLAGS="-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1" \
-DCMAKE_CXX_LINK_FLAGS="-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1"
cmake --build "$OUTDIR" -- -j$NUM_JOBS
@@ -0,0 +1,40 @@
diff --git a/cmake/ConfigureOpenSSL.cmake b/cmake/ConfigureOpenSSL.cmake
index 3012e05..2ae23ff 100644
--- a/cmake/ConfigureOpenSSL.cmake
+++ b/cmake/ConfigureOpenSSL.cmake
@@ -108,7 +108,8 @@ function(configure_openssl)
)
if(NOT "${CONFIGURE_OPTIONS_OLD}" STREQUAL "")
- if(CONFIGURE_OPTIONS STREQUAL CONFIGURE_OPTIONS_OLD)
+ # TODO(lizzie): Emscripten has issues with rebuilding due to the wrapper it uses
+ if(CMAKE_SYSTEM_NAME MATCHES "Emscripten" OR CONFIGURE_OPTIONS STREQUAL CONFIGURE_OPTIONS_OLD)
message(STATUS "Found previous configure results. Don't perform configuration")
return()
endif()
@@ -134,10 +135,24 @@ function(configure_openssl)
set(VERBOSE_OPTION OUTPUT_QUIET)
endif()
+ if (CMAKE_SYSTEM_NAME MATCHES "Emscripten")
+ set(EMSCRIPTEN_CMAKE_WRAPPER "emcmake")
+ find_program(EMCC emcc REQUIRED)
+ set(EMSCRIPTEN_LINKER ${EMCC})
+ list(APPEND CONFIGURE_COMMAND wasm64)
+ else()
+ set(EMSCRIPTEN_CMAKE_WRAPPER "")
+ set(EMSCRIPTEN_LINKER ${CMAKE_LINKER})
+ endif ()
+
execute_process(
- COMMAND ${CMAKE_COMMAND} -E env
+ COMMAND ${EMSCRIPTEN_CMAKE_WRAPPER} ${CMAKE_COMMAND} -E env
"CFLAGS=${CMAKE_C_FLAGS}"
"CXXFLAGS=${CMAKE_CXX_FLAGS}"
+ "LDFLAGS=${CMAKE_CXX_LINK_FLAGS}"
+ "CC=${CMAKE_C_COMPILER}"
+ "CXX=${CMAKE_CXX_COMPILER}"
+ "LD=${EMSCRIPTEN_LINKER}"
${CONFIGURE_COMMAND}
WORKING_DIRECTORY ${CONFIGURE_BUILD_DIR}
${VERBOSE_OPTION}
+112
View File
@@ -0,0 +1,112 @@
diff --git a/Configurations/10-main.conf b/Configurations/10-main.conf
index e62721e..243feb4 100644
--- a/Configurations/10-main.conf
+++ b/Configurations/10-main.conf
@@ -1970,6 +1970,26 @@ my %targets = (
multilib => "64",
},
+ "wasm32" => {
+ inherit_from => [ "BASE_unix" ],
+ CC => "emcc",
+ CXX => "emc++",
+ cflags => combine("--target=wasm32-unknown-emscripten", threads("-pthread")),
+ cxxflags => combine("--target=wasm32-unknown-emscripten", threads("-pthread")),
+ lib_cppflags => add("-DL_ENDIAN"),
+ bn_ops => "THIRTY_TWO_BIT",
+ },
+ "wasm64" => {
+ inherit_from => [ "BASE_unix" ],
+ CC => "emcc",
+ CXX => "emc++",
+ cflags => combine("--target=wasm64-unknown-emscripten", threads("-pthread")),
+ cxxflags => combine("--target=wasm64-unknown-emscripten", threads("-pthread")),
+ lib_cppflags => add("-DL_ENDIAN"),
+ bn_ops => "SIXTY_FOUR_BIT_LONG",
+ },
+
+
#### uClinux
"uClinux-dist" => {
inherit_from => [ "BASE_unix" ],
diff --git a/crypto/rand/rand_lib.c b/crypto/rand/rand_lib.c
index d9e8f02..7faf347 100644
--- a/crypto/rand/rand_lib.c
+++ b/crypto/rand/rand_lib.c
@@ -379,17 +379,27 @@ void RAND_add(const void *buf, int num, double randomness)
#if !defined(OPENSSL_NO_DEPRECATED_1_1_0)
int RAND_pseudo_bytes(unsigned char *buf, int num)
{
+#if defined(__wasi__)
+ arc4random_buf(buf, num);
+ return 1;
+#elif defined(__EMSCRIPTEN__)
+ return 1;
+#else
const RAND_METHOD *meth = RAND_get_rand_method();
if (meth != NULL && meth->pseudorand != NULL)
return meth->pseudorand(buf, num);
ERR_raise(ERR_LIB_RAND, RAND_R_FUNC_NOT_IMPLEMENTED);
return -1;
+#endif
}
#endif
int RAND_status(void)
{
+#if defined(__EMSCRIPTEN__) || defined(__wasi__)
+ return 1;
+#else
EVP_RAND_CTX *rand;
#ifndef OPENSSL_NO_DEPRECATED_3_0
const RAND_METHOD *meth = RAND_get_rand_method();
@@ -401,6 +411,7 @@ int RAND_status(void)
if ((rand = RAND_get0_primary(NULL)) == NULL)
return 0;
return EVP_RAND_get_state(rand) == EVP_RAND_STATE_READY;
+#endif
}
#else /* !FIPS_MODULE */
@@ -420,6 +431,12 @@ const RAND_METHOD *RAND_get_rand_method(void)
int RAND_priv_bytes_ex(OSSL_LIB_CTX *ctx, unsigned char *buf, size_t num,
unsigned int strength)
{
+#if defined(__wasi__)
+ arc4random_buf(buf, num);
+ return 1;
+#elif defined(__EMSCRIPTEN__)
+ return 1;
+#else
RAND_GLOBAL *dgbl;
EVP_RAND_CTX *rand;
#if !defined(OPENSSL_NO_DEPRECATED_3_0) && !defined(FIPS_MODULE)
@@ -451,6 +468,7 @@ int RAND_priv_bytes_ex(OSSL_LIB_CTX *ctx, unsigned char *buf, size_t num,
return EVP_RAND_generate(rand, buf, num, strength, 0, NULL, 0);
return 0;
+#endif
}
int RAND_priv_bytes(unsigned char *buf, int num)
diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c
index 3d21801..e026f8f 100644
--- a/ssl/ssl_cert.c
+++ b/ssl/ssl_cert.c
@@ -941,6 +941,7 @@ done:
return ret;
}
+#ifndef OPENSSL_NO_POSIX_IO
int SSL_add_dir_cert_subjects_to_stack(STACK_OF(X509_NAME) *stack,
const char *dir)
{
@@ -1016,6 +1017,7 @@ err:
return ret;
}
+#endif
static int add_uris_recursive(STACK_OF(X509_NAME) *stack,
const char *uri, int depth)
+21 -5
View File
@@ -375,6 +375,15 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
# Prefer the -pthread flag on Linux.
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
# It is absolutely promordial to enable on Emscripten
# Not only this allows to use std::thread and std::jthread without exceptions
# but it also fixes several issues related to MT operations.
# ...and CMake doesn't include it by default even when we specify
# that we prefer the pthread flag; why is that? I don't know.
if (PLATFORM_EMSCRIPTEN)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthread>)
add_link_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthread>)
endif()
find_package(RenderDoc MODULE)
@@ -403,7 +412,12 @@ set(BUILD_TESTING OFF)
set(ENABLE_TESTING OFF)
# boost
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
if (PLATFORM_EMSCRIPTEN)
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap headers filesystem crc variant)
set(BOOST_CONTAINER_HEADER_ONLY ON)
else()
set(BOOST_INCLUDE_LIBRARIES algorithm icl pool container heap asio headers process filesystem crc variant)
endif()
AddJsonPackage(boost)
@@ -424,10 +438,12 @@ if (Boost_ADDED)
target_compile_options(boost_heap INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
target_compile_options(boost_icl INTERFACE $<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shadow>)
target_compile_options(boost_asio INTERFACE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>
)
# May not exist (i.e emscripten)
if (TARGET boost_asio)
target_compile_options(boost_asio INTERFACE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-conversion>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-fallthrough>)
endif()
endif()
endif()
+5 -3
View File
@@ -108,7 +108,7 @@
"find_args": "MODULE GLOBAL",
"hash": "159ed94965018f2a371d45a3bfc1961e5fb1549e501ded70a6b4532d7fe99d0579c18b5195aff6e35f96f399b426cea2650ec9fb75ef80d4c9edeccb51f2e6c9",
"options": [
"HTTPLIB_REQUIRE_OPENSSL ON",
"HTTPLIB_REQUIRE_OPENSSL OFF",
"HTTPLIB_DISABLE_MACOSX_AUTOMATIC_ROOT_CERTIFICATES ON"
],
"patches": [
@@ -190,7 +190,8 @@
"min_version": "3.0.0",
"package": "OpenSSL",
"patches": [
"0001-add-bundled-cert.patch"
"0001-add-bundled-cert.patch",
"0002-wasm-support.patch"
],
"repo": "openssl/openssl",
"version": "openssl-3.6.2"
@@ -213,7 +214,8 @@
"0001-cpmutil-compat.patch",
"0002-use-ccache.patch",
"0003-use-cmake-compiler-flags.patch",
"0004-use-shell-wrapper.patch"
"0004-use-shell-wrapper.patch",
"0005-wasm-support.patch"
],
"repo": "jimmy-park/openssl-cmake",
"version": "3.6.2"
+17
View File
@@ -12,6 +12,7 @@
- [NetBSD](#netbsd)
- [MSYS2](#msys2)
- [RedoxOS](#redoxos)
- [WebAssembly](#webassembly)
- [Windows](#windows)
- [Windows 7, Windows 8 and Windows 8.1](#windows-7-windows-8-and-windows-81)
- [Windows Vista and below](#windows-vista-and-below)
@@ -245,6 +246,22 @@ The package install may randomly hang at times, in which case it has to be resta
When CMake invokes certain file syscalls - it may sometimes cause crashes or corruptions on the (kernel?) address space - so reboot the system if there is a "hang" in CMake.
## WebAssembly
**It doesn't run on a browser yet.**
WebAssembly or "WASM" for short is a *mainly 32-bit* virtual "architecture" which we only bootstrap on 64-bit only. This means not only the program runs 2x slower than it would due to using JS BigInt, it also means we need to go out of our way to enable proper 64-bit support via `-sMEMORY64=1`, however once again, some Emscripten quirks force us to specify `-s MEMORY64` and `-sMEMORY64=1` at the same time: see the [CI build script](../.ci/wasm/build.sh).
The WebAssembly target is very heavy on resources and requires at least 4 times the normal amount of resources that a native build would. Additionally, the only supported environment is Firefox at the moment, node.js and `wasmtime are not supported (PRs welcome!)
If running under Firefox and you hit "out of memory" on dev console, close the entire tab, then open it back again, see [this issue](https://github.com/emscripten-core/emscripten/issues/8126).
To run the binary (after building) you should be fine with `node ./eden-cli.js`. For obvious reasons no Qt frontend is available on WASM, support for Vulkan is done charily via [llvmpipe2wasm](https://github.com/Devsh-Graphics-Programming/llvmpipe2wasm).
If you run into the error "acorn.js can't be found" check [this associated issue](https://github.com/emscripten-core/emscripten/issues/13368), the fix in short is `npm --global install acorn`. On FreeBSD you could run `npm` under root, or you could do the sane thing and do `sudo chown -R $USER /usr/local/lib/node_modules/ /usr/local/bin/` (remember to restore permissions afterwards!) unless you wish to run `npm` under root which is generally a bad idea.
2026-06-09: As of writing, no Dynarmic-based JIT is possible on this target, full interpreted emulation is the only reasonable option. While there is some efforts on making a JIT like [here](https://github.com/wingo/wasm-jit) or [here](https://wingolog.org/archives/2022/08/18/just-in-time-code-generation-within-webassembly), the result is so latency expensive we're better off using an interpreter instead.
## Windows
### Windows 7, Windows 8 and Windows 8.1
+8
View File
@@ -359,6 +359,14 @@ pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel b
[Caveats](./Caveats.md#haikuos).
</details>
<details>
<summary>WebAssembly</summary>
Emscripten: The default installation should provide enough.
[Caveats](./Caveats.md#wasm).
</details>
<details>
<summary>RedoxOS</summary>
+9 -5
View File
@@ -94,6 +94,10 @@ function(detect_architecture_symbols)
endfunction()
# arches here are put in a sane default order of importance
# EXCEPT FOR WASM, which must be probed for FIRST, because some genius
# decided to also allow the host architecture to be defined when building
# for the emscripten target, absolutely lovely detail.
#
# notably, amd64, arm64, and riscv (in order) are BY FAR the most common
# mips is pretty popular in embedded
# ppc64 is pretty popular in supercomputing
@@ -101,6 +105,11 @@ endfunction()
# ia64 exists
# the rest exist, but are probably less popular than ia64
detect_architecture_symbols(
ARCH wasm
SYMBOLS
"__EMSCRIPTEN__")
detect_architecture_symbols(
ARCH arm64
SYMBOLS
@@ -203,11 +212,6 @@ detect_architecture_symbols(
"__loongarch__"
"__loongarch64")
detect_architecture_symbols(
ARCH wasm
SYMBOLS
"__EMSCRIPTEN__")
# "generic" target
# If you have reached this point, you're on some as-of-yet unsupported architecture.
# See the docs up above for known unsupported architectures
+25
View File
@@ -20,6 +20,9 @@ elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm")
set(MANAGARM ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
set(HAIKUOS ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
set(EMSCRIPTEN ON)
message(WARNING "${CMAKE_LIBRARY_ARCHITECTURE} support is highly experimental!!!")
endif()
# BSD
@@ -97,3 +100,25 @@ if (MINGW)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_FLAGS}")
endif()
if (PLATFORM_EMSCRIPTEN)
set(EMSCRIPTEN_C_FLAGS "-s MEMORY64 -m64 -pipe -sMEMORY64=1")
set(EMSCRIPTEN_LINK_FLAGS "-sMEMORY64=1 -m64 -Wl,-mwasm64 -sASYNCIFY=1")
# This prevents FFmpeg and other libraries from assuming it's the host's CPU
# Additionally some Emscripten installs may not be very good... generally
set(EMSCRIPTEN_SYSTEM_PROCESSOR wasm)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${EMSCRIPTEN_C_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} ${EMSCRIPTEN_C_FLAGS}")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
set(CMAKE_C_LINK_FLAGS "${CMAKE_C_LINK_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
set(CMAKE_CXX_LINK_FLAGS "${CMAKE_CXX_LINK_FLAGS} ${EMSCRIPTEN_LINK_FLAGS}")
unset(EMSCRIPTEN_C_FLAGS)
unset(EMSCRIPTEN_LINK_FLAGS)
endif()
# awesome
if (PLATFORM_FREEBSD OR PLATFORM_DRAGONFLYBSD)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/local/lib")
endif()
+56 -19
View File
@@ -37,26 +37,39 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
elseif (NOT (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES CMAKE_SYSTEM_PROCESSOR
AND CMAKE_HOST_SYSTEM_NAME MATCHES CMAKE_SYSTEM_NAME))
string(TOLOWER "${CMAKE_SYSTEM_NAME}" FFmpeg_SYSTEM_NAME)
if (FFmpeg_SYSTEM_NAME STREQUAL "openorbis" OR FFmpeg_SYSTEM_NAME STREQUAL "managarm")
# All of these platforms are supported by ffmpeg as native build OSes
# anything else (like Redox or Managarm or PS4) is NOT natively supported
# hence, assume the "unix like" is just "none" for the sake of OUR sanity.
# If YOUR OS/platform has actual native support:
# 1. fucking congrats
# 2. feel free to add it on the condition below
if (NOT (PLATFORM_NETBSD OR PLATFORM_SUN OR PLATFORM_FREEBSD
OR PLATFORM_OPENBSD OR PLATFORM_DRAGONFLYBSD OR PLATFORM_HAIKU
OR PLATFORM_LINUX OR PLATFORM_MSYS OR WIN32 OR ANDROID OR APPLE))
set(FFmpeg_SYSTEM_NAME "none")
endif()
# TODO: Can we really do better? Auto-detection? Something clever?
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--enable-cross-compile
--arch="${CMAKE_SYSTEM_PROCESSOR}"
--target-os="${FFmpeg_SYSTEM_NAME}"
--sysroot="${CMAKE_SYSROOT}"
)
--target-os="${FFmpeg_SYSTEM_NAME}")
if (PLATFORM_EMSCRIPTEN)
# funniest trolling from emscripten, such a classic!
# maybe I should PR so they use CMAKE_SYSROOT... y'know?
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --sysroot="${EMSCRIPTEN_SYSROOT}")
else()
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --sysroot="${CMAKE_SYSROOT}")
endif()
if (DEFINED FFmpeg_CROSS_PREFIX)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --cross-prefix="${FFmpeg_CROSS_PREFIX}")
else()
message(WARNING "Please set FFmpeg_CROSS_PREFIX to your cross toolchain prefix, for example: \${CMAKE_STAGING_PREFIX}/bin/${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}-")
message(WARNING "Please set FFmpeg_CROSS_PREFIX to your cross toolchain prefix, for example: ${CMAKE_STAGING_PREFIX}/bin/${CMAKE_SYSTEM_PROCESSOR}-${CMAKE_SYSTEM_NAME}")
endif()
set(FFmpeg_IS_CROSS_COMPILING TRUE)
endif()
endif()
if (OPENORBIS OR MANAGARM)
if (OPENORBIS OR MANAGARM OR EMSCRIPTEN)
# Doesn't support VA-API, don't go thru the embarrassment of trying to enable it
list(APPEND FFmpeg_HWACCEL_FLAGS --disable-vaapi)
elseif (ANDROID)
@@ -164,8 +177,7 @@ if (OPENORBIS)
-lSceUserService
-lSceSysmodule
-lSceNet
-lSceLibcInternal
)
-lSceLibcInternal)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--disable-pthreads
--extra-cflags=${CMAKE_SYSROOT}/usr/include
@@ -176,8 +188,18 @@ elseif (MANAGARM)
# Required for proper stuff
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--disable-pthreads
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}"
)
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}")
endif()
# Usually used by Emscripten
if (DEFINED CMAKE_AR)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --ar=${CMAKE_AR})
endif()
if (DEFINED CMAKE_NM)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --nm=${CMAKE_NM})
endif()
if (DEFINED CMAKE_RANLIB)
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS --ranlib=${CMAKE_RANLIB})
endif()
if (YUZU_USE_BUNDLED_FFMPEG)
@@ -207,6 +229,7 @@ else()
# Build FFmpeg from externals
message(STATUS "Using FFmpeg from externals")
set(FFmpeg_LD ${CMAKE_LINKER})
if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86_64|amd64)")
# FFmpeg has source that requires one of nasm or yasm to assemble it.
# REQUIRED throws an error if not found here during configuration rather than during compilation.
@@ -215,6 +238,12 @@ else()
message(FATAL_ERROR "One of either `nasm` or `yasm` not found but is required.")
endif()
endif()
if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
# TODO: this is 100% redundant but we need it because CMAKE_LINKER can resolve to ld.lld
# which is NOT compatible
find_program(EMCC NAMES emcc REQUIRED)
set(FFmpeg_LD ${EMCC})
endif()
find_program(AUTOCONF autoconf)
if ("${AUTOCONF}" STREQUAL "AUTOCONF-NOTFOUND")
@@ -248,7 +277,13 @@ else()
CACHE PATH "Paths to FFmpeg libraries" FORCE)
endforeach()
find_program(BASH_PROGRAM bash REQUIRED)
# Some SDKs (especially wasm) require us, actually, WANT us to use their "emconfigure"
# otherwise they will cry a billion tears
if (PLATFORM_EMSCRIPTEN)
find_program(FFmpeg_CONFIGURE_WRAPPER emconfigure REQUIRED)
else()
find_program(FFmpeg_CONFIGURE_WRAPPER bash REQUIRED)
endif()
# `configure` parameters builds only exactly what yuzu needs from FFmpeg
# `--disable-vdpau` is needed to avoid linking issues
@@ -258,7 +293,7 @@ else()
OUTPUT
${FFmpeg_MAKEFILE}
COMMAND
${BASH_PROGRAM} ${FFmpeg_PREFIX}/configure
${FFmpeg_CONFIGURE_WRAPPER} ${FFmpeg_PREFIX}/configure
--disable-avdevice
--disable-avformat
--disable-doc
@@ -267,6 +302,10 @@ else()
--disable-ffprobe
--disable-network
--disable-swresample
--disable-autodetect
--disable-runtime-cpudetect
--disable-debug
--disable-programs
--enable-decoder=h264
--enable-decoder=vp8
--enable-decoder=vp9
@@ -274,7 +313,7 @@ else()
--enable-pic
--cc=${FFmpeg_CC}
--cxx=${FFmpeg_CXX}
--ld=${CMAKE_LINKER}
--ld=${FFmpeg_LD}
--extra-cflags=${CMAKE_C_FLAGS}
--extra-cxxflags=${CMAKE_CXX_FLAGS}
--extra-ldflags=${CMAKE_C_LINK_FLAGS}
@@ -290,11 +329,7 @@ else()
# Workaround for Ubuntu 18.04's older version of make not being able to call make as a child
# with context of the jobserver. Also helps ninja users.
execute_process(
COMMAND
nproc
OUTPUT_VARIABLE
SYSTEM_THREADS)
cmake_host_system_information(RESULT SYSTEM_THREADS QUERY NUMBER_OF_LOGICAL_CORES)
set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
@@ -302,6 +337,8 @@ else()
if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD)
set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS})
else()
# No GNU make implies that this system may be highly non-GNU
find_program(MAKE make required)
set(FFmpeg_MAKE_ARGS "")
endif()
@@ -309,7 +346,7 @@ else()
OUTPUT
${FFmpeg_BUILD_LIBRARIES}
COMMAND
gmake ${FFmpeg_MAKE_ARGS}
${MAKE} ${FFmpeg_MAKE_ARGS}
WORKING_DIRECTORY
${FFmpeg_BUILD_DIR}
)
+2
View File
@@ -14,7 +14,9 @@
namespace Tz {
namespace {
#ifndef EINVAL
#define EINVAL 22
#endif
static Rule gmtmem{};
static Rule* const gmtptr = &gmtmem;
+5 -2
View File
@@ -177,7 +177,6 @@ else()
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-invalid-offsetof>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
if (CXX_CLANG OR CXX_ICC OR CXX_APPLE) # Clang, AppleClang, or Intel C++
if (NOT MSVC)
add_compile_options(
@@ -191,7 +190,11 @@ else()
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-nullability-completeness>)
endif()
if (ARCHITECTURE_x86_64)
if (ARCHITECTURE_wasm)
# we are evil but fmt is even more evil
add_compile_options(
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-shorten-64-to-32>)
elseif (ARCHITECTURE_x86_64)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
if (LINUX OR FREEBSD)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
+5 -1
View File
@@ -235,7 +235,11 @@ else()
target_link_libraries(common PUBLIC Boost::headers)
endif()
target_link_libraries(common PRIVATE OpenSSL::SSL)
target_link_libraries(common PUBLIC Boost::filesystem Boost::context httplib::httplib nlohmann_json::nlohmann_json)
target_link_libraries(common PUBLIC Boost::filesystem httplib::httplib nlohmann_json::nlohmann_json)
if (NOT PLATFORM_EMSCRIPTEN)
# Emscripten is: "bring your own implementation", boost doesn't add upstream support sadly
target_link_libraries(common PRIVATE Boost::context)
endif()
if (lz4_ADDED)
target_include_directories(common PRIVATE ${lz4_SOURCE_DIR}/lib)
+85 -12
View File
@@ -11,7 +11,12 @@
#include "common/fiber.h"
#include "common/virtual_buffer.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/fiber.h>
#else
#include <boost/context/detail/fcontext.hpp>
#endif
namespace Common {
@@ -22,36 +27,103 @@ constexpr size_t DEFAULT_STACK_SIZE = 512 * 4096;
#endif
constexpr u32 CANARY_VALUE = 0xDEADBEEF;
#ifdef __EMSCRIPTEN__
struct Fiber::FiberImpl {
FiberImpl() {}
u32 canary_1 = CANARY_VALUE;
std::array<u8, DEFAULT_STACK_SIZE> stack{};
std::array<u8, DEFAULT_STACK_SIZE> rewind_stack{};
std::array<u8, DEFAULT_STACK_SIZE> astack{};
u32 canary_2 = CANARY_VALUE;
boost::context::detail::fcontext_t context{};
boost::context::detail::fcontext_t rewind_context{};
emscripten_fiber_t* context{nullptr};
std::mutex guard;
std::function<void()> entry_point;
std::function<void()> rewind_point;
std::shared_ptr<Fiber> previous_fiber;
u8* stack_limit = nullptr;
u8* rewind_stack_limit = nullptr;
bool is_thread_fiber = false;
bool released = false;
};
void Fiber::SetRewindPoint(std::function<void()>&& rewind_func) {
impl->rewind_point = std::move(rewind_func);
Fiber::Fiber(std::function<void()>&& entry_point_func) : impl{std::make_unique<FiberImpl>()} {
impl->entry_point = std::move(entry_point_func);
emscripten_fiber_init(impl->context, [](void *user_data) -> void {
auto* fiber = static_cast<Fiber*>(user_data);
ASSERT(fiber && fiber->impl && fiber->impl->previous_fiber && fiber->impl->previous_fiber->impl);
ASSERT(fiber->impl->canary_1 == CANARY_VALUE);
ASSERT(fiber->impl->canary_2 == CANARY_VALUE);
fiber->impl->previous_fiber->impl->context = fiber->impl->context;
fiber->impl->previous_fiber->impl->guard.unlock();
fiber->impl->previous_fiber.reset();
fiber->impl->entry_point();
UNREACHABLE();
}, impl.get(), impl->stack.data(), impl->stack.size(), impl->astack.data(), impl->astack.size());
}
Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
Fiber::~Fiber() {
if (!impl->released) {
// Make sure the Fiber is not being used
const bool locked = impl->guard.try_lock();
ASSERT(locked && "Destroying a fiber that's still running");
if (locked) {
impl->guard.unlock();
}
}
}
void Fiber::Exit() {
ASSERT(impl->is_thread_fiber && "Exiting non main thread fiber");
if (impl->is_thread_fiber) {
impl->guard.unlock();
impl->released = true;
}
}
void Fiber::YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to) {
to.impl->guard.lock();
to.impl->previous_fiber = weak_from.lock();
emscripten_fiber_swap(to.impl->context, to.impl->previous_fiber->impl->context);
// "from" might no longer be valid if the thread was killed
if (auto from = weak_from.lock()) {
if (from->impl->previous_fiber == nullptr) {
ASSERT(false && "previous_fiber is nullptr!");
} else {
from->impl->previous_fiber->impl->guard.unlock();
from->impl->previous_fiber.reset();
}
}
}
std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
fiber->impl->guard.lock();
fiber->impl->is_thread_fiber = true;
return fiber;
}
#else
struct Fiber::FiberImpl {
FiberImpl() {}
u32 canary_1 = CANARY_VALUE;
std::array<u8, DEFAULT_STACK_SIZE> stack{};
u32 canary_2 = CANARY_VALUE;
boost::context::detail::fcontext_t context{};
std::mutex guard;
std::function<void()> entry_point;
std::shared_ptr<Fiber> previous_fiber;
u8* stack_limit = nullptr;
bool is_thread_fiber = false;
bool released = false;
};
Fiber::Fiber(std::function<void()>&& entry_point_func) : impl{std::make_unique<FiberImpl>()} {
impl->entry_point = std::move(entry_point_func);
impl->stack_limit = impl->stack.data();
impl->rewind_stack_limit = impl->rewind_stack.data();
u8* stack_base = impl->stack_limit + DEFAULT_STACK_SIZE;
impl->context = boost::context::detail::make_fcontext(stack_base, impl->stack.size(), [](boost::context::detail::transfer_t transfer) -> void {
auto* fiber = static_cast<Fiber*>(transfer.data);
@@ -72,7 +144,7 @@ Fiber::~Fiber() {
if (!impl->released) {
// Make sure the Fiber is not being used
const bool locked = impl->guard.try_lock();
ASSERT_MSG(locked, "Destroying a fiber that's still running");
ASSERT(locked && "Destroying a fiber that's still running");
if (locked) {
impl->guard.unlock();
}
@@ -80,7 +152,7 @@ Fiber::~Fiber() {
}
void Fiber::Exit() {
ASSERT_MSG(impl->is_thread_fiber, "Exiting non main thread fiber");
ASSERT(impl->is_thread_fiber && "Exiting non main thread fiber");
if (impl->is_thread_fiber) {
impl->guard.unlock();
impl->released = true;
@@ -110,5 +182,6 @@ std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
fiber->impl->is_thread_fiber = true;
return fiber;
}
#endif
} // namespace Common
+1 -2
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
@@ -45,7 +45,6 @@ public:
/// Fiber 'from' must be the currently running fiber.
static void YieldTo(std::weak_ptr<Fiber> weak_from, Fiber& to);
[[nodiscard]] static std::shared_ptr<Fiber> ThreadToFiber();
void SetRewindPoint(std::function<void()>&& rewind_func);
/// Only call from main thread's fiber
void Exit();
private:
+5
View File
@@ -136,6 +136,11 @@ public:
eden_path = GetDataDirectory("XDG_DATA_HOME") / EDEN_DIR;
eden_path_cache = GetDataDirectory("XDG_CACHE_HOME") / EDEN_DIR;
eden_path_config = GetDataDirectory("XDG_CONFIG_HOME") / EDEN_DIR;
#if defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(__managarm__)
// folders MAY not exist in this distrobution/OS
CreateParentDir(GetDataDirectory("XDG_CONFIG_HOME"));
CreateParentDir(GetDataDirectory("XDG_CACHE_HOME"));
#endif
} else {
eden_path_cache = eden_path / CACHE_DIR;
eden_path_config = eden_path / CONFIG_DIR;
+6 -6
View File
@@ -394,7 +394,7 @@ private:
ankerl::unordered_dense::map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
};
#elif defined(__OPENORBIS__) || defined(__managarm__)
#elif defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__)
// None of the luxuries of POSIX, all of the suffering
// For managarm: see https://github.com/managarm/managarm/issues/1370
#else // ^^^ Windows ^^^ vvv POSIX vvv
@@ -689,7 +689,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
: backing_size(backing_size_)
, virtual_size(virtual_size_)
{
#if defined(__OPENORBIS__) || defined(__managarm__)
#if defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__)
LOG_WARNING(HW_Memory, "Platform doesn't support fastmem");
fallback_buffer.emplace(backing_size);
backing_base = fallback_buffer->data();
@@ -723,7 +723,7 @@ HostMemory::HostMemory(HostMemory&&) noexcept = default;
HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, MemoryPermission perms, bool separate_heap) {
#if !(defined(__OPENORBIS__) || defined(__managarm__))
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(host_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
@@ -737,7 +737,7 @@ void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length, M
}
void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap) {
#if !(defined(__OPENORBIS__) || defined(__managarm__))
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
ASSERT(virtual_offset + length <= virtual_size);
@@ -749,7 +749,7 @@ void HostMemory::Unmap(size_t virtual_offset, size_t length, bool separate_heap)
}
void HostMemory::Protect(size_t virtual_offset, size_t length, MemoryPermission perm) {
#if !(defined(__OPENORBIS__) || defined(__managarm__))
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
ASSERT(virtual_offset % PageAlignment == 0);
ASSERT(length % PageAlignment == 0);
ASSERT(virtual_offset + length <= virtual_size);
@@ -768,7 +768,7 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
}
void HostMemory::EnableDirectMappedAddress() {
#if !(defined(__OPENORBIS__) || defined(__managarm__))
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
if (impl) {
impl->EnableDirectMappedAddress();
virtual_size += reinterpret_cast<uintptr_t>(virtual_base);
+1 -1
View File
@@ -77,7 +77,7 @@ private:
size_t backing_size{};
size_t virtual_size{};
#if !(defined(__OPENORBIS__) || defined(__managarm__))
#if !(defined(__OPENORBIS__) || defined(__managarm__) || defined(__wasi__) || defined(__EMSCRIPTEN__))
// Low level handler for the platform dependent memory routines
class Impl;
std::unique_ptr<Impl> impl;
+4
View File
@@ -490,6 +490,8 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value,
GetLastErrorMsg());
}
#elif defined(__EMSCRIPTEN__)
// TODO: set priority?
#else
const s32 max_prio = sched_get_priority_max(SCHED_OTHER);
const s32 min_prio = sched_get_priority_min(SCHED_OTHER);
@@ -534,6 +536,8 @@ void SetCurrentThreadName(const char* name) {
// See for reference
// https://gitlab.freedesktop.org/mesa/mesa/-/blame/main/src/util/u_thread.c?ref_type=heads#L75
(void)name;
#elif defined(__EMSCRIPTEN__)
// TODO: set thread name?
#else
pthread_setname_np(pthread_self(), name);
#endif
+12 -5
View File
@@ -38,10 +38,6 @@ add_library(core STATIC
debugger/debugger.cpp
debugger/debugger.h
debugger/debugger_interface.h
debugger/gdbstub.cpp
debugger/gdbstub.h
debugger/gdbstub_arch.cpp
debugger/gdbstub_arch.h
device_memory.cpp
device_memory.h
device_memory_manager.h
@@ -1170,6 +1166,14 @@ add_library(core STATIC
tools/freezer.h
tools/renderdoc.cpp
tools/renderdoc.h)
if (NOT PLATFORM_EMSCRIPTEN)
# incompatible with wasm's async model
target_sources(core PRIVATE
debugger/gdbstub.cpp
debugger/gdbstub.h
debugger/gdbstub_arch.cpp
debugger/gdbstub_arch.h)
endif()
if (ENABLE_WIFI_SCAN)
target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
@@ -1213,7 +1217,10 @@ target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
if (BOOST_NO_HEADERS)
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::asio Boost::process Boost::crc)
target_link_libraries(core PUBLIC Boost::container Boost::heap Boost::crc)
if (NOT PLATFORM_EMSCRIPTEN)
target_link_libraries(core PUBLIC Boost::asio Boost::process)
endif()
else()
target_link_libraries(core PUBLIC Boost::headers)
endif()
+4 -2
View File
@@ -6,19 +6,20 @@
#pragma once
#ifndef __EMSCRIPTEN__
#include <dynarmic/interface/halt_reason.h>
#endif
#include "core/arm/arm_interface.h"
namespace Core {
#ifndef __EMSCRIPTEN__
constexpr Dynarmic::HaltReason StepThread = Dynarmic::HaltReason::Step;
constexpr Dynarmic::HaltReason DataAbort = Dynarmic::HaltReason::MemoryAbort;
constexpr Dynarmic::HaltReason BreakLoop = Dynarmic::HaltReason::UserDefined2;
constexpr Dynarmic::HaltReason SupervisorCall = Dynarmic::HaltReason::UserDefined3;
constexpr Dynarmic::HaltReason InstructionBreakpoint = Dynarmic::HaltReason::UserDefined4;
constexpr Dynarmic::HaltReason PrefetchAbort = Dynarmic::HaltReason::UserDefined6;
constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
static_assert(u64(HaltReason::StepThread) == u64(StepThread));
static_assert(u64(HaltReason::DataAbort) == u64(DataAbort));
@@ -28,5 +29,6 @@ constexpr HaltReason TranslateHaltReason(Dynarmic::HaltReason hr) {
static_assert(u64(HaltReason::PrefetchAbort) == u64(PrefetchAbort));
return HaltReason(hr);
}
#endif
} // namespace Core
+27 -2
View File
@@ -6,30 +6,54 @@
#include <mutex>
#include <utility>
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
// TODO: gdb stub compat with emscripten?
#else
#include <boost/asio.hpp>
#include <boost/version.hpp>
#if BOOST_VERSION > 108400 && (!defined(_WINDOWS) && !defined(__ANDROID__)) || defined(YUZU_BOOST_v1)
#define USE_BOOST_v1
#endif
#ifdef USE_BOOST_v1
#include <boost/process/v1/async_pipe.hpp>
#else
#include <boost/process/async_pipe.hpp>
#endif
#endif
#include "common/logging.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/debugger/debugger.h"
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
// TODO: gdbstub with emscripten?
#else
#include "core/debugger/debugger_interface.h"
#include "core/debugger/gdbstub.h"
#endif
#include "core/hle/kernel/global_scheduler_context.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scheduler.h"
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
namespace Core {
// Dummy
class DebuggerImpl {
char pad;
};
Debugger::Debugger(Core::System& system, u16 port) {}
Debugger::~Debugger() = default;
bool Debugger::NotifyThreadStopped(Kernel::KThread* thread) {
return false;
}
bool Debugger::NotifyThreadWatchpoint(Kernel::KThread* thread, const Kernel::DebugWatchpoint& watch) {
return false;
}
void Debugger::NotifyShutdown() {}
} // namespace Core
#else
template <typename Readable, typename Buffer, typename Callback>
static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
static_assert(std::is_trivial_v<Buffer>);
@@ -400,3 +424,4 @@ void Debugger::NotifyShutdown() {
}
} // namespace Core
#endif
+10 -3
View File
@@ -7,8 +7,14 @@
#include <random>
#include "common/scope_exit.h"
#include "common/settings.h"
#include "core/arm/exclusive_monitor.h"
#ifndef __EMSCRIPTEN__
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/arm/dynarmic/dynarmic_exclusive_monitor.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#endif
#include "core/core.h"
#include "core/hle/kernel/k_process.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
@@ -18,8 +24,6 @@
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/k_worker_task_manager.h"
#include "core/arm/dynarmic/arm_dynarmic_32.h"
#include "core/arm/dynarmic/arm_dynarmic_64.h"
#ifdef HAS_NCE
#include "core/arm/nce/arm_nce.h"
#endif
@@ -1300,9 +1304,11 @@ void KProcess::LoadModule(KernelCore& kernel, CodeSet code_set, KProcessAddress
}
void KProcess::InitializeInterfaces(KernelCore& kernel) {
#ifdef __EMSCRIPTEN__
ASSERT(false && "unimplemented");
#else
m_exclusive_monitor =
Core::MakeExclusiveMonitor(this->GetMemory(), Core::Hardware::NUM_CPU_CORES);
#ifdef HAS_NCE
if (this->IsApplication() && Settings::IsNceEnabled()) {
for (size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++)
@@ -1322,6 +1328,7 @@ void KProcess::InitializeInterfaces(KernelCore& kernel) {
static_cast<Core::DynarmicExclusiveMonitor&>(*m_exclusive_monitor), i);
}
}
#endif
}
bool KProcess::InsertWatchpoint(KernelCore& kernel, KProcessAddress addr, u64 size, DebugWatchpointType type) {
+13 -3
View File
@@ -86,15 +86,22 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
// BEGONE cold clones of lambdas, for I have merged you all into a SINGLE lambda instead of
// spamming lambdas like it's some kind of lambda calculus class
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
std::vector<std::pair<std::string_view, void (*)(Core::System&)>> rt_services{
{"audio", &Audio::LoopProcess},
{"FS", &FileSystem::LoopProcess},
{"ldn", &LDN::LoopProcess},
{"nvservices", &Nvidia::LoopProcess},
{"bsdsocket", &Sockets::LoopProcess},
})
};
#if defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(__OPENORBIS__)
for (auto const& e : rt_services)
kernel.RunOnGuestCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); });
kernel.RunOnGuestCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); });
#else
for (auto const& e : rt_services)
kernel.RunOnHostCoreProcess(std::string(e.first), [&system, f = e.second] { f(system); }).detach();
kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
kernel.RunOnHostCoreProcess("vi", [&, token] { VI::LoopProcess(system, token); }).detach();
#endif
// Avoid cold clones of lambdas -- succintly
for (auto const& e : std::vector<std::pair<std::string_view, void (*)(Core::System&)>>{
{"sm", &SM::LoopProcess},
@@ -118,7 +125,10 @@ Services::Services(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system
{"glue", &Glue::LoopProcess},
{"grc", &GRC::LoopProcess},
{"hid", &HID::LoopProcess},
// Must match with src/core/CMakeLists.txt for target_source of jit.cpp
#if defined(ARCHITECTURE_x86_64) || defined(ARCHITECTURE_arm64) || defined(ARCHITECTURE_riscv64) || defined(ARCHITECTURE_loongarch64)
{"jit", &JIT::LoopProcess},
#endif
{"lbl", &LBL::LoopProcess},
{"Loader", &LDR::LoopProcess},
{"LogManager.Prod", &LM::LoopProcess},
@@ -28,7 +28,10 @@ enum class NpadMcuState : u32 {
struct NpadMcuHolder {
NpadMcuState state;
INSERT_PADDING_BYTES(0x4);
IAbstractedPad* abstracted_pad;
union {
IAbstractedPad* abstracted_pad;
u64 abstracted_pad_raw;
};
};
static_assert(sizeof(NpadMcuHolder) == 0x10, "NpadMcuHolder is an invalid size");
@@ -33,9 +33,15 @@ public:
bool is_created{};
bool is_mapped{};
INSERT_PADDING_BYTES(0x5);
Kernel::KSharedMemory* shared_memory;
union {
Kernel::KSharedMemory* shared_memory = nullptr;
u64 shared_memory_raw;
};
INSERT_PADDING_BYTES(0x38);
SharedMemoryFormat* address = nullptr;
union {
SharedMemoryFormat* address = nullptr;
u64 address_raw;
};
};
// Correct size is 0x50 bytes
static_assert(sizeof(SharedMemoryHolder) == 0x50, "SharedMemoryHolder is an invalid size");
+6 -4
View File
@@ -15,8 +15,6 @@ add_library(input_common STATIC
drivers/tas_input.h
drivers/touch_screen.cpp
drivers/touch_screen.h
drivers/udp_client.cpp
drivers/udp_client.h
drivers/virtual_amiibo.cpp
drivers/virtual_amiibo.h
drivers/virtual_gamepad.cpp
@@ -34,8 +32,12 @@ add_library(input_common STATIC
input_poller.cpp
input_poller.h
main.cpp
main.h
)
main.h)
if (NOT PLATFORM_EMSCRIPTEN)
target_sources(input_common PRIVATE
drivers/udp_client.cpp
drivers/udp_client.h)
endif()
if (MSVC)
target_compile_options(input_common PRIVATE
+21 -1
View File
@@ -12,7 +12,6 @@
#include "input_common/drivers/mouse.h"
#include "input_common/drivers/tas_input.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/udp_client.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/drivers/virtual_gamepad.h"
#include "input_common/helpers/stick_from_buttons.h"
@@ -21,6 +20,9 @@
#include "input_common/input_mapping.h"
#include "input_common/input_poller.h"
#include "input_common/main.h"
#ifndef __EMSCRIPTEN__
#include "input_common/drivers/udp_client.h"
#endif
#ifdef ENABLE_LIBUSB
#include "input_common/drivers/gc_adapter.h"
@@ -82,7 +84,9 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
RegisterEngine("gcpad", gcadapter);
#endif
#ifndef __EMSCRIPTEN__
RegisterEngine("cemuhookudp", udp_client);
#endif
RegisterEngine("tas", tas_input);
RegisterEngine("camera", camera);
#ifdef __ANDROID__
@@ -116,7 +120,9 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
UnregisterEngine(gcadapter);
#endif
#ifndef __EMSCRIPTEN__
UnregisterEngine(udp_client);
#endif
UnregisterEngine(tas_input);
UnregisterEngine(camera);
#ifdef __ANDROID__
@@ -152,8 +158,10 @@ struct InputSubsystem::Impl {
auto gcadapter_devices = gcadapter->GetInputDevices();
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
#endif
#ifndef __EMSCRIPTEN__
auto udp_devices = udp_client->GetInputDevices();
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
#endif
#ifdef HAVE_SDL3
auto joycon_devices = joycon->GetInputDevices();
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
@@ -186,9 +194,11 @@ struct InputSubsystem::Impl {
return gcadapter;
}
#endif
#ifndef __EMSCRIPTEN__
if (engine == udp_client->GetEngineName()) {
return udp_client;
}
#endif
#ifdef HAVE_SDL3
if (engine == sdl->GetEngineName()) {
return sdl;
@@ -271,9 +281,11 @@ struct InputSubsystem::Impl {
return true;
}
#endif
#ifndef __EMSCRIPTEN__
if (engine == udp_client->GetEngineName()) {
return true;
}
#endif
if (engine == tas_input->GetEngineName()) {
return true;
}
@@ -300,7 +312,9 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
gcadapter->BeginConfiguration();
#endif
#ifndef __EMSCRIPTEN__
udp_client->BeginConfiguration();
#endif
#ifdef HAVE_SDL3
sdl->BeginConfiguration();
joycon->BeginConfiguration();
@@ -316,7 +330,9 @@ struct InputSubsystem::Impl {
#ifdef ENABLE_LIBUSB
gcadapter->EndConfiguration();
#endif
#ifndef __EMSCRIPTEN__
udp_client->EndConfiguration();
#endif
#ifdef HAVE_SDL3
sdl->EndConfiguration();
joycon->EndConfiguration();
@@ -341,7 +357,9 @@ struct InputSubsystem::Impl {
std::shared_ptr<Mouse> mouse;
std::shared_ptr<TouchScreen> touch_screen;
std::shared_ptr<TasInput::Tas> tas_input;
#ifndef __EMSCRIPTEN__
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
#endif
std::shared_ptr<Camera> camera;
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
std::shared_ptr<VirtualGamepad> virtual_gamepad;
@@ -470,7 +488,9 @@ bool InputSubsystem::IsStickInverted(const Common::ParamPackage& params) const {
}
void InputSubsystem::ReloadInputDevices() {
#ifndef __EMSCRIPTEN__
impl->udp_client.get()->ReloadSockets();
#endif
}
void InputSubsystem::BeginMapping(Polling::InputType type) {
+1 -2
View File
@@ -257,8 +257,7 @@ else()
# Bracket depth determines maximum size of a fold expression in Clang since 9c9974c3ccb6.
# And this in turns limits the size of a std::array.
$<$<CXX_COMPILER_ID:Clang>:-fbracket-depth=1024>
$<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>
)
$<$<CXX_COMPILER_ID:AppleClang>:-fbracket-depth=1024>)
endif()
create_target_directory_groups(shader_recompiler)
+1 -1
View File
@@ -31,7 +31,7 @@ struct CbufWordKey {
struct CbufWordKeyHash {
constexpr size_t operator()(const CbufWordKey& k) const noexcept {
return (size_t(k.index) << 32) ^ k.offset;
return size_t((u64(k.index) << 32) ^ u64(k.offset));
}
};
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -157,16 +160,14 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
unsigned Swizzle(u64 insn) {
const Encoding texs{insn};
const size_t encoding{texs.swizzle};
u8 const encoding = u8(texs.swizzle);
if (texs.dest_reg_b == IR::Reg::RZ) {
if (encoding >= RG_LUT.size()) {
if (encoding >= RG_LUT.size())
throw NotImplementedException("Illegal RG encoding {}", encoding);
}
return RG_LUT[encoding];
} else {
if (encoding >= RGBA_LUT.size()) {
if (encoding >= RGBA_LUT.size())
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
}
return RGBA_LUT[encoding];
}
}
@@ -1,3 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -142,16 +145,14 @@ IR::Value Sample(TranslatorVisitor& v, u64 insn) {
unsigned Swizzle(u64 insn) {
const Encoding tlds{insn};
const size_t encoding{tlds.swizzle};
u8 const encoding = u8(tlds.swizzle);
if (tlds.dest_reg_b == IR::Reg::RZ) {
if (encoding >= RG_LUT.size()) {
if (encoding >= RG_LUT.size())
throw NotImplementedException("Illegal RG encoding {}", encoding);
}
return RG_LUT[encoding];
} else {
if (encoding >= RGBA_LUT.size()) {
if (encoding >= RGBA_LUT.size())
throw NotImplementedException("Illegal RGBA encoding {}", encoding);
}
return RGBA_LUT[encoding];
}
}
+12
View File
@@ -75,3 +75,15 @@ if (NOT MSVC)
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-unused-parameter>
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-missing-field-initializers>)
endif()
if (PLATFORM_EMSCRIPTEN)
# 10GB is required at max... yikes!
target_link_options(yuzu-cmd PRIVATE
-sALLOW_MEMORY_GROWTH=1
-sINITIAL_MEMORY=33554432
-sMAXIMUM_MEMORY=10737418240
-sGLOBAL_BASE=16777216
-sEXPORTED_RUNTIME_METHODS=['FS']
-sPTHREAD_POOL_SIZE_STRICT=0
-sPTHREAD_POOL_SIZE=navigator.hardwareConcurrency)
endif()
+1
View File
@@ -30,6 +30,7 @@ Tools for Eden and other subprojects. When adding new scripts please use `#!/bin
- `find-unused-strings.sh`: Find any unused strings in the Android app (XML -> Kotlin).
- `cpp-lint.sh`: Homemade dumb C++ linter.
- `fuzzsettings.cpp`: Fuzz settings files.
- `miniserver.js`: Make a quick server that serves a page with the WASM on it, takes a single argument which is the path to the build directory containing *both* `eden-cli.js` and `eden-cli.wasm`. Run via `node.js`, `wasmtime` isn't supported.
## Android
It's recommended to run these scritps after almost any Android change, as they are relatively fast and important both for APK bloat and CI.
+127
View File
@@ -0,0 +1,127 @@
#!/usr/bin/env node
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
import { createServer } from 'http';
import { readFile } from 'fs';
import { join } from 'path';
console.log(`dont forget to run: "npm --global install @jdmichaud/dwarf-2-sourcemap" for better debugging!`);
const server = createServer((req, res) => {
console.log(`get ${req.url}`);
if (req.url === '/') {
// https://developer.mozilla.org/en-US/docs/WebAssembly/Guides/Loading_and_running
// If your browser doesn't support fetch... HAHA GET FUCKED
res.writeHead(200, {
'Content-Type': 'text/html',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
});
res.end(`<!DOCTYPE html>
<html>
<head>
<title>eden-cli</title>
</head>
<body style="margin:0;padding:0;background-color:black;color:white;font-family:Monospace,Tahoma,Arial;">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:2px;width:100%;height:100vh;">
<canvas id="canvas" oncontextmenu="event.preventDefault()" style="width:100%;height:100%;background-color:gray;"></canvas>
<div id="tty-stdout"></div>
</div>
<script>
var Module = { //do not prepend var
mainScriptUrlOrBlob: 'eden-cli.js',
arguments: ['--null-render', '--singlecore', '--filter', '*:Trace', '/home/web_user/game.nro'],
canvas: document.getElementById('canvas'),
print: (e) => {
e = e.replace('[1;31m', '<span style="color:red;font-weight:bold;">');
e = e.replace('[0;37m', '<span style="color:white;font-weight:bold;">');
e = e.replace('[1;35m', '<span style="color:pink;font-weight:bold;">');
e = e.replace('[1;33m', '<span style="color:yellow;font-weight:bold;">');
e = e.replace('[0;36m', '<span style="color:white;font-weight:bold;">');
e = e.replace('[0m', '</span>');
document.getElementById('tty-stdout').innerHTML += \`\${e}</br>\`;
},
printErr: (e) => {
document.getElementById('tty-stdout').innerHTML += \`<span style="color:red">\${e}</span></br>\`;
},
// not a wasm func but idc
printInternal: (e) => {
document.getElementById('tty-stdout').innerHTML += \`<span style="color:white">Internal WASM: \${e}</span></br>\`;
},
preInit: [
() => {
// copy just most relevant :)
Module.FS.mkdir('/home/web_user/.local');
Module.FS.mkdir('/home/web_user/.local/share');
Module.FS.mkdir('/home/web_user/.local/share/eden');
Module.FS.mkdir('/home/web_user/.config');
Module.FS.mkdir('/home/web_user/.config/eden');
Module.FS.createDataFile('/home/web_user', 'game.nro', gameNroFileBuffer, true, false, true);
}
],
onRuntimeInitialized: () => { Module.printInternal("runtime ok"); },
setStatus: (e) => { Module.printInternal(e); },
monitorRunDependencies: (e) => { Module.printInternal("monitor deps: " + e); },
__wasm_call_ctors: () => { Module.printInternal("ctors beep"); },
};
var gameNroFileBuffer = {};
Module.printInternal(\`Atomics: \${window.Atomics}, SharedArrayBuffer: \${window.SharedArrayBuffer}\`);
Module.printInternal("trying to load script (if it hangs here check console)");
fetch('game.nro').then((resp) => {
if (!resp.ok)
throw Error(\`\${resp.status}\`);
return resp.bytes();
}).then((buffer) => {
gameNroFileBuffer = buffer;
// load the thingy AFTER loading the nro
Module.printInternal(\`loading from ${build_dir}/\${Module.mainScriptUrlOrBlob}\`);
var script = document.createElement('script');
script.src = '/eden-cli.js';
script.onload = (e) => Module.printInternal(\`loaded WASMy script \${e}!!\`);
document.head.appendChild(script);
}).catch(Module.printErr);
</script>
</body>
</html>`);
} else if (req.url === '/eden-cli.js') {
readFile(join(build_dir, 'eden-cli.js'), (err, content) => {
res.writeHead(200, {
'Content-Type': 'application/javascript',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
});
res.end(content, 'utf-8');
});
} else if (req.url === '/eden-cli.wasm') {
readFile(join(build_dir, 'eden-cli.wasm'), (err, content) => {
res.writeHead(200, {
'Content-Type': 'application/wasm',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
});
res.end(content);
});
} else if (req.url === '/game.nro') {
readFile(nro_file, (err, content) => {
res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Cross-Origin-Opener-Policy': 'same-origin',
'Cross-Origin-Embedder-Policy': 'require-corp'
});
res.end(content);
});
} else {
res.writeHead(404, {});
res.end('', 'utf-8');
}
});
const build_dir = process.argv[2];
const nro_file = process.argv[3];
if (typeof build_dir == "undefined" || typeof nro_file == "undefined") {
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} [build directory] [NRO file]`);
} else {
server.listen(2210, () => {
console.log(`${process.argv[0]} ${process.argv[1]} http://localhost:2210`);
console.log(`build dir = ${build_dir}`);
});
}