1

I just bumped into an obscure issue, while implementing a prototype for a native Win32 UI using Direct2D/DirectWrite for high performance text rendering. Things went fine and looked promising: Resizing the application window was devoid of any lag, and the entire text rendering was as solid as one could expect.

That is, until I decided to rename the binary to "ride.exe". At that point, the OS opted to force my application into "glitch mode". Resizing suddenly introduced a very noticeable lag, combined with gaps when sizing up, that would only eventually fill again. Plus, text output got all jumpy, with the perceived font size varying. Which is odd considering that I only ever create the font resource at application startup.

All those artifacts instantly go away, if I rename the binary to anything else, and instantly return, when I choose to call it "ride.exe" again. No recompilation or linking involved, it's literally just a rename operation.

I'm lost at this point, though it looks a lot like the OS is using some heuristic that involves the image name to decide, how to execute the code. So, what's going on here?


This is a complete repro to illustrate the issue:

#include <Windows.h>
#include <d2d1.h>
#include <d2d1_1helper.h>
#include <dwrite.h>

#pragma comment(lib, "D2d1.lib")
#pragma comment(lib, "Dwrite.lib")


LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void create_graphics_resources(HWND const hwnd) noexcept;
void discard_graphics_resources() noexcept;
void resize_render_target(HWND const hwnd) noexcept;
void create_dw_resources() noexcept;
void discard_dw_resources() noexcept;


ID2D1Factory1* pd2d_factory { nullptr };
ID2D1HwndRenderTarget* prender_target { nullptr };
ID2D1SolidColorBrush* ptext_color_brush { nullptr };
IDWriteFactory* pdw_factory { nullptr };
IDWriteTextFormat* pdw_text_format { nullptr };


int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE, LPWSTR, int nCmdShow)
{
    CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);

    WNDCLASSEXW wcex {};
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.hInstance = hInstance;
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszClassName = L"d2d_mcve";
    RegisterClassExW(&wcex);

    auto const hWnd { CreateWindowExW(0, L"d2d_mcve", L"D2D MCVE", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT,
                                      0, nullptr, nullptr, hInstance, nullptr) };
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    MSG msg {};

    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    CoUninitialize();
    return (int)msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE: {
        D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, D2D1_FACTORY_OPTIONS {}, &pd2d_factory);
        DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(pdw_factory),
                            reinterpret_cast<IUnknown**>(&pdw_factory));
    }
    break;

    case WM_DESTROY:
        discard_dw_resources();
        discard_graphics_resources();
        pdw_factory->Release();
        pd2d_factory->Release();
        PostQuitMessage(0);
        break;

    case WM_SIZE:
        resize_render_target(hWnd);
        InvalidateRect(hWnd, nullptr, FALSE);
        break;

    case WM_PAINT: {
        PAINTSTRUCT ps {};
        BeginPaint(hWnd, &ps);

        create_graphics_resources(hWnd);
        prender_target->BeginDraw();

        prender_target->Clear(D2D1::ColorF(D2D1::ColorF::Black));
        create_dw_resources();
        auto const target_size { prender_target->GetSize() };
        prender_target->SetTransform(D2D1::Matrix3x2F::Identity());

        auto const& placeholder_text { L"Lorem ipsum dolor sit amet, consectetur adipiscing elit." };
        prender_target->DrawTextW(placeholder_text, ARRAYSIZE(placeholder_text) - 1, pdw_text_format,
                                 D2D1::RectF(0.0f, 0.0f, target_size.width, target_size.height),
                                 ptext_color_brush);

        auto const hr { prender_target->EndDraw() };
        if (hr == D2DERR_RECREATE_TARGET)
        {
            discard_graphics_resources();
            InvalidateRect(hWnd, nullptr, FALSE);
        }

        EndPaint(hWnd, &ps);
    }
    break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}


void create_graphics_resources(HWND const hwnd) noexcept
{
    if (prender_target == nullptr)
    {
        RECT rc {};
        GetClientRect(hwnd, &rc);

        auto const size { D2D1::SizeU(static_cast<UINT32>(rc.right), static_cast<UINT32>(rc.bottom)) };
        pd2d_factory->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
                                             D2D1::HwndRenderTargetProperties(hwnd, size), &prender_target);

        prender_target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), &ptext_color_brush);
    }
}

void discard_graphics_resources() noexcept
{
    if (ptext_color_brush != nullptr)
    {
        ptext_color_brush->Release();
        ptext_color_brush = nullptr;
    }
    if (prender_target != nullptr)
    {
        prender_target->Release();
        prender_target = nullptr;
    }
}

void resize_render_target(HWND const hwnd) noexcept
{
    if (prender_target != nullptr)
    {
        RECT rc {};
        GetClientRect(hwnd, &rc);

        auto const size { D2D1::SizeU(static_cast<UINT32>(rc.right), static_cast<UINT32>(rc.bottom)) };
        prender_target->Resize(size);
    }
}

void create_dw_resources() noexcept
{
    if (pdw_text_format == nullptr)
    {
        pdw_factory->CreateTextFormat(L"Courier New", nullptr, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL,
                                      DWRITE_FONT_STRETCH_NORMAL, 48.0f, L"en-US", &pdw_text_format);
    }
}

void discard_dw_resources() noexcept
{
    if (pdw_text_format != nullptr)
    {
        pdw_text_format->Release();
        pdw_text_format = nullptr;
    }
}
  1. Save the source code as "main.cpp".
  2. Compile the code using the following command line:
    cl.exe /O2 /EHsc /D "NDEBUG" /D "UNICODE" /MD main.cpp user32.lib ole32.lib
  3. Create a directory named "choosing_a_trident_for_a_buttplug_is_not_prudent" anywhere in the filesystem and copy "main.exe" into that directory.
  4. Launch "main.exe" and observe the behavior of the application when resizing the main window.
  5. Rename the copy of "main.exe" under "choosing_a_trident_for_a_buttplug_is_not_prudent" to "ride.exe", launch "ride.exe" and observe the behavior when resizing the main window.

The last step exhibits all sorts of visual glitches on my system (Windows 10 19041.330). The application instantly returns to normal operation when either renaming the executable image to anything but "ride.exe", or renaming every parent directory to no longer contain the phrase "ride". Casing doesn't make a difference.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • Possible issue with antivirus and/or defender? "ride.exe" *does* have a certain connotation of malice. Have you tried other dodgy/rude names? – Adrian Mole Jun 27 '20 at 15:20
  • @adr That's possible, I guess, though disabling Windows' threat and virus protection doesn't change things for me. My gut feeling is telling me that the *"fix"* for [this game](https://store.steampowered.com/app/345660/RIDE/) breaks my application. I just don't even know where to look now. Is it the OS? Or the drivers? How do you even diagnose what's happening? – IInspectable Jun 27 '20 at 15:55
  • 1
    run (if possible together) ride and original name exe. compare dll set loaded - are the same ? say for compatibility *aclayers.dll* loaded by os to process. threads (are ride.exe containing additional threads compare original (need filter loader threads)), memory usage, handles. search for any different. and of course try do this in another windows. are there also will be different in work – RbMm Jun 27 '20 at 16:05
  • Thanks for the feedback. I figured it out. Give me an hour or two to calm down, find some family-friendly terms to describe the issue, and provide a full repro. For the time being, let me quote just quote John McEnroe: *"YOU CANNOT BE SERIOUS?!"* – IInspectable Jun 27 '20 at 16:17
  • 1
    FWIW, I don't reproduce any issue at step 5. My Windows is 1909, build 10.0.18363.900 – Simon Mourier Jun 27 '20 at 19:56
  • @rbm I haven't. But I provided a [mcve] so that others can. Could you run your RE machinery against my observations, given the information provided in the rant disguised as an answer? – IInspectable Jun 27 '20 at 20:33
  • @sim Could you check your registry to see, whether the key spelled out in my answer exists on your system, please? – IInspectable Jun 27 '20 at 20:34
  • @IInspectable - yes i build your code. and it run ok as *ride.exe* and under another name too. i not view any difference. and almost sure that this is only on your concrete system – RbMm Jun 27 '20 at 20:39
  • @rbm Did you make sure to place the binary in a directory that contains the phrase "RIDE"? This is a crucial part of the experiment. Also, did you look into your registry to see, whether the key I documented in my answer exists? – IInspectable Jun 27 '20 at 20:41
  • @IInspectable - ))) yes. inside *ride* folder, *ride.exe* really have problem in execute (visible side effect.) ok, i look in more details why this – RbMm Jun 27 '20 at 20:44
  • @rbm Great, thank you! – IInspectable Jun 27 '20 at 20:46
  • are you note that `"C:\Windows\System32\GameBarPresenceWriter.exe" -ServerName:Windows.Gaming.GameBar.Internal.PresenceWriterServer` execute just after your ride ? – RbMm Jun 27 '20 at 20:50
  • even more - we can take *any* exe. rename it as *\ride\ride.exe* and after it exec - will be just executed *GameBarPresenceWriter.exe* – RbMm Jun 27 '20 at 20:55
  • I have your "ride.exe" in a "ride" folder, I don't have any registry key like you listed in my whole registry (my GameConfigStore is almost empty). I do have a KnownGameList.bin in appdata that contains the "ride.exe" word. But I don't have seen issue so far. – Simon Mourier Jun 28 '20 at 09:08
  • @sim Thanks for the feedback. When you do update to Windows 10 2004, could you check those registry keys again? – IInspectable Jun 28 '20 at 09:15
  • Ok, it's not yet proposed. I'll try to remember :-) – Simon Mourier Jun 28 '20 at 10:06
  • @SimonMourier - i have also no such registry keys (*8e89aac5-6105-47bd-bfb3-6ed01bda07b0*) and test on already very old win 10. but exist visible effect on *ride\ride.exe* and `GameBarPresenceWriter.exe` executed (via `RoActivateInstance`) – RbMm Jun 28 '20 at 11:49

1 Answers1

1

I haven't done a thorough analysis on what exactly is happening, yet it appears that the system is using a - dare I say - somewhat (?) crude strategy to fix broken applications. As usual, the Hall of Shame is to be found in the registry:

On my system there's a key named 8e89aac5-6105-47bd-bfb3-6ed01bda07b0 under HKU\<sid>\System\GameConfigStore\Children. Leaving the obvious WTF aside for a minute, that GameConfigStore is seemingly a System-vital key for my user account, that key has values named ExeParentDirectory and Flags, with associated data RIDE (REG_SZ) and 0x11 (REG_DWORD), respectively.

The way I read this is: If a user identified by <sid> attempts to launch a binary called ride.exe (hardcoded, somewhere), that resides under a directory whose name contains RIDE, first apply the flags 0x11 to something, for sports, just to get their attention, before allowing the primary thread to execute. This is to ensure that broken app X continues to appear not broken, when <user> decides to change the environment that would otherwise exhibit the brokenness.

Even if this were to break other, properly written applications.

Now this may sound bad, but you have to give Microsoft credit where credit is due: Out of all possible combinations of characters that form a valid registry key name to communicate with us, devs, they have opted to keep this down to earth, matter of fact, professional, and use GUIDs.

I mean...

They could have just as well chosen to following:

  • Quality doesn't matter, ROI does
  • What do you mean, you did RTFM?!
  • Dude, what contractual guarantees? LOL, n00b
  • This is just a friendly FU. But no, we don't care about devs that actually TRY not to suck

But they didn't! So... thanks? I guess.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 1
    sure that [Renaming a Win32 executable to main.exe launch the Xbox DVR popup](https://lucasg.github.io/2017/01/07/Renaming-a-Win32-executable-to-main-exe-launch-the-Xbox-DVR-popup/) and https://stackoverflow.com/questions/36712801/windows-10-naming-programs-main-exe-cause-them-to-show-pop-up is direct related to your case – RbMm Jun 27 '20 at 21:15
  • @rbm I also experience the issue, when *"main.exe"* never materializes. You could simply rename the source file posted in my question to *"Source.cpp"*, execute the adjusted compiler command line, and perform the experiment using *"Source.exe"*. – IInspectable Jun 27 '20 at 21:27
  • 1
    i can confirm that *GameBarPresenceWriter.exe* is launched from explorer inside `BroadcastDVRComponent::OnApplicationViewChangedTask` when *ride.exe* show self icon on task bar. and inside *c:\windows\bcastdvr\KnownGameList.bin* and *C:\Users\???\AppData\Local\Microsoft\GameDVR\KnownGameList.bin* exist *ride.exe* name. all this related to Xbox DVR – RbMm Jun 27 '20 at 21:33
  • 1
    https://lifehacker.ru/how-to-increase-fps-in-windows-10/ this is why – RbMm Jun 27 '20 at 22:40