diff --git a/Assets/dll/encore.dll b/Assets/dll/encore.dll index 61e416ec764..7dc2bddced6 100644 Binary files a/Assets/dll/encore.dll and b/Assets/dll/encore.dll differ diff --git a/encore/encore b/encore/encore index f77d21aac0f..58d290ba517 160000 --- a/encore/encore +++ b/encore/encore @@ -1 +1 @@ -Subproject commit f77d21aac0fd3a4f33e071ba21e517cc31fec774 +Subproject commit 58d290ba517421e6fa93e5069ea54af3e582357a diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/3DS/Encore.IMemoryDomains.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/3DS/Encore.IMemoryDomains.cs index 0665eb230c5..206718dcfb1 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/3DS/Encore.IMemoryDomains.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/3DS/Encore.IMemoryDomains.cs @@ -1,5 +1,8 @@ +using System.Buffers.Binary; using System.Collections.Generic; +using System.Runtime.InteropServices; +using BizHawk.Common; using BizHawk.Emulation.Common; namespace BizHawk.Emulation.Cores.Consoles.Nintendo.N3DS @@ -28,6 +31,9 @@ private void InitMemoryDomains() domains.Add(_n3dsExRam); } + // extra domain for virtual memory (important for dealing with pointers!) + domains.Add(new EncoreMMU(_context)); + _memoryDomains = new MemoryDomainList(domains); _serviceProvider.Register(_memoryDomains); WireMemoryDomains(); @@ -47,5 +53,223 @@ void WireDomain(LibEncore.MemoryRegion region, MemoryDomainIntPtr domain) WireDomain(LibEncore.MemoryRegion.DSP, _dspRam); WireDomain(LibEncore.MemoryRegion.N3DS, _n3dsExRam); } + + private class EncoreMMU : MemoryDomain + { + private const uint ENCORE_PAGE_SIZE = 0x1000; + private const uint ENCORE_PAGE_MASK = ENCORE_PAGE_SIZE - 1; + + private readonly IntPtr _context; + + public EncoreMMU(IntPtr context) + { + Name = "System Bus"; + Size = 1L << 32; + WordSize = 4; + EndianType = Endian.Little; + Writable = true; + _context = context; + } + + private Span GetPage(uint addr) + { + var pagePointer = _core.Encore_GetPagePointer(_context, addr); + return pagePointer == IntPtr.Zero ? [ ] : Util.UnsafeSpanFromPointer(pagePointer, (int)(ENCORE_PAGE_SIZE - (addr & ENCORE_PAGE_MASK))); + } + + public override byte PeekByte(long addr) + { + var page = GetPage((uint)addr); + return page.IsEmpty ? (byte)0 : page[0]; + } + + public override ushort PeekUshort(long addr, bool bigEndian) + { + // if we cross a page boundary, we need to read multiple pages + if ((addr & ENCORE_PAGE_MASK) > ENCORE_PAGE_MASK - 1) + { + return base.PeekUshort(addr, bigEndian); + } + + var page = GetPage((uint)addr); + if (page.IsEmpty) + { + return 0; + } + + return bigEndian + ? BinaryPrimitives.ReadUInt16BigEndian(page) + : BinaryPrimitives.ReadUInt16LittleEndian(page); + } + + public override uint PeekUint(long addr, bool bigEndian) + { + // if we cross a page boundary, we need to read multiple pages + if ((addr & ENCORE_PAGE_MASK) > ENCORE_PAGE_MASK - 3) + { + return base.PeekUint(addr, bigEndian); + } + + var page = GetPage((uint)addr); + if (page.IsEmpty) + { + return 0; + } + + return bigEndian + ? BinaryPrimitives.ReadUInt32BigEndian(page) + : BinaryPrimitives.ReadUInt32LittleEndian(page); + } + + public override void PokeByte(long addr, byte val) + { + var page = GetPage((uint)addr); + if (page.IsEmpty) + { + return; + } + + page[0] = val; + } + + public override void PokeUshort(long addr, ushort val, bool bigEndian) + { + // if we cross a page boundary, we need to write to multiple pages + if ((addr & ENCORE_PAGE_MASK) > ENCORE_PAGE_MASK - 1) + { + base.PokeUshort(addr, val, bigEndian); + return; + } + + var page = GetPage((uint)addr); + if (page.IsEmpty) + { + return; + } + + if (bigEndian) + { + BinaryPrimitives.WriteUInt16BigEndian(page, val); + } + else + { + BinaryPrimitives.WriteUInt16LittleEndian(page, val); + } + } + + public override void PokeUint(long addr, uint val, bool bigEndian) + { + // if we cross a page boundary, we need to write to multiple pages + if ((addr & ENCORE_PAGE_MASK) > ENCORE_PAGE_MASK - 3) + { + base.PokeUint(addr, val, bigEndian); + return; + } + + var page = GetPage((uint)addr); + if (page.IsEmpty) + { + return; + } + + if (bigEndian) + { + BinaryPrimitives.WriteUInt32BigEndian(page, val); + } + else + { + BinaryPrimitives.WriteUInt32LittleEndian(page, val); + } + } + + private void BulkPeekByte(uint startAddr, Span values) + { + while (!values.IsEmpty) + { + var page = GetPage(startAddr); + var numBytes = Math.Min(values.Length, (int)(ENCORE_PAGE_SIZE - (startAddr & ENCORE_PAGE_MASK))); + if (page.IsEmpty) + { + values[..numBytes].Clear(); + } + else + { + page[..numBytes].CopyTo(values); + } + + values = values[numBytes..]; + startAddr += (uint)numBytes; + } + } + + public override void BulkPeekByte(Range 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"); + } + + BulkPeekByte((uint)addresses.Start, values); + } + + public override void BulkPeekUshort(Range 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 (values.LongLength * 2 != 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"); + } + + BulkPeekByte((uint)addresses.Start, MemoryMarshal.AsBytes(values.AsSpan())); + + if (!bigEndian) + { + for (var i = 0; i < values.Length; i++) + { + values[i] = BinaryPrimitives.ReverseEndianness(values[i]); + } + } + } + + public override void BulkPeekUint(Range 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 & 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) + { + // 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"); + } + + BulkPeekByte((uint)addresses.Start, MemoryMarshal.AsBytes(values.AsSpan())); + + if (!bigEndian) + { + for (var i = 0; i < values.Length; i++) + { + values[i] = BinaryPrimitives.ReverseEndianness(values[i]); + } + } + } + } } } \ No newline at end of file diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/3DS/LibEncore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/3DS/LibEncore.cs index 5f3f596fd93..d6ab9fabbd3 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/3DS/LibEncore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/3DS/LibEncore.cs @@ -120,9 +120,6 @@ public abstract IntPtr Encore_CreateContext( [BizImport(cc)] public abstract void Encore_Reset(IntPtr context); - [BizImport(cc)] - public abstract void Encore_GetVideoVirtualDimensions(IntPtr context, out int width, out int height); - [BizImport(cc)] public abstract void Encore_GetVideoBufferDimensions(IntPtr context, out int width, out int height); @@ -158,6 +155,9 @@ public enum MemoryRegion [BizImport(cc)] public abstract void Encore_GetMemoryRegion(IntPtr context, MemoryRegion region, out IntPtr ptr, out int size); + [BizImport(cc)] + public abstract IntPtr Encore_GetPagePointer(IntPtr context, uint addr); + [BizImport(cc)] public abstract void Encore_GetTouchScreenLayout(IntPtr context, out int x, out int y, out int width, out int height, out bool rotated, out bool enabled); }