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.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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -30,48 +29,41 @@ 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
|
ServiceProvider = new BasicServiceProvider(this);
|
||||||
? (IFPCtrl) new Unix_FPCtrl()
|
Context = QN.qn_new();
|
||||||
: new Win32_FPCtrl();
|
if (Context == IntPtr.Zero)
|
||||||
|
|
||||||
using (FP.Save())
|
|
||||||
{
|
{
|
||||||
ServiceProvider = new BasicServiceProvider(this);
|
throw new InvalidOperationException($"{nameof(QN.qn_new)}() returned NULL");
|
||||||
Context = QN.qn_new();
|
}
|
||||||
if (Context == IntPtr.Zero)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"{nameof(QN.qn_new)}() returned NULL");
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
file = FixInesHeader(file);
|
file = FixInesHeader(file);
|
||||||
LibQuickNES.ThrowStringError(QN.qn_loadines(Context, file, file.Length));
|
LibQuickNES.ThrowStringError(QN.qn_loadines(Context, file, file.Length));
|
||||||
|
|
||||||
InitSaveRamBuff();
|
InitSaveRamBuff();
|
||||||
InitSaveStateBuff();
|
InitSaveStateBuff();
|
||||||
InitAudio();
|
InitAudio();
|
||||||
InitMemoryDomains();
|
InitMemoryDomains();
|
||||||
|
|
||||||
int mapper = 0;
|
int mapper = 0;
|
||||||
string mappername = Marshal.PtrToStringAnsi(QN.qn_get_mapper(Context, ref mapper));
|
string mappername = Marshal.PtrToStringAnsi(QN.qn_get_mapper(Context, ref mapper));
|
||||||
Console.WriteLine("QuickNES: Booted with Mapper #{0} \"{1}\"", mapper, mappername);
|
Console.WriteLine("QuickNES: Booted with Mapper #{0} \"{1}\"", mapper, mappername);
|
||||||
BoardName = mappername;
|
BoardName = mappername;
|
||||||
PutSettings((QuickNESSettings)settings ?? new QuickNESSettings());
|
PutSettings((QuickNESSettings)settings ?? new QuickNESSettings());
|
||||||
|
|
||||||
_syncSettings = (QuickNESSyncSettings)syncSettings ?? new QuickNESSyncSettings();
|
_syncSettings = (QuickNESSyncSettings)syncSettings ?? new QuickNESSyncSettings();
|
||||||
_syncSettingsNext = _syncSettings.Clone();
|
_syncSettingsNext = _syncSettings.Clone();
|
||||||
|
|
||||||
SetControllerDefinition();
|
SetControllerDefinition();
|
||||||
ComputeBootGod();
|
ComputeBootGod();
|
||||||
|
|
||||||
ConnectTracer();
|
ConnectTracer();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
Dispose();
|
Dispose();
|
||||||
throw;
|
throw;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,34 +143,32 @@ 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"))
|
|
||||||
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));
|
QN.qn_set_tracecb(Context, Tracer.IsEnabled() ? _traceCb : null);
|
||||||
IsLagFrame = QN.qn_get_joypad_read_count(Context) == 0;
|
|
||||||
if (IsLagFrame)
|
|
||||||
LagCount++;
|
|
||||||
|
|
||||||
if (render)
|
LibQuickNES.ThrowStringError(QN.qn_emulate_frame(Context, j1, j2));
|
||||||
Blit();
|
IsLagFrame = QN.qn_get_joypad_read_count(Context) == 0;
|
||||||
if (rendersound)
|
if (IsLagFrame)
|
||||||
DrainAudio();
|
LagCount++;
|
||||||
|
|
||||||
_callBack1?.Invoke();
|
if (render)
|
||||||
_callBack2?.Invoke();
|
Blit();
|
||||||
|
if (rendersound)
|
||||||
|
DrainAudio();
|
||||||
|
|
||||||
Frame++;
|
_callBack1?.Invoke();
|
||||||
|
_callBack2?.Invoke();
|
||||||
|
|
||||||
return true;
|
Frame++;
|
||||||
}
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IntPtr Context;
|
private IntPtr Context;
|
||||||
|
|
Loading…
Reference in New Issue