From 6810bcf02199beecc7d66146b7eeaf00e229a244 Mon Sep 17 00:00:00 2001
From: NotAKidoS <37721153+NotAKidoS@users.noreply.github.com>
Date: Sat, 17 Aug 2024 00:24:11 -0500
Subject: [PATCH] CVRLuaToolsExtension: initial testing
---
.../CVRLuaToolsExtension.csproj | 6 +
.../LuaToolsExtension/Base/Singleton.cs | 44 +++++
.../CVRBaseLuaBehaviourExtensions.cs | 47 ++++++
.../CVRLuaClientBehaviourExtensions.cs | 124 ++++++++++++++
.../LuaToolsExtension/LuaHotReloadManager.cs | 155 ++++++++++++++++++
.../NamedPipes/NamedPipeServer.cs | 105 ++++++++++++
.../LuaToolsExtension/ScriptInfo.cs | 12 ++
CVRLuaToolsExtension/Main.cs | 67 ++++++++
.../Properties/AssemblyInfo.cs | 32 ++++
CVRLuaToolsExtension/README.md | 14 ++
CVRLuaToolsExtension/format.json | 24 +++
NAK_CVR_Mods.sln | 18 ++
References.Items.props | 20 +++
13 files changed, 668 insertions(+)
create mode 100644 CVRLuaToolsExtension/CVRLuaToolsExtension.csproj
create mode 100644 CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs
create mode 100644 CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs
create mode 100644 CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs
create mode 100644 CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs
create mode 100644 CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs
create mode 100644 CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs
create mode 100644 CVRLuaToolsExtension/Main.cs
create mode 100644 CVRLuaToolsExtension/Properties/AssemblyInfo.cs
create mode 100644 CVRLuaToolsExtension/README.md
create mode 100644 CVRLuaToolsExtension/format.json
diff --git a/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj b/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj
new file mode 100644
index 0000000..bec5b03
--- /dev/null
+++ b/CVRLuaToolsExtension/CVRLuaToolsExtension.csproj
@@ -0,0 +1,6 @@
+
+
+
+ LoadedObjectHack
+
+
diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs b/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs
new file mode 100644
index 0000000..3038865
--- /dev/null
+++ b/CVRLuaToolsExtension/LuaToolsExtension/Base/Singleton.cs
@@ -0,0 +1,44 @@
+using UnityEngine;
+
+namespace NAK.CVRLuaToolsExtension;
+
+public abstract class Singleton : MonoBehaviour where T : MonoBehaviour
+{
+ private static T _instance;
+ private static readonly object _lock = new();
+ private static bool _isShuttingDown;
+
+ public static T Instance
+ {
+ get
+ {
+ if (_isShuttingDown)
+ {
+ Debug.LogWarning($"[Singleton] Instance of {typeof(T)} already destroyed. Returning null.");
+ return null;
+ }
+
+ lock (_lock)
+ {
+ if (_instance != null) return _instance;
+ _instance = (T)FindObjectOfType(typeof(T));
+ if (_instance != null) return _instance;
+ GameObject singletonObject = new($"{typeof(T).Name} (Singleton)");
+ _instance = singletonObject.AddComponent();
+ DontDestroyOnLoad(singletonObject);
+ return _instance;
+ }
+ }
+ }
+
+ private void OnApplicationQuit()
+ {
+ _isShuttingDown = true;
+ }
+
+ private void OnDestroy()
+ {
+ if (_instance == this)
+ _isShuttingDown = true;
+ }
+}
\ No newline at end of file
diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs b/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs
new file mode 100644
index 0000000..99af0cd
--- /dev/null
+++ b/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRBaseLuaBehaviourExtensions.cs
@@ -0,0 +1,47 @@
+using ABI_RC.Core.Player;
+using ABI_RC.Core.Savior;
+using ABI.CCK.Components;
+using ABI.Scripting.CVRSTL.Common;
+using UnityEngine;
+
+namespace NAK.CVRLuaToolsExtension;
+
+public static class CVRBaseLuaBehaviourExtensions
+{
+ // check if the script is eligible for hot reload
+ public static bool IsScriptEligibleForHotReload(this CVRBaseLuaBehaviour script)
+ {
+ return script.Context.IsWornByMe // avatar if worn
+ || script.Context.IsSpawnedByMe // prop if spawned
+ || script.Context.objContext == CVRLuaObjectContext.WORLD; // always world scripts
+ }
+
+ // gets the asset id from the script
+ public static string GetAssetIdFromScript(this CVRBaseLuaBehaviour script)
+ {
+ switch (script.Context.objContext)
+ {
+ case CVRLuaObjectContext.WORLD:
+ //return MetaPort.Instance.CurrentWorldId; // do not trust CVRAssetInfo, can be destroyed at runtime
+ return "SYSTEM"; // default for world scripts is SYSTEM, but TODO: use actual world id
+ case CVRLuaObjectContext.AVATAR:
+ Component rootComponent = script.Context.RootComponent;
+ return rootComponent switch
+ {
+ PlayerSetup => MetaPort.Instance.currentAvatarGuid, // local avatar
+ PuppetMaster puppetMaster => puppetMaster.CVRPlayerEntity.AvatarId, // remote avatar
+ _ => string.Empty // fuck
+ };
+ case CVRLuaObjectContext.PROP:
+ {
+ CVRSpawnable spawnable = (CVRSpawnable)script.Context.RootComponent;
+ if (!string.IsNullOrEmpty(spawnable.guid)) return spawnable.guid; // after filtering has occured
+ return spawnable.TryGetComponent(out CVRAssetInfo assetInfo)
+ ? assetInfo.objectId // before filtering has occured
+ : string.Empty; // well shit
+ }
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+}
\ No newline at end of file
diff --git a/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs b/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs
new file mode 100644
index 0000000..6f77a61
--- /dev/null
+++ b/CVRLuaToolsExtension/LuaToolsExtension/Extensions/CVRLuaClientBehaviourExtensions.cs
@@ -0,0 +1,124 @@
+using ABI.CCK.Components;
+using ABI.Scripting.CVRSTL.Client;
+using System.Diagnostics;
+using MTJobSystem;
+
+namespace NAK.CVRLuaToolsExtension;
+
+public static class CVRLuaClientBehaviourExtensions
+{
+ internal static readonly Dictionary _isRestarting = new();
+
+ #region Public Methods
+
+ public static void Restart(this CVRLuaClientBehaviour behaviour)
+ {
+ if (_isRestarting.TryGetValue(behaviour, out bool isRestarting) && isRestarting)
+ {
+ CVRLuaToolsExtensionMod.Logger.Warning($"Restart is already in progress for {behaviour.ScriptName}.");
+ return;
+ }
+
+ _isRestarting[behaviour] = true;
+
+ bool wasEnabled = behaviour.enabled;
+ if (behaviour.Crashed)
+ {
+ CVRLuaToolsExtensionMod.Logger.Warning($"Restarting a script ({behaviour.ScriptName}) in a crashed state. Unable to determine original enabled state, defaulting to true.");
+ wasEnabled = true;
+ }
+
+ behaviour.enabled = false;
+
+ Task.Run(() =>
+ {
+ try
+ {
+ behaviour.ResetScriptCompletely();
+ if (CVRLuaToolsExtensionMod.EntryAttemptInitOffMainThread.Value)
+ {
+ CVRLuaToolsExtensionMod.Logger.Warning("Attempting to initialize the lua script off main thread. This may cause crashes or corruption if you are accessing UnityEngine Objects outside of Unity's callbacks!");
+ behaviour.DoInitialLoadOfScript();
+ }
+ }
+ catch (Exception e)
+ {
+ CVRLuaToolsExtensionMod.Logger.Error(e); // don't wanna die in task
+ }
+
+ MTJobManager.RunOnMainThread("RestartScript", () =>
+ {
+ Stopwatch stopwatch = new();
+ stopwatch.Start();
+
+ try
+ {
+ if (!CVRLuaToolsExtensionMod.EntryAttemptInitOffMainThread.Value)
+ behaviour.DoInitialLoadOfScript();
+
+ if (behaviour.InitialCodeRun) behaviour.IsScriptInitialized = true; // allow callbacks to run again
+
+ // invoke custom event on reset
+ if (!behaviour.ShouldSkipEvent("CVRLuaTools_OnReset")) behaviour.ExecuteEvent(5000, "CVRLuaTools_OnReset");
+
+ // re-enable the script & invoke the lifecycle events
+ if (!behaviour.ShouldSkipEvent("Awake")) behaviour.ExecuteEvent(1000, "Awake");
+ behaviour.enabled = wasEnabled;
+ if (wasEnabled)
+ {
+ if (!behaviour.ShouldSkipEvent("OnEnable")) behaviour.ExecuteEvent(1000, "OnEnable");
+ if (!behaviour.ShouldSkipEvent("Start")) behaviour.ExecuteEvent(1000, "Start");
+ }
+ }
+ catch (Exception e)
+ {
+ CVRLuaToolsExtensionMod.Logger.Error(e); // don't wanna die prior to resetting restart flag
+ }
+
+ _isRestarting.Remove(behaviour);
+
+ stopwatch.Stop();
+ CVRLuaToolsExtensionMod.Logger.Msg($"Restarted {behaviour.ScriptName} in {stopwatch.ElapsedMilliseconds}ms.");
+ });
+ });
+ }
+
+ #endregion Public Methods
+
+ #region Private Methods
+
+ private static void ResetScriptCompletely(this CVRLuaClientBehaviour behaviour)
+ {
+ var boundObjectEntries = new List();
+ foreach (var kvp in behaviour.BoundObjects)
+ {
+ LuaScriptFactory.BoundObjectEntry entry = new(behaviour, behaviour.Context, kvp.Key, kvp.Value);
+ boundObjectEntries.Add(entry);
+ }
+
+ behaviour.IsScriptInitialized = false; // prevent callbacks from causing null refs while restarting
+ behaviour.InitialCodeRun = false; // needs to be set so LoadAndRunScriptIfNeeded will run the script again
+ behaviour.Crashed = false; // reset the crash flag
+
+ behaviour._scriptGlobalFunctions.Clear(); // will be repopulated
+ behaviour._startupMessageQueue.Clear(); // will be repopulated
+ behaviour.LogInfo("[CVRLuaToolsExtension] Resetting script...\n");
+
+ behaviour.script = null;
+ behaviour.script = LuaScriptFactory.ForLuaBehaviour(behaviour, boundObjectEntries, behaviour.gameObject, behaviour.transform);
+
+ behaviour.InitTimerIfNeeded(); // only null if crashed prior
+ behaviour.script.AttachDebugger(behaviour.timer); // reattach the debugger
+ }
+
+ private static void DoInitialLoadOfScript(this CVRLuaClientBehaviour behaviour)
+ {
+ behaviour.LogInfo("[CVRLuaToolsExtension] Interpreting " + behaviour.ScriptName + "...\n");
+ behaviour.LoadAndRunScript(behaviour.ScriptText); // fucking slow
+ behaviour.PopulateScriptGlobalFunctionsCache(behaviour.script.Globals); // tbh don't think we need to clear in first place
+ behaviour.HandleMessageQueueEntries(); // shouldn't be any
+ behaviour.InitialCodeRun = true;
+ }
+
+ #endregion Private Methods
+}
\ No newline at end of file
diff --git a/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs b/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs
new file mode 100644
index 0000000..f9b526b
--- /dev/null
+++ b/CVRLuaToolsExtension/LuaToolsExtension/LuaHotReloadManager.cs
@@ -0,0 +1,155 @@
+using System.Text;
+using ABI_RC.Core.InteractionSystem;
+using ABI_RC.Core.UI;
+using ABI.CCK.Components;
+using ABI.CCK.Components.ScriptableObjects;
+using UnityEngine;
+
+namespace NAK.CVRLuaToolsExtension;
+
+public static class LuaHotReloadManager
+{
+ private static readonly Dictionary> s_AssetIdToLuaClientBehaviourIds = new();
+ private static readonly Dictionary s_LuaComponentIdsToLuaClientBehaviour = new();
+
+ #region Game Events
+
+ public static void OnCVRLuaBaseBehaviourLoadAndRunScript(CVRLuaClientBehaviour clientBehaviour)
+ {
+ //CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Script awake: {clientBehaviour.name}");
+
+ if (!clientBehaviour.IsScriptEligibleForHotReload())
+ return;
+
+ var assetId = clientBehaviour.GetAssetIdFromScript();
+ if (!s_AssetIdToLuaClientBehaviourIds.ContainsKey(assetId))
+ s_AssetIdToLuaClientBehaviourIds[assetId] = new List();
+
+ var luaComponentId = GetGameObjectPathHashCode(clientBehaviour.transform);
+ if (s_AssetIdToLuaClientBehaviourIds[assetId].Contains(luaComponentId))
+ {
+ CVRLuaToolsExtensionMod.Logger.Warning(
+ $"[LuaHotReloadManager] Script already exists: {clientBehaviour.name}");
+ return;
+ }
+
+ s_AssetIdToLuaClientBehaviourIds[assetId].Add(luaComponentId);
+ s_LuaComponentIdsToLuaClientBehaviour[luaComponentId] = clientBehaviour;
+ CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Added script: {clientBehaviour.name}");
+ }
+
+ public static void OnCVRLuaBaseBehaviourDestroy(CVRLuaClientBehaviour clientBehaviour)
+ {
+ //CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Script destroy: {clientBehaviour.name}");
+
+ var assetId = clientBehaviour.GetAssetIdFromScript();
+ if (!s_AssetIdToLuaClientBehaviourIds.ContainsKey(assetId))
+ return;
+
+ var luaClientBehaviourIds = s_AssetIdToLuaClientBehaviourIds[assetId];
+ foreach (var luaComponentId in luaClientBehaviourIds)
+ {
+ if (!s_LuaComponentIdsToLuaClientBehaviour.TryGetValue(luaComponentId,
+ out CVRLuaClientBehaviour luaClientBehaviour))
+ continue;
+
+ if (luaClientBehaviour != clientBehaviour)
+ continue;
+
+ s_LuaComponentIdsToLuaClientBehaviour.Remove(luaComponentId);
+ luaClientBehaviourIds.Remove(luaComponentId);
+ if (luaClientBehaviourIds.Count == 0) s_AssetIdToLuaClientBehaviourIds.Remove(assetId);
+ CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Removed script: {clientBehaviour.name}");
+ break;
+ }
+ }
+
+ public static void OnReceiveUpdatedScript(ScriptInfo info)
+ {
+ if (!s_AssetIdToLuaClientBehaviourIds.TryGetValue(info.AssetId, out var luaComponentIds))
+ {
+ CVRLuaToolsExtensionMod.Logger.Warning(
+ $"[LuaHotReloadManager] No scripts found for asset id: {info.AssetId}");
+ return;
+ }
+
+ bool found = false;
+ foreach (var luaComponentId in luaComponentIds)
+ {
+ if (!s_LuaComponentIdsToLuaClientBehaviour.TryGetValue(luaComponentId,
+ out CVRLuaClientBehaviour clientBehaviour))
+ continue;
+
+ found = true;
+ //CVRLuaToolsExtensionMod.Logger.Msg($"[LuaHotReloadManager] Reloading script: {info.ScriptName} for {clientBehaviour.name}");
+
+ if (clientBehaviour.asset.m_ScriptPath == info.ScriptPath)
+ {
+ CVRLuaToolsExtensionMod.Logger.Msg("[LuaHotReloadManager] Script path match, updating script.");
+ clientBehaviour.asset.name = info.ScriptName;
+ clientBehaviour.asset.m_ScriptText = info.ScriptText;
+ clientBehaviour.Restart();
+ }
+ else
+ {
+ CVRLuaToolsExtensionMod.Logger.Msg("[LuaHotReloadManager] Script path mismatch, creating new script.");
+
+ clientBehaviour.asset = null;
+ clientBehaviour.asset = ScriptableObject.CreateInstance();
+
+ clientBehaviour.asset.name = info.ScriptName;
+ clientBehaviour.asset.m_ScriptPath = info.ScriptPath;
+ clientBehaviour.asset.m_ScriptText = info.ScriptText;
+
+ clientBehaviour.Restart();
+ }
+ }
+
+ if (found) CohtmlHud.Instance.ViewDropTextImmediate("(Local) CVRLuaTools", "Received script update", "Reloaded script: " + info.ScriptName);
+ }
+
+ #endregion Game Events
+
+ #region Private Methods
+
+ private static int GetGameObjectPathHashCode(Transform transform)
+ {
+ // Attempt to find the root component transform in one step
+
+ Transform rootComponentTransform = null;
+
+ // both CVRAvatar & CVRSpawnable *should* have an asset info component
+ CVRAssetInfo rootComponent = transform.GetComponentInParent(true);
+ if (rootComponent != null && rootComponent.type != CVRAssetInfo.AssetType.World)
+ rootComponentTransform = rootComponent.transform;
+
+ // easy case, no need to crawl up the hierarchy
+ if (rootComponentTransform == transform)
+ return 581452743; // hash code for "[Root]"
+
+ StringBuilder pathBuilder = new(transform.name);
+ Transform parentTransform = transform.parent;
+
+ while (parentTransform != null)
+ {
+ // reached root component
+ // due to object loader renaming root, we can't rely on transform name, so we use "[Root]" instead
+ if (parentTransform == rootComponentTransform)
+ {
+ pathBuilder.Insert(0, "[Root]/");
+ break;
+ }
+
+ pathBuilder.Insert(0, parentTransform.name + "/");
+ parentTransform = parentTransform.parent;
+ }
+
+ string path = pathBuilder.ToString();
+
+ //Debug.Log($"[LuaComponentManager] Path: {path}");
+
+ return path.GetHashCode();
+ }
+
+ #endregion Private Methods
+}
\ No newline at end of file
diff --git a/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs b/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs
new file mode 100644
index 0000000..407be8b
--- /dev/null
+++ b/CVRLuaToolsExtension/LuaToolsExtension/NamedPipes/NamedPipeServer.cs
@@ -0,0 +1,105 @@
+using System.Diagnostics;
+using System.IO.Pipes;
+using System.Text;
+using ABI_RC.Core;
+
+namespace NAK.CVRLuaToolsExtension.NamedPipes;
+
+public class NamedPipeServer : Singleton
+{
+ private const string PipeName = "UnityPipe";
+
+ public async void StartListening()
+ {
+ Stopwatch stopwatch = new();
+ while (!CommonTools.IsQuitting)
+ {
+ await using NamedPipeServerStream namedPipeServer = new(PipeName, PipeDirection.In);
+ try
+ {
+ CVRLuaToolsExtensionMod.Logger.Msg("Waiting for client...");
+ await namedPipeServer.WaitForConnectionAsync();
+ stopwatch.Restart();
+ if (!namedPipeServer.IsConnected)
+ continue;
+
+ //Debug.Log("Client connected.");
+
+ // receive and process the ScriptInfo
+ ScriptInfo receivedScript = await ReceiveScriptInfo(namedPipeServer);
+ ProcessScriptInfo(receivedScript);
+ }
+ catch (Exception e)
+ {
+ CVRLuaToolsExtensionMod.Logger.Error(e);
+ }
+
+ if (namedPipeServer.IsConnected)
+ {
+ namedPipeServer.Disconnect();
+ CVRLuaToolsExtensionMod.Logger.Msg("Client disconnected.");
+ }
+
+ stopwatch.Stop();
+ CVRLuaToolsExtensionMod.Logger.Msg($"Elapsed time: {stopwatch.ElapsedMilliseconds}ms");
+ }
+
+ CVRLuaToolsExtensionMod.Logger.Msg("Quitting...");
+ }
+
+ private async Task ReceiveScriptInfo(NamedPipeServerStream stream)
+ {
+ // Read LuaComponentId (4 bytes for an int)
+ byte[] intBuffer = new byte[4];
+ await stream.ReadAsync(intBuffer, 0, intBuffer.Length);
+ int luaComponentId = BitConverter.ToInt32(intBuffer, 0);
+
+ string assetId = await ReadStringAsync(stream);
+ //CVRLuaToolsExtensionMod.Logger.Msg($"Received assetId: {assetId}");
+
+ string scriptName = await ReadStringAsync(stream);
+ //CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptName: {scriptName}");
+
+ string scriptPath = await ReadStringAsync(stream);
+ //CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptPath: {scriptPath}");
+
+ string scriptText = await ReadStringAsync(stream);
+ //CVRLuaToolsExtensionMod.Logger.Msg($"Received scriptText: {scriptText}");
+
+ //Debug.Log("ScriptInfo received.");
+
+ return new ScriptInfo
+ {
+ LuaComponentId = luaComponentId,
+ AssetId = assetId,
+ ScriptName = scriptName,
+ ScriptPath = scriptPath,
+ ScriptText = scriptText
+ };
+ }
+
+ private static async Task ReadStringAsync(Stream stream)
+ {
+ // Define a buffer size and initialize a memory stream
+ byte[] buffer = new byte[1024];
+ using MemoryStream memoryStream = new();
+
+ int bytesRead;
+ while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
+ {
+ memoryStream.Write(buffer, 0, bytesRead);
+ //CVRLuaToolsExtensionMod.Logger.Msg($"Read {bytesRead} bytes.");
+ if (bytesRead < buffer.Length) break; // Assuming no partial writes
+ }
+
+ return Encoding.UTF8.GetString(memoryStream.ToArray());
+ }
+
+ private static void ProcessScriptInfo(ScriptInfo scriptInfo)
+ {
+ // Handle the received ScriptInfo data
+ //Debug.Log($"Received ScriptInfo: {scriptInfo.AssetId}, {scriptInfo.LuaComponentId}, {scriptInfo.ScriptName}");
+
+ LuaHotReloadManager.OnReceiveUpdatedScript(scriptInfo);
+ }
+}
diff --git a/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs b/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs
new file mode 100644
index 0000000..7d7149e
--- /dev/null
+++ b/CVRLuaToolsExtension/LuaToolsExtension/ScriptInfo.cs
@@ -0,0 +1,12 @@
+namespace NAK.CVRLuaToolsExtension;
+
+[Serializable]
+public struct ScriptInfo
+{
+ public string AssetId; // we will reload all scripts with this asset id
+ public int LuaComponentId; // the target lua component id
+
+ public string ScriptName; // the new script name
+ public string ScriptPath;
+ public string ScriptText; // the new script text
+}
\ No newline at end of file
diff --git a/CVRLuaToolsExtension/Main.cs b/CVRLuaToolsExtension/Main.cs
new file mode 100644
index 0000000..29157f2
--- /dev/null
+++ b/CVRLuaToolsExtension/Main.cs
@@ -0,0 +1,67 @@
+using System.Reflection;
+using ABI_RC.Systems.GameEventSystem;
+using ABI.CCK.Components;
+using HarmonyLib;
+using MelonLoader;
+using NAK.CVRLuaToolsExtension.NamedPipes;
+
+namespace NAK.CVRLuaToolsExtension;
+
+public class CVRLuaToolsExtensionMod : MelonMod
+{
+ internal static MelonLogger.Instance Logger;
+
+ #region Melon Preferences
+
+ private const string ModName = nameof(CVRLuaToolsExtension);
+
+ private static readonly MelonPreferences_Category Category =
+ MelonPreferences.CreateCategory(ModName);
+
+ internal static readonly MelonPreferences_Entry EntryAttemptInitOffMainThread =
+ Category.CreateEntry("attempt_init_off_main_thread", false,
+ "Attempt init off Main Thread", "Attempt to initialize the lua script off main thread.");
+
+ #endregion Melon Preferences
+
+ #region Melon Events
+
+ public override void OnInitializeMelon()
+ {
+ Logger = LoggerInstance;
+
+ HarmonyInstance.Patch(
+ typeof(CVRLuaClientBehaviour).GetMethod(nameof(CVRLuaClientBehaviour.InitializeLuaVm)), // protected
+ postfix: new HarmonyMethod(typeof(CVRLuaToolsExtensionMod).GetMethod(nameof(OnCVRBaseLuaBehaviourLoadAndRunScript),
+ BindingFlags.NonPublic | BindingFlags.Static))
+ );
+ HarmonyInstance.Patch(
+ typeof(CVRLuaClientBehaviour).GetMethod(nameof(CVRLuaClientBehaviour.OnDestroy)), // public for some reason
+ postfix: new HarmonyMethod(typeof(CVRLuaToolsExtensionMod).GetMethod(nameof(OnCVRBaseLuaBehaviourDestroy),
+ BindingFlags.NonPublic | BindingFlags.Static))
+ );
+
+ CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(
+ () => NamedPipeServer.Instance.StartListening());
+ }
+
+ #endregion Melon Events
+
+ #region Harmony Patches
+
+ // theres no good place to patch where GetOwnerId & GetObjectId would be valid to call...
+ // pls move InitializeMain after Context is created... pls
+ private static void OnCVRBaseLuaBehaviourLoadAndRunScript(CVRLuaClientBehaviour __instance)
+ {
+ LuaHotReloadManager.OnCVRLuaBaseBehaviourLoadAndRunScript(__instance);
+ }
+
+ private static void OnCVRBaseLuaBehaviourDestroy(CVRLuaClientBehaviour __instance)
+ {
+ LuaHotReloadManager.OnCVRLuaBaseBehaviourDestroy(__instance);
+ if (CVRLuaClientBehaviourExtensions._isRestarting.ContainsKey(__instance))
+ CVRLuaClientBehaviourExtensions._isRestarting.Remove(__instance);
+ }
+
+ #endregion Harmony Patches
+}
\ No newline at end of file
diff --git a/CVRLuaToolsExtension/Properties/AssemblyInfo.cs b/CVRLuaToolsExtension/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a1c41b2
--- /dev/null
+++ b/CVRLuaToolsExtension/Properties/AssemblyInfo.cs
@@ -0,0 +1,32 @@
+using MelonLoader;
+using NAK.CVRLuaToolsExtension.Properties;
+using System.Reflection;
+
+[assembly: AssemblyVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyFileVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)]
+[assembly: AssemblyTitle(nameof(NAK.CVRLuaToolsExtension))]
+[assembly: AssemblyCompany(AssemblyInfoParams.Author)]
+[assembly: AssemblyProduct(nameof(NAK.CVRLuaToolsExtension))]
+
+[assembly: MelonInfo(
+ typeof(NAK.CVRLuaToolsExtension.CVRLuaToolsExtensionMod),
+ nameof(NAK.CVRLuaToolsExtension),
+ AssemblyInfoParams.Version,
+ AssemblyInfoParams.Author,
+ downloadLink: "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/CVRLuaToolsExtension"
+)]
+
+[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
+[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
+[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
+[assembly: MelonColor(255, 246, 25, 99)] // red-pink
+[assembly: MelonAuthorColor(255, 158, 21, 32)] // red
+[assembly: HarmonyDontPatchAll]
+
+namespace NAK.CVRLuaToolsExtension.Properties;
+internal static class AssemblyInfoParams
+{
+ public const string Version = "1.0.0";
+ public const string Author = "NotAKidoS";
+}
\ No newline at end of file
diff --git a/CVRLuaToolsExtension/README.md b/CVRLuaToolsExtension/README.md
new file mode 100644
index 0000000..8a46af2
--- /dev/null
+++ b/CVRLuaToolsExtension/README.md
@@ -0,0 +1,14 @@
+# IKSimulatedRootAngleFix
+
+Fixes a small issue with Desktop & HalfBody root angle being incorrectly calculated while on rotating Movement Parents. If you've ever noticed your body/feet insisting on facing opposite of the direction you are rotating, this fixes that.
+
+---
+
+Here is the block of text where I tell you this mod is not affiliated with or endorsed by ABI.
+https://documentation.abinteractive.net/official/legal/tos/#7-modding-our-games
+
+> This mod is an independent creation not affiliated with, supported by, or approved by Alpha Blend Interactive.
+
+> Use of this mod is done so at the user's own risk and the creator cannot be held responsible for any issues arising from its use.
+
+> To the best of my knowledge, I have adhered to the Modding Guidelines established by Alpha Blend Interactive.
diff --git a/CVRLuaToolsExtension/format.json b/CVRLuaToolsExtension/format.json
new file mode 100644
index 0000000..ddf506b
--- /dev/null
+++ b/CVRLuaToolsExtension/format.json
@@ -0,0 +1,24 @@
+{
+ "_id": -1,
+ "name": "AASDefaultProfileFix",
+ "modversion": "1.0.0",
+ "gameversion": "2024r175",
+ "loaderversion": "0.6.1",
+ "modtype": "Mod",
+ "author": "NotAKidoS",
+ "description": "Fixes the Default AAS profile not being applied when loading into an avatar without a profile selected.\n\nBy default, the game will not apply anything and the avatar will default to the state found within the Controller parameters.",
+ "searchtags": [
+ "aas",
+ "profile",
+ "default",
+ "fix",
+ "meow"
+ ],
+ "requirements": [
+ "None"
+ ],
+ "downloadlink": "https://github.com/NotAKidoS/NAK_CVR_Mods/releases/download/r33/AASDefaultProfileFix.dll",
+ "sourcelink": "https://github.com/NotAKidoS/NAK_CVR_Mods/tree/main/AASDefaultProfileFix/",
+ "changelog": "- Initial release",
+ "embedcolor": "#f61963"
+}
\ No newline at end of file
diff --git a/NAK_CVR_Mods.sln b/NAK_CVR_Mods.sln
index f4d81c0..ccdda68 100644
--- a/NAK_CVR_Mods.sln
+++ b/NAK_CVR_Mods.sln
@@ -85,6 +85,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarQueueSystemTweaks", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomSpawnPoint", "CustomSpawnPoint\CustomSpawnPoint.csproj", "{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NukePostPresentHandoff", "NukePostPresentHandoff\NukePostPresentHandoff.csproj", "{77F332CD-019A-472F-9269-CFDEB087B3F9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CVRLuaToolsExtension", "CVRLuaToolsExtension\CVRLuaToolsExtension.csproj", "{FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickers", "Stickers\Stickers.csproj", "{E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -255,6 +261,18 @@ Global
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51CA34CA-7684-4819-AC9E-89DFAD63E9AB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {77F332CD-019A-472F-9269-CFDEB087B3F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {77F332CD-019A-472F-9269-CFDEB087B3F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {77F332CD-019A-472F-9269-CFDEB087B3F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {77F332CD-019A-472F-9269-CFDEB087B3F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FE6BA6EC-2C11-49E1-A2FB-13F2A1C9E7F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E5F54B3E-A676-4DD5-A6DB-73AFA54BEC5E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/References.Items.props b/References.Items.props
index bb32776..b618151 100644
--- a/References.Items.props
+++ b/References.Items.props
@@ -76,6 +76,14 @@
$(MsBuildThisFileDirectory)\.ManagedLibs\Cohtml.Runtime.dll
False
+
+ $(MsBuildThisFileDirectory)\.ManagedLibs\com.dogu.gamium.engine.unity.runtime.dll
+ False
+
+
+ $(MsBuildThisFileDirectory)\.ManagedLibs\com.dogu.gamium.engine.unity.runtime.protocol.dll
+ False
+
$(MsBuildThisFileDirectory)\.ManagedLibs\Crc32.NET.dll
False
@@ -136,6 +144,10 @@
$(MsBuildThisFileDirectory)\.ManagedLibs\MeshBakerCore.dll
False
+
+ $(MsBuildThisFileDirectory)\.ManagedLibs\Meta.XR.BuildingBlocks.dll
+ False
+
$(MsBuildThisFileDirectory)\.ManagedLibs\Mono.Security.dll
False
@@ -172,6 +184,14 @@
$(MsBuildThisFileDirectory)\.ManagedLibs\Oculus.LipSync.dll
False
+
+ $(MsBuildThisFileDirectory)\.ManagedLibs\Oculus.Platform.dll
+ False
+
+
+ $(MsBuildThisFileDirectory)\.ManagedLibs\Oculus.VR.dll
+ False
+
$(MsBuildThisFileDirectory)\.ManagedLibs\PICO.Platform.dll
False