// Copyright (c) 2016, Joel Longanecker // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, // are permitted provided that the following conditions are met: // 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. // IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, // EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // QuitBit // Emulator Killer written by Joel Longanecker // 2015 // // Usage: // qb.exe --controller=2 --buttons=2+0+1 --exec=c:\emulators\nes\nes.exe --params=c:\roms\nes\mario.nes // // Example emulation station usage: // // genesis // Sega Genesis // C:\Roms\genesis // .bin .zip // qb.exe --buttons=6 --e=c:\retroarch\retroarch.exe --p=-D -L C:\retroarch\cores\genesis_plus_gx_libretro.dll "%ROM_RAW%" // genesis // genesis // // // Note: --controller and --params are not necessary using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace QuitBit { [StructLayout(LayoutKind.Sequential)] public struct JOYINFOEX { public int dwSize; public int dwFlags; public int dwXpos; public int dwYpos; public int dwZpos; public int dwRpos; public int dwUpos; public int dwVpos; public int dwButtons; public int dwButtonNumber; public int dwPOV; public int dwReserved1; public int dwReserved2; } /// /// This class is used to un-screw with the screen resolution if a game is being naughty. /// internal sealed class WindowManager : IDisposable { struct POINTL { public Int32 x; public Int32 y; } [Flags()] enum DM : int { Orientation = 0x1, PaperSize = 0x2, PaperLength = 0x4, PaperWidth = 0x8, Scale = 0x10, Position = 0x20, NUP = 0x40, DisplayOrientation = 0x80, Copies = 0x100, DefaultSource = 0x200, PrintQuality = 0x400, Color = 0x800, Duplex = 0x1000, YResolution = 0x2000, TTOption = 0x4000, Collate = 0x8000, FormName = 0x10000, LogPixels = 0x20000, BitsPerPixel = 0x40000, PelsWidth = 0x80000, PelsHeight = 0x100000, DisplayFlags = 0x200000, DisplayFrequency = 0x400000, ICMMethod = 0x800000, ICMIntent = 0x1000000, MediaType = 0x2000000, DitherType = 0x4000000, PanningWidth = 0x8000000, PanningHeight = 0x10000000, DisplayFixedOutput = 0x20000000 } [StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi)] struct DEVMODE { public const int CCHDEVICENAME = 32; public const int CCHFORMNAME = 32; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)] [System.Runtime.InteropServices.FieldOffset(0)] public string dmDeviceName; [System.Runtime.InteropServices.FieldOffset(32)] public Int16 dmSpecVersion; [System.Runtime.InteropServices.FieldOffset(34)] public Int16 dmDriverVersion; [System.Runtime.InteropServices.FieldOffset(36)] public Int16 dmSize; [System.Runtime.InteropServices.FieldOffset(38)] public Int16 dmDriverExtra; [System.Runtime.InteropServices.FieldOffset(40)] public DM dmFields; [System.Runtime.InteropServices.FieldOffset(44)] Int16 dmOrientation; [System.Runtime.InteropServices.FieldOffset(46)] Int16 dmPaperSize; [System.Runtime.InteropServices.FieldOffset(48)] Int16 dmPaperLength; [System.Runtime.InteropServices.FieldOffset(50)] Int16 dmPaperWidth; [System.Runtime.InteropServices.FieldOffset(52)] Int16 dmScale; [System.Runtime.InteropServices.FieldOffset(54)] Int16 dmCopies; [System.Runtime.InteropServices.FieldOffset(56)] Int16 dmDefaultSource; [System.Runtime.InteropServices.FieldOffset(58)] Int16 dmPrintQuality; [System.Runtime.InteropServices.FieldOffset(44)] public POINTL dmPosition; [System.Runtime.InteropServices.FieldOffset(52)] public Int32 dmDisplayOrientation; [System.Runtime.InteropServices.FieldOffset(56)] public Int32 dmDisplayFixedOutput; [System.Runtime.InteropServices.FieldOffset(60)] public short dmColor; // See note below! [System.Runtime.InteropServices.FieldOffset(62)] public short dmDuplex; // See note below! [System.Runtime.InteropServices.FieldOffset(64)] public short dmYResolution; [System.Runtime.InteropServices.FieldOffset(66)] public short dmTTOption; [System.Runtime.InteropServices.FieldOffset(68)] public short dmCollate; // See note below! [System.Runtime.InteropServices.FieldOffset(72)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)] public string dmFormName; [System.Runtime.InteropServices.FieldOffset(102)] public Int16 dmLogPixels; [System.Runtime.InteropServices.FieldOffset(104)] public Int32 dmBitsPerPel; [System.Runtime.InteropServices.FieldOffset(108)] public Int32 dmPelsWidth; [System.Runtime.InteropServices.FieldOffset(112)] public Int32 dmPelsHeight; [System.Runtime.InteropServices.FieldOffset(116)] public Int32 dmDisplayFlags; [System.Runtime.InteropServices.FieldOffset(116)] public Int32 dmNup; [System.Runtime.InteropServices.FieldOffset(120)] public Int32 dmDisplayFrequency; } [DllImport("user32.dll")] static extern int EnumDisplaySettings( string deviceName, int modeNum, ref DEVMODE devMode); [DllImport("user32.dll")] static extern int ChangeDisplaySettings( ref DEVMODE devMode, int flags); const int ENUM_CURRENT_SETTINGS = -1; const int CDS_UPDATEREGISTRY = 0x01; const int CDS_TEST = 0x02; const int DISP_CHANGE_SUCCESSFUL = 0; const int DISP_CHANGE_RESTART = 1; const int DISP_CHANGE_FAILED = -1; private List _resolutions; /// /// Save all the settings in the constructor /// public WindowManager() { _resolutions = new List(); DEVMODE vDevMode = new DEVMODE(); int i = 0; while (EnumDisplaySettings(null, i, ref vDevMode) == 1) { _resolutions.Add(vDevMode); } } /// /// Restore it all in the dispose /// public void Dispose() { DEVMODE dMode; foreach (var d in _resolutions) { dMode = d; ChangeDisplaySettings(ref dMode, CDS_UPDATEREGISTRY); } } } internal sealed class Controller { [DllImport("winmm.dll")] internal static extern int joyGetPosEx(int uJoyID, ref JOYINFOEX pji); //Get the state of a controller with their ID [DllImport("winmm.dll")] public static extern Int32 joyGetNumDevs(); //How many controllers are plugged in private int controllerNum; private int combo; private JOYINFOEX state = new JOYINFOEX(); public Controller(int n, int c) { controllerNum = n; combo = c; state.dwFlags = 128; state.dwSize = System.Runtime.InteropServices.Marshal.SizeOf(typeof(JOYINFOEX)); } public bool comboPressed() { if (controllerNum > -1) //Checking one controller { joyGetPosEx(controllerNum, ref state); return (combo == state.dwButtons); } else //Checking all controllers { for (int i = 0; i < joyGetNumDevs(); i++) { joyGetPosEx(i, ref state); if (combo == state.dwButtons) return true; } return false; } } } internal sealed class Program { [STAThread] private static void Main() { Controller controller; WindowManager windowManager = null; System.Diagnostics.Process runProgram; System.Diagnostics.Stopwatch timer = new System.Diagnostics.Stopwatch(); int time; { string controllerString = "-1", buttonsString = "", execString = "", paramsString = "", timeString = "0", clString = Environment.CommandLine; string[] stringElements = clString.Split(new string[] { "--" }, StringSplitOptions.RemoveEmptyEntries); int buttonCombo = 0, controllerNum = -1; foreach (var s in stringElements) { if (s.Contains("=")) { var lSide = s.Split('=')[0]; var rSide = s.Split('=')[1]; if (lSide == "buttons" || lSide == "b") buttonsString = rSide; else if (lSide == "exec" || lSide == "e") execString = rSide; else if (lSide == "params" || lSide == "p") paramsString = rSide; else if (lSide == "time" || lSide == "t") timeString = rSide; else if (lSide == "contoller" || lSide == "c") controllerString = rSide; } else { if (s == "rr") { windowManager = new WindowManager(); } } } { bool error = false; int oVal = 0; Console.ForegroundColor = ConsoleColor.Yellow; foreach (var b in buttonsString.Split('+')) //Find Button Combo that is required { if (int.TryParse(b, out oVal)) buttonCombo += (int)Math.Pow(2, oVal); else { if (buttonsString == string.Empty) Console.WriteLine("A button combination is not specififed."); else Console.WriteLine("The button argument is not used properly."); error = true; break; } } if (!System.IO.File.Exists(execString)) { if (execString == string.Empty) Console.WriteLine("An executable is not specififed."); else Console.WriteLine("The executable does not exist, it's possibly an invalid path."); error = true; } if (!int.TryParse(timeString, out time)) { Console.WriteLine("The time argument not used properly."); error = true; } if (!int.TryParse(controllerString, out controllerNum)) { Console.WriteLine("The controller argument not used properly."); error = true; } if (error) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Command Alt Purpose"); Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("--rr Restore Resolution if your emulator is not respecting the desktop" + Environment.NewLine + "--buttons --b Button combination to close the program" + Environment.NewLine + " --b=0+8+6" + Environment.NewLine + "--exec --e Full path to the executable" + Environment.NewLine + " --e=C:\\Emulators\\nestopia.exe" + Environment.NewLine + "--controller --c ID of specific controller to use [Optional]" + Environment.NewLine + " --c=0" + Environment.NewLine + "--time --t Milliseconds to hold down the combination [Optional]" + Environment.NewLine + " --t=2500" + Environment.NewLine + "--params --p Parameters when launching the program [Optional]" + Environment.NewLine + " --p=C:\\roms\\NES\\Super Mario Bros..nes"); Console.ForegroundColor = ConsoleColor.Gray; return; } else Console.ForegroundColor = ConsoleColor.Gray; } controller = new Controller(controllerNum, buttonCombo); //Controller class that handles button presses when checked runProgram = new System.Diagnostics.Process(); //Start up the program runProgram.StartInfo.FileName = execString; runProgram.StartInfo.Arguments = paramsString; runProgram.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(execString); runProgram.Start(); } while (true) { if (!controller.comboPressed()) { timer.Restart(); } else if (timer.ElapsedMilliseconds >= time) { try { if (windowManager != null) { windowManager.Dispose(); windowManager = null; } } catch { } try { runProgram.Kill(); } catch { } return; } System.Threading.Thread.Sleep(35); } } } }