using System;
using System.IO;
using System.Reflection;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Input;
using Newtonsoft.Json.Linq;
public static class AMSSeekGate {
 public static object Owner;
 public static bool Active;
 public static bool ShouldSkipQuestUpdate(object quest){return Active&&object.ReferenceEquals(Owner,quest);}
}
sealed class PreviewGame:Game {
 internal object Logic; internal int Frames; internal GameTime CurrentTime;internal Action<PreviewGame> AfterUpdate; Module m;
 internal PreviewGame(){m=Assembly.Load("Amorous.Game").ManifestModule;Logic=((ConstructorInfo)m.ResolveMethod(0x06000087)).Invoke(new object[]{this,false});IsFixedTimeStep=false;InactiveSleepTime=TimeSpan.Zero;}
 void Call(int t,params object[] args){m.ResolveMethod(t).Invoke(Logic,args);}
 protected override void Initialize(){Silence();Call(0x06000088);Silence();base.Initialize();}
 protected override void LoadContent(){Silence();Call(0x06000089);Silence();base.LoadContent();}
 internal static void Silence(){SoundEffect.MasterVolume=0f;MediaPlayer.Volume=0f;MediaPlayer.IsMuted=true;}
 internal void SaveAudioProof(string root){Silence();if(SoundEffect.MasterVolume!=0f||MediaPlayer.Volume!=0f||!MediaPlayer.IsMuted)throw new Exception("Preview audio mute verification failed.");File.WriteAllText(Path.Combine(root,"preview-audio.json"),"{\"status\":\"PASS\",\"soundEffectMasterVolume\":0,\"mediaPlayerVolume\":0,\"mediaPlayerIsMuted\":true,\"openAlDriver\":\""+Environment.GetEnvironmentVariable("ALSOFT_DRIVERS")+"\",\"checkedAfterFrames\":"+Frames+"}");}
 protected override void Update(GameTime t){CurrentTime=t;Silence();Call(0x0600008c,t);Silence();if(AfterUpdate!=null)AfterUpdate(this);base.Update(t);}
 protected override void Draw(GameTime t){Call(0x0600008d,t);base.Draw(t);Frames++;}
 internal void Save(string path){var p=GraphicsDevice.PresentationParameters;var colors=new Color[p.BackBufferWidth*p.BackBufferHeight];GraphicsDevice.GetBackBufferData<Color>(colors);using(var texture=new Texture2D(GraphicsDevice,p.BackBufferWidth,p.BackBufferHeight)){texture.SetData<Color>(colors);using(var f=File.Create(path))texture.SaveAsPng(f,texture.Width,texture.Height);}}
}
static class ScenePreviewRunner {
 // The hidden preview window must not feed the real desktop cursor into
 // mouse-driven cameras. Replace only this child process' FNA input delegate;
 // never warp the user's pointer or alter system input.
 static int injectedX=-1,injectedY=-1;static bool injectedDown;static object originalMouse;static FieldInfo mouseInput;
 static void NeutralMouse(IntPtr window,out int x,out int y,out ButtonState left,out ButtonState middle,out ButtonState right,out ButtonState x1,out ButtonState x2){
  var flags=BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic;
  x=(int)typeof(Mouse).GetField("INTERNAL_WindowWidth",flags).GetValue(null)/2;
  y=(int)typeof(Mouse).GetField("INTERNAL_WindowHeight",flags).GetValue(null)/2;
  left=middle=right=x1=x2=ButtonState.Released;if(injectedX>=0){x=injectedX*(int)typeof(Mouse).GetField("INTERNAL_WindowWidth",flags).GetValue(null)/1920;y=injectedY*(int)typeof(Mouse).GetField("INTERNAL_WindowHeight",flags).GetValue(null)/1080;left=injectedDown?ButtonState.Pressed:ButtonState.Released;File.AppendAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"interface-input.txt"),x+","+y+","+left+Environment.NewLine);}
 }
 static void NeutralizeMouse(){var flags=BindingFlags.Static|BindingFlags.Public|BindingFlags.NonPublic;var platform=typeof(Game).Assembly.GetType("Microsoft.Xna.Framework.FNAPlatform",true);var field=platform.GetField("GetMouseState",flags);if(field==null)throw new Exception("Unsupported FNA mouse input contract.");if(originalMouse==null){originalMouse=field.GetValue(null);mouseInput=field;}field.SetValue(null,Delegate.CreateDelegate(field.FieldType,typeof(ScenePreviewRunner).GetMethod("NeutralMouse",flags)));}
 static void RestoreMouse(){if(mouseInput!=null&&originalMouse!=null)mouseInput.SetValue(null,originalMouse);}
 static void SaveState(object logic,object scene,string root){
  var mouse=Mouse.GetState();if(mouse.X!=960||mouse.Y!=540||mouse.LeftButton!=ButtonState.Released||mouse.RightButton!=ButtonState.Released)throw new Exception("Preview neutral mouse verification failed.");
  var text=new System.Text.StringBuilder();text.AppendLine("Mouse: centered (960,540), buttons released; desktop pointer unchanged.");text.AppendLine("Scene: "+scene.GetType().FullName);
  var engine=Assembly.Load("Amorous.Engine").ManifestModule;var items=engine.ResolveField(0x040000ad).GetValue(scene) as System.Collections.IEnumerable;
  if(items!=null)foreach(object item in items)for(Type t=item.GetType();t!=null;t=t.BaseType)foreach(var f in t.GetFields(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.DeclaredOnly)){object value=f.GetValue(item);if(value!=null&&value.GetType().Namespace=="Amorous.Game.NPC")text.AppendLine("Registered NPC: "+value.GetType().FullName);}
  File.WriteAllText(Path.Combine(root,"preview-state.txt"),text.ToString());
 }
 [DllImport("kernel32",CharSet=CharSet.Unicode)]static extern bool SetDllDirectory(string p);
 [DllImport("SDL2.dll",CallingConvention=CallingConvention.Cdecl)]static extern void SDL_HideWindow(IntPtr w);
 [DllImport("SDL2.dll",CallingConvention=CallingConvention.Cdecl)]static extern void SDL_ShowWindow(IntPtr w);
 [DllImport("SDL2.dll",CallingConvention=CallingConvention.Cdecl)]static extern void SDL_SetWindowPosition(IntPtr w,int x,int y);
 static void ApplyTestState(object player,Module engine,string root){
  string path=Path.Combine(root,".ams-test-state.json");if(!File.Exists(path))return;
  var state=JObject.Parse(File.ReadAllText(path));
  engine.ResolveMethod(0x060002f5).Invoke(player,new object[]{(string)state["PlayerName"]});
  foreach(var flag in ((JObject)state["Flags"]).Properties())engine.ResolveMethod(0x06000388).Invoke(player,new object[]{flag.Name,(bool)flag.Value});
  foreach(var stage in ((JObject)state["QuestStages"]).Properties())engine.ResolveMethod(0x06000381).Invoke(player,new object[]{stage.Name,(int)stage.Value});
  var contactsSetter=engine.ResolveMethod(0x06000375);Type contactType=contactsSetter.GetParameters()[0].ParameterType;long contacts=0;
  foreach(JToken contact in (JArray)state["Contacts"])contacts|=Convert.ToInt64(Enum.Parse(contactType,(string)contact,false));
  contactsSetter.Invoke(player,new object[]{Enum.ToObject(contactType,contacts)});
  engine.ResolveMethod(0x06000084).Invoke(null,new object[]{(bool)state["Censored"]});
  var proof=new JObject();proof["AppliedBeforeSceneAndQuest"]=true;proof["PlayerName"]=(string)engine.ResolveMethod(0x060002f4).Invoke(player,null);
  proof["Flags"]=JObject.FromObject(engine.ResolveMethod(0x06000378).Invoke(player,null));proof["QuestStages"]=JObject.FromObject(engine.ResolveMethod(0x06000372).Invoke(player,null));proof["Contacts"]=engine.ResolveMethod(0x06000374).Invoke(player,null).ToString();proof["Censored"]=(bool)engine.ResolveMethod(0x06000083).Invoke(null,null);
  File.WriteAllText(Path.Combine(root,"test-state-applied.json"),proof.ToString());
 }
 static void SaveQuestProof(object logic,Module module,Module engine,string root){
  var proof=new JObject();object quest=module.ResolveField(0x04000081).GetValue(logic);proof["ActiveQuest"]=quest!=null;
  if(quest!=null){object progress=engine.ResolveField(0x04000327).GetValue(quest);if(progress!=null){object current=engine.ResolveField(0x0400032c).GetValue(progress);if(current!=null){proof["Event"]=JObject.FromObject(current);proof["EventType"]=current.GetType().FullName;}}}
  object player=module.ResolveMethod(0x0600091e).Invoke(null,null);proof["PlayerName"]=(string)engine.ResolveMethod(0x060002f4).Invoke(player,null);proof["Flags"]=JObject.FromObject(engine.ResolveMethod(0x06000378).Invoke(player,null));proof["QuestStages"]=JObject.FromObject(engine.ResolveMethod(0x06000372).Invoke(player,null));
  File.WriteAllText(Path.Combine(root,"quest-snapshot.json"),proof.ToString());
 }
 sealed class SeekController {
  readonly JObject plan;readonly JArray steps;readonly Module gameModule,engine;readonly string root;readonly bool capture;readonly PreviewGame game;
  readonly Stopwatch total=Stopwatch.StartNew(),eventClock=Stopwatch.StartNew();object quest;int index;public bool Reached;
  public SeekController(PreviewGame game,Module module,Module engine,string root,bool capture){this.game=game;this.gameModule=module;this.engine=engine;this.root=root;this.capture=capture;plan=JObject.Parse(File.ReadAllText(Path.Combine(root,".ams-seek-plan.json")));steps=(JArray)plan["Steps"];if(steps.Count<1||steps.Count>2000)throw new Exception("Invalid native seek route.");}
  static int N(JToken value,string name){return (int)value[name];}
  int Stage(object progress){object data=engine.ResolveField(0x0400032b).GetValue(progress);return (int)JObject.FromObject(data)["Stage"];}
  public void Attach(object owner){quest=owner;AMSSeekGate.Owner=owner;AMSSeekGate.Active=true;FieldInfo field=engine.ResolveField(0x04000324);Delegate callback=Delegate.CreateDelegate(field.FieldType,this,GetType().GetMethod("OnEvent",BindingFlags.Public|BindingFlags.Instance));field.SetValue(owner,Delegate.Combine((Delegate)field.GetValue(owner),callback));}
  public void OnEvent(object data){
   if(Reached)return;object progress=engine.ResolveField(0x04000327).GetValue(quest);int stage=Stage(progress),id=(int)JObject.FromObject(data)["ID"];JToken expected=steps[index];
   if(stage!=N(expected,"Stage")||id!=N(expected,"EventId"))throw new Exception("Реальный путь разошелся с выбранным: ожидалось "+N(expected,"Stage")+":#"+N(expected,"EventId")+", движок перешел в "+stage+":#"+id+". Проверьте флаги, ответы и эффекты команд.");
   eventClock.Restart();if(index==steps.Count-1){Reached=true;AMSSeekGate.Active=false;SaveBeforeTarget(stage,id);Publish("Выбранное событие достигнуто. Управление передано игре.",true);if(!capture){RestoreMouse();SDL_SetWindowPosition(game.Window.Handle,0x2FFF0000,0x2FFF0000);SDL_ShowWindow(game.Window.Handle);}return;}
   index++;Publish("Подготовка "+stage+":#"+id+" · "+(string)expected["Type"],false);
  }
  void Publish(string text,bool reached){var value=new JObject();value["Message"]=text;value["Applied"]=reached?steps.Count-1:Math.Max(0,index-1);value["Total"]=steps.Count-1;value["ReachedTarget"]=reached;File.WriteAllText(Path.Combine(root,"seek-progress.json"),value.ToString());}
  void SaveBeforeTarget(int stage,int id){
   object scene=gameModule.ResolveField(0x0400007c).GetValue(game.Logic);var proof=new JObject();proof["Status"]="PASS";proof["ReachedTarget"]=true;proof["BeforeTargetHandler"]=true;proof["TargetStage"]=stage;proof["TargetEventId"]=id;proof["Scene"]=scene.GetType().Name;proof["AppliedEvents"]=index;var route=new JArray();for(int i=0;i<index;i++)route.Add(steps[i].DeepClone());proof["Prefix"]=route;
   var actors=new JArray();var items=engine.ResolveField(0x040000ad).GetValue(scene) as System.Collections.IEnumerable;if(items!=null)foreach(object item in items)for(Type t=item.GetType();t!=null;t=t.BaseType)foreach(var f in t.GetFields(BindingFlags.Instance|BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.DeclaredOnly)){object value=f.GetValue(item);if(value!=null&&value.GetType().Namespace=="Amorous.Game.NPC")actors.Add(value.GetType().Name);}proof["Actors"]=actors;
   object player=gameModule.ResolveMethod(0x0600091e).Invoke(null,null);proof["Flags"]=JObject.FromObject(engine.ResolveMethod(0x06000378).Invoke(player,null));proof["QuestStages"]=JObject.FromObject(engine.ResolveMethod(0x06000372).Invoke(player,null));File.WriteAllText(Path.Combine(root,"seek-result.json"),proof.ToString());
  }
  public void Pump(){
   if(Reached)return;if(total.Elapsed.TotalSeconds>90||eventClock.Elapsed.TotalSeconds>15){JToken step=steps[Math.Max(0,index-1)];throw new TimeoutException("Подготовка остановлена на "+N(step,"Stage")+":#"+N(step,"EventId")+" ("+(string)step["Type"]+"). Событие требует ручного взаимодействия или внешнего состояния; выберите другую точку входа. Эффект не пропущен.");}
   if(!object.ReferenceEquals(gameModule.ResolveField(0x04000081).GetValue(game.Logic),quest))throw new Exception("Предыдущее событие переключило активное задание. Его продолжение нельзя восстановить этим маршрутом; выберите точку входа после внешнего задания.");
   object progress=engine.ResolveField(0x04000327).GetValue(quest);object data=engine.ResolveField(0x0400032c).GetValue(progress);object handler=engine.ResolveField(0x0400032d).GetValue(progress);
   if(data==null||handler==null)throw new Exception("Native seek lost its current event before the target.");
   string type=data.GetType().Name;
   if(type=="PlayDialogueEventData"){object ui=engine.ResolveField(0x0400023b).GetValue(null);var state=engine.ResolveField(0x0400024b);state.SetValue(ui,Enum.ToObject(state.FieldType,6));} // Native UI finished state, acknowledged by the original dialogue handler.
   else if(type=="DialogueChoiceEventData"){
    JToken step=steps[Math.Max(0,index-1)];if(step["Choice"]==null||step["Choice"].Type==JTokenType.Null)throw new Exception("Ответ для предыдущего выбора не задан.");
    object ui=engine.ResolveField(0x0400023b).GetValue(null),choices=engine.ResolveField(0x04000243).GetValue(ui);engine.ResolveMethod(0x060004fa).Invoke(choices,new object[]{(int)step["Choice"]+1});
   }else if(type=="DelayEventData")engine.ResolveField(0x0400033d).SetValue(handler,0); // Presentation delay only; its native Update still runs.
   bool completed=(bool)engine.ResolveMethod(0x0600074b).Invoke(progress,new object[]{game.CurrentTime});
   if(completed&&!Reached){
    JToken expected=steps[index];int nextStage=N(expected,"Stage");if(nextStage==Stage(progress))throw new Exception("Этап закончился до выбранного события.");
    object player=gameModule.ResolveMethod(0x0600091e).Invoke(null,null);var liveStages=JObject.FromObject(engine.ResolveMethod(0x06000372).Invoke(player,null));string own=(string)plan["SourceQuestName"];if(liveStages[own]==null||(int)liveStages[own]!=nextStage)throw new Exception("Реальное состояние задания «"+own+"» не подтверждает переход в этап "+nextStage+". Команда или внешнее событие изменили маршрут; подготовка остановлена.");
    object dataRoot=engine.ResolveField(0x04000326).GetValue(quest);object nextData=null;foreach(object s in (System.Collections.IEnumerable)engine.ResolveMethod(0x060006a1).Invoke(dataRoot,null))if((int)JObject.FromObject(s)["Stage"]==nextStage){nextData=s;break;}if(nextData==null)throw new Exception("Explicit next stage is absent.");
    object next=((ConstructorInfo)engine.ResolveMethod(0x06000749)).Invoke(new object[]{quest,nextData});engine.ResolveField(0x04000327).SetValue(quest,next);engine.ResolveMethod(0x0600074a).Invoke(next,new object[]{(int?)N(expected,"EventId")});
   }
  }
 }
 static int RunQuest(string root,string[] request){
  string scene=request[0],questName=request[1];int stage=int.Parse(request[2],System.Globalization.CultureInfo.InvariantCulture);int? start=request[3].Length==0?(int?)null:int.Parse(request[3],System.Globalization.CultureInfo.InvariantCulture);bool capture=request[4]=="capture";
  var module=Assembly.Load("Amorous.Game").ManifestModule;var engine=Assembly.Load("Amorous.Engine").ManifestModule;module.Assembly.GetType("Amorous.Game.Scenes."+scene,true);
  bool seekRequested=File.Exists(Path.Combine(root,".ams-seek-plan.json"));if(capture||seekRequested)NeutralizeMouse();using(var game=new PreviewGame()){
   game.Window.Title="AMS — проверка сценария (отдельная копия, без звука)";bool started=false,sceneRequested=false;int startFrame=0,sceneFrame=0,readyFrame=-1;SeekController seek=null;var clock=Stopwatch.StartNew();
   game.AfterUpdate=delegate(PreviewGame current){
    if(capture||(seekRequested&&(seek==null||!seek.Reached)))SDL_HideWindow(current.Window.Handle);
    if(!sceneRequested&&current.Frames>=30){object player=module.ResolveMethod(0x0600091e).Invoke(null,null);module.ResolveMethod(0x06000920).Invoke(null,new[]{player});ApplyTestState(player,engine,root);foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies()){var ui=assembly.GetType("AMSInterface.Runtime");if(ui!=null)ui.GetField("bypass",BindingFlags.Static|BindingFlags.NonPublic).SetValue(null,true);}module.ResolveMethod(0x06000095).Invoke(current.Logic,new object[]{scene});sceneRequested=true;sceneFrame=current.Frames;}
    object activeScene=module.ResolveField(0x0400007c).GetValue(current.Logic);if(!started&&sceneRequested&&current.Frames>=sceneFrame+2&&activeScene!=null&&activeScene.GetType().Name==scene){object quest=module.ResolveMethod(0x0600009e).Invoke(current.Logic,new object[]{questName});if(quest==null)throw new Exception("Test quest was not loaded.");module.ResolveField(0x04000081).SetValue(current.Logic,quest);if(seekRequested){seek=new SeekController(current,module,engine,root,capture);seek.Attach(quest);}engine.ResolveMethod(0x06000735).Invoke(quest,new object[]{stage,start});started=true;startFrame=current.Frames;File.WriteAllText(Path.Combine(root,"quest-ready.txt"),"Stage="+stage+"; Event="+(start.HasValue?start.Value.ToString():"StartID")+"; Scene="+scene+"; FreshPlayer=true; SourceQuest="+questName);}
    if(started&&seek!=null&&!seek.Reached)seek.Pump();if(started&&(seek==null||seek.Reached)&&readyFrame<0)readyFrame=current.Frames;
    if(capture&&started&&readyFrame>=0&&current.Frames>=readyFrame+120){SaveState(current.Logic,module.ResolveField(0x0400007c).GetValue(current.Logic),root);SaveQuestProof(current.Logic,module,engine,root);current.SaveAudioProof(root);current.Save(Path.Combine(root,"quest-preview.png"));current.Exit();}
    if(capture&&clock.ElapsedMilliseconds>(seekRequested?115000:25000))throw new TimeoutException("Quest capture timed out before the selected event was ready.");
   };
   game.Run();if(seekRequested&&(seek==null||!seek.Reached))throw new Exception("Выбранное событие не достигнуто: игровой прогон был закрыт или прерван.");game.SaveAudioProof(root);return 0;
  }
 }
 static void VerifyInterfaceAction(object logic,object actual,string[] action,string root){
  Type runtime=null;foreach(var a in AppDomain.CurrentDomain.GetAssemblies())if(a.GetName().Name.StartsWith("Interface."))runtime=a.GetType("AMSInterface.Runtime");if(runtime==null)throw new Exception("Interface runtime not loaded.");var flags=BindingFlags.Static|BindingFlags.NonPublic;
  bool disabled=(bool)runtime.GetField("disabled",flags).GetValue(null),credits=(bool)runtime.GetField("credits",flags).GetValue(null),bypass=(bool)runtime.GetField("bypass",flags).GetValue(null);string scene=actual==null?"":actual.GetType().Name;bool pass=false;
  switch(action[0]){case "Credits":pass=credits;break;case "Back":pass=!credits&&scene=="MainMenuScene";break;case "OriginalMenu":pass=bypass;break;case "NewGame":pass=Assembly.Load("Amorous.Game").ManifestModule.ResolveField(0x04000081).GetValue(logic)!=null&&scene!="MainMenuScene";break;case "Scene":pass=scene==action[3];break;}
  string report="Action="+action[0]+"; Pass="+(pass&&!disabled)+"; Scene="+scene+"; Credits="+credits+"; Native="+bypass+"; Disabled="+disabled;File.WriteAllText(Path.Combine(root,"interface-action-result.txt"),report);if(!pass||disabled)throw new Exception(report);
 }
 static void VerifyEarlyAudioGuard(string root){
  string marker=Path.Combine(root,".ams-audio-guard");if(!File.Exists(marker))throw new Exception("Preview requires audio protection before game initialization.");
  using(var sha=System.Security.Cryptography.SHA256.Create())using(var file=File.OpenRead(Path.Combine(root,"FNA.dll"))){string actual=BitConverter.ToString(sha.ComputeHash(file)).Replace("-","").ToLowerInvariant();if(actual!=File.ReadAllText(marker).Trim())throw new Exception("Preview audio guard SHA mismatch.");}
  SoundEffect.MasterVolume=1f;MediaPlayer.Volume=1f;MediaPlayer.IsMuted=false;
  if(SoundEffect.MasterVolume!=0f||MediaPlayer.Volume!=0f||!MediaPlayer.IsMuted)throw new Exception("Preview volume override escaped the audio guard.");
  File.WriteAllText(Path.Combine(root,"audio-preinit.json"),"{\"status\":\"PASS\",\"beforeEngineInitialization\":true,\"beforeGameConstructor\":true,\"attemptedMasterVolume\":1,\"actualMasterVolume\":0,\"attemptedMusicVolume\":1,\"actualMusicVolume\":0,\"attemptedUnmute\":true,\"actualMuted\":true}");
 }
 [STAThread]static int Main(string[] args){
  string root=AppDomain.CurrentDomain.BaseDirectory,output=Path.Combine(root,"preview.png");
  try{
   if(args.Length!=2||!File.Exists(Path.Combine(root,".ams-isolated-preview")))throw new Exception("Runner requires its own isolated preview copy.");
   output=Path.GetFullPath(args[1]);if(!output.StartsWith(root,StringComparison.OrdinalIgnoreCase))throw new Exception("Output must be inside isolated copy.");
   string[] request=args[0]=="--quest"?File.ReadAllLines(output):null;if(request!=null&&request.Length!=5)throw new Exception("Invalid quest request.");
   Directory.SetCurrentDirectory(root);SetDllDirectory(Path.Combine(root,"x64"));Environment.SetEnvironmentVariable("SDL_AUDIODRIVER","dummy");Environment.SetEnvironmentVariable("ALSOFT_DRIVERS","null");if(request==null||request[4]=="capture"||File.Exists(Path.Combine(root,".ams-seek-plan.json")))Environment.SetEnvironmentVariable("SDL_VIDEO_WINDOW_POS","-32000,-32000");
   File.WriteAllText(Path.Combine(root,"options.json"),"{\"ResolutionWidth\":1920,\"ResolutionHeight\":1080,\"Fullscreen\":false,\"MasterVolume\":0.0,\"MusicVolume\":0.0,\"SfxVolume\":0.0,\"DialogueTextSpeed\":3,\"DialogueAutoSkip\":false,\"CheatsCensored\":false,\"CheatsTopless\":false,\"CheatsBottomless\":false}");
   AppDomain.CurrentDomain.AssemblyResolve+=(s,e)=>{string p=Path.Combine(root,new AssemblyName(e.Name).Name+".dll");return File.Exists(p)?Assembly.LoadFrom(p):null;};
   VerifyEarlyAudioGuard(root);
   Assembly.Load("Amorous.Engine").ManifestModule.ResolveMethod(0x060005c0).Invoke(null,null);
   if(request!=null)return RunQuest(root,request);
   var m=Assembly.Load("Amorous.Game").ManifestModule;var type=m.Assembly.GetType("Amorous.Game.Scenes."+args[0],true);if(type.IsAbstract)throw new Exception("Scene is abstract.");
   string[] action=File.Exists(Path.Combine(root,"interface-action.txt"))?File.ReadAllLines(Path.Combine(root,"interface-action.txt")):null;
   NeutralizeMouse();using(var game=new PreviewGame()){
    SDL_HideWindow(game.Window.Handle);bool switched=false;var clock=Stopwatch.StartNew();int switchFrame=0;
    while(clock.ElapsedMilliseconds<18000){if(action!=null&&switched){int offset=game.Frames-switchFrame;injectedX=offset>=20&&offset<23?int.Parse(action[1]):-1;injectedY=offset>=20&&offset<23?int.Parse(action[2]):-1;injectedDown=offset==21;}game.RunOneFrame();SDL_HideWindow(game.Window.Handle);
     if(!switched&&game.Frames>=30){object player=m.ResolveMethod(0x0600091e).Invoke(null,null);m.ResolveMethod(0x06000920).Invoke(null,new[]{player});if(args[0]!="MainMenuScene")foreach(var assembly in AppDomain.CurrentDomain.GetAssemblies()){var ui=assembly.GetType("AMSInterface.Runtime");if(ui!=null)ui.GetField("bypass",BindingFlags.Static|BindingFlags.NonPublic).SetValue(null,true);}m.ResolveMethod(0x06000095).Invoke(game.Logic,new object[]{args[0]});switched=true;switchFrame=game.Frames;}
     if(switched&&game.Frames>=switchFrame+120){object actual=m.ResolveField(0x0400007c).GetValue(game.Logic);if(action==null){if(actual==null||actual.GetType()!=type)throw new Exception("Game did not enter requested scene.");}else VerifyInterfaceAction(game.Logic,actual,action,root);SaveState(game.Logic,actual,root);game.SaveAudioProof(root);game.Save(output);File.WriteAllText(Path.Combine(root,"preview-success.txt"),args[0]);return 0;}Thread.Sleep(16);
    }
    throw new TimeoutException("Scene rendering exceeded 18 seconds.");
   }
  }catch(Exception ex){File.WriteAllText(Path.Combine(root,"preview-error.txt"),(File.Exists(Path.Combine(root,".ams-seek-plan.json"))?"Подготовка выбранного события остановлена. Проверьте маршрут и начальное состояние.\r\n":"Exact game preview failed. Steam must be running for this game.\r\n")+ex);return 1;}
 }
}
