rumblefrog / go-a2s

Golang implementation of Source Query
MIT License
66 stars 16 forks source link

Request error #7

Closed 715kg closed 2 years ago

715kg commented 2 years ago

Hello. I have a problem when I enter the IP address from cs 1.6 servers, the error ErrBadChallengeResponse = errors.New("Bad challenge response") is triggered

90% of cs 1.6 servers receive this error in response

rumblefrog commented 2 years ago

CS 1.6 runs on the Gold Src Engine, no? In the readme, it says only Source Engine is supported.

715kg commented 2 years ago

C's 1.6 servers run on the hl1 engine. requests for bytes and headers are the same as for the source engine. the answer may be slightly different. So why not make a universal library for all types of games. ?)

715kg commented 2 years ago

https://github.com/RIscRIpt/hldsinfo/blob/27a0ed5fbd6f436cc29c08c8c0f9f4fd19c9bc99/hldsinfo/hldsinfo.go

here it works for both cs 1.6 and cs source. also cs go, hlaf life 1 .... and so on. all on the Source engine. Even those cs 1.6 servers that do not work on a2s work.

715kg commented 2 years ago

but I don't have enough information that the hldsinfo package outputs.

I'm not strong enough in programming to work with bytes, it's a little difficult for me to understand what and how the packet receives. tell me how to make it work not only for the source engine

715kg commented 2 years ago

IMG_20220408_082905 IMG_20220408_082926

The documentation says 2 engines. and there is a list of packages at the bottom of the page. so why not make a working package, according to the full description of the documentation. so that it works as described. I and other users will be grateful if you update your a2s package

715kg commented 2 years ago

When I change data, immediate, err := c.getChallenge(builder.Bytes(), A2S_INFO_RESPONSE)

On data, immediate, err := c.getChallenge(builder.Bytes(), 0x6D)

Which corresponds to the answer (0x6D) GoldSource

And I also change the check

if header != A2S_INFO_RESPONSE { 
return nil, ErrUnsupportedHeader
 }

on

if header != 0x6D{
 return nil, ErrUnsupportedHeader
 }

I get a response from GoldSource. But the data in the structure is not output correctly, since the GoldSource response is slightly different. I do not know how to fix it((

Quote from the documentation https://developer .valvesoftware.com/wiki/Server_queries (GoldSource servers reply using the same response as Source servers. Older and pre-Steam servers still reply with the response format below. GoldSource can be distinguished from Source via AppIDs.)

Please add the verification conditions on GoldSource, with the output of correct information in the existing structure. I will be grateful to you. I could implement the check myself, but I don't know much about bytes and how they work

715kg commented 2 years ago

I made changes, now it works more or less correctly. As an experienced programmer, I ask you to correct what I wrote, because I am not completely sure that it will work correctly with many servers.

Added

A2S_INFO_RESPONSE_GS = 0x6D // GoldSource

Check

header := reader.ReadUint8()
    if header != A2S_INFO_RESPONSE {
        if header != A2S_INFO_RESPONSE_GS {
            return nil, ErrUnsupportedHeader
        }
    }

Made a condition

// Source & Up
    if header == A2S_INFO_RESPONSE {

        info.Protocol = reader.ReadUint8()

        info.Name = reader.ReadString()
        info.Map = reader.ReadString()
        info.Folder = reader.ReadString()
        info.Game = reader.ReadString()

        info.ID = reader.ReadUint16()

        info.Players = reader.ReadUint8()
        info.MaxPlayers = reader.ReadUint8()
        info.Bots = reader.ReadUint8()

        // Rag Doll Kung Fu servers always return 0 for "Server type."
        info.ServerType = ParseServerType(reader.ReadUint8())

        info.ServerOS = ParseServerOS(reader.ReadUint8())

        info.Visibility = reader.ReadUint8() == 1

        info.VAC = reader.ReadUint8() == 1

        if AppID(info.ID) == App_TheShip {
            info.TheShip = &TheShipInfo{}
            info.TheShip.Mode = ParseTheShipMode(reader.ReadUint8())
            info.TheShip.Witnesses = reader.ReadUint8()
            info.TheShip.Duration = reader.ReadUint8()
        }

        info.Version = reader.ReadString()

        // Start of EDF

        if !reader.More() {
            return info, nil
        }

        info.ExtendedServerInfo = &ExtendedServerInfo{}

        info.EDF = reader.ReadUint8()

        if (info.EDF & 0x80) != 0 {
            info.ExtendedServerInfo.Port = reader.ReadUint16()
        }

        if (info.EDF & 0x10) != 0 {
            info.ExtendedServerInfo.SteamID = reader.ReadUint64()
        }

        if (info.EDF & 0x40) != 0 {
            info.SourceTV = &SourceTVInfo{}
            info.SourceTV.Port = reader.ReadUint16()
            info.SourceTV.Name = reader.ReadString()
        }

        if (info.EDF & 0x20) != 0 {
            info.ExtendedServerInfo.Keywords = reader.ReadString()
        }

        if (info.EDF & 0x01) != 0 {
            info.ExtendedServerInfo.GameID = reader.ReadUint64()
        }
    }

// GoldSource
    if header == A2S_INFO_RESPONSE_GS {

        info.Adresss = reader.ReadString()
        info.Name = reader.ReadString()
        info.Map = reader.ReadString()
        info.Folder = reader.ReadString()
        info.Game = reader.ReadString()

        info.Players = reader.ReadUint8()
        info.MaxPlayers = reader.ReadUint8()
        info.Protocol = reader.ReadUint8()
        // Rag Doll Kung Fu servers always return 0 for "Server type."
        info.ServerType = ParseServerType(reader.ReadUint8())
        info.ServerOS = ParseServerOS(reader.ReadUint8())
        info.Visibility = reader.ReadUint8() == 1
        info.Mod = reader.ReadUint8()

        if int8(info.Mod) == 1 {
            info.Mods = &InfoMod{}
            info.Mods.Link = reader.ReadString()
            info.Mods.DownLink = reader.ReadString()
            info.Mods.NULL = reader.ReadUint8()

            if info.Mods.NULL != 0 {
                info.Mods.Version = reader.ReadUint64()
                info.Mods.Size = reader.ReadUint64()
            }

            info.Mods.Type = reader.ReadUint8()
            info.Mods.DLL = reader.ReadUint8()

        }

        info.VAC = reader.ReadUint8() == 1
        info.Bots = reader.ReadUint8()

    }

Added structure

type InfoMod struct {
    Link     string `json:"Mode"`
    DownLink string `json:"DownLink"`
    NULL     uint8  `json:"NULL"`
    Version  uint64 `json:"Version"`
    Size     uint64 `json:"Size"`
    Type     uint8  `json:"Type"`
    DLL      uint8  `json:"DLL"`
}

Added variables to the ServerInfo structure

// Adresss server.
    Adresss string `json:"Adresss"`

    // Mod of game.
    Mod uint8 `json:"Mod"`

    // Mod info
    Mods *InfoMod `json:"Mods,omitempty"`

The final result info.go

package a2s

import (
    "errors"
    "fmt"
)

const (
    A2S_INFO_REQUEST     = 0x54
    A2S_INFO_RESPONSE    = 0x49 // Source & up
    A2S_INFO_RESPONSE_GS = 0x6D // GoldSource
)

var (
    ErrBadPacketHeader   = errors.New("Packet header mismatch")
    ErrUnsupportedHeader = errors.New("Unsupported protocol header")
)

type ServerInfo struct {

    // Adresss server.
    Adresss string `json:"Adresss"`

    // Protocol version used by the server.
    Protocol uint8 `json:"Protocol"`

    // Name of the server.
    Name string `json:"Name"`

    // Map the server has currently loaded.
    Map string `json:"Map"`

    // Name of the folder containing the game files.
    Folder string `json:"Folder"`

    // Full name of the game.
    Game string `json:"Game"`

    // Steam Application ID of game.
    ID uint16 `json:"AppID"`

    // Mod of game.
    Mod uint8 `json:"Mod"`

    // Mod info
    Mods *InfoMod `json:"Mods,omitempty"`

    // Number of players on the server.
    Players uint8 `json:"Players"`

    // Maximum number of players the server reports it can hold.
    MaxPlayers uint8 `json:"MaxPlayers"`

    // Number of bots on the server.
    Bots uint8 `json:"Bots"`

    // Indicates the type of server
    // Rag Doll Kung Fu servers always return 0 for "Server type."
    ServerType ServerType `json:"ServerType"`

    // Indicates the operating system of the server
    ServerOS ServerOS `json:"ServerOS"`

    // Indicates whether the server requires a password
    Visibility bool `json:"Visibility"`

    // Specifies whether the server uses VAC
    VAC bool `json:"VAC"`

    // These fields only exist in a response if the server is running The Ship
    TheShip *TheShipInfo `json:"TheShip,omitempty"`

    // Version of the game installed on the server.
    Version string `json:"Version"`

    // If present, this specifies which additional data fields will be included.
    EDF uint8 `json:"EDF,omitempty"`

    ExtendedServerInfo *ExtendedServerInfo `json:"ExtendedServerInfo,omitempty"`

    SourceTV *SourceTVInfo `json:"SourceTV,omitempty"`
}

type TheShipInfo struct {
    Mode      TheShipMode `json:"Mode"`
    Witnesses uint8       `json:"Witnesses"`
    Duration  uint8       `json:"Duration"`
}

type InfoMod struct {
    Link     string `json:"Mode"`
    DownLink string `json:"DownLink"`
    NULL     uint8  `json:"NULL"`
    Version  uint64 `json:"Version"`
    Size     uint64 `json:"Size"`
    Type     uint8  `json:"Type"`
    DLL      uint8  `json:"DLL"`
}

type ExtendedServerInfo struct {
    // The server's game port number.
    Port uint16 `json:"Port"`

    // Server's SteamID.
    SteamID uint64 `json:"SteamID"`

    // Tags that describe the game according to the server (for future use.)
    Keywords string `json:"Keywords"`

    // The server's 64-bit GameID. If this is present, a more accurate AppID is present in the low 24 bits. The earlier AppID could have been truncated as it was forced into 16-bit storage.
    GameID uint64 `json:"GameID"`
}

type SourceTVInfo struct {
    // Spectator port number for SourceTV.
    Port uint16 `json:"Port"`

    // Name of the spectator server for SourceTV.
    Name string `json:"Name"`
}

func (c *Client) QueryInfo() (*ServerInfo, error) {
    var builder PacketBuilder

    /*
        (FF FF FF FF) 54 53 6F 75 72 63 65 20 45 6E 67 69   ÿÿÿÿTSource Engi
        6E 65 20 51 75 65 72 79 00                        ne Query.

    */
    builder.WriteBytes([]byte{
        0xFF, 0xFF, 0xFF, 0xFF, A2S_INFO_REQUEST,
    })

    builder.WriteCString("Source Engine Query")

    data, immediate, err := c.getChallenge(builder.Bytes(), 0x6D)

    if err != nil {
        fmt.Println("Ошибка тут 1", err)
        return nil, err
    }

    if !immediate {
        builder.WriteBytes(data)
        if err := c.send(builder.Bytes()); err != nil {
            fmt.Println("Ошибка тут 2")
            return nil, err
        }

        data, err = c.receive()

        if err != nil {
            fmt.Println("Ошибка тут 3")
            return nil, err
        }
    }

    /*
        Header  long    Always equal to -1 (0xFFFFFFFF). Means it isn't split.
        Payload
    */

    reader := NewPacketReader(data)

    if reader.ReadInt32() != -1 {
        return nil, ErrBadPacketHeader
    }

    info := &ServerInfo{}

    header := reader.ReadUint8()
    if header != A2S_INFO_RESPONSE {
        if header != A2S_INFO_RESPONSE_GS {
            return nil, ErrUnsupportedHeader
        }
    }

    // Source & Up
    if header == A2S_INFO_RESPONSE {

        info.Protocol = reader.ReadUint8()

        info.Name = reader.ReadString()
        info.Map = reader.ReadString()
        info.Folder = reader.ReadString()
        info.Game = reader.ReadString()

        info.ID = reader.ReadUint16()

        info.Players = reader.ReadUint8()
        info.MaxPlayers = reader.ReadUint8()
        info.Bots = reader.ReadUint8()

        // Rag Doll Kung Fu servers always return 0 for "Server type."
        info.ServerType = ParseServerType(reader.ReadUint8())

        info.ServerOS = ParseServerOS(reader.ReadUint8())

        info.Visibility = reader.ReadUint8() == 1

        info.VAC = reader.ReadUint8() == 1

        if AppID(info.ID) == App_TheShip {
            info.TheShip = &TheShipInfo{}
            info.TheShip.Mode = ParseTheShipMode(reader.ReadUint8())
            info.TheShip.Witnesses = reader.ReadUint8()
            info.TheShip.Duration = reader.ReadUint8()
        }

        info.Version = reader.ReadString()

        // Start of EDF

        if !reader.More() {
            return info, nil
        }

        info.ExtendedServerInfo = &ExtendedServerInfo{}

        info.EDF = reader.ReadUint8()

        if (info.EDF & 0x80) != 0 {
            info.ExtendedServerInfo.Port = reader.ReadUint16()
        }

        if (info.EDF & 0x10) != 0 {
            info.ExtendedServerInfo.SteamID = reader.ReadUint64()
        }

        if (info.EDF & 0x40) != 0 {
            info.SourceTV = &SourceTVInfo{}
            info.SourceTV.Port = reader.ReadUint16()
            info.SourceTV.Name = reader.ReadString()
        }

        if (info.EDF & 0x20) != 0 {
            info.ExtendedServerInfo.Keywords = reader.ReadString()
        }

        if (info.EDF & 0x01) != 0 {
            info.ExtendedServerInfo.GameID = reader.ReadUint64()
        }
    }

    // GoldSource
    if header == A2S_INFO_RESPONSE_GS {

        info.Adresss = reader.ReadString()
        info.Name = reader.ReadString()
        info.Map = reader.ReadString()
        info.Folder = reader.ReadString()
        info.Game = reader.ReadString()

        info.Players = reader.ReadUint8()
        info.MaxPlayers = reader.ReadUint8()
        info.Protocol = reader.ReadUint8()
        // Rag Doll Kung Fu servers always return 0 for "Server type."
        info.ServerType = ParseServerType(reader.ReadUint8())
        info.ServerOS = ParseServerOS(reader.ReadUint8())
        info.Visibility = reader.ReadUint8() == 1
        info.Mod = reader.ReadUint8()

        if int8(info.Mod) == 1 {
            info.Mods = &InfoMod{}
            info.Mods.Link = reader.ReadString()
            info.Mods.DownLink = reader.ReadString()
            info.Mods.NULL = reader.ReadUint8()

            if info.Mods.NULL != 0 {
                info.Mods.Version = reader.ReadUint64()
                info.Mods.Size = reader.ReadUint64()
            }

            info.Mods.Type = reader.ReadUint8()
            info.Mods.DLL = reader.ReadUint8()

        }

        info.VAC = reader.ReadUint8() == 1
        info.Bots = reader.ReadUint8()

    }

    return info, nil
}
715kg commented 2 years ago

But here's what to do with A2S_PLAYER and A2S_RULES I don't know. They don't work on GoldSource. Although they should.

rumblefrog commented 2 years ago

This library could support Gold Src, I made it initially to suit my needs, PR is welcome.