WIP
DirectOutput framework for virtual pinball cabinets WIP
Go to:
Overview 
Plugin.cs
Go to the documentation of this file.
1 using System;
2 using System.Runtime.InteropServices;
3 using System.IO;
4 using System.Text.RegularExpressions;
5 using System.Collections.Generic;
6 using DracLabs;
7 using FuzzyStrings;
8 using System.Reflection;
10 
11 
12 namespace PinballX
13 {
14 
19  public class Plugin
20  {
21 
22  DOFManager DM = new DOFManager();
23 
24 
25  int LastPBXState = -1;
26  private void UpdatePBXState(int State)
27  {
28  if (State != LastPBXState)
29  {
30  string StateName = GetPBXStateName(LastPBXState);
31  if (StateName != null)
32  {
33  DM.UpdateNamedTableElement(StateName, 0);
34  }
35 
36  StateName = GetPBXStateName(State);
37  if (StateName != null)
38  {
39  DM.UpdateNamedTableElement(StateName, 1);
40  }
41 
42  LastPBXState = State;
43 
44  }
45 
46  }
47 
48 
49  private string GetPBXStateName(int State)
50  {
51  switch (State)
52  {
53  case 10:
54  return "PBXWheel";
55  case 20:
56  return "PBXMenu";
57  default:
58  break;
59  }
60  return null;
61  }
62 
63  private enum InputActionEnum
64  {
65  Quit,
66  Left,
67  Right,
68  Select,
69  PageLeft,
70  PageRight,
72  NoInput
73  }
74 
75  private Dictionary<int, InputActionEnum> KeyInputs = new Dictionary<int, InputActionEnum>();
76  private Dictionary<int, InputActionEnum> JoyInputs = new Dictionary<int, InputActionEnum>();
77  private bool OneClickLaunch = false;
78 
79  private void AddToDict(Dictionary<int, InputActionEnum> D, string Code, InputActionEnum Action)
80  {
81  int C = 0;
82  if (int.TryParse(Code, out C))
83  {
84  if (!D.ContainsKey(C))
85  {
86  D.Add(C, Action);
87  }
88  }
89  }
90 
91  private void ReadPBXConfig()
92  {
93  Log("Loading PBX config data");
94  string IniFileName = Path.Combine(new DirectoryInfo(".").FullName, "config\\PinballX.ini");
95 
96  if (File.Exists(IniFileName))
97  {
98  IniFile I = new IniFile();
99  I.Load(IniFileName, false);
100 
101  AddToDict(KeyInputs, I.GetKeyValue("KeyCodes", "quit"), InputActionEnum.Quit);
102  AddToDict(KeyInputs, I.GetKeyValue("KeyCodes", "left"), InputActionEnum.Left);
103  AddToDict(KeyInputs, I.GetKeyValue("KeyCodes", "right"), InputActionEnum.Right);
104  AddToDict(KeyInputs, I.GetKeyValue("KeyCodes", "select"), InputActionEnum.Select);
105  AddToDict(KeyInputs, I.GetKeyValue("KeyCodes", "pageleft"), InputActionEnum.PageLeft);
106  AddToDict(KeyInputs, I.GetKeyValue("KeyCodes", "pageright"), InputActionEnum.PageRight);
107  AddToDict(KeyInputs, I.GetKeyValue("KeyCodes", "instructions"), InputActionEnum.Instructions);
108 
109 
110  AddToDict(JoyInputs, I.GetKeyValue("JoyCodes", "quit"), InputActionEnum.Quit);
111  AddToDict(JoyInputs, I.GetKeyValue("JoyCodes", "left"), InputActionEnum.Left);
112  AddToDict(JoyInputs, I.GetKeyValue("JoyCodes", "right"), InputActionEnum.Right);
113  AddToDict(JoyInputs, I.GetKeyValue("JoyCodes", "select"), InputActionEnum.Select);
114  AddToDict(JoyInputs, I.GetKeyValue("JoyCodes", "instructions"), InputActionEnum.Instructions);
115 
116  try
117  {
118  OneClickLaunch = I.GetKeyValue("Interface", "OneClickLaunch").Equals("True", StringComparison.InvariantCultureIgnoreCase);
119 
120  }
121  catch
122  {
123 
124  OneClickLaunch = false;
125  }
126 
127  Log("PBX config data loaded");
128  }
129  else
130  {
131  Log("No PBX copnfig data found");
132  //No ini file found
133  }
134  }
135 
136  List<string> TableRomNames = new List<string>();
137  TableNameMappings AllTableMappings = new TableNameMappings();
138  // Dictionary<string, string> ConfiguredRomMap = new Dictionary<string, string>();
139  //Dictionary<string, string> AllRomMap = new Dictionary<string, string>();
140 
141 
142  private void SetupRomNameLinks()
143  {
144  Log("Loading Table/Romname mappings");
145  AllTableMappings = new TableNameMappings();
146 
147  //AllTableMappings.Add(new Mapping() { TableName = "xxx", RomName = "yyy" });
148  //AllTableMappings.Add(new Mapping() { TableName = "aaa", RomName = "bbb" });
149 
150  //AllTableMappings.SaveTableMappings(".\\mappings.xml");
151 
152  string TableMappingFileName = DM.GetTableMappingFilename();
153  Log("Table mapping filename: " + TableMappingFileName);
154  if (!string.IsNullOrEmpty(TableMappingFileName))
155  {
156  AllTableMappings = TableNameMappings.LoadTableMappings(TableMappingFileName);
157  }
158 
159  Log(AllTableMappings.ToString() + " TableMappings loaded");
160 
161  string[] L=DM.GetConfiguredTableElmentDescriptors();
162  foreach (string R in L)
163  {
164  if (R.StartsWith("$"))
165  {
166  TableRomNames.Add(R.Substring(1).Trim().ToUpper());
167  }
168 
169  }
170 
171  if (TableRomNames.Count > 0)
172  {
173  Log("The following " + TableRomNames.Count + " RomNames are configured in DOF: " + string.Join(",", TableRomNames.ToArray()));
174  }
175  else
176  {
177  Log("No romnames are configured in DOF.");
178  //return;
179  }
180  Log("Table/RomName mapping loaded");
181 
182 
183 
184 
185 
186 
187 
188 
189  }
190 
191 
192 
193  Dictionary<string, string> RomLookupCache = new Dictionary<string, string>();
194 
195  private string GetRom(string GameDecriptionShort)
196  {
197  string GameDesc = CleanGameDescription(GameDecriptionShort.ToUpper());
198 
199 
200  if (TableRomNames.Contains(GameDecriptionShort)) return GameDesc;
201 
202 
203  if (RomLookupCache.ContainsKey(GameDesc)) return RomLookupCache[GameDesc];
204 
205 
206  Mapping MatchMapping = null;
207  double MatchValue = 0;
208 
209  foreach (Mapping Mapping in AllTableMappings)
210  {
211  double M = FuzzyStrings.FuzzyText.DiceCoefficient(Mapping.TableName.ToUpper(), GameDesc);
212 
213  if (M > MatchValue)
214  {
215  MatchValue = M;
216  MatchMapping = Mapping;
217  }
218  }
219 
220  if(MatchMapping!=null) {
221  Log("Best match for " + GameDesc + " is " + MatchMapping.TableName.ToUpper() + " (" + MatchMapping.RomName + "). Match Value: " + MatchValue.ToString());
222  }
223  if (MatchValue > 0.55 && MatchMapping!=null)
224  {
225  string Rom = MatchMapping.RomName;
226 
227 
228  string UseTableRom = null;
229 
230  foreach (string TableRomName in TableRomNames)
231  {
232  if (TableRomName.Equals(Rom,StringComparison.InvariantCultureIgnoreCase))
233  {
234  UseTableRom = TableRomName;
235  break;
236  };
237  };
238 
239 
240 
241  if (!RomLookupCache.ContainsKey(GameDesc))
242  {
243  RomLookupCache.Add(GameDesc, UseTableRom);
244  }
245 
246  if (UseTableRom != null)
247  {
248 
249  return UseTableRom;
250  }
251  }
252 
253 
254  return null;
255  }
256 
257  private string CleanGameDescription(string GameName)
258  {
259  Regex rgx = new Regex("[^a-zA-Z0-9 ]");
260  RegexOptions options = RegexOptions.None;
261  Regex regex = new Regex(@"[ ]{2,}", options);
262 
263 
264  return regex.Replace(rgx.Replace(GameName, ""), @" ");
265 
266  }
267 
268  Config Config = null;
269 
270  private void LoadConfig()
271  {
272  Log("Loading plugin config");
273  try
274  {
275  Config = Config.GetConfigFromXmlFile();
276  }
277  catch { }
278  if (Config == null)
279  {
280  Log("Plugin config loading failed. Using default values.");
281  Config = new Config();
282 
283  }
284  Log("Plugin config loaded");
285  }
286 
287 
288  private void Log(string Text)
289  {
290  if (Config != null && Config.EnableLogging)
291  {
292  TextWriter tw = null;
293  try
294  {
295  tw = new StreamWriter(Path.Combine(new FileInfo(Assembly.GetExecutingAssembly().Location).Directory.FullName, "PinballX DirectOutput Plugin.log"), true);
296 
297  tw.WriteLine("{1:yy.MM.dd hh:mm:ss.fff} {0}", Text, DateTime.Now);
298 
299 
300  tw.Close();
301  }
302  catch
303  {
304 
305  }
306  }
307  }
315  public bool Initialize(IntPtr InfoPtr)
316  {
317  PinballXInfo Info = (PinballXInfo)Marshal.PtrToStructure(InfoPtr, typeof(PinballXInfo));
318 
319  bool InitOK = true;
320  try
321  {
322  LoadConfig();
323 
324  Config.EnableLogging = true;
325 
326  Log("Initializing plugin");
327 
328  ReadPBXConfig();
329 
330 
331 
332  Log("Initializing DOF");
333  DM.Init();
334  Log("DOF initialized");
335 
336 
337  SetupRomNameLinks();
338 
339  Log("Sending initial PBX state to DOF");
340  UpdatePBXState(10);
341 
342  DM.UpdateNamedTableElement("PBXScreenSaver", 0);
343  Log("Init complete");
344  }
345  catch (Exception E)
346  {
347  Log("Init failed: " + E.Message);
348  InitOK = false;
349  }
350 
351 
352  return InitOK;
353  }
354 
359  public void Configure()
360  {
361  Config CO = new Config(); ;
362  if (File.Exists(Config.ConfigFileName))
363  {
364  try
365  {
366  CO = Config.GetConfigFromXmlFile();
367 
368 
369  }
370  catch { }
371 
372  }
373 
374  Configure C = new Configure(CO);
375 
376  if (C.ShowDialog() == System.Windows.Forms.DialogResult.OK)
377  {
379  }
380 
381 
382  }
383 
384 
388  public void Event_App_Exit()
389  {
390  try
391  {
392  if (!string.IsNullOrEmpty(LastGameSelect))
393  {
394  DM.UpdateNamedTableElement(LastGameSelect, 0);
395  }
396  UpdatePBXState(-1);
397 
398  DM.Finish();
399  DM = null;
400 
401  Log("Exiting PBX");
402 
403  }
404  catch (Exception E)
405  {
406 
407  Log("App_Exit failed: " + E.Message);
408  }
409  }
410 
411 
412 
417 
418  private string LastGameSelect = null;
419  public void Event_GameSelect(IntPtr InfoPtr)
420  {
421  try
422  {
423  GameInfo Info = (GameInfo)Marshal.PtrToStructure(InfoPtr, typeof(GameInfo));
424 
425  if (!string.IsNullOrEmpty(LastGameSelect))
426  {
427  DM.UpdateNamedTableElement(LastGameSelect, 0);
428  }
429  SendAction("PBXGameSelect");
430  LastGameSelect = GetRom(Info.GameShortDescription);
431  if (!string.IsNullOrEmpty(LastGameSelect))
432  {
433  DM.UpdateNamedTableElement(LastGameSelect, 1);
434  }
435 
436  Log("Game selected " + Info.GameShortDescription + " (" + (string.IsNullOrEmpty(LastGameSelect) ? "No update sent" : "Update sent for " + LastGameSelect) + ")");
437  }
438  catch (Exception E)
439  {
440  Log("GameSelect failed: " + E.Message);
441  }
442  }
443 
451  public bool Event_GameRun(IntPtr InfoPtr)
452  {
453  try
454  {
455  GameInfo Info = (GameInfo)Marshal.PtrToStructure(InfoPtr, typeof(GameInfo));
456  if (!string.IsNullOrEmpty(LastGameSelect))
457  {
458  DM.UpdateNamedTableElement(LastGameSelect, 0);
459  }
460  UpdatePBXState(-1);
461  DM.Finish();
462 
463  Log("Running " + Info.GameShortDescription);
464  }
465  catch (Exception E)
466  {
467  Log("GameRun failed: " + E.Message);
468  }
469 
470  return true;
471  }
472 
473 
474 
475 
476 
484  public string Event_Parameters(IntPtr InfoPtr)
485  {
486 
487  GameInfo Info = (GameInfo)Marshal.PtrToStructure(InfoPtr, typeof(GameInfo));
488  string CmdLine = Info.Parameters;
489 
490  return CmdLine;
491  }
492 
493 
498  public void Event_GameExit(IntPtr InfoPtr)
499  {
500  try
501  {
502  GameInfo Info = (GameInfo)Marshal.PtrToStructure(InfoPtr, typeof(GameInfo));
503 
504 
505  DM.Init();
506  UpdatePBXState(10);
507  DM.UpdateNamedTableElement("PBXScreenSaver", 0);
508 
509  Log("Exiting game " + Info.GameShortDescription);
510  }
511  catch (Exception E)
512  {
513  Log("GameExit failed: " + E.Message);
514  }
515  }
516 
524  public bool Event_Input(bool[] Input_Keys, bool[] Input_Buttons, int PinballXStatus)
525  {
526  Log("Event_Input");
527  try
528  {
529 
530  InputActionEnum InputAction = GetInputAction(KeyInputs, Input_Keys);
531  if (InputAction == InputActionEnum.NoInput)
532  {
533  InputAction = GetInputAction(JoyInputs, Input_Buttons);
534  }
535 
536  string PBXAction = null;
537  if (InputAction != InputActionEnum.NoInput)
538  {
539 
540  switch (PinballXStatus)
541  {
542  case 10:
543  //Wheel mode
544  switch (InputAction)
545  {
546  case InputActionEnum.Quit:
547  PBXAction = "PBXQuit";
548  break;
549  case InputActionEnum.Left:
550  PBXAction = "PBXWheelRight";
551  break;
552  case InputActionEnum.Right:
553  PBXAction = "PBXWheelLeft";
554  break;
555  case InputActionEnum.Select:
556  if (!OneClickLaunch)
557  {
558  PBXAction = "PBXMenuOpen";
559  }
560  break;
561  case InputActionEnum.PageLeft:
562  PBXAction = "PBXWheelPageRight";
563  break;
564  case InputActionEnum.PageRight:
565  PBXAction = "PBXWheelPageLeft";
566  break;
567  case InputActionEnum.Instructions:
568  PBXAction = "PBXInstructions";
569  break;
570  default:
571  break;
572  }
573 
574  break;
575  case 20:
576  //Menu
577  switch (InputAction)
578  {
579  case InputActionEnum.Quit:
580  PBXAction = "PBXMenuQuit";
581  break;
582  case InputActionEnum.Left:
583  PBXAction = "PBXMenuUp";
584  break;
585  case InputActionEnum.Right:
586  PBXAction = "PBXMenuDown";
587  break;
588  case InputActionEnum.Select:
589  PBXAction = "PBXMenuSelect";
590  break;
591  default:
592  break;
593  }
594  break;
595  default:
596 
597  break;
598  }
599 
600 
601  }
602 
603 
604 
605  SendAction(PBXAction);
606  UpdatePBXState(PinballXStatus);
607  }
608  catch (Exception E)
609  {
610 
611  Log("Event_Input failed: " + E.Message);
612  }
613 
614 
615  return true;
616  }
617 
618 
619  private InputActionEnum GetInputAction(Dictionary<int, InputActionEnum> InputDefs, bool[] Input)
620  {
621  foreach (int K in InputDefs.Keys)
622  {
623  if (Input[K])
624  {
625  return InputDefs[K];
626  }
627  }
628 
629  return InputActionEnum.NoInput;
630 
631 
632 
633  }
634 
635  private void SendAction(string Action)
636  {
637  if (!string.IsNullOrEmpty(Action))
638  {
639  DM.SignalNamedTableElement(Action);
640  Log("Action: " + Action);
641  }
642  }
643 
644 
650  public bool Event_ScreenSaver(int Type)
651  {
652 
653  try
654  {
655  //Start = 1,
656  //End = 2,
657  switch (Type)
658  {
659 
660  case 1:
661  SendAction("PBXScreenSaverStart");
662  DM.UpdateNamedTableElement("PBXcreenSaver", 1);
663  break;
664 
665  case 2:
666  SendAction("PBXScreenSaverQuit");
667  DM.UpdateNamedTableElement("PBXScreenSaver", 0);
668 
669  break;
670  }
671  }
672  catch (Exception E)
673  {
674 
675  Log("Event screensaver failed: " + E.Message);
676  }
677  return true;
678  }
679 
680 
681 
682  public bool Event_DisableDMD()
683  {
684  return false;
685  }
686 
687  #region Constructor
688  public Plugin()
692  {
693  }
694 
695  #endregion
696  #region Dispose
697 
698 
702  public void Dispose()
703  {
704  Dispose(true);
705  GC.SuppressFinalize(this);
707  }
708 
709  private bool disposed = false;
710 
715  private void Dispose(bool disposing)
716  {
717  //
718  if (!(this.disposed))
719  {
720 
722  if ((disposing))
723  {
724 
725  // Clean up managed resources
726 
727  // Clean up unmanaged resources here.
728 
729  }
730  }
731 
732 
733  disposed = true;
734  }
735 
736  #endregion
737 
738  #region PluginInfo Properties
739 
740  // Usually it is not necessary to modfy the code for the following properties.
741  // Instead set the correct values for those properties in the PlufinInfo structure.
742 
743  public string Name
744  {
745  get { return PluginInfo.Name; }
746  }
747 
748  public string Version
749  {
750  get { return PluginInfo.Version; }
751  }
752 
753  public string Author
754  {
755  get { return PluginInfo.Author; }
756  }
757 
758  public string Description
759  {
760  get { return PluginInfo.Description; }
761  }
762 
763  public string PluginVersion
764  {
765  get { return PluginInfo.PluginVersion; }
766  }
767  #endregion
768 
769  }
770 
771 }
772 
773 
774 
void Configure()
The method is called when the configure button in the plugin manager of PinballX is clicked...
Definition: Plugin.cs:359
const string Version
Definition: PluginInfo.cs:10
This is the base calls for PinballX plugins. It contains the public methods which are called from Pin...
Definition: Plugin.cs:19
bool Event_Input(bool[] Input_Keys, bool[] Input_Buttons, int PinballXStatus)
This method is called when PinballX receives input from keyboard or buttons
Definition: Plugin.cs:524
string Event_Parameters(IntPtr InfoPtr)
This method is after PinballX has built the command line parameters. You can return a modified comman...
Definition: Plugin.cs:484
void SignalNamedTableElement(string TableElementName)
Definition: DOFManager.cs:148
string Description
Definition: Plugin.cs:759
string GetKeyValue(string sSection, string sKey)
Definition: IniFile.cs:180
bool EnableLogging
Definition: Config.cs:16
void Event_GameExit(IntPtr InfoPtr)
This method is called when the emulator exits from a game.
Definition: Plugin.cs:498
string Parameters
Definition: GameInfo.cs:17
static TableNameMappings LoadTableMappings(string Filename)
Simple class containing the mappinging between tablename and romname
Definition: Mapping.cs:10
void Dispose()
Releases unmanaged and managed resources.
Definition: Plugin.cs:702
bool Initialize(IntPtr InfoPtr)
Initializes the plugin. This method is called when PinballX loads and initializes the plugin...
Definition: Plugin.cs:315
Plugin()
Initializes a new instance of the Plugin class.
Definition: Plugin.cs:691
string GameShortDescription
Definition: GameInfo.cs:14
static Config GetConfigFromXmlFile(string FileName=null)
Instanciates a config object from a cabinet configuration in a XML file.
Definition: Config.cs:92
string Author
Definition: Plugin.cs:754
bool Event_DisableDMD()
Definition: Plugin.cs:682
const string Author
Definition: PluginInfo.cs:11
string PluginVersion
Definition: Plugin.cs:764
const string PluginVersion
Definition: PluginInfo.cs:14
void Event_GameSelect(IntPtr InfoPtr)
Definition: Plugin.cs:419
string GetTableMappingFilename()
Definition: DOFManager.cs:195
void Load(string sFileName, bool bMerge=false)
Definition: IniFile.cs:26
void Event_App_Exit()
This method is called when PinballX exits
Definition: Plugin.cs:388
void UpdateNamedTableElement(string TableElementName, int Value)
Definition: DOFManager.cs:136
string Name
Definition: Plugin.cs:744
string[] GetConfiguredTableElmentDescriptors()
Definition: DOFManager.cs:180
bool Event_ScreenSaver(int Type)
This event is called when PinballX starts or quits the screen saver.
Definition: Plugin.cs:650
string Version
Definition: Plugin.cs:749
const string Description
Definition: PluginInfo.cs:13
static string ConfigFileName
Definition: Config.cs:26
bool Event_GameRun(IntPtr InfoPtr)
This method is called before a game is launched. Return true to tell PinballX process the event Retur...
Definition: Plugin.cs:451
void SaveConfigXmlFile(string FileName=null)
Serializes the configuration to a XML file.
Definition: Config.cs:60
const string Name
Definition: PluginInfo.cs:9
string TableName
Gets or sets the name of the table. This is not necessarly the same as the name of the table file...
Definition: Mapping.cs:19