diff --git a/Brovan/Android/AndroidGdiSurface.cs b/Brovan/Android/AndroidGdiSurface.cs index 8feb3c46..dca3de31 100644 --- a/Brovan/Android/AndroidGdiSurface.cs +++ b/Brovan/Android/AndroidGdiSurface.cs @@ -18,12 +18,14 @@ private sealed class WindowBuffer public int Width; public int Height; public bool Dirty; + public long LastUsed; } private readonly object _sync = new(); private readonly Dictionary _windows = new(); private ulong _lastDrawn; + private long _accessCounter; public void Execute(in GdiPrimitive primitive) { @@ -106,15 +108,15 @@ private WindowBuffer Resolve(ulong hwnd) { if (!_windows.TryGetValue(hwnd, out WindowBuffer buffer)) { - // A guest that churns windows would otherwise grow this without bound; the emulator only - // presents one at a time, so dropping the oldest costs nothing visible. if (_windows.Count >= MaximumWindows) - _windows.Clear(); + Evict(); buffer = new WindowBuffer(); _windows[hwnd] = buffer; } + buffer.LastUsed = ++_accessCounter; + int width = AndroidHost.Width; int height = AndroidHost.Height; if (width <= 0 || height <= 0) @@ -131,6 +133,28 @@ private WindowBuffer Resolve(ulong hwnd) return buffer; } + private void Evict() + { + ulong selected = AndroidGuestWindows.Selected; + ulong victim = 0; + long oldest = long.MaxValue; + + foreach (KeyValuePair entry in _windows) + { + if (entry.Key == _lastDrawn || entry.Key == selected) + continue; + + if (entry.Value.LastUsed < oldest) + { + oldest = entry.Value.LastUsed; + victim = entry.Key; + } + } + + if (oldest != long.MaxValue) + _windows.Remove(victim); + } + private static void Draw(WindowBuffer target, in GdiPrimitive primitive) { int fill = ToPixel(primitive.Brush.ColorRef); @@ -164,6 +188,52 @@ private static void Draw(WindowBuffer target, in GdiPrimitive primitive) case GdiPrimitiveKind.Polyline: DrawPolyline(target, primitive.Points, primitive.Kind == GdiPrimitiveKind.Polygon, primitive.HasPen ? stroke : fill, thickness); break; + + case GdiPrimitiveKind.Blit: + DrawBlit(target, primitive); + break; + } + } + + private static void DrawBlit(WindowBuffer target, in GdiPrimitive primitive) + { + uint[] pixels = primitive.Pixels; + int sourceWidth = primitive.SourceWidth; + int sourceHeight = primitive.SourceHeight; + if (pixels == null || sourceWidth <= 0 || sourceHeight <= 0 || pixels.Length < sourceWidth * sourceHeight) + return; + + int left = primitive.X1; + int top = primitive.Y1; + int right = primitive.X2; + int bottom = primitive.Y2; + Normalize(ref left, ref right); + Normalize(ref top, ref bottom); + + int width = right - left; + int height = bottom - top; + if (width <= 0 || height <= 0) + return; + + int clippedLeft = Math.Max(left, 0); + int clippedTop = Math.Max(top, 0); + int clippedRight = Math.Min(right, target.Width); + int clippedBottom = Math.Min(bottom, target.Height); + + for (int y = clippedTop; y < clippedBottom; y++) + { + int sourceRow = (int)((long)(y - top) * sourceHeight / height); + int row = y * target.Width; + + for (int x = clippedLeft; x < clippedRight; x++) + { + int sourceColumn = (int)((long)(x - left) * sourceWidth / width); + uint pixel = pixels[(sourceRow * sourceWidth) + sourceColumn]; + target.Pixels[row + x] = unchecked((int)(0xFF000000u + | ((pixel & 0x00FF0000u) >> 16) + | (pixel & 0x0000FF00u) + | ((pixel & 0x000000FFu) << 16))); + } } } diff --git a/Brovan/Android/AndroidWinManager.cs b/Brovan/Android/AndroidWinManager.cs index ddd1efb4..189ca15e 100644 --- a/Brovan/Android/AndroidWinManager.cs +++ b/Brovan/Android/AndroidWinManager.cs @@ -189,6 +189,31 @@ public void DeleteFont(IntPtr font) { } + // Android draws with one system face, so that is the whole enumeration. + public IReadOnlyList EnumerateFontFamilies(string faceName, byte charSet) + { + const string SystemFace = "Roboto"; + + if (!string.IsNullOrEmpty(faceName) && !faceName.Equals(SystemFace, StringComparison.OrdinalIgnoreCase)) + return Array.Empty(); + + AndroidText.GetMetrics(out TextMetricsData metrics); + + return new FontFamilyData[] + { + new FontFamilyData + { + FaceName = SystemFace, + FullName = SystemFace, + Style = "Regular", + CharSet = metrics.CharSet, + PitchAndFamily = metrics.PitchAndFamily, + Weight = metrics.Weight != 0 ? metrics.Weight : 400, + Metrics = metrics, + } + }; + } + public void InvalidateSurface() { if (!_disposed) diff --git a/Brovan/Android/build-apk.sh b/Brovan/Android/build-apk.sh index 55b29e7c..f460ccc6 100644 --- a/Brovan/Android/build-apk.sh +++ b/Brovan/Android/build-apk.sh @@ -2,7 +2,7 @@ # Builds the Brovan APK. Must run on a Linux host (WSL is fine): NativeAOT does not cross-compile from # Windows to linux-bionic. # -# Expects a .NET 9 SDK (the source generator needs Roslyn >= 4.10), a JDK 17, Gradle 8.7+, and an Android +# Expects a .NET 10 SDK, a JDK 17, Gradle 8.7+, and an Android # SDK with NDK 26. Point the variables below at them if they are not already on PATH. set -euo pipefail @@ -16,8 +16,8 @@ TOOLS="${BROVAN_TOOLCHAIN:-$HOME/brovan-toolchain}" DOTNET="${DOTNET:-}" if [ -z "$DOTNET" ]; then - if [ -x "$HOME/.dotnet9/dotnet" ]; then - DOTNET="$HOME/.dotnet9/dotnet" + if [ -x "$HOME/.dotnet10/dotnet" ]; then + DOTNET="$HOME/.dotnet10/dotnet" else DOTNET="$(command -v dotnet || true)" fi @@ -64,12 +64,12 @@ UNICORN_PATCH_KEY="" # as a skip rather than a build failure. Anything else is a real failure. missing() { echo "$1" >&2; exit 3; } -[ -n "$DOTNET" ] && [ -x "$DOTNET" ] || missing "dotnet SDK not found; set DOTNET or install one at $HOME/.dotnet9" +[ -n "$DOTNET" ] && [ -x "$DOTNET" ] || missing "dotnet SDK not found; set DOTNET or install one at $HOME/.dotnet10" DOTNET_MAJOR="$("$DOTNET" --version 2>/dev/null | cut -d. -f1)" case "${DOTNET_MAJOR:-}" in ''|*[!0-9]*) missing "could not read the SDK version of $DOTNET" ;; esac -[ "$DOTNET_MAJOR" -ge 9 ] || missing "the source generator needs Roslyn >= 4.10, so a .NET 9 SDK is required; $DOTNET is $DOTNET_MAJOR.x" +[ "$DOTNET_MAJOR" -ge 10 ] || missing "the projects target net10.0, so a .NET 10 SDK is required; $DOTNET is $DOTNET_MAJOR.x" [ -n "$GRADLE" ] && [ -x "$GRADLE" ] || missing "gradle not found; set GRADLE (8.7 or newer, required by AGP 8.5)" [ -n "$NDK" ] && [ -d "$NDK" ] || missing "Android NDK not found under $ANDROID_SDK_ROOT/ndk" [ -n "${CMAKE:-}" ] && [ -x "$CMAKE" ] || missing "cmake not found" diff --git a/Brovan/Android/java/dev/brovan/input/SwitcherView.java b/Brovan/Android/java/dev/brovan/input/SwitcherView.java new file mode 100644 index 00000000..0abac8d8 --- /dev/null +++ b/Brovan/Android/java/dev/brovan/input/SwitcherView.java @@ -0,0 +1,369 @@ +package dev.brovan.input; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.Typeface; +import android.view.MotionEvent; +import android.view.View; + +/** + * Steps a selection one notch at a time. Tapping an end steps once and holding it repeats; sweeping a + * finger along the control runs through several. What a step means is the overlay's business. + */ +public class SwitcherView extends View { + + public interface Listener { + /** Direction is +1 or -1. Slot is the slot now selected, or -1 when the control keeps no slots. */ + void onStep(int direction, int slot); + } + + private static final float END_FRACTION = 0.32f; + private static final float RING_DP = 1.8f; + private static final float DROP_DP = 3f; + private static final float CHEVRON_DP = 2.4f; + private static final float STEP_DP = 26f; + private static final long REPEAT_FIRST_MS = 360; + private static final long REPEAT_MS = 140; + + private final Paint fillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint ringPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint dividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint litPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint dropPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint chevronPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint glyphPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Paint textPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + private final Path chevron = new Path(); + private final RectF bounds = new RectF(); + private final RectF endBounds = new RectF(); + private final RectF glyphBounds = new RectF(); + + private final float ringWidth; + private final float drop; + private final float stepDistance; + + private int fillColor = ControlItem.DEFAULT_COLOR; + private int strokeColor = ControlItem.DEFAULT_COLOR; + private int labelColor = ControlItem.DEFAULT_COLOR; + private float opacity = 1f; + + private Listener listener; + private int slots; + private int index; + private boolean horizontal; + private int lit; + private int repeatDirection; + private float swept; + private float lastAlong; + private int pointerId = MotionEvent.INVALID_POINTER_ID; + + private final Runnable repeat = new Runnable() { + @Override + public void run() { + step(repeatDirection); + postDelayed(this, REPEAT_MS); + } + }; + + public SwitcherView(Context context) { + super(context); + + float density = getResources().getDisplayMetrics().density; + ringWidth = RING_DP * density; + drop = DROP_DP * density; + stepDistance = STEP_DP * density; + + ringPaint.setStyle(Paint.Style.STROKE); + ringPaint.setStrokeWidth(ringWidth); + dividerPaint.setStyle(Paint.Style.STROKE); + dividerPaint.setStrokeWidth(density); + chevronPaint.setStyle(Paint.Style.STROKE); + chevronPaint.setStrokeWidth(CHEVRON_DP * density); + chevronPaint.setStrokeCap(Paint.Cap.ROUND); + chevronPaint.setStrokeJoin(Paint.Join.ROUND); + glyphPaint.setStyle(Paint.Style.STROKE); + glyphPaint.setStrokeWidth(CHEVRON_DP * density * 0.8f); + glyphPaint.setStrokeCap(Paint.Cap.ROUND); + textPaint.setTextAlign(Paint.Align.CENTER); + textPaint.setTypeface(Typeface.create("sans-serif-medium", Typeface.NORMAL)); + textPaint.setShadowLayer(2f * density, 0f, density, 0x99000000); + applyStyle(); + } + + public void setStyle(int fill, int stroke, int text, float opacity) { + fillColor = fill; + strokeColor = stroke; + labelColor = text; + this.opacity = opacity; + applyStyle(); + invalidate(); + } + + private void applyStyle() { + ringPaint.setColor(ControlItem.shade(strokeColor, 150, opacity)); + dividerPaint.setColor(ControlItem.shade(strokeColor, 60, opacity)); + litPaint.setColor(ControlItem.shade(fillColor, 90, opacity)); + dropPaint.setColor(ControlItem.shade(0x000000, 60, opacity)); + textPaint.setColor(ControlItem.shade(labelColor, 235, opacity)); + glyphPaint.setColor(ControlItem.shade(labelColor, 175, opacity)); + buildFill(); + } + + private void buildFill() { + if (getWidth() <= 0 || getHeight() <= 0) { + fillPaint.setShader(null); + fillPaint.setColor(ControlItem.shade(fillColor, 70, opacity)); + return; + } + + fillPaint.setShader(new LinearGradient(0f, 0f, horizontal ? getWidth() : 0f, + horizontal ? 0f : getHeight(), + ControlItem.shade(fillColor, 95, opacity), ControlItem.shade(fillColor, 45, opacity), + Shader.TileMode.CLAMP)); + } + + @Override + protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + super.onSizeChanged(width, height, oldWidth, oldHeight); + buildFill(); + } + + /** Zero keeps no selection of its own, which is what a control that only turns a wheel wants. */ + public void setSlots(int count) { + slots = Math.max(0, count); + index = 0; + invalidate(); + } + + public void setHorizontal(boolean value) { + horizontal = value; + buildFill(); + invalidate(); + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + @Override + protected void onDraw(Canvas canvas) { + float inset = ringWidth + drop; + float corner = (horizontal ? getHeight() : getWidth()) / 2f - inset; + if (corner <= 0f) { + return; + } + + bounds.set(inset, inset, getWidth() - inset, getHeight() - inset); + + canvas.save(); + canvas.translate(0f, drop * 0.7f); + canvas.drawRoundRect(bounds, corner, corner, dropPaint); + canvas.restore(); + + canvas.drawRoundRect(bounds, corner, corner, fillPaint); + + if (lit != 0) { + canvas.save(); + canvas.clipRect(endBounds()); + canvas.drawRoundRect(bounds, corner, corner, litPaint); + canvas.restore(); + } + + canvas.drawRoundRect(bounds, corner, corner, ringPaint); + drawDividers(canvas); + + float across = horizontal ? bounds.height() : bounds.width(); + drawEnds(canvas, across * 0.18f); + + if (slots > 0) { + textPaint.setTextSize(across * 0.40f); + float baseline = bounds.centerY() - (textPaint.descent() + textPaint.ascent()) / 2f; + canvas.drawText(Integer.toString(index + 1), bounds.centerX(), baseline, textPaint); + } else { + drawWheel(canvas, bounds.centerX(), bounds.centerY(), across * 0.20f); + } + } + + /** Forward is up on a tall control and right on a wide one, which is the way the finger already moves. */ + private void drawEnds(Canvas canvas, float half) { + float span = horizontal ? bounds.width() : bounds.height(); + float centre = span * END_FRACTION / 2f; + + if (horizontal) { + drawChevron(canvas, bounds.right - centre, bounds.centerY(), 90f, half, lit > 0); + drawChevron(canvas, bounds.left + centre, bounds.centerY(), 270f, half, lit < 0); + } else { + drawChevron(canvas, bounds.centerX(), bounds.top + centre, 0f, half, lit > 0); + drawChevron(canvas, bounds.centerX(), bounds.bottom - centre, 180f, half, lit < 0); + } + } + + private void drawChevron(Canvas canvas, float centreX, float centreY, float rotation, float half, + boolean bright) { + chevronPaint.setColor(ControlItem.shade(labelColor, bright ? 255 : 170, opacity)); + + canvas.save(); + canvas.rotate(rotation, centreX, centreY); + chevron.reset(); + chevron.moveTo(centreX - half, centreY + half * 0.5f); + chevron.lineTo(centreX, centreY - half * 0.5f); + chevron.lineTo(centreX + half, centreY + half * 0.5f); + canvas.drawPath(chevron, chevronPaint); + canvas.restore(); + } + + private RectF endBounds() { + float span = horizontal ? bounds.width() : bounds.height(); + float end = span * END_FRACTION; + endBounds.set(bounds); + + if (horizontal) { + if (lit > 0) { + endBounds.left = bounds.right - end; + } else { + endBounds.right = bounds.left + end; + } + } else { + if (lit > 0) { + endBounds.bottom = bounds.top + end; + } else { + endBounds.top = bounds.bottom - end; + } + } + + return endBounds; + } + + private void drawDividers(Canvas canvas) { + float span = horizontal ? bounds.width() : bounds.height(); + float end = span * END_FRACTION; + + if (horizontal) { + float top = bounds.top + bounds.height() * 0.24f; + float bottom = bounds.bottom - bounds.height() * 0.24f; + canvas.drawLine(bounds.left + end, top, bounds.left + end, bottom, dividerPaint); + canvas.drawLine(bounds.right - end, top, bounds.right - end, bottom, dividerPaint); + } else { + float left = bounds.left + bounds.width() * 0.24f; + float right = bounds.right - bounds.width() * 0.24f; + canvas.drawLine(left, bounds.top + end, right, bounds.top + end, dividerPaint); + canvas.drawLine(left, bounds.bottom - end, right, bounds.bottom - end, dividerPaint); + } + } + + /** A control with no slots of its own still has to say what it does, so it draws the wheel it turns. */ + private void drawWheel(Canvas canvas, float centreX, float centreY, float half) { + glyphBounds.set(centreX - half * 0.70f, centreY - half, centreX + half * 0.70f, centreY + half); + canvas.drawRoundRect(glyphBounds, half * 0.70f, half * 0.70f, glyphPaint); + canvas.drawLine(centreX, centreY - half * 0.52f, centreX, centreY - half * 0.10f, glyphPaint); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: { + if (pointerId != MotionEvent.INVALID_POINTER_ID) { + return true; + } + + int pointer = event.getActionIndex(); + pointerId = event.getPointerId(pointer); + lastAlong = horizontal ? event.getX(pointer) : event.getY(pointer); + swept = 0f; + + int zone = zoneAt(event.getX(pointer), event.getY(pointer)); + if (zone != 0) { + step(zone); + repeatDirection = zone; + postDelayed(repeat, REPEAT_FIRST_MS); + } + + return true; + } + + case MotionEvent.ACTION_MOVE: { + int pointer = event.findPointerIndex(pointerId); + if (pointer < 0) { + return true; + } + + float now = horizontal ? event.getX(pointer) : event.getY(pointer); + swept += horizontal ? now - lastAlong : lastAlong - now; + lastAlong = now; + + while (Math.abs(swept) >= stepDistance) { + int direction = swept > 0f ? 1 : -1; + swept -= direction * stepDistance; + removeCallbacks(repeat); + step(direction); + } + + return true; + } + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + if (event.getPointerId(event.getActionIndex()) != pointerId) { + return true; + } + + finish(); + return true; + + case MotionEvent.ACTION_CANCEL: + finish(); + return true; + + default: + return super.onTouchEvent(event); + } + } + + private void finish() { + pointerId = MotionEvent.INVALID_POINTER_ID; + removeCallbacks(repeat); + lit = 0; + invalidate(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeCallbacks(repeat); + } + + /** +1 for the end that steps forward, -1 for the other, 0 for the readout between them. */ + private int zoneAt(float x, float y) { + float position = horizontal ? x : y; + float span = horizontal ? getWidth() : getHeight(); + + if (position <= span * END_FRACTION) { + return horizontal ? -1 : 1; + } + + if (position >= span * (1f - END_FRACTION)) { + return horizontal ? 1 : -1; + } + + return 0; + } + + private void step(int direction) { + if (slots > 0) { + index = (index + direction + slots) % slots; + } + + lit = direction; + invalidate(); + + if (listener != null) { + listener.onStep(direction, slots > 0 ? index : -1); + } + } +} diff --git a/Brovan/Core/Emulation/Backends/Unicorn/UnicornBackend.cs b/Brovan/Core/Emulation/Backends/Unicorn/UnicornBackend.cs index bdfbb489..884a920f 100644 --- a/Brovan/Core/Emulation/Backends/Unicorn/UnicornBackend.cs +++ b/Brovan/Core/Emulation/Backends/Unicorn/UnicornBackend.cs @@ -125,12 +125,18 @@ public bool ConfigureEmulatedTimestampCounter(long hostStart, long hostFrequency public bool Emulate(ulong start, ulong end, uint timeout = 0, uint count = 0) { + HookFailure = null; _armedBudget = count; _sliceLimit = count; _sliceStart = Stopwatch.GetTimestamp(); bool Result = Inner.Emulate(start, end, timeout, count); UpdateInstructionRate(); - return Result; + + if (HookFailure == null) + return Result; + + HookFailure = null; + return false; } private uint _armedBudget; @@ -339,6 +345,25 @@ private interface IHookThunk : IDisposable IntPtr NativePtr { get; } } + // uc_emu_stop makes uc_emu_start return success, so a hook that threw has to fail the slice itself. + [ThreadStatic] + private static Exception HookFailure; + + private static void FailHook(IntPtr uc, Exception Error) + { + HookFailure ??= Error; + Helpers.Utils.LogError($"[Unicorn] Hook callback threw, stopping the run: {Error}"); + + try + { + Native.uc_emu_stop(uc); + } + catch + { + // The run is already being torn down. + } + } + private delegate bool NativeMemoryDelegate(IntPtr uc, MemoryType type, ulong address, uint size, ulong value, IntPtr userData); private delegate void NativeCodeDelegate(IntPtr uc, ulong address, uint size, IntPtr userData); private delegate void NativeInterruptDelegate(IntPtr uc, uint interruptNumber); @@ -355,7 +380,18 @@ private sealed class MemoryThunk : IHookThunk public MemoryThunk(MemoryHookCallback user) { _user = user; - _thunk = (_, type, address, size, value, _) => _user(TranslateMemoryType(type), address, size, value); + _thunk = (handle, type, address, size, value, _) => + { + try + { + return _user(TranslateMemoryType(type), address, size, value); + } + catch (Exception Error) + { + FailHook(handle, Error); + return false; + } + }; _selfPin = GCHandle.Alloc(this); NativePtr = Marshal.GetFunctionPointerForDelegate(_thunk); } @@ -372,7 +408,17 @@ private sealed class CodeThunk : IHookThunk public CodeThunk(CodeHookCallback user) { _user = user; - _thunk = (_, address, size, _) => _user(address, size); + _thunk = (handle, address, size, _) => + { + try + { + _user(address, size); + } + catch (Exception Error) + { + FailHook(handle, Error); + } + }; _selfPin = GCHandle.Alloc(this); NativePtr = Marshal.GetFunctionPointerForDelegate(_thunk); } @@ -389,7 +435,17 @@ private sealed class InterruptThunk : IHookThunk public InterruptThunk(InterruptHookCallback user) { _user = user; - _thunk = (_, intno) => _user(intno); + _thunk = (handle, intno) => + { + try + { + _user(intno); + } + catch (Exception Error) + { + FailHook(handle, Error); + } + }; _selfPin = GCHandle.Alloc(this); NativePtr = Marshal.GetFunctionPointerForDelegate(_thunk); } @@ -406,7 +462,17 @@ private sealed class InstructionThunk : IHookThunk public InstructionThunk(InstructionHookCallback user) { _user = user; - _thunk = (_, _) => _user(); + _thunk = (handle, _) => + { + try + { + _user(); + } + catch (Exception Error) + { + FailHook(handle, Error); + } + }; _selfPin = GCHandle.Alloc(this); NativePtr = Marshal.GetFunctionPointerForDelegate(_thunk); } @@ -423,7 +489,18 @@ private sealed class InstructionBoolThunk : IHookThunk public InstructionBoolThunk(InstructionBoolHookCallback user) { _user = user; - _thunk = (_, _) => _user(); + _thunk = (handle, _) => + { + try + { + return _user(); + } + catch (Exception Error) + { + FailHook(handle, Error); + return false; + } + }; _selfPin = GCHandle.Alloc(this); NativePtr = Marshal.GetFunctionPointerForDelegate(_thunk); } diff --git a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/GuiThreadManager.cs b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/GuiThreadManager.cs index 84c75d81..e269b43f 100644 --- a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/GuiThreadManager.cs +++ b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/GuiThreadManager.cs @@ -295,6 +295,15 @@ public IntPtr CreateFont(in FontDescription description) return support == null ? IntPtr.Zero : support.CreateFont(description); } + public IReadOnlyList EnumerateFontFamilies(string faceName, byte charSet) + { + if (_disposed || !WaitForInitialization()) + return Array.Empty(); + + ITextMetricsSupport support = _textMetrics; + return support == null ? Array.Empty() : support.EnumerateFontFamilies(faceName, charSet); + } + public void DeleteFont(IntPtr font) { if (_disposed || font == IntPtr.Zero) diff --git a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/LinuxWinManager.cs b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/LinuxWinManager.cs index 62008e72..cc647197 100644 --- a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/LinuxWinManager.cs +++ b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/LinuxWinManager.cs @@ -278,12 +278,29 @@ public static partial int XChangeProperty( [LibraryImport("libX11.so.6")] public static partial int XFillArc(IntPtr display, IntPtr drawable, IntPtr gc, int x, int y, uint width, uint height, int angle1, int angle2); + [LibraryImport("libX11.so.6")] + public static partial IntPtr XDefaultVisual(IntPtr display, int screen); + + [LibraryImport("libX11.so.6")] + public static unsafe partial IntPtr XCreateImage(IntPtr display, IntPtr visual, uint depth, int format, int offset, + void* data, uint width, uint height, int bitmapPad, int bytesPerLine); + + [LibraryImport("libX11.so.6")] + public static partial int XPutImage(IntPtr display, IntPtr drawable, IntPtr gc, IntPtr image, + int srcX, int srcY, int destX, int destY, uint width, uint height); + [LibraryImport("libX11.so.6")] public static unsafe partial int XFillPolygon(IntPtr display, IntPtr drawable, IntPtr gc, XPoint* points, int count, int shape, int mode); [LibraryImport("libX11.so.6", StringMarshalling = StringMarshalling.Utf8)] public static partial IntPtr XLoadQueryFont(IntPtr display, string name); + [LibraryImport("libX11.so.6", StringMarshalling = StringMarshalling.Utf8)] + public static unsafe partial IntPtr* XListFonts(IntPtr display, string pattern, int maxNames, out int count); + + [LibraryImport("libX11.so.6")] + public static unsafe partial int XFreeFontNames(IntPtr* list); + [LibraryImport("libX11.so.6", StringMarshalling = StringMarshalling.Utf8)] public static partial int XDrawString(IntPtr display, IntPtr drawable, IntPtr gc, int x, int y, string str, int length); @@ -1574,9 +1591,61 @@ public void ExecuteGdiPrimitive(IntPtr windowHandle, GdiPrimitive primitive) case GdiPrimitiveKind.Polyline: DrawPoly(windowHandle, gc, primitive); break; + + case GdiPrimitiveKind.Blit: + DrawBlit(windowHandle, gc, primitive); + break; } } + private unsafe void DrawBlit(IntPtr windowHandle, IntPtr gc, in GdiPrimitive primitive) + { + uint[] pixels = primitive.Pixels; + int sourceWidth = primitive.SourceWidth; + int sourceHeight = primitive.SourceHeight; + if (pixels == null || sourceWidth <= 0 || sourceHeight <= 0 || pixels.Length < sourceWidth * sourceHeight) + return; + + int width = primitive.X2 - primitive.X1; + int height = primitive.Y2 - primitive.Y1; + if (width <= 0 || height <= 0) + return; + + IntPtr visual = X11.XDefaultVisual(_xDisplay, _screen); + uint depth = (uint)X11.XDefaultDepth(_xDisplay, _screen); + if (visual == IntPtr.Zero) + return; + + // XPutImage does not scale. + nuint bytes = (nuint)((long)width * height * sizeof(uint)); + uint* data = (uint*)NativeMemory.Alloc(bytes); + + for (int row = 0; row < height; row++) + { + int sourceRow = height == sourceHeight ? row : (int)((long)row * sourceHeight / height); + uint* target = data + (long)row * width; + + for (int column = 0; column < width; column++) + { + int sourceColumn = width == sourceWidth ? column : (int)((long)column * sourceWidth / width); + target[column] = pixels[sourceRow * sourceWidth + sourceColumn]; + } + } + + IntPtr image = X11.XCreateImage(_xDisplay, visual, depth, X11.ZPixmap, 0, data, + (uint)width, (uint)height, 32, 0); + + if (image == IntPtr.Zero) + { + NativeMemory.Free(data); + return; + } + + SetGraphicsFunction(gc, X11.GXcopy); + X11.XPutImage(_xDisplay, windowHandle, gc, image, 0, 0, primitive.X1, primitive.Y1, (uint)width, (uint)height); + DestroyImage(image); + } + private unsafe void DrawPoly(IntPtr windowHandle, IntPtr gc, in GdiPrimitive primitive) { GdiPoint[] points = primitive.Points; @@ -1928,6 +1997,57 @@ public void DeleteFont(IntPtr font) X11.XFreeFont(_xDisplay, font); } + // An XLFD name carries the family in its second field, one name per size and style. + public unsafe IReadOnlyList EnumerateFontFamilies(string faceName, byte charSet) + { + const int MaxNames = 4096; + List faces = new List(); + + if (_xDisplay == IntPtr.Zero) + return faces; + + IntPtr* names = X11.XListFonts(_xDisplay, "-*-*-*-*-*-*-*-*-*-*-*-*-*-*", MaxNames, out int count); + if (names == null) + return faces; + + HashSet seen = new HashSet(StringComparer.OrdinalIgnoreCase); + for (int i = 0; i < count; i++) + { + string name = Marshal.PtrToStringUTF8(names[i]); + if (string.IsNullOrEmpty(name) || name[0] != '-') + continue; + + int familyStart = name.IndexOf('-', 1) + 1; + if (familyStart <= 0) + continue; + + int familyEnd = name.IndexOf('-', familyStart); + if (familyEnd <= familyStart) + continue; + + string family = name.Substring(familyStart, familyEnd - familyStart); + if (family.Length == 0 || family == "*" || !seen.Add(family)) + continue; + + if (!string.IsNullOrEmpty(faceName) && !family.Equals(faceName, StringComparison.OrdinalIgnoreCase)) + continue; + + faces.Add(new FontFamilyData + { + FaceName = family, + FullName = family, + Style = "Regular", + CharSet = charSet == 0 ? (byte)0 : charSet, + PitchAndFamily = 0, + Weight = 400, + FontType = 0, + }); + } + + X11.XFreeFontNames(names); + return faces; + } + private IntPtr ResolveFont(IntPtr font) { return font != IntPtr.Zero ? font : _fontStruct; diff --git a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowManager.cs b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowManager.cs index e14b11e1..124a6ded 100644 --- a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowManager.cs +++ b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowManager.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; @@ -546,7 +547,8 @@ public enum GdiPrimitiveKind Ellipse, RoundRect, Polygon, - Polyline + Polyline, + Blit } public struct GdiPenDescriptor @@ -583,6 +585,11 @@ public struct GdiPrimitive public GdiBrushDescriptor Brush; public bool HasPen; public bool HasBrush; + + // Blit only. Top-down 32 bit rows, stretched onto the destination rectangle. + public uint[] Pixels; + public int SourceWidth; + public int SourceHeight; } public interface IGdiRenderSupport @@ -630,6 +637,26 @@ public readonly record struct FontDescription( byte PitchAndFamily, string FaceName); + public sealed class FontFamilyData + { + public string FaceName; + public string FullName; + public string Style; + public byte CharSet; + public byte PitchAndFamily; + public int Weight; + public bool Italic; + + // RASTER_FONTTYPE, DEVICE_FONTTYPE, TRUETYPE_FONTTYPE. + public uint FontType; + + public TextMetricsData Metrics; + public uint NtmFlags; + public uint SizeEm; + public uint CellHeight; + public uint AvgWidth; + } + public static class HostColor { // A COLORREF is 0x00BBGGRR, a 32 bit DIB pixel is 0x00RRGGBB. @@ -651,6 +678,9 @@ bool RasterizeText(IntPtr font, string text, Span pixels, int width, int h IntPtr CreateFont(in FontDescription description); void DeleteFont(IntPtr font); + + // No face name asks for every face, a face name for that family's styles. + IReadOnlyList EnumerateFontFamilies(string faceName, byte charSet); } public interface IDisplayConnection : IDisposable diff --git a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowsWinManager.cs b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowsWinManager.cs index e432b603..b4141336 100644 --- a/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowsWinManager.cs +++ b/Brovan/Core/Emulation/OS/SharedHelpers/WindowManager/WindowsWinManager.cs @@ -636,6 +636,37 @@ public unsafe void ExecuteGdiPrimitive(IntPtr windowHandle, GdiPrimitive primiti case GdiPrimitiveKind.Polyline: DrawPoly(hdc, primitive); break; + + case GdiPrimitiveKind.Blit: + DrawBlit(hdc, primitive); + break; + } + } + + private static unsafe void DrawBlit(IntPtr hdc, in GdiPrimitive primitive) + { + uint[] pixels = primitive.Pixels; + if (pixels == null || primitive.SourceWidth <= 0 || primitive.SourceHeight <= 0) + return; + + if (pixels.Length < primitive.SourceWidth * primitive.SourceHeight) + return; + + BITMAPINFOHEADER header = new BITMAPINFOHEADER + { + biSize = (uint)sizeof(BITMAPINFOHEADER), + biWidth = primitive.SourceWidth, + // A negative height makes the rows read top-down. + biHeight = -primitive.SourceHeight, + biPlanes = 1, + biBitCount = 32, + biCompression = 0, + }; + + fixed (uint* bits = pixels) + { + StretchDIBits(hdc, primitive.X1, primitive.Y1, primitive.X2 - primitive.X1, primitive.Y2 - primitive.Y1, + 0, 0, primitive.SourceWidth, primitive.SourceHeight, bits, ref header, 0, primitive.Rop); } } @@ -749,6 +780,78 @@ public void DeleteFont(IntPtr font) DeleteObject(font); } + public IReadOnlyList EnumerateFontFamilies(string faceName, byte charSet) + { + List faces = new List(); + + lock (MetricsLock) + { + IntPtr hdc = EnsureMetricsDc(); + if (hdc == IntPtr.Zero) + return faces; + + LOGFONTW query = new LOGFONTW + { + lfCharSet = charSet, + lfFaceName = faceName ?? string.Empty, + }; + + EnumFontFamiliesExW(hdc, ref query, (logFont, textMetric, fontType, _) => + { + ENUMLOGFONTEXW enumerated = Marshal.PtrToStructure(logFont); + NEWTEXTMETRICW metrics = Marshal.PtrToStructure(textMetric); + + faces.Add(new FontFamilyData + { + FaceName = enumerated.elfLogFont.lfFaceName, + FullName = enumerated.elfFullName, + Style = enumerated.elfStyle, + CharSet = enumerated.elfLogFont.lfCharSet, + PitchAndFamily = enumerated.elfLogFont.lfPitchAndFamily, + Weight = enumerated.elfLogFont.lfWeight, + Italic = enumerated.elfLogFont.lfItalic != 0, + FontType = fontType, + Metrics = ToMetricsData(metrics), + NtmFlags = metrics.ntmFlags, + SizeEm = metrics.ntmSizeEM, + CellHeight = metrics.ntmCellHeight, + AvgWidth = metrics.ntmAvgWidth, + }); + + return 1; + }, IntPtr.Zero, 0); + } + + return faces; + } + + private static TextMetricsData ToMetricsData(in NEWTEXTMETRICW native) + { + return new TextMetricsData + { + Height = native.tmHeight, + Ascent = native.tmAscent, + Descent = native.tmDescent, + InternalLeading = native.tmInternalLeading, + ExternalLeading = native.tmExternalLeading, + AveCharWidth = native.tmAveCharWidth, + MaxCharWidth = native.tmMaxCharWidth, + Weight = native.tmWeight, + Overhang = native.tmOverhang, + DigitizedAspectX = native.tmDigitizedAspectX, + DigitizedAspectY = native.tmDigitizedAspectY, + FirstChar = native.tmFirstChar, + LastChar = native.tmLastChar, + DefaultChar = native.tmDefaultChar, + BreakChar = native.tmBreakChar, + Italic = native.tmItalic, + Underlined = native.tmUnderlined, + StruckOut = native.tmStruckOut, + PitchAndFamily = native.tmPitchAndFamily, + CharSet = native.tmCharSet, + }; + } + public bool GetTextMetrics(IntPtr font, out TextMetricsData metrics) { metrics = default; @@ -1551,6 +1654,10 @@ private struct BITMAPINFOHEADER private static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFOHEADER pbmi, uint usage, out IntPtr ppvBits, IntPtr hSection, uint offset); + [DllImport("gdi32.dll", SetLastError = true)] + private static extern unsafe int StretchDIBits(IntPtr hdc, int xDest, int yDest, int destWidth, int destHeight, + int xSrc, int ySrc, int srcWidth, int srcHeight, void* bits, ref BITMAPINFOHEADER bmi, uint usage, uint rop); + [DllImport("gdi32.dll", SetLastError = true)] private static extern bool DeleteDC(IntPtr hdc); @@ -1613,6 +1720,52 @@ private struct LOGFONTW public string lfFaceName; } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + private struct ENUMLOGFONTEXW + { + public LOGFONTW elfLogFont; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)] + public string elfFullName; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string elfStyle; + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string elfScript; + } + + [StructLayout(LayoutKind.Sequential)] + private struct NEWTEXTMETRICW + { + public int tmHeight; + public int tmAscent; + public int tmDescent; + public int tmInternalLeading; + public int tmExternalLeading; + public int tmAveCharWidth; + public int tmMaxCharWidth; + public int tmWeight; + public int tmOverhang; + public int tmDigitizedAspectX; + public int tmDigitizedAspectY; + public ushort tmFirstChar; + public ushort tmLastChar; + public ushort tmDefaultChar; + public ushort tmBreakChar; + public byte tmItalic; + public byte tmUnderlined; + public byte tmStruckOut; + public byte tmPitchAndFamily; + public byte tmCharSet; + public uint ntmFlags; + public uint ntmSizeEM; + public uint ntmCellHeight; + public uint ntmAvgWidth; + } + + private delegate int EnumFontFamExProc(IntPtr logFont, IntPtr textMetric, uint fontType, IntPtr parameter); + + [DllImport("gdi32.dll", CharSet = CharSet.Unicode)] + private static extern int EnumFontFamiliesExW(IntPtr hdc, ref LOGFONTW logFont, EnumFontFamExProc callback, IntPtr parameter, uint flags); + [DllImport("gdi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr CreateFontIndirectW(ref LOGFONTW lplf); diff --git a/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs b/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs index ac6d541e..4df8f456 100644 --- a/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs +++ b/Brovan/Core/Emulation/OS/Windows/BinaryEmulator.WindowsBridge.cs @@ -812,7 +812,7 @@ internal bool TrySatisfyThreadWait(EmulatedThread Thread, long Now) if (State != null && State.WaitMessageActive) { - if (!Win32kHelper.HasQueuedInputEvent(this, Win32kHelper.QS_ALLINPUT)) + if (!Win32kHelper.HasQueuedInputEvent(this, Win32kHelper.QS_ALLINPUT, Thread.ThreadId)) return false; State.WaitMessageActive = false; @@ -823,7 +823,7 @@ internal bool TrySatisfyThreadWait(EmulatedThread Thread, long Now) if (State != null && State.GetMessageWaitActive) { - if (Win32kHelper.TryGetMessage(this, State.GetMessageHwndFilter, State.GetMessageMinMessage, State.GetMessageMaxMessage, true, out Win32kMessage Message)) + if (Win32kHelper.TryGetMessage(this, State.GetMessageHwndFilter, State.GetMessageMinMessage, State.GetMessageMaxMessage, true, Thread.ThreadId, out Win32kMessage Message)) { Win32kHelper.WriteMessage(this, State.GetMessageMessagePtr, Message); State.GetMessageWaitActive = false; @@ -837,7 +837,7 @@ internal bool TrySatisfyThreadWait(EmulatedThread Thread, long Now) if (State != null && State.MsgWaitActive) { - bool MessageReady = Win32kHelper.HasQueuedInputEvent(this, State.MsgWaitMask); + bool MessageReady = Win32kHelper.HasQueuedInputEvent(this, State.MsgWaitMask, Thread.ThreadId); bool HasHandles = Thread.WaitHandles != null && Thread.WaitHandles.Count > 0; if (Thread.WaitAll) diff --git a/Brovan/Core/Emulation/OS/Windows/Misc/NtAddAtomEx.cs b/Brovan/Core/Emulation/OS/Windows/Misc/NtAddAtomEx.cs new file mode 100644 index 00000000..968e6419 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Misc/NtAddAtomEx.cs @@ -0,0 +1,41 @@ +using System.Text; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows +{ + internal class NtAddAtomEx : IWinSyscall + { + private const int MaxAtomChars = 255; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong NamePtr = Instance.WinHelper.GetArg(0); + uint Length = Instance.WinHelper.GetArg32(1); + ulong AtomPtr = Instance.WinHelper.GetArg(2); + + if (NamePtr == 0 || Length == 0 || Length > MaxAtomChars * 2 || (Length & 1) != 0) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + if (!Instance.IsRegionMapped(NamePtr, Length)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + Span Buffer = Instance.WinHelper.Shared.GetSpan(Length).Slice(0, (int)Length); + if (!Instance.ReadMemory(NamePtr, Buffer)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + string Name = Encoding.Unicode.GetString(Buffer).TrimEnd('\0'); + if (Name.Length == 0) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + // A registered window message and a global atom come out of the same table on NT. + ushort Atom = Instance.WinHelper.RegisterWindowMessageAtom(Name); + if (Atom == 0) + return NTSTATUS.STATUS_NO_MEMORY; + + if (AtomPtr != 0 && !Instance._emulator.WriteMemory(AtomPtr, Atom, 2)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Misc/NtQueryDefaultLocale.cs b/Brovan/Core/Emulation/OS/Windows/Misc/NtQueryDefaultLocale.cs new file mode 100644 index 00000000..e9a0e99c --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Misc/NtQueryDefaultLocale.cs @@ -0,0 +1,18 @@ +namespace Brovan.Core.Emulation.OS.Windows +{ + internal class NtQueryDefaultLocale : IWinSyscall + { + private const uint EnglishUnitedStates = 0x0409; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong LocaleIdPtr = Instance.WinHelper.GetArg(1); + + if (LocaleIdPtr == 0 || !Instance.IsRegionMapped(LocaleIdPtr, 4)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + Instance._emulator.WriteMemory(LocaleIdPtr, EnglishUnitedStates, 4); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Misc/NtSetSecurityObject.cs b/Brovan/Core/Emulation/OS/Windows/Misc/NtSetSecurityObject.cs new file mode 100644 index 00000000..f52d4ac2 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Misc/NtSetSecurityObject.cs @@ -0,0 +1,23 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows +{ + internal sealed class NtSetSecurityObject : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + bool Wide = Instance._binary.Architecture == BinaryArchitecture.x64; + ulong Handle = Wide ? Instance.WinHelper.GetArg(0) : Instance.WinHelper.GetArg32(0); + ulong SecurityDescriptorPtr = Wide ? Instance.WinHelper.GetArg(2) : Instance.WinHelper.GetArg32(2); + + if (SecurityDescriptorPtr == 0) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + if (!Instance.WinHelper.HandleManager.TryGetHandle(Handle, out _)) + return NTSTATUS.STATUS_INVALID_HANDLE; + + // One user owns everything here, so the descriptor never changes. + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Misc/NtSetTimerResolution.cs b/Brovan/Core/Emulation/OS/Windows/Misc/NtSetTimerResolution.cs new file mode 100644 index 00000000..1bf7e67f --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Misc/NtSetTimerResolution.cs @@ -0,0 +1,25 @@ +namespace Brovan.Core.Emulation.OS.Windows +{ + internal class NtSetTimerResolution : IWinSyscall + { + private const uint CoarsestResolution = 156250; + private const uint FinestResolution = 5000; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + uint DesiredResolution = (uint)Instance.WinHelper.GetArg(0); + bool SetResolution = Instance.WinHelper.GetArg(1) != 0; + ulong CurrentResolutionPtr = Instance.WinHelper.GetArg(2); + + if (CurrentResolutionPtr == 0 || !Instance.IsRegionMapped(CurrentResolutionPtr, 4)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + uint Granted = SetResolution + ? System.Math.Clamp(DesiredResolution, FinestResolution, CoarsestResolution) + : CoarsestResolution; + + Instance._emulator.WriteMemory(CurrentResolutionPtr, Granted, 4); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtQueryInformationProcess.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtQueryInformationProcess.cs index 6b2c9a47..5d0de346 100644 --- a/Brovan/Core/Emulation/OS/Windows/Process/NtQueryInformationProcess.cs +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtQueryInformationProcess.cs @@ -530,6 +530,39 @@ void SetReturnLength(uint Len) } case PROCESSINFOCLASS.ProcessImageFileNameWin32: return QueryProcessImageFileNameWin32(Instance, ProcessHandle, OutBufferPtr, OutBufferLength, SetReturnLength); + case PROCESSINFOCLASS.ProcessDeviceMap: + { + // PROCESS_DEVICEMAP_INFORMATION: drive bitmask, then one DRIVE_* byte per letter. 0x24 bytes on both architectures. + const uint StructSize = 0x24; + const byte DriveFixed = 3; + + if (OutBufferLength < StructSize) + { + SetReturnLength(StructSize); + return NTSTATUS.STATUS_INFO_LENGTH_MISMATCH; + } + + if (!Instance.IsRegionMapped(OutBufferPtr, StructSize)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + uint Map = Instance.WinHelper.DriveMap; + + Span Buffer = GetSharedWriteBuffer(Instance, StructSize); + Buffer.Clear(); + WriteUInt32(Buffer, 0, Map); + + for (int Index = 0; Index < 26; Index++) + { + if ((Map & (1u << Index)) != 0) + Buffer[4 + Index] = DriveFixed; + } + + if (!Instance.WriteMemory(OutBufferPtr, Buffer)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + SetReturnLength(StructSize); + return NTSTATUS.STATUS_SUCCESS; + } case (PROCESSINFOCLASS)52: { // PROCESS_MITIGATION_POLICY_INFORMATION: policy id in, policy value out. 8 bytes on both architectures. diff --git a/Brovan/Core/Emulation/OS/Windows/Process/NtUnlockVirtualMemory.cs b/Brovan/Core/Emulation/OS/Windows/Process/NtUnlockVirtualMemory.cs new file mode 100644 index 00000000..75bb0985 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Process/NtUnlockVirtualMemory.cs @@ -0,0 +1,52 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows +{ + internal sealed class NtUnlockVirtualMemory : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + if (Instance._binary.Architecture == BinaryArchitecture.x64) + return HandleCommon(Instance, Instance.WinHelper.GetArg64(0), Instance.WinHelper.GetArg64(1), Instance.WinHelper.GetArg64(2), 8); + + return HandleCommon(Instance, Instance.WinHelper.GetArg(0), Instance.WinHelper.GetArg(1), Instance.WinHelper.GetArg(2), 4); + } + + private static NTSTATUS HandleCommon(BinaryEmulator Instance, ulong ProcessHandle, ulong BaseAddressPtr, ulong NumberOfBytesPtr, uint PointerSize) + { + if (!Instance.WinHelper.IsCurrentProcessHandle(ProcessHandle, AccessMask.ProcessVMOperation)) + return NTSTATUS.STATUS_INVALID_HANDLE; + + if (BaseAddressPtr == 0 || NumberOfBytesPtr == 0) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + if (!Instance.IsRegionMapped(BaseAddressPtr, PointerSize) || !Instance.IsRegionMapped(NumberOfBytesPtr, PointerSize)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + ulong BaseAddress = Instance.WinHelper.ReadPointer(BaseAddressPtr, PointerSize); + ulong NumberOfBytes = Instance.WinHelper.ReadPointer(NumberOfBytesPtr, PointerSize); + + if (NumberOfBytes == 0) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + const ulong PageSize = 0x1000; + ulong AlignedBase = BaseAddress & ~(PageSize - 1UL); + ulong EndAddress = BaseAddress + NumberOfBytes; + if (EndAddress < BaseAddress) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + ulong AlignedEnd = (EndAddress + PageSize - 1UL) & ~(PageSize - 1UL); + if (AlignedEnd < EndAddress || AlignedEnd <= AlignedBase) + return NTSTATUS.STATUS_INVALID_PARAMETER; + + ulong AlignedSize = AlignedEnd - AlignedBase; + if (!Instance.IsRegionMapped(AlignedBase, AlignedSize)) + return NTSTATUS.STATUS_ACCESS_VIOLATION; + + bool WroteBase = Instance._emulator.WriteMemory(BaseAddressPtr, AlignedBase, PointerSize); + bool WroteSize = Instance._emulator.WriteMemory(NumberOfBytesPtr, AlignedSize, PointerSize); + + return WroteBase && WroteSize ? NTSTATUS.STATUS_SUCCESS : NTSTATUS.STATUS_ACCESS_VIOLATION; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtDCompositionEnableMMCSS.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtDCompositionEnableMMCSS.cs new file mode 100644 index 00000000..f4f44529 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtDCompositionEnableMMCSS.cs @@ -0,0 +1,14 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtDCompositionEnableMMCSS : IWinSyscall + { + // No host counterpart for multimedia class scheduling. + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiBitBlt.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiBitBlt.cs new file mode 100644 index 00000000..afa09bac --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiBitBlt.cs @@ -0,0 +1,59 @@ +using System; +using System.Buffers; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiBitBlt : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong DestDc = Instance.WinHelper.GetArg(0); + int X = unchecked((int)Instance.WinHelper.GetArg(1)); + int Y = unchecked((int)Instance.WinHelper.GetArg(2)); + int Width = unchecked((int)Instance.WinHelper.GetArg(3)); + int Height = unchecked((int)Instance.WinHelper.GetArg(4)); + ulong SourceDc = Instance.WinHelper.GetArg(5); + int SourceX = unchecked((int)Instance.WinHelper.GetArg(6)); + int SourceY = unchecked((int)Instance.WinHelper.GetArg(7)); + uint Rop = (uint)Instance.WinHelper.GetArg(8); + + if (!Win32kHelper.IsBlitExtentValid(Width, Height)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + int Count = Width * Height; + uint[] Pixels = ArrayPool.Shared.Rent(Count); + bool Rendered; + try + { + Span Block = Pixels.AsSpan(0, Count); + if (!Win32kHelper.TryReadDcBlock(Instance, SourceDc, SourceX, SourceY, Width, Height, Block)) + { + // A rop with no source term draws from the pattern alone. + if (Win32kHelper.RopUsesSource(Rop)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Block.Clear(); + } + + Rendered = Win32kHelper.BlitBlockToDc(Instance, DestDc, X, Y, Width, Height, Block, Width, Height, Rop); + } + finally + { + ArrayPool.Shared.Return(Pixels); + } + + Instance.SetLastWinError(Rendered ? Win32kHelper.ERROR_SUCCESS : Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn(Rendered ? 1UL : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCombineRgn.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCombineRgn.cs new file mode 100644 index 00000000..169050f4 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCombineRgn.cs @@ -0,0 +1,44 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiCombineRgn : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Destination = Instance.WinHelper.GetArg(0); + ulong SourceA = Instance.WinHelper.GetArg(1); + ulong SourceB = Instance.WinHelper.GetArg(2); + int Mode = unchecked((int)Instance.WinHelper.GetArg(3)); + + if (!Win32kHelper.TryReadRegionRect(Instance, SourceA, out int ALeft, out int ATop, out int ARight, out int ABottom)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn((ulong)Win32kHelper.RegionError); + return NTSTATUS.STATUS_SUCCESS; + } + + if (!Win32kHelper.TryReadRegionRect(Instance, SourceB, out int BLeft, out int BTop, out int BRight, out int BBottom)) + { + BLeft = 0; + BTop = 0; + BRight = 0; + BBottom = 0; + } + + int Result = Win32kHelper.CombineRegionRects(Mode, ALeft, ATop, ARight, ABottom, BLeft, BTop, BRight, BBottom, + out int Left, out int Top, out int Right, out int Bottom); + + if (!Win32kHelper.TryWriteRegionRect(Instance, Destination, Left, Top, Right, Bottom)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn((ulong)Win32kHelper.RegionError); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn((ulong)Result); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateCompatibleBitmap.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateCompatibleBitmap.cs new file mode 100644 index 00000000..45b6f13e --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateCompatibleBitmap.cs @@ -0,0 +1,20 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiCreateCompatibleBitmap : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + int Width = unchecked((int)Instance.WinHelper.GetArg32(1)); + int Height = unchecked((int)Instance.WinHelper.GetArg32(2)); + + ulong Bitmap = Win32kHelper.CreateCompatibleBitmap(Instance, Hdc, Width, Height); + + Instance.SetLastWinError(Bitmap == 0 ? Win32kHelper.ERROR_INVALID_PARAMETER : Win32kHelper.ERROR_SUCCESS); + Instance.SetRawSyscallReturn(Bitmap); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateHalftonePalette.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateHalftonePalette.cs new file mode 100644 index 00000000..f4c99215 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateHalftonePalette.cs @@ -0,0 +1,13 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiCreateHalftonePalette : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetRawSyscallReturn(Win32kHelper.CreatePaletteHandle(Instance)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreatePaletteInternal.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreatePaletteInternal.cs new file mode 100644 index 00000000..af45e2e5 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreatePaletteInternal.cs @@ -0,0 +1,13 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiCreatePaletteInternal : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetRawSyscallReturn(Win32kHelper.CreatePaletteHandle(Instance)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateRectRgn.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateRectRgn.cs index e691b7a0..3e8ba0bf 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateRectRgn.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiCreateRectRgn.cs @@ -4,7 +4,6 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k { internal class NtGdiCreateRectRgn : IWinSyscall { - private const byte RegionHandleType = 0x04; private const int RegionObjectSize = 0x30; public NTSTATUS Handle(BinaryEmulator Instance) @@ -20,7 +19,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) int Top = System.Math.Min(Y1, Y2); int Bottom = System.Math.Max(Y1, Y2); - ulong Handle = Instance.WinHelper.AllocateGdiHandle(RegionHandleType); + ulong Handle = Instance.WinHelper.AllocateGdiHandle(Win32kHelper.RegionHandleType); if (Handle == 0) { Instance.SetRawSyscallReturn(0); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiDeleteObjectApp.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiDeleteObjectApp.cs index 791efde1..d6e7282c 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiDeleteObjectApp.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiDeleteObjectApp.cs @@ -8,6 +8,12 @@ public NTSTATUS Handle(BinaryEmulator Instance) { ulong Handle = Instance.WinHelper.GetArg(0); + if (Win32kHelper.IsStockObject(Instance, Handle)) + { + Instance.SetRawSyscallReturn(1ul); + return NTSTATUS.STATUS_SUCCESS; + } + Win32kHelper.RemovePenBrush(Instance, Handle); Win32kHelper.RemoveBitmap(Instance, Handle); Win32kHelper.RemoveFont(Instance, Handle); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiDoPalette.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiDoPalette.cs new file mode 100644 index 00000000..5d810bcb --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiDoPalette.cs @@ -0,0 +1,37 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiDoPalette : IWinSyscall + { + private const uint PaletteGetEntries = 2; + private const uint PaletteGetSystemEntries = 3; + private const uint PaletteGetColorTable = 5; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ushort Start = (ushort)Instance.WinHelper.GetArg(1); + ushort Entries = (ushort)Instance.WinHelper.GetArg(2); + ulong EntriesPtr = Instance.WinHelper.GetArg(3); + uint Function = (uint)Instance.WinHelper.GetArg(4); + _ = Start; + + uint Bytes = (uint)Entries * 4; + bool Reading = Function == PaletteGetEntries || Function == PaletteGetSystemEntries || Function == PaletteGetColorTable; + + // The display never runs a palette, so the colours an app reads back are the identity ones. + if (Reading && EntriesPtr != 0 && Bytes != 0) + { + if (!Instance.IsRegionMapped(EntriesPtr, Bytes) || !Instance.WinHelper.WriteZeroMemory(EntriesPtr, Bytes)) + { + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Entries); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEnumFonts.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEnumFonts.cs new file mode 100644 index 00000000..afa2daad --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEnumFonts.cs @@ -0,0 +1,188 @@ +using System.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.Text; +using Brovan.Core.Emulation.OS.SharedHelpers; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiEnumFonts : IWinSyscall + { + // A record is a DWORD step to the next one at +0, an ENUMLOGFONTEXDVW at +12, then a + // NEWTEXTMETRICEXW. gdi32 walks the reply as a chain of them. + private const int RecordHeaderSize = 12; + private const int LogFontSize = 92; + private const int EnumLogFontExSize = 348; + private const int DesignVectorSize = 8; + private const int NewTextMetricExSize = 100; + private const int MetricsOffset = RecordHeaderSize + EnumLogFontExSize + DesignVectorSize; + private const int RecordSize = MetricsOffset + NewTextMetricExSize; + + private const int FaceNameChars = 32; + private const int FullNameChars = 64; + + private const uint ErrorBufferOverflow = 111; + private const byte DefaultCharSet = 1; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + uint FaceLength = Instance.WinHelper.GetArg32(3); + ulong FaceNamePtr = Instance.WinHelper.GetArg(4); + byte CharSet = (byte)Instance.WinHelper.GetArg32(5); + ulong CountPtr = Instance.WinHelper.GetArg(6); + ulong BufferPtr = Instance.WinHelper.GetArg(7); + + if (CountPtr == 0) + return Fail(Instance, Win32kHelper.ERROR_INVALID_PARAMETER); + + string FaceName = ReadFaceName(Instance, FaceNamePtr, FaceLength); + + IReadOnlyList Faces = Win32kHelper.GetFontFamilies(Instance, FaceName, CharSet == 0 ? DefaultCharSet : CharSet); + ulong Required = (ulong)Faces.Count * RecordSize; + + if (BufferPtr == 0) + { + if (!WriteCount(Instance, CountPtr, Required)) + return Fail(Instance, Win32kHelper.ERROR_INVALID_PARAMETER); + + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + + if (!ReadCount(Instance, CountPtr, out ulong Available)) + return Fail(Instance, Win32kHelper.ERROR_INVALID_PARAMETER); + + if (Available < Required) + { + WriteCount(Instance, CountPtr, Required); + return Fail(Instance, ErrorBufferOverflow); + } + + if (Required != 0) + { + if (!Instance.IsRegionMapped(BufferPtr, Required)) + return Fail(Instance, Win32kHelper.ERROR_INVALID_PARAMETER); + + byte[] Rented = ArrayPool.Shared.Rent((int)Required); + try + { + Span Buffer = Rented.AsSpan(0, (int)Required); + Buffer.Clear(); + + for (int i = 0; i < Faces.Count; i++) + WriteRecord(Buffer.Slice(i * RecordSize, RecordSize), Faces[i]); + + if (!Instance.WriteMemory(BufferPtr, Buffer)) + return Fail(Instance, Win32kHelper.ERROR_INVALID_PARAMETER); + } + finally + { + ArrayPool.Shared.Return(Rented); + } + } + + WriteCount(Instance, CountPtr, Required); + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + + private static void WriteRecord(Span Record, FontFamilyData Face) + { + BinaryPrimitives.WriteUInt32LittleEndian(Record.Slice(0, 4), RecordSize); + BinaryPrimitives.WriteUInt32LittleEndian(Record.Slice(4, 4), MetricsOffset - 8); + BinaryPrimitives.WriteUInt32LittleEndian(Record.Slice(8, 4), Face.FontType); + + Span LogFont = Record.Slice(RecordHeaderSize, EnumLogFontExSize); + BinaryPrimitives.WriteInt32LittleEndian(LogFont.Slice(0, 4), Face.Metrics.Height); + BinaryPrimitives.WriteInt32LittleEndian(LogFont.Slice(16, 4), Face.Weight); + LogFont[20] = Face.Italic ? (byte)1 : (byte)0; + LogFont[23] = Face.CharSet; + LogFont[27] = Face.PitchAndFamily; + WriteString(LogFont.Slice(28, FaceNameChars * 2), Face.FaceName); + WriteString(LogFont.Slice(LogFontSize, FullNameChars * 2), Face.FullName ?? Face.FaceName); + WriteString(LogFont.Slice(LogFontSize + FullNameChars * 2, FaceNameChars * 2), Face.Style); + + Span Metrics = Record.Slice(MetricsOffset, NewTextMetricExSize); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(0, 4), Face.Metrics.Height); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(4, 4), Face.Metrics.Ascent); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(8, 4), Face.Metrics.Descent); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(12, 4), Face.Metrics.InternalLeading); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(16, 4), Face.Metrics.ExternalLeading); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(20, 4), Face.Metrics.AveCharWidth); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(24, 4), Face.Metrics.MaxCharWidth); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(28, 4), Face.Metrics.Weight); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(32, 4), Face.Metrics.Overhang); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(36, 4), Face.Metrics.DigitizedAspectX); + BinaryPrimitives.WriteInt32LittleEndian(Metrics.Slice(40, 4), Face.Metrics.DigitizedAspectY); + BinaryPrimitives.WriteUInt16LittleEndian(Metrics.Slice(44, 2), Face.Metrics.FirstChar); + BinaryPrimitives.WriteUInt16LittleEndian(Metrics.Slice(46, 2), Face.Metrics.LastChar); + BinaryPrimitives.WriteUInt16LittleEndian(Metrics.Slice(48, 2), Face.Metrics.DefaultChar); + BinaryPrimitives.WriteUInt16LittleEndian(Metrics.Slice(50, 2), Face.Metrics.BreakChar); + Metrics[52] = Face.Metrics.Italic; + Metrics[53] = Face.Metrics.Underlined; + Metrics[54] = Face.Metrics.StruckOut; + Metrics[55] = Face.Metrics.PitchAndFamily; + Metrics[56] = Face.Metrics.CharSet; + BinaryPrimitives.WriteUInt32LittleEndian(Metrics.Slice(60, 4), Face.NtmFlags); + BinaryPrimitives.WriteUInt32LittleEndian(Metrics.Slice(64, 4), Face.SizeEm); + BinaryPrimitives.WriteUInt32LittleEndian(Metrics.Slice(68, 4), Face.CellHeight); + BinaryPrimitives.WriteUInt32LittleEndian(Metrics.Slice(72, 4), Face.AvgWidth); + } + + private static void WriteString(Span Target, string Value) + { + if (string.IsNullOrEmpty(Value)) + return; + + int Count = Math.Min(Value.Length, Target.Length / 2 - 1); + for (int i = 0; i < Count; i++) + BinaryPrimitives.WriteUInt16LittleEndian(Target.Slice(i * 2, 2), Value[i]); + } + + private static string ReadFaceName(BinaryEmulator Instance, ulong Address, uint Length) + { + if (Address == 0 || Length < 2 || Length > FaceNameChars) + return null; + + int Bytes = ((int)Length - 1) * 2; + if (!Instance.IsRegionMapped(Address, (ulong)Bytes)) + return null; + + Span Buffer = stackalloc byte[FaceNameChars * 2]; + Span Name = Buffer.Slice(0, Bytes); + if (!Instance.ReadMemory(Address, Name)) + return null; + + return Encoding.Unicode.GetString(Name).TrimEnd('\0'); + } + + // pulCount is a ULONG, four bytes on both architectures. + private const int CountSize = 4; + + private static bool ReadCount(BinaryEmulator Instance, ulong Address, out ulong Value) + { + Value = 0; + if (!Instance.IsRegionMapped(Address, CountSize)) + return false; + + Value = Instance.ReadMemoryUInt(Address); + return true; + } + + private static bool WriteCount(BinaryEmulator Instance, ulong Address, ulong Value) + { + return Instance.IsRegionMapped(Address, CountSize) + && Instance._emulator.WriteMemory(Address, (uint)Value, CountSize); + } + + private static NTSTATUS Fail(BinaryEmulator Instance, uint Error) + { + Instance.SetLastWinError(Error); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEqualRgn.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEqualRgn.cs new file mode 100644 index 00000000..efb7601f --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiEqualRgn.cs @@ -0,0 +1,21 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiEqualRgn : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong First = Instance.WinHelper.GetArg(0); + ulong Second = Instance.WinHelper.GetArg(1); + + bool Equal = Win32kHelper.TryReadRegionRect(Instance, First, out int ALeft, out int ATop, out int ARight, out int ABottom) + && Win32kHelper.TryReadRegionRect(Instance, Second, out int BLeft, out int BTop, out int BRight, out int BBottom) + && ALeft == BLeft && ATop == BTop && ARight == BRight && ABottom == BBottom; + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Equal ? 1UL : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiExtGetObjectW.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiExtGetObjectW.cs index 3a62129f..1529c35e 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiExtGetObjectW.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiExtGetObjectW.cs @@ -6,9 +6,17 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k internal class NtGdiExtGetObjectW : IWinSyscall { private const int Bitmap64Size = 0x20; - private const int LogPen64Size = 0x10; + private const int Bitmap32Size = 0x18; + private const int DibSection64Size = 0x68; + private const int DibSection32Size = 0x54; + private const int BitmapInfoHeaderSize = 0x28; + private const int LogPenSize = 0x10; private const int LogBrush64Size = 0x10; + // lbHatch is a ULONG_PTR, so LOGBRUSH loses its tail padding on x86. + private const int LogBrush32Size = 0x0C; + private const uint BiRgb = 0; + private const uint PsSolid = 0; private const uint BsSolid = 0; @@ -28,12 +36,21 @@ public NTSTATUS Handle(BinaryEmulator Instance) if (!IsBitmap && !IsPenBrush) return Fail(Instance); - int Size = IsBitmap ? Bitmap64Size : (PenBrush.IsPen ? LogPen64Size : LogBrush64Size); + bool Wide = Instance.WinHelper.PointerSize == 8; + int BitmapSize = Wide ? Bitmap64Size : Bitmap32Size; + + // A DIB section answers with the longer form only when the caller asked for all of it. + bool AsDibSection = IsBitmap && Bitmap.DibSection && Count >= (Wide ? DibSection64Size : DibSection32Size); + + int Size = IsBitmap + ? (AsDibSection ? (Wide ? DibSection64Size : DibSection32Size) : BitmapSize) + : (PenBrush.IsPen ? LogPenSize : (Wide ? LogBrush64Size : LogBrush32Size)); if (OutBuffer == 0) { + int Natural = IsBitmap && Bitmap.DibSection ? (Wide ? DibSection64Size : DibSection32Size) : Size; Instance.SetLastWinError(0); - Instance.SetRawSyscallReturn((ulong)Size); + Instance.SetRawSyscallReturn((ulong)Natural); return NTSTATUS.STATUS_SUCCESS; } @@ -44,7 +61,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Buffer.Clear(); if (IsBitmap) - WriteBitmap(Buffer, Bitmap); + WriteBitmap(Buffer, Bitmap, Wide, AsDibSection, BitmapSize); else WritePenBrush(Buffer, PenBrush); @@ -60,7 +77,7 @@ private static NTSTATUS Fail(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } - private static void WriteBitmap(Span Buffer, in Win32kBitmap Bitmap) + private static void WriteBitmap(Span Buffer, in Win32kBitmap Bitmap, bool Wide, bool AsDibSection, int BitmapSize) { BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x04, 4), Bitmap.Width); BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x08, 4), Bitmap.Height); @@ -70,7 +87,24 @@ private static void WriteBitmap(Span Buffer, in Win32kBitmap Bitmap) // Only a DIB section hands its pixels to the caller; a device-dependent bitmap reports no bits. if (Bitmap.DibSection) - BinaryPrimitives.WriteUInt64LittleEndian(Buffer.Slice(0x18, 8), Bitmap.BitsAddress); + { + if (Wide) + BinaryPrimitives.WriteUInt64LittleEndian(Buffer.Slice(0x18, 8), Bitmap.BitsAddress); + else + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x14, 4), (uint)Bitmap.BitsAddress); + } + + if (!AsDibSection) + return; + + Span Header = Buffer.Slice(BitmapSize, BitmapInfoHeaderSize); + BinaryPrimitives.WriteUInt32LittleEndian(Header.Slice(0x00, 4), BitmapInfoHeaderSize); + BinaryPrimitives.WriteInt32LittleEndian(Header.Slice(0x04, 4), Bitmap.Width); + BinaryPrimitives.WriteInt32LittleEndian(Header.Slice(0x08, 4), Bitmap.TopDown ? -Bitmap.Height : Bitmap.Height); + BinaryPrimitives.WriteUInt16LittleEndian(Header.Slice(0x0C, 2), Bitmap.Planes); + BinaryPrimitives.WriteUInt16LittleEndian(Header.Slice(0x0E, 2), Bitmap.BitsPerPixel); + BinaryPrimitives.WriteUInt32LittleEndian(Header.Slice(0x10, 4), BiRgb); + BinaryPrimitives.WriteUInt32LittleEndian(Header.Slice(0x14, 4), Bitmap.BitsSize); } private static void WritePenBrush(Span Buffer, in Win32kPenBrush PenBrush) diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetAppClipBox.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetAppClipBox.cs new file mode 100644 index 00000000..4f3fffeb --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetAppClipBox.cs @@ -0,0 +1,44 @@ +using System.Buffers.Binary; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiGetAppClipBox : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + ulong RectPtr = Instance.WinHelper.GetArg(1); + + if (RectPtr == 0 || !Instance.IsRegionMapped(RectPtr, 16)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn((ulong)Win32kHelper.RegionError); + return NTSTATUS.STATUS_SUCCESS; + } + + if (!Win32kHelper.TryGetDcExtent(Instance, Hdc, out int Width, out int Height)) + { + Width = 0; + Height = 0; + } + + Span Buffer = Instance.WinHelper.Shared.GetSpan(16); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0, 4), 0); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(4, 4), 0); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(8, 4), Width); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(12, 4), Height); + + if (!Instance.WriteMemory(RectPtr, Buffer.Slice(0, 16))) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn((ulong)Win32kHelper.RegionError); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn((ulong)(Width > 0 && Height > 0 ? Win32kHelper.RegionSimple : Win32kHelper.RegionNull)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDCObject.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDCObject.cs new file mode 100644 index 00000000..3562bb33 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDCObject.cs @@ -0,0 +1,36 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiGetDCObject : IWinSyscall + { + // GetCurrentObject maps OBJ_* onto the GDI handle type before it reaches win32k. + private const int BitmapType = 0x050000; + private const int PaletteType = 0x080000; + private const int ColorSpaceType = 0x090000; + private const int FontType = 0x0A0000; + private const int BrushType = 0x100000; + private const int PenType = 0x300000; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + int ObjectType = unchecked((int)Instance.WinHelper.GetArg(1)); + + ulong Object = ObjectType switch + { + PenType => Instance.WinHelper.ReadDcSelectedPen(Hdc), + BrushType => Instance.WinHelper.ReadDcSelectedBrush(Hdc), + FontType => Win32kHelper.GetDcSelectedFont(Instance, Hdc), + BitmapType => Win32kHelper.GetDcSelectedBitmap(Instance, Hdc), + PaletteType => Win32kHelper.GetDcSelectedPalette(Instance, Hdc), + ColorSpaceType => 0, + _ => 0, + }; + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Object); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDCforBitmap.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDCforBitmap.cs new file mode 100644 index 00000000..d0cb95c2 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDCforBitmap.cs @@ -0,0 +1,16 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiGetDCforBitmap : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong BitmapHandle = Instance.WinHelper.GetArg(0); + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Win32kHelper.FindDcForBitmap(Instance, BitmapHandle)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDIBitsInternal.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDIBitsInternal.cs new file mode 100644 index 00000000..79b4e133 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDIBitsInternal.cs @@ -0,0 +1,134 @@ +using System; +using System.Buffers; +using System.Buffers.Binary; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiGetDIBitsInternal : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong BitmapHandle = Instance.WinHelper.GetArg(1); + uint StartScan = (uint)Instance.WinHelper.GetArg(2); + uint Scans = (uint)Instance.WinHelper.GetArg(3); + ulong BitsAddress = Instance.WinHelper.GetArg(4); + ulong HeaderAddress = Instance.WinHelper.GetArg(5); + + if (!Win32kHelper.TryGetBitmap(Instance, BitmapHandle, out Win32kBitmap Bitmap) + || !Win32kHelper.TryReadDibHeader(Instance, HeaderAddress, out Win32kHelper.DibHeader Header)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + if (BitsAddress == 0) + { + bool Described = WriteHeader(Instance, HeaderAddress, Bitmap); + Instance.SetLastWinError(Described ? Win32kHelper.ERROR_SUCCESS : Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(Described ? 1UL : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + + if (Header.BitsPerPixel != 32 && Header.BitsPerPixel != 24) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + int Width = Math.Min(Header.Width, Bitmap.Width); + int Available = Bitmap.Height > (int)StartScan ? Bitmap.Height - (int)StartScan : 0; + int Rows = Math.Min((int)Scans, Available); + if (!Win32kHelper.IsBlitExtentValid(Width, Rows)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + int BytesPerPixel = Header.BitsPerPixel / 8; + int Stride = Win32kHelper.GetBitmapStride(Header.Width, 1, Header.BitsPerPixel, true); + if (Stride <= 0 || !Instance.IsRegionMapped(BitsAddress, (ulong)((long)Stride * Rows))) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + int Count = Width * Rows; + uint[] Pixels = ArrayPool.Shared.Rent(Count); + byte[] LineBuffer = ArrayPool.Shared.Rent(Stride); + int Written = 0; + try + { + Span Block = Pixels.AsSpan(0, Count); + if (!Win32kHelper.TryReadBitmapBlock(Instance, Bitmap, 0, (int)StartScan, Width, Rows, Block)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Span Line = LineBuffer.AsSpan(0, Stride); + + for (int Row = 0; Row < Rows; Row++) + { + Line.Clear(); + ReadOnlySpan Source = Block.Slice(Row * Width, Width); + + for (int Column = 0; Column < Width; Column++) + { + uint Pixel = Source[Column]; + int Offset = Column * BytesPerPixel; + Line[Offset] = (byte)Pixel; + Line[Offset + 1] = (byte)(Pixel >> 8); + Line[Offset + 2] = (byte)(Pixel >> 16); + } + + // The caller's rows follow its own header, bottom-up by default. + int TargetRow = Header.TopDown ? Row : Rows - 1 - Row; + if (!Instance.WriteMemory(BitsAddress + (ulong)((long)TargetRow * Stride), Line)) + break; + + Written++; + } + } + finally + { + ArrayPool.Shared.Return(Pixels); + ArrayPool.Shared.Return(LineBuffer); + } + + WriteHeader(Instance, HeaderAddress, Bitmap); + Instance.SetLastWinError(Written > 0 ? Win32kHelper.ERROR_SUCCESS : Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn((ulong)(uint)Written); + return NTSTATUS.STATUS_SUCCESS; + } + + private static bool WriteHeader(BinaryEmulator Instance, ulong HeaderAddress, in Win32kBitmap Bitmap) + { + if (!Instance.IsRegionMapped(HeaderAddress, Win32kHelper.BitmapInfoHeaderSize)) + return false; + + uint HeaderSize = Instance.ReadMemoryUInt(HeaderAddress); + if (HeaderSize < Win32kHelper.BitmapInfoHeaderSize) + return false; + + int Stride = ((Bitmap.Width * Bitmap.BitsPerPixel + 31) / 32) * 4; + + Span Buffer = Instance.WinHelper.Shared.GetSpan(Win32kHelper.BitmapInfoHeaderSize); + Buffer.Clear(); + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x00, 4), (uint)Win32kHelper.BitmapInfoHeaderSize); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x04, 4), Bitmap.Width); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x08, 4), Bitmap.Height); + BinaryPrimitives.WriteUInt16LittleEndian(Buffer.Slice(0x0C, 2), Bitmap.Planes); + BinaryPrimitives.WriteUInt16LittleEndian(Buffer.Slice(0x0E, 2), Bitmap.BitsPerPixel); + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x10, 4), Win32kHelper.BI_RGB); + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x14, 4), (uint)((long)Stride * Bitmap.Height)); + + return Instance.WriteMemory(HeaderAddress, Buffer); + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDeviceCaps.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDeviceCaps.cs index b50f86da..caf66112 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDeviceCaps.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiGetDeviceCaps.cs @@ -11,6 +11,10 @@ internal class NtGdiGetDeviceCaps : IWinSyscall private const int TenthsOfMillimetrePerInch = 254; + // RASTERCAPS: RC_BITBLT | RC_BITMAP64 | RC_GDI20_OUTPUT | RC_DI_BITMAP | RC_DIBTODEV | RC_BIGFONT + // | RC_STRETCHBLT | RC_STRETCHDIB + private const int RasterCaps = 0x00002E99; + public NTSTATUS Handle(BinaryEmulator Instance) { int Index = unchecked((int)Instance.WinHelper.GetArg(1)); @@ -43,6 +47,8 @@ internal static int GetDeviceCapability(BinaryEmulator Instance, int Index) 12 => 32, // BITSPIXEL 14 => 1, // PLANES 24 => -1, // NUMCOLORS + 38 => RasterCaps, + 108 => 24, // COLORRES 116 => 60, // VREFRESH 121 => 0x00000003, // COLORMGMTCAPS: CM_DEVICE_ICM | CM_GAMMA_RAMP _ => 0, diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiOffsetRgn.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiOffsetRgn.cs new file mode 100644 index 00000000..255d8bc4 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiOffsetRgn.cs @@ -0,0 +1,36 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiOffsetRgn : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Region = Instance.WinHelper.GetArg(0); + int OffsetX = unchecked((int)Instance.WinHelper.GetArg(1)); + int OffsetY = unchecked((int)Instance.WinHelper.GetArg(2)); + + if (!Win32kHelper.TryReadRegionRect(Instance, Region, out int Left, out int Top, out int Right, out int Bottom)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn((ulong)Win32kHelper.RegionError); + return NTSTATUS.STATUS_SUCCESS; + } + + bool Empty = Right <= Left || Bottom <= Top; + if (!Empty) + { + Left += OffsetX; + Right += OffsetX; + Top += OffsetY; + Bottom += OffsetY; + } + + Win32kHelper.TryWriteRegionRect(Instance, Region, Left, Top, Right, Bottom); + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn((ulong)(Empty ? Win32kHelper.RegionNull : Win32kHelper.RegionSimple)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiOpenDCW.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiOpenDCW.cs index 628794d1..1d543a60 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiOpenDCW.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiOpenDCW.cs @@ -6,7 +6,7 @@ internal class NtGdiOpenDCW : IWinSyscall { public NTSTATUS Handle(BinaryEmulator Instance) { - ulong Hdc = Win32kHelper.CreateDeviceContext(Instance, 0, false, false); + ulong Hdc = Win32kHelper.CreateDeviceContext(Instance, 0, false, false, true); Instance.SetLastWinError(Hdc == 0 ? Win32kHelper.ERROR_INVALID_PARAMETER : 0u); Instance.SetRawSyscallReturn(Hdc); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRestoreDC.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRestoreDC.cs new file mode 100644 index 00000000..d97cf3ab --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiRestoreDC.cs @@ -0,0 +1,19 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiRestoreDC : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + int Level = unchecked((int)Instance.WinHelper.GetArg(1)); + + bool Restored = Win32kHelper.RestoreDeviceContext(Instance, Hdc, Level); + + Instance.SetLastWinError(Restored ? Win32kHelper.ERROR_SUCCESS : Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(Restored ? 1UL : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSaveDC.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSaveDC.cs new file mode 100644 index 00000000..c09c92c9 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSaveDC.cs @@ -0,0 +1,17 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiSaveDC : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + int Level = Win32kHelper.SaveDeviceContext(Instance, Hdc); + + Instance.SetLastWinError(Level == 0 ? Win32kHelper.ERROR_INVALID_HANDLE : Win32kHelper.ERROR_SUCCESS); + Instance.SetRawSyscallReturn((ulong)(uint)Level); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetDIBitsToDeviceInternal.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetDIBitsToDeviceInternal.cs new file mode 100644 index 00000000..3e237459 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiSetDIBitsToDeviceInternal.cs @@ -0,0 +1,53 @@ +using System; +using System.Buffers; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiSetDIBitsToDeviceInternal : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + int DestX = unchecked((int)Instance.WinHelper.GetArg(1)); + int DestY = unchecked((int)Instance.WinHelper.GetArg(2)); + int Width = unchecked((int)Instance.WinHelper.GetArg(3)); + int Height = unchecked((int)Instance.WinHelper.GetArg(4)); + int SourceX = unchecked((int)Instance.WinHelper.GetArg(5)); + int SourceY = unchecked((int)Instance.WinHelper.GetArg(6)); + uint StartScan = (uint)Instance.WinHelper.GetArg(7); + uint Scans = (uint)Instance.WinHelper.GetArg(8); + ulong BitsAddress = Instance.WinHelper.GetArg(9); + ulong HeaderAddress = Instance.WinHelper.GetArg(10); + + if (!Win32kHelper.IsBlitExtentValid(Width, Height) + || !Win32kHelper.TryReadDibHeader(Instance, HeaderAddress, out Win32kHelper.DibHeader Header)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + _ = StartScan; + _ = Scans; + + int Count = Width * Height; + uint[] Pixels = ArrayPool.Shared.Rent(Count); + bool Rendered; + try + { + Span Block = Pixels.AsSpan(0, Count); + Rendered = Win32kHelper.TryReadDibBlock(Instance, BitsAddress, Header, SourceX, SourceY, Width, Height, Block) + && Win32kHelper.BlitBlockToDc(Instance, Hdc, DestX, DestY, Width, Height, Block, Width, Height, Win32kHelper.SrcCopyRop); + } + finally + { + ArrayPool.Shared.Return(Pixels); + } + + Instance.SetLastWinError(Rendered ? Win32kHelper.ERROR_SUCCESS : Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(Rendered ? (ulong)(uint)Height : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiStretchDIBitsInternal.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiStretchDIBitsInternal.cs new file mode 100644 index 00000000..56f4cde6 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtGdiStretchDIBitsInternal.cs @@ -0,0 +1,51 @@ +using System; +using System.Buffers; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtGdiStretchDIBitsInternal : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + int DestX = unchecked((int)Instance.WinHelper.GetArg(1)); + int DestY = unchecked((int)Instance.WinHelper.GetArg(2)); + int DestWidth = unchecked((int)Instance.WinHelper.GetArg(3)); + int DestHeight = unchecked((int)Instance.WinHelper.GetArg(4)); + int SourceX = unchecked((int)Instance.WinHelper.GetArg(5)); + int SourceY = unchecked((int)Instance.WinHelper.GetArg(6)); + int SourceWidth = unchecked((int)Instance.WinHelper.GetArg(7)); + int SourceHeight = unchecked((int)Instance.WinHelper.GetArg(8)); + ulong BitsAddress = Instance.WinHelper.GetArg(9); + ulong HeaderAddress = Instance.WinHelper.GetArg(10); + uint Rop = (uint)Instance.WinHelper.GetArg(12); + + if (!Win32kHelper.IsBlitExtentValid(DestWidth, DestHeight) || !Win32kHelper.IsBlitExtentValid(SourceWidth, SourceHeight) + || !Win32kHelper.TryReadDibHeader(Instance, HeaderAddress, out Win32kHelper.DibHeader Header)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + int Count = SourceWidth * SourceHeight; + uint[] Pixels = ArrayPool.Shared.Rent(Count); + bool Rendered; + try + { + Span Block = Pixels.AsSpan(0, Count); + Rendered = Win32kHelper.TryReadDibBlock(Instance, BitsAddress, Header, SourceX, SourceY, SourceWidth, SourceHeight, Block) + && Win32kHelper.BlitBlockToDc(Instance, Hdc, DestX, DestY, DestWidth, DestHeight, Block, SourceWidth, SourceHeight, Rop); + } + finally + { + ArrayPool.Shared.Return(Pixels); + } + + Instance.SetLastWinError(Rendered ? Win32kHelper.ERROR_SUCCESS : Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(Rendered ? (ulong)(uint)SourceHeight : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBeginDeferWindowPos.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBeginDeferWindowPos.cs new file mode 100644 index 00000000..682c6a9c --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBeginDeferWindowPos.cs @@ -0,0 +1,14 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserBeginDeferWindowPos : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Win32kHelper.BeginDeferWindowPos(Instance)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBeginPaint.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBeginPaint.cs index 8b4e2342..b1d70b32 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBeginPaint.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserBeginPaint.cs @@ -26,9 +26,16 @@ public NTSTATUS Handle(BinaryEmulator Instance) } if (!Win32kHelper.WritePaintStruct(Instance, PaintStructPtr, Hdc, Window)) - return NTSTATUS.STATUS_ACCESS_VIOLATION; + { + Win32kHelper.ReleaseDeviceContext(Instance, Hdc); + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } Window.Dirty = false; + Window.PaintPending = false; + Instance.WinHelper.PublishWindowPaintState(Window); Instance.SetLastWinError(0); Instance.SetRawSyscallReturn(Hdc); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserChangeWindowMessageFilterEx.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserChangeWindowMessageFilterEx.cs new file mode 100644 index 00000000..79e3dcb8 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserChangeWindowMessageFilterEx.cs @@ -0,0 +1,15 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserChangeWindowMessageFilterEx : IWinSyscall + { + // One integrity level here, so no message is ever filtered. + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCitSetInfo.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCitSetInfo.cs new file mode 100644 index 00000000..244477e7 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCitSetInfo.cs @@ -0,0 +1,14 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserCitSetInfo : IWinSyscall + { + // Input telemetry, which nothing here collects. + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateEmptyCursorObject.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateEmptyCursorObject.cs new file mode 100644 index 00000000..381a880d --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateEmptyCursorObject.cs @@ -0,0 +1,20 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserCreateEmptyCursorObject : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Handle = Instance.WinHelper.AllocateUserHandle(); + + // The object exists before SetCursorIconDataEx fills it in, so DestroyCursor has to find it. + if (Handle != 0) + Win32kHelper.SetCursorIconData(Instance, Handle, new Win32kHelper.Win32kCursorIcon()); + + Instance.SetLastWinError(Handle == 0 ? Win32kHelper.ERROR_INVALID_PARAMETER : Win32kHelper.ERROR_SUCCESS); + Instance.SetRawSyscallReturn(Handle); + 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 433eeaa6..4d4771ef 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserCreateWindowEx.cs @@ -68,6 +68,15 @@ public NTSTATUS Handle(BinaryEmulator Instance) string title = Win32kHelper.ReadLargeString(Instance, WindowNamePtr) ?? string.Empty; ulong hwnd = Instance.WinHelper.AllocateUserHandle(); + // Without WS_CHILD the argument names the owner, not the parent. + const uint WS_CHILD = 0x40000000; + ulong OwnerHwnd = 0; + if (((uint)StyleArg & WS_CHILD) == 0 && ParentHwnd != Win32kMessageOnlyParent.HwndMessage) + { + OwnerHwnd = ParentHwnd; + ParentHwnd = 0; + } + WinWindow window = new WinWindow { Hwnd = hwnd, @@ -82,6 +91,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Width = (uint)Math.Max(width, 0), Height = (uint)Math.Max(height, 0), ParentHwnd = ParentHwnd, + OwnerHwnd = OwnerHwnd, MenuHandle = MenuHandle, InstanceHandle = InstanceHandle, CreateParam = CreateParam, diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDeferWindowPosAndBand.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDeferWindowPosAndBand.cs new file mode 100644 index 00000000..a3eeb650 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDeferWindowPosAndBand.cs @@ -0,0 +1,29 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserDeferWindowPosAndBand : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong DeferHandle = Instance.WinHelper.GetArg(0); + + Win32kHelper.Win32kDeferredWindowPos Position = new Win32kHelper.Win32kDeferredWindowPos + { + Hwnd = Instance.WinHelper.GetArg(1), + InsertAfter = Instance.WinHelper.GetArg(2), + X = unchecked((int)Instance.WinHelper.GetArg(3)), + Y = unchecked((int)Instance.WinHelper.GetArg(4)), + Width = unchecked((int)Instance.WinHelper.GetArg(5)), + Height = unchecked((int)Instance.WinHelper.GetArg(6)), + Flags = (uint)Instance.WinHelper.GetArg(7), + }; + + bool Added = Win32kHelper.DeferWindowPos(Instance, DeferHandle, Position); + + Instance.SetLastWinError(Added ? 0u : Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn(Added ? DeferHandle : 0UL); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDestroyCursor.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDestroyCursor.cs new file mode 100644 index 00000000..f7d98a10 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserDestroyCursor.cs @@ -0,0 +1,17 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserDestroyCursor : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Cursor = Instance.WinHelper.GetArg(0); + bool Destroyed = Win32kHelper.DestroyCursorIcon(Instance, Cursor); + + Instance.SetLastWinError(Destroyed ? 0u : Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetBooleanSyscallReturn(Destroyed); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEnableWindow.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEnableWindow.cs new file mode 100644 index 00000000..0e5ad7e2 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEnableWindow.cs @@ -0,0 +1,36 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserEnableWindow : IWinSyscall + { + private const uint WS_DISABLED = 0x08000000; + private const uint WM_ENABLE = 0x000A; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + bool Enable = Instance.WinHelper.GetArg(1) != 0; + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + bool WasDisabled = (Window.Style & WS_DISABLED) != 0; + if (WasDisabled == Enable) + { + Window.Style = Enable ? Window.Style & ~WS_DISABLED : Window.Style | WS_DISABLED; + Instance.WinHelper.MaterializeUserWindow(Window); + Win32kHelper.PostMessage(Instance, Hwnd, WM_ENABLE, Enable ? 1UL : 0UL, 0); + } + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(WasDisabled); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEndDeferWindowPosEx.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEndDeferWindowPosEx.cs new file mode 100644 index 00000000..6fae992f --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserEndDeferWindowPosEx.cs @@ -0,0 +1,17 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserEndDeferWindowPosEx : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong DeferHandle = Instance.WinHelper.GetArg(0); + bool Applied = Win32kHelper.EndDeferWindowPos(Instance, DeferHandle); + + Instance.SetLastWinError(Applied ? 0u : Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetBooleanSyscallReturn(Applied); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetCursor.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetCursor.cs new file mode 100644 index 00000000..00361650 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetCursor.cs @@ -0,0 +1,14 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetCursor : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetRawSyscallReturn(Win32kHelper.GetCursorHandle(Instance)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetCursorInfo.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetCursorInfo.cs new file mode 100644 index 00000000..a66aea4d --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetCursorInfo.cs @@ -0,0 +1,52 @@ +using System.Buffers.Binary; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetCursorInfo : IWinSyscall + { + private const int CursorInfo64Size = 0x18; + private const int CursorInfo32Size = 0x14; + private const uint CursorShowing = 0x0001; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong InfoPtr = Instance.WinHelper.GetArg(0); + bool Wide = Instance.WinHelper.PointerSize == 8; + int Size = Wide ? CursorInfo64Size : CursorInfo32Size; + + if (InfoPtr == 0 || !Instance.IsRegionMapped(InfoPtr, (ulong)Size)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Win32kHelper.GetCursorPosition(Instance, out int X, out int Y); + + Span Buffer = Instance.WinHelper.Shared.GetSpan((ulong)Size).Slice(0, Size); + Buffer.Clear(); + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x00, 4), (uint)Size); + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x04, 4), Win32kHelper.IsCursorShowing(Instance) ? CursorShowing : 0); + + int PointOffset = Wide ? 0x10 : 0x0C; + if (Wide) + BinaryPrimitives.WriteUInt64LittleEndian(Buffer.Slice(0x08, 8), Win32kHelper.GetCursorHandle(Instance)); + else + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x08, 4), (uint)Win32kHelper.GetCursorHandle(Instance)); + + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(PointOffset, 4), X); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(PointOffset + 4, 4), Y); + + if (!Instance.WriteMemory(InfoPtr, Buffer)) + { + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDC.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDC.cs index 4a978813..58e51440 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDC.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetDC.cs @@ -8,7 +8,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) { ulong Hwnd = Instance.WinHelper.GetArg(0); - ulong Hdc = Win32kHelper.CreateDeviceContext(Instance, Hwnd, false, false); + ulong Hdc = Win32kHelper.CreateDeviceContext(Instance, Hwnd, false, false, true); if (Hdc == 0) Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetIconSize.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetIconSize.cs new file mode 100644 index 00000000..cef970c9 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetIconSize.cs @@ -0,0 +1,28 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetIconSize : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Icon = Instance.WinHelper.GetArg(0); + ulong WidthPtr = Instance.WinHelper.GetArg(2); + ulong HeightPtr = Instance.WinHelper.GetArg(3); + + if (!Win32kHelper.TryGetCursorIcon(Instance, Icon, out Win32kHelper.Win32kCursorIcon Data) + || WidthPtr == 0 || HeightPtr == 0 + || !Instance.WinHelper.WriteUInt32(WidthPtr, (uint)Data.Width) + || !Instance.WinHelper.WriteUInt32(HeightPtr, (uint)Data.Height)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetMessage.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetMessage.cs index 7e1cbc0a..d2ddf839 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetMessage.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetMessage.cs @@ -8,7 +8,7 @@ private static NTSTATUS ContinueWait(BinaryEmulator Instance, EmulatedThread Thr { WindowsThreadState State = WinEmulatedThread.GetState(Thread); - if (Win32kHelper.TryGetMessage(Instance, State.GetMessageHwndFilter, State.GetMessageMinMessage, State.GetMessageMaxMessage, true, out Win32kMessage Message)) + if (Win32kHelper.TryGetMessage(Instance, State.GetMessageHwndFilter, State.GetMessageMinMessage, State.GetMessageMaxMessage, true, Thread.ThreadId, out Win32kMessage Message)) { bool Written = Win32kHelper.WriteMessage(Instance, State.GetMessageMessagePtr, Message); Instance.WinHelper.ClearWaitState(Thread); @@ -24,6 +24,7 @@ private static NTSTATUS ContinueWait(BinaryEmulator Instance, EmulatedThread Thr return NTSTATUS.STATUS_SUCCESS; } + Thread.WaitDeadline = Win32kHelper.GetNextTimerDue(Instance, State.GetMessageHwndFilter, Thread.ThreadId, State.GetMessageMinMessage, State.GetMessageMaxMessage); Thread.State = EmulatedThreadState.Waiting; WinEmulatedThread.GetState(Thread).ApcAlertable = WinEmulatedThread.GetState(Thread).WaitAlertable; Instance._emulator.WriteRegister(Instance.IPRegister, WinEmulatedThread.GetState(Thread).WaitResumeRIP); @@ -65,7 +66,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } - if (Win32kHelper.TryGetMessage(Instance, HwndFilter, MinMessage, MaxMessage, true, out Win32kMessage Message)) + if (Win32kHelper.TryGetMessage(Instance, HwndFilter, MinMessage, MaxMessage, true, Thread.ThreadId, out Win32kMessage Message)) { if (!Win32kHelper.WriteMessage(Instance, MessagePtr, Message)) return NTSTATUS.STATUS_ACCESS_VIOLATION; @@ -81,7 +82,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Thread.WaitActive = true; Thread.WaitHandles = null; Thread.WaitAll = false; - Thread.WaitDeadline = -1; + Thread.WaitDeadline = Win32kHelper.GetNextTimerDue(Instance, HwndFilter, Thread.ThreadId, MinMessage, MaxMessage); State.WaitCompleted = false; State.WaitStatus = NTSTATUS.STATUS_PENDING; State.WaitResumeRIP = Instance.WinHelper.GetSyscallRip(Thread, false); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetQueueStatus.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetQueueStatus.cs index d97f9308..7833c4cc 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetQueueStatus.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetQueueStatus.cs @@ -7,7 +7,7 @@ internal class NtUserGetQueueStatus : IWinSyscall public NTSTATUS Handle(BinaryEmulator Instance) { uint Flags = (uint)Instance.WinHelper.GetArg(0); - uint Bits = Win32kHelper.GetQueuedWakeBits(Instance, Flags); + uint Bits = Win32kHelper.GetQueuedWakeBits(Instance, Flags, Instance.CurrentThread?.ThreadId ?? 0); // The low half should be only what arrived since the last call, which the queue does not record. Instance.SetRawSyscallReturn((Bits << 16) | Bits); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetRequiredCursorSizes.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetRequiredCursorSizes.cs new file mode 100644 index 00000000..ea1b2bb9 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetRequiredCursorSizes.cs @@ -0,0 +1,15 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetRequiredCursorSizes : IWinSyscall + { + // One cursor size is served here, so user32 has no per-DPI variants to build. + public NTSTATUS Handle(BinaryEmulator Instance) + { + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetUpdateRect.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetUpdateRect.cs new file mode 100644 index 00000000..3ba017c9 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetUpdateRect.cs @@ -0,0 +1,55 @@ +using System.Buffers.Binary; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetUpdateRect : IWinSyscall + { + private const int RectSize = 16; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong RectPtr = 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; + } + + // The update area is one flag here, so a paint owed covers the whole client area. + bool Pending = Window.PaintPending && Window.Visible; + + if (RectPtr != 0) + { + if (!Instance.IsRegionMapped(RectPtr, RectSize)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Win32kHelper.GetClientRect(Instance, Window, out int Left, out int Top, out int Width, out int Height); + + Span Buffer = Instance.WinHelper.Shared.GetSpan(RectSize).Slice(0, RectSize); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x00, 4), Pending ? Left : 0); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x04, 4), Pending ? Top : 0); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x08, 4), Pending ? Left + Width : 0); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x0C, 4), Pending ? Top + Height : 0); + + if (!Instance.WriteMemory(RectPtr, Buffer)) + { + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + } + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(Pending); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetWindowPlacement.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetWindowPlacement.cs new file mode 100644 index 00000000..3f431966 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserGetWindowPlacement.cs @@ -0,0 +1,60 @@ +using System.Buffers.Binary; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserGetWindowPlacement : IWinSyscall + { + private const int WindowPlacementSize = 44; + private const uint SW_SHOWNORMAL = 1; + private const uint SW_SHOWMINIMIZED = 2; + private const uint SW_SHOWMAXIMIZED = 3; + private const uint SW_HIDE = 0; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong PlacementPtr = Instance.WinHelper.GetArg(1); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null || PlacementPtr == 0 || !Instance.IsRegionMapped(PlacementPtr, WindowPlacementSize)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Win32kHelper.GetAbsoluteWindowPosition(Instance, Window, out int Left, out int Top); + int Right = Left + (int)Window.Width; + int Bottom = Top + (int)Window.Height; + + uint ShowCommand = !Window.Visible ? SW_HIDE + : Window.Minimized ? SW_SHOWMINIMIZED + : Window.Maximized ? SW_SHOWMAXIMIZED + : SW_SHOWNORMAL; + + Span Buffer = Instance.WinHelper.Shared.GetSpan(WindowPlacementSize); + Buffer.Clear(); + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x00, 4), WindowPlacementSize); + BinaryPrimitives.WriteUInt32LittleEndian(Buffer.Slice(0x08, 4), ShowCommand); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x0C, 4), Left); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x10, 4), Top); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x14, 4), -1); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x18, 4), -1); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x1C, 4), Left); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x20, 4), Top); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x24, 4), Right); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0x28, 4), Bottom); + + if (!Instance.WriteMemory(PlacementPtr, Buffer.Slice(0, WindowPlacementSize))) + { + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserKillTimer.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserKillTimer.cs new file mode 100644 index 00000000..9f537cb3 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserKillTimer.cs @@ -0,0 +1,19 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserKillTimer : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong TimerId = Instance.WinHelper.GetArg(1); + + bool Killed = Win32kHelper.KillTimer(Instance, Hwnd, TimerId); + + Instance.SetLastWinError(Killed ? Win32kHelper.ERROR_SUCCESS : Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetBooleanSyscallReturn(Killed); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserLockWindowUpdate.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserLockWindowUpdate.cs new file mode 100644 index 00000000..7d81f895 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserLockWindowUpdate.cs @@ -0,0 +1,17 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserLockWindowUpdate : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + bool Locked = Win32kHelper.LockWindowUpdate(Instance, Hwnd); + + Instance.SetLastWinError(Locked ? Win32kHelper.ERROR_SUCCESS : Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(Locked); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMoveWindow.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMoveWindow.cs index fd464aed..6aaed027 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMoveWindow.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMoveWindow.cs @@ -27,7 +27,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Window.Y = Y; Window.Width = (uint)Math.Max(Width, 0); Window.Height = (uint)Math.Max(Height, 0); - Window.Dirty = true; + Win32kHelper.MarkWindowDirty(Instance, Window); Instance.WinHelper.MaterializeUserWindow(Window); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMsgWaitForMultipleObjectsEx.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMsgWaitForMultipleObjectsEx.cs index 21669dc8..7349157e 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMsgWaitForMultipleObjectsEx.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserMsgWaitForMultipleObjectsEx.cs @@ -12,7 +12,7 @@ internal class NtUserMsgWaitForMultipleObjectsEx : IWinSyscall private static bool TryGetSatisfiedIndex(BinaryEmulator Instance, EmulatedThread Thread, List Handles, bool WaitAll, uint WakeMask, out NTSTATUS WaitStatus) { WaitStatus = NTSTATUS.STATUS_SUCCESS; - bool MessageReady = Win32kHelper.HasQueuedInputEvent(Instance, WakeMask); + bool MessageReady = Win32kHelper.HasQueuedInputEvent(Instance, WakeMask, Thread.ThreadId); if (WaitAll) { diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserPeekMessage.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserPeekMessage.cs index 8c715427..3faca9ac 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserPeekMessage.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserPeekMessage.cs @@ -20,7 +20,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } - if (!Win32kHelper.TryGetMessage(Instance, HwndFilter, MinMessage, MaxMessage, Win32kHelper.RemoveFlagSet(Flags), out Win32kMessage Message)) + if (!Win32kHelper.TryGetMessage(Instance, HwndFilter, MinMessage, MaxMessage, Win32kHelper.RemoveFlagSet(Flags), Instance.CurrentThread?.ThreadId ?? 0, out Win32kMessage Message)) { Instance.SetLastWinError(0); if (Win32kHelper.TryDeliverWindowPosChanged(Instance, 0)) diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserPostThreadMessage.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserPostThreadMessage.cs index bb1a3d59..9d27b67d 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserPostThreadMessage.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserPostThreadMessage.cs @@ -6,7 +6,7 @@ internal class NtUserPostThreadMessage : IWinSyscall { public NTSTATUS Handle(BinaryEmulator Instance) { - Instance.WinHelper.GetArg(0); + uint TargetThreadId = (uint)Instance.WinHelper.GetArg(0); uint Message = (uint)Instance.WinHelper.GetArg(1); ulong WParam = Instance.WinHelper.GetArg(2); ulong LParam = Instance.WinHelper.GetArg(3); @@ -18,9 +18,17 @@ public NTSTATUS Handle(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } - // One message queue per process here, so the target thread id cannot pick a queue and a thread - // message is queued with no window. Waking the pump is the part callers depend on. - Win32kHelper.PostMessage(Instance, 0, Message, WParam, LParam); + if (TargetThreadId == 0 + || !Instance.Threads.TryGetValue(TargetThreadId, out EmulatedThread Target) + || Target == null + || Target.State == EmulatedThreadState.Terminated) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_THREAD_ID); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Win32kHelper.PostThreadMessage(Instance, TargetThreadId, Message, WParam, LParam); Instance.SetLastWinError(0); Instance.SetBooleanSyscallReturn(true); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRealizePalette.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRealizePalette.cs new file mode 100644 index 00000000..b44e7e7a --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRealizePalette.cs @@ -0,0 +1,17 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserRealizePalette : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + + // Nothing maps on a display that is not palettised, so no entry changes. + Instance.SetLastWinError(Win32kHelper.IsKnownDc(Instance, Hdc) ? 0u : Win32kHelper.ERROR_INVALID_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRedrawWindow.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRedrawWindow.cs index 31eeb303..28e0d436 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRedrawWindow.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserRedrawWindow.cs @@ -4,15 +4,60 @@ namespace Brovan.Core.Emulation.OS.Windows.Win32k { internal class NtUserRedrawWindow : IWinSyscall { + private const uint RdwInvalidate = 0x0001; + private const uint RdwValidate = 0x0008; + private const uint RdwUpdateNow = 0x0100; + public NTSTATUS Handle(BinaryEmulator Instance) { - ulong Hwnd = Instance.WinHelper.GetArg(0); - ulong RectPtr = Instance.WinHelper.GetArg(1); - ulong Region = Instance.WinHelper.GetArg(2); - uint Flags = (uint)Instance.WinHelper.GetArg(3); + uint Flags = (uint)Instance.WinHelper.GetArg32(3); + + if ((Flags & RdwValidate) != 0) + { + WinWindow Target = Instance.WinHelper.GetWindow(Hwnd); + if (Target == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Target.Dirty = false; + Target.PaintPending = false; + Instance.WinHelper.PublishWindowPaintState(Target); + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + + // The re-run after the paint callback must not invalidate a second time. + if (Win32kHelper.TakePaintRetry(Instance, Hwnd)) + { + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + + bool Success = (Flags & RdwInvalidate) != 0 + ? Win32kHelper.InvalidateWindow(Instance, Hwnd) + : Hwnd == 0 || Window != null; + + // WM_PAINT runs the window procedure, so this syscall runs again when the callback returns. + if (Success && (Flags & RdwUpdateNow) != 0 && Window != null && 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, Hwnd)) + return NTSTATUS.STATUS_SUCCESS; + + Win32kHelper.MarkWindowDirty(Instance, Window); + } - bool Success = Win32kHelper.InvalidateWindow(Instance, Hwnd); Instance.SetLastWinError(Success ? 0u : Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); Instance.SetBooleanSyscallReturn(Success); return NTSTATUS.STATUS_SUCCESS; diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSelectPalette.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSelectPalette.cs new file mode 100644 index 00000000..01589846 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSelectPalette.cs @@ -0,0 +1,17 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSelectPalette : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hdc = Instance.WinHelper.GetArg(0); + ulong Palette = Instance.WinHelper.GetArg(1); + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Win32kHelper.SelectDcPalette(Instance, Hdc, Palette)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetClassLongPtr.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetClassLongPtr.cs new file mode 100644 index 00000000..26dd5225 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetClassLongPtr.cs @@ -0,0 +1,85 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetClassLongPtr : IWinSyscall + { + private const int GclpHbrBackground = -10; + private const int GclpHcursor = -12; + private const int GclpHicon = -14; + private const int GclpHmodule = -16; + private const int GclCbWndExtra = -18; + private const int GclCbClsExtra = -20; + private const int GclpWndProc = -24; + private const int GclStyle = -26; + private const int GclpHiconSm = -34; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + int Index = unchecked((int)Instance.WinHelper.GetArg32(1)); + ulong Value = Instance.WinHelper.GetArg(2); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + WinWindowClass Class = Window == null || Window.ClassAtom == 0 + ? null + : Instance.WinHelper.GetWindowClass(Window.ClassAtom); + + if (Class == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + ulong Previous; + switch (Index) + { + case GclpHbrBackground: + Previous = Class.BackgroundBrush; + Class.BackgroundBrush = Value; + break; + case GclpHcursor: + Previous = Class.CursorHandle; + Class.CursorHandle = Value; + break; + case GclpHicon: + Previous = Class.IconHandle; + Class.IconHandle = Value; + break; + case GclpHiconSm: + Previous = Class.SmallIconHandle; + Class.SmallIconHandle = Value; + break; + case GclpHmodule: + Previous = Class.InstanceHandle; + Class.InstanceHandle = Value; + break; + case GclpWndProc: + Previous = Class.WndProc; + Class.WndProc = Value; + break; + case GclStyle: + Previous = Class.Style; + Class.Style = (uint)Value; + break; + case GclCbClsExtra: + Previous = (ulong)Class.ClassExtraBytes; + Class.ClassExtraBytes = unchecked((int)Value); + break; + case GclCbWndExtra: + Previous = (ulong)Class.WindowExtraBytes; + Class.WindowExtraBytes = unchecked((int)Value); + break; + default: + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetRawSyscallReturn(Previous); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetCursorIconDataEx.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetCursorIconDataEx.cs new file mode 100644 index 00000000..60210582 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetCursorIconDataEx.cs @@ -0,0 +1,89 @@ +using System.Buffers.Binary; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetCursorIconDataEx : IWinSyscall + { + // CURSORDATA, which CreateIconIndirect fills in and hands to win32k. + private const int CursorData64Size = 0x88; + private const int CursorData32Size = 0x54; + + private const int Flags64Offset = 0x14; + private const int Hotspot64Offset = 0x18; + private const int MaskBitmap64Offset = 0x20; + private const int ColorBitmap64Offset = 0x28; + private const int Bpp64Offset = 0x50; + private const int Width64Offset = 0x54; + private const int Height64Offset = 0x58; + + private const int Flags32Offset = 0x0C; + private const int Hotspot32Offset = 0x10; + private const int MaskBitmap32Offset = 0x14; + private const int ColorBitmap32Offset = 0x18; + private const int Bpp32Offset = 0x34; + private const int Width32Offset = 0x38; + private const int Height32Offset = 0x3C; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Cursor = Instance.WinHelper.GetArg(0); + ulong DataPtr = Instance.WinHelper.GetArg(3); + + bool Wide = Instance.WinHelper.PointerSize == 8; + int Size = Wide ? CursorData64Size : CursorData32Size; + + if (Cursor == 0 || DataPtr == 0 || !Instance.IsRegionMapped(DataPtr, (ulong)Size)) + return Fail(Instance); + + Span Buffer = Instance.WinHelper.Shared.GetSpan((ulong)Size).Slice(0, Size); + if (!Instance.ReadMemory(DataPtr, Buffer)) + return Fail(Instance); + + Win32kHelper.Win32kCursorIcon Data = new() + { + Flags = BinaryPrimitives.ReadUInt32LittleEndian(Buffer.Slice(Wide ? Flags64Offset : Flags32Offset, 4)), + HotspotX = BinaryPrimitives.ReadInt16LittleEndian(Buffer.Slice(Wide ? Hotspot64Offset : Hotspot32Offset, 2)), + HotspotY = BinaryPrimitives.ReadInt16LittleEndian(Buffer.Slice((Wide ? Hotspot64Offset : Hotspot32Offset) + 2, 2)), + MaskBitmap = ReadHandle(Buffer, Wide ? MaskBitmap64Offset : MaskBitmap32Offset, Wide), + ColorBitmap = ReadHandle(Buffer, Wide ? ColorBitmap64Offset : ColorBitmap32Offset, Wide), + BitsPerPixel = BinaryPrimitives.ReadUInt32LittleEndian(Buffer.Slice(Wide ? Bpp64Offset : Bpp32Offset, 4)), + Width = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(Wide ? Width64Offset : Width32Offset, 4)), + Height = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(Wide ? Height64Offset : Height32Offset, 4)), + }; + + // The mask holds the AND and XOR images one above the other unless a colour bitmap is given. + if ((Data.Width <= 0 || Data.Height <= 0) && Win32kHelper.TryGetBitmap(Instance, Data.MaskBitmap, out Win32kBitmap Mask)) + { + Data.Width = Mask.Width; + Data.Height = Data.ColorBitmap != 0 ? Mask.Height : Mask.Height / 2; + } + + if (Data.Width <= 0 || Data.Height <= 0) + return Fail(Instance); + + if (Data.BitsPerPixel == 0) + Data.BitsPerPixel = Data.ColorBitmap != 0 ? 32u : 1u; + + Win32kHelper.SetCursorIconData(Instance, Cursor, Data); + + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + + private static ulong ReadHandle(ReadOnlySpan Buffer, int Offset, bool Wide) + { + return Wide + ? BinaryPrimitives.ReadUInt64LittleEndian(Buffer.Slice(Offset, 8)) + : BinaryPrimitives.ReadUInt32LittleEndian(Buffer.Slice(Offset, 4)); + } + + private static NTSTATUS Fail(BinaryEmulator Instance) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetDialogPointer.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetDialogPointer.cs index a0f12c80..8a477ff7 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetDialogPointer.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetDialogPointer.cs @@ -20,7 +20,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) // 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; + Win32kHelper.MarkWindowDirty(Instance, Window); Instance.WinHelper.MaterializeUserWindow(Window); Instance.SetLastWinError(0); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetMenu.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetMenu.cs new file mode 100644 index 00000000..c571f7ec --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetMenu.cs @@ -0,0 +1,28 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetMenu : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong Menu = 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.MenuHandle = Menu; + Instance.WinHelper.MaterializeUserWindow(Window); + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetTimer.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetTimer.cs new file mode 100644 index 00000000..95b19461 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetTimer.cs @@ -0,0 +1,26 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetTimer : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong TimerId = Instance.WinHelper.GetArg(1); + uint Elapse = (uint)Instance.WinHelper.GetArg(2); + ulong TimerProc = Instance.WinHelper.GetArg(3); + + if (Hwnd != 0 && Instance.WinHelper.GetWindow(Hwnd) == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetRawSyscallReturn(0); + return NTSTATUS.STATUS_SUCCESS; + } + + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetRawSyscallReturn(Win32kHelper.SetTimer(Instance, Hwnd, TimerId, Elapse, TimerProc)); + 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 17e21007..9f8e61fd 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLong.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLong.cs @@ -76,7 +76,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } - Window.Dirty = true; + Win32kHelper.MarkWindowDirty(Instance, Window); Instance.WinHelper.MaterializeUserWindow(Window); Instance.WinHelper.PresentDesktop(); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLongPtr.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLongPtr.cs index eadc568a..e765bce9 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLongPtr.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowLongPtr.cs @@ -76,7 +76,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) return NTSTATUS.STATUS_SUCCESS; } - Window.Dirty = true; + Win32kHelper.MarkWindowDirty(Instance, Window); Instance.WinHelper.MaterializeUserWindow(Window); Instance.WinHelper.PresentDesktop(); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowPlacement.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowPlacement.cs new file mode 100644 index 00000000..a53a1ff9 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowPlacement.cs @@ -0,0 +1,67 @@ +using System.Buffers.Binary; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserSetWindowPlacement : IWinSyscall + { + private const int WindowPlacementSize = 44; + private const uint SwpNoZOrder = 0x0004; + private const uint SwpShowWindow = 0x0040; + private const uint SwpHideWindow = 0x0080; + + private const uint SwHide = 0; + private const uint SwShowMinimized = 2; + private const uint SwShowMaximized = 3; + private const uint SwShowMinNoActive = 7; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong PlacementPtr = Instance.WinHelper.GetArg(1); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null || PlacementPtr == 0 || !Instance.IsRegionMapped(PlacementPtr, WindowPlacementSize)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + Span Buffer = Instance.WinHelper.Shared.GetSpan(WindowPlacementSize).Slice(0, WindowPlacementSize); + if (!Instance.ReadMemory(PlacementPtr, Buffer)) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_PARAMETER); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + uint ShowCommand = BinaryPrimitives.ReadUInt32LittleEndian(Buffer.Slice(0x08, 4)); + int Left = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(0x1C, 4)); + int Top = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(0x20, 4)); + int Right = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(0x24, 4)); + int Bottom = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(0x28, 4)); + + Window.Minimized = ShowCommand == SwShowMinimized || ShowCommand == SwShowMinNoActive; + Window.Maximized = ShowCommand == SwShowMaximized; + + uint Flags = SwpNoZOrder | (ShowCommand == SwHide ? SwpHideWindow : SwpShowWindow); + + Win32kHelper.ApplyWindowPos(Instance, new Win32kHelper.Win32kDeferredWindowPos + { + Hwnd = Hwnd, + X = Left, + Y = Top, + Width = Right - Left, + Height = Bottom - Top, + Flags = Flags, + }); + + Instance.WinHelper.PresentDesktop(); + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowPos.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowPos.cs index 2adad7ad..e20bf7ca 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowPos.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserSetWindowPos.cs @@ -60,7 +60,8 @@ public NTSTATUS Handle(BinaryEmulator Instance) Instance.WinHelper.UpdateTopLevelWindowZOrder(Hwnd, InsertAfter); } - Window.Dirty = true; + Win32kHelper.MarkWindowDirty(Instance, Window); + Instance.WinHelper.MaterializeUserWindow(Window); Instance.WinHelper.PresentDesktop(); Instance.SetLastWinError(0); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowWindow.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowWindow.cs index f3b0271c..ad417d71 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowWindow.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserShowWindow.cs @@ -63,7 +63,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) break; } - Window.Dirty = true; + Win32kHelper.MarkWindowDirty(Instance, Window); if (Window.ParentHwnd == 0 && Window.Visible && !Instance.WinHelper.TopLevelWindows.Contains(Window.Hwnd)) Instance.WinHelper.TopLevelWindows.Add(Window.Hwnd); @@ -82,6 +82,8 @@ public NTSTATUS Handle(BinaryEmulator Instance) Win32kHelper.InvalidateWindowTree(Instance, Window.Hwnd); } + // user32 answers IsWindowVisible and GetWindowLong out of the client window object. + Instance.WinHelper.MaterializeUserWindow(Window); Instance.WinHelper.PresentDesktop(); Instance.SetLastWinError(0); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateLayeredWindow.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateLayeredWindow.cs new file mode 100644 index 00000000..52c0bbff --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateLayeredWindow.cs @@ -0,0 +1,92 @@ +using System.Buffers; +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserUpdateLayeredWindow : IWinSyscall + { + private const uint SrcCopy = 0x00CC0020; + + public NTSTATUS Handle(BinaryEmulator Instance) + { + ulong Hwnd = Instance.WinHelper.GetArg(0); + ulong DestPointPtr = Instance.WinHelper.GetArg(2); + ulong SizePtr = Instance.WinHelper.GetArg(3); + ulong SourceDc = Instance.WinHelper.GetArg(4); + ulong SourcePointPtr = Instance.WinHelper.GetArg(5); + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + if (Window == null) + { + Instance.SetLastWinError(Win32kHelper.ERROR_INVALID_WINDOW_HANDLE); + Instance.SetBooleanSyscallReturn(false); + return NTSTATUS.STATUS_SUCCESS; + } + + int Width = (int)Window.Width; + int Height = (int)Window.Height; + + if (SizePtr != 0 && TryReadPair(Instance, SizePtr, out int NewWidth, out int NewHeight)) + { + Width = NewWidth; + Height = NewHeight; + } + + int DestX = Window.X; + int DestY = Window.Y; + bool Moved = DestPointPtr != 0 && TryReadPair(Instance, DestPointPtr, out DestX, out DestY); + + uint Flags = Win32kHelper.SwpNoZOrder | Win32kHelper.SwpNoActivate | (Moved ? 0u : Win32kHelper.SwpNoMove); + Win32kHelper.ApplyWindowPos(Instance, new Win32kHelper.Win32kDeferredWindowPos + { + Hwnd = Hwnd, + X = DestX, + Y = DestY, + Width = Width, + Height = Height, + Flags = Flags, + }); + + int SourceX = 0; + int SourceY = 0; + if (SourcePointPtr != 0) + TryReadPair(Instance, SourcePointPtr, out SourceX, out SourceY); + + // The layered surface is the whole window. + if (SourceDc != 0 && Win32kHelper.IsBlitExtentValid(Width, Height)) + { + int Count = Width * Height; + uint[] Pixels = ArrayPool.Shared.Rent(Count); + try + { + Span Block = Pixels.AsSpan(0, Count); + if (Win32kHelper.TryReadDcBlock(Instance, SourceDc, SourceX, SourceY, Width, Height, Block)) + Win32kHelper.BlitBlockToWindow(Instance, Hwnd, 0, 0, Width, Height, Block, Width, Height, SrcCopy); + } + finally + { + ArrayPool.Shared.Return(Pixels); + } + } + + Instance.WinHelper.PresentDesktop(); + + Instance.SetLastWinError(Win32kHelper.ERROR_SUCCESS); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + + private static bool TryReadPair(BinaryEmulator Instance, ulong Address, out int First, out int Second) + { + First = 0; + Second = 0; + + if (!Instance.IsRegionMapped(Address, 8)) + return false; + + First = unchecked((int)Instance.ReadMemoryUInt(Address)); + Second = unchecked((int)Instance.ReadMemoryUInt(Address + 4)); + return true; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateWindow.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateWindow.cs index d9e73f8d..4fe5bd14 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateWindow.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserUpdateWindow.cs @@ -28,7 +28,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Win32kHelper.InvokeWindowProc(Instance, Hwnd, Window.WndProc, Win32kHelper.WM_PAINT, 0, 0, null, SyscallRip)) return NTSTATUS.STATUS_SUCCESS; - Window.Dirty = true; + Win32kHelper.MarkWindowDirty(Instance, Window); } Instance.SetBooleanSyscallReturn(true); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserValidateRect.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserValidateRect.cs new file mode 100644 index 00000000..1f8d0aec --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserValidateRect.cs @@ -0,0 +1,29 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserValidateRect : 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; + } + + // The update area is one flag here, so validating any part of it validates the window. + Window.Dirty = false; + Window.PaintPending = false; + Instance.WinHelper.PublishWindowPaintState(Window); + + Instance.SetLastWinError(0); + Instance.SetBooleanSyscallReturn(true); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserValidateRgn.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserValidateRgn.cs new file mode 100644 index 00000000..ccff7ce6 --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserValidateRgn.cs @@ -0,0 +1,28 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserValidateRgn : 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; + } + + Window.Dirty = false; + Window.PaintPending = false; + Instance.WinHelper.PublishWindowPaintState(Window); + + Instance.SetLastWinError(0); + 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 index 1f1e1184..f3888a04 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWaitMessage.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWaitMessage.cs @@ -20,7 +20,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) return Status; } - if (Win32kHelper.HasQueuedInputEvent(Instance, Win32kHelper.QS_ALLINPUT)) + if (Win32kHelper.HasQueuedInputEvent(Instance, Win32kHelper.QS_ALLINPUT, Thread.ThreadId)) { Instance.SetRawSyscallReturn(1); return NTSTATUS.STATUS_SUCCESS; @@ -31,7 +31,7 @@ public NTSTATUS Handle(BinaryEmulator Instance) Thread.WaitActive = true; Thread.WaitHandles = null; Thread.WaitAll = false; - Thread.WaitDeadline = -1; + Thread.WaitDeadline = Win32kHelper.GetNextTimerDue(Instance, 0, Thread.ThreadId, 0, 0); State.WaitCompleted = false; State.WaitStatus = NTSTATUS.STATUS_PENDING; State.WaitResumeRIP = Instance.WinHelper.GetSyscallRip(Thread, false); diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWindowFromPoint.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWindowFromPoint.cs new file mode 100644 index 00000000..9a3dbaaa --- /dev/null +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/NtUserWindowFromPoint.cs @@ -0,0 +1,17 @@ +using static Brovan.Core.Helpers.BinaryHelpers; + +namespace Brovan.Core.Emulation.OS.Windows.Win32k +{ + internal class NtUserWindowFromPoint : IWinSyscall + { + public NTSTATUS Handle(BinaryEmulator Instance) + { + int X = unchecked((int)Instance.WinHelper.GetArg(0)); + int Y = unchecked((int)Instance.WinHelper.GetArg(1)); + + Instance.SetLastWinError(0); + Instance.SetRawSyscallReturn(Win32kHelper.WindowFromPoint(Instance, X, Y)); + return NTSTATUS.STATUS_SUCCESS; + } + } +} diff --git a/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs b/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs index b5dcc5ea..1cd04161 100644 --- a/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs +++ b/Brovan/Core/Emulation/OS/Windows/Win32k/Win32kHelper.cs @@ -19,7 +19,10 @@ internal readonly struct Win32kMessage public readonly int X; public readonly int Y; - public Win32kMessage(ulong Hwnd, uint Message, ulong WParam, ulong LParam, uint Time, int X, int Y) + // Set only for a thread message, which has no window to name its reader. + public readonly uint TargetThreadId; + + public Win32kMessage(ulong Hwnd, uint Message, ulong WParam, ulong LParam, uint Time, int X, int Y, uint TargetThreadId = 0) { this.Hwnd = Hwnd; this.Message = Message; @@ -28,6 +31,7 @@ public Win32kMessage(ulong Hwnd, uint Message, ulong WParam, ulong LParam, uint this.Time = Time; this.X = X; this.Y = Y; + this.TargetThreadId = TargetThreadId; } } @@ -91,6 +95,9 @@ internal static class Win32kHelper { private const uint EtoOpaque = 0x0002; + private const uint USER_TIMER_MINIMUM = 0x0000000A; + private const uint USER_TIMER_MAXIMUM = 0x7FFFFFFF; + internal const uint ERROR_SUCCESS = 0; internal const uint ERROR_INVALID_HANDLE = 6; internal const uint ERROR_ACCESS_DENIED = 5; @@ -99,6 +106,7 @@ internal static class Win32kHelper 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 uint ERROR_INVALID_THREAD_ID = 1444; internal const int MaxClassExtraBytes = 0x10000; @@ -108,7 +116,9 @@ internal static class Win32kHelper internal const byte PenHandleType = 0x30; internal const byte BrushHandleType = 0x10; internal const byte BitmapHandleType = 0x05; + internal const byte RegionHandleType = 0x04; internal const byte FontHandleType = 0x0A; + internal const byte PaletteHandleType = 0x08; internal const uint WM_NULL = 0x0000; internal const uint WM_CREATE = 0x0001; @@ -143,6 +153,7 @@ internal static class Win32kHelper internal const int COLOR_BTNFACE = 15; internal const uint WM_NCDESTROY = 0x0082; internal const uint WM_PAINT = 0x000F; + internal const uint WM_TIMER = 0x0113; internal const uint WM_SETTEXT = 0x000C; internal const uint WM_KEYDOWN = 0x0100; internal const uint WM_KEYUP = 0x0101; @@ -190,6 +201,7 @@ internal static class Win32kHelper private const int HTCLIENT = 1; private const ulong HWND_BROADCAST = 0xFFFF; private const ulong FirstDeviceContextHandle = 0x770001; + private const ulong FirstDeferWindowPosHandle = 0x780001; private const uint PM_REMOVE = 0x0001; private const int MSG64_SIZE = 48; private const int MSG32_SIZE = 28; @@ -197,6 +209,7 @@ internal static class Win32kHelper private const int PAINTSTRUCT32_SIZE = 64; private const int MaxWindowTextBytes = 0x1000; private const long MaxBitmapBytes = 0x40000000; + private const long MaxBlitPixels = MaxBitmapBytes / 4; private const uint BitmapCopyChunkBytes = 0x10000; private static readonly ConditionalWeakTable States = new(); @@ -204,15 +217,20 @@ internal static class Win32kHelper private sealed class Win32kState { public readonly Queue MessageQueue = new(); + public readonly List Timers = new(); + public readonly Dictionary CursorIcons = new(); + public ulong NextWindowlessTimerId = 1; public readonly Dictionary DeviceContexts = new(); public readonly Dictionary PenBrushObjects = new(); public readonly Dictionary Bitmaps = new(); public readonly Dictionary Fonts = new(); + public readonly Dictionary> FontFamilies = new(); // Advance width per character, biased by one so that zero reads as unmeasured. public readonly Dictionary CharAdvanceWidthsByFont = new(); public ulong StockBitmap; + public ulong DisplaySurfaceBitmap; public ulong NextDeviceContext = FirstDeviceContextHandle; public ulong CaptureWindow; public ulong ActivatedWindow; @@ -221,6 +239,7 @@ private sealed class Win32kState public int CursorX; public int CursorY; public ulong CursorHandle; + public ulong UpdateLockWindow; public ulong StockCursor; public bool CursorAssigned; public int CursorShowCount; @@ -241,6 +260,9 @@ private sealed class Win32kState public readonly byte[] KeyState = new byte[256]; public readonly byte[] KeyPressedSinceQuery = new byte[256]; + public readonly Dictionary> DeferredWindowPositions = new(); + public ulong NextDeferHandle = FirstDeferWindowPosHandle; + public Win32kCaret Caret; public uint QueuedWakeBits; @@ -258,6 +280,39 @@ private sealed class Win32kState public ulong PointerTargetHwnd; } + internal struct Win32kDeferredWindowPos + { + public ulong Hwnd; + public ulong InsertAfter; + public int X; + public int Y; + public int Width; + public int Height; + public uint Flags; + } + + internal sealed class Win32kCursorIcon + { + public ulong MaskBitmap; + public ulong ColorBitmap; + public int Width; + public int Height; + public int HotspotX; + public int HotspotY; + public uint Flags; + public uint BitsPerPixel; + } + + private sealed class Win32kTimer + { + public ulong Hwnd; + public ulong Id; + public ulong Proc; + public uint Elapse; + public long Due; + public uint ThreadId; + } + internal sealed class Win32kCaret { public ulong Hwnd; @@ -275,14 +330,23 @@ private sealed class Win32kFont public IntPtr HostFont; } + private struct Win32kDcState + { + public ulong Bitmap; + public ulong Font; + } + private sealed class Win32kDeviceContext { public ulong Handle; public ulong Hwnd; public bool WindowDc; public bool PaintDc; + public bool Display; public ulong SelectedBitmap; public ulong SelectedFont; + public ulong SelectedPalette; + public List SavedStates; public uint BoundsFlags; public int BoundsLeft; public int BoundsTop; @@ -295,6 +359,85 @@ private static Win32kState GetState(BinaryEmulator Instance) return States.GetValue(Instance, static _ => new Win32kState()); } + internal const uint SwpNoSize = 0x0001; + internal const uint SwpNoMove = 0x0002; + internal const uint SwpNoZOrder = 0x0004; + internal const uint SwpNoActivate = 0x0010; + + internal static bool ApplyWindowPos(BinaryEmulator Instance, in Win32kDeferredWindowPos Position) + { + const uint SWP_NOSIZE = SwpNoSize; + const uint SWP_NOMOVE = SwpNoMove; + const uint SWP_NOZORDER = SwpNoZOrder; + const uint SWP_SHOWWINDOW = 0x0040; + const uint SWP_HIDEWINDOW = 0x0080; + const uint WS_VISIBLE = 0x10000000; + + WinWindow Window = Instance.WinHelper.GetWindow(Position.Hwnd); + if (Window == null) + return false; + + if ((Position.Flags & SWP_NOMOVE) == 0) + { + Window.X = Position.X; + Window.Y = Position.Y; + } + + if ((Position.Flags & SWP_NOSIZE) == 0) + { + Window.Width = (uint)Math.Max(Position.Width, 0); + Window.Height = (uint)Math.Max(Position.Height, 0); + } + + if ((Position.Flags & SWP_HIDEWINDOW) != 0) + { + Window.Visible = false; + Window.Style &= ~WS_VISIBLE; + } + else if ((Position.Flags & SWP_SHOWWINDOW) != 0) + { + Window.Visible = true; + Window.Style |= WS_VISIBLE; + } + + if (Window.ParentHwnd == 0 && (Position.Flags & SWP_NOZORDER) == 0) + Instance.WinHelper.UpdateTopLevelWindowZOrder(Position.Hwnd, Position.InsertAfter); + + MarkWindowDirty(Instance, Window); + Instance.WinHelper.MaterializeUserWindow(Window); + return true; + } + + internal static ulong BeginDeferWindowPos(BinaryEmulator Instance) + { + Win32kState State = GetState(Instance); + ulong Handle = State.NextDeferHandle++; + State.DeferredWindowPositions[Handle] = new List(); + return Handle; + } + + internal static bool DeferWindowPos(BinaryEmulator Instance, ulong Handle, in Win32kDeferredWindowPos Position) + { + if (!GetState(Instance).DeferredWindowPositions.TryGetValue(Handle, out List Positions)) + return false; + + Positions.Add(Position); + return true; + } + + internal static bool EndDeferWindowPos(BinaryEmulator Instance, ulong Handle) + { + Win32kState State = GetState(Instance); + if (!State.DeferredWindowPositions.Remove(Handle, out List Positions)) + return false; + + foreach (Win32kDeferredWindowPos Position in Positions) + ApplyWindowPos(Instance, Position); + + Instance.WinHelper.PresentDesktop(); + return true; + } + internal static bool CreateCaret(BinaryEmulator Instance, ulong Hwnd, ulong Bitmap, int Width, int Height) { if (Instance.WinHelper.GetWindow(Hwnd) == null) @@ -349,7 +492,7 @@ internal static bool IsKnownWindow(BinaryEmulator Instance, ulong Hwnd) return Hwnd == 0 || Instance.WinHelper.GetWindow(Hwnd) != null; } - internal static ulong CreateDeviceContext(BinaryEmulator Instance, ulong Hwnd, bool WindowDc, bool PaintDc) + internal static ulong CreateDeviceContext(BinaryEmulator Instance, ulong Hwnd, bool WindowDc, bool PaintDc, bool Display = false) { if (Hwnd != 0 && Instance.WinHelper.GetWindow(Hwnd) == null) return 0; @@ -365,6 +508,7 @@ internal static ulong CreateDeviceContext(BinaryEmulator Instance, ulong Hwnd, b Hwnd = Hwnd, WindowDc = WindowDc, PaintDc = PaintDc, + Display = Display || Hwnd != 0, SelectedBitmap = EnsureStockBitmap(Instance), }; return GdiHandle; @@ -561,6 +705,14 @@ internal static ulong CreateSolidBrush(BinaryEmulator Instance, uint ColorRef) return Handle; } + internal static ulong CreatePaletteHandle(BinaryEmulator Instance) + { + ulong Handle = Instance.WinHelper.AllocateGdiHandle(PaletteHandleType); + + Instance.SetLastWinError(Handle == 0 ? ERROR_INVALID_PARAMETER : ERROR_SUCCESS); + return Handle; + } + internal static Win32kPenBrush ResolvePenBrush(BinaryEmulator Instance, ulong Handle, bool IsPen) { if (Handle != 0 && GetState(Instance).PenBrushObjects.TryGetValue(Handle, out Win32kPenBrush Found)) @@ -860,6 +1012,23 @@ internal static int GetBitmapStride(int Width, int Planes, int BitsPerPixel, boo return DibSection ? (int)(((Bits + 31) / 32) * 4) : (int)(((Bits + 15) / 16) * 2); } + // A blit block is built in host memory, so a guest extent gets the budget a bitmap gets. + internal static bool IsBlitExtentValid(int Width, int Height) + { + return Width > 0 && Height > 0 && (long)Width * Height <= MaxBlitPixels; + } + + // A stock object outlives every caller, so DeleteObject on one succeeds. Every window DC reports the + // one display surface, which is shared the same way. + internal static bool IsStockObject(BinaryEmulator Instance, ulong Handle) + { + if (Handle == 0) + return false; + + Win32kState State = GetState(Instance); + return Handle == State.StockBitmap || Handle == State.DisplaySurfaceBitmap; + } + // Every DC starts on this, so a caller that selects its own bitmap has one to select back. internal static ulong EnsureStockBitmap(BinaryEmulator Instance) { @@ -870,6 +1039,18 @@ internal static ulong EnsureStockBitmap(BinaryEmulator Instance) return State.StockBitmap; } + internal static ulong CreateCompatibleBitmap(BinaryEmulator Instance, ulong Hdc, int Width, int Height) + { + if (!TryGetBitmap(Instance, GetDcSelectedBitmap(Instance, Hdc), out Win32kBitmap Source)) + return 0; + + // The caller deletes what it gets back, so a zero extent still needs its own bitmap. + if (Width == 0 || Height == 0) + return CreateBitmap(Instance, 1, 1, 1, 1, false, false); + + return CreateBitmap(Instance, Width, Height, Source.Planes, Source.BitsPerPixel, false, false); + } + 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); @@ -944,7 +1125,7 @@ internal static bool TryRenderTextToDcBitmap(BinaryEmulator Instance, ulong Hdc, } } - // The buffer is always top-down; a bottom-up DIB stores its first row last. + // The buffer is top-down. A bottom-up DIB stores its first row last. private static bool TransferBitmapRows(BinaryEmulator Instance, in Win32kBitmap Bitmap, Span Pixels, bool ToGuest) { for (int Row = 0; Row < Bitmap.Height; Row++) @@ -953,42 +1134,669 @@ private static bool TransferBitmapRows(BinaryEmulator Instance, in Win32kBitmap ulong Address = Bitmap.BitsAddress + (ulong)((long)GuestRow * Bitmap.Stride); Span Line = MemoryMarshal.AsBytes(Pixels.Slice(Row * Bitmap.Width, Bitmap.Width)); - if (ToGuest) - { - if (!Instance.WriteMemory(Address, Line)) - return false; - } - else if (!Instance.ReadMemory(Address, Line, (uint)Line.Length)) - { - return false; - } + if (ToGuest) + { + if (!Instance.WriteMemory(Address, Line)) + return false; + } + else if (!Instance.ReadMemory(Address, Line, (uint)Line.Length)) + { + return false; + } + } + + return true; + } + + internal static bool CopyBitmapBitsIn(BinaryEmulator Instance, in Win32kBitmap Bitmap, ulong SourceAddress) + { + if (SourceAddress == 0 || !Instance.IsRegionMapped(SourceAddress, Bitmap.BitsSize)) + return false; + + Span Chunk = Instance.WinHelper.Shared.GetSpan(BitmapCopyChunkBytes); + for (uint Copied = 0; Copied < Bitmap.BitsSize;) + { + int Size = (int)Math.Min(BitmapCopyChunkBytes, Bitmap.BitsSize - Copied); + Span Slice = Chunk.Slice(0, Size); + if (!Instance.ReadMemory(SourceAddress + Copied, Slice, (uint)Size)) + return false; + + if (!Instance.WriteMemory(Bitmap.BitsAddress + Copied, Slice)) + return false; + + Copied += (uint)Size; + } + + return true; + } + + internal const int BitmapCoreHeaderSize = 12; + internal const int BitmapInfoHeaderSize = 40; + internal const uint BI_RGB = 0; + internal const uint BI_BITFIELDS = 3; + + internal struct DibHeader + { + public int Width; + public int Height; + public ushort Planes; + public ushort BitsPerPixel; + public uint Compression; + public uint HeaderSize; + public bool TopDown => Height < 0; + public int Rows => Height < 0 ? -Height : Height; + } + + internal static bool TryReadDibHeader(BinaryEmulator Instance, ulong Address, out DibHeader Header) + { + Header = default; + + if (Address == 0 || !Instance.IsRegionMapped(Address, BitmapCoreHeaderSize)) + return false; + + uint HeaderSize = Instance.ReadMemoryUInt(Address); + int ReadSize = HeaderSize == BitmapCoreHeaderSize ? BitmapCoreHeaderSize : BitmapInfoHeaderSize; + if (HeaderSize != BitmapCoreHeaderSize && HeaderSize < BitmapInfoHeaderSize) + return false; + + if (!Instance.IsRegionMapped(Address, (ulong)ReadSize)) + return false; + + Span Buffer = Instance.WinHelper.Shared.GetSpan((ulong)ReadSize); + if (!Instance.ReadMemory(Address, Buffer, (uint)ReadSize)) + return false; + + Header.HeaderSize = HeaderSize; + + if (ReadSize == BitmapCoreHeaderSize) + { + Header.Width = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.Slice(0x04, 2)); + Header.Height = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.Slice(0x06, 2)); + Header.Planes = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.Slice(0x08, 2)); + Header.BitsPerPixel = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.Slice(0x0A, 2)); + Header.Compression = BI_RGB; + return true; + } + + Header.Width = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(0x04, 4)); + Header.Height = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(0x08, 4)); + Header.Planes = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.Slice(0x0C, 2)); + Header.BitsPerPixel = BinaryPrimitives.ReadUInt16LittleEndian(Buffer.Slice(0x0E, 2)); + Header.Compression = BinaryPrimitives.ReadUInt32LittleEndian(Buffer.Slice(0x10, 4)); + return true; + } + + internal static bool TryReadDibBlock(BinaryEmulator Instance, ulong BitsAddress, in DibHeader Header, + int X, int Y, int Width, int Height, Span Destination) + { + if (BitsAddress == 0 || Header.Planes != 1 || Width <= 0 || Height <= 0) + return false; + + if (Header.Compression != BI_RGB && Header.Compression != BI_BITFIELDS) + return false; + + if (Header.BitsPerPixel != 32 && Header.BitsPerPixel != 24) + return false; + + int Rows = Header.Rows; + int BytesPerPixel = Header.BitsPerPixel / 8; + int Stride = GetBitmapStride(Header.Width, 1, Header.BitsPerPixel, true); + if (Header.Width <= 0 || Rows <= 0 || Stride <= 0) + return false; + + if (!Instance.IsRegionMapped(BitsAddress, (ulong)((long)Stride * Rows))) + return false; + + byte[] Rented = ArrayPool.Shared.Rent(Stride); + try + { + Span Line = Rented.AsSpan(0, Stride); + + for (int Row = 0; Row < Height; Row++) + { + Span Target = Destination.Slice(Row * Width, Width); + int SourceRow = Y + Row; + if ((uint)SourceRow >= (uint)Rows) + { + Target.Clear(); + continue; + } + + int StoredRow = Header.TopDown ? SourceRow : Rows - 1 - SourceRow; + if (!Instance.ReadMemory(BitsAddress + (ulong)((long)StoredRow * Stride), Line, (uint)Stride)) + return false; + + for (int Column = 0; Column < Width; Column++) + { + int SourceColumn = X + Column; + if ((uint)SourceColumn >= (uint)Header.Width) + { + Target[Column] = 0; + continue; + } + + int Offset = SourceColumn * BytesPerPixel; + Target[Column] = (uint)(Line[Offset] | (Line[Offset + 1] << 8) | (Line[Offset + 2] << 16)); + } + } + + return true; + } + finally + { + ArrayPool.Shared.Return(Rented); + } + } + + // Layout of the region object NtGdiCreateRectRgn builds. + internal const int RegionObjectSize = 0x30; + internal const int RegionRectOffset = 0x08; + + internal const int RegionNull = 1; + internal const int RegionSimple = 2; + internal const int RegionComplex = 3; + internal const int RegionError = 0; + + internal static bool TryReadRegionRect(BinaryEmulator Instance, ulong Handle, out int Left, out int Top, out int Right, out int Bottom) + { + Left = 0; + Top = 0; + Right = 0; + Bottom = 0; + + ulong Object = Instance.WinHelper.GetGdiKernelObject(Handle, RegionHandleType); + if (Object == 0 || !Instance.IsRegionMapped(Object, RegionObjectSize)) + return false; + + Span Buffer = Instance.WinHelper.Shared.GetSpan(16); + if (!Instance.ReadMemory(Object + RegionRectOffset, Buffer, 16)) + return false; + + Left = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(0, 4)); + Top = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(4, 4)); + Right = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(8, 4)); + Bottom = BinaryPrimitives.ReadInt32LittleEndian(Buffer.Slice(12, 4)); + return true; + } + + internal static bool TryWriteRegionRect(BinaryEmulator Instance, ulong Handle, int Left, int Top, int Right, int Bottom) + { + ulong Object = Instance.WinHelper.GetGdiKernelObject(Handle, RegionHandleType); + if (Object == 0 || !Instance.IsRegionMapped(Object, RegionObjectSize)) + return false; + + bool Empty = Right <= Left || Bottom <= Top; + + Span Buffer = Instance.WinHelper.Shared.GetSpan(20); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(0, 4), Empty ? RegionNull : RegionSimple); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(4, 4), Empty ? 0 : Left); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(8, 4), Empty ? 0 : Top); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(12, 4), Empty ? 0 : Right); + BinaryPrimitives.WriteInt32LittleEndian(Buffer.Slice(16, 4), Empty ? 0 : Bottom); + + return Instance.WriteMemory(Object + 0x04, Buffer.Slice(0, 20)); + } + + // CombineRgn. A non-rectangular result is widened to its bounding rectangle. + internal static int CombineRegionRects(int Mode, + int ALeft, int ATop, int ARight, int ABottom, + int BLeft, int BTop, int BRight, int BBottom, + out int Left, out int Top, out int Right, out int Bottom) + { + const int RgnAnd = 1; + const int RgnOr = 2; + const int RgnXor = 3; + const int RgnDiff = 4; + const int RgnCopy = 5; + + bool AEmpty = ARight <= ALeft || ABottom <= ATop; + bool BEmpty = BRight <= BLeft || BBottom <= BTop; + + switch (Mode) + { + case RgnCopy: + Left = ALeft; Top = ATop; Right = ARight; Bottom = ABottom; + break; + + case RgnAnd: + Left = Math.Max(ALeft, BLeft); + Top = Math.Max(ATop, BTop); + Right = Math.Min(ARight, BRight); + Bottom = Math.Min(ABottom, BBottom); + break; + + case RgnDiff: + if (AEmpty || BEmpty) + { + Left = ALeft; Top = ATop; Right = ARight; Bottom = ABottom; + break; + } + + Left = ALeft; Top = ATop; Right = ARight; Bottom = ABottom; + if (BLeft <= ALeft && BRight >= ARight && BTop <= ATop && BBottom >= ABottom) + { + Right = Left; + Bottom = Top; + } + break; + + case RgnOr: + case RgnXor: + default: + if (AEmpty) + { + Left = BLeft; Top = BTop; Right = BRight; Bottom = BBottom; + } + else if (BEmpty) + { + Left = ALeft; Top = ATop; Right = ARight; Bottom = ABottom; + } + else + { + Left = Math.Min(ALeft, BLeft); + Top = Math.Min(ATop, BTop); + Right = Math.Max(ARight, BRight); + Bottom = Math.Max(ABottom, BBottom); + } + break; + } + + if (Right <= Left || Bottom <= Top) + { + Left = 0; Top = 0; Right = 0; Bottom = 0; + return RegionNull; + } + + return RegionSimple; + } + + internal const uint SrcCopyRop = 0x00CC0020; + + internal static ulong FindDcForBitmap(BinaryEmulator Instance, ulong BitmapHandle) + { + if (BitmapHandle == 0) + return 0; + + foreach (KeyValuePair Entry in GetState(Instance).DeviceContexts) + { + if (Entry.Value.SelectedBitmap == BitmapHandle) + return Entry.Key; + } + + return 0; + } + + internal static int SaveDeviceContext(BinaryEmulator Instance, ulong Hdc) + { + if (!GetState(Instance).DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc)) + return 0; + + Dc.SavedStates ??= new List(); + Dc.SavedStates.Add(new Win32kDcState { Bitmap = Dc.SelectedBitmap, Font = Dc.SelectedFont }); + return Dc.SavedStates.Count; + } + + internal static bool RestoreDeviceContext(BinaryEmulator Instance, ulong Hdc, int Level) + { + if (!GetState(Instance).DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc) + || Dc.SavedStates == null || Dc.SavedStates.Count == 0) + return false; + + // A negative level counts back from the top, so RestoreDC(-1) pops one state. + int Target = Level < 0 ? Dc.SavedStates.Count + Level : Level - 1; + if ((uint)Target >= (uint)Dc.SavedStates.Count) + return false; + + Win32kDcState Saved = Dc.SavedStates[Target]; + Dc.SelectedBitmap = Saved.Bitmap; + Dc.SelectedFont = Saved.Font; + Dc.SavedStates.RemoveRange(Target, Dc.SavedStates.Count - Target); + return true; + } + + internal static ulong SelectDcPalette(BinaryEmulator Instance, ulong Hdc, ulong Palette) + { + if (!GetState(Instance).DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc)) + return 0; + + ulong Previous = Dc.SelectedPalette; + Dc.SelectedPalette = Palette; + return Previous; + } + + // A screen context reports the display surface, not the bitmap selected into it. + internal static ulong GetDcSelectedBitmap(BinaryEmulator Instance, ulong Hdc) + { + Win32kState State = GetState(Instance); + if (!State.DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc)) + return 0; + + return Dc.Display ? EnsureDisplaySurfaceBitmap(Instance, State) : Dc.SelectedBitmap; + } + + private static ulong EnsureDisplaySurfaceBitmap(BinaryEmulator Instance, Win32kState State) + { + if (State.DisplaySurfaceBitmap == 0) + State.DisplaySurfaceBitmap = CreateBitmap(Instance, HostDisplayMetrics.ScreenWidth, HostDisplayMetrics.ScreenHeight, 1, 32, false, false); + + return State.DisplaySurfaceBitmap; + } + + internal static ulong GetDcSelectedFont(BinaryEmulator Instance, ulong Hdc) + { + return GetState(Instance).DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc) ? Dc.SelectedFont : 0; + } + + // gdi32 asks twice, once for the size and once for the data. + internal static IReadOnlyList GetFontFamilies(BinaryEmulator Instance, string FaceName, byte CharSet) + { + Win32kState State = GetState(Instance); + string Key = CharSet.ToString(CultureInfo.InvariantCulture) + "|" + (FaceName ?? string.Empty); + + if (State.FontFamilies.TryGetValue(Key, out IReadOnlyList Cached)) + return Cached; + + IReadOnlyList Faces = Instance.WinHelper.EnumerateFontFamilies(FaceName, CharSet) ?? Array.Empty(); + State.FontFamilies[Key] = Faces; + return Faces; + } + + internal static ulong GetDcSelectedPalette(BinaryEmulator Instance, ulong Hdc) + { + return GetState(Instance).DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc) ? Dc.SelectedPalette : 0; + } + + internal static bool TryGetDcExtent(BinaryEmulator Instance, ulong Hdc, out int Width, out int Height) + { + Width = 0; + Height = 0; + + if (!GetState(Instance).DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc)) + return false; + + if (Dc.Hwnd != 0) + { + GetClientSize(Instance, Instance.WinHelper.GetWindow(Dc.Hwnd), out Width, out Height); + return true; + } + + if (!TryGetDcBitmap(Instance, Hdc, out Win32kBitmap Bitmap)) + return false; + + Width = Bitmap.Width; + Height = Bitmap.Height; + return true; + } + + // A window DC draws through the host window, so only a screen context resolves to the display surface. + internal static bool TryGetDcBitmap(BinaryEmulator Instance, ulong Hdc, out Win32kBitmap Bitmap) + { + Win32kState State = GetState(Instance); + Bitmap = default; + + if (!State.DeviceContexts.TryGetValue(Hdc, out Win32kDeviceContext Dc)) + return false; + + ulong Handle = Dc.Display && Dc.Hwnd == 0 ? EnsureDisplaySurfaceBitmap(Instance, State) : Dc.SelectedBitmap; + return Handle != 0 && State.Bitmaps.TryGetValue(Handle, out Bitmap); + } + + // Packed colour only. Lower depths need a colour table the blit does not carry. + internal static bool CanBlitBitmap(in Win32kBitmap Bitmap) + { + return Bitmap.Planes == 1 + && (Bitmap.BitsPerPixel == 32 || Bitmap.BitsPerPixel == 24) + && Bitmap.BitsAddress != 0 + && Bitmap.Width > 0 + && Bitmap.Height > 0; + } + + // Flipping the term changes the result for at least one of the other four combinations. + internal static bool RopUsesSource(uint Rop) + { + uint Index = (Rop >> 16) & 0xFF; + return ((Index >> 2) & 0x33) != (Index & 0x33); + } + + internal static bool RopUsesDestination(uint Rop) + { + uint Index = (Rop >> 16) & 0xFF; + return ((Index >> 1) & 0x55) != (Index & 0x55); + } + + // Index is bits 16 to 23 of the rop code. + internal static uint ApplyRop(uint Index, uint Pattern, uint Source, uint Destination) + { + uint Result = 0; + + if ((Index & 0x01) != 0) Result |= ~Pattern & ~Source & ~Destination; + if ((Index & 0x02) != 0) Result |= ~Pattern & ~Source & Destination; + if ((Index & 0x04) != 0) Result |= ~Pattern & Source & ~Destination; + if ((Index & 0x08) != 0) Result |= ~Pattern & Source & Destination; + if ((Index & 0x10) != 0) Result |= Pattern & ~Source & ~Destination; + if ((Index & 0x20) != 0) Result |= Pattern & ~Source & Destination; + if ((Index & 0x40) != 0) Result |= Pattern & Source & ~Destination; + if ((Index & 0x80) != 0) Result |= Pattern & Source & Destination; + + return Result & 0x00FFFFFF; + } + + private static ulong BitmapRowAddress(in Win32kBitmap Bitmap, int Row) + { + int Line = Bitmap.TopDown ? Row : Bitmap.Height - 1 - Row; + return Bitmap.BitsAddress + (ulong)((long)Line * Bitmap.Stride); + } + + private static bool TryReadBitmapRow(BinaryEmulator Instance, in Win32kBitmap Bitmap, int Row, int X, int Width, Span Destination) + { + int BytesPerPixel = Bitmap.BitsPerPixel / 8; + int Bytes = Width * BytesPerPixel; + ulong Address = BitmapRowAddress(Bitmap, Row) + (ulong)(X * BytesPerPixel); + + if (BytesPerPixel == 4) + { + Span Line = MemoryMarshal.AsBytes(Destination.Slice(0, Width)); + return Instance.ReadMemory(Address, Line, (uint)Bytes); + } + + byte[] Rented = ArrayPool.Shared.Rent(Bytes); + try + { + Span Line = Rented.AsSpan(0, Bytes); + if (!Instance.ReadMemory(Address, Line, (uint)Bytes)) + return false; + + for (int Column = 0; Column < Width; Column++) + { + int Offset = Column * 3; + Destination[Column] = (uint)(Line[Offset] | (Line[Offset + 1] << 8) | (Line[Offset + 2] << 16)); + } + + return true; + } + finally + { + ArrayPool.Shared.Return(Rented); + } + } + + private static bool TryWriteBitmapRow(BinaryEmulator Instance, in Win32kBitmap Bitmap, int Row, int X, int Width, ReadOnlySpan Source) + { + int BytesPerPixel = Bitmap.BitsPerPixel / 8; + int Bytes = Width * BytesPerPixel; + ulong Address = BitmapRowAddress(Bitmap, Row) + (ulong)(X * BytesPerPixel); + + if (BytesPerPixel == 4) + return Instance.WriteMemory(Address, MemoryMarshal.AsBytes(Source.Slice(0, Width))); + + byte[] Rented = ArrayPool.Shared.Rent(Bytes); + try + { + Span Line = Rented.AsSpan(0, Bytes); + for (int Column = 0; Column < Width; Column++) + { + uint Pixel = Source[Column]; + int Offset = Column * 3; + Line[Offset] = (byte)Pixel; + Line[Offset + 1] = (byte)(Pixel >> 8); + Line[Offset + 2] = (byte)(Pixel >> 16); + } + + return Instance.WriteMemory(Address, Line); + } + finally + { + ArrayPool.Shared.Return(Rented); + } + } + + internal static bool TryReadBitmapBlock(BinaryEmulator Instance, in Win32kBitmap Bitmap, int X, int Y, int Width, int Height, Span Destination) + { + if (!CanBlitBitmap(Bitmap) || Width <= 0 || Height <= 0) + return false; + + if (!Instance.IsRegionMapped(Bitmap.BitsAddress, Bitmap.BitsSize)) + return false; + + for (int Row = 0; Row < Height; Row++) + { + Span Line = Destination.Slice(Row * Width, Width); + int SourceRow = Y + Row; + + if ((uint)SourceRow >= (uint)Bitmap.Height) + { + Line.Clear(); + continue; + } + + int Left = Math.Max(X, 0); + int Right = Math.Min(X + Width, Bitmap.Width); + if (Right <= Left) + { + Line.Clear(); + continue; + } + + if (Left != X || Right != X + Width) + Line.Clear(); + + if (!TryReadBitmapRow(Instance, Bitmap, SourceRow, Left, Right - Left, Line.Slice(Left - X))) + return false; + } + + return true; + } + + internal static bool TryBlitBlockIntoBitmap(BinaryEmulator Instance, in Win32kBitmap Bitmap, int X, int Y, int Width, int Height, + ReadOnlySpan Source, int SourceWidth, int SourceHeight, uint Rop, uint PatternPixel) + { + if (!CanBlitBitmap(Bitmap) || Width <= 0 || Height <= 0 || SourceWidth <= 0 || SourceHeight <= 0) + return false; + + if (!Instance.IsRegionMapped(Bitmap.BitsAddress, Bitmap.BitsSize)) + return false; + + int Left = Math.Max(X, 0); + int Top = Math.Max(Y, 0); + int Right = Math.Min(X + Width, Bitmap.Width); + int Bottom = Math.Min(Y + Height, Bitmap.Height); + if (Right <= Left || Bottom <= Top) + return true; + + int Span = Right - Left; + uint Index = (Rop >> 16) & 0xFF; + bool Copy = Rop == SrcCopyRop; + + uint[] Rented = ArrayPool.Shared.Rent(Span); + try + { + System.Span Line = Rented.AsSpan(0, Span); + + for (int Row = Top; Row < Bottom; Row++) + { + int SourceRow = (int)((long)(Row - Y) * SourceHeight / Height); + ReadOnlySpan SourceLine = Source.Slice(SourceRow * SourceWidth, SourceWidth); + + if (!Copy && !TryReadBitmapRow(Instance, Bitmap, Row, Left, Span, Line)) + return false; + + for (int Column = 0; Column < Span; Column++) + { + int SourceColumn = (int)((long)(Left + Column - X) * SourceWidth / Width); + uint Pixel = SourceLine[SourceColumn]; + Line[Column] = Copy ? Pixel & 0x00FFFFFF : ApplyRop(Index, PatternPixel, Pixel, Line[Column]); + } + + if (!TryWriteBitmapRow(Instance, Bitmap, Row, Left, Span, Line)) + return false; + } + + return true; + } + finally + { + ArrayPool.Shared.Return(Rented); + } + } + + internal static bool BlitBlockToDc(BinaryEmulator Instance, ulong Hdc, int X, int Y, int Width, int Height, + ReadOnlySpan Source, int SourceWidth, int SourceHeight, uint Rop) + { + if (Width <= 0 || Height <= 0 || SourceWidth <= 0 || SourceHeight <= 0) + return false; + + if (Source.Length < SourceWidth * SourceHeight) + return false; + + if (TryGetDcBitmap(Instance, Hdc, out Win32kBitmap Target) && CanBlitBitmap(Target)) + { + uint PatternPixel = HostColor.FromColorRef(ResolvePenBrush(Instance, Instance.WinHelper.ReadDcSelectedBrush(Hdc), false).ColorRef); + return TryBlitBlockIntoBitmap(Instance, Target, X, Y, Width, Height, Source, SourceWidth, SourceHeight, Rop, PatternPixel); + } + + ulong Hwnd = Instance.WinHelper.GetHwndFromDc(Hdc); + if (Hwnd == 0) + return false; + + // The GUI thread drains the queue later, so it gets rows of its own. + uint[] Owned = Source.Slice(0, SourceWidth * SourceHeight).ToArray(); + + // A window has no readable surface, so only a rop that ignores the destination can be resolved. + if (Rop != SrcCopyRop && !RopUsesDestination(Rop)) + { + uint Index = (Rop >> 16) & 0xFF; + uint PatternPixel = HostColor.FromColorRef(ResolvePenBrush(Instance, Instance.WinHelper.ReadDcSelectedBrush(Hdc), false).ColorRef); + + for (int i = 0; i < Owned.Length; i++) + Owned[i] = ApplyRop(Index, PatternPixel, Owned[i], 0); + + Rop = SrcCopyRop; } + Instance.WinHelper.EnqueueGdiBlit(Hwnd, Hdc, X, Y, X + Width, Y + Height, Owned, SourceWidth, SourceHeight, Rop); return true; } - internal static bool CopyBitmapBitsIn(BinaryEmulator Instance, in Win32kBitmap Bitmap, ulong SourceAddress) + internal static bool BlitBlockToWindow(BinaryEmulator Instance, ulong Hwnd, int X, int Y, int Width, int Height, + ReadOnlySpan Source, int SourceWidth, int SourceHeight, uint Rop) { - if (SourceAddress == 0 || !Instance.IsRegionMapped(SourceAddress, Bitmap.BitsSize)) + if (Hwnd == 0 || Width <= 0 || Height <= 0 || SourceWidth <= 0 || SourceHeight <= 0) return false; - Span Chunk = Instance.WinHelper.Shared.GetSpan(BitmapCopyChunkBytes); - for (uint Copied = 0; Copied < Bitmap.BitsSize;) - { - int Size = (int)Math.Min(BitmapCopyChunkBytes, Bitmap.BitsSize - Copied); - Span Slice = Chunk.Slice(0, Size); - if (!Instance.ReadMemory(SourceAddress + Copied, Slice, (uint)Size)) - return false; - - if (!Instance.WriteMemory(Bitmap.BitsAddress + Copied, Slice)) - return false; - - Copied += (uint)Size; - } + if (Source.Length < SourceWidth * SourceHeight) + return false; + uint[] Owned = Source.Slice(0, SourceWidth * SourceHeight).ToArray(); + Instance.WinHelper.EnqueueGdiBlit(Hwnd, 0, X, Y, X + Width, Y + Height, Owned, SourceWidth, SourceHeight, Rop); return true; } + internal static bool TryReadDcBlock(BinaryEmulator Instance, ulong Hdc, int X, int Y, int Width, int Height, Span Destination) + { + return TryGetDcBitmap(Instance, Hdc, out Win32kBitmap Source) + && TryReadBitmapBlock(Instance, Source, X, Y, Width, Height, Destination); + } + internal static bool TryGetBitmap(BinaryEmulator Instance, ulong Handle, out Win32kBitmap Bitmap) { if (Handle != 0) @@ -1057,7 +1865,12 @@ internal static bool PostMessage(BinaryEmulator Instance, ulong Hwnd, uint Messa 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) + internal static bool PostThreadMessage(BinaryEmulator Instance, uint TargetThreadId, uint Message, ulong WParam, ulong LParam) + { + return PostMessage(Instance, GetState(Instance), 0, Message, WParam, LParam, TargetThreadId); + } + + private static bool PostMessage(BinaryEmulator Instance, Win32kState State, ulong Hwnd, uint Message, ulong WParam, ulong LParam, uint TargetThreadId = 0) { uint Time = unchecked((uint)Instance.EmulatedTickCount64); @@ -1083,7 +1896,7 @@ private static bool PostMessage(BinaryEmulator Instance, Win32kState State, ulon if (Message == WM_PAINT && IsQueued(State, Hwnd, WM_PAINT)) return true; - State.MessageQueue.Enqueue(new Win32kMessage(Hwnd, Message, WParam, LParam, Time, 0, 0)); + State.MessageQueue.Enqueue(new Win32kMessage(Hwnd, Message, WParam, LParam, Time, 0, 0, TargetThreadId)); NoteQueuedMessage(State, Message); Instance.WakeSignal.Bump(); return true; @@ -1114,7 +1927,158 @@ internal static void PostQuitMessage(BinaryEmulator Instance, ulong ExitCode) Instance.WakeSignal.Bump(); } - internal static bool TryGetMessage(BinaryEmulator Instance, ulong HwndFilter, uint MinMessage, uint MaxMessage, bool Remove, out Win32kMessage Message) + internal static ulong SetTimer(BinaryEmulator Instance, ulong Hwnd, ulong Id, uint Elapse, ulong Proc) + { + Win32kState State = GetState(Instance); + + if (Elapse < USER_TIMER_MINIMUM) + Elapse = USER_TIMER_MINIMUM; + else if (Elapse > USER_TIMER_MAXIMUM) + Elapse = USER_TIMER_MAXIMUM; + + if (Hwnd == 0) + Id = State.NextWindowlessTimerId++; + + Win32kTimer Timer = null; + foreach (Win32kTimer Candidate in State.Timers) + { + if (Candidate.Hwnd == Hwnd && Candidate.Id == Id) + { + Timer = Candidate; + break; + } + } + + if (Timer == null) + { + Timer = new Win32kTimer { Hwnd = Hwnd, Id = Id, ThreadId = Instance.CurrentThread?.ThreadId ?? 0 }; + State.Timers.Add(Timer); + } + + Timer.Proc = Proc; + Timer.Elapse = Elapse; + Timer.Due = Instance.CreateEmulatedDeadlineMilliseconds(Elapse); + + WakeMessageWaiters(Instance); + return Id; + } + + internal static bool KillTimer(BinaryEmulator Instance, ulong Hwnd, ulong Id) + { + Win32kState State = GetState(Instance); + uint ThreadId = Instance.CurrentThread?.ThreadId ?? 0; + + for (int i = 0; i < State.Timers.Count; i++) + { + if (State.Timers[i].Hwnd != Hwnd || State.Timers[i].Id != Id) + continue; + + if (!TimerOwnedByThread(Instance, State.Timers[i], ThreadId)) + return false; + + State.Timers.RemoveAt(i); + return true; + } + + return false; + } + + // A windowless timer belongs to the thread that set it, one on a window to the thread that owns it. + private static bool TimerOwnedByThread(BinaryEmulator Instance, Win32kTimer Timer, uint ThreadId) + { + return Timer.Hwnd != 0 + ? OwnedByThread(Instance, Timer.Hwnd, ThreadId) + : ThreadId == 0 || Timer.ThreadId == 0 || Timer.ThreadId == ThreadId; + } + + // A deadline for a timer the caller cannot consume parks it on an expiry it never clears. + internal static long GetNextTimerDue(BinaryEmulator Instance, ulong HwndFilter, uint ThreadId, uint MinMessage, uint MaxMessage) + { + if (!MessageInFilter(WM_TIMER, MinMessage, MaxMessage)) + return -1; + + Win32kState State = GetState(Instance); + DropTimersOfGoneWindows(Instance, State); + + long Earliest = -1; + foreach (Win32kTimer Timer in State.Timers) + { + if (HwndFilter != 0 && Timer.Hwnd != HwndFilter) + continue; + + bool Owned = Timer.Hwnd != 0 + ? OwnedByThread(Instance, Timer.Hwnd, ThreadId) + : ThreadId == 0 || Timer.ThreadId == 0 || Timer.ThreadId == ThreadId; + + if (!Owned) + continue; + + if (Earliest == -1 || Timer.Due < Earliest) + Earliest = Timer.Due; + } + + return Earliest; + } + + // A parked thread carries the deadline it was given, so each one is re-armed on the timer it can take. + private static void WakeMessageWaiters(BinaryEmulator Instance) + { + foreach (EmulatedThread Thread in Instance.Threads.Values) + { + if (Thread == null || !Thread.WaitActive || Thread.State != EmulatedThreadState.Waiting) + continue; + + WindowsThreadState State = WinEmulatedThread.TryGetState(Thread); + if (State == null || (!State.GetMessageWaitActive && !State.WaitMessageActive)) + continue; + + Thread.WaitDeadline = State.GetMessageWaitActive + ? GetNextTimerDue(Instance, State.GetMessageHwndFilter, Thread.ThreadId, State.GetMessageMinMessage, State.GetMessageMaxMessage) + : GetNextTimerDue(Instance, 0, Thread.ThreadId, 0, 0); + } + + Instance.WakeSignal.Bump(); + } + + private static void DropTimersOfGoneWindows(BinaryEmulator Instance, Win32kState State) + { + for (int i = State.Timers.Count - 1; i >= 0; i--) + { + ulong Hwnd = State.Timers[i].Hwnd; + if (Hwnd != 0 && Instance.WinHelper.GetWindow(Hwnd) == null) + State.Timers.RemoveAt(i); + } + } + + private static Win32kTimer FindDueTimer(BinaryEmulator Instance, Win32kState State, ulong HwndFilter, uint ThreadId) + { + if (State.Timers.Count == 0) + return null; + + DropTimersOfGoneWindows(Instance, State); + + long Now = Instance.EmulatedTickCount64; + Win32kTimer Earliest = null; + + foreach (Win32kTimer Timer in State.Timers) + { + if (HwndFilter != 0 && Timer.Hwnd != HwndFilter) + continue; + + if (!TimerOwnedByThread(Instance, Timer, ThreadId)) + continue; + + if (Timer.Due > Now) + continue; + + if (Earliest == null || Timer.Due < Earliest.Due) + Earliest = Timer; + } + + return Earliest; + } + + internal static bool TryGetMessage(BinaryEmulator Instance, ulong HwndFilter, uint MinMessage, uint MaxMessage, bool Remove, uint ThreadId, out Win32kMessage Message) { DrainHostEvents(Instance); @@ -1122,7 +2086,7 @@ internal static bool TryGetMessage(BinaryEmulator Instance, ulong HwndFilter, ui int Index = 0; foreach (Win32kMessage Candidate in State.MessageQueue) { - if (MatchesFilter(Candidate, HwndFilter, MinMessage, MaxMessage)) + if (MatchesFilter(Instance, Candidate, HwndFilter, MinMessage, MaxMessage, ThreadId)) { Message = Candidate; if (Remove) @@ -1137,6 +2101,20 @@ internal static bool TryGetMessage(BinaryEmulator Instance, ulong HwndFilter, ui Index++; } + // NT hands out WM_PAINT only when the queue is empty, and keeps doing so until validation. + if (MessageInFilter(WM_PAINT, MinMessage, MaxMessage)) + { + WinWindow Dirty = FindDirtyWindow(Instance, HwndFilter, ThreadId); + if (Dirty != null) + { + Message = new Win32kMessage(Dirty.Hwnd, WM_PAINT, 0, 0, unchecked((uint)Instance.EmulatedTickCount64), 0, 0); + if (Remove) + Dirty.Dirty = false; + + return true; + } + } + if (State.QuitPosted) { Message = new Win32kMessage(0, WM_QUIT, State.QuitExitCode, 0, unchecked((uint)Instance.EmulatedTickCount64), 0, 0); @@ -1145,13 +2123,56 @@ internal static bool TryGetMessage(BinaryEmulator Instance, ulong HwndFilter, ui return true; } + // WM_TIMER is the lowest priority message and is synthesized, not queued. + if (MessageInFilter(WM_TIMER, MinMessage, MaxMessage)) + { + Win32kTimer Timer = FindDueTimer(Instance, State, HwndFilter, ThreadId); + if (Timer != null) + { + Message = new Win32kMessage(Timer.Hwnd, WM_TIMER, Timer.Id, Timer.Proc, unchecked((uint)Instance.EmulatedTickCount64), 0, 0); + if (Remove) + Timer.Due = Instance.CreateEmulatedDeadlineMilliseconds(Timer.Elapse); + + return true; + } + } + Message = default; return false; } - internal static bool HasQueuedInputEvent(BinaryEmulator Instance, uint WakeMask) + private static bool MessageInFilter(uint Message, uint MinMessage, uint MaxMessage) + { + return (MinMessage == 0 && MaxMessage == 0) || (Message >= MinMessage && Message <= MaxMessage); + } + + private static WinWindow FindDirtyWindow(BinaryEmulator Instance, ulong HwndFilter, uint ThreadId) + { + ulong Locked = GetState(Instance).UpdateLockWindow; + + if (HwndFilter != 0) + { + if (HwndFilter == Locked) + return null; + + WinWindow Target = Instance.WinHelper.GetWindow(HwndFilter); + return Target != null && Target.Dirty && Target.Visible && !Target.Destroyed + && OwnedByThread(Instance, HwndFilter, ThreadId) ? Target : null; + } + + foreach (WinWindow Window in Instance.WinHelper.WinWindows.Values) + { + if (Window.Dirty && Window.Visible && !Window.Destroyed && Window.Hwnd != Locked + && (ThreadId == 0 || Window.OwnerThreadId == 0 || Window.OwnerThreadId == ThreadId)) + return Window; + } + + return null; + } + + internal static bool HasQueuedInputEvent(BinaryEmulator Instance, uint WakeMask, uint ThreadId) { - return GetQueuedWakeBits(Instance, WakeMask) != 0; + return GetQueuedWakeBits(Instance, WakeMask, ThreadId) != 0; } // For a layout that is not a substitute, both halves of the HKL are the language the KLID ends with. @@ -1244,7 +2265,8 @@ internal static int GetCharAdvanceWidth(BinaryEmulator Instance, IntPtr Font, in return Measured; } - internal static uint GetQueuedWakeBits(BinaryEmulator Instance, uint WakeMask) + // A thread told about work it may not dequeue wakes, finds nothing, and parks again at once. + internal static uint GetQueuedWakeBits(BinaryEmulator Instance, uint WakeMask, uint ThreadId) { DrainHostEvents(Instance); @@ -1252,18 +2274,39 @@ internal static uint GetQueuedWakeBits(BinaryEmulator Instance, uint WakeMask) return 0; Win32kState State = GetState(Instance); + uint Queued; - if (!State.QueuedWakeBitsValid) + if (ThreadId != 0) { - uint Queued = 0; + Queued = 0; foreach (Win32kMessage Candidate in State.MessageQueue) - Queued |= GetMessageWakeBits(Candidate.Message); + { + if (OwnedByThread(Instance, Candidate, ThreadId)) + Queued |= GetMessageWakeBits(Candidate.Message); + } + } + else + { + if (!State.QueuedWakeBitsValid) + { + uint All = 0; + foreach (Win32kMessage Candidate in State.MessageQueue) + All |= GetMessageWakeBits(Candidate.Message); + + State.QueuedWakeBits = All; + State.QueuedWakeBitsValid = true; + } - State.QueuedWakeBits = Queued; - State.QueuedWakeBitsValid = true; + Queued = State.QueuedWakeBits; } - uint Bits = State.QuitPosted ? State.QueuedWakeBits | QS_POSTMESSAGE : State.QueuedWakeBits; + uint Bits = State.QuitPosted ? Queued | QS_POSTMESSAGE : Queued; + if ((WakeMask & QS_PAINT) != 0 && FindDirtyWindow(Instance, 0, ThreadId) != null) + Bits |= QS_PAINT; + + if ((WakeMask & QS_TIMER) != 0 && FindDueTimer(Instance, State, 0, ThreadId) != null) + Bits |= QS_TIMER; + return Bits & WakeMask; } @@ -1273,6 +2316,8 @@ private static uint GetMessageWakeBits(uint Message) { case WM_PAINT: return QS_PAINT; + case WM_TIMER: + return QS_TIMER; case WM_INPUT: return QS_RAWINPUT; case WM_MOUSEMOVE: @@ -1491,15 +2536,18 @@ internal static ulong DispatchMessage(BinaryEmulator Instance, Win32kMessage Mes private static void DrainHostEvents(BinaryEmulator Instance) { - ulong Foreground = Instance.WinHelper.GetForegroundWindow(); + ulong Active = Instance.WinHelper.GetForegroundWindow(); // Nothing can be delivered before the guest makes a window visible, and the host queue must survive // until then: consuming the repaint flag (or draining input) here would discard the only events a // thread parked in MsgWaitForMultipleObjectsEx can ever be woken by. - if (Foreground == 0) + if (Active == 0) return; - SyncActivation(Instance, Foreground); + SyncActivation(Instance, Active); + + // Everything the host reports is about the window it draws. + ulong Foreground = Instance.WinHelper.PresentedWindow != 0 ? Instance.WinHelper.PresentedWindow : Active; Win32kDpi.DrainHostDpiChange(Instance); @@ -2015,7 +3063,11 @@ internal static void DropRawInputMessages(BinaryEmulator Instance, uint LastHand internal static bool TryDeliverWindowPosChanged(BinaryEmulator Instance, ulong SyscallResult) { - ulong Hwnd = Instance.WinHelper.GetForegroundWindow(); + // The host frame follows the presented window. + ulong Hwnd = Instance.WinHelper.PresentedWindow != 0 + ? Instance.WinHelper.PresentedWindow + : Instance.WinHelper.GetForegroundWindow(); + if (Hwnd == 0) return false; @@ -2101,6 +3153,60 @@ internal static void GetClientRect(BinaryEmulator Instance, WinWindow Window, ou Height = Math.Max((int)Window.Height - InsetTop - InsetBottom, 0); } + internal static void GetAbsoluteWindowPosition(BinaryEmulator Instance, WinWindow Window, out int Left, out int Top) + { + Left = Window.X; + Top = Window.Y; + + ulong ParentHwnd = Window.ParentHwnd; + while (ParentHwnd != 0) + { + WinWindow Parent = Instance.WinHelper.GetWindow(ParentHwnd); + if (Parent == null) + break; + + GetClientRect(Instance, Parent, out int ClientLeft, out int ClientTop, out _, out _); + Left += ClientLeft; + Top += ClientTop; + ParentHwnd = Parent.ParentHwnd; + } + } + + internal static ulong WindowFromPoint(BinaryEmulator Instance, int X, int Y) + { + ulong Found = 0; + List TopLevel = Instance.WinHelper.TopLevelWindows; + + for (int i = TopLevel.Count - 1; i >= 0 && Found == 0; i--) + Found = HitTest(Instance, Instance.WinHelper.GetWindow(TopLevel[i]), X, Y); + + return Found; + } + + private static ulong HitTest(BinaryEmulator Instance, WinWindow Window, int X, int Y) + { + const uint WS_EX_TRANSPARENT = 0x00000020; + + if (Window == null || Window.Destroyed || !Window.Visible || (Window.ExStyle & WS_EX_TRANSPARENT) != 0) + return 0; + + GetAbsoluteWindowPosition(Instance, Window, out int Left, out int Top); + if (X < Left || Y < Top || X >= Left + (int)Window.Width || Y >= Top + (int)Window.Height) + return 0; + + foreach (WinWindow Child in Instance.WinHelper.WinWindows.Values) + { + if (Child.ParentHwnd != Window.Hwnd) + continue; + + ulong Hit = HitTest(Instance, Child, X, Y); + if (Hit != 0) + return Hit; + } + + return Window.Hwnd; + } + internal static void GetClientSize(BinaryEmulator Instance, WinWindow Window, out int Width, out int Height) { GetClientRect(Instance, Window, out _, out _, out Width, out Height); @@ -2158,7 +3264,7 @@ private static void ApplyHostResize(BinaryEmulator Instance, ulong Hwnd, ulong W Window.Height = Height + (uint)(InsetTop + InsetBottom); } - Window.Dirty = true; + MarkWindowDirty(Instance, Window); Instance.WinHelper.MaterializeUserWindow(Window); } @@ -2271,6 +3377,66 @@ internal static ulong SetCursorHandle(BinaryEmulator Instance, ulong Handle) return Previous; } + internal static void SetCursorIconData(BinaryEmulator Instance, ulong Handle, Win32kCursorIcon Data) + { + GetState(Instance).CursorIcons[Handle] = Data; + } + + internal static bool TryGetCursorIcon(BinaryEmulator Instance, ulong Handle, out Win32kCursorIcon Data) + { + return GetState(Instance).CursorIcons.TryGetValue(Handle, out Data); + } + + internal static bool DestroyCursorIcon(BinaryEmulator Instance, ulong Handle) + { + Win32kState State = GetState(Instance); + if (!State.CursorIcons.Remove(Handle)) + return false; + + if (Handle != State.StockCursor && Handle != State.CursorHandle) + Instance.WinHelper.ReleaseUserHandle(Handle); + + return true; + } + + internal static ulong GetCursorHandle(BinaryEmulator Instance) + { + Win32kState State = GetState(Instance); + return State.CursorAssigned ? State.CursorHandle : EnsureStockCursor(Instance); + } + + internal static bool IsCursorShowing(BinaryEmulator Instance) + { + Win32kState State = GetState(Instance); + return State.CursorShowCount >= 0 && !State.CursorHiddenWhileTyping; + } + + internal static bool LockWindowUpdate(BinaryEmulator Instance, ulong Hwnd) + { + Win32kState State = GetState(Instance); + + if (Hwnd == 0) + { + ulong Locked = State.UpdateLockWindow; + State.UpdateLockWindow = 0; + + WinWindow Window = Locked != 0 ? Instance.WinHelper.GetWindow(Locked) : null; + if (Window != null && Window.Visible) + MarkWindowDirty(Instance, Window); + + return true; + } + + if (State.UpdateLockWindow != 0 && State.UpdateLockWindow != Hwnd) + return false; + + if (Instance.WinHelper.GetWindow(Hwnd) == null) + return false; + + State.UpdateLockWindow = Hwnd; + return true; + } + internal static int ShowCursor(BinaryEmulator Instance, bool Show) { Win32kState State = GetState(Instance); @@ -2298,6 +3464,18 @@ private static void ApplyCursorVisibility(BinaryEmulator Instance, Win32kState S Instance.WinHelper.SetHostCursorVisible(!Hidden); } + // WM_PAINT is not queued. The message fetch reports one while the flag stands. + internal static void MarkWindowDirty(BinaryEmulator Instance, WinWindow Window) + { + if (Window == null || Window.Destroyed) + return; + + Window.Dirty = true; + Window.PaintPending = true; + Instance.WinHelper.PublishWindowPaintState(Window); + Instance.WakeSignal.Bump(); + } + internal static bool InvalidateWindow(BinaryEmulator Instance, ulong Hwnd) { if (Hwnd == 0) @@ -2305,11 +3483,7 @@ internal static bool InvalidateWindow(BinaryEmulator Instance, ulong Hwnd) foreach (ulong TopLevelHwnd in Instance.WinHelper.TopLevelWindows) { WinWindow TopLevel = Instance.WinHelper.GetWindow(TopLevelHwnd); - if (TopLevel != null) - { - TopLevel.Dirty = true; - PostMessage(Instance, TopLevel.Hwnd, WM_PAINT, 0, 0); - } + MarkWindowDirty(Instance, TopLevel); } Instance.WinHelper.PresentDesktop(); @@ -2320,8 +3494,7 @@ internal static bool InvalidateWindow(BinaryEmulator Instance, ulong Hwnd) if (Window == null) return false; - Window.Dirty = true; - PostMessage(Instance, Hwnd, WM_PAINT, 0, 0); + MarkWindowDirty(Instance, Window); Instance.WinHelper.PresentDesktop(); return true; } @@ -2341,8 +3514,7 @@ private static void InvalidateWindowTree(BinaryEmulator Instance, Win32kState St if (Window == null || !Window.Visible) return; - Window.Dirty = true; - PostMessage(Instance, State, Hwnd, WM_PAINT, 0, 0); + MarkWindowDirty(Instance, Window); for (int i = 0; i < Window.Children.Count; i++) InvalidateWindowTree(Instance, State, Window.Children[i], Depth + 1); @@ -2360,13 +3532,27 @@ private static void InvalidateWindowTree(BinaryEmulator Instance, Win32kState St 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) + internal static bool InvokeWindowProc(BinaryEmulator Instance, ulong Hwnd, ulong WndProc, uint Message, ulong WParam, ulong LParam, WinWindowCreation Creation = null, ulong SyscallRetryRip = 0, ulong PaintRetryHwnd = 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); + return Instance.WinHelper.EnterUserCallback(Callback, WindowProcCallbackIndex, ArgumentBuffer, Creation, SyscallRetryRip, PaintRetryHwnd); + } + + // True once, for the syscall that the returning WM_PAINT callback is re-running. + internal static bool TakePaintRetry(BinaryEmulator Instance, ulong Hwnd) + { + if (Hwnd == 0) + return false; + + WindowsThreadState State = WinEmulatedThread.TryGetState(Instance.CurrentThread); + if (State == null || State.PendingPaintRetryHwnd != Hwnd) + return false; + + State.PendingPaintRetryHwnd = 0; + return true; } internal static bool SendWindowCreateMessage(BinaryEmulator Instance, WinWindow Window, uint Message, WinWindowCreation Creation) @@ -2482,7 +3668,7 @@ private static ulong DefaultWindowProc(BinaryEmulator Instance, WinWindow Window { case WM_SETTEXT: Window.Title = ReadWindowTextPointer(Instance, LParam, Ansi) ?? string.Empty; - Window.Dirty = true; + MarkWindowDirty(Instance, Window); Instance.WinHelper.MaterializeUserWindow(Window); Instance.WinHelper.PresentDesktop(); return 1; @@ -2556,17 +3742,39 @@ internal static bool RemoveFlagSet(uint Flags) return (Flags & PM_REMOVE) != 0; } - private static bool MatchesFilter(Win32kMessage Message, ulong HwndFilter, uint MinMessage, uint MaxMessage) + private static bool MatchesFilter(BinaryEmulator Instance, Win32kMessage Message, ulong HwndFilter, uint MinMessage, uint MaxMessage, uint ThreadId) { if (HwndFilter != 0 && Message.Hwnd != HwndFilter) return false; + if (!OwnedByThread(Instance, Message, ThreadId)) + return false; + if (MinMessage == 0 && MaxMessage == 0) return true; return Message.Message >= MinMessage && Message.Message <= MaxMessage; } + // A thread message names its reader outright, a window message is read by the thread that owns it. + private static bool OwnedByThread(BinaryEmulator Instance, in Win32kMessage Message, uint ThreadId) + { + if (Message.TargetThreadId != 0) + return ThreadId == 0 || Message.TargetThreadId == ThreadId; + + return OwnedByThread(Instance, Message.Hwnd, ThreadId); + } + + // Only the creating thread may run a window procedure, so another thread's message stays queued. + private static bool OwnedByThread(BinaryEmulator Instance, ulong Hwnd, uint ThreadId) + { + if (Hwnd == 0 || ThreadId == 0) + return true; + + WinWindow Window = Instance.WinHelper.GetWindow(Hwnd); + return Window == null || Window.OwnerThreadId == 0 || Window.OwnerThreadId == ThreadId; + } + private static void RemoveMessageAt(Win32kState State, int Index) { Queue Queue = State.MessageQueue; @@ -2778,7 +3986,7 @@ internal static bool TryExchangeWindowExtra(BinaryEmulator Instance, WinWindow W return false; ulong Address = Extra + (ulong)Offset; - Previous = Size == 8 ? Instance.ReadMemoryULong(Address) : Instance.ReadMemoryUInt(Address); + Previous = Instance.WinHelper.ReadPointer(Address, Size); return Instance._emulator.WriteMemory(Address, Value, Size); } diff --git a/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs b/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs index 08bba7e7..9494af6f 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinHelperConstants.cs @@ -1696,6 +1696,10 @@ public class WinWindowClass public class WinWindow : IHandleObject { public ulong Hwnd; + + // Dirty is a WM_PAINT owed to the message fetch. + // PaintPending is the update region user32 reads, cleared only on validation. + public bool PaintPending; public ushort ClassAtom; public string Title; public string ClassName; diff --git a/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs b/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs index 6d7682a5..3e5b724c 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinSyscallsHelper.cs @@ -1467,6 +1467,20 @@ public static WinFile CreateStandardHandleFile(ConsoleObjectKind Kind) internal byte[] SyntheticMountDevUniqueId { get; private set; } private BinaryEmulator Emulator; + private uint CachedDriveMap; + + // Bit 0 is A:. + internal uint DriveMap + { + get + { + if (CachedDriveMap == 0) + CachedDriveMap = GeneralHelper.IO.GetWindowsDriveMap(); + + return CachedDriveMap; + } + } + private bool _argCacheValid; private ulong _argCacheR10; private ulong _argCacheRDX; @@ -3275,7 +3289,7 @@ 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) + public bool EnterUserCallback(ulong Callback, uint CallbackIndex, ulong ArgumentBuffer, WinWindowCreation Creation, ulong SyscallRetryRip = 0, ulong PaintRetryHwnd = 0) { EmulatedThread Thread = Emulator.CurrentThread; if (Thread == null || Callback == 0 || PointerSize != 8) @@ -3290,6 +3304,7 @@ public bool EnterUserCallback(ulong Callback, uint CallbackIndex, ulong Argument SavedRsp = CurrentRsp, SavedReturnAddress = Emulator.ReadMemoryULong(CurrentRsp), SyscallRetryRip = SyscallRetryRip, + PaintRetryHwnd = PaintRetryHwnd, WindowCreation = Creation, }; @@ -3645,6 +3660,7 @@ public bool CompleteUserCallback(ulong ResultAddress, uint ResultLength) if (Frame.SyscallRetryRip != 0) { + WinEmulatedThread.GetState(Thread).PendingPaintRetryHwnd = Frame.PaintRetryHwnd; 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); @@ -4079,7 +4095,6 @@ public ulong AllocateGdiHandle(byte type) Zeroed.Clear(); Emulator._emulator.WriteMemory(DcAttr, Zeroed); - Emulator._emulator.WriteMemory(DcAttr, 1, 1); Emulator._emulator.WriteMemory(DcAttr + 0x68, 1, 4); Emulator._emulator.WriteMemory(DcAttr + 0x128, 0x00010001UL, 8); Emulator._emulator.WriteMemory(DcAttr + 0x140, 1, 4); @@ -4147,6 +4162,23 @@ private static ulong DecryptGdiPointer(ulong Encrypted) private const int DcAttrBrushOriginXOffset = 0x158; private const int DcAttrBrushOriginYOffset = 0x15C; + public ulong GetGdiKernelObject(ulong Handle, byte ExpectedType) + { + if (Handle == 0 || ((Handle >> 16) & GdiHandleTypeMask) != ExpectedType || !ValidateGdiHandle(Handle)) + return 0; + + EnsureGdiHandleTable(); + if (GdiHandleTableAddress == 0) + return 0; + + ushort Index = (ushort)(Handle & 0xFFFF); + if (Index == 0 || Index >= GdiHandleEntryCount) + return 0; + + ulong Encrypted = Emulator.ReadMemoryULong(GdiHandleTableAddress + (ulong)Index * GdiHandleEntrySize + 0x10); + return Encrypted == 0 ? 0 : DecryptGdiPointer(Encrypted); + } + public ulong GetDcAttributeAddress(ulong Hdc) { if (Hdc == 0 || ((Hdc >> 16) & GdiHandleTypeMask) != 1 || !ValidateGdiHandle(Hdc)) @@ -4497,6 +4529,28 @@ public void EnqueueGdiFillRect(ulong Hwnd, ulong Hdc, int Left, int Top, int Rig }); } + public void EnqueueGdiBlit(ulong Hwnd, ulong Hdc, int Left, int Top, int Right, int Bottom, uint[] Pixels, int SourceWidth, int SourceHeight, uint Rop) + { + if (DesktopDisplay is not GuiThreadManager guiManager) + return; + + GetDcSurfaceOrigin(Hwnd, Hdc, out int SurfaceX, out int SurfaceY); + + guiManager.EnqueueGdiPrimitive(new GdiPrimitive + { + Hwnd = Hwnd, + Kind = GdiPrimitiveKind.Blit, + X1 = Left + SurfaceX, + Y1 = Top + SurfaceY, + X2 = Right + SurfaceX, + Y2 = Bottom + SurfaceY, + Rop = Rop, + Pixels = Pixels, + SourceWidth = SourceWidth, + SourceHeight = SourceHeight, + }); + } + 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) @@ -4750,6 +4804,14 @@ public void DeleteHostFont(IntPtr Font) guiManager.DeleteFont(Font); } + public IReadOnlyList EnumerateFontFamilies(string FaceName, byte CharSet) + { + EnsureDesktopDisplay(); + return DesktopDisplay is GuiThreadManager guiManager + ? guiManager.EnumerateFontFamilies(FaceName, CharSet) + : Array.Empty(); + } + private void WriteUserMessageFont(ulong Address, uint Dpi) { Span Font = stackalloc byte[LogFontSize]; @@ -5349,6 +5411,24 @@ private ulong EnsureUserDesktopWindowObject() return UserDesktopWindowAddress; } + // user32 answers UpdateWindow and GetUpdateRect out of the window object, with no syscall. + // WNDS_INTERNALPAINT is a paint owed with no update region. + private const int UserWindowPaintStateOffset = 0x10; + private const uint UserWindowStateInternalPaint = 0x00001000; + private const uint UserWindowStateUpdateDirty = 0x00002000; + + public void PublishWindowPaintState(WinWindow Window) + { + if (Window == null || Window.ClientWindowAddress == 0) + return; + + const uint PaintBits = UserWindowStateInternalPaint | UserWindowStateUpdateDirty; + uint State = Emulator.ReadMemoryUInt(Window.ClientWindowAddress + UserWindowPaintStateOffset); + State = Window.PaintPending ? State | PaintBits : State & ~PaintBits; + + Emulator._emulator.WriteMemory(Window.ClientWindowAddress + (ulong)UserWindowPaintStateOffset, State, 4); + } + private void RefreshUserWindowObject(WinWindow Window) { ulong ClassObject = EnsureUserClassObject(Window); @@ -5418,6 +5498,8 @@ private void RefreshUserWindowObject(WinWindow Window) Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0xC0, TextObject, 8); Emulator._emulator.WriteMemory(Window.ClientWindowAddress + 0xE0, 0UL, 8); + PublishWindowPaintState(Window); + Win32kDpi.ApplyWindowContext(Emulator, Window.ClientWindowAddress); } @@ -5859,7 +5941,7 @@ public bool ReparentWindow(WinWindow Window, WinWindow NewParent) } Window.ParentHwnd = NewParent?.Hwnd ?? 0; - Window.Dirty = true; + Win32kHelper.MarkWindowDirty(Emulator, Window); if (NewParent != null) { @@ -6056,6 +6138,9 @@ private void PublishForegroundWindow() Emulator._emulator.WriteMemory(ServerInfo + (ulong)Offset, Foreground, Width); } + // Host geometry and host input belong to this window, not always the foreground one. + public ulong PresentedWindow { get; private set; } + public void PresentDesktop() { try @@ -6067,7 +6152,7 @@ public void PresentDesktop() return; WinWindow Window = GetWindow(GetForegroundWindow()); - if (Window == null || IsToolWindow(Window)) + if (Window == null || IsToolWindow(Window) || !Window.Visible || Window.Destroyed) Window = GetTopLevelWindow(); string title; @@ -6096,6 +6181,7 @@ public void PresentDesktop() state = WindowState.Normal; } + PresentedWindow = visible ? (Window?.Hwnd ?? 0) : 0; guiManager.EnqueuePresent(title, width, height, visible, state); } catch @@ -6179,14 +6265,21 @@ public void EnsureHostXlibSurfaceHandles(out IntPtr connection, out IntPtr windo private WinWindow GetTopLevelWindow() { + WinWindow Hidden = null; + for (int i = TopLevelWindows.Count - 1; i >= 0; i--) { ulong Hwnd = TopLevelWindows[i]; - if (WinWindows.TryGetValue(Hwnd, out WinWindow Window) && Window != null && !Window.Destroyed && !IsToolWindow(Window)) + if (!WinWindows.TryGetValue(Hwnd, out WinWindow Window) || Window == null || Window.Destroyed || IsToolWindow(Window)) + continue; + + if (Window.Visible) return Window; + + Hidden ??= Window; } - return null; + return Hidden; } private static bool IsToolWindow(WinWindow Window) @@ -6236,7 +6329,7 @@ public void RegisterWindow(WinWindow Window) if (Window.Visible && Window.ParentHwnd == 0) SetThreadWindowContext(Window); - Window.Dirty = true; + Win32kHelper.MarkWindowDirty(Emulator, Window); MaterializeUserWindow(Window); PresentDesktop(); } diff --git a/Brovan/Core/Emulation/OS/Windows/WinThreading.cs b/Brovan/Core/Emulation/OS/Windows/WinThreading.cs index e9b14c7f..2ec3e403 100644 --- a/Brovan/Core/Emulation/OS/Windows/WinThreading.cs +++ b/Brovan/Core/Emulation/OS/Windows/WinThreading.cs @@ -73,6 +73,10 @@ public sealed class WindowsThreadState public uint GetMessageMinMessage { get; set; } public uint GetMessageMaxMessage { get; set; } public Stack UserCallbackFrames { get; set; } = new(); + + // Set by a returning WM_PAINT callback, so the re-run of its syscall knows itself apart from a + // fresh call made inside the procedure. + public ulong PendingPaintRetryHwnd { get; set; } } public sealed class WinUserCallbackFrame @@ -80,6 +84,7 @@ public sealed class WinUserCallbackFrame public ulong SavedRsp; public ulong SavedReturnAddress; public ulong SyscallRetryRip; + public ulong PaintRetryHwnd; public ulong SavedSyscallNumber; public ulong SavedArg0; diff --git a/Brovan/GeneralHelper.cs b/Brovan/GeneralHelper.cs index dc7eee1a..6de2f0cc 100644 --- a/Brovan/GeneralHelper.cs +++ b/Brovan/GeneralHelper.cs @@ -7,7 +7,6 @@ using System.IO.Compression; using System.Linq; using System.Runtime.InteropServices; -using System.Runtime.Versioning; using System.Security.Principal; using System.Text; using System.Threading.Tasks; @@ -15,6 +14,7 @@ using Brovan.Core.Helpers; using Brovan.Core; using Microsoft.Win32.SafeHandles; +using System.Runtime.Versioning; using static Brovan.Core.Helpers.BinaryHelpers; using static Brovan.Core.Helpers.Utils; @@ -626,7 +626,11 @@ public static bool RestartProcessWithCfgDisabled(bool KeepAlive) if (string.IsNullOrEmpty(ExePath)) return false; - string[] Args = Environment.GetCommandLineArgs().Skip(1).ToArray(); + // Under "dotnet Brovan.dll" argv[0] is the assembly, which the shared host needs again. + string[] Raw = Environment.GetCommandLineArgs(); + bool SharedHost = Raw.Length != 0 && !string.Equals(Path.GetFileNameWithoutExtension(Raw[0]), + Path.GetFileNameWithoutExtension(ExePath), StringComparison.OrdinalIgnoreCase); + string[] Args = Raw.Skip(SharedHost ? 0 : 1).ToArray(); string CommandLine = BuildCommandLine(ExePath, Args); const uint ExtendedStartupInfoPresent = 0x00080000; @@ -1585,16 +1589,16 @@ private static void ApplyTarMetadata(string TargetPath, UnixFileMode Mode, DateT { } - if (!IsWindows) + if (IsWindows) + return; + + try + { + FileSystemInfo Info = IsDirectory ? new DirectoryInfo(TargetPath) : new FileInfo(TargetPath); + Info.UnixFileMode = Mode; + } + catch { - try - { - FileSystemInfo Info = IsDirectory ? new DirectoryInfo(TargetPath) : new FileInfo(TargetPath); - Info.UnixFileMode = Mode; - } - catch - { - } } } private static bool TryMaterializeRootfsHardLink(UbuntuRootfsPendingHardLink Link, IReadOnlyDictionary SymlinkTargetsByArchivePath) @@ -1823,6 +1827,56 @@ public static void SetDriveMapping(char DriveLetter, string HostRoot) RefreshAllowedRoots(); } + /// + /// Reports the drive letters the emulated Windows filesystem can reach, bit 0 for A:. + /// + public static uint GetWindowsDriveMap() + { + uint Map = 1u << ('C' - 'A'); + + for (int Index = 0; Index < 26; Index++) + { + char Letter = (char)('A' + Index); + + if (IsWindows && Directory.Exists($"{Letter}:\\")) + { + Map |= 1u << Index; + continue; + } + + string Root; + bool Mapped; + lock (DriveMapLock) + { + Mapped = DriveMappings.TryGetValue(Letter, out Root) && !string.IsNullOrWhiteSpace(Root); + if (!Mapped) + Root = Path.Combine(VirtualFileSystemRoot, Letter.ToString()); + } + + try + { + if (!Directory.Exists(Root)) + continue; + + if (Mapped) + { + Map |= 1u << Index; + continue; + } + + using IEnumerator Entries = Directory.EnumerateFileSystemEntries(Root).GetEnumerator(); + if (Entries.MoveNext()) + Map |= 1u << Index; + } + catch + { + // A root the host refuses to list is not a drive the guest can use. + } + } + + return Map; + } + /// /// Sets a host directory mapping for an emulated linux mount point. /// @@ -3045,6 +3099,10 @@ private static string ResolveSandboxLinks(string FullPath, bool IncludeFinal, bo string LinkTarget; if (IsWindows) { + // A throw per missing component costs an unwind through the backend's own frames. + if (!Path.Exists(Current)) + continue; + FileAttributes Attributes; try {