narzoul / DDrawCompat

DirectDraw and Direct3D 1-7 compatibility, performance and visual enhancements for Windows Vista, 7, 8, 10 and 11
BSD Zero Clause License
923 stars 70 forks source link

Cursor drift when the config overlay is active #342

Closed automatedbugreportingfacility closed 2 weeks ago

automatedbugreportingfacility commented 1 month ago

I noticed that when I open the config overlay in Wild Wild West: The Steel Assassin, the game cursor will drift to the lower right corner. This reminded me of a comment I read at https://github.com/narzoul/DDrawCompat/issues/338#issuecomment-2211725395 :

When you open the config overlay, a low level mouse hook is installed in order to make sure mouse events are not processed by the game, and are processed be the overlay instead. When you close the overlay, the hook is uninstalled, so your game can start processing mouse events again. This is why you can't move your in-game cursor while the config overlay is open, and clicks will go to the overlay only and won't affect the game.

I'm not sure if this behavior is a result of some event not being caught/redirected properly or rather the game acting weird in the absence of some expected input event, but I thought I'd report it anyway, especially that the game will occasionally crash when the config overlay is dismissed.

narzoul commented 1 month ago

I noticed that when I open the config overlay in Wild Wild West: The Steel Assassin, the game cursor will drift to the lower right corner.

Is it drifting constantly, or only occasionally while moving the mouse? If it's the latter, I can reproduce it, though it's in a random direction during active mouse movement and pretty rare. Maybe it's easier to reproduce with high polling rate mouse. The game periodically calls SetCursorPos after every flip (no idea what for), and the mouse hook is temporarily uninstalled and reinstalled during that, which might allow for a mouse move event to slip through unblocked.

especially that the game will occasionally crash when the config overlay is dismissed.

This I have never seen. Please enable CrashDump=full, it might produce a usable dump file next time it happens. If it's easy to reproduce, LogLevel=debug could also be useful.

automatedbugreportingfacility commented 1 month ago

I've made some further tests, and this is related to DPI scaling. Please set the DPI scale factor to 150% or 125% (DpiAwareness=unaware as well to fix the broken cursor) and retest.

The drift is constant and does not depend on any mouse movement, the game cursor moves even if the overlay cursor is not moved at all. The drift starts immediately after opening the config overlay. My mouse allows switching the polling rates, and it does not seem to affect the drift speed.

Debug log: DDrawCompat-VRDMfc.zip

The main thread stack from the crash dump is:

[0x0]   ntdll!NtWaitForAlertByThreadId+0xc   0x19f614   0x77a1fcf9   
[0x1]   ntdll!RtlpWaitOnAddressWithTimeout+0x64   0x19f618   0x77a1fa4d   
[0x2]   ntdll!RtlpWaitOnCriticalSection+0x18d   0x19f648   0x77a0023a   
[0x3]   ntdll!RtlpEnterCriticalSectionContended+0x1aa   0x19f6e8   0x77a00089   
[0x4]   ntdll!RtlEnterCriticalSection+0x49   0x19f720   0x5fc698f6   
[0x5]   ddraw_5fc40000!AcquireDDThreadLock+0x16   0x19f72c   0x5fdfe0c0   
[0x6]   ddraw!DDraw::ScopedThreadLock::{ctor}+0x6   0x19f740   0x21504405   
[0x7]   ddraw!VtableHookVisitor<IDirectDrawSurface4Vtbl,DDraw::ScopedThreadLock>::hookFunc<28,long,IDirectDrawSurface4 *,unsigned long,unsigned long,IDirectDrawSurface4 *,tagRECT *,unsigned long>+0xa0   0x19f740   0x21504405   
[0x8]   xvideoio!BltSurface+0xc5   0x19f7e8   0x27005a22   
[0x9]   svpdrv!VrdDibDraw+0x1bc   0x19f814   0x2902a8f1   
[0xa]   svprun!VrdRun+0x4f41   0x19f87c   0x2902afdd   
[0xb]   svprun!VrdRun+0x562d   0x19f8fc   0x2902b5a3   
[0xc]   svprun!VrdRun+0x5bf3   0x19f928   0x29025287   
[0xd]   svprun!RunProduction+0x467   0x19f9a0   0x507ffa   
[0xe]   VRDMfc + 0x7ffa!VRDMfc+0x7ffa   0x19fb40   0x19fbc0   
[0xf]   0x19fbc0!+   0x19fb44   0x50e648   
[0x10]   VRDMfc + 0xe648!VRDMfc+0xe648   0x19fb48   0x5c747365   
[0x11]   0x5c747365!+   0x19fbc8   0x6e75614c   
[0x12]   0x6e75614c!+   0x19fbcc   0x32336863   
[0x13]   0x32336863!+   0x19fbd0   0x696e692e   
[0x14]   0x696e692e!+   0x19fbd4   0x0   

The game periodically calls SetCursorPos after every flip (no idea what for), and the mouse hook is temporarily uninstalled and reinstalled during that, which might allow for a mouse move event to slip through unblocked.

Just a wild guess, but is the case of the overlay being dismissed while the mouse hook is temporarily disabled handled correctly? I noticed that in the case of a crash, the cursor will continue to drift for a while after I dismiss the overlay until a crash occurs. In the lucky case, the cursor will stop moving immediately after closing the overlay.

narzoul commented 1 month ago

I've made some further tests, and this is related to DPI scaling. Please set the DPI scale factor to 150% or 125% (DpiAwareness=unaware as well to fix the broken cursor) and retest.

Of course, that was one of my first ideas too, but it wasn't enough. Based on your logs, I assume you have changed the game's Hardware Configuration to "No page flipping". With that, I can reproduce the cursor drift now.

The main thread stack from the crash dump is:

That doesn't look like something that crashed, it's just waiting for the DirectDraw critical section. The crash must have happened on a different thread. Debug logs might indicate which one.

Anyway, even without page flipping and with high DPI scaling, I can't reproduce it. I pressed shift+f12 continuously for a few minutes so that the overlay rapidly turns on and off, but no crashes.

automatedbugreportingfacility commented 1 month ago

Based on your logs, I assume you have changed the game's Hardware Configuration to "No page flipping". With that, I can reproduce the cursor drift now.

Yes, as mentioned at https://github.com/narzoul/DDrawCompat/issues/306#issuecomment-2126756466, "No page flipping" is the only configuration option that delivers a perfectly smooth 60 FPS animation when DDrawCompat is used (bar a special case or two, but I digress).

The exact steps I use to reproduce the crash: on the main menu screen, press SHIFT+F11 to open the overlay screen. After a random amount of time (1/2/3 seconds), press SHIFT+F11 to dismiss. If the cursor continues to drift even with the overlay closed, it will crash as soon as you move the mouse (this is a detail I missed, moving the mouse is necessary after all). If the drift doesn't continue -> bring up the overlay again to retry. A minor detail I just noticed: when the cursor continues the drift with the overlay closed, instead of moving the mouse, I can bring up the overlay again and avoid the crash. Please remember to use CpuAffinity=all -- the unstable nature of the crash suggests it might be a race condition.

I've just figured out I can use .ecxr on the dump file, and it led me to this:

0:010> .ecxr
eax=00000000 ebx=00000200 ecx=00000000 edx=002b3000 esi=000002d0 edi=00000500
eip=5ff764d0 esp=0e5afdac ebp=0e5afdc4 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246
ddraw!Overlay::Control::isEnabled:
5ff764d0 f7411800000008  test    dword ptr [ecx+18h],8000000h ds:002b:00000018=????????

Stack trace:

[0x0]   ddraw!Overlay::Control::isEnabled   0xe5afdac   0x5ff7210d   
[0x1]   ddraw!`anonymous namespace'::lowLevelMouseProc+0xdd   0xe5afdb0   0x75f45ba9   
[0x2]   user32!DispatchHookA+0x89   0xe5afdcc   0x75fb3473   
[0x3]   user32!__fnHkINLPMSLLHOOKSTRUCT+0x33   0xe5afe0c   0x77a354ed   
[0x4]   ntdll!KiUserCallbackDispatcher+0x4d   0xe5afe40   0xe5aff2c   
[0x5]   0xe5aff2c!+   0xe5afe4c   0x5ff5644a   
[0x6]   ddraw!`anonymous namespace'::messageWindowThreadProc+0x1da   0xe5afec4   0x5ffadbd8   
[0x7]   ddraw!thread_start<unsigned int (__stdcall*)(void *),1>+0x58   0xe5aff40   0x76e0fcc9   
[0x8]   kernel32!BaseThreadInitThunk+0x19   0xe5aff78   0x77a280ce   
[0x9]   ntdll!__RtlUserThreadStart+0x2f   0xe5aff88   0x77a2809e   
[0xa]   ntdll!_RtlUserThreadStart+0x1b   0xe5affe4   0x0   

Looks like g_capture is dead when if (!g_capture->isEnabled()) is executed in lowLevelMouseProc().

EDIT: the mouse hook is installed asynchronously:

    void resetMouseHook()
    {
        Gdi::GuiThread::executeAsyncFunc(resetMouseHookFunc);
    }

So there's no guarantee that g_capture isn't cleared between the time resetMouseHookFunc() is scheduled and the moment it's actually executed. This may lead to lowLevelMouseProc() running with a null g_capture.

narzoul commented 2 weeks ago

Thanks, both issues should be fixed in v0.5.4 now. The cursor will still drift when moving the mouse while the config overlay is open, which seems to be a side effect of the frequent GetCursorPos/SetCursorPos calls, combined with the general cursor position inaccuracy of DPI unaware mode. The cursor is not supposed to be able to move at all when LowLevelMouseProc returns 1, but apparently this isn't quite working correctly in this case. I can't do much about it, but it also doesn't seem to cause any other serious problems.