cemu-DS4Windows/DS4Windows/Program.cs

306 lines
12 KiB
C#
Raw Permalink Normal View History

using System;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
2018-07-26 09:42:28 +02:00
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.
2017-07-03 17:31:58 +02:00
private static string SingleAppComEventName = "{a52b5b20-d9ee-4f32-8518-307fa14aa0c6}";
private static EventWaitHandle threadComEvent = null;
2017-07-03 17:31:58 +02:00
private static bool exitComThread = false;
public static ControlService rootHub;
2017-09-06 04:28:54 +02:00
private static Thread testThread;
2017-09-08 08:29:35 +02:00
private static Thread controlThread;
private static System.Threading.Timer collectTimer;
2019-01-16 21:12:28 +01:00
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;
/// <summary>
/// The main entry point for the application.
/// </summary>
[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();
2019-04-30 21:29:50 +02:00
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;
}
}
2017-06-20 06:37:08 +02:00
try
{
2018-07-26 09:42:28 +02:00
Process.GetCurrentProcess().PriorityClass =
System.Diagnostics.ProcessPriorityClass.High;
}
2018-07-26 09:42:28 +02:00
catch { } // Ignore problems raising the priority.
2017-04-21 05:09:08 +02:00
// 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.
2018-07-26 10:23:38 +02:00
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.
}
2018-07-26 09:42:28 +02:00
catch { /* don't care about errors */ }
2017-04-21 05:09:08 +02:00
// Create the Event handle
2017-07-03 17:31:58 +02:00
threadComEvent = new EventWaitHandle(false, EventResetMode.ManualReset, SingleAppComEventName);
2017-09-06 04:28:54 +02:00
//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();
2017-07-03 10:24:54 +02:00
//if (mutex.WaitOne(TimeSpan.Zero, true))
//{
createControlService();
//rootHub = new ControlService();
Application.EnableVisualStyles();
2019-04-30 21:29:50 +02:00
ds4form = new Forms.DS4Form(args);
2018-11-17 15:57:18 +01:00
Application.Run();
2017-07-03 17:31:58 +02:00
//mutex.ReleaseMutex();
2017-07-03 10:24:54 +02:00
//}
2017-07-03 17:31:58 +02:00
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);
});
2017-09-08 08:29:35 +02:00
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);
}
2017-09-06 04:28:54 +02:00
private static void CreateTempWorkerThread()
{
testThread = new Thread(SingleAppComThread_DoWork);
2017-09-06 04:28:54 +02:00
testThread.Priority = ThreadPriority.Lowest;
testThread.IsBackground = true;
testThread.Start();
}
private static void SingleAppComThread_DoWork()
{
2017-07-03 17:31:58 +02:00
while (!exitComThread)
{
// check for a signal.
2018-07-26 08:10:53 +02:00
if (threadComEvent.WaitOne())
{
2017-07-03 17:31:58 +02:00
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);
}
}
}
}
/// <summary>
/// When this method is called using a Invoke then this runs in the thread
/// that created the form, which is nice.
/// </summary>
/// <param name="frm"></param>
private delegate void SetFormVisableDelegate(Form frm);
private static void ThreadFormVisable(Form frm)
{
2019-04-30 21:29:50 +02:00
if (frm is Forms.DS4Form)
{
// display the form and bring to foreground.
2019-04-30 21:29:50 +02:00
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;
}
}
}