feat: made prototype in c#
This commit is contained in:
commit
f727066c06
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Build results
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
out/
|
||||||
|
[Bb]uild/
|
||||||
|
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.user
|
||||||
|
*.suo
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# OS-specific files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Dotnet specific
|
||||||
|
*.dbmdl
|
||||||
|
*.jfm
|
||||||
|
*.pdb
|
||||||
|
*.mdb
|
||||||
|
|
||||||
|
# NuGet
|
||||||
|
*.nupkg
|
||||||
|
*.snupkg
|
||||||
|
.nuget/
|
||||||
|
packages/
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
|
||||||
|
# Rider / JetBrains
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# Visual Studio (if someone opens in full VS)
|
||||||
|
*.VC.db
|
||||||
|
*.VC.opendb
|
||||||
|
|
||||||
|
# Others
|
||||||
|
*.swp
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
21
.vscode/launch.json
vendored
Normal file
21
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Conjure Arcade Overlay",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/bin/Debug/net9.0-windows/Conjure.Arcade.Overlay.dll",
|
||||||
|
"args": ["${workspaceFolder}/Resources/placeholder_logo.png"],
|
||||||
|
"cwd": "${workspaceFolder}",
|
||||||
|
"stopAtEntry": false,
|
||||||
|
"console": "internalConsole",
|
||||||
|
"preLaunchTask": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": ".NET Core Attach",
|
||||||
|
"type": "coreclr",
|
||||||
|
"request": "attach"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
54
.vscode/tasks.json
vendored
Normal file
54
.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
{
|
||||||
|
"version": "2.0.0",
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"label": "build",
|
||||||
|
"command": "dotnet",
|
||||||
|
"type": "process",
|
||||||
|
"args": [
|
||||||
|
"build",
|
||||||
|
"${workspaceFolder}/Conjure.Arcade.Overlay.csproj",
|
||||||
|
"/property:GenerateFullPaths=true",
|
||||||
|
"/consoleloggerparameters:NoSummary"
|
||||||
|
],
|
||||||
|
"problemMatcher": "$msCompile",
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Build Conjure.Arcade.Overlay (no dependencies)",
|
||||||
|
"type": "shell",
|
||||||
|
"command": "dotnet build Conjure.Arcade.Overlay.csproj --no-dependencies",
|
||||||
|
"group": "build",
|
||||||
|
"problemMatcher": "$msCompile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Clean all obj", // A descriptive name for your task
|
||||||
|
"type": "shell",
|
||||||
|
"command": "${workspaceFolder}/cleaning_bin_obj.bat", // Replace with the actual path
|
||||||
|
"group": {
|
||||||
|
"kind": "build", // Optional: Assign to a task group (e.g., build, test)
|
||||||
|
"isDefault": true // Optional: Make it the default for the group
|
||||||
|
},
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always", // Show the terminal when the task runs
|
||||||
|
"panel": "new" // Open a new terminal panel for the task
|
||||||
|
},
|
||||||
|
"problemMatcher": [] // Configure problem matching if needed
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Clean and Rebuild",
|
||||||
|
"dependsOrder": "sequence",
|
||||||
|
"dependsOn": [
|
||||||
|
"Clean all bin and obj",
|
||||||
|
"Build Conjure.Arcade.Overlay (no dependencies)"
|
||||||
|
],
|
||||||
|
"group": {
|
||||||
|
"kind": "build",
|
||||||
|
"isDefault": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
24
Conjure.Arcade.Overlay.csproj
Normal file
24
Conjure.Arcade.Overlay.csproj
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0-windows</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
|
||||||
|
<!-- Assembly info generation -->
|
||||||
|
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||||
|
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
|
||||||
|
<Product>Conjure Arcade Overlay</Product>
|
||||||
|
<AssemblyName>Conjure.Arcade.Overlay</AssemblyName>
|
||||||
|
<Version>1.0.0</Version>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Resources\placeholder_logo.png">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
24
Conjure.Arcade.Overlay.sln
Normal file
24
Conjure.Arcade.Overlay.sln
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Conjure.Arcade.Overlay", "Conjure.Arcade.Overlay.csproj", "{89927838-91A3-5909-5048-DCD37BEFC139}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{89927838-91A3-5909-5048-DCD37BEFC139}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{89927838-91A3-5909-5048-DCD37BEFC139}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{89927838-91A3-5909-5048-DCD37BEFC139}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{89927838-91A3-5909-5048-DCD37BEFC139}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {1AA29E9A-935E-4FE5-8E85-4D386D166F1C}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
126
MainOverlay.cs
Normal file
126
MainOverlay.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
|
||||||
|
namespace Conjure.Arcade.Overlay
|
||||||
|
{
|
||||||
|
public class MainOverlay : OverlayWindow
|
||||||
|
{
|
||||||
|
private readonly TickEngine _tickEngine;
|
||||||
|
private Process? _targetProcess;
|
||||||
|
private const string TARGET_PROCESS = "notepad";
|
||||||
|
private readonly TextBlock _overlayText;
|
||||||
|
private bool _isPaused;
|
||||||
|
private HotkeyManager? _hotkeyManager;
|
||||||
|
private bool _isVisible;
|
||||||
|
|
||||||
|
public MainOverlay()
|
||||||
|
{
|
||||||
|
_tickEngine = new TickEngine(Update, 60);
|
||||||
|
Loaded += OnLoaded;
|
||||||
|
Closed += OnClosed;
|
||||||
|
|
||||||
|
// Set semi-transparent background
|
||||||
|
Background = new SolidColorBrush(Color.FromArgb(128, 0, 0, 0));
|
||||||
|
|
||||||
|
// Create and configure the text
|
||||||
|
_overlayText = new TextBlock
|
||||||
|
{
|
||||||
|
Text = "Overlay Active",
|
||||||
|
Foreground = Brushes.White,
|
||||||
|
FontSize = 24,
|
||||||
|
HorizontalAlignment = HorizontalAlignment.Center,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add text to the window
|
||||||
|
Content = _overlayText;
|
||||||
|
|
||||||
|
// Add key event handler
|
||||||
|
KeyDown += OnKeyDown;
|
||||||
|
|
||||||
|
// Update text block
|
||||||
|
UpdateOverlayText();
|
||||||
|
|
||||||
|
// Start hidden
|
||||||
|
Visibility = Visibility.Hidden;
|
||||||
|
_isVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var windowHandle = new WindowInteropHelper(this).Handle;
|
||||||
|
_hotkeyManager = new HotkeyManager(windowHandle, ToggleOverlay);
|
||||||
|
_hotkeyManager.Register();
|
||||||
|
|
||||||
|
_targetProcess = WindowUtils.FindProcess(TARGET_PROCESS);
|
||||||
|
if (_targetProcess == null)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Could not find process: {TARGET_PROCESS}");
|
||||||
|
Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tickEngine.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClosed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_hotkeyManager?.Unregister();
|
||||||
|
_tickEngine.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (_targetProcess?.HasExited ?? true)
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(Close);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle = _targetProcess.MainWindowHandle;
|
||||||
|
if (WindowUtils.GetWindowBounds(handle, out var rect))
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Left = rect.Left;
|
||||||
|
Top = rect.Top;
|
||||||
|
Width = rect.Right - rect.Left;
|
||||||
|
Height = rect.Bottom - rect.Top;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleOverlay()
|
||||||
|
{
|
||||||
|
_isVisible = !_isVisible;
|
||||||
|
_isPaused = _isVisible; // Pause when visible, unpause when hidden
|
||||||
|
|
||||||
|
Visibility = _isVisible ? Visibility.Visible : Visibility.Hidden;
|
||||||
|
|
||||||
|
if (_targetProcess != null)
|
||||||
|
{
|
||||||
|
if (_isPaused)
|
||||||
|
WindowUtils.SuspendProcess(_targetProcess);
|
||||||
|
else
|
||||||
|
WindowUtils.ResumeProcess(_targetProcess);
|
||||||
|
|
||||||
|
UpdateOverlayText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the space key handler since we're using the hotkey now
|
||||||
|
private void OnKeyDown(object sender, KeyEventArgs e)
|
||||||
|
{
|
||||||
|
// Empty or remove this method
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateOverlayText()
|
||||||
|
{
|
||||||
|
_overlayText.Text = _isPaused ? "PAUSED (Ctrl+Alt+O to Resume)" : "Running";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Overlay/HotkeyManager.cs
Normal file
50
Overlay/HotkeyManager.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
|
||||||
|
namespace Conjure.Arcade.Overlay
|
||||||
|
{
|
||||||
|
public class HotkeyManager
|
||||||
|
{
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool RegisterHotKey(IntPtr hWnd, int id, uint fsModifiers, uint vk);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
|
||||||
|
|
||||||
|
private const int HOTKEY_ID = 9000;
|
||||||
|
private const uint MOD_ALT = 0x0001;
|
||||||
|
private const uint MOD_CONTROL = 0x0002;
|
||||||
|
|
||||||
|
private readonly IntPtr _windowHandle;
|
||||||
|
private readonly Action _callback;
|
||||||
|
|
||||||
|
public HotkeyManager(IntPtr windowHandle, Action callback)
|
||||||
|
{
|
||||||
|
_windowHandle = windowHandle;
|
||||||
|
_callback = callback;
|
||||||
|
ComponentDispatcher.ThreadPreprocessMessage += OnThreadPreprocessMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register()
|
||||||
|
{
|
||||||
|
// Register Ctrl+Alt+O as the hotkey
|
||||||
|
RegisterHotKey(_windowHandle, HOTKEY_ID, MOD_CONTROL | MOD_ALT, 0x4F); // 0x4F is 'O'
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unregister()
|
||||||
|
{
|
||||||
|
UnregisterHotKey(_windowHandle, HOTKEY_ID);
|
||||||
|
ComponentDispatcher.ThreadPreprocessMessage -= OnThreadPreprocessMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnThreadPreprocessMessage(ref MSG msg, ref bool handled)
|
||||||
|
{
|
||||||
|
if (msg.message == 0x0312 && msg.wParam.ToInt32() == HOTKEY_ID)
|
||||||
|
{
|
||||||
|
_callback();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
55
Overlay/OverlayWindow.cs
Normal file
55
Overlay/OverlayWindow.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Conjure.Arcade.Overlay
|
||||||
|
{
|
||||||
|
public class OverlayWindow : Window
|
||||||
|
{
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||||
|
|
||||||
|
private const int GWL_EXSTYLE = -20;
|
||||||
|
private const int WS_EX_TRANSPARENT = 0x20;
|
||||||
|
private const int WS_EX_LAYERED = 0x80000;
|
||||||
|
private bool _isClickThrough = true;
|
||||||
|
|
||||||
|
public OverlayWindow()
|
||||||
|
{
|
||||||
|
// Make window transparent and click-through
|
||||||
|
WindowStyle = WindowStyle.None;
|
||||||
|
AllowsTransparency = true;
|
||||||
|
Background = null;
|
||||||
|
Topmost = true;
|
||||||
|
ShowInTaskbar = false;
|
||||||
|
|
||||||
|
// Handle window creation
|
||||||
|
SourceInitialized += OnSourceInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSourceInitialized(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var hwnd = new WindowInteropHelper(this).Handle;
|
||||||
|
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||||
|
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle | WS_EX_TRANSPARENT | WS_EX_LAYERED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetClickThrough(bool enabled)
|
||||||
|
{
|
||||||
|
var hwnd = new WindowInteropHelper(this).Handle;
|
||||||
|
var extendedStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
extendedStyle |= WS_EX_TRANSPARENT;
|
||||||
|
else
|
||||||
|
extendedStyle &= ~WS_EX_TRANSPARENT;
|
||||||
|
|
||||||
|
SetWindowLong(hwnd, GWL_EXSTYLE, extendedStyle);
|
||||||
|
_isClickThrough = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
76
Overlay/Settings/SerializableSettings.cs
Normal file
76
Overlay/Settings/SerializableSettings.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Conjure.Arcade.Overlay.Settings
|
||||||
|
{
|
||||||
|
public class SerializableSettings<T> : IDisposable where T : new()
|
||||||
|
{
|
||||||
|
private T? _current; // Make nullable
|
||||||
|
private bool _currentExists;
|
||||||
|
private readonly string _settingsPath;
|
||||||
|
|
||||||
|
public SerializableSettings(string settingsName)
|
||||||
|
{
|
||||||
|
_settingsPath = Path.Combine(
|
||||||
|
AppDomain.CurrentDomain.BaseDirectory,
|
||||||
|
"Settings",
|
||||||
|
$"{settingsName}.json"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public T Current
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!_currentExists)
|
||||||
|
{
|
||||||
|
_current = new T();
|
||||||
|
_currentExists = true;
|
||||||
|
}
|
||||||
|
return _current!; // We know it's not null here
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
_current = value;
|
||||||
|
_currentExists = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
var directory = Path.GetDirectoryName(_settingsPath);
|
||||||
|
if (!Directory.Exists(directory))
|
||||||
|
Directory.CreateDirectory(directory!);
|
||||||
|
|
||||||
|
File.WriteAllText(_settingsPath,
|
||||||
|
System.Text.Json.JsonSerializer.Serialize(Current,
|
||||||
|
new System.Text.Json.JsonSerializerOptions { WriteIndented = true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Load()
|
||||||
|
{
|
||||||
|
if (!File.Exists(_settingsPath))
|
||||||
|
{
|
||||||
|
Save();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = File.ReadAllText(_settingsPath);
|
||||||
|
Current = System.Text.Json.JsonSerializer.Deserialize<T>(json) ?? new T();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _disposed;
|
||||||
|
public event EventHandler? OnDispose;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (!_disposed)
|
||||||
|
{
|
||||||
|
OnDispose?.Invoke(this, EventArgs.Empty);
|
||||||
|
_disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Overlay/TickEngine.cs
Normal file
62
Overlay/TickEngine.cs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Conjure.Arcade.Overlay
|
||||||
|
{
|
||||||
|
public class TickEngine
|
||||||
|
{
|
||||||
|
private readonly Action _tickAction;
|
||||||
|
private readonly int _targetFps;
|
||||||
|
private readonly CancellationTokenSource _cancellationSource;
|
||||||
|
private bool _isRunning;
|
||||||
|
|
||||||
|
public TickEngine(Action tickAction, int targetFps = 60)
|
||||||
|
{
|
||||||
|
_tickAction = tickAction;
|
||||||
|
_targetFps = targetFps;
|
||||||
|
_cancellationSource = new CancellationTokenSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
if (_isRunning) return;
|
||||||
|
|
||||||
|
_isRunning = true;
|
||||||
|
Task.Run(RunTickLoop, _cancellationSource.Token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
_isRunning = false;
|
||||||
|
_cancellationSource.Cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RunTickLoop()
|
||||||
|
{
|
||||||
|
var stopwatch = new Stopwatch();
|
||||||
|
var targetFrameTime = TimeSpan.FromSeconds(1.0 / _targetFps);
|
||||||
|
|
||||||
|
while (_isRunning && !_cancellationSource.Token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
stopwatch.Restart();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_tickAction();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine($"Error in tick loop: {ex}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var elapsed = stopwatch.Elapsed;
|
||||||
|
if (elapsed < targetFrameTime)
|
||||||
|
{
|
||||||
|
await Task.Delay(targetFrameTime - elapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Overlay/WindowUtils.cs
Normal file
89
Overlay/WindowUtils.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Conjure.Arcade.Overlay
|
||||||
|
{
|
||||||
|
public static class WindowUtils
|
||||||
|
{
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern uint SuspendThread(IntPtr hThread);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern uint ResumeThread(IntPtr hThread);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll")]
|
||||||
|
private static extern bool CloseHandle(IntPtr hObject);
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
private enum ThreadAccess : int
|
||||||
|
{
|
||||||
|
SUSPEND_RESUME = 0x0002
|
||||||
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll")]
|
||||||
|
private static extern IntPtr GetWindowRect(IntPtr hWnd, ref Rect rect);
|
||||||
|
|
||||||
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
|
public struct Rect
|
||||||
|
{
|
||||||
|
public int Left;
|
||||||
|
public int Top;
|
||||||
|
public int Right;
|
||||||
|
public int Bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool GetWindowBounds(IntPtr handle, out Rect rect)
|
||||||
|
{
|
||||||
|
rect = new Rect();
|
||||||
|
return GetWindowRect(handle, ref rect) != IntPtr.Zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Process? FindProcess(string processName)
|
||||||
|
{
|
||||||
|
var processes = Process.GetProcessesByName(processName);
|
||||||
|
return processes.Length > 0 ? processes[0] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SuspendProcess(Process process)
|
||||||
|
{
|
||||||
|
foreach (ProcessThread thread in process.Threads)
|
||||||
|
{
|
||||||
|
var threadHandle = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||||
|
if (threadHandle != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SuspendThread(threadHandle);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseHandle(threadHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ResumeProcess(Process process)
|
||||||
|
{
|
||||||
|
foreach (ProcessThread thread in process.Threads)
|
||||||
|
{
|
||||||
|
var threadHandle = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)thread.Id);
|
||||||
|
if (threadHandle != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ResumeThread(threadHandle);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
CloseHandle(threadHandle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Program.cs
Normal file
23
Program.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using Conjure.Arcade.Overlay.Views;
|
||||||
|
|
||||||
|
namespace Conjure.Arcade.Overlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// </summary>
|
||||||
|
public class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the entry point of the application.
|
||||||
|
/// </summary>
|
||||||
|
[STAThread]
|
||||||
|
public static void Main(string[] args)
|
||||||
|
{
|
||||||
|
var logoPath = args.Length > 0 ? args[0] : "default_logo.png";
|
||||||
|
var application = new Application();
|
||||||
|
var mainWindow = new MainOverlayWindow(logoPath);
|
||||||
|
application.Run(mainWindow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Resources/placeholder_logo.png
Normal file
BIN
Resources/placeholder_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.9 KiB |
114
Views/MainOverlayWindow.xaml
Normal file
114
Views/MainOverlayWindow.xaml
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
<local:OverlayWindow x:Class="Conjure.Arcade.Overlay.Views.MainOverlayWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:Conjure.Arcade.Overlay"
|
||||||
|
Title="Overlay" Height="450" Width="800">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<!-- Pixel art glow effect -->
|
||||||
|
<DropShadowEffect x:Key="GlowEffect"
|
||||||
|
Color="#4d97f8"
|
||||||
|
BlurRadius="15"
|
||||||
|
ShadowDepth="0"/>
|
||||||
|
|
||||||
|
<!-- Game UI button style -->
|
||||||
|
<Style x:Key="GameButton" TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="#4d97f8"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="FontFamily" Value="Segoe UI"/>
|
||||||
|
<Setter Property="FontSize" Value="20"/>
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||||
|
<Setter Property="Width" Value="180"/>
|
||||||
|
<Setter Property="Height" Value="50"/>
|
||||||
|
<Setter Property="Margin" Value="15"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
CornerRadius="8"
|
||||||
|
BorderThickness="0">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"/>
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<!-- Background gradient matching Vue app -->
|
||||||
|
<Rectangle>
|
||||||
|
<Rectangle.Fill>
|
||||||
|
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
|
||||||
|
<!-- Almost transparent at top -->
|
||||||
|
<GradientStop Color="#104d97f8" Offset="0"/>
|
||||||
|
<!-- Semi-transparent in middle -->
|
||||||
|
<GradientStop Color="#804d97f8" Offset="0.5"/>
|
||||||
|
<!-- More opaque at bottom -->
|
||||||
|
<GradientStop Color="#FF100a7d" Offset="1"/>
|
||||||
|
</LinearGradientBrush>
|
||||||
|
</Rectangle.Fill>
|
||||||
|
</Rectangle>
|
||||||
|
|
||||||
|
<!-- Main content layout with defined rows -->
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto"/> <!-- Time -->
|
||||||
|
<RowDefinition Height="*"/> <!-- Main content -->
|
||||||
|
<RowDefinition Height="Auto"/> <!-- Buttons -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<!-- Top bar with time -->
|
||||||
|
<TextBlock x:Name="TimeText"
|
||||||
|
Grid.Row="0"
|
||||||
|
Text="00:00:00"
|
||||||
|
Foreground="White"
|
||||||
|
FontFamily="Segoe UI"
|
||||||
|
FontSize="24"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
TextAlignment="Center"
|
||||||
|
Margin="0,20,0,0"/>
|
||||||
|
|
||||||
|
<!-- Center content -->
|
||||||
|
<StackPanel Grid.Row="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="20">
|
||||||
|
<!-- Game Logo -->
|
||||||
|
<Image x:Name="GameLogo"
|
||||||
|
Width="400"
|
||||||
|
Height="150"
|
||||||
|
Stretch="Uniform"
|
||||||
|
Margin="0,0,0,40"/>
|
||||||
|
|
||||||
|
<!-- Status text -->
|
||||||
|
<TextBlock x:Name="StatusText"
|
||||||
|
Text="PAUSED"
|
||||||
|
Foreground="White"
|
||||||
|
FontFamily="Segoe UI"
|
||||||
|
FontSize="36"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
TextAlignment="Center"
|
||||||
|
Effect="{StaticResource GlowEffect}"/>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Action Buttons at bottom -->
|
||||||
|
<StackPanel Grid.Row="2"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Margin="0,0,0,40">
|
||||||
|
<Button Style="{StaticResource GameButton}"
|
||||||
|
x:Name="ResumeButton"
|
||||||
|
Content="Resume Game"
|
||||||
|
Click="OnResumeClick"/>
|
||||||
|
|
||||||
|
<Button Style="{StaticResource GameButton}"
|
||||||
|
x:Name="QuitButton"
|
||||||
|
Content="Exit"
|
||||||
|
Background="#FF4444"
|
||||||
|
Click="OnQuitClick"/>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</local:OverlayWindow>
|
||||||
161
Views/MainOverlayWindow.xaml.cs
Normal file
161
Views/MainOverlayWindow.xaml.cs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
using System.Windows.Media.Imaging; // Add this for BitmapImage
|
||||||
|
|
||||||
|
namespace Conjure.Arcade.Overlay.Views
|
||||||
|
{
|
||||||
|
public partial class MainOverlayWindow : OverlayWindow
|
||||||
|
{
|
||||||
|
private readonly TickEngine _tickEngine;
|
||||||
|
private Process? _targetProcess;
|
||||||
|
private const string TARGET_PROCESS = "notepad";
|
||||||
|
private HotkeyManager? _hotkeyManager;
|
||||||
|
private bool _isPaused;
|
||||||
|
private bool _isVisible;
|
||||||
|
private readonly DispatcherTimer _timer;
|
||||||
|
private readonly string _logoPath;
|
||||||
|
|
||||||
|
public MainOverlayWindow(string logoPath)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_logoPath = logoPath;
|
||||||
|
|
||||||
|
_tickEngine = new TickEngine(Update, 60);
|
||||||
|
Loaded += OnLoaded;
|
||||||
|
Closed += OnClosed;
|
||||||
|
|
||||||
|
// Start hidden
|
||||||
|
Visibility = Visibility.Hidden;
|
||||||
|
_isVisible = false;
|
||||||
|
|
||||||
|
// Setup timer for clock updates
|
||||||
|
_timer = new DispatcherTimer();
|
||||||
|
_timer.Interval = TimeSpan.FromSeconds(1);
|
||||||
|
_timer.Tick += Timer_Tick;
|
||||||
|
_timer.Start();
|
||||||
|
|
||||||
|
// Initial time update
|
||||||
|
UpdateTime();
|
||||||
|
|
||||||
|
// Load logo
|
||||||
|
LoadLogo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadLogo()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var image = new BitmapImage(new Uri(_logoPath, UriKind.Absolute));
|
||||||
|
GameLogo.Source = image;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Failed to load game logo: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var windowHandle = new WindowInteropHelper(this).Handle;
|
||||||
|
_hotkeyManager = new HotkeyManager(windowHandle, ToggleOverlay);
|
||||||
|
_hotkeyManager.Register();
|
||||||
|
|
||||||
|
_targetProcess = WindowUtils.FindProcess(TARGET_PROCESS);
|
||||||
|
if (_targetProcess == null)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Could not find process: {TARGET_PROCESS}");
|
||||||
|
Close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_tickEngine.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnClosed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
_hotkeyManager?.Unregister();
|
||||||
|
_tickEngine.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (_targetProcess?.HasExited ?? true)
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(Close);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var handle = _targetProcess.MainWindowHandle;
|
||||||
|
if (WindowUtils.GetWindowBounds(handle, out var rect))
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Left = rect.Left;
|
||||||
|
Top = rect.Top;
|
||||||
|
Width = rect.Right - rect.Left;
|
||||||
|
Height = rect.Bottom - rect.Top;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleOverlay()
|
||||||
|
{
|
||||||
|
_isVisible = !_isVisible;
|
||||||
|
_isPaused = _isVisible;
|
||||||
|
|
||||||
|
Visibility = _isVisible ? Visibility.Visible : Visibility.Hidden;
|
||||||
|
SetClickThrough(!_isVisible); // Make window interactive when visible
|
||||||
|
|
||||||
|
if (_targetProcess != null)
|
||||||
|
{
|
||||||
|
if (_isPaused)
|
||||||
|
WindowUtils.SuspendProcess(_targetProcess);
|
||||||
|
else
|
||||||
|
WindowUtils.ResumeProcess(_targetProcess);
|
||||||
|
|
||||||
|
UpdateOverlayText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateOverlayText()
|
||||||
|
{
|
||||||
|
StatusText.Text = _isPaused ? "PAUSED" : "Running";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Timer_Tick(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
UpdateTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTime()
|
||||||
|
{
|
||||||
|
TimeText.Text = DateTime.Now.ToString("HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnResumeClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_targetProcess != null && _isPaused)
|
||||||
|
{
|
||||||
|
_isPaused = false;
|
||||||
|
_isVisible = false;
|
||||||
|
WindowUtils.ResumeProcess(_targetProcess);
|
||||||
|
Visibility = Visibility.Hidden;
|
||||||
|
SetClickThrough(true);
|
||||||
|
UpdateOverlayText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnQuitClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosed(EventArgs e)
|
||||||
|
{
|
||||||
|
_timer.Stop();
|
||||||
|
base.OnClosed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
cleaning_bin_obj.bat
Normal file
2
cleaning_bin_obj.bat
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
@echo off
|
||||||
|
for /d /r . %%d in (bin,obj) do @if exist "%%d" rd /s/q "%%d"
|
||||||
Loading…
x
Reference in New Issue
Block a user