parent
df8612e355
commit
1c582acdb5
36 changed files with 539 additions and 731 deletions
1
.eslintignore
Normal file
1
.eslintignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
test/
|
||||
3
babel.config.js
Normal file
3
babel.config.js
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
presets: ['@babel/preset-env']
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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.
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -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));
|
||||
});
|
||||
|
|
@ -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");
|
||||
})
|
||||
});
|
||||
|
|
@ -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");
|
||||
})
|
||||
});
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
3
index.js
3
index.js
|
|
@ -1,3 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = require('./dist/index.js');
|
||||
18
package.json
18
package.json
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
module.exports = {
|
||||
PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V'
|
||||
PUBLIC_KEY: 'MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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] }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
44
src/rak.js
44
src/rak.js
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
35
src/relay.js
35
src/relay.js
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue