Start work on multi-version support, test cleanup (#43)
* some cleanup * start work on multi-version support * undelete some old examples, can update them later * move old examples
This commit is contained in:
parent
3ffcf841ea
commit
df8612e355
35 changed files with 368 additions and 3517 deletions
10
.gitignore
vendored
10
.gitignore
vendored
|
|
@ -2,7 +2,11 @@ node_modules/
|
|||
npm-debug.log
|
||||
package-lock.json
|
||||
__*
|
||||
data/*.js
|
||||
src/**/*.json
|
||||
src/**/*.txt
|
||||
dist/
|
||||
# Runtime generated data
|
||||
data/*/sample
|
||||
data/**/read.js
|
||||
data/**/write.js
|
||||
data/**/size.js
|
||||
samples/*.txt
|
||||
samples/*.json
|
||||
1
data/1.16.201/skin_geom.txt
Normal file
1
data/1.16.201/skin_geom.txt
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -8,11 +8,14 @@
|
|||
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
|
||||
|
||||
// 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')) {
|
||||
|
|
@ -30,31 +33,39 @@ function genProtoSchema() {
|
|||
l1 += ` 0x${id.toString(16).padStart(2, '0')}: ${name}\n`
|
||||
l2 += ` if ${name}: ${fname}\n`
|
||||
}
|
||||
const t = `!import: types.yaml\nmcpe_packet:\n name: varint =>\n${l1}\n params: name ?\n${l2}`
|
||||
// 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
|
||||
}
|
||||
|
||||
genProtoSchema()
|
||||
|
||||
fs.writeFileSync('../newproto.json', JSON.stringify({ types: require('./protocol.json') }, null, 2))
|
||||
fs.unlinkSync('./protocol.json') //remove temp file
|
||||
|
||||
function createProtocol() {
|
||||
// Compile the ProtoDef JSON into JS
|
||||
function createProtocol(version) {
|
||||
const compiler = new ProtoDefCompiler()
|
||||
const protocol = require('../newproto.json').types
|
||||
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('../read.js', 'module.exports = ' + compiler.readCompiler.generate())
|
||||
fs.writeFileSync('../write.js', 'module.exports = ' + compiler.writeCompiler.generate())
|
||||
fs.writeFileSync('../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
|
||||
}
|
||||
|
||||
console.log('Generating JS...')
|
||||
createProtocol()
|
||||
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)
|
||||
}
|
||||
|
||||
main()
|
||||
|
|
@ -1,317 +0,0 @@
|
|||
!import: types.yaml
|
||||
mcpe_packet:
|
||||
name: varint =>
|
||||
0x01: login
|
||||
0x02: play_status
|
||||
0x03: server_to_client_handshake
|
||||
0x04: client_to_server_handshake
|
||||
0x05: disconnect
|
||||
0x06: resource_packs_info
|
||||
0x07: resource_pack_stack
|
||||
0x08: resource_pack_client_response
|
||||
0x09: text
|
||||
0x0a: set_time
|
||||
0x0b: start_game
|
||||
0x0c: add_player
|
||||
0x0d: add_entity
|
||||
0x0e: remove_entity
|
||||
0x0f: add_item_entity
|
||||
0x11: take_item_entity
|
||||
0x12: move_entity
|
||||
0x13: move_player
|
||||
0x14: rider_jump
|
||||
0x15: update_block
|
||||
0x16: add_painting
|
||||
0x17: tick_sync
|
||||
0x18: level_sound_event_old
|
||||
0x19: level_event
|
||||
0x1a: block_event
|
||||
0x1b: entity_event
|
||||
0x1c: mob_effect
|
||||
0x1d: update_attributes
|
||||
0x1e: inventory_transaction
|
||||
0x1f: mob_equipment
|
||||
0x20: mob_armor_equipment
|
||||
0x21: interact
|
||||
0x22: block_pick_request
|
||||
0x23: entity_pick_request
|
||||
0x24: player_action
|
||||
0x26: hurt_armor
|
||||
0x27: set_entity_data
|
||||
0x28: set_entity_motion
|
||||
0x29: set_entity_link
|
||||
0x2a: set_health
|
||||
0x2b: set_spawn_position
|
||||
0x2c: animate
|
||||
0x2d: respawn
|
||||
0x2e: container_open
|
||||
0x2f: container_close
|
||||
0x30: player_hotbar
|
||||
0x31: inventory_content
|
||||
0x32: inventory_slot
|
||||
0x33: container_set_data
|
||||
0x34: crafting_data
|
||||
0x35: crafting_event
|
||||
0x36: gui_data_pick_item
|
||||
0x37: adventure_settings
|
||||
0x38: block_entity_data
|
||||
0x39: player_input
|
||||
0x3a: level_chunk
|
||||
0x3b: set_commands_enabled
|
||||
0x3c: set_difficulty
|
||||
0x3d: change_dimension
|
||||
0x3e: set_player_game_type
|
||||
0x3f: player_list
|
||||
0x40: simple_event
|
||||
0x41: event
|
||||
0x42: spawn_experience_orb
|
||||
0x43: clientbound_map_item_data
|
||||
0x44: map_info_request
|
||||
0x45: request_chunk_radius
|
||||
0x46: chunk_radius_update
|
||||
0x47: item_frame_drop_item
|
||||
0x48: game_rules_changed
|
||||
0x49: camera
|
||||
0x4a: boss_event
|
||||
0x4b: show_credits
|
||||
0x4c: available_commands
|
||||
0x4d: command_request
|
||||
0x4e: command_block_update
|
||||
0x4f: command_output
|
||||
0x50: update_trade
|
||||
0x51: update_equipment
|
||||
0x52: resource_pack_data_info
|
||||
0x53: resource_pack_chunk_data
|
||||
0x54: resource_pack_chunk_request
|
||||
0x55: transfer
|
||||
0x56: play_sound
|
||||
0x57: stop_sound
|
||||
0x58: set_title
|
||||
0x59: add_behavior_tree
|
||||
0x5a: structure_block_update
|
||||
0x5b: show_store_offer
|
||||
0x5c: purchase_receipt
|
||||
0x5d: player_skin
|
||||
0x5e: sub_client_login
|
||||
0x5f: initiate_web_socket_connection
|
||||
0x60: set_last_hurt_by
|
||||
0x61: book_edit
|
||||
0x62: npc_request
|
||||
0x63: photo_transfer
|
||||
0x64: modal_form_request
|
||||
0x65: modal_form_response
|
||||
0x66: server_settings_request
|
||||
0x67: server_settings_response
|
||||
0x68: show_profile
|
||||
0x69: set_default_game_type
|
||||
0x6a: remove_objective
|
||||
0x6b: set_display_objective
|
||||
0x6c: set_score
|
||||
0x6d: lab_table
|
||||
0x6e: update_block_synced
|
||||
0x6f: move_entity_delta
|
||||
0x70: set_scoreboard_identity
|
||||
0x71: set_local_player_as_initialized
|
||||
0x72: update_soft_enum
|
||||
0x73: network_stack_latency
|
||||
0x75: script_custom_event
|
||||
0x76: spawn_particle_effect
|
||||
0x77: available_entity_identifiers
|
||||
0x78: level_sound_event_v2
|
||||
0x79: network_chunk_publisher_update
|
||||
0x7a: biome_definition_list
|
||||
0x7b: level_sound_event
|
||||
0x7c: level_event_generic
|
||||
0x7d: lectern_update
|
||||
0x7e: video_stream_connect
|
||||
0x7f: add_ecs_entity
|
||||
0x80: remove_ecs_entity
|
||||
0x81: client_cache_status
|
||||
0x82: on_screen_texture_animation
|
||||
0x83: map_create_locked_copy
|
||||
0x84: structure_template_data_export_request
|
||||
0x85: structure_template_data_export_response
|
||||
0x86: update_block_properties
|
||||
0x87: client_cache_blob_status
|
||||
0x88: client_cache_miss_response
|
||||
0x89: education_settings
|
||||
0x8b: multiplayer_settings
|
||||
0x8c: settings_command
|
||||
0x8d: anvil_damage
|
||||
0x8e: completed_using_item
|
||||
0x8f: network_settings
|
||||
0x90: player_auth_input
|
||||
0x91: creative_content
|
||||
0x92: player_enchant_options
|
||||
0x93: item_stack_request
|
||||
0x94: item_stack_response
|
||||
0x95: player_armor_damage
|
||||
0x97: update_player_game_type
|
||||
0x9a: position_tracking_db_request
|
||||
0x99: position_tracking_db_broadcast
|
||||
0x9c: packet_violation_warning
|
||||
0x9d: motion_prediction_hints
|
||||
0x9e: animate_entity
|
||||
0x9f: camera_shake
|
||||
0xa0: player_fog
|
||||
0xa1: correct_player_move_prediction
|
||||
0xa2: item_component
|
||||
0xa3: filter_text_packet
|
||||
|
||||
params: name ?
|
||||
if login: packet_login
|
||||
if play_status: packet_play_status
|
||||
if server_to_client_handshake: packet_server_to_client_handshake
|
||||
if client_to_server_handshake: packet_client_to_server_handshake
|
||||
if disconnect: packet_disconnect
|
||||
if resource_packs_info: packet_resource_packs_info
|
||||
if resource_pack_stack: packet_resource_pack_stack
|
||||
if resource_pack_client_response: packet_resource_pack_client_response
|
||||
if text: packet_text
|
||||
if set_time: packet_set_time
|
||||
if start_game: packet_start_game
|
||||
if add_player: packet_add_player
|
||||
if add_entity: packet_add_entity
|
||||
if remove_entity: packet_remove_entity
|
||||
if add_item_entity: packet_add_item_entity
|
||||
if take_item_entity: packet_take_item_entity
|
||||
if move_entity: packet_move_entity
|
||||
if move_player: packet_move_player
|
||||
if rider_jump: packet_rider_jump
|
||||
if update_block: packet_update_block
|
||||
if add_painting: packet_add_painting
|
||||
if tick_sync: packet_tick_sync
|
||||
if level_sound_event_old: packet_level_sound_event_old
|
||||
if level_event: packet_level_event
|
||||
if block_event: packet_block_event
|
||||
if entity_event: packet_entity_event
|
||||
if mob_effect: packet_mob_effect
|
||||
if update_attributes: packet_update_attributes
|
||||
if inventory_transaction: packet_inventory_transaction
|
||||
if mob_equipment: packet_mob_equipment
|
||||
if mob_armor_equipment: packet_mob_armor_equipment
|
||||
if interact: packet_interact
|
||||
if block_pick_request: packet_block_pick_request
|
||||
if entity_pick_request: packet_entity_pick_request
|
||||
if player_action: packet_player_action
|
||||
if hurt_armor: packet_hurt_armor
|
||||
if set_entity_data: packet_set_entity_data
|
||||
if set_entity_motion: packet_set_entity_motion
|
||||
if set_entity_link: packet_set_entity_link
|
||||
if set_health: packet_set_health
|
||||
if set_spawn_position: packet_set_spawn_position
|
||||
if animate: packet_animate
|
||||
if respawn: packet_respawn
|
||||
if container_open: packet_container_open
|
||||
if container_close: packet_container_close
|
||||
if player_hotbar: packet_player_hotbar
|
||||
if inventory_content: packet_inventory_content
|
||||
if inventory_slot: packet_inventory_slot
|
||||
if container_set_data: packet_container_set_data
|
||||
if crafting_data: packet_crafting_data
|
||||
if crafting_event: packet_crafting_event
|
||||
if gui_data_pick_item: packet_gui_data_pick_item
|
||||
if adventure_settings: packet_adventure_settings
|
||||
if block_entity_data: packet_block_entity_data
|
||||
if player_input: packet_player_input
|
||||
if level_chunk: packet_level_chunk
|
||||
if set_commands_enabled: packet_set_commands_enabled
|
||||
if set_difficulty: packet_set_difficulty
|
||||
if change_dimension: packet_change_dimension
|
||||
if set_player_game_type: packet_set_player_game_type
|
||||
if player_list: packet_player_list
|
||||
if simple_event: packet_simple_event
|
||||
if event: packet_event
|
||||
if spawn_experience_orb: packet_spawn_experience_orb
|
||||
if clientbound_map_item_data: packet_clientbound_map_item_data
|
||||
if map_info_request: packet_map_info_request
|
||||
if request_chunk_radius: packet_request_chunk_radius
|
||||
if chunk_radius_update: packet_chunk_radius_update
|
||||
if item_frame_drop_item: packet_item_frame_drop_item
|
||||
if game_rules_changed: packet_game_rules_changed
|
||||
if camera: packet_camera
|
||||
if boss_event: packet_boss_event
|
||||
if show_credits: packet_show_credits
|
||||
if available_commands: packet_available_commands
|
||||
if command_request: packet_command_request
|
||||
if command_block_update: packet_command_block_update
|
||||
if command_output: packet_command_output
|
||||
if update_trade: packet_update_trade
|
||||
if update_equipment: packet_update_equipment
|
||||
if resource_pack_data_info: packet_resource_pack_data_info
|
||||
if resource_pack_chunk_data: packet_resource_pack_chunk_data
|
||||
if resource_pack_chunk_request: packet_resource_pack_chunk_request
|
||||
if transfer: packet_transfer
|
||||
if play_sound: packet_play_sound
|
||||
if stop_sound: packet_stop_sound
|
||||
if set_title: packet_set_title
|
||||
if add_behavior_tree: packet_add_behavior_tree
|
||||
if structure_block_update: packet_structure_block_update
|
||||
if show_store_offer: packet_show_store_offer
|
||||
if purchase_receipt: packet_purchase_receipt
|
||||
if player_skin: packet_player_skin
|
||||
if sub_client_login: packet_sub_client_login
|
||||
if initiate_web_socket_connection: packet_initiate_web_socket_connection
|
||||
if set_last_hurt_by: packet_set_last_hurt_by
|
||||
if book_edit: packet_book_edit
|
||||
if npc_request: packet_npc_request
|
||||
if photo_transfer: packet_photo_transfer
|
||||
if modal_form_request: packet_modal_form_request
|
||||
if modal_form_response: packet_modal_form_response
|
||||
if server_settings_request: packet_server_settings_request
|
||||
if server_settings_response: packet_server_settings_response
|
||||
if show_profile: packet_show_profile
|
||||
if set_default_game_type: packet_set_default_game_type
|
||||
if remove_objective: packet_remove_objective
|
||||
if set_display_objective: packet_set_display_objective
|
||||
if set_score: packet_set_score
|
||||
if lab_table: packet_lab_table
|
||||
if update_block_synced: packet_update_block_synced
|
||||
if move_entity_delta: packet_move_entity_delta
|
||||
if set_scoreboard_identity: packet_set_scoreboard_identity
|
||||
if set_local_player_as_initialized: packet_set_local_player_as_initialized
|
||||
if update_soft_enum: packet_update_soft_enum
|
||||
if network_stack_latency: packet_network_stack_latency
|
||||
if script_custom_event: packet_script_custom_event
|
||||
if spawn_particle_effect: packet_spawn_particle_effect
|
||||
if available_entity_identifiers: packet_available_entity_identifiers
|
||||
if level_sound_event_v2: packet_level_sound_event_v2
|
||||
if network_chunk_publisher_update: packet_network_chunk_publisher_update
|
||||
if biome_definition_list: packet_biome_definition_list
|
||||
if level_sound_event: packet_level_sound_event
|
||||
if level_event_generic: packet_level_event_generic
|
||||
if lectern_update: packet_lectern_update
|
||||
if video_stream_connect: packet_video_stream_connect
|
||||
if add_ecs_entity: packet_add_ecs_entity
|
||||
if remove_ecs_entity: packet_remove_ecs_entity
|
||||
if client_cache_status: packet_client_cache_status
|
||||
if on_screen_texture_animation: packet_on_screen_texture_animation
|
||||
if map_create_locked_copy: packet_map_create_locked_copy
|
||||
if structure_template_data_export_request: packet_structure_template_data_export_request
|
||||
if structure_template_data_export_response: packet_structure_template_data_export_response
|
||||
if update_block_properties: packet_update_block_properties
|
||||
if client_cache_blob_status: packet_client_cache_blob_status
|
||||
if client_cache_miss_response: packet_client_cache_miss_response
|
||||
if education_settings: packet_education_settings
|
||||
if multiplayer_settings: packet_multiplayer_settings
|
||||
if settings_command: packet_settings_command
|
||||
if anvil_damage: packet_anvil_damage
|
||||
if completed_using_item: packet_completed_using_item
|
||||
if network_settings: packet_network_settings
|
||||
if player_auth_input: packet_player_auth_input
|
||||
if creative_content: packet_creative_content
|
||||
if player_enchant_options: packet_player_enchant_options
|
||||
if item_stack_request: packet_item_stack_request
|
||||
if item_stack_response: packet_item_stack_response
|
||||
if player_armor_damage: packet_player_armor_damage
|
||||
if update_player_game_type: packet_update_player_game_type
|
||||
if position_tracking_db_request: packet_position_tracking_db_request
|
||||
if position_tracking_db_broadcast: packet_position_tracking_db_broadcast
|
||||
if packet_violation_warning: packet_packet_violation_warning
|
||||
if motion_prediction_hints: packet_motion_prediction_hints
|
||||
if animate_entity: packet_animate_entity
|
||||
if camera_shake: packet_camera_shake
|
||||
if player_fog: packet_player_fog
|
||||
if correct_player_move_prediction: packet_correct_player_move_prediction
|
||||
if item_component: packet_item_component
|
||||
if filter_text_packet: packet_filter_text_packet
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
# Created from MiNET docs
|
||||
# Created from MiNET and gophertunnel docs
|
||||
# The version below is the latest version this protocol schema was updated for.
|
||||
# The output protocol.json will be in the folder for the version
|
||||
!version: 1.16.201
|
||||
|
||||
# Some ProtoDef aliases
|
||||
string: ["pstring",{"countType":"varint"}]
|
||||
ByteArray: ["buffer",{"countType":"varint"}]
|
||||
SignedByteArray: ["buffer",{"countType":"zigzag32"}]
|
||||
|
|
@ -12,8 +17,10 @@ byterot: native
|
|||
MapInfo: native
|
||||
nbt: native
|
||||
|
||||
# load the packet map file
|
||||
!import: packet_map.yml
|
||||
|
||||
#todo: docs
|
||||
!StartDocs: Packets
|
||||
|
||||
# # Login Sequence
|
||||
|
|
|
|||
2517
data/protocol.json
2517
data/protocol.json
File diff suppressed because it is too large
Load diff
45
data/provider.js
Normal file
45
data/provider.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
const { Versions } = require('../src/options')
|
||||
const { getFiles } = require('../src/datatypes/util')
|
||||
|
||||
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() {
|
||||
for (const version in Versions) {
|
||||
let files = []
|
||||
try {
|
||||
files = getFiles(__dirname + '/' + version)
|
||||
} catch {}
|
||||
for (let file of files) {
|
||||
const rfile = file.replace(__dirname + '/' + version + '/', '')
|
||||
fileMap[rfile] ??= []
|
||||
fileMap[rfile].push([Versions[version], file])
|
||||
fileMap[rfile].sort().reverse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!fileMap[file]) {
|
||||
throw Error('Unknown file ' + file)
|
||||
}
|
||||
for (const [ pver, path ] of fileMap[file]) {
|
||||
if (pver <= protocolVersion) {
|
||||
// console.debug('for', file, 'returining', path)
|
||||
return path
|
||||
}
|
||||
}
|
||||
throw Error('unknown file ' + file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadVersions()
|
||||
// console.log('file map', fileMap)
|
||||
// module.exports(Versions['1.16.210']).open('creativeitems.json')
|
||||
38
examples/createRelay.js
Normal file
38
examples/createRelay.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
const { Relay } = require('../src/relay')
|
||||
|
||||
function createRelay() {
|
||||
console.log('Creating relay')
|
||||
/**
|
||||
* Example to create a non-transparent proxy (or 'Relay') connection to destination server
|
||||
* In Relay we de-code and re-encode packets
|
||||
*/
|
||||
const relay = new Relay({
|
||||
/* Hostname and port for clients to listen to */
|
||||
hostname: '0.0.0.0',
|
||||
port: 19130,
|
||||
/**
|
||||
* Who does the authentication
|
||||
* If set to `client`, all connecting clients will be sent a message with a link to authenticate
|
||||
* If set to `server`, the server will authenticate and only one client will be able to join
|
||||
* (Default) If set to `none`, no authentication will be done
|
||||
*/
|
||||
auth: 'server',
|
||||
|
||||
/**
|
||||
* Sets if packets will automatically be forwarded. If set to false, you must listen for on('packet')
|
||||
* events and
|
||||
*/
|
||||
auto: true,
|
||||
|
||||
/* Where to send upstream packets to */
|
||||
destination: {
|
||||
hostname: '127.0.0.1',
|
||||
port: 19132,
|
||||
// encryption: true
|
||||
}
|
||||
})
|
||||
|
||||
relay.create()
|
||||
}
|
||||
|
||||
createRelay()
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
process.env.DEBUG = 'minecraft-protocol raknet'
|
||||
const { Server } = require('../src/server')
|
||||
const CreativeItems = require('../data/creativeitems.json')
|
||||
// const CreativeItems = require('../data/creativeitems.json')
|
||||
const NBT = require('prismarine-nbt')
|
||||
const fs = require('fs')
|
||||
const DataProvider = require('../data/provider')
|
||||
|
||||
|
||||
let server = new Server({
|
||||
|
|
@ -10,6 +11,14 @@ let server = new Server({
|
|||
})
|
||||
server.create('0.0.0.0', 19132)
|
||||
|
||||
function getPath(packetPath) {
|
||||
return DataProvider(server.options.protocolVersion).getPath(packetPath)
|
||||
}
|
||||
|
||||
function get(packetPath) {
|
||||
return require(getPath('sample/' + packetPath))
|
||||
}
|
||||
|
||||
let ran = false
|
||||
|
||||
server.on('connect', ({ client }) => {
|
||||
|
|
@ -50,35 +59,35 @@ server.on('connect', ({ client }) => {
|
|||
client.queue('inventory_slot', {"inventory_id":120,"slot":i,"uniqueid":0,"item":{"network_id":0}})
|
||||
}
|
||||
|
||||
client.queue('inventory_transaction', require('../src/packets/inventory_transaction.json'))
|
||||
client.queue('player_list', require('../src/packets/player_list.json'))
|
||||
client.queue('start_game', require('../src/packets/start_game.json'))
|
||||
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('set_spawn_position', require('../src/packets/set_spawn_position.json'))
|
||||
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', require('../src/packets/adventure_settings.json'))
|
||||
client.queue('adventure_settings', get('packets/adventure_settings.json'))
|
||||
|
||||
client.queue('biome_definition_list', require('../src/packets/biome_definition_list.json'))
|
||||
client.queue('available_entity_identifiers', require('../src/packets/available_entity_identifiers.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', require('../src/packets/update_attributes.json'))
|
||||
client.queue('creative_content', require('../src/packets/creative_content.json'))
|
||||
client.queue('inventory_content', require('../src/packets/inventory_content.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('crafting_data', require('../src/packets/crafting_data.json'))
|
||||
client.queue('available_commands', require('../src/packets/available_commands.json'))
|
||||
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('set_entity_data', require('../src/packets/set_entity_data.json'))
|
||||
client.queue('set_entity_data', get('packets/set_entity_data.json'))
|
||||
|
||||
client.queue('game_rules_changed', require('../src/packets/game_rules_changed.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})
|
||||
|
||||
for (const file of fs.readdirSync('../src/chunks')) {
|
||||
const buffer = Buffer.from(fs.readFileSync('../src/chunks/' + file, 'utf8'), 'hex')
|
||||
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')
|
||||
// console.log('Sending chunk', chunk)
|
||||
client.sendBuffer(buffer)
|
||||
}
|
||||
|
|
@ -52,6 +52,8 @@ function verifyAuth(chain) {
|
|||
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 }
|
||||
|
|
@ -87,22 +89,22 @@ function encodeLoginJWT(localChain, mojangChain) {
|
|||
|
||||
module.exports = { encodeLoginJWT, decodeLoginJWT }
|
||||
|
||||
function testServer() {
|
||||
const loginPacket = require('./login.json')
|
||||
// function testServer() {
|
||||
// const loginPacket = require('./login.json')
|
||||
|
||||
// console.log(loginPacket)
|
||||
const authChains = JSON.parse(loginPacket.data.chain)
|
||||
const skinChain = loginPacket.data.clientData
|
||||
// // console.log(loginPacket)
|
||||
// const authChains = JSON.parse(loginPacket.data.chain)
|
||||
// const skinChain = loginPacket.data.clientData
|
||||
|
||||
try {
|
||||
var { data, chain } = decodeLoginJWT(authChains.chain, skinChain)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw new Error('Failed to verify user')
|
||||
}
|
||||
// try {
|
||||
// var { data, chain } = decodeLoginJWT(authChains.chain, skinChain)
|
||||
// } catch (e) {
|
||||
// console.error(e)
|
||||
// throw new Error('Failed to verify user')
|
||||
// }
|
||||
|
||||
console.log('Authed')
|
||||
// console.log(loginPacket)
|
||||
}
|
||||
// console.log('Authed')
|
||||
// // console.log(loginPacket)
|
||||
// }
|
||||
|
||||
// testServer()
|
||||
// // testServer()
|
||||
|
|
@ -2,11 +2,15 @@ const JWT = require('jsonwebtoken')
|
|||
const crypto = require('crypto')
|
||||
const { Ber } = require('asn1')
|
||||
const ec_pem = require('ec-pem')
|
||||
const fs = require('fs')
|
||||
const DataProvider = require('../../data/provider')
|
||||
|
||||
const SALT = '🧂'
|
||||
const curve = 'secp384r1'
|
||||
|
||||
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())
|
||||
|
|
@ -84,7 +88,6 @@ function Encrypt(client, server, options) {
|
|||
}
|
||||
|
||||
client.on('server.client_handshake', startClientboundEncryption)
|
||||
|
||||
client.on('client.server_handshake', startServerboundEncryption)
|
||||
|
||||
client.createClientChain = (mojangKey) => {
|
||||
|
|
@ -118,7 +121,7 @@ function Encrypt(client, server, options) {
|
|||
SkinId: '5eb65f73-af11-448e-82aa-1b7b165316ad.persona-e199672a8c1a87e0-0',
|
||||
SkinData: 'AAAAAA==',
|
||||
SkinResourcePatch: 'ewogICAiZ2VvbWV0cnkiIDogewogICAgICAiYW5pbWF0ZWRfMTI4eDEyOCIgOiAiZ2VvbWV0cnkuYW5pbWF0ZWRfMTI4eDEyOF9wZXJzb25hLWUxOTk2NzJhOGMxYTg3ZTAtMCIsCiAgICAgICJhbmltYXRlZF9mYWNlIiA6ICJnZW9tZXRyeS5hbmltYXRlZF9mYWNlX3BlcnNvbmEtZTE5OTY3MmE4YzFhODdlMC0wIiwKICAgICAgImRlZmF1bHQiIDogImdlb21ldHJ5LnBlcnNvbmFfZTE5OTY3MmE4YzFhODdlMC0wIgogICB9Cn0K',
|
||||
SkinGeometryData: require('./geom'),
|
||||
SkinGeometryData: skinGeom,
|
||||
"SkinImageHeight": 1,
|
||||
"SkinImageWidth": 1,
|
||||
"ArmSize": "wide",
|
||||
|
|
|
|||
|
|
@ -1,232 +0,0 @@
|
|||
const crypto = require('crypto')
|
||||
const JWT = require('jsonwebtoken')
|
||||
const constants = require('./constants')
|
||||
const { Ber } = require('asn1')
|
||||
const ec_pem = require('ec-pem');
|
||||
|
||||
// function Encrypt(client, options) {
|
||||
// this.startClientboundEncryption = (pubKeyBuf) => {
|
||||
|
||||
// }
|
||||
// client.on('start_encrypt', this.startClientboundEncryption)
|
||||
// }
|
||||
|
||||
// module.exports = Encrypt
|
||||
|
||||
|
||||
// Server -> Client : sent right after the client sends us a LOGIN_PACKET so
|
||||
// we can start the encryption process
|
||||
// @param {key} - The key from the client Login Packet final JWT chain
|
||||
function startClientboundEncryption(pubKeyBuf) {
|
||||
// create our ecdh keypair
|
||||
const type = 'secp256k1'
|
||||
const alice = crypto.createECDH(type)
|
||||
const aliceKey = alice.generateKeys()
|
||||
const alicePublicKey = aliceKey.toString('base64')
|
||||
const alicePrivateKey = mcPubKeyToPem(alice.getPrivateKey('base64'))
|
||||
// get our secret key hex encoded
|
||||
// const aliceSecret = alice.computeSecret(pubKeyBuf, null, 'hex')
|
||||
|
||||
// (yawkat:)
|
||||
// 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 sha256(server_token + shared_secret). These secret
|
||||
// key bytes are 32 bytes long.
|
||||
const salt = Buffer.from('', 'utf-8')
|
||||
let secret = crypto.createHash('sha256').update(Buffer.concat([salt, pubKeyBuf])).digest()
|
||||
console.log('alice', alicePrivateKey)
|
||||
const pem = mcPubKeyToPem(alice.getPrivateKey().toString('base64'))
|
||||
console.log('pem', pem)
|
||||
|
||||
const token = JWT.sign({
|
||||
salt,
|
||||
signedToken: alicePublicKey
|
||||
}, pem, { algorithm: 'ES384' })
|
||||
|
||||
console.log('Token', token)
|
||||
|
||||
// get our Secret Bytes from the secret key
|
||||
|
||||
|
||||
// alice.setPrivateKey(
|
||||
// crypto.createHash('sha256').update('alice', 'utf8').digest()
|
||||
// )
|
||||
|
||||
// using (var sha = SHA256.Create())
|
||||
// {
|
||||
// secret = sha.ComputeHash(secretPrepend.Concat(agreement.CalculateAgreement(remotePublicKey).ToByteArrayUnsigned()).ToArray());
|
||||
// }
|
||||
|
||||
|
||||
|
||||
const bob = crypto.createECDH('secp256k1');
|
||||
|
||||
|
||||
// URI x5u = URI.create(Base64.getEncoder().encodeToString(serverKeyPair.getPublic().getEncoded()));
|
||||
|
||||
// JWTClaimsSet claimsSet = new JWTClaimsSet.Builder().claim("salt", Base64.getEncoder().encodeToString(token)).build();
|
||||
// SignedJWT jwt = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.ES384).x509CertURL(x5u).build(), claimsSet);
|
||||
|
||||
// signJwt(jwt, (ECPrivateKey) serverKeyPair.getPrivate());
|
||||
|
||||
// return jwt;
|
||||
}
|
||||
|
||||
function testECDH() {
|
||||
const crypto = require('crypto')
|
||||
const alice = crypto.createECDH('secp256k1')
|
||||
const bob = crypto.createECDH('secp256k1')
|
||||
|
||||
// Note: This is a shortcut way to specify one of Alice's previous private
|
||||
// keys. It would be unwise to use such a predictable private key in a real
|
||||
// application.
|
||||
alice.setPrivateKey(
|
||||
crypto.createHash('sha256').update('alice', 'utf8').digest()
|
||||
);
|
||||
|
||||
// Bob uses a newly generated cryptographically strong
|
||||
// pseudorandom key pair bob.generateKeys();
|
||||
|
||||
const alice_secret = alice.computeSecret(bob.getPublicKey(), null, 'hex')
|
||||
const bob_secret = bob.computeSecret(alice.getPublicKey(), null, 'hex')
|
||||
|
||||
// alice_secret and bob_secret should be the same shared secret value
|
||||
console.log(alice_secret === bob_secret)
|
||||
}
|
||||
|
||||
function testECDH2() {
|
||||
const type = 'secp256k1'
|
||||
const alice = crypto.createECDH(type);
|
||||
const aliceKey = alice.generateKeys();
|
||||
|
||||
// Generate Bob's keys...
|
||||
const bob = crypto.createECDH(type);
|
||||
const bobKey = bob.generateKeys();
|
||||
|
||||
console.log("\nAlice private key:\t", alice.getPrivateKey().toString('hex'));
|
||||
console.log("Alice public key:\t", aliceKey.toString('hex'))
|
||||
|
||||
console.log("\nBob private key:\t", bob.getPrivateKey().toString('hex'));
|
||||
console.log("Bob public key:\t", bobKey.toString('hex'));
|
||||
|
||||
|
||||
// Exchange and generate the secret...
|
||||
const aliceSecret = alice.computeSecret(bobKey);
|
||||
const bobSecret = bob.computeSecret(aliceKey);
|
||||
|
||||
console.log("\nAlice shared key:\t", aliceSecret.toString('hex'))
|
||||
console.log("Bob shared key:\t\t", bobSecret.toString('hex'));
|
||||
//wow it actually works?!
|
||||
}
|
||||
|
||||
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 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 testMC() {
|
||||
// const pubKeyBuf = Buffer.from(constants.PUBLIC_KEY, 'base64')
|
||||
// const pem = mcPubKeyToPem(pubKeyBuf)
|
||||
// console.log(mcPubKeyToPem(pubKeyBuf))
|
||||
// const publicKey = crypto.createPublicKey({ key: pem, format: 'der' })
|
||||
|
||||
const pubKeyBuf = readX509PublicKey(constants.PUBLIC_KEY)
|
||||
|
||||
// console.log('Mojang pub key', pubKeyBuf.toString('hex'), publicKey)
|
||||
startClientboundEncryption(pubKeyBuf)
|
||||
}
|
||||
|
||||
function testMC2() {
|
||||
// const mojangPubKeyBuf = Buffer.from('MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V', 'base64')
|
||||
// const pem = mcPubKeyToPem(mojangPubKeyBuf)
|
||||
// const publicKey = crypto.createPublicKey({ key: pem })
|
||||
|
||||
const publicKey = readX509PublicKey(constants.PUBLIC_KEY)
|
||||
|
||||
const curve = 'secp384r1'
|
||||
const alice = crypto.createECDH(curve)
|
||||
// const keys = crypto.generateKeyPair('ec',)
|
||||
|
||||
// const bob = crypto.generateKeyPairSync('ec', {
|
||||
// namedCurve: type
|
||||
// })
|
||||
// alice.setPrivateKey(bob.privateKey.export({ type: 'pkcs8', format: 'pem' }))
|
||||
// alice.setPublicKey(bob.publicKey.export({ type: 'spki', format: 'pem' }))
|
||||
|
||||
// console.log(bob)
|
||||
|
||||
const aliceKey = alice.generateKeys()
|
||||
|
||||
const alicePEM = ec_pem(alice, curve)
|
||||
|
||||
const alicePEMPrivate = alicePEM.encodePrivateKey()
|
||||
const alicePEMPublic = alicePEM.encodePublicKey()
|
||||
|
||||
// const alicePublicKey = aliceKey.toString('base64')
|
||||
// const alicePrivateKey = alice.getPrivateKey().toString('base64')
|
||||
const aliceSecret = alice.computeSecret(publicKey, null, 'hex')
|
||||
|
||||
console.log('Alice private key PEM', alicePEMPrivate)
|
||||
console.log('Alice public key PEM', alicePEMPublic)
|
||||
console.log('Alice public key', alice.getPublicKey('base64'))
|
||||
console.log('Alice secret key', aliceSecret)
|
||||
|
||||
|
||||
var sign = crypto.createSign('RSA-SHA256')
|
||||
sign.write('something')
|
||||
sign.end()
|
||||
// // const pem2 =
|
||||
// // `-----BEGIN PRIVATE KEY-----
|
||||
// // ${alice.getPrivateKey('base64')}
|
||||
// // -----END PRIVATE KEY-----`
|
||||
|
||||
// console.log('PEM', bob.privateKey)
|
||||
const sig = sign.sign(alicePEMPrivate, 'hex')
|
||||
console.log('Signature', sig)
|
||||
|
||||
|
||||
|
||||
const token = JWT.sign({
|
||||
salt: 'HELLO',
|
||||
signedToken: alice.getPublicKey('base64')
|
||||
}, alicePEMPrivate, { algorithm: 'ES384' })
|
||||
console.log('Token', token)
|
||||
|
||||
const verified = JWT.verify(token, alicePEMPublic, { algorithms: 'ES384' })
|
||||
console.log('Verified!', verified)
|
||||
}
|
||||
|
||||
function testMC3() {
|
||||
var Ber = require('asn1').Ber;
|
||||
var reader = new Ber.Reader(new Buffer(constants.PUBLIC_KEY, "base64"));
|
||||
reader.readSequence();
|
||||
reader.readSequence();
|
||||
reader.readOID(); // Hey, I'm an elliptic curve
|
||||
reader.readOID(); // This contains the curve type, could be useful
|
||||
var pubKey = reader.readString(Ber.BitString, true).slice(1);
|
||||
var server = crypto.createECDH('secp384r1');
|
||||
server.generateKeys();
|
||||
console.log(server.computeSecret(pubKey));
|
||||
|
||||
}
|
||||
|
||||
// testECDH2()
|
||||
testMC2()
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
const crypto = require('crypto')
|
||||
const JWT = require('jsonwebtoken')
|
||||
const constants = require('./constants')
|
||||
const { Ber } = require('asn1')
|
||||
const ec_pem = require('ec-pem')
|
||||
|
||||
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 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 test(pubKey = constants.PUBLIC_KEY) {
|
||||
const publicKey = readX509PublicKey(pubKey)
|
||||
const curve = 'secp384r1'
|
||||
const alice = crypto.createECDH(curve)
|
||||
const aliceKey = alice.generateKeys()
|
||||
const alicePEM = ec_pem(alice, curve)
|
||||
const alicePEMPrivate = alicePEM.encodePrivateKey()
|
||||
const alicePEMPublic = alicePEM.encodePublicKey()
|
||||
const aliceSecret = alice.computeSecret(publicKey, null, 'hex')
|
||||
console.log('Alice private key PEM', alicePEMPrivate)
|
||||
console.log('Alice public key PEM', alicePEMPublic)
|
||||
console.log('Alice public key', alice.getPublicKey('hex'))
|
||||
console.log('Alice secret key', aliceSecret)
|
||||
|
||||
// Test signing manually
|
||||
const sign = crypto.createSign('RSA-SHA256')
|
||||
sign.write('🧂')
|
||||
sign.end()
|
||||
const sig = sign.sign(alicePEMPrivate, 'hex')
|
||||
console.log('Signature', sig)
|
||||
|
||||
// Test JWT sign+verify
|
||||
const x509 = writeX509PublicKey(alice.getPublicKey())
|
||||
const token = JWT.sign({
|
||||
salt: 'HELLO',
|
||||
signedToken: alice.getPublicKey('base64')
|
||||
}, alicePEMPrivate, { algorithm: 'ES384', header: { x5u: x509 } })
|
||||
console.log('Encoded JWT', token)
|
||||
// send the jwt to the client...
|
||||
|
||||
const verified = JWT.verify(token, alicePEMPublic, { algorithms: 'ES384' })
|
||||
console.log('Decoded JWT', verified)
|
||||
// Good
|
||||
}
|
||||
|
||||
/**
|
||||
* Alice private key PEM -----BEGIN EC PRIVATE KEY-----
|
||||
MIGkAgEBBDBGgHZwH3BzieyJrdrVTVLmrEoUxpDUSqSzS98lobTXeUxJR/OmywPV
|
||||
57I8YtnsJlCgBwYFK4EEACKhZANiAATjvTRgjsxKruO7XbduSQoHeR/6ouIm4Rmc
|
||||
La9EkSpLFpuYZfsdtq9Vcf2t3Q3+jIbXjD/wNo97P4Hr5ghXG8sCVV7jpqadOF8j
|
||||
SzyfajLGfX9mkS5WWLAg+dpi/KiEo/g=
|
||||
-----END EC PRIVATE KEY-----
|
||||
|
||||
Alice public key PEM -----BEGIN PUBLIC KEY-----
|
||||
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE4700YI7MSq7ju123bkkKB3kf+qLiJuEZ
|
||||
nC2vRJEqSxabmGX7HbavVXH9rd0N/oyG14w/8DaPez+B6+YIVxvLAlVe46amnThf
|
||||
I0s8n2oyxn1/ZpEuVliwIPnaYvyohKP4
|
||||
-----END PUBLIC KEY-----
|
||||
|
||||
Alice public key 04e3bd34608ecc4aaee3bb5db76e490a07791ffaa2e226e1199c2daf44912a4b169b9865fb1db6af5571fdaddd0dfe8c86d78c3ff0368f7b3f81ebe608571bcb02555ee3a6a69d385f234b3c9f6a32c67d7f66912e5658b020f9da62fca884a3f8
|
||||
Alice secret key 76feb5d420b33907c4841a74baa707b717a29c021b17b6662fd46dba3227cac3e256eee9e890edb0308f66a3119b4914
|
||||
Signature 3066023100d5ea70b8fc5e441c5e93d9f7dcde031f54291011c950a4aa8625ea9b27f7c798a8bc4de40baf35d487a05db6b5c628c6023100ae06cc2ea65db77138163c546ccf13933faae3d91bd6aa7108b99539cdb1c86f1e8a3704cb099f0b00eebed4ee75ccb2
|
||||
Encoded JWT eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJzYWx0IjoiSEVMTE8iLCJzaWduZWRUb2tlbiI6IkJPTzlOR0NPekVxdTQ3dGR0MjVKQ2dkNUgvcWk0aWJoR1p3dHIwU1JLa3NXbTVobCt4MjJyMVZ4L2EzZERmNk1odGVNUC9BMmozcy9nZXZtQ0ZjYnl3SlZYdU9tcHAwNFh5TkxQSjlxTXNaOWYyYVJMbFpZc0NENTJtTDhxSVNqK0E9PSIsImlhdCI6MTYxMTc4MDYwNX0._g8k086U7nD-Tthn8jGWuuM3Q4EfhgqCfFA1Q5ePmjqhqMHOJvmrCz6tWsCytr2i-a2M51fb9K_YDAHbZ66Kos9ZkjF4Tqz5fPS880fM9woZ_1xjh7nGcOQ6sbY81zyi
|
||||
Decoded JWT {
|
||||
salt: 'HELLO',
|
||||
signedToken: 'BOO9NGCOzEqu47tdt25JCgd5H/qi4ibhGZwtr0SRKksWm5hl+x22r1Vx/a3dDf6MhteMP/A2j3s/gevmCFcbywJVXuOmpp04XyNLPJ9qMsZ9f2aRLlZYsCD52mL8qISj+A==',
|
||||
iat: 1611780605
|
||||
}
|
||||
*/
|
||||
|
||||
test()
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
function test() {
|
||||
const chain = require('./sampleChain.json').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(constants.PUBLIC_KEY_NEW)
|
||||
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 [header] = token.split('.')
|
||||
const hdec = Buffer.from(header, 'base64').toString('utf-8')
|
||||
const hjson = JSON.parse(hdec)
|
||||
if (hjson.x5u == constants.PUBLIC_KEY && !data.extraData?.XUID) {
|
||||
didVerify = true
|
||||
console.log('verified with mojang key!', hjson.x5u)
|
||||
}
|
||||
|
||||
pubKey = mcPubKeyToPem(decoded.identityPublicKey)
|
||||
data = { ...data, ...decoded }
|
||||
}
|
||||
console.log('Result', data)
|
||||
}
|
||||
|
||||
function test2() {
|
||||
const chain = require('./login.json')
|
||||
const token = chain.data.clientData
|
||||
// console.log(token)
|
||||
|
||||
const pubKey = mcPubKeyToPem(constants.CDATA_PUBLIC_KEY)
|
||||
|
||||
const decoded = JWT.verify(token, pubKey, { algorithms: 'ES384' })
|
||||
|
||||
// console.log('Decoded', decoded)
|
||||
|
||||
fs.writeFileSync('clientData.json', JSON.stringify(decoded))
|
||||
}
|
||||
|
|
@ -1,22 +1,25 @@
|
|||
const fs = require('fs')
|
||||
const debug = require('debug')('minecraft-protocol')
|
||||
const auth = require('./client/auth')
|
||||
const Options = require('./options')
|
||||
const { Connection } = require('./connection')
|
||||
const { createDeserializer, createSerializer } = require('./transforms/serializer')
|
||||
const { Encrypt } = require('./auth/encryption')
|
||||
const auth = require('./client/auth')
|
||||
const Options = require('./options')
|
||||
const { RakClient } = require('./Rak')
|
||||
const { serialize } = require('./datatypes/util')
|
||||
|
||||
const debugging = false
|
||||
|
||||
class Client extends Connection {
|
||||
/** @param {{ version: number, hostname: string, port: number }} options */
|
||||
constructor(options) {
|
||||
super()
|
||||
this.options = { ...Options.defaultOptions, ...options }
|
||||
this.serializer = createSerializer()
|
||||
this.deserializer = createDeserializer()
|
||||
this.validateOptions()
|
||||
this.serializer = createSerializer(this.options.version)
|
||||
this.deserializer = createDeserializer(this.options.version)
|
||||
|
||||
Encrypt(this, null, options)
|
||||
Encrypt(this, null, this.options)
|
||||
|
||||
if (options.password) {
|
||||
auth.authenticatePassword(this, options)
|
||||
|
|
@ -28,26 +31,29 @@ class Client extends Connection {
|
|||
this.startQueue()
|
||||
this.inLog = (...args) => console.info('C ->', ...args)
|
||||
this.outLog = (...args) => console.info('C <-', ...args)
|
||||
// this.on('decrypted', this.onDecryptedPacket)
|
||||
}
|
||||
|
||||
validateOptions() {
|
||||
// console.log('Options', this.options)
|
||||
if (!this.options.hostname || this.options.port == null) throw Error('Invalid hostname/port')
|
||||
if (this.options.version < Options.MIN_VERSION) {
|
||||
throw new Error(`Unsupported protocol version < ${Options.MIN_VERSION} : ${this.options.version}`)
|
||||
|
||||
if (!Options.Versions[this.options.version]) {
|
||||
console.warn('Supported versions: ', Options.Versions)
|
||||
throw Error(`Unsupported version ${this.options.version}`)
|
||||
}
|
||||
this.options.protocolVersion = Options.Versions[this.options.version]
|
||||
if (this.options.protocolVersion < Options.MIN_VERSION) {
|
||||
throw new Error(`Protocol version < ${Options.MIN_VERSION} : ${this.options.protocolVersion}, too old`)
|
||||
}
|
||||
}
|
||||
|
||||
onEncapsulated = (encapsulated, inetAddr) => {
|
||||
// log(inetAddr.address, ': Encapsulated', encapsulated)
|
||||
const buffer = Buffer.from(encapsulated.buffer)
|
||||
this.handle(buffer)
|
||||
}
|
||||
|
||||
connect = async (sessionData) => {
|
||||
const hostname = this.options.hostname || '127.0.0.1'
|
||||
const port = this.options.port || 19132
|
||||
const hostname = this.options.hostname
|
||||
const port = this.options.port
|
||||
|
||||
this.connection = new RakClient({ useWorkers: true, hostname, port })
|
||||
this.connection.onConnected = () => this.sendLogin()
|
||||
|
|
@ -64,14 +70,13 @@ class Client extends Connection {
|
|||
]
|
||||
|
||||
const encodedChain = JSON.stringify({ chain })
|
||||
// const skinChain = JSON.stringify({})
|
||||
|
||||
const bodyLength = this.clientUserChain.length + encodedChain.length + 8
|
||||
|
||||
debug('Auth chain', chain)
|
||||
|
||||
this.write('login', {
|
||||
protocol_version: this.options.version,
|
||||
protocol_version: this.options.protocolVersion,
|
||||
payload_size: bodyLength,
|
||||
chain: encodedChain,
|
||||
client_data: this.clientUserChain
|
||||
|
|
@ -83,7 +88,7 @@ class Client extends Connection {
|
|||
// 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)
|
||||
process.exit(1) // TODO: handle
|
||||
}
|
||||
|
||||
close() {
|
||||
|
|
@ -109,32 +114,28 @@ class Client extends Connection {
|
|||
}
|
||||
|
||||
readPacket(packet) {
|
||||
// console.log('packet', 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)*/)
|
||||
|
||||
// No idea what this exotic 0xA0 packet is, it's not implemented anywhere
|
||||
// and seems empty. Possible gibberish from the raknet impl
|
||||
if (pakData.name == '160' || !pakData.name) { // eslint-ignore-line
|
||||
console.warn('?? Ignoring extraneous packet ', des)
|
||||
return
|
||||
}
|
||||
|
||||
// Packet verifying (decode + re-encode + match test)
|
||||
if (pakData.name) {
|
||||
this.tryRencode(pakData.name, pakData.params, packet)
|
||||
}
|
||||
|
||||
// console.info('->', JSON.stringify(pakData, (k,v) => typeof v == 'bigint' ? v.toString() : v))
|
||||
// Packet dumping
|
||||
try {
|
||||
if (!fs.existsSync(`./packets/${pakData.name}.json`)) {
|
||||
fs.writeFileSync(`./packets/${pakData.name}.json`, serialize(pakData.params, 2))
|
||||
fs.writeFileSync(`./packets/${pakData.name}.txt`, packet.toString('hex'))
|
||||
if (debugging) {
|
||||
// Packet verifying (decode + re-encode + match test)
|
||||
if (pakData.name) {
|
||||
this.tryRencode(pakData.name, pakData.params, packet)
|
||||
}
|
||||
} catch { }
|
||||
|
||||
// console.info('->', JSON.stringify(pakData, (k,v) => typeof v == 'bigint' ? v.toString() : v))
|
||||
// Packet dumping
|
||||
try {
|
||||
const root = __dirname + `../data/${this.options.version}/sample/`
|
||||
if (!fs.existsSync(root + `packets/${pakData.name}.json`)) {
|
||||
fs.writeFileSync(root + `packets/${pakData.name}.json`, serialize(pakData.params, 2))
|
||||
fs.writeFileSync(root + `packets/${pakData.name}.txt`, packet.toString('hex'))
|
||||
}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
// Abstract some boilerplate before sending to listeners
|
||||
switch (des.data.name) {
|
||||
case 'server_to_client_handshake':
|
||||
this.emit('client.server_handshake', des.data.params)
|
||||
|
|
@ -142,27 +143,22 @@ class Client extends Connection {
|
|||
case 'disconnect': // Client kicked
|
||||
this.onDisconnectRequest(des.data.params)
|
||||
break
|
||||
case 'crafting_data':
|
||||
fs.writeFileSync('crafting.json', JSON.stringify(des.data.params, (k, v) => typeof v == 'bigint' ? v.toString() : v))
|
||||
break
|
||||
case 'start_game':
|
||||
fs.writeFileSync('start_game.json', JSON.stringify(des.data.params, (k, v) => typeof v == 'bigint' ? v.toString() : v))
|
||||
break
|
||||
case 'level_chunk':
|
||||
// fs.writeFileSync(`./chunks/chunk-${chunks++}.txt`, packet.toString('hex'))
|
||||
break
|
||||
// case 'crafting_data':
|
||||
// fs.writeFileSync('crafting.json', JSON.stringify(des.data.params, (k, v) => typeof v == 'bigint' ? v.toString() : v))
|
||||
// break
|
||||
// case 'start_game':
|
||||
// fs.writeFileSync('start_game.json', JSON.stringify(des.data.params, (k, v) => typeof v == 'bigint' ? v.toString() : v))
|
||||
// break
|
||||
// case 'level_chunk':
|
||||
// // fs.writeFileSync(`./chunks/chunk-${chunks++}.txt`, packet.toString('hex'))
|
||||
// break
|
||||
default:
|
||||
// console.log('Sending to listeners')
|
||||
}
|
||||
this.emit(des.data.name, des.data.params)
|
||||
|
||||
// Emit packet
|
||||
this.emit(des.data.name, des.data.params)
|
||||
}
|
||||
}
|
||||
|
||||
var chunks = 0;
|
||||
|
||||
function serialize(obj = {}, fmt) {
|
||||
return JSON.stringify(obj, (k, v) => typeof v == 'bigint' ? v.toString() : v, fmt)
|
||||
}
|
||||
|
||||
module.exports = { Client }
|
||||
|
|
@ -1,16 +1,5 @@
|
|||
const XboxLiveAuth = require('@xboxreplay/xboxlive-auth')
|
||||
const debug = require('debug')('minecraft-protocol')
|
||||
const fetch = require('node-fetch')
|
||||
const authConstants = require('./authConstants')
|
||||
const { MsAuthFlow } = require('./authFlow.js')
|
||||
|
||||
const getFetchOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'node-minecraft-protocol'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains Minecaft profile data using a Minecraft access token and starts the join sequence
|
||||
* @param {object} client - The client passed to protocol
|
||||
|
|
@ -71,33 +60,25 @@ async function authenticateDeviceCode (client, options) {
|
|||
}
|
||||
}
|
||||
|
||||
function checkStatus (res) {
|
||||
if (res.ok) { // res.status >= 200 && res.status < 300
|
||||
return res.json()
|
||||
} else {
|
||||
throw Error(res.statusText)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
authenticatePassword,
|
||||
authenticateDeviceCode
|
||||
}
|
||||
|
||||
async function msaTest () {
|
||||
// MsAuthFlow.resetTokenCaches()
|
||||
// async function msaTest () {
|
||||
// // MsAuthFlow.resetTokenCaches()
|
||||
|
||||
await authenticateDeviceCode({
|
||||
connect(...args) {
|
||||
console.log('Connecting', args)
|
||||
},
|
||||
emit(...e) {
|
||||
console.log('Event', e)
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
// await authenticateDeviceCode({
|
||||
// connect(...args) {
|
||||
// console.log('Connecting', args)
|
||||
// },
|
||||
// emit(...e) {
|
||||
// console.log('Event', e)
|
||||
// }
|
||||
// }, {})
|
||||
// }
|
||||
|
||||
// debug with node microsoftAuth.js
|
||||
if (!module.parent) {
|
||||
msaTest()
|
||||
}
|
||||
// // debug with node microsoftAuth.js
|
||||
// if (!module.parent) {
|
||||
// msaTest()
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -2,11 +2,26 @@ const BinaryStream = require('@jsprismarine/jsbinaryutils').default
|
|||
const BatchPacket = require('./datatypes/BatchPacket')
|
||||
const cipher = require('./transforms/encryption')
|
||||
const { EventEmitter } = require('events')
|
||||
const Reliability = require('jsp-raknet/protocol/reliability')
|
||||
|
||||
const Versions = require('./options')
|
||||
const debug = require('debug')('minecraft-protocol')
|
||||
|
||||
class Connection extends EventEmitter {
|
||||
versionLessThan(version) {
|
||||
if (typeof version === 'string') {
|
||||
return Versions[version] < this.options.version
|
||||
} else {
|
||||
return version < this.options.version
|
||||
}
|
||||
}
|
||||
|
||||
versionGreaterThan(version) {
|
||||
if (typeof version === 'string') {
|
||||
return Versions[version] > this.options.version
|
||||
} else {
|
||||
return version > this.options.version
|
||||
}
|
||||
}
|
||||
|
||||
startEncryption(iv) {
|
||||
this.encryptionEnabled = true
|
||||
this.inLog('Started encryption', this.sharedSecret, iv)
|
||||
|
|
@ -15,14 +30,10 @@ class Connection extends EventEmitter {
|
|||
this.q2 = []
|
||||
}
|
||||
|
||||
write(name, params) { // TODO: Batch
|
||||
// console.log('Need to encode', name, params)
|
||||
var s = this.connect ? 'C' : 'S'
|
||||
if (this.downQ) s += 'P'
|
||||
this.outLog('NB <- ' + s, name,params)
|
||||
write(name, params) {
|
||||
this.outLog('sending', name, params)
|
||||
const batch = new BatchPacket()
|
||||
const packet = this.serializer.createPacketBuffer({ name, params })
|
||||
// console.log('Sending buf', packet.toString('hex').)
|
||||
batch.addEncodedPacket(packet)
|
||||
|
||||
if (this.encryptionEnabled) {
|
||||
|
|
@ -51,8 +62,6 @@ class Connection extends EventEmitter {
|
|||
//TODO: can we just build Batch before the queue loop?
|
||||
const batch = new BatchPacket()
|
||||
this.outLog('<- BATCH', this.q2)
|
||||
// For now, we're over conservative so send max 3 packets
|
||||
// per batch and hold the rest for the next tick
|
||||
const sending = []
|
||||
for (let i = 0; i < this.q.length; i++) {
|
||||
const packet = this.q.shift()
|
||||
|
|
@ -65,28 +74,10 @@ class Connection extends EventEmitter {
|
|||
} else {
|
||||
this.sendDecryptedBatch(batch)
|
||||
}
|
||||
// this.q2 = []
|
||||
}
|
||||
}, 20)
|
||||
}
|
||||
|
||||
writeRaw(name, buffer) { // skip protodef serializaion
|
||||
// temporary hard coded stuff
|
||||
const batch = new BatchPacket()
|
||||
if (name == 'biome_definition_list') {
|
||||
// so we can send nbt straight from file without parsing
|
||||
const stream = new BinaryStream()
|
||||
stream.writeUnsignedVarInt(0x7a)
|
||||
stream.append(buffer)
|
||||
batch.addEncodedPacket(stream.getBuffer())
|
||||
}
|
||||
|
||||
if (this.encryptionEnabled) {
|
||||
this.sendEncryptedBatch(batch)
|
||||
} else {
|
||||
this.sendDecryptedBatch(batch)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a MCPE packet buffer
|
||||
|
|
@ -121,21 +112,11 @@ class Connection extends EventEmitter {
|
|||
// TODO: Rename this to sendEncapsulated
|
||||
sendMCPE(buffer, immediate) {
|
||||
this.connection.sendReliable(buffer, immediate)
|
||||
// if (this.worker) {
|
||||
// this.outLog('-> buf', buffer)
|
||||
// this.worker.postMessage({ type: 'queueEncapsulated', packet: buffer, immediate })
|
||||
// } else {
|
||||
// const sendPacket = new EncapsulatedPacket()
|
||||
// sendPacket.reliability = Reliability.ReliableOrdered
|
||||
// sendPacket.buffer = buffer
|
||||
// this.connection.addEncapsulatedToQueue(sendPacket)
|
||||
// if (immediate) this.connection.sendQueue()
|
||||
// }
|
||||
}
|
||||
|
||||
// These are callbacks called from encryption.js
|
||||
onEncryptedPacket = (buf) => {
|
||||
this.outLog('ENC BUF', buf)
|
||||
this.outLog('Enc buf', buf)
|
||||
const packet = Buffer.concat([Buffer.from([0xfe]), buf]) // add header
|
||||
|
||||
this.outLog('Sending wrapped encrypted batch', packet)
|
||||
|
|
@ -143,8 +124,6 @@ class Connection extends EventEmitter {
|
|||
}
|
||||
|
||||
onDecryptedPacket = (buf) => {
|
||||
// console.log('🟢 Decrypted', buf)
|
||||
|
||||
const stream = new BinaryStream(buf)
|
||||
const packets = BatchPacket.getPackets(stream)
|
||||
|
||||
|
|
@ -168,10 +147,7 @@ class Connection extends EventEmitter {
|
|||
}
|
||||
}
|
||||
}
|
||||
// console.log('[client] handled incoming ', buffer)
|
||||
}
|
||||
}
|
||||
function serialize(obj = {}, fmt) {
|
||||
return JSON.stringify(obj, (k, v) => typeof v == 'bigint' ? v.toString() : v, fmt)
|
||||
}
|
||||
|
||||
module.exports = { Connection }
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
module.exports = {
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
},
|
||||
|
||||
waitFor(cb, withTimeout) {
|
||||
return Promise.race([
|
||||
new Promise((res, rej) => cb(res)),
|
||||
sleep(withTimeout)
|
||||
])
|
||||
}
|
||||
}
|
||||
37
src/datatypes/util.js
Normal file
37
src/datatypes/util.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
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);
|
||||
if (stat && stat.isDirectory()) {
|
||||
/* Recurse into a subdirectory */
|
||||
results = results.concat(getFiles(file));
|
||||
} else {
|
||||
/* Is a file */
|
||||
results.push(file);
|
||||
}
|
||||
});
|
||||
return results;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
},
|
||||
|
||||
waitFor(cb, withTimeout) {
|
||||
return Promise.race([
|
||||
new Promise((res, rej) => cb(res)),
|
||||
sleep(withTimeout)
|
||||
])
|
||||
},
|
||||
|
||||
serialize(obj = {}, fmt) {
|
||||
return JSON.stringify(obj, (k, v) => typeof v == 'bigint' ? v.toString() : v, fmt)
|
||||
},
|
||||
|
||||
getFiles
|
||||
}
|
||||
|
|
@ -1,12 +1,16 @@
|
|||
// Minimum supported version (< will be kicked)
|
||||
const MIN_VERSION = 422
|
||||
const MIN_VERSION = '1.16.201'
|
||||
// Currently supported verson
|
||||
const CURRENT_VERSION = 422
|
||||
|
||||
const CURRENT_VERSION = '1.16.201'
|
||||
|
||||
const defaultOptions = {
|
||||
// https://minecraft.gamepedia.com/Protocol_version#Bedrock_Edition_2
|
||||
version: CURRENT_VERSION
|
||||
}
|
||||
|
||||
module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION }
|
||||
const Versions = {
|
||||
'1.16.210': 428,
|
||||
'1.16.201': 422
|
||||
}
|
||||
|
||||
module.exports = { defaultOptions, MIN_VERSION, CURRENT_VERSION, Versions }
|
||||
47
src/rak.js
47
src/rak.js
|
|
@ -3,6 +3,7 @@ const Listener = require('jsp-raknet/listener')
|
|||
const EncapsulatedPacket = require('jsp-raknet/protocol/encapsulated_packet')
|
||||
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')
|
||||
} catch (e) {
|
||||
|
|
@ -12,14 +13,14 @@ try {
|
|||
class RakNativeClient extends EventEmitter {
|
||||
constructor(options) {
|
||||
super()
|
||||
this.onConnected = () => {}
|
||||
this.onCloseConnection = () => {}
|
||||
this.onEncapsulated = () => {}
|
||||
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
|
||||
const { buffer, address, guid } = thingy
|
||||
this.onEncapsulated(buffer, address)
|
||||
})
|
||||
this.raknet.on('connected', () => {
|
||||
|
|
@ -51,18 +52,17 @@ class RakNativeClient extends EventEmitter {
|
|||
class RakNativeServer extends EventEmitter {
|
||||
constructor(options = {}) {
|
||||
super()
|
||||
console.log('opts',options)
|
||||
this.onOpenConnection = () => {}
|
||||
this.onCloseConnection = () => {}
|
||||
this.onEncapsulated = () => {}
|
||||
this.onOpenConnection = () => { }
|
||||
this.onCloseConnection = () => { }
|
||||
this.onEncapsulated = () => { }
|
||||
this.raknet = new Server(options.hostname, options.port, {
|
||||
maxConnections: options.maxConnections || 3,
|
||||
minecraft: { },
|
||||
minecraft: {},
|
||||
message: new McPingMessage().toBuffer()
|
||||
})
|
||||
|
||||
|
||||
this.raknet.on('openConnection', (client) => {
|
||||
client.sendReliable = function(buffer, immediate) {
|
||||
client.sendReliable = function (buffer, immediate) {
|
||||
const priority = immediate ? PacketPriority.IMMEDIATE_PRIORITY : PacketPriority.MEDIUM_PRIORITY
|
||||
return this.send(buffer, priority, PacketReliability.RELIABLE_ORDERED, 0)
|
||||
}
|
||||
|
|
@ -70,12 +70,13 @@ class RakNativeServer extends EventEmitter {
|
|||
})
|
||||
|
||||
this.raknet.on('closeConnection', (client) => {
|
||||
console.log('!!! Client CLOSED CONNECTION!')
|
||||
console.warn('! Client closed connection')
|
||||
// TODO: need to handle this properly..
|
||||
this.onCloseConnection(client)
|
||||
})
|
||||
|
||||
this.raknet.on('encapsulated', (thingy) => {
|
||||
const { buffer, address, guid }=thingy
|
||||
const { buffer, address, guid } = thingy
|
||||
// console.log('ENCAP',thingy)
|
||||
this.onEncapsulated(buffer, address)
|
||||
})
|
||||
|
|
@ -89,8 +90,8 @@ class RakNativeServer extends EventEmitter {
|
|||
class RakJsClient extends EventEmitter {
|
||||
constructor(options = {}) {
|
||||
super()
|
||||
this.onConnected = () => {}
|
||||
this.onEncapsulated = () => {}
|
||||
this.onConnected = () => { }
|
||||
this.onEncapsulated = () => { }
|
||||
if (options.useWorkers) {
|
||||
this.connect = this.workerConnect
|
||||
this.sendReliable = this.workerSendReliable
|
||||
|
|
@ -145,9 +146,9 @@ class RakJsServer extends EventEmitter {
|
|||
constructor(options = {}) {
|
||||
super()
|
||||
this.options = options
|
||||
this.onOpenConnection = () => {}
|
||||
this.onCloseConnection = () => {}
|
||||
this.onEncapsulated = () => {}
|
||||
this.onOpenConnection = () => { }
|
||||
this.onCloseConnection = () => { }
|
||||
this.onEncapsulated = () => { }
|
||||
|
||||
if (options.useWorkers) {
|
||||
throw Error('nyi')
|
||||
|
|
@ -159,13 +160,13 @@ class RakJsServer extends EventEmitter {
|
|||
async plainListen() {
|
||||
this.raknet = new Listener()
|
||||
await this.raknet.listen(this.options.hostname, this.options.port)
|
||||
this.raknet.on('openConnection', (conn) => {
|
||||
conn.sendReliable = function(buffer, immediate) {
|
||||
this.raknet.on('openConnection', (conn) => {
|
||||
conn.sendReliable = function (buffer, immediate) {
|
||||
const sendPacket = new EncapsulatedPacket()
|
||||
sendPacket.reliability = Reliability.ReliableOrdered
|
||||
sendPacket.buffer = buffer
|
||||
this.connection.addEncapsulatedToQueue(sendPacket)
|
||||
if (immediate) this.raknet.sendQueue()
|
||||
if (immediate) this.raknet.sendQueue()
|
||||
}
|
||||
this.onOpenConnection(conn)
|
||||
})
|
||||
|
|
@ -174,7 +175,7 @@ class RakJsServer extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
RakClient: Client ? RakNativeClient : RakJsClient,
|
||||
module.exports = {
|
||||
RakClient: Client ? RakNativeClient : RakJsClient,
|
||||
RakServer: Server ? RakNativeServer : RakJsServer
|
||||
}
|
||||
48
src/relay.js
48
src/relay.js
|
|
@ -4,6 +4,7 @@ const { Client } = require("./client")
|
|||
const { Server } = require("./server")
|
||||
const { Player } = require("./serverPlayer")
|
||||
const debug = require('debug')('minecraft-protocol relay')
|
||||
const { serialize } = require('./datatypes/util')
|
||||
|
||||
/** @typedef {{ hostname: string, port: number, auth: 'client' | 'server' | null, destination?: { hostname: string, port: number } }} Options */
|
||||
|
||||
|
|
@ -67,7 +68,6 @@ class RelayPlayer extends Player {
|
|||
console.warn('Old', packet.toString('hex'))
|
||||
console.log('Failed to re-encode', name, params)
|
||||
process.exit(1)
|
||||
throw Error('re-encoding fail for' + name + ' - ' + JSON.stringify(params))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,7 +159,7 @@ class Relay extends Server {
|
|||
client.once('join', () => { // Intercept once handshaking done
|
||||
ds.upstream = client
|
||||
ds.flushUpQueue()
|
||||
console.log('UPSTREAM HAS JOINED')
|
||||
console.log('Connected to upstream server')
|
||||
client.readPacket = (packet) => ds.readUpstream(packet)
|
||||
})
|
||||
this.upstreams.set(clientAddr.hash, client)
|
||||
|
|
@ -167,7 +167,7 @@ class Relay extends Server {
|
|||
|
||||
closeUpstreamConnection(clientAddr) {
|
||||
const up = this.upstreams.get(clientAddr.hash)
|
||||
if (!up) throw Error(`unable to close non-existant connection ${clientAddr.hash}`)
|
||||
if (!up) throw Error(`unable to close non-open connection ${clientAddr.hash}`)
|
||||
up.close()
|
||||
this.upstreams.delete(clientAddr.hash)
|
||||
debug('relay closed connection', clientAddr)
|
||||
|
|
@ -188,43 +188,5 @@ class Relay extends Server {
|
|||
}
|
||||
}
|
||||
|
||||
function serialize(obj = {}, fmt) {
|
||||
return JSON.stringify(obj, (k, v) => typeof v == 'bigint' ? v.toString() : v, fmt)
|
||||
}
|
||||
|
||||
function createRelay() {
|
||||
console.log('Creating relay')
|
||||
/**
|
||||
* Example to create a non-transparent proxy (or 'Relay') connection to destination server
|
||||
* In Relay we de-code and re-encode packets
|
||||
*/
|
||||
const relay = new Relay({
|
||||
/* Hostname and port for clients to listen to */
|
||||
hostname: '0.0.0.0',
|
||||
port: 19130,
|
||||
/**
|
||||
* Who does the authentication
|
||||
* If set to `client`, all connecting clients will be sent a message with a link to authenticate
|
||||
* If set to `server`, the server will authenticate and only one client will be able to join
|
||||
* (Default) If set to `none`, no authentication will be done
|
||||
*/
|
||||
auth: 'server',
|
||||
|
||||
/**
|
||||
* Sets if packets will automatically be forwarded. If set to false, you must listen for on('packet')
|
||||
* events and
|
||||
*/
|
||||
auto: true,
|
||||
|
||||
/* Where to send upstream packets to */
|
||||
destination: {
|
||||
hostname: '127.0.0.1',
|
||||
port: 19132,
|
||||
// encryption: true
|
||||
}
|
||||
})
|
||||
|
||||
relay.create()
|
||||
}
|
||||
|
||||
createRelay()
|
||||
// Too many things called 'Proxy' ;)
|
||||
module.exports = { Relay }
|
||||
|
|
@ -9,18 +9,23 @@ class Server extends EventEmitter {
|
|||
constructor(options) {
|
||||
super()
|
||||
this.options = { ...Options.defaultOptions, ...options }
|
||||
this.serializer = createSerializer()
|
||||
this.deserializer = createDeserializer()
|
||||
this.validateOptions()
|
||||
this.serializer = createSerializer(this.options.version)
|
||||
this.deserializer = createDeserializer(this.options.version)
|
||||
this.clients = {}
|
||||
this.clientCount = 0
|
||||
this.validateOptions()
|
||||
this.inLog = (...args) => console.debug('S', ...args)
|
||||
this.outLog = (...args) => console.debug('S', ...args)
|
||||
this.inLog = (...args) => console.debug('C -> S', ...args)
|
||||
this.outLog = (...args) => console.debug('S -> C', ...args)
|
||||
}
|
||||
|
||||
validateOptions() {
|
||||
if (this.options.version < Options.MIN_VERSION) {
|
||||
throw new Error(`Unsupported protocol version < ${Options.MIN_VERSION} : ${this.options.version}`)
|
||||
if (!Options.Versions[this.options.version]) {
|
||||
console.warn('Supported versions: ', Options.Versions)
|
||||
throw Error(`Unsupported version ${this.options.version}`)
|
||||
}
|
||||
this.options.protocolVersion = Options.Versions[this.options.version]
|
||||
if (this.options.protocolVersion < Options.MIN_VERSION) {
|
||||
throw new Error(`Protocol version < ${Options.MIN_VERSION} : ${this.options.protocolVersion}, too old`)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +44,7 @@ class Server extends EventEmitter {
|
|||
}
|
||||
|
||||
onEncapsulated = (buffer, address) => {
|
||||
debug(address, 'Encapsulated', buffer)
|
||||
this.inLog('encapsulated', address, buffer)
|
||||
const client = this.clients[address]
|
||||
if (!client) {
|
||||
throw new Error(`packet from unknown inet addr: ${address}`)
|
||||
|
|
@ -57,6 +62,4 @@ class Server extends EventEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
const hash = (inetAddr) => inetAddr.address + '/' + inetAddr.port
|
||||
|
||||
module.exports = { Server }
|
||||
|
|
@ -16,15 +16,14 @@ class Player extends Connection {
|
|||
this.server = server
|
||||
this.serializer = server.serializer
|
||||
this.deserializer = server.deserializer
|
||||
// console.log('serializer/des',this.serializer,this.deserializer)
|
||||
this.connection = connection
|
||||
this.options = server.options
|
||||
Encrypt(this, server, this.options)
|
||||
|
||||
this.startQueue()
|
||||
this.status = ClientStatus.Authenticating
|
||||
this.inLog = (...args) => console.info('S ->', ...args)
|
||||
this.outLog = (...args) => console.info('S <-', ...args)
|
||||
this.inLog = (...args) => console.info('S -> C', ...args)
|
||||
this.outLog = (...args) => console.info('C -> S', ...args)
|
||||
}
|
||||
|
||||
getData() {
|
||||
|
|
@ -33,17 +32,17 @@ class Player extends Connection {
|
|||
|
||||
onLogin(packet) {
|
||||
let body = packet.data
|
||||
debug('Body', body)
|
||||
// debug('Login body', body)
|
||||
this.emit('loggingIn', body)
|
||||
|
||||
const clientVer = body.protocol_version
|
||||
if (this.server.options.version) {
|
||||
if (this.server.options.version < clientVer) {
|
||||
this.sendDisconnectStatus(failed_client)
|
||||
if (this.server.options.protocolVersion) {
|
||||
if (this.server.options.protocolVersion < clientVer) {
|
||||
this.sendDisconnectStatus('failed_client')
|
||||
return
|
||||
}
|
||||
} else if (clientVer < MIN_VERSION) {
|
||||
this.sendDisconnectStatus(failed_client)
|
||||
this.sendDisconnectStatus('failed_client')
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +54,7 @@ class Player extends Connection {
|
|||
var { key, userData, chain } = decodeLoginJWT(authChain.chain, skinChain)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
// TODO: disconnect user
|
||||
throw new Error('Failed to verify user')
|
||||
}
|
||||
console.log('Verified user', 'got pub key', key, userData)
|
||||
|
|
@ -66,7 +66,6 @@ class Player extends Connection {
|
|||
this.version = clientVer
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Disconnects a client before it has joined
|
||||
* @param {string} play_status
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
const { PassThrough, Transform } = require('readable-stream')
|
||||
const { Transform } = require('readable-stream')
|
||||
const crypto = require('crypto')
|
||||
const aesjs = require('aes-js')
|
||||
const Zlib = require('zlib')
|
||||
|
||||
const CIPHER = 'aes-256-cfb8'
|
||||
const CIPHER_ALG = 'aes-256-cfb8'
|
||||
|
||||
function createCipher(secret, initialValue) {
|
||||
if (crypto.getCiphers().includes(CIPHER)) {
|
||||
return crypto.createCipheriv(CIPHER, secret, initialValue)
|
||||
if (crypto.getCiphers().includes(CIPHER_ALG)) {
|
||||
return crypto.createCipheriv(CIPHER_ALG, secret, initialValue)
|
||||
}
|
||||
return new Cipher(secret, initialValue)
|
||||
}
|
||||
|
||||
function createDecipher(secret, initialValue) {
|
||||
if (crypto.getCiphers().includes(CIPHER)) {
|
||||
return crypto.createDecipheriv(CIPHER, secret, initialValue)
|
||||
if (crypto.getCiphers().includes(CIPHER_ALG)) {
|
||||
return crypto.createDecipheriv(CIPHER_ALG, secret, initialValue)
|
||||
}
|
||||
return new Decipher(secret, initialValue)
|
||||
}
|
||||
|
|
@ -54,14 +54,11 @@ class Decipher extends Transform {
|
|||
function computeCheckSum(packetPlaintext, sendCounter, secretKeyBytes) {
|
||||
let digest = crypto.createHash('sha256');
|
||||
let counter = Buffer.alloc(8)
|
||||
// writeLI64(sendCounter, counter, 0);
|
||||
counter.writeBigInt64LE(sendCounter, 0)
|
||||
// console.log('Send counter', counter)
|
||||
digest.update(counter);
|
||||
digest.update(packetPlaintext);
|
||||
digest.update(secretKeyBytes);
|
||||
let hash = digest.digest();
|
||||
// console.log('Hash', hash.toString('hex'))
|
||||
return hash.slice(0, 8);
|
||||
}
|
||||
|
||||
|
|
@ -74,21 +71,14 @@ function createEncryptor(client, iv) {
|
|||
|
||||
function process(chunk) {
|
||||
const buffer = Zlib.deflateRawSync(chunk, { level: 7 })
|
||||
// client.outLog('🟡 Compressed', buffer, client.sendCounter)
|
||||
const packet = Buffer.concat([buffer, computeCheckSum(buffer, client.sendCounter, client.secretKeyBytes)])
|
||||
client.sendCounter++
|
||||
// client.outLog('writing to cipher...', packet, client.secretKeyBytes, iv)
|
||||
client.cipher.write(packet)
|
||||
}
|
||||
|
||||
// const stream = new PassThrough()
|
||||
|
||||
client.cipher.on('data', client.onEncryptedPacket)
|
||||
|
||||
|
||||
return (blob) => {
|
||||
// client.outLog(client.options ? 'C':'S', '🟡 Encrypting', client.sendCounter, blob)
|
||||
// stream.write(blob)
|
||||
process(blob)
|
||||
}
|
||||
}
|
||||
|
|
@ -99,6 +89,8 @@ function createDecryptor(client, iv) {
|
|||
client.receiveCounter = client.receiveCounter || 0n
|
||||
|
||||
function verify(chunk) {
|
||||
// TODO: remove the extra logic here, probably fixed with new raknet impl
|
||||
|
||||
// console.log('Decryptor: checking checksum', client.receiveCounter, chunk)
|
||||
// client.outLog('🔵 Inflating', chunk)
|
||||
// First try to zlib decompress, then see how much bytes get read
|
||||
|
|
@ -139,8 +131,6 @@ function createDecryptor(client, iv) {
|
|||
client.decipher.on('data', verify)
|
||||
|
||||
return (blob) => {
|
||||
// client.inLog(client.options ? 'C':'S', ' 🔵 Decrypting', client.receiveCounter, blob)
|
||||
// client.inLog('Using shared key', client.secretKeyBytes, iv)
|
||||
client.decipher.write(blob)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
const { ProtoDefCompiler, CompiledProtodef } = require('protodef').Compiler
|
||||
const { FullPacketParser, Serializer } = require('protodef')
|
||||
|
||||
function createProtocol() {
|
||||
const protocol = require('../../data/newproto.json').types
|
||||
console.log('Proto', protocol)
|
||||
// Compiles the ProtoDef schema at runtime
|
||||
function createProtocol(version) {
|
||||
const protocol = require(`../../data/${version}/protocol.json`).types
|
||||
var compiler = new ProtoDefCompiler()
|
||||
compiler.addTypesToCompile(protocol)
|
||||
compiler.addTypes(require('../datatypes/compiler-minecraft'))
|
||||
|
|
@ -13,8 +13,8 @@ function createProtocol() {
|
|||
return compiledProto
|
||||
}
|
||||
|
||||
|
||||
function getProtocol() {
|
||||
// Loads already generated read/write/sizeof code
|
||||
function getProtocol(version) {
|
||||
const compiler = new ProtoDefCompiler()
|
||||
compiler.addTypes(require('../datatypes/compiler-minecraft'))
|
||||
compiler.addTypes(require('prismarine-nbt/compiler-zigzag'))
|
||||
|
|
@ -26,22 +26,19 @@ function getProtocol() {
|
|||
}
|
||||
|
||||
return new CompiledProtodef(
|
||||
compile(compiler.sizeOfCompiler, '../../data/size.js'),
|
||||
compile(compiler.writeCompiler, '../../data/write.js'),
|
||||
compile(compiler.readCompiler, '../../data/read.js')
|
||||
// compiler.sizeOfCompiler.compile(fs.readFileSync(__dirname + '/../../data/size.js', 'utf-8')),
|
||||
// compiler.writeCompiler.compile(fs.readFileSync(__dirname + '/../../data/write.js', 'utf-8')),
|
||||
// compiler.readCompiler.compile(fs.readFileSync(__dirname + '/../../data/read.js', 'utf-8'))
|
||||
compile(compiler.sizeOfCompiler, `../../data/${version}/size.js`),
|
||||
compile(compiler.writeCompiler, `../../data/${version}/write.js`),
|
||||
compile(compiler.readCompiler, `../../data/${version}/read.js`)
|
||||
)
|
||||
}
|
||||
|
||||
function createSerializer() {
|
||||
var proto = getProtocol()
|
||||
function createSerializer(version) {
|
||||
var proto = getProtocol(version)
|
||||
return new Serializer(proto, 'mcpe_packet');
|
||||
}
|
||||
|
||||
function createDeserializer() {
|
||||
var proto = getProtocol()
|
||||
function createDeserializer(version) {
|
||||
var proto = getProtocol(version)
|
||||
return new FullPacketParser(proto, 'mcpe_packet');
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue