using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem.Collections.Generic; using Microsoft.CodeAnalysis; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("SoDDiskAvailability")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("SoDDiskAvailability")] [assembly: AssemblyTitle("SoDDiskAvailability")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [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 SoDDiskAvailability { [BepInPlugin("ta.sod.diskavailability", "SoD Disk Availability", "0.1.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BasePlugin { public const string GUID = "ta.sod.diskavailability"; public const string VER = "0.1.0"; internal static ManualLogSource L; internal static ConfigEntry Mode; internal static ConfigEntry Salt; internal static ConfigEntry LogStock; internal static readonly List Pools = new List(); public override void Load() { L = ((BasePlugin)this).Log; L.LogInfo((object)"=== SoD Disk Availability 0.1.0 starting ==="); Mode = ((BasePlugin)this).Config.Bind("General", "Mode", "Rotation", "Unrestricted = every disk stocked everywhere, always. Vanilla = restore the stock lists as the game shipped them. Rotation = each vendor stocks a small set that changes daily."); Salt = ((BasePlugin)this).Config.Bind("General", "RotationSalt", 0, "Change this number to get a different rotation sequence. Rotation is derived from the in-game day plus this salt, so the same day always produces the same stock and reloading never rerolls it."); LogStock = ((BasePlugin)this).Config.Bind("General", "LogStockToConsole", true, "Write the chosen stock to the BepInEx log on every rotation."); Add("SyncClinic", "SyncClinics", 4, 0, "The ordinary sync clinics. There are usually six or seven in a city and they all share one stock list, so they will show the same disks as each other."); Add("BlackmarketSyncClinic", "BlackMarketClinic", 6, 0, "The black market doctor. Stocks independently of the ordinary clinics."); Add("BlackmarketTrader", "BlackMarketTrader", 3, 0, "The black market trader. Vanilla sells almost no disks here."); Add("WeaponsDealer", "WeaponsDealer", 3, 0, "The arms dealer. Vanilla sells no disks here at all, so this gives a reason to visit in person."); Add("Newsstand", "Newsstands", 2, 0, "Street newsstands. Vanilla draws one random CandorNews disk."); Add("NewspaperBox", "NewspaperBoxes", 2, 0, "Coin-operated newspaper boxes. Vanilla sells no disks here."); Harmony.CreateAndPatchAll(typeof(StockPatch), (string)null); ((BasePlugin)this).AddComponent(); L.LogInfo((object)("=== SoD Disk Availability ready, mode=" + Mode.Value + " ===")); } private void Add(string preset, string section, int count, int slots, string blurb) { Pool item = new Pool { Preset = preset, Enabled = ((BasePlugin)this).Config.Bind(section, "Enabled", true, blurb + " Set false to leave this vendor exactly as the game left it."), Count = ((BasePlugin)this).Config.Bind(section, "DisksInStock", count, "How many disks this vendor offers at once in Rotation mode. 0 for none."), Slots = ((BasePlugin)this).Config.Bind(section, "ExtraRandomSlots", slots, "Additional disks the game draws at random on top of the list above, filtered by the manufacturers below. Vanilla uses 1 for sync clinics."), Manufacturers = ((BasePlugin)this).Config.Bind(section, "RestrictToManufacturers", "", "Comma separated. Blank means draw from every disk in the game. Valid names: ElGen, Kaizen, KensingtonIndigo, StarchKola, CandorNews, BlackMarket.") }; Pools.Add(item); } } internal class Pool { public string Preset; public ConfigEntry Enabled; public ConfigEntry Count; public ConfigEntry Slots; public ConfigEntry Manufacturers; } [HarmonyPatch(typeof(Toolbox), "Start")] public static class StockPatch { private static bool captured; [HarmonyPriority(0)] private static void Postfix() { if (captured) { return; } captured = true; try { Stock.CaptureOriginals(); Stock.Apply(force: true); } catch (Exception ex) { Plugin.L.LogError((object)("initial stock pass failed: " + ex)); } } } public class DayWatcher : MonoBehaviour { private float _next; public DayWatcher(IntPtr ptr) : base(ptr) { } private void Update() { if (Time.time < _next) { return; } _next = Time.time + 1f; try { Stock.Apply(force: false); } catch { } } } internal static class Stock { private static readonly List MasterPool = new List(); private static readonly Dictionary> Originals = new Dictionary>(); private static readonly Dictionary OriginalSlots = new Dictionary(); private static int _lastDay = int.MinValue; private static string _lastMode = null; public static void CaptureOriginals() { MasterPool.Clear(); Originals.Clear(); OriginalSlots.Clear(); List allSyncDisks = Toolbox.Instance.allSyncDisks; if (allSyncDisks == null) { Plugin.L.LogError((object)"Toolbox.allSyncDisks null - availability disabled this session"); return; } for (int i = 0; i < allSyncDisks.Count; i++) { SyncDiskPreset val = allSyncDisks[i]; if (!((SoCustomComparison)(object)val == (SoCustomComparison)null) && !val.disabled) { MasterPool.Add(val); } } Il2CppArrayBase val2 = Resources.FindObjectsOfTypeAll(); if (val2 != null) { foreach (MenuPreset item in val2) { if ((SoCustomComparison)(object)item == (SoCustomComparison)null || ((Object)item).name == null || Originals.ContainsKey(((Object)item).name)) { continue; } List list = new List(); if (item.syncDisks != null) { for (int j = 0; j < item.syncDisks.Count; j++) { SyncDiskPreset val3 = item.syncDisks[j]; if ((SoCustomComparison)(object)val3 != (SoCustomComparison)null) { list.Add(val3); } } } Originals[((Object)item).name] = list; OriginalSlots[((Object)item).name] = item.syncDiskSlots; } } Plugin.L.LogInfo((object)("=== captured " + MasterPool.Count + " disks in the master pool, " + Originals.Count + " menu presets snapshotted ===")); } private static int CurrentDay() { try { return SessionData.Instance.dayInt; } catch { return 0; } } public static void Apply(bool force) { if (MasterPool.Count == 0) { return; } string text = (Plugin.Mode.Value ?? "Rotation").Trim(); int num = CurrentDay(); if (!force && num == _lastDay && text == _lastMode) { return; } _lastDay = num; _lastMode = text; bool flag = text.Equals("Unrestricted", StringComparison.OrdinalIgnoreCase); bool flag2 = text.Equals("Vanilla", StringComparison.OrdinalIgnoreCase); foreach (Pool pool in Plugin.Pools) { if (!pool.Enabled.Value) { continue; } Il2CppArrayBase val = Resources.FindObjectsOfTypeAll(); if (val == null) { continue; } foreach (MenuPreset item in val) { if ((SoCustomComparison)(object)item == (SoCustomComparison)null || ((Object)item).name != pool.Preset || item.syncDisks == null) { continue; } if (flag2) { WriteList(item, Originals.ContainsKey(((Object)item).name) ? Originals[((Object)item).name] : new List()); if (OriginalSlots.ContainsKey(((Object)item).name)) { item.syncDiskSlots = OriginalSlots[((Object)item).name]; } continue; } if (flag) { WriteList(item, MasterPool); item.syncDiskSlots = pool.Slots.Value; continue; } List list = Pick(Filter(pool.Manufacturers.Value), pool.Count.Value, num, pool.Preset); WriteList(item, list); item.syncDiskSlots = pool.Slots.Value; if (!Plugin.LogStock.Value) { continue; } StringBuilder stringBuilder = new StringBuilder(); foreach (SyncDiskPreset item2 in list) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(Short(((Object)item2).name)); } Plugin.L.LogInfo((object)("[STOCK] day " + num + " " + pool.Preset + " (" + list.Count + "): " + stringBuilder)); } } } private static string Short(string n) { if (string.IsNullOrEmpty(n)) { return "?"; } int num = n.LastIndexOf('_'); if (num < 0 || num >= n.Length - 1) { return n; } return n.Substring(num + 1); } private static void WriteList(MenuPreset m, List items) { m.syncDisks.Clear(); foreach (SyncDiskPreset item in items) { if ((SoCustomComparison)(object)item != (SoCustomComparison)null) { m.syncDisks.Add(item); } } } private static List Filter(string csv) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (string.IsNullOrEmpty(csv) || csv.Trim().Length == 0) { return MasterPool; } List list = new List(); string[] array = csv.Split(','); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length > 0) { list.Add(text); } } if (list.Count == 0) { return MasterPool; } List list2 = new List(); foreach (SyncDiskPreset item in MasterPool) { string a; try { a = ((object)item.manufacturer/*cast due to .constrained prefix*/).ToString(); } catch { continue; } foreach (string item2 in list) { if (string.Equals(a, item2, StringComparison.OrdinalIgnoreCase)) { list2.Add(item); break; } } } if (list2.Count <= 0) { return MasterPool; } return list2; } private static List Pick(List from, int count, int day, string vendor) { List list = new List(); if (count <= 0 || from.Count == 0) { return list; } if (count >= from.Count) { list.AddRange(from); return list; } uint num = (uint)(day * 2654435761u); num ^= (uint)(Plugin.Salt.Value * 40503); foreach (char c in vendor) { num = num * 31 + c; } if (num == 0) { num = 2463534242u; } List list2 = new List(); for (int j = 0; j < from.Count; j++) { list2.Add(j); } for (int num2 = list2.Count - 1; num2 > 0; num2--) { num ^= num << 13; num ^= num >> 17; num ^= num << 5; int index = (int)(num % (uint)(num2 + 1)); int value = list2[num2]; list2[num2] = list2[index]; list2[index] = value; } for (int k = 0; k < count; k++) { list.Add(from[list2[k]]); } return list; } } }