diff --git a/BizHawk.Common/IMonitor.cs b/BizHawk.Common/IMonitor.cs
index a31335ef93..7b287f9ea2 100644
--- a/BizHawk.Common/IMonitor.cs
+++ b/BizHawk.Common/IMonitor.cs
@@ -1,8 +1,40 @@
-namespace BizHawk.Common
+using System;
+
+namespace BizHawk.Common
{
public interface IMonitor
{
void Enter();
void Exit();
}
+
+ public static class MonitorExtensions
+ {
+ public static IDisposable EnterExit(this IMonitor m)
+ {
+ var ret = new EnterExitWrapper(m);
+ m.Enter();
+ return ret;
+ }
+
+ private class EnterExitWrapper : IDisposable
+ {
+ private readonly IMonitor _m;
+ private bool _disposed = false;
+
+ public EnterExitWrapper(IMonitor m)
+ {
+ _m = m;
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _m.Exit();
+ _disposed = true;
+ }
+ }
+ }
+ }
}
diff --git a/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs b/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs
index d72ce948fc..0a27e2f67f 100644
--- a/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/ElfRunner.cs
@@ -12,10 +12,11 @@ using System.Security.Cryptography;
using System.IO;
using System.Collections.Concurrent;
using System.Threading;
+using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Waterbox
{
- public sealed class ElfRunner : Swappable, IImportResolver, IDisposable
+ public sealed class ElfRunner : Swappable, IImportResolver, IBinaryStateable, IDisposable
{
// TODO: a lot of things only work with our elves and aren't fully generalized
diff --git a/BizHawk.Emulation.Cores/Waterbox/Heap.cs b/BizHawk.Emulation.Cores/Waterbox/Heap.cs
index 1d83e7618d..cb73c3a751 100644
--- a/BizHawk.Emulation.Cores/Waterbox/Heap.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/Heap.cs
@@ -1,4 +1,5 @@
-using System;
+using BizHawk.Emulation.Common;
+using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -9,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
///
/// a simple grow-only fixed max size heap
///
- internal sealed class Heap : IDisposable
+ internal sealed class Heap : IBinaryStateable, IDisposable
{
public MemoryBlock Memory { get; private set; }
///
diff --git a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs
index 2c151b7f4f..2e67aa8dd4 100644
--- a/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs
+++ b/BizHawk.Emulation.Cores/Waterbox/PeRunner.cs
@@ -1,18 +1,18 @@
using BizHawk.Common;
+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
{
- public class PeRunner
+ public class PeRunner : Swappable, IImportResolver, IBinaryStateable
{
- private static readonly ulong CanonicalStart = 0x0000036f00000000;
-
- public class PeWrapper : IImportResolver
+ public class PeWrapper : IImportResolver, IBinaryStateable, IDisposable
{
public Dictionary ExportsByOrdinal { get; } = new Dictionary();
///
@@ -26,6 +26,7 @@ namespace BizHawk.Emulation.Cores.Waterbox
private readonly byte[] _fileData;
private readonly PeFile _pe;
+ private readonly byte[] _fileHash;
public ulong Size { get; }
public ulong Start { get; private set; }
@@ -47,6 +48,8 @@ namespace BizHawk.Emulation.Cores.Waterbox
{
throw new InvalidOperationException("Image not Big Enough");
}
+
+ _fileHash = WaterboxUtils.Hash(fileData);
}
///
@@ -54,6 +57,8 @@ namespace BizHawk.Emulation.Cores.Waterbox
///
public void FinishMount()
{
+ Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.R);
+
foreach (var s in _pe.ImageSectionHeaders)
{
ulong start = Start + s.VirtualAddress;
@@ -90,7 +95,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
ulong length = _pe.ImageNtHeaders.OptionalHeader.SizeOfHeaders;
Memory.Protect(Start, length, MemoryBlock.Protection.RW);
Marshal.Copy(_fileData, 0, Z.US(Start), (int)length);
- Memory.Protect(Start, length, MemoryBlock.Protection.R);
}
// copy sections
@@ -99,7 +103,6 @@ namespace BizHawk.Emulation.Cores.Waterbox
ulong start = Start + s.VirtualAddress;
ulong length = s.VirtualSize;
- Memory.Protect(start, length, MemoryBlock.Protection.RW);
Marshal.Copy(_fileData, (int)s.PointerToRawData, Z.US(start), (int)s.SizeOfRawData);
WaterboxUtils.ZeroMemory(Z.US(start + s.SizeOfRawData), (long)(length - s.SizeOfRawData));
}
@@ -181,6 +184,233 @@ namespace BizHawk.Emulation.Cores.Waterbox
}
}
}
+
+ private bool _disposed = false;
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ Memory.Dispose();
+ Memory = null;
+ _disposed = true;
+ }
+ }
+
+ const ulong MAGIC = 0x420cccb1a2e17420;
+
+ public void SaveStateBinary(BinaryWriter bw)
+ {
+ bw.Write(MAGIC);
+ bw.Write(_fileHash);
+ bw.Write(Start);
+
+ foreach (var s in _pe.ImageSectionHeaders)
+ {
+ if ((s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_WRITE) == 0)
+ continue;
+
+ ulong start = Start + s.VirtualAddress;
+ ulong length = s.VirtualSize;
+
+ var ms = Memory.GetStream(start, length, false);
+ bw.Write(length);
+ ms.CopyTo(bw.BaseStream);
+ }
+ }
+
+ public void LoadStateBinary(BinaryReader br)
+ {
+ if (br.ReadUInt64() != MAGIC)
+ throw new InvalidOperationException("Magic not magic enough!");
+ if (!br.ReadBytes(_fileHash.Length).SequenceEqual(_fileHash))
+ throw new InvalidOperationException("Elf changed disguise!");
+ if (br.ReadUInt64() != Start)
+ throw new InvalidOperationException("Trickys elves moved on you!");
+
+ Memory.Protect(Memory.Start, Memory.Size, MemoryBlock.Protection.RW);
+
+ foreach (var s in _pe.ImageSectionHeaders)
+ {
+ if ((s.Characteristics & (uint)Constants.SectionFlags.IMAGE_SCN_MEM_WRITE) == 0)
+ continue;
+
+ ulong start = Start + s.VirtualAddress;
+ ulong length = s.VirtualSize;
+
+ if (br.ReadUInt64() != length)
+ throw new InvalidOperationException("Unexpected section size for " + s.Name);
+
+ var ms = Memory.GetStream(start, length, true);
+ WaterboxUtils.CopySome(br.BaseStream, ms, (long)length);
+ }
+
+ FinishMount();
+ }
+ }
+
+ // usual starting address for the executable
+ private static readonly ulong CanonicalStart = 0x0000036f00000000;
+
+ ///
+ /// 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;
+
+ private readonly List _modules = new List();
+
+ private readonly List _disposeList = new List();
+
+ private readonly List _savestateComponents = new List();
+
+ public PeRunner(string directory, string filename, ulong heapsize, ulong sealedheapsize, ulong invisibleheapsize)
+ {
+ Enter();
+ try
+ {
+ // load and connect all modules, starting with the executable
+ var todoModules = new Queue();
+ todoModules.Enqueue(filename);
+ var loadedModules = new Dictionary();
+
+ while (todoModules.Count > 0)
+ {
+ var moduleName = todoModules.Dequeue();
+ if (!loadedModules.ContainsKey(moduleName))
+ {
+ var module = new PeWrapper(moduleName, File.ReadAllBytes(Path.Combine(directory, moduleName)));
+ module.Mount(_nextStart);
+ ComputeNextStart(module.Size);
+ AddMemoryBlock(module.Memory);
+ _savestateComponents.Add(module);
+ _disposeList.Add(module);
+
+ loadedModules.Add(moduleName, module);
+ _modules.Add(module);
+ foreach (var name in module.ImportsByModule.Keys)
+ {
+ todoModules.Enqueue(name);
+ }
+ }
+ }
+
+ foreach (var module in _modules)
+ {
+ foreach (var name in module.ImportsByModule.Keys)
+ {
+ module.ConnectImports(name, loadedModules[name]);
+ }
+ }
+ foreach (var module in _modules)
+ {
+ module.FinishMount();
+ }
+
+ // load all heaps
+ _heap = new Heap(_nextStart, heapsize, "brk-heap");
+ _heap.Memory.Activate();
+ ComputeNextStart(heapsize);
+ AddMemoryBlock(_heap.Memory);
+ _savestateComponents.Add(_heap);
+ _disposeList.Add(_heap);
+
+ _sealedheap = new Heap(_nextStart, sealedheapsize, "sealed-heap");
+ _sealedheap.Memory.Activate();
+ ComputeNextStart(sealedheapsize);
+ AddMemoryBlock(_sealedheap.Memory);
+ _savestateComponents.Add(_sealedheap);
+ _disposeList.Add(_sealedheap);
+
+ _invisibleheap = new Heap(_nextStart, invisibleheapsize, "invisible-heap");
+ _invisibleheap.Memory.Activate();
+ ComputeNextStart(invisibleheapsize);
+ AddMemoryBlock(_invisibleheap.Memory);
+ _savestateComponents.Add(_invisibleheap);
+ _disposeList.Add(_invisibleheap);
+ }
+ catch
+ {
+ Dispose();
+ throw;
+ }
+ finally
+ {
+ Exit();
+ }
+ }
+
+ public IntPtr Resolve(string entryPoint)
+ {
+ // modules[0] is always the main module
+ return _modules[0].Resolve(entryPoint);
+ }
+
+ public void SaveStateBinary(BinaryWriter bw)
+ {
+ using (this.EnterExit())
+ {
+ bw.Write(_savestateComponents.Count);
+ foreach (var c in _savestateComponents)
+ {
+ c.SaveStateBinary(bw);
+ }
+ }
+ }
+
+ public void LoadStateBinary(BinaryReader br)
+ {
+ if (br.ReadInt32() != _savestateComponents.Count)
+ throw new InvalidOperationException("Internal savestate error");
+ using (this.EnterExit())
+ {
+ foreach (var c in _savestateComponents)
+ {
+ c.LoadStateBinary(br);
+ }
+ }
+ }
+
+ private bool _disposed = false;
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ foreach (var d in _disposeList)
+ d.Dispose();
+ _disposeList.Clear();
+ PurgeMemoryBlocks();
+ _modules.Clear();
+ _heap = null;
+ _sealedheap = null;
+ _invisibleheap = null;
+ _disposed = true;
+ }
}
}
}