support 1.21.3 (#1347)

* support 1.21.3

* Add bitflags, registryEntryHolder and  registryEntryHolderSet types

* Fix spacing in compiler types

* Update compiler-minecraft.js

* Fix registryEntryHolderSet read code (#1351)

* Update ci.yml

* Fix test for 1.21.3 (#1353)

* Remove debug logging

* Fix benchmark tests for 1.21.3

* Start updating packetTest for 1.21.3

* Update packetTest.js with new types

* Fix minecraft-compiler

* Speedup tests by setting world type to flat and disabling structures.

* Didn't mean to commit that

---------

Co-authored-by: extremeheat <extreme@protonmail.ch>
Co-authored-by: Grooble <grooble.dierne@gmail.com>
This commit is contained in:
Romain Beaumont 2024-12-04 21:21:43 +01:00 committed by GitHub
parent 590dc33fed
commit 2224d82406
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 247 additions and 15 deletions

View file

@ -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.

View file

@ -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())
}]
}
}

View file

@ -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']
}

View file

@ -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: '<Bob> 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: '<Bob> 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 = []

View file

@ -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()

View file

@ -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) {