minerecon/packetsender.go
2024-08-23 12:19:44 +02:00

799 lines
23 KiB
Go

package main
import (
"bufio"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/google/uuid"
"net"
"reflect"
"strconv"
"strings"
)
func PingIP(ip net.IP, port uint16, host string) (response Response, errOut error) {
if host == "" {
host = ip.String()
}
addr := &net.TCPAddr{IP: ip, Port: int(port)}
conn, err := net.DialTCP("tcp", nil, addr)
if err != nil {
errOut = err
return
}
didRestartConnection := false
defer func(conn *net.TCPConn) {
_ = conn.Close()
}(conn)
var state byte
state = 0
// 0 - handshaking
// 1 - status
// 2 - login
// 3 - configuration
// 4 - play(since this is a scanner, we don't need to actually join the game)
response.Encryption = false
response.CompressionThreshold = -2
response.PluginDataSent = map[string]string{}
response.ScanProgress = 0
response.ServerInfo = ServerInfo{
Hostname: host,
Port: port,
IP: ip.String(),
}
handshakePacketPing, err := createHandshakePacket(-1, host, port, 1, response.CompressionThreshold)
if err != nil {
errOut = err
return
}
_, err = conn.Write(handshakePacketPing)
if err != nil {
errOut = err
return
}
statusRequestPacket, err := createStatusRequestPacket(response.CompressionThreshold)
if err != nil {
errOut = err
return
}
_, err = conn.Write(statusRequestPacket)
if err != nil {
errOut = err
return
}
var username string
var PlayerUUID []byte
compressionStarted := false
reader := bufio.NewReader(conn)
for {
packetID, packetData, packetLength, err := readPacket(reader, response.CompressionThreshold)
if err != nil {
errOut = err
return
}
switch packetID {
case 0x00:
if state == 0 {
_, stringJson := receiveString(packetData)
jsonDecoder := json.NewDecoder(strings.NewReader(stringJson))
err = jsonDecoder.Decode(&response)
if err != nil {
errOut = err
println(stringJson)
return
}
response.ScanProgress = 1
if !didRestartConnection {
handshakePacketJoin, err := createHandshakePacket(response.Version.Protocol, host, port, 2, response.CompressionThreshold)
if err != nil {
errOut = err
return
}
err = conn.Close()
if err != nil {
errOut = err
return
}
conn, err = net.DialTCP("tcp", nil, addr)
if err != nil {
errOut = err
return
}
reader = bufio.NewReader(conn)
_, err = conn.Write(handshakePacketJoin)
if err != nil {
errOut = err
return
}
didRestartConnection = true
}
if len(response.Players.Sample) > 0 {
username = response.Players.Sample[0].Name
} else {
username = "YeahAkis_"
}
response.Username = username
PlayerUUID = constructOfflinePlayerUUID(username)
response.RawMessage = stringJson
state = 1
loginPacket, err := createOfflineLoginPacket(username, response.CompressionThreshold)
if err != nil {
errOut = err
return
}
_, err = conn.Write(loginPacket)
if err != nil {
errOut = err
return
}
state = 2
} else if state == 2 {
currentOffset, component, err := receiveTextComponent(packetData)
if err != nil {
errOut = err
return
}
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.Message = component
return
}
break
case 0x01:
if state == 3 {
currentOffset, channelName := receiveString(packetData)
nextOffset, channelValue := receiveString(packetData[currentOffset:])
currentOffset += nextOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.PluginDataSent[channelName] = channelValue
} else if state == 2 {
response.Encryption = true
response.ScanProgress = 2
return
} else if state == 0 {
//ping response
}
break
case 0x02:
if state == 2 {
playerRecvUUID := packetData[:16]
currentOffset := 16
if !reflect.DeepEqual(PlayerUUID, playerRecvUUID) {
errOut = errors.New("player UUID mismatch")
return
}
addToOffset, receivedUsername := receiveString(packetData[16:])
currentOffset += addToOffset
if receivedUsername != username {
errOut = errors.New("username mismatch")
return
}
//TODO property array handling
confirmLoginPacket, err := createConfirmLoginPacket(response.CompressionThreshold, compressionStarted)
if err != nil {
errOut = err
return
}
_, err = conn.Write(confirmLoginPacket)
if err != nil {
errOut = err
return
}
clientBrandPacket, err := createClientBrandPacket("vanilla", response.CompressionThreshold, compressionStarted)
if err != nil {
errOut = err
return
}
_, err = conn.Write(clientBrandPacket)
if err != nil {
errOut = err
return
}
state = 3
} else if state == 3 {
currentOffset, component, err := receiveTextComponent(packetData)
if err != nil {
errOut = err
return
}
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.Message = component
return
}
break
case 0x03:
if state == 2 {
currentOffset, compression := receiveVarint(packetData)
response.CompressionThreshold = compression
compressionStarted = true
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
} else if state == 3 {
confirmPlaySwitchPacket, err := createConfirmLoginPacket(response.CompressionThreshold, compressionStarted)
if err != nil {
errOut = err
return
}
_, err = conn.Write(confirmPlaySwitchPacket)
state = 4
}
break
case 0x07:
if state == 3 {
currentOffset, registryID := receiveString(packetData)
newOffset, entryCount := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
var entries []RegistryEntry
for i := 0; i < int(entryCount); i++ {
newOffset, entryID := receiveString(packetData[currentOffset:])
currentOffset += newOffset
newOffset, hasNBT := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
var nbtData []byte
if hasNBT {
nbtData = packetData[currentOffset:]
}
entries = append(entries, RegistryEntry{EntryID: entryID, HasNBT: hasNBT, NBTData: nbtData})
}
registryData := RegistryData{RegistryID: registryID, Entries: entries}
response.RegistryDatas = append(response.RegistryDatas, registryData)
}
break
case 0x0b:
if state == 4 {
currentOffset, difficulty := receiveByte(packetData)
newOffset, locked := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.ServerDifficulty = DifficultyObject{Difficulty: difficulty, Locked: locked}
}
case 0x0c:
if state == 3 {
currentOffset, flagCount := receiveVarint(packetData)
for i := 0; i < int(flagCount); i++ {
nextOffset, feature := receiveString(packetData[currentOffset:])
response.FeatureFlags = append(response.FeatureFlags, feature)
currentOffset += nextOffset
}
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
}
clientInformationPacket, err := createClientInformationPacket("en_us", 32, 0, true, 0x7f, false, false, false, response.CompressionThreshold, compressionStarted)
if err != nil {
errOut = err
return
}
_, err = conn.Write(clientInformationPacket)
if err != nil {
errOut = err
return
}
}
break
case 0x0d:
if state == 3 {
currentOffset, entryCount := receiveVarint(packetData)
for i := 0; i < int(entryCount); i++ {
nextOffset, registryIdentifier := receiveString(packetData[currentOffset:])
currentOffset += nextOffset
nextOffset, lengthSubarray := receiveVarint(packetData[currentOffset:])
currentOffset += nextOffset
var tagArray []TagArray
for j := 0; j < int(lengthSubarray); j++ {
nextOffset, tagIdentifier := receiveString(packetData[currentOffset:])
currentOffset += nextOffset
nextOffset, subSubArrayLength := receiveVarint(packetData[currentOffset:])
currentOffset += nextOffset
var varInts []int32
for e := 0; e < int(subSubArrayLength); e++ {
nextOffset, varIntInArray := receiveVarint(packetData[currentOffset:])
currentOffset += nextOffset
varInts = append(varInts, varIntInArray)
}
tagPiece := TagArray{TagName: tagIdentifier, Entries: varInts}
tagArray = append(tagArray, tagPiece)
}
updateTag := UpdateTag{TagRegistryIdentifier: registryIdentifier, Tags: tagArray}
response.Tags = append(response.Tags, updateTag)
}
}
break
case 0x0e:
if state == 3 {
currentOffset, enabledDatapacks := decodeEnabledDatapacks(packetData)
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
}
response.EnabledDatapacks = enabledDatapacks
datapackPacket, err := createEnabledDatapacksPacket(enabledDatapacks, response.CompressionThreshold, compressionStarted)
if err != nil {
errOut = err
return
}
_, err = conn.Write(datapackPacket)
if err != nil {
errOut = err
return
}
}
break
case 0x1D:
if state == 4 {
currentOffset, component, err := receiveTextComponent(packetData)
if err != nil {
errOut = err
return
}
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.Message = component
return
}
break
case 0x2b:
if state == 4 {
currentOffset, entityID := receiveInt32(packetData)
newOffset, isHardCore := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
newOffset, dimensionCount := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
var dimensions []string
for i := 0; i < int(dimensionCount); i++ {
newOffset, dimension := receiveString(packetData[currentOffset:])
currentOffset += newOffset
dimensions = append(dimensions, dimension)
}
newOffset, maxPlayers := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
newOffset, viewDistance := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
newOffset, simulationDistance := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
newOffset, reducedDebugInfo := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
newOffset, enableRespawnScreen := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
newOffset, doLimitedCrafting := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
newOffset, dimensionType := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
newOffset, dimensionName := receiveString(packetData[currentOffset:])
currentOffset += newOffset
newOffset, hashedSeed := receiveInt64(packetData[currentOffset:])
currentOffset += newOffset
newOffset, gameMode := receiveByte(packetData[currentOffset:])
currentOffset += newOffset
newOffset, previousGameMode := receiveByte(packetData[currentOffset:])
currentOffset += newOffset
newOffset, isDebug := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
newOffset, isFlat := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
newOffset, hasDeathLocation := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
var deathDimensionName string
var deathLocation Position
if hasDeathLocation {
newOffset, deathDimensionName = receiveString(packetData[currentOffset:])
currentOffset += newOffset
newOffset, deathLocation = receivePosition(packetData[currentOffset:])
currentOffset += newOffset
} else {
deathDimensionName = ""
deathLocation = Position{}
}
newOffset, portalCooldown := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
newOffset, enforcesSecureChat := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.PlayerLoginInfo = LoginInfo{
EntityID: entityID,
Hardcore: isHardCore,
Dimensions: dimensions,
MaxPlayers: maxPlayers,
ViewDistance: viewDistance,
SimulationDistance: simulationDistance,
ReducedDebugInfo: reducedDebugInfo,
EnableRespawnScreen: enableRespawnScreen,
DoLimitedCrafting: doLimitedCrafting,
DimensionType: dimensionType,
DimensionName: dimensionName,
HashedSeed: hashedSeed,
GameMode: gameMode,
PreviousGameMode: previousGameMode,
IsDebug: isDebug,
IsFlat: isFlat,
HasDeathLocation: hasDeathLocation,
DeathDimensionName: deathDimensionName,
DeathLocation: deathLocation,
PortalCooldown: portalCooldown,
EnforcesSecureChat: enforcesSecureChat,
}
}
break
case 0x38:
if state == 4 {
currentOffset, flags := receiveByte(packetData)
newOffset, flyingSpeed := receiveFloat32(packetData[currentOffset:])
currentOffset += newOffset
newOffset, fieldOfViewModifer := receiveFloat32(packetData[currentOffset:])
currentOffset += newOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
}
response.PlayerAbilities = PlayerAbilitiesObject{
Invulnerable: flags&0x01 != 0x00,
Flying: flags&0x02 != 0x00,
AllowFlying: flags&0x04 != 0x00,
CreativeMode: flags&0x08 != 0x00,
FlyingSpeed: flyingSpeed,
FieldOfViewModifier: fieldOfViewModifer,
}
}
break
case 0x53:
if state == 4 {
currentOffset, heldSlot := receiveByte(packetData)
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.PlayerSlot = heldSlot
}
break
case 0x77:
if state == 4 {
//todo RECIPES
}
break
case 0x11:
//commands
if state == 4 {
}
break
case 0x41:
//recipe book
if state == 4 {
}
break
case 0x40:
//position update
if state == 4 {
currentOffset, x := receiveFloat64(packetData)
newOffset, y := receiveFloat64(packetData[currentOffset:])
currentOffset += newOffset
newOffset, z := receiveFloat64(packetData[currentOffset:])
currentOffset += newOffset
newOffset, yaw := receiveFloat32(packetData[currentOffset:])
currentOffset += newOffset
newOffset, pitch := receiveFloat32(packetData[currentOffset:])
currentOffset += newOffset
newOffset, flags := receiveByte(packetData[currentOffset:])
currentOffset += newOffset
newOffset, teleportId := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
teleportConfirmPacket, err := createTeleportConfirmPacket(teleportId, response.CompressionThreshold, compressionStarted)
if err != nil {
errOut = err
return
}
_, err = conn.Write(teleportConfirmPacket)
if err != nil {
errOut = err
return
}
response.PlayerLocation = PlayerPosition{
X: x,
Y: y,
Z: z,
Yaw: yaw,
Pitch: pitch,
IsXRelative: flags&0x01 != 0x00,
IsYRelative: flags&0x02 != 0x00,
IsZRelative: flags&0x04 != 0x00,
IsYawRelative: flags&0x08 != 0x00,
IsPitchRelative: flags&0x10 != 0x00,
}
}
break
case 0x4b:
if state == 4 {
currentOffset, motd, errx := receiveTextComponent(packetData[2:])
currentOffset += 2
err = errx
if err != nil {
errOut = err
return
}
response.Description = motd
newOffset, hasIcon := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
var iconSize int32
var iconData []byte
if hasIcon {
newOffset, iconSize = receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
iconData = packetData[currentOffset : currentOffset+int(iconSize)]
response.Favicon.PngData = iconData
} else {
iconSize = 0
iconData = []byte{}
}
}
break
case 0x3e:
if state == 4 {
currentOffset, actions := receiveByte(packetData)
newOffset, numberOfPlayers := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
for i := 0; i < int(numberOfPlayers); i++ {
player := PlayerUpdate{}
newUUID, err := uuid.FromBytes(packetData[currentOffset : currentOffset+16])
if err != nil {
errOut = err
return
}
player.UUID = newUUID
currentOffset += 16
if actions&0x01 != 0x00 {
newOffset, player.Name = receiveString(packetData[currentOffset:])
currentOffset += newOffset
newOffset, numberOfProperties := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
for x := 0; x < int(numberOfProperties); x++ {
property := PlayerProperty{}
newOffset, property.Name = receiveString(packetData[currentOffset:])
currentOffset += newOffset
newOffset, property.Value = receiveString(packetData[currentOffset:])
currentOffset += newOffset
newOffset, propertySigned := receiveByte(packetData[currentOffset:])
currentOffset += newOffset
if propertySigned != 0 {
newOffset, property.Signature = receiveString(packetData[currentOffset:])
currentOffset += newOffset
}
player.Properties = append(player.Properties, property)
}
}
if actions&0x02 != 0x00 {
newOffset, hasSignatureData := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
if hasSignatureData {
player.SignatureData = &PlayerSignatureData{}
player.SignatureData.ChatSessionID = packetData[currentOffset : currentOffset+16]
currentOffset += 16
newOffset, player.SignatureData.PublicKeyExpiryTime = receiveInt64(packetData[currentOffset:])
currentOffset += newOffset
newOffset, encodedPublicKeySize := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
player.SignatureData.EncodedPublicKey = packetData[currentOffset : currentOffset+int(encodedPublicKeySize)]
currentOffset += int(encodedPublicKeySize)
newOffset, publicKeySignatureSize := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
player.SignatureData.PublicKeySignature = packetData[currentOffset : currentOffset+int(publicKeySignatureSize)]
currentOffset += int(publicKeySignatureSize)
}
}
if actions&0x04 != 0x00 {
newOffset, player.GameMode = receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
}
if actions&0x08 != 0x00 {
newOffset, player.Listed = receiveBool(packetData[currentOffset:])
currentOffset += newOffset
}
if actions&0x10 != 0x00 {
newOffset, player.Ping = receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
}
if actions&0x20 != 0x00 {
newOffset, hasDisplayName := receiveBool(packetData[currentOffset:])
currentOffset += newOffset
if hasDisplayName {
newOffset, displayName, err := receiveTextComponent(packetData[currentOffset:])
if err != nil {
errOut = err
return
}
currentOffset += newOffset
player.DisplayName = &displayName
}
}
if !reflect.DeepEqual(PlayerUUID, player.UUID) {
response.PlayersInfo = append(response.PlayersInfo, player)
}
}
}
break
case 0x25:
if state == 4 {
currentOffset, x := receiveFloat64(packetData)
newOffset, z := receiveFloat64(packetData[currentOffset:])
currentOffset += newOffset
newOffset, oldDiameter := receiveFloat64(packetData[currentOffset:])
currentOffset += newOffset
newOffset, newDiameter := receiveFloat64(packetData[currentOffset:])
currentOffset += newOffset
newOffset, speed := receiveVarlong(packetData[currentOffset:])
currentOffset += newOffset
newOffset, portalTeleportBoundary := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
newOffset, warningBlocks := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
newOffset, warningTime := receiveVarint(packetData[currentOffset:])
currentOffset += newOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.WorldBorder = WorldBorderInfo{
X: x,
Z: z,
OldDiameter: oldDiameter,
NewDiameter: newDiameter,
Speed: speed,
PortalTeleportBoundary: portalTeleportBoundary,
WarningBlocks: warningBlocks,
WarningTime: warningTime,
}
}
break
case 0x64:
if state == 4 {
currentOffset, worldAge := receiveInt64(packetData)
newOffset, timeOfDay := receiveInt64(packetData[currentOffset:])
currentOffset += newOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.WorldAge = worldAge
response.TimeOfDay = timeOfDay
}
break
case 0x56:
if state == 4 {
currentOffset, location := receivePosition(packetData)
newOffset, angle := receiveFloat32(packetData[currentOffset:])
currentOffset += newOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
response.DefaultPositionSpawn = DefaultSpawnPosition{
Location: location,
Angle: angle,
}
}
break
case 0x22:
if state == 4 {
currentOffset, event := receiveByte(packetData)
nextOffset, value := receiveFloat32(packetData[currentOffset:])
currentOffset += nextOffset
if currentOffset != packetLength {
errOut = errors.New("packet length mismatch")
return
}
if event == 13 && value == 0 {
chatMessagePacket, err := createChatMessagePacket("Thanks for letting me scan this server", response.CompressionThreshold, compressionStarted)
if err != nil {
errOut = err
return
}
_, err = conn.Write(chatMessagePacket)
if err != nil {
errOut = err
return
}
response.ScanProgress = 3
return //we dont want chunks
}
}
case 0x1f:
if state == 4 {
//todo entityEvent
}
break
default:
packetIDBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(packetIDBytes, uint64(packetID))
fmt.Printf("Unknown packet type %d (%s)\n", packetID, hex.EncodeToString(packetIDBytes))
}
}
}
func PingHostname(host string, port uint16) (Response, error) {
tcpServer, err := net.ResolveTCPAddr("tcp", host+":"+strconv.Itoa(int(port)))
if err != nil {
return Response{}, err
}
return PingIP(tcpServer.IP, port, host)
}