narzoul / DDrawCompat

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

Request for help #161

Closed ghotik closed 7 months ago

ghotik commented 1 year ago

Hi, Narzoul. Here's gho speaking, the author of one of the DxWnd releases, probably the only one that is currently still open-source. I was trying to fix some issues in "Star Wars: Shadows of the Empire" when I realized that joining DxWnd and DDrawCompat was fixing all the problems, making the game running in window mode with two big enhancements: 1) fogging was working 2) the Z-Buffer ordering was perfect

Thanks to your choice to make DDrawCompat an open source project, I hope you don't mind if I try to migrate these two enhancements in my code, but the trouble is that very likely I could need your help. In particular, I noticed that the fogging (I'm not sure about Z-ordering) is fixed in the D3Dddi module, a D3D dependency that is very poorly documented and not meant for direct usage by application programs. If you don't mind wasting some time trying to help me, please give me a way to contact you. I searched here with no success a private mail channel, so if you want you could use mine at SourceForge, or partecipate to the discussion in this thread: https://sourceforge.net/p/dxwnd/discussion/general/thread/5aec3e8bf5/#4bd7

narzoul commented 1 year ago

Well, it looks like I lost my old SourceForge account and it was tied to an e-mail address that no longer exists, so I can't recover it either. I'll just reply here.

I'm not sure you need to mess with D3DDDI at all just to fix fog, most likely changing the default projection matrix to comply with the rules explained in the "Eye-Relative vs. Z-based Depth" of the DX7 SDK is enough. Specifically, this sentence: "Direct3D checks the fourth column of the projection matrix, and if the coefficients are [0,0,0,1] (for an affine projection) the system will use z-based depth values for fog."

The default projection matrix is the identity matrix if I remember right, so it should default to Z-based fog, but for some reason even the reference rasterizer defaults to W-based fog anyway. But changing just the last column to [0,0,1,0] might be enough to switch to W-fog. It could be done immediately after device creation. Most likely apps that don't change the projection matrix at all won't care about what's in there anyway, though I haven't tested this, and I could be wrong.

As for Z-buffers, there is only one fix in DDrawCompat that I recall, but I don't see how it would fix the mentioned issues. I don't think I ever tested this game either (even though I have it on GOG). Anyway, the fix was related to AMD GPUs, specifically how they report the supported Z-buffer bit depths incorrectly. The dwDeviceZBufferBitDepth member of D3DDEVICEDESC(7) is supposed to indicate the supported depths, and my AMD Radeon RX 480 reports 16 and 32 there, even though it only supports 32 bits as 24 bits Z + 8 bits stencil. I remember this causes a crash at least in Rogue Spear, where the game tries to create a Z-buffer with the old DDSURFACEDESC struct, where you have to specify the Z-buffer depth as a single number instead of as a DDPIXELFORMAT. The game puts 32 there and then fails to create the Z-buffer, because it interprets it as a request to create a pure 32-bit Z-buffer, which isn't supported. This fix was moved to D3DDDI also, but it used to be fixed in pure D3D in older versions. You can find both alternatives here: https://github.com/narzoul/DDrawCompat/commit/050248a552f61a8c87365021490ac1bed59ff850

If you want to hook D3DDDI anyway, here is the basic procedure:

  1. The runtime calls GetProcAddress to find the OpenAdapter function's address in the UMD DLL.
  2. The runtime calls OpenAdapter, which fills an important function table (pAdapterFuncs). You can replace whichever functions you need there before passing the results back to the runtime.
  3. The runtime calls CreateDevice (from the previous function table) to create a device. Upon return, this fills another important function table (pDeviceFuncs). Again, you can replace whichever functions you want there before returning the result to the runtime.

The tricky part is that in theory, a system can have multiple UMD DLLs, and more than one can be used simultaneously from a single process. So, for full support it's best to make a copy of each original function table and associate them with the adapter/device handles that are also returned by OpenAdapter/CreateDevice. Whenever the runtime calls one of the adapter/device functions, it passes those handles as the first parameter, so you'll know which table to look up to call the original function from the correct DLL.

The procedure is also a bit different if D3D9On12 is involved. I don't remember the specifics, but I managed to add support for it in this commit a while ago: https://github.com/narzoul/DDrawCompat/commit/20912dce37b481cd7e76eec90a37235ca026ecfe I used the D3D9On12 readme and source code for guidance.

ghotik commented 1 year ago

There's a lot of stuff to work with, so at the moment I'll just thank you very much, though very likely I'll have further requests as long as I try to apply your suggestions. Thanks a lot!

ghotik commented 1 year ago

Well, the first part of the problem was a big success. As you suggested, I set the projection matrix after the device creation and the fog appeared in the game test level. The second part, though, seems a bit harder to fix. I tried to fix the z-ordering problem by either setting some of the existing DxWnd flags related to Z-Buffer capabilities or by hardcoding some value in experimental builds, but with no success. Also, the game shows strange reactions to the z-ordering. I will try to explain better what's going on. The Z-Buffer creation in effect doesn't fail, I can have a Z-Buffer attached to the backbuffer surface, but this way the default Z-ordering is no good at all. In the test level all you can see is a blue panel, probably the sky texture brought in front instead of the far back. Also the Z-ordering works in some weird way: if I force a D3DCMP_ALWAYS flag I see a different scene, where some surfaces are correctly placed in the proper z-order and some other are not. It seems that polygons too close to the camera are not visible, but also some distant objects have problems. Maybe it's just a matter of the polygons creation order? Again, I can't blame all this to the windowing mode limitations because if I copy a DDrawCompat ddraw.dll wrapper in the game folder the scene is rendered perfectly. The picture here in attach shows the test level, now with the fog but with surfaces in the wrong z-order. In particular, there should be Dash Rendar in the foreground, but he is completely invisible. foggy

ghotik commented 1 year ago

Here is an interesting tweak: the PC version of "Star Wars: Shadows of the Empire" has several releases / patches. The version that I tested initially was using IDirect3D2 and the trick of setting the projection matrix works perfectly. Another game release, instead, is using IDirect3D where the SetTransform method does not exist. I tried to fix differently, with CreateMatrix and SetMatrix instructions placed at the device creation and at every BeginScene call, but it didn't work. This is not too surprising because in Direct3D it doesn't seem possible to select the matrix type to select specifically the projection matrix. But, again, your DDrawCompat wrapper works perfectly also in this case, so I wonder what else can be done. Maybe I will have to fix the projection matrix at the D3DDDI level, or could there be some other way?

narzoul commented 1 year ago

The IDirect3DDevice interface only supports execute buffers, so you'd have to set the projection matrix through the Execute method. Or maybe it's possible to QueryInterface for IDirect3DDevice2 and use SetTransform on that, but I doubt it. For execute buffers, I recommend the DX5 SDK, as it seems to be the only version to have any meaningful documentation on them, and it even includes a tutorial. The "Filling the Execute Buffer" and "Working with Matrices" parts of the tutorial could be especially useful.

ghotik commented 1 year ago

Wow, working with D3D1 is really tough! Maybe I start to understand, but maybe not. I believe I could create a matrix using IDirect3DDevice::CreateMatrix, this should return a matrix handle LPD3DMATRIXHANDLE that I should use in an additional execute buffer using D3DOP_STATETRANSFORM operation and D3DTRANSFORMSTATE_PROJECTION argument to address the matrix handle correctly. I'll let you know if it works. I found some useful stuff here http://archive.gamedev.net/archive/reference/articles/article911.html but I couldn't locate the tutorial that you mentioned, though I downloaded the full DX5 SDK from the Internet archive. Could you be more specific? I think a tutorial about matrices would be quite useful here.

BEENNath58 commented 1 year ago

but I couldn't locate the tutorial that you mentioned, though I downloaded the full DX5 SDK from the Internet archive. Could you be more specific?

I think he meant this file: d3dimref.zip

narzoul commented 1 year ago

I meant the docs/winhelp/directx.hlp file, Direct3D -> Direct3D Immediate Mode -> Direct3D Immediate Mode: Overview -> Direct3D Execute-Buffer Tutorial. I don't know if your SDK is different, I think I got my copy from here: https://archive.org/details/directxsdks You might also need some tool to open hlp files on Windows 10/11, I think I'm using this one: https://raxsoft.com/raxccm/software_app.php?progid=13

ghotik commented 1 year ago

Very good, with the winhlp reader I can now open the tutorial, thanks. I tried to fit this code into the execute buffer like I did in other cases:

  // Transform state - world, view and projection. 

    lpInstruction = (LPD3DINSTRUCTION)lpVertex; 
    lpInstruction->bOpcode = D3DOP_STATETRANSFORM; 
    lpInstruction->bSize   = sizeof(D3DSTATE); 
    lpInstruction->wCount  = 3U; 
    lpInstruction++; 
    lpState = (LPD3DSTATE)lpInstruction; 
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_WORLD; 
    lpState->dwArg[0] = hd3dWorldMatrix; 
    lpState++; 
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_VIEW; 
    lpState->dwArg[0] = hd3dViewMatrix; 
    lpState++; 
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_PROJECTION; 
    lpState->dwArg[0] = hd3dProjMatrix; 
    lpState++; 

but the D3DOP_STATETRANSFORM opcode seems to require the setting of all three transformation matrices at once and I don't know the content of the first two. I tried to bypass the problem by setting all three matrices to the w-based fog matrix or by setting the first two to a NULL matrix handle. In both cases the Execute instruction didn't return an error, but the fog did not appear. There must be something still wrong ... Curiously, there seems to be no way to get the currently active matrix handles to resubmit those that don't need a change.

narzoul commented 1 year ago

I haven't really messed with execute buffers much myself, but don't you just need to change wCount to 1 if you don't want to specify all 3 matrices? Then you need to add only one D3DSTATE struct I imagine.

ghotik commented 1 year ago

Right, I didn't figure it myself. But now, I'm sorry, things are getting more complicated. Thanks to all the suggestions and tutorials, I finally wrote some code that does what it was intended to, it gives no errors. The only problem is that I still see no fog! Now, in roper order: At the beginning of the Execute wrapper I build once and for all a transformation matrix for w-based fog, (exactly as for IDirect3DDevice2), then inject a small execute buffer with the instructions to make this matrix active:

    if(dxw.dwFlags16 & FORCEWBASEDFOG){
        if(mh == NULL){
            D3DMATRIX matrix = {
                1.0, 0.0, 0.0, 0.0,
                0.0, 1.0, 0.0, 0.0,
                0.0, 0.0, 1.0, 0.0,
                0.0, 0.0, 1.0, 0.0};
            extCreateMatrix(lpd3d, &mh);
            extSetMatrix(lpd3d, mh, &matrix);
        }
        PatchExecuteBufferTransformMatrix(lpd3d, mh);
    }

The creation and execution is done inside this PatchExecuteBufferTransformMatrix routine:

static void PatchExecuteBufferTransformMatrix(void *d3dd, D3DMATRIXHANDLE mh)
{
    HRESULT res;
    D3DEXECUTEBUFFERDESC ebdesc;
    LPD3DINSTRUCTION lpInstruction;
    LPDIRECT3DEXECUTEBUFFER lpeb;
    D3DEXECUTEDATA ed;
    LPBYTE lpBuf;
    int dwSize;
    LPD3DSTATE lpState;

    OutTraceDW("Patch: d3dd=%#x mh=%#x\n", d3dd, mh);
    // reserve space for the following:
    // instruction D3DOP_STATETRANSFORM with 1 LPD3DSTATE state record for D3DTRANSFORMSTATE_PROJECTION
    dwSize = sizeof(D3DINSTRUCTION) + sizeof(D3DSTATE);
    memset(&ebdesc, 0, sizeof(D3DEXECUTEBUFFERDESC));
    ebdesc.dwSize = sizeof(D3DEXECUTEBUFFERDESC);
    ebdesc.dwFlags = D3DDEB_BUFSIZE;
    ebdesc.dwBufferSize = dwSize;
    res = (*pCreateExecuteBuffer)(d3dd, &ebdesc, &lpeb, NULL);
    if(res) {
        OutTraceDW("Patch: CreateExecuteBuffer error ret=%d(%s) @%d\n", res, ExplainDDError(res), __LINE__);
        return;
    }
    res = (*pEBLock)(lpeb, &ebdesc);
    if(res) {
        OutTraceE("Patch: Lock error ret=%d(%s) @%d\n", res, ExplainDDError(res), __LINE__);
        return;
    }
    lpBuf = (LPBYTE)ebdesc.lpData;
    memset(lpBuf, 0, dwSize);

    lpInstruction = (LPD3DINSTRUCTION)lpBuf;
    lpInstruction->bOpcode = D3DOP_STATETRANSFORM; 
    lpInstruction->bSize   = sizeof(D3DSTATE); 
    lpInstruction->wCount  = 1U; 
    lpBuf += sizeof(D3DINSTRUCTION);
    lpState = (LPD3DSTATE)lpBuf; 
    lpState->dtstTransformStateType = D3DTRANSFORMSTATE_PROJECTION; 
    lpState->dwArg[0] = mh; 
    lpState++; 

    res = (*pEBUnlock)(lpeb);
    if(res) {
        OutTraceE("Patch: Unlock error ret=%#x(%s) @%d\n", res, ExplainDDError(res), __LINE__);
        return;
    }
    memset(&ed, 0, sizeof(D3DEXECUTEDATA));
    ed.dwSize = sizeof(D3DEXECUTEDATA);
    ed.dwInstructionOffset = 0;
    ed.dwHVertexOffset = 0;
    ed.dwInstructionLength = dwSize;
    ed.dwVertexCount = 0;
    ed.dwVertexOffset = 0;

    res = (*pSetExecuteData)(lpeb, &ed);
    if(res) {
        OutTraceE("Patch: SetExecuteData error ret=%#x(%s)\n", res, ExplainDDError(res));
        return;
    }
#ifdef DUMPEXECUTEBUFFER
#ifndef DXW_NOTRACES
    DumpEBData(lpeb);
#endif // DXW_NOTRACES
#endif // DUMPEXECUTEBUFFER
    res = (*pExecute)(d3dd, lpeb, lpCurrViewport, D3DEXECUTE_CLIPPED); // viewport? flags?
    if(res) {
        OutTraceE("Patch: Execute error ret=%#x(%s)\n", res, ExplainDDError(res));
        return;
    }
    (LPDIRECT3DEXECUTEBUFFER)lpeb->Release();
}

Since I didn't trust this logic myself, I made a dump of the execution buffer using DumpEBData(lpeb). THis is the result:

> ==== DumpEBData lpeb=0x7fe4f20 ====
> Vertex: offset=0 count=0 hvertexoffset=0
> Instr.: offset=0 len=12
> Instr.[0000]: op=6(STATETRANSFORM) size=8 count=1
>> STATETRANSFORM 0x3(PROJECTION) matrix=0xd6c868

now of course the buffer creation and dump routine could be affected by the same bugs, but this should be unlikely enough. So, assuming that I'm really pushing an execute buffer with this content, why the fog doesn't come out? P.s. the game full dump shows that its logic in effect tries to show some fog, at least in the second level "Escape from Echo Base", this an extract from the full log:

> Instr.[0005]: op=8(STATERENDER) size=8 count=5
>> [0000]: code=0x1c(FOGENABLE) val=0x1
>> [0001]: code=0x22(FOGCOLOR) val=0x96c8ff
>> [0002]: code=0x23(FOGTABLEMODE) val=0x3
>> [0003]: code=0x24(FOGSTART) val=0x41800000
>> [0004]: code=0x25(FOGEND) val=0x43250000
narzoul commented 1 year ago

According to the docs, the last instruction should be D3DOP_EXIT, but I don't know if that's the problem. If that doesn't help, maybe try looking at the DDI logs (if you can do that with DDrawCompat) and see if pfnUpdateWInfo is getting called sometime after the execute buffer is executed, but before the first pfnDrawPrimitive/pfnDrawIndexedPrimitive2 call. wNear and wFar should not be both 1.0.

ghotik commented 1 year ago

The missing D3DOP_EXIT was intentional because the effect of all preceding commands could be terminated there, as I found in other tweaks (for instance, the injection of execute buffer chunks to force the wireframe display mode). In any case, to clear any doubt, I tried adding it and nothing changed. It seems that there is some basic thing that stops the effects, because I tried also setting weird matrices and applying them to the other transformation, expecting to see weird effect, but nothing still happened. Still in the attempt to clear any possible doubt, I also added the hexdump of the injected buffer, here it is:

> ==== DumpEBData lpeb=0x862cb80 ====
> Vertex: offset=0 count=0 hvertexoffset=0
> Instr.: offset=0 len=12
0000: 06.08.01.00.03.00.00.00.58.39.C1.00.__.__.__.__. - ........X9..
> Instr.[0000]: op=6(STATETRANSFORM) size=8 count=1
0000: 03.00.00.00.58.39.C1.00.__.__.__.__.__.__.__.__. - ....X9..
>> STATETRANSFORM 0x3(PROJECTION) matrix=0xc13958
> ==== DumpEBData END ====

I would dare say that now the buffer is fine, either with or without D3DOP_EXIT statement. I can't imagine what's wrong, but I'm sure it must be something really stupid. Well, sooner or later I will stumble over the problem, hopefully. But I'd like to open a new request about the other bigger problem About the polygons sorting. After all you can play the game without fog, but you can't shoot to invisible enemies! P.s. I don't want to bother you too much, but it is intended that, in the case you wish to have a closer look at this mess, I can easily share with you the project in its current, half-completed stage.

ghotik commented 1 year ago

Well, surprise! I don't know what happened, but now changing some DxWnd flags the 3D scene seems rendered almost correctly (some clipped textures here and there, like the distant wall that is transparent and shows the sky, but not too many and not always). So, the fogging remain my last pain in the neck ... swsote

BEENNath58 commented 1 year ago

Well, surprise! I don't know what happened, but now changing some DxWnd flags the 3D scene seems rendered almost correctly (some clipped textures here and there, like the distant wall that is transparent and shows the sky, but not too many and not always).

I would want to know what setting you choose that made the textures work. Maybe it will help me in my cases too where it doesn't render?

ghotik commented 1 year ago

@narzoul: the only thing that comes to my mind and I didn't experiment yet is this one: currently the transformation matrix is set into separate execute buffers that precede the one set by the game, assuming that you can do a BeginScene() followed by one or more Execute() and a final EndScene(). Could it be necessary to inject the matrix setting inside the same execute buffer? This could be easily coded by reading the buffer content and creating a new one with increased length that holds both buffers. Well, maybe it's worth a try ... @BEENNath58: the DxWnd flag is "Direct3D / Clean ZBUFFER 1.0 fix", but also "Direct3D / Clean ZBUFFER 0.0 fix" works. What puzzles me is that I Am quite sure I tried this trick before and it didn't work, who knows why ...

narzoul commented 1 year ago

Well, I'm no longer sure if it's possible without hacking the DDI. If the device was created originally with CreateDevice, then it's possible to QueryInterface for IDirect3DDevice2 from IDirect3DDevice and call SetTransform on that, which seems to work. But if the device is created with QueryInterface, then querying for IDirect3DDevice2 fails.

I tried to get around it with some tricks, like creating a separate IDirect3DDevice2 and using SetTransform on that. If you then render something through that device, it will call pfnUpdateWInfo with the right values. But as soon as switching back to the execute buffer of the original device, no matter what's in the buffer, it calls pfnUpdateWInfo again with the wrong values.

Maybe the QueryInterface strategy is viable if you can ensure that the original device is created through CreateDevice rather than QueryInterface, so that you can immediately call SetTransform on it, and just query for IDirect3DDevice and pass that one back to the application. The IDirect3DDevice2 interface can probably even be released then to avoid a leak. I'm too tired to test this approach now.

This approach can have some pitfalls too, for example I noticed it's possible to QueryInterface for IDirectDrawSurface from a device created with QueryInterface, but not for a device created with CreateDevice. So, if needed, this might have to be emulated too e.g., by using GetRenderTarget to get an interface to the surface instead. I'm not sure what else could be broken like that.

ghotik commented 1 year ago

It seems that the path is filled with obstacles ... Thank you for your notes, in the meanwhile I was trying the QueryInterface approach as well and I was just wonderingwhy the operation failed. Now I know it's a practical dead end. Still, I can't understand how comes that pushing a transform matrix on the execute buffer gives a null result: the documentation for IDirect3DDevice includes that possibility, so there must be something else that I don't understand. I was wondering if there could be some instruction that resets al matrices to some default value, but I didn't see anything like that so far. I'll keeps reading the docs. Finally, I wonder if it's not time to make an "investment for the future" and try hooking the DDI interface. This could open better possibilities even for other features, as it seems to happen in DDrawCompat. If you read the DxWnd source code you may notice that the hooking approach is different from other wrappers (maybe different also from DDrawCompat, though I'm not sure) because it doesn't build wrapper classes that inherit from the native class and must interface every single method. Instead, it replaces the method pointers inside the vtable so that it is necessary to hook ONLY the methods that need patching, plus of course every method that can lead there like CreateXxx, QueryInterface etc. This should make it quicker to code some compact wrapper, though it has its own drawbacks. Well, ATM thanks again, I'll let you know about any further progress.

ghotik commented 1 year ago

After some painful coding, I got a different implementation for the SetTransform equivalent for IDIrect3DDevice version 1: Instead of sending a dedicated execute buffer (with the transform matrix definition) before the target execute buffer (the one built by the application), now I intercept the target execute buffer and rebuild it filling inside the old content plus the matrix definition. This would eliminate any concern about the scope to the matrix definition and the fear that the D3DOP_EXIT instuction could clear the validity of the transformation. I knew it was unlikely, but in effect nothing changes: also in this way the transformation matrix is not considered and the scene has no fog. But this cleared any doubt about it. I think that pushing a transformation matrix is not enough (despite what seems to see in the tutorials), I believe that once defined it should be also enabled in some way. But how?

ghotik commented 1 year ago

On the other front, about the DDI patching: I found the coding of your method in DDrawCompat source file hook.cpp within the procedure getProcAddress (the wrapper of the actual system call). I assume that the getProcAddress calls for "OpenAdapter" and "GetPrivateDDITable" are done within the ddraw.dll modules, that it is not further scanned by the current DxWnd PE scan method. So, a basic requirement for all this to work seems to use inline hooking to be sure that every getProcAddress call is hooked and passed to the proxy wrapper. I wonder if, despite the quite different coding style (mine is more rudely closer to plain C than actual C++) there could be some code chunk that I could grab and import in DxWnd once the hooking is set.

narzoul commented 1 year ago

But this cleared any doubt about it. I think that pushing a transformation matrix is not enough (despite what seems to see in the tutorials), I believe that once defined it should be also enabled in some way. But how?

I think there is probably no way to do this with execute buffers, you need at least IDirect3DDevice2. I found no other workaround (other than hooking DDI). That's why I recommended hooking the device creation and replacing the QueryInterface way with IDirect3D2::CreateDevice or newer, then SetTransform on that one should do the trick.

I found the coding of your method in DDrawCompat source file hook.cpp within the procedure getProcAddress (the wrapper of the actual system call). I assume that the getProcAddress calls for "OpenAdapter" and "GetPrivateDDITable" are done within the ddraw.dll modules, that it is not further scanned by the current DxWnd PE scan method. So, a basic requirement for all this to work seems to use inline hooking to be sure that every getProcAddress call is hooked and passed to the proxy wrapper.

The code in Common/Hook.cpp is a generic GetProcAddress hook installed via a method similar to detours, but it's not relevant for this. The one that handles the OpenAdapter/GetPrivateDDITable calls is an IAT hook of ddraw.dll's GetProcAddress import, in D3dDdi/Hooks.cpp.

ghotik commented 1 year ago

That's why I recommended hooking the device creation and replacing the QueryInterface way with IDirect3D2::CreateDevice or newer

I spent some time trying different approaches on the QueryInterface(IDirect3D) wrapper. Sadly, it seems that there's a sort of impossibility to switch to IDirect3D2 objects: either through multiple QueryInterface calls or through the creation of a new IDirect3D2 session something goes wrong. In particular, the direct creation of Direct3D2 or Direct3D2Device through the dedicated API calls just crashes the program. I will stop trying.

The one that handles the OpenAdapter/GetPrivateDDITable calls is an IAT hook of ddraw.dll's GetProcAddress import, in D3dDdi/Hooks.cpp.

Yes, I found that code, and this brings an interesting problem to DxWnd: my IAT patching routines navigate the IAT searching the module first and then searching the API call (that is reasonable because different dlls could share calls with the same name and different behavior). But looking at the ddraw.dll PE header the system calls are not all within kernel32.dll as I would expect, but they are spread on different modules (see screenshot) and I suspect that the module names could change on different ddraw and system releases. It seems that DDrawCompat doesn't have this problem, probably you are using some IAT scanning routine that browse the whole dependency tree.

ddraw

ghotik commented 1 year ago

Hi again. And again, thank you for your precious hints and source code. Trying not to bother you too much I spent some time trying to understand the DDrawCompat logic and implement your suggestions, and finally I got a IAT hook placed right inside the DDRAW module for the GetProcAddress system call. Now I'm ready to do whatever it takes to hook the OpenAdapter and GetPrivateDdiTable calls and remap the relative vtables, but I surely would need some help to explain what are the relevant actions to be set. I'm not trying now to replicate all the DDrawCompat features, also because the windowing may bring some important limitations to what it is possible to make. So, please, could you spend a few words explaining the actions that would enhance the ddraw compatibility? I am referring in particular to the two features that started this thread, that is:

thanks in advance.

narzoul commented 1 year ago

For the fog, just hook the pfnUpdateWInfo function in the device function table (see my earlier comment, it's the table returned by CreateDevice). My implementation is currently more complex, but this old version is sufficient for this and it's very simple: https://github.com/narzoul/DDrawCompat/commit/4dc1c70f4113721c523b29f05cd8537fa41e3564

For the Z-order, as I already stated earlier, there are no such fixes in DDrawCompat that I'm aware of, so I've no idea how to fix that one.

ghotik commented 1 year ago

Thanks, I think I got it (more or less). Whille trying to implement the W-fog fix I realized that DxWnd has a severe annoyance: since it is developed in VS2008 and targeted (for everything possible) to platforms starting from WinXP it seems that I can't take advantage of the Microssoft DDK and import some logic directly. Neverthless, all d3dumddi.h structures are documented in the Microsoft online web pages, so it should be possible to build a d3dumddi.h subset holding strictly what is needed for DxWnd and compatible with VS2008. It sounds a little painful, but it can deserve a try. I suppose this will keep me busy for a while!

ghotik commented 1 year ago

Some fog, at last! Please, don't mind the shrinked rendered area, this is an artifact caused by my bugged testbed, it should work better in other conditions. The picture is taken from the test level with the old game version based on Direct3D version 1 and through a navigation of the d3dumddi.h structures and functions as you suggested, though I had to import some simplified data structures in the VS2008 project. When the code will be a little more polished I'm going to show it to you, you'll realize what a mess I did! wfog1

here is a still rough version of the code. As soon as I load the ddraw.dll library (either statically or by LoadLibrary or whatever) I call the ddHookVideoAdapter() function that is sort of a selfish one. In DxWnd the (dxw.dwFlags16 & FORCEWBASEDFOG) boolean comes from the configuration panel and enables the trick. Of course, I'm going to clean the source some more and add the due thanks to you. You may notice that the d3dumddi.h file was not included and incorporated in the source code only for the relevant part. The LPGENERIC macro is a placeholder for any unreferenced field sized as a DWORD. I told you it was a mess! d3dumddi.zip