Jump to content

[C++] Metin2 Client Window Centering Bug (Win10/11) – Real Cause and Permanent Solution


Recommended Posts

  • Active+ Member

Problem Description

There has been a small but long-standing issue in the Metin2 client:
the game window was opening a few pixels shifted to the right, instead of being perfectly centered.

On my system, this offset was around 7 pixels.
Not a huge problem, but the “not exactly centered” feeling was clearly noticeable.

 

All fixes shared so far were hard-coded workarounds rather than a proper fix. Today, we'll fix it for good.

 

Before: the window is slightly shifted to the right

.png


After: the window is perfectly centered, regardless of taskbar position

 

What Is the Problem?

This is not a simple math mistake.

On Windows 10/11:

GetWindowRect() does not always reflect the exact visual bounds of the window
Due to DWM (Desktop Window Manager), the left and right window frames are not symmetrical
Because of this, the classic calculation:

(screenWidth - windowWidth) / 2

does not always produce a true visual center.

 

What Changed?

Along with fixing the centering issue, a few small but important cleanups were made:

  • Fixed the centering logic that caused the client window to open ~7 pixels off to the right
  • The window is now centered based on the actual visual frame seen by the user (Win10/11 DWM-aware)
  • Removed an unnecessary GetWindowRect wrapper that conflicted with the WinAPI function name
  • Completely removed the legacy “another window” reposition logic based on FindWindow
    • This logic was not deterministic on modern Windows
    • It also interfered with correct centering
  • No fixed offsets, magic numbers, or “just move it a bit” hacks were used
  • The window now always opens inside the work area, without overlapping the taskbar or appearing offset

 

How to

Client\Source\EterLib\MSWindow.cpp:

// Find and erase

void CMSWindow::GetWindowRect(RECT* prc)
{
    ::GetWindowRect(m_hWnd, prc);
}

// Add new helper functions

static bool GetVisualWindowRect(HWND hwnd, RECT& outRect)
{
    if (!::GetWindowRect(hwnd, &outRect))
        return false;

    using DwmGetWindowAttributeFn = HRESULT(WINAPI*)(HWND, DWORD, PVOID, DWORD);
    constexpr DWORD ExtendedFrameBounds = 9;

    static HMODULE s_dwm = ::LoadLibraryA("dwmapi.dll");
    if (!s_dwm)
        return true;

    static auto s_getAttr =
        reinterpret_cast<DwmGetWindowAttributeFn>(
            ::GetProcAddress(s_dwm, "DwmGetWindowAttribute"));

    if (!s_getAttr)
        return true;

    RECT visual{};
    if (SUCCEEDED(s_getAttr(hwnd, ExtendedFrameBounds, &visual, sizeof(visual))))
    {
        outRect = visual;
    }

    return true;
}

static POINT GetCenteredPosition(const RECT& visualRect, const RECT& workArea, const RECT& windowRect)
{
    const int width = visualRect.right - visualRect.left;
    const int height = visualRect.bottom - visualRect.top;

    const int dx = visualRect.left - windowRect.left;
    const int dy = visualRect.top - windowRect.top;

    POINT pt{};
    pt.x = workArea.left + ((workArea.right - workArea.left) - width) / 2 - dx;
    pt.y = workArea.top + ((workArea.bottom - workArea.top) - height) / 2 - dy;
    return pt;
}

// change function void CMSWindow::SetCenterPosition()

void CMSWindow::SetCenterPosition()
{
    RECT window{};
    RECT visual{};
    RECT workArea{};

    if (!::GetWindowRect(m_hWnd, &window))
        return;

    visual = window;
    GetVisualWindowRect(m_hWnd, visual);

    ::SystemParametersInfo(SPI_GETWORKAREA, 0, &workArea, 0);

    const POINT pos = GetCenteredPosition(visual, workArea, window);
    SetPosition(pos.x, pos.y);
}

Client\Source\EterLib\MSWindow.h:

// Find and erase

        void GetWindowRect(RECT* prc);

Client\Source\UserInterface\PythonApplication.cpp:

// Find and erase

    bool bAnotherWindow = false;

    if (FindWindow(NULL, c_szName))
    {
        bAnotherWindow = true;
    }

// Find @@ bool CPythonApplication::Create(PyObject * poSelf, const char* c_szName, int wid

        AdjustSize(m_pySystem.GetWidth(), m_pySystem.GetHeight());

// Add this underneath

        CMSWindow::SetCenterPosition();

// Find and erase @@ bool CPythonApplication::Create(PyObject * poSelf, const char* c_szName, int wid


            if (bAnotherWindow)
            {
                RECT rc;

                GetClientRect(&rc);

                int windowWidth = rc.right - rc.left;
                int windowHeight = (rc.bottom - rc.top);

                CMSApplication::SetPosition(GetScreenWidth() - windowWidth, GetScreenHeight() - 60 - windowHeight);
            }

 

Client\Source\UserInterface\UserInterface.cpp:

// Add to the top of the file

#include <shellscalingapi.h>
#pragma comment(lib, "Shcore.lib")

// Find
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{

// Add this
    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

 

Edited by Kaptan Yosun
Fix improved
  • Good 1
  • Love 5

Thanks but did you tried change DPI or multi monitor when secondary is not locked or multi gpu???

i solved this problem some long time ago like this way:

UserInterface.cpp

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); // this is what we need add before ini and create window

PythonApplication.cpp

		AdjustSize(m_pySystem.GetWidth(), m_pySystem.GetHeight());

		if (Windowed)
		{
			RECT rcWorkArea{};
			SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, 0);

			RECT rc{};
			GetWindowRect(&rc);
			int windowWidth  = rc.right - rc.left;
			int windowHeight = rc.bottom - rc.top;

			int x = rcWorkArea.left + (rcWorkArea.right - rcWorkArea.left - windowWidth) / 2;
			int y = rcWorkArea.top  + (rcWorkArea.bottom - rcWorkArea.top - windowHeight) / 2;

			SetWindowPos(GetWindowHandle(), NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
		}
		else
		{
			m_isWindowed = false;
			SetPosition(0, 0);
		}

 

  • Active+ Member
18 minutes ago, Filachilla said:

Thanks but did you tried change DPI or multi monitor when secondary is not locked or multi gpu???

i solved this problem some long time ago like this way:

UserInterface.cpp

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); // this is what we need add before ini and create window

PythonApplication.cpp

		AdjustSize(m_pySystem.GetWidth(), m_pySystem.GetHeight());

		if (Windowed)
		{
			RECT rcWorkArea{};
			SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, 0);

			RECT rc{};
			GetWindowRect(&rc);
			int windowWidth  = rc.right - rc.left;
			int windowHeight = rc.bottom - rc.top;

			int x = rcWorkArea.left + (rcWorkArea.right - rcWorkArea.left - windowWidth) / 2;
			int y = rcWorkArea.top  + (rcWorkArea.bottom - rcWorkArea.top - windowHeight) / 2;

			SetWindowPos(GetWindowHandle(), NULL, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
		}
		else
		{
			m_isWindowed = false;
			SetPosition(0, 0);
		}

 

I haven't as I only have one monitor, thank you for your contribution

  • 3 weeks later...
  • Active+ Member

I have updated the topic to make this fix Per-Monitor DPI Aware.
You only need to do the UserInterface.cpp part if you have applied this fix earlier.

Don't use any images from : imgur, turkmmop, freakgamers, inforge, hizliresim... Or your content will be deleted without notice...
Use : https://metin2.download/media/add/

Please use https://metin2.download/ when uploading files smaller than 100MB, otherwise the approval will take longer due to manual upload.

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.