Implement generator and type for packet IDs
This commit is contained in:
196
data/gen_packetIDs.go
Normal file
196
data/gen_packetIDs.go
Normal file
@ -0,0 +1,196 @@
|
||||
// gen_packetIDs.go generates the enumeration of packet IDs used on the wire.
|
||||
|
||||
//+build ignore
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/iancoleman/strcase"
|
||||
)
|
||||
|
||||
const (
|
||||
protocolURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/1.16.2/protocol.json"
|
||||
)
|
||||
|
||||
// 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[string]string
|
||||
Serverbound map[string]string
|
||||
}
|
||||
|
||||
func (m *duplexMappings) EnsureUniqueNames() {
|
||||
// Assemble a slice of keys to check across both maps, because we cannot
|
||||
// mutate a map while iterating it.
|
||||
clientKeys := make([]string, 0, len(m.Clientbound))
|
||||
for k, _ := range m.Clientbound {
|
||||
clientKeys = append(clientKeys, k)
|
||||
}
|
||||
|
||||
for _, k := range clientKeys {
|
||||
if _, alsoServerKey := m.Serverbound[k]; alsoServerKey {
|
||||
cVal, sVal := m.Clientbound[k], m.Serverbound[k]
|
||||
delete(m.Clientbound, k)
|
||||
delete(m.Serverbound, k)
|
||||
m.Clientbound[k+"Clientbound"] = cVal
|
||||
m.Serverbound[k+"Serverbound"] = sVal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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[string]string),
|
||||
Serverbound: make(map[string]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[strcase.ToCamel(v.(string))] = k
|
||||
}
|
||||
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[strcase.ToCamel(v.(string))] = k
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type protocolIDs struct {
|
||||
Login duplexMappings
|
||||
Play duplexMappings
|
||||
Status duplexMappings
|
||||
// Handshake state has no packets
|
||||
}
|
||||
|
||||
func (p protocolIDs) MaxLen() int {
|
||||
var max int
|
||||
for _, m := range []duplexMappings{p.Login, p.Play, p.Status} {
|
||||
for k, _ := range m.Clientbound {
|
||||
if len(k) > max {
|
||||
max = len(k)
|
||||
}
|
||||
}
|
||||
for k, _ := range m.Serverbound {
|
||||
if len(k) > max {
|
||||
max = len(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func main() {
|
||||
pIDs, err := downloadInfo()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
maxLen := pIDs.MaxLen()
|
||||
|
||||
fmt.Println("package data")
|
||||
fmt.Println()
|
||||
fmt.Println("//go:generate bash -c \"go run gen_packetIDs.go > packetIDs.go\"")
|
||||
fmt.Println()
|
||||
fmt.Println("// This file is automatically generated by gen_packetIDs.go. DO NOT EDIT.")
|
||||
fmt.Println()
|
||||
fmt.Println("// PktID represents a packet ID used in the minecraft protocol.")
|
||||
fmt.Println("type PktID int32")
|
||||
fmt.Println()
|
||||
fmt.Println("// Valid PktID values.")
|
||||
fmt.Println("const (")
|
||||
|
||||
fmt.Println(" // Clientbound packets for connections in the login state.")
|
||||
for k, v := range pIDs.Login.Clientbound {
|
||||
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||
}
|
||||
fmt.Println(" // Serverbound packets for connections in the login state.")
|
||||
for k, v := range pIDs.Login.Serverbound {
|
||||
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println(" // Clientbound packets for connections in the play state.")
|
||||
for k, v := range pIDs.Play.Clientbound {
|
||||
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||
}
|
||||
fmt.Println(" // Serverbound packets for connections in the play state.")
|
||||
for k, v := range pIDs.Play.Serverbound {
|
||||
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println(" // Clientbound packets used to respond to ping/status requests.")
|
||||
for k, v := range pIDs.Status.Clientbound {
|
||||
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||
}
|
||||
fmt.Println(" // Serverbound packets used to ping or read server status.")
|
||||
for k, v := range pIDs.Status.Serverbound {
|
||||
fmt.Printf(" %s%s PktID = %s\n", k, strings.Repeat(" ", maxLen-len(k)), v)
|
||||
}
|
||||
fmt.Println()
|
||||
|
||||
fmt.Println(")")
|
||||
}
|
Reference in New Issue
Block a user