Decompiled source of S1API Forked v3.2.0

Plugins/S1APILoader.MelonLoader.dll

Decompiled a month ago
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using MelonLoader;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using S1APILoader;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(global::S1APILoader.S1APILoader), "S1APILoader", "2.5.0", "KaBooMa & Bars", null)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("S1APILoader")]
[assembly: AssemblyConfiguration("MonoMelon")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+633d19fd05c26e2310559e66e847cf284134c62d")]
[assembly: AssemblyProduct("S1APILoader")]
[assembly: AssemblyTitle("S1APILoader")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace S1APILoader
{
	public class S1APILoader : MelonPlugin
	{
		private const string BuildFolderName = "S1API";

		private const string ProblematicMelonVersion = "0.7.1";

		private const int InteropFixRevision = 1;

		private const string InteropMarkerFileName = "S1API.InteropFix.marker";

		private static bool _earlyInteropFixAttempted;

		public override void OnApplicationEarlyStart()
		{
			TryApplyEarlyIl2CppInteropFix();
			string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
			if (directoryName == null)
			{
				throw new Exception("Failed to identify plugins folder.");
			}
			string fullPath = Path.GetFullPath(Path.Combine(directoryName, "../Mods"));
			string folder = Path.Combine(directoryName, "S1API");
			string text = (MelonUtils.IsGameIl2Cpp() ? "Il2Cpp" : "Mono");
			string build = ((!MelonUtils.IsGameIl2Cpp()) ? "Il2Cpp" : "Mono");
			MelonLogger.Msg("Loading S1API for " + text + "...");
			NormalizeBuild(fullPath, text, shouldBeEnabled: true, "S1API.{0}.MelonLoader.dll");
			NormalizeBuild(fullPath, build, shouldBeEnabled: false, "S1API.{0}.MelonLoader.dll");
			NormalizeBuild(folder, text, shouldBeEnabled: true, "S1API.{0}.dll");
			NormalizeBuild(folder, build, shouldBeEnabled: false, "S1API.{0}.dll");
			MelonLogger.Msg("Successfully loaded S1API for " + text + "!");
		}

		private static void TryApplyEarlyIl2CppInteropFix()
		{
			if (_earlyInteropFixAttempted)
			{
				return;
			}
			_earlyInteropFixAttempted = true;
			if (MelonUtils.IsGameIl2Cpp())
			{
				string melonLoaderVersion = GetMelonLoaderVersion();
				if (!string.IsNullOrEmpty(melonLoaderVersion) && melonLoaderVersion.StartsWith("0.7.1", StringComparison.OrdinalIgnoreCase))
				{
					MelonLogger.Warning("[S1APILoader] MelonLoader 0.7.1 detected. Applying early Il2CppInterop workaround...");
					RegisterAssemblyResolveFallback();
					InstallIl2CppInteropFixes();
					TryForceAssemblyRegenerationIfNeeded(melonLoaderVersion);
				}
			}
		}

		private static string? GetMelonLoaderVersion()
		{
			try
			{
				Assembly assembly = typeof(MelonPlugin).Assembly;
				object obj = (assembly.GetType("MelonLoader.Properties.BuildInfo")?.GetProperty("VersionNumber", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(null);
				if (obj != null)
				{
					return obj.ToString();
				}
				Version version = assembly.GetName().Version;
				if (version != null)
				{
					return $"{version.Major}.{version.Minor}.{version.Build}";
				}
			}
			catch
			{
			}
			return null;
		}

		private static void RegisterAssemblyResolveFallback()
		{
			AppDomain.CurrentDomain.AssemblyResolve += delegate(object _, ResolveEventArgs args)
			{
				string name = new AssemblyName(args.Name).Name;
				if (string.IsNullOrEmpty(name) || !name.StartsWith("Il2Cpp", StringComparison.Ordinal))
				{
					return (Assembly)null;
				}
				string strippedName = name.Substring("Il2Cpp".Length);
				return string.IsNullOrEmpty(strippedName) ? null : AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => string.Equals(a.GetName().Name, strippedName, StringComparison.OrdinalIgnoreCase));
			};
		}

		private static void InstallIl2CppInteropFixes()
		{
			try
			{
				Assembly assembly = typeof(MelonPlugin).Assembly;
				MethodInfo methodInfo = assembly.GetType("MelonLoader.Fixes.Il2CppInteropFixes")?.GetMethod("Install", BindingFlags.Static | BindingFlags.NonPublic);
				if (methodInfo == null)
				{
					MelonLogger.Warning("[S1APILoader] Could not find Il2CppInteropFixes.Install().");
					return;
				}
				methodInfo.Invoke(null, null);
				MelonLogger.Msg("[S1APILoader] Early Il2CppInterop fixes applied.");
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[S1APILoader] Failed to apply early Il2CppInterop fixes: " + ex.Message);
			}
		}

		private static void TryForceAssemblyRegenerationIfNeeded(string melonVersion)
		{
			try
			{
				string interopMarkerPath = GetInteropMarkerPath();
				if (IsMarkerValid(interopMarkerPath, melonVersion))
				{
					MelonLogger.Msg("[S1APILoader] Interop fix marker found. Skipping assembly regeneration.");
					return;
				}
				if (!HasPreGeneratedIl2CppAssemblies())
				{
					MelonLogger.Msg("[S1APILoader] No pre-generated Il2Cpp assemblies found. MelonLoader will generate them normally.");
					WriteMarker(interopMarkerPath, melonVersion);
					return;
				}
				MelonLogger.Warning("[S1APILoader] Pre-generated Il2Cpp assemblies detected. Forcing regeneration...");
				if (ForceRunIl2CppAssemblyGenerator())
				{
					WriteMarker(interopMarkerPath, melonVersion);
					MelonLogger.Msg("[S1APILoader] Il2Cpp assembly regeneration completed successfully.");
				}
				else
				{
					MelonLogger.Warning("[S1APILoader] Il2Cpp assembly regeneration may have failed. Will retry on next launch.");
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[S1APILoader] Error during assembly regeneration check: " + ex.Message);
			}
		}

		private static string GetInteropMarkerPath()
		{
			string userDataDirectory = MelonEnvironment.UserDataDirectory;
			if (!Directory.Exists(userDataDirectory))
			{
				Directory.CreateDirectory(userDataDirectory);
			}
			return Path.Combine(userDataDirectory, "S1API.InteropFix.marker");
		}

		private static bool IsMarkerValid(string markerPath, string melonVersion)
		{
			if (!File.Exists(markerPath))
			{
				return false;
			}
			try
			{
				string[] array = File.ReadAllLines(markerPath);
				if (array.Length < 2)
				{
					return false;
				}
				string text = null;
				int result = -1;
				string[] array2 = array;
				foreach (string text2 in array2)
				{
					if (text2.StartsWith("melonloader=", StringComparison.OrdinalIgnoreCase))
					{
						text = text2.Substring("melonloader=".Length).Trim();
					}
					else if (text2.StartsWith("fixrevision=", StringComparison.OrdinalIgnoreCase))
					{
						string s = text2.Substring("fixrevision=".Length).Trim();
						int.TryParse(s, out result);
					}
				}
				if (string.IsNullOrEmpty(text))
				{
					return false;
				}
				bool flag = text.Equals(melonVersion, StringComparison.OrdinalIgnoreCase);
				bool flag2 = result >= 1;
				return flag && flag2;
			}
			catch
			{
				return false;
			}
		}

		private static void WriteMarker(string markerPath, string melonVersion)
		{
			try
			{
				string contents = $"melonloader={melonVersion}{Environment.NewLine}fixrevision={1}{Environment.NewLine}timestamp={DateTime.UtcNow:O}";
				File.WriteAllText(markerPath, contents);
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[S1APILoader] Failed to write interop fix marker: " + ex.Message);
			}
		}

		private static bool HasPreGeneratedIl2CppAssemblies()
		{
			try
			{
				string il2CppAssembliesDirectory = MelonEnvironment.Il2CppAssembliesDirectory;
				if (!Directory.Exists(il2CppAssembliesDirectory))
				{
					return false;
				}
				string[] files = Directory.GetFiles(il2CppAssembliesDirectory, "Il2Cpp*.dll", SearchOption.TopDirectoryOnly);
				return files.Length != 0;
			}
			catch
			{
				return false;
			}
		}

		private static bool ForceRunIl2CppAssemblyGenerator()
		{
			Assembly assembly = typeof(MelonPlugin).Assembly;
			try
			{
				object obj = (assembly.GetType("MelonLoader.LoaderConfig")?.GetProperty("Current", BindingFlags.Static | BindingFlags.Public))?.GetValue(null);
				if (obj != null)
				{
					object obj2 = obj.GetType().GetProperty("UnityEngine", BindingFlags.Instance | BindingFlags.Public)?.GetValue(obj);
					if (obj2 != null)
					{
						PropertyInfo property = obj2.GetType().GetProperty("ForceRegeneration", BindingFlags.Instance | BindingFlags.Public);
						if (property != null && property.CanWrite)
						{
							property.SetValue(obj2, true);
							MelonLogger.Msg("[S1APILoader] Set LoaderConfig.Current.UnityEngine.ForceRegeneration=true");
						}
						else
						{
							MelonLogger.Warning("[S1APILoader] ForceRegeneration property is read-only or not found.");
						}
					}
					else
					{
						MelonLogger.Warning("[S1APILoader] UnityEngine config is null.");
					}
				}
				else
				{
					MelonLogger.Warning("[S1APILoader] LoaderConfig.Current is null.");
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("[S1APILoader] Error setting ForceRegeneration via LoaderConfig: " + ex.Message);
			}
			return true;
		}

		private static void NormalizeBuild(string folder, string build, bool shouldBeEnabled, string fileNamePattern)
		{
			if (!Directory.Exists(folder))
			{
				return;
			}
			string path = string.Format(fileNamePattern, build);
			string text = Path.Combine(folder, path);
			string text2 = text + ".disabled";
			bool flag = File.Exists(text);
			bool flag2 = File.Exists(text2);
			if (!flag && !flag2)
			{
				return;
			}
			if (shouldBeEnabled)
			{
				if (flag && !flag2)
				{
					return;
				}
				string text3 = null;
				if (flag2)
				{
					text3 = text2;
				}
				if (flag)
				{
					text3 = ChooseNewest(text3, text);
				}
				if (text3 != null)
				{
					if (StringComparer.OrdinalIgnoreCase.Equals(text3, text2))
					{
						SafeDelete(text);
						SafeMoveReplace(text2, text);
					}
					else if (flag2)
					{
						SafeDelete(text2);
					}
				}
			}
			else
			{
				if ((!flag && flag2) || !flag)
				{
					return;
				}
				SafeDelete(text2);
				if (!SafeMoveReplace(text, text2) || File.Exists(text))
				{
					MelonLogger.Warning("Failed to disable '" + text + "'. File may be locked or in use.");
					if (File.Exists(text))
					{
						TryCopyAndDelete(text, text2);
					}
				}
			}
		}

		private static string ChooseNewest(string? currentBestPath, string candidatePath)
		{
			if (currentBestPath == null)
			{
				return candidatePath;
			}
			Version version = TryGetAssemblyVersion(currentBestPath);
			Version version2 = TryGetAssemblyVersion(candidatePath);
			if (version != null && version2 != null)
			{
				return (version2 > version) ? candidatePath : currentBestPath;
			}
			DateTime safeWriteTimeUtc = GetSafeWriteTimeUtc(currentBestPath);
			DateTime safeWriteTimeUtc2 = GetSafeWriteTimeUtc(candidatePath);
			return (safeWriteTimeUtc2 > safeWriteTimeUtc) ? candidatePath : currentBestPath;
		}

		private static Version? TryGetAssemblyVersion(string path)
		{
			try
			{
				return AssemblyName.GetAssemblyName(path).Version;
			}
			catch
			{
				return null;
			}
		}

		private static DateTime GetSafeWriteTimeUtc(string path)
		{
			try
			{
				return File.GetLastWriteTimeUtc(path);
			}
			catch
			{
				return DateTime.MinValue;
			}
		}

		private static bool SafeMoveReplace(string sourcePath, string destinationPath)
		{
			try
			{
				if (File.Exists(destinationPath))
				{
					File.Delete(destinationPath);
				}
				File.Move(sourcePath, destinationPath);
				return true;
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Failed to move '" + sourcePath + "' to '" + destinationPath + "': " + ex.Message);
				return false;
			}
		}

		private static void TryCopyAndDelete(string sourcePath, string destinationPath)
		{
			try
			{
				if (File.Exists(destinationPath))
				{
					File.Delete(destinationPath);
				}
				File.Copy(sourcePath, destinationPath, overwrite: true);
				try
				{
					File.Delete(sourcePath);
				}
				catch
				{
					MelonLogger.Warning("Copied '" + sourcePath + "' to '" + destinationPath + "' but could not delete source. You may need to manually delete it.");
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Failed to copy '" + sourcePath + "' to '" + destinationPath + "': " + ex.Message);
			}
		}

		private static void SafeDelete(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch (Exception ex)
			{
				MelonLogger.Warning("Failed to delete '" + path + "': " + ex.Message);
			}
		}
	}
}