Files
SymlinkModels/symlink_models.ps1
T
2026-06-22 06:44:43 -04:00

155 lines
5.5 KiB
PowerShell
Executable File

<#
.SYNOPSIS
symlink_models.ps1 -- place in the "Sven Co-op" root folder alongside tags.json.
.DESCRIPTION
Creates a relative symlink for every model:
svencoop_addon\models\player\<id>\<id>.mdl -> ..\..\..\..\ svencoop\models\player\helmet\helmet.mdl
Sources (pick one, or default to tags.json):
default -- model IDs from tags.json, grouped by category
-Server PATH -- scan any svencoop_addon\models\player\ directory for .mdl files
-FromFile FILE -- read one model ID per line from a text file
Dump server model IDs to a file (run on the server):
Get-ChildItem -Path "C:\path\to\svencoop_addon\models\player" -Recurse -Filter "*.mdl" |
Where-Object { $_.DirectoryName -ne "...\player" } |
ForEach-Object { $_.BaseName } | Sort-Object -Unique | Set-Content server_models.txt
.NOTES
Requires PowerShell 5.1+ and either:
- Developer Mode enabled (Settings > For developers)
- Running as Administrator
#>
[CmdletBinding()]
param(
[switch]$DryRun,
[string]$Server,
[string]$FromFile
)
Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"
# --- Paths ---
$scriptDir = $PSScriptRoot
$helmetAbs = Join-Path $scriptDir "svencoop\models\player\helmet\helmet.mdl"
$addonRoot = Join-Path $scriptDir "svencoop_addon\models\player"
$symlinkTarget = "..\..\..\..\svencoop\models\player\helmet\helmet.mdl"
# --- Pre-flight ---
if (-not (Test-Path $helmetAbs)) {
Write-Error "helmet.mdl not found: $helmetAbs`nRun from the Sven Co-op root folder."
exit 1
}
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
[Security.Principal.WindowsBuiltInRole]::Administrator
)
if (-not $isAdmin) {
$devMode = $false
try {
$dm = Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" -ErrorAction SilentlyContinue
$devMode = $dm.AllowDevelopmentWithoutDevLicense -eq 1
} catch {}
if (-not $devMode) {
Write-Warning @"
Symlink creation may fail. To fix, either:
- Enable Developer Mode (Settings > For developers > Developer Mode)
- Re-run this script as Administrator
"@
}
}
# --- Shared: symlink one model ID ---
function Invoke-Symlink {
param([string]$ModelId)
$modelDir = Join-Path $addonRoot $ModelId
$mdlPath = Join-Path $modelDir "$ModelId.mdl"
if (-not (Test-Path $modelDir -PathType Container)) {
if (-not $DryRun) { New-Item -ItemType Directory -Path $modelDir -Force | Out-Null }
}
$item = Get-Item $mdlPath -ErrorAction SilentlyContinue
if ($item -and $item.LinkType) {
if ($item.Target -eq $symlinkTarget) { return "skipped" }
if (-not $DryRun) { Remove-Item $mdlPath -Force }
Write-Host " replace : $ModelId"
$result = "replaced"
} elseif ($item) {
if (-not $DryRun) { Remove-Item $mdlPath -Force }
Write-Host " replace : $ModelId (was real file)"
$result = "replaced"
} else {
Write-Host " create : $ModelId"
$result = "created"
}
if (-not $DryRun) {
try {
New-Item -ItemType SymbolicLink -Path $mdlPath -Target $symlinkTarget | Out-Null
} catch {
Write-Host " ERROR : $ModelId -- $_"
return "error"
}
}
return $result
}
function Write-Summary {
param([hashtable]$Counts)
$total = $Counts.created + $Counts.replaced
$prefix = if ($DryRun) { "[DRY RUN] " } else { "" }
$verb = if ($DryRun) { "would be written" } else { "written" }
$errors = if ($Counts.error) { ", $($Counts.error) errors" } else { "" }
Write-Host "`n${prefix}$total symlinks $verb ($($Counts.created) new, $($Counts.replaced) replaced), $($Counts.skipped) already correct${errors}"
}
$counts = @{ created = 0; replaced = 0; skipped = 0; error = 0 }
# --- -Server: scan a player/ directory ---
if ($Server) {
if (-not (Test-Path $Server -PathType Container)) {
Write-Error "Server path not found: $Server"
exit 1
}
$modelIds = Get-ChildItem -Path $Server -Recurse -Filter "*.mdl" |
Where-Object { $_.DirectoryName -ne $Server } |
ForEach-Object { $_.BaseName } |
Sort-Object -Unique
Write-Host "`n[server] -- $($modelIds.Count) models found in $Server`n"
foreach ($id in $modelIds) { $counts[(Invoke-Symlink $id)]++ }
Write-Summary $counts
return
}
# --- -FromFile: read model IDs from a text file ---
if ($FromFile) {
if (-not (Test-Path $FromFile)) {
Write-Error "File not found: $FromFile"
exit 1
}
$modelIds = Get-Content $FromFile | Where-Object { $_.Trim() -ne "" } | ForEach-Object { $_.Trim() }
Write-Host "`n[from file] -- $($modelIds.Count) models in $FromFile`n"
foreach ($id in $modelIds) { $counts[(Invoke-Symlink $id)]++ }
Write-Summary $counts
return
}
# --- Default: tags.json ---
$tagsPath = Join-Path $scriptDir "tags.json"
if (-not (Test-Path $tagsPath)) {
Write-Error "tags.json not found next to script ($tagsPath)"
exit 1
}
$tags = Get-Content $tagsPath -Raw -Encoding UTF8 | ConvertFrom-Json
foreach ($category in $tags.PSObject.Properties) {
Write-Host "`n[$($category.Name)] -- $($category.Value.Count) models"
foreach ($id in $category.Value) { $counts[(Invoke-Symlink $id)]++ }
}
Write-Summary $counts