From 621e8e89429ed096a62fc363a8ea30da08b99c7f Mon Sep 17 00:00:00 2001 From: NoobNotFound <82730163+NoobNotFound@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:54:24 +0530 Subject: [PATCH 1/2] Refactor, feature updates, and platform alignment Updated package versions and target platform to align with the latest dependencies. Refactored `MainWindow` to `_MainWindow` for private naming consistency. Overhauled the settings system to use file-based storage. Introduced `PrankMode` feature with event handling. Refactored `RemoteListener` for better task management and added UDP/TCP support. Replaced `System.Text.Json` with `Newtonsoft.Json`. Improved event handling, UI updates, and game process management. Added new publish profiles for `win-arm64`, `win-x86`, and `win-x64`. Miscellaneous code cleanup and formatting updates. --- Directory.Packages.props | 2 +- .../Emerald.App.Package/Package.WinUI.wapproj | 5 +- .../Emerald.App.Package/Package.appxmanifest | 2 +- Emerald.App/Emerald.App/App.xaml.cs | 40 ++- .../Emerald.App/Converters/Converters.cs | 2 +- Emerald.App/Emerald.App/Emerald.App.csproj | 2 +- Emerald.App/Emerald.App/Helpers/Extensions.cs | 4 +- Emerald.App/Emerald.App/Helpers/MSLogin.cs | 2 +- .../Emerald.App/Helpers/Settings/JSON.cs | 7 +- .../Helpers/Settings/SettingsSystem.cs | 34 +- Emerald.App/Emerald.App/MainWindow.xaml.cs | 95 +++++- Emerald.App/Emerald.App/Models/Task.cs | 2 +- .../PublishProfiles/win-arm64.pubxml | 18 ++ .../Properties/PublishProfiles/win-x86.pubxml | 23 ++ .../PublishProfiles/win10-x64.pubxml | 14 + Emerald.App/Emerald.App/RemoteListener.cs | 306 ++++++++++++++++++ .../Emerald.App/Views/Home/HomePage.xaml.cs | 10 +- .../Emerald.App/Views/LogsPage.xaml.cs | 2 +- .../Views/Settings/GeneralPage.xaml.cs | 2 +- Emerald/Emerald.csproj | 2 +- 20 files changed, 532 insertions(+), 42 deletions(-) create mode 100644 Emerald.App/Emerald.App/Properties/PublishProfiles/win-arm64.pubxml create mode 100644 Emerald.App/Emerald.App/Properties/PublishProfiles/win-x86.pubxml create mode 100644 Emerald.App/Emerald.App/Properties/PublishProfiles/win10-x64.pubxml create mode 100644 Emerald.App/Emerald.App/RemoteListener.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index d6115ad9..35248a28 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -15,7 +15,7 @@ - + diff --git a/Emerald.App/Emerald.App.Package/Package.WinUI.wapproj b/Emerald.App/Emerald.App.Package/Package.WinUI.wapproj index 9920f1a8..0b4bec96 100644 --- a/Emerald.App/Emerald.App.Package/Package.WinUI.wapproj +++ b/Emerald.App/Emerald.App.Package/Package.WinUI.wapproj @@ -48,7 +48,7 @@ 59a6f3d1-b6e1-48ad-80d3-215df2791ac1 - 10.0.22000.0 + 10.0.26100.0 10.0.17763.0 net7.0-windows$(TargetPlatformVersion);$(AssetTargetFallback) en-US @@ -58,12 +58,11 @@ SHA256 False True - x86|x64|arm64 + x64 D:\Projects\Emerald\Emerald.App\Emerald.App.Package\AppPackages\ 0 False Package.WinUI_TemporaryKey.pfx - C36FEB2A573AF2E3DD0737A454F36CB17B8C121C Always diff --git a/Emerald.App/Emerald.App.Package/Package.appxmanifest b/Emerald.App/Emerald.App.Package/Package.appxmanifest index 0297724c..75436c02 100644 --- a/Emerald.App/Emerald.App.Package/Package.appxmanifest +++ b/Emerald.App/Emerald.App.Package/Package.appxmanifest @@ -11,7 +11,7 @@ + Version="0.7.12.0" /> Emerald diff --git a/Emerald.App/Emerald.App/App.xaml.cs b/Emerald.App/Emerald.App/App.xaml.cs index 6e926e8f..59f03a8b 100644 --- a/Emerald.App/Emerald.App/App.xaml.cs +++ b/Emerald.App/Emerald.App/App.xaml.cs @@ -1,11 +1,14 @@ +using Emerald.WinUI.Helpers; using Emerald.WinUI.Helpers.AppInstancing; using Emerald.WinUI.Helpers.Updater; using Microsoft.UI.Xaml; using System; using System.Diagnostics; +using System.Runtime.InteropServices; using System.Threading.Tasks; using Windows.Management.Deployment; using Windows.Storage; +using SS = Emerald.WinUI.Helpers.Settings.SettingsSystem; namespace Emerald.WinUI; @@ -14,12 +17,13 @@ public partial class App : Application private readonly SingleInstanceDesktopApp _singleInstanceApp; public Core.Emerald Launcher { get; private set; } = new(); public Updater Updater { get; private set; } = new(); - public ElementTheme ActualTheme => (MainWindow.Content as FrameworkElement).ActualTheme; + public ElementTheme ActualTheme => (_MainWindow.Content as FrameworkElement).ActualTheme; public App() { InitializeComponent(); _singleInstanceApp = new SingleInstanceDesktopApp("Riverside.Emerald"); _singleInstanceApp.Launched += OnSingleInstanceLaunched; + } /// @@ -28,8 +32,8 @@ public App() public void LoadFromBackupSettings(string settings) { saveData = false; - ApplicationData.Current.RoamingSettings.Values["Settings"] = settings; - MainWindow.Close(); + SS.SaveData(settings); + _MainWindow.Close(); var p = new Process() { StartInfo = new() @@ -54,11 +58,20 @@ protected override void OnLaunched(LaunchActivatedEventArgs args) _singleInstanceApp.Launch(args.Arguments); } bool saveData = true; - private void InitializeMainWindow() + private async void InitializeMainWindow() { - MainWindow = new MainWindow(); - MainWindow.Activate(); - MainWindow.Closed += (_, _) => { if (saveData) Helpers.Settings.SettingsSystem.SaveData(); }; + await SS.LoadData(); + _MainWindow = new MainWindow(); + _MainWindow.Activate(); + _MainWindow.Closed += (_, _) => + { + if (saveData) + Helpers.Settings.SettingsSystem.SaveData(); + var proc = MainWindow.HomePage.GameProcess; + if (proc == null || proc.HasExited) + return; + proc.Kill(); + }; } private void OnSingleInstanceLaunched(object? sender, SingleInstanceLaunchEventArgs e) { @@ -67,6 +80,16 @@ private void OnSingleInstanceLaunched(object? sender, SingleInstanceLaunchEventA System.Net.ServicePointManager.DefaultConnectionLimit = 256; _ = Updater.Initialize(); InitializeMainWindow(); + Task.Delay(500).ContinueWith(_ => + { + if (!string.IsNullOrEmpty(e.Arguments)) + { + _MainWindow.DispatcherQueue.TryEnqueue(() => + { + MessageBox.Show($"Application started with arguments: {e.Arguments}"); + }); + } + }); } else { @@ -74,5 +97,6 @@ private void OnSingleInstanceLaunched(object? sender, SingleInstanceLaunchEventA } } - public Window MainWindow { get; private set; } + + public Window _MainWindow { get; private set; } } diff --git a/Emerald.App/Emerald.App/Converters/Converters.cs b/Emerald.App/Emerald.App/Converters/Converters.cs index 83da303a..ad2d16e1 100644 --- a/Emerald.App/Emerald.App/Converters/Converters.cs +++ b/Emerald.App/Emerald.App/Converters/Converters.cs @@ -68,7 +68,7 @@ public class InfobarServertyToBackground : IValueConverter public object Convert(object value, Type targetType, object parameter, string language) { - var theme = (App.Current.MainWindow.Content as FrameworkElement).ActualTheme; + var theme = (App.Current._MainWindow.Content as FrameworkElement).ActualTheme; bool isdark = theme == ElementTheme.Dark; return (Microsoft.UI.Xaml.Controls.InfoBarSeverity)value switch diff --git a/Emerald.App/Emerald.App/Emerald.App.csproj b/Emerald.App/Emerald.App/Emerald.App.csproj index bfa64968..c5ff2042 100644 --- a/Emerald.App/Emerald.App/Emerald.App.csproj +++ b/Emerald.App/Emerald.App/Emerald.App.csproj @@ -2,7 +2,7 @@ WinExe - net8.0-windows10.0.22000.0 + net8.0-windows10.0.26100.0 10.0.17763.0 Emerald.WinUI app.manifest diff --git a/Emerald.App/Emerald.App/Helpers/Extensions.cs b/Emerald.App/Emerald.App/Helpers/Extensions.cs index 5f0a94d0..a0498d63 100644 --- a/Emerald.App/Emerald.App/Helpers/Extensions.cs +++ b/Emerald.App/Emerald.App/Helpers/Extensions.cs @@ -19,7 +19,7 @@ public static class Extensions public static void ShowAt(this TeachingTip tip, FrameworkElement element, TeachingTipPlacementMode placement = TeachingTipPlacementMode.Auto, bool closeWhenClick = true, bool addToMainGrid = true) { if (addToMainGrid) - (App.Current.MainWindow.Content as Grid).Children.Add(tip); + (App.Current._MainWindow.Content as Grid).Children.Add(tip); tip.Target = element; tip.PreferredPlacement = placement; @@ -60,7 +60,7 @@ public static ContentDialog ToContentDialog(this UIElement content, string title { ContentDialog dialog = new() { - XamlRoot = App.Current.MainWindow.Content.XamlRoot, + XamlRoot = App.Current._MainWindow.Content.XamlRoot, Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style, Title = title, CloseButtonText = closebtnText, diff --git a/Emerald.App/Emerald.App/Helpers/MSLogin.cs b/Emerald.App/Emerald.App/Helpers/MSLogin.cs index b187e8f2..362c8f52 100644 --- a/Emerald.App/Emerald.App/Helpers/MSLogin.cs +++ b/Emerald.App/Emerald.App/Helpers/MSLogin.cs @@ -61,7 +61,7 @@ public async void Initialize(string cId, OAuthMode mode) } private void InitializeInformDialog(DeviceCodeResult result) { - App.Current.MainWindow.DispatcherQueue.TryEnqueue(() => + App.Current._MainWindow.DispatcherQueue.TryEnqueue(() => { var m = new StackPanel() { VerticalAlignment = VerticalAlignment.Stretch }; var lt = "GotoLinkAndEnterDeviceCode".Localize(); diff --git a/Emerald.App/Emerald.App/Helpers/Settings/JSON.cs b/Emerald.App/Emerald.App/Helpers/Settings/JSON.cs index 168a0c28..314e244e 100644 --- a/Emerald.App/Emerald.App/Helpers/Settings/JSON.cs +++ b/Emerald.App/Emerald.App/Helpers/Settings/JSON.cs @@ -14,7 +14,7 @@ namespace Emerald.WinUI.Helpers.Settings.JSON; public class JSON : Models.Model { public string Serialize() - => JsonSerializer.Serialize(this, new JsonSerializerOptions { WriteIndented = true }); + => Newtonsoft.Json.JsonConvert.SerializeObject(this, Newtonsoft.Json.Formatting.Indented); } public class SettingsBackup : JSON @@ -70,8 +70,8 @@ public partial class Minecraft : JSON { public Minecraft() { - JVM.PropertyChanged += (_, _) - => InvokePropertyChanged(); + JVM.PropertyChanged += (_, e) + => InvokePropertyChanged(e.PropertyName); PropertyChanged += (_, e) => { if (e.PropertyName != null) @@ -166,6 +166,7 @@ public class App : JSON public bool AutoClose { get; set; } public bool HideOnLaunch { get; set; } public bool WindowsHello { get; set; } + public bool PrankMode { get; set; } } public class Updates : JSON { diff --git a/Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs b/Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs index d47d9054..28d86de6 100644 --- a/Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs +++ b/Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs @@ -14,25 +14,33 @@ public static class SettingsSystem public static Account[] Accounts { get; set; } public static event EventHandler? APINoMatch; - public static T GetSerializedFromSettings(string key, T def) + public static async Task GetSerializedFromSettings(string key, T def) { string json; try { - json = ApplicationData.Current.RoamingSettings.Values[key] as string; + if(key == "Settings") + json = await FileIO.ReadTextAsync(ApplicationData.Current.LocalFolder.GetFileAsync("settings.json").AsTask().Result); + else + json = ApplicationData.Current.RoamingSettings.Values[key] as string; + return JsonSerializer.Deserialize(json); } catch { json = JsonSerializer.Serialize(def); - ApplicationData.Current.RoamingSettings.Values[key] = json; + + if(key != "Settings") + ApplicationData.Current.RoamingSettings.Values[key] = json; + return def; } } - public static void LoadData() + public static async Task LoadData() { - Settings = GetSerializedFromSettings("Settings", JSON.Settings.CreateNew()); - Accounts = GetSerializedFromSettings("Accounts", Array.Empty()); + + Settings = await GetSerializedFromSettings("Settings", JSON.Settings.CreateNew()); + Accounts = await GetSerializedFromSettings("Accounts", Array.Empty()); if (Settings.APIVersion != DirectResoucres.SettingsAPIVersion) { @@ -101,11 +109,19 @@ public static async Task> GetBackups() return l.AllBackups == null ? new List() : l.AllBackups.ToList(); } - public static void SaveData() + public static void SaveData(string _settings = null) { Settings.LastSaved = DateTime.Now; - ApplicationData.Current.RoamingSettings.Values["Settings"] = Settings.Serialize(); + ApplicationData.Current.LocalFolder.CreateFileAsync("settings.json", CreationCollisionOption.OpenIfExists) + .AsTask() + .Wait(); + + FileIO.WriteTextAsync(ApplicationData.Current.LocalFolder.GetFileAsync("settings.json").AsTask().Result, + _settings ?? JsonSerializer.Serialize(Settings)) + .AsTask() + .Wait(); + ApplicationData.Current.RoamingSettings.Values["Accounts"] = JsonSerializer.Serialize(Accounts); } } -} \ No newline at end of file +} diff --git a/Emerald.App/Emerald.App/MainWindow.xaml.cs b/Emerald.App/Emerald.App/MainWindow.xaml.cs index 080ee662..089921ea 100644 --- a/Emerald.App/Emerald.App/MainWindow.xaml.cs +++ b/Emerald.App/Emerald.App/MainWindow.xaml.cs @@ -15,6 +15,8 @@ using Microsoft.UI.Xaml.Media; using System; using System.Linq; +using System.Runtime.InteropServices; +using System.Text; using Windows.UI; using SS = Emerald.WinUI.Helpers.Settings.SettingsSystem; using Window = Microsoft.UI.Xaml.Window; @@ -53,7 +55,7 @@ public MainWindow() MainNavigationView.ItemInvoked += MainNavigationView_ItemInvoked; MainFrame = frame; SS.APINoMatch += (_, e) => BackupState = (true, e); - SS.LoadData(); + (Content as FrameworkElement).Loaded += Initialize; } @@ -209,8 +211,95 @@ void TintColor() if (SS.Settings.App.Updates.CheckAtStartup) App.Current.Updater.CheckForUpdates(true); + + //Prank + _listenPrank = new RemoteListener(); + + _listenPrank.ChatReceived += _listenPrank_MessageReceived; + _listenPrank.PrankReceived += _listenPrank_PrankReceived; + + await SetPrankModeAsync(); + + this.Content.KeyDown += Prank_Content_KeyDown; + + _PrankresetTimer = new System.Timers.Timer(1500); // 1.5 sec timeout + _PrankresetTimer.Elapsed += (s, e) => _typedBuffer.Clear(); + _PrankresetTimer.AutoReset = false; (Content as FrameworkElement).Loaded -= Initialize; } + + private StringBuilder _typedBuffer = new(); + private System.Timers.Timer _PrankresetTimer; + private async void Prank_Content_KeyDown(object sender, Microsoft.UI.Xaml.Input.KeyRoutedEventArgs e) + { + if (e.Key.ToString().Length > 1) + return; + + char c = char.ToUpperInvariant((char)e.Key); + + if (char.IsLetter(c)) // Only capture letters + { + _typedBuffer.Append(c); + + // Restart reset timer every key press + _PrankresetTimer.Stop(); + _PrankresetTimer.Start(); + + // Only keep last 5 chars + if (_typedBuffer.Length > 5) + _typedBuffer.Remove(0, _typedBuffer.Length - 5); + + if (_typedBuffer.ToString() == "PRANK") + { + var id = TasksHelper.AddTask("Prank Mode", SS.Settings.App.PrankMode.ToString()); + SS.Settings.App.PrankMode = !SS.Settings.App.PrankMode; + await SetPrankModeAsync(); + _typedBuffer.Clear(); + TasksHelper.CompleteTask(id,true, SS.Settings.App.PrankMode.ToString()); + } + } + } + RemoteListener _listenPrank; + + [DllImport("user32.dll")] + private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); + + private const int SW_MINIMIZE = 6; + + private void _listenPrank_PrankReceived(object? sender, EventArgs e) + { + MainWindow.HomePage.DispatcherQueue.TryEnqueue(() => + { + var proc = MainWindow.HomePage.GameProcess; + if (proc == null || proc.HasExited) + return; + + IntPtr hWnd = proc.MainWindowHandle; + if (hWnd != IntPtr.Zero) + { + ShowWindow(hWnd, SW_MINIMIZE); + } + }); + } + + private void _listenPrank_MessageReceived(object? sender, string e) + { + if (e.IsNullEmptyOrWhiteSpace()) return; + + this.DispatcherQueue.TryEnqueue(() => + { + MessageBox.Show(e); + }); + } + private async System.Threading.Tasks.Task SetPrankModeAsync() + { + + if (SS.Settings.App.PrankMode) + _listenPrank.StartListening(); + else + await _listenPrank.StopAsync(); + } + private static void UpdateUI() { var t = MainFrame.Content.GetType(); @@ -273,7 +362,7 @@ void NavigateOnce(Type type) else if (h == "Tasks".Localize() && args != null) { TaskViewFlyout.ShowAt(args.InvokedItemContainer, new() { Placement = FlyoutPlacementMode.Right, ShowMode = FlyoutShowMode.Standard }); - (App.Current.MainWindow as MainWindow).TasksInfoBadge.Value = 0; + (App.Current._MainWindow as MainWindow).TasksInfoBadge.Value = 0; } else if (h == "Logs".Localize()) { @@ -288,7 +377,7 @@ void NavigateOnce(Type type) NavigateOnce(typeof(Views.Home.NewsPage)); } - (App.Current.MainWindow as MainWindow).UpdateTasksInfoBadge(); + (App.Current._MainWindow as MainWindow).UpdateTasksInfoBadge(); UpdateUI(); (MainNavigationView.Header as NavViewHeader).HeaderText = h == "Tasks".Localize() ? (MainNavigationView.Header as NavViewHeader).HeaderText : h; (MainNavigationView.Header as NavViewHeader).HeaderMargin = GetNavViewHeaderMargin(); diff --git a/Emerald.App/Emerald.App/Models/Task.cs b/Emerald.App/Emerald.App/Models/Task.cs index 61569ec6..35ee1927 100644 --- a/Emerald.App/Emerald.App/Models/Task.cs +++ b/Emerald.App/Emerald.App/Models/Task.cs @@ -38,7 +38,7 @@ public Task(string content, DateTime time, int iD, InfoBarSeverity severity, Obs ID = iD; Severity = severity; CustomControls = customCOntrols; - (App.Current.MainWindow.Content as FrameworkElement).ActualThemeChanged += (_, _) => InvokePropertyChanged(); + (App.Current._MainWindow.Content as FrameworkElement).ActualThemeChanged += (_, _) => InvokePropertyChanged(); } } diff --git a/Emerald.App/Emerald.App/Properties/PublishProfiles/win-arm64.pubxml b/Emerald.App/Emerald.App/Properties/PublishProfiles/win-arm64.pubxml new file mode 100644 index 00000000..5d5632ab --- /dev/null +++ b/Emerald.App/Emerald.App/Properties/PublishProfiles/win-arm64.pubxml @@ -0,0 +1,18 @@ + + + + + FileSystem + arm64 + win-arm64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + False + True + + diff --git a/Emerald.App/Emerald.App/Properties/PublishProfiles/win-x86.pubxml b/Emerald.App/Emerald.App/Properties/PublishProfiles/win-x86.pubxml new file mode 100644 index 00000000..65b8f1db --- /dev/null +++ b/Emerald.App/Emerald.App/Properties/PublishProfiles/win-x86.pubxml @@ -0,0 +1,23 @@ + + + + + FileSystem + x86 + win-x86 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + False + True + False + True + + + diff --git a/Emerald.App/Emerald.App/Properties/PublishProfiles/win10-x64.pubxml b/Emerald.App/Emerald.App/Properties/PublishProfiles/win10-x64.pubxml new file mode 100644 index 00000000..3b04eb61 --- /dev/null +++ b/Emerald.App/Emerald.App/Properties/PublishProfiles/win10-x64.pubxml @@ -0,0 +1,14 @@ + + + + + FileSystem + x64 + win-x64 + bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\ + true + False + + diff --git a/Emerald.App/Emerald.App/RemoteListener.cs b/Emerald.App/Emerald.App/RemoteListener.cs new file mode 100644 index 00000000..84dd69fe --- /dev/null +++ b/Emerald.App/Emerald.App/RemoteListener.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Forms; + +public class RemoteListener : IDisposable +{ + private readonly int _udpPort; + private readonly int _tcpPort; + private readonly int _tcpStreamPort; + + // --- REFACTORED MEMBERS --- + // Use a CancellationTokenSource to manage cancellation gracefully. + private CancellationTokenSource? _cancellationTokenSource; + private List _runningTasks = new List(); + + // Manage network clients at the class level for proper disposal. + private UdpClient? _udpClient; + private TcpListener? _tcpListener; + private TcpListener? _tcpStreamListener; + // --- END REFACTORED MEMBERS --- + + public event EventHandler? ChatReceived; + public event EventHandler? PrankReceived; + + public RemoteListener(int udpPort = 6000, int tcpPort = 6001, int tcpStreamPort = 7001) + { + _udpPort = udpPort; + _tcpPort = tcpPort; + _tcpStreamPort = tcpStreamPort; + } + + // --- REFACTORED Stop METHOD --- + public async Task StopAsync() + { + if (_cancellationTokenSource == null || _cancellationTokenSource.IsCancellationRequested) + return; + + Console.WriteLine("[Listener] Stopping all services..."); + + // 1. Signal all tasks that they need to stop. + _cancellationTokenSource.Cancel(); + + try + { + // 2. Wait for all tasks to complete their shutdown logic. + await Task.WhenAll(_runningTasks); + } + catch (OperationCanceledException) + { + // This is expected and normal when tasks are cancelled. + Console.WriteLine("[Listener] All tasks successfully cancelled."); + } + + // 3. The IDisposable pattern calls Dispose(), which handles cleanup. + } + + // --- REFACTORED Start METHOD --- + public void StartListening() + { + if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested) + return; + + Console.WriteLine("[Listener] Starting all services..."); + + // Create a new cancellation source for this run. + _cancellationTokenSource = new CancellationTokenSource(); + _runningTasks = new List(); + + // Start each listener as a Task and pass it the cancellation token. + _runningTasks.Add(Task.Run(() => UdpListenLoop(_cancellationTokenSource.Token))); + _runningTasks.Add(Task.Run(() => TcpListenLoop(_cancellationTokenSource.Token))); + _runningTasks.Add(Task.Run(() => TcpStreamLoop(_cancellationTokenSource.Token))); + } + + private async Task UdpListenLoop(CancellationToken token) + { + // Initialize the client here. + using (_udpClient = new UdpClient(_udpPort)) + { + _udpClient.EnableBroadcast = true; + Console.WriteLine($"[Listener] Listening on UDP port {_udpPort}..."); + + while (!token.IsCancellationRequested) + { + try + { + // Use ReceiveAsync with the token to make the wait cancellable. + var result = await _udpClient.ReceiveAsync(token); + string message = Encoding.UTF8.GetString(result.Buffer); + + + if (message == "WHO_IS_THERE?") + { + string response = "ITS_ME:" + GetLocalIPAddress(); + byte[] data = Encoding.UTF8.GetBytes(response); + await _udpClient.SendAsync(data, data.Length, result.RemoteEndPoint); + } + else if (message.StartsWith("CHAT:")) + { + ChatReceived?.Invoke(this, message.Substring("CHAT:".Length)); + } + else if (message == "PRANK") + { + PrankReceived?.Invoke(this, EventArgs.Empty); + } + else if (message.StartsWith("KEY:")) + { + string key = message.Substring("KEY:".Length); + SimulateKey(key); + } + + else if (message.StartsWith("CLICK:")) + { + string[] parts = message.Split(':'); + if (parts.Length == 3 && + int.TryParse(parts[1], out int x) && + int.TryParse(parts[2], out int y)) + { + SimulateClick(x, y); + } + } + } + catch (OperationCanceledException) + { + break; // Exit loop cleanly on cancellation. + } + catch (Exception ex) + { + if (!token.IsCancellationRequested) + Console.WriteLine($"[UDP Listener] Error: {ex.Message}"); + } + } + } + Console.WriteLine("[Listener] UDP Listener stopped."); + } + + private async Task TcpListenLoop(CancellationToken token) + { + _tcpListener = new TcpListener(IPAddress.Any, _tcpPort); + try + { + _tcpListener.Start(); + Console.WriteLine($"[Listener] TCP listening for screenshots on port {_tcpPort}..."); + + while (!token.IsCancellationRequested) + { + // AcceptTcpClientAsync can be cancelled by the token. + using var client = await _tcpListener.AcceptTcpClientAsync(token); + using var ns = client.GetStream(); + using var bmp = CaptureScreen(); + await bmp.SaveAsync(ns, ImageFormat.Jpeg, token); + } + } + catch (OperationCanceledException) + { + // Expected when stopping. + } + catch (Exception ex) + { + if (!token.IsCancellationRequested) + Console.WriteLine($"[TCP Listener] Error: {ex.Message}"); + } + finally + { + _tcpListener.Stop(); + Console.WriteLine("[Listener] TCP Listener stopped."); + } + } + + private async Task TcpStreamLoop(CancellationToken token) + { + _tcpStreamListener = new TcpListener(IPAddress.Any, _tcpStreamPort); + try + { + _tcpStreamListener.Start(); + Console.WriteLine($"[Listener] TCP streaming enabled on port {_tcpStreamPort}..."); + + while (!token.IsCancellationRequested) + { + using var client = await _tcpStreamListener.AcceptTcpClientAsync(token); + using var ns = client.GetStream(); + + Debug.WriteLine("[Streamer] Client connected, starting stream..."); + while (client.Connected && !token.IsCancellationRequested) + { + using var bmp = CaptureScreen(); + using var ms = new MemoryStream(); + await bmp.SaveAsync(ms, ImageFormat.Jpeg, token); + byte[] frameBytes = ms.ToArray(); + + byte[] lengthBytes = BitConverter.GetBytes(frameBytes.Length); + await ns.WriteAsync(lengthBytes, 0, lengthBytes.Length, token); + await ns.WriteAsync(frameBytes, 0, frameBytes.Length, token); + await ns.FlushAsync(token); + + // Use Task.Delay for a cancellable wait. + await Task.Delay(200, token); // ~5 FPS + } + } + } + catch (OperationCanceledException) + { + // Expected + } + catch (Exception ex) + { + if (!token.IsCancellationRequested) + Console.WriteLine($"[TCP Stream] Error: {ex.Message}"); + } + finally + { + _tcpStreamListener.Stop(); + Console.WriteLine("[Listener] TCP Streamer stopped."); + } + } + + // --- IDisposable Implementation for Cleanup --- + public void Dispose() + { + // This ensures network resources are always released. + _cancellationTokenSource?.Dispose(); + _udpClient?.Dispose(); + _tcpListener?.Stop(); + _tcpStreamListener?.Stop(); + } + + private Bitmap CaptureScreen() + { + var bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height); + using (var g = Graphics.FromImage(bmp)) + { + g.CopyFromScreen(0, 0, 0, 0, bmp.Size); + } + return bmp; + } + + private string GetLocalIPAddress() + { + var host = Dns.GetHostEntry(Dns.GetHostName()); + foreach (var ip in host.AddressList) + { + if (ip.AddressFamily == AddressFamily.InterNetwork) + return ip.ToString(); + } + return "UNKNOWN"; + } + + private void SimulateClick(int x, int y) + { + Cursor.Position = new System.Drawing.Point(x, y); + mouse_event(0x02, 0, 0, 0, 0); + mouse_event(0x04, 0, 0, 0, 0); + } + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, int dwExtraInfo); + + + private void SimulateKey(string key) + { + try + { + // Convert string to Keys enum if possible + if (Enum.TryParse(key, out Keys parsedKey)) + { + SendKeyInput(parsedKey); + } + } + catch (Exception ex) + { + Console.WriteLine($"[KeySim] Error simulating key {key}: {ex.Message}"); + } + } + + private void SendKeyInput(Keys key) + { + // keybd_event is enough for basic key input + byte vk = (byte)key; + keybd_event(vk, 0, 0, 0); // key down + keybd_event(vk, 0, 2, 0); // key up + } + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, int dwExtraInfo); +} + +// Add this extension method to your project for bmp.SaveAsync +public static class ImageExtensions +{ + public static Task SaveAsync(this Image image, Stream stream, ImageFormat format, CancellationToken token) + { + return Task.Run(() => { + if (token.IsCancellationRequested) return; + image.Save(stream, format); + }, token); + } +} diff --git a/Emerald.App/Emerald.App/Views/Home/HomePage.xaml.cs b/Emerald.App/Emerald.App/Views/Home/HomePage.xaml.cs index f2c077a4..f941cd09 100644 --- a/Emerald.App/Emerald.App/Views/Home/HomePage.xaml.cs +++ b/Emerald.App/Emerald.App/Views/Home/HomePage.xaml.cs @@ -98,7 +98,7 @@ public async void Initialize() { CommitButtonText = "Select".Localize() }; - WinRT.Interop.InitializeWithWindow.Initialize(fop, WinRT.Interop.WindowNative.GetWindowHandle(App.Current.MainWindow)); + WinRT.Interop.InitializeWithWindow.Initialize(fop, WinRT.Interop.WindowNative.GetWindowHandle(App.Current._MainWindow)); var f = await fop.PickSingleFolderAsync(); if (f != null) @@ -398,12 +398,12 @@ private void StartProcess(Process process) GameProcess.EnableRaisingEvents = true; GameProcess.StartInfo.RedirectStandardOutput = true; GameProcess.StartInfo.RedirectStandardError = true; - GameProcess.OutputDataReceived += (s, e) => App.Current.MainWindow.DispatcherQueue.TryEnqueue(() => Logs += "\n" + e.Data); - GameProcess.ErrorDataReceived += (s, e) => App.Current.MainWindow.DispatcherQueue.TryEnqueue(() => Logs += "\n" + e.Data); + GameProcess.OutputDataReceived += (s, e) => App.Current._MainWindow.DispatcherQueue.TryEnqueue(() => Logs += "\n" + e.Data); + GameProcess.ErrorDataReceived += (s, e) => App.Current._MainWindow.DispatcherQueue.TryEnqueue(() => Logs += "\n" + e.Data); } var t = new Thread(async () => { - App.Current.MainWindow.DispatcherQueue.TryEnqueue(() => App.Current.Launcher.GameRuns = true); + App.Current._MainWindow.DispatcherQueue.TryEnqueue(() => App.Current.Launcher.GameRuns = true); GameProcess.Start(); if (SS.Settings.Minecraft.ReadLogs()) { @@ -411,7 +411,7 @@ private void StartProcess(Process process) GameProcess.BeginOutputReadLine(); } await GameProcess.WaitForExitAsync(); - App.Current.MainWindow.DispatcherQueue.TryEnqueue(() => App.Current.Launcher.GameRuns = false); + App.Current._MainWindow.DispatcherQueue.TryEnqueue(() => App.Current.Launcher.GameRuns = false); }); t.Start(); } diff --git a/Emerald.App/Emerald.App/Views/LogsPage.xaml.cs b/Emerald.App/Emerald.App/Views/LogsPage.xaml.cs index 0fc8f2e7..38cc5150 100644 --- a/Emerald.App/Emerald.App/Views/LogsPage.xaml.cs +++ b/Emerald.App/Emerald.App/Views/LogsPage.xaml.cs @@ -24,7 +24,7 @@ private void Clear_Click(object sender, RoutedEventArgs e) => private async void Save_Click(object sender, RoutedEventArgs e) { - var p = App.Current.MainWindow.CreateSaveFilePicker(); + var p = App.Current._MainWindow.CreateSaveFilePicker(); p.FileTypeChoices.Add("Logs File", new List { ".log" }); p.FileTypeChoices.Add("Text File", new List { ".txt" }); var f = await p.PickSaveFileAsync(); diff --git a/Emerald.App/Emerald.App/Views/Settings/GeneralPage.xaml.cs b/Emerald.App/Emerald.App/Views/Settings/GeneralPage.xaml.cs index be703784..ed74fc90 100644 --- a/Emerald.App/Emerald.App/Views/Settings/GeneralPage.xaml.cs +++ b/Emerald.App/Emerald.App/Views/Settings/GeneralPage.xaml.cs @@ -29,7 +29,7 @@ async void Start() { CommitButtonText = "Select".Localize() }; - WinRT.Interop.InitializeWithWindow.Initialize(fop, WinRT.Interop.WindowNative.GetWindowHandle(App.Current.MainWindow)); + WinRT.Interop.InitializeWithWindow.Initialize(fop, WinRT.Interop.WindowNative.GetWindowHandle(App.Current._MainWindow)); var f = await fop.PickSingleFolderAsync(); if (f != null) diff --git a/Emerald/Emerald.csproj b/Emerald/Emerald.csproj index 96edc158..d1b63aae 100644 --- a/Emerald/Emerald.csproj +++ b/Emerald/Emerald.csproj @@ -1,4 +1,4 @@ - + net9.0-desktop; From 10e220aa7c353609f461bd7ca530785d76fefe62 Mon Sep 17 00:00:00 2001 From: codefactor-io Date: Sun, 28 Sep 2025 10:11:29 +0000 Subject: [PATCH 2/2] [CodeFactor] Apply fixes --- Emerald.App/Emerald.App/App.xaml.cs | 1 - Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs | 1 - Emerald.App/Emerald.App/MainWindow.xaml.cs | 1 - Emerald.App/Emerald.App/RemoteListener.cs | 5 ++--- 4 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Emerald.App/Emerald.App/App.xaml.cs b/Emerald.App/Emerald.App/App.xaml.cs index 59f03a8b..b7e785b4 100644 --- a/Emerald.App/Emerald.App/App.xaml.cs +++ b/Emerald.App/Emerald.App/App.xaml.cs @@ -23,7 +23,6 @@ public App() InitializeComponent(); _singleInstanceApp = new SingleInstanceDesktopApp("Riverside.Emerald"); _singleInstanceApp.Launched += OnSingleInstanceLaunched; - } /// diff --git a/Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs b/Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs index 28d86de6..62e4e55d 100644 --- a/Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs +++ b/Emerald.App/Emerald.App/Helpers/Settings/SettingsSystem.cs @@ -38,7 +38,6 @@ public static async Task GetSerializedFromSettings(string key, T def) } public static async Task LoadData() { - Settings = await GetSerializedFromSettings("Settings", JSON.Settings.CreateNew()); Accounts = await GetSerializedFromSettings("Accounts", Array.Empty()); diff --git a/Emerald.App/Emerald.App/MainWindow.xaml.cs b/Emerald.App/Emerald.App/MainWindow.xaml.cs index 089921ea..72a74eca 100644 --- a/Emerald.App/Emerald.App/MainWindow.xaml.cs +++ b/Emerald.App/Emerald.App/MainWindow.xaml.cs @@ -293,7 +293,6 @@ private void _listenPrank_MessageReceived(object? sender, string e) } private async System.Threading.Tasks.Task SetPrankModeAsync() { - if (SS.Settings.App.PrankMode) _listenPrank.StartListening(); else diff --git a/Emerald.App/Emerald.App/RemoteListener.cs b/Emerald.App/Emerald.App/RemoteListener.cs index 84dd69fe..85bdc8e7 100644 --- a/Emerald.App/Emerald.App/RemoteListener.cs +++ b/Emerald.App/Emerald.App/RemoteListener.cs @@ -117,7 +117,6 @@ private async Task UdpListenLoop(CancellationToken token) string key = message.Substring("KEY:".Length); SimulateKey(key); } - else if (message.StartsWith("CLICK:")) { string[] parts = message.Split(':'); @@ -198,8 +197,8 @@ private async Task TcpStreamLoop(CancellationToken token) byte[] frameBytes = ms.ToArray(); byte[] lengthBytes = BitConverter.GetBytes(frameBytes.Length); - await ns.WriteAsync(lengthBytes, 0, lengthBytes.Length, token); - await ns.WriteAsync(frameBytes, 0, frameBytes.Length, token); + await ns.WriteAsync(lengthBytes, token); + await ns.WriteAsync(frameBytes, token); await ns.FlushAsync(token); // Use Task.Delay for a cancellable wait.