├── MasterServer.cs
├── README.md
└── server.js
/MasterServer.cs:
--------------------------------------------------------------------------------
1 | // Copyright 2014 DonUrks
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 | using System.IO;
15 | using System.Collections;
16 | using System.Collections.Generic;
17 | using System.Net.Sockets;
18 |
19 | namespace Urks
20 | {
21 | public class MasterServer : UnityEngine.MonoBehaviour
22 | {
23 | public bool dedicatedServer = false;
24 | public string ipAddress = "";
25 | public int port = 0;
26 | public int updateRate = 60;
27 |
28 | private bool hostRegistered = false;
29 |
30 | private TcpClient socket;
31 | private Stream socketStream;
32 |
33 | private string hostId = "";
34 | private string token = "";
35 | private string gameName = "";
36 | private byte[] data;
37 |
38 | // actions to server
39 | private const byte ACTION_REGISTER_HOST = 1;
40 | private const byte ACTION_UNREGISTER_HOST = 2;
41 | private const byte ACTION_UPDATE_HOST = 3;
42 | private const byte ACTION_REQUEST_HOSTS = 4;
43 |
44 | // actions to client
45 | private const byte ACTION_HOST_REGISTERED = 5;
46 | private const byte ACTION_HOSTS = 6;
47 |
48 | private List hostData = new List();
49 |
50 | private void Update()
51 | {
52 | if (null != this.socket && this.socket.Connected)
53 | {
54 | if (0 < this.socket.Available)
55 | {
56 | byte action = (byte)this.socketStream.ReadByte();
57 |
58 | switch(action)
59 | {
60 | case ACTION_HOST_REGISTERED:
61 | // token
62 | byte[] tokenBytes = new byte[16];
63 | this.socketStream.Read(tokenBytes, 0, tokenBytes.Length);
64 |
65 | // hostId
66 | byte hostIdLength = (byte)this.socketStream.ReadByte();
67 | byte[] hostIdBytes = new byte[hostIdLength];
68 | this.socketStream.Read(hostIdBytes, 0, hostIdLength);
69 |
70 | this.token = System.Text.Encoding.ASCII.GetString(tokenBytes);
71 | this.hostId = System.Text.Encoding.ASCII.GetString(hostIdBytes);
72 |
73 | this.hostRegistered = true;
74 | StartCoroutine("UpdateHostData");
75 |
76 | UnityEngine.Debug.Log("Host registered on master server. (hostId: " + this.hostId + ")");
77 | break;
78 |
79 | case ACTION_HOSTS:
80 | // host count
81 | byte hostCount = (byte)this.socketStream.ReadByte();
82 |
83 | UnityEngine.Debug.Log("Hosts: " + hostCount);
84 |
85 | for(int i = 0; i < hostCount; i++)
86 | {
87 | // address
88 | byte addressLength = (byte)this.socketStream.ReadByte();
89 | byte[] adressBytes = new byte[addressLength];
90 | this.socketStream.Read(adressBytes, 0, addressLength);
91 |
92 | // port
93 | byte portLength = (byte)this.socketStream.ReadByte();
94 | byte[] portBytes = new byte[portLength];
95 | this.socketStream.Read(portBytes, 0, portLength);
96 |
97 | // name
98 | byte nameLength = (byte)this.socketStream.ReadByte();
99 | byte[] nameBytes = new byte[nameLength];
100 | this.socketStream.Read(nameBytes, 0, nameLength);
101 |
102 | // passwordRequired
103 | byte passwordRequired = (byte)this.socketStream.ReadByte();
104 |
105 | // playerCount
106 | byte playerCount = (byte)this.socketStream.ReadByte();
107 |
108 | // playerLimit
109 | byte playerLimit = (byte)this.socketStream.ReadByte();
110 |
111 | // useNat
112 | byte useNat = (byte)this.socketStream.ReadByte();
113 |
114 | // playerGUID
115 | byte playerGUIDLength = (byte)this.socketStream.ReadByte();
116 | byte[] playerGUIDBytes = new byte[playerGUIDLength];
117 | this.socketStream.Read(playerGUIDBytes, 0, playerGUIDLength);
118 |
119 | UnityEngine.HostData hostData = new UnityEngine.HostData();
120 | hostData.ip = new string[1];
121 | hostData.ip[0] = System.Text.ASCIIEncoding.ASCII.GetString(adressBytes);
122 | hostData.port = int.Parse(System.Text.ASCIIEncoding.ASCII.GetString(portBytes));
123 | hostData.gameName = System.Text.UTF8Encoding.UTF8.GetString(nameBytes);
124 | hostData.passwordProtected = passwordRequired > 0 ? true : false;
125 | hostData.connectedPlayers = playerCount;
126 | hostData.playerLimit = playerLimit;
127 | hostData.useNat = useNat > 0 ? true : false;
128 | hostData.guid = System.Text.ASCIIEncoding.ASCII.GetString(playerGUIDBytes);
129 |
130 | this.hostData.Add(hostData);
131 | }
132 |
133 | break;
134 | default:
135 | UnityEngine.Debug.Log("Unknown action from MasterServer: " + action);
136 | this.socketStream.Close();
137 | break;
138 | }
139 | }
140 | }
141 | }
142 |
143 | public void RegisterHost(string gameTypeName, string gameName, bool useNat, byte[] data)
144 | {
145 | this.data = data;
146 | this.RegisterHost(gameTypeName, gameName, useNat);
147 | }
148 |
149 | public void RegisterHost(string gameTypeName, string gameName, bool useNat)
150 | {
151 | if (!UnityEngine.Network.isServer)
152 | {
153 | throw new UnityEngine.UnityException("It's not possible to register a host until it is running.");
154 | }
155 |
156 | this.gameName = gameName;
157 |
158 | byte[] gameTypeNameBytes = System.Text.UTF8Encoding.UTF8.GetBytes(gameTypeName);
159 | if (gameTypeNameBytes.Length <= 0 || gameTypeNameBytes.Length > 255)
160 | {
161 | throw new UnityEngine.UnityException("You must pass a GameTypeName 1-255 bytes.");
162 | }
163 |
164 | byte[] portBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(UnityEngine.Network.player.port.ToString());
165 | byte[] playerGUIDBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(UnityEngine.Network.player.guid);
166 |
167 | List bytes = new List();
168 |
169 | // action
170 | bytes.Add(ACTION_REGISTER_HOST);
171 |
172 | // gameTypeName
173 | bytes.Add((byte)gameTypeNameBytes.Length);
174 | bytes.AddRange(gameTypeNameBytes);
175 |
176 | // port
177 | bytes.Add((byte)portBytes.Length);
178 | bytes.AddRange(portBytes);
179 |
180 | // useNat
181 | bytes.Add((byte)(useNat == true ? 1 : 0));
182 |
183 | // player GUID
184 | bytes.Add((byte)playerGUIDBytes.Length);
185 | bytes.AddRange(playerGUIDBytes);
186 |
187 | this.RequireConnection();
188 | this.socketStream.Write(bytes.ToArray(), 0, bytes.Count);
189 | }
190 |
191 | public void ClearHostList()
192 | {
193 | this.hostData.Clear();
194 | }
195 |
196 | public UnityEngine.HostData[] PollHostList()
197 | {
198 | return this.hostData.ToArray();
199 | }
200 |
201 | public void RequestHostList(string gameTypeName)
202 | {
203 | byte[] gameTypeNameBytes = System.Text.UTF8Encoding.UTF8.GetBytes(gameTypeName);
204 |
205 | List bytes = new List();
206 |
207 | // action
208 | bytes.Add(ACTION_REQUEST_HOSTS);
209 |
210 | // gameTypeName
211 | bytes.Add((byte)gameTypeNameBytes.Length);
212 | bytes.AddRange(gameTypeNameBytes);
213 |
214 | this.RequireConnection();
215 | this.socketStream.Write(bytes.ToArray(), 0, bytes.Count);
216 | }
217 |
218 | public void UnregisterHost()
219 | {
220 | if (this.hostRegistered)
221 | {
222 | StopCoroutine("UpdateHostData");
223 |
224 | byte[] hostIdBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(this.hostId);
225 | byte[] tokenBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(this.token);
226 |
227 | List bytes = new List();
228 |
229 | // action
230 | bytes.Add(ACTION_UNREGISTER_HOST);
231 |
232 | // hostId
233 | bytes.Add((byte)hostIdBytes.Length);
234 | bytes.AddRange(hostIdBytes);
235 |
236 | // token
237 | bytes.AddRange(tokenBytes);
238 |
239 | this.RequireConnection();
240 | this.socketStream.Write(bytes.ToArray(), 0, bytes.Count);
241 | }
242 | }
243 |
244 | private void RequireConnection()
245 | {
246 | if (null == this.socket || !this.socket.Connected)
247 | {
248 | this.socket = new TcpClient(this.ipAddress, this.port);
249 | this.socket.NoDelay = true;
250 | }
251 | this.socketStream = this.socket.GetStream();
252 | }
253 |
254 | private IEnumerator UpdateHostData()
255 | {
256 | byte[] hostIdBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(this.hostId);
257 | byte[] tokenBytes = System.Text.ASCIIEncoding.ASCII.GetBytes(this.token);
258 | byte[] gameNameBytes = System.Text.UTF8Encoding.UTF8.GetBytes(this.gameName);
259 |
260 | while (true)
261 | {
262 | byte passwordRequired = UnityEngine.Network.incomingPassword != "" ? (byte)1 : (byte)0;
263 | byte playerCount = (byte)UnityEngine.Network.connections.Length;
264 | if(!this.dedicatedServer)
265 | {
266 | playerCount += 1;
267 | }
268 | byte playerLimit = (byte)UnityEngine.Network.maxConnections;
269 |
270 | List bytes = new List();
271 |
272 | // action
273 | bytes.Add(ACTION_UPDATE_HOST);
274 |
275 | // hostId
276 | bytes.Add((byte)hostIdBytes.Length);
277 | bytes.AddRange(hostIdBytes);
278 |
279 | // token
280 | bytes.AddRange(tokenBytes);
281 |
282 | // gameName
283 | bytes.Add((byte)gameNameBytes.Length);
284 | bytes.AddRange(gameNameBytes);
285 |
286 | // passwordRequired
287 | bytes.Add(passwordRequired);
288 |
289 | // playerCount
290 | bytes.Add(playerCount);
291 |
292 | // playerLimit
293 | bytes.Add(playerLimit);
294 |
295 | // data
296 | if (this.data != null)
297 | {
298 | bytes.Add((byte)this.data.Length);
299 | bytes.AddRange(this.data);
300 | }
301 | else
302 | {
303 | bytes.Add(0);
304 | }
305 |
306 | this.RequireConnection();
307 | this.socketStream.Write(bytes.ToArray(), 0, bytes.Count);
308 |
309 | yield return new UnityEngine.WaitForSeconds(this.updateRate);
310 | }
311 | }
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Unity MasterServer written in NodeJS
2 | =================
3 |
4 | This program is an alternative to the Unity MasterServer functionality. It includes a working UnregisterHost function :) and a policy server for webplayer applications. For webplayer applications the policy server is required see: https://docs.unity3d.com/Documentation/Manual/SecuritySandbox.html.
5 |
6 | ### Dependencies
7 | - NodeJS installation
8 | - NodeJS module "NoSQL"
9 |
10 | ### Server Guide
11 | 1. Install NodeJS on your server (http://nodejs.org/)
12 | 2. Install the NoSQL module: npm install nosql
13 | 3. Copy the server.js to your server
14 | 4. Change to the directory where the server.js is located
15 | 5. Link the NoSQL modul to the directory: npm link nosql
16 | 6. Open the server.js and edit the config section
17 | 7. Start the server: node server.js
18 | 8. You should see in the console: "server started"
19 |
20 | ##### Server Configuration
21 | | config | description |
22 | | ------------- |-------------|
23 | | port | listen port for the masterserver |
24 | | policyPort | listen port for the policyServer; set 0 to disable |
25 | | host | listen host for the servers |
26 | | socketTimeout | timeout in milliseconds for inactive sockets |
27 | | hostTimeout | timeout in milliseconds for inactive hosts without any updates |
28 | | hostTimeoutCheck | check timer in milliseconds for outdated hosts |
29 | | hostsFile | directory + filename for the NoSQL database file |
30 |
31 | ### Unity Guide
32 | 1. Create a empty gameobject
33 | 2. Attach the Urks.MasterServer-Script to it
34 | 3. Configure the script in the inspector (especially host and port)
35 | 4. Tag the GameObject (for example: "Urks.MasterServer")
36 | 5. Obtain in your script the "Urks.MasterServer" object and gain access to the script component
37 | 6. Now you can use all methods similiar to the built-in masterserver
38 |
39 | ##### Example Usage
40 | ```javascript
41 | Urks.MasterServer ms = UnityEngine.GameObject.FindGameObjectWithTag("Urks.MasterServer").GetComponent();
42 | ms.RegisterHost("TheNextMinecraftClone", "Free for all!", true);
43 | ```
44 |
45 | ### ToDo
46 | - security check (buffer overflow ...)
47 | - correct error handling
48 | - policy xml customizable on server filesystem
49 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | // Copyright 2014 DonUrks
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | //
16 | // listen on
17 | var port = 8000; // port for masterserver
18 | var policyPort = 843; // port for poilcy server (set 0 to disable)
19 | var host = "127.0.0.1"; // listen on address
20 |
21 | // timeouts in milliseconds
22 | var socketTimeout = 15000; // closes the socket after timeout
23 | var hostTimeout = 180000; // remove the host from the database after timespan without any updates
24 | var hostTimeoutCheck = 10000; // check intervall for timeout hosts
25 |
26 | // database file
27 | var hostsFile = '';
28 | //
29 |
30 | var net = require('net');
31 | var nosql = require('nosql').load(hostsFile);
32 | nosql.description('Hosts database.');
33 |
34 | var tokenChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
35 | var currentHostId = -1;
36 |
37 | // find highest hostId
38 | nosql.each(
39 | function(doc)
40 | {
41 | var docId = parseInt(doc.hostId);
42 |
43 | if(currentHostId < docId)
44 | {
45 | currentHostId = docId;
46 | }
47 | }
48 | );
49 |
50 | // actions to server
51 | var ACTION_REGISTER_HOST = 1;
52 | var ACTION_UNREGISTER_HOST = 2;
53 | var ACTION_UPDATE_HOST = 3;
54 | var ACTION_REQUEST_HOSTS = 4;
55 |
56 | // actions to client
57 | var ACTION_HOST_REGISTERED = 5;
58 | var ACTION_HOSTS = 6;
59 |
60 | // masterserver
61 | var server = net.createServer();
62 | server.on('connection', onServerConnection);
63 | server.listen(port, host, onServerListen);
64 |
65 | // policyserver
66 | if(policyPort > 0)
67 | {
68 | net.createServer(
69 | function(socket)
70 | {
71 | // todo: configable policy from filesystem
72 | socket.write("");
73 | socket.end();
74 | }
75 | ).listen(policyPort, host);
76 | }
77 |
78 | function onServerListen()
79 | {
80 | console.log('server started');
81 | setInterval(hostsCleanup, hostTimeoutCheck);
82 | }
83 |
84 | function onServerConnection(socket)
85 | {
86 | console.log('connected: ' + socket.remoteAddress + ':' + socket.remotePort);
87 |
88 | // on socket event error: the original remoteAddress and remotePort are "undefined"
89 | socket.remoteAddressCopy = socket.remoteAddress;
90 | socket.remotePortCopy = socket.remotePort;
91 |
92 | socket.setTimeout(socketTimeout, onSocketTimeout);
93 | socket.on('end', onSocketEnd);
94 | socket.on('error', onSocketError);
95 | socket.on('data', onSocketData);
96 | }
97 |
98 | function onSocketTimeout()
99 | {
100 | socketClose(this);
101 | }
102 |
103 | function onSocketEnd()
104 | {
105 | socketClose(this);
106 | }
107 |
108 | function onSocketError()
109 | {
110 | socketClose(this, true);
111 | }
112 |
113 | function socketClose(socket, error)
114 | {
115 | var log = 'disconnected: ' + socket.remoteAddressCopy + ':' + socket.remotePortCopy;
116 | if(error)
117 | {
118 | log += " error (client process terminated?)";
119 | }
120 |
121 | console.log(log);
122 |
123 | socket.end();
124 | socket.destroy();
125 | }
126 |
127 | function onSocketData(data)
128 | {
129 | console.log('incoming: ' + this.remoteAddress + ':' + this.remotePort);
130 | console.log('data length: ' + data.length);
131 | console.log('data: ' + data);
132 |
133 | try
134 | {
135 | var action = data.readUInt8(0);
136 | var actionBody = data.slice(1);
137 | }
138 | catch(err)
139 | {
140 | console.log("malformed data: " + err);
141 | socketClose(this);
142 | return;
143 | }
144 |
145 | switch(action)
146 | {
147 | case ACTION_REGISTER_HOST:
148 | registerHost(this, actionBody);
149 | break;
150 | case ACTION_UNREGISTER_HOST:
151 | unregisterHost(this, actionBody);
152 | break;
153 | case ACTION_UPDATE_HOST:
154 | updateHost(this, actionBody);
155 | break;
156 | case ACTION_REQUEST_HOSTS:
157 | requestHosts(this, actionBody);
158 | break;
159 | default:
160 | console.log("unknown action: " + action);
161 | socketClose(this);
162 | break;
163 |
164 | }
165 | }
166 |
167 | function registerHost(socket, data)
168 | {
169 | var gameName = "";
170 | var port = "";
171 | var useNat = false;
172 | var playerGUID = "";
173 |
174 | try
175 | {
176 | var offset = 0;
177 |
178 | // gameName
179 | var gameNameLength = data.readUInt8(offset);
180 | gameName = data.toString("utf8", offset+1, offset+1+gameNameLength);
181 | offset += 1 + gameNameLength;
182 |
183 | // port
184 | var portLength = data.readUInt8(offset);
185 | port = data.toString("ascii", offset+1, offset+1+portLength);
186 | offset += 1 + portLength;
187 |
188 | // useNat
189 | var useNat = data.readUInt8(offset) > 0 ? true : false;
190 | offset += 1;
191 |
192 | // playerGUID
193 | var playerGUIDLength = data.readUInt8(offset);
194 | playerGUID = data.toString("ascii", offset+1, offset+1+playerGUIDLength);
195 | offset += 1 + playerGUIDLength;
196 |
197 | }
198 | catch(err)
199 | {
200 | console.log("malformed registerHost: " + err);
201 | socketClose(socket);
202 | return;
203 | }
204 |
205 | var token = "";
206 | for(var i=0; i<16; i++)
207 | {
208 | token += tokenChars.charAt(Math.floor(Math.random() * tokenChars.length));
209 | }
210 |
211 | currentHostId++;
212 | var currentId = currentHostId.toString();
213 |
214 | var id = nosql.insert(
215 | {
216 | "hostId": currentId,
217 | "gameName": gameName,
218 | "token": token,
219 | "ready": false,
220 | "address": socket.remoteAddressCopy,
221 | "port": port,
222 | "useNat": useNat,
223 | "playerGUID": playerGUID,
224 | "timestamp": new Date().getTime()
225 | }
226 | );
227 |
228 | console.log("host registered");
229 | console.log("hostId: " + currentId);
230 | console.log("token: " + token);
231 | console.log("gameName: " + gameName);
232 | console.log("port: " + port);
233 | console.log("useNat: " + useNat);
234 | console.log("playerGUID: " + playerGUID);
235 |
236 | var currentIdLength = Buffer.byteLength(currentId, "ascii");
237 |
238 | var hostRegistered = new Buffer(currentIdLength+18);
239 | hostRegistered.writeUInt8(ACTION_HOST_REGISTERED, 0);
240 | hostRegistered.write(token, "ascii", 1);
241 | hostRegistered.writeUInt8(currentIdLength, 17);
242 | hostRegistered.write(currentId, 'ascii', 18);
243 |
244 | socket.write(hostRegistered);
245 | }
246 |
247 | function unregisterHost(socket, data)
248 | {
249 | var hostId = "";
250 | var token = "";
251 |
252 | try
253 | {
254 | var offset = 0;
255 |
256 | var hostIdLength = data.readUInt8(offset);
257 | offset += 1;
258 |
259 | hostId = data.toString('ascii', offset, offset+hostIdLength);
260 | offset += hostIdLength;
261 |
262 | token = data.toString('ascii', offset, offset+16);
263 | offset += 16;
264 | }
265 | catch(err)
266 | {
267 | console.log("malformed unregisterHost: " + err);
268 | socketClose(socket);
269 | return;
270 | }
271 |
272 | nosql.remove(
273 | function(doc)
274 | {
275 | return doc.hostId == hostId && doc.token == token;
276 | },
277 | function(count)
278 | {
279 | if(count)
280 | {
281 | console.log("host unregistered");
282 | console.log("hostId: " + hostId);
283 | console.log("token: " + token);
284 | }
285 | else
286 | {
287 | console.log("host not found");
288 | console.log("hostId: " + hostId);
289 | console.log("token: " + token);
290 | socketClose(socket);
291 | }
292 | }
293 | );
294 | }
295 |
296 | function updateHost(socket, data)
297 | {
298 | var hostId = "";
299 | var token = "";
300 | var name = "";
301 | var passwordRequired = 0;
302 | var playerCount = 0;
303 | var playerLimit = 0;
304 | var customData;
305 |
306 | try
307 | {
308 | var offset = 0;
309 |
310 | var hostIdLength = data.readUInt8(offset);
311 | offset += 1;
312 |
313 | hostId = data.toString('ascii', offset, offset+hostIdLength);
314 | offset += hostIdLength;
315 |
316 | token = data.toString('ascii', offset, offset+16);
317 | offset += 16;
318 |
319 | var nameLength = data.readUInt8(offset);
320 | offset += 1;
321 |
322 | name = data.toString('utf8', offset, offset+nameLength);
323 | offset += nameLength;
324 |
325 | passwordRequired = data.readUInt8(offset);
326 | offset += 1;
327 |
328 | playerCount = data.readUInt8(offset);
329 | offset += 1;
330 |
331 | playerLimit = data.readUInt8(offset);
332 | offset += 1;
333 |
334 | var customDataLength = data.readUInt8(offset);
335 | offset += 1;
336 |
337 | customData = data.slice(offset, offset+customDataLength);
338 | }
339 | catch(err)
340 | {
341 | console.log("malformed updateHost: " + err);
342 | socketClose(socket);
343 | return;
344 | }
345 |
346 | nosql.update(
347 | function(doc)
348 | {
349 | if (doc.hostId == hostId && doc.token == token)
350 | {
351 | doc.ready = true;
352 | doc.name = name;
353 | doc.passwordRequired = passwordRequired;
354 | doc.playerCount = playerCount;
355 | doc.playerLimit = playerLimit;
356 | doc.customData = customData;
357 | doc.timestamp = new Date().getTime();
358 | }
359 | return doc;
360 | },
361 | function(count)
362 | {
363 | if(count)
364 | {
365 | console.log("host updated");
366 | console.log("hostId: " + hostId);
367 | console.log("token: " + token);
368 | console.log("name: " + name);
369 | console.log("passwordRequired: " + passwordRequired);
370 | console.log("playerCount: " + playerCount);
371 | console.log("playerLimit: " + playerLimit);
372 | console.log("customData: " + customData);
373 | }
374 | else
375 | {
376 | console.log("host not found");
377 | console.log("hostId: " + hostId);
378 | console.log("token: " + token);
379 | console.log("name: " + name);
380 | socketClose(socket);
381 | }
382 | }
383 | );
384 | }
385 |
386 | function requestHosts(socket, data)
387 | {
388 | var gameName = "";
389 |
390 | try
391 | {
392 | var gameNameLength = data.readUInt8(0);
393 | gameName = data.toString("utf8", 1, 1+gameNameLength);
394 | }
395 | catch(err)
396 | {
397 | console.log("malformed requestHosts: " + err);
398 | socketClose(socket);
399 | return;
400 | }
401 |
402 | console.log("request hosts '" + gameName + "'");
403 |
404 | nosql.all(
405 | function(doc)
406 | {
407 | if (doc.gameName == gameName && doc.ready == true)
408 | {
409 | return doc;
410 | }
411 | },
412 | function(selected)
413 | {
414 | var outputBuffer = new Buffer(2);
415 | outputBuffer.writeUInt8(ACTION_HOSTS, 0);
416 | outputBuffer.writeUInt8(selected.length, 1);
417 | selected.forEach(
418 | function(doc)
419 | {
420 | var offset = 0;
421 |
422 | var bufferSize = 1 + doc.address.length; // addressLength + address
423 | bufferSize += 1 + doc.port.length; // portLength + port
424 | bufferSize += 1 + Buffer.byteLength(doc.name, "utf8"); // nameLength + name
425 | bufferSize += 1; // passwordRequired
426 | bufferSize += 1; // playerCount
427 | bufferSize += 1; // playerLimit
428 | bufferSize += 1; // useNat
429 | bufferSize += 1 + Buffer.byteLength(doc.playerGUID, "ascii"); // playerGUIDLength + playerGUID
430 |
431 | var documentBuffer = new Buffer(bufferSize);
432 |
433 | // address
434 | documentBuffer.writeUInt8(doc.address.length, offset);
435 | offset += 1;
436 | documentBuffer.write(doc.address, "ascii", offset);
437 | offset += doc.address.length;
438 |
439 | // port
440 | documentBuffer.writeUInt8(doc.port.length, offset);
441 | offset += 1;
442 | documentBuffer.write(doc.port, "ascii", offset);
443 | offset += doc.port.length;
444 |
445 | // name
446 | documentBuffer.writeUInt8(Buffer.byteLength(doc.name, "utf8"), offset);
447 | offset += 1;
448 | documentBuffer.write(doc.name, "utf8", offset);
449 | offset += Buffer.byteLength(doc.name, "utf8");
450 |
451 | // passwordRequired
452 | documentBuffer.writeUInt8(doc.passwordRequired, offset);
453 | offset += 1;
454 |
455 | // playerCount
456 | documentBuffer.writeUInt8(doc.playerCount, offset);
457 | offset += 1;
458 |
459 | // playerLimit
460 | documentBuffer.writeUInt8(doc.playerLimit, offset);
461 | offset += 1;
462 |
463 | // useNat
464 | documentBuffer.writeUInt8(doc.useNat, offset);
465 | offset += 1;
466 |
467 | // playerGUID
468 | documentBuffer.writeUInt8(doc.playerGUID.length, offset);
469 | offset += 1;
470 | documentBuffer.write(doc.playerGUID, "ascii", offset);
471 | offset += doc.playerGUID.length;
472 |
473 | outputBuffer += documentBuffer;
474 | }
475 | );
476 |
477 | socket.write(outputBuffer);
478 | }
479 | );
480 | }
481 |
482 | function hostsCleanup()
483 | {
484 | var timeoutTimestamp = new Date().getTime() - hostTimeout;
485 |
486 | nosql.remove(
487 | function(doc)
488 | {
489 | if(doc.timestamp <= timeoutTimestamp)
490 | {
491 | console.log("host timeout: " + doc.hostId);
492 | return true;
493 | }
494 | return false;
495 | }
496 | );
497 | }
498 |
--------------------------------------------------------------------------------