Standard (#44)

* standard

* remove old examples
This commit is contained in:
extremeheat 2021-03-13 13:38:31 -05:00 committed by GitHub
commit 1c582acdb5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 539 additions and 731 deletions

1
.eslintignore Normal file
View file

@ -0,0 +1 @@
test/

3
babel.config.js Normal file
View file

@ -0,0 +1,3 @@
module.exports = {
presets: ['@babel/preset-env']
}

View file

@ -1,71 +1,71 @@
/**
* This is a utility script that converts the YAML here into ProtoDef schema code and (soon) docs/typescript definitions.
* It also pre-compiles JS code from the schema for easier development.
*
*
* You can run this with `npm run build`
*
*
*/
const fs = require('fs')
const { ProtoDefCompiler } = require('protodef').Compiler
// Parse the YML files and turn to JSON
function genProtoSchema() {
const { parse, compile } = require('protodef-yaml/compiler')
let version
function genProtoSchema () {
const { parse, compile } = require('protodef-yaml/compiler')
// Create the packet_map.yml from proto.yml
const parsed = parse('./proto.yml')
version = parsed['!version']
const packets = []
for (const key in parsed) {
if (key.startsWith('%container')) {
const [, name] = key.split(',')
if (name.startsWith('packet_')) {
const children = parsed[key]
const packetName = name.replace('packet_', '')
const packetID = children['!id']
packets.push([packetID, packetName, name])
}
}
// Create the packet_map.yml from proto.yml
const parsed = parse('./proto.yml')
const version = parsed['!version']
const packets = []
for (const key in parsed) {
if (key.startsWith('%container')) {
const [, name] = key.split(',')
if (name.startsWith('packet_')) {
const children = parsed[key]
const packetName = name.replace('packet_', '')
const packetID = children['!id']
packets.push([packetID, packetName, name])
}
}
let l1 = l2 = ''
for (const [id,name,fname] of packets) {
l1 += ` 0x${id.toString(16).padStart(2, '0')}: ${name}\n`
l2 += ` if ${name}: ${fname}\n`
}
// TODO: skip creating packet_map.yml and just generate the ProtoDef map JSON directly
const t = `#Auto-generated from proto.yml, do not modify\n!import: types.yaml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}`
fs.writeFileSync('./packet_map.yml', t)
}
let l1 = ''
let l2 = ''
for (const [id, name, fname] of packets) {
l1 += ` 0x${id.toString(16).padStart(2, '0')}: ${name}\n`
l2 += ` if ${name}: ${fname}\n`
}
// TODO: skip creating packet_map.yml and just generate the ProtoDef map JSON directly
const t = `#Auto-generated from proto.yml, do not modify\n!import: types.yaml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}`
fs.writeFileSync('./packet_map.yml', t)
compile('./proto.yml', 'protocol.json')
return version
compile('./proto.yml', 'protocol.json')
return version
}
// Compile the ProtoDef JSON into JS
function createProtocol(version) {
const compiler = new ProtoDefCompiler()
const protocol = require(`../${version}/protocol.json`).types
compiler.addTypes(require('../../src/datatypes/compiler-minecraft'))
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
compiler.addTypesToCompile(protocol)
function createProtocol (version) {
const compiler = new ProtoDefCompiler()
const protocol = require(`../${version}/protocol.json`).types
compiler.addTypes(require('../../src/datatypes/compiler-minecraft'))
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
compiler.addTypesToCompile(protocol)
fs.writeFileSync(`../${version}/read.js`, 'module.exports = ' + compiler.readCompiler.generate())
fs.writeFileSync(`../${version}/write.js`, 'module.exports = ' + compiler.writeCompiler.generate())
fs.writeFileSync(`../${version}/size.js`, 'module.exports = ' + compiler.sizeOfCompiler.generate())
fs.writeFileSync(`../${version}/read.js`, 'module.exports = ' + compiler.readCompiler.generate())
fs.writeFileSync(`../${version}/write.js`, 'module.exports = ' + compiler.writeCompiler.generate())
fs.writeFileSync(`../${version}/size.js`, 'module.exports = ' + compiler.sizeOfCompiler.generate())
const compiledProto = compiler.compileProtoDefSync()
return compiledProto
const compiledProto = compiler.compileProtoDefSync()
return compiledProto
}
function main() {
const version = genProtoSchema()
function main () {
const version = genProtoSchema()
fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: require('./protocol.json') }, null, 2))
fs.unlinkSync('./protocol.json') //remove temp file
fs.unlinkSync('./packet_map.yml') //remove temp file
console.log('Generating JS...')
createProtocol(version)
fs.writeFileSync(`../${version}/protocol.json`, JSON.stringify({ types: require('./protocol.json') }, null, 2))
fs.unlinkSync('./protocol.json') // remove temp file
fs.unlinkSync('./packet_map.yml') // remove temp file
console.log('Generating JS...')
createProtocol(version)
}
main()
main()

View file

@ -1,19 +1,20 @@
const { Versions } = require('../src/options')
const { getFiles } = require('../src/datatypes/util')
const { join } = require('path')
const fileMap = {}
// Walks all the directories for each of the supported versions in options.js
// then builds a file map for each version
// { 'protocol.json': { '1.16.200': '1.16.200/protocol.json', '1.16.210': '1.16.210/...' } }
function loadVersions() {
function loadVersions () {
for (const version in Versions) {
let files = []
try {
files = getFiles(__dirname + '/' + version)
try {
files = getFiles(join(__dirname, '/', version))
} catch {}
for (let file of files) {
const rfile = file.replace(__dirname + '/' + version + '/', '')
for (const file of files) {
const rfile = file.replace(join(__dirname, '/', version), '')
fileMap[rfile] ??= []
fileMap[rfile].push([Versions[version], file])
fileMap[rfile].sort().reverse()
@ -25,11 +26,11 @@ module.exports = (protocolVersion) => {
return {
// Returns the most recent file based on the specified protocolVersion
// e.g. if `version` is 1.16 and a file for 1.16 doesn't exist, load from 1.15 file
getPath(file) {
getPath (file) {
if (!fileMap[file]) {
throw Error('Unknown file ' + file)
}
for (const [ pver, path ] of fileMap[file]) {
for (const [pver, path] of fileMap[file]) {
if (pver <= protocolVersion) {
// console.debug('for', file, 'returining', path)
return path
@ -42,4 +43,4 @@ module.exports = (protocolVersion) => {
loadVersions()
// console.log('file map', fileMap)
// module.exports(Versions['1.16.210']).open('creativeitems.json')
// module.exports(Versions['1.16.210']).open('creativeitems.json')

View file

@ -1,9 +1,7 @@
process.env.DEBUG = 'minecraft-protocol raknet'
const { Client } = require('../src/client')
const fs = require('fs')
// console.log = () =>
async function test() {
async function test () {
const client = new Client({
hostname: '127.0.0.1',
port: 19132
@ -32,11 +30,8 @@ async function test() {
client.queue('client_cache_status', { enabled: false })
client.queue('request_chunk_radius', { chunk_radius: 1 })
client.queue('tick_sync', { request_time: BigInt(Date.now()), response_time: 0n })
})
// var read = 0;
// client.on('level_chunk', (packet) => {
// read++
@ -44,4 +39,4 @@ async function test() {
// })
}
test()
test()

View file

@ -1,6 +1,6 @@
const { Relay } = require('../src/relay')
function createRelay() {
function createRelay () {
console.log('Creating relay')
/**
* Example to create a non-transparent proxy (or 'Relay') connection to destination server
@ -27,7 +27,7 @@ function createRelay() {
/* Where to send upstream packets to */
destination: {
hostname: '127.0.0.1',
port: 19132,
port: 19132
// encryption: true
}
})
@ -35,4 +35,4 @@ function createRelay() {
relay.create()
}
createRelay()
createRelay()

Binary file not shown.

View file

@ -1,25 +0,0 @@
'use strict';
var pmp = require('../');
if(process.argv.length !=5) {
console.log("Usage: node client.js <host> <port> <username>");
process.exit(1);
}
var client = pmp.createClient({
host: process.argv[2],
port: parseInt(process.argv[3]),
username:process.argv[4]
});
client.on('mcpe', packet => console.log(packet));
client.on('set_spawn_position', () => {
client.writeMCPE('request_chunk_radius', {
chunkRadius:8
});
});
client.on('error',function(err){
console.log(err);
});

View file

@ -1,16 +0,0 @@
'use strict';
var mcpe = require('../');
var Parser = require('protodef').Parser;
var parser = new Parser(mcpe.createProtocol(),'mcpe_packet');
var serializer = mcpe.createSerializer();
parser.write(new Buffer('9F000000010000007E000000804800B0', 'hex'));
parser.on('error', function(err) {
console.log(err.stack);
})
parser.on('data', function(chunk) {
console.log(JSON.stringify(chunk, null, 2));
});

View file

@ -1,108 +0,0 @@
'use strict';
var pmp = require('../');
var fs = require("fs");
if(process.argv.length !=4) {
console.log("Usage: node server.js <host> <port>");
process.exit(1);
}
var server = pmp.createServer({
host: process.argv[2],
port: parseInt(process.argv[3]),
name: 'MCPE;Minecraft: PE Server;81 81;0.15.0;0;20'
});
server.on('connection', function(client) {
client.on("mcpe",packet => console.log(packet));
client.on("login_mcpe",packet => {
client.writeMCPE("player_status",{
status:0
});
client.writeMCPE('move_player', {
entityId: [0,0],
x: 1,
y: 64 + 1.62,
z: 1,
yaw: 0,
headYaw: 0,
pitch: 0,
mode: 0,
onGround: 1
});
client.writeMCPE("start_game",{
seed:-1,
dimension:0,
generator:1,
gamemode:1,
entityId:[0,0],
spawnX:1,
spawnY:1,
spawnZ:1,
x:0,
y:1+1.62,
z:0,
isLoadedInCreative:0,
dayCycleStopTime:0,
eduMode:0,
worldName:""
});
client.writeMCPE('set_spawn_position', {
x: 1,
y: 64,
z: 1
});
client.writeMCPE("set_time",{
time:0,
started:1
});
client.writeMCPE('respawn', {
x: 1,
y: 64,
z: 1
});
});
client.on("chunk_radius_update",() => {
client.writeMCPE('chunk_radius_update',{
chunk_radius:1
});
for (let x = -1; x <=1; x++) {
for (let z = -1; z <=1; z++) {
client.writeBatch([{name:"full_chunk_data",params:{
chunkX: x,
chunkZ: z,
order: 1,
chunkData:fs.readFileSync(__dirname+"/chunk")
}}]);
}
}
client.writeMCPE('player_status', {
status: 3
});
client.writeMCPE('set_time', {
time: 0,
started: 1
});
});
client.on('error', function(err) {
console.log(err.stack);
});
client.on('end',function() {
console.log("client left");
})
});

View file

@ -1,31 +0,0 @@
'use strict';
var pmp = require('../');
var fs = require("fs");
if(process.argv.length !=4) {
console.log("Usage: node server.js <host> <port>");
process.exit(1);
}
var server = pmp.createServer({
host: process.argv[2],
port: parseInt(process.argv[3]),
name: 'MCPE;Minecraft: PE Server;81 81;0.15.0;0;20'
});
server.on('connection', function(client) {
client.on("mcpe", packet => console.log(packet));
client.on("login_mcpe", data => {
console.log(client.displayName + '(' + client.XUID + ') ' + ' joined the game');
});
client.on('error', err => {
console.error(err);
});
client.on('end', () => {
console.log("client left");
})
});

View file

@ -1,25 +1,23 @@
const fs = require('fs')
process.env.DEBUG = 'minecraft-protocol raknet'
const { Server } = require('../src/server')
// const CreativeItems = require('../data/creativeitems.json')
const NBT = require('prismarine-nbt')
const fs = require('fs')
const DataProvider = require('../data/provider')
let server = new Server({
const server = new Server({
})
server.create('0.0.0.0', 19132)
function getPath(packetPath) {
function getPath (packetPath) {
return DataProvider(server.options.protocolVersion).getPath(packetPath)
}
function get(packetPath) {
function get (packetPath) {
return require(getPath('sample/' + packetPath))
}
let ran = false
// const ran = false
server.on('connect', ({ client }) => {
/** @type {Player} */
@ -29,26 +27,26 @@ server.on('connect', ({ client }) => {
// ResourcePacksInfo is sent by the server to inform the client on what resource packs the server has. It
// sends a list of the resource packs it has and basic information on them like the version and description.
client.write('resource_packs_info', {
'must_accept': false,
'has_scripts': false,
'behaviour_packs': [],
'texture_packs': []
must_accept: false,
has_scripts: false,
behaviour_packs: [],
texture_packs: []
})
client.once('resource_pack_client_response', async (packet) => {
// ResourcePackStack is sent by the server to send the order in which resource packs and behaviour packs
// should be applied (and downloaded) by the client.
client.write('resource_pack_stack', {
'must_accept': false,
'behavior_packs': [],
'resource_packs': [],
'game_version': '',
'experiments': [],
'experiments_previously_used': false
must_accept: false,
behavior_packs: [],
resource_packs: [],
game_version: '',
experiments: [],
experiments_previously_used: false
})
client.once('resource_pack_client_response', async (packet) => {
})
client.write('network_settings', {
@ -56,35 +54,35 @@ server.on('connect', ({ client }) => {
})
for (let i = 0; i < 3; i++) {
client.queue('inventory_slot', {"inventory_id":120,"slot":i,"uniqueid":0,"item":{"network_id":0}})
client.queue('inventory_slot', { inventory_id: 120, slot: i, uniqueid: 0, item: { network_id: 0 } })
}
client.queue('inventory_transaction', get('packets/inventory_transaction.json'))
client.queue('player_list', get('packets/player_list.json'))
client.queue('start_game', get('packets/start_game.json'))
client.queue('item_component', {"entries":[]})
client.queue('item_component', { entries: [] })
client.queue('set_spawn_position', get('packets/set_spawn_position.json'))
client.queue('set_time', { time: 5433771 })
client.queue('set_difficulty', { difficulty: 1 })
client.queue('set_commands_enabled', { enabled: true })
client.queue('adventure_settings', get('packets/adventure_settings.json'))
client.queue('biome_definition_list', get('packets/biome_definition_list.json'))
client.queue('available_entity_identifiers', get('packets/available_entity_identifiers.json'))
client.queue('update_attributes', get('packets/update_attributes.json'))
client.queue('creative_content', get('packets/creative_content.json'))
client.queue('inventory_content', get('packets/inventory_content.json'))
client.queue('player_hotbar', {"selected_slot":3,"window_id":0,"select_slot":true})
client.queue('player_hotbar', { selected_slot: 3, window_id: 0, select_slot: true })
client.queue('crafting_data', get('packets/crafting_data.json'))
client.queue('available_commands', get('packets/available_commands.json'))
client.queue('chunk_radius_update', {"chunk_radius":5})
client.queue('chunk_radius_update', { chunk_radius: 5 })
client.queue('set_entity_data', get('packets/set_entity_data.json'))
client.queue('game_rules_changed', get('packets/game_rules_changed.json'))
client.queue('respawn', {"x":646.9405517578125,"y":65.62001037597656,"z":77.86255645751953,"state":0,"runtime_entity_id":0})
client.queue('respawn', { x: 646.9405517578125, y: 65.62001037597656, z: 77.86255645751953, state: 0, runtime_entity_id: 0 })
for (const file of fs.readdirSync(`../data/${server.options.version}/sample/chunks`)) {
const buffer = Buffer.from(fs.readFileSync(`../data/${server.options.version}/sample/chunks/` + file, 'utf8'), 'hex')
@ -97,18 +95,17 @@ server.on('connect', ({ client }) => {
// }
setInterval(() => {
client.write('network_chunk_publisher_update', {"coordinates":{"x":646,"y":130,"z":77},"radius":64})
client.write('network_chunk_publisher_update', { coordinates: { x: 646, y: 130, z: 77 }, radius: 64 })
}, 9500)
setTimeout(() => {
client.write('play_status', { status: 'player_spawn' })
}, 8000)
// Respond to tick synchronization packets
client.on('tick_sync', ({ request_time }) => {
client.on('tick_sync', (packet) => {
client.queue('tick_sync', {
request_time,
request_time: packet.request_time,
response_time: BigInt(Date.now())
})
})
@ -116,43 +113,41 @@ server.on('connect', ({ client }) => {
})
})
async function sleep(ms) {
return new Promise(res => {
setTimeout(() => { res() }, ms)
})
}
// CHUNKS
// const { ChunkColumn, Version } = require('bedrock-provider')
const mcData = require('minecraft-data')('1.16')
var chunks = []
async function buildChunks() {
// "x": 40,
// "z": 4,
const stone = mcData.blocksByName.stone
// const mcData = require('minecraft-data')('1.16')
// const chunks = []
// async function buildChunks () {
// // "x": 40,
// // "z": 4,
for (var cx = 35; cx < 45; cx++) {
for (var cz = 0; cz < 8; cz++) {
const column = new ChunkColumn(Version.v1_2_0_bis, x, z)
for (var x = 0; x < 16; x++) {
for (var y = 0; y < 60; y++) {
for (var z = 0; z < 16; z++) {
column.setBlock(x,y,z,stone)
}
}
}
// const stone = mcData.blocksByName.stone
const ser = await column.networkEncodeNoCache()
// for (let cx = 35; cx < 45; cx++) {
// for (let cz = 0; cz < 8; cz++) {
// const column = new ChunkColumn(Version.v1_2_0_bis, x, z)
// for (let x = 0; x < 16; x++) {
// for (let y = 0; y < 60; y++) {
// for (let z = 0; z < 16; z++) {
// column.setBlock(x, y, z, stone)
// }
// }
// }
chunks.push({
x:cx, z:cz, sub_chunk_count: column.sectionsLen, cache_enabled: false,
blobs: [], payload: ser
})
}
}
// const ser = await column.networkEncodeNoCache()
// console.log('Chunks',chunks)
}
// chunks.push({
// x: cx,
// z: cz,
// sub_chunk_count: column.sectionsLen,
// cache_enabled: false,
// blobs: [],
// payload: ser
// })
// }
// }
// buildChunks()
// // console.log('Chunks',chunks)
// }
// // buildChunks()

View file

@ -1,3 +0,0 @@
'use strict';
module.exports = require('./dist/index.js');

View file

@ -5,7 +5,11 @@
"main": "index.js",
"scripts": {
"build": "cd data/new && node compile.js",
"test": "echo \"Error: no test specified\" && exit 1"
"prepare": "npm run build",
"test": "mocha",
"pretest": "npm run lint",
"lint": "standard",
"fix": "standard --fix"
},
"keywords": [
"minecraft",
@ -19,19 +23,25 @@
"@xboxreplay/xboxlive-auth": "^3.3.3",
"aes-js": "^3.1.2",
"asn1": "^0.2.4",
"bedrock-provider": "github:extremeheat/bedrock-provider",
"bedrock-provider": "^0.1.1",
"debug": "^4.3.1",
"ec-pem": "^0.18.0",
"jsonwebtoken": "^8.5.1",
"jsp-raknet": "github:extremeheat/raknet#client",
"minecraft-folder-path": "^1.1.0",
"prismarine-nbt": "^1.5.0",
"protodef": "github:extremeheat/node-protodef#compiler",
"protodef": "github:extremeheat/node-protodef#compiler2",
"raknet-native": "^0.1.0",
"uuid-1345": "^0.99.7"
},
"devDependencies": {
"mocha": "^2.5.3"
"@babel/eslint-parser": "^7.13.10",
"babel-eslint": "^10.1.0",
"mocha": "^2.5.3",
"standard": "^16.0.3"
},
"standard": {
"parser": "babel-eslint"
},
"repository": {
"type": "git",

View file

@ -4,81 +4,81 @@ const constants = require('./constants')
// Refer to the docs:
// https://web.archive.org/web/20180917171505if_/https://confluence.yawk.at/display/PEPROTOCOL/Game+Packets#GamePackets-Login
function mcPubKeyToPem(mcPubKeyBuffer) {
console.log(mcPubKeyBuffer)
if (mcPubKeyBuffer[0] == '-') return mcPubKeyBuffer
let pem = '-----BEGIN PUBLIC KEY-----\n'
let base64PubKey = mcPubKeyBuffer.toString('base64')
const maxLineLength = 65
while (base64PubKey.length > 0) {
pem += base64PubKey.substring(0, maxLineLength) + '\n'
base64PubKey = base64PubKey.substring(maxLineLength)
}
pem += '-----END PUBLIC KEY-----\n'
return pem
function mcPubKeyToPem (mcPubKeyBuffer) {
console.log(mcPubKeyBuffer)
if (mcPubKeyBuffer[0] === '-') return mcPubKeyBuffer
let pem = '-----BEGIN PUBLIC KEY-----\n'
let base64PubKey = mcPubKeyBuffer.toString('base64')
const maxLineLength = 65
while (base64PubKey.length > 0) {
pem += base64PubKey.substring(0, maxLineLength) + '\n'
base64PubKey = base64PubKey.substring(maxLineLength)
}
pem += '-----END PUBLIC KEY-----\n'
return pem
}
function getX5U(token) {
const [header] = token.split('.')
const hdec = Buffer.from(header, 'base64').toString('utf-8')
const hjson = JSON.parse(hdec)
return hjson.x5u
function getX5U (token) {
const [header] = token.split('.')
const hdec = Buffer.from(header, 'base64').toString('utf-8')
const hjson = JSON.parse(hdec)
return hjson.x5u
}
function verifyAuth(chain) {
let data = {}
function verifyAuth (chain) {
let data = {}
// There are three JWT tokens sent to us, one signed by the client
// one signed by Mojang with the Mojang token we have and another one
// from Xbox with addition user profile data
// We verify that at least one of the tokens in the chain has been properly
// signed by Mojang by checking the x509 public key in the JWT headers
let didVerify = false
let pubKey = mcPubKeyToPem(getX5U(chain[0])) // the first one is client signed, allow it
let finalKey = null
console.log(pubKey)
for (var token of chain) {
// const decoded = jwt.decode(token, pubKey, 'ES384')
// console.log('Decoding...', token)
const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' })
// console.log('Decoded...')
console.log('Decoded', decoded)
// Check if signed by Mojang key
const x5u = getX5U(token)
if (x5u == constants.PUBLIC_KEY && !data.extraData?.XUID) {
didVerify = true
console.log('verified with mojang key!', x5u)
}
// TODO: Handle `didVerify` = false
pubKey = decoded.identityPublicKey ? mcPubKeyToPem(decoded.identityPublicKey) : x5u
finalKey = decoded.identityPublicKey || finalKey // non pem
data = { ...data, ...decoded }
}
// console.log('Result', data)
return { key: finalKey, data }
}
function verifySkin(publicKey, token) {
// console.log('token', token)
const pubKey = mcPubKeyToPem(publicKey)
// There are three JWT tokens sent to us, one signed by the client
// one signed by Mojang with the Mojang token we have and another one
// from Xbox with addition user profile data
// We verify that at least one of the tokens in the chain has been properly
// signed by Mojang by checking the x509 public key in the JWT headers
// let didVerify = false
let pubKey = mcPubKeyToPem(getX5U(chain[0])) // the first one is client signed, allow it
let finalKey = null
console.log(pubKey)
for (const token of chain) {
// const decoded = jwt.decode(token, pubKey, 'ES384')
// console.log('Decoding...', token)
const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' })
// console.log('Decoded...')
console.log('Decoded', decoded)
return decoded
// Check if signed by Mojang key
const x5u = getX5U(token)
if (x5u === constants.PUBLIC_KEY && !data.extraData?.XUID) {
// didVerify = true
console.log('verified with mojang key!', x5u)
}
// TODO: Handle `didVerify` = false
pubKey = decoded.identityPublicKey ? mcPubKeyToPem(decoded.identityPublicKey) : x5u
finalKey = decoded.identityPublicKey || finalKey // non pem
data = { ...data, ...decoded }
}
// console.log('Result', data)
return { key: finalKey, data }
}
function decodeLoginJWT(authTokens, skinTokens) {
const { key, data } = verifyAuth(authTokens)
const skinData = verifySkin(key, skinTokens)
return { key, userData: data, skinData }
function verifySkin (publicKey, token) {
// console.log('token', token)
const pubKey = mcPubKeyToPem(publicKey)
const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' })
return decoded
}
function encodeLoginJWT(localChain, mojangChain) {
function decodeLoginJWT (authTokens, skinTokens) {
const { key, data } = verifyAuth(authTokens)
const skinData = verifySkin(key, skinTokens)
return { key, userData: data, skinData }
}
function encodeLoginJWT (localChain, mojangChain) {
const chains = []
chains.push(localChain)
for (const chain of mojangChain) {
@ -107,4 +107,4 @@ module.exports = { encodeLoginJWT, decodeLoginJWT }
// // console.log(loginPacket)
// }
// // testServer()
// // testServer()

View file

@ -1,3 +1,3 @@
module.exports = {
PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V'
PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V'
}

View file

@ -1,26 +1,26 @@
const JWT = require('jsonwebtoken')
const crypto = require('crypto')
const { Ber } = require('asn1')
const ec_pem = require('ec-pem')
const ecPem = require('ec-pem')
const fs = require('fs')
const DataProvider = require('../../data/provider')
const SALT = '🧂'
const curve = 'secp384r1'
function Encrypt(client, server, options) {
function Encrypt (client, server, options) {
const skinGeom = fs.readFileSync(DataProvider(options.protocolVersion).getPath('skin_geom.txt'), 'utf-8')
client.ecdhKeyPair = crypto.createECDH(curve)
client.ecdhKeyPair.generateKeys()
client.clientX509 = writeX509PublicKey(client.ecdhKeyPair.getPublicKey())
function startClientboundEncryption(publicKey) {
function startClientboundEncryption (publicKey) {
console.warn('[encrypt] Pub key base64: ', publicKey)
const pubKeyBuf = readX509PublicKey(publicKey.key)
const alice = client.ecdhKeyPair
const alicePEM = ec_pem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125
const alicePEM = ecPem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125
const alicePEMPrivate = alicePEM.encodePrivateKey()
// Shared secret from bob's public key + our private key
client.sharedSecret = alice.computeSecret(pubKeyBuf)
@ -28,7 +28,7 @@ function Encrypt(client, server, options) {
// Secret hash we use for packet encryption:
// From the public key of the remote and the private key
// of the local, a shared secret is generated using ECDH.
// The secret key bytes are then computed as
// The secret key bytes are then computed as
// sha256(server_token + shared_secret). These secret key
// bytes are 32 bytes long.
const secretHash = crypto.createHash('sha256')
@ -49,13 +49,13 @@ function Encrypt(client, server, options) {
})
// The encryption scheme is AES/CFB8/NoPadding with the
// secret key being the result of the sha256 above and
// secret key being the result of the sha256 above and
// the IV being the first 16 bytes of this secret key.
const initial = client.secretKeyBytes.slice(0, 16)
client.startEncryption(initial)
}
function startServerboundEncryption(token) {
function startServerboundEncryption (token) {
console.warn('[encrypt] Starting serverbound encryption', token)
const jwt = token?.token
if (!jwt) {
@ -64,7 +64,7 @@ function Encrypt(client, server, options) {
}
// TODO: Should we do some JWT signature validation here? Seems pointless
const alice = client.ecdhKeyPair
const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64'))
const [header, payload] = jwt.split('.').map(k => Buffer.from(k, 'base64'))
const head = JSON.parse(String(header))
const body = JSON.parse(String(payload))
const serverPublicKey = readX509PublicKey(head.x5u)
@ -93,7 +93,7 @@ function Encrypt(client, server, options) {
client.createClientChain = (mojangKey) => {
mojangKey = mojangKey || require('./constants').PUBLIC_KEY
const alice = client.ecdhKeyPair
const alicePEM = ec_pem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125
const alicePEM = ecPem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125
const alicePEMPrivate = alicePEM.encodePrivateKey()
const token = JWT.sign({
@ -122,16 +122,16 @@ function Encrypt(client, server, options) {
SkinData: 'AAAAAA==',
SkinResourcePatch: 'ewogICAiZ2VvbWV0cnkiIDogewogICAgICAiYW5pbWF0ZWRfMTI4eDEyOCIgOiAiZ2VvbWV0cnkuYW5pbWF0ZWRfMTI4eDEyOF9wZXJzb25hLWUxOTk2NzJhOGMxYTg3ZTAtMCIsCiAgICAgICJhbmltYXRlZF9mYWNlIiA6ICJnZW9tZXRyeS5hbmltYXRlZF9mYWNlX3BlcnNvbmEtZTE5OTY3MmE4YzFhODdlMC0wIiwKICAgICAgImRlZmF1bHQiIDogImdlb21ldHJ5LnBlcnNvbmFfZTE5OTY3MmE4YzFhODdlMC0wIgogICB9Cn0K',
SkinGeometryData: skinGeom,
"SkinImageHeight": 1,
"SkinImageWidth": 1,
"ArmSize": "wide",
"CapeData": "",
"CapeId": "",
"CapeImageHeight": 0,
"CapeImageWidth": 0,
"CapeOnClassicSkin": false,
SkinImageHeight: 1,
SkinImageWidth: 1,
ArmSize: 'wide',
CapeData: '',
CapeId: '',
CapeImageHeight: 0,
CapeImageWidth: 0,
CapeOnClassicSkin: false,
PlatformOfflineId: '',
PlatformOnlineId: '', //chat
PlatformOnlineId: '', // chat
// a bunch of meaningless junk
CurrentInputMode: 1,
DefaultInputMode: 1,
@ -144,7 +144,7 @@ function Encrypt(client, server, options) {
PieceTintColors: [],
SkinAnimationData: '',
ThirdPartyNameOnly: false,
"SkinColor": "#ffffcd96",
SkinColor: '#ffffcd96'
}
payload = require('./logPack.json')
const customPayload = options.userData || {}
@ -155,33 +155,33 @@ function Encrypt(client, server, options) {
}
}
function toBase64(string) {
function toBase64 (string) {
return Buffer.from(string).toString('base64')
}
function readX509PublicKey(key) {
var reader = new Ber.Reader(Buffer.from(key, "base64"));
reader.readSequence();
reader.readSequence();
reader.readOID(); // Hey, I'm an elliptic curve
reader.readOID(); // This contains the curve type, could be useful
return Buffer.from(reader.readString(Ber.BitString, true)).slice(1);
function readX509PublicKey (key) {
const reader = new Ber.Reader(Buffer.from(key, 'base64'))
reader.readSequence()
reader.readSequence()
reader.readOID() // Hey, I'm an elliptic curve
reader.readOID() // This contains the curve type, could be useful
return Buffer.from(reader.readString(Ber.BitString, true)).slice(1)
}
function writeX509PublicKey(key) {
var writer = new Ber.Writer();
writer.startSequence();
writer.startSequence();
writer.writeOID("1.2.840.10045.2.1");
writer.writeOID("1.3.132.0.34");
writer.endSequence();
writer.writeBuffer(Buffer.concat([Buffer.from([0x00]), key]), Ber.BitString);
writer.endSequence();
return writer.buffer.toString("base64");
function writeX509PublicKey (key) {
const writer = new Ber.Writer()
writer.startSequence()
writer.startSequence()
writer.writeOID('1.2.840.10045.2.1')
writer.writeOID('1.3.132.0.34')
writer.endSequence()
writer.writeBuffer(Buffer.concat([Buffer.from([0x00]), key]), Ber.BitString)
writer.endSequence()
return writer.buffer.toString('base64')
}
module.exports = {
readX509PublicKey,
writeX509PublicKey,
Encrypt
}
}

View file

@ -12,7 +12,7 @@ const debugging = false
class Client extends Connection {
/** @param {{ version: number, hostname: string, port: number }} options */
constructor(options) {
constructor (options) {
super()
this.options = { ...Options.defaultOptions, ...options }
this.validateOptions()
@ -33,7 +33,7 @@ class Client extends Connection {
this.outLog = (...args) => console.info('C <-', ...args)
}
validateOptions() {
validateOptions () {
if (!this.options.hostname || this.options.port == null) throw Error('Invalid hostname/port')
if (!Options.Versions[this.options.version]) {
@ -61,7 +61,7 @@ class Client extends Connection {
this.connection.connect()
}
sendLogin() {
sendLogin () {
this.createClientChain()
const chain = [
@ -84,23 +84,22 @@ class Client extends Connection {
this.emit('loggingIn')
}
onDisconnectRequest(packet) {
onDisconnectRequest (packet) {
// We're talking over UDP, so there is no connection to close, instead
// we stop communicating with the server
console.warn(`Server requested ${packet.hide_disconnect_reason ? 'silent disconnect' : 'disconnect'}: ${packet.message}`)
process.exit(1) // TODO: handle
}
close() {
close () {
console.warn('Close not implemented!!')
}
tryRencode(name, params, actual) {
tryRencode (name, params, actual) {
const packet = this.serializer.createPacketBuffer({ name, params })
console.assert(packet.toString('hex') == actual.toString('hex'))
console.assert(packet.toString('hex') === actual.toString('hex'))
if (packet.toString('hex') !== actual.toString('hex')) {
const ours = packet.toString('hex').match(/.{1,16}/g).join('\n')
const theirs = actual.toString('hex').match(/.{1,16}/g).join('\n')
@ -113,10 +112,10 @@ class Client extends Connection {
}
}
readPacket(packet) {
readPacket (packet) {
const des = this.deserializer.parsePacketBuffer(packet)
const pakData = { name: des.data.name, params: des.data.params }
this.inLog('-> C', pakData.name/*, serialize(pakData.params).slice(0, 100)*/)
this.inLog('-> C', pakData.name/*, serialize(pakData.params).slice(0, 100) */)
if (debugging) {
// Packet verifying (decode + re-encode + match test)
@ -161,4 +160,4 @@ class Client extends Connection {
}
}
module.exports = { Client }
module.exports = { Client }

View file

@ -9,7 +9,7 @@ const { MsAuthFlow } = require('./authFlow.js')
async function postAuthenticate (client, options, chains) {
// First chain is Mojang stuff, second is Xbox profile data used by mc
const jwt = chains[1]
const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64'))
const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) // eslint-disable-line
const xboxProfile = JSON.parse(String(payload))
// This profile / session here could be simplified down to where it just passes the uuid of the player to encrypt.js
@ -17,7 +17,7 @@ async function postAuthenticate (client, options, chains) {
// - Kashalls
const profile = {
name: xboxProfile?.extraData?.displayName || 'Player',
uuid: xboxProfile?.extraData?.identity || 'adfcf5ca-206c-404a-aec4-f59fff264c9b', //random
uuid: xboxProfile?.extraData?.identity || 'adfcf5ca-206c-404a-aec4-f59fff264c9b', // random
xuid: xboxProfile?.extraData?.XUID || 0
}

View file

@ -1,4 +1,4 @@
module.exports = {
XSTSRelyingParty: 'https://multiplayer.minecraft.net/',
MinecraftAuth: 'https://multiplayer.minecraft.net/authentication'
XSTSRelyingParty: 'https://multiplayer.minecraft.net/',
MinecraftAuth: 'https://multiplayer.minecraft.net/authentication'
}

View file

@ -13,15 +13,16 @@ const msalConfig = {
// the minecraft client:
// clientId: "000000004C12AE6F",
clientId: '389b1b32-b5d5-43b2-bddc-84ce938d6737', // token from https://github.com/microsoft/Office365APIEditor
authority: 'https://login.microsoftonline.com/consumers',
authority: 'https://login.microsoftonline.com/consumers'
}
}
async function retry (methodFn, beforeRety, times) {
async function retry (methodFn, beforeRetry, times) {
while (times--) {
if (times !== 0) {
try { return await methodFn() } catch (e) { debug(e) }
await beforeRety()
await new Promise(resolve => setTimeout(resolve, 2000))
await beforeRetry()
} else {
return await methodFn()
}
@ -111,7 +112,7 @@ class MsAuthFlow {
async getMinecraftToken (publicKey) {
// TODO: Fix cache, in order to do cache we also need to cache the ECDH keys so disable it
// is this even a good idea to cache?
if (await this.mca.verifyTokens() && false) {
if (await this.mca.verifyTokens() && false) { // eslint-disable-line
debug('[mc] Using existing tokens')
return this.mca.getCachedAccessToken().chain
} else {

View file

@ -8,7 +8,7 @@ const authConstants = require('./authConstants')
// Manages Microsoft account tokens
class MsaTokenManager {
constructor(msalConfig, scopes, cacheLocation) {
constructor (msalConfig, scopes, cacheLocation) {
this.msaClientId = msalConfig.auth.clientId
this.scopes = scopes
this.cacheLocation = cacheLocation || path.join(__dirname, './msa-cache.json')
@ -42,7 +42,7 @@ class MsaTokenManager {
this.msalConfig = msalConfig
}
getUsers() {
getUsers () {
const accounts = this.msaCache.Account
const users = []
if (!accounts) return users
@ -52,7 +52,7 @@ class MsaTokenManager {
return users
}
getAccessToken() {
getAccessToken () {
const tokens = this.msaCache.AccessToken
if (!tokens) return
const account = Object.values(tokens).filter(t => t.client_id === this.msaClientId)[0]
@ -65,7 +65,7 @@ class MsaTokenManager {
return { valid, until: until, token: account.secret }
}
getRefreshToken() {
getRefreshToken () {
const tokens = this.msaCache.RefreshToken
if (!tokens) return
const account = Object.values(tokens).filter(t => t.client_id === this.msaClientId)[0]
@ -76,7 +76,7 @@ class MsaTokenManager {
return { token: account.secret }
}
async refreshTokens() {
async refreshTokens () {
const rtoken = this.getRefreshToken()
if (!rtoken) {
throw new Error('Cannot refresh without refresh token')
@ -97,7 +97,7 @@ class MsaTokenManager {
})
}
async verifyTokens() {
async verifyTokens () {
const at = this.getAccessToken()
const rt = this.getRefreshToken()
if (!at || !rt || this.forceRefresh) {
@ -111,13 +111,14 @@ class MsaTokenManager {
await this.refreshTokens()
return true
} catch (e) {
console.warn('Error refreshing token', e) // TODO: looks like an error happens here
return false
}
}
}
// Authenticate with device_code flow
async authDeviceCode(dataCallback) {
async authDeviceCode (dataCallback) {
const deviceCodeRequest = {
deviceCodeCallback: (resp) => {
debug('[msa] device_code response: ', resp)
@ -142,7 +143,7 @@ class MsaTokenManager {
// Manages Xbox Live tokens for xboxlive.com
class XboxTokenManager {
constructor(relyingParty, cacheLocation) {
constructor (relyingParty, cacheLocation) {
this.relyingParty = relyingParty
this.cacheLocation = cacheLocation || path.join(__dirname, './xbl-cache.json')
try {
@ -152,7 +153,7 @@ class XboxTokenManager {
}
}
getCachedUserToken() {
getCachedUserToken () {
const token = this.cache.userToken
if (!token) return
const until = new Date(token.NotAfter)
@ -162,7 +163,7 @@ class XboxTokenManager {
return { valid, token: token.Token, data: token }
}
getCachedXstsToken() {
getCachedXstsToken () {
const token = this.cache.xstsToken
if (!token) return
const until = new Date(token.expiresOn)
@ -172,17 +173,17 @@ class XboxTokenManager {
return { valid, token: token.XSTSToken, data: token }
}
setCachedUserToken(data) {
setCachedUserToken (data) {
this.cache.userToken = data
fs.writeFileSync(this.cacheLocation, JSON.stringify(this.cache))
}
setCachedXstsToken(data) {
setCachedXstsToken (data) {
this.cache.xstsToken = data
fs.writeFileSync(this.cacheLocation, JSON.stringify(this.cache))
}
async verifyTokens() {
async verifyTokens () {
const ut = this.getCachedUserToken()
const xt = this.getCachedXstsToken()
if (!ut || !xt || this.forceRefresh) {
@ -202,7 +203,7 @@ class XboxTokenManager {
return false
}
async getUserToken(msaAccessToken) {
async getUserToken (msaAccessToken) {
debug('[xbl] obtaining xbox token with ms token', msaAccessToken)
if (!msaAccessToken.startsWith('d=')) { msaAccessToken = 'd=' + msaAccessToken }
const xblUserToken = await XboxLiveAuth.exchangeRpsTicketForUserToken(msaAccessToken)
@ -211,7 +212,7 @@ class XboxTokenManager {
return xblUserToken
}
async getXSTSToken(xblUserToken) {
async getXSTSToken (xblUserToken) {
debug('[xbl] obtaining xsts token with xbox user token', xblUserToken.Token)
const xsts = await XboxLiveAuth.exchangeUserTokenForXSTSIdentity(
xblUserToken.Token, { XSTSRelyingParty: this.relyingParty, raw: false }
@ -224,7 +225,7 @@ class XboxTokenManager {
// Manages Minecraft tokens for sessionserver.mojang.com
class MinecraftTokenManager {
constructor(clientPublicKey, cacheLocation) {
constructor (clientPublicKey, cacheLocation) {
this.clientPublicKey = clientPublicKey
this.cacheLocation = cacheLocation || path.join(__dirname, './bed-cache.json')
try {
@ -234,13 +235,13 @@ class MinecraftTokenManager {
}
}
getCachedAccessToken() {
getCachedAccessToken () {
const token = this.cache.mca
debug('[mc] token cache', this.cache)
if (!token) return
console.log('TOKEN', token)
const jwt = token.chain[0]
const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64'))
const [header, payload, signature] = jwt.split('.').map(k => Buffer.from(k, 'base64')) // eslint-disable-line
const body = JSON.parse(String(payload))
const expires = new Date(body.exp * 1000)
@ -249,13 +250,13 @@ class MinecraftTokenManager {
return { valid, until: expires, chain: token.chain }
}
setCachedAccessToken(data) {
setCachedAccessToken (data) {
data.obtainedOn = Date.now()
this.cache.mca = data
fs.writeFileSync(this.cacheLocation, JSON.stringify(this.cache))
}
async verifyTokens() {
async verifyTokens () {
const at = this.getCachedAccessToken()
if (!at || this.forceRefresh) {
return false
@ -267,13 +268,13 @@ class MinecraftTokenManager {
return false
}
async getAccessToken(clientPublicKey, xsts) {
async getAccessToken (clientPublicKey, xsts) {
debug('[mc] authing to minecraft', clientPublicKey, xsts)
const getFetchOptions = {
headers: {
'Content-Type': 'application/json',
'User-Agent': 'node-minecraft-protocol',
'Authorization': `XBL3.0 x=${xsts.userHash};${xsts.XSTSToken}`
Authorization: `XBL3.0 x=${xsts.userHash};${xsts.XSTSToken}`
}
}
const MineServicesResponse = await fetch(authConstants.MinecraftAuth, {
@ -288,7 +289,7 @@ class MinecraftTokenManager {
}
}
function checkStatus(res) {
function checkStatus (res) {
if (res.ok) { // res.status >= 200 && res.status < 300
return res.json()
} else {

View file

@ -5,8 +5,10 @@ const { EventEmitter } = require('events')
const Versions = require('./options')
const debug = require('debug')('minecraft-protocol')
const SKIP_BATCH = ['level_chunk', 'client_cache_blob_status', 'client_cache_miss_response']
class Connection extends EventEmitter {
versionLessThan(version) {
versionLessThan (version) {
if (typeof version === 'string') {
return Versions[version] < this.options.version
} else {
@ -14,7 +16,7 @@ class Connection extends EventEmitter {
}
}
versionGreaterThan(version) {
versionGreaterThan (version) {
if (typeof version === 'string') {
return Versions[version] > this.options.version
} else {
@ -22,7 +24,7 @@ class Connection extends EventEmitter {
}
}
startEncryption(iv) {
startEncryption (iv) {
this.encryptionEnabled = true
this.inLog('Started encryption', this.sharedSecret, iv)
this.decrypt = cipher.createDecryptor(this, iv)
@ -30,7 +32,7 @@ class Connection extends EventEmitter {
this.q2 = []
}
write(name, params) {
write (name, params) {
this.outLog('sending', name, params)
const batch = new BatchPacket()
const packet = this.serializer.createPacketBuffer({ name, params })
@ -43,10 +45,10 @@ class Connection extends EventEmitter {
}
}
queue(name, params) {
queue (name, params) {
this.outLog('Q <- ', name, params)
const packet = this.serializer.createPacketBuffer({ name, params })
if (name == 'level_chunk' || name=='client_cache_blob_status' || name == 'client_cache_miss_response') {
if (SKIP_BATCH.includes(name)) {
// Skip queue, send ASAP
this.sendBuffer(packet)
return
@ -55,11 +57,11 @@ class Connection extends EventEmitter {
this.q2.push(name)
}
startQueue() {
startQueue () {
this.q = []
this.loop = setInterval(() => {
if (this.q.length) {
//TODO: can we just build Batch before the queue loop?
// TODO: can we just build Batch before the queue loop?
const batch = new BatchPacket()
this.outLog('<- BATCH', this.q2)
const sending = []
@ -78,11 +80,10 @@ class Connection extends EventEmitter {
}, 20)
}
/**
* Sends a MCPE packet buffer
*/
sendBuffer(buffer, immediate = false) {
sendBuffer (buffer, immediate = false) {
if (immediate) {
const batch = new BatchPacket()
batch.addEncodedPacket(buffer)
@ -97,20 +98,20 @@ class Connection extends EventEmitter {
}
}
sendDecryptedBatch(batch) {
sendDecryptedBatch (batch) {
const buf = batch.encode()
// send to raknet
this.sendMCPE(buf, true)
}
sendEncryptedBatch(batch) {
sendEncryptedBatch (batch) {
const buf = batch.stream.getBuffer()
debug('Sending encrypted batch', batch)
this.encrypt(buf)
}
// TODO: Rename this to sendEncapsulated
sendMCPE(buffer, immediate) {
sendMCPE (buffer, immediate) {
this.connection.sendReliable(buffer, immediate)
}
@ -132,8 +133,8 @@ class Connection extends EventEmitter {
}
}
handle(buffer) { // handle encapsulated
if (buffer[0] == 0xfe) { // wrapper
handle (buffer) { // handle encapsulated
if (buffer[0] === 0xfe) { // wrapper
if (this.encryptionEnabled) {
this.decrypt(buffer.slice(1))
} else {
@ -142,7 +143,7 @@ class Connection extends EventEmitter {
batch.decode()
const packets = batch.getPackets()
this.inLog('Reading ', packets.length, 'packets')
for (var packet of packets) {
for (const packet of packets) {
this.readPacket(packet)
}
}
@ -150,4 +151,4 @@ class Connection extends EventEmitter {
}
}
module.exports = { Connection }
module.exports = { Connection }

View file

@ -1,11 +1,11 @@
const BinaryStream = require('@jsprismarine/jsbinaryutils').default
const Zlib = require('zlib');
const Zlib = require('zlib')
const NETWORK_ID = 0xfe
// This is not a real MCPE packet, it's a wrapper that contains compressed/encrypted batched packets
class BatchPacket {
constructor(stream) {
constructor (stream) {
// Shared
this.payload = Buffer.alloc(0)
this.stream = stream || new BinaryStream()
@ -18,9 +18,9 @@ class BatchPacket {
this.count = 0
}
decode() {
decode () {
// Read header
const pid = this.stream.readByte();
const pid = this.stream.readByte()
if (!pid === NETWORK_ID) {
throw new Error(`Batch ID mismatch: is ${BatchPacket.NETWORK_ID}, got ${pid}`) // this is not a BatchPacket
}
@ -29,14 +29,14 @@ class BatchPacket {
try {
this.payload = Zlib.inflateRawSync(this.stream.readRemaining(), {
chunkSize: 1024 * 1024 * 2
});
})
} catch (e) {
console.error(e)
console.debug(`[bp] Error decompressing packet ${pid}`)
}
}
encode() {
encode () {
const buf = this.stream.getBuffer()
console.log('Encoding payload', buf)
const def = Zlib.deflateRawSync(buf, { level: this.compressionLevel })
@ -45,13 +45,13 @@ class BatchPacket {
return ret
}
addEncodedPacket(packet) {
addEncodedPacket (packet) {
this.stream.writeUnsignedVarInt(packet.byteLength)
this.stream.append(packet)
this.count++
}
getPackets() {
getPackets () {
const stream = new BinaryStream()
stream.buffer = this.payload
const packets = []
@ -64,7 +64,7 @@ class BatchPacket {
return packets
}
static getPackets(stream) {
static getPackets (stream) {
const packets = []
while (!stream.feof()) {
const length = stream.readUnsignedVarInt()
@ -76,4 +76,4 @@ class BatchPacket {
}
}
module.exports = BatchPacket
module.exports = BatchPacket

View file

@ -1,3 +1,4 @@
/* eslint-disable */
const UUID = require('uuid-1345')
const minecraft = require('./minecraft')
const { Read, Write, SizeOf } = require('./varlong')
@ -78,19 +79,6 @@ SizeOf.nbt = ['native', minecraft.nbt[2]]
/**
* Bits
*/
// nvm,
// Read.bitflags = ['parametrizable', (compiler, { type, flags }) => {
// return compiler.wrapCode(`
// const { value, size } = ${compiler.callType('buffer, offset', type)}
// const val = {}
// for (let i = 0; i < size; i++) {
// const hi = (value >> i) & 1
// if ()
// const v = value &
// if (flags[i])
// }
// `
// }]
Read.bitflags = ['parametrizable', (compiler, { type, flags }) => {
return compiler.wrapCode(`
@ -104,7 +92,6 @@ Read.bitflags = ['parametrizable', (compiler, { type, flags }) => {
`.trim())
}]
Write.bitflags = ['parametrizable', (compiler, { type, flags }) => {
return compiler.wrapCode(`
const flags = ${JSON.stringify(flags)}
@ -155,12 +142,12 @@ SizeOf.enum_size_based_on_values_len = ['parametrizable', (compiler) => {
})
}]
function js(fn) {
function js (fn) {
return fn.toString().split('\n').slice(1, -1).join('\n').trim()
}
function str(fn) {
function str (fn) {
return fn.toString() + ')();(()=>{}'
}
module.exports = { Read, Write, SizeOf }
module.exports = { Read, Write, SizeOf }

View file

@ -1,139 +1,139 @@
var nbt = require('prismarine-nbt')
/* eslint-disable */
const nbt = require('prismarine-nbt')
const UUID = require('uuid-1345')
const proto = nbt.protos.littleVarint
// TODO: deal with this:
var zigzag = require('prismarine-nbt/compiler-zigzag')
const zigzag = require('prismarine-nbt/compiler-zigzag')
function readUUID(buffer, offset) {
if (offset + 16 > buffer.length)
throw new PartialReadError();
function readUUID (buffer, offset) {
if (offset + 16 > buffer.length) { throw new PartialReadError() }
return {
value: UUID.stringify(buffer.slice(offset, 16 + offset)),
size: 16
};
}
}
function writeUUID(value, buffer, offset) {
const buf = UUID.parse(value);
buf.copy(buffer, offset);
return offset + 16;
function writeUUID (value, buffer, offset) {
const buf = UUID.parse(value)
buf.copy(buffer, offset)
return offset + 16
}
function readNbt(buffer, offset) {
return proto.read(buffer, offset, "nbt")
function readNbt (buffer, offset) {
return proto.read(buffer, offset, 'nbt')
}
function writeNbt(value, buffer, offset) {
return proto.write(value, buffer, offset, "nbt")
function writeNbt (value, buffer, offset) {
return proto.write(value, buffer, offset, 'nbt')
}
function sizeOfNbt(value) {
return proto.sizeOf(value, "nbt")
function sizeOfNbt (value) {
return proto.sizeOf(value, 'nbt')
}
function readEntityMetadata(buffer, offset, _ref) {
var type = _ref.type;
var endVal = _ref.endVal;
function readEntityMetadata (buffer, offset, _ref) {
const type = _ref.type
const endVal = _ref.endVal
var cursor = offset;
var metadata = [];
var item = undefined;
let cursor = offset
const metadata = []
let item
while (true) {
if (offset + 1 > buffer.length) throw new PartialReadError();
item = buffer.readUInt8(cursor);
if (offset + 1 > buffer.length) throw new PartialReadError()
item = buffer.readUInt8(cursor)
if (item === endVal) {
return {
value: metadata,
size: cursor + 1 - offset
};
}
}
var results = this.read(buffer, cursor, type, {});
metadata.push(results.value);
cursor += results.size;
const results = this.read(buffer, cursor, type, {})
metadata.push(results.value)
cursor += results.size
}
}
function writeEntityMetadata(value, buffer, offset, _ref2) {
var type = _ref2.type;
var endVal = _ref2.endVal;
function writeEntityMetadata (value, buffer, offset, _ref2) {
const type = _ref2.type
const endVal = _ref2.endVal
var self = this;
const self = this
value.forEach(function (item) {
offset = self.write(item, buffer, offset, type, {});
});
buffer.writeUInt8(endVal, offset);
return offset + 1;
offset = self.write(item, buffer, offset, type, {})
})
buffer.writeUInt8(endVal, offset)
return offset + 1
}
function sizeOfEntityMetadata(value, _ref3) {
var type = _ref3.type;
function sizeOfEntityMetadata (value, _ref3) {
const type = _ref3.type
var size = 1;
for (var i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {});
let size = 1
for (let i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {})
}
return size;
return size
}
function readIpAddress(buffer, offset) {
var address = buffer[offset] + '.' + buffer[offset + 1] + '.' + buffer[offset + 2] + '.' + buffer[offset + 3];
function readIpAddress (buffer, offset) {
const address = buffer[offset] + '.' + buffer[offset + 1] + '.' + buffer[offset + 2] + '.' + buffer[offset + 3]
return {
size: 4,
value: address
}
}
function writeIpAddress(value, buffer, offset) {
var address = value.split('.');
function writeIpAddress (value, buffer, offset) {
const address = value.split('.')
address.forEach(function (b) {
buffer[offset] = parseInt(b);
offset++;
});
buffer[offset] = parseInt(b)
offset++
})
return offset;
return offset
}
function readEndOfArray(buffer, offset, typeArgs) {
var type = typeArgs.type;
var cursor = offset;
var elements = [];
function readEndOfArray (buffer, offset, typeArgs) {
const type = typeArgs.type
let cursor = offset
const elements = []
while (cursor < buffer.length) {
var results = this.read(buffer, cursor, type, {});
elements.push(results.value);
cursor += results.size;
const results = this.read(buffer, cursor, type, {})
elements.push(results.value)
cursor += results.size
}
return {
value: elements,
size: cursor - offset
};
}
function writeEndOfArray(value, buffer, offset, typeArgs) {
var type = typeArgs.type;
var self = this;
value.forEach(function (item) {
offset = self.write(item, buffer, offset, type, {});
});
return offset;
}
function sizeOfEndOfArray(value, typeArgs) {
var type = typeArgs.type;
var size = 0;
for (var i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {});
}
return size;
}
function writeEndOfArray (value, buffer, offset, typeArgs) {
const type = typeArgs.type
const self = this
value.forEach(function (item) {
offset = self.write(item, buffer, offset, type, {})
})
return offset
}
function sizeOfEndOfArray (value, typeArgs) {
const type = typeArgs.type
let size = 0
for (let i = 0; i < value.length; ++i) {
size += this.sizeOf(value[i], type, {})
}
return size
}
module.exports = {
'uuid': [readUUID, writeUUID, 16],
'nbt': [readNbt, writeNbt, sizeOfNbt],
'entityMetadataLoop': [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
'ipAddress': [readIpAddress, writeIpAddress, 4],
'endOfArray': [readEndOfArray, writeEndOfArray, sizeOfEndOfArray],
'zigzag32': zigzag.zigzag32,
'zigzag64': zigzag.zigzag64
}
uuid: [readUUID, writeUUID, 16],
nbt: [readNbt, writeNbt, sizeOfNbt],
entityMetadataLoop: [readEntityMetadata, writeEntityMetadata, sizeOfEntityMetadata],
ipAddress: [readIpAddress, writeIpAddress, 4],
endOfArray: [readEndOfArray, writeEndOfArray, sizeOfEndOfArray],
zigzag32: zigzag.zigzag32,
zigzag64: zigzag.zigzag64
}

View file

@ -1,37 +1,33 @@
const fs = require('fs');
const fs = require('fs')
function getFiles(dir) {
var results = [];
var list = fs.readdirSync(dir);
list.forEach(function (file) {
file = dir + '/' + file;
var stat = fs.statSync(file);
function getFiles (dir) {
let results = []
const list = fs.readdirSync(dir)
list.forEach((file) => {
file = dir + '/' + file
const stat = fs.statSync(file)
if (stat && stat.isDirectory()) {
/* Recurse into a subdirectory */
results = results.concat(getFiles(file));
results = results.concat(getFiles(file))
} else {
/* Is a file */
results.push(file);
results.push(file)
}
});
return results;
})
return results
}
module.exports = {
sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
},
function sleep (ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
waitFor(cb, withTimeout) {
return Promise.race([
new Promise((res, rej) => cb(res)),
sleep(withTimeout)
])
},
function waitFor (cb, withTimeout) {
return Promise.race([
new Promise((resolve) => cb(resolve)),
sleep(withTimeout)
])
}
serialize(obj = {}, fmt) {
return JSON.stringify(obj, (k, v) => typeof v == 'bigint' ? v.toString() : v, fmt)
},
function serialize (obj = {}, fmt) {
return JSON.stringify(obj, (k, v) => typeof v === 'bigint' ? v.toString() : v, fmt)
}
getFiles
}
module.exports = { getFiles, sleep, waitFor, serialize }

View file

@ -1,4 +1,4 @@
function sizeOfVarLong(value) {
function sizeOfVarLong (value) {
if (typeof value.valueOf() === 'object') {
value = (BigInt(value[0]) << 32n) | BigInt(value[1])
} else if (typeof value !== 'bigint') value = BigInt(value)
@ -14,7 +14,7 @@ function sizeOfVarLong(value) {
/**
* Reads a 64-bit VarInt as a BigInt
*/
function readVarLong(buffer, offset) {
function readVarLong (buffer, offset) {
let result = BigInt(0)
let shift = 0n
let cursor = offset
@ -39,7 +39,7 @@ function readVarLong(buffer, offset) {
/**
* Writes a zigzag encoded 64-bit VarInt as a BigInt
*/
function writeVarLong(value, buffer, offset) {
function writeVarLong (value, buffer, offset) {
// if an array, turn it into a BigInt
if (typeof value.valueOf() === 'object') {
value = BigInt.asIntN(64, (BigInt(value[0]) << 32n)) | BigInt(value[1])
@ -60,4 +60,4 @@ module.exports = {
Read: { varint64: ['native', readVarLong] },
Write: { varint64: ['native', writeVarLong] },
SizeOf: { varint64: ['native', sizeOfVarLong] }
}
}

View file

@ -13,4 +13,4 @@ const Versions = {
'1.16.201': 422
}
module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION, Versions }
module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION, Versions }

View file

@ -1,26 +1,25 @@
const { EventEmitter } = require('events')
const Listener = require('jsp-raknet/listener')
const EncapsulatedPacket = require('jsp-raknet/protocol/encapsulated_packet')
const Reliability = require('jsp-raknet/protocol/reliability')
const RakClient = require('jsp-raknet/client')
const ConnWorker = require('./rakWorker')
const { waitFor } = require('./datatypes/util')
try {
var { Client, Server, PacketPriority, PacketReliability, McPingMessage } = require('raknet-native')
var { Client, Server, PacketPriority, PacketReliability, McPingMessage } = require('raknet-native') // eslint-disable-line
} catch (e) {
console.debug('[raknet] native not found, using js', e)
}
class RakNativeClient extends EventEmitter {
constructor(options) {
constructor (options) {
super()
this.onConnected = () => { }
this.onCloseConnection = () => { }
this.onEncapsulated = () => { }
this.raknet = new Client(options.hostname, options.port, 'minecraft')
this.raknet.on('encapsulated', thingy => {
// console.log('Encap',thingy)
const { buffer, address, guid } = thingy
this.raknet.on('encapsulated', ({ buffer, address }) => {
this.onEncapsulated(buffer, address)
})
this.raknet.on('connected', () => {
@ -28,7 +27,7 @@ class RakNativeClient extends EventEmitter {
})
}
async ping() {
async ping () {
this.raknet.ping()
return waitFor((done) => {
this.raknet.on('pong', (ret) => {
@ -39,18 +38,18 @@ class RakNativeClient extends EventEmitter {
}, 1000)
}
connect() {
connect () {
this.raknet.connect()
}
sendReliable(buffer, immediate) {
sendReliable (buffer, immediate) {
const priority = immediate ? PacketPriority.IMMEDIATE_PRIORITY : PacketPriority.MEDIUM_PRIORITY
return this.raknet.send(buffer, priority, PacketReliability.RELIABLE_ORDERED, 0)
}
}
class RakNativeServer extends EventEmitter {
constructor(options = {}) {
constructor (options = {}) {
super()
this.onOpenConnection = () => { }
this.onCloseConnection = () => { }
@ -75,20 +74,19 @@ class RakNativeServer extends EventEmitter {
this.onCloseConnection(client)
})
this.raknet.on('encapsulated', (thingy) => {
const { buffer, address, guid } = thingy
this.raknet.on('encapsulated', ({ buffer, address }) => {
// console.log('ENCAP',thingy)
this.onEncapsulated(buffer, address)
})
}
listen() {
listen () {
this.raknet.listen()
}
}
class RakJsClient extends EventEmitter {
constructor(options = {}) {
constructor (options = {}) {
super()
this.onConnected = () => { }
this.onEncapsulated = () => { }
@ -101,23 +99,25 @@ class RakJsClient extends EventEmitter {
}
}
workerConnect(hostname = this.options.hostname, port = this.options.port) {
workerConnect (hostname = this.options.hostname, port = this.options.port) {
this.worker = ConnWorker.connect(hostname, port)
this.worker.on('message', (evt) => {
switch (evt.type) {
case 'connected':
case 'connected': {
this.onConnected()
break
case 'encapsulated':
}
case 'encapsulated': {
const [ecapsulated, address] = evt.args
this.onEncapsulated(ecapsulated.buffer, address.hash)
break
}
}
})
}
async plainConnect(hostname = this.options.hostname, port = this.options.port) {
async plainConnect (hostname = this.options.hostname, port = this.options.port) {
this.raknet = new RakClient(hostname, port)
await this.raknet.connect()
@ -129,11 +129,11 @@ class RakJsClient extends EventEmitter {
this.raknet.on('encapsulated', (encapsulated, addr) => this.onEncapsulated(encapsulated.buffer, addr.hash))
}
workerSendReliable(buffer, immediate) {
workerSendReliable (buffer, immediate) {
this.worker.postMessage({ type: 'queueEncapsulated', packet: buffer, immediate })
}
plainSendReliable(buffer, immediate) {
plainSendReliable (buffer, immediate) {
const sendPacket = new EncapsulatedPacket()
sendPacket.reliability = Reliability.ReliableOrdered
sendPacket.buffer = buffer
@ -143,7 +143,7 @@ class RakJsClient extends EventEmitter {
}
class RakJsServer extends EventEmitter {
constructor(options = {}) {
constructor (options = {}) {
super()
this.options = options
this.onOpenConnection = () => { }
@ -157,7 +157,7 @@ class RakJsServer extends EventEmitter {
}
}
async plainListen() {
async plainListen () {
this.raknet = new Listener()
await this.raknet.listen(this.options.hostname, this.options.port)
this.raknet.on('openConnection', (conn) => {
@ -178,4 +178,4 @@ class RakJsServer extends EventEmitter {
module.exports = {
RakClient: Client ? RakNativeClient : RakJsClient,
RakServer: Server ? RakNativeServer : RakJsServer
}
}

View file

@ -3,7 +3,7 @@ const { Worker, isMainThread, parentPort } = require('worker_threads')
const EncapsulatedPacket = require('jsp-raknet/protocol/encapsulated_packet')
const Reliability = require('jsp-raknet/protocol/reliability')
function connect(hostname, port) {
function connect (hostname, port) {
if (isMainThread) {
const worker = new Worker(__filename)
worker.postMessage({ type: 'connect', hostname, port })
@ -11,12 +11,12 @@ function connect(hostname, port) {
}
}
var raknet
let raknet
function main() {
function main () {
parentPort.on('message', (evt) => {
if (evt.type == 'connect') {
const { hostname, port } =evt
if (evt.type === 'connect') {
const { hostname, port } = evt
raknet = new RakClient(hostname, port)
raknet.connect().then(() => {
@ -30,7 +30,7 @@ function main() {
})
raknet.once('connected', (connection) => {
console.log(`[worker] connected!`)
console.log('[worker] connected!')
globalThis.raknetConnection = connection
parentPort.postMessage({ type: 'connected' })
})
@ -45,8 +45,8 @@ function main() {
raknet.on('raw', (buffer, inetAddr) => {
console.log('Raw packet', buffer, inetAddr)
})
} else if (evt.type == 'queueEncapsulated') {
console.log('SEND' , globalThis.raknetConnection, evt.packet)
} else if (evt.type === 'queueEncapsulated') {
// console.log('SEND', globalThis.raknetConnection, evt.packet)
const sendPacket = new EncapsulatedPacket()
sendPacket.reliability = Reliability.ReliableOrdered
@ -61,4 +61,4 @@ function main() {
}
if (!isMainThread) main()
module.exports = { connect }
module.exports = { connect }

View file

@ -1,8 +1,7 @@
// process.env.DEBUG = 'minecraft-protocol raknet'
const fs = require('fs')
const { Client } = require("./client")
const { Server } = require("./server")
const { Player } = require("./serverPlayer")
const { Client } = require('./client')
const { Server } = require('./server')
const { Player } = require('./serverPlayer')
const debug = require('debug')('minecraft-protocol relay')
const { serialize } = require('./datatypes/util')
@ -11,7 +10,7 @@ const { serialize } = require('./datatypes/util')
const debugging = true // Do re-encoding tests
class RelayPlayer extends Player {
constructor(server, conn) {
constructor (server, conn) {
super(server, conn)
this.server = server
this.conn = conn
@ -47,7 +46,7 @@ class RelayPlayer extends Player {
}
// Called when we get a packet from backend server (Backend -> PROXY -> Client)
readUpstream(packet) {
readUpstream (packet) {
if (!this.startRelaying) {
console.warn('The downstream client is not ready yet !!')
this.downQ.push(packet)
@ -59,7 +58,7 @@ class RelayPlayer extends Player {
const params = des.data.params
this.upInLog('~ Bounce B->C', name, serialize(params).slice(0, 100))
// this.upInLog('~ ', des.buffer)
if (name == 'play_status' && params.status == 'login_success') return // We already sent this, this needs to be sent ASAP or client will disconnect
if (name === 'play_status' && params.status === 'login_success') return // We already sent this, this needs to be sent ASAP or client will disconnect
if (debugging) { // some packet encode/decode testing stuff
const rpacket = this.server.serializer.createPacketBuffer({ name, params })
@ -76,7 +75,7 @@ class RelayPlayer extends Player {
}
// Send queued packets to the connected client
flushDownQueue() {
flushDownQueue () {
for (const packet of this.downQ) {
const des = this.server.deserializer.parsePacketBuffer(packet)
this.write(des.data.name, des.data.params)
@ -85,10 +84,10 @@ class RelayPlayer extends Player {
}
// Send queued packets to the backend upstream server from the client
flushUpQueue() {
for (var e of this.upQ) { // Send the queue
flushUpQueue () {
for (const e of this.upQ) { // Send the queue
const des = this.server.deserializer.parsePacketBuffer(e)
if (des.data.name == 'client_cache_status') { // Currently broken, force off the chunk cache
if (des.data.name === 'client_cache_status') { // Currently broken, force off the chunk cache
this.upstream.write('client_cache_status', { enabled: false })
} else {
this.upstream.write(des.data.name, des.data.params)
@ -98,8 +97,8 @@ class RelayPlayer extends Player {
}
// Called when the server gets a packet from the downstream player (Client -> PROXY -> Backend)
readPacket(packet) {
if (this.startRelaying) { // The downstream client conn is established & we got a packet to send to upstream server
readPacket (packet) {
if (this.startRelaying) { // The downstream client conn is established & we got a packet to send to upstream server
if (!this.upstream) { // Upstream is still connecting/handshaking
this.downInLog('Got downstream connected packet but upstream is not connected yet, added to q', this.upQ.length)
this.upQ.push(packet) // Put into a queue
@ -138,16 +137,16 @@ class RelayPlayer extends Player {
class Relay extends Server {
/**
* Creates a new non-transparent proxy connection to a destination server
* @param {Options} options
* @param {Options} options
*/
constructor(options) {
constructor (options) {
super(options)
this.RelayPlayer = options.relayPlayer || RelayPlayer
this.forceSingle = true
this.upstreams = new Map()
}
openUpstreamConnection(ds, clientAddr) {
openUpstreamConnection (ds, clientAddr) {
const client = new Client({
hostname: this.options.destination.hostname,
port: this.options.destination.port,
@ -165,7 +164,7 @@ class Relay extends Server {
this.upstreams.set(clientAddr.hash, client)
}
closeUpstreamConnection(clientAddr) {
closeUpstreamConnection (clientAddr) {
const up = this.upstreams.get(clientAddr.hash)
if (!up) throw Error(`unable to close non-open connection ${clientAddr.hash}`)
up.close()
@ -189,4 +188,4 @@ class Relay extends Server {
}
// Too many things called 'Proxy' ;)
module.exports = { Relay }
module.exports = { Relay }

View file

@ -6,7 +6,7 @@ const Options = require('./options')
const debug = require('debug')('minecraft-protocol')
class Server extends EventEmitter {
constructor(options) {
constructor (options) {
super()
this.options = { ...Options.defaultOptions, ...options }
this.validateOptions()
@ -18,7 +18,7 @@ class Server extends EventEmitter {
this.outLog = (...args) => console.debug('S -> C', ...args)
}
validateOptions() {
validateOptions () {
if (!Options.Versions[this.options.version]) {
console.warn('Supported versions: ', Options.Versions)
throw Error(`Unsupported version ${this.options.version}`)
@ -52,7 +52,7 @@ class Server extends EventEmitter {
client.handle(buffer)
}
async create(hostname = this.options.hostname, port = this.options.port) {
async create (hostname = this.options.hostname, port = this.options.port) {
this.raknet = new RakServer({ hostname, port })
await this.raknet.listen()
console.debug('Listening on', hostname, port)
@ -62,4 +62,4 @@ class Server extends EventEmitter {
}
}
module.exports = { Server }
module.exports = { Server }

View file

@ -2,7 +2,8 @@ const { Encrypt } = require('./auth/encryption')
const { decodeLoginJWT } = require('./auth/chains')
const { Connection } = require('./connection')
const fs = require('fs')
const debug = require('debug')('minecraft-protocol')
// const debug = require('debug')('minecraft-protocol')
const { MIN_VERSION } = require('./options')
const ClientStatus = {
Authenticating: 0,
@ -11,7 +12,7 @@ const ClientStatus = {
}
class Player extends Connection {
constructor(server, connection) {
constructor (server, connection) {
super()
this.server = server
this.serializer = server.serializer
@ -26,12 +27,12 @@ class Player extends Connection {
this.outLog = (...args) => console.info('C -> S', ...args)
}
getData() {
getData () {
return this.userData
}
onLogin(packet) {
let body = packet.data
onLogin (packet) {
const body = packet.data
// debug('Login body', body)
this.emit('loggingIn', body)
@ -51,7 +52,7 @@ class Player extends Connection {
const skinChain = body.params.client_data
try {
var { key, userData, chain } = decodeLoginJWT(authChain.chain, skinChain)
var { key, userData, chain } = decodeLoginJWT(authChain.chain, skinChain) // eslint-disable-line
} catch (e) {
console.error(e)
// TODO: disconnect user
@ -68,17 +69,17 @@ class Player extends Connection {
/**
* Disconnects a client before it has joined
* @param {string} play_status
* @param {string} playStatus
*/
sendDisconnectStatus(play_status) {
this.write('play_status', { status: play_status })
sendDisconnectStatus (playStatus) {
this.write('play_status', { status: playStatus })
this.connection.close()
}
/**
* Disconnects a client after it has joined
*/
disconnect(reason, hide = false) {
disconnect (reason, hide = false) {
this.write('disconnect', {
hide_disconnect_screen: hide,
message: reason
@ -88,7 +89,7 @@ class Player extends Connection {
// After sending Server to Client Handshake, this handles the client's
// Client to Server handshake response. This indicates successful encryption
onHandshake() {
onHandshake () {
// this.outLog('Sending login success!', this.status)
// https://wiki.vg/Bedrock_Protocol#Play_Status
this.write('play_status', { status: 'login_success' })
@ -96,10 +97,10 @@ class Player extends Connection {
this.emit('join')
}
readPacket(packet) {
readPacket (packet) {
// console.log('packet', packet)
try {
var des = this.server.deserializer.parsePacketBuffer(packet)
var des = this.server.deserializer.parsePacketBuffer(packet) // eslint-disable-line
} catch (e) {
this.disconnect('Server error')
console.warn('Packet parsing failed! Writing dump to ./packetdump.bin')
@ -117,10 +118,12 @@ class Player extends Connection {
case 'client_to_server_handshake':
// Emit the 'join' event
this.onHandshake()
break
case 'set_local_player_as_initialized':
this.state = ClientStatus.Initialized
// Emit the 'spawn' event
this.emit('spawn')
break
default:
console.log('ignoring, unhandled')
}
@ -128,4 +131,4 @@ class Player extends Connection {
}
}
module.exports = { Player, ClientStatus }
module.exports = { Player, ClientStatus }

View file

@ -5,14 +5,14 @@ const Zlib = require('zlib')
const CIPHER_ALG = 'aes-256-cfb8'
function createCipher(secret, initialValue) {
function createCipher (secret, initialValue) {
if (crypto.getCiphers().includes(CIPHER_ALG)) {
return crypto.createCipheriv(CIPHER_ALG, secret, initialValue)
}
return new Cipher(secret, initialValue)
}
function createDecipher(secret, initialValue) {
function createDecipher (secret, initialValue) {
if (crypto.getCiphers().includes(CIPHER_ALG)) {
return crypto.createDecipheriv(CIPHER_ALG, secret, initialValue)
}
@ -20,12 +20,12 @@ function createDecipher(secret, initialValue) {
}
class Cipher extends Transform {
constructor(secret, iv) {
constructor (secret, iv) {
super()
this.aes = new aesjs.ModeOfOperation.cfb(secret, iv, 1) // eslint-disable-line new-cap
}
_transform(chunk, enc, cb) {
_transform (chunk, enc, cb) {
try {
const res = this.aes.encrypt(chunk)
cb(null, res)
@ -36,12 +36,12 @@ class Cipher extends Transform {
}
class Decipher extends Transform {
constructor(secret, iv) {
constructor (secret, iv) {
super()
this.aes = new aesjs.ModeOfOperation.cfb(secret, iv, 1) // eslint-disable-line new-cap
}
_transform(chunk, enc, cb) {
_transform (chunk, enc, cb) {
try {
const res = this.aes.decrypt(chunk)
cb(null, res)
@ -51,26 +51,26 @@ class Decipher extends Transform {
}
}
function computeCheckSum(packetPlaintext, sendCounter, secretKeyBytes) {
let digest = crypto.createHash('sha256');
let counter = Buffer.alloc(8)
function computeCheckSum (packetPlaintext, sendCounter, secretKeyBytes) {
const digest = crypto.createHash('sha256')
const counter = Buffer.alloc(8)
counter.writeBigInt64LE(sendCounter, 0)
digest.update(counter);
digest.update(packetPlaintext);
digest.update(secretKeyBytes);
let hash = digest.digest();
return hash.slice(0, 8);
digest.update(counter)
digest.update(packetPlaintext)
digest.update(secretKeyBytes)
const hash = digest.digest()
return hash.slice(0, 8)
}
function createEncryptor(client, iv) {
function createEncryptor (client, iv) {
client.cipher = createCipher(client.secretKeyBytes, iv)
client.sendCounter = client.sendCounter || 0n
// A packet is encrypted via AES256(plaintext + SHA256(send_counter + plaintext + secret_key)[0:8]).
// The send counter is represented as a little-endian 64-bit long and incremented after each packet.
function process(chunk) {
const buffer = Zlib.deflateRawSync(chunk, { level: 7 })
function process (chunk) {
const buffer = Zlib.deflateRawSync(chunk, { level: 7 })
const packet = Buffer.concat([buffer, computeCheckSum(buffer, client.sendCounter, client.secretKeyBytes)])
client.sendCounter++
client.cipher.write(packet)
@ -83,12 +83,11 @@ function createEncryptor(client, iv) {
}
}
function createDecryptor(client, iv) {
function createDecryptor (client, iv) {
client.decipher = createDecipher(client.secretKeyBytes, iv)
client.receiveCounter = client.receiveCounter || 0n
function verify(chunk) {
function verify (chunk) {
// TODO: remove the extra logic here, probably fixed with new raknet impl
// console.log('Decryptor: checking checksum', client.receiveCounter, chunk)
@ -102,7 +101,7 @@ function createDecryptor(client, iv) {
// Holds how much bytes we read, also where the checksum (should) start
const inflatedLen = engine.bytesRead
// It appears that mc sends extra bytes past the checksum. I don't think this is a raknet
// issue (as we are able to decipher properly, zlib works and should also have a checksum) so
// issue (as we are able to decipher properly, zlib works and should also have a checksum) so
// there needs to be more investigation done. If you know what's wrong here, please make an issue :)
const extraneousLen = chunk.length - inflatedLen - 8
if (extraneousLen > 0) { // Extra bytes
@ -115,16 +114,16 @@ function createDecryptor(client, iv) {
throw new Error('Decrypted packet is missing checksum')
}
const packet = chunk.slice(0, inflatedLen);
const checksum = chunk.slice(inflatedLen, inflatedLen + 8);
const packet = chunk.slice(0, inflatedLen)
const checksum = chunk.slice(inflatedLen, inflatedLen + 8)
const computedCheckSum = computeCheckSum(packet, client.receiveCounter, client.secretKeyBytes)
client.receiveCounter++
if (checksum.toString("hex") == computedCheckSum.toString("hex")) {
if (checksum.toString('hex') === computedCheckSum.toString('hex')) {
client.onDecryptedPacket(buffer)
} else {
console.log('Inflated', inflatedLen, chunk.length, extraneousLen, chunk.toString('hex'))
throw Error(`Checksum mismatch ${checksum.toString("hex")} != ${computedCheckSum.toString("hex")}`)
throw Error(`Checksum mismatch ${checksum.toString('hex')} != ${computedCheckSum.toString('hex')}`)
}
}
@ -139,16 +138,16 @@ module.exports = {
createCipher, createDecipher, createEncryptor, createDecryptor
}
function testDecrypt() {
const client = {
secretKeyBytes: Buffer.from('ZOBpyzki/M8UZv5tiBih048eYOBVPkQE3r5Fl0gmUP4=', 'base64'),
onDecryptedPacket: (...data) => console.log('Decrypted', data)
}
const iv = Buffer.from('ZOBpyzki/M8UZv5tiBih0w==', 'base64')
// function testDecrypt () {
// const client = {
// secretKeyBytes: Buffer.from('ZOBpyzki/M8UZv5tiBih048eYOBVPkQE3r5Fl0gmUP4=', 'base64'),
// onDecryptedPacket: (...data) => console.log('Decrypted', data)
// }
// const iv = Buffer.from('ZOBpyzki/M8UZv5tiBih0w==', 'base64')
const decrypt = createDecryptor(client, iv)
console.log('Dec', decrypt(Buffer.from('4B4FCA0C2A4114155D67F8092154AAA5EF', 'hex')))
console.log('Dec 2', decrypt(Buffer.from('DF53B9764DB48252FA1AE3AEE4', 'hex')))
}
// const decrypt = createDecryptor(client, iv)
// console.log('Dec', decrypt(Buffer.from('4B4FCA0C2A4114155D67F8092154AAA5EF', 'hex')))
// console.log('Dec 2', decrypt(Buffer.from('DF53B9764DB48252FA1AE3AEE4', 'hex')))
// }
// testDecrypt()
// testDecrypt()

View file

@ -2,9 +2,9 @@ const { ProtoDefCompiler, CompiledProtodef } = require('protodef').Compiler
const { FullPacketParser, Serializer } = require('protodef')
// Compiles the ProtoDef schema at runtime
function createProtocol(version) {
function createProtocol (version) {
const protocol = require(`../../data/${version}/protocol.json`).types
var compiler = new ProtoDefCompiler()
const compiler = new ProtoDefCompiler()
compiler.addTypesToCompile(protocol)
compiler.addTypes(require('../datatypes/compiler-minecraft'))
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
@ -14,7 +14,7 @@ function createProtocol(version) {
}
// Loads already generated read/write/sizeof code
function getProtocol(version) {
function getProtocol (version) {
const compiler = new ProtoDefCompiler()
compiler.addTypes(require('../datatypes/compiler-minecraft'))
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
@ -32,18 +32,18 @@ function getProtocol(version) {
)
}
function createSerializer(version) {
var proto = getProtocol(version)
return new Serializer(proto, 'mcpe_packet');
function createSerializer (version) {
const proto = getProtocol(version)
return new Serializer(proto, 'mcpe_packet')
}
function createDeserializer(version) {
var proto = getProtocol(version)
return new FullPacketParser(proto, 'mcpe_packet');
function createDeserializer (version) {
const proto = getProtocol(version)
return new FullPacketParser(proto, 'mcpe_packet')
}
module.exports = {
createDeserializer: createDeserializer,
createSerializer: createSerializer,
createProtocol: createProtocol
}
}