JustPaste.it

RTVModUpdater.ps1

#requires -version 5.1
# RTV Mod Updater -- WPF GUI for checking/updating Road to Vostok mods from ModWorkshop.

# ============================================================================
# DEBUG LOG
# ============================================================================

$_scriptDir = if ($MyInvocation.MyCommand.Path) {
    [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Path)
} else {
    $env:USERPROFILE
}
$script:LogPath = [System.IO.Path]::Combine($_scriptDir, "ModUpdater-debug.log")
if ($script:LogPath -and (Test-Path $script:LogPath)) { Remove-Item $script:LogPath -Force -ErrorAction SilentlyContinue }

function Write-Log {
    param([string]$Msg)
    $line = "[$(Get-Date -Format 'HH:mm:ss.fff')] $Msg"
    Add-Content -Path $script:LogPath -Value $line -ErrorAction SilentlyContinue
    Write-Host $line
}

# ============================================================================
# BACKEND FUNCTIONS
# ============================================================================

function Compare-SemVer {
    param([string]$V1, [string]$V2)
    $parse = {
        param([string]$v)
        $v = $v.TrimStart('v','V').Trim('"',"'")
        $parts = $v.Split('.')
        $major = if ($parts[0] -match '^\d+$') { [int]$parts[0] } else { 0 }
        $minor  = if ($parts.Count -gt 1 -and $parts[1] -match '^\d+$') { [int]$parts[1] } else { 0 }
        $patch  = if ($parts.Count -gt 2 -and $parts[2] -match '^\d+$') { [int]$parts[2] } else { 0 }
        return ($major * 10000) + ($minor * 100) + $patch
    }
    return ([int](& $parse $V1)) - ([int](& $parse $V2))
}

function Expand-VmzFile {
    param([string]$VmzPath, [string]$Destination)
    Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
    try {
        $zip = [System.IO.Compression.ZipFile]::OpenRead($VmzPath)
        if (-not (Test-Path $Destination)) { New-Item -ItemType Directory -Path $Destination -Force | Out-Null }
        foreach ($entry in $zip.Entries) {
            $destPath = [System.IO.Path]::Combine($Destination, $entry.FullName)
            $destDir  = [System.IO.Path]::GetDirectoryName($destPath)
            if (-not (Test-Path $destDir)) { New-Item -ItemType Directory -Path $destDir -Force | Out-Null }
            if ($entry.Length -gt 0) { [System.IO.Compression.ZipFileExtensions]::ExtractToFile($entry, $destPath, $true) }
        }
        $zip.Dispose(); return $true
    } catch { return $false }
}

function Get-VmzModTxt {
    param([string]$VmzPath)
    Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction SilentlyContinue
    try {
        $zip   = [System.IO.Compression.ZipFile]::OpenRead($VmzPath)
        $entry = $zip.GetEntry("mod.txt")
        if ($entry) {
            $reader  = New-Object System.IO.StreamReader($entry.Open())
            $content = $reader.ReadToEnd()
            $reader.Close()
            $zip.Dispose()
            return $content
        }
        $zip.Dispose()
    } catch { }
    return $null
}

function Get-ModInfoFromVmz {
    param([string]$VmzPath)
    $modTxt = Get-VmzModTxt $VmzPath
    if (-not $modTxt) { return $null }
    $info = @{ FilePath = $VmzPath; FileName = [System.IO.Path]::GetFileName($VmzPath)
               Name = ""; Id = ""; Version = "0.0.0"; ModworkshopId = $null }
    foreach ($line in $modTxt -split "`n") {
        $line = $line.Trim()
        if ($line -match '^name\s*=\s*"?([^"]+)"?\s*$')      { $info.Name          = $Matches[1].Trim() }
        if ($line -match '^id\s*=\s*"?([^"]+)"?\s*$')        { $info.Id            = $Matches[1].Trim() }
        if ($line -match '^version\s*=\s*"?([^"]+)"?\s*$')   { $info.Version       = $Matches[1].Trim() }
        if ($line -match '^modworkshop\s*=\s*(\d+)')          { $info.ModworkshopId = [int]$Matches[1] }
    }
    if (-not $info.Id) { $info.Id = [System.IO.Path]::GetFileNameWithoutExtension($VmzPath) }
    return $info
}

function Get-ApiModInfo {
    param([int]$ModId)
    try {
        return Invoke-RestMethod -Uri "https://api.modworkshop.net/mods/$ModId" `
               -Method Get -Headers @{"Accept"="application/json"} -TimeoutSec 15
    } catch { return $null }
}

function Get-ApiLatestFile {
    param([int]$ModId)
    try {
        $resp = Invoke-RestMethod -Uri "https://api.modworkshop.net/mods/$ModId/files" `
                -Method Get -Headers @{"Accept"="application/json"} -TimeoutSec 15
        if ($resp.data -and $resp.data.Count -gt 0) {
            return $resp.data | Sort-Object -Property { [int]$_.id } -Descending | Select-Object -First 1
        }
    } catch { }
    return $null
}

function Get-LocalModFileHashes {
    param([string]$ExtractedDir)
    $hashes = @{}
    Get-ChildItem -Path $ExtractedDir -Recurse -File | Where-Object { $_.Name -ne "mod.txt" } | ForEach-Object {
        $rel = $_.FullName.Substring($ExtractedDir.Length).TrimStart([char]'/',[char]'\').Replace('\','/')
        $hashes[$rel] = (Get-FileHash $_.FullName -Algorithm MD5).Hash
    }
    return $hashes
}

function Invoke-Download {
    param([string]$Url, [string]$OutFile, [int]$MinBytes = 1024)
    try {
        Invoke-WebRequest -Uri $Url -OutFile $OutFile -UseBasicParsing -TimeoutSec 90 -ErrorAction Stop
    } catch {
        Write-Log "Invoke-Download: IWR failed for $Url : $_"
    }
    if (-not (Test-Path $OutFile) -or (Get-Item $OutFile).Length -lt $MinBytes) {
        try {
            $wc = New-Object System.Net.WebClient
            $wc.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
            $wc.DownloadFile($Url, $OutFile)
            $wc.Dispose()
        } catch {
            Write-Log "Invoke-Download: WebClient failed for $Url : $_"
        }
    }
    if (Test-Path $OutFile) {
        $sz = (Get-Item $OutFile).Length
        if ($sz -ge $MinBytes) { return $true }
        Write-Log "Invoke-Download: $OutFile too small ($sz bytes), removing"
        Remove-Item $OutFile -Force -ErrorAction SilentlyContinue
    }
    return $false
}

function Get-7zaExe {
    $tmp   = [System.IO.Path]::GetTempPath()
    $cached = [System.IO.Path]::Combine($tmp, "7za.exe")
    if (Test-Path $cached) {
        $sz = (Get-Item $cached).Length
        if ($sz -gt 200000) {
            Write-Log "Get-7zaExe: Using cached $cached ($sz bytes)"
            return $cached
        }
        Write-Log "Get-7zaExe: Cached 7za.exe too small ($sz bytes), rebuilding"
        Remove-Item $cached -Force -ErrorAction SilentlyContinue
    }

    # Step 1: get 7zr.exe
    $szrPath = [System.IO.Path]::Combine($tmp, "7zr.exe")
    $szrOk   = $false
    if (Test-Path $szrPath) {
        $szrSz = (Get-Item $szrPath).Length
        if ($szrSz -gt 200000) { $szrOk = $true; Write-Log "Get-7zaExe: 7zr.exe already cached ($szrSz bytes)" }
        else { Remove-Item $szrPath -Force -ErrorAction SilentlyContinue }
    }
    if (-not $szrOk) {
        Write-Log "Get-7zaExe: Downloading 7zr.exe ..."
        $szrOk = Invoke-Download "https://github.com/ip7z/7zip/releases/download/26.01/7zr.exe" $szrPath 200000
        if (-not $szrOk) {
            Write-Log "Get-7zaExe: Failed to download 7zr.exe -- cannot bootstrap 7za.exe"
            return $null
        }
        Write-Log "Get-7zaExe: 7zr.exe downloaded OK ($((Get-Item $szrPath).Length) bytes)"
    }

    # Step 2: download 7z2601-extra.7z
    $extraPath = [System.IO.Path]::Combine($tmp, "7z-extra.7z")
    $extraOk   = $false
    if (Test-Path $extraPath) {
        $exSz = (Get-Item $extraPath).Length
        if ($exSz -gt 500000) { $extraOk = $true; Write-Log "Get-7zaExe: 7z-extra.7z already cached ($exSz bytes)" }
        else { Remove-Item $extraPath -Force -ErrorAction SilentlyContinue }
    }
    if (-not $extraOk) {
        Write-Log "Get-7zaExe: Downloading 7z2601-extra.7z ..."
        $extraOk = Invoke-Download "https://github.com/ip7z/7zip/releases/download/26.01/7z2601-extra.7z" $extraPath 500000
        if (-not $extraOk) {
            Write-Log "Get-7zaExe: Failed to download 7z-extra.7z -- cannot bootstrap 7za.exe"
            return $null
        }
        Write-Log "Get-7zaExe: 7z-extra.7z downloaded OK ($((Get-Item $extraPath).Length) bytes)"
    }

    # Step 3: extract 7za.exe
    Write-Log "Get-7zaExe: Extracting 7za.exe from 7z-extra.7z using 7zr.exe ..."
    $extractDir = [System.IO.Path]::Combine($tmp, "7z-extra-extracted")
    if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue }
    New-Item -ItemType Directory -Path $extractDir -Force | Out-Null
    $outArg = "-o" + $extractDir
    try {
        $proc = Start-Process -FilePath $szrPath `
            -ArgumentList "x", "-y", $outArg, $extraPath `
            -Wait -PassThru -WindowStyle Hidden -ErrorAction Stop
        Write-Log "Get-7zaExe: 7zr.exe extract exit=$($proc.ExitCode)"
    } catch {
        Write-Log "Get-7zaExe: 7zr.exe extract threw: $_"
        return $null
    }
    $found = Get-ChildItem -Path $extractDir -Recurse -Filter "7za.exe" -ErrorAction SilentlyContinue |
        Where-Object { $_.Length -gt 200000 } | Select-Object -First 1
    if ($found) {
        Copy-Item -Path $found.FullName -Destination $cached -Force
        Write-Log "Get-7zaExe: 7za.exe extracted and cached at $cached ($((Get-Item $cached).Length) bytes)"
        Remove-Item $extractDir -Recurse -Force -ErrorAction SilentlyContinue
        return $cached
    }
    Write-Log "Get-7zaExe: 7za.exe not found in extracted archive contents"
    return $null
}

function Expand-RarFile {
    param([string]$RarPath, [string]$Destination)

    if (-not (Test-Path $Destination)) { New-Item -ItemType Directory -Path $Destination -Force | Out-Null }

    # 1. 7za.exe
    $szr = Get-7zaExe
    if ($szr -and (Test-Path $szr)) {
        Write-Log "Expand-RarFile: Trying 7za.exe at $szr"
        $stdoutLog = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "7za-stdout.txt")
        $stderrLog = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "7za-stderr.txt")
        $outArg = "-o" + $Destination
        Write-Log "Expand-RarFile: 7za args: x -y `"$outArg`" `"$RarPath`""
        try {
            $proc = Start-Process -FilePath $szr `
                -ArgumentList "x", "-y", $outArg, $RarPath `
                -Wait -PassThru -WindowStyle Hidden `
                -RedirectStandardOutput $stdoutLog `
                -RedirectStandardError  $stderrLog `
                -ErrorAction Stop
            $exitCode = $proc.ExitCode
            $stdout = if (Test-Path $stdoutLog) { Get-Content $stdoutLog -Raw -ErrorAction SilentlyContinue } else { "" }
            $stderr = if (Test-Path $stderrLog) { Get-Content $stderrLog -Raw -ErrorAction SilentlyContinue } else { "" }
            Write-Log "Expand-RarFile: 7za.exe exit=$exitCode"
            if ($stdout) { Write-Log "Expand-RarFile: 7za stdout: $($stdout.Trim())" }
            if ($stderr) { Write-Log "Expand-RarFile: 7za stderr: $($stderr.Trim())" }
        } catch {
            Write-Log "Expand-RarFile: 7za.exe Start-Process threw: $_"
            try {
                $outArg2 = '-o"' + $Destination + '"'
                $result2 = & $szr x -y $outArg2 $RarPath 2>&1
                Write-Log "Expand-RarFile: 7za.exe direct invoke result: $result2"
            } catch {
                Write-Log "Expand-RarFile: 7za.exe direct invoke also failed: $_"
            }
        }
        $extractedFiles = @(Get-ChildItem -Path $Destination -Recurse -File -ErrorAction SilentlyContinue |
            Where-Object { $_.Extension -notin @('.rar', '.zip', '.7z') })
        $fileCount = $extractedFiles.Count
        Write-Log "Expand-RarFile: After 7za.exe, extracted file count (excl. archives) = $fileCount"
        if ($fileCount -gt 0) {
            Write-Log "Expand-RarFile: 7za.exe succeeded"
            return $true
        }
        Write-Log "Expand-RarFile: 7za.exe produced no extracted files"
    }

    # 2. WinRAR
    foreach ($winrar in @(
        "$env:ProgramFiles\WinRAR\WinRAR.exe",
        "${env:ProgramFiles(x86)}\WinRAR\WinRAR.exe",
        "$env:LOCALAPPDATA\Programs\WinRAR\WinRAR.exe"
    )) {
        if (Test-Path $winrar) {
            Write-Log "Expand-RarFile: Trying WinRAR at $winrar"
            & $winrar x -y -o+ $RarPath $Destination | Out-Null
            $wxCount = @(Get-ChildItem -Path $Destination -Recurse -File -ErrorAction SilentlyContinue |
                Where-Object { $_.Extension -notin @('.rar', '.zip', '.7z') }).Count
            if ($wxCount -gt 0) {
                Write-Log "Expand-RarFile: WinRAR succeeded ($wxCount extracted files)"
                return $true
            }
        }
    }

    # 3. 7-Zip installed
    foreach ($sz in @(
        "$env:ProgramFiles\7-Zip\7z.exe",
        "${env:ProgramFiles(x86)}\7-Zip\7z.exe",
        "$env:LOCALAPPDATA\Programs\7-Zip\7z.exe"
    )) {
        if (Test-Path $sz) {
            Write-Log "Expand-RarFile: Trying installed 7-Zip at $sz"
            & $sz x -y "-o$Destination" $RarPath | Out-Null
            $szCount = @(Get-ChildItem -Path $Destination -Recurse -File -ErrorAction SilentlyContinue |
                Where-Object { $_.Extension -notin @('.rar', '.zip', '.7z') }).Count
            if ($szCount -gt 0) {
                Write-Log "Expand-RarFile: Installed 7-Zip succeeded ($szCount extracted files)"
                return $true
            }
        }
    }

    # 4. unrar
    $unrarCmd = Get-Command unrar -ErrorAction SilentlyContinue
    if ($unrarCmd) {
        Write-Log "Expand-RarFile: Trying unrar from PATH ($($unrarCmd.Source))"
        & unrar x -o+ $RarPath $Destination | Out-Null
        $urCount = @(Get-ChildItem -Path $Destination -Recurse -File -ErrorAction SilentlyContinue |
            Where-Object { $_.Extension -notin @('.rar', '.zip', '.7z') }).Count
        if ($urCount -gt 0) {
            Write-Log "Expand-RarFile: unrar succeeded ($urCount extracted files)"
            return $true
        }
    }

    # 5. tar.exe
    $tarCmd = Get-Command tar -ErrorAction SilentlyContinue
    if ($tarCmd) {
        Write-Log "Expand-RarFile: Trying tar ($($tarCmd.Source))"
        try {
            & tar -xf $RarPath -C $Destination 2>$null | Out-Null
            $tarCount = @(Get-ChildItem -Path $Destination -Recurse -File -ErrorAction SilentlyContinue |
                Where-Object { $_.Extension -notin @('.rar', '.zip', '.7z') }).Count
            if ($tarCount -gt 0) {
                Write-Log "Expand-RarFile: tar succeeded ($tarCount extracted files)"
                return $true
            }
        } catch { Write-Log "Expand-RarFile: tar threw exception: $_" }
    }

    Write-Log "Expand-RarFile: ALL methods failed - no extraction tool available"
    return $false
}

function Get-ModpackSubModDetails {
    param([int]$ModId)
    $result  = @{}
    $tempDir = $null
    Write-Log "GetModpackSubModDetails: START ModId=$ModId"
    try {
        $latestFile = Get-ApiLatestFile $ModId
        if (-not $latestFile) {
            Write-Log "GetModpackSubModDetails: No latest file returned from API for $ModId"
            return $result
        }
        $mpReleaseDate = if ($latestFile.created_at) { $latestFile.created_at } else { "" }
        Write-Log "GetModpackSubModDetails: API file id=$($latestFile.id) type=$($latestFile.type) ext=$($latestFile.extension) name=$($latestFile.file) created_at=$mpReleaseDate"
        $downloadUrl = if ($latestFile.download_url) { $latestFile.download_url } elseif ($latestFile.url) { $latestFile.url } else { "" }
        Write-Log "GetModpackSubModDetails: downloadUrl=$downloadUrl"
        if (-not $downloadUrl) {
            Write-Log "GetModpackSubModDetails: No download URL found, aborting"
            return $result
        }

        $tempDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "rtv-modpack-$ModId")
        if (Test-Path $tempDir) { Remove-Item $tempDir -Recurse -Force }
        New-Item -ItemType Directory -Path $tempDir -Force | Out-Null
        Write-Log "GetModpackSubModDetails: tempDir=$tempDir"

        $ext         = if ($latestFile.type) { $latestFile.type.ToLower() } elseif ($latestFile.extension) { $latestFile.extension.ToLower() } else { 'zip' }
        $archivePath = [System.IO.Path]::Combine($tempDir, "modpack.$ext")
        Write-Log "GetModpackSubModDetails: Downloading to $archivePath (ext=$ext)..."
        Invoke-WebRequest -Uri $downloadUrl -OutFile $archivePath -TimeoutSec 120
        $archiveSize = if (Test-Path $archivePath) { (Get-Item $archivePath).Length } else { 0 }
        Write-Log "GetModpackSubModDetails: Download complete, archive size=$archiveSize bytes"

        Write-Log "GetModpackSubModDetails: Trying Expand-VmzFile (ZIP)..."
        $extracted = Expand-VmzFile $archivePath $tempDir
        $vmzCountAfterZip = @(Get-ChildItem -Path $tempDir -Recurse -Filter "*.vmz" -ErrorAction SilentlyContinue).Count
        Write-Log "GetModpackSubModDetails: Expand-VmzFile result=$extracted, vmz files found=$vmzCountAfterZip"

        if (-not $extracted -or $vmzCountAfterZip -eq 0) {
            Write-Log "GetModpackSubModDetails: ZIP failed or no vmz found, trying Expand-RarFile..."
            $extracted = Expand-RarFile $archivePath $tempDir
            $vmzCountAfterRar = @(Get-ChildItem -Path $tempDir -Recurse -Filter "*.vmz" -ErrorAction SilentlyContinue).Count
            Write-Log "GetModpackSubModDetails: Expand-RarFile result=$extracted, vmz files found=$vmzCountAfterRar"
        }

        $allFiles = @(Get-ChildItem -Path $tempDir -Recurse -File -ErrorAction SilentlyContinue)
        Write-Log "GetModpackSubModDetails: All files in tempDir after extraction ($($allFiles.Count) total):"
        foreach ($f in $allFiles) { Write-Log "  $($f.FullName) ($($f.Length) bytes)" }

        $vmzFiles = @(Get-ChildItem -Path $tempDir -Recurse -Filter "*.vmz" -ErrorAction SilentlyContinue |
                      Where-Object { $_.FullName -ne $archivePath })
        Write-Log "GetModpackSubModDetails: Processing $($vmzFiles.Count) vmz file(s)..."

        foreach ($vmz in $vmzFiles) {
            Write-Log "GetModpackSubModDetails: Reading mod.txt from $($vmz.Name)..."
            $sub = Get-ModInfoFromVmz $vmz.FullName
            if ($sub) {
                Write-Log "GetModpackSubModDetails: -> Id='$($sub.Id)' Name='$($sub.Name)' Version='$($sub.Version)'"
                if ($sub.Id) {
                    $innerDir = [System.IO.Path]::Combine($tempDir, "inner-$($sub.Id)")
                    Expand-VmzFile $vmz.FullName $innerDir | Out-Null
                    $hashes = @{}
                    if (Test-Path $innerDir) { $hashes = Get-LocalModFileHashes $innerDir }
                    $result[$sub.Id] = @{
                        Name        = $sub.Name
                        Version     = $sub.Version
                        FileHashes  = $hashes
                        VmzPath     = $vmz.FullName
                        ReleaseDate = $mpReleaseDate
                    }
                    Write-Log "GetModpackSubModDetails: -> Stored in result[$($sub.Id)], hashes=$($hashes.Count)"
                } else {
                    Write-Log "GetModpackSubModDetails: -> SKIPPED (empty Id)"
                }
            } else {
                Write-Log "GetModpackSubModDetails: -> mod.txt not found or unreadable in $($vmz.Name)"
            }
        }

        Write-Log "GetModpackSubModDetails: result keys: $($result.Keys -join ', ')"
        $script:ModpackTempDirs[$ModId] = $tempDir
    } catch {
        Write-Log "GetModpackSubModDetails: EXCEPTION: $_"
        if ($tempDir -and (Test-Path $tempDir)) {
            Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue
        }
    }
    Write-Log "GetModpackSubModDetails: END, returning $($result.Count) sub-mod(s)"
    return $result
}

function Resolve-ModsInDirectory {
    param([string]$Directory)
    $mods = @()
    foreach ($vmz in (Get-ChildItem -Path $Directory -Filter "*.vmz" -File)) {
        $info = Get-ModInfoFromVmz $vmz.FullName
        if ($info) { $info.FileSize = $vmz.Length; $mods += $info }
    }
    return $mods
}

function Find-ModworkshopIdByName {
    param([string]$ModName, [string]$ModId)
    $gameId = 864
    try {
        $query = $ModName -replace '\s+','+'
        $resp  = Invoke-RestMethod -Uri "https://api.modworkshop.net/games/$gameId/mods?query=$query&limit=10" `
                 -Method Get -Headers @{"Accept"="application/json"} -TimeoutSec 15
        if ($resp.data -and $resp.data.Count -gt 0) {
            foreach ($m in $resp.data) {
                if ($m.name -and ($m.name -replace '\s+','').ToLower() -eq ($ModName -replace '\s+','').ToLower()) { return [int]$m.id }
            }
            return [int]($resp.data[0].id)
        }
    } catch { }
    try {
        $terms = @($ModName.ToLower(), ($ModId -replace '-',' ').ToLower(), ($ModId -replace '-','').ToLower())
        $page = 1
        do {
            $resp = Invoke-RestMethod -Uri "https://api.modworkshop.net/games/$gameId/mods?limit=100&page=$page" `
                    -Method Get -Headers @{"Accept"="application/json"} -TimeoutSec 30
            if ($resp.data) {
                foreach ($m in $resp.data) {
                    $desc  = (if ($m.desc)  { $m.desc  } else { "" }).ToLower()
                    $mName = (if ($m.name) { $m.name } else { "" }).ToLower()
                    foreach ($term in $terms) {
                        if ($desc -match [regex]::Escape($term) -or $mName -match [regex]::Escape($term)) { return [int]$m.id }
                    }
                }
            }
            $lastPage = if ($resp.meta -and $resp.meta.last_page) { [int]$resp.meta.last_page } else { 1 }
            $page++
        } while ($page -le $lastPage)
    } catch { }
    return $null
}

function Format-FileSize {
    param([long]$Bytes)
    if ($Bytes -ge 1MB) { return "{0:N1} MB" -f ($Bytes / 1MB) }
    if ($Bytes -ge 1KB) { return "{0:N1} KB" -f ($Bytes / 1KB) }
    return "$Bytes B"
}

function Format-ReleaseDate {
    param([string]$IsoDate)
    if (-not $IsoDate) { return "--" }
    try {
        $dt = [System.DateTime]::Parse($IsoDate, [System.Globalization.CultureInfo]::InvariantCulture,
              [System.Globalization.DateTimeStyles]::RoundtripKind)
        return $dt.ToLocalTime().ToString("MM/dd/yyyy")
    } catch { return "--" }
}

# ============================================================================
# WPF ASSEMBLIES
# ============================================================================

Add-Type -AssemblyName PresentationFramework, PresentationCore, WindowsBase, System.Drawing, System.Windows.Forms

# ============================================================================
# DATA MODEL -- ModItem class for ListView binding + sorting
# ============================================================================

Add-Type -TypeDefinition @"
using System;
using System.ComponentModel;

public class RtvModItem2 : INotifyPropertyChanged {
    public event PropertyChangedEventHandler PropertyChanged;
    private void Notify(string n) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(n)); }

    private bool _checked;
    private string _statusText;
    private string _statusColor;
    private bool _isBold;
    private string _fileName;
    private string _modName;
    private string _modworkshopId;
    private string _installedVersion;
    private string _latestVersion;
    private string _installedVersionColor;
    private string _latestVersionColor;
    private string _fileSize;
    private string _filePath;
    private string _internalId;
    private string _releaseDate;

    public bool   Checked               { get { return _checked; }               set { _checked = value;               Notify("Checked"); } }
    public string StatusText            { get { return _statusText; }            set { _statusText = value;            Notify("StatusText"); } }
    public string StatusColor           { get { return _statusColor; }           set { _statusColor = value;           Notify("StatusColor"); } }
    public bool   IsBold                { get { return _isBold; }                set { _isBold = value;                Notify("IsBold"); Notify("FontWeight"); } }
    public string FontWeight            { get { return _isBold ? "Bold" : "Normal"; } }
    public string FileName              { get { return _fileName; }              set { _fileName = value;              Notify("FileName"); } }
    public string ModName               { get { return _modName; }               set { _modName = value;               Notify("ModName"); } }
    public string ModworkshopId         { get { return _modworkshopId; }         set { _modworkshopId = value;         Notify("ModworkshopId"); } }
    public string InstalledVersion      { get { return _installedVersion; }      set { _installedVersion = value;      Notify("InstalledVersion"); } }
    public string LatestVersion         { get { return _latestVersion; }         set { _latestVersion = value;         Notify("LatestVersion"); } }
    public string InstalledVersionColor { get { return _installedVersionColor; } set { _installedVersionColor = value; Notify("InstalledVersionColor"); } }
    public string LatestVersionColor    { get { return _latestVersionColor; }    set { _latestVersionColor = value;    Notify("LatestVersionColor"); } }
    public string FileSize              { get { return _fileSize; }              set { _fileSize = value;              Notify("FileSize"); } }
    public string FilePath              { get { return _filePath; }              set { _filePath = value;              Notify("FilePath"); } }
    public string InternalId            { get { return _internalId; }            set { _internalId = value;            Notify("InternalId"); } }
    public string ReleaseDate           { get { return _releaseDate; }           set { _releaseDate = value;           Notify("ReleaseDate"); } }
    public int    RawModworkshopId      { get; set; }
    public string RawStatus             { get; set; }
    public string RawVersion            { get; set; }
    public string RawLatestVersion      { get; set; }
    public string RawReleaseDate        { get; set; }
    public bool   IsModpack             { get; set; }
}
"@ -ReferencedAssemblies PresentationCore, PresentationFramework, WindowsBase

# ============================================================================
# WPF XAML
# ============================================================================

$xaml = @"
<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="RTV Mod Updater"
    Height="660" Width="1220"
    WindowStartupLocation="CenterScreen"
    Background="#1E1E2E"
    WindowStyle="SingleBorderWindow"
    ResizeMode="CanResizeWithGrip"
    FontFamily="Segoe UI"
    FontSize="13">

    <Window.Resources>
        <!-- Button Styles -->
        <Style x:Key="PrimaryButton" TargetType="Button">
            <Setter Property="Background"       Value="#89B4FA"/>
            <Setter Property="Foreground"       Value="#1E1E2E"/>
            <Setter Property="BorderThickness"  Value="0"/>
            <Setter Property="Padding"          Value="18,8"/>
            <Setter Property="FontSize"         Value="13"/>
            <Setter Property="FontWeight"       Value="SemiBold"/>
            <Setter Property="Cursor"           Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Background="{TemplateBinding Background}" CornerRadius="7" Padding="{TemplateBinding Padding}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">  <Setter Property="Background" Value="#B4BEFE"/> </Trigger>
                <Trigger Property="IsEnabled"   Value="False"> <Setter Property="Background" Value="#45475A"/> <Setter Property="Foreground" Value="#6C7086"/> </Trigger>
            </Style.Triggers>
        </Style>

        <Style x:Key="SuccessButton" TargetType="Button">
            <Setter Property="Background"       Value="#A6E3A1"/>
            <Setter Property="Foreground"       Value="#1E1E2E"/>
            <Setter Property="BorderThickness"  Value="0"/>
            <Setter Property="Padding"          Value="18,8"/>
            <Setter Property="FontSize"         Value="13"/>
            <Setter Property="FontWeight"       Value="SemiBold"/>
            <Setter Property="Cursor"           Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Background="{TemplateBinding Background}" CornerRadius="7" Padding="{TemplateBinding Padding}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">  <Setter Property="Background" Value="#B8F0B3"/> </Trigger>
                <Trigger Property="IsEnabled"   Value="False"> <Setter Property="Background" Value="#45475A"/> <Setter Property="Foreground" Value="#6C7086"/> </Trigger>
            </Style.Triggers>
        </Style>

        <Style x:Key="WarnButton" TargetType="Button">
            <Setter Property="Background"       Value="#F9E2AF"/>
            <Setter Property="Foreground"       Value="#1E1E2E"/>
            <Setter Property="BorderThickness"  Value="0"/>
            <Setter Property="Padding"          Value="18,8"/>
            <Setter Property="FontSize"         Value="13"/>
            <Setter Property="FontWeight"       Value="SemiBold"/>
            <Setter Property="Cursor"           Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Background="{TemplateBinding Background}" CornerRadius="7" Padding="{TemplateBinding Padding}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">  <Setter Property="Background" Value="#FAF0C8"/> </Trigger>
                <Trigger Property="IsEnabled"   Value="False"> <Setter Property="Background" Value="#45475A"/> <Setter Property="Foreground" Value="#6C7086"/> </Trigger>
            </Style.Triggers>
        </Style>

        <Style x:Key="DangerButton" TargetType="Button">
            <Setter Property="Background"       Value="#F38BA8"/>
            <Setter Property="Foreground"       Value="#1E1E2E"/>
            <Setter Property="BorderThickness"  Value="0"/>
            <Setter Property="Padding"          Value="18,8"/>
            <Setter Property="FontSize"         Value="13"/>
            <Setter Property="FontWeight"       Value="SemiBold"/>
            <Setter Property="Cursor"           Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Background="{TemplateBinding Background}" CornerRadius="7" Padding="{TemplateBinding Padding}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">  <Setter Property="Background" Value="#F5A3BA"/> </Trigger>
                <Trigger Property="IsEnabled"   Value="False"> <Setter Property="Background" Value="#45475A"/> <Setter Property="Foreground" Value="#6C7086"/> </Trigger>
            </Style.Triggers>
        </Style>

        <Style x:Key="GhostButton" TargetType="Button">
            <Setter Property="Background"       Value="Transparent"/>
            <Setter Property="Foreground"       Value="#BAC2DE"/>
            <Setter Property="BorderThickness"  Value="1"/>
            <Setter Property="BorderBrush"      Value="#45475A"/>
            <Setter Property="Padding"          Value="14,7"/>
            <Setter Property="FontSize"         Value="12"/>
            <Setter Property="Cursor"           Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border Background="{TemplateBinding Background}" CornerRadius="6" Padding="{TemplateBinding Padding}"
                                BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="#313244"/> <Setter Property="Foreground" Value="#CDD6F4"/> </Trigger>
            </Style.Triggers>
        </Style>

        <!-- ListView dark theme -->
        <Style TargetType="ListView">
            <Setter Property="Background"         Value="#1E1E2E"/>
            <Setter Property="Foreground"         Value="#CDD6F4"/>
            <Setter Property="BorderBrush"        Value="#313244"/>
            <Setter Property="BorderThickness"    Value="0"/>
        </Style>

        <!-- GridViewColumn header style -->
        <Style TargetType="GridViewColumnHeader">
            <Setter Property="Background"         Value="#181825"/>
            <Setter Property="Foreground"         Value="#A6ADC8"/>
            <Setter Property="BorderBrush"        Value="#313244"/>
            <Setter Property="BorderThickness"    Value="0,0,0,1"/>
            <Setter Property="Padding"            Value="8,7"/>
            <Setter Property="FontSize"           Value="12"/>
            <Setter Property="FontWeight"         Value="SemiBold"/>
            <Setter Property="Cursor"             Value="Hand"/>
            <Setter Property="HorizontalContentAlignment" Value="Left"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="GridViewColumnHeader">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Padding="{TemplateBinding Padding}">
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="{TemplateBinding Content}"
                                           Foreground="{TemplateBinding Foreground}"
                                           FontWeight="{TemplateBinding FontWeight}"
                                           FontSize="{TemplateBinding FontSize}"
                                           VerticalAlignment="Center"/>
                                <TextBlock x:Name="SortArrow" Grid.Column="1"
                                           Text="" FontSize="10" Foreground="#585B70"
                                           VerticalAlignment="Center" Margin="4,0,0,0"/>
                            </Grid>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Background" Value="#1E1E2E"/>
                                <Setter Property="Foreground" Value="#CDD6F4"/>
                            </Trigger>
                            <Trigger Property="Tag" Value="Asc">
                                <Setter TargetName="SortArrow" Property="Text" Value="  ^"/>
                                <Setter TargetName="SortArrow" Property="Foreground" Value="#89B4FA"/>
                                <Setter Property="Foreground" Value="#B4BEFE"/>
                            </Trigger>
                            <Trigger Property="Tag" Value="Desc">
                                <Setter TargetName="SortArrow" Property="Text" Value="  v"/>
                                <Setter TargetName="SortArrow" Property="Foreground" Value="#89B4FA"/>
                                <Setter Property="Foreground" Value="#B4BEFE"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- ScrollBar minimal dark style -->
        <Style TargetType="ScrollBar">
            <Setter Property="Background" Value="#181825"/>
        </Style>

        <!-- CheckBox dark style -->
        <Style TargetType="CheckBox">
            <Setter Property="Foreground"   Value="#CDD6F4"/>
            <Setter Property="BorderBrush"  Value="#585B70"/>
            <Setter Property="Cursor"       Value="Hand"/>
        </Style>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- Directory Bar -->
        <Border Grid.Row="0" Background="#181825" Padding="16,9"
                BorderBrush="#313244" BorderThickness="0,0,0,1">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" Text="Mods Directory:" FontSize="12"
                           Foreground="#A6ADC8" VerticalAlignment="Center" Margin="0,0,10,0"/>
                <TextBox x:Name="DirTextBox" Grid.Column="1"
                         Background="#313244" Foreground="#CDD6F4"
                         BorderBrush="#45475A" BorderThickness="1"
                         Padding="8,5" FontSize="12" VerticalContentAlignment="Center"
                         CaretBrush="#CDD6F4"/>
                <Button x:Name="BrowseButton" Grid.Column="2" Content="Browse"
                        Style="{StaticResource GhostButton}" Margin="10,0,0,0"/>
            </Grid>
        </Border>

        <!-- Toolbar -->
        <Border Grid.Row="1" Background="#1E1E2E" Padding="16,8"
                BorderBrush="#313244" BorderThickness="0,0,0,1">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <CheckBox x:Name="SelectAllCheckBox" Grid.Column="0"
                          Content="Select All" FontSize="12"
                          Foreground="#A6ADC8" VerticalAlignment="Center"/>
                <Button x:Name="CheckButton"  Grid.Column="1" Content="Check for Updates"
                        Style="{StaticResource PrimaryButton}" Margin="0,0,8,0"/>
                <Button x:Name="UpdateButton" Grid.Column="2" Content="Update Mod(s)"
                        Style="{StaticResource SuccessButton}" Margin="0,0,8,0"/>
                <Button x:Name="BackupButton" Grid.Column="3" Content="Backup Mods"
                        Style="{StaticResource WarnButton}" Margin="0,0,8,0"/>
                <Button x:Name="DeleteButton" Grid.Column="4" Content="Delete Mod(s)"
                        Style="{StaticResource DangerButton}"/>
            </Grid>
        </Border>

        <!-- Mod List Area -->
        <Grid Grid.Row="2">

            <!-- Empty / loading states -->
            <Border x:Name="LoadingPanel" Background="#1E1E2E" Visibility="Collapsed">
                <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
                    <TextBlock Text="Loading mods and checking for updates..."
                               FontSize="17" FontWeight="Medium" Foreground="#CDD6F4"
                               HorizontalAlignment="Center"/>
                    <TextBlock x:Name="LoadingText" Text="Scanning mods directory..."
                               FontSize="13" Foreground="#6C7086"
                               HorizontalAlignment="Center" Margin="0,10,0,0"/>
                </StackPanel>
            </Border>

            <Border x:Name="EmptyPanel" Background="#1E1E2E" Visibility="Visible">
                <StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
                    <TextBlock Text="No mods loaded" FontSize="19" FontWeight="Medium"
                               Foreground="#585B70" HorizontalAlignment="Center"/>
                    <TextBlock Text="Set your mods directory and click Check for Updates"
                               FontSize="12" Foreground="#45475A"
                               HorizontalAlignment="Center" Margin="0,8,0,0"/>
                </StackPanel>
            </Border>

            <!-- ListView with sortable GridView -->
            <ListView x:Name="ModListView" Visibility="Collapsed"
                      VirtualizingPanel.IsVirtualizing="True"
                      ScrollViewer.HorizontalScrollBarVisibility="Disabled"
                      SelectionMode="Single"
                      Focusable="False">
                <ListView.View>
                    <GridView>
                        <!-- Col 0: Checkbox (non-sortable) -->
                        <GridViewColumn Width="36">
                            <GridViewColumn.Header>
                                <GridViewColumnHeader x:Name="CheckHeader" Tag="NoSort" Cursor="Arrow"/>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <CheckBox IsChecked="{Binding Checked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                                              HorizontalAlignment="Center" VerticalAlignment="Center"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>

                        <!-- Col 1: Status -->
                        <GridViewColumn Width="130">
                            <GridViewColumn.Header>
                                <GridViewColumnHeader x:Name="StatusHeader" Content="Status" Tag="None"/>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <Border Background="{Binding StatusColor}" CornerRadius="5"
                                            Padding="8,4" HorizontalAlignment="Left"
                                            VerticalAlignment="Center">
                                        <TextBlock Text="{Binding StatusText}"
                                                   FontWeight="{Binding FontWeight}"
                                                   FontSize="12" Foreground="#1E1E2E"/>
                                    </Border>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>

                        <!-- Col 2: File Name -->
                        <GridViewColumn Width="230">
                            <GridViewColumn.Header>
                                <GridViewColumnHeader x:Name="FileNameHeader" Content="File Name" Tag="None"/>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <StackPanel VerticalAlignment="Center" Margin="0,2,0,2">
                                        <TextBlock Text="{Binding ModName}"
                                                   FontWeight="{Binding FontWeight}"
                                                   Foreground="#CDD6F4" FontSize="13"
                                                   TextTrimming="CharacterEllipsis"/>
                                        <TextBlock Text="{Binding FileName}"
                                                   Foreground="#6C7086" FontSize="11"
                                                   Margin="0,2,0,0"
                                                   TextTrimming="CharacterEllipsis"/>
                                    </StackPanel>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>

                        <!-- Col 3: Mod ID -->
                        <GridViewColumn Width="100">
                            <GridViewColumn.Header>
                                <GridViewColumnHeader x:Name="ModIdHeader" Content="Mod ID" Tag="None"/>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock x:Name="ModIdBlock"
                                               Text="{Binding ModworkshopId}"
                                               Foreground="#89B4FA" FontSize="12"
                                               TextDecorations="Underline" Cursor="Hand"
                                               VerticalAlignment="Center"
                                               ToolTip="{Binding ModworkshopId}"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>

                        <!-- Col 4: Installed Version -->
                        <GridViewColumn Width="130">
                            <GridViewColumn.Header>
                                <GridViewColumnHeader x:Name="InstalledVerHeader" Content="Installed Ver" Tag="None"/>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding InstalledVersion}"
                                               Foreground="{Binding InstalledVersionColor}"
                                               FontWeight="{Binding FontWeight}"
                                               FontSize="12"
                                               VerticalAlignment="Center"
                                               FontFamily="Cascadia Code, Consolas, Courier New"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>

                        <!-- Col 5: Latest Version -->
                        <GridViewColumn Width="130">
                            <GridViewColumn.Header>
                                <GridViewColumnHeader x:Name="LatestVerHeader" Content="Latest Ver" Tag="None"/>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding LatestVersion}"
                                               Foreground="{Binding LatestVersionColor}"
                                               FontWeight="{Binding FontWeight}"
                                               FontSize="12"
                                               VerticalAlignment="Center"
                                               FontFamily="Cascadia Code, Consolas, Courier New"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>

                        <!-- Col 6: Release Date -->
                        <GridViewColumn Width="110">
                            <GridViewColumn.Header>
                                <GridViewColumnHeader x:Name="ReleaseDateHeader" Content="Released" Tag="None"/>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding ReleaseDate}"
                                               Foreground="#7C849E" FontSize="12"
                                               VerticalAlignment="Center"
                                               FontFamily="Cascadia Code, Consolas, Courier New"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>

                        <!-- Col 7: Size -->
                        <GridViewColumn Width="90">
                            <GridViewColumn.Header>
                                <GridViewColumnHeader x:Name="SizeHeader" Content="Size" Tag="None"/>
                            </GridViewColumn.Header>
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding FileSize}"
                                               Foreground="#585B70" FontSize="12"
                                               HorizontalAlignment="Left"
                                               VerticalAlignment="Center"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                    </GridView>
                </ListView.View>

                <!-- Row style: no hover, no selection highlight -->
                <ListView.ItemContainerStyle>
                    <Style TargetType="ListViewItem">
                        <Setter Property="Background"   Value="Transparent"/>
                        <Setter Property="Foreground"   Value="#CDD6F4"/>
                        <Setter Property="BorderBrush"  Value="#313244"/>
                        <Setter Property="BorderThickness" Value="0,0,0,1"/>
                        <Setter Property="Padding"      Value="4,8"/>
                        <Setter Property="MinHeight"    Value="48"/>
                        <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
                        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
                        <Setter Property="Template">
                            <Setter.Value>
                                <ControlTemplate TargetType="ListViewItem">
                                    <Border x:Name="RowBorder"
                                            Background="{TemplateBinding Background}"
                                            BorderBrush="{TemplateBinding BorderBrush}"
                                            BorderThickness="{TemplateBinding BorderThickness}"
                                            Padding="{TemplateBinding Padding}"
                                            MinHeight="{TemplateBinding MinHeight}">
                                        <GridViewRowPresenter
                                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                            VerticalAlignment="Center"/>
                                    </Border>
                                    <ControlTemplate.Triggers>
                                    </ControlTemplate.Triggers>
                                </ControlTemplate>
                            </Setter.Value>
                        </Setter>
                    </Style>
                </ListView.ItemContainerStyle>

                <ListView.ContextMenu>
                    <ContextMenu x:Name="RowContextMenu"
                                 Background="#313244" Foreground="#CDD6F4"
                                 BorderBrush="#45475A">
                        <MenuItem x:Name="CtxOpenPage"   Header="Open ModWorkshop Page" Foreground="#CDD6F4"/>
                        <MenuItem x:Name="CtxOpenFolder" Header="Open Containing Folder" Foreground="#CDD6F4"/>
                    </ContextMenu>
                </ListView.ContextMenu>
            </ListView>
        </Grid>

        <!-- Status Bar -->
        <Border Grid.Row="3" Background="#11111B" Padding="16,5"
                BorderBrush="#313244" BorderThickness="0,1,0,0">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="*"/>
                    <ColumnDefinition Width="Auto"/>
                </Grid.ColumnDefinitions>
                <TextBlock x:Name="StatusText"   Grid.Column="0" Text="Ready"
                           FontSize="12" Foreground="#45475A"/>
                <TextBlock x:Name="ModCountText" Grid.Column="1" Text="0 mods"
                           FontSize="12" Foreground="#45475A"/>
            </Grid>
        </Border>
    </Grid>
</Window>
"@

# ============================================================================
# WINDOW ICON (Base64-encoded ICO, 7 sizes: 16/24/32/48/64/128/256px)
# ============================================================================

$IconBase64 = @'
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
'@

# ============================================================================
# PARSE XAML
# ============================================================================

$reader = [System.Xml.XmlReader]::Create([System.IO.StringReader]::new($xaml))
$script:MainWindow = [Windows.Markup.XamlReader]::Load($reader)
$reader.Close()

try {
    $iconBytes = [Convert]::FromBase64String($IconBase64)
    $iconStream = [System.IO.MemoryStream]::new($iconBytes, 0, $iconBytes.Length)
    $script:MainWindow.Icon = [System.Windows.Media.Imaging.BitmapFrame]::Create(
        $iconStream,
        [System.Windows.Media.Imaging.BitmapCreateOptions]::None,
        [System.Windows.Media.Imaging.BitmapCacheOption]::OnLoad
    )
} catch {
}

$C = @{
    DirTextBox        = $script:MainWindow.FindName("DirTextBox")
    BrowseButton      = $script:MainWindow.FindName("BrowseButton")
    SelectAllCheckBox = $script:MainWindow.FindName("SelectAllCheckBox")
    CheckButton       = $script:MainWindow.FindName("CheckButton")
    UpdateButton      = $script:MainWindow.FindName("UpdateButton")
    BackupButton      = $script:MainWindow.FindName("BackupButton")
    DeleteButton      = $script:MainWindow.FindName("DeleteButton")
    ModListView       = $script:MainWindow.FindName("ModListView")
    LoadingPanel      = $script:MainWindow.FindName("LoadingPanel")
    LoadingText       = $script:MainWindow.FindName("LoadingText")
    EmptyPanel        = $script:MainWindow.FindName("EmptyPanel")
    StatusText        = $script:MainWindow.FindName("StatusText")
    ModCountText      = $script:MainWindow.FindName("ModCountText")
    StatusHeader       = $script:MainWindow.FindName("StatusHeader")
    FileNameHeader     = $script:MainWindow.FindName("FileNameHeader")
    ModIdHeader        = $script:MainWindow.FindName("ModIdHeader")
    InstalledVerHeader  = $script:MainWindow.FindName("InstalledVerHeader")
    LatestVerHeader     = $script:MainWindow.FindName("LatestVerHeader")
    ReleaseDateHeader   = $script:MainWindow.FindName("ReleaseDateHeader")
    SizeHeader          = $script:MainWindow.FindName("SizeHeader")
    CtxOpenPage       = $script:MainWindow.FindName("CtxOpenPage")
    CtxOpenFolder     = $script:MainWindow.FindName("CtxOpenFolder")
}

# ============================================================================
# OBSERVABLE COLLECTION (data source)
# ============================================================================

Add-Type -AssemblyName PresentationCore
$script:ModCollection     = New-Object System.Collections.ObjectModel.ObservableCollection[RtvModItem2]
$script:ModpackTempDirs   = @{}   # ModworkshopId -> temp dir path (kept alive for Update button)
$C.ModListView.ItemsSource = $script:ModCollection

# ============================================================================
# HELPERS
# ============================================================================

function Set-Status {
    param([string]$Text)
    $C.StatusText.Text = $Text
}

function Set-LoadingText {
    param([string]$Text)
    $C.LoadingText.Text = $Text
}

function Show-Panel {
    param([string]$Name)
    $C.LoadingPanel.Visibility = "Collapsed"
    $C.EmptyPanel.Visibility   = "Collapsed"
    $C.ModListView.Visibility  = "Collapsed"
    switch ($Name) {
        "Loading" { $C.LoadingPanel.Visibility = "Visible" }
        "Empty"   { $C.EmptyPanel.Visibility   = "Visible" }
        "Mods"    { $C.ModListView.Visibility  = "Visible" }
    }
}

function Get-StatusProps {
    param([string]$Status)
    switch ($Status) {
        "Current" { return @{ Text = "Up to Date";  Color = "#A6E3A1"; InstalledColor = "#A6E3A1"; LatestColor = "#A6E3A1"; Bold = $false } }
        "Update"  { return @{ Text = "Out of Date"; Color = "#F9E2AF"; InstalledColor = "#F38BA8"; LatestColor = "#A6E3A1"; Bold = $true  } }
        "NoId"    { return @{ Text = "Unknown";     Color = "#585B70"; InstalledColor = "#6C7086"; LatestColor = "#585B70"; Bold = $false } }
        "Error"   { return @{ Text = "Error";       Color = "#F38BA8"; InstalledColor = "#F38BA8"; LatestColor = "#F38BA8"; Bold = $false } }
        default   { return @{ Text = "Pending...";  Color = "#45475A"; InstalledColor = "#6C7086"; LatestColor = "#45475A"; Bold = $false } }
    }
}

function New-ModItem {
    param([hashtable]$Mod)
    $props  = Get-StatusProps ($Mod.Status)
    $item   = New-Object RtvModItem2
    $item.Checked       = $false
    $item.StatusText    = $props.Text
    $item.StatusColor   = $props.Color
    $item.IsBold        = $props.Bold
    $item.FileName      = $Mod.FileName
    $item.ModName       = $Mod.Name
    $item.ModworkshopId = if ($Mod.ModworkshopId) { "$($Mod.ModworkshopId)" } else { "--" }
    $item.RawModworkshopId = if ($Mod.ModworkshopId) { [int]$Mod.ModworkshopId } else { 0 }
    $cleanVer  = $Mod.Version.Trim('"').Trim("'")
    $cleanApi  = if ($Mod.ApiVersion) { $Mod.ApiVersion.ToString().Trim('"').Trim("'") } else { "" }
    $item.InstalledVersion      = "v$cleanVer"
    $_latestDisp = if ($cleanApi) { "v$cleanApi" } else { "--" }
    $item.LatestVersion         = $_latestDisp
    $item.InstalledVersionColor = $props.InstalledColor
    $item.LatestVersionColor    = $props.LatestColor
    $item.RawVersion            = $cleanVer
    $item.RawLatestVersion      = $cleanApi
    $item.RawStatus             = if ($Mod.Status) { $Mod.Status } else { "Pending" }
    $_rdRaw = if ($Mod.ReleaseDate) { $Mod.ReleaseDate } else { "" }
    $item.ReleaseDate           = Format-ReleaseDate $_rdRaw
    $item.RawReleaseDate        = $_rdRaw
    $item.FileSize              = Format-FileSize $Mod.FileSize
    $item.FilePath              = $Mod.FilePath
    $item.InternalId            = $Mod.Id
    $item.IsModpack             = if ($Mod.IsModpack) { $true } else { $false }
    return $item
}

function Update-ModItem {
    param([RtvModItem2]$Item, [hashtable]$Mod)
    $props = Get-StatusProps ($Mod.Status)
    $Item.StatusText    = $props.Text
    $Item.StatusColor   = $props.Color
    $Item.IsBold        = $props.Bold
    $cleanVer  = $Mod.Version.Trim('"').Trim("'")
    $cleanApi  = if ($Mod.ApiVersion) { $Mod.ApiVersion.ToString().Trim('"').Trim("'") } else { "" }
    $Item.InstalledVersion      = "v$cleanVer"
    $Item.LatestVersion         = if ($cleanApi) { "v$cleanApi" } else { "--" }
    $Item.InstalledVersionColor = $props.InstalledColor
    $Item.LatestVersionColor    = $props.LatestColor
    $Item.RawVersion            = $cleanVer
    $Item.RawLatestVersion      = $cleanApi
    $Item.RawStatus             = $Mod.Status
    if ($Mod.ReleaseDate) {
        $Item.ReleaseDate    = Format-ReleaseDate $Mod.ReleaseDate
        $Item.RawReleaseDate = $Mod.ReleaseDate
    }
    if ($Mod.ModworkshopId) {
        $Item.ModworkshopId    = "$($Mod.ModworkshopId)"
        $Item.RawModworkshopId = [int]$Mod.ModworkshopId
    }
    if ($Mod.IsModpack) { $Item.IsModpack = $true }
}

# ============================================================================
# SORTING
# ============================================================================

$script:SortColumn    = "StatusText"
$script:SortDirection = "Asc"
$script:LastHeader    = $null

function Apply-Sort {
    $col = $script:SortColumn
    $dir = $script:SortDirection
    $items = @($script:ModCollection)

    $sorted = switch ($col) {
        "StatusText"    { if ($dir -eq "Asc") { $items | Sort-Object { @("Out of Date","Pending...","Unknown","Error","Up to Date").IndexOf($_.StatusText) } }
                          else                 { $items | Sort-Object { @("Out of Date","Pending...","Unknown","Error","Up to Date").IndexOf($_.StatusText) } -Descending } }
        "FileName"      { if ($dir -eq "Asc") { $items | Sort-Object FileName }       else { $items | Sort-Object FileName -Descending } }
        "ModworkshopId" { if ($dir -eq "Asc") { $items | Sort-Object RawModworkshopId } else { $items | Sort-Object RawModworkshopId -Descending } }
        "InstalledVersion" { if ($dir -eq "Asc") { $items | Sort-Object { Compare-SemVer $_.RawVersion "0.0.0" } } else { $items | Sort-Object { Compare-SemVer $_.RawVersion "0.0.0" } -Descending } }
        "LatestVersion"    { if ($dir -eq "Asc") { $items | Sort-Object { Compare-SemVer $_.RawLatestVersion "0.0.0" } } else { $items | Sort-Object { Compare-SemVer $_.RawLatestVersion "0.0.0" } -Descending } }
        "ReleaseDate"      { if ($dir -eq "Asc") { $items | Sort-Object { $_.RawReleaseDate } } else { $items | Sort-Object { $_.RawReleaseDate } -Descending } }
        "FileSize"      { if ($dir -eq "Asc") { $items | Sort-Object { (Get-Item $_.FilePath -ErrorAction SilentlyContinue).Length } }
                          else { $items | Sort-Object { (Get-Item $_.FilePath -ErrorAction SilentlyContinue).Length } -Descending } }
        default         { $items }
    }

    $script:ModCollection.Clear()
    foreach ($i in $sorted) { $script:ModCollection.Add($i) | Out-Null }
}

function On-ColumnHeaderClick {
    param($Header, $ColName)

    if ($script:LastHeader -and $script:LastHeader -ne $Header) {
        $script:LastHeader.Tag = "None"
    }

    if ($script:SortColumn -eq $ColName -and $script:SortDirection -eq "Asc") {
        $script:SortDirection = "Desc"
        $Header.Tag = "Desc"
    } else {
        $script:SortColumn    = $ColName
        $script:SortDirection = "Asc"
        $Header.Tag = "Asc"
    }

    $script:LastHeader = $Header
    Apply-Sort
}

$C.StatusHeader.Add_Click({        On-ColumnHeaderClick $C.StatusHeader       "StatusText"       })
$C.FileNameHeader.Add_Click({      On-ColumnHeaderClick $C.FileNameHeader     "FileName"         })
$C.ModIdHeader.Add_Click({         On-ColumnHeaderClick $C.ModIdHeader        "ModworkshopId"    })
$C.InstalledVerHeader.Add_Click({  On-ColumnHeaderClick $C.InstalledVerHeader  "InstalledVersion" })
$C.LatestVerHeader.Add_Click({     On-ColumnHeaderClick $C.LatestVerHeader     "LatestVersion"    })
$C.ReleaseDateHeader.Add_Click({   On-ColumnHeaderClick $C.ReleaseDateHeader   "ReleaseDate"      })
$C.SizeHeader.Add_Click({          On-ColumnHeaderClick $C.SizeHeader          "FileSize"         })

# ============================================================================
# RECYCLE BIN
# ============================================================================

if ([System.Environment]::OSVersion.Platform -eq "Win32NT" -and -not ([System.Management.Automation.PSTypeName]'RecycleBin').Type) {
    Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class RecycleBin {
    [DllImport("shell32.dll", CharSet = CharSet.Unicode)]
    public static extern int SHFileOperation(ref SHFILEOPSTRUCT lpFileOp);
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct SHFILEOPSTRUCT {
        public IntPtr hwnd;
        public int wFunc;
        [MarshalAs(UnmanagedType.LPTStr)] public string pFrom;
        [MarshalAs(UnmanagedType.LPTStr)] public string pTo;
        public int fFlags;
        public int fAnyOperationsAborted;
        public IntPtr hNameMappings;
        [MarshalAs(UnmanagedType.LPTStr)] public string lpszProgressTitle;
    }
    public const int FO_DELETE = 0x0003;
    public const int FOF_ALLOWUNDO = 0x0040;
    public const int FOF_NOCONFIRMATION = 0x0010;
    public const int FOF_SILENT = 0x0004;
    public static void Send(string path) {
        SHFILEOPSTRUCT op = new SHFILEOPSTRUCT();
        op.wFunc = FO_DELETE;
        op.pFrom = path + "\0\0";
        op.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT;
        SHFileOperation(ref op);
    }
}
"@
}

# ============================================================================
# EVENT HANDLERS
# ============================================================================

# Browse
$C.BrowseButton.Add_Click({
    $dlg = New-Object System.Windows.Forms.FolderBrowserDialog
    $dlg.Description = "Select your Road to Vostok mods directory"
    if ($C.DirTextBox.Text -and (Test-Path $C.DirTextBox.Text)) {
        $dlg.SelectedPath = $C.DirTextBox.Text
    }
    if ($dlg.ShowDialog() -eq "OK") {
        $C.DirTextBox.Text = $dlg.SelectedPath
        Save-ModsDir $dlg.SelectedPath
        $C.CheckButton.RaiseEvent(
            [System.Windows.RoutedEventArgs]::new([System.Windows.Controls.Button]::ClickEvent)
        )
    }
})

# Allow pressing Enter in the DirTextBox to trigger a check
$C.DirTextBox.Add_KeyDown({
    param($s, $e)
    if ($e.Key -eq [System.Windows.Input.Key]::Return -or
        $e.Key -eq [System.Windows.Input.Key]::Enter) {
        $typed = $C.DirTextBox.Text.Trim()
        if ($typed -and (Test-Path $typed)) { Save-ModsDir $typed }
        $C.CheckButton.RaiseEvent(
            [System.Windows.RoutedEventArgs]::new([System.Windows.Controls.Button]::ClickEvent)
        )
    }
})

# Select All
$C.SelectAllCheckBox.Add_Checked({
    foreach ($item in $script:ModCollection) { $item.Checked = $true }
})
$C.SelectAllCheckBox.Add_Unchecked({
    foreach ($item in $script:ModCollection) { $item.Checked = $false }
})

# Mod ID click -- open browser
$C.ModListView.Add_MouseLeftButtonUp({
    param($s, $e)
    $el = $e.OriginalSource
    $tb = $el
    while ($tb -and $tb -isnot [System.Windows.Controls.TextBlock]) { $tb = $tb.Parent }
    if ($tb -and $tb.Name -eq "ModIdBlock" -and $tb.Text -match '^\d+$') {
        Start-Process "https://modworkshop.net/mod/$($tb.Text)"
    }
})

# Context menu
$C.CtxOpenPage.Add_Click({
    $item = $C.ModListView.SelectedItem
    if ($item -and $item.RawModworkshopId -gt 0) {
        Start-Process "https://modworkshop.net/mod/$($item.RawModworkshopId)"
    }
})
$C.CtxOpenFolder.Add_Click({
    $item = $C.ModListView.SelectedItem
    if ($item -and $item.FilePath) {
        Start-Process ([System.IO.Path]::GetDirectoryName($item.FilePath))
    }
})

# ============================================================================
# CHECK FOR UPDATES
# ============================================================================

$C.CheckButton.Add_Click({
    $modsDir = $C.DirTextBox.Text
    if (-not $modsDir -or -not (Test-Path $modsDir)) {
        [System.Windows.MessageBox]::Show("Please select a valid mods directory first.", "No Directory", "OK", "Warning")
        return
    }

    $script:ModCollection.Clear()
    $script:RawMods  = @()
    $script:ItemMap  = @{}

    foreach ($td in $script:ModpackTempDirs.Values) {
        if (Test-Path $td) { Remove-Item $td -Recurse -Force -ErrorAction SilentlyContinue }
    }
    $script:ModpackTempDirs = @{}

    Show-Panel "Loading"
    Set-LoadingText "Scanning mods directory..."
    Set-Status "Scanning $modsDir..."

    $rawMods = Resolve-ModsInDirectory $modsDir
    if ($rawMods.Count -eq 0) {
        Show-Panel "Empty"
        Set-Status "No .vmz mods found."
        $C.ModCountText.Text = "0 mods"
        return
    }

    $script:RawMods = $rawMods

    Write-Log "Scan: Found $($rawMods.Count) local vmz file(s):"
    foreach ($mod in $rawMods) {
        Write-Log "Scan:   File='$($mod.FileName)' Id='$($mod.Id)' Name='$($mod.Name)' Version='$($mod.Version)' ModworkshopId='$($mod.ModworkshopId)'"
        $mod.Status = "Pending"
        $item = New-ModItem -Mod $mod
        $script:ModCollection.Add($item) | Out-Null
        $script:ItemMap[$mod.Id] = $item
    }

    $C.ModCountText.Text = "$($rawMods.Count) mods"
    Set-Status "Found $($rawMods.Count) mod(s). Resolving IDs..."

    foreach ($mod in $rawMods) {
        if (-not $mod.ModworkshopId) {
            Set-Status "Looking up: $($mod.Name)..."
            $found = Find-ModworkshopIdByName -ModName $mod.Name -ModId $mod.Id
            if ($found) {
                $mod.ModworkshopId = $found
                $key = $mod.Id
                if ($script:ItemMap[$key]) { Update-ModItem -Item $script:ItemMap[$key] -Mod $mod }
            }
        }
    }

    $groups = @{}
    foreach ($mod in $rawMods) {
        if ($mod.ModworkshopId) {
            $gkey = [string]$mod.ModworkshopId
            if (-not $groups[$gkey]) { $groups[$gkey] = @() }
            $groups[$gkey] += $mod
        }
    }

    $standaloneIds = @(); $modpackIds = @()
    foreach ($id in $groups.Keys) {
        if ($groups[$id].Count -gt 1) { $modpackIds += [string]$id } else { $standaloneIds += [string]$id }
    }
    Write-Log "Groups: standaloneIds=[$($standaloneIds -join ', ')] modpackIds=[$($modpackIds -join ', ')]"

    $saIdx = 0
    foreach ($mpId in $standaloneIds) {
        $saIdx++
        $mod = $groups[$mpId][0]
        Set-Status "Checking ($saIdx/$($standaloneIds.Count)): $($mod.Name)..."
        Write-Log "CheckStandalone: [$saIdx/$($standaloneIds.Count)] Id=$mpId Name='$($mod.Name)' LocalVer='$($mod.Version)'"
        try {
            $apiMod  = Get-ApiModInfo ([int]$mpId)
            $apiFile = Get-ApiLatestFile ([int]$mpId)
            if ($apiMod) {
                $apiVer  = if ($apiMod.version) { $apiMod.version } else { "0.0.0" }
                $apiDate = if ($apiFile -and $apiFile.created_at) { $apiFile.created_at } else { "" }
                Write-Log "CheckStandalone: API version=$apiVer created_at=$apiDate local=$($mod.Version) cmp=$(Compare-SemVer $apiVer $mod.Version)"
                $mod.ReleaseDate = $apiDate
                $mod.ApiVersion  = $apiVer
                if ((Compare-SemVer $apiVer $mod.Version) -gt 0) {
                    $mod.Status = "Update"
                    Write-Log "CheckStandalone: -> OUT OF DATE (local=$($mod.Version) api=$apiVer)"
                } else {
                    $mod.Status = "Current"
                    Write-Log "CheckStandalone: -> UP TO DATE (local=$($mod.Version) api=$apiVer)"
                }
            } else {
                $mod.Status = "Error"
                Write-Log "CheckStandalone: -> ERROR (no API response)"
            }
        } catch {
            $mod.Status = "Error"
            Write-Log "CheckStandalone: -> EXCEPTION: $_"
        }
        $key = $mod.Id
        if ($script:ItemMap[$key]) { Update-ModItem -Item $script:ItemMap[$key] -Mod $mod }
    }

    $mpIdx = 0
    foreach ($mpId in $modpackIds) {
        $mpIdx++
        Set-Status "Downloading modpack $mpId ($mpIdx/$($modpackIds.Count))..."
        Set-LoadingText "Downloading modpack $mpId..."
        Write-Log "CheckModpack: [$mpIdx/$($modpackIds.Count)] ModWorkshop ID=$mpId has $($groups[$mpId].Count) local mod(s)"
        foreach ($mod in $groups[$mpId]) {
            Write-Log "CheckModpack:   local mod Id='$($mod.Id)' Name='$($mod.Name)' Version='$($mod.Version)' File='$($mod.FileName)'"
        }
        foreach ($mod in $groups[$mpId]) { $mod.IsModpack = $true }

        try {
            $remote = Get-ModpackSubModDetails ([int]$mpId)
            Write-Log "CheckModpack: remote result has $($remote.Count) key(s): $($remote.Keys -join ', ')"
            Set-Status "Comparing modpack $mpId ($mpIdx/$($modpackIds.Count))..."

            foreach ($mod in $groups[$mpId]) {
                try {
                    Write-Log "CheckModpack: Looking up remote[$($mod.Id)]..."
                    $rem = $remote[$mod.Id]
                    if ($rem) {
                        $remVer = if ($rem.Version) { $rem.Version } else { "" }
                        Write-Log "CheckModpack: Found remote entry for '$($mod.Id)': RemoteVer='$remVer' LocalVer='$($mod.Version)' ReleaseDate=$($rem.ReleaseDate)"
                        $mod.ReleaseDate = if ($rem.ReleaseDate) { $rem.ReleaseDate } else { "" }
                        $semCmp = if ($remVer) { Compare-SemVer $remVer $mod.Version } else { 0 }
                        Write-Log "CheckModpack: semCmp=$semCmp"
                        $mod.ApiVersion = if ($remVer) { $remVer } else { $mod.Version }
                        if ($semCmp -gt 0) {
                            $mod.Status = "Update"
                        } else {
                            $localDir = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "rtv-local-$($mod.Id)")
                            if (Test-Path $localDir) { Remove-Item $localDir -Recurse -Force -ErrorAction SilentlyContinue }
                            $expandOk = Expand-VmzFile $mod.FilePath $localDir
                            Write-Log "CheckModpack: local vmz expand ok=$expandOk localDir exists=$(Test-Path $localDir)"
                            if ($expandOk -and (Test-Path $localDir)) {
                                $localH = Get-LocalModFileHashes $localDir
                                $remH   = if ($rem.FileHashes) { $rem.FileHashes } else { @{} }
                                Write-Log "CheckModpack: localH=$($localH.Count) keys, remH=$($remH.Count) keys"
                                Remove-Item $localDir -Recurse -Force -ErrorAction SilentlyContinue
                                $changed = $false
                                foreach ($k in $remH.Keys) {
                                    if (-not $localH[$k] -or $localH[$k] -ne $remH[$k]) { $changed = $true; break }
                                }
                                if (-not $changed) {
                                    foreach ($k in $localH.Keys) {
                                        if (-not $remH[$k]) { $changed = $true; break }
                                    }
                                }
                                if ($changed) {
                                    $mod.Status     = "Update"
                                    $mod.ApiVersion = if ($remVer) { "$remVer (content)" } else { "$($mod.Version) (content)" }
                                } else {
                                    $mod.Status = "Current"
                                    if ($remVer) { $mod.ApiVersion = $remVer }
                                }
                            } else {
                                Write-Log "CheckModpack: Could not expand local vmz, assuming Current"
                                $mod.Status = "Current"
                                if ($remVer) { $mod.ApiVersion = $remVer }
                            }
                        }
                    } else {
                        Write-Log "CheckModpack: No remote entry for '$($mod.Id)' -- marking NoId. Available keys: $($remote.Keys -join ', ')"
                        $mod.Status = "NoId"
                    }
                } catch {
                    Write-Log "CheckModpack: EXCEPTION processing '$($mod.Id)': $_"
                    $mod.Status = "Error"
                }
                Write-Log "CheckModpack: '$($mod.Id)' final status=$($mod.Status)"
                $mod.IsModpack = $true
                $key = $mod.Id
                if ($script:ItemMap[$key]) { Update-ModItem -Item $script:ItemMap[$key] -Mod $mod }
            }
        } catch {
            Write-Log "CheckModpack: OUTER EXCEPTION for modpack $mpId : $_"
            foreach ($mod in $groups[$mpId]) {
                $mod.Status = "Error"; $mod.IsModpack = $true
                $key = $mod.Id
                if ($script:ItemMap[$key]) { Update-ModItem -Item $script:ItemMap[$key] -Mod $mod }
            }
        }
    }

    foreach ($mod in $rawMods) {
        if ($mod.Status -eq "Pending") {
            $mod.Status = "NoId"
            $key = $mod.Id
            if ($script:ItemMap[$key]) { Update-ModItem -Item $script:ItemMap[$key] -Mod $mod }
        }
    }

    $script:SortColumn    = "StatusText"
    $script:SortDirection = "Asc"
    $C.StatusHeader.Tag   = "Asc"
    $script:LastHeader    = $C.StatusHeader
    Apply-Sort

    $upd   = ($rawMods | Where-Object { $_.Status -eq "Update" }).Count
    $ok    = ($rawMods | Where-Object { $_.Status -eq "Current" }).Count
    $total = $rawMods.Count
    Set-Status "$total mod(s) checked, $ok mod(s) up to date, $upd mod update(s) available."
    Show-Panel "Mods"
})

# ============================================================================
# UPDATE MOD(S)
# ============================================================================

$C.UpdateButton.Add_Click({
    $selected = @($script:ModCollection | Where-Object { $_.Checked -eq $true })
    if ($selected.Count -eq 0) {
        [System.Windows.MessageBox]::Show("Check the boxes next to mods you want to update.", "No Selection", "OK", "Information")
        return
    }

    $dl = 0; $fail = 0; $skipNotReady = 0; $skipNoId = 0
    $total   = @($selected | Where-Object { $_.RawModworkshopId -gt 0 -and $_.RawStatus -eq "Update" }).Count
    $current = 0
    foreach ($item in $selected) {
        if ($item.RawModworkshopId -le 0) { $skipNoId++;      continue }
        if ($item.RawStatus -ne "Update")  { $skipNotReady++; continue }

        $current++
        $label = if ($item.ModName -and $item.ModName -ne $item.FileName) { $item.ModName } else { $item.FileName }
        Set-Status "Updating $label... ($current of $total)"
        $script:MainWindow.Dispatcher.Invoke(
            [System.Windows.Threading.DispatcherPriority]::Render,
            [System.Action]{}
        )
        try {
            if ($item.IsModpack) {
                $mpId   = $item.RawModworkshopId
                $subId  = $item.InternalId

                $tempDir = $script:ModpackTempDirs[$mpId]
                if (-not $tempDir -or -not (Test-Path $tempDir)) {
                    Set-Status "Re-downloading modpack $mpId for $label... ($current of $total)"
                    $remote = Get-ModpackSubModDetails $mpId   # re-downloads and stores in ModpackTempDirs
                    $tempDir = $script:ModpackTempDirs[$mpId]
                }

                $subVmzPath = $null
                if ($tempDir -and (Test-Path $tempDir)) {
                    foreach ($vmz in (Get-ChildItem -Path $tempDir -Recurse -Filter "*.vmz")) {
                        $sub = Get-ModInfoFromVmz $vmz.FullName
                        if ($sub -and $sub.Id -eq $subId) {
                            $subVmzPath = $vmz.FullName
                            break
                        }
                    }
                }

                if ($subVmzPath -and (Test-Path $subVmzPath)) {
                    Copy-Item -Path $subVmzPath -Destination $item.FilePath -Force
                    $newVer = $item.RawLatestVersion
                    $newVer = $newVer -replace '\s*\(content\)', ''
                    if (-not $newVer) { $newVer = $item.RawVersion }
                    $item.InstalledVersion      = "v$newVer"
                    $item.LatestVersion         = "v$newVer"
                    $item.InstalledVersionColor = "#A6E3A1"
                    $item.LatestVersionColor    = "#A6E3A1"
                    $item.RawVersion            = $newVer
                    $item.RawLatestVersion      = $newVer
                    $item.StatusText            = "Up to Date"
                    $item.StatusColor           = "#A6E3A1"
                    $item.IsBold                = $false
                    $item.RawStatus             = "Current"
                    $dl++
                } else {
                    $item.StatusText  = "DL Failed"
                    $item.StatusColor = "#F38BA8"
                    $fail++
                }
            } else {
                $lf = Get-ApiLatestFile $item.RawModworkshopId
                if ($lf) {
                    $url = if ($lf.download_url) { $lf.download_url } elseif ($lf.url) { $lf.url } else { "" }
                    if ($url) {
                        $tempFile = [System.IO.Path]::Combine(
                            [System.IO.Path]::GetTempPath(),
                            "rtv-update-$($item.InternalId).vmz"
                        )
                        Invoke-WebRequest -Uri $url -OutFile $tempFile -TimeoutSec 120

                        if (Test-Path $tempFile) {
                            Copy-Item -Path $tempFile -Destination $item.FilePath -Force
                            Remove-Item $tempFile -Force -ErrorAction SilentlyContinue

                                    $newVer = $item.RawLatestVersion
                            if (-not $newVer) { $newVer = $item.RawVersion }
                            $item.InstalledVersion      = "v$newVer"
                            $item.LatestVersion         = "v$newVer"
                            $item.InstalledVersionColor = "#A6E3A1"
                            $item.LatestVersionColor    = "#A6E3A1"
                            $item.RawVersion            = $newVer
                            $item.RawLatestVersion      = $newVer
                            $item.StatusText            = "Up to Date"
                            $item.StatusColor           = "#A6E3A1"
                            $item.IsBold                = $false
                            $item.RawStatus             = "Current"
                            $dl++
                        } else { $fail++ }
                    } else { $fail++ }
                } else { $fail++ }
            }
        } catch {
            $item.StatusText  = "DL Failed"
            $item.StatusColor = "#F38BA8"
            $fail++
        }
    }

    if ($dl -gt 0 -and $fail -eq 0) {
        $msg = "All $dl mod(s) updated successfully."
    } elseif ($dl -gt 0) {
        $msg = "$dl updated"
        if ($fail -gt 0) { $msg += ", $fail failed" }
        $msg += "."
    } elseif ($fail -gt 0) {
        $msg = "$fail update(s) failed."
    } else {
        $msg = "All selected mod(s) are up to date."
    }
    if ($skipNoId -gt 0) { $msg += " $skipNoId skipped (no ModWorkshop ID)." }
    Set-Status $msg
})

# ============================================================================
# BACKUP MODS
# ============================================================================

$C.BackupButton.Add_Click({
    $modsDir = $C.DirTextBox.Text
    if (-not $modsDir -or -not (Test-Path $modsDir)) {
        [System.Windows.MessageBox]::Show("Please select a valid mods directory first.", "No Directory", "OK", "Warning")
        return
    }

    $vmzFiles = @(Get-ChildItem -Path $modsDir -Filter "*.vmz" -File)
    if ($vmzFiles.Count -eq 0) {
        [System.Windows.MessageBox]::Show("No .vmz mod files found in the mods directory.", "Nothing to Backup", "OK", "Information")
        return
    }

    $timestamp   = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"
    $zipName     = "mods-backup-$timestamp.zip"
    $zipPath     = [System.IO.Path]::Combine($modsDir, $zipName)

    Set-Status "Backing up $($vmzFiles.Count) mod(s) to $zipName..."

    try {
        Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop

        $zip = [System.IO.Compression.ZipFile]::Open($zipPath, [System.IO.Compression.ZipArchiveMode]::Create)
        foreach ($file in $vmzFiles) {
            [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile(
                $zip, $file.FullName, $file.Name,
                [System.IO.Compression.CompressionLevel]::Optimal
            ) | Out-Null
        }
        $zip.Dispose()

        $zipSize = Format-FileSize ((Get-Item $zipPath).Length)
        Set-Status "Backup complete. $zipName ($zipSize, $($vmzFiles.Count) mods)"
        [System.Windows.MessageBox]::Show(
            "Backup saved successfully.`n`nFile: $zipName`nSize: $zipSize`nMods: $($vmzFiles.Count)`nLocation: $modsDir",
            "Backup Complete", "OK", "None"
        )
    } catch {
        if (Test-Path $zipPath) { Remove-Item $zipPath -Force -ErrorAction SilentlyContinue }
        Set-Status "Backup failed: $_"
        [System.Windows.MessageBox]::Show("Backup failed:`n$_", "Backup Error", "OK", "Error")
    }
})

# ============================================================================
# DELETE MOD(S)
# ============================================================================

$C.DeleteButton.Add_Click({
    $selected = @($script:ModCollection | Where-Object { $_.Checked -eq $true })
    if ($selected.Count -eq 0) {
        [System.Windows.MessageBox]::Show("Check the boxes next to mods you want to delete.", "No Selection", "OK", "Information")
        return
    }

    $names  = ($selected | ForEach-Object { $_.FileName }) -join "`n"
    $result = [System.Windows.MessageBox]::Show("Move these mods to Recycle Bin?`n`n$names", "Confirm Delete", "YesNo", "Question")
    if ($result -ne "Yes") { return }

    $del = 0
    foreach ($item in $selected) {
        try {
            if ([System.Environment]::OSVersion.Platform -eq "Win32NT") { [RecycleBin]::Send($item.FilePath) }
            else { Remove-Item $item.FilePath -Force }
            $script:ModCollection.Remove($item) | Out-Null
            $del++
        } catch {
            [System.Windows.MessageBox]::Show("Failed to delete: $($item.FileName)`n$_", "Delete Error", "OK", "Error")
        }
    }

    $C.ModCountText.Text = "$($script:ModCollection.Count) mods"
    Set-Status "Moved $del mod(s) to Recycle Bin."
    if ($script:ModCollection.Count -eq 0) { Show-Panel "Empty" }
})

# ============================================================================
# INIT
# ============================================================================
# REGISTRY PERSISTENCE  (HKCU\Software\RTVModUpdater)
# ============================================================================

$script:RegPath = "HKCU:\Software\RTVModUpdater"

function Save-ModsDir {
    param([string]$Path)
    if (-not $Path) { return }
    try {
        if (-not (Test-Path $script:RegPath)) {
            New-Item -Path $script:RegPath -Force | Out-Null
        }
        Set-ItemProperty -Path $script:RegPath -Name "ModsDirectory" -Value $Path -ErrorAction Stop
    } catch { }
}

function Load-ModsDir {
    try {
        $val = (Get-ItemProperty -Path $script:RegPath -Name "ModsDirectory" -ErrorAction Stop).ModsDirectory
        if ($val -and (Test-Path $val)) { return $val }
    } catch { }
    return ""
}

# ============================================================================

$initialDir = ""
if ((Load-ModsDir) -ne "") {
    $initialDir = Load-ModsDir
} else {
    $steamPath = $null
    try {
        $steamPath = (Get-ItemProperty -Path "HKCU:\Software\Valve\Steam" -Name "SteamPath" -ErrorAction Stop).SteamPath
        $steamPath = $steamPath.Replace('/', '\')
    } catch { }

    $candidates = @()
    if ($steamPath) {
        $candidates += "$steamPath\steamapps\common\Road to Vostok\mods"
        $vdf = "$steamPath\steamapps\libraryfolders.vdf"
        if (Test-Path $vdf) {
            Select-String -Path $vdf -Pattern '"path"\s+"([^"]+)"' | ForEach-Object {
                $lib = $_.Matches[0].Groups[1].Value.Replace('\\','\').Replace('/','\')
                $candidates += "$lib\steamapps\common\Road to Vostok\mods"
            }
        }
    }
    foreach ($drive in 'C','D','E','F','G','H') {
        $candidates += "${drive}:\Program Files\Steam\steamapps\common\Road to Vostok\mods"
        $candidates += "${drive}:\Program Files (x86)\Steam\steamapps\common\Road to Vostok\mods"
        $candidates += "${drive}:\Steam\steamapps\common\Road to Vostok\mods"
        $candidates += "${drive}:\SteamLibrary\steamapps\common\Road to Vostok\mods"
    }
    $candidates += "$env:ProgramFiles\Steam\steamapps\common\Road to Vostok\mods"
    $candidates += "${env:ProgramFiles(x86)}\Steam\steamapps\common\Road to Vostok\mods"

    foreach ($p in $candidates) { if (Test-Path $p) { $initialDir = $p; break } }

    if (-not $initialDir) {
        $scriptDir = if ($PSScriptRoot) { $PSScriptRoot } else { Split-Path -Parent ([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName) }
        foreach ($p in @($scriptDir, (Get-Location).Path)) {
            if ($p -and (Test-Path $p) -and @(Get-ChildItem -Path $p -Filter "*.vmz" -File -ErrorAction SilentlyContinue).Count -gt 0) {
                $initialDir = $p; break
            }
        }
    }
}

$C.DirTextBox.Text = $initialDir
Set-Status "Ready. Select your mods directory and click Check for Updates."

if ($initialDir) {
    Show-Panel "Loading"
    Set-LoadingText "Scanning mods directory..."
    Set-Status "Starting up..."
    $script:MainWindow.Add_Loaded({
        $script:MainWindow.Dispatcher.Invoke(
            [System.Windows.Threading.DispatcherPriority]::Render,
            [System.Action]{}
        )
        $C.CheckButton.RaiseEvent(
            [System.Windows.RoutedEventArgs]::new([System.Windows.Controls.Button]::ClickEvent)
        )
    })
}

$script:MainWindow.Add_Closing({
    $dir = $C.DirTextBox.Text.Trim()
    if ($dir) { Save-ModsDir $dir }
})

try {
    $script:MainWindow.ShowDialog() | Out-Null
} catch {
    [System.Windows.MessageBox]::Show("Fatal error:`n$_", "Error", "OK", "Error")
}