This commit is contained in:
CasualPokePlayer 2023-08-10 00:05:45 -07:00
parent 1cf5af83dd
commit 8260a59bcf
3 changed files with 117 additions and 91 deletions

View File

@ -0,0 +1,67 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace BizHawk.BizInvoke
{
// .NET is weird and sets the x87 control register to double precision
// It does this even on Linux which normally expects it set to extended x87 precision
// Even stranger is that this appears to be completely unneeded on x86-64
// On Windows, x87 registers are prohibited to be used in kernel code, and
// MSVC will not use the x87 registers (and presumably this extends to userland OS code)
// .NET presumbly follows MSVC and would not use the x87 registers (why would it? SSE is available! long double doesn't exist!)
// This thus only screws over code compiled with MinGW (which use extended x87 precision for long double)
// Or screws over any unmanaged code on Linux, which doubly includes all waterbox cores (!!!)
// Of course in practice, this only applies to any code using long double, which is rarely used typically
// But musl (used for waterbox cores) does end up using it for float formating in the printf family
// This can extend to issues in games: https://github.com/TASEmulators/BizHawk/issues/3726
public static class FPCtrl
{
private const CallingConvention cc = CallingConvention.Cdecl;
[UnmanagedFunctionPointer(cc)]
private delegate void FixFPCtrlDelegate();
private static readonly MemoryBlock? _memory;
private static readonly FixFPCtrlDelegate? _fixFpCtrl;
static FPCtrl()
{
// not usable outside of x64
if (RuntimeInformation.ProcessArchitecture != Architecture.X64)
{
return;
}
// generate assembly for fixing the x87 control register
_memory = new(4096);
_memory.Protect(_memory.Start, _memory.Size, MemoryBlock.Protection.RW);
var ss = _memory.GetStream(_memory.Start, 64, true);
var bw = new BinaryWriter(ss);
// FYI: The push/pop is only needed on Windows, but doesn't do any harm on Linux
bw.Write((byte)0x50); // push rax
bw.Write(0x06247CD9); // fnstcw word[rsp + 6]
bw.Write(0x07244C80); // or byte[rsp + 7], 3
bw.Write((byte)0x03);
bw.Write(0x06246CD9); // fldcw word[rsp + 6]
bw.Write((byte)0x58); // pop rax
bw.Write((byte)0xC3); // ret
_memory.Protect(_memory.Start, _memory.Size, MemoryBlock.Protection.RX);
_fixFpCtrl = Marshal.GetDelegateForFunctionPointer<FixFPCtrlDelegate>((IntPtr)_memory.Start);
}
public static void FixFPCtrl()
{
// not usable outside of x64
if (RuntimeInformation.ProcessArchitecture != Architecture.X64)
{
return;
}
_fixFpCtrl!();
}
}
}

View File

@ -6,6 +6,7 @@ using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Windows.Forms; using System.Windows.Forms;
using BizHawk.BizInvoke;
using BizHawk.Bizware.BizwareGL; using BizHawk.Bizware.BizwareGL;
using BizHawk.Bizware.Graphics; using BizHawk.Bizware.Graphics;
using BizHawk.Common; using BizHawk.Common;
@ -285,6 +286,8 @@ namespace BizHawk.Client.EmuHawk
} }
} }
FPCtrl.FixFPCtrl();
var exitCode = 0; var exitCode = 0;
try try
{ {

View File

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@ -29,12 +28,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
[CoreConstructor(VSystemID.Raw.NES, Priority = CorePriority.Low)] [CoreConstructor(VSystemID.Raw.NES, Priority = CorePriority.Low)]
public QuickNES(byte[] file, QuickNESSettings settings, QuickNESSyncSettings syncSettings) public QuickNES(byte[] file, QuickNESSettings settings, QuickNESSyncSettings syncSettings)
{
FP = OSTailoredCode.IsUnixHost
? (IFPCtrl) new Unix_FPCtrl()
: new Win32_FPCtrl();
using (FP.Save())
{ {
ServiceProvider = new BasicServiceProvider(this); ServiceProvider = new BasicServiceProvider(this);
Context = QN.qn_new(); Context = QN.qn_new();
@ -73,7 +66,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
throw; throw;
} }
} }
}
private static readonly LibQuickNES QN; private static readonly LibQuickNES QN;
@ -83,40 +75,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
int IVideoLogicalOffsets.ScreenY => _settings.ClipTopAndBottom ? 8 : 0; int IVideoLogicalOffsets.ScreenY => _settings.ClipTopAndBottom ? 8 : 0;
private interface IFPCtrl : IDisposable
{
IDisposable Save();
}
private class Win32_FPCtrl : IFPCtrl
{
[Conditional("DEBUG")]
public static void PrintCurrentFP() => Console.WriteLine($"Current FP word: 0x{Win32Imports._control87(0, 0):X8}");
private uint cw;
public IDisposable Save()
{
cw = Win32Imports._control87(0, 0);
Win32Imports._control87(0x00000, 0x30000);
return this;
}
public void Dispose()
{
Win32Imports._control87(cw, 0x30000);
}
}
private class Unix_FPCtrl : IFPCtrl
{
public IDisposable Save() => this;
public void Dispose() {}
}
private readonly IFPCtrl FP;
public ControllerDefinition ControllerDefinition { get; private set; } public ControllerDefinition ControllerDefinition { get; private set; }
private void SetControllerDefinition() private void SetControllerDefinition()
@ -185,8 +143,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
public bool FrameAdvance(IController controller, bool render, bool rendersound = true) public bool FrameAdvance(IController controller, bool render, bool rendersound = true)
{ {
CheckDisposed(); CheckDisposed();
using (FP.Save())
{
if (controller.IsPressed("Power")) if (controller.IsPressed("Power"))
QN.qn_reset(Context, true); QN.qn_reset(Context, true);
if (controller.IsPressed("Reset")) if (controller.IsPressed("Reset"))
@ -213,7 +170,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
return true; return true;
} }
}
private IntPtr Context; private IntPtr Context;
public int Frame { get; private set; } public int Frame { get; private set; }