workingish client connection to server
Joining a vanilla server is still broken (related to encryption), but minet connects
This commit is contained in:
parent
49cff40097
commit
233f0d33a6
13 changed files with 232 additions and 104 deletions
|
|
@ -197,13 +197,13 @@ packet_start_game:
|
|||
entity_id: zigzag64
|
||||
# The runtime ID of the player. The runtime ID is unique for each world session,
|
||||
# and entities are generally identified in packets using this runtime ID.
|
||||
runtime_entity_id: varint
|
||||
runtime_entity_id: varint64
|
||||
player_gamemode: zigzag32
|
||||
# The spawn position of the player in the world. In servers this is often the same as the
|
||||
# world's spawn position found below.
|
||||
spawn: vec3f
|
||||
# The pitch and yaw of the player
|
||||
rotation: vec3f
|
||||
rotation: vec2f
|
||||
# The seed used to generate the world. Unlike in Java edition, the seed is a 32bit Integer here.
|
||||
seed: zigzag32
|
||||
biome_type: li16
|
||||
|
|
@ -302,8 +302,8 @@ packet_start_game:
|
|||
# The version of the game from which Vanilla features will be used.
|
||||
# The exact function of this field isn't clear.
|
||||
game_version: string
|
||||
limited_world_width_: li32
|
||||
limited_world_length_: li32
|
||||
limited_world_width: li32
|
||||
limited_world_length: li32
|
||||
is_new_nether_: bool
|
||||
experimental_gameplay_override: bool
|
||||
# A base64 encoded world ID that is used to identify the world.
|
||||
|
|
@ -335,7 +335,7 @@ packet_start_game:
|
|||
|
||||
## This is not sent anymore in protocol versions > 419 (Bedrock Edition v1.16.100)
|
||||
## A list of all blocks registered on the server.
|
||||
## block_palette: BlockPalette
|
||||
block_palette: BlockPalette
|
||||
# A list of all items with their legacy IDs which are available in the game.
|
||||
# Failing to send any of the items that are in the game will crash mobile clients.
|
||||
itemstates: Itemstates
|
||||
|
|
@ -525,9 +525,9 @@ packet_mob_effect:
|
|||
packet_update_attributes:
|
||||
!id: 0x1d
|
||||
!bound: server
|
||||
runtime_entity_id: varint
|
||||
runtime_entity_id: varint64
|
||||
attributes: PlayerAttributes
|
||||
tick: varint
|
||||
tick: varint64
|
||||
|
||||
packet_inventory_transaction:
|
||||
!id: 0x1e
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
BehaviourPackInfos: []li16
|
||||
uuid: string
|
||||
version: string
|
||||
length: lu64
|
||||
size: lu64
|
||||
content_key: string
|
||||
sub_pack_name: string
|
||||
content_identity: string
|
||||
|
|
@ -12,7 +12,7 @@ BehaviourPackInfos: []li16
|
|||
TexturePackInfos: []li16
|
||||
uuid: string
|
||||
version: string
|
||||
length: lu64
|
||||
size: lu64
|
||||
content_key: string
|
||||
sub_pack_name: string
|
||||
content_identity: string
|
||||
|
|
@ -27,13 +27,13 @@ ResourcePackIdVersions: []varint
|
|||
# The subpack name of the resource pack.
|
||||
name: string
|
||||
|
||||
ResourcePackIds: string[]varint
|
||||
ResourcePackIds: string[]li16
|
||||
|
||||
Experiment:
|
||||
name: string
|
||||
enabled: bool
|
||||
|
||||
Experiments: Experiment[]varint
|
||||
Experiments: Experiment[]li32
|
||||
|
||||
GameRule:
|
||||
name: string
|
||||
|
|
@ -43,7 +43,7 @@ GameRule:
|
|||
3: float
|
||||
value: type?
|
||||
if bool: bool
|
||||
if int: varint
|
||||
if int: zigzag32
|
||||
if float: lf32
|
||||
|
||||
GameRules: GameRule[]varint
|
||||
|
|
@ -150,10 +150,10 @@ BlockCoordinates: # mojang...
|
|||
z: zigzag32
|
||||
|
||||
PlayerAttributes: []varint
|
||||
min_value: lf32
|
||||
max_value: lf32
|
||||
current_value: lf32
|
||||
default_value: lf32
|
||||
min: lf32
|
||||
max: lf32
|
||||
current: lf32
|
||||
default: lf32
|
||||
name: string
|
||||
|
||||
Transaction:
|
||||
|
|
@ -190,7 +190,7 @@ Transaction:
|
|||
slot: varint
|
||||
old_item: Item
|
||||
new_item: Item
|
||||
new_item_stack_id: has_network_ids?
|
||||
new_item_stack_id: ../has_network_ids?
|
||||
if true: zigzag32
|
||||
default: void
|
||||
transaction_data: transaction_type?
|
||||
|
|
@ -242,7 +242,7 @@ PotionContainerChangeRecipes: []varint
|
|||
output_item_id: zigzag32
|
||||
|
||||
Recipes: []varint
|
||||
type: varint =>
|
||||
type: zigzag32 =>
|
||||
'0': 'shapeless' #'ENTRY_SHAPELESS',
|
||||
'1': 'shaped' #'ENTRY_SHAPED',
|
||||
'2': 'furnace' # 'ENTRY_FURNACE',
|
||||
|
|
@ -341,9 +341,6 @@ PlayerRecords:
|
|||
is_host: bool
|
||||
if remove:
|
||||
uuid: uuid
|
||||
uuid: type?
|
||||
if add: uuid
|
||||
default: void
|
||||
verified: bool[]$records_count
|
||||
|
||||
ScoreEntries:
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "length",
|
||||
"name": "size",
|
||||
"type": "lu64"
|
||||
},
|
||||
{
|
||||
|
|
@ -64,7 +64,7 @@
|
|||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "length",
|
||||
"name": "size",
|
||||
"type": "lu64"
|
||||
},
|
||||
{
|
||||
|
|
@ -117,7 +117,7 @@
|
|||
"ResourcePackIds": [
|
||||
"array",
|
||||
{
|
||||
"countType": "varint",
|
||||
"countType": "li16",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
"Experiments": [
|
||||
"array",
|
||||
{
|
||||
"countType": "varint",
|
||||
"countType": "li32",
|
||||
"type": "Experiment"
|
||||
}
|
||||
],
|
||||
|
|
@ -170,7 +170,7 @@
|
|||
"compareTo": "type",
|
||||
"fields": {
|
||||
"bool": "bool",
|
||||
"int": "varint",
|
||||
"int": "zigzag32",
|
||||
"float": "lf32"
|
||||
},
|
||||
"default": "void"
|
||||
|
|
@ -580,19 +580,19 @@
|
|||
"container",
|
||||
[
|
||||
{
|
||||
"name": "min_value",
|
||||
"name": "min",
|
||||
"type": "lf32"
|
||||
},
|
||||
{
|
||||
"name": "max_value",
|
||||
"name": "max",
|
||||
"type": "lf32"
|
||||
},
|
||||
{
|
||||
"name": "current_value",
|
||||
"name": "current",
|
||||
"type": "lf32"
|
||||
},
|
||||
{
|
||||
"name": "default_value",
|
||||
"name": "default",
|
||||
"type": "lf32"
|
||||
},
|
||||
{
|
||||
|
|
@ -775,7 +775,7 @@
|
|||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "has_network_ids",
|
||||
"compareTo": "../has_network_ids",
|
||||
"fields": {
|
||||
"true": "zigzag32"
|
||||
},
|
||||
|
|
@ -1010,7 +1010,7 @@
|
|||
"type": [
|
||||
"mapper",
|
||||
{
|
||||
"type": "varint",
|
||||
"type": "zigzag32",
|
||||
"mappings": {
|
||||
"0": "shapeless",
|
||||
"1": "shaped",
|
||||
|
|
@ -1449,19 +1449,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "uuid",
|
||||
"type": [
|
||||
"switch",
|
||||
{
|
||||
"compareTo": "type",
|
||||
"fields": {
|
||||
"add": "uuid"
|
||||
},
|
||||
"default": "void"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "verified",
|
||||
"type": [
|
||||
|
|
@ -2671,7 +2658,7 @@
|
|||
},
|
||||
{
|
||||
"name": "runtime_entity_id",
|
||||
"type": "varint"
|
||||
"type": "varint64"
|
||||
},
|
||||
{
|
||||
"name": "player_gamemode",
|
||||
|
|
@ -2683,7 +2670,7 @@
|
|||
},
|
||||
{
|
||||
"name": "rotation",
|
||||
"type": "vec3f"
|
||||
"type": "vec2f"
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
|
|
@ -2834,11 +2821,11 @@
|
|||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "limited_world_width_",
|
||||
"name": "limited_world_width",
|
||||
"type": "li32"
|
||||
},
|
||||
{
|
||||
"name": "limited_world_length_",
|
||||
"name": "limited_world_length",
|
||||
"type": "li32"
|
||||
},
|
||||
{
|
||||
|
|
@ -2887,6 +2874,10 @@
|
|||
"name": "enchantment_seed",
|
||||
"type": "zigzag32"
|
||||
},
|
||||
{
|
||||
"name": "block_palette",
|
||||
"type": "BlockPalette"
|
||||
},
|
||||
{
|
||||
"name": "itemstates",
|
||||
"type": "Itemstates"
|
||||
|
|
@ -3390,7 +3381,7 @@
|
|||
[
|
||||
{
|
||||
"name": "runtime_entity_id",
|
||||
"type": "varint"
|
||||
"type": "varint64"
|
||||
},
|
||||
{
|
||||
"name": "attributes",
|
||||
|
|
@ -3398,7 +3389,7 @@
|
|||
},
|
||||
{
|
||||
"name": "tick",
|
||||
"type": "varint"
|
||||
"type": "varint64"
|
||||
}
|
||||
]
|
||||
],
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@ const ec_pem = require('ec-pem')
|
|||
const SALT = '🧂'
|
||||
const curve = 'secp384r1'
|
||||
|
||||
function Encrypt(client, options) {
|
||||
function Encrypt(client, server, options) {
|
||||
client.ecdhKeyPair = crypto.createECDH(curve)
|
||||
client.ecdhKeyPair.generateKeys()
|
||||
client.clientX509 = writeX509PublicKey(client.ecdhKeyPair.getPublicKey())
|
||||
|
||||
createClientChain(client)
|
||||
|
||||
function startClientboundEncryption(publicKey) {
|
||||
console.warn('[encrypt] Pub key base64: ', publicKey)
|
||||
|
|
@ -31,9 +31,11 @@ function Encrypt(client, options) {
|
|||
const secretHash = crypto.createHash('sha256')
|
||||
secretHash.update(SALT)
|
||||
secretHash.update(client.sharedSecret)
|
||||
console.log('---- SHARED SECRET', client.sharedSecret)
|
||||
|
||||
|
||||
client.secretKeyBytes = secretHash.digest()
|
||||
|
||||
console.log('Hash', client.secretKeyBytes)
|
||||
const x509 = writeX509PublicKey(alice.getPublicKey())
|
||||
const token = JWT.sign({
|
||||
salt: toBase64(SALT),
|
||||
|
|
@ -51,30 +53,109 @@ function Encrypt(client, options) {
|
|||
client.startEncryption(initial)
|
||||
}
|
||||
|
||||
function startServerboundEncryption() {
|
||||
function startServerboundEncryption(token) {
|
||||
console.warn('Starting serverbound encryption', token)
|
||||
const jwt = token?.token
|
||||
if (!jwt) {
|
||||
// TODO: allow connecting to servers without encryption
|
||||
throw Error('Server did not return a valid JWT, cannot start encryption!')
|
||||
}
|
||||
// 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 head = JSON.parse(String(header))
|
||||
const body = JSON.parse(String(payload))
|
||||
const serverPublicKey = readX509PublicKey(head.x5u)
|
||||
client.sharedSecret = alice.computeSecret(serverPublicKey)
|
||||
console.log('------ SHARED SECRET', client.sharedSecret)
|
||||
|
||||
const salt = Buffer.from(body.salt, 'base64')
|
||||
|
||||
const secretHash = crypto.createHash('sha256')
|
||||
secretHash.update(salt)
|
||||
secretHash.update(client.sharedSecret)
|
||||
|
||||
client.secretKeyBytes = secretHash.digest()
|
||||
console.log('Hash', client.secretKeyBytes)
|
||||
const initial = client.secretKeyBytes.slice(0, 16)
|
||||
client.startEncryption(initial)
|
||||
|
||||
// It works! First encrypted packet :)
|
||||
client.write('client_to_server_handshake', {})
|
||||
}
|
||||
|
||||
client.on('server.client_handshake', startClientboundEncryption)
|
||||
}
|
||||
|
||||
function createClientChain(client) {
|
||||
const alice = client.ecdhKeyPair
|
||||
const alicePEM = ec_pem(alice, curve) // https://github.com/nodejs/node/issues/15116#issuecomment-384790125
|
||||
const alicePEMPrivate = alicePEM.encodePrivateKey()
|
||||
const x509 = writeX509PublicKey(alice.getPublicKey())
|
||||
client.on('client.server_handshake', startServerboundEncryption)
|
||||
|
||||
const token = JWT.sign({
|
||||
salt: toBase64(SALT),
|
||||
signedToken: alice.getPublicKey('base64')
|
||||
}, alicePEMPrivate, { algorithm: 'ES384', header: { x5u: x509 } })
|
||||
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 alicePEMPrivate = alicePEM.encodePrivateKey()
|
||||
|
||||
client.clientChain = token
|
||||
const token = JWT.sign({
|
||||
identityPublicKey: mojangKey,
|
||||
certificateAuthority: true
|
||||
}, alicePEMPrivate, { algorithm: 'ES384', header: { x5u: client.clientX509 } })
|
||||
|
||||
client.clientIdentityChain = token
|
||||
client.createClientUserChain(alicePEMPrivate)
|
||||
}
|
||||
|
||||
client.createClientUserChain = (privateKey) => {
|
||||
let payload = {
|
||||
ServerAddress: options.hostname,
|
||||
ThirdPartyName: client.profile.name,
|
||||
DeviceOS: client.session?.deviceOS || 1,
|
||||
GameVersion: options.version || '1.16.201',
|
||||
ClientRandomId: Date.now(), // TODO make biggeer
|
||||
DeviceId: '2099de18-429a-465a-a49b-fc4710a17bb3', // TODO random
|
||||
LanguageCode: 'en_GB', // TODO locale
|
||||
AnimatedImageData: [],
|
||||
PersonaPieces: [],
|
||||
PieceTintColours: [],
|
||||
SelfSignedId: '78eb38a6-950e-3ab9-b2cf-dd849e343701',
|
||||
SkinId: '5eb65f73-af11-448e-82aa-1b7b165316ad.persona-e199672a8c1a87e0-0',
|
||||
SkinData: 'AAAAAA==',
|
||||
SkinResourcePatch: 'ewogICAiZ2VvbWV0cnkiIDogewogICAgICAiYW5pbWF0ZWRfMTI4eDEyOCIgOiAiZ2VvbWV0cnkuYW5pbWF0ZWRfMTI4eDEyOF9wZXJzb25hLWUxOTk2NzJhOGMxYTg3ZTAtMCIsCiAgICAgICJhbmltYXRlZF9mYWNlIiA6ICJnZW9tZXRyeS5hbmltYXRlZF9mYWNlX3BlcnNvbmEtZTE5OTY3MmE4YzFhODdlMC0wIiwKICAgICAgImRlZmF1bHQiIDogImdlb21ldHJ5LnBlcnNvbmFfZTE5OTY3MmE4YzFhODdlMC0wIgogICB9Cn0K',
|
||||
SkinGeometryData: require('./geom'),
|
||||
"SkinImageHeight": 1,
|
||||
"SkinImageWidth": 1,
|
||||
"ArmSize": "wide",
|
||||
"CapeData": "",
|
||||
"CapeId": "",
|
||||
"CapeImageHeight": 0,
|
||||
"CapeImageWidth": 0,
|
||||
"CapeOnClassicSkin": false,
|
||||
PlatformOfflineId: '',
|
||||
PlatformOnlineId: '', //chat
|
||||
// a bunch of meaningless junk
|
||||
CurrentInputMode: 1,
|
||||
DefaultInputMode: 1,
|
||||
DeviceModel: '',
|
||||
GuiScale: -1,
|
||||
UIProfile: 0,
|
||||
TenantId: '',
|
||||
PremiumSkin: false,
|
||||
PersonaSkin: false,
|
||||
PieceTintColors: [],
|
||||
SkinAnimationData: '',
|
||||
ThirdPartyNameOnly: false,
|
||||
"SkinColor": "#ffffcd96",
|
||||
}
|
||||
payload = require('./logPack.json')
|
||||
const customPayload = options.userData || {}
|
||||
payload = { ...payload, ...customPayload }
|
||||
|
||||
client.clientUserChain = JWT.sign(payload, privateKey,
|
||||
{ algorithm: 'ES384', header: { x5u: client.clientX509 } })
|
||||
}
|
||||
}
|
||||
|
||||
function toBase64(string) {
|
||||
return Buffer.from(string).toString('base64')
|
||||
}
|
||||
}
|
||||
|
||||
function readX509PublicKey(key) {
|
||||
var reader = new Ber.Reader(Buffer.from(key, "base64"));
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ const { createDeserializer, createSerializer } = require('./transforms/serialize
|
|||
const { Encrypt } = require('./auth/encryption')
|
||||
const auth = require('./client/auth')
|
||||
const Options = require('./options')
|
||||
const fs = require('fs')
|
||||
|
||||
const log = console.log
|
||||
|
||||
class Client extends Connection {
|
||||
constructor(options) {
|
||||
|
|
@ -41,7 +44,7 @@ class Client extends Connection {
|
|||
|
||||
if (this.raknet) return
|
||||
|
||||
this.raknet = new RakClient('localhost', 19132)
|
||||
this.raknet = new RakClient('127.0.0.1', 19132)
|
||||
await this.raknet.connect()
|
||||
|
||||
this.raknet.on('connecting', () => {
|
||||
|
|
@ -61,16 +64,17 @@ class Client extends Connection {
|
|||
}
|
||||
|
||||
sendLogin() {
|
||||
this.createClientChain()
|
||||
|
||||
const chain = [
|
||||
this.clientChain, // JWT we generated for auth
|
||||
this.clientIdentityChain, // JWT we generated for auth
|
||||
...this.accessToken // Mojang + Xbox JWT from auth
|
||||
]
|
||||
|
||||
const encodedChain = JSON.stringify({ chain })
|
||||
const skinChain = JSON.stringify({})
|
||||
|
||||
const bodyLength = skinChain.length + encodedChain.length + 8
|
||||
const bodyLength = this.clientUserChain.length + encodedChain.length + 8
|
||||
|
||||
console.log('Auth chain', chain)
|
||||
|
||||
|
|
@ -78,7 +82,7 @@ class Client extends Connection {
|
|||
protocol_version: this.options.version,
|
||||
payload_size: bodyLength,
|
||||
chain: encodedChain,
|
||||
client_data: skinChain
|
||||
client_data: this.clientUserChain
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -90,19 +94,31 @@ class Client extends Connection {
|
|||
this.emit('join')
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
readPacket(packet) {
|
||||
console.log('packet', packet)
|
||||
const des = this.server.deserializer.parsePacketBuffer(packet)
|
||||
console.log('->', des)
|
||||
const des = this.deserializer.parsePacketBuffer(packet)
|
||||
console.info('->', des)
|
||||
switch (des.data.name) {
|
||||
case 'login':
|
||||
console.log(des)
|
||||
this.onLogin(des)
|
||||
return
|
||||
case 'client_to_server_handshake':
|
||||
this.onHandshake()
|
||||
case 'server_to_client_handshake':
|
||||
this.emit('client.server_handshake', des.data.params)
|
||||
break
|
||||
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))
|
||||
default:
|
||||
console.log('ignoring, unhandled')
|
||||
console.log('Sending to listeners')
|
||||
}
|
||||
this.emit(des.data.name, des.data.params)
|
||||
|
||||
|
|
|
|||
|
|
@ -63,8 +63,8 @@ async function authenticateDeviceCode (client, options) {
|
|||
try {
|
||||
const flow = new MsAuthFlow(options.username, options.profilesFolder, options.onMsaCode)
|
||||
|
||||
const chain = await flow.getMinecraftToken(client.clientChain)
|
||||
|
||||
const chain = await flow.getMinecraftToken(client.clientX509)
|
||||
// console.log('Chain', chain)
|
||||
await postAuthenticate(client, options, chain)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ class MsAuthFlow {
|
|||
}
|
||||
|
||||
async getMinecraftToken (publicKey) {
|
||||
if (await this.mca.verifyTokens()) {
|
||||
// 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) {
|
||||
debug('[mc] Using existing tokens')
|
||||
return this.mca.getCachedAccessToken().chain
|
||||
} else {
|
||||
|
|
@ -118,7 +120,8 @@ class MsAuthFlow {
|
|||
return await retry(async () => {
|
||||
const xsts = await this.getXboxToken()
|
||||
debug('[xbl] xsts data', xsts)
|
||||
return this.mca.getAccessToken(publicKey, xsts).chain
|
||||
const token = await this.mca.getAccessToken(publicKey, xsts)
|
||||
return token.chain
|
||||
}, () => { this.xbl.forceRefresh = true }, 2)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ class MinecraftTokenManager {
|
|||
}
|
||||
|
||||
async getAccessToken(clientPublicKey, xsts) {
|
||||
debug('[mc] authing to minecraft', xsts)
|
||||
debug('[mc] authing to minecraft', clientPublicKey, xsts)
|
||||
const getFetchOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
|
|
|||
|
|
@ -1,10 +1,19 @@
|
|||
process.env.DEBUG = 'minecraft-protocol raknet'
|
||||
const { Client } = require('./client')
|
||||
|
||||
// console.log = () =>
|
||||
|
||||
async function test() {
|
||||
const client = new Client({
|
||||
|
||||
hostname: '127.0.0.1',
|
||||
port: 19132
|
||||
})
|
||||
|
||||
client.once('resource_packs_info', (packet) => {
|
||||
client.write('resource_pack_client_response', {
|
||||
response_status: 'completed',
|
||||
resourcepackids: []
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ const EncapsulatedPacket = require('@jsprismarine/raknet/protocol/encapsulated_p
|
|||
class Connection extends EventEmitter {
|
||||
startEncryption(iv) {
|
||||
this.encryptionEnabled = true
|
||||
|
||||
console.log('Started encryption', this.sharedSecret, iv)
|
||||
this.decrypt = cipher.createDecryptor(this, iv)
|
||||
this.encrypt = cipher.createEncryptor(this, iv)
|
||||
}
|
||||
|
|
@ -74,7 +74,7 @@ class Connection extends EventEmitter {
|
|||
}
|
||||
|
||||
onDecryptedPacket = (buf) => {
|
||||
console.log('Decrypted', buf)
|
||||
console.log('🟢 Decrypted', buf)
|
||||
|
||||
const stream = new BinaryStream(buf)
|
||||
const packets = BatchPacket.getPackets(stream)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const minecraft = require('./minecraft')
|
|||
|
||||
module.exports = {
|
||||
Read: {
|
||||
UUID: ['native', (buffer, offset) => {
|
||||
uuid: ['native', (buffer, offset) => {
|
||||
return {
|
||||
value: UUID.stringify(buffer.slice(offset, 16 + offset)),
|
||||
size: 16
|
||||
|
|
@ -18,7 +18,7 @@ module.exports = {
|
|||
nbt: ['native', minecraft.nbt[0]]
|
||||
},
|
||||
Write: {
|
||||
UUID: ['native', (value, buffer, offset) => {
|
||||
uuid: ['native', (value, buffer, offset) => {
|
||||
const buf = UUID.parse(value)
|
||||
buf.copy(buffer, offset)
|
||||
return offset + 16
|
||||
|
|
@ -30,7 +30,7 @@ module.exports = {
|
|||
nbt: ['native', minecraft.nbt[1]]
|
||||
},
|
||||
SizeOf: {
|
||||
UUID: ['native', 16],
|
||||
uuid: ['native', 16],
|
||||
restBuffer: ['native', (value) => {
|
||||
return value.length
|
||||
}],
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const fs = require('fs')
|
|||
let server = new Server({
|
||||
|
||||
})
|
||||
server.create('0.0.0.0', 19130)
|
||||
server.create('0.0.0.0', 19132)
|
||||
|
||||
let ran = false
|
||||
|
||||
|
|
|
|||
|
|
@ -119,28 +119,46 @@ function createDecryptor(client, iv) {
|
|||
|
||||
const verifyChecksum = new Transform({ // verify checksum
|
||||
transform(chunk, encoding, cb) {
|
||||
console.log('Decryptor: checking checksum', chunk)
|
||||
console.log('Decryptor: checking checksum', client.receiveCounter, chunk)
|
||||
const packet = chunk.slice(0, chunk.length - 8);
|
||||
const checksum = chunk.slice(chunk.length - 8);
|
||||
const computedCheckSum = computeCheckSum(packet, client.receiveCounter, client.secretKeyBytes)
|
||||
// console.log(computedCheckSum2, computedCheckSum3)
|
||||
console.assert(checksum.toString("hex") == computedCheckSum.toString("hex"), 'checksum mismatch')
|
||||
client.receiveCounter++
|
||||
if (checksum.toString("hex") == computedCheckSum.toString("hex")) {
|
||||
// if (checksum.toString("hex") == computedCheckSum.toString("hex")) {
|
||||
this.push(packet)
|
||||
} else {
|
||||
throw Error(`Checksum mismatch ${checksum.toString("hex")} != ${computedCheckSum.toString("hex")}`)
|
||||
}
|
||||
// console.log('🔵 Decriphered', checksum)
|
||||
|
||||
// const inflated = Zlib.inflateRawSync(chunk, {
|
||||
// chunkSize: 1024 * 1024 * 2
|
||||
// })
|
||||
// console.log('🔵 Inflated')
|
||||
// client.onDecryptedPacket(inflated)
|
||||
// } else {
|
||||
// // console.log('🔴 Not OK')
|
||||
// throw Error(`Checksum mismatch ${checksum.toString("hex")} != ${computedCheckSum.toString("hex")}`)
|
||||
// }
|
||||
cb()
|
||||
}
|
||||
})
|
||||
|
||||
const inflator = new Transform({
|
||||
transform(chunk, enc, cb) {
|
||||
Zlib.inflateRaw(chunk, { chunkSize: 1024 * 1024 * 2 }, (err, buf) => {
|
||||
if (err) throw err
|
||||
this.push(buf)
|
||||
cb()
|
||||
console.log('🔵 Inflating')
|
||||
const inflated = Zlib.inflateRawSync(chunk, {
|
||||
chunkSize: 1024 * 1024 * 2
|
||||
})
|
||||
console.log('🔵 Inflated')
|
||||
this.push(inflated)
|
||||
cb()
|
||||
|
||||
// Zlib.inflateRaw(chunk, { chunkSize: 1024 * 1024 * 2 }, (err, buf) => {
|
||||
// console.log('🔵 INF')
|
||||
// if (err) throw err
|
||||
// this.push(buf)
|
||||
// cb()
|
||||
// })
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -148,11 +166,24 @@ function createDecryptor(client, iv) {
|
|||
.pipe(inflator)
|
||||
// .pipe(Zlib.createInflateRaw({ chunkSize: 1024 * 1024 * 2 }))
|
||||
.on('data', (...args) => client.onDecryptedPacket(...args))
|
||||
.on('end', () => console.log('Decryptor: finish pipeline'))
|
||||
// .on('end', () => console.log('Decryptor: finish pipeline'))
|
||||
|
||||
// Not sure why, but sending two packets to the decryption pipe before
|
||||
// the other is completed breaks the checksum check.
|
||||
// TODO: Refactor the logic here to be async so we can await a promise
|
||||
// queue
|
||||
let decQ = []
|
||||
setInterval(() => {
|
||||
if (decQ.length) {
|
||||
let pak = decQ.shift()
|
||||
console.log('🟡 DECRYPTING', pak)
|
||||
client.decipher.write(pak)
|
||||
}
|
||||
}, 500)
|
||||
|
||||
return (blob) => {
|
||||
client.decipher.write(blob)
|
||||
decQ.push(blob)
|
||||
// client.decipher.write(blob)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue