Skip to content

fix: restore the retail startup resolution path (szx/szy and profile video settings) - #72

Open
AndreasLauvhjell wants to merge 2 commits into
B2BF:mainfrom
AndreasLauvhjell:fix/videosettings-startup-handoff
Open

AndreasLauvhjell wants to merge 2 commits into
B2BF:mainfrom
AndreasLauvhjell:fix/videosettings-startup-handoff

Conversation

@AndreasLauvhjell

Copy link
Copy Markdown
Contributor

Base: main. Two commits, which belong together:

  1. d55aae5 fix: restore the retail resolution change in VideoOptions (Code/BF2/RendDX9/VideoOptions.cpp)
  2. 68d921d fix: hand the profile video settings to VideoOptions at renderer startup (Code/BF2/RendDX9/RendDX9Init.cpp)

Commit 1 lets a selected resolution reach the device and the menu viewport. Commit 2 makes sure that, at startup, the selected resolution is the profile's one and not preset 1's 800x600.


What this fixes

Two symptoms, with one shared root. The rebuilt client never ran the retail resolution change at startup.

  1. Windowed with +szx/+szy. For example, with +szx 1280 +szy 720 +fullscreen 0, the window and back buffer were 1280x720, but the menu was drawn only in an 800x600 area in the top-left corner. The SwiffHost viewport starts at 800x600. Only REChangeResolution (category 11, id 2) updates it, and the VideoOptions path that posts it was held back by [RETIRE-WHEN: rc-login-reset-hold].
  2. Fullscreen without +szx/+szy. The login menu ran at 800x600 (4:3, black bars on a widescreen monitor). It switched to the profile resolution only after you logged in. Retail BF2 1.50 shows the login menu at the Default profile's resolution from the first frame. On the test machine that is 3840x2160@165 Hz with 8x AA, from Profiles\Default\Video.con.

How the startup path works (retail)

  1. BF2Engine::init calls LocalProfileManager::init (BF2.exe 0x409364, before the renderer exists). It loads the Default profile and runs its Video.con. VideoSettings.setResolution and the other setters only write the pending record (VideoSettings +0xC8). applySettings(1) at 0x4A89D3 applies audio only, so nothing is pushed to the renderer here.
  2. BF2Render::init (0x454370) creates the window and device carrier from GSWindowSizeX/Y, which default to 800/600. The D3D device is created at 800x600.
  3. RendDX9 initializeD3DSubsystems (0x1000B160) runs VideoOptions::initialize (0x10010850). It reads GSWindowSizeX/Y (default 0) and ends with scheme detection (result discarded) and applyQualityPreset(1) (0x1000F390), which selects "800x600" / AA "Off". (commit 1)
  4. Immediately after that (0x1000B2EE..0x1000B31B), RendDX9 fetches the host's VideoSettings singleton and calls applyToOptions(VideoOptions) (vtable +0x28, BF2.exe 0x4B2040). This pushes the pending record into the fresh options: the eight qualities, the resolution (dynamic option 1), the antialiasing (dynamic option 0, while GSLoadLevel is empty) and the view distance scale. The singleton is not null-tested. (commit 2)
    1000b2e9  call 0x10010850          ; VideoOptions::initialize(this+0x3F8)
    1000b2ee  push "VideoSettings"
    1000b308  call [classManager+0x48] ; getSingleton
    1000b30e  mov ebx, eax             ; no null test
    1000b316  mov edx, [ebx]
    1000b318  push edi                 ; edi = this+0x3F8 (VideoOptions)
    1000b31b  call [edx+0x28]          ; IVideoSettings::applyToOptions
    
  5. Renderer::initialize's first VideoOptions::apply (0x1002CD0E) finds a selected resolution that differs from the applied one. It calls the mode change (sub_1000F8D0, at 0x10010365 and 0x100103AE), which applies the GSWindowSizeX/Y override, resets the device to the selected mode and posts REChangeResolution (11,2). The SwiffHost menu viewport then follows. (commit 1)

Without commit 1, step 5 never changes the mode, so the viewport stays at 800x600 whatever the window size is. Without commit 2, the selection at step 5 is still preset 1, and the first apply switches to 800x600@60.

The change

  • Code/BF2/RendDX9/VideoOptions.cpp (commit 1):
    • Both resolution-change sites in VideoOptions::apply call the recovered mode change again. The hold and its [RETIRE-WHEN: rc-login-reset-hold] marker are removed.
    • VideoOptions::initialize ends with the shipped tail: scheme detection (slot +0x34, result discarded) and applyQualityPreset(1).
  • Code/BF2/RendDX9/RendDX9Init.cpp (commit 2): the line // [FRONTIER] VideoSettings still lacks the VideoOptions handoff. is replaced with the recovered call. The native plugin ABI uses the typed IVideoSettings::applyToOptions. The retail-ABI arm calls slot +0x28, the same way other host calls in RendDX9 do. No null test, as shipped.

Both are retail recovery from BF2.exe and RendDX9.dll 1.50, so no issue is linked.

Note for review: upstream fix/client-testing also edits VideoOptions.cpp, so expect a small overlap there.

Where we got stuck

Commit 1 alone fixed the windowed +szx/+szy case, but fullscreen still showed the login menu at 800x600. Static reading alone pointed the wrong way. We first followed every path that could change the resolution before login:

  • BF2Render::init: hard-coded 800/600 defaults.
  • LocalProfileManager::init: applySettings(1) is audio only.
  • VideoOptions preset 1: "800x600".
  • Every reader of the VideoSettings global in BF2.exe (25 sites): apply only on login or the Options page.
  • SwiffPlayer.dll and mainMenu.swf: the login page never calls VideoSettings.apply.
  • The RendDX9 mode list and string match.
  • ECCore 5/2 and 5/3, which are only posted on level load.

All of these said "800x600 until login", which contradicted what retail visibly does. We also ruled out PE header and manifest differences (a test build with retail-like linker flags still showed black bars) and AMD per-game scaling.

The handoff was missed because it is not reached through the VideoSettings global in BF2.exe. RendDX9 calls it by singleton name from inside the DLL's own startup.

How apitrace found it

We captured vanilla retail BF2.exe 1.50 (no arguments, fullscreen) with apitrace 14.0 (32-bit, apitrace trace --api d3d9) up to the login menu, then dumped the D3D9 calls:

  • call 23 CreateDevice: 800x600, fullscreen, 85 Hz, no MSAA (same as the rebuild)
  • call 15876 Reset: 3840x2160, fullscreen, 165 Hz, 8x MSAA, before the first Present, right after the five event queries in Renderer::initialize

So retail never shows an 800x600 frame. The reset sits between VideoOptions::initialize and the first apply. A string search for VideoSettings in RendDX9.dll found exactly one reference: the getSingleton("VideoSettings") → +0x28 call at 0x1000B2EE that the rebuild lacked.

Testing

  • x86 client (bf2_client) built from this branch: PASS.
  • Windowed, variations of 4:3 and 16:9 resolutions like +szx 1600 +szy 900+fullscreen 0
  • Fullscreen, no +szx/+szy, Default Video.con = 3840x2160@165 / 8Samples (both commits): RendDX9Rebuilt.log shows the same order as the retail trace. The device is created at 800x600@85, then the first VideoOptions::apply sets presentParameters to 3840x2160 @165 fullscreen before the first frame. The quality rows come from Video.con (3 instead of preset 1's 2). Menu fonts are built for vp (0,0,3840,2160). The login menu fills the screen in 16:9, matching retail.
  • Logging in and changing resolution from the Options page still works.
  • Local CI-equivalent run (same steps as .github/workflows/build.yml) on 68d921d, plus the not-yet-merged fix/x64-voip-link that is needed for the x64 link. All PASS: x64 python, configure, bf2_w64ded; x86 python, configure, bf2_run_checks + bf2_editor_run_checks, bf2_w32ded, bf2_aidll + bf2_client, bf2_editor. The Linux CI job was not run locally.

Not exercised

  • x64 client, Linux build.
  • The 8x MSAA value is not printed by the rebuild log (presentParameters does not log multisample). It is pushed in the same call as the resolution and qualities.
  • Fullscreen with +szx/+szy together.

Remaining frontiers nearby

  • VideoOptions::handleECCoreEvent (PE 0x100103F0) is still not registered. It only matters on level load (ECCore 5/2 and 5/3), not at startup.
  • QUIT on the login page does not work (Logic.quit not registered). This is unrelated to this change.

AndreasLauvhjell and others added 2 commits November 4, 2026 22:40
VideoOptions::initialize ends with the scheme detection (result discarded)
and applyQualityPreset(1), which selects "800x600" and "Off" (RendDX9
0x10010850 tail, 0x1000F390). VideoOptions::apply then calls the resolution
change at 0x10010365 and 0x100103AE, which applies the GSWindowSizeX/Y
override and posts REChangeResolution, so the SwiffHost viewport follows
+szx/+szy before the login menu is built.

Both pieces were skipped as [CHOSEN, not recovered] (the startup preset and
the rc-login-reset-hold). Tested with a 1600x1200 window: the login and main
menus fill it, and the mode change at login and on Apply survives the
device reset.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
RendDX9 initializeD3DSubsystems, right after VideoOptions::initialize,
fetches the host "VideoSettings" singleton and calls its applyToOptions
(+0x28) with the embedded VideoOptions (RendDX9.dll 0x1000B2EE..0x1000B31B,
no null test on the singleton). This pushes the pending record loaded by
LocalProfileManager::init (the Default profile's Video.con) into the
options, so Renderer::initialize's first apply switches to the profile
resolution, antialiasing and qualities before the first frame.

The rebuild had this as a frontier, so the first apply used preset 1
(800x600) and the login menu showed 4:3 with black bars until login.
An apitrace D3D9 capture of retail BF2.exe shows CreateDevice 800x600@85
followed by a single Reset to 3840x2160@165 (8x MSAA) before the first
Present. Tested with the x86 client fullscreen without +szx/+szy: the
first apply now resets to 3840x2160@165 and the login menu opens in 16:9.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant