Esempio n. 1
0
        /// <summary>
        /// Do some basic validations over the protovessel
        /// </summary>
        private static bool ProtoVesselValidationsPassed(ProtoVessel vesselProto)
        {
            if (vesselProto == null)
            {
                LunaLog.LogError("[LMP]: protoVessel is null!");
                return(false);
            }

            if (vesselProto.vesselID == Guid.Empty)
            {
                LunaLog.LogError("[LMP]: protoVessel id is null!");
                return(false);
            }

            if (vesselProto.situation == Vessel.Situations.FLYING)
            {
                if (vesselProto.orbitSnapShot == null)
                {
                    LunaLog.LogWarning("[LMP]: Skipping flying vessel load - Protovessel does not have an orbit snapshot");
                    return(false);
                }
                if (FlightGlobals.Bodies == null || FlightGlobals.Bodies.Count < vesselProto.orbitSnapShot.ReferenceBodyIndex)
                {
                    LunaLog.LogWarning($"[LMP]: Skipping flying vessel load - Could not find celestial body index {vesselProto.orbitSnapShot.ReferenceBodyIndex}");
                    return(false);
                }
            }
            return(true);
        }
        public void SendVesselMessage(Vessel vessel, bool forceReload = false)
        {
            if (vessel == null || vessel.state == Vessel.State.DEAD || VesselRemoveSystem.Singleton.VesselWillBeKilled(vessel.id))
            {
                return;
            }

            if (!vessel.orbitDriver)
            {
                LunaLog.LogWarning($"Cannot send vessel {vessel.vesselName} - {vessel.id}. It's orbit driver is null!");
                return;
            }

            if (vessel.orbitDriver.Ready())
            {
                vessel.protoVessel = vessel.BackupVessel();
                SendVesselMessage(vessel.protoVessel, forceReload);
            }
            else
            {
                //Orbit driver is not ready so wait max 10 frames until it's ready
                CoroutineUtil.StartConditionRoutine("SendVesselMessage",
                                                    () => SendVesselMessage(vessel),
                                                    () => vessel.orbitDriver.Ready(), 10);
            }
        }
Esempio n. 3
0
        private static bool ConnectionIsStuck(int maxIdleMiliseconds = 2000)
        {
            if ((LunaComputerTime.UtcNow - _lastStateTime).TotalMilliseconds > maxIdleMiliseconds)
            {
                LunaLog.LogWarning($"Connection got stuck while connecting after waiting {maxIdleMiliseconds} ms, resending last request!");
                return(true);
            }

            return(false);
        }
        /// <summary>
        /// Checks if the protovessel has resources, parts that you don't have or that they are banned
        /// </summary>
        public static bool HasInvalidParts(this ProtoVessel pv, bool verboseErrors)
        {
            foreach (var pps in pv.protoPartSnapshots)
            {
                if (ModSystem.Singleton.ModControl && !ModSystem.Singleton.AllowedParts.Contains(pps.partName))
                {
                    if (verboseErrors)
                    {
                        var msg = $"Protovessel {pv.vesselID} ({pv.vesselName}) contains the BANNED PART '{pps.partName}'. Skipping load.";
                        LunaLog.LogWarning(msg);
                        ChatSystem.Singleton.PmMessageServer(msg);
                    }

                    return(true);
                }

                var invalidResources = pps.resources.Select(r => r.resourceName).Except(ModSystem.Singleton.AllowedResources).ToArray();
                if (ModSystem.Singleton.ModControl && invalidResources.Any())
                {
                    if (verboseErrors)
                    {
                        var msg = $"Protovessel {pv.vesselID} ({pv.vesselName}) contains the BANNED RESOURCE/S '{string.Join(", ", invalidResources)}'. Skipping load.";
                        LunaLog.LogWarning(msg);
                        ChatSystem.Singleton.PmMessageServer(msg);
                    }

                    return(true);
                }

                if (pps.partInfo == null)
                {
                    if (verboseErrors)
                    {
                        LunaLog.LogWarning($"Protovessel {pv.vesselID} ({pv.vesselName}) contains the MISSING PART '{pps.partName}'. Skipping load.");
                        LunaScreenMsg.PostScreenMessage($"Cannot load '{pv.vesselName}' - missing part: {pps.partName}", 10f, ScreenMessageStyle.UPPER_CENTER);
                    }

                    return(true);
                }

                var missingResource = pps.resources.FirstOrDefault(r => !PartResourceLibrary.Instance.resourceDefinitions.Contains(r.resourceName));
                if (missingResource != null && verboseErrors)
                {
                    var msg = $"Protovessel {pv.vesselID} ({pv.vesselName}) contains the MISSING RESOURCE '{missingResource.resourceName}'.";
                    LunaLog.LogWarning(msg);
                    ChatSystem.Singleton.PmMessageServer(msg);

                    LunaScreenMsg.PostScreenMessage($"Vessel '{pv.vesselName}' contains the modded RESOURCE: {pps.partName}", 10f, ScreenMessageStyle.UPPER_CENTER);
                    //We allow loading of vessels that have missing resources. They will be removed by the player with the lock tough...
                }
            }

            return(false);
        }
        public void PartModuleObjectFieldChanged(PartModule module, string fieldName, object newValue)
        {
            if (!CallIsValid(module, fieldName))
            {
                return;
            }

            LunaLog.Log($"Field {fieldName} in module {module.moduleName} from part {module.part.flightID} has a new OBJECT value of {newValue}.");
            LunaLog.LogWarning($"Field {fieldName} in module {module.moduleName} from part {module.part.flightID} has a field type that is not supported!");
            System.MessageSender.SendVesselPartSyncFieldObjectMsg(module.vessel, module.part, module.moduleName, fieldName, newValue);
        }
        /// <summary>
        /// Forces a time sync against the server time
        /// </summary>
        public void ForceTimeSync()
        {
            if (Enabled && !CurrentlyWarping && CanSyncTime && !WarpSystem.Singleton.WaitingSubspaceIdFromServer)
            {
                var targetTime   = WarpSystem.Singleton.CurrentSubspaceTime;
                var currentError = TimeUtil.SecondsToMilliseconds(CurrentErrorSec);

                LunaLog.LogWarning($"FORCING a time sync from: {Planetarium.GetUniversalTime()} to: {targetTime}. Error:{currentError}");
                ClockHandler.StepClock(targetTime);
            }
        }
Esempio n. 7
0
 /// <summary>
 /// Routine that checks our time against the server time and adjust it if needed.
 /// If the error is too big this routine will adjust the clock with the planetarium instead of accelerating / decreasing the game time
 /// </summary>
 /// <returns></returns>
 private void SyncTime()
 {
     if (Enabled && !CurrentlyWarping && CanSyncTime && !SystemsContainer.Get <WarpSystem>().WaitingSubspaceIdFromServer)
     {
         var targetTime   = (int)SystemsContainer.Get <WarpSystem>().CurrentSubspaceTime;
         var currentError = TimeSpan.FromSeconds(CurrentErrorSec).TotalMilliseconds;
         if (targetTime != 0 && Math.Abs(currentError) > MaxClockErrorMs)
         {
             LunaLog.LogWarning($"[LMP] Adjusted time from: {Planetarium.GetUniversalTime()} to: {targetTime} due to error:{currentError}");
             ClockHandler.StepClock(targetTime);
         }
     }
 }
 /// <summary>
 /// Routine that checks our time against the server time and adjust it if needed.
 /// If the error is too big this routine will adjust the clock with the planetarium instead of accelerating / decreasing the game time
 /// </summary>
 /// <returns></returns>
 private void SyncTime()
 {
     if (Enabled && !CurrentlyWarping && CanSyncTime && !WarpSystem.Singleton.WaitingSubspaceIdFromServer)
     {
         var targetTime   = WarpSystem.Singleton.CurrentSubspaceTime;
         var currentError = TimeUtil.SecondsToMilliseconds(CurrentErrorSec);
         if (targetTime != 0 && Math.Abs(currentError) > MaxClockErrorMs)
         {
             LunaLog.LogWarning($"[LMP] Adjusted time from: {Planetarium.GetUniversalTime()} to: {targetTime} due to error:{currentError}");
             ClockHandler.StepClock(targetTime);
         }
     }
 }
        /// <summary>
        /// Creates a protovessel from a ConfigNode
        /// </summary>
        public static ProtoVessel CreateSafeProtoVesselFromConfigNode(ConfigNode inputNode, Guid protoVesselId)
        {
            try
            {
                //Cannot create a protovessel if HighLogic.CurrentGame is null as we don't have a CrewRoster
                //and the protopartsnapshot constructor needs it
                if (HighLogic.CurrentGame == null)
                {
                    return(null);
                }

                //Cannot reuse the Protovessel to save memory garbage as it does not have any clear method :(
                var pv = new ProtoVessel(inputNode, HighLogic.CurrentGame);
                foreach (var pps in pv.protoPartSnapshots)
                {
                    if (ModSystem.Singleton.ModControl && !ModSystem.Singleton.AllowedParts.Contains(pps.partName))
                    {
                        var msg = $"Protovessel {protoVesselId} ({pv.vesselName}) contains the BANNED PART '{pps.partName}'. Skipping load.";
                        LunaLog.LogWarning(msg);
                        ChatSystem.Singleton.PmMessageServer(msg);

                        return(null);
                    }

                    if (pps.partInfo == null)
                    {
                        LunaLog.LogWarning($"WARNING: Protovessel {protoVesselId} ({pv.vesselName}) contains the MISSING PART '{pps.partName}'. Skipping load.");
                        LunaScreenMsg.PostScreenMessage($"Cannot load '{pv.vesselName}' - missing {pps.partName}", 10f, ScreenMessageStyle.UPPER_CENTER);

                        return(null);
                    }

                    var missingeResource = pps.resources.FirstOrDefault(r => !PartResourceLibrary.Instance.resourceDefinitions.Contains(r.resourceName));
                    if (missingeResource != null)
                    {
                        var msg = $"WARNING: Protovessel {protoVesselId} ({pv.vesselName}) contains the MISSING RESOURCE '{missingeResource.resourceName}'. Skipping load.";
                        LunaLog.LogWarning(msg);
                        ChatSystem.Singleton.PmMessageServer(msg);

                        LunaScreenMsg.PostScreenMessage($"Cannot load '{pv.vesselName}' - missing resource {missingeResource.resourceName}", 10f, ScreenMessageStyle.UPPER_CENTER);
                        return(null);
                    }
                }
                return(pv);
            }
            catch (Exception e)
            {
                LunaLog.LogError($"[LMP]: Damaged vessel {protoVesselId}, exception: {e}");
                return(null);
            }
        }
Esempio n. 10
0
        /// <summary>
        /// Check vessels that must be reloaded
        /// </summary>
        private void CheckVesselsToRefresh()
        {
            try
            {
                if (TimeUtil.IsInInterval(ref _lastReloadCheck, 1500) && ProtoSystemBasicReady)
                {
                    VesselsToRefresh.Clear();

                    //We get the vessels that already exist
                    VesselsToRefresh.AddRange(VesselsProtoStore.AllPlayerVessels
                                              .Where(pv => pv.Value.VesselExist && pv.Value.VesselHasUpdate && !pv.Value.HasInvalidParts)
                                              .Select(v => v.Key));

                    //Do not iterate directly trough the AllPlayerVessels dictionary as the collection can be modified in another threads!
                    foreach (var vesselIdToReload in VesselsToRefresh)
                    {
                        if (VesselRemoveSystem.VesselWillBeKilled(vesselIdToReload))
                        {
                            continue;
                        }

                        if (FlightGlobals.ActiveVessel?.id == vesselIdToReload)
                        {
                            LunaLog.LogWarning("Reloading our OWN active vessel!");
                        }

                        if (VesselsProtoStore.AllPlayerVessels.TryGetValue(vesselIdToReload, out var vesselProtoUpd))
                        {
                            CurrentlyUpdatingVesselId = vesselIdToReload;
                            ProtoToVesselRefresh.UpdateVesselPartsFromProtoVessel(vesselProtoUpd.Vessel, vesselProtoUpd.ProtoVessel, vesselProtoUpd.ForceReload, vesselProtoUpd.VesselParts.Keys);
                            vesselProtoUpd.VesselHasUpdate = false;
                            VesselReloadEvent.onLmpVesselReloaded.Fire(vesselProtoUpd.Vessel);
                            CurrentlyUpdatingVesselId = Guid.Empty;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                LunaLog.LogError($"[LMP]: Error in CheckVesselsToReload {e}");
            }
        }
        /// <summary>
        /// Checks the protovessel for errors
        /// </summary>
        public static bool Validate(this ProtoVessel protoVessel)
        {
            if (protoVessel == null)
            {
                LunaLog.LogError("[LMP]: protoVessel is null!");
                return(false);
            }

            if (protoVessel.vesselID == Guid.Empty)
            {
                LunaLog.LogError("[LMP]: protoVessel id is null!");
                return(false);
            }

            if (protoVessel.situation == Vessel.Situations.FLYING)
            {
                if (protoVessel.orbitSnapShot == null)
                {
                    LunaLog.LogWarning("[LMP]: Skipping flying vessel load - Protovessel does not have an orbit snapshot");
                    return(false);
                }
                if (FlightGlobals.Bodies == null || FlightGlobals.Bodies.Count < protoVessel.orbitSnapShot.ReferenceBodyIndex)
                {
                    LunaLog.LogWarning($"[LMP]: Skipping flying vessel load - Could not find celestial body index {protoVessel.orbitSnapShot.ReferenceBodyIndex}");
                    return(false);
                }
            }

            //Fix the flags urls in the vessel. The flag have the value as: "Squad/Flags/default"
            foreach (var part in protoVessel.protoPartSnapshots.Where(p => !string.IsNullOrEmpty(p.flagURL)))
            {
                if (!FlagSystem.Singleton.FlagExists(part.flagURL))
                {
                    LunaLog.Log($"[LMP]: Flag '{part.flagURL}' doesn't exist, setting to default!");
                    part.flagURL = "Squad/Flags/default";
                }
            }
            return(true);
        }
Esempio n. 12
0
 /// <summary>
 /// Routine that checks our time against the server time and adjust it if needed.
 /// </summary>
 /// <returns></returns>
 private void SyncTime()
 {
     if (Enabled && Synced && !CurrentlyWarping && CanSyncTime() && !SystemsContainer.Get <WarpSystem>().WaitingSubspaceIdFromServer)
     {
         var targetTime   = SystemsContainer.Get <WarpSystem>().GetCurrentSubspaceTime();
         var currentError = TimeSpan.FromSeconds(GetCurrentError()).TotalMilliseconds;
         if (targetTime != 0 && Math.Abs(currentError) > MaxClockMsError)
         {
             if (Math.Abs(currentError) > MaxClockSkew)
             {
                 LunaLog.LogWarning($"[LMP] Adjusted time from: {Planetarium.GetUniversalTime()} to: {targetTime} due to error:{currentError}");
                 //TODO: This causes the throttle to reset when called.  This happens due to vessel unpacking resetting the throttle controls.
                 //TODO: Try to get Squad to change their code.
                 ClockHandler.StepClock(targetTime);
             }
             else
             {
                 SkewClock(currentError);
             }
         }
     }
 }
Esempio n. 13
0
        /// <summary>
        /// Routine that checks our time against the server time and adjust it if needed.
        /// We only adjust the GAME time. And we will only do if the time error is between <see cref="MinPhisicsClockMsError"/> and <see cref="MaxPhisicsClockMsError"/>
        /// We cannot do it with more than <see cref="MaxPhisicsClockMsError"/> as then we would need a lot of time to catch up with the time error.
        /// For greater errors we just fix the time with the StepClock
        /// </summary>
        private void SyncTimeScale()
        {
            if (Enabled && !CurrentlyWarping && CanSyncTime && !WarpSystem.Singleton.WaitingSubspaceIdFromServer)
            {
                var targetTime   = WarpSystem.Singleton.CurrentSubspaceTime;
                var currentError = TimeUtil.SecondsToMilliseconds(CurrentErrorSec);

                if (Math.Abs(currentError) < MinPhisicsClockMsError)
                {
                    Time.timeScale = 1;
                }
                if (Math.Abs(currentError) > MinPhisicsClockMsError && Math.Abs(currentError) < MaxPhisicsClockMsError)
                {
                    //Time error is not so big so we can fix it adjusting the physics time
                    SkewClock();
                }
                else if (Math.Abs(currentError) > MaxPhisicsClockMsError)
                {
                    LunaLog.LogWarning($"[LMP] Adjusted time from: {UniversalTime} to: {targetTime} due to error: {currentError}");
                    ClockHandler.StepClock(targetTime);
                }
            }
        }
Esempio n. 14
0
        /// <summary>
        /// Load all the received vessels from the server into the game
        /// </summary>
        public void LoadVesselsIntoGame()
        {
            LunaLog.Log("[LMP]: Loading vessels in subspace 0 into game");
            var numberOfLoads = 0;

            foreach (var vessel in System.AllPlayerVessels)
            {
                if (vessel.Value.ProtoVessel != null && vessel.Value.ProtoVessel.vesselID == vessel.Key)
                {
                    RegisterServerAsteriodIfVesselIsAsteroid(vessel.Value.ProtoVessel);
                    HighLogic.CurrentGame.flightState.protoVessels.Add(vessel.Value.ProtoVessel);
                    numberOfLoads++;
                }
                else
                {
                    LunaLog.LogWarning($"[LMP]: Protovessel {vessel.Key} is DAMAGED!. Skipping load.");
                    SystemsContainer.Get <ChatSystem>().PmMessageServer($"WARNING: Protovessel {vessel.Key} is DAMAGED!. Skipping load.");
                }
                vessel.Value.Loaded = true;
            }

            LunaLog.Log($"[LMP]: {numberOfLoads} Vessels loaded into game");
        }
Esempio n. 15
0
 public void OnVesselCreate(Vessel vessel)
 {
     //Kerbals are put in the vessel *after* OnVesselCreate. Thanks squad!.
     if (System.VesselToKerbal.ContainsKey(vessel.id))
     {
         OnVesselDestroyed(vessel);
     }
     if (vessel.GetCrewCount() > 0)
     {
         System.VesselToKerbal.Add(vessel.id, new List <string>());
         foreach (var protoCrewMember in vessel.GetVesselCrew())
         {
             System.VesselToKerbal[vessel.id].Add(protoCrewMember.name);
             if (System.KerbalToVessel.ContainsKey(protoCrewMember.name) &&
                 System.KerbalToVessel[protoCrewMember.name] != vessel.id)
             {
                 LunaLog.LogWarning($"[LMP]: Warning, kerbal double take on {vessel.id} ({vessel.name})");
             }
             System.KerbalToVessel[protoCrewMember.name] = vessel.id;
             LunaLog.Log($"[LMP]: OVC {protoCrewMember.name} belongs to {vessel.id}");
         }
     }
 }
Esempio n. 16
0
        private static bool CheckDetouring(MethodInfo source, MethodInfo destination)
        {
            var sourceStr = source.DeclaringType?.FullName + "." + source.Name + " @ 0x" +
                            source.MethodHandle.GetFunctionPointer().ToString("X" + IntPtr.Size * 2);

            var destStr = destination.DeclaringType?.FullName + "." + destination.Name + " @ 0x" +
                          destination.MethodHandle.GetFunctionPointer().ToString("X" + IntPtr.Size * 2);

            if (Detours.ContainsKey(sourceStr))
            {
                //Othwerise we are just detouring to the same method...
                if (destStr != Detours[sourceStr])
                {
                    LunaLog.LogWarning($"[Detour] Source method('{sourceStr}') was previously detoured to '{Detours[sourceStr]}'");
                }

                return(false);
            }

            Detours.Add(sourceStr, destStr);
            LunaLog.Log($"[Detour] Detouring '{sourceStr}' to '{destStr}'");

            return(true);
        }
Esempio n. 17
0
        public void HandleMessage(IMessageData messageData)
        {
            var msgData = messageData as GroupBaseMsgData;

            if (msgData == null)
            {
                return;
            }

            switch (msgData.GroupMessageType)
            {
            case GroupMessageType.Add:
            {
                var data = (GroupAddMsgData)messageData;
                System.RegisterGroup(data.GroupName, data.Owner);
            }
            break;

            case GroupMessageType.Remove:
            {
                var data = (GroupRemoveMsgData)messageData;
                System.DeregisterGroup(data.GroupName);
            }
            break;

            case GroupMessageType.Invite:
            {
                var data = (GroupInviteMsgData)messageData;
                if (data.AddressedTo == SettingsSystem.CurrentSettings.PlayerName)
                {
                    System.Invite(data.GroupName);
                }
            }
            break;

            case GroupMessageType.Accept:
            {
                var data = (GroupAcceptMsgData)messageData;
                System.AddPlayerToGroup(data.GroupName, data.AddressedTo);
            }
            break;

            case GroupMessageType.Kick:
            {
                var data = (GroupKickMsgData)messageData;
                System.KickPlayerFromGroup(data.GroupName, data.Player);
            }
            break;

            case GroupMessageType.ListResponse:
            {
                var data = (GroupListResponseMsgData)messageData;

                if (data.Groups.Length != data.Owners.Length)
                {
                    LunaLog.LogWarning("Malformed message of type GroupSystem.ListResponse");
                }
                else
                {
                    for (var i = 0; i < data.Groups.Length; i++)
                    {
                        if (!System.GroupExists(data.Groups[i]))
                        {
                            System.RegisterGroup(data.Groups[i], data.Owners[i]);
                        }
                    }
                }

                if (!System.IsSynced)
                {
                    if (data.Groups.Length == 0)
                    {
                        System.IsSynced         = true;
                        MainSystem.NetworkState = ClientState.GroupsSynced;
                    }
                    System.NumGroups       = data.Groups.Length;
                    System.NumGroupsSynced = 0;
                    foreach (var groupName in data.Groups)
                    {
                        System.MessageSender.SendMessage(new GroupUpdateRequestMsgData {
                                GroupName = groupName
                            });
                    }
                }
            }
            break;

            case GroupMessageType.UpdateResponse:
            {
                var data = (GroupUpdateResponseMsgData)messageData;
                if (System.NumGroupsSynced < System.NumGroups)
                {
                    System.NumGroupsSynced += 1;
                }

                if (System.NumGroupsSynced == System.NumGroups)
                {
                    MainSystem.NetworkState = ClientState.GroupsSynced;
                    System.IsSynced         = true;
                }

                if (!System.GroupExists(data.Name))
                {
                    System.RegisterGroup(data.Name, data.Owner);
                }
                foreach (string member in data.Members)
                {
                    System.AddPlayerToGroup(data.Name, member);
                }
            }
            break;
            }
        }
Esempio n. 18
0
        public void Start()
        {
            // Checkers are identified by the type Name and version field Name.
            var fields =
                GetAllTypes()
                .Where(t => t.Name == "CompatibilityChecker")
                .Select(t => t.GetField("_version", BindingFlags.Static | BindingFlags.NonPublic))
                .Where(f => f != null)
                .Where(f => f.FieldType == typeof(int))
                .ToArray();

            // Let the latest version of the checker execute.
            if (_version != fields.Max(f => (int)f.GetValue(null)))
            {
                return;
            }

            LunaLog.Log($"[CompatibilityChecker] Running checker version {_version} from '{Assembly.GetExecutingAssembly().GetName().Name}'");

            // Other checkers will see this version and not run.
            // This accomplishes the same as an explicit "ran" flag with fewer moving parts.
            _version = int.MaxValue;

            // A mod is incompatible if its compatibility checker has an IsCompatible method which returns false.
            var incompatible =
                fields
                .Select(f => f.DeclaringType.GetMethod("IsCompatible", Type.EmptyTypes))
                .Where(m => m.IsStatic)
                .Where(m => m.ReturnType == typeof(bool))
                .Where(m =>
            {
                try
                {
                    return(!(bool)m.Invoke(null, new object[0]));
                }
                catch (Exception e)
                {
                    // If a mod throws an exception from IsCompatible, it's not compatible.
                    LunaLog.LogWarning($"[CompatibilityChecker] Exception while invoking IsCompatible() from '{m.DeclaringType?.Assembly.GetName().Name}':\n\n{e}");
                    return(true);
                }
            })
                .Select(m => m.DeclaringType?.Assembly.GetName().Name)
                .ToArray();

            // A mod is incompatible with Unity if its compatibility checker has an IsUnityCompatible method which returns false.
            var incompatibleUnity =
                fields
                .Select(f => f.DeclaringType.GetMethod("IsUnityCompatible", Type.EmptyTypes))
                .Where(m => m != null)     // Mods without IsUnityCompatible() are assumed to be compatible.
                .Where(m => m.IsStatic)
                .Where(m => m.ReturnType == typeof(bool))
                .Where(m =>
            {
                try
                {
                    return(!(bool)m.Invoke(null, new object[0]));
                }
                catch (Exception e)
                {
                    // If a mod throws an exception from IsUnityCompatible, it's not compatible.
                    LunaLog.LogWarning($"[CompatibilityChecker] Exception while invoking IsUnityCompatible() from '{m.DeclaringType?.Assembly.GetName().Name}':\n\n{e}");
                    return(true);
                }
            })
                .Select(m => m.DeclaringType?.Assembly.GetName().Name)
                .ToArray();

            Array.Sort(incompatible);
            Array.Sort(incompatibleUnity);

            var message =
                "Some installed mods may be incompatible with this version of Kerbal Space Program. Features may be broken or disabled. Please check for updates to the listed mods.";

            if (incompatible.Length > 0)
            {
                LunaLog.LogWarning($"[CompatibilityChecker] Incompatible mods detected: {string.Join(", ", incompatible)}");
                message += $"\n\nThese mods are incompatible with KSP {Versioning.version_major}.{Versioning.version_minor}.{Versioning.Revision}:\n\n";
                message += string.Join("\n", incompatible);
            }

            if (incompatibleUnity.Length > 0)
            {
                LunaLog.LogWarning("[CompatibilityChecker] Incompatible mods (Unity) detected: " +
                                   string.Join(", ", incompatibleUnity));
                message += $"\n\nThese mods are incompatible with Unity {Application.unityVersion}:\n\n";
                message += string.Join("\n", incompatibleUnity);
            }

            if ((incompatible.Length > 0) || (incompatibleUnity.Length > 0))
            {
                PopupDialog.SpawnPopupDialog(new MultiOptionDialog("CompatibilityChecker", message, "Incompatible Mods Detected", HighLogic.UISkin), true, HighLogic.UISkin);
            }
        }