From fdc759ee9b797887207170e9454eaadb43fcfd16 Mon Sep 17 00:00:00 2001 From: AdvDebug <90452585+AdvDebug@users.noreply.github.com> Date: Wed, 26 Aug 2026 22:00:42 +0300 Subject: [PATCH 1/2] Implement standard controls and dialogs in win32k Brovan runs the real user32, so supporting the standard controls means supplying what win32k owes it rather than writing the controls. Button, Edit, Static, ListBox, ComboBox and ScrollBar now create, answer their own messages and send real WM_COMMAND notifications to their parent. Most of that was client-side state user32 reads without a syscall, the system class atoms, the per-DPI character dimensions dialog units convert with, the system colours and their brushes, and the per-class message tables a control checks before running its worker. While those tables read zero every message went straight to DefWindowProc, which is why nothing ever initialized. Window procedures go through user32's kernel callback table now, so NtUserCreateWindowEx sends WM_NCCREATE and WM_CREATE and carries the creation on to WM_SIZE and WM_MOVE, and NtUserMessageCall delivers a real send to the window procedure instead of answering it as DefWindowProc. Painting picks up the DC viewport origin and shifts every primitive by the window's origin inside its top level ancestor, since they all land on one host surface. Also bounds cbWndExtra at class registration, recycles user handle indexes and releases the window object when a window is destroyed, and keeps the internal visible bit out of what GWL_EXSTYLE stores. --- Brovan/Core/Emulation/Guests/WindowsGuest.cs | 1 + .../Windows/BinaryEmulator.WindowsBridge.cs | 15 +- .../Emulation/OS/Windows/RPC/Ports/ApiPort.cs | 14 +- .../OS/Windows/Win32k/NtGdiEllipse.cs | 4 +- .../OS/Windows/Win32k/NtGdiExtTextOutW.cs | 2 +- .../Windows/Win32k/NtGdiGetCharWidthInfo.cs | 30 + .../Windows/Win32k/NtGdiGetTextCharsetInfo.cs | 39 + .../OS/Windows/Win32k/NtGdiGetTextExtent.cs | 2 +- .../OS/Windows/Win32k/NtGdiGetTextMetricsW.cs | 62 +- .../OS/Windows/Win32k/NtGdiLineTo.cs | 4 +- .../OS/Windows/Win32k/NtGdiPatBlt.cs | 4 +- .../OS/Windows/Win32k/NtGdiPolyPatBlt.cs | 6 +- .../OS/Windows/Win32k/NtGdiPolyPolyDraw.cs | 10 +- .../OS/Windows/Win32k/NtGdiRectangle.cs | 4 +- .../OS/Windows/Win32k/NtGdiRoundRect.cs | 4 +- .../OS/Windows/Win32k/NtGdiSelectBitmap.cs | 25 + .../OS/Windows/Win32k/NtGdiSetBoundsRect.cs | 41 + .../OS/Windows/Win32k/NtGdiSetBrushOrg.cs | 36 + .../OS/Windows/Win32k/NtUserBitBltSysBmp.cs | 20 + .../Windows/Win32k/NtUserClearWindowState.cs | 25 + .../OS/Windows/Win32k/NtUserCreateCaret.cs | 20 + .../OS/Windows/Win32k/NtUserCreateWindowEx.cs | 18 +- .../OS/Windows/Win32k/NtUserDefSetText.cs | 29 + .../OS/Windows/Win32k/NtUserDestroyCaret.cs | 15 + .../Windows/Win32k/NtUserDispatchMessage.cs | 44 +- .../OS/Windows/Win32k/NtUserGetClassName.cs | 61 ++ .../Windows/Win32k/NtUserGetControlBrush.cs | 34 + .../Windows/Win32k/NtUserGetOemBitmapSize.cs | 30 + .../Windows/Win32k/NtUserGetThreadDesktop.cs | 15 + .../OS/Windows/Win32k/NtUserHideCaret.cs | 32 + .../Win32k/NtUserInheritWindowMonitor.cs | 21 + .../Win32k/NtUserInitializeClientPfnArrays.cs | 21 + .../OS/Windows/Win32k/NtUserMessageCall.cs | 11 + .../Win32k/NtUserRegisterClassExWOW.cs | 11 +- .../OS/Windows/Win32k/NtUserSetCaretPos.cs | 27 + .../Windows/Win32k/NtUserSetDialogPointer.cs | 31 + .../OS/Windows/Win32k/NtUserSetParent.cs | 41 + .../OS/Windows/Win32k/NtUserSetScrollInfo.cs | 81 ++ .../OS/Windows/Win32k/NtUserSetWindowLong.cs | 14 +- .../Windows/Win32k/NtUserSetWindowLongPtr.cs | 14 +- .../OS/Windows/Win32k/NtUserSetWindowState.cs | 25 + .../OS/Windows/Win32k/NtUserShowCaret.cs | 34 + .../OS/Windows/Win32k/NtUserShowWindow.cs | 5 +- .../OS/Windows/Win32k/NtUserUpdateWindow.cs | 38 + .../OS/Windows/Win32k/NtUserWaitMessage.cs | 50 ++ .../OS/Windows/Win32k/Win32kHelper.cs | 687 ++++++++++++-- .../OS/Windows/WinHelperConstants.cs | 17 + .../Emulation/OS/Windows/WinSyscallsHelper.cs | 843 ++++++++++++++++-- .../Core/Emulation/OS/Windows/WinThreading.cs | 26 +- 49 files changed, 2372 insertions(+), 271 deletions(-) create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetCharWidthInfo.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextCharsetInfo.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSelectBitmap.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBrushOrg.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBitBltSysBmp.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserClearWindowState.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateCaret.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDefSetText.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDestroyCaret.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetClassName.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetControlBrush.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetOemBitmapSize.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetThreadDesktop.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserHideCaret.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserInheritWindowMonitor.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserInitializeClientPfnArrays.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetCaretPos.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetDialogPointer.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetParent.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetScrollInfo.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowState.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowCaret.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateWindow.cs create mode 100644 Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWaitMessage.cs diff --git a/Brovan/Core/Emulation/Guests/WindowsGuest.cs b/Brovan/Core/Emulation/Guests/WindowsGuest.cs index 9b285cf..7bb18b1 100644 --- a/Brovan/Core/Emulation/Guests/WindowsGuest.cs +++ b/Brovan/Core/Emulation/Guests/WindowsGuest.cs @@ -404,6 +404,7 @@ public void OnThreadWaitSatisfied(BinaryEmulator Instance, EmulatedThread Thread State.AlertByThreadIdAddress = 0; State.MsgWaitActive = false; State.MsgWaitMask = 0; + State.WaitMessageActive = false; State.GetMessageWaitActive = false; Thread.WaitTimedOut = false; Thread.WaitSatisfiedIndex = -1; diff --git a/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs b/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs index 5623bcb..971bd31 100644 --- a/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs +++ b/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs @@ -1,4 +1,4 @@ -using System.Buffers; +using System.Buffers; using System.Buffers.Binary; using System.Runtime.InteropServices; using Brovan.Core.Emulation.OS.Windows; @@ -732,6 +732,17 @@ internal bool TrySatisfyThreadWait(EmulatedThread Thread) return false; } + if (State != null && State.WaitMessageActive) + { + if (!Win32kHelper.HasQueuedInputEvent(this, Win32kHelper.QS_ALLINPUT)) + return false; + + State.WaitMessageActive = false; + Thread.WaitSatisfiedIndex = 1; + State.WaitStatus = NTSTATUS.STATUS_SUCCESS; + return true; + } + if (State != null && State.GetMessageWaitActive) { if (Win32kHelper.TryGetMessage(this, State.GetMessageHwndFilter, State.GetMessageMinMessage, State.GetMessageMaxMessage, true, out Win32kMessage Message)) @@ -894,7 +905,7 @@ internal bool HasActiveGetMessageWait() continue; WindowsThreadState State = WinEmulatedThread.TryGetState(Thread); - if (State != null && (State.GetMessageWaitActive || State.MsgWaitActive)) + if (State != null && (State.GetMessageWaitActive || State.MsgWaitActive || State.WaitMessageActive)) return true; } diff --git a/Brovan/Core/Emulation/OS/Windows/RPC/Ports/ApiPort.cs b/Brovan/Core/Emulation/OS/Windows/RPC/Ports/ApiPort.cs index 0cf045d..1767e29 100644 --- a/Brovan/Core/Emulation/OS/Windows/RPC/Ports/ApiPort.cs +++ b/Brovan/Core/Emulation/OS/Windows/RPC/Ports/ApiPort.cs @@ -311,11 +311,13 @@ public static bool TryBuildUserConnect(BinaryEmulator Instance, ulong Connection if (!Instance.WinHelper.EnsureUserSharedInfo(out ulong psi, out ulong aheList, out uint entrySize)) return false; + Instance.WinHelper.PublishDpiServerInfo(); + ulong DisplayInfo = Instance.WinHelper.EnsureUserDesktopInfo(); if (DisplayInfo == 0) return false; - if (!Instance.WinHelper.EnsureUserMessageBitmasks(out ulong Bitmask1, out ulong Bitmask2)) + if (!Instance.WinHelper.EnsureUserMessageBitmask(out ulong Bitmask)) return false; Data.Clear(); @@ -332,10 +334,12 @@ public static bool TryBuildUserConnect(BinaryEmulator Instance, ulong Connection WriteU32(Data, UserConnectHeaderSize + 0x10, entrySize); WriteU64(Data, UserConnectHeaderSize + 0x18, DisplayInfo); - WriteU32(Data, UserConnectHeaderSize + 0x218, 0x3FFu); - WriteU64(Data, UserConnectHeaderSize + 0x220, Bitmask1); - WriteU32(Data, UserConnectHeaderSize + 0x228, 0x3FFu); - WriteU64(Data, UserConnectHeaderSize + 0x230, Bitmask2); + for (int Index = 0; Index < WinSysHelper.UserSharedInfoMessageTableCount; Index++) + { + int Offset = UserConnectHeaderSize + WinSysHelper.UserSharedInfoMessageTableOffset(Index); + WriteU32(Data, Offset, WinSysHelper.UserMessageBitmaskLastMessage); + WriteU64(Data, Offset + 8, Bitmask); + } return true; } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEllipse.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEllipse.cs index 4f908f4..8ef6c18 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEllipse.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEllipse.cs @@ -1,4 +1,4 @@ -using Brovan.Core.Emulation.OS.SharedHelpers; +using Brovan.Core.Emulation.OS.SharedHelpers; using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k @@ -24,7 +24,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Win32kPenBrush Pen = Win32kHelper.ResolvePenBrush(Instance, Instance.WinHelper.ReadDcSelectedPen(Hdc), true); Win32kPenBrush Brush = Win32kHelper.ResolvePenBrush(Instance, Instance.WinHelper.ReadDcSelectedBrush(Hdc), false); - Instance.WinHelper.EnqueueGdiShape(Hwnd, GdiPrimitiveKind.Ellipse, Left, Top, Right, Bottom, Pen.ColorRef, Pen.PenWidth, Brush.ColorRef); + Instance.WinHelper.EnqueueGdiShape(Hwnd, Hdc, GdiPrimitiveKind.Ellipse, Left, Top, Right, Bottom, Pen.ColorRef, Pen.PenWidth, Brush.ColorRef); Instance.SetRawSyscallReturn(1); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiExtTextOutW.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiExtTextOutW.cs index 6ce940e..9762f03 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiExtTextOutW.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiExtTextOutW.cs @@ -50,7 +50,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) RectBottom = (int)Window.Height; } - Instance.WinHelper.EnqueueTextRender(Hwnd, Text, X, Y, RectLeft, RectTop, RectRight, RectBottom, Options); + Instance.WinHelper.EnqueueTextRender(Hwnd, Hdc, Text, X, Y, RectLeft, RectTop, RectRight, RectBottom, Options); Instance.SetRawSyscallReturn(1); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetCharWidthInfo.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetCharWidthInfo.cs new file mode 100644 index 0000000..7ca7074 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetCharWidthInfo.cs @@ -0,0 +1,30 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + // Brovan draws every glyph inside its advance width, so both side bearings are genuinely zero. + internal class NtGdiGetCharWidthInfo : IWinSyscall + { + private const int CharWidthInfoSize = 12; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + ulong InfoPtr = Instance.WinHelper.GetArg(1); + + if (InfoPtr == 0 || !Win32kHelper.IsKnownDc(Instance, Hdc)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + if (!Instance.WinHelper.WriteZeroMemory(InfoPtr, CharWidthInfoSize)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextCharsetInfo.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextCharsetInfo.cs new file mode 100644 index 0000000..e487488 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextCharsetInfo.cs @@ -0,0 +1,39 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiGetTextCharsetInfo : IWinSyscall + { + private const uint AnsiCharset = 0; + private const int FontSignatureSize = 24; + private const uint BasicLatinRangeBit = 1; + private const uint Latin1CodePageBit = 1; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + ulong SignaturePtr = Instance.WinHelper.GetArg(1); + + if (!Win32kHelper.IsKnownDc(Instance, Hdc)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn(uint.MaxValue); + return NTSTATUS.STATUS_SUCCESS; + } + + // FONTSIGNATURE is four unicode subset masks then two code page masks. + if (SignaturePtr != 0) + { + if (!Instance.WinHelper.WriteZeroMemory(SignaturePtr, FontSignatureSize)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + Instance._emulator.WriteMemory(SignaturePtr, BasicLatinRangeBit, 4); + Instance._emulator.WriteMemory(SignaturePtr + 16, Latin1CodePageBit, 4); + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(AnsiCharset); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextExtent.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextExtent.cs index 88298ff..76d5e67 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextExtent.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextExtent.cs @@ -1,4 +1,4 @@ -using static Brovan.Core.Helpers.BinaryHelpers; +using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k { diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextMetricsW.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextMetricsW.cs index c50969b..89ef61b 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextMetricsW.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetTextMetricsW.cs @@ -5,8 +5,6 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k { internal class NtGdiGetTextMetricsW : IWinSyscall { - private const int TextMetricWSize = 60; - public NTSTATUS Handle(BinaryEmulator Instance) { @@ -14,57 +12,19 @@ public NTSTATUS Handle(BinaryEmulator Instance) ulong BufferPtr = Instance.WinHelper.GetArg(1); uint BufferSize = (uint)Instance.WinHelper.GetArg(2); - if (BufferPtr == 0 || BufferSize < TextMetricWSize) + if (BufferPtr == 0 || BufferSize < Win32kHelper.TextMetricWSize) { Instance.SetRawSyscallReturn(0); return NTSTATUS.STATUS_SUCCESS; } - TextMetricsData Metrics; - if (!Instance.WinHelper.GetTextMetrics(out Metrics)) - { - Metrics = new TextMetricsData - { - Height = 16, - Ascent = 12, - Descent = 4, - AveCharWidth = 8, - MaxCharWidth = 16, - Weight = 400, - DigitizedAspectX = 96, - DigitizedAspectY = 96, - FirstChar = 0x20, - LastChar = 0xFF, - DefaultChar = 0x20, - BreakChar = 0x20, - PitchAndFamily = 0x01, - }; - } + if (!Instance.WinHelper.GetTextMetrics(out TextMetricsData Metrics)) + Metrics = Win32kHelper.DefaultTextMetrics; - Span Buffer = Instance.WinHelper.Shared.GetSpan(TextMetricWSize); - Buffer.Clear(); - WriteI32(Buffer, 0x00, Metrics.Height); - WriteI32(Buffer, 0x04, Metrics.Ascent); - WriteI32(Buffer, 0x08, Metrics.Descent); - WriteI32(Buffer, 0x0C, Metrics.InternalLeading); - WriteI32(Buffer, 0x10, Metrics.ExternalLeading); - WriteI32(Buffer, 0x14, Metrics.AveCharWidth); - WriteI32(Buffer, 0x18, Metrics.MaxCharWidth); - WriteI32(Buffer, 0x1C, Metrics.Weight); - WriteI32(Buffer, 0x20, Metrics.Overhang); - WriteI32(Buffer, 0x24, Metrics.DigitizedAspectX); - WriteI32(Buffer, 0x28, Metrics.DigitizedAspectY); - WriteU16(Buffer, 0x2C, Metrics.FirstChar); - WriteU16(Buffer, 0x2E, Metrics.LastChar); - WriteU16(Buffer, 0x30, Metrics.DefaultChar); - WriteU16(Buffer, 0x32, Metrics.BreakChar); - Buffer[0x34] = Metrics.Italic; - Buffer[0x35] = Metrics.Underlined; - Buffer[0x36] = Metrics.StruckOut; - Buffer[0x37] = Metrics.PitchAndFamily; - Buffer[0x38] = Metrics.CharSet; + Span Buffer = Instance.WinHelper.Shared.GetSpan(Win32kHelper.TextMetricWSize); + Win32kHelper.WriteTextMetricsW(Buffer, Metrics); - if (!Instance.WriteMemory(BufferPtr, Buffer.Slice(0, TextMetricWSize))) + if (!Instance.WriteMemory(BufferPtr, Buffer.Slice(0, Win32kHelper.TextMetricWSize))) { Instance.SetRawSyscallReturn(0); return NTSTATUS.STATUS_SUCCESS; @@ -73,15 +33,5 @@ public NTSTATUS Handle(BinaryEmulator Instance) Instance.SetRawSyscallReturn(1); return NTSTATUS.STATUS_SUCCESS; } - - private static void WriteI32(Span Buffer, int Offset, int Value) - { - System.Buffers.Binary.BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(Offset, 4), Value); - } - - private static void WriteU16(Span Buffer, int Offset, ushort Value) - { - System.Buffers.Binary.BinaryPrimitives.WriteUInt16LittleEndian(Buffer.Slice(Offset, 2), Value); - } } } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiLineTo.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiLineTo.cs index 91c7e3b..8a04043 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiLineTo.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiLineTo.cs @@ -1,4 +1,4 @@ -using static Brovan.Core.Helpers.BinaryHelpers; +using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k { @@ -22,7 +22,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) ulong PenHandle = Instance.WinHelper.ReadDcSelectedPen(Hdc); Win32kPenBrush Pen = Win32kHelper.ResolvePenBrush(Instance, PenHandle, true); - Instance.WinHelper.EnqueueGdiLine(Hwnd, StartX, StartY, X, Y, Pen.ColorRef, Pen.PenWidth); + Instance.WinHelper.EnqueueGdiLine(Hwnd, Hdc, StartX, StartY, X, Y, Pen.ColorRef, Pen.PenWidth); Instance.WinHelper.WriteDcCurrentPosition(Hdc, X, Y); Instance.SetRawSyscallReturn(1); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPatBlt.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPatBlt.cs index 35700d3..b1686d7 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPatBlt.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPatBlt.cs @@ -1,4 +1,4 @@ -using static Brovan.Core.Helpers.BinaryHelpers; +using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k { @@ -23,7 +23,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) ulong BrushHandle = Instance.WinHelper.ReadDcSelectedBrush(Hdc); Win32kPenBrush Brush = Win32kHelper.ResolvePenBrush(Instance, BrushHandle, false); - Instance.WinHelper.EnqueueGdiFillRect(Hwnd, X, Y, X + Width, Y + Height, Brush.ColorRef, Rop); + Instance.WinHelper.EnqueueGdiFillRect(Hwnd, Hdc, X, Y, X + Width, Y + Height, Brush.ColorRef, Rop); Instance.SetRawSyscallReturn(1); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPolyPatBlt.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPolyPatBlt.cs index 0fd9a98..d001223 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPolyPatBlt.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPolyPatBlt.cs @@ -33,8 +33,12 @@ public NTSTATUS Handle(BinaryEmulator Instance) int Height = unchecked((int)Instance.ReadMemoryUInt(EntryAddr + 0x0C)); ulong BrushHandle = Instance.ReadMemoryULong(EntryAddr + 0x10); + // An entry with no brush of its own paints with the one selected into the DC. + if (BrushHandle == 0) + BrushHandle = Instance.WinHelper.ReadDcSelectedBrush(Hdc); + Win32kPenBrush Brush = Win32kHelper.ResolvePenBrush(Instance, BrushHandle, false); - Instance.WinHelper.EnqueueGdiFillRect(Hwnd, X, Y, X + Width, Y + Height, Brush.ColorRef, Rop); + Instance.WinHelper.EnqueueGdiFillRect(Hwnd, Hdc, X, Y, X + Width, Y + Height, Brush.ColorRef, Rop); } Instance.SetRawSyscallReturn(1); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPolyPolyDraw.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPolyPolyDraw.cs index 0921667..73ad2c4 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPolyPolyDraw.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiPolyPolyDraw.cs @@ -1,4 +1,4 @@ -using Brovan.Core.Emulation.OS.SharedHelpers; +using Brovan.Core.Emulation.OS.SharedHelpers; using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k @@ -39,18 +39,22 @@ public NTSTATUS Handle(BinaryEmulator Instance) GdiPrimitiveKind Kind = DrawType == PolygonType ? GdiPrimitiveKind.Polygon : GdiPrimitiveKind.Polyline; ulong PointOffset = 0; + for (uint Figure = 0; Figure < FigureCount; Figure++) { if (!Instance.IsRegionMapped(CountsPtr + (ulong)Figure * 4, 4)) break; uint PointCount = Instance.ReadMemoryUInt(CountsPtr + (ulong)Figure * 4); + ulong FigureBytes = (ulong)PointCount * PointSize; + + // A figure too short to draw still owns its points, and the ones after it are found past them. if (PointCount < 2) { + PointOffset += FigureBytes; continue; } - ulong FigureBytes = (ulong)PointCount * PointSize; ulong FigureAddr = PointsPtr + PointOffset; if (!Instance.IsRegionMapped(FigureAddr, FigureBytes)) break; @@ -66,7 +70,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) }; } - Instance.WinHelper.EnqueueGdiPoly(Hwnd, Kind, Points, Pen.ColorRef, Pen.PenWidth, Brush.ColorRef, DrawType == PolygonType); + Instance.WinHelper.EnqueueGdiPoly(Hwnd, Hdc, Kind, Points, Pen.ColorRef, Pen.PenWidth, Brush.ColorRef, DrawType == PolygonType); PointOffset += FigureBytes; } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRectangle.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRectangle.cs index 84d9d1b..e09f832 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRectangle.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRectangle.cs @@ -1,4 +1,4 @@ -using Brovan.Core.Emulation.OS.SharedHelpers; +using Brovan.Core.Emulation.OS.SharedHelpers; using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k @@ -24,7 +24,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Win32kPenBrush Pen = Win32kHelper.ResolvePenBrush(Instance, Instance.WinHelper.ReadDcSelectedPen(Hdc), true); Win32kPenBrush Brush = Win32kHelper.ResolvePenBrush(Instance, Instance.WinHelper.ReadDcSelectedBrush(Hdc), false); - Instance.WinHelper.EnqueueGdiShape(Hwnd, GdiPrimitiveKind.Rectangle, Left, Top, Right, Bottom, Pen.ColorRef, Pen.PenWidth, Brush.ColorRef); + Instance.WinHelper.EnqueueGdiShape(Hwnd, Hdc, GdiPrimitiveKind.Rectangle, Left, Top, Right, Bottom, Pen.ColorRef, Pen.PenWidth, Brush.ColorRef); Instance.SetRawSyscallReturn(1); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRoundRect.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRoundRect.cs index 6216d15..3070395 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRoundRect.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRoundRect.cs @@ -1,4 +1,4 @@ -using Brovan.Core.Emulation.OS.SharedHelpers; +using Brovan.Core.Emulation.OS.SharedHelpers; using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k @@ -26,7 +26,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Win32kPenBrush Pen = Win32kHelper.ResolvePenBrush(Instance, Instance.WinHelper.ReadDcSelectedPen(Hdc), true); Win32kPenBrush Brush = Win32kHelper.ResolvePenBrush(Instance, Instance.WinHelper.ReadDcSelectedBrush(Hdc), false); - Instance.WinHelper.EnqueueGdiShape(Hwnd, GdiPrimitiveKind.RoundRect, Left, Top, Right, Bottom, Pen.ColorRef, Pen.PenWidth, Brush.ColorRef, Width, Height); + Instance.WinHelper.EnqueueGdiShape(Hwnd, Hdc, GdiPrimitiveKind.RoundRect, Left, Top, Right, Bottom, Pen.ColorRef, Pen.PenWidth, Brush.ColorRef, Width, Height); Instance.SetRawSyscallReturn(1); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSelectBitmap.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSelectBitmap.cs new file mode 100644 index 0000000..58d2bbc --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSelectBitmap.cs @@ -0,0 +1,25 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiSelectBitmap : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + ulong Bitmap = Instance.WinHelper.GetArg(1); + + if (!Win32kHelper.TryGetBitmap(Instance, Bitmap, out Win32kBitmap _) || + !Win32kHelper.TrySelectDcBitmap(Instance, Hdc, Bitmap, out ulong Previous)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Previous); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs new file mode 100644 index 0000000..a3f168a --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs @@ -0,0 +1,41 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiSetBoundsRect : IWinSyscall + { + private const uint DcbDisable = 0x0008; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + ulong RectPtr = Instance.WinHelper.GetArg(1); + uint Flags = (uint)Instance.WinHelper.GetArg(2); + + int Left = 0, Top = 0, Right = 0, Bottom = 0; + bool HasRect = RectPtr != 0; + + if (HasRect) + { + if (!Instance.IsRegionMapped(RectPtr, 16)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + Left = unchecked((int)Instance._emulator.ReadMemoryUInt(RectPtr)); + Top = unchecked((int)Instance._emulator.ReadMemoryUInt(RectPtr + 4)); + Right = unchecked((int)Instance._emulator.ReadMemoryUInt(RectPtr + 8)); + Bottom = unchecked((int)Instance._emulator.ReadMemoryUInt(RectPtr + 12)); + } + + if (!Win32kHelper.TrySetDcBounds(Instance, Hdc, Flags, HasRect, Left, Top, Right, Bottom, out uint Previous)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Previous == 0 ? DcbDisable : Previous); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBrushOrg.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBrushOrg.cs new file mode 100644 index 0000000..07820a7 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBrushOrg.cs @@ -0,0 +1,36 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiSetBrushOrg : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + int X = unchecked((int)Instance.WinHelper.GetArg(1)); + int Y = unchecked((int)Instance.WinHelper.GetArg(2)); + ulong PreviousPtr = Instance.WinHelper.GetArg(3); + + if (!Instance.WinHelper.ReadDcBrushOrigin(Hdc, out int PreviousX, out int PreviousY)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + if (PreviousPtr != 0) + { + if (!Instance.IsRegionMapped(PreviousPtr, 8)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + Instance._emulator.WriteMemory(PreviousPtr, unchecked((uint)PreviousX), 4); + Instance._emulator.WriteMemory(PreviousPtr + 4, unchecked((uint)PreviousY), 4); + } + + Instance.WinHelper.WriteDcBrushOrigin(Hdc, X, Y); + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBitBltSysBmp.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBitBltSysBmp.cs new file mode 100644 index 0000000..8f1a176 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBitBltSysBmp.cs @@ -0,0 +1,20 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserBitBltSysBmp : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + int X = unchecked((int)Instance.WinHelper.GetArg(1)); + int Y = unchecked((int)Instance.WinHelper.GetArg(2)); + int Index = unchecked((int)Instance.WinHelper.GetArg(3)); + + bool Drawn = Win32kHelper.DrawOemBitmap(Instance, Hdc, X, Y, Index); + Instance.SetLastWinError(Drawn ? 0u : Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetBooleanSyscallReturn(Drawn); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserClearWindowState.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserClearWindowState.cs new file mode 100644 index 0000000..34324f1 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserClearWindowState.cs @@ -0,0 +1,25 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserClearWindowState : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + uint Packed = (uint)Instance.WinHelper.GetArg(1); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + bool Applied = Win32kHelper.ApplyWindowState(Instance, Window, Packed, false); + Instance.SetRawSyscallReturn(Applied ? 1UL : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateCaret.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateCaret.cs new file mode 100644 index 0000000..13b94a2 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateCaret.cs @@ -0,0 +1,20 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserCreateCaret : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong Bitmap = Instance.WinHelper.GetArg(1); + int Width = unchecked((int)Instance.WinHelper.GetArg(2)); + int Height = unchecked((int)Instance.WinHelper.GetArg(3)); + + bool Created = Win32kHelper.CreateCaret(Instance, Hwnd, Bitmap, Width, Height); + Instance.SetLastWinError(Created ? 0u : Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(Created); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs index 208f513..cb96099 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs @@ -57,13 +57,21 @@ public NTSTATUS Handle(BinaryEmulator Instance) WindowClass = Instance.WinHelper.GetWindowClass(InstanceHandle, ClassName, classVersion); } + // Answering "no such class" is what makes user32 register a standard control and call back in. + if (WindowClass == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_CANNOT_FIND_WND_CLASS); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + string title = Win32kHelper.ReadLargeString(Instance, WindowNamePtr) ?? string.Empty; ulong hwnd = Instance.WinHelper.AllocateUserHandle(); WinWindow window = new WinWindow { Hwnd = hwnd, - ClassAtom = WindowClass?.Atom ?? 0, + ClassAtom = WindowClass.Atom, Title = title, ClassName = string.IsNullOrEmpty(ClassName) ? "#UNNAMED" : ClassName, Visible = ((uint)StyleArg & 0x10000000U) != 0, // WS_VISIBLE @@ -78,12 +86,18 @@ public NTSTATUS Handle(BinaryEmulator Instance) InstanceHandle = InstanceHandle, CreateParam = CreateParam, OwnerThreadId = Instance.CurrentThread?.ThreadId ?? 0, - WndProc = WindowClass?.WndProc ?? 0, + WndProc = WindowClass.WndProc, + WindowExtraBytes = WindowClass.WindowExtraBytes, Dirty = true, }; Instance.WinHelper.RegisterWindow(window); Instance.SetLastWinError(0); + + WinWindowCreation Creation = new WinWindowCreation { Hwnd = hwnd }; + if (Win32kHelper.SendWindowCreateMessage(Instance, window, Win32kHelper.WM_NCCREATE, Creation)) + return NTSTATUS.STATUS_SUCCESS; + Instance.SetRawSyscallReturn(hwnd); return NTSTATUS.STATUS_SUCCESS; } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDefSetText.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDefSetText.cs new file mode 100644 index 0000000..c35d9b7 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDefSetText.cs @@ -0,0 +1,29 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserDefSetText : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong TextPtr = Instance.WinHelper.GetArg(1); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Window.Title = TextPtr == 0 ? string.Empty : Win32kHelper.ReadLargeString(Instance, TextPtr) ?? string.Empty; + Instance.WinHelper.MaterializeUserWindow(Window); + Win32kHelper.InvalidateWindow(Instance, Hwnd); + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDestroyCaret.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDestroyCaret.cs new file mode 100644 index 0000000..8f29c48 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDestroyCaret.cs @@ -0,0 +1,15 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserDestroyCaret : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + bool Destroyed = Win32kHelper.DestroyCaret(Instance); + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(Destroyed); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDispatchMessage.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDispatchMessage.cs index 79e4dee..d2d1640 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDispatchMessage.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDispatchMessage.cs @@ -1,4 +1,4 @@ -using static Brovan.Core.Helpers.BinaryHelpers; +using static Brovan.Core.Helpers.BinaryHelpers; using Brovan.Core.Helpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k @@ -36,52 +36,12 @@ public NTSTATUS Handle(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } - const uint FN_DWORDOPTINLPMSG_INDEX = 4; - ulong Callback = Instance.WinHelper.GetKernelCallbackEntry(FN_DWORDOPTINLPMSG_INDEX); - if (Callback == 0) + if (!Win32kHelper.InvokeWindowProc(Instance, Message.Hwnd, Window.WndProc, Message.Message, Message.WParam, Message.LParam)) { ulong FallbackResult = Win32kHelper.DispatchMessage(Instance, Message); Instance.SetRawSyscallReturn(FallbackResult); - return NTSTATUS.STATUS_SUCCESS; } - ulong CurrentRsp = Instance.ReadRegister(Registers.UC_X86_REG_RSP); - ulong OriginalReturnAddress = Instance.ReadMemoryULong(CurrentRsp); - - WindowsThreadState State = WinEmulatedThread.GetState(Instance.CurrentThread); - WinUserCallbackFrame Frame = new WinUserCallbackFrame - { - SavedRsp = CurrentRsp, - SavedReturnAddress = OriginalReturnAddress, - }; - State.UserCallbackFrames.Push(Frame); - - const ulong StackReserved = 0x200; - ulong ArgBuffer = (CurrentRsp - StackReserved) & ~0xFUL; - const uint CallbackArgSize = 0x40; - - for (uint i = 0; i < CallbackArgSize; i += 8) - Instance._emulator.WriteMemory(ArgBuffer + i, 0UL, 8); - - Instance._emulator.WriteMemory(ArgBuffer + 0x00, Message.Hwnd, 8); - Instance._emulator.WriteMemory(ArgBuffer + 0x08, Message.Message, 4); - Instance._emulator.WriteMemory(ArgBuffer + 0x10, Message.WParam, 8); - Instance._emulator.WriteMemory(ArgBuffer + 0x18, 0u, 4); - Instance._emulator.WriteMemory(ArgBuffer + 0x28, Window.WndProc, 8); - Instance._emulator.WriteMemory(ArgBuffer + 0x30, Message.LParam, 8); - - ulong DispatcherRsp = (ArgBuffer - 0x80) & ~0xFUL; - for (ulong i = DispatcherRsp; i < ArgBuffer; i += 8) - Instance._emulator.WriteMemory(i, 0UL, 8); - - Instance._emulator.WriteMemory(DispatcherRsp + 0x20, ArgBuffer, 8); - Instance._emulator.WriteMemory(DispatcherRsp + 0x28, 0u, 4); - Instance._emulator.WriteMemory(DispatcherRsp + 0x2C, FN_DWORDOPTINLPMSG_INDEX, 4); - - Instance.WriteRegister(Registers.UC_X86_REG_RCX, ArgBuffer); - Instance.WriteRegister(Registers.UC_X86_REG_RSP, DispatcherRsp - 8); - Instance.WriteRegister(Instance.IPRegister, Callback - 2); - Instance.SuppressSyscallStatusWrite = true; return NTSTATUS.STATUS_SUCCESS; } } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetClassName.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetClassName.cs new file mode 100644 index 0000000..c701a39 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetClassName.cs @@ -0,0 +1,61 @@ +using System; +using System.Text; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetClassName : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong StringPtr = Instance.WinHelper.GetArg(2); + + uint BufferOffset = (uint)Instance.WinHelper.PointerSize; + uint StructSize = BufferOffset * 2; + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null || StringPtr == 0 || !Instance.IsRegionMapped(StringPtr, StructSize)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + string Name = Window.ClassName ?? string.Empty; + ushort MaximumLength = (ushort)(Instance.ReadMemoryUInt(StringPtr) >> 16); + ulong Buffer = Instance.WinHelper.ReadPointer(StringPtr + BufferOffset); + + int MaxChars = MaximumLength / sizeof(char); + if (Buffer == 0 || MaxChars <= 0) + { + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + if (Name.Length >= MaxChars) + Name = Name.Substring(0, MaxChars - 1); + + uint NameBytes = (uint)((Name.Length + 1) * sizeof(char)); + if (!Instance.IsRegionMapped(Buffer, NameBytes)) + { + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Span Bytes = Instance.WinHelper.Shared.GetSpan(NameBytes); + Bytes.Clear(); + Encoding.Unicode.GetBytes(Name, Bytes); + + if (!Instance.WriteMemory(Buffer, Bytes)) + { + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn((uint)Name.Length); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetControlBrush.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetControlBrush.cs new file mode 100644 index 0000000..6393b8f --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetControlBrush.cs @@ -0,0 +1,34 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetControlBrush : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong Hdc = Instance.WinHelper.GetArg(1); + uint Message = (uint)Instance.WinHelper.GetArg(2); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + WinWindow Parent = Window.ParentHwnd == 0 ? null : Instance.WinHelper.GetWindow(Window.ParentHwnd); + + // The owner picks the colour, and only its own thread may run its window procedure. + bool OwnedHere = Parent != null && Parent.OwnerThreadId == (Instance.CurrentThread?.ThreadId ?? 0); + if (OwnedHere && Win32kHelper.InvokeWindowProc(Instance, Parent.Hwnd, Parent.WndProc, Message, Hdc, Hwnd)) + return NTSTATUS.STATUS_SUCCESS; + + Instance.SetLastWinError(0); + + Instance.SetRawSyscallReturn(Instance.WinHelper.GetSystemColorBrush(Win32kHelper.DefaultControlColorIndex(Message))); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetOemBitmapSize.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetOemBitmapSize.cs new file mode 100644 index 0000000..6e63684 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetOemBitmapSize.cs @@ -0,0 +1,30 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetOemBitmapSize : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + int Index = unchecked((int)Instance.WinHelper.GetArg(0)); + ulong SizePtr = Instance.WinHelper.GetArg(1); + + if (SizePtr == 0 || !Win32kHelper.TryGetOemBitmapSize(Index, out int Width, out int Height)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + if (!Instance.IsRegionMapped(SizePtr, 8)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + Instance._emulator.WriteMemory(SizePtr, (uint)Width, 4); + Instance._emulator.WriteMemory(SizePtr + 4, (uint)Height, 4); + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetThreadDesktop.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetThreadDesktop.cs new file mode 100644 index 0000000..1fa0522 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetThreadDesktop.cs @@ -0,0 +1,15 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + // Brovan runs one desktop, so the thread the caller names does not change the answer. + internal class NtUserGetThreadDesktop : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Instance.WinHelper.EnsureThreadDesktopHandle()); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserHideCaret.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserHideCaret.cs new file mode 100644 index 0000000..d8839d4 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserHideCaret.cs @@ -0,0 +1,32 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserHideCaret : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + + if (Hwnd != 0 && Instance.WinHelper.GetWindow(Hwnd) == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Win32kHelper.Win32kCaret Caret = Win32kHelper.GetOwnedCaret(Instance, Hwnd); + if (Caret == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_ACCESS_DENIED); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Caret.ShowCount--; + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserInheritWindowMonitor.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserInheritWindowMonitor.cs new file mode 100644 index 0000000..a79a665 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserInheritWindowMonitor.cs @@ -0,0 +1,21 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + // Brovan runs one monitor, so both windows already share it. + internal class NtUserInheritWindowMonitor : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong SourceHwnd = Instance.WinHelper.GetArg(1); + + bool Valid = Instance.WinHelper.GetWindow(Hwnd) != null && + (SourceHwnd == 0 || Instance.WinHelper.GetWindow(SourceHwnd) != null); + + Instance.SetLastWinError(Valid ? 0u : Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(Valid); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserInitializeClientPfnArrays.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserInitializeClientPfnArrays.cs new file mode 100644 index 0000000..9adccaa --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserInitializeClientPfnArrays.cs @@ -0,0 +1,21 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + // Windows only calls this from the server process, and a class carries its own procedure anyway. + internal class NtUserInitializeClientPfnArrays : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Ansi = Instance.WinHelper.GetArg(0); + ulong Unicode = Instance.WinHelper.GetArg(1); + ulong Worker = Instance.WinHelper.GetArg(2); + + if (Unicode == 0) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + Instance.WinHelper.PublishClientPfnArrays(Ansi, Unicode, Worker); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMessageCall.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMessageCall.cs index 60ac29c..b657907 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMessageCall.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMessageCall.cs @@ -12,6 +12,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) ulong WParam = Instance.WinHelper.GetArg(2); ulong LParam = Instance.WinHelper.GetArg(3); ulong XParam = Instance.WinHelper.GetArg(4); + uint FunctionId = (uint)Instance.WinHelper.GetArg(5); ulong Flags = (uint)Instance.WinHelper.GetArg(6); bool Ansi = (XParam & 1) != 0 || (Flags & 1) != 0; @@ -22,6 +23,16 @@ public NTSTATUS Handle(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } + if (Win32kHelper.IsSendMessageFunction(FunctionId)) + { + WinWindow Target = Hwnd == 0 ? null : Instance.WinHelper.GetWindow(Hwnd); + + // A window procedure only ever runs on the thread that owns the window. + bool OwnedHere = Target != null && Target.OwnerThreadId == (Instance.CurrentThread?.ThreadId ?? 0); + if (OwnedHere && Win32kHelper.InvokeWindowProc(Instance, Hwnd, Target.WndProc, Message, WParam, LParam)) + return NTSTATUS.STATUS_SUCCESS; + } + ulong Result = Win32kHelper.HandleMessageCall(Instance, Hwnd, Message, WParam, LParam, Ansi); Instance.SetRawSyscallReturn(Result); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRegisterClassExWOW.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRegisterClassExWOW.cs index defcda5..c4cdb13 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRegisterClassExWOW.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRegisterClassExWOW.cs @@ -35,6 +35,15 @@ public NTSTATUS Handle(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } + // Both counts ride into a per-window allocation, so an unusable one is refused here. + if (WndClass.cbWndExtra < 0 || WndClass.cbWndExtra > Win32kHelper.MaxClassExtraBytes || + WndClass.cbClsExtra < 0 || WndClass.cbClsExtra > Win32kHelper.MaxClassExtraBytes) + { + Instance.SetLastWinError(ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + string ClassName = Win32kHelper.ReadUnicodeString(Instance, ClassNamePtr); if (string.IsNullOrEmpty(ClassName)) ClassName = ReadClassNameFromPointer(Instance, WndClass.lpszClassName); @@ -63,7 +72,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) CursorHandle = WndClass.hCursor, BackgroundBrush = WndClass.hbrBackground, SmallIconHandle = WndClass.hIconSm, - FunctionId = FunctionId, + FunctionId = Win32kHelper.MaskFunctionId(FunctionId), Flags = Flags, Ansi = (Flags & 1) != 0, }); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetCaretPos.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetCaretPos.cs new file mode 100644 index 0000000..4511a48 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetCaretPos.cs @@ -0,0 +1,27 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetCaretPos : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + int X = unchecked((int)Instance.WinHelper.GetArg(0)); + int Y = unchecked((int)Instance.WinHelper.GetArg(1)); + + Win32kHelper.Win32kCaret Caret = Win32kHelper.GetOwnedCaret(Instance, 0); + if (Caret == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_ACCESS_DENIED); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Caret.X = X; + Caret.Y = Y; + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetDialogPointer.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetDialogPointer.cs new file mode 100644 index 0000000..a0f12c8 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetDialogPointer.cs @@ -0,0 +1,31 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetDialogPointer : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong Dialog = Instance.WinHelper.GetArg(1); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + // The cbWndExtra block is the application's, and its first slots are the DWLP_ indexes. + Window.DialogPointer = Dialog; + Window.IsDialog = Dialog != 0; + Window.Dirty = true; + Instance.WinHelper.MaterializeUserWindow(Window); + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(1); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetParent.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetParent.cs new file mode 100644 index 0000000..f4be37b --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetParent.cs @@ -0,0 +1,41 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetParent : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong NewParentHwnd = Instance.WinHelper.GetArg(1); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + WinWindow NewParent = NewParentHwnd == 0 ? null : Instance.WinHelper.GetWindow(NewParentHwnd); + if (NewParentHwnd != 0 && NewParent == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + ulong Previous = Window.ParentHwnd; + if (!Instance.WinHelper.ReparentWindow(Window, NewParent)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Previous); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetScrollInfo.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetScrollInfo.cs new file mode 100644 index 0000000..d4b69fe --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetScrollInfo.cs @@ -0,0 +1,81 @@ +using System.Buffers.Binary; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetScrollInfo : IWinSyscall + { + private const int SbHorizontal = 0; + private const int SbVertical = 1; + + private const uint SifRange = 0x0001; + private const uint SifPage = 0x0002; + private const uint SifPos = 0x0004; + private const uint SifTrackPos = 0x0010; + private const int ScrollInfoSize = 28; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + int Bar = unchecked((int)Instance.WinHelper.GetArg(1)); + ulong InfoPtr = Instance.WinHelper.GetArg(2); + bool Redraw = Instance.WinHelper.GetArg(3) != 0; + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null || (Bar != SbHorizontal && Bar != SbVertical) || InfoPtr == 0) + { + Instance.SetLastWinError(Window == null + ? Win32kHelper.ERROR_INVALID_WINDOW_HANDLE + : Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Span Raw = stackalloc byte[ScrollInfoSize]; + if (!Instance.ReadMemory(InfoPtr, Raw, ScrollInfoSize)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + uint Mask = BinaryPrimitives.ReadUInt32LittleEndian(Raw.Slice(4, 4)); + WinScrollBarInfo Info = Bar == SbHorizontal ? Window.HorizontalScroll : Window.VerticalScroll; + + if ((Mask & SifRange) != 0) + { + Info.Minimum = BinaryPrimitives.ReadInt32LittleEndian(Raw.Slice(8, 4)); + Info.Maximum = BinaryPrimitives.ReadInt32LittleEndian(Raw.Slice(12, 4)); + if (Info.Maximum < Info.Minimum) + Info.Maximum = Info.Minimum; + } + + if ((Mask & SifPage) != 0) + Info.Page = BinaryPrimitives.ReadUInt32LittleEndian(Raw.Slice(16, 4)); + + if ((Mask & SifPos) != 0) + Info.Position = BinaryPrimitives.ReadInt32LittleEndian(Raw.Slice(20, 4)); + + if ((Mask & SifTrackPos) != 0) + Info.TrackPosition = BinaryPrimitives.ReadInt32LittleEndian(Raw.Slice(24, 4)); + + // The thumb stops where the page starts. The range is the guest's, so this is done wide. + long Highest = Info.Page > 1 ? (long)Info.Maximum - (Info.Page - 1) : Info.Maximum; + if (Highest < Info.Minimum) + Highest = Info.Minimum; + + if (Info.Position < Info.Minimum) + Info.Position = Info.Minimum; + else if (Info.Position > Highest) + Info.Position = (int)Highest; + + if (Bar == SbHorizontal) + Window.HorizontalScroll = Info; + else + Window.VerticalScroll = Info; + + if (Redraw) + Win32kHelper.InvalidateWindow(Instance, Hwnd); + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(unchecked((ulong)(long)Info.Position)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLong.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLong.cs index 6f7b5dc..17e2100 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLong.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLong.cs @@ -39,7 +39,8 @@ public NTSTATUS Handle(BinaryEmulator Instance) case GWL_EXSTYLE: Previous = Window.ExStyle; - Window.ExStyle = NewValue; + // The visible state bit shares this dword client-side and belongs to Visible. + Window.ExStyle = NewValue & ~WinSysHelper.UserWindowStateVisible; break; case GWL_USERDATA: @@ -63,8 +64,15 @@ public NTSTATUS Handle(BinaryEmulator Instance) break; default: - Instance.SetLastWinError(ERROR_INVALID_INDEX); - Instance.SetRawSyscallReturn(0); + if (!Win32kHelper.TryExchangeWindowExtra(Instance, Window, Index, NewValue, 4, out ulong PreviousExtra)) + { + Instance.SetLastWinError(ERROR_INVALID_INDEX); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn((uint)PreviousExtra); return NTSTATUS.STATUS_SUCCESS; } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLongPtr.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLongPtr.cs index 43361d0..eadc568 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLongPtr.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLongPtr.cs @@ -39,7 +39,8 @@ public NTSTATUS Handle(BinaryEmulator Instance) case GWL_EXSTYLE: Previous = Window.ExStyle; - Window.ExStyle = (uint)NewValue; + // The visible state bit shares this dword client-side and belongs to Visible. + Window.ExStyle = (uint)NewValue & ~WinSysHelper.UserWindowStateVisible; break; case GWLP_USERDATA: @@ -63,8 +64,15 @@ public NTSTATUS Handle(BinaryEmulator Instance) break; default: - Instance.SetLastWinError(ERROR_INVALID_INDEX); - Instance.SetRawSyscallReturn(0); + if (!Win32kHelper.TryExchangeWindowExtra(Instance, Window, Index, NewValue, 8, out Previous)) + { + Instance.SetLastWinError(ERROR_INVALID_INDEX); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Previous); return NTSTATUS.STATUS_SUCCESS; } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowState.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowState.cs new file mode 100644 index 0000000..f0189c1 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowState.cs @@ -0,0 +1,25 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetWindowState : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + uint Packed = (uint)Instance.WinHelper.GetArg(1); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + bool Applied = Win32kHelper.ApplyWindowState(Instance, Window, Packed, true); + Instance.SetRawSyscallReturn(Applied ? 1UL : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowCaret.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowCaret.cs new file mode 100644 index 0000000..40ae6eb --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowCaret.cs @@ -0,0 +1,34 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserShowCaret : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + + if (Hwnd != 0 && Instance.WinHelper.GetWindow(Hwnd) == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Win32kHelper.Win32kCaret Caret = Win32kHelper.GetOwnedCaret(Instance, Hwnd); + if (Caret == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_ACCESS_DENIED); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + if (Caret.ShowCount < 1) + Caret.ShowCount++; + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowWindow.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowWindow.cs index a9b4b49..f3b0271 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowWindow.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowWindow.cs @@ -1,4 +1,4 @@ -using static Brovan.Core.Helpers.BinaryHelpers; +using static Brovan.Core.Helpers.BinaryHelpers; namespace Brovan.Core.Emulation.OS.Windows.Win32k { @@ -77,6 +77,9 @@ public NTSTATUS Handle(BinaryEmulator Instance) Instance.WinHelper.FocusWindow = Window.Hwnd; Instance.WinHelper.SetThreadWindowContext(Window); + + if (!WasVisible) + Win32kHelper.InvalidateWindowTree(Instance, Window.Hwnd); } Instance.WinHelper.PresentDesktop(); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateWindow.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateWindow.cs new file mode 100644 index 0000000..d9e73f8 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateWindow.cs @@ -0,0 +1,38 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserUpdateWindow : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + + // WM_PAINT answers to the window procedure, so this syscall runs again once the callback returns. + if (Window.Dirty && Window.Visible) + { + ulong SyscallRip = Instance.WinHelper.GetSyscallRip(Instance.CurrentThread, false); + Window.Dirty = false; + + if (SyscallRip != 0 && + Win32kHelper.InvokeWindowProc(Instance, Hwnd, Window.WndProc, Win32kHelper.WM_PAINT, 0, 0, null, SyscallRip)) + return NTSTATUS.STATUS_SUCCESS; + + Window.Dirty = true; + } + + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWaitMessage.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWaitMessage.cs new file mode 100644 index 0000000..1f1e118 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWaitMessage.cs @@ -0,0 +1,50 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserWaitMessage : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + EmulatedThread Thread = Instance.CurrentThread; + if (Thread == null) + return NTSTATUS.STATUS_UNSUCCESSFUL; + + WindowsThreadState State = WinEmulatedThread.GetState(Thread); + + if (State.WaitCompleted) + { + NTSTATUS Status = State.WaitStatus; + State.WaitCompleted = false; + State.WaitStatus = NTSTATUS.STATUS_SUCCESS; + return Status; + } + + if (Win32kHelper.HasQueuedInputEvent(Instance, Win32kHelper.QS_ALLINPUT)) + { + Instance.SetRawSyscallReturn(1); + return NTSTATUS.STATUS_SUCCESS; + } + + if (!Thread.WaitActive) + { + Thread.WaitActive = true; + Thread.WaitHandles = null; + Thread.WaitAll = false; + Thread.WaitDeadline = -1; + State.WaitCompleted = false; + State.WaitStatus = NTSTATUS.STATUS_PENDING; + State.WaitResumeRIP = Instance.WinHelper.GetSyscallRip(Thread, false); + State.WaitReturnRIP = State.WaitResumeRIP + 2; + State.WaitAlertable = false; + State.WaitMessageActive = true; + } + + Thread.State = EmulatedThreadState.Waiting; + State.ApcAlertable = false; + Instance._emulator.WriteRegister(Instance.IPRegister, State.WaitResumeRIP); + Instance._emulator.StopEmulation(); + return NTSTATUS.STATUS_PENDING; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs index 805f0c3..94a577a 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs @@ -87,17 +87,23 @@ internal struct Win32kBitmap internal static class Win32kHelper { internal const uint ERROR_INVALID_HANDLE = 6; + internal const uint ERROR_ACCESS_DENIED = 5; internal const uint ERROR_INVALID_PARAMETER = 87; internal const uint ERROR_CALL_NOT_IMPLEMENTED = 120; internal const uint ERROR_INSUFFICIENT_BUFFER = 122; internal const uint ERROR_INVALID_WINDOW_HANDLE = 1400; + internal const uint ERROR_CANNOT_FIND_WND_CLASS = 1407; + + internal const int MaxClassExtraBytes = 0x10000; internal const byte PenHandleType = 0x30; internal const byte BrushHandleType = 0x10; internal const byte BitmapHandleType = 0x05; internal const uint WM_NULL = 0x0000; + internal const uint WM_CREATE = 0x0001; internal const uint WM_DESTROY = 0x0002; + internal const uint WM_NCCREATE = 0x0081; internal const uint WM_SIZE = 0x0005; internal const uint WM_ACTIVATE = 0x0006; internal const uint WM_SETFOCUS = 0x0007; @@ -114,6 +120,17 @@ internal static class Win32kHelper internal const uint WM_GETTEXT = 0x000D; internal const uint WM_GETTEXTLENGTH = 0x000E; internal const uint WM_NCHITTEST = 0x0084; + internal const uint WM_CTLCOLORMSGBOX = 0x0132; + internal const uint WM_CTLCOLOREDIT = 0x0133; + internal const uint WM_CTLCOLORLISTBOX = 0x0134; + internal const uint WM_CTLCOLORBTN = 0x0135; + internal const uint WM_CTLCOLORDLG = 0x0136; + internal const uint WM_CTLCOLORSCROLLBAR = 0x0137; + internal const uint WM_CTLCOLORSTATIC = 0x0138; + + internal const int COLOR_SCROLLBAR = 0; + internal const int COLOR_WINDOW = 5; + internal const int COLOR_BTNFACE = 15; internal const uint WM_NCDESTROY = 0x0082; internal const uint WM_PAINT = 0x000F; internal const uint WM_SETTEXT = 0x000C; @@ -171,6 +188,7 @@ private sealed class Win32kState public readonly Dictionary DeviceContexts = new(); public readonly Dictionary PenBrushObjects = new(); public readonly Dictionary Bitmaps = new(); + public ulong StockBitmap; public ulong NextDeviceContext = FirstDeviceContextHandle; public ulong CaptureWindow; public ulong ActivatedWindow; @@ -183,6 +201,18 @@ private sealed class Win32kState public bool CursorAssigned; public int CursorShowCount; public bool CursorHidden; + public Win32kCaret Caret; + } + + internal sealed class Win32kCaret + { + public ulong Hwnd; + public ulong Bitmap; + public int X; + public int Y; + public int Width; + public int Height; + public int ShowCount; } private sealed class Win32kDeviceContext @@ -191,6 +221,12 @@ private sealed class Win32kDeviceContext public ulong Hwnd; public bool WindowDc; public bool PaintDc; + public ulong SelectedBitmap; + public uint BoundsFlags; + public int BoundsLeft; + public int BoundsTop; + public int BoundsRight; + public int BoundsBottom; } private static Win32kState GetState(BinaryEmulator Instance) @@ -198,6 +234,41 @@ private static Win32kState GetState(BinaryEmulator Instance) return States.GetValue(Instance, static _ => new Win32kState()); } + internal static bool CreateCaret(BinaryEmulator Instance, ulong Hwnd, ulong Bitmap, int Width, int Height) + { + if (Instance.WinHelper.GetWindow(Hwnd) == null) + return false; + + GetState(Instance).Caret = new Win32kCaret + { + Hwnd = Hwnd, + Bitmap = Bitmap, + Width = Width, + Height = Height, + ShowCount = 0, + }; + return true; + } + + internal static bool DestroyCaret(BinaryEmulator Instance) + { + Win32kState State = GetState(Instance); + if (State.Caret == null) + return false; + + State.Caret = null; + return true; + } + + internal static Win32kCaret GetOwnedCaret(BinaryEmulator Instance, ulong Hwnd) + { + Win32kCaret Caret = GetState(Instance).Caret; + if (Caret == null) + return null; + + return Hwnd == 0 || Caret.Hwnd == Hwnd ? Caret : null; + } + internal static ulong GetCaptureWindow(BinaryEmulator Instance) { return GetState(Instance).CaptureWindow; @@ -233,6 +304,7 @@ internal static ulong CreateDeviceContext(BinaryEmulator Instance, ulong Hwnd, b Hwnd = Hwnd, WindowDc = WindowDc, PaintDc = PaintDc, + SelectedBitmap = EnsureStockBitmap(Instance), }; return GdiHandle; } @@ -270,6 +342,65 @@ internal static bool IsKnownDc(BinaryEmulator Instance, ulong Hdc) return GetState(Instance).DeviceContexts.ContainsKey(Hdc); } + internal static bool TrySelectDcBitmap(BinaryEmulator Instance, ulong Hdc, ulong Bitmap, out ulong Previous) + { + Previous = 0; + + Win32kState State = GetState(Instance); + if (!State.DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc)) + return false; + + Previous = Dc.SelectedBitmap != 0 ? Dc.SelectedBitmap : EnsureStockBitmap(Instance); + Dc.SelectedBitmap = Bitmap; + return true; + } + + internal static bool TrySetDcBounds(BinaryEmulator Instance, ulong Hdc, uint Flags, bool HasRect, + int Left, int Top, int Right, int Bottom, out uint Previous) + { + const uint DcbReset = 0x0001; + const uint DcbAccumulate = 0x0002; + const uint DcbEnable = 0x0004; + const uint DcbDisable = 0x0008; + + Previous = 0; + + Win32kState State = GetState(Instance); + if (!State.DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc)) + return false; + + Previous = Dc.BoundsFlags == 0 ? DcbDisable : Dc.BoundsFlags; + + bool Empty = (Flags & DcbReset) != 0 || + (Dc.BoundsRight <= Dc.BoundsLeft && Dc.BoundsBottom <= Dc.BoundsTop); + + if ((Flags & DcbReset) != 0) + { + Dc.BoundsLeft = 0; + Dc.BoundsTop = 0; + Dc.BoundsRight = 0; + Dc.BoundsBottom = 0; + } + + if (HasRect && (Flags & DcbAccumulate) != 0 && (Right != Left || Bottom != Top)) + { + int NewLeft = Math.Min(Left, Right); + int NewTop = Math.Min(Top, Bottom); + int NewRight = Math.Max(Left, Right); + int NewBottom = Math.Max(Top, Bottom); + + Dc.BoundsLeft = Empty ? NewLeft : Math.Min(Dc.BoundsLeft, NewLeft); + Dc.BoundsTop = Empty ? NewTop : Math.Min(Dc.BoundsTop, NewTop); + Dc.BoundsRight = Empty ? NewRight : Math.Max(Dc.BoundsRight, NewRight); + Dc.BoundsBottom = Empty ? NewBottom : Math.Max(Dc.BoundsBottom, NewBottom); + } + + if ((Flags & (DcbEnable | DcbDisable)) != 0) + Dc.BoundsFlags = Flags & (DcbEnable | DcbDisable); + + return true; + } + internal static ulong CreatePen(BinaryEmulator Instance, int Style, int Width, uint ColorRef) { ulong Handle = Instance.WinHelper.AllocateGdiHandle(PenHandleType); @@ -598,6 +729,16 @@ internal static int GetBitmapStride(int Width, int Planes, int BitsPerPixel, boo return DibSection ? (int)(((Bits + 31) / 32) * 4) : (int)(((Bits + 15) / 16) * 2); } + // Every DC starts on this, so a caller that selects its own bitmap has one to select back. + internal static ulong EnsureStockBitmap(BinaryEmulator Instance) + { + Win32kState State = GetState(Instance); + if (State.StockBitmap == 0) + State.StockBitmap = CreateBitmap(Instance, 1, 1, 1, 1, false, false); + + return State.StockBitmap; + } + internal static ulong CreateBitmap(BinaryEmulator Instance, int Width, int Height, ushort Planes, ushort BitsPerPixel, bool DibSection, bool TopDown) { int Stride = GetBitmapStride(Width, Planes, BitsPerPixel, DibSection); @@ -675,9 +816,57 @@ internal static bool RemoveBitmap(BinaryEmulator Instance, ulong Handle) return true; } + internal const int TextMetricWSize = 60; + + internal static TextMetricsData DefaultTextMetrics => new TextMetricsData + { + Height = 16, + Ascent = 12, + Descent = 4, + AveCharWidth = 8, + MaxCharWidth = 16, + Weight = 400, + DigitizedAspectX = 96, + DigitizedAspectY = 96, + FirstChar = 0x20, + LastChar = 0xFF, + DefaultChar = 0x20, + BreakChar = 0x20, + PitchAndFamily = 0x01, + }; + + internal static void WriteTextMetricsW(Span Buffer, in TextMetricsData Metrics) + { + Buffer.Slice(0, TextMetricWSize).Clear(); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x00, 4), Metrics.Height); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x04, 4), Metrics.Ascent); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x08, 4), Metrics.Descent); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x0C, 4), Metrics.InternalLeading); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x10, 4), Metrics.ExternalLeading); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x14, 4), Metrics.AveCharWidth); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x18, 4), Metrics.MaxCharWidth); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x1C, 4), Metrics.Weight); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x20, 4), Metrics.Overhang); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x24, 4), Metrics.DigitizedAspectX); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x28, 4), Metrics.DigitizedAspectY); + BinaryPrimitives.WriteUInt16LittleEndian(Buffer.Slice(0x2C, 2), Metrics.FirstChar); + BinaryPrimitives.WriteUInt16LittleEndian(Buffer.Slice(0x2E, 2), Metrics.LastChar); + BinaryPrimitives.WriteUInt16LittleEndian(Buffer.Slice(0x30, 2), Metrics.DefaultChar); + BinaryPrimitives.WriteUInt16LittleEndian(Buffer.Slice(0x32, 2), Metrics.BreakChar); + Buffer[0x34] = Metrics.Italic; + Buffer[0x35] = Metrics.Underlined; + Buffer[0x36] = Metrics.StruckOut; + Buffer[0x37] = Metrics.PitchAndFamily; + Buffer[0x38] = Metrics.CharSet; + } + internal static bool PostMessage(BinaryEmulator Instance, ulong Hwnd, uint Message, ulong WParam, ulong LParam) { - Win32kState State = GetState(Instance); + return PostMessage(Instance, GetState(Instance), Hwnd, Message, WParam, LParam); + } + + private static bool PostMessage(BinaryEmulator Instance, Win32kState State, ulong Hwnd, uint Message, ulong WParam, ulong LParam) + { uint Time = unchecked((uint)Instance.EmulatedTickCount64); if (Hwnd == HWND_BROADCAST) @@ -948,43 +1137,10 @@ internal static ulong DispatchMessage(BinaryEmulator Instance, Win32kMessage Mes return 0; } - if (Window != null) - { - switch (Message.Message) - { - case WM_SETTEXT: - Window.Title = ReadWindowTextPointer(Instance, Message.LParam, false) ?? string.Empty; - Window.Dirty = true; - Instance.WinHelper.MaterializeUserWindow(Window); - Instance.WinHelper.PresentDesktop(); - return 1; - - case WM_GETTEXT: - return WriteWindowText(Instance, Window.Title ?? string.Empty, Message.LParam, Message.WParam, false); - - case WM_GETTEXTLENGTH: - return (ulong)(Window.Title?.Length ?? 0); - - case WM_NCHITTEST: - return HTCLIENT; - - case WM_ERASEBKGND: - return 1; - - case WM_CLOSE: - Instance.WinHelper.DestroyWindow(Window.Hwnd); - return 0; - - case WM_DESTROY: - case WM_SETCURSOR: - case WM_PAINT: - case WM_NULL: - default: - return 0; - } - } + if (Window == null) + return 0; - return 0; + return DefaultWindowProc(Instance, Window, Message.Message, Message.WParam, Message.LParam, false); } private const int MaxHostInputEventsPerDrain = 64; @@ -1242,6 +1398,141 @@ internal static bool InvalidateWindow(BinaryEmulator Instance, ulong Hwnd) return true; } + internal static void InvalidateWindowTree(BinaryEmulator Instance, ulong Hwnd) + { + InvalidateWindowTree(Instance, GetState(Instance), Hwnd, 0); + } + + private static void InvalidateWindowTree(BinaryEmulator Instance, Win32kState State, ulong Hwnd, int Depth) + { + // A child list that has been closed into a loop would otherwise walk forever. + if (Depth >= MaxWindowTreeDepth) + return; + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null || !Window.Visible) + return; + + Window.Dirty = true; + PostMessage(Instance, State, Hwnd, WM_PAINT, 0, 0); + + for (int i = 0; i < Window.Children.Count; i++) + InvalidateWindowTree(Instance, State, Window.Children[i], Depth + 1); + } + + private const int MaxWindowTreeDepth = 64; + + // The plain callback shape passes hwnd, message, wParam and lParam unchanged. The + // DispatchMessage-shaped entry beside it carries no lParam, only a pointer to the MSG. + private const uint WindowProcCallbackIndex = 2; + private const ulong WindowProcArgumentReserve = 0x400; + private const int WindowProcArgumentHeaderSize = 0x30; + private const int WindowProcArgumentBlockSize = 0x40; + private const int CreateStructSize = 0x50; + private const int CreateStructNameChars = 96; + + // The syscall in progress does not answer. The procedure's result becomes its return value. + internal static bool InvokeWindowProc(BinaryEmulator Instance, ulong Hwnd, ulong WndProc, uint Message, ulong WParam, ulong LParam, WinWindowCreation Creation = null, ulong SyscallRetryRip = 0) + { + if (!TryBeginWindowProcCallback(Instance, WndProc, out ulong Callback, out ulong ArgumentBuffer)) + return false; + + WriteWindowProcCallbackArguments(Instance, ArgumentBuffer, Hwnd, WndProc, Message, WParam, LParam); + return Instance.WinHelper.EnterUserCallback(Callback, WindowProcCallbackIndex, ArgumentBuffer, Creation, SyscallRetryRip); + } + + internal static bool SendWindowCreateMessage(BinaryEmulator Instance, WinWindow Window, uint Message, WinWindowCreation Creation) + { + if (Window == null || !TryBeginWindowProcCallback(Instance, Window.WndProc, out ulong Callback, out ulong ArgumentBuffer)) + return false; + + ulong CreateStruct = ArgumentBuffer + WindowProcArgumentHeaderSize; + ulong NameAddress = CreateStruct + CreateStructSize; + ulong ClassAddress = NameAddress + (ulong)CreateStructNameChars * 2; + + if (!WriteCallbackString(Instance, NameAddress, Window.Title)) + NameAddress = 0; + + // A class named by atom stays an atom, the way CreateWindowEx was called. + if (Window.ClassAtom != 0 && Window.ClassName != null && Window.ClassName.StartsWith("#ATOM_", StringComparison.Ordinal)) + ClassAddress = Window.ClassAtom; + else if (!WriteCallbackString(Instance, ClassAddress, Window.ClassName)) + ClassAddress = 0; + + Span Data = Instance.WinHelper.Shared.GetSpan(CreateStructSize).Slice(0, CreateStructSize); + Data.Clear(); + + BinaryPrimitives.WriteUInt64LittleEndian(Data.Slice(0x00, 8), Window.CreateParam); + BinaryPrimitives.WriteUInt64LittleEndian(Data.Slice(0x08, 8), Window.InstanceHandle); + BinaryPrimitives.WriteUInt64LittleEndian(Data.Slice(0x10, 8), Window.MenuHandle); + BinaryPrimitives.WriteUInt64LittleEndian(Data.Slice(0x18, 8), Window.ParentHwnd); + BinaryPrimitives.WriteUInt32LittleEndian(Data.Slice(0x20, 4), Window.Height); + BinaryPrimitives.WriteUInt32LittleEndian(Data.Slice(0x24, 4), Window.Width); + BinaryPrimitives.WriteUInt32LittleEndian(Data.Slice(0x28, 4), (uint)Window.Y); + BinaryPrimitives.WriteUInt32LittleEndian(Data.Slice(0x2C, 4), (uint)Window.X); + BinaryPrimitives.WriteUInt32LittleEndian(Data.Slice(0x30, 4), Window.Style); + BinaryPrimitives.WriteUInt64LittleEndian(Data.Slice(0x38, 8), NameAddress); + BinaryPrimitives.WriteUInt64LittleEndian(Data.Slice(0x40, 8), ClassAddress); + BinaryPrimitives.WriteUInt32LittleEndian(Data.Slice(0x48, 4), Window.ExStyle); + + if (!Instance.WriteMemory(CreateStruct, Data)) + return false; + + WriteWindowProcCallbackArguments(Instance, ArgumentBuffer, Window.Hwnd, Window.WndProc, Message, 0, CreateStruct); + return Instance.WinHelper.EnterUserCallback(Callback, WindowProcCallbackIndex, ArgumentBuffer, Creation); + } + + private static bool TryBeginWindowProcCallback(BinaryEmulator Instance, ulong WndProc, out ulong Callback, out ulong ArgumentBuffer) + { + Callback = 0; + ArgumentBuffer = 0; + + if (WndProc == 0 || Instance.WinHelper.PointerSize != 8) + return false; + + Callback = Instance.WinHelper.GetKernelCallbackEntry(WindowProcCallbackIndex); + if (Callback == 0) + return false; + + ulong CurrentRsp = Instance.ReadRegister(Registers.UC_X86_REG_RSP); + if (!Instance.IsRegionMapped(CurrentRsp, 8)) + return false; + + ArgumentBuffer = (CurrentRsp - WindowProcArgumentReserve) & ~0xFUL; + if (!Instance.IsRegionMapped(ArgumentBuffer, WindowProcArgumentReserve)) + return false; + + for (int Offset = 0; Offset < WindowProcArgumentBlockSize; Offset += 8) + Instance._emulator.WriteMemory(ArgumentBuffer + (ulong)Offset, 0UL, 8); + + return true; + } + + private static void WriteWindowProcCallbackArguments(BinaryEmulator Instance, ulong ArgumentBuffer, + ulong Hwnd, ulong WndProc, uint Message, ulong WParam, ulong LParam) + { + Instance._emulator.WriteMemory(ArgumentBuffer + 0x00, Hwnd, 8); + Instance._emulator.WriteMemory(ArgumentBuffer + 0x08, (ulong)Message, 8); + Instance._emulator.WriteMemory(ArgumentBuffer + 0x10, WParam, 8); + Instance._emulator.WriteMemory(ArgumentBuffer + 0x18, LParam, 8); + Instance._emulator.WriteMemory(ArgumentBuffer + 0x20, 0UL, 8); + Instance._emulator.WriteMemory(ArgumentBuffer + 0x28, WndProc, 8); + } + + private static bool WriteCallbackString(BinaryEmulator Instance, ulong Address, string Value) + { + string Text = Value ?? string.Empty; + if (Text.Length >= CreateStructNameChars) + Text = Text.Substring(0, CreateStructNameChars - 1); + + uint Bytes = (uint)((Text.Length + 1) * 2); + Span Buffer = Instance.WinHelper.Shared.GetSpan(Bytes).Slice(0, (int)Bytes); + Buffer.Clear(); + Encoding.Unicode.GetBytes(Text, Buffer); + + return Instance.WriteMemory(Address, Buffer); + } + internal static ulong HandleMessageCall(BinaryEmulator Instance, ulong Hwnd, uint Message, ulong WParam, ulong LParam, bool Ansi) { if (Hwnd != 0 && Instance.WinHelper.GetWindow(Hwnd) == null) @@ -1254,34 +1545,82 @@ internal static ulong HandleMessageCall(BinaryEmulator Instance, ulong Hwnd, uin if (Window == null) return 0; - if (Message == WM_SETTEXT) + return DefaultWindowProc(Instance, Window, Message, WParam, LParam, Ansi); + } + + private static ulong DefaultWindowProc(BinaryEmulator Instance, WinWindow Window, uint Message, ulong WParam, ulong LParam, bool Ansi) + { + switch (Message) { - Window.Title = ReadWindowTextPointer(Instance, LParam, Ansi) ?? string.Empty; - Window.Dirty = true; - Instance.WinHelper.MaterializeUserWindow(Window); - Instance.WinHelper.PresentDesktop(); - return 1; + case WM_SETTEXT: + Window.Title = ReadWindowTextPointer(Instance, LParam, Ansi) ?? string.Empty; + Window.Dirty = true; + Instance.WinHelper.MaterializeUserWindow(Window); + Instance.WinHelper.PresentDesktop(); + return 1; + + case WM_GETTEXT: + return WriteWindowText(Instance, Window.Title ?? string.Empty, LParam, WParam, Ansi); + + case WM_GETTEXTLENGTH: + return (ulong)(Window.Title?.Length ?? 0); + + case WM_NCHITTEST: + return HTCLIENT; + + // DefWindowProc accepts the window. Answering zero here would refuse every creation. + case WM_NCCREATE: + return 1; + + case WM_ERASEBKGND: + return EraseWindowBackground(Instance, Window, WParam) ? 1ul : 0ul; + + case WM_CLOSE: + Instance.WinHelper.DestroyWindow(Window.Hwnd); + return 0; + + default: + if (Message >= WM_CTLCOLORMSGBOX && Message <= WM_CTLCOLORSTATIC) + return Instance.WinHelper.GetSystemColorBrush(DefaultControlColorIndex(Message)); + + return 0; } + } - if (Message == WM_GETTEXT) - return WriteWindowText(Instance, Window.Title ?? string.Empty, LParam, WParam, Ansi); + private static bool EraseWindowBackground(BinaryEmulator Instance, WinWindow Window, ulong Hdc) + { + WinWindowClass Class = Window.ClassAtom == 0 ? null : Instance.WinHelper.GetWindowClass(Window.ClassAtom); + ulong Background = Class?.BackgroundBrush ?? 0; + if (Background == 0) + return false; - if (Message == WM_GETTEXTLENGTH) - return (ulong)(Window.Title?.Length ?? 0); + // A class can name a system colour instead of a brush, as the colour index plus one. + uint Color = Background <= SystemColorCount + ? Instance.WinHelper.GetSystemColor((int)Background - 1) + : ResolvePenBrush(Instance, Background, false).ColorRef; - if (Message == WM_NCHITTEST) - return HTCLIENT; + Instance.WinHelper.EnqueueGdiFillRect(Window.Hwnd, Hdc, 0, 0, + (int)Window.Width, (int)Window.Height, Color, PatCopy); + return true; + } - if (Message == WM_ERASEBKGND) - return 1; + private const uint SystemColorCount = 31; + private const uint PatCopy = 0x00F00021; - if (Message == WM_CLOSE) + internal static int DefaultControlColorIndex(uint Message) + { + switch (Message) { - Instance.WinHelper.DestroyWindow(Window.Hwnd); - return 0; - } + case WM_CTLCOLOREDIT: + case WM_CTLCOLORLISTBOX: + return COLOR_WINDOW; - return 0; + case WM_CTLCOLORSCROLLBAR: + return COLOR_SCROLLBAR; + + default: + return COLOR_BTNFACE; + } } internal static bool RemoveFlagSet(uint Flags) @@ -1343,5 +1682,241 @@ private static ulong WriteWindowText(BinaryEmulator Instance, string Text, ulong return (ulong)Output.Length; } + + internal const ushort FnidFirst = 0x029A; + internal const ushort FnidScrollBar = 0x029A; + internal const ushort FnidIconTitle = 0x029B; + internal const ushort FnidMenu = 0x029C; + internal const ushort FnidDesktop = 0x029D; + internal const ushort FnidDefWindowProc = 0x029E; + internal const ushort FnidMessageWnd = 0x029F; + internal const ushort FnidSwitch = 0x02A0; + internal const ushort FnidButton = 0x02A1; + internal const ushort FnidComboBox = 0x02A2; + internal const ushort FnidComboLBox = 0x02A3; + internal const ushort FnidDialog = 0x02A4; + internal const ushort FnidEdit = 0x02A5; + internal const ushort FnidListBox = 0x02A6; + internal const ushort FnidMdiClient = 0x02A7; + internal const ushort FnidStatic = 0x02A8; + internal const ushort FnidIme = 0x02A9; + internal const ushort FnidGhost = 0x02AA; + + private const ushort FnidSendMessageFirst = 0x02B1; + private const ushort FnidSendMessageLast = 0x02B8; + internal const ushort FnidLast = FnidSendMessageLast; + internal const int FnidCount = FnidLast - FnidFirst + 1; + + // FNID_DDEML, FNID_DESTROY and FNID_FREED ride above the fnid itself. + private const uint FnidStatusBits = 0xE000; + + // A dialog template names a standard control by an ordinal into gpsi->atomSysClass. + private const int IclsButton = 0; + private const int IclsEdit = 1; + private const int IclsStatic = 2; + private const int IclsListBox = 3; + private const int IclsScrollBar = 4; + private const int IclsComboBox = 5; + private const int IclsMdiClient = 6; + private const int IclsComboLBox = 7; + private const int IclsIme = 15; + private const int IclsGhost = 16; + private const int IclsDesktop = 17; + private const int IclsDialog = 18; + private const int IclsMenu = 19; + private const int IclsSwitch = 20; + private const int IclsIconTitle = 21; + + internal static bool IsSendMessageFunction(uint FunctionId) + { + ushort Fnid = MaskFunctionId(FunctionId); + return Fnid >= FnidSendMessageFirst && Fnid <= FnidSendMessageLast; + } + + internal static ushort MaskFunctionId(uint FunctionId) + { + return (ushort)(FunctionId & ~FnidStatusBits); + } + + // A control reads a sibling's class atom out of gpsi->atomSysClass once, so every slot has to answer + // before user32 registers anything. The five named by an integer atom keep it. + internal static readonly (ushort FunctionId, int Index, ushort WellKnownAtom, string Name)[] ReservedClasses = + { + (FnidButton, IclsButton, (ushort)0, "Button"), + (FnidEdit, IclsEdit, (ushort)0, "Edit"), + (FnidStatic, IclsStatic, (ushort)0, "Static"), + (FnidListBox, IclsListBox, (ushort)0, "ListBox"), + (FnidScrollBar, IclsScrollBar, (ushort)0, "ScrollBar"), + (FnidComboBox, IclsComboBox, (ushort)0, "ComboBox"), + (FnidMdiClient, IclsMdiClient, (ushort)0, "MDIClient"), + (FnidComboLBox, IclsComboLBox, (ushort)0, "ComboLBox"), + (FnidIme, IclsIme, (ushort)0, "IME"), + (FnidGhost, IclsGhost, (ushort)0, "Ghost"), + (FnidMenu, IclsMenu, (ushort)32768, "#32768"), + (FnidDesktop, IclsDesktop, (ushort)32769, "#32769"), + (FnidDialog, IclsDialog, (ushort)32770, "#32770"), + (FnidSwitch, IclsSwitch, (ushort)32771, "#32771"), + (FnidIconTitle, IclsIconTitle, (ushort)32772, "#32772"), + }; + + internal static bool TryGetSystemClassIndex(uint FunctionId, out int Index) + { + switch (MaskFunctionId(FunctionId)) + { + case FnidButton: Index = IclsButton; return true; + case FnidEdit: Index = IclsEdit; return true; + case FnidStatic: Index = IclsStatic; return true; + case FnidListBox: Index = IclsListBox; return true; + case FnidScrollBar: Index = IclsScrollBar; return true; + case FnidComboBox: Index = IclsComboBox; return true; + case FnidMdiClient: Index = IclsMdiClient; return true; + case FnidComboLBox: Index = IclsComboLBox; return true; + case FnidIme: Index = IclsIme; return true; + case FnidGhost: Index = IclsGhost; return true; + case FnidDesktop: Index = IclsDesktop; return true; + case FnidDialog: Index = IclsDialog; return true; + case FnidMenu: Index = IclsMenu; return true; + case FnidSwitch: Index = IclsSwitch; return true; + case FnidIconTitle: Index = IclsIconTitle; return true; + default: Index = -1; return false; + } + } + + private const ulong WindowStateBase = 0x10; + private const int WindowStateExStyleFirstByte = 0x08; + private const int WindowStateStyleFirstByte = 0x0C; + private const int WindowStateFieldBytes = 4; + private const int WindowStateDialogByte = 0x02; + private const byte WindowStateDialogMask = 0x01; + + // user32 names the byte with a packed word, the high byte is the offset from tagWND+0x10 and + // the low byte is the mask. + internal static bool ApplyWindowState(BinaryEmulator Instance, WinWindow Window, uint Packed, bool Set) + { + if (Window == null || Window.ClientWindowAddress == 0) + return false; + + int Offset = (int)((Packed >> 8) & 0xFF); + byte Mask = (byte)(Packed & 0xFF); + ulong Address = Window.ClientWindowAddress + WindowStateBase + (ulong)Offset; + + if (!Instance.IsRegionMapped(Address, 1)) + return false; + + byte Current = (byte)Instance.ReadMemoryUInt(Address); + byte Updated = Set ? (byte)(Current | Mask) : (byte)(Current & ~Mask); + Instance._emulator.WriteMemory(Address, Updated, 1); + + // The next refresh of the window writes win32k's own copy back over whatever the guest set here. + if (Offset >= WindowStateStyleFirstByte && Offset < WindowStateStyleFirstByte + WindowStateFieldBytes) + { + Window.Style = ReplaceByte(Window.Style, Offset - WindowStateStyleFirstByte, Updated); + Window.Visible = (Window.Style & WinSysHelper.UserWindowStyleVisible) != 0; + } + else if (Offset >= WindowStateExStyleFirstByte && Offset < WindowStateExStyleFirstByte + WindowStateFieldBytes) + { + uint Composed = Window.ExStyle | (Window.Visible ? WinSysHelper.UserWindowStateVisible : 0u); + Composed = ReplaceByte(Composed, Offset - WindowStateExStyleFirstByte, Updated); + + Window.Visible = (Composed & WinSysHelper.UserWindowStateVisible) != 0; + Window.ExStyle = Composed & ~WinSysHelper.UserWindowStateVisible; + } + else if (Offset == WindowStateDialogByte && (Mask & WindowStateDialogMask) != 0) + { + Window.IsDialog = (Updated & WindowStateDialogMask) != 0; + } + + return true; + } + + private static uint ReplaceByte(uint Value, int Index, byte Replacement) + { + int Shift = Index * 8; + return (Value & ~(0xFFu << Shift)) | ((uint)Replacement << Shift); + } + + internal static bool TryExchangeWindowExtra(BinaryEmulator Instance, WinWindow Window, int Offset, ulong Value, uint Size, out ulong Previous) + { + Previous = 0; + + if (Window == null || Offset < 0 || Offset > Window.WindowExtraBytes - (int)Size) + return false; + + ulong Extra = Instance.WinHelper.GetWindowExtraBytesAddress(Window); + if (Extra == 0 || !Instance.IsRegionMapped(Extra + (ulong)Offset, Size)) + return false; + + ulong Address = Extra + (ulong)Offset; + Previous = Size == 8 ? Instance.ReadMemoryULong(Address) : Instance.ReadMemoryUInt(Address); + return Instance._emulator.WriteMemory(Address, Value, Size); + } + + private const int OemGlyphSize = 13; + private const int OemRadioMask = 71; + private const int OemCheckBoxFirst = 72; + private const int OemRadioFirst = 77; + private const int OemThreeStateFirst = 82; + private const int OemStatesPerGlyph = 5; + private const int OemLast = OemThreeStateFirst + OemStatesPerGlyph - 1; + + private const int OemStateChecked = 1; + private const int OemStatePushed = 2; + private const int OemStateCheckedPushed = 3; + private const int OemStateCheckedDisabled = 4; + + internal const int COLOR_WINDOWTEXT = 8; + internal const int COLOR_BTNSHADOW = 16; + internal const int COLOR_GRAYTEXT = 17; + + internal static bool TryGetOemBitmapSize(int Index, out int Width, out int Height) + { + Width = OemGlyphSize; + Height = OemGlyphSize; + return Index >= OemRadioMask && Index <= OemLast; + } + + // A radio arrives as two blits, a mask under SRCAND then the glyph under SRCINVERT. Drawing the + // circle once on the second is the same picture. + internal static bool DrawOemBitmap(BinaryEmulator Instance, ulong Hdc, int X, int Y, int Index) + { + if (Index < OemRadioMask || Index > OemLast) + return false; + + ulong Hwnd = Instance.WinHelper.GetHwndFromDc(Hdc); + if (Hwnd == 0) + return false; + + if (Index == OemRadioMask) + return true; + + bool Round = Index >= OemRadioFirst && Index < OemThreeStateFirst; + int First = Round ? OemRadioFirst : Index >= OemThreeStateFirst ? OemThreeStateFirst : OemCheckBoxFirst; + int State = Index - First; + + bool Marked = State == OemStateChecked || State == OemStateCheckedPushed || State == OemStateCheckedDisabled; + bool Sunken = State == OemStatePushed || State == OemStateCheckedPushed || State == OemStateCheckedDisabled || + Index >= OemThreeStateFirst; + + uint Interior = Instance.WinHelper.GetSystemColor(Sunken ? COLOR_BTNFACE : COLOR_WINDOW); + uint Border = Instance.WinHelper.GetSystemColor(COLOR_BTNSHADOW); + uint Mark = Instance.WinHelper.GetSystemColor(State == OemStateCheckedDisabled ? COLOR_GRAYTEXT : COLOR_WINDOWTEXT); + + Instance.WinHelper.EnqueueGdiShape(Hwnd, Hdc, Round ? GdiPrimitiveKind.Ellipse : GdiPrimitiveKind.Rectangle, + X, Y, X + OemGlyphSize, Y + OemGlyphSize, Border, 1, Interior); + + if (!Marked) + return true; + + if (Round) + { + Instance.WinHelper.EnqueueGdiShape(Hwnd, Hdc, GdiPrimitiveKind.Ellipse, + X + 4, Y + 4, X + OemGlyphSize - 4, Y + OemGlyphSize - 4, Mark, 1, Mark); + return true; + } + + Instance.WinHelper.EnqueueGdiLine(Hwnd, Hdc, X + 3, Y + 6, X + 5, Y + 9, Mark, 2); + Instance.WinHelper.EnqueueGdiLine(Hwnd, Hdc, X + 5, Y + 9, X + 10, Y + 3, Mark, 2); + return true; + } } } diff --git a/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs b/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs index b905413..0e44c29 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs @@ -1652,14 +1652,31 @@ public class WinWindow : IHandleObject public ulong WndProc; public ushort Fnid; + public int WindowExtraBytes; + public bool IsDialog; + public ulong DialogPointer; public bool Dirty = true; + public WinScrollBarInfo HorizontalScroll = WinScrollBarInfo.Default; + public WinScrollBarInfo VerticalScroll = WinScrollBarInfo.Default; + public string ObjectId => $"HWND_{Hwnd:X}"; public HandleType ObjectType => HandleType.Window; } + public struct WinScrollBarInfo + { + public int Minimum; + public int Maximum; + public uint Page; + public int Position; + public int TrackPosition; + + public static WinScrollBarInfo Default => new WinScrollBarInfo { Maximum = 100 }; + } + public sealed class WinIoCompletionEntry { public ulong KeyContext; diff --git a/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs b/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs index 37d7a0d..b406fc7 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs @@ -208,6 +208,7 @@ public void ClearWaitState(EmulatedThread Thread, bool ClearAlertByThreadId = fa State.ApcAlertable = false; State.MsgWaitActive = false; State.MsgWaitMask = 0; + State.WaitMessageActive = false; State.GetMessageWaitActive = false; State.IoCompletionWaitActive = false; State.IoCompletionHandle = 0; @@ -1301,6 +1302,7 @@ internal void RemoveWinHandle(ulong Handle) public readonly Dictionary WinWindowClassesByAtom = new(); private readonly Dictionary WinWindowClassAtomsByKey = new(StringComparer.OrdinalIgnoreCase); private ushort NextWindowClassAtom = 0xC000; + private readonly Dictionary ReservedSystemClassAtoms = new(); public readonly List TopLevelWindows = new(); private const uint UserHandleEntryCount = 0x200; private const uint UserHandleEntrySize = 0x20; @@ -1313,12 +1315,26 @@ internal void RemoveWinHandle(ulong Handle) private const int Win32ClientInfoPDeskInfoSlot = 2; private const int Win32ClientInfoDesktopSlot = 4; private const int Win32ClientInfoActiveWindowSlot = 8; + private const int Win32ClientInfoThreadInfoSlot = 35; + private const uint ClientThreadInfoSize = 0x40; private const int Win32ClientInfoActiveWindowPointerSlot = 9; private const ulong UserSharedInfoMirrorSize = 0x1B54; private const ulong UserServerInfoSize = 0x2000; private const ulong UserServerInfoWindowExtraOffset = 0x148; + private const ulong UserServerInfoSysClassAtomOffset = 0x364; + private const int UserServerInfoSysClassCount = 25; + + private const ulong UserServerInfoDpiBlockOffset = 104 * 49; + private const int UserServerInfoDpiBlockSize = 104; + private const int UserServerInfoDpiBlockCount = 18; + private const int UserServerInfoDpiCharWidthOffset = 32; + private const int UserServerInfoDpiCharHeightOffset = 36; + private const int UserServerInfoDpiTextMetricsOffset = 40; + private const int DpiPlateauBase = 72; + private const int DpiPlateauStep = 24; + private const string CharDimensionSample = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; private static readonly ushort[] UserServerInfoWindowExtraBytes = { 0x50, // Scrollbar. @@ -1340,9 +1356,40 @@ internal void RemoveWinHandle(ulong Handle) 0x08, // Ghost. }; + private const ulong UserServerInfoMessageFontOffset = 0x138C; + private const ulong UserServerInfoLogPixelsOffset = 0x1B56; + private const int LogFontSize = 92; + private const int LogFontFaceNameOffset = 28; + private const int LogFontFaceNameChars = 32; + private const int MessageFontPointSize = 9; + private const string MessageFontFace = "Segoe UI"; + + private const int MaxWindowAncestorDepth = 32; + + // user32 answers GetSysColor from the first array and GetSysColorBrush from the second. + private const ulong UserServerInfoSystemColorOffset = 0x11D8; + private const ulong UserServerInfoSystemBrushOffset = 0x1258; + private static readonly uint[] UserSystemColors = + { + 0x00C8C8C8, 0x00000000, 0x00D1B499, 0x00DBCDBF, 0x00F0F0F0, 0x00FFFFFF, 0x00646464, 0x00000000, + 0x00000000, 0x00000000, 0x00B4B4B4, 0x00FCF7F4, 0x00ABABAB, 0x00D77800, 0x00FFFFFF, 0x00F0F0F0, + 0x00A0A0A0, 0x006D6D6D, 0x00000000, 0x00544E43, 0x00FFFFFF, 0x00696969, 0x00E3E3E3, 0x00000000, + 0x00E1FFFF, 0x00CC6600, 0x00EAD1B9, 0x00F2E4D7, 0x00D77800, 0x00F0F0F0, 0x00F0F0F0, + }; + private bool SystemColorsPublished; + private readonly ulong[] SystemColorBrushes = new ulong[31]; private const ulong UserMessageBitmaskSize = 0x80; - private ulong UserMessageBitmask1Address; - private ulong UserMessageBitmask2Address; + + private const int UserSharedInfoMessageTablesOffset = 0x28; + private const int UserSharedInfoMessageTableSize = 0x10; + internal const int UserSharedInfoMessageTableCount = Win32kHelper.FnidCount + 2; + internal const uint UserMessageBitmaskLastMessage = 0x3FF; + + internal static int UserSharedInfoMessageTableOffset(int Index) + { + return UserSharedInfoMessageTablesOffset + Index * UserSharedInfoMessageTableSize; + } + private ulong UserMessageBitmaskAddress; private const uint GdiHandleEntryCount = 0x1000; private const uint GdiHandleEntrySize = 0x18; @@ -2872,6 +2919,79 @@ public ulong GetKernelCallbackEntry(uint Index) : Emulator.ReadMemoryUInt(Table + (ulong)Index * 4); } + public bool EnterUserCallback(ulong Callback, uint CallbackIndex, ulong ArgumentBuffer, WinWindowCreation Creation, ulong SyscallRetryRip = 0) + { + EmulatedThread Thread = Emulator.CurrentThread; + if (Thread == null || Callback == 0 || PointerSize != 8) + return false; + + ulong CurrentRsp = Emulator.ReadRegister(Registers.UC_X86_REG_RSP); + if (!Emulator.IsRegionMapped(CurrentRsp, 8)) + return false; + + WinUserCallbackFrame Frame = new WinUserCallbackFrame + { + SavedRsp = CurrentRsp, + SavedReturnAddress = Emulator.ReadMemoryULong(CurrentRsp), + SyscallRetryRip = SyscallRetryRip, + WindowCreation = Creation, + }; + + if (SyscallRetryRip != 0) + { + Frame.SavedSyscallNumber = Emulator.ReadRegister(Registers.UC_X86_REG_RAX); + Frame.SavedArg0 = Emulator.ReadRegister(Registers.UC_X86_REG_R10); + Frame.SavedArg1 = Emulator.ReadRegister(Registers.UC_X86_REG_RDX); + Frame.SavedArg2 = Emulator.ReadRegister(Registers.UC_X86_REG_R8); + Frame.SavedArg3 = Emulator.ReadRegister(Registers.UC_X86_REG_R9); + } + + WinEmulatedThread.GetState(Thread).UserCallbackFrames.Push(Frame); + + ulong DispatcherRsp = (ArgumentBuffer - 0x80) & ~0xFUL; + for (ulong i = DispatcherRsp; i < ArgumentBuffer; i += 8) + Emulator._emulator.WriteMemory(i, 0UL, 8); + + Emulator._emulator.WriteMemory(DispatcherRsp + 0x20, ArgumentBuffer, 8); + Emulator._emulator.WriteMemory(DispatcherRsp + 0x28, 0u, 4); + Emulator._emulator.WriteMemory(DispatcherRsp + 0x2C, CallbackIndex, 4); + + Emulator.WriteRegister(Registers.UC_X86_REG_RCX, ArgumentBuffer); + Emulator.WriteRegister(Registers.UC_X86_REG_RSP, DispatcherRsp - 8); + Emulator.WriteRegister(Emulator.IPRegister, Callback - 2); + Emulator.SuppressSyscallStatusWrite = true; + return true; + } + + private const ulong UserServerInfoClientProcsAnsiOffset = 0x188; + private const ulong UserServerInfoClientProcsUnicodeOffset = 0x248; + private const ulong UserServerInfoClientProcsWorkerOffset = 0x308; + private const int ClientPfnTableBytes = 192; + + // apfnClientWorker holds 11 procedures and ends at cbHandleTable, right below atomSysClass at 0x364. + private const int ClientPfnWorkerBytes = 88; + + public void PublishClientPfnArrays(ulong Ansi, ulong Unicode, ulong Worker) + { + ulong ServerInfo = EnsureUserServerInfo(); + if (ServerInfo == 0 || Unicode == 0) + return; + + CopyGuestBytes(Ansi, ServerInfo + UserServerInfoClientProcsAnsiOffset, ClientPfnTableBytes); + CopyGuestBytes(Unicode, ServerInfo + UserServerInfoClientProcsUnicodeOffset, ClientPfnTableBytes); + CopyGuestBytes(Worker, ServerInfo + UserServerInfoClientProcsWorkerOffset, ClientPfnWorkerBytes); + } + + private void CopyGuestBytes(ulong From, ulong To, int Length) + { + if (From == 0 || !Emulator.IsRegionMapped(From, (ulong)Length)) + return; + + byte[] Data = Emulator.ReadMemory(From, (uint)Length); + if (Data != null && Data.Length == Length) + Emulator.WriteMemory(To, Data); + } + public bool InvokeUserCallback(ulong CallbackIndex, ulong ArgumentBuffer, ulong ArgumentBufferSize) { ulong Dispatcher = GetKiUserCallbackDispatcher(); @@ -3091,13 +3211,87 @@ public bool CompleteUserCallback(ulong ResultAddress, uint ResultLength) if (ResultAddress != 0 && ResultLength >= 8 && Emulator.IsRegionMapped(ResultAddress, 8)) ResultValue = Emulator.ReadMemoryULong(ResultAddress); + if (Frame.SyscallRetryRip != 0) + { + Emulator.WriteRegister(Registers.UC_X86_REG_RSP, Frame.SavedRsp); + Emulator.WriteRegister(Registers.UC_X86_REG_RAX, Frame.SavedSyscallNumber); + Emulator.WriteRegister(Registers.UC_X86_REG_R10, Frame.SavedArg0); + Emulator.WriteRegister(Registers.UC_X86_REG_RDX, Frame.SavedArg1); + Emulator.WriteRegister(Registers.UC_X86_REG_R8, Frame.SavedArg2); + Emulator.WriteRegister(Registers.UC_X86_REG_R9, Frame.SavedArg3); + Emulator.WriteRegister(Emulator.IPRegister, Frame.SyscallRetryRip - 2); + Emulator.SuppressSyscallStatusWrite = true; + return true; + } + + if (Frame.WindowCreation != null && ContinueWindowCreation(Frame, ResultValue, out ResultValue)) + return true; + + ReturnFromUserCallback(Frame, ResultValue); + return true; + } + + private void ReturnFromUserCallback(WinUserCallbackFrame Frame, ulong ResultValue) + { Emulator.WriteRegister(Registers.UC_X86_REG_RSP, Frame.SavedRsp + 8); Emulator.WriteRegister(Emulator.IPRegister, Frame.SavedReturnAddress - 2); Emulator.WriteRegister(Registers.UC_X86_REG_RAX, ResultValue); Emulator.SuppressSyscallStatusWrite = true; - return true; } + // A window procedure refuses the window with FALSE from WM_NCCREATE or -1 from WM_CREATE, and the + // caller gets NULL. + private bool ContinueWindowCreation(WinUserCallbackFrame Frame, ulong Answer, out ulong Result) + { + const uint SizeRestored = 0; + + WinWindowCreation Creation = Frame.WindowCreation; + WinWindow Window = GetWindow(Creation.Hwnd); + + bool Refused = Creation.Step switch + { + WinWindowCreationStep.NonClientCreate => Answer == 0, + WinWindowCreationStep.Create => (long)Answer == -1, + _ => false, + }; + + if (Window == null || Refused) + { + if (Window != null) + DestroyWindow(Creation.Hwnd); + + Result = 0; + return false; + } + + Result = Creation.Hwnd; + + if (Creation.Step == WinWindowCreationStep.Move) + return false; + + Creation.Step++; + + // The callback runs on the caller's stack, so put it back where the syscall left it first. + Emulator.WriteRegister(Registers.UC_X86_REG_RSP, Frame.SavedRsp); + + if (Creation.Step == WinWindowCreationStep.Create) + return Win32kHelper.SendWindowCreateMessage(Emulator, Window, Win32kHelper.WM_CREATE, Creation); + + // A control sizes itself from WM_SIZE, not from the CREATESTRUCT. + if (Creation.Step == WinWindowCreationStep.Size) + return Win32kHelper.InvokeWindowProc(Emulator, Window.Hwnd, Window.WndProc, Win32kHelper.WM_SIZE, + SizeRestored, PackCoordinates((int)Window.Width, (int)Window.Height), Creation); + + return Win32kHelper.InvokeWindowProc(Emulator, Window.Hwnd, Window.WndProc, Win32kHelper.WM_MOVE, + 0, PackCoordinates(Window.X, Window.Y), Creation); + } + + private static ulong PackCoordinates(int Low, int High) + { + return (ulong)(uint)((Low & 0xFFFF) | (High << 16)); + } + + /// /// Convert windows memory protection to the internal enum. /// @@ -3258,10 +3452,19 @@ public ulong AllocateUserHandle() { EnsureUserSharedInfo(out _, out _, out _); - if (NextUserHandleIndex == 0 || NextUserHandleIndex >= UserHandleEntryCount) - return 0; + ushort Index; + if (FreeUserHandleIndexes.Count > 0) + { + Index = FreeUserHandleIndexes.Dequeue(); + } + else + { + if (NextUserHandleIndex == 0 || NextUserHandleIndex >= UserHandleEntryCount) + return 0; + + Index = NextUserHandleIndex++; + } - ushort Index = NextUserHandleIndex++; ushort Uniq = NextUserHandleUniq++; if (NextUserHandleUniq == 0 || NextUserHandleUniq >= 0x7FFF) @@ -3270,14 +3473,34 @@ public ulong AllocateUserHandle() return ((ulong)Uniq << 16) | Index; } - public bool EnsureUserMessageBitmasks(out ulong Bitmask1, out ulong Bitmask2) + // The uniq half moves on, so a handle the guest kept never names the window that takes the slot next. + private readonly Queue FreeUserHandleIndexes = new(); + + internal void ReleaseUserHandle(ulong Handle) { - Bitmask1 = EnsureUserMessageBitmask(ref UserMessageBitmask1Address); - Bitmask2 = EnsureUserMessageBitmask(ref UserMessageBitmask2Address); - return Bitmask1 != 0 && Bitmask2 != 0; + ushort Index = (ushort)(Handle & 0xFFFF); + if (Index != 0 && Index < UserHandleEntryCount) + FreeUserHandleIndexes.Enqueue(Index); } - private ulong EnsureUserMessageBitmask(ref ulong Cached) + private ulong ThreadDesktopHandle; + + // user32 only tests this for zero, but a zero answer makes it fall back to a display DC of its own. + public ulong EnsureThreadDesktopHandle() + { + if (ThreadDesktopHandle == 0) + ThreadDesktopHandle = AllocateUserHandle(); + + return ThreadDesktopHandle; + } + + public bool EnsureUserMessageBitmask(out ulong Bitmask) + { + Bitmask = EnsureUserMessageBitmaskPage(ref UserMessageBitmaskAddress); + return Bitmask != 0; + } + + private ulong EnsureUserMessageBitmaskPage(ref ulong Cached) { if (Cached != 0 && Emulator.IsRegionMapped(Cached, UserMessageBitmaskSize)) return Cached; @@ -3315,7 +3538,7 @@ public bool EnsureUserSharedInfo(out ulong ServerInfo, out ulong HandleTable, ou ServerInfo = EnsureUserServerInfo(); HandleTable = EnsureUserHandleTable(); EntrySize = UserHandleEntrySize; - EnsureUserMessageBitmasks(out _, out _); + EnsureUserMessageBitmask(out _); EnsureGdiHandleTable(); return ServerInfo != 0 && HandleTable != 0; @@ -3408,8 +3631,12 @@ private static ulong DecryptGdiPointer(ulong Encrypted) private const int DcAttrSelectedBrushOffset = 0xA0; private const int DcAttrSelectedPenOffset = 0xA8; + private const int DcAttrViewportOrgXOffset = 0x144; + private const int DcAttrViewportOrgYOffset = 0x148; private const int DcAttrCurrentPosXOffset = 0xD8; private const int DcAttrCurrentPosYOffset = 0xDC; + private const int DcAttrBrushOriginXOffset = 0x158; + private const int DcAttrBrushOriginYOffset = 0x15C; public ulong GetDcAttributeAddress(ulong Hdc) { @@ -3452,6 +3679,44 @@ public ulong ReadDcSelectedPen(ulong Hdc) return DcAttr == 0 ? 0 : Emulator.ReadMemoryULong(DcAttr + DcAttrSelectedPenOffset); } + public void ReadDcViewportOrigin(ulong Hdc, out int X, out int Y) + { + X = 0; + Y = 0; + + ulong DcAttr = GetDcAttributeAddress(Hdc); + if (DcAttr == 0) + return; + + X = unchecked((int)Emulator.ReadMemoryUInt(DcAttr + DcAttrViewportOrgXOffset)); + Y = unchecked((int)Emulator.ReadMemoryUInt(DcAttr + DcAttrViewportOrgYOffset)); + } + + // gdi32 reads this back out of DC_ATTR and only calls the kernel when it cannot batch the change. + public bool ReadDcBrushOrigin(ulong Hdc, out int X, out int Y) + { + X = 0; + Y = 0; + + ulong DcAttr = GetDcAttributeAddress(Hdc); + if (DcAttr == 0) + return false; + + X = unchecked((int)Emulator.ReadMemoryUInt(DcAttr + DcAttrBrushOriginXOffset)); + Y = unchecked((int)Emulator.ReadMemoryUInt(DcAttr + DcAttrBrushOriginYOffset)); + return true; + } + + public void WriteDcBrushOrigin(ulong Hdc, int X, int Y) + { + ulong DcAttr = GetDcAttributeAddress(Hdc); + if (DcAttr == 0) + return; + + Emulator._emulator.WriteMemory(DcAttr + DcAttrBrushOriginXOffset, unchecked((uint)X), 4); + Emulator._emulator.WriteMemory(DcAttr + DcAttrBrushOriginYOffset, unchecked((uint)Y), 4); + } + public ulong ReadDcSelectedBrush(ulong Hdc) { ulong DcAttr = GetDcAttributeAddress(Hdc); @@ -3566,9 +3831,10 @@ public void FlushGdiBatch() ulong Hwnd = Win32kHelper.GetHwndFromDc(Emulator, BatchHdc); if (Hwnd != 0) { - int FinalX = X + VpOrgX; - int FinalY = Y + VpOrgY; - EnqueueTextRender(Hwnd, Text, FinalX, FinalY, RectLeft, RectTop, RectRight, RectBottom, Options); + // The record carries the origin the batched call drew through, not the one + // the DC holds now. + EnqueueTextRender(Hwnd, 0, Text, X + VpOrgX, Y + VpOrgY, + RectLeft + VpOrgX, RectTop + VpOrgY, RectRight + VpOrgX, RectBottom + VpOrgY, Options); } } } @@ -3625,78 +3891,138 @@ public ulong GetHwndFromDc(ulong Hdc) return Win32kHelper.GetHwndFromDc(Emulator, Hdc); } - public void EnqueueTextRender(ulong Hwnd, string text, int x, int y, int rectLeft, int rectTop, int rectRight, int rectBottom, uint options) + // Every primitive lands on the one host surface, and a control draws in its own client coordinates. + public void GetSurfaceOrigin(ulong Hwnd, out int OffsetX, out int OffsetY) { - if (DesktopDisplay is GuiThreadManager guiManager) - guiManager.EnqueueTextRender(Hwnd, text, x, y, rectLeft, rectTop, rectRight, rectBottom, options); + OffsetX = 0; + OffsetY = 0; + + for (int Depth = 0; Depth < MaxWindowAncestorDepth; Depth++) + { + if (!WinWindows.TryGetValue(Hwnd, out WinWindow Window) || Window.ParentHwnd == 0) + return; + + OffsetX += Window.X; + OffsetY += Window.Y; + Hwnd = Window.ParentHwnd; + } } - public void EnqueueGdiLine(ulong Hwnd, int X1, int Y1, int X2, int Y2, uint PenColor, int PenWidth) + public void GetDcSurfaceOrigin(ulong Hwnd, ulong Hdc, out int OffsetX, out int OffsetY) { - if (DesktopDisplay is GuiThreadManager guiManager) - guiManager.EnqueueGdiPrimitive(new GdiPrimitive - { - Hwnd = Hwnd, - Kind = GdiPrimitiveKind.Line, - X1 = X1, - Y1 = Y1, - X2 = X2, - Y2 = Y2, - Pen = new GdiPenDescriptor { ColorRef = PenColor, Width = PenWidth }, - HasPen = true, - }); + GetSurfaceOrigin(Hwnd, out OffsetX, out OffsetY); + + if (Hdc == 0) + return; + + ReadDcViewportOrigin(Hdc, out int ViewportX, out int ViewportY); + OffsetX += ViewportX; + OffsetY += ViewportY; } - public void EnqueueGdiFillRect(ulong Hwnd, int Left, int Top, int Right, int Bottom, uint BrushColor, uint Rop) + public void EnqueueTextRender(ulong Hwnd, ulong Hdc, string text, int x, int y, int rectLeft, int rectTop, int rectRight, int rectBottom, uint options) { - if (DesktopDisplay is GuiThreadManager guiManager) - guiManager.EnqueueGdiPrimitive(new GdiPrimitive - { - Hwnd = Hwnd, - Kind = GdiPrimitiveKind.FillRect, - X1 = Left, - Y1 = Top, - X2 = Right, - Y2 = Bottom, - Rop = Rop, - Brush = new GdiBrushDescriptor { ColorRef = BrushColor }, - HasBrush = true, - }); + if (DesktopDisplay is not GuiThreadManager guiManager) + return; + + GetDcSurfaceOrigin(Hwnd, Hdc, out int SurfaceX, out int SurfaceY); + + guiManager.EnqueueTextRender(Hwnd, text, x + SurfaceX, y + SurfaceY, + rectLeft + SurfaceX, rectTop + SurfaceY, rectRight + SurfaceX, rectBottom + SurfaceY, options); } - public void EnqueueGdiShape(ulong Hwnd, GdiPrimitiveKind Kind, int Left, int Top, int Right, int Bottom, uint PenColor, int PenWidth, uint BrushColor, int RoundedWidth = 0, int RoundedHeight = 0) + public void EnqueueGdiLine(ulong Hwnd, ulong Hdc, int X1, int Y1, int X2, int Y2, uint PenColor, int PenWidth) { - if (DesktopDisplay is GuiThreadManager guiManager) - guiManager.EnqueueGdiPrimitive(new GdiPrimitive - { - Hwnd = Hwnd, - Kind = Kind, - X1 = Left, - Y1 = Top, - X2 = Right, - Y2 = Bottom, - RoundedWidth = RoundedWidth, - RoundedHeight = RoundedHeight, - Pen = new GdiPenDescriptor { ColorRef = PenColor, Width = PenWidth }, - Brush = new GdiBrushDescriptor { ColorRef = BrushColor }, - HasPen = true, - HasBrush = true, - }); + if (DesktopDisplay is not GuiThreadManager guiManager) + return; + + GetDcSurfaceOrigin(Hwnd, Hdc, out int SurfaceX, out int SurfaceY); + + guiManager.EnqueueGdiPrimitive(new GdiPrimitive + { + Hwnd = Hwnd, + Kind = GdiPrimitiveKind.Line, + X1 = X1 + SurfaceX, + Y1 = Y1 + SurfaceY, + X2 = X2 + SurfaceX, + Y2 = Y2 + SurfaceY, + Pen = new GdiPenDescriptor { ColorRef = PenColor, Width = PenWidth }, + HasPen = true, + }); } - public void EnqueueGdiPoly(ulong Hwnd, GdiPrimitiveKind Kind, GdiPoint[] Points, uint PenColor, int PenWidth, uint BrushColor, bool HasBrush) + public void EnqueueGdiFillRect(ulong Hwnd, ulong Hdc, int Left, int Top, int Right, int Bottom, uint BrushColor, uint Rop) { - if (DesktopDisplay is GuiThreadManager guiManager) - guiManager.EnqueueGdiPrimitive(new GdiPrimitive - { - Hwnd = Hwnd, - Kind = Kind, - Points = Points, - Pen = new GdiPenDescriptor { ColorRef = PenColor, Width = PenWidth }, - Brush = new GdiBrushDescriptor { ColorRef = BrushColor }, - HasPen = true, - HasBrush = HasBrush, - }); + if (DesktopDisplay is not GuiThreadManager guiManager) + return; + + GetDcSurfaceOrigin(Hwnd, Hdc, out int SurfaceX, out int SurfaceY); + + guiManager.EnqueueGdiPrimitive(new GdiPrimitive + { + Hwnd = Hwnd, + Kind = GdiPrimitiveKind.FillRect, + X1 = Left + SurfaceX, + Y1 = Top + SurfaceY, + X2 = Right + SurfaceX, + Y2 = Bottom + SurfaceY, + Rop = Rop, + Brush = new GdiBrushDescriptor { ColorRef = BrushColor }, + HasBrush = true, + }); + } + + public void EnqueueGdiShape(ulong Hwnd, ulong Hdc, GdiPrimitiveKind Kind, int Left, int Top, int Right, int Bottom, uint PenColor, int PenWidth, uint BrushColor, int RoundedWidth = 0, int RoundedHeight = 0) + { + if (DesktopDisplay is not GuiThreadManager guiManager) + return; + + GetDcSurfaceOrigin(Hwnd, Hdc, out int SurfaceX, out int SurfaceY); + + guiManager.EnqueueGdiPrimitive(new GdiPrimitive + { + Hwnd = Hwnd, + Kind = Kind, + X1 = Left + SurfaceX, + Y1 = Top + SurfaceY, + X2 = Right + SurfaceX, + Y2 = Bottom + SurfaceY, + RoundedWidth = RoundedWidth, + RoundedHeight = RoundedHeight, + Pen = new GdiPenDescriptor { ColorRef = PenColor, Width = PenWidth }, + Brush = new GdiBrushDescriptor { ColorRef = BrushColor }, + HasPen = true, + HasBrush = true, + }); + } + + public void EnqueueGdiPoly(ulong Hwnd, ulong Hdc, GdiPrimitiveKind Kind, GdiPoint[] Points, uint PenColor, int PenWidth, uint BrushColor, bool HasBrush) + { + if (DesktopDisplay is not GuiThreadManager guiManager) + return; + + GetDcSurfaceOrigin(Hwnd, Hdc, out int SurfaceX, out int SurfaceY); + + // The shifted figure is the primitive's own, so a caller reusing its array is not moved with it. + if (Points != null && (SurfaceX != 0 || SurfaceY != 0)) + { + GdiPoint[] Shifted = new GdiPoint[Points.Length]; + for (int i = 0; i < Points.Length; i++) + Shifted[i] = new GdiPoint { X = Points[i].X + SurfaceX, Y = Points[i].Y + SurfaceY }; + + Points = Shifted; + } + + guiManager.EnqueueGdiPrimitive(new GdiPrimitive + { + Hwnd = Hwnd, + Kind = Kind, + Points = Points, + Pen = new GdiPenDescriptor { ColorRef = PenColor, Width = PenWidth }, + Brush = new GdiBrushDescriptor { ColorRef = BrushColor }, + HasPen = true, + HasBrush = HasBrush, + }); } public bool TranslateVirtualKey(uint VirtualKey, uint ScanCode, out char Character) @@ -3794,10 +4120,47 @@ private ulong EnsureUserServerInfo() for (int i = 0; i < UserServerInfoWindowExtraBytes.Length; i++) Emulator._emulator.WriteMemory(Address + UserServerInfoWindowExtraOffset + (ulong)(i * 2), UserServerInfoWindowExtraBytes[i], 2); + uint SystemDpi = HostDisplayMetrics.SystemDpi; + Emulator._emulator.WriteMemory(Address + UserServerInfoLogPixelsOffset, SystemDpi, 2); + WriteUserMessageFont(Address + UserServerInfoMessageFontOffset, SystemDpi); + UserServerInfoAddress = Address; + ReserveSystemClassAtoms(); return UserServerInfoAddress; } + private void ReserveSystemClassAtoms() + { + foreach ((ushort FunctionId, int Index, ushort WellKnownAtom, string Name) in Win32kHelper.ReservedClasses) + { + if (ReservedSystemClassAtoms.ContainsKey(FunctionId)) + continue; + + ushort Atom = WellKnownAtom != 0 ? WellKnownAtom : NextWindowClassAtom++; + ReservedSystemClassAtoms[FunctionId] = Atom; + + PublishSystemClassAtom(Index, Atom); + } + } + + private void WriteUserMessageFont(ulong Address, uint Dpi) + { + Span Font = stackalloc byte[LogFontSize]; + Font.Clear(); + + BinaryPrimitives.WriteInt32LittleEndian(Font, -(int)((MessageFontPointSize * Dpi) / 72)); + BinaryPrimitives.WriteInt32LittleEndian(Font.Slice(16), 400); // FW_NORMAL + Font[23] = 1; // DEFAULT_CHARSET + Font[26] = 5; // CLEARTYPE_QUALITY + + Span Face = stackalloc char[LogFontFaceNameChars]; + Face.Clear(); + MessageFontFace.AsSpan().CopyTo(Face); + Encoding.Unicode.GetBytes(Face, Font.Slice(LogFontFaceNameOffset)); + + Emulator.WriteMemory(Address, Font); + } + private ulong EnsureUserHandleTable() { ulong TableSize = UserHandleEntryCount * UserHandleEntrySize; @@ -3834,6 +4197,20 @@ public void EnsureUserClientThreadInfo(EmulatedThread Thread, ulong ThreadInfo) WriteWin32ClientInfoSlot(State, Win32ClientInfoPDeskInfoSlot, DesktopInfo); WriteWin32ClientInfoSlot(State, Win32ClientInfoDesktopSlot, EnsureUserClientDesktop()); + + // The wake bits a message pump reads without a syscall. Windows fills this from a client callback + // the first time a thread enters USER. The block is the thread's either way, so it is published + // with the rest of CLIENTINFO instead. + if (State.ClientThreadInfo == 0) + { + ulong Block = Emulator.MapUniqueAddress(ClientThreadInfoSize, MemoryProtection.ReadWrite); + if (Block == 0 || !WriteZeroMemory(Block, ClientThreadInfoSize)) + return; + + State.ClientThreadInfo = Block; + } + + WriteWin32ClientInfoSlot(State, Win32ClientInfoThreadInfoSlot, State.ClientThreadInfo); } public void SetThreadWindowContext(WinWindow Window) @@ -4180,17 +4557,19 @@ public ulong GetUserWindowClientAddress(WinWindow Window) private ulong EnsureUserWindowObject(WinWindow Window) { - if (Window.ClientWindowAddress != 0 && Emulator.IsRegionMapped(Window.ClientWindowAddress, UserWindowObjectSize)) + ulong TotalSize = UserWindowObjectSize + WindowExtraByteCount(Window); + + if (Window.ClientWindowAddress != 0 && Emulator.IsRegionMapped(Window.ClientWindowAddress, TotalSize)) { RefreshUserWindowObject(Window); return Window.ClientWindowAddress; } - ulong Address = Emulator.MapUniqueAddress(UserWindowObjectSize, MemoryProtection.ReadWrite); + ulong Address = Emulator.MapUniqueAddress(TotalSize, MemoryProtection.ReadWrite); if (Address == 0) return 0; - if (!WriteZeroMemory(Address, (uint)UserWindowObjectSize)) + if (!WriteZeroMemory(Address, (uint)TotalSize)) return 0; Window.ClientWindowAddress = Address; @@ -4198,13 +4577,67 @@ private ulong EnsureUserWindowObject(WinWindow Window) return Window.ClientWindowAddress; } + private ulong FirstChildObject(WinWindow Window) + { + for (int i = 0; i < Window.Children.Count; i++) + { + if (WinWindows.TryGetValue(Window.Children[i], out WinWindow Child) && Child.ClientWindowAddress != 0) + return Child.ClientWindowAddress; + } + + return 0; + } + + private ulong NextSiblingObject(WinWindow Window) + { + if (Window.ParentHwnd == 0 || !WinWindows.TryGetValue(Window.ParentHwnd, out WinWindow Parent)) + return 0; + + int At = Parent.Children.IndexOf(Window.Hwnd); + if (At < 0) + return 0; + + for (int i = At + 1; i < Parent.Children.Count; i++) + { + if (WinWindows.TryGetValue(Parent.Children[i], out WinWindow Sibling) && Sibling.ClientWindowAddress != 0) + return Sibling.ClientWindowAddress; + } + + return 0; + } + + private static ulong WindowExtraByteCount(WinWindow Window) + { + int Requested = Window.WindowExtraBytes; + if (Requested <= 0) + return 0; + + return ((ulong)Requested + 7) & ~7UL; + } + + // user32 reaches this through the pointer at pwnd+0x128 for every non-negative GetWindowLongPtr index. + public ulong GetWindowExtraBytesAddress(WinWindow Window) + { + if (Window == null || Window.ClientWindowAddress == 0 || Window.WindowExtraBytes <= 0) + return 0; + + return Window.ClientWindowAddress + UserWindowObjectSize; + } + private const int UserWindowExStyleOffset = 0x18; private const int UserWindowStyleOffset = 0x1C; private const int UserWindowFnidOffset = 0x2A; private const int UserWindowParentOffset = 0x30; - private const uint UserWindowStateVisible = 0x800; + private const int UserWindowExtraBytesOffset = 0xC8; + private const int UserWindowExtraPointerOffset = 0x128; + private const int UserWindowChildOffset = 0x38; + private const int UserWindowNextOffset = 0x48; + private const int UserWindowIdOffset = 0x140; + private const int UserWindowStateOffset = 0x12; + private const byte UserWindowStateDialog = 0x01; + internal const uint UserWindowStateVisible = 0x800; private const ushort UserFnidDesktop = 0x29D; - private const uint UserWindowStyleVisible = 0x10000000; + internal const uint UserWindowStyleVisible = 0x10000000; private const uint UserWindowStyleClipChildren = 0x02000000; private ulong UserDesktopWindowAddress; @@ -4266,6 +4699,11 @@ private void RefreshUserWindowObject(WinWindow Window) Emulator._emulator.WriteMemory(Window.ClientWindowAddress + (ulong)UserWindowFnidOffset, Window.Fnid, 2); Emulator._emulator.WriteMemory(Window.ClientWindowAddress + (ulong)UserWindowParentOffset, ParentObject, 8); + // GetDlgItem, EnumChildWindows and the dialog tab order walk this list client-side. + Emulator._emulator.WriteMemory(Window.ClientWindowAddress + (ulong)UserWindowChildOffset, FirstChildObject(Window), 8); + Emulator._emulator.WriteMemory(Window.ClientWindowAddress + (ulong)UserWindowNextOffset, NextSiblingObject(Window), 8); + Emulator._emulator.WriteMemory(Window.ClientWindowAddress + (ulong)UserWindowIdOffset, (uint)Window.MenuHandle, 4); + int OuterLeft = Window.X; int OuterTop = Window.Y; int OuterWidth = (int)Window.Width; @@ -4289,6 +4727,15 @@ private void RefreshUserWindowObject(WinWindow Window) Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0x78, Window.WndProc, 8); Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0x80, ClassObject, 8); + // user32 re-runs its own dialog setup while this bit reads clear, throwing away what EndDialog + // recorded. + ulong StateAddress = Window.ClientWindowAddress + (ulong)UserWindowStateOffset; + byte State = (byte)Emulator.ReadMemoryUInt(StateAddress); + State = Window.IsDialog ? (byte)(State | UserWindowStateDialog) : (byte)(State & ~UserWindowStateDialog); + Emulator._emulator.WriteMemory(StateAddress, State, 1); + + Emulator._emulator.WriteMemory(Window.ClientWindowAddress + (ulong)UserWindowExtraBytesOffset, (uint)Window.WindowExtraBytes, 4); + Emulator._emulator.WriteMemory(Window.ClientWindowAddress + (ulong)UserWindowExtraPointerOffset, GetWindowExtraBytesAddress(Window), 8); Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0xB8, Window.ClientTextBytes, 4); Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0xC0, TextObject, 8); Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0xE0, 0UL, 8); @@ -4530,6 +4977,94 @@ public bool TryGetAtomName(ushort Atom, out string Name) return false; } + // The character dimensions user32 converts dialog units with, so they have to describe the font + // Brovan draws with. False while the host font cannot be measured, leaving it for a later try. + public bool PublishDpiServerInfo() + { + ulong ServerInfo = EnsureUserServerInfo(); + if (ServerInfo == 0) + return false; + + if (!MeasureText(CharDimensionSample, out int SampleWidth, out int SampleHeight) || SampleWidth <= 0 || SampleHeight <= 0) + return false; + + int CharWidth = ((SampleWidth / 26) + 1) / 2; + if (CharWidth <= 0) + return false; + + // An edit control takes its font description from here, and reads a zeroed one as no height. + if (!GetTextMetrics(out TextMetricsData Metrics)) + Metrics = Win32kHelper.DefaultTextMetrics; + + Span Buffer = Shared.GetSpan(Win32kHelper.TextMetricWSize).Slice(0, Win32kHelper.TextMetricWSize); + Win32kHelper.WriteTextMetricsW(Buffer, Metrics); + + // user32 indexes the blocks by the DPI the calling thread believes in, not the one the host runs + // at. Brovan draws with one host font at one size, so every block answers the same. + for (int Index = 0; Index < UserServerInfoDpiBlockCount; Index++) + { + ulong Block = ServerInfo + UserServerInfoDpiBlockOffset + (ulong)(Index * UserServerInfoDpiBlockSize); + Emulator._emulator.WriteMemory(Block + UserServerInfoDpiCharWidthOffset, (uint)CharWidth, 4); + Emulator._emulator.WriteMemory(Block + UserServerInfoDpiCharHeightOffset, (uint)SampleHeight, 4); + Emulator.WriteMemory(Block + UserServerInfoDpiTextMetricsOffset, Buffer); + } + + return true; + } + + public void PublishSystemClassAtom(int Icls, ushort Atom) + { + if (Icls < 0 || Icls >= UserServerInfoSysClassCount) + return; + + ulong ServerInfo = EnsureUserServerInfo(); + if (ServerInfo == 0) + return; + + Emulator._emulator.WriteMemory(ServerInfo + UserServerInfoSysClassAtomOffset + (ulong)(Icls * 2), Atom, 2); + } + + // The brushes need the GDI handle table, which the guest builds on its own schedule. + public void PublishSystemColors() + { + if (SystemColorsPublished) + return; + + ulong ServerInfo = EnsureUserServerInfo(); + if (ServerInfo == 0) + return; + + bool Complete = true; + + for (int i = 0; i < UserSystemColors.Length; i++) + { + Emulator._emulator.WriteMemory(ServerInfo + UserServerInfoSystemColorOffset + (ulong)(i * 4), UserSystemColors[i], 4); + + if (SystemColorBrushes[i] == 0) + SystemColorBrushes[i] = Win32kHelper.CreateSolidBrush(Emulator, UserSystemColors[i]); + + Complete &= SystemColorBrushes[i] != 0; + Emulator._emulator.WriteMemory(ServerInfo + UserServerInfoSystemBrushOffset + (ulong)(i * 8), SystemColorBrushes[i], 8); + } + + SystemColorsPublished = Complete; + } + + public uint GetSystemColor(int Index) + { + return Index >= 0 && Index < UserSystemColors.Length ? UserSystemColors[Index] : 0; + } + + public ulong GetSystemColorBrush(int Index) + { + PublishSystemColors(); + + if (Index < 0 || Index >= SystemColorBrushes.Length) + return 0; + + return SystemColorBrushes[Index]; + } + public WinWindowClass RegisterWindowClass(WinWindowClass WindowClass) { if (WindowClass == null || string.IsNullOrEmpty(WindowClass.Name)) @@ -4539,16 +5074,61 @@ public WinWindowClass RegisterWindowClass(WinWindowClass WindowClass) if (WinWindowClassAtomsByKey.TryGetValue(Key, out ushort ExistingAtom)) return WinWindowClassesByAtom.TryGetValue(ExistingAtom, out WinWindowClass ExistingClass) ? ExistingClass : null; - ushort Atom = NextWindowClassAtom++; - if (NextWindowClassAtom < 0xC000) - NextWindowClassAtom = 0xC000; + // A class named "#nnnnn" keeps nnnnn as its atom, which is how a dialog template names the dialog + // class by the bare atom 32770. An atom another class already answers for is not free. + if (!TryReserveSystemClassAtom(WindowClass.FunctionId, out ushort Atom) && + !TryTakeIntegerAtomName(WindowClass.Name, out Atom)) + { + Atom = NextWindowClassAtom++; + if (NextWindowClassAtom < 0xC000) + NextWindowClassAtom = 0xC000; + } WindowClass.Atom = Atom; WinWindowClassesByAtom[Atom] = WindowClass; WinWindowClassAtomsByKey[Key] = Atom; + + if (Win32kHelper.TryGetSystemClassIndex(WindowClass.FunctionId, out int SystemClassIndex)) + PublishSystemClassAtom(SystemClassIndex, Atom); + return WindowClass; } + // Lands the registration on the value the ICLS slot has been answering with. + private bool TryReserveSystemClassAtom(uint FunctionId, out ushort Atom) + { + Atom = 0; + + ushort Fnid = Win32kHelper.MaskFunctionId(FunctionId); + if (!ReservedSystemClassAtoms.TryGetValue(Fnid, out ushort Reserved)) + return false; + + if (WinWindowClassesByAtom.ContainsKey(Reserved)) + return false; + + Atom = Reserved; + return true; + } + + private bool TryTakeIntegerAtomName(string Name, out ushort Atom) + { + return TryParseIntegerAtomName(Name, out Atom) && !WinWindowClassesByAtom.ContainsKey(Atom); + } + + private static bool TryParseIntegerAtomName(string Name, out ushort Atom) + { + Atom = 0; + + if (Name == null || Name.Length < 2 || Name[0] != '#') + return false; + + if (!uint.TryParse(Name.AsSpan(1), out uint Value) || Value == 0 || Value > 0xBFFF) + return false; + + Atom = (ushort)Value; + return true; + } + public WinWindowClass GetWindowClass(ulong InstanceHandle, string Name, string Version) { if (string.IsNullOrEmpty(Name)) @@ -4585,6 +5165,75 @@ private static string BuildWindowClassKey(ulong InstanceHandle, string Name, str return $"{InstanceHandle:X}:{Name ?? string.Empty}:{Version ?? string.Empty}"; } + public bool ReparentWindow(WinWindow Window, WinWindow NewParent) + { + if (Window == null || IsWindowUnder(Window, NewParent)) + return false; + + if (Window.ParentHwnd != 0 && WinWindows.TryGetValue(Window.ParentHwnd, out WinWindow OldParent)) + { + OldParent.Children.Remove(Window.Hwnd); + RefreshWindowFamily(OldParent); + } + else + { + TopLevelWindows.Remove(Window.Hwnd); + } + + Window.ParentHwnd = NewParent?.Hwnd ?? 0; + Window.Dirty = true; + + if (NewParent != null) + { + if (!NewParent.Children.Contains(Window.Hwnd)) + NewParent.Children.Add(Window.Hwnd); + + RefreshWindowFamily(NewParent); + } + else + { + if (!TopLevelWindows.Contains(Window.Hwnd)) + TopLevelWindows.Add(Window.Hwnd); + + MaterializeUserWindow(Window); + } + + return true; + } + + // Reparenting onto one of its own descendants would close the window tree into a cycle. + private bool IsWindowUnder(WinWindow Root, WinWindow Candidate) + { + for (int Depth = 0; Candidate != null && Depth < MaxWindowAncestorDepth; Depth++) + { + if (Candidate == Root) + return true; + + Candidate = Candidate.ParentHwnd != 0 && WinWindows.TryGetValue(Candidate.ParentHwnd, out WinWindow Parent) + ? Parent + : null; + } + + return false; + } + + private void RefreshWindowFamily(WinWindow Parent) + { + MaterializeUserWindow(Parent); + + // Walking backwards hands each child the sibling that follows it, so one pass settles the list. + ulong NextObject = 0; + for (int i = Parent.Children.Count - 1; i >= 0; i--) + { + if (!WinWindows.TryGetValue(Parent.Children[i], out WinWindow Child) || Child.ClientWindowAddress == 0) + continue; + + Emulator._emulator.WriteMemory(Child.ClientWindowAddress + (ulong)UserWindowNextOffset, NextObject, 8); + Emulator._emulator.WriteMemory(Child.ClientWindowAddress + (ulong)UserWindowChildOffset, FirstChildObject(Child), 8); + NextObject = Child.ClientWindowAddress; + } + } + public void UpdateTopLevelWindowZOrder(ulong Hwnd, ulong InsertAfter) { const ulong HWND_TOP = 0; @@ -4729,6 +5378,7 @@ public void PresentDesktop() { EnsureDesktopWindow(); PublishForegroundWindow(); + PublishSystemColors(); if (DesktopDisplay is not GuiThreadManager guiManager) return; @@ -4882,6 +5532,8 @@ public void RegisterWindow(WinWindow Window) { if (!Parent.Children.Contains(Window.Hwnd)) Parent.Children.Add(Window.Hwnd); + + RefreshWindowFamily(Parent); } else { @@ -4920,9 +5572,11 @@ public bool DestroyWindow(ulong Hwnd) Win32kHelper.PostMessage(Emulator, Hwnd, Win32kHelper.WM_NCDESTROY, 0, 0); + WinWindow DestroyedParent = null; if (Window.ParentHwnd != 0 && WinWindows.TryGetValue(Window.ParentHwnd, out WinWindow Parent)) { Parent.Children.Remove(Hwnd); + DestroyedParent = Parent; } else { @@ -4932,7 +5586,12 @@ public bool DestroyWindow(ulong Hwnd) Window.Destroyed = true; ClearUserWindowHandleEntry(Window); WinWindows.Remove(Hwnd); + + if (DestroyedParent != null) + RefreshWindowFamily(DestroyedParent); + RememberDestroyedWindow(Window); + ReleaseUserHandle(Hwnd); PresentDesktop(); return true; } @@ -4940,15 +5599,33 @@ public bool DestroyWindow(ulong Hwnd) private void RememberDestroyedWindow(WinWindow Window) { if (Window.WndProc == 0) + { + ReleaseUserWindowObject(Window); return; + } while (DestroyedWindowOrder.Count >= MaxDestroyedWindows) - DestroyedWindows.Remove(DestroyedWindowOrder.Dequeue()); + { + ulong Evicted = DestroyedWindowOrder.Dequeue(); + if (DestroyedWindows.Remove(Evicted, out WinWindow Gone)) + ReleaseUserWindowObject(Gone); + } DestroyedWindows[Window.Hwnd] = Window; DestroyedWindowOrder.Enqueue(Window.Hwnd); } + // The mapping outlives the window while the handle can still be asked about, so user32 reading one + // it has not noticed is gone still lands on mapped memory. + private void ReleaseUserWindowObject(WinWindow Window) + { + if (Window == null || Window.ClientWindowAddress == 0) + return; + + Emulator.UnmapMemoryRegion(Window.ClientWindowAddress); + Window.ClientWindowAddress = 0; + } + public WinWindow GetDestroyedWindow(ulong Hwnd) { if (Hwnd == 0) diff --git a/Brovan/Core/Emulation/OS/Windows/WinThreading.cs b/Brovan/Core/Emulation/OS/Windows/WinThreading.cs index f81dbc1..c71a726 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinThreading.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinThreading.cs @@ -56,6 +56,8 @@ public sealed class WindowsThreadState public ulong AlertByThreadIdAddress { get; set; } public bool MsgWaitActive { get; set; } public uint MsgWaitMask { get; set; } + public ulong ClientThreadInfo { get; set; } + public bool WaitMessageActive { get; set; } public bool GetMessageWaitActive { get; set; } public ulong GetMessageMessagePtr { get; set; } public ulong GetMessageHwndFilter { get; set; } @@ -66,9 +68,31 @@ public sealed class WindowsThreadState public sealed class WinUserCallbackFrame { - public ulong SavedRip; public ulong SavedRsp; public ulong SavedReturnAddress; + public ulong SyscallRetryRip; + + public ulong SavedSyscallNumber; + public ulong SavedArg0; + public ulong SavedArg1; + public ulong SavedArg2; + public ulong SavedArg3; + + public WinWindowCreation WindowCreation; + } + + public sealed class WinWindowCreation + { + public ulong Hwnd; + public WinWindowCreationStep Step; + } + + public enum WinWindowCreationStep + { + NonClientCreate, + Create, + Size, + Move, } public static class WinEmulatedThread From 0e1925128b64986d10ace12d8abe93f3c1489aee Mon Sep 17 00:00:00 2001 From: AdvDebug <90452585+AdvDebug@users.noreply.github.com> Date: Wed, 26 Aug 2026 22:15:04 +0300 Subject: [PATCH 2/2] Fix claude's complaints --- Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs | 4 +--- .../Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs index a3f168a..064a523 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs @@ -4,8 +4,6 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k { internal class NtGdiSetBoundsRect : IWinSyscall { - private const uint DcbDisable = 0x0008; - public NTSTATUS Handle(BinaryEmulator Instance) { ulong Hdc = Instance.WinHelper.GetArg(0); @@ -34,7 +32,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) } Instance.SetLastWinError(0); - Instance.SetRawSyscallReturn(Previous == 0 ? DcbDisable : Previous); + Instance.SetRawSyscallReturn(Previous); return NTSTATUS.STATUS_SUCCESS; } } diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs index cb96099..433eeaa 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs @@ -98,6 +98,8 @@ public NTSTATUS Handle(BinaryEmulator Instance) if (Win32kHelper.SendWindowCreateMessage(Instance, window, Win32kHelper.WM_NCCREATE, Creation)) return NTSTATUS.STATUS_SUCCESS; + // The callback path is x64 only, so a 32-bit guest gets the window with none of its creation + // messages and the class has to cope on its own. Instance.SetRawSyscallReturn(hwnd); return NTSTATUS.STATUS_SUCCESS; }