XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
返回提交历史

XFEstudio/XFEEDToolBox

Wait for fresh input observer samples and expose read-only docking diagnostics

A newly loaded interaction observer can refuse its first input sample. Retry known transient reads within the existing preparation deadline, without sending inputs or relaxing ship identity and deployment checks. Add a bounded read-only FSD/docking action-registry provenance report. Live evidence confirms both registries are the same; the continuous SCA-to-docking handoff remains unresolved and protected exits stay enabled. Validation: isolated acceptance CLI publish; all 8 TravelPreparation tests pass, including transient startup, terminal ownership failure and cancellation.

b5b7ba4
XFE工作室室长 <mail@xfegzs.com>
提交于

代码差异

5 个文件 +97 -2
Added XFEEDToolBox.Core/Native/NativeDockingInputDiagnostics.cs +56 -0
@@ -0,0 +1,56 @@
1 using System.Diagnostics;
2 using System.Globalization;
3 using XFEEDToolBox.Core.Navigation;
4
5 namespace XFEEDToolBox.Core.Native;
6
7 /// <summary>Read-only provenance check for the original FSD and docking action registries.</summary>
8 public static class NativeDockingInputDiagnostics
9 {
10 public static async Task<object> ReadAsync(int processId,CancellationToken token=default)
11 {
12 using var process=Process.GetProcessById(processId);
13 var started=process.StartTime.ToUniversalTime().Ticks;
14 var frame=await new NavigationPipeClient(processId).CallAsync(new{op="read"},token);
15 var entity=ulong.Parse(frame.GetProperty("entityId").GetString()!,CultureInfo.InvariantCulture);
16 using var memory=new ReadOnlyPlayerMemory(processId,started,NativeSystemLocationReader.GameSha256);
17 var (fsd,life,root)=memory.Player(entity);
18 var computer=memory.Component(root,memory.U32(memory.ImageBase+0x5F5317C));
19 if(memory.U64(computer)!=memory.ImageBase+0x5165248)throw new InvalidDataException("Original docking computer differs.");
20 var fsdController=memory.U64(fsd+0x700);var dockingController=memory.U64(computer+0x240);
21 if(fsdController==0||fsdController!=dockingController||memory.U64(memory.U64(fsdController)+0x158)!=memory.ImageBase+0x25E5D10)
22 throw new InvalidDataException("Original throttle controller ownership differs.");
23 var fsdActions=memory.U64(fsd+0x6F8);var dockingActions=memory.U64(computer+0x230);
24 object Registry(ulong value)
25 {
26 if(memory.U64(value)!=memory.ImageBase+0x512FD90||memory.U64(memory.U64(value)+0x40)!=memory.ImageBase+0x8FEDC0)
27 throw new InvalidDataException("Original action registry differs.");
28 var buckets=memory.U64(value+0xC0);var count=memory.U64(value+0xC8);
29 if(buckets==0||count is 0 or >4096)throw new InvalidDataException("Original action registry exceeds bound.");
30 var seen=new HashSet<ulong>();object? zero=null;
31 for(ulong i=0;i<count;i++)
32 {
33 var node=memory.U64(buckets+i*8);
34 while(node<buckets||node>=buckets+count*8)
35 {
36 if(node==0||!seen.Add(node)||seen.Count>4096)throw new InvalidDataException("Original action registry chain differs.");
37 var name=memory.U64(node+8);var length=memory.U64(name);
38 if(length is 0 or >128)throw new InvalidDataException("Original action name exceeds bound.");
39 if(System.Text.Encoding.ASCII.GetString(memory.Read(name+0x14,(int)length))=="SetSpeedZero")
40 {
41 var binding=memory.U64(node+0x10);var bytes=memory.Read(binding,0x38);
42 zero=new{binding=binding.ToString("x"),pending=BitConverter.ToInt32(bytes,0x1C),forced=bytes[0x24],
43 consumed=BitConverter.ToInt32(bytes,0x2C),consumedForced=bytes[0x34]};
44 }
45 node=memory.U64(node);
46 }
47 }
48 if(memory.U64(value+0xC0)!=buckets||memory.U64(value+0xC8)!=count)throw new InvalidDataException("Original action registry changed.");
49 return new{address=value.ToString("x"),zero};
50 }
51 var originalFsd=Registry(fsdActions);var originalDocking=Registry(dockingActions);
52 memory.Validate();if(memory.U64(life+0xB0)!=entity)throw new InvalidDataException("Main ship identity changed.");
53 return new{entity=entity.ToString(),fsd=fsd.ToString("x"),computer=computer.ToString("x"),sameRegistry=fsdActions==dockingActions,
54 controllerThrottle=BitConverter.ToSingle(memory.Read(fsdController+0x680,4)),originalFsd,originalDocking,processAccess="QUERY_INFORMATION | VM_READ",mutationsSent=0};
55 }
56 }
Modified XFEEDToolBox.Core/Navigation/TravelPreparation.cs +10 -1
@@ -31,7 +31,16 @@ public static class TravelPreparation
31 31 {
32 32 token.ThrowIfCancellationRequested();var state=player();
33 33 if(!state.InGame||state.Docked||NavigationSafety.HasExternalThreat(state))throw new InvalidOperationException("收起设备时游戏会话或飞行安全状态已改变。");
34 var frame=await channel.ReadAsync(token);var now=DateTimeOffset.UtcNow;
34 GameInputState frame;
35 try{frame=await channel.ReadAsync(token);}
36 catch(Exception error)when(NavigationRecovery.CanRetry(error,token))
37 {
38 // A newly loaded input observer can refuse its first sample.
39 // Keep the same overall bound and send nothing until it is
40 // fresh and belongs to the selected main ship.
41 clearSince=null;await Task.Delay(40,token);continue;
42 }
43 var now=DateTimeOffset.UtcNow;
35 44 if(frame.Sequence<=0||frame.EntityId==0||frame.ObservedAt>now.AddMilliseconds(100)||now-frame.ObservedAt>TimeSpan.FromMilliseconds(300))throw new FlightTelemetryUnavailableException("收起设备的原生状态已过期。");
36 45 if(expectedEntity is {} expected&&expected!=frame.EntityId)throw new InvalidOperationException("输入通道与当前飞船身份不一致。");
37 46 if(!StatusConfirmed(state,now))
Modified XFEEDToolBox.Test/TravelPreparationTests.cs +20 -1
@@ -14,8 +14,10 @@ public class TravelPreparationTests
14 14 public readonly List<string> Writes=[];
15 15 public Action? OnWrite;
16 16 public bool Refresh=true;
17 public int RefusedReads,Reads;
18 public string Refusal="Game input telemetry unavailable or stale";
17 19 public Task ConnectAsync(int thread,CancellationToken token)=>Task.CompletedTask;
18 public Task<GameInputState> ReadAsync(CancellationToken token){if(Refresh)Frame=Frame with{Sequence=Frame.Sequence+1,ObservedAt=DateTimeOffset.UtcNow};return Task.FromResult(Frame);}
20 public Task<GameInputState> ReadAsync(CancellationToken token){Reads++;if(RefusedReads-->0)throw new InvalidOperationException(Refusal);if(Refresh)Frame=Frame with{Sequence=Frame.Sequence+1,ObservedAt=DateTimeOffset.UtcNow};return Task.FromResult(Frame);}
19 21 public Task<long> SetAsync(GameInputState frame,string input,bool value,CancellationToken token)
20 22 {
21 23 Check.That(!value);Writes.Add(input);OnWrite?.Invoke();
@@ -36,6 +38,23 @@ public class TravelPreparationTests
36 38 var c=new Channel{Frame=new(1,42,DateTimeOffset.UtcNow,false,false,false)};
37 39 await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0),CancellationToken.None,42);Check.Equal(0,c.Writes.Count);
38 40 }
41 [Test]public async Task NewObserverWaitsForFreshInputWithoutSendingAnyToggle()
42 {
43 var c=new Channel{Frame=new(1,42,DateTimeOffset.UtcNow,false,false,false),RefusedReads=2};
44 using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(2));
45 await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0),stop.Token,42);
46 Check.That(c.Reads>=4);Check.Equal(0,c.Writes.Count);
47 }
48 [Test]public async Task UnknownInputRefusalIsTerminalAndKnownWaitingCanBeCancelled()
49 {
50 var c=new Channel{RefusedReads=1,Refusal="Input observer ownership changed"};
51 try{await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0),CancellationToken.None,42);throw new Exception("Expected ownership refusal");}
52 catch(InvalidOperationException){Check.Equal(1,c.Reads);Check.Equal(0,c.Writes.Count);}
53 c.RefusedReads=int.MaxValue;c.Refusal="Game input telemetry unavailable or stale";
54 using var stop=new CancellationTokenSource(150);
55 try{await TravelPreparation.EnsureRetractedAsync(c,()=>Player(0),stop.Token,42);throw new Exception("Expected cancellation");}
56 catch(OperationCanceledException){Check.Equal(0,c.Writes.Count);}
57 }
39 58 [Test]public async Task UnchangedStatusRemainsValidWhenFreshlyReadAndBelongsToTheCurrentSession()
40 59 {
41 60 var now=DateTimeOffset.UtcNow;
Modified docs/CG-TRADING-VALIDATION.md +4 -0
@@ -16,6 +16,10 @@
16 16
17 17 原始返回菜单路径未通过实机;只有插件明确声明 `nativeReturnToMenuVerified=true` 才允许该路径。从已就绪主菜单进入Solo使用独立入口能力。
18 18
19 后续空载连续航程再次完成起飞、跃迁、SCA和空管,但自动降落仍未接管并触发保护退出。实际飞行油门持续为.75,即便控制器零位及原始输入回执已确认。只读来源诊断确认FSD与自动降落使用同一个原始输入注册表,因此不能把来源差异当作已确认原因。可使用 `inspect-docking-input-sources <PID> <新报告>` 读取来源与原始零输入待消费/已消费状态;该命令仅使用QUERY_INFORMATION和VM_READ权限。
20
21 新会话加载交互观察器时,首个输入样本可能暂未发布。准备流程现在在原有12秒上限内重读已知暂时拒绝;在新鲜输入、正确船舶和物理收起状态全部确认前不发送输入。未知所有权拒绝和取消仍立即停止。
22
19 23 ## 运行与恢复
20 24
21 25 先通过 `scripts/Build-Plugin.ps1` 构建原生包,使用游戏插件管理器安装;游戏构建哈希变化后必须重新适配。再构建 `research/NativeAcceptance/NativeAcceptance.csproj`。研究CLI仅在已授权且身份核对过的游戏会话使用,不模拟键鼠。
Modified research/NativeAcceptance/Program.cs +7 -0
@@ -7,6 +7,13 @@ using XFEEDToolBox.Core.Galaxy;
7 7
8 8 try
9 9 {
10 if(args.Length==3&&args[0]=="inspect-docking-input-sources")
11 {
12 if(File.Exists(args[2]))throw new IOException("Use a new read-only docking input report.");
13 var result=await XFEEDToolBox.Core.Native.NativeDockingInputDiagnostics.ReadAsync(int.Parse(args[1]));
14 await File.WriteAllTextAsync(args[2],JsonSerializer.Serialize(result,ToolboxSettings.JsonOptions));
15 Console.WriteLine(JsonSerializer.Serialize(result,ToolboxSettings.JsonOptions));return;
16 }
10 17 if(args.Length==3&&args[0]=="start-session")
11 18 {
12 19 try{await GameSessionAcceptance.StartAsync(args[1],args[2]);}