MAME Waterbox (#3437)
This commit is contained in:
parent
e7e587d625
commit
066297d5e7
|
@ -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
|
@ -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,
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
GGL,
|
||||
ChannelF,
|
||||
Odyssey2,
|
||||
MAME,
|
||||
Arcade,
|
||||
MSX,
|
||||
SuperGameBoy,
|
||||
UzeBox,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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_"))
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 " +
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue