You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

535 lines
13 KiB
Go

package main
import (
"bytes"
"flag"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"text/template"
)
var (
messageTypes = make(map[string]bool)
enumTypes = make(map[string]bool)
entityTypes = make(typeMap)
packetTypes = make(typeMap)
cmdEnumType = "EDemoCommands"
entityEnumTypes = map[string]bool{
"NET_Messages": true,
"SVC_Messages": true,
"EBaseUserMessages": true,
"EBaseEntityMessages": true,
"EBaseGameEvents": true,
"EDotaUserMessages": true,
"ETEProtobufIds": true,
}
prefixes = map[string]string{
"EDemoCommands_DEM_": "CDemo",
"NET_Messages_net_": "CNETMsg_",
"SVC_Messages_svc_": "CSVCMsg_",
"EBaseUserMessages_UM_": "CUserMessage",
"EBaseEntityMessages_EM_": "CEntityMessage",
"EBaseGameEvents_GE_": "CMsg",
"EDotaUserMessages_DOTA_UM_": "CDOTAUserMsg_",
}
specials = map[string]string{
"EDotaUserMessages_DOTA_UM_StatsHeroDetails": "CDOTAUserMsg_StatsHeroMinuteDetails",
"EDotaUserMessages_DOTA_UM_CombatLogDataHLTV": "CMsgDOTACombatLogEntry",
"EDotaUserMessages_DOTA_UM_TournamentDrop": "CMsgGCToClientTournamentItemDrop",
"EDotaUserMessages_DOTA_UM_MatchMetadata": "CDOTAClientMsg_MatchMetadata",
"ETEProtobufIds_TE_EffectDispatchId": "CMsgTEEffectDispatch",
"EDemoCommands_DEM_SignonPacket": "CDemoPacket",
}
pooled = map[string]bool{
"CDemoPacket": true,
"CMsgDOTACombatLogEntry": true,
"CDOTAUserMsg_ParticleManager": true,
"CMsgSosStartSoundEvent": true,
"CDOTAUserMsg_SpectatorPlayerUnitOrders": true,
"CNETMsg_Tick": true,
"CDOTAUserMsg_SpectatorPlayerClick": true,
"CDOTAUserMsg_TE_UnitAnimationEnd": true,
"CSVCMsg_PacketEntities": true,
"CDOTAUserMsg_TE_Projectile": true,
"CDOTAUserMsg_TE_UnitAnimation": true,
"CSVCMsg_UpdateStringTable": true,
"CMsgSosSetSoundEventParams": true,
"CDOTAUserMsg_UnitEvent": true,
"CDOTAUserMsg_TE_ProjectileLoc": true,
"CDOTAUserMsg_OverheadEvent": true,
"CDOTAUserMsg_LocationPing": true,
"CDOTAUserMsg_TE_DotaBloodImpact": true,
"CDOTAUserMsg_SharedCooldown": true,
"CDOTAUserMsg_ChatEvent": true,
}
// EBaseUserMessages_UM_HandHapticPulse
tpl = `package main
////////////////////////////////////////////////////////////////////////////////
//
// .aMMMb .aMMMb dMMMMb dMMMMMP
// dMP"VMP dMP"dMP dMP VMP dMP
// dMP dMP dMP dMP dMP dMMMP
// dMP.aMP dMP.aMP dMP.aMP dMP
// VMMMP" VMMMP" dMMMMP" dMMMMMP
//
// .aMMMMP dMMMMMP dMMMMb dMMMMMP dMMMMb .aMMMb dMMMMMMP dMMMMMP dMMMMb
// dMP" dMP dMP dMP dMP dMP.dMP dMP"dMP dMP dMP dMP VMP
// dMP MMP"dMMMP dMP dMP dMMMP dMMMMK" dMMMMMP dMP dMMMP dMP dMP
// dMP.dMP dMP dMP dMP dMP dMP"AMF dMP dMP dMP dMP dMP.aMP
// VMMMP" dMMMMMP dMP dMP dMMMMMP dMP dMP dMP dMP dMP dMMMMMP dMMMMP"
//
//
// This code was generated by a code-generation program. It was NOT written by
// hand. Do not edit this file by hand! Your edits will be destroyed!
//
// This file can be regenerated by running "go generate"
//
// The generator program is defined in "gen/main.go"
//
////////////////////////////////////////////////////////////////////////////////
import (
"fmt"
"sync"
"github.com/golang/protobuf/proto"
"github.com/jordanorelli/hyperstone/dota"
)
type packetType int32
type entityType int32
const (
EDemoCommands_DEM_Error packetType = -1
{{- range $id, $spec := .Packets }}
{{$spec.EnumName}} packetType = {{$id}}
{{- end }}
{{- range $id, $spec := .Entities }}
{{$spec.EnumName}} entityType = {{$id}}
{{- end }}
)
func (t packetType) String() string {
switch t {
{{- range $id, $spec := .Packets }}
case {{$spec.EnumName}}:
return "{{$spec.EnumName}}"
{{- end }}
default:
return fmt.Sprintf("UnknownPacketType_%d", t)
}
}
func (t entityType) String() string {
switch t {
{{- range $id, $spec := .Entities }}
case {{$spec.EnumName}}:
return "{{$spec.EnumName}}"
{{- end }}
default:
return fmt.Sprintf("UnknownEntityType_%d", t)
}
}
type messageStatus int
const (
m_Unknown messageStatus = iota
m_Skipped
)
func (m messageStatus) Error() string {
switch m {
case m_Unknown:
return "unknown message type"
case m_Skipped:
return "skipped message type"
default:
return "unknown message error"
}
}
type packetFactory map[packetType]func() proto.Message
type entityFactory map[entityType]func() proto.Message
type messageFactory struct {
packets packetFactory
entities entityFactory
}
func (m *messageFactory) BuildPacket(id packetType) (proto.Message, error) {
fn, ok := m.packets[id]
if !ok {
return nil, m_Unknown
}
return fn(), nil
}
func (m *messageFactory) BuildEntity(id entityType) (proto.Message, error) {
fn, ok := m.entities[id]
if !ok {
return nil, m_Unknown
}
return fn(), nil
}
func (m *messageFactory) Return(msg proto.Message) {
switch msg.(type) {
{{- range $name, $ok := .Pooled }}
case *dota.{{$name}}:
p_{{$name}}.Put(msg)
{{- end}}
}
}
var (
{{- range $name, $ok := .Pooled }}
p_{{$name}} = &sync.Pool{New: func() interface{} { return new(dota.{{$name}}) }}
{{- end }}
)
type packetWhitelist map[packetType]bool
type entityWhitelist map[entityType]bool
var allPackets = packetWhitelist{
{{- range $id, $spec := .Packets }}
{{$spec.EnumName}}: true,
{{- end }}
}
var allEntities = entityWhitelist{
{{- range $id, $spec := .Entities }}
{{$spec.EnumName}}: true,
{{- end }}
}
var netEntities = entityWhitelist{
{{- range $id, $spec := .Entities }}
{{- if eq $spec.EnumType "NET_Messages" }}
{{$spec.EnumName}}: true,
{{- end }}
{{- end}}
}
var svcEntities = entityWhitelist{
{{- range $id, $spec := .Entities }}
{{- if eq $spec.EnumType "SVC_Messages" }}
{{$spec.EnumName}}: true,
{{- end }}
{{- end}}
}
var baseUserEntities = entityWhitelist{
{{- range $id, $spec := .Entities }}
{{- if eq $spec.EnumType "EBaseUserMessages" }}
{{$spec.EnumName}}: true,
{{- end }}
{{- end}}
}
// ffffffffffffffff kkkkkkkk
// f::::::::::::::::f k::::::k
// f::::::::::::::::::f k::::::k
// f::::::fffffff:::::f k::::::k
// f:::::f ffffffuuuuuu uuuuuu cccccccccccccccc k:::::k kkkkkkk
// f:::::f u::::u u::::u cc:::::::::::::::c k:::::k k:::::k
// f:::::::ffffff u::::u u::::u c:::::::::::::::::c k:::::k k:::::k
// f::::::::::::f u::::u u::::u c:::::::cccccc:::::c k:::::k k:::::k
// f::::::::::::f u::::u u::::u c::::::c ccccccc k::::::k:::::k
// f:::::::ffffff u::::u u::::u c:::::c k:::::::::::k
// f:::::f u::::u u::::u c:::::c k:::::::::::k
// f:::::f u:::::uuuu:::::u c::::::c ccccccc k::::::k:::::k
// f:::::::f u:::::::::::::::uuc:::::::cccccc:::::ck::::::k k:::::k
// f:::::::f u:::::::::::::::u c:::::::::::::::::ck::::::k k:::::k
// f:::::::f uu::::::::uu:::u cc:::::::::::::::ck::::::k k:::::k
// fffffffff uuuuuuuu uuuu cccccccccccccccckkkkkkkk kkkkkkk
var entityEntities = entityWhitelist{
{{- range $id, $spec := .Entities }}
{{- if eq $spec.EnumType "EBaseEntityMessages" }}
{{$spec.EnumName}}: true,
{{- end }}
{{- end}}
}
var gameEntities = entityWhitelist{
{{- range $id, $spec := .Entities }}
{{- if eq $spec.EnumType "EBaseGameEvents" }}
{{$spec.EnumName}}: true,
{{- end }}
{{- end}}
}
var dotaUserEntities = entityWhitelist{
{{- range $id, $spec := .Entities }}
{{- if eq $spec.EnumType "EDotaUserMessages" }}
{{$spec.EnumName}}: true,
{{- end }}
{{- end}}
}
var eteEntities = entityWhitelist{
{{- range $id, $spec := .Entities }}
{{- if eq $spec.EnumType "ETEProtobufIds" }}
{{$spec.EnumName}}: true,
{{- end }}
{{- end}}
}
var messages = messageFactory{
packetFactory{
{{- range $id, $spec := .Packets }}
{{$spec.EnumName}}: {{$spec.CreateFn}},
{{- end }}
},
entityFactory{
{{- range $id, $spec := .Entities }}
{{$spec.EnumName}}: {{$spec.CreateFn}},
{{- end }}
},
}
`
)
type messageSpec struct {
EnumType string
EnumName string
EnumValue string
TypeName string
}
func (m messageSpec) CreateFn() string {
if m.Pooled() {
return fmt.Sprintf("func() proto.Message { return %s.Get().(*dota.%s) }", m.PoolName(), m.TypeName)
}
return m.New()
}
func (m messageSpec) New() string {
return fmt.Sprintf("func() proto.Message { return new(dota.%s) }", m.TypeName)
}
func (m messageSpec) Pooled() bool {
return pooled[m.TypeName]
}
func (m messageSpec) PoolName() string {
return fmt.Sprintf("p_%s", m.TypeName)
}
func (m messageSpec) IsCmd() bool {
return m.EnumType == cmdEnumType
}
type typeMap map[int]messageSpec
func (m typeMap) fillTypeNames() {
for id, spec := range m {
spec.TypeName = typeName(spec.EnumName)
if spec.TypeName == "" {
delete(m, id)
} else {
m[id] = spec
}
}
}
func ensureNewline(t string) string {
if strings.HasSuffix(t, "\n") {
return t
}
return t + "\n"
}
func bail(status int, t string, args ...interface{}) {
var out io.Writer
if status == 0 {
out = os.Stdout
} else {
out = os.Stderr
}
fmt.Fprintf(out, ensureNewline(t), args...)
os.Exit(status)
}
// processes a single value specification
func processValueSpec(spec *ast.ValueSpec) {
if spec.Type == nil {
return
}
t, ok := spec.Type.(*ast.Ident)
if !ok {
return
}
var isEntityType bool
switch {
case t.Name == cmdEnumType:
// it's a message type
case entityEnumTypes[t.Name]:
isEntityType = true
default:
return
}
for i, name := range spec.Names {
if name.Name == "_" {
continue
}
valExpr := spec.Values[i]
litExpr, ok := valExpr.(*ast.BasicLit)
if !ok {
continue
}
if litExpr.Kind != token.INT {
continue
}
n, err := strconv.Atoi(litExpr.Value)
if err != nil {
continue
}
if isEntityType {
entityTypes[n] = messageSpec{EnumName: name.Name, EnumType: t.Name}
} else {
packetTypes[n] = messageSpec{EnumName: name.Name, EnumType: t.Name}
}
}
}
// processes a single type specification from the Go source
// e.g.:
// type Thing int32
// type Message struct {
// ...
// }
func processTypeSpec(spec *ast.TypeSpec) {
switch tt := spec.Type.(type) {
case *ast.StructType:
// the only structes that are defined in our generated proto code are
// protobuf message types
messageTypes[spec.Name.Name] = true
case *ast.Ident:
switch tt.Name {
case "int32":
// all protobuf enums generate int32s in Go
enumTypes[spec.Name.Name] = true
}
}
}
// processes one specification from the Go source
func processSpec(spec ast.Spec) {
switch t := spec.(type) {
case *ast.ValueSpec:
processValueSpec(t)
case *ast.TypeSpec:
processTypeSpec(t)
}
}
// processes one protobuf-generated Go declaration
func processDeclaration(decl ast.Decl) {
d, ok := decl.(*ast.GenDecl)
if !ok {
return
}
for _, spec := range d.Specs {
processSpec(spec)
}
}
// processes one protobuf-generated Go file
func processFile(name string, fi *ast.File) {
for _, decl := range fi.Decls {
processDeclaration(decl)
}
}
// processes a package of protobuf-generated Go
func processPackage(name string, pkg *ast.Package) {
for name, fi := range pkg.Files {
processFile(name, fi)
}
}
// given an enum name, finds the appropriate message type
func typeName(enumName string) string {
if name, ok := specials[enumName]; ok {
return name
}
for prefix, replacement := range prefixes {
if strings.HasPrefix(enumName, prefix) {
candidate := strings.Replace(enumName, prefix, replacement, 1)
if messageTypes[candidate] {
return candidate
}
}
}
return ""
}
func main() {
flag.Parse()
if flag.NArg() != 1 {
bail(1, "gen should get exactly one argument: the directory to operate on")
}
path := flag.Arg(0)
fs := token.NewFileSet()
packages, err := parser.ParseDir(fs, path, nil, 0)
if err != nil {
bail(1, "go parser error: %v", err)
}
for name, pkg := range packages {
processPackage(name, pkg)
}
packetTypes.fillTypeNames()
entityTypes.fillTypeNames()
var ctx = struct {
Packets typeMap
Entities typeMap
Pooled map[string]bool
}{
Packets: packetTypes,
Entities: entityTypes,
Pooled: pooled,
}
t, err := template.New("out.go").Parse(tpl)
if err != nil {
bail(1, "bad template: %v", err)
}
buf := bytes.NewBuffer(nil)
if err := t.Execute(buf, ctx); err != nil {
bail(1, "template error: %v", err)
}
source, err := format.Source(buf.Bytes())
if err != nil {
fmt.Println(string(buf.Bytes()))
bail(1, "fmt error: %v", err)
}
if err := ioutil.WriteFile("generated.go", source, 0644); err != nil {
bail(1, "error writing source output: %v", err)
}
}