using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
if (args.Length is < 1 or > 3) throw new ArgumentException("Pass the repository root, optional navigation DLL and report path.");
var root = Path.GetFullPath(args[0]);
var results = new List<object>();
var loader = Path.Combine(root, "artifacts/plugin-build/dinput8.dll");
var navigation = args.Length >= 2 ? Path.GetFullPath(args[1]) : Path.Combine(root, "artifacts/navigation-native-build/XFEEDNavigation.dll");
var interaction = Path.Combine(root, "artifacts/interaction-native-build/XFEEDInteraction.dll");
var platform = Path.Combine(root, "artifacts/managed-platform-validation/XFEEDPluginValidation.dll");
var paths = new[] { loader, navigation, interaction, platform };
var handles = new Dictionary<string, nint>(StringComparer.OrdinalIgnoreCase);
foreach (var path in paths)
{
using var stream = File.OpenRead(path); using var pe = new PEReader(stream);
Require(pe.PEHeaders.CorHeader is null, "Expected a native PE DLL, not an IL assembly: " + path);
Require(pe.PEHeaders.CoffHeader.Machine == System.Reflection.PortableExecutable.Machine.Amd64, "Expected x64: " + path);
// AOT DLLs stay loaded for this fixture process's lifetime. Never FreeLibrary them.
handles[path] = NativeLibrary.Load(path);
}
using var abiContract = JsonDocument.Parse(File.ReadAllText(Path.Combine(root, "native/ManagedPlugin/abi-exports.json")));
Require(abiContract.RootElement.GetProperty("schema").GetInt32() == 1 && abiContract.RootElement.GetProperty("abiVersion").GetInt32() == 1, "Original ABI contract version");
foreach (var library in abiContract.RootElement.GetProperty("libraries").EnumerateArray())
{
var path = paths.Single(value => Path.GetFileName(value) == library.GetProperty("file").GetString());
var names = library.GetProperty("exports").EnumerateArray().Select(value => value.GetString()!).ToArray();
Require(names.Length > 0 && names.Distinct().Count() == names.Length, "Original export contract must be nonempty and unique");
foreach (var name in names) Require(NativeLibrary.TryGetExport(handles[path], name, out _), "Missing original ABI: " + name);
results.Add(new { check = "originalExports", file = Path.GetFileName(path), names });
}
Require(Call(loader, "XFEPluginGetAbiVersion") == 1, "Plugin ABI");
Require(Call(navigation, "XFENavigationGetAbiVersion") == 1, "Navigation ABI");
var checks = new (string Suffix, uint Expected)[]
{
("CommerceBatch",255),("CommunityGoal",255),("LocalDockingKind",1023),("NormalBoost",7),
("DockingInputMapper",255),("DockingFixture",255),
("Observer",7),("CommerceDocked",127),("LocalDockingHandoff",16777215),("DockingZeroUpdate",255),("ShipHealth",255),("HealthFlow",255),("LocalCooldown",255),("ServiceQuotes",255),("DockingSourceZero",255),("LocalTargets",255),("LocalEntityDirectory",255),("LocalSelection",255),("LocalDockingAvailability",31),("LocalPlayerIdentity",255),
("OverlayLifetime",31),("ComponentLookup",63),("FsdCancellation",127),("ThrottleControl",255),
("TargetLookup",63),("ActorSelection",63),("FsdReturn",3),("CommitGate",31),("OutwardThrottle",31),
("RouteArguments",31),("StellarSpeed",255),("ReferenceGeometry",524287),("AdaptiveMotion",1023),
("ScoopGeometry",63),("HeatNormalization",31),("ApplicationAxes",31),("SessionMenu",4095),
("Commerce",255),("InterfaceProtocol",4095),("GalaxyCoverage",8191),("GalaxyAccount",255),("GalaxyAccountWire",63),("GalaxyTradeData",255),("GalaxyColonisation",255),("GalaxyFilterFacts",255),("GalaxyFilterFactsQueue",255),("GalaxyVisitedFacts",255),("GalaxyColonisationFacts",255),("GalaxySocial",65535),("GalaxySocialModes",8191),("GalaxyPowerOptions",65535),("GalaxyAppearance",255),("GalaxyPowerActivity",255),("GalaxyPowerActivityWire",255),("GalaxyThargoid",65535),("GalaxyAccountCommunityGoals",255),("GalaxyAccountFleet",65535),("GalaxyAccountFleetWire",255),("GalaxyAccountMissions",65535),("GalaxyAccountMissionsWire",255),("GalaxyPowerOuter",65535),("GalaxyPowerOuterWire",255),("GalaxyPowerTerritory",255),("GalaxyPowerTerritoryWire",255),("GalaxyPowerStatus",255),("GalaxyPowerStatusWire",255),("GalaxyCommodityOptions",255),("GalaxyCommodityOptionsWire",255),("GalaxyCommodityEligibility",255),("GalaxyCommodityBalance",255)
,("GalaxyCommodityFilter",255),("GalaxyCommodityFilterWire",255),("GalaxyCommodityRebuild",255),("GalaxyCurrentTradeFlows",255),("GalaxyCurrentTradeFlowsWire",255)
,("GalaxyCells",65535),("GalaxyPowerOuterStyle",255),("GalaxyPowerOuterExtent",255),("GalaxyCatalogueQuality",255),("GalaxyCatalogueProfile",65535)
,("GalaxyBasicSystemFacts",255),("GalaxyBasicSystemFactsWire",255)
,("GalaxyAllegianceDefinitions",65535),("GalaxyCellBudget",255)
,("GalaxyNavRouteMembership",255)
,("GalaxyPointOverrides",255)
,("GalaxyRouteOptions",255)
,("GalaxyRouteStarClassFilter",255),("GalaxyCivilizationFacts",255),("GalaxyServiceFacts",255)
,("GalaxyCarrierFacts",255)
,("GalaxyDustInputs",255),("GalaxyRouteMapFilter",255),("GalaxyStateFacts",65535),("GalaxyColonisationOwnership",255),("GalaxyColonisationHighlight",255),("GalaxyPowerInfluence",255)
};
foreach (var (suffix, expected) in checks)
{
var name = "XFENavigation" + suffix + "SelfTest";
var values = Enumerable.Range(0, 8).Select(_ => Call(navigation, name)).ToArray();
Require(values.All(value => value == expected), name + " expected " + expected + ", received " + string.Join(',', values));
results.Add(new { check = name, expected, masks = values });
}
foreach (var (name, expected) in new[] { ("XFEPlatformSelfTest", 2047u), ("XFEInteractionSelfTest", 255u) })
{
var values = Enumerable.Range(0, 8).Select(_ => Call(platform, name)).ToArray();
Require(values.All(value => value == expected), name + " failed: " + string.Join(',', values));
results.Add(new { check = name, expected, masks = values });
}
// Unsupported fixture images must never install game hooks or start the input service.
Require(Call(navigation, "XFENavigationStart") == 0, "A fixture executable was accepted as the supported game");
var start = Marshal.GetDelegateForFunctionPointer<Start>(NativeLibrary.GetExport(handles[interaction], "XFEInteractionStart"));
foreach (var thread in new nuint[] { 0, nuint.MaxValue, 1 }) Require(start(thread) == 0, "Invalid interaction start was accepted");
var report = new
{
passed = true, gameAccess = false, processScope = "own fixture process and allocations", iterations = 8,
checks = results,
binaries = paths.Select(path => new { file = Path.GetFileName(path), sha256 = Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path))) }).ToArray()
};
var reportPath = args.Length >= 3 ? Path.GetFullPath(args[2]) : Path.Combine(root, "artifacts/managed-plugin-validation.json");
File.WriteAllText(reportPath, JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true }), new UTF8Encoding(false));
Console.WriteLine($"Passed {checks.Length * 8 + 16} native self-test invocations; original exports, PE format, ABI and unsupported-build rejection passed.");
Console.WriteLine(reportPath);
uint Call(string path, string name) => Marshal.GetDelegateForFunctionPointer<Probe>(NativeLibrary.GetExport(handles[path], name))();
void Require(bool value, string message) { if (!value) throw new InvalidOperationException(message); }
[UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate uint Probe();
[UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate uint Start(nuint thread);
using System.Reflection.PortableExecutable;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
if (args.Length is < 1 or > 3) throw new ArgumentException("Pass the repository root, optional navigation DLL and report path.");
var root = Path.GetFullPath(args[0]);
var results = new List<object>();
var loader = Path.Combine(root, "artifacts/plugin-build/dinput8.dll");
var navigation = args.Length >= 2 ? Path.GetFullPath(args[1]) : Path.Combine(root, "artifacts/navigation-native-build/XFEEDNavigation.dll");
var interaction = Path.Combine(root, "artifacts/interaction-native-build/XFEEDInteraction.dll");
var platform = Path.Combine(root, "artifacts/managed-platform-validation/XFEEDPluginValidation.dll");
var paths = new[] { loader, navigation, interaction, platform };
var handles = new Dictionary<string, nint>(StringComparer.OrdinalIgnoreCase);
foreach (var path in paths)
{
using var stream = File.OpenRead(path); using var pe = new PEReader(stream);
Require(pe.PEHeaders.CorHeader is null, "Expected a native PE DLL, not an IL assembly: " + path);
Require(pe.PEHeaders.CoffHeader.Machine == System.Reflection.PortableExecutable.Machine.Amd64, "Expected x64: " + path);
// AOT DLLs stay loaded for this fixture process's lifetime. Never FreeLibrary them.
handles[path] = NativeLibrary.Load(path);
}
using var abiContract = JsonDocument.Parse(File.ReadAllText(Path.Combine(root, "native/ManagedPlugin/abi-exports.json")));
Require(abiContract.RootElement.GetProperty("schema").GetInt32() == 1 && abiContract.RootElement.GetProperty("abiVersion").GetInt32() == 1, "Original ABI contract version");
foreach (var library in abiContract.RootElement.GetProperty("libraries").EnumerateArray())
{
var path = paths.Single(value => Path.GetFileName(value) == library.GetProperty("file").GetString());
var names = library.GetProperty("exports").EnumerateArray().Select(value => value.GetString()!).ToArray();
Require(names.Length > 0 && names.Distinct().Count() == names.Length, "Original export contract must be nonempty and unique");
foreach (var name in names) Require(NativeLibrary.TryGetExport(handles[path], name, out _), "Missing original ABI: " + name);
results.Add(new { check = "originalExports", file = Path.GetFileName(path), names });
}
Require(Call(loader, "XFEPluginGetAbiVersion") == 1, "Plugin ABI");
Require(Call(navigation, "XFENavigationGetAbiVersion") == 1, "Navigation ABI");
var checks = new (string Suffix, uint Expected)[]
{
("CommerceBatch",255),("CommunityGoal",255),("LocalDockingKind",1023),("NormalBoost",7),
("DockingInputMapper",255),("DockingFixture",255),
("Observer",7),("CommerceDocked",127),("LocalDockingHandoff",16777215),("DockingZeroUpdate",255),("ShipHealth",255),("HealthFlow",255),("LocalCooldown",255),("ServiceQuotes",255),("DockingSourceZero",255),("LocalTargets",255),("LocalEntityDirectory",255),("LocalSelection",255),("LocalDockingAvailability",31),("LocalPlayerIdentity",255),
("OverlayLifetime",31),("ComponentLookup",63),("FsdCancellation",127),("ThrottleControl",255),
("TargetLookup",63),("ActorSelection",63),("FsdReturn",3),("CommitGate",31),("OutwardThrottle",31),
("RouteArguments",31),("StellarSpeed",255),("ReferenceGeometry",524287),("AdaptiveMotion",1023),
("ScoopGeometry",63),("HeatNormalization",31),("ApplicationAxes",31),("SessionMenu",4095),
("Commerce",255),("InterfaceProtocol",4095),("GalaxyCoverage",8191),("GalaxyAccount",255),("GalaxyAccountWire",63),("GalaxyTradeData",255),("GalaxyColonisation",255),("GalaxyFilterFacts",255),("GalaxyFilterFactsQueue",255),("GalaxyVisitedFacts",255),("GalaxyColonisationFacts",255),("GalaxySocial",65535),("GalaxySocialModes",8191),("GalaxyPowerOptions",65535),("GalaxyAppearance",255),("GalaxyPowerActivity",255),("GalaxyPowerActivityWire",255),("GalaxyThargoid",65535),("GalaxyAccountCommunityGoals",255),("GalaxyAccountFleet",65535),("GalaxyAccountFleetWire",255),("GalaxyAccountMissions",65535),("GalaxyAccountMissionsWire",255),("GalaxyPowerOuter",65535),("GalaxyPowerOuterWire",255),("GalaxyPowerTerritory",255),("GalaxyPowerTerritoryWire",255),("GalaxyPowerStatus",255),("GalaxyPowerStatusWire",255),("GalaxyCommodityOptions",255),("GalaxyCommodityOptionsWire",255),("GalaxyCommodityEligibility",255),("GalaxyCommodityBalance",255)
,("GalaxyCommodityFilter",255),("GalaxyCommodityFilterWire",255),("GalaxyCommodityRebuild",255),("GalaxyCurrentTradeFlows",255),("GalaxyCurrentTradeFlowsWire",255)
,("GalaxyCells",65535),("GalaxyPowerOuterStyle",255),("GalaxyPowerOuterExtent",255),("GalaxyCatalogueQuality",255),("GalaxyCatalogueProfile",65535)
,("GalaxyBasicSystemFacts",255),("GalaxyBasicSystemFactsWire",255)
,("GalaxyAllegianceDefinitions",65535),("GalaxyCellBudget",255)
,("GalaxyNavRouteMembership",255)
,("GalaxyPointOverrides",255)
,("GalaxyRouteOptions",255)
,("GalaxyRouteStarClassFilter",255),("GalaxyCivilizationFacts",255),("GalaxyServiceFacts",255)
,("GalaxyCarrierFacts",255)
,("GalaxyDustInputs",255),("GalaxyRouteMapFilter",255),("GalaxyStateFacts",65535),("GalaxyColonisationOwnership",255),("GalaxyColonisationHighlight",255),("GalaxyPowerInfluence",255)
};
foreach (var (suffix, expected) in checks)
{
var name = "XFENavigation" + suffix + "SelfTest";
var values = Enumerable.Range(0, 8).Select(_ => Call(navigation, name)).ToArray();
Require(values.All(value => value == expected), name + " expected " + expected + ", received " + string.Join(',', values));
results.Add(new { check = name, expected, masks = values });
}
foreach (var (name, expected) in new[] { ("XFEPlatformSelfTest", 2047u), ("XFEInteractionSelfTest", 255u) })
{
var values = Enumerable.Range(0, 8).Select(_ => Call(platform, name)).ToArray();
Require(values.All(value => value == expected), name + " failed: " + string.Join(',', values));
results.Add(new { check = name, expected, masks = values });
}
// Unsupported fixture images must never install game hooks or start the input service.
Require(Call(navigation, "XFENavigationStart") == 0, "A fixture executable was accepted as the supported game");
var start = Marshal.GetDelegateForFunctionPointer<Start>(NativeLibrary.GetExport(handles[interaction], "XFEInteractionStart"));
foreach (var thread in new nuint[] { 0, nuint.MaxValue, 1 }) Require(start(thread) == 0, "Invalid interaction start was accepted");
var report = new
{
passed = true, gameAccess = false, processScope = "own fixture process and allocations", iterations = 8,
checks = results,
binaries = paths.Select(path => new { file = Path.GetFileName(path), sha256 = Convert.ToHexStringLower(SHA256.HashData(File.ReadAllBytes(path))) }).ToArray()
};
var reportPath = args.Length >= 3 ? Path.GetFullPath(args[2]) : Path.Combine(root, "artifacts/managed-plugin-validation.json");
File.WriteAllText(reportPath, JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true }), new UTF8Encoding(false));
Console.WriteLine($"Passed {checks.Length * 8 + 16} native self-test invocations; original exports, PE format, ABI and unsupported-build rejection passed.");
Console.WriteLine(reportPath);
uint Call(string path, string name) => Marshal.GetDelegateForFunctionPointer<Probe>(NativeLibrary.GetExport(handles[path], name))();
void Require(bool value, string message) { if (!value) throw new InvalidOperationException(message); }
[UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate uint Probe();
[UnmanagedFunctionPointer(CallingConvention.Winapi)] delegate uint Start(nuint thread);