|
1 | 1 | // import { createHash } from 'react-native-crypto' |
| 2 | +import { InteractionManager } from 'react-native' |
2 | 3 | import net from 'react-native-tcp' |
3 | 4 | import events from 'events' |
4 | 5 | import { |
@@ -100,92 +101,96 @@ const initiateConnection = async (opts: { |
100 | 101 | // Handle timeout after 20 seconds of no data. |
101 | 102 | if (conn.disconnectTimer) clearTimeout(conn.disconnectTimer) |
102 | 103 | conn.disconnectTimer = setTimeout(() => conn.close(), 20000) |
103 | | - // Buffer data for read. |
104 | | - conn.bufferedData = Buffer.concat([conn.bufferedData, newData]) |
105 | | - // ;(async () => { This would need a mutex. |
106 | | - while (true) { |
107 | | - const packet = conn.compressionEnabled |
108 | | - ? parseCompressedPacket(conn.bufferedData) |
109 | | - : parsePacket(conn.bufferedData) |
110 | | - if (packet) { |
111 | | - if (packet.id === 0x03 && !conn.loggedIn) { |
112 | | - const [threshold] = readVarInt(packet.data) |
113 | | - conn.compressionThreshold = threshold |
114 | | - conn.compressionEnabled = threshold >= 0 |
115 | | - } else if (packet.id === 0x02 && !conn.loggedIn) { |
116 | | - conn.loggedIn = true |
117 | | - } else if (packet.id === 0x21) { |
118 | | - conn |
119 | | - .writePacket(0x0f, packet.data) |
120 | | - .catch(err => conn.emit('error', err)) |
121 | | - } else if ( |
122 | | - (packet.id === 0x00 && !conn.loggedIn) || |
123 | | - (packet.id === 0x1a && conn.loggedIn) |
124 | | - ) { |
125 | | - const [chatLength, chatVarIntLength] = readVarInt(packet.data) |
126 | | - conn.disconnectReason = packet.data |
127 | | - .slice(chatVarIntLength, chatVarIntLength + chatLength) |
128 | | - .toString('utf8') |
129 | | - } /* else if (packet.id === 0x01 && !conn.loggedIn) { |
130 | | - // What if no accessToken was provided? |
131 | | - const [serverIdLen, serverIdLenLen] = readVarInt(packet.data) |
132 | | - const serverId = packet.data.slice( |
133 | | - serverIdLenLen, |
134 | | - serverIdLen + serverIdLenLen |
135 | | - ) |
136 | | - const data = packet.data.slice(serverIdLen + serverIdLenLen) |
137 | | - const [pkLen, pkLenLen] = readVarInt(data) |
138 | | - const publicKey = data.slice(pkLenLen, pkLen + pkLenLen) |
139 | | - const verifyTokenData = data.slice(pkLen + pkLenLen) |
140 | | - const [, verifyTokenLengthLength] = readVarInt(verifyTokenData) |
141 | | - const verifyToken = verifyTokenData.slice(verifyTokenLengthLength) |
142 | | - // TODO: https://wiki.vg/Protocol_Encryption |
143 | | - // eslint-disable-next-line @typescript-eslint/no-floating-promises |
144 | | - ;(async () => { |
145 | | - // Generate random 16-byte shared secret. |
146 | | - const sharedSecret = await generateSharedSecret() |
147 | | - // Generate hash. |
148 | | - const sha1 = createHash('sha1') |
149 | | - sha1.update(serverId) // ASCII encoding of the server id string from Encryption Request |
150 | | - sha1.update(sharedSecret) |
151 | | - sha1.update(publicKey) // Server's encoded public key from Encryption Request |
152 | | - const hash = mcHexDigest(sha1.digest()) |
153 | | - // Send hash to Mojang servers. |
154 | | - const req = await fetch( |
155 | | - 'https://sessionserver.mojang.com/session/minecraft/join', |
156 | | - { |
157 | | - method: 'POST', |
158 | | - body: JSON.stringify({}) |
159 | | - } |
| 104 | + // Run after interactions to improve user experience. |
| 105 | + InteractionManager.runAfterInteractions(() => { |
| 106 | + // Buffer data for read. |
| 107 | + // TODO: Implement decryption. |
| 108 | + conn.bufferedData = Buffer.concat([conn.bufferedData, newData]) |
| 109 | + // ;(async () => { This would need a mutex. |
| 110 | + while (true) { |
| 111 | + const packet = conn.compressionEnabled |
| 112 | + ? parseCompressedPacket(conn.bufferedData) |
| 113 | + : parsePacket(conn.bufferedData) |
| 114 | + if (packet) { |
| 115 | + if (packet.id === 0x03 && !conn.loggedIn) { |
| 116 | + const [threshold] = readVarInt(packet.data) |
| 117 | + conn.compressionThreshold = threshold |
| 118 | + conn.compressionEnabled = threshold >= 0 |
| 119 | + } else if (packet.id === 0x02 && !conn.loggedIn) { |
| 120 | + conn.loggedIn = true |
| 121 | + } else if (packet.id === 0x21) { |
| 122 | + conn |
| 123 | + .writePacket(0x0f, packet.data) |
| 124 | + .catch(err => conn.emit('error', err)) |
| 125 | + } else if ( |
| 126 | + (packet.id === 0x00 && !conn.loggedIn) || |
| 127 | + (packet.id === 0x1a && conn.loggedIn) |
| 128 | + ) { |
| 129 | + const [chatLength, chatVarIntLength] = readVarInt(packet.data) |
| 130 | + conn.disconnectReason = packet.data |
| 131 | + .slice(chatVarIntLength, chatVarIntLength + chatLength) |
| 132 | + .toString('utf8') |
| 133 | + } /* else if (packet.id === 0x01 && !conn.loggedIn) { |
| 134 | + // What if no accessToken was provided? |
| 135 | + const [serverIdLen, serverIdLenLen] = readVarInt(packet.data) |
| 136 | + const serverId = packet.data.slice( |
| 137 | + serverIdLenLen, |
| 138 | + serverIdLen + serverIdLenLen |
160 | 139 | ) |
161 | | - // POST https://sessionserver.mojang.com/session/minecraft/join |
162 | | - // Body: {"accessToken": "<accessToken>", |
163 | | - // "selectedProfile": "<player's uuid without dashes>", |
164 | | - // "serverId": "<serverHash>"} |
165 | | - // Encrypt shared secret and verify token with public key. |
166 | | - // Send encryption response packet. |
167 | | - // Encrypted Shared Secret Length - VarInt |
168 | | - // Encrypted Shared Secret - Byte Array |
169 | | - // Encrypted Verify Token Length - VarInt |
170 | | - // Encrypted Verify Token - Byte Array |
171 | | - // It then sends a Login Success, and enables AES/CFB8 encryption. |
172 | | - // For the Initial Vector (IV) and AES setup, both sides use the shared |
173 | | - // secret as both the IV and the key. Similarly, the client will also |
174 | | - // enable encryption upon sending Encryption Response. |
175 | | - // From this point forward, everything is encrypted. |
176 | | - // Note: the entire packet is encrypted, including the length |
177 | | - // fields and the packet's data. |
178 | | - // The Login Success packet is sent encrypted. |
179 | | - })() |
180 | | - } */ |
181 | | - conn.bufferedData = |
182 | | - conn.bufferedData.length <= packet.packetLength |
183 | | - ? Buffer.alloc(0) // Avoid errors shortening. |
184 | | - : conn.bufferedData.slice(packet.packetLength) |
185 | | - conn.emit('packet', packet) |
186 | | - } else break |
187 | | - } |
188 | | - conn.emit('data', newData) |
| 140 | + const data = packet.data.slice(serverIdLen + serverIdLenLen) |
| 141 | + const [pkLen, pkLenLen] = readVarInt(data) |
| 142 | + const publicKey = data.slice(pkLenLen, pkLen + pkLenLen) |
| 143 | + const verifyTokenData = data.slice(pkLen + pkLenLen) |
| 144 | + const [, verifyTokenLengthLength] = readVarInt(verifyTokenData) |
| 145 | + const verifyToken = verifyTokenData.slice(verifyTokenLengthLength) |
| 146 | + // TODO: https://wiki.vg/Protocol_Encryption |
| 147 | + // eslint-disable-next-line @typescript-eslint/no-floating-promises |
| 148 | + ;(async () => { |
| 149 | + // Generate random 16-byte shared secret. |
| 150 | + const sharedSecret = await generateSharedSecret() |
| 151 | + // Generate hash. |
| 152 | + const sha1 = createHash('sha1') |
| 153 | + sha1.update(serverId) // ASCII encoding of the server id string from Encryption Request |
| 154 | + sha1.update(sharedSecret) |
| 155 | + sha1.update(publicKey) // Server's encoded public key from Encryption Request |
| 156 | + const hash = mcHexDigest(sha1.digest()) |
| 157 | + // Send hash to Mojang servers. |
| 158 | + const req = await fetch( |
| 159 | + 'https://sessionserver.mojang.com/session/minecraft/join', |
| 160 | + { |
| 161 | + method: 'POST', |
| 162 | + body: JSON.stringify({}) |
| 163 | + } |
| 164 | + ) |
| 165 | + // POST https://sessionserver.mojang.com/session/minecraft/join |
| 166 | + // Body: {"accessToken": "<accessToken>", |
| 167 | + // "selectedProfile": "<player's uuid without dashes>", |
| 168 | + // "serverId": "<serverHash>"} |
| 169 | + // Encrypt shared secret and verify token with public key. |
| 170 | + // Send encryption response packet. |
| 171 | + // Encrypted Shared Secret Length - VarInt |
| 172 | + // Encrypted Shared Secret - Byte Array |
| 173 | + // Encrypted Verify Token Length - VarInt |
| 174 | + // Encrypted Verify Token - Byte Array |
| 175 | + // It then sends a Login Success, and enables AES/CFB8 encryption. |
| 176 | + // For the Initial Vector (IV) and AES setup, both sides use the shared |
| 177 | + // secret as both the IV and the key. Similarly, the client will also |
| 178 | + // enable encryption upon sending Encryption Response. |
| 179 | + // From this point forward, everything is encrypted. |
| 180 | + // Note: the entire packet is encrypted, including the length |
| 181 | + // fields and the packet's data. |
| 182 | + // The Login Success packet is sent encrypted. |
| 183 | + })() |
| 184 | + } */ |
| 185 | + conn.bufferedData = |
| 186 | + conn.bufferedData.length <= packet.packetLength |
| 187 | + ? Buffer.alloc(0) // Avoid errors shortening. |
| 188 | + : conn.bufferedData.slice(packet.packetLength) |
| 189 | + conn.emit('packet', packet) |
| 190 | + } else break |
| 191 | + } |
| 192 | + conn.emit('data', newData) |
| 193 | + }).then(() => {}, console.error) |
189 | 194 | }) |
190 | 195 | socket.on('close', () => { |
191 | 196 | conn.closed = true |
|
0 commit comments