@@ -289,6 +289,96 @@ public void ApplyLateStaticPatches(ReadOnlySpan<ModPatchInfo> patches)
289289 }
290290 }
291291
292+ /// <summary>
293+ /// Removes Harmony patches owned by another Harmony id from a resolved target method. This is intended for narrow
294+ /// compatibility fixes where a dependency's patch is unsafe on the current game version and the caller replaces it
295+ /// with a safe patch through this patcher.
296+ /// 从已解析的目标方法上移除另一个 Harmony id 拥有的 Harmony patch。用于依赖 mod 的 patch 在当前游戏版本
297+ /// 不安全、调用方需要用本 patcher 的安全 patch 替换它的窄范围兼容修复。
298+ /// </summary>
299+ /// <param name="originalMethod">
300+ /// Original method whose patch list should be inspected.
301+ /// 要检查 patch 列表的原始方法。
302+ /// </param>
303+ /// <param name="owner">
304+ /// Harmony owner id to remove.
305+ /// 要移除的 Harmony owner id。
306+ /// </param>
307+ /// <param name="patchDeclaringType">
308+ /// Optional declaring type filter for the patch method.
309+ /// 可选的 patch 方法声明类型过滤器。
310+ /// </param>
311+ /// <param name="patchMethodName">
312+ /// Optional patch method name filter.
313+ /// 可选的 patch 方法名过滤器。
314+ /// </param>
315+ /// <param name="patchType">
316+ /// Harmony patch kind to inspect; <see cref="HarmonyPatchType.All" /> checks every kind.
317+ /// 要检查的 Harmony patch 类型;<see cref="HarmonyPatchType.All" /> 会检查所有类型。
318+ /// </param>
319+ /// <returns>
320+ /// Number of patch methods removed.
321+ /// 已移除的 patch 方法数量。
322+ /// </returns>
323+ public int UnpatchExternalPatches (
324+ MethodBase originalMethod ,
325+ string owner ,
326+ Type ? patchDeclaringType = null ,
327+ string ? patchMethodName = null ,
328+ HarmonyPatchType patchType = HarmonyPatchType . All )
329+ {
330+ ArgumentNullException . ThrowIfNull ( originalMethod ) ;
331+ ArgumentException . ThrowIfNullOrWhiteSpace ( owner ) ;
332+
333+ var patchInfo = Harmony . GetPatchInfo ( originalMethod ) ;
334+ if ( patchInfo == null )
335+ {
336+ logger . Info ( $ "{ _logPrefix } No Harmony patches found on { FormatMethod ( originalMethod ) } ") ;
337+ return 0 ;
338+ }
339+
340+ var patches = EnumeratePatches ( patchInfo , patchType )
341+ . Where ( patch => MatchesPatch ( patch , owner , patchDeclaringType , patchMethodName ) )
342+ . ToArray ( ) ;
343+
344+ foreach ( var patch in patches ) _harmony . Unpatch ( originalMethod , patch . PatchMethod ) ;
345+
346+ logger . Info (
347+ $ "{ _logPrefix } Removed { patches . Length } external patch(es) from { FormatMethod ( originalMethod ) } owned by '{ owner } '") ;
348+ return patches . Length ;
349+ }
350+
351+ /// <summary>
352+ /// Resolves <paramref name="target" /> and removes matching external Harmony patches. Missing targets can be
353+ /// ignored for optional compatibility paths.
354+ /// 解析 <paramref name="target" /> 并移除匹配的外部 Harmony patch。可选兼容路径可以忽略缺失目标。
355+ /// </summary>
356+ public int UnpatchExternalPatches (
357+ ModPatchTarget target ,
358+ string owner ,
359+ Type ? patchDeclaringType = null ,
360+ string ? patchMethodName = null ,
361+ HarmonyPatchType patchType = HarmonyPatchType . All ,
362+ bool ignoreIfTargetMissing = true )
363+ {
364+ ArgumentNullException . ThrowIfNull ( target ) ;
365+
366+ var originalMethod = PatchTargetMethodResolver . Resolve ( target ) ;
367+ if ( originalMethod != null )
368+ return UnpatchExternalPatches (
369+ originalMethod ,
370+ owner ,
371+ patchDeclaringType ,
372+ patchMethodName ,
373+ patchType ) ;
374+
375+ var message = $ "{ _logPrefix } External unpatch target not found: { target } ";
376+ if ( ! ignoreIfTargetMissing && ! target . IgnoreIfMissing )
377+ throw new MissingMethodException ( target . TargetType . FullName , target . MethodName ) ;
378+ logger . Info ( message ) ;
379+ return 0 ;
380+ }
381+
292382 /// <summary>
293383 /// Removes all applied patches tracked by this instance from the underlying Harmony id.
294384 /// 从底层 Harmony id 移除此实例跟踪的所有已应用补丁。
@@ -498,6 +588,46 @@ private bool ProcessPatchResults(ReadOnlySpan<ModPatchResult> results)
498588 ) ;
499589 }
500590
591+ private static IEnumerable < Patch > EnumeratePatches ( Patches patchInfo , HarmonyPatchType patchType )
592+ {
593+ if ( patchType is HarmonyPatchType . All or HarmonyPatchType . Prefix )
594+ foreach ( var patch in patchInfo . Prefixes )
595+ yield return patch ;
596+
597+ if ( patchType is HarmonyPatchType . All or HarmonyPatchType . Postfix )
598+ foreach ( var patch in patchInfo . Postfixes )
599+ yield return patch ;
600+
601+ if ( patchType is HarmonyPatchType . All or HarmonyPatchType . Transpiler )
602+ foreach ( var patch in patchInfo . Transpilers )
603+ yield return patch ;
604+
605+ // ReSharper disable once InvertIf
606+ if ( patchType is HarmonyPatchType . All or HarmonyPatchType . Finalizer )
607+ foreach ( var patch in patchInfo . Finalizers )
608+ yield return patch ;
609+ }
610+
611+ private static bool MatchesPatch (
612+ Patch patch ,
613+ string owner ,
614+ Type ? patchDeclaringType ,
615+ string ? patchMethodName )
616+ {
617+ if ( patch . owner != owner )
618+ return false ;
619+
620+ if ( patchDeclaringType != null && patch . PatchMethod . DeclaringType != patchDeclaringType )
621+ return false ;
622+
623+ return string . IsNullOrEmpty ( patchMethodName ) || patch . PatchMethod . Name == patchMethodName ;
624+ }
625+
626+ private static string FormatMethod ( MethodBase method )
627+ {
628+ return $ "{ method . DeclaringType ? . FullName } .{ method . Name } ";
629+ }
630+
501631 /// <summary>
502632 /// Validate that patch type implements IPatchMethod interface (optional but recommended)
503633 /// 验证 patch 类型是否实现 IPatchMethod 接口(可选但推荐)
0 commit comments