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";
}
}