From 349df9c436c2d27e4193cec9f41b16a4b5b60a5c Mon Sep 17 00:00:00 2001 From: Andrew Eikum Date: Wed, 26 Feb 2020 10:50:10 -0600 Subject: [PATCH] steam_helper: Set up VR paths in steam.exe, not proton This speeds up game launch times. --- build/makefile_base.mak | 2 +- proton | 74 ------ steam_helper/steam.cpp | 293 +++++++++++++++++++++- vrclient_x64/vrclient_x64/vrclient_main.c | 42 +++- 4 files changed, 326 insertions(+), 85 deletions(-) diff --git a/build/makefile_base.mak b/build/makefile_base.mak index 15409ece..076a3a71 100644 --- a/build/makefile_base.mak +++ b/build/makefile_base.mak @@ -1149,7 +1149,7 @@ steam_configure: $(STEAMEXE_CONFIGURE_FILES) steam: SHELL = $(CONTAINER_SHELL32) steam: $(STEAMEXE_CONFIGURE_FILES) | $(WINE_BUILDTOOLS32) $(filter $(MAKECMDGOALS),wine64 wine32 wine) - +env PATH="$(abspath $(TOOLS_DIR32))/bin:$(PATH)" LDFLAGS="-m32" CXXFLAGS="-m32 -Wno-attributes $(COMMON_FLAGS) -g" CFLAGS="-m32 $(COMMON_FLAGS) -g" \ + +env PATH="$(abspath $(TOOLS_DIR32))/bin:$(PATH)" LDFLAGS="-m32" CXXFLAGS="-std=gnu++11 -m32 -Wno-attributes $(COMMON_FLAGS) -g" CFLAGS="-m32 $(COMMON_FLAGS) -g" \ $(MAKE) -C $(STEAMEXE_OBJ) [ x"$(STRIP)" = x ] || $(STRIP) $(STEAMEXE_OBJ)/steam.exe.so mkdir -pv $(DST_DIR)/lib/wine/ diff --git a/proton b/proton index 9c16aba0..38a6393e 100755 --- a/proton +++ b/proton @@ -513,79 +513,6 @@ class Session: else: self.env["WINEDLLOVERRIDES"] = s - def setup_vr(self): - #parse linux openvr config and present it in win32 format to the app. - #logic from openvr's CVRPathRegistry_Public::GetPaths - - #check environment for overrides - vr_runtime = None - if "VR_OVERRIDE" in self.env: - vr_runtime = self.env["VR_OVERRIDE"] - self.env.pop("VR_OVERRIDE") - - vr_config = None - if "VR_CONFIG_PATH" in self.env: - vr_config = self.env["VR_CONFIG_PATH"] - self.env.pop("VR_CONFIG_PATH") - - vr_log = None - if "VR_LOG_PATH" in self.env: - vr_log = self.env["VR_LOG_PATH"] - self.env.pop("VR_LOG_PATH") - - #load from json if needed - if vr_runtime is None or \ - vr_config is None or \ - vr_log is None: - try: - path = os.environ.get("XDG_CONFIG_HOME", os.environ["HOME"] + "/.config") - path = path + "/openvr/openvrpaths.vrpath" - - with open(path, "r") as jfile: - j = json.load(jfile) - - if vr_runtime is None: - vr_runtime = j["runtime"][0] - - if vr_config is None: - vr_config = j["config"][0] - - if vr_log is None: - vr_log = j["log"][0] - except (TypeError, ValueError, OSError): - #log("Missing or invalid openvrpaths.vrpath file! " + str(sys.exc_info()[1])) - pass - - makedirs(g_compatdata.prefix_dir + "/drive_c/users/steamuser/Local Settings/Application Data/openvr") - - #remove existing file - vrpaths_name = g_compatdata.prefix_dir + "/drive_c/users/steamuser/Local Settings/Application Data/openvr/openvrpaths.vrpath" - if os.path.exists(vrpaths_name): - os.remove(vrpaths_name) - - #dump new file - if not vr_runtime is None: - try: - self.env["PROTON_VR_RUNTIME"] = vr_runtime - - j = { "runtime": [ "C:\\vrclient\\", "C:\\vrclient" ] } - - if not vr_config is None: - win_vr_config = subprocess.check_output([g_proton.wine_bin, "winepath", "-w", vr_config], env=self.env, stderr=self.log_file).decode("utf-8") - j["config"] = [ win_vr_config.strip() ] - - if not vr_log is None: - win_vr_log = subprocess.check_output([g_proton.wine_bin, "winepath", "-w", vr_log], env=self.env, stderr=self.log_file).decode("utf-8") - j["log"] = [ win_vr_log.strip() ] - - j["version"] = 1 - j["jsonid"] = "vrpathreg" - - with open(vrpaths_name, "w") as vfile: - json.dump(j, vfile, indent=2) - except (ValueError, OSError): - log("Unable to write VR config! " + str(sys.exc_info()[1])) - def dump_dbg_env(self, f): f.write("PATH=\"" + self.env["PATH"] + "\" \\\n") f.write("\tTERM=\"xterm\" \\\n") #XXX @@ -696,7 +623,6 @@ class Session: subprocess.call(args, env=local_env, stderr=self.log_file, stdout=self.log_file) def run(self): - self.setup_vr() if "PROTON_DUMP_DEBUG_COMMANDS" in self.env and nonzero(self.env["PROTON_DUMP_DEBUG_COMMANDS"]): try: self.dump_dbg_scripts() diff --git a/steam_helper/steam.cpp b/steam_helper/steam.cpp index 9dcc8914..21134edd 100644 --- a/steam_helper/steam.cpp +++ b/steam_helper/steam.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, Valve Corporation + * Copyright (c) 2015, 2019, 2020 Valve Corporation * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -33,6 +33,7 @@ * Windows version of Steam running. */ #include +#include #include #include @@ -46,6 +47,8 @@ #include "wine/debug.h" +#include "json/json.h" + WINE_DEFAULT_DEBUG_CHANNEL(steam); EXTERN_C HANDLE CDECL __wine_make_process_system(void); @@ -117,6 +120,292 @@ static void setup_steam_registry(void) SteamAPI_Shutdown(); } +static std::string get_linux_vr_path(void) +{ + const char *e; + + e = getenv("VR_PATHREG_OVERRIDE"); + if(e && *e) + return e; + + e = getenv("XDG_CONFIG_HOME"); + + if(!e || !*e) + e = getenv("HOME"); + + if(!e || !*e) + return ""; + + return std::string(e) + "/.config/openvr/openvrpaths.vrpath"; +} + +static bool get_windows_vr_path(WCHAR *out_path, bool create) +{ + if(FAILED(SHGetFolderPathW(NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, + NULL, 0, out_path))) + return false; + + lstrcatW(out_path, L"\\openvr"); + + if(create) + CreateDirectoryW(out_path, NULL); + + lstrcatW(out_path, L"\\openvrpaths.vrpath"); + + return true; +} + +static WCHAR *str_to_wchar(const std::string &str) +{ + DWORD sz = MultiByteToWideChar(CP_UNIXCP, 0, str.c_str(), -1, NULL, 0); + if(!sz) + return NULL; + + WCHAR *ret = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, sizeof(WCHAR) * sz); + if(!ret) + return NULL; + + sz = MultiByteToWideChar(CP_UNIXCP, 0, str.c_str(), -1, ret, sz); + if(!sz) + { + HeapFree(GetProcessHeap(), 0, ret); + return NULL; + } + + return ret; +} + +static std::string read_text_file(const WCHAR *filename) +{ + HANDLE ifile = CreateFileW(filename, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if(ifile == INVALID_HANDLE_VALUE) + return ""; + + LARGE_INTEGER size; + + if(!GetFileSizeEx(ifile, &size)) + { + CloseHandle(ifile); + return ""; + } + + char *buf = (char *)HeapAlloc(GetProcessHeap(), 0, size.u.LowPart); + if(!buf) + { + CloseHandle(ifile); + return ""; + } + + DWORD readed; + + if(!ReadFile(ifile, buf, size.u.LowPart, &readed, NULL)) + { + HeapFree(GetProcessHeap(), 0, buf); + CloseHandle(ifile); + return ""; + } + + CloseHandle(ifile); + + DWORD outsize = 1; + for(DWORD i = 1; i < readed; ++i) + { + if(buf[i] == '\n' && buf[i - 1] == '\r') // CRLF + buf[outsize - 1] = '\n'; + else + buf[outsize++] = buf[i]; + } + + std::string ret(buf, outsize); + + HeapFree(GetProcessHeap(), 0, buf); + + return ret; +} + +static bool write_string_to_file(const WCHAR *filename, const std::string &contents) +{ + HANDLE ofile = CreateFileW(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, NULL); + if(ofile == INVALID_HANDLE_VALUE) + return false; + + DWORD written; + + if(!WriteFile(ofile, contents.data(), (DWORD)contents.length(), &written, NULL)) + { + CloseHandle(ofile); + return false; + } + + CloseHandle(ofile); + + return true; +} + +static bool convert_path_to_win(std::string &s) +{ + WCHAR *path = wine_get_dos_file_name(s.c_str()); + if(!path) + return false; + + DWORD sz = WideCharToMultiByte(CP_UTF8, 0, path, -1, NULL, 0, NULL, NULL); + if(!sz) + { + HeapFree(GetProcessHeap(), 0, path); + return false; + } + + char *pathUTF8 = (char *)HeapAlloc(GetProcessHeap(), 0, sz); + if(!pathUTF8) + { + HeapFree(GetProcessHeap(), 0, path); + return false; + } + + sz = WideCharToMultiByte(CP_UTF8, 0, path, -1, pathUTF8, sz, NULL, NULL); + if(!sz) + { + HeapFree(GetProcessHeap(), 0, pathUTF8); + HeapFree(GetProcessHeap(), 0, path); + return false; + } + + s = pathUTF8; + + HeapFree(GetProcessHeap(), 0, pathUTF8); + HeapFree(GetProcessHeap(), 0, path); + + return true; +} + +static void convert_json_array_paths(Json::Value &arr) +{ + for(uint32_t i = 0; i < arr.size(); ++i) + { + std::string path(arr[i].asString()); + if(convert_path_to_win(path)) + arr[i] = path; + } +} + +static void convert_environment_path(const char *nameA, const WCHAR *nameW) +{ + /* get linux-side variable */ + const char *e = getenv(nameA); + if(!e || !*e) + return; + + /* convert to win and set */ + WCHAR *path = wine_get_dos_file_name(e); + if(!path) + return; + + SetEnvironmentVariableW(nameW, path); + + HeapFree(GetProcessHeap(), 0, path); +} + +static void set_env_from_unix(const WCHAR *name, const std::string &val) +{ + WCHAR valW[MAX_PATH]; + DWORD sz; + + sz = MultiByteToWideChar(CP_UTF8, 0, val.c_str(), -1, valW, MAX_PATH); + if(!sz) + { + WINE_WARN("Invalid utf8 seq in vr runtime key\n"); + return; + } + + SetEnvironmentVariableW(name, valW); +} + +static bool convert_linux_vrpaths(void) +{ + /* read in linux vrpaths */ + std::string linux_vrpaths = get_linux_vr_path(); + if(linux_vrpaths.empty()) + { + WINE_TRACE("Couldn't get openvr vrpaths path\n"); + return false; + } + + WCHAR *linux_vrpathsW = str_to_wchar(linux_vrpaths); + if(!linux_vrpathsW) + return false; + + std::string contents = read_text_file(linux_vrpathsW); + HeapFree(GetProcessHeap(), 0, linux_vrpathsW); + if(contents.empty()) + { + WINE_TRACE("openvr vrpaths is empty\n"); + return false; + } + + Json::Value root; + Json::Reader reader; + + if(!reader.parse(contents, root)) + { + WINE_WARN("Invalid openvr vrpaths JSON\n"); + return false; + } + + /* pass original runtime path into Wine */ + if(root.isMember("runtime") && root["runtime"].isArray() && root["runtime"].size() > 0) + { + set_env_from_unix(L"PROTON_VR_RUNTIME", root["runtime"][0].asString()); + } + + /* set hard-coded paths */ + root["runtime"] = Json::Value(Json::ValueType::arrayValue); + root["runtime"][0] = "C:\\vrclient\\"; + root["runtime"][1] = "C:\\vrclient"; + + /* map linux paths into windows filesystem */ + if(root.isMember("config") && root["config"].isArray()) + convert_json_array_paths(root["config"]); + + if(root.isMember("log") && root["log"].isArray()) + convert_json_array_paths(root["log"]); + + /* external_drivers is currently unsupported in Proton */ + root["external_drivers"] = Json::Value(Json::ValueType::nullValue); + + /* write out windows vrpaths */ + SetEnvironmentVariableW(L"VR_PATHREG_OVERRIDE", NULL); + SetEnvironmentVariableW(L"VR_OVERRIDE", NULL); + convert_environment_path("VR_CONFIG_PATH", L"VR_CONFIG_PATH"); + convert_environment_path("VR_LOG_PATH", L"VR_LOG_PATH"); + Json::StyledWriter writer; + + WCHAR windows_vrpaths[MAX_PATH]; + if(!get_windows_vr_path(windows_vrpaths, true)) + return false; + + contents = writer.write(root); + + write_string_to_file(windows_vrpaths, contents); + + return true; +} + +static void setup_vrpaths(void) +{ + if(!convert_linux_vrpaths()) + { + /* delete the windows file only if the linux conversion fails */ + WCHAR windows_vrpaths[MAX_PATH]; + if(get_windows_vr_path(windows_vrpaths, false)) + { + DeleteFileW(windows_vrpaths); + } + } +} + static WCHAR *strchrW(WCHAR *h, WCHAR n) { do @@ -293,6 +582,8 @@ int main(int argc, char *argv[]) { HANDLE child; + setup_vrpaths(); + child = run_process(); if (child == INVALID_HANDLE_VALUE) diff --git a/vrclient_x64/vrclient_x64/vrclient_main.c b/vrclient_x64/vrclient_x64/vrclient_main.c index e42a1b3c..ad8a2404 100644 --- a/vrclient_x64/vrclient_x64/vrclient_main.c +++ b/vrclient_x64/vrclient_x64/vrclient_main.c @@ -43,6 +43,8 @@ typedef struct winRenderModel_t_1819 winRenderModel_t_1819; typedef struct winRenderModel_TextureMap_t_1819 winRenderModel_TextureMap_t_1819; #include "cppIVRRenderModels_IVRRenderModels_006.h" +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + WINE_DEFAULT_DEBUG_CHANNEL(vrclient); BOOL WINAPI DllMain(HINSTANCE instance, DWORD reason, void *reserved) @@ -216,25 +218,47 @@ static void *(*vrclient_VRClientCoreFactory)(const char *name, int *return_code) static int load_vrclient(void) { - char path[PATH_MAX]; + WCHAR pathW[PATH_MAX]; + char *pathU; + DWORD sz; + +#ifdef _WIN64 + static const char append_path[] = "/bin/linux64/vrclient.so"; +#else + static const char append_path[] = "/bin/vrclient.so"; +#endif if(vrclient_lib) return 1; /* PROTON_VR_RUNTIME is provided by the proton setup script */ - if(!getenv("PROTON_VR_RUNTIME")){ + if(!GetEnvironmentVariableW(L"PROTON_VR_RUNTIME", pathW, ARRAY_SIZE(pathW))) + { TRACE("Linux OpenVR runtime is not available\n"); return 0; } -#ifdef _WIN64 - snprintf(path, PATH_MAX, "%s/bin/linux64/vrclient.so", getenv("PROTON_VR_RUNTIME")); -#else - snprintf(path, PATH_MAX, "%s/bin/vrclient.so", getenv("PROTON_VR_RUNTIME")); -#endif - TRACE("got openvr runtime path: %s\n", path); + sz = WideCharToMultiByte(CP_UNIXCP, 0, pathW, -1, NULL, 0, NULL, NULL); + if(!sz) + { + ERR("Can't convert path to unixcp! %s\n", wine_dbgstr_w(pathW)); + return 0; + } - vrclient_lib = wine_dlopen(path, RTLD_NOW, NULL, 0); + pathU = HeapAlloc(GetProcessHeap(), 0, sz + sizeof(append_path)); + + sz = WideCharToMultiByte(CP_UNIXCP, 0, pathW, -1, pathU, sz, NULL, NULL); + if(!sz) + { + ERR("Can't convert path to unixcp! %s\n", wine_dbgstr_w(pathW)); + return 0; + } + + strcat(pathU, append_path); + + TRACE("got openvr runtime path: %s\n", pathU); + + vrclient_lib = wine_dlopen(pathU, RTLD_NOW, NULL, 0); if(!vrclient_lib){ TRACE("unable to load vrclient.so\n"); return 0;