Decompiled source of NebulaLocalResync v0.1.4

NebulaLocalResync.dll

Decompiled 2 hours ago
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using NebulaAPI;
using NebulaAPI.Networking;
using NebulaAPI.Packets;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace NebulaLocalResync;

[BepInPlugin("com.local.nebulalocalresync", "Nebula Local Resync", "0.1.4")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInDependency(/*Could not decode attribute arguments.*/)]
public sealed class LocalResyncPlugin : BaseUnityPlugin
{
	public const string PluginGuid = "com.local.nebulalocalresync";

	public const string PluginName = "Nebula Local Resync";

	public const string PluginVersion = "0.1.4";

	private static readonly object MainThreadLock = new object();

	private static readonly Queue<Action> MainThreadActions = new Queue<Action>();

	private static int NextRequestId;

	internal static LocalResyncPlugin Instance;

	internal static ManualLogSource Log;

	private ConfigEntry<KeyboardShortcut> _hotkey;

	private ConfigEntry<float> _defaultRadius;

	private ConfigEntry<float> _maximumRadius;

	private ConfigEntry<int> _maximumEntities;

	private ConfigEntry<int> _maximumGroundEnemies;

	private ConfigEntry<int> _maximumPayloadBytes;

	private ConfigEntry<float> _serverCooldownSeconds;

	private ConfigEntry<bool> _showRealtimeTip;

	private readonly Dictionary<int, float> _serverNextAllowedByConnection = new Dictionary<int, float>();

	private float _clientNextAllowedAt;

	private int _pendingRequestId;

	private void Awake()
	{
		//IL_003c: Unknown result type (might be due to invalid IL or missing references)
		//IL_016a: Unknown result type (might be due to invalid IL or missing references)
		//IL_016f: Unknown result type (might be due to invalid IL or missing references)
		Instance = this;
		Log = ((BaseUnityPlugin)this).Logger;
		_hotkey = ((BaseUnityPlugin)this).Config.Bind<KeyboardShortcut>("Client", "ResyncHotkey", new KeyboardShortcut((KeyCode)114, (KeyCode[])(object)new KeyCode[2]
		{
			(KeyCode)306,
			(KeyCode)304
		}), "Request a host-authoritative resync around the local mech. Default: Ctrl+Shift+R.");
		_defaultRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Client", "DefaultRadiusMeters", 150f, "Radius around the mech to audit/resync when the hotkey is pressed.");
		_maximumRadius = ((BaseUnityPlugin)this).Config.Bind<float>("Server", "MaximumRadiusMeters", 500f, "Hard server-side clamp for client resync requests.");
		_maximumEntities = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "MaximumEntitiesPerRequest", 5000, "Maximum number of host entities allowed in one resync area. Requests exceeding this are rejected rather than truncated.");
		_maximumGroundEnemies = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "MaximumGroundEnemiesPerPlanet", 100000, "Safety cap for the authoritative planet-wide active ground-enemy ID set included in a resync. The ID set is used to safely clear stale local Dark Fog enemies without deleting enemies that still exist elsewhere on the host planet.");
		_maximumPayloadBytes = ((BaseUnityPlugin)this).Config.Bind<int>("Server", "MaximumPayloadBytes", 16777216, "Maximum uncompressed custom resync payload. Requests exceeding this are rejected.");
		_serverCooldownSeconds = ((BaseUnityPlugin)this).Config.Bind<float>("Server", "MinimumSecondsBetweenRequestsPerConnection", 2f, "Minimum server-side interval between resync requests from the same client.");
		_showRealtimeTip = ((BaseUnityPlugin)this).Config.Bind<bool>("Client", "ShowRealtimeTip", true, "Attempt to show a small in-game realtime-tip message in addition to BepInEx logging.");
		NebulaModAPI.RegisterPackets(Assembly.GetExecutingAssembly());
		((BaseUnityPlugin)this).Logger.LogInfo((object)("Nebula Local Resync 0.1.4 loaded. Client hotkey: " + ((object)_hotkey.Value/*cast due to .constrained prefix*/).ToString()));
	}

	private void Update()
	{
		//IL_0034: Unknown result type (might be due to invalid IL or missing references)
		//IL_0039: Unknown result type (might be due to invalid IL or missing references)
		DrainMainThreadQueue();
		if (NebulaModAPI.IsMultiplayerActive && NebulaModAPI.MultiplayerSession != null && NebulaModAPI.MultiplayerSession.IsClient && NebulaModAPI.MultiplayerSession.IsGameLoaded)
		{
			KeyboardShortcut value = _hotkey.Value;
			if (((KeyboardShortcut)(ref value)).IsDown())
			{
				RequestLocalResync(_defaultRadius.Value);
			}
		}
	}

	private void OnDestroy()
	{
		if (object.ReferenceEquals(Instance, this))
		{
			Instance = null;
		}
	}

	internal static void EnqueueMainThread(Action action)
	{
		if (action == null)
		{
			return;
		}
		lock (MainThreadLock)
		{
			MainThreadActions.Enqueue(action);
		}
	}

	private static void DrainMainThreadQueue()
	{
		while (true)
		{
			Action action = null;
			lock (MainThreadLock)
			{
				if (MainThreadActions.Count == 0)
				{
					break;
				}
				action = MainThreadActions.Dequeue();
			}
			try
			{
				action();
			}
			catch (Exception ex)
			{
				if (Log != null)
				{
					Log.LogError((object)("Main-thread resync action failed: " + ex));
				}
			}
		}
	}

	private void RequestLocalResync(float requestedRadius)
	{
		if (Time.realtimeSinceStartup < _clientNextAllowedAt)
		{
			ShowClientMessage("Nebula resync is cooling down.");
			return;
		}
		if (!DspAccess.TryGetLocalFactoryContext(out var planetId, out var center, out var _, out var error))
		{
			ShowClientMessage("Resync unavailable: " + error);
			return;
		}
		float val = Math.Max(25f, _maximumRadius.Value);
		float radius = Math.Max(25f, Math.Min(requestedRadius, val));
		int requestId = (_pendingRequestId = Interlocked.Increment(ref NextRequestId));
		_clientNextAllowedAt = Time.realtimeSinceStartup + 1f;
		LocalResyncRequest localResyncRequest = new LocalResyncRequest();
		localResyncRequest.RequestId = requestId;
		localResyncRequest.PlanetId = planetId;
		localResyncRequest.CenterX = center.X;
		localResyncRequest.CenterY = center.Y;
		localResyncRequest.CenterZ = center.Z;
		localResyncRequest.Radius = radius;
		NebulaModAPI.MultiplayerSession.Network.SendPacket<LocalResyncRequest>(localResyncRequest);
		ShowClientMessage("Requested host resync within " + radius.ToString("0") + " m.");
	}

	internal void HandleHostRequest(LocalResyncRequest packet, INebulaConnection connection)
	{
		if (packet == null || connection == null || !connection.IsAlive || !NebulaModAPI.IsMultiplayerActive || NebulaModAPI.MultiplayerSession == null || !NebulaModAPI.MultiplayerSession.IsServer)
		{
			return;
		}
		float realtimeSinceStartup = Time.realtimeSinceStartup;
		if (_serverNextAllowedByConnection.TryGetValue(connection.Id, out var value) && realtimeSinceStartup < value)
		{
			SendError(connection, packet, "Server cooldown is active for this connection.");
			return;
		}
		_serverNextAllowedByConnection[connection.Id] = realtimeSinceStartup + Math.Max(0.5f, _serverCooldownSeconds.Value);
		float val = Math.Max(25f, _maximumRadius.Value);
		float radius = Math.Max(25f, Math.Min(packet.Radius, val));
		Vec3 center = new Vec3(packet.CenterX, packet.CenterY, packet.CenterZ);
		if (!DspAccess.TryGetFactoryForPlanet(packet.PlanetId, out var factory, out var error))
		{
			SendError(connection, packet, error);
			return;
		}
		try
		{
			SnapshotBuildResult snapshotBuildResult = SnapshotCodec.BuildHostSnapshot(factory, center, radius, Math.Max(100, _maximumEntities.Value), Math.Max(1000, _maximumGroundEnemies.Value), Math.Max(1048576, _maximumPayloadBytes.Value));
			if (!string.IsNullOrEmpty(snapshotBuildResult.Error))
			{
				SendError(connection, packet, snapshotBuildResult.Error);
				return;
			}
			LocalResyncResponse localResyncResponse = new LocalResyncResponse();
			localResyncResponse.RequestId = packet.RequestId;
			localResyncResponse.PlanetId = packet.PlanetId;
			localResyncResponse.CenterX = center.X;
			localResyncResponse.CenterY = center.Y;
			localResyncResponse.CenterZ = center.Z;
			localResyncResponse.Radius = radius;
			localResyncResponse.Payload = snapshotBuildResult.Payload;
			localResyncResponse.Error = string.Empty;
			connection.SendPacket<LocalResyncResponse>(localResyncResponse);
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Resync request " + packet.RequestId + " for planet " + packet.PlanetId + ": entities=" + snapshotBuildResult.EntityCount + ", componentSnapshots=" + snapshotBuildResult.ComponentCount + ", beltsAudited=" + snapshotBuildResult.BeltCount + ", activeGroundEnemies=" + snapshotBuildResult.ActiveGroundEnemyCount + ", nearbyGroundEnemies=" + snapshotBuildResult.NearbyGroundEnemyCount + ", bytes=" + snapshotBuildResult.Payload.Length + "."));
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Failed to build host resync snapshot: " + ex));
			SendError(connection, packet, "Host failed to build resync snapshot. Check the server BepInEx log.");
		}
	}

	private void SendError(INebulaConnection connection, LocalResyncRequest request, string message)
	{
		LocalResyncResponse localResyncResponse = new LocalResyncResponse();
		localResyncResponse.RequestId = request.RequestId;
		localResyncResponse.PlanetId = request.PlanetId;
		localResyncResponse.CenterX = request.CenterX;
		localResyncResponse.CenterY = request.CenterY;
		localResyncResponse.CenterZ = request.CenterZ;
		localResyncResponse.Radius = request.Radius;
		localResyncResponse.Payload = new byte[0];
		localResyncResponse.Error = message ?? "Unknown resync error.";
		connection.SendPacket<LocalResyncResponse>(localResyncResponse);
		((BaseUnityPlugin)this).Logger.LogWarning((object)("Rejected resync request " + request.RequestId + ": " + localResyncResponse.Error));
	}

	internal void HandleClientResponse(LocalResyncResponse packet)
	{
		if (packet == null || !NebulaModAPI.IsMultiplayerActive || NebulaModAPI.MultiplayerSession == null || !NebulaModAPI.MultiplayerSession.IsClient)
		{
			return;
		}
		if (packet.RequestId != _pendingRequestId)
		{
			((BaseUnityPlugin)this).Logger.LogWarning((object)("Ignoring stale resync response " + packet.RequestId + "; current request is " + _pendingRequestId + "."));
			return;
		}
		if (!string.IsNullOrEmpty(packet.Error))
		{
			ShowClientMessage("Resync rejected: " + packet.Error);
			return;
		}
		if (!DspAccess.TryGetLocalFactoryContext(out var planetId, out var _, out var factory, out var error))
		{
			ShowClientMessage("Resync response could not be applied: " + error);
			return;
		}
		if (planetId != packet.PlanetId)
		{
			ShowClientMessage("Resync discarded because the local player changed planets.");
			return;
		}
		try
		{
			SnapshotApplyResult snapshotApplyResult = SnapshotCodec.ApplyClientSnapshot(factory, packet.Payload, new Vec3(packet.CenterX, packet.CenterY, packet.CenterZ), packet.Radius);
			string text = "Resync complete: " + snapshotApplyResult.ComponentsApplied + " component states repaired; " + snapshotApplyResult.GroundEnemyPhantomsCleared + " Dark Fog phantoms cleared; " + snapshotApplyResult.GroundEnemyPendingFlagsRepaired + " enemy pending flags repaired; " + snapshotApplyResult.GroundEnemiesMissingLocally + " host enemies missing locally; " + snapshotApplyResult.HostOnlyEntities + " factory entities missing local, " + snapshotApplyResult.ClientOnlyEntities + " factory entities phantom local, " + snapshotApplyResult.MismatchedEntities + " factory entities mismatched; " + snapshotApplyResult.BeltsAudited + " belts audited (cargo paths unchanged).";
			((BaseUnityPlugin)this).Logger.LogInfo((object)text);
			if (snapshotApplyResult.StructuralProblems > 0)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)"Structural mismatches were NOT created/deleted by Local Resync. Use Nebula's /reconnect if the mismatches remain visible.");
			}
			if (snapshotApplyResult.GroundEnemiesMissingLocally > 0)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)(snapshotApplyResult.GroundEnemiesMissingLocally + " authoritative host Dark Fog enemies are missing locally. Local Resync does not synthesize new enemy units; use Nebula's /reconnect if they remain missing."));
			}
			if (snapshotApplyResult.GroundEnemyRepairsSkipped > 0)
			{
				((BaseUnityPlugin)this).Logger.LogWarning((object)(snapshotApplyResult.GroundEnemyRepairsSkipped + " Dark Fog repairs were skipped because the local state could not be reconciled safely."));
			}
			string message = "Resync complete: " + snapshotApplyResult.GroundEnemyPhantomsCleared + " Dark Fog phantoms cleared, " + snapshotApplyResult.GroundEnemyPendingFlagsRepaired + " enemy flags repaired, " + snapshotApplyResult.ComponentsApplied + " factory states repaired.";
			ShowClientMessage(message);
		}
		catch (Exception ex)
		{
			((BaseUnityPlugin)this).Logger.LogError((object)("Failed to apply resync response: " + ex));
			ShowClientMessage("Resync failed while applying the host snapshot. Check BepInEx LogOutput.log.");
		}
	}

	private void ShowClientMessage(string message)
	{
		((BaseUnityPlugin)this).Logger.LogMessage((object)message);
		if (_showRealtimeTip.Value)
		{
			DspAccess.TryShowRealtimeTip("[Nebula Resync] " + message);
		}
	}
}
public sealed class LocalResyncRequest
{
	public int RequestId { get; set; }

	public int PlanetId { get; set; }

	public float CenterX { get; set; }

	public float CenterY { get; set; }

	public float CenterZ { get; set; }

	public float Radius { get; set; }
}
public sealed class LocalResyncResponse
{
	public int RequestId { get; set; }

	public int PlanetId { get; set; }

	public float CenterX { get; set; }

	public float CenterY { get; set; }

	public float CenterZ { get; set; }

	public float Radius { get; set; }

	public byte[] Payload { get; set; }

	public string Error { get; set; }
}
[RegisterPacketProcessor]
public sealed class LocalResyncRequestProcessor : BasePacketProcessor<LocalResyncRequest>
{
	public override void ProcessPacket(LocalResyncRequest packet, INebulaConnection conn)
	{
		if (!base.IsHost)
		{
			return;
		}
		LocalResyncPlugin.EnqueueMainThread(delegate
		{
			if ((Object)(object)LocalResyncPlugin.Instance != (Object)null)
			{
				LocalResyncPlugin.Instance.HandleHostRequest(packet, conn);
			}
		});
	}
}
[RegisterPacketProcessor]
public sealed class LocalResyncResponseProcessor : BasePacketProcessor<LocalResyncResponse>
{
	public override void ProcessPacket(LocalResyncResponse packet, INebulaConnection conn)
	{
		if (!base.IsClient)
		{
			return;
		}
		LocalResyncPlugin.EnqueueMainThread(delegate
		{
			if ((Object)(object)LocalResyncPlugin.Instance != (Object)null)
			{
				LocalResyncPlugin.Instance.HandleClientResponse(packet);
			}
		});
	}
}
internal struct Vec3
{
	public readonly float X;

	public readonly float Y;

	public readonly float Z;

	public Vec3(float x, float y, float z)
	{
		X = x;
		Y = y;
		Z = z;
	}

	public float DistanceSquared(Vec3 other)
	{
		float num = X - other.X;
		float num2 = Y - other.Y;
		float num3 = Z - other.Z;
		return num * num + num2 * num2 + num3 * num3;
	}
}
internal sealed class EntitySnapshot
{
	public int EntityId;

	public int ProtoId;

	public int StorageId;

	public int AssemblerId;

	public int MinerId;

	public int LabId;

	public int BeltId;

	public Vec3 Position;
}
internal sealed class GroundEnemySnapshot
{
	public int EnemyId;

	public int ProtoId;

	public Vec3 Position;
}
internal sealed class LocalGroundEnemyState
{
	public int EnemyId;

	public int ProtoId;

	public Vec3 Position;

	public bool IsActive;

	public bool IsInvinciblePending;
}
internal sealed class ComponentSnapshot
{
	public byte Kind;

	public int EntityId;

	public int ComponentId;

	public byte[] State;
}
internal sealed class SnapshotBuildResult
{
	public byte[] Payload;

	public string Error;

	public int EntityCount;

	public int ComponentCount;

	public int BeltCount;

	public int ActiveGroundEnemyCount;

	public int NearbyGroundEnemyCount;
}
internal sealed class SnapshotApplyResult
{
	public int ComponentsApplied;

	public int ComponentsSkipped;

	public int HostOnlyEntities;

	public int ClientOnlyEntities;

	public int MismatchedEntities;

	public int BeltsAudited;

	public int GroundEnemyPhantomsCleared;

	public int GroundEnemyPendingFlagsRepaired;

	public int GroundEnemiesMissingLocally;

	public int GroundEnemyRepairsSkipped;

	public int StructuralProblems => HostOnlyEntities + ClientOnlyEntities + MismatchedEntities;
}
internal static class SnapshotCodec
{
	private const int Magic = 1313624625;

	private const int Version = 2;

	private const byte StorageKind = 1;

	private const byte AssemblerKind = 2;

	private const byte MinerKind = 3;

	private const byte LabKind = 4;

	internal static SnapshotBuildResult BuildHostSnapshot(object factory, Vec3 center, float radius, int maxEntities, int maxGroundEnemies, int maxPayloadBytes)
	{
		SnapshotBuildResult snapshotBuildResult = new SnapshotBuildResult();
		List<EntitySnapshot> list = DspAccess.ScanEntities(factory, center, radius, maxEntities + 1);
		if (list.Count > maxEntities)
		{
			snapshotBuildResult.Error = "Resync area contains more than " + maxEntities + " entities. Lower the radius or raise Server.MaximumEntitiesPerRequest.";
			snapshotBuildResult.Payload = new byte[0];
			return snapshotBuildResult;
		}
		List<ComponentSnapshot> list2 = new List<ComponentSnapshot>();
		int num = 0;
		if (!DspAccess.TryScanHostGroundEnemies(factory, center, radius, maxGroundEnemies, out var activeEnemyIds, out var nearbyEnemies, out var error))
		{
			snapshotBuildResult.Error = error;
			snapshotBuildResult.Payload = new byte[0];
			return snapshotBuildResult;
		}
		for (int i = 0; i < list.Count; i++)
		{
			EntitySnapshot entitySnapshot = list[i];
			if (entitySnapshot.BeltId > 0)
			{
				num++;
			}
			AddComponentSnapshot(factory, list2, 1, entitySnapshot.EntityId, entitySnapshot.StorageId, "factoryStorage", "storagePool", useExportImport: true);
			AddComponentSnapshot(factory, list2, 2, entitySnapshot.EntityId, entitySnapshot.AssemblerId, "factorySystem", "assemblerPool", useExportImport: false);
			AddComponentSnapshot(factory, list2, 3, entitySnapshot.EntityId, entitySnapshot.MinerId, "factorySystem", "minerPool", useExportImport: false);
			AddComponentSnapshot(factory, list2, 4, entitySnapshot.EntityId, entitySnapshot.LabId, "factorySystem", "labPool", useExportImport: false);
		}
		using (MemoryStream memoryStream = new MemoryStream())
		{
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(1313624625);
			binaryWriter.Write(2);
			binaryWriter.Write(list.Count);
			for (int i = 0; i < list.Count; i++)
			{
				EntitySnapshot entitySnapshot2 = list[i];
				binaryWriter.Write(entitySnapshot2.EntityId);
				binaryWriter.Write(entitySnapshot2.ProtoId);
				binaryWriter.Write(entitySnapshot2.StorageId);
				binaryWriter.Write(entitySnapshot2.AssemblerId);
				binaryWriter.Write(entitySnapshot2.MinerId);
				binaryWriter.Write(entitySnapshot2.LabId);
				binaryWriter.Write(entitySnapshot2.BeltId);
				binaryWriter.Write(entitySnapshot2.Position.X);
				binaryWriter.Write(entitySnapshot2.Position.Y);
				binaryWriter.Write(entitySnapshot2.Position.Z);
			}
			binaryWriter.Write(list2.Count);
			for (int i = 0; i < list2.Count; i++)
			{
				ComponentSnapshot componentSnapshot = list2[i];
				binaryWriter.Write(componentSnapshot.Kind);
				binaryWriter.Write(componentSnapshot.EntityId);
				binaryWriter.Write(componentSnapshot.ComponentId);
				binaryWriter.Write(componentSnapshot.State.Length);
				binaryWriter.Write(componentSnapshot.State);
			}
			binaryWriter.Write(activeEnemyIds.Count);
			for (int i = 0; i < activeEnemyIds.Count; i++)
			{
				binaryWriter.Write(activeEnemyIds[i]);
			}
			binaryWriter.Write(nearbyEnemies.Count);
			for (int i = 0; i < nearbyEnemies.Count; i++)
			{
				GroundEnemySnapshot groundEnemySnapshot = nearbyEnemies[i];
				binaryWriter.Write(groundEnemySnapshot.EnemyId);
				binaryWriter.Write(groundEnemySnapshot.ProtoId);
				binaryWriter.Write(groundEnemySnapshot.Position.X);
				binaryWriter.Write(groundEnemySnapshot.Position.Y);
				binaryWriter.Write(groundEnemySnapshot.Position.Z);
			}
			binaryWriter.Flush();
			if (memoryStream.Length > maxPayloadBytes)
			{
				snapshotBuildResult.Error = "Resync payload would be " + memoryStream.Length + " bytes, above the server limit of " + maxPayloadBytes + ". Lower the radius.";
				snapshotBuildResult.Payload = new byte[0];
				return snapshotBuildResult;
			}
			snapshotBuildResult.Payload = memoryStream.ToArray();
		}
		snapshotBuildResult.EntityCount = list.Count;
		snapshotBuildResult.ComponentCount = list2.Count;
		snapshotBuildResult.BeltCount = num;
		snapshotBuildResult.ActiveGroundEnemyCount = activeEnemyIds.Count;
		snapshotBuildResult.NearbyGroundEnemyCount = nearbyEnemies.Count;
		snapshotBuildResult.Error = string.Empty;
		return snapshotBuildResult;
	}

	private static void AddComponentSnapshot(object factory, List<ComponentSnapshot> components, byte kind, int entityId, int componentId, string subsystemMember, string poolMember, bool useExportImport)
	{
		if (componentId <= 0 || !DspAccess.TryGetComponent(factory, subsystemMember, poolMember, componentId, out var component, out var _))
		{
			return;
		}
		byte[] bytes;
		if (useExportImport)
		{
			if (!DspAccess.TryExportComponent(component, out bytes))
			{
				return;
			}
		}
		else
		{
			bytes = SimpleFieldState.Serialize(component);
			if (bytes == null || bytes.Length == 0)
			{
				return;
			}
		}
		ComponentSnapshot componentSnapshot = new ComponentSnapshot();
		componentSnapshot.Kind = kind;
		componentSnapshot.EntityId = entityId;
		componentSnapshot.ComponentId = componentId;
		componentSnapshot.State = bytes;
		components.Add(componentSnapshot);
	}

	internal static SnapshotApplyResult ApplyClientSnapshot(object factory, byte[] payload, Vec3 center, float radius)
	{
		if (payload == null || payload.Length == 0)
		{
			throw new InvalidDataException("Resync payload is empty.");
		}
		List<EntitySnapshot> list = new List<EntitySnapshot>();
		List<ComponentSnapshot> list2 = new List<ComponentSnapshot>();
		HashSet<int> hashSet = new HashSet<int>();
		List<GroundEnemySnapshot> list3 = new List<GroundEnemySnapshot>();
		using (MemoryStream input = new MemoryStream(payload, writable: false))
		{
			using BinaryReader binaryReader = new BinaryReader(input);
			if (binaryReader.ReadInt32() != 1313624625)
			{
				throw new InvalidDataException("Invalid Nebula Local Resync payload magic.");
			}
			int num = binaryReader.ReadInt32();
			if (num != 2)
			{
				throw new InvalidDataException("Unsupported Nebula Local Resync payload version " + num + ".");
			}
			int num2 = binaryReader.ReadInt32();
			if (num2 < 0 || num2 > 100000)
			{
				throw new InvalidDataException("Invalid entity count " + num2 + ".");
			}
			for (int i = 0; i < num2; i++)
			{
				EntitySnapshot entitySnapshot = new EntitySnapshot();
				entitySnapshot.EntityId = binaryReader.ReadInt32();
				entitySnapshot.ProtoId = binaryReader.ReadInt32();
				entitySnapshot.StorageId = binaryReader.ReadInt32();
				entitySnapshot.AssemblerId = binaryReader.ReadInt32();
				entitySnapshot.MinerId = binaryReader.ReadInt32();
				entitySnapshot.LabId = binaryReader.ReadInt32();
				entitySnapshot.BeltId = binaryReader.ReadInt32();
				entitySnapshot.Position = new Vec3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
				list.Add(entitySnapshot);
			}
			int num3 = binaryReader.ReadInt32();
			if (num3 < 0 || num3 > 100000)
			{
				throw new InvalidDataException("Invalid component snapshot count " + num3 + ".");
			}
			for (int i = 0; i < num3; i++)
			{
				ComponentSnapshot componentSnapshot = new ComponentSnapshot();
				componentSnapshot.Kind = binaryReader.ReadByte();
				componentSnapshot.EntityId = binaryReader.ReadInt32();
				componentSnapshot.ComponentId = binaryReader.ReadInt32();
				int num4 = binaryReader.ReadInt32();
				if (num4 < 0 || num4 > payload.Length)
				{
					throw new InvalidDataException("Invalid component payload length " + num4 + ".");
				}
				componentSnapshot.State = binaryReader.ReadBytes(num4);
				if (componentSnapshot.State.Length != num4)
				{
					throw new EndOfStreamException("Truncated component payload.");
				}
				list2.Add(componentSnapshot);
			}
			int num5 = binaryReader.ReadInt32();
			if (num5 < 0 || num5 > 500000)
			{
				throw new InvalidDataException("Invalid active ground enemy count " + num5 + ".");
			}
			for (int i = 0; i < num5; i++)
			{
				int num6 = binaryReader.ReadInt32();
				if (num6 > 0)
				{
					hashSet.Add(num6);
				}
			}
			int num7 = binaryReader.ReadInt32();
			if (num7 < 0 || num7 > 500000)
			{
				throw new InvalidDataException("Invalid nearby ground enemy count " + num7 + ".");
			}
			for (int i = 0; i < num7; i++)
			{
				GroundEnemySnapshot groundEnemySnapshot = new GroundEnemySnapshot();
				groundEnemySnapshot.EnemyId = binaryReader.ReadInt32();
				groundEnemySnapshot.ProtoId = binaryReader.ReadInt32();
				groundEnemySnapshot.Position = new Vec3(binaryReader.ReadSingle(), binaryReader.ReadSingle(), binaryReader.ReadSingle());
				list3.Add(groundEnemySnapshot);
			}
		}
		SnapshotApplyResult snapshotApplyResult = new SnapshotApplyResult();
		Dictionary<int, EntitySnapshot> dictionary = new Dictionary<int, EntitySnapshot>();
		HashSet<int> hashSet2 = new HashSet<int>();
		for (int j = 0; j < list.Count; j++)
		{
			EntitySnapshot entitySnapshot2 = list[j];
			dictionary[entitySnapshot2.EntityId] = entitySnapshot2;
			if (entitySnapshot2.BeltId > 0)
			{
				snapshotApplyResult.BeltsAudited++;
			}
			if (!DspAccess.TryReadEntity(factory, entitySnapshot2.EntityId, out var snapshot))
			{
				snapshotApplyResult.HostOnlyEntities++;
			}
			else if (snapshot.ProtoId != entitySnapshot2.ProtoId || snapshot.StorageId != entitySnapshot2.StorageId || snapshot.AssemblerId != entitySnapshot2.AssemblerId || snapshot.MinerId != entitySnapshot2.MinerId || snapshot.LabId != entitySnapshot2.LabId || snapshot.BeltId != entitySnapshot2.BeltId)
			{
				snapshotApplyResult.MismatchedEntities++;
			}
			else
			{
				hashSet2.Add(entitySnapshot2.EntityId);
			}
		}
		List<EntitySnapshot> list4 = DspAccess.ScanEntities(factory, center, radius, 100001);
		for (int k = 0; k < list4.Count; k++)
		{
			if (!dictionary.ContainsKey(list4[k].EntityId))
			{
				snapshotApplyResult.ClientOnlyEntities++;
			}
		}
		DspAccess.ReconcileGroundEnemies(factory, center, radius, hashSet, list3, snapshotApplyResult);
		for (int l = 0; l < list2.Count; l++)
		{
			ComponentSnapshot componentSnapshot2 = list2[l];
			if (!hashSet2.Contains(componentSnapshot2.EntityId))
			{
				snapshotApplyResult.ComponentsSkipped++;
				continue;
			}
			bool flag = false;
			if (componentSnapshot2.Kind == 1)
			{
				flag = ApplyComponent(factory, componentSnapshot2, "factoryStorage", "storagePool", useExportImport: true);
			}
			else if (componentSnapshot2.Kind == 2)
			{
				flag = ApplyComponent(factory, componentSnapshot2, "factorySystem", "assemblerPool", useExportImport: false);
			}
			else if (componentSnapshot2.Kind == 3)
			{
				flag = ApplyComponent(factory, componentSnapshot2, "factorySystem", "minerPool", useExportImport: false);
			}
			else if (componentSnapshot2.Kind == 4)
			{
				flag = ApplyComponent(factory, componentSnapshot2, "factorySystem", "labPool", useExportImport: false);
			}
			if (flag)
			{
				snapshotApplyResult.ComponentsApplied++;
			}
			else
			{
				snapshotApplyResult.ComponentsSkipped++;
			}
		}
		return snapshotApplyResult;
	}

	private static bool ApplyComponent(object factory, ComponentSnapshot snapshot, string subsystemMember, string poolMember, bool useExportImport)
	{
		if (!DspAccess.TryGetComponent(factory, subsystemMember, poolMember, snapshot.ComponentId, out var component, out var pool))
		{
			return false;
		}
		bool flag = ((!useExportImport) ? SimpleFieldState.Apply(component, snapshot.State) : DspAccess.TryImportComponent(component, snapshot.State));
		if (flag && component.GetType().IsValueType)
		{
			pool.SetValue(component, snapshot.ComponentId);
		}
		return flag;
	}
}
internal static class SimpleFieldState
{
	private const byte BoolCode = 1;

	private const byte ByteCode = 2;

	private const byte SByteCode = 3;

	private const byte Int16Code = 4;

	private const byte UInt16Code = 5;

	private const byte Int32Code = 6;

	private const byte UInt32Code = 7;

	private const byte Int64Code = 8;

	private const byte UInt64Code = 9;

	private const byte SingleCode = 10;

	private const byte DoubleCode = 11;

	private const byte CharCode = 12;

	private const byte StringCode = 13;

	private const byte EnumCode = 14;

	private const byte ArrayOffset = 64;

	internal static byte[] Serialize(object component)
	{
		if (component == null)
		{
			return new byte[0];
		}
		List<FieldInfo> serializableFields = GetSerializableFields(component.GetType());
		if (serializableFields.Count == 0)
		{
			return new byte[0];
		}
		using MemoryStream memoryStream = new MemoryStream();
		using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
		binaryWriter.Write(serializableFields.Count);
		for (int i = 0; i < serializableFields.Count; i++)
		{
			FieldInfo fieldInfo = serializableFields[i];
			object value = fieldInfo.GetValue(component);
			byte typeCode = GetTypeCode(fieldInfo.FieldType);
			binaryWriter.Write(fieldInfo.Name);
			binaryWriter.Write(typeCode);
			WriteValue(binaryWriter, fieldInfo.FieldType, typeCode, value);
		}
		binaryWriter.Flush();
		return memoryStream.ToArray();
	}

	internal static bool Apply(object component, byte[] state)
	{
		if (component == null || state == null || state.Length == 0)
		{
			return false;
		}
		Type type = component.GetType();
		Dictionary<string, FieldInfo> dictionary = new Dictionary<string, FieldInfo>(StringComparer.Ordinal);
		FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		for (int i = 0; i < fields.Length; i++)
		{
			if (!fields[i].IsStatic)
			{
				dictionary[fields[i].Name] = fields[i];
			}
		}
		using (MemoryStream input = new MemoryStream(state, writable: false))
		{
			using BinaryReader binaryReader = new BinaryReader(input);
			int num = binaryReader.ReadInt32();
			if (num < 0 || num > 1024)
			{
				return false;
			}
			for (int i = 0; i < num; i++)
			{
				string key = binaryReader.ReadString();
				byte b = binaryReader.ReadByte();
				if (!dictionary.TryGetValue(key, out var value) || value.IsInitOnly || value.IsLiteral)
				{
					SkipValue(binaryReader, b);
					continue;
				}
				byte typeCode = GetTypeCode(value.FieldType);
				if (typeCode == 0 || typeCode != b)
				{
					SkipValue(binaryReader, b);
					continue;
				}
				object value2 = ReadValue(binaryReader, value.FieldType, b);
				value.SetValue(component, value2);
			}
		}
		return true;
	}

	private static List<FieldInfo> GetSerializableFields(Type type)
	{
		List<FieldInfo> list = new List<FieldInfo>();
		FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
		foreach (FieldInfo fieldInfo in fields)
		{
			if (!fieldInfo.IsStatic && !fieldInfo.IsLiteral && !fieldInfo.IsInitOnly && GetTypeCode(fieldInfo.FieldType) != 0)
			{
				list.Add(fieldInfo);
			}
		}
		list.Sort((FieldInfo a, FieldInfo b) => string.CompareOrdinal(a.Name, b.Name));
		return list;
	}

	private static byte GetTypeCode(Type type)
	{
		if (type.IsArray)
		{
			Type elementType = type.GetElementType();
			byte scalarTypeCode = GetScalarTypeCode(elementType);
			if (scalarTypeCode == 0 || scalarTypeCode == 13)
			{
				return 0;
			}
			return (byte)(64 + scalarTypeCode);
		}
		return GetScalarTypeCode(type);
	}

	private static byte GetScalarTypeCode(Type type)
	{
		if (type == typeof(bool))
		{
			return 1;
		}
		if (type == typeof(byte))
		{
			return 2;
		}
		if (type == typeof(sbyte))
		{
			return 3;
		}
		if (type == typeof(short))
		{
			return 4;
		}
		if (type == typeof(ushort))
		{
			return 5;
		}
		if (type == typeof(int))
		{
			return 6;
		}
		if (type == typeof(uint))
		{
			return 7;
		}
		if (type == typeof(long))
		{
			return 8;
		}
		if (type == typeof(ulong))
		{
			return 9;
		}
		if (type == typeof(float))
		{
			return 10;
		}
		if (type == typeof(double))
		{
			return 11;
		}
		if (type == typeof(char))
		{
			return 12;
		}
		if (type == typeof(string))
		{
			return 13;
		}
		if (type != null && type.IsEnum)
		{
			return 14;
		}
		return 0;
	}

	private static void WriteValue(BinaryWriter writer, Type declaredType, byte code, object value)
	{
		if (code >= 64)
		{
			if (!(value is Array array))
			{
				writer.Write(-1);
				return;
			}
			writer.Write(array.Length);
			byte code2 = (byte)(code - 64);
			Type elementType = declaredType.GetElementType();
			for (int i = 0; i < array.Length; i++)
			{
				WriteScalar(writer, elementType, code2, array.GetValue(i));
			}
		}
		else
		{
			WriteScalar(writer, declaredType, code, value);
		}
	}

	private static object ReadValue(BinaryReader reader, Type declaredType, byte code)
	{
		if (code >= 64)
		{
			int num = reader.ReadInt32();
			if (num < 0)
			{
				return null;
			}
			if (num > 1000000)
			{
				throw new InvalidDataException("Component array is unreasonably large: " + num + ".");
			}
			Type elementType = declaredType.GetElementType();
			byte code2 = (byte)(code - 64);
			Array array = Array.CreateInstance(elementType, num);
			for (int i = 0; i < num; i++)
			{
				array.SetValue(ReadScalar(reader, elementType, code2), i);
			}
			return array;
		}
		return ReadScalar(reader, declaredType, code);
	}

	private static void SkipValue(BinaryReader reader, byte code)
	{
		if (code >= 64)
		{
			int num = reader.ReadInt32();
			if (num >= 0)
			{
				byte code2 = (byte)(code - 64);
				for (int i = 0; i < num; i++)
				{
					ReadScalar(reader, null, code2);
				}
			}
		}
		else
		{
			ReadScalar(reader, null, code);
		}
	}

	private static void WriteScalar(BinaryWriter writer, Type declaredType, byte code, object value)
	{
		switch (code)
		{
		case 1:
			writer.Write(value != null && (bool)value);
			break;
		case 2:
			writer.Write((byte)((value != null) ? ((byte)value) : 0));
			break;
		case 3:
			writer.Write((sbyte)((value != null) ? ((sbyte)value) : 0));
			break;
		case 4:
			writer.Write((short)((value != null) ? ((short)value) : 0));
			break;
		case 5:
			writer.Write((ushort)((value != null) ? ((ushort)value) : 0));
			break;
		case 6:
			writer.Write((value != null) ? ((int)value) : 0);
			break;
		case 7:
			writer.Write((value != null) ? ((uint)value) : 0u);
			break;
		case 8:
			writer.Write((value == null) ? 0 : ((long)value));
			break;
		case 9:
			writer.Write((value == null) ? 0 : ((ulong)value));
			break;
		case 10:
			writer.Write((value == null) ? 0f : ((float)value));
			break;
		case 11:
			writer.Write((value == null) ? 0.0 : ((double)value));
			break;
		case 12:
			writer.Write((value != null) ? ((char)value) : '\0');
			break;
		case 13:
			writer.Write(value != null);
			if (value != null)
			{
				writer.Write((string)value);
			}
			break;
		case 14:
			writer.Write((value == null) ? 0 : Convert.ToInt64(value));
			break;
		default:
			throw new InvalidDataException("Unsupported field type code " + code + ".");
		}
	}

	private static object ReadScalar(BinaryReader reader, Type declaredType, byte code)
	{
		switch (code)
		{
		case 1:
			return reader.ReadBoolean();
		case 2:
			return reader.ReadByte();
		case 3:
			return reader.ReadSByte();
		case 4:
			return reader.ReadInt16();
		case 5:
			return reader.ReadUInt16();
		case 6:
			return reader.ReadInt32();
		case 7:
			return reader.ReadUInt32();
		case 8:
			return reader.ReadInt64();
		case 9:
			return reader.ReadUInt64();
		case 10:
			return reader.ReadSingle();
		case 11:
			return reader.ReadDouble();
		case 12:
			return reader.ReadChar();
		case 13:
			if (!reader.ReadBoolean())
			{
				return null;
			}
			return reader.ReadString();
		case 14:
		{
			long num = reader.ReadInt64();
			if (!(declaredType != null) || !declaredType.IsEnum)
			{
				return num;
			}
			return Enum.ToObject(declaredType, num);
		}
		default:
			throw new InvalidDataException("Unsupported field type code " + code + ".");
		}
	}
}
internal static class DspAccess
{
	private const BindingFlags AnyInstance = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

	private const BindingFlags AnyStatic = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

	private static Type _gameMainType;

	internal static bool TryGetLocalFactoryContext(out int planetId, out Vec3 center, out object factory, out string error)
	{
		planetId = 0;
		center = default(Vec3);
		factory = null;
		error = string.Empty;
		try
		{
			Type gameMainType = GetGameMainType();
			if (gameMainType == null)
			{
				error = "GameMain type is unavailable.";
				return false;
			}
			object memberValue = GetMemberValue(gameMainType, null, "localPlanet", isStatic: true);
			object memberValue2 = GetMemberValue(gameMainType, null, "mainPlayer", isStatic: true);
			if (memberValue == null || memberValue2 == null)
			{
				error = "the local mech is not on a loaded planet.";
				return false;
			}
			planetId = Convert.ToInt32(GetMemberValue(memberValue.GetType(), memberValue, "id", isStatic: false));
			object memberValue3 = GetMemberValue(memberValue2.GetType(), memberValue2, "position", isStatic: false);
			if (!TryReadVector(memberValue3, out center))
			{
				error = "the local mech position could not be read.";
				return false;
			}
			factory = GetMemberValue(memberValue.GetType(), memberValue, "factory", isStatic: false);
			if (factory == null)
			{
				error = "the local PlanetFactory is not loaded.";
				return false;
			}
			return true;
		}
		catch (Exception ex)
		{
			error = "DSP state lookup failed: " + ex.Message;
			return false;
		}
	}

	internal static bool TryGetFactoryForPlanet(int planetId, out object factory, out string error)
	{
		factory = null;
		error = string.Empty;
		try
		{
			Type gameMainType = GetGameMainType();
			if (gameMainType == null)
			{
				error = "Host GameMain type is unavailable.";
				return false;
			}
			object memberValue = GetMemberValue(gameMainType, null, "galaxy", isStatic: true);
			if (memberValue == null)
			{
				error = "Host galaxy is not loaded.";
				return false;
			}
			MethodInfo method = memberValue.GetType().GetMethod("PlanetById", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(int) }, null);
			if (method == null)
			{
				error = "Host GalaxyData.PlanetById(int) was not found.";
				return false;
			}
			object obj = method.Invoke(memberValue, new object[1] { planetId });
			if (obj == null)
			{
				error = "Host does not know planet " + planetId + ".";
				return false;
			}
			factory = GetMemberValue(obj.GetType(), obj, "factory", isStatic: false);
			if (factory == null)
			{
				error = "Host PlanetFactory for planet " + planetId + " is not loaded.";
				return false;
			}
			return true;
		}
		catch (Exception ex)
		{
			error = "Host factory lookup failed: " + ex.Message;
			return false;
		}
	}

	internal static List<EntitySnapshot> ScanEntities(object factory, Vec3 center, float radius, int hardLimit)
	{
		List<EntitySnapshot> list = new List<EntitySnapshot>();
		if (!(GetMemberValue(factory.GetType(), factory, "entityPool", isStatic: false) is Array array))
		{
			return list;
		}
		int val = Convert.ToInt32(GetMemberValue(factory.GetType(), factory, "entityCursor", isStatic: false));
		int num = Math.Min(val, array.Length);
		float num2 = radius * radius;
		for (int i = 1; i < num; i++)
		{
			object value = array.GetValue(i);
			if (!IsActiveIndexedObject(value, i))
			{
				continue;
			}
			object value2 = TryGetMemberValue(value.GetType(), value, "pos", isStatic: false);
			if (TryReadVector(value2, out var vector) && !(vector.DistanceSquared(center) > num2) && TryCreateEntitySnapshot(value, i, vector, out var snapshot))
			{
				list.Add(snapshot);
				if (list.Count >= hardLimit)
				{
					return list;
				}
			}
		}
		return list;
	}

	internal static bool TryReadEntity(object factory, int entityId, out EntitySnapshot snapshot)
	{
		snapshot = null;
		if (!(TryGetMemberValue(factory.GetType(), factory, "entityPool", isStatic: false) is Array array) || entityId <= 0 || entityId >= array.Length)
		{
			return false;
		}
		object value = array.GetValue(entityId);
		if (!IsActiveIndexedObject(value, entityId))
		{
			return false;
		}
		if (!TryReadVector(TryGetMemberValue(value.GetType(), value, "pos", isStatic: false), out var vector))
		{
			vector = default(Vec3);
		}
		return TryCreateEntitySnapshot(value, entityId, vector, out snapshot);
	}

	private static bool TryCreateEntitySnapshot(object entity, int fallbackId, Vec3 position, out EntitySnapshot snapshot)
	{
		snapshot = null;
		if (entity == null)
		{
			return false;
		}
		EntitySnapshot entitySnapshot = new EntitySnapshot();
		entitySnapshot.EntityId = ReadIntMember(entity, "id", fallbackId);
		entitySnapshot.ProtoId = ReadIntMember(entity, "protoId", 0);
		entitySnapshot.StorageId = ReadIntMember(entity, "storageId", 0);
		entitySnapshot.AssemblerId = ReadIntMember(entity, "assemblerId", 0);
		entitySnapshot.MinerId = ReadIntMember(entity, "minerId", 0);
		entitySnapshot.LabId = ReadIntMember(entity, "labId", 0);
		entitySnapshot.BeltId = ReadIntMember(entity, "beltId", 0);
		entitySnapshot.Position = position;
		snapshot = entitySnapshot;
		if (entitySnapshot.EntityId > 0)
		{
			return entitySnapshot.ProtoId > 0;
		}
		return false;
	}

	internal static bool TryScanHostGroundEnemies(object factory, Vec3 center, float radius, int hardLimit, out List<int> activeEnemyIds, out List<GroundEnemySnapshot> nearbyEnemies, out string error)
	{
		activeEnemyIds = new List<int>();
		nearbyEnemies = new List<GroundEnemySnapshot>();
		error = string.Empty;
		if (factory == null)
		{
			error = "Host PlanetFactory is unavailable while scanning Dark Fog enemies.";
			return false;
		}
		if (!(TryGetMemberValue(factory.GetType(), factory, "enemyPool", isStatic: false) is Array array))
		{
			return true;
		}
		int val = ReadIntMember(factory, "enemyCursor", array.Length);
		int num = Math.Min(Math.Max(val, 0), array.Length);
		float num2 = radius * radius;
		for (int i = 1; i < num; i++)
		{
			object value = array.GetValue(i);
			if (value == null)
			{
				continue;
			}
			int num3 = ReadIntMember(value, "id", 0);
			if (num3 == i)
			{
				activeEnemyIds.Add(i);
				if (activeEnemyIds.Count > hardLimit)
				{
					error = "Planet contains more than " + hardLimit + " active ground enemies. Raise Server.MaximumGroundEnemiesPerPlanet before using Local Resync on this save.";
					activeEnemyIds.Clear();
					nearbyEnemies.Clear();
					return false;
				}
				if (TryReadVector(TryGetMemberValue(value.GetType(), value, "pos", isStatic: false), out var vector) && vector.DistanceSquared(center) <= num2)
				{
					GroundEnemySnapshot groundEnemySnapshot = new GroundEnemySnapshot();
					groundEnemySnapshot.EnemyId = i;
					groundEnemySnapshot.ProtoId = ReadIntMember(value, "protoId", 0);
					groundEnemySnapshot.Position = vector;
					nearbyEnemies.Add(groundEnemySnapshot);
				}
			}
		}
		return true;
	}

	internal static void ReconcileGroundEnemies(object factory, Vec3 center, float radius, HashSet<int> hostActiveEnemyIds, List<GroundEnemySnapshot> hostNearbyEnemies, SnapshotApplyResult result)
	{
		if (factory == null || result == null || hostActiveEnemyIds == null || hostNearbyEnemies == null)
		{
			return;
		}
		List<LocalGroundEnemyState> list = ScanLocalGroundEnemyCandidates(factory, center, radius);
		Dictionary<int, LocalGroundEnemyState> dictionary = new Dictionary<int, LocalGroundEnemyState>();
		for (int i = 0; i < list.Count; i++)
		{
			dictionary[list[i].EnemyId] = list[i];
		}
		for (int i = 0; i < hostNearbyEnemies.Count; i++)
		{
			GroundEnemySnapshot groundEnemySnapshot = hostNearbyEnemies[i];
			if (!dictionary.TryGetValue(groundEnemySnapshot.EnemyId, out var value))
			{
				result.GroundEnemiesMissingLocally++;
			}
			else if (groundEnemySnapshot.ProtoId > 0 && value.ProtoId > 0 && groundEnemySnapshot.ProtoId != value.ProtoId)
			{
				result.GroundEnemyRepairsSkipped++;
			}
			else if (value.IsInvinciblePending && hostActiveEnemyIds.Contains(value.EnemyId))
			{
				if (TryRestorePendingGroundEnemy(factory, value.EnemyId))
				{
					result.GroundEnemyPendingFlagsRepaired++;
				}
				else
				{
					result.GroundEnemyRepairsSkipped++;
				}
			}
		}
		using IDisposable disposable = TryEnterNebulaIncomingCombatScope();
		for (int i = 0; i < list.Count; i++)
		{
			LocalGroundEnemyState localGroundEnemyState = list[i];
			if (!hostActiveEnemyIds.Contains(localGroundEnemyState.EnemyId))
			{
				if (disposable == null)
				{
					result.GroundEnemyRepairsSkipped++;
				}
				else if (TryRemoveLocalGroundEnemyFinal(factory, localGroundEnemyState.EnemyId))
				{
					result.GroundEnemyPhantomsCleared++;
				}
				else
				{
					result.GroundEnemyRepairsSkipped++;
				}
			}
		}
	}

	private static List<LocalGroundEnemyState> ScanLocalGroundEnemyCandidates(object factory, Vec3 center, float radius)
	{
		List<LocalGroundEnemyState> list = new List<LocalGroundEnemyState>();
		if (!(TryGetMemberValue(factory.GetType(), factory, "enemyPool", isStatic: false) is Array array))
		{
			return list;
		}
		int val = ReadIntMember(factory, "enemyCursor", array.Length);
		int num = Math.Min(Math.Max(val, 0), array.Length);
		float num2 = radius * radius;
		for (int i = 1; i < num; i++)
		{
			object value = array.GetValue(i);
			if (value != null)
			{
				int num3 = ReadIntMember(value, "id", 0);
				bool flag = num3 == i;
				bool flag2 = ReadBoolMember(value, "isInvincible", fallback: false) && num3 != i;
				if ((flag || flag2) && TryReadVector(TryGetMemberValue(value.GetType(), value, "pos", isStatic: false), out var vector) && !(vector.DistanceSquared(center) > num2))
				{
					LocalGroundEnemyState localGroundEnemyState = new LocalGroundEnemyState();
					localGroundEnemyState.EnemyId = i;
					localGroundEnemyState.ProtoId = ReadIntMember(value, "protoId", 0);
					localGroundEnemyState.Position = vector;
					localGroundEnemyState.IsActive = flag;
					localGroundEnemyState.IsInvinciblePending = flag2;
					list.Add(localGroundEnemyState);
				}
			}
		}
		return list;
	}

	private static bool TryRestorePendingGroundEnemy(object factory, int enemyId)
	{
		if (!(TryGetMemberValue(factory.GetType(), factory, "enemyPool", isStatic: false) is Array array) || enemyId <= 0 || enemyId >= array.Length)
		{
			return false;
		}
		object value = array.GetValue(enemyId);
		if (value == null || !ReadBoolMember(value, "isInvincible", fallback: false))
		{
			return false;
		}
		if (!TrySetMemberValue(value.GetType(), value, "id", enemyId) || !TrySetMemberValue(value.GetType(), value, "isInvincible", false))
		{
			return false;
		}
		if (value.GetType().IsValueType)
		{
			array.SetValue(value, enemyId);
		}
		return true;
	}

	private static bool TryRemoveLocalGroundEnemyFinal(object factory, int enemyId)
	{
		if (factory == null || enemyId <= 0)
		{
			return false;
		}
		if (!(TryGetMemberValue(factory.GetType(), factory, "enemyPool", isStatic: false) is Array array) || enemyId >= array.Length)
		{
			return false;
		}
		MethodInfo method = factory.GetType().GetMethod("RemoveEnemyFinal", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(int) }, null);
		if (method == null)
		{
			return false;
		}
		object value = array.GetValue(enemyId);
		if (value == null)
		{
			return false;
		}
		int num = ReadIntMember(value, "id", 0);
		bool flag = ReadBoolMember(value, "isInvincible", fallback: false);
		if (num != enemyId && !TrySetMemberValue(value.GetType(), value, "id", enemyId))
		{
			return false;
		}
		if (flag && !TrySetMemberValue(value.GetType(), value, "isInvincible", false))
		{
			return false;
		}
		if (value.GetType().IsValueType)
		{
			array.SetValue(value, enemyId);
		}
		try
		{
			method.Invoke(factory, new object[1] { enemyId });
			return true;
		}
		catch
		{
			object value2 = array.GetValue(enemyId);
			if (value2 != null)
			{
				TrySetMemberValue(value2.GetType(), value2, "id", num);
				TrySetMemberValue(value2.GetType(), value2, "isInvincible", flag);
				if (value2.GetType().IsValueType)
				{
					array.SetValue(value2, enemyId);
				}
			}
			return false;
		}
	}

	private static IDisposable TryEnterNebulaIncomingCombatScope()
	{
		try
		{
			Type type = FindLoadedType("NebulaWorld.Multiplayer");
			if (type == null)
			{
				return null;
			}
			object obj = TryGetMemberValue(type, null, "Session", isStatic: true);
			if (obj == null)
			{
				return null;
			}
			object obj2 = TryGetMemberValue(obj.GetType(), obj, "Combat", isStatic: false);
			if (obj2 == null)
			{
				return null;
			}
			object obj3 = TryGetMemberValue(obj2.GetType(), obj2, "IsIncomingRequest", isStatic: false);
			if (obj3 == null)
			{
				return null;
			}
			MethodInfo method = obj3.GetType().GetMethod("On", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
			if (method == null)
			{
				return null;
			}
			return method.Invoke(obj3, null) as IDisposable;
		}
		catch
		{
			return null;
		}
	}

	internal static bool TryGetComponent(object factory, string subsystemMember, string poolMember, int componentId, out object component, out Array pool)
	{
		component = null;
		pool = null;
		if (factory == null || componentId <= 0)
		{
			return false;
		}
		object obj = TryGetMemberValue(factory.GetType(), factory, subsystemMember, isStatic: false);
		if (obj == null)
		{
			return false;
		}
		pool = TryGetMemberValue(obj.GetType(), obj, poolMember, isStatic: false) as Array;
		if (pool == null || componentId >= pool.Length)
		{
			return false;
		}
		component = pool.GetValue(componentId);
		if (!IsActiveIndexedObject(component, componentId))
		{
			component = null;
			return false;
		}
		return true;
	}

	internal static bool TryExportComponent(object component, out byte[] bytes)
	{
		bytes = null;
		if (component == null)
		{
			return false;
		}
		MethodInfo method = component.GetType().GetMethod("Export", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(BinaryWriter) }, null);
		if (method == null)
		{
			return false;
		}
		using MemoryStream memoryStream = new MemoryStream();
		using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
		method.Invoke(component, new object[1] { binaryWriter });
		binaryWriter.Flush();
		bytes = memoryStream.ToArray();
		return true;
	}

	internal static bool TryImportComponent(object component, byte[] bytes)
	{
		if (component == null || bytes == null)
		{
			return false;
		}
		MethodInfo method = component.GetType().GetMethod("Import", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[1] { typeof(BinaryReader) }, null);
		if (method == null)
		{
			return false;
		}
		using MemoryStream input = new MemoryStream(bytes, writable: false);
		using BinaryReader binaryReader = new BinaryReader(input);
		method.Invoke(component, new object[1] { binaryReader });
		return true;
	}

	internal static void TryShowRealtimeTip(string message)
	{
		try
		{
			Type type = FindLoadedType("UIRealtimeTip");
			if (type == null)
			{
				return;
			}
			MethodInfo[] methods = type.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			foreach (MethodInfo methodInfo in methods)
			{
				if (methodInfo.Name != "Popup")
				{
					continue;
				}
				ParameterInfo[] parameters = methodInfo.GetParameters();
				if (parameters.Length == 0 || parameters[0].ParameterType != typeof(string))
				{
					continue;
				}
				object[] array = new object[parameters.Length];
				bool flag = true;
				for (int j = 0; j < parameters.Length; j++)
				{
					Type parameterType = parameters[j].ParameterType;
					if (j == 0 && parameterType == typeof(string))
					{
						array[j] = message;
						continue;
					}
					if (parameters[j].HasDefaultValue)
					{
						array[j] = parameters[j].DefaultValue;
						continue;
					}
					if (parameterType == typeof(bool))
					{
						array[j] = false;
						continue;
					}
					if (parameterType == typeof(int))
					{
						array[j] = 0;
						continue;
					}
					if (parameterType == typeof(float))
					{
						array[j] = 0f;
						continue;
					}
					flag = false;
					break;
				}
				if (flag)
				{
					methodInfo.Invoke(null, array);
					break;
				}
			}
		}
		catch
		{
		}
	}

	private static Type GetGameMainType()
	{
		if (_gameMainType == null)
		{
			_gameMainType = FindLoadedType("GameMain");
		}
		return _gameMainType;
	}

	private static Type FindLoadedType(string fullName)
	{
		Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
		for (int i = 0; i < assemblies.Length; i++)
		{
			Type type = assemblies[i].GetType(fullName, throwOnError: false);
			if (type != null)
			{
				return type;
			}
		}
		return null;
	}

	private static bool IsActiveIndexedObject(object value, int expectedId)
	{
		if (value == null)
		{
			return false;
		}
		object obj = TryGetMemberValue(value.GetType(), value, "id", isStatic: false);
		if (obj == null)
		{
			return true;
		}
		try
		{
			int num = Convert.ToInt32(obj);
			return num == expectedId;
		}
		catch
		{
			return true;
		}
	}

	private static int ReadIntMember(object value, string memberName, int fallback)
	{
		object obj = TryGetMemberValue(value.GetType(), value, memberName, isStatic: false);
		if (obj == null)
		{
			return fallback;
		}
		try
		{
			return Convert.ToInt32(obj);
		}
		catch
		{
			return fallback;
		}
	}

	private static bool ReadBoolMember(object value, string memberName, bool fallback)
	{
		if (value == null)
		{
			return fallback;
		}
		object obj = TryGetMemberValue(value.GetType(), value, memberName, isStatic: false);
		if (obj == null)
		{
			return fallback;
		}
		try
		{
			return Convert.ToBoolean(obj);
		}
		catch
		{
			return fallback;
		}
	}

	private static bool TrySetMemberValue(Type type, object instance, string name, object value)
	{
		try
		{
			FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null && !field.IsInitOnly && !field.IsLiteral)
			{
				field.SetValue(instance, value);
				return true;
			}
			PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
			if (property != null && property.CanWrite)
			{
				property.SetValue(instance, value, null);
				return true;
			}
		}
		catch
		{
		}
		return false;
	}

	private static bool TryReadVector(object value, out Vec3 vector)
	{
		vector = default(Vec3);
		if (value == null)
		{
			return false;
		}
		object obj = TryGetMemberValue(value.GetType(), value, "x", isStatic: false);
		object obj2 = TryGetMemberValue(value.GetType(), value, "y", isStatic: false);
		object obj3 = TryGetMemberValue(value.GetType(), value, "z", isStatic: false);
		if (obj == null || obj2 == null || obj3 == null)
		{
			return false;
		}
		vector = new Vec3(Convert.ToSingle(obj), Convert.ToSingle(obj2), Convert.ToSingle(obj3));
		return true;
	}

	private static object TryGetMemberValue(Type type, object instance, string name, bool isStatic)
	{
		try
		{
			return GetMemberValue(type, instance, name, isStatic);
		}
		catch
		{
			return null;
		}
	}

	private static object GetMemberValue(Type type, object instance, string name, bool isStatic)
	{
		BindingFlags bindingAttr = (isStatic ? (BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) : (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic));
		FieldInfo field = type.GetField(name, bindingAttr);
		if (field != null)
		{
			return field.GetValue(instance);
		}
		PropertyInfo property = type.GetProperty(name, bindingAttr);
		if (property != null)
		{
			return property.GetValue(instance, null);
		}
		throw new MissingMemberException(type.FullName, name);
	}
}