Entrypoint.cs 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Reflection;
  5. using BepInEx.Preloader.RuntimeFixes;
  6. using HarmonyXInterop;
  7. namespace BepInEx.Preloader
  8. {
  9. internal static class PreloaderRunner
  10. {
  11. public static void PreloaderPreMain()
  12. {
  13. string bepinPath = Utility.ParentDirectory(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH), 2);
  14. Paths.SetExecutablePath(EnvVars.DOORSTOP_PROCESS_PATH, bepinPath, EnvVars.DOORSTOP_MANAGED_FOLDER_DIR);
  15. HarmonyInterop.Initialize();
  16. AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
  17. // Remove temporary resolver early so it won't override local resolver
  18. AppDomain.CurrentDomain.AssemblyResolve -= Entrypoint.ResolveCurrentDirectory;
  19. PreloaderMain();
  20. }
  21. private static void PreloaderMain()
  22. {
  23. if (Preloader.ConfigApplyRuntimePatches.Value)
  24. {
  25. XTermFix.Apply();
  26. ConsoleSetOutFix.Apply();
  27. }
  28. Preloader.Run();
  29. }
  30. private static Assembly LocalResolve(object sender, ResolveEventArgs args)
  31. {
  32. if (!Utility.TryParseAssemblyName(args.Name, out var assemblyName))
  33. return null;
  34. // Use parse assembly name on managed side because native GetName() can fail on some locales
  35. // if the game path has "exotic" characters
  36. var foundAssembly = AppDomain.CurrentDomain.GetAssemblies()
  37. .FirstOrDefault(x => Utility.TryParseAssemblyName(x.FullName, out var name) && name.Name == assemblyName.Name);
  38. if (foundAssembly != null)
  39. return foundAssembly;
  40. if (Utility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
  41. || Utility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
  42. || Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
  43. return foundAssembly;
  44. return null;
  45. }
  46. }
  47. internal static class Entrypoint
  48. {
  49. private static string preloaderPath;
  50. /// <summary>
  51. /// The main entrypoint of BepInEx, called from Doorstop.
  52. /// </summary>
  53. public static void Main()
  54. {
  55. // We set it to the current directory first as a fallback, but try to use the same location as the .exe file.
  56. string silentExceptionLog = $"preloader_{DateTime.Now:yyyyMMdd_HHmmss_fff}.log";
  57. try
  58. {
  59. EnvVars.LoadVars();
  60. string gamePath = Path.GetDirectoryName(EnvVars.DOORSTOP_PROCESS_PATH) ?? ".";
  61. silentExceptionLog = Path.Combine(gamePath, silentExceptionLog);
  62. // Get the path of this DLL via Doorstop env var because Assembly.Location mangles non-ASCII characters on some versions of Mono for unknown reasons
  63. preloaderPath = Path.GetDirectoryName(Path.GetFullPath(EnvVars.DOORSTOP_INVOKE_DLL_PATH));
  64. AppDomain.CurrentDomain.AssemblyResolve += ResolveCurrentDirectory;
  65. // In some versions of Unity 4, Mono tries to resolve BepInEx.dll prematurely because of the call to Paths.SetExecutablePath
  66. // To prevent that, we have to use reflection and a separate startup class so that we can install required assembly resolvers before the main code
  67. typeof(Entrypoint).Assembly.GetType($"BepInEx.Preloader.{nameof(PreloaderRunner)}")
  68. ?.GetMethod(nameof(PreloaderRunner.PreloaderPreMain))
  69. ?.Invoke(null, null);
  70. }
  71. catch (Exception ex)
  72. {
  73. File.WriteAllText(silentExceptionLog, ex.ToString());
  74. }
  75. finally
  76. {
  77. AppDomain.CurrentDomain.AssemblyResolve -= ResolveCurrentDirectory;
  78. }
  79. }
  80. internal static Assembly ResolveCurrentDirectory(object sender, ResolveEventArgs args)
  81. {
  82. // Can't use Utils here because it's not yet resolved
  83. var name = new AssemblyName(args.Name);
  84. try
  85. {
  86. return Assembly.LoadFile(Path.Combine(preloaderPath, $"{name.Name}.dll"));
  87. }
  88. catch (Exception)
  89. {
  90. return null;
  91. }
  92. }
  93. }
  94. }