#if UNITY_EDITOR using System; using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; // Fixed CLI helpers; no arbitrary method names or process-marker deletion. public static class KVGameplayLoopSetup { static string Root => Path.GetFullPath(Path.Combine(Application.dataPath,"../../CodexOutput/upserver/KVGameplayLoopTests")); static Type Find(string name) => AppDomain.CurrentDomain.GetAssemblies().Select(a=>a.GetType("game.PlayerDataStorage."+name)).FirstOrDefault(t=>t!=null) ?? throw new InvalidOperationException("Gameplay assembly not loaded."); static object ReadProperty(Type type,string name) => type.GetProperty(name,BindingFlags.Static|BindingFlags.NonPublic)?.GetValue(null); public static async System.Threading.Tasks.Task RunValidationAsync(string caseId) { if(caseId!="equipment-chain" && caseId!="fishing-consume" && caseId!="fishing-reward") throw new ArgumentException("Unknown fixed validation case."); if(EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling || EditorApplication.isUpdating) throw new InvalidOperationException("Idle editor required."); var method=Find("PlayerKVGameplayLoop").GetMethod(caseId=="equipment-chain"?"VerifyEquipmentForEditorAsync":caseId=="fishing-consume"?"VerifyFishingForEditorAsync":"VerifyFishingRewardForEditorAsync",BindingFlags.Static|BindingFlags.NonPublic); if(method==null) throw new InvalidOperationException("Fixed equipment validation entry unavailable."); return await (System.Threading.Tasks.Task)method.Invoke(null,null); } public static string State() { var type=Find("PlayerKVGameplayLoop"); var history=Path.Combine(Root,"run-control"); return JsonConvert.SerializeObject(new{project=Application.dataPath,playing=EditorApplication.isPlaying, enabled=ReadProperty(type,"Enabled"),ready=ReadProperty(type,"Ready"),error=ReadProperty(type,"Error"),run=ReadProperty(type,"RunPath"), permitExists=File.Exists(Path.Combine(Root,"permit.json")), processMarkers=Directory.Exists(history)?Directory.GetFiles(history,"*.claimed").Select(Path.GetFileName).OrderBy(x=>x).ToArray():Array.Empty()}); } public static string ValidatePermit(string path) { var type=Find("PlayerKVLoopPermit"); try { var permit=type.GetMethod("Read",BindingFlags.Static|BindingFlags.NonPublic).Invoke(null,new object[]{Path.GetFullPath(path)}); return JsonConvert.SerializeObject(permit); } catch(TargetInvocationException e) { throw e.InnerException ?? e; } } public static string InspectPFBackup(string path) { if(EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling || EditorApplication.isUpdating) throw new InvalidOperationException("Idle editor required."); var method=Find("PlayerKVGameplayLoop").GetMethod("InspectPFBackupForEditor",BindingFlags.Static|BindingFlags.NonPublic); try { return (string)method.Invoke(null,new object[]{path}); } catch(TargetInvocationException e) { throw e.InnerException ?? e; } } public static string PreparePermit(string account,string backupPath,string[] allowedKeys) => PreparePermitCore(account,backupPath,allowedKeys,false); public static string PrepareFishingPermit(string account,string backupPath,string[] allowedKeys) => PreparePermitCore(account,backupPath,allowedKeys,true); public static string PrepareFishingRewardPermit(string account,string backupPath,string[] allowedKeys) => PreparePermitCore(account,backupPath,allowedKeys,true,true); static string PreparePermitCore(string account,string backupPath,string[] allowedKeys,bool fishing,bool reward=false) { if(EditorApplication.isPlayingOrWillChangePlaymode || EditorApplication.isCompiling) throw new InvalidOperationException("Stop play before preparing a new process run."); backupPath=Path.GetFullPath(backupPath); var bytes=File.ReadAllBytes(backupPath); string hash; using(var sha=SHA256.Create()) hash=BitConverter.ToString(sha.ComputeHash(bytes)).Replace("-",""); var candidate=Path.Combine(Root,"permit.candidate.json"); Directory.CreateDirectory(Root); var json=JObject.FromObject(new{Version=reward?3:fishing?2:1,Enabled=true,PlayerId=account, Api="http://s101.local:18080/",BackupPath=backupPath,BackupSha256=hash,AllowedKeys=allowedKeys}); if(fishing) json["Operation"]=reward?"fishing-reward-once":"fishing-consume-once"; File.WriteAllText(candidate,json.ToString(Formatting.Indented)); var validated=ValidatePermit(candidate); // Empty/mismatched backups cannot enable a run. File.WriteAllText(Path.Combine(Root,"permit.json"),validated); return State(); } } #endif