Decompiled source of OstraAutoloader v0.1.10

plugins/Ostranauts.Autoloader.dll

Decompiled 2 weeks ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using LitJson;
using Microsoft.CodeAnalysis;
using OstraAutoloader.Courtesy;
using OstraAutoloader.Mods;
using OstraAutoloader.Patches;
using Tommy;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: AssemblyCompany("Ostranauts.Autoloader")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("0.1.10.0")]
[assembly: AssemblyInformationalVersion("0.1.10+3a76e54111c157c66af6b3a3003f4fa95799b851")]
[assembly: AssemblyProduct("Ostranauts.Autoloader")]
[assembly: AssemblyTitle("Ostranauts.Autoloader")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.1.10.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.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]
	[Microsoft.CodeAnalysis.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;
		}
	}
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
internal static class LCMPluginInfo
{
	public const string PLUGIN_GUID = "Ostranauts.Autoloader";

	public const string PLUGIN_NAME = "Ostranauts.Autoloader";

	public const string PLUGIN_VERSION = "0.1.10";
}
namespace OstraAutoloader
{
	[BepInPlugin("Ostranauts.Autoloader", "Ostranauts.Autoloader", "0.1.10")]
	public class AutoloaderPlugin : BaseUnityPlugin
	{
		internal static AutoloaderPlugin Instance;

		internal ManualLogSource Log;

		internal ConfigEntry<bool> BackupNeeded;

		internal ConfigEntry<bool> UninstallMode;

		public AutoloaderPlugin()
		{
			if (Instance == null)
			{
				Instance = this;
			}
			Log = ((BaseUnityPlugin)this).Logger;
			BackupNeeded = ((BaseUnityPlugin)this).Config.Bind<bool>("Backup", "BackupNeeded", true, "This is managed automatically by Ostra.Autoloader to keep track of if a backup of the original load_order.json file was made");
			UninstallMode = ((BaseUnityPlugin)this).Config.Bind<bool>("Uninstall", "UninstallMode", false, "Set this to true and run the game once. Ostra.Autoloader will undo any changes it made, where possible");
		}

		private void Awake()
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Log.LogInfo((object)"Plugin Ostranauts.Autoloader version 0.1.10 is loaded!");
			Log.LogDebug((object)("Plugins Path: " + Application.dataPath));
			Harmony val = new Harmony("Ostranauts.Autoloader");
			try
			{
				val.PatchAll(typeof(DataHandler_Patches));
			}
			catch (Exception arg)
			{
				Log.LogFatal((object)$"Failed to run a patch. Autoloader is aborting\n{arg}");
			}
		}
	}
}
namespace OstraAutoloader.Patches
{
	[HarmonyPatch(typeof(DataHandler), "Init")]
	public static class DataHandler_Patches
	{
		[HarmonyPrefix]
		public static bool Init_Prefix()
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_007f: Expected O, but got Unknown
			string text = string.Empty;
			AutoloaderPlugin instance = AutoloaderPlugin.Instance;
			if (File.Exists(Application.persistentDataPath + "/settings.json"))
			{
				Dictionary<string, JsonUserSettings> dictionary = new Dictionary<string, JsonUserSettings>();
				DataHandler.JsonToData<JsonUserSettings>(Application.persistentDataPath + "/settings.json", dictionary);
				if (dictionary.ContainsKey("UserSettings"))
				{
					text = dictionary["UserSettings"].strPathMods;
				}
			}
			if (string.IsNullOrEmpty(text))
			{
				JsonUserSettings val = new JsonUserSettings();
				val.Init();
				text = val.strPathMods;
			}
			string directoryName = Path.GetDirectoryName(text);
			if (Directory.Exists(text))
			{
				string text2 = "Error: The load path is a directory when it should be `loading_order.json` please use the file menu under option to select the correct file. Ostra.Autoloader cannot continue, shutting down.\n  Path: " + text;
				instance.Log.LogError((object)text2);
				ConsoleToGUI.instance.Log(text2, string.Empty, (LogType)0);
				return true;
			}
			instance.Log.LogDebug((object)("Using mods path: " + directoryName));
			if (instance.UninstallMode.Value)
			{
				instance.Log.LogMessage((object)"Ostra.Autoloader is uninstalling, goodbye!");
				SafeLoadOrderManager.RestoreLoadOrderIfPossible(text);
				return true;
			}
			DirectoryInfo directoryInfo = new DirectoryInfo(Paths.PluginPath);
			DirectoryInfo directoryInfo2 = new DirectoryInfo(directoryName);
			instance.Log.LogInfo((object)("Searching " + directoryInfo.FullName));
			ModListing.FindAllModsInDirectory(directoryInfo);
			instance.Log.LogInfo((object)("Searching " + directoryInfo2.FullName));
			ModListing.FindAllModsInDirectory(directoryInfo2);
			ModListing.CreateLoadingOrder();
			instance.Log.LogInfo((object)$"populating load_order.json automatically with {ModListing.sortedMods.Length} autoload mods");
			SafeLoadOrderManager.BackupLoadOrderIfRequired(text);
			WriteLoadingOrder(text);
			return true;
		}

		internal static void WriteLoadingOrder(string filePath)
		{
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Expected O, but got Unknown
			//IL_0195: Unknown result type (might be due to invalid IL or missing references)
			//IL_019a: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ac: Expected O, but got Unknown
			AutoloaderPlugin instance = AutoloaderPlugin.Instance;
			FileInfo fileInfo = new FileInfo(filePath);
			DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetDirectoryName(filePath));
			if (!directoryInfo.Exists)
			{
				instance.Log.LogWarning((object)("\nUnable to write load_order.json\nAutoloader will now shut down, your game will load without mods\n  Reason:    Mod folder does not exist\n  Directory: " + directoryInfo.FullName + "\n"));
				ConsoleToGUI.instance.Log("[Ostra.Autoloader] Unable to write mod_info.json, game will load without mods, see LogOutput.txt for more info.", "", (LogType)2);
				return;
			}
			JsonModList val = new JsonModList();
			val.strName = "Mod Loading Order";
			List<string> list = new List<string>();
			list.Add("core");
			list.AddRange(ModListing.sortedMods.Select((AutoloadMod x) => x.BaseDir.FullName));
			val.aLoadOrder = list.ToArray();
			val.aIgnorePatterns = new string[1] { "StreamingAssets/data/names_full" };
			JsonModList item = val;
			StringBuilder stringBuilder = new StringBuilder("\nAutoloaded the following mods in this order:\n");
			stringBuilder.Append("  ");
			stringBuilder.AppendLine("core");
			AutoloadMod[] sortedMods = ModListing.sortedMods;
			foreach (AutoloadMod autoloadMod in sortedMods)
			{
				stringBuilder.Append("  ");
				if (autoloadMod.FailedToLoad)
				{
					stringBuilder.Append("(FAILED) ");
				}
				stringBuilder.AppendLine(autoloadMod.Inf.strName);
			}
			instance.Log.LogInfo((object)stringBuilder);
			ConsoleToGUI.instance.LogInfo($"[Ostra.Autoloader]\n{stringBuilder}");
			using StreamWriter streamWriter = new StreamWriter(filePath);
			JsonWriter val2 = new JsonWriter((TextWriter)streamWriter)
			{
				PrettyPrint = true,
				IndentValue = 2
			};
			List<JsonModList> list2 = new List<JsonModList>(1) { item };
			JsonMapper.ToJson((object)list2, val2);
		}
	}
}
namespace OstraAutoloader.Mods
{
	public class AutoloadMetaInf
	{
		public readonly string[] Dependencies = new string[0];

		public readonly string[] SoftDependencies = new string[0];

		public readonly ModLoadingGroup LoadingGroup = ModLoadingGroup.WithVanilla;

		public AutoloadMetaInf(string[] Dependencies, string[] SoftDependencies, ModLoadingGroup LoadingGroup)
		{
			this.Dependencies = Dependencies;
			this.SoftDependencies = SoftDependencies;
			this.LoadingGroup = LoadingGroup;
		}

		public static AutoloadMetaInf? FromFile(FileInfo file)
		{
			if (!file.Exists)
			{
				return null;
			}
			using StreamReader streamReader = File.OpenText(file.FullName);
			TomlTable val = TOML.Parse((TextReader)streamReader);
			AutoloaderPlugin instance = AutoloaderPlugin.Instance;
			TomlString asString = ((TomlNode)val)["FileType"].AsString;
			if (!((TomlNode)asString).HasValue || !string.Equals(asString.Value, "AUTOLOAD.META", StringComparison.InvariantCultureIgnoreCase))
			{
				instance.Log.LogWarning((object)$"Skipping a malformed Autoload meta file (no or bad FileType header): {((TomlNode)asString).HasValue}|{asString.Value}");
				return null;
			}
			string value = ((TomlNode)val)["LoadGroup"].AsString.Value;
			ModLoadingGroup loadingGroup;
			switch (value.ToLowerInvariant())
			{
			case "withvanilla":
				loadingGroup = ModLoadingGroup.WithVanilla;
				break;
			case "ffucore":
				loadingGroup = ModLoadingGroup.FFUCore;
				break;
			case "afterffu":
				loadingGroup = ModLoadingGroup.AfterFFU;
				break;
			default:
				instance.Log.LogWarning((object)("Unable to parse loading group " + value));
				return null;
			}
			TomlNode val2 = ((TomlNode)val)["dependencies"];
			if (!val2.IsTable)
			{
				instance.Log.LogWarning((object)"Dependencies is malformed");
				return null;
			}
			TomlNode val3 = ((TomlNode)val)["softDependencies"];
			string[] softDependencies = new string[0];
			if (val3.IsTable)
			{
				softDependencies = val3.Keys.ToArray();
			}
			return new AutoloadMetaInf(val2.Keys.ToArray(), softDependencies, loadingGroup);
		}
	}
	public class AutoloadMod
	{
		public enum ResolutionState
		{
			Pending,
			Resolved,
			Failed
		}

		public readonly ModInfo Inf;

		public readonly AutoloadMetaInf MetaInf;

		public readonly DirectoryInfo BaseDir;

		public AutoloadMod[] Dependencies = new AutoloadMod[0];

		private ResolutionState _state = ResolutionState.Pending;

		private bool _visited = false;

		public bool DependenciesResolved => _state == ResolutionState.Resolved;

		public bool FailedToLoad => _state == ResolutionState.Failed;

		public AutoloadMod(ModInfo info, AutoloadMetaInf meta, DirectoryInfo baseDir)
		{
			Inf = info;
			MetaInf = meta;
			BaseDir = baseDir;
		}

		public bool ResolveDependencies()
		{
			AutoloaderPlugin plugin = AutoloaderPlugin.Instance;
			if (_visited)
			{
				plugin.Log.LogWarning((object)("Recursive dependency detected in " + Inf.strName));
				return false;
			}
			if (DependenciesResolved)
			{
				return true;
			}
			if (FailedToLoad)
			{
				return false;
			}
			List<AutoloadMod> temp = new List<AutoloadMod>();
			_visited = true;
			bool flag = false;
			string[] dependencies = MetaInf.Dependencies;
			foreach (string name2 in dependencies)
			{
				if (!TryLoadMod(name2))
				{
					flag = true;
					break;
				}
			}
			if (!flag)
			{
				string[] softDependencies = MetaInf.SoftDependencies;
				foreach (string text in softDependencies)
				{
					if (!TryLoadMod(text, hardDep: false))
					{
						plugin.Log.LogDebug((object)("Failed to resolve a soft dependency " + text + " for " + Inf.strName));
					}
				}
			}
			Dependencies = temp.ToArray();
			_state = ((!flag) ? ResolutionState.Resolved : ResolutionState.Failed);
			_visited = false;
			return !flag;
			bool TryLoadMod(string name, bool hardDep = true)
			{
				string text2 = (hardDep ? "Dependency" : "Soft Dependency");
				if (!ModListing.allModsByIdentifier.TryGetValue(name, out AutoloadMod value))
				{
					if (hardDep)
					{
						plugin.Log.LogWarning((object)("Unable to resolve dependency " + name + " for mod " + Inf.strName));
					}
					return false;
				}
				if (value.DependenciesResolved || (!value.FailedToLoad && value.ResolveDependencies()))
				{
					if (MetaInf.LoadingGroup >= value.MetaInf.LoadingGroup)
					{
						temp.Add(value);
						return true;
					}
					plugin.Log.LogWarning((object)(text2 + " " + name + " is in a later loading group than mod " + Inf.strName));
					return false;
				}
				if (hardDep)
				{
					plugin.Log.LogWarning((object)("Unable to resolve Dependency " + name + " for mod " + Inf.strName));
				}
				return false;
			}
		}

		public static AutoloadMod? FromDirectory(DirectoryInfo dir)
		{
			AutoloaderPlugin instance = AutoloaderPlugin.Instance;
			instance.Log.LogDebug((object)("Attempting to create AutoloadMod from " + dir.Name));
			if (!dir.Exists)
			{
				instance.Log.LogWarning((object)("Unable to create an Autoload from " + dir.FullName));
				return null;
			}
			string fileName = Path.Combine(dir.FullName, "mod_info.json");
			string fileName2 = Path.Combine(dir.FullName, "Autoload.Meta.toml");
			FileInfo fileInfo = new FileInfo(fileName);
			FileInfo fileInfo2 = new FileInfo(fileName2);
			if (!fileInfo.Exists)
			{
				instance.Log.LogWarning((object)(dir.FullName + " does not contain a mod_info.json"));
				return null;
			}
			if (!fileInfo2.Exists)
			{
				instance.Log.LogWarning((object)(dir.FullName + " does not contain an Autoload.Meta.toml"));
				return null;
			}
			try
			{
				Dictionary<string, JsonModInfo> dictionary = new Dictionary<string, JsonModInfo>();
				DataHandler.JsonToData<JsonModInfo>(fileInfo.FullName, dictionary);
				if (dictionary.Count >= 1)
				{
					JsonModInfo val = dictionary.Values.FirstOrDefault();
					if (val != null)
					{
						ModInfo info = new ModInfo(val);
						AutoloadMetaInf autoloadMetaInf = AutoloadMetaInf.FromFile(fileInfo2);
						if (autoloadMetaInf == null)
						{
							instance.Log.LogWarning((object)(fileInfo2.FullName + " cannot be deserialized into a mod meta"));
							return null;
						}
						return new AutoloadMod(info, autoloadMetaInf, dir);
					}
				}
				instance.Log.LogWarning((object)(fileInfo.FullName + " cannot be deserialized into a mod info"));
				return null;
			}
			catch (Exception arg)
			{
				instance.Log.LogWarning((object)$"Failed to create Autoload mod\n{arg}");
				return null;
			}
		}
	}
	public class ModInfo
	{
		public readonly string strName;

		public readonly string strAuthor;

		public readonly string strModURL;

		public readonly string strGameVersion;

		public readonly string strModVersion;

		public readonly string strNotes;

		public ModInfo(string strName, string strAuthor, string strModURL, string strGameVersion, string strModVersion, string strNotes)
		{
			this.strName = strName;
			this.strAuthor = strAuthor;
			this.strModURL = strModURL;
			this.strGameVersion = strGameVersion;
			this.strModVersion = strModVersion;
			this.strNotes = strNotes;
		}

		public ModInfo(JsonModInfo info)
			: this(info.strName, info.strAuthor, info.strModURL, info.strGameVersion, info.strModVersion, info.strNotes)
		{
		}
	}
	public static class ModListing
	{
		internal static readonly Dictionary<string, AutoloadMod> allModsByIdentifier = new Dictionary<string, AutoloadMod>();

		internal static AutoloadMod[] sortedMods = new AutoloadMod[0];

		internal static void FindAllModsInDirectory(DirectoryInfo baseDir)
		{
			AutoloaderPlugin instance = AutoloaderPlugin.Instance;
			if (!baseDir.Exists)
			{
				instance.Log.LogInfo((object)("Skipping a directory in FindAllModsInDirectory(DirectoryInfo baseDir)\n  Reason:    Directory does not exist\n  Directory: " + baseDir.FullName + "\n\nPlease check that your configured mod folder exists, mods from other sources will still load."));
				return;
			}
			DirectoryInfo[] directories = baseDir.GetDirectories();
			foreach (DirectoryInfo baseDir2 in directories)
			{
				FindAllModsInDirectory(baseDir2);
			}
			FileInfo[] files = baseDir.GetFiles("Autoload.Meta.toml");
			foreach (FileInfo fileInfo in files)
			{
				if (!(fileInfo.Name == "Autoload.Meta.toml"))
				{
					continue;
				}
				instance.Log.LogDebug((object)("Found an autoload in " + baseDir.Name));
				try
				{
					AutoloadMod autoloadMod = AutoloadMod.FromDirectory(baseDir);
					if (autoloadMod != null)
					{
						if (allModsByIdentifier.ContainsKey(autoloadMod.Inf.strName))
						{
							instance.Log.LogWarning((object)("Attempting to load the same mod twice! " + autoloadMod.Inf.strName));
							continue;
						}
						instance.Log.LogInfo((object)("Registered " + autoloadMod.Inf.strName + "@" + autoloadMod.Inf.strModVersion));
						allModsByIdentifier.Add(autoloadMod.Inf.strName, autoloadMod);
					}
				}
				catch (Exception arg)
				{
					instance.Log.LogWarning((object)$"Encountered malformed Autoload:\n{arg}");
				}
			}
		}

		internal static void CreateLoadingOrder()
		{
			List<AutoloadMod> _sortedTemp = new List<AutoloadMod>();
			HashSet<AutoloadMod> _added = new HashSet<AutoloadMod>();
			AutoloadMod[] items2 = allModsByIdentifier.Values.ToArray();
			ResolveLoadingGroup(items2, ModLoadingGroup.WithVanilla);
			ResolveLoadingGroup(items2, ModLoadingGroup.FFUCore);
			ResolveLoadingGroup(items2, ModLoadingGroup.AfterFFU);
			sortedMods = _sortedTemp.ToArray();
			void ResolveAndAdd(AutoloadMod item)
			{
				if (!_added.Contains(item) && item.ResolveDependencies())
				{
					AutoloadMod[] dependencies = item.Dependencies;
					foreach (AutoloadMod autoloadMod in dependencies)
					{
						if (autoloadMod.ResolveDependencies())
						{
							ResolveAndAdd(autoloadMod);
						}
					}
					_sortedTemp.Add(item);
					_added.Add(item);
				}
			}
			void ResolveLoadingGroup(AutoloadMod[] items, ModLoadingGroup group)
			{
				foreach (AutoloadMod autoloadMod2 in items)
				{
					if (autoloadMod2.MetaInf.LoadingGroup == group && autoloadMod2.ResolveDependencies())
					{
						ResolveAndAdd(autoloadMod2);
					}
				}
			}
		}
	}
	public enum ModLoadingGroup
	{
		WithVanilla,
		FFUCore,
		AfterFFU
	}
}
namespace OstraAutoloader.Courtesy
{
	public static class SafeLoadOrderManager
	{
		public static void BackupLoadOrderIfRequired(string loadLoc)
		{
			AutoloaderPlugin instance = AutoloaderPlugin.Instance;
			FileInfo fileInfo = new FileInfo(loadLoc);
			FileInfo fileInfo2 = new FileInfo(loadLoc + ".old");
			if (!instance.BackupNeeded.Value)
			{
				instance.Log.LogDebug((object)"Skipping backup, we've already made one");
				return;
			}
			instance.BackupNeeded.Value = false;
			((BaseUnityPlugin)instance).Config.Save();
			if (!fileInfo.Exists)
			{
				instance.Log.LogMessage((object)"Skipping backup, there is no load_order.json file");
				return;
			}
			if (fileInfo2.Exists)
			{
				instance.Log.LogMessage((object)"Skipping backup, already exists");
				return;
			}
			try
			{
				fileInfo.CopyTo(fileInfo2.FullName);
				instance.Log.LogMessage((object)"Copied original load_order.json to load_order.json.old");
			}
			catch (Exception arg)
			{
				instance.Log.LogError((object)$"Unable to backup load_order.json!\n{arg}");
			}
		}

		public static void RestoreLoadOrderIfPossible(string loadLoc)
		{
			AutoloaderPlugin instance = AutoloaderPlugin.Instance;
			FileInfo fileInfo = new FileInfo(loadLoc);
			FileInfo fileInfo2 = new FileInfo(loadLoc + ".old");
			if (!fileInfo2.Exists)
			{
				instance.Log.LogMessage((object)"Not restoring load_order.json backup, no file to restore");
				return;
			}
			if (fileInfo.Exists)
			{
				instance.Log.LogMessage((object)"Deleting auto-generated load_order.json");
				fileInfo.Delete();
			}
			try
			{
				fileInfo2.CopyTo(fileInfo.FullName);
				instance.Log.LogMessage((object)"Restored backup load_order.json.old to load_order.json");
				fileInfo2.Delete();
			}
			catch (Exception arg)
			{
				instance.Log.LogError((object)$"Unable to fully restore load_order.json!\n{arg}");
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}