BizHawk/BizHawk.Emulation.Cores/Waterbox/WaterboxCore.cs

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
}
}