Skip to content

Commit 1b0dcc9

Browse files
committed
Validate auth ticket event exposed to other parts of Torch and plugins
1 parent fe5dfa0 commit 1b0dcc9

7 files changed

Lines changed: 232 additions & 65 deletions

File tree

‎Torch.API/Event/IEventManager.cs‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
using System.Runtime.CompilerServices;
2+
using Torch.API.Managers;
23

34
namespace Torch.API.Event
45
{
56
/// <summary>
67
/// Manager class responsible for registration of event handlers.
78
/// </summary>
8-
public interface IEventManager
9+
public interface IEventManager : IManager
910
{
1011
/// <summary>
1112
/// Registers all event handler methods contained in the given instance

‎Torch.Server/Managers/MultiplayerManagerDedicated.cs‎

Lines changed: 118 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ public class MultiplayerManagerDedicated : MultiplayerManagerBase, IMultiplayerM
2525
private static readonly Logger _log = LogManager.GetCurrentClassLogger();
2626

2727
#pragma warning disable 649
28-
[ReflectedGetter(Name = "m_members")]
29-
private static Func<MyDedicatedServerBase, List<ulong>> _members;
28+
[ReflectedGetter(Name = "m_members")] private static Func<MyDedicatedServerBase, List<ulong>> _members;
3029
[ReflectedGetter(Name = "m_waitingForGroup")]
3130
private static Func<MyDedicatedServerBase, HashSet<ulong>> _waitingForGroup;
3231
#pragma warning restore 649
@@ -37,7 +36,9 @@ public class MultiplayerManagerDedicated : MultiplayerManagerBase, IMultiplayerM
3736
private Dictionary<ulong, ulong> _gameOwnerIds = new Dictionary<ulong, ulong>();
3837

3938
/// <inheritdoc />
40-
public MultiplayerManagerDedicated(ITorchBase torch) : base(torch) { }
39+
public MultiplayerManagerDedicated(ITorchBase torch) : base(torch)
40+
{
41+
}
4142

4243
/// <inheritdoc />
4344
public void KickPlayer(ulong steamId) => Torch.Invoke(() => MyMultiplayer.Static.KickClient(steamId));
@@ -54,16 +55,19 @@ public void BanPlayer(ulong steamId, bool banned = true)
5455
}
5556

5657
/// <inheritdoc />
57-
public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) || MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
58+
public bool IsBanned(ulong steamId) => _isClientBanned.Invoke(MyMultiplayer.Static, steamId) ||
59+
MySandboxGame.ConfigDedicated.Banned.Contains(steamId);
5860

5961
/// <inheritdoc/>
6062
public override void Attach()
6163
{
6264
base.Attach();
6365
_gameServerValidateAuthTicketReplacer = _gameServerValidateAuthTicketFactory.Invoke();
6466
_gameServerUserGroupStatusReplacer = _gameServerUserGroupStatusFactory.Invoke();
65-
_gameServerValidateAuthTicketReplacer.Replace(new Action<ulong, JoinResult, ulong>(ValidateAuthTicketResponse), MyGameService.GameServer);
66-
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse), MyGameService.GameServer);
67+
_gameServerValidateAuthTicketReplacer.Replace(
68+
new Action<ulong, JoinResult, ulong>(ValidateAuthTicketResponse), MyGameService.GameServer);
69+
_gameServerUserGroupStatusReplacer.Replace(new Action<ulong, ulong, bool, bool>(UserGroupStatusResponse),
70+
MyGameService.GameServer);
6771
_log.Info("Inserted steam authentication intercept");
6872
}
6973

@@ -80,94 +84,151 @@ public override void Detach()
8084

8185

8286
#pragma warning disable 649
83-
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse), typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
87+
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.ValidateAuthTicketResponse),
88+
typeof(MyDedicatedServerBase), "GameServer_ValidateAuthTicketResponse")]
8489
private static Func<ReflectedEventReplacer> _gameServerValidateAuthTicketFactory;
85-
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse), typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
90+
91+
[ReflectedEventReplace(typeof(MySteamGameServer), nameof(MySteamGameServer.UserGroupStatusResponse),
92+
typeof(MyDedicatedServerBase), "GameServer_UserGroupStatus")]
8693
private static Func<ReflectedEventReplacer> _gameServerUserGroupStatusFactory;
94+
8795
private ReflectedEventReplacer _gameServerValidateAuthTicketReplacer;
8896
private ReflectedEventReplacer _gameServerUserGroupStatusReplacer;
8997
#pragma warning restore 649
9098

9199
#region CustomAuth
100+
92101
#pragma warning disable 649
93102
[ReflectedStaticMethod(Type = typeof(MyDedicatedServerBase), Name = "ConvertSteamIDFrom64")]
94103
private static Func<ulong, string> _convertSteamIDFrom64;
95104

96105
[ReflectedStaticMethod(Type = typeof(MyGameService), Name = "GetServerAccountType")]
97106
private static Func<ulong, MyGameServiceAccountType> _getServerAccountType;
98107

99-
[ReflectedMethod(Name = "UserAccepted")]
100-
private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
108+
[ReflectedMethod(Name = "UserAccepted")] private static Action<MyDedicatedServerBase, ulong> _userAcceptedImpl;
101109

102110
[ReflectedMethod(Name = "UserRejected")]
103111
private static Action<MyDedicatedServerBase, ulong, JoinResult> _userRejected;
104-
[ReflectedMethod(Name = "IsClientBanned")]
105-
private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
106-
[ReflectedMethod(Name = "IsClientKicked")]
107-
private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
112+
113+
[ReflectedMethod(Name = "IsClientBanned")] private static Func<MyMultiplayerBase, ulong, bool> _isClientBanned;
114+
[ReflectedMethod(Name = "IsClientKicked")] private static Func<MyMultiplayerBase, ulong, bool> _isClientKicked;
115+
108116
[ReflectedMethod(Name = "RaiseClientKicked")]
109117
private static Action<MyMultiplayerBase, ulong> _raiseClientKicked;
110118
#pragma warning restore 649
111119

120+
private const int _waitListSize = 32;
121+
private readonly List<WaitingForGroup> _waitingForGroupLocal = new List<WaitingForGroup>(_waitListSize);
122+
123+
private struct WaitingForGroup
124+
{
125+
public readonly ulong SteamId;
126+
public readonly JoinResult Response;
127+
public readonly ulong SteamOwner;
128+
129+
public WaitingForGroup(ulong id, JoinResult response, ulong owner)
130+
{
131+
SteamId = id;
132+
Response = response;
133+
SteamOwner = owner;
134+
}
135+
}
136+
112137
//Largely copied from SE
113138
private void ValidateAuthTicketResponse(ulong steamID, JoinResult response, ulong steamOwner)
114139
{
115140
_log.Debug($"ValidateAuthTicketResponse(user={steamID}, response={response}, owner={steamOwner}");
116-
if (IsBanned(steamOwner))
117-
{
118-
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.BannedByAdmins);
119-
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
120-
}
121-
else if (_isClientKicked.Invoke(MyMultiplayer.Static, steamOwner))
122-
{
123-
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.KickedRecently);
124-
_raiseClientKicked.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID);
125-
}
126-
if (response != JoinResult.OK)
127-
{
128-
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, response);
129-
return;
130-
}
131-
if (MyMultiplayer.Static.MemberLimit > 0 && _members.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Count - 1 >= MyMultiplayer.Static.MemberLimit)
132-
{
133-
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.ServerFull);
134-
return;
135-
}
136-
if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
137-
MySandboxGame.ConfigDedicated.Administrators.Contains(steamID.ToString()) ||
138-
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(steamID)))
139-
{
140-
this.UserAccepted(steamID);
141-
return;
142-
}
143-
if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
141+
if (MySandboxGame.ConfigDedicated.GroupID == 0uL)
142+
RunEvent(new ValidateAuthTicketEvent(steamID, steamOwner, response, 0, true, false));
143+
else if (_getServerAccountType(MySandboxGame.ConfigDedicated.GroupID) != MyGameServiceAccountType.Clan)
144+
UserRejected(steamID, JoinResult.GroupIdInvalid);
145+
else if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
146+
lock (_waitingForGroupLocal)
147+
{
148+
if (_waitingForGroupLocal.Count >= _waitListSize)
149+
_waitingForGroupLocal.RemoveAt(0);
150+
_waitingForGroupLocal.Add(new WaitingForGroup(steamID, response, steamOwner));
151+
}
152+
else
153+
UserRejected(steamID, JoinResult.SteamServersOffline);
154+
}
155+
156+
private void RunEvent(ValidateAuthTicketEvent info)
157+
{
158+
MultiplayerManagerDedicatedEventShim.RaiseValidateAuthTicket(ref info);
159+
160+
if (info.FutureVerdict == null)
144161
{
145-
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.GroupIdInvalid);
162+
if (IsBanned(info.SteamOwner) || IsBanned(info.SteamID))
163+
CommitVerdict(info.SteamID, JoinResult.BannedByAdmins);
164+
else if (_isClientKicked(MyMultiplayer.Static, info.SteamID) ||
165+
_isClientKicked(MyMultiplayer.Static, info.SteamOwner))
166+
CommitVerdict(info.SteamID, JoinResult.KickedRecently);
167+
else if (info.SteamResponse != JoinResult.OK)
168+
CommitVerdict(info.SteamID, info.SteamResponse);
169+
else if (MyMultiplayer.Static.MemberLimit > 0 &&
170+
MyMultiplayer.Static.MemberCount + 1 > MyMultiplayer.Static.MemberLimit)
171+
CommitVerdict(info.SteamID, JoinResult.ServerFull);
172+
else if (MySandboxGame.ConfigDedicated.GroupID == 0uL ||
173+
MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString()) ||
174+
MySandboxGame.ConfigDedicated.Administrators.Contains(_convertSteamIDFrom64(info.SteamID)))
175+
CommitVerdict(info.SteamID, JoinResult.OK);
176+
else if (MySandboxGame.ConfigDedicated.GroupID == info.Group && (info.Member || info.Officer))
177+
CommitVerdict(info.SteamID, JoinResult.OK);
178+
else
179+
CommitVerdict(info.SteamID, JoinResult.NotInGroup);
146180
return;
147181
}
148-
if (MyGameService.GameServer.RequestGroupStatus(steamID, MySandboxGame.ConfigDedicated.GroupID))
182+
183+
info.FutureVerdict.ContinueWith((task) =>
149184
{
150-
_waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Add(steamID);
151-
return;
152-
}
153-
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamID, JoinResult.SteamServersOffline);
185+
JoinResult verdict;
186+
if (task.IsFaulted)
187+
{
188+
_log.Error(task.Exception, $"Future validation verdict faulted");
189+
verdict = JoinResult.TicketCanceled;
190+
}
191+
else
192+
verdict = task.Result;
193+
Torch.Invoke(() => { CommitVerdict(info.SteamID, verdict); });
194+
});
195+
}
196+
197+
private void CommitVerdict(ulong steamId, JoinResult verdict)
198+
{
199+
if (verdict == JoinResult.OK)
200+
UserAccepted(steamId);
201+
else
202+
UserRejected(steamId, verdict);
154203
}
155204

156205
private void UserGroupStatusResponse(ulong userId, ulong groupId, bool member, bool officer)
157206
{
158-
if (groupId == MySandboxGame.ConfigDedicated.GroupID && _waitingForGroup.Invoke((MyDedicatedServerBase)MyMultiplayer.Static).Remove(userId))
159-
{
160-
if (member || officer)
161-
UserAccepted(userId);
162-
else
163-
_userRejected.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, userId, JoinResult.NotInGroup);
164-
}
207+
lock (_waitingForGroupLocal)
208+
for (var j = 0; j < _waitingForGroupLocal.Count; j++)
209+
{
210+
var wait = _waitingForGroupLocal[j];
211+
if (wait.SteamId == userId)
212+
{
213+
RunEvent(new ValidateAuthTicketEvent(wait.SteamId, wait.SteamOwner, wait.Response, groupId,
214+
member, officer));
215+
_waitingForGroupLocal.RemoveAt(j);
216+
break;
217+
}
218+
}
219+
}
220+
221+
private void UserRejected(ulong steamId, JoinResult reason)
222+
{
223+
_userRejected.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId, reason);
165224
}
225+
166226
private void UserAccepted(ulong steamId)
167227
{
168-
_userAcceptedImpl.Invoke((MyDedicatedServerBase)MyMultiplayer.Static, steamId);
228+
_userAcceptedImpl.Invoke((MyDedicatedServerBase) MyMultiplayer.Static, steamId);
169229
base.RaiseClientJoined(steamId);
170230
}
231+
171232
#endregion
172233
}
173-
}
234+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
using NLog;
7+
using Sandbox;
8+
using Torch.API.Event;
9+
using Torch.Event;
10+
using VRage.Network;
11+
12+
namespace Torch.Server.Managers
13+
{
14+
[EventShim]
15+
internal static class MultiplayerManagerDedicatedEventShim
16+
{
17+
private static readonly EventList<ValidateAuthTicketEvent> _eventValidateAuthTicket =
18+
new EventList<ValidateAuthTicketEvent>();
19+
20+
21+
internal static void RaiseValidateAuthTicket(ref ValidateAuthTicketEvent info)
22+
{
23+
_eventValidateAuthTicket?.RaiseEvent(ref info);
24+
}
25+
}
26+
27+
// class AdminsCanJoinExample : IEventHandler
28+
// {
29+
// [EventHandler(SkipCancelled = false)]
30+
// public void Handle(ref ValidateAuthTicketEvent info)
31+
// {
32+
// if (MySandboxGame.ConfigDedicated.Administrators.Contains(info.SteamID.ToString())
33+
// || (info.ServerGroup == MySandboxGame.ConfigDedicated.GroupID && info.Officer))
34+
// info.Verdict = JoinResult.OK;
35+
// }
36+
// }
37+
38+
/// <summary>
39+
/// Event that occurs when a player tries to connect to the server.
40+
/// Use these values to choose a <see cref="ValidateAuthTicketEvent.Verdict"/>,
41+
/// or leave it unset to allow the default logic to handle the request.
42+
/// </summary>
43+
public struct ValidateAuthTicketEvent : IEvent
44+
{
45+
/// <summary>
46+
/// SteamID of the player
47+
/// </summary>
48+
public readonly ulong SteamID;
49+
50+
/// <summary>
51+
/// SteamID of the game owner
52+
/// </summary>
53+
public readonly ulong SteamOwner;
54+
55+
/// <summary>
56+
/// The response from steam
57+
/// </summary>
58+
public readonly JoinResult SteamResponse;
59+
60+
/// <summary>
61+
/// ID of the queried group, or <c>0</c> if no group.
62+
/// </summary>
63+
public readonly ulong Group;
64+
65+
/// <summary>
66+
/// Is this person a member of <see cref="Group"/>. If no group this is true.
67+
/// </summary>
68+
public readonly bool Member;
69+
70+
/// <summary>
71+
/// Is this person an officer of <see cref="Group"/>. If no group this is false.
72+
/// </summary>
73+
public readonly bool Officer;
74+
75+
/// <summary>
76+
/// A future verdict on this authorization request. If null, let the default logic choose.
77+
/// </summary>
78+
public Task<JoinResult> FutureVerdict;
79+
80+
internal ValidateAuthTicketEvent(ulong steamId, ulong steamOwner, JoinResult steamResponse,
81+
ulong serverGroup, bool member, bool officer)
82+
{
83+
SteamID = steamId;
84+
SteamOwner = steamOwner;
85+
SteamResponse = steamResponse;
86+
Group = serverGroup;
87+
Member = member;
88+
Officer = officer;
89+
FutureVerdict = null;
90+
}
91+
92+
/// <inheritdoc/>
93+
public bool Cancelled => FutureVerdict != null;
94+
}
95+
}

‎Torch.Server/Torch.Server.csproj‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@
198198
<Compile Include="Managers\EntityControlManager.cs" />
199199
<Compile Include="Managers\MultiplayerManagerDedicated.cs" />
200200
<Compile Include="Managers\InstanceManager.cs" />
201+
<Compile Include="Managers\MultiplayerManagerDedicatedEventShim.cs" />
201202
<Compile Include="NativeMethods.cs" />
202203
<Compile Include="Initializer.cs" />
203204
<Compile Include="Properties\AssemblyInfo.cs" />

‎Torch/Collections/MTObservableCollection.cs‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ protected MtObservableCollection(TC backing)
3838

3939
~MtObservableCollection()
4040
{
41-
_flushEventQueue.Dispose();
41+
Timer queue = _flushEventQueue;
42+
_flushEventQueue = null;
43+
queue?.Dispose();
4244
}
4345

4446
/// <summary>
@@ -208,10 +210,10 @@ protected void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
208210
return;
209211
_collectionEventQueue.Enqueue(e);
210212
// In half a second, flush the events
211-
_flushEventQueue.Change(500, -1);
213+
_flushEventQueue?.Change(500, -1);
212214
}
213215

214-
private readonly Timer _flushEventQueue;
216+
private Timer _flushEventQueue;
215217

216218
private readonly Queue<NotifyCollectionChangedEventArgs> _collectionEventQueue =
217219
new Queue<NotifyCollectionChangedEventArgs>();

0 commit comments

Comments
 (0)