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) }