fix #3726
This commit is contained in:
parent
1cf5af83dd
commit
8260a59bcf
|
@ -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!();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue