822 lines
23 KiB
Go
822 lines
23 KiB
Go
//go:build ignore
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
// XMLDefinition represents the root element containing structs
|
|
type XMLDefinition struct {
|
|
XMLName xml.Name `xml:"structs"`
|
|
Structs []XMLStruct `xml:"struct"`
|
|
}
|
|
|
|
// XMLStruct represents a packet structure definition
|
|
type XMLStruct struct {
|
|
Name string `xml:"name,attr"`
|
|
ClientVersion string `xml:"clientVersion,attr"`
|
|
OpcodeName string `xml:"opcodeName,attr"`
|
|
Fields []XMLData `xml:"data"`
|
|
}
|
|
|
|
// XMLData represents a field in the packet structure
|
|
type XMLData struct {
|
|
Name string `xml:"name,attr"`
|
|
Type string `xml:"type,attr"`
|
|
Size int `xml:"size,attr"`
|
|
ArraySizeVariable string `xml:"arraySizeVariable,attr"`
|
|
IfVarSet string `xml:"ifVarSet,attr"`
|
|
IfVarNotSet string `xml:"ifVarNotSet,attr"`
|
|
OversizedValue int `xml:"oversizedValue,attr"`
|
|
Children []XMLData `xml:"data"` // For nested array elements
|
|
}
|
|
|
|
// TypeMapping maps XML types to Go types
|
|
var TypeMapping = map[string]string{
|
|
"int8": "int8",
|
|
"int16": "int16",
|
|
"int32": "int32",
|
|
"int64": "int64",
|
|
"uint8": "uint8",
|
|
"uint16": "uint16",
|
|
"uint32": "uint32",
|
|
"uint64": "uint64",
|
|
"i8": "int8",
|
|
"i16": "int16",
|
|
"i32": "int32",
|
|
"i64": "int64",
|
|
"u8": "uint8",
|
|
"u16": "uint16",
|
|
"u32": "uint32",
|
|
"u64": "uint64",
|
|
"float": "float32",
|
|
"double": "float64",
|
|
"str8": "string",
|
|
"str16": "string",
|
|
"str32": "string",
|
|
"EQ2_32Bit_String": "string",
|
|
"EQ2_8Bit_String": "string",
|
|
"EQ2_16Bit_String": "string",
|
|
"char": "byte",
|
|
"color": "types.Color", // RGB color as 32-bit value
|
|
"equipmentItem": "types.EquipmentItem", // Custom type
|
|
"Array": "array", // Capital A variant
|
|
}
|
|
|
|
// GoStruct represents the generated Go struct
|
|
type GoStruct struct {
|
|
Name string
|
|
ClientVersion string
|
|
OpcodeName string
|
|
Fields []GoField
|
|
PackageName string
|
|
}
|
|
|
|
// GoField represents a field in the Go struct
|
|
type GoField struct {
|
|
Name string
|
|
GoName string
|
|
Type string
|
|
IsArray bool
|
|
IsDynamicArray bool
|
|
ArraySizeVariable string
|
|
Size int
|
|
Tag string
|
|
Comment string
|
|
IfVarSet string
|
|
IfVarNotSet string
|
|
ArrayElements []GoField // For complex array elements (anonymous struct)
|
|
}
|
|
|
|
// GenerateOutput holds the generated code
|
|
type GenerateOutput struct {
|
|
SourceFile string
|
|
PackageName string
|
|
Structs []GoStruct
|
|
NeedsMath bool
|
|
}
|
|
|
|
// parseXMLFile parses an XML definition file
|
|
func parseXMLFile(filename string) ([]XMLStruct, error) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
|
|
data, err := io.ReadAll(file)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Wrap content in root element if needed
|
|
content := string(data)
|
|
if !strings.HasPrefix(strings.TrimSpace(content), "<?xml") && !strings.HasPrefix(strings.TrimSpace(content), "<structs>") {
|
|
content = "<structs>" + content + "</structs>"
|
|
}
|
|
|
|
var def XMLDefinition
|
|
if err := xml.Unmarshal([]byte(content), &def); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return def.Structs, nil
|
|
}
|
|
|
|
// toGoName converts snake_case to CamelCase
|
|
func toGoName(name string) string {
|
|
parts := strings.Split(name, "_")
|
|
for i, part := range parts {
|
|
if len(part) > 0 {
|
|
parts[i] = strings.ToUpper(part[:1]) + part[1:]
|
|
}
|
|
}
|
|
return strings.Join(parts, "")
|
|
}
|
|
|
|
// mapSimpleType maps XML type to Go type
|
|
func mapSimpleType(xmlType string) (string, bool) {
|
|
goType, ok := TypeMapping[xmlType]
|
|
if !ok {
|
|
return "byte", false
|
|
}
|
|
return goType, true
|
|
}
|
|
|
|
// convertArrayChildren converts nested array data to Go fields
|
|
func convertArrayChildren(children []XMLData) []GoField {
|
|
fields := make([]GoField, 0, len(children))
|
|
|
|
for _, child := range children {
|
|
if child.Type == "array" && len(child.Children) > 0 {
|
|
// Nested array - recursive
|
|
nestedFields := convertArrayChildren(child.Children)
|
|
goField := GoField{
|
|
Name: child.Name,
|
|
GoName: toGoName(child.Name),
|
|
IsDynamicArray: true,
|
|
ArraySizeVariable: child.ArraySizeVariable,
|
|
ArrayElements: nestedFields,
|
|
}
|
|
fields = append(fields, goField)
|
|
} else {
|
|
// Simple field
|
|
goType, _ := mapSimpleType(child.Type)
|
|
isArray := child.Size > 0
|
|
|
|
goField := GoField{
|
|
Name: child.Name,
|
|
GoName: toGoName(child.Name),
|
|
Type: goType,
|
|
IsArray: isArray,
|
|
Size: child.Size,
|
|
}
|
|
|
|
if isArray {
|
|
goField.Type = fmt.Sprintf("[%d]%s", child.Size, goType)
|
|
}
|
|
|
|
fields = append(fields, goField)
|
|
}
|
|
}
|
|
|
|
return fields
|
|
}
|
|
|
|
// buildAnonymousStructType builds the anonymous struct type string
|
|
func buildAnonymousStructType(fields []GoField) string {
|
|
if len(fields) == 0 {
|
|
return "struct{}"
|
|
}
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString("struct {\n")
|
|
for _, field := range fields {
|
|
sb.WriteString("\t\t")
|
|
sb.WriteString(field.GoName)
|
|
sb.WriteString(" ")
|
|
|
|
if field.IsDynamicArray && len(field.ArrayElements) > 0 {
|
|
// Nested array
|
|
sb.WriteString("[]")
|
|
sb.WriteString(buildAnonymousStructType(field.ArrayElements))
|
|
} else if field.IsArray {
|
|
sb.WriteString(field.Type)
|
|
} else {
|
|
sb.WriteString(field.Type)
|
|
}
|
|
|
|
// Add struct tag
|
|
sb.WriteString(" `eq2:\"")
|
|
sb.WriteString(field.Name)
|
|
if field.Type == "string" {
|
|
// Determine string type from original XML
|
|
sb.WriteString(",type:str16") // Default to str16 for now
|
|
}
|
|
if field.IsArray && field.Size > 0 {
|
|
sb.WriteString(fmt.Sprintf(",size:%d", field.Size))
|
|
}
|
|
sb.WriteString("\"`")
|
|
sb.WriteString("\n")
|
|
}
|
|
sb.WriteString("\t}")
|
|
return sb.String()
|
|
}
|
|
|
|
// convertToGoStruct converts XML struct definition to Go struct
|
|
func convertToGoStruct(xmlStruct XMLStruct, packageName string) GoStruct {
|
|
goStruct := GoStruct{
|
|
Name: toGoName(xmlStruct.Name),
|
|
ClientVersion: xmlStruct.ClientVersion,
|
|
OpcodeName: xmlStruct.OpcodeName,
|
|
PackageName: packageName,
|
|
Fields: make([]GoField, 0, len(xmlStruct.Fields)),
|
|
}
|
|
|
|
// Add version suffix if needed
|
|
if xmlStruct.ClientVersion != "" && xmlStruct.ClientVersion != "1" {
|
|
goStruct.Name = fmt.Sprintf("%sV%s", goStruct.Name, xmlStruct.ClientVersion)
|
|
}
|
|
|
|
// Track field names to avoid duplicates
|
|
fieldNames := make(map[string]int)
|
|
|
|
for _, field := range xmlStruct.Fields {
|
|
if field.Type == "array" || field.Type == "Array" {
|
|
// Handle array with nested structure
|
|
if len(field.Children) > 0 {
|
|
arrayElements := convertArrayChildren(field.Children)
|
|
structType := buildAnonymousStructType(arrayElements)
|
|
|
|
// Check for duplicate field names and make unique if needed
|
|
baseName := toGoName(field.Name)
|
|
finalName := baseName
|
|
if count, exists := fieldNames[baseName]; exists {
|
|
finalName = fmt.Sprintf("%s_%d", baseName, count+1)
|
|
fieldNames[baseName] = count + 1
|
|
} else {
|
|
fieldNames[baseName] = 1
|
|
}
|
|
|
|
goField := GoField{
|
|
Name: field.Name,
|
|
GoName: finalName,
|
|
Type: "[]" + structType,
|
|
IsDynamicArray: true,
|
|
ArraySizeVariable: field.ArraySizeVariable,
|
|
ArrayElements: arrayElements,
|
|
IfVarSet: field.IfVarSet,
|
|
IfVarNotSet: field.IfVarNotSet,
|
|
}
|
|
|
|
// Generate struct tag
|
|
tag := fmt.Sprintf("`eq2:\"%s", field.Name)
|
|
if field.ArraySizeVariable != "" {
|
|
tag += fmt.Sprintf(",sizeVar:%s", field.ArraySizeVariable)
|
|
}
|
|
if field.IfVarSet != "" {
|
|
tag += fmt.Sprintf(",ifSet:%s", field.IfVarSet)
|
|
}
|
|
tag += "\"`"
|
|
goField.Tag = tag
|
|
|
|
goStruct.Fields = append(goStruct.Fields, goField)
|
|
} else {
|
|
// Simple array (shouldn't happen but handle it)
|
|
log.Printf("Warning: array field %s has no children", field.Name)
|
|
}
|
|
} else if field.Type == "equipmentItem" {
|
|
// Handle equipment item arrays
|
|
// Check for duplicate field names and make unique if needed
|
|
baseName := toGoName(field.Name)
|
|
finalName := baseName
|
|
if count, exists := fieldNames[baseName]; exists {
|
|
finalName = fmt.Sprintf("%s_%d", baseName, count+1)
|
|
fieldNames[baseName] = count + 1
|
|
} else {
|
|
fieldNames[baseName] = 1
|
|
}
|
|
|
|
goField := GoField{
|
|
Name: field.Name,
|
|
GoName: finalName,
|
|
Type: fmt.Sprintf("[%d]types.EquipmentItem", field.Size),
|
|
IsArray: true,
|
|
Size: field.Size,
|
|
}
|
|
|
|
tag := fmt.Sprintf("`eq2:\"%s,size:%d\"`", field.Name, field.Size)
|
|
goField.Tag = tag
|
|
|
|
goStruct.Fields = append(goStruct.Fields, goField)
|
|
} else {
|
|
// Regular field
|
|
goType, _ := mapSimpleType(field.Type)
|
|
isArray := field.Size > 0
|
|
|
|
// Check for duplicate field names and make unique if needed
|
|
baseName := toGoName(field.Name)
|
|
finalName := baseName
|
|
if count, exists := fieldNames[baseName]; exists {
|
|
finalName = fmt.Sprintf("%s_%d", baseName, count+1)
|
|
fieldNames[baseName] = count + 1
|
|
} else {
|
|
fieldNames[baseName] = 1
|
|
}
|
|
|
|
goField := GoField{
|
|
Name: field.Name,
|
|
GoName: finalName,
|
|
Type: goType,
|
|
IsArray: isArray,
|
|
Size: field.Size,
|
|
IfVarSet: field.IfVarSet,
|
|
IfVarNotSet: field.IfVarNotSet,
|
|
}
|
|
|
|
if isArray {
|
|
goField.Type = fmt.Sprintf("[%d]%s", field.Size, goType)
|
|
}
|
|
|
|
// Generate struct tag
|
|
tag := fmt.Sprintf("`eq2:\"%s", field.Name)
|
|
if field.Type == "str8" || field.Type == "str16" || field.Type == "str32" ||
|
|
field.Type == "EQ2_32Bit_String" || field.Type == "EQ2_16Bit_String" || field.Type == "EQ2_8Bit_String" {
|
|
tag += fmt.Sprintf(",type:%s", field.Type)
|
|
}
|
|
if isArray && field.Size > 0 {
|
|
tag += fmt.Sprintf(",size:%d", field.Size)
|
|
}
|
|
if field.IfVarSet != "" {
|
|
tag += fmt.Sprintf(",ifSet:%s", field.IfVarSet)
|
|
}
|
|
tag += "\"`"
|
|
goField.Tag = tag
|
|
|
|
// Add comment if needed
|
|
if strings.HasPrefix(field.Name, "unknown") || strings.HasPrefix(field.Name, "Unknown") {
|
|
goField.Comment = " // TODO: Identify purpose"
|
|
}
|
|
|
|
goStruct.Fields = append(goStruct.Fields, goField)
|
|
}
|
|
}
|
|
|
|
return goStruct
|
|
}
|
|
|
|
const structTemplate = `// Code generated by codegen. DO NOT EDIT.
|
|
// Source: {{.SourceFile}}
|
|
|
|
package {{.PackageName}}
|
|
|
|
import (
|
|
"encoding/binary"{{if .NeedsMath}}
|
|
"math"{{end}}
|
|
"git.sharkk.net/EQ2/Protocol/types"
|
|
)
|
|
|
|
{{range .Structs}}
|
|
// {{.Name}} represents packet structure for {{if .OpcodeName}}{{.OpcodeName}}{{else}}client version {{.ClientVersion}}{{end}}
|
|
type {{.Name}} struct {
|
|
{{- range .Fields}}
|
|
{{.GoName}} {{.Type}} {{.Tag}}{{if .Comment}} {{.Comment}}{{end}}
|
|
{{- end}}
|
|
}
|
|
|
|
// Serialize writes the packet data to the provided buffer
|
|
func (p *{{.Name}}) Serialize(dest []byte) uint32 {
|
|
offset := uint32(0)
|
|
{{range .Fields}}
|
|
{{- if .IsDynamicArray}}
|
|
// Write {{.GoName}} array (dynamic size)
|
|
for _, elem := range p.{{.GoName}} {
|
|
{{- template "serializeFields" .ArrayElements}}
|
|
}
|
|
{{- else if eq .Type "string"}}
|
|
// Write {{.GoName}} as {{if contains .Tag "str16"}}16-bit{{else if contains .Tag "str32"}}32-bit{{else if contains .Tag "EQ2_32Bit_String"}}32-bit{{else}}8-bit{{end}} length-prefixed string
|
|
{{- if or (contains .Tag "str16") (contains .Tag "EQ2_16Bit_String")}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], uint16(len(p.{{.GoName}})))
|
|
offset += 2
|
|
{{- else if or (contains .Tag "str32") (contains .Tag "EQ2_32Bit_String")}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], uint32(len(p.{{.GoName}})))
|
|
offset += 4
|
|
{{- else}}
|
|
dest[offset] = byte(len(p.{{.GoName}}))
|
|
offset++
|
|
{{- end}}
|
|
copy(dest[offset:], []byte(p.{{.GoName}}))
|
|
offset += uint32(len(p.{{.GoName}}))
|
|
{{- else if .IsArray}}
|
|
// Write {{.GoName}} array
|
|
for i := 0; i < {{.Size}}; i++ {
|
|
{{- if eq (baseType .Type) "float32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(p.{{.GoName}}[i]))
|
|
offset += 4
|
|
{{- else if eq (baseType .Type) "float64"}}
|
|
binary.LittleEndian.PutUint64(dest[offset:], math.Float64bits(p.{{.GoName}}[i]))
|
|
offset += 8
|
|
{{- else if (eq (baseType .Type) "int8")}}
|
|
dest[offset] = byte(p.{{.GoName}}[i])
|
|
offset++
|
|
{{- else if or (eq (baseType .Type) "uint8") (eq (baseType .Type) "byte")}}
|
|
dest[offset] = p.{{.GoName}}[i]
|
|
offset++
|
|
{{- else if or (eq (baseType .Type) "int16") (eq (baseType .Type) "uint16")}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], uint16(p.{{.GoName}}[i]))
|
|
offset += 2
|
|
{{- else if or (eq (baseType .Type) "int32") (eq (baseType .Type) "uint32")}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], uint32(p.{{.GoName}}[i]))
|
|
offset += 4
|
|
{{- else if or (eq (baseType .Type) "int64") (eq (baseType .Type) "uint64")}}
|
|
binary.LittleEndian.PutUint64(dest[offset:], uint64(p.{{.GoName}}[i]))
|
|
offset += 8
|
|
{{- else if eq (baseType .Type) "types.EquipmentItem"}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], p.{{.GoName}}[i].Type)
|
|
offset += 2
|
|
binary.LittleEndian.PutUint32(dest[offset:], p.{{.GoName}}[i].Color.ToUint32())
|
|
offset += 4
|
|
{{- end}}
|
|
}
|
|
{{- else}}
|
|
// Write {{.GoName}}
|
|
{{- if eq .Type "float32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(p.{{.GoName}}))
|
|
offset += 4
|
|
{{- else if eq .Type "float64"}}
|
|
binary.LittleEndian.PutUint64(dest[offset:], math.Float64bits(p.{{.GoName}}))
|
|
offset += 8
|
|
{{- else if or (eq .Type "int8") (eq .Type "uint8") (eq .Type "byte")}}
|
|
dest[offset] = byte(p.{{.GoName}})
|
|
offset++
|
|
{{- else if or (eq .Type "int16") (eq .Type "uint16")}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], uint16(p.{{.GoName}}))
|
|
offset += 2
|
|
{{- else if eq .Type "types.Color"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], p.{{.GoName}}.ToUint32())
|
|
offset += 4
|
|
{{- else if or (eq .Type "int32") (eq .Type "uint32")}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], uint32(p.{{.GoName}}))
|
|
offset += 4
|
|
{{- else if or (eq .Type "int64") (eq .Type "uint64")}}
|
|
binary.LittleEndian.PutUint64(dest[offset:], uint64(p.{{.GoName}}))
|
|
offset += 8
|
|
{{- end}}
|
|
{{- end}}
|
|
{{end}}
|
|
return offset
|
|
}
|
|
|
|
// Size returns the serialized size of the packet
|
|
func (p *{{.Name}}) Size() uint32 {
|
|
size := uint32(0)
|
|
{{range .Fields}}
|
|
{{- if .IsDynamicArray}}
|
|
// Dynamic array: {{.GoName}}
|
|
for _, elem := range p.{{.GoName}} {
|
|
{{- template "sizeFields" .ArrayElements}}
|
|
}
|
|
{{- else if eq .Type "string"}}
|
|
{{- if or (contains .Tag "str16") (contains .Tag "EQ2_16Bit_String")}}
|
|
size += 2 + uint32(len(p.{{.GoName}}))
|
|
{{- else if or (contains .Tag "str32") (contains .Tag "EQ2_32Bit_String")}}
|
|
size += 4 + uint32(len(p.{{.GoName}}))
|
|
{{- else}}
|
|
size += 1 + uint32(len(p.{{.GoName}}))
|
|
{{- end}}
|
|
{{- else if .IsArray}}
|
|
{{- if eq (baseType .Type) "types.EquipmentItem"}}
|
|
size += {{.Size}} * 6
|
|
{{- else if eq (sizeOf (baseType .Type)) 1}}
|
|
size += {{.Size}}
|
|
{{- else}}
|
|
size += {{.Size}} * {{sizeOf (baseType .Type)}}
|
|
{{- end}}
|
|
{{- else}}
|
|
size += {{sizeOf .Type}}
|
|
{{- end}}
|
|
{{end}}
|
|
return size
|
|
}
|
|
{{end}}
|
|
|
|
{{define "serializeFields"}}
|
|
{{- range .}}
|
|
{{- if .IsDynamicArray}}
|
|
// Write nested {{.GoName}} array
|
|
for _, nestedElem := range elem.{{.GoName}} {
|
|
{{- template "serializeNestedFields" .ArrayElements}}
|
|
}
|
|
{{- else if eq .Type "string"}}
|
|
// Write {{.GoName}} string field
|
|
dest[offset] = byte(len(elem.{{.GoName}}))
|
|
offset++
|
|
copy(dest[offset:], []byte(elem.{{.GoName}}))
|
|
offset += uint32(len(elem.{{.GoName}}))
|
|
{{- else if .IsArray}}
|
|
// Write {{.GoName}} array field
|
|
for i := 0; i < {{.Size}}; i++ {
|
|
{{- if eq (baseType .Type) "float32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(elem.{{.GoName}}[i]))
|
|
offset += 4
|
|
{{- else if eq (baseType .Type) "uint32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], elem.{{.GoName}}[i])
|
|
offset += 4
|
|
{{- else if eq (baseType .Type) "uint16"}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], elem.{{.GoName}}[i])
|
|
offset += 2
|
|
{{- else}}
|
|
dest[offset] = byte(elem.{{.GoName}}[i])
|
|
offset++
|
|
{{- end}}
|
|
}
|
|
{{- else if eq .Type "float32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(elem.{{.GoName}}))
|
|
offset += 4
|
|
{{- else if eq .Type "types.Color"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], elem.{{.GoName}}.ToUint32())
|
|
offset += 4
|
|
{{- else if eq .Type "uint32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], elem.{{.GoName}})
|
|
offset += 4
|
|
{{- else if eq .Type "int32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], uint32(elem.{{.GoName}}))
|
|
offset += 4
|
|
{{- else if eq .Type "uint16"}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], elem.{{.GoName}})
|
|
offset += 2
|
|
{{- else if eq .Type "int16"}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], uint16(elem.{{.GoName}}))
|
|
offset += 2
|
|
{{- else if eq .Type "int8"}}
|
|
dest[offset] = byte(elem.{{.GoName}})
|
|
offset++
|
|
{{- else if eq .Type "uint8"}}
|
|
dest[offset] = elem.{{.GoName}}
|
|
offset++
|
|
{{- else}}
|
|
dest[offset] = byte(elem.{{.GoName}})
|
|
offset++
|
|
{{- end}}
|
|
{{- end}}
|
|
{{end}}
|
|
|
|
{{define "sizeFields"}}
|
|
_ = elem // Avoid unused variable warning
|
|
{{- range .}}
|
|
{{- if .IsDynamicArray}}
|
|
// Nested array: {{.GoName}}
|
|
for _, nestedElem := range elem.{{.GoName}} {
|
|
{{- template "sizeNestedFields" .ArrayElements}}
|
|
}
|
|
{{- else if eq .Type "string"}}
|
|
size += 1 + uint32(len(elem.{{.GoName}}))
|
|
{{- else if .IsArray}}
|
|
{{- if eq (sizeOf (baseType .Type)) 1}}
|
|
size += {{.Size}}
|
|
{{- else}}
|
|
size += {{.Size}} * {{sizeOf (baseType .Type)}}
|
|
{{- end}}
|
|
{{- else}}
|
|
size += {{sizeOf .Type}}
|
|
{{- end}}
|
|
{{- end}}
|
|
{{end}}
|
|
|
|
{{define "serializeNestedFields"}}
|
|
{{- range .}}
|
|
{{- if .IsDynamicArray}}
|
|
// Write deeply nested {{.GoName}} array
|
|
for _, deepNested := range nestedElem.{{.GoName}} {
|
|
// TODO: Handle deeper nesting if needed
|
|
_ = deepNested
|
|
}
|
|
{{- else if eq .Type "string"}}
|
|
// Write {{.GoName}} string field
|
|
dest[offset] = byte(len(nestedElem.{{.GoName}}))
|
|
offset++
|
|
copy(dest[offset:], []byte(nestedElem.{{.GoName}}))
|
|
offset += uint32(len(nestedElem.{{.GoName}}))
|
|
{{- else if .IsArray}}
|
|
// Write {{.GoName}} array field
|
|
for i := 0; i < {{.Size}}; i++ {
|
|
{{- if eq (baseType .Type) "float32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(nestedElem.{{.GoName}}[i]))
|
|
offset += 4
|
|
{{- else if eq (baseType .Type) "uint32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], nestedElem.{{.GoName}}[i])
|
|
offset += 4
|
|
{{- else if eq (baseType .Type) "uint16"}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], nestedElem.{{.GoName}}[i])
|
|
offset += 2
|
|
{{- else}}
|
|
dest[offset] = nestedElem.{{.GoName}}[i]
|
|
offset++
|
|
{{- end}}
|
|
}
|
|
{{- else if eq .Type "float32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], math.Float32bits(nestedElem.{{.GoName}}))
|
|
offset += 4
|
|
{{- else if eq .Type "types.Color"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], nestedElem.{{.GoName}}.ToUint32())
|
|
offset += 4
|
|
{{- else if eq .Type "uint32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], nestedElem.{{.GoName}})
|
|
offset += 4
|
|
{{- else if eq .Type "int32"}}
|
|
binary.LittleEndian.PutUint32(dest[offset:], uint32(nestedElem.{{.GoName}}))
|
|
offset += 4
|
|
{{- else if eq .Type "uint16"}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], nestedElem.{{.GoName}})
|
|
offset += 2
|
|
{{- else if eq .Type "int16"}}
|
|
binary.LittleEndian.PutUint16(dest[offset:], uint16(nestedElem.{{.GoName}}))
|
|
offset += 2
|
|
{{- else if eq .Type "int8"}}
|
|
dest[offset] = byte(nestedElem.{{.GoName}})
|
|
offset++
|
|
{{- else if eq .Type "uint8"}}
|
|
dest[offset] = nestedElem.{{.GoName}}
|
|
offset++
|
|
{{- else}}
|
|
dest[offset] = byte(nestedElem.{{.GoName}})
|
|
offset++
|
|
{{- end}}
|
|
{{- end}}
|
|
{{end}}
|
|
|
|
{{define "sizeNestedFields"}}
|
|
_ = nestedElem // Avoid unused variable
|
|
{{- range .}}
|
|
{{- if .IsDynamicArray}}
|
|
// Deeply nested array: {{.GoName}}
|
|
for _, deepNested := range nestedElem.{{.GoName}} {
|
|
// TODO: Handle deeper nesting if needed
|
|
_ = deepNested
|
|
}
|
|
{{- else if eq .Type "string"}}
|
|
size += 1 + uint32(len(nestedElem.{{.GoName}}))
|
|
{{- else if .IsArray}}
|
|
{{- if eq (sizeOf (baseType .Type)) 1}}
|
|
size += {{.Size}}
|
|
{{- else}}
|
|
size += {{.Size}} * {{sizeOf (baseType .Type)}}
|
|
{{- end}}
|
|
{{- else}}
|
|
size += {{sizeOf .Type}}
|
|
{{- end}}
|
|
{{- end}}
|
|
{{end}}
|
|
`
|
|
|
|
func contains(s, substr string) bool {
|
|
return strings.Contains(s, substr)
|
|
}
|
|
|
|
func baseType(arrayType string) string {
|
|
// Extract base type from array declaration like "[10]uint32"
|
|
if strings.HasPrefix(arrayType, "[") {
|
|
idx := strings.Index(arrayType, "]")
|
|
if idx > 0 {
|
|
return arrayType[idx+1:]
|
|
}
|
|
}
|
|
return arrayType
|
|
}
|
|
|
|
func sizeOf(typeName string) int {
|
|
// Return the size in bytes for a given type
|
|
switch typeName {
|
|
case "int8", "uint8", "byte":
|
|
return 1
|
|
case "int16", "uint16":
|
|
return 2
|
|
case "int32", "uint32", "float32", "types.Color":
|
|
return 4
|
|
case "int64", "uint64", "float64":
|
|
return 8
|
|
case "types.EquipmentItem":
|
|
return 6
|
|
default:
|
|
return 0
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
var (
|
|
input = flag.String("input", "", "Input XML file or directory")
|
|
output = flag.String("output", "", "Output Go file or directory")
|
|
pkgName = flag.String("package", "generated", "Package name for generated code")
|
|
)
|
|
flag.Parse()
|
|
|
|
if *input == "" || *output == "" {
|
|
fmt.Fprintf(os.Stderr, "Usage: %s -input <xml-file> -output <go-file> [-package <name>]\n", os.Args[0])
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Get file info
|
|
info, err := os.Stat(*input)
|
|
if err != nil {
|
|
log.Fatalf("Error accessing input: %v", err)
|
|
}
|
|
|
|
if info.IsDir() {
|
|
// Process directory
|
|
files, err := filepath.Glob(filepath.Join(*input, "*.xml"))
|
|
if err != nil {
|
|
log.Fatalf("Error listing XML files: %v", err)
|
|
}
|
|
|
|
for _, xmlFile := range files {
|
|
processFile(xmlFile, *output, *pkgName)
|
|
}
|
|
} else {
|
|
// Process single file
|
|
processFile(*input, *output, *pkgName)
|
|
}
|
|
}
|
|
|
|
func processFile(inputFile, outputPath, packageName string) {
|
|
log.Printf("Processing %s...", inputFile)
|
|
|
|
// Parse XML file
|
|
structs, err := parseXMLFile(inputFile)
|
|
if err != nil {
|
|
log.Printf("Error parsing %s: %v", inputFile, err)
|
|
return
|
|
}
|
|
|
|
// Convert to Go structs
|
|
goStructs := make([]GoStruct, 0, len(structs))
|
|
|
|
for _, xmlStruct := range structs {
|
|
goStructs = append(goStructs, convertToGoStruct(xmlStruct, packageName))
|
|
}
|
|
|
|
// Determine output file
|
|
outputFile := outputPath
|
|
if info, err := os.Stat(outputPath); err == nil && info.IsDir() {
|
|
base := strings.TrimSuffix(filepath.Base(inputFile), ".xml")
|
|
outputFile = filepath.Join(outputPath, base+".go")
|
|
}
|
|
|
|
// Generate code
|
|
tmpl := template.New("struct")
|
|
tmpl.Funcs(template.FuncMap{
|
|
"contains": contains,
|
|
"baseType": baseType,
|
|
"sizeOf": sizeOf,
|
|
})
|
|
|
|
// Parse the main template
|
|
tmpl, err = tmpl.Parse(structTemplate)
|
|
if err != nil {
|
|
log.Fatalf("Error parsing template: %v", err)
|
|
}
|
|
|
|
// Create output file
|
|
out, err := os.Create(outputFile)
|
|
if err != nil {
|
|
log.Fatalf("Error creating output file: %v", err)
|
|
}
|
|
defer out.Close()
|
|
|
|
// Check if math package is needed (for float types)
|
|
needsMath := false
|
|
for _, goStruct := range goStructs {
|
|
for _, field := range goStruct.Fields {
|
|
if strings.Contains(field.Type, "float") {
|
|
needsMath = true
|
|
break
|
|
}
|
|
}
|
|
if needsMath {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Execute template
|
|
data := GenerateOutput{
|
|
SourceFile: filepath.Base(inputFile),
|
|
PackageName: packageName,
|
|
Structs: goStructs,
|
|
NeedsMath: needsMath,
|
|
}
|
|
|
|
if err := tmpl.Execute(out, data); err != nil {
|
|
log.Fatalf("Error executing template: %v", err)
|
|
}
|
|
|
|
log.Printf("Generated %s", outputFile)
|
|
}
|