diff --git a/CHANGELOG.md b/CHANGELOG.md index c94842ac..86edaf35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,6 @@ - Added VR interactions to the big doors on Artiface **Changes**: -- Changed the mod package layout, dropping the `RuntimeDeps` in favor of `package` - Reworked the OpenXR loader, which will now attempt every runtime instead of only the default/preconfigured runtime - Moved startup logic to a prefix, fixing an issue where occasionally the camera would be black when loading in diff --git a/Properties/Resources.Designer.cs b/Properties/Resources.Designer.cs index d65bd936..d9cd93cf 100644 --- a/Properties/Resources.Designer.cs +++ b/Properties/Resources.Designer.cs @@ -69,5 +69,28 @@ internal static byte[] lethalcompanyvr { return ((byte[])(obj)); } } + + /// + /// Looks up a localized string similar to { + /// "name": "OpenXR XR Plugin", + /// "version": "1.8.2", + /// "libraryName": "UnityOpenXR", + /// "displays": [ + /// { + /// "id": "OpenXR Display" + /// } + /// ], + /// "inputs": [ + /// { + /// "id": "OpenXR Input" + /// } + /// ] + ///}. + /// + internal static string UnitySubsystemsManifest { + get { + return ResourceManager.GetString("UnitySubsystemsManifest", resourceCulture); + } + } } } diff --git a/Properties/Resources.resx b/Properties/Resources.resx index c7b5f6a9..b029ac2d 100644 --- a/Properties/Resources.resx +++ b/Properties/Resources.resx @@ -122,4 +122,21 @@ ..\Resources\lethalcompanyvr;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + { + "name": "OpenXR XR Plugin", + "version": "1.8.2", + "libraryName": "UnityOpenXR", + "displays": [ + { + "id": "OpenXR Display" + } + ], + "inputs": [ + { + "id": "OpenXR Input" + } + ] +} + \ No newline at end of file diff --git a/Source/Plugin.cs b/Source/Plugin.cs index 3c3455dc..2719ed6e 100644 --- a/Source/Plugin.cs +++ b/Source/Plugin.cs @@ -6,8 +6,8 @@ using System.Collections; using System.Globalization; using System.IO; -using System.IO.Compression; using System.Linq; +using System.Reflection; using UnityEngine; using UnityEngine.InputSystem; using UnityEngine.Rendering; @@ -51,9 +51,6 @@ private void Awake() // configurations runs after the Input System has already been initialized InputSystem.PerformDefaultPluginInitialization(); - // Force the XR Interaction Toolkit assembly to load before we load asset bundles - _ = TrackedDeviceGraphicRaycaster.s_Corners; - // Plugin startup logic LCVR.Logger.SetSource(Logger); @@ -62,16 +59,6 @@ private void Awake() Logger.LogInfo($"Plugin {PLUGIN_NAME} is starting..."); - // Extract LCVR dependencies - if (!ExtractPackage(out var mustRestart)) - { - Logger.LogError("Failed to extract LCVR dependencies, disabling mod"); - return; - } - - if (mustRestart) - Flags |= Flags.RestartRequired; - // Allow disabling VR via config and command line var disableVr = Config.DisableVR.Value || Environment.GetCommandLineArgs().Contains("--disable-vr", StringComparer.OrdinalIgnoreCase); @@ -118,6 +105,12 @@ private void Awake() return; } } + + if (!LoadEarlyRuntimeDependencies()) + { + Logger.LogError("Disabling mod because required runtime dependencies could not be loaded!"); + return; + } if (!AssetManager.LoadAssets()) { @@ -149,56 +142,113 @@ private bool VerifyGameVersion() return GAME_ASSEMBLY_HASHES.Contains(shasum); } - /// - /// Verifies and extracts the LCVR dependencies (if necessary), returning whether the game needs to restart - /// - private bool ExtractPackage(out bool mustRestart) + private bool LoadEarlyRuntimeDependencies() { - mustRestart = false; - try { - var basePath = Path.Combine(Paths.GameRootPath, "Lethal Company_Data"); - using var zip = ZipFile.OpenRead(Path.Combine(Info.Location, "package")); + var deps = Path.Combine(Path.GetDirectoryName(Info.Location)!, "RuntimeDeps"); - foreach (var entry in zip.Entries.Where(entry => - !entry.FullName.EndsWith('/') || !string.IsNullOrEmpty(entry.Name))) + foreach (var file in Directory.GetFiles(deps, "*.dll")) { - var fullPath = Path.Combine(basePath, entry.FullName); - var directoryName = Path.GetDirectoryName(fullPath)!; + var filename = Path.GetFileName(file); - if (!Directory.Exists(directoryName)) - { - Directory.CreateDirectory(directoryName); - mustRestart = true; - } - - using var stream = entry.Open(); - using var reader = new BinaryReader(stream); - - var bytes = reader.ReadBytes((int)entry.Length); + // Ignore known unmanaged libraries + if (filename == "UnityOpenXR.dll" || filename == "openxr_loader.dll") + continue; - // Check if file is up-to-date - if (File.Exists(fullPath) && - Utils.ComputeHash(bytes).SequenceEqual(Utils.ComputeHash(File.ReadAllBytes(fullPath)))) continue; + Logger.LogDebug($"Early loading {filename}"); - File.WriteAllBytes(fullPath, bytes); - - mustRestart = true; + try + { + Assembly.LoadFile(file); + } + catch (Exception ex) + { + Logger.LogWarning($"Failed to early load {filename}: {ex.Message}"); + } } } catch (Exception ex) { - Logger.LogError($"Failed to validate and extract LCVR package: {ex.Message}"); + Logger.LogError($"Unexpected error occured while loading early runtime dependencies (incorrect folder structure?): {ex.Message}"); return false; } + return true; } + + /// + /// Helper function for SetupRuntimeAssets() to copy resource files and return false if the source does not exist + /// + private bool CopyResourceFile(string sourceFile, string destinationFile) + { + if (!File.Exists(sourceFile)) + return false; + + if (File.Exists(destinationFile)) + { + var sourceHash = Utils.ComputeHash(File.ReadAllBytes(sourceFile)); + var destHash = Utils.ComputeHash(File.ReadAllBytes(destinationFile)); + + if (sourceHash.SequenceEqual(destHash)) + return true; + } + + File.Copy(sourceFile, destinationFile, true); + + return true; + } + + /// + /// Place required runtime libraries and configuration in the game files to allow VR to be started + /// + private bool SetupRuntimeAssets() + { + var mustRestart = false; + + var root = Path.Combine(Paths.GameRootPath, "Lethal Company_Data"); + var subsystems = Path.Combine(root, "UnitySubsystems"); + if (!Directory.Exists(subsystems)) + Directory.CreateDirectory(subsystems); + + var openXr = Path.Combine(subsystems, "UnityOpenXR"); + if (!Directory.Exists(openXr)) + Directory.CreateDirectory(openXr); + + var manifest = Path.Combine(openXr, "UnitySubsystemsManifest.json"); + if (!File.Exists(manifest)) + { + File.WriteAllText(manifest, Properties.Resources.UnitySubsystemsManifest); + mustRestart = true; + } + + var plugins = Path.Combine(root, "Plugins"); + var uoxrTarget = Path.Combine(plugins, "UnityOpenXR.dll"); + var oxrLoaderTarget = Path.Combine(plugins, "openxr_loader.dll"); + + var current = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + var uoxr = Path.Combine(current, "RuntimeDeps/UnityOpenXR.dll"); + var oxrLoader = Path.Combine(current, "RuntimeDeps/openxr_loader.dll"); + + if (!CopyResourceFile(uoxr, uoxrTarget)) + Logger.LogWarning("Could not find UnityOpenXR.dll to copy to the game, VR might not work!"); + + if (!CopyResourceFile(oxrLoader, oxrLoaderTarget)) + Logger.LogWarning("Could not find openxr_loader.dll to copy to the game, VR might not work!"); + + return mustRestart; + } private bool InitializeVR() { Logger.LogInfo("Loading VR..."); + + if (SetupRuntimeAssets()) + { + Logger.LogWarning("You might have to restart the game to allow VR to function properly"); + Flags |= Flags.RestartRequired; + } if (!OpenXR.Loader.InitializeXR()) {