# 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-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 } # 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) New-Item -ItemType Directory -Force -Path $DestModDir | Out-Null # Fail fast with a clear message if a DLL we're about to overwrite is locked # (game still running) — before touching anything. foreach ($name in @("S3.dll", "S3Native.dll")) { $path = Join-Path $DestModDir $name if (Test-Path $path) { try { [System.IO.File]::Open($path, 'Open', 'ReadWrite', 'None').Close() } catch { throw "$name is locked - close Railroader before installing." } } } # 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 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 } } }