From 29514f0d2b669e66c41ba94ef61bce178bfe8434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trung=20L=C3=AA?= <8@tle.id.au> Date: Fri, 27 Mar 2026 13:44:02 +1100 Subject: [PATCH] Box64 hybrid NaCl emulation for unsupported platforms Run x86_64 NaCl loader and amd64 .nexe game modules under Box64 emulation on platforms without native NaCl support. The native engine communicates with the emulated NaCl sandbox processes via architecture-independent Unix domain socket IPC, requiring no changes to game modules. - Add opt-in DAEMON_NACL_BOX64_EMULATION CMake option (OFF by default, Linux only) that remaps NACL_ARCH to amd64 - Replace architecture-specific NaCl dummy defines with a generic fallback for any unsupported architecture - Prepend box64 to NaCl loader args with PATH resolution - Skip bootstrap helper (incompatible with Box64 double-exec) - Disable platform qualification for emulated loader - Add cvars: vm.box64.path, workaround.box64.disableBootstrap, workaround.box64.disableQualification --- cmake/DaemonArchitecture.cmake | 11 +++ cmake/DaemonNacl.cmake | 9 +- src/engine/framework/VirtualMachine.cpp | 111 +++++++++++++++++++++++- 3 files changed, 123 insertions(+), 8 deletions(-) diff --git a/cmake/DaemonArchitecture.cmake b/cmake/DaemonArchitecture.cmake index 414aa775d0..73962ac408 100644 --- a/cmake/DaemonArchitecture.cmake +++ b/cmake/DaemonArchitecture.cmake @@ -81,6 +81,17 @@ if (LINUX OR FREEBSD) # The nexe is system agnostic so there should be no difference with armel. set(NACL_ARCH "armhf") endif() + + set(BOX64_USAGE ppc64el riscv64) + if (ARCH IN_LIST BOX64_USAGE) + option(DAEMON_NACL_BOX64_EMULATION "Use Box64 to emulate x86_64 NaCl loader on unsupported platforms" ON) + if (DAEMON_NACL_BOX64_EMULATION) + # Use Box64 to run x86_64 NaCl loader and amd64 nexe. + # Box64 must be installed and available in PATH at runtime. + set(NACL_ARCH "amd64") + add_definitions(-DDAEMON_NACL_BOX64_EMULATION) + endif() + endif() elseif(APPLE) if ("${ARCH}" STREQUAL arm64) # You can get emulated NaCl going like this: diff --git a/cmake/DaemonNacl.cmake b/cmake/DaemonNacl.cmake index bfdded2638..a763cddb82 100644 --- a/cmake/DaemonNacl.cmake +++ b/cmake/DaemonNacl.cmake @@ -71,13 +71,12 @@ else() add_definitions( -DNACL_BUILD_SUBARCH=32 ) elseif( NACL_ARCH STREQUAL "armhf" ) add_definitions( -DNACL_BUILD_ARCH=arm ) - elseif( NACL_ARCH STREQUAL "ppc64el" OR NACL_ARCH STREQUAL "ppc64" ) - # NaCl does not support PPC, but these defines must be set for native - # builds. Use dummy x86 values as PNaCl does for arch-independent builds. + else() + # NaCl does not support this architecture natively, but these defines must + # be set because nacl_config.h is included unconditionally. Use dummy x86 + # values as PNaCl does for architecture-independent builds. add_definitions( -DNACL_BUILD_ARCH=x86 ) add_definitions( -DNACL_BUILD_SUBARCH=64 ) - else() - message(WARNING "Unknown architecture ${NACL_ARCH}") endif() endif() diff --git a/src/engine/framework/VirtualMachine.cpp b/src/engine/framework/VirtualMachine.cpp index 0051b56a8d..bc37adae72 100644 --- a/src/engine/framework/VirtualMachine.cpp +++ b/src/engine/framework/VirtualMachine.cpp @@ -42,6 +42,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include #include #include +// POSIX: environ is the process environment, not always declared in headers. +extern char **environ; #ifdef __linux__ #include #if defined(DAEMON_ARCH_armhf) @@ -73,6 +75,54 @@ static Cvar::Cvar workaround_naclSystem_freebsd_disableQualification( "Disable platform qualification when running Linux NaCl loader on FreeBSD through Linuxulator", Cvar::NONE, true); +#if defined(DAEMON_NACL_BOX64_EMULATION) +static Cvar::Cvar workaround_box64_disableQualification( + "workaround.box64.disableQualification", + "Disable platform qualification when running amd64 NaCl loader under Box64 emulation", + Cvar::NONE, true); + +static Cvar::Cvar workaround_box64_disableBootstrap( + "workaround.box64.disableBootstrap", + "Disable NaCl bootstrap helper when using Box64 emulation", + Cvar::NONE, true); + +static Cvar::Cvar vm_box64_path( + "vm.box64.path", + "Path to the box64 binary for NaCl emulation (empty = search PATH)", + Cvar::NONE, ""); + +// Resolve box64 binary path by searching PATH if not explicitly set. +static std::string ResolveBox64Path() { + std::string path = vm_box64_path.Get(); + if (!path.empty()) { + return path; + } + + const char* envPath = getenv("PATH"); + if (!envPath) { + Sys::Error("Box64 emulation is enabled but PATH is not set and vm.box64.path is empty."); + } + + std::string pathStr(envPath); + size_t start = 0; + while (start < pathStr.size()) { + size_t end = pathStr.find(':', start); + if (end == std::string::npos) { + end = pathStr.size(); + } + std::string candidate = pathStr.substr(start, end - start) + "/box64"; + if (access(candidate.c_str(), X_OK) == 0) { + return candidate; + } + start = end + 1; + } + + Sys::Error("Box64 emulation is enabled but 'box64' was not found in PATH. " + "Install Box64 or set vm.box64.path to the full path of the box64 binary."); + return ""; // unreachable +} +#endif + static Cvar::Cvar vm_nacl_qualification( "vm.nacl.qualification", "Enable NaCl loader platform qualification", @@ -128,7 +178,7 @@ static void CheckMinAddressSysctlTooLarge() } // Platform-specific code to load a module -static std::pair InternalLoadModule(std::pair pair, const char* const* args, bool reserve_mem, FS::File stderrRedirect = FS::File()) +static std::pair InternalLoadModule(std::pair pair, const char* const* args, bool reserve_mem, FS::File stderrRedirect = FS::File(), bool inheritEnvironment = false) { #ifdef _WIN32 // Inherit the socket in the child process @@ -213,6 +263,7 @@ static std::pair InternalLoadModule(std::pair InternalLoadModule(std::pair(args), nullptr); + // By default, the child process gets an empty environment for sandboxing. + // When Box64 emulation is used, the child needs to inherit the parent's + // environment so Box64 can find its configuration (e.g. ~/.box64rc, HOME) + // and honor settings like BOX64_DYNAREC_PERFMAP. + char* emptyEnv[] = {nullptr}; + char** envp = inheritEnvironment ? environ : emptyEnv; + int err = posix_spawn(&pid, args[0], &fileActions, nullptr, const_cast(args), envp); posix_spawn_file_actions_destroy(&fileActions); if (err != 0) { Sys::Drop("VM: Failed to spawn process: %s", strerror(err)); @@ -255,6 +312,10 @@ static std::pair CreateNaClVM(std::pair CreateNaClVM(std::pair CreateNaClVM(std::pair CreateNaClVM(std::pair CreateNaClVM(std::pair CreateNativeVM(std::pair pair, Str::StringRef name, bool debug) {