using System.Reflection; using System.Security.Cryptography; using Newtonsoft.Json; var root=Path.GetFullPath(Path.Combine("CodexOutput/upserver/KVGameplayLoopTests/runs",Guid.NewGuid().ToString("N"))); Directory.CreateDirectory(root); int assertions=0; void Check(bool yes,string why){assertions++;if(!yes)throw new Exception(why);} try { var type=Assembly.GetExecutingAssembly().GetType("game.PlayerDataStorage.PlayerKVLoopPermit"); Check(type!=null,"Missing approved loop permit validation"); object Load(string file)=>type.GetMethod("Read",BindingFlags.Static|BindingFlags.NonPublic).Invoke(null,new object[]{file}); void Reject(string file,string why){bool bad=false;try{Load(file);}catch(TargetInvocationException e){bad=e.InnerException is InvalidOperationException;}Check(bad,why);} var backup=Path.Combine(root,"backup.json");File.WriteAllText(backup,JsonConvert.SerializeObject(new{playerId="101",api="http://s101.local:18080/",data=new Dictionary{{"NewPlayerDataDic","{}"},{"k","v"}}})); var digest=Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(backup))); var permit=Path.Combine(root,"permit.json"); void Put(string id="101",string api="http://s101.local:18080/",string hash=null,string[] keys=null)=>File.WriteAllText(permit,JsonConvert.SerializeObject(new{Version=1,Enabled=true,PlayerId=id,Api=api,BackupPath=backup,BackupSha256=hash??digest,AllowedKeys=keys??new[]{"NewPlayerDataDic","k"}})); Put();var value=Load(permit);Check(value!=null,"valid explicit permit"); Put(id:"102");Reject(permit,"backup account must match"); Put(id:"103");Reject(permit,"outside user-selected accounts"); Put(api:"http://elsewhere.invalid/");Reject(permit,"fixed formal API"); Put(hash:new string('0',64));Reject(permit,"backup integrity"); Put(keys:Array.Empty());Reject(permit,"no empty permission"); Put(keys:new[]{"k"});Reject(permit,"core whole-save permission required"); Put(keys:new[]{"NewPlayerDataDic","new-unbacked-key"});Reject(permit,"no keys absent from backup without new reviewed fixture"); Put(keys:new[]{"NewPlayerDataDic","NewPlayerDataDic"});Reject(permit,"duplicate key permission rejected"); var originalBackup=File.ReadAllBytes(backup); File.WriteAllText(backup,JsonConvert.SerializeObject(new{playerId="101",api="http://s101.local:18080/",data=new Dictionary()})); digest=Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(backup))); Put(keys:new[]{"NewPlayerDataDic"});Reject(permit,"actual empty existing save cannot enable loop"); File.WriteAllBytes(backup,originalBackup);digest=Convert.ToHexString(SHA256.HashData(originalBackup)); Put();value=Load(permit); var claim=type.GetMethod("Claim",BindingFlags.Static|BindingFlags.NonPublic); string Claim(string instance)=>(string)claim.Invoke(null,new object[]{root,instance}); var marker=Claim("process-1");Check(File.Exists(marker),"durable claim"); bool denied=false;try{Claim("process-1");}catch(TargetInvocationException e){denied=e.InnerException is IOException;}Check(denied,"same process and reload rejected"); Check(File.Exists(Claim("process-2")),"new process can claim"); Check(File.Exists(marker),"old process mark retained"); Console.WriteLine($"PASS permit-and-process-claim assertions={assertions} root={root}");return 0; }catch(Exception e){Console.WriteLine($"FAIL assertions={assertions} {e}");return 1;}