mame core wip (#1705)

* add MAME to OpenAdvanced
* make mame launch games
limited to arcades that only need rom name. other devices require machine name and rom name, and won't run. nor they are meant to be supported anyway: we have enough emulators that do the job better for particular devices.
dunno if direct disk access will be avoidable, there are quite some files it might want to load other than the rom (parent rom, bios, artwork). trapping all of these might be a future task.
it is also known that mame can load "romname.zip" file just as well as "romname" folder, which would represent an unarchived zip. I make use of it to send it zip name with extension. it's easy, and we're not obliged to recognize mere folder paths in the mame-advanced-loader logic.
* ability to run lua code inside mame
This commit is contained in:
feos 2019-10-29 18:37:27 +03:00 committed by GitHub
parent d65092e967
commit 0247a8f1a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 903 additions and 175 deletions

View File

@ -117,6 +117,9 @@ namespace BizHawk.Client.ApiHawk
case "GB4x":
return CoreSystem.GB3x;
case "MAME":
return CoreSystem.MAME;
case "VB":
case "NGP":
case "DNGP":

View File

@ -34,8 +34,9 @@
ZXSpectrum,
AmstradCPC,
GGL,
ChannelF,
GB3x,
GB4x
GB4x,
ChannelF,
MAME
}
}

View File

@ -36,6 +36,7 @@ namespace BizHawk.Client.Common
public const string OpenRom = "OpenRom";
public const string Libretro = "Libretro";
public const string LibretroNoGame = "LibretroNoGame";
public const string MAME = "MAME";
}
@ -55,12 +56,33 @@ namespace BizHawk.Client.Common
string type = text.Substring(0, idx);
string token = text.Substring(idx + 1);
IOpenAdvanced ioa;
if (type == OpenAdvancedTypes.OpenRom) ioa = new OpenAdvanced_OpenRom();
else if (type == OpenAdvancedTypes.Libretro) ioa = new OpenAdvanced_Libretro();
else if (type == OpenAdvancedTypes.LibretroNoGame) ioa = new OpenAdvanced_LibretroNoGame();
else ioa = null;
if (ioa == null)
throw new InvalidOperationException($"{nameof(IOpenAdvanced)} deserialization error");
if (type == OpenAdvancedTypes.OpenRom)
{
ioa = new OpenAdvanced_OpenRom();
}
else if (type == OpenAdvancedTypes.Libretro)
{
ioa = new OpenAdvanced_Libretro();
}
else if (type == OpenAdvancedTypes.LibretroNoGame)
{
ioa = new OpenAdvanced_LibretroNoGame();
}
else if (type == OpenAdvancedTypes.MAME)
{
ioa = new OpenAdvanced_MAME();
}
else
{
ioa = null;
}
if (ioa == null)
{
throw new InvalidOperationException($"{nameof(IOpenAdvanced)} deserialization error");
}
ioa.Deserialize(token);
return ioa;
}
@ -161,4 +183,26 @@ namespace BizHawk.Client.Common
tw.Write(Path);
}
}
public class OpenAdvanced_MAME : IOpenAdvanced
{
public OpenAdvanced_MAME()
{ }
public string Path;
public string TypeName { get { return "MAME"; } }
public string DisplayName { get { return Path; } }
public string SimplePath { get { return Path; } }
public void Deserialize(string str)
{
Path = str;
}
public void Serialize(TextWriter tw)
{
tw.Write(Path);
}
}
}

View File

@ -24,6 +24,7 @@ using BizHawk.Emulation.Cores.Sega.Saturn;
using BizHawk.Emulation.Cores.Sony.PSP;
using BizHawk.Emulation.Cores.Sony.PSX;
using BizHawk.Emulation.Cores.Computers.SinclairSpectrum;
using BizHawk.Emulation.Cores.Arcades.MAME;
using BizHawk.Emulation.DiscSystem;
using GPGX64 = BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
@ -171,7 +172,7 @@ namespace BizHawk.Client.Common
return false;
}
public bool AsLibretro { get; set; }
public AdvancedRomLoaderType AdvancedLoader { get; set; }
private bool HandleArchiveBinding(HawkFile file)
{
@ -268,8 +269,13 @@ namespace BizHawk.Client.Common
// only try mounting a file if a filename was given
if (!string.IsNullOrEmpty(path))
{
// lets not use this unless we need to
// file.NonArchiveExtensions = romExtensions;
// MAME uses these extensions for arcade ROMs, but also accepts all sorts of variations of archives, folders, and files. if we let archive loader handle this, it won't know where to stop, since it'd require MAME's ROM database (which contains ROM names and blob hashes) to look things up, and even then it might be confused by archive/folder structure
// so assume the user provides the proper ROM directly, and handle possible errors later
if (AdvancedLoader == AdvancedRomLoaderType.MAMELaunchGame)
{
file.NonArchiveExtensions = new[] { ".zip", ".7z" };
}
file.Open(path);
// if the provided file doesnt even exist, give up!
@ -289,7 +295,7 @@ namespace BizHawk.Client.Common
{
string ext = null;
if (AsLibretro)
if (AdvancedLoader == AdvancedRomLoaderType.LibretroLaunchGame)
{
string codePathPart = Path.GetFileNameWithoutExtension(nextComm.LaunchLibretroCore);
@ -1152,6 +1158,9 @@ namespace BizHawk.Client.Common
nextEmulator = new Octoshock(nextComm, null, null, rom.FileData, GetCoreSettings<Octoshock>(), GetCoreSyncSettings<Octoshock>());
nextEmulator.CoreComm.RomStatusDetails = "PSX etc.";
break;
case "Arcade":
nextEmulator = new MAME(nextComm, file.Directory, file.CanonicalName);
break;
case "GEN":
if (Global.Config.CoreForcingViaGameDB && game.ForcedCore?.ToLower() == "pico")
{

View File

@ -223,6 +223,11 @@ namespace BizHawk.Client.Common
/// </summary>
public static SystemInfo ChannelF { get; } = new SystemInfo("Channel F", CoreSystem.ChannelF, 2);
/// <summary>
/// Gets the <see cref="SystemInfo"/> instance for MAME
/// </summary>
public static SystemInfo MAME { get; } = new SystemInfo("MAME", CoreSystem.MAME, 4);
#endregion Get SystemInfo
/// <summary>

View File

@ -1896,6 +1896,7 @@
</ItemGroup>
<ItemGroup>
<None Include="images\StopButton.png" />
<None Include="images\mame.png" />
<None Include="Resources\MoveTop.png" />
<None Include="Resources\MoveBottom.png" />
<None Include="Resources\MoveTop.bmp" />
@ -2225,6 +2226,7 @@
<None Include="images\ESE.png" />
<None Include="images\ControllerImages\NGPController.png" />
<Content Include="config\ControllerImages\ZXSpectrumKeyboards.png" />
<None Include="images\ControllerImages\ArcadeController.jpg" />
<Content Include="images\logo.ico" />
<None Include="images\Paste.png" />
<None Include="images\reboot.png" />

View File

@ -11,6 +11,7 @@ using BizHawk.Emulation.Cores.Nintendo.SNES9X;
using BizHawk.Emulation.Cores.Sega.Saturn;
using BizHawk.Emulation.Cores.Consoles.Sega.gpgx;
using BizHawk.Emulation.Cores.Sony.PSP;
using BizHawk.Emulation.Cores.Arcades.MAME;
using BizHawk.Client.Common;
@ -51,6 +52,10 @@ namespace BizHawk.Client.EmuHawk.CoreExtensions
{
return Properties.Resources.snes9x;
}
else if (core is MAME)
{
return Properties.Resources.mame;
}
else
{
return null;

View File

@ -306,6 +306,7 @@ namespace BizHawk.Client.EmuHawk
private void OpenRomMenuItem_Click(object sender, EventArgs e)
{
AdvancedLoader = AdvancedRomLoaderType.None;
OpenRom();
}
@ -317,7 +318,9 @@ namespace BizHawk.Client.EmuHawk
return;
}
if (oac.Result == OpenAdvancedChooser.Command.RetroLaunchNoGame)
AdvancedLoader = oac.Result;
if (AdvancedLoader == AdvancedRomLoaderType.LibretroLaunchNoGame)
{
var argsNoGame = new LoadRomArgs
{
@ -331,15 +334,20 @@ namespace BizHawk.Client.EmuHawk
var filter = RomFilter;
if (oac.Result == OpenAdvancedChooser.Command.RetroLaunchGame)
if (AdvancedLoader == AdvancedRomLoaderType.LibretroLaunchGame)
{
args.OpenAdvanced = new OpenAdvanced_Libretro();
filter = oac.SuggestedExtensionFilter;
}
else if (oac.Result == OpenAdvancedChooser.Command.ClassicLaunchGame)
else if (AdvancedLoader == AdvancedRomLoaderType.ClassicLaunchGame)
{
args.OpenAdvanced = new OpenAdvanced_OpenRom();
}
else if (AdvancedLoader == AdvancedRomLoaderType.MAMELaunchGame)
{
args.OpenAdvanced = new OpenAdvanced_MAME();
filter = "MAME Arcade ROMs (*.zip)|*.zip";
}
else
{
throw new InvalidOperationException("Automatic Alpha Sanitizer");

View File

@ -588,6 +588,8 @@ namespace BizHawk.Client.EmuHawk
// runloop won't exec lua
public bool SuppressLua { get; set; }
public AdvancedRomLoaderType AdvancedLoader { get; set; }
public long MouseWheelTracker { get; private set; }
private int? _pauseOnFrame;
@ -610,9 +612,7 @@ namespace BizHawk.Client.EmuHawk
}
public bool IsSeeking => PauseOnFrame.HasValue;
private bool IsTurboSeeking => PauseOnFrame.HasValue && Global.Config.TurboSeek;
public bool IsTurboing => Global.ClientControls["Turbo"] || IsTurboSeeking;
#endregion
@ -3488,15 +3488,13 @@ namespace BizHawk.Client.EmuHawk
return false;
}
bool asLibretro = args.OpenAdvanced is OpenAdvanced_Libretro || args.OpenAdvanced is OpenAdvanced_LibretroNoGame;
var loader = new RomLoader
{
ChooseArchive = LoadArchiveChooser,
ChoosePlatform = ChoosePlatformForRom,
Deterministic = deterministic,
MessageCallback = GlobalWin.OSD.AddMessage,
AsLibretro = asLibretro
AdvancedLoader = AdvancedLoader
};
Global.FirmwareManager.RecentlyServed.Clear();

View File

@ -28,147 +28,184 @@
/// </summary>
private void InitializeComponent()
{
this.label3 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.btnLibretroLaunchNoGame = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.txtLibretroCore = new System.Windows.Forms.TextBox();
this.btnLibretroLaunchGame = new System.Windows.Forms.Button();
this.btnSetLibretroCore = new System.Windows.Forms.Button();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.btnClassicLaunchGame = new System.Windows.Forms.Button();
this.groupBox2.SuspendLayout();
this.groupBox3.SuspendLayout();
this.SuspendLayout();
//
// label3
//
this.label3.Location = new System.Drawing.Point(6, 25);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(250, 29);
this.label3.TabIndex = 5;
this.label3.Text = "Load a rom with the classic BizHawk autodetection method. But why not just use Op" +
"en Rom?";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(6, 26);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(69, 13);
this.label2.TabIndex = 3;
this.label2.Text = "Current Core:";
//
// btnLibretroLaunchNoGame
//
this.btnLibretroLaunchNoGame.Location = new System.Drawing.Point(217, 50);
this.btnLibretroLaunchNoGame.Name = "btnLibretroLaunchNoGame";
this.btnLibretroLaunchNoGame.Size = new System.Drawing.Size(102, 23);
this.btnLibretroLaunchNoGame.TabIndex = 1;
this.btnLibretroLaunchNoGame.Text = "Launch No Game";
this.btnLibretroLaunchNoGame.UseVisualStyleBackColor = true;
this.btnLibretroLaunchNoGame.Click += new System.EventHandler(this.btnLibretroLaunchNoGame_Click);
//
// btnCancel
//
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(370, 176);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 2;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// groupBox2
//
this.groupBox2.Controls.Add(this.txtLibretroCore);
this.groupBox2.Controls.Add(this.btnLibretroLaunchGame);
this.groupBox2.Controls.Add(this.btnSetLibretroCore);
this.groupBox2.Controls.Add(this.label2);
this.groupBox2.Controls.Add(this.btnLibretroLaunchNoGame);
this.groupBox2.Location = new System.Drawing.Point(12, 12);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(433, 81);
this.groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Libretro";
//
// txtLibretroCore
//
this.txtLibretroCore.AllowDrop = true;
this.txtLibretroCore.Location = new System.Drawing.Point(81, 23);
this.txtLibretroCore.Name = "txtLibretroCore";
this.txtLibretroCore.ReadOnly = true;
this.txtLibretroCore.Size = new System.Drawing.Size(314, 20);
this.txtLibretroCore.TabIndex = 6;
this.txtLibretroCore.DragDrop += new System.Windows.Forms.DragEventHandler(this.txtLibretroCore_DragDrop);
this.txtLibretroCore.DragEnter += new System.Windows.Forms.DragEventHandler(this.txtLibretroCore_DragEnter);
//
// btnLibretroLaunchGame
//
this.btnLibretroLaunchGame.Location = new System.Drawing.Point(325, 50);
this.btnLibretroLaunchGame.Name = "btnLibretroLaunchGame";
this.btnLibretroLaunchGame.Size = new System.Drawing.Size(102, 23);
this.btnLibretroLaunchGame.TabIndex = 5;
this.btnLibretroLaunchGame.Text = "Launch Game";
this.btnLibretroLaunchGame.UseVisualStyleBackColor = true;
this.btnLibretroLaunchGame.Click += new System.EventHandler(this.btnLibretroLaunchGame_Click);
//
// btnSetLibretroCore
//
this.btnSetLibretroCore.AutoSize = true;
this.btnSetLibretroCore.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnSetLibretroCore.Location = new System.Drawing.Point(401, 21);
this.btnSetLibretroCore.Name = "btnSetLibretroCore";
this.btnSetLibretroCore.Size = new System.Drawing.Size(26, 23);
this.btnSetLibretroCore.TabIndex = 4;
this.btnSetLibretroCore.Text = "...";
this.btnSetLibretroCore.UseVisualStyleBackColor = true;
this.btnSetLibretroCore.Click += new System.EventHandler(this.btnSetLibretroCore_Click);
//
// groupBox3
//
this.groupBox3.Controls.Add(this.btnClassicLaunchGame);
this.groupBox3.Controls.Add(this.label3);
this.groupBox3.Location = new System.Drawing.Point(12, 99);
this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(277, 100);
this.groupBox3.TabIndex = 6;
this.groupBox3.TabStop = false;
this.groupBox3.Text = "BizHawk Classic";
//
// btnClassicLaunchGame
//
this.btnClassicLaunchGame.Location = new System.Drawing.Point(169, 71);
this.btnClassicLaunchGame.Name = "btnClassicLaunchGame";
this.btnClassicLaunchGame.Size = new System.Drawing.Size(102, 23);
this.btnClassicLaunchGame.TabIndex = 6;
this.btnClassicLaunchGame.Text = "Launch Game";
this.btnClassicLaunchGame.UseVisualStyleBackColor = true;
this.btnClassicLaunchGame.Click += new System.EventHandler(this.btnClassicLaunchGame_Click);
//
// OpenAdvancedChooser
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(457, 208);
this.Controls.Add(this.groupBox3);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.btnCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "OpenAdvancedChooser";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Open Advanced";
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.groupBox3.ResumeLayout(false);
this.ResumeLayout(false);
this.label3 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.btnLibretroLaunchNoGame = new System.Windows.Forms.Button();
this.btnCancel = new System.Windows.Forms.Button();
this.groupBox2 = new System.Windows.Forms.GroupBox();
this.txtLibretroCore = new System.Windows.Forms.TextBox();
this.btnLibretroLaunchGame = new System.Windows.Forms.Button();
this.btnSetLibretroCore = new System.Windows.Forms.Button();
this.groupBox3 = new System.Windows.Forms.GroupBox();
this.btnClassicLaunchGame = new System.Windows.Forms.Button();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.label1 = new System.Windows.Forms.Label();
this.btnMAMELaunchGame = new System.Windows.Forms.Button();
this.groupBox2.SuspendLayout();
this.groupBox3.SuspendLayout();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// label3
//
this.label3.Location = new System.Drawing.Point(6, 25);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(198, 45);
this.label3.TabIndex = 5;
this.label3.Text = "Load a ROM with the classic BizHawk autodetection method. But why not just use Op" +
"en Rom?";
//
// label2
//
this.label2.AutoSize = true;
this.label2.Location = new System.Drawing.Point(6, 26);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(69, 13);
this.label2.TabIndex = 3;
this.label2.Text = "Current Core:";
//
// btnLibretroLaunchNoGame
//
this.btnLibretroLaunchNoGame.Location = new System.Drawing.Point(217, 50);
this.btnLibretroLaunchNoGame.Name = "btnLibretroLaunchNoGame";
this.btnLibretroLaunchNoGame.Size = new System.Drawing.Size(102, 23);
this.btnLibretroLaunchNoGame.TabIndex = 1;
this.btnLibretroLaunchNoGame.Text = "Launch No Game";
this.btnLibretroLaunchNoGame.UseVisualStyleBackColor = true;
this.btnLibretroLaunchNoGame.Click += new System.EventHandler(this.btnLibretroLaunchNoGame_Click);
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
this.btnCancel.Location = new System.Drawing.Point(370, 221);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 2;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// groupBox2
//
this.groupBox2.Controls.Add(this.txtLibretroCore);
this.groupBox2.Controls.Add(this.btnLibretroLaunchGame);
this.groupBox2.Controls.Add(this.btnSetLibretroCore);
this.groupBox2.Controls.Add(this.label2);
this.groupBox2.Controls.Add(this.btnLibretroLaunchNoGame);
this.groupBox2.Location = new System.Drawing.Point(12, 12);
this.groupBox2.Name = "groupBox2";
this.groupBox2.Size = new System.Drawing.Size(433, 81);
this.groupBox2.TabIndex = 3;
this.groupBox2.TabStop = false;
this.groupBox2.Text = "Libretro";
//
// txtLibretroCore
//
this.txtLibretroCore.AllowDrop = true;
this.txtLibretroCore.Location = new System.Drawing.Point(81, 23);
this.txtLibretroCore.Name = "txtLibretroCore";
this.txtLibretroCore.ReadOnly = true;
this.txtLibretroCore.Size = new System.Drawing.Size(314, 20);
this.txtLibretroCore.TabIndex = 6;
this.txtLibretroCore.DragDrop += new System.Windows.Forms.DragEventHandler(this.txtLibretroCore_DragDrop);
this.txtLibretroCore.DragEnter += new System.Windows.Forms.DragEventHandler(this.txtLibretroCore_DragEnter);
//
// btnLibretroLaunchGame
//
this.btnLibretroLaunchGame.Location = new System.Drawing.Point(325, 50);
this.btnLibretroLaunchGame.Name = "btnLibretroLaunchGame";
this.btnLibretroLaunchGame.Size = new System.Drawing.Size(102, 23);
this.btnLibretroLaunchGame.TabIndex = 5;
this.btnLibretroLaunchGame.Text = "Launch Game";
this.btnLibretroLaunchGame.UseVisualStyleBackColor = true;
this.btnLibretroLaunchGame.Click += new System.EventHandler(this.btnLibretroLaunchGame_Click);
//
// btnSetLibretroCore
//
this.btnSetLibretroCore.AutoSize = true;
this.btnSetLibretroCore.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
this.btnSetLibretroCore.Location = new System.Drawing.Point(401, 21);
this.btnSetLibretroCore.Name = "btnSetLibretroCore";
this.btnSetLibretroCore.Size = new System.Drawing.Size(26, 23);
this.btnSetLibretroCore.TabIndex = 4;
this.btnSetLibretroCore.Text = "...";
this.btnSetLibretroCore.UseVisualStyleBackColor = true;
this.btnSetLibretroCore.Click += new System.EventHandler(this.btnSetLibretroCore_Click);
//
// groupBox3
//
this.groupBox3.Controls.Add(this.btnClassicLaunchGame);
this.groupBox3.Controls.Add(this.label3);
this.groupBox3.Location = new System.Drawing.Point(235, 99);
this.groupBox3.Name = "groupBox3";
this.groupBox3.Size = new System.Drawing.Size(210, 100);
this.groupBox3.TabIndex = 6;
this.groupBox3.TabStop = false;
this.groupBox3.Text = "BizHawk Classic";
//
// btnClassicLaunchGame
//
this.btnClassicLaunchGame.Location = new System.Drawing.Point(102, 71);
this.btnClassicLaunchGame.Name = "btnClassicLaunchGame";
this.btnClassicLaunchGame.Size = new System.Drawing.Size(102, 23);
this.btnClassicLaunchGame.TabIndex = 6;
this.btnClassicLaunchGame.Text = "Launch Game";
this.btnClassicLaunchGame.UseVisualStyleBackColor = true;
this.btnClassicLaunchGame.Click += new System.EventHandler(this.btnClassicLaunchGame_Click);
//
// groupBox1
//
this.groupBox1.Controls.Add(this.label1);
this.groupBox1.Controls.Add(this.btnMAMELaunchGame);
this.groupBox1.Location = new System.Drawing.Point(13, 99);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(216, 100);
this.groupBox1.TabIndex = 7;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "MAME Arcade";
//
// label1
//
this.label1.Location = new System.Drawing.Point(6, 25);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(204, 42);
this.label1.TabIndex = 1;
this.label1.Text = "Load .zip archive as MAME Arcade ROM (do not unzip)";
this.label1.Click += new System.EventHandler(this.btnMAMELaunchGame_Click);
//
// btnMAMELaunchGame
//
this.btnMAMELaunchGame.Location = new System.Drawing.Point(108, 71);
this.btnMAMELaunchGame.Name = "btnMAMELaunchGame";
this.btnMAMELaunchGame.Size = new System.Drawing.Size(102, 23);
this.btnMAMELaunchGame.TabIndex = 0;
this.btnMAMELaunchGame.Text = "Launch Game";
this.btnMAMELaunchGame.UseVisualStyleBackColor = true;
this.btnMAMELaunchGame.Click += new System.EventHandler(this.btnMAMELaunchGame_Click);
//
// OpenAdvancedChooser
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.CancelButton = this.btnCancel;
this.ClientSize = new System.Drawing.Size(457, 256);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.groupBox3);
this.Controls.Add(this.groupBox2);
this.Controls.Add(this.btnCancel);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "OpenAdvancedChooser";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent;
this.Text = "Open Advanced";
this.groupBox2.ResumeLayout(false);
this.groupBox2.PerformLayout();
this.groupBox3.ResumeLayout(false);
this.groupBox1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
@ -182,6 +219,9 @@
private System.Windows.Forms.TextBox txtLibretroCore;
private System.Windows.Forms.Button btnLibretroLaunchGame;
private System.Windows.Forms.GroupBox groupBox3;
private System.Windows.Forms.Button btnClassicLaunchGame;
private System.Windows.Forms.Button btnClassicLaunchGame;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button btnMAMELaunchGame;
}
}

View File

@ -7,7 +7,8 @@ using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores;
using BizHawk.Emulation.Cores.Libretro;
using BizHawk.Client.Common;
@ -20,13 +21,7 @@ namespace BizHawk.Client.EmuHawk
{
MainForm mainForm;
public enum Command
{
RetroLaunchNoGame, RetroLaunchGame,
ClassicLaunchGame
}
public Command Result;
public AdvancedRomLoaderType Result;
public string SuggestedExtensionFilter;
public OpenAdvancedChooser(MainForm mainForm)
@ -131,21 +126,28 @@ namespace BizHawk.Client.EmuHawk
filter = MainForm.FormatFilter(args.ToArray());
SuggestedExtensionFilter = filter;
Result = Command.RetroLaunchGame;
Result = AdvancedRomLoaderType.LibretroLaunchGame;
DialogResult = DialogResult.OK;
Close();
}
private void btnMAMELaunchGame_Click(object sender, EventArgs e)
{
Result = AdvancedRomLoaderType.MAMELaunchGame;
DialogResult = DialogResult.OK;
Close();
}
private void btnClassicLaunchGame_Click(object sender, EventArgs e)
{
Result = Command.ClassicLaunchGame;
Result = AdvancedRomLoaderType.ClassicLaunchGame;
DialogResult = DialogResult.OK;
Close();
}
private void btnLibretroLaunchNoGame_Click(object sender, EventArgs e)
{
Result = Command.RetroLaunchNoGame;
Result = AdvancedRomLoaderType.LibretroLaunchNoGame;
DialogResult = DialogResult.OK;
Close();
}
@ -170,6 +172,6 @@ namespace BizHawk.Client.EmuHawk
var filePaths = (string[])e.Data.GetData(DataFormats.FileDrop);
Global.Config.LibretroCore = filePaths[0];
RefreshLibretroCore(false);
}
}
}
}

View File

@ -120,6 +120,16 @@ namespace BizHawk.Client.EmuHawk.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap ArcadeController {
get {
object obj = ResourceManager.GetObject("ArcadeController", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
@ -870,6 +880,16 @@ namespace BizHawk.Client.EmuHawk.Properties {
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>
internal static System.Drawing.Bitmap mame {
get {
object obj = ResourceManager.GetObject("mame", resourceCulture);
return ((System.Drawing.Bitmap)(obj));
}
}
/// <summary>
/// Looks up a localized resource of type System.Drawing.Bitmap.
/// </summary>

View File

@ -1566,4 +1566,10 @@
<data name="StopButton" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\images\StopButton.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="ArcadeController" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\images\ControllerImages\ArcadeController.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="mame" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\images\mame.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

View File

@ -49,6 +49,7 @@ namespace BizHawk.Client.EmuHawk
ControllerImages.Add("Apple IIe Keyboard", Properties.Resources.AppleIIKeyboard);
ControllerImages.Add("VirtualBoy Controller", Properties.Resources.VBoyController);
ControllerImages.Add("NeoGeo Portable Controller", Properties.Resources.NGPController);
ControllerImages.Add("MAME Controller", Properties.Resources.ArcadeController);
}
private ControllerConfig()
@ -192,7 +193,10 @@ namespace BizHawk.Client.EmuHawk
if (Global.Emulator.SystemId == "ZXSpectrum" || Global.Emulator.SystemId == "AmstradCPC" || Global.Emulator.SystemId == "ChannelF")
return;
string tabname = (Global.Emulator.SystemId == "C64") ? "Keyboard" : "Console"; // hack
string tabname =
(Global.Emulator.SystemId == "C64") ? "Keyboard" :
(Global.Emulator.SystemId == "MAME") ? "Misc" :
"Console"; // hack
tt.TabPages.Add(tabname);
tt.TabPages[pageidx].Controls.Add(createpanel(settings, buckets[0], tt.Size));
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

View File

@ -371,6 +371,7 @@ namespace BizHawk.Emulation.Common
case ".UZE":
game.System = "UZE";
break;
case ".32X":
game.System = "32X";
game.AddOption("32X", "true");
@ -380,6 +381,12 @@ namespace BizHawk.Emulation.Common
game.System = "VEC";
game.AddOption("VEC", "true");
break;
// refactor to use mame db (output of "mame -listxml" command)
// there's no good definition for Arcade anymore, so we might limit to coin-based machines?
case ".ZIP":
game.System = "Arcade";
break;
}
game.Name = Path.GetFileNameWithoutExtension(fileName)?.Replace('_', ' ');

View File

@ -35,4 +35,16 @@
Overdump,
NotInDatabase
}
/// <summary>
/// The Advanced ROM Loader type in MainForm/RomLoader/OpenAdvancedChooser
/// </summary>
public enum AdvancedRomLoaderType
{
None,
LibretroLaunchNoGame,
LibretroLaunchGame,
ClassicLaunchGame,
MAMELaunchGame
}
}

View File

@ -0,0 +1,76 @@
using System;
using System.Runtime.InteropServices;
namespace BizHawk.Emulation.Cores.Arcades.MAME
{
public static class LibMAME
{
const string dll = "libmamearcade64.dll"; // libmamearcade64.dll libpacmansh64d.dll
const CallingConvention cc = CallingConvention.Cdecl;
public enum OutputChannel
{
ERROR, WARNING, INFO, DEBUG, VERBOSE, LOG, COUNT
};
// main launcher
[DllImport(dll, CallingConvention = cc)]
public static extern UInt32 mame_launch(int argc, string[] argv);
#region Lua API
// execute
[DllImport(dll, CallingConvention = cc)]
public static extern void mame_lua_execute(string code);
// get int
[DllImport(dll, CallingConvention = cc)]
public static extern int mame_lua_get_int(string code);
// get double
[DllImport(dll, CallingConvention = cc)]
public static extern double mame_lua_get_double(string code);
// get bool
[DllImport(dll, CallingConvention = cc)]
public static extern bool mame_lua_get_bool(string code);
// get string
[DllImport(dll, CallingConvention = cc)]
public static extern IntPtr mame_lua_get_string(string code, out int length);
// free string
[DllImport(dll, CallingConvention = cc)]
public static extern bool mame_lua_free_string(IntPtr pointer);
#endregion
#region Callbacks
// periodic
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void PeriodicCallbackDelegate();
[DllImport(dll, CallingConvention = cc)]
public static extern void mame_set_periodic_callback(PeriodicCallbackDelegate cb);
// sound
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void SoundCallbackDelegate();
[DllImport(dll, CallingConvention = cc)]
public static extern void mame_set_sound_callback(SoundCallbackDelegate cb);
// boot
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void BootCallbackDelegate();
[DllImport(dll, CallingConvention = cc)]
public static extern void mame_set_boot_callback(BootCallbackDelegate cb);
// log
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void LogCallbackDelegate(OutputChannel channel, int size, string data);
[DllImport(dll, CallingConvention = cc)]
public static extern void mame_set_log_callback(LogCallbackDelegate cb);
#endregion
}
}

View File

@ -0,0 +1,484 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Common.IEmulatorExtensions;
namespace BizHawk.Emulation.Cores.Arcades.MAME
{
[Core(
name: "MAME",
author: "MAMEDev",
isPorted: true,
portedVersion: "0.214",
portedUrl: "https://github.com/mamedev/mame.git",
singleInstance: false)]
public partial class MAME : IEmulator, IVideoProvider, ISoundProvider
{
public MAME(CoreComm comm, string dir, string file)
{
ServiceProvider = new BasicServiceProvider(this);
CoreComm = comm;
gameDirectory = dir;
gameFilename = file;
MAMEThread = new Thread(ExecuteMAMEThread);
AsyncLaunchMAME();
}
#region Properties
public CoreComm CoreComm { get; private set; }
public IEmulatorServiceProvider ServiceProvider { get; private set; }
public ControllerDefinition ControllerDefinition => MAMEController;
public string SystemId => "MAME";
public int[] GetVideoBuffer() => frameBuffer;
public bool DeterministicEmulation => true;
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
public int BackgroundColor => 0;
public int Frame { get; private set; }
public int VirtualWidth { get; private set; } = 320;
public int VirtualHeight { get; private set; } = 240;
public int BufferWidth { get; private set; } = 320;
public int BufferHeight { get; private set; } = 240;
public int VsyncNumerator { get; private set; } = 60;
public int VsyncDenominator { get; private set; } = 1;
private int samplesPerFrame => (int)Math.Round(sampleRate / this.VsyncRate());
#endregion
#region Fields
private Thread MAMEThread;
private ManualResetEvent MAMEStartupComplete = new ManualResetEvent(false);
private ManualResetEvent MAMEFrameComplete = new ManualResetEvent(false);
private SortedDictionary<string, string> fieldsPorts = new SortedDictionary<string, string>();
private IController Controller = NullController.Instance;
private int[] frameBuffer = new int[0];
private short[] audioBuffer = new short[0];
private Queue<short> audioSamples = new Queue<short>();
private int sampleRate = 44100;
private bool paused = true;
private bool exiting = false;
private bool frameDone = true;
private int numSamples = 0;
private string gameDirectory;
private string gameFilename;
private LibMAME.PeriodicCallbackDelegate periodicCallback;
private LibMAME.SoundCallbackDelegate soundCallback;
private LibMAME.BootCallbackDelegate bootCallback;
private LibMAME.LogCallbackDelegate logCallback;
#endregion
#region IEmulator
public bool FrameAdvance(IController controller, bool render, bool rendersound = true)
{
if (exiting)
{
return false;
}
Controller = controller;
paused = false;
frameDone = false;
for (; frameDone == false;)
{
MAMEFrameComplete.WaitOne();
}
Frame++;
return true;
}
public void ResetCounters()
{
Frame = 0;
}
public void Dispose()
{
exiting = true;
MAMEThread.Join();
}
#endregion
#region ISoundProvider
public void SetSyncMode(SyncSoundMode mode)
{
if (mode == SyncSoundMode.Async)
{
throw new NotSupportedException("Async mode is not supported.");
}
}
public void GetSamplesSync(out short[] samples, out int nsamp)
{
nsamp = samplesPerFrame;
samples = new short[samplesPerFrame * 2];
for (int i = 0; i < samplesPerFrame * 2; i++)
{
if (audioSamples.Any())
{
samples[i] = audioSamples.Dequeue();
}
else
{
samples[i] = 0;
}
}
}
public void GetSamplesAsync(short[] samples)
{
throw new InvalidOperationException("Async mode is not supported.");
}
public void DiscardSamples()
{
audioSamples.Clear();
}
#endregion
#region Launchers
private void AsyncLaunchMAME()
{
MAMEThread.Start();
MAMEStartupComplete.WaitOne();
}
private void ExecuteMAMEThread()
{
// dodge GC
periodicCallback = MAMEPeriodicCallback;
soundCallback = MAMESoundCallback;
bootCallback = MAMEBootCallback;
logCallback = MAMELogCallback;
LibMAME.mame_set_periodic_callback(periodicCallback);
LibMAME.mame_set_sound_callback(soundCallback);
LibMAME.mame_set_boot_callback(bootCallback);
LibMAME.mame_set_log_callback(logCallback);
// https://docs.mamedev.org/commandline/commandline-index.html
string[] args = new string[] {
"mame" // dummy, internally discarded by index, so has to go first
, gameFilename // no dash for rom names
, "-noreadconfig" // forbid reading any config files
, "-norewind" // forbid rewind savestates (captured upon frame advance)
, "-skip_gameinfo" // forbid this blocking screen that requires user input
, "-nothrottle" // forbid throttling to "real" speed of the device
, "-update_in_pause" // ^ including frame-advancing
, "-rompath", gameDirectory // mame doesn't load roms from full paths, only from dirs to scan
, "-volume", "-32" // lowest attenuation means mame osd remains silent
, "-output", "console" // print everyting to hawk console
, "-samplerate", sampleRate.ToString() // match hawk samplerate
, "-video", "none" // forbid mame window altogether
, "-keyboardprovider", "none"
, "-mouseprovider", "none"
, "-lightgunprovider", "none"
, "-joystickprovider", "none"
};
LibMAME.mame_launch(args.Length, args);
}
#endregion
#region Updaters
private void UpdateFramerate()
{
VsyncNumerator = 1000000000;
UInt64 refresh = (UInt64)LibMAME.mame_lua_get_double(MAMELuaCommand.GetRefresh);
VsyncDenominator = (int)(refresh / 1000000000);
}
private void UpdateAspect()
{
int x = (int)LibMAME.mame_lua_get_double(MAMELuaCommand.GetBoundX);
int y = (int)LibMAME.mame_lua_get_double(MAMELuaCommand.GetBoundY);
VirtualHeight = BufferWidth > BufferHeight * x / y
? BufferWidth * y / x
: BufferHeight;
VirtualWidth = VirtualHeight * x / y;
}
private void UpdateVideo()
{
BufferWidth = LibMAME.mame_lua_get_int(MAMELuaCommand.GetWidth);
BufferHeight = LibMAME.mame_lua_get_int(MAMELuaCommand.GetHeight);
int expectedSize = BufferWidth * BufferHeight;
int bytesPerPixel = 4;
int lengthInBytes;
IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetPixels, out lengthInBytes);
if (ptr == IntPtr.Zero)
{
Console.WriteLine("LibMAME ERROR: frame buffer pointer is null");
return;
}
if (expectedSize * bytesPerPixel != lengthInBytes)
{
Console.WriteLine(
"LibMAME ERROR: frame buffer has wrong size\n" +
$"width: { BufferWidth } pixels\n" +
$"height: { BufferHeight } pixels\n" +
$"expected: { expectedSize * bytesPerPixel } bytes\n" +
$"received: { lengthInBytes } bytes\n");
return;
}
frameBuffer = new int[expectedSize];
Marshal.Copy(ptr, frameBuffer, 0, expectedSize);
if (!LibMAME.mame_lua_free_string(ptr))
{
Console.WriteLine("LibMAME ERROR: frame buffer wasn't freed");
}
}
private void UpdateInput()
{
foreach (var fieldPort in fieldsPorts)
{
LibMAME.mame_lua_execute(
"manager:machine():ioport()" +
$".ports [\"{ fieldPort.Value }\"]" +
$".fields [\"{ fieldPort.Key }\"]" +
$":set_value({ (Controller.IsPressed(fieldPort.Key) ? 1 : 0) })");
}
}
private void Update()
{
UpdateFramerate();
UpdateVideo();
UpdateAspect();
UpdateInput();
}
private void CheckVersions()
{
int lengthInBytes;
IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetVersion, out lengthInBytes);
string MAMEVersion = Marshal.PtrToStringAnsi(ptr, lengthInBytes);
if (!LibMAME.mame_lua_free_string(ptr))
{
Console.WriteLine("LibMAME ERROR: string buffer wasn't freed");
}
string version = this.Attributes().PortedVersion;
Debug.Assert(version == MAMEVersion,
"MAME versions desync!\n\n" +
$"MAME is { MAMEVersion }\n" +
$"MAMEHawk is { version }");
}
#endregion
#region Callbacks
/*
* FrameAdvance() and MAME
*
* MAME fires the periodic callback on every video and debugger update,
* which happens every VBlank and also repeatedly at certain time
* intervals while paused. Since MAME's luaengine runs in a separate
* thread, it's only safe to update everything we need per frame during
* this callback, when it's explicitly waiting for further lua commands.
*
* If we disable throttling and pass -update_in_pause, there will be no
* delay between video updates. This allows to run at full speed while
* frame-stepping.
*
* MAME only captures new frame data once per VBlank, while unpaused.
* But it doesn't have an exclusive VBlank callback we could attach to.
* It has a LUA_ON_FRAME_DONE callback, but that fires even more
* frequently and updates all sorts of other non-video stuff, and we
* need none of that here.
*
* So we filter out all the calls that happen while paused (non-VBlank
* updates). Then, when Hawk asks us to advance a frame, we virtually
* unpause and declare the new frame unfinished. This informs MAME that
* it should advance one frame internally. Hawk starts waiting for the
* MAME thread to complete the request.
*
* After MAME's done advancing, it fires the periodic callback again.
* That's when we update everything and declare the new frame finished,
* filtering out any further updates again. Then we allow Hawk to
* complete frame-advancing.
*/
private void MAMEPeriodicCallback()
{
if (exiting)
{
LibMAME.mame_lua_execute(MAMELuaCommand.Exit);
exiting = false;
}
int MAMEFrame = LibMAME.mame_lua_get_int(MAMELuaCommand.GetFrameNumber);
if (!paused)
{
LibMAME.mame_lua_execute(MAMELuaCommand.Step);
frameDone = false;
paused = true;
}
else if (!frameDone)
{
Update();
frameDone = true;
MAMEFrameComplete.Set();
}
}
private void MAMESoundCallback()
{
int bytesPerSample = 2;
int lengthInBytes;
IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetSamples, out lengthInBytes);
if (ptr == IntPtr.Zero)
{
Console.WriteLine("LibMAME ERROR: audio buffer pointer is null");
return;
}
numSamples = lengthInBytes / bytesPerSample;
unsafe
{
short* pSample = (short*)ptr.ToPointer();
for (int i = 0; i < numSamples; i++)
{
audioSamples.Enqueue(*(pSample + i));
}
}
if (!LibMAME.mame_lua_free_string(ptr))
{
Console.WriteLine("LibMAME ERROR: audio buffer wasn't freed");
}
}
private void MAMEBootCallback()
{
LibMAME.mame_lua_execute(MAMELuaCommand.Pause);
CheckVersions();
GetInputFields();
Update();
MAMEStartupComplete.Set();
}
private void MAMELogCallback(LibMAME.OutputChannel channel, int size, string data)
{
// mame sends osd_output_channel casted to int, we implicitly cast is back
if (!data.Contains("pause = "))
{
Console.WriteLine(
$"[MAME { channel.ToString() }] " +
$"{ data.Replace('\n', ' ') }");
}
}
#endregion
#region Input
public static ControllerDefinition MAMEController = new ControllerDefinition
{
Name = "MAME Controller",
BoolButtons = new List<string>()
};
private void GetInputFields()
{
int lengthInBytes;
IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetInputFields, out lengthInBytes);
if (ptr == IntPtr.Zero)
{
Console.WriteLine("LibMAME ERROR: string buffer pointer is null");
return;
}
string inputFields = Marshal.PtrToStringAnsi(ptr, lengthInBytes);
string[] portFields = inputFields.Split(';');
MAMEController.BoolButtons.Clear();
foreach (string portField in portFields)
{
if (portField != string.Empty)
{
string[] substrings = portField.Split(',');
string tag = substrings.First();
string field = substrings.Last();
fieldsPorts.Add(field, tag);
MAMEController.BoolButtons.Add(field);
}
}
if (!LibMAME.mame_lua_free_string(ptr))
{
Console.WriteLine("LibMAME ERROR: string buffer wasn't freed");
}
}
#endregion
#region Lua Commands
private class MAMELuaCommand
{
public const string Step = "emu.step()";
public const string Pause = "emu.pause()";
public const string Unpause = "emu.unpause()";
public const string Exit = "manager:machine():exit()";
public const string GetVersion = "return emu.app_version()";
public const string GetPixels = "return manager:machine():video():pixels()";
public const string GetSamples = "return manager:machine():sound():samples()";
public const string GetFrameNumber = "return select(2, next(manager:machine().screens)):frame_number()";
public const string GetRefresh = "return select(2, next(manager:machine().screens)):refresh_attoseconds()";
public const string GetWidth = "return (select(1, manager:machine():video():size()))";
public const string GetHeight = "return (select(2, manager:machine():video():size()))";
public const string GetBoundX =
"local x0,x1,y0,y1 = manager:machine():render():ui_target():view_bounds() " +
"return x1-x0";
public const string GetBoundY =
"local x0,x1,y0,y1 = manager:machine():render():ui_target():view_bounds() " +
"return y1-y0";
public const string GetInputFields =
"final = {} " +
"for tag, _ in pairs(manager:machine():ioport().ports) do " +
"for name, field in pairs(manager:machine():ioport().ports[tag].fields) do " +
"if field.type_class ~= \"dipswitch\" then " +
"table.insert(final, string.format(\"%s,%s;\", tag, name)) " +
"end " +
"end " +
"end " +
"table.sort(final) " +
"return table.concat(final)";
}
#endregion
}
}

View File

@ -103,6 +103,8 @@
<Compile Include="..\Version\VersionInfo.cs">
<Link>VersionInfo.cs</Link>
</Compile>
<Compile Include="Arcades\MAME\LibMAME.cs" />
<Compile Include="Arcades\MAME\MAME.cs" />
<Compile Include="Calculator\TI83.IEmulator.cs">
<DependentUpon>TI83.cs</DependentUpon>
</Compile>