using System; using System.IO; using System.Reflection; using System.Text; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [DisallowMultipleComponent] public sealed class RuntimeBootProbe : MonoBehaviour { [Header("Optional References")] [SerializeField] private GameObject hudRoot; [SerializeField] private GeoTileAddressablesLoader geoTileLoader; [SerializeField] private Transform expectedPlayer; [Header("Probe Settings")] [SerializeField] private bool logOnAwake = true; [SerializeField] private bool logOnStart = true; [SerializeField] private bool logAfterDelay = true; [SerializeField] private float delayedLogSeconds = 2.0f; private void Awake() { if (logOnAwake) Dump("Awake"); } private void Start() { if (logOnStart) Dump("Start"); if (logAfterDelay && delayedLogSeconds > 0.0f) Invoke(nameof(DumpDelayed), delayedLogSeconds); } private void DumpDelayed() { Dump("Delayed"); } private void Dump(string phase) { var sb = new StringBuilder(1024); sb.Append("[RuntimeBootProbe] ").Append(phase).Append(" "); sb.Append("activeScene=").Append(SceneManager.GetActiveScene().name).Append(" "); sb.Append("loadedScenes=").Append(SceneManager.sceneCount).Append(" "); sb.Append("persistentDataPath=").Append(Application.persistentDataPath).Append(" "); if (hudRoot == null) { sb.Append("hudRoot=null "); } else { sb.Append("hudRoot.activeInHierarchy=").Append(hudRoot.activeInHierarchy).Append(" "); sb.Append("hudRoot.activeSelf=").Append(hudRoot.activeSelf).Append(" "); } if (expectedPlayer == null) { sb.Append("expectedPlayer=null "); } else { Vector3 p = expectedPlayer.position; sb.Append("expectedPlayer.pos=(") .Append(p.x.ToString("F2")).Append(",") .Append(p.y.ToString("F2")).Append(",") .Append(p.z.ToString("F2")).Append(") "); } if (geoTileLoader == null) { sb.Append("geoTileLoader=null "); AppendRenderState(sb); Debug.Log(sb.ToString()); return; } bool loaderEnabled = geoTileLoader.enabled && geoTileLoader.gameObject.activeInHierarchy; sb.Append("geoTileLoader.enabled=").Append(loaderEnabled).Append(" "); string tileBundleFolderName = ReadPrivateField(geoTileLoader, "tileBundleFolderName", "TileBundles"); string manifestFileName = ReadPrivateField(geoTileLoader, "manifestFileName", "TileManifest.json"); string buildingManifestFileName = ReadPrivateField(geoTileLoader, "buildingManifestFileName", "TileBuildingsManifest.json"); string buildTargetFolderOverride = ReadPrivateField(geoTileLoader, "buildTargetFolderOverride", ""); string buildTargetFolder = string.IsNullOrWhiteSpace(buildTargetFolderOverride) ? GetBuildTargetFolderName() : buildTargetFolderOverride; string basePath = Path.Combine(Application.persistentDataPath, tileBundleFolderName, buildTargetFolder); string manifestPath = Path.Combine(basePath, manifestFileName); string buildingsManifestPath = Path.Combine(basePath, buildingManifestFileName); string catalogPath = ResolveCatalogPath(manifestPath); sb.Append("basePath=").Append(basePath).Append(" "); sb.Append("manifestExists=").Append(File.Exists(manifestPath)).Append(" "); sb.Append("buildingsManifestExists=").Append(File.Exists(buildingsManifestPath)).Append(" "); if (!string.IsNullOrWhiteSpace(catalogPath)) sb.Append("catalogExists=").Append(File.Exists(catalogPath)).Append(" "); else sb.Append("catalogExists=unknown "); AppendRenderState(sb); Debug.Log(sb.ToString()); } private static void AppendRenderState(StringBuilder sb) { var rp = GraphicsSettings.currentRenderPipeline; string rpName = rp != null ? $"{rp.name} ({rp.GetType().Name})" : "BuiltInRenderPipeline"; int qualityIndex = QualitySettings.GetQualityLevel(); string qualityName = qualityIndex >= 0 && qualityIndex < QualitySettings.names.Length ? QualitySettings.names[qualityIndex] : ""; sb.Append("quality=").Append(qualityIndex).Append(":").Append(qualityName).Append(" "); sb.Append("renderPipeline=").Append(rpName).Append(" "); var cam = Camera.main; if (cam == null) { sb.Append("mainCamera= "); } else { sb.Append("mainCamera=").Append(cam.name).Append(" "); sb.Append("mainCamera.farClip=").Append(cam.farClipPlane.ToString("F1")).Append(" "); sb.Append("mainCamera.nearClip=").Append(cam.nearClipPlane.ToString("F3")).Append(" "); sb.Append("mainCamera.cullingMask=0x").Append(cam.cullingMask.ToString("X")).Append(" "); } AppendXrState(sb); } private static void AppendXrState(StringBuilder sb) { try { Type xrSettingsType = Type.GetType("UnityEngine.XR.XRSettings, UnityEngine.XRModule"); if (xrSettingsType == null) { sb.Append("xrEnabled= "); return; } var enabledProp = xrSettingsType.GetProperty("enabled", BindingFlags.Public | BindingFlags.Static); bool xrEnabled = enabledProp != null && enabledProp.GetValue(null) is bool enabled && enabled; sb.Append("xrEnabled=").Append(xrEnabled).Append(" "); if (!xrEnabled) return; var stereoModeProp = xrSettingsType.GetProperty("stereoRenderingMode", BindingFlags.Public | BindingFlags.Static); object stereoMode = stereoModeProp?.GetValue(null); sb.Append("xrStereoMode=").Append(stereoMode != null ? stereoMode.ToString() : "").Append(" "); } catch { sb.Append("xrEnabled= "); } } private static string ReadPrivateField(GeoTileAddressablesLoader loader, string fieldName, string fallback) { try { FieldInfo fi = typeof(GeoTileAddressablesLoader).GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); if (fi == null) return fallback; object value = fi.GetValue(loader); return value as string ?? fallback; } catch { return fallback; } } private static string ResolveCatalogPath(string manifestPath) { try { if (!File.Exists(manifestPath)) return null; string json = File.ReadAllText(manifestPath); TileManifest manifest = JsonUtility.FromJson(json); if (manifest == null || string.IsNullOrWhiteSpace(manifest.catalogFile)) return null; return Path.Combine(Path.GetDirectoryName(manifestPath) ?? string.Empty, manifest.catalogFile); } catch { return null; } } private static string GetBuildTargetFolderName() { switch (Application.platform) { case RuntimePlatform.Android: return "Android"; case RuntimePlatform.WindowsPlayer: case RuntimePlatform.WindowsEditor: return "StandaloneWindows64"; case RuntimePlatform.OSXPlayer: case RuntimePlatform.OSXEditor: return "StandaloneOSX"; case RuntimePlatform.LinuxPlayer: case RuntimePlatform.LinuxEditor: return "StandaloneLinux64"; default: return "Android"; } } }