Add cockpit deployment package + single-instance guard
deploy/ builds a single zip for TeslaConsole: the self-contained tray app and the signed ViGEmBus installer under a riojoy\ folder, with postinstall.bat at the root. postinstall elevates and runs riojoy\install-rio.ps1, which installs ViGEmBus silently (if absent) and registers RIOJoy to start at logon — no cert trust, test signing, Secure Boot change, or reboot. Also guard against two instances fighting over the COM port (machine-wide + per-user autostart both firing): a per-session named mutex makes a second launch exit immediately. Built zips (dist/) and the bundled installer (deploy/vendor/) are git-ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,10 @@
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||
|
||||
# RIOJoy deployment: built zips and bundled third-party installers (not source).
|
||||
/dist/
|
||||
/deploy/vendor/
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
RIOJoy — cockpit deployment package
|
||||
====================================
|
||||
|
||||
This package deploys RIOJoy to a single directory (e.g. C:\games\RIOJOY) and runs
|
||||
all local installation steps from postinstall.bat. It uses the signed ViGEmBus
|
||||
virtual-controller driver, so there is NO test signing, NO Secure Boot change, and
|
||||
NO reboot.
|
||||
|
||||
Contents
|
||||
--------
|
||||
postinstall.bat Entry point (at the root). Elevates, then runs
|
||||
riojoy\install-rio.ps1.
|
||||
riojoy\ The payload folder:
|
||||
app\ The RIOJoy tray application (self-contained .NET 8 — no runtime
|
||||
install required on the target machine).
|
||||
vendor\ The signed ViGEmBus installer (Nefarius, WHQL/attestation-signed).
|
||||
install-rio.ps1 The actual install steps (idempotent).
|
||||
VERSION.txt Build stamp (date + git short SHA).
|
||||
|
||||
What postinstall.bat does
|
||||
-------------------------
|
||||
1. Elevates to administrator (UAC) if not already elevated.
|
||||
2. Installs ViGEmBus silently if its driver service is not already present.
|
||||
3. Registers RIOJoy to start at logon (HKLM ...\Run, machine-wide).
|
||||
4. Launches RIOJoy.
|
||||
|
||||
Deploying with TeslaConsole
|
||||
---------------------------
|
||||
1. Extract this zip so that postinstall.bat and the riojoy\ folder sit together
|
||||
(e.g. under C:\games\, giving C:\games\postinstall.bat and C:\games\riojoy\).
|
||||
2. Run postinstall.bat ELEVATED (TeslaConsole should run it as administrator).
|
||||
It is idempotent — re-running it is safe (e.g. for updates). The app and
|
||||
ViGEmBus install from the riojoy\ folder; install-rio.ps1 locates itself, so
|
||||
the riojoy\ folder can live wherever it is extracted.
|
||||
|
||||
After install
|
||||
-------------
|
||||
* RIOJoy runs in the system tray. Right-click it to pick/edit profiles, set the
|
||||
auto-switch executables, and toggle output to the PC.
|
||||
* The RIO's joystick output appears to games as a standard "Xbox 360 Controller"
|
||||
(verify in joy.cpl). Note the XInput layout limits joystick BUTTONS to 11
|
||||
(A, B, X, Y, LB, RB, Back, Start, L3, R3, Guide); bind everything else to the
|
||||
keyboard. The hat maps to the D-pad; axes map to the sticks and triggers.
|
||||
* Per-machine config lives in %APPDATA%\RIOJoy\config.json for the running user.
|
||||
|
||||
Building this package (on a dev machine)
|
||||
-----------------------------------------
|
||||
Requires the .NET 8 SDK and the signed ViGEmBus installer.
|
||||
|
||||
powershell -ExecutionPolicy Bypass -File deploy\build-package.ps1 `
|
||||
-VigemInstaller <path-to-ViGEmBus_x.y.z_x64_x86_arm64.exe>
|
||||
|
||||
(Or drop ViGEmBus_*.exe into deploy\vendor\ and omit -VigemInstaller.)
|
||||
Output: dist\RIOJoy-<version>.zip
|
||||
@@ -0,0 +1,103 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build the RIOJoy cockpit deployment zip: publish the tray app self-contained,
|
||||
bundle the signed ViGEmBus installer + the post-install scripts, and zip it for
|
||||
TeslaConsole. The zip extracts directly into a single directory (C:\games\RIOJOY).
|
||||
|
||||
.PARAMETER VigemInstaller
|
||||
Path to the signed ViGEmBus installer to bundle. If omitted, looks in
|
||||
deploy\vendor\ViGEmBus*.exe.
|
||||
.PARAMETER OutDir
|
||||
Where to write the zip (default: dist, relative to the repo root).
|
||||
.PARAMETER Configuration
|
||||
Build configuration (default: Release).
|
||||
#>
|
||||
param(
|
||||
[string]$VigemInstaller,
|
||||
[string]$OutDir = 'dist',
|
||||
[string]$Configuration = 'Release'
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = Split-Path $PSScriptRoot -Parent # deploy\ -> repo root
|
||||
$staging = Join-Path ([IO.Path]::GetTempPath()) "riojoy-pkg-$([Guid]::NewGuid().ToString('N'))"
|
||||
|
||||
Write-Host '== RIOJoy deployment package ==' -ForegroundColor Cyan
|
||||
|
||||
# --- version stamp --------------------------------------------------------
|
||||
$sha = (& git -C $repo rev-parse --short HEAD 2>$null)
|
||||
if (-not $sha) { $sha = 'nogit' }
|
||||
$version = "{0}-{1}" -f (Get-Date -Format 'yyyyMMdd'), $sha
|
||||
|
||||
# --- locate the ViGEmBus installer ----------------------------------------
|
||||
if (-not $VigemInstaller) {
|
||||
$VigemInstaller = Get-ChildItem -Path (Join-Path $PSScriptRoot 'vendor') -Filter 'ViGEmBus*.exe' `
|
||||
-ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName
|
||||
}
|
||||
if (-not $VigemInstaller -or -not (Test-Path $VigemInstaller)) {
|
||||
throw "ViGEmBus installer not found. Pass -VigemInstaller <path>, or drop ViGEmBus_*.exe into deploy\vendor\. Get it from https://github.com/nefarius/ViGEmBus/releases."
|
||||
}
|
||||
$sig = Get-AuthenticodeSignature $VigemInstaller
|
||||
if ($sig.Status -ne 'Valid') { throw "ViGEmBus installer is not validly signed ($($sig.Status)): $VigemInstaller" }
|
||||
Write-Host "ViGEmBus installer: $(Split-Path $VigemInstaller -Leaf) (signature $($sig.Status))"
|
||||
|
||||
try {
|
||||
# The payload lives in a 'riojoy' folder; postinstall.bat sits beside it at the
|
||||
# zip root (TeslaConsole runs it; it calls riojoy\install-rio.ps1).
|
||||
$pkgDir = Join-Path $staging 'riojoy'
|
||||
New-Item -ItemType Directory -Force -Path $pkgDir | Out-Null
|
||||
|
||||
# 1. Publish the tray app into riojoy\app (self-contained win-x64 — no .NET on target).
|
||||
$appOut = Join-Path $pkgDir 'app'
|
||||
Write-Host "Publishing RioJoy.Tray ($Configuration, self-contained win-x64)..."
|
||||
& dotnet publish (Join-Path $repo 'src\RioJoy.Tray\RioJoy.Tray.csproj') `
|
||||
-c $Configuration -r win-x64 --self-contained true `
|
||||
-p:PublishSingleFile=false -p:DebugType=none `
|
||||
-o $appOut | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { throw 'dotnet publish failed.' }
|
||||
|
||||
# 2. Bundle the signed ViGEmBus installer into riojoy\vendor.
|
||||
$vendorOut = Join-Path $pkgDir 'vendor'
|
||||
New-Item -ItemType Directory -Force -Path $vendorOut | Out-Null
|
||||
Copy-Item $VigemInstaller $vendorOut
|
||||
|
||||
# 3. Install script + readme + version stamp inside riojoy\; postinstall.bat at root.
|
||||
Copy-Item (Join-Path $PSScriptRoot 'install-rio.ps1') $pkgDir
|
||||
Copy-Item (Join-Path $PSScriptRoot 'README-DEPLOY.txt') $pkgDir
|
||||
Set-Content -Path (Join-Path $pkgDir 'VERSION.txt') -Value "RIOJoy $version" -Encoding utf8
|
||||
Copy-Item (Join-Path $PSScriptRoot 'postinstall.bat') $staging
|
||||
|
||||
# 4. Zip the package contents (so it extracts straight into C:\games\RIOJOY).
|
||||
$outDirFull = if ([IO.Path]::IsPathRooted($OutDir)) { $OutDir } else { Join-Path $repo $OutDir }
|
||||
New-Item -ItemType Directory -Force -Path $outDirFull | Out-Null
|
||||
$zip = Join-Path $outDirFull "RIOJoy-$version.zip"
|
||||
if (Test-Path $zip) { Remove-Item $zip -Force }
|
||||
Write-Host "Zipping -> $zip"
|
||||
# Build entries by hand with forward-slash names: both Compress-Archive and
|
||||
# ZipFile.CreateFromDirectory write BACKSLASH separators under Windows PowerShell
|
||||
# (.NET Framework), which some extractors mishandle. Entries are relative to the
|
||||
# staging root, so the zip extracts straight into the target (C:\games\RIOJOY\...).
|
||||
Add-Type -AssemblyName System.IO.Compression
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
$base = (Resolve-Path $staging).Path.TrimEnd('\') + '\'
|
||||
$fs = [System.IO.File]::Open($zip, [System.IO.FileMode]::CreateNew)
|
||||
try {
|
||||
$archive = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create)
|
||||
try {
|
||||
foreach ($file in Get-ChildItem -Path $staging -Recurse -File) {
|
||||
$entryName = $file.FullName.Substring($base.Length) -replace '\\', '/'
|
||||
[System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
|
||||
$archive, $file.FullName, $entryName,
|
||||
[System.IO.Compression.CompressionLevel]::Optimal) | Out-Null
|
||||
}
|
||||
} finally { $archive.Dispose() }
|
||||
} finally { $fs.Dispose() }
|
||||
|
||||
$size = '{0:N1} MB' -f ((Get-Item $zip).Length / 1MB)
|
||||
Write-Host ''
|
||||
Write-Host "Package built: $zip ($size)" -ForegroundColor Green
|
||||
Write-Host 'Deploy: extract (postinstall.bat + riojoy\), then run postinstall.bat (elevated).'
|
||||
}
|
||||
finally {
|
||||
if (Test-Path $staging) { Remove-Item $staging -Recurse -Force }
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
RIOJoy cockpit post-install. Installs the signed ViGEmBus virtual-controller
|
||||
driver (if absent) and sets RIOJoy to start at logon. Must run elevated
|
||||
(postinstall.bat elevates for you). Idempotent — safe to re-run.
|
||||
|
||||
.DESCRIPTION
|
||||
Run from inside the deployed package directory (e.g. C:\games\RIOJOY). Paths
|
||||
are resolved relative to this script, so the layout is:
|
||||
|
||||
<root>\app\RioJoy.Tray.exe the published tray app (self-contained)
|
||||
<root>\vendor\ViGEmBus_*.exe the signed ViGEmBus installer
|
||||
<root>\install-rio.ps1 (this file)
|
||||
|
||||
ViGEmBus is WHQL/attestation-signed: it installs with NO test signing, NO
|
||||
Secure Boot change, and NO reboot. The RIO appears to games as a standard
|
||||
Xbox 360 controller.
|
||||
|
||||
.PARAMETER NoLaunch
|
||||
Do not start the app after install (it still starts at the next logon).
|
||||
#>
|
||||
param([switch]$NoLaunch)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$root = $PSScriptRoot
|
||||
$appExe = Join-Path $root 'app\RioJoy.Tray.exe'
|
||||
$vendorDir = Join-Path $root 'vendor'
|
||||
$runValueName = 'RIOJoy'
|
||||
|
||||
# --- guards ---------------------------------------------------------------
|
||||
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
||||
throw 'install-rio.ps1 must run elevated. Run postinstall.bat (it elevates).'
|
||||
}
|
||||
if (-not (Test-Path $appExe)) { throw "App not found: $appExe (is the package complete?)." }
|
||||
|
||||
Write-Host '== RIOJoy post-install ==' -ForegroundColor Cyan
|
||||
|
||||
# 1. Install ViGEmBus if its driver service is not already present.
|
||||
$svc = Get-Service -Name ViGEmBus -ErrorAction SilentlyContinue
|
||||
if ($svc) {
|
||||
Write-Host "ViGEmBus already installed (service is $($svc.Status))."
|
||||
} else {
|
||||
$installer = Get-ChildItem -Path $vendorDir -Filter 'ViGEmBus*.exe' -ErrorAction SilentlyContinue |
|
||||
Select-Object -First 1
|
||||
if ($installer) {
|
||||
Write-Host "Installing ViGEmBus ($($installer.Name))..."
|
||||
$p = Start-Process -FilePath $installer.FullName `
|
||||
-ArgumentList '/install', '/quiet', '/norestart' -Wait -PassThru
|
||||
if ($p.ExitCode -notin 0, 3010) {
|
||||
throw "ViGEmBus install failed (exit code $($p.ExitCode))."
|
||||
}
|
||||
Write-Host 'ViGEmBus installed.'
|
||||
} else {
|
||||
Write-Warning "ViGEmBus installer not found in $vendorDir — joystick output will be"
|
||||
Write-Warning "unavailable until ViGEmBus is installed (keyboard/mouse still work)."
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Start RIOJoy at logon (machine-wide Run key -> runs in each user's session).
|
||||
Write-Host 'Registering RIOJoy to start at logon...'
|
||||
$runKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run'
|
||||
New-ItemProperty -Path $runKey -Name $runValueName -Value "`"$appExe`"" -PropertyType String -Force | Out-Null
|
||||
|
||||
# --- summary --------------------------------------------------------------
|
||||
Write-Host ''
|
||||
Write-Host "RIOJoy installed to $root" -ForegroundColor Green
|
||||
Write-Host 'RIOJoy starts automatically at logon.'
|
||||
if (-not $NoLaunch) {
|
||||
Write-Host 'Launching RIOJoy...'
|
||||
# Launch via explorer so the tray app runs de-elevated in the user session.
|
||||
Start-Process -FilePath (Join-Path $env:WINDIR 'explorer.exe') -ArgumentList $appExe
|
||||
}
|
||||
exit 0
|
||||
@@ -0,0 +1,29 @@
|
||||
@echo off
|
||||
rem ===========================================================================
|
||||
rem RIOJoy cockpit post-install entry point (run by TeslaConsole).
|
||||
rem Sits beside the 'riojoy' payload folder; elevates, then runs
|
||||
rem riojoy\install-rio.ps1.
|
||||
rem ===========================================================================
|
||||
setlocal
|
||||
title RIOJoy post-install
|
||||
|
||||
rem --- elevate if we are not already administrator --------------------------
|
||||
net session >nul 2>&1
|
||||
if %errorlevel% neq 0 (
|
||||
echo Requesting administrator privileges...
|
||||
powershell -NoProfile -Command "Start-Process -FilePath '%~f0' -Verb RunAs"
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
cd /d "%~dp0"
|
||||
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0riojoy\install-rio.ps1"
|
||||
set RC=%errorlevel%
|
||||
|
||||
echo.
|
||||
if %RC% neq 0 (
|
||||
echo postinstall FAILED with exit code %RC%.
|
||||
) else (
|
||||
echo postinstall completed.
|
||||
)
|
||||
exit /b %RC%
|
||||
@@ -2,6 +2,12 @@ namespace RioJoy.Tray;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
// Per-session single-instance guard. Two instances would both grab the RIO COM
|
||||
// port, so a second launch (e.g. machine-wide + per-user autostart both firing)
|
||||
// must exit immediately. A named mutex is released by the OS if the owner dies,
|
||||
// so a crash never leaves a stale lock.
|
||||
private const string SingleInstanceMutex = "RIOJoy.Tray.SingleInstance";
|
||||
|
||||
/// <summary>
|
||||
/// Entry point. RIOJoy runs as a background tray application with no main
|
||||
/// window: an ApplicationContext owns the NotifyIcon and the runtime, so the
|
||||
@@ -10,6 +16,10 @@ internal static class Program
|
||||
[STAThread]
|
||||
private static void Main()
|
||||
{
|
||||
using var instance = new Mutex(initiallyOwned: true, SingleInstanceMutex, out bool createdNew);
|
||||
if (!createdNew)
|
||||
return; // another RIOJoy is already running in this session
|
||||
|
||||
ApplicationConfiguration.Initialize();
|
||||
Application.Run(new TrayApplicationContext());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user