diff --git a/KSPCommunityFixes/BugFixes/DoubleCurvePreserveTangents.cs b/KSPCommunityFixes/BugFixes/DoubleCurvePreserveTangents.cs index 44ab46b4..e4af8f87 100644 --- a/KSPCommunityFixes/BugFixes/DoubleCurvePreserveTangents.cs +++ b/KSPCommunityFixes/BugFixes/DoubleCurvePreserveTangents.cs @@ -1,7 +1,5 @@ using System; -using System.Collections.Generic; -using HarmonyLib; -using System.Reflection.Emit; +using System.Runtime.CompilerServices; namespace KSPCommunityFixes.BugFixes { @@ -11,31 +9,17 @@ class DoubleCurvePreserveTangents : BasePatch protected override void ApplyPatches() { - AddPatch(PatchType.Transpiler, typeof(DoubleCurve), nameof(DoubleCurve.RecomputeTangents)); + AddPatch(PatchType.Prefix, typeof(DoubleCurve), nameof(DoubleCurve.RecomputeTangents)); } - static IEnumerable DoubleCurve_RecomputeTangents_Transpiler(IEnumerable instructions) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool DoubleCurve_RecomputeTangents_Prefix(DoubleCurve __instance) { // The existing function has a test if ( count == 1 ) and, if true, it // will flatten the tangents of the key regardless of if it is // set to autotangent or not. Since the tangents of a single-key - // curve don't matter, let's just return. - List code = new List(instructions); - for (int i = 1; i < code.Count; ++i) - { - if (code[i].opcode == OpCodes.Ldc_I4_1 && code[i - 1].opcode != OpCodes.Ldloc_1) - { - code[i] = new CodeInstruction(OpCodes.Ret); - code[i + 1] = new CodeInstruction(OpCodes.Nop); - code[i + 2] = new CodeInstruction(OpCodes.Nop); - code[i + 3] = new CodeInstruction(OpCodes.Nop); - code[i + 4] = new CodeInstruction(OpCodes.Nop); - code[i + 5] = new CodeInstruction(OpCodes.Nop); - break; - } - } - - return code; + // curve don't matter, we skip the function in that case. + return __instance.keys.Count != 1; } } } diff --git a/KSPCommunityFixes/BugFixes/ExtendedDeployableParts.cs b/KSPCommunityFixes/BugFixes/ExtendedDeployableParts.cs index bd924df3..6f1921f1 100644 --- a/KSPCommunityFixes/BugFixes/ExtendedDeployableParts.cs +++ b/KSPCommunityFixes/BugFixes/ExtendedDeployableParts.cs @@ -1,4 +1,5 @@ -using HarmonyLib; +using CommNet.Network; +using HarmonyLib; using System; using System.Collections.Generic; using System.Reflection; @@ -22,7 +23,6 @@ class ExtendedDeployableParts : BasePatch protected override void ApplyPatches() { AddPatch(PatchType.Transpiler, typeof(ModuleDeployablePart), nameof(ModuleDeployablePart.startFSM)); - AddPatch(PatchType.Transpiler, typeof(ModuleDeployableSolarPanel), nameof(ModuleDeployablePart.OnStart)); } @@ -85,25 +85,106 @@ static IEnumerable ModuleDeployableSolarPanel_OnStart_Transpile // anim.Stop(animationName); //} - // We remove that entire if statement by replacing the if (Brtrue_S) by an unconditional jump (Br_S) + // We remove that entire if statement by making the if (Brtrue_S) unconditional FieldInfo ModuleDeployablePart_deployState = AccessTools.Field(typeof(ModuleDeployablePart), nameof(ModuleDeployablePart.deployState)); - List code = new List(instructions); + var matcher = new CodeMatcher(instructions); + matcher + .MatchStartForward( + new CodeMatch(OpCodes.Ldarg_0), + new CodeMatch(OpCodes.Ldfld, ModuleDeployablePart_deployState), + new CodeMatch(OpCodes.Brtrue_S) + ); - for (int i = 0; i < code.Count; i++) + if (!matcher.IsValid) + return matcher.Instructions(); + + matcher + .RemoveInstructions(2) + .Insert(new CodeInstruction(OpCodes.Ldc_I4_1)); + + return matcher.Instructions(); + } + + static bool ModuleDeployablePart_startFSM_Prefix(ModuleDeployableSolarPanel __instance) + { + if (__instance.useAnimation) { - if (code[i].opcode == OpCodes.Ldarg_0 - && code[i + 1].opcode == OpCodes.Ldfld && ReferenceEquals(code[i+1].operand, ModuleDeployablePart_deployState) - && code[i + 2].opcode == OpCodes.Brtrue_S) + __instance.anim[__instance.animationName].wrapMode = WrapMode.ClampForever; + switch (__instance.deployState) { - code[i].opcode = OpCodes.Br_S; - code[i].operand = code[i + 2].operand; // grab the target instruction from the original jump - break; + case ModuleDeployablePart.DeployState.RETRACTED: + __instance.anim[__instance.animationName].normalizedTime = 0f; + __instance.anim[__instance.animationName].enabled = true; + __instance.anim[__instance.animationName].weight = 1f; + __instance.anim[__instance.animationName].speed = 0f; + __instance.bypassSetupAnimation = true; + __instance.Events["Retract"].active = false; + __instance.Events["Extend"].active = true; + break; + case ModuleDeployablePart.DeployState.EXTENDED: + __instance.anim[__instance.animationName].normalizedTime = 1f; + __instance.anim[__instance.animationName].enabled = true; + __instance.anim[__instance.animationName].speed = 0f; + __instance.anim[__instance.animationName].weight = 1f; + __instance.Events["Extend"].active = false; + __instance.Events["Retract"].active = __instance.retractable || HighLogic.LoadedSceneIsEditor; + if (__instance.hasPivot) + { + __instance.panelRotationTransform.localRotation = __instance.currentRotation; + } + break; + case ModuleDeployablePart.DeployState.RETRACTING: + __instance.Events["Retract"].active = false; + __instance.Events["Extend"].active = false; + break; + case ModuleDeployablePart.DeployState.EXTENDING: + __instance.Events["Retract"].active = false; + __instance.Events["Extend"].active = false; + break; + } + if (__instance.deployState == ModuleDeployablePart.DeployState.RETRACTING || __instance.deployState == ModuleDeployablePart.DeployState.EXTENDING || __instance.deployState == ModuleDeployablePart.DeployState.BROKEN) + { + __instance.anim[__instance.animationName].normalizedTime = __instance.storedAnimationTime; + __instance.anim[__instance.animationName].speed = __instance.storedAnimationSpeed; + } + if (!__instance.bypassSetupAnimation) + { + __instance.anim.Play(__instance.animationName); + } + if (!__instance.playAnimationOnStart && __instance.deployState != ModuleDeployablePart.DeployState.EXTENDING && __instance.deployState != ModuleDeployablePart.DeployState.RETRACTING) + { + __instance.stopAnimation = true; } } - - return code; + else + { + if (__instance.hasPivot) + { + __instance.panelRotationTransform.localRotation = __instance.originalRotation; + } + if (__instance.deployState != ModuleDeployablePart.DeployState.BROKEN) + { + __instance.deployState = ModuleDeployablePart.DeployState.EXTENDED; + } + __instance.Events["Retract"].active = false; + __instance.Events["Extend"].active = false; + __instance.Actions["ExtendPanelsAction"].active = false; + __instance.Actions["ExtendAction"].active = false; + __instance.Actions["RetractAction"].active = false; + __instance.Fields["status"].guiActiveEditor = false; + } + if (__instance.deployState == ModuleDeployablePart.DeployState.BROKEN) + { + __instance.Events["Retract"].active = false; + __instance.Events["Extend"].active = false; + if (__instance.panelBreakTransform) + { + __instance.panelBreakTransform.gameObject.SetActive(false); + } + } + return false; } } } diff --git a/KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs b/KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs index 2965fc49..1c19443a 100644 --- a/KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs +++ b/KSPCommunityFixes/BugFixes/ModuleIndexingMismatch.cs @@ -29,7 +29,6 @@ public class ModuleIndexingMismatch : BasePatch private const string VALUENAME_MODULEPARTCONFIGID = "modulePartConfigId"; private static readonly HashSet multiModules = new HashSet(); private static readonly Dictionary allModuleTypes = new Dictionary(); - protected override Version VersionMin => new Version(1, 8, 0); protected override void ApplyPatches() @@ -44,7 +43,6 @@ protected override void ApplyPatches() { AddPatch(PatchType.Transpiler, typeof(ProtoPartSnapshot), "ConfigurePart"); } - AddPatch(PatchType.Transpiler, typeof(ShipConstruct), "LoadShip", new Type[] { typeof(ConfigNode), typeof(uint), typeof(bool), typeof(string).MakeByRefType() }); Type multiModuleType = typeof(IMultipleModuleInPart); @@ -222,6 +220,7 @@ private static void LoadProtoPartSnapshotModule(ProtoPartModuleSnapshot protoMod protoModule.moduleRef = module; } + static void LoadModules(ProtoPartSnapshot protoPart, Part part) { int protoModuleCount = protoPart.modules.Count; @@ -348,7 +347,7 @@ static void LoadModules(ProtoPartSnapshot protoPart, Part part) { // see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/236 // when persisted type is a derived type of the module type, or when the module type is a derived type of the persisted type, still attempt to load it. - if (protoModuleIdx < partModuleCount && allModuleTypes.TryGetValue(protoModule.moduleName, out Type moduleType) + if (protoModuleIdx < partModuleCount && allModuleTypes.TryGetValue(protoModule.moduleName, out Type moduleType) && (part.Modules[protoModuleIdx].GetType().IsAssignableFrom(moduleType) || moduleType.IsInstanceOfType(part.Modules[protoModuleIdx]))) { LoadProtoPartSnapshotModule(protoPart.modules[protoModuleIdx], part.modules[protoModuleIdx]); @@ -375,75 +374,69 @@ static IEnumerable ShipConstruct_LoadShip_Transpiler(IEnumerabl MethodInfo LoadShipModuleNodes = AccessTools.Method(typeof(ModuleIndexingMismatch), nameof(ModuleIndexingMismatch.LoadShipModuleNodes)); MethodInfo ConfigNode_get_nodes = AccessTools.PropertyGetter(typeof(ConfigNode), nameof(ConfigNode.nodes)); - List code = new List(instructions); + var matcher = new CodeMatcher(instructions); // first, remove the original module load call - bool originalFound = false; - for (int i = 0; i < code.Count - 6; i++) - { - //// part.LoadModule(configNode2, ref moduleIndex); - // ldloc.s 6 - // ldloc.3 - // ldloca.s 39 - // callvirt instance class PartModule Part::LoadModule(class ConfigNode, int32&) - // dup - // pop - // pop - if (code[i].opcode == OpCodes.Ldloc_S - && code[i + 1].opcode == OpCodes.Ldloc_3 - && code[i + 2].opcode == OpCodes.Ldloca_S - && code[i + 3].opcode == OpCodes.Callvirt && ReferenceEquals(code[i + 3].operand, Part_LoadModule) - && code[i + 4].opcode == OpCodes.Dup - && code[i + 5].opcode == OpCodes.Pop - && code[i + 6].opcode == OpCodes.Pop) - { - originalFound = true; - for (int j = i; j < i + 7; j++) - { - code[j].opcode = OpCodes.Nop; - code[j].operand = null; - } - break; - } - } - - if (!originalFound) - { - Debug.LogError($"Error applying ModuleIndexingMismatch patch : couldn't find Part.LoadModule() call in ShipConstruct.LoadShip()"); - return instructions; - } + //// part.LoadModule(configNode2, ref moduleIndex); + // ldloc.s 6 + // ldloc.3 + // ldloca.s 39 + // callvirt instance class PartModule Part::LoadModule(class ConfigNode, int32&) + // dup ; only if obfuscated + // pop ; only if obfuscated + // pop + matcher + .MatchStartForward( + new CodeMatch(OpCodes.Ldloc_S), + new CodeMatch(OpCodes.Ldloc_3), + new CodeMatch(OpCodes.Ldloca_S), + new CodeMatch(OpCodes.Callvirt, Part_LoadModule) + ) + .ThrowIfInvalid("couldn't find Part.LoadModule() call in ShipConstruct.LoadShip()"); + + // The dup;pop; pair might not be present in a deobfuscated assembly + // so we push a dummy value on the stack instead of deleting them. + // + // One of these appears to have a label, so removing it causes patching + // to fail. The same applies to using SetInstruction. + matcher + .SetAndAdvance(OpCodes.Nop, null) + .SetAndAdvance(OpCodes.Nop, null) + .SetAndAdvance(OpCodes.Nop, null) + .SetAndAdvance(OpCodes.Ldnull, null); // then, insert our own module loading call before the original part nodes parsing loop - for (int i = 0; i < code.Count - 6; i++) - { - //// int moduleIndex = 0; - // ldc.i4.0 NULL - // stloc.s 39(System.Int32) - //// int m = 0; - // ldc.i4.0 NULL - // stloc.s 45(System.Int32) - //// int count5 = configNode.nodes.Count - // ldloc.2 NULL - // callvirt ConfigNodeList ConfigNode::get_nodes() - // dup NULL - // pop NULL - // callvirt System.Int32 ConfigNodeList::get_Count() - - if (code[i].opcode == OpCodes.Ldc_I4_0 - && code[i + 1].opcode == OpCodes.Stloc_S - && code[i + 2].opcode == OpCodes.Ldc_I4_0 - && code[i + 3].opcode == OpCodes.Stloc_S - && code[i + 4].opcode == OpCodes.Ldloc_2 - && code[i + 5].opcode == OpCodes.Callvirt && ReferenceEquals(code[i + 5].operand, ConfigNode_get_nodes)) - { - code.Insert(i, new CodeInstruction(OpCodes.Ldloc_S, 6)); // ldloc.s 6 is the Part local variable - code.Insert(i + 1, new CodeInstruction(OpCodes.Ldloc_2)); // Ldloc_2 is the part ConfigNode variable - code.Insert(i + 2, new CodeInstruction(OpCodes.Call, LoadShipModuleNodes)); - break; - } - } - - return code; + //// int moduleIndex = 0; + // ldc.i4.0 NULL + // stloc.s 39(System.Int32) + //// int m = 0; + // ldc.i4.0 NULL + // stloc.s 45(System.Int32) + //// int count5 = configNode.nodes.Count + // ldloc.2 NULL + // callvirt ConfigNodeList ConfigNode::get_nodes() + // dup NULL ; only if obfuscated + // pop NULL ; only obfuscated + // callvirt System.Int32 ConfigNodeList::get_Count() + matcher + .Start() + .MatchStartForward( + new CodeMatch(OpCodes.Ldc_I4_0), + new CodeMatch(OpCodes.Stloc_S), + new CodeMatch(OpCodes.Ldc_I4_0), + new CodeMatch(OpCodes.Stloc_S), + new CodeMatch(OpCodes.Ldloc_2), + new CodeMatch(OpCodes.Callvirt, ConfigNode_get_nodes) + ) + .ThrowIfInvalid("Could not find the call to ConfigNode::get_nodes") + .Advance(6) + .Insert( + new CodeInstruction(OpCodes.Ldloc_S, 6), // Part local variable + new CodeInstruction(OpCodes.Ldloc_2), // part ConfigNode variable + new CodeInstruction(OpCodes.Call, LoadShipModuleNodes) + ); + + return matcher.Instructions(); } private static readonly List currentModuleNodes = new List(); @@ -589,7 +582,7 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode) { // see https://github.com/KSPModdingLibs/KSPCommunityFixes/issues/236 // when persisted type is a derived type of the module type, or when the module type is a derived type of the persisted type, still attempt to load it. - if (moduleNodeIdx < partModuleCount + if (moduleNodeIdx < partModuleCount && allModuleTypes.TryGetValue(nodeModuleName, out Type moduleType) && (part.Modules[moduleNodeIdx].GetType().IsAssignableFrom(moduleType) || moduleType.IsInstanceOfType(part.Modules[moduleNodeIdx]))) { @@ -611,4 +604,4 @@ static void LoadShipModuleNodes(Part part, ConfigNode partNode) } } } -} +} \ No newline at end of file diff --git a/KSPCommunityFixes/BugFixes/RefundingOnRecovery.cs b/KSPCommunityFixes/BugFixes/RefundingOnRecovery.cs index f5d9fb35..6934733e 100644 --- a/KSPCommunityFixes/BugFixes/RefundingOnRecovery.cs +++ b/KSPCommunityFixes/BugFixes/RefundingOnRecovery.cs @@ -1,8 +1,10 @@ -using System; +using HarmonyLib; +using KSP.UI.Screens; +using KSP.UI.Screens.SpaceCenter.MissionSummaryDialog; +using System; using System.Collections.Generic; using System.Reflection; using System.Reflection.Emit; -using HarmonyLib; namespace KSPCommunityFixes.BugFixes { @@ -72,6 +74,7 @@ static IEnumerable Funding_onVesselRecoveryProcessing_Transpile // public static float GetPartCosts(ProtoPartSnapshot protoPart, bool includeModuleCosts, AvailablePart aP, out float dryCost, out float fuelCost) MethodInfo getPartCostsMethod = AccessTools.Method(typeof(ShipConstruction), "GetPartCosts", new[] { typeof(ProtoPartSnapshot), typeof(bool), typeof(AvailablePart), typeof(float).MakeByRefType(), typeof(float).MakeByRefType() }); + MethodInfo getStoredPartsModuleCosts = AccessTools.Method(typeof(RefundingOnRecovery), nameof(GetStoredPartsCosts)); if (getPartCostsMethod == null) { @@ -79,51 +82,36 @@ static IEnumerable Funding_onVesselRecoveryProcessing_Transpile return instructions; } - - int insertionIndex = -1; - object dryCostVarOperand = default; - OpCode protoPartSnapshotOpcode = default; - - for (int i = 4; i < code.Count - 1; i++) - { - if (code[i].opcode == OpCodes.Call && (MethodInfo)code[i].operand == getPartCostsMethod) - { - // change includeModuleCosts from false to true - code[i - 4].opcode = OpCodes.Ldc_I4_1; - // Insert our method call after "pop" - insertionIndex = i + 1; - // find the variables we need - dryCostVarOperand = code[i - 2].operand; - protoPartSnapshotOpcode = code[i - 5].opcode; - break; - } - } - - if (insertionIndex == -1) - { - UnityEngine.Debug.LogError("Error patching recovery costs : transpiler patch failed"); - return instructions; - } - - List instructionsToInsert = new List(); - MethodInfo getStoredPartsModuleCosts = AccessTools.Method(typeof(RefundingOnRecovery), nameof(GetStoredPartsCosts)); - - instructionsToInsert.Add(new CodeInstruction(OpCodes.Ldloc_S, dryCostVarOperand)); - instructionsToInsert.Add(new CodeInstruction(protoPartSnapshotOpcode)); - instructionsToInsert.Add(new CodeInstruction(OpCodes.Call, getStoredPartsModuleCosts)); - instructionsToInsert.Add(new CodeInstruction(OpCodes.Sub)); - instructionsToInsert.Add(new CodeInstruction(OpCodes.Stloc_S, dryCostVarOperand)); - - code.InsertRange(insertionIndex, instructionsToInsert); - - return code; + var matcher = new CodeMatcher(instructions); + matcher + .MatchStartForward(new CodeMatch(OpCodes.Call, getPartCostsMethod)) + .Advance(-5); + + var loadProtoPart = new CodeInstruction(matcher.Instruction); + matcher + .Advance(1) // index - 4 + // change includeModuleCosts from false to true + .SetInstruction(new CodeInstruction(OpCodes.Ldc_I4_1)) + .Advance(2); // index - 2 + + var dryCostVarOperand = matcher.Operand; + matcher + .Advance(3) // index + 1 + .Insert( + new CodeInstruction(OpCodes.Ldloc_S, dryCostVarOperand), + loadProtoPart, + new CodeInstruction(OpCodes.Call, getStoredPartsModuleCosts), + new CodeInstruction(OpCodes.Sub), + new CodeInstruction(OpCodes.Stloc_S, dryCostVarOperand) + ); + + return matcher.Instructions(); } - // Derived from the ModuleInventoryPart.OnLoad() code, get stored parts cost static float GetStoredPartsCosts(ProtoPartSnapshot protoPart) { - ConstructorInfo storedPartCtor = AccessTools.Constructor(typeof(StoredPart), new[] {typeof(ConfigNode)}); + ConstructorInfo storedPartCtor = AccessTools.Constructor(typeof(StoredPart), new[] { typeof(ConfigNode) }); float cost = 0f; foreach (ProtoPartModuleSnapshot protoModule in protoPart.modules) @@ -181,7 +169,6 @@ static float GetStoredPartsCosts(ProtoPartSnapshot protoPart) return cost; } - } public class ModulePartCostTest : PartModule, IPartCostModifier diff --git a/KSPCommunityFixes/KSPCommunityFixes.cs b/KSPCommunityFixes/KSPCommunityFixes.cs index 0113d4cc..37c4e4e7 100644 --- a/KSPCommunityFixes/KSPCommunityFixes.cs +++ b/KSPCommunityFixes/KSPCommunityFixes.cs @@ -52,6 +52,30 @@ public static Version KspVersion } } + //returns true if KSP Assembly-CSharp is probably a cleaned/deobfuscated image + private static bool? cleanedDllCachedValue = null; + public static bool IsCleanedDll + { + get + { + if (cleanedDllCachedValue != null) + { + return (bool)cleanedDllCachedValue; + } + else + { + String dllPath = typeof(GameDatabase).Assembly.Location; + if ((new FileInfo(dllPath).Length < 10000000) && Versioning.version_minor.Equals(12)) + { + cleanedDllCachedValue = true; + return true; //certainly a home-cleaned dll, no official 1.12.x build of Assembly-CSharp is less than 10MBs. + } + cleanedDllCachedValue = false; + return false; + } + } + } + static KSPCommunityFixes() { Harmony = new Harmony("KSPCommunityFixes");