@@ -118,126 +118,123 @@ const initiateConnection = async (opts: ConnectionOptions) => {
118
118
)
119
119
)
120
120
} )
121
+ socket . on ( 'close' , ( ) => {
122
+ conn . closed = true
123
+ conn . emit ( 'close' )
124
+ } )
125
+ socket . on ( 'error' , err => {
126
+ if ( ! resolved ) reject ( err )
127
+ else conn . emit ( 'error' , err )
128
+ } )
121
129
socket . on ( 'data' , newData => {
122
130
// Handle timeout after 20 seconds of no data.
123
131
if ( conn . disconnectTimer ) clearTimeout ( conn . disconnectTimer )
124
132
conn . disconnectTimer = setTimeout ( ( ) => conn . close ( ) , 20000 )
125
133
// Run after interactions to improve user experience.
126
134
InteractionManager . runAfterInteractions ( ( ) => {
127
- // Note: the entire packet is encrypted, including the length fields and the packet's data.
128
- // https://github.com/PrismarineJS/node-minecraft-protocol/blob/master/src/transforms/encryption.js
129
- let finalData = newData
130
- if ( conn . aesDecipher ) finalData = conn . aesDecipher . update ( newData )
131
- // Buffer data for read.
132
- conn . bufferedData = Buffer . concat ( [ conn . bufferedData , finalData ] )
133
- // ;(async () => { This would need a mutex.
134
- while ( true ) {
135
- const packet = conn . compressionEnabled
136
- ? parseCompressedPacket ( conn . bufferedData )
137
- : parsePacket ( conn . bufferedData )
138
- if ( packet ) {
139
- if ( packet . id === 0x03 && ! conn . loggedIn ) {
140
- const [ threshold ] = readVarInt ( packet . data )
141
- conn . compressionThreshold = threshold
142
- conn . compressionEnabled = threshold >= 0
143
- } else if ( packet . id === 0x02 && ! conn . loggedIn ) {
144
- conn . loggedIn = true
145
- } else if ( packet . id === 0x21 ) {
146
- conn
147
- . writePacket ( 0x0f , packet . data )
148
- . catch ( err => conn . emit ( 'error' , err ) )
149
- } else if (
150
- ( packet . id === 0x00 && ! conn . loggedIn ) ||
151
- ( packet . id === 0x1a && conn . loggedIn )
152
- ) {
153
- const [ chatLength , chatVarIntLength ] = readVarInt ( packet . data )
154
- conn . disconnectReason = packet . data
155
- . slice ( chatVarIntLength , chatVarIntLength + chatLength )
156
- . toString ( 'utf8' )
157
- } else if ( packet . id === 0x01 && ! conn . loggedIn && ! accessToken ) {
158
- conn . disconnectReason =
159
- '{"text":"This server requires a premium account to be logged in!"}'
160
- conn . close ( )
161
- } else if ( packet . id === 0x01 && ! conn . loggedIn ) {
162
- // https://wiki.vg/Protocol_Encryption
163
- const [ serverId , publicKey , verifyToken ] =
164
- parseEncryptionRequestPacket ( packet )
165
- ; ( async ( ) => {
166
- const sharedSecret = await generateSharedSecret ( ) // Generate random 16-byte shared secret.
167
- // Generate hash.
168
- const sha1 = createHash ( 'sha1' )
169
- sha1 . update ( serverId ) // ASCII encoding of the server id string from Encryption Request
170
- sha1 . update ( sharedSecret )
171
- sha1 . update ( publicKey ) // Server's encoded public key from Encryption Request
172
- const hash = mcHexDigest ( sha1 . digest ( ) )
173
- // Send hash to Mojang servers .
174
- const body = JSON . stringify ( {
175
- accessToken ,
176
- selectedProfile ,
177
- serverId : hash
178
- } )
179
- const req = await fetch ( authUrl , {
180
- headers : { 'content-type' : 'application/json' } ,
181
- method : 'POST' ,
182
- body
183
- } )
184
- if ( ! req . ok ) {
185
- throw new Error ( 'Mojang online mode network request failed' )
186
- }
187
- // Encrypt shared secret and verify token with public key.
188
- const pk =
189
- '-----BEGIN PUBLIC KEY-----\n' +
190
- publicKey . toString ( 'base64' ) +
191
- '\n-----END PUBLIC KEY-----'
192
- const ePrms = { key : pk , padding : 1 } // RSA_PKCS1_PADDING
193
- const encryptedSharedSecret = publicEncrypt ( ePrms , sharedSecret )
194
- const encryptedVerifyToken = publicEncrypt ( ePrms , verifyToken )
195
- // Send encryption response packet.
196
- // From this point forward, everything is encrypted, including the Login Success packet.
197
- conn . aesDecipher = createDecipheriv (
198
- 'aes-128-cfb8' ,
199
- sharedSecret ,
200
- sharedSecret
201
- )
202
- await conn . writePacket (
203
- 0x01 ,
204
- concatPacketData ( [
135
+ try {
136
+ // Note: the entire packet is encrypted, including the length fields and the packet's data.
137
+ // https://github.com/PrismarineJS/node-minecraft-protocol/blob/master/src/transforms/encryption.js
138
+ let finalData = newData
139
+ if ( conn . aesDecipher ) finalData = conn . aesDecipher . update ( newData )
140
+ // Buffer data for read.
141
+ conn . bufferedData = Buffer . concat ( [ conn . bufferedData , finalData ] )
142
+ // ;(async () => { This would need a mutex.
143
+ while ( true ) {
144
+ const packet = conn . compressionEnabled
145
+ ? parseCompressedPacket ( conn . bufferedData )
146
+ : parsePacket ( conn . bufferedData )
147
+ if ( packet ) {
148
+ // Remove packet from buffered data.
149
+ conn . bufferedData =
150
+ conn . bufferedData . length <= packet . packetLength
151
+ ? Buffer . alloc ( 0 ) // Avoid errors shortening.
152
+ : conn . bufferedData . slice ( packet . packetLength )
153
+ // Internally handle login packets.
154
+ if ( packet . id === 0x03 && ! conn . loggedIn ) {
155
+ const [ threshold ] = readVarInt ( packet . data )
156
+ conn . compressionThreshold = threshold
157
+ conn . compressionEnabled = threshold >= 0
158
+ } else if ( packet . id === 0x02 && ! conn . loggedIn ) {
159
+ conn . loggedIn = true
160
+ } else if ( packet . id === 0x21 ) {
161
+ conn
162
+ . writePacket ( 0x0f , packet . data )
163
+ . catch ( err => conn . emit ( 'error' , err ) )
164
+ } else if (
165
+ ( packet . id === 0x00 && ! conn . loggedIn ) ||
166
+ ( packet . id === 0x1a && conn . loggedIn )
167
+ ) {
168
+ const [ chatLength , chatVarIntLength ] = readVarInt ( packet . data )
169
+ conn . disconnectReason = packet . data
170
+ . slice ( chatVarIntLength , chatVarIntLength + chatLength )
171
+ . toString ( 'utf8' )
172
+ } else if ( packet . id === 0x01 && ! conn . loggedIn && ! accessToken ) {
173
+ conn . disconnectReason =
174
+ '{"text":"This server requires a premium account to be logged in!"}'
175
+ conn . close ( )
176
+ } else if ( packet . id === 0x01 && ! conn . loggedIn ) {
177
+ // https://wiki.vg/Protocol_Encryption
178
+ const [ serverId , publicKey , verifyToken ] =
179
+ parseEncryptionRequestPacket ( packet )
180
+ ; ( async ( ) => {
181
+ const secret = await generateSharedSecret ( ) // Generate random 16-byte shared secret .
182
+ // Generate hash.
183
+ const sha1 = createHash ( 'sha1' )
184
+ sha1 . update ( serverId ) // ASCII encoding of the server id string from Encryption Request
185
+ sha1 . update ( secret )
186
+ sha1 . update ( publicKey ) // Server's encoded public key from Encryption Request
187
+ const hash = mcHexDigest ( sha1 . digest ( ) )
188
+ // Send hash to Mojang servers.
189
+ const body = JSON . stringify ( {
190
+ accessToken ,
191
+ selectedProfile ,
192
+ serverId : hash
193
+ } )
194
+ const req = await fetch ( authUrl , {
195
+ headers : { 'content-type' : 'application/json' } ,
196
+ method : 'POST' ,
197
+ body
198
+ } )
199
+ if ( ! req . ok ) {
200
+ throw new Error ( 'Mojang online mode network request failed' )
201
+ }
202
+ // Encrypt shared secret and verify token with public key.
203
+ const pk =
204
+ '-----BEGIN PUBLIC KEY-----\n' +
205
+ publicKey . toString ( 'base64' ) +
206
+ '\n-----END PUBLIC KEY-----'
207
+ const ePrms = { key : pk , padding : 1 } // RSA_PKCS1_PADDING
208
+ const encryptedSharedSecret = publicEncrypt ( ePrms , secret )
209
+ const encryptedVerifyToken = publicEncrypt ( ePrms , verifyToken )
210
+ // Send encryption response packet.
211
+ // From this point forward, everything is encrypted, including the Login Success packet.
212
+ const encryptionResponse = concatPacketData ( [
205
213
writeVarInt ( encryptedSharedSecret . byteLength ) ,
206
214
encryptedSharedSecret ,
207
215
writeVarInt ( encryptedVerifyToken . byteLength ) ,
208
216
encryptedVerifyToken
209
217
] )
210
- )
211
- conn . aesCipher = createCipheriv (
212
- 'aes-128-cfb8' ,
213
- sharedSecret ,
214
- sharedSecret
215
- )
216
- } ) ( ) . catch ( e => {
217
- console . error ( e )
218
- conn . disconnectReason =
219
- '{"text":"Failed to authenticate with Mojang servers!"}'
220
- conn . close ( )
221
- } )
222
- }
223
- conn . bufferedData =
224
- conn . bufferedData . length <= packet . packetLength
225
- ? Buffer . alloc ( 0 ) // Avoid errors shortening.
226
- : conn . bufferedData . slice ( packet . packetLength )
227
- conn . emit ( 'packet' , packet )
228
- } else break
218
+ const AES_ALG = 'aes-128-cfb8'
219
+ conn . aesDecipher = createDecipheriv ( AES_ALG , secret , secret )
220
+ await conn . writePacket ( 0x01 , encryptionResponse )
221
+ conn . aesCipher = createCipheriv ( AES_ALG , secret , secret )
222
+ } ) ( ) . catch ( e => {
223
+ console . error ( e )
224
+ conn . disconnectReason =
225
+ '{"text":"Failed to authenticate with Mojang servers!"}'
226
+ conn . close ( )
227
+ } )
228
+ }
229
+ conn . emit ( 'packet' , packet )
230
+ } else break
231
+ }
232
+ conn . emit ( 'data' , newData )
233
+ } catch ( err ) {
234
+ conn . emit ( 'error' , err )
229
235
}
230
- conn . emit ( 'data' , newData )
231
236
} ) . then ( ( ) => { } , console . error )
232
237
} )
233
- socket . on ( 'close' , ( ) => {
234
- conn . closed = true
235
- conn . emit ( 'close' )
236
- } )
237
- socket . on ( 'error' , err => {
238
- if ( ! resolved ) reject ( err )
239
- else conn . emit ( 'error' , err )
240
- } )
241
238
} )
242
239
}
243
240
0 commit comments