387 lines
10 KiB
C#
387 lines
10 KiB
C#
using BizHawk.Common;
|
|
using BizHawk.Common.BizInvoke;
|
|
using BizHawk.Common.BufferExtensions;
|
|
using BizHawk.Emulation.Common;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace BizHawk.Emulation.Cores.Waterbox
|
|
{
|
|
public abstract class WaterboxCore : IEmulator, IVideoProvider, ISoundProvider, IStatable,
|
|
IInputPollable, ISaveRam
|
|
{
|
|
private LibWaterboxCore _core;
|
|
protected PeRunner _exe;
|
|
protected LibWaterboxCore.MemoryArea[] _memoryAreas;
|
|
private LibWaterboxCore.EmptyCallback _inputCallback;
|
|
|
|
public class Configuration
|
|
{
|
|
public int MaxWidth;
|
|
public int MaxHeight;
|
|
public int DefaultWidth;
|
|
public int DefaultHeight;
|
|
public int DefaultFpsNumerator;
|
|
public int DefaultFpsDenominator;
|
|
public int MaxSamples;
|
|
public string SystemId;
|
|
}
|
|
|
|
protected WaterboxCore(CoreComm comm, Configuration c)
|
|
{
|
|
BufferWidth = c.DefaultWidth;
|
|
BufferHeight = c.DefaultHeight;
|
|
_videoBuffer = new int[c.MaxWidth * c.MaxHeight];
|
|
_soundBuffer = new short[c.MaxSamples * 2];
|
|
VsyncNumerator = c.DefaultFpsNumerator;
|
|
VsyncDenominator = c.DefaultFpsDenominator;
|
|
_serviceProvider = new BasicServiceProvider(this);
|
|
SystemId = c.SystemId;
|
|
CoreComm = comm;
|
|
_inputCallback = InputCallbacks.Call;
|
|
}
|
|
|
|
protected T PreInit<T>(PeRunnerOptions options)
|
|
where T : LibWaterboxCore
|
|
{
|
|
if (options.Path == null)
|
|
options.Path = CoreComm.CoreFileProvider.DllPath();
|
|
_exe = new PeRunner(options);
|
|
using (_exe.EnterExit())
|
|
{
|
|
var ret = BizInvoker.GetInvoker<T>(_exe, _exe, CallingConventionAdapters.Waterbox);
|
|
_core = ret;
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
protected void PostInit()
|
|
{
|
|
using (_exe.EnterExit())
|
|
{
|
|
var areas = new LibWaterboxCore.MemoryArea[256];
|
|
_core.GetMemoryAreas(areas);
|
|
_memoryAreas = areas.Where(a => a.Data != IntPtr.Zero && a.Size != 0)
|
|
.ToArray();
|
|
_saveramAreas = _memoryAreas.Where(a => (a.Flags & LibWaterboxCore.MemoryDomainFlags.Saverammable) != 0)
|
|
.ToArray();
|
|
_saveramSize = (int)_saveramAreas.Sum(a => a.Size);
|
|
|
|
var memoryDomains = _memoryAreas.Select(a => new LibWaterboxCore.WaterboxMemoryDomain(a, _exe));
|
|
var primaryIndex = _memoryAreas
|
|
.Select((a, i) => new { a, i })
|
|
.Single(a => (a.a.Flags & LibWaterboxCore.MemoryDomainFlags.Primary) != 0).i;
|
|
var mdl = new MemoryDomainList(memoryDomains.Cast<MemoryDomain>().ToList());
|
|
mdl.MainMemory = mdl[primaryIndex];
|
|
_serviceProvider.Register<IMemoryDomains>(mdl);
|
|
|
|
var sr = _core as ICustomSaveram;
|
|
if (sr != null)
|
|
_serviceProvider.Register<ISaveRam>(new CustomSaverammer(sr)); // override the default implementation
|
|
|
|
_exe.Seal();
|
|
}
|
|
}
|
|
|
|
#region RTC
|
|
|
|
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0);
|
|
private long _clockTime;
|
|
private int _clockRemainder;
|
|
|
|
protected void InitializeRtc(DateTime start)
|
|
{
|
|
_clockTime = (long)(start - Epoch).TotalSeconds;
|
|
}
|
|
|
|
protected long GetRtcTime(bool realTime)
|
|
{
|
|
if (realTime && DeterministicEmulation)
|
|
throw new InvalidOperationException();
|
|
return realTime ? (long)(DateTime.Now - Epoch).TotalSeconds : _clockTime;
|
|
}
|
|
|
|
private void AdvanceRtc()
|
|
{
|
|
_clockRemainder += VsyncDenominator;
|
|
if (_clockRemainder >= VsyncNumerator)
|
|
{
|
|
_clockRemainder -= VsyncNumerator;
|
|
_clockTime++;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ISaveRam
|
|
|
|
private LibWaterboxCore.MemoryArea[] _saveramAreas;
|
|
private int _saveramSize;
|
|
|
|
public unsafe bool SaveRamModified
|
|
{
|
|
get
|
|
{
|
|
if (_saveramSize == 0)
|
|
return false;
|
|
using (_exe.EnterExit())
|
|
{
|
|
foreach (var area in _saveramAreas)
|
|
{
|
|
int* p = (int*)area.Data;
|
|
int* pend = p + area.Size / sizeof(int);
|
|
int cmp = (area.Flags & LibWaterboxCore.MemoryDomainFlags.OneFilled) != 0 ? -1 : 0;
|
|
|
|
while (p < pend)
|
|
{
|
|
if (*p++ != cmp)
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public byte[] CloneSaveRam()
|
|
{
|
|
if (_saveramSize == 0)
|
|
return null;
|
|
using (_exe.EnterExit())
|
|
{
|
|
var ret = new byte[_saveramSize];
|
|
var offs = 0;
|
|
foreach (var area in _saveramAreas)
|
|
{
|
|
Marshal.Copy(area.Data, ret, offs, (int)area.Size);
|
|
offs += (int)area.Size;
|
|
}
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
public void StoreSaveRam(byte[] data)
|
|
{
|
|
using (_exe.EnterExit())
|
|
{
|
|
if (data.Length != _saveramSize)
|
|
throw new InvalidOperationException("Saveram size mismatch");
|
|
using (_exe.EnterExit())
|
|
{
|
|
var offs = 0;
|
|
foreach (var area in _saveramAreas)
|
|
{
|
|
Marshal.Copy(data, offs, area.Data, (int)area.Size);
|
|
offs += (int)area.Size;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion ISaveRam
|
|
|
|
#region IEmulator
|
|
|
|
protected abstract LibWaterboxCore.FrameInfo FrameAdvancePrep(IController controller, bool render, bool rendersound);
|
|
protected virtual void FrameAdvancePost()
|
|
{ }
|
|
|
|
public unsafe void FrameAdvance(IController controller, bool render, bool rendersound = true)
|
|
{
|
|
using (_exe.EnterExit())
|
|
{
|
|
_core.SetInputCallback(InputCallbacks.Count > 0 ? _inputCallback : null);
|
|
|
|
fixed (int* vp = _videoBuffer)
|
|
fixed (short* sp = _soundBuffer)
|
|
{
|
|
var frame = FrameAdvancePrep(controller, render, rendersound);
|
|
frame.VideoBuffer = (IntPtr)vp;
|
|
frame.SoundBuffer = (IntPtr)sp;
|
|
|
|
_core.FrameAdvance(frame);
|
|
|
|
Frame++;
|
|
if (IsLagFrame = frame.Lagged != 0)
|
|
LagCount++;
|
|
AdvanceRtc();
|
|
|
|
BufferWidth = frame.Width;
|
|
BufferHeight = frame.Height;
|
|
_numSamples = frame.Samples;
|
|
|
|
FrameAdvancePost();
|
|
}
|
|
}
|
|
}
|
|
|
|
private bool _disposed = false;
|
|
|
|
public virtual void Dispose()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
_exe.Dispose();
|
|
_disposed = true;
|
|
}
|
|
}
|
|
|
|
public CoreComm CoreComm { get; }
|
|
public int Frame { get; private set; }
|
|
public int LagCount { get; set; }
|
|
public bool IsLagFrame { get; set; }
|
|
|
|
public void ResetCounters()
|
|
{
|
|
Frame = 0;
|
|
}
|
|
|
|
protected readonly BasicServiceProvider _serviceProvider;
|
|
public IEmulatorServiceProvider ServiceProvider => _serviceProvider;
|
|
public virtual string SystemId { get; }
|
|
public bool DeterministicEmulation { get; protected set; } = true;
|
|
public IInputCallbackSystem InputCallbacks { get; } = new InputCallbackSystem();
|
|
public virtual ControllerDefinition ControllerDefinition { get; protected set; } = NullController.Instance.Definition;
|
|
|
|
#endregion
|
|
|
|
#region IStatable
|
|
|
|
public bool BinarySaveStatesPreferred => true;
|
|
|
|
public void SaveStateText(TextWriter writer)
|
|
{
|
|
var temp = SaveStateBinary();
|
|
temp.SaveAsHexFast(writer);
|
|
}
|
|
|
|
public void LoadStateText(TextReader reader)
|
|
{
|
|
string hex = reader.ReadLine();
|
|
byte[] state = new byte[hex.Length / 2];
|
|
state.ReadFromHexFast(hex);
|
|
LoadStateBinary(new BinaryReader(new MemoryStream(state)));
|
|
}
|
|
|
|
public void LoadStateBinary(BinaryReader reader)
|
|
{
|
|
_exe.LoadStateBinary(reader);
|
|
// other variables
|
|
Frame = reader.ReadInt32();
|
|
LagCount = reader.ReadInt32();
|
|
IsLagFrame = reader.ReadBoolean();
|
|
BufferWidth = reader.ReadInt32();
|
|
BufferHeight = reader.ReadInt32();
|
|
_clockTime = reader.ReadInt64();
|
|
_clockRemainder = reader.ReadInt32();
|
|
// reset pointers here!
|
|
_core.SetInputCallback(null);
|
|
//_exe.PrintDebuggingInfo();
|
|
LoadStateBinaryInternal(reader);
|
|
}
|
|
|
|
public void SaveStateBinary(BinaryWriter writer)
|
|
{
|
|
_exe.SaveStateBinary(writer);
|
|
// other variables
|
|
writer.Write(Frame);
|
|
writer.Write(LagCount);
|
|
writer.Write(IsLagFrame);
|
|
writer.Write(BufferWidth);
|
|
writer.Write(BufferHeight);
|
|
writer.Write(_clockTime);
|
|
writer.Write(_clockRemainder);
|
|
SaveStateBinaryInternal(writer);
|
|
}
|
|
|
|
public byte[] SaveStateBinary()
|
|
{
|
|
var ms = new MemoryStream();
|
|
var bw = new BinaryWriter(ms);
|
|
SaveStateBinary(bw);
|
|
bw.Flush();
|
|
ms.Close();
|
|
return ms.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// called after the base core saves state. the core must save any other
|
|
/// variables that it needs to.
|
|
/// the default implementation does nothing
|
|
/// </summary>
|
|
/// <param name="writer"></param>
|
|
protected virtual void SaveStateBinaryInternal(BinaryWriter writer)
|
|
{
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// called after the base core loads state. the core must load any other variables
|
|
/// that were in SaveStateBinaryInternal and reset any native pointers.
|
|
/// the default implementation does nothing
|
|
/// </summary>
|
|
/// <param name="reader"></param>
|
|
protected virtual void LoadStateBinaryInternal(BinaryReader reader)
|
|
{
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ISoundProvider
|
|
|
|
public void SetSyncMode(SyncSoundMode mode)
|
|
{
|
|
if (mode == SyncSoundMode.Async)
|
|
{
|
|
throw new NotSupportedException("Async mode is not supported.");
|
|
}
|
|
}
|
|
|
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
|
{
|
|
samples = _soundBuffer;
|
|
nsamp = _numSamples;
|
|
}
|
|
|
|
public void GetSamplesAsync(short[] samples)
|
|
{
|
|
throw new InvalidOperationException("Async mode is not supported.");
|
|
}
|
|
|
|
public void DiscardSamples()
|
|
{
|
|
}
|
|
|
|
protected readonly short[] _soundBuffer;
|
|
protected int _numSamples;
|
|
public bool CanProvideAsync => false;
|
|
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
|
|
|
#endregion
|
|
|
|
#region IVideoProvider
|
|
|
|
public int[] GetVideoBuffer()
|
|
{
|
|
return _videoBuffer;
|
|
}
|
|
|
|
protected readonly int[] _videoBuffer;
|
|
public virtual int VirtualWidth => BufferWidth;
|
|
public virtual int VirtualHeight => BufferHeight;
|
|
public int BufferWidth { get; protected set; }
|
|
public int BufferHeight { get; protected set; }
|
|
public virtual int VsyncNumerator { get; protected set; }
|
|
public virtual int VsyncDenominator { get; protected set; }
|
|
public int BackgroundColor => unchecked((int)0xff000000);
|
|
|
|
#endregion
|
|
}
|
|
}
|