diff --git a/BetterGenshinImpact.sln b/BetterGenshinImpact.sln
index 0958d77a..38db720a 100644
--- a/BetterGenshinImpact.sln
+++ b/BetterGenshinImpact.sln
@@ -9,24 +9,48 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Vision.WindowCapture", "Vis
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BetterGenshinImpact.Win32", "BetterGenshinImpact.Win32\BetterGenshinImpact.Win32.csproj", "{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fischless.WindowCapture", "Fischless.WindowCapture\Fischless.WindowCapture.csproj", "{6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
+ Debug|x64 = Debug|x64
Release|Any CPU = Release|Any CPU
+ Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{75EC89E2-413D-4725-BCEA-AFAC57708F07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75EC89E2-413D-4725-BCEA-AFAC57708F07}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {75EC89E2-413D-4725-BCEA-AFAC57708F07}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {75EC89E2-413D-4725-BCEA-AFAC57708F07}.Debug|x64.Build.0 = Debug|Any CPU
{75EC89E2-413D-4725-BCEA-AFAC57708F07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75EC89E2-413D-4725-BCEA-AFAC57708F07}.Release|Any CPU.Build.0 = Release|Any CPU
+ {75EC89E2-413D-4725-BCEA-AFAC57708F07}.Release|x64.ActiveCfg = Release|Any CPU
+ {75EC89E2-413D-4725-BCEA-AFAC57708F07}.Release|x64.Build.0 = Release|Any CPU
{C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Debug|x64.Build.0 = Debug|Any CPU
{C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Release|x64.ActiveCfg = Release|Any CPU
+ {C9080C1D-1B26-46CB-A494-A5E7FE3FCEBA}.Release|x64.Build.0 = Release|Any CPU
{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Debug|x64.Build.0 = Debug|Any CPU
{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Release|x64.ActiveCfg = Release|Any CPU
+ {75F4541B-9624-4AFB-BAEA-3EAFD3300EE1}.Release|x64.Build.0 = Release|Any CPU
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Debug|Any CPU.Build.0 = Debug|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Debug|x64.ActiveCfg = Debug|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Debug|x64.Build.0 = Debug|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Release|Any CPU.ActiveCfg = Release|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Release|Any CPU.Build.0 = Release|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Release|x64.ActiveCfg = Release|x64
+ {6B0A3D96-D88D-48DD-8112-4CD5BA5D27CE}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/BetterGenshinImpact/BetterGenshinImpact.csproj b/BetterGenshinImpact/BetterGenshinImpact.csproj
index a520563a..b8beda8d 100644
--- a/BetterGenshinImpact/BetterGenshinImpact.csproj
+++ b/BetterGenshinImpact/BetterGenshinImpact.csproj
@@ -1,63 +1,65 @@
-
- WinExe
- net7.0-windows10.0.22000.0
- enable
- true
-
+
+ WinExe
+ net7.0-windows10.0.22621.0
+ enable
+ true
+ True
+ 11.0
+ true
+
-
-
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
-
-
-
-
-
+
+
+
-
-
- Code
-
-
+
+
+ Code
+
+
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
-
+
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Config/ApplicationConfiguration.cs b/BetterGenshinImpact/Core/Config/ApplicationConfiguration.cs
index 36eec6e6..5b73b99a 100644
--- a/BetterGenshinImpact/Core/Config/ApplicationConfiguration.cs
+++ b/BetterGenshinImpact/Core/Config/ApplicationConfiguration.cs
@@ -1,7 +1,7 @@
using BetterGenshinImpact.GameTask.AutoFishing;
using BetterGenshinImpact.GameTask.AutoSkip;
using System;
-using Vision.WindowCapture;
+using Fischless.WindowCapture;
namespace BetterGenshinImpact.Core.Config
{
@@ -14,7 +14,7 @@ namespace BetterGenshinImpact.Core.Config
///
/// 窗口捕获的方式
///
- public string CaptureMode { get; set; } = CaptureModeEnum.BitBlt.ToString();
+ public string CaptureMode { get; set; } = CaptureModes.BitBlt.ToString();
///
/// 窗口捕获帧数/触发器触发频率
diff --git a/BetterGenshinImpact/Core/Simulator/MouseEventSimulator.cs b/BetterGenshinImpact/Core/Simulator/MouseEventSimulator.cs
index 5cac39b1..f8341927 100644
--- a/BetterGenshinImpact/Core/Simulator/MouseEventSimulator.cs
+++ b/BetterGenshinImpact/Core/Simulator/MouseEventSimulator.cs
@@ -1,8 +1,7 @@
-using System.Threading;
+using BetterGenshinImpact.Helpers;
+using System.Threading;
using System.Windows;
-using Windows.Win32.UI.Input.KeyboardAndMouse;
-using BetterGenshinImpact.Helpers;
-using static Windows.Win32.PInvoke;
+using Vanara.PInvoke;
namespace BetterGenshinImpact.Core.Simulator;
@@ -10,22 +9,19 @@ public class MouseEventSimulator
{
public static void Move(int x, int y)
{
- mouse_event(
- MOUSE_EVENT_FLAGS.MOUSEEVENTF_ABSOLUTE | MOUSE_EVENT_FLAGS.MOUSEEVENTF_MOVE,
- x * 65535 / PrimaryScreen.DESKTOP.Width,
- y * 65535 / PrimaryScreen.DESKTOP.Height,
- 0,
- 0);
+ User32.mouse_event(User32.MOUSEEVENTF.MOUSEEVENTF_ABSOLUTE | User32.MOUSEEVENTF.MOUSEEVENTF_MOVE,
+ x * 65535 / PrimaryScreen.DESKTOP.Width, y * 65535 / PrimaryScreen.DESKTOP.Height,
+ 0, 0);
}
public static void LeftButtonDown()
{
- mouse_event(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
+ User32.mouse_event(User32.MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);
}
public static void LeftButtonUp()
{
- mouse_event(MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
+ User32.mouse_event(User32.MOUSEEVENTF.MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);
}
public static bool Click(int x, int y)
diff --git a/BetterGenshinImpact/Core/Simulator/PostMessageSimulator.cs b/BetterGenshinImpact/Core/Simulator/PostMessageSimulator.cs
index ecaf4101..24c96eb2 100644
--- a/BetterGenshinImpact/Core/Simulator/PostMessageSimulator.cs
+++ b/BetterGenshinImpact/Core/Simulator/PostMessageSimulator.cs
@@ -1,16 +1,20 @@
-using System.Threading;
-using Windows.Win32.Foundation;
-using static Windows.Win32.PInvoke;
+using System;
+using System.Threading;
+using Vanara.PInvoke;
namespace BetterGenshinImpact.Core.Simulator;
public class PostMessageSimulator
{
- private readonly HWND _hWnd;
+ public static readonly uint WM_LBUTTONDOWN = 0x201; //按下鼠标左键
- public PostMessageSimulator(HWND hWnd)
+ public static readonly uint WM_LBUTTONUP = 0x202; //释放鼠标左键
+
+ private readonly IntPtr _hWnd;
+
+ public PostMessageSimulator(IntPtr hWnd)
{
- _hWnd = hWnd;
+ this._hWnd = hWnd;
}
///
@@ -20,10 +24,10 @@ public class PostMessageSimulator
///
public void LeftButtonClick(int x, int y)
{
- LPARAM p = y << 16 | x;
- PostMessage(_hWnd, WM_LBUTTONDOWN, default, p);
+ IntPtr p = (y << 16) | x;
+ User32.PostMessage(_hWnd, WM_LBUTTONDOWN, IntPtr.Zero, p);
Thread.Sleep(100);
- PostMessage(_hWnd, WM_LBUTTONUP, default, p);
+ User32.PostMessage(_hWnd, WM_LBUTTONUP, IntPtr.Zero, p);
}
///
@@ -31,7 +35,7 @@ public class PostMessageSimulator
///
public void LeftButtonDown()
{
- PostMessage(_hWnd, WM_LBUTTONDOWN, default, default);
+ User32.PostMessage(_hWnd, WM_LBUTTONDOWN, IntPtr.Zero);
}
///
@@ -39,6 +43,6 @@ public class PostMessageSimulator
///
public void LeftButtonUp()
{
- PostMessage(_hWnd, WM_LBUTTONUP, default, default);
+ User32.PostMessage(_hWnd, WM_LBUTTONUP, IntPtr.Zero);
}
}
\ No newline at end of file
diff --git a/BetterGenshinImpact/Core/Simulator/Simulator.cs b/BetterGenshinImpact/Core/Simulator/Simulator.cs
index 019eb389..5639d1a5 100644
--- a/BetterGenshinImpact/Core/Simulator/Simulator.cs
+++ b/BetterGenshinImpact/Core/Simulator/Simulator.cs
@@ -1,10 +1,10 @@
-using Windows.Win32.Foundation;
+using System;
namespace BetterGenshinImpact.Core.Simulator;
public class Simulator
{
- public static PostMessageSimulator PostMessage(HWND hWnd)
+ public static PostMessageSimulator PostMessage(IntPtr hWnd)
{
return new PostMessageSimulator(hWnd);
}
diff --git a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs
index b0bc03d3..dbcd4bf9 100644
--- a/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs
+++ b/BetterGenshinImpact/GameTask/AutoFishing/AutoFishingTrigger.cs
@@ -1,7 +1,6 @@
using BetterGenshinImpact.Core.Recognition;
using BetterGenshinImpact.Core.Recognition.OCR;
using BetterGenshinImpact.Core.Recognition.OpenCv;
-using BetterGenshinImpact.Core.Simulator;
using BetterGenshinImpact.GameTask.AutoFishing.Assets;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.View;
@@ -12,9 +11,8 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
-using Windows.Win32.Foundation;
-using Windows.Win32.UI.Input.KeyboardAndMouse;
using WindowsInput;
+using static Vanara.PInvoke.User32;
namespace BetterGenshinImpact.GameTask.AutoFishing
{
@@ -285,7 +283,7 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
private int _noRectsCount = 0;
private Rect _cur, _left, _right;
- private MOUSE_EVENT_FLAGS _prevMouseEvent = 0x0;
+ private MOUSEEVENTF _prevMouseEvent = 0x0;
private bool _findFishBoxTips;
@@ -331,21 +329,21 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
if (_cur.X < _left.X)
{
- if (_prevMouseEvent != MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN)
+ if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
{
simulator.Mouse.LeftButtonDown();
//Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown();
- _prevMouseEvent = MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN;
+ _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN;
//Debug.WriteLine("进度不到 左键按下");
}
}
else
{
- if (_prevMouseEvent == MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN)
+ if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
{
simulator.Mouse.LeftButtonUp();
//Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp();
- _prevMouseEvent = MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP;
+ _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP;
//Debug.WriteLine("进度超出 左键松开");
}
}
@@ -360,21 +358,21 @@ namespace BetterGenshinImpact.GameTask.AutoFishing
if (_right.X + _right.Width - (_cur.X + _cur.Width) <= _cur.X - _left.X)
{
- if (_prevMouseEvent == MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN)
+ if (_prevMouseEvent == MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
{
simulator.Mouse.LeftButtonUp();
//Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonUp();
- _prevMouseEvent = MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTUP;
+ _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTUP;
//Debug.WriteLine("进入框内中间 左键松开");
}
}
else
{
- if (_prevMouseEvent != MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN)
+ if (_prevMouseEvent != MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN)
{
simulator.Mouse.LeftButtonDown();
//Simulator.PostMessage(TaskContext.Instance().GameHandle).LeftButtonDown();
- _prevMouseEvent = MOUSE_EVENT_FLAGS.MOUSEEVENTF_LEFTDOWN;
+ _prevMouseEvent = MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN;
//Debug.WriteLine("未到框内中间 左键按下");
}
}
diff --git a/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs b/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs
index 0dd5d023..3c81cd57 100644
--- a/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs
+++ b/BetterGenshinImpact/GameTask/AutoSkip/AutoSkipTrigger.cs
@@ -1,114 +1,109 @@
using BetterGenshinImpact.Core.Recognition.OpenCv;
using BetterGenshinImpact.GameTask.AutoSkip.Assets;
-using BetterGenshinImpact.Helpers.Extensions;
-using BetterGenshinImpact.View.Drawable;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
-using BetterGenshinImpact.GameTask.Model;
using WindowsInput;
-using Point = OpenCvSharp.Point;
-namespace BetterGenshinImpact.GameTask.AutoSkip
+namespace BetterGenshinImpact.GameTask.AutoSkip;
+
+///
+/// 自动剧情有选项点击
+///
+public class AutoSkipTrigger : ITaskTrigger
{
- ///
- /// 自动剧情有选项点击
- ///
- public class AutoSkipTrigger : ITaskTrigger
+ private readonly ILogger _logger = App.GetLogger();
+
+ public string Name => "自动剧情";
+ public bool IsEnabled { get; set; }
+ public int Priority => 20;
+ public bool IsExclusive => false;
+
+ private readonly AutoSkipAssets _autoSkipAssets;
+
+ public AutoSkipTrigger()
{
- private readonly ILogger _logger = App.GetLogger();
+ _autoSkipAssets = new AutoSkipAssets();
+ }
- public string Name => "自动剧情";
- public bool IsEnabled { get; set; }
- public int Priority => 20;
- public bool IsExclusive => false;
+ public void Init()
+ {
+ IsEnabled = true;
+ }
- private readonly AutoSkipAssets _autoSkipAssets;
-
- public AutoSkipTrigger()
+ public void OnCapture(CaptureContent content)
+ {
+ if (content.IsReachInterval(TimeSpan.FromMilliseconds(200)))
{
- _autoSkipAssets = new AutoSkipAssets();
+ return;
}
- public void Init()
+ // 找左上角剧情自动的按钮
+ content.CaptureRectArea.Find(_autoSkipAssets.StopAutoButtonRo, (_) =>
{
- IsEnabled = true;
- }
+ new InputSimulator().Keyboard.KeyPress(VirtualKeyCode.SPACE);
+ });
- public void OnCapture(CaptureContent content)
+ // 不存在则找右下的选项按钮
+ content.CaptureRectArea.Find(_autoSkipAssets.OptionButtonRo, (optionButtonRectArea) =>
{
- if (content.IsReachInterval(TimeSpan.FromMilliseconds(200)))
+ // 不存在菜单的情况下 剧情在播放中
+ var menuRectArea = content.CaptureRectArea.Find(_autoSkipAssets.MenuRo);
+ if (menuRectArea.IsEmpty())
{
- return;
+ optionButtonRectArea.ClickCenter();
+ _logger.LogInformation("点击选项按钮");
}
+ });
- // 找左上角剧情自动的按钮
- content.CaptureRectArea.Find(_autoSkipAssets.StopAutoButtonRo, (_) =>
- {
- new InputSimulator().Keyboard.KeyPress(VirtualKeyCode.SPACE);
- });
-
- // 不存在则找右下的选项按钮
- content.CaptureRectArea.Find(_autoSkipAssets.OptionButtonRo, (optionButtonRectArea) =>
- {
- // 不存在菜单的情况下 剧情在播放中
- var menuRectArea = content.CaptureRectArea.Find(_autoSkipAssets.MenuRo);
- if (menuRectArea.IsEmpty())
- {
- optionButtonRectArea.ClickCenter();
- _logger.LogInformation("点击选项按钮");
- }
- });
-
- // 黑屏剧情要点击鼠标(多次) 几乎全黑的时候不用点击
- var grayMat = content.CaptureRectArea.SrcGreyMat;
- var blackCount = OpenCvCommonHelper.CountGrayMatColor(content.SrcGreyMat, 0);
- var rate = blackCount * 1.0 / (grayMat.Width * grayMat.Height);
- if (rate > 0.7 && rate < 0.99)
- {
- new InputSimulator().Mouse.LeftButtonClick();
- Debug.WriteLine($"点击黑屏剧情:{rate}");
- return;
- }
-
- // TODO 自动交付材料
-
- //var grayMat = content.SrcGreyMat;
- //// 找左上角剧情自动的按钮
- //var grayLeftTopMat = CutHelper.CutLeftTop(grayMat, grayMat.Width / 5, grayMat.Height / 5);
- //var p1 = OldMatchTemplateHelper.FindSingleTarget(grayLeftTopMat, AutoSkipAssets.StopAutoButtonMat, 0.9);
- //if (p1 is { X: > 0, Y: > 0 })
- //{
- // //_logger.LogInformation($"找到剧情自动按钮:{p1}");
- // VisionContext.Instance().DrawContent.PutRect("StopAutoButton",
- // p1.CenterPointToRect(AutoSkipAssets.StopAutoButtonMat).ToRectDrawable());
- // new InputSimulator().Keyboard.KeyPress(VirtualKeyCode.SPACE);
- //}
- //else
- //{
- // VisionContext.Instance().DrawContent.RemoveRect("StopAutoButton");
- //}
-
- //// 不存在则找右下的选项按钮
- //var grayRightBottomMat = content.SrcGreyRightBottomMat;
- //var p2 = OldMatchTemplateHelper.FindSingleTarget(grayRightBottomMat, AutoSkipAssets.OptionMat);
- //if (p2 is { X: > 0, Y: > 0 })
- //{
- // // 不存在菜单的情况下 剧情在播放中
- // var grayLeftTopMat2 = CutHelper.CutLeftTop(grayMat, grayMat.Width / 4, grayMat.Height / 4);
- // var pMenu = OldMatchTemplateHelper.FindSingleTarget(grayLeftTopMat2, AutoSkipAssets.MenuMat);
- // if (pMenu is { X: 0, Y: 0 })
- // {
- // p2 = p2.ToDesktopPositionOffset65535(grayMat.Width - grayMat.Width / 2,
- // grayMat.Height - grayMat.Height / 3 * 2);
- // new InputSimulator().Mouse.MoveMouseTo(p2.X, p2.Y).LeftButtonClick();
- // _logger.LogInformation($"点击选项按钮:{p2}");
- // return;
- // }
- //}
-
-
-
+ // 黑屏剧情要点击鼠标(多次) 几乎全黑的时候不用点击
+ var grayMat = content.CaptureRectArea.SrcGreyMat;
+ var blackCount = OpenCvCommonHelper.CountGrayMatColor(content.SrcGreyMat, 0);
+ var rate = blackCount * 1.0 / (grayMat.Width * grayMat.Height);
+ if (rate > 0.7 && rate < 0.99)
+ {
+ new InputSimulator().Mouse.LeftButtonClick();
+ Debug.WriteLine($"点击黑屏剧情:{rate}");
+ return;
}
+
+ // TODO 自动交付材料
+
+ //var grayMat = content.SrcGreyMat;
+ //// 找左上角剧情自动的按钮
+ //var grayLeftTopMat = CutHelper.CutLeftTop(grayMat, grayMat.Width / 5, grayMat.Height / 5);
+ //var p1 = OldMatchTemplateHelper.FindSingleTarget(grayLeftTopMat, AutoSkipAssets.StopAutoButtonMat, 0.9);
+ //if (p1 is { X: > 0, Y: > 0 })
+ //{
+ // //_logger.LogInformation($"找到剧情自动按钮:{p1}");
+ // VisionContext.Instance().DrawContent.PutRect("StopAutoButton",
+ // p1.CenterPointToRect(AutoSkipAssets.StopAutoButtonMat).ToRectDrawable());
+ // new InputSimulator().Keyboard.KeyPress(VirtualKeyCode.SPACE);
+ //}
+ //else
+ //{
+ // VisionContext.Instance().DrawContent.RemoveRect("StopAutoButton");
+ //}
+
+ //// 不存在则找右下的选项按钮
+ //var grayRightBottomMat = content.SrcGreyRightBottomMat;
+ //var p2 = OldMatchTemplateHelper.FindSingleTarget(grayRightBottomMat, AutoSkipAssets.OptionMat);
+ //if (p2 is { X: > 0, Y: > 0 })
+ //{
+ // // 不存在菜单的情况下 剧情在播放中
+ // var grayLeftTopMat2 = CutHelper.CutLeftTop(grayMat, grayMat.Width / 4, grayMat.Height / 4);
+ // var pMenu = OldMatchTemplateHelper.FindSingleTarget(grayLeftTopMat2, AutoSkipAssets.MenuMat);
+ // if (pMenu is { X: 0, Y: 0 })
+ // {
+ // p2 = p2.ToDesktopPositionOffset65535(grayMat.Width - grayMat.Width / 2,
+ // grayMat.Height - grayMat.Height / 3 * 2);
+ // new InputSimulator().Mouse.MoveMouseTo(p2.X, p2.Y).LeftButtonClick();
+ // _logger.LogInformation($"点击选项按钮:{p2}");
+ // return;
+ // }
+ //}
+
+
+
}
}
\ No newline at end of file
diff --git a/BetterGenshinImpact/GameTask/CaptureContent.cs b/BetterGenshinImpact/GameTask/CaptureContent.cs
index b8449470..55482f8a 100644
--- a/BetterGenshinImpact/GameTask/CaptureContent.cs
+++ b/BetterGenshinImpact/GameTask/CaptureContent.cs
@@ -17,6 +17,8 @@ public class CaptureContent
public int FrameIndex { get; }
public double TimerInterval { get; }
+ public int FrameRate => (int)(1000 / TimerInterval);
+
public RectArea CaptureRectArea { get; private set; }
public CaptureContent(Bitmap srcBitmap, int frameIndex, double interval)
diff --git a/BetterGenshinImpact/GameTask/Model/SystemInfo.cs b/BetterGenshinImpact/GameTask/Model/SystemInfo.cs
index 83211896..a4935f6b 100644
--- a/BetterGenshinImpact/GameTask/Model/SystemInfo.cs
+++ b/BetterGenshinImpact/GameTask/Model/SystemInfo.cs
@@ -1,13 +1,7 @@
using BetterGenshinImpact.Helpers;
-using Microsoft.Win32.SafeHandles;
using System;
-using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Windows.Win32.Foundation;
-using OpenCvSharp;
+using Vanara.PInvoke;
using Size = System.Drawing.Size;
namespace BetterGenshinImpact.GameTask.Model
diff --git a/BetterGenshinImpact/GameTask/SystemControl.cs b/BetterGenshinImpact/GameTask/SystemControl.cs
index 5684f3ce..5111ba77 100644
--- a/BetterGenshinImpact/GameTask/SystemControl.cs
+++ b/BetterGenshinImpact/GameTask/SystemControl.cs
@@ -1,8 +1,7 @@
using System;
using System.Diagnostics;
using System.Linq;
-using Windows.Win32.Foundation;
-using static Windows.Win32.PInvoke;
+using Vanara.PInvoke;
namespace BetterGenshinImpact.GameTask
{
@@ -21,8 +20,8 @@ namespace BetterGenshinImpact.GameTask
public static bool IsGenshinImpactActive2()
{
- var hWnd = GetForegroundWindow();
- return (nint)hWnd == TaskContext.Instance().GameHandle;
+ var hWnd = User32.GetForegroundWindow();
+ return hWnd == TaskContext.Instance().GameHandle;
}
public static nint FindHandleByProcessName(params string[] names)
@@ -39,29 +38,26 @@ namespace BetterGenshinImpact.GameTask
return 0;
}
- public static unsafe string? GetActiveProcessName()
+ public static string? GetActiveProcessName()
{
try
{
- var hWnd = GetForegroundWindow();
- uint pid = default;
- _ = GetWindowThreadProcessId(hWnd, &pid);
+ var hWnd = User32.GetForegroundWindow();
+ _ = User32.GetWindowThreadProcessId(hWnd, out var pid);
var p = Process.GetProcessById((int)pid);
return p.ProcessName;
}
- catch (Exception ex)
+ catch
{
- Debug.WriteLine(ex);
return null;
}
}
- public static unsafe Process GetProcessByHandle(nint hWnd)
+ public static Process? GetProcessByHandle(IntPtr hWnd)
{
try
{
- uint pid = default;
- _ = GetWindowThreadProcessId((HWND)hWnd, &pid);
+ _ = User32.GetWindowThreadProcessId(hWnd, out var pid);
var p = Process.GetProcessById((int)pid);
return p;
}
@@ -79,7 +75,7 @@ namespace BetterGenshinImpact.GameTask
///
public static RECT GetWindowRect(IntPtr hWnd)
{
- Windows.Win32.PInvoke.GetWindowRect((HWND)hWnd, out var windowRect);
+ User32.GetWindowRect(hWnd, out var windowRect);
return windowRect;
}
@@ -90,7 +86,7 @@ namespace BetterGenshinImpact.GameTask
///
public static RECT GetGameScreenRect(IntPtr hWnd)
{
- GetClientRect((HWND)hWnd, out var clientRect);
+ User32.GetClientRect(hWnd, out var clientRect);
return clientRect;
}
diff --git a/BetterGenshinImpact/GameTask/TaskDispatcher.cs b/BetterGenshinImpact/GameTask/TaskDispatcher.cs
index f6ab5895..afd302a6 100644
--- a/BetterGenshinImpact/GameTask/TaskDispatcher.cs
+++ b/BetterGenshinImpact/GameTask/TaskDispatcher.cs
@@ -1,13 +1,11 @@
-using Microsoft.Extensions.Logging;
+using Fischless.WindowCapture;
+using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
-using System.Windows;
-using Windows.Win32.Foundation;
-using BetterGenshinImpact.GameTask.Model;
-using Vision.WindowCapture;
+using Vanara.PInvoke;
namespace BetterGenshinImpact.GameTask
{
@@ -30,7 +28,7 @@ namespace BetterGenshinImpact.GameTask
//_timer.Tick += Tick;
}
- public void Start(IntPtr hWnd, CaptureModeEnum mode, int interval = 50)
+ public void Start(IntPtr hWnd, CaptureModes mode, int interval = 50)
{
// 初始化任务上下文
TaskContext.Instance().Init(hWnd);
@@ -39,7 +37,7 @@ namespace BetterGenshinImpact.GameTask
// 初始化截图器
_capture = WindowCaptureFactory.Create(mode);
- _capture.Start((HWND)hWnd);
+ _capture.Start(hWnd);
// 启动定时器
_frameIndex = 0;
diff --git a/BetterGenshinImpact/Helpers/DpiHelper.cs b/BetterGenshinImpact/Helpers/DpiHelper.cs
index 5c9c68d3..5ad00d17 100644
--- a/BetterGenshinImpact/Helpers/DpiHelper.cs
+++ b/BetterGenshinImpact/Helpers/DpiHelper.cs
@@ -1,8 +1,6 @@
using System;
using System.Windows;
using System.Windows.Interop;
-using Windows.Win32.Foundation;
-using Windows.Win32.Graphics.Gdi;
using Vanara.PInvoke;
using HMONITOR = Vanara.PInvoke.HMONITOR;
using HWND = Vanara.PInvoke.HWND;
diff --git a/BetterGenshinImpact/Helpers/Extensions/PointExtension.cs b/BetterGenshinImpact/Helpers/Extensions/PointExtension.cs
index 0b76832e..9e337b56 100644
--- a/BetterGenshinImpact/Helpers/Extensions/PointExtension.cs
+++ b/BetterGenshinImpact/Helpers/Extensions/PointExtension.cs
@@ -1,8 +1,6 @@
using BetterGenshinImpact.GameTask;
using OpenCvSharp;
using System;
-using Windows.Win32.Foundation;
-using BetterGenshinImpact.Core.Simulator;
namespace BetterGenshinImpact.Helpers.Extensions
{
@@ -33,7 +31,7 @@ namespace BetterGenshinImpact.Helpers.Extensions
return point;
}
- var rc = SystemControl.GetWindowRect((HWND)TaskContext.Instance().GameHandle);
+ var rc = SystemControl.GetWindowRect(TaskContext.Instance().GameHandle);
return new Point(rc.X + point.X + offsetX, rc.Y + point.Y + offsetY);
}
diff --git a/BetterGenshinImpact/Helpers/PrimaryScreen.cs b/BetterGenshinImpact/Helpers/PrimaryScreen.cs
index f9644bec..85ccee99 100644
--- a/BetterGenshinImpact/Helpers/PrimaryScreen.cs
+++ b/BetterGenshinImpact/Helpers/PrimaryScreen.cs
@@ -1,6 +1,7 @@
-using System.Drawing;
-using Windows.Win32.Graphics.Gdi;
-using static Windows.Win32.PInvoke;
+using System;
+using System.Drawing;
+using static Vanara.PInvoke.Gdi32;
+using Vanara.PInvoke;
namespace BetterGenshinImpact.Helpers
{
@@ -13,13 +14,13 @@ namespace BetterGenshinImpact.Helpers
{
get
{
- var hdc = GetDC(default);
+ var hdc = User32.GetDC(IntPtr.Zero);
var size = new Size
{
- Width = GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.HORZRES),
- Height = GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.VERTRES)
+ Width = Gdi32.GetDeviceCaps(hdc, DeviceCap.HORZRES),
+ Height = Gdi32.GetDeviceCaps(hdc, DeviceCap.VERTRES)
};
- ReleaseDC(default, hdc);
+ User32.ReleaseDC(IntPtr.Zero, hdc);
return size;
}
}
@@ -30,9 +31,9 @@ namespace BetterGenshinImpact.Helpers
{
get
{
- var hdc = GetDC(default);
- var dpiX = GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.LOGPIXELSX);
- ReleaseDC(default, hdc);
+ var hdc = User32.GetDC(IntPtr.Zero);
+ var dpiX = Gdi32.GetDeviceCaps(hdc, DeviceCap.LOGPIXELSX);
+ User32.ReleaseDC(IntPtr.Zero, hdc);
return dpiX;
}
}
@@ -43,9 +44,9 @@ namespace BetterGenshinImpact.Helpers
{
get
{
- var hdc = GetDC(default);
- var dpiX = GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.LOGPIXELSY);
- ReleaseDC(default, hdc);
+ var hdc = User32.GetDC(IntPtr.Zero);
+ var dpiX = Gdi32.GetDeviceCaps(hdc, DeviceCap.LOGPIXELSY);
+ User32.ReleaseDC(IntPtr.Zero, hdc);
return dpiX;
}
}
@@ -56,13 +57,13 @@ namespace BetterGenshinImpact.Helpers
{
get
{
- var hdc = GetDC(default);
+ var hdc = User32.GetDC(IntPtr.Zero);
var size = new Size
{
- Width = GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.DESKTOPHORZRES),
- Height = GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.DESKTOPVERTRES)
+ Width = Gdi32.GetDeviceCaps(hdc, DeviceCap.DESKTOPHORZRES),
+ Height = Gdi32.GetDeviceCaps(hdc, DeviceCap.DESKTOPVERTRES)
};
- ReleaseDC(default, hdc);
+ User32.ReleaseDC(IntPtr.Zero, hdc);
return size;
}
}
@@ -74,9 +75,9 @@ namespace BetterGenshinImpact.Helpers
{
get
{
- var hdc = GetDC(default);
- var scaleX = (float)GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.DESKTOPHORZRES) / GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.HORZRES);
- ReleaseDC(default, hdc);
+ var hdc = User32.GetDC(IntPtr.Zero);
+ var scaleX = (float)Gdi32.GetDeviceCaps(hdc, DeviceCap.DESKTOPHORZRES) / (float)Gdi32.GetDeviceCaps(hdc, DeviceCap.HORZRES);
+ User32.ReleaseDC(IntPtr.Zero, hdc);
return scaleX;
}
}
@@ -87,11 +88,12 @@ namespace BetterGenshinImpact.Helpers
{
get
{
- var hdc = GetDC(default);
- var scaleY = (float)GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.DESKTOPVERTRES) / GetDeviceCaps(hdc, GET_DEVICE_CAPS_INDEX.VERTRES);
- ReleaseDC(default, hdc);
+ var hdc = User32.GetDC(IntPtr.Zero);
+ var scaleY = (float)Gdi32.GetDeviceCaps(hdc, DeviceCap.DESKTOPVERTRES) / (float)Gdi32.GetDeviceCaps(hdc, DeviceCap.VERTRES);
+ User32.ReleaseDC(IntPtr.Zero, hdc);
return scaleY;
}
}
}
+
}
diff --git a/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs b/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs
index bc8552bd..1767c825 100644
--- a/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs
+++ b/BetterGenshinImpact/View/CaptureTestWindow.xaml.cs
@@ -1,10 +1,10 @@
using BetterGenshinImpact.Helpers.Extensions;
+using Fischless.WindowCapture;
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Media;
-using Windows.Win32.Foundation;
-using Vision.WindowCapture;
+using Vanara.PInvoke;
namespace BetterGenshinImpact.View.Test
{
@@ -19,7 +19,7 @@ namespace BetterGenshinImpact.View.Test
InitializeComponent();
}
- public void StartCapture(IntPtr hWnd, CaptureModeEnum captureMode)
+ public void StartCapture(IntPtr hWnd, CaptureModes captureMode)
{
if (hWnd == IntPtr.Zero)
{
@@ -29,7 +29,7 @@ namespace BetterGenshinImpact.View.Test
_capture = WindowCaptureFactory.Create(captureMode);
- _capture.Start((HWND)hWnd);
+ _capture.Start(hWnd);
CompositionTarget.Rendering += Loop;
}
diff --git a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs
index 58ff5196..99bfc296 100644
--- a/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs
+++ b/BetterGenshinImpact/ViewModel/MainWindowViewModel.cs
@@ -1,17 +1,15 @@
-using BetterGenshinImpact.Core.Simulator;
-using BetterGenshinImpact.GameTask;
+using BetterGenshinImpact.GameTask;
using BetterGenshinImpact.Helpers;
using BetterGenshinImpact.View;
using BetterGenshinImpact.View.Test;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
+using Fischless.WindowCapture;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
using System.Windows;
-using Vision.WindowCapture;
-using Windows.Win32.Foundation;
-using static Windows.Win32.PInvoke;
+using Vanara.PInvoke;
namespace BetterGenshinImpact.ViewModel
{
@@ -19,7 +17,7 @@ namespace BetterGenshinImpact.ViewModel
{
[ObservableProperty] private string[] _modeNames = WindowCaptureFactory.ModeNames();
- [ObservableProperty] private string? _selectedMode = CaptureModeEnum.BitBlt.ToString();
+ [ObservableProperty] private string? _selectedMode = CaptureModes.BitBlt.ToString();
private MaskWindow? _maskWindow;
private readonly ILogger _logger = App.GetLogger();
@@ -98,7 +96,7 @@ namespace BetterGenshinImpact.ViewModel
private void ShowMaskWindow(IntPtr hWnd)
{
- GetWindowRect((HWND)hWnd, out var rect);
+ User32.GetWindowRect(hWnd, out var rect);
//var x = rect.X;
//var y = rect.Y;
//var w = rect.Width;
diff --git a/Fischless.WindowCapture/BitBlt/BitBltCapture.cs b/Fischless.WindowCapture/BitBlt/BitBltCapture.cs
new file mode 100644
index 00000000..c2cd7f54
--- /dev/null
+++ b/Fischless.WindowCapture/BitBlt/BitBltCapture.cs
@@ -0,0 +1,113 @@
+using System.Diagnostics;
+using Vanara.PInvoke;
+
+namespace Fischless.WindowCapture.BitBlt;
+
+public class BitBltCapture : IWindowCapture
+{
+ private nint _hWnd;
+ public bool IsCapturing { get; private set; }
+ public bool IsClientEnabled { get; set; } = false;
+ public bool IsCursorCaptureEnabled { get; set; } = false;
+
+ public void Dispose()
+ {
+ Stop();
+ }
+
+ public void Start(nint hWnd)
+ {
+ _hWnd = hWnd;
+ IsCapturing = true;
+ }
+
+ public Bitmap? Capture()
+ {
+ if (_hWnd == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ try
+ {
+ if (IsClientEnabled)
+ {
+ _ = User32.GetWindowRect(_hWnd, out var windowRect);
+ var width = windowRect.right - windowRect.left;
+ var height = windowRect.bottom - windowRect.top;
+
+ var hdcSrc = User32.GetWindowDC(_hWnd);
+ var hdcDest = Gdi32.CreateCompatibleDC(hdcSrc);
+ var hBitmap = Gdi32.CreateCompatibleBitmap(hdcSrc, width, height);
+ var hOld = Gdi32.SelectObject(hdcDest, hBitmap);
+ _ = Gdi32.BitBlt(hdcDest, 0, 0, width, height, hdcSrc, 0, 0, Gdi32.RasterOperationMode.SRCCOPY);
+ _ = Gdi32.SelectObject(hdcDest, hOld);
+ _ = Gdi32.DeleteDC(hdcDest);
+ _ = User32.ReleaseDC(_hWnd, hdcSrc);
+
+ var bitmap = hBitmap.ToBitmap();
+ _ = Gdi32.DeleteObject(hBitmap);
+ return bitmap;
+ }
+ else
+ {
+ _ = User32.GetClientRect(_hWnd, out var windowRect);
+ int x = default, y = default;
+ int width = windowRect.right - windowRect.left;
+ int height = windowRect.bottom - windowRect.top;
+
+ Bitmap bitmap = new(width, height);
+ using System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(bitmap);
+ nint hdcDest = g.GetHdc();
+ Gdi32.SafeHDC hdcSrc = User32.GetDC(_hWnd == IntPtr.Zero ? User32.GetDesktopWindow() : _hWnd);
+ _ = Gdi32.StretchBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, width, height, Gdi32.RasterOperationMode.SRCCOPY);
+ g.ReleaseHdc();
+ _ = Gdi32.DeleteDC(hdcDest);
+ _ = Gdi32.DeleteDC(hdcSrc);
+ return bitmap;
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+ return null!;
+ }
+
+ public Bitmap? Capture(int x, int y, int width, int height)
+ {
+ if (_hWnd == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ if (IsClientEnabled)
+ {
+ throw new NotSupportedException();
+ }
+
+ try
+ {
+ Bitmap copied = new(width, height);
+ using System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(copied);
+ nint hdcDest = g.GetHdc();
+ Gdi32.SafeHDC hdcSrc = User32.GetDC(_hWnd == IntPtr.Zero ? User32.GetDesktopWindow() : _hWnd);
+ _ = Gdi32.StretchBlt(hdcDest, 0, 0, width, height, hdcSrc, x, y, width, height, Gdi32.RasterOperationMode.SRCCOPY);
+ g.ReleaseHdc();
+ _ = Gdi32.DeleteDC(hdcDest);
+ _ = Gdi32.DeleteDC(hdcSrc);
+ return copied;
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+ return null;
+ }
+
+ public void Stop()
+ {
+ _hWnd = IntPtr.Zero;
+ IsCapturing = false;
+ }
+}
diff --git a/Fischless.WindowCapture/BitmapExtensions.cs b/Fischless.WindowCapture/BitmapExtensions.cs
new file mode 100644
index 00000000..4cb4d091
--- /dev/null
+++ b/Fischless.WindowCapture/BitmapExtensions.cs
@@ -0,0 +1,14 @@
+namespace Fischless.WindowCapture;
+
+internal static class BitmapExtensions
+{
+ public static Bitmap Crop(this Bitmap src, int x, int y, int width, int height)
+ {
+ Rectangle cropRect = new(x, y, width, height);
+ Bitmap target = new(cropRect.Width, cropRect.Height);
+
+ using System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(target);
+ g.DrawImage(src, new Rectangle(0, 0, target.Width, target.Height), cropRect, GraphicsUnit.Pixel);
+ return target;
+ }
+}
diff --git a/Fischless.WindowCapture/CaptionHelper.cs b/Fischless.WindowCapture/CaptionHelper.cs
new file mode 100644
index 00000000..c488b83b
--- /dev/null
+++ b/Fischless.WindowCapture/CaptionHelper.cs
@@ -0,0 +1,28 @@
+using System.Windows;
+using Vanara.PInvoke;
+
+namespace Fischless.WindowCapture;
+
+internal static class CaptionHelper
+{
+ public static bool IsFullScreenMode(nint hWnd)
+ {
+ if (hWnd == IntPtr.Zero)
+ {
+ return false;
+ }
+
+ int exStyle = User32.GetWindowLong(hWnd, User32.WindowLongFlags.GWL_EXSTYLE);
+
+ if ((exStyle & (int)User32.WindowStylesEx.WS_EX_TOPMOST) != 0)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ public static int GetSystemCaptionHeight()
+ {
+ return (int)Math.Round(SystemParameters.CaptionHeight * DpiHelper.ScaleY);
+ }
+}
diff --git a/Fischless.WindowCapture/CaptureModeExtensions.cs b/Fischless.WindowCapture/CaptureModeExtensions.cs
new file mode 100644
index 00000000..d54c60c2
--- /dev/null
+++ b/Fischless.WindowCapture/CaptureModeExtensions.cs
@@ -0,0 +1,9 @@
+namespace Fischless.WindowCapture;
+
+public static class CaptureModeExtensions
+{
+ public static CaptureModes ToCaptureMode(this string modeName)
+ {
+ return (CaptureModes)Enum.Parse(typeof(CaptureModes), modeName);
+ }
+}
diff --git a/Fischless.WindowCapture/CaptureModes.cs b/Fischless.WindowCapture/CaptureModes.cs
new file mode 100644
index 00000000..bf0e1f9a
--- /dev/null
+++ b/Fischless.WindowCapture/CaptureModes.cs
@@ -0,0 +1,7 @@
+namespace Fischless.WindowCapture;
+
+public enum CaptureModes
+{
+ BitBlt,
+ WindowsGraphicsCapture,
+}
diff --git a/Fischless.WindowCapture/DpiHelper.cs b/Fischless.WindowCapture/DpiHelper.cs
new file mode 100644
index 00000000..50a9d63d
--- /dev/null
+++ b/Fischless.WindowCapture/DpiHelper.cs
@@ -0,0 +1,27 @@
+using System.Windows.Interop;
+using Vanara.PInvoke;
+using Application = System.Windows.Application;
+
+namespace Fischless.WindowCapture;
+
+internal static class DpiHelper
+{
+ public static float ScaleY => GetScaleY();
+
+ private static float GetScaleY()
+ {
+ if (Environment.OSVersion.Version >= new Version(6, 3)
+ && Application.Current?.MainWindow != null)
+ {
+ HWND hWnd = new WindowInteropHelper(Application.Current?.MainWindow).Handle;
+ HMONITOR hMonitor = User32.MonitorFromWindow(hWnd, User32.MonitorFlags.MONITOR_DEFAULTTONEAREST);
+ _ = SHCore.GetDpiForMonitor(hMonitor, SHCore.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out _, out uint dpiY);
+ return dpiY / 96f;
+ }
+
+ HDC hdc = User32.GetDC(HWND.NULL);
+ float scaleY = Gdi32.GetDeviceCaps(hdc, Gdi32.DeviceCap.LOGPIXELSY);
+ _ = User32.ReleaseDC(HWND.NULL, hdc);
+ return scaleY / 96f;
+ }
+}
diff --git a/Fischless.WindowCapture/Fischless.WindowCapture.csproj b/Fischless.WindowCapture/Fischless.WindowCapture.csproj
new file mode 100644
index 00000000..f008b74b
--- /dev/null
+++ b/Fischless.WindowCapture/Fischless.WindowCapture.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net7.0-windows10.0.22621.0
+ enable
+ enable
+ x64
+ True
+ True
+ 11.0
+ true
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Fischless.WindowCapture/Graphics/GraphicsCapture.cs b/Fischless.WindowCapture/Graphics/GraphicsCapture.cs
new file mode 100644
index 00000000..d76411cd
--- /dev/null
+++ b/Fischless.WindowCapture/Graphics/GraphicsCapture.cs
@@ -0,0 +1,152 @@
+using System.Diagnostics;
+using System.Windows;
+using Vanara.PInvoke;
+using Windows.Graphics.Capture;
+using Windows.Graphics.DirectX;
+
+namespace Fischless.WindowCapture.Graphics;
+
+public class GraphicsCapture : IWindowCapture
+{
+ private nint _hWnd;
+
+ private Direct3D11CaptureFramePool _captureFramePool = null!;
+ private GraphicsCaptureItem _captureItem = null!;
+ private GraphicsCaptureSession _captureSession = null!;
+
+ public bool IsCapturing { get; private set; }
+ public bool IsClientEnabled { get; set; } = false;
+
+ public void Dispose()
+ {
+ Stop();
+ }
+
+ public void Start(nint hWnd)
+ {
+ _hWnd = hWnd;
+ IsCapturing = true;
+
+ _captureItem = CaptureHelper.CreateItemForWindow(_hWnd);
+
+ if (_captureItem == null)
+ {
+ throw new InvalidOperationException("Failed to create capture item.");
+ }
+
+ _captureItem.Closed += CaptureItemOnClosed;
+
+ var device = Direct3D11Helper.CreateDevice();
+
+ _captureFramePool = Direct3D11CaptureFramePool.Create(device, DirectXPixelFormat.B8G8R8A8UIntNormalized, 2,
+ _captureItem.Size);
+ _captureSession = _captureFramePool.CreateCaptureSession(_captureItem);
+ _captureSession.IsCursorCaptureEnabled = false;
+ _captureSession.IsBorderRequired = false;
+ _captureSession.StartCapture();
+ IsCapturing = true;
+ }
+
+ public Bitmap? Capture()
+ {
+ if (_hWnd == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ try
+ {
+ using var frame = _captureFramePool?.TryGetNextFrame();
+
+ if (frame == null)
+ {
+ return null;
+ }
+
+ Bitmap? bitmap = frame.ToBitmap();
+
+ if (bitmap == null)
+ {
+ return null;
+ }
+
+ if (IsClientEnabled)
+ {
+ return bitmap;
+ }
+ else
+ {
+ _ = User32.GetClientRect(_hWnd, out var windowRect);
+ int border = Math.Max(SystemParameters.Border, 1);
+ int captionHeight = CaptionHelper.IsFullScreenMode(_hWnd)
+ ? default
+ : Math.Max(CaptionHelper.GetSystemCaptionHeight(), frame.ContentSize.Height - windowRect.Height - border * 2);
+
+ using (bitmap)
+ {
+ return bitmap.Crop(
+ border,
+ border + captionHeight,
+ frame.ContentSize.Width - border * 2,
+ frame.ContentSize.Height - border * 2 - captionHeight
+ );
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+ return null;
+ }
+
+ public Bitmap? Capture(int x, int y, int width, int height)
+ {
+ if (_hWnd == IntPtr.Zero)
+ {
+ return null;
+ }
+
+ try
+ {
+ using var frame = _captureFramePool?.TryGetNextFrame();
+ using Bitmap bitmap = frame?.ToBitmap();
+ _ = User32.GetClientRect(_hWnd, out var windowRect);
+ int border = Math.Max(SystemParameters.Border, 1);
+ int captionHeight = CaptionHelper.IsFullScreenMode(_hWnd)
+ ? default
+ : Math.Max(CaptionHelper.GetSystemCaptionHeight()
+ , frame.ContentSize.Height - windowRect.Height - border * 2
+ );
+
+ return bitmap.Crop(
+ border + x,
+ border + y + captionHeight,
+ width - border * 2,
+ height - border * 2 - captionHeight
+ );
+ }
+ catch (Exception e)
+ {
+ Debug.WriteLine(e);
+ }
+ return null;
+ }
+
+ public void Stop()
+ {
+ _captureSession?.Dispose();
+ _captureFramePool?.Dispose();
+ _captureSession = null!;
+ _captureFramePool = null!;
+ _captureItem = null!;
+
+ _hWnd = IntPtr.Zero;
+ IsCapturing = false;
+ }
+
+ private void CaptureItemOnClosed(GraphicsCaptureItem sender, object args)
+ {
+ Stop();
+ }
+}
diff --git a/Fischless.WindowCapture/Graphics/Helpers/CaptureHelper.cs b/Fischless.WindowCapture/Graphics/Helpers/CaptureHelper.cs
new file mode 100644
index 00000000..61707aa9
--- /dev/null
+++ b/Fischless.WindowCapture/Graphics/Helpers/CaptureHelper.cs
@@ -0,0 +1,49 @@
+using System.Runtime.InteropServices;
+using Windows.Graphics.Capture;
+using WinRT;
+
+namespace Fischless.WindowCapture.Graphics;
+
+public static class CaptureHelper
+{
+ static readonly Guid GraphicsCaptureItemGuid = new("79C3F95B-31F7-4EC2-A464-632EF5D30760");
+
+ [ComImport]
+ [Guid("3E68D4BD-7135-4D10-8018-9FB6D9F33FA1")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ [ComVisible(true)]
+ interface IInitializeWithWindow
+ {
+ void Initialize(
+ nint hWnd);
+ }
+
+ [ComImport]
+ [Guid("3628E81B-3CAC-4C60-B7F4-23CE0E0C3356")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ [ComVisible(true)]
+ interface IGraphicsCaptureItemInterop
+ {
+ nint CreateForWindow(
+ [In] nint window,
+ [In] ref Guid iid);
+
+ nint CreateForMonitor(
+ [In] nint monitor,
+ [In] ref Guid iid);
+ }
+
+ public static void SetWindow(this GraphicsCapturePicker picker, nint hWnd)
+ {
+ var interop = picker.As();
+ interop.Initialize(hWnd);
+ }
+
+ public static GraphicsCaptureItem CreateItemForWindow(nint hWnd)
+ {
+ var factory = WinrtModule.GetActivationFactory("Windows.Graphics.Capture.GraphicsCaptureItem");
+ var interop = factory.AsInterface();
+ var itemPointer = interop.CreateForWindow(hWnd, GraphicsCaptureItemGuid);
+ return GraphicsCaptureItem.FromAbi(itemPointer);
+ }
+}
diff --git a/Fischless.WindowCapture/Graphics/Helpers/Direct3D11Helper.cs b/Fischless.WindowCapture/Graphics/Helpers/Direct3D11Helper.cs
new file mode 100644
index 00000000..6a5fc5ac
--- /dev/null
+++ b/Fischless.WindowCapture/Graphics/Helpers/Direct3D11Helper.cs
@@ -0,0 +1,115 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
+using Windows.Graphics.DirectX.Direct3D11;
+using WinRT;
+
+namespace Fischless.WindowCapture.Graphics;
+
+[SuppressMessage("CodeQuality", "IDE0052:")]
+public static class Direct3D11Helper
+{
+ internal static Guid IInspectable = new("AF86E2E0-B12D-4c6a-9C5A-D7AA65101E90");
+ internal static Guid ID3D11Resource = new("dc8e63f3-d12b-4952-b47b-5e45026a862d");
+ internal static Guid IDXGIAdapter3 = new("645967A4-1392-4310-A798-8053CE3E93FD");
+ internal static Guid ID3D11Device = new("db6f6ddb-ac77-4e88-8253-819df9bbf140");
+ internal static Guid ID3D11Texture2D = new("6f15aaf2-d208-4e89-9ab4-489535d34f9c");
+
+ [ComImport]
+ [Guid("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1")]
+ [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+ [ComVisible(true)]
+ interface IDirect3DDxgiInterfaceAccess
+ {
+ nint GetInterface([In] ref Guid iid);
+ };
+
+ [DllImport(
+ "d3d11.dll",
+ EntryPoint = "CreateDirect3D11DeviceFromDXGIDevice",
+ SetLastError = true,
+ CharSet = CharSet.Unicode,
+ ExactSpelling = true,
+ CallingConvention = CallingConvention.StdCall
+ )]
+ static extern uint CreateDirect3D11DeviceFromDXGIDevice(nint dxgiDevice, out nint graphicsDevice);
+
+ [DllImport(
+ "d3d11.dll",
+ EntryPoint = "CreateDirect3D11SurfaceFromDXGISurface",
+ SetLastError = true,
+ CharSet = CharSet.Unicode,
+ ExactSpelling = true,
+ CallingConvention = CallingConvention.StdCall
+ )]
+ static extern uint CreateDirect3D11SurfaceFromDXGISurface(nint dxgiSurface, out nint graphicsSurface);
+
+ public static IDirect3DDevice CreateDevice()
+ {
+ return CreateDevice(false);
+ }
+
+ public static IDirect3DDevice CreateDevice(bool useWARP)
+ {
+ var d3dDevice = new SharpDX.Direct3D11.Device(
+ useWARP ? SharpDX.Direct3D.DriverType.Software : SharpDX.Direct3D.DriverType.Hardware,
+ SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport);
+ var device = CreateDirect3DDeviceFromSharpDXDevice(d3dDevice);
+ return device;
+ }
+
+ public static IDirect3DDevice CreateDirect3DDeviceFromSharpDXDevice(SharpDX.Direct3D11.Device d3dDevice)
+ {
+ IDirect3DDevice device = null;
+
+ // Acquire the DXGI interface for the Direct3D device.
+ using (var dxgiDevice = d3dDevice.QueryInterface())
+ {
+ // Wrap the native device using a WinRT interop object.
+ uint hr = CreateDirect3D11DeviceFromDXGIDevice(dxgiDevice.NativePointer, out nint pUnknown);
+
+ if (hr == 0)
+ {
+ device = MarshalInterface.FromAbi(pUnknown);
+ Marshal.Release(pUnknown);
+ }
+ }
+
+ return device;
+ }
+
+ public static IDirect3DSurface CreateDirect3DSurfaceFromSharpDXTexture(SharpDX.Direct3D11.Texture2D texture)
+ {
+ IDirect3DSurface surface = null;
+
+ // Acquire the DXGI interface for the Direct3D surface.
+ using (var dxgiSurface = texture.QueryInterface())
+ {
+ // Wrap the native device using a WinRT interop object.
+ uint hr = CreateDirect3D11SurfaceFromDXGISurface(dxgiSurface.NativePointer, out nint pUnknown);
+
+ if (hr == 0)
+ {
+ surface = Marshal.GetObjectForIUnknown(pUnknown) as IDirect3DSurface;
+ Marshal.Release(pUnknown);
+ }
+ }
+
+ return surface;
+ }
+
+ public static SharpDX.Direct3D11.Device CreateSharpDXDevice(IDirect3DDevice device)
+ {
+ var access = (IDirect3DDxgiInterfaceAccess)device;
+ var d3dPointer = access.GetInterface(ID3D11Device);
+ var d3dDevice = new SharpDX.Direct3D11.Device(d3dPointer);
+ return d3dDevice;
+ }
+
+ public static SharpDX.Direct3D11.Texture2D CreateSharpDXTexture2D(IDirect3DSurface surface)
+ {
+ var access = surface.As();
+ var d3dPointer = access.GetInterface(ID3D11Texture2D);
+ var d3dSurface = new SharpDX.Direct3D11.Texture2D(d3dPointer);
+ return d3dSurface;
+ }
+}
diff --git a/Fischless.WindowCapture/Graphics/Helpers/Texture2DExtensions.cs b/Fischless.WindowCapture/Graphics/Helpers/Texture2DExtensions.cs
new file mode 100644
index 00000000..e31328d0
--- /dev/null
+++ b/Fischless.WindowCapture/Graphics/Helpers/Texture2DExtensions.cs
@@ -0,0 +1,51 @@
+using SharpDX;
+using SharpDX.Direct3D11;
+using SharpDX.DXGI;
+using System.Drawing.Imaging;
+using Windows.Graphics.Capture;
+
+namespace Fischless.WindowCapture.Graphics;
+
+public static class Texture2DExtensions
+{
+ public static Bitmap? ToBitmap(this Direct3D11CaptureFrame frame)
+ {
+ var texture2dBitmap = Direct3D11Helper.CreateSharpDXTexture2D(frame.Surface);
+
+ var d3dDevice = texture2dBitmap.Device;
+
+ // Create texture copy
+ var staging = new Texture2D(d3dDevice, new Texture2DDescription
+ {
+ Width = frame.ContentSize.Width,
+ Height = frame.ContentSize.Height,
+ MipLevels = 1,
+ ArraySize = 1,
+ Format = texture2dBitmap.Description.Format,
+ Usage = ResourceUsage.Staging,
+ SampleDescription = new SampleDescription(1, 0),
+ BindFlags = BindFlags.None,
+ CpuAccessFlags = CpuAccessFlags.Read,
+ OptionFlags = ResourceOptionFlags.None
+ });
+
+ try
+ {
+ // Copy data
+ d3dDevice.ImmediateContext.CopyResource(texture2dBitmap, staging);
+
+ var dataBox = d3dDevice.ImmediateContext.MapSubresource(staging, 0, 0, MapMode.Read,
+ SharpDX.Direct3D11.MapFlags.None,
+ out DataStream stream);
+
+ var bitmap = new Bitmap(staging.Description.Width, staging.Description.Height, dataBox.RowPitch,
+ PixelFormat.Format32bppArgb, dataBox.DataPointer);
+
+ return bitmap;
+ }
+ finally
+ {
+ staging.Dispose();
+ }
+ }
+}
diff --git a/Fischless.WindowCapture/Graphics/Helpers/WinRT.cs b/Fischless.WindowCapture/Graphics/Helpers/WinRT.cs
new file mode 100644
index 00000000..4b06d7f8
--- /dev/null
+++ b/Fischless.WindowCapture/Graphics/Helpers/WinRT.cs
@@ -0,0 +1,111 @@
+using System.ComponentModel;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using WinRT;
+
+namespace Fischless.WindowCapture.Graphics;
+
+#pragma warning disable CS0649
+
+[ComImport]
+[Guid("3628E81B-3CAC-4C60-B7F4-23CE0E0C3356")]
+[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
+internal interface IGraphicsCaptureItemInterop
+{
+ int CreateForWindow([In] nint window, [In] ref Guid iid, out nint result);
+
+ int CreateForMonitor([In] nint monitor, [In] ref Guid iid, out nint result);
+}
+
+[Guid("00000035-0000-0000-C000-000000000046")]
+internal unsafe struct IActivationFactoryVftbl
+{
+ public readonly IInspectable.Vftbl IInspectableVftbl;
+ private readonly void* _ActivateInstance;
+
+ public delegate* unmanaged[Stdcall] ActivateInstance => (delegate* unmanaged[Stdcall])_ActivateInstance;
+}
+
+internal class Platform
+{
+ [DllImport("api-ms-win-core-com-l1-1-0.dll")]
+ internal static extern int CoDecrementMTAUsage(nint cookie);
+
+ [DllImport("api-ms-win-core-com-l1-1-0.dll")]
+ internal static extern unsafe int CoIncrementMTAUsage(nint* cookie);
+
+ [DllImport("api-ms-win-core-winrt-l1-1-0.dll")]
+ internal static extern unsafe int RoGetActivationFactory(nint runtimeClassId, ref Guid iid, nint* factory);
+}
+
+///
+/// https://github.com/zlatanov/windows-screen-recorder
+///
+internal static class WinrtModule
+{
+ private static readonly Dictionary> Cache = new();
+
+ public static ObjectReference GetActivationFactory(string runtimeClassId)
+ {
+ lock (Cache)
+ {
+ if (Cache.TryGetValue(runtimeClassId, out var factory))
+ return factory;
+
+ var m = MarshalString.CreateMarshaler(runtimeClassId);
+
+ try
+ {
+ var instancePtr = GetActivationFactory(MarshalString.GetAbi(m));
+
+ factory = ObjectReference.Attach(ref instancePtr);
+ Cache.Add(runtimeClassId, factory);
+
+ return factory;
+ }
+ finally
+ {
+ m.Dispose();
+ }
+ }
+ }
+
+ private static unsafe nint GetActivationFactory(nint hstrRuntimeClassId)
+ {
+ if (s_cookie == IntPtr.Zero)
+ {
+ lock (s_lock)
+ {
+ if (s_cookie == IntPtr.Zero)
+ {
+ nint cookie;
+ Marshal.ThrowExceptionForHR(Platform.CoIncrementMTAUsage(&cookie));
+
+ s_cookie = cookie;
+ }
+ }
+ }
+
+ Guid iid = typeof(IActivationFactoryVftbl).GUID;
+ nint instancePtr;
+ int hr = Platform.RoGetActivationFactory(hstrRuntimeClassId, ref iid, &instancePtr);
+
+ if (hr == 0)
+ return instancePtr;
+
+ throw new Win32Exception(hr);
+ }
+
+ public static bool ResurrectObjectReference(IObjectReference objRef)
+ {
+ var disposedField = objRef.GetType().GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance)!;
+ if (!(bool)disposedField.GetValue(objRef)!)
+ return false;
+ disposedField.SetValue(objRef, false);
+ GC.ReRegisterForFinalize(objRef);
+ return true;
+ }
+
+ private static nint s_cookie;
+ private static readonly object s_lock = new();
+}
diff --git a/Fischless.WindowCapture/IWindowCapture.cs b/Fischless.WindowCapture/IWindowCapture.cs
new file mode 100644
index 00000000..21f741a8
--- /dev/null
+++ b/Fischless.WindowCapture/IWindowCapture.cs
@@ -0,0 +1,14 @@
+namespace Fischless.WindowCapture;
+
+public interface IWindowCapture : IDisposable
+{
+ public bool IsCapturing { get; }
+ public bool IsClientEnabled { get; set; }
+
+ public void Start(nint hWnd);
+
+ public Bitmap? Capture();
+ public Bitmap? Capture(int x, int y, int width, int height);
+
+ public void Stop();
+}
diff --git a/Fischless.WindowCapture/WindowCaptureBenchmark.cs b/Fischless.WindowCapture/WindowCaptureBenchmark.cs
new file mode 100644
index 00000000..c982371c
--- /dev/null
+++ b/Fischless.WindowCapture/WindowCaptureBenchmark.cs
@@ -0,0 +1,44 @@
+using System.Diagnostics;
+
+namespace Fischless.WindowCapture;
+
+public class WindowCaptureBenchmark
+{
+ public static void Action()
+ {
+ foreach (CaptureModes mode in Enum.GetValues(typeof(CaptureModes)))
+ {
+ _ = Task.Run(async () =>
+ {
+ IWindowCapture capture = WindowCaptureFactory.Create(mode);
+
+ capture.Start(GetHwnd());
+ await Task.Delay(1234);
+ using Bitmap frame = capture.Capture();
+ frame?.Save($"Benchmark_{mode}_{frame.Width}x{frame.Height}_DPI{DpiHelper.ScaleY * 100f:F0}.jpg");
+ });
+ }
+ }
+
+ private static nint GetHwnd()
+ {
+ Process[] processes = Process.GetProcessesByName("YuanShen");
+
+ if (processes.Length <= 0)
+ {
+ processes = Process.GetProcessesByName("Genshin Impact");
+ }
+ if (processes.Length <= 0)
+ {
+ processes = Process.GetProcessesByName("GenshinImpact");
+ }
+ if (processes.Length > 0)
+ {
+ foreach (Process? process in processes)
+ {
+ return process.MainWindowHandle;
+ }
+ }
+ return IntPtr.Zero;
+ }
+}
diff --git a/Fischless.WindowCapture/WindowCaptureFactory.cs b/Fischless.WindowCapture/WindowCaptureFactory.cs
new file mode 100644
index 00000000..41179156
--- /dev/null
+++ b/Fischless.WindowCapture/WindowCaptureFactory.cs
@@ -0,0 +1,19 @@
+namespace Fischless.WindowCapture;
+
+public class WindowCaptureFactory
+{
+ public static string[] ModeNames()
+ {
+ return Enum.GetNames(typeof(CaptureModes));
+ }
+
+ public static IWindowCapture Create(CaptureModes mode)
+ {
+ return mode switch
+ {
+ CaptureModes.BitBlt => new BitBlt.BitBltCapture(),
+ CaptureModes.WindowsGraphicsCapture => new Graphics.GraphicsCapture(),
+ _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, null),
+ };
+ }
+}