[CmdletBinding()] param( [ValidateSet('Debug', 'Release')] [string]$Configuration = 'Release', [string]$ClientPath = '', [string]$PackagePath = '', [switch]$Deploy, [switch]$NativeRenderer ) function Enable-LargeAddressAware { param( [Parameter(Mandatory)] [string]$Path ) $bytes = [System.IO.File]::ReadAllBytes($Path) if ($bytes.Length -lt 64) { throw "File is too small to be a valid PE executable: $Path" } # DOS header: MZ if ($bytes[0] -ne 0x4D -or $bytes[1] -ne 0x5A) { throw "DOS MZ signature was not found: $Path" } # IMAGE_DOS_HEADER.e_lfanew $peOffset = [BitConverter]::ToInt32($bytes, 0x3C) if ($peOffset -lt 0 -or ($peOffset + 24) -gt $bytes.Length) { throw "Invalid PE header offset in: $Path" } # PE\0\0 signature $peSignature = [BitConverter]::ToUInt32($bytes, $peOffset) if ($peSignature -ne 0x00004550) { throw "PE signature was not found: $Path" } # IMAGE_FILE_HEADER starts after the 4-byte PE signature. # Characteristics is at offset 18 inside IMAGE_FILE_HEADER. $characteristicsOffset = $peOffset + 4 + 18 $characteristics = [BitConverter]::ToUInt16( $bytes, $characteristicsOffset ) $newCharacteristics = [uint16]( $characteristics -bor 0x0020 ) if ($newCharacteristics -ne $characteristics) { $encoded = [BitConverter]::GetBytes($newCharacteristics) $bytes[$characteristicsOffset] = $encoded[0] $bytes[$characteristicsOffset + 1] = $encoded[1] [System.IO.File]::WriteAllBytes($Path, $bytes) Write-Host ( "LAA enabled for {0}: 0x{1:X4} -> 0x{2:X4}" -f $Path, $characteristics, $newCharacteristics ) -ForegroundColor Green } else { Write-Host ( "LAA is already enabled for {0}: 0x{1:X4}" -f $Path, $characteristics ) -ForegroundColor DarkGreen } } function Initialize-MissingSubmodules { param( [Parameter(Mandatory)] [string]$RepositoryPath ) $modulesFile = Join-Path $RepositoryPath '.gitmodules' if (-not (Test-Path -LiteralPath $modulesFile -PathType Leaf)) { return } $configuredModules = @( & git -C $RepositoryPath config --file .gitmodules --get-regexp '^submodule\..*\.path$' ) if ($LASTEXITCODE -gt 1) { throw "Failed to read submodules from $modulesFile" } foreach ($configuredModule in $configuredModules) { $fields = $configuredModule -split '\s+', 2 if ($fields.Count -ne 2) { continue } $relativePath = $fields[1].Trim() $modulePath = Join-Path $RepositoryPath $relativePath $gitMarker = Join-Path $modulePath '.git' # Updating an initialized submodule checks out the SHA recorded by its parent and detaches # any active development branch. Only initialize genuinely missing worktrees. if (-not (Test-Path -LiteralPath $gitMarker)) { Write-Host "Initializing missing submodule: $modulePath" & git -C $RepositoryPath submodule update --init -- $relativePath if ($LASTEXITCODE -ne 0) { throw "Failed to initialize submodule: $modulePath" } } if (Test-Path -LiteralPath $modulePath -PathType Container) { Initialize-MissingSubmodules -RepositoryPath $modulePath } } } $ErrorActionPreference = 'Stop' $repoRoot = $PSScriptRoot . (Join-Path $repoRoot 'pipeline-layout.ps1') $cmake = 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe' if (-not (Test-Path -LiteralPath $cmake)) { $cmakeCommand = Get-Command cmake -ErrorAction SilentlyContinue if (-not $cmakeCommand) { throw 'CMake 3.20+ was not found.' } $cmake = $cmakeCommand.Source } Initialize-MissingSubmodules -RepositoryPath $repoRoot $win32BuildDir = Join-Path $repoRoot 'build\warcraftxl-win32' $stockExe = Join-Path $repoRoot 'Wow_Original.exe' if ($Deploy -or -not [string]::IsNullOrWhiteSpace($PackagePath)) { if (-not (Test-Path -LiteralPath $stockExe -PathType Leaf)) { throw "Stock executable was not found: $stockExe" } $expectedStockHash = 'AA63A5750D60EF16746C686B3D5E26876D98953EAB08B1C026CD0FAF78E88CB8' $stockHash = (Get-FileHash -LiteralPath $stockExe -Algorithm SHA256).Hash if ($stockHash -ne $expectedStockHash) { throw "Wow_Original.exe is not the verified MoonWell 3.3.5a stock executable (SHA-256 $stockHash)." } } & $cmake -S $repoRoot -B $win32BuildDir -A Win32 '-DCLIENT_PATH=' if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Win32 configure failed.' } & $cmake --build $win32BuildDir --config $Configuration --parallel 4 if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Win32 build failed.' } $win32ArtifactDir = Join-Path $win32BuildDir "vendor\warcraftxl\$Configuration" $warcraftXL = Join-Path $win32ArtifactDir 'WarcraftXL.dll' $selectedProxy = Join-Path $win32ArtifactDir 'd3d9.dll' $recoveryProxy = Join-Path $win32BuildDir "artifacts\$Configuration\d3d9-native.dll" $extensionNames = Get-MoonWellExtensionNames if ($NativeRenderer) { Write-Warning '-NativeRenderer is no longer needed: WarcraftXL 1.1 uses native D3D9 by default.' } function Install-WarcraftXLArtifacts { param( [Parameter(Mandatory)] [string]$Destination ) Remove-MoonWellRuntimeGarbage -Destination $Destination $utils = Join-Path $Destination 'Utils' New-Item ` -ItemType Directory ` -Path $Destination, $utils ` -Force | Out-Null $destinationExe = Join-Path $Destination 'Wow.exe' Copy-Item ` -LiteralPath $stockExe ` -Destination $destinationExe ` -Force Enable-LargeAddressAware -Path $destinationExe Copy-Item ` -LiteralPath $warcraftXL ` -Destination (Join-Path $Destination 'WarcraftXL.dll') ` -Force Copy-Item ` -LiteralPath $selectedProxy ` -Destination (Join-Path $Destination 'd3d9.dll') ` -Force Copy-Item ` -LiteralPath $recoveryProxy ` -Destination (Join-Path $utils 'd3d9-native.dll') ` -Force foreach ($extensionName in $extensionNames) { $extensionSource = Join-Path $win32BuildDir "$Configuration\$extensionName.dll" $extensionDestination = Join-Path $Destination "Extensions\$extensionName" New-Item -ItemType Directory -Path $extensionDestination -Force | Out-Null Copy-Item ` -LiteralPath $extensionSource ` -Destination (Join-Path $extensionDestination "$extensionName.dll") ` -Force } } if ($Deploy) { if ([string]::IsNullOrWhiteSpace($ClientPath)) { $ClientPath = 'C:\Program Files (x86)\World of Warcraft' } $ClientPath = [System.IO.Path]::GetFullPath($ClientPath) if (-not (Test-Path -LiteralPath $ClientPath -PathType Container)) { throw "Client directory was not found: $ClientPath" } $clientExe = Join-Path $ClientPath 'Wow.exe' if (Test-Path -LiteralPath $clientExe -PathType Leaf) { $clientHash = (Get-FileHash -LiteralPath $clientExe -Algorithm SHA256).Hash if ($clientHash -ne $stockHash) { $legacyBackup = Join-Path $ClientPath 'Wow.moonwell-patched.backup.exe' if (-not (Test-Path -LiteralPath $legacyBackup)) { Copy-Item -LiteralPath $clientExe -Destination $legacyBackup } Write-Host "Legacy executable saved as $legacyBackup" } } Install-WarcraftXLArtifacts -Destination $ClientPath Write-Host "Installed WarcraftXL 1.1 runtime and extensions in $ClientPath" } if (-not [string]::IsNullOrWhiteSpace($PackagePath)) { $PackagePath = [System.IO.Path]::GetFullPath($PackagePath) Assert-SafePackagePath ` -PackagePath $PackagePath ` -RepositoryPath $repoRoot ` -ClientPath $ClientPath Install-WarcraftXLArtifacts -Destination $PackagePath Write-Host "Packaged stock Wow.exe and WarcraftXL artifacts in $PackagePath" } Write-Host "WarcraftXL 1.1 build complete (native D3D9 proxy): $win32ArtifactDir"