Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 74 additions & 6 deletions src/BizHawk.Client.Common/Api/Classes/MemoryApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ public IReadOnlyList<byte> ReadByteRange(long addr, int length, string domain =
return newBytes;
}

public void WriteByteRange(long addr, IReadOnlyList<byte> memoryblock, string domain = null)
public void WriteByteRange(long addr, Span<byte> memoryblock, string domain = null)
{
var d = NamedDomainOrCurrent(domain);
if (!d.Writable)
Expand All @@ -273,15 +273,83 @@ public void WriteByteRange(long addr, IReadOnlyList<byte> memoryblock, string do
return;
}
if (addr < 0) LogCallback($"Warning: Attempted writes on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
var lastReqAddr = addr + memoryblock.Count - 1;
var indexAfterLast = Math.Min(Math.Max(-1L, lastReqAddr), d.Size - 1L) + 1L;
var iDst = Math.Min(Math.Max(0L, addr), d.Size);
var iSrc = checked((int) (iDst - addr));
using (d.EnterExit())
var lastReqAddrExclusive = addr + memoryblock.Length;
var lastAddressExclusive = Math.Min(Math.Max(0, lastReqAddrExclusive), d.Size);
var length = lastAddressExclusive - iDst;
if (length > 0) using (d.EnterExit()) d.BulkPokeByte(iDst, memoryblock.Slice(start: iSrc, length: (int)length));
if (lastReqAddrExclusive > d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddrExclusive} outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
}

public IReadOnlyList<ushort> ReadU16Range(long addr, int count, string domain = null)
{
var d = NamedDomainOrCurrent(domain);
if (addr < 0) LogCallback($"Warning: Attempted reads on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(ReadU16Range)}()");
var lastReqAddr = addr + count * sizeof(ushort) - 1;
var indexAfterLast = Math.Min(Math.Max(-1L, lastReqAddr), d.Size - 1L) + 1L;
var iSrc = Math.Min(Math.Max(0L, addr), d.Size);
var iDst = (iSrc - addr) / sizeof(ushort);
var values = new ushort[(indexAfterLast - iSrc) / sizeof(ushort)];
if (iSrc < indexAfterLast) using (d.EnterExit()) d.BulkPeekUshort(iSrc.RangeToExclusive(indexAfterLast), _isBigEndian, values);
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted reads on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(ReadU16Range)}()");
if (values.Length == count) return values;
var newValues = new ushort[count];
if (values.Length is not 0) Array.Copy(sourceArray: values, sourceIndex: 0, destinationArray: newValues, destinationIndex: iDst, length: values.Length);
return newValues;
}

public void WriteU16Range(long addr, Span<ushort> memoryblock, string domain = null)
{
var d = NamedDomainOrCurrent(domain);
if (!d.Writable)
{
LogCallback($"Error: the domain {d.Name} is not writable");
return;
}
if (addr < 0) LogCallback($"Warning: Attempted writes on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(WriteU16Range)}()");
var iDst = Math.Min(Math.Max(0L, addr), d.Size);
var iSrc = checked((int) (iDst - addr)) / sizeof(ushort);
var lastReqAddrExclusive = addr + memoryblock.Length * sizeof(ushort);
var lastAddressExclusive = Math.Min(Math.Max(0, lastReqAddrExclusive), d.Size);
var length = lastAddressExclusive - iDst;
if (length > 0) using (d.EnterExit()) d.BulkPokeUshort(iDst, _isBigEndian, memoryblock.Slice(start: iSrc, length: (int)(length / sizeof(ushort))));
if (lastReqAddrExclusive >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddrExclusive} outside range of domain {d.Name} in {nameof(WriteU16Range)}()");
}

public IReadOnlyList<uint> ReadU32Range(long addr, int count, string domain = null)
{
var d = NamedDomainOrCurrent(domain);
if (addr < 0) LogCallback($"Warning: Attempted reads on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(ReadU32Range)}()");
var lastReqAddr = addr + count * sizeof(uint) - 1;
var indexAfterLast = Math.Min(Math.Max(-1L, lastReqAddr), d.Size - 1L) + 1L;
var iSrc = Math.Min(Math.Max(0L, addr), d.Size);
var iDst = (iSrc - addr) / sizeof(uint);
var values = new uint[(indexAfterLast - iSrc) / sizeof(uint)];
if (iSrc < indexAfterLast) using (d.EnterExit()) d.BulkPeekUint(iSrc.RangeToExclusive(indexAfterLast), _isBigEndian, values);
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted reads on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(ReadU32Range)}()");
if (values.Length == count) return values;
var newValues = new uint[count];
if (values.Length is not 0) Array.Copy(sourceArray: values, sourceIndex: 0, destinationArray: newValues, destinationIndex: iDst, length: values.Length);
return newValues;
}

public void WriteU32Range(long addr, Span<uint> memoryblock, string domain = null)
{
var d = NamedDomainOrCurrent(domain);
if (!d.Writable)
{
while (iDst < indexAfterLast) d.PokeByte(iDst++, memoryblock[iSrc++]);
LogCallback($"Error: the domain {d.Name} is not writable");
return;
}
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
if (addr < 0) LogCallback($"Warning: Attempted writes on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(WriteU32Range)}()");
var iDst = Math.Min(Math.Max(0L, addr), d.Size);
var iSrc = checked((int) (iDst - addr)) / sizeof(uint);
var lastReqAddrExclusive = addr + memoryblock.Length * sizeof(uint);
var lastAddressExclusive = Math.Min(Math.Max(0, lastReqAddrExclusive), d.Size);
var length = lastAddressExclusive - iDst;
if (length > 0) using (d.EnterExit()) d.BulkPokeUint(iDst, _isBigEndian, memoryblock.Slice(start: iSrc, length: (int)(length / sizeof(uint))));
if (lastReqAddrExclusive >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddrExclusive} outside range of domain {d.Name} in {nameof(WriteU32Range)}()");
}

public float ReadFloat(long addr, string domain = null)
Expand Down
7 changes: 6 additions & 1 deletion src/BizHawk.Client.Common/Api/Interfaces/IMemoryApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,14 @@ public interface IMemoryApi : IExternalApi
uint ReadU32(long addr, string domain = null);

void WriteByte(long addr, uint value, string domain = null);
void WriteByteRange(long addr, IReadOnlyList<byte> memoryblock, string domain = null);
void WriteByteRange(long addr, Span<byte> memoryblock, string domain = null);
void WriteFloat(long addr, float value, string domain = null);

IReadOnlyList<ushort> ReadU16Range(long addr, int count, string domain = null);
IReadOnlyList<uint> ReadU32Range(long addr, int count, string domain = null);
void WriteU16Range(long addr, Span<ushort> memoryblock, string domain = null);
void WriteU32Range(long addr, Span<uint> memoryblock, string domain = null);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write*Range signatures are fine for Span, but then why do Read*Range allocate and return arrays?
See also #3472.
Not that you need these anyway, since the caller can just MemoryMarshal.Cast to/from byte.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was just a lot simpler to do it that way, and the byte one returns an array IIRC so I was following that.
I knew you were wanting to update some of this stuff anyway, so I didn't think it very important.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update on this: The new read methods return IReadOnlyList, so as to match the existing ReadByteRange method. I considered changing them all to accept a Span instead but that's too much work to do before release.
The existing WriteByteRange method was changed to accept a Span though, since this is good and easy to do and makes it match the new ones.


void WriteS8(long addr, int value, string domain = null);
void WriteS16(long addr, int value, string domain = null);
void WriteS24(long addr, int value, string domain = null);
Expand Down
66 changes: 65 additions & 1 deletion src/BizHawk.Client.Common/lua/CommonLibs/MemoryLuaLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public void WriteByteRange([LuaZeroIndexed] LuaTable memoryblock, string domain
[LuaMethodExample("memory.write_bytes_as_array(0x100, { 0xAB, 0x12, 0xCD, 0x34 });")]
[LuaMethod("write_bytes_as_array", "Writes sequential bytes starting at addr.")]
public void WriteBytesAsArray(long addr, LuaTable bytes, string domain = null)
=> APIs.Memory.WriteByteRange(addr, _th.EnumerateValues<long>(bytes).Select(l => (byte) l).ToList(), domain);
=> APIs.Memory.WriteByteRange(addr, _th.EnumerateValues<long>(bytes).Select(l => (byte) l).ToArray(), domain);

[LuaMethodExample("memory.write_bytes_as_dict({ [0x100] = 0xAB, [0x104] = 0xCD, [0x106] = 0x12, [0x107] = 0x34, [0x108] = 0xEF });")]
[LuaMethod("write_bytes_as_dict", "Writes bytes at arbitrary addresses (the keys of the given table are the addresses, relative to the start of the domain).")]
Expand All @@ -132,6 +132,70 @@ public void WriteBytesAsDict([LuaZeroIndexed] LuaTable addrMap, string domain =
}
}

[LuaMethodExample("local values = memory.read_u16_le_as_array(0x100, 30, \"WRAM\");")]
[LuaMethod("read_u16_le_as_array", "Reads count 16-bit values starting at addr into an array-like table (1-indexed).")]
public LuaTable ReadUshortsAsArray(long addr, int count, string domain = null)
{
APIs.Memory.SetBigEndian(false);
return _th.ListToTable(APIs.Memory.ReadU16Range(addr, count, domain));
}

[LuaMethodExample("memory.write_u16_le_as_array(0x100, { 0xABCD, 0x1234 });")]
[LuaMethod("write_u16_le_as_array", "Writes sequential 16-byte values starting at addr.")]
public void WriteUshortsAsArray(long addr, LuaTable values, string domain = null)
{
APIs.Memory.SetBigEndian(false);
APIs.Memory.WriteU16Range(addr, _th.EnumerateValues<long>(values).Select(l => (ushort) l).ToArray(), domain);
}

[LuaMethodExample("local values = memory.read_u16_be_as_array(0x100, 30, \"WRAM\");")]
[LuaMethod("read_u16_be_as_array", "Reads count 16-bit values starting at addr into an array-like table (1-indexed).")]
public LuaTable ReadUshortsBigAsArray(long addr, int count, string domain = null)
{
APIs.Memory.SetBigEndian(true);
return _th.ListToTable(APIs.Memory.ReadU16Range(addr, count, domain));
}

[LuaMethodExample("memory.write_u16_be_as_array(0x100, { 0xABCD, 0x1234 });")]
[LuaMethod("write_u16_be_as_array", "Writes sequential 16-byte values starting at addr.")]
public void WriteUshortsAsBigArray(long addr, LuaTable values, string domain = null)
{
APIs.Memory.SetBigEndian(true);
APIs.Memory.WriteU16Range(addr, _th.EnumerateValues<long>(values).Select(l => (ushort) l).ToArray(), domain);
}

[LuaMethodExample("local values = memory.read_u32_le_as_array(0x100, 30, \"WRAM\");")]
[LuaMethod("read_u32_le_as_array", "Reads count 32-bit values starting at addr into an array-like table (1-indexed).")]
public LuaTable ReadUintsAsArray(long addr, int count, string domain = null)
{
APIs.Memory.SetBigEndian(false);
return _th.ListToTable(APIs.Memory.ReadU32Range(addr, count, domain));
}

[LuaMethodExample("memory.write_u32_le_as_array(0x100, { 0xABCD, 0x1234 });")]
[LuaMethod("write_u32_le_as_array", "Writes sequential 32-byte values starting at addr.")]
public void WriteUintsAsArray(long addr, LuaTable values, string domain = null)
{
APIs.Memory.SetBigEndian(false);
APIs.Memory.WriteU32Range(addr, _th.EnumerateValues<long>(values).Select(l => (uint) l).ToArray(), domain);
}

[LuaMethodExample("local values = memory.read_u32_be_as_array(0x100, 30, \"WRAM\");")]
[LuaMethod("read_u32_be_as_array", "Reads count 32-bit values starting at addr into an array-like table (1-indexed).")]
public LuaTable ReadUintsBigAsArray(long addr, int count, string domain = null)
{
APIs.Memory.SetBigEndian(true);
return _th.ListToTable(APIs.Memory.ReadU32Range(addr, count, domain));
}

[LuaMethodExample("memory.write_u32_be_as_array(0x100, { 0xABCD, 0x1234 });")]
[LuaMethod("write_u32_be_as_array", "Writes sequential 32-byte values starting at addr.")]
public void WriteUintsBigAsArray(long addr, LuaTable values, string domain = null)
{
APIs.Memory.SetBigEndian(true);
APIs.Memory.WriteU32Range(addr, _th.EnumerateValues<long>(values).Select(l => (uint) l).ToArray(), domain);
}

[LuaMethodExample("""
memory.write_bytes_as_binary_string(0x100, string.pack("<i4f", 1234, 456.789), "WRAM")
memory.write_bytes_as_binary_string(0x108, "\xFE\xED", "WRAM")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public void WriteByteRange([LuaZeroIndexed] LuaTable memoryblock)
[LuaMethodExample("mainmemory.write_bytes_as_array(0x100, { 0xAB, 0x12, 0xCD, 0x34 });")]
[LuaMethod("write_bytes_as_array", "Writes sequential bytes starting at addr.")]
public void WriteBytesAsArray(long addr, LuaTable bytes)
=> APIs.Memory.WriteByteRange(addr, _th.EnumerateValues<long>(bytes).Select(l => (byte) l).ToList(), MainMemName);
=> APIs.Memory.WriteByteRange(addr, _th.EnumerateValues<long>(bytes).Select(l => (byte) l).ToArray(), MainMemName);

[LuaMethodExample("mainmemory.write_bytes_as_dict({ [0x100] = 0xAB, [0x104] = 0xCD, [0x106] = 0x12, [0x107] = 0x34, [0x108] = 0xEF });")]
[LuaMethod("write_bytes_as_dict", "Writes bytes at arbitrary addresses (the keys of the given table are the addresses, relative to the start of the main memory).")]
Expand Down
46 changes: 39 additions & 7 deletions src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ public enum Endian

private const string ERR_MSG_BUFFER_WRONG_SIZE = "Invalid length of values array";

private const string ERR_MSG_UNALIGNED = "The API contract doesn't define what to do for unaligned reads and writes!";

public string Name { get; protected set; }

public long Size { get; protected set; }
Expand Down Expand Up @@ -93,7 +91,7 @@ public virtual void BulkPeekByte(Range<long> addresses, byte[] values)
{
if (addresses is null) throw new ArgumentNullException(paramName: nameof(addresses));
if (values is null) throw new ArgumentNullException(paramName: nameof(values));
if ((long) addresses.Count() != values.Length) throw new InvalidOperationException(ERR_MSG_BUFFER_WRONG_SIZE);
if ((long) addresses.Count() > values.Length) throw new InvalidOperationException(ERR_MSG_BUFFER_WRONG_SIZE);

using (this.EnterExit())
{
Expand All @@ -104,15 +102,26 @@ public virtual void BulkPeekByte(Range<long> addresses, byte[] values)
}
}

public virtual void BulkPokeByte(long startAddress, Span<byte> values)
Comment thread
SuuperW marked this conversation as resolved.
{
using (this.EnterExit())
{
long address = startAddress;
for (var i = 0; i < values.Length; i++, address++)
{
PokeByte(address, values[i]);
}
}
}

public virtual void BulkPeekUshort(Range<long> addresses, bool bigEndian, ushort[] values)
{
if (addresses is null) throw new ArgumentNullException(paramName: nameof(addresses));
if (values is null) throw new ArgumentNullException(paramName: nameof(values));

var start = addresses.Start;
var end = addresses.EndInclusive + 1;
if ((start & 0b1) is not 0 || (end & 0b1) is not 0) throw new InvalidOperationException(ERR_MSG_UNALIGNED);
if (values.LongLength * sizeof(ushort) != end - start) throw new InvalidOperationException(ERR_MSG_BUFFER_WRONG_SIZE); // a longer array could be valid, but nothing needs that so don't support it for now
if (values.LongLength * sizeof(ushort) < end - start) throw new InvalidOperationException(ERR_MSG_BUFFER_WRONG_SIZE);

using (this.EnterExit())
{
Expand All @@ -123,15 +132,26 @@ public virtual void BulkPeekUshort(Range<long> addresses, bool bigEndian, ushort
}
}

public virtual void BulkPokeUshort(long startAddress, bool bigEndian, Span<ushort> values)
{
using (this.EnterExit())
{
long address = startAddress;
for (var i = 0; i < values.Length; i++, address += sizeof(ushort))
{
PokeUshort(address, values[i], bigEndian);
}
}
}

public virtual void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[] values)
{
if (addresses is null) throw new ArgumentNullException(paramName: nameof(addresses));
if (values is null) throw new ArgumentNullException(paramName: nameof(values));

var start = addresses.Start;
var end = addresses.EndInclusive + 1;
if ((start & 0b11) is not 0 || (end & 0b11) is not 0) throw new InvalidOperationException(ERR_MSG_UNALIGNED);
if (values.LongLength * sizeof(uint) != end - start) throw new InvalidOperationException(ERR_MSG_BUFFER_WRONG_SIZE); // `!=` not `<`, per `BulkPeekUshort`
if (values.LongLength * sizeof(uint) < end - start) throw new InvalidOperationException(ERR_MSG_BUFFER_WRONG_SIZE);

using (this.EnterExit())
{
Expand All @@ -142,6 +162,18 @@ public virtual void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[] v
}
}

public virtual void BulkPokeUint(long startAddress, bool bigEndian, Span<uint> values)
{
using (this.EnterExit())
{
long address = startAddress;
for (var i = 0; i < values.Length; i++, address += sizeof(uint))
{
PokeUint(address, values[i], bigEndian);
}
}
}

public virtual void SendCheatToCore(int addr, byte value, int compare, int compare_type) { }

/// <summary>
Expand Down
12 changes: 2 additions & 10 deletions src/BizHawk.Emulation.Cores/Computers/Doom/DSDA.IMemoryDomains.cs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,8 @@ public override void BulkPeekUshort(Range<long> addresses, bool bigEndian, ushor
var start = addresses.Start;
var end = addresses.EndInclusive + 1;

if ((start & 1) != 0 || (end & 1) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");

if (values.LongLength * 2 != end - start)
if (values.LongLength * sizeof(ushort) < end - start)
{
// a longer array could be valid, but nothing needs that so don't support it for now
throw new InvalidOperationException("Invalid length of values array");
}

Expand All @@ -262,12 +258,8 @@ public override void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[]
var start = addresses.Start;
var end = addresses.EndInclusive + 1;

if ((start & 3) != 0 || (end & 3) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");

if (values.LongLength * 4 != end - start)
if (values.LongLength * sizeof(uint) < end - start)
{
// a longer array could be valid, but nothing needs that so don't support it for now
throw new InvalidOperationException("Invalid length of values array");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,12 +222,8 @@ public override void BulkPeekUshort(Range<long> addresses, bool bigEndian, ushor
var start = addresses.Start;
var end = addresses.EndInclusive + 1;

if ((start & 1) != 0 || (end & 1) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");

if (values.LongLength * 2 != end - start)
if (values.LongLength * sizeof(ushort) < end - start)
{
// a longer array could be valid, but nothing needs that so don't support it for now
throw new InvalidOperationException("Invalid length of values array");
}

Expand All @@ -250,12 +246,8 @@ public override void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[]
var start = addresses.Start;
var end = addresses.EndInclusive + 1;

if ((start & 3) != 0 || (end & 3) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");

if (values.LongLength * 4 != end - start)
if (values.LongLength * sizeof(uint) < end - start)
{
// a longer array could be valid, but nothing needs that so don't support it for now
throw new InvalidOperationException("Invalid length of values array");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ public override void BulkPeekByte(Range<long> addresses, byte[] values)
_read8.Access((IntPtr)p, addresses.Start, (long)addresses.Count());
}

public void BulkPokeByte(long addr, Span<byte> values)
public override void BulkPokeByte(long addr, Span<byte> values)
{
if ((ulong)addr + (ulong)values.Length > (ulong)Size || addr < 0) throw new ArgumentOutOfRangeException(nameof(addr), message: AddressRangeError);

Expand Down
Loading
Loading