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..064a523 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetBoundsRect.cs @@ -0,0 +1,39 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiSetBoundsRect : IWinSyscall + { + 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); + 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..433eeaa 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,20 @@ 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; + + // 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; } 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