Compare commits

..

9 Commits

Author SHA1 Message Date
lizzie 9b3d87b367 Trigger Build 2026-08-12 19:20:13 +00:00
lizzie d5bd6630d5 Trigger Build 2026-08-12 18:07:02 +00:00
lizzie 2f7b9a096c aaaaaa chromeos
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-08-12 16:53:39 +00:00
lizzie aa361e8600 bring back evil strings 2026-08-12 16:13:10 +00:00
lizzie 7de5db10ad ok 2026-08-09 17:00:57 +02:00
lizzie 1fdc6c13ae fix excepts 2026-08-09 17:00:57 +02:00
lizzie f4755a2a42 Trigger Build 2026-08-09 17:00:57 +02:00
lizzie b96b9107f1 Trigger Build 2026-08-09 17:00:57 +02:00
lizzie 966c2bd383 fx 2026-08-09 17:00:57 +02:00
42 changed files with 1111 additions and 952 deletions
+2 -2
View File
@@ -3,7 +3,7 @@ description: File a bug report
body:
- type: markdown
attributes:
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with Eden.
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu.
- type: checkboxes
attributes:
label: Is there an existing issue for this?
@@ -43,7 +43,7 @@ body:
id: log
attributes:
label: Log File
description: A log file will help our developers to better diagnose and fix the issue. Instructions can be found [here](https://git.eden-emu.dev/eden-emu/eden/src/branch/master/docs/user/HowToAccessLogs.md).
description: A log file will help our developers to better diagnose and fix the issue. Instructions can be found [here](https://yuzu-emu.org/help/reference/log-files).
validations:
required: true
- type: textarea
+2 -2
View File
@@ -4,7 +4,7 @@ labels: "request"
body:
- type: markdown
attributes:
value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make Eden better.
value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make yuzu better.
- type: checkboxes
attributes:
label: Is there an existing issue for this?
@@ -23,6 +23,6 @@ body:
id: why-feature
attributes:
label: Why would this feature be useful?
description: A brief description of why this feature would make Eden better.
description: A brief description of why this feature would make yuzu better.
validations:
required: true
+25 -15
View File
@@ -28,24 +28,27 @@ if (NOT DEFINED ARCHITECTURE)
endif()
# Needed for FFmpeg w/ VAAPI and DRM
if (OPENBSD)
if (PLATFORM_OPENBSD)
# OpenBSD 7.8 broke libcxx when upgrading, so we must define the PSTL backend manually
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R6/include -D_LIBCPP_PSTL_BACKEND_SERIAL=1")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R6/lib")
elseif (NETBSD)
elseif (PLATFORM_NETBSD)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${CMAKE_SYSROOT}/usr/X11R7/include -I${CMAKE_SYSROOT}/usr/pkg/include/c++/v1")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_SYSROOT}/usr/X11R7/lib")
endif()
# NetBSD: Fun for the whole family!
if (NETBSD)
if (PLATFORM_NETBSD)
set(ENV{PKG_CONFIG_PATH} "${PKG_CONFIG_PATH}:${CMAKE_SYSROOT}/usr/pkg/lib/ffmpeg7/pkgconfig")
endif()
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "LINUX" OFF)
cmake_dependent_option(YUZU_STATIC_ROOM "Build a static room executable only (CI only)" OFF "PLATFORM_LINUX" OFF)
if (YUZU_STATIC_ROOM)
set(YUZU_ROOM ON)
set(YUZU_ROOM_STANDALONE ON)
# disable e v e r y t h i n g
set(ENABLE_QT OFF)
set(YUZU_CMD OFF)
@@ -221,7 +224,7 @@ endif()
# ffmpeg
option(YUZU_USE_BUNDLED_FFMPEG "Download bundled FFmpeg" ${EXT_DEFAULT})
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${SOLARIS}" "NOT WIN32 AND NOT ANDROID" OFF)
cmake_dependent_option(YUZU_USE_EXTERNAL_FFMPEG "Build FFmpeg from external source" "${PLATFORM_SUN}" "NOT WIN32 AND NOT ANDROID" OFF)
# sirit
set(BUNDLED_SIRIT_DEFAULT OFF)
@@ -232,7 +235,7 @@ endif()
option(YUZU_USE_BUNDLED_SIRIT "Download bundled sirit" ${BUNDLED_SIRIT_DEFAULT})
# FreeBSD 15+ has libusb, versions below should disable it
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR LINUX OR FREEBSD OR APPLE" OFF)
cmake_dependent_option(ENABLE_LIBUSB "Enable the use of LibUSB" ON "WIN32 OR PLATFORM_LINUX OR PLATFORM_FREEBSD OR APPLE" OFF)
cmake_dependent_option(ENABLE_OPENGL "Enable OpenGL" ON "NOT (WIN32 AND ARCHITECTURE_arm64) AND NOT APPLE" OFF)
mark_as_advanced(FORCE ENABLE_OPENGL)
@@ -246,7 +249,7 @@ option(YUZU_TESTS "Compile tests" "${BUILD_TESTING}")
# Install udev rules on Linux (mainly for gyros)
# Only acts on joysticks and nothing else.
cmake_dependent_option(YUZU_INSTALL_UDEV_RULES "Install udev rules for gyro access" OFF "LINUX" OFF)
cmake_dependent_option(YUZU_INSTALL_UDEV_RULES "Install udev rules for gyro access" OFF "PLATFORM_LINUX" OFF)
option(YUZU_DOWNLOAD_ANDROID_VVL "Download validation layer binary for android" ON)
@@ -254,9 +257,12 @@ option(YUZU_LEGACY "Apply patches that improve compatibility with older GPUs (e.
option(NIGHTLY_BUILD "Use Nightly qualifiers in the update checker and build metadata" OFF)
cmake_dependent_option(YUZU_ROOM "Enable dedicated room functionality" ON "NOT ANDROID" OFF)
cmake_dependent_option(YUZU_ROOM_STANDALONE "Enable standalone room executable" ON "YUZU_ROOM" OFF)
cmake_dependent_option(YUZU_CMD "Compile the eden-cli executable" ON "NOT ANDROID" OFF)
cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR LINUX" OFF)
cmake_dependent_option(YUZU_CRASH_DUMPS "Compile crash dump (Minidump) support" OFF "WIN32 OR PLATFORM_LINUX" OFF)
option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" ON)
set(YUZU_TZDB_PATH "" CACHE STRING "Path to a pre-downloaded timezone database")
@@ -266,7 +272,7 @@ cmake_dependent_option(YUZU_USE_BUNDLED_MOLTENVK "Download bundled MoltenVK lib"
option(YUZU_DISABLE_LLVM "Disable LLVM (useful for CI)" OFF)
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL OFF)
if (EXT_DEFAULT OR SOLARIS OR OPENBSD)
if (EXT_DEFAULT OR PLATFORM_SUN OR PLATFORM_OPENBSD)
set(DEFAULT_YUZU_USE_BUNDLED_OPENSSL ON)
endif()
@@ -289,12 +295,16 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE
endif()
endif()
if (ARCHITECTURE_arm64 AND (ANDROID OR LINUX))
if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX))
set(HAS_NCE 1)
add_compile_definitions(HAS_NCE=1)
endif()
if (UNIX AND NOT (LINUX OR WIN32))
if (YUZU_ROOM)
add_compile_definitions(YUZU_ROOM)
endif()
if (UNIX AND NOT (PLATFORM_LINUX OR WIN32))
if(CXX_APPLE OR CXX_CLANG)
# libc++ has stop_token and jthread as experimental
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library")
@@ -408,7 +418,7 @@ if (Boost_ADDED)
if (NOT MSVC OR CXX_CLANG)
# boost sucks
if (SOLARIS)
if (PLATFORM_SUN)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-pthreads>)
endif()
@@ -512,9 +522,9 @@ elseif (WIN32)
# PSAPI is the Process Status API
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version crypt32 rpcrt4 gdi32 wldap32 mswsock)
endif()
elseif (MANAGARM)
elseif (PLATFORM_MANAGARM)
set(PLATFORM_LIBRARIES iconv intl)
elseif (HAIKUOS)
elseif (PLATFORM_HAIKU)
# Haiku is so special :)
set(PLATFORM_LIBRARIES bsd /boot/system/lib/libnetwork.so)
elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
@@ -608,7 +618,7 @@ if (ENABLE_QT)
# Best practice is to ask for all components at once, so they are from the same version
set(YUZU_QT_COMPONENTS Core Widgets Charts Concurrent Gui)
if (LINUX OR FREEBSD)
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
list(APPEND YUZU_QT_COMPONENTS DBus)
# yes Qt, we get it
set(QT_NO_PRIVATE_MODULE_WARNING ON)
+1 -1
View File
@@ -13,7 +13,7 @@ find_package_handle_standard_args(Opus
VERSION_VAR OPUS_VERSION
)
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::OPUS)
endif()
+1 -1
View File
@@ -14,7 +14,7 @@ find_package_handle_standard_args(enet
VERSION_VAR ENET_VERSION
)
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::ENET)
endif()
+1 -1
View File
@@ -14,7 +14,7 @@ find_package_handle_standard_args(libusb
VERSION_VAR LIBUSB_VERSION
)
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::LIBUSB)
endif()
+1 -1
View File
@@ -13,7 +13,7 @@ else()
find_package(PkgConfig QUIET)
pkg_search_module(LZ4 QUIET IMPORTED_TARGET liblz4)
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::LZ4)
endif()
+2 -2
View File
@@ -65,6 +65,8 @@ These options control executables and build flavors.
**Desktop only**:
- `YUZU_CMD` (ON) Compile the SDL-based frontend (eden-cli)
- `YUZU_ROOM` (OFF) Compile dedicated room functionality into the main executable
- `YUZU_ROOM_STANDALONE` (OFF) Compile a separate executable for room functionality
- `YUZU_STATIC_ROOM` (OFF) Compile the room executable *only* as a static, portable executable
- This is only usable on Alpine Linux.
@@ -99,7 +101,5 @@ The following options were a part of Eden at one point, but have since been reti
- `YUZU_USE_CPM` - This option once had a purpose, but that purpose has long since passed us by. *All* builds use CPMUtil to manage dependencies now.
- If you want to *force* the usage of system dependencies, use `-DCPMUTIL_FORCE_SYSTEM=ON`.
- `YUZU_USE_EXTERNAL_SDL` - This is now handled automatically. It was included even after CPM for purposes that have not applied for a very long time.
- `YUZU_ROOM` - Room functionality is now built-in into eden-cli unconditionally.
- `YUZU_ROOM_STANDALONE` - Likewise.
See `src/dynarmic/CMakeLists.txt` for additional options--usually, these don't need changed
+22 -37
View File
@@ -1,47 +1,32 @@
# User Handbook - Command Line
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments and share almost the same accepted arguments.
There are two main applications, an SDL-based app (`eden-cli`) and a Qt based app (`eden`); both accept command line arguments.
## eden
- `./eden <path>`: Running with a single argument and nothing else, will make the emulator look for the given file and load it, this behaviour is similar to `eden-cli`; allows dragging and dropping games into the application.
- `--hlaunch`: Launch homebrew launcher `nx-hbloader`.
- `-g <path>`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
- `-f`: Use fullscreen.
- `-u <number>`: Select the index of the user to load as.
- `-input-profile <name>`: Specifies input profile name to use (for player #0 only).
- `-qlaunch`: Launch QLaunch.
- `-hlaunch`: Launch homebrew launcher `nx-hbloader`.
- Requires a copy of Atmosphere to be extracted onto `sdmc`.
- This is a shorthand for `<eden folder>/sdmc/atmosphere/hbl.nsp`.
- `--setup`: Launch setup applet.
- `-q/--qlaunch`: Launch QLaunch.
- `-d/--debug`: Enter debug mode, allow gdb stub at port `1234`
- `-c/--config`: Specify alternate configuration file.
- `-f/--fullscreen`: Set fullscreen.
- `-h/--help`: Display help.
- `-g/--game`: Alternate way to specify what to load, overrides. However let it be noted that arguments that use `-` will be treated as options/ignored, if your game, for some reason, starts with `-`, in order to safely handle it you may need to specify it as an argument.
- `-m/--multiplayer`: Specify multiplayer options.
- `-p/--program`: Specify the program arguments to pass (optional).
- `-u/--user`: Select the index of the user to load as.
- `-v/--version`: Display version and quit.
- `-i/--input-profile`: Specifies input profile name to use (for player #0 only).
- `-n/--null-render`: Forces the usage of the "Null" render backend irrespective of settings.
- `-x/--filter`: Sets the debug log filter irrespective of settings.
- `-s/--singlecore`: Forces single-core regardless of settings.
- `-l/--log-file`: The file for storing the room log.
- `-H/--headless`: Force headless mode (no GUI). Currently only used for rooms.
- `-setup`: Launch setup applet.
Room settings:
- `-N/--name`: The name of the room.
- `-D/--description`: The room description.
- `-S/--bind-address`: The bind address for the room.
- `-P/--port`: The port used for the room.
- `-M/--max-members`: The maximum number of players for this room.
- `-W/--password`: The password for the room.
- `-G/--preferred-game`: The preferred game for this room.
- `-I/--preferred-game-id`: The preferred game-id for this room.
- `-U/--username`: The username used for announce.
- `-T/--token`: The token used for announce.
- `-A/--web-api-url`: yuzu Web API url.
- `-B/--ban-list-file`: The file for storing the room ban list.
- `-h/--help`: Display this help and exit.
- `-v/--version`: Output version information and exit.,
## eden-cli
If the name and description of the room is specified, then it will default to headless mode if not already specified.
Old settings `-hlaunch`, `-qlaunch` and `-setup` are recognized independently. They're kept for backwards compatibility with shortcuts made before the change. Using one of these makes the parser immediately halt and ignore every other option.
- `--debug/-d`: Enter debug mode, allow gdb stub at port `1234`
- `--config/-c`: Specify alternate configuration file.
- `--fullscreen/-f`: Set fullscreen.
- `--help/-h`: Display help.
- `--game/-g`: Specify the game to run.
- `--multiplayer/-m`: Specify multiplayer options.
- `--program/-p`: Specify the program arguments to pass (optional).
- `--user/-u`: Specify the user index.
- `--version/-v`: Display version and quit.
- `--input-profile/-i`: Specifies input profile name to use (for player #0 only).
- `--null-render/-n`: Forces the usage of the "Null" render backend irrespective of settings.
- `--filter/-x`: Sets the debug log filter irrespective of settings.
- `--singlecore/-s`: Forces single-core regardless of settings.
+1 -1
View File
@@ -164,7 +164,7 @@ if (NOT ANDROID)
AddJsonPackage(sdl3)
else()
message(STATUS "Using bundled SDL3")
if (FREEBSD)
if (PLATFORM_FREEBSD)
set(BUILD_SHARED_LIBS ON)
endif()
AddJsonPackage(sdl3-ci)
+80 -23
View File
@@ -1,46 +1,45 @@
# SPDX-FileCopyrightText: Copyright 2026 crueter
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: LGPL-3.0-or-later
## DetectPlatform ##
# This is a small helper that sets platform variables for various
# This is a small helper that sets PLATFORM_<platform> variables for various
# operating systems and distributions. Note that Apple, Windows, Android, etc.
# are not covered, as CMake already does that for us.
# It also sets CXX_<compiler> for the C++ compiler.
# Furthermore, some platforms have really silly requirements/quirks, so this
# also does a few of those.
# This module contains contributions from the Eden Emulator Project,
# notably from crueter and Lizzie.
if (${CMAKE_SYSTEM_NAME} STREQUAL "SunOS")
set(SOLARIS ON)
set(PLATFORM_SUN ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenOrbis")
set(OPENORBIS ON)
set(PLATFORM_PS4 ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "managarm")
set(MANAGARM ON)
set(PLATFORM_MANAGARM ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
set(PLATFORM_FREEBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "OpenBSD")
set(PLATFORM_OPENBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "NetBSD")
set(PLATFORM_NETBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "DragonFly")
set(PLATFORM_DRAGONFLYBSD ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Haiku")
set(HAIKUOS ON)
endif()
# BSD
if (DEFINED BSD)
if ("${BSD}" STREQUAL "DragonFlyBSD")
set(DRAGONFLYBSD ON)
elseif ("${BSD}" STREQUAL "FreeBSD")
set(FREEBSD ON)
elseif ("${BSD}" STREQUAL "OpenBSD")
set(OPENBSD ON)
elseif ("${BSD}" STREQUAL "NetBSD")
set(NETBSD ON)
endif()
set(PLATFORM_HAIKU ON)
elseif (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(PLATFORM_LINUX ON)
endif()
# dumb heuristic to detect msys2
if (CMAKE_COMMAND MATCHES "msys64")
set(MSYS2 ON)
set(PLATFORM_MSYS ON)
endif()
# compiler checks
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
set(CXX_CLANG ON)
if (MSVC)
@@ -48,6 +47,8 @@ if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
endif()
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
set(CXX_GCC ON)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
set(CXX_CL ON)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM")
set(CXX_ICC ON)
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
@@ -60,13 +61,37 @@ if(MINGW AND CXX_CLANG)
set(CMAKE_SYSTEM_VERSION 10.0.0)
endif()
# NB: this does not account for SPARC
if (PLATFORM_SUN)
# Terrific OpenIndiana pkg shenanigans
list(APPEND CMAKE_PREFIX_PATH
"${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake")
list(APPEND CMAKE_MODULE_PATH
"${CMAKE_SYSROOT}/usr/lib/qt/6.6/lib/amd64/cmake")
# Amazing - absolutely incredible
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SYSROOT}/usr/lib/amd64/cmake")
# For some mighty reason, doing a normal release build sometimes
# may not trigger the proper -O3 switch to materialize
if (CMAKE_BUILD_TYPE MATCHES "Release")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3")
endif()
if (CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O2")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2")
endif()
endif()
# MSYS2 utilities
# Sometimes, PkgConfig modules will incorrectly reference / when CMake
# wants you to reference it as C:/msys64/. This function corrects that.
# Example in a Find module:
#[[
if (MSYS2)
if (PLATFORM_MSYS)
FixMsysPath(PkgConfig::OPUS)
endif()
]]
@@ -91,9 +116,41 @@ function(FixMsysPath target)
INTERFACE_INCLUDE_DIRECTORIES ${include_dir})
endfunction()
# Saves linking time
# MSYSTEM handling + program_path
if (PLATFORM_MSYS)
# really, really dumb heuristic to detect what environment we are in
macro(system var)
if (CMAKE_COMMAND MATCHES ${var})
set(MSYSTEM ${var})
endif()
endmacro()
system(mingw64)
system(clang64)
system(clangarm64)
system(ucrt64)
if (NOT DEFINED MSYSTEM)
set(MSYSTEM msys2)
endif()
# We generally want to prioritize environment-specific binaries if possible
# some, like autoconf, are not present on environments besides msys2 though
set(CMAKE_PROGRAM_PATH C:/msys64/${MSYSTEM}/bin C:/msys64/usr/bin)
set(ENV{PKG_CONFIG_PATH} C:/msys64/${MSYSTEM}/lib/pkgconfig)
endif()
# This saves a truly ridiculous amount of time during linking
# In my tests, without this, Eden takes 2 mins, with this, it takes 3-5 seconds
# or on GitHub Actions, 10 minutes -> 3 seconds
if (MINGW)
set(MINGW_FLAGS "-Wl,--strip-all -Wl,--gc-sections")
set(CMAKE_EXE_LINKER_FLAGS_RELEASE
"${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${MINGW_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()
+5 -5
View File
@@ -56,7 +56,7 @@ if (NOT YUZU_USE_BUNDLED_FFMPEG)
endif()
endif()
if (OPENORBIS OR MANAGARM)
if (PLATFORM_PS4 OR PLATFORM_MANAGARM)
# 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)
@@ -75,7 +75,7 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID)
if(X11_FOUND)
if (NOT APPLE)
# In Solaris needs explicit linking for ffmpeg which links to /lib/amd64/libX11.so
if(SOLARIS)
if(PLATFORM_SUN)
list(APPEND FFmpeg_HWACCEL_LIBRARIES
X11
"${CMAKE_SYSROOT}/usr/lib/xorg/amd64/libdrm.so")
@@ -158,7 +158,7 @@ elseif (UNIX AND NOT DEFINED FFmpeg_IS_CROSS_COMPILING AND NOT ANDROID)
endif()
endif()
if (OPENORBIS)
if (PLATFORM_PS4)
list(APPEND FFmpeg_CROSS_COMPILE_LIBS
-lkernel
-lSceUserService
@@ -172,7 +172,7 @@ if (OPENORBIS)
--extra-cxxflags=${CMAKE_SYSROOT}/usr/include
--extra-libs="${FFmpeg_CROSS_COMPILE_LIBS}"
)
elseif (MANAGARM)
elseif (PLATFORM_MANAGARM)
# Required for proper stuff
list(APPEND FFmpeg_CROSS_COMPILE_FLAGS
--disable-pthreads
@@ -299,7 +299,7 @@ else()
set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
# BSD make or Solaris make don't support ffmpeg make-j8
if (LINUX OR ANDROID OR APPLE OR WIN32 OR FREEBSD)
if (PLATFORM_LINUX OR ANDROID OR APPLE OR WIN32 OR PLATFORM_FREEBSD)
set(FFmpeg_MAKE_ARGS -j${SYSTEM_THREADS})
else()
set(FFmpeg_MAKE_ARGS "")
+1 -1
View File
@@ -11,7 +11,7 @@ if (NOT libusb_ADDED)
endif()
# TODO: *BSD fails to compile--may need different configs/symbols
if (MINGW OR LINUX OR APPLE)
if (MINGW OR PLATFORM_LINUX OR APPLE)
set(LIBUSB_FOUND ON CACHE BOOL "libusb is present" FORCE)
set(LIBUSB_VERSION "1.0.24" CACHE STRING "libusb version string" FORCE)
+10 -1
View File
@@ -193,7 +193,7 @@ else()
if (ARCHITECTURE_x86_64)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mcx16>)
if (LINUX OR FREEBSD)
if (PLATFORM_LINUX OR PLATFORM_FREEBSD)
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:-mtls-dialect=gnu2>)
endif()
endif()
@@ -251,6 +251,15 @@ endif()
add_subdirectory(common)
add_subdirectory(network)
if (YUZU_ROOM)
add_subdirectory(dedicated_room)
endif()
if (YUZU_ROOM_STANDALONE)
add_subdirectory(yuzu_room_standalone)
set_target_properties(yuzu-room PROPERTIES OUTPUT_NAME "eden-room")
endif()
if (YUZU_STATIC_ROOM)
return()
endif()
+1 -5
View File
@@ -133,8 +133,6 @@ add_library(
typed_address.h
uint128.h
unique_function.h
program_args.cpp
program_args.h
random.cpp
random.h
uuid.cpp
@@ -156,9 +154,7 @@ if(WIN32)
windows/timer_resolution.h)
target_link_libraries(common PRIVATE ntdll)
endif()
if (MSVC)
target_link_libraries(common PRIVATE getopt)
endif()
if(NOT WIN32)
target_sources(common PRIVATE signal_chain.cpp signal_chain.h)
endif()
-280
View File
@@ -1,280 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <regex>
#include "common/assert.h"
#include "common/program_args.h"
#include "common/logging.h"
#include "common/scm_rev.h"
#include "common/string_util.h"
#include "network/room.h"
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
#include <shellapi.h>
#include "common/windows/timer_resolution.h"
#endif
#undef _UNICODE
#include <getopt.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
namespace Common {
static void PrintHelp(const char* argv0) {
LOG_INFO(Frontend, "Usage: {} [options] <filename>\n"
"Core options:\n"
"-c, --config Load the specified configuration file\n"
"-f, --fullscreen Start in fullscreen mode\n"
"-g, --game File path of the game to load\n"
"-h, --help Display this help and exit\n"
"-m, --multiplayer=nick:password@address:port Nickname, password, address and port for multiplayer\n"
"-p, --program Pass following string as arguments to executable\n"
"-u, --user Select a specific user profile from 0 to 7\n"
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
"-i, --input-profile Specifies input profile name to use (for player #0 only)\n"
"-n, --null-render Forces the usage of the \"Null\" render backend irrespective of settings\n"
"-x, --filter Sets the debug log filter irrespective of settings\n"
"-s, --singlecore Forces single-core regardless of settings\n"
"Shared options:\n"
"-l, --log-file The file for storing the room log\n"
"-H, --headless Force headless mode (no GUI). Currently only used for rooms\n"
"Room options:\n"
"-N, --name The name of the room\n"
"-D, --description The room description\n"
"-S, --bind-address The bind address for the room\n"
"-P, --port The port used for the room\n"
"-M, --max-members The maximum number of players for this room\n"
"-W, --password The password for the room\n"
"-G, --preferred-game The preferred game for this room\n"
"-I, --preferred-game-id The preferred game-id for this room\n"
"-U, --username The username used for announce\n"
"-T, --token The token used for announce\n"
"-A, --web-api-url yuzu Web API url\n"
"-B, --ban-list-file The file for storing the room ban list\n"
"Misc. options:\n"
"-h, --help Display this help and exit\n"
"-v, --version Output version information and exit\n",
argv0);
}
static void PrintVersion() {
LOG_INFO(Frontend, "Eden {} {}, Libnetwork: {}", Common::g_scm_branch, Common::g_scm_desc, Network::network_version);
}
int ParseArguments(ProgramArguments& args, int argc, char *argv[]) {
int option_index = 0;
#ifdef _WIN32
int argc_w;
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
if (argv_w == nullptr) {
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
return -1;
}
#endif
static struct option long_options[] = {
// clang-format off
{"debug", no_argument, 0, 'd'},
{"config", required_argument, 0, 'c'},
{"fullscreen", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"game", required_argument, 0, 'g'},
{"multiplayer", required_argument, 0, 'm'},
{"program", optional_argument, 0, 'p'},
{"user", required_argument, 0, 'u'},
{"version", no_argument, 0, 'v'},
{"input-profile", no_argument, 0, 'i'},
{"null-render", no_argument, 0, 'n'},
{"singlecore", no_argument, 0, 's'},
{"filter", no_argument, 0, 'x'},
{"log-file", required_argument, 0, 'l'},
{"headless", required_argument, 0, 'H'},
{"room-name", required_argument, 0, 'N'},
{"room-description", required_argument, 0, 'D'},
{"bind-address", required_argument, 0, 'S'},
{"port", required_argument, 0, 'P'},
{"max-members", required_argument, 0, 'M'},
{"password", required_argument, 0, 'W'},
{"preferred-game", required_argument, 0, 'G'},
{"preferred-game-id", required_argument, 0, 'I'},
{"username", optional_argument, 0, 'U'},
{"token", required_argument, 0, 'T'},
{"web-api-url", required_argument, 0, 'A'},
{"ban-list-file", required_argument, 0, 'B'},
// Entry option
{"room", no_argument, 0, 0},
{"hlaunch", no_argument, 0, 500},
{"qlaunch", no_argument, 0, 'q'},
{"setup", no_argument, 0, 502},
{0, 0, 0, 0},
// clang-format on
};
// Kept for compatibility!
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-hlaunch") == 0) {
args.should_launch_hlaunch = true;
return 0;
} else if (strcmp(argv[i], "-qlaunch") == 0) {
args.should_launch_qlaunch = true;
return 0;
} else if (strcmp(argv[i], "-setup") == 0) {
args.should_launch_setup = true;
return 0;
}
}
// Preserves drag and drop functionality (i.e ./eden <game path>)
[[maybe_unused]] char *endarg = nullptr;
while (optind < argc) {
int arg = getopt_long(argc, argv, "g:fhvcip::c:u:d:", long_options, &option_index);
if (arg != -1) {
switch (arg) {
case 'd':
args.override_gdb_port = uint16_t(atoi(optarg));
break;
case 'c':
args.config_path = optarg;
break;
case 'f':
args.fullscreen = true;
LOG_INFO(Frontend, "Starting in fullscreen mode...");
break;
case 'h':
PrintHelp(argv[0]);
return 0;
case 'g':
args.filepath = std::string(optarg);
break;
case 'i': {
args.input_profile = std::string(optarg);
break;
}
case 'm': {
args.use_multiplayer = true;
const std::string str_arg(optarg);
// regex to check if the format is nickname:password@ip:port
// with optional :password
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
if (!std::regex_match(str_arg, re)) {
LOG_ERROR(Frontend, "Wrong format for option --multiplayer");
return -1;
} else {
std::smatch match;
std::regex_search(str_arg, match, re);
ASSERT(match.size() == 5);
args.nickname = match[1];
args.password = match[2];
args.address = match[3];
if (!match[4].str().empty()) {
args.port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
}
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
if (!std::regex_match(args.nickname, nickname_re)) {
LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
return -1;
} else {
if (args.address.empty()) {
LOG_ERROR(Frontend, "Address to room must not be empty");
return -1;
}
}
}
break;
}
case 'p':
args.program_args.assign(optarg);
break;
case 'u':
args.selected_user = atoi(optarg);
break;
case 'v':
PrintVersion();
break;
case 'n':
args.force_null_render = true;
break;
case 's':
args.force_single_core = true;
break;
case 'x':
args.log_filter.emplace(optarg);
break;
// shared
case 'l':
args.log_file.assign(optarg);
break;
case 'H':
args.headless.emplace(true);
break;
// room
case 'N':
args.room_name.assign(optarg);
break;
case 'D':
args.room_description.assign(optarg);
break;
case 'S':
args.bind_address.assign(optarg);
break;
case 'P': {
auto const value = strtoul(optarg, &endarg, 0);
if (value <= USHRT_MAX) {
args.port = value;
} else {
LOG_ERROR(Frontend, "port must be between 0-{}", USHRT_MAX);
}
break;
}
case 'M': {
auto const value = strtoul(optarg, &endarg, 0);
if (value >= 2 && value <= Network::MaxConcurrentConnections) {
args.max_members = value;
} else {
LOG_ERROR(Frontend, "max members must be between 2-{}", value, Network::MaxConcurrentConnections);
}
break;
}
case 'W':
args.password.assign(optarg);
break;
case 'G':
args.preferred_game.assign(optarg);
break;
case 'I':
args.preferred_game_id = strtoull(optarg, &endarg, 16);
break;
case 'U':
args.nickname.assign(optarg);
break;
case 'T':
args.token.assign(optarg);
break;
case 'A':
args.web_api_url.assign(optarg);
break;
case 'B':
args.ban_list_file.assign(optarg);
break;
}
} else {
#ifdef _WIN32
args.filepath = Common::UTF16ToUTF8(argv_w[optind]);
#else
args.filepath = argv[optind];
#endif
optind++;
}
}
#ifdef _WIN32
LocalFree(argv_w);
#endif
return 0;
}
}
-49
View File
@@ -1,49 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <string>
#include <optional>
#include "common/common_types.h"
namespace Common {
inline constexpr u16 DEFAULT_ROOM_PORT = 24872;
struct ProgramArguments {
std::optional<std::string> config_path{};
std::optional<std::string> log_filter{};
std::string nickname{};
std::string password{};
std::string address{};
std::string input_profile{};
std::string filepath{};
std::string program_args{};
std::string room_name{};
std::string room_description{};
std::string preferred_game{};
std::string username{};
std::string token{};
std::string web_api_url{};
std::string ban_list_file{};
std::string log_file = "eden-room.log";
std::string bind_address{};
std::optional<int> selected_user{};
std::optional<u16> override_gdb_port{};
std::optional<bool> headless{};
u64 preferred_game_id = 0;
u32 max_members = 16;
u16 port = DEFAULT_ROOM_PORT;
bool use_multiplayer = false;
bool fullscreen = false;
bool force_null_render = false;
bool force_single_core = false;
bool should_launch_qlaunch = false;
bool should_launch_hlaunch = false;
bool should_launch_setup = false;
};
int ParseArguments(ProgramArguments& args, int argc, char *argv[]);
}
+1 -2
View File
@@ -24,14 +24,13 @@
// You must ensure this matches with src/common/x64/xbyak.h on root dir
#include <ankerl/unordered_dense.h>
#include <boost/unordered_map.hpp>
#define XBYAK_NO_EXCEPTION 1
#define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set
#define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map
#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap
#include <xbyak/xbyak.h>
#include <xbyak/xbyak_util.h>
#include <xbyak/xbyak.h>
namespace Common::X64 {
constexpr size_t RegToIndex(const Xbyak::Reg& reg) {
+1 -1
View File
@@ -1173,7 +1173,7 @@ add_library(core STATIC
if (ENABLE_WIFI_SCAN)
target_sources(core PRIVATE internal_network/wifi_scanner.cpp)
if (LINUX)
if (PLATFORM_LINUX)
target_link_libraries(core PRIVATE iw)
endif()
else()
+27
View File
@@ -0,0 +1,27 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2017 Citra Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
add_library(yuzu-room STATIC EXCLUDE_FROM_ALL
yuzu_room.cpp
yuzu_room.h
yuzu_room.rc)
target_link_libraries(yuzu-room PRIVATE common network)
if (ENABLE_WEB_SERVICE)
target_compile_definitions(yuzu-room PRIVATE ENABLE_WEB_SERVICE)
target_link_libraries(yuzu-room PRIVATE web_service)
endif()
target_link_libraries(yuzu-room PRIVATE
OpenSSL::SSL
OpenSSL::Crypto)
if (MSVC)
target_link_libraries(yuzu-room PRIVATE getopt)
endif()
target_link_libraries(yuzu-room PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
create_target_directory_groups(yuzu-room)
+394
View File
@@ -0,0 +1,394 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
// SPDX-FileCopyrightText: Copyright yuzu/Citra Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include <chrono>
#include <fstream>
#include <iostream>
#include <memory>
#include <regex>
#include <string>
#include <thread>
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
#include <shellapi.h>
#endif
#include <openssl/evp.h>
#include "common/common_types.h"
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging.h"
#include "common/scm_rev.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "core/core.h"
#include "network/announce_multiplayer_session.h"
#include "network/network.h"
#include "network/room.h"
#include "network/verify_user.h"
#ifdef ENABLE_WEB_SERVICE
#include "web_service/verify_user_jwt.h"
#endif
#undef _UNICODE
#include <getopt.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif
#include "yuzu_room.h"
static void PrintHelp(const char* argv0) {
LOG_INFO(Network,
"Usage: {}"
" [options] <filename>\n"
"-n, --room-name The name of the room\n"
"-d, --room-description The room description\n"
"-s, --bind-address The bind address for the room\n"
"-p, --port The port used for the room\n"
"-m, --max-members The maximum number of players for this room\n"
"-w, --password The password for the room\n"
"-g, --preferred-game The preferred game for this room\n"
"-i, --preferred-game-id The preferred game-id for this room\n"
"-u, --username The username used for announce\n"
"-t, --token The token used for announce\n"
"-a, --web-api-url yuzu Web API url\n"
"-b, --ban-list-file The file for storing the room ban list\n"
"-l, --log-file The file for storing the room log\n"
"-h, --help Display this help and exit\n"
"-v, --version Output version information and exit\n",
argv0);
}
static void PrintVersion() {
LOG_INFO(Network, "Eden dedicated room {} {} Libnetwork: {}", Common::g_scm_branch,
Common::g_scm_desc, Network::network_version);
}
/// The magic text at the beginning of a yuzu-room ban list file.
static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
static constexpr char token_delimiter{':'};
static void PadToken(std::string& token) {
std::array<unsigned char, 512> output{};
std::array<unsigned char, 2048> roundtrip{};
for (size_t i = 0; i < 3; i++) {
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(token.c_str()), token.size());
EVP_EncodeBlock(output.data(), roundtrip.data(), roundtrip.size());
if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) {
break;
}
token.push_back('=');
}
}
static std::string UsernameFromDisplayToken(const std::string& display_token) {
std::size_t outlen = 4 * ((display_token.length() + 2) / 3);
std::array<unsigned char, 512> output{};
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(display_token.c_str()), display_token.length());
std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
return decoded_display_token.substr(0, decoded_display_token.find(token_delimiter));
}
static std::string TokenFromDisplayToken(const std::string& display_token) {
std::size_t outlen = 4 * ((display_token.length() + 2) / 3);
std::array<unsigned char, 512> output{};
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(display_token.c_str()), display_token.length());
std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
return decoded_display_token.substr(decoded_display_token.find(token_delimiter) + 1);
}
static Network::Room::BanList LoadBanList(const std::string& path) {
std::ifstream file;
Common::FS::OpenFileStream(file, path, std::ios_base::in);
if (!file || file.eof()) {
LOG_ERROR(Network, "Could not open ban list!");
return {};
}
std::string magic;
std::getline(file, magic);
if (magic != BanListMagic) {
LOG_ERROR(Network, "Ban list is not valid!");
return {};
}
// false = username ban list, true = ip ban list
bool ban_list_type = false;
Network::Room::UsernameBanList username_ban_list;
Network::Room::IPBanList ip_ban_list;
while (!file.eof()) {
std::string line;
std::getline(file, line);
line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
line = Common::StripSpaces(line);
if (line.empty()) {
// An empty line marks start of the IP ban list
ban_list_type = true;
continue;
}
if (ban_list_type) {
ip_ban_list.emplace_back(line);
} else {
username_ban_list.emplace_back(line);
}
}
return {username_ban_list, ip_ban_list};
}
static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) {
std::ofstream file;
Common::FS::OpenFileStream(file, path, std::ios_base::out);
if (!file) {
LOG_ERROR(Network, "Could not save ban list!");
return;
}
file << BanListMagic << "\n";
// Username ban list
for (const auto& username : ban_list.first) {
file << username << "\n";
}
file << "\n";
// IP ban list
for (const auto& ip : ban_list.second) {
file << ip << "\n";
}
}
/// Application entry point
void LaunchRoom(int argc, char** argv, bool called_by_option) {
int option_index = 0;
char* endarg;
char* new_argv0 = argv[0];
if (called_by_option) {
strncat(new_argv0, " --room", 8);
}
std::string room_name;
std::string room_description;
std::string password;
std::string preferred_game;
std::string username;
std::string token;
std::string web_api_url;
std::string ban_list_file;
std::string log_file = "eden-room.log";
std::string bind_address;
u64 preferred_game_id = 0;
u32 port = Network::DefaultRoomPort;
u32 max_members = 16;
static struct option long_options[] = {
{"room-name", required_argument, 0, 'n'},
{"room-description", required_argument, 0, 'd'},
{"bind-address", required_argument, 0, 's'},
{"port", required_argument, 0, 'p'},
{"max-members", required_argument, 0, 'm'},
{"password", required_argument, 0, 'w'},
{"preferred-game", required_argument, 0, 'g'},
{"preferred-game-id", required_argument, 0, 'i'},
{"username", optional_argument, 0, 'u'},
{"token", required_argument, 0, 't'},
{"web-api-url", required_argument, 0, 'a'},
{"ban-list-file", required_argument, 0, 'b'},
{"log-file", required_argument, 0, 'l'},
{"help", no_argument, 0, 'h'},
{"version", no_argument, 0, 'v'},
// Entry option
{"room", 0, 0, 0},
{0, 0, 0, 0},
};
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start();
while (optind < argc) {
int arg = getopt_long(argc, argv, "n:d:s:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index);
if (arg != -1) {
char carg = static_cast<char>(arg);
switch (carg) {
case 'n':
room_name.assign(optarg);
break;
case 'd':
room_description.assign(optarg);
break;
case 's':
bind_address.assign(optarg);
break;
case 'p':
port = strtoul(optarg, &endarg, 0);
break;
case 'm':
max_members = strtoul(optarg, &endarg, 0);
break;
case 'w':
password.assign(optarg);
break;
case 'g':
preferred_game.assign(optarg);
break;
case 'i':
preferred_game_id = strtoull(optarg, &endarg, 16);
break;
case 'u':
username.assign(optarg);
break;
case 't':
token.assign(optarg);
break;
case 'a':
web_api_url.assign(optarg);
break;
case 'b':
ban_list_file.assign(optarg);
break;
case 'l':
log_file.assign(optarg);
break;
case 'h':
PrintHelp(argv[0]);
std::exit(0);
case 'v':
PrintVersion();
std::exit(0);
default:
break;
}
}
}
if (room_name.empty()) {
LOG_ERROR(Network, "Room name is empty!");
PrintHelp(argv[0]);
std::exit(-1);
}
if (preferred_game.empty()) {
LOG_ERROR(Network, "Preferred game is empty!");
PrintHelp(argv[0]);
std::exit(-1);
}
if (preferred_game_id == 0) {
LOG_ERROR(Network,
"preferred-game-id not set!\nThis should get set to allow users to find your "
"room.\nSet with --preferred-game-id id");
}
if (max_members > Network::MaxConcurrentConnections || max_members < 2) {
LOG_ERROR(Network,
"max_members needs to be in the range 2 - {}!",
Network::MaxConcurrentConnections);
PrintHelp(argv[0]);
std::exit(-1);
}
if (bind_address.empty()) {
LOG_INFO(Network, "Bind address is empty: defaulting to 0.0.0.0");
}
if (port > UINT16_MAX) {
LOG_ERROR(Network, "Port needs to be in the range 0 - 65535!");
PrintHelp(argv[0]);
std::exit(-1);
}
if (ban_list_file.empty()) {
LOG_ERROR(Network,
"Ban list file not set!\nThis should get set to load and save room ban "
"list.\nSet with --ban-list-file <file>");
}
bool announce = true;
if (token.empty() && announce) {
announce = false;
LOG_INFO(Network, "Token is empty: Hosting a private room");
}
if (web_api_url.empty() && announce) {
announce = false;
LOG_INFO(Network, "Endpoint url is empty: Hosting a private room");
}
if (announce) {
if (username.empty()) {
LOG_INFO(Network, "Hosting a public room");
Settings::values.web_api_url = web_api_url;
PadToken(token);
Settings::values.eden_username = UsernameFromDisplayToken(token);
username = Settings::values.eden_username.GetValue();
Settings::values.eden_token = TokenFromDisplayToken(token);
} else {
LOG_INFO(Network, "Hosting a public room");
Settings::values.web_api_url = web_api_url;
Settings::values.eden_username = username;
Settings::values.eden_token = token;
}
}
// Load the ban list
Network::Room::BanList ban_list;
if (!ban_list_file.empty()) {
ban_list = LoadBanList(ban_list_file);
}
std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
if (announce) {
#ifdef ENABLE_WEB_SERVICE
verify_backend =
std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
#else
LOG_INFO(Network,
"Eden Web Services is not available with this build: validation is disabled.");
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
#endif
} else {
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
}
Network::Init();
if (auto room = Network::GetRoom().lock()) {
AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game,
.id = preferred_game_id};
if (!room->Create(room_name, room_description, bind_address, static_cast<u16>(port),
password, max_members, username, preferred_game_info,
std::move(verify_backend), ban_list)) {
LOG_INFO(Network, "Failed to create room: ");
std::exit(-1);
}
LOG_INFO(Network, "Room is open. Close with Q+Enter...");
auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>();
if (announce) {
announce_session->Start();
}
while (room->GetState() == Network::Room::State::Open) {
std::string in;
std::cin >> in;
if (in.size() > 0) {
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (announce) {
announce_session->Stop();
}
announce_session.reset();
// Save the ban list
if (!ban_list_file.empty()) {
SaveBanList(room->GetBanList(), ban_list_file);
}
room->Destroy();
}
Network::Shutdown();
std::exit(0);
}
+7
View File
@@ -0,0 +1,7 @@
// Copyright eden Emulator Project
// Licensed under GPLv3 or any later version
// Refer to the license.txt file included.
#pragma once
void LaunchRoom(int argc, char** argv, bool called_by_option);
+20
View File
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2017 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "winresrc.h"
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
YUZU_ICON ICON "../../dist/eden.ico"
/////////////////////////////////////////////////////////////////////////////
//
// RT_MANIFEST
//
0 RT_MANIFEST "../../dist/yuzu.manifest"
+6 -2
View File
@@ -25,7 +25,7 @@ endif()
# Dynarmic project options
option(DYNARMIC_ENABLE_CPU_FEATURE_DETECTION "Turning this off causes dynarmic to assume the host CPU doesn't support anything later than SSE3" ON)
if (OPENBSD OR DRAGONFLY OR NETBSD)
if (PLATFORM_OPENBSD OR PLATFORM_DRAGONFLY OR PLATFORM_NETBSD)
set(REQUIRE_WX ON)
else()
set(REQUIRE_WX OFF)
@@ -95,7 +95,6 @@ else()
-pedantic
-Wno-missing-braces
-fno-rtti
#-fno-exceptions
)
if (CXX_GCC)
# GCC produces bogus -Warray-bounds warnings from xbyak headers for code paths that are not
@@ -113,6 +112,11 @@ else()
# Clang mistakenly blames CMake for using unused arguments during compilation
list(APPEND DYNARMIC_CXX_FLAGS -Wno-unused-command-line-argument)
endif()
# TODO: oaknut exceptions
# TODO: fix chromeOS
if ("x86_64" IN_LIST ARCHITECTURE AND NOT ANDROID)
list(APPEND DYNARMIC_CXX_FLAGS -fno-exceptions)
endif()
endif()
if (NOT Boost_FOUND)
+1 -1
View File
@@ -363,7 +363,7 @@ elseif (APPLE)
backend/exception_handler_macos_mig.c
)
endif()
elseif (UNIX AND NOT HAIKUOS)
elseif (UNIX AND NOT PLATFORM_HAIKU)
# Haiku lacks <ucontext.h>
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
target_link_libraries(dynarmic PRIVATE rt)
@@ -66,10 +66,7 @@ public:
#ifdef _WIN32
uint8_t* alloc(size_t size) override {
void* p = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
if (p == nullptr) {
using Xbyak::Error;
XBYAK_THROW(Xbyak::ERR_CANT_ALLOC);
}
ASSERT(p != nullptr);
return static_cast<uint8_t*>(p);
}
@@ -105,10 +102,7 @@ public:
prot |= PROT_MPROTECT(PROT_READ) | PROT_MPROTECT(PROT_WRITE) | PROT_MPROTECT(PROT_EXEC);
#endif
void* p = mmap(nullptr, size, prot, mode, -1, 0);
if (p == MAP_FAILED) {
using Xbyak::Error;
XBYAK_THROW(Xbyak::ERR_CANT_ALLOC);
}
ASSERT(p != MAP_FAILED);
std::memcpy(p, &size, sizeof(size_t));
return static_cast<uint8_t*>(p) + DYNARMIC_PAGE_SIZE;
}
@@ -532,13 +526,8 @@ size_t BlockOfCode::GetTotalCodeSize() const {
}
void* BlockOfCode::AllocateFromCodeSpace(size_t alloc_size) {
if (size_ + alloc_size >= maxSize_) {
using Xbyak::Error;
XBYAK_THROW(Xbyak::ERR_CODE_IS_TOO_BIG);
}
ASSERT(size_ + alloc_size < maxSize_);
EnsureMemoryCommitted(alloc_size);
void* ret = getCurr<void*>();
size_ += alloc_size;
memset(ret, 0, alloc_size);
@@ -6,6 +6,7 @@
// You must ensure this matches with src/common/x64/xbyak.h on root dir
#include <ankerl/unordered_dense.h>
#include <boost/unordered_map.hpp>
#define XBYAK_NO_EXCEPTION 1
#define XBYAK_STD_UNORDERED_SET ankerl::unordered_dense::set
#define XBYAK_STD_UNORDERED_MAP ankerl::unordered_dense::map
#define XBYAK_STD_UNORDERED_MULTIMAP boost::unordered_multimap
+2 -2
View File
@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
@@ -28,6 +28,6 @@ if (ENABLE_WEB_SERVICE)
endif()
# Solaris uses /lib/amd64/libsocket.so and /lib/amd64/libnsl.so
if (SOLARIS)
if (PLATFORM_SUN)
target_link_libraries(network PRIVATE socket nsl)
endif()
-208
View File
@@ -6,31 +6,18 @@
#include <algorithm>
#include <atomic>
#include <iomanip>
#include <iostream>
#include <mutex>
#include <random>
#include <regex>
#include <shared_mutex>
#include <sstream>
#include <thread>
#include <fstream>
#include <openssl/evp.h>
#include "common/fs/file.h"
#include "common/polyfill_thread.h"
#include "common/logging.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "enet/enet.h"
#include "network/announce_multiplayer_session.h"
#include "network/packet.h"
#include "network/room.h"
#include "network/network.h"
#include "network/verify_user.h"
#ifdef ENABLE_WEB_SERVICE
#include "web_service/verify_user_jwt.h"
#endif
namespace Network {
@@ -1155,199 +1142,4 @@ void Room::Destroy() {
room_impl->room_information.name.clear();
}
/// The magic text at the beginning of a yuzu-room ban list file.
static constexpr char BAN_LIST_MAGIC[] = "YuzuRoom-BanList-1";
static constexpr char TOKEN_DELIMITER{':'};
static void PadToken(std::string& token) {
std::array<unsigned char, 512> output{};
std::array<unsigned char, 2048> roundtrip{};
for (size_t i = 0; i < 3; i++) {
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(token.c_str()), token.size());
EVP_EncodeBlock(output.data(), roundtrip.data(), roundtrip.size());
if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) {
break;
}
token.push_back('=');
}
}
static std::string UsernameFromDisplayToken(const std::string& display_token) {
std::size_t outlen = 4 * ((display_token.length() + 2) / 3);
std::array<unsigned char, 512> output{};
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(display_token.c_str()), display_token.length());
std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
return decoded_display_token.substr(0, decoded_display_token.find(TOKEN_DELIMITER));
}
static std::string TokenFromDisplayToken(const std::string& display_token) {
std::size_t outlen = 4 * ((display_token.length() + 2) / 3);
std::array<unsigned char, 512> output{};
EVP_DecodeBlock(output.data(), reinterpret_cast<const unsigned char*>(display_token.c_str()), display_token.length());
std::string decoded_display_token(reinterpret_cast<char*>(&output), outlen);
return decoded_display_token.substr(decoded_display_token.find(TOKEN_DELIMITER) + 1);
}
static Network::Room::BanList LoadBanList(const std::string& path) {
std::ifstream file;
Common::FS::OpenFileStream(file, path, std::ios_base::in);
if (!file || file.eof()) {
LOG_ERROR(Network, "Could not open ban list!");
return {};
}
std::string magic;
std::getline(file, magic);
if (magic != BAN_LIST_MAGIC) {
LOG_ERROR(Network, "Ban list is not valid!");
return {};
}
// false = username ban list, true = ip ban list
bool ban_list_type = false;
Network::Room::UsernameBanList username_ban_list;
Network::Room::IPBanList ip_ban_list;
while (!file.eof()) {
std::string line;
std::getline(file, line);
line.erase(std::remove(line.begin(), line.end(), '\0'), line.end());
line = Common::StripSpaces(line);
if (line.empty()) {
// An empty line marks start of the IP ban list
ban_list_type = true;
continue;
}
if (ban_list_type) {
ip_ban_list.emplace_back(line);
} else {
username_ban_list.emplace_back(line);
}
}
return {username_ban_list, ip_ban_list};
}
static void SaveBanList(const Network::Room::BanList& ban_list, const std::string& path) {
std::ofstream file;
Common::FS::OpenFileStream(file, path, std::ios_base::out);
if (!file) {
LOG_ERROR(Network, "Could not save ban list!");
return;
}
file << BAN_LIST_MAGIC << "\n";
// Username ban list
for (const auto& username : ban_list.first)
file << username << "\n";
file << "\n";
// IP ban list
for (const auto& ip : ban_list.second)
file << ip << "\n";
}
int LaunchRoomLoopWithArguments(Common::ProgramArguments& args) {
if (args.room_name.empty()) {
LOG_ERROR(Network, "Room name is empty!");
return -1;
}
if (args.preferred_game.empty()) {
LOG_ERROR(Network, "Preferred game is empty!");
return -1;
}
if (args.preferred_game_id == 0) {
LOG_WARNING(Network,
"preferred-game-id not set!\n"
"This should get set to allow users to find your room.\n"
"Set with --preferred-game-id id");
}
if (args.bind_address.empty()) {
LOG_INFO(Network, "Bind address is empty: defaulting to 0.0.0.0");
}
if (args.ban_list_file.empty()) {
LOG_WARNING(Network,
"Ban list file not set!\n"
"This should get set to load and save room ban list.\n"
"Set with --ban-list-file <file>");
}
bool announce = true;
if (args.token.empty() && announce) {
announce = false;
LOG_INFO(Network, "Token is empty: Hosting a private room");
}
if (args.web_api_url.empty() && announce) {
announce = false;
LOG_INFO(Network, "Endpoint url is empty: Hosting a private room");
}
if (announce) {
if (args.username.empty()) {
LOG_INFO(Network, "Hosting a public room");
Settings::values.web_api_url = args.web_api_url;
PadToken(args.token);
Settings::values.eden_username = UsernameFromDisplayToken(args.token);
args.username = Settings::values.eden_username.GetValue();
Settings::values.eden_token = TokenFromDisplayToken(args.token);
} else {
LOG_INFO(Network, "Hosting a public room");
Settings::values.web_api_url = args.web_api_url;
Settings::values.eden_username = args.username;
Settings::values.eden_token = args.token;
}
}
// Load the ban list
Network::Room::BanList ban_list;
if (!args.ban_list_file.empty()) {
ban_list = LoadBanList(args.ban_list_file);
}
std::unique_ptr<Network::VerifyUser::Backend> verify_backend;
if (announce) {
#ifdef ENABLE_WEB_SERVICE
verify_backend =
std::make_unique<WebService::VerifyUserJWT>(Settings::values.web_api_url.GetValue());
#else
LOG_INFO(Network,
"Eden Web Services is not available with this build: validation is disabled.");
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
#endif
} else {
verify_backend = std::make_unique<Network::VerifyUser::NullBackend>();
}
Network::Init();
if (auto room = Network::GetRoom().lock()) {
AnnounceMultiplayerRoom::GameInfo preferred_game_info{
.name = args.preferred_game,
.id = args.preferred_game_id
};
if (!room->Create(args.room_name, args.room_description, args.bind_address, u16(args.port),
args.password, args.max_members, args.username, preferred_game_info,
std::move(verify_backend), ban_list)) {
LOG_INFO(Network, "Failed to create room: ");
return -1;
}
LOG_INFO(Network, "Room is open. Close with Q+Enter...");
auto announce_session = std::make_unique<Core::AnnounceMultiplayerSession>();
if (announce) {
announce_session->Start();
}
while (room->GetState() == Network::Room::State::Open) {
std::string in;
std::cin >> in;
if (in.size() > 0) {
break;
}
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
if (announce) {
announce_session->Stop();
}
announce_session.reset();
// Save the ban list
if (!args.ban_list_file.empty()) {
SaveBanList(room->GetBanList(), args.ban_list_file);
}
room->Destroy();
}
Network::Shutdown();
return 0;
}
} // namespace Network
+1 -4
View File
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2017 Citra Emulator Project
@@ -10,7 +10,6 @@
#include <memory>
#include <string>
#include <vector>
#include "common/program_args.h"
#include "common/announce_multiplayer_room.h"
#include "common/common_types.h"
#include "common/socket_types.h"
@@ -149,6 +148,4 @@ private:
std::unique_ptr<RoomImpl> room_impl;
};
int LaunchRoomLoopWithArguments(Common::ProgramArguments& args);
} // namespace Network
+1 -1
View File
@@ -88,7 +88,7 @@ set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/sgsr1_shader_mobile_edge_direction.frag
)
if (HAIKUOS)
if (PLATFORM_HAIKU)
# glslangValidator WILL crash, glslang will not
set(GLSLANGVALIDATOR "glslang")
else()
+5 -4
View File
@@ -8,7 +8,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
# Set the RPATH for Qt Libraries
# This must be done before the `yuzu` target is created
if (YUZU_USE_BUNDLED_QT AND LINUX)
if (YUZU_USE_BUNDLED_QT AND PLATFORM_LINUX)
set(CMAKE_BUILD_RPATH "${CMAKE_BINARY_DIR}/bin/lib/")
endif()
@@ -398,9 +398,6 @@ endif()
target_link_libraries(yuzu PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(yuzu PRIVATE common core input_common frontend_common network video_core qt_common)
target_link_libraries(yuzu PRIVATE Boost::headers Qt6::Widgets Qt6::Charts Qt6::Concurrent)
target_link_libraries(yuzu PRIVATE
OpenSSL::SSL
OpenSSL::Crypto)
target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
if (ENABLE_OPENGL)
target_link_libraries(yuzu PRIVATE glad)
@@ -437,6 +434,10 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64)
target_link_libraries(yuzu PRIVATE dynarmic::dynarmic)
endif()
if (YUZU_ROOM)
target_link_libraries(yuzu PRIVATE yuzu-room)
endif()
if (NOT MSVC AND (APPLE OR NOT YUZU_STATIC_BUILD))
# needed for vma
target_compile_options(yuzu PRIVATE
+19 -27
View File
@@ -2,10 +2,12 @@
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QApplication>
#include "common/program_args.h"
#include "network/room.h"
#include "startup_checks.h"
#if YUZU_ROOM
#include <cstring>
#include "dedicated_room/yuzu_room.h"
#endif
#ifdef __unix__
#include "qt_common/gui_settings.h"
#endif
@@ -14,12 +16,6 @@
#include <sys/resource.h>
#endif
#if defined(__APPLE__)
#include <climits>
#include <cstdlib>
#include <cstring>
#endif
#include "main_window.h"
#ifdef _WIN32
@@ -76,6 +72,20 @@ static Qt::HighDpiScaleFactorRoundingPolicy GetHighDpiRoundingPolicy() {
}
int main(int argc, char* argv[]) {
#if YUZU_ROOM
bool launch_room = false;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--room") == 0) {
launch_room = true;
}
}
if (launch_room) {
LaunchRoom(argc, argv, true);
return 0;
}
#endif
bool has_broken_vulkan = false;
bool is_child = false;
if (CheckEnvVars(&is_child)) {
@@ -116,16 +126,6 @@ int main(int argc, char* argv[]) {
#endif // _WIN32
#if defined(__APPLE__)
// Convert the relative path to an absolute path before the chdir
char resolved[PATH_MAX];
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-u") == 0 || strcmp(argv[i], "-input-profile") == 0) {
++i;
} else if (argv[i][0] != '-' && argv[i][0] != '/' && realpath(argv[i], resolved)) {
argv[i] = strdup(resolved);
}
}
// If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
// But since we require the working directory to be the executable path for the location of
// the user folder in the Qt Frontend, we need to cd into that working directory
@@ -184,15 +184,7 @@ int main(int argc, char* argv[]) {
// generating shaders
setlocale(LC_ALL, "C");
Common::ProgramArguments args{};
Common::ParseArguments(args, argc, argv);
if (!args.room_name.empty() || !args.room_description.empty()) {
LOG_INFO(Frontend, "Assuming (headless) room mode");
Network::LaunchRoomLoopWithArguments(args);
return -1;
}
MainWindow main_window{std::move(args), has_broken_vulkan};
MainWindow main_window{has_broken_vulkan};
// After settings have been loaded by GMainWindow, apply the filter
main_window.show();
+58 -18
View File
@@ -350,7 +350,7 @@ inline static bool isDarkMode() {
}
#endif // _WIN32
MainWindow::MainWindow(Common::ProgramArguments&& opts, bool has_broken_vulkan)
MainWindow::MainWindow(bool has_broken_vulkan)
: ui{std::make_unique<Ui::MainWindow>()},
input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, user_data_migrator{this} {
QtCommon::Init(this);
@@ -512,33 +512,73 @@ MainWindow::MainWindow(Common::ProgramArguments&& opts, bool has_broken_vulkan)
return;
}
if (opts.selected_user) {
auto user_index = *opts.selected_user;
if (QtCommon::system->GetProfileManager().UserExistsIndex(user_index)) {
Settings::values.current_user = s32(user_index);
user_flag_cmd_line = true;
QString game_path;
bool should_launch_qlaunch = false;
bool should_launch_hlaunch = false;
bool should_launch_setup = false;
bool has_gamepath = false;
bool is_fullscreen = false;
// Preserves drag/drop functionality
for (int i = 1; i < args.size(); ++i) {
if (args[i] == QStringLiteral("-f")) {
// Launch game in fullscreen mode
is_fullscreen = true;
} else if (args[i] == QStringLiteral("-u") && i < args.size() - 1) {
// Launch game with a specific user
int user_arg_idx = ++i;
bool argument_ok;
std::size_t selected_user = args[user_arg_idx].toUInt(&argument_ok);
if (!argument_ok) {
// try to look it up by username, only finds the first username that matches.
std::string const user_arg_str = args[user_arg_idx].toStdString();
auto const user_idx =
QtCommon::system->GetProfileManager().GetUserIndex(user_arg_str);
if (user_idx != std::nullopt) {
selected_user = user_idx.value();
} else {
LOG_ERROR(Frontend, "Invalid user argument '{}'", user_arg_str);
continue;
}
}
if (QtCommon::system->GetProfileManager().UserExistsIndex(selected_user)) {
Settings::values.current_user = s32(selected_user);
user_flag_cmd_line = true;
} else {
LOG_ERROR(Frontend, "Selected user {} doesn't exist", selected_user);
}
} else if (args[i] == QStringLiteral("-g") && i < args.size() - 1) {
// Launch game at path
game_path = args[++i];
has_gamepath = true;
} else if (args[i] == QStringLiteral("-input-profile") && i < args.size() - 1) {
auto& players = Settings::values.players.GetValue();
players[0].profile_name = args[++i].toStdString();
} else if (args[i] == QStringLiteral("-qlaunch")) {
should_launch_qlaunch = true;
} else if (args[i] == QStringLiteral("-hlaunch")) {
should_launch_hlaunch = true;
} else if (args[i] == QStringLiteral("-setup")) {
should_launch_setup = true;
} else {
LOG_ERROR(Frontend, "Selected user {} doesn't exist", user_index);
game_path = args[i];
has_gamepath = true;
}
}
if (!opts.input_profile.empty()) {
Settings::values.players.GetValue()[0].profile_name = opts.input_profile;
}
// Override fullscreen setting if gamepath or argument is provided
if (!opts.filepath.empty() && opts.fullscreen) {
ui->action_Fullscreen->setChecked(opts.fullscreen);
if (has_gamepath || is_fullscreen) {
ui->action_Fullscreen->setChecked(is_fullscreen);
}
if (opts.should_launch_setup) {
if (should_launch_setup) {
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::Starter), std::nullopt);
} else {
if (!opts.filepath.empty()) {
BootGame(QString::fromStdString(opts.filepath), ApplicationAppletParameters());
} else if (opts.should_launch_qlaunch) {
if (!game_path.isEmpty()) {
BootGame(game_path, ApplicationAppletParameters());
} else if (should_launch_qlaunch) {
LaunchFirmwareApplet(u64(Service::AM::AppletProgramId::QLaunch), std::nullopt);
} else if (opts.should_launch_hlaunch) {
} else if (should_launch_hlaunch) {
std::filesystem::path const sd_dir =
Common::FS::GetEdenPathString(Common::FS::EdenPath::SDMCDir);
auto const hbl_path = (sd_dir / "atmosphere" / "hbl.nsp").string();
+1 -2
View File
@@ -17,7 +17,6 @@
#include <QTranslator>
#include <qaction.h>
#include "common/program_args.h"
#include "common/common_types.h"
#include "common/settings_enums.h"
#include "frontend_common/content_manager.h"
@@ -166,7 +165,7 @@ class MainWindow : public QMainWindow {
public:
void filterBarSetChecked(bool state);
void UpdateUITheme();
explicit MainWindow(Common::ProgramArguments&& args, bool has_broken_vulkan);
explicit MainWindow(bool has_broken_vulkan);
~MainWindow() override;
bool DropAction(QDropEvent* event);
+1 -10
View File
@@ -38,16 +38,7 @@ add_executable(yuzu-cmd
${OPENGL_SOURCES}
)
target_link_libraries(yuzu-cmd PRIVATE common network core input_common frontend_common video_core)
if (ENABLE_WEB_SERVICE)
target_compile_definitions(yuzu-cmd PRIVATE ENABLE_WEB_SERVICE)
target_link_libraries(yuzu-cmd PRIVATE web_service)
endif()
target_link_libraries(yuzu-cmd PRIVATE
OpenSSL::SSL
OpenSSL::Crypto)
target_link_libraries(yuzu-cmd PRIVATE common core input_common frontend_common video_core)
if (ENABLE_OPENGL)
target_link_libraries(yuzu-cmd PRIVATE glad)
endif()
+56 -36
View File
@@ -5,10 +5,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <SDL3/SDL.h>
#include <SDL3/SDL_timer.h>
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#include "common/logging.h"
#include "common/scm_rev.h"
@@ -30,17 +26,9 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Co
LOG_CRITICAL(Frontend, "Failed to initialize SDL3: {}, Exiting...", SDL_GetError());
exit(1);
}
titlebar_timer = SDL_AddTimer(2000, [](void *userdata, SDL_TimerID, Uint32) -> Uint32 {
auto* this_ = (EmuWindow_SDL3*)userdata;
auto const results = this_->system.GetAndResetPerfStats();
auto const title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc, results.average_game_fps, results.emulation_speed * 100.0f);
SDL_SetWindowTitle(this_->render_window, title.c_str());
return 2000;
}, this);
}
EmuWindow_SDL3::~EmuWindow_SDL3() {
SDL_RemoveTimer(titlebar_timer);
system.HIDCore().UnloadInputDevices();
input_subsystem->Shutdown();
SDL_Quit();
@@ -144,7 +132,8 @@ void EmuWindow_SDL3::Fullscreen() {
switch (Settings::values.fullscreen_mode.GetValue()) {
case Settings::FullscreenMode::Exclusive:
// Set window size to render size before entering fullscreen in exclusive mode.
if (const SDL_DisplayMode* display_mode_ptr = SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window))) {
if (const SDL_DisplayMode* display_mode_ptr =
SDL_GetDesktopDisplayMode(SDL_GetDisplayForWindow(render_window))) {
display_mode = *display_mode_ptr;
SDL_SetWindowSize(render_window, display_mode.w, display_mode.h);
SDL_SetWindowFullscreenMode(render_window, &display_mode);
@@ -176,60 +165,92 @@ void EmuWindow_SDL3::Fullscreen() {
}
}
void EmuWindow_SDL3::OnEvent(SDL_Event& event) {
// Notice how we skip the "update title" aspect on most events
// this is because some WMs do NOT like changing titles while resizing
// so let's just... not do that, thanks :)
// Afterall we don't really expect the user to pay attention to the titlebar
// while they're moving a lot of shit around...
void EmuWindow_SDL3::WaitEvent() {
// Called on main thread
SDL_Event event;
if (!SDL_WaitEvent(&event)) {
const char* error = SDL_GetError();
if (!error || strcmp(error, "") == 0) {
// https://github.com/libsdl-org/SDL/issues/5780
// Sometimes SDL will return without actually having hit an error condition;
// just ignore it in this case.
return;
}
LOG_CRITICAL(Frontend, "SDL_WaitEvent failed: {}", error);
exit(1);
}
switch (event.type) {
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
return OnResize();
OnResize();
break;
case SDL_EVENT_WINDOW_MINIMIZED:
is_shown = false;
return OnResize();
OnResize();
break;
case SDL_EVENT_WINDOW_EXPOSED:
is_shown = true;
return OnResize();
OnResize();
break;
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
is_open = false;
return;
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
return OnKeyEvent(int(event.key.scancode), event.key.down ? 1 : 0);
OnKeyEvent(static_cast<int>(event.key.scancode), event.key.down ? 1 : 0);
break;
case SDL_EVENT_MOUSE_MOTION:
// ignore if it came from touch
if (event.button.which != SDL_TOUCH_MOUSEID)
OnMouseMotion(event.motion.x, event.motion.y);
return;
break;
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
// ignore if it came from touch
if (event.button.which != SDL_TOUCH_MOUSEID)
OnMouseButton(event.button.button, event.button.down ? 1 : 0, s32(event.button.x), s32(event.button.y));
return;
if (event.button.which != SDL_TOUCH_MOUSEID) {
OnMouseButton(event.button.button, event.button.down ? 1 : 0,
static_cast<s32>(event.button.x), static_cast<s32>(event.button.y));
}
break;
case SDL_EVENT_FINGER_DOWN:
return OnFingerDown(event.tfinger.x, event.tfinger.y, std::size_t(event.tfinger.touchID));
OnFingerDown(event.tfinger.x, event.tfinger.y,
static_cast<std::size_t>(event.tfinger.touchID));
break;
case SDL_EVENT_FINGER_MOTION:
return OnFingerMotion(event.tfinger.x, event.tfinger.y, std::size_t(event.tfinger.touchID));
OnFingerMotion(event.tfinger.x, event.tfinger.y,
static_cast<std::size_t>(event.tfinger.touchID));
break;
case SDL_EVENT_FINGER_UP:
return OnFingerUp();
OnFingerUp();
break;
case SDL_EVENT_QUIT:
is_open = false;
return;
break;
default:
break;
}
const u32 current_time = SDL_GetTicks();
if (current_time > last_time + 2000) {
const auto results = system.GetAndResetPerfStats();
const auto title = fmt::format("{} | {}-{} | FPS: {:.0f} ({:.0f}%)",
Common::g_build_fullname,
Common::g_scm_branch,
Common::g_scm_desc,
results.average_game_fps,
results.emulation_speed * 100.0);
SDL_SetWindowTitle(render_window, title.c_str());
last_time = current_time;
}
}
// Credits to Samantas5855 and others for this function.
void EmuWindow_SDL3::SetWindowIcon() {
#if defined(__EMSCRIPTEN__) || defined(__wasi__)
// Icons do not work yet
#else
SDL_IOStream* const yuzu_icon_stream = SDL_IOFromConstMem((void*)yuzu_icon, yuzu_icon_size);
if (yuzu_icon_stream == nullptr) {
LOG_WARNING(Frontend, "Failed to create Eden icon stream.");
@@ -243,7 +264,6 @@ void EmuWindow_SDL3::SetWindowIcon() {
// The icon is attached to the window pointer
SDL_SetWindowIcon(render_window, window_icon);
SDL_DestroySurface(window_icon);
#endif
}
void EmuWindow_SDL3::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) {
+6 -7
View File
@@ -9,12 +9,11 @@
#include <tuple>
#include <utility>
#include <SDL3/SDL.h>
#include <SDL3/SDL_timer.h>
#include "core/frontend/emu_window.h"
#include "core/frontend/graphics_context.h"
struct SDL_Window;
namespace Core {
class System;
}
@@ -36,7 +35,7 @@ public:
bool IsShown() const override;
/// Wait for the next event on the main thread.
void OnEvent(SDL_Event& event);
void WaitEvent();
// Sets the window icon from yuzu.bmp
void SetWindowIcon();
@@ -81,9 +80,6 @@ protected:
/// Called when a configuration change affects the minimal size of the window
void OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal_size) override;
/// Periodic changer of titlebar (independent of event loop)
SDL_TimerID titlebar_timer;
/// Is the window still open?
bool is_open = true;
@@ -93,6 +89,9 @@ protected:
/// Internal SDL3 render window
SDL_Window* render_window{};
/// Keeps track of how often to update the title bar during gameplay
u32 last_time = 0;
/// Input subsystem to use with this window.
InputCommon::InputSubsystem* input_subsystem;
+309 -186
View File
@@ -8,16 +8,6 @@
#include <memory>
#include <regex>
#include <string>
#include <SDL3/SDL_init.h>
#include <openssl/evp.h>
#include "common/fs/file.h"
#include "common/program_args.h"
#include "common/settings_enums.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#endif
#define SDL_MAIN_USE_CALLBACKS 1
#include <SDL3/SDL_main.h>
#include <fmt/ostream.h>
@@ -36,14 +26,9 @@
#include "core/loader/loader.h"
#include "frontend_common/config.h"
#include "input_common/main.h"
#include "network/announce_multiplayer_session.h"
#include "network/network.h"
#include "network/room.h"
#include "network/verify_user.h"
#include "yuzu_cmd/sdl_config.h"
#include "sdl_config.h"
#include "video_core/renderer_base.h"
#include "yuzu_cmd/emu_window/emu_window_sdl3.h"
#ifdef HAS_OPENGL
#include "yuzu_cmd/emu_window/emu_window_sdl3_gl.h"
@@ -54,7 +39,9 @@
#ifdef _WIN32
// windows.h needs to be included before shellapi.h
#include <windows.h>
#include <shellapi.h>
#include "common/windows/timer_resolution.h"
#endif
@@ -73,6 +60,25 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
}
#endif
static void PrintHelp(const char* argv0) {
std::cout << "Usage: " << argv0
<< " [options] <filename>\n"
"-c, --config Load the specified configuration file\n"
"-f, --fullscreen Start in fullscreen mode\n"
"-g, --game File path of the game to load\n"
"-h, --help Display this help and exit\n"
"-m, --multiplayer=nick:password@address:port"
" Nickname, password, address and port for multiplayer\n"
"-p, --program Pass following string as arguments to executable\n"
"-u, --user Select a specific user profile from 0 to 7\n"
"-d, --debug Run the GDB stub on a port from 1 to 65535\n"
"-v, --version Output version information and exit\n";
}
static void PrintVersion() {
std::cout << "Eden " << Common::g_scm_branch << " " << Common::g_scm_desc << std::endl;
}
static void OnStateChanged(const Network::RoomMember::State& state) {
switch (state) {
case Network::RoomMember::State::Idle:
@@ -168,154 +174,8 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
std::cout << std::endl << "* " << message << std::endl << std::endl;
}
struct SdlState {
Core::System system{};
std::unique_ptr<EmuWindow_SDL3> emu_window;
// settings
Common::ProgramArguments opts = {};
};
static SDL_AppResult ExecuteWithGUI(SdlState& state) {
SdlConfig config{state.opts.config_path};
// apply the log_filter setting
// the logger was initialized before and doesn't pick up the filter on its own
Common::Log::Filter filter;
filter.ParseFilterString(state.opts.log_filter.value_or(Settings::values.log_filter.GetValue()));
Common::Log::SetGlobalFilter(filter);
if (!state.opts.program_args.empty()) {
Settings::values.program_args = state.opts.program_args;
}
if (!state.opts.input_profile.empty()) {
auto& players = Settings::values.players.GetValue();
players[0].profile_name = state.opts.input_profile;
}
if (state.opts.selected_user.has_value()) {
Settings::values.current_user = std::clamp(*state.opts.selected_user, 0, 7);
}
if (state.opts.override_gdb_port.has_value()) {
Settings::values.use_gdbstub = true;
Settings::values.gdbstub_port = *state.opts.override_gdb_port;
}
if (state.opts.force_single_core) {
Settings::values.use_multi_core = false;
}
if (state.opts.force_null_render) {
Settings::values.renderer_backend = Settings::RendererBackend::Null;
}
if (state.opts.filepath.empty()) {
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
return SDL_APP_FAILURE;
}
state.system.Initialize();
InputCommon::InputSubsystem input_subsystem{};
// Apply the command line arguments
state.system.ApplySettings();
switch (Settings::values.renderer_backend.GetValue()) {
#ifdef HAS_OPENGL
case Settings::RendererBackend::OpenGL_GLSL:
case Settings::RendererBackend::OpenGL_GLASM:
case Settings::RendererBackend::OpenGL_SPIRV:
state.emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, state.system, state.opts.fullscreen);
break;
#endif
case Settings::RendererBackend::Vulkan:
state.emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, state.system, state.opts.fullscreen);
break;
case Settings::RendererBackend::Null:
state.emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, state.system, state.opts.fullscreen);
break;
default:
LOG_CRITICAL(Frontend, "Invalid renderer backend");
return SDL_APP_FAILURE;
}
#ifdef _WIN32
Common::Windows::SetCurrentTimerResolutionToMaximum();
state.system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
#endif
state.system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
state.system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
state.system.GetFileSystemController().CreateFactories(*state.system.GetFilesystem());
state.system.GetUserChannel().clear();
Service::AM::FrontendAppletParameters load_parameters{
.applet_id = Service::AM::AppletId::Application,
};
const Core::SystemResultStatus load_result = state.system.Load(*state.emu_window, state.opts.filepath, load_parameters);
switch (load_result) {
case Core::SystemResultStatus::Success:
break; // Expected case
case Core::SystemResultStatus::ErrorGetLoader:
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", state.opts.filepath);
return SDL_APP_FAILURE;
case Core::SystemResultStatus::ErrorLoader:
LOG_CRITICAL(Frontend, "Failed to load ROM!");
return SDL_APP_FAILURE;
case Core::SystemResultStatus::ErrorNotInitialized:
LOG_CRITICAL(Frontend, "CPUCore not initialized");
return SDL_APP_FAILURE;
case Core::SystemResultStatus::ErrorVideoCore:
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
return SDL_APP_FAILURE;
default:
const u16 loader_id = u16(Core::SystemResultStatus::ErrorLoader);
const u16 error_id = u16(load_result) - loader_id;
LOG_CRITICAL(Frontend,
"While attempting to load the ROM requested, an error occurred. Please "
"refer to the Eden wiki for more information or the Eden discord for "
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
loader_id, error_id, Loader::ResultStatus(error_id));
return SDL_APP_FAILURE;
}
if (state.opts.use_multiplayer) {
if (auto member = Network::GetRoomMember().lock()) {
member->BindOnChatMessageReceived(OnMessageReceived);
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
member->BindOnStateChanged(OnStateChanged);
member->BindOnError(OnNetworkError);
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", state.opts.address, state.opts.port, state.opts.nickname);
member->Join(state.opts.nickname, state.opts.address.c_str(), state.opts.port, 0, Network::NoPreferredIP, state.opts.password);
} else {
LOG_ERROR(Network, "Could not access RoomMember");
return SDL_APP_FAILURE;
}
}
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
state.system.GPU().Start();
state.system.GetCpuManager().OnGpuReady();
if (Settings::values.use_disk_shader_cache.GetValue()) {
state.system.Renderer().ReadRasterizer()->LoadDiskResources(
state.system.GetApplicationProcessProgramID(), std::stop_token{},
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
}
// don't do anything, SDL3 already exists for us :D
state.system.RegisterExitCallback([] {});
void(state.system.Run());
if (state.system.DebuggerEnabled())
state.system.InitializeDebugger();
return SDL_APP_SUCCESS;
}
extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
SdlState* state = new SdlState();
/// Application entry point
int main(int argc, char** argv) {
#ifdef _WIN32
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "wb", stdout);
@@ -327,29 +187,292 @@ extern "C" SDL_AppResult SDL_AppInit(void **appstate, int argc, char **argv) {
Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start();
Common::ParseArguments(state->opts, argc, argv);
if (!state->opts.room_name.empty() || !state->opts.room_description.empty()) {
LOG_INFO(Frontend, "Assuming (headless) room mode");
Network::LaunchRoomLoopWithArguments(state->opts);
return SDL_APP_FAILURE;
int option_index = 0;
#ifdef _WIN32
int argc_w;
auto argv_w = CommandLineToArgvW(GetCommandLineW(), &argc_w);
if (argv_w == nullptr) {
LOG_CRITICAL(Frontend, "Failed to get command line arguments");
return -1;
}
return ExecuteWithGUI(*state);
}
extern "C" SDL_AppResult SDL_AppIterate(void *appstate) {
SdlState *state = (SdlState *)appstate;
return state->emu_window->IsOpen() ? SDL_APP_CONTINUE : SDL_APP_SUCCESS;
}
extern "C" SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) {
SdlState *state = (SdlState *)appstate;
state->emu_window->OnEvent(*event);
return SDL_APP_SUCCESS;
}
extern "C" void SDL_AppQuit(void *appstate, SDL_AppResult result) {
SdlState *state = (SdlState *)appstate;
state->system.DetachDebugger();
void(state->system.Pause());
state->system.ShutdownMainProcess();
delete state;
#endif
std::string filepath;
std::optional<std::string> config_path{};
std::string program_args;
std::optional<int> selected_user{};
std::optional<u16> override_gdb_port{};
bool use_multiplayer = false;
bool fullscreen = false;
bool force_null_render = false;
bool force_single_core = false;
std::string nickname{};
std::string password{};
std::string address{};
std::string input_profile{};
std::optional<std::string> log_filter{};
u16 port = Network::DefaultRoomPort;
static struct option long_options[] = {
// clang-format off
{"debug", no_argument, 0, 'd'},
{"config", required_argument, 0, 'c'},
{"fullscreen", no_argument, 0, 'f'},
{"help", no_argument, 0, 'h'},
{"game", required_argument, 0, 'g'},
{"multiplayer", required_argument, 0, 'm'},
{"program", optional_argument, 0, 'p'},
{"user", required_argument, 0, 'u'},
{"version", no_argument, 0, 'v'},
{"input-profile", no_argument, 0, 'i'},
{"null-render", no_argument, 0, 'n'},
{"singlecore", no_argument, 0, 's'},
{"filter", no_argument, 0, 'x'},
{0, 0, 0, 0},
// clang-format on
};
while (optind < argc) {
int arg = getopt_long(argc, argv, "g:fhvcip::c:u:d:", long_options, &option_index);
if (arg != -1) {
switch (char(arg)) {
case 'd':
override_gdb_port = uint16_t(atoi(optarg));
break;
case 'c':
config_path = optarg;
break;
case 'f':
fullscreen = true;
LOG_INFO(Frontend, "Starting in fullscreen mode...");
break;
case 'h':
PrintHelp(argv[0]);
return 0;
case 'g':
filepath = std::string(optarg);
break;
case 'i': {
input_profile = std::string(optarg);
break;
}
case 'm': {
use_multiplayer = true;
const std::string str_arg(optarg);
// regex to check if the format is nickname:password@ip:port
// with optional :password
const std::regex re("^([^:]+)(?::(.+))?@([^:]+)(?::([0-9]+))?$");
if (!std::regex_match(str_arg, re)) {
std::cout << "Wrong format for option --multiplayer\n";
PrintHelp(argv[0]);
return -1;
}
std::smatch match;
std::regex_search(str_arg, match, re);
ASSERT(match.size() == 5);
nickname = match[1];
password = match[2];
address = match[3];
if (!match[4].str().empty()) {
port = u16(std::strtoul(match[4].str().c_str(), nullptr, 0));
}
std::regex nickname_re("^[a-zA-Z0-9._\\- ]+$");
if (!std::regex_match(nickname, nickname_re)) {
LOG_ERROR(Frontend, "Nickname is not valid. Must be 4 to 20 alphanumeric characters");
return -1;
}
if (address.empty()) {
LOG_ERROR(Frontend, "Address to room must not be empty");
return -1;
}
break;
}
case 'p':
program_args = argv[optind];
++optind;
break;
case 'u':
selected_user = atoi(optarg);
break;
case 'v':
PrintVersion();
return -1;
case 'n':
force_null_render = true;
break;
case 's':
force_single_core = true;
break;
case 'x':
log_filter = argv[optind];
++optind;
break;
}
} else {
#ifdef _WIN32
filepath = Common::UTF16ToUTF8(argv_w[optind]);
#else
filepath = argv[optind];
#endif
optind++;
}
}
SdlConfig config{config_path};
// apply the log_filter setting
// the logger was initialized before and doesn't pick up the filter on its own
Common::Log::Filter filter;
filter.ParseFilterString(log_filter.value_or(Settings::values.log_filter.GetValue()));
Common::Log::SetGlobalFilter(filter);
if (!program_args.empty()) {
Settings::values.program_args = program_args;
}
if (!input_profile.empty()) {
auto& players = Settings::values.players.GetValue();
players[0].profile_name = input_profile;
}
if (selected_user.has_value()) {
Settings::values.current_user = std::clamp(*selected_user, 0, 7);
}
if (override_gdb_port.has_value()) {
Settings::values.use_gdbstub = true;
Settings::values.gdbstub_port = *override_gdb_port;
}
if (force_single_core) {
Settings::values.use_multi_core = false;
}
if (force_null_render) {
Settings::values.renderer_backend = Settings::RendererBackend::Null;
}
#ifdef _WIN32
LocalFree(argv_w);
#endif
if (filepath.empty()) {
LOG_CRITICAL(Frontend, "Failed to load ROM: No ROM specified");
return -1;
}
Core::System system{};
system.Initialize();
InputCommon::InputSubsystem input_subsystem{};
// Apply the command line arguments
system.ApplySettings();
std::unique_ptr<EmuWindow_SDL3> emu_window;
switch (Settings::values.renderer_backend.GetValue()) {
#ifdef HAS_OPENGL
case Settings::RendererBackend::OpenGL_GLSL:
case Settings::RendererBackend::OpenGL_GLASM:
case Settings::RendererBackend::OpenGL_SPIRV:
emu_window = std::make_unique<EmuWindow_SDL3_GL>(&input_subsystem, system, fullscreen);
break;
#endif
case Settings::RendererBackend::Vulkan:
emu_window = std::make_unique<EmuWindow_SDL3_VK>(&input_subsystem, system, fullscreen);
break;
case Settings::RendererBackend::Null:
emu_window = std::make_unique<EmuWindow_SDL3_Null>(&input_subsystem, system, fullscreen);
break;
default:
LOG_CRITICAL(Frontend, "Invalid renderer backend");
return -1;
}
#ifdef _WIN32
Common::Windows::SetCurrentTimerResolutionToMaximum();
system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
#endif
system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
system.GetFileSystemController().CreateFactories(*system.GetFilesystem());
system.GetUserChannel().clear();
Service::AM::FrontendAppletParameters load_parameters{
.applet_id = Service::AM::AppletId::Application,
};
const Core::SystemResultStatus load_result{system.Load(*emu_window, filepath, load_parameters)};
switch (load_result) {
case Core::SystemResultStatus::ErrorGetLoader:
LOG_CRITICAL(Frontend, "Failed to obtain loader for {}!", filepath);
return -1;
case Core::SystemResultStatus::ErrorLoader:
LOG_CRITICAL(Frontend, "Failed to load ROM!");
return -1;
case Core::SystemResultStatus::ErrorNotInitialized:
LOG_CRITICAL(Frontend, "CPUCore not initialized");
return -1;
case Core::SystemResultStatus::ErrorVideoCore:
LOG_CRITICAL(Frontend, "Failed to initialize VideoCore!");
return -1;
case Core::SystemResultStatus::Success:
break; // Expected case
default:
if (static_cast<u32>(load_result) >
static_cast<u32>(Core::SystemResultStatus::ErrorLoader)) {
const u16 loader_id = static_cast<u16>(Core::SystemResultStatus::ErrorLoader);
const u16 error_id = static_cast<u16>(load_result) - loader_id;
LOG_CRITICAL(Frontend,
"While attempting to load the ROM requested, an error occurred. Please "
"refer to the Eden wiki for more information or the Eden discord for "
"additional help.\n\nError Code: {:04X}-{:04X}\nError Description: {}",
loader_id, error_id, static_cast<Loader::ResultStatus>(error_id));
}
break;
}
if (use_multiplayer) {
if (auto member = Network::GetRoomMember().lock()) {
member->BindOnChatMessageReceived(OnMessageReceived);
member->BindOnStatusMessageReceived(OnStatusMessageReceived);
member->BindOnStateChanged(OnStateChanged);
member->BindOnError(OnNetworkError);
LOG_DEBUG(Network, "Start connection to {}:{} with nickname {}", address, port,
nickname);
member->Join(nickname, address.c_str(), port, 0, Network::NoPreferredIP, password);
} else {
LOG_ERROR(Network, "Could not access RoomMember");
return 0;
}
}
// Core is loaded, start the GPU (makes the GPU contexts current to this thread)
system.GPU().Start();
system.GetCpuManager().OnGpuReady();
if (Settings::values.use_disk_shader_cache.GetValue()) {
system.Renderer().ReadRasterizer()->LoadDiskResources(
system.GetApplicationProcessProgramID(), std::stop_token{},
[](VideoCore::LoadCallbackStage, size_t value, size_t total) {});
}
system.RegisterExitCallback([&] {
// Just exit right away.
exit(0);
});
void(system.Run());
if (system.DebuggerEnabled()) {
system.InitializeDebugger();
}
while (emu_window->IsOpen()) {
emu_window->WaitEvent();
}
system.DetachDebugger();
void(system.Pause());
system.ShutdownMainProcess();
return 0;
}
#define VMA_IMPLEMENTATION
+18
View File
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
add_executable(yuzu_room_standalone yuzu_room_standalone.cpp)
set_target_properties(yuzu_room_standalone PROPERTIES OUTPUT_NAME "eden-room")
target_link_libraries(yuzu_room_standalone PRIVATE yuzu-room)
if(UNIX AND NOT APPLE)
install(TARGETS yuzu_room_standalone RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}/bin")
endif()
if (YUZU_STATIC_ROOM)
target_link_options(yuzu_room_standalone PRIVATE "-static")
endif()
create_target_directory_groups(yuzu_room_standalone)
+9
View File
@@ -0,0 +1,9 @@
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!" << endl;
return 0;
}
@@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#include "dedicated_room/yuzu_room.h"
int main(int argc, char* argv[])
{
LaunchRoom(argc, argv, false);
}