Four-monitor MFD bring-up on the new bench (MR_new: AMD FirePro W4100, 4 outputs,
Win10). Adds permanent display diagnostics, a Release-capable frame pacing logger,
a self-locating AppCompat shim installer, and settles -- empirically -- whether
dgVoodoo2 can be dropped for the multi-monitor MFD modes. It cannot.
WHY THIS WAS HARD
-----------------
CHSH_Device::InitFirst/InitSecond discarded EVERY HRESULT (SetCooperativeLevel,
SetDisplayMode, CreateSurface, GetAttachedSurface, QueryInterface, CreateDevice)
and returned true unconditionally. A panel that failed to open produced no error,
no crash and no log entry -- the monitor just stayed on the desktop. SPEW is
compiled out of shipping builds, so none of it was visible. Restoring that
visibility is what unblocked everything else.
DIAGNOSTICS ADDED (keep these)
------------------------------
* VideoCard.cpp -- LogDisplayDevices() writes gos-displays.txt next to the exe:
NumDevices/NumHWDevices/NumMonitors, every DirectDraw device with its
hw_rasterization flag, the role assignment (FullScreenDevice / g_nNonDualHead /
g_nDualHead / g_nDualHead2 / g_nMFD1 / g_nMFD2), per-slot -tmon APPLIED/REJECTED
(previously silent), and whether mode 4's "BOTH mfd1 and mfd2" requirement is met.
* render.cpp -- HSH_LogInit()/HSH_CheckHR()/HSH_HRName() log every DirectDraw call
in the panel init path with its HRESULT decoded by name (27 DDERR_* codes, all
verified present and collision-free against build-env/dx7asdk/include/ddraw.h).
Each line is opened/appended/closed individually so the log survives a crash.
* WinMain.cpp -- GOS_LogFrameRate() writes gos-fps.txt: per second, frame count,
average fps, 1% low (mean of the worst 1% of frames), worst frame in ms, and a
count of frames exceeding 2x average ("hitches"). Average fps alone cannot
distinguish 60fps from 60fps-with-a-dropped-frame-every-second; the 1% low can.
The engine's own FrameRate readout is #ifdef LAB_ONLY (MWMission.cpp) so it only
exists in MW4pro.exe; this works in Release, which is what runs on the pods.
NEW SWITCHES (both documented in -help)
---------------------------------------
* -fps Enable the frame pacing report. Off by default: the gate is the first
statement in GOS_LogFrameRate, so an unflagged run does no arithmetic
and does not even create the file. The file handle is held open for the
process lifetime -- opening/closing it every second would put a syscall
of unpredictable latency on the render thread, i.e. the measurement tool
perturbing what it measures.
* -tcoop <0-5> Selects the SetCooperativeLevel form used by the radar/MFD panels.
0 = legacy (unchanged shipped behaviour, remains the default).
Exists so every candidate fix could be compared on real hardware without
a rebuild between attempts.
CRASH-SAFETY FIX
----------------
hsh_initialized was set unconditionally after panel init, so a failed panel left
null surfaces and a null IDirect3DDevice7 behind and the per-frame path called
straight through them. Now:
- all four InitSecond overrides (CMR/CRadar/CMFD/CMFDRight) bail on base failure,
- CMFD_Device::InitFirst reports its real result instead of always returning true,
- hsh_initialized is only set when the panels genuinely came up.
A display failure now leaves the game running without MFDs instead of bombing to
desktop. Crash signature for the record: `call [ecx+0x44]` with ECX=0 is
IDirectDrawSurface7::GetDC on a never-created surface (vtable offset confirmed
against the DX7 header), reported as "Attempt to read from address 0x00000044".
APPCOMPAT SHIM INSTALLER (new)
------------------------------
build-env/set-appcompat.ps1 + set-appcompat.bat. Self-locating via $PSScriptRoot:
applies DWM8And16BitMitigation to the MW4 executables sitting next to it, wherever
that install lives. HKCU always, HKLM too when elevated (the HKLM value format
differs -- it carries a leading "$" marker -- so the two must not be interchanged).
Verifies by reading back; detects the HIGHDPIAWARE-only entry that SUPPRESSES the
automatic shim; supports -Remove and -WhatIfOnly. deploy-mw4.ps1 now ships both
files into every deployment.
This matters because the layer is keyed on the executable's FULL PATH -- any copy
of an install to another folder or machine silently loses it, and the resulting
error is actively misleading (see below).
WHAT WE LEARNED
---------------
* The AppCompat shim SYNTHESISES 16-bit display modes. Proved directly: the crash
dump shows "16 bit modes :" EMPTY without it and fully populated with it. MW4
renders at bitdepth=16 and modern GPUs expose no 16-bit modes at all.
* Without the shim, GameOS reports "Another application is preventing use of full
screen mode" (GOS_DXRASTERIZER_NOFULLSCREEN, DXRasterizer.cpp ~1125). That is a
catch-all fired after every SetDisplayMode attempt fails -- it even scans for
NetMeeting -- and it sends you looking for a conflicting program that does not
exist. The real cause is the missing shim.
* Exclusive fullscreen DOES work on Win10 with the system ddraw.dll and dgVoodoo2
physically removed, once the shim is applied to that exe path.
* Windowed mode works natively with no shim at all: the windowed path sets
Environment.bitDepth = DesktopBpp (32), so there is no mode switch. Verified for
the console/shell; a full mission windowed is still untested.
* Native DirectDraw enumerates all four W4100 outputs, so dgVoodoo2 was never
needed for device enumeration.
* The engine is 4:3 ONLY. ImageHlp.cpp ~464 asserts the complete supported set:
640x480, 512x384, 800x600, 960x720, 1024x768, 1280x1024 (5:4), 1600x1200. No
16:9 mode and no aspect correction anywhere in the codebase. On a 16:9 monitor
the scaler must adapt: plain stretch distorts geometry, keep-aspect pillarboxes.
* -2dt is not a recognised switch anywhere in the codebase, despite appearing in
production ctcl.ini launch lines. Completely inert.
* NumHWDevices (5) can exceed NumDevices (4): it counts D3D device-enumeration
callbacks, and an adapter exposing both a HAL and a T&L HAL yields two. Benign.
WHAT WE TRIED AND WHY IT FAILED
-------------------------------
The panel cooperative-level call was genuinely wrong -- a latent 2002 bug. Every
panel asked to be BOTH the process focus window AND its own device window, on the
one shared hWindow, after the main device had already taken exclusive mode on it.
The main device (DXRasterizer.cpp ~1027) already uses the correct two-call idiom
(SETFOCUSWINDOW alone, then EXCLUSIVE|FULLSCREEN) -- tagged //sanghoon, the same
author. The panels never were.
Results on real hardware, no dgVoodoo2, shim applied, -tmfds 4:
-tcoop 0 SETFOCUSWINDOW|CREATEDEVICEWINDOW|ALLOWREBOOT|EXCLUSIVE|FULLSCREEN
-> DDERR_EXCLUSIVEMODEALREADYSET
-tcoop 1 CREATEDEVICEWINDOW|EXCLUSIVE|FULLSCREEN (no focus claim)
-> DDERR_INVALIDPARAMS (CREATEDEVICEWINDOW needs a focus window)
-tcoop 2 SETFOCUSWINDOW, then CREATEDEVICEWINDOW|EXCLUSIVE|FULLSCREEN
-> DDERR_INVALIDPARAMS
-tcoop 3 SETFOCUSWINDOW, then EXCLUSIVE|FULLSCREEN
-> first panel collides, but that collision STEALS exclusive mode from
the main display, after which panels 2 and 3 fully initialise (the
radar reached CreateDevice(HAL) = DD_OK -- a secondary panel running
entirely on native DirectDraw). Side effect: the desktop was left at
1920x1080 16bpp. Not viable.
-tcoop 4 EXCLUSIVE|FULLSCREEN only -> EXCLUSIVEMODEALREADYSET, all panels
-tcoop 5 ALLOWREBOOT|EXCLUSIVE|FULLSCREEN -> EXCLUSIVEMODEALREADYSET, all panels
CONCLUSION: on modern Windows only ONE DirectDraw object per process may hold
exclusive fullscreen. The main display takes it; every secondary panel is refused.
XP allowed multiple. dgVoodoo2 allows it because it is a full reimplementation of
ddraw and is not bound by that rule -- it is not papering over a bug we can fix.
=> dgVoodoo2 CANNOT be removed for -tmfds 1/3/4 by correcting these flags. The
default stays -tcoop 0. The switch is retained because it is how this was settled
and it will re-settle it on different hardware.
The only native path is a borderless windowed panel design (DDSCL_NORMAL + clipper
per monitor, no exclusive mode anywhere). Assessment and staged plan are recorded
in CLAUDE.md STEP 10; not started.
WORKING 4-MONITOR CONFIG (with dgVoodoo2)
-----------------------------------------
dgVoodoo2 Scaling mode MUST be "Stretched, Keep Aspect Ratio". Plain "Stretched"
fails silently: main and radar go fullscreen black, both MFD monitors keep showing
the desktop, and every DirectDraw call still returns DD_OK -- the devices are alive
but dgVoodoo2 never drives those outputs. Diagnosed with a temporary per-panel
colour-flash test (since removed), which also established that device index maps
1:1 to physical monitor on this bench, so -tmon 1,2,3,4 equals auto-detection.
Confirmed working end to end: all three secondary panels present, full mission
played.
KNOWN GAP: the working dgVoodoo.conf is still not versioned in the repo (removed
in 0ceba9c7), so a fresh deploy will reproduce the silent MFD failure.
Behaviour with no new switches supplied is unchanged from the previous build
except on failure paths, which now degrade gracefully instead of crashing.
Co-authored-by: Claude Opus 5 (Anthropic) <noreply@anthropic.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
245 lines
15 KiB
PowerShell
245 lines
15 KiB
PowerShell
<#
|
|
deploy-mw4.ps1 - assemble a runnable copy of BattleTech:FireStorm at c:\VWE\firestorm\MW4
|
|
|
|
RULES-BASED deployment, sourced entirely from the working directory (c:\VWE\firestorm).
|
|
No external/image paths are read.
|
|
|
|
Sources:
|
|
Runtime = c:\VWE\firestorm\Gameleap\mw4 (dev superset: runtime DLLs, resources, config)
|
|
BinDir = c:\VWE\firestorm\Gameleap\code\rel.bin (our freshly built binaries)
|
|
|
|
The dev runtime tree is bloated (~6 GB) with source content and dev-only data.
|
|
A clean runnable deployment is ~900 MB. These rules select the runtime subset:
|
|
|
|
KEEP (whole): Resource\, hsh\, Assets\, Stats\ (compiled .mw4 packages etc.)
|
|
KEEP (subset): Content\shellscripts\files\ ONLY (runtime *_music.wav). The loose .script/.h
|
|
sources are redundant -- they're packed into props.mw4 and the engine loads
|
|
packed resources before loose disk files (s_DiskFirst defaults to false).
|
|
KEEP (root): *.dll *.ini *.options *.txt *.rtf *.ico *.m4c *.sem *.016 *.256
|
|
plus clokspl.exe (redistributable loader). (.lnk/.h dropped; no runtime use.)
|
|
DROP: Content\* (except shellscripts\files), Movies\, Notes\, fonts\, tool exes,
|
|
Thumbs.db, dead .lnk shortcuts, source headers, test/variant .ini, editor EULA.
|
|
OPTIMIZE: hsh\mechs\*.bmp reduced from 24-bit to 256-color (8bpp) in the deployed copy
|
|
(matches production; ~3x smaller, no runtime quality loss; source untouched).
|
|
OVERLAY: fresh build binaries from rel.bin (MW4.exe + the DLLs/EXEs we compile)
|
|
|
|
Re-runnable: overlay always runs last so deployed binaries are the fresh build.
|
|
|
|
RESOURCE PACKING: by default this first repacks resource\*.mw4 from the Content\ source tree
|
|
(via build-resources.ps1) so the deployed packages reflect current source -- including loose
|
|
edits like Content\ShellScripts\*.script that would otherwise be shadowed by stale .mw4 files
|
|
(the runtime loads packed resources before loose disk files; s_DiskFirst defaults to false).
|
|
Pass -SkipResourceBuild to deploy the existing prebuilt packages as-is.
|
|
#>
|
|
|
|
param(
|
|
[string]$Runtime = "c:\VWE\firestorm\Gameleap\mw4",
|
|
[string]$BinDir = "c:\VWE\firestorm\Gameleap\code\rel.bin",
|
|
[string]$Dest = "c:\VWE\firestorm\MW4",
|
|
[switch]$SkipResourceBuild,
|
|
[int] $ResourceBuildTimeoutSec = 1800
|
|
)
|
|
$ErrorActionPreference = "Stop"
|
|
Add-Type -AssemblyName PresentationCore # WPF imaging, for the 256-color BMP optimization
|
|
|
|
function Robo($src,$dst,$extra) {
|
|
# /XF Thumbs.db: never copy Windows thumbnail caches that litter the dev tree.
|
|
$a = @("`"$src`"","`"$dst`"","/E","/R:1","/W:1","/NFL","/NDL","/NP","/NJH","/XF","Thumbs.db") + $extra
|
|
$p = Start-Process robocopy -ArgumentList $a -Wait -PassThru -NoNewWindow
|
|
if ($p.ExitCode -ge 8) { throw "robocopy '$src' failed (code $($p.ExitCode))" }
|
|
}
|
|
|
|
# Reduce a 24-bit BMP to 256-color (8bpp indexed) in place, matching production's hsh\mechs art.
|
|
# Returns bytes saved, or -1 if skipped (already 8bpp) / failed. ~3x size reduction, no visible
|
|
# loss on these small mech thumbnails. Uses WPF FormatConvertedBitmap with an optimal palette.
|
|
function Convert-BmpTo8bit($path) {
|
|
try {
|
|
$fs = [System.IO.File]::OpenRead($path)
|
|
$dec = New-Object System.Windows.Media.Imaging.BmpBitmapDecoder($fs,
|
|
[System.Windows.Media.Imaging.BitmapCreateOptions]::PreservePixelFormat,
|
|
[System.Windows.Media.Imaging.BitmapCacheOption]::OnLoad)
|
|
$src = $dec.Frames[0]; $fs.Close() # OnLoad caches in memory; file now free to overwrite
|
|
if ($src.Format -eq [System.Windows.Media.PixelFormats]::Indexed8) { return -1 } # already optimized
|
|
$before = (Get-Item $path).Length
|
|
$pal = New-Object System.Windows.Media.Imaging.BitmapPalette($src,256)
|
|
$conv = New-Object System.Windows.Media.Imaging.FormatConvertedBitmap
|
|
$conv.BeginInit(); $conv.Source=$src
|
|
$conv.DestinationFormat=[System.Windows.Media.PixelFormats]::Indexed8
|
|
$conv.DestinationPalette=$pal; $conv.EndInit()
|
|
$enc = New-Object System.Windows.Media.Imaging.BmpBitmapEncoder
|
|
$enc.Frames.Add([System.Windows.Media.Imaging.BitmapFrame]::Create($conv))
|
|
$os = [System.IO.File]::Create($path); $enc.Save($os); $os.Close()
|
|
return ($before - (Get-Item $path).Length)
|
|
} catch { Write-Warning " BMP convert failed: $path ($($_.Exception.Message))"; return -1 }
|
|
}
|
|
|
|
# Return the canonical set of game resource packages (lowercased "resource\...\X.mw4" keys) by
|
|
# parsing the build manifest tree (Content\MechWarrior4.build -> child .build files -> each one's
|
|
# [resource\X.mw4] page). The shared dev resource\ also holds EDITOR-only extras (extra maps,
|
|
# _backup sets); this lets the game deploy ship only the packages the game actually references.
|
|
function Get-GamePackages($contentDir) {
|
|
$set = New-Object 'System.Collections.Generic.HashSet[string]'
|
|
$master = Join-Path $contentDir "MechWarrior4.build"
|
|
if (-not (Test-Path $master)) { return $set }
|
|
$refs = [regex]::Matches((Get-Content $master -Raw), '([^\s={}]+\.build)') |
|
|
ForEach-Object { $_.Groups[1].Value } | Sort-Object -Unique
|
|
foreach ($ref in $refs) {
|
|
$bf = Join-Path $contentDir $ref
|
|
if (-not (Test-Path $bf)) { continue }
|
|
foreach ($ln in (Get-Content $bf)) {
|
|
if ($ln -match '\[\s*(resource\\[^\]]+\.mw4)\s*\]') { # -match is case-insensitive
|
|
[void]$set.Add(($Matches[1] -replace '/','\').ToLower()); break
|
|
}
|
|
}
|
|
}
|
|
return $set
|
|
}
|
|
|
|
Write-Host "== MW4 rules-based deployment ==" -ForegroundColor Cyan
|
|
Write-Host " runtime: $Runtime"
|
|
Write-Host " build : $BinDir"
|
|
Write-Host " dest : $Dest`n"
|
|
if (-not (Test-Path $Runtime)) { throw "Runtime source not found: $Runtime" }
|
|
if (-not (Test-Path $BinDir)) { throw "Build output not found: $BinDir (build MW4Application first)" }
|
|
if (Test-Path $Dest) { Write-Host " (dest exists, refreshing in place)`n" }
|
|
New-Item -ItemType Directory -Force -Path $Dest | Out-Null
|
|
|
|
# 0) Pack resources from source BEFORE copying, so the deployed resource\*.mw4 are current.
|
|
if ($SkipResourceBuild) {
|
|
Write-Host "[0/5] Resource packing SKIPPED (-SkipResourceBuild); deploying prebuilt packages.`n" -ForegroundColor DarkYellow
|
|
} else {
|
|
Write-Host "[0/5] Packing resources from source (resource\*.mw4) ..." -ForegroundColor Yellow
|
|
& (Join-Path $PSScriptRoot "build-resources.ps1") -Runtime $Runtime -BinDir $BinDir -TimeoutSec $ResourceBuildTimeoutSec
|
|
Write-Host ""
|
|
}
|
|
|
|
# 1) Runtime dirs. Resource is copied SELECTIVELY -- only the canonical game packages from the
|
|
# build manifest (+ each one's .dep, + non-package metadata .nfo/.tga/.txt). The shared dev
|
|
# resource\ also holds editor-only extras (extra maps, _backup sets) that must NOT ship.
|
|
# hsh/Assets/Stats copy whole (Thumbs.db excluded by Robo).
|
|
Write-Host "[1/5] Resource (game packages only), hsh, Assets, Stats ..." -ForegroundColor Yellow
|
|
$gamePkgs = Get-GamePackages (Join-Path $Runtime "Content")
|
|
if ($gamePkgs.Count -eq 0) { throw "Manifest parse returned 0 game packages -- check Content\MechWarrior4.build" }
|
|
$srcRes = Join-Path $Runtime "resource"; $dstRes = Join-Path $Dest "Resource"
|
|
$rc = 0; $rs = 0
|
|
if (Test-Path $srcRes) {
|
|
Get-ChildItem $srcRes -Recurse -File | ForEach-Object {
|
|
$rel = $_.FullName.Substring($srcRes.Length).TrimStart('\')
|
|
if ($_.Name -ieq "Thumbs.db") { $rs++; return }
|
|
if ($rel -imatch '^Pilots\\') { return } # player profiles -> copied verbatim below (not package-filtered)
|
|
$ext = $_.Extension.ToLower()
|
|
if ($ext -eq ".mw4" -or $ext -eq ".dep") {
|
|
$key = ("resource\" + ($rel -replace '\.dep$','.mw4')).ToLower()
|
|
if (-not $gamePkgs.Contains($key)) { $rs++; return } # editor-only package -> skip
|
|
}
|
|
$dst = Join-Path $dstRes $rel
|
|
$dd = Split-Path $dst -Parent
|
|
if (-not (Test-Path $dd)) { New-Item -ItemType Directory -Force $dd | Out-Null }
|
|
Copy-Item $_.FullName $dst -Force; $rc++
|
|
}
|
|
}
|
|
Write-Host (" Resource: {0} files copied ({1} game packages + metadata), {2} skipped (editor-only/cruft)" -f $rc,$gamePkgs.Count,$rs)
|
|
# Pilot profiles (Resource\Pilots\*) are player data, NOT content packages -- copy verbatim
|
|
# (each pilot's options.mw4 has a .mw4 extension but isn't in the manifest, so the package
|
|
# filter above would drop it). Robo preserves the per-pilot Games\ subtree too.
|
|
$srcPilots = Join-Path $srcRes "Pilots"
|
|
if (Test-Path $srcPilots) {
|
|
Robo $srcPilots (Join-Path $dstRes "Pilots") @()
|
|
Write-Host " Resource\Pilots copied verbatim (player profiles)"
|
|
}
|
|
foreach ($d in "hsh","Assets","Stats") {
|
|
if (Test-Path (Join-Path $Runtime $d)) { Robo (Join-Path $Runtime $d) (Join-Path $Dest $d) @() }
|
|
}
|
|
|
|
# 2) Content subset: ONLY shellscripts\files (runtime audio: *_music.wav). The loose
|
|
# .script/.h/.hpp sources are redundant -- they're packed into props.mw4 and the engine reads
|
|
# packed resources before loose disk files (s_DiskFirst=false), so they'd never be loaded.
|
|
# Production ships only files\ as well.
|
|
Write-Host "[2/5] Content\shellscripts\files (runtime audio only) ..." -ForegroundColor Yellow
|
|
$ssf = Join-Path $Runtime "Content\shellscripts\files"
|
|
if (Test-Path $ssf) { Robo $ssf (Join-Path $Dest "Content\shellscripts\files") @() }
|
|
|
|
# 3) Root runtime files by extension whitelist (.lnk/.h excluded -- shortcuts & source headers
|
|
# have no runtime use). Plus a name-based skip list of dev-only files absent from a production
|
|
# install: editor EULA, alternate icon, empty dev logs, and test/variant config .ini files.
|
|
Write-Host "[3/5] Root runtime files (dlls, config, localization) ..." -ForegroundColor Yellow
|
|
$keepExt = ".dll",".ini",".options",".txt",".rtf",".ico",".m4c",".sem",".016",".256"
|
|
# Dev-only DLLs that must NOT be deployed: dbghelp.dll/imagehlp.dll are SYSTEM dlls
|
|
# whose 2003 copies shadow Windows' and break DirectDraw init (DDERR_NODIRECTDRAWHW);
|
|
# the rest are tool/optional dlls the runtime game (per known-good) does not use.
|
|
# ddraw.dll is DDrawCompat -- the editor installs it into the shared data tree for its windowed
|
|
# viewport, but the fullscreen game must keep native DirectDraw, so never ship it here.
|
|
$skipDll = "dbghelp.dll","imagehlp.dll","ijl10.dll","ctcl.dll","Language.oeg.dll","Resources.dll","ddraw.dll",
|
|
"D3DImm.dll","D3D8.dll","D3D9.dll" # dgVoodoo2 -- game uses AppCompat shim, not dgVoodoo
|
|
$skipName = "edeula.rtf","mech4.ico","debuglog.txt","erfviewer.txt","servercyclex.txt","tctd.ini",
|
|
"options-ext.ini","options-cam-var.ini","options-game-var.ini","options-mr-var.ini",
|
|
"ctcl-camera-test.ini","ctcl-game-test.ini","ctcl-mr-test.ini"
|
|
Get-ChildItem $Runtime -File |
|
|
Where-Object { ($keepExt -contains $_.Extension.ToLower()) -and ($skipDll -notcontains $_.Name) -and ($skipName -notcontains $_.Name) } |
|
|
ForEach-Object { Copy-Item $_.FullName (Join-Path $Dest $_.Name) -Force }
|
|
# clokspl.exe is a runtime loader (the only non-built exe the clean deploy keeps)
|
|
if (Test-Path (Join-Path $Runtime "clokspl.exe")) { Copy-Item (Join-Path $Runtime "clokspl.exe") $Dest -Force }
|
|
|
|
# 4) Overlay freshly built binaries
|
|
Write-Host "[4/5] Overlaying fresh build artifacts from rel.bin ..." -ForegroundColor Yellow
|
|
$artifacts = "MW4.exe","MW4pro.exe","Launcher.exe","autoconfig.exe","mw4print.exe","MissionLang.dll","ScriptStrings.dll","ctcls.dll"
|
|
foreach ($a in $artifacts) {
|
|
$src = Join-Path $BinDir $a
|
|
if (Test-Path $src) { Copy-Item $src (Join-Path $Dest $a) -Force; Write-Host (" + {0,-18} {1:N0} bytes" -f $a,(Get-Item $src).Length) }
|
|
else { Write-Warning " build artifact missing: $a" }
|
|
}
|
|
|
|
# 4b) Apply the DirectDraw 16-bit compatibility shim (REQUIRED on Win10/11).
|
|
# MW4 runs at bitdepth=16; without DWM8And16BitMitigation it fails graphics init
|
|
# with DDERR_NODIRECTDRAWHW. Per-user (HKCU) needs no admin. The matching all-users
|
|
# (HKLM) entry is in build-env\mw4-compat-hklm.reg (import elevated for full parity).
|
|
Write-Host "[4b ] Applying DirectDraw compat shim (HKCU) for $Dest\MW4.exe ..." -ForegroundColor Yellow
|
|
$layers = "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
|
|
if (-not (Test-Path $layers)) { New-Item -Path $layers -Force | Out-Null }
|
|
Set-ItemProperty -Path $layers -Name "$Dest\MW4.exe" -Value "DWM8And16BitMitigation HIGHDPIAWARE" -Type String
|
|
Write-Host " set: $Dest\MW4.exe = DWM8And16BitMitigation HIGHDPIAWARE"
|
|
|
|
# 4b-2) Ship the standalone shim installer with the game.
|
|
# The AppCompat layer is keyed on the executable's FULL PATH, so any copy of this
|
|
# deployment made to another folder or machine silently loses it -- and the resulting
|
|
# failure ("Another application is preventing use of full screen mode") points at the
|
|
# wrong cause entirely. set-appcompat.bat re-applies the shim for whatever folder it
|
|
# is sitting in, so every install can fix itself with one click.
|
|
foreach ($f in @("set-appcompat.ps1", "set-appcompat.bat")) {
|
|
$src = Join-Path $PSScriptRoot $f
|
|
if (Test-Path $src) {
|
|
Copy-Item $src (Join-Path $Dest $f) -Force
|
|
Write-Host " shipped: $f"
|
|
} else {
|
|
Write-Warning " missing: $src"
|
|
}
|
|
}
|
|
|
|
# 4c) Optimize hsh\mechs preview BMPs to 256-color (8bpp). The dev tree stores them as 24-bit
|
|
# (~600 KB each, ~23 MB total); production ships 256-color (~200 KB). Converts the DEPLOYED
|
|
# copies only -- source 24-bit originals are untouched. Idempotent (skips already-8bpp).
|
|
Write-Host "[4c ] Optimizing hsh\mechs BMPs to 256-color ..." -ForegroundColor Yellow
|
|
$mechDir = Join-Path $Dest "hsh\mechs"
|
|
$saved = 0L; $cnt = 0
|
|
if (Test-Path $mechDir) {
|
|
Get-ChildItem $mechDir -Filter *.bmp | ForEach-Object {
|
|
$d = Convert-BmpTo8bit $_.FullName
|
|
if ($d -ge 0) { $saved += $d; $cnt++ }
|
|
}
|
|
Write-Host (" converted {0} BMP(s), saved {1:N1} MB" -f $cnt,($saved/1MB))
|
|
} else { Write-Host " (no hsh\mechs dir)" }
|
|
|
|
# 5) Report
|
|
Write-Host "[5/5] Deployment summary:" -ForegroundColor Yellow
|
|
$exe = Join-Path $Dest "MW4.exe"
|
|
if (-not (Test-Path $exe)) { throw "MW4.exe missing from $Dest" }
|
|
$files = Get-ChildItem $Dest -Recurse -File
|
|
$mb = ($files | Measure-Object Length -Sum).Sum/1MB
|
|
Write-Host (" {0} : {1:N0} files, {2:N0} MB" -f $Dest, $files.Count, $mb) -ForegroundColor Green
|
|
Write-Host (" MW4.exe {0:N0} bytes ({1})" -f (Get-Item $exe).Length,(Get-Item $exe).LastWriteTime) -ForegroundColor Green
|
|
" per top-level:"
|
|
Get-ChildItem $Dest -Directory | ForEach-Object {
|
|
$m=(Get-ChildItem $_.FullName -Recurse -File -EA SilentlyContinue|Measure-Object Length -Sum).Sum/1MB
|
|
" {0,8:N1} MB {1}" -f $m,$_.Name }
|
|
Write-Host "`nDone. Runnable game deployed to $Dest" -ForegroundColor Cyan
|