mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
Add DevWatchButton for native watch console injection and command handling
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
// Native watch "Dev Console" injection — typed against the per-install Il2Cpp
|
||||
// interop assemblies (referenced in the csproj, dev-tools only, never shipped).
|
||||
// Pure reflection couldn't drive this build's watch UI (FindObjectOfType /
|
||||
// property-get / GetComponentsInChildren all failed under Il2CppInterop), so
|
||||
// we use real types here: clone an existing watch Button3D, relabel it, and
|
||||
// wire its OnClick to run console commands via DebugConsole.Execute (which
|
||||
// works headless even though the console UI isn't in the scene). It's the
|
||||
// game's own Button3D, so it's interactable with the VR laser AND the
|
||||
// Screen-Mode mouse. Gated behind Cfg.EnableDevWatchButton.
|
||||
using System;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppAGUI.StackedUI;
|
||||
using Il2CppRecRoom.Debugging;
|
||||
using Il2CppTMPro;
|
||||
using UnityEngine.Events;
|
||||
using UObject = UnityEngine.Object;
|
||||
|
||||
namespace DorkNet.ClientMod;
|
||||
|
||||
internal static class DevWatchButton
|
||||
{
|
||||
private static int _frame;
|
||||
private static bool _done;
|
||||
|
||||
// Polled from Mod.OnUpdate while Cfg.EnableDevWatchButton is set. Waits for
|
||||
// the watch's HomeScreenFlow to exist, then injects once.
|
||||
public static void Tick()
|
||||
{
|
||||
if (_done) return;
|
||||
_frame++;
|
||||
if (_frame < 120 || (_frame % 30) != 0) return; // settle, then ~2/s
|
||||
var home = UObject.FindObjectOfType<HomeScreenFlow>();
|
||||
if (home == null && _frame < 3600) return; // wait for the watch
|
||||
_done = true;
|
||||
if (home == null) { Mod.Log.Warning("[devbtn] HomeScreenFlow never appeared"); return; }
|
||||
try { Inject(home); }
|
||||
catch (Exception ex) { Mod.Log.Warning($"[devbtn] inject failed: {ex}"); }
|
||||
}
|
||||
|
||||
private static void Inject(HomeScreenFlow home)
|
||||
{
|
||||
var buttons = home.GetComponentsInChildren<Button3D>(true);
|
||||
if (buttons == null || buttons.Length == 0)
|
||||
{
|
||||
Mod.Log.Warning("[devbtn] no Button3D found under HomeScreenFlow");
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefer a source button that HAS a TMP label, so our clones can be
|
||||
// relabeled (HomeButton etc. are icon-only — no text child).
|
||||
Button3D? src = null;
|
||||
for (int i = 0; i < buttons.Length; i++)
|
||||
if (buttons[i].GetComponentInChildren<TextMeshProUGUI>(true) != null) { src = buttons[i]; break; }
|
||||
bool labeled = src != null;
|
||||
if (src == null) src = buttons[0]; // fall back: inject anyway, just no text
|
||||
Mod.Log.Msg($"[devbtn] {buttons.Length} watch buttons; clone source='{src.name}' labeled={labeled}");
|
||||
|
||||
int n = 0;
|
||||
foreach (var (label, cmd) in ParsePresets())
|
||||
{
|
||||
var clone = UObject.Instantiate(src, src.transform.parent);
|
||||
clone.name = $"DorkNetDev_{label}";
|
||||
clone.gameObject.SetActive(true);
|
||||
clone.transform.localScale = src.transform.localScale;
|
||||
|
||||
var tmp = clone.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
if (tmp != null) tmp.text = label;
|
||||
|
||||
var command = cmd; // capture per-button
|
||||
clone.OnClick.RemoveAllListeners();
|
||||
clone.OnClick.AddListener(DelegateSupport.ConvertDelegate<UnityAction>((Action)(() => Run(label, command))));
|
||||
_clones.Add(clone);
|
||||
n++;
|
||||
}
|
||||
Mod.Log.Msg($"[devbtn] injected {n} dev command button(s) into the watch bar — open the watch to use them");
|
||||
DumpCommands();
|
||||
}
|
||||
|
||||
// Log the real console command names from DebugConsoleCommandConfig.Metas
|
||||
// so we can set DevCommands correctly (the asset loads even though the
|
||||
// console component isn't in the scene).
|
||||
private static void DumpCommands()
|
||||
{
|
||||
try
|
||||
{
|
||||
var cfgs = UObject.FindObjectsOfTypeAll(Il2CppType.Of<DebugConsoleCommandConfig>());
|
||||
Mod.Log.Msg($"[devcmds] {cfgs.Length} DebugConsoleCommandConfig asset(s) loaded");
|
||||
for (int c = 0; c < cfgs.Length; c++)
|
||||
{
|
||||
var cfg = cfgs[c].TryCast<DebugConsoleCommandConfig>();
|
||||
var metas = cfg?.Metas;
|
||||
if (metas == null) continue;
|
||||
Mod.Log.Msg($"[devcmds] {metas.Count} command(s):");
|
||||
for (int i = 0; i < metas.Count; i++)
|
||||
{
|
||||
var m = metas[i];
|
||||
Mod.Log.Msg($" {m.MethodName} ({m.DeclaringType})");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex) { Mod.Log.Warning($"[devcmds] failed: {ex.Message}"); }
|
||||
}
|
||||
|
||||
private static void Run(string label, string command)
|
||||
{
|
||||
Mod.Log.Msg($"[devbtn] '{label}' → DebugConsole.Execute(\"{command}\")");
|
||||
try { DebugConsole.Execute(command); }
|
||||
catch (Exception ex) { Mod.Log.Warning($"[devbtn] Execute('{command}') failed: {ex.Message}"); }
|
||||
}
|
||||
|
||||
// Presets come from Cfg.DevCommands as "Label=command" strings.
|
||||
private static System.Collections.Generic.IEnumerable<(string label, string cmd)> ParsePresets()
|
||||
{
|
||||
foreach (var entry in Mod.Cfg.DevCommands)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entry)) continue;
|
||||
var eq = entry.IndexOf('=');
|
||||
if (eq > 0) yield return (entry.Substring(0, eq).Trim(), entry.Substring(eq + 1).Trim());
|
||||
else yield return (entry.Trim(), entry.Trim());
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly System.Collections.Generic.List<Button3D> _clones = new();
|
||||
}
|
||||
@@ -64,4 +64,29 @@
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
Dev-tools only: typed references to the per-install Il2Cpp interop
|
||||
assemblies MelonLoader generates (Assembly-CSharp / UnityEngine.* /
|
||||
TextMeshPro). Used by the native watch "Dev Console" injection
|
||||
(DevWatchButton.cs), which needs real types (Button3D.OnClick,
|
||||
Object.Instantiate, TextMeshProUGUI, UnityAction) that pure reflection
|
||||
couldn't drive in this build. These DLLs are game-derived and are NOT
|
||||
redistributed — they're only referenced at build time and resolved at
|
||||
runtime from the player's own install. Private=false so they're never
|
||||
copied into the mod output. Override the dir with -p:Il2CppAsmDir=… .
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<Il2CppAsmDir Condition="'$(Il2CppAsmDir)' == ''">C:\Users\Alexa\Downloads\DorkRoom-2020.12\MelonLoader\Il2CppAssemblies</Il2CppAsmDir>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Condition="Exists('$(Il2CppAsmDir)')">
|
||||
<Reference Include="Assembly-CSharp"><HintPath>$(Il2CppAsmDir)\Assembly-CSharp.dll</HintPath><Private>false</Private></Reference>
|
||||
<Reference Include="Il2Cppmscorlib"><HintPath>$(Il2CppAsmDir)\Il2Cppmscorlib.dll</HintPath><Private>false</Private></Reference>
|
||||
<Reference Include="UnityEngine.CoreModule"><HintPath>$(Il2CppAsmDir)\UnityEngine.CoreModule.dll</HintPath><Private>false</Private></Reference>
|
||||
<Reference Include="UnityEngine.UI"><HintPath>$(Il2CppAsmDir)\UnityEngine.UI.dll</HintPath><Private>false</Private></Reference>
|
||||
<Reference Include="UnityEngine.InputLegacyModule"><HintPath>$(Il2CppAsmDir)\UnityEngine.InputLegacyModule.dll</HintPath><Private>false</Private></Reference>
|
||||
<Reference Include="Unity.TextMeshPro"><HintPath>$(Il2CppAsmDir)\Unity.TextMeshPro.dll</HintPath><Private>false</Private></Reference>
|
||||
<Reference Include="Il2CppRecRoom.Promises.Runtime"><HintPath>$(Il2CppAsmDir)\Il2CppRecRoom.Promises.Runtime.dll</HintPath><Private>false</Private></Reference>
|
||||
<Reference Include="Il2CppRecRoom.Datastructures.Runtime"><HintPath>$(Il2CppAsmDir)\Il2CppRecRoom.Datastructures.Runtime.dll</HintPath><Private>false</Private></Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user