improve Program startup, remove the OSTailoredCode duplication hack, use RuntimeInformation.IsOSPlatform for figuring out OS, use AppContext.BaseDirectory to get exe location (apparently even works with bundled assemblies in .net5+)
This commit is contained in:
parent
eca0ca41a9
commit
859ea8e249
|
@ -22,7 +22,7 @@ namespace BizHawk.Bizware.Test
|
||||||
{
|
{
|
||||||
var firstAsm = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), asm => asm.FullName == args.Name);
|
var firstAsm = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), asm => asm.FullName == args.Name);
|
||||||
if (firstAsm is not null) return firstAsm;
|
if (firstAsm is not null) return firstAsm;
|
||||||
var guessFilename = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "dll", $"{new AssemblyName(args.Name).Name}.dll");
|
var guessFilename = Path.Combine(AppContext.BaseDirectory, "dll", $"{new AssemblyName(args.Name).Name}.dll");
|
||||||
return File.Exists(guessFilename) ? Assembly.LoadFile(guessFilename) : null;
|
return File.Exists(guessFilename) ? Assembly.LoadFile(guessFilename) : null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<ApplicationIcon>discohawk.ico</ApplicationIcon>
|
<ApplicationIcon>discohawk.ico</ApplicationIcon>
|
||||||
<Nullable>disable</Nullable>
|
<Nullable>disable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System.Windows.Forms" />
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
|
|
@ -3,74 +3,82 @@ using System.Runtime.InteropServices;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using BizHawk.Emulation.DiscSystem;
|
using System.Windows.Forms;
|
||||||
|
|
||||||
using OSTC = EXE_PROJECT.OSTailoredCode;
|
using BizHawk.Common;
|
||||||
|
using BizHawk.Emulation.DiscSystem;
|
||||||
|
|
||||||
namespace BizHawk.Client.DiscoHawk
|
namespace BizHawk.Client.DiscoHawk
|
||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
|
// Declared here instead of a more usual place to avoid dependencies on the more usual place
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool SetDllDirectoryW(string lpPathName);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool DeleteFileW(string lpFileName);
|
||||||
|
|
||||||
static Program()
|
static Program()
|
||||||
{
|
{
|
||||||
if (OSTC.IsUnixHost)
|
// in case assembly resolution fails, such as if we moved them into the dll subdirectory, this event handler can reroute to them
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||||
|
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
// for Unix, skip everything else and just wire up the event handler
|
// Windows needs extra considerations for the dll directory
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
// we can skip all this on non-Windows platforms
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://www.codeproject.com/Articles/310675/AppDomain-AssemblyResolve-Event-Tips
|
// http://www.codeproject.com/Articles/310675/AppDomain-AssemblyResolve-Event-Tips
|
||||||
// this will look in subdirectory "dll" to load pinvoked stuff
|
// this will look in subdirectory "dll" to load pinvoked stuff
|
||||||
string dllDir = Path.Combine(GetExeDirectoryAbsolute(), "dll");
|
var dllDir = Path.Combine(AppContext.BaseDirectory, "dll");
|
||||||
SetDllDirectory(dllDir);
|
SetDllDirectoryW(dllDir);
|
||||||
|
|
||||||
// in case assembly resolution fails, such as if we moved them into the dll subdirectory, this event handler can reroute to them
|
try
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
|
||||||
|
|
||||||
// but before we even try doing that, whack the MOTW from everything in that directory (that's a dll)
|
|
||||||
// otherwise, some people will have crashes at boot-up due to .net security disliking MOTW.
|
|
||||||
// some people are getting MOTW through a combination of browser used to download BizHawk, and program used to dearchive it
|
|
||||||
static void RemoveMOTW(string path) => DeleteFileW($"{path}:Zone.Identifier");
|
|
||||||
var todo = new Queue<DirectoryInfo>(new[] { new DirectoryInfo(dllDir) });
|
|
||||||
while (todo.Count != 0)
|
|
||||||
{
|
{
|
||||||
var di = todo.Dequeue();
|
// but before we even try doing that, whack the MOTW from everything in that directory (that's a dll)
|
||||||
foreach (var diSub in di.GetDirectories()) todo.Enqueue(diSub);
|
// otherwise, some people will have crashes at boot-up due to .net security disliking MOTW.
|
||||||
foreach (var fi in di.GetFiles("*.dll")) RemoveMOTW(fi.FullName);
|
// some people are getting MOTW through a combination of browser used to download bizhawk, and program used to dearchive it
|
||||||
foreach (var fi in di.GetFiles("*.exe")) RemoveMOTW(fi.FullName);
|
static void RemoveMOTW(string path) => DeleteFileW($"{path}:Zone.Identifier");
|
||||||
|
var todo = new Queue<DirectoryInfo>(new[] { new DirectoryInfo(dllDir) });
|
||||||
|
while (todo.Count != 0)
|
||||||
|
{
|
||||||
|
var di = todo.Dequeue();
|
||||||
|
foreach (var disub in di.GetDirectories()) todo.Enqueue(disub);
|
||||||
|
foreach (var fi in di.GetFiles("*.dll")) RemoveMOTW(fi.FullName);
|
||||||
|
foreach (var fi in di.GetFiles("*.exe")) RemoveMOTW(fi.FullName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"MotW remover failed: {e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[STAThread]
|
[STAThread]
|
||||||
private static void Main(string[] args)
|
private static void Main(string[] args)
|
||||||
{
|
{
|
||||||
SubMain(args);
|
if (!OSTailoredCode.IsUnixHost)
|
||||||
}
|
|
||||||
|
|
||||||
// NoInlining should keep this code from getting jammed into Main() which would create dependencies on types which haven't been setup by the resolver yet... or something like that
|
|
||||||
[DllImport("user32.dll", SetLastError = true)]
|
|
||||||
public static extern bool ChangeWindowMessageFilterEx(IntPtr hWnd, uint msg, ChangeWindowMessageFilterExAction action, ref CHANGEFILTERSTRUCT changeInfo);
|
|
||||||
|
|
||||||
private static void SubMain(string[] args)
|
|
||||||
{
|
|
||||||
if (!OSTC.IsUnixHost)
|
|
||||||
{
|
{
|
||||||
// MICROSOFT BROKE DRAG AND DROP IN WINDOWS 7. IT DOESN'T WORK ANYMORE
|
// MICROSOFT BROKE DRAG AND DROP IN WINDOWS 7. IT DOESN'T WORK ANYMORE
|
||||||
// WELL, OBVIOUSLY IT DOES SOMETIMES. I DON'T REMEMBER THE DETAILS OR WHY WE HAD TO DO THIS SHIT
|
// WELL, OBVIOUSLY IT DOES SOMETIMES. I DON'T REMEMBER THE DETAILS OR WHY WE HAD TO DO THIS SHIT
|
||||||
// BUT THE FUNCTION WE NEED DOESN'T EXIST UNTIL WINDOWS 7, CONVENIENTLY
|
const uint WM_DROPFILES = 0x0233;
|
||||||
// SO CHECK FOR IT
|
const uint WM_COPYDATA = 0x004A;
|
||||||
IntPtr lib = OSTC.LinkedLibManager.LoadOrThrow("user32.dll");
|
const uint WM_COPYGLOBALDATA = 0x0049;
|
||||||
IntPtr proc = OSTC.LinkedLibManager.GetProcAddrOrZero(lib, "ChangeWindowMessageFilterEx");
|
WmImports.ChangeWindowMessageFilter(WM_DROPFILES, WmImports.ChangeWindowMessageFilterFlags.Add);
|
||||||
if (proc != IntPtr.Zero)
|
WmImports.ChangeWindowMessageFilter(WM_COPYDATA, WmImports.ChangeWindowMessageFilterFlags.Add);
|
||||||
{
|
WmImports.ChangeWindowMessageFilter(WM_COPYGLOBALDATA, WmImports.ChangeWindowMessageFilterFlags.Add);
|
||||||
ChangeWindowMessageFilter(WM_DROPFILES, ChangeWindowMessageFilterFlags.Add);
|
|
||||||
ChangeWindowMessageFilter(WM_COPYDATA, ChangeWindowMessageFilterFlags.Add);
|
|
||||||
ChangeWindowMessageFilter(0x0049, ChangeWindowMessageFilterFlags.Add);
|
|
||||||
}
|
|
||||||
OSTC.LinkedLibManager.FreeByPtr(lib);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Do something for visuals, I guess
|
||||||
|
Application.EnableVisualStyles();
|
||||||
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
|
||||||
if (args.Length == 0)
|
if (args.Length == 0)
|
||||||
{
|
{
|
||||||
using var dialog = new MainDiscoForm();
|
using var dialog = new MainDiscoForm();
|
||||||
|
@ -88,61 +96,26 @@ namespace BizHawk.Client.DiscoHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <remarks>http://www.codeproject.com/Articles/310675/AppDomain-AssemblyResolve-Event-Tips</remarks>
|
||||||
public static string GetExeDirectoryAbsolute()
|
|
||||||
{
|
|
||||||
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||||
{
|
{
|
||||||
|
var requested = args.Name;
|
||||||
|
|
||||||
lock (AppDomain.CurrentDomain)
|
lock (AppDomain.CurrentDomain)
|
||||||
{
|
{
|
||||||
var asms = AppDomain.CurrentDomain.GetAssemblies();
|
var firstAsm = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), asm => asm.FullName == requested);
|
||||||
foreach (var asm in asms)
|
if (firstAsm != null)
|
||||||
if (asm.FullName == args.Name)
|
{
|
||||||
return asm;
|
return firstAsm;
|
||||||
|
}
|
||||||
//load missing assemblies by trying to find them in the dll directory
|
|
||||||
string dllName = $"{new AssemblyName(args.Name).Name}.dll";
|
|
||||||
string directory = Path.Combine(GetExeDirectoryAbsolute(), "dll");
|
|
||||||
string fname = Path.Combine(directory, dllName);
|
|
||||||
return File.Exists(fname) ? Assembly.LoadFile(fname) : null;
|
|
||||||
|
|
||||||
|
// load missing assemblies by trying to find them in the dll directory
|
||||||
|
var dllname = $"{new AssemblyName(requested).Name}.dll";
|
||||||
|
var directory = Path.Combine(AppContext.BaseDirectory, "dll");
|
||||||
|
var fname = Path.Combine(directory, dllname);
|
||||||
// it is important that we use LoadFile here and not load from a byte array; otherwise mixed (managed/unmanaged) assemblies can't load
|
// it is important that we use LoadFile here and not load from a byte array; otherwise mixed (managed/unmanaged) assemblies can't load
|
||||||
|
return File.Exists(fname) ? Assembly.LoadFile(fname) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//declared here instead of a more usual place to avoid dependencies on the more usual place
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
|
||||||
private static extern bool SetDllDirectory(string lpPathName);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", EntryPoint = "DeleteFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
|
||||||
private static extern bool DeleteFileW([MarshalAs(UnmanagedType.LPWStr)]string lpFileName);
|
|
||||||
|
|
||||||
private const uint WM_DROPFILES = 0x0233;
|
|
||||||
private const uint WM_COPYDATA = 0x004A;
|
|
||||||
[DllImport("user32")]
|
|
||||||
public static extern bool ChangeWindowMessageFilter(uint msg, ChangeWindowMessageFilterFlags flags);
|
|
||||||
public enum ChangeWindowMessageFilterFlags : uint
|
|
||||||
{
|
|
||||||
Add = 1, Remove = 2
|
|
||||||
}
|
|
||||||
public enum MessageFilterInfo : uint
|
|
||||||
{
|
|
||||||
None = 0, AlreadyAllowed = 1, AlreadyDisAllowed = 2, AllowedHigher = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum ChangeWindowMessageFilterExAction : uint
|
|
||||||
{
|
|
||||||
Reset = 0, Allow = 1, DisAllow = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
public struct CHANGEFILTERSTRUCT
|
|
||||||
{
|
|
||||||
public uint size;
|
|
||||||
public MessageFilterInfo info;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string execPath = Assembly.GetEntryAssembly().Location;
|
var execPath = AppContext.BaseDirectory;
|
||||||
dynamic ji = Activator.CreateInstance(JumpTask);
|
dynamic ji = Activator.CreateInstance(JumpTask);
|
||||||
|
|
||||||
ji.ApplicationPath = execPath;
|
ji.ApplicationPath = execPath;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
|
@ -13,60 +14,54 @@ using BizHawk.Common.PathExtensions;
|
||||||
using BizHawk.Client.Common;
|
using BizHawk.Client.Common;
|
||||||
using BizHawk.Client.EmuHawk.CustomControls;
|
using BizHawk.Client.EmuHawk.CustomControls;
|
||||||
|
|
||||||
using OSTC = EXE_PROJECT.OSTailoredCode;
|
|
||||||
|
|
||||||
namespace BizHawk.Client.EmuHawk
|
namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
internal static class Program
|
internal static class Program
|
||||||
{
|
{
|
||||||
|
// Declared here instead of a more usual place to avoid dependencies on the more usual place
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool SetDllDirectoryW(string lpPathName);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
private static extern bool DeleteFileW(string lpFileName);
|
||||||
|
|
||||||
static Program()
|
static Program()
|
||||||
{
|
{
|
||||||
//this needs to be done before the warnings/errors show up
|
// This needs to be done before the warnings/errors show up
|
||||||
Application.EnableVisualStyles();
|
Application.EnableVisualStyles();
|
||||||
Application.SetCompatibleTextRenderingDefault(false);
|
Application.SetCompatibleTextRenderingDefault(false);
|
||||||
|
|
||||||
// quickly check if the user is running this as a 32 bit process somehow
|
// Quickly check if the user is running this as a 32 bit process somehow
|
||||||
|
// TODO: We may want to remove this sometime, EmuHawk should be able to run somewhat as 32 bit if the user really wants to
|
||||||
|
// (There are no longer any hard 64 bit deps, i.e. SlimDX is no longer around)
|
||||||
if (!Environment.Is64BitProcess)
|
if (!Environment.Is64BitProcess)
|
||||||
{
|
{
|
||||||
using (var box = new ExceptionBox($"EmuHawk requires a 64 bit environment in order to run! EmuHawk will now close.")) box.ShowDialog();
|
using (var box = new ExceptionBox(
|
||||||
Process.GetCurrentProcess().Kill();
|
"EmuHawk requires a 64 bit environment in order to run! EmuHawk will now close."))
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (OSTC.IsUnixHost)
|
|
||||||
{
|
|
||||||
// for Unix, skip everything else and just wire up the event handler
|
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (dllToLoad, desc) in new[]
|
|
||||||
{
|
|
||||||
("vcruntime140_1.dll", "Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017 and 2019 (x64)"),
|
|
||||||
("msvcr100.dll", "Microsoft Visual C++ 2010 SP1 Runtime (x64)"), // for Mupen64Plus, and some others
|
|
||||||
})
|
|
||||||
{
|
|
||||||
var p = OSTC.LinkedLibManager.LoadOrZero(dllToLoad);
|
|
||||||
if (p != IntPtr.Zero)
|
|
||||||
{
|
|
||||||
OSTC.LinkedLibManager.FreeByPtr(p);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// else it's missing or corrupted
|
|
||||||
using (ExceptionBox box = new($"EmuHawk needs {desc} in order to run! See the readme on GitHub for more info. (EmuHawk will now close.) Internal error message: {OSTC.LinkedLibManager.GetErrorMessage()}"))
|
|
||||||
{
|
{
|
||||||
box.ShowDialog();
|
box.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
Process.GetCurrentProcess().Kill();
|
Process.GetCurrentProcess().Kill();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In case assembly resolution fails, such as if we moved them into the dll subdiretory, this event handler can reroute to them
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||||
|
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
// Windows needs extra considerations for the dll directory
|
||||||
|
// we can skip all this on non-Windows platforms
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// this will look in subdirectory "dll" to load pinvoked stuff
|
// this will look in subdirectory "dll" to load pinvoked stuff
|
||||||
var dllDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "dll");
|
var dllDir = Path.Combine(AppContext.BaseDirectory, "dll");
|
||||||
_ = SetDllDirectory(dllDir);
|
_ = SetDllDirectoryW(dllDir);
|
||||||
|
|
||||||
//in case assembly resolution fails, such as if we moved them into the dll subdiretory, this event handler can reroute to them
|
|
||||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -94,7 +89,7 @@ namespace BizHawk.Client.EmuHawk
|
||||||
private static int Main(string[] args)
|
private static int Main(string[] args)
|
||||||
{
|
{
|
||||||
var exitCode = SubMain(args);
|
var exitCode = SubMain(args);
|
||||||
if (OSTC.IsUnixHost)
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
{
|
{
|
||||||
Console.WriteLine("BizHawk has completed its shutdown routines, killing process...");
|
Console.WriteLine("BizHawk has completed its shutdown routines, killing process...");
|
||||||
Process.GetCurrentProcess().Kill();
|
Process.GetCurrentProcess().Kill();
|
||||||
|
@ -102,37 +97,60 @@ namespace BizHawk.Client.EmuHawk
|
||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
//NoInlining should keep this code from getting jammed into Main() which would create dependencies on types which havent been setup by the resolver yet... or something like that
|
// NoInlining should keep this code from getting jammed into Main() which would create dependencies on types which havent been setup by the resolver yet... or something like that
|
||||||
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
|
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
|
||||||
private static int SubMain(string[] args)
|
private static int SubMain(string[] args)
|
||||||
{
|
{
|
||||||
// this check has to be done VERY early. i stepped through a debug build with wrong .dll versions purposely used,
|
// this check has to be done VERY early. i stepped through a debug build with wrong .dll versions purposely used,
|
||||||
// and there was a TypeLoadException before the first line of SubMain was reached (some static ColorType init?)
|
// and there was a TypeLoadException before the first line of SubMain was reached (some static ColorType init?)
|
||||||
var thisAsmVer = EmuHawk.ReflectionCache.AsmVersion;
|
var thisAsmVer = ReflectionCache.AsmVersion;
|
||||||
foreach (var asmVer in new[]
|
if (new[]
|
||||||
{
|
|
||||||
BizInvoke.ReflectionCache.AsmVersion,
|
|
||||||
Bizware.Audio.ReflectionCache.AsmVersion,
|
|
||||||
Bizware.BizwareGL.ReflectionCache.AsmVersion,
|
|
||||||
Bizware.Graphics.ReflectionCache.AsmVersion,
|
|
||||||
Bizware.Graphics.Controls.ReflectionCache.AsmVersion,
|
|
||||||
Bizware.Input.ReflectionCache.AsmVersion,
|
|
||||||
Client.Common.ReflectionCache.AsmVersion,
|
|
||||||
Common.ReflectionCache.AsmVersion,
|
|
||||||
Emulation.Common.ReflectionCache.AsmVersion,
|
|
||||||
Emulation.Cores.ReflectionCache.AsmVersion,
|
|
||||||
Emulation.DiscSystem.ReflectionCache.AsmVersion,
|
|
||||||
WinForms.Controls.ReflectionCache.AsmVersion,
|
|
||||||
})
|
|
||||||
{
|
|
||||||
if (asmVer != thisAsmVer)
|
|
||||||
{
|
{
|
||||||
MessageBox.Show("One or more of the BizHawk.* assemblies have the wrong version!\n(Did you attempt to update by overwriting an existing install?)");
|
BizInvoke.ReflectionCache.AsmVersion,
|
||||||
|
Bizware.Audio.ReflectionCache.AsmVersion,
|
||||||
|
Bizware.BizwareGL.ReflectionCache.AsmVersion,
|
||||||
|
Bizware.Graphics.ReflectionCache.AsmVersion,
|
||||||
|
Bizware.Graphics.Controls.ReflectionCache.AsmVersion,
|
||||||
|
Bizware.Input.ReflectionCache.AsmVersion,
|
||||||
|
Client.Common.ReflectionCache.AsmVersion,
|
||||||
|
Common.ReflectionCache.AsmVersion,
|
||||||
|
Emulation.Common.ReflectionCache.AsmVersion,
|
||||||
|
Emulation.Cores.ReflectionCache.AsmVersion,
|
||||||
|
Emulation.DiscSystem.ReflectionCache.AsmVersion,
|
||||||
|
WinForms.Controls.ReflectionCache.AsmVersion,
|
||||||
|
}.Any(asmVer => asmVer != thisAsmVer))
|
||||||
|
{
|
||||||
|
MessageBox.Show("One or more of the BizHawk.* assemblies have the wrong version!\n(Did you attempt to update by overwriting an existing install?)");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!OSTailoredCode.IsUnixHost)
|
||||||
|
{
|
||||||
|
// TODO: We optimally should only require the new VS2015-2022 all in one redist be installed
|
||||||
|
// None of this old 2010 runtime garbage
|
||||||
|
// (It's probably as simple as just recompiling old mupen .dlls against newer VS and probably just removing speex completely)
|
||||||
|
foreach (var (dllToLoad, desc) in new[]
|
||||||
|
{
|
||||||
|
("vcruntime140_1.dll", "Microsoft Visual C++ Redistributable for Visual Studio 2015, 2017, 2019, and 2022 (x64)"),
|
||||||
|
// for libspeexdsp.dll, mupen64plus-audio-bkm.dll, mupen64plus-video-glide64.dll, mupen64plus-video-glide64mk2.dll, mupen64plus-video-rice.dll
|
||||||
|
("msvcr100.dll", "Microsoft Visual C++ 2010 SP1 Runtime (x64)"),
|
||||||
|
})
|
||||||
|
{
|
||||||
|
var p = OSTailoredCode.LinkedLibManager.LoadOrZero(dllToLoad);
|
||||||
|
if (p != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
OSTailoredCode.LinkedLibManager.FreeByPtr(p);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// else it's missing or corrupted
|
||||||
|
MessageBox.Show($"EmuHawk needs {desc} in order to run! See the readme on GitHub for more info. (EmuHawk will now close.) " +
|
||||||
|
$"Internal error message: {OSTailoredCode.LinkedLibManager.GetErrorMessage()}");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
typeof(Form).GetField(OSTC.IsUnixHost ? "default_icon" : "defaultIcon", BindingFlags.NonPublic | BindingFlags.Static)
|
typeof(Form).GetField(OSTailoredCode.IsUnixHost ? "default_icon" : "defaultIcon", BindingFlags.NonPublic | BindingFlags.Static)!
|
||||||
.SetValue(null, Properties.Resources.Logo);
|
.SetValue(null, Properties.Resources.Logo);
|
||||||
|
|
||||||
TempFileManager.Start();
|
TempFileManager.Start();
|
||||||
|
@ -195,8 +213,8 @@ namespace BizHawk.Client.EmuHawk
|
||||||
{
|
{
|
||||||
// try to fallback on the faster option on Windows
|
// try to fallback on the faster option on Windows
|
||||||
// if we're on a Unix platform, there's only 1 fallback here...
|
// if we're on a Unix platform, there's only 1 fallback here...
|
||||||
1 when OSTC.IsUnixHost => (EDispMethod.GdiPlus, "GDI+"),
|
1 when OSTailoredCode.IsUnixHost => (EDispMethod.GdiPlus, "GDI+"),
|
||||||
1 or 2 when !OSTC.IsUnixHost => dispMethod == EDispMethod.D3D9
|
1 or 2 when !OSTailoredCode.IsUnixHost => dispMethod == EDispMethod.D3D9
|
||||||
? (EDispMethod.OpenGL, "OpenGL")
|
? (EDispMethod.OpenGL, "OpenGL")
|
||||||
: (EDispMethod.D3D9, "Direct3D9"),
|
: (EDispMethod.D3D9, "Direct3D9"),
|
||||||
_ => (EDispMethod.GdiPlus, "GDI+")
|
_ => (EDispMethod.GdiPlus, "GDI+")
|
||||||
|
@ -210,16 +228,16 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var fallback = ChooseFallback();
|
var (method, name) = ChooseFallback();
|
||||||
new ExceptionBox(new Exception($"Initialization of Display Method failed; falling back to {fallback.Name}", ex)).ShowDialog();
|
new ExceptionBox(new Exception($"Initialization of Display Method failed; falling back to {name}", ex)).ShowDialog();
|
||||||
return TryInitIGL(initialConfig.DispMethod = fallback.Method);
|
return TryInitIGL(initialConfig.DispMethod = method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (dispMethod)
|
switch (dispMethod)
|
||||||
{
|
{
|
||||||
case EDispMethod.D3D9:
|
case EDispMethod.D3D9:
|
||||||
if (OSTC.IsUnixHost || OSTC.IsWine)
|
if (OSTailoredCode.IsUnixHost || OSTailoredCode.IsWine)
|
||||||
{
|
{
|
||||||
// possibly sharing config w/ Windows, assume the user wants the not-slow method (but don't change the config)
|
// possibly sharing config w/ Windows, assume the user wants the not-slow method (but don't change the config)
|
||||||
return TryInitIGL(EDispMethod.OpenGL);
|
return TryInitIGL(EDispMethod.OpenGL);
|
||||||
|
@ -230,17 +248,17 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var fallback = ChooseFallback();
|
var (method, name) = ChooseFallback();
|
||||||
new ExceptionBox(new Exception($"Initialization of Direct3D9 Display Method failed; falling back to {fallback.Name}", ex)).ShowDialog();
|
new ExceptionBox(new Exception($"Initialization of Direct3D9 Display Method failed; falling back to {name}", ex)).ShowDialog();
|
||||||
return TryInitIGL(initialConfig.DispMethod = fallback.Method);
|
return TryInitIGL(initialConfig.DispMethod = method);
|
||||||
}
|
}
|
||||||
case EDispMethod.OpenGL:
|
case EDispMethod.OpenGL:
|
||||||
if (!IGL_OpenGL.Available)
|
if (!IGL_OpenGL.Available)
|
||||||
{
|
{
|
||||||
// too old to use, need to fallback to something else
|
// too old to use, need to fallback to something else
|
||||||
var fallback = ChooseFallback();
|
var (method, name) = ChooseFallback();
|
||||||
new ExceptionBox(new Exception($"Initialization of OpenGL Display Method failed; falling back to {fallback.Name}")).ShowDialog();
|
new ExceptionBox(new Exception($"Initialization of OpenGL Display Method failed; falling back to {name}")).ShowDialog();
|
||||||
return TryInitIGL(initialConfig.DispMethod = fallback.Method);
|
return TryInitIGL(initialConfig.DispMethod = method);
|
||||||
}
|
}
|
||||||
var igl = new IGL_OpenGL();
|
var igl = new IGL_OpenGL();
|
||||||
// need to have a context active for checking renderer, will be disposed afterwards
|
// need to have a context active for checking renderer, will be disposed afterwards
|
||||||
|
@ -265,24 +283,25 @@ namespace BizHawk.Client.EmuHawk
|
||||||
|
|
||||||
Sound globalSound = null;
|
Sound globalSound = null;
|
||||||
|
|
||||||
if (!OSTC.IsUnixHost)
|
if (!OSTailoredCode.IsUnixHost)
|
||||||
{
|
{
|
||||||
//WHY do we have to do this? some intel graphics drivers (ig7icd64.dll 10.18.10.3304 on an unknown chip on win8.1) are calling SetDllDirectory() for the process, which ruins stuff.
|
// WHY do we have to do this? some intel graphics drivers (ig7icd64.dll 10.18.10.3304 on an unknown chip on win8.1) are calling SetDllDirectory() for the process, which ruins stuff.
|
||||||
//The relevant initialization happened just before in "create IGL context".
|
// The relevant initialization happened just before in "create IGL context".
|
||||||
//It isn't clear whether we need the earlier SetDllDirectory(), but I think we do.
|
// It isn't clear whether we need the earlier SetDllDirectory(), but I think we do.
|
||||||
//note: this is pasted instead of being put in a static method due to this initialization code being sensitive to things like that, and not wanting to cause it to break
|
// note: this is pasted instead of being put in a static method due to this initialization code being sensitive to things like that, and not wanting to cause it to break
|
||||||
//pasting should be safe (not affecting the jit order of things)
|
// pasting should be safe (not affecting the jit order of things)
|
||||||
var dllDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "dll");
|
var dllDir = Path.Combine(AppContext.BaseDirectory, "dll");
|
||||||
_ = SetDllDirectory(dllDir);
|
_ = SetDllDirectoryW(dllDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (OSTC.HostWindowsVersion is null || OSTC.HostWindowsVersion.Value.Version >= OSTC.WindowsVersion._10) // "windows isn't capable of being useful for non-administrators until windows 10" --zeromus
|
if (OSTailoredCode.HostWindowsVersion is null ||
|
||||||
|
OSTailoredCode.HostWindowsVersion.Value.Version >= OSTailoredCode.WindowsVersion._10) // "windows isn't capable of being useful for non-administrators until windows 10" --zeromus
|
||||||
{
|
{
|
||||||
if (EmuHawkUtil.CLRHostHasElevatedPrivileges)
|
if (EmuHawkUtil.CLRHostHasElevatedPrivileges)
|
||||||
{
|
{
|
||||||
using MsgBox dialog = new(
|
using MsgBox dialog = new(
|
||||||
title: "This EmuHawk is privileged",
|
title: "This EmuHawk is privileged",
|
||||||
message: $"EmuHawk detected it {(OSTC.IsUnixHost ? "is running as root (Superuser)" : "has Administrator privileges")}.\n"
|
message: $"EmuHawk detected it {(OSTailoredCode.IsUnixHost ? "is running as root (Superuser)" : "has Administrator privileges")}.\n"
|
||||||
+ "This is a bad idea.",
|
+ "This is a bad idea.",
|
||||||
boxIcon: MessageBoxIcon.Warning);
|
boxIcon: MessageBoxIcon.Warning);
|
||||||
dialog.ShowDialog();
|
dialog.ShowDialog();
|
||||||
|
@ -321,11 +340,10 @@ namespace BizHawk.Client.EmuHawk
|
||||||
}
|
}
|
||||||
initialConfig = ConfigService.Load<Config>(iniPath);
|
initialConfig = ConfigService.Load<Config>(iniPath);
|
||||||
initialConfig.ResolveDefaults();
|
initialConfig.ResolveDefaults();
|
||||||
|
// ReSharper disable once AccessToDisposedClosure
|
||||||
mf.Config = initialConfig;
|
mf.Config = initialConfig;
|
||||||
};
|
};
|
||||||
// var title = mf.Text;
|
|
||||||
mf.Show();
|
mf.Show();
|
||||||
// mf.Text = title;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
exitCode = mf.ProgramRunLoop();
|
exitCode = mf.ProgramRunLoop();
|
||||||
|
@ -357,17 +375,9 @@ namespace BizHawk.Client.EmuHawk
|
||||||
Input.Instance?.Adapter?.DeInitAll();
|
Input.Instance?.Adapter?.DeInitAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
//return 0 assuming things have gone well, non-zero values could be used as error codes or for scripting purposes
|
// return 0 assuming things have gone well, non-zero values could be used as error codes or for scripting purposes
|
||||||
return exitCode;
|
return exitCode;
|
||||||
} //SubMain
|
}
|
||||||
|
|
||||||
//declared here instead of a more usual place to avoid dependencies on the more usual place
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true)]
|
|
||||||
private static extern uint SetDllDirectory(string lpPathName);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", EntryPoint = "DeleteFileW", SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
|
|
||||||
private static extern bool DeleteFileW([MarshalAs(UnmanagedType.LPWStr)]string lpFileName);
|
|
||||||
|
|
||||||
/// <remarks>http://www.codeproject.com/Articles/310675/AppDomain-AssemblyResolve-Event-Tips</remarks>
|
/// <remarks>http://www.codeproject.com/Articles/310675/AppDomain-AssemblyResolve-Event-Tips</remarks>
|
||||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||||
|
@ -377,14 +387,16 @@ namespace BizHawk.Client.EmuHawk
|
||||||
lock (AppDomain.CurrentDomain)
|
lock (AppDomain.CurrentDomain)
|
||||||
{
|
{
|
||||||
var firstAsm = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), asm => asm.FullName == requested);
|
var firstAsm = Array.Find(AppDomain.CurrentDomain.GetAssemblies(), asm => asm.FullName == requested);
|
||||||
if (firstAsm != null) return firstAsm;
|
if (firstAsm != null)
|
||||||
|
{
|
||||||
|
return firstAsm;
|
||||||
|
}
|
||||||
|
|
||||||
//load missing assemblies by trying to find them in the dll directory
|
// load missing assemblies by trying to find them in the dll directory
|
||||||
var dllname = $"{new AssemblyName(requested).Name}.dll";
|
var dllname = $"{new AssemblyName(requested).Name}.dll";
|
||||||
var directory = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "dll");
|
var directory = Path.Combine(AppContext.BaseDirectory, "dll");
|
||||||
var simpleName = new AssemblyName(requested).Name;
|
|
||||||
var fname = Path.Combine(directory, dllname);
|
var fname = Path.Combine(directory, dllname);
|
||||||
//it is important that we use LoadFile here and not load from a byte array; otherwise mixed (managed/unmanaged) assemblies can't load
|
// it is important that we use LoadFile here and not load from a byte array; otherwise mixed (managed/unmanaged) assemblies can't load
|
||||||
return File.Exists(fname) ? Assembly.LoadFile(fname) : null;
|
return File.Exists(fname) ? Assembly.LoadFile(fname) : null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using BizHawk.Common.StringExtensions;
|
using BizHawk.Common.StringExtensions;
|
||||||
|
|
||||||
|
@ -200,7 +199,7 @@ namespace BizHawk.Common.PathExtensions
|
||||||
}
|
}
|
||||||
if (OSTailoredCode.IsUnixHost)
|
if (OSTailoredCode.IsUnixHost)
|
||||||
{
|
{
|
||||||
var dirPath = ReadPathFromEnvVar("BIZHAWK_HOME") ?? Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
|
var dirPath = ReadPathFromEnvVar("BIZHAWK_HOME") ?? AppContext.BaseDirectory;
|
||||||
ExeDirectoryPath = string.IsNullOrEmpty(dirPath) || dirPath == "/" ? string.Empty : dirPath;
|
ExeDirectoryPath = string.IsNullOrEmpty(dirPath) || dirPath == "/" ? string.Empty : dirPath;
|
||||||
DllDirectoryPath = Path.Combine(ExeDirectoryPath == string.Empty ? "/" : ExeDirectoryPath, "dll");
|
DllDirectoryPath = Path.Combine(ExeDirectoryPath == string.Empty ? "/" : ExeDirectoryPath, "dll");
|
||||||
// yes, this is a lot of extra code to make sure BizHawk can run in `/` on Unix, but I've made up for it by caching these for the program lifecycle --yoshi
|
// yes, this is a lot of extra code to make sure BizHawk can run in `/` on Unix, but I've made up for it by caching these for the program lifecycle --yoshi
|
||||||
|
@ -208,8 +207,8 @@ namespace BizHawk.Common.PathExtensions
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var dirPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
|
var dirPath = AppContext.BaseDirectory;
|
||||||
DataDirectoryPath = ExeDirectoryPath = string.IsNullOrEmpty(dirPath) ? throw new Exception("failed to get location of executable, very bad things must have happened") : dirPath.RemoveSuffix('\\');
|
DataDirectoryPath = ExeDirectoryPath = string.IsNullOrEmpty(dirPath) ? throw new("failed to get location of executable, very bad things must have happened") : dirPath.RemoveSuffix('\\');
|
||||||
DllDirectoryPath = Path.Combine(ExeDirectoryPath, "dll");
|
DllDirectoryPath = Path.Combine(ExeDirectoryPath, "dll");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace BizHawk.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Imports of functions in dlfcn.h
|
||||||
|
/// For Linux, these come from libdl historically
|
||||||
|
/// (Although since glibc 2.34 these are just in libc, with libdl just calling into libc)
|
||||||
|
/// </summary>
|
||||||
|
public static class LinuxDlfcnImports
|
||||||
|
{
|
||||||
|
public const int RTLD_NOW = 2;
|
||||||
|
|
||||||
|
[DllImport("libdl.so.2")]
|
||||||
|
public static extern IntPtr dlopen(string fileName, int flags);
|
||||||
|
|
||||||
|
[DllImport("libdl.so.2")]
|
||||||
|
public static extern int dlclose(IntPtr handle);
|
||||||
|
|
||||||
|
[DllImport("libdl.so.2")]
|
||||||
|
public static extern IntPtr dlsym(IntPtr handle, string symbol);
|
||||||
|
|
||||||
|
[DllImport("libdl.so.2")]
|
||||||
|
public static extern IntPtr dlerror();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,25 +1,43 @@
|
||||||
#pragma warning disable IDE0240
|
|
||||||
#nullable enable
|
|
||||||
#pragma warning restore IDE0240
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using BizHawk.Common.StringExtensions;
|
using BizHawk.Common.StringExtensions;
|
||||||
|
|
||||||
#if EXE_PROJECT
|
using static BizHawk.Common.LoaderApiImports;
|
||||||
namespace EXE_PROJECT // Use a different namespace so the executable can still use this class' members without an implicit dependency on the BizHawk.Common library, and without resorting to code duplication.
|
|
||||||
#else
|
|
||||||
namespace BizHawk.Common
|
namespace BizHawk.Common
|
||||||
#endif
|
|
||||||
{
|
{
|
||||||
public static class OSTailoredCode
|
public static class OSTailoredCode
|
||||||
{
|
{
|
||||||
/// <remarks>macOS doesn't use <see cref="PlatformID.MacOSX">PlatformID.MacOSX</see></remarks>
|
public static readonly DistinctOS CurrentOS;
|
||||||
public static readonly DistinctOS CurrentOS = Environment.OSVersion.Platform == PlatformID.Unix
|
public static readonly bool IsUnixHost;
|
||||||
? SimpleSubshell("uname", "-s", "Can't determine OS") == "Darwin" ? DistinctOS.macOS : DistinctOS.Linux
|
|
||||||
: DistinctOS.Windows;
|
static OSTailoredCode()
|
||||||
|
{
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
|
||||||
|
{
|
||||||
|
CurrentOS = DistinctOS.Linux;
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
|
||||||
|
{
|
||||||
|
CurrentOS = DistinctOS.macOS;
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
CurrentOS = DistinctOS.Windows;
|
||||||
|
}
|
||||||
|
else if (RuntimeInformation.OSDescription.ToUpperInvariant().Contains("BSD", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
CurrentOS = DistinctOS.BSD;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CurrentOS = DistinctOS.Unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsUnixHost = CurrentOS != DistinctOS.Windows;
|
||||||
|
}
|
||||||
|
|
||||||
private static readonly Lazy<(WindowsVersion, Version?)?> _HostWindowsVersion = new(() =>
|
private static readonly Lazy<(WindowsVersion, Version?)?> _HostWindowsVersion = new(() =>
|
||||||
{
|
{
|
||||||
|
@ -65,8 +83,6 @@ namespace BizHawk.Common
|
||||||
|
|
||||||
public static (WindowsVersion Version, Version? Win10PlusVersion)? HostWindowsVersion => _HostWindowsVersion.Value;
|
public static (WindowsVersion Version, Version? Win10PlusVersion)? HostWindowsVersion => _HostWindowsVersion.Value;
|
||||||
|
|
||||||
public static readonly bool IsUnixHost = CurrentOS != DistinctOS.Windows;
|
|
||||||
|
|
||||||
public static bool IsWSL => _isWSL.Value;
|
public static bool IsWSL => _isWSL.Value;
|
||||||
|
|
||||||
private static readonly Lazy<bool> _isWine = new(() =>
|
private static readonly Lazy<bool> _isWine = new(() =>
|
||||||
|
@ -89,11 +105,13 @@ namespace BizHawk.Common
|
||||||
|
|
||||||
public static bool IsWine => _isWine.Value;
|
public static bool IsWine => _isWine.Value;
|
||||||
|
|
||||||
private static readonly Lazy<ILinkedLibManager> _LinkedLibManager = new Lazy<ILinkedLibManager>(() => CurrentOS switch
|
private static readonly Lazy<ILinkedLibManager> _LinkedLibManager = new(() => CurrentOS switch
|
||||||
{
|
{
|
||||||
DistinctOS.Linux => new UnixMonoLLManager(),
|
DistinctOS.Linux => new LinuxLLManager(),
|
||||||
DistinctOS.macOS => new UnixMonoLLManager(),
|
DistinctOS.macOS => new PosixLLManager(),
|
||||||
DistinctOS.Windows => new WindowsLLManager(),
|
DistinctOS.Windows => new WindowsLLManager(),
|
||||||
|
DistinctOS.BSD => new PosixLLManager(),
|
||||||
|
DistinctOS.Unknown => throw new NotSupportedException("Cannot link libraries with Unknown OS"),
|
||||||
_ => throw new InvalidOperationException()
|
_ => throw new InvalidOperationException()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -117,65 +135,72 @@ namespace BizHawk.Common
|
||||||
string GetErrorMessage();
|
string GetErrorMessage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class UnixMonoLLManager : ILinkedLibManager
|
private class LinuxLLManager : ILinkedLibManager
|
||||||
{
|
{
|
||||||
private const int RTLD_NOW = 2;
|
public int FreeByPtr(IntPtr hModule) => LinuxDlfcnImports.dlclose(hModule);
|
||||||
|
|
||||||
[DllImport("libdl.so.2")]
|
public IntPtr GetProcAddrOrZero(IntPtr hModule, string procName) => LinuxDlfcnImports.dlsym(hModule, procName);
|
||||||
private static extern int dlclose(IntPtr handle);
|
|
||||||
|
|
||||||
[DllImport("libdl.so.2")]
|
|
||||||
private static extern IntPtr dlerror();
|
|
||||||
|
|
||||||
[DllImport("libdl.so.2")]
|
|
||||||
private static extern IntPtr dlopen(string fileName, int flags);
|
|
||||||
|
|
||||||
[DllImport("libdl.so.2")]
|
|
||||||
private static extern IntPtr dlsym(IntPtr handle, string symbol);
|
|
||||||
|
|
||||||
public int FreeByPtr(IntPtr hModule) => dlclose(hModule);
|
|
||||||
|
|
||||||
public IntPtr GetProcAddrOrZero(IntPtr hModule, string procName) => dlsym(hModule, procName);
|
|
||||||
|
|
||||||
public IntPtr GetProcAddrOrThrow(IntPtr hModule, string procName)
|
public IntPtr GetProcAddrOrThrow(IntPtr hModule, string procName)
|
||||||
{
|
{
|
||||||
_ = dlerror(); // the Internet said to do this
|
_ = LinuxDlfcnImports.dlerror(); // the Internet said to do this
|
||||||
var p = GetProcAddrOrZero(hModule, procName);
|
var p = GetProcAddrOrZero(hModule, procName);
|
||||||
if (p != IntPtr.Zero) return p;
|
if (p != IntPtr.Zero) return p;
|
||||||
var errCharPtr = dlerror();
|
throw new InvalidOperationException($"error in dlsym: {GetErrorMessage()}");
|
||||||
throw new InvalidOperationException($"error in {nameof(dlsym)}{(errCharPtr == IntPtr.Zero ? string.Empty : $": {Marshal.PtrToStringAnsi(errCharPtr)}")}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public IntPtr LoadOrZero(string dllToLoad) => dlopen(dllToLoad, RTLD_NOW);
|
public IntPtr LoadOrZero(string dllToLoad) => LinuxDlfcnImports.dlopen(dllToLoad, LinuxDlfcnImports.RTLD_NOW);
|
||||||
|
|
||||||
public IntPtr LoadOrThrow(string dllToLoad)
|
public IntPtr LoadOrThrow(string dllToLoad)
|
||||||
{
|
{
|
||||||
var ret = LoadOrZero(dllToLoad);
|
var ret = LoadOrZero(dllToLoad);
|
||||||
return ret != IntPtr.Zero ? ret : throw new InvalidOperationException($"got null pointer from {nameof(dlopen)}, error: {Marshal.PtrToStringAnsi(dlerror())}");
|
return ret != IntPtr.Zero
|
||||||
|
? ret
|
||||||
|
: throw new InvalidOperationException($"got null pointer from dlopen, error: {GetErrorMessage()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetErrorMessage()
|
public string GetErrorMessage()
|
||||||
{
|
{
|
||||||
var errCharPtr = dlerror();
|
var errCharPtr = LinuxDlfcnImports.dlerror();
|
||||||
return errCharPtr == IntPtr.Zero ? "No error present" : Marshal.PtrToStringAnsi(errCharPtr)!;
|
return errCharPtr == IntPtr.Zero ? "dlerror reported no error" : Marshal.PtrToStringAnsi(errCharPtr)!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is just a copy paste of LinuxLLManager using PosixDlfcnImports instead of LinuxDlfcnImports
|
||||||
|
// TODO: probably could do some OOP magic so there isn't just a copy paste here
|
||||||
|
private class PosixLLManager : ILinkedLibManager
|
||||||
|
{
|
||||||
|
public int FreeByPtr(IntPtr hModule) => PosixDlfcnImports.dlclose(hModule);
|
||||||
|
|
||||||
|
public IntPtr GetProcAddrOrZero(IntPtr hModule, string procName) => PosixDlfcnImports.dlsym(hModule, procName);
|
||||||
|
|
||||||
|
public IntPtr GetProcAddrOrThrow(IntPtr hModule, string procName)
|
||||||
|
{
|
||||||
|
_ = PosixDlfcnImports.dlerror(); // the Internet said to do this
|
||||||
|
var p = GetProcAddrOrZero(hModule, procName);
|
||||||
|
if (p != IntPtr.Zero) return p;
|
||||||
|
throw new InvalidOperationException($"error in dlsym: {GetErrorMessage()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntPtr LoadOrZero(string dllToLoad) => PosixDlfcnImports.dlopen(dllToLoad, PosixDlfcnImports.RTLD_NOW);
|
||||||
|
|
||||||
|
public IntPtr LoadOrThrow(string dllToLoad)
|
||||||
|
{
|
||||||
|
var ret = LoadOrZero(dllToLoad);
|
||||||
|
return ret != IntPtr.Zero
|
||||||
|
? ret
|
||||||
|
: throw new InvalidOperationException($"got null pointer from dlopen, error: {GetErrorMessage()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetErrorMessage()
|
||||||
|
{
|
||||||
|
var errCharPtr = PosixDlfcnImports.dlerror();
|
||||||
|
return errCharPtr == IntPtr.Zero ? "dlerror reported no error" : Marshal.PtrToStringAnsi(errCharPtr)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class WindowsLLManager : ILinkedLibManager
|
private class WindowsLLManager : ILinkedLibManager
|
||||||
{
|
{
|
||||||
// functions taken from LoaderApiImports
|
|
||||||
// TODO: Should we apply the same EXE_PROJECT hack to that file?
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true, ExactSpelling = true)]
|
|
||||||
private static extern IntPtr LoadLibraryW(string lpLibFileName);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
|
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
|
||||||
private static extern bool FreeLibrary(IntPtr hLibModule);
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
|
|
||||||
private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);
|
|
||||||
|
|
||||||
public int FreeByPtr(IntPtr hModule) => FreeLibrary(hModule) ? 0 : 1;
|
public int FreeByPtr(IntPtr hModule) => FreeLibrary(hModule) ? 0 : 1;
|
||||||
|
|
||||||
public IntPtr GetProcAddrOrZero(IntPtr hModule, string procName) => GetProcAddress(hModule, procName);
|
public IntPtr GetProcAddrOrZero(IntPtr hModule, string procName) => GetProcAddress(hModule, procName);
|
||||||
|
@ -194,18 +219,12 @@ namespace BizHawk.Common
|
||||||
return ret != IntPtr.Zero ? ret : throw new InvalidOperationException($"got null pointer from {nameof(LoadLibraryW)}, {GetErrorMessage()}");
|
return ret != IntPtr.Zero ? ret : throw new InvalidOperationException($"got null pointer from {nameof(LoadLibraryW)}, {GetErrorMessage()}");
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("kernel32.dll", ExactSpelling = true)]
|
|
||||||
private static extern uint GetLastError();
|
|
||||||
|
|
||||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
|
|
||||||
private static extern unsafe int FormatMessageW(int flags, IntPtr source, uint messageId, uint languageId, char* outMsg, int size, IntPtr args);
|
|
||||||
|
|
||||||
public unsafe string GetErrorMessage()
|
public unsafe string GetErrorMessage()
|
||||||
{
|
{
|
||||||
var errCode = GetLastError();
|
var errCode = Win32Imports.GetLastError();
|
||||||
var buffer = stackalloc char[1024];
|
var buffer = stackalloc char[1024];
|
||||||
const int FORMAT_MESSAGE_FROM_SYSTEM = 0x1000;
|
const int FORMAT_MESSAGE_FROM_SYSTEM = 0x1000;
|
||||||
var sz = FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, errCode, 0, buffer, 1024, IntPtr.Zero);
|
var sz = Win32Imports.FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, errCode, 0, buffer, 1024, IntPtr.Zero);
|
||||||
return $"error code: 0x{errCode:X8}, error message: {new string(buffer, 0, sz)}";
|
return $"error code: 0x{errCode:X8}, error message: {new string(buffer, 0, sz)}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +233,9 @@ namespace BizHawk.Common
|
||||||
{
|
{
|
||||||
Linux,
|
Linux,
|
||||||
macOS,
|
macOS,
|
||||||
Windows
|
Windows,
|
||||||
|
BSD, // covering all the *BSDs
|
||||||
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum WindowsVersion
|
public enum WindowsVersion
|
||||||
|
@ -234,8 +255,10 @@ namespace BizHawk.Common
|
||||||
/// <param name="checkStderr">stderr is discarded if false</param>
|
/// <param name="checkStderr">stderr is discarded if false</param>
|
||||||
/// <remarks>OS is implicit and needs to be checked at callsite. Returned <see cref="Process"/> has not been started.</remarks>
|
/// <remarks>OS is implicit and needs to be checked at callsite. Returned <see cref="Process"/> has not been started.</remarks>
|
||||||
public static Process ConstructSubshell(string cmd, string args, bool checkStdout = true, bool checkStderr = false)
|
public static Process ConstructSubshell(string cmd, string args, bool checkStdout = true, bool checkStderr = false)
|
||||||
=> new Process {
|
=> new()
|
||||||
StartInfo = new ProcessStartInfo {
|
{
|
||||||
|
StartInfo = new()
|
||||||
|
{
|
||||||
Arguments = args,
|
Arguments = args,
|
||||||
CreateNoWindow = true,
|
CreateNoWindow = true,
|
||||||
FileName = cmd,
|
FileName = cmd,
|
||||||
|
@ -257,7 +280,7 @@ namespace BizHawk.Common
|
||||||
using var proc = ConstructSubshell(cmd, args);
|
using var proc = ConstructSubshell(cmd, args);
|
||||||
proc.Start();
|
proc.Start();
|
||||||
var stdout = proc.StandardOutput;
|
var stdout = proc.StandardOutput;
|
||||||
if (stdout.EndOfStream) throw new Exception($"{noOutputMsg} ({cmd} wrote nothing to stdout)");
|
if (stdout.EndOfStream) throw new($"{noOutputMsg} ({cmd} wrote nothing to stdout)");
|
||||||
return stdout.ReadLine()!;
|
return stdout.ReadLine()!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace BizHawk.Common
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Imports of functions in dlfcn.h
|
||||||
|
/// For most POSIX systems, these come from libc
|
||||||
|
/// Linux is a partial exception, as they weren't in libc until glibc 2.34
|
||||||
|
/// (For reference, Debian 10, the current Debian oldoldstable, is on glibc 2.28)
|
||||||
|
/// </summary>
|
||||||
|
public static class PosixDlfcnImports
|
||||||
|
{
|
||||||
|
public const int RTLD_NOW = 2;
|
||||||
|
|
||||||
|
[DllImport("libc")]
|
||||||
|
public static extern IntPtr dlopen(string fileName, int flags);
|
||||||
|
|
||||||
|
[DllImport("libc")]
|
||||||
|
public static extern int dlclose(IntPtr handle);
|
||||||
|
|
||||||
|
[DllImport("libc")]
|
||||||
|
public static extern IntPtr dlsym(IntPtr handle, string symbol);
|
||||||
|
|
||||||
|
[DllImport("libc")]
|
||||||
|
public static extern IntPtr dlerror();
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,13 +14,13 @@ namespace BizHawk.Common
|
||||||
Execute = 0x4
|
Execute = 0x4
|
||||||
}
|
}
|
||||||
|
|
||||||
[DllImport("libc.so.6")]
|
[DllImport("libc")]
|
||||||
public static extern IntPtr mmap(IntPtr addr, UIntPtr length, MemoryProtection prot, int flags, int fd, IntPtr offset);
|
public static extern IntPtr mmap(IntPtr addr, UIntPtr length, MemoryProtection prot, int flags, int fd, IntPtr offset);
|
||||||
|
|
||||||
[DllImport("libc.so.6")]
|
[DllImport("libc")]
|
||||||
public static extern int mprotect(IntPtr addr, UIntPtr len, MemoryProtection prot);
|
public static extern int mprotect(IntPtr addr, UIntPtr len, MemoryProtection prot);
|
||||||
|
|
||||||
[DllImport("libc.so.6")]
|
[DllImport("libc")]
|
||||||
public static extern int munmap(IntPtr addr, UIntPtr length);
|
public static extern int munmap(IntPtr addr, UIntPtr length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Reflection;
|
|
||||||
|
|
||||||
using BizHawk.Common.StringExtensions;
|
using BizHawk.Common.StringExtensions;
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ namespace BizHawk.Common
|
||||||
static VersionInfo()
|
static VersionInfo()
|
||||||
{
|
{
|
||||||
var path = Path.Combine(
|
var path = Path.Combine(
|
||||||
Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location)?.RemoveSuffix(Path.DirectorySeparatorChar) ?? string.Empty,
|
AppContext.BaseDirectory.RemoveSuffix(Path.DirectorySeparatorChar),
|
||||||
"dll",
|
"dll",
|
||||||
"custombuild.txt"
|
"custombuild.txt"
|
||||||
);
|
);
|
||||||
|
|
|
@ -51,6 +51,12 @@ namespace BizHawk.Common
|
||||||
[return: MarshalAs(UnmanagedType.Bool)]
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
public static extern bool DestroyMenu(IntPtr hMenu);
|
public static extern bool DestroyMenu(IntPtr hMenu);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||||
|
public static extern unsafe int FormatMessageW(int flags, IntPtr source, uint messageId, uint languageId, char* outMsg, int size, IntPtr args);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", ExactSpelling = true)]
|
||||||
|
public static extern uint GetLastError();
|
||||||
|
|
||||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
|
[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
|
||||||
public static extern uint MapVirtualKeyW(uint uCode, uint uMapType);
|
public static extern uint MapVirtualKeyW(uint uCode, uint uMapType);
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,12 @@ namespace BizHawk.Common
|
||||||
public static readonly IntPtr HWND_MESSAGE = new(-3);
|
public static readonly IntPtr HWND_MESSAGE = new(-3);
|
||||||
public const int GWLP_USERDATA = -21;
|
public const int GWLP_USERDATA = -21;
|
||||||
|
|
||||||
|
public enum ChangeWindowMessageFilterFlags : uint
|
||||||
|
{
|
||||||
|
Add = 1,
|
||||||
|
Remove = 2,
|
||||||
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
public struct MSG
|
public struct MSG
|
||||||
{
|
{
|
||||||
|
@ -44,6 +50,9 @@ namespace BizHawk.Common
|
||||||
public string lpszClassName;
|
public string lpszClassName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[DllImport("user32.dll", ExactSpelling = true)]
|
||||||
|
public static extern bool ChangeWindowMessageFilter(uint msg, ChangeWindowMessageFilterFlags flags);
|
||||||
|
|
||||||
[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
[DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
|
||||||
public static extern IntPtr CreateWindowExW(int dwExStyle, IntPtr lpClassName, string lpWindowName,
|
public static extern IntPtr CreateWindowExW(int dwExStyle, IntPtr lpClassName, string lpWindowName,
|
||||||
int dwStyle, int X, int Y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
|
int dwStyle, int X, int Y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<Project>
|
<Project>
|
||||||
<Import Project="MainSlnCommon.props" />
|
<Import Project="MainSlnCommon.props" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<DefineConstants>$(DefineConstants);EXE_PROJECT</DefineConstants>
|
|
||||||
<OutputPath>$(MSBuildProjectDirectory)/../../output</OutputPath>
|
<OutputPath>$(MSBuildProjectDirectory)/../../output</OutputPath>
|
||||||
<AssemblyName>$(MSBuildProjectName.Substring($([MSBuild]::Add($(MSBuildProjectName.LastIndexOf('.')), 1))))</AssemblyName>
|
<AssemblyName>$(MSBuildProjectName.Substring($([MSBuild]::Add($(MSBuildProjectName.LastIndexOf('.')), 1))))</AssemblyName>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
@ -11,9 +10,6 @@
|
||||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
<OutputType>WinExe</OutputType>
|
<OutputType>WinExe</OutputType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Compile Include="$(MSBuildProjectDirectory)/../BizHawk.Common/OSTailoredCode.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<NotExecFilesFromExecProj Include="$(OutputPath)*.deps.json" />
|
<NotExecFilesFromExecProj Include="$(OutputPath)*.deps.json" />
|
||||||
|
|
Loading…
Reference in New Issue