diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index 10b057b..0000000
--- a/.editorconfig
+++ /dev/null
@@ -1,43 +0,0 @@
-[*.cs]
-
-# IDE0022: 使用方法的程序块主体
-csharp_style_expression_bodied_methods = true
-
-# IDE0008: 使用显式类型
-csharp_style_var_elsewhere = true
-
-# IDE0008: 使用显式类型
-csharp_style_var_for_built_in_types = true
-
-# IDE0008: 使用显式类型
-csharp_style_var_when_type_is_apparent = true
-
-# IDE0058: 永远不会使用表达式值
-csharp_style_unused_value_expression_statement_preference = unused_local_variable
-
-# IDE0058: 永远不会使用表达式值
-dotnet_diagnostic.IDE0058.severity = none
-
-# IDE0011: 添加大括号
-csharp_prefer_braces = when_multiline
-
-# CA1816: Dispose 方法应调用 SuppressFinalize
-dotnet_diagnostic.CA1816.severity = none
-
-###############################
-# Core EditorConfig Options #
-###############################
-
-insert_final_newline = true
-
-###############################
-# .NET Coding Conventions #
-###############################
-
-# Organize usings
-dotnet_sort_system_directives_first = true
-
-
-# Editor Guidelines extension
-[*.cs]
-guidelines = 120
diff --git a/.gitignore b/.gitignore
index 8fc32d6..c32152f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,347 +1,8 @@
-## Ignore Visual Studio temporary files, build results, and
-## files generated by popular Visual Studio add-ons.
-##
-## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
-
-# User-specific files
-*.rsuser
-*.suo
-*.user
-*.userosscache
-*.sln.docstates
-
-# User-specific files (MonoDevelop/Xamarin Studio)
-*.userprefs
-
-# Build results
-[Dd]ebug/
-[Dd]ebugPublic/
-[Rr]elease/
-[Rr]eleases/
-[Aa][Rr][Mm]/
-[Aa][Rr][Mm]64/
-bld/
-[Bb]in/
-[Oo]bj/
-[Ll]og/
-
-# Visual Studio 2015/2017 cache/options directory
.vs/
-# Uncomment if you have tasks that create the project's static files in wwwroot
-#wwwroot/
-
-# Visual Studio 2017 auto generated files
-Generated\ Files/
-
-# MSTest test Results
-[Tt]est[Rr]esult*/
-[Bb]uild[Ll]og.*
-
-# NUNIT
-*.VisualState.xml
-TestResult.xml
-
-# Build Results of an ATL Project
-[Dd]ebugPS/
-[Rr]eleasePS/
-dlldata.c
-
-# Benchmark Results
-BenchmarkDotNet.Artifacts/
-
-# .NET Core
-project.lock.json
-project.fragment.lock.json
-artifacts/
-
-# StyleCop
-StyleCopReport.xml
-
-# Files built by Visual Studio
-*_i.c
-*_p.c
-*_h.h
-*.ilk
-*.meta
-*.obj
-*.iobj
-*.pch
-*.pdb
-*.ipdb
-*.pgc
-*.pgd
-*.rsp
-*.sbr
-*.tlb
-*.tli
-*.tlh
-*.tmp
-*.tmp_proj
-*_wpftmp.csproj
-*.log
-*.vspscc
-*.vssscc
-.builds
-*.pidb
-*.svclog
-*.scc
-
-# Chutzpah Test files
-_Chutzpah*
-
-# Visual C++ cache files
-ipch/
-*.aps
-*.ncb
-*.opendb
-*.opensdf
-*.sdf
-*.cachefile
-*.VC.db
-*.VC.VC.opendb
-
-# Visual Studio profiler
-*.psess
-*.vsp
-*.vspx
-*.sap
-
-# Visual Studio Trace Files
-*.e2e
-
-# TFS 2012 Local Workspace
-$tf/
-
-# Guidance Automation Toolkit
-*.gpState
-
-# ReSharper is a .NET coding add-in
-_ReSharper*/
-*.[Rr]e[Ss]harper
-*.DotSettings.user
-
-# JustCode is a .NET coding add-in
-.JustCode
-
-# TeamCity is a build add-in
-_TeamCity*
-
-# DotCover is a Code Coverage Tool
-*.dotCover
-
-# AxoCover is a Code Coverage Tool
-.axoCover/*
-!.axoCover/settings.json
-
-# Visual Studio code coverage results
-*.coverage
-*.coveragexml
-
-# NCrunch
-_NCrunch_*
-.*crunch*.local.xml
-nCrunchTemp_*
-
-# MightyMoose
-*.mm.*
-AutoTest.Net/
-
-# Web workbench (sass)
-.sass-cache/
-
-# Installshield output folder
-[Ee]xpress/
-
-# DocProject is a documentation generator add-in
-DocProject/buildhelp/
-DocProject/Help/*.HxT
-DocProject/Help/*.HxC
-DocProject/Help/*.hhc
-DocProject/Help/*.hhk
-DocProject/Help/*.hhp
-DocProject/Help/Html2
-DocProject/Help/html
-
-# Click-Once directory
-publish/
-
-# Publish Web Output
-*.[Pp]ublish.xml
-*.azurePubxml
-# Note: Comment the next line if you want to checkin your web deploy settings,
-# but database connection strings (with potential passwords) will be unencrypted
-*.pubxml
-*.publishproj
-
-# Microsoft Azure Web App publish settings. Comment the next line if you want to
-# checkin your Azure Web App publish settings, but sensitive information contained
-# in these scripts will be unencrypted
-PublishScripts/
-
-# NuGet Packages
-*.nupkg
-# The packages folder can be ignored because of Package Restore
-**/[Pp]ackages/*
-# except build/, which is used as an MSBuild target.
-!**/[Pp]ackages/build/
-# Uncomment if necessary however generally it will be regenerated when needed
-#!**/[Pp]ackages/repositories.config
-# NuGet v3's project.json files produces more ignorable files
-*.nuget.props
-*.nuget.targets
-
-# Microsoft Azure Build Output
-csx/
-*.build.csdef
-
-# Microsoft Azure Emulator
-ecf/
-rcf/
-
-# Windows Store app package directories and files
-AppPackages/
-BundleArtifacts/
-Package.StoreAssociation.xml
-_pkginfo.txt
-*.appx
-
-# Visual Studio cache files
-# files ending in .cache can be ignored
-*.[Cc]ache
-# but keep track of directories ending in .cache
-!?*.[Cc]ache/
-
-# Others
-ClientBin/
-~$*
-*~
-*.dbmdl
-*.dbproj.schemaview
-*.jfm
-*.pfx
-*.publishsettings
-orleans.codegen.cs
-
-# Including strong name files can present a security risk
-# (https://github.com/github/gitignore/pull/2483#issue-259490424)
-#*.snk
-
-# Since there are multiple workflows, uncomment next line to ignore bower_components
-# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
-#bower_components/
-
-# RIA/Silverlight projects
-Generated_Code/
-
-# Backup & report files from converting an old project file
-# to a newer Visual Studio version. Backup files are not needed,
-# because we have git ;-)
-_UpgradeReport_Files/
-Backup*/
-UpgradeLog*.XML
-UpgradeLog*.htm
-ServiceFabricBackup/
-*.rptproj.bak
-
-# SQL Server files
-*.mdf
-*.ldf
-*.ndf
-
-# Business Intelligence projects
-*.rdl.data
-*.bim.layout
-*.bim_*.settings
-*.rptproj.rsuser
-*- Backup*.rdl
-
-# Microsoft Fakes
-FakesAssemblies/
-
-# GhostDoc plugin setting file
-*.GhostDoc.xml
-
-# Node.js Tools for Visual Studio
-.ntvs_analysis.dat
-node_modules/
-
-# Visual Studio 6 build log
-*.plg
-
-# Visual Studio 6 workspace options file
-*.opt
-
-# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
-*.vbw
-
-# Visual Studio LightSwitch build output
-**/*.HTMLClient/GeneratedArtifacts
-**/*.DesktopClient/GeneratedArtifacts
-**/*.DesktopClient/ModelManifest.xml
-**/*.Server/GeneratedArtifacts
-**/*.Server/ModelManifest.xml
-_Pvt_Extensions
-
-# Paket dependency manager
-.paket/paket.exe
-paket-files/
-
-# FAKE - F# Make
-.fake/
-
-# JetBrains Rider
-.idea/
-*.sln.iml
-
-# CodeRush personal settings
-.cr/personal
-
-# Python Tools for Visual Studio (PTVS)
-__pycache__/
-*.pyc
-
-# Cake - Uncomment if you are using it
-# tools/**
-# !tools/packages.config
-
-# Tabs Studio
-*.tss
-
-# Telerik's JustMock configuration file
-*.jmconfig
-
-# BizTalk build output
-*.btp.cs
-*.btm.cs
-*.odx.cs
-*.xsd.cs
-
-# OpenCover UI analysis results
-OpenCover/
-
-# Azure Stream Analytics local run output
-ASALocalRun/
-
-# MSBuild Binary and Structured Log
-*.binlog
-
-# NVidia Nsight GPU debugger configuration file
-*.nvuser
-
-# MFractors (Xamarin productivity tool) working folder
-.mfractor/
-
-# Local History for Visual Studio
-.localhistory/
-
-# BeatPulse healthcheck temp database
-healthchecksdb
-
-# Visual Studio Code local config folder
.vscode/
+bin/
+obj/
-# Remove launchSettings.json
-launchSettings.json
-
-# Folder info on Mac
-.DS_Store
+*.user
+LEConfig.xml
+launchSettings.json
\ No newline at end of file
diff --git a/ErogeHelper.AssistiveTouch/App.xaml b/ErogeHelper.AssistiveTouch/App.xaml
new file mode 100644
index 0000000..68e28ab
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/App.xaml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+ Segoe MDL2 Assets
+
+
+
diff --git a/ErogeHelper.AssistiveTouch/App.xaml.cs b/ErogeHelper.AssistiveTouch/App.xaml.cs
new file mode 100644
index 0000000..cbfe6d8
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/App.xaml.cs
@@ -0,0 +1,37 @@
+using System.Diagnostics;
+using System.IO.Pipes;
+using System.Windows;
+using ErogeHelper.AssistiveTouch.Helper;
+
+namespace ErogeHelper.AssistiveTouch
+{
+ ///
+ /// Interaction logic for App.xaml
+ ///
+ public partial class App : Application
+ {
+ public static Process GameProcess { get; private set; } = null!;
+
+ public static IntPtr GameWindowHandle { get; private set; } = IntPtr.Zero;
+
+ protected override void OnStartup(StartupEventArgs e)
+ {
+ if (e.Args.Length > 0)
+ {
+ var _pipeClient = new AnonymousPipeClientStream(PipeDirection.Out, e.Args[0]);
+ new IpcRenderer(_pipeClient);
+
+ GameProcess = Process.GetProcessById(int.Parse(e.Args[1]));
+
+ if (GameProcess is null)
+ {
+ Environment.Exit(-1);
+ return;
+ }
+
+ GameWindowHandle = HwndTools.FindMainWindowHandle(GameProcess);
+ //GameWindowHandle = GameProcess.MainWindowHandle;
+ }
+ }
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/ErogeHelper.AssistiveTouch.csproj b/ErogeHelper.AssistiveTouch/ErogeHelper.AssistiveTouch.csproj
new file mode 100644
index 0000000..dcc8832
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/ErogeHelper.AssistiveTouch.csproj
@@ -0,0 +1,31 @@
+
+
+
+ WinExe
+ net8.0-windows;net472
+ latest
+ enable
+ enable
+ true
+
+
+
+ ..\app.manifest
+ ..\bin
+ None
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ErogeHelper.AssistiveTouch/Helper/AnimationTool.cs b/ErogeHelper.AssistiveTouch/Helper/AnimationTool.cs
new file mode 100644
index 0000000..7968ac5
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/AnimationTool.cs
@@ -0,0 +1,73 @@
+using System.Windows;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+
+namespace ErogeHelper.AssistiveTouch.Helper;
+
+internal static class AnimationTool
+{
+ private static TimeSpan TransformDuration => TouchButton.MenuTransistDuration;
+
+ // Use with storyboard and no TransformGroup
+ public static readonly PropertyPath XProperty = new("(UIElement.RenderTransform).(TranslateTransform.X)");
+ public static readonly PropertyPath YProperty = new("(UIElement.RenderTransform).(TranslateTransform.Y)");
+
+ public static DoubleAnimation FadeOutAnimation => new()
+ {
+ From = 1.0,
+ To = 0.0,
+ Duration = TransformDuration,
+ FillBehavior = FillBehavior.Stop,
+ };
+ public static DoubleAnimation FadeInAnimation => new()
+ {
+ From = 0.0,
+ To = 1.0,
+ Duration = TransformDuration,
+ FillBehavior = FillBehavior.Stop,
+ };
+
+ // The status of menu item at animation's beginning
+ public static TranslateTransform ZeroTransform => new(0, 0);
+ public static TranslateTransform LeftOneTransform(double distance) => new(distance, 0);
+ public static TranslateTransform LeftTwoTransform(double distance) => new(distance * 2, 0);
+ public static TranslateTransform RightOneTransform(double distance) => new(-distance, 0);
+ public static TranslateTransform RightTwoTransform(double distance) => new(-distance * 2, 0);
+ public static TranslateTransform BottomOneTransform(double distance) => new(0, -distance);
+ public static TranslateTransform BottomTwoTransform(double distance) => new(0, -distance * 2);
+
+ public static TranslateTransform LeftOneBottomOneTransform(double distance) => new(distance, -distance);
+ public static TranslateTransform LeftOneBottomTwoTransform(double distance) => new(distance, -distance * 2);
+ public static TranslateTransform LeftOneTopOneTransform(double distance) => new(distance, distance);
+ public static TranslateTransform RightOneTopOneTransform(double distance) => new(-distance, distance);
+ public static TranslateTransform RightOneBottomOneTransform(double distance) => new(-distance, -distance);
+ public static TranslateTransform RightOneBottomTwoTransform(double distance) => new(-distance, -distance * 2);
+ public static TranslateTransform RightTwoTopOneTransform(double distance) => new(-distance * 2, distance);
+
+ ///
+ /// Used by menu items
+ ///
+ public static DoubleAnimation TransformMoveToZeroAnimation => new()
+ {
+ To = 0.0,
+ Duration = TransformDuration,
+ FillBehavior = FillBehavior.Stop,
+ };
+
+ ///
+ /// Binding animations in storyboard
+ ///
+ public static void BindingAnimation(
+ Storyboard storyboard,
+ AnimationTimeline animation,
+ DependencyObject target,
+ PropertyPath path,
+ bool freeze = false)
+ {
+ Storyboard.SetTarget(animation, target);
+ Storyboard.SetTargetProperty(animation, path);
+ if (freeze)
+ animation.Freeze();
+ storyboard.Children.Add(animation);
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/Fullscreen.cs b/ErogeHelper.AssistiveTouch/Helper/Fullscreen.cs
new file mode 100644
index 0000000..af1043d
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/Fullscreen.cs
@@ -0,0 +1,16 @@
+using ErogeHelper.Share;
+
+namespace ErogeHelper.AssistiveTouch.Helper
+{
+ internal class Fullscreen
+ {
+ // See: http://www.msghelp.net/showthread.php?tid=67047&pid=740345
+ public static bool IsWindowFullscreen(IntPtr hwnd)
+ {
+ User32.GetWindowRect(hwnd, out var rect);
+ return rect.left < 50 && rect.top < 50 &&
+ rect.Width >= User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN) &&
+ rect.Height >= User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
+ }
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/GameWindowHooker.cs b/ErogeHelper.AssistiveTouch/Helper/GameWindowHooker.cs
new file mode 100644
index 0000000..66e7c82
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/GameWindowHooker.cs
@@ -0,0 +1,90 @@
+using ErogeHelper.Share;
+using System.Runtime.InteropServices;
+
+namespace ErogeHelper.AssistiveTouch.Helper;
+
+internal class GameWindowHooker : IDisposable
+{
+ public event EventHandler? SizeChanged;
+
+ private readonly User32.HWINEVENTHOOK _windowsEventHook;
+
+ private readonly GCHandle _gcSafetyHandle;
+
+ public GameWindowHooker()
+ {
+ var targetThreadId = User32.GetWindowThreadProcessId(App.GameWindowHandle, out var pid);
+
+ User32.WinEventProc winEventDelegate = WinEventCallback;
+ _gcSafetyHandle = GCHandle.Alloc(winEventDelegate);
+
+ _windowsEventHook = User32.SetWinEventHook(
+ EventObjectLocationChange, EventObjectLocationChange,
+ IntPtr.Zero, winEventDelegate, pid, targetThreadId,
+ WinEventHookInternalFlags);
+
+ _throttle = new(300, rectClient =>
+ {
+ _rev = !_rev;
+ User32.SetWindowPos(MainWindow.Handle, IntPtr.Zero, 0, 0, rectClient.Width + (_rev ? 1 : -1), rectClient.Height, User32.SetWindowPosFlags.SWP_NOZORDER | User32.SetWindowPosFlags.SWP_NOMOVE);
+ });
+ }
+
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook
+ private const User32.WINEVENT WinEventHookInternalFlags = User32.WINEVENT.WINEVENT_INCONTEXT |
+ User32.WINEVENT.WINEVENT_SKIPOWNPROCESS; //TODO test
+ private const uint EventObjectLocationChange = 0x800B;
+ private const long SWEH_CHILDID_SELF = 0;
+ private const int OBJID_WINDOW = 0;
+
+ ///
+ /// Running in UI thread
+ ///
+ private void WinEventCallback(
+ User32.HWINEVENTHOOK hWinEventHook,
+ uint eventType,
+ HWND hWnd,
+ int idObject,
+ int idChild,
+ uint dwEventThread,
+ uint dwmsEventTime)
+ {
+ if (eventType == EventObjectLocationChange &&
+ hWnd == App.GameWindowHandle &&
+ idObject == OBJID_WINDOW && idChild == SWEH_CHILDID_SELF)
+ {
+ User32.GetClientRect(hWnd, out var rectClient);
+
+ if (rectClient.Size != _lastGameWindowSize)
+ {
+ User32.SetWindowPos(MainWindow.Handle, IntPtr.Zero, 0, 0, rectClient.Width, rectClient.Height, User32.SetWindowPosFlags.SWP_NOZORDER | User32.SetWindowPosFlags.SWP_NOMOVE);
+ _lastGameWindowSize = rectClient.Size;
+ SizeChanged?.Invoke(this, new());
+ }
+ else
+ {
+ // https://bugreports.qt.io/browse/QTBUG-64116?focusedCommentId=377425&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel
+ // HACK: Hack way to fix touch position
+ _throttle.Signal(rectClient);
+ }
+
+ var p = new POINT();
+ User32.MapWindowPoints(MainWindow.Handle, hWnd, ref p);
+ if (p.X != 0 || p.Y != 0)
+ {
+ User32.SetWindowPos(MainWindow.Handle, IntPtr.Zero, 0, 0, 0, 0, User32.SetWindowPosFlags.SWP_NOZORDER | User32.SetWindowPosFlags.SWP_NOSIZE);
+ }
+ }
+ }
+
+ private readonly Throttle _throttle;
+ private bool _rev;
+ private System.Drawing.Size _lastGameWindowSize;
+
+ public void Dispose()
+ {
+ _gcSafetyHandle.Free();
+ // May produce EventObjectDestroy
+ User32.UnhookWinEvent(_windowsEventHook);
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/HwndTools.cs b/ErogeHelper.AssistiveTouch/Helper/HwndTools.cs
new file mode 100644
index 0000000..d36bfa3
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/HwndTools.cs
@@ -0,0 +1,142 @@
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using ErogeHelper.Share;
+
+namespace ErogeHelper.AssistiveTouch.Helper;
+
+public static class HwndTools
+{
+ public static void HideWindowInAltTab(nint windowHandle)
+ {
+ if (windowHandle == IntPtr.Zero)
+ return;
+
+ const int wsExToolWindow = 0x00000080;
+
+ var exStyle = User32.GetWindowLong(windowHandle,
+ User32.WindowLongFlags.GWL_EXSTYLE);
+ exStyle |= wsExToolWindow;
+ _ = User32.SetWindowLong(windowHandle, User32.WindowLongFlags.GWL_EXSTYLE, exStyle);
+ }
+
+ public static void WindowLostFocus(nint windowHandle, bool loseFocus)
+ {
+ if (windowHandle == IntPtr.Zero)
+ return;
+
+ var exStyle = User32.GetWindowLong(windowHandle, User32.WindowLongFlags.GWL_EXSTYLE);
+ if (loseFocus)
+ {
+ User32.SetWindowLong(windowHandle,
+ User32.WindowLongFlags.GWL_EXSTYLE,
+ exStyle | (int)User32.WindowStylesEx.WS_EX_NOACTIVATE);
+ }
+ else
+ {
+ User32.SetWindowLong(windowHandle,
+ User32.WindowLongFlags.GWL_EXSTYLE,
+ exStyle & ~(int)User32.WindowStylesEx.WS_EX_NOACTIVATE);
+ }
+ }
+
+ public static void RemovePopupAddChildStyle(IntPtr handle)
+ {
+ var style = (uint)User32.GetWindowLong(handle, User32.WindowLongFlags.GWL_STYLE);
+ style = style & ~(uint)User32.WindowStyles.WS_POPUP | (uint)User32.WindowStyles.WS_CHILD;
+ User32.SetWindowLong(handle, User32.WindowLongFlags.GWL_STYLE, (int)style);
+ }
+
+ /// Bring game to front
+ public static IntPtr FindMainWindowHandle(Process proc, bool activeGame = true)
+ {
+ const int WaitGameStartTimeout = 20000;
+ const int UIMinimumResponseTime = 50;
+
+ proc.WaitForInputIdle(WaitGameStartTimeout);
+ proc.Refresh();
+ // Might be zero at first
+ var gameHwnd = proc.MainWindowHandle;
+
+ if (activeGame && User32.IsIconic(proc.MainWindowHandle))
+ {
+ User32.ShowWindow(proc.MainWindowHandle, ShowWindowCommand.SW_RESTORE);
+ }
+
+ User32.GetClientRect(gameHwnd, out var clientRect);
+
+ if (clientRect.bottom > GoodWindowHeight &&
+ clientRect.right > GoodWindowWidth)
+ {
+ return gameHwnd;
+ }
+ else
+ {
+ var spendTime = new Stopwatch();
+ spendTime.Start();
+ while (spendTime.Elapsed.TotalMilliseconds < WaitGameStartTimeout)
+ {
+ if (proc.HasExited)
+ return IntPtr.Zero;
+
+ // Process.MainGameHandle should included in handles
+ var handles = GetRootWindowsOfProcess(proc.Id);
+ foreach (var handle in handles)
+ {
+ User32.GetClientRect(handle, out clientRect);
+ if (clientRect.bottom > GoodWindowHeight &&
+ clientRect.right > GoodWindowWidth)
+ {
+ return handle.DangerousGetHandle();
+ }
+ }
+ Thread.Sleep(UIMinimumResponseTime);
+ }
+ throw new ArgumentException("Find window handle failed");
+ }
+ }
+
+ // private const int VNRWindowWidth = 160;
+ // private const int VNRWindowHeight = 120;
+ // private const int MinWindowSize = 12;
+ private const int GoodWindowWidth = 500;
+ private const int GoodWindowHeight = 320;
+
+ private static IEnumerable GetRootWindowsOfProcess(int pid)
+ {
+ var rootWindows = GetChildWindows(IntPtr.Zero);
+ var dsProcRootWindows = new List();
+ foreach (var hWnd in rootWindows)
+ {
+ _ = User32.GetWindowThreadProcessId(hWnd, out var lpdwProcessId);
+ if (lpdwProcessId == pid)
+ dsProcRootWindows.Add(hWnd);
+ }
+ return dsProcRootWindows;
+ }
+
+ private static IEnumerable GetChildWindows(HWND parent)
+ {
+ List result = new();
+ var listHandle = GCHandle.Alloc(result);
+ try
+ {
+ static bool ChildProc(HWND handle, IntPtr pointer)
+ {
+ var gch = GCHandle.FromIntPtr(pointer);
+ if (gch.Target is not List list)
+ {
+ throw new InvalidCastException("GCHandle Target could not be cast as List");
+ }
+ list.Add(handle);
+ return true;
+ }
+ User32.EnumChildWindows(parent, ChildProc, GCHandle.ToIntPtr(listHandle));
+ }
+ finally
+ {
+ if (listHandle.IsAllocated)
+ listHandle.Free();
+ }
+ return result;
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/Symbol.cs b/ErogeHelper.AssistiveTouch/Helper/Symbol.cs
new file mode 100644
index 0000000..85e62f7
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/Symbol.cs
@@ -0,0 +1,37 @@
+namespace ErogeHelper.AssistiveTouch.Helper
+{
+ ///
+ /// Defines constants that specify a glyph from the **Segoe MDL2 Assets** font to
+ /// use as the content of a SymbolIcon.
+ ///
+ public enum Symbol
+ {
+ Emoji = 0xE11D,
+ Tablet = 0xE70A,
+ Brightness = 0xE706,
+ Setting = 0xE713,
+ Attach = 0xE723,
+ Back = 0xE72B,
+ BackToWindow = 0xE73F,
+ FullScreen = 0xE740,
+ KeyboardClassic = 0xE765,
+ Volume = 0xE767,
+ Trim = 0xE78A,
+ TaskView = 0xE7C4,
+ TouchPointer = 0xE7C9,
+ Game = 0xE7FC,
+ Picture = 0xE8B9,
+ ChromeClose = 0xE8BB,
+ DockRight = 0xE90D,
+ Repair = 0xE90F,
+ Info = 0xE946,
+ Volume1 = 0xE993,
+ Cloud = 0xE753,
+ DictionaryCloud = 0xEBC3,
+ LowerBrightness = 0xEC8A,
+ Speech = 0xEFA9,
+
+ SettingSolid = 0xF8B0,
+ TBD = 0xFFFF
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/Throttle.cs b/ErogeHelper.AssistiveTouch/Helper/Throttle.cs
new file mode 100644
index 0000000..dc89c19
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/Throttle.cs
@@ -0,0 +1,101 @@
+// CODE FROM https://qiita.com/matarillo/items/9958b90ab04de5c2234e
+namespace ErogeHelper.AssistiveTouch.Helper
+{
+ public sealed class Throttle : IDisposable
+ {
+ private readonly AutoResetEvent producer = new AutoResetEvent(true);
+ private readonly AutoResetEvent consumer = new AutoResetEvent(false);
+ private Tuple? value;
+
+ private readonly int millisec;
+ private readonly Action action;
+ private volatile bool disposed = false;
+ private int producers = 0;
+
+ public Throttle(int millisec, Action action)
+ {
+ this.millisec = millisec;
+ this.action = action;
+ Task.Run(() => WaitAndFire());
+ }
+
+ private void WaitAndFire()
+ {
+ Thread.CurrentThread.Name = "Throttle.cs";
+ try
+ {
+ while (true)
+ {
+ producer.Set();
+ consumer.WaitOne();
+ if (disposed) return;
+
+ while (true)
+ {
+ producer.Set();
+ var timedOut = !consumer.WaitOne(millisec);
+ if (disposed) return;
+ if (timedOut) break;
+ }
+ // !!Enforce not null to avoid warning
+ Fire(action, value!.Item1, value.Item2);
+ value = null;
+ }
+ }
+ catch (ObjectDisposedException)
+ {
+ // ignore and exit
+ }
+ }
+
+ private static void Fire(Action action, T input, SynchronizationContext? context)
+ {
+ if (context != null)
+ {
+ // !!Enforce not null to avoid warning
+ context.Post(state => action((T)state!), input);
+ }
+ else
+ {
+ Task.Run(() => action(input));
+ }
+ }
+
+ public void Signal(T input)
+ {
+ if (disposed) return;
+ try
+ {
+ Interlocked.Increment(ref producers);
+ producer.WaitOne();
+ if (disposed) return;
+ // !!Enforce not null to avoid warning
+ value = Tuple.Create(input, SynchronizationContext.Current)!;
+ consumer.Set();
+ }
+ catch (ObjectDisposedException)
+ {
+ // ignore and exit
+ }
+ finally
+ {
+ Interlocked.Decrement(ref producers);
+ }
+ }
+
+ public void Dispose()
+ {
+ if (!disposed)
+ {
+ disposed = true;
+ while (Interlocked.CompareExchange(ref producers, 0, 0) > 0)
+ {
+ producer.Set();
+ }
+ consumer.Set();
+ producer.Dispose();
+ consumer.Dispose();
+ }
+ }
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/ToggleTouchMenuItem.xaml b/ErogeHelper.AssistiveTouch/Helper/ToggleTouchMenuItem.xaml
new file mode 100644
index 0000000..8a9ba9c
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/ToggleTouchMenuItem.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ErogeHelper.AssistiveTouch/Helper/ToggleTouchMenuItem.xaml.cs b/ErogeHelper.AssistiveTouch/Helper/ToggleTouchMenuItem.xaml.cs
new file mode 100644
index 0000000..46381ca
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/ToggleTouchMenuItem.xaml.cs
@@ -0,0 +1,116 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace ErogeHelper.AssistiveTouch.Helper
+{
+ public partial class ToggleTouchMenuItem : UserControl
+ {
+ #region Symbol DependencyProperty
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register(
+ nameof(Symbol),
+ typeof(Symbol),
+ typeof(ToggleTouchMenuItem),
+ new PropertyMetadata(Symbol.Emoji, (d, e) => ((ToggleTouchMenuItem)d).OnSymbolChanged(e)));
+
+ ///
+ /// Gets or sets the Segoe MDL2 Assets glyph used as the icon content.
+ ///
+ public Symbol Symbol
+ {
+ get => (Symbol)GetValue(SymbolProperty);
+ set => SetValue(SymbolProperty, value);
+ }
+
+ protected void OnSymbolChanged(DependencyPropertyChangedEventArgs e) =>
+ ItemIcon.SetCurrentValue(TextBlock.TextProperty, char.ConvertFromUtf32((int)e.NewValue));
+ #endregion
+
+ #region Text DependencyProperty
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
+ nameof(Text),
+ typeof(string),
+ typeof(ToggleTouchMenuItem),
+ new PropertyMetadata(string.Empty, (d, e) => ((ToggleTouchMenuItem)d).OnTextChanged(e)));
+
+ public string Text
+ {
+ get => (string)GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ protected void OnTextChanged(DependencyPropertyChangedEventArgs e) =>
+ ItemText.SetCurrentValue(TextBlock.TextProperty, (string)e.NewValue);
+ #endregion
+
+ #region IsOn DependencyProperty
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty IsOnProperty = DependencyProperty.Register(
+ nameof(IsOn),
+ typeof(bool),
+ typeof(ToggleTouchMenuItem),
+ new PropertyMetadata(false, (d, e) => ((ToggleTouchMenuItem)d).OnIsOnChanged(e)));
+
+ public bool IsOn
+ {
+ get => (bool)GetValue(IsOnProperty);
+ set => SetValue(IsOnProperty, value);
+ }
+
+ protected void OnIsOnChanged(DependencyPropertyChangedEventArgs e)
+ {
+ SetItemForegroundColor((bool)e.NewValue ? ItemPressedColor : Brushes.White);
+ Toggled?.Invoke(this, new());
+ }
+ #endregion
+
+ public event EventHandler? Toggled;
+
+ public ToggleTouchMenuItem()
+ {
+ InitializeComponent();
+ }
+
+ private static readonly Brush ItemPressedColor = new SolidColorBrush(Color.FromArgb(255, 111, 196, 241));
+
+ private bool _buttonPressed;
+
+ private void ItemOnPreviewMouseLeftButtonDown(object sender, InputEventArgs e)
+ {
+ if (!TouchMenuItem.ClickLocked)
+ {
+ _buttonPressed = true;
+ SetItemForegroundColor(IsOn ? Brushes.DeepSkyBlue : Brushes.Gray);
+ }
+ }
+
+ private void ItemOnPreviewMouseLeave(object sender, InputEventArgs e)
+ {
+ _buttonPressed = false;
+ SetItemForegroundColor(IsOn ? ItemPressedColor : Brushes.White);
+ }
+
+ private void ItemOnPreviewMouseLeftButtonUp(object sender, InputEventArgs e)
+ {
+ if (_buttonPressed)
+ {
+ SetCurrentValue(IsOnProperty, !IsOn);
+ _buttonPressed = false;
+ }
+ }
+
+ private void ItemOnTouchUp(object sender, TouchEventArgs e)
+ {
+ if (_buttonPressed)
+ {
+ SetCurrentValue(IsOnProperty, !IsOn);
+ _buttonPressed = false;
+ }
+ }
+
+ private void SetItemForegroundColor(Brush color) => ItemIcon.Foreground = ItemText.Foreground = color;
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/TouchConversionHooker.cs b/ErogeHelper.AssistiveTouch/Helper/TouchConversionHooker.cs
new file mode 100644
index 0000000..7ff6c33
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/TouchConversionHooker.cs
@@ -0,0 +1,76 @@
+using ErogeHelper.Share;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+
+namespace ErogeHelper.AssistiveTouch.Helper
+{
+ public static class TouchConversionHooker
+ {
+ // User32.MOUSEEVENTF.MOUSEEVENTF_FROMTOUCH
+ private const uint MOUSEEVENTF_FROMTOUCH = 0xFF515700;
+
+ private static User32.SafeHHOOK? _hookId;
+ public static void Install()
+ {
+ var moduleHandle = Kernel32.GetModuleHandle();
+
+ _hookId = User32.SetWindowsHookEx(User32.HookType.WH_MOUSE_LL, Hook, moduleHandle, 0);
+ if (_hookId == IntPtr.Zero)
+ throw new Win32Exception(Marshal.GetLastWin32Error());
+ }
+
+ public static void UnInstall() => _hookId?.Close();
+
+ private static IntPtr Hook(int nCode, IntPtr wParam, IntPtr lParam)
+ {
+ if (nCode < 0)
+ return User32.CallNextHookEx(_hookId, nCode, wParam, lParam);
+
+ var obj = Marshal.PtrToStructure(lParam, typeof(User32.MSLLHOOKSTRUCT));
+ if (obj is not User32.MSLLHOOKSTRUCT info)
+ return User32.CallNextHookEx(_hookId, nCode, wParam, lParam);
+
+ var extraInfo = (uint)info.dwExtraInfo;
+ if ((extraInfo & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH)
+ {
+ var isGameWindow = User32.GetForegroundWindow() == App.GameWindowHandle;
+ if (!isGameWindow)
+ return User32.CallNextHookEx(_hookId, nCode, wParam, lParam);
+
+ switch ((int)wParam)
+ {
+ case 0x202:
+ Task.Run(() =>
+ {
+ var (x, y) = GetCursorPosition();
+ User32.mouse_event(User32.MOUSEEVENTF.MOUSEEVENTF_LEFTDOWN, x, y, 0, IntPtr.Zero);
+ Thread.Sleep(UserTimerMinimum);
+ User32.mouse_event(User32.MOUSEEVENTF.MOUSEEVENTF_LEFTUP, x, y, 0, IntPtr.Zero);
+ });
+ break;
+ case 0x205:
+ Task.Run(() =>
+ {
+ var (x, y) = GetCursorPosition();
+ User32.mouse_event(User32.MOUSEEVENTF.MOUSEEVENTF_RIGHTDOWN, x, y, 0, IntPtr.Zero);
+ Thread.Sleep(UserTimerMinimum);
+ User32.mouse_event(User32.MOUSEEVENTF.MOUSEEVENTF_RIGHTUP, x, y, 0, IntPtr.Zero);
+ });
+ break;
+ }
+ }
+
+ return User32.CallNextHookEx(_hookId, nCode, wParam, lParam);
+ }
+
+ private const int UserTimerMinimum = 0x0000000A;
+
+ private static (int X, int Y) GetCursorPosition()
+ {
+ var gotPoint = User32.GetCursorPos(out var currentMousePoint);
+ if (!gotPoint)
+ currentMousePoint = new POINT(0, 0);
+ return (currentMousePoint.X, currentMousePoint.Y);
+ }
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/TouchMenuItem.xaml b/ErogeHelper.AssistiveTouch/Helper/TouchMenuItem.xaml
new file mode 100644
index 0000000..16909a7
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/TouchMenuItem.xaml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ErogeHelper.AssistiveTouch/Helper/TouchMenuItem.xaml.cs b/ErogeHelper.AssistiveTouch/Helper/TouchMenuItem.xaml.cs
new file mode 100644
index 0000000..eea1f1a
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/TouchMenuItem.xaml.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace ErogeHelper.AssistiveTouch.Helper
+{
+ public partial class TouchMenuItem : UserControl
+ {
+ #region Symbol DependencyProperty
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty SymbolProperty = DependencyProperty.Register(
+ nameof(Symbol),
+ typeof(Symbol),
+ typeof(TouchMenuItem),
+ new PropertyMetadata(Symbol.Emoji, (d, e) => ((TouchMenuItem)d).OnSymbolChanged(e)));
+
+ ///
+ /// Gets or sets the Segoe MDL2 Assets glyph used as the icon content.
+ ///
+ public Symbol Symbol
+ {
+ get => (Symbol)GetValue(SymbolProperty);
+ set => SetValue(SymbolProperty, value);
+ }
+
+ protected void OnSymbolChanged(DependencyPropertyChangedEventArgs e) =>
+ ItemIcon.SetCurrentValue(TextBlock.TextProperty, char.ConvertFromUtf32((int)e.NewValue));
+ #endregion
+
+ #region Text DependencyProperty
+ /// Identifies the dependency property.
+ public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
+ nameof(Text),
+ typeof(string),
+ typeof(TouchMenuItem),
+ new PropertyMetadata(string.Empty, (d, e) => ((TouchMenuItem)d).OnTextChanged(e)));
+
+ public string Text
+ {
+ get => (string)GetValue(TextProperty);
+ set => SetValue(TextProperty, value);
+ }
+
+ protected void OnTextChanged(DependencyPropertyChangedEventArgs e) =>
+ ItemText.SetCurrentValue(TextBlock.TextProperty, (string)e.NewValue);
+ #endregion
+
+ public event EventHandler? Click;
+
+ public static bool ClickLocked { get; set; }
+
+ public TouchMenuItem()
+ {
+ InitializeComponent();
+ }
+
+ private static readonly Brush ItemPressedColor = new SolidColorBrush(Color.FromArgb(255, 111, 196, 241));
+
+ private void ItemOnPreviewMouseLeftButtonDown(object sender, InputEventArgs e)
+ {
+ if (!ClickLocked) SetItemForegroundColor(ItemPressedColor);
+ }
+
+ private void ItemOnPreviewMouseLeave(object sender, InputEventArgs e) =>
+ SetItemForegroundColor(Brushes.White);
+
+ private void ItemOnPreviewMouseLeftButtonUp(object sender, InputEventArgs e)
+ {
+ if (ItemIcon.Foreground != Brushes.White && !ClickLocked)
+ {
+ SetItemForegroundColor(Brushes.White);
+
+ Click?.Invoke(this, e);
+ }
+ }
+
+ private void ItemOnTouchUp(object sender, TouchEventArgs e)
+ {
+ if (!ClickLocked) Click?.Invoke(this, e);
+ }
+
+ private void SetItemForegroundColor(Brush color) => ItemIcon.Foreground = ItemText.Foreground = color;
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Helper/XamlResource.cs b/ErogeHelper.AssistiveTouch/Helper/XamlResource.cs
new file mode 100644
index 0000000..e7a9214
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Helper/XamlResource.cs
@@ -0,0 +1,40 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+
+namespace ErogeHelper.AssistiveTouch.Helper;
+
+internal class XamlResource
+{
+ public static double AssistiveTouchSize
+ {
+ get => (double)Application.Current.Resources["AssistiveTouchSize"];
+ set => Application.Current.Resources["AssistiveTouchSize"] = value;
+ }
+
+ public static ControlTemplate AssistiveTouchTemplate =>
+ (ControlTemplate)Application.Current.Resources["AssistiveTouchTemplate"];
+
+ public static SolidColorBrush AssistiveTouchBackground =>
+ (SolidColorBrush)Application.Current.Resources["AssistiveTouchBackground"];
+ public static Thickness AssistiveTouchMenuPadding =>
+ (Thickness)Application.Current.Resources["AssistiveTouchMenuPadding"];
+
+ public static void SetAssistiveTouchSize(double value) =>
+ Application.Current.Resources["AssistiveTouchSize"] = value;
+ public static void SetAssistiveTouchCornerRadius(CornerRadius value) =>
+ Application.Current.Resources["AssistiveTouchCornerRadius"] = value;
+ public static void SetAssistiveTouchCircleLinear(Thickness value) =>
+ Application.Current.Resources["AssistiveTouchCircleLinear"] = value;
+ public static void SetAssistiveTouchLayerOneMargin(Thickness value) =>
+ Application.Current.Resources["AssistiveTouchLayerOneMargin"] = value;
+ public static void SetAssistiveTouchLayerTwoMargin(Thickness value) =>
+ Application.Current.Resources["AssistiveTouchLayerTwoMargin"] = value;
+ public static void SetAssistiveTouchLayerThreeMargin(Thickness value) =>
+ Application.Current.Resources["AssistiveTouchLayerThreeMargin"] = value;
+
+ public static void SetAssistiveTouchItemSize(double value) =>
+ Application.Current.Resources["AssistiveTouchItemSize"] = value;
+ public static void SetAssistiveTouchItemBackground(SolidColorBrush value) =>
+ Application.Current.Resources["AssistiveTouchItemBackground"] = value;
+}
diff --git a/ErogeHelper.AssistiveTouch/IpcRenderer.cs b/ErogeHelper.AssistiveTouch/IpcRenderer.cs
new file mode 100644
index 0000000..80b8b0a
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/IpcRenderer.cs
@@ -0,0 +1,23 @@
+using ErogeHelper.Share;
+using System.IO;
+using System.IO.Pipes;
+
+namespace ErogeHelper.AssistiveTouch
+{
+ internal class IpcRenderer
+ {
+ private static AnonymousPipeClientStream PipeClient = null!;
+
+ public IpcRenderer(AnonymousPipeClientStream pipeClient)
+ {
+ PipeClient = pipeClient;
+ }
+
+ public static void Send(IpcTypes channel)
+ {
+ using var sw = new StreamWriter(PipeClient);
+ sw.AutoFlush = true;
+ sw.WriteLine(channel);
+ }
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/MainWindow.xaml b/ErogeHelper.AssistiveTouch/MainWindow.xaml
new file mode 100644
index 0000000..4b3633b
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/MainWindow.xaml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/ErogeHelper.AssistiveTouch/MainWindow.xaml.cs b/ErogeHelper.AssistiveTouch/MainWindow.xaml.cs
new file mode 100644
index 0000000..0a1b6a8
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/MainWindow.xaml.cs
@@ -0,0 +1,64 @@
+using ErogeHelper.AssistiveTouch.Helper;
+using ErogeHelper.Share;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Interop;
+using System.Windows.Threading;
+
+namespace ErogeHelper.AssistiveTouch
+{
+ ///
+ /// Interaction logic for MainWindow.xaml
+ ///
+ public partial class MainWindow : Window
+ {
+ public static IntPtr Handle { get; private set; }
+
+ public MainWindow()
+ {
+ InitializeComponent();
+ Handle = new WindowInteropHelper(this).EnsureHandle();
+
+ ContentRendered += (_, _) => IpcRenderer.Send(IpcTypes.Loaded);
+
+ HwndTools.RemovePopupAddChildStyle(Handle);
+ User32.SetParent(Handle, App.GameWindowHandle);
+ User32.GetClientRect(App.GameWindowHandle, out var rectClient);
+ User32.SetWindowPos(Handle, IntPtr.Zero, 0, 0, rectClient.Width, rectClient.Height, User32.SetWindowPosFlags.SWP_NOZORDER);
+
+ var hooker = new GameWindowHooker();
+ }
+
+
+ // Use EnableTouchPointer instead but wait until DisableTouchFeedback get fixed
+ #region Disable Touch White Point
+
+ protected override void OnPreviewTouchDown(TouchEventArgs e)
+ {
+ base.OnPreviewTouchDown(e);
+ Cursor = Cursors.None;
+ }
+
+ protected override void OnPreviewTouchMove(TouchEventArgs e)
+ {
+ base.OnPreviewTouchMove(e);
+ Cursor = Cursors.None;
+ }
+
+ protected override void OnGotMouseCapture(MouseEventArgs e)
+ {
+ base.OnGotMouseCapture(e);
+ Cursor = Cursors.Arrow;
+ }
+
+ protected override void OnPreviewMouseMove(MouseEventArgs e)
+ {
+ base.OnPreviewMouseMove(e);
+
+ if (e.StylusDevice == null)
+ Cursor = Cursors.Arrow;
+ }
+
+ #endregion
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Menu/DevicePage.xaml b/ErogeHelper.AssistiveTouch/Menu/DevicePage.xaml
new file mode 100644
index 0000000..0f6595c
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Menu/DevicePage.xaml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ErogeHelper.AssistiveTouch/Menu/DevicePage.xaml.cs b/ErogeHelper.AssistiveTouch/Menu/DevicePage.xaml.cs
new file mode 100644
index 0000000..d39488d
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Menu/DevicePage.xaml.cs
@@ -0,0 +1,142 @@
+using ErogeHelper.AssistiveTouch.Helper;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using WindowsInput.Events;
+using static System.Runtime.CompilerServices.RuntimeHelpers;
+
+namespace ErogeHelper.AssistiveTouch.Menu
+{
+ ///
+ public partial class DevicePage : Page, ITouchMenuPage
+ {
+ public event EventHandler? PageChanged;
+
+ public DevicePage()
+ {
+ InitializeComponent();
+ InitializeAnimation();
+ }
+
+ public void Show(double moveDistance)
+ {
+ SetCurrentValue(VisibilityProperty, Visibility.Visible);
+
+ XamlResource.SetAssistiveTouchItemBackground(Brushes.Transparent);
+
+ var volumeDownTransform = AnimationTool.LeftOneTransform(moveDistance);
+ var screenshotTransform = AnimationTool.RightOneTransform(moveDistance);
+ var backTransform = AnimationTool.BottomOneTransform(moveDistance);
+ var taskviewTransform = AnimationTool.LeftOneBottomOneTransform(moveDistance);
+ var dockrightTransform = AnimationTool.RightOneBottomOneTransform(moveDistance);
+ VolumeDown.SetCurrentValue(RenderTransformProperty, volumeDownTransform);
+ ScreenShot.SetCurrentValue(RenderTransformProperty, screenshotTransform);
+ Back.SetCurrentValue(RenderTransformProperty, backTransform);
+ TaskView.SetCurrentValue(RenderTransformProperty, taskviewTransform);
+ DockRight.SetCurrentValue(RenderTransformProperty, dockrightTransform);
+
+ _volumeDownMoveAnimation.SetCurrentValue(DoubleAnimation.FromProperty, volumeDownTransform.X);
+ _screenshotMoveXAnimation.SetCurrentValue(DoubleAnimation.FromProperty, screenshotTransform.X);
+ _backMoveAnimation.SetCurrentValue(DoubleAnimation.FromProperty, backTransform.Y);
+ _taskviewMoveXAnimation.SetCurrentValue(DoubleAnimation.FromProperty, taskviewTransform.X);
+ _taskviewMoveYAnimation.SetCurrentValue(DoubleAnimation.FromProperty, taskviewTransform.Y);
+ _dockrightMoveXAnimation.SetCurrentValue(DoubleAnimation.FromProperty, dockrightTransform.X);
+ _dockrightMoveYAnimation.SetCurrentValue(DoubleAnimation.FromProperty, dockrightTransform.Y);
+
+ _transitionInStoryboard.Begin();
+ }
+
+ public void Close()
+ {
+ XamlResource.SetAssistiveTouchItemBackground(Brushes.Transparent);
+ _transitionInStoryboard.SetCurrentValue(Timeline.AutoReverseProperty, true);
+ _transitionInStoryboard.Begin();
+ _transitionInStoryboard.Seek(TouchButton.MenuTransistDuration);
+ }
+
+ private void BackOnClickEvent(object sender, EventArgs e) => PageChanged?.Invoke(this, new(TouchMenuPageTag.DeviceBack));
+
+ private readonly Storyboard _transitionInStoryboard = new();
+ private readonly DoubleAnimation _volumeDownMoveAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _screenshotMoveXAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _backMoveAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _taskviewMoveXAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _taskviewMoveYAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _dockrightMoveXAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _dockrightMoveYAnimation = AnimationTool.TransformMoveToZeroAnimation;
+
+ private void InitializeAnimation()
+ {
+ AnimationTool.BindingAnimation(_transitionInStoryboard, AnimationTool.FadeInAnimation, this, new(OpacityProperty), true);
+
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _volumeDownMoveAnimation, VolumeDown, AnimationTool.XProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _backMoveAnimation, Back, AnimationTool.YProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _taskviewMoveXAnimation, TaskView, AnimationTool.XProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _taskviewMoveYAnimation, TaskView, AnimationTool.YProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _dockrightMoveXAnimation, DockRight, AnimationTool.XProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _dockrightMoveYAnimation, DockRight, AnimationTool.YProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _screenshotMoveXAnimation, ScreenShot, AnimationTool.XProperty);
+
+ _transitionInStoryboard.Completed += (_, _) =>
+ {
+ XamlResource.SetAssistiveTouchItemBackground(XamlResource.AssistiveTouchBackground);
+
+ if (!_transitionInStoryboard.AutoReverse)
+ {
+ VolumeDown.SetCurrentValue(RenderTransformProperty, AnimationTool.ZeroTransform);
+ ScreenShot.SetCurrentValue(RenderTransformProperty, AnimationTool.ZeroTransform);
+ TaskView.SetCurrentValue(RenderTransformProperty, AnimationTool.ZeroTransform);
+ Back.SetCurrentValue(RenderTransformProperty, AnimationTool.ZeroTransform);
+ DockRight.SetCurrentValue(RenderTransformProperty, AnimationTool.ZeroTransform);
+ }
+ else
+ {
+ _transitionInStoryboard.SetCurrentValue(Timeline.AutoReverseProperty, false);
+ SetCurrentValue(VisibilityProperty, Visibility.Collapsed);
+ TouchMenuItem.ClickLocked = false;
+ }
+ };
+ }
+ private async void VolumeDownOnClickEvent(object sender, EventArgs e) =>
+ await WindowsInput.Simulate.Events()
+ .Click(KeyCode.VolumeDown)
+ .Invoke().ConfigureAwait(false);
+
+ private async void VolumeUpOnClickEvent(object sender, EventArgs e) =>
+ await WindowsInput.Simulate.Events()
+ .Click(KeyCode.VolumeUp)
+ .Invoke().ConfigureAwait(false);
+ private async void ActionCenterOnClickEvent(object sender, EventArgs e) =>
+ await WindowsInput.Simulate.Events()
+ .ClickChord(KeyCode.LWin, KeyCode.A)
+ .Invoke().ConfigureAwait(false);
+ private async void TaskViewOnClickEvent(object sender, EventArgs e) =>
+ await WindowsInput.Simulate.Events()
+ .ClickChord(KeyCode.LWin, KeyCode.Tab)
+ .Invoke().ConfigureAwait(false);
+
+ private async void ScreenShotOnClickEvent(object sender, EventArgs e)
+ {
+ ((MainWindow)Application.Current.MainWindow).Menu.ManualClose();
+ await Task.Delay(500);
+ await WindowsInput.Simulate.Events()
+ .Click(KeyCode.PrintScreen)
+ .Invoke().ConfigureAwait(false);
+ //await WindowsInput.Simulate.Events()
+ // .ClickChord(KeyCode.LWin, KeyCode.Shift, KeyCode.S)
+ // .Invoke().ConfigureAwait(false);
+ }
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Menu/GamePage.xaml b/ErogeHelper.AssistiveTouch/Menu/GamePage.xaml
new file mode 100644
index 0000000..0688072
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Menu/GamePage.xaml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ErogeHelper.AssistiveTouch/Menu/GamePage.xaml.cs b/ErogeHelper.AssistiveTouch/Menu/GamePage.xaml.cs
new file mode 100644
index 0000000..fc480c5
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Menu/GamePage.xaml.cs
@@ -0,0 +1,120 @@
+using ErogeHelper.AssistiveTouch.Helper;
+using ErogeHelper.Share;
+using ErogeHelper.Share.Languages;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using WindowsInput.Events;
+
+namespace ErogeHelper.AssistiveTouch.Menu
+{
+ public partial class GamePage : Page, ITouchMenuPage
+ {
+ public event EventHandler? PageChanged;
+
+ public GamePage()
+ {
+ InitializeComponent();
+ InitializeAnimation();
+
+ var inFullscreen = Fullscreen.IsWindowFullscreen(App.GameWindowHandle);
+ (FullScreenSwitcher.Symbol, FullScreenSwitcher.Text) = inFullscreen ?
+ (Symbol.BackToWindow, Strings.AssistiveTouch_Window) :
+ (Symbol.FullScreen, Strings.AssistiveTouch_FullScreen);
+
+ TouchToMouse.Toggled += (_, _) =>
+ {
+ if (TouchToMouse.IsOn) TouchConversionHooker.Install();
+ else TouchConversionHooker.UnInstall();
+ };
+ }
+
+ public void Show(double moveDistance)
+ {
+ SetCurrentValue(VisibilityProperty, Visibility.Visible);
+
+ XamlResource.SetAssistiveTouchItemBackground(Brushes.Transparent);
+
+ var fullscreenTransform = AnimationTool.RightOneTopOneTransform(moveDistance);
+ var backTransform = AnimationTool.RightOneTransform(moveDistance);
+ var closeGameTransform = AnimationTool.RightTwoTransform(moveDistance);
+
+ FullScreenSwitcher.SetCurrentValue(RenderTransformProperty, fullscreenTransform);
+ Back.SetCurrentValue(RenderTransformProperty, backTransform);
+ CloseGame.SetCurrentValue(RenderTransformProperty, closeGameTransform);
+
+ _fullscreenMoveXAnimation.SetCurrentValue(DoubleAnimation.FromProperty, fullscreenTransform.X);
+ _fullscreenMoveYAnimation.SetCurrentValue(DoubleAnimation.FromProperty, fullscreenTransform.Y);
+ _backMoveAnimation.SetCurrentValue(DoubleAnimation.FromProperty, backTransform.X);
+ _closeGameMoveAnimation.SetCurrentValue(DoubleAnimation.FromProperty, closeGameTransform.X);
+
+ _transitionInStoryboard.Begin();
+ }
+
+ public void Close()
+ {
+ XamlResource.SetAssistiveTouchItemBackground(Brushes.Transparent);
+ _transitionInStoryboard.SetCurrentValue(Timeline.AutoReverseProperty, true);
+ _transitionInStoryboard.Begin();
+ _transitionInStoryboard.Seek(TouchButton.MenuTransistDuration);
+ }
+
+ private readonly Storyboard _transitionInStoryboard = new();
+ private readonly DoubleAnimation _fullscreenMoveXAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _fullscreenMoveYAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _backMoveAnimation = AnimationTool.TransformMoveToZeroAnimation;
+ private readonly DoubleAnimation _closeGameMoveAnimation = AnimationTool.TransformMoveToZeroAnimation;
+
+ private void InitializeAnimation()
+ {
+ AnimationTool.BindingAnimation(_transitionInStoryboard, AnimationTool.FadeInAnimation, this, new(OpacityProperty), true);
+
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _fullscreenMoveXAnimation, FullScreenSwitcher, AnimationTool.XProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _fullscreenMoveYAnimation, FullScreenSwitcher, AnimationTool.YProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _backMoveAnimation, Back, AnimationTool.XProperty);
+ AnimationTool.BindingAnimation(_transitionInStoryboard, _closeGameMoveAnimation, CloseGame, AnimationTool.XProperty);
+
+ _transitionInStoryboard.Completed += (_, _) =>
+ {
+ XamlResource.SetAssistiveTouchItemBackground(XamlResource.AssistiveTouchBackground);
+
+ if (!_transitionInStoryboard.AutoReverse)
+ {
+ FullScreenSwitcher.SetCurrentValue(RenderTransformProperty, AnimationTool.ZeroTransform);
+ Back.SetCurrentValue(RenderTransformProperty, AnimationTool.ZeroTransform);
+ CloseGame.SetCurrentValue(RenderTransformProperty, AnimationTool.ZeroTransform);
+ }
+ else
+ {
+ _transitionInStoryboard.SetCurrentValue(Timeline.AutoReverseProperty, false);
+ SetCurrentValue(VisibilityProperty, Visibility.Collapsed);
+ TouchMenuItem.ClickLocked = false;
+ }
+ };
+ }
+
+ private const int UIMinimumResponseTime = 50;
+ private async void FullScreenSwitcherOnClickEvent(object sender, EventArgs e)
+ {
+ await WindowsInput.Simulate.Events()
+ .Hold(KeyCode.Alt)
+ .Hold(KeyCode.Enter)
+ .Wait(UIMinimumResponseTime)
+ .Release(KeyCode.Enter)
+ .Release(KeyCode.Alt)
+ .Invoke().ConfigureAwait(false);
+ }
+
+ private void BackOnClick(object sender, EventArgs e) => PageChanged?.Invoke(this, new(TouchMenuPageTag.GameBack));
+
+ private void CloseGameOnClick(object sender, EventArgs e)
+ {
+ User32.PostMessage(
+ App.GameProcess.MainWindowHandle,
+ (uint)User32.WindowMessage.WM_SYSCOMMAND,
+ (IntPtr)User32.SysCommand.SC_CLOSE);
+ ((MainWindow)Application.Current.MainWindow).Menu.ManualClose();
+ }
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Menu/ITouchMenuPage.cs b/ErogeHelper.AssistiveTouch/Menu/ITouchMenuPage.cs
new file mode 100644
index 0000000..768729d
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Menu/ITouchMenuPage.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace ErogeHelper.AssistiveTouch.Menu
+{
+ interface ITouchMenuPage
+ {
+ event EventHandler? PageChanged;
+
+ void Show(double distance);
+
+ void Close();
+
+ Visibility Visibility { set; }
+ }
+
+ public static class PreferencePageTag
+ {
+ public const string General = "general";
+ public const string About = "about";
+ public const string MeCab = "mecab";
+ public const string Danmaku = "danmaku";
+ public const string Trans = "trans";
+ public const string TTS = "tts";
+ }
+
+ public enum TouchMenuPageTag
+ {
+ None,
+ Game,
+ GameBack,
+ Device,
+ DeviceBack,
+ Function,
+ FunctionBack
+ }
+
+ public class PageEventArgs : EventArgs
+ {
+ public TouchMenuPageTag Tag { get; }
+
+ public PageEventArgs(TouchMenuPageTag tag)
+ {
+ Tag = tag;
+ }
+ }
+}
+
diff --git a/ErogeHelper.AssistiveTouch/Menu/MainPage.xaml b/ErogeHelper.AssistiveTouch/Menu/MainPage.xaml
new file mode 100644
index 0000000..a12f516
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Menu/MainPage.xaml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ErogeHelper.AssistiveTouch/Menu/MainPage.xaml.cs b/ErogeHelper.AssistiveTouch/Menu/MainPage.xaml.cs
new file mode 100644
index 0000000..ebb1cee
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Menu/MainPage.xaml.cs
@@ -0,0 +1,42 @@
+using ErogeHelper.AssistiveTouch.Helper;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Animation;
+
+namespace ErogeHelper.AssistiveTouch.Menu
+{
+ public partial class MainPage : Page, ITouchMenuPage
+ {
+ private readonly DoubleAnimation _fadeOutAnimation = AnimationTool.FadeOutAnimation;
+ private readonly DoubleAnimation _fadeInAnimation = AnimationTool.FadeInAnimation;
+
+ public event EventHandler? PageChanged;
+
+ public MainPage()
+ {
+ InitializeComponent();
+ _fadeOutAnimation.Completed += (_, _) =>
+ {
+ Visibility = Visibility.Hidden;
+ TouchMenuItem.ClickLocked = false;
+ };
+ _fadeOutAnimation.Freeze();
+ _fadeInAnimation.Freeze();
+ }
+
+ public void Close() => BeginAnimation(OpacityProperty, _fadeOutAnimation);
+
+ public void Show(double _)
+ {
+ Visibility = Visibility.Visible;
+ BeginAnimation(OpacityProperty, _fadeInAnimation);
+ }
+
+ private void GameOnClick(object sender, EventArgs e) => PageChanged?.Invoke(this, new(TouchMenuPageTag.Game));
+
+ private void DeviceOnClick(object sender, EventArgs e) => PageChanged?.Invoke(this, new(TouchMenuPageTag.Device));
+
+ private void FunctionOnClick(object sender, EventArgs e) => PageChanged?.Invoke(this, new(TouchMenuPageTag.Function));
+
+ }
+}
diff --git a/ErogeHelper.AssistiveTouch/Properties/AssistiveTouchResource.xaml b/ErogeHelper.AssistiveTouch/Properties/AssistiveTouchResource.xaml
new file mode 100644
index 0000000..201b6a0
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/Properties/AssistiveTouchResource.xaml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 12
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ErogeHelper.AssistiveTouch/TouchButton.xaml b/ErogeHelper.AssistiveTouch/TouchButton.xaml
new file mode 100644
index 0000000..f602bcf
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/TouchButton.xaml
@@ -0,0 +1,16 @@
+
\ No newline at end of file
diff --git a/ErogeHelper.AssistiveTouch/TouchButton.xaml.cs b/ErogeHelper.AssistiveTouch/TouchButton.xaml.cs
new file mode 100644
index 0000000..2604040
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/TouchButton.xaml.cs
@@ -0,0 +1,349 @@
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using ErogeHelper.AssistiveTouch.Helper;
+
+namespace ErogeHelper.AssistiveTouch;
+
+public partial class TouchButton
+{
+ public static TimeSpan MenuTransistDuration { get; } = TimeSpan.FromMilliseconds(200);
+
+ public bool IsTouchMenuOpend { get; set; }
+ public event EventHandler? TouchMenuClosed;
+ public void RaiseMenuClosedEvent(object sender) => TouchMenuClosed?.Invoke(sender, new());
+ public event EventHandler? Clicked;
+
+ // iPhone iPad 200ms, but 300ms more suit for eh
+ private static readonly TimeSpan TouchReleaseToEdgeDuration = TimeSpan.FromMilliseconds(300);
+ private static readonly TimeSpan OpacityFadeInDuration = TimeSpan.FromMilliseconds(100);
+ private static readonly TimeSpan OpacityTransformDuration = TimeSpan.FromMilliseconds(400);
+ private const int OpacityChangeDuration = 4000;
+
+ private const double OpacityHalf = 0.4;
+ private const double OpacityFull = 1;
+ private const double ButtonSpace = 2;
+
+ // The diameter of button use for mouse releasing
+ public const double TouchSize = 75; // 60, 100
+ private double _distance;
+ private double _halfDistance;
+ private double _oneThirdDistance;
+ private double _twoThirdDistance;
+
+ public TouchButton()
+ {
+ InitializeComponent();
+
+ SetAssistiveTouchProperties();
+
+ var mainWindow = Application.Current.MainWindow;
+ mainWindow.Loaded += (_, _) => SetTouchPosition();
+ mainWindow.SizeChanged += (_, _) => SetTouchPosition();
+
+
+ #region Core Logic of Moving
+
+ var lastPosRealTime = new Point(TouchPosTransform.X, TouchPosTransform.Y);
+ bool isDraging = false;
+ bool isMoving = false;
+ Point relativeMousePos = default;
+ Point pointWhenMouseDown = default;
+ Point pointWhenMouseUp = default;
+
+ PreviewMouseLeftButtonDown += (_, evt) =>
+ {
+ if (isMoving)
+ return;
+
+ isDraging = true;
+ relativeMousePos = evt.GetPosition(this);
+ pointWhenMouseDown = evt.GetPosition(mainWindow);
+ };
+
+ PreviewMouseMove += (_, evt) =>
+ {
+ if (!isDraging)
+ return;
+
+ isMoving = true;
+ // Max mouse event message frequency: 125 fps, dirty react 250 (2*fps)
+ var newPos = (Point)(evt.GetPosition(mainWindow) - relativeMousePos);
+
+ TouchPosTransform.SetCurrentValue(TranslateTransform.XProperty, newPos.X);
+ TouchPosTransform.SetCurrentValue(TranslateTransform.YProperty, newPos.Y);
+
+ lastPosRealTime = newPos;
+
+ // if mouse go out of the edge
+ if (newPos.X < -_oneThirdDistance || newPos.Y < -_oneThirdDistance ||
+ newPos.X > mainWindow.ActualWidth - _twoThirdDistance ||
+ newPos.Y > mainWindow.ActualHeight - _twoThirdDistance)
+ {
+ RaiseMouseReleasedEventInCode(this);
+ }
+ };
+
+ PreviewMouseUp += (_, evt) =>
+ {
+ isDraging = false;
+ pointWhenMouseUp = evt.GetPosition(mainWindow);
+
+ if (isMoving && pointWhenMouseUp != pointWhenMouseDown)
+ {
+ WhenMouseReleased(mainWindow, TouchPosTransform.X, TouchPosTransform.Y);
+ }
+ };
+
+ PreviewMouseUp += (_, _) => { if (pointWhenMouseUp == pointWhenMouseDown) isMoving = false; };
+ TranslateTouchStoryboard.Completed += (_, _) => isMoving = false;
+ TouchMenuClosed += (_, _) => isDraging = isMoving = false;
+
+ #endregion Core Logic of Moving
+
+ #region Opacity Adjust
+ // Fade in
+ PreviewMouseLeftButtonDown += (_, _) =>
+ {
+ if (!isMoving && Opacity != OpacityFull)
+ {
+ BeginAnimation(OpacityProperty, FadeInOpacityAnimation);
+ }
+ };
+
+ // Fade out
+ var throttle = new Throttle(OpacityChangeDuration, (_) =>
+ {
+ if (isMoving == false && IsTouchMenuOpend == false)
+ {
+ BeginAnimation(OpacityProperty, FadeOpacityAnimation);
+ }
+ });
+ mainWindow.Loaded += (_, _) => throttle.Signal(default);
+ TranslateTouchStoryboard.Completed += (_, _) => throttle.Signal(default);
+ PreviewMouseUp += (_, _) => { if (pointWhenMouseUp == pointWhenMouseDown) throttle.Signal(default); };
+ TouchMenuClosed += (_, _) => throttle.Signal(default);
+ #endregion
+
+ FadeInOpacityAnimation.Completed += (_, _) =>
+ {
+ if (pointWhenMouseUp == pointWhenMouseDown)
+ {
+ Clicked?.Invoke(this, EventArgs.Empty);
+ }
+ };
+
+ PreviewMouseUp += (s, e) =>
+ {
+ if (pointWhenMouseUp == pointWhenMouseDown &&
+ Opacity != OpacityHalf)
+ {
+ Clicked?.Invoke(s, e);
+ }
+ };
+
+
+ FadeOpacityAnimation.Freeze();
+ FadeInOpacityAnimation.Freeze();
+ AnimationTool.BindingAnimation(TranslateTouchStoryboard, TranslateXAnimation, this, AnimationTool.XProperty);
+ AnimationTool.BindingAnimation(TranslateTouchStoryboard, TranslateYAnimation, this, AnimationTool.YProperty);
+ TranslateTouchStoryboard.Begin();
+ TranslateTouchStoryboard.Stop();
+ }
+
+ private AssistiveTouchPosition TouchPosition { get; set; } = new AssistiveTouchPosition(TouchButtonCorner.Left, 0.5);
+
+ private void SetAssistiveTouchProperties()
+ {
+ XamlResource.AssistiveTouchSize = TouchSize;
+ XamlResource.SetAssistiveTouchCornerRadius(new(TouchSize / 4));
+ XamlResource.SetAssistiveTouchCircleLinear(new(TouchSize >= 100 ? 2 : 1.5));
+ XamlResource.SetAssistiveTouchLayerOneMargin(new(TouchSize / 8));
+ XamlResource.SetAssistiveTouchLayerTwoMargin(new(TouchSize * 3 / 16));
+ XamlResource.SetAssistiveTouchLayerThreeMargin(new(TouchSize / 4));
+
+ _distance = TouchSize;
+ _halfDistance = _distance / 2;
+ _oneThirdDistance = _distance / 3;
+ _twoThirdDistance = _oneThirdDistance * 2;
+ }
+ private void SetTouchPosition()
+ {
+ var pos = CalculateTouchMargin(TouchSize, TouchPosition, Application.Current.MainWindow);
+ TouchPosTransform.SetCurrentValue(TranslateTransform.XProperty, pos.Item1);
+ TouchPosTransform.SetCurrentValue(TranslateTransform.YProperty, pos.Item2);
+ }
+
+ private void WhenMouseReleased(FrameworkElement parent, double left, double top)
+ {
+ var parentActualHeight = parent.ActualHeight;
+ var parentActualWidth = parent.ActualWidth;
+
+ // distance of button and rifht edge
+ var right = parentActualWidth - left - TouchSize;
+ // distance of button and bottom edge
+ var bottom = parentActualHeight - top - TouchSize;
+ var verticalMiddleLine = parentActualWidth / 2;
+
+ // determinate the position where button should be with animation
+ if (left < _halfDistance && top < _twoThirdDistance) // top-left
+ {
+ // when the distance of button and top-left less equal than distance
+ left = ButtonSpace;
+ top = ButtonSpace;
+ TouchPosition = AssistiveTouchPosition.UpperLeft;
+ }
+ else if (left < _halfDistance && bottom < _twoThirdDistance) // bottom-left
+ {
+ left = ButtonSpace;
+ top = parentActualHeight - TouchSize - ButtonSpace;
+ TouchPosition = AssistiveTouchPosition.LowerLeft;
+ }
+ else if (right < _halfDistance && top < _twoThirdDistance) // top-right
+ {
+ left = parentActualWidth - TouchSize - ButtonSpace;
+ top = ButtonSpace;
+ TouchPosition = AssistiveTouchPosition.UpperRight;
+ }
+ else if (right < _halfDistance && bottom < _twoThirdDistance) // bottom-right
+ {
+ left = parentActualWidth - TouchSize - ButtonSpace;
+ top = parentActualHeight - TouchSize - ButtonSpace;
+ TouchPosition = AssistiveTouchPosition.LowerRight;
+ }
+ else if (top < _twoThirdDistance) // top
+ {
+ top = ButtonSpace;
+ TouchPosition = new()
+ {
+ Corner = TouchButtonCorner.Top,
+ Scale = (left + ButtonSpace + (TouchSize / 2)) / parentActualWidth
+ };
+ }
+ else if (bottom < _twoThirdDistance) // bottom
+ {
+ top = parentActualHeight - TouchSize - ButtonSpace;
+ TouchPosition = new()
+ {
+ Corner = TouchButtonCorner.Bottom,
+ Scale = (left + ButtonSpace + (TouchSize / 2)) / parentActualWidth
+ };
+ }
+ else if (left + (TouchSize / 2) < verticalMiddleLine) // left
+ {
+ left = ButtonSpace;
+ TouchPosition = new()
+ {
+ Corner = TouchButtonCorner.Left,
+ Scale = (top + ButtonSpace + (TouchSize / 2)) / parentActualHeight
+ };
+ }
+ else // right
+ {
+ left = parentActualWidth - TouchSize - ButtonSpace;
+ TouchPosition = new()
+ {
+ Corner = TouchButtonCorner.Right,
+ Scale = (top + ButtonSpace + TouchSize / 2) / parentActualHeight
+ };
+ }
+
+ SmoothMoveAnimation(left, top);
+ }
+
+ private static readonly DoubleAnimation FadeOpacityAnimation = new()
+ {
+ From = OpacityFull,
+ To = OpacityHalf,
+ Duration = OpacityTransformDuration,
+ };
+ private static readonly DoubleAnimation FadeInOpacityAnimation = new()
+ {
+ From = OpacityHalf,
+ To = OpacityFull,
+ Duration = OpacityFadeInDuration,
+ };
+
+ private static readonly Storyboard TranslateTouchStoryboard = new();
+
+ private static readonly DoubleAnimation TranslateXAnimation = new() { Duration = TouchReleaseToEdgeDuration };
+ private static readonly DoubleAnimation TranslateYAnimation = new() { Duration = TouchReleaseToEdgeDuration };
+
+ private static void RaiseMouseReleasedEventInCode(Button touch)
+ {
+ var timestamp = new TimeSpan(DateTime.Now.Ticks).Milliseconds;
+
+ var mouseUpEvent = new MouseButtonEventArgs(Mouse.PrimaryDevice, timestamp, MouseButton.Left)
+ {
+ RoutedEvent = PreviewMouseUpEvent,
+ Source = touch,
+ };
+
+ touch.RaiseEvent(mouseUpEvent);
+ }
+
+ private static void SmoothMoveAnimation(double left, double top)
+ {
+ if (TranslateXAnimation.To == left)
+ {
+ TranslateYAnimation.To = top;
+ }
+ else if (TranslateYAnimation.To == top)
+ {
+ TranslateXAnimation.To = left;
+ }
+ else
+ {
+ TranslateXAnimation.To = left;
+ TranslateYAnimation.To = top;
+ }
+ TranslateTouchStoryboard.Begin();
+ }
+
+ ///
+ /// Get current AsisitiveTouch margin where it should be.
+ ///
+ private static (double, double) CalculateTouchMargin(
+ double buttonSize, AssistiveTouchPosition touchPos, FrameworkElement parent)
+ {
+ var rightLineMargin = parent.ActualWidth - buttonSize - ButtonSpace;
+ var bottomLineMargin = parent.ActualHeight - buttonSize - ButtonSpace;
+ var verticalScaleMargin = (touchPos.Scale * parent.ActualHeight) - (buttonSize / 2) - ButtonSpace;
+ var horizontalScaleMargin = (touchPos.Scale * parent.ActualWidth) - buttonSize / 2 - ButtonSpace;
+ return touchPos.Corner switch
+ {
+ TouchButtonCorner.UpperLeft => (ButtonSpace, ButtonSpace),
+ TouchButtonCorner.UpperRight => (rightLineMargin, ButtonSpace),
+ TouchButtonCorner.LowerLeft => (ButtonSpace, bottomLineMargin),
+ TouchButtonCorner.LowerRight => (rightLineMargin, bottomLineMargin),
+ TouchButtonCorner.Left => (ButtonSpace, verticalScaleMargin),
+ TouchButtonCorner.Top => (horizontalScaleMargin, ButtonSpace),
+ TouchButtonCorner.Right => (rightLineMargin, verticalScaleMargin),
+ TouchButtonCorner.Bottom => (horizontalScaleMargin, bottomLineMargin),
+ _ => default,
+ };
+ }
+}
+
+public record struct AssistiveTouchPosition(TouchButtonCorner Corner, double Scale = 0)
+{
+ public static readonly AssistiveTouchPosition Default = new(default, 0.5);
+ public static readonly AssistiveTouchPosition UpperLeft = new(TouchButtonCorner.UpperLeft);
+ public static readonly AssistiveTouchPosition LowerLeft = new(TouchButtonCorner.LowerLeft);
+ public static readonly AssistiveTouchPosition UpperRight = new(TouchButtonCorner.UpperRight);
+ public static readonly AssistiveTouchPosition LowerRight = new(TouchButtonCorner.LowerRight);
+}
+
+public enum TouchButtonCorner
+{
+ Left,
+ Top,
+ Right,
+ Bottom,
+ UpperLeft,
+ UpperRight,
+ LowerLeft,
+ LowerRight
+}
\ No newline at end of file
diff --git a/ErogeHelper.AssistiveTouch/TouchMenu.xaml b/ErogeHelper.AssistiveTouch/TouchMenu.xaml
new file mode 100644
index 0000000..c1ca1c4
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/TouchMenu.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ErogeHelper.AssistiveTouch/TouchMenu.xaml.cs b/ErogeHelper.AssistiveTouch/TouchMenu.xaml.cs
new file mode 100644
index 0000000..7b4271f
--- /dev/null
+++ b/ErogeHelper.AssistiveTouch/TouchMenu.xaml.cs
@@ -0,0 +1,228 @@
+using ErogeHelper.AssistiveTouch.Helper;
+using ErogeHelper.AssistiveTouch.Menu;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Media.Animation;
+
+namespace ErogeHelper.AssistiveTouch
+{
+ public partial class TouchMenu : Border
+ {
+ public static TimeSpan MenuTransistDuration { get; } = TimeSpan.FromMilliseconds(200);
+
+ private readonly ITouchMenuPage _menuMainPage = new MainPage();
+ private readonly ITouchMenuPage _menuDevicePage = new DevicePage();
+ private readonly ITouchMenuPage _menuGamePage = new GamePage();
+
+ public TouchMenu()
+ {
+ InitializeComponent();
+ XamlResource.SetAssistiveTouchItemBackground(XamlResource.AssistiveTouchBackground);
+
+ MainMenu.Navigate(_menuMainPage);
+ GameMenu.Navigate(_menuGamePage);
+ DeviceMenu.Navigate(_menuDevicePage);
+
+ _menuMainPage.PageChanged += (s, e) => PageNavigation(e.Tag);
+ _menuGamePage.PageChanged += (s, e) => PageNavigation(e.Tag);
+ _menuDevicePage.PageChanged += (s, e) => PageNavigation(e.Tag);
+
+ var mainWindow = (MainWindow)Application.Current.MainWindow;
+
+ Loaded += (_, _) => UpdateProperties(TouchButton.TouchSize);
+
+ Loaded += (_, _) => mainWindow.SizeChanged += (_, e) =>
+ {
+ if (e.HeightChanged && e.NewSize.Height > EndureEdgeHeight)
+ {
+ UpdateMenuSize(e.NewSize.Height);
+ }
+ };
+
+ #region Move Logic
+ var isOpen = false;
+ var isAnimating = false;
+
+ mainWindow.Touch.Clicked += (_, _) =>
+ {
+ TouchMenuItem.ClickLocked = true;
+ Visibility = Visibility.Visible;
+ mainWindow.Touch.Visibility = Visibility.Hidden;
+ mainWindow.Touch.IsTouchMenuOpend = isOpen = isAnimating = true;
+
+ _menuMainPage.Visibility = Visibility.Visible;
+ _menuGamePage.Visibility = Visibility.Collapsed;
+ _menuDevicePage.Visibility = Visibility.Collapsed;
+
+ var relativeBeginX = mainWindow.Width / 2 - mainWindow.Touch.ActualWidth / 2;
+ var relativeBeginY = mainWindow.Height / 2 - mainWindow.Touch.ActualWidth / 2;
+
+ TransformXAnimation.From = mainWindow.Touch.TouchPosTransform.X - relativeBeginX;
+ TransformYAnimation.From = mainWindow.Touch.TouchPosTransform.Y - relativeBeginY;
+ MovementStoryboard.Begin();
+ };
+
+ void CloseMenuInternel()
+ {
+ if (!isAnimating)
+ {
+ isAnimating = true;
+ FakeWhitePoint.Visibility = Visibility.Visible;
+ MovementStoryboard.AutoReverse = true;
+ MovementStoryboard.Begin();
+ MovementStoryboard.Seek(MenuTransistDuration);
+ }
+ }
+ // TODO Nonuse change
+ mainWindow.Deactivated += (_, _) => { if (isOpen == true) CloseMenuInternel(); };
+ PreviewMouseLeftButtonUp += (_, e) => { if (e.OriginalSource is TouchMenu && !TouchMenuItem.ClickLocked) CloseMenuInternel(); };
+
+ // Mene opend
+ MovementStoryboard.Completed += (_, _) =>
+ {
+ if (MovementStoryboard.AutoReverse == false)
+ {
+ isAnimating = false;
+ TouchMenuItem.ClickLocked = false;
+ FakeWhitePoint.Visibility = Visibility.Hidden;
+ }
+ };
+
+ // Menu closed
+ MovementStoryboard.Completed += (_, _) =>
+ {
+ // FIXEME: after window size change the touch position
+ if (MovementStoryboard.AutoReverse == true)
+ {
+ mainWindow.Touch.IsTouchMenuOpend = isOpen = isAnimating = false;
+ MovementStoryboard.AutoReverse = false;
+ mainWindow.Touch.Visibility = Visibility.Visible;
+ mainWindow.Touch.RaiseMenuClosedEvent(this);
+ Visibility = Visibility.Hidden;
+ }
+ };
+
+ // Initialize animations
+ var fakePointAnimation = AnimationTool.FadeOutAnimation;
+ fakePointAnimation.FillBehavior = FillBehavior.HoldEnd;
+ fakePointAnimation.EasingFunction = UnifiedPowerFunction;
+ AnimationTool.BindingAnimation(MovementStoryboard, fakePointAnimation, FakeWhitePoint, new(OpacityProperty), true);
+ AnimationTool.BindingAnimation(MovementStoryboard, AnimationTool.FadeInAnimation, MenuArea, new(OpacityProperty), true);
+ AnimationTool.BindingAnimation(MovementStoryboard, TransformXAnimation, this, AnimationTool.XProperty);
+ AnimationTool.BindingAnimation(MovementStoryboard, TransformYAnimation, this, AnimationTool.YProperty);
+ #endregion
+ }
+
+ public void ManualClose()
+ {
+ FakeWhitePoint.Visibility = Visibility.Visible;
+ MovementStoryboard.AutoReverse = true;
+ MovementStoryboard.Begin();
+ MovementStoryboard.Seek(MenuTransistDuration);
+ }
+
+ ///
+ /// Must initialize first after MainWindow loaded
+ ///
+ private void UpdateProperties(double touchSize)
+ {
+ MaxHeight = MaxWidth = touchSize * 5;
+ EndureEdgeHeight = MaxHeight / 10;
+ XamlResource.SetAssistiveTouchItemSize(touchSize / 2);
+ UpdateMenuSize(Application.Current.MainWindow.Height);
+
+ // Update width height animation
+ UpdateMenuSizeAnimation(touchSize);
+ }
+
+ ///
+ /// The minimal distance between window top to menu edge
+ ///
+ private double EndureEdgeHeight { get; set; }
+
+ #region X Y Width Height Animations
+ private static readonly PowerEase UnifiedPowerFunction = new() { EasingMode = EasingMode.EaseInOut };
+ private static readonly Storyboard MovementStoryboard = new();
+ private static readonly DoubleAnimation TransformXAnimation = new()
+ {
+ Duration = MenuTransistDuration,
+ EasingFunction = UnifiedPowerFunction
+ };
+ private static readonly DoubleAnimation TransformYAnimation = new()
+ {
+ Duration = MenuTransistDuration,
+ EasingFunction = UnifiedPowerFunction
+ };
+ private static DoubleAnimation? _widthAnimation;
+ private static DoubleAnimation? _heightAnimation;
+
+ ///
+ /// Called when menu size changed.
+ ///
+ /// Dependent on touch button size
+ private void UpdateMenuSizeAnimation(double touchSize)
+ {
+ MovementStoryboard.Children.Remove(_widthAnimation);
+ MovementStoryboard.Children.Remove(_heightAnimation);
+
+ _widthAnimation = new()
+ {
+ From = touchSize,
+ Duration = MenuTransistDuration,
+ EasingFunction = UnifiedPowerFunction
+ };
+ _heightAnimation = new()
+ {
+ From = touchSize,
+ Duration = MenuTransistDuration,
+ EasingFunction = UnifiedPowerFunction
+ };
+
+ AnimationTool.BindingAnimation(MovementStoryboard, _widthAnimation, this, new(WidthProperty), true);
+ AnimationTool.BindingAnimation(MovementStoryboard, _heightAnimation, this, new(HeightProperty), true);
+ }
+ #endregion
+
+ ///
+ /// When game window height changed, update this menu size.
+ ///
+ private void UpdateMenuSize(double newGameWindowHeight)
+ {
+ // The normal size of menu
+ if (newGameWindowHeight > EndureEdgeHeight + MaxHeight)
+ {
+ Height = Width = MaxHeight;
+ }
+ // Small scaled size of menu
+ else
+ {
+ var newSize = newGameWindowHeight - EndureEdgeHeight;
+ Height = Width = newSize > 0 ? newSize : 0;
+ }
+ }
+
+ private void PageNavigation(TouchMenuPageTag nav)
+ {
+ TouchMenuItem.ClickLocked = true;
+ switch (nav)
+ {
+ case TouchMenuPageTag.Game:
+ _menuMainPage.Close();
+ _menuGamePage.Show(Height / 3);
+ break;
+ case TouchMenuPageTag.GameBack:
+ _menuMainPage.Show(0);
+ _menuGamePage.Close();
+ break;
+ case TouchMenuPageTag.Device:
+ _menuMainPage.Close();
+ _menuDevicePage.Show(Height / 3);
+ break;
+ case TouchMenuPageTag.DeviceBack:
+ _menuMainPage.Show(0);
+ _menuDevicePage.Close();
+ break;
+ }
+ }
+ }
+}
diff --git a/ErogeHelper.Installer/App.xaml b/ErogeHelper.Installer/App.xaml
deleted file mode 100644
index d6a00be..0000000
--- a/ErogeHelper.Installer/App.xaml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/ErogeHelper.Installer/App.xaml.cs b/ErogeHelper.Installer/App.xaml.cs
deleted file mode 100644
index a20a966..0000000
--- a/ErogeHelper.Installer/App.xaml.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using System.Windows;
-
-namespace ErogeHelper.Installer
-{
- ///
- /// Interaction logic for App.xaml
- ///
- public partial class App
- {
- public App()
- {
- SingleInstanceWatcher();
- var currentDirectory = Path.GetDirectoryName(AppContext.BaseDirectory);
- Directory.SetCurrentDirectory(currentDirectory ??
- throw new ArgumentNullException(nameof(currentDirectory)));
- }
-
- private const string UniqueEventName = "{2d0ccd54-f861-46be-9804-43aff3775111}";
- private EventWaitHandle _eventWaitHandle = null!;
-
- private void SingleInstanceWatcher()
- {
- try
- {
- _eventWaitHandle = EventWaitHandle.OpenExisting(UniqueEventName);
- _eventWaitHandle.Set();
- Shutdown();
- }
- catch (WaitHandleCannotBeOpenedException)
- {
- _eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
- }
-
- new Task(() =>
- {
- while (_eventWaitHandle.WaitOne())
- {
- Current.Dispatcher.Invoke(() =>
- {
- if (Current.MainWindow is not null)
- {
- var mainWindow = Current.MainWindow;
-
- if (mainWindow.WindowState == WindowState.Minimized || mainWindow.Visibility != Visibility.Visible)
- {
- mainWindow.Show();
- mainWindow.WindowState = WindowState.Normal;
- }
-
- mainWindow.Activate();
- mainWindow.Topmost = true;
- mainWindow.Topmost = false;
- mainWindow.Focus();
- }
- });
- }
- })
- .Start();
- }
- }
-}
diff --git a/ErogeHelper.Installer/AssemblyInfo.cs b/ErogeHelper.Installer/AssemblyInfo.cs
deleted file mode 100644
index 8b5504e..0000000
--- a/ErogeHelper.Installer/AssemblyInfo.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using System.Windows;
-
-[assembly: ThemeInfo(
- ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
- //(used if a resource is not found in the page,
- // or application resource dictionaries)
- ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
- //(used if a resource is not found in the page,
- // app, or any theme specific resource dictionaries)
-)]
diff --git a/ErogeHelper.Installer/ErogeHelper.Installer.csproj b/ErogeHelper.Installer/ErogeHelper.Installer.csproj
deleted file mode 100644
index d3f134b..0000000
--- a/ErogeHelper.Installer/ErogeHelper.Installer.csproj
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
- WinExe
- net6.0-windows10.0.19041.0
- enable
- true
- 1.0.0
- k1mlka
- app.manifest
- app.ico
- ..\bin\$(Configuration)
- false
- true
- AnyCPU;x86
-
-
-
-
- PreserveNewest
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ErogeHelper.Installer/ExplorerHelper.cs b/ErogeHelper.Installer/ExplorerHelper.cs
deleted file mode 100644
index 0e740e4..0000000
--- a/ErogeHelper.Installer/ExplorerHelper.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace ErogeHelper.Installer
-{
- internal static class ExplorerHelper
- {
- public static List GetOpenedDirectories() => new OpenedDirectoryGenerator().Paths;
-
- public static void KillExplorer()
- {
- var processes = Process.GetProcessesByName("explorer");
-
- foreach (var process in processes)
- {
- process.Kill();
- }
- }
-
- public static void OpenDirectories(List directories)
- {
- foreach (var dir in directories)
- {
- Process.Start("explorer", dir);
- }
- }
-
- private class OpenedDirectoryGenerator
- {
- public OpenedDirectoryGenerator() => _ = EnumWindows(Report, 0);
-
- public List Paths { get; private set; } = new();
-
- #region Win32 Apis
- private delegate bool CallBack(int hWnd, int y);
-
- [DllImport("user32.dll")]
- private static extern int EnumWindows(CallBack x, int y);
-
- [DllImport("user32.dll", CharSet = CharSet.Unicode)]
- private static extern int GetWindowText(int hWnd, StringBuilder lPtrString, int nMaxCount);
-
- [DllImport("user32.dll")]
- private static extern int GetParent(int hWnd);
-
- [DllImport("user32.dll")]
- private static extern bool IsWindowVisible(int hWnd);
-
- [DllImport("user32.Dll", CharSet = CharSet.Unicode)]
- private static extern void GetClassName(IntPtr hWnd, StringBuilder s, int nMaxCount);
-
- [DllImport("user32.dll", CharSet = CharSet.Unicode)]
- private static extern IntPtr FindWindowEx(IntPtr parent, IntPtr child, string strClass, string? frmText);
-
- private static IntPtr FindWindowEx(IntPtr parent, string strClass)
- => FindWindowEx(parent, IntPtr.Zero, strClass, null);
-
- private static string GetFormClassName(IntPtr ptr)
- {
- var nameBuilder = new StringBuilder(255);
- GetClassName(ptr, nameBuilder, 255);
- return nameBuilder.ToString();
- }
-
- private static string GetFormTitle(IntPtr ptr)
- {
- var titleBuilder = new StringBuilder(255);
- _ = GetWindowText((int)ptr, titleBuilder, 255);
- return titleBuilder.ToString();
- }
-
- private bool Report(int hWnd, int lParam)
- {
- var pHWnd = GetParent(hWnd);
- if (pHWnd == 0 && IsWindowVisible(hWnd))
- {
- var cabinetWClassIntPtr = new IntPtr(hWnd);
- var cabinetWClassName = GetFormClassName(cabinetWClassIntPtr);
- if (cabinetWClassName.Equals("CabinetWClass", StringComparison.OrdinalIgnoreCase))
- {
- var workerWIntPtr = FindWindowEx(cabinetWClassIntPtr, "WorkerW");
- var reBarWindow32IntPtr = FindWindowEx(workerWIntPtr, "ReBarWindow32");
- var addressBandRootIntPtr = FindWindowEx(reBarWindow32IntPtr, "Address Band Root");
- var msctlsProgress32IntPtr = FindWindowEx(addressBandRootIntPtr, "msctls_progress32");
- var breadcrumbParentIntPtr = FindWindowEx(msctlsProgress32IntPtr, "Breadcrumb Parent");
- var toolbarWindow32IntPtr = FindWindowEx(breadcrumbParentIntPtr, "ToolbarWindow32");
-
- var title = GetFormTitle(toolbarWindow32IntPtr);
-
- var index = title.IndexOf(':') + 1;
- Paths.Add(title[index..]);
- }
- }
- return true;
- }
- #endregion
- }
- }
-}
diff --git a/ErogeHelper.Installer/MainWindow.xaml b/ErogeHelper.Installer/MainWindow.xaml
deleted file mode 100644
index 2007e2f..0000000
--- a/ErogeHelper.Installer/MainWindow.xaml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ErogeHelper.Installer/MainWindow.xaml.cs b/ErogeHelper.Installer/MainWindow.xaml.cs
deleted file mode 100644
index 7fce732..0000000
--- a/ErogeHelper.Installer/MainWindow.xaml.cs
+++ /dev/null
@@ -1,228 +0,0 @@
-using System;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Reflection;
-using System.Security.Principal;
-using System.Windows;
-using ErogeHelper.Shared;
-using ModernWpf.Controls;
-
-namespace ErogeHelper.Installer
-{
- ///
- /// Interaction logic for MainWindow.xaml
- ///
- public partial class MainWindow
- {
- private const string ShellMenuDllName = "ErogeHelper.ShellMenuHandler.dll";
-
- public MainWindow()
- {
- InitializeComponent();
-
- InstallButton.IsEnabled = !ShellExtensionManager.IsInstalled();
- UninstallButton.IsEnabled = ShellExtensionManager.IsInstalled();
-
- Loaded += CheckNecessaryFile;
- }
-
- private void CheckNecessaryFile(object sender, RoutedEventArgs e)
- {
- var shellMenuDllPath = Path.Combine(Environment.CurrentDirectory, "ErogeHelper.ShellMenuHandler.dll");
- var serverRegistrationManagerPath = Path.Combine(Environment.CurrentDirectory, "ServerRegistrationManager.exe");
- if (!File.Exists(shellMenuDllPath))
- {
- ModernWpf.MessageBox.Show($"File {shellMenuDllPath} does not exist", "Eroge Helper");
- Close();
- }
- else if (!File.Exists(serverRegistrationManagerPath))
- {
- ModernWpf.MessageBox.Show($"File {serverRegistrationManagerPath} does not exist", "Eroge Helper");
- Close();
- }
- else if (!IsAdministrator())
- {
- ModernWpf.MessageBox.Show($"Please run as Administrator", "Eroge Helper");
- Close();
- }
- }
-
- private void Register(object sender, RoutedEventArgs e)
- {
- InstallButton.IsEnabled = false;
- UninstallButton.IsEnabled = false;
-
- Process _srm = new()
- {
- EnableRaisingEvents = true,
- StartInfo = new ProcessStartInfo()
- {
- FileName = "ServerRegistrationManager.exe",
- Arguments = $"install {ShellMenuDllName} -codebase",
- UseShellExecute = false,
- RedirectStandardOutput = true,
- RedirectStandardError = true
- }
- };
- var outputData = string.Empty;
- var errorData = string.Empty;
- _srm.OutputDataReceived += (_, e) => outputData += e.Data + '\n';
- _srm.ErrorDataReceived += (_, e) => errorData += e.Data;
- _srm.Exited += (_, _) =>
- {
- if (!outputData.Contains("error"))
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- InstallButton.IsEnabled = false;
- UninstallButton.IsEnabled = true;
- });
- }
- else
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- ModernWpf.MessageBox.Show(outputData);
- InstallButton.IsEnabled = true;
- UninstallButton.IsEnabled = false;
- });
- }
- };
-
- _srm.Start();
- _srm.BeginErrorReadLine();
- _srm.BeginOutputReadLine();
- }
-
- private async void UnInstall(object sender, RoutedEventArgs e)
- {
- InstallButton.IsEnabled = false;
- UninstallButton.IsEnabled = false;
-
- if (DeleteCacheCheckBox.IsChecked ?? false)
- {
- var deleteTipDialog = new ContentDialog
- {
- Title = Shared.Languages.Strings.Common_Warn,
- Content = Shared.Languages.Strings.Installer_DeleteTip,
- PrimaryButtonText = Shared.Languages.Strings.Common_OK,
- CloseButtonText = Shared.Languages.Strings.Common_Cancel,
- DefaultButton = ContentDialogButton.Close
- };
- var result = await deleteTipDialog.ShowAsync();
- if (result == ContentDialogResult.None)
- {
- UninstallButton.IsEnabled = true;
- return;
- }
- if (result == ContentDialogResult.Primary)
- {
- var roamingDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
- var cacheDir = Path.Combine(roamingDir, "ErogeHelper");
- if (Directory.Exists(cacheDir))
- {
- Directory.Delete(cacheDir, true);
- }
- }
- }
-
- // unload ShellHandle.dll first
- Process _srm = new()
- {
- EnableRaisingEvents = true,
- StartInfo = new ProcessStartInfo()
- {
- FileName = "ServerRegistrationManager.exe",
- Arguments = $"uninstall {ShellMenuDllName} -codebase",
- UseShellExecute = false,
- RedirectStandardOutput = true,
- RedirectStandardError = true
- }
- };
- var outputData = string.Empty;
- var errorData = string.Empty;
- _srm.OutputDataReceived += (_, e) => outputData += e.Data + '\n';
- _srm.ErrorDataReceived += (_, e) => errorData += e.Data;
- _srm.Exited += (_, _) =>
- {
- if (!outputData.Contains("error"))
- {
- // restart all explore.exe
- var directories = ExplorerHelper.GetOpenedDirectories();
- ExplorerHelper.KillExplorer();
- ExplorerHelper.OpenDirectories(directories);
-
- // TODO: Arm64 installer unload dll progress
- if (Utils.IsArm)
- {
- Process.GetProcessesByName("dllhost").ToList()
- .ForEach(p =>
- {
- try
- {
- var accessMainModule = p.MainModule!.BaseAddress;
- p.Kill();
- }
- catch { }
- });
- }
-
- Application.Current.Dispatcher.Invoke(() =>
- {
- InstallButton.IsEnabled = true;
- UninstallButton.IsEnabled = false;
- DeleteCacheCheckBox.IsChecked = false;
- });
- }
- else
- {
- Application.Current.Dispatcher.Invoke(() =>
- {
- ModernWpf.MessageBox.Show(outputData);
- InstallButton.IsEnabled = false;
- UninstallButton.IsEnabled = true;
- });
- }
- };
-
- _srm.Start();
- _srm.BeginErrorReadLine();
- _srm.BeginOutputReadLine();
- }
-
- private static bool IsAdministrator()
- {
- var current = WindowsIdentity.GetCurrent();
- var windowsPrincipal = new WindowsPrincipal(current);
- return windowsPrincipal.IsInRole(WindowsBuiltInRole.Administrator);
- }
-
- // https://stackoverflow.com/questions/21566528/what-happens-if-i-remove-the-auto-added-supportedruntime-element
- ///
- /// Throws an exception if the current version of the .NET Framework is smaller than the specified
- /// .
- ///
- /// The minimum supported version of the .NET Framework on which the current
- /// program can run.
- /// The version to use is not the marketing version, but the file version of mscorlib.dll.
- /// See File
- /// version history for CLR 4.x and/or
- /// .NET Framework Versioni (Build pubblicata) for the version to use.
- ///
- /// The current version of the .NET Framework is smaller than the
- /// specified .
- /// The version of the .NET Framework on which the current program is running.
- public static Version EnsureSupportedDotNetFrameworkVersion(Version supportedVersion)
- {
- var fileVersion = typeof(int).Assembly.GetCustomAttribute();
- var currentVersion = new Version(fileVersion!.Version);
- MessageBox.Show(currentVersion.ToString());
- if (currentVersion < supportedVersion)
- throw new NotSupportedException(
- $"Microsoft .NET Framework {supportedVersion} or newer is required. " +
- $"Current version ({currentVersion}) is not supported.");
- return currentVersion;
- }
- }
-}
diff --git a/ErogeHelper.Installer/ServerRegistrationManager.exe b/ErogeHelper.Installer/ServerRegistrationManager.exe
deleted file mode 100644
index ccc258d..0000000
Binary files a/ErogeHelper.Installer/ServerRegistrationManager.exe and /dev/null differ
diff --git a/ErogeHelper.Installer/ShellExtensionManager.cs b/ErogeHelper.Installer/ShellExtensionManager.cs
deleted file mode 100644
index 4307521..0000000
--- a/ErogeHelper.Installer/ShellExtensionManager.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using Microsoft.Win32;
-
-namespace ErogeHelper.Installer
-{
- internal static class ShellExtensionManager
- {
- //private static readonly string keyName = $@"CLSID\{{FC0DDE3F-C236-3705-8E20-1BEF78D62D0B}}";
- private const string FriendlyName = "ErogeHelper.ShellMenuHandler.ShellMenuExtension";
-
- public static bool IsInstalled()
- {
- var rootName = Registry.ClassesRoot;
-
- return rootName.OpenSubKey(FriendlyName, false) is not null;
- }
- }
-}
diff --git a/ErogeHelper.Installer/app.ico b/ErogeHelper.Installer/app.ico
deleted file mode 100644
index c23210a..0000000
Binary files a/ErogeHelper.Installer/app.ico and /dev/null differ
diff --git a/ErogeHelper.Installer/app.manifest b/ErogeHelper.Installer/app.manifest
deleted file mode 100644
index 0f857a1..0000000
--- a/ErogeHelper.Installer/app.manifest
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ErogeHelper.Model/DataModel/Tables/GameInfoTable.cs b/ErogeHelper.Model/DataModel/Tables/GameInfoTable.cs
deleted file mode 100644
index a483d66..0000000
--- a/ErogeHelper.Model/DataModel/Tables/GameInfoTable.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using Dapper.Contrib.Extensions;
-
-namespace ErogeHelper.Model.DataModel.Tables;
-
-[Table("GameInfo")]
-public record GameInfoTable
-{
- [ExplicitKey]
- public string Md5 { get; set; } = string.Empty;
-
- // Saved as "186,143,123"
- public string GameIdList { get; set; } = string.Empty;
-
- public string RegExp { get; set; } = string.Empty;
-
- public string TextractorSettingJson { get; set; } = string.Empty;
-
- public bool IsLoseFocus { get; set; }
-
- public bool IsEnableTouchToMouse { get; set; }
-
- public bool UseCloudSave { get; set; }
-
- public string SaveDataPath { get; set; } = string.Empty;
-}
diff --git a/ErogeHelper.Model/DataServices/GameDataService.cs b/ErogeHelper.Model/DataServices/GameDataService.cs
deleted file mode 100644
index 43204bd..0000000
--- a/ErogeHelper.Model/DataServices/GameDataService.cs
+++ /dev/null
@@ -1,69 +0,0 @@
-using System.Diagnostics;
-using System.Reactive.Subjects;
-using ErogeHelper.Model.DataServices.Interface;
-using ErogeHelper.Shared;
-using ErogeHelper.Shared.Contracts;
-using Splat;
-using Vanara.PInvoke;
-
-namespace ErogeHelper.Model.DataServices;
-
-public class GameDataService : IGameDataService, IEnableLogger, IDisposable
-{
- public void InitGameMd5AndPath(string md5, string gamePath) => (Md5, GamePath) = (md5, gamePath);
- public string Md5 { get; private set; } = string.Empty;
- public string GamePath { get; private set; } = string.Empty;
-
- private IDisposable? _fullscreenDisposable;
- public void InitFullscreenChanged(IConnectableObservable observable)
- {
- GameFullscreenChanged = observable;
- _fullscreenDisposable = GameFullscreenChanged.Connect();
- }
-
- public IConnectableObservable GameFullscreenChanged { get; private set; } = null!;
-
- public void SearchingProcesses(string gamePath) =>
- (MainProcess, GameProcesses) = ProcessCollect(Path.GetFileNameWithoutExtension(gamePath));
- public List GameProcesses { get; private set; } = null!;
- public Process MainProcess { get; private set; } = null!;
-
- public HWND GameRealWindowHandle { get; private set; }
- public void SetGameRealWindowHandle(HWND handle) => GameRealWindowHandle = handle;
-
-
- ///
- /// Get all processes of the game (timeout 20s)
- ///
- /// aka
- /// if process with hWnd found, give all back, other wise return blank list
- private static (Process, List) ProcessCollect(string friendlyName)
- {
- var spendTime = new Stopwatch();
- spendTime.Start();
- Process? mainProcess = null;
- var procList = new List();
-
- while (mainProcess is null && spendTime.Elapsed.TotalMilliseconds < ConstantValue.WaitGameStartTimeout)
- {
- Thread.Sleep(ConstantValue.UIMinimumResponseTime);
- procList = Utils.GetProcessesByFriendlyName(friendlyName);
-
- mainProcess = procList.FirstOrDefault(p => p.MainWindowHandle != IntPtr.Zero);
- }
- spendTime.Stop();
-
- if (mainProcess is null)
- {
- throw new TimeoutException("Timeout! Find MainWindowHandle Failed");
- }
-
- LogHost.Default.Debug(
- $"{procList.Count} Process(es) and MainWindowHandle 0x{mainProcess.MainWindowHandle:X8} Found.");
-
- return (mainProcess, procList);
- }
-
- public void Dispose() => _fullscreenDisposable?.Dispose();
-}
-
diff --git a/ErogeHelper.Model/DataServices/Interface/IGameDataService.cs b/ErogeHelper.Model/DataServices/Interface/IGameDataService.cs
deleted file mode 100644
index 9268291..0000000
--- a/ErogeHelper.Model/DataServices/Interface/IGameDataService.cs
+++ /dev/null
@@ -1,23 +0,0 @@
-using System.Diagnostics;
-using System.Reactive.Subjects;
-using Vanara.PInvoke;
-
-namespace ErogeHelper.Model.DataServices.Interface;
-
-public interface IGameDataService : IDisposable
-{
- void InitGameMd5AndPath(string md5, string gamePath);
- string Md5 { get; }
- string GamePath { get; }
-
- void InitFullscreenChanged(IConnectableObservable observable);
- IConnectableObservable GameFullscreenChanged { get; }
-
- void SearchingProcesses(string gamePath);
- List GameProcesses { get; }
- Process MainProcess { get; }
-
- HWND GameRealWindowHandle { get; }
- void SetGameRealWindowHandle(HWND handle);
-}
-
diff --git a/ErogeHelper.Model/DataServices/Interface/IWindowDataService.cs b/ErogeHelper.Model/DataServices/Interface/IWindowDataService.cs
deleted file mode 100644
index ad7c309..0000000
--- a/ErogeHelper.Model/DataServices/Interface/IWindowDataService.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using Vanara.PInvoke;
-
-namespace ErogeHelper.Model.DataServices.Interface;
-
-public interface IWindowDataService
-{
- void InitMainWindowHandle(HWND handle);
- HWND MainWindowHandle { get; }
- void InitTextWindowHandle(HWND handle);
- HWND TextWindowHandle { get; }
- double Dpi { get; set; }
- IObservable DpiChanged { get; }
- void DpiOnNext(double dpi);
-}
diff --git a/ErogeHelper.Model/DataServices/WindowDataService.cs b/ErogeHelper.Model/DataServices/WindowDataService.cs
deleted file mode 100644
index 0a24352..0000000
--- a/ErogeHelper.Model/DataServices/WindowDataService.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using System.Reactive.Subjects;
-using ErogeHelper.Model.DataServices.Interface;
-using Vanara.PInvoke;
-
-namespace ErogeHelper.Model.DataServices;
-
-public class WindowDataService : IWindowDataService
-{
- public void InitMainWindowHandle(HWND handle) => MainWindowHandle = handle;
-
- public HWND MainWindowHandle { get; private set; }
-
- public void InitTextWindowHandle(HWND handle) => TextWindowHandle = handle;
-
- public HWND TextWindowHandle { get; private set; }
-
- public double Dpi { get; set; }
-
- public IObservable DpiChanged => _dpiSubj;
-
- public void DpiOnNext(double dpi) => _dpiSubj.OnNext(dpi);
-
- private readonly ReplaySubject _dpiSubj = new(1);
-}
diff --git a/ErogeHelper.Model/ErogeHelper.Model.csproj b/ErogeHelper.Model/ErogeHelper.Model.csproj
deleted file mode 100644
index d442809..0000000
--- a/ErogeHelper.Model/ErogeHelper.Model.csproj
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
- net6.0
- enable
- 10
- false
- enable
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/ErogeHelper.Model/Repositories/GameInfoRepository.cs b/ErogeHelper.Model/Repositories/GameInfoRepository.cs
deleted file mode 100644
index d849411..0000000
--- a/ErogeHelper.Model/Repositories/GameInfoRepository.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-using System.Data;
-using Dapper.Contrib.Extensions;
-using ErogeHelper.Model.DataModel.Tables;
-using ErogeHelper.Model.DataServices.Interface;
-using ErogeHelper.Model.Repositories.Interface;
-using ErogeHelper.Shared;
-using Microsoft.Data.Sqlite;
-
-namespace ErogeHelper.Model.Repositories;
-
-public class GameInfoRepository : IGameInfoRepository
-{
- private readonly string _connectString;
- private readonly IGameDataService _gameDataService;
- private GameInfoTable? _gameInfo;
-
- private string GameMd5 => _gameDataService.Md5;
-
- public GameInfoRepository(string connectString, IGameDataService? gameDataService = null)
- {
- _gameDataService = gameDataService ?? DependencyResolver.GetService();
- _connectString = connectString;
- }
-
- private IDbConnection GetOpenConnection()
- {
- var connection = new SqliteConnection(_connectString);
- connection.Open();
- return connection;
- }
-
- public GameInfoTable GameInfo
- {
- get
- {
- ArgumentNullException.ThrowIfNull(_gameInfo);
- return _gameInfo;
- }
- }
-
- public GameInfoTable? TryGetGameInfo()
- {
- if (_gameInfo is null)
- {
- using var connection = GetOpenConnection();
- _gameInfo = connection.Get(GameMd5);
- }
-
- return _gameInfo;
- }
-
- public void AddGameInfo(GameInfoTable gameInfoTable)
- {
- using var connection = GetOpenConnection();
- connection.Insert(gameInfoTable);
- _gameInfo = gameInfoTable;
- }
-
- public void UpdateGameInfo(GameInfoTable gameInfoTable)
- {
- using var connection = GetOpenConnection();
- connection.Update(gameInfoTable);
- }
-
- public void UpdateCloudStatus(bool useCloudSaveData)
- {
- using var connection = GetOpenConnection();
- connection.Update(_gameInfo! with { UseCloudSave = useCloudSaveData });
- }
-
- public void UpdateSaveDataPath(string path)
- {
- using var connection = GetOpenConnection();
- connection.Update(_gameInfo! with { SaveDataPath = path });
- }
-
- public void UpdateLostFocusStatus(bool status)
- {
- using var connection = GetOpenConnection();
- connection.Update(_gameInfo! with { IsLoseFocus = status });
- }
-
- public void UpdateTouchEnable(bool status)
- {
- using var connection = GetOpenConnection();
- connection.Update(_gameInfo! with { IsEnableTouchToMouse = status });
- }
-
- public void UpdateTextractorSetting(string setting)
- {
- using var connection = GetOpenConnection();
- connection.Update(_gameInfo! with { TextractorSettingJson = setting });
- }
-}
diff --git a/ErogeHelper.Model/Repositories/Interface/IEhConfigRepository.cs b/ErogeHelper.Model/Repositories/Interface/IEhConfigRepository.cs
deleted file mode 100644
index 04c2a0a..0000000
--- a/ErogeHelper.Model/Repositories/Interface/IEhConfigRepository.cs
+++ /dev/null
@@ -1,156 +0,0 @@
-using System.ComponentModel;
-using Config.Net;
-using ErogeHelper.Shared.Contracts;
-using ErogeHelper.Shared.Enums;
-
-namespace ErogeHelper.Model.Repositories.Interface;
-
-public interface IEHConfigRepository : INotifyPropertyChanged
-{
- [Option(DefaultValue = "https://eh.nya.run")]
- public string ServerBaseUrl { get; set; }
-
- // TextWindow 関する
- [Option(DefaultValue = false)]
- public bool TextWindowBlur { get; set; }
-
- [Option(DefaultValue = 800.0)]
- public double TextWindowWidth { get; set; }
-
- [Option(DefaultValue = 0.0)]
- public double TextWindowOpacity { get; set; }
-
- [Option(DefaultValue = false)]
- public bool HideTextWindow { get; set; }
-
- // Text 関する
- [Option(DefaultValue = 120)]
- public int MaxAcceptTextLength { get; set; }
-
- [Option(DefaultValue = 28.0)]
- public double FontSize { get; set; }
-
- [Option(DefaultValue = false)]
- public bool EnableMeCab { get; set; }
-
- // MISC
- [Option(DefaultValue = true)]
- public bool InjectProcessByDefalut { get; set; }
-
- [Option(DefaultValue = 600.0)]
- public double PreferenceWindowHeight { get; set; }
-
- [Option(DefaultValue = 600.0)]
- public double PreferenceWindowWidth { get; set; }
-
-
- [Option(DefaultValue = ConstantValue.DefaultAssistiveTouchPositionStr)]
- public string AssistiveTouchPosition { get; set; }
-
- [Option(DefaultValue = false)]
- public bool UseBigAssistiveTouchSize { get; set; }
-
- [Option(DefaultValue = false)]
- public bool MonitorClipboard { get; set; }
-
- [Option(DefaultValue = false)]
- public bool PasteToDeepL { get; set; }
-
- [Option(DefaultValue = false)]
- public bool UseDanmaku { get; set; }
-
- [Option(DefaultValue = "")]
- public string ExternalSharedDrivePath { get; set; }
-
- [Option(DefaultValue = false)]
- public bool DPIByApplication { get; set; }
-
- [Option(DefaultValue = false)]
- public bool UseEdgeTouchMask { get; set; }
-
- [Option(DefaultValue = false)]
- public bool UseTouchToolBox { get; set; }
-
- [Option(DefaultValue = false)]
- public bool UpdatePreviewVersion { get; set; }
-
- // Text 模様
- [Option(DefaultValue = TextTemplate.OutLineKanaTop)]
- public TextTemplate TextTemplateConfig { get; set; }
-
- [Option(DefaultValue = KanaPosition.Top)]
- public KanaPosition KanaPosition { get; set; }
-
- [Option(DefaultValue = KanaRuby.Hiragana)]
- public KanaRuby KanaRuby { get; set; }
-
- // Dictionaries
- [Option(DefaultValue = false)]
- public bool MojiDictEnable { get; set; }
-
- // Account: erogehelper@github.com
- // Password: ******
- [Option(DefaultValue = "r:02625329e868e96b5eb65bca9dead47e")]
- public string MojiSessionToken { get; set; }
-
- [Option(DefaultValue = false)]
- public bool JishoDictEnable { get; set; }
-
-
- public TransLanguage SrcTransLanguage { get; set; }
-
- public TransLanguage TargetTransLanguage { get; set; }
-
- #region Translators
-
- [Option(Alias = "Translators.BaiduApiEnable", DefaultValue = false)]
- public bool BaiduApiEnable { get; set; }
-
- [Option(Alias = "Translators.BaiduApiAppid", DefaultValue = "")]
- public string BaiduApiAppid { get; set; }
-
- [Option(Alias = "Translators.BaiduApiSecretKey", DefaultValue = "")]
- public string BaiduApiSecretKey { get; set; }
-
- [Option(Alias = "Translators.YeekitEnable", DefaultValue = false)]
- public bool YeekitEnable { get; set; }
-
- [Option(Alias = "Translators.BaiduWebEnable", DefaultValue = false)]
- public bool BaiduWebEnable { get; set; }
-
- [Option(Alias = "Translators.CaiyunEnable", DefaultValue = false)]
- public bool CaiyunEnable { get; set; }
-
- [Option(Alias = "Translators.CaiyunToken", DefaultValue = "3975l6lr5pcbvidl6jl2")]
- public string CaiyunToken { get; set; }
-
- [Option(Alias = "Translators.AlapiEnable", DefaultValue = false)]
- public bool AlapiEnable { get; set; }
-
- [Option(Alias = "Translators.YoudaoEnable", DefaultValue = false)]
- public bool YoudaoEnable { get; set; }
-
- [Option(Alias = "Translators.NiuTransEnable", DefaultValue = false)]
- public bool NiuTransEnable { get; set; }
-
- [Option(Alias = "Translators.NiuTransApiKey", DefaultValue = "")]
- public string NiuTransApiKey { get; set; }
-
- [Option(Alias = "Translators.GoogleCnEnable", DefaultValue = false)]
- public bool GoogleCnEnable { get; set; }
-
- [Option(Alias = "Translators.TencentMtEnable", DefaultValue = false)]
- public bool TencentMtEnable { get; set; }
-
- [Option(Alias = "Translators.TencentMtSecretId", DefaultValue = "")]
- public string TencentMtSecretId { get; set; }
-
- [Option(Alias = "Translators.TencentMtSecretKey", DefaultValue = "")]
- public string TencentMtSecretKey { get; set; }
-
- [Option(Alias = "Translators.CloudEnable", DefaultValue = false)]
- public bool CloudEnable { get; set; }
-
- #endregion
-}
-
diff --git a/ErogeHelper.Model/Repositories/Interface/IGameInfoRepository.cs b/ErogeHelper.Model/Repositories/Interface/IGameInfoRepository.cs
deleted file mode 100644
index 8753495..0000000
--- a/ErogeHelper.Model/Repositories/Interface/IGameInfoRepository.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using ErogeHelper.Model.DataModel.Tables;
-
-namespace ErogeHelper.Model.Repositories.Interface;
-
-public interface IGameInfoRepository
-{
- public GameInfoTable GameInfo { get; }
-
- public GameInfoTable? TryGetGameInfo();
-
- public void AddGameInfo(GameInfoTable gameInfoTable);
-
- public void UpdateGameInfo(GameInfoTable gameInfoTable);
-
- public void UpdateCloudStatus(bool useCloudSaveData);
-
- public void UpdateSaveDataPath(string path);
-
- public void UpdateLostFocusStatus(bool status);
-
- public void UpdateTouchEnable(bool status);
-
- public void UpdateTextractorSetting(string setting);
-}
diff --git a/ErogeHelper.Model/Repositories/Migration/_001AddGameInfoTable.cs b/ErogeHelper.Model/Repositories/Migration/_001AddGameInfoTable.cs
deleted file mode 100644
index fea5e8a..0000000
--- a/ErogeHelper.Model/Repositories/Migration/_001AddGameInfoTable.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-using FluentMigrator;
-
-namespace ErogeHelper.Model.Repositories.Migration;
-
-[Migration(20210325151400)]
-public class _001AddGameInfoTable : AutoReversingMigration
-{
- public override void Up() =>
- Create.Table("GameInfo")
- .WithColumn("Md5").AsString()
- .WithColumn("GameIdList").AsString()
- .WithColumn("RegExp").AsString()
- .WithColumn("TextractorSettingJson").AsString()
- .WithColumn("IsLoseFocus").AsBoolean()
- .WithColumn("IsEnableTouchToMouse").AsBoolean();
-}
diff --git a/ErogeHelper.Model/Repositories/Migration/_002AddUserTermTable.cs b/ErogeHelper.Model/Repositories/Migration/_002AddUserTermTable.cs
deleted file mode 100644
index e26cff2..0000000
--- a/ErogeHelper.Model/Repositories/Migration/_002AddUserTermTable.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FluentMigrator;
-
-namespace ErogeHelper.Model.Repositories.Migration;
-
-[Migration(20210421201800)]
-public class _002AddUserTermTable : AutoReversingMigration
-{
- public override void Up() =>
- Create.Table("UserTerm")
- .WithColumn("From").AsString()
- .WithColumn("To").AsString();
-}
diff --git a/ErogeHelper.Model/Repositories/Migration/_003AddSaveDataCloudColumn.cs b/ErogeHelper.Model/Repositories/Migration/_003AddSaveDataCloudColumn.cs
deleted file mode 100644
index cd26c10..0000000
--- a/ErogeHelper.Model/Repositories/Migration/_003AddSaveDataCloudColumn.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using FluentMigrator;
-
-namespace ErogeHelper.Model.Repositories.Migration;
-
-[Migration(20210819071700)]
-public class _003AddSaveDataCloudColumn : AutoReversingMigration
-{
- public override void Up() =>
- Alter.Table("GameInfo")
- .AddColumn("UseCloudSave").AsBoolean().WithDefaultValue(false)
- .AddColumn("SaveDataPath").AsString().WithDefaultValue(string.Empty);
-}
diff --git a/ErogeHelper.Model/Services/GameWindowHooker.cs b/ErogeHelper.Model/Services/GameWindowHooker.cs
deleted file mode 100644
index cfd3626..0000000
--- a/ErogeHelper.Model/Services/GameWindowHooker.cs
+++ /dev/null
@@ -1,268 +0,0 @@
-using System.Diagnostics;
-using System.Reactive.Concurrency;
-using System.Reactive.Subjects;
-using System.Runtime.InteropServices;
-using ErogeHelper.Model.DataServices.Interface;
-using ErogeHelper.Model.Services.Interface;
-using ErogeHelper.Shared;
-using ErogeHelper.Shared.Contracts;
-using ErogeHelper.Shared.Enums;
-using ErogeHelper.Shared.Structs;
-using Splat;
-using Vanara.PInvoke;
-
-namespace ErogeHelper.Model.Services;
-
-public class GameWindowHooker : IGameWindowHooker, IEnableLogger
-{
- private HWND _gameHwnd;
- private Process _gameProc = null!;
- private static readonly WindowPosition HiddenPos = new(0, 0, -32000, -32000);
- //private static readonly RECT MinimizedPosition = new(-32000, -32000, -31840, -31972);
- //private static readonly RECT MinimizedPosition4K = new(-64000, -64000, -63680, -63944);
-
- private User32.HWINEVENTHOOK _windowsEventHook = IntPtr.Zero;
- private const uint EventObjectDestroy = 0x8001;
- private const uint EventObjectShow = 0x8002;
- private const uint EventObjectHide = 0x8003;
- private const uint EventObjectLocationChange = 0x800B;
- private GCHandle _gcSafetyHandle;
- // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwineventhook
- private const User32.WINEVENT WinEventHookInternalFlags = User32.WINEVENT.WINEVENT_INCONTEXT |
- User32.WINEVENT.WINEVENT_SKIPOWNPROCESS;
- private const long SWEH_CHILDID_SELF = 0;
-
- private readonly ReplaySubject _gamePositionSubj = new(1);
- public IObservable GamePosUpdated => _gamePositionSubj;
-
- private readonly ReplaySubject _gamePosChangedSubj = new(1);
- public IObservable GamePosChanged => _gamePosChangedSubj;
-
- private readonly Subject _ViewOperationSubj = new();
- public IObservable WhenViewOperated => _ViewOperationSubj;
-
- private IGameDataService? _gameDataService;
- public void SetupGameWindowHook(Process process, IGameDataService? gameDataService, IScheduler mainScheduler)
- {
- _gameProc = process;
-
- _gameProc.EnableRaisingEvents = true;
- _gameDataService = gameDataService ?? DependencyResolver.GetService();
- _gameHwnd = CurrentWindowHandle(_gameProc);
- _gameDataService.SetGameRealWindowHandle(_gameHwnd);
-
- var targetThreadId = User32.GetWindowThreadProcessId(_gameHwnd, out var processId);
-
- User32.WinEventProc winEventDelegate = WinEventCallback;
- _gcSafetyHandle = GCHandle.Alloc(winEventDelegate);
-
- mainScheduler.Schedule(() =>
- _windowsEventHook = User32.SetWinEventHook(
- EventObjectDestroy, EventObjectLocationChange,
- IntPtr.Zero, winEventDelegate, processId, targetThreadId,
- WinEventHookInternalFlags));
-
- _gameProc.Exited += (_, _) =>
- {
- _gamePositionSubj.OnNext(HiddenPos);
- this.Log().Debug("Detected game quit event");
- _gcSafetyHandle.Free();
- // Produce EventObjectDestroy
- User32.UnhookWinEvent(_windowsEventHook);
-
- _ViewOperationSubj.OnNext(ViewOperation.TerminateApp);
- _gamePositionSubj.OnCompleted();
- _gamePosChangedSubj.OnCompleted();
- _ViewOperationSubj.OnCompleted();
- };
- }
-
- public WindowPosition InvokeUpdatePosition() => UpdateLocation();
-
- private static HWND CurrentWindowHandle(Process proc, bool activeGame = true)
- {
- proc.WaitForInputIdle(ConstantValue.WaitGameStartTimeout);
- proc.Refresh();
- var gameHwnd = proc.MainWindowHandle;
-
- if (activeGame && User32.IsIconic(proc.MainWindowHandle))
- {
- User32.ShowWindow(proc.MainWindowHandle, ShowWindowCommand.SW_RESTORE);
- }
-
- User32.GetClientRect(gameHwnd, out var clientRect);
-
- if (clientRect.bottom > ConstantValue.GoodWindowHeight &&
- clientRect.right > ConstantValue.GoodWindowWidth)
- {
- return gameHwnd;
- }
- else
- {
- var spendTime = new Stopwatch();
- spendTime.Start();
- while (spendTime.Elapsed.TotalMilliseconds < ConstantValue.WaitGameStartTimeout)
- {
- if (proc.HasExited)
- return IntPtr.Zero;
-
- var handles = GetRootWindowsOfProcess(proc.Id);
- foreach (var handle in handles)
- {
- User32.GetClientRect(handle, out clientRect);
- if (clientRect.bottom > ConstantValue.GoodWindowHeight &&
- clientRect.right > ConstantValue.GoodWindowWidth)
- {
- LogHost.Default.Debug($"Set new handle 0x{handle.DangerousGetHandle():X8}");
- return handle;
- }
- }
- Thread.Sleep(ConstantValue.UIMinimumResponseTime);
- }
- throw new ArgumentException("Find window handle failed");
- }
- }
-
- ///
- /// Running in UI thread
- ///
- private void WinEventCallback(
- User32.HWINEVENTHOOK hWinEventHook,
- uint eventType,
- HWND hWnd,
- int idObject,
- int idChild,
- uint dwEventThread,
- uint dwmsEventTime)
- {
- switch (eventType)
- {
- case EventObjectDestroy:
- {
- // even if game lost focus would get EventObjectDestroy object (krkr)
- if (hWnd == _gameHwnd)
- {
- try
- {
- _gameHwnd = CurrentWindowHandle(_gameProc, false);
- if (_gameHwnd.IsNull)
- {
- _gamePositionSubj.OnNext(HiddenPos);
- _gamePositionSubj.OnCompleted();
- }
- else
- {
- _gameDataService!.SetGameRealWindowHandle(_gameHwnd);
- _ViewOperationSubj.OnNext(ViewOperation.Show);
- this.Log().Debug("Game window show - recreate");
- UpdateLocation();
- }
- }
- catch (InvalidOperationException ex)
- {
- // game process may get already exit, caused by Process.WaitForInputIdle() with delay
- this.Log().Debug(ex.Message);
- }
- }
- }
- break;
- case EventObjectShow:
- {
- if (_gameProc.MainWindowHandle != hWnd && hWnd == _gameHwnd)
- {
- _ViewOperationSubj.OnNext(ViewOperation.Show);
- this.Log().Debug("Game window show");
- UpdateLocation();
- }
- }
- break;
- case EventObjectHide:
- {
- if (_gameProc.MainWindowHandle != hWnd && hWnd == _gameHwnd)
- {
- _ViewOperationSubj.OnNext(ViewOperation.Hide);
- this.Log().Debug("Game window hide");
- }
- }
- break;
- case EventObjectLocationChange:
- {
- // Update game's position information
- if (hWnd == _gameHwnd && idObject == SWEH_CHILDID_SELF)
- {
- UpdateLocation();
- }
- }
- break;
- }
- }
-
- private WindowPosition _lastPos = HiddenPos;
-
- private WindowPosition UpdateLocation()
- {
- User32.GetWindowRect(_gameHwnd, out var rect);
- User32.GetClientRect(_gameHwnd, out var rectClient);
-
- var winShadow = (rect.Width - rectClient.right) / 2;
- var left = rect.left + winShadow;
-
- var wholeHeight = rect.bottom - rect.top;
- var winTitleHeight = wholeHeight - rectClient.bottom - winShadow;
- var top = rect.top + winTitleHeight;
-
- var windowPosition = new WindowPosition(rectClient.Height, rectClient.Width, left, top);
-
- if (windowPosition.Left == 0 && windowPosition.Top == 0 &&
- windowPosition.Height == 0 && windowPosition.Width == 0 && !_gameProc.HasExited)
- {
- // SST311212 MessageBox
- _gameHwnd = CurrentWindowHandle(_gameProc, false);
- _gameDataService!.SetGameRealWindowHandle(_gameHwnd);
- }
-
- _gamePosChangedSubj.OnNext(new WindowPositionChange(left - _lastPos.Left, top - _lastPos.Top));
- _lastPos = windowPosition;
- _gamePositionSubj.OnNext(windowPosition);
- return windowPosition;
- }
-
- private static IEnumerable GetRootWindowsOfProcess(int pid)
- {
- var rootWindows = GetChildWindows(IntPtr.Zero);
- List dsProcRootWindows = new();
- foreach (var hWnd in rootWindows)
- {
- _ = User32.GetWindowThreadProcessId(hWnd, out var lpdwProcessId);
- if (lpdwProcessId == pid)
- dsProcRootWindows.Add(hWnd);
- }
- return dsProcRootWindows;
- }
-
- private static IEnumerable GetChildWindows(HWND parent)
- {
- List result = new();
- var listHandle = GCHandle.Alloc(result);
- try
- {
- static bool ChildProc(HWND handle, IntPtr pointer)
- {
- var gch = GCHandle.FromIntPtr(pointer);
- if (gch.Target is not List list)
- {
- throw new InvalidCastException("GCHandle Target could not be cast as List");
- }
- list.Add(handle);
- return true;
- }
- User32.EnumChildWindows(parent, ChildProc, GCHandle.ToIntPtr(listHandle));
- }
- finally
- {
- if (listHandle.IsAllocated)
- listHandle.Free();
- }
- return result;
- }
-}
diff --git a/ErogeHelper.Model/Services/Interface/IGameWindowHooker.cs b/ErogeHelper.Model/Services/Interface/IGameWindowHooker.cs
deleted file mode 100644
index b95bfb7..0000000
--- a/ErogeHelper.Model/Services/Interface/IGameWindowHooker.cs
+++ /dev/null
@@ -1,20 +0,0 @@
-using System.Diagnostics;
-using System.Reactive.Concurrency;
-using ErogeHelper.Model.DataServices.Interface;
-using ErogeHelper.Shared.Enums;
-using ErogeHelper.Shared.Structs;
-
-namespace ErogeHelper.Model.Services.Interface;
-
-public interface IGameWindowHooker
-{
- IObservable GamePosUpdated { get; }
-
- IObservable GamePosChanged { get; }
-
- IObservable WhenViewOperated { get; }
-
- void SetupGameWindowHook(Process process, IGameDataService? gameDataService, IScheduler scheduler);
-
- WindowPosition InvokeUpdatePosition();
-}
diff --git a/ErogeHelper.Model/Services/Interface/IMeCabService.cs b/ErogeHelper.Model/Services/Interface/IMeCabService.cs
deleted file mode 100644
index 6993699..0000000
--- a/ErogeHelper.Model/Services/Interface/IMeCabService.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-using ErogeHelper.Shared.Structs;
-
-namespace ErogeHelper.Model.Services.Interface;
-
-public interface IMeCabService
-{
- bool Loaded { get; }
-
- void CreateTagger();
-
- List GenerateMeCabWords(string sentence);
-}
diff --git a/ErogeHelper.Model/Services/Interface/ITextractorService.cs b/ErogeHelper.Model/Services/Interface/ITextractorService.cs
deleted file mode 100644
index fb43075..0000000
--- a/ErogeHelper.Model/Services/Interface/ITextractorService.cs
+++ /dev/null
@@ -1,35 +0,0 @@
-using System.Diagnostics;
-using ErogeHelper.Shared.Entities;
-using ErogeHelper.Shared.Structs;
-
-namespace ErogeHelper.Model.Services.Interface;
-
-public interface ITextractorService
-{
- IObservable Data { get; }
-
- IObservable SelectedData { get; }
-
- TextractorSetting Setting { get; set; }
-
- bool Injected { get; }
-
- ///
- /// Inject hooks into processes, also initialize Textractor service. This should be called only once
- ///
- ///
- /// Textractor init callback functions depends on some parameters
- void InjectProcesses(List gameProcesses);
-
- void InsertHook(string hookcode);
-
- void SearchRCode(string text);
-
- List GetConsoleOutputInfo();
-
- void ReAttachProcesses();
-
- void RemoveHook(long address);
-
- void RemoveUselessHooks();
-}
diff --git a/ErogeHelper.Model/Services/Interface/ITouchConversionHooker.cs b/ErogeHelper.Model/Services/Interface/ITouchConversionHooker.cs
deleted file mode 100644
index 3876876..0000000
--- a/ErogeHelper.Model/Services/Interface/ITouchConversionHooker.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace ErogeHelper.Model.Services.Interface;
-
-public interface ITouchConversionHooker : IDisposable
-{
- bool Enable { get; set; }
-
- void Init();
-}
-
diff --git a/ErogeHelper.Model/Services/Interface/IUpdateService.cs b/ErogeHelper.Model/Services/Interface/IUpdateService.cs
deleted file mode 100644
index 409c00b..0000000
--- a/ErogeHelper.Model/Services/Interface/IUpdateService.cs
+++ /dev/null
@@ -1,8 +0,0 @@
-using System.Drawing;
-
-namespace ErogeHelper.Model.Services.Interface;
-
-public interface IUpdateService
-{
- IObservable<(string tip, Color versionColor, bool canUpdate)> CheckUpdate(string version, bool previewVersion);
-}
diff --git a/ErogeHelper.Model/Services/MeCabService.cs b/ErogeHelper.Model/Services/MeCabService.cs
deleted file mode 100644
index 919a7cc..0000000
--- a/ErogeHelper.Model/Services/MeCabService.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-using System.Reactive.Linq;
-using ErogeHelper.Model.Repositories.Interface;
-using ErogeHelper.Model.Services.Interface;
-using ErogeHelper.Shared;
-using ErogeHelper.Shared.Contracts;
-using ErogeHelper.Shared.Enums;
-using ErogeHelper.Shared.Extensions;
-using ErogeHelper.Shared.Structs;
-using MeCab;
-using MeCab.Extension.UniDic;
-using WanaKanaNet;
-
-namespace ErogeHelper.Model.Services;
-
-public class MeCabService : IMeCabService
-{
- private MeCabTagger? _tagger;
- private readonly IEHConfigRepository _configRepository;
-
- public MeCabService(IEHConfigRepository? ehConfigRepository = null)
- {
- _configRepository = ehConfigRepository ?? DependencyResolver.GetService();
-
- Loaded = false;
- if (Directory.Exists(EHContext.MeCabDicFolder))
- {
- _tagger = MeCabTagger.Create(new MeCabParam
- {
- DicDir = EHContext.MeCabDicFolder
- });
- Loaded = true;
- }
- }
-
- public bool Loaded { get; private set; }
-
- public void CreateTagger()
- {
- _tagger = MeCabTagger.Create(new MeCabParam
- {
- DicDir = EHContext.MeCabDicFolder
- });
-
- Loaded = true;
- }
-
- public List GenerateMeCabWords(string sentence) => MeCabWordUniDicEnumerable(sentence).ToList();
-
- private IEnumerable MeCabWordUniDicEnumerable(string sentence)
- {
- ArgumentNullException.ThrowIfNull(_tagger, nameof(_tagger));
-
- foreach (var node in _tagger.ParseToNodes(sentence))
- {
- if (node.CharType <= 0)
- continue;
-
- var hinshi = (node.GetPos1() ?? string.Empty).ToHinshi();
- var kana = " "; // full-width space to force render it
- if ((node.GetGoshu() ?? string.Empty).Equals("外"))
- {
- // katakana's source form(外来語) like english supplied by unidic
- kana = (node.GetLemma() ?? " ").Split('-')[^1];
- }
- else if (_configRepository.KanaRuby == KanaRuby.Romaji)
- {
- // all to romaji
- kana = WanaKana.ToRomaji(node.GetPron() ?? " ");
- }
- else if (!WanaKana.IsKana(node.Surface) && hinshi != JapanesePartOfSpeech.Mark)
- {
- // kanji to kana
- kana = _configRepository.KanaRuby == KanaRuby.Hiragana
- ? WanaKana.ToHiragana(node.GetPron() ?? " ")
- : node.GetPron() ?? " "; // katakana by default
- }
-
- yield return new MeCabWord
- {
- Word = node.Surface,
- Kana = kana,
- PartOfSpeech = hinshi
- }; ;
- }
- }
-}
diff --git a/ErogeHelper.Model/Services/MeCabWinRTService.cs b/ErogeHelper.Model/Services/MeCabWinRTService.cs
deleted file mode 100644
index 3358d00..0000000
--- a/ErogeHelper.Model/Services/MeCabWinRTService.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using ErogeHelper.Model.Repositories.Interface;
-using ErogeHelper.Model.Services.Interface;
-using ErogeHelper.Shared;
-using ErogeHelper.Shared.Enums;
-using ErogeHelper.Shared.Structs;
-using WanaKanaNet;
-
-namespace ErogeHelper.Model.Services;
-
-public class MeCabWinRTService : IMeCabService
-{
- public static Func>? MeCabWordWinRTCallback { private get; set; }
-
- public bool Loaded => true;
-
- public void CreateTagger() => throw new NotImplementedException();
-
- public List GenerateMeCabWords(string sentence)
- {
- // phrase like すっご would break winrt Japanese analyzer
- var words = MeCabWordWinRTEnumerable(sentence).ToList();
- return words.Count == 0 ? new()
- {
- new() { Word = sentence, Kana = " ", WordIsKanji = false }
- } : words;
- }
-
- public MeCabWinRTService(IEHConfigRepository? ehConfigRepository = null)
- => _configRepository = ehConfigRepository ?? DependencyResolver.GetService();
-
- private readonly IEHConfigRepository _configRepository;
-
- private IEnumerable MeCabWordWinRTEnumerable(string sentence)
- {
- ArgumentNullException.ThrowIfNull(MeCabWordWinRTCallback, nameof(MeCabWordWinRTCallback));
-
- if (sentence.Length > 100)
- {
- sentence = sentence[..100];
- }
- foreach (var mecabWord in MeCabWordWinRTCallback(sentence))
- {
- var kana = " "; // full-width space to force render it
- if (_configRepository.KanaRuby == KanaRuby.Romaji)
- {
- // all to romaji
- kana = WanaKana.ToRomaji(mecabWord.Kana);
- }
- else if (mecabWord.WordIsKanji)
- {
- // kanji or katakana to katakana
- kana = _configRepository.KanaRuby == KanaRuby.Katakana
- ? WanaKana.ToKatakana(mecabWord.Kana)
- : mecabWord.Kana; // hiragana by default
- }
-
- yield return new MeCabWord
- {
- Word = mecabWord.Word,
- Kana = kana,
- };
- }
- }
-}
diff --git a/ErogeHelper.Model/Services/TextractorServiceCli.cs b/ErogeHelper.Model/Services/TextractorServiceCli.cs
deleted file mode 100644
index 1b5f53a..0000000
--- a/ErogeHelper.Model/Services/TextractorServiceCli.cs
+++ /dev/null
@@ -1,170 +0,0 @@
-using System.ComponentModel;
-using System.Diagnostics;
-using System.Reactive.Subjects;
-using System.Text;
-using System.Text.RegularExpressions;
-using ErogeHelper.Model.Services.Interface;
-using ErogeHelper.Shared.Entities;
-using ErogeHelper.Shared.Structs;
-using Splat;
-using Vanara.PInvoke;
-
-namespace ErogeHelper.Model.Services;
-
-public class TextractorServiceCli : ITextractorService, IEnableLogger
-{
- private readonly Subject _dataSubj = new();
- public IObservable Data => _dataSubj;
-
- private readonly Subject _selectedDataSubj = new();
- public IObservable SelectedData => _selectedDataSubj;
-
- public TextractorSetting Setting { get; set; } = null!;
-
- private readonly List _consoleOutput = new();
- public List GetConsoleOutputInfo() => _consoleOutput;
-
- private Process _textractorCli = null!;
-
- private List _gameProcesses = null!;
-
- public bool Injected { get; private set; } = false;
-
- ///
- public void InjectProcesses(List gameProcesses)
- {
- _gameProcesses = gameProcesses;
- // CODESMELL: Operate _gameProcesses in IsX86Process() function
- bool isX86 = gameProcesses.ToList().Any(p => IsX86Process(p));
-
- var textractorCliPath = Path.Combine(
- Directory.GetCurrentDirectory(),
- "libs",
- isX86 ? "x86" : "x64",
- "cli",
- "TextractorCLI.exe");
-
- ProcessStartInfo processStartInfo = new ProcessStartInfo()
- {
- UseShellExecute = false,
- CreateNoWindow = true,
- StandardOutputEncoding = Encoding.Unicode,
- StandardInputEncoding = new UnicodeEncoding(false, false),
- FileName = textractorCliPath,
- RedirectStandardInput = true,
- RedirectStandardOutput = true,
- RedirectStandardError = true
- };
-
- _textractorCli = Process.Start(processStartInfo) ?? throw new InvalidOperationException();
- _textractorCli.OutputDataReceived += OutputDataRetrieveCallback;
- _textractorCli.BeginOutputReadLine();
-
- foreach (Process p in _gameProcesses)
- {
- _textractorCli.StandardInput.WriteLine("attach -P" + p.Id);
- _textractorCli.StandardInput.Flush();
- this.Log().Debug($"attach to PID {p.Id}.");
- }
-
- if (Setting.IsUserHook)
- {
- InsertHook(Setting.HookCode);
- }
-
- Injected = true;
- }
-
- public void InsertHook(string hookcode) => throw new NotImplementedException();
- public void ReAttachProcesses()
- {
- foreach (Process p in _gameProcesses)
- {
- _textractorCli.StandardInput.WriteLine("detach -P" + p.Id);
- _textractorCli.StandardInput.Flush();
- this.Log().Debug($"detach to PID {p.Id}.");
- }
- }
-
- public void RemoveHook(long address) => throw new NotImplementedException();
- public void RemoveUselessHooks() => throw new NotImplementedException();
- public void SearchRCode(string text) => throw new NotImplementedException();
-
- private void OutputDataRetrieveCallback(object sender, DataReceivedEventArgs e)
- {
- if (e.Data is not string outputData ||
- !outputData.StartsWith('[') ||
- outputData.Length > 500)
- {
- return;
- }
-
- var regex = new Regex(@"\[(?