190 lines
6.2 KiB
PowerShell
Executable File
190 lines
6.2 KiB
PowerShell
Executable File
<#
|
|
.SYNOPSIS
|
|
symlink_downloads.ps1 -- place in the "Sven Co-op" root folder alongside tags.json.
|
|
|
|
.DESCRIPTION
|
|
1. Scans svencoop_downloads\models\player\ for .mdl files whose name matches a
|
|
model ID in tags.json (case-insensitive).
|
|
2. Creates a relative symlink in svencoop_addon\models\player\ for each match:
|
|
<id>\<id>.mdl -> ..\..\..\..\svencoop\models\player\helmet\helmet.mdl
|
|
3. Lists matched folders and prompts to delete them from svencoop_downloads.
|
|
The player\ root itself is never deleted.
|
|
|
|
.NOTES
|
|
Requires PowerShell 5.1+ and either:
|
|
- Developer Mode enabled (Settings > For developers)
|
|
- Running as Administrator
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$DryRun
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
# --- Paths ---
|
|
$scriptDir = $PSScriptRoot
|
|
$tagsPath = Join-Path $scriptDir "tags.json"
|
|
$helmetAbs = Join-Path $scriptDir "svencoop\models\player\helmet\helmet.mdl"
|
|
$dlRoot = Join-Path $scriptDir "svencoop_downloads\models\player"
|
|
$addonRoot = Join-Path $scriptDir "svencoop_addon\models\player"
|
|
$symlinkTarget = "..\..\..\..\svencoop\models\player\helmet\helmet.mdl"
|
|
|
|
# --- Pre-flight checks ---
|
|
if (-not (Test-Path $tagsPath)) {
|
|
Write-Error "tags.json not found next to script ($tagsPath)"
|
|
exit 1
|
|
}
|
|
if (-not (Test-Path $helmetAbs)) {
|
|
Write-Error "helmet.mdl not found: $helmetAbs`nRun from the Sven Co-op root folder."
|
|
exit 1
|
|
}
|
|
if (-not (Test-Path $dlRoot)) {
|
|
Write-Error "Downloads folder not found: $dlRoot"
|
|
exit 1
|
|
}
|
|
|
|
# Check for symlink privilege on Windows
|
|
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
|
|
[Security.Principal.WindowsBuiltInRole]::Administrator
|
|
)
|
|
if (-not $isAdmin) {
|
|
$devMode = $false
|
|
try {
|
|
$dm = Get-ItemProperty -Path "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
|
|
"@
|
|
}
|
|
}
|
|
|
|
# --- Build case-insensitive tag lookup: mdl stem -> canonical ID ---
|
|
$tags = Get-Content $tagsPath -Raw -Encoding UTF8 | ConvertFrom-Json
|
|
$tagLookup = @{}
|
|
foreach ($category in $tags.PSObject.Properties) {
|
|
foreach ($id in $category.Value) {
|
|
$tagLookup[$id] = $id
|
|
}
|
|
}
|
|
|
|
# --- Scan downloads for matching .mdl files ---
|
|
$folderMdls = @{}
|
|
$matched = [System.Collections.Generic.List[hashtable]]::new()
|
|
|
|
Get-ChildItem -Path $dlRoot -Recurse -Filter "*.mdl" | ForEach-Object {
|
|
$dir = $_.DirectoryName
|
|
if ($dir -eq $dlRoot) { return }
|
|
|
|
if (-not $folderMdls.ContainsKey($dir)) { $folderMdls[$dir] = @() }
|
|
$folderMdls[$dir] += $_
|
|
|
|
$tagsId = $tagLookup[$_.BaseName]
|
|
if ($tagsId) {
|
|
$matched.Add(@{ File = $_; TagsId = $tagsId })
|
|
}
|
|
}
|
|
|
|
if ($matched.Count -eq 0) {
|
|
Write-Host "No downloaded .mdl files matched any model ID in tags.json."
|
|
exit 0
|
|
}
|
|
|
|
# --- Symlink pass ---
|
|
$prefix = if ($DryRun) { "[DRY RUN] " } else { "" }
|
|
Write-Host "${prefix}Symlinking $($matched.Count) matched model(s):`n"
|
|
|
|
$counts = @{ created = 0; replaced = 0; ok = 0; error = 0 }
|
|
|
|
foreach ($m in $matched) {
|
|
$tagsId = $m.TagsId
|
|
$addonMdl = Join-Path $addonRoot "$tagsId\$tagsId.mdl"
|
|
$addonDir = Split-Path $addonMdl -Parent
|
|
|
|
if ($DryRun) {
|
|
$link = Get-Item $addonMdl -ErrorAction SilentlyContinue
|
|
if ($link -and $link.LinkType -and $link.Target -eq $symlinkTarget) {
|
|
$status = "ok"
|
|
} else {
|
|
$status = "would link"
|
|
}
|
|
Write-Host (" {0,-12} {1} (from {2})" -f $status, $tagsId, $m.File.FullName.Substring($dlRoot.Length + 1))
|
|
$counts.created++
|
|
continue
|
|
}
|
|
|
|
try {
|
|
if (Test-Path $addonMdl) { Remove-Item $addonMdl -Force }
|
|
if (-not (Test-Path $addonDir)) { New-Item -ItemType Directory -Path $addonDir -Force | Out-Null }
|
|
|
|
$existing = Get-Item $addonMdl -ErrorAction SilentlyContinue
|
|
if ($existing -and $existing.LinkType -and $existing.Target -eq $symlinkTarget) {
|
|
Write-Host " already ok $tagsId"
|
|
$counts.ok++
|
|
} else {
|
|
New-Item -ItemType SymbolicLink -Path $addonMdl -Target $symlinkTarget | Out-Null
|
|
Write-Host " linked $tagsId"
|
|
$counts.created++
|
|
}
|
|
} catch {
|
|
Write-Host " ERROR ${tagsId}: $_"
|
|
$counts.error++
|
|
}
|
|
}
|
|
|
|
if (-not $DryRun) {
|
|
Write-Host ("`n $($counts.created) created, $($counts.replaced) replaced, $($counts.ok) already correct, $($counts.error) errors")
|
|
}
|
|
|
|
# --- Cleanup prompt ---
|
|
$foldersToClean = @{}
|
|
foreach ($m in $matched) {
|
|
$dir = $m.File.DirectoryName
|
|
if (-not $foldersToClean.ContainsKey($dir)) { $foldersToClean[$dir] = @() }
|
|
$foldersToClean[$dir] += $m.File
|
|
}
|
|
|
|
Write-Host "`nFolders to delete from svencoop_downloads ($($foldersToClean.Count)):`n"
|
|
foreach ($dir in ($foldersToClean.Keys | Sort-Object)) {
|
|
$matchedHere = $foldersToClean[$dir]
|
|
$matchedPaths = $matchedHere | ForEach-Object { $_.FullName }
|
|
$other = $folderMdls[$dir] | Where-Object { $_.FullName -notin $matchedPaths }
|
|
$relDir = $dir.Substring($scriptDir.Length + 1)
|
|
Write-Host " $relDir\"
|
|
foreach ($f in $matchedHere) { Write-Host " [tagged] $($f.Name)" }
|
|
foreach ($f in $other) { Write-Host " [extra ] $($f.Name)" }
|
|
}
|
|
|
|
Write-Host ""
|
|
if ($DryRun) {
|
|
Write-Host "[DRY RUN] Skipping delete prompt."
|
|
exit 0
|
|
}
|
|
|
|
$answer = Read-Host "Delete these folders from svencoop_downloads? [y/N]"
|
|
if ($answer -ne "y") {
|
|
Write-Host "Skipped cleanup."
|
|
exit 0
|
|
}
|
|
|
|
$deleted = 0
|
|
foreach ($dir in ($foldersToClean.Keys | Sort-Object)) {
|
|
if ($dir -eq $dlRoot) {
|
|
Write-Host " SKIP (is player\ root): $dir"
|
|
continue
|
|
}
|
|
try {
|
|
Remove-Item -Path $dir -Recurse -Force
|
|
Write-Host " deleted $($dir.Substring($scriptDir.Length + 1))\"
|
|
$deleted++
|
|
} catch {
|
|
Write-Host " ERROR $([System.IO.Path]::GetFileName($dir)): $_"
|
|
}
|
|
}
|
|
|
|
Write-Host "`nDeleted $deleted folder(s)." |