node-minecraft-protocol/test/packetTest.js

463 lines
12 KiB
JavaScript
Raw Normal View History

/* eslint-env mocha */
2015-10-01 20:02:54 -04:00
const mc = require('../')
const Client = mc.Client
const Server = mc.Server
const net = require('net')
const assert = require('power-assert')
const getFieldInfo = require('protodef').utils.getFieldInfo
const getField = require('protodef').utils.getField
const { getPort } = require('./common/util')
function evalCount (count, fields) {
2019-08-20 18:14:37 -04:00
if (fields[count.field] in count.map) { return count.map[fields[count.field]] }
return count.default
2015-10-01 20:02:54 -04:00
}
2018-08-10 18:04:21 -04:00
const slotValue = {
present: true,
2018-08-10 18:04:21 -04:00
blockId: 5,
itemDamage: 2,
nbtData: {
type: 'compound',
name: 'test',
value: {
2018-09-24 16:07:34 -04:00
test1: { type: 'int', value: 4 },
test2: { type: 'long', value: [12, 42] },
test3: { type: 'byteArray', value: [32] },
test4: { type: 'string', value: 'ohi' },
test5: { type: 'list', value: { type: 'int', value: [4] } },
test6: { type: 'compound', value: { test: { type: 'int', value: 4 } } },
test7: { type: 'intArray', value: [12, 42] }
2018-08-10 18:04:21 -04:00
}
},
// 1.20.5
itemCount: 1,
itemId: 1111,
addedComponentCount: 0,
removedComponentCount: 0,
components: [],
removeComponents: []
2018-08-10 18:04:21 -04:00
}
const nbtValue = {
type: 'compound',
name: 'test',
value: {
test1: { type: 'int', value: 4 },
test2: { type: 'long', value: [12, 42] },
test3: { type: 'byteArray', value: [32] },
test4: { type: 'string', value: 'ohi' },
test5: { type: 'list', value: { type: 'int', value: [4] } },
test6: { type: 'compound', value: { test: { type: 'int', value: 4 } } },
test7: { type: 'intArray', value: [12, 42] }
}
}
function getFixedPacketPayload (version, packetName) {
if (packetName === 'declare_recipes') {
if (version['>=']('1.21.3')) {
return {
recipes: [
{
name: 'minecraft:campfire_input',
items: [
903,
976
]
}
],
stoneCutterRecipes: [
{
input: {
ids: [
6
]
},
slotDisplay: {
type: 'item_stack',
data: slotValue
}
}
]
}
} else if (version['>=']('1.20.5')) {
return {
recipes: [
{
name: 'minecraft:crafting_decorated_pot',
type: 'minecraft:crafting_decorated_pot',
data: {
category: 0
}
}
]
}
}
}
}
2017-07-13 08:03:52 -04:00
const values = {
2019-08-03 19:29:14 -04:00
i32: 123456,
i16: -123,
u16: 123,
varint: 1,
varlong: -20,
2019-08-03 19:29:14 -04:00
i8: -10,
u8: 8,
ByteArray: [],
2019-08-03 19:29:14 -04:00
string: 'hi hi this is my client string',
buffer: function (typeArgs, context) {
let count
if (typeof typeArgs.count === 'number') {
count = typeArgs.count
} else if (typeof typeArgs.count === 'object') {
count = evalCount(typeArgs.count, context)
} else if (typeArgs.count !== undefined) {
count = getField(typeArgs.count, context)
} else if (typeArgs.countType !== undefined) {
count = 8
}
return Buffer.alloc(count)
},
2019-08-03 19:29:14 -04:00
array: function (typeArgs, context) {
let count
if (typeof typeArgs.count === 'number') {
count = typeArgs.count
} else if (typeof typeArgs.count === 'object') {
count = evalCount(typeArgs.count, context)
} else if (typeArgs.count !== undefined) {
count = getField(typeArgs.count, context)
} else if (typeArgs.countType !== undefined) {
count = 1
}
const arr = []
2015-10-01 20:02:54 -04:00
while (count > 0) {
arr.push(getValue(typeArgs.type, context))
count--
2015-10-01 20:02:54 -04:00
}
return arr
2015-10-01 20:02:54 -04:00
},
2019-08-03 19:29:14 -04:00
container: function (typeArgs, context) {
2017-07-13 08:03:52 -04:00
const results = {
'..': context
}
Object.keys(typeArgs).forEach(function (index) {
2019-05-19 19:46:02 -04:00
const v = typeArgs[index].name === 'type' && typeArgs[index].type === 'string' && typeArgs[2] !== undefined &&
typeArgs[2].type !== undefined
2019-08-20 18:14:37 -04:00
? (typeArgs[2].type[1].fields['minecraft:crafting_shapeless'] === undefined ? 'crafting_shapeless' : 'minecraft:crafting_shapeless')
: getValue(typeArgs[index].type, results)
if (typeArgs[index].anon) {
Object.keys(v).forEach(key => {
results[key] = v[key]
})
} else {
results[typeArgs[index].name] = v
}
})
2020-05-16 10:57:04 -04:00
delete results['..']
return results
2015-10-01 20:02:54 -04:00
},
vec2f: {
x: 0, y: 0
},
vec3f: {
x: 0, y: 0, z: 0
},
vec3f64: {
x: 0, y: 0, z: 0
},
vec4f: {
x: 0, y: 0, z: 0, w: 0
},
2019-08-03 19:29:14 -04:00
count: 1, // TODO : might want to set this to a correct value
bool: true,
f64: 99999.2222,
f32: -333.444,
slot: slotValue,
Slot: slotValue,
SlotComponent: {
type: 'hide_tooltip'
},
ChatTypes: {
registryIndex: 1
},
SlotComponentType: 0,
nbt: nbtValue,
optionalNbt: nbtValue,
compressedNbt: nbtValue,
anonymousNbt: nbtValue,
anonOptionalNbt: nbtValue,
previousMessages: [],
2019-08-03 19:29:14 -04:00
i64: [0, 1],
u64: [0, 1],
entityMetadata: [
2018-09-24 16:07:34 -04:00
{ key: 17, value: 0, type: 0 }
2015-10-01 20:02:54 -04:00
],
topBitSetTerminatedArray: [
{
slot: 0,
item: slotValue
},
{
slot: 1,
item: slotValue
}
],
2019-08-03 19:29:14 -04:00
objectData: {
2015-10-01 20:02:54 -04:00
intField: 9,
velocityX: 1,
velocityY: 2,
velocityZ: 3
2015-10-01 20:02:54 -04:00
},
2019-08-03 19:29:14 -04:00
UUID: '00112233-4455-6677-8899-aabbccddeeff',
position: { x: 12, y: 100, z: 4382821 },
position_ibi: { x: 12, y: 100, z: 4382821 },
position_isi: { x: 12, y: 100, z: 4382821 },
position_iii: { x: 12, y: 100, z: 4382821 },
restBuffer: Buffer.alloc(0),
switch: function (typeArgs, context) {
const i = typeArgs.fields[getField(typeArgs.compareTo, context)]
if (i === undefined) {
if (typeArgs.default === undefined) {
typeArgs.default = 'void'
// throw new Error("couldn't find the field " + typeArgs.compareTo + ' of the compareTo and the default is not defined')
}
return getValue(typeArgs.default, context)
} else { return getValue(i, context) }
2015-10-01 20:02:54 -04:00
},
2019-08-03 19:29:14 -04:00
option: function (typeArgs, context) {
return getValue(typeArgs, context)
},
2019-08-03 19:29:14 -04:00
bitfield: function (typeArgs, context) {
const results = {}
Object.keys(typeArgs).forEach(function (index) {
results[typeArgs[index].name] = 1
})
return results
2018-01-01 15:01:23 -05:00
},
mapper: '',
2019-08-03 19:29:14 -04:00
tags: [{ tagName: 'hi', entries: [1, 2, 3, 4, 5] }],
ingredient: [slotValue],
particleData: null,
chunkBlockEntity: { x: 10, y: 11, z: 12, type: 25 },
command_node: {
flags: {
has_custom_suggestions: 1,
has_redirect_node: 1,
has_command: 1,
command_node_type: 2
},
children: [23, 29],
redirectNode: 83,
extraNodeData: {
name: 'command_node name',
parser: 'brigadier:double',
properties: {
flags: {
max_present: 1,
min_present: 1
},
min: -5.0,
max: 256.0
},
suggestionType: 'minecraft:summonable_entities'
}
},
bitflags: function (typeArgs, context) {
const results = {}
Object.keys(typeArgs.flags).forEach(function (index) {
results[typeArgs.flags[index]] = true
})
return results
},
soundSource: 'master',
packedChunkPos: {
x: 10,
z: 12
},
particle: {
particleId: 0,
data: null
},
Particle: {},
SpawnInfo: {
dimension: 0,
name: 'minecraft:overworld',
hashedSeed: [
572061085,
1191958278
],
gamemode: 'survival',
previousGamemode: 255,
isDebug: false,
isFlat: false,
portalCooldown: 0
},
MovementFlags: {
onGround: true,
hasHorizontalCollision: false
},
ContainerID: 0,
PositionUpdateRelatives: {
x: true,
y: true,
z: true,
yaw: true,
pitch: true,
dx: true,
dy: true,
dz: true,
yawDelta: true
},
RecipeDisplay: {
type: 'stonecutter',
data: {
ingredient: { type: 'empty' },
result: { type: 'empty' },
craftingStation: { type: 'empty' }
}
},
SlotDisplay: { type: 'empty' },
game_profile: {
name: 'test',
properties: [{
key: 'foo',
value: 'bar'
}]
},
optvarint: 1,
chat_session: {
uuid: '00112233-4455-6677-8899-aabbccddeeff',
publicKey: {
expireTime: 30,
keyBytes: [],
keySignature: []
}
},
IDSet: { ids: [2, 5] }
}
2015-10-01 20:02:54 -04:00
function getValue (_type, packet) {
const fieldInfo = getFieldInfo(_type)
if (typeof values[fieldInfo.type] === 'function') {
return values[fieldInfo.type](fieldInfo.typeArgs, packet)
} else if (values[fieldInfo.type] !== undefined) {
return values[fieldInfo.type]
} else if (fieldInfo.type !== 'void') {
throw new Error('No value for type ' + fieldInfo.type)
}
2015-10-01 20:02:54 -04:00
}
for (const supportedVersion of mc.supportedVersions) {
let PORT
const mcData = require('minecraft-data')(supportedVersion)
const version = mcData.version
const packets = mcData.protocol
describe('packets ' + supportedVersion + 'v', function () {
let client, server, serverClient
before(async function () {
PORT = await getPort()
server = new Server(version.minecraftVersion)
if (mcData.supportFeature('mcDataHasEntityMetadata')) {
values.entityMetadata[0].type = 'byte'
} else {
values.entityMetadata[0].type = 0
}
return new Promise((resolve) => {
console.log(`Using port for tests: ${PORT}`)
server.once('listening', function () {
server.once('connection', function (c) {
serverClient = c
resolve()
})
client = new Client(false, version.minecraftVersion)
client.setSocket(net.connect(PORT, 'localhost'))
})
server.listen(PORT, 'localhost')
})
})
after(function (done) {
client.on('end', function () {
server.on('close', done)
server.close()
})
client.end()
})
let packetInfo
Object.keys(packets).filter(function (state) { return state !== 'types' })
.forEach(function (state) {
Object.keys(packets[state]).forEach(function (direction) {
Object.keys(packets[state][direction].types)
.filter(function (packetName) {
return packetName !== 'packet' && packetName.startsWith('packet_')
})
.forEach(function (packetName) {
packetInfo = packets[state][direction].types[packetName]
packetInfo = packetInfo || null
if (packetName.includes('bundle_delimiter')) return // not a real packet
if (['packet_set_projectile_power', 'packet_debug_sample_subscription'].includes(packetName)) return
it(state + ',' + (direction === 'toServer' ? 'Server' : 'Client') + 'Bound,' + packetName,
callTestPacket(mcData, packetName.substr(7), packetInfo, state, direction === 'toServer'))
})
})
})
function callTestPacket (mcData, packetName, packetInfo, state, toServer) {
return function (done) {
client.state = state
serverClient.state = state
testPacket(mcData, packetName, packetInfo, state, toServer, done)
}
2015-10-01 20:02:54 -04:00
}
function testPacket (mcData, packetName, packetInfo, state, toServer, done) {
2015-10-01 20:02:54 -04:00
// empty object uses default values
const packet = getFixedPacketPayload(mcData.version, packetName) || getValue(packetInfo, {})
if (toServer) {
console.log('Writing to server', packetName, JSON.stringify(packet))
serverClient.once(packetName, function (receivedPacket) {
console.log('Recv', packetName)
2015-10-01 20:02:54 -04:00
try {
assertPacketsMatch(packet, receivedPacket)
2015-10-01 20:02:54 -04:00
} catch (e) {
console.log(packet, receivedPacket)
throw e
2015-10-01 20:02:54 -04:00
}
done()
})
client.write(packetName, packet)
2015-10-01 20:02:54 -04:00
} else {
console.log('Writing to client', packetName, JSON.stringify(packet))
client.once(packetName, function (receivedPacket) {
console.log('Recv', packetName)
assertPacketsMatch(packet, receivedPacket)
done()
})
serverClient.write(packetName, packet)
2015-10-01 20:02:54 -04:00
}
}
function assertPacketsMatch (p1, p2) {
packetInfo.forEach(function (field) {
assert.deepEqual(p1[field], p2[field])
})
Object.keys(p1).forEach(function (field) {
if (p1[field] !== undefined) {
assert.ok(field in p2, 'field ' + field +
' missing in p2, in p1 it has value ' + JSON.stringify(p1[field]))
}
})
Object.keys(p2).forEach(function (field) {
2020-05-16 10:57:04 -04:00
if (p2[field] !== undefined) {
assert.ok(field in p1, 'field ' + field + ' missing in p1, in p2 it has value ' +
JSON.stringify(p2[field]))
}
})
2015-10-01 20:02:54 -04:00
}
})
}