using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using Microsoft.CodeAnalysis; using Silksong.ModMenu.Elements; using Silksong.ModMenu.Plugin; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.ResourceManagement.ResourceLocations; using UnityEngine.SceneManagement; [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("StratosTweaks")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+ade4d818d8e80ebb82796a5a0d6c4c21f53068d9")] [assembly: AssemblyProduct("StratosTweaks")] [assembly: AssemblyTitle("StratosTweaks")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/AbsoluteStratos/StratosTweaks")] [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.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 BepInEx { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class BepInAutoPluginAttribute : Attribute { public BepInAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace BepInEx.Preloader.Core.Patching { [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] [Conditional("CodeGeneration")] internal sealed class PatcherAutoPluginAttribute : Attribute { public PatcherAutoPluginAttribute(string? id = null, string? name = null, string? version = null) { } } } namespace StratosTweaks { internal sealed class AddressableCatalog { private const string RuntimeToken = "{UnityEngine.AddressableAssets.Addressables.RuntimePath}"; private const string OurGroupMarker = "stratostweaks_assets_all"; private const string DefaultLocalGroupMarker = "defaultlocalgroup_assets_all"; private const string OurSharedMarker = "cf1fd25e771d52098d1bacfb793670d7_"; private readonly ManualLogSource _log; private readonly HashSet _loggedRewrites = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet _loadedCatalogs = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet _shippedBundleNames = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly Dictionary _shippedBundlePaths = new Dictionary(StringComparer.OrdinalIgnoreCase); private string _catalogDirectory = ""; private Func? _idTransform; public bool IsReady { get; private set; } public AddressableCatalog(ManualLogSource log) { _log = log; } public IEnumerator Load(string catalogFilePath) { catalogFilePath = Path.GetFullPath(catalogFilePath); if (_loadedCatalogs.Contains(catalogFilePath)) { yield break; } if (!File.Exists(catalogFilePath)) { _log.LogError((object)("Addressables catalog missing: " + catalogFilePath)); string directoryName = Path.GetDirectoryName(catalogFilePath); if (!string.IsNullOrEmpty(directoryName) && Directory.Exists(directoryName)) { _log.LogError((object)("Folder exists. Files: " + string.Join(", ", Directory.GetFiles(directoryName, "*", SearchOption.AllDirectories)))); } yield break; } _catalogDirectory = Path.GetFullPath(Path.GetDirectoryName(catalogFilePath)); IndexShippedBundles(); _log.LogInfo((object)("Loading catalog " + _catalogDirectory)); AsyncOperationHandle init = Addressables.InitializeAsync(); yield return init; if (init.IsValid() && (int)init.Status == 2) { _log.LogError((object)$"Addressables.InitializeAsync failed: {init.OperationException}"); yield break; } InstallIdTransform(); AsyncOperationHandle catalog = Addressables.LoadContentCatalogAsync(catalogFilePath, false, "StratosTweaks"); yield return catalog; if (!catalog.IsValid() || (int)catalog.Status != 1) { string text = ((!catalog.IsValid()) ? "handle released" : catalog.OperationException?.ToString()); _log.LogError((object)("LoadContentCatalogAsync failed: " + text)); yield break; } Addressables.Release(catalog); _loadedCatalogs.Add(catalogFilePath); IsReady = true; _log.LogInfo((object)("Loaded catalog from " + _catalogDirectory)); } private void IndexShippedBundles() { _shippedBundleNames.Clear(); _shippedBundlePaths.Clear(); if (!Directory.Exists(_catalogDirectory)) { return; } string[] files = Directory.GetFiles(_catalogDirectory, "*.bundle", SearchOption.AllDirectories); foreach (string path in files) { string fullPath = Path.GetFullPath(path); if (fullPath.IndexOf($"{Path.DirectorySeparatorChar}BellTown{Path.DirectorySeparatorChar}", StringComparison.OrdinalIgnoreCase) < 0) { string fileName = Path.GetFileName(path); _shippedBundleNames.Add(fileName); _shippedBundlePaths[fileName] = fullPath; } } } public void InstallIdTransform() { if (string.IsNullOrEmpty(_catalogDirectory)) { return; } if (_idTransform == null) { Func previous = Addressables.InternalIdTransformFunc; _idTransform = delegate(IResourceLocation location) { string internalId = location.InternalId; string text = ((previous != null) ? previous(location) : internalId); string text2 = (string.IsNullOrEmpty(text) ? internalId : text); return (IsOurBundle(internalId) || IsOurBundle(text2)) ? RewriteOurBundles(text2) : RewriteVanillaReference(text2); }; } Addressables.InternalIdTransformFunc = _idTransform; } private bool IsOurBundle(string? id) { if (string.IsNullOrEmpty(id) || id.IndexOf("(reference)", StringComparison.OrdinalIgnoreCase) >= 0) { return false; } string bundleFileName = GetBundleFileName(id); if (bundleFileName != null && _shippedBundleNames.Contains(bundleFileName)) { return true; } if (!id.Contains("stratostweaks_assets_all", StringComparison.OrdinalIgnoreCase) && !id.Contains("defaultlocalgroup_assets_all", StringComparison.OrdinalIgnoreCase)) { return id.Contains("cf1fd25e771d52098d1bacfb793670d7_", StringComparison.OrdinalIgnoreCase); } return true; } private static string? GetBundleFileName(string internalId) { string text = internalId.Replace('/', '\\'); int num = text.LastIndexOf('\\'); string text2 = ((num >= 0) ? text.Substring(num + 1) : text); int num2 = text2.IndexOf('?'); if (num2 >= 0) { text2 = text2.Substring(0, num2); } if (!text2.EndsWith(".bundle", StringComparison.OrdinalIgnoreCase)) { return null; } return text2; } private string RewriteOurBundles(string internalId) { string text = MapToCatalogDir(internalId); if (text != internalId && _loggedRewrites.Add(text)) { bool flag = File.Exists(text); _log.LogInfo((object)$"Rewrite bundle -> {text} (exists={flag})"); if (!flag) { _log.LogError((object)("Expected bundle missing: " + text)); } } return text; } private string MapToCatalogDir(string internalId) { string bundleFileName = GetBundleFileName(internalId); if (bundleFileName != null && _shippedBundlePaths.TryGetValue(bundleFileName, out string value)) { return value; } if (internalId.IndexOf("{UnityEngine.AddressableAssets.Addressables.RuntimePath}", StringComparison.Ordinal) >= 0) { return Path.GetFullPath(internalId.Replace("{UnityEngine.AddressableAssets.Addressables.RuntimePath}", _catalogDirectory).Replace('/', Path.DirectorySeparatorChar)); } string text = internalId.Replace('/', '\\'); string text2 = Addressables.RuntimePath?.Replace('/', '\\'); if (!string.IsNullOrEmpty(text2) && text.StartsWith(text2, StringComparison.OrdinalIgnoreCase)) { string path = text.Substring(text2.Length).TrimStart('\\'); return Path.GetFullPath(Path.Combine(_catalogDirectory, path)); } string[] array = new string[2] { "StratosTweaks\\", "StandaloneWindows64\\" }; foreach (string value2 in array) { int num = text.IndexOf(value2, StringComparison.OrdinalIgnoreCase); if (num >= 0) { return Path.GetFullPath(Path.Combine(_catalogDirectory, text.Substring(num))); } } return internalId; } private string RewriteVanillaReference(string internalId) { if (internalId.IndexOf("(reference)", StringComparison.OrdinalIgnoreCase) < 0) { return internalId; } string text = StripReferenceNaming(internalId); if (text != internalId && _loggedRewrites.Add(text)) { bool flag = File.Exists(text); _log.LogInfo((object)$"Rewrite vanilla (reference) -> {text} (exists={flag})"); if (!flag) { _log.LogError((object)("Game bundle missing after stripping (reference): " + text)); } } return text; } private static string StripReferenceNaming(string internalId) { string text = internalId.Replace("(reference)", "", StringComparison.OrdinalIgnoreCase); if (!text.EndsWith(".bundle", StringComparison.OrdinalIgnoreCase)) { return text; } int num = Math.Max(text.LastIndexOf('\\'), text.LastIndexOf('/')) + 1; string text2 = text.Substring(num, text.Length - num - ".bundle".Length); int num2 = text2.LastIndexOf('_'); if (num2 < 0 || !IsHex32(text2, num2 + 1)) { return text; } return text.Substring(0, num) + text2.Substring(0, num2) + ".bundle"; } private static bool IsHex32(string value, int start) { if (value.Length - start != 32) { return false; } for (int i = start; i < value.Length; i++) { char c = value[i]; if ((c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F')) { return false; } } return true; } } internal sealed class BenchwarpIntegration : IDisposable { public const string PluginId = "io.github.homothetyhk.benchwarp"; private readonly PositionTracker _tracker; private readonly ManualLogSource _log; private readonly List<(EventInfo Event, Delegate Handler)> _subscriptions = new List<(EventInfo, Delegate)>(); private BenchwarpIntegration(PositionTracker tracker, PluginInfo pluginInfo, ManualLogSource log) { _tracker = tracker; _log = log; Type type = ((object)pluginInfo.Instance).GetType().Assembly.GetType("Benchwarp.Events.ModEvents") ?? throw new InvalidOperationException("Benchwarp.Events.ModEvents type not found."); Subscribe(type, "OnBenchwarp", CreateBenchwarpHandler()); Subscribe(type, "OnDoorwarp", CreateDoorwarpHandler(type.GetEvent("OnDoorwarp"))); } public static bool TryCreate(PositionTracker tracker, ManualLogSource log, out BenchwarpIntegration? integration) { integration = null; if (!Chainloader.PluginInfos.TryGetValue("io.github.homothetyhk.benchwarp", out var value)) { log.LogInfo((object)"Benchwarp not installed; position tracking uses movement heuristics for warp."); return false; } try { integration = new BenchwarpIntegration(tracker, value, log); log.LogInfo((object)"Benchwarp detected; bench_warp and door_warp events enabled."); return true; } catch (Exception ex) { log.LogWarning((object)("Benchwarp is installed but Stratos Tweaks could not hook ModEvents: " + ex.Message)); return false; } } private Action CreateBenchwarpHandler() { return delegate { _tracker.NotifyPendingWarp(PositionTracker.WarpSource.Bench); }; } private Delegate CreateDoorwarpHandler(EventInfo doorEvent) { ParameterInfo[] parameters = (doorEvent.EventHandlerType.GetMethod("Invoke") ?? throw new InvalidOperationException("OnDoorwarp invoke method not found.")).GetParameters(); if (parameters.Length != 2) { throw new InvalidOperationException("OnDoorwarp has unexpected signature."); } MethodInfo method = typeof(BenchwarpIntegration).GetMethod("HandleDoorwarp", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(parameters[0].ParameterType, parameters[1].ParameterType); return Delegate.CreateDelegate(doorEvent.EventHandlerType, this, method); } private void HandleDoorwarp(TRoom room, TDoor gate) { _tracker.NotifyPendingWarp(PositionTracker.WarpSource.Door); } private void Subscribe(Type modEvents, string eventName, Delegate handler) { EventInfo eventInfo = modEvents.GetEvent(eventName, BindingFlags.Static | BindingFlags.Public) ?? throw new InvalidOperationException("Benchwarp event " + eventName + " not found."); eventInfo.AddEventHandler(null, handler); _subscriptions.Add((eventInfo, handler)); } public void Dispose() { foreach (var (eventInfo, handler) in _subscriptions) { eventInfo.RemoveEventHandler(null, handler); } _subscriptions.Clear(); } } internal interface ISceneHandler { string SceneName { get; } bool Enabled { get; } string CatalogRelativePath { get; } string PrefabAddress { get; } string SpawnedRootName { get; } IEnumerator TryBegin(Scene scene, SceneInjectSession session); } internal sealed class SceneInjectSession { public bool Proceed { get; set; } = true; public string? SkipReason { get; set; } public Action? AfterSpawn { get; set; } } internal sealed class PluginConfig { private static readonly string SharedCatalogRelativePath = Path.Combine("addressables", "catalog.bin"); public ConfigEntry EnableSceneInjection { get; } public ConfigEntry EnablePositionTracking { get; } public ConfigEntry TrackingSampleIntervalSeconds { get; } public ConfigEntry TrackingFlushIntervalSeconds { get; } public ConfigEntry TrackingCsvRelativePath { get; } public ConfigEntry TrackingWarpDistance { get; } public ConfigEntry TrackingMovementWindowSeconds { get; } public ConfigEntry TrackingWalkThreshold { get; } public string BelltownPrefabAddress => "Assets/Mods/Noss2026/stratos_tweaks_root.prefab"; public string BelltownSpawnedRootName => "stratos_tweaks_root"; public string BelltownCatalogRelativePath => SharedCatalogRelativePath; public string BonetownPrefabAddress => "Assets/Mods/Noss2026/stratos_bowntown_root.prefab"; public string BonetownSpawnedRootName => "stratos_bowntown_root"; public string BonetownCatalogRelativePath => SharedCatalogRelativePath; public string SongEnclavePrefabAddress => "Assets/Mods/Noss2026/stratos_song_enclave_root.prefab"; public string SongEnclaveSpawnedRootName => "stratos_song_enclave_root"; public string SongEnclaveCatalogRelativePath => SharedCatalogRelativePath; public string Tut01PrefabAddress => "Assets/Mods/Noss2026/stratos_tut_root.prefab"; public string Tut01SpawnedRootName => "stratos_tut_root"; public string Tut01CatalogRelativePath => SharedCatalogRelativePath; public PluginConfig(ConfigFile file) { EnableSceneInjection = BindToggle(file, "General", "EnableSceneInjection", true, "Master switch for all scene injections.", "General"); EnablePositionTracking = BindToggle(file, "General", "EnablePositionTracking", true, "Log Hornet's room-local position to a CSV (enter/exit always; samples on an interval).", "General"); TrackingSampleIntervalSeconds = file.Bind("Tracking", "SampleIntervalSeconds", 1f, "Seconds between sample rows while in a gameplay room. Enter/exit ignore this."); TrackingFlushIntervalSeconds = file.Bind("Tracking", "FlushIntervalSeconds", 60f, "Seconds between writing buffered sample rows to disk. Enter/exit flush immediately."); TrackingCsvRelativePath = file.Bind("Tracking", "CsvRelativePath", Path.Combine("logs", "positions.csv"), "CSV path template relative to this plugin DLL. Each session writes positions_yyyyMMdd_HHmmss_.csv."); TrackingWarpDistance = file.Bind("Tracking", "WarpDistance", 15f, "Same-room position jump (room-local units) logged as warp instead of sample."); TrackingMovementWindowSeconds = file.Bind("Tracking", "MovementWindowSeconds", 2f, "Seconds before a room change to measure walking; little movement implies warp (e.g. Benchwarp)."); TrackingWalkThreshold = file.Bind("Tracking", "WalkThreshold", 3f, "Max room-local travel in the movement window below which a room arrival is logged as warp."); } private static ConfigEntry BindToggle(ConfigFile file, string section, string key, bool defaultValue, string description, params string[] menuPath) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Expected O, but got Unknown object[] array; if (menuPath.Length == 0) { array = Array.Empty(); } else if (menuPath.Length == 1) { array = new object[1] { (object)new ConfigEntrySubgroup(LocalizedText.op_Implicit(menuPath[0]), Array.Empty()) }; } else { LocalizedText[] array2 = (LocalizedText[])(object)new LocalizedText[menuPath.Length - 1]; for (int i = 1; i < menuPath.Length; i++) { array2[i - 1] = LocalizedText.op_Implicit(menuPath[i]); } array = new object[1] { (object)new ConfigEntrySubgroup(LocalizedText.op_Implicit(menuPath[0]), array2) }; } return file.Bind(section, key, defaultValue, new ConfigDescription(description, (AcceptableValueBase)null, array)); } } internal sealed class PositionTracker { internal enum WarpSource { None, Bench, Door } private const string Header = "utc,event,scene,x,y,z"; private readonly PluginConfig _config; private readonly ManualLogSource _log; private readonly string _pluginDir; private readonly StringBuilder _buffer = new StringBuilder(); private string _sessionId = ""; private string _csvPath = ""; private string _room = ""; private Vector3 _lastPos; private bool _inRoom; private float _nextSampleAt; private float _nextFlushAt; private float _movementWindowStart; private Vector3 _movementWindowOrigin; private float _movementInWindow; private WarpSource _pendingWarp; private float _pendingWarpUntil; public PositionTracker(PluginConfig config, ManualLogSource log, string pluginDir) { _config = config; _log = log; _pluginDir = pluginDir; } public void NotifyPendingWarp(WarpSource source) { _pendingWarp = source; _pendingWarpUntil = Time.unscaledTime + PendingWarpTimeoutSeconds(); } public void Tick() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: 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: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: 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_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) if (!_config.EnablePositionTracking.Value) { if (_inRoom) { Write("exit", _room, _lastPos, flush: true); _inRoom = false; _room = ""; } return; } EnsureSession(); GameManager instance = GameManager.instance; HeroController instance2 = HeroController.instance; if ((Object)(object)instance == (Object)null || (Object)(object)instance2 == (Object)null || !instance.IsGameplayScene()) { if (_inRoom) { Write("exit", _room, _lastPos, flush: true); _inRoom = false; _room = ""; } return; } string sceneNameString = instance.GetSceneNameString(); if (string.IsNullOrEmpty(sceneNameString)) { return; } Vector3 position = instance2.transform.position; float unscaledTime = Time.unscaledTime; if (_inRoom && string.Equals(sceneNameString, _room, StringComparison.Ordinal) && IsSameRoomWarp(position, _lastPos)) { Write("warp", sceneNameString, position, flush: true); _lastPos = position; ResetMovementWindow(position, unscaledTime); _nextSampleAt = unscaledTime + SampleInterval(); return; } if (_inRoom && !string.Equals(sceneNameString, _room, StringComparison.Ordinal)) { Write("exit", _room, _lastPos, flush: true); _inRoom = false; } if (!_inRoom) { _room = sceneNameString; _inRoom = true; string eventName = ConsumePendingWarpEvent() ?? (LikelyTeleportArrival() ? "warp" : "enter"); Write(eventName, sceneNameString, position, flush: true); _nextSampleAt = unscaledTime + SampleInterval(); _lastPos = position; ResetMovementWindow(position, unscaledTime); return; } UpdateMovementWindow(position, unscaledTime); _lastPos = position; if (unscaledTime >= _nextSampleAt) { Write("sample", sceneNameString, position, flush: false); _nextSampleAt = unscaledTime + SampleInterval(); } if (unscaledTime >= _nextFlushAt) { Flush(); } } public void Shutdown() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) if (_inRoom) { Write("exit", _room, _lastPos, flush: false); _inRoom = false; _room = ""; } Flush(); } private float SampleInterval() { return Mathf.Max(0.1f, _config.TrackingSampleIntervalSeconds.Value); } private float FlushInterval() { return Mathf.Max(1f, _config.TrackingFlushIntervalSeconds.Value); } private float WarpDistance() { return Mathf.Max(1f, _config.TrackingWarpDistance.Value); } private float MovementWindowSeconds() { return Mathf.Max(0.5f, _config.TrackingMovementWindowSeconds.Value); } private float WalkThreshold() { return Mathf.Max(0.5f, _config.TrackingWalkThreshold.Value); } private float PendingWarpTimeoutSeconds() { return Mathf.Max(5f, _config.TrackingMovementWindowSeconds.Value * 15f); } private string? ConsumePendingWarpEvent() { if (_pendingWarp == WarpSource.None || Time.unscaledTime > _pendingWarpUntil) { _pendingWarp = WarpSource.None; return null; } object result = _pendingWarp switch { WarpSource.Bench => "bench_warp", WarpSource.Door => "door_warp", _ => null, }; _pendingWarp = WarpSource.None; return (string?)result; } private bool IsSameRoomWarp(Vector3 pos, Vector3 previous) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) return Vector3.Distance(pos, previous) >= WarpDistance(); } private bool LikelyTeleportArrival() { return _movementInWindow < WalkThreshold(); } private void ResetMovementWindow(Vector3 pos, float now) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) _movementWindowStart = now; _movementWindowOrigin = pos; _movementInWindow = 0f; } private void UpdateMovementWindow(Vector3 pos, float now) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) if (now - _movementWindowStart > MovementWindowSeconds()) { ResetMovementWindow(pos, now); } else { _movementInWindow = Mathf.Max(_movementInWindow, Vector3.Distance(pos, _movementWindowOrigin)); } } private void EnsureSession() { if (string.IsNullOrEmpty(_sessionId)) { byte[] array = new byte[16]; using (RandomNumberGenerator randomNumberGenerator = RandomNumberGenerator.Create()) { randomNumberGenerator.GetBytes(array); } DateTime utcNow = DateTime.UtcNow; _sessionId = ToHex(array); _csvPath = BuildSessionCsvPath(_pluginDir, _config.TrackingCsvRelativePath.Value, utcNow, _sessionId); _nextFlushAt = Time.unscaledTime + FlushInterval(); _log.LogInfo((object)("Position tracking session " + _sessionId + " -> " + _csvPath)); } } private static string BuildSessionCsvPath(string pluginDir, string relativeTemplate, DateTime utc, string sessionId) { string fullPath = Path.GetFullPath(Path.Combine(pluginDir, relativeTemplate)); string path = Path.GetDirectoryName(fullPath) ?? pluginDir; string text = Path.GetFileNameWithoutExtension(fullPath); if (string.IsNullOrEmpty(text)) { text = "positions"; } string text2 = Path.GetExtension(fullPath); if (string.IsNullOrEmpty(text2)) { text2 = ".csv"; } string text3 = utc.ToString("yyyyMMdd_HHmmss", CultureInfo.InvariantCulture); string text4 = ((sessionId.Length >= 8) ? sessionId.Substring(0, 8) : sessionId); return Path.Combine(path, text + "_" + text3 + "_" + text4 + text2); } private void Write(string eventName, string scene, Vector3 pos, bool flush) { if (!string.IsNullOrEmpty(_sessionId) && !string.IsNullOrEmpty(scene)) { DateTime utcNow = DateTime.UtcNow; CultureInfo invariantCulture = CultureInfo.InvariantCulture; _buffer.Append(utcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ", invariantCulture)).Append(',').Append(eventName) .Append(',') .Append(scene) .Append(',') .Append(pos.x.ToString("0.###", invariantCulture)) .Append(',') .Append(pos.y.ToString("0.###", invariantCulture)) .Append(',') .Append(pos.z.ToString("0.###", invariantCulture)) .Append('\n'); if (flush) { Flush(); } } } private void Flush() { if (_buffer.Length == 0 || string.IsNullOrEmpty(_csvPath)) { _nextFlushAt = Time.unscaledTime + FlushInterval(); return; } try { string directoryName = Path.GetDirectoryName(_csvPath); if (!string.IsNullOrEmpty(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(_csvPath)) { File.WriteAllText(_csvPath, "utc,event,scene,x,y,z\n"); } File.AppendAllText(_csvPath, _buffer.ToString()); _buffer.Clear(); _nextFlushAt = Time.unscaledTime + FlushInterval(); } catch (Exception arg) { _log.LogError((object)$"Position CSV write failed: {arg}"); } } private static string ToHex(byte[] bytes) { StringBuilder stringBuilder = new StringBuilder(bytes.Length * 2); foreach (byte b in bytes) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } } internal static class SceneHierarchy { public static GameObject? FindDescendant(Scene scene, string parentName, string childName) { if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { return null; } GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); for (int i = 0; i < rootGameObjects.Length; i++) { Transform val = FindByName(rootGameObjects[i].transform, parentName); if (!((Object)(object)val == (Object)null)) { Transform val2 = FindNamedChild(val, childName); if ((Object)(object)val2 != (Object)null) { return ((Component)val2).gameObject; } } } return null; } public static void FindAllByName(Scene scene, string name, List results) { if (((Scene)(ref scene)).IsValid() && ((Scene)(ref scene)).isLoaded) { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); for (int i = 0; i < rootGameObjects.Length; i++) { CollectByName(rootGameObjects[i].transform, name, results); } } } private static void CollectByName(Transform root, string name, List results) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown if (((Object)root).name == name) { results.Add(((Component)root).gameObject); } foreach (Transform item in root) { CollectByName(item, name, results); } } private static Transform? FindByName(Transform root, string name) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown if (((Object)root).name == name) { return root; } foreach (Transform item in root) { Transform val = FindByName(item, name); if ((Object)(object)val != (Object)null) { return val; } } return null; } private static Transform? FindNamedChild(Transform parent, string name) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown foreach (Transform item in parent) { Transform val = item; if (((Object)val).name == name) { return val; } } foreach (Transform item2 in parent) { Transform val2 = FindNamedChild(item2, name); if ((Object)(object)val2 != (Object)null) { return val2; } } return null; } } internal sealed class SceneInjector { private readonly AddressableCatalog _catalog; private readonly ManualLogSource _log; private readonly Dictionary _spawnedByScene = new Dictionary(); public SceneInjector(AddressableCatalog catalog, ManualLogSource log) { _catalog = catalog; _log = log; } public IEnumerator Inject(ISceneHandler handler, Scene loadedScene = default(Scene)) { //IL_0015: 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) if (!handler.Enabled) { yield break; } Scene scene = ResolveTargetScene(loadedScene, handler.SceneName); if (!((Scene)(ref scene)).IsValid() || !((Scene)(ref scene)).isLoaded) { yield break; } if (_spawnedByScene.ContainsKey(handler.SceneName)) { _log.LogInfo((object)("Skip inject: already spawned in '" + handler.SceneName + "'.")); yield break; } SceneInjectSession session = new SceneInjectSession(); yield return handler.TryBegin(scene, session); if (!session.Proceed) { _log.LogInfo((object)("Skip inject '" + handler.SceneName + "': " + session.SkipReason)); yield break; } _catalog.InstallIdTransform(); _log.LogInfo((object)("Injecting '" + handler.PrefabAddress + "' into '" + ((Scene)(ref scene)).name + "'.")); AsyncOperationHandle handle = Addressables.InstantiateAsync((object)handler.PrefabAddress, Vector3.zero, Quaternion.identity, (Transform)null, true); yield return handle; if ((int)handle.Status != 1 || (Object)(object)handle.Result == (Object)null) { _log.LogError((object)$"InstantiateAsync('{handler.PrefabAddress}') failed: {handle.OperationException}"); yield break; } GameObject result = handle.Result; if (!string.IsNullOrWhiteSpace(handler.SpawnedRootName)) { ((Object)result).name = handler.SpawnedRootName; } SceneManager.MoveGameObjectToScene(result, scene); LogSceneShaders(scene, result, "grass_03"); _spawnedByScene[handler.SceneName] = result; session.AfterSpawn?.Invoke(result); _log.LogInfo((object)("Spawned '" + handler.PrefabAddress + "' as '" + ((Object)result).name + "' in '" + ((Scene)(ref scene)).name + "'.")); } private void LogSceneShaders(Scene scene, GameObject spawned, string objectName) { GameObject[] rootGameObjects = ((Scene)(ref scene)).GetRootGameObjects(); for (int i = 0; i < rootGameObjects.Length; i++) { Renderer[] componentsInChildren = rootGameObjects[i].GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if (!((Object)(object)((Component)val).transform.root == (Object)(object)spawned.transform) && !(((Object)val).name != objectName)) { string text = (((Object)(object)((Component)val).transform.parent != (Object)null) ? ((Object)((Component)val).transform.parent).name : ""); Material sharedMaterial = val.sharedMaterial; string text2 = (((Object)(object)sharedMaterial == (Object)null) ? "(null material)" : (((Object)(object)sharedMaterial.shader != (Object)null) ? ((Object)sharedMaterial.shader).name : "(null shader)")); _log.LogInfo((object)("Scene " + text + "/" + ((Object)val).name + ": mat='" + ((sharedMaterial != null) ? ((Object)sharedMaterial).name : null) + "' shader='" + text2 + "'")); } } } } public void OnSceneUnloaded(Scene scene) { if (_spawnedByScene.TryGetValue(((Scene)(ref scene)).name, out GameObject value)) { if ((Object)(object)value != (Object)null) { Object.Destroy((Object)(object)value); } _spawnedByScene.Remove(((Scene)(ref scene)).name); } } private static Scene ResolveTargetScene(Scene loadedScene, string targetName) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (((Scene)(ref loadedScene)).IsValid() && ((Scene)(ref loadedScene)).isLoaded && string.Equals(((Scene)(ref loadedScene)).name, targetName, StringComparison.Ordinal)) { return loadedScene; } return SceneManager.GetSceneByName(targetName); } } internal sealed class Belltown : ISceneHandler { public const string Scene = "Belltown"; private const string NormalWorld = "Normal World"; private const string Fountain = "fountain"; private const string BlackThreadWorld = "Black Thread World"; private const string BlackThreadFountain = "fountain (1)"; private const float WaitSeconds = 10f; private readonly PluginConfig _config; private readonly ManualLogSource _log; public string SceneName => "Belltown"; public bool Enabled => _config.EnableSceneInjection.Value; public string CatalogRelativePath => _config.BelltownCatalogRelativePath; public string PrefabAddress => _config.BelltownPrefabAddress; public string SpawnedRootName => _config.BelltownSpawnedRootName; public Belltown(PluginConfig config, ManualLogSource log) { _config = config; _log = log; } public IEnumerator TryBegin(Scene scene, SceneInjectSession session) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) float deadline = Time.realtimeSinceStartup + 10f; while (((Scene)(ref scene)).isLoaded && Time.realtimeSinceStartup < deadline) { GameObject val = SceneHierarchy.FindDescendant(scene, "Normal World", "fountain"); GameObject val2 = SceneHierarchy.FindDescendant(scene, "Black Thread World", "fountain (1)"); if (ShouldReplace(val, val2)) { _log.LogInfo((object)("Belltown: replacing vanilla fountain parent " + $"(fountain={(Object)(object)val != (Object)null}, fountain (1)={(Object)(object)val2 != (Object)null}).")); session.AfterSpawn = delegate(GameObject spawned) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) spawned.SetActive(true); Disable(SceneHierarchy.FindDescendant(spawned.scene, "Normal World", "fountain")); Disable(SceneHierarchy.FindDescendant(spawned.scene, "Black Thread World", "fountain (1)")); }; yield break; } yield return null; } session.Proceed = false; session.SkipReason = "no active 'Normal World/fountain' or 'Black Thread World/fountain (1)'"; } private static bool ShouldReplace(GameObject? normalFountain, GameObject? blackFountain) { if (!IsActive(normalFountain)) { return IsActive(blackFountain); } return true; } private static bool IsActive(GameObject? go) { if ((Object)(object)go != (Object)null) { return go.activeInHierarchy; } return false; } private static void Disable(GameObject? go) { if ((Object)(object)go != (Object)null) { go.SetActive(false); } } } internal sealed class Bonetown : ISceneHandler { public const string Scene = "Bonetown"; private readonly PluginConfig _config; private readonly ManualLogSource _log; public string SceneName => "Bonetown"; public bool Enabled => _config.EnableSceneInjection.Value; public string CatalogRelativePath => _config.BonetownCatalogRelativePath; public string PrefabAddress => _config.BonetownPrefabAddress; public string SpawnedRootName => _config.BonetownSpawnedRootName; public Bonetown(PluginConfig config, ManualLogSource log) { _config = config; _log = log; } public IEnumerator TryBegin(Scene scene, SceneInjectSession session) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) _log.LogInfo((object)("Bonetown: injecting '" + PrefabAddress + "' into '" + ((Scene)(ref scene)).name + "'.")); yield break; } } internal sealed class SongEnclave : ISceneHandler { public const string Scene = "Song_Enclave"; private readonly PluginConfig _config; private readonly ManualLogSource _log; public string SceneName => "Song_Enclave"; public bool Enabled => _config.EnableSceneInjection.Value; public string CatalogRelativePath => _config.SongEnclaveCatalogRelativePath; public string PrefabAddress => _config.SongEnclavePrefabAddress; public string SpawnedRootName => _config.SongEnclaveSpawnedRootName; public SongEnclave(PluginConfig config, ManualLogSource log) { _config = config; _log = log; } public IEnumerator TryBegin(Scene scene, SceneInjectSession session) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) _log.LogInfo((object)("Song_Enclave: injecting '" + PrefabAddress + "' into '" + ((Scene)(ref scene)).name + "'.")); yield break; } } internal sealed class Tut01 : ISceneHandler { public const string Scene = "Tut_01"; private readonly PluginConfig _config; private readonly ManualLogSource _log; public string SceneName => "Tut_01"; public bool Enabled => _config.EnableSceneInjection.Value; public string CatalogRelativePath => _config.Tut01CatalogRelativePath; public string PrefabAddress => _config.Tut01PrefabAddress; public string SpawnedRootName => _config.Tut01SpawnedRootName; public Tut01(PluginConfig config, ManualLogSource log) { _config = config; _log = log; } public IEnumerator TryBegin(Scene scene, SceneInjectSession session) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) _log.LogInfo((object)("Tut_01: injecting '" + PrefabAddress + "' into '" + ((Scene)(ref scene)).name + "'.")); yield break; } } [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInPlugin("io.github.absolutestratos.stratostweaks", "StratosTweaks", "0.1.0")] public class StratosTweaksPlugin : BaseUnityPlugin, IModMenuNestedMenu, IModMenuInterface { private PluginConfig _config; private AddressableCatalog _catalog; private SceneInjector _injector; private ISceneHandler[] _scenes; private PositionTracker? _tracker; private BenchwarpIntegration? _benchwarp; private string _pluginDir = ""; public const string Id = "io.github.absolutestratos.stratostweaks"; public static string Name => "StratosTweaks"; public static string Version => "0.1.0"; public string ModMenuName() { return "Stratos Tweaks"; } public int MinSubgroupSize() { return 1; } private void Awake() { _config = new PluginConfig(((BaseUnityPlugin)this).Config); _catalog = new AddressableCatalog(((BaseUnityPlugin)this).Logger); _injector = new SceneInjector(_catalog, ((BaseUnityPlugin)this).Logger); _scenes = new ISceneHandler[4] { new Belltown(_config, ((BaseUnityPlugin)this).Logger), new Bonetown(_config, ((BaseUnityPlugin)this).Logger), new SongEnclave(_config, ((BaseUnityPlugin)this).Logger), new Tut01(_config, ((BaseUnityPlugin)this).Logger) }; _pluginDir = Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location) ?? ""; _tracker = new PositionTracker(_config, ((BaseUnityPlugin)this).Logger, _pluginDir); BenchwarpIntegration.TryCreate(_tracker, ((BaseUnityPlugin)this).Logger, out _benchwarp); SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.sceneUnloaded += _injector.OnSceneUnloaded; if (_config.EnableSceneInjection.Value) { ((MonoBehaviour)this).StartCoroutine(BootInject()); } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin " + Name + " (io.github.absolutestratos.stratostweaks) loaded.")); } private void Update() { _tracker?.Tick(); } private void OnDestroy() { _benchwarp?.Dispose(); _tracker?.Shutdown(); SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.sceneUnloaded -= _injector.OnSceneUnloaded; } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) ((BaseUnityPlugin)this).Logger.LogInfo((object)("Scene loaded: '" + ((Scene)(ref scene)).name + "'")); foreach (ISceneHandler item in HandlersFor(((Scene)(ref scene)).name)) { ((MonoBehaviour)this).StartCoroutine(RunHandler(item, scene)); } } private IEnumerator BootInject() { while ((Object)(object)GameManager.instance == (Object)null) { yield return null; } ISceneHandler[] scenes = _scenes; foreach (ISceneHandler sceneHandler in scenes) { Scene sceneByName = SceneManager.GetSceneByName(sceneHandler.SceneName); if (((Scene)(ref sceneByName)).IsValid() && ((Scene)(ref sceneByName)).isLoaded) { yield return RunHandler(sceneHandler, sceneByName); } } } private IEnumerator RunHandler(ISceneHandler handler, Scene scene) { //IL_0015: 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) if (!_config.EnableSceneInjection.Value || !handler.Enabled) { yield break; } if (string.IsNullOrEmpty(_pluginDir)) { ((BaseUnityPlugin)this).Logger.LogError((object)"Could not resolve plugin directory."); yield break; } string text = Path.GetFullPath(Path.Combine(_pluginDir, handler.CatalogRelativePath)); string text2 = text.Replace($"{Path.DirectorySeparatorChar}BellTown{Path.DirectorySeparatorChar}", $"{Path.DirectorySeparatorChar}"); if (text2 != text && File.Exists(text2)) { if (text != text2) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Catalog path migrated: '" + text + "' -> '" + text2 + "'")); } text = text2; } yield return _catalog.Load(text); if (_catalog.IsReady) { yield return _injector.Inject(handler, scene); } } private IEnumerable HandlersFor(string sceneName) { ISceneHandler[] scenes = _scenes; foreach (ISceneHandler sceneHandler in scenes) { if (string.Equals(sceneHandler.SceneName, sceneName, StringComparison.Ordinal)) { yield return sceneHandler; } } } } }