Loopback MCP host with observe/control/develop gates and a hot-reload tools DLL. Default token is local-dev only. Leave the module off unless you are driving S3 from an agent.
154 lines
6.4 KiB
PowerShell
154 lines
6.4 KiB
PowerShell
# Shared build helpers for S³ (dot-sourced by build-local.ps1 and build-release.ps1).
|
|
# Not run directly.
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
$script:RepoRoot = Split-Path $PSScriptRoot -Parent
|
|
$script:ModId = "S3"
|
|
|
|
function Get-ModVersion {
|
|
# Single source of truth: the Version field in Info.json.
|
|
$info = Get-Content (Join-Path $RepoRoot "Info.json") -Raw | ConvertFrom-Json
|
|
return $info.Version
|
|
}
|
|
|
|
function Invoke-ManagedBuild {
|
|
param([string]$Configuration = "Release", [string]$GameDir = "")
|
|
|
|
Write-Host "=== Building managed (S3.dll, $Configuration) ===" -ForegroundColor Cyan
|
|
$csproj = Join-Path $RepoRoot "src\S3.csproj"
|
|
$args = @($csproj, "-c", $Configuration, "--nologo", "-v", "minimal")
|
|
if ($GameDir) { $args += "/p:GameDir=$GameDir" }
|
|
dotnet build @args | Out-Host
|
|
if ($LASTEXITCODE -ne 0) { throw "Managed build failed." }
|
|
|
|
$dll = Join-Path $RepoRoot "src\bin\$Configuration\S3.dll"
|
|
if (-not (Test-Path $dll)) { throw "S3.dll not found at $dll" }
|
|
return $dll
|
|
}
|
|
|
|
function Invoke-McpToolsBuild {
|
|
param([string]$Configuration = "Release", [string]$GameDir = "")
|
|
|
|
Write-Host "=== Building MCP tools (S3.Mcp.Tools.dll, $Configuration) ===" -ForegroundColor Cyan
|
|
$csproj = Join-Path $RepoRoot "mcp-tools\S3.Mcp.Tools.csproj"
|
|
$args = @($csproj, "-c", $Configuration, "--nologo", "-v", "minimal")
|
|
if ($GameDir) { $args += "/p:GameDir=$GameDir" }
|
|
dotnet build @args | Out-Host
|
|
if ($LASTEXITCODE -ne 0) { throw "MCP tools build failed." }
|
|
}
|
|
|
|
function Invoke-NativeBuild {
|
|
param([string]$Configuration = "Release")
|
|
|
|
$cmakeLists = Join-Path $RepoRoot "native\CMakeLists.txt"
|
|
if (-not (Test-Path $cmakeLists)) {
|
|
Write-Host "=== No native/CMakeLists.txt — skipping native build ===" -ForegroundColor DarkYellow
|
|
return $null
|
|
}
|
|
|
|
Write-Host "=== Building native (S3Native.dll, $Configuration) ===" -ForegroundColor Cyan
|
|
$buildDir = Join-Path $RepoRoot "native\build"
|
|
if (-not (Test-Path $buildDir)) { cmake -B $buildDir -A x64 (Join-Path $RepoRoot "native") | Out-Host }
|
|
cmake --build $buildDir --config $Configuration | Out-Host
|
|
if ($LASTEXITCODE -ne 0) { throw "Native build failed." }
|
|
|
|
# MSVC multi-config generators place the DLL in a per-config subfolder.
|
|
$dll = Join-Path $buildDir "bin\$Configuration\S3Native.dll"
|
|
if (-not (Test-Path $dll)) { $dll = Join-Path $buildDir "bin\S3Native.dll" } # single-config fallback
|
|
if (-not (Test-Path $dll)) { throw "S3Native.dll not found under $buildDir\bin" }
|
|
return $dll
|
|
}
|
|
|
|
function Test-ModDllLocked {
|
|
param([Parameter(Mandatory)][string]$Path)
|
|
if (-not (Test-Path $Path)) { return $false }
|
|
try {
|
|
[System.IO.File]::Open($Path, 'Open', 'ReadWrite', 'None').Close()
|
|
return $false
|
|
} catch {
|
|
return $true
|
|
}
|
|
}
|
|
|
|
function Get-LockedModDlls {
|
|
param([Parameter(Mandatory)][string]$DestModDir)
|
|
$locked = @()
|
|
foreach ($name in @("S3.dll", "S3Native.dll")) {
|
|
if (Test-ModDllLocked (Join-Path $DestModDir $name)) { $locked += $name }
|
|
}
|
|
return $locked
|
|
}
|
|
|
|
# Poll until Mods\S3 DLLs are writable. Railroader.exe is the usual locker;
|
|
# we wait on the file itself so a hung Unity process is still detected.
|
|
function Wait-ModUnlocked {
|
|
param(
|
|
[Parameter(Mandatory)][string]$DestModDir,
|
|
[int]$PollSeconds = 2
|
|
)
|
|
$locked = @(Get-LockedModDlls $DestModDir)
|
|
if ($locked.Count -eq 0) { return }
|
|
|
|
Write-Host "=== Waiting for Railroader to close ($($locked -join ', ') locked) ===" -ForegroundColor Yellow
|
|
Write-Host " Close the game; install will run automatically. Ctrl+C to abort." -ForegroundColor DarkYellow
|
|
while ($true) {
|
|
Start-Sleep -Seconds $PollSeconds
|
|
$still = @(Get-LockedModDlls $DestModDir)
|
|
if ($still.Count -eq 0) {
|
|
Write-Host "=== Unlocked, installing ===" -ForegroundColor Green
|
|
return
|
|
}
|
|
$rr = Get-Process -Name "Railroader" -ErrorAction SilentlyContinue
|
|
$hint = if ($rr) { "Railroader.exe still running" } else { "$($still -join ', ') still locked" }
|
|
Write-Host " ... $hint" -ForegroundColor DarkGray
|
|
}
|
|
}
|
|
|
|
# Assembles the Mods\S3 layout into $DestModDir (the S3 folder itself).
|
|
#
|
|
# Overwrites in place rather than wiping the folder. A full wipe is dangerous: if
|
|
# the game is running, a loaded DLL is locked, so the wipe deletes Info.json and
|
|
# the managed DLL, then aborts on the locked native DLL — leaving the mod with no
|
|
# manifest, so it silently vanishes from the loader. Overwriting in place keeps
|
|
# the last-good install if a copy fails, and preserves the user's settings files.
|
|
function New-ModLayout {
|
|
param([Parameter(Mandatory)][string]$DestModDir,
|
|
[Parameter(Mandatory)][string]$ManagedDll,
|
|
[string]$NativeDll = $null,
|
|
[switch]$Wait)
|
|
|
|
New-Item -ItemType Directory -Force -Path $DestModDir | Out-Null
|
|
|
|
if ($Wait) {
|
|
Wait-ModUnlocked -DestModDir $DestModDir
|
|
}
|
|
|
|
# Fail fast with a clear message if a DLL we're about to overwrite is locked
|
|
# (game still running) — before touching anything.
|
|
$locked = @(Get-LockedModDlls $DestModDir)
|
|
if ($locked.Count -gt 0) {
|
|
throw "$($locked -join ', ') is locked - close Railroader before installing (or rerun with -Wait)."
|
|
}
|
|
|
|
# Drop stale artifacts from older builds (the pre-rename native DLL, ngen
|
|
# caches) so they don't linger, but leave Info.json and settings intact.
|
|
$stale = Join-Path $DestModDir "RRPopout.dll"
|
|
if (Test-Path $stale) { Remove-Item $stale -Force }
|
|
Get-ChildItem $DestModDir -Filter "*.cache" -ErrorAction SilentlyContinue | Remove-Item -Force
|
|
|
|
Copy-Item (Join-Path $RepoRoot "Info.json") (Join-Path $DestModDir "Info.json") -Force
|
|
Copy-Item $ManagedDll (Join-Path $DestModDir "S3.dll") -Force
|
|
$icon = Join-Path $RepoRoot "src\Modules\QuickActions\Icons\consist.png"
|
|
if (Test-Path $icon) { Copy-Item $icon (Join-Path $DestModDir "consist.png") -Force }
|
|
if ($NativeDll) {
|
|
Copy-Item $NativeDll (Join-Path $DestModDir "S3Native.dll") -Force
|
|
# Ship ProggyClean.ttf alongside the native DLL so IMGUI_DISABLE_DEFAULT_FONT
|
|
# can load it at runtime without the base85 blob in the binary.
|
|
$fontSrc = Join-Path $RepoRoot "native\fonts\ProggyClean.ttf"
|
|
if (Test-Path $fontSrc) {
|
|
Copy-Item $fontSrc (Join-Path $DestModDir "ProggyClean.ttf") -Force
|
|
}
|
|
}
|
|
}
|