using System; using System.Reflection; using HarmonyLib; using UnityEngine; namespace AnantaSpoonProbe; // Hotkeys (main thread): // F8 = SAFE probe: SpoonTaskMgr.Instance != null? path constants, workAction count for 60004938. // Ctrl+F8 = LoadSingleTask(60004938) with empty md5. Empty = safe-ignore per R211b. public class SpoonProbeBehaviour : MonoBehaviour { public SpoonProbeBehaviour(IntPtr ptr) : base(ptr) { } public SpoonProbeBehaviour() { } private void Update() { try { if (Input.GetKeyDown(KeyCode.F8)) { if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) TryLoadSingleTask(); else SafeProbe(); } } catch (Exception ex) { SpoonProbePlugin.PLog($"hotkey error: {ex}"); } } private static void SafeProbe() { try { var mgrType = AccessTools.TypeByName("L50.Spoon.SpoonRunTime.SpoonTaskMgr"); if (mgrType is null) { SpoonProbePlugin.PLog("SAFE: SpoonTaskMgr type NOT FOUND"); return; } var instProp = AccessTools.Property(mgrType, "Instance"); var inst = instProp?.GetValue(null); SpoonProbePlugin.PLog($"SAFE: SpoonTaskMgr.Instance {(inst is null ? "NULL" : "OK")}"); foreach (var n in new[] { "TASK_SPOON_FILE_PATH", "TASK_RAID_FILE_PATH", "EVENT_FILE_PATH", "TASK_SPOON_FILE_NAME", "TASK_RAID_FILE_NAME", "EVENT_FILE_NAME" }) { try { var f = AccessTools.Field(mgrType, n); var v = f?.GetValue(null); SpoonProbePlugin.PLog($"SAFE: const {n} = {SpoonProbePlugin.Summarize(v)} (field {(f is null ? "MISSING" : f.FieldType.Name)})"); } catch (Exception ex) { SpoonProbePlugin.PLog($"SAFE: const {n} read FAIL: {ex.Message}"); } } // v2: enumerate ALL static string fields (interop may rename/hide privates). try { int n = 0; foreach (var f in mgrType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static)) { if (f.FieldType != typeof(string)) continue; if (n >= 20) break; n++; object? v = null; try { v = f.GetValue(null); } catch (Exception ex) { v = $""; } SpoonProbePlugin.PLog($"SAFE: staticstr {f.Name} = {SpoonProbePlugin.Summarize(v)}"); } if (n == 0) SpoonProbePlugin.PLog("SAFE: no static string fields visible via reflection"); } catch (Exception ex) { SpoonProbePlugin.PLog($"SAFE: staticstr enum FAIL: {ex.Message}"); } // Read-only: workAction count for the Prologue start task. try { var m = AccessTools.Method(mgrType, "GetTaskWorkActionByTask"); if (m is not null && inst is not null) { var r = m.Invoke(inst, new object[] { 60004938u }); SpoonProbePlugin.PLog($"SAFE: GetTaskWorkActionByTask(60004938) -> {SpoonProbePlugin.Summarize(r)}"); } else SpoonProbePlugin.PLog("SAFE: GetTaskWorkActionByTask method/instance missing"); } catch (Exception ex) { SpoonProbePlugin.PLog($"SAFE: workAction read FAIL: {ex.Message}"); } SpoonProbePlugin.PLog("SAFE probe done. (Ctrl+F8 = LoadSingleTask attempt)"); } catch (Exception ex) { SpoonProbePlugin.PLog($"SAFE probe FAIL: {ex}"); } } private static void TryLoadSingleTask() { try { const uint taskId = 60004938; var mgrType = AccessTools.TypeByName("L50.Spoon.SpoonRunTime.SpoonTaskMgr"); var spoonType = AccessTools.TypeByName("UX.Game.TaskSpoonViewInfo"); var stateType = AccessTools.TypeByName("UX.Game.TaskState"); if (mgrType is null || spoonType is null) { SpoonProbePlugin.PLog($"LOAD: type missing mgr={mgrType is not null} spoon={spoonType is not null}"); return; } var inst = AccessTools.Property(mgrType, "Instance")?.GetValue(null); if (inst is null) { SpoonProbePlugin.PLog("LOAD: Instance NULL"); return; } var spoon = Activator.CreateInstance(spoonType); Set(spoon!, "SpoonMd5", ""); // MUST stay empty (fake md5 hard-crashes, R211b) Set(spoon!, "SpRaidId", 23301180u); Set(spoon!, "StartTaskId", taskId); Set(spoon!, "EndTaskId", 60004955u); Set(spoon!, "Alias", ""); Set(spoon!, "EventId", 1441u); Set(spoon!, "EventStartTaskId", taskId); SpoonProbePlugin.PLog($"LOAD: calling LoadSingleTask task={taskId} {SpoonProbePlugin.Summarize(spoon)}"); object stateVal = 1; // TaskState.Accepted = 1 try { if (stateType is not null && stateType.IsEnum) stateVal = Enum.ToObject(stateType, 1); } catch { } // counterValues: match the declared parameter type (Il2Cpp List or managed List). object? counters = null; try { var loadMi = FindLoadSingleTask(mgrType); var ps = loadMi?.GetParameters(); if (ps is not null && ps.Length >= 5) { var listType = ps[2].ParameterType; counters = Activator.CreateInstance(listType); var add = listType.GetMethod("Add"); add?.Invoke(counters, new object[] { 0 }); } } catch (Exception ex) { SpoonProbePlugin.PLog($"LOAD: counter list build FAIL: {ex.Message}"); } var mi2 = FindLoadSingleTask(mgrType); if (mi2 is null) { SpoonProbePlugin.PLog("LOAD: LoadSingleTask method NOT FOUND"); return; } mi2.Invoke(inst, new object?[] { taskId, stateVal, counters, spoon, false }); SpoonProbePlugin.PLog("LOAD: LoadSingleTask invoked (watch ENTER/LEAVE lines + dumps/spoon/)"); } catch (Exception ex) { SpoonProbePlugin.PLog($"LOAD FAIL: {ex}"); } } private static MethodInfo? FindLoadSingleTask(Type mgrType) { foreach (var mi in mgrType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) if (mi.Name == "LoadSingleTask" && mi.GetParameters().Length == 5) return mi; return AccessTools.Method(mgrType, "LoadSingleTask"); } private static void Set(object o, string name, object? v) { var t = o.GetType(); var f = t.GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (f is not null) { if (v is uint u && f.FieldType.IsEnum) f.SetValue(o, Enum.ToObject(f.FieldType, u)); else f.SetValue(o, v); return; } var p = t.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); p?.SetValue(o, v); } }