Decompiled source of RageClanPappyHUDLocal v1.2.0

RageClanPappyHUDLocal.dll

Decompiled a week ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("RageClanPappyHUDLocal")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("RageClanPappyHUDLocal")]
[assembly: AssemblyTitle("RageClanPappyHUDLocal")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace RageClanPappyHUDLocal;

[BepInPlugin("rageclan.valheim.pappyhudlocal", "Pappy HUD Local Config", "1.2.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class RageClanPappyHUDLocalPlugin : BaseUnityPlugin
{
	private sealed class PendingSettingChange
	{
		public ConfigEntryBase Entry;

		public float LastChangedAt;
	}

	public const string PluginGuid = "rageclan.valheim.pappyhudlocal";

	public const string PluginName = "Pappy HUD Local Config";

	public const string PluginVersion = "1.2.0";

	public const string PappyGuid = "pappy.valheim.hudmaster";

	private const long MaxDedicatedLogBytes = 1048576L;

	private const float ChangeLogDelaySeconds = 0.75f;

	private ConfigFile localConfig;

	private bool localFileExisted;

	private MethodInfo genericBindMethod;

	private string dedicatedLogPath;

	private bool dedicatedLogDisabled;

	private readonly object dedicatedLogLock = new object();

	private readonly object pendingChangesLock = new object();

	private readonly Dictionary<string, PendingSettingChange> pendingChanges = new Dictionary<string, PendingSettingChange>();

	private void Awake()
	{
		//IL_0088: Unknown result type (might be due to invalid IL or missing references)
		//IL_0092: Expected O, but got Unknown
		try
		{
			InitializeDedicatedLog();
			LogInfoBoth("Starting Pappy HUD Local Config v1.2.0.");
			if (!Chainloader.PluginInfos.TryGetValue("pappy.valheim.hudmaster", out var value) || value == null || (Object)(object)value.Instance == (Object)null)
			{
				LogErrorBoth("Pappy HUD Master is not loaded. The local HUD redirect cannot start.");
				return;
			}
			string text = Path.Combine(Application.persistentDataPath, "PappyHUDLocal");
			Directory.CreateDirectory(text);
			string text2 = Path.Combine(text, "hud.cfg");
			bool flag = MigrateLegacyLocalConfig(text2);
			localFileExisted = File.Exists(text2);
			localConfig = new ConfigFile(text2, true, ((BaseUnityPlugin)this).Info.Metadata);
			localConfig.SettingChanged += OnLocalSettingChanged;
			genericBindMethod = FindGenericBindMethod();
			if (genericBindMethod == null)
			{
				LogErrorBoth("Unable to locate BepInEx ConfigFile.Bind<T>. Local HUD redirect disabled.");
				return;
			}
			LogInfoBoth("Pappy detected: " + value.Metadata.Version?.ToString() + ".");
			int num = RebindConfigFields(value.Instance);
			localConfig.Save();
			LogInfoBoth("Pappy HUD settings redirected to a per-player local file.");
			LogInfoBoth("Local HUD config: " + text2);
			LogInfoBoth("Dedicated log: " + dedicatedLogPath);
			LogInfoBoth("Redirected config entries: " + num + ".");
			if (flag)
			{
				LogInfoBoth("Legacy RageClan HUD config migrated to the generic PappyHUDLocal directory.");
			}
			else
			{
				LogInfoBoth(localFileExisted ? "Existing per-player HUD config loaded." : "First run: current Pappy HUD values were copied into the local HUD file.");
			}
		}
		catch (Exception ex)
		{
			LogErrorBoth("Failed to redirect Pappy HUD config: " + ex);
		}
	}

	private bool MigrateLegacyLocalConfig(string targetPath)
	{
		if (File.Exists(targetPath))
		{
			return false;
		}
		string text = Path.Combine(Application.persistentDataPath, "RageClan", "PappyHUDMaster", "hud.cfg");
		if (!File.Exists(text))
		{
			return false;
		}
		try
		{
			File.Copy(text, targetPath, overwrite: false);
			return true;
		}
		catch (Exception ex)
		{
			LogWarningBoth("Legacy HUD config migration failed; the original file was left untouched: " + ex.Message);
			return false;
		}
	}

	private int RebindConfigFields(object pappyInstance)
	{
		int num = 0;
		Type type = pappyInstance.GetType();
		BindingFlags bindingAttr = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
		FieldInfo[] fields = type.GetFields(bindingAttr);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (typeof(ConfigEntryBase).IsAssignableFrom(fieldInfo.FieldType))
			{
				object? value = fieldInfo.GetValue(pappyInstance);
				ConfigEntryBase val = (ConfigEntryBase)((value is ConfigEntryBase) ? value : null);
				if (val != null)
				{
					ConfigEntryBase value2 = CloneEntryToLocalFile(val);
					fieldInfo.SetValue(pappyInstance, value2);
					num++;
				}
			}
		}
		FieldInfo field = type.GetField("elements", bindingAttr);
		IEnumerable enumerable = ((field == null) ? null : (field.GetValue(pappyInstance) as IEnumerable));
		if (enumerable == null)
		{
			LogWarningBoth("Pappy HUD Master 'elements' collection was not found. Only top-level settings were redirected.");
			return num;
		}
		MethodInfo method = type.GetMethod("SyncEditStrings", BindingFlags.Static | BindingFlags.NonPublic);
		foreach (object item in enumerable)
		{
			if (item == null)
			{
				continue;
			}
			fields = item.GetType().GetFields(bindingAttr);
			foreach (FieldInfo fieldInfo2 in fields)
			{
				if (typeof(ConfigEntryBase).IsAssignableFrom(fieldInfo2.FieldType))
				{
					object? value3 = fieldInfo2.GetValue(item);
					ConfigEntryBase val2 = (ConfigEntryBase)((value3 is ConfigEntryBase) ? value3 : null);
					if (val2 != null)
					{
						ConfigEntryBase value4 = CloneEntryToLocalFile(val2);
						fieldInfo2.SetValue(item, value4);
						num++;
					}
				}
			}
			if (method != null)
			{
				try
				{
					method.Invoke(null, new object[1] { item });
				}
				catch (Exception ex)
				{
					LogWarningBoth("Could not refresh one Pappy HUD editor value: " + ex.Message);
				}
			}
		}
		return num;
	}

	private ConfigEntryBase CloneEntryToLocalFile(ConfigEntryBase oldEntry)
	{
		MethodInfo methodInfo = genericBindMethod.MakeGenericMethod(oldEntry.SettingType);
		string text = ((oldEntry.Description == null) ? string.Empty : oldEntry.Description.Description);
		object? obj = methodInfo.Invoke(localConfig, new object[4]
		{
			oldEntry.Definition.Section,
			oldEntry.Definition.Key,
			oldEntry.BoxedValue,
			text
		});
		ConfigEntryBase val = (ConfigEntryBase)((obj is ConfigEntryBase) ? obj : null);
		if (val == null)
		{
			throw new InvalidOperationException("ConfigFile.Bind<T> did not return a ConfigEntryBase.");
		}
		LogDebugBoth("Redirected [" + oldEntry.Definition.Section + "] " + oldEntry.Definition.Key + " = " + FormatValue(val.BoxedValue));
		return val;
	}

	private static MethodInfo FindGenericBindMethod()
	{
		MethodInfo[] methods = typeof(ConfigFile).GetMethods(BindingFlags.Instance | BindingFlags.Public);
		foreach (MethodInfo methodInfo in methods)
		{
			if (!(methodInfo.Name != "Bind") && methodInfo.IsGenericMethodDefinition)
			{
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (parameters.Length == 4 && parameters[0].ParameterType == typeof(string) && parameters[1].ParameterType == typeof(string) && parameters[3].ParameterType == typeof(string))
				{
					return methodInfo;
				}
			}
		}
		return null;
	}

	private void OnLocalSettingChanged(object sender, SettingChangedEventArgs args)
	{
		ConfigEntryBase changedSetting = args.ChangedSetting;
		if (changedSetting == null)
		{
			return;
		}
		string key = changedSetting.Definition.Section + "\n" + changedSetting.Definition.Key;
		lock (pendingChangesLock)
		{
			if (!pendingChanges.TryGetValue(key, out var value))
			{
				value = new PendingSettingChange();
				pendingChanges[key] = value;
			}
			value.Entry = changedSetting;
			value.LastChangedAt = Time.realtimeSinceStartup;
		}
	}

	private void Update()
	{
		FlushPendingSettingLogs(force: false);
	}

	private void OnDestroy()
	{
		FlushPendingSettingLogs(force: true);
		if (localConfig != null)
		{
			try
			{
				localConfig.Save();
			}
			catch (Exception ex)
			{
				LogErrorBoth("Final local HUD save failed: " + ex.Message);
			}
		}
		LogInfoBoth("Session ended.");
	}

	private void FlushPendingSettingLogs(bool force)
	{
		List<string> list = new List<string>();
		List<ConfigEntryBase> list2 = new List<ConfigEntryBase>();
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		lock (pendingChangesLock)
		{
			foreach (KeyValuePair<string, PendingSettingChange> pendingChange in pendingChanges)
			{
				if (force || !(realtimeSinceStartup - pendingChange.Value.LastChangedAt < 0.75f))
				{
					list.Add(pendingChange.Key);
					list2.Add(pendingChange.Value.Entry);
				}
			}
			foreach (string item in list)
			{
				pendingChanges.Remove(item);
			}
		}
		foreach (ConfigEntryBase item2 in list2)
		{
			if (item2 != null)
			{
				LogDebugBoth("HUD setting saved: [" + item2.Definition.Section + "] " + item2.Definition.Key + " = " + FormatValue(item2.BoxedValue));
			}
		}
	}

	private static string FormatValue(object value)
	{
		if (value == null)
		{
			return "<null>";
		}
		if (value is IFormattable formattable)
		{
			return formattable.ToString(null, CultureInfo.InvariantCulture);
		}
		return value.ToString();
	}

	private void InitializeDedicatedLog()
	{
		try
		{
			string text = Path.Combine(Application.persistentDataPath, "PappyHUDLocal");
			Directory.CreateDirectory(text);
			dedicatedLogPath = Path.Combine(text, "PappyHUDLocal.log");
			string text2 = Path.Combine(text, "PappyHUDLocal.previous.log");
			if (File.Exists(dedicatedLogPath) && new FileInfo(dedicatedLogPath).Length >= 1048576)
			{
				if (File.Exists(text2))
				{
					File.Delete(text2);
				}
				File.Move(dedicatedLogPath, text2);
			}
			WriteDedicatedLogRaw("============================================================");
			WriteDedicatedLogRaw("New session - " + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
		}
		catch (Exception ex)
		{
			dedicatedLogDisabled = true;
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Dedicated Pappy HUD local log could not be initialized: " + ex.Message));
		}
	}

	private void LogInfoBoth(string message)
	{
		((BaseUnityPlugin)this).Logger.LogInfo((object)message);
		WriteDedicatedLog("INFO", message);
	}

	private void LogWarningBoth(string message)
	{
		((BaseUnityPlugin)this).Logger.LogWarning((object)message);
		WriteDedicatedLog("WARN", message);
	}

	private void LogErrorBoth(string message)
	{
		((BaseUnityPlugin)this).Logger.LogError((object)message);
		WriteDedicatedLog("ERROR", message);
	}

	private void LogDebugBoth(string message)
	{
		((BaseUnityPlugin)this).Logger.LogDebug((object)message);
		WriteDedicatedLog("DEBUG", message);
	}

	private void WriteDedicatedLog(string level, string message)
	{
		WriteDedicatedLogRaw(DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") + " [" + level + "] " + message);
	}

	private void WriteDedicatedLogRaw(string message)
	{
		if (dedicatedLogDisabled || string.IsNullOrEmpty(dedicatedLogPath))
		{
			return;
		}
		try
		{
			lock (dedicatedLogLock)
			{
				File.AppendAllText(dedicatedLogPath, message + Environment.NewLine);
			}
		}
		catch (Exception ex)
		{
			dedicatedLogDisabled = true;
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Dedicated Pappy HUD local log disabled after write failure: " + ex.Message));
		}
	}
}