NorthstarMasterServer/shared/pjson.js

436 lines
11 KiB
JavaScript

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 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 )
{
// 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,
}