#requires -Version 5.1 <# .SYNOPSIS Downloads an IW4x patch package and installs it into a legitimate Steam installation of Call of Duty: Modern Warfare 2 (2009). .EXAMPLE .\Install-IW4x.ps1 .EXAMPLE .\Install-IW4x.ps1 -ServerAddress "192.168.0.66:28960" .EXAMPLE .\Install-IW4x.ps1 -GamePath "D:\SteamLibrary\steamapps\common\Call of Duty Modern Warfare 2" #> [CmdletBinding()] param( [Parameter()] [ValidateNotNullOrEmpty()] [string]$PackageUrl = "https://files.dayv.se/mw4.zip", [Parameter()] [string]$GamePath, [Parameter()] [ValidatePattern('^[^:\s]+(?::\d{1,5})?$')] [string]$ServerAddress = "192.168.0.66:28960", [Parameter()] [switch]$SkipShortcuts ) Set-StrictMode -Version Latest $ErrorActionPreference = "Stop" $ProgressPreference = "Continue" function Write-Step { param([Parameter(Mandatory)][string]$Message) Write-Host "" Write-Host "==> $Message" -ForegroundColor Cyan } function Test-IsAdministrator { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal]::new($identity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } function Quote-ProcessArgument { param([Parameter(Mandatory)][string]$Value) return '"' + ($Value -replace '(\\*)"', '$1$1\"' -replace '(\\+)$', '$1$1') + '"' } function Restart-Elevated { if (-not $PSCommandPath) { throw "Save this script as a .ps1 file and run it again so it can request administrator access." } $arguments = @( "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", (Quote-ProcessArgument $PSCommandPath), "-PackageUrl", (Quote-ProcessArgument $PackageUrl), "-ServerAddress", (Quote-ProcessArgument $ServerAddress) ) if ($GamePath) { $arguments += @("-GamePath", (Quote-ProcessArgument $GamePath)) } if ($SkipShortcuts) { $arguments += "-SkipShortcuts" } Start-Process -FilePath "powershell.exe" ` -Verb RunAs ` -ArgumentList ($arguments -join " ") exit } function Get-SteamRootCandidates { $roots = [System.Collections.Generic.List[string]]::new() $registryLocations = @( @{ Path = "HKCU:\Software\Valve\Steam"; Name = "SteamPath" }, @{ Path = "HKLM:\SOFTWARE\WOW6432Node\Valve\Steam"; Name = "InstallPath" }, @{ Path = "HKLM:\SOFTWARE\Valve\Steam"; Name = "InstallPath" } ) foreach ($entry in $registryLocations) { try { $value = (Get-ItemProperty -LiteralPath $entry.Path -Name $entry.Name -ErrorAction Stop).($entry.Name) if ($value) { $roots.Add(([IO.Path]::GetFullPath($value))) } } catch { # Registry location is optional. } } if (${env:ProgramFiles(x86)}) { $roots.Add((Join-Path ${env:ProgramFiles(x86)} "Steam")) } if ($env:ProgramFiles) { $roots.Add((Join-Path $env:ProgramFiles "Steam")) } return $roots | Where-Object { $_ -and (Test-Path -LiteralPath $_) } | Select-Object -Unique } function Get-SteamLibraries { $libraries = [System.Collections.Generic.List[string]]::new() foreach ($steamRoot in Get-SteamRootCandidates) { $libraries.Add($steamRoot) $vdf = Join-Path $steamRoot "steamapps\libraryfolders.vdf" if (-not (Test-Path -LiteralPath $vdf)) { continue } $content = Get-Content -LiteralPath $vdf -Raw $matches = [regex]::Matches($content, '"path"\s+"([^"]+)"') foreach ($match in $matches) { $path = $match.Groups[1].Value -replace '\\\\', '\' if ($path -and (Test-Path -LiteralPath $path)) { $libraries.Add(([IO.Path]::GetFullPath($path))) } } } return $libraries | Select-Object -Unique } function Find-MW2GamePath { foreach ($library in Get-SteamLibraries) { $steamApps = Join-Path $library "steamapps" foreach ($appId in @("10190", "10180")) { $manifest = Join-Path $steamApps "appmanifest_$appId.acf" if (-not (Test-Path -LiteralPath $manifest)) { continue } $manifestText = Get-Content -LiteralPath $manifest -Raw $installDirMatch = [regex]::Match($manifestText, '"installdir"\s+"([^"]+)"') if ($installDirMatch.Success) { $candidate = Join-Path $steamApps ("common\" + $installDirMatch.Groups[1].Value) if (Test-Path -LiteralPath (Join-Path $candidate "iw4mp.exe")) { return [IO.Path]::GetFullPath($candidate) } } } $fallback = Join-Path $steamApps "common\Call of Duty Modern Warfare 2" if (Test-Path -LiteralPath (Join-Path $fallback "iw4mp.exe")) { return [IO.Path]::GetFullPath($fallback) } } return $null } function Download-File { param( [Parameter(Mandatory)][uri]$Uri, [Parameter(Mandatory)][string]$Destination ) $bits = Get-Command Start-BitsTransfer -ErrorAction SilentlyContinue if ($bits) { Start-BitsTransfer -Source $Uri.AbsoluteUri -Destination $Destination } else { Invoke-WebRequest -Uri $Uri -OutFile $Destination -UseBasicParsing } } function New-Shortcut { param( [Parameter(Mandatory)][string]$ShortcutPath, [Parameter(Mandatory)][string]$TargetPath, [Parameter(Mandatory)][string]$WorkingDirectory, [string]$Arguments = "" ) $shell = New-Object -ComObject WScript.Shell $shortcut = $shell.CreateShortcut($ShortcutPath) $shortcut.TargetPath = $TargetPath $shortcut.WorkingDirectory = $WorkingDirectory $shortcut.Arguments = $Arguments $shortcut.IconLocation = "$TargetPath,0" $shortcut.Save() } if (-not (Test-IsAdministrator)) { Restart-Elevated } [Net.ServicePointManager]::SecurityProtocol = [Net.ServicePointManager]::SecurityProtocol -bor [Net.SecurityProtocolType]::Tls12 Write-Step "Locating the Steam MW2 installation" if ($GamePath) { $GamePath = [IO.Path]::GetFullPath($GamePath) } else { $GamePath = Find-MW2GamePath } if (-not $GamePath -or -not (Test-Path -LiteralPath (Join-Path $GamePath "iw4mp.exe"))) { throw @" A valid Steam installation of Modern Warfare 2 (2009) was not found. Install or verify "Call of Duty: Modern Warfare 2 (2009) - Multiplayer" in Steam, then run this script again. You can also specify: -GamePath "D:\SteamLibrary\steamapps\common\Call of Duty Modern Warfare 2" "@ } Write-Host "Game directory: $GamePath" $tempRoot = Join-Path $env:TEMP ("IW4x-Install-" + [guid]::NewGuid().ToString("N")) $zipPath = Join-Path $tempRoot "mw4.zip" $extractPath = Join-Path $tempRoot "extracted" $hashPath = Join-Path $tempRoot "mw4.zip.sha256" New-Item -ItemType Directory -Path $tempRoot, $extractPath -Force | Out-Null try { Write-Step "Downloading the IW4x package" Download-File -Uri ([uri]$PackageUrl) -Destination $zipPath if ((Get-Item -LiteralPath $zipPath).Length -lt 1MB) { throw "The downloaded file is unexpectedly small. Check $PackageUrl." } Write-Step "Checking the package hash" $hashUrl = "$PackageUrl.sha256" $hashDownloaded = $false try { Invoke-WebRequest -Uri $hashUrl -OutFile $hashPath -UseBasicParsing $hashDownloaded = $true } catch { Write-Warning "The optional checksum could not be downloaded from $hashUrl. Installation will continue over HTTPS." } if ($hashDownloaded) { $hashText = Get-Content -LiteralPath $hashPath -Raw $expectedMatch = [regex]::Match($hashText, '(?i)\b[0-9a-f]{64}\b') if (-not $expectedMatch.Success) { throw "The downloaded checksum file did not contain a valid SHA-256 value." } $expected = $expectedMatch.Value.ToUpperInvariant() $actual = (Get-FileHash -LiteralPath $zipPath -Algorithm SHA256).Hash.ToUpperInvariant() if ($actual -ne $expected) { throw "SHA-256 mismatch. Expected $expected but downloaded $actual. Installation was stopped." } Write-Host "SHA-256 verified: $actual" -ForegroundColor Green } Write-Step "Extracting and validating the package" Expand-Archive -LiteralPath $zipPath -DestinationPath $extractPath -Force $requiredItems = @( (Join-Path $extractPath "iw4x.exe"), (Join-Path $extractPath "iw4x.dll"), (Join-Path $extractPath "iw4x"), (Join-Path $extractPath "iw4x-launcher.exe") ) foreach ($requiredItem in $requiredItems) { if (-not (Test-Path -LiteralPath $requiredItem)) { throw "The package is missing required item: $requiredItem" } } Write-Step "Backing up an existing IW4x installation" $backupRoot = Join-Path $GamePath ("IW4x-backup-" + (Get-Date -Format "yyyyMMdd-HHmmss")) $backupCandidates = @( "iw4x.exe", "iw4x.dll", "iw4x-launcher.exe", "iw4x", "zone\patch" ) $createdBackup = $false foreach ($relativePath in $backupCandidates) { $source = Join-Path $GamePath $relativePath if (-not (Test-Path -LiteralPath $source)) { continue } $destination = Join-Path $backupRoot $relativePath $destinationParent = Split-Path -Parent $destination New-Item -ItemType Directory -Path $destinationParent -Force | Out-Null Move-Item -LiteralPath $source -Destination $destination -Force $createdBackup = $true } if ($createdBackup) { Write-Host "Previous IW4x files moved to: $backupRoot" } else { Remove-Item -LiteralPath $backupRoot -Recurse -Force -ErrorAction SilentlyContinue } Write-Step "Installing IW4x into the MW2 directory" Get-ChildItem -LiteralPath $extractPath -Force | ForEach-Object { Copy-Item -LiteralPath $_.FullName -Destination $GamePath -Recurse -Force } Get-ChildItem -LiteralPath $GamePath -Filter "iw4x*.exe" -File -ErrorAction SilentlyContinue | Unblock-File -ErrorAction SilentlyContinue Unblock-File -LiteralPath (Join-Path $GamePath "iw4x.dll") -ErrorAction SilentlyContinue if (-not $SkipShortcuts) { Write-Step "Creating desktop shortcuts" $desktop = [Environment]::GetFolderPath("Desktop") $launcher = Join-Path $GamePath "iw4x-launcher.exe" New-Shortcut ` -ShortcutPath (Join-Path $desktop "IW4x.lnk") ` -TargetPath $launcher ` -WorkingDirectory $GamePath if ($ServerAddress) { New-Shortcut ` -ShortcutPath (Join-Path $desktop "IW4x - Witten Server.lnk") ` -TargetPath $launcher ` -WorkingDirectory $GamePath ` -Arguments ('--game-args "+connect {0}"' -f $ServerAddress) } } Write-Step "Installation complete" Write-Host "IW4x directory: $GamePath" -ForegroundColor Green Write-Host "Server shortcut: $ServerAddress" -ForegroundColor Green Write-Host "" Write-Host "Launch IW4x from the new desktop shortcut." } finally { Remove-Item -LiteralPath $tempRoot -Recurse -Force -ErrorAction SilentlyContinue }