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+445586c624144aaaaf08ada54621558064b0ab7a")] [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); } } } }