Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 6 additions & 12 deletions SecretAPI/Attributes/CallOnLoadAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using SecretAPI.Extensions;
using SecretAPI.Features;

/// <summary>
Expand Down Expand Up @@ -50,21 +51,14 @@ public static void Load(Assembly? assembly = null)
internal static void CallAttributeMethodPriority<TAttribute>(Assembly assembly)
where TAttribute : Attribute, IPriority
{
const BindingFlags methodFlags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
Dictionary<TAttribute, MethodInfo> methods = new();

// get all types
foreach (Type type in assembly.GetTypes())
foreach (MethodInfo method in assembly.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic))
{
// get all static methods
foreach (MethodInfo method in type.GetMethods(methodFlags))
{
TAttribute? attribute = method.GetCustomAttribute<TAttribute>();
if (attribute == null)
continue;
TAttribute? attribute = method.GetCustomAttribute<TAttribute>();
if (attribute == null)
continue;

methods.Add(attribute, method);
}
methods.Add(attribute, method);
}

foreach (KeyValuePair<TAttribute, MethodInfo> method in methods.OrderBy(static v => v.Key.Priority))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ namespace SecretAPI.Debugging;
/// <summary>
/// Debugs basegame prefabs by logging information about them.
/// </summary>
internal static class PrefabDebugging
internal static class PrefabDebugger
{
/// <summary>
/// Loads the prefab debugging.
Expand Down
44 changes: 36 additions & 8 deletions SecretAPI/Extensions/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
namespace SecretAPI.Extensions;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using HarmonyLib;

/// <summary>
/// Extensions for reflection.
Expand Down Expand Up @@ -36,16 +38,42 @@ public static string GetLongFuncName(Type type, MethodInfo method)
}

/// <summary>
/// Copies the properties.
/// Searches through an assembly and returns all <see cref="MethodInfo"/> based on the provided flags.
/// </summary>
/// <param name="assembly">The assembly to search through.</param>
/// <param name="flags">The <see cref="BindingFlags"/> used for the method search within a <see cref="Type"/>.</param>
/// <returns>A collection of <see cref="MethodInfo"/> based on the provided assembly and flags.</returns>
public static IEnumerable<MethodInfo> GetMethods(this Assembly assembly, BindingFlags flags)
=> assembly.GetTypes().SelectMany(type => type.GetMethods(flags));

/// <summary>
/// Gets a nested <see cref="MethodInfo"/> within a <see cref="Type"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> containing the needed method.</param>
/// <param name="nestedTypeName">The name of the nested type, this does not need to be exact.</param>
/// <param name="methodName">The name of the method, this does not need to be exact.</param>
/// <returns>The found method, or null if none matched.</returns>
public static MethodInfo? GetNestedMethod(this Type type, string nestedTypeName, string methodName)
{
return type.GetNestedTypes(AccessTools.all)
.Where(t => t.Name.Contains(nestedTypeName))
.SelectMany(t => t.GetMethods(AccessTools.all))
.FirstOrDefault(m => m.Name.Contains(methodName));
}

/// <summary>
/// Copies the properties from a <see cref="Type"/> onto another instance.
/// </summary>
/// <param name="source">The source of the properties to copy.</param>
/// <param name="destination">Where to copy to.</param>
public static void CopyProperties(this object source, object destination)
/// <param name="destination">Where the source properties should be copied to, this should match the same <see cref="Type"/> as source.</param>
public static void CopyPropertiesTo(this object source, object destination)
{
Type destinationType = destination.GetType();
foreach (PropertyInfo property in source.GetType().GetProperties())
{
destinationType.GetProperty(property.Name)?.SetValue(destination, property.GetValue(source));
}
Type type = source.GetType();

if (type != destination.GetType())
throw new InvalidOperationException($"[ReflectionExtensions.CopyPropertiesTo] Source and destination types are mismatched: {type.FullName} | {destination.GetType().FullName}");

foreach (PropertyInfo property in type.GetProperties())
property.SetValue(destination, property.GetValue(source));
}
}
21 changes: 9 additions & 12 deletions SecretAPI/Patches/Features/RoundEndIgnorePatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using PlayerRoles;
using SecretAPI.Attributes;
Expand All @@ -14,37 +15,33 @@
using SecretAPI.Features;

/// <summary>
/// Handles patching to implement <see cref="RoundIgnoreStatus.RoundEndingCheck"/> into <see cref="RoundSummary.UpdateTargetCount"/>.
/// Handles patching to implement <see cref="RoundIgnoreStatus.RoundEndingCheck"/> into <see cref="RoundSummary._ProcessServerSideCode"/>.
/// </summary>
[HarmonyPatchCategory(nameof(PlayerRoundIgnore))]
[HarmonyPatch]
internal static class RoundEndIgnorePatch
{
private const string StateMachine = "<_ProcessServerSideCode>d__58";
private const string StateMachine = "_ProcessServerSideCode";
private const string MoveNext = "MoveNext";
private const int ReferenceHubLocalIndex = 20;
private const int SkipAdvanceAmount = 4; // amount needed to get to IL_0131: br IL_01bd

private static MethodInfo TargetMethod()
{
// typeof(RoundSummary).GetNestedTypes(AccessTools.all).ForEach(type => Logger.Debug(type.FullName ?? "NULL"));
Type nestedType = typeof(RoundSummary).GetNestedTypes(AccessTools.all)
.FirstOrDefault(type => type.Name is StateMachine) ?? throw new Exception($"Could not locate state machine for {StateMachine}");

// nestedType.GetMethods(AccessTools.all).ForEach(method => Logger.Debug(method.Name));
MethodInfo moveNextMethod = nestedType.GetMethods(AccessTools.all)
.FirstOrDefault(x => x.Name.Contains(MoveNext)) ?? throw new Exception($"Could not locate {MoveNext} method in state machine");

return moveNextMethod;
return typeof(RoundSummary).GetNestedMethod(StateMachine, MoveNext)
?? throw new Exception($"Could not locate state machine for {StateMachine} | {MoveNext}");
}

private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
{
CodeMatcher matcher = new CodeMatcher(instructions, generator)
.MatchEndForward(new CodeMatch(CodeInstruction.Call(typeof(PlayerRolesUtils), nameof(PlayerRolesUtils.GetTeam), [typeof(ReferenceHub)])))
.Advance(SkipAdvanceAmount)
.CreateLabel(out Label skip)
.Advance(-SkipAdvanceAmount)
.Insert(
new CodeInstruction(OpCodes.Ldloc_S, ReferenceHubLocalIndex),
CodeInstruction.Call(typeof(RoundEndIgnorePatch), nameof(IsPlayerIgnored)),
new CodeInstruction(OpCodes.Callvirt, AccessTools.Method(typeof(RoundEndIgnorePatch), nameof(IsPlayerIgnored))),
new CodeInstruction(OpCodes.Brtrue_S, skip));

return matcher.InstructionEnumeration();
Expand Down
28 changes: 8 additions & 20 deletions SecretAPI/Patches/Features/RoundIgnoreCountPatch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Reflection.Emit;
using HarmonyLib;
using LabApi.Features.Console;
using LabApi.Features.Wrappers;
using Mirror;
using SecretAPI.Attributes;
Expand All @@ -25,14 +26,8 @@ internal static class RoundIgnoreCountPatch

private static MethodInfo TargetMethod()
{
Type nestedType = typeof(RoundSummary).GetNestedTypes(AccessTools.all)
.FirstOrDefault(currentType => currentType.Name is StateMachine) ?? throw new Exception("Could not locate state machine for RoundSummary::<>c");

// nestedType.GetMethods(AccessTools.all).ForEach(method => Logger.Debug(method.Name));
MethodInfo updateCountMethod = nestedType.GetMethods(AccessTools.all)
.FirstOrDefault(x => x.Name.Contains(UpdateTargetCount)) ?? throw new Exception($"Could not locate {UpdateTargetCount} method in state machine");

return updateCountMethod;
return typeof(RoundSummary).GetNestedMethod(StateMachine, UpdateTargetCount)
?? throw new Exception($"Could not locate state machine for {StateMachine} | {UpdateTargetCount}");
}

private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions, ILGenerator generator)
Expand All @@ -41,21 +36,14 @@ private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstructi
.Start()
.CreateLabel(out Label skip)
.Insert(
new CodeInstruction(OpCodes.Ldarg_0),
new CodeInstruction(OpCodes.Ldarg_1),
CodeInstruction.Call(typeof(RoundIgnoreCountPatch), nameof(IsPlayerIgnored)),
new CodeInstruction(OpCodes.Brtrue_S, skip));
new CodeInstruction(OpCodes.Brfalse_S, skip),
new CodeInstruction(OpCodes.Ldc_I4_0),
new CodeInstruction(OpCodes.Ret));

return matcher.InstructionEnumeration();
}

private static bool IsPlayerIgnored(ReferenceHub hub)
{
if (!NetworkServer.active || !Round.IsRoundStarted)
return false;

if (hub == null)
return false;

return hub && Player.Get(hub).RoundIgnoreStatus.HasFlagFast(RoundIgnoreStatus.ScpTargetCount);
}
private static bool IsPlayerIgnored(ReferenceHub hub) => Player.Get(hub).RoundIgnoreStatus.HasFlagFast(RoundIgnoreStatus.ScpTargetCount);
}
Loading