mirror of
https://github.com/narzoul/DDrawCompat
synced 2024-12-30 08:55:36 +01:00
Added support for OBS window capture and taskbar thumbnails
This commit is contained in:
parent
d734c41443
commit
5ce45f8063
@ -408,35 +408,50 @@ namespace
|
||||
void updatePresentationWindow()
|
||||
{
|
||||
LOG_FUNC("RealPrimarySurface::updatePresentationWindow");
|
||||
const bool isFullscreen = isProcessActive() && (g_isFullscreen ||
|
||||
g_frontBuffer && DDraw::PrimarySurface::getPrimary() && SUCCEEDED(g_frontBuffer->IsLost(g_frontBuffer)) &&
|
||||
Gdi::Window::hasFullscreenWindow());
|
||||
|
||||
const bool isActive = isProcessActive();
|
||||
HWND fullscreenWindow = nullptr;
|
||||
if (g_isFullscreen && IsWindowVisible(g_deviceWindow) && !IsIconic(g_deviceWindow))
|
||||
{
|
||||
fullscreenWindow = g_deviceWindow;
|
||||
}
|
||||
else if (g_frontBuffer && DDraw::PrimarySurface::getPrimary() && SUCCEEDED(g_frontBuffer->IsLost(g_frontBuffer)))
|
||||
{
|
||||
fullscreenWindow = Gdi::Window::getFullscreenWindow();
|
||||
}
|
||||
|
||||
fullscreenWindow = fullscreenWindow ? Gdi::Window::getPresentationWindow(fullscreenWindow) : nullptr;
|
||||
|
||||
if (g_windowedBackBuffer)
|
||||
{
|
||||
auto resource = D3dDdi::Device::findResource(
|
||||
DDraw::DirectDrawSurface::getDriverResourceHandle(*g_windowedBackBuffer));
|
||||
resource->setFullscreenMode(isFullscreen);
|
||||
resource->setFullscreenMode(isActive && fullscreenWindow);
|
||||
}
|
||||
|
||||
Gdi::GuiThread::execute([&]()
|
||||
{
|
||||
if (isFullscreen)
|
||||
if (!isActive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
g_presentationWindow = fullscreenWindow;
|
||||
|
||||
if (g_presentationWindow)
|
||||
{
|
||||
Gdi::GuiThread::execute([&]()
|
||||
{
|
||||
if (!g_presentationWindow)
|
||||
{
|
||||
g_presentationWindow = Gdi::PresentationWindow::create(nullptr);
|
||||
}
|
||||
CALL_ORIG_FUNC(SetWindowPos)(g_presentationWindow, HWND_TOPMOST, g_monitorRect.left, g_monitorRect.top,
|
||||
g_monitorRect.right - g_monitorRect.left, g_monitorRect.bottom - g_monitorRect.top,
|
||||
SWP_NOACTIVATE | SWP_NOSENDCHANGING | SWP_NOREDRAW | SWP_NOOWNERZORDER | SWP_SHOWWINDOW);
|
||||
}
|
||||
else if (g_presentationWindow)
|
||||
{
|
||||
DestroyWindow(g_presentationWindow);
|
||||
g_presentationWindow = nullptr;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static HWND prevPresentationWindow = nullptr;
|
||||
if (prevPresentationWindow && prevPresentationWindow != g_presentationWindow)
|
||||
{
|
||||
Gdi::Window::updatePresentationWindowPos(prevPresentationWindow, GetParent(prevPresentationWindow));
|
||||
}
|
||||
prevPresentationWindow = g_presentationWindow;
|
||||
}
|
||||
|
||||
unsigned WINAPI updateThreadProc(LPVOID /*lpParameter*/)
|
||||
@ -605,7 +620,7 @@ namespace DDraw
|
||||
Compat::ScopedCriticalSection lock(g_presentCs);
|
||||
isPresentationWindowUpdateNeeded =
|
||||
0 != g_qpcUpdatePresentationWindow && Time::queryPerformanceCounter() - g_qpcUpdatePresentationWindow >= 0 ||
|
||||
g_presentationWindow && !isProcessActive();
|
||||
!isProcessActive();
|
||||
}
|
||||
|
||||
if (isPresentationWindowUpdateNeeded)
|
||||
@ -799,14 +814,14 @@ namespace DDraw
|
||||
|
||||
void RealPrimarySurface::setPresentationWindowTopmost()
|
||||
{
|
||||
Gdi::GuiThread::execute([&]()
|
||||
{
|
||||
if (g_presentationWindow)
|
||||
if (g_presentationWindow && IsWindowVisible(g_presentationWindow))
|
||||
{
|
||||
Gdi::GuiThread::execute([&]()
|
||||
{
|
||||
CALL_ORIG_FUNC(SetWindowPos)(g_presentationWindow, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING | SWP_NOREDRAW | SWP_NOOWNERZORDER);
|
||||
}
|
||||
});
|
||||
SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOSENDCHANGING | SWP_NOREDRAW | SWP_NOOWNERZORDER);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void RealPrimarySurface::setUpdateReady()
|
||||
|
@ -105,17 +105,17 @@ namespace Gdi
|
||||
{
|
||||
namespace GuiThread
|
||||
{
|
||||
HWND createWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle,
|
||||
HWND createWindow(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle,
|
||||
int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam)
|
||||
{
|
||||
// Workaround for ForceSimpleWindow shim
|
||||
static auto createWindowExA = reinterpret_cast<decltype(&CreateWindowExA)>(
|
||||
Compat::getProcAddress(GetModuleHandle("user32"), "CreateWindowExA"));
|
||||
static auto createWindowExW = reinterpret_cast<decltype(&CreateWindowExW)>(
|
||||
Compat::getProcAddress(GetModuleHandle("user32"), "CreateWindowExW"));
|
||||
|
||||
HWND hwnd = nullptr;
|
||||
execute([&]()
|
||||
{
|
||||
hwnd = createWindowExA(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight,
|
||||
hwnd = createWindowExW(dwExStyle, lpClassName, lpWindowName, dwStyle, X, Y, nWidth, nHeight,
|
||||
hWndParent, hMenu, hInstance, lpParam);
|
||||
});
|
||||
return hwnd;
|
||||
|
@ -16,7 +16,7 @@ namespace Gdi
|
||||
{
|
||||
namespace GuiThread
|
||||
{
|
||||
HWND createWindow(DWORD dwExStyle, LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle,
|
||||
HWND createWindow(DWORD dwExStyle, LPCWSTR lpClassName, LPCWSTR lpWindowName, DWORD dwStyle,
|
||||
int X, int Y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HINSTANCE hInstance, LPVOID lpParam);
|
||||
void deleteTaskbarTab(HWND hwnd);
|
||||
void destroyWindow(HWND hwnd);
|
||||
|
@ -26,8 +26,8 @@ namespace Gdi
|
||||
GuiThread::execute([&]()
|
||||
{
|
||||
presentationWindow = GuiThread::createWindow(
|
||||
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOPARENTNOTIFY | WS_EX_TOOLWINDOW,
|
||||
reinterpret_cast<const char*>(g_classAtom),
|
||||
WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_NOPARENTNOTIFY | (owner ? 0 : WS_EX_TOOLWINDOW),
|
||||
reinterpret_cast<const wchar_t*>(g_classAtom),
|
||||
nullptr,
|
||||
WS_DISABLED | WS_POPUP,
|
||||
0, 0, 1, 1,
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
#include <Windows.h>
|
||||
#include <Windowsx.h>
|
||||
#include <dwmapi.h>
|
||||
|
||||
#include <Common/Hook.h>
|
||||
#include <Common/Log.h>
|
||||
@ -49,6 +50,10 @@ namespace
|
||||
WNDPROC wndProcW;
|
||||
};
|
||||
|
||||
decltype(&DwmSetIconicThumbnail) g_dwmSetIconicThumbnail = nullptr;
|
||||
decltype(&SetThreadDpiAwarenessContext) g_setThreadDpiAwarenessContext = nullptr;
|
||||
|
||||
wchar_t g_dummyWindowText;
|
||||
std::map<HMENU, UINT> g_menuMaxHeight;
|
||||
std::set<Gdi::WindowPosChangeNotifyFunc> g_windowPosChangeNotifyFuncs;
|
||||
|
||||
@ -63,7 +68,9 @@ namespace
|
||||
thread_local bool g_isFrameStarted = false;
|
||||
thread_local bool g_waiting = false;
|
||||
|
||||
void dwmSendIconicThumbnail(HWND hwnd, LONG width, LONG height);
|
||||
WindowProc getWindowProc(HWND hwnd);
|
||||
std::wstring getWindowText(HWND hwnd, WNDPROC wndProc);
|
||||
bool isUser32ScrollBar(HWND hwnd);
|
||||
void onDestroyWindow(HWND hwnd);
|
||||
void onGetMinMaxInfo(MINMAXINFO& mmi);
|
||||
@ -106,6 +113,10 @@ namespace
|
||||
Gdi::checkDesktopComposition();
|
||||
break;
|
||||
|
||||
case WM_DWMSENDICONICTHUMBNAIL:
|
||||
dwmSendIconicThumbnail(hwnd, HIWORD(lParam), LOWORD(lParam));
|
||||
return 0;
|
||||
|
||||
case WM_GETMINMAXINFO:
|
||||
onGetMinMaxInfo(*reinterpret_cast<MINMAXINFO*>(lParam));
|
||||
break;
|
||||
@ -123,6 +134,19 @@ namespace
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_NULL:
|
||||
if (WM_GETTEXT == wParam && reinterpret_cast<LPARAM>(&g_dummyWindowText) == lParam)
|
||||
{
|
||||
auto presentationWindow = Gdi::Window::getPresentationWindow(hwnd);
|
||||
if (presentationWindow)
|
||||
{
|
||||
std::wstring windowText(L"[DDrawCompat] " + getWindowText(hwnd, wndProc));
|
||||
SendMessageW(presentationWindow, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(windowText.c_str()));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case WM_SYNCPAINT:
|
||||
if (Gdi::Window::isTopLevelWindow(hwnd))
|
||||
{
|
||||
@ -266,6 +290,59 @@ namespace
|
||||
return LOG_RESULT(CALL_ORIG_FUNC(SetWindowLongA)(hWnd, nIndex, dwNewLong));
|
||||
}
|
||||
|
||||
void dwmSendIconicThumbnail(HWND hwnd, LONG width, LONG height)
|
||||
{
|
||||
auto presentationWindow = Gdi::Window::getPresentationWindow(hwnd);
|
||||
if (!presentationWindow || !g_dwmSetIconicThumbnail)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
BITMAPINFO bmi = {};
|
||||
bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
|
||||
bmi.bmiHeader.biWidth = width;
|
||||
bmi.bmiHeader.biHeight = -height;
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 32;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
void* bits = nullptr;
|
||||
HBITMAP bmp = CreateDIBSection(nullptr, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
|
||||
if (!bmp)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
HDC srcDc = GetWindowDC(presentationWindow);
|
||||
HDC dstDc = CreateCompatibleDC(nullptr);
|
||||
auto prevBmp = SelectBitmap(dstDc, bmp);
|
||||
|
||||
RECT srcRect = {};
|
||||
GetWindowRect(presentationWindow, &srcRect);
|
||||
|
||||
SetStretchBltMode(dstDc, HALFTONE);
|
||||
CALL_ORIG_FUNC(StretchBlt)(dstDc, 0, 0, width, height,
|
||||
srcDc, 0, 0, srcRect.right - srcRect.left, srcRect.bottom - srcRect.top, SRCCOPY);
|
||||
|
||||
SelectBitmap(dstDc, prevBmp);
|
||||
DeleteDC(dstDc);
|
||||
ReleaseDC(presentationWindow, srcDc);
|
||||
|
||||
DPI_AWARENESS_CONTEXT prevContext = nullptr;
|
||||
if (g_setThreadDpiAwarenessContext)
|
||||
{
|
||||
prevContext = g_setThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE);
|
||||
}
|
||||
|
||||
g_dwmSetIconicThumbnail(hwnd, bmp, 0);
|
||||
|
||||
if (prevContext)
|
||||
{
|
||||
g_setThreadDpiAwarenessContext(prevContext);
|
||||
}
|
||||
|
||||
DeleteObject(bmp);
|
||||
}
|
||||
|
||||
BOOL WINAPI getCursorPos(LPPOINT lpPoint)
|
||||
{
|
||||
if (lpPoint && g_cursorPos)
|
||||
@ -326,6 +403,25 @@ namespace
|
||||
return g_windowProc[hwnd];
|
||||
}
|
||||
|
||||
std::wstring getWindowText(HWND hwnd, WNDPROC wndProc)
|
||||
{
|
||||
const UINT MAX_LEN = 256;
|
||||
if (IsWindowUnicode(hwnd))
|
||||
{
|
||||
wchar_t windowText[MAX_LEN] = {};
|
||||
CallWindowProcW(wndProc, hwnd, WM_GETTEXT, MAX_LEN, reinterpret_cast<LPARAM>(windowText));
|
||||
windowText[MAX_LEN - 1] = 0;
|
||||
return windowText;
|
||||
}
|
||||
else
|
||||
{
|
||||
char windowText[MAX_LEN] = {};
|
||||
CallWindowProcA(wndProc, hwnd, WM_GETTEXT, MAX_LEN, reinterpret_cast<LPARAM>(windowText));
|
||||
windowText[MAX_LEN - 1] = 0;
|
||||
return std::wstring(windowText, windowText + strlen(windowText));
|
||||
}
|
||||
}
|
||||
|
||||
template <auto func, typename... Params>
|
||||
int WINAPI messageBox(Params... params)
|
||||
{
|
||||
@ -462,6 +558,13 @@ namespace
|
||||
{
|
||||
DDraw::RealPrimarySurface::setPresentationWindowTopmost();
|
||||
Gdi::Window::updateAll();
|
||||
|
||||
if (g_dwmSetIconicThumbnail)
|
||||
{
|
||||
const BOOL isIconic = IsIconic(hwnd);
|
||||
DwmSetWindowAttribute(hwnd, DWMWA_FORCE_ICONIC_REPRESENTATION, &isIconic, sizeof(isIconic));
|
||||
DwmSetWindowAttribute(hwnd, DWMWA_HAS_ICONIC_BITMAP, &isIconic, sizeof(isIconic));
|
||||
}
|
||||
}
|
||||
|
||||
if (wp.flags & SWP_FRAMECHANGED)
|
||||
@ -670,6 +773,12 @@ namespace
|
||||
break;
|
||||
|
||||
case EVENT_OBJECT_NAMECHANGE:
|
||||
if (OBJID_WINDOW == idObject && Gdi::Window::isTopLevelWindow(hwnd) && !Gdi::GuiThread::isGuiThreadWindow(hwnd))
|
||||
{
|
||||
Gdi::WinProc::updatePresentationWindowText(hwnd);
|
||||
break;
|
||||
}
|
||||
|
||||
case EVENT_OBJECT_SHOW:
|
||||
case EVENT_OBJECT_HIDE:
|
||||
if (OBJID_CURSOR == idObject && Gdi::Cursor::isEmulated())
|
||||
@ -712,6 +821,7 @@ namespace
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -766,6 +876,11 @@ namespace Gdi
|
||||
HOOK_FUNCTION(user32, SetWindowLongW, setWindowLongW);
|
||||
HOOK_FUNCTION(user32, SetWindowPos, setWindowPos);
|
||||
|
||||
g_dwmSetIconicThumbnail = reinterpret_cast<decltype(&DwmSetIconicThumbnail)>(
|
||||
GetProcAddress(GetModuleHandle("dwmapi"), "DwmSetIconicThumbnail"));
|
||||
g_setThreadDpiAwarenessContext = reinterpret_cast<decltype(&SetThreadDpiAwarenessContext)>(
|
||||
GetProcAddress(GetModuleHandle("user32"), "SetThreadDpiAwarenessContext"));
|
||||
|
||||
Compat::hookIatFunction(Dll::g_origDDrawModule, "SetWindowLongA", ddrawSetWindowLongA);
|
||||
|
||||
SetWinEventHook(EVENT_OBJECT_CREATE, EVENT_OBJECT_CREATE,
|
||||
@ -864,6 +979,11 @@ namespace Gdi
|
||||
}
|
||||
}
|
||||
|
||||
void updatePresentationWindowText(HWND owner)
|
||||
{
|
||||
SendNotifyMessageW(owner, WM_NULL, WM_GETTEXT, reinterpret_cast<LPARAM>(&g_dummyWindowText));
|
||||
}
|
||||
|
||||
void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc)
|
||||
{
|
||||
g_windowPosChangeNotifyFuncs.insert(notifyFunc);
|
||||
|
@ -10,6 +10,7 @@ namespace Gdi
|
||||
void installHooks();
|
||||
void onCreateWindow(HWND hwnd);
|
||||
void startFrame();
|
||||
void updatePresentationWindowText(HWND owner);
|
||||
void watchWindowPosChanges(WindowPosChangeNotifyFunc notifyFunc);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include <Gdi/PresentationWindow.h>
|
||||
#include <Gdi/VirtualScreen.h>
|
||||
#include <Gdi/Window.h>
|
||||
#include <Gdi/WinProc.h>
|
||||
#include <Input/Input.h>
|
||||
#include <Overlay/ConfigWindow.h>
|
||||
#include <Overlay/StatsWindow.h>
|
||||
@ -269,6 +270,7 @@ namespace
|
||||
if (!isLayered)
|
||||
{
|
||||
it->second.presentationWindow = Gdi::PresentationWindow::create(hwnd);
|
||||
Gdi::WinProc::updatePresentationWindowText(hwnd);
|
||||
setPresentationWindowRgn = true;
|
||||
}
|
||||
else if (it->second.presentationWindow)
|
||||
@ -350,7 +352,10 @@ namespace
|
||||
Gdi::GuiThread::setWindowRgn(it->second.presentationWindow, it->second.windowRegion);
|
||||
}
|
||||
|
||||
Gdi::Window::updatePresentationWindowPos(it->second.presentationWindow, hwnd);
|
||||
if (it->second.presentationWindow != DDraw::RealPrimarySurface::getPresentationWindow())
|
||||
{
|
||||
Gdi::Window::updatePresentationWindowPos(it->second.presentationWindow, hwnd);
|
||||
}
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
@ -420,7 +425,7 @@ namespace Gdi
|
||||
return layeredWindows;
|
||||
}
|
||||
|
||||
bool hasFullscreenWindow()
|
||||
HWND getFullscreenWindow()
|
||||
{
|
||||
D3dDdi::ScopedCriticalSection lock;
|
||||
RECT mr = DDraw::PrimarySurface::getMonitorRect();
|
||||
@ -434,10 +439,10 @@ namespace Gdi
|
||||
IsWindowVisible(window.first) &&
|
||||
!IsIconic(window.first))
|
||||
{
|
||||
return true;
|
||||
return window.first;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool isTopLevelWindow(HWND hwnd)
|
||||
@ -626,11 +631,14 @@ namespace Gdi
|
||||
|
||||
void updatePresentationWindowPos(HWND presentationWindow, HWND owner)
|
||||
{
|
||||
const bool isOwnerVisible = IsWindowVisible(owner) && !IsIconic(owner);
|
||||
if (IsIconic(owner))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WINDOWPOS wp = {};
|
||||
wp.flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW | SWP_NOSENDCHANGING;
|
||||
if (isOwnerVisible)
|
||||
if (IsWindowVisible(owner))
|
||||
{
|
||||
wp.hwndInsertAfter = GetWindow(owner, GW_HWNDPREV);
|
||||
if (!wp.hwndInsertAfter)
|
||||
|
@ -20,7 +20,7 @@ namespace Gdi
|
||||
HWND getPresentationWindow(HWND hwnd);
|
||||
std::vector<LayeredWindow> getVisibleLayeredWindows();
|
||||
std::vector<LayeredWindow> getVisibleOverlayWindows();
|
||||
bool hasFullscreenWindow();
|
||||
HWND getFullscreenWindow();
|
||||
bool isTopLevelWindow(HWND hwnd);
|
||||
void onStyleChanged(HWND hwnd, WPARAM wParam);
|
||||
void onSyncPaint(HWND hwnd);
|
||||
|
@ -110,6 +110,29 @@ std::ostream& operator<<(std::ostream& os, const BITMAP& bm)
|
||||
<< bm.bmBits;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const BITMAPINFO& bmi)
|
||||
{
|
||||
return Compat::LogStruct(os)
|
||||
<< bmi.bmiHeader
|
||||
<< Compat::array(bmi.bmiColors, 1);
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const BITMAPINFOHEADER& bmih)
|
||||
{
|
||||
return Compat::LogStruct(os)
|
||||
<< bmih.biSize
|
||||
<< bmih.biWidth
|
||||
<< bmih.biHeight
|
||||
<< bmih.biPlanes
|
||||
<< bmih.biBitCount
|
||||
<< bmih.biCompression
|
||||
<< bmih.biSizeImage
|
||||
<< bmih.biXPelsPerMeter
|
||||
<< bmih.biYPelsPerMeter
|
||||
<< bmih.biClrUsed
|
||||
<< bmih.biClrImportant;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const COMPAREITEMSTRUCT& cis)
|
||||
{
|
||||
return Compat::LogStruct(os)
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include <Windows.h>
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const BITMAP& bm);
|
||||
std::ostream& operator<<(std::ostream& os, const BITMAPINFO& bmi);
|
||||
std::ostream& operator<<(std::ostream& os, const BITMAPINFOHEADER& bmih);
|
||||
std::ostream& operator<<(std::ostream& os, const COMPAREITEMSTRUCT& cis);
|
||||
std::ostream& operator<<(std::ostream& os, const COPYDATASTRUCT& cds);
|
||||
std::ostream& operator<<(std::ostream& os, const CREATESTRUCTA& cs);
|
||||
|
Loading…
x
Reference in New Issue
Block a user