diff --git a/BizHawk.Common/BizInvoke/MemoryBlock.cs b/BizHawk.Common/BizInvoke/MemoryBlock.cs
index e0437898fc..3407960818 100644
--- a/BizHawk.Common/BizInvoke/MemoryBlock.cs
+++ b/BizHawk.Common/BizInvoke/MemoryBlock.cs
@@ -186,6 +186,23 @@ namespace BizHawk.Common.BizInvoke
ProtectAll();
}
+ ///
+ /// take a hash of the current full contents of the block, including unreadable areas
+ ///
+ ///
+ public byte[] FullHash()
+ {
+ if (!Active)
+ throw new InvalidOperationException("Not active");
+ // temporarily switch the entire block to `R`
+ Kernel32.MemoryProtection old;
+ if (!Kernel32.VirtualProtect(Z.UU(Start), Z.UU(Size), Kernel32.MemoryProtection.READONLY, out old))
+ throw new InvalidOperationException("VirtualProtect() returned FALSE!");
+ var ret = WaterboxUtils.Hash(GetStream(Start, Size, false));
+ ProtectAll();
+ return ret;
+ }
+
private static Kernel32.MemoryProtection GetKernelMemoryProtectionValue(Protection prot)
{
Kernel32.MemoryProtection p;
diff --git a/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs b/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs
index 5498331740..000d762995 100644
--- a/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs
@@ -92,7 +92,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
try
{
_disposeList.Add(_base);
- AddMemoryBlock(_base);
+ AddMemoryBlock(_base, "elf");
_base.Activate();
_base.Protect(_base.Start, _base.Size, MemoryBlock.Protection.RW);
@@ -122,7 +122,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
_heap.Memory.Activate();
end = _heap.Memory.End;
_disposeList.Add(_heap);
- AddMemoryBlock(_heap.Memory);
+ AddMemoryBlock(_heap.Memory, "sbrk - heap");
}
if (sealedheapsize > 0)
@@ -131,7 +131,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
_sealedheap.Memory.Activate();
end = _sealedheap.Memory.End;
_disposeList.Add(_sealedheap);
- AddMemoryBlock(_sealedheap.Memory);
+ AddMemoryBlock(_sealedheap.Memory, "sealed-heap");
}
if (invisibleheapsize > 0)
@@ -140,7 +140,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
_invisibleheap.Memory.Activate();
end = _invisibleheap.Memory.End;
_disposeList.Add(_invisibleheap);
- AddMemoryBlock(_invisibleheap.Memory);
+ AddMemoryBlock(_invisibleheap.Memory, "invisible-heap");
}
ConnectAllClibPatches();
diff --git a/BizHawk.Emulation.Cores/Waterbox/Heap.cs b/BizHawk.Emulation.Cores/Waterbox/Heap.cs
index 8d2d58b35d..26eb952972 100644
--- a/BizHawk.Emulation.Cores/Waterbox/Heap.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/Heap.cs
@@ -38,7 +38,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
Console.WriteLine("Created heap `{1}` at {0:x16}:{2:x16}", start, name, start + size);
}
- private void EnsureAlignment(int align)
+ private ulong EnsureAlignment(int align)
{
if (align > 1)
{
@@ -47,8 +47,9 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
throw new InvalidOperationException(string.Format("Failed to meet alignment {0} on heap {1}", align, Name));
}
- Used = newused;
+ return newused;
}
+ return Used;
}
public ulong Allocate(ulong size, int align)
@@ -56,15 +57,14 @@ namespace BizHawk.Emulation.Cores.Waterbox
if (Sealed)
throw new InvalidOperationException(string.Format("Attempt made to allocate from sealed heap {0}", Name));
- EnsureAlignment(align);
-
- ulong newused = Used + size;
+ ulong allocstart = EnsureAlignment(align);
+ ulong newused = allocstart + size;
if (newused > Memory.Size)
{
throw new InvalidOperationException(string.Format("Failed to allocate {0} bytes from heap {1}", size, Name));
}
- ulong ret = Memory.Start + Used;
- Memory.Protect(ret, newused - Used, MemoryBlock.Protection.RW);
+ ulong ret = Memory.Start + allocstart;
+ Memory.Protect(Memory.Start + Used, newused - Used, MemoryBlock.Protection.RW);
Used = newused;
Console.WriteLine($"Allocated {size} bytes on {Name}, utilization {Used}/{Memory.Size} ({100.0 * Used / Memory.Size:0.#}%)");
return ret;
@@ -91,7 +91,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
if (!Sealed)
{
bw.Write(Memory.XorHash);
- var ms = Memory.GetXorStream(Memory.Start, Used, false);
+ var ms = Memory.GetXorStream(Memory.Start, WaterboxUtils.AlignUp(Used), false);
ms.CopyTo(bw.BaseStream);
}
else
@@ -116,11 +116,12 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
throw new InvalidOperationException(string.Format("Hash did not match for heap {0}. Is this the same rom with the same SyncSettings?", Name));
}
+ var usedAligned = WaterboxUtils.AlignUp(used);
Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.None);
Memory.Protect(Memory.Start, used, MemoryBlock.Protection.RW);
- var ms = Memory.GetXorStream(Memory.Start, used, true);
- WaterboxUtils.CopySome(br.BaseStream, ms, (long)used);
+ var ms = Memory.GetXorStream(Memory.Start, usedAligned, true);
+ WaterboxUtils.CopySome(br.BaseStream, ms, (long)usedAligned);
Used = used;
}
else
diff --git a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs
index 90d78b15ea..913312ccd3 100644
--- a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs
@@ -1,359 +1,359 @@
-using BizHawk.Common;
-using BizHawk.Common.BizInvoke;
-using BizHawk.Emulation.Common;
-using PeNet;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.IO.Compression;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace BizHawk.Emulation.Cores.Waterbox
-{
- public class PeRunnerOptions
- {
- // string directory, string filename, ulong heapsize, ulong sealedheapsize, ulong invisibleheapsize
- ///
- /// path which the main executable and all associated libraries should be found
- ///
- public string Path { get; set; }
-
- ///
- /// filename of the main executable; expected to be in Path
- ///
- public string Filename { get; set; }
-
- ///
- /// how large the normal heap should be. it services sbrk calls
- /// can be 0, but sbrk calls will crash.
- ///
- public uint SbrkHeapSizeKB { get; set; }
-
- ///
- /// how large the sealed heap should be. it services special allocations that become readonly after init
- /// Must be > 0 and at least large enough to store argv and envp, and any alloc_sealed() calls
- ///
- public uint SealedHeapSizeKB { get; set; }
-
- ///
- /// how large the invisible heap should be. it services special allocations which are not savestated
- /// Must be > 0 and at least large enough for the internal vtables, and any alloc_invisible() calls
- ///
- public uint InvisibleHeapSizeKB { get; set; }
-
- ///
- /// how large the "plain" heap should be. it is savestated, and contains
- /// Must be > 0 and at least large enough for the internal pthread structure, and any alloc_plain() calls
- ///
- public uint PlainHeapSizeKB { get; set; }
-
- ///
- /// how large the mmap heap should be. it is savestated.
- /// can be 0, but mmap calls will crash.
- ///
- public uint MmapHeapSizeKB { get; set; }
-
- ///
- /// start address in memory
- ///
- public ulong StartAddress { get; set; } = PeRunner.CanonicalStart;
- }
-
- public class PeRunner : Swappable, IImportResolver, IBinaryStateable
- {
- ///
- /// serves as a standin for libpsxscl.so
- ///
- private class Psx
- {
- private readonly PeRunner _parent;
- private readonly List _traps = new List();
-
- public Psx(PeRunner parent)
- {
- _parent = parent;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public struct PsxContext
- {
- public int Size;
- public int Options;
- public IntPtr SyscallVtable;
- public IntPtr LdsoVtable;
- public IntPtr PsxVtable;
- public uint SysIdx;
- public uint LibcIdx;
- public IntPtr PthreadSurrogate;
- public IntPtr PthreadCreate;
- public IntPtr DoGlobalCtors;
- public IntPtr DoGlobalDtors;
- }
-
- private void PopulateVtable(string moduleName, ICollection entries, IntPtr table)
- {
- var imports = _parent._exports[moduleName];
- var pointers = entries.Select(e =>
- {
- var ptr = imports.Resolve(e);
- if (ptr == IntPtr.Zero)
- {
- var s = string.Format("Trapped on unimplemented function {0}:{1}", moduleName, e);
- Action del = () =>
- {
- Console.WriteLine(s);
- throw new InvalidOperationException(s);
- };
- _traps.Add(del);
- ptr = CallingConventionAdapters.Waterbox.GetFunctionPointerForDelegate(del);
- }
- return ptr;
- }).ToArray();
- Marshal.Copy(pointers, 0, table, pointers.Length);
- }
-
- ///
- /// called by the PeRunner to reset pointers after a loadsave
- ///
- public void ReloadVtables()
- {
- _traps.Clear();
-
- PopulateVtable("__syscalls", Enumerable.Range(0, 340).Select(i => "n" + i).ToList(), _syscallVtable);
- PopulateVtable("__syscalls", new[] // ldso
- {
- "dladdr", "dlinfo", "dlsym", "dlopen", "dlclose", "dlerror", "reset_tls"
- }, _ldsoVtable);
- PopulateVtable("__syscalls", new[] // psx
- {
- "start_main", "convert_thread", "unmapself", "log_output", "pthread_self"
- }, _psxVtable);
- /*unsafe
- {
- var ptr = (IntPtr*)_psxVtable;
- Console.WriteLine("AWESOMES: " + ptr[0]);
- }*/
- }
-
- private IntPtr _syscallVtable;
- private IntPtr _ldsoVtable;
- private IntPtr _psxVtable;
-
- private IntPtr AllocVtable(int count)
- {
- return Z.US(_parent._invisibleheap.Allocate((ulong)(count * IntPtr.Size), 16));
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "__psx_init")]
- public int PsxInit(ref int argc, ref IntPtr argv, ref IntPtr envp, [In, Out]ref PsxContext context)
- {
- {
- // argc = 1, argv = ["foobar, NULL], envp = [NULL]
- argc = 1;
- var argArea = _parent._sealedheap.Allocate(32, 16);
- argv = Z.US(argArea);
- envp = Z.US(argArea + (uint)IntPtr.Size * 2);
- Marshal.WriteIntPtr(Z.US(argArea), Z.US(argArea + 24));
- Marshal.WriteInt64(Z.US(argArea + 24), 0x7261626f6f66);
- }
-
- context.SyscallVtable = _syscallVtable = AllocVtable(340);
- context.LdsoVtable = _ldsoVtable = AllocVtable(7);
- context.PsxVtable = _psxVtable = AllocVtable(5);
- // ctx comes from the native stack, where it could have any garbage in uninited fields
- context.SysIdx = 0;
- context.LibcIdx = 0;
- context.DoGlobalCtors = IntPtr.Zero;
- context.DoGlobalDtors = IntPtr.Zero;
-
- ReloadVtables();
-
- // TODO: we can't set these pointers 4 and preserve across session
- // until we find out where they get saved to and add a way to reset them
- /*var extraTable = CreateVtable("__syscalls", new[]
- {
- "pthread_surrogate", "pthread_create", "do_global_ctors", "do_global_dtors"
- });
- var tmp = new IntPtr[4];
- Marshal.Copy(extraTable, tmp, 0, 4);
- context.PthreadSurrogate = tmp[0];
- context.PthreadCreate = tmp[1];
- context.DoGlobalCtors = tmp[2];
- context.DoGlobalDtors = tmp[3];*/
-
- return 0; // success
- }
- }
-
- ///
- /// special emulator-functions
- ///
- private class Emu
- {
- private readonly PeRunner _parent;
- public Emu(PeRunner parent)
- {
- _parent = parent;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_sealed")]
- public IntPtr AllocSealed(UIntPtr size)
- {
- return Z.US(_parent._sealedheap.Allocate((ulong)size, 16));
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_invisible")]
- public IntPtr AllocInvisible(UIntPtr size)
- {
- return Z.US(_parent._invisibleheap.Allocate((ulong)size, 16));
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_plain")]
- public IntPtr AllocPlain(UIntPtr size)
- {
- return Z.US(_parent._plainheap.Allocate((ulong)size, 16));
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "_debug_puts")]
- public void DebugPuts(IntPtr s)
- {
- Console.WriteLine("_debug_puts:" + Marshal.PtrToStringAnsi(s));
- }
- }
-
- ///
- /// syscall emulation layer, as well as a bit of other stuff
- ///
- private class Syscalls
- {
- private readonly PeRunner _parent;
- public Syscalls(PeRunner parent)
- {
- _parent = parent;
- }
-
- private IntPtr _pthreadSelf;
-
- public void Init()
- {
- // as the inits are done in a defined order with a defined memory map,
- // we don't need to savestate _pthreadSelf, only its contents
- _pthreadSelf = Z.US(_parent._plainheap.Allocate(512, 1));
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "log_output")]
- public void DebugPuts(IntPtr s)
- {
- Console.WriteLine("_psx_log_output:" + Marshal.PtrToStringAnsi(s));
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n12")]
- public UIntPtr Brk(UIntPtr _p)
- {
- var heap = _parent._heap;
-
- var start = heap.Memory.Start;
- var end = start + heap.Used;
- var max = heap.Memory.End;
-
- var p = (ulong)_p;
-
- if (p < start || p > max)
- {
- // failure: return current break
- return Z.UU(end);
- }
- else if (p > end)
- {
- // increase size of heap
- heap.Allocate(p - end, 1);
- return Z.UU(p);
- }
- else if (p < end)
- {
- throw new InvalidOperationException("We don't support shrinking heaps");
- }
- else
- {
- // no change
- return Z.UU(end);
- }
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n16")]
- public int IoCtl(int fd, ulong req)
- {
- return 0; // sure it worked, honest
- }
-
- public struct Iovec
- {
- public IntPtr Base;
- public ulong Length;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n0")]
- public long Read(int fd, IntPtr buff, ulong count)
- {
- return 0;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n1")]
- public long Write(int fd, IntPtr buff, ulong count)
- {
- return (long)count;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n19")]
- public unsafe long Readv(int fd, Iovec* iov, int iovcnt)
- {
- return 0;
- }
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n20")]
- public unsafe long Writev(int fd, Iovec* iov, int iovcnt)
- {
- long ret = 0;
- for (int i = 0; i < iovcnt; i++)
- {
- ret += (long)iov[i].Length;
- }
- return ret;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n2")]
- public int Open(string path, int flags, int mode)
- {
- return -1;
- }
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n3")]
- public int Close(int fd)
- {
- return 0;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n4")]
- public int Stat(string path, IntPtr statbuf)
- {
- return -1;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n5")]
- public int Fstat(int fd, IntPtr statbuf)
- {
- return -1;
- }
-
- //[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- //private delegate int LibcStartMain(IntPtr main, int argc, IntPtr argv);
-
- //bool _firstTime = true;
-
- // aka __psx_init_frame (it's used elsewhere for thread setup)
- // in midipix, this just sets up a SEH frame and then calls musl's start_main
- [BizExport(CallingConvention.Cdecl, EntryPoint = "start_main")]
- public unsafe int StartMain(IntPtr main, int argc, IntPtr argv, IntPtr libc_start_main)
+using BizHawk.Common;
+using BizHawk.Common.BizInvoke;
+using BizHawk.Emulation.Common;
+using PeNet;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace BizHawk.Emulation.Cores.Waterbox
+{
+ public class PeRunnerOptions
+ {
+ // string directory, string filename, ulong heapsize, ulong sealedheapsize, ulong invisibleheapsize
+ ///
+ /// path which the main executable and all associated libraries should be found
+ ///
+ public string Path { get; set; }
+
+ ///
+ /// filename of the main executable; expected to be in Path
+ ///
+ public string Filename { get; set; }
+
+ ///
+ /// how large the normal heap should be. it services sbrk calls
+ /// can be 0, but sbrk calls will crash.
+ ///
+ public uint SbrkHeapSizeKB { get; set; }
+
+ ///
+ /// how large the sealed heap should be. it services special allocations that become readonly after init
+ /// Must be > 0 and at least large enough to store argv and envp, and any alloc_sealed() calls
+ ///
+ public uint SealedHeapSizeKB { get; set; }
+
+ ///
+ /// how large the invisible heap should be. it services special allocations which are not savestated
+ /// Must be > 0 and at least large enough for the internal vtables, and any alloc_invisible() calls
+ ///
+ public uint InvisibleHeapSizeKB { get; set; }
+
+ ///
+ /// how large the "plain" heap should be. it is savestated, and contains
+ /// Must be > 0 and at least large enough for the internal pthread structure, and any alloc_plain() calls
+ ///
+ public uint PlainHeapSizeKB { get; set; }
+
+ ///
+ /// how large the mmap heap should be. it is savestated.
+ /// can be 0, but mmap calls will crash.
+ ///
+ public uint MmapHeapSizeKB { get; set; }
+
+ ///
+ /// start address in memory
+ ///
+ public ulong StartAddress { get; set; } = PeRunner.CanonicalStart;
+ }
+
+ public class PeRunner : Swappable, IImportResolver, IBinaryStateable
+ {
+ ///
+ /// serves as a standin for libpsxscl.so
+ ///
+ private class Psx
+ {
+ private readonly PeRunner _parent;
+ private readonly List _traps = new List();
+
+ public Psx(PeRunner parent)
+ {
+ _parent = parent;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct PsxContext
+ {
+ public int Size;
+ public int Options;
+ public IntPtr SyscallVtable;
+ public IntPtr LdsoVtable;
+ public IntPtr PsxVtable;
+ public uint SysIdx;
+ public uint LibcIdx;
+ public IntPtr PthreadSurrogate;
+ public IntPtr PthreadCreate;
+ public IntPtr DoGlobalCtors;
+ public IntPtr DoGlobalDtors;
+ }
+
+ private void PopulateVtable(string moduleName, ICollection entries, IntPtr table)
+ {
+ var imports = _parent._exports[moduleName];
+ var pointers = entries.Select(e =>
+ {
+ var ptr = imports.Resolve(e);
+ if (ptr == IntPtr.Zero)
+ {
+ var s = string.Format("Trapped on unimplemented function {0}:{1}", moduleName, e);
+ Action del = () =>
+ {
+ Console.WriteLine(s);
+ throw new InvalidOperationException(s);
+ };
+ _traps.Add(del);
+ ptr = CallingConventionAdapters.Waterbox.GetFunctionPointerForDelegate(del);
+ }
+ return ptr;
+ }).ToArray();
+ Marshal.Copy(pointers, 0, table, pointers.Length);
+ }
+
+ ///
+ /// called by the PeRunner to reset pointers after a loadsave
+ ///
+ public void ReloadVtables()
+ {
+ _traps.Clear();
+
+ PopulateVtable("__syscalls", Enumerable.Range(0, 340).Select(i => "n" + i).ToList(), _syscallVtable);
+ PopulateVtable("__syscalls", new[] // ldso
+ {
+ "dladdr", "dlinfo", "dlsym", "dlopen", "dlclose", "dlerror", "reset_tls"
+ }, _ldsoVtable);
+ PopulateVtable("__syscalls", new[] // psx
+ {
+ "start_main", "convert_thread", "unmapself", "log_output", "pthread_self"
+ }, _psxVtable);
+ /*unsafe
+ {
+ var ptr = (IntPtr*)_psxVtable;
+ Console.WriteLine("AWESOMES: " + ptr[0]);
+ }*/
+ }
+
+ private IntPtr _syscallVtable;
+ private IntPtr _ldsoVtable;
+ private IntPtr _psxVtable;
+
+ private IntPtr AllocVtable(int count)
+ {
+ return Z.US(_parent._invisibleheap.Allocate((ulong)(count * IntPtr.Size), 16));
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "__psx_init")]
+ public int PsxInit(ref int argc, ref IntPtr argv, ref IntPtr envp, [In, Out]ref PsxContext context)
+ {
+ {
+ // argc = 1, argv = ["foobar, NULL], envp = [NULL]
+ argc = 1;
+ var argArea = _parent._sealedheap.Allocate(32, 16);
+ argv = Z.US(argArea);
+ envp = Z.US(argArea + (uint)IntPtr.Size * 2);
+ Marshal.WriteIntPtr(Z.US(argArea), Z.US(argArea + 24));
+ Marshal.WriteInt64(Z.US(argArea + 24), 0x7261626f6f66);
+ }
+
+ context.SyscallVtable = _syscallVtable = AllocVtable(340);
+ context.LdsoVtable = _ldsoVtable = AllocVtable(7);
+ context.PsxVtable = _psxVtable = AllocVtable(5);
+ // ctx comes from the native stack, where it could have any garbage in uninited fields
+ context.SysIdx = 0;
+ context.LibcIdx = 0;
+ context.DoGlobalCtors = IntPtr.Zero;
+ context.DoGlobalDtors = IntPtr.Zero;
+
+ ReloadVtables();
+
+ // TODO: we can't set these pointers 4 and preserve across session
+ // until we find out where they get saved to and add a way to reset them
+ /*var extraTable = CreateVtable("__syscalls", new[]
+ {
+ "pthread_surrogate", "pthread_create", "do_global_ctors", "do_global_dtors"
+ });
+ var tmp = new IntPtr[4];
+ Marshal.Copy(extraTable, tmp, 0, 4);
+ context.PthreadSurrogate = tmp[0];
+ context.PthreadCreate = tmp[1];
+ context.DoGlobalCtors = tmp[2];
+ context.DoGlobalDtors = tmp[3];*/
+
+ return 0; // success
+ }
+ }
+
+ ///
+ /// special emulator-functions
+ ///
+ private class Emu
+ {
+ private readonly PeRunner _parent;
+ public Emu(PeRunner parent)
+ {
+ _parent = parent;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_sealed")]
+ public IntPtr AllocSealed(UIntPtr size)
+ {
+ return Z.US(_parent._sealedheap.Allocate((ulong)size, 16));
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_invisible")]
+ public IntPtr AllocInvisible(UIntPtr size)
+ {
+ return Z.US(_parent._invisibleheap.Allocate((ulong)size, 16));
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "alloc_plain")]
+ public IntPtr AllocPlain(UIntPtr size)
+ {
+ return Z.US(_parent._plainheap.Allocate((ulong)size, 16));
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "_debug_puts")]
+ public void DebugPuts(IntPtr s)
+ {
+ Console.WriteLine("_debug_puts:" + Marshal.PtrToStringAnsi(s));
+ }
+ }
+
+ ///
+ /// syscall emulation layer, as well as a bit of other stuff
+ ///
+ private class Syscalls
+ {
+ private readonly PeRunner _parent;
+ public Syscalls(PeRunner parent)
+ {
+ _parent = parent;
+ }
+
+ private IntPtr _pthreadSelf;
+
+ public void Init()
+ {
+ // as the inits are done in a defined order with a defined memory map,
+ // we don't need to savestate _pthreadSelf, only its contents
+ _pthreadSelf = Z.US(_parent._plainheap.Allocate(512, 1));
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "log_output")]
+ public void DebugPuts(IntPtr s)
+ {
+ Console.WriteLine("_psx_log_output:" + Marshal.PtrToStringAnsi(s));
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n12")]
+ public UIntPtr Brk(UIntPtr _p)
+ {
+ var heap = _parent._heap;
+
+ var start = heap.Memory.Start;
+ var end = start + heap.Used;
+ var max = heap.Memory.End;
+
+ var p = (ulong)_p;
+
+ if (p < start || p > max)
+ {
+ // failure: return current break
+ return Z.UU(end);
+ }
+ else if (p > end)
+ {
+ // increase size of heap
+ heap.Allocate(p - end, 1);
+ return Z.UU(p);
+ }
+ else if (p < end)
+ {
+ throw new InvalidOperationException("We don't support shrinking heaps");
+ }
+ else
+ {
+ // no change
+ return Z.UU(end);
+ }
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n16")]
+ public int IoCtl(int fd, ulong req)
+ {
+ return 0; // sure it worked, honest
+ }
+
+ public struct Iovec
+ {
+ public IntPtr Base;
+ public ulong Length;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n0")]
+ public long Read(int fd, IntPtr buff, ulong count)
+ {
+ return 0;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n1")]
+ public long Write(int fd, IntPtr buff, ulong count)
+ {
+ return (long)count;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n19")]
+ public unsafe long Readv(int fd, Iovec* iov, int iovcnt)
+ {
+ return 0;
+ }
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n20")]
+ public unsafe long Writev(int fd, Iovec* iov, int iovcnt)
+ {
+ long ret = 0;
+ for (int i = 0; i < iovcnt; i++)
+ {
+ ret += (long)iov[i].Length;
+ }
+ return ret;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n2")]
+ public int Open(string path, int flags, int mode)
+ {
+ return -1;
+ }
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n3")]
+ public int Close(int fd)
+ {
+ return 0;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n4")]
+ public int Stat(string path, IntPtr statbuf)
+ {
+ return -1;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n5")]
+ public int Fstat(int fd, IntPtr statbuf)
+ {
+ return -1;
+ }
+
+ //[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ //private delegate int LibcStartMain(IntPtr main, int argc, IntPtr argv);
+
+ //bool _firstTime = true;
+
+ // aka __psx_init_frame (it's used elsewhere for thread setup)
+ // in midipix, this just sets up a SEH frame and then calls musl's start_main
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "start_main")]
+ public unsafe int StartMain(IntPtr main, int argc, IntPtr argv, IntPtr libc_start_main)
{
//var del = (LibcStartMain)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libc_start_main, typeof(LibcStartMain));
// this will init, and then call user main, and then call exit()
@@ -365,533 +365,533 @@ namespace BizHawk.Emulation.Cores.Waterbox
//if (_firstTime)
//{
//_firstTime = false;
- throw new InvalidOperationException("This shouldn't be called");
- //}
- //else
- //{
- // return 0;
- //}
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_self")]
- public IntPtr PthreadSelf()
- {
- return _pthreadSelf;
- }
-
- /*[BizExport(CallingConvention.Cdecl, EntryPoint = "convert_thread")]
- public void ConvertThread()
- {
-
- }*/
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n218")]
- public long SetTidAddress(IntPtr address)
- {
- return 8675309;
- }
-
- [StructLayout(LayoutKind.Sequential)]
- public class TimeSpec
- {
- public long Seconds;
- public long NanoSeconds;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n228")]
- public int SysClockGetTime(int which, [In, Out] TimeSpec time)
- {
- time.Seconds = 1495889068;
- time.NanoSeconds = 0;
- return 0;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n9")]
- public IntPtr MMap(IntPtr address, UIntPtr size, int prot, int flags, int fd, IntPtr offs)
- {
- if (address != IntPtr.Zero)
- return Z.SS(-1);
- MemoryBlock.Protection mprot;
- switch (prot)
- {
- case 0: mprot = MemoryBlock.Protection.None; break;
- default:
- case 6: // W^X
- case 7: // W^X
- case 4: // exec only????
- case 2: return Z.SS(-1); // write only????
- case 3: mprot = MemoryBlock.Protection.RW; break;
- case 1: mprot = MemoryBlock.Protection.R; break;
- case 5: mprot = MemoryBlock.Protection.RX; break;
- }
- if ((flags & 0x20) == 0)
- {
- // MAP_ANONYMOUS is required
- return Z.SS(-1);
- }
- if ((flags & 0xf00) != 0)
- {
- // various unsupported flags
- return Z.SS(-1);
- }
-
- var ret = _parent._mmapheap.Map((ulong)size, mprot);
- return ret == 0 ? Z.SS(-1) : Z.US(ret);
- }
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n25")]
- public IntPtr MRemap(UIntPtr oldAddress, UIntPtr oldSize,
- UIntPtr newSize, int flags)
- {
- if ((flags & 2) != 0)
- {
- // don't support MREMAP_FIXED
- return Z.SS(-1);
- }
- var ret = _parent._mmapheap.Remap((ulong)oldAddress, (ulong)oldSize, (ulong)newSize,
- (flags & 1) != 0);
- return ret == 0 ? Z.SS(-1) : Z.US(ret);
- }
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n11")]
- public int MUnmap(UIntPtr address, UIntPtr size)
- {
- return _parent._mmapheap.Unmap((ulong)address, (ulong)size) ? 0 : -1;
- }
-
- [BizExport(CallingConvention.Cdecl, EntryPoint = "n10")]
- public int MProtect(UIntPtr address, UIntPtr size, int prot)
- {
- MemoryBlock.Protection mprot;
- switch (prot)
- {
- case 0: mprot = MemoryBlock.Protection.None; break;
- default:
- case 6: // W^X
- case 7: // W^X
- case 4: // exec only????
- case 2: return -1; // write only????
- case 3: mprot = MemoryBlock.Protection.RW; break;
- case 1: mprot = MemoryBlock.Protection.R; break;
- case 5: mprot = MemoryBlock.Protection.RX; break;
- }
- return _parent._mmapheap.Protect((ulong)address, (ulong)size, mprot) ? 0 : -1;
- }
- }
-
- ///
- /// libc.so functions that we want to redirect
- ///
- private class LibcPatch
- {
- private readonly PeRunner _parent;
- public LibcPatch(PeRunner parent)
- {
- _parent = parent;
- }
-
-
- private bool _didOnce = false;
- private readonly Dictionary _specificKeys = new Dictionary();
- private uint _nextSpecificKey = 401;
-
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- public delegate void PthreadCallback();
-
- // pthread stuff:
- // since we don't allow multiple threads (for now), this is all pretty simple
- /*
- // int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_key_create")]
- public int PthreadKeyCreate(ref uint key, PthreadCallback destructor)
- {
- key = _nextSpecificKey++;
- _specificKeys.Add(key, IntPtr.Zero);
- return 0;
- }
- // int pthread_key_delete(pthread_key_t key);
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_key_delete")]
- public int PthreadKeyDelete(uint key)
- {
- _specificKeys.Remove(key);
- return 0;
- }
- // int pthread_setspecific(pthread_key_t key, const void *value);
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_setspecific")]
- public int PthreadSetSpecific(uint key, IntPtr value)
- {
- _specificKeys[key] = value;
- return 0;
- }
- // void *pthread_getspecific(pthread_key_t key);
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_getspecific")]
- public IntPtr PthreadGetSpecific(uint key)
- {
- IntPtr ret;
- _specificKeys.TryGetValue(key, out ret);
- return ret;
- }
-
- // int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_once")]
- public int PthreadOnce(IntPtr control, PthreadCallback init)
- {
- if (!_didOnce)
- {
- System.Diagnostics.Debugger.Break();
- _didOnce = true;
- init();
- }
- return 0;
- }
-
- // int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t* attr);
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_init")]
- public int PthreadMutexInit(IntPtr mutex, IntPtr attr) { return 0; }
- // int pthread_mutex_destroy(pthread_mutex_t* mutex);
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_destroy")]
- public int PthreadMutexDestroy(IntPtr mutex) { return 0; }
-
- // int pthread_mutex_lock(pthread_mutex_t* mutex);
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_lock")]
- public int PthreadMutexLock(IntPtr mutex) { return 0; }
- // int pthread_mutex_trylock(pthread_mutex_t* mutex);
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_trylock")]
- public int PthreadMutexTryLock(IntPtr mutex) { return 0; }
- // int pthread_mutex_unlock(pthread_mutex_t* mutex);
- [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_unlock")]
- public int PthreadMutexUnlock(IntPtr mutex) { return 0; }*/
-
-
- }
-
- ///
- /// usual starting point for the executable
- ///
- public const ulong CanonicalStart = 0x0000036f00000000;
-
- public const ulong AlternateStart = 0x0000036e00000000;
-
- ///
- /// the next place where we can put a module or heap
- ///
- private ulong _nextStart = CanonicalStart;
-
- ///
- /// increment _nextStart after adding a module
- ///
- private void ComputeNextStart(ulong size)
- {
- _nextStart += size;
- // align to 1MB, then increment 16MB
- _nextStart = ((_nextStart - 1) | 0xfffff) + 0x1000001;
- }
-
- ///
- /// standard malloc() heap
- ///
- private Heap _heap;
-
- ///
- /// sealed heap (writable only during init)
- ///
- private Heap _sealedheap;
-
- ///
- /// invisible heap (not savestated, use with care)
- ///
- private Heap _invisibleheap;
-
- ///
- /// extra savestated heap
- ///
- private Heap _plainheap;
-
- ///
- /// memory map emulation
- ///
- private MapHeap _mmapheap;
-
- ///
- /// all loaded PE files
- ///
- private readonly List _modules = new List();
-
- ///
- /// all loaded heaps
- ///
- private readonly List _heaps = new List();
-
- ///
- /// anything at all that needs to be disposed on finish
- ///
- private readonly List _disposeList = new List();
-
- ///
- /// anything at all that needs its state saved and loaded
- ///
- private readonly List _savestateComponents = new List();
-
- ///
- /// all of the exports, including real PeWrapper ones and fake ones
- ///
- private readonly Dictionary _exports = new Dictionary();
-
- private Psx _psx;
- private Emu _emu;
- private Syscalls _syscalls;
- private LibcPatch _libcpatch;
-
- ///
- /// timestamp of creation acts as a sort of "object id" in the savestate
- ///
- private readonly long _createstamp = WaterboxUtils.Timestamp();
-
- private Heap CreateHeapHelper(uint sizeKB, string name, bool saveStated)
- {
- if (sizeKB != 0)
- {
- var heap = new Heap(_nextStart, sizeKB * 1024, name);
- heap.Memory.Activate();
- ComputeNextStart(sizeKB * 1024);
- AddMemoryBlock(heap.Memory);
- if (saveStated)
- _savestateComponents.Add(heap);
- _disposeList.Add(heap);
- _heaps.Add(heap);
- return heap;
- }
- else
- {
- return null;
- }
- }
-
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
- private delegate void LibcEntryRoutineD(IntPtr appMain, IntPtr psxInit, int options);
-
- public PeRunner(PeRunnerOptions opt)
- {
- _nextStart = opt.StartAddress;
- Initialize(_nextStart);
- using (this.EnterExit())
- {
- // load any predefined exports
- _psx = new Psx(this);
- _exports.Add("libpsxscl.so", BizExvoker.GetExvoker(_psx, CallingConventionAdapters.Waterbox));
- _emu = new Emu(this);
- _exports.Add("libemuhost.so", BizExvoker.GetExvoker(_emu, CallingConventionAdapters.Waterbox));
- _syscalls = new Syscalls(this);
- _exports.Add("__syscalls", BizExvoker.GetExvoker(_syscalls, CallingConventionAdapters.Waterbox));
-
- // load and connect all modules, starting with the executable
- var todoModules = new Queue();
- todoModules.Enqueue(opt.Filename);
-
- while (todoModules.Count > 0)
- {
- var moduleName = todoModules.Dequeue();
- if (!_exports.ContainsKey(moduleName))
- {
- var path = Path.Combine(opt.Path, moduleName);
- var gzpath = path + ".gz";
- byte[] data;
- if (File.Exists(gzpath))
- {
- using (var fs = new FileStream(gzpath, FileMode.Open, FileAccess.Read))
- {
- var tmp = new byte[4];
- fs.Seek(-4, SeekOrigin.End);
- fs.Read(tmp, 0, 4);
- int size = BitConverter.ToInt32(tmp, 0);
- data = new byte[size];
- var ms = new MemoryStream(data);
- fs.Seek(0, SeekOrigin.Begin);
- using (var gs = new GZipStream(fs, CompressionMode.Decompress))
- gs.CopyTo(ms);
- }
- }
- else
- {
- data = File.ReadAllBytes(path);
- }
-
- var module = new PeWrapper(moduleName, data, _nextStart);
- ComputeNextStart(module.Size);
- AddMemoryBlock(module.Memory);
- _savestateComponents.Add(module);
- _disposeList.Add(module);
-
- _exports.Add(moduleName, module);
- _modules.Add(module);
- foreach (var name in module.ImportsByModule.Keys)
- {
- todoModules.Enqueue(name);
- }
- }
- }
-
- _libcpatch = new LibcPatch(this);
- _exports["libc.so"] = new PatchImportResolver(_exports["libc.so"], BizExvoker.GetExvoker(_libcpatch, CallingConventionAdapters.Waterbox));
-
- ConnectAllImports();
-
- // load all heaps
- _heap = CreateHeapHelper(opt.SbrkHeapSizeKB, "brk-heap", true);
- _sealedheap = CreateHeapHelper(opt.SealedHeapSizeKB, "sealed-heap", true);
- _invisibleheap = CreateHeapHelper(opt.InvisibleHeapSizeKB, "invisible-heap", false);
- _plainheap = CreateHeapHelper(opt.PlainHeapSizeKB, "plain-heap", true);
-
- if (opt.MmapHeapSizeKB != 0)
- {
- _mmapheap = new MapHeap(_nextStart, opt.MmapHeapSizeKB * 1024, "mmap-heap");
- _mmapheap.Memory.Activate();
- ComputeNextStart(opt.MmapHeapSizeKB * 1024);
- AddMemoryBlock(_mmapheap.Memory);
- _savestateComponents.Add(_mmapheap);
- _disposeList.Add(_mmapheap);
- }
-
- _syscalls.Init();
-
- Console.WriteLine("About to enter unmanaged code");
- if (Win32Hacks.IsDebuggerReallyPresent() && !System.Diagnostics.Debugger.IsAttached)
- {
- // this means that GDB or another unconventional debugger is attached.
- // if that's the case, and it's observing this core, it probably wants a break
- System.Diagnostics.Debugger.Break();
- }
-
- // run unmanaged init code
- var libcEnter = _exports["libc.so"].SafeResolve("__libc_entry_routine");
- var psxInit = _exports["libpsxscl.so"].SafeResolve("__psx_init");
-
- var del = (LibcEntryRoutineD)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libcEnter, typeof(LibcEntryRoutineD));
- // the current mmglue code doesn't use the main pointer at all, and this just returns
- del(IntPtr.Zero, psxInit, 0);
-
- foreach (var m in _modules)
- {
- m.RunGlobalCtors();
- }
-
- /*try
- {
- _modules[0].RunExeEntry();
- //throw new InvalidOperationException("main() returned!");
- }
- catch //(EndOfMainException)
- {
- }
- _modules[0].RunGlobalCtors();
- foreach (var m in _modules.Skip(1))
- {
- if (!m.RunDllEntry())
- throw new InvalidOperationException("DllMain() returned false");
- m.RunGlobalCtors();
- }*/
- }
- }
-
- public IntPtr Resolve(string entryPoint)
- {
- // modules[0] is always the main module
- return _modules[0].Resolve(entryPoint);
- }
-
- public void Seal()
- {
- using (this.EnterExit())
- {
- // if libco is used, the jmp_buf for the main cothread can have stack stuff in it.
- // this isn't a problem, since we only savestate when the core is not running, and
- // the next time it's run, that buf will be overridden again.
- // but it breaks xor state verification, so when we seal, nuke it.
-
- // this could be the responsibility of something else other than the PeRunner; I am not sure yet...
- IImportResolver libco;
- if (_exports.TryGetValue("libco.so", out libco))
- {
- Console.WriteLine("Calling co_clean()...");
- CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libco.SafeResolve("co_clean"))();
- }
-
- _sealedheap.Seal();
- foreach (var h in _heaps)
- {
- if (h != _invisibleheap) // TODO: if we have more non-savestated heaps, refine this hack
- h.Memory.SaveXorSnapshot();
- }
- foreach (var pe in _modules)
- {
- pe.SealImportsAndTakeXorSnapshot();
- }
- if (_mmapheap != null)
- _mmapheap.Memory.SaveXorSnapshot();
- }
- }
-
- private void ConnectAllImports()
- {
- foreach (var module in _modules)
- {
- foreach (var name in module.ImportsByModule.Keys)
- {
- module.ConnectImports(name, _exports[name]);
- }
- }
- }
-
- public void SaveStateBinary(BinaryWriter bw)
- {
- bw.Write(_createstamp);
- bw.Write(_savestateComponents.Count);
- using (this.EnterExit())
- {
- foreach (var c in _savestateComponents)
- {
- c.SaveStateBinary(bw);
- }
- }
- }
-
- public void LoadStateBinary(BinaryReader br)
- {
- var differentCore = br.ReadInt64() != _createstamp; // true if a different core instance created the state
- if (br.ReadInt32() != _savestateComponents.Count)
- throw new InvalidOperationException("Internal savestate error");
- using (this.EnterExit())
- {
- foreach (var c in _savestateComponents)
- {
- c.LoadStateBinary(br);
- }
- if (differentCore)
- {
- // if a different runtime instance than this one saved the state,
- // Exvoker imports need to be reconnected
- Console.WriteLine("Restoring PeRunner state from a different core...");
- ConnectAllImports();
- _psx.ReloadVtables();
- }
- }
- }
-
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- {
- foreach (var d in _disposeList)
- d.Dispose();
- _disposeList.Clear();
- PurgeMemoryBlocks();
- _modules.Clear();
- _exports.Clear();
- _heap = null;
- _sealedheap = null;
- _invisibleheap = null;
- _plainheap = null;
- _mmapheap = null;
- }
- }
- }
-}
+ throw new InvalidOperationException("This shouldn't be called");
+ //}
+ //else
+ //{
+ // return 0;
+ //}
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_self")]
+ public IntPtr PthreadSelf()
+ {
+ return _pthreadSelf;
+ }
+
+ /*[BizExport(CallingConvention.Cdecl, EntryPoint = "convert_thread")]
+ public void ConvertThread()
+ {
+
+ }*/
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n218")]
+ public long SetTidAddress(IntPtr address)
+ {
+ return 8675309;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public class TimeSpec
+ {
+ public long Seconds;
+ public long NanoSeconds;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n228")]
+ public int SysClockGetTime(int which, [In, Out] TimeSpec time)
+ {
+ time.Seconds = 1495889068;
+ time.NanoSeconds = 0;
+ return 0;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n9")]
+ public IntPtr MMap(IntPtr address, UIntPtr size, int prot, int flags, int fd, IntPtr offs)
+ {
+ if (address != IntPtr.Zero)
+ return Z.SS(-1);
+ MemoryBlock.Protection mprot;
+ switch (prot)
+ {
+ case 0: mprot = MemoryBlock.Protection.None; break;
+ default:
+ case 6: // W^X
+ case 7: // W^X
+ case 4: // exec only????
+ case 2: return Z.SS(-1); // write only????
+ case 3: mprot = MemoryBlock.Protection.RW; break;
+ case 1: mprot = MemoryBlock.Protection.R; break;
+ case 5: mprot = MemoryBlock.Protection.RX; break;
+ }
+ if ((flags & 0x20) == 0)
+ {
+ // MAP_ANONYMOUS is required
+ return Z.SS(-1);
+ }
+ if ((flags & 0xf00) != 0)
+ {
+ // various unsupported flags
+ return Z.SS(-1);
+ }
+
+ var ret = _parent._mmapheap.Map((ulong)size, mprot);
+ return ret == 0 ? Z.SS(-1) : Z.US(ret);
+ }
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n25")]
+ public IntPtr MRemap(UIntPtr oldAddress, UIntPtr oldSize,
+ UIntPtr newSize, int flags)
+ {
+ if ((flags & 2) != 0)
+ {
+ // don't support MREMAP_FIXED
+ return Z.SS(-1);
+ }
+ var ret = _parent._mmapheap.Remap((ulong)oldAddress, (ulong)oldSize, (ulong)newSize,
+ (flags & 1) != 0);
+ return ret == 0 ? Z.SS(-1) : Z.US(ret);
+ }
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n11")]
+ public int MUnmap(UIntPtr address, UIntPtr size)
+ {
+ return _parent._mmapheap.Unmap((ulong)address, (ulong)size) ? 0 : -1;
+ }
+
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "n10")]
+ public int MProtect(UIntPtr address, UIntPtr size, int prot)
+ {
+ MemoryBlock.Protection mprot;
+ switch (prot)
+ {
+ case 0: mprot = MemoryBlock.Protection.None; break;
+ default:
+ case 6: // W^X
+ case 7: // W^X
+ case 4: // exec only????
+ case 2: return -1; // write only????
+ case 3: mprot = MemoryBlock.Protection.RW; break;
+ case 1: mprot = MemoryBlock.Protection.R; break;
+ case 5: mprot = MemoryBlock.Protection.RX; break;
+ }
+ return _parent._mmapheap.Protect((ulong)address, (ulong)size, mprot) ? 0 : -1;
+ }
+ }
+
+ ///
+ /// libc.so functions that we want to redirect
+ ///
+ private class LibcPatch
+ {
+ private readonly PeRunner _parent;
+ public LibcPatch(PeRunner parent)
+ {
+ _parent = parent;
+ }
+
+
+ private bool _didOnce = false;
+ private readonly Dictionary _specificKeys = new Dictionary();
+ private uint _nextSpecificKey = 401;
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void PthreadCallback();
+
+ // pthread stuff:
+ // since we don't allow multiple threads (for now), this is all pretty simple
+ /*
+ // int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_key_create")]
+ public int PthreadKeyCreate(ref uint key, PthreadCallback destructor)
+ {
+ key = _nextSpecificKey++;
+ _specificKeys.Add(key, IntPtr.Zero);
+ return 0;
+ }
+ // int pthread_key_delete(pthread_key_t key);
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_key_delete")]
+ public int PthreadKeyDelete(uint key)
+ {
+ _specificKeys.Remove(key);
+ return 0;
+ }
+ // int pthread_setspecific(pthread_key_t key, const void *value);
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_setspecific")]
+ public int PthreadSetSpecific(uint key, IntPtr value)
+ {
+ _specificKeys[key] = value;
+ return 0;
+ }
+ // void *pthread_getspecific(pthread_key_t key);
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_getspecific")]
+ public IntPtr PthreadGetSpecific(uint key)
+ {
+ IntPtr ret;
+ _specificKeys.TryGetValue(key, out ret);
+ return ret;
+ }
+
+ // int pthread_once(pthread_once_t* once_control, void (*init_routine)(void));
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_once")]
+ public int PthreadOnce(IntPtr control, PthreadCallback init)
+ {
+ if (!_didOnce)
+ {
+ System.Diagnostics.Debugger.Break();
+ _didOnce = true;
+ init();
+ }
+ return 0;
+ }
+
+ // int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t* attr);
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_init")]
+ public int PthreadMutexInit(IntPtr mutex, IntPtr attr) { return 0; }
+ // int pthread_mutex_destroy(pthread_mutex_t* mutex);
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_destroy")]
+ public int PthreadMutexDestroy(IntPtr mutex) { return 0; }
+
+ // int pthread_mutex_lock(pthread_mutex_t* mutex);
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_lock")]
+ public int PthreadMutexLock(IntPtr mutex) { return 0; }
+ // int pthread_mutex_trylock(pthread_mutex_t* mutex);
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_trylock")]
+ public int PthreadMutexTryLock(IntPtr mutex) { return 0; }
+ // int pthread_mutex_unlock(pthread_mutex_t* mutex);
+ [BizExport(CallingConvention.Cdecl, EntryPoint = "pthread_mutex_unlock")]
+ public int PthreadMutexUnlock(IntPtr mutex) { return 0; }*/
+
+
+ }
+
+ ///
+ /// usual starting point for the executable
+ ///
+ public const ulong CanonicalStart = 0x0000036f00000000;
+
+ public const ulong AlternateStart = 0x0000036e00000000;
+
+ ///
+ /// the next place where we can put a module or heap
+ ///
+ private ulong _nextStart = CanonicalStart;
+
+ ///
+ /// increment _nextStart after adding a module
+ ///
+ private void ComputeNextStart(ulong size)
+ {
+ _nextStart += size;
+ // align to 1MB, then increment 16MB
+ _nextStart = ((_nextStart - 1) | 0xfffff) + 0x1000001;
+ }
+
+ ///
+ /// standard malloc() heap
+ ///
+ private Heap _heap;
+
+ ///
+ /// sealed heap (writable only during init)
+ ///
+ private Heap _sealedheap;
+
+ ///
+ /// invisible heap (not savestated, use with care)
+ ///
+ private Heap _invisibleheap;
+
+ ///
+ /// extra savestated heap
+ ///
+ private Heap _plainheap;
+
+ ///
+ /// memory map emulation
+ ///
+ private MapHeap _mmapheap;
+
+ ///
+ /// all loaded PE files
+ ///
+ private readonly List _modules = new List();
+
+ ///
+ /// all loaded heaps
+ ///
+ private readonly List _heaps = new List();
+
+ ///
+ /// anything at all that needs to be disposed on finish
+ ///
+ private readonly List _disposeList = new List();
+
+ ///
+ /// anything at all that needs its state saved and loaded
+ ///
+ private readonly List _savestateComponents = new List();
+
+ ///
+ /// all of the exports, including real PeWrapper ones and fake ones
+ ///
+ private readonly Dictionary _exports = new Dictionary();
+
+ private Psx _psx;
+ private Emu _emu;
+ private Syscalls _syscalls;
+ private LibcPatch _libcpatch;
+
+ ///
+ /// timestamp of creation acts as a sort of "object id" in the savestate
+ ///
+ private readonly long _createstamp = WaterboxUtils.Timestamp();
+
+ private Heap CreateHeapHelper(uint sizeKB, string name, bool saveStated)
+ {
+ if (sizeKB != 0)
+ {
+ var heap = new Heap(_nextStart, sizeKB * 1024, name);
+ heap.Memory.Activate();
+ ComputeNextStart(sizeKB * 1024);
+ AddMemoryBlock(heap.Memory, name);
+ if (saveStated)
+ _savestateComponents.Add(heap);
+ _disposeList.Add(heap);
+ _heaps.Add(heap);
+ return heap;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ private delegate void LibcEntryRoutineD(IntPtr appMain, IntPtr psxInit, int options);
+
+ public PeRunner(PeRunnerOptions opt)
+ {
+ _nextStart = opt.StartAddress;
+ Initialize(_nextStart);
+ using (this.EnterExit())
+ {
+ // load any predefined exports
+ _psx = new Psx(this);
+ _exports.Add("libpsxscl.so", BizExvoker.GetExvoker(_psx, CallingConventionAdapters.Waterbox));
+ _emu = new Emu(this);
+ _exports.Add("libemuhost.so", BizExvoker.GetExvoker(_emu, CallingConventionAdapters.Waterbox));
+ _syscalls = new Syscalls(this);
+ _exports.Add("__syscalls", BizExvoker.GetExvoker(_syscalls, CallingConventionAdapters.Waterbox));
+
+ // load and connect all modules, starting with the executable
+ var todoModules = new Queue();
+ todoModules.Enqueue(opt.Filename);
+
+ while (todoModules.Count > 0)
+ {
+ var moduleName = todoModules.Dequeue();
+ if (!_exports.ContainsKey(moduleName))
+ {
+ var path = Path.Combine(opt.Path, moduleName);
+ var gzpath = path + ".gz";
+ byte[] data;
+ if (File.Exists(gzpath))
+ {
+ using (var fs = new FileStream(gzpath, FileMode.Open, FileAccess.Read))
+ {
+ var tmp = new byte[4];
+ fs.Seek(-4, SeekOrigin.End);
+ fs.Read(tmp, 0, 4);
+ int size = BitConverter.ToInt32(tmp, 0);
+ data = new byte[size];
+ var ms = new MemoryStream(data);
+ fs.Seek(0, SeekOrigin.Begin);
+ using (var gs = new GZipStream(fs, CompressionMode.Decompress))
+ gs.CopyTo(ms);
+ }
+ }
+ else
+ {
+ data = File.ReadAllBytes(path);
+ }
+
+ var module = new PeWrapper(moduleName, data, _nextStart);
+ ComputeNextStart(module.Size);
+ AddMemoryBlock(module.Memory, moduleName);
+ _savestateComponents.Add(module);
+ _disposeList.Add(module);
+
+ _exports.Add(moduleName, module);
+ _modules.Add(module);
+ foreach (var name in module.ImportsByModule.Keys)
+ {
+ todoModules.Enqueue(name);
+ }
+ }
+ }
+
+ _libcpatch = new LibcPatch(this);
+ _exports["libc.so"] = new PatchImportResolver(_exports["libc.so"], BizExvoker.GetExvoker(_libcpatch, CallingConventionAdapters.Waterbox));
+
+ ConnectAllImports();
+
+ // load all heaps
+ _heap = CreateHeapHelper(opt.SbrkHeapSizeKB, "brk-heap", true);
+ _sealedheap = CreateHeapHelper(opt.SealedHeapSizeKB, "sealed-heap", true);
+ _invisibleheap = CreateHeapHelper(opt.InvisibleHeapSizeKB, "invisible-heap", false);
+ _plainheap = CreateHeapHelper(opt.PlainHeapSizeKB, "plain-heap", true);
+
+ if (opt.MmapHeapSizeKB != 0)
+ {
+ _mmapheap = new MapHeap(_nextStart, opt.MmapHeapSizeKB * 1024, "mmap-heap");
+ _mmapheap.Memory.Activate();
+ ComputeNextStart(opt.MmapHeapSizeKB * 1024);
+ AddMemoryBlock(_mmapheap.Memory, "mmap-heap");
+ _savestateComponents.Add(_mmapheap);
+ _disposeList.Add(_mmapheap);
+ }
+
+ _syscalls.Init();
+
+ Console.WriteLine("About to enter unmanaged code");
+ if (Win32Hacks.IsDebuggerReallyPresent() && !System.Diagnostics.Debugger.IsAttached)
+ {
+ // this means that GDB or another unconventional debugger is attached.
+ // if that's the case, and it's observing this core, it probably wants a break
+ System.Diagnostics.Debugger.Break();
+ }
+
+ // run unmanaged init code
+ var libcEnter = _exports["libc.so"].SafeResolve("__libc_entry_routine");
+ var psxInit = _exports["libpsxscl.so"].SafeResolve("__psx_init");
+
+ var del = (LibcEntryRoutineD)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libcEnter, typeof(LibcEntryRoutineD));
+ // the current mmglue code doesn't use the main pointer at all, and this just returns
+ del(IntPtr.Zero, psxInit, 0);
+
+ foreach (var m in _modules)
+ {
+ m.RunGlobalCtors();
+ }
+
+ /*try
+ {
+ _modules[0].RunExeEntry();
+ //throw new InvalidOperationException("main() returned!");
+ }
+ catch //(EndOfMainException)
+ {
+ }
+ _modules[0].RunGlobalCtors();
+ foreach (var m in _modules.Skip(1))
+ {
+ if (!m.RunDllEntry())
+ throw new InvalidOperationException("DllMain() returned false");
+ m.RunGlobalCtors();
+ }*/
+ }
+ }
+
+ public IntPtr Resolve(string entryPoint)
+ {
+ // modules[0] is always the main module
+ return _modules[0].Resolve(entryPoint);
+ }
+
+ public void Seal()
+ {
+ using (this.EnterExit())
+ {
+ // if libco is used, the jmp_buf for the main cothread can have stack stuff in it.
+ // this isn't a problem, since we only savestate when the core is not running, and
+ // the next time it's run, that buf will be overridden again.
+ // but it breaks xor state verification, so when we seal, nuke it.
+
+ // this could be the responsibility of something else other than the PeRunner; I am not sure yet...
+ IImportResolver libco;
+ if (_exports.TryGetValue("libco.so", out libco))
+ {
+ Console.WriteLine("Calling co_clean()...");
+ CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(libco.SafeResolve("co_clean"))();
+ }
+
+ _sealedheap.Seal();
+ foreach (var h in _heaps)
+ {
+ if (h != _invisibleheap) // TODO: if we have more non-savestated heaps, refine this hack
+ h.Memory.SaveXorSnapshot();
+ }
+ foreach (var pe in _modules)
+ {
+ pe.SealImportsAndTakeXorSnapshot();
+ }
+ if (_mmapheap != null)
+ _mmapheap.Memory.SaveXorSnapshot();
+ }
+ }
+
+ private void ConnectAllImports()
+ {
+ foreach (var module in _modules)
+ {
+ foreach (var name in module.ImportsByModule.Keys)
+ {
+ module.ConnectImports(name, _exports[name]);
+ }
+ }
+ }
+
+ public void SaveStateBinary(BinaryWriter bw)
+ {
+ bw.Write(_createstamp);
+ bw.Write(_savestateComponents.Count);
+ using (this.EnterExit())
+ {
+ foreach (var c in _savestateComponents)
+ {
+ c.SaveStateBinary(bw);
+ }
+ }
+ }
+
+ public void LoadStateBinary(BinaryReader br)
+ {
+ var differentCore = br.ReadInt64() != _createstamp; // true if a different core instance created the state
+ if (br.ReadInt32() != _savestateComponents.Count)
+ throw new InvalidOperationException("Internal savestate error");
+ using (this.EnterExit())
+ {
+ foreach (var c in _savestateComponents)
+ {
+ c.LoadStateBinary(br);
+ }
+ if (differentCore)
+ {
+ // if a different runtime instance than this one saved the state,
+ // Exvoker imports need to be reconnected
+ Console.WriteLine("Restoring PeRunner state from a different core...");
+ ConnectAllImports();
+ _psx.ReloadVtables();
+ }
+ }
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (disposing)
+ {
+ foreach (var d in _disposeList)
+ d.Dispose();
+ _disposeList.Clear();
+ PurgeMemoryBlocks();
+ _modules.Clear();
+ _exports.Clear();
+ _heap = null;
+ _sealedheap = null;
+ _invisibleheap = null;
+ _plainheap = null;
+ _mmapheap = null;
+ }
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs b/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs
index 07c057f2cd..bb7cb2e9fb 100644
--- a/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/PeWrapper.cs
@@ -1,433 +1,435 @@
-using BizHawk.Common;
+using BizHawk.Common;
using BizHawk.Common.BizInvoke;
-using BizHawk.Emulation.Common;
-using PeNet;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-
-namespace BizHawk.Emulation.Cores.Waterbox
-{
- ///
- /// represents one PE file. used in PeRunner
- ///
- internal class PeWrapper : IImportResolver, IBinaryStateable, IDisposable
- {
- public Dictionary ExportsByOrdinal { get; } = new Dictionary();
- ///
- /// ordinal only exports will not show up in this list!
- ///
- public Dictionary ExportsByName { get; } = new Dictionary();
-
- public Dictionary> ImportsByModule { get; } =
- new Dictionary>();
-
- private class Section
- {
- public string Name { get; set; }
- public ulong Start { get; set; }
- public ulong Size { get; set; }
- public bool W { get; set; }
- public bool R { get; set; }
- public bool X { get; set; }
- public MemoryBlock.Protection Prot { get; set; }
- public ulong DiskStart { get; set; }
- public ulong DiskSize { get; set; }
- }
-
- private readonly Dictionary _sectionsByName = new Dictionary();
- private readonly List _sections = new List();
- private Section _imports;
-
- public string ModuleName { get; }
-
- private readonly byte[] _fileData;
- private readonly PeFile _pe;
- private readonly byte[] _fileHash;
-
- public ulong Size { get; }
- public ulong Start { get; private set; }
-
- public long LoadOffset { get; private set; }
-
- public MemoryBlock Memory { get; private set; }
-
- public IntPtr EntryPoint { get; private set; }
-
- ///
- /// for midipix-built PEs, pointer to the construtors to run during init
- ///
- public IntPtr CtorList { get; private set; }
- ///
- /// for midipix-build PEs, pointer to the destructors to run during fini
- ///
- public IntPtr DtorList { get; private set; }
-
- // true if the imports have been set to readonly
- private bool _importsSealed = false;
-
- /*[UnmanagedFunctionPointer(CallingConvention.Winapi)]
- private delegate bool DllEntry(IntPtr instance, int reason, IntPtr reserved);
- [UnmanagedFunctionPointer(CallingConvention.Winapi)]
- private delegate void ExeEntry();*/
- [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+using BizHawk.Emulation.Common;
+using PeNet;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+
+namespace BizHawk.Emulation.Cores.Waterbox
+{
+ ///
+ /// represents one PE file. used in PeRunner
+ ///
+ internal class PeWrapper : IImportResolver, IBinaryStateable, IDisposable
+ {
+ public Dictionary ExportsByOrdinal { get; } = new Dictionary();
+ ///
+ /// ordinal only exports will not show up in this list!
+ ///
+ public Dictionary ExportsByName { get; } = new Dictionary();
+
+ public Dictionary> ImportsByModule { get; } =
+ new Dictionary>();
+
+ private class Section
+ {
+ public string Name { get; set; }
+ public ulong Start { get; set; }
+ public ulong Size { get; set; }
+ public ulong SavedSize { get; set; }
+ public bool W { get; set; }
+ public bool R { get; set; }
+ public bool X { get; set; }
+ public MemoryBlock.Protection Prot { get; set; }
+ public ulong DiskStart { get; set; }
+ public ulong DiskSize { get; set; }
+ }
+
+ private readonly Dictionary _sectionsByName = new Dictionary();
+ private readonly List _sections = new List();
+ private Section _imports;
+
+ public string ModuleName { get; }
+
+ private readonly byte[] _fileData;
+ private readonly PeFile _pe;
+ private readonly byte[] _fileHash;
+
+ public ulong Size { get; }
+ public ulong Start { get; private set; }
+
+ public long LoadOffset { get; private set; }
+
+ public MemoryBlock Memory { get; private set; }
+
+ public IntPtr EntryPoint { get; private set; }
+
+ ///
+ /// for midipix-built PEs, pointer to the construtors to run during init
+ ///
+ public IntPtr CtorList { get; private set; }
+ ///
+ /// for midipix-build PEs, pointer to the destructors to run during fini
+ ///
+ public IntPtr DtorList { get; private set; }
+
+ // true if the imports have been set to readonly
+ private bool _importsSealed = false;
+
+ /*[UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ private delegate bool DllEntry(IntPtr instance, int reason, IntPtr reserved);
+ [UnmanagedFunctionPointer(CallingConvention.Winapi)]
+ private delegate void ExeEntry();*/
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void GlobalCtor();
- /*public bool RunDllEntry()
- {
- var entryThunk = (DllEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(DllEntry));
- return entryThunk(Z.US(Start), 1, IntPtr.Zero); // DLL_PROCESS_ATTACH
- }
- public void RunExeEntry()
- {
- var entryThunk = (ExeEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(ExeEntry));
- entryThunk();
+ /*public bool RunDllEntry()
+ {
+ var entryThunk = (DllEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(DllEntry));
+ return entryThunk(Z.US(Start), 1, IntPtr.Zero); // DLL_PROCESS_ATTACH
+ }
+ public void RunExeEntry()
+ {
+ var entryThunk = (ExeEntry)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(EntryPoint, typeof(ExeEntry));
+ entryThunk();
}*/
- public unsafe void RunGlobalCtors()
- {
- int did = 0;
- if (CtorList != IntPtr.Zero)
- {
- IntPtr* p = (IntPtr*)CtorList;
- IntPtr f;
- while ((f = *++p) != IntPtr.Zero) // skip 0th dummy pointer
- {
- var ctorThunk = (GlobalCtor)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(f, typeof(GlobalCtor));
- //Console.WriteLine(f);
- //System.Diagnostics.Debugger.Break();
- ctorThunk();
- did++;
- }
- }
-
- if (did > 0)
- {
- Console.WriteLine($"Did {did} global ctors for {ModuleName}");
- }
- else
- {
- Console.WriteLine($"Warn: no global ctors for {ModuleName}; possibly no C++?");
- }
- }
-
- public PeWrapper(string moduleName, byte[] fileData, ulong destAddress)
- {
- ModuleName = moduleName;
- _fileData = fileData;
- _pe = new PeFile(fileData);
- Size = _pe.ImageNtHeaders.OptionalHeader.SizeOfImage;
- Start = destAddress;
-
- if (Size < _pe.ImageSectionHeaders.Max(s => (ulong)s.VirtualSize + s.VirtualAddress))
- {
- throw new InvalidOperationException("Image not Big Enough");
- }
-
- _fileHash = WaterboxUtils.Hash(fileData);
-
- foreach (var s in _pe.ImageSectionHeaders)
- {
- ulong start = Start + s.VirtualAddress;
- ulong length = s.VirtualSize;
-
- MemoryBlock.Protection prot;
- var r = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_READ) != 0;
- var w = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_WRITE) != 0;
- var x = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_EXECUTE) != 0;
- if (w && x)
- {
- throw new InvalidOperationException("Write and Execute not allowed");
- }
-
- prot = x ? MemoryBlock.Protection.RX : w ? MemoryBlock.Protection.RW : MemoryBlock.Protection.R;
-
- var section = new Section
- {
- // chop off possible null padding from name
- Name = Encoding.ASCII.GetString(s.Name, 0,
- (s.Name.Select((v, i) => new { v, i }).FirstOrDefault(a => a.v == 0) ?? new { v = (byte)0, i = s.Name.Length }).i),
- Start = start,
- Size = length,
- R = r,
- W = w,
- X = x,
- Prot = prot,
- DiskStart = s.PointerToRawData,
- DiskSize = s.SizeOfRawData
- };
-
- _sections.Add(section);
- _sectionsByName.Add(section.Name, section);
- }
- _sectionsByName.TryGetValue(".idata", out _imports);
-
- Mount();
- }
-
- ///
- /// set memory protections.
- ///
- private void ProtectMemory()
- {
- Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.R);
-
- foreach (var s in _sections)
- {
- Memory.Protect(s.Start, s.Size, s.Prot);
- }
- }
-
- ///
- /// load the PE into memory
- ///
- /// start address
- private void Mount()
- {
- LoadOffset = (long)Start - (long)_pe.ImageNtHeaders.OptionalHeader.ImageBase;
- Memory = new MemoryBlock(Start, Size);
- Memory.Activate();
- Memory.Protect(Start, Size, MemoryBlock.Protection.RW);
-
- // copy headers
- Marshal.Copy(_fileData, 0, Z.US(Start), (int)_pe.ImageNtHeaders.OptionalHeader.SizeOfHeaders);
-
- // copy sections
- foreach (var s in _sections)
- {
- ulong datalength = Math.Min(s.Size, s.DiskSize);
- Marshal.Copy(_fileData, (int)s.DiskStart, Z.US(s.Start), (int)datalength);
- WaterboxUtils.ZeroMemory(Z.US(s.Start + datalength), (long)(s.Size - datalength));
- }
-
- // apply relocations
- var n32 = 0;
- var n64 = 0;
- foreach (var rel in _pe.ImageRelocationDirectory)
- {
- foreach (var to in rel.TypeOffsets)
- {
- ulong address = Start + rel.VirtualAddress + to.Offset;
-
- switch (to.Type)
- {
- // there are many other types of relocation specified,
- // but the only that are used is 0 (does nothing), 3 (32 bit standard), 10 (64 bit standard)
-
- case 3: // IMAGE_REL_BASED_HIGHLOW
- {
- byte[] tmp = new byte[4];
- Marshal.Copy(Z.US(address), tmp, 0, 4);
- uint val = BitConverter.ToUInt32(tmp, 0);
- tmp = BitConverter.GetBytes((uint)(val + LoadOffset));
- Marshal.Copy(tmp, 0, Z.US(address), 4);
- n32++;
- break;
- }
-
- case 10: // IMAGE_REL_BASED_DIR64
- {
- byte[] tmp = new byte[8];
- Marshal.Copy(Z.US(address), tmp, 0, 8);
- long val = BitConverter.ToInt64(tmp, 0);
- tmp = BitConverter.GetBytes(val + LoadOffset);
- Marshal.Copy(tmp, 0, Z.US(address), 8);
- n64++;
- break;
- }
- }
- }
- }
- if (IntPtr.Size == 8 && n32 > 0)
- {
- // check mcmodel, etc
- throw new InvalidOperationException("32 bit relocations found in 64 bit dll! This will fail.");
- }
- Console.WriteLine($"Processed {n32} 32 bit and {n64} 64 bit relocations");
-
- ProtectMemory();
-
- // publish exports
- EntryPoint = Z.US(Start + _pe.ImageNtHeaders.OptionalHeader.AddressOfEntryPoint);
- foreach (var export in _pe.ExportedFunctions)
- {
- if (export.Name != null)
- ExportsByName.Add(export.Name, Z.US(Start + export.Address));
- ExportsByOrdinal.Add(export.Ordinal, Z.US(Start + export.Address));
- }
-
- // collect information about imports
- // NB: Hints are not the same as Ordinals
- foreach (var import in _pe.ImportedFunctions)
- {
- Dictionary module;
- if (!ImportsByModule.TryGetValue(import.DLL, out module))
- {
- module = new Dictionary();
- ImportsByModule.Add(import.DLL, module);
- }
- var dest = Start + import.Thunk;
- if (_imports == null || dest >= _imports.Start + _imports.Size || dest < _imports.Start)
- throw new InvalidOperationException("Import record outside of .idata!");
-
- module.Add(import.Name, Z.US(dest));
- }
-
- Section midipix;
- if (_sectionsByName.TryGetValue(".midipix", out midipix))
- {
- var dataOffset = midipix.DiskStart;
- CtorList = Z.SS(BitConverter.ToInt64(_fileData, (int)(dataOffset + 0x30)) + LoadOffset);
- DtorList = Z.SS(BitConverter.ToInt64(_fileData, (int)(dataOffset + 0x38)) + LoadOffset);
- }
-
- Console.WriteLine($"Mounted `{ModuleName}` @{Start:x16}");
- foreach (var s in _sections.OrderBy(s => s.Start))
- {
- Console.WriteLine(" @{0:x16} {1}{2}{3} `{4}` {5} bytes",
- s.Start,
- s.R ? "R" : " ",
- s.W ? "W" : " ",
- s.X ? "X" : " ",
- s.Name,
- s.Size);
- }
- Console.WriteLine("GDB Symbol Load:");
- var symload = $"add-sym {ModuleName} {_sectionsByName[".text"].Start}";
- if (_sectionsByName.ContainsKey(".data"))
- symload += $" -s .data {_sectionsByName[".data"].Start}";
- if (_sectionsByName.ContainsKey(".bss"))
- symload += $" -s .bss {_sectionsByName[".bss"].Start}";
- Console.WriteLine(symload);
- }
-
- public IntPtr Resolve(string entryPoint)
- {
- IntPtr ret;
- ExportsByName.TryGetValue(entryPoint, out ret);
- return ret;
- }
-
- public void ConnectImports(string moduleName, IImportResolver module)
- {
- // this is called once internally when bootstrapping, and externally
- // when we need to restore a savestate from another run. so imports might or might not be sealed
-
- if (_importsSealed && _imports != null)
- Memory.Protect(_imports.Start, _imports.Size, MemoryBlock.Protection.RW);
-
- Dictionary imports;
- if (ImportsByModule.TryGetValue(moduleName, out imports))
- {
- foreach (var kvp in imports)
- {
- var valueArray = new IntPtr[] { module.SafeResolve(kvp.Key) };
- Marshal.Copy(valueArray, 0, kvp.Value, 1);
- }
- }
-
- if (_importsSealed && _imports != null)
- Memory.Protect(_imports.Start, _imports.Size, _imports.Prot);
- }
-
- public void SealImportsAndTakeXorSnapshot()
- {
- if (_importsSealed)
- throw new InvalidOperationException("Imports already sealed!");
-
- // save import values, then zero them all (for hash purposes), then take our snapshot, then load them again,
- // then set the .idata area to read only
- if (_imports != null)
- {
- var data = new byte[_imports.Size];
- Marshal.Copy(Z.US(_imports.Start), data, 0, (int)_imports.Size);
- WaterboxUtils.ZeroMemory(Z.US(_imports.Start), (long)_imports.Size);
- Memory.SaveXorSnapshot();
- Marshal.Copy(data, 0, Z.US(_imports.Start), (int)_imports.Size);
- _imports.W = false;
- Memory.Protect(_imports.Start, _imports.Size, _imports.Prot);
- }
- else
- {
- Memory.SaveXorSnapshot();
- }
-
- _importsSealed = true;
- }
-
- private bool _disposed = false;
-
- public void Dispose()
- {
- if (!_disposed)
- {
- Memory.Dispose();
- Memory = null;
- _disposed = true;
- }
- }
-
- const ulong MAGIC = 0x420cccb1a2e17420;
-
- public void SaveStateBinary(BinaryWriter bw)
- {
- if (!_importsSealed)
- throw new InvalidOperationException(".idata sections must be closed before saving state");
-
- bw.Write(MAGIC);
- bw.Write(_fileHash);
- bw.Write(Memory.XorHash);
- bw.Write(Start);
-
- foreach (var s in _sections)
- {
- if (!s.W)
- continue;
-
- var ms = Memory.GetXorStream(s.Start, s.Size, false);
- bw.Write(s.Size);
- ms.CopyTo(bw.BaseStream);
- }
- }
-
- public void LoadStateBinary(BinaryReader br)
- {
- if (!_importsSealed)
- // operations happening in the wrong order. probable cause: internal logic error. make sure frontend calls Seal
- throw new InvalidOperationException(".idata sections must be closed before loading state");
-
- if (br.ReadUInt64() != MAGIC)
- // file id is missing. probable cause: garbage savestate
- throw new InvalidOperationException("Savestate corrupted!");
- if (!br.ReadBytes(_fileHash.Length).SequenceEqual(_fileHash))
- // the .dll file that is loaded now has a different hash than the .dll that created the savestate
- throw new InvalidOperationException("Core consistency check failed. Is this a savestate from a different version?");
- if (!br.ReadBytes(Memory.XorHash.Length).SequenceEqual(Memory.XorHash))
- // the post-Seal memory state is different. probable cause: different rom or different version of rom,
- // different syncsettings
- throw new InvalidOperationException("Memory consistency check failed. Is this savestate from different SyncSettings?");
- if (br.ReadUInt64() != Start)
- // dll loaded somewhere else. probable cause: internal logic error.
- // unlikely to get this far if the previous checks pssed
- throw new InvalidOperationException("Trickys elves moved on you!");
-
- Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.RW);
-
- foreach (var s in _sections)
- {
- if (!s.W)
- continue;
-
- if (br.ReadUInt64() != s.Size)
- throw new InvalidOperationException("Unexpected section size for " + s.Name);
-
- var ms = Memory.GetXorStream(s.Start, s.Size, true);
- WaterboxUtils.CopySome(br.BaseStream, ms, (long)s.Size);
- }
-
- ProtectMemory();
- }
- }
-}
+ public unsafe void RunGlobalCtors()
+ {
+ int did = 0;
+ if (CtorList != IntPtr.Zero)
+ {
+ IntPtr* p = (IntPtr*)CtorList;
+ IntPtr f;
+ while ((f = *++p) != IntPtr.Zero) // skip 0th dummy pointer
+ {
+ var ctorThunk = (GlobalCtor)CallingConventionAdapters.Waterbox.GetDelegateForFunctionPointer(f, typeof(GlobalCtor));
+ //Console.WriteLine(f);
+ //System.Diagnostics.Debugger.Break();
+ ctorThunk();
+ did++;
+ }
+ }
+
+ if (did > 0)
+ {
+ Console.WriteLine($"Did {did} global ctors for {ModuleName}");
+ }
+ else
+ {
+ Console.WriteLine($"Warn: no global ctors for {ModuleName}; possibly no C++?");
+ }
+ }
+
+ public PeWrapper(string moduleName, byte[] fileData, ulong destAddress)
+ {
+ ModuleName = moduleName;
+ _fileData = fileData;
+ _pe = new PeFile(fileData);
+ Size = _pe.ImageNtHeaders.OptionalHeader.SizeOfImage;
+ Start = destAddress;
+
+ if (Size < _pe.ImageSectionHeaders.Max(s => (ulong)s.VirtualSize + s.VirtualAddress))
+ {
+ throw new InvalidOperationException("Image not Big Enough");
+ }
+
+ _fileHash = WaterboxUtils.Hash(fileData);
+
+ foreach (var s in _pe.ImageSectionHeaders)
+ {
+ ulong start = Start + s.VirtualAddress;
+ ulong length = s.VirtualSize;
+
+ MemoryBlock.Protection prot;
+ var r = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_READ) != 0;
+ var w = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_WRITE) != 0;
+ var x = (s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_EXECUTE) != 0;
+ if (w && x)
+ {
+ throw new InvalidOperationException("Write and Execute not allowed");
+ }
+
+ prot = x ? MemoryBlock.Protection.RX : w ? MemoryBlock.Protection.RW : MemoryBlock.Protection.R;
+
+ var section = new Section
+ {
+ // chop off possible null padding from name
+ Name = Encoding.ASCII.GetString(s.Name, 0,
+ (s.Name.Select((v, i) => new { v, i }).FirstOrDefault(a => a.v == 0) ?? new { v = (byte)0, i = s.Name.Length }).i),
+ Start = start,
+ Size = length,
+ SavedSize = WaterboxUtils.AlignUp(length),
+ R = r,
+ W = w,
+ X = x,
+ Prot = prot,
+ DiskStart = s.PointerToRawData,
+ DiskSize = s.SizeOfRawData
+ };
+
+ _sections.Add(section);
+ _sectionsByName.Add(section.Name, section);
+ }
+ _sectionsByName.TryGetValue(".idata", out _imports);
+
+ Mount();
+ }
+
+ ///
+ /// set memory protections.
+ ///
+ private void ProtectMemory()
+ {
+ Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.R);
+
+ foreach (var s in _sections)
+ {
+ Memory.Protect(s.Start, s.Size, s.Prot);
+ }
+ }
+
+ ///
+ /// load the PE into memory
+ ///
+ /// start address
+ private void Mount()
+ {
+ LoadOffset = (long)Start - (long)_pe.ImageNtHeaders.OptionalHeader.ImageBase;
+ Memory = new MemoryBlock(Start, Size);
+ Memory.Activate();
+ Memory.Protect(Start, Size, MemoryBlock.Protection.RW);
+
+ // copy headers
+ Marshal.Copy(_fileData, 0, Z.US(Start), (int)_pe.ImageNtHeaders.OptionalHeader.SizeOfHeaders);
+
+ // copy sections
+ foreach (var s in _sections)
+ {
+ ulong datalength = Math.Min(s.Size, s.DiskSize);
+ Marshal.Copy(_fileData, (int)s.DiskStart, Z.US(s.Start), (int)datalength);
+ WaterboxUtils.ZeroMemory(Z.US(s.Start + datalength), (long)(s.SavedSize - datalength));
+ }
+
+ // apply relocations
+ var n32 = 0;
+ var n64 = 0;
+ foreach (var rel in _pe.ImageRelocationDirectory)
+ {
+ foreach (var to in rel.TypeOffsets)
+ {
+ ulong address = Start + rel.VirtualAddress + to.Offset;
+
+ switch (to.Type)
+ {
+ // there are many other types of relocation specified,
+ // but the only that are used is 0 (does nothing), 3 (32 bit standard), 10 (64 bit standard)
+
+ case 3: // IMAGE_REL_BASED_HIGHLOW
+ {
+ byte[] tmp = new byte[4];
+ Marshal.Copy(Z.US(address), tmp, 0, 4);
+ uint val = BitConverter.ToUInt32(tmp, 0);
+ tmp = BitConverter.GetBytes((uint)(val + LoadOffset));
+ Marshal.Copy(tmp, 0, Z.US(address), 4);
+ n32++;
+ break;
+ }
+
+ case 10: // IMAGE_REL_BASED_DIR64
+ {
+ byte[] tmp = new byte[8];
+ Marshal.Copy(Z.US(address), tmp, 0, 8);
+ long val = BitConverter.ToInt64(tmp, 0);
+ tmp = BitConverter.GetBytes(val + LoadOffset);
+ Marshal.Copy(tmp, 0, Z.US(address), 8);
+ n64++;
+ break;
+ }
+ }
+ }
+ }
+ if (IntPtr.Size == 8 && n32 > 0)
+ {
+ // check mcmodel, etc
+ throw new InvalidOperationException("32 bit relocations found in 64 bit dll! This will fail.");
+ }
+ Console.WriteLine($"Processed {n32} 32 bit and {n64} 64 bit relocations");
+
+ ProtectMemory();
+
+ // publish exports
+ EntryPoint = Z.US(Start + _pe.ImageNtHeaders.OptionalHeader.AddressOfEntryPoint);
+ foreach (var export in _pe.ExportedFunctions)
+ {
+ if (export.Name != null)
+ ExportsByName.Add(export.Name, Z.US(Start + export.Address));
+ ExportsByOrdinal.Add(export.Ordinal, Z.US(Start + export.Address));
+ }
+
+ // collect information about imports
+ // NB: Hints are not the same as Ordinals
+ foreach (var import in _pe.ImportedFunctions)
+ {
+ Dictionary module;
+ if (!ImportsByModule.TryGetValue(import.DLL, out module))
+ {
+ module = new Dictionary();
+ ImportsByModule.Add(import.DLL, module);
+ }
+ var dest = Start + import.Thunk;
+ if (_imports == null || dest >= _imports.Start + _imports.Size || dest < _imports.Start)
+ throw new InvalidOperationException("Import record outside of .idata!");
+
+ module.Add(import.Name, Z.US(dest));
+ }
+
+ Section midipix;
+ if (_sectionsByName.TryGetValue(".midipix", out midipix))
+ {
+ var dataOffset = midipix.DiskStart;
+ CtorList = Z.SS(BitConverter.ToInt64(_fileData, (int)(dataOffset + 0x30)) + LoadOffset);
+ DtorList = Z.SS(BitConverter.ToInt64(_fileData, (int)(dataOffset + 0x38)) + LoadOffset);
+ }
+
+ Console.WriteLine($"Mounted `{ModuleName}` @{Start:x16}");
+ foreach (var s in _sections.OrderBy(s => s.Start))
+ {
+ Console.WriteLine(" @{0:x16} {1}{2}{3} `{4}` {5} bytes",
+ s.Start,
+ s.R ? "R" : " ",
+ s.W ? "W" : " ",
+ s.X ? "X" : " ",
+ s.Name,
+ s.Size);
+ }
+ Console.WriteLine("GDB Symbol Load:");
+ var symload = $"add-sym {ModuleName} {_sectionsByName[".text"].Start}";
+ if (_sectionsByName.ContainsKey(".data"))
+ symload += $" -s .data {_sectionsByName[".data"].Start}";
+ if (_sectionsByName.ContainsKey(".bss"))
+ symload += $" -s .bss {_sectionsByName[".bss"].Start}";
+ Console.WriteLine(symload);
+ }
+
+ public IntPtr Resolve(string entryPoint)
+ {
+ IntPtr ret;
+ ExportsByName.TryGetValue(entryPoint, out ret);
+ return ret;
+ }
+
+ public void ConnectImports(string moduleName, IImportResolver module)
+ {
+ // this is called once internally when bootstrapping, and externally
+ // when we need to restore a savestate from another run. so imports might or might not be sealed
+
+ if (_importsSealed && _imports != null)
+ Memory.Protect(_imports.Start, _imports.Size, MemoryBlock.Protection.RW);
+
+ Dictionary imports;
+ if (ImportsByModule.TryGetValue(moduleName, out imports))
+ {
+ foreach (var kvp in imports)
+ {
+ var valueArray = new IntPtr[] { module.SafeResolve(kvp.Key) };
+ Marshal.Copy(valueArray, 0, kvp.Value, 1);
+ }
+ }
+
+ if (_importsSealed && _imports != null)
+ Memory.Protect(_imports.Start, _imports.Size, _imports.Prot);
+ }
+
+ public void SealImportsAndTakeXorSnapshot()
+ {
+ if (_importsSealed)
+ throw new InvalidOperationException("Imports already sealed!");
+
+ // save import values, then zero them all (for hash purposes), then take our snapshot, then load them again,
+ // then set the .idata area to read only
+ if (_imports != null)
+ {
+ var data = new byte[_imports.Size];
+ Marshal.Copy(Z.US(_imports.Start), data, 0, (int)_imports.Size);
+ WaterboxUtils.ZeroMemory(Z.US(_imports.Start), (long)_imports.Size);
+ Memory.SaveXorSnapshot();
+ Marshal.Copy(data, 0, Z.US(_imports.Start), (int)_imports.Size);
+ _imports.W = false;
+ Memory.Protect(_imports.Start, _imports.Size, _imports.Prot);
+ }
+ else
+ {
+ Memory.SaveXorSnapshot();
+ }
+
+ _importsSealed = true;
+ }
+
+ private bool _disposed = false;
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ Memory.Dispose();
+ Memory = null;
+ _disposed = true;
+ }
+ }
+
+ const ulong MAGIC = 0x420cccb1a2e17420;
+
+ public void SaveStateBinary(BinaryWriter bw)
+ {
+ if (!_importsSealed)
+ throw new InvalidOperationException(".idata sections must be closed before saving state");
+
+ bw.Write(MAGIC);
+ bw.Write(_fileHash);
+ bw.Write(Memory.XorHash);
+ bw.Write(Start);
+
+ foreach (var s in _sections)
+ {
+ if (!s.W)
+ continue;
+
+ var ms = Memory.GetXorStream(s.Start, s.SavedSize, false);
+ bw.Write(s.SavedSize);
+ ms.CopyTo(bw.BaseStream);
+ }
+ }
+
+ public void LoadStateBinary(BinaryReader br)
+ {
+ if (!_importsSealed)
+ // operations happening in the wrong order. probable cause: internal logic error. make sure frontend calls Seal
+ throw new InvalidOperationException(".idata sections must be closed before loading state");
+
+ if (br.ReadUInt64() != MAGIC)
+ // file id is missing. probable cause: garbage savestate
+ throw new InvalidOperationException("Savestate corrupted!");
+ if (!br.ReadBytes(_fileHash.Length).SequenceEqual(_fileHash))
+ // the .dll file that is loaded now has a different hash than the .dll that created the savestate
+ throw new InvalidOperationException("Core consistency check failed. Is this a savestate from a different version?");
+ if (!br.ReadBytes(Memory.XorHash.Length).SequenceEqual(Memory.XorHash))
+ // the post-Seal memory state is different. probable cause: different rom or different version of rom,
+ // different syncsettings
+ throw new InvalidOperationException("Memory consistency check failed. Is this savestate from different SyncSettings?");
+ if (br.ReadUInt64() != Start)
+ // dll loaded somewhere else. probable cause: internal logic error.
+ // unlikely to get this far if the previous checks pssed
+ throw new InvalidOperationException("Trickys elves moved on you!");
+
+ Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.RW);
+
+ foreach (var s in _sections)
+ {
+ if (!s.W)
+ continue;
+
+ if (br.ReadUInt64() != s.SavedSize)
+ throw new InvalidOperationException("Unexpected section size for " + s.Name);
+
+ var ms = Memory.GetXorStream(s.Start, s.SavedSize, true);
+ WaterboxUtils.CopySome(br.BaseStream, ms, (long)s.SavedSize);
+ }
+
+ ProtectMemory();
+ }
+ }
+}
diff --git a/BizHawk.Emulation.Cores/Waterbox/Swappable.cs b/BizHawk.Emulation.Cores/Waterbox/Swappable.cs
index bee1f6a0ab..32bc1bf0f5 100644
--- a/BizHawk.Emulation.Cores/Waterbox/Swappable.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/Swappable.cs
@@ -1,5 +1,6 @@
using BizHawk.Common;
using BizHawk.Common.BizInvoke;
+using BizHawk.Common.BufferExtensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -30,9 +31,15 @@ namespace BizHawk.Emulation.Cores.Waterbox
///
private List _memoryBlocks = new List();
- protected void AddMemoryBlock(MemoryBlock block)
+ ///
+ /// an informative name for each memory block: used for debugging purposes
+ ///
+ private List _memoryBlockNames = new List();
+
+ protected void AddMemoryBlock(MemoryBlock block, string name)
{
_memoryBlocks.Add(block);
+ _memoryBlockNames.Add(name);
}
protected void PurgeMemoryBlocks()
@@ -130,6 +137,17 @@ namespace BizHawk.Emulation.Cores.Waterbox
foreach (var m in _memoryBlocks)
m.Activate();
}
+
+ public void PrintDebuggingInfo()
+ {
+ using (this.EnterExit())
+ {
+ foreach (var a in _memoryBlocks.Zip(_memoryBlockNames, (m, s) => new { m, s }))
+ {
+ Console.WriteLine($"{a.m.FullHash().BytesToHexString()}: {a.s}");
+ }
+ }
+ }
private bool _disposed = false;