diff --git a/GameShuffler.sln b/GameShuffler.sln new file mode 100644 index 0000000..5d87823 --- /dev/null +++ b/GameShuffler.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33213.308 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameShuffler", "GameShuffler\GameShuffler.csproj", "{3B963663-2FDD-4E59-BB36-1F1AA1C5FE4C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3B963663-2FDD-4E59-BB36-1F1AA1C5FE4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B963663-2FDD-4E59-BB36-1F1AA1C5FE4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B963663-2FDD-4E59-BB36-1F1AA1C5FE4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B963663-2FDD-4E59-BB36-1F1AA1C5FE4C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CDDFD207-731D-46AF-B821-D5261AFD2D9F} + EndGlobalSection +EndGlobal diff --git a/GameShuffler/Form1.Designer.cs b/GameShuffler/Form1.Designer.cs new file mode 100644 index 0000000..8426e5f --- /dev/null +++ b/GameShuffler/Form1.Designer.cs @@ -0,0 +1,201 @@ +using System.Runtime.InteropServices; + +namespace GameShuffler +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + + if (disposing) + { + UnregisterHotKey(this.Handle, RemoveGameKeyId); + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.runningProcessesSelectionList = new System.Windows.Forms.CheckedListBox(); + this.label1 = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.startButton = new System.Windows.Forms.Button(); + this.label3 = new System.Windows.Forms.Label(); + this.minTimeTextBox = new System.Windows.Forms.TextBox(); + this.maxTimeTextBox = new System.Windows.Forms.TextBox(); + this.label4 = new System.Windows.Forms.Label(); + this.label5 = new System.Windows.Forms.Label(); + this.label6 = new System.Windows.Forms.Label(); + this.refreshButton = new System.Windows.Forms.Button(); + this.stopButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // runningProcessesSelectionList + // + this.runningProcessesSelectionList.FormattingEnabled = true; + this.runningProcessesSelectionList.Location = new System.Drawing.Point(40, 40); + this.runningProcessesSelectionList.Name = "runningProcessesSelectionList"; + this.runningProcessesSelectionList.Size = new System.Drawing.Size(720, 340); + this.runningProcessesSelectionList.TabIndex = 0; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(40, 9); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(200, 25); + this.label1.TabIndex = 1; + this.label1.Text = "Select games to shuffle:"; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(40, 502); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(426, 25); + this.label2.TabIndex = 2; + this.label2.Text = "Press PageDown to remove a game from the shuffle"; + // + // startButton + // + this.startButton.Location = new System.Drawing.Point(472, 497); + this.startButton.Name = "startButton"; + this.startButton.Size = new System.Drawing.Size(133, 34); + this.startButton.TabIndex = 3; + this.startButton.Text = "Start Shuffling"; + this.startButton.UseVisualStyleBackColor = true; + this.startButton.Click += new System.EventHandler(this.StartButton_Clicked); + // + // label3 + // + this.label3.AutoSize = true; + this.label3.Location = new System.Drawing.Point(40, 389); + this.label3.Name = "label3"; + this.label3.Size = new System.Drawing.Size(263, 25); + this.label3.TabIndex = 4; + this.label3.Text = "Minimum time before shuffling:"; + // + // minTimeTextBox + // + this.minTimeTextBox.Location = new System.Drawing.Point(309, 386); + this.minTimeTextBox.Name = "minTimeTextBox"; + this.minTimeTextBox.Size = new System.Drawing.Size(150, 31); + this.minTimeTextBox.TabIndex = 5; + this.minTimeTextBox.Text = "60"; + // + // maxTimeTextBox + // + this.maxTimeTextBox.Location = new System.Drawing.Point(309, 423); + this.maxTimeTextBox.Name = "maxTimeTextBox"; + this.maxTimeTextBox.Size = new System.Drawing.Size(150, 31); + this.maxTimeTextBox.TabIndex = 6; + this.maxTimeTextBox.Text = "120"; + // + // label4 + // + this.label4.AutoSize = true; + this.label4.Location = new System.Drawing.Point(40, 426); + this.label4.Name = "label4"; + this.label4.Size = new System.Drawing.Size(266, 25); + this.label4.TabIndex = 7; + this.label4.Text = "Maximum time before shuffling:"; + // + // label5 + // + this.label5.AutoSize = true; + this.label5.Location = new System.Drawing.Point(465, 389); + this.label5.Name = "label5"; + this.label5.Size = new System.Drawing.Size(77, 25); + this.label5.TabIndex = 8; + this.label5.Text = "seconds"; + // + // label6 + // + this.label6.AutoSize = true; + this.label6.Location = new System.Drawing.Point(465, 426); + this.label6.Name = "label6"; + this.label6.Size = new System.Drawing.Size(77, 25); + this.label6.TabIndex = 9; + this.label6.Text = "seconds"; + // + // refreshButton + // + this.refreshButton.Location = new System.Drawing.Point(648, 4); + this.refreshButton.Name = "refreshButton"; + this.refreshButton.Size = new System.Drawing.Size(112, 34); + this.refreshButton.TabIndex = 10; + this.refreshButton.Text = "Refresh"; + this.refreshButton.UseVisualStyleBackColor = true; + this.refreshButton.Click += new System.EventHandler(this.Refresh_Clicked); + // + // stopButton + // + this.stopButton.Enabled = false; + this.stopButton.Location = new System.Drawing.Point(611, 497); + this.stopButton.Name = "stopButton"; + this.stopButton.Size = new System.Drawing.Size(149, 34); + this.stopButton.TabIndex = 11; + this.stopButton.Text = "Stop Shuffling"; + this.stopButton.UseVisualStyleBackColor = true; + this.stopButton.Click += new System.EventHandler(this.StopButton_Clicked); + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(10F, 25F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(800, 593); + this.Controls.Add(this.stopButton); + this.Controls.Add(this.refreshButton); + this.Controls.Add(this.label6); + this.Controls.Add(this.label5); + this.Controls.Add(this.label4); + this.Controls.Add(this.maxTimeTextBox); + this.Controls.Add(this.minTimeTextBox); + this.Controls.Add(this.label3); + this.Controls.Add(this.startButton); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.Controls.Add(this.runningProcessesSelectionList); + this.Name = "Form1"; + this.Text = "Game Shuffler"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private CheckedListBox runningProcessesSelectionList; + private Label label1; + private Label label2; + private Button startButton; + private Label label3; + private TextBox minTimeTextBox; + private TextBox maxTimeTextBox; + private Label label4; + private Label label5; + private Label label6; + private Button refreshButton; + private Button stopButton; + } +} \ No newline at end of file diff --git a/GameShuffler/Form1.cs b/GameShuffler/Form1.cs new file mode 100644 index 0000000..2e1f4c2 --- /dev/null +++ b/GameShuffler/Form1.cs @@ -0,0 +1,230 @@ +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace GameShuffler +{ + public partial class Form1 : Form + { + [Flags] + public enum ThreadAccess : int + { + TERMINATE = (0x0001), + SUSPEND_RESUME = (0x0002), + GET_CONTEXT = (0x0008), + SET_CONTEXT = (0x0010), + SET_INFORMATION = (0x0020), + QUERY_INFORMATION = (0x0040), + SET_THREAD_TOKEN = (0x0080), + IMPERSONATE = (0x0100), + DIRECT_IMPERSONATION = (0x0200) + } + + [DllImport("kernel32.dll")] + static extern IntPtr OpenThread(ThreadAccess dwDesiredAccess, bool bInheritHandle, uint dwThreadId); + [DllImport("kernel32.dll")] + static extern uint SuspendThread(IntPtr hThread); + [DllImport("kernel32.dll")] + static extern int ResumeThread(IntPtr hThread); + [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] + static extern bool CloseHandle(IntPtr handle); + + [DllImport("user32.dll")] + static extern bool SetForegroundWindow(IntPtr hWnd); + [DllImport("User32.dll")] + public static extern bool ShowWindow(IntPtr handle, int nCmdShow); + + [DllImport("user32.dll")] + public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc); + [DllImport("user32.dll")] + public static extern bool UnregisterHotKey(IntPtr hWnd, int id); + + int RemoveGameKeyId = 1; + + int RemoveGameKey = (int)Keys.PageDown; + + Thread? shuffleThread = null; + bool stopShuffle = false; + + int minShuffleTime = 0; + int maxShuffleTime = 0; + List gamesToShuffle = new List(); + + Process? currentGame = null; + + Random rand = new Random(); + + public Form1() + { + InitializeComponent(); + } + + private void Refresh_Clicked(object sender, EventArgs e) + { + var allProcesses = Process.GetProcesses(); + runningProcessesSelectionList.SelectedItems.Clear(); + runningProcessesSelectionList.Items.Clear(); + runningProcessesSelectionList.Items.AddRange( + allProcesses.Where(process => !string.IsNullOrEmpty(process.MainWindowTitle)) + .Select(process => $"{process.MainWindowTitle}\t{process.Id}") + .ToArray()); + } + + private void StartButton_Clicked(object sender, EventArgs e) + { + refreshButton.Enabled = false; + runningProcessesSelectionList.Enabled = false; + startButton.Enabled = false; + minTimeTextBox.Enabled = false; + maxTimeTextBox.Enabled = false; + stopButton.Enabled = true; + if (!int.TryParse(minTimeTextBox.Text, out minShuffleTime) || minShuffleTime < 0) + { + MessageBox.Show("Minimum shuffle time must be a positive number"); + } + + if (!int.TryParse(maxTimeTextBox.Text, out maxShuffleTime) || maxShuffleTime < minShuffleTime) + { + MessageBox.Show("Maximum shuffle time must be a positive number greater than minimum shuffle time"); + } + + if (!RegisterHotKey(this.Handle, RemoveGameKeyId, 0x0000, RemoveGameKey)) + { + MessageBox.Show("Failed to initialize shuffle start key"); + } + + foreach (var processString in runningProcessesSelectionList.CheckedItems) + { + var process = Process.GetProcessById(int.Parse(((string)processString).Split("\t")[1])); + gamesToShuffle.Add(process); + } + + shuffleThread = new Thread(ShuffleLoop); + shuffleThread.Start(); + } + + private void StopButton_Clicked(object sender, EventArgs e) + { + refreshButton.Enabled = true; + runningProcessesSelectionList.Items.Clear(); + runningProcessesSelectionList.Enabled = true; + startButton.Enabled = true; + minTimeTextBox.Enabled = true; + maxTimeTextBox.Enabled = true; + stopButton.Enabled = false; + + if (shuffleThread != null) + { + stopShuffle = true; + } + + UnregisterHotKey(this.Handle, RemoveGameKeyId); + } + + private void ShuffleLoop() + { + foreach (var process in gamesToShuffle) + { + SuspendProcess(process); + } + + while (gamesToShuffle.Any() && !stopShuffle) + { + if (currentGame != null) + { + ShowWindow(currentGame.MainWindowHandle, 0); + SuspendProcess(currentGame); + } + + StartNewGame(); + + Thread.Sleep(rand.Next(minShuffleTime, maxShuffleTime)*1000); + } + + foreach (var process in gamesToShuffle) + { + if (process != currentGame) + { + ResumeProcess(process); + } + } + + stopShuffle = false; + } + + private void StartNewGame() + { + var currentGameIndex = currentGame == null ? 0 : gamesToShuffle.IndexOf(currentGame); + var newGameIndex = currentGameIndex; + while (newGameIndex == currentGameIndex) + { + newGameIndex = rand.Next(gamesToShuffle.Count); + } + var newGame = gamesToShuffle[newGameIndex]; + ResumeProcess(newGame); + ShowWindow(newGame.MainWindowHandle, 3); + SetForegroundWindow(newGame.MainWindowHandle); + currentGame = newGame; + } + + private static void SuspendProcess(Process process) + { + foreach (ProcessThread pT in process.Threads) + { + IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id); + + if (pOpenThread == IntPtr.Zero) + { + continue; + } + + SuspendThread(pOpenThread); + + CloseHandle(pOpenThread); + } + } + + public static void ResumeProcess(Process process) + { + if (process.ProcessName == string.Empty) + return; + + foreach (ProcessThread pT in process.Threads) + { + IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)pT.Id); + + if (pOpenThread == IntPtr.Zero) + { + continue; + } + + var suspendCount = 0; + do + { + suspendCount = ResumeThread(pOpenThread); + } while (suspendCount > 0); + + CloseHandle(pOpenThread); + } + } + + protected override void WndProc(ref Message m) + { + if (m.Msg == 0x0312) + { + int id = (int)m.WParam; + + if (id == RemoveGameKeyId && currentGame != null) + { + gamesToShuffle.Remove(currentGame); + currentGame.Kill(); + if (gamesToShuffle.Any()) + { + StartNewGame(); + } + } + } + + base.WndProc(ref m); + } + } +} \ No newline at end of file diff --git a/GameShuffler/Form1.resx b/GameShuffler/Form1.resx new file mode 100644 index 0000000..f298a7b --- /dev/null +++ b/GameShuffler/Form1.resx @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/GameShuffler/GameShuffler.csproj b/GameShuffler/GameShuffler.csproj new file mode 100644 index 0000000..da05a80 --- /dev/null +++ b/GameShuffler/GameShuffler.csproj @@ -0,0 +1,16 @@ + + + + WinExe + net6.0-windows + enable + true + enable + true + true + win-x64 + true + true + + + \ No newline at end of file diff --git a/GameShuffler/Program.cs b/GameShuffler/Program.cs new file mode 100644 index 0000000..4eada52 --- /dev/null +++ b/GameShuffler/Program.cs @@ -0,0 +1,17 @@ +namespace GameShuffler +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.Run(new Form1()); + } + } +} \ No newline at end of file