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:
CasualPokePlayer 2023-11-18 20:00:16 -08:00
parent eca0ca41a9
commit 859ea8e249
14 changed files with 339 additions and 267 deletions

View File

@ -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;
} }
}; };

View File

@ -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" />

View File

@ -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
{
// for Unix, skip everything else and just wire up the event handler
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; 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; 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
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
try
{
// but before we even try doing that, whack the MOTW from everything in that directory (that's a dll) // 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. // 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 // 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"); static void RemoveMOTW(string path) => DeleteFileW($"{path}:Zone.Identifier");
var todo = new Queue<DirectoryInfo>(new[] { new DirectoryInfo(dllDir) }); var todo = new Queue<DirectoryInfo>(new[] { new DirectoryInfo(dllDir) });
while (todo.Count != 0) while (todo.Count != 0)
{ {
var di = todo.Dequeue(); var di = todo.Dequeue();
foreach (var diSub in di.GetDirectories()) todo.Enqueue(diSub); 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("*.dll")) RemoveMOTW(fi.FullName);
foreach (var fi in di.GetFiles("*.exe")) 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 // load missing assemblies by trying to find them in the dll directory
string dllName = $"{new AssemblyName(args.Name).Name}.dll"; var dllname = $"{new AssemblyName(requested).Name}.dll";
string directory = Path.Combine(GetExeDirectoryAbsolute(), "dll"); var directory = Path.Combine(AppContext.BaseDirectory, "dll");
string fname = Path.Combine(directory, dllName); var fname = Path.Combine(directory, dllname);
return File.Exists(fname) ? Assembly.LoadFile(fname) : null;
// 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;
} }
} }
} }

View File

@ -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;

View File

@ -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();
@ -108,8 +103,8 @@ namespace BizHawk.Client.EmuHawk
{ {
// 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, BizInvoke.ReflectionCache.AsmVersion,
Bizware.Audio.ReflectionCache.AsmVersion, Bizware.Audio.ReflectionCache.AsmVersion,
@ -123,16 +118,39 @@ namespace BizHawk.Client.EmuHawk
Emulation.Cores.ReflectionCache.AsmVersion, Emulation.Cores.ReflectionCache.AsmVersion,
Emulation.DiscSystem.ReflectionCache.AsmVersion, Emulation.DiscSystem.ReflectionCache.AsmVersion,
WinForms.Controls.ReflectionCache.AsmVersion, WinForms.Controls.ReflectionCache.AsmVersion,
}) }.Any(asmVer => asmVer != thisAsmVer))
{
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?)"); 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; 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;
} }
typeof(Form).GetField(OSTC.IsUnixHost ? "default_icon" : "defaultIcon", BindingFlags.NonPublic | BindingFlags.Static) // 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;
}
}
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();
@ -359,15 +377,7 @@ namespace BizHawk.Client.EmuHawk
// 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,12 +387,14 @@ 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;

View File

@ -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");
} }
} }

View File

@ -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();
}
}

View File

@ -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()!;
} }
} }

View File

@ -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();
}
}

View File

@ -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);
} }
} }

View File

@ -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"
); );

View File

@ -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);

View File

@ -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);

View File

@ -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" />