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
Binary file modified Assets/dll/melonDS.wbx.zst
Binary file not shown.
68 changes: 68 additions & 0 deletions src/BizHawk.Client.Common/Api/Classes/MemoryApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,74 @@ public void WriteByteRange(long addr, IReadOnlyList<byte> memoryblock, string do
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
}

public 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(ReadByteRange)}()");
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;
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(ReadByteRange)}()");
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(WriteByteRange)}()");
var lastReqAddr = addr + memoryblock.Length * sizeof(ushort) - 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));
if (iDst < indexAfterLast) using (d.EnterExit()) d.BulkPokeUshort(iDst, _isBigEndian, memoryblock.Slice(iSrc));
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
}

public 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(ReadByteRange)}()");
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;
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(ReadByteRange)}()");
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)
{
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(WriteByteRange)}()");
var lastReqAddr = addr + memoryblock.Length * sizeof(uint) - 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));
if (iDst < indexAfterLast) using (d.EnterExit()) d.BulkPokeUint(iDst, _isBigEndian, memoryblock.Slice(iSrc));
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
}

public float ReadFloat(long addr, string domain = null)
{
var d = NamedDomainOrCurrent(domain);
Expand Down
5 changes: 5 additions & 0 deletions src/BizHawk.Client.Common/Api/Interfaces/IMemoryApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public interface IMemoryApi : IExternalApi
void WriteByteRange(long addr, IReadOnlyList<byte> memoryblock, string domain = null);
void WriteFloat(long addr, float value, string domain = null);

ushort[] ReadU16Range(long addr, int count, string domain = null);
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);

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
32 changes: 32 additions & 0 deletions src/BizHawk.Client.Common/lua/CommonLibs/MemoryLuaLibrary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,38 @@ public void WriteBytesAsDict(LuaTable addrMap, string domain = null)
}
}

[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_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("""
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
69 changes: 45 additions & 24 deletions src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,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("Invalid length of values array");
}
if (addresses.Count() > (uint)values.Length) throw new ArgumentException($"Length of {nameof(values)} must be at least {nameof(addresses)} count.", nameof(values));

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

public virtual void BulkPokeByte(long startAddress, Span<byte> values)
{
if (values == null) throw new ArgumentNullException(paramName: nameof(values));

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 & 1) != 0 || (end & 1) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");
if (addresses.Count() > (uint)values.Length * sizeof(ushort)) throw new ArgumentException($"Length of {nameof(values)} must be at least {nameof(addresses)} count.", nameof(values));

if (values.LongLength * 2 != end - start)
using (this.EnterExit())
{
// 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");
long address = addresses.Start;
for (var i = 0; i < values.Length; i++, address += sizeof(ushort))
values[i] = PeekUshort(address, bigEndian);
}
}

public virtual void BulkPokeUshort(long startAddress, bool bigEndian, Span<ushort> values)
{
if (values == null) throw new ArgumentNullException(paramName: nameof(values));

using (this.EnterExit())
{
for (var i = 0; i < values.Length; i++, start += 2)
values[i] = PeekUshort(start, bigEndian);
long address = startAddress;
for (var i = 0; i < values.Length; i++, address += sizeof(ushort))
{
PokeUshort(address, values[i], bigEndian);
}
}
}

Expand All @@ -133,22 +149,27 @@ public virtual void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[] v
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 & 3) != 0 || (end & 3) != 0)
throw new InvalidOperationException("The API contract doesn't define what to do for unaligned reads and writes!");
if (addresses.Count() > (uint)values.Length * sizeof(uint)) throw new ArgumentException($"Length of {nameof(values)} must be at least {nameof(addresses)} count.", nameof(values));

if (values.LongLength * 4 != end - start)
using (this.EnterExit())
{
// 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");
var address = addresses.Start;
for (var i = 0; i < values.Length; i++, address += sizeof(uint))
values[i] = PeekUint(address, bigEndian);
}
}

public virtual void BulkPokeUint(long startAddress, bool bigEndian, Span<uint> values)
{
if (values == null) throw new ArgumentNullException(paramName: nameof(values));

using (this.EnterExit())
{
for (var i = 0; i < values.Length; i++, start += 4)
values[i] = PeekUint(start, bigEndian);
long address = startAddress;
for (var i = 0; i < values.Length; i++, address += sizeof(uint))
{
PokeUint(address, values[i], bigEndian);
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions src/BizHawk.Emulation.Cores/Waterbox/LibWaterboxCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ public enum MemoryDomainFlags : long
/// If true, Data is a function to call and not a pointer
/// </summary>
FunctionHook = 1024,
/// <summary>
/// If true, Data is a list of functions; function(s) may be null
/// </summary>
SizedFunctionHooks = 2048,
}

[StructLayout(LayoutKind.Sequential)]
Expand Down
Loading