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())
{