Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of Reflash v1.0.0
Reflash.dll
Decompiled 3 days ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Net.Sockets; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Cartel; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.Delivery; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.Effects; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Levelling; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.Messaging; using Il2CppScheduleOne.Money; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Relation; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.Property; using Il2CppScheduleOne.Quests; using Il2CppScheduleOne.SpecialCustomers; using Il2CppScheduleOne.StationFramework; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Phone; using Il2CppScheduleOne.UI.Phone.ContactsApp; using Il2CppScheduleOne.UI.Phone.Delivery; using Il2CppScheduleOne.UI.Phone.Map; using Il2CppScheduleOne.UI.Phone.Messages; using Il2CppScheduleOne.UI.Phone.ProductManagerApp; using Il2CppScheduleOne.UI.Relations; using Il2CppScheduleOne.UI.Shop; using Il2CppScheduleOne.Vehicles; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using Reflash; using Reflash.Companion; using Reflash.Game; using Reflash.Hijack; using Reflash.Screens; using Reflash.Wire; using Sideload.Api; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "Reflash", "1.0.0", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-Reflash")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonOptionalDependencies(new string[] { "Sideload" })] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("DooDesch")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © DooDesch")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+9650be7dea9a0257c88aaef32a3f5d4bf38e4e99")] [assembly: AssemblyProduct("Reflash")] [assembly: AssemblyTitle("Reflash")] [assembly: NeutralResourcesLanguage("en-US")] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially created with the assistance of Generative AI.")] [assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic/OpenAI")] [assembly: AssemblyVersion("1.0.0.0")] [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 DooDesch { internal static class ModVersion { internal const string Current = "1.0.0"; } } namespace Sideload.Api { public static class Apps { private static bool _bound; private static int _probeAttempts; private static readonly List<Action> _pending = new List<Action>(); private static Action<string, string, string, string, Assembly> _registerApp; private static Action<string, string, Func<string, string, string>> _handle; private static Action<string, string, string> _emit; private static Action<string, string> _allowHost; private static Action<string, string> _declareOrientations; private static Action<string, int> _setBadge; private static Action<string, string, string> _notify; private static Action<string, string, string, float> _notifyFor; private static Func<string, bool> _isOnScreen; private static Action<string, string, byte[]> _setImage; private static Action<string, bool> _setIconHidden; private static Action<string, bool> _setAppOpen; private static Func<string, bool> _isAppOpen; private static Func<bool, bool> _setPhoneRaised; private static Func<bool> _isPhoneRaised; private static Action<string, string, Func<string, string, bool>> _claimKeys; private static Func<object, string, string, Assembly, float, bool> _mountSurface; private static Action<string> _unmountSurface; private static Func<string, bool> _isSurfaceMounted; public static bool Available { get { EnsureBound(); return _bound; } } internal static bool HasSurfaces { get { EnsureBound(); return _mountSurface != null; } } internal static bool HasKeys { get { EnsureBound(); return _claimKeys != null; } } internal static bool HasPhone { get { EnsureBound(); return _setPhoneRaised != null; } } internal static bool HasOpen { get { EnsureBound(); if (_setAppOpen != null) { return _setIconHidden != null; } return false; } } public static AppHandle Register(string id, string bundlePrefix, string title = null, string iconLabel = null, Assembly hostAssembly = null) { AppHandle result = new AppHandle(id); if (string.IsNullOrEmpty(id)) { return result; } Assembly asm = hostAssembly ?? Assembly.GetCallingAssembly(); string t = title; string il = iconLabel; string prefix = bundlePrefix; EnsureBound(); if (_registerApp != null) { _registerApp(id, t, il, prefix, asm); } else { _pending.Add(delegate { _registerApp?.Invoke(id, t, il, prefix, asm); }); } return result; } internal static void WhenBound(Action work) { if (work != null) { EnsureBound(); if (_bound) { work(); } else { _pending.Add(work); } } } internal static void HandleCall(string appId, string name, Func<string, string> handler) { if (_handle != null && handler != null) { _handle(appId, name, (string app, string argument) => handler(argument)); } } internal static void EmitEvent(string appId, string name, string payload) { _emit?.Invoke(appId, name, payload); } internal static void AllowNetHost(string appId, string host) { _allowHost?.Invoke(appId, host); } internal static void Orient(string appId, string orientations) { _declareOrientations?.Invoke(appId, orientations); } internal static void Badge(string appId, int count) { _setBadge?.Invoke(appId, count); } internal static void Notify(string appId, string title, string subtitle, float seconds) { if (_notifyFor != null) { _notifyFor(appId, title, subtitle, seconds); } else if (_notify != null) { _notify(appId, title, subtitle); } } internal static bool OnScreen(string appId) { if (_isOnScreen != null) { return _isOnScreen(appId); } return false; } internal static void SetImage(string appId, string name, byte[] png) { _setImage?.Invoke(appId, name, png); } internal static void HideIcon(string appId, bool hidden) { _setIconHidden?.Invoke(appId, hidden); } internal static void OpenApp(string appId, bool open) { _setAppOpen?.Invoke(appId, open); } internal static bool AppIsOpen(string appId) { if (_isAppOpen != null) { return _isAppOpen(appId); } return false; } internal static bool RaisePhone(bool raised) { if (_setPhoneRaised != null) { return _setPhoneRaised(raised); } return false; } internal static bool PhoneIsRaised() { if (_isPhoneRaised != null) { return _isPhoneRaised(); } return false; } internal static void ClaimKeys(string appId, string keys, Func<string, string, bool> handler) { _claimKeys?.Invoke(appId, keys, handler); } internal static bool MountSurface(object rect, string id, string prefix, Assembly host, float shortSide) { if (_mountSurface != null) { return _mountSurface(rect, id, prefix, host, shortSide); } return false; } internal static void UnmountSurface(string id) { _unmountSurface?.Invoke(id); } internal static bool SurfaceIsMounted(string id) { if (_isSurfaceMounted != null) { return _isSurfaceMounted(id); } return false; } private static void EnsureBound() { if (_bound) { return; } try { Type type = FindBridge(_probeAttempts++ % 30 == 0); if (type == null || (type.GetField("AbiVersion", BindingFlags.Static | BindingFlags.Public)?.GetValue(null) is int num && num < 1)) { return; } _registerApp = Get<Action<string, string, string, string, Assembly>>(type, "RegisterApp"); if (_registerApp == null) { return; } _handle = Get<Action<string, string, Func<string, string, string>>>(type, "Handle"); _emit = Get<Action<string, string, string>>(type, "Emit"); _allowHost = Get<Action<string, string>>(type, "AllowHost"); _declareOrientations = Get<Action<string, string>>(type, "DeclareOrientations"); _setBadge = Get<Action<string, int>>(type, "SetBadge"); _notify = Get<Action<string, string, string>>(type, "Notify"); _notifyFor = Get<Action<string, string, string, float>>(type, "NotifyFor"); _isOnScreen = Get<Func<string, bool>>(type, "IsAppOnScreen"); _setImage = Get<Action<string, string, byte[]>>(type, "SetImage"); _setIconHidden = Get<Action<string, bool>>(type, "SetIconHidden"); _setAppOpen = Get<Action<string, bool>>(type, "SetAppOpen"); _isAppOpen = Get<Func<string, bool>>(type, "IsAppOpen"); _setPhoneRaised = Get<Func<bool, bool>>(type, "SetPhoneRaised"); _isPhoneRaised = Get<Func<bool>>(type, "IsPhoneRaised"); _claimKeys = Get<Action<string, string, Func<string, string, bool>>>(type, "ClaimKeys"); _mountSurface = Get<Func<object, string, string, Assembly, float, bool>>(type, "MountSurface"); _unmountSurface = Get<Action<string>>(type, "UnmountSurface"); _isSurfaceMounted = Get<Func<string, bool>>(type, "IsSurfaceMounted"); _bound = true; for (int i = 0; i < _pending.Count; i++) { try { _pending[i](); } catch { } } _pending.Clear(); } catch { } } private static T Get<T>(Type t, string field) where T : class { return t.GetField(field, BindingFlags.Static | BindingFlags.Public)?.GetValue(null) as T; } private static Type FindBridge(bool scan) { Type type = Type.GetType("Sideload.Bridge.SideloadBridge, Sideload", throwOnError: false); if (type != null || !scan) { return type; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { type = assembly.GetType("Sideload.Bridge.SideloadBridge", throwOnError: false); if (type != null) { return type; } } catch { } } return null; } } public sealed class AppHandle { private readonly string _id; public string Id => _id; public bool IsOnScreen { get { if (Apps.Available) { return Apps.OnScreen(_id); } return false; } } public bool IsOpen { get { if (Apps.Available) { return Apps.AppIsOpen(_id); } return false; } } public static bool CanOpenProgrammatically { get { if (Apps.Available) { return Apps.HasOpen; } return false; } } public static bool CanClaimKeys { get { if (Apps.Available) { return Apps.HasKeys; } return false; } } internal AppHandle(string id) { _id = id ?? ""; } public AppHandle OnCall(string name, Func<string, string> handler) { string id = _id; Apps.WhenBound(delegate { Apps.HandleCall(id, name, handler); }); return this; } public void Emit(string name, string payload = "") { string id = _id; Apps.WhenBound(delegate { Apps.EmitEvent(id, name, payload); }); } public AppHandle AllowHost(string host) { string id = _id; Apps.WhenBound(delegate { Apps.AllowNetHost(id, host); }); return this; } public AppHandle Orientation(params string[] supported) { string id = _id; string list = ((supported == null) ? "" : string.Join(",", supported)); Apps.WhenBound(delegate { Apps.Orient(id, list); }); return this; } public AppHandle Badge(int count) { string id = _id; Apps.WhenBound(delegate { Apps.Badge(id, count); }); return this; } public AppHandle Notify(string title, string subtitle = "", float seconds = 0f) { string id = _id; Apps.WhenBound(delegate { Apps.Notify(id, title, subtitle, seconds); }); return this; } public AppHandle Image(string name, byte[] png) { string id = _id; Apps.WhenBound(delegate { Apps.SetImage(id, name, png); }); return this; } public AppHandle Icon(bool visible) { Apps.HideIcon(_id, !visible); return this; } public AppHandle NoIcon() { string id = _id; Apps.WhenBound(delegate { Apps.HideIcon(id, hidden: true); }); return this; } public AppHandle Open() { string id = _id; Apps.WhenBound(delegate { Apps.OpenApp(id, open: true); }); return this; } public AppHandle Close() { string id = _id; Apps.WhenBound(delegate { Apps.OpenApp(id, open: false); }); return this; } public bool Show() { if (!Apps.RaisePhone(raised: true)) { return false; } Apps.OpenApp(_id, open: true); return true; } public AppHandle OnKey(string keys, Func<string, bool> handler) { string id = _id; Apps.WhenBound(delegate { Apps.ClaimKeys(id, keys, (handler == null) ? null : ((Func<string, string, bool>)((string _, string key) => handler(key)))); }); return this; } public AppHandle Hide() { string id = _id; Apps.WhenBound(delegate { Apps.OpenApp(id, open: false); Apps.RaisePhone(raised: false); }); return this; } } public static class Surfaces { public static bool Available { get { if (Apps.Available) { return Apps.HasSurfaces; } return false; } } public static SurfaceHandle Mount(object hostRect, string id, string bundlePrefix, float designShortSide = 0f, Assembly hostAssembly = null) { SurfaceHandle result = new SurfaceHandle(id); if (string.IsNullOrEmpty(id) || hostRect == null) { return result; } Assembly host = hostAssembly ?? Assembly.GetCallingAssembly(); Apps.MountSurface(hostRect, id, bundlePrefix, host, designShortSide); return result; } public static void Unmount(string id) { Apps.UnmountSurface(id); } public static bool IsMounted(string id) { return Apps.SurfaceIsMounted(id); } } public sealed class SurfaceHandle { private readonly string _id; public string Id => _id; internal SurfaceHandle(string id) { _id = id; } public SurfaceHandle OnCall(string name, Func<string, string> handler) { string id = _id; Apps.WhenBound(delegate { Apps.HandleCall(id, name, handler); }); return this; } public SurfaceHandle Emit(string name, string payload = "") { string id = _id; Apps.WhenBound(delegate { Apps.EmitEvent(id, name, payload); }); return this; } public SurfaceHandle AllowHost(string host) { string id = _id; Apps.WhenBound(delegate { Apps.AllowNetHost(id, host); }); return this; } public SurfaceHandle Image(string name, byte[] png) { string id = _id; Apps.WhenBound(delegate { Apps.SetImage(id, name, png); }); return this; } public void Unmount() { Apps.UnmountSurface(_id); } } public static class PhoneScreen { public static bool IsRaised { get { if (Apps.Available) { return Apps.PhoneIsRaised(); } return false; } } public static bool Available { get { if (Apps.Available) { return Apps.HasPhone; } return false; } } public static bool Raise() { return Apps.RaisePhone(raised: true); } public static bool Lower() { return Apps.RaisePhone(raised: false); } } } namespace Reflash { public sealed class Core : MelonMod { internal static Instance Log; private readonly Pulse _pulse = new Pulse(); private Runner _companion; private static Runner _running; internal static bool CompanionWatching { get { if (_running != null && _running.Running) { return _running.Server.Devices.Count > 0; } return false; } } internal static string CompanionPairingUrl() { if (_running != null && _running.Running) { return "http://" + _running.Server.LanIp + ":" + _running.Server.Port + "/#t=" + _running.Server.Devices.CurrentToken(Clock.Now); } return ""; } public override void OnInitializeMelon() { Log = ((MelonBase)this).LoggerInstance; Prefs.Load(); if (!AppHandle.CanOpenProgrammatically) { Log.Error(Apps.Available ? "Sideload is too old - Reflash needs 1.1.0 or newer for iconless apps. The vanilla phone is untouched." : "Sideload is not installed - Reflash does nothing without it. The vanilla phone is untouched."); return; } RegisterReplacements(); AppHijack.Arm(Prefs.TakeOverVanillaApps); if (!Prefs.TakeOverVanillaApps) { Log.Msg("the in-game phone keeps its own apps. The replacements are registered anyway - that is what the companion serves. Set ReplaceVanillaApps to try them in the game."); } SetOpenPatches.ApplyAll(((MelonBase)this).HarmonyInstance); PoiRegistry.Apply(((MelonBase)this).HarmonyInstance); StartCompanion(); } private void RegisterReplacements() { foreach (IAppPort item in AppRoster.All()) { AppHandle handle = Apps.Register(item.Id, "Reflash.Assets." + item.Id, AppRoster.TitleOf(item.Replaces)).NoIcon().Orientation(AppRoster.OrientationsOf(item.Replaces)); Wire(item, handle); AppHijack.Register(item.Replaces, handle); } } private void StartCompanion() { AppHandle handle = Apps.Register("reflash-connect", "Reflash.Assets.reflash-connect", "Connect", "Connect").Orientation("landscape", "portrait"); AppHijack.Watch(handle); _companion = new Runner(); _running = _companion; Wire(new ConnectApp(_companion, handle), handle); _companion.FollowPreference(); } private void Wire(IAppPort port, AppHandle handle) { if (port is INeedsAppHandle needsAppHandle) { needsAppHandle.UseHandle(handle); } handle.OnCall(port.Id + ".state", delegate(string raw) { _pulse.Heard(port); return port.State(raw); }).OnCall(port.Id + ".act", (string raw) => port.Act(Cmd.Parse(raw))); _pulse.Add(port, handle); } public override void OnUpdate() { _pulse.Tick(Time.unscaledTime); try { _companion?.Pump(); } catch (Exception ex) { Log.Error("companion pump failed: " + ex.Message); } } public override void OnApplicationQuit() { Log.Msg("the game is quitting - shutting the companion down."); _companion?.Shutdown(); } } internal static class Prefs { private const string Category = "Reflash_01_Main"; private static MelonPreferences_Category _category; private static MelonPreferences_Entry<bool> _companion; private static MelonPreferences_Entry<int> _port; private static MelonPreferences_Entry<bool> _takeOver; internal static bool TakeOverVanillaApps => _takeOver?.Value ?? false; internal static bool CompanionEnabled => _companion?.Value ?? false; internal static int CompanionPort { get { int num = _port?.Value ?? 6180; if (num >= 1024 && num <= 65535) { return num; } return 6180; } } internal static void Load() { _category = MelonPreferences.CreateCategory("Reflash_01_Main", "Reflash"); _takeOver = _category.CreateEntry<bool>("ReplaceVanillaApps", false, (string)null, "Phone icons open the Reflash apps instead of the original screens. Off: only the Connect app is added.", false, false, (ValueValidator)null, (string)null); _companion = _category.CreateEntry<bool>("Companion", false, (string)null, "Serves the apps to a real phone on your network. Set by the Connect app. Opens a port; pairing needs the code on screen.", false, false, (ValueValidator)null, (string)null); _port = _category.CreateEntry<int>("CompanionPort", 6180, (string)null, "Port for the phone server. Change it only if another program uses it.", false, false, (ValueValidator)null, (string)null); } internal static void SetTakeOverVanillaApps(bool on) { if (_takeOver != null && _takeOver.Value != on) { _takeOver.Value = on; Save(); } } internal static void SetCompanionEnabled(bool on) { if (_companion != null && _companion.Value != on) { _companion.Value = on; Save(); } } private static void Save() { try { MelonPreferences_Category category = _category; if (category != null) { category.SaveToFile(false); } } catch (Exception ex) { Core.Log.Warning("could not save settings: " + ex.Message); } } } internal sealed class Pulse { private sealed class Entry { internal IAppPort Port; internal AppHandle Handle; internal int PushedRevision = int.MinValue; internal int PushedBadge = -1; internal bool Missed; } private const float IntervalSeconds = 0.25f; private readonly List<Entry> _entries = new List<Entry>(); private float _next; internal void Add(IAppPort port, AppHandle handle) { if (port != null && handle != null) { Entry item = new Entry { Port = port, Handle = handle }; _entries.Add(item); } } internal void Heard(IAppPort port) { for (int i = 0; i < _entries.Count; i++) { Entry entry = _entries[i]; if (entry.Port == port) { entry.Missed = false; try { entry.PushedRevision = port.Revision; break; } catch (Exception ex) { Core.Log.Warning("'" + port.Id + "' revision unreadable while its page read state: " + ex.Message); break; } } } } internal void Tick(float unscaledTime) { if (unscaledTime < _next) { return; } _next = unscaledTime + 0.25f; for (int i = 0; i < _entries.Count; i++) { Entry entry = _entries[i]; try { TickOne(entry); } catch (Exception ex) { Core.Log.Error("pulse for '" + entry.Port.Id + "' failed: " + ex.Message); } } } private static void TickOne(Entry e) { if (e.Port is IWarmUp warmUp) { warmUp.WarmUp(); } int badge = e.Port.Badge; int revision = e.Port.Revision; if (badge != e.PushedBadge) { e.PushedBadge = badge; e.Handle.Badge(badge); } bool flag = e.Handle.IsOnScreen || Core.CompanionWatching; if (revision != e.PushedRevision) { bool flag2 = e.PushedRevision == int.MinValue; e.PushedRevision = revision; if (!flag) { e.Missed |= !flag2; return; } } else if (!flag || !e.Missed) { return; } e.Missed = false; e.Handle.Emit(e.Port.Id + ".changed", revision.ToString()); } } } namespace Reflash.Wire { internal readonly struct Cmd { internal const char Separator = '\u001f'; private readonly string[] _fields; internal string Op { get; } internal int Count { get { string[] fields = _fields; if (fields == null) { return 0; } return fields.Length; } } internal bool IsEmpty => string.IsNullOrEmpty(Op); private Cmd(string op, string[] fields) { Op = op; _fields = fields; } internal static Cmd Parse(string raw) { if (string.IsNullOrEmpty(raw)) { return default(Cmd); } string[] array = raw.Split('\u001f'); string text = array[0].Trim(); if (text.Length == 0) { return default(Cmd); } string[] array2 = new string[array.Length - 1]; Array.Copy(array, 1, array2, 0, array2.Length); return new Cmd(text, array2); } internal string Str(int index) { if (_fields == null || index < 0 || index >= _fields.Length) { return null; } return _fields[index]; } internal bool Int(int index, out int value) { value = 0; string text = Str(index); if (text != null) { return int.TryParse(text.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out value); } return false; } internal bool Num(int index, out float value) { value = 0f; string text = Str(index); if (text != null) { return float.TryParse(text.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out value); } return false; } internal bool Flag(int index, out bool value) { value = false; string text = Str(index)?.Trim(); if (text == null) { return false; } if (text == "1" || string.Equals(text, "true", StringComparison.OrdinalIgnoreCase)) { value = true; return true; } if (text == "0" || string.Equals(text, "false", StringComparison.OrdinalIgnoreCase)) { value = false; return true; } return false; } } internal static class Reply { internal const string Ok = "ok"; internal const string BadArgs = "err:bad-args"; internal const string NotFound = "err:not-found"; internal const string Stale = "err:stale"; internal const string Refused = "err:refused"; internal const string NoGame = "err:no-game"; } internal sealed class Json { private readonly StringBuilder _sb = new StringBuilder(); private bool _empty = true; private string _closed; private Json(char open) { _sb.Append(open); } internal static Json Object() { return new Json('{'); } internal static Json Array() { return new Json('['); } internal Json Add(string key, string value) { Separate(); Quote(key); _sb.Append(':'); Quote(value); return this; } internal Json Add(string key, long value) { Separate(); Quote(key); _sb.Append(':').Append(value); return this; } internal Json Add(string key, bool value) { Separate(); Quote(key); _sb.Append(':').Append(value ? "true" : "false"); return this; } internal Json Add(string key, double value) { Separate(); Quote(key); _sb.Append(':'); if (double.IsNaN(value) || double.IsInfinity(value)) { _sb.Append("null"); } else { _sb.Append(value.ToString("0.####", CultureInfo.InvariantCulture)); } return this; } internal Json Add(string key, Json value) { Separate(); Quote(key); _sb.Append(':').Append(value.Close()); return this; } internal Json AddNull(string key) { Separate(); Quote(key); _sb.Append(":null"); return this; } internal Json Item(Json value) { Separate(); _sb.Append(value.Close()); return this; } internal Json Item(string value) { Separate(); Quote(value); return this; } internal Json Item(long value) { Separate(); _sb.Append(value); return this; } internal string Close() { if (_closed != null) { return _closed; } _sb.Append((_sb[0] == '{') ? '}' : ']'); return _closed = _sb.ToString(); } public override string ToString() { return Close(); } private void Separate() { if (_closed != null) { throw new InvalidOperationException("this JSON value is already closed and cannot be added to"); } if (!_empty) { _sb.Append(','); } _empty = false; } private void Quote(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('"'); } } internal interface IAppPort { string Id { get; } VanillaApp Replaces { get; } int Revision { get; } int Badge { get; } string State(string section); string Act(Cmd cmd); } internal interface INeedsAppHandle { void UseHandle(AppHandle handle); } internal interface IWarmUp { void WarmUp(); } internal enum VanillaApp { Messages, Map, Delivery, ProductManager, Contacts, DealerManagement, Journal } internal interface IJournalSource { int Revision { get; } List<QuestView> ActiveQuests(); RankView Rank(); string ShowStepOnMap(string questId, int stepIndex); string ToggleTracked(string questId); } internal interface IMessagesSource { int Revision { get; } int Unread { get; } List<ThreadView> Threads(); void WarmFaces(); ThreadDetailView Thread(string npcId); string MarkRead(string npcId); string SetHidden(string npcId, bool hidden); string ChooseReply(string npcId, int index, int seenRevision); string SendCanned(string npcId, int index, int seenRevision); string SendCounterOffer(string productId, int quantity, int price); string PlaceOrder(IReadOnlyList<KeyValuePair<string, int>> quantities); string ChooseDealWindow(int index); string CloseSheet(); void HideVanillaSheets(); } internal interface IProductsSource { int Revision { get; } List<ProductView> Products(); void WarmIcons(); ProductDetailView Product(string productId); string SetListed(string productId, bool listed); string SetFavourite(string productId, bool favourite); string SetPrice(string productId, float price); } internal interface IDeliverySource { int Balance { get; } int Revision { get; } int ActiveCount { get; } List<ShopView> Shops(); void WarmIcons(); List<DeliveryView> Active(); List<DeliveryView> History(); string Order(string shopId, IReadOnlyList<KeyValuePair<string, int>> quantities); string Reorder(string deliveryId); string Fill(string shopId, IReadOnlyList<KeyValuePair<string, int>> quantities); string Choose(string shopId, bool dock, int index); } internal interface IContactsSource { int Revision { get; } List<RegionView> Regions(); GraphView Graph(string regionId); void WarmFaces(string regionId); ContactDetailView Contact(string npcId); string ShowOnMap(string npcId); } internal interface IDealersSource { int Revision { get; } List<DealerView> Dealers(); void WarmIcons(); DealerDetailView Dealer(string dealerId); List<ContactView> AssignableTo(string dealerId); string AddCustomer(string dealerId, string npcId); string RemoveCustomer(string dealerId, string npcId); string SetCut(string dealerId, int percent); } internal interface IMapSource { float LabelsFrom { get; } float LabelsFull { get; } bool ImageReady { get; } int Revision { get; } List<PoiView> Pois(); List<RegionView> Regions(); PoiView Player(); List<PlaceNameView> Labels(); void EnsureImage(); void WarmFaces(); } internal static class Text { private const char Ellipsis0 = '…'; private const char SingleQuoteOpen = '‘'; private const char SingleQuoteClose = '’'; private const char DoubleQuoteOpen = '“'; private const char DoubleQuoteClose = '”'; private const char EnDash = '–'; private const char EmDash = '—'; private const char NonBreakingSpace = '\u00a0'; private const char Bullet = '•'; private const char ArrowRight = '→'; private const char ArrowLeft = '←'; private const char Multiply = '×'; private const char Check = '✓'; private const char HeavyCheck = '✔'; private const char LastDrawable = 'ÿ'; private const int MaxTagLength = 64; internal static string Clean(string value) { if (string.IsNullOrEmpty(value)) { return ""; } StringBuilder stringBuilder = null; for (int i = 0; i < value.Length; i++) { char c = value[i]; string text = Replace(c); if (text == null) { stringBuilder?.Append(c); continue; } if (stringBuilder == null) { stringBuilder = new StringBuilder(value.Length + 8).Append(value, 0, i); } stringBuilder.Append(text); } return stringBuilder?.ToString() ?? value; } internal static string Ellipsis(string value, int max) { string text = Clean(value); if (max <= 3) { return text; } if (text.Length <= max && text.IndexOf('<') < 0) { return text; } if (Visible(text) <= max) { return text; } int num = max - 3; StringBuilder stringBuilder = new StringBuilder(text.Length + 16); List<string> list = new List<string>(); int num2 = 0; int num3 = 0; while (num3 < text.Length && num2 < num) { if (TryTag(text, num3, out var end, out var name, out var closing)) { stringBuilder.Append(text, num3, end - num3 + 1); if (closing) { CloseTag(list, name); } else if (!IsVoidTag(name)) { list.Add(name); } num3 = end + 1; } else { stringBuilder.Append(text[num3]); num2++; num3++; } } while (stringBuilder.Length > 0 && stringBuilder[stringBuilder.Length - 1] == ' ') { stringBuilder.Length--; } stringBuilder.Append("..."); for (int num4 = list.Count - 1; num4 >= 0; num4--) { stringBuilder.Append("</").Append(list[num4]).Append('>'); } return stringBuilder.ToString(); } private static int Visible(string value) { int num = 0; int num2 = 0; while (num2 < value.Length) { if (TryTag(value, num2, out var end, out var _, out var _)) { num2 = end + 1; continue; } num++; num2++; } return num; } private static bool TryTag(string value, int start, out int end, out string name, out bool closing) { end = -1; name = ""; closing = false; if (value[start] != '<') { return false; } int num = Math.Min(value.Length, start + 64); for (int i = start + 1; i < num; i++) { switch (value[i]) { case '<': return false; case '>': { end = i; int num2 = start + 1; if (num2 < end && value[num2] == '/') { closing = true; num2++; } int j; for (j = num2; j < end && value[j] != '=' && value[j] != ' '; j++) { } name = value.Substring(num2, j - num2).ToLowerInvariant(); return true; } } } return false; } private static void CloseTag(List<string> open, string name) { if (open.Count == 0) { return; } if (name.Length == 0) { open.RemoveAt(open.Count - 1); return; } for (int num = open.Count - 1; num >= 0; num--) { if (!(open[num] != name)) { open.RemoveRange(num, open.Count - num); break; } } } private static bool IsVoidTag(string name) { switch (name) { default: return name == "nbsp"; case "br": case "sprite": case "space": return true; } } private static string Replace(char c) { switch (c) { case '\r': return ""; case '\n': return " "; case '\t': return " "; case '…': return "..."; case '‘': case '’': return "'"; case '“': case '”': return "\""; case '–': case '—': return "-"; case '\u00a0': return " "; case '•': return "*"; case '→': return "->"; case '←': return "<-"; case '×': return "x"; case '✓': case '✔': return "+"; default: if (c < ' ') { return ""; } if (c <= 'ÿ') { return null; } return "?"; } } } internal sealed class LabelView { internal string Text = ""; internal string Colour = ""; } internal sealed class QuestView { internal string Id; internal string Title; internal string Subtitle; internal string Description; internal bool Tracked; internal string ExpiresIn = ""; internal bool Critical; internal List<QuestStepView> Steps = new List<QuestStepView>(); } internal sealed class QuestStepView { internal string Title; internal string State; internal bool HasPoi; } internal sealed class RankView { internal string Name = ""; internal int Tier; internal int Xp; internal int XpForNext; } internal sealed class ThreadView { internal string Id; internal string Name; internal bool Known; internal bool Unread; internal string Preview = ""; internal List<string> Categories = new List<string>(); internal bool HasFace; internal double OfferLeft = -1.0; internal string OfferColour; internal string BadgeLetter; internal string BadgeColour; internal bool CanHide; } internal sealed class MessageView { internal string From; internal string Text; internal bool EndOfGroup; } internal sealed class ThreadDetailView { internal string Id; internal string Name; internal bool Known; internal string Avatar = ""; internal string InfoLabel = ""; internal string InfoValue = ""; internal bool HasFace; internal double Relationship = -1.0; internal string StandardsColour = ""; internal int Debt = -1; internal List<MessageView> Messages = new List<MessageView>(); internal List<string> Replies = new List<string>(); internal List<SendableView> Sendables = new List<SendableView>(); internal int ReplyToken; internal int SendToken; internal CounterOfferView Counter; internal OrderSheetView Order; internal DealWindowView Deal; } internal sealed class SendableView { internal string Text; internal bool Valid; internal string Reason = ""; } internal sealed class CounterOfferView { internal string ProductId = ""; internal string ProductName = ""; internal int Quantity; internal int Price; internal int FairPrice; internal int MaxQuantity = 50; internal List<PickView> Products = new List<PickView>(); } internal sealed class PickView { internal string Id; internal string Name = ""; internal bool HasIcon; } internal sealed class OrderSheetView { internal string Title = ""; internal string Subtitle = ""; internal int OrderLimit; internal int Debt; internal int ItemLimit = 10; internal List<OrderItemView> Items = new List<OrderItemView>(); } internal sealed class DealWindowView { internal string Now = ""; internal int Minutes; internal List<DealSlotView> Windows = new List<DealSlotView>(); } internal sealed class DealSlotView { internal string Name = ""; internal string Span = ""; internal bool Choosable; } internal sealed class OrderItemView { internal string Id; internal string Name = ""; internal int Price; internal bool HasIcon; internal bool Locked; internal string LockText = ""; } internal sealed class ProductView { internal string Id; internal string Name; internal bool Listed; internal bool Favourite; internal int Price; internal int MarketValue; internal string Quality = ""; internal bool HasIcon; } internal sealed class ProductDetailView { internal string Id; internal string Name; internal string Description = ""; internal bool Listed; internal bool Favourite; internal int Price; internal int MarketValue; internal int Addictiveness; internal List<LabelView> Properties = new List<LabelView>(); internal List<string> Recipe = new List<string>(); } internal sealed class ShopView { internal string Id; internal string Name; internal string Description = ""; internal string Colour = ""; internal bool HasIcon; internal string IconKey = ""; internal string ItemTotal = ""; internal string Fee = ""; internal string OrderTotal = ""; internal string Time = ""; internal string Note = ""; internal bool CanOrder; internal List<string> Destinations = new List<string>(); internal int DestinationIndex; internal List<string> Docks = new List<string>(); internal int DockIndex; internal List<ListingView> Listings = new List<ListingView>(); } internal sealed class ListingView { internal string Id; internal string Name; internal int Price; internal int Quantity; internal bool Affordable; internal bool HasIcon; internal bool Locked; } internal sealed class DeliveryView { internal string Id; internal string Shop; internal string Destination = ""; internal string Status = ""; internal string Dock = ""; internal string ShopId = ""; internal int Total; internal string ShopColour = ""; internal string Description = ""; internal string StatusColour = ""; internal bool Affordable; internal string PriceColour = ""; internal bool CanReorder; internal string Reason = ""; internal List<DeliveryItemView> Items = new List<DeliveryItemView>(); } internal sealed class DeliveryItemView { internal string Id = ""; internal string Name = ""; internal int Quantity; internal bool HasIcon; } internal sealed class DealerView { internal string Id; internal string Name; internal string Region = ""; internal string Home = ""; internal int Cash; internal int CutPercent; internal int CustomerCount; internal int CustomerLimit; internal string Avatar = ""; internal bool HasFace; } internal sealed class DealerDetailView { internal DealerView Dealer; internal List<SlotView> Inventory = new List<SlotView>(); internal List<ContactView> Customers = new List<ContactView>(); } internal sealed class SlotView { internal string Id = ""; internal string Name = ""; internal int Quantity; internal int Quality = -1; internal bool HasIcon; } internal sealed class ContactView { internal string Id; internal string Name; internal string Kind = ""; internal string Region = ""; internal string Avatar = ""; internal double Relationship; internal string RelationshipLabel = ""; internal bool Unlocked; internal string StandardsColour = ""; internal bool HasFace; } internal sealed class ContactDetailView { internal ContactView Contact; internal string Standards = ""; internal string StandardsColour = ""; internal int AddictionPercent; internal int SpentTotal; internal int Debt; internal List<LabelView> PreferredProperties = new List<LabelView>(); internal List<string> TopPurchases = new List<string>(); internal List<ContactView> Connections = new List<ContactView>(); internal bool HasPoi; } internal sealed class RegionView { internal string Id; internal string Name; internal bool Unlocked; internal int CartelInfluencePercent; internal int ContactCount; } internal sealed class GraphView { internal List<NodeView> Nodes = new List<NodeView>(); internal List<EdgeView> Edges = new List<EdgeView>(); internal double MinX; internal double MinY; internal double Width; internal double Height; } internal sealed class NodeView { internal string Id; internal string Name = ""; internal string Kind = ""; internal double X; internal double Y; internal double Relationship; internal string RelationshipLabel = ""; internal string RelationshipColour = ""; internal double Addiction; internal bool Unlocked; internal bool Hidden; internal bool Supplier; internal bool Dealer; internal bool HasFace; } internal sealed class EdgeView { internal double X; internal double Y; internal double Length; internal double AngleDeg; } internal sealed class PoiView { internal string Id; internal string Label = ""; internal string Kind = ""; internal string Region = ""; internal double X; internal double Y; internal double Radius; internal bool HasFace; internal double Heading; internal string Colour = ""; internal bool AlwaysLabelled; } internal sealed class PlaceNameView { internal string Text = ""; internal double X; internal double Y; internal double Width; internal double Height; internal double Size; internal string Colour = "#7d7d7d"; internal bool Top; internal double Rotation; } } namespace Reflash.Screens { internal static class AppRoster { internal static IEnumerable<IAppPort> All() { yield return new JournalApp(new JournalGame()); yield return new MessagesApp(new MessagesGame()); yield return new DeliveryApp(new DeliveryGame()); yield return new ProductsApp(new ProductsGame()); yield return new DealersApp(new DealersGame()); yield return new ContactsApp(new ContactsGame()); yield return new MapApp(new MapGame()); } internal static string TitleOf(VanillaApp app) { return app switch { VanillaApp.Messages => "Messages", VanillaApp.Map => "Map", VanillaApp.Delivery => "Deliveries", VanillaApp.ProductManager => "Products", VanillaApp.Contacts => "Contacts", VanillaApp.DealerManagement => "Dealers", VanillaApp.Journal => "Journal", _ => "App", }; } internal static string[] OrientationsOf(VanillaApp app) { return app switch { VanillaApp.Messages => new string[2] { "portrait", "landscape" }, VanillaApp.DealerManagement => new string[2] { "portrait", "landscape" }, _ => new string[2] { "landscape", "portrait" }, }; } } internal sealed class ConnectApp : IAppPort { private readonly Runner _companion; private readonly AppHandle _handle; private CompanionServer Server => _companion.Server; public string Id => "reflash-connect"; public VanillaApp Replaces => (VanillaApp)(-1); public int Badge => 0; public int Revision { get { int num = 17; num = num * 31 + (_companion.Running ? 1 : 0); num = num * 31 + Server.Devices.Count; num = num * 31 + _companion.Problem.Length; num = num * 31 + (AppHijack.Ready ? 1 : 0); if (_companion.Running) { num = num * 31 + (int)(Server.Devices.TokenAgeSeconds(Clock.Now) / 5.0); } return num; } } internal ConnectApp(Runner companion, AppHandle handle) { _companion = companion; _handle = handle; } public string State(string section) { Json json = Json.Object().Add("rev", Revision).Add("running", _companion.Running) .Add("devices", Server.Devices.Count) .Add("problem", _companion.Problem) .Add("takeover", AppHijack.Ready); if (!_companion.Running) { return json.Add("url", "").Add("qr", value: false).Close(); } string text = Server.Devices.CurrentToken(Clock.Now); string text2 = "http://" + Server.LanIp + ":" + Server.Port + "/#t=" + text; byte[] array = QrImage.For(text2); if (array != null) { _handle.Image("qr", array); } double num = Server.Devices.TokenAgeSeconds(Clock.Now); return json.Add("url", text2).Add("qr", array != null).Add("plain", Server.LanIp + ":" + Server.Port) .Add("expires", Math.Max(0, (int)(120.0 - num))) .Close(); } public string Act(Cmd cmd) { switch (cmd.Op) { case "on": _companion.TurnOn(); return "ok"; case "off": _companion.TurnOff(); return "ok"; case "apps-on": case "apps-off": AppHijack.SetTakeOver(cmd.Op == "apps-on"); return "ok"; case "new": Server.Devices.NewToken(Clock.Now); return "ok"; case "kick": Server.Devices.DropAll(); return "ok"; default: return "err:bad-args"; } } } internal sealed class ContactsApp : IAppPort, INeedsAppHandle, IWarmUp { private readonly IContactsSource _game; private string _showing = ""; public string Id => "reflash-contacts"; public VanillaApp Replaces => VanillaApp.Contacts; public int Revision => _game.Revision; public int Badge => 0; internal ContactsApp(IContactsSource game) { _game = game; } public void UseHandle(AppHandle handle) { if (_game is INeedsAppHandle needsAppHandle) { needsAppHandle.UseHandle(handle); } } public void WarmUp() { if (_showing.Length > 0) { _game.WarmFaces(_showing); } } public string State(string section) { if (section != null && section.StartsWith("region:", StringComparison.Ordinal)) { return RegionJson(section.Substring(7)); } if (section != null && section.StartsWith("contact:", StringComparison.Ordinal)) { return ContactJson(section.Substring(8)); } Json json = Json.Array(); foreach (RegionView item in _game.Regions()) { json.Item(Json.Object().Add("id", item.Id).Add("name", item.Name) .Add("unlocked", item.Unlocked) .Add("cartel", item.CartelInfluencePercent) .Add("count", item.ContactCount)); } return Json.Object().Add("rev", Revision).Add("regions", json) .Close(); } private string RegionJson(string regionId) { _showing = regionId ?? ""; GraphView graphView = _game.Graph(regionId); Json json = Json.Array(); foreach (NodeView node in graphView.Nodes) { json.Item(Json.Object().Add("id", node.Id).Add("name", node.Name) .Add("kind", node.Kind) .Add("x", node.X) .Add("y", node.Y) .Add("rel", node.Relationship) .Add("relLabel", node.RelationshipLabel) .Add("relColour", node.RelationshipColour) .Add("addiction", node.Addiction) .Add("unlocked", node.Unlocked) .Add("hidden", node.Hidden) .Add("supplier", node.Supplier) .Add("face", node.HasFace)); } Json json2 = Json.Array(); foreach (EdgeView edge in graphView.Edges) { json2.Item(Json.Object().Add("x", edge.X).Add("y", edge.Y) .Add("len", edge.Length) .Add("deg", edge.AngleDeg)); } return Json.Object().Add("rev", Revision).Add("region", regionId) .Add("w", graphView.Width) .Add("h", graphView.Height) .Add("ox", 0.0 - graphView.MinX) .Add("oy", 0.0 - graphView.MinY) .Add("nodes", json) .Add("edges", json2) .Close(); } private static Json Brief(ContactView c) { return Json.Object().Add("id", c.Id).Add("name", c.Name) .Add("kind", c.Kind) .Add("region", c.Region) .Add("rel", c.Relationship) .Add("relLabel", c.RelationshipLabel) .Add("unlocked", c.Unlocked); } private string ContactJson(string npcId) { ContactDetailView contactDetailView = _game.Contact(npcId); if (contactDetailView == null || contactDetailView.Contact == null) { return Json.Object().Add("rev", Revision).AddNull("contact") .Close(); } Json json = Json.Array(); foreach (LabelView preferredProperty in contactDetailView.PreferredProperties) { json.Item(Json.Object().Add("text", preferredProperty.Text).Add("colour", preferredProperty.Colour)); } Json json2 = Json.Array(); foreach (string topPurchase in contactDetailView.TopPurchases) { json2.Item(topPurchase); } Json json3 = Json.Array(); foreach (ContactView connection in contactDetailView.Connections) { json3.Item(Brief(connection)); } return Json.Object().Add("rev", Revision).Add("contact", Brief(contactDetailView.Contact)) .Add("standards", contactDetailView.Standards) .Add("standardsColour", contactDetailView.StandardsColour) .Add("addiction", contactDetailView.AddictionPercent) .Add("spent", contactDetailView.SpentTotal) .Add("debt", contactDetailView.Debt) .Add("properties", json) .Add("purchases", json2) .Add("connections", json3) .Add("poi", contactDetailView.HasPoi) .Close(); } public string Act(Cmd cmd) { if (cmd.Op == "map") { string text = cmd.Str(0); if (!string.IsNullOrEmpty(text)) { return _game.ShowOnMap(text); } return "err:bad-args"; } return "err:bad-args"; } } internal sealed class DealersApp : IAppPort, INeedsAppHandle, IWarmUp { private readonly IDealersSource _game; public string Id => "reflash-dealers"; public VanillaApp Replaces => VanillaApp.DealerManagement; public int Revision => _game.Revision; public int Badge => 0; public void UseHandle(AppHandle handle) { if (_game is INeedsAppHandle needsAppHandle) { needsAppHandle.UseHandle(handle); } } public void WarmUp() { _game.WarmIcons(); } internal DealersApp(IDealersSource game) { _game = game; } public string State(string section) { if (section != null && section.StartsWith("dealer:", StringComparison.Ordinal)) { return DetailJson(section.Substring(7)); } if (section != null && section.StartsWith("assignable:", StringComparison.Ordinal)) { return AssignableJson(section.Substring(11)); } Json json = Json.Array(); foreach (DealerView item in _game.Dealers()) { json.Item(DealerJson(item)); } return Json.Object().Add("rev", Revision).Add("dealers", json) .Close(); } private static Json DealerJson(DealerView d) { return Json.Object().Add("id", d.Id).Add("name", d.Name) .Add("region", d.Region) .Add("home", d.Home) .Add("cash", d.Cash) .Add("cut", d.CutPercent) .Add("customers", d.CustomerCount) .Add("limit", d.CustomerLimit) .Add("avatar", d.Avatar) .Add("face", d.HasFace); } private string DetailJson(string dealerId) { DealerDetailView dealerDetailView = _game.Dealer(dealerId); if (dealerDetailView == null || dealerDetailView.Dealer == null) { return Json.Object().Add("rev", Revision).AddNull("dealer") .Close(); } Json json = Json.Array(); foreach (SlotView item in dealerDetailView.Inventory) { json.Item(Json.Object().Add("id", item.Id).Add("name", item.Name) .Add("qty", item.Quantity) .Add("quality", item.Quality) .Add("icon", item.HasIcon)); } Json json2 = Json.Array(); foreach (ContactView customer in dealerDetailView.Customers) { json2.Item(Json.Object().Add("id", customer.Id).Add("name", customer.Name) .Add("region", customer.Region) .Add("rel", customer.Relationship) .Add("relLabel", customer.RelationshipLabel) .Add("standards", customer.StandardsColour) .Add("face", customer.HasFace)); } return Json.Object().Add("rev", Revision).Add("dealer", DealerJson(dealerDetailView.Dealer)) .Add("inventory", json) .Add("customers", json2) .Close(); } private string AssignableJson(string dealerId) { Json json = Json.Array(); foreach (ContactView item in _game.AssignableTo(dealerId)) { json.Item(Json.Object().Add("id", item.Id).Add("name", item.Name) .Add("region", item.Region) .Add("standards", item.StandardsColour) .Add("face", item.HasFace)); } return Json.Object().Add("rev", Revision).Add("candidates", json) .Close(); } public string Act(Cmd cmd) { string text = cmd.Str(0); if (string.IsNullOrEmpty(text)) { return "err:bad-args"; } switch (cmd.Op) { case "add": { string text3 = cmd.Str(1); if (!string.IsNullOrEmpty(text3)) { return _game.AddCustomer(text, text3); } return "err:bad-args"; } case "remove": { string text2 = cmd.Str(1); if (!string.IsNullOrEmpty(text2)) { return _game.RemoveCustomer(text, text2); } return "err:bad-args"; } case "cut": { if (!cmd.Int(1, out var value)) { return "err:bad-args"; } return _game.SetCut(text, value); } default: return "err:bad-args"; } } } internal sealed class DeliveryApp : IAppPort, INeedsAppHandle, IWarmUp { private readonly IDeliverySource _game; public string Id => "reflash-delivery"; public VanillaApp Replaces => VanillaApp.Delivery; public int Revision => _game.Revision; public int Badge => _game.ActiveCount; internal DeliveryApp(IDeliverySource game) { _game = game; } public void UseHandle(AppHandle handle) { if (_game is INeedsAppHandle needsAppHandle) { needsAppHandle.UseHandle(handle); } } public void WarmUp() { _game.WarmIcons(); } public string State(string section) { if (!(section == "active")) { if (section == "history") { return DeliveriesJson("history", _game.History()); } return ShopsJson(); } return DeliveriesJson("active", _game.Active()); } private string ShopsJson() { Json json = Json.Array(); foreach (ShopView item in _game.Shops()) { Json json2 = Json.Array(); foreach (ListingView listing in item.Listings) { json2.Item(Json.Object().Add("id", listing.Id).Add("name", listing.Name) .Add("price", listing.Price) .Add("qty", listing.Quantity) .Add("afford", listing.Affordable) .Add("icon", listing.HasIcon) .Add("locked", listing.Locked)); } json.Item(Json.Object().Add("id", item.Id).Add("name", item.Name) .Add("desc", item.Description) .Add("colour", item.Colour) .Add("icon", item.HasIcon) .Add("iconKey", item.IconKey) .Add("itemTotal", item.ItemTotal) .Add("fee", item.Fee) .Add("orderTotal", item.OrderTotal) .Add("time", item.Time) .Add("note", item.Note) .Add("canOrder", item.CanOrder) .Add("destinations", Strings(item.Destinations)) .Add("destination", item.DestinationIndex) .Add("docks", Strings(item.Docks)) .Add("dock", item.DockIndex) .Add("listings", json2)); } return Json.Object().Add("rev", Revision).Add("balance", _game.Balance) .Add("active", _game.ActiveCount) .Add("shops", json) .Close(); } private string DeliveriesJson(string which, List<DeliveryView> deliveries) { Json json = Json.Array(); foreach (DeliveryView delivery in deliveries) { Json json2 = Json.Array(); foreach (DeliveryItemView item in delivery.Items) { json2.Item(Json.Object().Add("id", item.Id).Add("name", item.Name) .Add("qty", item.Quantity) .Add("icon", item.HasIcon)); } json.Item(Json.Object().Add("id", delivery.Id).Add("shop", delivery.Shop) .Add("destination", delivery.Destination) .Add("status", delivery.Status) .Add("dock", delivery.Dock) .Add("shopId", delivery.ShopId) .Add("total", delivery.Total) .Add("shopColour", delivery.ShopColour) .Add("description", delivery.Description) .Add("statusColour", delivery.StatusColour) .Add("afford", delivery.Affordable) .Add("priceColour", delivery.PriceColour) .Add("canReorder", delivery.CanReorder) .Add("reason", delivery.Reason) .Add("items", json2)); } return Json.Object().Add("rev", Revision).Add("which", which) .Add("deliveries", json) .Close(); } private static Json Strings(List<string> values) { Json json = Json.Array(); foreach (string value in values) { json.Item(value); } return json; } public string Act(Cmd cmd) { switch (cmd.Op) { case "reorder": { string text4 = cmd.Str(0); if (!string.IsNullOrEmpty(text4)) { return _game.Reorder(text4); } return "err:bad-args"; } case "order": { string text2 = cmd.Str(0); if (string.IsNullOrEmpty(text2)) { return "err:bad-args"; } List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(); for (int i = 1; i + 1 < cmd.Count + 1; i += 2) { string text3 = cmd.Str(i); if (text3 == null) { break; } if (!cmd.Int(i + 1, out var value2)) { return "err:bad-args"; } if (value2 > 0) { list.Add(new KeyValuePair<string, int>(text3, value2)); } } if (list.Count == 0) { return "err:bad-args"; } return _game.Order(text2, list); } case "fill": { string text5 = cmd.Str(0); if (string.IsNullOrEmpty(text5)) { return "err:bad-args"; } List<KeyValuePair<string, int>> list2 = new List<KeyValuePair<string, int>>(); for (int j = 1; j + 1 < cmd.Count + 1; j += 2) { string text6 = cmd.Str(j); if (text6 == null) { break; } if (!cmd.Int(j + 1, out var value3)) { return "err:bad-args"; } if (value3 > 0) { list2.Add(new KeyValuePair<string, int>(text6, value3)); } } return _game.Fill(text5, list2); } case "dest": case "dock": { string text = cmd.Str(0); if (string.IsNullOrEmpty(text) || !cmd.Int(1, out var value)) { return "err:bad-args"; } return _game.Choose(text, cmd.Op == "dock", value); } default: return "err:bad-args"; } } } internal sealed class JournalApp : IAppPort { private readonly IJournalSource _game; public string Id => "reflash-journal"; public VanillaApp Replaces => VanillaApp.Journal; public int Revision => _game.Revision; public int Badge => 0; internal JournalApp(IJournalSource game) { _game = game; } public string State(string section) { List<QuestView> list = _game.ActiveQuests(); RankView rankView = _game.Rank(); Json json = Json.Object(); json.Add("rev", Revision); Json json2 = Json.Array(); foreach (QuestView item in list) { Json json3 = Json.Array(); foreach (QuestStepView step in item.Steps) { json3.Item(Json.Object().Add("title", step.Title).Add("state", step.State) .Add("poi", step.HasPoi)); } json2.Item(Json.Object().Add("id", item.Id).Add("title", item.Title) .Add("subtitle", item.Subtitle) .Add("description", item.Description) .Add("tracked", item.Tracked) .Add("expires", item.ExpiresIn) .Add("critical", item.Critical) .Add("steps", json3)); } json.Add("quests", json2); json.Add("rank", Json.Object().Add("name", rankView.Name).Add("tier", rankView.Tier) .Add("xp", rankView.Xp) .Add("next", rankView.XpForNext)); return json.Close(); } public string Act(Cmd cmd) { string op = cmd.Op; if (!(op == "map")) { if (op == "track") { string text = cmd.Str(0); if (!string.IsNullOrEmpty(text)) { return _game.ToggleTracked(text); } return "err:bad-args"; } return "err:bad-args"; } if (!cmd.Int(1, out var value)) { return "err:bad-args"; } string text2 = cmd.Str(0); if (!string.IsNullOrEmpty(text2)) { return _game.ShowStepOnMap(text2, value); } return "err:bad-args"; } } internal sealed class MapApp : IAppPort, INeedsAppHandle, IWarmUp { private readonly IMapSource _game; public string Id => "reflash-map"; public VanillaApp Replaces => VanillaApp.Map; public int Revision => _game.Revision; public int Badge => 0; internal MapApp(IMapSource game) { _game = game; } public void UseHandle(AppHandle handle) { if (_game is INeedsAppHandle needsAppHandle) { needsAppHandle.UseHandle(handle); } } public void WarmUp() { _game.EnsureImage(); _game.WarmFaces(); } public string State(string section) { if (section == "labels") { return LabelsJson(); } bool imageReady = _game.ImageReady; Json json = Json.Array(); foreach (PoiView item in _game.Pois()) { json.Item(Json.Object().Add("id", item.Id).Add("label", item.Label) .Add("kind", item.Kind) .Add("region", item.Region) .Add("x", item.X) .Add("y", item.Y) .Add("radius", item.Radius) .Add("face", item.HasFace) .Add("colour", item.Colour) .Add("always", item.AlwaysLabelled)); } Json json2 = Json.Array(); foreach (RegionView item2 in _game.Regions()) { json2.Item(Json.Object().Add("id", item2.Id).Add("name", item2.Name) .Add("unlocked", item2.Unlocked)); } Json json3 = Json.Object().Add("rev", Revision).Add("image", imageReady) .Add("pins", json) .Add("regions", json2); PoiView poiView = _game.Player(); if (poiView != null) { json3.Add("player", Json.Object().Add("x", poiView.X).Add("y", poiView.Y) .Add("heading", poiView.Heading)); } else { json3.AddNull("player"); } json3.Add("focus", AppHijack.TakePendingArg(VanillaApp.Map)); return json3.Close(); } private string LabelsJson() { Json json = Json.Array(); foreach (PlaceNameView item in _game.Labels()) { json.Item(Json.Object().Add("text", item.Text).Add("x", item.X) .Add("y", item.Y) .Add("w", item.Width) .Add("h", item.Height) .Add("size", item.Size) .Add("colour", item.Colour) .Add("top", item.Top) .Add("rot", item.Rotation)); } return Json.Object().Add("labels", json).Add("from", _game.LabelsFrom) .Add("full", _game.LabelsFull) .Close(); } public string Act(Cmd cmd) { return "err:bad-args"; } } internal sealed class MessagesApp : IAppPort, INeedsAppHandle, IWarmUp { private readonly IMessagesSource _game; public string Id => "reflash-messages"; public VanillaApp Replaces => VanillaApp.Messages; public int Revision => _game.Revision; public int Badge => _game.Unread; public void UseHandle(AppHandle handle) { if (_game is INeedsAppHandle needsAppHandle) { needsAppHandle.UseHandle(handle); } } public void WarmUp() { _game.WarmFaces(); _game.HideVanillaSheets(); } internal MessagesApp(IMessagesSource game) { _game = game; } public string State(string section) { if (section != null && section.StartsWith("thread:", StringComparison.Ordinal)) { return ThreadJson(section.Substring(7)); } return ListJson(); } private string ListJson() { Json json = Json.Object().Add("rev", Revision).Add("unread", _game.Unread); Json json2 = Json.Array(); foreach (ThreadView item in _game.Threads()) { Json json3 = Json.Array(); foreach (string category in item.Categories) { json3.Item(category); } json2.Item(Json.Object().Add("id", item.Id).Add("name", item.Name) .Add("known", item.Known) .Add("unread", item.Unread) .Add("preview", Text.Ellipsis(item.Preview, 160)) .Add("face", item.HasFace) .Add("offer", item.OfferLeft) .Add("offerColour", item.OfferColour) .Add("badge", item.BadgeLetter) .Add("badgeColour", item.BadgeColour) .Add("canHide", item.CanHide) .Add("cats", json3)); } return json.Add("threads", json2).Close(); } private string ThreadJson(string npcId) { ThreadDetailView threadDetailView = _game.Thread(npcId); if (threadDetailView == null) { return Json.Object().Add("rev", Revision).AddNull("thread") .Close(); } Json json = Json.Array(); foreach (MessageView message in threadDetailView.Messages) { json.Item(Json.Object().Add("from", message.From).Add("text", message.Text) .Add("end", message.EndOfGroup)); } Json json2 = Json.Array(); foreach (string reply in threadDetailView.Replies) { json2.Item(reply); } Json json3 = Json.Array(); foreach (SendableView sendable in threadDetailView.Sendables) { json3.Item(Json.Object().Add("text", sendable.Text).Add("valid", sendable.Valid) .Add("reason", sendable.Reason)); } Json json4 = Json.Object().Add("rev", Revision); if (threadDetailView.Counter == null) { json4.AddNull("counter"); } else { json4.Add("counter", CounterJson(threadDetailView.Counter)); } if (threadDetailView.Order == null) { json4.AddNull("order"); } else { json4.Add("order", OrderJson(threadDetailView.Order)); } if (threadDetailView.Deal == null) { json4.AddNull("deal"); } else { json4.Add("deal", DealJson(threadDetailView.Deal)); } return json4.Add("thread", Json.Object().Add("id", threadDetailView.Id).Add("name", threadDetailView.Name) .Add("known", threadDetailView.Known) .Add("avatar", threadDetailView.Avatar) .Add("face", threadDetailView.HasFace) .Add("rel", threadDetailView.Relationship) .Add("standardsColour", threadDetailView.StandardsColour) .Add("debt", threadDetailView.Debt) .Add("replyToken", threadDetailView.ReplyToken) .Add("sendToken", threadDetailView.SendToken) .Add("infoLabel", threadDetailView.InfoLabel) .Add("infoValue", threadDetailView.InfoValue) .Add("messages", json) .Add("replies", json2) .Add("sendables", json3)).Close(); } private static Json CounterJson(CounterOfferView c) { Json json = Json.Array(); foreach (PickView product in c.Products) { json.Item(Json.Object().Add("id", product.Id).Add("name", product.Name) .Add("icon", product.HasIcon)); } return Json.Object().Add("productId", c.ProductId).Add("productName", c.ProductName) .Add("quantity", c.Quantity) .Add("price", c.Price) .Add("fair", c.FairPrice) .Add("maxQuantity", c.MaxQuantity) .Add("products", json); } private static Json OrderJson(OrderSheetView o) { Json json = Json.Array(); foreach (OrderItemView item in o.Items) { json.Item(Json.Object().Add("id", item.Id).Add("name", item.Name) .Add("price", item.Price) .Add("icon", item.HasIcon) .Add("locked", item.Locked) .Add("lockText", item.LockText)); } return Json.Object().Add("title", o.Title).Add("subtitle", o.Subtitle) .Add("limit", o.OrderLimit) .Add("debt", o.Debt) .Add("itemLimit", o.ItemLimit) .Add("items", json); } private static Json DealJson(DealWindowView d) { Json json = Json.Array(); foreach (DealSlotView window in d.Windows) { json.Item(Json.Object().Add("name", window.Name).Add("span", window.Span) .Add("open", window.Choosable)); } return Json.Object().Add("now", d.Now).Add("minutes", d.Minutes) .Add("windows", json); } private static List<KeyValuePair<string, int>> Pairs(Cmd cmd) { List<KeyValuePair<string, int>> list = new List<KeyValuePair<string, int>>(); for (int i = 0; i + 1 < cmd.Count; i += 2) { string text = cmd.Str(i); if (string.IsNullOrEmpty(text) || !cmd.Int(i + 1, out var value)) { break; } if (value > 0) { list.Add(new KeyValuePair<string, int>(text, value)); } } return list; } public string Act(Cmd cmd) { switch (cmd.Op) { case "counter": { if (!cmd.Int(1, out var value6) || !cmd.Int(2, out var value7)) { return "err:bad-args"; } return _game.SendCounterOffer(cmd.Str(0), value6, value7); } case "order": return _game.PlaceOrder(Pairs(cmd)); case "deal": { if (!cmd.Int(0, out var value8)) { return "err:bad-args"; } return _game.ChooseDealWindow(value8); } case "sheet-close": return _game.CloseSheet(); default: { string text = cmd.Str(0); if (string.IsNullOrEmpty(text)) { return "err:bad-args"; } switch (cmd.Op) { case "read": return _game.MarkRead(text); case "hide": { if (!cmd.Flag(1, out var value3)) { return "err:bad-args"; } return _game.SetHidden(text, value3); } case "reply": { if (!cmd.Int(1, out var value4) || !cmd.Int(2, out var value5)) { return "err:bad-args"; } return _game.ChooseReply(text, value4, value5); } case "send": { if (!cmd.Int(1, out var value) || !cmd.Int(2, out var value2)) { return "err:bad-args"; } return _game.SendCanned(text, value, value2); } default: return "err:bad-args"; } } } } } internal sealed class ProductsApp : IAppPort, INeedsAppHandle, IWarmUp { private readonly IProductsSource _game; public string Id => "reflash-products"; public VanillaApp Replaces => VanillaApp.ProductManager; public int Revision => _game.Revision; public int Badge => 0; public void UseHandle(AppHandle handle) { if (_game is INeedsAppHandle needsAppHandle) { needsAppHandle.UseHandle(handle); } } public void WarmUp() { _game.WarmIcons(); } internal ProductsApp(IProductsSource game) { _game = game; } public string State(string section) { if (section != null && section.StartsWith("item:", StringComparison.Ordinal)) { return DetailJson(section.Substring(5)); } Json json = Json.Array(); foreach (ProductView item in _game.Products()) { json.Item(Json.Object().Add("id", item.Id).Add("name", item.Name) .Add("listed", item.Listed) .Add("fav", item.Favourite) .Add("price", item.Price) .Add("value", item.MarketValue) .Add("quality", item.Quality) .Add("icon", item.HasIcon)); } return Json.Object().Add("rev", Revision).Add("products", json) .Close(); } private string DetailJson(string productId) { ProductDetailView productDetailView = _game.Product(productId); if (productDetailView == null) { return Json.Object().Add("rev", Revision).AddNull("product") .Close(); } Json json = Json.Array(); foreach (LabelView property in productDetailView.Properties) { json.Item(Json.Object().Add("text", property.Text).Add("colour", property.Colour)); } Json json2 = Json.Array(); foreach (string item in productDetailView.Recipe) { json2.Item(item); } return Json.Object().Add("rev", Revision).Add("product", Json.Object().Add("id", productDetailView.Id).Add("name", productDetailView.Name) .Add("description", productDetailView.Description) .Add("listed", productDetailView.Listed) .Add("fav", productDetailView.Favourite) .Add("price", productDetailView.Price) .Add("value", productDetailView.MarketValue) .Add("addictiveness", productDetailView.Addictiveness) .Add("properties", json) .Add("recipe", json2)) .Close(); } public string Act(Cmd cmd) { string text = cmd.Str(0); if (string.IsNullOrEmpty(text)) { return "err:bad-args"; } switch (cmd.Op) { case "list": { if (!cmd.Flag(1, out var value3)) { return "err:bad-args"; } return _game.SetListed(text, value3); } case "fav": { if (!cmd.Flag(1, out var value2)) { return "err:bad-args"; } return _game.SetFavourite(text, value2); } case "price": { if (!cmd.Num(1, out var value)) { return "err:bad-args"; } return _game.SetPrice(text, value); } default: return "err:bad-args"; } } } } namespace Reflash.Hijack { internal static class AppHijack { private static readonly Dictionary<VanillaApp, AppHandle> _handles = new Dictionary<VanillaApp, AppHandle>(); private static readonly Dictionary<VanillaApp, string> _pendingArg = new Dictionary<VanillaApp, string>(); private static readonly List<AppHandle> _ours = new List<AppHandle>(); internal static bool Ready { get; private set; } internal static void Register(VanillaApp vanilla, AppHandle handle) { if (handle != null) { _handles[vanilla] = handle; Watch(handle); } } internal static void Watch(AppHandle handle) { if (handle != null && !_ours.Contains(handle)) { _ours.Add(handle); } } internal static void StandAside() { for (int i = 0; i < _ours.Count; i++) { AppHandle appHandle = _ours[i]; if (appHandle != null && appHandle.IsOpen) { appHandle.Close(); } } } internal static void Arm(bool takeOver) { Ready = takeOver && _handles.Count > 0; } internal static void SetTakeOver(bool on) { Prefs.SetTakeOverVanillaApps(on); Arm(on); } internal static bool Open(VanillaApp vanilla) { if (!Ready || !_handles.TryGetValue(vanilla, out var value)) { return false; } value.Open(); return true; } internal static bool Open(VanillaApp vanilla, string argument) { Sessions.Session current = CallOrigin.Current; if (current != null) { return OpenOnCompanion(current, vanilla, argument); } _pendingArg[vanilla] = argument ?? ""; if (!Open(vanilla)) { return false; } if (_handles.TryGetValue(vanilla, out var value)) { value.Emit(value.Id + ".changed", "open"); Instance log = Core.Log; if (log != null) { log.Msg($"Opened {value.Id} with '{argument}'."); } } return true; } private static bool OpenOnCompanion(Sessions.Session from, VanillaApp vanilla, string argument) { if (!_handles.TryGetValue(vanilla, out var value)) { Instance log = Core.Log; if (log != null) { log.Warning($"No app replaces {vanilla}, so the companion cannot open it."); } return false; } from.PendingArg[value.Id] = argument ?? ""; from.Outbox.Enqueue("{\"k\":\"open\",\"app\":" + JsonRead.Quote(value.Id) + "}"); Instance log2 = Core.Log; if (log2 != null) { log2.Msg($"Opened {value.Id} on the companion ({from.Device}) with '{argument}'."); } return true; } internal static string TakePendingArg(VanillaApp vanilla) { Sessions.Session current = CallOrigin.Current; if (current != null) { if (!_handles.TryGetValue(vanilla, out var value) || !current.PendingArg.TryGetValue(value.Id, out var value2)) { return ""; } current.PendingArg.Remove(value.Id); return value2 ?? ""; } if (!_pendingArg.TryGetValue(vanilla, out var value3)) { return ""; } _pendingArg.Remove(vanilla); return value3 ?? ""; } } internal static class SetOpenPatches { internal static void ApplyAll(Harmony harmony) { int num = 0; num += (Apply(harmony, typeof(MessagesApp), "Prefix_Messages") ? 1 : 0); num += (Apply(harmony, typeof(MapApp), "Prefix_Map") ? 1 : 0); num += (Apply(harmony, typeof(DeliveryApp), "Prefix_Delivery") ? 1 : 0); num += (Apply(harmony, typeof(ProductManagerApp), "Prefix_Products") ? 1 : 0); num += (Apply(harmony, typeof(ContactsApp), "Prefix_Contacts") ? 1 : 0); num += (Apply(harmony, typeof(DealerManagementApp), "Prefix_Dealers") ? 1 : 0); num += (Apply(harmony, typeof(JournalApp), "Prefix_Journal") ? 1 : 0); Core.Log.Msg($"hooked {num} of 7 phone apps."); if (num < 7) { Core.Log.Warning("the rest keep their vanilla screens whatever the setting says - see the errors above."); } } private static bool Apply(Harmony harmony, Type appType, string prefixName) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(appType, "SetOpen", new Type[1] { typeof(bool) }, (Type[])null); if (methodInfo == null) { Core.Log.Error(appType.Name + ".SetOpen(bool) not found - that app keeps its vanilla screen."); return false; } HarmonyMethod val = new HarmonyMethod(AccessTools.Method(typeof(SetOpenPatches), prefixName, (Type[])null, (Type[])null)); harmony.Patch((MethodBase)methodInfo, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { Core.Log.Error($"patching {appType.Name}.SetOpen failed ({ex.Message}) - that app keeps its vanilla screen."); return false; } } private static bool Intercept(bool open, VanillaApp which) { if (!open) { return true; } if (AppHijack.Open(which)) { return false; } AppHijack.StandAside(); return true; } private static bool Prefix_Messages(bool open) { return Intercept(open, VanillaApp.Messages); } private static bool Prefix_Map(bool open) { return Intercept(open, VanillaApp.Map); } private static bool Prefix_Delivery(bool open) { return Intercept(open, VanillaApp.Delivery); } private static bool Prefix_Products(bool open) { return Intercept(open, VanillaApp.ProductManager); } private static bool Prefix_Contacts(bool open) { return Intercept(open, VanillaApp.Contacts); } private static bool Prefix_Dealers(bool open) { return Intercept(open, VanillaApp.DealerManagement); } private static bool Prefix_Journal(bool open) { return Intercept(open, VanillaApp.Journal); } } } namespace Reflash.Game { internal static class Colours { internal static readonly string[] Relationship = new string[5] { "#ad3f3f", "#e38837", "#d0d0d0", "#3db5f3", "#3fd33f" }; internal static string Hex(Color c) { //IL_0005: 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) //IL_001b: Unknown result type (might be due to invalid IL or missing references) return "#" + Byte(c.r) + Byte(c.g) + Byte(c.b); } internal static string Hex(Color32 c) { return "#" + c.r.ToString("x2") + c.g.ToString("x2") + c.b.ToString("x2"); } private static string Byte(float channel) { return Mathf.Clamp(Mathf.RoundToInt(channel * 255f), 0, 255).ToString("x2"); } internal static string OfStandards(string standards) { return standards switch { "VeryHigh" => "#ffc832", "High" => "#e14bff", "Moderate" => "#64beff", "Low" => "#509132", _ => "#7d3232", }; } internal static string StandardsName(string standards) { return standards switch { "VeryLow" => "Very Low", "Low" => "Low", "Moderate" => "Moderate", "High" => "High", "VeryHigh" => "Very High", _ => "Standard", }; } } internal sealed class ContactsGame : IContactsSource, INeedsAppHandle { private readonly ContactsGraph _graph = new ContactsGraph(); public int Revision { get { int num = 17; List<NPC> nPCRegistry = NPCManager.NPCRegistry; num = num * 31 + (nPCRegistry?.Count ?? 0); if (nPCRegistry == null) { return num; } int num2 = 0; int num3 = 0; for (int i = 0; i < nPCRegistry.Count; i++) { NPC val = nPCRegistry[i]; if (((val != null) ? val.RelationData : null) != null && val.RelationData.Unlocked) { num2++; num3 += (int)(val.RelationData.RelationDelta * 2f); } } num = num * 31 + num2; num = num * 31 + num3; return num * 31 + _graph.PublishedFaces; } } public void UseHandle(AppHandle handle) { _graph.UseHandle(handle); } public GraphView Graph(string regionId) { return _graph.Graph(regionId); } public void WarmFaces(string regionId) { _graph.WarmFaces(regionId); } public List<RegionView> Regions() { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) List<RegionView> list = new List<RegionView>(); Map val = (Singleton<Map>.InstanceExists ? Singleton<Map>.Instance : null); foreach (EMapRegion value in Enum.GetValues(typeof(EMapRegion))) { MapRegionData val3 = null; if ((Object)(object)val != (Object)null) { try { val3 = val.GetRegionData(value); } catch { } } list.Add(new RegionView { Id = ((object)value/*cast due to .constrained prefix*/).ToString(), Name = Text.Clean(((object)value/*cast due to .constrained prefix*/).ToString()), Unlocked = (val3 != null && val3.IsUnlocked), ContactCount = CountIn(value), CartelInfluencePercent = CartelInfluence(value) }); } return list; } public ContactDetailView Contact(string npcId) { //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) NPC val = FindNpc(npcId); if ((Object)(object)val == (Object)null) { return null; } ContactView contactView = DealersGame.ContactOf(val); contactView.Kind = KindOf(val); ContactDetailView contactDetailView = new ContactDetailView { Contact = contactView, HasPoi = (FocusOf(val).Length > 0) }; Customer component = ((Component)val).GetComponent<Customer>(); if ((Object)(object)component != (Object)null) { contactDetailView.AddictionPercent = (int)Math.Round(component.CurrentAddiction * 100f); if ((Object)(object)component.CustomerData != (Object)null) { string standards = ((object)component.CustomerData.Standards/*cast due to .constrained prefix*/).ToString(); contactDetailView.Standards = Colours.StandardsName(standards); contactDetailView.StandardsColour = Colours.OfStandards(standards); List<Effect> preferredProperties = component.CustomerData.PreferredProperties; if (preferredProperties != null) { for (int i = 0; i < preferredProperties.Count; i++) { if ((Object)(object)preferredProperties[i] != (Object)null) { contactDetailView.PreferredProperties.Add(new LabelView { Text = Text.Clean(preferredProperties[i].Name), Colour = Colours.Hex(preferredProperties[i].LabelColor) }); } } } } if (val.RelationData != null && val.RelationData.Unlocked) { FillPurchases(component, contactDetailView); } } Supplier component2 = ((Component)val).GetComponent<Supplier>(); if ((Object)(object)component2 != (Object)null) { contactDetailView.Debt = (int)Math.Round(component2.Debt); } FillConnections(val, contactDetailView); return contactDetailView; } private static void FillPurchases(Customer customer, ContactDetailView view) { List<StringIntPair> val = default(List<StringIntPair>); float num = default(float); try { customer.CalculateTopWeeklyPurchases(ref val, ref num); } catch { return; } view.SpentTotal = (int)Math.Round(num); if (val == null) { return; } for (int i = 0; i < val.Count; i++) { if (view.TopPurchases.Count >= 3) { break; } StringIntPair val2 = val[i]; if (val2 != null) { ItemDefinition item = Registry.GetItem(val2.String); string text = (((Object)(object)item != (Object)null) ? Text.Clean(((BaseItemDefinition)item).Name) : Text.Clean(val2.String)); view.TopPurchases.Add(val2.Int + "x " + text); } } } private static void FillConnections(NPC npc, ContactDetailView view) { NPCRelationData relationData = npc.RelationData; List<NPC> val = ((relationData != null) ? relationData.Connections : null); if (val == null) { return; } for (int i = 0; i < val.Count; i++) { if (view.Connections.Count >= 12) { break; } NPC val2 = val[i]; if (!((Object)(object)val2 == (Object)null) && val2.RelationData != null && val2.RelationData.Unlocked) { ContactView contactView = DealersGame.ContactOf(val2); contactView.Kind = KindOf(val2); view.Connections.Add(contactView); } } } public string ShowOnMap(string npcId) { NPC val = FindNpc(npcId); if ((Object)(object)val == (Object)null) { return "err:not-found"; } string text = FocusOf(val); if (text.Length == 0) { Instance log = Core.Log; if (log != null) { log.Warning("Contacts: " + val.ID + " has no marker on the map to show."); } return "err:refused"; } if (!AppHijack.Open(VanillaApp.Map, text)) { return "err:refused"; } return "ok"; } private static int CountIn(EMapRegion region) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) List<NPC> nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry == null) { return 0; } int num = 0; for (int i = 0; i < nPCRegistry.Count; i++) { NPC val = nPCRegistry[i]; if ((Object)(object)val != (Object)null && val.Region == region && val.RelationData != null && val.RelationData.Unlocked) { num++; } } return num; } private static int CartelInfluence(EMapRegion region) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) try { if (!NetworkSingleton<Cartel>.InstanceExists) { return 0; } Cartel instance = NetworkSingleton<Cartel>.Instance; if ((Object)(object)instance.Influence == (Object)null) { return 0; } return (int)Math.Round(instance.Influence.GetInfluence(region) * 100f); } catch { return 0; } } private static string FocusOf(NPC npc) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) POI val = MarkerOf(npc); if (!((Object)(object)val != (Object)null)) { return ""; } return MapSpace.FocusArg(((Component)val).transform.position); } private static POI MarkerOf(NPC npc) { Dealer component = ((Component)npc).GetComponent<Dealer>(); if ((Object)(object)component != (Object)null) { if (component.IsRecruited) { return (POI)(object)component.DealerPoI; } if (!component.HasBeenRecommended) { return null; } return (POI)(object)component.PotentialDealerPoI; } Supplier component2 = ((Component)npc).GetComponent<Supplier>(); if ((Object)(object)component2 != (Object)null) { if (npc.RelationData == null || !npc.RelationData.Unlocked || !((Object)(object)component2.Stash != (Object)null)) { return null; } return component2.Stash.StashPoI; } Customer component3 = ((Component)npc).GetComponent<Customer>(); if ((Object)(object)component3 == (Object)null || npc.RelationData == null) { return null; } if (!npc.RelationData.IsMutuallyKnown() || npc.RelationData.Unlocked || GameManager.IS_TUTORIAL) { return null; } return (POI)(object)component3.potentialCustomerPoI; } private static string KindOf(NPC npc) { if ((Object)(object)((Component)npc).GetComponent<Dealer>() != (Object)null) { return "dealer"; } if ((Object)(object)((Component)npc).GetComponent<Supplier>() != (Object)null) { return "supplier"; } if ((Object)(object)((Component)npc).GetComponent<Customer>() != (Object)null) { return "customer"; } return ""; } private static NPC FindNpc(string npcId) { if (string.IsNullOrEmpty(npcId)) { return null; } List<NPC> nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry == null) { return null; } for (int i = 0; i < nPCRegistry.Count; i++) { if ((Object)(object)nPCRegistry[i] != (Object)null && nPCRegistry[i].ID == npcId) { return nPCRegistry[i]; } } return null; } } internal sealed class ContactsGraph { private const double CanvasToCss = 1.6375; private const double NodeSize = 61.06870229007634; private readonly SpriteFeed _faces = new SpriteFeed("face-"); private readonly List<string> _readIds = new List<string>(); private int _facesRead; private int _facesSeen; private readonly List<(string Id, NPC Npc, EMapRegion Region)> _people = new List<(string, NPC, EMapRegion)>(); private Il2CppArrayBase<RelationCircle> _peopleFrom; private Il2CppArrayBase<RelationCircle> _circles; private bool _searchedScene; internal int PublishedFaces { get; private set; } internal void UseHandle(AppHandle handle) { _faces.UseHandle(handle); } internal GraphView Graph(string regionId) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) GraphView graphView = new GraphView(); if (!Enum.TryParse<EMapRegion>(regionId, out EMapRegion result)) { return graphView; } Il2CppArrayBase<RelationCircle> val = Circles(fresh: true); if (val == null) { return graphView; } Dictionary<string, Vector2> dictionary = new Dictionary<string, Vector2>(StringComparer.OrdinalIgnoreCase); double num = double.MaxValue; double num2 = double.MaxValue; double num3 = double.MinValue; double num4 = double.MinValue; for (int i = 0; i < val.Length; i++) { RelationCircle val2 = val[i]; if ((Object)(object)val2 == (Object)null || (Object)(object)val2.AssignedNPC == (Object)null || (Object)(object)val2.Rect == (Object)null) { continue; } NPC assignedNPC = val2.AssignedNPC; if (assignedNPC.Region == result) { Vector2 anchoredPosition = val2.Rect.anchoredPosition; dictionary[assignedNPC.ID] = anchoredPosition; double num5 = (double)anchoredPosition.x / 1.6375; double num6 = (double)(0f - anchoredPosition.y) / 1.6375; graphView.Nodes.Add(NodeFor(assignedNPC, num5, num6)); if (num5 < num) { num = num5; } if (num6 < num2) { num2 = num6; } if (num5 > num3) { num3 = num5; } if (num6 > num4) { num4 = num6; } } } _readIds.Clear(); int num7 = 0; foreach (NodeView node in graphView.Nodes) { _readIds.Add(node.Id); if (node.HasFace) { num7++; } } _facesRead = num7; _facesSeen = num7; if (graphView.Nodes.Count == 0) { return graphView; } AddEdges(graphView, val, result, dictionary); graphView.MinX = num - 61.06870229007634; graphView.MinY = num2 - 61.06870229007634; graphView.Width = num3 - num + 122.13740458015268; graphView.Height = num4 - num2 + 122.13740458015268; foreach (NodeView node2 in graphView.Nodes) { node2.X -= graphView.MinX; node2.Y -= graphView.MinY; } foreach (EdgeView edge in graphView.Edges) { edge.X -= graphView.MinX; edge.Y -= graphView.MinY; } return graphView; } private static void AddEdges(GraphView view, Il2CppArrayBase<RelationCircle> circles, EMapRegion region, Dictionary<string, Vector2> placed) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0159: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); for (int i = 0; i < circles.Length; i++) { RelationCircle val = circles[i]; if ((Object)(object)val == (Object)null || (Object)(object)val.AssignedNPC == (Object)null) { continue; } NPC assignedNPC = val.AssignedNPC; if (assignedNPC.Region != region || assignedNPC.RelationData == null) { continue; } List<NPC> connections = assignedNPC.RelationData.Connections; if (connections == null) { continue; } for (int j = 0; j < connections.Count; j++) { NPC val2 = connections[j]; if (!((Object)(object)val2 == (Object)null) && val2.Region == region && placed.TryGetValue(assignedNPC.ID, out var value) && placed.TryGetValue(val2.ID, out var value2)) { string item = ((string.CompareOrdinal(assignedNPC.ID, val2.ID) < 0) ? (assignedNPC.ID + "|" + val2.ID) : (val2.ID + "|" + assignedNPC.ID)); if (hashSet.Add(item)) { Vector2 val3 = value2 - value; view.Edges.Add(new EdgeView { X = (double)(value.x + value2.x) / 2.0 / 1.6375, Y = (double)(0f - (value.y + value2.y)) / 2.0 / 1.6375, Length = (double)((Vector2)(ref val3)).magnitude / 1.6375, AngleDeg = Math.Atan2(val3.x, val3.y) * (180.0 / Math.PI) }); } } } } } private NodeView NodeFor(NPC npc, double x, double y) { NodeView nodeView = new NodeView { Id = npc.ID, Name = Text.Clean(npc.FullName), X = x, Y = y, Unlocked = (npc.RelationData != null && npc.RelationData.Unlocked), Relationship = ((npc.RelationData != null) ? npc.RelationData.RelationDelta : 0f), HasFace = _faces.Has(npc.ID) }; Customer component = ((Component)npc).GetComponent<Customer>(); if ((Object)(object)component != (Object)null) { nodeView.Addiction = component.CurrentAddiction; } nodeView.Supplier = (Object)(object)((Component)npc).GetComponent<Supplier>() != (Object)null; nodeView.Dealer = (Object)(object)((Component)npc).GetComponent<Dealer>() != (Object)null; nodeView.Kind = (nodeView.Dealer ? "dealer" : (nodeView.Supplier ? "supplier" : (((Object)(object)component != (Object)null) ? "customer" : ""))); nodeView.Hidden = !nodeView.Unlocked && Blacked(npc, nodeView); nodeView.RelationshipLabel = CategoryOf(nodeView.Relationship); nodeView.RelationshipColour = CategoryColour(nodeView.RelationshipLabel); return nodeView; } private static bool Blacked(NPC npc, NodeView node) { try { if (node.Dealer) { Dealer component = ((Component)npc).GetComponent<Dealer>(); return (Object)(object)component != (Object)null && !component.HasBeenRecommended; } if (node.Supplier) { return true; } return (Object)(object)((Component)npc).GetComponent<Customer>() != (Object)null && !npc.RelationData.IsMutuallyKnown(); } catch { return false; } } internal void WarmFaces(string regionId) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) if (!Enum.TryParse<EMapRegion>(regionId, out EMapRegion result)) { return; } Il2CppArrayBase<RelationCircle> val = Circles(); if (val == null) { return; } _faces.Warm(RegionFirst(People(val), result)); int num = 0; for (int i = 0; i < _readIds.Count; i++) { if (_faces.Has(_readIds[i])) { num++; } } if (num != _facesSeen) { _facesSeen = num; } else if (num != _facesRead) { _facesRead = num; PublishedFaces++; } } private IEnumerable<KeyValuePair<string, Sprite>> RegionFirst(List<(string Id, NPC Npc, EMapRegion Region)> people, EMapRegion region) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) for (int pass = 0; pass < 2; pass++) { for (int i = 0; i < people.Count; i++) { (string, NPC, EMapRegion) tuple = people[i]; if (tuple.Item3 == region == (pass == 0) && !_faces.Tried(tuple.Item1) && !((Object)(object)tuple.Item2 == (Object)null)) { yield return new KeyValuePair<string, Sprite>(tuple.Item1, tuple.Item2.MugshotSprite); } } } } private List<(string Id, NPC Npc, EMapRegion Region)> People(Il2CppArrayBase<RelationCircle> circles) { //IL_005e: Unknown result type (migh