Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of StratosTweaks v0.1.0
StratosTweaks.dll
Decompiled a week agousing 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<string> _loggedRewrites = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _loadedCatalogs = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly HashSet<string> _shippedBundleNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, string> _shippedBundlePaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); private string _catalogDirectory = ""; private Func<IResourceLocation, string>? _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<IResourceLocator> 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<IResourceLocator> 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<IResourceLocator>(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<IResourceLocation, string> 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, TDoor>(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<GameObject>? AfterSpawn { get; set; } } internal sealed class PluginConfig { private static readonly string SharedCatalogRelativePath = Path.Combine("addressables", "catalog.bin"); public ConfigEntry<bool> EnableSceneInjection { get; } public ConfigEntry<bool> EnablePositionTracking { get; } public ConfigEntry<float> TrackingSampleIntervalSeconds { get; } public ConfigEntry<float> TrackingFlushIntervalSeconds { get; } public ConfigEntry<string> TrackingCsvRelativePath { get; } public ConfigEntry<float> TrackingWarpDistance { get; } public ConfigEntry<float> TrackingMovementWindowSeconds { get; } public ConfigEntry<float> 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<float>("Tracking", "SampleIntervalSeconds", 1f, "Seconds between sample rows while in a gameplay room. Enter/exit ignore this."); TrackingFlushIntervalSeconds = file.Bind<float>("Tracking", "FlushIntervalSeconds", 60f, "Seconds between writing buffered sample rows to disk. Enter/exit flush immediately."); TrackingCsvRelativePath = file.Bind<string>("Tracking", "CsvRelativePath", Path.Combine("logs", "positions.csv"), "CSV path template relative to this plugin DLL. Each session writes positions_yyyyMMdd_HHmmss_<id>.csv."); TrackingWarpDistance = file.Bind<float>("Tracking", "WarpDistance", 15f, "Same-room position jump (room-local units) logged as warp instead of sample."); TrackingMovementWindowSeconds = file.Bind<float>("Tracking", "MovementWindowSeconds", 2f, "Seconds before a room change to measure walking; little movement implies warp (e.g. Benchwarp)."); TrackingWalkThreshold = file.Bind<float>("Tracking", "WalkThreshold", 3f, "Max room-local travel in the movement window below which a room arrival is logged as warp."); } private static ConfigEntry<bool> 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<object>(); } else if (menuPath.Length == 1) { array = new object[1] { (object)new ConfigEntrySubgroup(LocalizedText.op_Implicit(menuPath[0]), Array.Empty<LocalizedText>()) }; } 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<bool>(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<GameObject> 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<GameObject> 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<string, GameObject> _spawnedByScene = new Dictionary<string, GameObject>(); 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<GameObject> 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<Renderer>(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<ISceneHandler> HandlersFor(string sceneName) { ISceneHandler[] scenes = _scenes; foreach (ISceneHandler sceneHandler in scenes) { if (string.Equals(sceneHandler.SceneName, sceneName, StringComparison.Ordinal)) { yield return sceneHandler; } } } } }