This commit is contained in:
parent
320c60a0e4
commit
ec93573cba
7 changed files with 320 additions and 0 deletions
1
.direnv/flake-profile
Symbolic link
1
.direnv/flake-profile
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
flake-profile-1-link
|
||||||
1
.direnv/flake-profile-1-link
Symbolic link
1
.direnv/flake-profile-1-link
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
/nix/store/73lbr3h20af5k4bmdz8wd5wi0vyv29d6-nix-shell-env
|
||||||
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
||||||
37
internal/a2s/bread/helpers.go
Normal file
37
internal/a2s/bread/helpers.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package bread
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrUnderflow = errors.New("buffer underflow: not enough data to read") // Error if buffer underflow
|
||||||
|
ErrNumber = errors.New("failed to read number from buffer") // Error with read number
|
||||||
|
ErrBool = errors.New("unsupported boolean byte in buffer") // Error with read boolean
|
||||||
|
ErrString = errors.New("length of the string from the buffer is less than expected") // Error with read string
|
||||||
|
)
|
||||||
|
|
||||||
|
// Helper for check bytes in buffer not underflow
|
||||||
|
func checkLength(buf *bytes.Buffer, size int) error {
|
||||||
|
if buf.Len() < size {
|
||||||
|
return fmt.Errorf("%w: got %d of expected %d bytes", ErrUnderflow, buf.Len(), size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for read number in LittleEndian
|
||||||
|
func readNumber(buf *bytes.Buffer, data any, size int) error {
|
||||||
|
if buf.Len() < size {
|
||||||
|
return fmt.Errorf("%w: got %d of expected %d bytes", ErrUnderflow, buf.Len(), size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := binary.Read(buf, binary.LittleEndian, data); err != nil {
|
||||||
|
return fmt.Errorf("%w: %w", ErrNumber, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
222
internal/a2s/bread/reader.go
Normal file
222
internal/a2s/bread/reader.go
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
// Package bread is a set of helper functions for reading bytes into different types from a buffer.
|
||||||
|
package bread
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Read bytes buffer and return byte
|
||||||
|
func Byte(buf *bytes.Buffer) (byte, error) {
|
||||||
|
if err := checkLength(buf, 1); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.ReadByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return boolean, where 1 is true, 0 is false, other error
|
||||||
|
func Bool(buf *bytes.Buffer) (bool, error) {
|
||||||
|
value, err := Byte(buf)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch value {
|
||||||
|
case 1:
|
||||||
|
return true, nil
|
||||||
|
case 0:
|
||||||
|
return false, nil
|
||||||
|
default:
|
||||||
|
return false, fmt.Errorf("%w: 0x%X", ErrBool, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return int8
|
||||||
|
func Int8(buf *bytes.Buffer) (int8, error) {
|
||||||
|
value, err := Byte(buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return int8(value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return int16
|
||||||
|
func Int16(buf *bytes.Buffer) (int16, error) {
|
||||||
|
var value int16
|
||||||
|
if err := readNumber(buf, &value, 2); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return int32
|
||||||
|
func Int32(buf *bytes.Buffer) (int32, error) {
|
||||||
|
var value int32
|
||||||
|
if err := readNumber(buf, &value, 4); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return int64
|
||||||
|
func Int64(buf *bytes.Buffer) (int64, error) {
|
||||||
|
var value int64
|
||||||
|
if err := readNumber(buf, &value, 8); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return uint16
|
||||||
|
func Uint16(buf *bytes.Buffer) (uint16, error) {
|
||||||
|
var value uint16
|
||||||
|
if err := readNumber(buf, &value, 2); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return uint32
|
||||||
|
func Uint32(buf *bytes.Buffer) (uint32, error) {
|
||||||
|
var value uint32
|
||||||
|
if err := readNumber(buf, &value, 4); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return uint64
|
||||||
|
func Uint64(buf *bytes.Buffer) (uint64, error) {
|
||||||
|
var value uint64
|
||||||
|
if err := readNumber(buf, &value, 8); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return float32
|
||||||
|
func Float32(buf *bytes.Buffer) (float32, error) {
|
||||||
|
var value float32
|
||||||
|
if err := readNumber(buf, &value, 4); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer and return float64
|
||||||
|
func Float64(buf *bytes.Buffer) (float64, error) {
|
||||||
|
var value float64
|
||||||
|
if err := readNumber(buf, &value, 8); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer to first 0x00 delimiter and return as string
|
||||||
|
func String(buf *bytes.Buffer) (string, error) {
|
||||||
|
value, err := buf.ReadBytes(0x00)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(value[:len(value)-1]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer by size count and return as string
|
||||||
|
func StringLen(buf *bytes.Buffer, size int) (string, error) {
|
||||||
|
if err := checkLength(buf, size); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
value := make([]byte, size)
|
||||||
|
n, err := buf.Read(value)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n != size {
|
||||||
|
return "", fmt.Errorf("%w: got '%s' with %d length but expected %d", ErrString, value, n, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(value), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer as float32 and return as time.Duration
|
||||||
|
func Duration32(buf *bytes.Buffer) (time.Duration, error) {
|
||||||
|
f, err := Float32(buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
seconds := int64(f)
|
||||||
|
nanoseconds := int64(math.Round(float64(f-float32(seconds)) * 1e9))
|
||||||
|
|
||||||
|
return time.Duration(seconds)*time.Second + time.Duration(nanoseconds)*time.Nanosecond, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer as float64 and return as time.Duration
|
||||||
|
func Duration64(buf *bytes.Buffer) (time.Duration, error) {
|
||||||
|
f, err := Float64(buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
seconds := int64(f)
|
||||||
|
nanoseconds := int64(math.Round((f - float64(seconds)) * 1e9))
|
||||||
|
|
||||||
|
return time.Duration(seconds)*time.Second + time.Duration(nanoseconds)*time.Nanosecond, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read bytes buffer to first 0x00 delimiter
|
||||||
|
func BytesPage(buf *bytes.Buffer) ([]byte, error) {
|
||||||
|
value, err := buf.ReadBytes(0x00)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
n := len(value) - 1
|
||||||
|
|
||||||
|
return value[:n], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace Escape sequence to Escape value
|
||||||
|
//
|
||||||
|
// {0x01, 0x01} -> 0x01
|
||||||
|
// {0x01, 0x02} -> 0x00
|
||||||
|
// {0x01, 0x03} -> 0xFF
|
||||||
|
func EscapeSequences(data []byte) []byte {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
for i := 0; i < len(data); i++ {
|
||||||
|
if data[i] == 0x01 && i+1 < len(data) {
|
||||||
|
switch data[i+1] {
|
||||||
|
case 0x01:
|
||||||
|
buf.WriteByte(0x01)
|
||||||
|
i++
|
||||||
|
case 0x02:
|
||||||
|
buf.WriteByte(0x00)
|
||||||
|
i++
|
||||||
|
case 0x03:
|
||||||
|
buf.WriteByte(0xFF)
|
||||||
|
i++
|
||||||
|
default:
|
||||||
|
buf.WriteByte(data[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.WriteByte(data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
57
internal/a2s/players.go
Normal file
57
internal/a2s/players.go
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
package a2s
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"reforgerds-updater/internal/bread"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://developer.valvesoftware.com/wiki/Server_queries#Response_Format_2
|
||||||
|
type Player struct {
|
||||||
|
Name string `json:"name,omitempty"`
|
||||||
|
Duration time.Duration `json:"duration,omitempty"`
|
||||||
|
Score uint32 `json:"score,omitempty"`
|
||||||
|
Index byte `json:"index,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get A2S_PLAYER
|
||||||
|
func (c *Client) GetPlayers() (*[]Player, error) {
|
||||||
|
data, _, _, err := c.Get(PlayerRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer(data)
|
||||||
|
count, err := bread.Byte(buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w count: %w", ErrPlayerRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
players := []Player{}
|
||||||
|
|
||||||
|
for i := 0; i < int(count); i++ {
|
||||||
|
player := Player{}
|
||||||
|
|
||||||
|
if player.Index, err = bread.Byte(buf); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w index: %w", ErrPlayerRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.Name, err = bread.String(buf); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w name: %w", ErrPlayerRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.Score, err = bread.Uint32(buf); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w score: %w", ErrPlayerRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if player.Duration, err = bread.Duration32(buf); err != nil {
|
||||||
|
return nil, fmt.Errorf("%w duration: %w", ErrPlayerRead, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
players = append(players, player)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &players, nil
|
||||||
|
}
|
||||||
1
main.go
1
main.go
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
"git.skdevstudios.com/specCon18/reforgerds-updater/internal/a2s"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Update struct {
|
type Update struct {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue