narzoul / DDrawCompat

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

Age of Wonders compatability #167

Closed pas-de-2 closed 10 months ago

pas-de-2 commented 1 year ago

First of all, Age of Wonders requires the COMRedirection regedit fix, otherwise it attempts to load the system ddraw.dll.

The menu loads okay, but an exception gets thrown anytime your party reaches something that would trigger a pop up: image

narzoul commented 1 year ago

I don't have this issue and it doesn't sound DirectDraw related. First hit for the error message on google shows this: https://support.gog.com/hc/en-us/articles/360020012933-Age-of-Wonders-exception-during-MapViewer-ShowScene?product=gog Have you tried it?

pas-de-2 commented 1 year ago

Hey that actually does fix the issue, thanks. I assumed it was DDraw related because it didn't occur for me without the DDrawCompat .dll.

One other problem I'm noticing is that the game doesn't exit cleanly, I have to end it via task manager whether I alt+f4 or use the game's menu to quit. Does this happen for you?

narzoul commented 1 year ago

Do you have a save game that can reliably reproduce the exception? Maybe I'm checking the wrong places. Yeah, I've noticed the issues with exiting, I'll look into it.

pas-de-2 commented 1 year ago

It happens every time I move the party to the first watchtower in the tutorial. FWIW my desktop resolution is 2560x1440, maybe that has something to do with it.

Edit: Reading further, some people suggest it's a problem with windowed mode, but forcing FullscreenMode = exclusive in DDrawCompat.ini causes the game to crash on startup. E: tried disabling G-Sync, and that let me load the game, but then I got the MapViewer crash as soon as I pressed Tutorial.

It'd be great if we could figure out what's happening there, because it looks like the patch just nops out a few problematic loops, and that results in some ugly artifacts across the top of the map.

narzoul commented 1 year ago

Thanks for the tip, indeed running the game in windowed mode (in game settings, not via FullscreenMode), I can finally reproduce the exception with the watch tower in the tutorial. It took way longer to find the issue than it should have, although it's a rather simple bug in the game's code.

I found this old forum post explaining what the patch I linked earlier does: https://aow.heavengames.com/cgi-bin/forums/display.cgi?action=ct&f=1,4694,0,all

Basically, it replaces the following hexadecimal byte sequence in the Ilpack.dpl file: 66 8B 16 8D 74 16 03 81 E6 FC FF FF 0F 48 75 F0 with this: 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90

This actually just removes the problematic code (replaces it with no-op instructions), which could have some side effects, like the mentioned graphical glitches.

When running the game through Visual Studio's debugger, I got an exception at the same code and was able to investigate it a bit further. The assembly version of it looks like this:

55210BD0 66 8B 16             mov         dx,word ptr [esi]
55210BD3 8D 74 16 03          lea         esi,[esi+edx+3]
55210BD7 81 E6 FC FF FF 0F    and         esi,0FFFFFFCh
55210BDD 48                   dec         eax
55210BDE 75 F0                jne         55210BD0

In my case, the exception was an invalid access at memory location 0x3DED9C, which matched the value of the esi register at the instruction at 0x55210BD0 in the debugger. This simple loop is iterating through some list, initially starting at the pointer stored in esi. In each iteration, it increments esi by the word value found at the memory location stored in esi, then rounds the result up to the nearest multiple of 4. It repeats this a number of times as specified by the eax register.

I backtracked the original value of esi from the stack, which was 0x103DED88. The word value found at that memory location was 0x0014, so the new value of esi should be 0x103DED88 + 0x0014 = 0x103DED9C, which is already a multiple of 4, so the rounding up should do nothing. It's almost the same value that the exception complains about, except there is an extra "1" digit at the beginning. And that's when I noticed, looking closer at the mask passed to the "and" operation, that it was missing an F! It's supposed to 0FFFFFFFCh instead of 0FFFFFFCh. So not only does it round the address up to the nearest multiple of 4, it also accidentally cuts off the topmost 4 bits of it. Which happens to crash on the next read from that address, since 0x3DED9C wasn't a valid memory area in the process.

Anyway, it's pretty simple to fix that one constant. Instead of using the replacement provided by the patch, use this sequence: 66 8B 16 8D 74 16 03 81 E6 FC FF FF FF 48 75 F0 The only difference compared to the original is that 0F is replaced with FF. If you already have a patched file, look for the sequence of 90s and replace that with this new sequence instead. Note that there are 3 such replacements to be made in the file. I haven't had any exceptions after a few tries with this.


As for the issues with exiting the game, one of them seems to be caused by DDrawCompat. It deadlocks in NtUserDestroyWindow, I didn't really understand why, but I found a workaround anyway: ddraw.zip (diff.txt compared to v0.4.0)

This should fix the problem when exiting "cleanly". Alt+F4 still won't work properly, at least in fullscreen mode, but that seems to be another bug in the game. I don't think it handles WM_CLOSE properly, it's just passed on to DefWindowProc, which destroys the fullscreen device window used by DirectDraw. The behavior is the same with native ddraw, so I don't think it can be properly fixed from DDrawCompat. Maybe I'll add some kind of workaround, like a customizable hotkey to work as a "kill switch" that force terminates the application, which I thought would be a useful feature in some other cases anyway. It could then even be set to trigger on Alt+F4, thus hiding the bug in the game.

pas-de-2 commented 1 year ago

That's great! I was staring at the assembly for a bit but couldn't parse what was wrong, as I'm pretty new to this stuff, so I really appreciate the explanation.

Finished the tutorial scenario without encountering any exceptions or visual artifacts. It's true that alt+F4 is still bugged, but I noticed you can at least close the process from the task bar now should you forget to exit "normally".

int19h commented 1 year ago

Thank you for digging in further. I did this patch so long ago that I couldn't even remember the original methodology of how I did it (beside using IDA).

FWIW I actually tried detouring DDraw calls to see if there could be a workaround by patching arguments. Alas, it turns out that the game does its own composition and then just blits the resulting surface to video buffer, so there is no place where actual rendering can be intercepted. So hopefully this will remain the only rendering bug in the game going forward...

narzoul commented 10 months ago

The deadlock on clean exit is fixed in v0.5.0. You can use the new TerminateHotKey setting to "fix" alt+f4.