diff --git a/CMakeLists.txt b/CMakeLists.txt index 4023fbc91..cd6dd15a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,7 +33,7 @@ option(ENABLE_FEXCORE_PROFILER "Enables use of the FEXCore timeline profiling ca set (FEXCORE_PROFILER_BACKEND "gpuvis" CACHE STRING "Set which backend to use for the FEXCore profiler (gpuvis, tracy)") option(ENABLE_GLIBC_ALLOCATOR_HOOK_FAULT "Enables glibc memory allocation hooking with fault for CI testing") option(USE_PDB_DEBUGINFO "Builds debug info in PDB format" FALSE) -option(BUILD_STEAM_SUPPORT "Builds support for steampipe depot creation" FALSE) +option(BUILD_STEAM_SUPPORT "Builds FEX for integration into Steam" FALSE) set (X86_32_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/Data/CMake/toolchain_x86_32.cmake" CACHE FILEPATH "Toolchain file for the (cross-)compiler targeting i686") set (X86_64_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/Data/CMake/toolchain_x86_64.cmake" CACHE FILEPATH "Toolchain file for the (cross-)compiler targeting x86_64") @@ -590,6 +590,10 @@ if (BUILD_THUNKS) add_dependencies(uninstall uninstall_guest-libs-32) endif() +if (BUILD_STEAM_SUPPORT) + add_subdirectory(Source/Steam/) +endif() + set(FEX_VERSION_MAJOR "0") set(FEX_VERSION_MINOR "0") set(FEX_VERSION_PATCH "0") diff --git a/Source/Steam/CMakeLists.txt b/Source/Steam/CMakeLists.txt new file mode 100644 index 000000000..1f79b962d --- /dev/null +++ b/Source/Steam/CMakeLists.txt @@ -0,0 +1,20 @@ +add_executable(FEXCompatTool + CompatTool.cpp) + +target_link_libraries(FEXCompatTool + PRIVATE + FEXCore Common CommonTools JemallocLibs) + +install(TARGETS FEXCompatTool + RUNTIME + DESTINATION / + COMPONENT Runtime +) + +# Description json gets installed into root of depot +install(FILES emulator.json + DESTINATION / + COMPONENT Runtime) +install(FILES ConfigTemplate.json + DESTINATION / + COMPONENT Runtime) diff --git a/Source/Steam/CompatTool.cpp b/Source/Steam/CompatTool.cpp new file mode 100644 index 000000000..c27c5f370 --- /dev/null +++ b/Source/Steam/CompatTool.cpp @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT +/* +$info$ +tags: Bin|FEXCompatTool +desc: Used for launching games from Steam +$end_info$ +*/ + +#include "PortabilityInfo.h" +#include "Common/Config.h" +#include "FEXCore/Utils/FileLoading.h" +#include "FEXCore/Utils/StringUtils.h" +#include "FEXHeaderUtils/Filesystem.h" + +#include +#include + +fextl::string GenerateSteamConfigTemplate(const FEX::Config::PortableInformation& PortableInfo) { + const auto ConfigTemplatePath = PortableInfo.InterpreterPath + "ConfigTemplate.json"; + if (!FHU::Filesystem::Exists(ConfigTemplatePath)) { + return {}; + } + + fextl::string Data; + if (!FEXCore::FileLoading::LoadFile(Data, ConfigTemplatePath)) { + return {}; + } + + // Try and find a mount point. + fextl::string MountPoint {}; + const char* RuntimeDir = getenv("XDG_RUNTIME_DIR"); + if (RuntimeDir) { + MountPoint = fextl::fmt::format("{}/fexrootfs/", RuntimeDir); + } else { + const auto UserDirectory = fextl::fmt::format("/run/user/{}", geteuid()); + if (FHU::Filesystem::Exists(UserDirectory)) { + MountPoint = fextl::fmt::format("{}/fexrootfs/", UserDirectory); + } else { + const char* CacheDir = getenv("XDG_CACHE_HOME"); + if (CacheDir) { + MountPoint = fextl::fmt::format("{}/fexrootfs/", CacheDir); + } else { + // We tried really hard to find a mount path. + MountPoint = "~/.cache/fexrootfs/"; + } + } + } + + // Update the @FEX_COMPAT_TOOL@ config to point to the root of the depot. + FEXCore::StringUtils::ReplaceAllInPlace(Data, "@FEX_COMPAT_TOOL@", PortableInfo.InterpreterPath); + + // TODO: This path is getting phased out. + FEXCore::StringUtils::ReplaceAllInPlace(Data, "@FEX_ROOTFS_PATH@", MountPoint); + + // Save the json. + const auto ConfigPath = FEX::Config::GetConfigDirectory(false, PortableInfo); + const auto ConfigLocation = ConfigPath + "Config.json"; + if (!FHU::Filesystem::CreateDirectories(ConfigPath)) { + return {}; + } + + auto File = FEXCore::File::File(ConfigLocation.c_str(), + FEXCore::File::FileModes::WRITE | FEXCore::File::FileModes::CREATE | FEXCore::File::FileModes::TRUNCATE); + + if (!File.IsValid()) { + return {}; + } + + File.Write(Data.data(), Data.size()); + return ConfigPath; +} + +fextl::string GenerateSteamAppConfig(const FEX::Config::PortableInformation& PortableInfo) { + const auto user_config = getenv("FEX_APP_CONFIG"); + if (user_config) { + // If user supplied config then don't use Steam config. + return {}; + } + + // Current supported Steam options. + struct SteamOptions { + bool TSO = true; + bool Multiblock = true; + bool Thunks_GL = false; + bool Thunks_Vulkan = false; + }; + SteamOptions Options {}; + + // Game overrides. + const auto steam_fex_tso = getenv("STEAM_FEX_TSOENABLED"); + if (steam_fex_tso) { + Options.TSO = std::strtoull(steam_fex_tso, nullptr, 0) != 0; + } + + const auto steam_fex_multiblock = getenv("STEAM_FEX_MULTIBLOCK"); + if (steam_fex_multiblock) { + Options.Multiblock = std::strtoull(steam_fex_multiblock, nullptr, 0) != 0; + } + + // UI overrides. + const auto steam_fex_compat = getenv("STEAM_COMPAT_FEX_CONFIG"); + if (steam_fex_compat) { + const auto steam_fex_compat_view = std::string_view(steam_fex_compat); + if (steam_fex_compat_view.find("TSOEnabled:1") != steam_fex_compat_view.npos) { + Options.TSO = true; + } + if (steam_fex_compat_view.find("Multiblock:1") != steam_fex_compat_view.npos) { + Options.Multiblock = true; + } + if (steam_fex_compat_view.find("ThunksDB_GL:1") != steam_fex_compat_view.npos) { + Options.Thunks_GL = true; + } + if (steam_fex_compat_view.find("ThunksDB_Vulkan:1") != steam_fex_compat_view.npos) { + Options.Thunks_Vulkan = true; + } + } + + // Create the json. + char Buffer[4096]; + char* Dest {}; + Dest = json_objOpen(Buffer, nullptr); + { + Dest = json_objOpen(Dest, "Config"); + Dest = json_str(Dest, "TSOEnabled", Options.TSO ? "1" : "0"); + Dest = json_str(Dest, "Multiblock", Options.Multiblock ? "1" : "0"); + Dest = json_objClose(Dest); + } + + { + Dest = json_objOpen(Dest, "ThunksDB"); + Dest = json_str(Dest, "GL", Options.Thunks_GL ? "1" : "0"); + Dest = json_str(Dest, "Vulkan", Options.Thunks_Vulkan ? "1" : "0"); + Dest = json_objClose(Dest); + } + + Dest = json_objClose(Dest); + json_end(Dest); + + // Save the json. + const auto ConfigPath = FEX::Config::GetConfigDirectory(false, PortableInfo); + const auto ConfigLocation = ConfigPath + "app_config.json"; + if (!FHU::Filesystem::CreateDirectories(ConfigPath)) { + return {}; + } + + auto File = FEXCore::File::File(ConfigLocation.c_str(), + FEXCore::File::FileModes::WRITE | FEXCore::File::FileModes::CREATE | FEXCore::File::FileModes::TRUNCATE); + + if (!File.IsValid()) { + return {}; + } + + File.Write(Buffer, strlen(Buffer)); + return ConfigLocation; +} + +int main(int argc, const char** argv) { + const auto PortableInfo = FEX::ReadPortabilityInformation(); + + const auto TemplateConfigPath = GenerateSteamConfigTemplate(PortableInfo); + const auto AppConfigPath = GenerateSteamAppConfig(PortableInfo); + + if (!TemplateConfigPath.empty()) { + setenv("FEX_APP_CONFIG_LOCATION", TemplateConfigPath.c_str(), true); + } + + if (!AppConfigPath.empty()) { + setenv("FEX_APP_CONFIG", AppConfigPath.c_str(), true); + } + + const auto FEXInterpreterPath = PortableInfo.InterpreterPath + "usr/bin/FEX"; + + // Due to no arguments for this application, just replace argv[0] and execve again. + argv[0] = FEXInterpreterPath.c_str(); + execv(FEXInterpreterPath.c_str(), const_cast(argv)); + + // Save errno as it can change after calling `perror`. + const auto saved_errno = errno; + + perror(argv[0]); + + if (saved_errno == ENOENT) { + return 127; + } + + return 126; +} diff --git a/Source/Steam/ConfigTemplate.json b/Source/Steam/ConfigTemplate.json new file mode 100644 index 000000000..4be0c9c6b --- /dev/null +++ b/Source/Steam/ConfigTemplate.json @@ -0,0 +1,9 @@ +{ + "Config": { + "X87ReducedPrecision": "1", + "RootFS": "@FEX_ROOTFS_PATH@/", + "ThunkHostLibs": "@FEX_COMPAT_TOOL@/usr/lib/aarch64-linux-gnu/fex-emu/HostThunks", + "ThunkGuestLibs": "@FEX_COMPAT_TOOL@/usr/share/fex-emu/GuestThunks", + "ProfileStats": "1" + } +} diff --git a/Source/Steam/emulator.json b/Source/Steam/emulator.json new file mode 100644 index 000000000..2ae15536a --- /dev/null +++ b/Source/Steam/emulator.json @@ -0,0 +1,12 @@ +{ + "emulator_v0": { + "argv": "./usr/bin/FEX", + "environment": { "FEX_PORTABLE": "1" }, + "container_argv": "./usr/bin/FEX", + "container_environment": { "FEX_ROOTFS": "" }, + "main_argv": "./FEXCompatTool", + "emulated_architectures": ["x86_64-linux-gnu", "i386-linux-gnu"], + "required_architectures": ["aarch64-linux-gnu"], + "required_libraries": ["libc.so.6", "libstdc++.so.6"] + } +}