Skip to content

Commit b445e02

Browse files
committed
Expose multi-byte reads and writes.
1 parent 7366dbe commit b445e02

5 files changed

Lines changed: 150 additions & 3 deletions

File tree

src/BizHawk.Client.Common/Api/Classes/MemoryApi.cs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,74 @@ public void WriteByteRange(long addr, IReadOnlyList<byte> memoryblock, string do
284284
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
285285
}
286286

287+
public ushort[] ReadU16Range(long addr, int count, string domain = null)
288+
{
289+
var d = NamedDomainOrCurrent(domain);
290+
if (addr < 0) LogCallback($"Warning: Attempted reads on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(ReadByteRange)}()");
291+
var lastReqAddr = addr + count * sizeof(ushort) - 1;
292+
var indexAfterLast = Math.Min(Math.Max(-1L, lastReqAddr), d.Size - 1L) + 1L;
293+
var iSrc = Math.Min(Math.Max(0L, addr), d.Size);
294+
var iDst = iSrc - addr;
295+
var values = new ushort[(indexAfterLast - iSrc) / sizeof(ushort)];
296+
if (iSrc < indexAfterLast) using (d.EnterExit()) d.BulkPeekUshort(iSrc.RangeToExclusive(indexAfterLast), _isBigEndian, values);
297+
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted reads on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(ReadByteRange)}()");
298+
if (values.Length == count) return values;
299+
var newValues = new ushort[count];
300+
if (values.Length is not 0) Array.Copy(sourceArray: values, sourceIndex: 0, destinationArray: newValues, destinationIndex: iDst, length: values.Length);
301+
return newValues;
302+
}
303+
304+
public void WriteU16Range(long addr, Span<ushort> memoryblock, string domain = null)
305+
{
306+
var d = NamedDomainOrCurrent(domain);
307+
if (!d.Writable)
308+
{
309+
LogCallback($"Error: the domain {d.Name} is not writable");
310+
return;
311+
}
312+
if (addr < 0) LogCallback($"Warning: Attempted writes on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
313+
var lastReqAddr = addr + memoryblock.Length * sizeof(ushort) - 1;
314+
var indexAfterLast = Math.Min(Math.Max(-1L, lastReqAddr), d.Size - 1L) + 1L;
315+
var iDst = Math.Min(Math.Max(0L, addr), d.Size);
316+
var iSrc = checked((int) (iDst - addr));
317+
if (iDst < indexAfterLast) using (d.EnterExit()) d.BulkPokeUshort(iDst, _isBigEndian, memoryblock.Slice(iSrc));
318+
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
319+
}
320+
321+
public uint[] ReadU32Range(long addr, int count, string domain = null)
322+
{
323+
var d = NamedDomainOrCurrent(domain);
324+
if (addr < 0) LogCallback($"Warning: Attempted reads on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(ReadByteRange)}()");
325+
var lastReqAddr = addr + count * sizeof(uint) - 1;
326+
var indexAfterLast = Math.Min(Math.Max(-1L, lastReqAddr), d.Size - 1L) + 1L;
327+
var iSrc = Math.Min(Math.Max(0L, addr), d.Size);
328+
var iDst = iSrc - addr;
329+
var values = new uint[(indexAfterLast - iSrc) / sizeof(uint)];
330+
if (iSrc < indexAfterLast) using (d.EnterExit()) d.BulkPeekUint(iSrc.RangeToExclusive(indexAfterLast), _isBigEndian, values);
331+
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted reads on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(ReadByteRange)}()");
332+
if (values.Length == count) return values;
333+
var newValues = new uint[count];
334+
if (values.Length is not 0) Array.Copy(sourceArray: values, sourceIndex: 0, destinationArray: newValues, destinationIndex: iDst, length: values.Length);
335+
return newValues;
336+
}
337+
338+
public void WriteU32Range(long addr, Span<uint> memoryblock, string domain = null)
339+
{
340+
var d = NamedDomainOrCurrent(domain);
341+
if (!d.Writable)
342+
{
343+
LogCallback($"Error: the domain {d.Name} is not writable");
344+
return;
345+
}
346+
if (addr < 0) LogCallback($"Warning: Attempted writes on addresses {addr}..-1 outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
347+
var lastReqAddr = addr + memoryblock.Length * sizeof(uint) - 1;
348+
var indexAfterLast = Math.Min(Math.Max(-1L, lastReqAddr), d.Size - 1L) + 1L;
349+
var iDst = Math.Min(Math.Max(0L, addr), d.Size);
350+
var iSrc = checked((int) (iDst - addr));
351+
if (iDst < indexAfterLast) using (d.EnterExit()) d.BulkPokeUint(iDst, _isBigEndian, memoryblock.Slice(iSrc));
352+
if (lastReqAddr >= d.Size) LogCallback($"Warning: Attempted writes on addresses {d.Size}..{lastReqAddr} outside range of domain {d.Name} in {nameof(WriteByteRange)}()");
353+
}
354+
287355
public float ReadFloat(long addr, string domain = null)
288356
{
289357
var d = NamedDomainOrCurrent(domain);

src/BizHawk.Client.Common/Api/Interfaces/IMemoryApi.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ public interface IMemoryApi : IExternalApi
3333
void WriteByteRange(long addr, IReadOnlyList<byte> memoryblock, string domain = null);
3434
void WriteFloat(long addr, float value, string domain = null);
3535

36+
ushort[] ReadU16Range(long addr, int count, string domain = null);
37+
uint[] ReadU32Range(long addr, int count, string domain = null);
38+
void WriteU16Range(long addr, Span<ushort> memoryblock, string domain = null);
39+
void WriteU32Range(long addr, Span<uint> memoryblock, string domain = null);
40+
3641
void WriteS8(long addr, int value, string domain = null);
3742
void WriteS16(long addr, int value, string domain = null);
3843
void WriteS24(long addr, int value, string domain = null);

src/BizHawk.Client.Common/lua/CommonLibs/MemoryLuaLibrary.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,38 @@ public void WriteBytesAsDict(LuaTable addrMap, string domain = null)
129129
}
130130
}
131131

132+
[LuaMethodExample("local values = memory.read_u16_le_as_array(0x100, 30, \"WRAM\");")]
133+
[LuaMethod("read_u16_le_as_array", "Reads count 16-bit values starting at addr into an array-like table (1-indexed).")]
134+
public LuaTable ReadUshortsAsArray(long addr, int count, string domain = null)
135+
{
136+
APIs.Memory.SetBigEndian(false);
137+
return _th.ListToTable(APIs.Memory.ReadU16Range(addr, count, domain));
138+
}
139+
140+
[LuaMethodExample("memory.write_u16_le_as_array(0x100, { 0xABCD, 0x1234 });")]
141+
[LuaMethod("write_u16_le_as_array", "Writes sequential 16-byte values starting at addr.")]
142+
public void WriteUshortsAsArray(long addr, LuaTable values, string domain = null)
143+
{
144+
APIs.Memory.SetBigEndian(false);
145+
APIs.Memory.WriteU16Range(addr, _th.EnumerateValues<long>(values).Select(l => (ushort) l).ToArray(), domain);
146+
}
147+
148+
[LuaMethodExample("local values = memory.read_u32_le_as_array(0x100, 30, \"WRAM\");")]
149+
[LuaMethod("read_u32_le_as_array", "Reads count 32-bit values starting at addr into an array-like table (1-indexed).")]
150+
public LuaTable ReadUintsAsArray(long addr, int count, string domain = null)
151+
{
152+
APIs.Memory.SetBigEndian(false);
153+
return _th.ListToTable(APIs.Memory.ReadU32Range(addr, count, domain));
154+
}
155+
156+
[LuaMethodExample("memory.write_u32_le_as_array(0x100, { 0xABCD, 0x1234 });")]
157+
[LuaMethod("write_u32_le_as_array", "Writes sequential 32-byte values starting at addr.")]
158+
public void WriteUintsAsArray(long addr, LuaTable values, string domain = null)
159+
{
160+
APIs.Memory.SetBigEndian(false);
161+
APIs.Memory.WriteU32Range(addr, _th.EnumerateValues<long>(values).Select(l => (uint) l).ToArray(), domain);
162+
}
163+
132164
[LuaMethodExample("""
133165
memory.write_bytes_as_binary_string(0x100, string.pack("<i4f", 1234, 456.789), "WRAM")
134166
memory.write_bytes_as_binary_string(0x108, "\xFE\xED", "WRAM")

src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@ public virtual void BulkPeekByte(Range<long> addresses, byte[] values)
101101
}
102102
}
103103

104+
public virtual void BulkPokeByte(long startAddress, Span<byte> values)
105+
{
106+
if (values == null) throw new ArgumentNullException(paramName: nameof(values));
107+
108+
using (this.EnterExit())
109+
{
110+
long address = startAddress;
111+
for (var i = 0; i < values.Length; i++, address++)
112+
{
113+
PokeByte(address, values[i]);
114+
}
115+
}
116+
}
117+
104118
public virtual void BulkPeekUshort(Range<long> addresses, bool bigEndian, ushort[] values)
105119
{
106120
if (addresses is null) throw new ArgumentNullException(paramName: nameof(addresses));
@@ -116,6 +130,20 @@ public virtual void BulkPeekUshort(Range<long> addresses, bool bigEndian, ushort
116130
}
117131
}
118132

133+
public virtual void BulkPokeUshort(long startAddress, bool bigEndian, Span<ushort> values)
134+
{
135+
if (values == null) throw new ArgumentNullException(paramName: nameof(values));
136+
137+
using (this.EnterExit())
138+
{
139+
long address = startAddress;
140+
for (var i = 0; i < values.Length; i++, address += sizeof(ushort))
141+
{
142+
PokeUshort(address, values[i], bigEndian);
143+
}
144+
}
145+
}
146+
119147
public virtual void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[] values)
120148
{
121149
if (addresses is null) throw new ArgumentNullException(paramName: nameof(addresses));
@@ -131,6 +159,20 @@ public virtual void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[] v
131159
}
132160
}
133161

162+
public virtual void BulkPokeUint(long startAddress, bool bigEndian, Span<uint> values)
163+
{
164+
if (values == null) throw new ArgumentNullException(paramName: nameof(values));
165+
166+
using (this.EnterExit())
167+
{
168+
long address = startAddress;
169+
for (var i = 0; i < values.Length; i++, address += sizeof(uint))
170+
{
171+
PokeUint(address, values[i], bigEndian);
172+
}
173+
}
174+
}
175+
134176
public virtual void SendCheatToCore(int addr, byte value, int compare, int compare_type) { }
135177

136178
/// <summary>

src/BizHawk.Emulation.Cores/Waterbox/WaterboxMemoryDomain.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ public override void BulkPeekByte(Range<long> addresses, byte[] values)
336336
_read8.Access((IntPtr)p, addresses.Start, (long)addresses.Count());
337337
}
338338

339-
public void BulkPokeByte(long addr, Span<byte> values)
339+
public override void BulkPokeByte(long addr, Span<byte> values)
340340
{
341341
if ((ulong)addr + (ulong)values.Length > (ulong)Size || addr < 0) throw new ArgumentOutOfRangeException(nameof(addr));
342342

@@ -354,7 +354,7 @@ public override void BulkPeekUshort(Range<long> addresses, bool bigEndian, ushor
354354
_read16.Access((IntPtr)p, addresses.Start, (long)addresses.Count() / sizeof(ushort));
355355
}
356356

357-
public void BulkPokeUshort(long addr, Span<ushort> values)
357+
public override void BulkPokeUshort(long addr, bool bigEndian, Span<ushort> values)
358358
{
359359
if ((ulong)addr + (ulong)values.Length * sizeof(ushort) > (ulong)Size || addr < 0) throw new ArgumentOutOfRangeException(nameof(addr));
360360

@@ -372,7 +372,7 @@ public override void BulkPeekUint(Range<long> addresses, bool bigEndian, uint[]
372372
_read32.Access((IntPtr)p, addresses.Start, (long)addresses.Count() / sizeof(uint));
373373
}
374374

375-
public void BulkPokeUint(long addr, Span<uint> values)
375+
public override void BulkPokeUint(long addr, bool bigEndian, Span<uint> values)
376376
{
377377
if ((ulong)addr + (ulong)values.Length * sizeof(uint) > (ulong)Size || addr < 0) throw new ArgumentOutOfRangeException(nameof(addr));
378378

0 commit comments

Comments
 (0)