Greetings,
First time poster here. I thought I'd talk about my attempt to making my own front end.
I know there are lots of wonderful ones already, though I don't know how they work in terms of code, I am doing this for fun.
I am fairly new to the windows programming world, so if you see what I'm doing in this thread and realize it is just terrible, feel free to throw around some ideas.
After my front end, I really want to build an actual cabinet, though I have never touched a tool in my life - so I've been very back-and-forth about it.
Hopefully after the software is done I'll feel accomplished enough to learn some simple things about wood working.
Here you can see my 'cade setup. If you can call it a setup.
http://i16.photobucket.com/albums/b5/whitebabylon/cade_zpscd5c7f6b.jpgHere's a 3 second very crappy video.
http://s16.photobucket.com/user/whitebabylon/media/20141010_1317401_zpsde346f7d.mp4.htmlVideo background. The top right video which is the same as the background video will be for system/game preview videos.
OK, code time.
For each game system we want to keep track of a few things. It's rom directory, it's rom file type, its exe directory, and the name of the exe.
string dir_MAME = @"D:\arcade\mame\roms\";
string type_MAME = "*.zip";
string exeDir_MAME = @"D:\arcade\mame\";
string exeName_MAME = @"mame.exe";
If you imagine a simple design like below. You click a button and depending on the button you get the roms from the proper directory. After that, on pressing a key, you want to open the emulator with that ROM.

When you click a button it fires up some code. For MAME I have this:
cmdString = "D:\\ & " + "cd " + exeDir_MAME + " & " + exeName_MAME + " \"" + listBox1.SelectedItem.ToString() + "\"";
WorkingDir = exeDir_MAME;
dir_CUR = dir_MAME;
type_CUR = type_MAME;
procName = "mame";
windowMsg = "Default IME";
You set up the directory of the EXE, rom and the type of file the rom is. You also want to keep track of the emulators process name, which can be seen under Processes tab of CTRL_ALT_DELETE menu in windows. Also, we want to know the 'window message' for when the emulator is open and ready to be manipulated. I don't know much about it, but I've found that every emulator thus far fires off a 'Default IME' when it's basically 'ready'.
The one variable I didn't mention yet is a command string. We basically are forming the windows command line command to properly execute the 'open file with these exe' command.
We call
ExecuteCommand(cmdString);
private void ExecuteCommand(string command)
{
ProcessStartInfo processInfo;
Process process;
processInfo = new ProcessStartInfo("cmd.exe", "/c " + command);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
processInfo.WorkingDirectory = WorkingDir;
process = Process.Start(processInfo);
process.Close();
}
My command strings are actually 3 commands chained together with &. First we change from C to D directory, where my arcade files are. Then we navigate to the EXE directory, then we directly call the EXE and the rom file command like:
"file.exe file.rom"
I've found I have to directly navigate to the exe folder first, before calling the exe and call it directly. I cannot explain why I can't just call 'D:\arcade\mame.exe', but it did not work.
With any luck that should fire up your emulator with the rom. This does not work with all emulators, and I've chosen emulators that allow me to do this. For example, Project 64 uses something else to open it's own files. One work around that I thought about was to send the CTRL+O keystrokes to the emulator which is the shortcut for file->open, and to send the keystrokes of the rom path\rom name to the 'Windows File Open Directory' box that pops up, to open it. You could do this behind the scenes and it would probably only take a second or two. I'll get more into input in a bit.
Basically so far all we've done is call the command line tool to open a file. After a file has been called to open a timer starts immediately. As we keep track of each emulators process name, we say for each process see if this one exists, if it does, grab the first one (if multiples are open, which they should not be). Then it grabs the systems message of the window and checks of its the correct message before we manipulate a few things.
Read the comments to understand a bit more.
private void programOpen_Tick(object sender, EventArgs e)
{
//Create a windows handle
IntPtr WindowHandle = IntPtr.Zero;
try
{
//grab the process, in our example 'mame'
Process[] processes = Process.GetProcessesByName(procName);
//for each process that exists, grab our process
foreach (var handle in EnumerateProcessWindowHandles(
Process.GetProcessesByName(procName).First().Id))
{
//stringbuilder and sendmessage grab our MAME emulators message
StringBuilder message = new StringBuilder(1000);
SendMessage(handle, WM_GETTEXT, message.Capacity, message);
Console.WriteLine("NEW MSG + " + message);
// if our message is whatever we decided was 'file loaded' then do some stuff
if (message.ToString() == windowMsg)
{
//Here we use a lot of User32.dll calls to manipulate the window, first we grab the width/height of our computer screen
//then we get the window handle of our process, and set it's position to 0,0 and its height/width to the monitor. in my example I take into account the window border
//so that it hides the windows borders, which are 2 pixels thick by setting this to -2,-2 and added 4 to the width/height
//emulators have menus which look like [File, Open, Help] etc... so we call GetMenu and SetMenu and remove menu and drawmenu bar to erase this
//what we are doing here is 'full screen'ing the emulator. This works for all the ones I've tried. It's kind of dirty I think, but I love it.
System.Drawing.Rectangle resolution = Screen.PrimaryScreen.Bounds;
int screenWidth = resolution.Width;
int screenHeight = resolution.Height;
foreach (Process process in processes)
{
WindowHandle = process.MainWindowHandle;
SetWindowPos(WindowHandle, IntPtr.Zero, -2, -2, (screenWidth + 4), (screenHeight + 4), SWP_NOZORDER | SWP_SHOWWINDOW);
IntPtr hmenu = GetMenu(WindowHandle);
int counter = GetMenuItemCount(hmenu);
int i = 0;
uint MF_BYPOSITION = (0x400);
uint value_zero = 0;
int GWL_STYLE = -16;
int WS_VISIBLE = 0x10000000;
for (i = 0; i < (int)counter; i++)
{
RemoveMenu(hmenu, value_zero, (uint)MF_BYPOSITION);
}
DrawMenuBar(WindowHandle);
SetWindowLong(WindowHandle, GWL_STYLE, WS_VISIBLE);
InvalidateRect(WindowHandle, IntPtr.Zero, true);
}
programOpen.Stop();
}
}
}
catch
{
}
}
So user32 does some window manipulations. Setting positions, width/height, windows style, and menus of the applications we run to full screen things. We can also set a 'topmost' flag to make applications appear always on the top of our screen, this will be important to assure the user is seeing what we want. When the program starts up we will want it to be full screen and always on top, thus if anything ever happens on windows for whatever reason, it won't stop the users visual experience. When we launch an emulator we will trade which application is always-top.
Using this we can handle our windows and running applications. Next big thing is input. I use a global key hook to essentially create my own keyboard shortcut commands, and to allow me to remap keys.
Say if you press A, you can 'stop' the A value from being sent, and replace it with your own value.
Here's some dirty code:
/***********************************************************
*
* Keyboard Input
*
*
* *********************************************************/
private enum INPUTTYPE : uint
{
Keyboard = 1
}
private enum KEYEVENTF : uint
{
KeyUp = 2,
Scan = 8,
}
[DllImport("User32.dll", SetLastError = true)]
private static extern uint SendInput(int nInputs, [MarshalAs(UnmanagedType.LPArray), In] INPUT[] inputs, int cbSize);
[StructLayout(LayoutKind.Sequential)]
private struct INPUT
{
public INPUTTYPE type;
public INPUT_U u;
public static INPUT VirtualKeyDown(ushort keyP)
{
var input = new INPUT() { type = INPUTTYPE.Keyboard };
input.u.ki = new KEYBDINPUT() { scanCode = (ushort)keyP, flags = (KEYEVENTF)0x0008 };
return input;
}
public static INPUT VirtualKeyUp(ushort keyP)
{
var input = new INPUT() { type = INPUTTYPE.Keyboard };
input.u.ki = new KEYBDINPUT() { scanCode = (ushort)keyP, flags = (KEYEVENTF)0x0008 | (KEYEVENTF)0x0002 };
return input;
}
}
[StructLayout(LayoutKind.Explicit)]
private struct INPUT_U
{
[FieldOffset(0)]
public KEYBDINPUT ki;
[FieldOffset(0)]
public MOUSEINPUT mi;
[FieldOffset(0)]
public HARDWAREINPUT hi;
}
[StructLayout(LayoutKind.Sequential)]
private struct KEYBDINPUT
{
public ushort virtualKey;
public ushort scanCode;
public KEYEVENTF flags;
public uint time;
public IntPtr extraInfo;
}
[StructLayout(LayoutKind.Sequential)]
private struct MOUSEINPUT
{
public int dx;
public int dy;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr extraInfo;
}
[StructLayout(LayoutKind.Sequential)]
private struct HARDWAREINPUT
{
public int uMsg;
public IntPtr wParam;
public IntPtr lParam;
}
Once you have that stuff though its a bit easier to use it.
first you initiate
hookId = SetHook(hookProc2, moduleHandle);
private static IntPtr SetHook(LowLevelKeyboardProc hookProc, IntPtr moduleHandle)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, moduleHandle, 0);
}
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
Then you can use it in this function
private static IntPtr HookCallback2(int nCode, IntPtr wParam, IntPtr lParam)
{
int vkCode = Marshal.ReadInt32(lParam);
//If key up of a button
if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP)
{
//If keyboard input is "C" button, then
if (((System.Windows.Forms.Keys)vkCode).ToString() == "C")
{
//create an input array, we can have more than 1 input in this array, but this one just replaces "C" with the value 0x2c. I don't remember the value, but you can look up the vlaues here:
//http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html
INPUT[] inputs = new INPUT[] {
INPUT.VirtualKeyUp((ushort)0x2c),
};
//send this input
SendInput(inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
//I use a value here, and if it's 1, then we don't return the original value
returnMe = 1;
}
}
//if a keyDOWN event is called, then if it's Z then send a keydown of whatever key
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
returnMe = 1;
if (((System.Windows.Forms.Keys)vkCode).ToString() == "Z")
{
INPUT[] inputs = new INPUT[] {
INPUT.VirtualKeyDown((ushort)0x2e),
};
SendInput(inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT)));
}
//calling 'CallNextHookEx' sends the original key message, returning 1 instead lets you send your own message and ignore the original key press
if (returnMe == (int)1)
{
return (System.IntPtr)1;
}
else
{
return CallNextHookEx(hookId, nCode, wParam, lParam);
}
}
This has allowed me to send custom input to nestopia, MAME and all the other emulators that I've tried.
It also allows me to remap A -> B and B -> A. Which I originally thought wasn't allowed.
You can get a keys current state, like say CONTROL key, and if it's down, and your original key is A you could execute some code. Essentially making CTRL+A your very own hotkey. With our windows handling code we can create a menu that pops up on whatever hot key we want to allow the user to close current emulator, and to switch back to the arcade program.
You can look up user32.dll on p/invoke and learn about some of the functionality that I use
[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern IntPtr FindWindow(string lpClassName,
string lpWindowName);
[DllImport("user32.dll")]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
internal static extern short GetKeyState(int virtualKeyCode);
// Activate an application window.
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
static extern IntPtr GetMessageExtraInfo();
[DllImport("user32.dll")]
static extern UInt32 SendInput(UInt32 nInputs, [MarshalAs(UnmanagedType.LPArray, SizeConst = 1)] INPUT[] pInputs, Int32 cbSize);
[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
/* enumerate windows*/
delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn,
IntPtr lParam);
static IEnumerable<IntPtr> EnumerateProcessWindowHandles(int processId)
{
var handles = new List<IntPtr>();
foreach (ProcessThread thread in Process.GetProcessById(processId).Threads)
EnumThreadWindows(thread.Id,
(hWnd, lParam) => { handles.Add(hWnd); return true; }, IntPtr.Zero);
return handles;
}
private const uint WM_GETTEXT = 0x000D;
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, int wParam,
StringBuilder lParam);
/* */
/* Erase menu fron a window*/
[DllImport("user32.dll")]
static extern IntPtr GetMenu(IntPtr hWnd);
[DllImport("user32.dll")]
static extern int GetMenuItemCount(IntPtr hMenu);
[DllImport("user32.dll")]
static extern bool RemoveMenu(IntPtr hMenu, uint uPosition, uint uFlags);
[DllImport("user32.dll")]
static extern bool DrawMenuBar(IntPtr hWnd);
[DllImport("user32.dll")]
static extern bool InvalidateRect(IntPtr hWnd, IntPtr lpRect, bool bErase);
/* */
/* Set border style */
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
/* */
private const int WH_KEYBOARD_LL = 13;
private const int WH_GETMESSAGE = 3;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private static LowLevelKeyboardProc hookProc = HookCallback;
private static LowLevelKeyboardProc hookProc2 = HookCallback2;
private static IntPtr hookId = IntPtr.Zero;
private string pressedBefore = "A";
public IntPtr moduleHandle;
const short SWP_NOSIZE = 1;
const short SWP_NOZORDER = 0X4;
const int SWP_SHOWWINDOW = 0x0040;
const UInt32 SWP_NOSIZE2 = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 TOPMOST_FLAGS = (UInt32) (SWP_NOMOVE | SWP_NOSIZE2);
static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
Full code for the alpha program here:
http://pastebin.com/emCjcjk5Now the current application from the image above is just a WPF front end. It uses a media element as the background, and Labels to display a menu. It uses brushes to paint the currently selected menu item.
It might not make much sense until I actually finish the code and post the fully functional fully commented end peice.
Game plan is to have 3 types of input, keyboard/xarcade (same as keyboard really)/ and xbox controller. Remapping the first two with SendInput function, and xbox controller with "Microsoft.Xna.Framework".