Stronger runtime checks in MultiplayerManager and Reflection
MultiplayerManager now uses Keen's wrapper wrapper (wrapper?) of SteamSDK Removed rogue file in the csproj
This commit is contained in:
@@ -16,6 +16,7 @@ using NLog;
|
|||||||
using Torch;
|
using Torch;
|
||||||
using Sandbox;
|
using Sandbox;
|
||||||
using Sandbox.Engine.Multiplayer;
|
using Sandbox.Engine.Multiplayer;
|
||||||
|
using Sandbox.Engine.Networking;
|
||||||
using Sandbox.Game.Entities.Character;
|
using Sandbox.Game.Entities.Character;
|
||||||
using Sandbox.Game.Multiplayer;
|
using Sandbox.Game.Multiplayer;
|
||||||
using Sandbox.Game.World;
|
using Sandbox.Game.World;
|
||||||
@@ -31,6 +32,7 @@ using VRage.Game.ModAPI;
|
|||||||
using VRage.GameServices;
|
using VRage.GameServices;
|
||||||
using VRage.Library.Collections;
|
using VRage.Library.Collections;
|
||||||
using VRage.Network;
|
using VRage.Network;
|
||||||
|
using VRage.Steam;
|
||||||
using VRage.Utils;
|
using VRage.Utils;
|
||||||
|
|
||||||
namespace Torch.Managers
|
namespace Torch.Managers
|
||||||
@@ -61,7 +63,7 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
internal MultiplayerManager(ITorchBase torch) : base(torch)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -166,11 +168,22 @@ namespace Torch.Managers
|
|||||||
ValidateOnlinePlayersList();
|
ValidateOnlinePlayersList();
|
||||||
|
|
||||||
//TODO: Move these with the methods?
|
//TODO: Move these with the methods?
|
||||||
RemoveHandlers();
|
if (!RemoveHandlers())
|
||||||
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
|
{
|
||||||
SteamServerAPI.Instance.GameServer.UserGroupStatus += UserGroupStatus;
|
Log.Error("Steam auth failed to initialize");
|
||||||
_members = (List<ulong>)typeof(MyDedicatedServerBase).GetField("m_members", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
return;
|
||||||
_waitingForGroup = (HashSet<ulong>)typeof(MyDedicatedServerBase).GetField("m_waitingForGroup", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(MyMultiplayer.Static);
|
}
|
||||||
|
MyGameService.GameServer.ValidateAuthTicketResponse += ValidateAuthTicketResponse;
|
||||||
|
MyGameService.GameServer.UserGroupStatusResponse += UserGroupStatusResponse;
|
||||||
|
_members = MyMultiplayer.Static.GetPrivateField<List<ulong>>("m_members", true);
|
||||||
|
if (_members == null)
|
||||||
|
throw new InvalidOperationException("Unable to get m_members from MyMultiplayer.Static. Is this a dedicated server?");
|
||||||
|
_waitingForGroup = MyMultiplayer.Static.GetPrivateField<HashSet<ulong>>("m_waitingForGroup", true);
|
||||||
|
if (_waitingForGroup == null)
|
||||||
|
throw new InvalidOperationException("Unable to get m_waitingForGroup from MyMultiplayer.Static. Is this a dedicated server?");
|
||||||
|
_kickedClients = MyMultiplayer.Static.GetPrivateField<Dictionary<ulong, int>>("m_kickedClients", true);
|
||||||
|
if (_kickedClients == null)
|
||||||
|
throw new InvalidOperationException("Unable to get m_kickedClients from MyMultiplayer.Static. Is this a dedicated server?");
|
||||||
Log.Info("Steam auth initialized");
|
Log.Info("Steam auth initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,6 +207,7 @@ namespace Torch.Managers
|
|||||||
//This lets us have a server set to private (admins only) or friends (friends of all listed admins)
|
//This lets us have a server set to private (admins only) or friends (friends of all listed admins)
|
||||||
private List<ulong> _members;
|
private List<ulong> _members;
|
||||||
private HashSet<ulong> _waitingForGroup;
|
private HashSet<ulong> _waitingForGroup;
|
||||||
|
private Dictionary<ulong, int> _kickedClients;
|
||||||
//private HashSet<ulong> _waitingForFriends;
|
//private HashSet<ulong> _waitingForFriends;
|
||||||
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
|
||||||
//private IMultiplayer _multiplayerImplementation;
|
//private IMultiplayer _multiplayerImplementation;
|
||||||
@@ -201,149 +215,116 @@ namespace Torch.Managers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Removes Keen's hooks into some Steam events so we have full control over client authentication
|
/// Removes Keen's hooks into some Steam events so we have full control over client authentication
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static void RemoveHandlers()
|
private static bool RemoveHandlers()
|
||||||
{
|
{
|
||||||
var eventField = typeof(GameServer).GetField("<backing_store>" + nameof(SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse), BindingFlags.NonPublic | BindingFlags.Instance);
|
MethodInfo methodValidateAuthTicket = typeof(MyDedicatedServerBase).GetMethod("GameServer_ValidateAuthTicketResponse",
|
||||||
if (eventField?.GetValue(SteamServerAPI.Instance.GameServer) is MulticastDelegate eventDel)
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
if (methodValidateAuthTicket == null)
|
||||||
{
|
{
|
||||||
foreach (var handle in eventDel.GetInvocationList())
|
Log.Error("Unable to find the GameServer_ValidateAuthTicketResponse method to unhook");
|
||||||
{
|
return false;
|
||||||
if (handle.Method.Name == "GameServer_ValidateAuthTicketResponse")
|
|
||||||
{
|
|
||||||
SteamServerAPI.Instance.GameServer.ValidateAuthTicketResponse -=
|
|
||||||
handle as ValidateAuthTicketResponse;
|
|
||||||
Log.Debug("Removed GameServer_ValidateAuthTicketResponse");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
var eventValidateAuthTicket = Reflection.GetEvents(MyGameService.GameServer, nameof(MyGameService.GameServer.ValidateAuthTicketResponse))
|
||||||
Log.Warn("Unable to unhook GameServer_ValidateAuthTicketResponse from the ValidateAuthTicketResponse event");
|
.FirstOrDefault(x => x.Method == methodValidateAuthTicket) as Action<ulong, JoinResult, ulong>;
|
||||||
|
if (eventValidateAuthTicket == null)
|
||||||
eventField = typeof(GameServer).GetField("<backing_store>" + nameof(SteamServerAPI.Instance.GameServer.UserGroupStatus), BindingFlags.NonPublic | BindingFlags.Instance);
|
|
||||||
eventDel = eventField?.GetValue(SteamServerAPI.Instance.GameServer) as MulticastDelegate;
|
|
||||||
if (eventDel != null)
|
|
||||||
{
|
{
|
||||||
foreach (var handle in eventDel.GetInvocationList())
|
Log.Error(
|
||||||
{
|
"Unable to unhook the GameServer_ValidateAuthTicketResponse method from GameServer.ValidateAuthTicketResponse");
|
||||||
if (handle.Method.Name == "GameServer_UserGroupStatus")
|
Log.Debug(" Want to unhook {0}", methodValidateAuthTicket);
|
||||||
{
|
Log.Debug(" Registered handlers: ");
|
||||||
SteamServerAPI.Instance.GameServer.UserGroupStatus -= handle as UserGroupStatus;
|
foreach (Delegate method in Reflection.GetEvents(MyGameService.GameServer,
|
||||||
Log.Debug("Removed GameServer_UserGroupStatus");
|
nameof(MyGameService.GameServer.ValidateAuthTicketResponse)))
|
||||||
}
|
Log.Debug(" - " + method.Method);
|
||||||
}
|
return false;
|
||||||
} else
|
}
|
||||||
Log.Warn("Unable to unhook GameServer_UserGroupStatus from the UserGroupStatusResponse event");
|
|
||||||
|
MethodInfo methodUserGroupStatus = typeof(MyDedicatedServerBase).GetMethod("GameServer_UserGroupStatus",
|
||||||
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
||||||
|
if (methodUserGroupStatus == null)
|
||||||
|
{
|
||||||
|
Log.Error("Unable to find the GameServer_UserGroupStatus method to unhook");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var eventUserGroupStatus = Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse))
|
||||||
|
.FirstOrDefault(x => x.Method == methodUserGroupStatus)
|
||||||
|
as Action<ulong, ulong, bool, bool>;
|
||||||
|
if (eventUserGroupStatus == null)
|
||||||
|
{
|
||||||
|
Log.Error("Unable to unhook the GameServer_UserGroupStatus method from GameServer.UserGroupStatus");
|
||||||
|
Log.Debug(" Want to unhook {0}", methodUserGroupStatus);
|
||||||
|
Log.Debug(" Registered handlers: ");
|
||||||
|
foreach (Delegate method in Reflection.GetInstanceEvent(MyGameService.GameServer, nameof(MyGameService.GameServer.UserGroupStatusResponse)))
|
||||||
|
Log.Debug(" - " + method.Method);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyGameService.GameServer.ValidateAuthTicketResponse -=
|
||||||
|
eventValidateAuthTicket;
|
||||||
|
MyGameService.GameServer.UserGroupStatusResponse -=
|
||||||
|
eventUserGroupStatus;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Largely copied from SE
|
//Largely copied from SE
|
||||||
private void ValidateAuthTicketResponse(ulong steamID, AuthSessionResponseEnum response, ulong ownerSteamID)
|
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
|
||||||
{
|
{
|
||||||
Log.Info($"Server ValidateAuthTicketResponse ({response}), owner: {ownerSteamID}");
|
Log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
|
||||||
|
if (IsClientBanned(steamOwner) || MySandboxGame.ConfigDedicated.Banned.Contains(steamOwner))
|
||||||
if (steamID != ownerSteamID)
|
|
||||||
{
|
{
|
||||||
Log.Info($"User {steamID} is using a game owned by {ownerSteamID}. Tracking...");
|
this.UserRejected(steamID, JoinResult.BannedByAdmins);
|
||||||
_gameOwnerIds[steamID] = ownerSteamID;
|
RaiseClientKicked(steamID);
|
||||||
|
|
||||||
if (MySandboxGame.ConfigDedicated.Banned.Contains(ownerSteamID))
|
|
||||||
{
|
|
||||||
Log.Info($"Game owner {ownerSteamID} is banned. Banning and rejecting client {steamID}...");
|
|
||||||
UserRejected(steamID, JoinResult.BannedByAdmins);
|
|
||||||
BanPlayer(steamID);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (IsClientKicked(steamOwner))
|
||||||
if (response == AuthSessionResponseEnum.OK)
|
|
||||||
{
|
{
|
||||||
if (MySession.Static.MaxPlayers > 0 && _members.Count - 1 >= MySession.Static.MaxPlayers)
|
UserRejected(steamID, JoinResult.KickedRecently);
|
||||||
{
|
RaiseClientKicked(steamID);
|
||||||
UserRejected(steamID, JoinResult.ServerFull);
|
|
||||||
}
|
|
||||||
else if (MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) /*|| MySandboxGame.ConfigDedicated.Administrators.Contains(MyDedicatedServerBase.ConvertSteamIDFrom64(steamID))*/)
|
|
||||||
{
|
|
||||||
UserAccepted(steamID);
|
|
||||||
}
|
|
||||||
else if (MySandboxGame.ConfigDedicated.GroupID == 0)
|
|
||||||
{
|
|
||||||
switch (MySession.Static.OnlineMode)
|
|
||||||
{
|
|
||||||
case MyOnlineModeEnum.PUBLIC:
|
|
||||||
UserAccepted(steamID);
|
|
||||||
break;
|
|
||||||
case MyOnlineModeEnum.PRIVATE:
|
|
||||||
UserRejected(steamID, JoinResult.NotInGroup);
|
|
||||||
break;
|
|
||||||
case MyOnlineModeEnum.FRIENDS:
|
|
||||||
//TODO: actually verify friendship
|
|
||||||
UserRejected(steamID, JoinResult.NotInGroup);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (SteamServerAPI.Instance.GetAccountType(MySandboxGame.ConfigDedicated.GroupID) != AccountType.Clan)
|
|
||||||
{
|
|
||||||
UserRejected(steamID, JoinResult.GroupIdInvalid);
|
|
||||||
}
|
|
||||||
else if (SteamServerAPI.Instance.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
|
||||||
{
|
|
||||||
// Returns false when there's no connection to Steam
|
|
||||||
_waitingForGroup.Add(steamID);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UserRejected(steamID, JoinResult.SteamServersOffline);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
if (response != JoinResult.OK)
|
||||||
{
|
{
|
||||||
JoinResult joinResult = JoinResult.TicketInvalid;
|
UserRejected(steamID, response);
|
||||||
switch (response)
|
return;
|
||||||
{
|
|
||||||
case AuthSessionResponseEnum.AuthTicketCanceled:
|
|
||||||
joinResult = JoinResult.TicketCanceled;
|
|
||||||
break;
|
|
||||||
case AuthSessionResponseEnum.AuthTicketInvalidAlreadyUsed:
|
|
||||||
joinResult = JoinResult.TicketAlreadyUsed;
|
|
||||||
break;
|
|
||||||
case AuthSessionResponseEnum.LoggedInElseWhere:
|
|
||||||
joinResult = JoinResult.LoggedInElseWhere;
|
|
||||||
break;
|
|
||||||
case AuthSessionResponseEnum.NoLicenseOrExpired:
|
|
||||||
joinResult = JoinResult.NoLicenseOrExpired;
|
|
||||||
break;
|
|
||||||
case AuthSessionResponseEnum.UserNotConnectedToSteam:
|
|
||||||
joinResult = JoinResult.UserNotConnected;
|
|
||||||
break;
|
|
||||||
case AuthSessionResponseEnum.VACBanned:
|
|
||||||
joinResult = JoinResult.VACBanned;
|
|
||||||
break;
|
|
||||||
case AuthSessionResponseEnum.VACCheckTimedOut:
|
|
||||||
joinResult = JoinResult.VACCheckTimedOut;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserRejected(steamID, joinResult);
|
|
||||||
}
|
}
|
||||||
|
if (MyMultiplayer.Static.MemberLimit > 0 && this._members.Count - 1 >= MyMultiplayer.Static.MemberLimit)
|
||||||
|
{
|
||||||
|
UserRejected(steamID, JoinResult.ServerFull);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
|
||||||
|
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
|
||||||
|
MySandboxGame.ConfigDedicated.Administrators.Contains((string)Reflection.InvokeStaticMethod(typeof(MyDedicatedServerBase), "ConvertSteamIDFrom64", steamID)))
|
||||||
|
{
|
||||||
|
this.UserAccepted(steamID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ((MyGameServiceAccountType)Reflection.InvokeStaticMethod(typeof(MyGameService), "GetServerAccountType", MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
|
||||||
|
{
|
||||||
|
this.UserRejected(steamID, JoinResult.GroupIdInvalid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
|
||||||
|
{
|
||||||
|
this._waitingForGroup.Add(steamID);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.UserRejected(steamID, JoinResult.SteamServersOffline);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserGroupStatus(ulong userId, ulong groupId, bool member, bool officer)
|
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
|
||||||
{
|
{
|
||||||
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Remove(userId))
|
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Remove(userId))
|
||||||
{
|
{
|
||||||
if (member || officer)
|
if (member || officer)
|
||||||
{
|
|
||||||
UserAccepted(userId);
|
UserAccepted(userId);
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
UserRejected(userId, JoinResult.NotInGroup);
|
UserRejected(userId, JoinResult.NotInGroup);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UserAccepted(ulong steamId)
|
private void UserAccepted(ulong steamId)
|
||||||
{
|
{
|
||||||
typeof(MyDedicatedServerBase).GetMethod("UserAccepted", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId});
|
Reflection.InvokePrivateMethod(MyMultiplayer.Static, "UserAccepted", steamId);
|
||||||
var vm = new PlayerViewModel(steamId) {State = ConnectionState.Connected};
|
|
||||||
|
var vm = new PlayerViewModel(steamId) { State = ConnectionState.Connected };
|
||||||
Log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
Log.Info($"Player {vm.Name} joined ({vm.SteamId})");
|
||||||
Players.Add(steamId, vm);
|
Players.Add(steamId, vm);
|
||||||
PlayerJoined?.Invoke(vm);
|
PlayerJoined?.Invoke(vm);
|
||||||
@@ -351,7 +332,22 @@ namespace Torch.Managers
|
|||||||
|
|
||||||
private void UserRejected(ulong steamId, JoinResult reason)
|
private void UserRejected(ulong steamId, JoinResult reason)
|
||||||
{
|
{
|
||||||
typeof(MyDedicatedServerBase).GetMethod("UserRejected", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MyMultiplayer.Static, new object[] {steamId, reason});
|
Reflection.InvokePrivateMethod(MyMultiplayer.Static, "UserRejected", steamId, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsClientBanned(ulong steamId)
|
||||||
|
{
|
||||||
|
return (bool)Reflection.InvokePrivateMethod(MyMultiplayer.Static, "IsClientBanned", steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsClientKicked(ulong steamId)
|
||||||
|
{
|
||||||
|
return (bool)Reflection.InvokePrivateMethod(MyMultiplayer.Static, "IsClientKicked", steamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RaiseClientKicked(ulong steamId)
|
||||||
|
{
|
||||||
|
Reflection.InvokePrivateMethod(MyMultiplayer.Static, "RaiseClientKicked", steamId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,127 +5,223 @@ using System.Reflection;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using NLog;
|
using NLog;
|
||||||
|
using SteamSDK;
|
||||||
|
|
||||||
namespace Torch
|
namespace Torch
|
||||||
{
|
{
|
||||||
public static class Reflection
|
public static class Reflection
|
||||||
{
|
{
|
||||||
private static readonly Logger Log = LogManager.GetLogger("Reflection");
|
private static readonly Logger Log = LogManager.GetLogger("Reflection");
|
||||||
|
|
||||||
public static bool HasMethod(Type type, string methodName, Type[] argTypes = null)
|
public static bool HasMethod(Type type, string methodName, Type[] argTypes = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(methodName))
|
if (string.IsNullOrEmpty(methodName))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (argTypes == null)
|
if (argTypes == null)
|
||||||
{
|
{
|
||||||
var methodInfo = type.GetMethod(methodName);
|
var methodInfo = type.GetMethod(methodName);
|
||||||
if (methodInfo == null)
|
if (methodInfo == null)
|
||||||
methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
methodInfo = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||||
if (methodInfo == null && type.BaseType != null)
|
if (methodInfo == null && type.BaseType != null)
|
||||||
methodInfo = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
methodInfo = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||||
if (methodInfo == null)
|
if (methodInfo == null)
|
||||||
{
|
{
|
||||||
Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "'" );
|
Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "'");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MethodInfo method = type.GetMethod(methodName, argTypes);
|
MethodInfo method = type.GetMethod(methodName, argTypes);
|
||||||
if (method == null)
|
if (method == null)
|
||||||
method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null);
|
method = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null);
|
||||||
if (method == null && type.BaseType != null)
|
if (method == null && type.BaseType != null)
|
||||||
method = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null);
|
method = type.BaseType.GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy, Type.DefaultBinder, argTypes, null);
|
||||||
if (method == null)
|
if (method == null)
|
||||||
{
|
{
|
||||||
Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "'" );
|
Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "'");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (AmbiguousMatchException)
|
catch (AmbiguousMatchException)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error( "Failed to find method '" + methodName + "' in type '" + type.FullName + "': " + ex.Message );
|
Log.Error("Failed to find method '" + methodName + "' in type '" + type.FullName + "': " + ex.Message);
|
||||||
Log.Error( ex );
|
Log.Error(ex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool HasField(Type type, string fieldName)
|
public static bool HasField(Type type, string fieldName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(fieldName))
|
if (string.IsNullOrEmpty(fieldName))
|
||||||
return false;
|
return false;
|
||||||
var field = type.GetField(fieldName);
|
var field = type.GetField(fieldName);
|
||||||
if (field == null)
|
if (field == null)
|
||||||
field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||||
if (field == null)
|
if (field == null)
|
||||||
field = type.BaseType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
field = type.BaseType.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||||
if (field == null)
|
if (field == null)
|
||||||
{
|
{
|
||||||
Log.Error("Failed to find field '{0}' in type '{1}'", fieldName, type.FullName);
|
Log.Error("Failed to find field '{0}' in type '{1}'", fieldName, type.FullName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Failed to find field '{0}' in type '{1}'", fieldName, type.FullName);
|
Log.Error(ex, "Failed to find field '{0}' in type '{1}'", fieldName, type.FullName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool HasProperty(Type type, string propertyName)
|
public static bool HasProperty(Type type, string propertyName)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(propertyName))
|
if (string.IsNullOrEmpty(propertyName))
|
||||||
return false;
|
return false;
|
||||||
var prop = type.GetProperty(propertyName);
|
var prop = type.GetProperty(propertyName);
|
||||||
if (prop == null)
|
if (prop == null)
|
||||||
prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
prop = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||||
if (prop == null)
|
if (prop == null)
|
||||||
prop = type.BaseType?.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
prop = type.BaseType?.GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.FlattenHierarchy);
|
||||||
if (prop == null)
|
if (prop == null)
|
||||||
{
|
{
|
||||||
Log.Error("Failed to find property '{0}' in type '{1}'", propertyName, type.FullName);
|
Log.Error("Failed to find property '{0}' in type '{1}'", propertyName, type.FullName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Failed to find property '{0}' in type '{1}'", propertyName, type.FullName);
|
Log.Error(ex, "Failed to find property '{0}' in type '{1}'", propertyName, type.FullName);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static object InvokeStaticMethod(Type type, string methodName, params object[] args)
|
/// <summary>
|
||||||
{
|
/// Invokes the static method of the given type, with the given arguments.
|
||||||
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
/// </summary>
|
||||||
if (method == null)
|
/// <param name="type">Type the method is contained in</param>
|
||||||
{
|
/// <param name="methodName">Method name</param>
|
||||||
|
/// <param name="args">Arguments to the method</param>
|
||||||
|
/// <returns>return value of the invoked method, or null if it failed</returns>
|
||||||
|
public static object InvokeStaticMethod(Type type, string methodName, params object[] args)
|
||||||
|
{
|
||||||
|
var method = type.GetMethod(methodName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
if (method == null)
|
||||||
|
{
|
||||||
Log.Error($"Method {methodName} not found in static class {type.FullName}");
|
Log.Error($"Method {methodName} not found in static class {type.FullName}");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return method.Invoke(null, args);
|
return method.Invoke(null, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static T GetPrivateField<T>(this object obj, string fieldName)
|
/// <summary>
|
||||||
{
|
/// Invokes the private method with the given arguments on the instance. Includes base types of instance.
|
||||||
var type = obj.GetType();
|
/// </summary>
|
||||||
return (T)type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(obj);
|
/// <param name="instance"></param>
|
||||||
}
|
/// <param name="methodName"></param>
|
||||||
}
|
/// <param name="args"></param>
|
||||||
|
/// <returns>the return value of the method, or null if it failed</returns>
|
||||||
|
public static object InvokePrivateMethod(object instance, string methodName, params object[] args)
|
||||||
|
{
|
||||||
|
Type type = instance.GetType();
|
||||||
|
while (type != null)
|
||||||
|
{
|
||||||
|
MethodInfo method = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
if (method != null)
|
||||||
|
return method.Invoke(instance, args);
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.Error($"Method {methodName} not found in type {instance.GetType().FullName} or its parents");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the value of a private field in an instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the private field</typeparam>
|
||||||
|
/// <param name="obj">The instance</param>
|
||||||
|
/// <param name="fieldName">Field name</param>
|
||||||
|
/// <param name="recurse">Should the base types be examined</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static T GetPrivateField<T>(this object obj, string fieldName, bool recurse = false)
|
||||||
|
{
|
||||||
|
var type = obj.GetType();
|
||||||
|
while (type != null)
|
||||||
|
{
|
||||||
|
FieldInfo field = type.GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
if (field != null)
|
||||||
|
return (T)field
|
||||||
|
.GetValue(obj);
|
||||||
|
|
||||||
|
if (!recurse)
|
||||||
|
break;
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
Log.Error($"Field {fieldName} not found in type {obj.GetType().FullName}" + (recurse ? " or its parents" : ""));
|
||||||
|
return default(T);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of all delegates registered in the named static event
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="type">The type (or child type) that contains the event</param>
|
||||||
|
/// <param name="eventName">Name of the event</param>
|
||||||
|
/// <returns>All delegates registered with the event</returns>
|
||||||
|
public static IEnumerable<Delegate> GetStaticEvent(Type type, string eventName)
|
||||||
|
{
|
||||||
|
return GetEventsInternal(null, eventName, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of all delegates registered in the named event
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="instance">Instance to retrieve the event list for</param>
|
||||||
|
/// <param name="eventName">Name of the event</param>
|
||||||
|
/// <returns>All delegates registered with the event</returns>
|
||||||
|
public static IEnumerable<Delegate> GetInstanceEvent(object instance, string eventName)
|
||||||
|
{
|
||||||
|
return GetEventsInternal(instance, eventName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly string[] _backingFieldForEvent = { "{0}", "<backing_store>{0}" };
|
||||||
|
private static IEnumerable<Delegate> GetEventsInternal(object instance, string eventName, Type baseType = null)
|
||||||
|
{
|
||||||
|
BindingFlags bindingFlags = BindingFlags.NonPublic |
|
||||||
|
(instance == null ? BindingFlags.Static : BindingFlags.Instance);
|
||||||
|
|
||||||
|
FieldInfo eventField = null;
|
||||||
|
baseType = baseType ?? instance?.GetType();
|
||||||
|
Type type = baseType;
|
||||||
|
while (type != null && eventField == null)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _backingFieldForEvent.Length && eventField == null; i++)
|
||||||
|
eventField = type.GetField(string.Format(_backingFieldForEvent[i], eventName), bindingFlags);
|
||||||
|
type = type.BaseType;
|
||||||
|
}
|
||||||
|
if (eventField?.GetValue(instance) is MulticastDelegate eventDel)
|
||||||
|
{
|
||||||
|
foreach (Delegate handle in eventDel.GetInvocationList())
|
||||||
|
yield return handle;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Log.Error($"{eventName} doesn't have a backing store in {baseType} or its parents.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -181,7 +181,6 @@
|
|||||||
<Compile Include="TorchBase.cs" />
|
<Compile Include="TorchBase.cs" />
|
||||||
<Compile Include="SteamService.cs" />
|
<Compile Include="SteamService.cs" />
|
||||||
<Compile Include="TorchPluginBase.cs" />
|
<Compile Include="TorchPluginBase.cs" />
|
||||||
<Compile Include="TorchSession.cs" />
|
|
||||||
<Compile Include="ViewModels\ModViewModel.cs" />
|
<Compile Include="ViewModels\ModViewModel.cs" />
|
||||||
<Compile Include="Collections\MTObservableCollection.cs" />
|
<Compile Include="Collections\MTObservableCollection.cs" />
|
||||||
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
<Compile Include="Extensions\MyPlayerCollectionExtensions.cs" />
|
||||||
|
Reference in New Issue
Block a user