diff --git a/CameraPlus/CameraPlus.csproj b/CameraPlus/CameraPlus.csproj
new file mode 100644
index 0000000..37ee59d
--- /dev/null
+++ b/CameraPlus/CameraPlus.csproj
@@ -0,0 +1,83 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {EB29D3F0-3BC0-4459-9269-C9DA0F3FCD1A}
+ Library
+ Properties
+ CameraPlus
+ CameraPlus
+ v3.5
+ 512
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\Assembly-CSharp.dll
+
+
+ D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\IllusionPlugin.dll
+
+
+
+
+
+
+ D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll
+
+
+ D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.Networking.dll
+
+
+ D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.PhysicsModule.dll
+
+
+ D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.UnityWebRequestModule.dll
+
+
+ D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.UnityWebRequestWWWModule.dll
+
+
+ D:\SteamLibrary\steamapps\common\Beat Saber\Beat Saber_Data\Managed\UnityEngine.VRModule.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CameraPlus/CameraPlusBehaviour.cs b/CameraPlus/CameraPlusBehaviour.cs
new file mode 100644
index 0000000..615d0f3
--- /dev/null
+++ b/CameraPlus/CameraPlusBehaviour.cs
@@ -0,0 +1,77 @@
+using System;
+using UnityEngine;
+using UnityEngine.XR;
+
+namespace CameraPlus
+{
+ public class CameraPlusBehaviour : MonoBehaviour
+ {
+ public static Camera MainCamera;
+ private Camera _cam;
+ private float _fov;
+ private float _posSmooth;
+ private float _rotSmooth;
+
+ private void Awake()
+ {
+ var gameObj = Instantiate(MainCamera.gameObject);
+ gameObj.SetActive(false);
+ gameObj.name = "Camera Plus";
+ gameObj.tag = "Untagged";
+ while (gameObj.transform.childCount > 0)
+ {
+ DestroyImmediate(gameObj.transform.GetChild(0).gameObject);
+ }
+ DestroyImmediate(gameObj.GetComponent("CameraRenderCallbacksManager"));
+ DestroyImmediate(gameObj.GetComponent("AudioListener"));
+ DestroyImmediate(gameObj.GetComponent("MeshCollider"));
+ if (SteamVRCompatibility.IsAvailable)
+ {
+ DestroyImmediate(gameObj.GetComponent(SteamVRCompatibility.SteamVRCamera));
+ DestroyImmediate(gameObj.GetComponent(SteamVRCompatibility.SteamVRFade));
+ }
+
+ _cam = gameObj.GetComponent();
+ _cam.stereoTargetEye = StereoTargetEyeMask.None;
+ _cam.targetTexture = null;
+ _cam.depth += 100;
+
+ gameObj.SetActive(true);
+
+ var camera = MainCamera.transform;
+ transform.position = camera.position;
+ transform.rotation = camera.rotation;
+
+ gameObj.transform.parent = transform;
+ gameObj.transform.localPosition = Vector3.zero;
+ gameObj.transform.localRotation = Quaternion.identity;
+ gameObj.transform.localScale = Vector3.one;
+
+ ReadIni();
+ }
+
+ public void ReadIni()
+ {
+ _fov = Convert.ToSingle(Plugin.Ini.GetValue("fov", "", "90"));
+ _posSmooth = Convert.ToSingle(Plugin.Ini.GetValue("positionSmooth", "", "10"));
+ _rotSmooth = Convert.ToSingle(Plugin.Ini.GetValue("rotationSmooth", "", "10"));
+ SetFOV();
+ }
+
+ private void LateUpdate()
+ {
+ var camera = MainCamera.transform;
+
+ transform.position = Vector3.Lerp(transform.position, camera.position, _posSmooth * Time.deltaTime);
+ transform.rotation = Quaternion.Slerp(transform.rotation, camera.rotation, _rotSmooth * Time.deltaTime);
+ }
+
+ private void SetFOV()
+ {
+ if (_cam == null) return;
+ _cam.fieldOfView = (float) (57.2957801818848 *
+ (2.0 * Mathf.Atan(Mathf.Tan((float) (_fov * (Math.PI / 180.0) * 0.5)) /
+ MainCamera.aspect)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/CameraPlus/Ini.cs b/CameraPlus/Ini.cs
new file mode 100644
index 0000000..36dd5d1
--- /dev/null
+++ b/CameraPlus/Ini.cs
@@ -0,0 +1,205 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+public class Ini
+{
+ Dictionary> ini = new Dictionary>(StringComparer.InvariantCultureIgnoreCase);
+ string file;
+
+ ///
+ /// Initialize an INI file
+ /// Load it if it exists
+ ///
+ /// Full path where the INI file has to be read from or written to
+ public Ini(string file)
+ {
+ this.file = file;
+
+ if (!File.Exists(file))
+ return;
+
+ Load();
+ }
+
+ ///
+ /// Load the INI file content
+ ///
+ public void Load()
+ {
+ var txt = File.ReadAllText(file);
+
+ Dictionary currentSection = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+
+ ini[""] = currentSection;
+
+ foreach (var l in txt.Split(new[] { "\n" }, StringSplitOptions.RemoveEmptyEntries)
+ .Select((t, i) => new
+ {
+ idx = i,
+ text = t.Trim()
+ }))
+ {
+ var line = l.text;
+
+ if (line.StartsWith(";") || string.IsNullOrEmpty(line))
+ {
+ currentSection.Add(";" + l.idx.ToString(), line);
+ continue;
+ }
+
+ if (line.StartsWith("[") && line.EndsWith("]"))
+ {
+ currentSection = new Dictionary(StringComparer.InvariantCultureIgnoreCase);
+ ini[line.Substring(1, line.Length - 2)] = currentSection;
+ continue;
+ }
+
+ var idx = line.IndexOf("=");
+ if (idx == -1)
+ currentSection[line] = "";
+ else
+ currentSection[line.Substring(0, idx)] = line.Substring(idx + 1);
+ }
+ }
+
+ ///
+ /// Get a parameter value at the root level
+ ///
+ /// parameter key
+ ///
+ public string GetValue(string key)
+ {
+ return GetValue(key, "", "");
+ }
+
+ ///
+ /// Get a parameter value in the section
+ ///
+ /// parameter key
+ /// section
+ ///
+ public string GetValue(string key, string section)
+ {
+ return GetValue(key, section, "");
+ }
+
+ ///
+ /// Returns a parameter value in the section, with a default value if not found
+ ///
+ /// parameter key
+ /// section
+ /// default value
+ ///
+ public string GetValue(string key, string section, string @default)
+ {
+ if (!ini.ContainsKey(section))
+ return @default;
+
+ if (!ini[section].ContainsKey(key))
+ return @default;
+
+ return ini[section][key];
+ }
+
+ ///
+ /// Save the INI file
+ ///
+ public void Save()
+ {
+ var sb = new StringBuilder();
+ foreach (var section in ini)
+ {
+ if (section.Key != "")
+ {
+ sb.AppendFormat("[{0}]", section.Key);
+ sb.AppendLine();
+ }
+
+ foreach (var keyValue in section.Value)
+ {
+ if (keyValue.Key.StartsWith(";"))
+ {
+ sb.Append(keyValue.Value);
+ sb.AppendLine();
+ }
+ else
+ {
+ sb.AppendFormat("{0}={1}", keyValue.Key, keyValue.Value);
+ sb.AppendLine();
+ }
+ }
+
+ if (!endWithCRLF(sb))
+ sb.AppendLine();
+ }
+
+ File.WriteAllText(file, sb.ToString());
+ }
+
+ bool endWithCRLF(StringBuilder sb)
+ {
+ if (sb.Length < 4)
+ return sb[sb.Length - 2] == '\r' &&
+ sb[sb.Length - 1] == '\n';
+ else
+ return sb[sb.Length - 4] == '\r' &&
+ sb[sb.Length - 3] == '\n' &&
+ sb[sb.Length - 2] == '\r' &&
+ sb[sb.Length - 1] == '\n';
+ }
+
+ ///
+ /// Write a parameter value at the root level
+ ///
+ /// parameter key
+ /// parameter value
+ public void WriteValue(string key, string value)
+ {
+ WriteValue(key, "", value);
+ }
+
+ ///
+ /// Write a parameter value in a section
+ ///
+ /// parameter key
+ /// section
+ /// parameter value
+ public void WriteValue(string key, string section, string value)
+ {
+ Dictionary currentSection;
+ if (!ini.ContainsKey(section))
+ {
+ currentSection = new Dictionary();
+ ini.Add(section, currentSection);
+ }
+ else
+ currentSection = ini[section];
+
+ currentSection[key] = value;
+ }
+
+ ///
+ /// Get all the keys names in a section
+ ///
+ /// section
+ ///
+ public string[] GetKeys(string section)
+ {
+ if (!ini.ContainsKey(section))
+ return new string[0];
+
+ return ini[section].Keys.ToArray();
+ }
+
+ ///
+ /// Get all the section names of the INI file
+ ///
+ ///
+ public string[] GetSections()
+ {
+ return ini.Keys.Where(t => t != "").ToArray();
+ }
+}
\ No newline at end of file
diff --git a/CameraPlus/Plugin.cs b/CameraPlus/Plugin.cs
new file mode 100644
index 0000000..03ce8cd
--- /dev/null
+++ b/CameraPlus/Plugin.cs
@@ -0,0 +1,100 @@
+using System;
+using System.IO;
+using System.Linq;
+using IllusionPlugin;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using Object = UnityEngine.Object;
+
+namespace CameraPlus
+{
+ public class Plugin : IPlugin
+ {
+ private CameraPlusBehaviour _cameraPlus;
+ public static readonly Ini Ini = new Ini(Path.Combine(Environment.CurrentDirectory, "cameraplus.cfg"));
+ private bool _init;
+ private FileSystemWatcher _iniWatcher;
+
+ public string Name
+ {
+ get { return "Camera+"; }
+ }
+
+ public string Version
+ {
+ get { return "v1.0"; }
+ }
+
+ public void OnApplicationStart()
+ {
+ if (_init) return;
+ _init = true;
+ SceneManager.activeSceneChanged += SceneManagerOnActiveSceneChanged;
+ if (!File.Exists(Path.Combine(Environment.CurrentDirectory, "cameraplus.cfg")))
+ {
+ Ini.WriteValue("fov", "90.0");
+ Ini.WriteValue("positionSmooth", "10.0");
+ Ini.WriteValue("rotationSmooth", "10.0");
+ Ini.Save();
+ }
+
+ _iniWatcher = new FileSystemWatcher(Environment.CurrentDirectory)
+ {
+ NotifyFilter = NotifyFilters.LastWrite,
+ Filter = "cameraplus.cfg",
+ EnableRaisingEvents = true
+ };
+ _iniWatcher.Changed += IniWatcherOnChanged;
+ }
+
+ public void OnApplicationQuit()
+ {
+ SceneManager.activeSceneChanged -= SceneManagerOnActiveSceneChanged;
+ _iniWatcher.Changed -= IniWatcherOnChanged;
+ }
+
+ private void SceneManagerOnActiveSceneChanged(Scene arg0, Scene scene)
+ {
+ if (scene.buildIndex < 1) return;
+ if (_cameraPlus != null)
+ {
+ Object.Destroy(_cameraPlus.gameObject);
+ }
+ var mainCamera = Object.FindObjectsOfType().FirstOrDefault(x => x.CompareTag("MainCamera"));
+ if (mainCamera == null)
+ {
+ return;
+ }
+ var gameObj = new GameObject("CameraPlus");
+ CameraPlusBehaviour.MainCamera = mainCamera;
+ _cameraPlus = gameObj.AddComponent();
+ }
+
+ private void IniWatcherOnChanged(object sender, FileSystemEventArgs fileSystemEventArgs)
+ {
+ if (_cameraPlus == null) return;
+ Ini.Load();
+ _cameraPlus.ReadIni();
+ }
+
+ public void OnLevelWasLoaded(int level)
+ {
+
+ }
+
+ public void OnLevelWasInitialized(int level)
+ {
+
+ }
+
+ public void OnUpdate()
+ {
+
+ }
+
+ public void OnFixedUpdate()
+ {
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/CameraPlus/Properties/AssemblyInfo.cs b/CameraPlus/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..6ff5e5c
--- /dev/null
+++ b/CameraPlus/Properties/AssemblyInfo.cs
@@ -0,0 +1,35 @@
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("CameraPlus")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("CameraPlus")]
+[assembly: AssemblyCopyright("Copyright © 2018")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("EB29D3F0-3BC0-4459-9269-C9DA0F3FCD1A")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
\ No newline at end of file
diff --git a/CameraPlus/ReflectionUtil.cs b/CameraPlus/ReflectionUtil.cs
new file mode 100644
index 0000000..f27923e
--- /dev/null
+++ b/CameraPlus/ReflectionUtil.cs
@@ -0,0 +1,32 @@
+using System.Reflection;
+
+namespace CameraPlus
+{
+ public static class ReflectionUtil
+ {
+ public static void SetPrivateField(object obj, string fieldName, object value)
+ {
+ var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
+ prop.SetValue(obj, value);
+ }
+
+ public static T GetPrivateField(object obj, string fieldName)
+ {
+ var prop = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
+ var value = prop.GetValue(obj);
+ return (T) value;
+ }
+
+ public static void SetPrivateProperty(object obj, string propertyName, object value)
+ {
+ var prop = obj.GetType().GetProperty(propertyName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
+ prop.SetValue(obj, value, null);
+ }
+
+ public static void InvokePrivateMethod(object obj, string methodName, object[] methodParams)
+ {
+ MethodInfo dynMethod = obj.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
+ dynMethod.Invoke(obj, methodParams);
+ }
+ }
+}
diff --git a/CameraPlus/SteamVRCompatibility.cs b/CameraPlus/SteamVRCompatibility.cs
new file mode 100644
index 0000000..1a39515
--- /dev/null
+++ b/CameraPlus/SteamVRCompatibility.cs
@@ -0,0 +1,31 @@
+using System;
+
+namespace CameraPlus
+{
+ public class SteamVRCompatibility
+ {
+ // Token: 0x0600032D RID: 813 RVA: 0x00012124 File Offset: 0x00010324
+ private static bool FindSteamVRAsset()
+ {
+ SteamVRCamera = Type.GetType("SteamVR_Camera", false);
+ SteamVRExternalCamera = Type.GetType("SteamVR_ExternalCamera", false);
+ SteamVRFade = Type.GetType("SteamVR_Fade", false);
+ return SteamVRCamera != null && SteamVRExternalCamera != null && SteamVRFade != null;
+ }
+
+ // Token: 0x040002C1 RID: 705
+ public static bool IsAvailable
+ {
+ get { return FindSteamVRAsset(); }
+ }
+
+ // Token: 0x040002C2 RID: 706
+ public static Type SteamVRCamera;
+
+ // Token: 0x040002C3 RID: 707
+ public static Type SteamVRExternalCamera;
+
+ // Token: 0x040002C4 RID: 708
+ public static Type SteamVRFade;
+ }
+}
\ No newline at end of file
diff --git a/CameraPlus/packages.config b/CameraPlus/packages.config
new file mode 100644
index 0000000..e89e7a2
--- /dev/null
+++ b/CameraPlus/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file