param([string]$OutputDirectory='artifacts/plugin-handshake-test')
$ErrorActionPreference='Stop'
$taskRoot=Split-Path -Parent $PSScriptRoot
$taskFolder=[IO.Path]::GetFullPath((Join-Path $taskRoot $OutputDirectory))
if(-not $taskFolder.StartsWith($taskRoot+[IO.Path]::DirectorySeparatorChar,[StringComparison]::OrdinalIgnoreCase)){throw 'The fixture must stay within this workspace.'}
New-Item -ItemType Directory -Path $taskFolder -Force | Out-Null
$taskHost=Join-Path $taskFolder 'EliteDangerous64.exe'
$taskSource=Join-Path $taskFolder 'host.cpp'
@'
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <unknwn.h>
#include <string>
#include <initializer_list>
int wmain(int argc,wchar_t** argv) {
if(argc!=2)return 2;
const auto module=LoadLibraryW(argv[1]);if(!module)return 3;
using Create=HRESULT(WINAPI*)(HINSTANCE,DWORD,REFIID,LPVOID*,LPUNKNOWN);
const auto create=reinterpret_cast<Create>(GetProcAddress(module,"DirectInput8Create"));if(!create)return 4;
wchar_t system[MAX_PATH]{};GetSystemDirectoryW(system,MAX_PATH);
const auto genuine=LoadLibraryExW((std::wstring(system)+L"\\dinput8.dll").c_str(),nullptr,LOAD_LIBRARY_SEARCH_SYSTEM32);if(!genuine)return 5;
const auto baseline=reinterpret_cast<Create>(GetProcAddress(genuine,"DirectInput8Create"));if(!baseline)return 6;
struct Result{HRESULT result;DWORD last;bool output;};
auto call=[](Create function,DWORD version,DWORD seed){
void* output{};SetLastError(seed);
const auto result=function(GetModuleHandleW(nullptr),version,__uuidof(IUnknown),&output,nullptr);
const auto error=GetLastError();const bool returned=output!=nullptr;
if(output)reinterpret_cast<IUnknown*>(output)->Release();
return Result{result,error,returned};
};
for(const DWORD version:{0x800u,0u}){
const auto expected=call(baseline,version,0x5A5A1234);
const auto actual=call(create,version,0x5A5A1234);
if(expected.result!=actual.result||expected.last!=actual.last||expected.output!=actual.output)return 7;
}
Sleep(20000);return 0;
}
'@ | Set-Content -LiteralPath $taskSource -Encoding utf8
$taskVs=& 'C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe' -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
$taskBatch=Join-Path $taskFolder 'build.cmd'
[IO.File]::WriteAllText($taskBatch,"@call `"$taskVs\VC\Auxiliary\Build\vcvars64.bat`" >nul`r`n@cl /nologo /std:c++20 /O2 /MT /EHsc `"$taskSource`" /Fo`"$taskFolder\host.obj`" /link /OUT:`"$taskHost`" ole32.lib`r`n@exit /b %ERRORLEVEL%`r`n")
& $env:ComSpec /d /c $taskBatch
if($LASTEXITCODE -ne 0){throw 'Fixture build failed.'}
$taskDll=Join-Path $taskRoot 'artifacts/plugin-build/dinput8.dll'
if(-not (Test-Path -LiteralPath $taskDll -PathType Leaf)){throw 'Build the diagnostic loader first: scripts/Build-Plugin.ps1 -ExportDiagnostics.'}
$taskProcess=Start-Process -FilePath $taskHost -ArgumentList @(('"'+$taskDll+'"')) -WindowStyle Hidden -PassThru
$taskReplies=@()
try {
for($taskIndex=0;$taskIndex -lt 23;$taskIndex++) {
$taskPipe=[IO.Pipes.NamedPipeClientStream]::new('.','XFEEDToolBox.Plugin.'+$taskProcess.Id,[IO.Pipes.PipeDirection]::InOut,[IO.Pipes.PipeOptions]::Asynchronous)
try {
$taskPipe.Connect(2000)
$taskText=switch($taskIndex){20{"navigation.observe.start`n"}21{"deactivate`n"}default{"status`n"}}
$taskCommand=[Text.Encoding]::UTF8.GetBytes($taskText);$taskPipe.Write($taskCommand,0,$taskCommand.Length);$taskPipe.Flush()
$taskReader=[IO.StreamReader]::new($taskPipe,[Text.Encoding]::UTF8,$false,4096,$true)
$taskJson=$taskReader.ReadLineAsync().WaitAsync([TimeSpan]::FromSeconds(2)).GetAwaiter().GetResult()
$taskReply=$taskJson|ConvertFrom-Json
if($taskReply.processId -ne $taskProcess.Id -or -not $taskReply.loaded -or $taskReply.active -ne ($taskIndex -lt 21) -or $taskReply.observationStarted -or $taskReply.supportsNavigation -or $taskReply.supportsTrade -or $taskReply.supportsRelog){throw 'Unexpected fixture capability or identity.'}
$taskReplies+=$taskReply
} finally {$taskPipe.Dispose()}
}
[ordered]@{fixtureExecutable=$taskHost;pluginSha256=(Get-FileHash -LiteralPath $taskDll -Algorithm SHA256).Hash;version=$taskReplies[0].version;statusReplies=20;lifecycleReplies=3;directInputReturnAndLastErrorMatched=$true;passed=$true;commands=@('status','navigation.observe.start','deactivate','status');actualGameTouched=$false}|ConvertTo-Json|Set-Content -LiteralPath (Join-Path $taskFolder 'result.json') -Encoding utf8
Get-Content -LiteralPath (Join-Path $taskFolder 'result.json')
} finally {
$taskObserved=Get-Process -Id $taskProcess.Id -ErrorAction SilentlyContinue
if($taskObserved -and [string]::Equals($taskObserved.Path,$taskHost,[StringComparison]::OrdinalIgnoreCase)){Stop-Process -Id $taskObserved.Id -ErrorAction SilentlyContinue}
}
param([string]$OutputDirectory='artifacts/plugin-handshake-test')
$ErrorActionPreference='Stop'
$taskRoot=Split-Path -Parent $PSScriptRoot
$taskFolder=[IO.Path]::GetFullPath((Join-Path $taskRoot $OutputDirectory))
if(-not $taskFolder.StartsWith($taskRoot+[IO.Path]::DirectorySeparatorChar,[StringComparison]::OrdinalIgnoreCase)){throw 'The fixture must stay within this workspace.'}
New-Item -ItemType Directory -Path $taskFolder -Force | Out-Null
$taskHost=Join-Path $taskFolder 'EliteDangerous64.exe'
$taskSource=Join-Path $taskFolder 'host.cpp'
@'
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <unknwn.h>
#include <string>
#include <initializer_list>
int wmain(int argc,wchar_t** argv) {
if(argc!=2)return 2;
const auto module=LoadLibraryW(argv[1]);if(!module)return 3;
using Create=HRESULT(WINAPI*)(HINSTANCE,DWORD,REFIID,LPVOID*,LPUNKNOWN);
const auto create=reinterpret_cast<Create>(GetProcAddress(module,"DirectInput8Create"));if(!create)return 4;
wchar_t system[MAX_PATH]{};GetSystemDirectoryW(system,MAX_PATH);
const auto genuine=LoadLibraryExW((std::wstring(system)+L"\\dinput8.dll").c_str(),nullptr,LOAD_LIBRARY_SEARCH_SYSTEM32);if(!genuine)return 5;
const auto baseline=reinterpret_cast<Create>(GetProcAddress(genuine,"DirectInput8Create"));if(!baseline)return 6;
struct Result{HRESULT result;DWORD last;bool output;};
auto call=[](Create function,DWORD version,DWORD seed){
void* output{};SetLastError(seed);
const auto result=function(GetModuleHandleW(nullptr),version,__uuidof(IUnknown),&output,nullptr);
const auto error=GetLastError();const bool returned=output!=nullptr;
if(output)reinterpret_cast<IUnknown*>(output)->Release();
return Result{result,error,returned};
};
for(const DWORD version:{0x800u,0u}){
const auto expected=call(baseline,version,0x5A5A1234);
const auto actual=call(create,version,0x5A5A1234);
if(expected.result!=actual.result||expected.last!=actual.last||expected.output!=actual.output)return 7;
}
Sleep(20000);return 0;
}
'@ | Set-Content -LiteralPath $taskSource -Encoding utf8
$taskVs=& 'C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe' -latest -products '*' -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
$taskBatch=Join-Path $taskFolder 'build.cmd'
[IO.File]::WriteAllText($taskBatch,"@call `"$taskVs\VC\Auxiliary\Build\vcvars64.bat`" >nul`r`n@cl /nologo /std:c++20 /O2 /MT /EHsc `"$taskSource`" /Fo`"$taskFolder\host.obj`" /link /OUT:`"$taskHost`" ole32.lib`r`n@exit /b %ERRORLEVEL%`r`n")
& $env:ComSpec /d /c $taskBatch
if($LASTEXITCODE -ne 0){throw 'Fixture build failed.'}
$taskDll=Join-Path $taskRoot 'artifacts/plugin-build/dinput8.dll'
if(-not (Test-Path -LiteralPath $taskDll -PathType Leaf)){throw 'Build the diagnostic loader first: scripts/Build-Plugin.ps1 -ExportDiagnostics.'}
$taskProcess=Start-Process -FilePath $taskHost -ArgumentList @(('"'+$taskDll+'"')) -WindowStyle Hidden -PassThru
$taskReplies=@()
try {
for($taskIndex=0;$taskIndex -lt 23;$taskIndex++) {
$taskPipe=[IO.Pipes.NamedPipeClientStream]::new('.','XFEEDToolBox.Plugin.'+$taskProcess.Id,[IO.Pipes.PipeDirection]::InOut,[IO.Pipes.PipeOptions]::Asynchronous)
try {
$taskPipe.Connect(2000)
$taskText=switch($taskIndex){20{"navigation.observe.start`n"}21{"deactivate`n"}default{"status`n"}}
$taskCommand=[Text.Encoding]::UTF8.GetBytes($taskText);$taskPipe.Write($taskCommand,0,$taskCommand.Length);$taskPipe.Flush()
$taskReader=[IO.StreamReader]::new($taskPipe,[Text.Encoding]::UTF8,$false,4096,$true)
$taskJson=$taskReader.ReadLineAsync().WaitAsync([TimeSpan]::FromSeconds(2)).GetAwaiter().GetResult()
$taskReply=$taskJson|ConvertFrom-Json
if($taskReply.processId -ne $taskProcess.Id -or -not $taskReply.loaded -or $taskReply.active -ne ($taskIndex -lt 21) -or $taskReply.observationStarted -or $taskReply.supportsNavigation -or $taskReply.supportsTrade -or $taskReply.supportsRelog){throw 'Unexpected fixture capability or identity.'}
$taskReplies+=$taskReply
} finally {$taskPipe.Dispose()}
}
[ordered]@{fixtureExecutable=$taskHost;pluginSha256=(Get-FileHash -LiteralPath $taskDll -Algorithm SHA256).Hash;version=$taskReplies[0].version;statusReplies=20;lifecycleReplies=3;directInputReturnAndLastErrorMatched=$true;passed=$true;commands=@('status','navigation.observe.start','deactivate','status');actualGameTouched=$false}|ConvertTo-Json|Set-Content -LiteralPath (Join-Path $taskFolder 'result.json') -Encoding utf8
Get-Content -LiteralPath (Join-Path $taskFolder 'result.json')
} finally {
$taskObserved=Get-Process -Id $taskProcess.Id -ErrorAction SilentlyContinue
if($taskObserved -and [string]::Equals($taskObserved.Path,$taskHost,[StringComparison]::OrdinalIgnoreCase)){Stop-Process -Id $taskObserved.Id -ErrorAction SilentlyContinue}
}