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();
}
}
}
}