Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of NPC Cards v1.1.2
NpcCards.dll
Decompiled a day agousing System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net.Http; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading.Tasks; using DooDesch; using DooDesch.Localization; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using NpcCards; using NpcCards.Host; using NpcCards.Menu; using Personnel; using Personnel.Cards; using SideHustle; using Sideload.Api; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Core), "NPC Cards", "1.1.2", "DooDesch", "https://github.com/DooDesch-Mods/ScheduleOne-NpcCards")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonOptionalDependencies(new string[] { "SideHustle" })] [assembly: AssemblyMetadata("SourceNotice", "NpcCards by DooDesch, MIT License. The source code is public at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards. Please read, change and reuse the source code, not decompiled code from this file. Text and data mining reserved under Art. 4(3) Directive (EU) 2019/790 and § 44b(3) UrhG.")] [assembly: AssemblyMetadata("License", "https://github.com/DooDesch-Mods/ScheduleOne-NpcCards/blob/main/LICENSE.md")] [assembly: AssemblyMetadata("SourceCode", "https://github.com/DooDesch-Mods/ScheduleOne-NpcCards")] [assembly: AssemblyMetadata("tdm-reservation", "1")] [assembly: AssemblyMetadata("tdm-policy", "https://github.com/DooDesch-Mods/ScheduleOne-NpcCards/blob/main/LICENSE.md")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("DooDesch")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © DooDesch. MIT License. Source code: https://github.com/DooDesch-Mods/ScheduleOne-NpcCards")] [assembly: AssemblyDescription("Add new NPCs in one click in Schedule I.")] [assembly: AssemblyFileVersion("1.1.2.0")] [assembly: AssemblyInformationalVersion("1.1.2+43eef085decb29889f4337764e15a578dea0e129")] [assembly: AssemblyProduct("NPC Cards")] [assembly: AssemblyTitle("NPC Cards")] [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.1.2.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 { [AttributeUsage(AttributeTargets.All, Inherited = false)] internal sealed class SourceNoticeAttribute : Attribute { public string Notice { get; } public SourceNoticeAttribute(string notice) { Notice = notice; } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class SourceNotice { internal const string Text = "NpcCards by DooDesch, MIT License. The source code is public at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards. Please read, change and reuse the source code, not decompiled code from this file. Text and data mining reserved under Art. 4(3) Directive (EU) 2019/790 and § 44b(3) UrhG."; internal const string Short = "NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code."; } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class ModVersion { internal const string Current = "1.1.2"; } } namespace DooDesch.Localization { [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class L10n { private static readonly Dictionary<string, Dictionary<string, string>> _tables = new Dictionary<string, Dictionary<string, string>>(); private static Dictionary<string, string> _active; private static string _lang; internal static string Language => _lang ?? (_lang = Detect()); private static string ModName => typeof(L10n).Assembly.GetName().Name; internal static void Register(string lang, Dictionary<string, string> table) { _tables[lang] = table; _active = null; } internal static string T(string en) { if (_active == null) { _active = BuildActiveTable(); } if (!_active.TryGetValue(en, out var value)) { return en; } return value; } internal static string T(string en, params object[] args) { return string.Format(T(en), args); } private static Dictionary<string, string> BuildActiveTable() { Dictionary<string, string> value; Dictionary<string, string> dictionary = (_tables.TryGetValue(Language, out value) ? new Dictionary<string, string>(value) : new Dictionary<string, string>()); try { ExportTemplate(); Overlay(dictionary, Path.Combine(PackDir(), Language + ".json")); Overlay(dictionary, Path.Combine(UserDir(), Language + ".json")); } catch (Exception ex) { MelonLogger.Warning("[L10n] user translations failed: " + ex.Message); } return dictionary; } private static void Overlay(Dictionary<string, string> table, string file) { if (!File.Exists(file)) { return; } Dictionary<string, string> dictionary = ParseJsonObject(File.ReadAllText(file)); if (dictionary == null) { MelonLogger.Warning("[L10n] ignoring invalid translation file (flat JSON object of \"english\": \"translated\" strings expected): " + file); return; } foreach (KeyValuePair<string, string> item in dictionary) { table[item.Key] = item.Value; } } private static string PackDir() { return Path.Combine(MelonEnvironment.ModsDirectory, "Localization", ModName); } private static string UserDir() { return Path.Combine(MelonEnvironment.UserDataDirectory, "DooDesch", "Localization", ModName); } private static void ExportTemplate() { if (_tables.Count == 0) { return; } SortedSet<string> sortedSet = new SortedSet<string>(StringComparer.Ordinal); foreach (Dictionary<string, string> value in _tables.Values) { foreach (string key in value.Keys) { sortedSet.Add(key); } } if (sortedSet.Count == 0) { return; } Directory.CreateDirectory(UserDir()); WriteTemplate("en", sortedSet, null); foreach (string key2 in _tables.Keys) { if (key2 != "en") { WriteTemplate(key2, sortedSet, _tables[key2]); } } } private static void WriteTemplate(string lang, SortedSet<string> keys, Dictionary<string, string> table) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("{\n ").Append(Quote("_readme")).Append(": ") .Append(Quote("This file is regenerated on every game start - do not edit it. To change or add a translation, copy it to <language code>.json (for example " + lang + ".json), keep only the lines you want to change, and translate the VALUES only - the keys are the mod's English source strings and must stay unchanged. Keep placeholders like {0}. Set Language in MelonPreferences.cfg under [DooDesch] to force a language (auto = OS language). To share your translation as an installable mod, see https://github.com/DooDesch-Mods/ScheduleOne-L10n/wiki")) .Append(",\n"); foreach (string key in keys) { string value; string s = ((table != null && table.TryGetValue(key, out value)) ? value : key); stringBuilder.Append(" ").Append(Quote(key)).Append(": ") .Append(Quote(s)) .Append(",\n"); } stringBuilder.Length -= 2; stringBuilder.Append("\n}\n"); File.WriteAllText(Path.Combine(UserDir(), "_template." + lang + ".json"), stringBuilder.ToString(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); } private static string Detect() { //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Invalid comparison between Unknown and I4 //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Invalid comparison between Unknown and I4 //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Invalid comparison between Unknown and I4 //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Invalid comparison between Unknown and I4 //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected I4, but got Unknown //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Invalid comparison between Unknown and I4 //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Invalid comparison between Unknown and I4 //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Invalid comparison between Unknown and I4 //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Invalid comparison between Unknown and I4 string text = "auto"; try { MelonPreferences_Category val = MelonPreferences.CreateCategory("DooDesch", "DooDesch Mods"); text = ((val.GetEntry<string>("Language") ?? val.CreateEntry<string>("Language", "auto", "Language", "Language for all DooDesch mods: auto (= OS language), en, de, ...", false, false, (ValueValidator)null, (string)null)).Value ?? "auto").Trim().ToLowerInvariant(); } catch { } if (text.Length > 0 && text != "auto") { return text; } SystemLanguage systemLanguage = Application.systemLanguage; if ((int)systemLanguage <= 15) { if ((int)systemLanguage == 6) { goto IL_0121; } if ((int)systemLanguage == 14) { return "fr"; } if ((int)systemLanguage == 15) { return "de"; } } else if ((int)systemLanguage <= 34) { switch (systemLanguage - 21) { default: if ((int)systemLanguage != 34) { break; } return "es"; case 0: return "it"; case 7: return "pt"; case 6: return "pl"; case 9: return "ru"; case 1: return "ja"; case 2: return "ko"; case 3: case 4: case 5: case 8: break; } } else { if ((int)systemLanguage == 37) { return "tr"; } if (systemLanguage - 40 <= 1) { goto IL_0121; } } return "en"; IL_0121: return "zh"; } private static Dictionary<string, string> ParseJsonObject(string json) { if (json == null) { return null; } int i = 0; Dictionary<string, string> dictionary = new Dictionary<string, string>(); SkipWs(json, ref i); if (i >= json.Length || json[i] != '{') { return null; } i++; SkipWs(json, ref i); if (i < json.Length && json[i] == '}') { return dictionary; } while (true) { string text = ParseString(json, ref i); if (text == null) { return null; } SkipWs(json, ref i); if (i >= json.Length || json[i] != ':') { return null; } i++; SkipWs(json, ref i); string text2 = ParseString(json, ref i); if (text2 == null) { return null; } dictionary[text] = text2; SkipWs(json, ref i); if (i >= json.Length) { return null; } if (json[i] != ',') { break; } i++; SkipWs(json, ref i); if (i < json.Length && json[i] == '}') { return dictionary; } } if (json[i] != '}') { return null; } return dictionary; } private static void SkipWs(string s, ref int i) { while (i < s.Length && (char.IsWhiteSpace(s[i]) || s[i] == '\ufeff')) { i++; } } private static string ParseString(string s, ref int i) { SkipWs(s, ref i); if (i >= s.Length || s[i] != '"') { return null; } i++; StringBuilder stringBuilder = new StringBuilder(); while (i < s.Length) { char c = s[i++]; switch (c) { case '"': return stringBuilder.ToString(); default: stringBuilder.Append(c); break; case '\\': if (i >= s.Length) { return null; } switch (s[i++]) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': if (i + 4 > s.Length) { return null; } try { stringBuilder.Append((char)Convert.ToInt32(s.Substring(i, 4), 16)); } catch { return null; } i += 4; break; default: return null; } break; } } return null; } private static string Quote(string s) { StringBuilder stringBuilder = new StringBuilder(s.Length + 2); stringBuilder.Append('"'); foreach (char c in s) { switch (c) { case '"': stringBuilder.Append("\\\""); continue; case '\\': stringBuilder.Append("\\\\"); continue; case '\n': stringBuilder.Append("\\n"); continue; case '\r': stringBuilder.Append("\\r"); continue; case '\t': stringBuilder.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder2 = stringBuilder.Append("\\u"); int num = c; stringBuilder2.Append(num.ToString("x4")); } else { stringBuilder.Append(c); } } stringBuilder.Append('"'); return stringBuilder.ToString(); } } } namespace Sideload.Api { [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] 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; private static Action<string> _reloadApp; private static Action<string> _unregisterApp; private static Action<string, string> _allowGameWrites; public static bool Available { get { EnsureBound(); return _bound; } } internal static bool HasLiveEditing { get { EnsureBound(); if (_reloadApp != null) { return _unregisterApp != null; } return false; } } 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; } internal static void ReloadApp(string appId) { _reloadApp?.Invoke(appId); } internal static void UnregisterApp(string appId) { _unregisterApp?.Invoke(appId); } internal static void AllowGameWrites(string appId, string scope) { _allowGameWrites?.Invoke(appId, scope); } 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"); _reloadApp = Get<Action<string>>(type, "ReloadApp"); _unregisterApp = Get<Action<string>>(type, "UnregisterApp"); _allowGameWrites = Get<Action<string, string>>(type, "AllowGameWrites"); _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; } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] 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; } } public static bool CanEditLive { get { if (Apps.Available) { return Apps.HasLiveEditing; } 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 Reload() { string id = _id; Apps.WhenBound(delegate { Apps.ReloadApp(id); }); return this; } public void Unregister() { string id = _id; Apps.WhenBound(delegate { Apps.UnregisterApp(id); }); } public AppHandle AllowGameWrites(string scope) { string id = _id; Apps.WhenBound(delegate { Apps.AllowGameWrites(id, scope); }); return this; } public AppHandle Hide() { string id = _id; Apps.WhenBound(delegate { Apps.OpenApp(id, open: false); Apps.RaisePhone(raised: false); }); return this; } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] 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); } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] 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); } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] 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 NpcCards { [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] public sealed class Core : MelonMod { public static Core Instance { get; private set; } public static Instance Log { get; private set; } public override void OnInitializeMelon() { Instance = this; Log = ((MelonBase)this).LoggerInstance; Strings.Register(); bool num = PersonnelCheck.Detect(); if (num) { StartCards(); } Calls.RegisterPhone(); MenuEntry.TryRegister(); if (num) { LogReady(); } else { Log.Msg("NPC Cards " + ((MelonBase)this).Info.Version + " ready without Personnel: the app only shows the notice."); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void StartCards() { Library.Start(); } [MethodImpl(MethodImplOptions.NoInlining)] private void LogReady() { Log.Msg($"NPC Cards {((MelonBase)this).Info.Version} ready (Personnel cards API, vault {Vault.BaseUrl})."); } public override void OnUpdate() { MainThread.Drain(); MenuSurface.Tick(); } public override void OnSceneWasUnloaded(int buildIndex, string sceneName) { if (sceneName == "Menu") { MenuSurface.Close("the menu scene unloaded"); } } } } namespace NpcCards.Menu { [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class MenuEntry { internal const string GamemodeId = "doodesch.npccards"; private static LaunchContext _ctx; internal static void TryRegister() { if (!AppDomain.CurrentDomain.GetAssemblies().Any((Assembly a) => a.GetName().Name == "SideHustle")) { Core.Log.Msg("[menu] Side Hustle is not installed - NPC Cards is a phone app only."); return; } try { Register(); } catch (Exception ex) { Core.Log.Warning("[menu] registering with Side Hustle failed (" + ex.Message + ") - NPC Cards is a phone app only."); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void Register() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //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) //IL_002b: 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_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //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_0065: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown API.Register(new GamemodeDescriptor { Id = "doodesch.npccards", DisplayName = "NPC Cards", Description = Strings.T("Add new NPCs in one click."), Author = "DooDesch", Support = (GamemodeSupport)0, Surface = (GamemodeSurface)0, OnLaunchSingleplayer = OnLaunch, OnExitToHub = OnExit }); Core.Log.Msg("[menu] registered with Side Hustle as a main-menu gamemode."); } private static void OnLaunch(LaunchContext ctx) { _ctx = ctx; MenuSurface.Open(ReturnToHub); } private static void OnExit(LaunchContext ctx) { _ctx = null; MenuSurface.Close("Side Hustle left the gamemode"); } private static void ReturnToHub() { LaunchContext ctx = _ctx; if (ctx == null) { Core.Log.Warning("[menu] back to the hub asked without a launch context - closing the page only."); MenuSurface.Close("no launch context"); return; } try { ctx.ReturnToHub(); } catch (Exception ex) { Core.Log.Error("[menu] returning to the Side Hustle hub failed: " + ex.Message); MenuSurface.Close("returning to the hub failed"); } } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class MenuSurface { private const string SurfaceId = "npccards-menu"; private static GameObject _root; private static Action _returnToHub; private static bool _closeRequested; internal static bool IsOpen => (Object)(object)_root != (Object)null; internal static void Open(Action returnToHub) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Expected O, but got Unknown _returnToHub = returnToHub; if (!Surfaces.Available) { Core.Log.Error("[menu] Sideload 1.36 or newer is needed for the menu page - it was not found."); returnToHub?.Invoke(); return; } if ((Object)(object)_root != (Object)null) { Close("opened again"); } _root = new GameObject("NpcCards_Menu"); Object.DontDestroyOnLoad((Object)(object)_root); Canvas obj = _root.AddComponent<Canvas>(); obj.renderMode = (RenderMode)0; obj.sortingOrder = 30000; _root.AddComponent<GraphicRaycaster>(); SurfaceHandle surface = Surfaces.Mount(_root.GetComponent<RectTransform>(), "npccards-menu", "NpcCards.Assets.app", 720f); if (!Surfaces.IsMounted("npccards-menu")) { Core.Log.Error("[menu] the page could not be mounted - see the Sideload log lines above."); Close("mount failed"); returnToHub?.Invoke(); } else { Calls.RegisterMenu(surface); Pages.AttachMenu(surface); Core.Log.Msg("[menu] page mounted over the main menu."); } } internal static void Close(string why) { if (!((Object)(object)_root == (Object)null)) { Pages.DetachMenu(); try { Surfaces.Unmount("npccards-menu"); } catch (Exception ex) { Core.Log.Warning("[menu] unmounting the page failed: " + ex.Message); } Object.Destroy((Object)(object)_root); _root = null; Core.Log.Msg("[menu] page closed (" + why + ")."); } } internal static JObject RequestClose(string where) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Expected O, but got Unknown if (where != "menu" || (Object)(object)_root == (Object)null) { Core.Log.Warning($"[menu] close asked from the {where} page while the menu page is {(IsOpen ? "open" : "closed")} - ignored."); return Calls.Fail("Something went wrong. See the log."); } _closeRequested = true; return new JObject { ["ok"] = JToken.op_Implicit(true) }; } internal static void Tick() { if ((Object)(object)_root == (Object)null) { return; } if (_closeRequested) { _closeRequested = false; Action returnToHub = _returnToHub; if (returnToHub != null) { returnToHub(); } else { Close("closed from the page"); } } else if (Input.GetKeyDown((KeyCode)27) || Input.GetMouseButtonDown(1)) { Pages.EmitMenu("nav.back", null); } } } } namespace NpcCards.Host { [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class Calls { internal const string AppId = "npccards"; internal const string Bundle = "NpcCards.Assets.app"; internal const string PersonifyUrl = "https://thunderstore.io/c/schedule-i/p/DooDesch/Personify/"; private static readonly Dictionary<string, Func<JObject, string, JObject>> Handlers = new Dictionary<string, Func<JObject, string, JObject>>(StringComparer.Ordinal) { ["init"] = (JObject a, string where) => Init(where), ["vault.state"] = (JObject a, string _) => Vault.State(), ["vault.get"] = (JObject a, string _) => Vault.Get((string)a["id"], (string)a["path"]), ["vault.install"] = (JObject a, string _) => Vault.Install((string)a["id"], (string)a["code"]), ["vault.thumb"] = (JObject a, string _) => Vault.Thumb((string)a["code"]), ["vault.connect"] = (JObject a, string _) => Vault.Connect(), ["vault.disconnect"] = (JObject a, string _) => Vault.Disconnect(), ["vault.upload"] = (JObject a, string _) => Vault.Upload((string)a["id"], (string)a["file"]), ["cards.installed"] = (JObject a, string _) => Library.List(), ["cards.remove"] = (JObject a, string _) => Library.Remove((string)a["code"]), ["cards.importFiles"] = (JObject a, string _) => Library.ImportFiles(), ["cards.importFile"] = (JObject a, string _) => Library.ImportFile((string)a["file"]), ["cards.openFolder"] = (JObject a, string _) => Library.OpenFolder(), ["cards.inbox"] = (JObject a, string _) => new JObject { ["ok"] = JToken.op_Implicit(true), ["cards"] = (JToken)(object)Library.Arrivals() }, ["cards.uploadFiles"] = (JObject a, string _) => Library.UploadFiles(), ["open.url"] = (JObject a, string _) => Vault.OpenUrl((string)a["url"]), ["copy"] = (JObject a, string _) => Vault.Copy((string)a["text"]), ["siesta.hint"] = (JObject a, string _) => SiestaHint.State(), ["siesta.close"] = (JObject a, string _) => SiestaHint.Close(), ["menu.close"] = (JObject a, string where) => MenuSurface.RequestClose(where) }; private static readonly HashSet<string> WithoutPersonnel = new HashSet<string>(StringComparer.Ordinal) { "init", "menu.close" }; internal static void RegisterPhone() { try { AppHandle appHandle = Apps.Register("npccards", "NpcCards.Assets.app", "NPC Cards", "NPC Cards"); appHandle.Orientation("landscape", "portrait"); foreach (KeyValuePair<string, Func<JObject, string, JObject>> handler in Handlers) { KeyValuePair<string, Func<JObject, string, JObject>> h = handler; appHandle.OnCall(h.Key, (string arg) => Run(h.Key, arg, "phone", h.Value)); } Pages.Phone = appHandle; Core.Log.Msg(Apps.Available ? "[phone] app registered with Sideload." : "[phone] Sideload is not loaded yet - the app registration is queued."); } catch (Exception value) { Core.Log.Error($"[phone] registering the phone app failed: {value}"); } } internal static void RegisterMenu(SurfaceHandle surface) { foreach (KeyValuePair<string, Func<JObject, string, JObject>> handler in Handlers) { KeyValuePair<string, Func<JObject, string, JObject>> h = handler; surface.OnCall(h.Key, (string arg) => Run(h.Key, arg, "menu", h.Value)); } } private static string Run(string name, string arg, string where, Func<JObject, string, JObject> handler) { //IL_0088: Unknown result type (might be due to invalid IL or missing references) try { if (!PersonnelCheck.Loaded && !WithoutPersonnel.Contains(name)) { Core.Log.Warning($"[{where}] {name} refused: Personnel is not loaded."); return ((JToken)Fail("Personnel is not loaded.")).ToString((Formatting)0); } JObject arg2 = (JObject)((!string.IsNullOrWhiteSpace(arg)) ? ((object)JObject.Parse(arg)) : ((object)new JObject())); return ((JToken)(handler(arg2, where) ?? Fail("Something went wrong. See the log."))).ToString((Formatting)0); } catch (Exception value) { Core.Log.Error($"[{where}] {name}({arg}) failed: {value}"); return ((JToken)Fail("Something went wrong. See the log.")).ToString((Formatting)0); } } internal static JObject Fail(string en) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown return new JObject { ["ok"] = JToken.op_Implicit(false), ["msg"] = JToken.op_Implicit(Strings.T(en)) }; } private static JObject Init(string where) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Expected O, but got Unknown if (!PersonnelCheck.Loaded) { return new JObject { ["ok"] = JToken.op_Implicit(true), ["where"] = JToken.op_Implicit(where), ["version"] = JToken.op_Implicit("1.1.2"), ["strings"] = (JToken)(object)Strings.ForPage(), ["personnelMissing"] = JToken.op_Implicit(true) }; } return FullInit(where); } [MethodImpl(MethodImplOptions.NoInlining)] private static JObject FullInit(string where) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown return new JObject { ["ok"] = JToken.op_Implicit(true), ["where"] = JToken.op_Implicit(where), ["version"] = JToken.op_Implicit("1.1.2"), ["strings"] = (JToken)(object)Strings.ForPage(), ["vault"] = (JToken)(object)Vault.State(), ["personifyUrl"] = JToken.op_Implicit("https://thunderstore.io/c/schedule-i/p/DooDesch/Personify/"), ["importFolder"] = JToken.op_Implicit(Library.ImportDir) }; } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class Library { private sealed class Arrival { public string Code; public string Name; public DateTime At; } private static Dictionary<string, string> _loaded; private static readonly Dictionary<string, string> Names = new Dictionary<string, string>(StringComparer.Ordinal); private static readonly List<Arrival> Inbox = new List<Arrival>(); private static HashSet<string> _known; private static readonly Dictionary<string, JObject> FileCache = new Dictionary<string, JObject>(StringComparer.OrdinalIgnoreCase); internal static string ImportDir => CardsApi.ImportFolder(); private static string DoneDir => Path.Combine(ImportDir, "Done"); internal static void Start() { List<JObject> list = Installed(); _loaded = list.ToDictionary<JObject, string, string>((JObject c) => (string)c["code"], (JObject c) => ((string)c["contentHash"]) ?? "", StringComparer.Ordinal); _known = new HashSet<string>(_loaded.Keys, StringComparer.Ordinal); foreach (JObject item in list) { Names[(string)item["code"]] = (string)item["name"]; } if (CardsApi.PendingRestart()) { Core.Log.Warning("[cards] Personnel already reports changed cards at our start; the 'after the next start' count may miss some."); } CardLibrary.Changed += delegate { MainThread.Post("cards changed", OnChanged); }; Core.Log.Msg($"[cards] {_loaded.Count} card(s) loaded this game; import folder {ImportDir}."); } private static List<JObject> Installed() { try { return (from c in ((IEnumerable)JArray.Parse(CardsApi.List())).OfType<JObject>() where !string.IsNullOrEmpty((string)c["code"]) select c).ToList(); } catch (Exception ex) { Core.Log.Error("[cards] reading the installed cards from Personnel failed: " + ex.Message); return new List<JObject>(); } } private static void OnChanged() { foreach (JObject item in Installed()) { string text = (string)item["code"]; Names[text] = (string)item["name"]; if (_known.Add(text) && (((string)item["source"]) ?? "").StartsWith("inbox ", StringComparison.Ordinal)) { Inbox.Insert(0, new Arrival { Code = text, Name = (((string)item["name"]) ?? text), At = DateTime.Now }); Core.Log.Msg($"[cards] '{(string)item["name"]}' ({text}) arrived from the website."); } } Pages.Emit("cards.changed", (JToken)(object)Summary()); } internal static void AfterInstall(JObject r) { if ((bool?)r["ok"] == true && (bool?)r["alreadyInstalled"] != true) { Pages.Notify(((string)r["name"]) ?? ((string)r["code"]), Strings.T("Arrives after the next game start.")); } } internal static JObject Summary() { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Expected O, but got Unknown //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Expected O, but got Unknown //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Expected O, but got Unknown List<JObject> source = Installed(); Dictionary<string, string> nowCodes = source.ToDictionary<JObject, string, string>((JObject c) => (string)c["code"], (JObject c) => ((string)c["contentHash"]) ?? "", StringComparer.Ordinal); JArray val = new JArray((object)(from c in source where !_loaded.ContainsKey((string)c["code"]) select (string)c["name"])); string value; JArray val2 = new JArray((object)(from c in source where _loaded.TryGetValue((string)c["code"], out value) && value != (((string)c["contentHash"]) ?? "") select (string)c["name"])); JArray val3 = new JArray((object)(from k in _loaded.Keys where !nowCodes.ContainsKey(k) select (!Names.TryGetValue(k, out value) || value == null) ? k : value)); return new JObject { ["arriving"] = (JToken)(object)val, ["leaving"] = (JToken)(object)val3, ["changed"] = (JToken)(object)val2 }; } internal static JObject List() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //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) //IL_0046: 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) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Expected O, but got Unknown //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0189: 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_01a6: Expected O, but got Unknown JArray val = new JArray(); foreach (JObject item in Installed()) { string text = (string)item["code"]; val.Add((JToken)new JObject { ["code"] = JToken.op_Implicit(text), ["name"] = JToken.op_Implicit(((string)item["name"]) ?? text), ["role"] = JToken.op_Implicit((string)item["role"]), ["creator"] = JToken.op_Implicit((string)item["creator"]), ["importedAt"] = item["importedAt"], ["basedOn"] = JToken.op_Implicit((string)item["basedOn"]), ["loaded"] = JToken.op_Implicit(_loaded.ContainsKey(text)), ["picture"] = JToken.op_Implicit(Portrait(text)) }); } Core.Log.Msg($"[cards] listed {((JContainer)val).Count} installed card(s)."); return new JObject { ["ok"] = JToken.op_Implicit(true), ["cards"] = (JToken)(object)val, ["next"] = (JToken)(object)Summary() }; } private static string Portrait(string code) { string text = "portrait-" + code; if (Pages.HasPicture(text)) { return "s1://" + text; } string path = Path.Combine(CardLibrary.PackDir, code, "portrait.png"); if (!File.Exists(path)) { return null; } try { return Pages.Picture(text, File.ReadAllBytes(path)); } catch (Exception ex) { Core.Log.Warning($"[cards] reading the picture of {code} failed ({ex.Message}); the row shows none."); return null; } } internal static JObject Remove(string code) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown if (!CardLibrary.IsValidCode(code)) { Core.Log.Warning("[cards] remove refused: '" + code + "' is not a card code."); return Calls.Fail("Not a card code."); } bool flag = CardsApi.Remove(code); Core.Log.Msg($"[cards] remove {code}: {(flag ? "removed" : "no such card")}."); if (!flag) { return Calls.Fail("This card is not installed."); } return new JObject { ["ok"] = JToken.op_Implicit(true), ["next"] = (JToken)(object)Summary() }; } internal static JObject ImportFiles() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Expected O, but got Unknown JArray val = new JArray(); string importDir = ImportDir; try { Directory.CreateDirectory(importDir); foreach (FileInfo item in new DirectoryInfo(importDir).GetFiles("*.png").OrderBy<FileInfo, string>((FileInfo f) => f.Name, StringComparer.OrdinalIgnoreCase)) { val.Add((JToken)(object)Describe(item)); } } catch (Exception ex) { Core.Log.Error("[cards] listing " + importDir + " failed: " + ex.Message); return Calls.Fail("The import folder could not be read."); } Core.Log.Msg($"[cards] import folder: {((JContainer)val).Count} file(s) in {importDir}."); return new JObject { ["ok"] = JToken.op_Implicit(true), ["folder"] = JToken.op_Implicit(importDir), ["files"] = (JToken)(object)val }; } private static string AuthorOf(JObject meta) { object obj; if (meta == null) { obj = null; } else { JToken obj2 = meta["credit"]; obj = ((obj2 != null) ? obj2[(object)"author"] : null); } string text = ((string)(JToken)obj)?.Trim(); if (((meta != null) ? meta["basedOn"] : null) is JObject || string.IsNullOrEmpty(text)) { return (string)((meta != null) ? meta["creator"] : null); } return text; } private static JObject Describe(FileInfo fi) { //IL_0062: 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_007d: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Expected O, but got Unknown string text = fi.FullName + "|" + fi.LastWriteTimeUtc.Ticks + "|" + fi.Length; if (FileCache.TryGetValue(text, out var value)) { return value; } JObject val = new JObject { ["file"] = JToken.op_Implicit(fi.Name), ["sizeKb"] = JToken.op_Implicit(fi.Length / 1024) }; try { byte[] array = File.ReadAllBytes(fi.FullName); JObject val2 = JObject.Parse(CardsApi.ReadCard(array)); if ((bool?)val2["ok"] == true) { JToken obj = val2["npc"]; JObject val3 = (JObject)(object)((obj is JObject) ? obj : null); val["card"] = JToken.op_Implicit(true); val["name"] = JToken.op_Implicit(((string)((val3 != null) ? val3["name"] : null)) ?? Path.GetFileNameWithoutExtension(fi.Name)); val["role"] = JToken.op_Implicit(CardLibrary.RoleOf(val3)); ? val4 = val; JToken obj2 = val2["meta"]; ((JObject)val4)["creator"] = JToken.op_Implicit(AuthorOf((JObject)(object)((obj2 is JObject) ? obj2 : null))); val["picture"] = JToken.op_Implicit(Pages.Picture("file-" + Fnv(text), array)); } else { val["card"] = JToken.op_Implicit(false); val["message"] = JToken.op_Implicit((string)val2["message"]); Core.Log.Msg($"[cards] {fi.Name} in the import folder is not a card: {(string)val2["error"]} {(string)val2["message"]}"); } } catch (Exception ex) { val["card"] = JToken.op_Implicit(false); val["message"] = JToken.op_Implicit(ex.Message); Core.Log.Warning("[cards] reading " + fi.Name + " from the import folder failed: " + ex.Message); } FileCache[text] = val; return val; } internal static JObject ImportFile(string file) { string text = InFolder(ImportDir, file); if (text == null) { return Calls.Fail("File not found."); } JObject val = JObject.Parse(CardsApi.ImportFile(text)); if ((bool?)val["ok"] != true) { Core.Log.Error($"[cards] installing {file} from the import folder failed: {(string)val["error"]} {(string)val["message"]}"); val["message"] = JToken.op_Implicit(((string)val["message"]) ?? Strings.T("This file is not a card.")); return val; } Core.Log.Msg($"[cards] {file} installed as '{(string)val["name"]}' ({(string)val["code"]}){(((bool?)val["alreadyInstalled"] == true) ? ", already installed" : "")}."); MoveToDone(text); AfterInstall(val); return val; } private static void MoveToDone(string full) { try { Directory.CreateDirectory(DoneDir); string text = Path.Combine(DoneDir, Path.GetFileName(full)); if (File.Exists(text)) { text = Path.Combine(DoneDir, Path.GetFileNameWithoutExtension(full) + "-" + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".png"); } File.Move(full, text); Core.Log.Msg($"[cards] moved {Path.GetFileName(full)} to {text}."); } catch (Exception ex) { Core.Log.Warning($"[cards] moving {full} to {DoneDir} failed ({ex.Message}); Personnel installs it again at the next start and finds it already there."); } } internal static JObject OpenFolder() { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Expected O, but got Unknown string importDir = ImportDir; try { Directory.CreateDirectory(importDir); Application.OpenURL(new Uri(importDir).AbsoluteUri); Core.Log.Msg("[cards] opened " + importDir + " in the file browser."); return new JObject { ["ok"] = JToken.op_Implicit(true), ["folder"] = JToken.op_Implicit(importDir) }; } catch (Exception ex) { Core.Log.Error("[cards] opening " + importDir + " failed: " + ex.Message); return Calls.Fail("The folder could not be opened."); } } internal static JObject UploadFiles() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Expected O, but got Unknown JArray val = new JArray(); string[] array = new string[2] { ImportDir, DoneDir }; foreach (string text in array) { if (!Directory.Exists(text)) { continue; } try { foreach (FileInfo item in from f in new DirectoryInfo(text).GetFiles("*.png") orderby f.LastWriteTimeUtc descending select f) { JObject val2 = Describe(item); if ((bool?)val2["card"] == true) { JObject val3 = (JObject)((JToken)val2).DeepClone(); val3["file"] = JToken.op_Implicit((text == DoneDir) ? ("Done/" + item.Name) : item.Name); val.Add((JToken)(object)val3); } } } catch (Exception ex) { Core.Log.Error("[cards] listing " + text + " for uploads failed: " + ex.Message); } } Core.Log.Msg($"[cards] {((JContainer)val).Count} card file(s) can be uploaded."); return new JObject { ["ok"] = JToken.op_Implicit(true), ["files"] = (JToken)(object)val }; } internal static string UploadPath(string file) { if (file != null && file.StartsWith("Done/", StringComparison.Ordinal)) { return InFolder(DoneDir, file.Substring(5)); } return InFolder(ImportDir, file); } private static string InFolder(string dir, string file) { if (string.IsNullOrEmpty(file) || file.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || file.Contains("..")) { Core.Log.Warning($"[cards] '{file}' is not a file name in {dir}."); return null; } string text = Path.Combine(dir, file); if (!File.Exists(text)) { Core.Log.Warning("[cards] " + text + " does not exist."); return null; } return text; } internal static JArray Arrivals() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Expected O, but got Unknown return new JArray((object)((IEnumerable<Arrival>)Inbox).Select((Func<Arrival, JObject>)((Arrival a) => new JObject { ["code"] = JToken.op_Implicit(a.Code), ["name"] = JToken.op_Implicit(a.Name), ["at"] = JToken.op_Implicit(a.At.ToString("HH:mm")), ["picture"] = JToken.op_Implicit(Portrait(a.Code)) }))); } private static string Fnv(string s) { uint num = 2166136261u; byte[] bytes = Encoding.UTF8.GetBytes(s); foreach (byte b in bytes) { num ^= b; num *= 16777619; } return num.ToString("x8"); } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class MainThread { private static readonly ConcurrentQueue<(string what, Action run)> Queue = new ConcurrentQueue<(string, Action)>(); internal static void Post(string what, Action run) { if (run != null) { Queue.Enqueue((what, run)); } } internal static void Drain() { int num = 0; (string, Action) result; while (num++ < 32 && Queue.TryDequeue(out result)) { try { result.Item2(); } catch (Exception value) { Core.Log.Error($"[main] {result.Item1} failed on the main thread: {value}"); } } } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class Pages { internal static AppHandle Phone; private static SurfaceHandle _menu; private static readonly Dictionary<string, byte[]> Pictures = new Dictionary<string, byte[]>(StringComparer.Ordinal); internal static void AttachMenu(SurfaceHandle surface) { _menu = surface; foreach (KeyValuePair<string, byte[]> picture in Pictures) { surface.Image(picture.Key, picture.Value); } Core.Log.Msg($"[pages] menu page attached, {Pictures.Count} picture(s) handed over."); } internal static void DetachMenu() { _menu = null; } internal static bool HasPicture(string name) { return Pictures.ContainsKey(name); } internal static void Emit(string name, JToken payload) { string payload2 = ((payload != null) ? payload.ToString((Formatting)0) : null) ?? ""; if (Phone == null && _menu == null) { Core.Log.Warning("[pages] event '" + name + "' dropped: no page is registered."); return; } Phone?.Emit(name, payload2); _menu?.Emit(name, payload2); } internal static void EmitMenu(string name, JToken payload) { _menu?.Emit(name, ((payload != null) ? payload.ToString((Formatting)0) : null) ?? ""); } internal static string Picture(string name, byte[] png) { Pictures[name] = png; Phone?.Image(name, png); _menu?.Image(name, png); return "s1://" + name; } internal static void Notify(string title, string text) { if (Phone == null) { Core.Log.Warning($"[pages] notification '{title}: {text}' dropped: the phone app is not registered."); } else { Phone.Notify(title, text, 6f); Core.Log.Msg("[pages] notification: " + title + ": " + text); } } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class PersonnelCheck { internal const string Notice = "Personnel is not loaded."; private const string ProbeType = "Personnel.Cards.CardsApi, Personnel"; internal static bool Loaded { get; private set; } internal static bool Detect() { try { Loaded = Type.GetType("Personnel.Cards.CardsApi, Personnel", throwOnError: false) != null; } catch (Exception ex) { Core.Log.Error("[personnel] looking for Personnel.Cards.CardsApi, Personnel failed: " + ex.Message); Loaded = false; } if (Loaded) { Core.Log.Msg("[personnel] found."); } else { Core.Log.Error("[personnel] not loaded: the app only shows a notice. Personnel.dll has to sit directly in Mods."); } return Loaded; } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class SiestaHint { internal const string Url = "https://thunderstore.io/c/schedule-i/p/DooDesch/Siesta/"; private const string MelonName = "Siesta"; private const string DllName = "Siesta.dll"; private const int Threshold = 40; private static bool _closed; internal static JObject State() { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: 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) //IL_0124: Expected O, but got Unknown int num = -1; try { num = API.All.Count; } catch (Exception ex) { Core.Log.Warning("[siesta hint] reading the NPC count from Personnel failed (" + ex.Message + ") - no hint."); } bool flag = IsInstalled(); Core.Log.Msg($"[siesta hint] {num} custom NPC(s), threshold {40}, Siesta installed: {flag}, closed: {_closed}."); return new JObject { ["ok"] = JToken.op_Implicit(true), ["offer"] = JToken.op_Implicit(num >= 0 && !flag && !_closed), ["npcs"] = JToken.op_Implicit(num), ["threshold"] = JToken.op_Implicit(40), ["url"] = JToken.op_Implicit("https://thunderstore.io/c/schedule-i/p/DooDesch/Siesta/") }; } internal static JObject Close() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown _closed = true; Core.Log.Msg("[siesta hint] closed by the player until the game is closed."); return new JObject { ["ok"] = JToken.op_Implicit(true) }; } private static bool IsInstalled() { try { if (MelonBase.RegisteredMelons.Any((MelonBase m) => m.Info.Name == "Siesta")) { return true; } return File.Exists(Path.Combine(MelonEnvironment.ModsDirectory, "Siesta.dll")); } catch (Exception ex) { Core.Log.Warning("[siesta hint] checking for Siesta failed (" + ex.Message + ") - treated as installed, no hint."); return true; } } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class Strings { private const string Resource = "NpcCards.Assets.app.de.json"; private static readonly Dictionary<string, string> German = new Dictionary<string, string>(StringComparer.Ordinal); internal static void Register() { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Invalid comparison between Unknown and I4 try { using Stream stream = typeof(Strings).Assembly.GetManifestResourceStream("NpcCards.Assets.app.de.json"); if (stream == null) { Core.Log.Warning("[l10n] NpcCards.Assets.app.de.json is not embedded - the app shows English only."); return; } using StreamReader streamReader = new StreamReader(stream); foreach (JProperty item in JObject.Parse(streamReader.ReadToEnd()).Properties()) { if ((int)item.Value.Type == 8) { German[item.Name] = (string)item.Value; } } L10n.Register("de", German); Core.Log.Msg($"[l10n] {German.Count} German strings registered; language {L10n.Language}."); } catch (Exception ex) { Core.Log.Error($"[l10n] reading {"NpcCards.Assets.app.de.json"} failed ({ex.Message}) - the app shows English only."); } } internal static string T(string en) { return L10n.T(en); } internal static string T(string en, params object[] args) { try { return string.Format(T(en), args); } catch (FormatException ex) { Core.Log.Warning($"[l10n] translation of '{en}' has broken placeholders ({ex.Message}) - English is shown."); return string.Format(en, args); } } internal static JObject ForPage() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown JObject val = new JObject(); foreach (string key in German.Keys) { val[key] = JToken.op_Implicit(L10n.T(key)); } return val; } } [SourceNotice("NpcCards by DooDesch, MIT License. Please read and reuse the source code at https://github.com/DooDesch-Mods/ScheduleOne-NpcCards, not decompiled code.")] internal static class Vault { private static readonly HttpClient Http = MakeClient(); private static readonly HashSet<string> Loading = new HashSet<string>(StringComparer.Ordinal); internal static string BaseUrl { get { try { return (string)JObject.Parse(CardsApi.VaultState())["baseUrl"]; } catch (Exception ex) { Core.Log.Error("[vault] reading the vault address from Personnel failed: " + ex.Message); return "https://personify.doomods.com"; } } } internal static string Caller => "NPCCards/1.1.2"; private static HttpClient MakeClient() { HttpClient httpClient = new HttpClient(); httpClient.Timeout = TimeSpan.FromSeconds(20.0); httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(Caller); return httpClient; } internal static JObject State() { JObject obj = JObject.Parse(CardsApi.VaultState()); obj["ok"] = JToken.op_Implicit(true); return obj; } private static JObject Pending() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Expected O, but got Unknown return new JObject { ["ok"] = JToken.op_Implicit(true), ["pending"] = JToken.op_Implicit(true) }; } private static void Reply(string id, bool ok, JToken body) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: 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) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Expected O, but got Unknown Pages.Emit("vault.reply", (JToken)new JObject { ["id"] = JToken.op_Implicit(id), ["ok"] = JToken.op_Implicit(ok), ["body"] = body }); } internal static JObject Get(string id, string path) { if (string.IsNullOrEmpty(path) || !path.StartsWith("/api/v1/", StringComparison.Ordinal)) { Core.Log.Warning("[vault] path '" + path + "' refused: not under /api/v1/."); return Calls.Fail("Something went wrong. See the log."); } Core.Log.Msg("[vault] GET " + path); using (CardsApi.Caller(Caller)) { CardsApi.VaultGet(path, (Action<bool, string>)delegate(bool ok, string body) { //IL_0041: Expected O, but got Unknown if (!ok) { Core.Log.Error("[vault] GET " + path + " failed: " + body); Reply(id, ok: false, JToken.op_Implicit(body)); } else { JToken val; try { val = JToken.Parse(body); } catch (JsonException ex) { JsonException ex2 = ex; Core.Log.Error($"[vault] GET {path} answered something that is not JSON ({((Exception)(object)ex2).Message})."); Reply(id, ok: false, JToken.op_Implicit("The vault answered something unreadable.")); return; } JToken obj = val[(object)"cards"]; JToken obj2 = ((obj is JArray) ? obj : null); int num = ((obj2 != null) ? ((JContainer)obj2).Count : (-1)); Core.Log.Msg((num >= 0) ? $"[vault] GET {path}: {num} card(s), next {((string)val[(object)"next"]) ?? "none"}." : ("[vault] GET " + path + ": ok.")); Reply(id, ok: true, val); } }); return Pending(); } } internal static JObject Install(string id, string code) { if (!CardLibrary.IsValidCode(code)) { Core.Log.Warning("[vault] install refused: '" + code + "' is not a card code."); return Calls.Fail("Not a card code."); } Core.Log.Msg("[vault] installing card " + code + "."); using (CardsApi.Caller(Caller)) { CardsApi.VaultInstall(code, (Action<string>)delegate(string json) { JObject val = Parse(json, "install " + code); if ((bool?)val["ok"] == true) { Core.Log.Msg($"[vault] card {code} installed as '{(string)val["name"]}'{(((bool?)val["alreadyInstalled"] == true) ? " (already installed)" : "")}."); Library.AfterInstall(val); } else { Core.Log.Error($"[vault] installing card {code} failed: {(string)val["error"]} {(string)val["message"]}"); } Reply(id, ok: true, (JToken)(object)val); }); return Pending(); } } internal static JObject Connect() { Core.Log.Msg("[vault] connect started."); using (CardsApi.Caller(Caller)) { CardsApi.VaultConnect((Action<string>)delegate(string json) { JObject val = Parse(json, "connect"); Instance log = Core.Log; string text = (string)val["state"]; string text2 = (string)val["message"]; log.Msg("[vault] connect: " + text + ((text2 != null && text2.Length > 0) ? (" - " + text2) : "") + "."); Pages.Emit("vault.connect", (JToken)(object)val); if ((string)val["state"] == "connected") { Pages.Emit("vault.state", (JToken)(object)State()); } }); return Pending(); } } internal static JObject Disconnect() { Core.Log.Msg("[vault] disconnect asked."); using (CardsApi.Caller(Caller)) { CardsApi.VaultDisconnect((Action<bool>)delegate(bool ok) { Core.Log.Msg($"[vault] disconnected; token revoked on the vault: {ok}."); Pages.Emit("vault.state", (JToken)(object)State()); }); return State(); } } internal static JObject Upload(string id, string file) { string text = Library.UploadPath(file); if (text == null) { return Calls.Fail("File not found."); } if (!VaultClient.IsConnected) { Core.Log.Warning("[vault] upload of " + file + " refused: not connected."); return Calls.Fail("Connect this game to NPC Vault first."); } byte[] array; try { array = File.ReadAllBytes(text); } catch (Exception ex) { Core.Log.Error("[vault] reading " + text + " for the upload failed: " + ex.Message); return Calls.Fail("The file could not be read."); } Core.Log.Msg($"[vault] uploading {Path.GetFileName(text)} ({array.Length / 1024} KB)."); using (CardsApi.Caller(Caller)) { CardsApi.VaultUpload(array, (Action<string>)delegate(string json) { JObject val = Parse(json, "upload " + file); if ((bool?)val["ok"] == true) { Core.Log.Msg($"[vault] {file} published as {(string)val["code"]} ({(string)val["url"]})."); } else if ((bool?)val["duplicate"] == true) { Core.Log.Msg($"[vault] {file} is already on the vault as {(string)val["code"]}."); } else { Core.Log.Error("[vault] upload of " + file + " failed: " + (string)val["message"]); } Reply(id, ok: true, (JToken)(object)val); }); return Pending(); } } internal static JObject Thumb(string code) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown if (!CardLibrary.IsValidCode(code)) { Core.Log.Warning("[vault] thumb refused: '" + code + "' is not a card code."); return Calls.Fail("Not a card code."); } string name = "vault-" + code; if (Pages.HasPicture(name)) { return new JObject { ["ok"] = JToken.op_Implicit(true), ["url"] = JToken.op_Implicit("s1://" + name) }; } if (!Loading.Add(code)) { return Pending(); } string url = BaseUrl.TrimEnd('/') + "/c/" + code + "/thumb.png"; Task.Run(async delegate { byte[] png = null; string error = null; try { using HttpResponseMessage res = await Http.GetAsync(url).ConfigureAwait(continueOnCapturedContext: false); if (res.IsSuccessStatusCode) { png = await res.Content.ReadAsByteArrayAsync().ConfigureAwait(continueOnCapturedContext: false); } else { error = "HTTP " + (int)res.StatusCode; } } catch (Exception ex) { error = ex.GetType().Name + ": " + ex.GetBaseException().Message; } MainThread.Post("thumb " + code, delegate { //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown Loading.Remove(code); if (png == null) { Core.Log.Error($"[vault] picture {url} failed ({error})."); Pages.Emit("vault.thumb", (JToken)new JObject { ["code"] = JToken.op_Implicit(code), ["url"] = null }); } else { Pages.Emit("vault.thumb", (JToken)new JObject { ["code"] = JToken.op_Implicit(code), ["url"] = JToken.op_Implicit(Pages.Picture(name, png)) }); } }); }); return Pending(); } internal static JObject OpenUrl(string url) { //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Expected O, but got Unknown string value = BaseUrl.TrimEnd('/') + "/"; if (string.IsNullOrEmpty(url) || (!url.StartsWith(value, StringComparison.OrdinalIgnoreCase) && !(url == "https://thunderstore.io/c/schedule-i/p/DooDesch/Personify/") && !(url == "https://thunderstore.io/c/schedule-i/p/DooDesch/Siesta/"))) { Core.Log.Warning("[vault] opening '" + url + "' refused: not a vault page."); return Calls.Fail("Something went wrong. See the log."); } Application.OpenURL(url); Core.Log.Msg("[vault] opened " + url + " in the browser."); return new JObject { ["ok"] = JToken.op_Implicit(true) }; } internal static JObject Copy(string text) { //IL_004c: 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_0063: Expected O, but got Unknown if (string.IsNullOrEmpty(text) || text.Length > 64) { Core.Log.Warning("[vault] copy refused: empty or too long."); return Calls.Fail("Something went wrong. See the log."); } GUIUtility.systemCopyBuffer = text; Core.Log.Msg("[vault] '" + text + "' copied to the clipboard."); return new JObject { ["ok"] = JToken.op_Implicit(true) }; } private static JObject Parse(string json, string what) { //IL_005c: Expected O, but got Unknown //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Expected O, but got Unknown //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: 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_004e: Expected O, but got Unknown if (string.IsNullOrEmpty(json)) { Core.Log.Error("[vault] " + what + ": Personnel answered nothing."); return new JObject { ["ok"] = JToken.op_Implicit(false), ["message"] = JToken.op_Implicit("Personnel answered nothing.") }; } try { return JObject.Parse(json); } catch (JsonException ex) { JsonException ex2 = ex; Core.Log.Error($"[vault] {what}: Personnel's answer is not JSON ({((Exception)(object)ex2).Message})."); return new JObject { ["ok"] = JToken.op_Implicit(false), ["message"] = JToken.op_Implicit("Personnel answered something unreadable.") }; } } } }