Display diagnostics + -fps/-tcoop; native multi-monitor MFD ruled out
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>
This commit is contained in:
co-authored by
Claude Opus 5
GitHub Copilot
parent
f402e5becc
commit
9c67dddd83
@@ -937,7 +937,162 @@ Docs updates:
|
||||
- missing `MissionName` now falls back to first map for that game type,
|
||||
- added `NoReturn` semantics and examples.
|
||||
|
||||
## 📋 STEP 10: Four-monitor MFD bring-up, display diagnostics, and the native-DirectDraw
|
||||
## exclusive-mode wall (2026-07-25)
|
||||
|
||||
A long, dense session on the new 4-monitor bench (`MR_new`: AMD FirePro W4100, 4 outputs,
|
||||
Win10). Everything below is empirical — measured on real hardware, not inferred.
|
||||
|
||||
### The root problem that made all of this hard
|
||||
`CHSH_Device::InitFirst` / `InitSecond` (`CoreTech\Libraries\GameOS\render.cpp`) **discarded
|
||||
every single `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 simply stayed on the desktop. SPEW is compiled out of shipping builds, so there
|
||||
was no way to see any of it. **Fixing that visibility was what unblocked the whole session.**
|
||||
|
||||
### ✅ Diagnostics added (KEEP THESE — they are why everything below was findable)
|
||||
- **`gos-displays.txt`** (written next to the exe by `VideoCard.cpp` `LogDisplayDevices()`):
|
||||
`NumDevices`/`NumHWDevices`/`NumMonitors`, every DirectDraw device + `hw_rasterization`,
|
||||
the role assignment (`FullScreenDevice`/`g_nNonDualHead`/`g_nDualHead`/`g_nDualHead2`/
|
||||
`g_nMFD1`/`g_nMFD2`), `-tmon` APPLIED/REJECTED per slot, and the decisive
|
||||
"mode 4 requires BOTH mfd1 and mfd2" line.
|
||||
- **Per-call HRESULT logging** in `InitFirst`/`InitSecond` via `HSH_LogInit()` / `HSH_CheckHR()`
|
||||
/ `HSH_HRName()` (27 `DDERR_*` codes decoded by name). Appends to the same file; each line is
|
||||
written and flushed individually so **the log survives a crash**.
|
||||
- **`gos-fps.txt`** — new **`-fps`** switch (`WinMain.cpp` `GOS_LogFrameRate`, hooked onto the
|
||||
existing `frameRate` global). Per-second: frames, avg fps, **1% low**, worst frame in ms,
|
||||
and a count of frames over 2x average ("hitches"). Works in **Release** — the engine's own
|
||||
`AddDebugData("FrameRate")` is `#ifdef LAB_ONLY` (`MWMission.cpp`) so it only exists in
|
||||
`MW4pro.exe`. Off unless `-fps` is given (a global read + branch when absent, no file
|
||||
created). Handle is held open for the process lifetime so the once-per-second write does not
|
||||
put an unpredictable syscall on the render thread — the tool must not perturb what it measures.
|
||||
|
||||
### ✅ Crash-safety fix (independent of everything else, worth keeping)
|
||||
`hsh_initialized` was set **unconditionally** after panel init. A failed panel therefore 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 the real result instead of always `true`, and
|
||||
`hsh_initialized` is only set when the panels genuinely came up → **the game runs without MFDs
|
||||
instead of dying**.
|
||||
- **Crash signature to recognise:** `call [ecx+0x44]` with `ECX=0` = `IDirectDrawSurface7::GetDC`
|
||||
on a never-created surface (verified against `build-env\dx7asdk\include\ddraw.h` vtable order).
|
||||
Reported as `EXCEPTION : Attempt to read from address 0x00000044`.
|
||||
|
||||
### ✅ AppCompat shim — the single most important operational fact
|
||||
**`DWM8And16BitMitigation` is keyed on the executable's FULL PATH.** A copy of the game at a
|
||||
new path silently loses it. MW4 renders at `bitdepth=16` and **modern GPUs expose ZERO 16-bit
|
||||
display modes** — the shim *synthesises* them (proved directly: crash dump shows
|
||||
`16 bit modes :` EMPTY without the shim, populated with it).
|
||||
- **Without the shim the error message actively misleads.** GameOS raises
|
||||
`GOS_DXRASTERIZER_NOFULLSCREEN` — *"Another application is preventing use of full screen
|
||||
mode"* (`DXRasterizer.cpp:~1125`). That is a **catch-all** fired after every `SetDisplayMode`
|
||||
attempt fails; it even scans for NetMeeting. It sends you hunting for a conflicting program
|
||||
that does not exist. The real cause is the missing shim / absent 16-bit modes.
|
||||
- **NEW: `build-env\set-appcompat.ps1` + `set-appcompat.bat`** — self-locating (`$PSScriptRoot`)
|
||||
one-click installer. Applies the layer to `MW4.exe`/`MW4pro.exe`/`MW4Ed2.exe` sitting next to
|
||||
it, wherever that install lives; HKCU always, HKLM too if elevated (note the HKLM value format
|
||||
differs — it carries a leading `$` marker). Verifies by reading back; detects the
|
||||
`HIGHDPIAWARE`-only entry that *suppresses* the auto-shim (the original STEP 5 bug).
|
||||
`-Remove` / `-WhatIfOnly` supported. **`deploy-mw4.ps1` now ships both into every deployment.**
|
||||
|
||||
### ✅ Exclusive fullscreen DOES work on Win10 without dgVoodoo2
|
||||
Confirmed on the W4100 with system `ddraw.dll` and dgVoodoo2 physically removed: `MW4.exe`
|
||||
alt-enters to exclusive fullscreen correctly **once the shim is applied to that exe path**.
|
||||
Windowed mode also works natively with no shim at all (windowed sets
|
||||
`Environment.bitDepth = DesktopBpp` = 32, so there is no mode switch). Console/shell confirmed
|
||||
windowed; a **full mission windowed is still untested**.
|
||||
|
||||
### ❌ Native multi-monitor MFD is BLOCKED — and no flag combination fixes it
|
||||
The panels' 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`, same author. The
|
||||
panels never were.
|
||||
|
||||
A new **`-tcoop <0-5>`** switch was added to test every plausible form on real hardware without a
|
||||
rebuild between attempts. Results (no dgVoodoo2, shim applied, `-tmfds 4`):
|
||||
|
||||
| `-tcoop` | Flags | Result |
|
||||
|---|---|---|
|
||||
| 0 | `SETFOCUSWINDOW\|CREATEDEVICEWINDOW\|ALLOWREBOOT\|EXCLUSIVE\|FULLSCREEN` (legacy) | `DDERR_EXCLUSIVEMODEALREADYSET` |
|
||||
| 1 | `CREATEDEVICEWINDOW\|EXCLUSIVE\|FULLSCREEN` (no focus claim) | `DDERR_INVALIDPARAMS` |
|
||||
| 2 | `SETFOCUSWINDOW`, then `CREATEDEVICEWINDOW\|…` | `DDERR_INVALIDPARAMS` |
|
||||
| 3 | `SETFOCUSWINDOW`, then `EXCLUSIVE\|FULLSCREEN` | 1st panel collides; **that collision steals exclusive from the main display**, after which panels 2+3 fully init (radar reached `CreateDevice(HAL) = DD_OK`). Side effect: desktop left at 1920x1080 **16bpp**. Not viable. |
|
||||
| 4 | `EXCLUSIVE\|FULLSCREEN` only | `DDERR_EXCLUSIVEMODEALREADYSET` on **all** panels |
|
||||
| 5 | `ALLOWREBOOT\|EXCLUSIVE\|FULLSCREEN` | `DDERR_EXCLUSIVEMODEALREADYSET` on **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, not bound by that rule.**
|
||||
=> **dgVoodoo2 cannot be removed for `-tmfds 1/3/4` by fixing flags.** Default stays `-tcoop 0`.
|
||||
(`-tcoop` is retained: it is how this was settled and will re-settle it on different hardware.)
|
||||
|
||||
### ✅ Working 4-monitor config (WITH dgVoodoo2)
|
||||
- **dgVoodoo2 Scaling mode MUST be "Stretched, Keep Aspect Ratio".** Plain "Stretched" fails
|
||||
*silently*: main + 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).
|
||||
- Device index → physical monitor is **1:1** on this bench (colour test confirmed): main=0,
|
||||
radar=1, mfd-left=2, mfd-right=3, so `-tmon 1,2,3,4` is identical to auto-detection.
|
||||
- **Confirmed working end-to-end: all three secondary panels present, full mission played.**
|
||||
- ⚠️ **The working `dgVoodoo.conf` is NOT versioned in the repo** (it was removed in `0ceba9c7`).
|
||||
A fresh deploy will reproduce the silent MFD failure. TODO: commit it + have `deploy-mw4.ps1`
|
||||
place it.
|
||||
|
||||
### 📐 Engine is 4:3 ONLY (relevant to every display decision)
|
||||
`ImageHlp.cpp:~464` asserts the complete supported resolution set: **640x480, 512x384, 800x600,
|
||||
960x720, 1024x768, 1280x1024 (5:4), 1600x1200**. There is no 16:9 mode and **no aspect
|
||||
correction anywhere** in the codebase (the only `aspect` hits are texture-dimension caps and
|
||||
CameraShip). On a 16:9 monitor a 4:3 image must be adapted by the scaler: plain stretch =
|
||||
distorted (circles → ovals, reticle wrong); keep-aspect = correct geometry + pillarbox bars.
|
||||
Native widescreen would require re-authoring every 2D/shell layout (all fixed 640x480 pixel
|
||||
coordinates) — a content project, not a code tweak.
|
||||
|
||||
### 📐 Assessment: moving to borderless windowed (discussed, NOT started)
|
||||
The only native path to multi-monitor, since it removes exclusive mode from the picture entirely.
|
||||
Smaller than it sounds because the mechanisms already exist:
|
||||
- The windowed present is already `wBlt(FrontBufferSurface, &Window, BackBufferSurface, …)` —
|
||||
a blit to an arbitrary screen rect. `Blt` stretches when the dest rect differs in size, so
|
||||
aspect-preserving borderless output is **rect maths, not new machinery**.
|
||||
- Clipper wrappers (`wCreateClipper`/`wSetClipper`/`wSetHWnd`) already exist in `DirectDraw.cpp`,
|
||||
unused by the fullscreen path.
|
||||
- **None of the drawing code changes** — panels already render into `pDDSTarget` and composite;
|
||||
`DrawQuad`/`DrawTexture`/fonts/`SwapRightState` don't care whether the present is Flip or Blt.
|
||||
- Work is confined to `render.cpp`, `DXRasterizer.cpp`, `WinMain.cpp` (+ a little `Windows.cpp`):
|
||||
panel init → `DDSCL_NORMAL` + clipper + offscreen render surface (~150 lines); ~4 panel `Flip`
|
||||
sites → `Blt`; break the `HSH_EnterFullScreen2()` ↔ `EnterFullScreenMode()` coupling
|
||||
(`DXRasterizer.cpp:~1132` is its ONLY call site, which is also why windowed mode currently
|
||||
creates no panels at all); **new: explicit frame pacing** (windowed Blt has no vsync — this is
|
||||
the same root cause as the known mechlab fast-spin bug).
|
||||
- **Gains:** no dgVoodoo2, no shim, no 16-bit dependency, no exclusive-mode contention, aspect
|
||||
under our control, and panels stay at their monitor's native mode so "this panel won't accept
|
||||
640x480" becomes impossible.
|
||||
- **Risk that decides it:** windowed D3D7 device creation is per-GPU (the *editor* hit
|
||||
`DDERR_INVALIDOBJECT` on Win11 and needed DDrawCompat; the *game* succeeded natively on the
|
||||
W4100). Version lockstep means one build must serve every pod.
|
||||
- **Recommended staging:** (1) `-borderless` for the MAIN display only, default off; (2) convert
|
||||
**one** panel (radar) — the small, make-or-break test of `DDSCL_NORMAL` + clipper +
|
||||
D3D-on-offscreen on the target GPU; (3) all three panels; (4) retire dgVoodoo2 + shim. Keep
|
||||
exclusive fullscreen switch-selectable indefinitely.
|
||||
|
||||
### Incidental findings
|
||||
- **`-2dt` is not a recognised switch anywhere in the codebase**, despite appearing in production
|
||||
`ctcl.ini` launch lines. Completely inert. (2D targets are already the default; `-3dt` is what
|
||||
switches to the 3D model.)
|
||||
- `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; `InitSecond` hardcodes
|
||||
`IID_IDirect3DHALDevice`.
|
||||
- New switches are documented in `-help` (`-fps` under LOGGING AND DIAGNOSTICS, `-tcoop` under
|
||||
DISPLAY AND VIDEO).
|
||||
|
||||
## Next steps (proposed)
|
||||
- [ ] **Commit the working `dgVoodoo.conf`** and have `deploy-mw4.ps1` place it — without it a
|
||||
fresh 4-monitor deploy silently fails (see STEP 10).
|
||||
- [ ] (Decision pending) Borderless-windowed migration — see the staged assessment in STEP 10.
|
||||
Step 2 (one panel) is the cheap, decisive experiment.
|
||||
- [ ] Test a **full mission windowed** (only the console/shell has been verified windowed).
|
||||
- [x] ~~Windowed 3D viewport on Win11~~ — DONE via DDrawCompat (see STEP 8 viewport section).
|
||||
- [ ] (Optional) Curate remaining WIP content as features are exercised (editor loads dev `Content\`).
|
||||
- [ ] (Optional) Build Debug/Armor configs (need `dbg.bin`/`arm.bin` output dirs).
|
||||
|
||||
@@ -285,7 +285,9 @@ HRESULT CALLBACK EnumModesCallback2( LPDDSURFACEDESC2 lpDDSurfaceDesc, LPVOID lp
|
||||
// [tmon] Apply a hard-assigned display device, but only if the operator supplied one
|
||||
// and it names a device that actually exists. Anything else is ignored, so a stale
|
||||
// -tmon left on the command line of a machine with fewer monitors simply falls back
|
||||
// to auto-detection instead of breaking startup.
|
||||
// to auto-detection instead of breaking startup. Rejections are reported in
|
||||
// gos-displays.txt - silently doing nothing is worse than useless when the operator
|
||||
// is trying to work out why a monitor did not light up.
|
||||
//
|
||||
static void ApplyMonitorOverride(int slot, int* pTarget)
|
||||
{
|
||||
@@ -294,6 +296,114 @@ static void ApplyMonitorOverride(int slot, int* pTarget)
|
||||
*pTarget = dev;
|
||||
}
|
||||
|
||||
//
|
||||
// [displaylog] Append one printf-style line to the display report.
|
||||
//
|
||||
static void EmitDisplayLine( HANDLE hFile, const char* fmt, ... )
|
||||
{
|
||||
char szLine[512];
|
||||
DWORD dwWritten=0;
|
||||
va_list ap;
|
||||
va_start( ap, fmt );
|
||||
vsprintf( szLine, fmt, ap );
|
||||
va_end( ap );
|
||||
WriteFile( hFile, szLine, (DWORD)strlen(szLine), &dwWritten, NULL );
|
||||
}
|
||||
|
||||
//
|
||||
// [displaylog] Write gos-displays.txt next to the executable, describing every
|
||||
// DirectDraw device GameOS found and which display role each one was assigned.
|
||||
//
|
||||
// This exists because there is no other way to see it: the SPEW macros are compiled
|
||||
// out of shipping builds, and a monitor that simply never lights up gives no clue
|
||||
// whether the device was missing, lacked hardware caps, or was assigned elsewhere.
|
||||
//
|
||||
static void LogDisplayDevices()
|
||||
{
|
||||
char szDir[MAX_PATH];
|
||||
char szPath[MAX_PATH];
|
||||
|
||||
szDir[0]='\0';
|
||||
GetModuleFileNameA( NULL, szDir, sizeof(szDir) );
|
||||
char* pSlash=strrchr( szDir, '\\' );
|
||||
if( pSlash )
|
||||
*(pSlash+1)='\0';
|
||||
else
|
||||
szDir[0]='\0';
|
||||
sprintf( szPath, "%sgos-displays.txt", szDir );
|
||||
|
||||
HANDLE hFile=CreateFileA( szPath, GENERIC_WRITE, FILE_SHARE_READ, NULL,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
|
||||
if( hFile==INVALID_HANDLE_VALUE )
|
||||
return;
|
||||
|
||||
EmitDisplayLine( hFile, "GameOS display device enumeration\r\n" );
|
||||
EmitDisplayLine( hFile, "=================================\r\n\r\n" );
|
||||
EmitDisplayLine( hFile, "NumDevices = %d (DeviceArray holds at most 8)\r\n", (int)NumDevices );
|
||||
EmitDisplayLine( hFile, "NumHWDevices = %d\r\n", (int)NumHWDevices );
|
||||
EmitDisplayLine( hFile, "NumMonitors = %d\r\n\r\n", (int)NumMonitors );
|
||||
|
||||
for( DWORD t0=0; t0<NumDevices; t0++ )
|
||||
{
|
||||
EmitDisplayLine( hFile, " [%d] %-40s hw_rasterization=%s\r\n",
|
||||
(int)t0,
|
||||
DeviceArray[t0].DDid.szDescription,
|
||||
(DeviceArray[t0].D3DCaps.dwDevCaps & D3DDEVCAPS_HWRASTERIZATION) ? "yes" : "NO" );
|
||||
}
|
||||
|
||||
EmitDisplayLine( hFile, "\r\nAssigned roles (-1 means not found):\r\n" );
|
||||
EmitDisplayLine( hFile, " main (FullScreenDevice) = %d\r\n", Environment.FullScreenDevice );
|
||||
EmitDisplayLine( hFile, " radar (g_nNonDualHead) = %d\r\n", g_nNonDualHead );
|
||||
EmitDisplayLine( hFile, " span (g_nDualHead) = %d\r\n", g_nDualHead );
|
||||
EmitDisplayLine( hFile, " span2 (g_nDualHead2) = %d\r\n", g_nDualHead2 );
|
||||
EmitDisplayLine( hFile, " mfd1 (g_nMFD1) = %d\r\n", g_nMFD1 );
|
||||
EmitDisplayLine( hFile, " mfd2 (g_nMFD2) = %d\r\n", g_nMFD2 );
|
||||
|
||||
// Report what -tmon asked for and whether each slot could be honoured.
|
||||
{
|
||||
static const char* apszRole[4] = { "main", "radar", "mfd1", "mfd2" };
|
||||
bool bAny=false;
|
||||
for( int s=0; s<4; s++ )
|
||||
if( g_naMonitorOverride[s] >= 0 )
|
||||
bAny=true;
|
||||
if( bAny )
|
||||
{
|
||||
EmitDisplayLine( hFile, "\r\n-tmon override:\r\n" );
|
||||
for( int s2=0; s2<4; s2++ )
|
||||
{
|
||||
int dev=g_naMonitorOverride[s2];
|
||||
if( dev < 0 )
|
||||
EmitDisplayLine( hFile, " %-5s : auto\r\n", apszRole[s2] );
|
||||
else if( dev < (int)NumDevices )
|
||||
EmitDisplayLine( hFile, " %-5s : device %d APPLIED\r\n", apszRole[s2], dev );
|
||||
else
|
||||
EmitDisplayLine( hFile, " %-5s : device %d *** REJECTED - only %d device(s) exist ***\r\n",
|
||||
apszRole[s2], dev, (int)NumDevices );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EmitDisplayLine( hFile, "\r\n-tmon override: not supplied (all roles auto-detected)\r\n" );
|
||||
}
|
||||
}
|
||||
|
||||
// The decisive line: whether the MFD subsystem will initialise at all.
|
||||
EmitDisplayLine( hFile, "\r\nMFD mode (-tmfds) = %d\r\n", g_nTypeOfMFDs );
|
||||
if( g_nTypeOfMFDs == 4 )
|
||||
{
|
||||
bool bOK = (g_nMFD1 != -1) && (g_nMFD2 != -1);
|
||||
EmitDisplayLine( hFile, "mode 4 requires BOTH mfd1 and mfd2: %s\r\n", bOK ? "OK" : "*** FAILED ***" );
|
||||
if( !bOK )
|
||||
EmitDisplayLine( hFile,
|
||||
" -> IsMultimonitorAvaliable() returns false, so HSH_EnterFullScreen2()\r\n"
|
||||
" falls back to the single-secondary-monitor path and the two MFD\r\n"
|
||||
" displays are never opened. Four hardware-rasterizing DirectDraw\r\n"
|
||||
" devices are needed: main, radar, mfd1, mfd2.\r\n" );
|
||||
}
|
||||
|
||||
CloseHandle( hFile );
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
void FindVideoCards()
|
||||
@@ -420,10 +530,10 @@ void FindVideoCards()
|
||||
// [tmon] Hard-assigned MFD overrides (only meaningful with -tmfds 4).
|
||||
ApplyMonitorOverride(2, &g_nMFD1);
|
||||
ApplyMonitorOverride(3, &g_nMFD2);
|
||||
// Log the final assignment so the operator can confirm which physical monitor each
|
||||
// role landed on without needing a debugger.
|
||||
SPEW(( GROUP_GAMEOS, "Display roles: devices=%d main=%d radar=%d mfd1=%d mfd2=%d span=%d",
|
||||
(int)NumDevices, Environment.FullScreenDevice, g_nNonDualHead, g_nMFD1, g_nMFD2, g_nDualHead ));
|
||||
// [displaylog] Write gos-displays.txt with the full enumeration and the role
|
||||
// assignment. SPEW is compiled out of shipping builds, so a file is the only way
|
||||
// the operator can see why a display did or did not get picked up.
|
||||
LogDisplayDevices();
|
||||
// jcem
|
||||
//
|
||||
// Check any known video cards have up to date drivers (Only on Windows9x)
|
||||
|
||||
@@ -841,6 +841,133 @@ HRESULT App_Render( LPDIRECT3DDEVICE7 pd3dDevice,LPDIRECTDRAWSURFACE7 pddsTextur
|
||||
//sanghoon end
|
||||
|
||||
|
||||
//
|
||||
// [fpslog] Per-second frame pacing report, written to gos-fps.txt next to the executable.
|
||||
//
|
||||
// Off unless the -fps switch is given, so a normal pod run does no extra work at all.
|
||||
// Set from MW4Application's command-line parser.
|
||||
//
|
||||
int g_nFpsLog = 0;
|
||||
|
||||
// The engine's own FrameRate readout is inside #ifdef LAB_ONLY, so it exists only in
|
||||
// MW4pro.exe. This works in Release builds, which is what actually runs on the pods.
|
||||
//
|
||||
// Average FPS on its own hides stutter: 60 fps average is indistinguishable from 60 fps
|
||||
// with a dropped frame every second. So this also reports the slowest single frame and a
|
||||
// 1% low (the mean of the worst 1% of frames in that second), which is where rhythmic
|
||||
// hitching shows up, plus a count of frames that took more than twice the average.
|
||||
//
|
||||
static void GOS_FpsWriteLine( const char* pszLine )
|
||||
{
|
||||
// The handle is held open for the life of the process. Opening and closing the file
|
||||
// every second would put a syscall of unpredictable latency (worse with antivirus or
|
||||
// a network path) on the render thread - which is exactly what this code is trying to
|
||||
// measure. Writes still survive a crash, because WriteFile hands the data to the OS
|
||||
// cache rather than a CRT buffer.
|
||||
static HANDLE s_hFile=INVALID_HANDLE_VALUE;
|
||||
char szDir[MAX_PATH];
|
||||
char szPath[MAX_PATH];
|
||||
DWORD dwWritten=0;
|
||||
|
||||
if( s_hFile==INVALID_HANDLE_VALUE )
|
||||
{
|
||||
szDir[0]='\0';
|
||||
GetModuleFileNameA( NULL, szDir, sizeof(szDir) );
|
||||
char* pSlash=strrchr( szDir, '\\' );
|
||||
if( pSlash )
|
||||
*(pSlash+1)='\0';
|
||||
else
|
||||
szDir[0]='\0';
|
||||
sprintf( szPath, "%sgos-fps.txt", szDir );
|
||||
|
||||
// FILE_SHARE_READ so the file can be tailed while the game is running.
|
||||
s_hFile=CreateFileA( szPath, GENERIC_WRITE, FILE_SHARE_READ, NULL,
|
||||
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
|
||||
if( s_hFile==INVALID_HANDLE_VALUE )
|
||||
return;
|
||||
}
|
||||
|
||||
WriteFile( s_hFile, pszLine, (DWORD)strlen(pszLine), &dwWritten, NULL );
|
||||
}
|
||||
|
||||
static void GOS_LogFrameRate( float fFrameRate )
|
||||
{
|
||||
enum { MAX_WORST = 64 }; // >= 1% of any plausible per-second frame count
|
||||
static float s_afWorst[MAX_WORST]; // longest frames this second, descending ms
|
||||
static int s_nWorst = 0;
|
||||
static float s_fAccumMS= 0.0f;
|
||||
static float s_fSumMS = 0.0f;
|
||||
static float s_fMaxMS = 0.0f;
|
||||
static int s_nFrames = 0;
|
||||
static int s_nSecond = 0;
|
||||
char szLine[256];
|
||||
|
||||
if( !g_nFpsLog ) // [fpslog] -fps not given: do nothing
|
||||
return;
|
||||
|
||||
if( fFrameRate<=0.0f )
|
||||
return;
|
||||
|
||||
float fMS = 1000.0f/fFrameRate;
|
||||
|
||||
s_nFrames++;
|
||||
s_fSumMS += fMS;
|
||||
s_fAccumMS += fMS;
|
||||
if( fMS>s_fMaxMS )
|
||||
s_fMaxMS = fMS;
|
||||
|
||||
// Maintain the longest frames of this second, sorted longest-first.
|
||||
if( s_nWorst<MAX_WORST )
|
||||
s_afWorst[s_nWorst++] = fMS;
|
||||
else if( fMS>s_afWorst[MAX_WORST-1] )
|
||||
s_afWorst[MAX_WORST-1] = fMS;
|
||||
for( int i=s_nWorst-1; i>0 && s_afWorst[i]>s_afWorst[i-1]; i-- ) {
|
||||
float fTmp=s_afWorst[i]; s_afWorst[i]=s_afWorst[i-1]; s_afWorst[i-1]=fTmp;
|
||||
}
|
||||
|
||||
if( s_fAccumMS<1000.0f )
|
||||
return;
|
||||
|
||||
float fAvgMS = s_fSumMS/(float)s_nFrames;
|
||||
float fAvgFPS = 1000.0f/fAvgMS;
|
||||
float fMinFPS = 1000.0f/s_fMaxMS;
|
||||
|
||||
// 1% low: mean of the worst 1% of frames (at least one).
|
||||
int nLow = s_nFrames/100;
|
||||
if( nLow<1 )
|
||||
nLow = 1;
|
||||
if( nLow>s_nWorst )
|
||||
nLow = s_nWorst;
|
||||
float fLowSum=0.0f;
|
||||
for( int j=0; j<nLow; j++ )
|
||||
fLowSum += s_afWorst[j];
|
||||
float fLowFPS = (fLowSum>0.0f) ? (1000.0f/(fLowSum/(float)nLow)) : 0.0f;
|
||||
|
||||
// Frames that took more than twice the average - i.e. visible hitches.
|
||||
int nHitch=0;
|
||||
for( int k=0; k<s_nWorst; k++ )
|
||||
if( s_afWorst[k]>fAvgMS*2.0f )
|
||||
nHitch++;
|
||||
|
||||
if( s_nSecond==0 )
|
||||
GOS_FpsWriteLine( " sec frames avg fps 1% low worst frame hitches\r\n"
|
||||
" --- ------ ------- ------ ----------- -------\r\n" );
|
||||
|
||||
sprintf( szLine, "%5d %6d %8.1f %8.1f %8.1f ms %s%d\r\n",
|
||||
s_nSecond, s_nFrames, fAvgFPS, fLowFPS, s_fMaxMS,
|
||||
nHitch ? "<-- " : "", nHitch );
|
||||
GOS_FpsWriteLine( szLine );
|
||||
|
||||
s_nSecond++;
|
||||
s_nFrames = 0;
|
||||
s_nWorst = 0;
|
||||
s_fSumMS = 0.0f;
|
||||
s_fMaxMS = 0.0f;
|
||||
s_fAccumMS-= 1000.0f;
|
||||
if( s_fAccumMS<0.0f )
|
||||
s_fAccumMS = 0.0f;
|
||||
}
|
||||
|
||||
//
|
||||
// Update the display (call all the 'Update Renderers' routines
|
||||
//
|
||||
@@ -885,6 +1012,7 @@ void gos_UpdateDisplay( bool Everything )
|
||||
|
||||
frameRate=(float)frequency/(float)thisframe; // Calculate frame rate based on clock frequency
|
||||
StartOfRenderTime=GetCycles();
|
||||
GOS_LogFrameRate( frameRate ); // [fpslog] per-second pacing report
|
||||
//
|
||||
// Don't trigger watchdog timer
|
||||
//
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <d3d.h>
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h> // [displaylog] va_list/va_start for HSH_LogInit
|
||||
//#include "directx.hpp"
|
||||
#include "dxrasterizer.hpp"
|
||||
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } }
|
||||
@@ -522,19 +523,185 @@ void CHSH_Device::SetRenderTargetTexture()
|
||||
const DWORD dwFlags = DDSCL_SETFOCUSWINDOW | DDSCL_CREATEDEVICEWINDOW |
|
||||
DDSCL_ALLOWREBOOT | DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN;
|
||||
|
||||
// [panelcoop] Which SetCooperativeLevel form the secondary panels use; 0 = legacy.
|
||||
// Selected with -tcoop <0-3>. See CHSH_Device::InitFirst for what each one does.
|
||||
int g_nHshCoopMode = 0;
|
||||
|
||||
extern HRESULT wDirectDrawCreateEx( GUID* lpGUID, void** lplpDD, REFIID iid, IUnknown* pUnkOuter );
|
||||
|
||||
//
|
||||
// [displaylog] Append a printf-style line to gos-displays.txt, the report VideoCard.cpp
|
||||
// writes at device-enumeration time.
|
||||
//
|
||||
// This exists because the panel initialisation below discarded every HRESULT it produced
|
||||
// and returned true unconditionally. A display that failed to open therefore generated no
|
||||
// error, no crash and no log entry - the monitor simply stayed on the desktop, which is
|
||||
// indistinguishable from the display never having been asked to open at all.
|
||||
//
|
||||
void HSH_LogInit( const char* fmt, ... )
|
||||
{
|
||||
char szDir[MAX_PATH];
|
||||
char szPath[MAX_PATH];
|
||||
char szLine[512];
|
||||
DWORD dwWritten=0;
|
||||
va_list ap;
|
||||
|
||||
szDir[0]='\0';
|
||||
GetModuleFileNameA( NULL, szDir, sizeof(szDir) );
|
||||
char* pSlash=strrchr( szDir, '\\' );
|
||||
if( pSlash )
|
||||
*(pSlash+1)='\0';
|
||||
else
|
||||
szDir[0]='\0';
|
||||
sprintf( szPath, "%sgos-displays.txt", szDir );
|
||||
|
||||
HANDLE hFile=CreateFileA( szPath, GENERIC_WRITE, FILE_SHARE_READ, NULL,
|
||||
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
|
||||
if( hFile==INVALID_HANDLE_VALUE )
|
||||
return;
|
||||
SetFilePointer( hFile, 0, NULL, FILE_END );
|
||||
|
||||
va_start( ap, fmt );
|
||||
vsprintf( szLine, fmt, ap );
|
||||
va_end( ap );
|
||||
|
||||
WriteFile( hFile, szLine, (DWORD)strlen(szLine), &dwWritten, NULL );
|
||||
CloseHandle( hFile );
|
||||
}
|
||||
|
||||
//
|
||||
// [displaylog] Readable name for the result codes multi-device fullscreen init can return.
|
||||
//
|
||||
static const char* HSH_HRName( HRESULT hr )
|
||||
{
|
||||
switch( hr )
|
||||
{
|
||||
case DD_OK: return "DD_OK";
|
||||
case DDERR_ALREADYINITIALIZED: return "DDERR_ALREADYINITIALIZED";
|
||||
case DDERR_CANNOTATTACHSURFACE: return "DDERR_CANNOTATTACHSURFACE";
|
||||
case DDERR_DIRECTDRAWALREADYCREATED: return "DDERR_DIRECTDRAWALREADYCREATED";
|
||||
case DDERR_EXCLUSIVEMODEALREADYSET: return "DDERR_EXCLUSIVEMODEALREADYSET";
|
||||
case DDERR_GENERIC: return "DDERR_GENERIC";
|
||||
case DDERR_HWNDALREADYSET: return "DDERR_HWNDALREADYSET";
|
||||
case DDERR_HWNDSUBCLASSED: return "DDERR_HWNDSUBCLASSED";
|
||||
case DDERR_INCOMPATIBLEPRIMARY: return "DDERR_INCOMPATIBLEPRIMARY";
|
||||
case DDERR_INVALIDCAPS: return "DDERR_INVALIDCAPS";
|
||||
case DDERR_INVALIDMODE: return "DDERR_INVALIDMODE";
|
||||
case DDERR_INVALIDOBJECT: return "DDERR_INVALIDOBJECT";
|
||||
case DDERR_INVALIDPARAMS: return "DDERR_INVALIDPARAMS";
|
||||
case DDERR_INVALIDPIXELFORMAT: return "DDERR_INVALIDPIXELFORMAT";
|
||||
case DDERR_INVALIDSURFACETYPE: return "DDERR_INVALIDSURFACETYPE";
|
||||
case DDERR_NOCOOPERATIVELEVELSET: return "DDERR_NOCOOPERATIVELEVELSET";
|
||||
case DDERR_NODIRECTDRAWHW: return "DDERR_NODIRECTDRAWHW";
|
||||
case DDERR_NOEXCLUSIVEMODE: return "DDERR_NOEXCLUSIVEMODE";
|
||||
case DDERR_NO3D: return "DDERR_NO3D";
|
||||
case DDERR_NOTFOUND: return "DDERR_NOTFOUND";
|
||||
case DDERR_OUTOFMEMORY: return "DDERR_OUTOFMEMORY";
|
||||
case DDERR_OUTOFVIDEOMEMORY: return "DDERR_OUTOFVIDEOMEMORY";
|
||||
case DDERR_PRIMARYSURFACEALREADYEXISTS: return "DDERR_PRIMARYSURFACEALREADYEXISTS";
|
||||
case DDERR_SURFACEBUSY: return "DDERR_SURFACEBUSY";
|
||||
case DDERR_UNSUPPORTED: return "DDERR_UNSUPPORTED";
|
||||
case DDERR_UNSUPPORTEDMODE: return "DDERR_UNSUPPORTEDMODE";
|
||||
case DDERR_WRONGMODE: return "DDERR_WRONGMODE";
|
||||
}
|
||||
return "(unrecognised)";
|
||||
}
|
||||
|
||||
// [displaylog] Log one DirectDraw call result, flagging failures.
|
||||
static void HSH_CheckHR( const char* pszWhat, HRESULT hr )
|
||||
{
|
||||
HSH_LogInit( " %-24s = 0x%08lX %s%s\r\n",
|
||||
pszWhat, (unsigned long)hr, HSH_HRName(hr),
|
||||
FAILED(hr) ? " *** FAILED ***" : "" );
|
||||
}
|
||||
|
||||
bool CHSH_Device::InitFirst(int devicenum,DWORD resx,DWORD resy, CHSH_Device* pOtherHSHD/* = NULL*/)
|
||||
{
|
||||
m_pOtherHSHD = pOtherHSHD;
|
||||
if (pOtherHSHD) {
|
||||
pDD = pOtherHSHD->pDD;
|
||||
HSH_LogInit( " InitFirst : shares the DirectDraw object of another panel\r\n" );
|
||||
} else {
|
||||
HRESULT hr;
|
||||
HSH_LogInit( " InitFirst : device %d, %lux%lu 16bpp 60Hz\r\n",
|
||||
devicenum, (unsigned long)resx, (unsigned long)resy );
|
||||
if( devicenum<0 || devicenum>=(int)NumDevices ) {
|
||||
HSH_LogInit( " *** device index out of range - this panel cannot open ***\r\n" );
|
||||
pDD=0;
|
||||
size_back.cx=resx;
|
||||
size_back.cy=resy;
|
||||
return false;
|
||||
}
|
||||
//1. Resolution change.
|
||||
wDirectDrawCreateEx( &DeviceArray[devicenum].DeviceGUID , (VOID**) &(pDD), IID_IDirectDraw7, NULL );
|
||||
pDD->SetCooperativeLevel(hWindow, dwFlags );
|
||||
hr=wDirectDrawCreateEx( &DeviceArray[devicenum].DeviceGUID , (VOID**) &(pDD), IID_IDirectDraw7, NULL );
|
||||
HSH_CheckHR( "DirectDrawCreateEx", hr );
|
||||
if( FAILED(hr) || !pDD ) {
|
||||
pDD=0;
|
||||
size_back.cx=resx;
|
||||
size_back.cy=resy;
|
||||
return false;
|
||||
}
|
||||
// [panelcoop] Establish the cooperative level for this panel.
|
||||
//
|
||||
// The legacy call asks every panel to be BOTH the process focus window and its own
|
||||
// device window, using the one shared hWindow, after the main display device has
|
||||
// already taken exclusive mode on it. XP's DirectDraw and dgVoodoo2 tolerate that;
|
||||
// Windows 10's does not - it returns DDERR_EXCLUSIVEMODEALREADYSET on the first
|
||||
// panel and DDERR_INVALIDPARAMS on the rest, after which the primary surface fails
|
||||
// with DDERR_NOCOOPERATIVELEVELSET.
|
||||
//
|
||||
// The main device (DXRasterizer.cpp, EnterFullScreenMode) already uses the correct
|
||||
// two-call form: SETFOCUSWINDOW on its own, then EXCLUSIVE|FULLSCREEN. The panels
|
||||
// never were. -tcoop picks the form so the alternatives can be compared on real
|
||||
// hardware without a rebuild between each one.
|
||||
switch( g_nHshCoopMode )
|
||||
{
|
||||
case 1: // one call, but stop claiming the focus window
|
||||
HSH_LogInit( " coop 1: CREATEDEVICEWINDOW|EXCLUSIVE|FULLSCREEN\r\n" );
|
||||
hr=pDD->SetCooperativeLevel( hWindow,
|
||||
DDSCL_CREATEDEVICEWINDOW|DDSCL_ALLOWREBOOT|DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN );
|
||||
HSH_CheckHR( "SetCoopLevel", hr );
|
||||
break;
|
||||
|
||||
case 2: // two calls, DirectDraw makes a device window for this monitor
|
||||
HSH_LogInit( " coop 2: SETFOCUSWINDOW, then CREATEDEVICEWINDOW|EXCLUSIVE|FULLSCREEN\r\n" );
|
||||
hr=pDD->SetCooperativeLevel( hWindow, DDSCL_SETFOCUSWINDOW );
|
||||
HSH_CheckHR( "SetCoopLevel(focus)", hr );
|
||||
hr=pDD->SetCooperativeLevel( hWindow,
|
||||
DDSCL_CREATEDEVICEWINDOW|DDSCL_ALLOWREBOOT|DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN );
|
||||
HSH_CheckHR( "SetCoopLevel(device)", hr );
|
||||
break;
|
||||
|
||||
case 3: // two calls, exactly what the main display device does
|
||||
HSH_LogInit( " coop 3: SETFOCUSWINDOW, then EXCLUSIVE|FULLSCREEN\r\n" );
|
||||
hr=pDD->SetCooperativeLevel( hWindow, DDSCL_SETFOCUSWINDOW );
|
||||
HSH_CheckHR( "SetCoopLevel(focus)", hr );
|
||||
hr=pDD->SetCooperativeLevel( hWindow, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN );
|
||||
HSH_CheckHR( "SetCoopLevel(device)", hr );
|
||||
break;
|
||||
|
||||
case 4: // exclusive only - the main device has ALREADY set the focus window,
|
||||
// so re-claiming it is what makes the first panel collide with it.
|
||||
HSH_LogInit( " coop 4: EXCLUSIVE|FULLSCREEN only (no focus claim)\r\n" );
|
||||
hr=pDD->SetCooperativeLevel( hWindow, DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN );
|
||||
HSH_CheckHR( "SetCoopLevel", hr );
|
||||
break;
|
||||
|
||||
case 5: // as 4, plus ALLOWREBOOT (the legacy flag set minus the two window flags)
|
||||
HSH_LogInit( " coop 5: ALLOWREBOOT|EXCLUSIVE|FULLSCREEN (no focus claim)\r\n" );
|
||||
hr=pDD->SetCooperativeLevel( hWindow,
|
||||
DDSCL_ALLOWREBOOT|DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN );
|
||||
HSH_CheckHR( "SetCoopLevel", hr );
|
||||
break;
|
||||
|
||||
default: // 0 = legacy, byte-for-byte the original behaviour
|
||||
HSH_LogInit( " coop 0: legacy combined call\r\n" );
|
||||
hr=pDD->SetCooperativeLevel( hWindow, dwFlags );
|
||||
HSH_CheckHR( "SetCoopLevel", hr );
|
||||
break;
|
||||
}
|
||||
hr=pDD->SetDisplayMode( resx, resy, 16, 60, 0 );
|
||||
HSH_CheckHR( "SetDisplayMode", hr );
|
||||
}
|
||||
size_back.cx=resx;
|
||||
size_back.cy=resy;
|
||||
@@ -664,25 +831,50 @@ bool CHSH_Device::InitSecond(DWORD tresx,DWORD tresy)
|
||||
//////Front Buffer
|
||||
DDSURFACEDESC2 ddsd={0,};
|
||||
DDSCAPS2 ddsCaps;
|
||||
HRESULT hr;
|
||||
|
||||
// [displaylog] InitFirst can now fail cleanly; without this guard every call below
|
||||
// dereferences a null IDirectDraw7 and takes the process down.
|
||||
if( !pDD ) {
|
||||
HSH_LogInit( " InitSecond: skipped - InitFirst produced no DirectDraw object\r\n" );
|
||||
return false;
|
||||
}
|
||||
|
||||
ddsd.dwSize = sizeof(ddsd);
|
||||
ddsd.dwFlags = DDSD_BACKBUFFERCOUNT | DDSD_CAPS;
|
||||
ddsd.dwBackBufferCount = 1;
|
||||
ddsd.ddsCaps.dwCaps = DDSCAPS_COMPLEX | DDSCAPS_FLIP | DDSCAPS_PRIMARYSURFACE| DDSCAPS_3DDEVICE;
|
||||
|
||||
pDD->CreateSurface( &ddsd, &pDDSFront, NULL );
|
||||
|
||||
hr=pDD->CreateSurface( &ddsd, &pDDSFront, NULL );
|
||||
HSH_CheckHR( "CreateSurface(primary)", hr );
|
||||
if( FAILED(hr) || !pDDSFront ) {
|
||||
pDDSFront=0;
|
||||
return false;
|
||||
}
|
||||
|
||||
//////BackBuffer
|
||||
ZeroMemory( &ddsCaps, sizeof(ddsCaps) );
|
||||
ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
|
||||
pDDSFront->GetAttachedSurface( &ddsCaps, &pDDSBack );
|
||||
hr=pDDSFront->GetAttachedSurface( &ddsCaps, &pDDSBack );
|
||||
HSH_CheckHR( "GetAttachedSurface", hr );
|
||||
if( FAILED(hr) || !pDDSBack ) {
|
||||
pDDSBack=0;
|
||||
return false;
|
||||
}
|
||||
|
||||
//////3D Device
|
||||
LPDIRECT3D7 pD3D;
|
||||
pDD->QueryInterface( IID_IDirect3D7, (VOID**)&(pD3D) );
|
||||
pD3D->CreateDevice(IID_IDirect3DHALDevice,pDDSBack,&pD3DDevice);
|
||||
LPDIRECT3D7 pD3D=0;
|
||||
hr=pDD->QueryInterface( IID_IDirect3D7, (VOID**)&(pD3D) );
|
||||
HSH_CheckHR( "QueryInterface(D3D7)", hr );
|
||||
if( FAILED(hr) || !pD3D )
|
||||
return false;
|
||||
hr=pD3D->CreateDevice(IID_IDirect3DHALDevice,pDDSBack,&pD3DDevice);
|
||||
HSH_CheckHR( "CreateDevice(HAL)", hr );
|
||||
pD3D->Release();
|
||||
if( FAILED(hr) || !pD3DDevice ) {
|
||||
pD3DDevice=0;
|
||||
return false;
|
||||
}
|
||||
|
||||
D3DVIEWPORT7 vp = { 0, 0, size_back.cx, size_back.cy, 0.0f, 1.0f };
|
||||
pD3DDevice->SetViewport( &vp );
|
||||
@@ -1018,7 +1210,10 @@ bool CMR_Device::InitFirst()
|
||||
bool CMR_Device::InitSecond()
|
||||
{
|
||||
map_loaded=false;
|
||||
CHSH_Device::InitSecond(0,0); // no off screen - do not create Target Texture.
|
||||
// [panelcoop] see CRadar_Device::InitSecond - do not build surfaces on a device
|
||||
// that never got a cooperative level.
|
||||
if( !CHSH_Device::InitSecond(0,0) ) // no off screen - do not create Target Texture.
|
||||
return false;
|
||||
|
||||
char temppath[MAX_PATH],temppatha[MAX_PATH];
|
||||
|
||||
@@ -1122,7 +1317,10 @@ bool CMFDRight_Device::InitFirst()
|
||||
bool CMFDRight_Device::InitSecond()
|
||||
{
|
||||
// Create flip chain, D3D device, render target (1024x512) and fonts.
|
||||
CHSH_Device::InitSecond(1024, 512);
|
||||
// [displaylog] If the device never came up, everything below dereferences a null
|
||||
// IDirect3DDevice7; bail out and leave the reason in gos-displays.txt.
|
||||
if( !CHSH_Device::InitSecond(1024, 512) )
|
||||
return false;
|
||||
|
||||
// Independent copy of the MFD texture set owned by THIS DirectDraw object.
|
||||
HSH_CreateMFDTextures(pDD, &pDDSMechTexture, &pDDSTexture);
|
||||
@@ -1192,7 +1390,11 @@ bool CRadar_Device::InitFirst()
|
||||
bool CRadar_Device::InitSecond()
|
||||
{
|
||||
ZeroMemory(recent_state,sizeof(recent_state));
|
||||
CHSH_Device::InitSecond(512,1024); // 480 x 640
|
||||
// [panelcoop] If the device did not come up, stop here. Continuing would create
|
||||
// surfaces on a DirectDraw object with no cooperative level - they come back NULL,
|
||||
// and the first GetDC on one faults (IDirectDrawSurface7 vtable +0x44).
|
||||
if( !CHSH_Device::InitSecond(512,1024) ) // 480 x 640
|
||||
return false;
|
||||
MapDrawn=false;
|
||||
char temppath[MAX_PATH],temppatha[MAX_PATH];
|
||||
|
||||
@@ -1446,9 +1648,11 @@ bool CMFD_Device::InitFirst()
|
||||
// device here so both displays are ready before InitSecond runs.
|
||||
if (g_nTypeOfMFDs == 4) {
|
||||
m_pRightDevice = &mfd_device_right;
|
||||
CHSH_Device::InitFirst(g_nMFD1, 640, 480); // left device
|
||||
mfd_device_right.InitFirst(); // right device display mode
|
||||
return true;
|
||||
// [panelcoop] Report the REAL result. This used to return true unconditionally,
|
||||
// which hid a failed panel and let the per-frame code run on null surfaces.
|
||||
bool bLeft = CHSH_Device::InitFirst(g_nMFD1, 640, 480);
|
||||
bool bRight = mfd_device_right.InitFirst();
|
||||
return (bLeft && bRight);
|
||||
}
|
||||
m_pRightDevice = NULL;
|
||||
return CHSH_Device::InitFirst(g_nDualHead,1280,480);
|
||||
@@ -1456,7 +1660,10 @@ bool CMFD_Device::InitFirst()
|
||||
|
||||
bool CMFD_Device::InitSecond()
|
||||
{
|
||||
CHSH_Device::InitSecond(1024,512); // 640 x 480
|
||||
// [displaylog] see CMFDRight_Device::InitSecond - a failed device must not fall
|
||||
// through into the texture/material setup below.
|
||||
if( !CHSH_Device::InitSecond(1024,512) ) // 640 x 480
|
||||
return false;
|
||||
|
||||
HSH_CreateMFDTextures(pDD, &pDDSMechTexture, &pDDSTexture);
|
||||
|
||||
@@ -1478,7 +1685,8 @@ bool CMFD_Device::InitSecond()
|
||||
|
||||
// Mode 4: create flip chain + D3D device for the right 640x480 monitor.
|
||||
if (g_nTypeOfMFDs == 4 && m_pRightDevice) {
|
||||
mfd_device_right.InitSecond();
|
||||
if( !mfd_device_right.InitSecond() )
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2716,21 +2924,57 @@ bool IsSecondaryMonitorAvaliable()
|
||||
|
||||
void HSH_EnterFullScreen2()
|
||||
{
|
||||
// [displaylog] Record which of the three possible paths was taken and how each
|
||||
// individual panel fared. Mode 4 opens four fullscreen DirectDraw devices that all
|
||||
// share one focus window, so a partial failure is both plausible and, until now,
|
||||
// completely invisible.
|
||||
HSH_LogInit( "\r\nHSH_EnterFullScreen2\r\n" );
|
||||
HSH_LogInit( "--------------------\r\n" );
|
||||
|
||||
if(IsMultimonitorAvaliable()){
|
||||
mfd_device.InitFirst();
|
||||
radar_device.InitFirst();
|
||||
HSH_LogInit( " path: multi-monitor (MFD + radar)\r\n" );
|
||||
|
||||
mfd_device.InitSecond();
|
||||
radar_device.InitSecond();
|
||||
HSH_LogInit( " [mfd]\r\n" );
|
||||
bool bMFD = mfd_device.InitFirst();
|
||||
HSH_LogInit( " [radar]\r\n" );
|
||||
bool bRadar = radar_device.InitFirst();
|
||||
|
||||
hsh_initialized=true;
|
||||
if(g_f3dtarget){
|
||||
CopyTargetImage(0,mfd_device.pDDSMechTexture);
|
||||
if( bMFD ) {
|
||||
HSH_LogInit( " [mfd]\r\n" );
|
||||
bMFD = mfd_device.InitSecond();
|
||||
}
|
||||
if( bRadar ) {
|
||||
HSH_LogInit( " [radar]\r\n" );
|
||||
bRadar = radar_device.InitSecond();
|
||||
}
|
||||
|
||||
// [panelcoop] Only claim the panels are up if they actually are.
|
||||
//
|
||||
// This flag used to be set unconditionally. A panel that failed to initialise
|
||||
// therefore left null surfaces and a null IDirect3DDevice7 behind, and the
|
||||
// per-frame render path called straight through them - the process died on a
|
||||
// null COM vtable call ("Attempt to read from address 0x00000044") rather than
|
||||
// simply running without MFDs.
|
||||
if( bMFD && bRadar ) {
|
||||
hsh_initialized=true;
|
||||
HSH_LogInit( " result: panels initialised OK\r\n" );
|
||||
if(g_f3dtarget){
|
||||
CopyTargetImage(0,mfd_device.pDDSMechTexture);
|
||||
}
|
||||
} else {
|
||||
HSH_LogInit( " result: *** FAILED (mfd=%s radar=%s) - continuing WITHOUT panels ***\r\n",
|
||||
bMFD ? "ok" : "failed", bRadar ? "ok" : "failed" );
|
||||
mfd_device.Release();
|
||||
radar_device.Release();
|
||||
hsh_initialized=false;
|
||||
}
|
||||
}else if(IsSecondaryMonitorAvaliable()){
|
||||
HSH_LogInit( " path: single secondary monitor (mr_device)\r\n" );
|
||||
mr_device.InitFirst();
|
||||
mr_device.InitSecond();
|
||||
hsh_mrdev_initialized=true;
|
||||
}else{
|
||||
HSH_LogInit( " path: none - no secondary displays opened\r\n" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -92,6 +92,12 @@ extern int g_nTypeOfMFDs; // 0 - no MFDs, 1 - orginal(5+1=dual & single), 2 - B&
|
||||
extern int g_naMonitorOverride[4];
|
||||
extern int g_nRIOType;
|
||||
extern DWORD g_dwRIOBaud; // [tbaud]
|
||||
// [fpslog] Non-zero enables the per-second frame pacing report (gos-fps.txt).
|
||||
// Defined in CoreTech GameOS WinMain.cpp.
|
||||
extern int g_nFpsLog;
|
||||
// [panelcoop] SetCooperativeLevel form used by the radar/MFD panels; 0 = legacy.
|
||||
// Defined in CoreTech GameOS render.cpp.
|
||||
extern int g_nHshCoopMode;
|
||||
extern LONG g_ThrottleDir;
|
||||
extern bool g_bCanSuicideAllways;
|
||||
extern bool g_f3dtarget;
|
||||
@@ -862,6 +868,19 @@ static const char* const g_apszCommandLineHelp[] =
|
||||
" Separators may be ',' '/' or ':'.",
|
||||
" MFD1 and MFD2 only apply with -tmfds 4.",
|
||||
" Example: -tmon 1,2,4,3 swaps the two MFD panels.",
|
||||
" -tcoop <0-5> How the radar/MFD panels claim their displays.",
|
||||
" 0 = legacy single call (as shipped; works on XP",
|
||||
" and under dgVoodoo2, fails on Windows 10).",
|
||||
" 1 = drop SETFOCUSWINDOW, let DirectDraw create",
|
||||
" the device window.",
|
||||
" 2 = SETFOCUSWINDOW first, then CREATEDEVICEWINDOW.",
|
||||
" 3 = SETFOCUSWINDOW first, then EXCLUSIVE only,",
|
||||
" matching what the main display already does.",
|
||||
" 4 = EXCLUSIVE|FULLSCREEN only. The main display",
|
||||
" has already set the focus window, so the",
|
||||
" panels never contend for it.",
|
||||
" 5 = as 4, plus ALLOWREBOOT.",
|
||||
" Results are reported in gos-displays.txt.",
|
||||
" -mechview <1|2> Show the rotating mech view. 1 = on the radar",
|
||||
" -mv <1|2> screen, 2 = on the main screen. Default: off.",
|
||||
" -3dt Draw the MFD target as a live 3D model instead of",
|
||||
@@ -941,6 +960,12 @@ static const char* const g_apszCommandLineHelp[] =
|
||||
" -zlogname <filename> ZLog output file name.",
|
||||
" -olog Write the legacy .log match report instead of the",
|
||||
" newer .rpt format.",
|
||||
" -fps Write a per-second frame pacing report to",
|
||||
" gos-fps.txt next to the executable: average fps,",
|
||||
" 1% low, worst frame in ms, and a count of frames",
|
||||
" taking over twice the average (hitches). Useful",
|
||||
" for confirming smooth pacing on the MFD modes.",
|
||||
" Off unless this switch is given.",
|
||||
" -gamestats Save game statistics.",
|
||||
" -help Show this reference and exit without starting.",
|
||||
"",
|
||||
@@ -1675,6 +1700,17 @@ int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int n
|
||||
if ((9600 <= n) && (n <= 921600))
|
||||
g_dwRIOBaud = (DWORD)n;
|
||||
}
|
||||
// [panelcoop] Cooperative-level form for the radar/MFD panels. 0 = legacy combined
|
||||
// call (what shipped); 1-3 are forms compatible with strict modern DirectDraw.
|
||||
token = strstr(all_lower, "-tcoop ");
|
||||
if (token && token[7])
|
||||
{
|
||||
int n = atoi(&token[7]);
|
||||
if ((0 <= n) && (n <= 5))
|
||||
g_nHshCoopMode = n;
|
||||
}
|
||||
// [fpslog] Enable the per-second frame pacing report written to gos-fps.txt.
|
||||
g_nFpsLog = (strstr(all_lower, "-fps") != NULL) ? 1 : 0;
|
||||
g_bCanSuicideAllways = (strstr(all_lower, "-suicide") != NULL);
|
||||
g_f3dtarget = (strstr(all_lower, "-3dt") != NULL);
|
||||
g_bOldLOG = (strstr(all_lower, "-olog") != NULL);
|
||||
|
||||
@@ -199,6 +199,22 @@ 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).
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
@echo off
|
||||
REM One-click wrapper for set-appcompat.ps1.
|
||||
REM
|
||||
REM Bypasses the PowerShell execution policy for this run only (it does not change any
|
||||
REM machine setting), passes through any arguments such as -Remove or -WhatIfOnly, and
|
||||
REM keeps the window open so the result is readable.
|
||||
REM
|
||||
REM To cover every account on the machine, right-click this file and choose
|
||||
REM "Run as administrator".
|
||||
|
||||
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "%~dp0set-appcompat.ps1" %*
|
||||
|
||||
echo.
|
||||
pause
|
||||
@@ -0,0 +1,175 @@
|
||||
<#
|
||||
set-appcompat.ps1 - apply the DirectDraw compatibility shim to the MW4 executables
|
||||
sitting NEXT TO this script, wherever that copy of the game happens to live.
|
||||
|
||||
WHY THIS EXISTS
|
||||
MW4 renders at bitdepth=16. Modern GPUs report ZERO 16-bit display modes, so the
|
||||
exclusive-fullscreen path cannot set a mode and fails. The DWM8And16BitMitigation
|
||||
AppCompat layer is what makes 8/16-bit fullscreen work under the desktop compositor.
|
||||
|
||||
Without it GameOS reports:
|
||||
"Another application is preventing use of full screen mode."
|
||||
That message is a catch-all raised after every SetDisplayMode attempt has failed --
|
||||
it even scans for NetMeeting -- so it sends you hunting for a conflicting program
|
||||
that does not exist. The real cause is the missing shim.
|
||||
|
||||
The layer is keyed on the executable's FULL PATH. Every install needs its own entry,
|
||||
and copying or moving an install silently loses it. Hence this script: run it from
|
||||
inside any deployment and it fixes that deployment.
|
||||
|
||||
USAGE
|
||||
Double-click set-appcompat.bat (recommended - one click, no execution-policy fuss)
|
||||
or: .\set-appcompat.ps1 apply the shim
|
||||
.\set-appcompat.ps1 -Remove remove the entries this script adds
|
||||
.\set-appcompat.ps1 -WhatIfOnly report current state, change nothing
|
||||
|
||||
Run as Administrator to also write the all-users (HKLM) entry. Without elevation the
|
||||
per-user (HKCU) entry is written, which is enough for the account that runs the game.
|
||||
#>
|
||||
|
||||
param(
|
||||
[string]$Layer = "DWM8And16BitMitigation HIGHDPIAWARE",
|
||||
[switch]$Remove,
|
||||
[switch]$WhatIfOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Locate ourselves -------------------------------------------------------------
|
||||
# $PSScriptRoot is empty on very old hosts and when dot-sourced, so fall back.
|
||||
$here = $PSScriptRoot
|
||||
if ([string]::IsNullOrEmpty($here)) {
|
||||
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
}
|
||||
|
||||
$hkcuLayers = "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
|
||||
$hklmLayers = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
|
||||
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal] `
|
||||
[Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "MW4 DirectDraw compatibility shim" -ForegroundColor Cyan
|
||||
Write-Host "---------------------------------" -ForegroundColor Cyan
|
||||
Write-Host " folder : $here"
|
||||
Write-Host " elevated : $(if ($isAdmin) { 'yes (HKCU + HKLM)' } else { 'no (HKCU only)' })"
|
||||
Write-Host ""
|
||||
|
||||
# --- Find the executables that need it --------------------------------------------
|
||||
# Only the game/editor executables drive DirectDraw. Launcher/autoconfig/mw4print do not.
|
||||
$wanted = @("MW4.exe", "MW4pro.exe", "MW4Ed2.exe")
|
||||
$targets = @()
|
||||
foreach ($name in $wanted) {
|
||||
$p = Join-Path $here $name
|
||||
if (Test-Path -LiteralPath $p) { $targets += $p }
|
||||
}
|
||||
|
||||
if ($targets.Count -eq 0) {
|
||||
Write-Host "No MW4 executables found next to this script." -ForegroundColor Red
|
||||
Write-Host "Expected one of: $($wanted -join ', ')"
|
||||
Write-Host "Put this script in the same folder as MW4.exe and run it again."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Helpers ----------------------------------------------------------------------
|
||||
function Get-Layer([string]$root, [string]$exe) {
|
||||
if (-not (Test-Path -LiteralPath $root)) { return $null }
|
||||
$item = Get-ItemProperty -LiteralPath $root -ErrorAction SilentlyContinue
|
||||
if ($null -eq $item) { return $null }
|
||||
$prop = $item.PSObject.Properties | Where-Object { $_.Name -eq $exe }
|
||||
if ($null -eq $prop) { return $null }
|
||||
return $prop.Value
|
||||
}
|
||||
|
||||
function Set-Layer([string]$root, [string]$exe, [string]$value) {
|
||||
if (-not (Test-Path -LiteralPath $root)) { New-Item -Path $root -Force | Out-Null }
|
||||
Set-ItemProperty -LiteralPath $root -Name $exe -Value $value -Type String
|
||||
}
|
||||
|
||||
$failed = 0
|
||||
|
||||
foreach ($exe in $targets) {
|
||||
|
||||
Write-Host $exe -ForegroundColor White
|
||||
|
||||
$before = Get-Layer $hkcuLayers $exe
|
||||
if ($null -ne $before) {
|
||||
Write-Host " existing HKCU : $before" -ForegroundColor DarkGray
|
||||
# The historical failure mode: a HIGHDPIAWARE-only entry, which suppresses the
|
||||
# automatic shim and produces exactly the fullscreen failure this script fixes.
|
||||
if (-not $Remove -and $before -notmatch "DWM8And16BitMitigation") {
|
||||
Write-Host " NOTE: entry exists but has no DWM8And16BitMitigation -" -ForegroundColor Yellow
|
||||
Write-Host " this specific state BLOCKS fullscreen. Replacing it." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
if ($WhatIfOnly) {
|
||||
$lm = Get-Layer $hklmLayers $exe
|
||||
Write-Host " existing HKLM : $(if ($null -ne $lm) { $lm } else { '(none)' })" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
continue
|
||||
}
|
||||
|
||||
if ($Remove) {
|
||||
Remove-ItemProperty -LiteralPath $hkcuLayers -Name $exe -ErrorAction SilentlyContinue
|
||||
if ($isAdmin) {
|
||||
Remove-ItemProperty -LiteralPath $hklmLayers -Name $exe -ErrorAction SilentlyContinue
|
||||
}
|
||||
Write-Host " removed" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
continue
|
||||
}
|
||||
|
||||
# Per-user. No admin needed, and applies to the account that actually runs the game.
|
||||
Set-Layer $hkcuLayers $exe $Layer
|
||||
|
||||
# All-users, if we can. Note the different value format: the HKLM entries use a
|
||||
# leading "$ " marker. Do not copy one format into the other key.
|
||||
if ($isAdmin) {
|
||||
Set-Layer $hklmLayers $exe ("$ " + ($Layer -replace '\s*HIGHDPIAWARE\s*', ''))
|
||||
}
|
||||
|
||||
# Verify by reading back rather than trusting the write.
|
||||
$after = Get-Layer $hkcuLayers $exe
|
||||
if ($after -eq $Layer) {
|
||||
Write-Host " HKCU set : $after" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " HKCU FAILED : got '$after'" -ForegroundColor Red
|
||||
$failed++
|
||||
}
|
||||
|
||||
if ($isAdmin) {
|
||||
$afterM = Get-Layer $hklmLayers $exe
|
||||
if ($null -ne $afterM) {
|
||||
Write-Host " HKLM set : $afterM" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host " HKLM FAILED" -ForegroundColor Red
|
||||
$failed++
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
if ($WhatIfOnly) {
|
||||
Write-Host "Report only - nothing was changed." -ForegroundColor Cyan
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($failed -gt 0) {
|
||||
Write-Host "$failed registry write(s) failed." -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($Remove) {
|
||||
Write-Host "Shim removed. Fullscreen will fail on modern GPUs until it is re-applied." -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "Done. The shim is read when the process starts -" -ForegroundColor Green
|
||||
Write-Host "restart MW4 to pick it up. No reboot needed." -ForegroundColor Green
|
||||
if (-not $isAdmin) {
|
||||
Write-Host ""
|
||||
Write-Host "Applied for the current user only. To cover all accounts on this" -ForegroundColor DarkGray
|
||||
Write-Host "machine, run this script again as Administrator." -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
exit 0
|
||||
Reference in New Issue
Block a user