Decompiled source of TweakUI v0.0.2

TweakUI/TweakUI.dll

Decompiled 11 months ago
using System;
using System.Collections;
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;
using BepInEx;
using Colossal.Reflection;
using Colossal.Serialization.Entities;
using Colossal.UI;
using Colossal.UI.Binding;
using Game;
using Game.Common;
using Game.SceneFlow;
using Game.UI;
using HarmonyLib;
using HookUILib.Core;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using TweakUI.Configuration;
using TweakUI.Core;
using TweakUI.IO;
using TweakUI.Systems;
using TweakUI.UI;
using UnityEngine;
using cohtml.Net;

[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("TweakUI")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+e51f45ebd24c7a5ec55e0d33be192a7726352658")]
[assembly: AssemblyProduct("TweakUI")]
[assembly: AssemblyTitle("TweakUI")]
[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.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace TweakUI
{
	[BepInPlugin("TweakUI", "TweakUI", "1.0.0")]
	public class Plugin : BaseUnityPlugin
	{
		private void Awake()
		{
			Harmony val = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), "TweakUI_Cities2Harmony");
			MethodBase[] array = val.GetPatchedMethods().ToArray();
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================================================");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"TweakUI by Cities2Modding community.");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================================================");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Reddit link: https://www.reddit.com/r/cities2modding/");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Discord link: https://discord.gg/KGRNBbm5Fh");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Our mods are officially distributed via Thunderstore.io and https://github.com/Cities2Modding");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Example mod repository and modding info: https://github.com/optimus-code/Cities2Modding");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"Thanks to Captain_Of_Coit, 89pleasure, Rebecca, optimus-code and the Cites2Modding community!");
			((BaseUnityPlugin)this).Logger.LogInfo((object)"=================================================================");
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin TweakUI is loaded! Patched methods: " + array.Length));
			MethodBase[] array2 = array;
			foreach (MethodBase methodBase in array2)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Patched method: " + methodBase.Module.Name + ":" + methodBase.Name));
			}
		}
	}
	public class TweakUIHookUI : UIExtension
	{
		public readonly ExtensionType extensionType = (ExtensionType)6;

		public readonly string extensionID = "cities2modding.tweakui";

		public readonly string extensionContent;

		public TweakUIHookUI()
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			extensionContent = ((UIExtension)this).LoadEmbeddedResource("TweakUI.Resources.TweakUI.js");
		}
	}
	public static class MyPluginInfo
	{
		public const string PLUGIN_GUID = "TweakUI";

		public const string PLUGIN_NAME = "TweakUI";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace TweakUI.UI
{
	public class ModelWriter : IJsonWritable
	{
		public static Dictionary<Type, PropertyInfo[]> _propertiesCache = new Dictionary<Type, PropertyInfo[]>();

		public void Write(IJsonWriter writer)
		{
			Type type = GetType();
			if (!_propertiesCache.TryGetValue(type, out var value))
			{
				value = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
				_propertiesCache[type] = value;
			}
			ParseType(writer, this, value);
		}

		private void ParseType(IJsonWriter writer, object instance, PropertyInfo[] properties)
		{
			writer.TypeBegin(instance.GetType().FullName);
			foreach (PropertyInfo propertyInfo in properties)
			{
				object value = propertyInfo.GetValue(instance);
				if (value == null)
				{
					writer.WriteNull();
				}
				else
				{
					WriteProperty(writer, propertyInfo, value);
				}
			}
			writer.TypeEnd();
		}

		private void WriteProperty(IJsonWriter writer, PropertyInfo property, object value)
		{
			writer.PropertyName(property.Name);
			WritePropertyValue(writer, value);
		}

		private void WritePropertyValue(IJsonWriter writer, object value)
		{
			Type type = value.GetType();
			if (!(value is string text))
			{
				if (!(value is float num))
				{
					if (!(value is double num2))
					{
						if (!(value is short num3))
						{
							if (!(value is uint num4))
							{
								if (!(value is int num5))
								{
									if (!(value is long num6))
									{
										if (!(value is bool flag))
										{
											if (!(value is decimal num7))
											{
												if (value is Enum @enum)
												{
													writer.Write(@enum.ToString());
												}
												else
												{
													WriteComplexType(writer, value, type);
												}
											}
											else
											{
												writer.Write((int)(num7 * 100m));
											}
										}
										else
										{
											writer.Write(flag);
										}
									}
									else
									{
										writer.Write((float)num6);
									}
								}
								else
								{
									writer.Write(num5);
								}
							}
							else
							{
								writer.Write(num4);
							}
						}
						else
						{
							writer.Write((int)num3);
						}
					}
					else
					{
						writer.Write(num2);
					}
				}
				else
				{
					writer.Write(num);
				}
			}
			else
			{
				writer.Write(text);
			}
		}

		private void WriteComplexType(IJsonWriter writer, object value, Type valueType)
		{
			if (ReflectionUtils.IsSubclassOfRawGeneric(valueType, typeof(List<>)))
			{
				HandleList(writer, value, valueType);
			}
			else if (valueType == typeof(Dictionary<string, string>))
			{
				HandleDictionary(writer, value);
			}
		}

		private void HandleList(IJsonWriter writer, object value, Type valueType)
		{
			if (!(value is IList list))
			{
				return;
			}
			int count = list.Count;
			JsonWriterExtensions.ArrayBegin(writer, count);
			foreach (object item in list)
			{
				object obj = item;
				object obj2 = obj;
				if (!(obj2 is string text))
				{
					if (!(obj2 is float num))
					{
						if (!(obj2 is double num2))
						{
							if (!(obj2 is short num3))
							{
								if (!(obj2 is uint num4))
								{
									if (!(obj2 is int num5))
									{
										if (!(obj2 is long num6))
										{
											if (!(obj2 is bool flag))
											{
												if (!(obj2 is decimal num7))
												{
													if (obj2 is Enum @enum)
													{
														writer.Write(@enum.ToString());
														continue;
													}
													Type type = valueType.GenericTypeArguments[0];
													PropertyInfo[] properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
													ParseType(writer, item, properties);
												}
												else
												{
													writer.Write((int)(num7 * 100m));
												}
											}
											else
											{
												writer.Write(flag);
											}
										}
										else
										{
											writer.Write((float)num6);
										}
									}
									else
									{
										writer.Write(num5);
									}
								}
								else
								{
									writer.Write(num4);
								}
							}
							else
							{
								writer.Write((int)num3);
							}
						}
						else
						{
							writer.Write(num2);
						}
					}
					else
					{
						writer.Write(num);
					}
				}
				else
				{
					writer.Write(text);
				}
			}
			writer.ArrayEnd();
		}

		private void HandleDictionary(IJsonWriter writer, object value)
		{
			if (!(value is IDictionary<string, string> dictionary))
			{
				return;
			}
			int count = dictionary.Count;
			if (count == 0)
			{
				JsonWriterExtensions.WriteEmptyMap(writer);
				return;
			}
			JsonWriterExtensions.MapBegin(writer, count);
			foreach (KeyValuePair<string, string> item in dictionary)
			{
				writer.Write(item.Key);
				writer.Write(item.Value);
			}
			writer.MapEnd();
		}
	}
}
namespace TweakUI.Systems
{
	public class TweakUISystem : UISystemBase
	{
		private string kGroup = "cities2modding_tweakui";

		private static GetterValueBinding<TweakUIConfig> _binding;

		private static FieldInfo _dirtyField = typeof(GetterValueBinding<TweakUIConfig>).GetField("m_ValueDirty", BindingFlags.Instance | BindingFlags.NonPublic);

		private BrowserBridge _bridge;

		private readonly TweakUIConfig _config = ConfigBase.Load<TweakUIConfig>();

		private bool hasInjected;

		public static void EnsureModUIFolder()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			GameUIResourceHandler val = (GameUIResourceHandler)GameManager.instance.userInterface.view.uiSystem.resourceHandler;
			if (val != null && !((DefaultResourceHandler)val).HostLocationsMap.ContainsKey("tweakui"))
			{
				((DefaultResourceHandler)val).HostLocationsMap.Add("tweakui", new List<string> { ConfigBase.MOD_PATH });
			}
		}

		protected override void OnCreate()
		{
			//IL_00cc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d6: Expected O, but got Unknown
			((UISystemBase)this).OnCreate();
			EnsureModUIFolder();
			_bridge = new BrowserBridge();
			_bridge.LoadEmbeddedCSS();
			_binding = new GetterValueBinding<TweakUIConfig>(kGroup, "config", (Func<TweakUIConfig>)(() => _config), (IWriter<TweakUIConfig>)(object)ValueWriters.Nullable<TweakUIConfig>((IWriter<TweakUIConfig>)(object)new ValueWriter<TweakUIConfig>()), (EqualityComparer<TweakUIConfig>)null);
			ConfigBase.OnUpdated = (Action)Delegate.Combine(ConfigBase.OnUpdated, (Action)delegate
			{
				_dirtyField.SetValue(_binding, true);
			});
			((UISystemBase)this).AddUpdateBinding((IUpdateBinding)(object)_binding);
			((UISystemBase)this).AddBinding((IBinding)(object)new TriggerBinding<string>(kGroup, "updateProperty", (Action<string>)UpdateProperty, (IReader<string>)null));
			((UISystemBase)this).AddBinding((IBinding)new TriggerBinding(kGroup, "inject", (Action)delegate
			{
				_bridge.InjectEmbeddedCSS();
				_bridge.ExecuteJS("document.body.classList.add('" + _config.TransportationOverviewSize + "');");
				_bridge.ExecuteJS("document.body.classList.add('" + _config.AssetMenuRows + "');");
			}));
			Debug.Log((object)"TweakUI OnCreate");
		}

		protected override void OnGameLoadingComplete(Purpose purpose, GameMode mode)
		{
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			((GameSystemBase)this).OnGameLoadingComplete(purpose, mode);
			if (!hasInjected)
			{
				_bridge.InjectEmbeddedCSS();
				_bridge.ExecuteJS("document.body.classList.add('" + _config.TransportationOverviewSize + "');");
				_bridge.ExecuteJS("document.body.classList.add('" + _config.AssetMenuRows + "');");
				Debug.Log((object)"TweakUI Injected");
				hasInjected = true;
			}
		}

		protected override void OnUpdate()
		{
		}

		private void UpdateProperty(string json)
		{
			if (string.IsNullOrEmpty(json))
			{
				return;
			}
			PropertyInfo[] array = ModelWriter._propertiesCache[typeof(TweakUIConfig)];
			if (array == null)
			{
				return;
			}
			Dictionary<string, object> dictionary = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
			if (dictionary == null || !dictionary.TryGetValue("property", out var propertyName))
			{
				return;
			}
			PropertyInfo propertyInfo = array.FirstOrDefault((PropertyInfo p) => p.Name == (string)propertyName);
			if (propertyInfo == null || !dictionary.TryGetValue("value", out var value))
			{
				return;
			}
			if (value.GetType() != propertyInfo.PropertyType)
			{
				if (TryConvertValue(propertyInfo.PropertyType, value, out var result))
				{
					propertyInfo.SetValue(_config, result);
				}
			}
			else
			{
				propertyInfo.SetValue(_config, value);
			}
			_config.Save();
		}

		private bool TryConvertValue(Type propertyType, object val, out object result)
		{
			result = null;
			object result3;
			if (propertyType == typeof(decimal))
			{
				if (val is string s && int.TryParse(s, out var result2))
				{
					result = (decimal)result2 / 100m;
					return true;
				}
				if (val is long num)
				{
					result = (decimal)num / 100m;
					return true;
				}
				if (val is int num2)
				{
					result = (decimal)num2 / 100m;
					return true;
				}
			}
			else if (propertyType.IsEnum && val is string value && Enum.TryParse(propertyType, value, out result3))
			{
				result = result3;
				return true;
			}
			return false;
		}
	}
}
namespace TweakUI.Patches
{
	[HarmonyPatch(typeof(SystemOrder), "Initialize")]
	internal class SystemOrder_InitializePatch
	{
		private static void Postfix(UpdateSystem updateSystem)
		{
			updateSystem.UpdateAt<TweakUISystem>((SystemUpdatePhase)1);
		}
	}
}
namespace TweakUI.IO
{
	public static class EmbeddedResource
	{
		private static readonly Assembly _assembly = typeof(EmbeddedResource).Assembly;

		public static byte[] Load(string resourceName, Assembly targetAssembly = null)
		{
			Assembly assembly = targetAssembly ?? _assembly;
			using Stream stream = assembly.GetManifestResourceStream(resourceName);
			if (stream == null)
			{
				throw new InvalidOperationException("Resource not found: " + resourceName);
			}
			byte[] array = new byte[stream.Length];
			stream.Read(array, 0, array.Length);
			return array;
		}

		public static string LoadText(string resourceName, Assembly targetAssembly = null)
		{
			Assembly assembly = targetAssembly ?? _assembly;
			using Stream stream = assembly.GetManifestResourceStream(resourceName);
			if (stream == null)
			{
				throw new InvalidOperationException("Resource not found: " + resourceName);
			}
			using StreamReader streamReader = new StreamReader(stream);
			return streamReader.ReadToEnd();
		}
	}
}
namespace TweakUI.Core
{
	internal class BrowserBridge
	{
		private View _gameFace;

		internal BrowserBridge()
		{
			_gameFace = GameManager.instance.userInterface.view.View;
			InjectPolyfills();
		}

		private void InjectPolyfills()
		{
			InjectBase64Function();
		}

		public void ExecuteJS(string javaScript)
		{
			View gameFace = _gameFace;
			if (gameFace != null)
			{
				gameFace.ExecuteScript(javaScript);
			}
		}

		public void LoadEmbeddedCSS()
		{
			string text = EmbeddedResource.LoadText("TweakUI.Resources.TweakUI.css");
			if (text != null)
			{
				if (!Directory.Exists(ConfigBase.MOD_PATH))
				{
					Directory.CreateDirectory(ConfigBase.MOD_PATH);
				}
				string path = Path.Combine(ConfigBase.MOD_PATH, "TweakUI.css");
				File.WriteAllText(path, text);
			}
		}

		public void InjectEmbeddedCSS()
		{
			string text = EmbeddedResource.LoadText("TweakUI.Resources.StyleInject.js");
			if (text != null)
			{
				ExecuteJS(text);
			}
		}

		private void InjectBase64Function()
		{
			string javaScript = "\r\n                if (typeof atob !== 'function') {\r\n                    function atob(str) {\r\n                        // Going to use a lookup table to find characters.\r\n                        var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';\r\n                        var output = '';\r\n\r\n                        str = String(str).replace(/=+$/, ''); // Remove any trailing '='s\r\n\r\n                        if (str.length % 4 == 1) {\r\n                            throw new Error(\"'atob' failed: The string to be decoded is not correctly encoded.\");\r\n                        }\r\n\r\n                        for (\r\n                            // initialize variables\r\n                            var bc = 0, bs, buffer, idx = 0, output = '';\r\n                            // get next character\r\n                            buffer = str.charAt(idx++);\r\n                            // character found in table? initialize bit storage and add its ascii value;\r\n                            ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,\r\n                                // and if not first of each 4 characters,\r\n                                // convert the first 8 bits to one ascii character\r\n                                bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0\r\n                        ) {\r\n                            buffer = chars.indexOf(buffer);\r\n                        }\r\n\r\n                        return output;\r\n                    }                \r\n                }";
			ExecuteJS(javaScript);
		}

		private string ToBase64(string input)
		{
			return Convert.ToBase64String(Encoding.UTF8.GetBytes(input));
		}
	}
}
namespace TweakUI.Configuration
{
	public abstract class ConfigBase : ModelWriter
	{
		public static readonly string MOD_PATH = Path.Combine(Application.persistentDataPath, "Mods", "TweakUI");

		private static readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings
		{
			NullValueHandling = (NullValueHandling)1,
			Formatting = (Formatting)1
		};

		public static Action OnUpdated;

		protected abstract string ConfigFileName { get; }

		public virtual void Save()
		{
			string contents = JsonConvert.SerializeObject((object)this, _serializerSettings);
			if (!Directory.Exists(MOD_PATH))
			{
				Directory.CreateDirectory(MOD_PATH);
			}
			string path = Path.Combine(MOD_PATH, ConfigFileName);
			File.WriteAllText(path, contents);
			OnUpdated?.Invoke();
		}

		public static T Load<T>(bool useDefaultAsTemplate = true) where T : ConfigBase, new()
		{
			T val = new T();
			string path = Path.Combine(MOD_PATH, val.ConfigFileName);
			string text = "";
			if (File.Exists(path))
			{
				text = File.ReadAllText(path);
			}
			else
			{
				if (!useDefaultAsTemplate)
				{
					return new T();
				}
				text = LoadDefaultAndSave<T>();
			}
			return (T)JsonConvert.DeserializeObject(text, typeof(T), _serializerSettings);
		}

		public static T LoadDefault<T>() where T : ConfigBase, new()
		{
			T val = new T();
			string text = val.LoadDefaultJson();
			return JsonConvert.DeserializeObject<T>(text, _serializerSettings);
		}

		private static string LoadDefaultAndSave<T>() where T : ConfigBase, new()
		{
			T val = new T();
			string text = val.LoadDefaultJson();
			if (!Directory.Exists(MOD_PATH))
			{
				Directory.CreateDirectory(MOD_PATH);
			}
			string path = Path.Combine(MOD_PATH, val.ConfigFileName);
			File.WriteAllText(path, text);
			return text;
		}

		protected virtual string LoadDefaultJson()
		{
			string text = "TweakUI.Resources.default" + UppercaseFirst(ConfigFileName);
			Assembly executingAssembly = Assembly.GetExecutingAssembly();
			using Stream stream = executingAssembly.GetManifestResourceStream(text);
			if (stream == null)
			{
				Debug.LogError((object)("Embedded default config not found: " + text));
				return null;
			}
			using StreamReader streamReader = new StreamReader(stream);
			return streamReader.ReadToEnd();
		}

		private static string UppercaseFirst(string s)
		{
			if (string.IsNullOrEmpty(s))
			{
				return string.Empty;
			}
			return char.ToUpper(s[0]) + s.Substring(1);
		}
	}
	public class TweakUIConfig : ConfigBase
	{
		protected override string ConfigFileName => "config.json";

		public string TransportationOverviewSize { get; set; } = "tu-transportation-overview-60";


		public string AssetMenuRows { get; set; } = "tu-asset-menu-rows-2";

	}
}