diff --git a/src/BizHawk.Client.Common/RomLoader.cs b/src/BizHawk.Client.Common/RomLoader.cs index 975c12fd4d..c7694f3046 100644 --- a/src/BizHawk.Client.Common/RomLoader.cs +++ b/src/BizHawk.Client.Common/RomLoader.cs @@ -464,7 +464,7 @@ namespace BizHawk.Client.Common nextEmulator = new MAME( file.Directory, file.CanonicalName, - GetCoreSyncSettings(), + GetCoreSyncSettings(), out var gameName ); rom.GameInfo.Name = gameName; diff --git a/src/BizHawk.Client.EmuHawk/config/GenericCoreConfig.cs b/src/BizHawk.Client.EmuHawk/config/GenericCoreConfig.cs index 850c400d77..e412cdd932 100644 --- a/src/BizHawk.Client.EmuHawk/config/GenericCoreConfig.cs +++ b/src/BizHawk.Client.EmuHawk/config/GenericCoreConfig.cs @@ -92,6 +92,19 @@ namespace BizHawk.Client.EmuHawk TypeDescriptor.RemoveProvider(desc, typeof(Emulation.Cores.Waterbox.NymaCore.NymaSyncSettings)); } } + else if(owner.Emulator is Emulation.Cores.Arcades.MAME.MAME mame) + { + var desc = new Emulation.Cores.Arcades.MAME.MAMETypeDescriptorProvider(mame.CurrentDriverSettings); + try + { + TypeDescriptor.AddProvider(desc, typeof(Emulation.Cores.Arcades.MAME.MAME.MAMESyncSettings)); + DoDialog(owner, "MAME", true, false); + } + finally + { + TypeDescriptor.RemoveProvider(desc, typeof(Emulation.Cores.Arcades.MAME.MAME.MAMESyncSettings)); + } + } else { using var dlg = new GenericCoreConfig(owner) { Text = title }; diff --git a/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.ISettable.ComponentModel.cs b/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.ISettable.ComponentModel.cs new file mode 100644 index 0000000000..5dcb2b975c --- /dev/null +++ b/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.ISettable.ComponentModel.cs @@ -0,0 +1,104 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using static BizHawk.Emulation.Cores.Arcades.MAME.MAME; +using static BizHawk.Emulation.Cores.Arcades.MAME.MAME.DriverSetting; + +namespace BizHawk.Emulation.Cores.Arcades.MAME +{ + public class MAMETypeDescriptorProvider : TypeDescriptionProvider + { + public MAMETypeDescriptorProvider(List settings) + { + Settings = settings; + } + + public List Settings { get; } + public override bool IsSupportedType(Type type) => type == typeof(List); + public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) + => new SyncSettingsCustomTypeDescriptor(Settings); + } + + public class SyncSettingsCustomTypeDescriptor : CustomTypeDescriptor + { + public SyncSettingsCustomTypeDescriptor(List settings) + { + Settings = settings; + } + + public List Settings { get; } + public override string GetClassName() => nameof(List); + public override string GetComponentName() => nameof(List); + public override PropertyDescriptor GetDefaultProperty() => GetProperties()[0]; // "default" ?? + public override PropertyDescriptorCollection GetProperties(Attribute[] attributes) => GetProperties(); + + public override PropertyDescriptorCollection GetProperties() + { + var s = Settings.Select(m => new MAMEPropertyDescriptor(m)); + return new PropertyDescriptorCollection(s.ToArray()); + } + } + + public class MAMEPropertyDescriptor : PropertyDescriptor + { + public MAMEPropertyDescriptor(DriverSetting setting) : base(setting.LookupKey, new Attribute[0]) + { + Setting = setting; + } + + public DriverSetting Setting { get; private set; } + 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 Type ComponentType => typeof(List); + public override bool IsReadOnly => false; + public override string Name => Setting.LookupKey; + public override string DisplayName => Setting.Name; + public override string Description => Setting.LookupKey; + + public override object GetValue(object component) + { + var ss = (MAMESyncSettings)component; + if (!ss.DriverSettings.TryGetValue(Setting.LookupKey, out var val)) + val = Setting.DefaultValue; + return ConvertFromString(val); + } + + public override void ResetValue(object component) + { + ((MAMESyncSettings)component).DriverSettings.Remove(Setting.LookupKey); + } + + public override void SetValue(object component, object value) + { + var s = ConvertToString(value); + if (s == null || s == Setting.DefaultValue) + { + ResetValue(component); + return; + } + ((MAMESyncSettings)component).DriverSettings[Setting.LookupKey] = s; + } + + private class MyTypeConverter : TypeConverter + { + public DriverSetting Setting { get; set; } + 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); + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) => destinationType == typeof(string); + public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context) + => new StandardValuesCollection(Setting.Options.Select(e => e.Key).ToList()); + public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) + => Setting.Options.SingleOrDefault(d => d.Value == (string)value).Key ?? Setting.DefaultValue; + public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + => Setting.Options[(string)value] ?? Setting.Options[Setting.DefaultValue]; + } + } +} \ No newline at end of file diff --git a/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.ISettable.cs b/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.ISettable.cs index 894463c339..e4007304b4 100644 --- a/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.ISettable.cs +++ b/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.ISettable.cs @@ -1,41 +1,116 @@ -using System.Dynamic; +using System; +using System.Collections.Generic; +using System.Linq; using BizHawk.Common; using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Arcades.MAME { - public partial class MAME : ISettable + public partial class MAME : ISettable { public object GetSettings() => null; public PutSettingsDirtyBits PutSettings(object o) => PutSettingsDirtyBits.None; + public List CurrentDriverSettings = new List(); + private MAMESyncSettings _syncSettings; - private SyncSettings _syncSettings; - - public SyncSettings GetSyncSettings() + public MAMESyncSettings GetSyncSettings() { return _syncSettings.Clone(); } - public PutSettingsDirtyBits PutSyncSettings(SyncSettings o) + public PutSettingsDirtyBits PutSyncSettings(MAMESyncSettings o) { - bool ret = SyncSettings.NeedsReboot(o, _syncSettings); + bool ret = MAMESyncSettings.NeedsReboot(o, _syncSettings); _syncSettings = o; return ret ? PutSettingsDirtyBits.RebootCore : PutSettingsDirtyBits.None; } - public class SyncSettings + public class MAMESyncSettings { - public static bool NeedsReboot(SyncSettings x, SyncSettings y) + public Dictionary DriverSettings { get; set; } = new Dictionary(); + + public static bool NeedsReboot(MAMESyncSettings x, MAMESyncSettings y) { return !DeepEquality.DeepEquals(x, y); } - public SyncSettings Clone() + public MAMESyncSettings Clone() { - return (SyncSettings)MemberwiseClone(); + return (MAMESyncSettings)MemberwiseClone(); } + } - public ExpandoObject ExpandoSettings { get; set; } + public void FetchDefaultGameSettings() + { + string DIPSwitchTags = MameGetString(MAMELuaCommand.GetDIPSwitchTags); + string[] tags = DIPSwitchTags.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string tag in tags) + { + string DIPSwitchFields = MameGetString(MAMELuaCommand.GetDIPSwitchFields(tag)); + string[] fieldNames = DIPSwitchFields.Split(new char[] { '^' }, StringSplitOptions.RemoveEmptyEntries); + + foreach (string fieldName in fieldNames) + { + DriverSetting setting = new DriverSetting() + { + Name = fieldName, + GameName = _gameShortName, + LuaCode = MAMELuaCommand.InputField(tag, fieldName), + Type = SettingType.DIPSWITCH, + DefaultValue = LibMAME.mame_lua_get_int( + $"return { MAMELuaCommand.InputField(tag, fieldName) }.defvalue").ToString() + }; + + string DIPSwitchOptions = MameGetString(MAMELuaCommand.GetDIPSwitchOptions(tag, fieldName)); + string[] options = DIPSwitchOptions.Split(new char[] { '@' }, StringSplitOptions.RemoveEmptyEntries); + + foreach(string option in options) + { + string[] opt = option.Split(new char[] { '~' }, StringSplitOptions.RemoveEmptyEntries); + setting.Options.Add(opt[0], opt[1]); + } + + CurrentDriverSettings.Add(setting); + } + } + } + + public void OverrideGameSettings() + { + foreach (KeyValuePair setting in _syncSettings.DriverSettings) + { + DriverSetting s = CurrentDriverSettings.SingleOrDefault(s => s.LookupKey == setting.Key); + + if (s != null) + { + LibMAME.mame_lua_execute($"{ s.LuaCode }:set_value({ setting.Value })"); + } + } + } + + public class DriverSetting + { + public string Name { get; set; } + public string GameName { get; set; } + public string LuaCode { get; set; } + public string DefaultValue { get; set; } + public SettingType Type { get; set; } + public Dictionary Options { get; set; } + public string LookupKey => $"[{ GameName }] { LuaCode }"; + + public DriverSetting() + { + Name = null; + GameName = null; + DefaultValue = null; + Options = new Dictionary(); + } + } + + public enum SettingType + { + DIPSWITCH, BIOS } } } \ No newline at end of file diff --git a/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs b/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs index 25b8c9bda2..a43839135c 100644 --- a/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs +++ b/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs @@ -84,26 +84,24 @@ using BizHawk.Emulation.Common; 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, IStatable, IInputPollable + public partial class MAME : IEmulator, IVideoProvider, ISoundProvider, ISettable, IStatable, IInputPollable { - public MAME(string dir, string file, MAME.SyncSettings syncSettings, out string gamename) + public MAME(string dir, string file, MAME.MAMESyncSettings syncSettings, out string gamename) { OSTailoredCode.LinkedLibManager.FreeByPtr(OSTailoredCode.LinkedLibManager.LoadOrThrow(LibMAME.dll)); // don't bother if the library is missing + _gameDirectory = dir; + _gameFileName = file; + ServiceProvider = new BasicServiceProvider(this); - _gameDirectory = dir; - _gameFilename = file; + _syncSettings = syncSettings ?? new MAMESyncSettings(); _mameThread = new Thread(ExecuteMAMEThread); _mameThread.Start(); _mameStartupComplete.WaitOne(); - _syncSettings = (SyncSettings)syncSettings ?? new SyncSettings(); - _syncSettings.ExpandoSettings = new ExpandoObject(); - var dynamicObject = (IDictionary)_syncSettings.ExpandoSettings; - dynamicObject.Add("OKAY", 1); - gamename = _gameName; + gamename = _gameFullName; if (_loadFailure != "") { @@ -112,9 +110,10 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME } } - private string _gameName = "Arcade"; + private string _gameFullName = "Arcade"; + private string _gameShortName = "arcade"; private readonly string _gameDirectory; - private readonly string _gameFilename; + private readonly string _gameFileName; private string _loadFailure = ""; private readonly Thread _mameThread; private readonly ManualResetEvent _mameStartupComplete = new ManualResetEvent(false); @@ -128,7 +127,6 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME private void ExecuteMAMEThread() { - // dodge GC _periodicCallback = MAMEPeriodicCallback; _soundCallback = MAMESoundCallback; _bootCallback = MAMEBootCallback; @@ -143,7 +141,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME string[] args = { "mame" // dummy, internally discarded by index, so has to go first - , _gameFilename // no dash for rom names + , _gameFileName // no dash for rom names , "-noreadconfig" // forbid reading ini files , "-nowriteconfig" // forbid writing ini files , "-norewind" // forbid rewind savestates (captured upon frame advance) @@ -192,7 +190,8 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME private void UpdateGameName() { - _gameName = MameGetString(MAMELuaCommand.GetGameName); + _gameFullName = MameGetString(MAMELuaCommand.GetGameFullName); + _gameShortName = MameGetString(MAMELuaCommand.GetGameShortName); } private void CheckVersions() @@ -282,6 +281,8 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME UpdateFramerate(); UpdateGameName(); InitMemoryDomains(); + FetchDefaultGameSettings(); + OverrideGameSettings(); int length = LibMAME.mame_lua_get_int("return string.len(manager.machine:buffer_save())"); _mameSaveBuffer = new byte[length]; @@ -341,21 +342,22 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME // getters public const string GetVersion = "return emu.app_version()"; - public const string GetGameName = "return manager.machine.system.description"; - public const string GetPixels = "return manager.machine.video:snapshot_pixels()"; - public const string GetSamples = "return manager.machine.sound:get_samples()"; + public const string GetGameShortName = "return manager.machine.system.name"; + public const string GetGameFullName = "return manager.machine.system.description"; public const string GetWidth = "return (select(1, manager.machine.video:snapshot_size()))"; public const string GetHeight = "return (select(2, manager.machine.video:snapshot_size()))"; + public const string GetPixels = "return manager.machine.video:snapshot_pixels()"; + public const string GetSamples = "return manager.machine.sound:get_samples()"; public const string GetMainCPUName = "return manager.machine.devices[\":maincpu\"].shortname"; // memory space public const string GetSpace = "return manager.machine.devices[\":maincpu\"].spaces[\"program\"]"; - public const string GetSpaceMapCount = "return #manager.machine.devices[\":maincpu\"].spaces[\"program\"].map.entries"; - public const string SpaceMap = "manager.machine.devices[\":maincpu\"].spaces[\"program\"].map.entries"; public const string GetSpaceAddressMask = "return manager.machine.devices[\":maincpu\"].spaces[\"program\"].address_mask"; public const string GetSpaceAddressShift = "return manager.machine.devices[\":maincpu\"].spaces[\"program\"].shift"; public const string GetSpaceDataWidth = "return manager.machine.devices[\":maincpu\"].spaces[\"program\"].data_width"; public const string GetSpaceEndianness = "return manager.machine.devices[\":maincpu\"].spaces[\"program\"].endianness"; + public const string GetSpaceMapCount = "return #manager.machine.devices[\":maincpu\"].spaces[\"program\"].map.entries"; + public const string SpaceMap = "manager.machine.devices[\":maincpu\"].spaces[\"program\"].map.entries"; // complex stuff public const string GetFrameNumber = @@ -372,8 +374,17 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME public const string GetBoundY = "local b = manager.machine.render.ui_target.current_view.bounds " + "return b.y1-b.y0"; + public const string GetROMsInfo = + "local final = {} " + + "for __, r in pairs(manager.machine.devices[\":\"].roms) do " + + "if (r:hashdata() ~= \"\") then " + + "table.insert(final, string.format(\"%s,%s,%s;\", r:name(), r:hashdata(), r:flags())) " + + "end " + + "end " + + "table.sort(final) " + + "return table.concat(final)"; public const string GetInputFields = - "final = {} " + + "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 " + @@ -383,15 +394,37 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME "end " + "table.sort(final) " + "return table.concat(final)"; - public const string GetROMsInfo = - "final = {} " + - "for __, r in pairs(manager.machine.devices[\":\"].roms) do " + - "if (r:hashdata() ~= \"\") then " + - "table.insert(final, string.format(\"%s,%s,%s;\", r:name(), r:hashdata(), r:flags())) " + + public const string GetDIPSwitchTags = + "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 " + + "table.insert(final, tag..\";\") " + + "break " + + "end " + "end " + "end " + "table.sort(final) " + "return table.concat(final)"; + + public static string InputField(string tag, string fieldName) => + $"manager.machine.ioport.ports[\"{ tag }\"].fields[\"{ fieldName }\"]"; + public static string GetDIPSwitchFields(string tag) => + "local final = { } " + + $"for name, field in pairs(manager.machine.ioport.ports[\"{ tag }\"].fields) do " + + "if field.type_class == \"dipswitch\" then " + + "table.insert(final, field.name..\"^\") " + + "end " + + "end " + + "table.sort(final) " + + "return table.concat(final)"; + public static string GetDIPSwitchOptions(string tag, string fieldName) => + "local final = { } " + + $"for value, description in pairs(manager.machine.ioport.ports[\"{ tag }\"].fields[\"{ fieldName }\"].settings) do " + + "table.insert(final, string.format(\"%d~%s@\", value, description)) " + + "end " + + "table.sort(final) " + + "return table.concat(final)"; } } }