cemu-DS4Windows/DS4Windows/App.xaml.cs
Travis Nickles cb871f7606 Added extra full shutdown check.
Fixes exclusive mode issues for non-admin users. Related to issue #1071.
2020-01-28 09:12:08 -06:00

484 lines
18 KiB
C#

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Net.Http;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using WPFLocalizeExtension.Engine;
using NLog;
namespace DS4WinWPF
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
[System.Security.SuppressUnmanagedCodeSecurity]
public partial class App : Application
{
[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;
}
private Thread controlThread;
public static DS4Windows.ControlService rootHub;
public static HttpClient requestClient;
private bool skipSave;
private bool runShutdown;
private bool exitApp;
private Thread testThread;
private bool exitComThread = false;
private const string SingleAppComEventName = "{a52b5b20-d9ee-4f32-8518-307fa14aa0c6}";
private EventWaitHandle threadComEvent = null;
private Timer collectTimer;
private static LoggerHolder logHolder;
private MemoryMappedFile ipcClassNameMMF = null; // MemoryMappedFile for inter-process communication used to hold className of DS4Form window
private MemoryMappedViewAccessor ipcClassNameMMA = null;
private void Application_Startup(object sender, StartupEventArgs e)
{
runShutdown = true;
skipSave = true;
ArgumentParser parser = new ArgumentParser();
parser.Parse(e.Args);
CheckOptions(parser);
if (exitApp)
{
return;
}
try
{
Process.GetCurrentProcess().PriorityClass =
ProcessPriorityClass.High;
}
catch { } // Ignore problems raising the priority.
// Force Normal IO Priority
IntPtr ioPrio = new IntPtr(2);
DS4Windows.Util.NtSetInformationProcess(Process.GetCurrentProcess().Handle,
DS4Windows.Util.PROCESS_INFORMATION_CLASS.ProcessIoPriority, ref ioPrio, 4);
// Force Normal Page Priority
IntPtr pagePrio = new IntPtr(5);
DS4Windows.Util.NtSetInformationProcess(Process.GetCurrentProcess().Handle,
DS4Windows.Util.PROCESS_INFORMATION_CLASS.ProcessPagePriority, ref pagePrio, 4);
try
{
// another instance is already running if OpenExisting succeeds.
threadComEvent = EventWaitHandle.OpenExisting(SingleAppComEventName,
System.Security.AccessControl.EventWaitHandleRights.Synchronize |
System.Security.AccessControl.EventWaitHandleRights.Modify);
threadComEvent.Set(); // signal the other instance.
threadComEvent.Close();
Current.Shutdown(); // Quit temp instance
return;
}
catch { /* don't care about errors */ }
// Create the Event handle
threadComEvent = new EventWaitHandle(false, EventResetMode.ManualReset, SingleAppComEventName);
CreateTempWorkerThread();
CreateControlService();
DS4Windows.Global.FindConfigLocation();
bool firstRun = DS4Windows.Global.firstRun;
if (firstRun)
{
DS4Forms.SaveWhere savewh = new DS4Forms.SaveWhere(false);
savewh.ShowDialog();
}
DS4Windows.Global.Load();
if (!CreateConfDirSkeleton())
{
MessageBox.Show($"Cannot create config folder structure in {DS4Windows.Global.appdatapath}. Exiting",
"DS4Windows", MessageBoxButton.OK, MessageBoxImage.Error);
Current.Shutdown(1);
}
logHolder = new LoggerHolder(rootHub);
DispatcherUnhandledException += App_DispatcherUnhandledException;
Logger logger = logHolder.Logger;
string version = DS4Windows.Global.exeversion;
logger.Info($"DS4Windows version {version}");
//logger.Info("DS4Windows version 2.0");
logger.Info("Logger created");
//DS4Windows.Global.ProfilePath[0] = "mixed";
//DS4Windows.Global.LoadProfile(0, false, rootHub, false, false);
if (firstRun)
{
logger.Info("No config found. Creating default config");
//Directory.CreateDirectory(DS4Windows.Global.appdatapath);
AttemptSave();
//Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Profiles\");
//Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Macros\");
DS4Windows.Global.SaveProfile(0, "Default");
DS4Windows.Global.ProfilePath[0] = DS4Windows.Global.OlderProfilePath[0] = "Default";
/*DS4Windows.Global.ProfilePath[1] = DS4Windows.Global.OlderProfilePath[1] = "Default";
DS4Windows.Global.ProfilePath[2] = DS4Windows.Global.OlderProfilePath[2] = "Default";
DS4Windows.Global.ProfilePath[3] = DS4Windows.Global.OlderProfilePath[3] = "Default";
*/
logger.Info("Default config created");
}
skipSave = false;
if (!DS4Windows.Global.LoadActions())
{
DS4Windows.Global.CreateStdActions();
}
SetUICulture(DS4Windows.Global.UseLang);
DS4Windows.Global.LoadLinkedProfiles();
rootHub.LaunchHidGuardHelper();
DS4Forms.MainWindow window = new DS4Forms.MainWindow(parser);
MainWindow = window;
window.Show();
window.CheckMinStatus();
HwndSource source = PresentationSource.FromVisual(window) as HwndSource;
CreateIPCClassNameMMF(source.Handle);
}
private void App_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
//Console.WriteLine("App Crashed");
//Console.WriteLine(e.Exception.StackTrace);
Logger logger = logHolder.Logger;
logger.Error($"App Crashed with message {e.Exception.Message}");
logger.Error(e.Exception.ToString());
LogManager.Flush();
LogManager.Shutdown();
}
private bool CreateConfDirSkeleton()
{
bool result = true;
try
{
Directory.CreateDirectory(DS4Windows.Global.appdatapath);
Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Profiles\");
Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Logs\");
//Directory.CreateDirectory(DS4Windows.Global.appdatapath + @"\Macros\");
}
catch (UnauthorizedAccessException)
{
result = false;
}
return result;
}
private void AttemptSave()
{
if (!DS4Windows.Global.Save()) //if can't write to file
{
if (MessageBox.Show("Cannot write at current location\nCopy Settings to appdata?", "DS4Windows",
MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
{
try
{
Directory.CreateDirectory(DS4Windows.Global.appDataPpath);
File.Copy(DS4Windows.Global.exedirpath + "\\Profiles.xml",
DS4Windows.Global.appDataPpath + "\\Profiles.xml");
File.Copy(DS4Windows.Global.exedirpath + "\\Auto Profiles.xml",
DS4Windows.Global.appDataPpath + "\\Auto Profiles.xml");
Directory.CreateDirectory(DS4Windows.Global.appDataPpath + "\\Profiles");
foreach (string s in Directory.GetFiles(DS4Windows.Global.exedirpath + "\\Profiles"))
{
File.Copy(s, DS4Windows.Global.appDataPpath + "\\Profiles\\" + Path.GetFileName(s));
}
}
catch { }
MessageBox.Show("Copy complete, please relaunch DS4Windows and remove settings from Program Directory",
"DS4Windows");
}
else
{
MessageBox.Show("DS4Windows cannot edit settings here, This will now close",
"DS4Windows");
}
DS4Windows.Global.appdatapath = null;
skipSave = true;
Current.Shutdown();
return;
}
}
private void CheckOptions(ArgumentParser parser)
{
if (parser.HasErrors)
{
runShutdown = false;
exitApp = true;
Current.Shutdown(1);
}
else if (parser.Driverinstall)
{
CreateBaseThread();
DS4Forms.WelcomeDialog dialog = new DS4Forms.WelcomeDialog(true);
dialog.ShowDialog();
runShutdown = false;
exitApp = true;
Current.Shutdown();
}
else if (parser.ReenableDevice)
{
DS4Windows.DS4Devices.reEnableDevice(parser.DeviceInstanceId);
runShutdown = false;
exitApp = true;
Current.Shutdown();
}
else if (parser.Runtask)
{
StartupMethods.LaunchOldTask();
runShutdown = false;
exitApp = true;
Current.Shutdown();
}
else if (parser.Command)
{
IntPtr hWndDS4WindowsForm = IntPtr.Zero;
hWndDS4WindowsForm = FindWindow(ReadIPCClassNameMMF(), "DS4Windows");
if (hWndDS4WindowsForm != IntPtr.Zero)
{
COPYDATASTRUCT cds;
cds.lpData = IntPtr.Zero;
try
{
cds.dwData = IntPtr.Zero;
cds.cbData = parser.CommandArgs.Length;
cds.lpData = Marshal.StringToHGlobalAnsi(parser.CommandArgs);
SendMessage(hWndDS4WindowsForm, DS4Forms.MainWindow.WM_COPYDATA, IntPtr.Zero, ref cds);
}
finally
{
if (cds.lpData != IntPtr.Zero)
Marshal.FreeHGlobal(cds.lpData);
}
}
runShutdown = false;
exitApp = true;
Current.Shutdown();
}
}
private void CreateControlService()
{
controlThread = new Thread(() => {
rootHub = new DS4Windows.ControlService();
DS4Windows.Program.rootHub = rootHub;
requestClient = new HttpClient();
collectTimer = new Timer(GarbageTask, null, 30000, 30000);
});
controlThread.Priority = ThreadPriority.Normal;
controlThread.IsBackground = true;
controlThread.Start();
while (controlThread.IsAlive)
Thread.SpinWait(500);
}
private void CreateBaseThread()
{
controlThread = new Thread(() => {
DS4Windows.Program.rootHub = rootHub;
requestClient = new HttpClient();
collectTimer = new Timer(GarbageTask, null, 30000, 30000);
});
controlThread.Priority = ThreadPriority.Normal;
controlThread.IsBackground = true;
controlThread.Start();
while (controlThread.IsAlive)
Thread.SpinWait(500);
}
private void GarbageTask(object state)
{
GC.Collect(0, GCCollectionMode.Forced, false);
}
private void CreateTempWorkerThread()
{
testThread = new Thread(SingleAppComThread_DoWork);
testThread.Priority = ThreadPriority.Lowest;
testThread.IsBackground = true;
testThread.Start();
}
private 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)
{
Dispatcher.BeginInvoke((Action)(() =>
{
MainWindow.Show();
MainWindow.WindowState = WindowState.Normal;
}));
}
}
}
}
public 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 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;
}
private void SetUICulture(string culture)
{
try
{
//CultureInfo ci = new CultureInfo("ja");
CultureInfo ci = CultureInfo.GetCultureInfo(culture);
LocalizeDictionary.Instance.SetCurrentThreadCulture = true;
LocalizeDictionary.Instance.Culture = ci;
// fixes the culture in threads
CultureInfo.DefaultThreadCurrentCulture = ci;
CultureInfo.DefaultThreadCurrentUICulture = ci;
//DS4WinWPF.Properties.Resources.Culture = ci;
Thread.CurrentThread.CurrentCulture = ci;
Thread.CurrentThread.CurrentUICulture = ci;
}
catch (CultureNotFoundException) { /* Skip setting culture that we cannot set */ }
}
private void Application_Exit(object sender, ExitEventArgs e)
{
if (runShutdown)
{
Logger logger = logHolder.Logger;
logger.Info("Request App Shutdown");
CleanShutdown();
}
}
private void Application_SessionEnding(object sender, SessionEndingCancelEventArgs e)
{
Logger logger = logHolder.Logger;
logger.Info("User Session Ending");
CleanShutdown();
}
private void CleanShutdown()
{
if (runShutdown)
{
if (rootHub != null)
{
Task.Run(() =>
{
rootHub.Stop();
}).Wait();
}
if (!skipSave)
{
DS4Windows.Global.Save();
}
exitComThread = true;
if (threadComEvent != null)
{
threadComEvent.Set(); // signal the other instance.
while (testThread.IsAlive)
Thread.SpinWait(500);
threadComEvent.Close();
}
if (ipcClassNameMMA != null) ipcClassNameMMA.Dispose();
if (ipcClassNameMMF != null) ipcClassNameMMF.Dispose();
LogManager.Flush();
LogManager.Shutdown();
}
}
}
}