Skip to content

Commit

Permalink
Added Feature: "AntiSpawn"
Browse files Browse the repository at this point in the history
Prevents clients from spawning enemies

PlayerLobbyManagement:
- exposed kick, ban  and unban methods
  • Loading branch information
AuriRex committed Apr 24, 2023
1 parent caa42b4 commit 4d8482d
Show file tree
Hide file tree
Showing 3 changed files with 251 additions and 10 deletions.
48 changes: 38 additions & 10 deletions TheArchive.IL2CPP/Features/PlayerLobbyManagement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public static PlayerRelationShip GetRelationship(SNet_Player player)
if (IsPlayerBanned(player.Lookup))
return PlayerRelationShip.Banned;

if (SNet.Friends.TryGetFriend(player.Lookup, out _))
if (player.IsFriend())
return PlayerRelationShip.Friend;

return PlayerRelationShip.None;
Expand Down Expand Up @@ -323,6 +323,21 @@ internal static void BanPlayerButtonPressed(int playerID)

PopupWindow.SetVisible(false);

if (!IsPlayerBanned(player.Lookup))
{
BanPlayer(player);
}
else
{
UnbanPlayer(player);
}
}

public static bool BanPlayer(SNet_Player player, bool kickPlayer = true)
{
if (player == null)
return false;

if (!IsPlayerBanned(player.Lookup))
{
Settings.BanList.Add(new LobbyManagementSettings.BanListEntry
Expand All @@ -333,20 +348,33 @@ internal static void BanPlayerButtonPressed(int playerID)
});
FeatureLogger.Fail($"Player has been added to list of banned players: Name:\"{player.GetName()}\" SteamID:\"{player.Lookup}\"");

KickPlayer(player);
if (kickPlayer)
KickPlayer(player);
return true;
}
else

return false;
}

public static bool UnbanPlayer(SNet_Player player) => UnbanPlayer(player?.Lookup ?? ulong.MaxValue);

public static bool UnbanPlayer(ulong playerID)
{
if (playerID == ulong.MaxValue)
return false;

var playerToUnban = Settings.BanList.FirstOrDefault(entry => entry.SteamID == playerID);
if (playerToUnban != null)
{
var playerToUnban = Settings.BanList.FirstOrDefault(entry => entry.SteamID == player.Lookup);
if (playerToUnban != null)
{
Settings.BanList.Remove(playerToUnban);
FeatureLogger.Success($"Player has been removed from the list of banned players: Name:\"{player.GetName()}\" SteamID:\"{player.Lookup}\"");
}
Settings.BanList.Remove(playerToUnban);
FeatureLogger.Success($"Player has been removed from the list of banned players: Name:\"{playerToUnban.Name}\" SteamID:\"{playerToUnban.SteamID}\"");
return true;
}

return false;
}

private static void KickPlayer(SNet_Player player)
public static void KickPlayer(SNet_Player player)
{
if (!SNet.IsMaster)
return;
Expand Down
206 changes: 206 additions & 0 deletions TheArchive.IL2CPP/Features/Special/AntiSpawn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
using Enemies;
using SNetwork;
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using TheArchive.Core.Attributes;
using TheArchive.Core.Attributes.Feature.Settings;
using TheArchive.Core.FeaturesAPI;
using TheArchive.Interfaces;
using TheArchive.Loader;
using TheArchive.Utilities;

namespace TheArchive.Features.Special
{
#if BepInEx
[ForceDisable] // LoaderWrapper.NativeHookAttach isn't implemented for BIE yet
#endif
[EnableFeatureByDefault]
public class AntiSpawn : Feature
{
public override string Name => "Anti Spawn";

public override string Group => FeatureGroups.Special;

public override string Description => "Prevents clients from spawning in enemies.";

public override bool PlaceSettingsInSubMenu => true;

public new static IArchiveLogger FeatureLogger { get; set; }

public static bool IsEnabled { get; set; }

[FeatureConfig]
public static AntiSpawnSettings Settings { get; set; }

public class AntiSpawnSettings
{
[FSDisplayName("Punish Friends")]
[FSDescription("If (Steam) Friends should be affected as well.")]
public bool PunishFriends { get; set; } = false;

[FSDisplayName("Punishment")]
[FSDescription("What to do with griefers that are trying to spawn in enemies.")]
public PunishmentMode Punishment { get; set; } = PunishmentMode.Kick;

public enum PunishmentMode
{
NoneAndLog,
Kick,
KickAndBan
}
}

public override void OnEnable()
{
OneTimePatch();
}

#warning TODO: Refactor after ML upgrade to Il2CppInterop
private static unsafe TDelegate ApplyNativePatch<TToPatch, TDelegate>(MethodInfo patchMethod, string originalMethodName, Type[] parameterTypes = null) where TDelegate : Delegate
{
var ptr = GetMethodPointerFromGenericType<TToPatch>(originalMethodName, parameterTypes);

var patch = patchMethod.MethodHandle.GetFunctionPointer();

FeatureLogger.Debug($"Attaching Native Patch ...");
FeatureLogger.Debug($"Info: {nameof(GetMethodPointerFromGenericType)}: {typeof(TToPatch).FullName} | {originalMethodName} (Ptr:{ptr})");

//calls MelonUtils.NativeHookAttach((IntPtr)(&ptr), patch);
LoaderWrapper.NativeHookAttach((IntPtr)(&ptr), patch);

var del = (TDelegate)Marshal.GetDelegateForFunctionPointer(ptr, typeof(TDelegate));

FeatureLogger.Debug($"OG Ptr: {ptr}");
FeatureLogger.Debug($"Patch Ptr: {patch}");
FeatureLogger.Debug($"Delegate Ptr: {del.Method.MethodHandle.GetFunctionPointer()}");

return del;
}

private static unsafe IntPtr GetMethodPointerFromGenericType<T>(string originalMethodName, Type[] parameterTypes = null)
{
var constructedGenericType = typeof(T);

MethodInfo originalMethodInfo;

if(parameterTypes != null)
{
originalMethodInfo = constructedGenericType
.GetMethod(originalMethodName, Utils.AnyBindingFlagss, null, parameterTypes, null);
}
else
{
originalMethodInfo = constructedGenericType
.GetMethod(originalMethodName, Utils.AnyBindingFlagss);
}

#if Unhollower
var field = UnhollowerBaseLib.UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(originalMethodInfo);
#elif Il2CppInterop
var field = Il2CppInterop.Common.Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(originalMethodInfo);
#else
FieldInfo field = null;
throw new NotImplementedException();
#endif

// does not work at all if one does not resolve the field from the generic type
field = constructedGenericType.GetField(field.Name, Utils.AnyBindingFlagss);

var ptr = *(IntPtr*)(IntPtr)field.GetValue(null);

return ptr;
}

private static bool _hasBeenPatched = false;
private static unsafe void OneTimePatch()
{
if (_hasBeenPatched)
return;

//_originalMethod_InternalSpawnRequestFromSlaveCallback = ApplyNativePatch<SNet_ReplicationManager<pEnemySpawnData, EnemyReplicator>, Original_InternalSpawnRequestFromSlaveCallback>(typeof(AntiSpawn).GetMethod(nameof(InternalSpawnRequestFromSlaveCallback_Replacement)), "InternalSpawnRequestFromSlaveCallback");

_originalMethod_InternalSpawnCallback = ApplyNativePatch<SNet_ReplicationManager<pEnemySpawnData, EnemyReplicator>, Original_InternalSpawnCallback>(typeof(AntiSpawn).GetMethod(nameof(InternalSpawnCallback_Replacement)), "InternalSpawnCallback");
_originalMethod_Spawn = ApplyNativePatch<SNet_ReplicationManager<pEnemySpawnData, EnemyReplicator>, Original_Spawn>(typeof(AntiSpawn).GetMethod(nameof(Spawn_Replacement)), "Spawn", new Type[] { typeof(pEnemySpawnData) });

_hasBeenPatched = true;
}

/*
private static Original_InternalSpawnRequestFromSlaveCallback _originalMethod_InternalSpawnRequestFromSlaveCallback;
public delegate void Original_InternalSpawnRequestFromSlaveCallback(IntPtr self, IntPtr spawnData);
public static void InternalSpawnRequestFromSlaveCallback_Replacement(IntPtr self, IntPtr spawnData)
{
var lastSenderIsMaster = SNet.Replication.IsLastSenderMaster();
if (IsEnabled)
{
FeatureLogger.Fail($"Intercepted enemy spawn request packet from client! lastSenderIsMaster: {lastSenderIsMaster}");
}
else
{
_originalMethod_InternalSpawnRequestFromSlaveCallback.Invoke(self, spawnData);
}
}
*/

internal static bool isLocallySpawned = false;
private static Original_Spawn _originalMethod_Spawn;
public delegate void Original_Spawn(IntPtr type, IntPtr self, IntPtr spawnData);
public static void Spawn_Replacement(IntPtr type, IntPtr self, IntPtr spawnData)
{
isLocallySpawned = true;
_originalMethod_Spawn.Invoke(type, self, spawnData);
isLocallySpawned = false;
}

private static Original_InternalSpawnCallback _originalMethod_InternalSpawnCallback;
public delegate void Original_InternalSpawnCallback(IntPtr type, IntPtr self, IntPtr spawnData);
public static void InternalSpawnCallback_Replacement(IntPtr type, IntPtr self, IntPtr spawnData)
{
if (IsEnabled && SNet.IsMaster && !isLocallySpawned)
{
bool cancelSpawn = true;

if (SNet.Replication.TryGetLastSender(out var sender))
{
cancelSpawn = PunishPlayer(sender);
}

if (cancelSpawn)
{
FeatureLogger.Fail("Cancelled enemy spawn!");
return;
}
}

_originalMethod_InternalSpawnCallback.Invoke(type, self, spawnData);
}

public static bool PunishPlayer(SNet_Player player)
{
if (player == null)
return true;

if (player.IsFriend() && !Settings.PunishFriends)
{
FeatureLogger.Notice($"Friend \"{player.NickName}\" is spawning something in!");
return false;
}

switch(Settings.Punishment)
{
case AntiSpawnSettings.PunishmentMode.KickAndBan:
PlayerLobbyManagement.BanPlayer(player);
goto default;
case AntiSpawnSettings.PunishmentMode.Kick:
PlayerLobbyManagement.KickPlayer(player);
goto default;
default:
case AntiSpawnSettings.PunishmentMode.NoneAndLog:
FeatureLogger.Notice($"Player \"{player.NickName}\" tried to spawn something! ({Settings.Punishment})");
return true;
}
}
}
}
7 changes: 7 additions & 0 deletions TheArchive.IL2CPP/Utilities/SharedUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,13 @@ public static bool SafeIsBot(this SNetwork.SNet_Player player)
return false;
}

public static bool IsFriend(this SNetwork.SNet_Player player)
{
if (player == null)
return false;
return SNetwork.SNet.Friends.TryGetFriend(player.Lookup, out _);
}

[MethodImpl(MethodImplOptions.NoInlining)]
private static bool IsBotR6(SNetwork.SNet_Player player)
{
Expand Down

0 comments on commit 4d8482d

Please sign in to comment.