185 lines
4.6 KiB
Go
185 lines
4.6 KiB
Go
// gen_packetIDs.go generates the enumeration of packet IDs used on the wire.
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strconv"
|
|
"text/template"
|
|
|
|
"github.com/iancoleman/strcase"
|
|
)
|
|
|
|
const (
|
|
protocolURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/1.17.1/protocol.json"
|
|
packetidTmpl = `// This file is automatically generated by gen_packetIDs.go. DO NOT EDIT.
|
|
|
|
package packetid
|
|
|
|
// Login state
|
|
const (
|
|
// Clientbound
|
|
{{range $ID, $Name := .Login.Clientbound}} {{$Name}} = {{$ID | printf "%#x"}}
|
|
{{end}}
|
|
// Serverbound
|
|
{{range $ID, $Name := .Login.Serverbound}} {{$Name}} = {{$ID | printf "%#x"}}
|
|
{{end}}
|
|
)
|
|
|
|
// Ping state
|
|
const (
|
|
// Clientbound
|
|
{{range $ID, $Name := .Status.Clientbound}} {{$Name}} = {{$ID | printf "%#x"}}
|
|
{{end}}
|
|
// Serverbound
|
|
{{range $ID, $Name := .Status.Serverbound}} {{$Name}} = {{$ID | printf "%#x"}}
|
|
{{end}}
|
|
)
|
|
|
|
// Play state
|
|
const (
|
|
// Clientbound
|
|
{{range $ID, $Name := .Play.Clientbound}} {{$Name}} = {{$ID | printf "%#x"}}
|
|
{{end}}
|
|
// Serverbound
|
|
{{range $ID, $Name := .Play.Serverbound}} {{$Name}} = {{$ID | printf "%#x"}}
|
|
{{end}}
|
|
)
|
|
`
|
|
)
|
|
|
|
// unnest is a utility function to unpack a value from a nested map, given
|
|
// an arbitrary set of keys to reach through.
|
|
func unnest(input map[string]interface{}, keys ...string) (map[string]interface{}, error) {
|
|
for _, k := range keys {
|
|
sub, ok := input[k]
|
|
if !ok {
|
|
return nil, fmt.Errorf("key %q not found", k)
|
|
}
|
|
next, ok := sub.(map[string]interface{})
|
|
if !ok {
|
|
return nil, fmt.Errorf("key %q was %T, expected a string map", k, sub)
|
|
}
|
|
input = next
|
|
}
|
|
return input, nil
|
|
}
|
|
|
|
type duplexMappings struct {
|
|
Clientbound map[int32]string
|
|
Serverbound map[int32]string
|
|
}
|
|
|
|
func (m *duplexMappings) EnsureUniqueNames() {
|
|
// Assemble a slice of keys to check across both maps, because we cannot
|
|
// mutate a map while iterating it.
|
|
clientBounds := make(map[string]int32)
|
|
for sk, sv := range m.Clientbound {
|
|
clientBounds[sv] = sk
|
|
}
|
|
for sk, sv := range m.Serverbound {
|
|
if ck, ok := clientBounds[sv]; ok {
|
|
m.Clientbound[ck] = sv + "Clientbound"
|
|
m.Serverbound[sk] = sv + "Serverbound"
|
|
}
|
|
}
|
|
}
|
|
|
|
// unpackMapping returns the set of packet IDs and their names for a given
|
|
// game state.
|
|
func unpackMapping(data map[string]interface{}, gameState string) (duplexMappings, error) {
|
|
out := duplexMappings{
|
|
Clientbound: make(map[int32]string),
|
|
Serverbound: make(map[int32]string),
|
|
}
|
|
|
|
info, err := unnest(data, gameState, "toClient", "types")
|
|
if err != nil {
|
|
return duplexMappings{}, err
|
|
}
|
|
pType := info["packet"].([]interface{})[1].([]interface{})[0].(map[string]interface{})["type"]
|
|
mappings := pType.([]interface{})[1].(map[string]interface{})["mappings"].(map[string]interface{})
|
|
for k, v := range mappings {
|
|
out.Clientbound[mustAtoi(k)] = strcase.ToCamel(v.(string))
|
|
}
|
|
info, err = unnest(data, gameState, "toServer", "types")
|
|
if err != nil {
|
|
return duplexMappings{}, err
|
|
}
|
|
pType = info["packet"].([]interface{})[1].([]interface{})[0].(map[string]interface{})["type"]
|
|
mappings = pType.([]interface{})[1].(map[string]interface{})["mappings"].(map[string]interface{})
|
|
for k, v := range mappings {
|
|
out.Serverbound[mustAtoi(k)] = strcase.ToCamel(v.(string))
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func mustAtoi(num string) int32 {
|
|
if n, err := strconv.ParseInt(num, 0, 32); err != nil {
|
|
panic(err)
|
|
} else {
|
|
return int32(n)
|
|
}
|
|
}
|
|
|
|
type protocolIDs struct {
|
|
Login duplexMappings
|
|
Play duplexMappings
|
|
Status duplexMappings
|
|
// Handshake state has no packets
|
|
}
|
|
|
|
func downloadInfo() (*protocolIDs, error) {
|
|
resp, err := http.Get(protocolURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
var data map[string]interface{}
|
|
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var out protocolIDs
|
|
if out.Login, err = unpackMapping(data, "login"); err != nil {
|
|
return nil, fmt.Errorf("login: %v", err)
|
|
}
|
|
out.Login.EnsureUniqueNames()
|
|
if out.Play, err = unpackMapping(data, "play"); err != nil {
|
|
return nil, fmt.Errorf("play: %v", err)
|
|
}
|
|
out.Play.EnsureUniqueNames()
|
|
if out.Status, err = unpackMapping(data, "status"); err != nil {
|
|
return nil, fmt.Errorf("play: %v", err)
|
|
}
|
|
out.Status.EnsureUniqueNames()
|
|
|
|
return &out, nil
|
|
}
|
|
|
|
//go:generate go run $GOFILE
|
|
//go:generate go fmt ../packetid.go
|
|
func main() {
|
|
pIDs, err := downloadInfo()
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
f, err := os.Create("../packetid.go")
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
defer f.Close()
|
|
|
|
tmpl := template.Must(template.New("packetIDs").Parse(packetidTmpl))
|
|
if err := tmpl.Execute(f, pIDs); err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|