private void UpdateFramerate() { VsyncNumerator = 1000000000; long refresh = (long)LibMAME.mame_lua_get_double(MAMELuaCommand.GetRefresh); VsyncDenominator = (int)(refresh / 1000000000); }
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); if (ptr == IntPtr.Zero) { Console.WriteLine("LibMAME ERROR: frame buffer pointer is null"); return; } if (expectedSize * bytesPerPixel != lengthInBytes) { Console.WriteLine( "LibMAME ERROR: frame buffer has wrong size\n" + $"width: { BufferWidth } pixels\n" + $"height: { BufferHeight } pixels\n" + $"expected: { expectedSize * bytesPerPixel } bytes\n" + $"received: { lengthInBytes } bytes\n"); return; } _frameBuffer = new int[expectedSize]; Marshal.Copy(ptr, _frameBuffer, 0, expectedSize); if (!LibMAME.mame_lua_free_string(ptr)) { Console.WriteLine("LibMAME ERROR: frame buffer wasn't freed"); } }
private void GetInputFields() { int lengthInBytes; IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetInputFields, out lengthInBytes); if (ptr == IntPtr.Zero) { Console.WriteLine("LibMAME ERROR: string buffer pointer is null"); return; } string inputFields = Marshal.PtrToStringAnsi(ptr, lengthInBytes); string[] portFields = inputFields.Split(';'); MAMEController.BoolButtons.Clear(); foreach (string portField in portFields) { if (portField != string.Empty) { string[] substrings = portField.Split(','); string tag = substrings.First(); string field = substrings.Last(); fieldsPorts.Add(field, tag); MAMEController.BoolButtons.Add(field); } } if (!LibMAME.mame_lua_free_string(ptr)) { Console.WriteLine("LibMAME ERROR: string buffer wasn't freed"); } }
private void MAMESoundCallback() { 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; } _numSamples = lengthInBytes / bytesPerSample; unsafe { short *pSample = (short *)ptr.ToPointer(); for (int i = 0; i < _numSamples; i++) { _audioSamples.Enqueue(*(pSample + i)); _dAudioSamples++; } } if (!LibMAME.mame_lua_free_string(ptr)) { Console.WriteLine("LibMAME ERROR: audio buffer wasn't freed"); } }
/* * FrameAdvance() and MAME * * MAME fires the periodic callback on every video and debugger update, * which happens every VBlank and also repeatedly at certain time * intervals while paused. Since MAME's luaengine runs in a separate * thread, it's only safe to update everything we need per frame during * this callback, when it's explicitly waiting for further lua commands. * * If we disable throttling and pass -update_in_pause, there will be no * delay between video updates. This allows to run at full speed while * frame-stepping. * * MAME only captures new frame data once per VBlank, while unpaused. * But it doesn't have an exclusive VBlank callback we could attach to. * It has a LUA_ON_FRAME_DONE callback, but that fires even more * frequently and updates all sorts of other non-video stuff, and we * need none of that here. * * So we filter out all the calls that happen while paused (non-VBlank * updates). Then, when Hawk asks us to advance a frame, we virtually * unpause and declare the new frame unfinished. This informs MAME that * it should advance one frame internally. Hawk starts waiting for the * MAME thread to complete the request. * * After MAME's done advancing, it fires the periodic callback again. * That's when we update everything and declare the new frame finished, * filtering out any further updates again. Then we allow Hawk to * complete frame-advancing. */ private void MAMEPeriodicCallback() { if (_exiting) { LibMAME.mame_lua_execute(MAMELuaCommand.Exit); _exiting = false; } if (_memAccess) { _mamePeriodicComplete.Set(); _memoryAccessComplete.WaitOne(); return; } //int MAMEFrame = LibMAME.mame_lua_get_int(MAMELuaCommand.GetFrameNumber); if (!_paused) { LibMAME.mame_lua_execute(MAMELuaCommand.Step); _frameDone = false; _paused = true; } else if (!_frameDone) { Update(); _frameDone = true; _mameFrameComplete.Set(); } }
private void ExecuteMAMEThread() { // dodge GC periodicCallback = MAMEPeriodicCallback; soundCallback = MAMESoundCallback; bootCallback = MAMEBootCallback; logCallback = MAMELogCallback; LibMAME.mame_set_periodic_callback(periodicCallback); LibMAME.mame_set_sound_callback(soundCallback); LibMAME.mame_set_boot_callback(bootCallback); LibMAME.mame_set_log_callback(logCallback); // https://docs.mamedev.org/commandline/commandline-index.html string[] args = new string[] { "mame" // dummy, internally discarded by index, so has to go first , gameFilename // no dash for rom names , "-noreadconfig" // forbid reading any config files , "-norewind" // forbid rewind savestates (captured upon frame advance) , "-skip_gameinfo" // forbid this blocking screen that requires user input , "-nothrottle" // forbid throttling to "real" speed of the device , "-update_in_pause" // ^ including frame-advancing , "-rompath", gameDirectory // mame doesn't load roms from full paths, only from dirs to scan , "-volume", "-32" // lowest attenuation means mame osd remains silent , "-output", "console" // print everyting to hawk console , "-samplerate", sampleRate.ToString() // match hawk samplerate , "-video", "none" // forbid mame window altogether , "-keyboardprovider", "none" , "-mouseprovider", "none" , "-lightgunprovider", "none" , "-joystickprovider", "none" }; LibMAME.mame_launch(args.Length, args); }
private void InitMemoryDomains() { var domains = new List <MemoryDomain>(); _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 endianString = MameGetString(MAMELuaCommand.GetSpaceEndianness); var deviceName = MameGetString(MAMELuaCommand.GetMainCPUName); //var addrSize = (size * 2).ToString(); MemoryDomain.Endian endian = MemoryDomain.Endian.Unknown; if (endianString == "little") { endian = MemoryDomain.Endian.Little; } else if (endianString == "big") { endian = MemoryDomain.Endian.Big; } var mapCount = LibMAME.mame_lua_get_int(MAMELuaCommand.GetSpaceMapCount); for (int i = 1; i <= mapCount; i++) { var read = MameGetString($"return { MAMELuaCommand.SpaceMap }[{ i }].readtype"); var write = MameGetString($"return { MAMELuaCommand.SpaceMap }[{ i }].writetype"); if (read == "ram" && write == "ram" || read == "rom") { var firstOffset = LibMAME.mame_lua_get_int($"return { MAMELuaCommand.SpaceMap }[{ i }].offset"); var lastOffset = LibMAME.mame_lua_get_int($"return { MAMELuaCommand.SpaceMap }[{ i }].endoff"); var name = $"{ deviceName } : { read } : 0x{ firstOffset:X}-0x{ lastOffset:X}"; domains.Add(new MemoryDomainDelegate(name, lastOffset - firstOffset + 1, endian, delegate(long addr) { return(_peek(addr, firstOffset, size)); }, read == "rom" ? (Action <long, byte>) null : delegate(long addr, byte val) { _poke(addr, val, firstOffset, size); }, dataWidth)); } } domains.Add(new MemoryDomainDelegate(deviceName + " : System Bus", size, endian, delegate(long addr) { return(_peek(addr, 0, size)); }, null, dataWidth)); _memoryDomains = new MemoryDomainList(domains); (ServiceProvider as BasicServiceProvider).Register <IMemoryDomains>(_memoryDomains); }
private void MAMEBootCallback() { LibMAME.mame_lua_execute(MAMELuaCommand.Pause); CheckVersions(); GetInputFields(); Update(); MAMEStartupComplete.Set(); }
private void UpdateAspect() { int x = (int)LibMAME.mame_lua_get_double(MAMELuaCommand.GetBoundX); int y = (int)LibMAME.mame_lua_get_double(MAMELuaCommand.GetBoundY); VirtualHeight = BufferWidth > BufferHeight * x / y ? BufferWidth * y / x : BufferHeight; VirtualWidth = VirtualHeight * x / y; }
private void SendInput() { foreach (var fieldPort in _fieldsPorts) { LibMAME.mame_lua_execute( "manager:machine():ioport()" + $".ports [\"{ fieldPort.Value }\"]" + $".fields [\"{ fieldPort.Key }\"]" + $":set_value({ (_controller.IsPressed(fieldPort.Key) ? 1 : 0) })"); } }
private void UpdateGameName() { int lengthInBytes; IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetGameName, out lengthInBytes); gameName = Marshal.PtrToStringAnsi(ptr, lengthInBytes); if (!LibMAME.mame_lua_free_string(ptr)) { Console.WriteLine("LibMAME ERROR: string buffer wasn't freed"); } }
public void OverrideGameSettings() { foreach (KeyValuePair <string, string> setting in _syncSettings.DriverSettings) { DriverSetting 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 }"); } } }
private void MAMEBootCallback() { LibMAME.mame_lua_execute(MAMELuaCommand.Pause); CheckVersions(); GetInputFields(); Update(); UpdateGameName(); InitMemoryDomains(); _mameStartupComplete.Set(); }
public void LoadStateBinary(BinaryReader reader) { int length = reader.ReadInt32(); if (length != _mameSaveBuffer.Length) { throw new InvalidOperationException("Savestate buffer size mismatch!"); } reader.Read(_mameSaveBuffer, 0, _mameSaveBuffer.Length); Frame = reader.ReadInt32(); LibMAME.mame_lua_execute($"manager:machine():buffer_load({ _mameSaveBuffer })"); }
private void ExecuteMAMEThread() { _periodicCallback = MAMEPeriodicCallback; _soundCallback = MAMESoundCallback; _bootCallback = MAMEBootCallback; _logCallback = MAMELogCallback; LibMAME.mame_set_periodic_callback(_periodicCallback); LibMAME.mame_set_sound_callback(_soundCallback); LibMAME.mame_set_boot_callback(_bootCallback); LibMAME.mame_set_log_callback(_logCallback); // https://docs.mamedev.org/commandline/commandline-index.html List <string> args = new List <string> { "mame" // dummy, internally discarded by index, so has to go first , _gameFileName // no dash for rom names , "-noreadconfig" // forbid reading ini files , "-nowriteconfig" // forbid writing ini files , "-norewind" // forbid rewind savestates (captured upon frame advance) , "-skip_gameinfo" // forbid this blocking screen that requires user input , "-nothrottle" // forbid throttling to "real" speed of the device , "-update_in_pause" // ^ including frame-advancing , "-rompath", _gameDirectory // mame doesn't load roms from full paths, only from dirs to scan , "-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 , "-volume", "-32" // lowest attenuation means mame osd remains silent , "-output", "console" // print everything to hawk console , "-samplerate", _sampleRate.ToString() // match hawk samplerate , "-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)) { args.AddRange(new[] { "-bios", value }); } LibMAME.mame_launch(args.Count, args.ToArray()); }
/* * FrameAdvance() and MAME * * MAME fires the periodic callback on every video and debugger update, * which happens every VBlank and also repeatedly at certain time * intervals while paused. Since MAME's luaengine runs in a separate * thread, it's only safe to update everything we need per frame during * this callback, when it's explicitly waiting for further lua commands. * * If we disable throttling and pass -update_in_pause, there will be no * delay between video updates. This allows to run at full speed while * frame-stepping. * * MAME only captures new frame data once per VBlank, while unpaused. * But it doesn't have an exclusive VBlank callback we could attach to. * It has a LUA_ON_FRAME_DONE callback, but that fires even more * frequently and updates all sorts of other non-video stuff, and we * need none of that here. * * So we filter out all the calls that happen while paused (non-VBlank * updates). Then, when Hawk asks us to advance a frame, we virtually * unpause and declare the new frame unfinished. This informs MAME that * it should advance one frame internally. Hawk starts waiting for the * MAME thread to complete the request. * * After MAME's done advancing, it fires the periodic callback again. * That's when we update everything and declare the new frame finished, * filtering out any further updates again. Then we allow Hawk to * complete frame-advancing. */ private void MAMEPeriodicCallback() { if (_exiting) { LibMAME.mame_lua_execute(MAMELuaCommand.Exit); _exiting = false; } if (_memAccess) { _mamePeriodicComplete.Set(); _memoryAccessComplete.WaitOne(); return; } //int MAMEFrame = LibMAME.mame_lua_get_int(MAMELuaCommand.GetFrameNumber); if (!_paused) { LibMAME.mame_lua_execute(MAMELuaCommand.Step); _frameDone = false; _paused = true; } else if (!_frameDone) { /* * IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetSpaceBuffer, out var lengthInBytes); * * if (ptr == IntPtr.Zero) * { * Console.WriteLine("LibMAME ERROR: audio buffer pointer is null"); * return; * } * * //byte[] buf = new byte[lengthInBytes]; * //Marshal.Copy(ptr, buf, 0, lengthInBytes); * * if (!LibMAME.mame_lua_free_string(ptr)) * { * Console.WriteLine("LibMAME ERROR: audio buffer wasn't freed"); * } */ Update(); _frameDone = true; _mameFrameComplete.Set(); } }
private void MAMEBootCallback() { LibMAME.mame_lua_execute(MAMELuaCommand.Pause); CheckVersions(); GetInputFields(); Update(); UpdateGameName(); InitMemoryDomains(); IntPtr ptr = LibMAME.mame_lua_get_string("return manager:machine():buffer_save()", out var lengthInBytes); _mameSaveBuffer = new byte[lengthInBytes]; _hawkSaveBuffer = new byte[lengthInBytes + 4 + 4]; _mameStartupComplete.Set(); }
private void CheckVersions() { int lengthInBytes; IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetVersion, out lengthInBytes); string MAMEVersion = Marshal.PtrToStringAnsi(ptr, lengthInBytes); if (!LibMAME.mame_lua_free_string(ptr)) { Console.WriteLine("LibMAME ERROR: string buffer wasn't freed"); } string version = this.Attributes().PortedVersion; Debug.Assert(version == MAMEVersion, "MAME versions desync!\n\n" + $"MAME is { MAMEVersion }\n" + $"MAMEHawk is { version }"); }
private static string MameGetString(string command) { IntPtr ptr = LibMAME.mame_lua_get_string(command, out var lengthInBytes); if (ptr == IntPtr.Zero) { Console.WriteLine("LibMAME ERROR: string buffer pointer is null"); return(""); } var ret = Marshal.PtrToStringAnsi(ptr, lengthInBytes); if (!LibMAME.mame_lua_free_string(ptr)) { Console.WriteLine("LibMAME ERROR: string buffer wasn't freed"); } return(ret); }
private void _poke(long addr, byte val, int firstOffset, long size) { if (addr < 0 || addr >= size) { throw new ArgumentOutOfRangeException(); } if (!_memAccess) { _memAccess = true; _mamePeriodicComplete.WaitOne(); } addr += firstOffset; LibMAME.mame_lua_execute($"{ MAMELuaCommand.GetSpace }:write_u8({ addr << _systemBusAddressShift }, { val })"); _memoryAccessComplete.Set(); }
private void MAMEBootCallback() { LibMAME.mame_lua_execute(MAMELuaCommand.Pause); CheckVersions(); GetInputFields(); UpdateVideo(); UpdateAspect(); UpdateFramerate(); UpdateGameName(); InitMemoryDomains(); 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 ExecuteMAMEThread() { // dodge GC _periodicCallback = MAMEPeriodicCallback; _soundCallback = MAMESoundCallback; _bootCallback = MAMEBootCallback; _logCallback = MAMELogCallback; LibMAME.mame_set_periodic_callback(_periodicCallback); LibMAME.mame_set_sound_callback(_soundCallback); LibMAME.mame_set_boot_callback(_bootCallback); LibMAME.mame_set_log_callback(_logCallback); // https://docs.mamedev.org/commandline/commandline-index.html string[] args = { "mame" // dummy, internally discarded by index, so has to go first , _gameFilename // no dash for rom names , "-noreadconfig" // forbid reading ini files , "-nowriteconfig" // forbid writing ini files , "-norewind" // forbid rewind savestates (captured upon frame advance) , "-skip_gameinfo" // forbid this blocking screen that requires user input , "-nothrottle" // forbid throttling to "real" speed of the device , "-update_in_pause" // ^ including frame-advancing , "-rompath", _gameDirectory // mame doesn't load roms from full paths, only from dirs to scan , "-joystick_contradictory" // 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 , "-volume", "-32" // lowest attenuation means mame osd remains silent , "-output", "console" // print everything to hawk console , "-samplerate", _sampleRate.ToString() // match hawk samplerate , "-video", "none" // forbid mame window altogether , "-keyboardprovider", "none" , "-mouseprovider", "none" , "-lightgunprovider", "none" , "-joystickprovider", "none" }; LibMAME.mame_launch(args.Length, args); }
private byte _peek(long addr, int firstOffset, long size) { if (addr < 0 || addr >= size) { throw new ArgumentOutOfRangeException(); } if (!_memAccess) { _memAccess = true; _mamePeriodicComplete.WaitOne(); } addr += firstOffset; var val = (byte)LibMAME.mame_lua_get_int($"{ MAMELuaCommand.GetSpace }:read_u8({ addr << _systemBusAddressShift })"); _memoryAccessComplete.Set(); return(val); }
private byte _peek(long addr, int firstOffset, long size) { if (addr < 0 || addr >= size) { throw new ArgumentOutOfRangeException(); } if (!_memAccess) { _memAccess = true; _mamePeriodicComplete.WaitOne(); } addr += firstOffset; var val = (byte)LibMAME.mame_read_byte((uint)addr << _systemBusAddressShift); _memoryAccessComplete.Set(); return(val); }
public void LoadStateBinary(BinaryReader reader) { int length = reader.ReadInt32(); if (length != _mameSaveBuffer.Length) { throw new InvalidOperationException("Savestate buffer size mismatch!"); } reader.Read(_mameSaveBuffer, 0, _mameSaveBuffer.Length); LibMAME.SaveError err = LibMAME.mame_load_buffer(_mameSaveBuffer, _mameSaveBuffer.Length); if (err != LibMAME.SaveError.NONE) { throw new InvalidOperationException("MAME SAVESTATE ERROR: " + err.ToString()); } Frame = reader.ReadInt32(); LagCount = reader.ReadInt32(); IsLagFrame = reader.ReadBoolean(); }
public void SaveStateBinary(BinaryWriter writer) { writer.Write(_mameSaveBuffer.Length); LibMAME.SaveError 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 LOADSTATE ERROR: " + err.ToString()); } writer.Write(_mameSaveBuffer); writer.Write(Frame); writer.Write(LagCount); writer.Write(IsLagFrame); }
public void SaveStateBinary(BinaryWriter writer) { IntPtr ptr = LibMAME.mame_lua_get_string("return manager:machine():buffer_save()", out var lengthInBytes); if (ptr == IntPtr.Zero) { Console.WriteLine("LibMAME ERROR: audio buffer pointer is null"); return; } Marshal.Copy(ptr, _mameSaveBuffer, 0, lengthInBytes); if (!LibMAME.mame_lua_free_string(ptr)) { Console.WriteLine("LibMAME ERROR: audio buffer wasn't freed"); } writer.Write(_mameSaveBuffer.Length); writer.Write(_mameSaveBuffer); writer.Write(Frame); }
/* * FrameAdvance() and MAME * * MAME fires the periodic callback on every video and debugger update, * which happens every VBlank and also repeatedly at certain time * intervals while paused. Since MAME's luaengine runs in a separate * thread, it's only safe to update everything we need per frame during * this callback, when it's explicitly waiting for further lua commands. * * If we disable throttling and pass -update_in_pause, there will be no * delay between video updates. This allows to run at full speed while * frame-stepping. * * MAME only captures new frame data once per VBlank, while unpaused. * But it doesn't have an exclusive VBlank callback we could attach to. * It has a LUA_ON_FRAME_DONE callback, but that fires even more * frequently and updates all sorts of other non-video stuff, and we * need none of that here. * * So we filter out all the calls that happen while paused (non-VBlank * updates). Then, when Hawk asks us to advance a frame, we virtually * unpause and declare the new frame unfinished. This informs MAME that * it should advance one frame internally. Hawk starts waiting for the * MAME thread to complete the request. * * After MAME's done advancing, it fires the periodic callback again. * That's when we update everything and declare the new frame finished, * filtering out any further updates again. Then we allow Hawk to * complete frame-advancing. */ private void MAMEPeriodicCallback() { if (exiting) { LibMAME.mame_lua_execute(MAMELuaCommand.Exit); exiting = false; } int MAMEFrame = LibMAME.mame_lua_get_int(MAMELuaCommand.GetFrameNumber); if (!paused) { LibMAME.mame_lua_execute(MAMELuaCommand.Step); frameDone = false; paused = true; } else if (!frameDone) { Update(); frameDone = true; MAMEFrameComplete.Set(); } }
private void MAMEPeriodicCallback() { if (_exiting) { LibMAME.mame_lua_execute(MAMELuaCommand.Exit); _exiting = false; } for (; _memAccess;) { _mamePeriodicComplete.Set(); _memoryAccessComplete.WaitOne(); if (!_frameDone && !_paused || _exiting) // FrameAdvance() has been requested { _memAccess = false; return; } } //int MAMEFrame = LibMAME.mame_lua_get_int(MAMELuaCommand.GetFrameNumber); if (!_paused) { SendInput(); LibMAME.mame_lua_execute(MAMELuaCommand.Step); _frameDone = false; _paused = true; } else if (!_frameDone) { UpdateVideo(); _frameDone = true; _mameFrameComplete.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); } } }