Skip to content

Commit

Permalink
Support 1.19 offline mode servers, fix a crash.
Browse files Browse the repository at this point in the history
  • Loading branch information
retrixe committed Aug 10, 2022
1 parent 4d87389 commit 446e5e8
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 33 deletions.
4 changes: 2 additions & 2 deletions src/minecraft/chatToJsx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -231,15 +231,15 @@ const parseChatToJsx = (

// React Component-ised.
const ChatToJsxNonMemo = (props: {
chat: MinecraftChat
chat?: MinecraftChat
component: React.ComponentType<TextProps>
colorMap: ColorMap
componentProps?: {}
clickEventHandler?: (clickEvent: ClickEvent) => void
trim?: boolean
}) =>
parseChatToJsx(
props.chat,
props.chat ?? { text: '' },
props.component,
props.colorMap,
props.clickEventHandler,
Expand Down
47 changes: 27 additions & 20 deletions src/minecraft/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import {
readVarInt,
writeVarInt,
resolveHostname,
generateSharedSecret,
mcHexDigest,
protocolMap
protocolMap,
getRandomBytes
} from './utils'
import { Certificate, joinMinecraftSession } from './api/mojang'

Expand Down Expand Up @@ -59,6 +59,7 @@ export class ServerConnection extends events.EventEmitter {
disconnectReason?: string
aesDecipher?: Decipher
aesCipher?: Cipher
msgSalt?: Buffer

constructor(socket: net.Socket, options: ConnectionOptions) {
super()
Expand Down Expand Up @@ -180,39 +181,44 @@ const initiateConnection = async (opts: ConnectionOptions) => {
? Buffer.alloc(0) // Avoid errors shortening.
: conn.bufferedData.slice(packet.packetLength)
// Internally handle login packets.
if (packet.id === 0x03 && !conn.loggedIn) {
const is119 = conn.options.protocolVersion >= protocolMap[1.19]
if (packet.id === 0x03 && !conn.loggedIn /* Set Compression */) {
const [threshold] = readVarInt(packet.data)
conn.compressionThreshold = threshold
conn.compressionEnabled = threshold >= 0
} else if (packet.id === 0x02 && !conn.loggedIn) {
conn.loggedIn = true
} else if (packet.id === 0x21) {
conn.loggedIn = true // Login Success
} else if (
// Keep Alive (clientbound)
(packet.id === 0x21 && !is119) ||
(packet.id === 0x1e && is119)
) {
conn
.writePacket(0x0f, packet.data)
.writePacket(is119 ? 0x11 : 0x0f, packet.data)
.catch(err => conn.emit('error', err))
} else if (
// Disconnect (login) or Disconnect (play)
(packet.id === 0x00 && !conn.loggedIn) ||
(packet.id === 0x1a &&
conn.loggedIn &&
opts.protocolVersion < protocolMap[1.19]) ||
(packet.id === 0x17 &&
conn.loggedIn &&
opts.protocolVersion >= protocolMap[1.19])
(packet.id === 0x1a && conn.loggedIn && !is119) ||
(packet.id === 0x17 && conn.loggedIn && is119)
) {
const [chatLength, chatVarIntLength] = readVarInt(packet.data)
conn.disconnectReason = packet.data
.slice(chatVarIntLength, chatVarIntLength + chatLength)
.toString('utf8')
} else if (packet.id === 0x01 && !conn.loggedIn && !accessToken) {
conn.disconnectReason =
'{"text":"This server requires a premium account to be logged in!"}'
conn.close()
} else if (packet.id === 0x01 && !conn.loggedIn) {
/* Encryption Request */
if (!accessToken || !selectedProfile) {
conn.disconnectReason =
'{"text":"This server requires a premium account to be logged in!"}'
conn.close()
continue
}
// https://wiki.vg/Protocol_Encryption
const [serverId, publicKey, verifyToken] =
parseEncryptionRequestPacket(packet)
;(async () => {
const secret = await generateSharedSecret() // Generate random 16-byte shared secret.
const secret = await getRandomBytes(16) // Generate random 16-byte shared secret.
// Generate hash.
const sha1 = createHash('sha1')
sha1.update(serverId) // ASCII encoding of the server id string from Encryption Request
Expand All @@ -221,8 +227,8 @@ const initiateConnection = async (opts: ConnectionOptions) => {
const hash = mcHexDigest(sha1.digest())
// Send hash to Mojang servers.
const req = await joinMinecraftSession(
accessToken as string,
selectedProfile as string,
accessToken,
selectedProfile,
hash
)
if (!req.ok) {
Expand All @@ -244,7 +250,8 @@ const initiateConnection = async (opts: ConnectionOptions) => {
writeVarInt(encryptedVerifyToken.byteLength),
encryptedVerifyToken
]
if (opts.protocolVersion >= protocolMap[1.19]) {
if (is119) {
conn.msgSalt = await getRandomBytes(8)
response.splice(2, 0, true)
}
const AES_ALG = 'aes-128-cfb8'
Expand Down
6 changes: 4 additions & 2 deletions src/minecraft/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ export const protocolMap = {
'1.18.1': 757,
'1.18.2': 758,
1.19: 759,
'1.19.1': 760,
'1.19.2': 760,
auto: -1
}

Expand Down Expand Up @@ -72,9 +74,9 @@ export const resolveHostname = async (
} else return [hostname, port]
}

export const generateSharedSecret = async () =>
export const getRandomBytes = async (size: number) =>
await new Promise<Buffer>((resolve, reject) => {
randomBytes(16, (err, buf) => {
randomBytes(size, (err, buf) => {
if (err) reject(err)
else resolve(buf)
})
Expand Down
1 change: 1 addition & 0 deletions src/screens/ServerScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ const ServerScreen = (props: Props) => {
dropdownIconColor={darkMode ? '#ffffff' : '#000000'}
>
<Picker.Item label='Auto' value='auto' />
<Picker.Item label='1.19' value='1.19' />
<Picker.Item label='1.18.2' value='1.18.2' />
<Picker.Item label='1.18/1.18.1' value='1.18' />
<Picker.Item label='1.17.1' value='1.17.1' />
Expand Down
17 changes: 12 additions & 5 deletions src/screens/chat/ChatScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,14 +162,21 @@ const ChatScreen = ({ navigation, route }: Props) => {
.writePacket(0x03, concatPacketData([msg]))
.catch(handleError(addMessage, sendMessageError))
} else {
const id = msg.startsWith('/') ? 0x03 : 0x04
const timestamp = Buffer.alloc(8)
timestamp.writeIntBE(Date.now(), 2, 6) // writeBigInt64BE(BigInt(Date.now()))
const salt = connection.connection.msgSalt ?? Buffer.alloc(8)
// TODO-1.19: Send signature(s) and preview chat if possible.
const data = concatPacketData([
id === 0x03 ? msg.substring(1) : msg,
timestamp,
salt,
writeVarInt(0),
false
])
connection.connection
.writePacket(
0x04,
concatPacketData([msg, timestamp, writeVarInt(0), false])
)
.writePacket(id, data)
.catch(handleError(addMessage, sendMessageError))
// TODO-1.19: Support sending Chat Command/Chat Message/Chat Preview.
}
}

Expand Down
14 changes: 10 additions & 4 deletions src/screens/chat/packetHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const packetHandler =
.slice(chatVarIntLength, chatVarIntLength + chatLength)
.toString('utf8')
const position = packet.data.readInt8(chatVarIntLength + chatLength)
// TODO: Support position 2 (also in 0x5f packet) and sender for disableChat/blocked players.
// TODO: Support position 2 (also in 0x5f 1.19 packet) and sender for disableChat/blocked players.
if (position === 0 || position === 1) {
addMessage(parseValidJson(chatJson))
}
Expand Down Expand Up @@ -87,15 +87,21 @@ export const packetHandler =
} catch (e) {
handleError(addMessage, parseMessageError)(e)
}
} else if (packet.id === 0x2e /* Open Window */) {
} else if (
(packet.id === 0x2e && !is119) ||
(packet.id === 0x2b && is119) /* Open Window */
) {
// Just close the window.
const [windowId] = readVarInt(packet.data)
const buf = Buffer.alloc(1)
buf.writeUInt8(windowId)
connection // Close Window (serverbound)
.writePacket(0x09, buf)
.catch(handleError(addMessage, inventoryCloseError))
} else if (packet.id === 0x35 /* Death Combat Event */) {
} else if (
(packet.id === 0x35 && !is119) ||
(packet.id === 0x33 && is119) /* Death Combat Event */
) {
const [, playerIdLen] = readVarInt(packet.data)
const offset = playerIdLen + 4 // Entity ID
const [chatLen, chatVarIntLength] = readVarInt(packet.data, offset)
Expand All @@ -108,7 +114,7 @@ export const packetHandler =
// Automatically respawn.
// LOW-TODO: Should this be manual, or a dialog, like MC?
connection // Client Status
.writePacket(0x04, writeVarInt(0))
.writePacket(is119 ? 0x06 : 0x04, writeVarInt(0))
.catch(handleError(addMessage, respawnError))
} else if (packet.id === 0x52 /* Update Health */) {
const newHealth = packet.data.readFloatBE(0)
Expand Down

0 comments on commit 446e5e8

Please sign in to comment.