-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGameServerProvider.cs
More file actions
172 lines (149 loc) · 6.89 KB
/
Copy pathGameServerProvider.cs
File metadata and controls
172 lines (149 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using NLog;
using ZProtocol;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
using Protocol = ZProtocol.ZProtocol;
namespace ZamboniGameServerProvider;
public class GameServerProvider
{
private static readonly Logger Logger = LogManager.GetCurrentClassLogger();
public static readonly string PublicIp = new HttpClient().GetStringAsync("https://checkip.amazonaws.com/").GetAwaiter().GetResult().Trim();
private static GameServerProviderConfig _config = null!;
public readonly ConcurrentDictionary<Guid, GameInstance> Servers = new();
private readonly ConcurrentQueue<ushort> _freePorts = new();
public GameServerProvider()
{
InitConfig();
for (ushort p = _config.PortRangeStart; p < _config.PortRangeEnd; p++)
{
_freePorts.Enqueue(p);
}
}
public Task Start()
{
_ = Listener();
return Task.CompletedTask;
}
private static void InitConfig()
{
const string configFile = "game-server-provider-config.yml";
var serializer = new SerializerBuilder().WithNamingConvention(PascalCaseNamingConvention.Instance).Build();
if (!File.Exists(configFile))
{
_config = new GameServerProviderConfig();
File.WriteAllText(configFile, serializer.Serialize(_config));
Logger.Warn("Config file created: " + configFile);
return;
}
var deserializer = new DeserializerBuilder().WithNamingConvention(PascalCaseNamingConvention.Instance).Build();
_config = deserializer.Deserialize<GameServerProviderConfig>(File.ReadAllText(configFile));
}
private async Task Listener()
{
var listener = new TcpListener(IPAddress.Any, _config.ZProtocolPort);
listener.Start();
while (true)
{
var client = await listener.AcceptTcpClientAsync();
_ = Task.Run(async () =>
{
using (client)
{
var remoteIp = ((IPEndPoint)client.Client.RemoteEndPoint!).Address;
if (!_config.MatchmakingServerAddresses.Contains(remoteIp))
{
Logger.Warn("Blocked packet from: " + remoteIp);
return;
}
try
{
await using var stream = client.GetStream();
var command = await Protocol.ReadCommandAsync(stream);
if (command == null)
{
Logger.Warn("Packet is null");
return;
}
if (command.Version != Protocol.ProtocolVersion)
{
Logger.Warn($"Version mismatch: {command.Version}");
return;
}
switch (command)
{
case ReserveInstanceCommand reserve:
if (!_freePorts.TryDequeue(out var port))
{
await Protocol.SendResponseAsync(stream, new GenericResponse
{
Status = Status.NoCapacity
});
break;
}
GameInstance gameInstance;
if (reserve.Request.Topology == ZamboniTopology.Dedicated)
{
gameInstance = new DedicatedInstance(port, reserve.Request);
}
else
{
gameInstance = new H2HRelayInstance(port, reserve.Request);
}
Servers.TryAdd(reserve.Request.Guid, gameInstance);
gameInstance.Start();
await Protocol.SendResponseAsync(stream, new ReserveInstanceResponse(new GameInstanceInfo(_config.PublicIp.ToLower().Equals("auto") ? PublicIp : _config.PublicIp, port))
{
Status = Status.Ok
});
break;
case DestroyInstanceCommand destroy:
if (Servers.TryRemove(destroy.Guid, out var destroyed))
{
destroyed.Stop();
_freePorts.Enqueue(destroyed.Port);
Logger.Debug($"Destroyed server on port {destroyed.Port}");
await Protocol.SendResponseAsync(stream, new GenericResponse
{
Status = Status.Ok
});
}
else
{
await Protocol.SendResponseAsync(stream, new GenericResponse
{
Status = Status.Error
});
}
break;
case ResetAllInstancesCommand resetAllInstancesCommand:
var versions = resetAllInstancesCommand.GameProtocolVersions;
foreach (var server in Servers.Values.ToList().Where(server => versions.Contains(server.GameProtocolVersion)))
{
if (Servers.TryRemove(server.Guid, out var removed))
{
removed.Stop();
_freePorts.Enqueue(server.Port);
}
}
await Protocol.SendResponseAsync(stream, new GenericResponse
{
Status = Status.Ok
});
break;
default:
Logger.Warn($"Unknown command type: {command.GetType().Name}");
break;
}
}
catch (Exception ex)
{
Logger.Error(ex, "Error processing request");
}
}
});
}
}
}