diff options
Diffstat (limited to 'vendor/github.com/Philipp15b/go-steam/generator')
-rw-r--r-- | vendor/github.com/Philipp15b/go-steam/generator/generator.go | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/vendor/github.com/Philipp15b/go-steam/generator/generator.go b/vendor/github.com/Philipp15b/go-steam/generator/generator.go new file mode 100644 index 00000000..40522de7 --- /dev/null +++ b/vendor/github.com/Philipp15b/go-steam/generator/generator.go @@ -0,0 +1,295 @@ +/* +This program generates the protobuf and SteamLanguage files from the SteamKit data. +*/ +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strings" +) + +var printCommands = false + +func main() { + args := strings.Join(os.Args[1:], " ") + + found := false + if strings.Contains(args, "clean") { + clean() + found = true + } + if strings.Contains(args, "steamlang") { + buildSteamLanguage() + found = true + } + if strings.Contains(args, "proto") { + buildProto() + found = true + } + + if !found { + os.Stderr.WriteString("Invalid target!\nAvailable targets: clean, proto, steamlang\n") + os.Exit(1) + } +} + +func clean() { + print("# Cleaning") + cleanGlob("../protocol/**/*.pb.go") + cleanGlob("../tf2/protocol/**/*.pb.go") + cleanGlob("../dota/protocol/**/*.pb.go") + + os.Remove("../protocol/steamlang/enums.go") + os.Remove("../protocol/steamlang/messages.go") +} + +func cleanGlob(pattern string) { + protos, _ := filepath.Glob(pattern) + for _, proto := range protos { + err := os.Remove(proto) + if err != nil { + panic(err) + } + } +} + +func buildSteamLanguage() { + print("# Building Steam Language") + exePath := "./GoSteamLanguageGenerator/bin/Debug/GoSteamLanguageGenerator.exe" + + if runtime.GOOS != "windows" { + execute("mono", exePath, "./SteamKit", "../protocol/steamlang") + } else { + execute(exePath, "./SteamKit", "../protocol/steamlang") + } + execute("gofmt", "-w", "../protocol/steamlang/enums.go", "../protocol/steamlang/messages.go") +} + +func buildProto() { + print("# Building Protobufs") + + buildProtoMap("steamclient", clientProtoFiles, "../protocol/protobuf") + buildProtoMap("tf", tf2ProtoFiles, "../tf2/protocol/protobuf") + buildProtoMap("dota", dotaProtoFiles, "../dota/protocol/protobuf") +} + +func buildProtoMap(srcSubdir string, files map[string]string, outDir string) { + os.MkdirAll(outDir, os.ModePerm) + for proto, out := range files { + full := filepath.Join(outDir, out) + compileProto("SteamKit/Resources/Protobufs", srcSubdir, proto, full) + fixProto(full) + } +} + +// Maps the proto files to their target files. +// See `SteamKit/Resources/Protobufs/steamclient/generate-base.bat` for reference. +var clientProtoFiles = map[string]string{ + "steammessages_base.proto": "base.pb.go", + "encrypted_app_ticket.proto": "app_ticket.pb.go", + + "steammessages_clientserver.proto": "client_server.pb.go", + "steammessages_clientserver_2.proto": "client_server_2.pb.go", + + "content_manifest.proto": "content_manifest.pb.go", + + "steammessages_unified_base.steamclient.proto": "unified/base.pb.go", + "steammessages_cloud.steamclient.proto": "unified/cloud.pb.go", + "steammessages_credentials.steamclient.proto": "unified/credentials.pb.go", + "steammessages_deviceauth.steamclient.proto": "unified/deviceauth.pb.go", + "steammessages_gamenotifications.steamclient.proto": "unified/gamenotifications.pb.go", + "steammessages_offline.steamclient.proto": "unified/offline.pb.go", + "steammessages_parental.steamclient.proto": "unified/parental.pb.go", + "steammessages_partnerapps.steamclient.proto": "unified/partnerapps.pb.go", + "steammessages_player.steamclient.proto": "unified/player.pb.go", + "steammessages_publishedfile.steamclient.proto": "unified/publishedfile.pb.go", +} + +var tf2ProtoFiles = map[string]string{ + "base_gcmessages.proto": "base.pb.go", + "econ_gcmessages.proto": "econ.pb.go", + "gcsdk_gcmessages.proto": "gcsdk.pb.go", + "tf_gcmessages.proto": "tf.pb.go", + "gcsystemmsgs.proto": "system.pb.go", +} + +var dotaProtoFiles = map[string]string{ + "base_gcmessages.proto": "base.pb.go", + "econ_gcmessages.proto": "econ.pb.go", + "gcsdk_gcmessages.proto": "gcsdk.pb.go", + "dota_gcmessages_common.proto": "dota_common.pb.go", + "dota_gcmessages_client.proto": "dota_client.pb.go", + "dota_gcmessages_client_fantasy.proto": "dota_client_fantasy.pb.go", + "gcsystemmsgs.proto": "system.pb.go", +} + +func compileProto(srcBase, srcSubdir, proto, target string) { + outDir, _ := filepath.Split(target) + err := os.MkdirAll(outDir, os.ModePerm) + if err != nil { + panic(err) + } + execute("protoc", "--go_out="+outDir, "-I="+srcBase+"/"+srcSubdir, "-I="+srcBase, filepath.Join(srcBase, srcSubdir, proto)) + out := strings.Replace(filepath.Join(outDir, proto), ".proto", ".pb.go", 1) + err = forceRename(out, target) + if err != nil { + panic(err) + } +} + +func forceRename(from, to string) error { + if from != to { + os.Remove(to) + } + return os.Rename(from, to) +} + +var pkgRegex = regexp.MustCompile(`(package \w+)`) +var pkgCommentRegex = regexp.MustCompile(`(?s)(\/\*.*?\*\/\n)package`) +var unusedImportCommentRegex = regexp.MustCompile("// discarding unused import .*\n") +var fileDescriptorVarRegex = regexp.MustCompile(`fileDescriptor\d+`) + +func fixProto(path string) { + // goprotobuf is really bad at dependencies, so we must fix them manually... + // It tries to load each dependency of a file as a seperate package (but in a very, very wrong way). + // Because we want some files in the same package, we'll remove those imports to local files. + + file, err := ioutil.ReadFile(path) + if err != nil { + panic(err) + } + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, path, file, parser.ImportsOnly) + if err != nil { + panic("Error parsing " + path + ": " + err.Error()) + } + + importsToRemove := make([]*ast.ImportSpec, 0) + for _, i := range f.Imports { + // We remove all local imports + if i.Path.Value == "\".\"" { + importsToRemove = append(importsToRemove, i) + } + } + + for _, itr := range importsToRemove { + // remove the package name from all types + file = bytes.Replace(file, []byte(itr.Name.Name+"."), []byte{}, -1) + // and remove the import itself + file = bytes.Replace(file, []byte(fmt.Sprintf("import %v %v\n", itr.Name.Name, itr.Path.Value)), []byte{}, -1) + } + + // remove the package comment because it just includes a list of all messages and + // collides not only with the other compiled protobuf files, but also our own documentation. + file = cutAllSubmatch(pkgCommentRegex, file, 1) + + // remove warnings + file = unusedImportCommentRegex.ReplaceAllLiteral(file, []byte{}) + + // fix the package name + file = pkgRegex.ReplaceAll(file, []byte("package "+inferPackageName(path))) + + // fix the google dependency; + // we just reuse the one from protoc-gen-go + file = bytes.Replace(file, []byte("google/protobuf"), []byte("github.com/golang/protobuf/protoc-gen-go/descriptor"), -1) + + // we need to prefix local variables created by protoc-gen-go so that they don't clash with others in the same package + filename := strings.Split(filepath.Base(path), ".")[0] + file = fileDescriptorVarRegex.ReplaceAllFunc(file, func(match []byte) []byte { + return []byte(filename + "_" + string(match)) + }) + + err = ioutil.WriteFile(path, file, os.ModePerm) + if err != nil { + panic(err) + } +} + +func inferPackageName(path string) string { + pieces := strings.Split(path, string(filepath.Separator)) + return pieces[len(pieces)-2] +} + +func cutAllSubmatch(r *regexp.Regexp, b []byte, n int) []byte { + i := r.FindSubmatchIndex(b) + return bytesCut(b, i[2*n], i[2*n+1]) +} + +// Removes the given section from the byte array +func bytesCut(b []byte, from, to int) []byte { + buf := new(bytes.Buffer) + buf.Write(b[:from]) + buf.Write(b[to:]) + return buf.Bytes() +} + +func print(text string) { os.Stdout.WriteString(text + "\n") } + +func printerr(text string) { os.Stderr.WriteString(text + "\n") } + +// This writer appends a "> " after every newline so that the outpout appears quoted. +type QuotedWriter struct { + w io.Writer + started bool +} + +func NewQuotedWriter(w io.Writer) *QuotedWriter { + return &QuotedWriter{w, false} +} + +func (w *QuotedWriter) Write(p []byte) (n int, err error) { + if !w.started { + _, err = w.w.Write([]byte("> ")) + if err != nil { + return n, err + } + w.started = true + } + + for i, c := range p { + if c == '\n' { + nw, err := w.w.Write(p[n : i+1]) + n += nw + if err != nil { + return n, err + } + + _, err = w.w.Write([]byte("> ")) + if err != nil { + return n, err + } + } + } + if n != len(p) { + nw, err := w.w.Write(p[n:len(p)]) + n += nw + return n, err + } + return +} + +func execute(command string, args ...string) { + if printCommands { + print(command + " " + strings.Join(args, " ")) + } + cmd := exec.Command(command, args...) + cmd.Stdout = NewQuotedWriter(os.Stdout) + cmd.Stderr = NewQuotedWriter(os.Stderr) + err := cmd.Run() + if err != nil { + printerr(err.Error()) + os.Exit(1) + } +} |