using System; using System.Windows.Forms; using System.Threading; using System.Runtime.InteropServices; using Process = System.Diagnostics.Process; using System.ComponentModel; using System.Globalization; using Microsoft.Win32.TaskScheduler; using System.IO.MemoryMappedFiles; using System.Text; namespace DS4Windows { [System.Security.SuppressUnmanagedCodeSecurity] static class Program { [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount); [DllImport("user32.dll", EntryPoint = "FindWindow")] private static extern IntPtr FindWindow(string sClass, string sWindow); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)] private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, ref COPYDATASTRUCT lParam); [StructLayout(LayoutKind.Sequential)] public struct COPYDATASTRUCT { public IntPtr dwData; public int cbData; public IntPtr lpData; } // Add "global\" in front of the EventName, then only one instance is allowed on the // whole system, including other users. But the application can not be brought // into view, of course. private static string SingleAppComEventName = "{a52b5b20-d9ee-4f32-8518-307fa14aa0c6}"; private static EventWaitHandle threadComEvent = null; private static bool exitComThread = false; public static ControlService rootHub; private static Thread testThread; private static Thread controlThread; private static System.Threading.Timer collectTimer; private static Form ds4form; private static MemoryMappedFile ipcClassNameMMF = null; // MemoryMappedFile for inter-process communication used to hold className of DS4Form window private static MemoryMappedViewAccessor ipcClassNameMMA = null; /// /// The main entry point for the application. /// [STAThread] static void Main(string[] args) { //Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo("ja"); for (int i = 0, argsLen = args.Length; i < argsLen; i++) { string s = args[i]; if (s == "driverinstall" || s == "-driverinstall") { Application.EnableVisualStyles(); Application.Run(new Forms.WelcomeDialog(true)); return; } else if (s == "re-enabledevice" || s == "-re-enabledevice") { try { i++; string deviceInstanceId = args[i]; DS4Devices.reEnableDevice(deviceInstanceId); Environment.ExitCode = 0; return; } catch (Exception) { Environment.ExitCode = Marshal.GetLastWin32Error(); return; } } else if (s == "runtask" || s == "-runtask") { TaskService ts = new TaskService(); Task tasker = ts.FindTask("RunDS4Windows"); if (tasker != null) { tasker.Run(""); } Environment.ExitCode = 0; return; } else if (s == "command" || s == "-command") { i++; IntPtr hWndDS4WindowsForm = IntPtr.Zero; if (args[i].Length > 0 && args[i].Length <= 256) { // Find the main DS4Form window handle and post WM_COPYDATA inter-process message. IPCClasName.dat file was created by the main DS4Windows process // and it contains the name of the DS4Form .NET window class name string (the hash key part of the string is dynamically generated by .NET engine and it may change, // so that's why the main process re-creates the file if the window class name is changed by .NET framework). Finding the WND handle usig both class name and window name // limits chances that WM_COPYDATA message is sent to a wrong window. hWndDS4WindowsForm = FindWindow(ReadIPCClassNameMMF(), "DS4Windows"); if (hWndDS4WindowsForm != IntPtr.Zero) { COPYDATASTRUCT cds; cds.lpData = IntPtr.Zero; try { cds.dwData = IntPtr.Zero; cds.cbData = args[i].Length; cds.lpData = Marshal.StringToHGlobalAnsi(args[i]); SendMessage(hWndDS4WindowsForm, Forms.DS4Form.WM_COPYDATA, IntPtr.Zero, ref cds); } finally { if(cds.lpData != IntPtr.Zero) Marshal.FreeHGlobal(cds.lpData); } } else { Environment.ExitCode = 2; } } else { Environment.ExitCode = 1; } return; } } try { Process.GetCurrentProcess().PriorityClass = System.Diagnostics.ProcessPriorityClass.High; } catch { } // Ignore problems raising the priority. // Force Normal IO Priority IntPtr ioPrio = new IntPtr(2); Util.NtSetInformationProcess(Process.GetCurrentProcess().Handle, Util.PROCESS_INFORMATION_CLASS.ProcessIoPriority, ref ioPrio, 4); // Force Normal Page Priority IntPtr pagePrio = new IntPtr(5); Util.NtSetInformationProcess(Process.GetCurrentProcess().Handle, Util.PROCESS_INFORMATION_CLASS.ProcessPagePriority, ref pagePrio, 4); try { // another instance is already running if OpenExsting succeeds. threadComEvent = EventWaitHandle.OpenExisting(SingleAppComEventName, System.Security.AccessControl.EventWaitHandleRights.Synchronize | System.Security.AccessControl.EventWaitHandleRights.Modify); threadComEvent.Set(); // signal the other instance. threadComEvent.Close(); return; // return immediatly. } catch { /* don't care about errors */ } // Create the Event handle threadComEvent = new EventWaitHandle(false, EventResetMode.ManualReset, SingleAppComEventName); //System.Threading.Tasks.Task.Run(() => CreateTempWorkerThread()); //CreateInterAppComThread(); CreateTempWorkerThread(); //System.Threading.Tasks.Task.Run(() => { Thread.CurrentThread.Priority = ThreadPriority.Lowest; CreateInterAppComThread(); Thread.CurrentThread.Priority = ThreadPriority.Lowest; }).Wait(); //if (mutex.WaitOne(TimeSpan.Zero, true)) //{ createControlService(); //rootHub = new ControlService(); Application.EnableVisualStyles(); ds4form = new Forms.DS4Form(args); Application.Run(); //mutex.ReleaseMutex(); //} exitComThread = true; threadComEvent.Set(); // signal the other instance. while (testThread.IsAlive) Thread.SpinWait(500); threadComEvent.Close(); if (ipcClassNameMMA != null) ipcClassNameMMA.Dispose(); if (ipcClassNameMMF != null) ipcClassNameMMF.Dispose(); } private static void createControlService() { controlThread = new Thread(() => { rootHub = new ControlService(); collectTimer = new System.Threading.Timer(GarbageTask, null, 30000, 30000); }); controlThread.Priority = ThreadPriority.Normal; controlThread.IsBackground = true; controlThread.Start(); while (controlThread.IsAlive) Thread.SpinWait(500); } private static void GarbageTask(object state) { GC.Collect(0, GCCollectionMode.Forced, false); } private static void CreateTempWorkerThread() { testThread = new Thread(SingleAppComThread_DoWork); testThread.Priority = ThreadPriority.Lowest; testThread.IsBackground = true; testThread.Start(); } private static void SingleAppComThread_DoWork() { while (!exitComThread) { // check for a signal. if (threadComEvent.WaitOne()) { threadComEvent.Reset(); // The user tried to start another instance. We can't allow that, // so bring the other instance back into view and enable that one. // That form is created in another thread, so we need some thread sync magic. if (!exitComThread) { ds4form?.Invoke(new SetFormVisableDelegate(ThreadFormVisable), ds4form); } } } } /// /// When this method is called using a Invoke then this runs in the thread /// that created the form, which is nice. /// /// private delegate void SetFormVisableDelegate(Form frm); private static void ThreadFormVisable(Form frm) { if (frm is Forms.DS4Form) { // display the form and bring to foreground. Forms.DS4Form temp = (Forms.DS4Form)frm; temp.Show(); temp.WindowState = FormWindowState.Normal; } } public static void CreateIPCClassNameMMF(IntPtr hWnd) { if (ipcClassNameMMA != null) return; // Already holding a handle to MMF file. No need to re-write the data try { StringBuilder wndClassNameStr = new StringBuilder(128); if (GetClassName(hWnd, wndClassNameStr, wndClassNameStr.Capacity) != 0 && wndClassNameStr.Length > 0) { byte[] buffer = ASCIIEncoding.ASCII.GetBytes(wndClassNameStr.ToString()); ipcClassNameMMF = MemoryMappedFile.CreateNew("DS4Windows_IPCClassName.dat", 128); ipcClassNameMMA = ipcClassNameMMF.CreateViewAccessor(0, buffer.Length); ipcClassNameMMA.WriteArray(0, buffer, 0, buffer.Length); // The MMF file is alive as long this process holds the file handle open } } catch (Exception) { /* Eat all exceptions because errors here are not fatal for DS4Win */ } } private static string ReadIPCClassNameMMF() { MemoryMappedFile mmf = null; MemoryMappedViewAccessor mma = null; try { byte[] buffer = new byte[128]; mmf = MemoryMappedFile.OpenExisting("DS4Windows_IPCClassName.dat"); mma = mmf.CreateViewAccessor(0, 128); mma.ReadArray(0, buffer, 0, buffer.Length); return ASCIIEncoding.ASCII.GetString(buffer); } catch (Exception) { // Eat all exceptions } finally { if (mma != null) mma.Dispose(); if (mmf != null) mmf.Dispose(); } return null; } } }