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.Windows.Forms;
using BizHawk.BizInvoke;
using BizHawk.Bizware.BizwareGL;
using BizHawk.Bizware.Graphics;
using BizHawk.Common;
@ -285,6 +286,8 @@ namespace BizHawk.Client.EmuHawk
}
}
FPCtrl.FixFPCtrl();
var exitCode = 0;
try
{

View File

@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
@ -30,48 +29,41 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
[CoreConstructor(VSystemID.Raw.NES, Priority = CorePriority.Low)]
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);
Context = QN.qn_new();
if (Context == IntPtr.Zero)
{
ServiceProvider = new BasicServiceProvider(this);
Context = QN.qn_new();
if (Context == IntPtr.Zero)
{
throw new InvalidOperationException($"{nameof(QN.qn_new)}() returned NULL");
}
throw new InvalidOperationException($"{nameof(QN.qn_new)}() returned NULL");
}
try
{
file = FixInesHeader(file);
LibQuickNES.ThrowStringError(QN.qn_loadines(Context, file, file.Length));
try
{
file = FixInesHeader(file);
LibQuickNES.ThrowStringError(QN.qn_loadines(Context, file, file.Length));
InitSaveRamBuff();
InitSaveStateBuff();
InitAudio();
InitMemoryDomains();
InitSaveRamBuff();
InitSaveStateBuff();
InitAudio();
InitMemoryDomains();
int mapper = 0;
string mappername = Marshal.PtrToStringAnsi(QN.qn_get_mapper(Context, ref mapper));
Console.WriteLine("QuickNES: Booted with Mapper #{0} \"{1}\"", mapper, mappername);
BoardName = mappername;
PutSettings((QuickNESSettings)settings ?? new QuickNESSettings());
int mapper = 0;
string mappername = Marshal.PtrToStringAnsi(QN.qn_get_mapper(Context, ref mapper));
Console.WriteLine("QuickNES: Booted with Mapper #{0} \"{1}\"", mapper, mappername);
BoardName = mappername;
PutSettings((QuickNESSettings)settings ?? new QuickNESSettings());
_syncSettings = (QuickNESSyncSettings)syncSettings ?? new QuickNESSyncSettings();
_syncSettingsNext = _syncSettings.Clone();
_syncSettings = (QuickNESSyncSettings)syncSettings ?? new QuickNESSyncSettings();
_syncSettingsNext = _syncSettings.Clone();
SetControllerDefinition();
ComputeBootGod();
SetControllerDefinition();
ComputeBootGod();
ConnectTracer();
}
catch
{
Dispose();
throw;
}
ConnectTracer();
}
catch
{
Dispose();
throw;
}
}
@ -83,40 +75,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
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; }
private void SetControllerDefinition()
@ -185,34 +143,32 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
public bool FrameAdvance(IController controller, bool render, bool rendersound = true)
{
CheckDisposed();
using (FP.Save())
{
if (controller.IsPressed("Power"))
QN.qn_reset(Context, true);
if (controller.IsPressed("Reset"))
QN.qn_reset(Context, false);
SetPads(controller, out var j1, out var j2);
if (controller.IsPressed("Power"))
QN.qn_reset(Context, true);
if (controller.IsPressed("Reset"))
QN.qn_reset(Context, false);
QN.qn_set_tracecb(Context, Tracer.IsEnabled() ? _traceCb : null);
SetPads(controller, out var j1, out var j2);
LibQuickNES.ThrowStringError(QN.qn_emulate_frame(Context, j1, j2));
IsLagFrame = QN.qn_get_joypad_read_count(Context) == 0;
if (IsLagFrame)
LagCount++;
QN.qn_set_tracecb(Context, Tracer.IsEnabled() ? _traceCb : null);
if (render)
Blit();
if (rendersound)
DrainAudio();
LibQuickNES.ThrowStringError(QN.qn_emulate_frame(Context, j1, j2));
IsLagFrame = QN.qn_get_joypad_read_count(Context) == 0;
if (IsLagFrame)
LagCount++;
_callBack1?.Invoke();
_callBack2?.Invoke();
if (render)
Blit();
if (rendersound)
DrainAudio();
Frame++;
_callBack1?.Invoke();
_callBack2?.Invoke();
return true;
}
Frame++;
return true;
}
private IntPtr Context;