mirror of
https://github.com/PrismarineJS/node-minecraft-protocol.git
synced 2024-11-23 15:57:50 -05:00
Pc1.20.2 (#1265)
* Initial changes for 1.20.2 * add NBT serialize tag type handling * update tests * Update pnbt and mcdata for nbt change * lint * fix wrong param to sizeOfNbt * fix dupe NBT types * move nbt logic to prismarine-nbt * update tests * update tests * disable protodef validator in pluginChannel * Fix state desync * dump loginPacket.json in test output * enable validation * remove testing line in ci.yml * update pnbt to 2.5.0 * update doc for `playerJoin` * Update serializer.js * update examples * lint * disable client bundle handling if bundle becomes too big * Update client.js * bump mcdata * add soundSource and packedChunkPos example test values --------- Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
This commit is contained in:
parent
1740124c47
commit
112926da0c
25 changed files with 212 additions and 178 deletions
|
@ -89,6 +89,11 @@ Called when a client connects, but before any login has happened. Takes a
|
|||
|
||||
Called when a client is logged in against server. Takes a `Client` parameter.
|
||||
|
||||
### `playerJoin` event
|
||||
|
||||
Emitted after a player joins and enters the PLAY protocol state and can send and recieve game packets. This is emitted after the `login` event. On 1.20.2 and above after we emit the `login` event, the player will enter a CONFIG state, as opposed to the PLAY state (where game packets can be sent), so you must instead now wait for `playerJoin`.
|
||||
|
||||
|
||||
### `listening` event
|
||||
|
||||
Called when the server is listening for connections. This means that the server is ready to accept incoming connections.
|
||||
|
@ -261,6 +266,10 @@ Called when user authentication is resolved. Takes session data as parameter.
|
|||
Called when the protocol changes state. Takes the new state and old state as
|
||||
parameters.
|
||||
|
||||
### `playerJoin` event
|
||||
|
||||
Emitted after the player enters the PLAY protocol state and can send and recieve game packets
|
||||
|
||||
### `error` event
|
||||
|
||||
Called when an error occurs within the client. Takes an Error as parameter.
|
||||
|
|
|
@ -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.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.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.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)
|
||||
* Parses all packets and emits events with packet fields as JavaScript
|
||||
objects.
|
||||
* Send a packet by supplying fields as a JavaScript object.
|
||||
|
@ -115,6 +115,8 @@ const client = mc.createClient({
|
|||
|
||||
### Hello World server example
|
||||
|
||||
For a more up to date example, see examples/server/server.js.
|
||||
|
||||
```js
|
||||
const mc = require('minecraft-protocol');
|
||||
const server = mc.createServer({
|
||||
|
@ -126,18 +128,12 @@ const server = mc.createServer({
|
|||
});
|
||||
const mcData = require('minecraft-data')(server.version)
|
||||
|
||||
server.on('login', function(client) {
|
||||
server.on('playerJoin', function(client) {
|
||||
const loginPacket = mcData.loginPacket
|
||||
|
||||
client.write('login', {
|
||||
...loginPacket,
|
||||
entityId: client.id,
|
||||
isHardcore: false,
|
||||
gameMode: 0,
|
||||
previousGameMode: 255,
|
||||
worldNames: loginPacket.worldNames,
|
||||
dimensionCodec: loginPacket.dimensionCodec,
|
||||
dimension: loginPacket.dimension,
|
||||
worldName: 'minecraft:overworld',
|
||||
hashedSeed: [0, 0],
|
||||
maxPlayers: server.maxPlayers,
|
||||
viewDistance: 10,
|
||||
|
|
|
@ -11,7 +11,7 @@ const server = mc.createServer(options)
|
|||
const mcData = require('minecraft-data')(server.version)
|
||||
const loginPacket = mcData.loginPacket
|
||||
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
broadcast(client.username + ' joined the game.')
|
||||
const addr = client.socket.remoteAddress + ':' + client.socket.remotePort
|
||||
console.log(client.username + ' connected', '(' + addr + ')')
|
||||
|
@ -23,14 +23,11 @@ server.on('login', function (client) {
|
|||
|
||||
// send init data so client will start rendering world
|
||||
client.write('login', {
|
||||
...loginPacket,
|
||||
entityId: client.id,
|
||||
isHardcore: false,
|
||||
gameMode: 0,
|
||||
previousGameMode: 1,
|
||||
worldNames: loginPacket.worldNames,
|
||||
dimensionCodec: loginPacket.dimensionCodec,
|
||||
dimension: loginPacket.dimension,
|
||||
worldName: 'minecraft:overworld',
|
||||
hashedSeed: [0, 0],
|
||||
maxPlayers: server.maxPlayers,
|
||||
viewDistance: 10,
|
||||
|
@ -48,11 +45,13 @@ server.on('login', function (client) {
|
|||
flags: 0x00
|
||||
})
|
||||
|
||||
client.on('chat', function (data) {
|
||||
function handleChat (data) {
|
||||
const message = '<' + client.username + '>' + ' ' + data.message
|
||||
broadcast(message, null, client.username)
|
||||
console.log(message)
|
||||
})
|
||||
}
|
||||
client.on('chat', handleChat) // pre-1.19
|
||||
client.on('chat_message', handleChat) // post 1.19
|
||||
})
|
||||
|
||||
server.on('error', function (error) {
|
||||
|
@ -63,27 +62,28 @@ server.on('listening', function () {
|
|||
console.log('Server listening on port', server.socketServer.address().port)
|
||||
})
|
||||
|
||||
function broadcast (message, exclude, username) {
|
||||
let client
|
||||
const translate = username ? 'chat.type.announcement' : 'chat.type.text'
|
||||
username = username || 'Server'
|
||||
for (const clientId in server.clients) {
|
||||
if (server.clients[clientId] === undefined) continue
|
||||
|
||||
client = server.clients[clientId]
|
||||
if (client !== exclude) {
|
||||
const msg = {
|
||||
translate,
|
||||
with: [
|
||||
username,
|
||||
message
|
||||
]
|
||||
}
|
||||
client.write('chat', {
|
||||
message: JSON.stringify(msg),
|
||||
position: 0,
|
||||
sender: '0'
|
||||
})
|
||||
}
|
||||
function sendBroadcastMessage (server, clients, message, sender) {
|
||||
if (mcData.supportFeature('signedChat')) {
|
||||
server.writeToClients(clients, 'player_chat', {
|
||||
plainMessage: message,
|
||||
signedChatContent: '',
|
||||
unsignedChatContent: JSON.stringify({ text: message }),
|
||||
type: 0,
|
||||
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
|
||||
senderName: JSON.stringify({ text: sender }),
|
||||
senderTeam: undefined,
|
||||
timestamp: Date.now(),
|
||||
salt: 0n,
|
||||
signature: mcData.supportFeature('useChatSessions') ? undefined : Buffer.alloc(0),
|
||||
previousMessages: [],
|
||||
filterType: 0,
|
||||
networkName: JSON.stringify({ text: sender })
|
||||
})
|
||||
} else {
|
||||
server.writeToClients(clients, 'chat', { message: JSON.stringify({ text: message }), position: 0, sender: sender || '0' })
|
||||
}
|
||||
}
|
||||
|
||||
function broadcast (message, exclude, username) {
|
||||
sendBroadcastMessage(server, Object.values(server.clients).filter(client => client !== exclude), message)
|
||||
}
|
||||
|
|
|
@ -8,18 +8,16 @@ const server = mc.createServer({
|
|||
const mcData = require('minecraft-data')(server.version)
|
||||
const loginPacket = mcData.loginPacket
|
||||
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
client.registerChannel('minecraft:brand', ['string', []])
|
||||
client.on('minecraft:brand', console.log)
|
||||
|
||||
client.write('login', {
|
||||
...loginPacket,
|
||||
entityId: client.id,
|
||||
isHardcore: false,
|
||||
gameMode: 0,
|
||||
previousGameMode: 1,
|
||||
worldNames: loginPacket.worldNames,
|
||||
dimensionCodec: loginPacket.dimensionCodec,
|
||||
dimension: loginPacket.dimension,
|
||||
worldName: 'minecraft:overworld',
|
||||
hashedSeed: [0, 0],
|
||||
maxPlayers: server.maxPlayers,
|
||||
|
|
|
@ -8,15 +8,13 @@ const server = mc.createServer({
|
|||
const mcData = require('minecraft-data')(server.version)
|
||||
const loginPacket = mcData.loginPacket
|
||||
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
client.write('login', {
|
||||
...loginPacket,
|
||||
entityId: client.id,
|
||||
isHardcore: false,
|
||||
gameMode: 0,
|
||||
previousGameMode: 1,
|
||||
worldNames: loginPacket.worldNames,
|
||||
dimensionCodec: loginPacket.dimensionCodec,
|
||||
dimension: loginPacket.dimension,
|
||||
worldName: 'minecraft:overworld',
|
||||
hashedSeed: [0, 0],
|
||||
maxPlayers: server.maxPlayers,
|
||||
|
|
|
@ -9,7 +9,7 @@ const server = mc.createServer(options)
|
|||
const mcData = require('minecraft-data')(server.version)
|
||||
const loginPacket = mcData.loginPacket
|
||||
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
const addr = client.socket.remoteAddress
|
||||
console.log('Incoming connection', '(' + addr + ')')
|
||||
|
||||
|
|
|
@ -22,15 +22,13 @@ for (let x = 0; x < 16; x++) {
|
|||
}
|
||||
}
|
||||
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
client.write('login', {
|
||||
...loginPacket,
|
||||
entityId: client.id,
|
||||
isHardcore: false,
|
||||
gameMode: 0,
|
||||
previousGameMode: 1,
|
||||
worldNames: loginPacket.worldNames,
|
||||
dimensionCodec: loginPacket.dimensionCodec,
|
||||
dimension: loginPacket.dimension,
|
||||
worldName: 'minecraft:overworld',
|
||||
hashedSeed: [0, 0],
|
||||
maxPlayers: server.maxPlayers,
|
||||
|
|
|
@ -51,12 +51,12 @@
|
|||
"endian-toggle": "^0.0.0",
|
||||
"lodash.get": "^4.1.2",
|
||||
"lodash.merge": "^4.3.0",
|
||||
"minecraft-data": "^3.37.0",
|
||||
"minecraft-data": "^3.53.0",
|
||||
"minecraft-folder-path": "^1.2.0",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-rsa": "^0.4.2",
|
||||
"prismarine-auth": "^2.2.0",
|
||||
"prismarine-nbt": "^2.0.0",
|
||||
"prismarine-nbt": "^2.5.0",
|
||||
"prismarine-realms": "^1.2.0",
|
||||
"protodef": "^1.8.0",
|
||||
"readable-stream": "^4.1.0",
|
||||
|
|
|
@ -93,7 +93,7 @@ class Client extends EventEmitter {
|
|||
const s = JSON.stringify(parsed.data, null, 2)
|
||||
debug(s && s.length > 10000 ? parsed.data : s)
|
||||
}
|
||||
if (parsed.metadata.name === 'bundle_delimiter') {
|
||||
if (this._hasBundlePacket && parsed.metadata.name === 'bundle_delimiter') {
|
||||
if (this._mcBundle.length) { // End bundle
|
||||
this._mcBundle.forEach(emitPacket)
|
||||
emitPacket(parsed)
|
||||
|
@ -103,6 +103,11 @@ class Client extends EventEmitter {
|
|||
}
|
||||
} else if (this._mcBundle.length) {
|
||||
this._mcBundle.push(parsed)
|
||||
if (this._mcBundle.length > 32) {
|
||||
this._mcBundle.forEach(emitPacket)
|
||||
this._mcBundle = []
|
||||
this._hasBundlePacket = false
|
||||
}
|
||||
} else {
|
||||
emitPacket(parsed)
|
||||
}
|
||||
|
|
|
@ -32,30 +32,54 @@ module.exports = function (client, options) {
|
|||
|
||||
function onLogin (packet) {
|
||||
const mcData = require('minecraft-data')(client.version)
|
||||
client.state = states.PLAY
|
||||
client.uuid = packet.uuid
|
||||
client.username = packet.username
|
||||
|
||||
if (mcData.supportFeature('signedChat')) {
|
||||
if (options.disableChatSigning && client.serverFeatures.enforcesSecureChat) {
|
||||
throw new Error('"disableChatSigning" was enabled in client options, but server is enforcing secure chat')
|
||||
}
|
||||
signedChatPlugin(client, options)
|
||||
if (mcData.supportFeature('hasConfigurationState')) {
|
||||
client.write('login_acknowledged', {})
|
||||
enterConfigState()
|
||||
// Server can tell client to re-enter config state
|
||||
client.on('start_configuration', enterConfigState)
|
||||
} else {
|
||||
client.on('chat', (packet) => {
|
||||
client.emit(packet.position === 0 ? 'playerChat' : 'systemChat', {
|
||||
formattedMessage: packet.message,
|
||||
sender: packet.sender,
|
||||
positionId: packet.position,
|
||||
verified: false
|
||||
})
|
||||
client.state = states.PLAY
|
||||
onReady()
|
||||
}
|
||||
|
||||
function enterConfigState () {
|
||||
if (client.state === states.CONFIGURATION) return
|
||||
client.state = states.CONFIGURATION
|
||||
// Server should send finish_configuration on its own right after sending the client a dimension codec
|
||||
// for login (that has data about world height, world gen, etc) after getting a login success from client
|
||||
client.once('finish_configuration', () => {
|
||||
client.write('finish_configuration', {})
|
||||
client.state = states.PLAY
|
||||
onReady()
|
||||
})
|
||||
}
|
||||
|
||||
function unsignedChat (message) {
|
||||
client.write('chat', { message })
|
||||
}
|
||||
function onReady () {
|
||||
client.emit('playerJoin')
|
||||
if (mcData.supportFeature('signedChat')) {
|
||||
if (options.disableChatSigning && client.serverFeatures.enforcesSecureChat) {
|
||||
throw new Error('"disableChatSigning" was enabled in client options, but server is enforcing secure chat')
|
||||
}
|
||||
signedChatPlugin(client, options)
|
||||
} else {
|
||||
client.on('chat', (packet) => {
|
||||
client.emit(packet.position === 0 ? 'playerChat' : 'systemChat', {
|
||||
formattedMessage: packet.message,
|
||||
sender: packet.sender,
|
||||
positionId: packet.position,
|
||||
verified: false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
client.chat = client._signedChat || unsignedChat
|
||||
function unsignedChat (message) {
|
||||
client.write('chat', { message })
|
||||
}
|
||||
|
||||
client.chat = client._signedChat || unsignedChat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
const ProtoDef = require('protodef').ProtoDef
|
||||
const minecraft = require('../datatypes/minecraft')
|
||||
const debug = require('debug')('minecraft-protocol')
|
||||
const nbt = require('prismarine-nbt')
|
||||
|
||||
module.exports = function (client, options) {
|
||||
const mcdata = require('minecraft-data')(options.version || require('../version').defaultVersion)
|
||||
const channels = []
|
||||
const proto = new ProtoDef(options.validateChannelProtocol ?? true)
|
||||
nbt.addTypesToInterpreter('big', proto)
|
||||
proto.addTypes(mcdata.protocol.types)
|
||||
proto.addTypes(minecraft)
|
||||
proto.addType('registerarr', [readDumbArr, writeDumbArr, sizeOfDumbArr])
|
||||
|
|
|
@ -37,7 +37,7 @@ module.exports = function (client, options) {
|
|||
: client.profileKeys.signature
|
||||
}
|
||||
: null,
|
||||
playerUUID: client.session?.selectedProfile?.id
|
||||
playerUUID: client.session?.selectedProfile?.id ?? client.uuid
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ const tcpDns = require('./client/tcp_dns')
|
|||
const autoVersion = require('./client/autoVersion')
|
||||
const pluginChannels = require('./client/pluginChannels')
|
||||
const versionChecking = require('./client/versionChecking')
|
||||
const uuid = require('./datatypes/uuid')
|
||||
|
||||
module.exports = createClient
|
||||
|
||||
|
@ -54,6 +55,8 @@ function createClient (options) {
|
|||
case 'offline':
|
||||
default:
|
||||
client.username = options.username
|
||||
client.uuid = uuid.nameToMcOfflineUUID(client.username)
|
||||
options.auth = 'offline'
|
||||
options.connect(client)
|
||||
break
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@ function createServer (options = {}) {
|
|||
server.onlineModeExceptions = Object.create(null)
|
||||
server.favicon = favicon
|
||||
server.options = options
|
||||
options.registryCodec = options.registryCodec || mcData.registryCodec || mcData.loginPacket?.dimensionCodec
|
||||
|
||||
// The RSA keypair can take some time to generate
|
||||
// and is only needed for online-mode
|
||||
|
|
|
@ -16,8 +16,6 @@ module.exports = {
|
|||
size: buffer.length - offset
|
||||
}
|
||||
}],
|
||||
nbt: ['native', minecraft.nbt[0]],
|
||||
optionalNbt: ['native', minecraft.optionalNbt[0]],
|
||||
compressedNbt: ['native', minecraft.compressedNbt[0]],
|
||||
entityMetadataLoop: ['parametrizable', (compiler, { type, endVal }) => {
|
||||
let code = 'let cursor = offset\n'
|
||||
|
@ -55,8 +53,6 @@ module.exports = {
|
|||
value.copy(buffer, offset)
|
||||
return offset + value.length
|
||||
}],
|
||||
nbt: ['native', minecraft.nbt[1]],
|
||||
optionalNbt: ['native', minecraft.optionalNbt[1]],
|
||||
compressedNbt: ['native', minecraft.compressedNbt[1]],
|
||||
entityMetadataLoop: ['parametrizable', (compiler, { type, endVal }) => {
|
||||
let code = 'for (const i in value) {\n'
|
||||
|
@ -84,8 +80,6 @@ module.exports = {
|
|||
restBuffer: ['native', (value) => {
|
||||
return value.length
|
||||
}],
|
||||
nbt: ['native', minecraft.nbt[2]],
|
||||
optionalNbt: ['native', minecraft.optionalNbt[2]],
|
||||
compressedNbt: ['native', minecraft.compressedNbt[2]],
|
||||
entityMetadataLoop: ['parametrizable', (compiler, { type }) => {
|
||||
let code = 'let size = 1\n'
|
||||
|
|
|
@ -8,8 +8,6 @@ const [readVarInt, writeVarInt, sizeOfVarInt] = require('protodef').types.varint
|
|||
module.exports = {
|
||||
varlong: [readVarLong, writeVarLong, sizeOfVarLong],
|
||||
UUID: [readUUID, writeUUID, 16],
|
||||
nbt: [readNbt, writeNbt, sizeOfNbt],
|
||||
optionalNbt: [readOptionalNbt, writeOptionalNbt, sizeOfOptionalNbt],
|
||||
compressedNbt: [readCompressedNbt, writeCompressedNbt, sizeOfCompressedNbt],
|
||||
restBuffer: [readRestBuffer, writeRestBuffer, sizeOfRestBuffer],
|
||||
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
|
||||
|
@ -43,35 +41,8 @@ function writeUUID (value, buffer, offset) {
|
|||
return offset + 16
|
||||
}
|
||||
|
||||
function readNbt (buffer, offset) {
|
||||
return nbt.proto.read(buffer, offset, 'nbt')
|
||||
}
|
||||
|
||||
function writeNbt (value, buffer, offset) {
|
||||
return nbt.proto.write(value, buffer, offset, 'nbt')
|
||||
}
|
||||
|
||||
function sizeOfNbt (value) {
|
||||
return nbt.proto.sizeOf(value, 'nbt')
|
||||
}
|
||||
|
||||
function readOptionalNbt (buffer, offset) {
|
||||
if (offset + 1 > buffer.length) { throw new PartialReadError() }
|
||||
if (buffer.readInt8(offset) === 0) return { size: 1 }
|
||||
return nbt.proto.read(buffer, offset, 'nbt')
|
||||
}
|
||||
|
||||
function writeOptionalNbt (value, buffer, offset) {
|
||||
if (value === undefined) {
|
||||
buffer.writeInt8(0, offset)
|
||||
return offset + 1
|
||||
}
|
||||
return nbt.proto.write(value, buffer, offset, 'nbt')
|
||||
}
|
||||
|
||||
function sizeOfOptionalNbt (value) {
|
||||
if (value === undefined) { return 1 }
|
||||
return nbt.proto.sizeOf(value, 'nbt')
|
||||
function sizeOfNbt (value, { tagType } = { tagType: 'nbt' }) {
|
||||
return nbt.proto.sizeOf(value, tagType)
|
||||
}
|
||||
|
||||
// Length-prefixed compressed NBT, see differences: http://wiki.vg/index.php?title=Slot_Data&diff=6056&oldid=4753
|
||||
|
@ -111,7 +82,7 @@ function writeCompressedNbt (value, buffer, offset) {
|
|||
function sizeOfCompressedNbt (value) {
|
||||
if (value === undefined) { return 2 }
|
||||
|
||||
const nbtBuffer = Buffer.alloc(sizeOfNbt(value, 'nbt'))
|
||||
const nbtBuffer = Buffer.alloc(sizeOfNbt(value, { tagType: 'nbt' }))
|
||||
nbt.proto.write(value, nbtBuffer, 0, 'nbt')
|
||||
|
||||
const compressedNbt = zlib.gzipSync(nbtBuffer) // TODO: async
|
||||
|
|
18
src/datatypes/uuid.js
Normal file
18
src/datatypes/uuid.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
const crypto = require('crypto')
|
||||
const UUID = require('uuid-1345')
|
||||
|
||||
// https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/UUID.java#L163
|
||||
function javaUUID (s) {
|
||||
const hash = crypto.createHash('md5')
|
||||
hash.update(s, 'utf8')
|
||||
const buffer = hash.digest()
|
||||
buffer[6] = (buffer[6] & 0x0f) | 0x30
|
||||
buffer[8] = (buffer[8] & 0x3f) | 0x80
|
||||
return buffer
|
||||
}
|
||||
|
||||
function nameToMcOfflineUUID (name) {
|
||||
return (new UUID(javaUUID('OfflinePlayer:' + name))).toString()
|
||||
}
|
||||
|
||||
module.exports = { nameToMcOfflineUUID }
|
4
src/index.d.ts
vendored
4
src/index.d.ts
vendored
|
@ -51,6 +51,8 @@ declare module 'minecraft-protocol' {
|
|||
on(event: `raw.${string}`, handler: (buffer: Buffer, packetMeta: PacketMeta) => PromiseLike): this
|
||||
on(event: 'playerChat', handler: (data: { formattedMessage: string, message: string, type: string, sender: string, senderName: string, senderTeam: string, verified?: boolean }) => PromiseLike): this
|
||||
on(event: 'systemChat', handler: (data: { positionId: number, formattedMessage: string }) => PromiseLike): this
|
||||
// Emitted after the player enters the PLAY state and can send and recieve game packets
|
||||
on(event: 'playerJoin', handler: () => void): this
|
||||
once(event: 'error', listener: (error: Error) => PromiseLike): this
|
||||
once(event: 'packet', handler: (data: any, packetMeta: PacketMeta, buffer: Buffer, fullBuffer: Buffer) => PromiseLike): this
|
||||
once(event: 'raw', handler: (buffer: Buffer, packetMeta: PacketMeta) => PromiseLike): this
|
||||
|
@ -156,6 +158,8 @@ declare module 'minecraft-protocol' {
|
|||
on(event: 'error', listener: (error: Error) => PromiseLike): this
|
||||
on(event: 'login', handler: (client: ServerClient) => PromiseLike): this
|
||||
on(event: 'listening', listener: () => PromiseLike): this
|
||||
// Emitted after the player enters the PLAY state and can send and recieve game packets
|
||||
on(event: 'playerJoin', handler: (client: ServerClient) => void): this
|
||||
once(event: 'connection', handler: (client: ServerClient) => PromiseLike): this
|
||||
once(event: 'error', listener: (error: Error) => PromiseLike): this
|
||||
once(event: 'login', handler: (client: ServerClient) => PromiseLike): this
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const UUID = require('uuid-1345')
|
||||
const uuid = require('../datatypes/uuid')
|
||||
const crypto = require('crypto')
|
||||
const pluginChannels = require('../client/pluginChannels')
|
||||
const states = require('../states')
|
||||
|
@ -166,37 +166,28 @@ module.exports = function (client, server, options) {
|
|||
}
|
||||
}
|
||||
|
||||
// https://github.com/openjdk-mirror/jdk7u-jdk/blob/f4d80957e89a19a29bb9f9807d2a28351ed7f7df/src/share/classes/java/util/UUID.java#L163
|
||||
function javaUUID (s) {
|
||||
const hash = crypto.createHash('md5')
|
||||
hash.update(s, 'utf8')
|
||||
const buffer = hash.digest()
|
||||
buffer[6] = (buffer[6] & 0x0f) | 0x30
|
||||
buffer[8] = (buffer[8] & 0x3f) | 0x80
|
||||
return buffer
|
||||
}
|
||||
|
||||
function nameToMcOfflineUUID (name) {
|
||||
return (new UUID(javaUUID('OfflinePlayer:' + name))).toString()
|
||||
}
|
||||
|
||||
function loginClient () {
|
||||
const isException = !!server.onlineModeExceptions[client.username.toLowerCase()]
|
||||
if (onlineMode === false || isException) {
|
||||
client.uuid = nameToMcOfflineUUID(client.username)
|
||||
client.uuid = uuid.nameToMcOfflineUUID(client.username)
|
||||
}
|
||||
options.beforeLogin?.(client)
|
||||
if (client.protocolVersion >= 27) { // 14w28a (27) added whole-protocol compression (http://wiki.vg/Protocol_History#14w28a), earlier versions per-packet compressed TODO: refactor into minecraft-data
|
||||
client.write('compress', { threshold: 256 }) // Default threshold is 256
|
||||
client.compressionThreshold = 256
|
||||
}
|
||||
// TODO: find out what properties are on 'success' packet
|
||||
client.write('success', {
|
||||
uuid: client.uuid,
|
||||
username: client.username,
|
||||
properties: []
|
||||
})
|
||||
// TODO: find out what properties are on 'success' packet
|
||||
client.state = states.PLAY
|
||||
if (client.supportFeature('hasConfigurationState')) {
|
||||
client.once('login_acknowledged', onClientLoginAck)
|
||||
} else {
|
||||
client.state = states.PLAY
|
||||
server.emit('playerJoin', client)
|
||||
}
|
||||
client.settings = {}
|
||||
|
||||
if (client.supportFeature('chainedChatWithHashing')) { // 1.19.1+
|
||||
|
@ -217,6 +208,16 @@ module.exports = function (client, server, options) {
|
|||
if (client.supportFeature('signedChat')) chatPlugin(client, server, options)
|
||||
server.emit('login', client)
|
||||
}
|
||||
|
||||
function onClientLoginAck () {
|
||||
client.state = states.CONFIGURATION
|
||||
client.write('registry_data', { codec: options.registryCodec || {} })
|
||||
client.once('finish_configuration', () => {
|
||||
client.state = states.PLAY
|
||||
server.emit('playerJoin', client)
|
||||
})
|
||||
client.write('finish_configuration', {})
|
||||
}
|
||||
}
|
||||
|
||||
function mcPubKeyToPem (mcPubKeyBuffer) {
|
||||
|
|
|
@ -4,6 +4,7 @@ const states = {
|
|||
HANDSHAKING: 'handshaking',
|
||||
STATUS: 'status',
|
||||
LOGIN: 'login',
|
||||
CONFIGURATION: 'configuration',
|
||||
PLAY: 'play'
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ const Serializer = require('protodef').Serializer
|
|||
const Parser = require('protodef').FullPacketParser
|
||||
const { ProtoDefCompiler } = require('protodef').Compiler
|
||||
|
||||
const nbt = require('prismarine-nbt')
|
||||
const minecraft = require('../datatypes/minecraft')
|
||||
const states = require('../states')
|
||||
const merge = require('lodash.merge')
|
||||
|
@ -30,6 +31,7 @@ function createProtocol (state, direction, version, customPackets, compiled = tr
|
|||
const compiler = new ProtoDefCompiler()
|
||||
compiler.addTypes(require('../datatypes/compiler-minecraft'))
|
||||
compiler.addProtocol(merge(mcData.protocol, get(customPackets, [mcData.version.majorVersion])), [state, direction])
|
||||
nbt.addTypesToCompiler('big', compiler)
|
||||
const proto = compiler.compileProtoDefSync()
|
||||
protocols[key] = proto
|
||||
return proto
|
||||
|
@ -38,6 +40,7 @@ function createProtocol (state, direction, version, customPackets, compiled = tr
|
|||
const proto = new ProtoDef(false)
|
||||
proto.addTypes(minecraft)
|
||||
proto.addProtocol(merge(mcData.protocol, get(customPackets, [mcData.version.majorVersion])), [state, direction])
|
||||
nbt.addTypesToInterperter('big', proto)
|
||||
protocols[key] = proto
|
||||
return proto
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
'use strict'
|
||||
|
||||
module.exports = {
|
||||
defaultVersion: '1.20.1',
|
||||
supportedVersions: ['1.7', '1.8', '1.9', '1.10', '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']
|
||||
defaultVersion: '1.20.2',
|
||||
supportedVersions: ['1.7', '1.8', '1.9', '1.10', '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']
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
const mc = require('../')
|
||||
const os = require('os')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const assert = require('power-assert')
|
||||
const util = require('util')
|
||||
|
@ -20,7 +21,8 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
const version = mcData.version
|
||||
const MC_SERVER_JAR_DIR = process.env.MC_SERVER_JAR_DIR || os.tmpdir()
|
||||
const MC_SERVER_JAR = MC_SERVER_JAR_DIR + '/minecraft_server.' + version.minecraftVersion + '.jar'
|
||||
const wrap = new Wrap(MC_SERVER_JAR, MC_SERVER_PATH + '_' + supportedVersion, {
|
||||
const MC_SERVER_DIR = MC_SERVER_PATH + '_' + supportedVersion
|
||||
const wrap = new Wrap(MC_SERVER_JAR, MC_SERVER_DIR, {
|
||||
minMem: 1024,
|
||||
maxMem: 1024
|
||||
})
|
||||
|
@ -118,7 +120,23 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
assert.strictEqual(packet.gameMode, 0)
|
||||
client.chat('hello everyone; I have logged in.')
|
||||
})
|
||||
|
||||
// Dump some data for easier debugging
|
||||
client.on('raw.registry_data', (buffer) => {
|
||||
fs.writeFileSync(MC_SERVER_DIR + '_registry_data.bin', buffer)
|
||||
})
|
||||
client.on('registry_data', (json) => {
|
||||
fs.writeFileSync(MC_SERVER_DIR + '_registry_data.json', JSON.stringify(json))
|
||||
})
|
||||
client.on('login', (packet) => {
|
||||
fs.writeFileSync(MC_SERVER_DIR + '_login.json', JSON.stringify(packet))
|
||||
if (fs.existsSync(MC_SERVER_DIR + '_registry_data.json')) {
|
||||
// generate a loginPacket.json for minecraft-data
|
||||
fs.writeFileSync(MC_SERVER_DIR + '_loginPacket.json', JSON.stringify({
|
||||
...packet,
|
||||
dimensionCodec: JSON.parse(fs.readFileSync(MC_SERVER_DIR + '_registry_data.json')).codec
|
||||
}, null, 2))
|
||||
}
|
||||
})
|
||||
client.on('playerChat', function (data) {
|
||||
chatCount += 1
|
||||
assert.ok(chatCount <= 2)
|
||||
|
|
|
@ -35,6 +35,20 @@ const slotValue = {
|
|||
}
|
||||
}
|
||||
|
||||
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] }
|
||||
}
|
||||
}
|
||||
|
||||
const values = {
|
||||
i32: 123456,
|
||||
i16: -123,
|
||||
|
@ -110,46 +124,12 @@ const values = {
|
|||
f64: 99999.2222,
|
||||
f32: -333.444,
|
||||
slot: slotValue,
|
||||
nbt: {
|
||||
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] }
|
||||
}
|
||||
},
|
||||
optionalNbt: {
|
||||
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] }
|
||||
}
|
||||
},
|
||||
nbt: nbtValue,
|
||||
optionalNbt: nbtValue,
|
||||
compressedNbt: nbtValue,
|
||||
anonymousNbt: nbtValue,
|
||||
anonOptionalNbt: nbtValue,
|
||||
previousMessages: [],
|
||||
compressedNbt: {
|
||||
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] }
|
||||
}
|
||||
},
|
||||
i64: [0, 1],
|
||||
u64: [0, 1],
|
||||
entityMetadata: [
|
||||
|
@ -223,6 +203,11 @@ const values = {
|
|||
},
|
||||
suggestionType: 'minecraft:summonable_entities'
|
||||
}
|
||||
},
|
||||
soundSource: 'master',
|
||||
packedChunkPos: {
|
||||
x: 10,
|
||||
z: 12
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
|
||||
describe('mc-server ' + supportedVersion + 'v', function () {
|
||||
this.timeout(5000)
|
||||
this.beforeAll(async function () {
|
||||
this.beforeEach(async function () {
|
||||
PORT = await getPort()
|
||||
console.log(`Using port for tests: ${PORT}`)
|
||||
})
|
||||
|
@ -299,7 +299,8 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
|
||||
const username = ['player1', 'player2']
|
||||
let index = 0
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
console.log('ChatTest: Player has joined')
|
||||
assert.notEqual(client.id, null)
|
||||
assert.strictEqual(client.username, username[index++])
|
||||
broadcast(client.username + ' joined the game.')
|
||||
|
@ -322,8 +323,10 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
version: version.minecraftVersion,
|
||||
port: PORT
|
||||
}))
|
||||
console.log('ChatTest: Player1 is joining...')
|
||||
|
||||
player1.on('login', async function (packet) {
|
||||
console.log('ChatTest: Player 1 has joined')
|
||||
assert.strictEqual(packet.gameMode, 1)
|
||||
const player2 = applyClientHelpers(mc.createClient({
|
||||
username: 'player2',
|
||||
|
@ -332,14 +335,16 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
port: PORT
|
||||
}))
|
||||
|
||||
console.log('ChatTest: waiting for next message from P2')
|
||||
const p1Join = await player1.nextMessage('player2')
|
||||
|
||||
assert.strictEqual(p1Join, '{"text":"player2 joined the game."}')
|
||||
|
||||
console.log('ChatTest: Got message from P2')
|
||||
player2.chat('hi')
|
||||
const p2hi = await player1.nextMessage('player2')
|
||||
assert.strictEqual(p2hi, '{"text":"<player2> hi"}')
|
||||
|
||||
console.log('ChatTest: Waiting again for next message from P2')
|
||||
player1.chat('hello')
|
||||
const p1hello = await player2.nextMessage('player1')
|
||||
assert.strictEqual(p1hello, '{"text":"<player1> hello"}')
|
||||
|
@ -389,7 +394,7 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
port: PORT
|
||||
})
|
||||
let count = 2
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
client.on('end', function (reason) {
|
||||
assert.strictEqual(reason, 'ServerShutdown')
|
||||
resolve()
|
||||
|
@ -404,7 +409,7 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
version: version.minecraftVersion,
|
||||
port: PORT
|
||||
})
|
||||
client.on('login', function () {
|
||||
client.on('playerJoin', function () {
|
||||
server.close()
|
||||
})
|
||||
})
|
||||
|
@ -420,7 +425,7 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
version: version.minecraftVersion,
|
||||
port: PORT
|
||||
})
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
client.write('login', loginPacket(client, server))
|
||||
})
|
||||
server.on('close', done)
|
||||
|
@ -459,7 +464,7 @@ for (const supportedVersion of mc.supportedVersions) {
|
|||
version: version.minecraftVersion,
|
||||
port: PORT
|
||||
})
|
||||
server.on('login', function (client) {
|
||||
server.on('playerJoin', function (client) {
|
||||
client.on('end', function (reason) {
|
||||
assert.strictEqual(reason, 'ServerShutdown')
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue