MAME Waterbox (#3437)

This commit is contained in:
CasualPokePlayer 2022-11-10 00:05:25 -08:00 committed by GitHub
parent e7e587d625
commit 066297d5e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 45803 additions and 589 deletions

4
.gitmodules vendored
View File

@ -51,3 +51,7 @@
path = libmupen64plus/mupen64plus-video-angrylion-rdp
url = https://github.com/TASEmulators/angrylion-rdp.git
branch = biz_mupen64plus
[submodule "waterbox/mame-arcade/mame"]
path = waterbox/mame-arcade/mame
url = https://github.com/TASEmulators/mame.git
branch = mamehawk0249

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@ namespace BizHawk.Client.Common
VSystemID.Raw.AppleII => CoreSystem.AppleII,
VSystemID.Raw.A26 => CoreSystem.Atari2600,
VSystemID.Raw.A78 => CoreSystem.Atari7800,
VSystemID.Raw.Arcade => CoreSystem.Arcade,
VSystemID.Raw.Coleco => CoreSystem.ColecoVision,
VSystemID.Raw.C64 => CoreSystem.Commodore64,
VSystemID.Raw.GBL => CoreSystem.GameBoyLink,
@ -57,7 +58,6 @@ namespace BizHawk.Client.Common
VSystemID.Raw.AmstradCPC => CoreSystem.AmstradCPC,
VSystemID.Raw.GGL => CoreSystem.GGL,
VSystemID.Raw.ChannelF => CoreSystem.ChannelF,
VSystemID.Raw.MAME => CoreSystem.MAME,
VSystemID.Raw.O2 => CoreSystem.Odyssey2,
VSystemID.Raw.MSX => CoreSystem.MSX,
VSystemID.Raw.VB => CoreSystem.VirtualBoy,
@ -98,6 +98,7 @@ namespace BizHawk.Client.Common
CoreSystem.AppleII => VSystemID.Raw.AppleII,
CoreSystem.Atari2600 => VSystemID.Raw.A26,
CoreSystem.Atari7800 => VSystemID.Raw.A78,
CoreSystem.Arcade => VSystemID.Raw.Arcade,
CoreSystem.ChannelF => VSystemID.Raw.ChannelF,
CoreSystem.ColecoVision => VSystemID.Raw.Coleco,
CoreSystem.Commodore64 => VSystemID.Raw.C64,
@ -109,7 +110,6 @@ namespace BizHawk.Client.Common
CoreSystem.Intellivision => VSystemID.Raw.INTV,
CoreSystem.Libretro => VSystemID.Raw.Libretro,
CoreSystem.Lynx => VSystemID.Raw.Lynx,
CoreSystem.MAME => VSystemID.Raw.MAME,
CoreSystem.MasterSystem => VSystemID.Raw.SMS,
CoreSystem.MSX => VSystemID.Raw.MSX,
CoreSystem.NeoGeoPocket => VSystemID.Raw.NGP,

View File

@ -36,7 +36,7 @@
GGL,
ChannelF,
Odyssey2,
MAME,
Arcade,
MSX,
SuperGameBoy,
UzeBox,

View File

@ -476,15 +476,6 @@ namespace BizHawk.Client.Common
game.System = VSystemID.Raw.SGB;
}
break;
case VSystemID.Raw.MAME:
nextEmulator = new MAME(
file.Directory,
file.CanonicalName,
GetCoreSyncSettings<MAME, MAME.MAMESyncSettings>(),
out var gameName
);
rom.GameInfo.Name = gameName;
return;
}
var cip = new CoreInventoryParameters(this)
{
@ -548,6 +539,7 @@ namespace BizHawk.Client.Common
RomData = kvp.Value,
FileData = kvp.Value, // TODO: Hope no one needed anything special here
Extension = Path.GetExtension(kvp.Key),
RomPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path.SubstringBefore('|')), kvp.Key)),
Game = Database.GetGameInfo(kvp.Value, Path.GetFileName(kvp.Key))
})
.ToList(),
@ -604,7 +596,7 @@ namespace BizHawk.Client.Common
}
bool allowArchives = true;
if (OpenAdvanced is OpenAdvanced_MAME) allowArchives = false;
if (OpenAdvanced is OpenAdvanced_MAME || MAMEMachineDB.IsMAMEMachine(path)) allowArchives = false;
using var file = new HawkFile(path, false, allowArchives);
if (!file.Exists && OpenAdvanced is not OpenAdvanced_LibretroNoGame) return false; // if the provided file doesn't even exist, give up! (unless libretro no game is used)
@ -788,6 +780,8 @@ namespace BizHawk.Client.Common
public static readonly IReadOnlyCollection<string> AppleII = new[] { "dsk", "do", "po" };
public static readonly IReadOnlyCollection<string> Arcade = new[] { "zip", "chd" };
public static readonly IReadOnlyCollection<string> C64 = new[] { "prg", "d64", "g64", "crt", "tap" };
public static readonly IReadOnlyCollection<string> Coleco = new[] { "col" };
@ -905,6 +899,7 @@ namespace BizHawk.Client.Common
new FilesystemFilter("Uzebox", RomFileExtensions.UZE),
new FilesystemFilter("Vectrex", RomFileExtensions.VEC),
new FilesystemFilter("MSX", RomFileExtensions.MSX),
new FilesystemFilter("Arcade", RomFileExtensions.Arcade),
FilesystemFilter.EmuHawkSaveStates)
{
CombinedEntryDesc = "Everything",

View File

@ -8,6 +8,7 @@ using BizHawk.Common;
using BizHawk.Common.IOExtensions;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Arcades.MAME;
namespace BizHawk.Client.Common
{
@ -76,7 +77,7 @@ namespace BizHawk.Client.Common
fullPath = Path.Combine(fullPath, filename.SubstringBefore('|'));
try
{
using var hf = new HawkFile(fullPath);
using var hf = new HawkFile(fullPath, allowArchives: !MAMEMachineDB.IsMAMEMachine(fullPath));
if (hf.IsArchive)
{
var archiveItem = hf.ArchiveItems.First(ai => ai.Name == filename.Split('|').Skip(1).First());

View File

@ -22,6 +22,7 @@ namespace BizHawk.Client.Common
private static readonly Dictionary<string, string> _displayNameLookup = new()
{
[GLOBAL] = "Global",
[VSystemID.Raw.Arcade] = "Arcade",
[VSystemID.Raw.INTV] = "Intellivision",
[VSystemID.Raw.NES] = "NES",
[VSystemID.Raw.SNES] = "SNES",
@ -198,6 +199,8 @@ namespace BizHawk.Client.Common
CommonEntriesFor(VSystemID.Raw.AppleII, basePath: Path.Combine(".", "Apple II"), omitSaveRAM: true),
CommonEntriesFor(VSystemID.Raw.Arcade, basePath: Path.Combine(".", "Arcade")),
CommonEntriesFor(VSystemID.Raw.C64, basePath: Path.Combine(".", "C64"), omitSaveRAM: true),
CommonEntriesFor(VSystemID.Raw.ChannelF, basePath: Path.Combine(".", "Channel F"), omitSaveRAM: true),

View File

@ -21,6 +21,7 @@ namespace BizHawk.Client.Common
public const string SyncSettings = "SyncSettings";
public const string CycleCount = "CycleCount";
public const string ClockRate = "ClockRate";
public const string VsyncAttoseconds = "VsyncAttoseconds"; // used for Arcade due to it representing thousands of different systems with different vsync rates
public const string Core = "Core";
public static bool Contains(string val) =>

View File

@ -5,6 +5,7 @@ using System.Linq;
using BizHawk.Common;
using BizHawk.Common.PathExtensions;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Arcades.MAME;
using BizHawk.Emulation.Cores.Atari.Jaguar;
using BizHawk.Emulation.Cores.Consoles.Nintendo.Gameboy;
using BizHawk.Emulation.Cores.Consoles.Nintendo.NDS;
@ -285,6 +286,11 @@ namespace BizHawk.Client.Common
movie.HeaderEntries.Add("IsJaguarCD", "1");
}
if (emulator is MAME mame)
{
movie.HeaderEntries.Add(HeaderKeys.VsyncAttoseconds, mame.VsyncAttoseconds.ToString());
}
if (emulator is ICycleTiming)
{
movie.HeaderEntries.Add(HeaderKeys.CycleCount, "0");

View File

@ -511,7 +511,7 @@ namespace BizHawk.Client.Common
["CLR"] = 'c',
["ENT"] = 'e'
},
[VSystemID.Raw.MAME] = new()
[VSystemID.Raw.Arcade] = new()
{
["1 Player Start"] = '1',
["2 Players Start"] = '2',
@ -521,6 +521,7 @@ namespace BizHawk.Client.Common
["6 Players Start"] = '6',
["7 Players Start"] = '7',
["8 Players Start"] = '8',
["Attack"] = 'a',
["Board 0 (SW4)"] = '0',
["Board 1 (SW5)"] = '1',
["Board 2 (SW6)"] = '2',
@ -549,13 +550,12 @@ namespace BizHawk.Client.Common
["Handle A"] = 'A',
["Handle B"] = 'B',
["Jab Punch"] = 'J',
["Jump"] = 'j',
["Left Stick/Up"] = '^',
["Left Stick/Down"] = 'v',
["Left Stick/Left"] = '<',
["Left Stick/Right"] = '>',
["Light"] = 'l',
["Lightgun X"] = 'X',
["Lightgun Y"] = 'Y',
["Medium"] = 'm',
["Paddle"] = 'P',
["Pedal 1"] = '1',

View File

@ -92,7 +92,21 @@ namespace BizHawk.Client.Common
}
}
public double FrameRate => PlatformFrameRates.GetFrameRate(SystemID, IsPal);
public double FrameRate
{
get
{
if (SystemID == VSystemID.Raw.Arcade && Header.TryGetValue(HeaderKeys.VsyncAttoseconds, out var vsyncAttoStr))
{
const decimal attosInSec = 1000000000000000000;
return (double)(attosInSec / Convert.ToUInt64(vsyncAttoStr));
}
else
{
return PlatformFrameRates.GetFrameRate(SystemID, IsPal);
}
}
}
public IStringLog GetLogEntries() => Log;

View File

@ -24,6 +24,7 @@ using BizHawk.Bizware.BizwareGL;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Common.Base_Implementations;
using BizHawk.Emulation.Cores;
using BizHawk.Emulation.Cores.Arcades.MAME;
using BizHawk.Emulation.Cores.Calculators.TI83;
using BizHawk.Emulation.Cores.Consoles.NEC.PCE;
using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES;
@ -355,6 +356,7 @@ namespace BizHawk.Client.EmuHawk
userRoot: Path.Combine(PathUtils.DataDirectoryPath, "gamedb"),
silent: true);
BootGodDb.Initialize(Path.Combine(PathUtils.ExeDirectoryPath, "gamedb"));
MAMEMachineDB.Initialize(Path.Combine(PathUtils.ExeDirectoryPath, "gamedb"));
_argParser = cliFlags;
_getConfigPath = getConfigPath;

View File

@ -11,6 +11,7 @@ using BizHawk.Client.Common;
using BizHawk.Common;
using BizHawk.Common.CollectionExtensions;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Arcades.MAME;
namespace BizHawk.Client.EmuHawk
{
@ -405,6 +406,13 @@ namespace BizHawk.Client.EmuHawk
item.ToolTipText = $"Expected: {v}\n Actual: {_emulator.SystemId}";
}
break;
case HeaderKeys.VsyncAttoseconds:
if (_emulator is MAME mame && mame.VsyncAttoseconds != Convert.ToInt64(v))
{
item.BackColor = Color.Pink;
item.ToolTipText = $"Expected: {v}\n Actual: {mame.VsyncAttoseconds}";
}
break;
default:
if (k.Contains("_Firmware_"))
{

View File

@ -78,7 +78,7 @@
// grpName
//
this.grpName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
| System.Windows.Forms.AnchorStyles.Right)));
this.grpName.Controls.Add(this.BrowseBtn);
this.grpName.Controls.Add(this.NameBox);
this.grpName.Location = new System.Drawing.Point(8, 28);
@ -102,7 +102,7 @@
// NameBox
//
this.NameBox.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
| System.Windows.Forms.AnchorStyles.Right)));
this.NameBox.Location = new System.Drawing.Point(6, 19);
this.NameBox.Name = "NameBox";
this.NameBox.Size = new System.Drawing.Size(405, 20);
@ -112,8 +112,8 @@
// FileSelectorPanel
//
this.FileSelectorPanel.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.FileSelectorPanel.AutoScroll = true;
this.FileSelectorPanel.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.FileSelectorPanel.Location = new System.Drawing.Point(8, 101);
@ -137,20 +137,6 @@
this.SystemDropDown.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.SystemDropDown.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.SystemDropDown.FormattingEnabled = true;
this.SystemDropDown.Items.AddRange(new object[] {
"AppleII",
"C64",
"GBL",
"GEN",
"NDS",
"PCFX",
"PSX",
"SAT",
"ZXSpectrum",
"AmstradCPC",
"GGL",
"TI83",
"Jaguar"});
this.SystemDropDown.Location = new System.Drawing.Point(405, 75);
this.SystemDropDown.Name = "SystemDropDown";
this.SystemDropDown.Size = new System.Drawing.Size(89, 21);

View File

@ -31,6 +31,23 @@ namespace BizHawk.Client.EmuHawk
{
InitializeComponent();
Icon = Properties.Resources.DualIcon;
SystemDropDown.Items.AddRange(new[]
{
VSystemID.Raw.AmstradCPC,
VSystemID.Raw.AppleII,
VSystemID.Raw.Arcade,
VSystemID.Raw.C64,
VSystemID.Raw.GBL,
VSystemID.Raw.GEN,
VSystemID.Raw.GGL,
VSystemID.Raw.Jaguar,
VSystemID.Raw.NDS,
VSystemID.Raw.PCFX,
VSystemID.Raw.PSX,
VSystemID.Raw.SAT,
VSystemID.Raw.TI83,
VSystemID.Raw.ZXSpectrum,
});
}
private void MultiGameCreator_Load(object sender, EventArgs e) => Restart();
@ -41,7 +58,7 @@ namespace BizHawk.Client.EmuHawk
AddButton_Click(null, null);
AddButton_Click(null, null);
if (!Game.IsNullInstance() && !MainForm.CurrentlyOpenRom.EndsWith(".xml"))
if (!Game.IsNullInstance() && !MainForm.CurrentlyOpenRom.EndsWith(".xml"))
{
if (MainForm.CurrentlyOpenRom.Contains("|"))
{
@ -130,7 +147,8 @@ namespace BizHawk.Client.EmuHawk
Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top
};
var mdf = new MultiDiskFileSelector(MainForm, Config.PathEntries, () => MainForm.CurrentlyOpenRom)
var mdf = new MultiDiskFileSelector(MainForm, Config.PathEntries,
() => MainForm.CurrentlyOpenRom, () => SystemDropDown.SelectedItem?.ToString())
{
Location = UIHelper.Scale(new Point(7, 12)),
Width = groupBox.ClientSize.Width - UIHelper.ScaleX(13),
@ -138,7 +156,6 @@ namespace BizHawk.Client.EmuHawk
};
mdf.NameChanged += FileSelector_NameChanged;
mdf.SystemString = SystemDropDown.SelectedText;
groupBox.Controls.Add(mdf);
FileSelectorPanel.Controls.Add(groupBox);
@ -244,7 +261,9 @@ namespace BizHawk.Client.EmuHawk
private void BrowseBtn_Click(object sender, EventArgs e)
{
string filename = "";
string initialDirectory = Config.PathEntries.MultiDiskAbsolutePath();
string initialDirectory = Config.PathEntries.UseRecentForRoms
? string.Empty
: Config.PathEntries.MultiDiskAbsolutePath();
if (!Game.IsNullInstance())
{
@ -267,27 +286,6 @@ namespace BizHawk.Client.EmuHawk
private void SystemDropDown_SelectedIndexChanged(object sender, EventArgs e)
{
Recalculate();
do
{
foreach (Control ctrl in FileSelectorPanel.Controls)
{
ctrl.Dispose();
}
} while (FileSelectorPanel.Controls.Count != 0);
if (SystemDropDown.SelectedItem.ToString() == VSystemID.Raw.GB)
{
AddButton.Enabled = false;
btnRemove.Enabled = false;
}
else
{
AddButton.Enabled = true;
btnRemove.Enabled = true;
}
AddButton_Click(null, null);
AddButton_Click(null, null);
}
}
}

View File

@ -5,6 +5,7 @@ using System.Windows.Forms;
using BizHawk.Common;
using BizHawk.Client.Common;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Arcades.MAME;
namespace BizHawk.Client.EmuHawk
{
@ -16,7 +17,7 @@ namespace BizHawk.Client.EmuHawk
public IDialogController DialogController { get; }
public string SystemString { get; set; } = "";
private readonly Func<string> _getSystemNameCallback;
public string Path
{
@ -31,11 +32,13 @@ namespace BizHawk.Client.EmuHawk
OnNameChanged(EventArgs.Empty);
}
public MultiDiskFileSelector(IDialogController dialogController, PathEntryCollection pathEntries, Func<string> getLoadedRomNameCallback)
public MultiDiskFileSelector(IDialogController dialogController, PathEntryCollection pathEntries,
Func<string> getLoadedRomNameCallback, Func<string> getSystemNameCallback)
{
DialogController = dialogController;
_pathEntries = pathEntries;
_getLoadedRomNameCallback = getLoadedRomNameCallback;
_getSystemNameCallback = getSystemNameCallback;
InitializeComponent();
PathBox.TextChanged += HandleLabelTextChanged;
}
@ -72,17 +75,18 @@ namespace BizHawk.Client.EmuHawk
private void BrowseButton_Click(object sender, EventArgs e)
{
var systemName = _getSystemNameCallback();
var hawkPath = this.ShowFileOpenDialog(
discardCWDChange: true,
filter: RomLoader.RomFilter,
initDir: _pathEntries.RomAbsolutePath());
initDir: _pathEntries.UseRecentForRoms ? string.Empty : _pathEntries.RomAbsolutePath(systemName));
if (hawkPath is null) return;
try
{
FileInfo file = new(hawkPath);
var path = EmuHawkUtil.ResolveShortcut(file.FullName);
using HawkFile hf = new(path);
using HawkFile hf = new(path, allowArchives: !MAMEMachineDB.IsMAMEMachine(hawkPath));
if (!hf.IsArchive)
{
// file is not an archive
@ -91,7 +95,7 @@ namespace BizHawk.Client.EmuHawk
}
// else archive - run the archive chooser
if (SystemString is VSystemID.Raw.PSX or VSystemID.Raw.PCFX or VSystemID.Raw.SAT)
if (systemName is VSystemID.Raw.PSX or VSystemID.Raw.PCFX or VSystemID.Raw.SAT)
{
DialogController.ShowMessageBox("Using archives with PSX, PCFX or SATURN is not currently recommended/supported.");
return;

View File

@ -403,10 +403,8 @@ namespace BizHawk.Emulation.Common
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 = VSystemID.Raw.MAME;
game.System = VSystemID.Raw.Arcade;
break;
}

View File

@ -22,6 +22,7 @@ namespace BizHawk.Emulation.Common
[VSystemID.Raw.A78] = "Atari 7800",
[VSystemID.Raw.AmstradCPC] = "Amstrad CPC",
[VSystemID.Raw.AppleII] = "Apple II",
[VSystemID.Raw.Arcade] = "Arcade",
[VSystemID.Raw.C64] = "Commodore 64",
[VSystemID.Raw.ChannelF] = "Channel F",
[VSystemID.Raw.Coleco] = "ColecoVision",
@ -38,7 +39,6 @@ namespace BizHawk.Emulation.Common
[VSystemID.Raw.Jaguar] = "Jaguar",
[VSystemID.Raw.Libretro] = "Libretro",
[VSystemID.Raw.Lynx] = "Lynx",
[VSystemID.Raw.MAME] = "MAME",
[VSystemID.Raw.MSX] = "MSX",
[VSystemID.Raw.N64] = "Nintendo 64",
[VSystemID.Raw.NDS] = "NDS",

View File

@ -19,6 +19,7 @@ namespace BizHawk.Emulation.Common
public const string Amiga = "Amiga";
public const string AmstradCPC = "AmstradCPC";
public const string AppleII = "AppleII";
public const string Arcade = "Arcade";
public const string C64 = "C64";
public const string ChannelF = "ChannelF";
public const string Coleco = "Coleco";
@ -36,7 +37,6 @@ namespace BizHawk.Emulation.Common
public const string Jaguar = "Jaguar";
public const string Libretro = "Libretro";
public const string Lynx = "Lynx";
public const string MAME = "MAME";
public const string MSX = "MSX";
public const string N64 = "N64";
public const string NDS = "NDS";

View File

@ -1,24 +1,21 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using BizHawk.BizInvoke;
namespace BizHawk.Emulation.Cores.Arcades.MAME
{
public static class LibMAME
public abstract class LibMAME
{
internal const string dll = "libmamearcade.dll"; // libmamearcade.dll libpacshd.dll
private const CallingConvention cc = CallingConvention.Cdecl;
// enums
public enum OutputChannel
public enum OutputChannel : int
{
ERROR, WARNING, INFO, DEBUG, VERBOSE, LOG, COUNT
}
public enum SaveError
{
NONE, NOT_FOUND, ILLEGAL_REGISTRATIONS, INVALID_HEADER, READ_ERROR, WRITE_ERROR, DISABLED
}
// constants
public const int ROMENTRYTYPE_SYSTEM_BIOS = 9;
public const int ROMENTRYTYPE_DEFAULT_BIOS = 10;
@ -28,64 +25,98 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
public const string BIOS_LUA_CODE = "bios";
// main launcher
[DllImport(dll, CallingConvention = cc)]
public static extern uint mame_launch(int argc, string[] argv);
[BizImport(cc, Compatibility = true)]
public abstract uint mame_launch(int argc, string[] argv);
[DllImport(dll, CallingConvention = cc)]
public static extern char mame_read_byte(uint address);
[BizImport(cc)]
public abstract bool mame_coswitch();
[DllImport(dll, CallingConvention = cc)]
public static extern SaveError mame_save_buffer(byte[] buf, out int length);
[BizImport(cc)]
public abstract byte mame_read_byte(uint address);
[DllImport(dll, CallingConvention = cc)]
public static extern SaveError mame_load_buffer(byte[] buf, int length);
[BizImport(cc)]
public abstract IntPtr mame_input_get_field_ptr(string tag, string field);
// execute
[DllImport(dll, CallingConvention = cc)]
public static extern void mame_lua_execute(string code);
[BizImport(cc)]
public abstract void mame_input_set_fields(IntPtr[] fields, int[] inputs, int length);
// get int
[DllImport(dll, CallingConvention = cc)]
public static extern int mame_lua_get_int(string code);
[BizImport(cc)]
public abstract int mame_sound_get_samples(short[] buffer);
// get double
[DllImport(dll, CallingConvention = cc)]
public static extern double mame_lua_get_double(string code);
[BizImport(cc)]
public abstract int mame_video_get_dimensions(out int width, out int height);
// get bool
[DllImport(dll, CallingConvention = cc)]
public static extern bool mame_lua_get_bool(string code);
[BizImport(cc)]
public abstract int mame_video_get_pixels(int[] buffer);
// get string
[DllImport(dll, CallingConvention = cc)]
public static extern IntPtr mame_lua_get_string(string code, out int length);
[UnmanagedFunctionPointer(cc)]
public delegate void FilenameCallbackDelegate(string name);
// free string
[DllImport(dll, CallingConvention = cc)]
public static extern bool mame_lua_free_string(IntPtr pointer);
[BizImport(cc)]
public abstract void mame_nvram_get_filenames(FilenameCallbackDelegate cb);
// periodic
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void PeriodicCallbackDelegate();
[DllImport(dll, CallingConvention = cc)]
public static extern void mame_set_periodic_callback(PeriodicCallbackDelegate cb);
[BizImport(cc)]
public abstract void mame_nvram_save();
// 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);
[BizImport(cc)]
public abstract void mame_nvram_load();
// log
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[UnmanagedFunctionPointer(cc)]
public delegate void LogCallbackDelegate(OutputChannel channel, int size, string data);
[DllImport(dll, CallingConvention = cc)]
public static extern void mame_set_log_callback(LogCallbackDelegate cb);
[BizImport(cc)]
public abstract void mame_set_log_callback(LogCallbackDelegate cb);
// base time
[UnmanagedFunctionPointer(cc)]
public delegate long BaseTimeCallbackDelegate();
[BizImport(cc)]
public abstract void mame_set_base_time_callback(BaseTimeCallbackDelegate cb);
// input poll
[UnmanagedFunctionPointer(cc)]
public delegate void InputPollCallbackDelegate();
[BizImport(cc)]
public abstract void mame_set_input_poll_callback(InputPollCallbackDelegate cb);
// execute
[BizImport(cc)]
public abstract void mame_lua_execute(string code);
// get int
[BizImport(cc)]
public abstract int mame_lua_get_int(string code);
// get long
// nb: this is actually a double cast to long internally
[BizImport(cc)]
public abstract long mame_lua_get_long(string code);
// get bool
[BizImport(cc)]
public abstract bool mame_lua_get_bool(string code);
/// <summary>
/// MAME's luaengine uses lua strings to return C strings as well as
/// binary buffers. You're meant to know which you're going to get and
/// handle that accordingly. When we want to get a C string, we
/// Marshal.PtrToStringAnsi(). With buffers, we Marshal.Copy()
/// to our new buffer. MameGetString() only covers the former
/// because it's the same steps every time, while buffers use to
/// need aditional logic. In both cases MAME wants us to manually
/// free the string buffer. It's made that way to make the buffer
/// persist actoss C API calls.
/// </summary>
// get string
[BizImport(cc)]
public abstract IntPtr mame_lua_get_string(string code, out int length);
// free string
[BizImport(cc)]
public abstract bool mame_lua_free_string(IntPtr pointer);
}
}

View File

@ -1,35 +1,43 @@
using BizHawk.Emulation.Common;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Arcades.MAME
{
public partial class MAME : IEmulator
{
public string SystemId => VSystemID.Raw.MAME;
public bool DeterministicEmulation => true;
public string SystemId => VSystemID.Raw.Arcade;
public bool DeterministicEmulation { get; }
public int Frame { get; private set; }
public IEmulatorServiceProvider ServiceProvider { get; }
public ControllerDefinition ControllerDefinition => MAMEController;
/// <summary>
/// MAME fires the periodic callback on every video and debugger update,
/// which happens every VBlank and also repeatedly at certain time
/// intervals while paused. In our implementation, MAME's emulation
/// runs in a separate co-thread, which we swap over with mame_coswitch
/// On a periodic callback, control will be switched back to the host
/// co-thread. If MAME is internally unpaused, then the next periodic
/// callback will occur once a frame is done, making mame_coswitch
/// act like a frame advance.
/// </summary>
public bool FrameAdvance(IController controller, bool render, bool renderSound = true)
{
if (IsCrashed)
using (_exe.EnterExit())
{
return false;
SendInput(controller);
IsLagFrame = _core.mame_coswitch();
UpdateSound();
if (render)
{
UpdateVideo();
}
}
_controller = controller;
// signal to mame we want to frame advance
_mameCmd = MAME_CMD.STEP;
SafeWaitEvent(_mameCommandComplete);
// tell mame the next periodic callback will update video
_mameCmd = MAME_CMD.VIDEO;
_mameCommandWaitDone.Set();
// wait until the mame thread is done updating video
SafeWaitEvent(_mameCommandComplete);
_mameCommandWaitDone.Set();
if (!renderSound)
{
DiscardSamples();
}
Frame++;
@ -48,13 +56,13 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
IsLagFrame = false;
}
private bool _disposed = false;
public void Dispose()
{
_mameCmd = MAME_CMD.EXIT;
_mameCommandWaitDone.Set();
_mameThread.Join();
_mameSaveBuffer = new byte[0];
_hawkSaveBuffer = new byte[0];
if (_disposed) return;
_exe.Dispose();
_disposed = true;
}
}
}

View File

@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Common;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common;
@ -10,48 +12,88 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
{
public int LagCount { get; set; } = 0;
public bool IsLagFrame { get; set; } = false;
public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem();
[FeatureNotImplemented]
public IInputCallbackSystem InputCallbacks => throw new NotImplementedException();
private readonly ControllerDefinition MAMEController = new("MAME Controller");
public static ControllerDefinition MAMEController = new("MAME Controller");
private IController _controller = NullController.Instance;
private readonly SortedDictionary<string, string> _fieldsPorts = new();
private SortedDictionary<string, string> _romHashes = new();
private string[] _buttonFields;
private string[] _analogFields;
private IntPtr[] _fieldPtrs;
private int[] _fieldInputs;
private void GetInputFields()
{
string inputFields = MameGetString(MAMELuaCommand.GetInputFields);
string[] portFields = inputFields.Split(';');
MAMEController = new("MAME Controller");
MAMEController.BoolButtons.Clear();
_fieldsPorts.Clear();
var buttonFields = MameGetString(MAMELuaCommand.GetButtonFields).Split(';');
var analogFields = MameGetString(MAMELuaCommand.GetAnalogFields).Split(';');
foreach (string portField in portFields)
var buttonFieldList = new List<string>();
var analogFieldList = new List<string>();
var fieldPtrList = new List<IntPtr>();
void AddFieldPtr(string tag, string field)
{
if (portField != string.Empty)
var ptr = _core.mame_input_get_field_ptr(tag, field);
if (ptr == IntPtr.Zero)
{
var tag = portField.SubstringBefore(',');
var field = portField.SubstringAfterLast(',');
_fieldsPorts.Add(field, tag);
Dispose();
throw new Exception($"Fatal error: {nameof(LibMAME.mame_input_get_field_ptr)} returned NULL!");
}
fieldPtrList.Add(ptr);
}
foreach (var buttonField in buttonFields)
{
if (buttonField != string.Empty && !buttonField.Contains('%'))
{
var tag = buttonField.SubstringBefore(',');
var field = buttonField.SubstringAfterLast(',');
buttonFieldList.Add(field);
AddFieldPtr(tag, field);
MAMEController.BoolButtons.Add(field);
}
}
foreach (var analogField in analogFields)
{
if (analogField != string.Empty && !analogField.Contains('%'))
{
var keys = analogField.Split(',');
var tag = keys[0];
var field = keys[1];
analogFieldList.Add(field);
AddFieldPtr(tag, field);
var def = int.Parse(keys[2]);
var min = int.Parse(keys[3]);
var max = int.Parse(keys[4]);
MAMEController.AddAxis(field, min.RangeTo(max), def);
}
}
_buttonFields = buttonFieldList.ToArray();
_analogFields = analogFieldList.ToArray();
_fieldPtrs = fieldPtrList.ToArray();
_fieldInputs = new int[_fieldPtrs.Length];
MAMEController.MakeImmutable();
}
private void SendInput()
private void SendInput(IController controller)
{
foreach (var fieldPort in _fieldsPorts)
for (var i = 0; i < _buttonFields.Length; i++)
{
LibMAME.mame_lua_execute(
"manager.machine.ioport" +
$".ports [\"{ fieldPort.Value }\"]" +
$".fields [\"{ fieldPort.Key }\"]" +
$":set_value({ (_controller.IsPressed(fieldPort.Key) ? 1 : 0) })");
_fieldInputs[i] = controller.IsPressed(_buttonFields[i]) ? 1 : 0;
}
for (var i = 0; i < _analogFields.Length; i++)
{
_fieldInputs[_buttonFields.Length + i] = controller.AxisValue(_analogFields[i]);
}
_core.mame_input_set_fields(_fieldPtrs, _fieldInputs, _fieldInputs.Length);
_core.mame_set_input_poll_callback(InputCallbacks.Count > 0 ? _inputPollCallback : null);
}
}
}

View File

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.IO;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Arcades.MAME
{
public partial class MAME : ISaveRam
{
private readonly LibMAME.FilenameCallbackDelegate _filenameCallback;
private readonly List<string> _nvramFilenames = new();
private const string NVRAM_MAGIC = "MAMEHAWK_NVRAM";
private void GetNVRAMFilenames()
{
_core.mame_nvram_get_filenames(_filenameCallback);
}
public bool SaveRamModified => _nvramFilenames.Count > 0;
public byte[] CloneSaveRam()
{
if (_nvramFilenames.Count == 0)
{
return null;
}
for (int i = 0; i < _nvramFilenames.Count; i++)
{
_exe.AddTransientFile(Array.Empty<byte>(), _nvramFilenames[i]);
}
_core.mame_nvram_save();
using var ms = new MemoryStream();
using var writer = new BinaryWriter(ms);
writer.Write(NVRAM_MAGIC);
writer.Write(_nvramFilenames.Count);
for (var i = 0; i < _nvramFilenames.Count; i++)
{
var res = _exe.RemoveTransientFile(_nvramFilenames[i]);
writer.Write(_nvramFilenames[i]);
writer.Write(res.Length);
writer.Write(res);
}
return ms.ToArray();
}
public void StoreSaveRam(byte[] data)
{
if (_nvramFilenames.Count == 0)
{
return;
}
using var ms = new MemoryStream(data, false);
using var reader = new BinaryReader(ms);
if (reader.ReadString() != NVRAM_MAGIC)
{
throw new InvalidOperationException("Bad NVRAM magic!");
}
var cnt = reader.ReadInt32();
if (cnt != _nvramFilenames.Count)
{
throw new InvalidOperationException($"Wrong NVRAM file count! (got {cnt}, expected {_nvramFilenames.Count})");
}
var nvramFilesToClose = new List<string>();
void RemoveFiles()
{
foreach (var nvramFileToClose in nvramFilesToClose)
{
_exe.RemoveReadonlyFile(nvramFileToClose);
}
}
try
{
for (var i = 0; i < cnt; i++)
{
var name = reader.ReadString();
if (name != _nvramFilenames[i])
{
throw new InvalidOperationException($"Wrong NVRAM filename! (got {name}, expected {_nvramFilenames[i]})");
}
var len = reader.ReadInt32();
var buf = reader.ReadBytes(len);
if (len != buf.Length)
{
throw new InvalidOperationException($"Unexpected NVRAM size difference! (got {buf.Length}, expected {len})");
}
_exe.AddReadonlyFile(buf, name);
nvramFilesToClose.Add(name);
}
}
catch
{
RemoveFiles();
throw;
}
_core.mame_nvram_load();
RemoveFiles();
}
}
}

View File

@ -3,6 +3,9 @@ using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using BizHawk.Common;
using static BizHawk.Emulation.Cores.Arcades.MAME.MAME;
@ -16,9 +19,20 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
}
public List<DriverSetting> Settings { get; }
public override bool IsSupportedType(Type type) => type == typeof(List<DriverSetting>);
public override bool IsSupportedType(Type type) => type == typeof(MAMESyncSettings);
public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance)
=> new SyncSettingsCustomTypeDescriptor(Settings);
{
if (objectType == typeof(MAMESyncSettings))
{
return new SyncSettingsCustomTypeDescriptor(Settings);
}
else
{
return null; // ???
}
}
}
public class SyncSettingsCustomTypeDescriptor : CustomTypeDescriptor
@ -29,33 +43,68 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
}
public List<DriverSetting> Settings { get; }
public override string GetClassName() => nameof(List<DriverSetting>);
public override string GetComponentName() => nameof(List<DriverSetting>);
public override string GetClassName() => nameof(MAMESyncSettings);
public override string GetComponentName() => nameof(MAMESyncSettings);
public override PropertyDescriptor GetDefaultProperty() => GetProperties()[0]; // "default" ??
public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) => GetProperties();
public override PropertyDescriptorCollection GetProperties()
{
var rtc = typeof(MAMERTCSettings).GetProperties()
.Select(t => new RTCPropertyDescriptor(t))
.Cast<PropertyDescriptor>();
var s = Settings.Select(m => new MAMEPropertyDescriptor(m));
return new PropertyDescriptorCollection(s.ToArray());
return new PropertyDescriptorCollection(rtc.Concat(s).ToArray());
}
}
public class RTCPropertyDescriptor : PropertyDescriptor
{
public RTCPropertyDescriptor(PropertyInfo settingInfo)
: base(settingInfo.Name, settingInfo.GetCustomAttributes(false).Cast<Attribute>().ToArray())
{
SettingInfo = settingInfo;
}
private PropertyInfo SettingInfo { get; }
protected object ConvertFromString(string s) => Converter.ConvertFromString(s);
protected string ConvertToString(object o) => Converter.ConvertToString(o);
public override bool CanResetValue(object component) => true;
public override bool ShouldSerializeValue(object component)
=> !DeepEquality.DeepEquals(GetValue(component), SettingInfo.GetCustomAttribute<DefaultValueAttribute>().Value);
public override Type PropertyType => SettingInfo.PropertyType;
public override Type ComponentType => typeof(MAMERTCSettings);
public override bool IsReadOnly => false;
public override object GetValue(object component)
=> SettingInfo.GetValue(((MAMESyncSettings)component).RTCSettings);
public override void ResetValue(object component)
=> SetValue(component, SettingInfo.GetCustomAttribute<DefaultValueAttribute>().Value);
public override void SetValue(object component, object value)
=> SettingInfo.SetValue(((MAMESyncSettings)component).RTCSettings, value);
}
public class MAMEPropertyDescriptor : PropertyDescriptor
{
public MAMEPropertyDescriptor(DriverSetting setting) : base(setting.LookupKey, new Attribute[0])
public MAMEPropertyDescriptor(DriverSetting setting)
: base(setting.LookupKey, new Attribute[0])
{
Setting = setting;
Converter = new MyTypeConverter(Setting);
}
public DriverSetting Setting { get; private set; }
private DriverSetting Setting { get; }
protected object ConvertFromString(string s) => s;
protected string ConvertToString(object o) => (string)o;
public override bool CanResetValue(object component) => true;
public override bool ShouldSerializeValue(object component)
=> ((MAMESyncSettings)component).DriverSettings.ContainsKey(Setting.LookupKey);
public override Type PropertyType => typeof(string);
public override TypeConverter Converter => new MyTypeConverter { Setting = Setting };
public override TypeConverter Converter { get; }
public override Type ComponentType => typeof(List<DriverSetting>);
public override bool IsReadOnly => false;
public override string Name => Setting.LookupKey;
@ -88,7 +137,9 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
private class MyTypeConverter : TypeConverter
{
public DriverSetting Setting { get; set; }
public MyTypeConverter(DriverSetting setting)
=> Setting = setting;
private DriverSetting Setting { get; }
public override bool GetStandardValuesExclusive(ITypeDescriptorContext context) => true;
public override bool GetStandardValuesSupported(ITypeDescriptorContext context) => true;
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) => sourceType == typeof(string);

View File

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using BizHawk.Common;
@ -27,52 +28,75 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None;
}
public class MAMERTCSettings
{
[DisplayName("Initial Time")]
[Description("Initial time of emulation.")]
[DefaultValue(typeof(DateTime), "2010-01-01")]
[TypeConverter(typeof(BizDateTimeConverter))]
public DateTime InitialTime { get; set; }
[DisplayName("Use Real Time")]
[Description("If true, RTC clock will be based off of real time instead of emulated time. Ignored (set to false) when recording a movie.")]
[DefaultValue(false)]
public bool UseRealTime { get; set; }
public MAMERTCSettings()
=> SettingsUtil.SetDefaultValues(this);
public MAMERTCSettings Clone()
=> (MAMERTCSettings)MemberwiseClone();
}
public class MAMESyncSettings
{
public MAMERTCSettings RTCSettings { get; set; } = new();
public SortedDictionary<string, string> DriverSettings { get; set; } = new();
public static bool NeedsReboot(MAMESyncSettings x, MAMESyncSettings y)
{
return !DeepEquality.DeepEquals(x.DriverSettings, y.DriverSettings);
return !DeepEquality.DeepEquals(x.RTCSettings, y.RTCSettings)
|| !DeepEquality.DeepEquals(x.DriverSettings, y.DriverSettings);
}
public MAMESyncSettings Clone()
{
return new()
{
DriverSettings = new(DriverSettings)
RTCSettings = RTCSettings.Clone(),
DriverSettings = new(DriverSettings),
};
}
}
public void FetchDefaultGameSettings()
{
string DIPSwitchTags = MameGetString(MAMELuaCommand.GetDIPSwitchTags);
string[] tags = DIPSwitchTags.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var DIPSwitchTags = MameGetString(MAMELuaCommand.GetDIPSwitchTags);
var tags = DIPSwitchTags.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string tag in tags)
foreach (var tag in tags)
{
string DIPSwitchFields = MameGetString(MAMELuaCommand.GetDIPSwitchFields(tag));
string[] fieldNames = DIPSwitchFields.Split(new[] { '^' }, StringSplitOptions.RemoveEmptyEntries);
var DIPSwitchFields = MameGetString(MAMELuaCommand.GetDIPSwitchFields(tag));
var fieldNames = DIPSwitchFields.Split(new[] { '^' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string fieldName in fieldNames)
foreach (var fieldName in fieldNames)
{
DriverSetting setting = new()
var setting = new DriverSetting
{
Name = fieldName,
GameName = _gameShortName,
LuaCode = MAMELuaCommand.InputField(tag, fieldName),
Type = SettingType.DIPSWITCH,
DefaultValue = LibMAME.mame_lua_get_int(
DefaultValue = _core.mame_lua_get_int(
$"return { MAMELuaCommand.InputField(tag, fieldName) }.defvalue").ToString()
};
string DIPSwitchOptions = MameGetString(MAMELuaCommand.GetDIPSwitchOptions(tag, fieldName));
string[] options = DIPSwitchOptions.Split(new[] { '@' }, StringSplitOptions.RemoveEmptyEntries);
var DIPSwitchOptions = MameGetString(MAMELuaCommand.GetDIPSwitchOptions(tag, fieldName));
var options = DIPSwitchOptions.Split(new[] { '@' }, StringSplitOptions.RemoveEmptyEntries);
foreach(string option in options)
foreach (var option in options)
{
string[] opt = option.Split(new[] { '~' }, StringSplitOptions.RemoveEmptyEntries);
var opt = option.Split(new[] { '~' }, StringSplitOptions.RemoveEmptyEntries);
setting.Options.Add(opt[0], opt[1]);
}
@ -83,24 +107,24 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
public void OverrideGameSettings()
{
foreach (KeyValuePair<string, string> setting in _syncSettings.DriverSettings)
foreach (var setting in _syncSettings.DriverSettings)
{
DriverSetting s = CurrentDriverSettings.SingleOrDefault(s => s.LookupKey == setting.Key);
var s = CurrentDriverSettings.SingleOrDefault(s => s.LookupKey == setting.Key);
if (s != null && s.Type == SettingType.DIPSWITCH)
{
LibMAME.mame_lua_execute($"{ s.LuaCode }.user_value = { setting.Value }");
_core.mame_lua_execute($"{ s.LuaCode }.user_value = { setting.Value }");
}
}
}
private void GetROMsInfo()
{
string ROMsInfo = MameGetString(MAMELuaCommand.GetROMsInfo);
string[] ROMs = ROMsInfo.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
string tempDefault = "";
var ROMsInfo = MameGetString(MAMELuaCommand.GetROMsInfo);
var ROMs = ROMsInfo.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
var tempDefault = string.Empty;
DriverSetting setting = new()
var setting = new DriverSetting
{
Name = "BIOS",
GameName = _gameShortName,
@ -108,14 +132,14 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
Type = SettingType.BIOS
};
foreach (string ROM in ROMs)
foreach (var ROM in ROMs)
{
if (ROM != string.Empty)
{
string[] substrings = ROM.Split('~');
string name = substrings[0];
string hashdata = substrings[1];
long flags = long.Parse(substrings[2]);
var substrings = ROM.Split('~');
var name = substrings[0];
var hashdata = substrings[1];
var flags = long.Parse(substrings[2]);
if ((flags & LibMAME.ROMENTRY_TYPEMASK) == LibMAME.ROMENTRYTYPE_SYSTEM_BIOS
|| (flags & LibMAME.ROMENTRY_TYPEMASK) == LibMAME.ROMENTRYTYPE_DEFAULT_BIOS)
@ -137,7 +161,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
}
else
{
hashdata = hashdata.Replace("R", " CRC:").Replace("S", " SHA:");
hashdata = hashdata.Replace("R", "CRC:").Replace("S", " SHA:");
_romHashes.Add(name, hashdata);
}
}

View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using BizHawk.Common;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Arcades.MAME
@ -11,10 +9,9 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
private readonly Queue<short> _audioSamples = new();
private const int _sampleRate = 44100;
private int _samplesPerFrame;
private short[] _sampleBuffer;
private readonly short[] _sampleBuffer = new short[_sampleRate * 2]; // MAME internally guarentees refresh rate is never < 1Hz
private int _nsamps = 0;
public void SetSyncMode(SyncSoundMode mode)
{
@ -24,35 +21,15 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
}
}
private void InitSound()
private void UpdateSound()
{
_samplesPerFrame = (int)Math.Ceiling(((long)_sampleRate * (long)VsyncDenominator / (double)VsyncNumerator));
_sampleBuffer = new short[_samplesPerFrame * 2];
_nsamps = _core.mame_sound_get_samples(_sampleBuffer);
}
/*
* GetSamplesSync() and MAME
*
* MAME generates samples 50 times per second, regardless of the VBlank
* rate of the emulated machine. It then uses complicated logic to
* output the required amount of audio to the OS driver and to the AVI,
* where it's meant to tie flushed samples to video frame duration.
*
* I'm doing my own logic here for now. I grab MAME's audio buffer
* whenever it's filled (MAMESoundCallback()) and enqueue it.
*
* Whenever Hawk wants new audio, I dequeue it, but never more than the
* maximum samples a frame contains, keeping pending samples for the next frame
*/
public void GetSamplesSync(out short[] samples, out int nsamp)
{
samples = _sampleBuffer;
nsamp = Math.Min(_samplesPerFrame, _audioSamples.Count / 2);
for (int i = 0; i < nsamp * 2; i++)
{
samples[i] = _audioSamples.Dequeue();
}
nsamp = _nsamps;
}
public void GetSamplesAsync(short[] samples)
@ -62,7 +39,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
public void DiscardSamples()
{
_audioSamples.Clear();
_nsamps = 0;
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.IO;
using System.IO;
using BizHawk.Common;
using BizHawk.Emulation.Common;
@ -8,29 +7,13 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
{
public partial class MAME : IStatable
{
private byte[] _mameSaveBuffer;
private byte[] _hawkSaveBuffer;
public void SaveStateBinary(BinaryWriter writer)
{
writer.Write(_mameSaveBuffer.Length);
using (this.EnterExit())
using (_exe.EnterExit())
{
var err = LibMAME.mame_save_buffer(_mameSaveBuffer, out int length);
if (length != _mameSaveBuffer.Length)
{
throw new InvalidOperationException("Savestate buffer size mismatch!");
}
if (err != LibMAME.SaveError.NONE)
{
throw new InvalidOperationException("MAME SAVESTATE ERROR: " + err.ToString());
}
_exe.SaveStateBinary(writer);
}
writer.Write(_mameSaveBuffer);
writer.Write(Frame);
writer.Write(LagCount);
writer.Write(IsLagFrame);
@ -38,23 +21,9 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
public void LoadStateBinary(BinaryReader reader)
{
int length = reader.ReadInt32();
if (length != _mameSaveBuffer.Length)
using (_exe.EnterExit())
{
throw new InvalidOperationException("Savestate buffer size mismatch!");
}
reader.Read(_mameSaveBuffer, 0, _mameSaveBuffer.Length);
using (this.EnterExit())
{
var err = LibMAME.mame_load_buffer(_mameSaveBuffer, _mameSaveBuffer.Length);
if (err != LibMAME.SaveError.NONE)
{
throw new InvalidOperationException("MAME LOADSTATE ERROR: " + err.ToString());
}
_exe.LoadStateBinary(reader);
}
Frame = reader.ReadInt32();

View File

@ -19,17 +19,23 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
private int[] _frameBuffer = new int[0];
/// <summary>
/// Attoseconds for the emulated system's vsync rate.
/// Use this to calculate a precise movie time
/// </summary>
public long VsyncAttoseconds { get; private set; }
private void UpdateFramerate()
{
VsyncNumerator = 1000000000;
long refresh = (long)LibMAME.mame_lua_get_double(MAMELuaCommand.GetRefresh);
VsyncDenominator = (int)(refresh / 1000000000);
VsyncAttoseconds = _core.mame_lua_get_long(MAMELuaCommand.GetRefresh);
VsyncDenominator = (int)(VsyncAttoseconds / 1000000000);
}
private void UpdateAspect()
{
int x = (int)LibMAME.mame_lua_get_double(MAMELuaCommand.GetBoundX);
int y = (int)LibMAME.mame_lua_get_double(MAMELuaCommand.GetBoundY);
int x = (int)_core.mame_lua_get_long(MAMELuaCommand.GetBoundX);
int y = (int)_core.mame_lua_get_long(MAMELuaCommand.GetBoundY);
VirtualHeight = BufferWidth > BufferHeight * x / y
? BufferWidth * y / x
: BufferHeight;
@ -38,40 +44,18 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
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;
IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetPixels, out var lengthInBytes);
_core.mame_video_get_dimensions(out var width, out var height);
if (ptr == IntPtr.Zero)
BufferWidth = width;
BufferHeight = height;
var numPixels = width * height;
if (_frameBuffer.Length < numPixels)
{
Console.WriteLine("LibMAME ERROR: frame buffer pointer is null");
return;
_frameBuffer = new int[numPixels];
}
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;
}
if (_frameBuffer.Length < expectedSize)
{
_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");
}
_core.mame_video_get_pixels(_frameBuffer);
}
}
}

View File

@ -6,45 +6,19 @@ using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Arcades.MAME
{
public partial class MAME : IMonitor
public partial class MAME
{
private IMemoryDomains _memoryDomains;
private int _enterCount;
public void Enter()
{
if (_enterCount == 0)
{
_mameCmd = MAME_CMD.WAIT;
SafeWaitEvent(_mameCommandComplete);
}
_enterCount++;
}
public void Exit()
{
if (_enterCount <= 0)
{
throw new InvalidOperationException();
}
else if (_enterCount == 1)
{
_mameCommandWaitDone.Set();
}
_enterCount--;
}
private MemoryDomainList _memoryDomains;
public class MAMEMemoryDomain : MemoryDomain
{
private readonly LibMAME _core;
private readonly IMonitor _monitor;
private readonly int _firstOffset;
private readonly int _systemBusAddressShift;
private readonly long _systemBusSize;
public MAMEMemoryDomain(string name, long size, Endian endian, int dataWidth, bool writable, IMonitor monitor, int firstOffset, int systemBusAddressShift, long systemBusSize)
public MAMEMemoryDomain(string name, long size, Endian endian, int dataWidth, bool writable, LibMAME core, IMonitor monitor, int firstOffset, int systemBusAddressShift, long systemBusSize)
{
Name = name;
Size = size;
@ -52,6 +26,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
WordSize = dataWidth;
Writable = writable;
_core = core;
_monitor = monitor;
_firstOffset = firstOffset;
_systemBusAddressShift = systemBusAddressShift;
@ -67,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
try
{
_monitor.Enter();
return (byte)LibMAME.mame_read_byte((uint)addr << _systemBusAddressShift);
return _core.mame_read_byte((uint)addr << _systemBusAddressShift);
}
finally
{
@ -86,7 +61,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
try
{
_monitor.Enter();
LibMAME.mame_lua_execute($"{MAMELuaCommand.GetSpace}:write_u8({addr << _systemBusAddressShift}, {val})");
_core.mame_lua_execute($"{MAMELuaCommand.GetSpace}:write_u8({addr << _systemBusAddressShift}, {val})");
}
finally
{
@ -106,14 +81,14 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
{
var domains = new List<MemoryDomain>();
var systemBusAddressShift = LibMAME.mame_lua_get_int(MAMELuaCommand.GetSpaceAddressShift);
var dataWidth = LibMAME.mame_lua_get_int(MAMELuaCommand.GetSpaceDataWidth) >> 3; // mame returns in bits
var size = (long)LibMAME.mame_lua_get_double(MAMELuaCommand.GetSpaceAddressMask) + dataWidth;
var systemBusAddressShift = _core.mame_lua_get_int(MAMELuaCommand.GetSpaceAddressShift);
var dataWidth = _core.mame_lua_get_int(MAMELuaCommand.GetSpaceDataWidth) >> 3; // mame returns in bits
var size = _core.mame_lua_get_long(MAMELuaCommand.GetSpaceAddressMask) + dataWidth;
var endianString = MameGetString(MAMELuaCommand.GetSpaceEndianness);
var deviceName = MameGetString(MAMELuaCommand.GetMainCPUName);
//var addrSize = (size * 2).ToString();
MemoryDomain.Endian endian = MemoryDomain.Endian.Unknown;
var endian = MemoryDomain.Endian.Unknown;
if (endianString == "little")
{
@ -124,28 +99,29 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
endian = MemoryDomain.Endian.Big;
}
var mapCount = LibMAME.mame_lua_get_int(MAMELuaCommand.GetSpaceMapCount);
var mapCount = _core.mame_lua_get_int(MAMELuaCommand.GetSpaceMapCount);
for (int i = 1; i <= mapCount; i++)
for (var i = 1; i <= mapCount; i++)
{
var read = MameGetString($"return { MAMELuaCommand.SpaceMap }[{ i }].read.handlertype");
var write = MameGetString($"return { MAMELuaCommand.SpaceMap }[{ i }].write.handlertype");
if (read == "ram" && write == "ram" || read == "rom")
{
var firstOffset = LibMAME.mame_lua_get_int($"return { MAMELuaCommand.SpaceMap }[{ i }].address_start");
var lastOffset = LibMAME.mame_lua_get_int($"return { MAMELuaCommand.SpaceMap }[{ i }].address_end");
var firstOffset = _core.mame_lua_get_int($"return { MAMELuaCommand.SpaceMap }[{ i }].address_start");
var lastOffset = _core.mame_lua_get_int($"return { MAMELuaCommand.SpaceMap }[{ i }].address_end");
var name = $"{ deviceName } : { read } : 0x{ firstOffset:X}-0x{ lastOffset:X}";
domains.Add(new MAMEMemoryDomain(name, lastOffset - firstOffset + 1, endian,
dataWidth, read != "rom", this, firstOffset, systemBusAddressShift, size));
dataWidth, read != "rom", _core, _exe, firstOffset, systemBusAddressShift, size));
}
}
domains.Add(new MAMEMemoryDomain(deviceName + " : System Bus", size, endian, dataWidth, false, this, 0, systemBusAddressShift, size));
domains.Add(new MAMEMemoryDomain(deviceName + " : System Bus", size, endian, dataWidth, false, _core, _exe, 0, systemBusAddressShift, size));
domains.Add(_exe.GetPagesDomain());
_memoryDomains = new MemoryDomainList(domains);
((MemoryDomainList)_memoryDomains).SystemBus = _memoryDomains[deviceName + " : System Bus"];
_memoryDomains = new(domains);
_memoryDomains.SystemBus = _memoryDomains[deviceName + " : System Bus"];
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(_memoryDomains);
}
}

View File

@ -1,170 +1,115 @@
/*
Build command
make SUBTARGET=arcade NO_USE_PORTAUDIO=1 DONT_USE_NETWORK=1 NO_USE_MIDI=1 MAIN_SHARED_LIB=1 BIN_DIR="..\somewhere\BizHawk\output\dll" OPTIMIZE=3 PTR64=1 REGENIE=1 vs2019_clang -j8
FrameAdvance()
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.
Memory access
All memory access needs to be done while we're inside a callback,
otherwise we get crashes inside SOL (as described above).
We can't know in advance how many addresses we'll be reading (bulkread
in hawk is too complicated to fully implement), but we can assume
that when a new FrameAdvance() request arrives, all the reading requests
have ended for that frame.
So once the first memory read is requested, we put this whole callback
on hold and just wait for FrameAdvance(). This allows for as many memreads
as one may dream of, without letting MAME to execute anything in its main
thread.
Upon new FrameAdvance(), we wait for the current memread to complete,
then we immeditely let go of the callback, without executing any further
logic. Only when MAME fires the callback once more, we consider it safe to
process FrameAdvance() further.
Strings
MAME's luaengine uses lua strings to return C strings as well as
binary buffers. You're meant to know which you're going to get and
handle that accordingly.
When we want to get a C string, we Marshal.PtrToStringAnsi().
With buffers, we Marshal.Copy() to our new buffer.
MameGetString() only covers the former because it's the same steps
every time, while buffers use to need aditional logic.
In both cases MAME wants us to manually free the string buffer. It's
made that way to make the buffer persist actoss C API calls.
*/
using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using BizHawk.BizInvoke;
using BizHawk.Common;
using BizHawk.Common.StringExtensions;
using BizHawk.Emulation.Common;
using BizHawk.Emulation.Cores.Waterbox;
namespace BizHawk.Emulation.Cores.Arcades.MAME
{
[PortedCore(CoreNames.MAME, "MAMEDev", "0.231", "https://github.com/mamedev/mame.git", isReleased: false)]
public partial class MAME : IEmulator, IVideoProvider, ISoundProvider, ISettable<object, MAME.MAMESyncSettings>, IStatable, IInputPollable
[PortedCore(CoreNames.MAME, "MAMEDev", "0.249", "https://github.com/mamedev/mame.git", isReleased: false)]
public partial class MAME : IRomInfo
{
public MAME(string dir, string file, MAMESyncSettings syncSettings, out string gamename)
[CoreConstructor(VSystemID.Raw.Arcade)]
public MAME(CoreLoadParameters<object, MAMESyncSettings> lp)
{
try
{
OSTailoredCode.LinkedLibManager.FreeByPtr(OSTailoredCode.LinkedLibManager.LoadOrThrow(LibMAME.dll));
}
catch (Exception e)
{
throw new Exception("failed to load MAME core library (is this actually a MAME build of BizHawk?)", e);
}
_gameDirectory = dir;
_gameFileName = file;
_gameFileName = Path.GetFileName(lp.Roms[0].RomPath).ToLowerInvariant();
ServiceProvider = new BasicServiceProvider(this);
_syncSettings = syncSettings ?? new();
_syncSettings = lp.SyncSettings ?? new();
_mameThread = new Thread(ExecuteMAMEThread);
_mameThread.Start();
DeterministicEmulation = !_syncSettings.RTCSettings.UseRealTime || lp.DeterministicEmulationRequested;
SafeWaitEvent(_mameStartupComplete);
_logCallback = MAMELogCallback;
_baseTimeCallback = MAMEBaseTimeCallback;
_inputPollCallback = InputCallbacks.Call;
_filenameCallback = name => _nvramFilenames.Add(name);
gamename = _gameFullName;
_exe = new(new()
{
Filename = "libmamearcade.wbx",
Path = lp.Comm.CoreFileProvider.DllPath(),
SbrkHeapSizeKB = 512 * 1024,
InvisibleHeapSizeKB = 4,
MmapHeapSizeKB = 1024 * 1024,
PlainHeapSizeKB = 4,
SealedHeapSizeKB = 4,
SkipCoreConsistencyCheck = lp.Comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxCoreConsistencyCheck),
SkipMemoryConsistencyCheck = lp.Comm.CorePreferences.HasFlag(CoreComm.CorePreferencesFlags.WaterboxMemoryConsistencyCheck),
});
if (_loadFailure != "")
using (_exe.EnterExit())
{
_adapter = CallingConventionAdapters.MakeWaterbox(new Delegate[] { _logCallback, _baseTimeCallback, _inputPollCallback, _filenameCallback }, _exe);
_core = BizInvoker.GetInvoker<LibMAME>(_exe, _exe, _adapter);
StartMAME(lp.Roms);
}
if (_loadFailure != string.Empty)
{
Dispose();
throw new Exception("\n\n" + _loadFailure);
}
RomDetails = _gameFullName + "\r\n" + string.Join("\r\n", _romHashes.Select(static r => $"{r.Key} - {r.Value}"));
// concat all SHA1 hashes together (unprefixed), then hash that
var hashes = string.Concat(_romHashes
.Select(static r => r.Value.Split(' ')
.First(static s => s.StartsWith("SHA:"))
.RemovePrefix("SHA:")));
lp.Game.Name = _gameFullName;
lp.Game.Hash = SHA1Checksum.ComputeDigestHex(Encoding.ASCII.GetBytes(hashes));
lp.Game.Status = RomStatus.GoodDump;
_exe.Seal();
}
private bool IsCrashed => !_mameThread.IsAlive;
private readonly LibMAME _core;
private readonly WaterboxHost _exe;
private readonly ICallingConventionAdapter _adapter;
// use this instead of a standard WaitOne on the main thread
// throws if the mame thread dies
private void SafeWaitEvent(WaitHandle waiter)
{
while (!waiter.WaitOne(200))
{
// timed out, check the other thread is dead
if (IsCrashed)
{
throw new Exception("MAME thread died unexpectingly?");
}
}
}
private readonly LibMAME.LogCallbackDelegate _logCallback;
private readonly LibMAME.BaseTimeCallbackDelegate _baseTimeCallback;
private readonly LibMAME.InputPollCallbackDelegate _inputPollCallback;
public string RomDetails { get; }
private readonly string _gameFileName;
private string _gameFullName = "Arcade";
private string _gameShortName = "arcade";
private readonly string _gameDirectory;
private readonly string _gameFileName;
private string _loadFailure = "";
private readonly SortedList<string, string> _romHashes = new();
private string _loadFailure = string.Empty;
private readonly Thread _mameThread;
private readonly ManualResetEvent _mameStartupComplete = new(false);
private readonly AutoResetEvent _mameCommandComplete = new(false);
private readonly AutoResetEvent _mameCommandWaitDone = new(false);
private LibMAME.PeriodicCallbackDelegate _periodicCallback;
private LibMAME.SoundCallbackDelegate _soundCallback;
private LibMAME.BootCallbackDelegate _bootCallback;
private LibMAME.LogCallbackDelegate _logCallback;
private void ExecuteMAMEThread()
private void StartMAME(List<IRomAsset> roms)
{
_periodicCallback = MAMEPeriodicCallback;
_soundCallback = MAMESoundCallback;
_bootCallback = MAMEBootCallback;
_logCallback = MAMELogCallback;
_core.mame_set_log_callback(_logCallback);
_core.mame_set_base_time_callback(_baseTimeCallback);
LibMAME.mame_set_periodic_callback(_periodicCallback);
LibMAME.mame_set_sound_callback(_soundCallback);
LibMAME.mame_set_boot_callback(_bootCallback);
LibMAME.mame_set_log_callback(_logCallback);
var gameName = _gameFileName.Split('.')[0];
// mame expects chd files in a folder of the game name
string MakeFileName(IRomAsset rom)
=> rom.Extension.ToLowerInvariant() == ".chd"
? gameName + '/' + Path.GetFileNameWithoutExtension(rom.RomPath).ToLowerInvariant() + rom.Extension.ToLowerInvariant()
: Path.GetFileNameWithoutExtension(rom.RomPath).ToLowerInvariant() + rom.Extension.ToLowerInvariant();
foreach (var rom in roms)
{
_exe.AddReadonlyFile(rom.FileData, MakeFileName(rom));
}
// https://docs.mamedev.org/commandline/commandline-index.html
List<string> args = new List<string>
var args = new List<string>
{
"mame" // dummy, internally discarded by index, so has to go first
, _gameFileName // no dash for rom names
@ -174,46 +119,77 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
, "-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
, "-rompath", "" // mame doesn't load roms from full paths, only from dirs to scan
, "-joystick_contradictory" // allow L+R/U+D on digital joystick
, "-nonvram_save" // prevent dumping non-volatile ram to disk
, "-artpath", "mame\\artwork" // path to load artowrk from
, "-diff_directory", "mame\\diff" // hdd diffs, whenever stuff is written back to an image
, "-cfg_directory", "?" // send invalid path to prevent cfg handling
, "-nvram_directory", "" // path to nvram from
, "-artpath", "?" // path to artwork
, "-diff_directory", "?" // path to hdd diffs
, "-cfg_directory", "?" // path to config
, "-volume", "-32" // lowest attenuation means mame osd remains silent
, "-output", "console" // print everything to hawk console
, "-samplerate", _sampleRate.ToString() // match hawk samplerate
, "-sound", "none" // forbid osd sound driver
, "-video", "none" // forbid mame window altogether
, "-keyboardprovider", "none"
, "-mouseprovider", "none"
, "-lightgunprovider", "none"
, "-joystickprovider", "none"
// , "-debug" // launch mame debugger (because we can)
};
if (_syncSettings.DriverSettings.TryGetValue(
MAMELuaCommand.MakeLookupKey(_gameFileName.Split('.')[0], LibMAME.BIOS_LUA_CODE),
out string value))
MAMELuaCommand.MakeLookupKey(gameName, LibMAME.BIOS_LUA_CODE),
out var value))
{
args.AddRange(new[] { "-bios", value });
}
LibMAME.mame_launch(args.Count, args.ToArray());
if (_core.mame_launch(args.Count, args.ToArray()) == 0)
{
CheckVersions();
UpdateGameName();
UpdateVideo();
UpdateAspect();
UpdateFramerate();
InitMemoryDomains();
GetNVRAMFilenames();
GetInputFields();
GetROMsInfo();
FetchDefaultGameSettings();
OverrideGameSettings();
// advance to the first periodic callback while paused (to ensure no emulation is done)
_core.mame_lua_execute(MAMELuaCommand.Pause);
_core.mame_coswitch();
_core.mame_lua_execute(MAMELuaCommand.Unpause);
}
else if (_loadFailure == string.Empty)
{
_loadFailure = "Unknown load error occurred???";
}
foreach (var rom in roms)
{
// only close non-chd files
if (rom.Extension.ToLowerInvariant() != ".chd")
{
_exe.RemoveReadonlyFile(MakeFileName(rom));
}
}
}
private static string MameGetString(string command)
private string MameGetString(string command)
{
IntPtr ptr = LibMAME.mame_lua_get_string(command, out var lengthInBytes);
var ptr = _core.mame_lua_get_string(command, out var lengthInBytes);
if (ptr == IntPtr.Zero)
{
Console.WriteLine("LibMAME ERROR: string buffer pointer is null");
return "";
return string.Empty;
}
var ret = Marshal.PtrToStringAnsi(ptr, lengthInBytes);
if (!LibMAME.mame_lua_free_string(ptr))
if (!_core.mame_lua_free_string(ptr))
{
Console.WriteLine("LibMAME ERROR: string buffer wasn't freed");
}
@ -236,94 +212,6 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
$"MAME is { mameVersion }\n" +
$"MAMEHawk is { version }");
}
private enum MAME_CMD
{
NO_CMD = -1,
STEP,
VIDEO,
EXIT,
WAIT,
}
private volatile MAME_CMD _mameCmd = MAME_CMD.NO_CMD;
private void MAMEPeriodicCallback()
{
if (_mameCmd != MAME_CMD.NO_CMD)
{
switch (_mameCmd)
{
case MAME_CMD.STEP:
SendInput();
LibMAME.mame_lua_execute(MAMELuaCommand.Step);
break;
case MAME_CMD.VIDEO:
UpdateVideo();
break;
case MAME_CMD.EXIT:
LibMAME.mame_lua_execute(MAMELuaCommand.Exit);
break;
case MAME_CMD.WAIT:
break;
}
_mameCmd = MAME_CMD.NO_CMD;
_mameCommandComplete.Set();
_mameCommandWaitDone.WaitOne();
}
}
private void MAMESoundCallback()
{
const int bytesPerSample = 2;
IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetSamples, out var lengthInBytes);
if (ptr == IntPtr.Zero)
{
Console.WriteLine("LibMAME ERROR: audio buffer pointer is null");
return;
}
int numSamples = lengthInBytes / bytesPerSample;
unsafe
{
short* pSample = (short*)ptr;
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();
UpdateGameName();
UpdateVideo();
UpdateAspect();
UpdateFramerate();
InitSound();
InitMemoryDomains();
GetInputFields();
GetROMsInfo();
FetchDefaultGameSettings();
OverrideGameSettings();
int length = LibMAME.mame_lua_get_int("return string.len(manager.machine:buffer_save())");
_mameSaveBuffer = new byte[length];
_hawkSaveBuffer = new byte[length + 4 + 4 + 4 + 1];
_mameStartupComplete.Set();
}
private void MAMELogCallback(LibMAME.OutputChannel channel, int size, string data)
{
@ -334,7 +222,6 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
if (data.Contains("Fatal error"))
{
_mameStartupComplete.Set();
_loadFailure += data;
}
@ -342,11 +229,19 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
if (!data.Contains("pause = "))
{
Console.WriteLine(
$"[MAME { channel.ToString() }] " +
$"[MAME { channel }] " +
$"{ data.Replace('\n', ' ') }");
}
}
private static readonly DateTime _epoch = new(1970, 1, 1, 0, 0, 0);
private long MAMEBaseTimeCallback()
{
var start = DeterministicEmulation ? _syncSettings.RTCSettings.InitialTime : DateTime.Now;
return (long)(start - _epoch).TotalSeconds;
}
private static class MAMELuaCommand
{
// commands
@ -398,17 +293,28 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
"end " +
"table.sort(final) " +
"return table.concat(final)";
public const string GetInputFields =
public const string GetButtonFields =
"local 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 " +
"if field.type_class ~= \"dipswitch\" and not field.is_analog then " +
"table.insert(final, string.format(\"%s,%s;\", tag, name)) " +
"end " +
"end " +
"end " +
"table.sort(final) " +
"return table.concat(final)";
public const string GetAnalogFields =
"local 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\" and field.is_analog then " +
"table.insert(final, string.format(\"%s,%s,%d,%d,%d;\", tag, name, field.defvalue, field.minvalue, field.maxvalue)) " +
"end " +
"end " +
"end " +
"table.sort(final) " +
"return table.concat(final)";
public const string GetDIPSwitchTags =
"local final = {} " +
"for tag, _ in pairs(manager.machine.ioport.ports) do " +

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Threading;
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Arcades.MAME
{
public class MAMEMachineDB
{
/// <summary>
/// blocks until the DB is done loading
/// </summary>
private static ManualResetEvent _acquire;
private readonly HashSet<string> MachineDB = new();
private static MAMEMachineDB Instance;
public static void Initialize(string basePath)
{
if (_acquire != null) throw new InvalidOperationException("MAME Machine DB multiply initialized");
_acquire = new(false);
var sw = Stopwatch.StartNew();
ThreadPool.QueueUserWorkItem(_ =>
{
Instance = new(basePath);
Util.DebugWriteLine("MAME Machine DB load: " + sw.Elapsed + " sec");
_acquire.Set();
});
}
private MAMEMachineDB(string basePath)
{
using HawkFile mameMachineFile = new(Path.Combine(basePath, "mame_machines.txt"));
using var sr = new StreamReader(mameMachineFile.GetStream());
while (true)
{
var line = sr.ReadLine();
if (string.IsNullOrEmpty(line))
{
break;
}
MachineDB.Add(line);
}
}
public static bool IsMAMEMachine(string path)
{
if (_acquire == null) throw new InvalidOperationException("MAME Machine DB not initialized. It's a client responsibility because only a client knows where the database is located.");
if (Path.GetExtension(path).ToLowerInvariant() != ".zip") return false;
_acquire.WaitOne();
return Instance.MachineDB.Contains(Path.GetFileNameWithoutExtension(path).ToLowerInvariant());
}
}
}

View File

@ -0,0 +1,55 @@
# special makefile for MAME, which simply calls MAME's own makefile with special arguments for waterboxing
WATERBOX_DIR := $(realpath ..)
ROOT_DIR := $(realpath .)
OUTPUTDLL_DIR := $(realpath $(WATERBOX_DIR)/../Assets/dll)
OUTPUTDLLCOPY_DIR := $(realpath $(WATERBOX_DIR)/../output/dll)
OUT_DIR := $(ROOT_DIR)/obj
OBJ_DIR := $(OUT_DIR)/release
DOBJ_DIR := $(OUT_DIR)/debug
TARGET := libmamearcade.wbx
.DEFAULT_GOAL := release
TARGET_RELEASE := $(OBJ_DIR)/$(TARGET)
TARGET_DEBUG := $(DOBJ_DIR)/$(TARGET)
.PHONY: release debug install install-debug
release: $(TARGET_RELEASE)
debug: $(TARGET_DEBUG)
$(TARGET_RELEASE):
@$(MAKE) SUBTARGET=arcade WATERBOX=1 OPTIMIZE=s DEPRECATED=0 NOWERROR=1 \
WBX_DIR=$(WATERBOX_DIR) BUILDDIR=$(OBJ_DIR) -C $(ROOT_DIR)/mame
@mv -f $(ROOT_DIR)/mame/$(TARGET) $(TARGET_RELEASE)
@strip --strip-all -wK "mame_*" -K "co_clean" -K "ecl_seal" $(TARGET_RELEASE)
$(TARGET_DEBUG):
@$(MAKE) SUBTARGET=arcade WATERBOX=1 OPTIMIZE=g DEBUG=1 SYMBOLS=1 PROFILER=0 DEPRECATED=0 NOWERROR=1 \
WBX_DIR=$(WATERBOX_DIR) BUILDDIR=$(DOBJ_DIR) -C $(ROOT_DIR)/mame
@mv -f $(ROOT_DIR)/mame/$(TARGET) $(TARGET_DEBUG)
install: $(TARGET_RELEASE)
@cp -f $< $(OUTPUTDLL_DIR)
@zstd --stdout --ultra -22 --threads=0 $< > $(OUTPUTDLL_DIR)/$(TARGET).zst
@cp $(OUTPUTDLL_DIR)/$(TARGET).zst $(OUTPUTDLLCOPY_DIR)/$(TARGET).zst || true
@echo Release build of $(TARGET) installed.
install-debug: $(TARGET_DEBUG)
@cp -f $< $(OUTPUTDLL_DIR)
@zstd --stdout --ultra -22 --threads=0 $< > $(OUTPUTDLL_DIR)/$(TARGET).zst
@cp $(OUTPUTDLL_DIR)/$(TARGET).zst $(OUTPUTDLLCOPY_DIR)/$(TARGET).zst || true
@echo Debug build of $(TARGET) installed.
.PHONY: clean clean-release clean-debug
clean:
@$(MAKE) clean -C $(ROOT_DIR)/mame
rm -rf $(OUT_DIR)
clean-release:
@$(MAKE) clean -C $(ROOT_DIR)/mame
rm -rf $(OUT_DIR)/release
clean-debug:
@$(MAKE) clean -C $(ROOT_DIR)/mame
rm -rf $(OUT_DIR)/debug

@ -0,0 +1 @@
Subproject commit fc574d2005eb439da6112cc524618d27cdcfb71e