FunkyFr3sh / cnc-ddraw

GDI, OpenGL and Direct3D 9 re-implementation of the DirectDraw API for classic 2D games for better compatibility with Windows 2000, XP, Vista, 7, 8, 10, 11, Wine (Linux/macOS/Android) and Virtual Machines
https://discord.gg/afWXJNDDF5
MIT License
2.12k stars 143 forks source link

American Girls Dress Designer support? #313

Closed huckleberrypie closed 1 month ago

huckleberrypie commented 1 month ago

Been trying to get this game to upscale for higher-resolution displays. I know the game uses DirectDraw as I can hook to it, but for some reason it doesn't seem to be doing anything at all. cnc-ddraw-1.log

FunkyFr3sh commented 1 month ago

My log looks a bit different compared to yours, in mine it's actually using DirectDraw.

Problem is, the game runs in a window and not in fullscreen (that's usually not supported by cnc-ddraw) - But I added a little hack to trick it into fullscreen (this may or may not work - more testing is required)

cnc-ddraw1.zip

huckleberrypie commented 1 month ago

My log looks a bit different compared to yours, in mine it's actually using DirectDraw.

Problem is, the game runs in a window and not in fullscreen (that's usually not supported by cnc-ddraw) - But I added a little hack to trick it into fullscreen (this may or may not work - more testing is required)

cnc-ddraw1.zip

You tried the game yourself? I was trying to stretch it to a larger viewport to no avail. The game does have a "fullscreen" mode but it only displays its contents on the center of the screen if the desktop resolution is higher. If I change the resolution values in the game's settings file it doesn't actually scale the viewport properly. 2024-05-22_17-17

FunkyFr3sh commented 1 month ago

Yes I tried it. And yes, I got that fake-fullscreen by default too. The game runs in 640x480 in the center of the screen. With the build i posted above it does go into proper fullscreen without white bars around it.

For any reason in your log the game didn't use DirectDraw, so that would explain why it doesn't work for you. Not sure why that is, for me it worked out of the box, no changes required.

Did you change any settings? How did you make your game run in such a small window without white bars?

huckleberrypie commented 1 month ago

Yes I tried it. And yes, I got that fake-fullscreen by default too. The game runs in 640x480 in the center of the screen. With the build i posted above it does go into proper fullscreen without white bars around it.

For any reason in your log the game didn't use DirectDraw, so that would explain why it doesn't work for you. Not sure why that is, for me it worked out of the box, no changes required.

Did you change any settings? How did you make your game run in such a small window without white bars?

What I did was change fsenabled in Dress Designer.ini to 0.

FunkyFr3sh commented 1 month ago

Ah cool, thanks.

I guess the reason why it may work for me is that i'm testing it on windows 7 VM currently. I'll have to try on Windows 10/11 to see if there is any difference.

Problem is the game will not upscale though - So it may not be worth the effort. Fullscreen without white bars is working, just no upscaling (cursor is glitching with upscaling enabled).

Maybe it's better you try a different wrapper that supports windowed games like DxWnd: https://sourceforge.net/projects/dxwnd/

huckleberrypie commented 1 month ago

Ah cool, thanks.

I guess the reason why it may work for me is that i'm testing it on windows 7 VM currently. I'll have to try on Windows 10/11 to see if there is any difference.

Problem is the game will not upscale though - So it may not be worth the effort. Fullscreen without white bars is working, just no upscaling (cursor is glitching with upscaling enabled).

Maybe it's better you try a different wrapper that supports windowed games like DxWnd: https://sourceforge.net/projects/dxwnd/

Yea that could be the case. May I have a screenshot of the game with upscaling enabled?

FunkyFr3sh commented 1 month ago

Here's a video, first regular fullscreen and then with upscaling enabled

https://github.com/FunkyFr3sh/cnc-ddraw/assets/8355237/2c223c42-393b-4149-b988-2f1cb912f8ec

huckleberrypie commented 1 month ago

Here's a video, first regular fullscreen and then with upscaling enabled 2024-05-22.12-12-31.mp4

I see. Upscaling works in Windows 7 but for some reason this does not extend to the clickable regions as they still assume the old resolution rather than scale to the current resolution. I tried DXWnd but it's giving me a Quicktime error whenever I try to set a higher resolution.

FunkyFr3sh commented 1 month ago

Yes, I think there are multiple windows layered on top of each other and the topmost one handles the input.

Upscaling probably works on any version of windows, It just doesn't use DirectDraw in your case (not sure yet why, maybe needs the winmm proxy from the proxy-dlls.zip?)

huckleberrypie commented 1 month ago

Yes, I think there are multiple windows layered on top of each other and the topmost one handles the input.

Upscaling probably works on any version of windows, It just doesn't use DirectDraw in your case (not sure yet why, maybe needs the winmm proxy from the proxy-dlls.zip?)

Proxy-dlls? Where can I get that one?

And I managed to sort of upscale it in DXWnd, but the main surface retains its old resolution while the bounding boxes for input does somehow scale up to the resolution I chose. Actually no, the bounding box is still the same, albeit at the center of the screen. There seems to be some weird hackery going on with how the game was bound to run at 640x480. :/

FunkyFr3sh commented 1 month ago

Yes, I think there are multiple windows layered on top of each other and the topmost one handles the input. Upscaling probably works on any version of windows, It just doesn't use DirectDraw in your case (not sure yet why, maybe needs the winmm proxy from the proxy-dlls.zip?)

Proxy-dlls? Where can I get that one?

It's on the release page next to cnc-ddraw.zip - You can use the one from hooligans/atrox/chaos gate (they're all the same). This dll just helps to inject cnc-ddraw a bit earlier which then in turn allows it to hook the needed functions so it can be loaded via COM (not sure if htat's actually the case here, but worth a try)

~And I managed to sort of upscale it in DXWnd, but the main surface retains its old resolution while the bounding boxes for input does somehow scale up to the resolution I chose.~ Actually no, the bounding box is still the same, albeit at the center of the screen. There seems to be some weird hackery going on with how the game was bound to run at 640x480. :/

Ah - Yeah, it seems to be a problematic game

huckleberrypie commented 1 month ago

Yes, I think there are multiple windows layered on top of each other and the topmost one handles the input. Upscaling probably works on any version of windows, It just doesn't use DirectDraw in your case (not sure yet why, maybe needs the winmm proxy from the proxy-dlls.zip?)

Proxy-dlls? Where can I get that one?

It's on the release page next to cnc-ddraw.zip - You can use the one from hooligans/atrox/chaos gate (they're all the same). This dll just helps to inject cnc-ddraw a bit earlier which then in turn allows it to hook the needed functions so it can be loaded via COM (not sure if htat's actually the case here, but worth a try)

~And I managed to sort of upscale it in DXWnd, but the main surface retains its old resolution while the bounding boxes for input does somehow scale up to the resolution I chose.~ Actually no, the bounding box is still the same, albeit at the center of the screen. There seems to be some weird hackery going on with how the game was bound to run at 640x480. :/

Ah - Yeah, it seems to be a problematic game

I also tried the winmm.dll hook to no avail, regardless of version included in the proxy-dlls zip. Some further investigation on this game is certainly needed.

elishacloud commented 1 month ago

@huckleberrypie, you could try the game with dxwrapper. It supports windowed mode for most games. Just extract these files into the game folder, overwriting the existing ddraw.dll: dxwrapper.zip

huckleberrypie commented 1 month ago

@huckleberrypie, you could try the game with dxwrapper. It supports windowed mode for most games. Just extract these files into the game folder, overwriting the existing ddraw.dll: dxwrapper.zip

Can it properly handle upscaling?

elishacloud commented 1 month ago

Can it properly handle upscaling?

No, it does not focus on upscaling. It is trying to be compatible with many games.

FunkyFr3sh commented 1 month ago

Just installed the game on windows 10 and it's working there too (it'S using DirectDraw)

FunkyFr3sh commented 1 month ago

@huckleberrypie got it upscaled with working cursor

https://github.com/FunkyFr3sh/cnc-ddraw/assets/8355237/94aa5560-9e41-4c95-91b3-1796922b7864

But it's just a hack on top of a hack on top of a hack!

In case you figure out what's different with your game installation compared to mine (AKA non working DirectDraw...), here's how you can use it

  1. Game must be set to fullscreen
  2. use the following cnc-ddraw with its settings ini: cnc-ddraw-dress-designer.zip
  3. start the game, then switch to borderless with [Alt] + [Enter]

I'm going to stop messing around with this now because this is not something that supposed to be working with cnc-ddraw lol - Maybe this is good enough for playing at least (Will not add the game to the supported games list)

huckleberrypie commented 1 month ago

@huckleberrypie got it upscaled with working cursor 2024-05-23.03-36-14.mp4

But it's just a hack on top of a hack on top of a hack!

In case you figure out what's different with your game installation compared to mine (AKA non working DirectDraw...), here's how you can use it

1. Game must be set to fullscreen

2. use the following cnc-ddraw with its settings ini: [cnc-ddraw-dress-designer.zip](https://github.com/FunkyFr3sh/cnc-ddraw/files/15410533/cnc-ddraw-dress-designer.zip)

3. start the game, then switch to borderless with [Alt] + [Enter]

I'm going to stop messing around with this now because this is not something that supposed to be working with cnc-ddraw lol - Maybe this is good enough for playing at least (Will not add the game to the supported games list)

OK, I tried it on the ISO you linked earlier. idfk why ddraw isn't initialising on my end even when you managed to get it to work on Windows 10.

UPDATE: Setting compatibility mode to Windows XP sort of does the trick, but it's struggling to repaint things especially when you Alt-Enter to upscaled mode.

FunkyFr3sh commented 1 month ago

The needed call to DirectDrawCreate comes from "QuickTime_qts". So maybe something didn't install properly or you got the wrong version? OR maybe a setting is wrong? Not sure really, not an expert with these QuickTime libraries.

You got a screenshot of the issues?

I created a new branch now with my test code: https://github.com/FunkyFr3sh/cnc-ddraw/tree/dress-designer

Here's also a slightly newer build where it goes automatically into borderless mode + it also supports regualr fullscreen cnc-ddraw-dress-designer.zip

Win10 video:

https://github.com/FunkyFr3sh/cnc-ddraw/assets/8355237/9574a7b6-f1fc-4403-8dd4-82d0e0aa1589

huckleberrypie commented 1 month ago

The needed call to DirectDrawCreate comes from "QuickTime_qts". So maybe something didn't install properly or you got the wrong version? OR maybe a setting is wrong? Not sure really, not an expert with these QuickTime libraries.

You got a screenshot of the issues?

I created a new branch now with my test code: https://github.com/FunkyFr3sh/cnc-ddraw/tree/dress-designer

Here's also a slightly newer build where it goes automatically into borderless mode + it also supports regualr fullscreen cnc-ddraw-dress-designer.zip

I am using QT Lite 4.1.0 (QuickTime 7.6.9) as most other QuickTime/QT Lite versions tend to mess up the game's rendering.

FunkyFr3sh commented 1 month ago

I got the one that came with the game image

huckleberrypie commented 1 month ago

I got the one that came with the game image

I see. The problem with that is it goes bonkers with newer versions of Windows hence why I had to use a newer build.

FunkyFr3sh commented 1 month ago

@huckleberrypie, you could try the game with dxwrapper. It supports windowed mode for most games. Just extract these files into the game folder, overwriting the existing ddraw.dll: dxwrapper.zip

@elishacloud Just curious, how do you handle such windowed games? cnc-ddraw doesn't support windowed games (and I don't plan to add full support for them either), but I was just wondering, how do you even get the HWND that you need to pass to d3d9 CreateDevice? HWND is NULL in this case in the IDirectDraw__SetCooperativeLevel call (that's never the case in fullscreen games)

elishacloud commented 1 month ago

@FunkyFr3sh, I never had any problem getting the HWND from SetCooperativeLevel() even in windowed mode games. As far as I know, even in windowed mode they still need to send in a HWND to tell DirectDraw where to draw to. I am not sure what cases the game would send in a nullptr for HWND. I have not seen any cases where a windowed mode game didn't send in an HWND.

The biggest issue with windowed mode games in DirectDraw is that the primary surface (and backbuffers) cover the whole desktop and the game draws to the location on the surface where the window client rect is located. So as the user moves the window around, the location where the game writes to moves. I suppose you could try and trick the game into thinking that the window is always at the upper left hand corner of the desktop, but I opted to actual figure out where the window was and use the surface data from that location of the primary surface.

Edit: this also caused issues if the user resized the window. Basically, I used the Driect3D9 Present() function to tell Driect3D9 what rect to present from and to. See here and here.

FunkyFr3sh commented 1 month ago

@FunkyFr3sh, I never had any problem getting the HWND from SetCooperativeLevel() even in windowed mode games. As far as I know, even in windowed mode they still need to send in a HWND to tell DirectDraw where to draw to. I am not sure what cases the game would send in a nullptr for HWND. I have not seen any cases where a windowed mode game didn't send in an HWND.

Oh, that's weird. I've seen it in a few windowed games already. Here is how it looks like in this game

[1376] 03:31:39.081 -> DirectDrawEnumerateExA(lpCallback=6686D4B0, lpContext=0012F25C, dwFlags=1)
[1376] 03:31:39.081 <- DirectDrawEnumerateExA
[1376] 03:31:39.081 -> DirectDrawCreate(lpGUID=00000000, lplpDD=0012F398, pUnkOuter=00000000)
[1376] 03:31:39.081      proc_affinity=00000001, system_affinity=00000003
[1376] 03:31:39.088      GUID = 6C14DB80 (IID_IDirectDraw), ddraw = 001F66B0
[1376] 03:31:39.089 -> IDirectDraw__AddRef(This=001F66B0)
[1376] 03:31:39.089 <- IDirectDraw__AddRef(This ref=1, global ref=1)
[1376] 03:31:39.089 <- DirectDrawCreate
[1376] 03:31:39.089 -> IDirectDraw__SetCooperativeLevel(This=001F66B0, hwnd=00000000, dwFlags=0x00000008)

The biggest issue with windowed mode games in DirectDraw is that the primary surface (and backbuffers) cover the whole desktop and the game draws to the location on the surface where the window client rect is located. So as the user moves the window around, the location where the game writes to moves. I suppose you could try and trick the game into thinking that the window is always at the upper left hand corner of the desktop, but I opted to actual figure out where the window was and use the surface data from that location of the primary surface.

Yeah, you done it the right way I think. If you put it to the top left your window will not be resizeable, so it's not a good solution.

elishacloud commented 1 month ago

Oh, that's weird. I've seen it in a few windowed games already. Here is how it looks like in this game

Some games may send a SetCooperativeLevel() with a nullptr at first and then later send one with the correct HWND. But as far as I know, even DirectDraw needs to know which HWND to draw to. Games should always eventually send the HWND before actually drawing anything to the screen. Otherwise even DirectDraw will not know what window to draw to. If you never see a SetCooperativeLevel() with a valid HWND then it could be that the game is in an error mode and did not see the right data in some previous function's return value or one of the caps, or something like that.

I don't have the setup to test out a QuickTime game right now. But if you have an example of a non-QuickTime game with this issue let me know.

FunkyFr3sh commented 1 month ago

Oh, that's weird. I've seen it in a few windowed games already. Here is how it looks like in this game

Some games may send a SetCooperativeLevel() with a nullptr at first and then later send one with the correct HWND. But as far as I know, even DirectDraw needs to know which HWND to draw to. Games should always eventually send the HWND before actually drawing anything to the screen. Otherwise even DirectDraw will not know what window to draw to. If you never see a SetCooperativeLevel() with a valid HWND then it could be that the game is in an error mode and did not see the right data in some previous function's return value or one of the caps, or something like that.

I don't have the setup to test out a QuickTime game right now. But if you have an example of a non-QuickTime game with this issue let me know.

I checked with a debugger and it does actually pass NULL on purpsoe there (it's not a uninitialized var or something similiar). TBH, I think DirectDraw didn't need the HWND in windowed mode, because there was no DWM back then

This game here can be used for testing too http://www.winbolo.com/downloads.php - but this is a bit more complicated to test run though

elishacloud commented 1 month ago

I think DirectDraw didn't need the HWND in windowed mode, because there was no DWM back then

This game here can be used for testing too http://www.winbolo.com/downloads.php - but this is a bit more complicated to test run though

I checked out WinBolo, and the game indeed calls SetCooperativeLevel() with a nullptr, even with the native DirectDraw. In this case the game does call IDirectDrawClipper::SetHWnd() and I can use that to get the HWND. But I tested on native DirectDraw and it works even if I block that call from getting to DirectDraw. I guess if DirectDraw is not given a HWND then it just uses the main window, which you can get by calling EnumWindows().

I put a fix in to dxwrapper for this game. Basically, I use the clipper of the primary surface if it is available and if not I use the main window. I had some code already to detect the main window but had to update it a bit since this game's main window is not visible at the beginning. So it should work similar to DirectDraw, unless there is a problem with the code I use to get the main window. But I am not sure how many games use a nullptr for SetCooperativeLevel() and don't have a clipper for the primary surface.

Here is a build that should work with WinBolo: dxwrapper.zip

FunkyFr3sh commented 1 month ago

I think DirectDraw didn't need the HWND in windowed mode, because there was no DWM back then This game here can be used for testing too http://www.winbolo.com/downloads.php - but this is a bit more complicated to test run though

I checked out WinBolo, and the game indeed calls SetCooperativeLevel() with a nullptr, even with the native DirectDraw. In this case the game does call IDirectDrawClipper::SetHWnd() and I can use that to get the HWND. But I tested on native DirectDraw and it works even if I block that call from getting to DirectDraw. I guess if DirectDraw is not given a HWND then it just uses the main window, which you can get by calling EnumWindows().

I put a fix in to dxwrapper for this game. Basically, I use the clipper of the primary surface if it is available and if not I use the main window. I had some code already to detect the main window but had to update it a bit since this game's main window is not visible at the beginning. So it should work similar to DirectDraw, unless there is a problem with the code I use to get the main window. But I am not sure how many games use a nullptr for SetCooperativeLevel() and don't have a clipper for the primary surface.

Here is a build that should work with WinBolo: dxwrapper.zip

Cool, I'll try it out!

BTW this game here does pass NULL, does not use a clipper and does create multiple windows! I'm using EnumThreadWindows, which ended up selecting the wrong window (that's why the cursor didn't work in my earlier tests!) - I do not have a solution for this game, I just manually picked window 2 lol

elishacloud commented 1 month ago

Here is some simple code to get the main window. I tested this and it works for WinBolo.

// Function prototype
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);

struct EnumData
{
    DWORD processId;
    HWND hWnd;
};

// Function to check if a window is a potential main application window
bool IsMainApplicationWindow(HWND hwnd)
{
    // Check if the window has a caption (to avoid tool windows and other non-main windows)
    LONG style = GetWindowLong(hwnd, GWL_STYLE);
    if ((style & WS_CAPTION) == 0)
    {
        return false;
    }

    // Check if the window is not a child window
    if (GetWindowLong(hwnd, GWL_HWNDPARENT) != 0)
    {
        return false;
    }

    // Check if the window has a significant title
    TCHAR title[256];
    GetWindowText(hwnd, title, sizeof(title) / sizeof(TCHAR));
    if (_tcslen(title) == 0)
    {
        return false;
    }

    return true;
}

// Function to enumerate windows and find the one matching the process ID
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
    EnumData& data = *(EnumData*)lParam;
    DWORD windowProcessId;
    GetWindowThreadProcessId(hwnd, &windowProcessId);
    if (data.processId == windowProcessId && IsMainApplicationWindow(hwnd))
    {
        data.hWnd = hwnd;
        // Check if the window is visible
        if (IsWindowVisible(hwnd))
        {
            return FALSE; // Stop enumeration once we find a match
        }
    }
    return TRUE; // Continue enumeration
}

// Function to get the main window handle of the current process
HWND GetMainWindowHandle()
{
    EnumData data = { 0 };
    data.processId = GetCurrentProcessId();
    data.hWnd = nullptr;
    EnumWindows(EnumWindowsProc, (LPARAM)&data);
    return data.hWnd;
}
FunkyFr3sh commented 1 month ago

Here is some simple code to get the main window. I tested this and it works for WinBolo.

// Function prototype
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);

struct EnumData
{
  DWORD processId;
  HWND hWnd;
};

// Function to check if a window is a potential main application window
bool IsMainApplicationWindow(HWND hwnd)
{
  // Check if the window has a caption (to avoid tool windows and other non-main windows)
  LONG style = GetWindowLong(hwnd, GWL_STYLE);
  if ((style & WS_CAPTION) == 0)
  {
      return false;
  }

  // Check if the window is not a child window
  if (GetWindowLong(hwnd, GWL_HWNDPARENT) != 0)
  {
      return false;
  }

  // Check if the window has a significant title
  TCHAR title[256];
  GetWindowText(hwnd, title, sizeof(title) / sizeof(TCHAR));
  if (_tcslen(title) == 0)
  {
      return false;
  }

  return true;
}

// Function to enumerate windows and find the one matching the process ID
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
  EnumData& data = *(EnumData*)lParam;
  DWORD windowProcessId;
  GetWindowThreadProcessId(hwnd, &windowProcessId);
  if (data.processId == windowProcessId && IsMainApplicationWindow(hwnd))
  {
      data.hWnd = hwnd;
      // Check if the window is visible
      if (IsWindowVisible(hwnd))
      {
          return FALSE; // Stop enumeration once we find a match
      }
  }
  return TRUE; // Continue enumeration
}

// Function to get the main window handle of the current process
HWND GetMainWindowHandle()
{
  EnumData data = { 0 };
  data.processId = GetCurrentProcessId();
  data.hWnd = nullptr;
  EnumWindows(EnumWindowsProc, (LPARAM)&data);
  return data.hWnd;
}

Oh nice, yeah that's a cool idea! I'll try it out

FunkyFr3sh commented 1 month ago

Did a few more minor changes and merged it into the master branch now.

cnc-ddraw-dress-designer.zip

Didn't try to make windowed mode working with cnc-ddraw since the game already got its own windowed mode anyways.

Closing this now

FunkyFr3sh commented 1 month ago

Little update here, I just had to debug another game using QuickTime which led me to solving the last remaining problems I had in my tests in this game. Seems like it's fully supported now.

Working windowed mode:

https://github.com/FunkyFr3sh/cnc-ddraw/assets/8355237/2a30d49a-7cec-4dfd-aa77-6bce150b34b1

cnc-ddraw.zip