pce: basic rom loading and playing works

This commit is contained in:
nattthebear 2020-05-24 11:42:39 -04:00
parent d82d67996f
commit 69d3dbc35f
9 changed files with 302 additions and 206 deletions

View File

@ -0,0 +1,20 @@
using System;
using System.Text;
namespace BizHawk.Common
{
public static class Mershul
{
/// <summary>
/// TODO: Update to a version of .nyet that includes this
/// </summary>
public static unsafe string PtrToStringUtf8(IntPtr p)
{
byte* b = (byte*)p;
int len = 0;
while (*b++ != 0)
len++;
return Encoding.UTF8.GetString((byte*)p, len);
}
}
}

View File

@ -195,10 +195,12 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// </summary>
private bool IsSpecialReadonlySection(Section<ulong> sec)
{
// TODO: I don't think there are any more relro sections, right?
return sec.Name.Contains(".rel.ro")
|| sec.Name.StartsWith(".got")
|| sec.Name == ".init_array"
|| sec.Name == ".fini_array"
|| sec.Name == ".tbss"
|| sec == _imports
|| sec == _sealed;
}

View File

@ -1,6 +1,7 @@
using System;
using System.Runtime.InteropServices;
using BizHawk.BizInvoke;
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Waterbox
{
@ -36,8 +37,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
[BizExport(CallingConvention.Cdecl, EntryPoint = "__w_debug_puts")]
public void DebugPuts(IntPtr s)
{
// TODO: Should be PtrToStringUtf8
Console.WriteLine(Marshal.PtrToStringAnsi(s));
Console.WriteLine(Mershul.PtrToStringUtf8(s));
}
}
}

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using BizHawk.BizInvoke;
using BizHawk.Common;
namespace BizHawk.Emulation.Cores.Waterbox
{
@ -59,7 +60,11 @@ namespace BizHawk.Emulation.Cores.Waterbox
/// <summary>
/// true to skip video rendering
/// </summary>
public int SkipRendering;
public short SkipRendering;
/// <summary>
/// true to skip audion rendering
/// </summary>
public short SkipSoundening;
/// <summary>
/// a single command to run at the start of this frame
/// </summary>
@ -129,9 +134,9 @@ namespace BizHawk.Emulation.Cores.Waterbox
public IntPtr _defaultDeviceShortName;
public uint NumDevices;
public string ShortName => Marshal.PtrToStringAnsi(_shortName);
public string FullName => Marshal.PtrToStringAnsi(_fullName);
public string DefaultDeviceShortName => Marshal.PtrToStringAnsi(_defaultDeviceShortName);
public string ShortName => Mershul.PtrToStringUtf8(_shortName);
public string FullName => Mershul.PtrToStringUtf8(_fullName);
public string DefaultDeviceShortName => Mershul.PtrToStringUtf8(_defaultDeviceShortName);
}
[StructLayout(LayoutKind.Sequential)]
public struct NDeviceInfo
@ -143,9 +148,9 @@ namespace BizHawk.Emulation.Cores.Waterbox
public uint ByteLength;
public uint NumInputs;
public string ShortName => Marshal.PtrToStringAnsi(_shortName);
public string FullName => Marshal.PtrToStringAnsi(_fullName);
public string Description => Marshal.PtrToStringAnsi(_description);
public string ShortName => Mershul.PtrToStringUtf8(_shortName);
public string FullName => Mershul.PtrToStringUtf8(_fullName);
public string Description => Mershul.PtrToStringUtf8(_description);
}
[StructLayout(LayoutKind.Sequential)]
public struct NInputInfo
@ -158,15 +163,15 @@ namespace BizHawk.Emulation.Cores.Waterbox
public AxisFlags Flags;
public byte BitSize;
public string SettingName => Marshal.PtrToStringAnsi(_settingName);
public string Name => Marshal.PtrToStringAnsi(_name);
public string SettingName => Mershul.PtrToStringUtf8(_settingName);
public string Name => Mershul.PtrToStringUtf8(_name);
}
[StructLayout(LayoutKind.Sequential)]
public struct NButtonInfo
{
public IntPtr _excludeName;
public string ExcludeName => Marshal.PtrToStringAnsi(_excludeName);
public string ExcludeName => Mershul.PtrToStringUtf8(_excludeName);
}
[StructLayout(LayoutKind.Sequential)]
public struct NAxisInfo
@ -176,10 +181,10 @@ namespace BizHawk.Emulation.Cores.Waterbox
public IntPtr _nameNeg;
public IntPtr _namePos;
public string SettingsNameNeg => Marshal.PtrToStringAnsi(_settingsNameNeg);
public string SettingsNamePos => Marshal.PtrToStringAnsi(_settingsNamePos);
public string NameNeg => Marshal.PtrToStringAnsi(_nameNeg);
public string NamePos => Marshal.PtrToStringAnsi(_namePos);
public string SettingsNameNeg => Mershul.PtrToStringUtf8(_settingsNameNeg);
public string SettingsNamePos => Mershul.PtrToStringUtf8(_settingsNamePos);
public string NameNeg => Mershul.PtrToStringUtf8(_nameNeg);
public string NamePos => Mershul.PtrToStringUtf8(_namePos);
}
[StructLayout(LayoutKind.Sequential)]
public struct NSwitchInfo
@ -193,9 +198,9 @@ namespace BizHawk.Emulation.Cores.Waterbox
public IntPtr _name;
public IntPtr _description;
public string SettingName => Marshal.PtrToStringAnsi(_settingName);
public string Name => Marshal.PtrToStringAnsi(_name);
public string Description => Marshal.PtrToStringAnsi(_description);
public string SettingName => Mershul.PtrToStringUtf8(_settingName);
public string Name => Mershul.PtrToStringUtf8(_name);
public string Description => Mershul.PtrToStringUtf8(_description);
}
}
[StructLayout(LayoutKind.Sequential)]
@ -210,8 +215,8 @@ namespace BizHawk.Emulation.Cores.Waterbox
public int Color; // (msb)0RGB(lsb), -1 for unused.
public int _Padding;
public string ShortName => Marshal.PtrToStringAnsi(_shortName);
public string Name => Marshal.PtrToStringAnsi(_name);
public string ShortName => Mershul.PtrToStringUtf8(_shortName);
public string Name => Mershul.PtrToStringUtf8(_name);
}
}
@ -236,6 +241,10 @@ namespace BizHawk.Emulation.Cores.Waterbox
[BizImport(CC, Compatibility = true)]
public abstract NAxisInfo* GetAxis(uint port, uint dev, uint input);
/// <summary>
/// Set what input devices we're going to use
/// </summary>
/// <param name="devices">MUST end with a null string</param>
[BizImport(CC, Compatibility = true)]
public abstract void SetInputDevices(string[] devices);
@ -249,7 +258,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
[StructLayout(LayoutKind.Sequential)]
public class SystemInfo
public struct SystemInfo
{
public int MaxWidth;
public int MaxHeight;
@ -260,6 +269,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
[BizImport(CC, Compatibility = true)]
public abstract SystemInfo GetSystemInfo();
public abstract SystemInfo* GetSystemInfo();
}
}

View File

@ -176,7 +176,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
public WaterboxMemoryDomain(MemoryArea m, IMonitor monitor)
{
Name = Marshal.PtrToStringAnsi(m.Name);
Name = Mershul.PtrToStringUtf8(m.Name);
EndianType = (m.Flags & MemoryDomainFlags.YugeEndian) != 0 ? Endian.Big : Endian.Little;
_data = m.Data;
Size = m.Size;

View File

@ -0,0 +1,185 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Waterbox
{
unsafe partial class NymaCore
{
private ControllerAdapter _controllerAdapter;
private readonly byte[] _inputPortData = new byte[16 * 16];
private void InitControls()
{
_controllerAdapter = new ControllerAdapter(_nyma, new string[0]);
_nyma.SetInputDevices(_controllerAdapter.Devices);
ControllerDefinition = _controllerAdapter.Definition;
}
protected delegate void ControllerThunk(IController c, byte[] b);
protected class ControllerAdapter
{
/// <summary>
/// allowed number of input ports. must match native
/// </summary>
private const int MAX_PORTS = 16;
/// <summary>
/// total maximum bytes on each input port. must match native
/// </summary>
private const int MAX_PORT_DATA = 16;
/// <summary>
/// Device list suitable to pass back to the core
/// </summary>
public string[] Devices { get; }
public ControllerDefinition Definition { get; }
public ControllerAdapter(LibNymaCore core, string[] config)
{
var ret = new ControllerDefinition
{
Name = "TODO"
};
var finalDevices = new List<string>();
var numPorts = core.GetNumPorts();
if (numPorts > MAX_PORTS)
throw new InvalidOperationException($"Too many input ports");
for (uint port = 0, devByteStart = 0; port < numPorts; port++, devByteStart += MAX_PORT_DATA)
{
var portInfo = *core.GetPort(port);
var deviceName = port < config.Length ? config[port] : portInfo.DefaultDeviceShortName;
finalDevices.Add(deviceName);
var devices = Enumerable.Range(0, (int)portInfo.NumDevices)
.Select(i => new { Index = (uint)i, Device = *core.GetDevice(port, (uint)i) })
.ToList();
var device = devices.FirstOrDefault(a => a.Device.ShortName == deviceName);
if (device == null)
{
Console.WriteLine($"Warn: unknown controller device {deviceName}");
device = devices.FirstOrDefault(a => a.Device.ShortName == portInfo.DefaultDeviceShortName);
if (device == null)
throw new InvalidOperationException($"Fail: unknown controller device {portInfo.DefaultDeviceShortName}");
}
var deviceInfo = device.Device;
if (deviceInfo.ByteLength > MAX_PORT_DATA)
throw new InvalidOperationException($"Input device {deviceInfo.ShortName} uses more than {MAX_PORT_DATA} bytes");
var category = portInfo.FullName + " - " + deviceInfo.FullName;
var inputs = Enumerable.Range(0, (int)deviceInfo.NumInputs)
.Select(i => new { Index = i, Data = *core.GetInput(port, device.Index, (uint)i) })
.OrderBy(a => a.Data.ConfigOrder);
foreach (var input in inputs)
{
var inputInfo = input.Data;
var bitSize = (int)inputInfo.BitSize;
var bitOffset = (int)inputInfo.BitOffset;
var byteStart = devByteStart + bitOffset / 8;
bitOffset %= 8;
var name = $"P{port + 1} {inputInfo.Name}";
switch (inputInfo.Type)
{
case LibNymaCore.InputType.PADDING:
{
break;
}
case LibNymaCore.InputType.BUTTON:
case LibNymaCore.InputType.BUTTON_CAN_RAPID:
{
var data = *core.GetButton(port, device.Index, (uint)input.Index);
// TODO: Wire up data.ExcludeName
ret.BoolButtons.Add(name);
_thunks.Add((c, b) =>
{
if (c.IsPressed(name))
b[byteStart] |= (byte)(1 << bitOffset);
});
break;
}
case LibNymaCore.InputType.SWITCH:
{
var data = *core.GetSwitch(port, device.Index, (uint)input.Index);
// TODO: Possibly bulebutton for 2 states?
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
0, (int)data.DefaultPosition, (int)data.NumPositions - 1));
// HACK: Silently discard this until bizhawk fixes its shit
// _thunks.Add((c, b) =>
// {
// var val = (int)Math.Round(c.AxisValue(name));
// b[byteStart] |= (byte)(1 << bitOffset);
// });
break;
}
case LibNymaCore.InputType.AXIS:
{
var data = core.GetAxis(port, device.Index, (uint)input.Index);
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
0, 0x8000, 0xffff, (inputInfo.Flags & LibNymaCore.AxisFlags.INVERT_CO) != 0
));
_thunks.Add((c, b) =>
{
var val = (ushort)Math.Round(c.AxisValue(name));
b[byteStart] = (byte)val;
b[byteStart + 1] = (byte)(val >> 8);
});
break;
}
case LibNymaCore.InputType.AXIS_REL:
{
var data = core.GetAxis(port, device.Index, (uint)input.Index);
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
-0x8000, 0, 0x7fff, (inputInfo.Flags & LibNymaCore.AxisFlags.INVERT_CO) != 0
));
_thunks.Add((c, b) =>
{
var val = (short)Math.Round(c.AxisValue(name));
b[byteStart] = (byte)val;
b[byteStart + 1] = (byte)(val >> 8);
});
break;
}
case LibNymaCore.InputType.POINTER_X:
{
throw new Exception("TODO: Axis ranges are ints????");
// ret.AxisControls.Add(name);
// ret.AxisRanges.Add(new ControllerDefinition.AxisRange(0, 0.5, 1));
// break;
}
case LibNymaCore.InputType.POINTER_Y:
{
throw new Exception("TODO: Axis ranges are ints????");
// ret.AxisControls.Add(name);
// ret.AxisRanges.Add(new ControllerDefinition.AxisRange(0, 0.5, 1, true));
// break;
}
// TODO: wire up statuses to something (not controller, of course)
default:
throw new NotImplementedException($"Unimplemented button type {inputInfo.Type}");
}
ret.CategoryLabels[name] = category;
}
}
Definition = ret;
finalDevices.Add(null);
Devices = finalDevices.ToArray();
}
private readonly List<Action<IController, byte[]>> _thunks = new List<Action<IController, byte[]>>();
public void SetBits(IController src, byte[] dest)
{
Array.Clear(dest, 0, dest.Length);
foreach (var t in _thunks)
t(src, dest);
}
}
}
}

View File

@ -7,7 +7,7 @@ using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Waterbox
{
public unsafe abstract class NymaCore : WaterboxCore
public unsafe abstract partial class NymaCore : WaterboxCore
{
protected NymaCore(GameInfo game, byte[] rom, CoreComm comm, Configuration c)
: base(comm, c)
@ -15,9 +15,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
private LibNymaCore _nyma;
private ControllerAdapter _controllerAdapter;
private readonly byte[] _inputPortData = new byte[16 * 16];
protected T DoInit<T>(GameInfo game, byte[] rom, string filename, string extension)
where T : LibNymaCore
{
@ -55,7 +52,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
_exe.RemoveReadonlyFile(fn);
var info = _nyma.GetSystemInfo();
var info = *_nyma.GetSystemInfo();
_videoBuffer = new int[info.MaxWidth * info.MaxHeight];
BufferWidth = info.NominalWidth;
BufferHeight = info.NominalHeight;
@ -76,8 +73,9 @@ namespace BizHawk.Emulation.Cores.Waterbox
VsyncNumerator = info.FpsFixed;
VsyncDenominator = 1 << 24;
_controllerAdapter = new ControllerAdapter(_nyma, new string[0]);
_nyma.SetInputDevices(_controllerAdapter.Devices);
_soundBuffer = new short[22050 * 2];
InitControls();
PostInit();
}
@ -94,7 +92,8 @@ namespace BizHawk.Emulation.Cores.Waterbox
_frameAdvanceInputLock = GCHandle.Alloc(_inputPortData, GCHandleType.Pinned);
var ret = new LibNymaCore.FrameInfo
{
SkipRendering = render ? 0 : 1,
SkipRendering = (short)(render ? 0 : 1),
SkipSoundening =(short)(rendersound ? 0 : 1),
Command = LibNymaCore.CommandType.NONE,
InputPortData = (byte*)_frameAdvanceInputLock.AddrOfPinnedObject()
};
@ -105,156 +104,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
_frameAdvanceInputLock.Free();
}
protected delegate void ControllerThunk(IController c, byte[] b);
protected class ControllerAdapter
{
public string[] Devices { get; }
public ControllerDefinition Definition { get; }
public ControllerAdapter(LibNymaCore core, string[] config)
{
var ret = new ControllerDefinition
{
Name = "TODO"
};
var finalDevices = new List<string>();
var numPorts = core.GetNumPorts();
for (uint i = 0, devByteStart = 0; i < numPorts; i++)
{
var port = *core.GetPort(i);
var devName = i < config.Length ? config[i] : port.DefaultDeviceShortName;
finalDevices.Add(devName);
var devices = Enumerable.Range(0, (int)port.NumDevices)
.Select(j => new { Index = (uint)j, Device = *core.GetDevice(i, (uint)j) })
.ToList();
var device = devices.FirstOrDefault(a => a.Device.ShortName == devName);
if (device == null)
{
Console.WriteLine($"Warn: unknown controller device {devName}");
device = devices.FirstOrDefault(a => a.Device.ShortName == port.DefaultDeviceShortName);
if (device == null)
throw new InvalidOperationException($"Fail: unknown controller device {port.DefaultDeviceShortName}");
}
var dev = device.Device;
var category = port.FullName + " - " + dev.FullName;
var inputs = Enumerable.Range(0, (int)dev.NumInputs)
.Select(iix => new { Index = iix, Data = *core.GetInput(i, device.Index, (uint)iix) })
.OrderBy(a => a.Data.ConfigOrder);
foreach (var inputzz in inputs)
{
var input = inputzz.Data;
var bitSize = (int)input.BitSize;
var bitOffset = (int)input.BitOffset;
var byteStart = devByteStart + bitOffset / 8;
bitOffset %= 8;
var name = input.Name;
switch (input.Type)
{
case LibNymaCore.InputType.PADDING:
{
break;
}
case LibNymaCore.InputType.BUTTON:
case LibNymaCore.InputType.BUTTON_CAN_RAPID:
{
var data = *core.GetButton(i, device.Index, (uint)inputzz.Index);
// TODO: Wire up data.ExcludeName
ret.BoolButtons.Add(name);
_thunks.Add((c, b) =>
{
if (c.IsPressed(name))
b[byteStart] |= (byte)(1 << bitOffset);
});
break;
}
case LibNymaCore.InputType.SWITCH:
{
var data = *core.GetSwitch(i, device.Index, (uint)inputzz.Index);
// TODO: Possibly bulebutton for 2 states?
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
0, (int)data.DefaultPosition, (int)data.NumPositions - 1));
_thunks.Add((c, b) =>
{
var val = (int)Math.Round(c.AxisValue(name));
b[byteStart] |= (byte)(1 << bitOffset);
});
break;
}
case LibNymaCore.InputType.AXIS:
{
var data = core.GetAxis(i, device.Index, (uint)inputzz.Index);
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
0, 0x8000, 0xffff, (input.Flags & LibNymaCore.AxisFlags.INVERT_CO) != 0
));
_thunks.Add((c, b) =>
{
var val = (ushort)Math.Round(c.AxisValue(name));
b[byteStart] = (byte)val;
b[byteStart + 1] = (byte)(val >> 8);
});
break;
}
case LibNymaCore.InputType.AXIS_REL:
{
var data = core.GetAxis(i, device.Index, (uint)inputzz.Index);
ret.AxisControls.Add(name);
ret.AxisRanges.Add(new ControllerDefinition.AxisRange(
-0x8000, 0, 0x7fff, (input.Flags & LibNymaCore.AxisFlags.INVERT_CO) != 0
));
_thunks.Add((c, b) =>
{
var val = (short)Math.Round(c.AxisValue(name));
b[byteStart] = (byte)val;
b[byteStart + 1] = (byte)(val >> 8);
});
break;
}
case LibNymaCore.InputType.POINTER_X:
{
throw new Exception("TODO: Axis ranges are ints????");
// ret.AxisControls.Add(name);
// ret.AxisRanges.Add(new ControllerDefinition.AxisRange(0, 0.5, 1));
// break;
}
case LibNymaCore.InputType.POINTER_Y:
{
throw new Exception("TODO: Axis ranges are ints????");
// ret.AxisControls.Add(name);
// ret.AxisRanges.Add(new ControllerDefinition.AxisRange(0, 0.5, 1, true));
// break;
}
// TODO: wire up statuses to something (not controller, of course)
default:
throw new NotImplementedException($"Unimplemented button type {input.Type}");
}
ret.CategoryLabels[name] = category;
}
devByteStart += dev.ByteLength;
}
Definition = ret;
Devices = finalDevices.ToArray();
}
private readonly List<Action<IController, byte[]>> _thunks = new List<Action<IController, byte[]>>();
public void SetBits(IController src, byte[] dest)
{
Array.Clear(dest, 0, dest.Length);
foreach (var t in _thunks)
t(src, dest);
}
}
public DisplayType Region { get; protected set; }
/// <summary>
@ -274,7 +123,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
if (*q == 0)
{
if (q > p)
ret.Add(Marshal.PtrToStringAnsi((IntPtr)p));
ret.Add(Mershul.PtrToStringUtf8((IntPtr)p));
else
break;
p = ++q;

View File

@ -197,9 +197,19 @@ namespace BizHawk.Emulation.Cores.Waterbox
LagCount++;
AdvanceRtc();
BufferWidth = frame.Width;
BufferHeight = frame.Height;
_numSamples = frame.Samples;
if (render)
{
BufferWidth = frame.Width;
BufferHeight = frame.Height;
}
if (rendersound)
{
_numSamples = frame.Samples;
}
else
{
_numSamples = 0;
}
FrameAdvancePost();
}
@ -319,7 +329,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
}
protected readonly short[] _soundBuffer;
protected short[] _soundBuffer;
protected int _numSamples;
public bool CanProvideAsync => false;
public SyncSoundMode SyncMode => SyncSoundMode.Sync;

View File

@ -36,12 +36,13 @@ ECL_EXPORT bool Init(const InitData& data)
samples = new int16_t[22050 * 2];
Surf = new MDFN_Surface(
pixels, Game->fb_width, Game->fb_height, Game->fb_width,
MDFN_PixelFormat(MDFN_COLORSPACE_RGB, 0, 8, 16, 24)
MDFN_PixelFormat(MDFN_COLORSPACE_RGB, 16, 8, 0, 24)
);
EES = new EmulateSpecStruct();
EES->surface = Surf;
EES->VideoFormatChanged = true;
EES->LineWidths = new int32_t[Game->fb_height];
memset(EES->LineWidths, 0xff, Game->fb_height * sizeof(int32_t));
EES->SoundBuf = samples;
EES->SoundBufMaxSize = 22050;
EES->SoundFormatChanged = true;
@ -71,7 +72,8 @@ ECL_EXPORT bool Init(const InitData& data)
struct MyFrameInfo: public FrameInfo
{
// true to skip video rendering
int32_t SkipRendering;
int16_t SkipRendering;
int16_t SkipSoundening;
// a single MDFN_MSC_* command to run at the start of this frame; 0 if none
int32_t Command;
// raw data for each input port, assumed to be MAX_PORTS * MAX_PORT_DATA long
@ -92,24 +94,43 @@ ECL_EXPORT void FrameAdvance(MyFrameInfo& frame)
EES->VideoFormatChanged = false;
EES->SoundFormatChanged = false;
frame.Cycles = EES->MasterCycles; // TODO: Was this supposed to be total or delta?
memcpy(frame.SoundBuffer, EES->SoundBuf, EES->SoundBufSize * 4);
frame.Samples = EES->SoundBufSize;
// TODO: Use linewidths
int w = EES->DisplayRect.w;
int h = EES->DisplayRect.h;
frame.Width = w;
frame.Height = h;
int srcp = Game->fb_width;
int dstp = Game->fb_height;
uint32_t* src = pixels + EES->DisplayRect.x + EES->DisplayRect.y * srcp;
uint32_t* dst = pixels;
for (int line = 0; line < h; line++)
frame.Cycles = EES->MasterCycles;
if (!frame.SkipSoundening)
{
memcpy(dst, src, w * 4);
src += srcp;
dst += dstp;
memcpy(frame.SoundBuffer, EES->SoundBuf, EES->SoundBufSize * 4);
frame.Samples = EES->SoundBufSize;
}
if (!frame.SkipRendering)
{
int h = EES->DisplayRect.h;
int lineStart = EES->DisplayRect.y;
int lineEnd = lineStart + h;
auto multiWidth = EES->LineWidths[0] != -1;
int w;
if (multiWidth)
{
w = 0;
for (int line = lineStart; line < lineEnd; line++)
w = std::max(w, EES->LineWidths[line]);
}
else
{
w = EES->DisplayRect.w;
}
frame.Width = w;
frame.Height = h;
int srcp = Game->fb_width;
int dstp = w;
uint32_t* src = pixels + EES->DisplayRect.x + EES->DisplayRect.y * srcp;
uint32_t* dst = frame.VideoBuffer;
for (int line = lineStart; line < lineEnd; line++)
{
memcpy(dst, src, (multiWidth ? EES->LineWidths[line] : w) * sizeof(uint32_t));
src += srcp;
dst += dstp;
}
}
}