Files
mcsrv/packetLib.go
2024-10-26 12:41:37 +02:00

792 lines
18 KiB
Go

package main
import (
"bufio"
"bytes"
"compress/zlib"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"io"
"log"
"math"
"net/http"
"regexp"
"strings"
)
func generateRandomBytes(size int) ([]byte, error) {
randomBytes := make([]byte, size)
_, err := rand.Read(randomBytes)
if err != nil {
return nil, err
}
return randomBytes, nil
}
type TextPiece struct {
Text string `json:"text,omitempty"`
Color string `json:"color,omitempty"`
CleanText string `json:"cleantext,omitempty"`
}
type TextComponent struct {
Text string `json:"text,omitempty"`
Extra []TextPiece `json:"extra,omitempty"`
CleanText string `json:"cleantext,omitempty"`
}
func getTextComponent(text string) TextComponent {
return TextComponent{
Text: text,
Extra: nil,
CleanText: text,
}
}
type RegistryEntry struct {
EntryID string `json:"entryID,omitempty"`
HasNBT bool `json:"hasNBT,omitempty"`
//TODO implement the nbt data
NBTData *bytes.Buffer `json:"nbtData,omitempty"`
}
type RegistryData struct {
RegistryID string `json:"registryID,omitempty"`
Entries []RegistryEntry `json:"entries,omitempty"`
}
type TagArray struct {
TagName string `json:"tagName,omitempty"`
Entries []int32 `json:"entries,omitempty"`
}
type UpdateTag struct {
TagRegistryIdentifier string `json:"tagRegistryIdentifier,omitempty"`
Tags []TagArray `json:"tags,omitempty"`
}
type DatapackInfo struct {
Namespace string `json:"namespace,omitempty"`
ID string `json:"id,omitempty"`
Version string `json:"version,omitempty"`
}
type ServerLink struct {
LabelString string `json:"labelString,omitempty"`
LabelInt int32 `json:"labelInt,omitempty"`
URL string `json:"url,omitempty"`
}
// GenerateRSAKeyPair generates an RSA key pair and returns the public and private keys.
func GenerateRSAKeyPair() (*rsa.PrivateKey, *rsa.PublicKey, error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
return nil, nil, err
}
return privateKey, &privateKey.PublicKey, nil
}
// ExportRSAPublicKey exports the public key in ASN.1 DER format.
func ExportRSAPublicKey(pubKey *rsa.PublicKey) ([]byte, error) {
// Marshal the public key to ASN.1 DER format
pubASN1, err := x509.MarshalPKIXPublicKey(pubKey)
if err != nil {
return nil, err
}
// Return the raw DER-encoded bytes
return pubASN1, nil
}
type cfb8 struct {
b cipher.Block
blockSize int
in []byte
out []byte
decrypt bool
}
func (x *cfb8) XORKeyStream(dst, src []byte) {
for i := range src {
x.b.Encrypt(x.out, x.in)
copy(x.in[:x.blockSize-1], x.in[1:])
if x.decrypt {
x.in[x.blockSize-1] = src[i]
}
dst[i] = src[i] ^ x.out[0]
if !x.decrypt {
x.in[x.blockSize-1] = dst[i]
}
}
}
// NewCFB8Encryptor returns a Stream which encrypts with cipher feedback mode
// (segment size = 8), using the given Block. The iv must be the same length as
// the Block's block size.
func newCFB8Encryptor(block cipher.Block, iv []byte) cipher.Stream {
return newCFB8(block, iv, false)
}
// NewCFB8Decryptor returns a Stream which decrypts with cipher feedback mode
// (segment size = 8), using the given Block. The iv must be the same length as
// the Block's block size.
func newCFB8Decryptor(block cipher.Block, iv []byte) cipher.Stream {
return newCFB8(block, iv, true)
}
func newCFB8(block cipher.Block, iv []byte, decrypt bool) cipher.Stream {
blockSize := block.BlockSize()
if len(iv) != blockSize {
// stack trace will indicate whether it was de or encryption
panic("cipher.newCFB: IV length must equal block size")
}
x := &cfb8{
b: block,
blockSize: blockSize,
out: make([]byte, blockSize),
in: make([]byte, blockSize),
decrypt: decrypt,
}
copy(x.in, iv)
return x
}
// NewCFB8Cipher Initialize AES/CFB8 encryption using the shared secret
func NewCFB8Cipher(secret []byte) (cipher.Stream, cipher.Stream, error) {
block, err := aes.NewCipher(secret)
if err != nil {
return nil, nil, err
}
// Create CFB8 encryption/decryption ciphers using the custom implementation
iv := secret // Initial vector (IV) is the same as the secret in this case
encryptStream := newCFB8Encryptor(block, iv)
decryptStream := newCFB8Decryptor(block, iv)
return encryptStream, decryptStream, nil
}
func AuthDigest(parts [][]byte) string {
h := sha1.New()
// Hash each part incrementally
for _, part := range parts {
io.WriteString(h, string(part))
}
// Finalize the hash
hash := h.Sum(nil)
// Check for negative hashes
negative := (hash[0] & 0x80) == 0x80
if negative {
hash = twosComplement(hash)
}
// Trim away zeroes
res := strings.TrimLeft(hex.EncodeToString(hash), "0")
if negative {
res = "-" + res
}
return res
}
// little endian
func twosComplement(p []byte) []byte {
carry := true
for i := len(p) - 1; i >= 0; i-- {
p[i] = ^p[i]
if carry {
carry = p[i] == 0xff
p[i]++
}
}
return p
}
type MojangProfile struct {
ID string `json:"id"`
Name string `json:"name"`
Properties []MojangProperty `json:"properties"`
}
type MojangProperty struct {
Name string `json:"name"`
Value string `json:"value"`
Signature string `json:"signature"`
}
func (mojangProperty *MojangProperty) add(buf *bytes.Buffer) {
addString(buf, mojangProperty.Name)
addString(buf, mojangProperty.Value)
hasSignature := mojangProperty.Signature != ""
addBool(buf, hasSignature)
if hasSignature {
addString(buf, mojangProperty.Signature)
}
}
type PlayerAction struct {
ActionMask byte
Name string
Properties []MojangProperty
GameMode int32
Listed bool
Ping int32
DisplayName string
HasDisplayName bool
}
// Helper to add a player with properties
func (playerAction *PlayerAction) add(buf *bytes.Buffer) {
addString(buf, playerAction.Name)
// Add properties
addVarint(buf, int32(len(playerAction.Properties)))
for _, prop := range playerAction.Properties {
prop.add(buf)
}
}
// Helper to update game mode
// Helper to update listed state
// Helper to update latency (ping)
// Helper to update display name
func (player *Player) hasJoinedSession(sharedSecret []byte) (bool, error) {
serverKey, err := ExportRSAPublicKey(serverPublicKey)
serverIDHash := AuthDigest([][]byte{serverID, sharedSecret, serverKey})
remoteAddr := strings.Split(player.conn.RemoteAddr().String(), ":")[0]
url := fmt.Sprintf("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s", player.Entity.Name, serverIDHash, remoteAddr)
resp, err := http.Get(url)
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("failed to verify session: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return false, err
}
var profile MojangProfile
err = json.Unmarshal(body, &profile)
if err != nil {
return false, err
}
player.profile = profile
return true, nil
}
// EncryptPacket encrypts a packet using the AES/CFB8 cipher stream.
func EncryptPacket(stream cipher.Stream, data *bytes.Buffer) *bytes.Buffer {
if stream == nil {
return data
}
encryptedData := make([]byte, data.Len())
stream.XORKeyStream(encryptedData, data.Bytes())
return bytes.NewBuffer(encryptedData)
}
// DecryptingReader returns a *bufio.Reader that provides decrypted data from the original reader.
func DecryptingReader(stream cipher.Stream, reader *bufio.Reader) *bufio.Reader {
pr, pw := io.Pipe() // create a pipe to stream decrypted data
go func() {
defer pw.Close() // ensure the pipe writer is closed when done
buf := make([]byte, 4096) // buffer size, adjust as needed
for {
n, err := reader.Read(buf)
if err != nil {
if err == io.EOF {
break
}
pw.CloseWithError(err) // propagate any non-EOF error to the pipe reader
return
}
data := buf[:n]
decryptedBlock := make([]byte, len(data))
if stream != nil {
stream.XORKeyStream(decryptedBlock, data) // decrypt the data
} else {
decryptedBlock = data // if no stream, just pass the data through
}
_, err = pw.Write(decryptedBlock) // write decrypted data to the pipe writer
if err != nil {
pw.CloseWithError(err) // propagate write errors
return
}
}
}()
return bufio.NewReader(pr) // wrap the pipe reader in a bufio.Reader
}
// Function to clean Minecraft formatting from a text
func cleanMinecraftFormatting(text string) string {
// Regex to match Minecraft formatting codes like § and &
regex := regexp.MustCompile(`(?i)§[0-9A-FK-OR]`)
return regex.ReplaceAllString(text, "")
}
// Decompress function using zlib
func decompress(data *bytes.Buffer) (buf *bytes.Buffer, err error) {
reader, err := zlib.NewReader(data)
if err != nil {
return
}
defer func(reader io.ReadCloser) {
_ = reader.Close()
}(reader)
var decompressedData bytes.Buffer
_, err = io.Copy(&decompressedData, reader)
if err != nil {
return
}
return &decompressedData, nil
}
// Compress function using zlib
func compress(data *bytes.Buffer) (*bytes.Buffer, error) {
var compressedData bytes.Buffer
writer := zlib.NewWriter(&compressedData)
_, err := writer.Write(data.Bytes())
if err != nil {
return nil, err
}
err = writer.Close()
if err != nil {
return nil, err
}
return &compressedData, nil
}
// Modified version of `sendPacket` with hexdump logging, including unencrypted data
func (player *Player) sendPacket(packetID int32, packetData *bytes.Buffer) (err error) {
var origDataBuffer bytes.Buffer
addVarint(&origDataBuffer, packetID) // Add the Packet ID as a VarInt
_, err = origDataBuffer.ReadFrom(packetData)
if err != nil {
return err
}
var outBuffer bytes.Buffer
var outTempBuffer bytes.Buffer
length := int32(origDataBuffer.Len()) // Get the length of the uncompressed packet
compressed := false // Track if the packet is compressed
// Compression based on a threshold
if player.compressionThreshold >= 0 && length >= player.compressionThreshold {
compressed = true // Mark that compression was applied
compressedData, err := compress(&origDataBuffer)
if err != nil {
return err
}
origDataBuffer = *compressedData
// Add the uncompressed length to outTempBuffer
addVarint(&outTempBuffer, length)
}
encrypted := player.encryptStream != nil
// Log the unencrypted packet data
log.Printf("Unencrypted Packet ID: %d", packetID)
log.Printf("Unencrypted Packet Length: %d (compressed: %v, encrypted: %v)", origDataBuffer.Len(), compressed, encrypted)
log.Printf("Hexdump of Unencrypted Packet:\n%s", hex.Dump(origDataBuffer.Bytes()))
// Encrypt the (compressed or uncompressed) data buffer
dataBuffer := origDataBuffer
// Append the (compressed or uncompressed) data buffer to outTempBuffer
_, err = outTempBuffer.ReadFrom(&dataBuffer)
if err != nil {
return err
}
// Add the total packet length to outBuffer
addVarint(&outBuffer, int32(outTempBuffer.Len()))
_, err = outBuffer.ReadFrom(&outTempBuffer)
if err != nil {
return err
}
if encrypted {
outBuffer = *EncryptPacket(player.encryptStream, &outBuffer)
}
_, err = player.conn.Write(outBuffer.Bytes())
if err != nil {
return err
}
return nil
}
// Modified version of `readPacket` with hexdump logging
func (player *Player) readPacket() (packetID int32, packetData *bytes.Buffer, err error) {
packetData = &bytes.Buffer{}
finalReader := player.reader
if player.decryptStream != nil {
finalReader = DecryptingReader(player.decryptStream, player.reader)
}
packetLength, _, err := readVarint(finalReader)
if err != nil {
return
}
packetDataBytes := make([]byte, packetLength)
nt, err := io.ReadFull(finalReader, packetDataBytes)
if err != nil {
return
}
_, err = packetData.Write(packetDataBytes)
if err != nil {
return 0, &bytes.Buffer{}, err
}
n := int32(nt)
if n != packetLength {
err = errors.New("packet read length mismatch")
return
}
var dataLength int
compressed := false // Track if the packet is compressed
if player.compressionThreshold > 0 {
dataLengthTemp, errTemp := receiveVarint(packetData)
err = errTemp
dataLength = int(dataLengthTemp)
}
// If dataLength > 0, it means the packet is compressed
if dataLength > 0 {
compressed = true
packetData, err = decompress(packetData)
if err != nil {
return
}
}
// Extract packet ID
packetID, err = receiveVarint(packetData)
// Log the packet information
log.Printf("Received Packet ID: %d", packetID)
log.Printf("Packet Length: %d (compressed: %v)", packetLength, compressed)
log.Printf("Hexdump of Decoded Packet:\n%s", hex.Dump(packetData.Bytes()))
return
}
func addVarint(buffer *bytes.Buffer, value int32) {
uValue := uint32(value) // Explicitly cast to an unsigned type
for {
b := byte(uValue & 0x7F)
uValue >>= 7
if (uValue != 0 && uValue != 0xFFFFFFFF) || (b&0x40 != 0) {
b |= 0x80
}
addByte(buffer, b)
if uValue == 0 || uValue == 0xFFFFFFFF {
break
}
}
}
func addVarlong(buffer *bytes.Buffer, value int64) {
uValue := uint64(value) // Explicitly cast to an unsigned type
for {
b := byte(uValue & 0x7F)
uValue >>= 7
if (uValue != 0 && uValue != 0xFFFFFFFFFFFFFFFF) || (b&0x40 != 0) {
b |= 0x80
}
addByte(buffer, b)
if uValue == 0 || uValue == 0xFFFFFFFFFFFFFFFF {
break
}
}
}
func readVarint(r *bufio.Reader) (value int32, length uint32, err error) {
var (
b byte
shift uint64
uValue uint32
)
for {
b, err = r.ReadByte()
if err != nil {
return 0, 0, err
}
length++
uValue |= (uint32(b) & 0x7F) << shift
shift += 7
if b&0x80 == 0 {
break
}
}
if shift < 8*uint64(length) && b&0x40 != 0 {
uValue |= ^uint32(0) << shift
}
value = int32(uValue)
return
}
func receiveVarint(buf *bytes.Buffer) (value int32, err error) {
var (
b byte
shift uint64
uValue uint32
length uint32
)
for i := 0; i < buf.Len(); i++ {
b, err = buf.ReadByte()
if err != nil {
return
}
length++
uValue |= (uint32(b) & 0x7F) << shift
shift += 7
if b&0x80 == 0 {
break
}
}
if shift < 8*uint64(length) && b&0x40 != 0 {
uValue |= ^uint32(0) << shift
}
value = int32(uValue)
return
}
func addUUID(buffer *bytes.Buffer, inUUID uuid.UUID) int32 {
binaryUUID, err := inUUID.MarshalBinary()
if err != nil {
log.Println("Error marshalling UUID:", err)
return 0
}
buffer.Write(binaryUUID)
return int32(len(binaryUUID))
}
// Decodes a UUID from the buffer.
func receiveUUID(buffer *bytes.Buffer) (value uuid.UUID, err error) {
b := make([]byte, 16)
_, err = buffer.Read(b)
if err != nil {
return uuid.UUID{}, err
}
value, err = uuid.FromBytes(b)
if err != nil {
return uuid.UUID{}, err
}
return value, nil
}
func addByte(buffer *bytes.Buffer, byte byte) {
buffer.WriteByte(byte)
}
func addBytes(buffer *bytes.Buffer, bytes []byte) {
buffer.Write(bytes)
}
func addBool(buffer *bytes.Buffer, boolean bool) {
var boolByte byte = 0x00
if boolean {
boolByte = 0x01
}
buffer.WriteByte(boolByte)
}
func receiveBool(buffer *bytes.Buffer) (boolean bool, err error) {
b, err := buffer.ReadByte()
if err != nil {
return false, err
}
boolean = b == 1
return
}
// Decodes an uint16 from the buffer.
func receiveUint16(buffer *bytes.Buffer) (value uint16) {
// Create a byte slice to hold the 2 bytes for the uint16.
b := make([]byte, 2)
// Read exactly 2 bytes from the buffer.
_, err := buffer.Read(b)
if err != nil {
return 0
}
// Convert the byte slice to uint16 using BigEndian.
value = binary.BigEndian.Uint16(b)
return value
}
// Decodes an uint16 from the buffer.
func receiveInt16(buffer *bytes.Buffer) (value int16) {
// Create a byte slice to hold the 2 bytes for the uint16.
b := make([]byte, 2)
// Read exactly 2 bytes from the buffer.
_, err := buffer.Read(b)
if err != nil {
return 0
}
// Convert the byte slice to uint16 using BigEndian.
value = int16(binary.BigEndian.Uint16(b))
return value
}
func addInt64(buffer *bytes.Buffer, value int64) {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, uint64(value))
buffer.Write(b)
}
func receiveInt64(buffer *bytes.Buffer) (value int64) {
b := make([]byte, 8)
_, err := buffer.Read(b)
if err != nil {
return 0
}
value = int64(binary.BigEndian.Uint64(b))
return
}
func addFloat32(buffer *bytes.Buffer, value float32) {
bits := math.Float32bits(value)
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, bits)
buffer.Write(b)
}
// Decodes a float32 from the buffer.
func receiveFloat32(buffer *bytes.Buffer) (value float32) {
b := make([]byte, 4)
_, err := buffer.Read(b)
if err != nil {
return 0
}
value = math.Float32frombits(binary.BigEndian.Uint32(b))
return
}
func addFloat64(buffer *bytes.Buffer, value float64) {
bits := math.Float64bits(value)
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, bits)
buffer.Write(b)
}
// Decodes a float64 from the buffer.
func addInt32(buffer *bytes.Buffer, value int32) {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, uint32(value))
buffer.Write(b)
}
// Decodes an int32 from the buffer.
func addInt16(buffer *bytes.Buffer, value int16) {
b := make([]byte, 2)
binary.BigEndian.PutUint16(b, uint16(value))
buffer.Write(b)
}
func addUint32(buffer *bytes.Buffer, value uint32) {
b := make([]byte, 4)
binary.BigEndian.PutUint32(b, value)
buffer.Write(b)
}
// Decodes an uint32 from the buffer.
func receiveUint32(buffer *bytes.Buffer) (value uint32, err error) {
b := make([]byte, 4)
if _, err = buffer.Read(b); err != nil {
return 0, err
}
value = binary.BigEndian.Uint32(b)
return
}
func addString(buffer *bytes.Buffer, str string) {
// Encode string length using VarInt.
addVarint(buffer, int32(len(str)))
// Append the string bytes.
buffer.Write([]byte(str))
}
// Decodes a string from the buffer.
func receiveString(buf *bytes.Buffer) (strOut string, err error) {
stringLen, err := receiveVarint(buf)
if err != nil {
return "", err
}
stringBytes := make([]byte, stringLen)
_, err = buf.Read(stringBytes)
if err != nil {
return "", err
}
strOut = string(stringBytes)
return
}
// addTextComponent serializes a TextComponent to JSON and adds it to the buffer
func addTextComponent(buffer *bytes.Buffer, component TextComponent) {
// Serialize the TextComponent to a JSON string
jsonBytes, err := json.Marshal(component)
if err != nil {
return
}
// Convert JSON bytes to a string
jsonString := string(jsonBytes)
// Use addString to addExplosion the serialized TextComponent JSON to the buffer
addString(buffer, jsonString)
return
}