2019-10-29 15:37:27 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Runtime.InteropServices;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Diagnostics;
|
2019-12-14 14:55:38 +00:00
|
|
|
|
using System.Dynamic;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
2019-12-14 14:55:38 +00:00
|
|
|
|
using BizHawk.Common;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2020-02-24 21:41:22 +00:00
|
|
|
|
using System.IO;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
|
|
|
|
namespace BizHawk.Emulation.Cores.Arcades.MAME
|
|
|
|
|
{
|
|
|
|
|
[Core(
|
|
|
|
|
name: "MAME",
|
|
|
|
|
author: "MAMEDev",
|
|
|
|
|
isPorted: true,
|
2020-02-18 15:10:24 +00:00
|
|
|
|
isReleased: false,
|
2020-02-24 21:41:22 +00:00
|
|
|
|
portedVersion: "0.218",
|
2019-10-29 15:37:27 +00:00
|
|
|
|
portedUrl: "https://github.com/mamedev/mame.git",
|
|
|
|
|
singleInstance: false)]
|
2020-02-24 21:41:22 +00:00
|
|
|
|
public partial class MAME : IEmulator, IVideoProvider, ISoundProvider, ISettable<object, MAME.SyncSettings>, IStatable
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
2019-12-14 15:04:49 +00:00
|
|
|
|
public MAME(CoreComm comm, string dir, string file, object syncSettings, out string gamename)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
|
|
|
|
ServiceProvider = new BasicServiceProvider(this);
|
|
|
|
|
|
|
|
|
|
CoreComm = comm;
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_gameDirectory = dir;
|
|
|
|
|
_gameFilename = file;
|
|
|
|
|
_mameThread = new Thread(ExecuteMAMEThread);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
|
|
|
|
AsyncLaunchMAME();
|
2019-12-06 18:34:20 +00:00
|
|
|
|
|
2019-12-14 15:04:49 +00:00
|
|
|
|
_syncSettings = (SyncSettings)syncSettings ?? new SyncSettings();
|
|
|
|
|
_syncSettings.ExpandoSettings = new ExpandoObject();
|
|
|
|
|
var dynamicObject = (IDictionary<string, object>)_syncSettings.ExpandoSettings;
|
2019-12-14 14:55:38 +00:00
|
|
|
|
dynamicObject.Add("OKAY", 1);
|
2019-12-14 15:04:49 +00:00
|
|
|
|
gamename = _gameName;
|
2019-12-23 16:11:10 +00:00
|
|
|
|
|
|
|
|
|
if (_loadFailure != "")
|
|
|
|
|
{
|
|
|
|
|
Dispose();
|
2019-12-23 16:41:20 +00:00
|
|
|
|
throw new Exception("\n\n" + _loadFailure);
|
2019-12-23 16:11:10 +00:00
|
|
|
|
}
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-28 21:17:18 +00:00
|
|
|
|
#region Utility
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
/* strings and MAME
|
|
|
|
|
*
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
*/
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-28 21:17:18 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2019-10-29 15:37:27 +00:00
|
|
|
|
#region Properties
|
|
|
|
|
|
2020-02-18 17:11:59 +00:00
|
|
|
|
public CoreComm CoreComm { get; }
|
|
|
|
|
public IEmulatorServiceProvider ServiceProvider { get; }
|
2019-10-29 15:37:27 +00:00
|
|
|
|
public ControllerDefinition ControllerDefinition => MAMEController;
|
|
|
|
|
public string SystemId => "MAME";
|
2019-12-23 17:18:42 +00:00
|
|
|
|
public int[] GetVideoBuffer() => _frameBuffer;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
public bool DeterministicEmulation => true;
|
|
|
|
|
public bool CanProvideAsync => false;
|
|
|
|
|
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
|
|
|
|
public int BackgroundColor => 0;
|
|
|
|
|
public int Frame { get; private set; }
|
|
|
|
|
public int VirtualWidth { get; private set; } = 320;
|
|
|
|
|
public int VirtualHeight { get; private set; } = 240;
|
|
|
|
|
public int BufferWidth { get; private set; } = 320;
|
|
|
|
|
public int BufferHeight { get; private set; } = 240;
|
|
|
|
|
public int VsyncNumerator { get; private set; } = 60;
|
|
|
|
|
public int VsyncDenominator { get; private set; } = 1;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Fields
|
|
|
|
|
|
2019-12-14 15:04:49 +00:00
|
|
|
|
private SyncSettings _syncSettings;
|
2019-12-23 17:18:42 +00:00
|
|
|
|
private Thread _mameThread;
|
|
|
|
|
private ManualResetEvent _mameStartupComplete = new ManualResetEvent(false);
|
|
|
|
|
private ManualResetEvent _mameFrameComplete = new ManualResetEvent(false);
|
|
|
|
|
private AutoResetEvent _mamePeriodicComplete = new AutoResetEvent(false);
|
|
|
|
|
private AutoResetEvent _memoryAccessComplete = new AutoResetEvent(false);
|
|
|
|
|
private SortedDictionary<string, string> _fieldsPorts = new SortedDictionary<string, string>();
|
|
|
|
|
private IController _controller = NullController.Instance;
|
2019-12-22 20:27:33 +00:00
|
|
|
|
private IMemoryDomains _memoryDomains;
|
2020-02-24 21:41:22 +00:00
|
|
|
|
private byte[] _mameSaveBuffer;
|
|
|
|
|
private byte[] _hawkSaveBuffer;
|
2019-12-23 17:18:42 +00:00
|
|
|
|
private int _systemBusAddressShift = 0;
|
|
|
|
|
private bool _memAccess = false;
|
|
|
|
|
private int[] _frameBuffer = new int[0];
|
|
|
|
|
private Queue<short> _audioSamples = new Queue<short>();
|
|
|
|
|
private decimal _dAudioSamples = 0;
|
|
|
|
|
private int _sampleRate = 44100;
|
|
|
|
|
private int _numSamples = 0;
|
|
|
|
|
private bool _paused = true;
|
|
|
|
|
private bool _exiting = false;
|
|
|
|
|
private bool _frameDone = true;
|
|
|
|
|
private string _gameDirectory;
|
|
|
|
|
private string _gameFilename;
|
2019-12-14 15:04:49 +00:00
|
|
|
|
private string _gameName = "Arcade";
|
2019-12-23 16:11:10 +00:00
|
|
|
|
private string _loadFailure = "";
|
2019-12-23 17:18:42 +00:00
|
|
|
|
private LibMAME.PeriodicCallbackDelegate _periodicCallback;
|
|
|
|
|
private LibMAME.SoundCallbackDelegate _soundCallback;
|
|
|
|
|
private LibMAME.BootCallbackDelegate _bootCallback;
|
|
|
|
|
private LibMAME.LogCallbackDelegate _logCallback;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region IEmulator
|
|
|
|
|
|
2019-12-14 15:04:49 +00:00
|
|
|
|
public bool FrameAdvance(IController controller, bool render, bool renderSound = true)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
if (_exiting)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_controller = controller;
|
|
|
|
|
_paused = false;
|
|
|
|
|
_frameDone = false;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
for (; _frameDone == false;)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_mameFrameComplete.WaitOne();
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Frame++;
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void ResetCounters()
|
|
|
|
|
{
|
|
|
|
|
Frame = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_exiting = true;
|
|
|
|
|
_mameThread.Join();
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2020-02-24 21:41:22 +00:00
|
|
|
|
#region IStatable
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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 })");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public byte[] SaveStateBinary()
|
|
|
|
|
{
|
|
|
|
|
MemoryStream ms = new MemoryStream(_hawkSaveBuffer);
|
|
|
|
|
BinaryWriter bw = new BinaryWriter(ms);
|
|
|
|
|
SaveStateBinary(bw);
|
|
|
|
|
bw.Flush();
|
|
|
|
|
|
|
|
|
|
if (ms.Position != _hawkSaveBuffer.Length)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ms.Close();
|
|
|
|
|
return _hawkSaveBuffer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2019-12-14 14:55:38 +00:00
|
|
|
|
#region ISettable
|
|
|
|
|
|
|
|
|
|
public object GetSettings() => null;
|
|
|
|
|
public bool PutSettings(object o) => false;
|
|
|
|
|
|
|
|
|
|
public SyncSettings GetSyncSettings()
|
|
|
|
|
{
|
2019-12-14 15:04:49 +00:00
|
|
|
|
return _syncSettings.Clone();
|
2019-12-14 14:55:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool PutSyncSettings(SyncSettings o)
|
|
|
|
|
{
|
2019-12-14 15:04:49 +00:00
|
|
|
|
bool ret = SyncSettings.NeedsReboot(o, _syncSettings);
|
|
|
|
|
_syncSettings = o;
|
2019-12-14 14:55:38 +00:00
|
|
|
|
return ret;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class SyncSettings
|
|
|
|
|
{
|
|
|
|
|
public static bool NeedsReboot(SyncSettings x, SyncSettings y)
|
|
|
|
|
{
|
|
|
|
|
return !DeepEquality.DeepEquals(x, y);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public SyncSettings Clone()
|
|
|
|
|
{
|
|
|
|
|
return (SyncSettings)MemberwiseClone();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ExpandoObject ExpandoSettings { get; set; }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2019-10-29 15:37:27 +00:00
|
|
|
|
#region ISoundProvider
|
|
|
|
|
|
|
|
|
|
public void SetSyncMode(SyncSoundMode mode)
|
2019-11-04 01:55:38 +00:00
|
|
|
|
{
|
|
|
|
|
if (mode == SyncSoundMode.Async)
|
|
|
|
|
{
|
|
|
|
|
throw new NotSupportedException("Async mode is not supported.");
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-06 18:34:20 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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 flashed 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 with a little quirk.
|
|
|
|
|
* Since sample count per frame may not align with frame duration, I
|
|
|
|
|
* subtract the entire decimal fraction of "required" samples from total
|
|
|
|
|
* samples. I check if the fractional reminder of total samples is > 0.5
|
|
|
|
|
* by rounding it. I invert it to see what number I should add to the
|
|
|
|
|
* integer representation of "required" samples, to compensate for
|
|
|
|
|
* misalignment between fractional and integral "required" samples.
|
|
|
|
|
*
|
|
|
|
|
* TODO: Figure out how MAME does this and maybe use their method instead.
|
|
|
|
|
*/
|
2019-10-29 15:37:27 +00:00
|
|
|
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
2019-11-04 01:55:38 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
decimal dSamplesPerFrame = (decimal)_sampleRate * VsyncDenominator / VsyncNumerator;
|
2019-12-06 18:34:20 +00:00
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
if (_audioSamples.Any())
|
2019-12-06 18:34:20 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_dAudioSamples -= dSamplesPerFrame;
|
|
|
|
|
int remainder = (int)Math.Round(_dAudioSamples - Math.Truncate(_dAudioSamples)) ^ 1;
|
2019-12-06 18:34:20 +00:00
|
|
|
|
nsamp = (int)Math.Round(dSamplesPerFrame) + remainder;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
nsamp = (int)Math.Round(dSamplesPerFrame);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
samples = new short[nsamp * 2];
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
2019-12-06 18:34:20 +00:00
|
|
|
|
for (int i = 0; i < nsamp * 2; i++)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
if (_audioSamples.Any())
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
samples[i] = _audioSamples.Dequeue();
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
samples[i] = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void GetSamplesAsync(short[] samples)
|
2019-11-04 01:55:38 +00:00
|
|
|
|
{
|
2019-10-29 15:37:27 +00:00
|
|
|
|
throw new InvalidOperationException("Async mode is not supported.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_audioSamples.Clear();
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2019-12-22 20:27:33 +00:00
|
|
|
|
#region IMemoryDomains
|
|
|
|
|
|
|
|
|
|
private void InitMemoryDomains()
|
|
|
|
|
{
|
|
|
|
|
var domains = new List<MemoryDomain>();
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_systemBusAddressShift = LibMAME.mame_lua_get_int(MAMELuaCommand.GetSpaceAddressShift);
|
2019-12-22 20:27:33 +00:00
|
|
|
|
var size = (long)LibMAME.mame_lua_get_double(MAMELuaCommand.GetSpaceAddressMask) + 1;
|
2019-12-23 17:18:42 +00:00
|
|
|
|
var dataWidth = LibMAME.mame_lua_get_int(MAMELuaCommand.GetSpaceDataWidth) >> 3; // mame returns in bits
|
|
|
|
|
var endianString = MameGetString(MAMELuaCommand.GetSpaceEndianness);
|
2019-12-22 20:27:33 +00:00
|
|
|
|
|
|
|
|
|
MemoryDomain.Endian endian = MemoryDomain.Endian.Unknown;
|
|
|
|
|
|
|
|
|
|
if (endianString == "little")
|
|
|
|
|
{
|
|
|
|
|
endian = MemoryDomain.Endian.Little;
|
|
|
|
|
}
|
|
|
|
|
else if (endianString == "big")
|
|
|
|
|
{
|
|
|
|
|
endian = MemoryDomain.Endian.Big;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-28 12:20:51 +00:00
|
|
|
|
var mapCount = LibMAME.mame_lua_get_int(MAMELuaCommand.GetSpaceMapCount);
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i <= mapCount; i++)
|
|
|
|
|
{
|
2019-12-28 21:13:31 +00:00
|
|
|
|
var read = MameGetString($"return { MAMELuaCommand.SpaceMap }[{ i }].readtype");
|
|
|
|
|
var write = MameGetString($"return { MAMELuaCommand.SpaceMap }[{ i }].writetype");
|
2019-12-28 12:20:51 +00:00
|
|
|
|
|
2019-12-28 21:13:31 +00:00
|
|
|
|
if (read == "ram" && write == "ram" /* || read == "rom" */)
|
2019-12-28 12:20:51 +00:00
|
|
|
|
{
|
|
|
|
|
var firstOffset = LibMAME.mame_lua_get_int($"return { MAMELuaCommand.SpaceMap }[{ i }].offset");
|
|
|
|
|
var lastOffset = LibMAME.mame_lua_get_int($"return { MAMELuaCommand.SpaceMap }[{ i }].endoff");
|
2019-12-28 21:13:31 +00:00
|
|
|
|
var name = $"{ read.ToUpper() } { firstOffset:X}-{ lastOffset:X}";
|
2019-12-28 12:20:51 +00:00
|
|
|
|
|
|
|
|
|
domains.Add(new MemoryDomainDelegate(name, lastOffset - firstOffset + 1, endian,
|
|
|
|
|
delegate (long addr)
|
|
|
|
|
{
|
|
|
|
|
if (addr < 0 || addr >= size)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_memAccess = true;
|
|
|
|
|
_mamePeriodicComplete.WaitOne();
|
|
|
|
|
addr += firstOffset;
|
|
|
|
|
var val = (byte)LibMAME.mame_lua_get_int($"{ MAMELuaCommand.GetSpace }:read_u8({ addr << _systemBusAddressShift })");
|
|
|
|
|
_memoryAccessComplete.Set();
|
|
|
|
|
_memAccess = false;
|
|
|
|
|
return val;
|
|
|
|
|
},
|
2019-12-28 21:13:31 +00:00
|
|
|
|
read == "rom" ? (Action<long, byte>)null : delegate (long addr, byte val)
|
2019-12-28 12:20:51 +00:00
|
|
|
|
{
|
|
|
|
|
if (addr < 0 || addr >= size)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_memAccess = true;
|
|
|
|
|
_mamePeriodicComplete.WaitOne();
|
|
|
|
|
addr += firstOffset;
|
|
|
|
|
LibMAME.mame_lua_execute($"{ MAMELuaCommand.GetSpace }:write_u8({ addr << _systemBusAddressShift }, { val })");
|
|
|
|
|
_memoryAccessComplete.Set();
|
|
|
|
|
_memAccess = false;
|
|
|
|
|
}, dataWidth));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-22 20:27:33 +00:00
|
|
|
|
domains.Add(new MemoryDomainDelegate("System Bus", size, endian,
|
|
|
|
|
delegate (long addr)
|
|
|
|
|
{
|
|
|
|
|
if (addr < 0 || addr >= size)
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentOutOfRangeException();
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_memAccess = true;
|
|
|
|
|
_mamePeriodicComplete.WaitOne();
|
2019-12-28 12:20:51 +00:00
|
|
|
|
var val = (byte)LibMAME.mame_lua_get_int($"{ MAMELuaCommand.GetSpace }:read_u8({ addr << _systemBusAddressShift })");
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_memoryAccessComplete.Set();
|
|
|
|
|
_memAccess = false;
|
2019-12-22 20:27:33 +00:00
|
|
|
|
return val;
|
|
|
|
|
},
|
2019-12-28 12:20:51 +00:00
|
|
|
|
null, dataWidth));
|
2019-12-22 20:27:33 +00:00
|
|
|
|
|
|
|
|
|
_memoryDomains = new MemoryDomainList(domains);
|
|
|
|
|
(ServiceProvider as BasicServiceProvider).Register<IMemoryDomains>(_memoryDomains);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
2019-10-29 15:37:27 +00:00
|
|
|
|
#region Launchers
|
|
|
|
|
|
|
|
|
|
private void AsyncLaunchMAME()
|
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_mameThread.Start();
|
|
|
|
|
_mameStartupComplete.WaitOne();
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void ExecuteMAMEThread()
|
|
|
|
|
{
|
|
|
|
|
// dodge GC
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_periodicCallback = MAMEPeriodicCallback;
|
|
|
|
|
_soundCallback = MAMESoundCallback;
|
|
|
|
|
_bootCallback = MAMEBootCallback;
|
|
|
|
|
_logCallback = MAMELogCallback;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
LibMAME.mame_set_periodic_callback(_periodicCallback);
|
|
|
|
|
LibMAME.mame_set_sound_callback(_soundCallback);
|
|
|
|
|
LibMAME.mame_set_boot_callback(_bootCallback);
|
|
|
|
|
LibMAME.mame_set_log_callback(_logCallback);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
|
|
|
|
// https://docs.mamedev.org/commandline/commandline-index.html
|
2019-12-14 15:04:49 +00:00
|
|
|
|
string[] args =
|
|
|
|
|
{
|
2019-12-28 12:20:51 +00:00
|
|
|
|
"mame" // dummy, internally discarded by index, so has to go first
|
2019-12-23 17:18:42 +00:00
|
|
|
|
, _gameFilename // no dash for rom names
|
2019-12-28 12:20:51 +00:00
|
|
|
|
, "-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
|
2019-12-23 17:18:42 +00:00
|
|
|
|
, "-rompath", _gameDirectory // mame doesn't load roms from full paths, only from dirs to scan
|
2019-12-28 12:20:51 +00:00
|
|
|
|
, "-volume", "-32" // lowest attenuation means mame osd remains silent
|
|
|
|
|
, "-output", "console" // print everything to hawk console
|
2019-12-23 17:18:42 +00:00
|
|
|
|
, "-samplerate", _sampleRate.ToString() // match hawk samplerate
|
2019-12-28 12:20:51 +00:00
|
|
|
|
, "-video", "none" // forbid mame window altogether
|
2019-10-29 15:37:27 +00:00
|
|
|
|
, "-keyboardprovider", "none"
|
|
|
|
|
, "-mouseprovider", "none"
|
|
|
|
|
, "-lightgunprovider", "none"
|
|
|
|
|
, "-joystickprovider", "none"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
LibMAME.mame_launch(args.Length, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Updaters
|
|
|
|
|
|
|
|
|
|
private void UpdateFramerate()
|
|
|
|
|
{
|
|
|
|
|
VsyncNumerator = 1000000000;
|
2019-12-14 15:04:49 +00:00
|
|
|
|
long refresh = (long)LibMAME.mame_lua_get_double(MAMELuaCommand.GetRefresh);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
VsyncDenominator = (int)(refresh / 1000000000);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateAspect()
|
|
|
|
|
{
|
|
|
|
|
int x = (int)LibMAME.mame_lua_get_double(MAMELuaCommand.GetBoundX);
|
|
|
|
|
int y = (int)LibMAME.mame_lua_get_double(MAMELuaCommand.GetBoundY);
|
|
|
|
|
VirtualHeight = BufferWidth > BufferHeight * x / y
|
|
|
|
|
? BufferWidth * y / x
|
|
|
|
|
: BufferHeight;
|
|
|
|
|
VirtualWidth = VirtualHeight * x / y;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateVideo()
|
|
|
|
|
{
|
|
|
|
|
BufferWidth = LibMAME.mame_lua_get_int(MAMELuaCommand.GetWidth);
|
|
|
|
|
BufferHeight = LibMAME.mame_lua_get_int(MAMELuaCommand.GetHeight);
|
|
|
|
|
int expectedSize = BufferWidth * BufferHeight;
|
|
|
|
|
int bytesPerPixel = 4;
|
2019-12-14 15:04:49 +00:00
|
|
|
|
IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetPixels, out var lengthInBytes);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_frameBuffer = new int[expectedSize];
|
|
|
|
|
Marshal.Copy(ptr, _frameBuffer, 0, expectedSize);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
|
|
|
|
if (!LibMAME.mame_lua_free_string(ptr))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("LibMAME ERROR: frame buffer wasn't freed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void UpdateInput()
|
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
foreach (var fieldPort in _fieldsPorts)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
|
|
|
|
LibMAME.mame_lua_execute(
|
|
|
|
|
"manager:machine():ioport()" +
|
|
|
|
|
$".ports [\"{ fieldPort.Value }\"]" +
|
|
|
|
|
$".fields [\"{ fieldPort.Key }\"]" +
|
2019-12-23 17:18:42 +00:00
|
|
|
|
$":set_value({ (_controller.IsPressed(fieldPort.Key) ? 1 : 0) })");
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void Update()
|
|
|
|
|
{
|
|
|
|
|
UpdateFramerate();
|
|
|
|
|
UpdateVideo();
|
|
|
|
|
UpdateAspect();
|
|
|
|
|
UpdateInput();
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
private void UpdateGameName()
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_gameName = MameGetString(MAMELuaCommand.GetGameName);
|
|
|
|
|
}
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
private void CheckVersions()
|
|
|
|
|
{
|
|
|
|
|
var mameVersion = MameGetString(MAMELuaCommand.GetVersion);
|
|
|
|
|
var version = this.Attributes().PortedVersion;
|
2019-12-14 15:04:49 +00:00
|
|
|
|
Debug.Assert(version == mameVersion,
|
2019-10-29 15:37:27 +00:00
|
|
|
|
"MAME versions desync!\n\n" +
|
2019-12-14 15:04:49 +00:00
|
|
|
|
$"MAME is { mameVersion }\n" +
|
2019-10-29 15:37:27 +00:00
|
|
|
|
$"MAMEHawk is { version }");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Callbacks
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* FrameAdvance() and MAME
|
|
|
|
|
*
|
|
|
|
|
* MAME fires the periodic callback on every video and debugger update,
|
|
|
|
|
* which happens every VBlank and also repeatedly at certain time
|
|
|
|
|
* intervals while paused. Since MAME's luaengine runs in a separate
|
|
|
|
|
* thread, it's only safe to update everything we need per frame during
|
|
|
|
|
* this callback, when it's explicitly waiting for further lua commands.
|
|
|
|
|
*
|
|
|
|
|
* If we disable throttling and pass -update_in_pause, there will be no
|
|
|
|
|
* delay between video updates. This allows to run at full speed while
|
|
|
|
|
* frame-stepping.
|
|
|
|
|
*
|
|
|
|
|
* MAME only captures new frame data once per VBlank, while unpaused.
|
|
|
|
|
* But it doesn't have an exclusive VBlank callback we could attach to.
|
|
|
|
|
* It has a LUA_ON_FRAME_DONE callback, but that fires even more
|
|
|
|
|
* frequently and updates all sorts of other non-video stuff, and we
|
|
|
|
|
* need none of that here.
|
|
|
|
|
*
|
|
|
|
|
* So we filter out all the calls that happen while paused (non-VBlank
|
|
|
|
|
* updates). Then, when Hawk asks us to advance a frame, we virtually
|
|
|
|
|
* unpause and declare the new frame unfinished. This informs MAME that
|
|
|
|
|
* it should advance one frame internally. Hawk starts waiting for the
|
|
|
|
|
* MAME thread to complete the request.
|
|
|
|
|
*
|
|
|
|
|
* After MAME's done advancing, it fires the periodic callback again.
|
|
|
|
|
* That's when we update everything and declare the new frame finished,
|
|
|
|
|
* filtering out any further updates again. Then we allow Hawk to
|
|
|
|
|
* complete frame-advancing.
|
|
|
|
|
*/
|
|
|
|
|
private void MAMEPeriodicCallback()
|
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
if (_exiting)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
|
|
|
|
LibMAME.mame_lua_execute(MAMELuaCommand.Exit);
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_exiting = false;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
2019-12-22 20:27:33 +00:00
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
if (_memAccess)
|
2019-12-22 20:27:33 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_mamePeriodicComplete.Set();
|
|
|
|
|
_memoryAccessComplete.WaitOne();
|
2019-12-22 20:27:33 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
2019-12-22 20:27:33 +00:00
|
|
|
|
//int MAMEFrame = LibMAME.mame_lua_get_int(MAMELuaCommand.GetFrameNumber);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
if (!_paused)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
|
|
|
|
LibMAME.mame_lua_execute(MAMELuaCommand.Step);
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_frameDone = false;
|
|
|
|
|
_paused = true;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
2019-12-23 17:18:42 +00:00
|
|
|
|
else if (!_frameDone)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
2020-02-24 20:18:46 +00:00
|
|
|
|
/*
|
2020-02-02 15:31:39 +00:00
|
|
|
|
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");
|
|
|
|
|
}
|
2020-02-24 20:18:46 +00:00
|
|
|
|
*/
|
2020-02-02 15:31:39 +00:00
|
|
|
|
|
2019-10-29 15:37:27 +00:00
|
|
|
|
Update();
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_frameDone = true;
|
|
|
|
|
_mameFrameComplete.Set();
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void MAMESoundCallback()
|
|
|
|
|
{
|
|
|
|
|
int bytesPerSample = 2;
|
2019-12-14 15:04:49 +00:00
|
|
|
|
IntPtr ptr = LibMAME.mame_lua_get_string(MAMELuaCommand.GetSamples, out var lengthInBytes);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
|
|
|
|
if (ptr == IntPtr.Zero)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("LibMAME ERROR: audio buffer pointer is null");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_numSamples = lengthInBytes / bytesPerSample;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
|
|
|
|
|
unsafe
|
|
|
|
|
{
|
|
|
|
|
short* pSample = (short*)ptr.ToPointer();
|
2019-12-23 17:18:42 +00:00
|
|
|
|
for (int i = 0; i < _numSamples; i++)
|
2019-10-29 15:37:27 +00:00
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_audioSamples.Enqueue(*(pSample + i));
|
|
|
|
|
_dAudioSamples++;
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
2019-12-14 14:55:38 +00:00
|
|
|
|
|
2019-10-29 15:37:27 +00:00
|
|
|
|
CheckVersions();
|
|
|
|
|
GetInputFields();
|
|
|
|
|
Update();
|
2019-12-06 18:34:20 +00:00
|
|
|
|
UpdateGameName();
|
2019-12-22 20:27:33 +00:00
|
|
|
|
InitMemoryDomains();
|
2019-12-14 14:55:38 +00:00
|
|
|
|
|
2020-02-24 21:41:22 +00:00
|
|
|
|
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];
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_mameStartupComplete.Set();
|
2019-10-29 15:37:27 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void MAMELogCallback(LibMAME.OutputChannel channel, int size, string data)
|
|
|
|
|
{
|
2019-12-22 20:27:33 +00:00
|
|
|
|
if (data.Contains("NOT FOUND"))
|
|
|
|
|
{
|
2019-12-23 16:11:10 +00:00
|
|
|
|
_loadFailure = data;
|
2019-12-22 20:27:33 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-12-23 16:41:20 +00:00
|
|
|
|
if (data.Contains("Fatal error"))
|
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_mameStartupComplete.Set();
|
2019-12-23 16:41:20 +00:00
|
|
|
|
_loadFailure += data;
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-22 20:27:33 +00:00
|
|
|
|
// mame sends osd_output_channel casted to int, we implicitly cast it back
|
2019-10-29 15:37:27 +00:00
|
|
|
|
if (!data.Contains("pause = "))
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(
|
|
|
|
|
$"[MAME { channel.ToString() }] " +
|
|
|
|
|
$"{ data.Replace('\n', ' ') }");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Input
|
|
|
|
|
|
|
|
|
|
public static ControllerDefinition MAMEController = new ControllerDefinition
|
|
|
|
|
{
|
|
|
|
|
Name = "MAME Controller",
|
|
|
|
|
BoolButtons = new List<string>()
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
private void GetInputFields()
|
|
|
|
|
{
|
2019-12-23 17:18:42 +00:00
|
|
|
|
string inputFields = MameGetString(MAMELuaCommand.GetInputFields);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
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();
|
|
|
|
|
|
2019-12-23 17:18:42 +00:00
|
|
|
|
_fieldsPorts.Add(field, tag);
|
2019-10-29 15:37:27 +00:00
|
|
|
|
MAMEController.BoolButtons.Add(field);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Lua Commands
|
|
|
|
|
|
|
|
|
|
private class MAMELuaCommand
|
|
|
|
|
{
|
2019-12-28 21:21:27 +00:00
|
|
|
|
// commands
|
2019-10-29 15:37:27 +00:00
|
|
|
|
public const string Step = "emu.step()";
|
|
|
|
|
public const string Pause = "emu.pause()";
|
|
|
|
|
public const string Unpause = "emu.unpause()";
|
|
|
|
|
public const string Exit = "manager:machine():exit()";
|
2019-12-28 12:20:51 +00:00
|
|
|
|
|
2019-12-28 21:21:27 +00:00
|
|
|
|
// getters
|
2019-10-29 15:37:27 +00:00
|
|
|
|
public const string GetVersion = "return emu.app_version()";
|
2019-12-06 18:34:20 +00:00
|
|
|
|
public const string GetGameName = "return manager:machine():system().description";
|
2019-10-29 15:37:27 +00:00
|
|
|
|
public const string GetPixels = "return manager:machine():video():pixels()";
|
|
|
|
|
public const string GetSamples = "return manager:machine():sound():samples()";
|
|
|
|
|
public const string GetFrameNumber = "return select(2, next(manager:machine().screens)):frame_number()";
|
|
|
|
|
public const string GetRefresh = "return select(2, next(manager:machine().screens)):refresh_attoseconds()";
|
|
|
|
|
public const string GetWidth = "return (select(1, manager:machine():video():size()))";
|
|
|
|
|
public const string GetHeight = "return (select(2, manager:machine():video():size()))";
|
2019-12-28 12:20:51 +00:00
|
|
|
|
|
2019-12-28 21:21:27 +00:00
|
|
|
|
// memory space
|
2019-12-22 20:27:33 +00:00
|
|
|
|
public const string GetSpace = "return manager:machine().devices[\":maincpu\"].spaces[\"program\"]";
|
2019-12-28 12:20:51 +00:00
|
|
|
|
public const string GetSpaceMapCount = "return #manager:machine().devices[\":maincpu\"].spaces[\"program\"].map";
|
|
|
|
|
public const string SpaceMap = "manager:machine().devices[\":maincpu\"].spaces[\"program\"].map";
|
2019-12-22 20:27:33 +00:00
|
|
|
|
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";
|
2020-02-02 15:31:39 +00:00
|
|
|
|
public const string GetSpaceBuffer =
|
|
|
|
|
"local space = manager:machine().devices[\":maincpu\"].spaces[\"program\"]" +
|
|
|
|
|
"local address_shift = space.shift " +
|
|
|
|
|
"local data_width = space.data_width " +
|
|
|
|
|
"local bit_step " +
|
|
|
|
|
"if address_shift == 0 then bit_step = data_width " +
|
|
|
|
|
"elseif address_shift > 0 then bit_step = data_width << address_shift " +
|
|
|
|
|
"elseif address_shift< 0 then bit_step = 8 " +
|
|
|
|
|
"end " +
|
|
|
|
|
"return space:read_range(0, 0xfffffff, space.data_width, math.floor(bit_step / 8))";
|
2019-12-28 12:20:51 +00:00
|
|
|
|
|
2019-12-28 21:21:27 +00:00
|
|
|
|
// complex stuff
|
2019-10-29 15:37:27 +00:00
|
|
|
|
public const string GetBoundX =
|
|
|
|
|
"local x0,x1,y0,y1 = manager:machine():render():ui_target():view_bounds() " +
|
|
|
|
|
"return x1-x0";
|
|
|
|
|
public const string GetBoundY =
|
|
|
|
|
"local x0,x1,y0,y1 = manager:machine():render():ui_target():view_bounds() " +
|
|
|
|
|
"return y1-y0";
|
|
|
|
|
public const string GetInputFields =
|
|
|
|
|
"final = {} " +
|
|
|
|
|
"for tag, _ in pairs(manager:machine():ioport().ports) do " +
|
|
|
|
|
"for name, field in pairs(manager:machine():ioport().ports[tag].fields) do " +
|
|
|
|
|
"if field.type_class ~= \"dipswitch\" then " +
|
|
|
|
|
"table.insert(final, string.format(\"%s,%s;\", tag, name)) " +
|
|
|
|
|
"end " +
|
|
|
|
|
"end " +
|
|
|
|
|
"end " +
|
|
|
|
|
"table.sort(final) " +
|
|
|
|
|
"return table.concat(final)";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|