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