├── 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 | --------------------------------------------------------------------------------