using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; using System.Net.WebSockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using AngleSharp.Dom; using AngleSharp.Html.Parser; using Esprima; using HarmonyLib; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Input; using Il2CppScheduleOne.UI.Phone; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using Jint; using Jint.Native; using Jint.Native.Function; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Sideload; using Sideload.Bundle; using Sideload.Config; using Sideload.Css; using Sideload.Devtools.Cdp; using Sideload.Dom; using Sideload.Host; using Sideload.Input; using Sideload.Layout; using Sideload.Model; using Sideload.Net; using Sideload.Paint; using Sideload.Phone; using Sideload.Script; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Sideload", "1.0.1", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Sideload")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("DooDesch")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © DooDesch")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1+6974c626e698673232f2fb3ea70da64862741945")] [assembly: AssemblyProduct("Sideload")] [assembly: AssemblyTitle("Sideload")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [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] [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] [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 Sideload { public class Core : MelonMod { internal static Instance Log; public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; HostAllowlist.Log = delegate(string line) { Log.Warning(line); }; PreloadRuntimeDependency("AngleSharp.dll"); PreloadRuntimeDependency("Esprima.dll"); PreloadRuntimeDependency("Jint.dll"); try { ((MelonBase)this).HarmonyInstance.PatchAll(); Log.Msg("[Sideload] patches applied."); } catch (Exception ex) { Log.Error("[Sideload] PatchAll failed - no app will reach the phone: " + ex); } Preferences.Initialize(); if (Preferences.DevTools) { CdpServer.Start(Preferences.DevToolsPort); } } public override void OnUpdate() { WebView.TickAll(Time.deltaTime); TurnInput.Tick(); CdpServer.Pump(); } public override void OnDeinitializeMelon() { CdpServer.Stop(); ChromeLauncher.Close(); } private static void PreloadRuntimeDependency(string fileName) { try { string text = Path.Combine(MelonEnvironment.UserLibsDirectory, fileName); if (!File.Exists(text)) { string text2 = Path.Combine(Path.GetDirectoryName(typeof(Core).Assembly.Location) ?? MelonEnvironment.ModsDirectory, fileName); if (!File.Exists(text2)) { Log.Warning("[Sideload] runtime dependency not found in UserLibs or next to the mod: " + fileName); return; } text = text2; } AssemblyName name = Assembly.LoadFrom(text).GetName(); Log.Msg($"[Sideload] preloaded {name.Name} {name.Version}"); } catch (Exception ex) { Log.Warning("[Sideload] preloading " + fileName + " failed: " + ex.Message); } } } internal sealed class AppRegistration { internal string Id; internal string Title; internal string IconLabel; internal string BundlePrefix; internal Assembly HostAssembly; internal AppBundle Bundle; internal Sprite IconSprite; internal bool Portrait; internal bool DeclaredPortrait; internal bool CanTurn; internal int Badge; internal Action OrientationChanged; internal bool Supports(bool portrait) { if (!CanTurn) { return DeclaredPortrait == portrait; } return true; } } internal static class Registry { private static readonly List _apps = new List(); internal static IReadOnlyList Apps => _apps; internal static void RegisterApp(string id, string title, string iconLabel, string bundlePrefix, Assembly host) { if (string.IsNullOrWhiteSpace(id) || host == null) { return; } AppRegistration appRegistration = new AppRegistration { Id = id.Trim(), Title = (string.IsNullOrWhiteSpace(title) ? id : title), IconLabel = ((!string.IsNullOrWhiteSpace(iconLabel)) ? iconLabel : (string.IsNullOrWhiteSpace(title) ? id : title)), BundlePrefix = (bundlePrefix ?? ""), HostAssembly = host }; appRegistration.Bundle = new AppBundle(appRegistration.Id, appRegistration.BundlePrefix, host); for (int i = 0; i < _apps.Count; i++) { if (string.Equals(_apps[i].Id, appRegistration.Id, StringComparison.OrdinalIgnoreCase)) { _apps[i] = appRegistration; Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] app '" + appRegistration.Id + "' registered twice; the later registration wins."); } return; } } _apps.Add(appRegistration); Instance log2 = Core.Log; if (log2 != null) { log2.Msg($"[Sideload] app registered: {appRegistration.Id} ('{appRegistration.Title}') from {host.GetName().Name}"); } } internal static void DeclareOrientations(string appId, string orientations) { AppRegistration appRegistration = Find(appId); if (appRegistration == null) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] orientation: no app '" + appId + "'."); } return; } OrientationSet orientationSet = OrientationSet.Parse(orientations); foreach (string item in orientationSet.Ignored) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"[Sideload] '{appId}' declared orientation '{item}', which is neither 'portrait' " + "nor 'landscape' - ignored."); } } if (orientationSet.Declared) { appRegistration.DeclaredPortrait = orientationSet.Portrait; appRegistration.CanTurn = orientationSet.CanTurn; appRegistration.Portrait = orientationSet.Portrait; } } internal static void SetOrientation(string appId, string orientation) { AppRegistration appRegistration = Find(appId); if (appRegistration == null) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] setOrientation: no app '" + appId + "'."); } return; } bool flag = string.Equals((orientation ?? "").Trim(), "portrait", StringComparison.OrdinalIgnoreCase); if (appRegistration.Portrait == flag) { return; } if (!appRegistration.Supports(flag)) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning($"[Sideload] '{appRegistration.Id}' was asked for {(flag ? "portrait" : "landscape")} but " + "only declared the other one - ignored. Declare both to allow turning: Apps.Register(...).Orientation(\"landscape\", \"portrait\")."); } } else { appRegistration.Portrait = flag; appRegistration.OrientationChanged?.Invoke(); } } internal static void SetBadge(string appId, int count) { AppRegistration appRegistration = Find(appId); if (appRegistration == null) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] badge: no app '" + appId + "'."); } } else { appRegistration.Badge = Math.Max(0, count); LiveHost(appId)?.SetBadge(appRegistration.Badge); } } internal static void Notify(string appId, string title, string subtitle) { LiveHost(appId)?.Notify(title, subtitle); } internal static bool IsOnScreen(string appId) { return LiveHost(appId)?.IsShowing ?? false; } private static PhoneAppHost LiveHost(string appId) { IReadOnlyList hosts = HomeScreenPatch.Hosts; for (int i = 0; i < hosts.Count; i++) { if (string.Equals(hosts[i].Id, appId, StringComparison.OrdinalIgnoreCase) && hosts[i].IsAlive) { return hosts[i]; } } return null; } internal static AppRegistration Find(string appId) { if (string.IsNullOrWhiteSpace(appId)) { return null; } for (int i = 0; i < _apps.Count; i++) { if (string.Equals(_apps[i].Id, appId, StringComparison.OrdinalIgnoreCase)) { return _apps[i]; } } return null; } } } namespace Sideload.Script { public sealed class Bridge { public sealed class JsStorage { private readonly string _path; private readonly Dictionary _values = new Dictionary(StringComparer.Ordinal); private bool _loaded; internal JsStorage(string appId) { string path = Path.Combine(MelonEnvironment.UserDataDirectory, "Sideload"); _path = Path.Combine(path, Sanitise(appId) + ".json"); } public string Get(string key, string fallback = "") { Load(); if (key == null || !_values.TryGetValue(key, out var value)) { return fallback; } return value; } public void Set(string key, string value) { if (!string.IsNullOrEmpty(key)) { Load(); _values[key] = value ?? ""; Save(); } } public void Remove(string key) { if (!string.IsNullOrEmpty(key)) { Load(); if (_values.Remove(key)) { Save(); } } } public void Clear() { Load(); _values.Clear(); Save(); } private void Load() { if (_loaded) { return; } _loaded = true; try { if (!File.Exists(_path)) { return; } foreach (KeyValuePair item in MiniJson.ParseObject(File.ReadAllText(_path))) { _values[item.Key] = item.Value; } } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] storage read failed: " + ex.Message); } } } private void Save() { try { Directory.CreateDirectory(Path.GetDirectoryName(_path)); File.WriteAllText(_path, MiniJson.WriteObject(_values)); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] storage write failed: " + ex.Message); } } } private static string Sanitise(string id) { StringBuilder stringBuilder = new StringBuilder(id?.Length ?? 0); string text = id ?? "app"; foreach (char c in text) { stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '_') ? c : '-'); } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "app"; } } private static readonly Dictionary> _handlers = new Dictionary>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary> _subscribers = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary> _listeners = new Dictionary>(StringComparer.OrdinalIgnoreCase); private readonly ScriptHost _host; private readonly string _appId; public JsStorage Storage { get; } public string AppId => _appId; public string Orientation { get { AppRegistration appRegistration = Registry.Find(_appId); if (appRegistration == null || !appRegistration.Portrait) { return "landscape"; } return "portrait"; } } internal Bridge(ScriptHost host, string appId) { _host = host; _appId = appId; Storage = new JsStorage(appId); } internal static void Handle(string appId, string name, Func handler) { if (!string.IsNullOrWhiteSpace(name) && handler != null) { _handlers[Key(appId, name)] = handler; } } internal static void Emit(string appId, string name, string payload) { if (string.IsNullOrWhiteSpace(name) || !_subscribers.TryGetValue(name, out var value)) { return; } Bridge[] array = value.ToArray(); foreach (Bridge bridge in array) { if (appId == null || string.Equals(bridge._appId, appId, StringComparison.OrdinalIgnoreCase)) { bridge.Deliver(name, payload ?? ""); } } } private static string Key(string appId, string name) { return appId + "\0" + name.Trim(); } public string Call(string name, string argument = "") { if (string.IsNullOrWhiteSpace(name)) { return ""; } if (!_handlers.TryGetValue(Key(_appId, name), out var value) && !_handlers.TryGetValue(Key(null, name), out value)) { Instance log = Core.Log; if (log != null) { log.Warning($"[Sideload] {_appId}: s1.call('{name}') has no handler."); } return ""; } try { return value(_appId, argument ?? "") ?? ""; } catch (Exception ex) { Instance log2 = Core.Log; if (log2 != null) { log2.Error($"[Sideload] {_appId}: s1.call('{name}') threw: {ex.Message}"); } return ""; } } public void On(string name, JsValue handler) { if (!string.IsNullOrWhiteSpace(name) && !(handler == (JsValue)null) && JsValueExtensions.IsObject(handler)) { if (!_listeners.TryGetValue(name, out var value)) { value = (_listeners[name] = new List()); } value.Add(handler); if (!_subscribers.TryGetValue(name, out var value2)) { value2 = (_subscribers[name] = new List()); } if (!value2.Contains(this)) { value2.Add(this); } } } public void Log(params object[] args) { Instance log = Core.Log; if (log != null) { log.Msg("[" + _appId + "] " + string.Join(" ", Array.ConvertAll(args ?? Array.Empty(), (object a) => a?.ToString() ?? "null"))); } } public void SetOrientation(string orientation) { Registry.SetOrientation(_appId, orientation); } internal void Dispose() { foreach (KeyValuePair> subscriber in _subscribers) { subscriber.Value.Remove(this); } _listeners.Clear(); } private void Deliver(string name, string payload) { if (!_listeners.TryGetValue(name, out var value)) { return; } JsValue[] array = value.ToArray(); foreach (JsValue val in array) { try { _host.Engine.Invoke(val, new object[1] { payload }); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Error($"[Sideload] {_appId}: s1.on('{name}') handler failed: {ex.Message}"); } } } } } public sealed class JsElement { private readonly ScriptHost _host; internal IElement Native { get; } public string TagName => Native.LocalName; public string Id { get { return Native.Id ?? ""; } set { if (Set(Native.Id, value)) { Native.Id = value; _host.MarkDirty(); } } } public string ClassName { get { return Native.ClassName ?? ""; } set { if (Set(Native.ClassName, value)) { Native.ClassName = value; _host.MarkDirty(); } } } public JsClassList ClassList => new JsClassList(_host, Native); public JsStyle Style => new JsStyle(_host, Native); public string TextContent { get { return ((INode)Native).TextContent ?? ""; } set { if (Set(((INode)Native).TextContent, value)) { ((INode)Native).TextContent = value; _host.MarkDirty(); } } } public string InnerHTML { get { return Native.InnerHtml ?? ""; } set { if (!Set(Native.InnerHtml, value)) { return; } foreach (IElement item in (IEnumerable)((IParentNode)Native).Children) { _host.Forget(item); } Native.InnerHtml = value; _host.MarkDirty(); } } public string Value { get { return Native.GetAttribute("value") ?? ""; } set { if (Set(Native.GetAttribute("value"), value)) { Native.SetAttribute("value", value ?? ""); _host.MarkDirty(); } } } public bool Disabled { get { return Native.HasAttribute("disabled"); } set { if (value) { Native.SetAttribute("disabled", ""); } else { Native.RemoveAttribute("disabled"); } _host.MarkDirty(); } } public JsElement ParentElement => _host.Wrap(((INode)Native).ParentElement); public JsValue Children { get { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown List list = new List(); foreach (IElement item in (IEnumerable)((IParentNode)Native).Children) { list.Add(JsValue.FromObject(_host.Engine, (object)_host.Wrap(item))); } return (JsValue)new JsArray(_host.Engine, list.ToArray()); } } internal JsElement(ScriptHost host, IElement element) { _host = host; Native = element; } public string GetAttribute(string name) { return Native.GetAttribute(name); } public void SetAttribute(string name, string value) { if (Set(Native.GetAttribute(name), value)) { Native.SetAttribute(name, value ?? ""); _host.MarkDirty(); } } public void RemoveAttribute(string name) { if (Native.HasAttribute(name)) { Native.RemoveAttribute(name); _host.MarkDirty(); } } public bool HasAttribute(string name) { return Native.HasAttribute(name); } public JsElement QuerySelector(string selector) { return _host.Wrap(((IParentNode)Native).QuerySelector(selector)); } public JsValue QuerySelectorAll(string selector) { return _host.WrapAll((IEnumerable)((IParentNode)Native).QuerySelectorAll(selector)); } public JsElement AppendChild(JsElement child) { if (child != null) { ((INode)Native).AppendChild((INode)(object)child.Native); _host.MarkDirty(); } return child; } public JsElement RemoveChild(JsElement child) { if (child == null) { return null; } ((INode)Native).RemoveChild((INode)(object)child.Native); _host.Forget(child.Native); _host.MarkDirty(); return child; } public JsElement InsertBefore(JsElement child, JsElement reference) { if (child == null) { return null; } ((INode)Native).InsertBefore((INode)(object)child.Native, (INode)(object)reference?.Native); _host.MarkDirty(); return child; } public void Remove() { ((IChildNode)Native).Remove(); _host.Forget(Native); _host.MarkDirty(); } public void ReplaceChildren() { foreach (IElement item in (IEnumerable)((IParentNode)Native).Children) { _host.Forget(item); } Native.InnerHtml = ""; _host.MarkDirty(); } public void AddEventListener(string type, JsValue handler) { _host.AddListener(Native, type, handler); } public void RemoveEventListener(string type, JsValue handler) { _host.RemoveListener(Native, type, handler); } public void Click() { _host.Dispatch(Native, "click"); } public void Focus() { _host.RequestFocus(Native); } public void ScrollToEnd() { _host.RequestScrollToEnd(Native); } public override string ToString() { return "<" + Native.LocalName + ">"; } private static bool Set(string current, string next) { return !string.Equals(current ?? "", next ?? "", StringComparison.Ordinal); } } public sealed class JsClassList { private readonly ScriptHost _host; private readonly IElement _element; public int Length => _element.ClassList.Length; internal JsClassList(ScriptHost host, IElement element) { _host = host; _element = element; } public bool Contains(string name) { return _element.ClassList.Contains(name); } public void Add(string name) { _element.ClassList.Add(new string[1] { name }); _host.MarkDirty(); } public void Remove(string name) { _element.ClassList.Remove(new string[1] { name }); _host.MarkDirty(); } public bool Toggle(string name) { bool flag = !_element.ClassList.Contains(name); if (flag) { _element.ClassList.Add(new string[1] { name }); } else { _element.ClassList.Remove(new string[1] { name }); } _host.MarkDirty(); return flag; } } public sealed class JsStyle : ObjectInstance { private const string Attribute = "style"; private readonly ScriptHost _host; private readonly IElement _element; internal JsStyle(ScriptHost host, IElement element) : base(host.Engine) { _host = host; _element = element; } public override JsValue Get(JsValue property, JsValue receiver) { string text = (JsValueExtensions.IsString(property) ? JsValueExtensions.AsString(property) : null); if (text == null) { return JsValue.Undefined; } if (text == "cssText") { return JsValue.op_Implicit(_element.GetAttribute("style") ?? ""); } string text2 = Read(Kebab(text)); if (text2 == null) { return JsValue.op_Implicit(""); } return JsValue.op_Implicit(text2); } public override bool Set(JsValue property, JsValue value, JsValue receiver) { if (!JsValueExtensions.IsString(property)) { return false; } string text = JsValueExtensions.AsString(property); string text2 = ((JsValueExtensions.IsNull(value) || JsValueExtensions.IsUndefined(value)) ? "" : ((object)value).ToString()); string text3 = ((text == "cssText") ? null : Kebab(text)); if (string.Equals(((text3 == null) ? _element.GetAttribute("style") : Read(text3)) ?? "", text2, StringComparison.Ordinal)) { return true; } Write(text3, text2); _host.MarkDirty(); return true; } public override bool HasProperty(JsValue property) { if (JsValueExtensions.IsString(property)) { if (!(JsValueExtensions.AsString(property) == "cssText")) { return Read(Kebab(JsValueExtensions.AsString(property))) != null; } return true; } return false; } private string Read(string property) { foreach (Declaration item in CssParser.ParseDeclarations(_element.GetAttribute("style") ?? "")) { if (string.Equals(item.Property, property, StringComparison.OrdinalIgnoreCase)) { return item.Value; } } return null; } private void Write(string property, string value) { if (property == null) { _element.SetAttribute("style", value); return; } List list = new List(); foreach (Declaration item in CssParser.ParseDeclarations(_element.GetAttribute("style") ?? "")) { if (!string.Equals(item.Property, property, StringComparison.OrdinalIgnoreCase)) { list.Add(item.Property + ": " + item.Value); } } if (!string.IsNullOrEmpty(value)) { list.Add(property + ": " + value); } _element.SetAttribute("style", string.Join("; ", list)); } private static string Kebab(string name) { if (string.IsNullOrEmpty(name)) { return name; } StringBuilder stringBuilder = new StringBuilder(name.Length + 4); foreach (char c in name) { if (char.IsUpper(c)) { stringBuilder.Append('-'); stringBuilder.Append(char.ToLowerInvariant(c)); } else { stringBuilder.Append(c); } } return stringBuilder.ToString(); } } public sealed class JsDocument { private readonly ScriptHost _host; private readonly IDocument _document; public JsElement Body { get { ScriptHost host = _host; IElement body = (IElement)(object)_document.Body; return host.Wrap(body ?? _document.DocumentElement); } } internal JsDocument(ScriptHost host, IDocument document) { _host = host; _document = document; } public JsElement GetElementById(string id) { return _host.Wrap(((INonElementParentNode)_document).GetElementById(id)); } public JsElement QuerySelector(string selector) { return _host.Wrap(((IParentNode)_document).QuerySelector(selector)); } public JsValue QuerySelectorAll(string selector) { return _host.WrapAll((IEnumerable)((IParentNode)_document).QuerySelectorAll(selector)); } public JsElement CreateElement(string tag) { return _host.Wrap(_document.CreateElement(tag)); } public void AddEventListener(string type, JsValue handler) { ScriptHost host = _host; IElement body = (IElement)(object)_document.Body; host.AddListener(body ?? _document.DocumentElement, type, handler); } } public sealed class JsEvent { public string Type { get; } public JsElement Target { get; } public JsElement CurrentTarget { get; internal set; } public string Value { get; internal set; } = ""; public string Key { get; internal set; } = ""; public string Source { get; internal set; } = ""; public bool DefaultPrevented { get; private set; } internal bool PropagationStopped { get; private set; } internal JsEvent(string type, JsElement target) { Type = type; Target = target; CurrentTarget = target; } public void PreventDefault() { DefaultPrevented = true; } public void StopPropagation() { PropagationStopped = true; } } internal sealed class FetchApi { private const int MaxInFlight = 8; private const string ResponseFactory = "(function (status, statusText, url, headerLines, body, redirected) {\n var map = {};\n var lines = headerLines ? headerLines.split('\\n') : [];\n for (var i = 0; i < lines.length; i++) {\n var at = lines[i].indexOf(':');\n if (at > 0) map[lines[i].substring(0, at).trim().toLowerCase()] = lines[i].substring(at + 1).trim();\n }\n\n return {\n ok: status >= 200 && status < 300,\n status: status,\n statusText: statusText,\n url: url,\n redirected: redirected,\n headers: {\n get: function (name) { var k = String(name).toLowerCase(); return k in map ? map[k] : null; },\n has: function (name) { return String(name).toLowerCase() in map; }\n },\n // Already-settled promises: the body was read before the page was handed the response, so these match the web's\n // shape without ever being pending.\n text: function () { return Promise.resolve(body); },\n json: function () {\n try { return Promise.resolve(JSON.parse(body)); }\n catch (e) { return Promise.reject(e); }\n }\n };\n})"; private const string Wrapper = "(function (call) {\n return function fetch(url, init) {\n return call(url === undefined || url === null ? '' : String(url), init);\n };\n})"; private readonly ConcurrentQueue _finished = new ConcurrentQueue(); private readonly Engine _engine; private readonly Promises _promises; private readonly Action _onError; private readonly string _appId; private JsValue _responseFactory; private int _inFlight; internal int Ready => _finished.Count; internal FetchApi(Engine engine, string appId, Promises promises, Action onError) { _engine = engine; _appId = appId ?? ""; _promises = promises; _onError = onError; } internal void Install() { _responseFactory = _engine.Evaluate("(function (status, statusText, url, headerLines, body, redirected) {\n var map = {};\n var lines = headerLines ? headerLines.split('\\n') : [];\n for (var i = 0; i < lines.length; i++) {\n var at = lines[i].indexOf(':');\n if (at > 0) map[lines[i].substring(0, at).trim().toLowerCase()] = lines[i].substring(at + 1).trim();\n }\n\n return {\n ok: status >= 200 && status < 300,\n status: status,\n statusText: statusText,\n url: url,\n redirected: redirected,\n headers: {\n get: function (name) { var k = String(name).toLowerCase(); return k in map ? map[k] : null; },\n has: function (name) { return String(name).toLowerCase() in map; }\n },\n // Already-settled promises: the body was read before the page was handed the response, so these match the web's\n // shape without ever being pending.\n text: function () { return Promise.resolve(body); },\n json: function () {\n try { return Promise.resolve(JSON.parse(body)); }\n catch (e) { return Promise.reject(e); }\n }\n };\n})", (string)null); JsValue val = JsValue.FromObject(_engine, (object)new Func(Call)); _engine.SetValue("fetch", _engine.Invoke(_engine.Evaluate("(function (call) {\n return function fetch(url, init) {\n return call(url === undefined || url === null ? '' : String(url), init);\n };\n})", (string)null), new object[1] { val })); } internal void Settle() { Action result; while (_finished.TryDequeue(out result)) { try { result(); } catch (Exception ex) { _onError?.Invoke("settling a fetch failed: " + ex.Message); } } } private JsValue Call(string url, JsValue init) { Deferred deferred = _promises.Create(); FetchCall fetchCall = new FetchCall { AppId = _appId }; if (!Uri.TryCreate(url, UriKind.Absolute, out Uri result)) { Later(delegate { deferred.Reject("fetch needs an absolute http(s) URL - got '" + Trim(url) + "'."); }); return deferred.Promise; } fetchCall.Url = result; try { ReadInit(init, fetchCall); } catch (Exception ex) { Exception ex2 = ex; Exception e = ex2; Later(delegate { deferred.Reject("the second argument to fetch is not usable: " + e.Message); }); return deferred.Promise; } if (!HostAllowlist.Allows(_appId, result, out var reason)) { HostAllowlist.ReportOnce(_appId, result, reason); Later(delegate { deferred.Reject(reason); }); return deferred.Promise; } if (_inFlight >= 8) { Later(delegate { deferred.Reject($"too many requests at once - this app already has {8} " + "fetches in flight. Wait for one to settle before starting another."); }); return deferred.Promise; } _inFlight++; Fetcher.Send(fetchCall, delegate(FetchOutcome outcome) { _finished.Enqueue(delegate { _inFlight--; if (outcome.Failed) { deferred.Reject(outcome.Error); } else { deferred.Resolve(Response(outcome)); } }); }); return deferred.Promise; } private void Later(Action settle) { _finished.Enqueue(settle); } private JsValue Response(FetchOutcome outcome) { return _engine.Invoke(_responseFactory, new object[6] { outcome.Status, outcome.StatusText, outcome.Url, outcome.HeaderLines, outcome.Body, outcome.Redirected }); } private static void ReadInit(JsValue init, FetchCall call) { if (init == (JsValue)null || JsValueExtensions.IsUndefined(init) || JsValueExtensions.IsNull(init) || !JsValueExtensions.IsObject(init)) { return; } ObjectInstance obj = JsValueExtensions.AsObject(init); JsValue val = ((JsValue)obj).Get(JsValue.op_Implicit("method")); if (!JsValueExtensions.IsUndefined(val) && !JsValueExtensions.IsNull(val)) { call.Method = ((object)val).ToString(); } JsValue val2 = ((JsValue)obj).Get(JsValue.op_Implicit("body")); if (!JsValueExtensions.IsUndefined(val2) && !JsValueExtensions.IsNull(val2)) { call.Body = ((object)val2).ToString(); } JsValue val3 = ((JsValue)obj).Get(JsValue.op_Implicit("timeout")); if (JsValueExtensions.IsNumber(val3)) { call.TimeoutMs = (int)JsValueExtensions.AsNumber(val3); } JsValue val4 = ((JsValue)obj).Get(JsValue.op_Implicit("headers")); if (!JsValueExtensions.IsObject(val4)) { return; } ObjectInstance val5 = JsValueExtensions.AsObject(val4); foreach (KeyValuePair ownProperty in val5.GetOwnProperties()) { JsValue val6 = ((JsValue)val5).Get(ownProperty.Key); if (!JsValueExtensions.IsUndefined(val6) && !JsValueExtensions.IsNull(val6)) { call.Headers.Add(new KeyValuePair(((object)ownProperty.Key).ToString(), ((object)val6).ToString())); } } } private static string Trim(string url) { string text; if (url == null || url.Length <= 80) { text = url; if (text == null) { return ""; } } else { text = url.Substring(0, 80) + "..."; } return text; } } internal static class MiniJson { internal static string WriteObject(Dictionary values) { if (values == null || values.Count == 0) { return "{}"; } StringBuilder stringBuilder = new StringBuilder("{"); bool flag = true; foreach (KeyValuePair value in values) { if (!flag) { stringBuilder.Append(','); } flag = false; stringBuilder.Append('\n').Append(" "); Escape(stringBuilder, value.Key); stringBuilder.Append(": "); Escape(stringBuilder, value.Value); } return stringBuilder.Append("\n}").ToString(); } internal static Dictionary ParseObject(string json) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); if (string.IsNullOrWhiteSpace(json)) { return dictionary; } int i = 0; SkipWhitespace(json, ref i); if (i >= json.Length || json[i] != '{') { return dictionary; } i++; while (true) { SkipWhitespace(json, ref i); if (i >= json.Length || json[i] == '}') { break; } if (json[i] == ',') { i++; continue; } if (json[i] != '"') { break; } string key = ReadString(json, ref i); SkipWhitespace(json, ref i); if (i >= json.Length || json[i] != ':') { break; } i++; SkipWhitespace(json, ref i); if (i >= json.Length) { break; } dictionary[key] = ((json[i] == '"') ? ReadString(json, ref i) : ReadBareValue(json, ref i)); } return dictionary; } private static void Escape(StringBuilder sb, string value) { sb.Append('"'); string text = value ?? ""; foreach (char c in text) { switch (c) { case '"': sb.Append("\\\""); continue; case '\\': sb.Append("\\\\"); continue; case '\n': sb.Append("\\n"); continue; case '\r': sb.Append("\\r"); continue; case '\t': sb.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder = sb.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4")); } else { sb.Append(c); } } sb.Append('"'); } private static string ReadString(string json, ref int i) { StringBuilder stringBuilder = new StringBuilder(); i++; while (i < json.Length) { char c = json[i++]; switch (c) { default: stringBuilder.Append(c); continue; case '\\': { if (i >= json.Length) { break; } char c2 = json[i++]; switch (c2) { case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'u': { if (i + 4 <= json.Length && int.TryParse(json.Substring(i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var result)) { stringBuilder.Append((char)result); i += 4; } break; } default: stringBuilder.Append(c2); break; } continue; } case '"': break; } break; } return stringBuilder.ToString(); } private static string ReadBareValue(string json, ref int i) { int num = i; while (i < json.Length && json[i] != ',' && json[i] != '}') { i++; } return json.Substring(num, i - num).Trim(); } private static void SkipWhitespace(string json, ref int i) { while (i < json.Length && char.IsWhiteSpace(json[i])) { i++; } } } internal sealed class Promises { private const string Factory = "(function (report) {\n var settle, fail;\n var root = new Promise(function (ok, no) { settle = ok; fail = no; });\n\n function watch(p) {\n var chained = false;\n var then = p.then.bind(p);\n\n p.then = function (ok, no) { chained = true; return watch(then(ok, no)); };\n p.catch = function (no) { chained = true; return watch(then(undefined, no)); };\n p.finally = function (fn) { chained = true; return watch(then(\n function (v) { fn(); return v; },\n function (e) { fn(); throw e; })); };\n\n then(undefined, function (err) {\n if (!chained) report(String(err && err.message ? err.message : err));\n });\n return p;\n }\n\n return {\n promise: watch(root),\n resolve: settle,\n reject: function (message) { fail(new Error(message)); }\n };\n})"; private readonly Engine _engine; private readonly JsValue _factory; private readonly JsValue _report; internal Promises(Engine engine, Action onUnhandledRejection) { _engine = engine; _factory = engine.Evaluate("(function (report) {\n var settle, fail;\n var root = new Promise(function (ok, no) { settle = ok; fail = no; });\n\n function watch(p) {\n var chained = false;\n var then = p.then.bind(p);\n\n p.then = function (ok, no) { chained = true; return watch(then(ok, no)); };\n p.catch = function (no) { chained = true; return watch(then(undefined, no)); };\n p.finally = function (fn) { chained = true; return watch(then(\n function (v) { fn(); return v; },\n function (e) { fn(); throw e; })); };\n\n then(undefined, function (err) {\n if (!chained) report(String(err && err.message ? err.message : err));\n });\n return p;\n }\n\n return {\n promise: watch(root),\n resolve: settle,\n reject: function (message) { fail(new Error(message)); }\n };\n})", (string)null); _report = JsValue.FromObject(engine, (object)(Action)delegate(string m) { onUnhandledRejection?.Invoke(m); }); } internal Deferred Create() { ObjectInstance val = JsValueExtensions.AsObject(_engine.Invoke(_factory, new object[1] { _report })); return new Deferred(_engine, ((JsValue)val).Get(JsValue.op_Implicit("promise")), ((JsValue)val).Get(JsValue.op_Implicit("resolve")), ((JsValue)val).Get(JsValue.op_Implicit("reject"))); } internal void Pump() { _engine.Constraints.Reset(); _engine.Advanced.ProcessTasks(); } } internal sealed class Deferred { private readonly Engine _engine; private readonly JsValue _resolve; private readonly JsValue _reject; internal JsValue Promise { get; } internal Deferred(Engine engine, JsValue promise, JsValue resolve, JsValue reject) { _engine = engine; Promise = promise; _resolve = resolve; _reject = reject; } internal void Resolve(JsValue value) { _engine.Invoke(_resolve, new object[1] { value ?? JsValue.Undefined }); } internal void Reject(string message) { _engine.Invoke(_reject, new object[1] { message ?? "" }); } } internal sealed class ScriptHost { private sealed class Timer { internal int Id; internal JsValue Callback; internal double Remaining; internal double Interval; internal bool Cancelled; } public sealed class Console { private readonly string _appId; internal Console(string appId) { _appId = appId; } public void Log(params object[] args) { Emit("log", args); } public void Info(params object[] args) { Emit("info", args); } public void Warn(params object[] args) { Emit("warn", args); } public void Error(params object[] args) { Emit("error", args); } private void Emit(string level, object[] args) { string text = Format(args); if (!(level == "warn")) { if (level == "error") { Instance log = Core.Log; if (log != null) { log.Error(text); } } else { Instance log2 = Core.Log; if (log2 != null) { log2.Msg(text); } } } else { Instance log3 = Core.Log; if (log3 != null) { log3.Warning(text); } } Diagnostics?.Invoke(_appId, level, args, text); } private string Format(object[] args) { if (args == null || args.Length == 0) { return "[" + _appId + "]"; } return "[" + _appId + "] " + string.Join(" ", Array.ConvertAll(args, (object a) => a?.ToString() ?? "null")); } } private static readonly TimeSpan Budget = TimeSpan.FromMilliseconds(250.0); internal static Action Diagnostics; private readonly Dictionary>> _listeners = new Dictionary>>(); private readonly Dictionary _wrappers = new Dictionary(); private readonly List _timers = new List(); private readonly string _appId; private Promises _promises; private FetchApi _fetch; private IDocument _document; private Bridge _bridge; private int _nextTimerId = 1; private bool _failed; internal Engine Engine { get; } internal Action OnDomChanged { get; } internal Action OnFocusRequested { get; } internal Action OnScrollToEnd { get; } internal bool Failed => _failed; internal string LastError { get; private set; } = ""; internal ScriptHost(string appId, IDocument document, Action onDomChanged, Action onFocusRequested, Action onScrollToEnd) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Expected O, but got Unknown _appId = appId; _document = document; OnDomChanged = onDomChanged; OnFocusRequested = onFocusRequested; OnScrollToEnd = onScrollToEnd; Engine = new Engine((Action)delegate(Options options) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown ConstraintsOptionsExtensions.TimeoutInterval(options, Budget); ConstraintsOptionsExtensions.MaxStatements(options, 2000000); OptionsExtensions.LimitRecursion(options, 256); options.Strict = false; options.ExperimentalFeatures = (ExperimentalFeature)3; OptionsExtensions.SetTypeResolver(options, new TypeResolver { MemberNameCreator = BothCasings }); }); Bind(); } internal void Rebound(IDocument document) { _document = document; _wrappers.Clear(); Engine.SetValue("document", new JsDocument(this, _document)); } internal void Dispose() { _bridge?.Dispose(); _listeners.Clear(); _wrappers.Clear(); _timers.Clear(); } internal void Forget(IElement element) { if (element == null) { return; } _wrappers.Remove(element); _listeners.Remove(element); foreach (IElement item in (IEnumerable)((IParentNode)element).Children) { Forget(item); } } internal void MarkDirty() { OnDomChanged?.Invoke(); } internal void RequestFocus(IElement element) { OnFocusRequested?.Invoke(element); } internal void RequestScrollToEnd(IElement element) { OnScrollToEnd?.Invoke(element); } internal JsElement Wrap(IElement element) { if (element == null) { return null; } if (_wrappers.TryGetValue(element, out var value)) { return value; } JsElement jsElement = new JsElement(this, element); _wrappers[element] = jsElement; return jsElement; } internal JsValue WrapAll(IEnumerable elements) { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown List list = new List(); if (elements != null) { foreach (IElement element in elements) { list.Add(JsValue.FromObject(Engine, (object)Wrap(element))); } } return (JsValue)new JsArray(Engine, list.ToArray()); } internal void Run(string source, string fileName) { if (string.IsNullOrWhiteSpace(source)) { return; } WarnAboutAwaitedFetch(source, fileName); try { Engine.Execute(source, fileName); Instance log = Core.Log; if (log != null) { log.Msg($"[Sideload] {fileName} executed ({source.Length} chars)."); } } catch (Exception e) { _failed = true; LastError = Describe(e); Instance log2 = Core.Log; if (log2 != null) { log2.Error("[Sideload] " + fileName + " failed: " + LastError); } Diagnostics?.Invoke(_appId, "exception", null, fileName + ": " + LastError); } } private static void WarnAboutAwaitedFetch(string source, string fileName) { if (source.IndexOf("fetch(", StringComparison.Ordinal) >= 0 && Regex.IsMatch(source, "\bawait\b")) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] " + fileName + " uses both `await` and `fetch(`. Awaiting a PENDING promise freezes the game on this engine - it blocks the main thread that would settle it. Use `fetch(url).then(res => ...)` instead. Awaiting an already-settled promise, such as `res.text()`, is safe."); } } } internal string Evaluate(string source, out bool failed) { failed = false; if (string.IsNullOrWhiteSpace(source)) { return ""; } try { JsValue val = Engine.Evaluate(source, (string)null); return JsValueExtensions.IsUndefined(val) ? "undefined" : Describe(val); } catch (Exception e) { failed = true; LastError = Describe(e); return LastError; } } private string Describe(JsValue value) { try { if (JsValueExtensions.IsNull(value)) { return "null"; } if (JsValueExtensions.IsString(value) || JsValueExtensions.IsNumber(value) || JsValueExtensions.IsBoolean(value)) { return ((object)value).ToString(); } JsValue val = JsValueExtensions.Call(Engine.Evaluate("JSON.stringify", (string)null), JsValue.Undefined, value); return JsValueExtensions.IsUndefined(val) ? ((object)value).ToString() : JsValueExtensions.AsString(val); } catch { return ((object)value).ToString(); } } internal void AddListener(IElement element, string type, JsValue handler) { if (element != null && !string.IsNullOrEmpty(type) && !(handler == (JsValue)null) && JsValueExtensions.IsObject(handler)) { if (!_listeners.TryGetValue(element, out var value)) { value = (_listeners[element] = new Dictionary>(StringComparer.OrdinalIgnoreCase)); } if (!value.TryGetValue(type, out var value2)) { value2 = (value[type] = new List()); } value2.Add(handler); } } internal void RemoveListener(IElement element, string type, JsValue handler) { if (element != null && !(handler == (JsValue)null) && _listeners.TryGetValue(element, out var value) && value.TryGetValue(type, out var value2)) { value2.RemoveAll((JsValue h) => h == handler || object.Equals(h, handler)); } } internal IEnumerable ElementsListeningFor(string type) { foreach (KeyValuePair>> listener in _listeners) { if (listener.Value.TryGetValue(type, out var value) && value.Count > 0) { yield return listener.Key; } } } internal JsEvent Dispatch(IElement target, string type, string value = "", string key = "", string source = "") { JsEvent jsEvent = new JsEvent(type, Wrap(target)) { Value = (value ?? ""), Key = (key ?? ""), Source = (source ?? "") }; if (_failed || target == null) { return jsEvent; } for (IElement val = target; val != null; val = ((INode)val).ParentElement) { if (_listeners.TryGetValue(val, out var value2) && value2.TryGetValue(type, out var value3) && value3.Count > 0) { jsEvent.CurrentTarget = Wrap(val); JsValue[] array = value3.ToArray(); foreach (JsValue callback in array) { Invoke(callback, type + " handler on <" + val.LocalName + ">", JsValue.FromObject(Engine, (object)jsEvent)); if (jsEvent.PropagationStopped) { return jsEvent; } } } } return jsEvent; } internal void Tick(float deltaSeconds) { if (_failed) { return; } try { _fetch?.Settle(); } catch (Exception e) { Report("settling a fetch", e); } try { _promises?.Pump(); } catch (Exception e2) { Report("a pending promise", e2); } if (_timers.Count == 0) { return; } double num = (double)deltaSeconds * 1000.0; Timer[] array = _timers.ToArray(); foreach (Timer timer in array) { if (timer.Cancelled) { continue; } timer.Remaining -= num; if (!(timer.Remaining > 0.0)) { if (timer.Interval > 0.0) { timer.Remaining += timer.Interval; } else { timer.Cancelled = true; } Invoke(timer.Callback, $"timer {timer.Id}"); } } _timers.RemoveAll((Timer t) => t.Cancelled); } private int AddTimer(JsValue callback, double delayMs, bool repeating) { if (callback == (JsValue)null || !JsValueExtensions.IsObject(callback)) { return 0; } Timer timer = new Timer { Id = _nextTimerId++, Callback = callback, Remaining = Math.Max(delayMs, 0.0), Interval = (repeating ? Math.Max(delayMs, 16.0) : 0.0) }; _timers.Add(timer); return timer.Id; } private void ClearTimer(double id) { foreach (Timer timer in _timers) { if (timer.Id == (int)id) { timer.Cancelled = true; } } } private void Invoke(JsValue callback, string what, params JsValue[] args) { try { Engine.Invoke(callback, (object[])args); } catch (Exception e) { LastError = Describe(e); Instance log = Core.Log; if (log != null) { log.Error("[Sideload] " + what + " failed: " + LastError); } Diagnostics?.Invoke(_appId, "exception", null, what + " failed: " + LastError); } } private void Report(string what, Exception e) { LastError = Describe(e); Fault(what + " failed: " + LastError); } private void Fault(string line) { Instance log = Core.Log; if (log != null) { log.Error("[Sideload] [" + _appId + "] " + line); } Diagnostics?.Invoke(_appId, "error", null, "[" + _appId + "] " + line); } private void Bind() { Engine.SetValue("document", new JsDocument(this, _document)); _bridge = new Bridge(this, _appId); Engine.SetValue("s1", _bridge); Engine.SetValue("console", new Console(_appId)); _promises = new Promises(Engine, delegate(string message) { Fault("unhandled promise rejection: " + message); }); _fetch = new FetchApi(Engine, _appId, _promises, delegate(string line) { Fault(line); }); _fetch.Install(); Engine.SetValue>("setTimeout", (Func)((JsValue fn, double ms) => AddTimer(fn, ms, repeating: false))); Engine.SetValue>("setInterval", (Func)((JsValue fn, double ms) => AddTimer(fn, ms, repeating: true))); Engine.SetValue>("clearTimeout", (Action)ClearTimer); Engine.SetValue>("clearInterval", (Action)ClearTimer); } private static IEnumerable BothCasings(MemberInfo member) { string name = member.Name; if (!string.IsNullOrEmpty(name)) { string camel = (char.IsUpper(name[0]) ? (char.ToLowerInvariant(name[0]) + name.Substring(1)) : name); yield return camel; if (camel != name) { yield return name; } } } private static string Describe(Exception e) { JavaScriptException ex = (JavaScriptException)(object)((e is JavaScriptException) ? e : null); if (ex != null) { return $"{((Exception)(object)ex).Message} ({((Location)ex.Location).Source}:{((Location)ex.Location).Start.Line}:{((Location)ex.Location).Start.Column})"; } ParserException ex2 = (ParserException)(object)((e is ParserException) ? e : null); if (ex2 != null) { return "syntax error: " + ((Exception)(object)ex2).Message; } return e.ToString(); } } } namespace Sideload.Phone { internal static class AppIconSprite { private const int Fallback = 128; internal static Sprite For(AppRegistration reg) { if ((Object)(object)reg.IconSprite != (Object)null) { return reg.IconSprite; } reg.IconSprite = FromBundle(reg) ?? Generated(reg.Id); return reg.IconSprite; } private static Sprite FromBundle(AppRegistration reg) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown byte[] array = reg.Bundle?.ReadBytes("icon.png"); if (array == null || array.Length == 0) { return null; } try { Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false) { filterMode = (FilterMode)1 }; if (!ImageConversion.LoadImage(val, Il2CppStructArray.op_Implicit(array))) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] '" + reg.Id + "' has an icon.png that is not a readable PNG."); } return null; } return Finish(val); } catch (Exception ex) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("[Sideload] loading icon.png for '" + reg.Id + "' failed: " + ex.Message); } return null; } } private static Sprite Generated(string id) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) Color32 val = FromHue((float)(Hash(id) % 360) / 360f); Texture2D val2 = new Texture2D(128, 128, (TextureFormat)4, false) { filterMode = (FilterMode)1 }; Color32 val3 = default(Color32); ((Color32)(ref val3))..ctor((byte)0, (byte)0, (byte)0, (byte)0); for (int i = 0; i < 128; i++) { for (int j = 0; j < 128; j++) { val2.SetPixel(j, i, Color32.op_Implicit(Inside((float)j + 0.5f, (float)i + 0.5f, 26f) ? val : val3)); } } val2.Apply(); return Finish(val2); } private static bool Inside(float x, float y, float r) { float num = Mathf.Clamp(x, r, 128f - r); float num2 = Mathf.Clamp(y, r, 128f - r); float num3 = x - num; float num4 = y - num2; return num3 * num3 + num4 * num4 <= r * r; } private static Sprite Finish(Texture2D tex) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) ((Object)tex).hideFlags = (HideFlags)32; Sprite val = Sprite.Create(tex, new Rect(0f, 0f, (float)((Texture)tex).width, (float)((Texture)tex).height), new Vector2(0.5f, 0.5f), 100f); if ((Object)(object)val != (Object)null) { ((Object)val).hideFlags = (HideFlags)32; } return val; } private static int Hash(string s) { int num = 17; string text = s ?? ""; foreach (char c in text) { num = (num * 31 + c) & 0x7FFFFFF; } return num; } private static Color32 FromHue(float h) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) return Color32.op_Implicit(Color.HSVToRGB(h, 0.62f, 0.86f)); } } [HarmonyPatch(typeof(HomeScreen), "Start")] internal static class HomeScreenPatch { private static readonly List _hosts = new List(); private static HomeScreen _home; internal static IReadOnlyList Hosts => _hosts; internal static void LogIconRow() { if ((Object)(object)_home == (Object)null) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload/probe] no HomeScreen to inspect."); } return; } Transform appIconContainer = (Transform)(object)_home.appIconContainer; if ((Object)(object)appIconContainer == (Object)null) { Instance log2 = Core.Log; if (log2 != null) { log2.Warning("[Sideload/probe] HomeScreen exposes no icon container."); } return; } List list = new List(); for (int i = 0; i < appIconContainer.childCount; i++) { list.Add(((Object)appIconContainer.GetChild(i)).name); } Instance log3 = Core.Log; if (log3 != null) { log3.Msg($"[Sideload/probe] real AppIcons: {appIconContainer.childCount} child(ren) -> {string.Join(", ", list)}"); } } private static void Postfix(HomeScreen __instance) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)__instance == (Object)null) { return; } _home = __instance; Scene activeScene = SceneManager.GetActiveScene(); if (!string.Equals(((Scene)(ref activeScene)).name, "Main", StringComparison.OrdinalIgnoreCase)) { return; } if (_hosts.Count > 0 && _hosts[0].IsAlive) { Instance log = Core.Log; if (log != null) { log.Msg("[Sideload] apps are already live on a phone - skipping this second HomeScreen."); } return; } _hosts.Clear(); if (Registry.Apps.Count == 0) { Instance log2 = Core.Log; if (log2 != null) { log2.Msg("[Sideload] no apps registered - nothing to put on the phone."); } return; } foreach (AppRegistration app in Registry.Apps) { try { PhoneAppHost phoneAppHost = PhoneAppHost.Spawn(__instance, app); if (phoneAppHost != null) { _hosts.Add(phoneAppHost); } } catch (Exception value) { Instance log3 = Core.Log; if (log3 != null) { log3.Error($"[Sideload] spawning '{app.Id}' failed: {value}"); } } } Instance log4 = Core.Log; if (log4 != null) { log4.Msg($"[Sideload] {_hosts.Count}/{Registry.Apps.Count} app(s) live on the phone."); } } } internal static class OrientationStore { private static readonly Dictionary _portraitById = new Dictionary(StringComparer.OrdinalIgnoreCase); private static bool _loaded; private static string Path => System.IO.Path.Combine(MelonEnvironment.UserDataDirectory, "Sideload", "orientation.json"); internal static bool? Remembered(AppRegistration reg) { if (reg == null) { return null; } Load(); if (!_portraitById.TryGetValue(reg.Id, out var value)) { return null; } if (!reg.Supports(value)) { return null; } return value; } internal static void Remember(string appId, bool portrait) { if (!string.IsNullOrWhiteSpace(appId)) { Load(); if (!_portraitById.TryGetValue(appId, out var value) || value != portrait) { _portraitById[appId] = portrait; Save(); } } } private static void Load() { if (_loaded) { return; } _loaded = true; try { if (!File.Exists(Path)) { return; } foreach (KeyValuePair item in MiniJson.ParseObject(File.ReadAllText(Path))) { _portraitById[item.Key] = string.Equals(item.Value, "portrait", StringComparison.OrdinalIgnoreCase); } } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] remembered orientations unreadable (" + Path + "): " + ex.Message); } } } private static void Save() { try { Directory.CreateDirectory(System.IO.Path.GetDirectoryName(Path) ?? "."); Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (KeyValuePair item in _portraitById) { dictionary[item.Key] = (item.Value ? "portrait" : "landscape"); } File.WriteAllText(Path, MiniJson.WriteObject(dictionary)); } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] could not write remembered orientations: " + ex.Message); } } } } internal sealed class PhoneAppHost { private readonly AppRegistration _reg; private GameObject _panel; private GameObject _container; private GameObject _icon; private WebView _view; private Transform _appsCanvas; private Transform _badge; private Text _badgeText; private Action _closeAppsHandler; private ExitDelegate _exitHandler; internal string Id => _reg.Id; internal bool IsOpen { get { if ((Object)(object)_panel != (Object)null && _panel.activeInHierarchy) { return (Object)(object)Phone.ActiveApp == (Object)(object)_panel; } return false; } } internal bool IsShowing { get { if (IsOpen && PlayerSingleton.InstanceExists) { return PlayerSingleton.Instance.IsOpen; } return false; } } internal bool IsAlive => (Object)(object)_panel != (Object)null; internal bool CanTurn => _reg.CanTurn; private PhoneAppHost(AppRegistration reg) { _reg = reg; } internal static PhoneAppHost Spawn(HomeScreen home, AppRegistration reg) { PhoneAppHost phoneAppHost = new PhoneAppHost(reg); if (!phoneAppHost.Build(home)) { return null; } return phoneAppHost; } private bool Build(HomeScreen home) { //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Expected O, but got Unknown //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Expected O, but got Unknown //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) Transform val = (((Object)(object)((Component)home).transform.parent != (Object)null) ? ((Component)home).transform.parent.Find("AppsCanvas") : null); if ((Object)(object)val == (Object)null) { Instance log = Core.Log; if (log != null) { log.Error("[Sideload] AppsCanvas not found - '" + _reg.Id + "' not spawned."); } return false; } string text = "Sideload_" + _reg.Id; Transform val2 = val.Find(text); if ((Object)(object)val2 != (Object)null) { Object.DestroyImmediate((Object)(object)((Component)val2).gameObject); } _appsCanvas = val; bool? flag = OrientationStore.Remembered(_reg); if (flag.HasValue) { _reg.Portrait = flag.Value; } _panel = new GameObject(text); ((Transform)_panel.AddComponent()).SetParent(val, false); if (!ApplyOrientation()) { Instance log2 = Core.Log; if (log2 != null) { log2.Error("[Sideload] no vanilla app panel to measure - '" + _reg.Id + "' not spawned."); } return false; } _container = new GameObject("Container"); RectTransform val3 = _container.AddComponent(); ((Transform)val3).SetParent(_panel.transform, false); val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; val3.pivot = new Vector2(0.5f, 0.5f); val3.anchoredPosition = Vector2.zero; val3.sizeDelta = Vector2.zero; _view = WebView.Mount(val3, _reg.Bundle, _reg.Id); _reg.OrientationChanged = OnOrientationChanged; _panel.SetActive(true); _container.SetActive(false); SpawnIcon(home); SubscribeShellEvents(); Instance log3 = Core.Log; if (log3 != null) { log3.Msg("[Sideload] '" + _reg.Id + "' spawned on the phone."); } return true; } private bool ApplyOrientation() { if ((Object)(object)_panel == (Object)null || (Object)(object)_appsCanvas == (Object)null) { return false; } Transform val = FindPanelTemplate(_appsCanvas, _reg.Portrait) ?? FindPanelTemplate(_appsCanvas, !_reg.Portrait); if ((Object)(object)val == (Object)null) { return false; } CopyRect(((Component)val).GetComponent(), _panel.GetComponent()); return true; } private static Transform FindPanelTemplate(Transform appsCanvas, bool portrait) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < appsCanvas.childCount; i++) { Transform child = appsCanvas.GetChild(i); if (((Object)child).name.StartsWith("Sideload_", StringComparison.Ordinal)) { continue; } RectTransform component = ((Component)child).GetComponent(); if (!((Object)(object)component == (Object)null)) { Rect rect = component.rect; if (!(((Rect)(ref rect)).width < 1f) && !(((Rect)(ref rect)).height < 1f) && ((Rect)(ref rect)).height > ((Rect)(ref rect)).width == portrait) { return child; } } } return null; } private void OnOrientationChanged() { try { if (ApplyOrientation()) { OrientationStore.Remember(_reg.Id, _reg.Portrait); if (IsOpen && PlayerSingleton.InstanceExists) { PlayerSingleton.Instance.SetIsHorizontal(!_reg.Portrait); PlayerSingleton.Instance.SetLookOffsetMultiplier(_reg.Portrait ? 1f : 0.6f); } Canvas.ForceUpdateCanvases(); _view?.QueueResize(); } } catch (Exception ex) { Instance log = Core.Log; if (log != null) { log.Error("[Sideload] turning '" + _reg.Id + "' failed: " + ex.Message); } } } private static void CopyRect(RectTransform from, RectTransform to) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)from == (Object)null) && !((Object)(object)to == (Object)null)) { to.anchorMin = from.anchorMin; to.anchorMax = from.anchorMax; to.pivot = from.pivot; to.anchoredPosition3D = from.anchoredPosition3D; to.sizeDelta = from.sizeDelta; ((Transform)to).localScale = ((Transform)from).localScale; ((Transform)to).localRotation = ((Transform)from).localRotation; } } private void SpawnIcon(HomeScreen home) { Transform appIconContainer = (Transform)(object)home.appIconContainer; GameObject appIconPrefab = home.appIconPrefab; if ((Object)(object)appIconContainer == (Object)null || (Object)(object)appIconPrefab == (Object)null) { Instance log = Core.Log; if (log != null) { log.Warning("[Sideload] HomeScreen exposes no icon prefab or container - '" + _reg.Id + "' has no home-screen icon."); } return; } string text = "SideloadIcon_" + _reg.Id; Transform val = appIconContainer.Find(text); if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)((Component)val).gameObject); } _icon = Object.Instantiate(appIconPrefab, appIconContainer); ((Object)_icon).name = text; _icon.transform.SetAsLastSibling(); _icon.SetActive(true); Dress(_icon.transform, AppIconSprite.For(_reg), _reg.IconLabel, _reg.Id); Button component = _icon.GetComponent