diff --git a/docs/README.md b/docs/README.md index 9736002..f8cb78a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,7 @@ Parse and serialize minecraft packets, plus authentication and encryption. * Supports Minecraft PC version 1.7.10, 1.8.8, 1.9 (15w40b, 1.9, 1.9.1-pre2, 1.9.2, 1.9.4), 1.10 (16w20a, 1.10-pre1, 1.10, 1.10.1, 1.10.2), 1.11 (16w35a, 1.11, 1.11.2), 1.12 (17w15a, 17w18b, 1.12-pre4, 1.12, 1.12.1, 1.12.2), and 1.13 (17w50a, 1.13, 1.13.1, 1.13.2-pre1, 1.13.2-pre2, 1.13.2), 1.14 (1.14, 1.14.1, 1.14.3, 1.14.4) - , 1.15 (1.15, 1.15.1, 1.15.2) and 1.16 (20w13b, 20w14a, 1.16-rc1, 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5), 1.17 (21w07a, 1.17, 1.17.1), 1.18 (1.18, 1.18.1 and 1.18.2), 1.19 (1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4, 1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6, 1.21.1) + , 1.15 (1.15, 1.15.1, 1.15.2) and 1.16 (20w13b, 20w14a, 1.16-rc1, 1.16, 1.16.1, 1.16.2, 1.16.3, 1.16.4, 1.16.5), 1.17 (21w07a, 1.17, 1.17.1), 1.18 (1.18, 1.18.1 and 1.18.2), 1.19 (1.19, 1.19.1, 1.19.2, 1.19.3, 1.19.4), 1.20 (1.20, 1.20.1, 1.20.2, 1.20.3, 1.20.4, 1.20.5, 1.20.6), 1.21 (1.21, 1.21.1, 1.21.3) * Parses all packets and emits events with packet fields as JavaScript objects. * Send a packet by supplying fields as a JavaScript object. diff --git a/src/datatypes/compiler-minecraft.js b/src/datatypes/compiler-minecraft.js index ba4db3e..864b269 100644 --- a/src/datatypes/compiler-minecraft.js +++ b/src/datatypes/compiler-minecraft.js @@ -1,3 +1,4 @@ +/* eslint-disable no-return-assign */ const UUID = require('uuid-1345') const minecraft = require('./minecraft') @@ -41,7 +42,7 @@ module.exports = { code += '}' return compiler.wrapCode(code) }], - arrayWithLengthOffset: ['parametrizable', (compiler, array) => { + arrayWithLengthOffset: ['parametrizable', (compiler, array) => { // TODO: remove let code = '' if (array.countType) { code += 'const { value: count, size: countSize } = ' + compiler.callType(array.countType) + '\n' @@ -61,6 +62,56 @@ module.exports = { code += '}\n' code += 'return { value: data, size }' return compiler.wrapCode(code) + }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` + const { value: _value, size } = ${compiler.callType(type, 'offset')} + const value = { _value } + const flags = ${fstr} + for (const key in flags) { + value[key] = (_value & flags[key]) == flags[key] + } + return { value, size } + `.trim()) + }], + registryEntryHolder: ['parametrizable', (compiler, opts) => { + return compiler.wrapCode(` +const { value: n, size: nSize } = ${compiler.callType('varint')} +if (n !== 0) { + return { value: { ${opts.baseName}: n - 1 }, size: nSize } +} else { + const holder = ${compiler.callType(opts.otherwise.type)} + return { value: { ${opts.otherwise.name}: holder.data }, size: nSize + holder.size } +} + `.trim()) + }], + registryEntryHolderSet: ['parametrizable', (compiler, opts) => { + return compiler.wrapCode(` + const { value: n, size: nSize } = ${compiler.callType('varint')} + if (n === 0) { + const base = ${compiler.callType(opts.base.type, 'offset + nSize')} + return { value: { ${opts.base.name}: base.value }, size: base.size + nSize } + } else { + const set = [] + let accSize = nSize + for (let i = 0; i < n - 1; i++) { + const entry = ${compiler.callType(opts.otherwise.type, 'offset + accSize')} + set.push(entry.value) + accSize += entry.size + } + return { value: { ${opts.otherwise.name}: set }, size: accSize } + } + `.trim()) }] }, Write: { @@ -106,6 +157,58 @@ module.exports = { code += '}\n' code += 'return offset' return compiler.wrapCode(code) + }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` + const flags = ${fstr} + let val = value._value ${big ? '|| 0n' : ''} + for (const key in flags) { + if (value[key]) val |= flags[key] + } + return (ctx.${type})(val, buffer, offset) + `.trim()) + }], + registryEntryHolder: ['parametrizable', (compiler, opts) => { + const baseName = `value.${opts.baseName}` + const otherwiseName = `value.${opts.otherwise.name}` + return compiler.wrapCode(` +if (${baseName}) { + offset = ${compiler.callType(`${baseName} + 1`, 'varint')} +} else if (${otherwiseName}) { + offset = ${compiler.callType(`${otherwiseName}`, opts.otherwise.type)} +} else { + throw new Error('registryEntryHolder type requires "${baseName}" or "${otherwiseName}" fields to be set') +} +return offset + `.trim()) + }], + registryEntryHolderSet: ['parametrizable', (compiler, opts) => { + const baseName = `value.${opts.base.name}` + const otherwiseName = `value.${opts.otherwise.name}` + return compiler.wrapCode(` +if (${baseName}) { + offset = ${compiler.callType(0, 'varint')} + offset = ${compiler.callType(`${baseName}`, opts.base.type)} +} else if (${otherwiseName}) { + offset = ${compiler.callType(`${otherwiseName}.length + 1`, 'varint')} + for (let i = 0; i < ${otherwiseName}.length; i++) { + offset = ${compiler.callType(`${otherwiseName}[i]`, opts.otherwise.type)} + } +} else { + throw new Error('registryEntryHolder type requires "${opts.base.name}" or "${opts.otherwise.name}" fields to be set') +} +return offset + `.trim()) }] }, SizeOf: { @@ -149,6 +252,60 @@ module.exports = { } code += 'return size' return compiler.wrapCode(code) + }], + bitflags: ['parametrizable', (compiler, { type, flags, shift, big }) => { + let fstr = JSON.stringify(flags) + if (Array.isArray(flags)) { + fstr = '{' + for (const [k, v] of Object.entries(flags)) fstr += `"${v}": ${big ? (1n << BigInt(k)) : (1 << k)}` + (big ? 'n,' : ',') + fstr += '}' + } else if (shift) { + fstr = '{' + for (const key in flags) fstr += `"${key}": ${1 << flags[key]},` + fstr += '}' + } + return compiler.wrapCode(` + const flags = ${fstr} + let val = value._value ${big ? '|| 0n' : ''} + for (const key in flags) { + if (value[key]) val |= flags[key] + } + return (ctx.${type})(val) + `.trim()) + }], + registryEntryHolder: ['parametrizable', (compiler, opts) => { + const baseName = `value.${opts.baseName}` + const otherwiseName = `value.${opts.otherwise.name}` + return compiler.wrapCode(` +let size = 0 +if (${baseName}) { + size += ${compiler.callType(`${baseName} + 1`, 'varint')} +} else if (${otherwiseName}) { + size += ${compiler.callType(`${otherwiseName}`, opts.otherwise.type)} +} else { + throw new Error('registryEntryHolder type requires "${baseName}" or "${otherwiseName}" fields to be set') +} +return size + `.trim()) + }], + registryEntryHolderSet: ['parametrizable', (compiler, opts) => { + const baseName = `value.${opts.base.name}` + const otherwiseName = `value.${opts.otherwise.name}` + return compiler.wrapCode(` +let size = 0 +if (${baseName}) { + size += ${compiler.callType(0, 'varint')} + size += ${compiler.callType(`${baseName}`, opts.base.type)} +} else if (${otherwiseName}) { + size += ${compiler.callType(`${otherwiseName}.length + 1`, 'varint')} + for (let i = 0; i < ${otherwiseName}.length; i++) { + size += ${compiler.callType(`${otherwiseName}[i]`, opts.otherwise.type)} + } +} else { + throw new Error('registryEntryHolder type requires "${opts.base.name}" or "${opts.otherwise.name}" fields to be set') +} +return size + `.trim()) }] } } diff --git a/src/version.js b/src/version.js index 327c242..29d71e7 100644 --- a/src/version.js +++ b/src/version.js @@ -2,5 +2,5 @@ module.exports = { defaultVersion: '1.21.1', - supportedVersions: ['1.7', '1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1'] + supportedVersions: ['1.7', '1.8.8', '1.9.4', '1.10.2', '1.11.2', '1.12.2', '1.13.2', '1.14.4', '1.15.2', '1.16.5', '1.17.1', '1.18.2', '1.19', '1.19.2', '1.19.3', '1.19.4', '1.20', '1.20.1', '1.20.2', '1.20.4', '1.20.6', '1.21.1', '1.21.3'] } diff --git a/test/benchmark.js b/test/benchmark.js index a461037..1d24cd7 100644 --- a/test/benchmark.js +++ b/test/benchmark.js @@ -5,17 +5,18 @@ const ITERATIONS = 10000 const mc = require('../') const states = mc.states -const testDataWrite = [ - { name: 'keep_alive', params: { keepAliveId: 957759560 } }, - // TODO: 1.19+ `chat` -> `player_chat` feature toggle - // { name: 'chat', params: { message: ' Hello World!' } }, - { name: 'position_look', params: { x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, onGround: true } } - // TODO: add more packets for better quality data -] - for (const supportedVersion of mc.supportedVersions) { const mcData = require('minecraft-data')(supportedVersion) const version = mcData.version + const positionFlags = mcData.isNewerOrEqualTo('1.21.3') ? { flags: { onGround: true, hasHorizontalCollision: false } } : { onGround: true } + const testDataWrite = [ + { name: 'keep_alive', params: { keepAliveId: 957759560 } }, + // TODO: 1.19+ `chat` -> `player_chat` feature toggle + // { name: 'chat', params: { message: ' Hello World!' } }, + { name: 'position_look', params: { x: 6.5, y: 65.62, stance: 67.24, z: 7.5, yaw: 0, pitch: 0, ...positionFlags } } + // TODO: add more packets for better quality data + ] + describe('benchmark ' + supportedVersion + 'v', function () { this.timeout(60 * 1000) const inputData = [] diff --git a/test/clientTest.js b/test/clientTest.js index 67eb8ec..ef46d34 100644 --- a/test/clientTest.js +++ b/test/clientTest.js @@ -59,7 +59,8 @@ for (const supportedVersion of mc.supportedVersions) { 'server-port': PORT, motd: 'test1234', 'max-players': 120, - // 'level-type': 'flat', + 'level-type': 'flat', + 'generate-structures': 'false', // 12m 'use-native-transport': 'false' // java 16 throws errors without this, https://www.spigotmc.org/threads/unable-to-access-address-of-buffer.311602 }, (err) => { if (err) reject(err) @@ -191,7 +192,6 @@ for (const supportedVersion of mc.supportedVersions) { } } else { // 1.19+ - console.log('Chat Message', data) const sender = JSON.parse(data.senderName) const msgPayload = data.formattedMessage ? JSON.parse(data.formattedMessage) : data.plainMessage const plainMessage = client.parseMessage(msgPayload).toString() diff --git a/test/packetTest.js b/test/packetTest.js index 534d28e..7f89b95 100644 --- a/test/packetTest.js +++ b/test/packetTest.js @@ -57,7 +57,32 @@ const nbtValue = { function getFixedPacketPayload (version, packetName) { if (packetName === 'declare_recipes') { - if (version['>=']('1.20.5')) { + 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: [ { @@ -241,6 +266,13 @@ const values = { 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, @@ -263,7 +295,49 @@ const values = { 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] } } function getValue (_type, packet) {