Skip to content

Commit

Permalink
FEXInterpreter: Support portable installs
Browse files Browse the repository at this point in the history
  • Loading branch information
Sonicadvance1 committed Sep 2, 2024
1 parent 114112a commit 509ae13
Show file tree
Hide file tree
Showing 11 changed files with 111 additions and 39 deletions.
8 changes: 8 additions & 0 deletions FEXCore/Scripts/config_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ def print_man_environment_tail():
],
"''", True)

print_man_env_option(
"FEX_PORTABLE",
[
"Allows FEX to run without installation. Global locations for configuration and binfmt_misc are ignored. These files are instead read from <FEXInterpreterPath>/fex-emu/ by default.",
"For further customization, see FEX_APP_CONFIG_LOCATION and FEX_APP_DATA_LOCATION."
],
"''", True)

def print_man_header():
header ='''.Dd {0}
.Dt FEX
Expand Down
51 changes: 33 additions & 18 deletions Source/Common/Config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,10 +338,13 @@ fextl::string RecoverGuestProgramFilename(fextl::string Program, bool ExecFDInte
}

ApplicationNames LoadConfig(fextl::unique_ptr<FEX::ArgLoader::ArgLoader> ArgsLoader, bool LoadProgramConfig, char** const envp,
bool ExecFDInterp, int ProgramFDFromEnv) {
FEX::Config::InitializeConfigs();
bool ExecFDInterp, int ProgramFDFromEnv, const PortableInformation& PortableInfo) {
const bool IsPortable = PortableInfo.IsPortable;
FEX::Config::InitializeConfigs(PortableInfo);
FEXCore::Config::Initialize();
FEXCore::Config::AddLayer(CreateGlobalMainLayer());
if (!IsPortable) {
FEXCore::Config::AddLayer(CreateGlobalMainLayer());
}
FEXCore::Config::AddLayer(CreateMainLayer());

auto Args = ArgsLoader->Get();
Expand Down Expand Up @@ -391,15 +394,19 @@ ApplicationNames LoadConfig(fextl::unique_ptr<FEX::ArgLoader::ArgLoader> ArgsLoa
}
}

FEXCore::Config::AddLayer(CreateAppLayer(ProgramName, FEXCore::Config::LayerType::LAYER_GLOBAL_APP));
if (!IsPortable) {
FEXCore::Config::AddLayer(CreateAppLayer(ProgramName, FEXCore::Config::LayerType::LAYER_GLOBAL_APP));
}
FEXCore::Config::AddLayer(CreateAppLayer(ProgramName, FEXCore::Config::LayerType::LAYER_LOCAL_APP));

auto SteamID = getenv("SteamAppId");
if (SteamID) {
// If a SteamID exists then let's search for Steam application configs as well.
// We want to key off both the SteamAppId number /and/ the executable since we may not want to thunk all binaries.
fextl::string SteamAppName = fextl::fmt::format("Steam_{}_{}", SteamID, ProgramName);
FEXCore::Config::AddLayer(CreateAppLayer(SteamAppName, FEXCore::Config::LayerType::LAYER_GLOBAL_STEAM_APP));
if (!IsPortable) {
FEXCore::Config::AddLayer(CreateAppLayer(SteamAppName, FEXCore::Config::LayerType::LAYER_GLOBAL_STEAM_APP));
}
FEXCore::Config::AddLayer(CreateAppLayer(SteamAppName, FEXCore::Config::LayerType::LAYER_LOCAL_STEAM_APP));
}
}
Expand Down Expand Up @@ -473,12 +480,16 @@ const char* GetHomeDirectory() {
}
#endif

fextl::string GetDataDirectory() {
fextl::string DataDir {};
fextl::string GetDataDirectory(const PortableInformation& PortableInfo) {
const char* DataOverride = getenv("FEX_APP_DATA_LOCATION");

if (PortableInfo.IsPortable && !DataOverride) {
return fextl::fmt::format("{}fex-emu/", PortableInfo.InterpreterPath);
}

fextl::string DataDir {};
const char* HomeDir = GetHomeDirectory();
const char* DataXDG = getenv("XDG_DATA_HOME");
const char* DataOverride = getenv("FEX_APP_DATA_LOCATION");
if (DataOverride) {
// Data override will override the complete directory
DataDir = DataOverride;
Expand All @@ -489,14 +500,18 @@ fextl::string GetDataDirectory() {
return DataDir;
}

fextl::string GetConfigDirectory(bool Global) {
fextl::string GetConfigDirectory(bool Global, const PortableInformation& PortableInfo) {
const char* ConfigOverride = getenv("FEX_APP_CONFIG_LOCATION");
if (PortableInfo.IsPortable && (Global || !ConfigOverride)) {
return fextl::fmt::format("{}fex-emu/", PortableInfo.InterpreterPath);
}

fextl::string ConfigDir;
if (Global) {
ConfigDir = GLOBAL_DATA_DIRECTORY;
} else {
const char* HomeDir = GetHomeDirectory();
const char* ConfigXDG = getenv("XDG_CONFIG_HOME");
const char* ConfigOverride = getenv("FEX_APP_CONFIG_LOCATION");
if (ConfigOverride) {
// Config override completely overrides the config directory
ConfigDir = ConfigOverride;
Expand All @@ -515,15 +530,15 @@ fextl::string GetConfigDirectory(bool Global) {
return ConfigDir;
}

fextl::string GetConfigFileLocation(bool Global) {
return GetConfigDirectory(Global) + "Config.json";
fextl::string GetConfigFileLocation(bool Global, const PortableInformation& PortableInfo) {
return GetConfigDirectory(Global, PortableInfo) + "Config.json";
}

void InitializeConfigs() {
FEXCore::Config::SetDataDirectory(GetDataDirectory());
FEXCore::Config::SetConfigDirectory(GetConfigDirectory(false), false);
FEXCore::Config::SetConfigDirectory(GetConfigDirectory(true), true);
FEXCore::Config::SetConfigFileLocation(GetConfigFileLocation(false), false);
FEXCore::Config::SetConfigFileLocation(GetConfigFileLocation(true), true);
void InitializeConfigs(const PortableInformation& PortableInfo) {
FEXCore::Config::SetDataDirectory(GetDataDirectory(PortableInfo));
FEXCore::Config::SetConfigDirectory(GetConfigDirectory(false, PortableInfo), false);
FEXCore::Config::SetConfigDirectory(GetConfigDirectory(true, PortableInfo), true);
FEXCore::Config::SetConfigFileLocation(GetConfigFileLocation(false, PortableInfo), false);
FEXCore::Config::SetConfigFileLocation(GetConfigFileLocation(true, PortableInfo), true);
}
} // namespace FEX::Config
16 changes: 11 additions & 5 deletions Source/Common/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ struct ApplicationNames {
fextl::string ProgramName;
};

struct PortableInformation {
bool IsPortable;
// Path of folder containing FEXInterpreter (including / at the end)
fextl::string InterpreterPath;
};

/**
* @brief Loads the FEX and application configurations for the application that is getting ready to run.
*
Expand All @@ -40,15 +46,15 @@ struct ApplicationNames {
* @return The application name and path structure
*/
ApplicationNames LoadConfig(fextl::unique_ptr<FEX::ArgLoader::ArgLoader> ArgLoader, bool LoadProgramConfig, char** const envp,
bool ExecFDInterp, int ProgramFDFromEnv);
bool ExecFDInterp, int ProgramFDFromEnv, const PortableInformation& PortableInfo);

const char* GetHomeDirectory();

fextl::string GetDataDirectory();
fextl::string GetConfigDirectory(bool Global);
fextl::string GetConfigFileLocation(bool Global);
fextl::string GetDataDirectory(const PortableInformation& PortableInfo);
fextl::string GetConfigDirectory(bool Global, const PortableInformation& PortableInfo);
fextl::string GetConfigFileLocation(bool Global, const PortableInformation& PortableInfo);

void InitializeConfigs();
void InitializeConfigs(const PortableInformation& PortableInfo);

/**
* @brief Loads the global FEX config
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXConfig/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,7 @@ bool DrawUI() {
} // namespace

int main(int argc, char** argv) {
FEX::Config::InitializeConfigs();
FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {});

fextl::string ImGUIConfig = FEXCore::Config::GetConfigDirectory(false) + "FEXConfig_imgui.ini";
auto [window, gl_context] = FEX::GUI::SetupIMGui("#FEXConfig", ImGUIConfig);
Expand Down
61 changes: 52 additions & 9 deletions Source/Tools/FEXLoader/FEXLoader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ desc: Glues the ELF loader, FEXCore and LinuxSyscalls to launch an elf under fex
namespace {
static bool SilentLog;
static int OutputFD {-1};
static bool ExecutedWithFD {false};

void MsgHandler(LogMan::DebugLevels Level, const char* Message) {
if (SilentLog) {
Expand Down Expand Up @@ -194,14 +193,55 @@ void RootFSRedirect(fextl::string* Filename, const fextl::string& RootFS) {
}
}

bool RanAsInterpreter(const char* Program) {
FEX::Config::PortableInformation ReadPortabilityInformation() {
const FEX::Config::PortableInformation BadResult {false, {}};
const char* PortableConfig = getenv("FEX_PORTABLE");
if (!PortableConfig) {
return BadResult;
}

uint32_t Value {};
std::string_view PortableView {PortableConfig};

if (std::from_chars(PortableView.data(), PortableView.data() + PortableView.size(), Value).ec != std::errc {} || Value == 0) {
return BadResult;
}

// Read the FEXInterpreter path from `/proc/self/exe` which is always a symlink to the absolute path of the executable running.
// This way we can get the parent path that the application is executing from.
char SelfPath[PATH_MAX];
auto Result = readlink("/proc/self/exe", SelfPath, PATH_MAX);
if (Result == -1) {
return BadResult;
}

std::string_view SelfPathView {SelfPath, std::min<size_t>(PATH_MAX, Result)};

// Extract the absolute path from the FEXInterpreter path
return {true, fextl::string {SelfPathView.substr(0, SelfPathView.find_last_of('/') + 1)}};
}

bool RanAsInterpreter(bool ExecutedWithFD) {
return ExecutedWithFD || FEXLOADER_AS_INTERPRETER;
}

bool IsInterpreterInstalled() {
// The interpreter is installed if both the binfmt_misc handlers are available
// Or if we were originally executed with FD. Which means the interpreter is installed
/**
* @brief Queries if FEX is installed as a binfmt_misc interpreter
*
* @param ExecutedWithFD If FEXInterpreter was executed using a binfmt_misc FD handle from the kernel
* @param Portable Portability information about FEX being run in portable mode
*
* @return true if the binfmt_misc handlers are installed and being used
*/
bool QueryInterpreterInstalled(bool ExecutedWithFD, const FEX::Config::PortableInformation& Portable) {
if (Portable.IsPortable) {
// Don't use binfmt interpreter even if it's installed
return false;
}

// We know the FEX interpreter is installed through a couple paths.
// - If both of the binfmt_misc handlers are installed
// - Or if we were executed with an FD (Which can only happen from the kernel executing through binfmt_misc)
return ExecutedWithFD || (access("/proc/sys/fs/binfmt_misc/FEX-x86", F_OK) == 0 && access("/proc/sys/fs/binfmt_misc/FEX-x86_64", F_OK) == 0);
}

Expand Down Expand Up @@ -267,9 +307,12 @@ static int StealFEXFDFromEnv(const char* Env) {
int main(int argc, char** argv, char** const envp) {
auto SBRKPointer = FEXCore::Allocator::DisableSBRKAllocations();
FEXCore::Allocator::GLIBCScopedFault GLIBFaultScope;
const bool IsInterpreter = RanAsInterpreter(argv[0]);

ExecutedWithFD = getauxval(AT_EXECFD) != 0;
const bool ExecutedWithFD = getauxval(AT_EXECFD) != 0;
const bool IsInterpreter = RanAsInterpreter(ExecutedWithFD);
const auto PortableInfo = ReadPortabilityInformation();
const bool InterpreterInstalled = QueryInterpreterInstalled(ExecutedWithFD, PortableInfo);

int FEXFD {StealFEXFDFromEnv("FEX_EXECVEFD")};

LogMan::Throw::InstallHandler(AssertHandler);
Expand All @@ -280,7 +323,7 @@ int main(int argc, char** argv, char** const envp) {
argc, argv);
auto Args = ArgsLoader->Get();
auto ParsedArgs = ArgsLoader->GetParsedArgs();
auto Program = FEX::Config::LoadConfig(std::move(ArgsLoader), true, envp, ExecutedWithFD, FEXFD);
auto Program = FEX::Config::LoadConfig(std::move(ArgsLoader), true, envp, ExecutedWithFD, FEXFD, PortableInfo);

if (Program.ProgramPath.empty() && FEXFD == -1) {
// Early exit if we weren't passed an argument
Expand All @@ -290,7 +333,7 @@ int main(int argc, char** argv, char** const envp) {
// Reload the meta layer
FEXCore::Config::ReloadMetaLayer();
FEXCore::Config::Set(FEXCore::Config::CONFIG_IS_INTERPRETER, IsInterpreter ? "1" : "0");
FEXCore::Config::Set(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, IsInterpreterInstalled() ? "1" : "0");
FEXCore::Config::Set(FEXCore::Config::CONFIG_INTERPRETER_INSTALLED, InterpreterInstalled ? "1" : "0");
#ifdef VIXL_SIMULATOR
// If running under the vixl simulator, ensure that indirect runtime calls are enabled.
FEXCore::Config::EraseSet(FEXCore::Config::CONFIG_DISABLE_VIXL_INDIRECT_RUNTIME_CALLS, "0");
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXQonfig/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ void ConfigRuntime::onLoad(const QUrl& Filename) {
int main(int Argc, char** Argv) {
QApplication App(Argc, Argv);

FEX::Config::InitializeConfigs();
FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {});
fextl::string ConfigFilename = Argc > 1 ? Argv[1] : FEXCore::Config::GetConfigFileLocation();
ConfigInit(ConfigFilename);

Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXRootFSFetcher/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ bool ExtractEroFS(const fextl::string& Path, const fextl::string& RootFS, const

int main(int argc, char** argv, char** const envp) {
auto ArgsLoader = fextl::make_unique<FEX::ArgLoader::ArgLoader>(FEX::ArgLoader::ArgLoader::LoadType::WITHOUT_FEXLOADER_PARSER, argc, argv);
FEX::Config::LoadConfig(std::move(ArgsLoader), false, envp, false, {});
FEX::Config::LoadConfig(std::move(ArgsLoader), false, envp, false, false, FEX::Config::PortableInformation {});

// Reload the meta layer
FEXCore::Config::ReloadMetaLayer();
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/FEXServer/Main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ int main(int argc, char** argv, char** const envp) {
}

auto ArgsLoader = fextl::make_unique<FEX::ArgLoader::ArgLoader>(FEX::ArgLoader::ArgLoader::LoadType::WITHOUT_FEXLOADER_PARSER, argc, argv);
FEX::Config::LoadConfig(std::move(ArgsLoader), false, envp, false, {});
FEX::Config::LoadConfig(std::move(ArgsLoader), false, envp, false, false, FEX::Config::PortableInformation {});

// Reload the meta layer
FEXCore::Config::ReloadMetaLayer();
Expand Down
2 changes: 1 addition & 1 deletion Source/Tools/TestHarnessRunner/TestHarnessRunner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ int main(int argc, char** argv, char** const envp) {
LogMan::Throw::InstallHandler(AssertHandler);
LogMan::Msg::InstallHandler(MsgHandler);

FEX::Config::InitializeConfigs();
FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {});
FEXCore::Config::Initialize();
auto ArgsLoader = fextl::make_unique<FEX::ArgLoader::ArgLoader>(FEX::ArgLoader::ArgLoader::LoadType::WITH_FEXLOADER_PARSER, argc, argv);
auto Args = ArgsLoader->Get();
Expand Down
2 changes: 1 addition & 1 deletion Source/Windows/ARM64EC/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ extern "C" void SyncThreadContext(CONTEXT* Context) {

NTSTATUS ProcessInit() {
FEX::Windows::InitCRTProcess();
FEX::Config::InitializeConfigs();
FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {});
FEXCore::Config::Initialize();
FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer());
FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer());
Expand Down
2 changes: 1 addition & 1 deletion Source/Windows/WOW64/Module.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ class WowSyscallHandler : public FEXCore::HLE::SyscallHandler, public FEXCore::A

void BTCpuProcessInit() {
FEX::Windows::InitCRTProcess();
FEX::Config::InitializeConfigs();
FEX::Config::InitializeConfigs(FEX::Config::PortableInformation {});
FEXCore::Config::Initialize();
FEXCore::Config::AddLayer(FEX::Config::CreateGlobalMainLayer());
FEXCore::Config::AddLayer(FEX::Config::CreateMainLayer());
Expand Down

0 comments on commit 509ae13

Please sign in to comment.