Format project

This commit is contained in:
Northstar 2022-02-26 15:43:05 -03:00 committed by Barichello
parent 8c5e6e151e
commit 1d8eb277cc
No known key found for this signature in database
GPG Key ID: 29197D315AE4FF67
11 changed files with 751 additions and 695 deletions

View File

@ -1,50 +1,56 @@
const path = require( "path" )
const accounts = require( path.join( __dirname, "../shared/accounts.js" ) )
const { getRatelimit } = require("../shared/ratelimit.js")
const { GetGameServers } = require( "../shared/gameserver.js" )
const { getRatelimit } = require( "../shared/ratelimit.js" )
module.exports = ( fastify, opts, done ) => {
module.exports = ( fastify, opts, done ) =>
{
// exported routes
// POST /accounts/write_persistence
// attempts to write persistent data for a player
// note: this is entirely insecure atm, at the very least, we should prevent it from being called on servers that the account being written to isn't currently connected to
fastify.post( '/accounts/write_persistence',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__ACCOUNT_WRITEPERSISTENCE") }, // ratelimit
schema: {
querystring: {
"id": { type: "string" },
"serverId": { type: "string" }
}
fastify.post( "/accounts/write_persistence",
{
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__ACCOUNT_WRITEPERSISTENCE" ) }, // ratelimit
schema: {
querystring: {
"id": { type: "string" },
"serverId": { type: "string" }
}
},
},
},
async ( request, response ) => {
async ( request ) =>
{
// check if account exists
let account = await accounts.AsyncGetPlayerByID( request.query.id )
if ( !account )
return null
let account = await accounts.AsyncGetPlayerByID( request.query.id )
if ( !account )
return null
// if the client is on their own server then don't check this since their own server might not be on masterserver
if ( account.currentServerId == "self" ) {
// if the client is on their own server then don't check this since their own server might not be on masterserver
if ( account.currentServerId == "self" )
{
// if the ip sending the request isn't the same as the one that last authed using client/origin_auth then don't update
if ( request.ip != account.lastAuthIp )
return null
} else {
let server = GetGameServers()[ request.query.serverId ]
// dont update if the server doesnt exist, or the server isnt the one sending the heartbeat
if ( !server || request.ip != server.ip || account.currentServerId != request.query.serverId )
return null
}
if ( request.ip != account.lastAuthIp )
return null
}
else
{
let server = GetGameServers()[ request.query.serverId ]
// dont update if the server doesnt exist, or the server isnt the one sending the heartbeat
if ( !server || request.ip != server.ip || account.currentServerId != request.query.serverId )
return null
}
// mostly temp
let buf = await ( await request.file() ).toBuffer()
// mostly temp
let buf = await ( await request.file() ).toBuffer()
if ( buf.length == account.persistentDataBaseline.length )
await accounts.AsyncWritePlayerPersistenceBaseline( request.query.id, buf )
if ( buf.length == account.persistentDataBaseline.length )
await accounts.AsyncWritePlayerPersistenceBaseline( request.query.id, buf )
return null
})
return null
} )
done()
}

View File

@ -1,185 +1,192 @@
const path = require( "path" )
const crypto = require( "crypto" )
const { GameServer, GetGameServers } = require( path.join( __dirname, "../shared/gameserver.js" ) )
const { GetGameServers } = require( path.join( __dirname, "../shared/gameserver.js" ) )
const accounts = require( path.join( __dirname, "../shared/accounts.js" ) )
const asyncHttp = require( path.join( __dirname, "../shared/asynchttp.js" ) )
let shouldRequireSessionToken = process.env.REQUIRE_SESSION_TOKEN = true
const { getRatelimit } = require("../shared/ratelimit.js")
const { getRatelimit } = require( "../shared/ratelimit.js" )
module.exports = ( fastify, opts, done ) => {
module.exports = ( fastify, opts, done ) =>
{
// exported routes
// POST /client/origin_auth
// used to authenticate a user on northstar, so we know the person using their uid is really them
// returns the user's northstar session token
fastify.get( '/client/origin_auth',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_ORIGINAUTH") }, // ratelimit
schema: {
querystring: {
id: { type: "string" }, // the authing player's id
token: { type: "string" } // the authing player's origin token
fastify.get( "/client/origin_auth",
{
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__CLIENT_ORIGINAUTH" ) }, // ratelimit
schema: {
querystring: {
id: { type: "string" }, // the authing player's id
token: { type: "string" } // the authing player's origin token
}
}
}
},
async ( request, reply ) => {
},
async ( request ) =>
{
// only do this if we're in an environment that actually requires session tokens
if ( shouldRequireSessionToken )
{
if ( shouldRequireSessionToken )
{
// todo: we should find origin endpoints that can verify game tokens so we don't have to rely on stryder for this in case of a ratelimit
if ( request.query.token.includes( "&" ) )
return { success: false }
if ( request.query.token.includes( "&" ) )
return { success: false }
let authResponse = await asyncHttp.request( {
method: "GET",
host: "https://r2-pc.stryder.respawn.com",
port: 443,
path: `/nucleus-oauth.php?qt=origin-requesttoken&type=server_token&code=${ request.query.token }&forceTrial=0&proto=0&json=1&&env=production&userId=${ parseInt( request.query.id ).toString(16).toUpperCase() }`
} )
let authResponse = await asyncHttp.request( {
method: "GET",
host: "https://r2-pc.stryder.respawn.com",
port: 443,
path: `/nucleus-oauth.php?qt=origin-requesttoken&type=server_token&code=${ request.query.token }&forceTrial=0&proto=0&json=1&&env=production&userId=${ parseInt( request.query.id ).toString( 16 ).toUpperCase() }`
} )
let authJson
try {
authJson = JSON.parse( authResponse.toString() )
} catch (error) {
return { success: false }
let authJson
try
{
authJson = JSON.parse( authResponse.toString() )
}
catch ( error )
{
return { success: false }
}
// check origin auth was fine
// unsure if we can check the exact value of storeUri? doing an includes check just in case
if ( !authResponse.length || authJson.hasOnlineAccess != "1" /* this is actually a string of either "1" or "0" */ || !authJson.storeUri.includes( "titanfall-2" ) )
return { success: false }
}
// check origin auth was fine
// unsure if we can check the exact value of storeUri? doing an includes check just in case
if ( !authResponse.length || authJson.hasOnlineAccess != "1" /* this is actually a string of either "1" or "0" */ || !authJson.storeUri.includes( "titanfall-2" ) )
return { success: false }
}
let account = await accounts.AsyncGetPlayerByID( request.query.id )
if ( !account ) // create account for user
{
await accounts.AsyncCreateAccountForID( request.query.id )
account = await accounts.AsyncGetPlayerByID( request.query.id )
}
let account = await accounts.AsyncGetPlayerByID( request.query.id )
if ( !account ) // create account for user
{
await accounts.AsyncCreateAccountForID( request.query.id )
account = await accounts.AsyncGetPlayerByID( request.query.id )
}
let authToken = crypto.randomBytes( 16 ).toString( "hex" )
accounts.AsyncUpdateCurrentPlayerAuthToken( account.id, authToken )
let authToken = crypto.randomBytes( 16 ).toString( "hex" )
accounts.AsyncUpdateCurrentPlayerAuthToken( account.id, authToken )
accounts.AsyncUpdatePlayerAuthIp( account.id, request.ip )
accounts.AsyncUpdatePlayerAuthIp( account.id, request.ip )
return {
success: true,
token: authToken
}
})
return {
success: true,
token: authToken
}
} )
// POST /client/auth_with_server
// attempts to authenticate a client with a gameserver, so they can connect
// authentication includes giving them a 1-time token to join the gameserver, as well as sending their persistent data to the gameserver
fastify.post( '/client/auth_with_server',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_AUTHWITHSERVER") }, // ratelimit
schema: {
querystring: {
id: { type: "string" }, // id of the player trying to auth
playerToken: { type: "string" }, // not implemented yet: the authing player's account token
server: { type: "string" },
password: { type: "string" } // the password the player is using to connect to the server
}
}
},
async ( request, reply ) => {
let server = GetGameServers()[ request.query.server ]
if ( !server || ( server.hasPassword && request.query.password != server.password ) )
return { success: false }
let account = await accounts.AsyncGetPlayerByID( request.query.id )
if ( !account )
return { success: false }
if ( shouldRequireSessionToken )
fastify.post( "/client/auth_with_server",
{
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__CLIENT_AUTHWITHSERVER" ) }, // ratelimit
schema: {
querystring: {
id: { type: "string" }, // id of the player trying to auth
playerToken: { type: "string" }, // not implemented yet: the authing player's account token
server: { type: "string" },
password: { type: "string" } // the password the player is using to connect to the server
}
}
},
async ( request ) =>
{
let server = GetGameServers()[ request.query.server ]
if ( !server || ( server.hasPassword && request.query.password != server.password ) )
return { success: false }
let account = await accounts.AsyncGetPlayerByID( request.query.id )
if ( !account )
return { success: false }
if ( shouldRequireSessionToken )
{
// check token
if ( request.query.playerToken != account.currentAuthToken )
if ( request.query.playerToken != account.currentAuthToken )
return { success: false }
// check expired token
if ( account.currentAuthTokenExpirationTime < Date.now() )
return { success: false }
}
// fix this: game doesnt seem to set serverFilter right if it's >31 chars long, so restrict it to 31
let authToken = crypto.randomBytes( 16 ).toString( "hex" ).substr( 0, 31 )
// todo: build persistent data here, rather than sending baseline only
let pdata = await accounts.AsyncGetPlayerPersistenceBufferForMods( request.query.id, server.modInfo.Mods.filter( m => !!m.pdiff ).map( m => m.pdiff ) )
let authResponse = await asyncHttp.request( {
method: "POST",
host: server.ip,
port: server.authPort,
path: `/authenticate_incoming_player?id=${request.query.id}&authToken=${authToken}&serverAuthToken=${server.serverAuthToken}`
}, pdata )
if ( !authResponse )
return { success: false }
// check expired token
if ( account.currentAuthTokenExpirationTime < Date.now() )
let jsonResponse = JSON.parse( authResponse.toString() )
if ( !jsonResponse.success )
return { success: false }
}
// fix this: game doesnt seem to set serverFilter right if it's >31 chars long, so restrict it to 31
let authToken = crypto.randomBytes( 16 ).toString( "hex" ).substr( 0, 31 )
// update the current server for the player account
accounts.AsyncUpdatePlayerCurrentServer( account.id, server.id )
// todo: build persistent data here, rather than sending baseline only
let pdata = await accounts.AsyncGetPlayerPersistenceBufferForMods( request.query.id, server.modInfo.Mods.filter( m => !!m.pdiff ).map( m => m.pdiff ) )
return {
success: true,
let authResponse = await asyncHttp.request( {
method: "POST",
host: server.ip,
port: server.authPort,
path: `/authenticate_incoming_player?id=${request.query.id}&authToken=${authToken}&serverAuthToken=${server.serverAuthToken}`
}, pdata )
if ( !authResponse )
return { success: false }
let jsonResponse = JSON.parse( authResponse.toString() )
if ( !jsonResponse.success )
return { success: false }
// update the current server for the player account
accounts.AsyncUpdatePlayerCurrentServer( account.id, server.id )
return {
success: true,
ip: server.ip,
port: server.port,
authToken: authToken
}
})
ip: server.ip,
port: server.port,
authToken: authToken
}
} )
// POST /client/auth_with_self
// attempts to authenticate a client with their own server, before the server is created
// note: atm, this just sends pdata to clients and doesn't do any kind of auth stuff, potentially rewrite later
fastify.post( '/client/auth_with_self',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_AUTHWITHSELF") }, // ratelimit
schema: {
querystring: {
id: { type: "string" }, // id of the player trying to auth
playerToken: { type: "string" }, // not implemented yet: the authing player's account token
}
}
},
async ( request, reply ) => {
let account = await accounts.AsyncGetPlayerByID( request.query.id )
if ( !account )
return { success: false }
if ( shouldRequireSessionToken )
fastify.post( "/client/auth_with_self",
{
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__CLIENT_AUTHWITHSELF" ) }, // ratelimit
schema: {
querystring: {
id: { type: "string" }, // id of the player trying to auth
playerToken: { type: "string" }, // not implemented yet: the authing player's account token
}
}
},
async ( request ) =>
{
let account = await accounts.AsyncGetPlayerByID( request.query.id )
if ( !account )
return { success: false }
if ( shouldRequireSessionToken )
{
// check token
if ( request.query.playerToken != account.currentAuthToken )
return { success: false }
if ( request.query.playerToken != account.currentAuthToken )
return { success: false }
// check expired token
if ( account.currentAuthTokenExpirationTime < Date.now() )
return { success: false }
}
// check expired token
if ( account.currentAuthTokenExpirationTime < Date.now() )
return { success: false }
}
// fix this: game doesnt seem to set serverFilter right if it's >31 chars long, so restrict it to 31
let authToken = crypto.randomBytes( 16 ).toString("hex").substr( 0, 31 )
accounts.AsyncUpdatePlayerCurrentServer( account.id, "self" ) // bit of a hack: use the "self" id for local servers
// fix this: game doesnt seem to set serverFilter right if it's >31 chars long, so restrict it to 31
let authToken = crypto.randomBytes( 16 ).toString( "hex" ).substr( 0, 31 )
accounts.AsyncUpdatePlayerCurrentServer( account.id, "self" ) // bit of a hack: use the "self" id for local servers
return {
success: true,
return {
success: true,
id: account.id,
authToken: authToken,
// this fucking sucks, but i couldn't get game to behave if i sent it as an ascii string, so using this for now
persistentData: Array.from( new Uint8Array( account.persistentDataBaseline ) )
}
})
id: account.id,
authToken: authToken,
// this fucking sucks, but i couldn't get game to behave if i sent it as an ascii string, so using this for now
persistentData: Array.from( new Uint8Array( account.persistentDataBaseline ) )
}
} )
done()
}
}

View File

@ -1,40 +1,43 @@
const path = require( "path" )
const fs = require( "fs" )
const { getRatelimit } = require("../shared/ratelimit.js")
const { getRatelimit } = require( "../shared/ratelimit.js" )
let promodataPath = path.join( __dirname, "mainmenupromodata.json" )
// watch the mainmenupromodata file so we can update it without a masterserver restart
fs.watch( promodataPath, ( curr, prev ) => {
try
{
mainMenuPromoData = JSON.parse( fs.readFileSync( promodataPath ).toString() )
console.log( "updated main menu promo data successfully!" )
}
catch ( ex )
{
console.log( `encountered error updating main menu promo data: ${ ex }` )
}
fs.watch( promodataPath =>
{
try
{
mainMenuPromoData = JSON.parse( fs.readFileSync( promodataPath ).toString() )
console.log( "updated main menu promo data successfully!" )
}
catch ( ex )
{
console.log( `encountered error updating main menu promo data: ${ ex }` )
}
})
} )
let mainMenuPromoData = {}
if ( fs.existsSync( promodataPath ))
mainMenuPromoData = JSON.parse( fs.readFileSync( promodataPath ).toString() )
if ( fs.existsSync( promodataPath ) )
mainMenuPromoData = JSON.parse( fs.readFileSync( promodataPath ).toString() )
module.exports = ( fastify, opts, done ) => {
module.exports = ( fastify, opts, done ) =>
{
// exported routes
// GET /client/mainmenupromos
// returns main menu promo info
fastify.get( '/client/mainmenupromos',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__CLIENT_MAINMENUPROMOS") }, // ratelimit
},
async ( request, reply ) => {
return mainMenuPromoData
})
// GET /client/mainmenupromos
// returns main menu promo info
fastify.get( "/client/mainmenupromos",
{
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__CLIENT_MAINMENUPROMOS" ) }, // ratelimit
},
async ( ) =>
{
return mainMenuPromoData
} )
done()
}
done()
}

View File

@ -1,57 +1,60 @@
if ( process.argv.includes( "-devenv" ) )
require( 'dotenv' ).config({ path: "./dev.env" })
require( "dotenv" ).config( { path: "./dev.env" } )
else
require( 'dotenv' ).config()
require( "dotenv" ).config()
const fs = require( "fs" )
const path = require( "path" )
let trustProxy = !!(process.env.TRUST_PROXY)
if(trustProxy && process.env.TRUST_PROXY_LIST_PATH) {
let addressList = fs.readFileSync( process.env.TRUST_PROXY_LIST_PATH ).toString();
trustProxy = addressList.split("\n").map(a => a.trim()).filter(a => !a.startsWith("#") && a != '')
let trustProxy = !!( process.env.TRUST_PROXY )
if( trustProxy && process.env.TRUST_PROXY_LIST_PATH )
{
let addressList = fs.readFileSync( process.env.TRUST_PROXY_LIST_PATH ).toString()
trustProxy = addressList.split( "\n" ).map( a => a.trim() ).filter( a => !a.startsWith( "#" ) && a != "" )
}
let fastify = require( "fastify" )
if ( process.env.USE_HTTPS )
{
fastify = fastify({
fastify = fastify( {
logger: process.env.USE_FASTIFY_LOGGER || false,
https: {
key: fs.readFileSync( process.env.SSL_KEY_PATH ),
cert: fs.readFileSync( process.env.SSL_CERT_PATH )
},
trustProxy
})
} )
}
else
{
fastify = fastify({
fastify = fastify( {
logger: process.env.USE_FASTIFY_LOGGER || false,
trustProxy
})
} )
}
fastify.register( require( "fastify-multipart" ) )
const ROUTE_PATHS = [ "client", "server", "account" ]
if(!!(process.env.USE_RATELIMIT)) {
fastify.register(require('fastify-rate-limit'),
{
global: false,
errorResponseBuilder: function(req, context) {
return {
code: 429,
error: 'Too Many Requests',
message: `Request limit reached. You can send ${context.max} requests every ${context.after}. Timeout expiry: ${context.ttl}ms.`,
date: Date.now(),
expiresIn: context.ttl // milliseconds
if( process.env.USE_RATELIMIT )
{
fastify.register( require( "fastify-rate-limit" ),
{
global: false,
errorResponseBuilder: function( req, context )
{
return {
code: 429,
error: "Too Many Requests",
message: `Request limit reached. You can send ${context.max} requests every ${context.after}. Timeout expiry: ${context.ttl}ms.`,
date: Date.now(),
expiresIn: context.ttl // milliseconds
}
}
}
}
);
)
}
for ( let routePath of ROUTE_PATHS )
@ -66,12 +69,12 @@ for ( let routePath of ROUTE_PATHS )
}
}
async function start()
async function start()
{
try
try
{
await fastify.listen( process.env.LISTEN_PORT || 80, process.env.LISTEN_IP || "0.0.0.0" )
}
}
catch ( ex )
{
console.error( ex )
@ -79,4 +82,4 @@ async function start()
}
}
start()
start()

View File

@ -3,19 +3,18 @@ const crypto = require( "crypto" )
const { GameServer, GetGameServers, AddGameServer, RemoveGameServer } = require( path.join( __dirname, "../shared/gameserver.js" ) )
const asyncHttp = require( path.join( __dirname, "../shared/asynchttp.js" ) )
const pjson = require( path.join( __dirname, "../shared/pjson.js" ) )
const Filter = require('bad-words')
let filter = new Filter();
const Filter = require( "bad-words" )
let filter = new Filter()
const VERIFY_STRING = "I am a northstar server!"
const { getRatelimit } = require("../shared/ratelimit.js")
const {updateServerList} = require("../shared/serverlist_state.js")
const { getRatelimit } = require( "../shared/ratelimit.js" )
const {updateServerList} = require( "../shared/serverlist_state.js" )
async function SharedTryAddServer( request )
{
// check server's verify endpoint on their auth server, make sure it's fine
// in the future we could probably check the server's connect port too, with a c2s_connect packet or smth, but atm this is good enough
let hasValidModInfo = true
let modInfo
if ( request.isMultipart() )
@ -23,9 +22,11 @@ async function SharedTryAddServer( request )
try
{
modInfo = JSON.parse( ( await ( await request.file() ).toBuffer() ).toString() )
hasValidModInfo = Array.isArray( modInfo.Mods )
}
catch ( ex ) {}
catch ( ex )
{
console.error( ex )
}
}
let authServerResponse = await asyncHttp.request( {
@ -33,7 +34,7 @@ async function SharedTryAddServer( request )
host: request.ip,
port: request.query.authPort,
path: "/verify"
})
} )
if ( !authServerResponse || authServerResponse.toString() != VERIFY_STRING )
return { success: false }
@ -43,7 +44,7 @@ async function SharedTryAddServer( request )
{
for ( let mod of modInfo.Mods )
{
if ( !!mod.pdiff )
if ( mod.pdiff )
{
try
{
@ -60,16 +61,16 @@ async function SharedTryAddServer( request )
}
let playerCount = request.query.playerCount || 0
if ( typeof playerCount == 'string' )
if ( typeof playerCount == "string" )
playerCount = parseInt( playerCount )
if ( typeof request.query.maxPlayers == 'string' )
if ( typeof request.query.maxPlayers == "string" )
request.query.maxPlayers = parseInt( request.query.maxPlayers )
if ( typeof request.query.port == 'string' )
if ( typeof request.query.port == "string" )
request.query.port = parseInt( request.query.port )
if ( typeof request.query.authPort == 'string' )
if ( typeof request.query.authPort == "string" )
request.query.authPort = parseInt( request.query.authPort )
let name = filter.clean( request.query.name )
@ -86,121 +87,126 @@ async function SharedTryAddServer( request )
}
}
module.exports = ( fastify, opts, done ) => {
module.exports = ( fastify, opts, done ) =>
{
// exported routes
// POST /server/add_server
// adds a gameserver to the server list
fastify.post( '/server/add_server',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__SERVER_ADDSERVER") }, // ratelimit
schema: {
querystring: {
port: { type: "integer" }, // the port the gameserver is being hosted on ( for connect )
authPort: { type: "integer" }, // the port the server's http auth server is being hosted on
name: { type: "string" }, // the name of the server
description: { type: "string" }, // the description of the server
map: { type: "string" }, // the map the server is on
playlist: { type: "string" }, // the playlist the server is using
maxPlayers: { type: "integer" }, // the maximum number of players the server accepts
password: { type: "string" } // the server's password, if 0 length, the server does not accept a password
fastify.post( "/server/add_server",
{
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__SERVER_ADDSERVER" ) }, // ratelimit
schema: {
querystring: {
port: { type: "integer" }, // the port the gameserver is being hosted on ( for connect )
authPort: { type: "integer" }, // the port the server's http auth server is being hosted on
name: { type: "string" }, // the name of the server
description: { type: "string" }, // the description of the server
map: { type: "string" }, // the map the server is on
playlist: { type: "string" }, // the playlist the server is using
maxPlayers: { type: "integer" }, // the maximum number of players the server accepts
password: { type: "string" } // the server's password, if 0 length, the server does not accept a password
}
}
}
},
async ( request, reply ) => {
return SharedTryAddServer( request )
})
},
async ( request ) =>
{
return SharedTryAddServer( request )
} )
// POST /server/heartbeat
// refreshes a gameserver's last heartbeat time, gameservers are removed after 30 seconds without a heartbeat
fastify.post( '/server/heartbeat',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__SERVER_HEARTBEAT") }, // ratelimit
schema: {
querystring: {
id: { type: "string" }, // the id of the server sending this message
playerCount: { type: "integer" }
fastify.post( "/server/heartbeat",
{
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__SERVER_HEARTBEAT" ) }, // ratelimit
schema: {
querystring: {
id: { type: "string" }, // the id of the server sending this message
playerCount: { type: "integer" }
}
}
}
},
async ( request, reply ) => {
let server = GetGameServers()[ request.query.id ]
// dont update if the server doesnt exist, or the server isnt the one sending the heartbeat
if ( !server || request.ip != server.ip || !request.query.id )// remove !request.playerCount as if playercount==0 it will trigger skip heartbeat update
},
async ( request ) =>
{
return null
}
let server = GetGameServers()[ request.query.id ]
// dont update if the server doesnt exist, or the server isnt the one sending the heartbeat
if ( !server || request.ip != server.ip || !request.query.id )// remove !request.playerCount as if playercount==0 it will trigger skip heartbeat update
{
return null
}
else // Added else so update heartbeat will trigger,Have to add the brackets for me to work for some reason
{
server.lastHeartbeat = Date.now()
server.playerCount = request.query.playerCount
return null
}
})
else // Added else so update heartbeat will trigger,Have to add the brackets for me to work for some reason
{
server.lastHeartbeat = Date.now()
server.playerCount = request.query.playerCount
return null
}
} )
// POST /server/update_values
// updates values shown on the server list, such as map, playlist, or player count
// no schema for this one, since it's fully dynamic and fastify doesnt do optional params
fastify.post( '/server/update_values',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__SERVER_UPDATEVALUES") }, // ratelimit
},
async ( request, reply ) => {
if ( !( "id" in request.query ) )
return null
let server = GetGameServers()[ request.query.id ]
// if server doesn't exist, try adding it
if ( !server )
fastify.post( "/server/update_values",
{
return SharedTryAddServer( request )
}
else if ( request.ip != server.ip ) // dont update if the server isnt the one sending the heartbeat
return null
// update heartbeat
server.lastHeartbeat = Date.now()
for ( let key of Object.keys( request.query ) )
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__SERVER_UPDATEVALUES" ) }, // ratelimit
},
async ( request ) =>
{
if ( key == "id" || key == "port" || key == "authport" || !( key in server ) || request.query[ key ].length >= 512 )
continue
if ( !( "id" in request.query ) )
return null
if ( key == "playerCount" || key == "maxPlayers" )
let server = GetGameServers()[ request.query.id ]
// if server doesn't exist, try adding it
if ( !server )
{
server[ key ] = parseInt( request.query[ key ] )
return SharedTryAddServer( request )
}
else //i suppose maybe add the brackets here to as upper one works with it. but actually its fine not to i guess.
else if ( request.ip != server.ip ) // dont update if the server isnt the one sending the heartbeat
return null
// update heartbeat
server.lastHeartbeat = Date.now()
for ( let key of Object.keys( request.query ) )
{
server[ key ] = request.query[ key ]
if ( key == "id" || key == "port" || key == "authport" || !( key in server ) || request.query[ key ].length >= 512 )
continue
if ( key == "playerCount" || key == "maxPlayers" )
{
server[ key ] = parseInt( request.query[ key ] )
}
else //i suppose maybe add the brackets here to as upper one works with it. but actually its fine not to i guess.
{
server[ key ] = request.query[ key ]
}
}
}
return null
})
return null
} )
// DELETE /server/remove_server
// removes a gameserver from the server list
fastify.delete( '/server/remove_server',
{
config: { rateLimit: getRatelimit("REQ_PER_MINUTE__SERVER_REMOVESERVER") }, // ratelimit
schema: {
querystring: {
id: { type: "string" }
fastify.delete( "/server/remove_server",
{
config: { rateLimit: getRatelimit( "REQ_PER_MINUTE__SERVER_REMOVESERVER" ) }, // ratelimit
schema: {
querystring: {
id: { type: "string" }
}
}
}
},
async ( request, reply ) => {
let server = GetGameServers()[ request.query.id ]
// dont remove if the server doesnt exist, or the server isnt the one sending the heartbeat
if ( !server || request.ip != server.ip )
return null
},
async ( request ) =>
{
let server = GetGameServers()[ request.query.id ]
// dont remove if the server doesnt exist, or the server isnt the one sending the heartbeat
if ( !server || request.ip != server.ip )
return null
RemoveGameServer( server )
updateServerList()
return null
})
RemoveGameServer( server )
updateServerList()
return null
} )
done()
}

View File

@ -1,18 +1,19 @@
const sqlite = require( "sqlite3" ).verbose()
const path = require( "path" )
const fs = require( "fs" )
const pjson = require( path.join( __dirname, "../shared/pjson.js" ) )
const TOKEN_EXPIRATION_TIME = 3600000 * 24 // 24 hours
const DEFAULT_PDATA_BASELINE = fs.readFileSync( "default.pdata" )
const DEFAULT_PDEF_OBJECT = pjson.ParseDefinition( fs.readFileSync( "persistent_player_data_version_231.pdef" ).toString() )
// const path = require( "path" )
// const pjson = require( path.join( __dirname, "../shared/pjson.js" ) )
// const DEFAULT_PDEF_OBJECT = pjson.ParseDefinition( fs.readFileSync( "persistent_player_data_version_231.pdef" ).toString() )
let playerDB = new sqlite.Database( 'playerdata.db', sqlite.OPEN_CREATE | sqlite.OPEN_READWRITE, ex => {
let playerDB = new sqlite.Database( "playerdata.db", sqlite.OPEN_CREATE | sqlite.OPEN_READWRITE, ex =>
{
if ( ex )
console.error( ex )
else
console.log( "Connected to player database successfully" )
// create account table
// this should mirror the PlayerAccount class's properties
playerDB.run( `
@ -22,14 +23,15 @@ let playerDB = new sqlite.Database( 'playerdata.db', sqlite.OPEN_CREATE | sqlite
currentAuthTokenExpirationTime INTEGER,
currentServerId TEXT,
persistentDataBaseline BLOB NOT NULL,
lastAuthIp TEXT
lastAuthIp TEXT
)
`, ex => {
`, ex =>
{
if ( ex )
console.error( ex )
else
console.log( "Created player account table successfully" )
})
} )
// create mod persistent data table
// this should mirror the PlayerAccount class's properties
@ -40,33 +42,38 @@ let playerDB = new sqlite.Database( 'playerdata.db', sqlite.OPEN_CREATE | sqlite
data TEXT NOT NULL,
PRIMARY KEY ( id, pdiffHash )
)
`, ex => {
`, ex =>
{
if ( ex )
console.error( ex )
else
console.log( "Created mod persistent data table successfully" )
})
})
} )
} )
function asyncDBGet( sql, params = [] )
{
return new Promise( ( resolve, reject ) => {
playerDB.get( sql, params, ( ex, row ) => {
return new Promise( ( resolve, reject ) =>
{
playerDB.get( sql, params, ( ex, row ) =>
{
if ( ex )
{
console.error( "Encountered error querying player database: " + ex )
reject( ex )
}
else
else
resolve( row )
})
})
} )
} )
}
function asyncDBRun( sql, params = [] )
{
return new Promise( ( resolve, reject ) => {
playerDB.run( sql, params, ex => {
return new Promise( ( resolve, reject ) =>
{
playerDB.run( sql, params, ex =>
{
if ( ex )
{
console.error( "Encountered error querying player database: " + ex )
@ -74,8 +81,8 @@ function asyncDBRun( sql, params = [] )
}
else
resolve()
})
})
} )
} )
}
class PlayerAccount
@ -87,7 +94,7 @@ class PlayerAccount
// int currentAuthTokenExpirationTime
// string currentServerId
// Buffer persistentDataBaseline
constructor ( id, currentAuthToken, currentAuthTokenExpirationTime, currentServerId, persistentDataBaseline, lastAuthIp )
{
this.id = id
@ -100,67 +107,76 @@ class PlayerAccount
}
module.exports = {
AsyncGetPlayerByID: async function AsyncGetPlayerByID( id ) {
AsyncGetPlayerByID: async function AsyncGetPlayerByID( id )
{
let row = await asyncDBGet( "SELECT * FROM accounts WHERE id = ?", [ id ] )
if ( !row )
return null
return new PlayerAccount( row.id, row.currentAuthToken, row.currentAuthTokenExpirationTime, row.currentServerId, row.persistentDataBaseline, row.lastAuthIp )
},
AsyncCreateAccountForID: async function AsyncCreateAccountForID( id ) {
AsyncCreateAccountForID: async function AsyncCreateAccountForID( id )
{
await asyncDBRun( "INSERT INTO accounts ( id, persistentDataBaseline ) VALUES ( ?, ? )", [ id, DEFAULT_PDATA_BASELINE ] )
},
AsyncUpdateCurrentPlayerAuthToken: async function AsyncUpdateCurrentPlayerAuthToken( id, token ) {
AsyncUpdateCurrentPlayerAuthToken: async function AsyncUpdateCurrentPlayerAuthToken( id, token )
{
await asyncDBRun( "UPDATE accounts SET currentAuthToken = ?, currentAuthTokenExpirationTime = ? WHERE id = ?", [ token, Date.now() + TOKEN_EXPIRATION_TIME, id ] )
},
AsyncUpdatePlayerAuthIp: async function AsyncUpdatePlayerAuthIp( id, lastAuthIp ) {
AsyncUpdatePlayerAuthIp: async function AsyncUpdatePlayerAuthIp( id, lastAuthIp )
{
await asyncDBRun( "UPDATE accounts SET lastAuthIp = ? WHERE id = ?", [ lastAuthIp, id ] )
},
AsyncUpdatePlayerCurrentServer: async function AsyncUpdatePlayerCurrentServer( id, serverId ) {
AsyncUpdatePlayerCurrentServer: async function AsyncUpdatePlayerCurrentServer( id, serverId )
{
await asyncDBRun( "UPDATE accounts SET currentServerId = ? WHERE id = ?", [ serverId, id ] )
},
AsyncWritePlayerPersistenceBaseline: async function AsyncWritePlayerPersistenceBaseline( id, persistentDataBaseline ) {
AsyncWritePlayerPersistenceBaseline: async function AsyncWritePlayerPersistenceBaseline( id, persistentDataBaseline )
{
await asyncDBRun( "UPDATE accounts SET persistentDataBaseline = ? WHERE id = ?", [ persistentDataBaseline, id ] )
},
AsyncGetPlayerModPersistence: async function AsyncGetPlayerModPersistence( id, pdiffHash ) {
AsyncGetPlayerModPersistence: async function AsyncGetPlayerModPersistence( id, pdiffHash )
{
return JSON.parse( await asyncDBGet( "SELECT data from modPersistentData WHERE id = ? AND pdiffHash = ?", [ id, pdiffHash ] ) )
},
AsyncWritePlayerModPersistence: async function AsyncWritePlayerModPersistence( id, pdiffHash, data ) {
},
// AsyncWritePlayerModPersistence: async function AsyncWritePlayerModPersistence( id, pdiffHash, data )
// {
AsyncGetPlayerPersistenceBufferForMods: async function( id, pdiffs ) {
let player = await module.exports.AsyncGetPlayerByID( id )
return player.persistentDataBaseline
// },
// disabling this for now
/*let pdefCopy = DEFAULT_PDEF_OBJECT
let baselineJson = pjson.PdataToJson( player.persistentDataBaseline, DEFAULT_PDEF_OBJECT )
// AsyncGetPlayerPersistenceBufferForMods: async function( id, pdiffs )
// {
// let player = await module.exports.AsyncGetPlayerByID( id )
// return player.persistentDataBaseline
let newPdataJson = baselineJson
// // disabling this for now
// let pdefCopy = DEFAULT_PDEF_OBJECT
// let baselineJson = pjson.PdataToJson( player.persistentDataBaseline, DEFAULT_PDEF_OBJECT )
if ( !player )
return null
// temp etc
/*for ( let pdiff of pdiffs )
{
for ( let enumAdd in pdiff.enums )
pdefCopy.enums[ enumAdd ] = [ ...pdefCopy.enums[ enumAdd ], ...pdiff.enums[ enumAdd ] ]
pdefCopy = Object.assign( pdefCopy, pdiff.pdef )
// this assign call won't work, but basically what it SHOULD do is replace any pdata keys that are in the mod pdata and append new ones to the end
newPdataJson = Object.assign( newPdataJson, this.AsyncGetPlayerModPersistence( id, pdiff.hash ) )
}
return PdataJsonToBuffer( newPdataJson, pdefCopy )*/
}
// let newPdataJson = baselineJson
// if ( !player )
// return null
// // temp etc
// for ( let pdiff of pdiffs )
// {
// for ( let enumAdd in pdiff.enums )
// pdefCopy.enums[ enumAdd ] = [ ...pdefCopy.enums[ enumAdd ], ...pdiff.enums[ enumAdd ] ]
// pdefCopy = Object.assign( pdefCopy, pdiff.pdef )
// // this assign call won't work, but basically what it SHOULD do is replace any pdata keys that are in the mod pdata and append new ones to the end
// newPdataJson = Object.assign( newPdataJson, this.AsyncGetPlayerModPersistence( id, pdiff.hash ) )
// }
// return PdataJsonToBuffer( newPdataJson, pdefCopy )
// }
}

View File

@ -1,10 +1,12 @@
const http = require( "http" )
const https = require( "https")
const https = require( "https" )
//const url = require( "url" )
module.exports = {
request: function request( params, postData = null ) {
return new Promise( ( resolve, reject ) => {
request: function request( params, postData = null )
{
return new Promise( ( resolve, reject ) =>
{
let lib = http
if ( params.host.startsWith( "https://" ) )
{
@ -12,21 +14,22 @@ module.exports = {
lib = https
}
let req = lib.request( params, reqResult => {
if ( reqResult.statusCode < 200 || reqResult.statusCode >= 300)
let req = lib.request( params, reqResult =>
{
if ( reqResult.statusCode < 200 || reqResult.statusCode >= 300 )
return reject()
let data = []
reqResult.on( "data", c => data.push( c ) )
reqResult.on( "end", _ => resolve( Buffer.concat( data ) ) )
});
reqResult.on( "end", resolve( Buffer.concat( data ) ) )
} )
req.on( "error", reject )
if ( postData )
req.write( postData )
req.end()
})
} )
}
}
}

View File

@ -1,4 +1,4 @@
const crypto = require( 'crypto' )
const crypto = require( "crypto" )
class GameServer
{
@ -8,23 +8,23 @@ class GameServer
// int maxPlayers
// string map
// string playlist
// string ip
// int port
// int authPort
// bool hasPassword
// string password
// object modInfo
// object pdiff
constructor( nameOrServer, description, playerCount, maxPlayers, map, playlist, ip, port, authPort, password = "", modInfo = {} )
{
if ( nameOrServer instanceof( GameServer ) ) // copy constructor
{
this.lastHeartbeat = nameOrServer.lastHeartbeat
this.id = nameOrServer.id
this.serverAuthToken = nameOrServer.serverAuthToken
this.updateValues( nameOrServer.name, nameOrServer.description, nameOrServer.playerCount, nameOrServer.maxPlayers, nameOrServer.map, nameOrServer.playlist, nameOrServer.ip, nameOrServer.port, nameOrServer.authPort, nameOrServer.password, nameOrServer.modInfo, nameOrServer.pdiffs )
@ -32,9 +32,9 @@ class GameServer
else // normal constructor
{
this.lastHeartbeat = Date.now()
this.id = crypto.randomBytes(16).toString( "hex" )
this.serverAuthToken = crypto.randomBytes(16).toString( "hex" )
this.id = crypto.randomBytes( 16 ).toString( "hex" )
this.serverAuthToken = crypto.randomBytes( 16 ).toString( "hex" )
this.updateValues( nameOrServer, description, playerCount, maxPlayers, map, playlist, ip, port, authPort, password, modInfo )
}
}
@ -47,14 +47,14 @@ class GameServer
this.maxPlayers = maxPlayers
this.map = map
this.playlist = playlist
this.ip = ip
this.port = port
this.authPort = authPort
this.hasPassword = false
if ( !!password )
if ( password )
{
this.password = password
this.hasPassword = true
@ -71,8 +71,17 @@ let gameServers = {}
module.exports = {
GameServer: GameServer,
GetGameServers: function() { return gameServers },
AddGameServer: function( gameserver ) { gameServers[ gameserver.id ] = gameserver },
RemoveGameServer: function( gameserver ) { delete gameServers[ gameserver.id ] }
}
GetGameServers: function()
{
return gameServers
},
AddGameServer: function( gameserver )
{
gameServers[ gameserver.id ] = gameserver
},
RemoveGameServer: function( gameserver )
{
delete gameServers[ gameserver.id ]
}
}

View File

@ -1,23 +1,23 @@
const NATIVE_TYPES = {
int: { size: 4,
read: ( buf, idx ) => buf.readInt32LE( idx ),
write: ( buf, value, idx ) => buf.writeInt32LE( value, idx )
},
float: { size: 4,
read: ( buf, idx ) => buf.readFloatLE( idx ),
write: ( buf, value, idx ) => buf.writeFloatLE( value, idx )
},
bool: {
size: 1,
read: ( buf, idx ) => !!buf.readUInt8( idx ),
write: ( buf, value, idx ) => buf.writeUint8( value, idx )
},
string: {
size: 1,
nativeArrayType: true,
read: ( buf, idx, length ) => buf.toString( 'ascii', idx, idx + length ),
write: ( buf, value, idx, length ) => buf.write( value.padEnd( length, '\u0000' ), idx, length, 'ascii' )
}
int: { size: 4,
read: ( buf, idx ) => buf.readInt32LE( idx ),
write: ( buf, value, idx ) => buf.writeInt32LE( value, idx )
},
float: { size: 4,
read: ( buf, idx ) => buf.readFloatLE( idx ),
write: ( buf, value, idx ) => buf.writeFloatLE( value, idx )
},
bool: {
size: 1,
read: ( buf, idx ) => !!buf.readUInt8( idx ),
write: ( buf, value, idx ) => buf.writeUint8( value, idx )
},
string: {
size: 1,
nativeArrayType: true,
read: ( buf, idx, length ) => buf.toString( "ascii", idx, idx + length ),
write: ( buf, value, idx, length ) => buf.write( value.padEnd( length, "\u0000" ), idx, length, "ascii" )
}
}
const ENUM_START = "$ENUM_START"
@ -32,219 +32,217 @@ const PDIFF_PDEF_START = "$PROP_START"
// reads a .pdef file, outputs all keys/values in a json format we use for this module
function ParseDefinition( pdef )
{
let ret = {
structs: {},
enums: {},
let ret = {
structs: {},
enums: {},
members: []
}
members: []
}
// falsey if not in use
let currentEnumName
let currentStructName
// falsey if not in use
let currentEnumName
let currentStructName
// read each line
for ( let line of pdef.split( '\n' ) )
{
// read types/names
let split = line.trim().split( /\s+/g )
let type = split[ 0 ]
// read each line
for ( let line of pdef.split( "\n" ) )
{
// read types/names
let split = line.trim().split( /\s+/g )
let type = split[ 0 ]
// check if this is a comment line
if ( type.includes( "//" ) || type.length == 0 )
continue
// user-defined type keywords
if ( type[ 0 ][ 0 ] == '$' )
{
if ( !currentEnumName && !currentStructName )
{
let name = split[ 1 ]
if ( !name.match( /^\w+$/ ) )
throw new Error( `unexpected characters in keyword ${type}` )
// check if this is a comment line
if ( type.includes( "//" ) || type.length == 0 )
continue
if ( type == ENUM_START )
{
currentEnumName = name
ret.enums[ currentEnumName ] = []
}
else if ( type == STRUCT_START )
{
currentStructName = name
ret.structs[ currentStructName ] = []
}
else
throw Error( `encountered unknown keyword ${type}` )
}
else if ( type == ENUM_END && currentEnumName )
currentEnumName = ""
else if ( type == STRUCT_END && currentStructName )
currentStructName = ""
else
throw Error( `encountered unknown case with keyword ${type}` )
}
// we're in an enum, so enum members
else if ( currentEnumName )
{
if ( !type.match( /^\w+$/ ) )
throw new Error( `unexpected characters in enum member` )
// user-defined type keywords
if ( type[ 0 ][ 0 ] == "$" )
{
if ( !currentEnumName && !currentStructName )
{
let name = split[ 1 ]
if ( !name.match( /^\w+$/ ) )
throw new Error( `unexpected characters in keyword ${type}` )
// enums only have member names, values are just their index in the enum
ret.enums[ currentEnumName ].push( type )
}
// normal members/struct members
else
{
let name = split[ 1 ]
if ( !name.match( /^[\w\[\]\{\}]+$/ ) )
throw new Error( `unexpected characters in member name` )
if ( type == ENUM_START )
{
currentEnumName = name
ret.enums[ currentEnumName ] = []
}
else if ( type == STRUCT_START )
{
currentStructName = name
ret.structs[ currentStructName ] = []
}
else
throw Error( `encountered unknown keyword ${type}` )
}
else if ( type == ENUM_END && currentEnumName )
currentEnumName = ""
else if ( type == STRUCT_END && currentStructName )
currentStructName = ""
else
throw Error( `encountered unknown case with keyword ${type}` )
}
// we're in an enum, so enum members
else if ( currentEnumName )
{
if ( !type.match( /^\w+$/ ) )
throw new Error( "unexpected characters in enum member" )
// preparse type name for checking
let checkType = type
// enums only have member names, values are just their index in the enum
ret.enums[ currentEnumName ].push( type )
}
// normal members/struct members
else
{
let name = split[ 1 ]
if ( !name.match( /^[\w[\]{}]+$/ ) )
throw new Error( "unexpected characters in member name" )
let isNativeArrayType = false
let isArray = false
if ( type.includes( '{' ) )
{
// native array types are literally only strings, which are defined with string{length}
checkType = type.substr( 0, type.indexOf( '{' ) )
isNativeArrayType = true
}
else if ( name.includes( '[' ) )
isArray = true
// preparse type name for checking
let checkType = type
// verify type stuff
if ( !( checkType in NATIVE_TYPES || checkType in ret.structs || checkType in ret.enums ) )
throw Error( `got unknown type ${checkType}` )
else
{
let arrayLength = -1
let isNativeArrayType = false
let isArray = false
if ( type.includes( "{" ) )
{
// native array types are literally only strings, which are defined with string{length}
checkType = type.substr( 0, type.indexOf( "{" ) )
isNativeArrayType = true
}
else if ( name.includes( "[" ) )
isArray = true
let newMember = { type: checkType, name: name }
// verify type stuff
if ( !( checkType in NATIVE_TYPES || checkType in ret.structs || checkType in ret.enums ) )
throw Error( `got unknown type ${checkType}` )
else
{
let newMember = { type: checkType, name: name }
if ( isNativeArrayType )
{
if ( !( checkType in NATIVE_TYPES ) || !NATIVE_TYPES[ checkType ].nativeArrayType )
throw Error( `type ${checkType} was accessed like a native array type, while it is not one` )
if ( isNativeArrayType )
{
if ( !( checkType in NATIVE_TYPES ) || !NATIVE_TYPES[ checkType ].nativeArrayType )
throw Error( `type ${checkType} was accessed like a native array type, while it is not one` )
// pretty sure this can't be an enum and has to be a number? unsure
newMember.nativeArraySize = parseInt( type.substr( type.indexOf( '{' ) + 1 ) )
}
else if ( isArray )
{
let bracketIndex = name.indexOf( '[' )
newMember.name = name.substr( 0, bracketIndex )
// pretty sure this can't be an enum and has to be a number? unsure
newMember.nativeArraySize = parseInt( type.substr( type.indexOf( "{" ) + 1 ) )
}
else if ( isArray )
{
let bracketIndex = name.indexOf( "[" )
newMember.name = name.substr( 0, bracketIndex )
let arraySize = name.substring( bracketIndex + 1, name.indexOf( ']' ) )
let arraySize = name.substring( bracketIndex + 1, name.indexOf( "]" ) )
if ( arraySize in ret.enums )
newMember.arraySize = arraySize // just use the enum name here, easier to just let people resolve this themselves
else
newMember.arraySize = parseInt( arraySize )
}
if ( arraySize in ret.enums )
newMember.arraySize = arraySize // just use the enum name here, easier to just let people resolve this themselves
else
newMember.arraySize = parseInt( arraySize )
}
if ( currentStructName )
ret.structs[ currentStructName ].push( newMember )
else
ret.members.push( newMember )
}
}
}
if ( currentStructName )
ret.structs[ currentStructName ].push( newMember )
else
ret.members.push( newMember )
}
}
}
return ret
return ret
}
function ParseDefinitionDiff( pdiff )
{
let ret = {
enumAdds: {},
pdefString: "",
pdef: {}
}
let ret = {
enumAdds: {},
pdefString: "",
pdef: {}
}
let pdefIdx = -1
let currentEnumAddName
let lines = pdiff.split( '\n' )
let pdefIdx = -1
let currentEnumAddName
let lines = pdiff.split( "\n" )
// read each line
for ( let i = 0; i < lines.length; i++ )
{
// read types/names
let split = lines[ i ].trim().split( /\s+/g )
let type = split[ 0 ]
// read each line
for ( let i = 0; i < lines.length; i++ )
{
// read types/names
let split = lines[ i ].trim().split( /\s+/g )
let type = split[ 0 ]
// check if this is a comment line
if ( type.includes( "//" ) || type.length == 0 )
continue
if ( currentEnumAddName )
{
if ( type == ENUM_END )
currentEnumAddName = ""
else
{
if ( !type.match( /^\w+$/ ) )
throw Error( `unexpected characters in enum member` )
// check if this is a comment line
if ( type.includes( "//" ) || type.length == 0 )
continue
ret.enumAdds[ currentEnumAddName ].push(type)
}
}
else if ( type == PDIFF_ENUM_ADD )
{
currentEnumAddName = split[ 1 ]
if ( !currentEnumAddName.match( /^\w+$/ ) )
throw Error( `unexpected characters in enum name` )
if ( currentEnumAddName )
{
if ( type == ENUM_END )
currentEnumAddName = ""
else
{
if ( !type.match( /^\w+$/ ) )
throw Error( "unexpected characters in enum member" )
ret.enumAdds[ currentEnumAddName ] = []
}
else if ( type == PDIFF_PDEF_START )
{
pdefIdx = i + 1
break
}
else
throw Error( `hit unexpected case` )
}
ret.enumAdds[ currentEnumAddName ].push( type )
}
}
else if ( type == PDIFF_ENUM_ADD )
{
currentEnumAddName = split[ 1 ]
if ( !currentEnumAddName.match( /^\w+$/ ) )
throw Error( "unexpected characters in enum name" )
if ( pdefIdx != -1 )
{
let pdef = lines.slice( pdefIdx ).join( '\n' )
ret.pdefString = pdef
ret.enumAdds[ currentEnumAddName ] = []
}
else if ( type == PDIFF_PDEF_START )
{
pdefIdx = i + 1
break
}
else
throw Error( "hit unexpected case" )
}
ret.pdef = ParseDefinition( pdef )
}
return ret
if ( pdefIdx != -1 )
{
let pdef = lines.slice( pdefIdx ).join( "\n" )
ret.pdefString = pdef
ret.pdef = ParseDefinition( pdef )
}
return ret
}
function GetMemberSize( member, parsedDef )
{
let multiplier = 1
let multiplier = 1
if ( typeof( member.arraySize ) == "string" )
member.arraySize = parsedDef.enums[ member.arraySize ].length
if ( typeof( member.arraySize ) == "string" )
member.arraySize = parsedDef.enums[ member.arraySize ].length
multiplier *= member.arraySize || 1
multiplier *= member.arraySize || 1
if ( member.type in NATIVE_TYPES )
{
if ( NATIVE_TYPES[ member.type ].nativeArrayType )
multiplier *= member.nativeArraySize
if ( member.type in NATIVE_TYPES )
{
if ( NATIVE_TYPES[ member.type ].nativeArrayType )
multiplier *= member.nativeArraySize
return NATIVE_TYPES[ member.type ].size * multiplier
}
else if ( member.type in parsedDef.enums )
return NATIVE_TYPES.int.size * multiplier
else if ( member.type in parsedDef.structs )
{
let structSize = 0
for ( let structMember of parsedDef.structs[ member.type ] )
structSize += GetMemberSize( structMember, parsedDef )
return structSize * multiplier
}
else
throw Error( `got unknown member type ${member.type}` )
return NATIVE_TYPES[ member.type ].size * multiplier
}
else if ( member.type in parsedDef.enums )
return NATIVE_TYPES.int.size * multiplier
else if ( member.type in parsedDef.structs )
{
let structSize = 0
for ( let structMember of parsedDef.structs[ member.type ] )
structSize += GetMemberSize( structMember, parsedDef )
return structSize * multiplier
}
else
throw Error( `got unknown member type ${member.type}` )
}
//function PdataToJson( pdata, pdef )
@ -295,43 +293,43 @@ function GetMemberSize( member, parsedDef )
function PdataToJson( pdata, pdef )
{
let ret = {}
let i = 0
let ret = {}
let i = 0
function recursiveReadPdata( struct, base )
{
for ( let member of struct )
{
let arraySize = member.arraySize || 1
if ( typeof( arraySize ) == 'string' )
arraySize = pdef.enums[ member.arraySize ].length
function recursiveReadPdata( struct, base )
{
for ( let member of struct )
{
let arraySize = member.arraySize || 1
if ( typeof( arraySize ) == "string" )
arraySize = pdef.enums[ member.arraySize ].length
let retArray = []
for ( let j = 0; j < arraySize; j++ )
{
if ( member.type in NATIVE_TYPES )
{
retArray.push( NATIVE_TYPES[ member.type ].read( pdata, i, member.nativeArraySize ) )
i += NATIVE_TYPES[ member.type ].size * ( member.nativeArraySize || 1 )
}
else if ( member.type in pdef.enums )
retArray.push( pdef.enums[ member.type ][ pdata.readUInt8( i++ ) ] ) // enums are uint8s
else if ( member.type in pdef.structs )
{
let newStruct = {}
recursiveReadPdata( pdef.structs[ member.type ], newStruct )
retArray.push( newStruct )
}
}
let retArray = []
base[ member.name ] = { type: member.type, arraySize: member.arraySize, nativeArraySize: member.nativeArraySize, value: member.arraySize ? retArray : retArray[ 0 ] }
}
}
for ( let j = 0; j < arraySize; j++ )
{
if ( member.type in NATIVE_TYPES )
{
retArray.push( NATIVE_TYPES[ member.type ].read( pdata, i, member.nativeArraySize ) )
i += NATIVE_TYPES[ member.type ].size * ( member.nativeArraySize || 1 )
}
else if ( member.type in pdef.enums )
retArray.push( pdef.enums[ member.type ][ pdata.readUInt8( i++ ) ] ) // enums are uint8s
else if ( member.type in pdef.structs )
{
let newStruct = {}
recursiveReadPdata( pdef.structs[ member.type ], newStruct )
retArray.push( newStruct )
}
}
recursiveReadPdata( pdef.members, ret )
base[ member.name ] = { type: member.type, arraySize: member.arraySize, nativeArraySize: member.nativeArraySize, value: member.arraySize ? retArray : retArray[ 0 ] }
}
}
return ret
recursiveReadPdata( pdef.members, ret )
return ret
}
@ -377,61 +375,61 @@ function PdataToJson( pdata, pdef )
// return buf
//}
function PdataJsonToBuffer( json, pdef, pdata )
function PdataJsonToBuffer( json, pdef )
{
// calc size
let size = 0
for ( let member of pdef.members )
size += GetMemberSize( member, pdef )
// calc size
let size = 0
for ( let member of pdef.members )
size += GetMemberSize( member, pdef )
let buf = Buffer.alloc( size )
let buf = Buffer.alloc( size )
let i = 0
let currentKey = 0
let keys = Object.keys( json )
let i = 0
// let currentKey = 0
// let keys = Object.keys( json )
function recursiveWritePdata( struct )
{
for ( let memberName in struct )
{
let member = struct[ memberName ]
function recursiveWritePdata( struct )
{
for ( let memberName in struct )
{
let member = struct[ memberName ]
let arraySize = member.arraySize || 1
if ( typeof( arraySize ) == 'string' )
arraySize = pdef.enums[ arraySize ].length
let arraySize = member.arraySize || 1
if ( typeof( arraySize ) == "string" )
arraySize = pdef.enums[ arraySize ].length
for ( let j = 0; j < arraySize; j++ )
{
let val = member.value
if ( Array.isArray( val ) )
val = member.value[ j ]
for ( let j = 0; j < arraySize; j++ )
{
let val = member.value
if ( Array.isArray( val ) )
val = member.value[ j ]
if ( member.type in NATIVE_TYPES )
{
NATIVE_TYPES[ member.type ].write( buf, val, i, member.nativeArraySize )
i += NATIVE_TYPES[ member.type ].size * ( member.nativeArraySize || 1 )
}
else if ( member.type in pdef.enums )
{
buf.writeUInt8( pdef.enums[ member.type ].indexOf( val ), i++ ) // enums are uint8s
}
else if ( member.type in pdef.structs )
recursiveWritePdata( val )
}
}
}
if ( member.type in NATIVE_TYPES )
{
NATIVE_TYPES[ member.type ].write( buf, val, i, member.nativeArraySize )
i += NATIVE_TYPES[ member.type ].size * ( member.nativeArraySize || 1 )
}
else if ( member.type in pdef.enums )
{
buf.writeUInt8( pdef.enums[ member.type ].indexOf( val ), i++ ) // enums are uint8s
}
else if ( member.type in pdef.structs )
recursiveWritePdata( val )
}
}
}
recursiveWritePdata( json )
return buf
recursiveWritePdata( json )
return buf
}
module.exports = {
NATIVE_TYPES: NATIVE_TYPES,
NATIVE_TYPES: NATIVE_TYPES,
ParseDefinition: ParseDefinition,
ParseDefinitionDiff: ParseDefinitionDiff,
GetMemberSize: GetMemberSize,
PdataToJson: PdataToJson,
PdataJsonToBuffer: PdataJsonToBuffer,
}
ParseDefinition: ParseDefinition,
ParseDefinitionDiff: ParseDefinitionDiff,
GetMemberSize: GetMemberSize,
PdataToJson: PdataToJson,
PdataJsonToBuffer: PdataJsonToBuffer,
}

View File

@ -1,7 +1,8 @@
function getRatelimit(envVar) {
return { max: Number(process.env[envVar]) || (Number(process.env.REQ_PER_MINUTE__GLOBAL) || 9999) }
function getRatelimit( envVar )
{
return { max: Number( process.env[envVar] ) || ( Number( process.env.REQ_PER_MINUTE__GLOBAL ) || 9999 ) }
}
module.exports = {
getRatelimit
}
getRatelimit
}

View File

@ -4,13 +4,16 @@ let lastChecked = Date.now()
const { GameServer, GetGameServers, RemoveGameServer } = require( "../shared/gameserver.js" )
module.exports = {
getLastChecked: function () {
getLastChecked: function ()
{
return lastChecked
},
getServerList: function () {
getServerList: function ()
{
return serverList
},
updateServerList: function () {
updateServerList: function ()
{
let displayServerArray = []
let expiredServers = []
let servers = Object.values( GetGameServers() )
@ -26,11 +29,12 @@ module.exports = {
}
// don't show non-private_match servers on lobby since they'll pollute server list
if ( servers[ i ].map == "mp_lobby" && servers[ i ].playlist != "private_match" ) {
if ( servers[ i ].map == "mp_lobby" && servers[ i ].playlist != "private_match" )
{
// console.log(`SKIP: (${servers[i].id}) - ${servers[i].name}`)
continue
}
// create a copy of the gameserver obj for clients so we can hide sensitive info
let copy = new GameServer( servers[ i ] )
delete copy.ip