Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
452b49d
Remove TypeLoadExceptionFixer dependency
DaXcess Feb 10, 2026
af4fc70
Finish hooks
DaXcess Feb 10, 2026
001d920
Refactor interaction system (#311)
misterbubb Feb 11, 2026
e1d63b3
The V80 patch
DaXcess Mar 30, 2026
476598c
The SPI patch
DaXcess Apr 5, 2026
06c5a59
Actions fix
DaXcess Apr 5, 2026
2291e01
Shader and bug fixes
DaXcess Apr 6, 2026
070edcc
A few more little fixes
DaXcess Apr 6, 2026
471c9c9
Add status effect and lever slapping
DaXcess Apr 6, 2026
85dde55
Remove dynamic resolution support
DaXcess Apr 6, 2026
300be1f
Add item jiggle shaking interaction
DaXcess Apr 6, 2026
2ec36c9
Revert "Refactor interaction system (#311)" (#315)
DaXcess Apr 6, 2026
2cef1d2
lever fix
DaXcess Apr 6, 2026
becdd7f
more lever fix
DaXcess Apr 6, 2026
4a9ab05
add commit info
DaXcess Apr 6, 2026
b014207
Add automatic log collection
DaXcess Apr 7, 2026
6fc35fc
Fix interactions not being allowed
DaXcess Apr 7, 2026
f99b16b
Fix custom cam bugs and remove ladder climb
DaXcess Apr 7, 2026
e9bfe0b
Fog fixes
DaXcess Apr 7, 2026
376d3a4
Fix helmet option breaking after rejoining
DaXcess Apr 7, 2026
cc664e3
Add SPI compatibility docs
DaXcess Apr 8, 2026
a77db8e
Smooth camera teleport fix
DaXcess Apr 8, 2026
2f9e33e
Fix growths not visible in custom cam
DaXcess Apr 8, 2026
172f295
Fix few bugs
DaXcess Apr 9, 2026
e405fc4
Update changelog
DaXcess Apr 9, 2026
2278532
Multiple bug fixes
DaXcess Apr 10, 2026
734266a
Update COMPATIBILITY.md
DaXcess Apr 10, 2026
7f93207
Update COMPATIBILITY.md
DaXcess Apr 10, 2026
db91e8c
Fix spectator door and enemy interaction
DaXcess Apr 10, 2026
074c5e1
Fix bugs and add item sound sync
DaXcess Apr 11, 2026
be04734
Update README.md
DaXcess Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build-debug.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ jobs:
# Set up template
mkdir package
git --work-tree=./package checkout origin/thunderstore ./

# Create patchers directory
mkdir -p ./package/BepInEx/patchers/LCVR/

# Copy and sign debug binaries
cp bin/Debug/netstandard2.1/LCVR.dll ./package/BepInEx/plugins/LCVR/
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/build-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ jobs:
# Set up template
mkdir package
git --work-tree=./package checkout origin/thunderstore ./

# Create patchers directory
mkdir -p ./package/BepInEx/patchers/LCVR/

# Copy and sign release binaries
cp bin/Release/netstandard2.1/LCVR.dll ./package/BepInEx/plugins/LCVR/
Expand Down
27 changes: 27 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,30 @@
# 1.5.0

**Additions**:
- Added support for the new utility slot, which is accessible like a normal inventory slot
- Added the status text UI (like oxygen level critical) to the VR interface
- Added a fog quality option
- Added audible item shaking on certain supported items

**Changes**:
- Changed rendering to Single Pass Instanced, this will break a lot of mods
- Entrance teleports now behave the same as vanilla when determining your looking direction
- Motion blur has been disabled and cannot be enabled while in VR
- Removed `TypeLoadExceptionFixer` dependency, fixing some issues with UnityExplorer
- Removed toggle sprint option as this is now a vanilla setting
- Reverted the vignette shader back to the built-in one, which doesn't look as good but still functions normally for now
- Spectators can no longer experience the underwater effect
- Made some visual changes to some assets

**Removals**:
- Removed dynamic resolution support as it doesn't seem to function properly with SPI rendering
- Removed the ladder climb interaction for now until a better replacement is ready

**Fixes**:
- Fixed smooth camera having jarring motion after player teleports
- Fixed nutcrackers following spectators after kicking them
- Fixed loading screen partially covering the custom camera

# 1.4.6

**Additions**:
Expand Down
34 changes: 34 additions & 0 deletions Docs/COMPATIBILITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Compatibility with LCVR

> This document is specifically for mods that add anything that can be rendered by the game, it is not about code or hooking into the VR mod.

With the VR mod *finally* hopping over to [Single Pass Instanced rendering](https://docs.unity3d.com/6000.6/Documentation/Manual/SinglePassInstancing.html), a lot of mods will break due to them missing SPI support in their exported shaders.

Single Pass Instanced rendering requires two things from shaders for them to render properly, which are:

- The shaders **must** support GPU instancing
- The shaders **must** be built with Stereo variants

# Supporting SPI in custom shaders

> Full technical details are explained [over here](https://docs.unity3d.com/6000.6/Documentation/Manual/SinglePassInstancing.html)

If you are shipping custom/hand written shaders in your mods, you must make changes to the shaders for them to work correctly.

These changes differ depending on what kind of shader you are writing (e.g. post processing shaders require additional changes).

If you are **not** using custom shaders, you do not have to modify anything.

# Exporting shaders with Stereo variants

Even if your shaders already support SPI rendering (whether you're only using HDRP built-in shaders, or your custom shaders already support SPI), Unity will still strip this support out of the shaders by default.

To tell Unity to also build in SPI support when exporting your assets, you will have to install the Unity OpenXR plugin.

<img width="1279" height="351" alt="image" src="https://github.com/user-attachments/assets/fe8bbcee-19ce-4ff0-a190-016e0cd3dfc9" />

> You can also add the package by name: `com.unity.xr.openxr`

You do not have to configure anything after installing this plugin, just re-export your asset bundle and all bundled shaders will now have SPI variants built in.

> In the case play mode starts behaving weirdly, disable VR by going to `Player Settings` -> `XR Plug-in Management` and disabling the `Initialize XR on Startup` option
10 changes: 4 additions & 6 deletions Docs/Thunderstore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ Here is a list of LCVR versions and which version(s) of Lethal Company it suppor

| LCVR | Lethal Company |
|-------------------|-------------------|
| v1.4.6 *(LATEST)* | V73 |
| v1.5.0 *(LATEST)* | V81 |
| v1.4.7 | V73 |
| v1.4.6 | V73 |
| v1.4.5 | V73 |
| v1.4.4 | V73 |
| v1.4.3 | V73 |
Expand Down Expand Up @@ -110,10 +112,6 @@ Most mods should all work fine with LCVR, like interior mods, new moons, most it

In general, most emote mods, mods adding UI elements and mods that require new bindings are not compatible with LCVR by default, and either require configuration changes, or dedicated VR support.

# Hot switching

You can swap between VR and flatscreen when using LCVR. For more info, check the [Hot Switching Wiki Page](https://thunderstore.io/c/lethal-company/p/DaXcess/LethalCompanyVR/wiki/2888-hot-switching/).

# Configuring the mod

You can change the mod configuration from within the game itself. Just launch the game with the VR mod installed, get to the main menu, and press the big VR button on the right side of the screen. This will open a big settings menu where you can configure the VR mod to your liking.
Expand Down Expand Up @@ -239,7 +237,7 @@ This mod, in addition to adding VR and motion controls, also adds a few special

Hate having to just watch a flat screen where your fellow employees die to the horrors of the facilities? Well fear no more! With the new Company™ Device© you retain the rights to wander the desolate planets even when your physical body is no longer showing signs compatible with life!

_Since the company was a big fan of using Linux for the Device©, the colors look more gray when dead since they cheaped out on the HDR support._
_Since the company wants to cut costs, the colors look more gray when dead. This is for **your** benefit!_

You can teleport to other employees by using the **Interact** _(Default: Right Controller Trigger)_ button. This will cycle through each employee in the lobby that has not yet met their maker. Use this to quickly see how a fellow employee is going about their day, or to get unstuck if you have fallen into a pit.

Expand Down
10 changes: 5 additions & 5 deletions LCVR.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>LCVR</AssemblyName>
<Description>Collecting Scrap in VR</Description>
<Version>1.4.6</Version>
<Version>1.5.0</Version>
<Authors>DaXcess</Authors>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>12.0</LangVersion>
<Title>LethalCompanyVR</Title>
<Copyright>Copyright (c) DaXcess 2024-2025</Copyright>
<Copyright>Copyright (c) DaXcess 2024-2026</Copyright>
<PackageProjectUrl>https://lcvr.daxcess.io</PackageProjectUrl>
<RepositoryUrl>https://github.com/DaXcess/LCVR</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
Expand Down Expand Up @@ -37,7 +37,7 @@
<PackageReference Include="CullFactory" Version="1.0.4" />
<PackageReference Include="DissonanceVoip" Version="1.50.0-lc.1" />
<PackageReference Include="Facepunch.Steamworks" Version="2.3.3" />
<PackageReference Include="LethalCompany" Version="1.70.0" />
<PackageReference Include="LethalCompany" Version="1.81.0" />
<PackageReference Include="MoreCompany" Version="1.10.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
<PackageReference Include="Unity.Animation.Rigging" Version="1.2.1" />
Expand All @@ -48,8 +48,8 @@
<PackageReference Include="Unity.TextMeshPro" Version="3.0.6" />
<PackageReference Include="Unity.XR.CoreUtils" Version="2.2.3" />
<PackageReference Include="Unity.XR.Interaction.Toolkit" Version="2.5.3" />
<PackageReference Include="Unity.XR.Management" Version="4.4.1" />
<PackageReference Include="Unity.XR.OpenXR" Version="1.8.2" />
<PackageReference Include="Unity.XR.Management" Version="4.5.4" />
<PackageReference Include="Unity.XR.OpenXR" Version="1.16.1" />
<PackageReference Include="UnityEngine.Modules" Version="2022.3.9" IncludeAssets="compile" />
<PackageReference Include="UnityEngine.SpatialTracking" Version="2.1.10" />
<PackageReference Include="UnityEngine.UI" Version="1.0.0" />
Expand Down
4 changes: 2 additions & 2 deletions Preloader/LCVR.Preload.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
<PropertyGroup>
<Description>LCVR Preloader</Description>
<Authors>DaXcess</Authors>
<Version>1.4.6</Version>
<Version>1.5.0</Version>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<LangVersion>12.0</LangVersion>
<Title>LCVR.Preload</Title>
<Copyright>Copyright (c) DaXcess 2024-2025</Copyright>
<Copyright>Copyright (c) DaXcess 2024-2026</Copyright>
<PackageProjectUrl>https://lcvr.daxcess.io</PackageProjectUrl>
<RepositoryUrl>https://github.com/DaXcess/LCVR</RepositoryUrl>
<ImplicitUsings>enable</ImplicitUsings>
Expand Down
110 changes: 41 additions & 69 deletions Preloader/Preload.cs
Original file line number Diff line number Diff line change
@@ -1,96 +1,68 @@
using System.Reflection;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Mono.Cecil;
using MonoMod.RuntimeDetour;

namespace LCVR.Preload;

public static class Preload
{
public static IEnumerable<string> TargetDLLs { get; } = [];

private const string VR_MANIFEST = """
{
"name": "OpenXR XR Plugin",
"version": "1.8.2",
"libraryName": "UnityOpenXR",
"displays": [
{
"id": "OpenXR Display"
}
],
"inputs": [
{
"id": "OpenXR Input"
}
]
}
""";

private static readonly ManualLogSource Logger = BepInEx.Logging.Logger.CreateLogSource("LCVR.Preload");

public static void Initialize()
{
Logger.LogInfo("Setting up VR runtime assets");

SetupRuntimeAssets();

Logger.LogInfo("We're done here. Goodbye!");
}

/// <summary>
/// Place required runtime libraries and configuration in the game files to allow VR to be started
/// </summary>
private static void SetupRuntimeAssets()
{
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);
Logger.LogInfo("Patching soft dependencies");

var manifest = Path.Combine(openXr, "UnitySubsystemsManifest.json");
if (!File.Exists(manifest))
File.WriteAllText(manifest, VR_MANIFEST);
PatchTypeMethods();

var plugins = Path.Combine(root, "Plugins");
var oxrPluginTarget = Path.Combine(plugins, "UnityOpenXR.dll");
var oxrLoaderTarget = Path.Combine(plugins, "openxr_loader.dll");

var current = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;
var oxrPlugin = Path.Combine(current, "RuntimeDeps/UnityOpenXR.dll");
var oxrLoader = Path.Combine(current, "RuntimeDeps/openxr_loader.dll");
Logger.LogInfo("We're done here. Goodbye!");
}

if (!CopyResourceFile(oxrPlugin, oxrPluginTarget))
Logger.LogWarning("Could not find plugin UnityOpenXR.dll, VR might not work!");
#pragma warning disable CS8618
// Keep in scope just to be sure the hook stays attached
private static Hook _getTypesHook;
private static Hook _isAssignableFromHook;
#pragma warning restore CS8618

if (!CopyResourceFile(oxrLoader, oxrLoaderTarget))
Logger.LogWarning("Could not find plugin openxr_loader.dll, VR might not work!");
}

/// <summary>
/// Helper function for SetupRuntimeAssets() to copy resource files and return false if the source does not exist
/// Hook multiple methods that deal with types so they won't crash if it encounters references to missing assemblies
/// </summary>
private static bool CopyResourceFile(string sourceFile, string destinationFile)
private static void PatchTypeMethods()
{
if (!File.Exists(sourceFile))
return false;
_getTypesHook = new Hook(AccessTools.Method(typeof(Assembly), nameof(Assembly.GetTypes)),
AccessTools.Method(typeof(Preload), nameof(GetTypesHook)));

if (File.Exists(destinationFile))
{
var sourceHash = Utils.ComputeHash(File.ReadAllBytes(sourceFile));
var destHash = Utils.ComputeHash(File.ReadAllBytes(destinationFile));
_isAssignableFromHook =
new Hook(
AccessTools.Method(AccessTools.TypeByName("System.RuntimeType"), "IsAssignableFrom", [typeof(Type)]),
AccessTools.Method(typeof(Preload), nameof(IsAssignableFromHook)));
}

if (sourceHash.SequenceEqual(destHash))
return true;
private static Type[] GetTypesHook(Func<Assembly, Type[]> orig, Assembly self)
{
try
{
return orig(self).Where(t => t != null).ToArray();
}
catch (ReflectionTypeLoadException e)
{
return e.Types.Where(t => t != null).ToArray();
}
}

File.Copy(sourceFile, destinationFile, true);

return true;
private static bool IsAssignableFromHook(Func<Type, Type, bool> orig, Type self, Type c)
{
try
{
return orig(self, c);
}
catch (TypeLoadException)
{
return false;
}
}

public static void Patch(AssemblyDefinition assembly)
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ Here is a list of LCVR versions and which version(s) of Lethal Company it suppor

| LCVR | Lethal Company | [Configuration version](Docs/Configuration/README.md) |
|-------------------|-------------------|-------------------------------------------------------|
| v1.4.6 *(LATEST)* | V74 | 1 |
| v1.5.0 *(LATEST)* | V81 | 1 |
| v1.4.7 | V73 | 1 |
| v1.4.6 | V73 | 1 |
| v1.4.5 | V73 | 1 |
| v1.4.4 | V73 | 1 |
| v1.4.3 | V73 | 1 |
Expand Down
14 changes: 11 additions & 3 deletions Source/Assets/AssetManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace LCVR.Assets;

public static class AssetManager
{
private static AssetBundle assetsBundle;
public static AssetBundle assetsBundle;
private static AssetBundle scenesBundle;

public static GameObject Interactable;
Expand All @@ -28,10 +28,11 @@ public static class AssetManager

public static Material SplashMaterial;
public static Material DefaultRayMat;
public static Material PosterizationShaderMat;

public static Shader TMPAlwaysOnTop;
public static Shader VignettePostProcess;

public static InputActionAsset VRActions;
public static InputActionAsset DefaultXRActions;
public static InputActionAsset NullActions;
Expand All @@ -45,6 +46,9 @@ public static class AssetManager
public static Sprite SprintImage;

public static AudioClip DoorLocked;
public static AudioClip LeverShove;

public static RuntimeAnimatorController IntroLeverAnimator;

internal static bool LoadAssets()
{
Expand Down Expand Up @@ -82,12 +86,13 @@ internal static bool LoadAssets()

TMPAlwaysOnTop = assetsBundle.LoadAsset<Shader>("TextMeshPro Always On Top");
VignettePostProcess = assetsBundle.LoadAsset<Shader>("Vignette");

RemappableControls =
assetsBundle.LoadAsset<GameObject>("Remappable Controls").GetComponent<RemappableControls>();

SplashMaterial = assetsBundle.LoadAsset<Material>("Splash");
DefaultRayMat = assetsBundle.LoadAsset<Material>("Default Ray");
PosterizationShaderMat = assetsBundle.LoadAsset<Material>("FullScreen_SpongePosterizeNew");

GithubImage = assetsBundle.LoadAsset<Sprite>("Github");
KofiImage = assetsBundle.LoadAsset<Sprite>("Ko-Fi");
Expand All @@ -96,6 +101,9 @@ internal static bool LoadAssets()
SprintImage = assetsBundle.LoadAsset<Sprite>("Aguy");

DoorLocked = assetsBundle.LoadAsset<AudioClip>("doorlocked");
LeverShove = assetsBundle.LoadAsset<AudioClip>("EndGameShoveLever");

IntroLeverAnimator = assetsBundle.LoadAsset<RuntimeAnimatorController>("HangarDoorLeverAlt");

if (RemappableControls == null || RemappableControls.controls == null)
{
Expand Down
Loading
Loading