Decompiled source of HookUI v1.1.0

HookUILib.dll

Decompiled 9 months ago
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using Microsoft.CodeAnalysis;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("Captain-Of-Coit")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Provides HookUI functionality for mod authors")]
[assembly: AssemblyFileVersion("0.3.0.0")]
[assembly: AssemblyInformationalVersion("0.3.0")]
[assembly: AssemblyProduct("HookUILib")]
[assembly: AssemblyTitle("HookUILib")]
[assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/Captain-of-Coit/hookui")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.3.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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace HookUILib.Core
{
	public enum ExtensionType
	{
		Panel
	}
	public class UIExtension
	{
		public readonly string extensionID;

		public readonly string extensionContent;

		public readonly ExtensionType extensionType;

		public virtual string LoadEmbeddedResource(string resourceName)
		{
			Assembly assembly = GetType().Assembly;
			using Stream stream = assembly.GetManifestResourceStream(resourceName);
			using StreamReader streamReader = new StreamReader(stream);
			return streamReader.ReadToEnd();
		}
	}
}

HookUIMod.dll

Decompiled 9 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using BepInEx;
using Colossal;
using Colossal.Annotations;
using Colossal.Logging;
using Colossal.UI.Binding;
using Game;
using Game.Common;
using Game.SceneFlow;
using Game.UI;
using HarmonyLib;
using HookUI.UI;
using HookUILib.Core;
using Microsoft.CodeAnalysis;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("HookUIMod")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyDescription("Mod UI loader/framework for Cities: Skylines 2")]
[assembly: AssemblyFileVersion("1.1.0.0")]
[assembly: AssemblyInformationalVersion("1.1.0")]
[assembly: AssemblyProduct("HookUIMod")]
[assembly: AssemblyTitle("HookUIMod")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.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 HookUI.UI
{
	internal class HookUIUISystem : UISystemBase
	{
		private HashSet<string> availableExtensions = new HashSet<string>();

		private HashSet<string> visiblePanels = new HashSet<string>();

		private bool isMenuOpen = false;

		private string kGroup = "hookui";

		public static ILog Log { get; private set; }

		protected override void OnCreate()
		{
			((UISystemBase)this).OnCreate();
			Log = LogManager.GetLogger("HookUIUISystem");
			((UISystemBase)this).AddUpdateBinding((IUpdateBinding)(object)new GetterValueBinding<HashSet<string>>(kGroup, "visible_panels", (Func<HashSet<string>>)(() => visiblePanels), (IWriter<HashSet<string>>)new HashSetWriter<string>(), (EqualityComparer<HashSet<string>>)null));
			((UISystemBase)this).AddUpdateBinding((IUpdateBinding)(object)new GetterValueBinding<HashSet<string>>(kGroup, "available_extensions", (Func<HashSet<string>>)(() => availableExtensions), (IWriter<HashSet<string>>)new HashSetWriter<string>(), (EqualityComparer<HashSet<string>>)null));
			((UISystemBase)this).AddUpdateBinding((IUpdateBinding)(object)new GetterValueBinding<bool>(kGroup, "is_menu_open", (Func<bool>)(() => isMenuOpen), (IWriter<bool>)null, (EqualityComparer<bool>)null));
			((UISystemBase)this).AddBinding((IBinding)(object)new TriggerBinding<bool>(kGroup, "toggle_menu", (Action<bool>)delegate(bool is_open)
			{
				isMenuOpen = is_open;
			}, (IReader<bool>)null));
			((UISystemBase)this).AddBinding((IBinding)(object)new TriggerBinding<HashSet<string>>(kGroup, "set_visible_panels", (Action<HashSet<string>>)delegate(HashSet<string> panels)
			{
				visiblePanels = panels;
			}, (IReader<HashSet<string>>)new HashSetReader<string>()));
			Log.Info((object)"Finding hookui extensions");
			Type baseType = typeof(UIExtension);
			IEnumerable<Type> enumerable = from type in AppDomain.CurrentDomain.GetAssemblies().SelectMany((Assembly assembly) => assembly.GetTypes())
				where type.IsSubclassOf(baseType)
				select type;
			foreach (Type item in enumerable)
			{
				object obj = Activator.CreateInstance(item);
				string fullName = item.FullName;
				object value = item.GetField("extensionID", BindingFlags.Instance | BindingFlags.Public).GetValue(obj);
				string text = (string)item.GetField("extensionContent", BindingFlags.Instance | BindingFlags.Public).GetValue(obj);
				string text2 = $"panel.{value}.js";
				Log.Info((object)$"{fullName} extensionPath: {value}, extensionContent length: {text.Length}");
				string text3 = Path.Combine(Application.dataPath, "StreamingAssets", "~UI~", "HookUI", "Extensions", text2);
				Log.Info((object)(fullName + " installPath: " + text3));
				File.WriteAllText(text3, text);
				AddExtension(text2);
			}
		}

		public void AddExtension(string id)
		{
			availableExtensions.Add(id);
		}
	}
	internal class HashSetWriter<T> : IWriter<HashSet<T>>
	{
		[NotNull]
		private readonly IWriter<T> m_ItemWriter;

		public HashSetWriter(IWriter<T> itemWriter = null)
		{
			m_ItemWriter = itemWriter ?? ValueWriters.Create<T>();
		}

		public void Write(IJsonWriter writer, HashSet<T> value)
		{
			if (value != null)
			{
				JsonWriterExtensions.ArrayBegin(writer, value.Count);
				foreach (T item in value)
				{
					m_ItemWriter.Write(writer, item);
				}
				writer.ArrayEnd();
				return;
			}
			writer.WriteNull();
			throw new ArgumentNullException("value", "Null passed to non-nullable hashset writer");
		}
	}
	internal class HashSetReader<T> : IReader<HashSet<T>>
	{
		private readonly IReader<T> m_ItemReader;

		public HashSetReader(IReader<T> itemReader = null)
		{
			m_ItemReader = itemReader ?? ValueReaders.Create<T>();
		}

		public void Read(IJsonReader reader, out HashSet<T> value)
		{
			value = new HashSet<T>();
			uint num = reader.ReadArrayBegin();
			T item = default(T);
			for (uint num2 = 0u; num2 < num; num2++)
			{
				reader.ReadArrayElement(num2);
				m_ItemReader.Read(reader, ref item);
				value.Add(item);
			}
			reader.ReadArrayEnd();
		}
	}
	public record PanelPosition
	{
		public int top;

		public int left;
	}
	public record PanelSize
	{
		public int height;

		public int width;
	}
}
namespace HookUIMod
{
	[BepInPlugin("HookUIMod", "HookUIMod", "1.1.0")]
	public class Plugin : BaseUnityPlugin
	{
		private readonly string hookUIPath = Path.Combine(Application.dataPath, "StreamingAssets", "~UI~", "HookUI");

		private readonly string extensionsPath = Path.Combine(Application.dataPath, "StreamingAssets", "~UI~", "HookUI", "Extensions");

		private FileSystemWatcher watcher;

		private void Awake()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Plugin HookUIMod is loaded!");
			string filePath = "Cities2_Data\\StreamingAssets\\~UI~\\HookUI\\index.html.template";
			string destinationPath = "Cities2_Data\\StreamingAssets\\~UI~\\HookUI\\index.html";
			Version current = Version.current;
			string shortVersion = ((Version)(ref current)).shortVersion;
			string text = "1.1.0f1";
			Harmony val = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "HookUIMod_Cities2Harmony");
			MethodBase[] array = val.GetPatchedMethods().ToArray();
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin HookUIMod made patches! Patched methods: " + array.Length));
			MethodBase[] array2 = array;
			foreach (MethodBase methodBase in array2)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched method: " + methodBase.Module.Name + ":" + methodBase.Name));
			}
			InstallHookUI();
			if (CheckVersion(shortVersion, text))
			{
				WriteFileToPath(filePath, destinationPath);
			}
			else
			{
				PrintVersionWarning(filePath, destinationPath, shortVersion, text);
			}
		}

		public static string InjectHookUILoader(string orig_text)
		{
			string oldValue = "tutorialListFocusKey:pIe.tutorialList})";
			string newValue = "tutorialListFocusKey:pIe.tutorialList}),(0,z.jsx)(window._$hookui_loader,{react:Y})";
			return orig_text.Replace(oldValue, newValue);
		}

		public static string HookPanelsMenu(string orig_text)
		{
			string oldValue = "ube.lock})})})})]";
			string newValue = "ube.lock})})})}),(0,z.jsx)(window._$hookui_menu,{react:Y})]";
			return orig_text.Replace(oldValue, newValue);
		}

		public static string OverwriteAbsoluteButton(string orig_text)
		{
			string oldValue = ".button_H9N{pointer-events:auto;position:absolute;top:0;left:0}";
			string newValue = ".button_H9N{pointer-events:auto}";
			return orig_text.Replace(oldValue, newValue);
		}

		public static void WriteResourcesToDisk()
		{
			string text = "Cities2_Data\\StreamingAssets\\~UI~\\HookUI\\lib";
			string[] array = new string[3] { "hookui.api.bundle.js", "hookui.loader.bundle.js", "hookui.menu.bundle.js" };
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			Directory.CreateDirectory(text);
			string[] array2 = array;
			foreach (string text2 in array2)
			{
				string name = "HookUIMod." + text2;
				using Stream stream = executingAssembly.GetManifestResourceStream(name);
				if (stream == null)
				{
					throw new InvalidOperationException("Could not find embedded resource: " + text2);
				}
				string path = Path.Combine(text, text2);
				using FileStream destination = new FileStream(path, FileMode.Create);
				stream.CopyTo(destination);
			}
		}

		public static void InstallHookUI()
		{
			string text = "Cities2_Data\\StreamingAssets\\~UI~";
			CopyDirectory(text + "\\GameUI", text + "\\HookUI");
			File.WriteAllText(text + "\\HookUI\\version", "1.1.0");
			string path = text + "\\HookUI\\index.js";
			string orig_text = File.ReadAllText(path);
			orig_text = InjectHookUILoader(orig_text);
			orig_text = HookPanelsMenu(orig_text);
			File.WriteAllText(path, orig_text);
			string path2 = text + "\\HookUI\\index.css";
			string orig_text2 = File.ReadAllText(path2);
			orig_text2 = OverwriteAbsoluteButton(orig_text2);
			File.WriteAllText(path2, orig_text2);
			WriteResourcesToDisk();
			Directory.CreateDirectory(text + "\\HookUI\\Extensions");
			string name = "HookUIMod.index.html.template";
			string path3 = Path.Combine("Cities2_Data\\StreamingAssets\\~UI~\\HookUI", "index.html.template");
			using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
			using FileStream destination = new FileStream(path3, FileMode.Create, FileAccess.Write);
			stream.CopyTo(destination);
		}

		public static bool CheckVersion(string currentVersion, string compatibleVersion)
		{
			Debug.Log((object)"HookUI VersionCheck");
			Debug.Log((object)currentVersion);
			if (currentVersion == compatibleVersion)
			{
				Debug.Log((object)"Passed version check");
				string text = "";
				string text2 = "";
				string text3 = "CAA2852C609B391E942A474EA4A26A4AD14E66DE6A1C5FEE4E1B8C111D3E9492";
				string text4 = "CAA2852C609B391E942A474EA4A26A4AD14E66DE6A1C5FEE4E1B8C111D3E9492";
				bool flag = true;
				bool flag2 = true;
				Debug.Log((object)"Passed hash checks");
				return true;
			}
			Debug.Log((object)"This HookUI version might not be compatible with your game version");
			return false;
		}

		public static void CopyDirectory(string sourceDir, string destinationDir)
		{
			DirectoryInfo directoryInfo = new DirectoryInfo(destinationDir);
			if (!directoryInfo.Exists)
			{
				directoryInfo.Create();
			}
			DirectoryInfo directoryInfo2 = new DirectoryInfo(sourceDir);
			FileInfo[] files = directoryInfo2.GetFiles();
			foreach (FileInfo fileInfo in files)
			{
				string destFileName = Path.Combine(destinationDir, fileInfo.Name);
				fileInfo.CopyTo(destFileName, overwrite: true);
			}
			DirectoryInfo[] directories = directoryInfo2.GetDirectories();
			foreach (DirectoryInfo directoryInfo3 in directories)
			{
				string destinationDir2 = Path.Combine(destinationDir, directoryInfo3.Name);
				CopyDirectory(directoryInfo3.FullName, destinationDir2);
			}
		}

		public static void WriteFileToPath(string filePath, string destinationPath)
		{
			string contents = File.ReadAllText(filePath);
			File.WriteAllText(destinationPath, contents);
		}

		public static void PrintVersionWarning(string filePath, string destinationPath, string actualVersion, string expectedVersion)
		{
			string text = File.ReadAllText(filePath);
			text = text.Replace("<!--EXTENSIONS_LIST-->", "<div style=\"position: absolute; top: 10px; left: 10px; z-index: 10000; color: white;\" onclick=\"if (this.parentNode) this.parentNode.removeChild(this);\"><div>This HookUI version is not compatible with your game version.</div><div>Loading of extensions disabled.</div><div>" + actualVersion + " = Your CS2 version</div><div>" + expectedVersion + " = Last tested CS2 version with HookUI</div><div>Click to hide</div></div>");
			File.WriteAllText(destinationPath, text);
		}

		public List<string> GenerateScriptPathsList(string directoryPath)
		{
			string[] files = Directory.GetFiles(directoryPath, "*.js");
			return files.Select((string fullPath) => "Extensions/" + Path.GetFileName(fullPath)).ToList();
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "HookUIMod";

		public const string PLUGIN_NAME = "HookUIMod";

		public const string PLUGIN_VERSION = "1.1.0";
	}
}
namespace HookUIMod.Patches
{
	[HarmonyPatch(typeof(SystemOrder))]
	public static class SystemOrderPatch
	{
		[HarmonyPatch("Initialize")]
		[HarmonyPostfix]
		public static void Postfix(UpdateSystem updateSystem)
		{
			updateSystem.UpdateAt<HookUIUISystem>((SystemUpdatePhase)22);
		}
	}
	[HarmonyPatch(typeof(GameManager))]
	[HarmonyPatch("InitializeUI")]
	public static class GameManager_InitializeUIPatch
	{
		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			string text = "/~UI~/GameUI";
			string text2 = "/~UI~/HookUI";
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i].opcode == OpCodes.Ldstr && (string)list[i].operand == text)
				{
					Debug.Log((object)$"Replacing string: {list[i].operand} with {text2}");
					list[i].operand = text2;
				}
			}
			return list.AsEnumerable();
		}
	}
	[HarmonyPatch(typeof(UISystemBootstrapper))]
	[HarmonyPatch("Awake")]
	public static class UISystemBootstrapper_AwakePatch
	{
		public static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions)
		{
			List<CodeInstruction> list = new List<CodeInstruction>(instructions);
			string text = "/~UI~/GameUI";
			string text2 = "/~UI~/HookUI";
			for (int i = 0; i < list.Count; i++)
			{
				if (list[i].opcode == OpCodes.Ldstr && (string)list[i].operand == text)
				{
					Debug.Log((object)$"Replacing string: {list[i].operand} with {text2}");
					list[i].operand = text2;
				}
			}
			return list.AsEnumerable();
		}
	}
}