WidescreenFixesPack icon indicating copy to clipboard operation
WidescreenFixesPack copied to clipboard

[NFSMW] implement windowed mode

Open xan1242 opened this issue 1 year ago • 7 comments

I'm not sure how much you like function hooks but here you go. If you like this, I can backport it to other games too.

xan1242 avatar Aug 05 '22 22:08 xan1242

Function hooks are fine by me. I will check this one when I'm home (should be 1.5 weeks from now), and merge as well. Other games would be great!

ThirteenAG avatar Aug 06 '22 17:08 ThirteenAG

Alright! Just let me know if this style works for you or not before I go and do the others (no rush though, enjoy your vacation if you're on it!)

I've added 2 separate functions (for hooking) and 2 global vars for configuration to dllmain.cpp because I'm trying to pass them into the hooked functions.

Whereas with Carbon, the hooks to SetWindowLong are inlined within Init() which isn't something I normally do, but if you prefer that I can try and inline it that way.

My implementation results in the window being summoned normally while the border option is active (with the Windows' fade effect and everything) with the icon being visible in the corner, while also optionally enabling resizing (with scaling, not native resize, although native resizing is also possible to do).

Current Carbon implementation (with the border option active) just pops up the window without the icon and doesn't allow resizing.

Both implementations respond to the command/alt key to open the system menu (the restore, move, minimize, etc. menu) while the border is active. This might be something to consider disabling optionally, because if you use NOS on the Alt key you can freeze the game by opening the menu.

I think my implementation might also behave better in Wine/Proton, but I hadn't tested it out yet.

xan1242 avatar Aug 06 '22 19:08 xan1242

I had implemented a proper, resizable and bug-free windowed mode implementation for MW'05 way back in 2017 too. But since I didn't get any feedback, I didn't bother pushing a PR here.

Also @xan1242 if you don't want syscall hooks, you can just lazy load and wait for window handle. If you want, you can use my code (though, it is 5 years old). Here it is: https://github.com/berkayylmao/RockportEd/blob/master/RockportEd/Extensions/WndProc/WindowedModeImprovements.hpp

...and here's a bad video showing it working:

https://user-images.githubusercontent.com/46968988/183444204-06de84a1-2abc-4991-abc9-73a0276c8f1d.mp4

berkayylmao avatar Aug 08 '22 14:08 berkayylmao

Oh yeah I actually remember seeing this, I honestly forgot about it.

So as far as I can understand, that's just also updating the resolution+fov in real time.

This could work too, but, as for any values that live within the code they'd have to be put into a variable (if there even are any). I'm not entirely sure how your memory hook worked, but calling VirtualProtect often isn't very good. (I did it a few times and caused BSODs, ugh)

Nevertheless this could be a part as well. I do wonder how the older NFS games would handle this.

As for the LazyHook, I've never actually used it so I don't know about it, but I'll look into it. This hook worked for me for 3 games at least, so I just put it in for practical reasons.

xan1242 avatar Aug 08 '22 19:08 xan1242

The good thing about ThirteenAG's widescreen fix implementation is that all game values are pointed to the fix. No memory page is updated unless a WM_SIZE event occurs.

This is the relevant code for live resizing:

WndProc:

case WM_SIZE:
{
  // update pointed values with new width-height
  updateValues((float)LOWORD(lParam), (float)HIWORD(lParam));  
  // set resizing flag (needed to enable HUD in D3D9.AfterReset event, more on that below)
  isResizing = true;
  // hide HUD by force. this is needed because game does not update their size/position while still being drawn
  InternalVariables::setVariable(InternalVariables::drawHUD, false);
  // toggle game's window resize event variable, normally used for resolution changes inside the display menu
  InternalVariables::setVariable(InternalVariables::resetGameWindow, true);
  // do NOT pass the event to the game
  return TRUE;
}

D3D9.AfterReset:

// restore hud
if (isResizing) {
  InternalVariables::setVariable(InternalVariables::drawHUD, true);
  isResizing = false;
}

inline void updateValues(const float& newWidth, const float& newHeight) is just a copy-paste of the widescreen fix's init function.

--

Also, please take into account the window borders when setting up for bordered window. All widescreen fix implementations for NFS games do not calculate the border difference, resulting in a blurry/jagged display.

RECT pre_c;
::GetClientRect(hwnd, &pre_c);
// change to bordered window
DWORD style = ::GetWindowLongPtr(hwnd, GWL_STYLE) | WS_OVERLAPPEDWINDOW;
::SetWindowLongPtr(hwnd, GWL_STYLE, wStyle);
// refresh window
::SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_DRAWFRAME);
RECT post_w;
RECT post_c;
::GetWindowRect(hwnd, &post_w);
::GetClientRect(hwnd, &post_c);
// correct window <-> client size diff
std::int32_t width  = (post_w.right  - post_w.left) + (pre_c.right  - post_c.right);
std::int32_t height = (post_w.bottom - post_w.top)  + (pre_c.bottom - post_c.bottom);
// resize+activate window
::SetWindowPos(hwnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_DRAWFRAME);

@ThirteenAG @xan1242 you guys can use this code freely, no license.

berkayylmao avatar Aug 08 '22 20:08 berkayylmao

Awesome, I'll try to come up with something soon.

Just to be clear, your HUD drawing variable is referring to just the HUD or Frontend entirely?

xan1242 avatar Aug 08 '22 20:08 xan1242

For MW'05 v1.3 RELOADED it's .text:0057CAA7, inside FEngHud::DetermineHudFeatures(IPlayer*). It's one of the if cases, by forcing it to 0 all FEng is hidden.

berkayylmao avatar Aug 08 '22 20:08 berkayylmao

Alright, I've taken your research into use @berkayylmao and I adapted it to this code.

The only thing left to add is the shadow scaling updating. As for the border compensation, I'm not sure how that would work. From what I can see in the debugger, the game already seems to do this, but I could be wrong.

Currently there is 1 major issue with resizing - FE objects need to refresh any time the window is resized. berkay's HUD updating method sorta works here. I guess it's the timing of my control of the variable berkay found being wonky. I'm considering some global method to refresh FE in general (if there is such, I believe there should be).

As for the minor issues - I didn't implement shadow scale updating yet. Not a big problem, I'll add it later on.

Also with every reset (and therefore resize), if you have ReShade, it will recompile shaders. This doesn't seem like something fixable easily.

@ThirteenAG this was a pretty massive change I made so nitpick away.

  • I resorted to global variables to enable access for other functions. If you want me to use namespaces or something let me know.
  • Variables that need updating are now left unprotected - if they were in a read only memory page, they're not anymore.
  • Screen.fHudPosX - for this I had to create new inline hooks to make the game read the variable at 0x894B40 in real time as opposed to hardcoded values. This is to make updating easier.
  • Same for mirror position fix. It was rewritten into an inline hook.
  • I added 2 caves in eDisplayFrame for adding a quicker window reset method - it normally recompiles shaders with every reset, but I added a skip for it in those caves so the window resize works faster
  • I added a WndProc hook to allow WM_SIZE reading. I implemented it in the same way as my other projects (reads the offset of the WndProc at location and hooks into it)
  • Screen resolution function which reads the g_RacingResolution variable was also replaced with a hook instead to simplify realtime updating

xan1242 avatar Aug 15 '22 04:08 xan1242

Just to add one more thing - I've tested this under Wine 7.15 and it works flawlessly. Only thing needed was to add the ASI loader (dinput8) as a native library. Proton might need some commandline flags to do the same thing.

image

xan1242 avatar Aug 15 '22 23:08 xan1242

@xan1242 looks good, but I'm wondering if separate option for resizing is really needed? Wouldn't it be simpler to have borderless/bordered with resizing?

ThirteenAG avatar Aug 23 '22 09:08 ThirteenAG

I guess so. I only did it because of implementation testing actually but it could be reduced in this case as you said.

Another reason would be because resizing causes bugs with the FEng stuff until it's redrawn so I guess it would be more elegant to keep it off in certain cases.

I'll edit it when I get the chance and start working on porting it to other games.

xan1242 avatar Aug 23 '22 09:08 xan1242

I see, I guess I'll merge this one for now.

ThirteenAG avatar Aug 23 '22 09:08 ThirteenAG

@xan1242 btw any idea how to override resolution options in menu with our list? I was thinking it would be nice to at least display actual res there, even if without an ability to switch it.

ThirteenAG avatar Aug 24 '22 07:08 ThirteenAG

Yes, there indeed is.

A simple hack would be to override the printed string in the option for the resolution for the display. This could be done over FEPrintf easily.

An actual hack to switch available resolutions is also possible. I could try porting the ResDetect code from ProStreet/Undercover.

xan1242 avatar Aug 24 '22 07:08 xan1242