1
mirror of https://github.com/R2Northstar/NorthstarMasterServer synced 2024-11-15 11:06:58 +01:00

oops forgot to commit

This commit is contained in:
BobTheBob 2021-11-07 22:08:51 +00:00
parent b073906510
commit aaa1f2488e
5 changed files with 510 additions and 9 deletions

View File

@ -7,6 +7,33 @@ const asyncHttp = require( path.join( __dirname, "../shared/asynchttp.js" ) )
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',
{
schema: {
querystring: {
uid: { type: "string" },
token: { type: "string" }
}
}
},
async ( request, reply ) => {
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.uid).toString(16).toUpperCase()}`
} )
let authJson = JSON.parse( authResponse.toString() )
return {
success: ( !!authResponse.length && !!authJson.hasOnlineAccess && authJson.storeUri.includes( "titanfall-2" ) )
}
})
// POST /client/auth_with_server
// attempts to authenticate a client with a gameserver, so they can connect
fastify.post( '/client/auth_with_server',

View File

@ -5,6 +5,8 @@ const asyncHttp = require( path.join( __dirname, "../shared/asynchttp.js" ) )
const VERIFY_STRING = "I am a northstar server!"
module.exports = ( fastify, opts, done ) => {
fastify.register( require( "fastify-multipart" ) )
// exported routes
// POST /server/add_server
@ -27,6 +29,20 @@ module.exports = ( fastify, opts, done ) => {
async ( request, reply ) => {
// 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() )
{
try
{
modInfo = JSON.parse( ( await ( await request.file() ).toBuffer() ).toString() )
hasValidModInfo = Array.isArray( modInfo.Mods )
}
catch ( ex ) {}
}
let authServerResponse = await asyncHttp.request( {
method: "GET",
host: request.ip,
@ -37,7 +53,8 @@ module.exports = ( fastify, opts, done ) => {
if ( !authServerResponse || authServerResponse.toString() != VERIFY_STRING )
return { success: false }
let newServer = new GameServer( request.query.name, request.query.description, 0, request.query.maxPlayers, request.query.map, request.query.playlist, request.ip, request.query.port, request.query.authPort, request.query.password )
let newServer = new GameServer( request.query.name, request.query.description, 0, request.query.maxPlayers, request.query.map, request.query.playlist, request.ip, request.query.port, request.query.authPort, request.query.password, modInfo )
AddGameServer( newServer )
return {

View File

@ -1,11 +1,19 @@
const http = require( "http" )
const https = require( "https")
//const url = require( "url" )
// could probably convert this to support https too later, effort tho
module.exports = {
request: function request( params, postData = null ) {
return new Promise( ( resolve, reject ) => {
let req = http.request( params, reqResult => {
let lib = http
if ( params.host.startsWith( "https://" ) )
{
params.host = params.host.replace( "https://", "" )
lib = https
}
let req = lib.request( params, reqResult => {
if ( reqResult.statusCode < 200 || reqResult.statusCode >= 300)
return reject()

View File

@ -1,4 +1,5 @@
const crypto = require( "crypto" )
const crypto = require( 'crypto' )
const pjson = require( './pjson' )
class GameServer
{
@ -15,26 +16,29 @@ class GameServer
// bool hasPassword
// string password
// object modInfo
// object pdiff
constructor( nameOrServer, description, playerCount, maxPlayers, map, playlist, ip, port, authPort, password = "" )
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.updateValues(nameOrServer.name, nameOrServer.description, nameOrServer.playerCount, nameOrServer.maxPlayers, nameOrServer.map, nameOrServer.playlist, nameOrServer.ip, nameOrServer.port, nameOrServer.authPort, nameOrServer.password)
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 )
}
else // normal constructor
{
this.lastHeartbeat = Date.now()
this.id = crypto.randomBytes(16).toString("hex")
this.updateValues(nameOrServer, description, playerCount, maxPlayers, map, playlist, ip, port, authPort, password)
this.id = crypto.randomBytes(16).toString( "hex" )
this.updateValues( nameOrServer, description, playerCount, maxPlayers, map, playlist, ip, port, authPort, password, modInfo, pdiffs )
}
}
updateValues( name, description, playerCount, maxPlayers, map, playlist, ip, port, authPort, password = "" )
updateValues( name, description, playerCount, maxPlayers, map, playlist, ip, port, authPort, password, modInfo, pdiffs )
{
this.name = name
this.description = description
@ -48,11 +52,19 @@ class GameServer
this.authPort = authPort
this.hasPassword = false
if ( password != "" )
if ( !!password )
{
this.password = password
this.hasPassword = true
}
// restrict modinfo keys
this.modInfo = { Mods:[] }
for ( let mod of modInfo.Mods )
this.modInfo.Mods.push( { Name: mod.Name || "", Version: mod.Version || "0.0.0" } )
this.pdiffs = pdiffs
}
}

437
shared/pjson.js Normal file
View File

@ -0,0 +1,437 @@
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' )
}
}
const ENUM_START = "$ENUM_START"
const ENUM_END = "$ENUM_END"
const STRUCT_START = "$STRUCT_START"
const STRUCT_END = "$STRUCT_END"
const PDIFF_ENUM_ADD = "$ENUM_ADD"
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: {},
members: []
}
// 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 ]
// 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}` )
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` )
// 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` )
// preparse type name for checking
let checkType = type
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
// 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 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` )
// 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( ']' ) )
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 )
}
}
}
return ret
}
function ParseDefinitionDiff( pdiff )
{
let ret = {
enumAdds: {},
pdefString: "",
pdef: {}
}
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 ]
// 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` )
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` )
ret.enumAdds[ currentEnumAddName ] = []
}
else if ( type == PDIFF_PDEF_START )
{
pdefIdx = i + 1
break
}
else
throw Error( `hit unexpected case` )
}
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
if ( typeof( member.arraySize ) == "string" )
member.arraySize = parsedDef.enums[ member.arraySize ].length
multiplier *= member.arraySize || 1
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}` )
}
//function PdataToJson( pdata, pdef )
//{
// 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
//
// for ( let j = 0; j < arraySize; j++ )
// {
// let memberName = base + member.name
//
// if ( member.arraySize )
// {
// memberName += "["
// if ( typeof( member.arraySize ) == 'string' )
// memberName += pdef.enums[ member.arraySize ][ j ]
// else
// memberName += j
//
// memberName += "]"
// }
//
// if ( member.type in NATIVE_TYPES )
// {
// ret[ memberName ] = 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 )
// ret[ memberName ] = pdef.enums[ member.type ][ pdata.readUInt8( i++ ) ] // enums are uint8s
// else if ( member.type in pdef.structs )
// recursiveReadPdata( pdef.structs[ member.type ], memberName + "." )
// }
// }
// }
//
// recursiveReadPdata( pdef.members )
//
// return ret
//}
function PdataToJson( pdata, pdef )
{
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
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 )
}
}
base[ member.name ] = { type: member.type, arraySize: member.arraySize, nativeArraySize: member.nativeArraySize, value: member.arraySize ? retArray : retArray[ 0 ] }
}
}
recursiveReadPdata( pdef.members, ret )
return ret
}
//function PdataJsonToBuffer( json, pdef )
//{
// // calc size
// let size = 0
// for ( let member of pdef.members )
// size += GetMemberSize( member, pdef )
//
// let buf = Buffer.alloc( size )
//
// let i = 0
// let currentKey = 0
// let keys = Object.keys( json )
//
// function recursiveWritePdata( struct )
// {
// for ( let member of struct )
// {
// let arraySize = member.arraySize || 1
// if ( typeof( arraySize ) == 'string' )
// arraySize = pdef.enums[ arraySize ].length
//
// for ( let j = 0; j < arraySize; j++ )
// {
// if ( member.type in NATIVE_TYPES )
// {
// NATIVE_TYPES[ member.type ].write( buf, json[ keys[ currentKey++ ] ], 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( json[ keys[ currentKey++ ] ] ), i++ ) // enums are uint8s
// }
// else if ( member.type in pdef.structs )
// recursiveWritePdata( pdef.structs[ member.type ] )
// }
// }
// }
//
// recursiveWritePdata( pdef.members )
// return buf
//}
function PdataJsonToBuffer( json, pdef, pdata )
{
// calc size
let size = 0
for ( let member of pdef.members )
size += GetMemberSize( member, pdef )
let buf = Buffer.alloc( size )
let i = 0
let currentKey = 0
let keys = Object.keys( json )
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
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 )
}
}
}
recursiveWritePdata( json )
return buf
}
module.exports = {
NATIVE_TYPES: NATIVE_TYPES,
ParseDefinition: ParseDefinition,
ParseDefinitionDiff: ParseDefinitionDiff,
GetMemberSize: GetMemberSize,
PdataToJson: PdataToJson,
PdataJsonToBuffer: PdataJsonToBuffer,
}