diff --git a/TLM/TLM/LoadingExtension.cs b/TLM/TLM/LoadingExtension.cs index 0b9fed34a..1e75b36ec 100644 --- a/TLM/TLM/LoadingExtension.cs +++ b/TLM/TLM/LoadingExtension.cs @@ -1,60 +1,30 @@ namespace TrafficManager { - using ColossalFramework.UI; using ColossalFramework; + using ColossalFramework.UI; using CSUtil.Commons; - using Harmony; using ICities; using JetBrains.Annotations; - using UnityEngine; - using Object = UnityEngine.Object; + using System; using System.Collections.Generic; using System.Reflection; - using System; using TrafficManager.API.Manager; using TrafficManager.Custom.PathFinding; using TrafficManager.Manager.Impl; - using TrafficManager.RedirectionFramework; using TrafficManager.State; using TrafficManager.UI; - using static TrafficManager.Util.Shortcuts; using UnityEngine; + using Object = UnityEngine.Object; [UsedImplicitly] public class LoadingExtension : LoadingExtensionBase { - private const string HARMONY_ID = "de.viathinksoft.tmpe"; internal static LoadingExtension Instance = null; FastList simManager => typeof(SimulationManager).GetField("m_managers", BindingFlags.Static | BindingFlags.NonPublic) ?.GetValue(null) as FastList; - public class Detour { - public MethodInfo OriginalMethod; - public MethodInfo CustomMethod; - public RedirectCallsState Redirect; - - public Detour(MethodInfo originalMethod, MethodInfo customMethod) { - OriginalMethod = originalMethod; - CustomMethod = customMethod; - Redirect = RedirectionHelper.RedirectCalls(originalMethod, customMethod); - } - } - - public class ManualHarmonyPatch { - public MethodInfo method; - public HarmonyMethod prefix; - public HarmonyMethod transpiler = null; - public HarmonyMethod postfix = null; - } - public static CustomPathManager CustomPathManager { get; set; } - public static bool DetourInited { get; set; } - - public static List Detours { get; set; } - - public static HarmonyInstance HarmonyInst { get; private set; } - /// /// Contains loaded languages and lookup functions for text translations /// @@ -70,70 +40,6 @@ public static bool IsPathManagerReplaced { get; private set; } - /// - /// Manually deployed Harmony patches - /// - public static IList ManualHarmonyPatches { get; } = - new List { - new ManualHarmonyPatch() { - method = typeof(CommonBuildingAI).GetMethod( - "SimulationStep", - BindingFlags.Public | BindingFlags.Instance, - null, - new[] { typeof(ushort), typeof(Building).MakeByRefType() }, - null), - prefix = new HarmonyMethod( - typeof(Patch._CommonBuildingAI.SimulationStepPatch).GetMethod("Prefix")) - }, - new ManualHarmonyPatch() { - method = typeof(RoadBaseAI).GetMethod( - "TrafficLightSimulationStep", - BindingFlags.Public | BindingFlags.Static, - null, - new[] { typeof(ushort), typeof(NetNode).MakeByRefType() }, - null), - prefix = new HarmonyMethod( - typeof(Patch._RoadBaseAI.TrafficLightSimulationStepPatch).GetMethod( - "Prefix")) - }, - new ManualHarmonyPatch() { - method = typeof(TrainTrackBaseAI).GetMethod( - "LevelCrossingSimulationStep", - BindingFlags.Public | BindingFlags.Static, - null, - new[] { typeof(ushort), typeof(NetNode).MakeByRefType() }, - null), - prefix = new HarmonyMethod( - typeof(Patch._TrainTrackBase.LevelCrossingSimulationStepPatch).GetMethod( - "Prefix")) - }, - new ManualHarmonyPatch() { - method = typeof(RoadBaseAI).GetMethod( - "SimulationStep", - BindingFlags.Public | BindingFlags.Instance, - null, - new[] { typeof(ushort), typeof(NetSegment).MakeByRefType() }, - null), - prefix = new HarmonyMethod( - typeof(Patch._RoadBaseAI.SegmentSimulationStepPatch).GetMethod("Prefix")) - } - }; - - /// - /// Method redirection states for Harmony-driven patches - /// - public static IDictionary HarmonyMethodStates { - get; - } = new Dictionary(); - - /// - /// Method redirection states for attribute-driven detours - /// - public static IDictionary DetouredMethodStates { - get; - private set; - } = new Dictionary(); - static LoadingExtension() { TranslationDatabase.LoadAllTranslations(); } @@ -141,140 +47,18 @@ static LoadingExtension() { public LoadingExtension() { } - public void RevertDetours() { - if (!DetourInited) { - return; - } - - Log.Info("Reverting manual detours"); - Detours.Reverse(); - foreach (Detour d in Detours) { - RedirectionHelper.RevertRedirect(d.OriginalMethod, d.Redirect); - } - - Detours.Clear(); - - Log.Info("Reverting attribute-driven detours"); - AssemblyRedirector.Revert(); - - Log.Info("Reverting Harmony detours"); - foreach (MethodBase m in HarmonyMethodStates.Keys) { - HarmonyInst.Unpatch(m, HarmonyPatchType.All, HARMONY_ID); - } - - DetourInited = false; - Log.Info("Reverting detours finished."); - } - - private void InitDetours() { - // TODO realize detouring with annotations - if (DetourInited) { - return; - } - - Log.Info("Init detours"); - bool detourFailed = false; - - try { - Log.Info("Deploying Harmony patches"); -#if DEBUG - HarmonyInstance.DEBUG = true; -#endif - Assembly assembly = Assembly.GetExecutingAssembly(); - - HarmonyMethodStates.Clear(); - - // Harmony attribute-driven patching - Log.Info($"Performing Harmony attribute-driven patching"); - HarmonyInst = HarmonyInstance.Create(HARMONY_ID); - HarmonyInst.PatchAll(assembly); - - foreach (Type type in assembly.GetTypes()) { - object[] attributes = type.GetCustomAttributes(typeof(HarmonyPatch), true); - if (attributes.Length <= 0) { - continue; - } - - foreach (object attr in attributes) { - HarmonyPatch harmonyPatchAttr = (HarmonyPatch)attr; - MethodBase info = HarmonyUtil.GetOriginalMethod(harmonyPatchAttr.info); - IntPtr ptr = info.MethodHandle.GetFunctionPointer(); - RedirectCallsState state = RedirectionHelper.GetState(ptr); - HarmonyMethodStates[info] = state; - } - } - - // Harmony manual patching - Log.Info($"Performing Harmony manual patching"); - - foreach (ManualHarmonyPatch manualPatch in ManualHarmonyPatches) { - Log.InfoFormat( - "Manually patching method {0}.{1}. Prefix: {2}, Postfix: {3}, Transpiler: {4}", - manualPatch.method.DeclaringType.FullName, - manualPatch.method.Name, manualPatch.prefix?.method, - manualPatch.postfix?.method, manualPatch.transpiler?.method); - - HarmonyInst.Patch( - manualPatch.method, - manualPatch.prefix, - manualPatch.postfix, - manualPatch.transpiler); - - IntPtr ptr = manualPatch.method.MethodHandle.GetFunctionPointer(); - RedirectCallsState state = RedirectionHelper.GetState(ptr); - HarmonyMethodStates[manualPatch.method] = state; - } - } catch (Exception e) { - Log.Error("Could not deploy Harmony patches"); - Log.Info(e.ToString()); - Log.Info(e.StackTrace); - detourFailed = true; - } - - try { - Log.Info("Deploying attribute-driven detours"); - DetouredMethodStates = AssemblyRedirector.Deploy(); - } catch (Exception e) { - Log.Error("Could not deploy attribute-driven detours"); - Log.Info(e.ToString()); - Log.Info(e.StackTrace); - detourFailed = true; - } - - if (detourFailed) { - Log.Info("Detours failed"); - Singleton.instance.m_ThreadingWrapper.QueueMainThread( - () => { - UIView.library - .ShowModal("ExceptionPanel") - .SetMessage( - "TM:PE failed to load", - "Traffic Manager: President Edition failed to load. You can " + - "continue playing but it's NOT recommended. Traffic Manager will " + - "not work as expected.", - true); - }); - } else { - Log.Info("Detours successful"); - } - - DetourInited = true; - } - public override void OnCreated(ILoading loading) { Log._Debug("LoadingExtension.OnCreated() called"); // SelfDestruct.DestructOldInstances(this); base.OnCreated(loading); - if(IsGameLoaded) { + if (IsGameLoaded) { // When another mod is detected, OnCreated is called again for god - or CS team - knows what reason! Log._Debug("Hot reload of another mod detected. Skipping LoadingExtension.OnCreated() ..."); return; } - Detours = new List(); RegisteredManagers = new List(); - DetourInited = false; CustomPathManager = new CustomPathManager(); RegisterCustomManagers(); @@ -340,8 +124,7 @@ public override void OnLevelUnloading() { var gameObject = UIView.GetAView().gameObject; - void Destroy() where T : MonoBehaviour - { + void Destroy() where T : MonoBehaviour { Object obj = (Object)gameObject.GetComponent(); if (obj != null) { Object.Destroy(obj); @@ -376,7 +159,7 @@ void Destroy() where T : MonoBehaviour // ignored - prevents collision with other mods } - RevertDetours(); + Patcher.Instance?.Uninstall(); IsGameLoaded = false; } @@ -393,81 +176,80 @@ public override void OnLevelLoaded(LoadMode mode) { case SimulationManager.UpdateMode.NewGameFromMap: case SimulationManager.UpdateMode.NewGameFromScenario: case SimulationManager.UpdateMode.LoadGame: { - if (BuildConfig.applicationVersion != BuildConfig.VersionToString( - TrafficManagerMod.GAME_VERSION, - false)) - { - string[] majorVersionElms = BuildConfig.applicationVersion.Split('-'); - string[] versionElms = majorVersionElms[0].Split('.'); - uint versionA = Convert.ToUInt32(versionElms[0]); - uint versionB = Convert.ToUInt32(versionElms[1]); - uint versionC = Convert.ToUInt32(versionElms[2]); - - Log.Info($"Detected game version v{BuildConfig.applicationVersion}"); - - bool isModTooOld = TrafficManagerMod.GAME_VERSION_A < versionA || - (TrafficManagerMod.GAME_VERSION_A == versionA && - TrafficManagerMod.GAME_VERSION_B < versionB); + if (BuildConfig.applicationVersion != BuildConfig.VersionToString( + TrafficManagerMod.GAME_VERSION, + false)) { + string[] majorVersionElms = BuildConfig.applicationVersion.Split('-'); + string[] versionElms = majorVersionElms[0].Split('.'); + uint versionA = Convert.ToUInt32(versionElms[0]); + uint versionB = Convert.ToUInt32(versionElms[1]); + uint versionC = Convert.ToUInt32(versionElms[2]); + + Log.Info($"Detected game version v{BuildConfig.applicationVersion}"); + + bool isModTooOld = TrafficManagerMod.GAME_VERSION_A < versionA || + (TrafficManagerMod.GAME_VERSION_A == versionA && + TrafficManagerMod.GAME_VERSION_B < versionB); // || (TrafficManagerMod.GameVersionA == versionA // && TrafficManagerMod.GameVersionB == versionB // && TrafficManagerMod.GameVersionC < versionC); - bool isModNewer = TrafficManagerMod.GAME_VERSION_A < versionA || - (TrafficManagerMod.GAME_VERSION_A == versionA && - TrafficManagerMod.GAME_VERSION_B > versionB); + bool isModNewer = TrafficManagerMod.GAME_VERSION_A < versionA || + (TrafficManagerMod.GAME_VERSION_A == versionA && + TrafficManagerMod.GAME_VERSION_B > versionB); // || (TrafficManagerMod.GameVersionA == versionA // && TrafficManagerMod.GameVersionB == versionB // && TrafficManagerMod.GameVersionC > versionC); - if (isModTooOld) { - string msg = string.Format( - "Traffic Manager: President Edition detected that you are running " + - "a newer game version ({0}) than TM:PE has been built for ({1}). " + - "Please be aware that TM:PE has not been updated for the newest game " + - "version yet and thus it is very likely it will not work as expected.", - BuildConfig.applicationVersion, - BuildConfig.VersionToString(TrafficManagerMod.GAME_VERSION, false)); - - Log.Error(msg); - Singleton.instance.m_ThreadingWrapper.QueueMainThread( - () => { - UIView.library - .ShowModal("ExceptionPanel") - .SetMessage( - "TM:PE has not been updated yet", - msg, - false); - }); - } else if (isModNewer) { - string msg = string.Format( - "Traffic Manager: President Edition has been built for game version {0}. " + - "You are running game version {1}. Some features of TM:PE will not " + - "work with older game versions. Please let Steam update your game.", - BuildConfig.VersionToString(TrafficManagerMod.GAME_VERSION, false), - BuildConfig.applicationVersion); - - Log.Error(msg); - Singleton - .instance.m_ThreadingWrapper.QueueMainThread( - () => { - UIView.library - .ShowModal("ExceptionPanel") - .SetMessage( - "Your game should be updated", - msg, - false); - }); + if (isModTooOld) { + string msg = string.Format( + "Traffic Manager: President Edition detected that you are running " + + "a newer game version ({0}) than TM:PE has been built for ({1}). " + + "Please be aware that TM:PE has not been updated for the newest game " + + "version yet and thus it is very likely it will not work as expected.", + BuildConfig.applicationVersion, + BuildConfig.VersionToString(TrafficManagerMod.GAME_VERSION, false)); + + Log.Error(msg); + Singleton.instance.m_ThreadingWrapper.QueueMainThread( + () => { + UIView.library + .ShowModal("ExceptionPanel") + .SetMessage( + "TM:PE has not been updated yet", + msg, + false); + }); + } else if (isModNewer) { + string msg = string.Format( + "Traffic Manager: President Edition has been built for game version {0}. " + + "You are running game version {1}. Some features of TM:PE will not " + + "work with older game versions. Please let Steam update your game.", + BuildConfig.VersionToString(TrafficManagerMod.GAME_VERSION, false), + BuildConfig.applicationVersion); + + Log.Error(msg); + Singleton + .instance.m_ThreadingWrapper.QueueMainThread( + () => { + UIView.library + .ShowModal("ExceptionPanel") + .SetMessage( + "Your game should be updated", + msg, + false); + }); + } } - } - IsGameLoaded = true; - break; - } + IsGameLoaded = true; + break; + } default: { - Log.Info($"OnLevelLoaded: Unsupported game mode {mode}"); - return; - } + Log.Info($"OnLevelLoaded: Unsupported game mode {mode}"); + return; + } } //it will replace stock PathManager or already Replaced before HotReload @@ -520,7 +302,8 @@ public override void OnLevelLoaded(LoadMode mode) { Log._Debug("Should be custom: " + Singleton.instance.GetType()); IsPathManagerReplaced = true; - } catch (Exception ex) { + } + catch (Exception ex) { string error = "Traffic Manager: President Edition failed to load. You can continue " + "playing but it's NOT recommended. Traffic Manager will not work as expected."; @@ -556,7 +339,7 @@ public override void OnLevelLoaded(LoadMode mode) { UIView.GetAView().gameObject.AddComponent(); - InitDetours(); + Patcher.Create().Install(); // Log.Info("Fixing non-created nodes with problems..."); // FixNonCreatedNodeProblems(); diff --git a/TLM/TLM/Manager/Impl/OptionsManager.cs b/TLM/TLM/Manager/Impl/OptionsManager.cs index 7de7ad7f7..76bbbbb1a 100644 --- a/TLM/TLM/Manager/Impl/OptionsManager.cs +++ b/TLM/TLM/Manager/Impl/OptionsManager.cs @@ -1,4 +1,4 @@ -namespace TrafficManager.Manager.Impl { +namespace TrafficManager.Manager.Impl { using CSUtil.Commons; using System; using TrafficManager.API.Manager; diff --git a/TLM/TLM/Manager/Impl/UtilityManager.cs b/TLM/TLM/Manager/Impl/UtilityManager.cs index 6bb7164f0..bb5102e62 100644 --- a/TLM/TLM/Manager/Impl/UtilityManager.cs +++ b/TLM/TLM/Manager/Impl/UtilityManager.cs @@ -1,4 +1,4 @@ -namespace TrafficManager.Manager.Impl { +namespace TrafficManager.Manager.Impl { using ColossalFramework; using CSUtil.Commons; using System.Threading; diff --git a/TLM/TLM/Patch/_CitizenManager/ReleaseCitizenInstancePatch.cs b/TLM/TLM/Patch/_CitizenManager/ReleaseCitizenInstancePatch.cs index 2843b7895..75e9d635e 100644 --- a/TLM/TLM/Patch/_CitizenManager/ReleaseCitizenInstancePatch.cs +++ b/TLM/TLM/Patch/_CitizenManager/ReleaseCitizenInstancePatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._CitizenManager { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; [HarmonyPatch(typeof(CitizenManager), "ReleaseCitizenInstance")] diff --git a/TLM/TLM/Patch/_CitizenManager/ReleaseCitizenPatch.cs b/TLM/TLM/Patch/_CitizenManager/ReleaseCitizenPatch.cs index 28f82b118..49870d0f3 100644 --- a/TLM/TLM/Patch/_CitizenManager/ReleaseCitizenPatch.cs +++ b/TLM/TLM/Patch/_CitizenManager/ReleaseCitizenPatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._CitizenManager { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; [HarmonyPatch(typeof(CitizenManager), "ReleaseCitizen")] diff --git a/TLM/TLM/Patch/_CommonBuildingAI/SimulationStepPatch.cs b/TLM/TLM/Patch/_CommonBuildingAI/SimulationStepPatch.cs index 076eefda1..1432bbbc5 100644 --- a/TLM/TLM/Patch/_CommonBuildingAI/SimulationStepPatch.cs +++ b/TLM/TLM/Patch/_CommonBuildingAI/SimulationStepPatch.cs @@ -1,13 +1,28 @@ -namespace TrafficManager.Patch._CommonBuildingAI { +namespace TrafficManager.Patch._CommonBuildingAI { + using HarmonyLib; using JetBrains.Annotations; + using System.Reflection; + + [HarmonyPatch] + [UsedImplicitly] + public class SimulationStepPatch + { + [UsedImplicitly] + public static MethodBase TargetMethod() + { + return HarmonyLib.AccessTools.DeclaredMethod( + typeof(CommonBuildingAI), + "SimulationStep", + new[] { typeof(ushort), typeof(Building).MakeByRefType() }) ?? + throw new System.Exception("_CommonBuildingAI.SimulationStepPatch failed to find TargetMethod"); + } - // [Harmony] Manually patched because struct references are used - public class SimulationStepPatch { /// /// Decreases parking space and public transport demand before each simulation step if the Parking AI is active. /// [UsedImplicitly] - public static void Prefix(ushort buildingID, ref Building data) { + public static void Prefix(ushort buildingID, ref Building data) + { Constants.ManagerFactory.ExtBuildingManager.OnBeforeSimulationStep(buildingID, ref data); } } diff --git a/TLM/TLM/Patch/_DefaultTool/OnToolGUIPatch.cs b/TLM/TLM/Patch/_DefaultTool/OnToolGUIPatch.cs index 14b605d85..e79a95421 100644 --- a/TLM/TLM/Patch/_DefaultTool/OnToolGUIPatch.cs +++ b/TLM/TLM/Patch/_DefaultTool/OnToolGUIPatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._DefaultTool { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.UI; using UnityEngine; diff --git a/TLM/TLM/Patch/_DefaultTool/RenderOverlayPatch.cs b/TLM/TLM/Patch/_DefaultTool/RenderOverlayPatch.cs index 375d07340..2674b564b 100644 --- a/TLM/TLM/Patch/_DefaultTool/RenderOverlayPatch.cs +++ b/TLM/TLM/Patch/_DefaultTool/RenderOverlayPatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._DefaultTool { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.UI; diff --git a/TLM/TLM/Patch/_HumanAI/ArriveAtDestinationPatch.cs b/TLM/TLM/Patch/_HumanAI/ArriveAtDestinationPatch.cs index 2da447f8f..ad879d80e 100644 --- a/TLM/TLM/Patch/_HumanAI/ArriveAtDestinationPatch.cs +++ b/TLM/TLM/Patch/_HumanAI/ArriveAtDestinationPatch.cs @@ -1,6 +1,6 @@ namespace TrafficManager.Patch._HumanAI { using ColossalFramework; - using Harmony; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.State; diff --git a/TLM/TLM/Patch/_InfoManager/SetModePatch.cs b/TLM/TLM/Patch/_InfoManager/SetModePatch.cs index 70a22bd80..5f07b25ba 100644 --- a/TLM/TLM/Patch/_InfoManager/SetModePatch.cs +++ b/TLM/TLM/Patch/_InfoManager/SetModePatch.cs @@ -1,7 +1,7 @@ namespace TrafficManager.Patch._InfoManager { using ColossalFramework; - using Harmony; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.Util; using TrafficManager.UI; diff --git a/TLM/TLM/Patch/_NetManager/FinalizeSegmentPatch.cs b/TLM/TLM/Patch/_NetManager/FinalizeSegmentPatch.cs index c949ecc66..aa12f999e 100644 --- a/TLM/TLM/Patch/_NetManager/FinalizeSegmentPatch.cs +++ b/TLM/TLM/Patch/_NetManager/FinalizeSegmentPatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._NetManager { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; [HarmonyPatch(typeof(NetManager), "FinalizeSegment")] diff --git a/TLM/TLM/Patch/_NetManager/UpdateSegmentPatch.cs b/TLM/TLM/Patch/_NetManager/UpdateSegmentPatch.cs index c82830cf0..f9efcb16b 100644 --- a/TLM/TLM/Patch/_NetManager/UpdateSegmentPatch.cs +++ b/TLM/TLM/Patch/_NetManager/UpdateSegmentPatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._NetManager { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; [HarmonyPatch( diff --git a/TLM/TLM/Patch/_NetSegment/CalculateSegmentPatch.cs b/TLM/TLM/Patch/_NetSegment/CalculateSegmentPatch.cs index 5190c3bc6..6dbe45d1a 100644 --- a/TLM/TLM/Patch/_NetSegment/CalculateSegmentPatch.cs +++ b/TLM/TLM/Patch/_NetSegment/CalculateSegmentPatch.cs @@ -1,6 +1,5 @@ namespace TrafficManager.Patch._NetSegment { - using Harmony; - using JetBrains; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.Manager.Impl; diff --git a/TLM/TLM/Patch/_RoadBaseAI/GetTrafficLightNodeStatePatch.cs b/TLM/TLM/Patch/_RoadBaseAI/GetTrafficLightNodeStatePatch.cs index b0d603a40..d6492030a 100644 --- a/TLM/TLM/Patch/_RoadBaseAI/GetTrafficLightNodeStatePatch.cs +++ b/TLM/TLM/Patch/_RoadBaseAI/GetTrafficLightNodeStatePatch.cs @@ -1,6 +1,6 @@ namespace TrafficManager.Patch._RoadBaseAI { using ColossalFramework; - using Harmony; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.State; using UnityEngine; diff --git a/TLM/TLM/Patch/_RoadBaseAI/SegmentSimulationStepPatch.cs b/TLM/TLM/Patch/_RoadBaseAI/SegmentSimulationStepPatch.cs index aebb6158d..4b7c5700c 100644 --- a/TLM/TLM/Patch/_RoadBaseAI/SegmentSimulationStepPatch.cs +++ b/TLM/TLM/Patch/_RoadBaseAI/SegmentSimulationStepPatch.cs @@ -1,13 +1,24 @@ -namespace TrafficManager.Patch._RoadBaseAI { +namespace TrafficManager.Patch._RoadBaseAI { using API.Traffic.Enums; using ColossalFramework; + using HarmonyLib; using JetBrains.Annotations; + using System.Reflection; using TrafficManager.State; - using CSUtil.Commons.Benchmark; - - // [Harmony] Manually patched because struct references are used - public class SegmentSimulationStepPatch { + [HarmonyPatch] + [UsedImplicitly] + public class SegmentSimulationStepPatch + { + [UsedImplicitly] + public static MethodBase TargetMethod() + { + return HarmonyLib.AccessTools.DeclaredMethod( + typeof(RoadBaseAI), + "SimulationStep", + new[] { typeof(ushort), typeof(NetSegment).MakeByRefType() }) ?? + throw new System.Exception("SegmentSimulationStepPatch failed to find TargetMethod"); + } private static ushort lastSimulatedSegmentId = 0; private static byte trafficMeasurementMod = 0; diff --git a/TLM/TLM/Patch/_RoadBaseAI/SetTrafficLightStatePatch.cs b/TLM/TLM/Patch/_RoadBaseAI/SetTrafficLightStatePatch.cs index 71e2de631..e879523aa 100644 --- a/TLM/TLM/Patch/_RoadBaseAI/SetTrafficLightStatePatch.cs +++ b/TLM/TLM/Patch/_RoadBaseAI/SetTrafficLightStatePatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._RoadBaseAI { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.State; using static RoadBaseAI; diff --git a/TLM/TLM/Patch/_RoadBaseAI/TrafficLightSimulationStepPatch.cs b/TLM/TLM/Patch/_RoadBaseAI/TrafficLightSimulationStepPatch.cs index 11d4ef6d7..29eff0b8b 100644 --- a/TLM/TLM/Patch/_RoadBaseAI/TrafficLightSimulationStepPatch.cs +++ b/TLM/TLM/Patch/_RoadBaseAI/TrafficLightSimulationStepPatch.cs @@ -1,8 +1,9 @@ -namespace TrafficManager.Patch._RoadBaseAI { +namespace TrafficManager.Patch._RoadBaseAI { + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.State; - // [Harmony] Manually patched because struct references are used + [HarmonyPatch(typeof(TrainTrackBaseAI), nameof(TrainTrackBaseAI.LevelCrossingSimulationStep))] public class TrafficLightSimulationStepPatch { /// /// Decides whether the stock simulation step for traffic lights should run. diff --git a/TLM/TLM/Patch/_RoadBaseAI/UpdateLanesPatch.cs b/TLM/TLM/Patch/_RoadBaseAI/UpdateLanesPatch.cs index 790ea3a4f..4e7e58a03 100644 --- a/TLM/TLM/Patch/_RoadBaseAI/UpdateLanesPatch.cs +++ b/TLM/TLM/Patch/_RoadBaseAI/UpdateLanesPatch.cs @@ -1,6 +1,6 @@ namespace TrafficManager.Patch._RoadBaseAI { using ColossalFramework; - using Harmony; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.State; diff --git a/TLM/TLM/Patch/_RoadBaseAI/UpdateNodePatch.cs b/TLM/TLM/Patch/_RoadBaseAI/UpdateNodePatch.cs index ad00f6a2b..aaf5ec254 100644 --- a/TLM/TLM/Patch/_RoadBaseAI/UpdateNodePatch.cs +++ b/TLM/TLM/Patch/_RoadBaseAI/UpdateNodePatch.cs @@ -1,6 +1,6 @@ namespace TrafficManager.Patch._RoadBaseAI { using ColossalFramework; - using Harmony; + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.Manager.Impl; using TrafficManager.State; diff --git a/TLM/TLM/Patch/_RoadWorldInfoPanel/OnAdjustRoadButtonPatch.cs b/TLM/TLM/Patch/_RoadWorldInfoPanel/OnAdjustRoadButtonPatch.cs index 7c97ce993..a2746ff7b 100644 --- a/TLM/TLM/Patch/_RoadWorldInfoPanel/OnAdjustRoadButtonPatch.cs +++ b/TLM/TLM/Patch/_RoadWorldInfoPanel/OnAdjustRoadButtonPatch.cs @@ -1,7 +1,7 @@ namespace TrafficManager.Patch._RoadWorldInfoPanel { using ColossalFramework; - using Harmony; + using HarmonyLib; using JetBrains.Annotations; [HarmonyPatch(typeof(RoadWorldInfoPanel), "OnAdjustRoadButton")] diff --git a/TLM/TLM/Patch/_TrainTrackBaseAI/LevelCrossingSimulationStepPatch.cs b/TLM/TLM/Patch/_TrainTrackBaseAI/LevelCrossingSimulationStepPatch.cs index 18e609a7c..33fb557dc 100644 --- a/TLM/TLM/Patch/_TrainTrackBaseAI/LevelCrossingSimulationStepPatch.cs +++ b/TLM/TLM/Patch/_TrainTrackBaseAI/LevelCrossingSimulationStepPatch.cs @@ -1,8 +1,9 @@ -namespace TrafficManager.Patch._TrainTrackBase { +namespace TrafficManager.Patch._TrainTrackBase { + using HarmonyLib; using JetBrains.Annotations; using TrafficManager.State; - // [Harmony] Manually patched because struct references are used + [HarmonyPatch(typeof(RoadBaseAI), nameof(RoadBaseAI.TrafficLightSimulationStep))] public class LevelCrossingSimulationStepPatch { /// /// Decides whether the stock simulation step for traffic lights should run. diff --git a/TLM/TLM/Patch/_Vehicle/SpawnPatch.cs b/TLM/TLM/Patch/_Vehicle/SpawnPatch.cs index dae8047d2..1b2b8f7cf 100644 --- a/TLM/TLM/Patch/_Vehicle/SpawnPatch.cs +++ b/TLM/TLM/Patch/_Vehicle/SpawnPatch.cs @@ -1,7 +1,7 @@ -using ColossalFramework; +using ColossalFramework; using ColossalFramework.Math; using CSUtil.Commons; -using Harmony; +using HarmonyLib; using System; using System.Collections.Generic; using System.Linq; @@ -9,7 +9,7 @@ using UnityEngine; namespace TrafficManager.Patch._Vehicle { - // TODO this does currently not work with Harmony v1.1 + // TODO [issue #864] replace custom AI with harmony patch when possible. //[HarmonyPatch(typeof(Vehicle), "Spawn")] //public static class SpawnPatch { diff --git a/TLM/TLM/Patch/_Vehicle/UnspawnPatch.cs b/TLM/TLM/Patch/_Vehicle/UnspawnPatch.cs index 3cdc5682c..16e528c66 100644 --- a/TLM/TLM/Patch/_Vehicle/UnspawnPatch.cs +++ b/TLM/TLM/Patch/_Vehicle/UnspawnPatch.cs @@ -1,7 +1,7 @@ -using ColossalFramework; +using ColossalFramework; using ColossalFramework.Math; using CSUtil.Commons; -using Harmony; +using HarmonyLib; using System; using System.Collections.Generic; using System.Linq; @@ -9,7 +9,7 @@ using UnityEngine; namespace TrafficManager.Patch._Vehicle { - // TODO this does currently not work with Harmony v1.1 + // TODO [issue #864] replace custom AI with harmony patch when possible. //[HarmonyPatch(typeof(Vehicle), "Unspawn")] //public static class UnspawnPatch { diff --git a/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs b/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs index e60303080..1566d0d63 100644 --- a/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs +++ b/TLM/TLM/Patch/_VehicleManager/CreateVehiclePatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._VehicleManager { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; [HarmonyPatch(typeof(VehicleManager), "CreateVehicle")] diff --git a/TLM/TLM/Patch/_VehicleManager/ReleaseVehiclePatch.cs b/TLM/TLM/Patch/_VehicleManager/ReleaseVehiclePatch.cs index 3172fc299..6b9f5e4a6 100644 --- a/TLM/TLM/Patch/_VehicleManager/ReleaseVehiclePatch.cs +++ b/TLM/TLM/Patch/_VehicleManager/ReleaseVehiclePatch.cs @@ -1,5 +1,5 @@ namespace TrafficManager.Patch._VehicleManager { - using Harmony; + using HarmonyLib; using JetBrains.Annotations; [HarmonyPatch(typeof(VehicleManager), "ReleaseVehicle")] diff --git a/TLM/TLM/Patcher.cs b/TLM/TLM/Patcher.cs new file mode 100644 index 000000000..d75f693d3 --- /dev/null +++ b/TLM/TLM/Patcher.cs @@ -0,0 +1,92 @@ +namespace TrafficManager { + using ColossalFramework; + using ColossalFramework.UI; + using CSUtil.Commons; + using HarmonyLib; + using System; + using System.Collections.Generic; + using System.Reflection; + using TrafficManager.RedirectionFramework; + using TrafficManager.Util; + + public class Patcher { + public static Patcher Instance { get; private set; } + + public static Patcher Create() => Instance = new Patcher(); + + private const string HARMONY_ID = "de.viathinksoft.tmpe"; + + private bool initialized_ = false; + + public void Install() { + if (initialized_) { + return; + } + + Log.Info("Init detours"); + bool fail = false; + + try { +#if DEBUG + Harmony.DEBUG = true; +#endif + // Harmony attribute-driven patching + Log.Info($"Performing Harmony attribute-driven patching"); + var harmony = new Harmony(HARMONY_ID); + Shortcuts.Assert(harmony != null, "HarmonyInst!=null"); + harmony.PatchAll(); + Log.Info($"Harmony attribute-driven patching successfull!"); + } + catch (Exception e) { + Log.Error("Could not deploy Harmony patches"); + Log.Info(e.Message); + Log.Info(e.StackTrace); + fail = true; + } + + try { + Log.Info("Deploying attribute-driven detours"); + AssemblyRedirector.Deploy(); + } + catch (Exception e) { + Log.Error("Could not deploy attribute-driven detours"); + Log.Info(e.ToString()); + Log.Info(e.StackTrace); + fail = true; + } + + if (fail) { + Log.Info("Detours failed"); + Singleton.instance.m_ThreadingWrapper.QueueMainThread( + () => { + UIView.library + .ShowModal("ExceptionPanel") + .SetMessage( + "TM:PE failed to load", + "Traffic Manager: President Edition failed to load. You can " + + "continue playing but it's NOT recommended. Traffic Manager will " + + "not work as expected.", + true); + }); + } else { + Log.Info("Detours successful"); + } + + initialized_ = true; + } + + public void Uninstall() { + if (!initialized_) { + return; + } + + var harmony = new Harmony(HARMONY_ID); + Shortcuts.Assert(harmony != null, "HarmonyInst!=null"); + harmony.UnpatchAll(HARMONY_ID); + + initialized_ = false; + Log.Info("Reverting detours finished."); + } + + } +} diff --git a/TLM/TLM/RedirectionFramework b/TLM/TLM/RedirectionFramework index 68a47a187..89b0ff1d9 160000 --- a/TLM/TLM/RedirectionFramework +++ b/TLM/TLM/RedirectionFramework @@ -1 +1 @@ -Subproject commit 68a47a18757ea50269498d7db8a5f3e70318aa88 +Subproject commit 89b0ff1d99c5b25c488bc5408d4bb0586519ee2c diff --git a/TLM/TLM/State/GlobalConfig.cs b/TLM/TLM/State/GlobalConfig.cs index 4a104d9ed..0385fa472 100644 --- a/TLM/TLM/State/GlobalConfig.cs +++ b/TLM/TLM/State/GlobalConfig.cs @@ -1,4 +1,4 @@ -namespace TrafficManager.State { +namespace TrafficManager.State { using CSUtil.Commons; using JetBrains.Annotations; using System.IO; diff --git a/TLM/TLM/TLM.csproj b/TLM/TLM/TLM.csproj index e8caeefbe..24b58b4f2 100644 --- a/TLM/TLM/TLM.csproj +++ b/TLM/TLM/TLM.csproj @@ -1,4 +1,4 @@ - + @@ -87,13 +87,19 @@ ..\dependencies - - RedirectionFramework\lib\0TMPE.Harmony.dll + + ..\packages\Lib.Harmony.2.0.0.9\lib\net35\0Harmony.dll + False + False $(MangedDLLPath)\Assembly-CSharp.dll False + + ..\packages\CitiesHarmony.API.1.0.4\lib\net35\CitiesHarmony.API.dll + False + $(MangedDLLPath)\ColossalManaged.dll False @@ -144,6 +150,7 @@ + @@ -177,7 +184,6 @@ - @@ -207,7 +213,7 @@ - + @@ -685,7 +691,7 @@ xcopy /y "$(TargetDir)TMPE.API.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)CSUtil.CameraControl.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)TMPE.RedirectionFramework.dll" "%25DEPLOYDIR%25" xcopy /y "$(TargetDir)CSUtil.Commons.dll" "%25DEPLOYDIR%25" -xcopy /y "$(TargetDir)0TMPE.Harmony.dll" "%25DEPLOYDIR%25" +xcopy /y "$(TargetDir)CitiesHarmony.API.dll" "%25DEPLOYDIR%25" rem To avoid double hot reload, TrafficManager.dll must be replaced last and fast. rem Once TrafficManager.dll is re-loaded, all other dlls will be reloaded as well diff --git a/TLM/TLM/ThreadingExtension.cs b/TLM/TLM/ThreadingExtension.cs index 10de0d79c..290651625 100644 --- a/TLM/TLM/ThreadingExtension.cs +++ b/TLM/TLM/ThreadingExtension.cs @@ -25,8 +25,6 @@ public sealed class ThreadingExtension : ThreadingExtensionBase { IRoutingManager routeMan = Constants.ManagerFactory.RoutingManager; IUtilityManager utilMan = Constants.ManagerFactory.UtilityManager; - bool firstFrame = true; - public override void OnCreated(IThreading threading) { base.OnCreated(threading); @@ -43,87 +41,11 @@ public override void OnBeforeSimulationTick() { public override void OnBeforeSimulationFrame() { base.OnBeforeSimulationFrame(); - if (firstFrame) { - firstFrame = false; - Log.Info("ThreadingExtension.OnBeforeSimulationFrame: First frame detected. Checking detours."); - - List missingDetours = new List(); - - foreach (Detour detour in Detours) { - if (!RedirectionHelper.IsRedirected( - detour.OriginalMethod, - detour.CustomMethod)) - { - missingDetours.Add( - string.Format( - " {0}.{1} with {2} parameters ({3})", - detour.OriginalMethod.DeclaringType.Name, - detour.OriginalMethod.Name, - detour.OriginalMethod.GetParameters().Length, - detour.OriginalMethod.DeclaringType.AssemblyQualifiedName)); - } - } - - foreach (KeyValuePair entry in HarmonyMethodStates) { - MethodBase method = entry.Key; - RedirectCallsState oldState = entry.Value; - RedirectCallsState newState = - RedirectionHelper.GetState(method.MethodHandle.GetFunctionPointer()); - - if (!oldState.Equals(newState)) { - missingDetours.Add( - string.Format( - " {0}.{1} with {2} parameters ({3})", - method.DeclaringType.Name, - method.Name, - method.GetParameters().Length, - method.DeclaringType.AssemblyQualifiedName)); - } - } - - Log.Info($"ThreadingExtension.OnBeforeSimulationFrame: First frame detected. " + - $"Detours checked. Result: {missingDetours.Count} missing detours"); - - if (missingDetours.Count > 0) { - string error = - "Traffic Manager: President Edition detected an incompatibility with another " + - "mod! You can continue playing but it's NOT recommended. Traffic Manager will " + - "not work as expected. See TMPE.log for technical details."; - Log.Error(error); - string log = "The following methods were overriden by another mod:"; - - foreach (string missingDetour in missingDetours) { - log += $"\n\t{missingDetour}"; - } - - Log.Info(log); - - if (GlobalConfig.Instance.Main.ShowCompatibilityCheckErrorMessage) { - Prompt.Error("TM:PE Incompatibility Issue", error); - } - } - } - if (Options.timedLightsEnabled) { tlsMan.SimulationStep(); } } - // public override void OnAfterSimulationFrame() { - // base.OnAfterSimulationFrame(); - // - // routeMan.SimulationStep(); - // - // ++ticksSinceLastMinuteUpdate; - // if (ticksSinceLastMinuteUpdate > 60 * 60) { - // ticksSinceLastMinuteUpdate = 0; - // GlobalConfig.Instance.SimulationStep(); - // #if DEBUG - // DebugMenuPanel.PrintTransportStats(); - // #endif - // } - // } - public override void OnUpdate(float realTimeDelta, float simulationTimeDelta) { base.OnUpdate(realTimeDelta, simulationTimeDelta); diff --git a/TLM/TLM/TrafficManagerMod.cs b/TLM/TLM/TrafficManagerMod.cs index 513e507dc..99f35f958 100644 --- a/TLM/TLM/TrafficManagerMod.cs +++ b/TLM/TLM/TrafficManagerMod.cs @@ -12,6 +12,7 @@ namespace TrafficManager { using static TrafficManager.Util.Shortcuts; using ColossalFramework; using UnityEngine.SceneManagement; + using CitiesHarmony.API; public class TrafficManagerMod : IUserMod { #if LABS @@ -85,6 +86,8 @@ public void OnEnabled() { Instance = this; InGameHotReload = InGame(); + + HarmonyHelper.EnsureHarmonyInstalled(); } [UsedImplicitly] diff --git a/TLM/TLM/UI/Helpers/GuideHandler.cs b/TLM/TLM/UI/Helpers/GuideHandler.cs index 552b2f4e9..76aaad576 100644 --- a/TLM/TLM/UI/Helpers/GuideHandler.cs +++ b/TLM/TLM/UI/Helpers/GuideHandler.cs @@ -1,8 +1,8 @@ +namespace TrafficManager.UI.Helpers { using ColossalFramework; using CSUtil.Commons; using System.Collections.Generic; -namespace TrafficManager.UI.Helpers { public class GuideHandler { private Dictionary GuideTable = new Dictionary(); diff --git a/TLM/TLM/packages.config b/TLM/TLM/packages.config index 5011e1991..595a8c302 100644 --- a/TLM/TLM/packages.config +++ b/TLM/TLM/packages.config @@ -1,5 +1,7 @@  + + \ No newline at end of file