Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion BlazorServer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ private static void ConfigureServices(IConfiguration configuration, IServiceColl

services.AddWoWProcess(log);

services.AddCoreBase();
services.AddCoreBase(log);

if (AddonConfig.Exists() && FrameConfig.Exists())
{
Expand Down
2 changes: 1 addition & 1 deletion BlazorServer/run.bat
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
start "" "http://localhost:5000"
cd /D "%~dp0"
dotnet run --configuration Release --no-build
dotnet run --configuration Release --no-build -- --Reader:Type=%~1

pause
43 changes: 34 additions & 9 deletions Core/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ public static IServiceCollection AddCoreNormal(
return s;
}

public static IServiceCollection AddCoreBase(this IServiceCollection s)
public static IServiceCollection AddCoreBase(this IServiceCollection s, ILogger log)
{
s.AddSingleton<ManualResetEventSlim>(x => new(false));
s.AddSingleton<Wait>();
Expand All @@ -247,7 +247,9 @@ public static IServiceCollection AddCoreBase(this IServiceCollection s)
s.AddSingleton<DataConfig>(x => DataConfig.Load(
x.GetRequiredService<StartupClientVersion>().Path));

s.ForwardSingleton<IWowScreen, IScreenImageProvider, IMinimapImageProvider, WowScreenDXGI>();
s.AddSingleton<IWowScreen>(x => CreateWowScreen(x.GetRequiredService<IServiceProvider>(), log));
s.AddSingleton<IScreenImageProvider>(x => x.GetRequiredService<IWowScreen>());
s.AddSingleton<IMinimapImageProvider>(x => x.GetRequiredService<IWowScreen>());

s.ForwardSingleton<WowProcessInput, IMouseInput>();

Expand All @@ -264,6 +266,34 @@ public static IServiceCollection AddCoreBase(this IServiceCollection s)
return s;
}

private static IWowScreen CreateWowScreen(IServiceProvider sp, ILogger log)
{
var scr = sp.GetRequiredService<IOptions<StartupConfigReader>>().Value;
var loggerFactory = sp.GetRequiredService<ILoggerFactory>();
var process = sp.GetRequiredService<WowProcess>();
var frames = sp.GetRequiredService<DataFrame[]>();

// Use WGC if configured and supported (Windows 10 2004+)
if (scr.ReaderType == AddonDataProviderType.WGC)
{
if (OperatingSystem.IsWindowsVersionAtLeast(10, 0, 19041) &&
GraphicsCaptureInterop.IsSupported)
{
var wgcLogger = loggerFactory.CreateLogger<WowScreenWGC>();
log.LogInformation("Using WGC (Windows Graphics Capture) - supports background capture");
return new WowScreenWGC(wgcLogger, process, frames);
}

log.LogWarning(
"WGC requested but not supported (requires Windows 10 2004+). Falling back to DXGI.");
}

// Default: DXGI
var dxgiLogger = loggerFactory.CreateLogger<WowScreenDXGI>();
log.LogInformation("Using DXGI Desktop Duplication");
return new WowScreenDXGI(dxgiLogger, process, frames);
}


public static bool AddWoWProcess(
this IServiceCollection services, ILogger log)
Expand Down Expand Up @@ -343,15 +373,10 @@ private static IScreenCapture GetScreenCapture(
private static IAddonDataProvider GetAddonDataProvider(
IServiceProvider sp, ILogger log)
{
var scr = sp.GetRequiredService<IOptions<StartupConfigReader>>().Value;
var screen = sp.GetRequiredService<IWowScreen>();

IAddonDataProvider value = scr.ReaderType switch
{
AddonDataProviderType.DXGI =>
(IAddonDataProvider)screen,
_ => throw new NotImplementedException(),
};
// Both WowScreenDXGI and WowScreenWGC implement IAddonDataProvider
IAddonDataProvider value = (IAddonDataProvider)screen;

log.LogInformation(value.GetType().Name);
return value;
Expand Down
163 changes: 163 additions & 0 deletions Core/WoWScreen/GraphicsCaptureInterop.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using System;
using System.Runtime.InteropServices;

using Vortice.Direct3D11;

using Windows.Graphics.Capture;
using Windows.Graphics.DirectX.Direct3D11;

using WinRT;

namespace Core;

/// <summary>
/// Provides interop helpers for Windows Graphics Capture API.
/// </summary>
public static class GraphicsCaptureInterop
{
[ComImport]
[Guid("3628E81B-3CAC-4C60-B7F4-23CE0E0C3356")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
private interface IGraphicsCaptureItemInterop
{
IntPtr CreateForWindow(
[In] IntPtr window,
[In] ref Guid iid);

IntPtr CreateForMonitor(
[In] IntPtr monitor,
[In] ref Guid iid);
}

[ComImport]
[Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[ComVisible(true)]
private interface IDirect3DDxgiInterfaceAccess
{
// HRESULT GetInterface(REFIID iid, void** p)
[PreserveSig]
int GetInterface([In] ref Guid iid, out IntPtr p);
}

[DllImport("d3d11.dll", EntryPoint = "CreateDirect3D11DeviceFromDXGIDevice",
SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true,
CallingConvention = CallingConvention.StdCall)]
private static extern uint CreateDirect3D11DeviceFromDXGIDevice(
IntPtr dxgiDevice, out IntPtr graphicsDevice);

private static readonly Guid GraphicsCaptureItemGuid =
new("79C3F95B-31F7-4EC2-A464-632EF5D30760");

private static readonly Guid IDXGIDeviceGuid =
new("54ec77fa-1377-44e6-8c32-88fd5f44c84c");

/// <summary>
/// Creates a GraphicsCaptureItem for the specified window handle.
/// </summary>
/// <param name="hwnd">The window handle to capture.</param>
/// <returns>A GraphicsCaptureItem for the window, or null if creation fails.</returns>
public static GraphicsCaptureItem? CreateCaptureItemForWindow(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
return null;

try
{
object factory = GraphicsCaptureItem.As<IGraphicsCaptureItemInterop>();
var interop = (IGraphicsCaptureItemInterop)factory;

Guid guid = GraphicsCaptureItemGuid;
IntPtr itemPointer = interop.CreateForWindow(hwnd, ref guid);

if (itemPointer == IntPtr.Zero)
return null;

return MarshalInterface<GraphicsCaptureItem>.FromAbi(itemPointer);
}
catch
{
return null;
}
}

/// <summary>
/// Creates a WinRT IDirect3DDevice from a Vortice D3D11 device.
/// </summary>
/// <param name="d3dDevice">The Vortice D3D11 device.</param>
/// <returns>A WinRT Direct3D device for use with Graphics Capture, or null if creation fails.</returns>
public static IDirect3DDevice? CreateDirect3DDeviceFromD3D11(ID3D11Device d3dDevice)
{
try
{
// Get the DXGI device from the D3D11 device
using Vortice.DXGI.IDXGIDevice dxgiDevice = d3dDevice.QueryInterface<Vortice.DXGI.IDXGIDevice>();

uint hr = CreateDirect3D11DeviceFromDXGIDevice(
dxgiDevice.NativePointer,
out IntPtr graphicsDevice);

if (hr != 0 || graphicsDevice == IntPtr.Zero)
return null;

return MarshalInterface<IDirect3DDevice>.FromAbi(graphicsDevice);
}
catch
{
return null;
}
}

/// <summary>
/// Gets the underlying DXGI surface from a WinRT Direct3D surface.
/// </summary>
/// <param name="surface">The WinRT Direct3D surface.</param>
/// <returns>A pointer to the DXGI surface, or IntPtr.Zero if failed.</returns>
public static IntPtr GetDXGISurface(IDirect3DSurface surface)
{
return GetDXGISurface(surface, out _);
}

/// <summary>
/// Gets the underlying DXGI surface from a WinRT Direct3D surface.
/// </summary>
/// <param name="surface">The WinRT Direct3D surface.</param>
/// <param name="hresult">The HRESULT from the COM call.</param>
/// <returns>A pointer to the DXGI surface, or IntPtr.Zero if failed.</returns>
public static IntPtr GetDXGISurface(IDirect3DSurface surface, out int hresult)
{
hresult = 0;
try
{
object access = surface.As<IDirect3DDxgiInterfaceAccess>();
var dxgiAccess = (IDirect3DDxgiInterfaceAccess)access;

Guid dxgiSurfaceGuid = typeof(Vortice.DXGI.IDXGISurface).GUID;
hresult = dxgiAccess.GetInterface(ref dxgiSurfaceGuid, out IntPtr surfacePtr);

// S_OK = 0, anything else is failure
return hresult >= 0 ? surfacePtr : IntPtr.Zero;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"GetDXGISurface exception: {ex.Message}");
hresult = ex.HResult;
return IntPtr.Zero;
}
}

/// <summary>
/// Checks if Windows Graphics Capture is supported on this system.
/// Requires Windows 10 version 1903 (build 18362) or later for basic support,
/// and version 2004 (build 19041) for borderless capture (no yellow border).
/// </summary>
public static bool IsSupported => GraphicsCaptureSession.IsSupported();

/// <summary>
/// Checks if borderless capture is supported (Windows 10 20H2 build 20348+).
/// When supported, the yellow capture border can be disabled.
/// </summary>
public static bool IsBorderlessSupported =>
OperatingSystem.IsWindowsVersionAtLeast(10, 0, 20348);
}
32 changes: 32 additions & 0 deletions Core/WoWScreen/ScreenCaptureHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Runtime.CompilerServices;

namespace Core;

internal static class ScreenCaptureHelper
{
public const int Bgra32Size = 4;

[SkipLocalsInit]
public static void CopyRegion(
ReadOnlySpan<byte> src, int srcRowPitch,
int srcX, int srcY,
Span<byte> dest,
int width, int height)
{
int bytesPerRow = width * Bgra32Size;

// Fast path: source region is contiguous (no X offset, no pitch padding)
if (srcX == 0 && srcRowPitch == bytesPerRow)
{
src.Slice(srcY * srcRowPitch, bytesPerRow * height).CopyTo(dest);
return;
}

for (int y = 0; y < height; y++)
{
src.Slice((srcY + y) * srcRowPitch + srcX * Bgra32Size, bytesPerRow)
.CopyTo(dest.Slice(y * bytesPerRow, bytesPerRow));
}
}
}
Loading
Loading