Decompiled source of PluginReloader v1.0.0

PluginReloader.dll

Decompiled 2 months ago
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text.Json;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using PluginReloader.Behaviors;
using PluginReloader.Settings;
using PluginReloader.Systems;
using UnityEngine;
using Utils.Database;
using Utils.Logger;
using Utils.Settings;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("PluginReloader")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyDescription("PluginReloader is a BepInEx plugin designed to streamline the development and debugging of other BepInEx plugins. It provides hot-reloading capabilities for plugins, configuration and data to make plugin iteration faster and safer.\n")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+062fa89640461173ec537b0db470eb510e3ee883")]
[assembly: AssemblyProduct("PluginReloader")]
[assembly: AssemblyTitle("PluginReloader")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.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;
		}
	}
}
namespace Utils.Settings
{
	public class Config
	{
		public static string PluginGUID;

		public static string PluginFolderPath;

		internal static ConfigFile cfg;

		private static List<Action> configActions = new List<Action>();

		public static ConfigElement<T> Bind<T>(string section, string key, T defaultValue, string description)
		{
			return new ConfigElement<T>(cfg.Bind<T>(section, key, defaultValue, description), section, key, defaultValue, description);
		}

		public static void Save()
		{
			cfg.Save();
		}

		public static void Reload()
		{
			cfg.Reload();
		}

		public static void Setup(string pluginGUID, ConfigFile config, int skipCaller = 4, params Action[] actions)
		{
			PluginGUID = pluginGUID;
			cfg = config;
			PluginFolderPath = Paths.ConfigPath + "\\" + PluginGUID;
			if (!Directory.Exists(PluginFolderPath))
			{
				Directory.CreateDirectory(PluginFolderPath);
			}
			ENV.Debug.Setup(skipCaller);
			AddConfigActions(actions);
		}

		public static void Load()
		{
			if (configActions == null || configActions.Count == 0)
			{
				return;
			}
			foreach (Action configAction in configActions)
			{
				configAction();
			}
		}

		public static void AddConfigActions(params Action[] actions)
		{
			configActions.AddRange(actions);
		}
	}
	public class ConfigElement<T>
	{
		public List<Action<T>> OnValueChanged = new List<Action<T>>();

		private ConfigEntry<T> OriginalConfig;

		public string Section { get; }

		public string Key { get; }

		public string Description { get; }

		public Type ElementType => typeof(T);

		public T Value
		{
			get
			{
				return GetValue();
			}
			set
			{
				SetValue(value);
			}
		}

		public T DefaultValue { get; }

		private T GetValue()
		{
			return OriginalConfig.Value;
		}

		public ConfigElement(ConfigEntry<T> original, string section, string key, T defaultValue, string description)
		{
			OriginalConfig = original;
			Section = section;
			Key = key;
			DefaultValue = defaultValue;
			Description = description;
		}

		private void SetValue(T value)
		{
			if ((Value == null && value == null) || (Value != null && Value.Equals(value)))
			{
				return;
			}
			OriginalConfig.Value = value;
			foreach (Action<T> item in OnValueChanged)
			{
				item?.Invoke(value);
			}
		}
	}
	public static class ENV
	{
		public class Debug
		{
			private static string debugSection = "\ud83e\udeb2Debug";

			private static ConfigElement<bool> DebugLogOnTempFile;

			private static ConfigElement<bool> DebugEnableTraceLogs;

			public static void Setup(int skipCaller)
			{
				Config.AddConfigActions(delegate
				{
					load(skipCaller);
				});
			}

			private static void load(int skipCaller)
			{
				if (enableDebugConfigs(skipCaller))
				{
					DebugLogOnTempFile = Config.Bind(debugSection, "LogOnTempFile", defaultValue: false, "Enabled, will log every plugin log on a temp file");
					DebugEnableTraceLogs = Config.Bind(debugSection, "EnableTraceLogs", defaultValue: false, "Enabled, will print Trace logs (Debug output in BepInEx)");
				}
				validateValues();
			}

			private static void validateValues()
			{
				if (DebugLogOnTempFile != null)
				{
					LogOnTempFile = DebugLogOnTempFile.Value;
				}
				if (DebugEnableTraceLogs != null)
				{
					EnableTraceLogs = DebugEnableTraceLogs.Value;
				}
				Config.cfg.Save();
			}

			private static bool enableDebugConfigs(int skipCaller)
			{
				return new StackTrace().GetFrame(skipCaller).GetMethod().DeclaringType.Assembly.GetCustomAttribute<AssemblyConfigurationAttribute>()?.Configuration != "Release";
			}
		}

		public static bool LogOnTempFile;

		public static bool EnableTraceLogs;
	}
}
namespace Utils.Logger
{
	public class Config
	{
		internal static ManualLogSource logger;

		private static string tempLogFile;

		public static void Setup(ManualLogSource logger, string worldType = "")
		{
			Config.logger = logger;
			string value = "";
			if (worldType != "")
			{
				value = "-" + worldType;
			}
			tempLogFile = $"{Utils.Settings.Config.PluginFolderPath}\\{Utils.Settings.Config.PluginGUID}{value}.txt";
			Log.Start("Using \"" + tempLogFile + "\" to save logs.");
		}

		public static void TestSetup()
		{
		}

		internal static void logFile(object data, string level, string prefix = "")
		{
			if (Utils.Settings.ENV.LogOnTempFile)
			{
				using (StreamWriter streamWriter = File.AppendText(tempLogFile))
				{
					string value = $"{prefix}{DateTime.Now.ToString("hh:mm:ss")} [{level} {Utils.Settings.Config.PluginGUID}]: {data}";
					streamWriter.WriteLine(value);
				}
			}
		}
	}
	public class Log
	{
		private static ConcurrentDictionary<string, long> timedLog = new ConcurrentDictionary<string, long>();

		private static List<string> firstLog = new List<string>();

		public static void Info(object data)
		{
			Config.logger.LogInfo(data);
			Config.logFile(data, "Info:   ");
		}

		public static void Error(object data)
		{
			Config.logger.LogError(data);
			Config.logFile(data, "Error:  ");
		}

		public static void Debug(object data)
		{
			Config.logger.LogDebug(data);
			Config.logFile(data, "Debug:  ");
		}

		public static void Fatal(object data)
		{
			Config.logger.LogFatal(data);
			Config.logFile(data, "Fatal:  ");
		}

		public static void Warning(object data)
		{
			Config.logger.LogWarning(data);
			Config.logFile(data, "Warning:");
		}

		public static void Message(object data)
		{
			Config.logger.LogMessage(data);
			Config.logFile(data, "Message:");
		}

		public static void Start(object data)
		{
			Config.logger.LogMessage(data);
			Config.logFile(data, "Start:  ", "\n");
		}

		public static void Trace(object data)
		{
			if (Utils.Settings.ENV.EnableTraceLogs)
			{
				Config.logger.LogDebug(data);
				Config.logFile(data, "Trace:  ");
			}
		}

		public static void Struct<T>(T data)
		{
			if (Utils.Settings.ENV.EnableTraceLogs)
			{
				string text = structToString(data);
				Config.logger.LogDebug((object)text);
				Config.logFile(text, "Struct: ");
			}
		}

		public static void Timed(Action action, int ms, string id = "")
		{
			if (!blocked(ms, id))
			{
				action();
			}
		}

		public static void First(Action action, string id = "")
		{
			if (first(id))
			{
				action();
			}
		}

		private static string structToString<T>(T data)
		{
			Type type = data.GetType();
			FieldInfo[] fields = type.GetFields();
			PropertyInfo[] properties = type.GetProperties();
			Dictionary<string, object> values = new Dictionary<string, object>();
			Array.ForEach(properties, delegate(PropertyInfo property)
			{
				values.TryAdd(property.Name, property.GetValue(data));
			});
			Array.ForEach(fields, delegate(FieldInfo field)
			{
				values.TryAdd(field.Name, field.GetValue(data));
			});
			List<string> list = new List<string>();
			foreach (KeyValuePair<string, object> item in values)
			{
				list.Add($"\"{item.Key}\":\"{item.Value}\"");
			}
			return "\"" + type.ToString() + "\": {" + string.Join(",", list) + "}";
		}

		private static bool first(string id)
		{
			if (firstLog.Contains(id))
			{
				return false;
			}
			firstLog.Add(id);
			return true;
		}

		private static bool blocked(int ms, string id)
		{
			long num = DateTimeOffset.Now.ToUnixTimeMilliseconds();
			if (!timedLog.TryGetValue(id, out var value))
			{
				long newTimestamp = DateTimeOffset.Now.AddMilliseconds(ms).ToUnixTimeMilliseconds();
				timedLog.AddOrUpdate(id, newTimestamp, (string key, long oldValue) => newTimestamp);
				return true;
			}
			if (num < value)
			{
				return true;
			}
			timedLog.TryRemove(id, out var _);
			return false;
		}
	}
}
namespace Utils.Database
{
	public static class Cache
	{
		public static ConcurrentDictionary<string, long> LastUpdate = new ConcurrentDictionary<string, long>();

		public static ConcurrentDictionary<string, bool> Cached = new ConcurrentDictionary<string, bool>();

		public static bool IsBlocked(string key, long blockedDuration)
		{
			long now = DateTimeOffset.Now.ToUnixTimeMilliseconds();
			if (LastUpdate.TryGetValue(key, out var value) && now - value < blockedDuration)
			{
				return true;
			}
			LastUpdate.AddOrUpdate(key, now, (string _, long _) => now);
			return false;
		}

		public static bool Key(string key, bool cache = true)
		{
			if (!cache)
			{
				return false;
			}
			return Cached.AddOrUpdate(key, addValue: true, (string _, bool _) => true);
		}

		public static bool Exists(string key)
		{
			if (Cached.TryGetValue(key, out var value) && value)
			{
				return true;
			}
			return false;
		}

		public static bool RemoveKey(string key)
		{
			bool value;
			return Cached.TryRemove(key, out value);
		}

		public static void Clear()
		{
			Cached.Clear();
			LastUpdate.Clear();
		}
	}
	public static class DB
	{
		private static JsonSerializerOptions JSONOptions = new JsonSerializerOptions
		{
			WriteIndented = false,
			IncludeFields = false
		};

		private static JsonSerializerOptions Pretty_JSON_options = new JsonSerializerOptions
		{
			WriteIndented = true,
			IncludeFields = true
		};

		private static List<Action> loadActions = new List<Action>();

		private static List<Action> saveActions = new List<Action>();

		private static List<Action> cleanActions = new List<Action>();

		public static void Setup(List<Action> load, List<Action> save, List<Action> clean)
		{
			if (load != null)
			{
				loadActions.AddRange(load);
			}
			if (save != null)
			{
				saveActions.AddRange(save);
			}
			if (clean != null)
			{
				cleanActions.AddRange(clean);
			}
		}

		public static void Load()
		{
			if (loadActions == null)
			{
				return;
			}
			foreach (Action loadAction in loadActions)
			{
				loadAction();
			}
			Log.Info($"All({loadActions.Count}) database actions loaded.");
		}

		public static void Save()
		{
			if (saveActions == null)
			{
				return;
			}
			foreach (Action saveAction in saveActions)
			{
				saveAction();
			}
			Log.Info($"All({saveActions.Count}) database actions saved.");
		}

		public static void Clean()
		{
			if (cleanActions == null)
			{
				return;
			}
			foreach (Action cleanAction in cleanActions)
			{
				cleanAction();
			}
			Log.Info($"All({cleanActions.Count}) database actions cleaned.");
		}

		public static void AddLoadActions(params Action[] actions)
		{
			loadActions.AddRange(actions);
		}

		public static void AddSaveActions(params Action[] actions)
		{
			saveActions.AddRange(actions);
		}

		public static void AddCleanActions(params Action[] actions)
		{
			cleanActions.AddRange(actions);
		}

		public static void saveFile<T>(string fileName, T data, bool pretty = false, string extension = ".json")
		{
			JsonSerializerOptions options = JSONOptions;
			if (pretty)
			{
				options = Pretty_JSON_options;
			}
			File.WriteAllText(Utils.Settings.Config.PluginFolderPath + "\\" + fileName + extension, JsonSerializer.Serialize(data, options));
		}

		public static void loadFile<T>(string fileName, ref T data, string extension = ".json") where T : new()
		{
			string path = Utils.Settings.Config.PluginFolderPath + "\\" + fileName + extension;
			if (!File.Exists(path))
			{
				File.Create(path).Dispose();
			}
			string json = File.ReadAllText(path);
			try
			{
				data = JsonSerializer.Deserialize<T>(json);
				Log.Trace(fileName + " DB Populated");
			}
			catch
			{
				data = new T();
				Log.Trace(fileName + " DB Created");
			}
		}
	}
}
namespace PluginReloader
{
	[BepInPlugin("PluginReloader", "PluginReloader", "1.0.0")]
	public class Plugin : BasePlugin
	{
		public static readonly Harmony Harmony = new Harmony("PluginReloader");

		private static MonoBehaviour _clientBehavior;

		public override void Load()
		{
			PluginReloader.Settings.Config.Load(((BasePlugin)this).Config, ((BasePlugin)this).Log, "Client");
			_clientBehavior = (MonoBehaviour)(object)((BasePlugin)this).AddComponent<PluginReloader.Behaviors.Reload>();
			PluginReloader.Systems.Reload.LoadPlugins();
			Log.Info("Plugin PluginReloader v1.0.0 loaded!");
		}

		public override bool Unload()
		{
			if ((Object)(object)_clientBehavior != (Object)null)
			{
				Object.Destroy((Object)(object)_clientBehavior);
				_clientBehavior = null;
			}
			Log.Info("Plugin PluginReloader v1.0.0 unloaded!");
			return true;
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "PluginReloader";

		public const string PLUGIN_NAME = "PluginReloader";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace PluginReloader.Systems
{
	public static class Reload
	{
		private static string _reloadPluginsFolder = Path.Combine(Paths.BepInExRootPath, "PluginReloader");

		public static List<BasePlugin> LoadedPlugins { get; } = new List<BasePlugin>();


		internal static void UnloadPlugins()
		{
			for (int num = LoadedPlugins.Count - 1; num >= 0; num--)
			{
				BasePlugin val = LoadedPlugins[num];
				if (!val.Unload())
				{
					Log.Warning("Plugin " + ((object)val).GetType().FullName + " does not support unloading, skipping...");
				}
				else
				{
					LoadedPlugins.RemoveAt(num);
				}
			}
		}

		internal static void ReloadPlugins()
		{
			UnloadPlugins();
			LoadPlugins();
		}

		internal static void ReloadIfReloadFileExists()
		{
			if (Directory.Exists(_reloadPluginsFolder))
			{
				string text = Path.Combine(_reloadPluginsFolder, "reload");
				if (File.Exists(text))
				{
					File.Move(text, Path.Combine(_reloadPluginsFolder, "reloaded"));
					ReloadPlugins();
				}
			}
		}

		internal static List<string> LoadPlugins()
		{
			Log.Info("Loading plugins from " + _reloadPluginsFolder);
			if (!Directory.Exists(_reloadPluginsFolder))
			{
				return new List<string>();
			}
			return Directory.GetFiles(_reloadPluginsFolder, "*.dll").SelectMany(LoadPlugin).ToList();
		}

		private static List<string> LoadPlugin(string path)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0042: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Expected O, but got Unknown
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Expected O, but got Unknown
			DefaultAssemblyResolver val = new DefaultAssemblyResolver();
			((BaseAssemblyResolver)val).AddSearchDirectory(_reloadPluginsFolder);
			((BaseAssemblyResolver)val).AddSearchDirectory(Paths.ManagedPath);
			((BaseAssemblyResolver)val).AddSearchDirectory(Paths.BepInExAssemblyDirectory);
			((BaseAssemblyResolver)val).AddSearchDirectory(Path.Combine(Paths.BepInExRootPath, "interop"));
			AssemblyDefinition val2 = AssemblyDefinition.ReadAssembly(path, new ReaderParameters
			{
				AssemblyResolver = (IAssemblyResolver)(object)val
			});
			try
			{
				((AssemblyNameReference)val2.Name).Name = $"{((AssemblyNameReference)val2.Name).Name}-{DateTime.Now.Ticks}";
				using MemoryStream memoryStream = new MemoryStream();
				val2.Write((Stream)memoryStream);
				List<string> list = new List<string>();
				foreach (Type pluginType in from x in Assembly.Load(memoryStream.ToArray()).GetTypes()
					where typeof(BasePlugin).IsAssignableFrom(x)
					select x)
				{
					if (!LoadedPlugins.Any((BasePlugin x) => ((object)x).GetType() == pluginType))
					{
						try
						{
							BasePlugin val3 = (BasePlugin)Activator.CreateInstance(pluginType);
							BepInPlugin metadata = MetadataHelper.GetMetadata((object)val3);
							LoadedPlugins.Add(val3);
							val3.Load();
							list.Add(metadata.Name);
							Log.Info("Loaded plugin " + pluginType.FullName);
						}
						catch (Exception data)
						{
							Log.Error("Plugin " + pluginType.FullName + " threw an exception during initialization:");
							Log.Error(data);
						}
					}
				}
				return list;
			}
			finally
			{
				((IDisposable)val2)?.Dispose();
			}
		}
	}
}
namespace PluginReloader.Settings
{
	public class Config
	{
		public static void Load(ConfigFile configFile, ManualLogSource logger, string worldType)
		{
			ENV.Settings.Setup();
			Utils.Settings.Config.Setup("PluginReloader", configFile, 4);
			Utils.Settings.Config.Load();
			Utils.Logger.Config.Setup(logger, worldType);
		}
	}
	public static class ENV
	{
		public static class Settings
		{
			public static void Setup()
			{
				Utils.Settings.Config.AddConfigActions(load);
			}

			private static void load()
			{
				EnableReloadByFile = Utils.Settings.Config.Bind(settings, "EnableReloadByFile", defaultValue: true, "Enable reload by file, to reload the plugins when the file 'reload' exists in the PluginReloader directory.");
				ReloadKeyCode = Utils.Settings.Config.Bind<KeyCode>(settings, "ReloadKeyCode", (KeyCode)287, "The keycode to reload the plugin in game.");
				ReloadSettingsKey = Utils.Settings.Config.Bind<KeyCode>(settings, "ReloadSettingsKey", (KeyCode)286, "The keycode to reload the plugin settings in game.");
				Utils.Settings.Config.Save();
			}
		}

		private static readonly string settings = "0.⚙\ufe0f Settings";

		public static ConfigElement<bool> EnableReloadByFile;

		public static ConfigElement<KeyCode> ReloadKeyCode;

		public static ConfigElement<KeyCode> ReloadSettingsKey;
	}
}
namespace PluginReloader.Behaviors
{
	public class Reload : MonoBehaviour
	{
		public void Update()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			if (Input.GetKeyDown(PluginReloader.Settings.ENV.ReloadSettingsKey.Value))
			{
				Utils.Settings.Config.Reload();
			}
			if (Input.GetKeyDown(PluginReloader.Settings.ENV.ReloadKeyCode.Value))
			{
				PluginReloader.Systems.Reload.ReloadPlugins();
			}
			if (PluginReloader.Settings.ENV.EnableReloadByFile.Value && !Cache.IsBlocked("ReloadIfReloadFileExists", 5000L))
			{
				PluginReloader.Systems.Reload.ReloadIfReloadFileExists();
			}
		}
	}
}