Format project
This commit is contained in:
parent
8c5e6e151e
commit
1d8eb277cc
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
59
index.js
59
index.js
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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 )
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
} )
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 ]
|
||||
}
|
||||
}
|
||||
|
|
546
shared/pjson.js
546
shared/pjson.js
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue