1
0

work on packet def codegen

This commit is contained in:
Sky Johnson 2025-09-05 13:17:32 -05:00
parent c592ea7b02
commit 678d653932
12 changed files with 15289 additions and 163 deletions

821
defs/gen/main.go Normal file
View File

@ -0,0 +1,821 @@
//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)
}

5941
defs/generated/common.go Normal file

File diff suppressed because it is too large Load Diff

7722
defs/generated/login.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,21 @@
<struct name="CreateCharacter" clientVersion="1" opcodeName="OP_CreateCharacterRequestMsg">
<data name="account_id" type="int32"/>
<data name="server_id" type="int32"/>
<data name="account_id" type="u32"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="unknown1" type="int8" size="2"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="unknown1" type="u8" size="2"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="float" size="3"/>
<data name="eye_color" type="float" size="3"/>
<data name="hair_color1" type="float" size="3"/>
<data name="hair_color2" type="float" size="3"/>
<data name="hair_highlight" type="float" size="3"/>
<data name="unknown2" type="int8" size="26"/>
<data name="unknown2" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="float" size="3"/>
<data name="hair_type_highlight_color" type="float" size="3"/>
@ -41,24 +41,24 @@
</struct>
<struct name="CreateCharacter" clientVersion="373" opcodeName="OP_CreateCharacterRequestMsg">
<data name="unknown0" type="int32"/>
<data name="account_id" type="int32"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u32"/>
<data name="account_id" type="u32"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="unknown1" type="int8" size="2"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="unknown1" type="u8" size="2"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="float" size="3"/>
<data name="eye_color" type="float" size="3"/>
<data name="hair_color1" type="float" size="3"/>
<data name="hair_color2" type="float" size="3"/>
<data name="hair_highlight" type="float" size="3"/>
<data name="unknown2" type="int8" size="26"/>
<data name="unknown2" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="float" size="3"/>
<data name="hair_type_highlight_color" type="float" size="3"/>
@ -84,26 +84,26 @@
</struct>
<struct name="CreateCharacter" clientVersion="546" opcodeName="OP_CreateCharacterRequestMsg">
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="cc_unknown_0" type="int8"/>
<data name="version" type="int8"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="cc_unknown_0" type="u8"/>
<data name="version" type="u8"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="float" size="3"/>
<data name="eye_color" type="float" size="3"/>
<data name="hair_color1" type="float" size="3"/>
<data name="hair_color2" type="float" size="3"/>
<data name="hair_highlight" type="float" size="3"/>
<data name="unknown2" type="int8" size="26"/>
<data name="unknown2" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="float" size="3"/>
<data name="hair_type_highlight_color" type="float" size="3"/>
@ -129,25 +129,25 @@
</struct>
<struct name="CreateCharacter" clientVersion="561" opcodeName="OP_CreateCharacterRequestMsg">
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="version" type="int8"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="version" type="u8"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="float" size="3"/>
<data name="eye_color" type="float" size="3"/>
<data name="hair_color1" type="float" size="3"/>
<data name="hair_color2" type="float" size="3"/>
<data name="hair_highlight" type="float" size="3"/>
<data name="unknown2" type="int8" size="26"/>
<data name="unknown2" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="float" size="3"/>
<data name="hair_type_highlight_color" type="float" size="3"/>
@ -173,26 +173,26 @@
</struct>
<struct name="CreateCharacter" clientVersion="562" opcodeName="OP_CreateCharacterRequestMsg">
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="unknown3" type="int8"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="unknown3" type="u8"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="version" type="int8"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="version" type="u8"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="color"/>
<data name="skin_color2" type="color"/>
<data name="eye_color" type="color"/>
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="unknown8" type="int8" size="26"/>
<data name="unknown8" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
@ -218,14 +218,14 @@
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<data name="soga_version" type="int8"/>
<data name="soga_version" type="u8"/>
<data name="soga_race_file" type="str16"/>
<data name="soga_skin_color" type="color"/>
<data name="soga_eye_color" type="color"/>
<data name="soga_hair_color1" type="color"/>
<data name="soga_hair_color2" type="color"/>
<data name="soga_hair_highlight" type="color"/>
<data name="soga_unknown11" type="int8" size="26"/>
<data name="soga_unknown11" type="u8" size="26"/>
<data name="soga_hair_file" type="str16"/>
<data name="soga_hair_type_color" type="color"/>
<data name="soga_hair_type_highlight_color" type="color"/>
@ -254,19 +254,19 @@
</struct>
<struct name="CreateCharacter" clientVersion="869" opcodeName="OP_CreateCharacterRequestMsg">
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="unknown3" type="int8"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="unknown3" type="u8"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="version" type="int8"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="version" type="u8"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="color"/>
<data name="skin_color2" type="color"/>
@ -274,7 +274,7 @@
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="hair_highlight" type="color"/>
<data name="unknown8" type="int8" size="26"/>
<data name="unknown8" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
@ -300,7 +300,7 @@
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<data name="soga_version" type="int8"/>
<data name="soga_version" type="u8"/>
<data name="soga_race_file" type="str16"/>
<data name="soga_skin_color" type="color"/>
<data name="soga_eye_color" type="color"/>
@ -308,7 +308,7 @@
<data name="soga_hair_color2" type="color"/>
<data name="soga_hair_highlight" type="color"/>
<data name="soga_unknown_color1" type="color"/>
<data name="soga_unknown11" type="int8" size="26"/>
<data name="soga_unknown11" type="u8" size="26"/>
<data name="soga_hair_file" type="str16"/>
<data name="soga_hair_type_color" type="color"/>
<data name="soga_hair_type_highlight_color" type="color"/>
@ -337,19 +337,19 @@
</struct>
<struct name="CreateCharacter" clientVersion="1096" opcodeName="OP_CreateCharacterRequestMsg">
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="unknown3" type="int8"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="unknown3" type="u8"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="version" type="int8"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="version" type="u8"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="color"/>
<data name="skin_color2" type="color"/>
@ -357,7 +357,7 @@
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="hair_highlight" type="color"/>
<data name="unknown8" type="int8" size="26"/>
<data name="unknown8" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
@ -385,7 +385,7 @@
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<data name="soga_version" type="int8"/>
<data name="soga_version" type="u8"/>
<data name="soga_race_file" type="str16"/>
<data name="soga_skin_color" type="color"/>
<data name="soga_eye_color" type="color"/>
@ -393,7 +393,7 @@
<data name="soga_hair_color2" type="color"/>
<data name="soga_hair_highlight" type="color"/>
<data name="soga_unknown_color" type="color"/>
<data name="soga_unknown11" type="int8" size="26"/>
<data name="soga_unknown11" type="u8" size="26"/>
<data name="soga_hair_file" type="str16"/>
<data name="soga_hair_type_color" type="color"/>
<data name="soga_hair_type_highlight_color" type="color"/>
@ -422,20 +422,20 @@
</struct>
<struct name="CreateCharacter" clientVersion="57080" opcodeName="OP_CreateCharacterRequestMsg">
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="unknown3" type="int8"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="unknown3" type="u8"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="version" type="int8"/>
<data name="unknown10" type="int16"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="version" type="u8"/>
<data name="unknown10" type="u16"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="color"/>
<data name="eye_color" type="color"/>
@ -443,7 +443,7 @@
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="hair_highlight" type="color"/>
<data name="unknown8" type="int8" size="26"/>
<data name="unknown8" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
@ -471,7 +471,7 @@
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<data name="soga_version" type="int8"/>
<data name="soga_version" type="u8"/>
<data name="soga_race_file" type="str16"/>
<data name="soga_skin_color" type="color"/>
<data name="soga_eye_color" type="color"/>
@ -479,7 +479,7 @@
<data name="soga_hair_color2" type="color"/>
<data name="soga_hair_highlight" type="color"/>
<data name="soga_unknown_color" type="color"/>
<data name="soga_unknown11" type="int8" size="26"/>
<data name="soga_unknown11" type="u8" size="26"/>
<data name="soga_hair_file" type="str16"/>
<data name="soga_hair_type_color" type="color"/>
<data name="soga_hair_type_highlight_color" type="color"/>
@ -508,20 +508,20 @@
</struct>
<struct name="CreateCharacter" clientVersion="60085" opcodeName="OP_CreateCharacterRequestMsg" >
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="unknown3" type="int8"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="unknown3" type="u8"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int8"/>
<data name="version" type="int8"/>
<data name="unknown10" type="int16"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u8"/>
<data name="version" type="u8"/>
<data name="unknown10" type="u16"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="color"/>
<data name="eye_color" type="color"/>
@ -529,7 +529,7 @@
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="hair_highlight" type="color"/>
<data name="unknown8" type="int8" size="26"/>
<data name="unknown8" type="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
@ -557,7 +557,7 @@
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<data name="soga_version" type="int8"/>
<data name="soga_version" type="u8"/>
<data name="soga_race_file" type="str16"/>
<data name="soga_skin_color" type="color"/>
<data name="soga_eye_color" type="color"/>
@ -565,7 +565,7 @@
<data name="soga_hair_color2" type="color"/>
<data name="soga_hair_highlight" type="color"/>
<data name="soga_unknown_color" type="color"/>
<data name="soga_unknown11" type="int8" size="26"/>
<data name="soga_unknown11" type="u8" size="26"/>
<data name="soga_hair_file" type="str16"/>
<data name="soga_hair_type_color" type="color"/>
<data name="soga_hair_type_highlight_color" type="color"/>
@ -594,26 +594,26 @@
</struct>
<struct name="CreateCharacter" clientVersion="64659" opcodeName="OP_CreateCharacterRequestMsg" >
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="unknown3" type="int8"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="unknown3" type="u8"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int32"/>
<data name="version" type="int8"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u32"/>
<data name="version" type="u8"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="color"/>
<data name="skin_color2" type="color"/>
<data name="eye_color" type="color"/>
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="unknown8" type="int8" size="38"/>
<data name="unknown8" type="u8" size="38"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
@ -639,14 +639,14 @@
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<data name="soga_version" type="int8"/>
<data name="soga_version" type="u8"/>
<data name="soga_race_file" type="str16"/>
<data name="soga_skin_color" type="color"/>
<data name="soga_eye_color" type="color"/>
<data name="soga_hair_color1" type="color"/>
<data name="soga_hair_color2" type="color"/>
<data name="soga_hair_highlight" type="color"/>
<data name="soga_unknown11" type="int8" size="38"/>
<data name="soga_unknown11" type="u8" size="38"/>
<data name="soga_hair_file" type="str16"/>
<data name="soga_hair_type_color" type="color"/>
<data name="soga_hair_type_highlight_color" type="color"/>
@ -672,30 +672,30 @@
<data name="soga_nose" type="float" size="3"/>
<data name="soga_body_size" type="float"/>
<data name="soga_body_age" type="float"/>
<data name="unknown13" type="int8" size="2"/>
<data name="unknown13" type="u8" size="2"/>
</struct>
<struct name="CreateCharacter" clientVersion="65534" opcodeName="OP_CreateCharacterRequestMsg" >
<data name="unknown0" type="int8"/>
<data name="unknown1" type="int32"/>
<data name="account_id" type="int32"/>
<data name="unknown3" type="int8"/>
<data name="server_id" type="int32"/>
<data name="unknown0" type="u8"/>
<data name="unknown1" type="u32"/>
<data name="account_id" type="u32"/>
<data name="unknown3" type="u8"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<data name="race" type="int8"/>
<data name="gender" type="int8"/>
<data name="deity" type="int8"/>
<data name="class" type="int8"/>
<data name="level" type="int8"/>
<data name="starting_zone" type="int32"/>
<data name="version" type="int8"/>
<data name="race" type="u8"/>
<data name="gender" type="u8"/>
<data name="deity" type="u8"/>
<data name="class" type="u8"/>
<data name="level" type="u8"/>
<data name="starting_zone" type="u32"/>
<data name="version" type="u8"/>
<data name="race_file" type="str16"/>
<data name="skin_color" type="color"/>
<data name="skin_color2" type="color"/>
<data name="eye_color" type="color"/>
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="unknown8" type="int8" size="38"/>
<data name="unknown8" type="u8" size="38"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
@ -721,14 +721,14 @@
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<data name="soga_version" type="int8"/>
<data name="soga_version" type="u8"/>
<data name="soga_race_file" type="str16"/>
<data name="soga_skin_color" type="color"/>
<data name="soga_eye_color" type="color"/>
<data name="soga_hair_color1" type="color"/>
<data name="soga_hair_color2" type="color"/>
<data name="soga_hair_highlight" type="color"/>
<data name="soga_unknown11" type="int8" size="38"/>
<data name="soga_unknown11" type="u8" size="38"/>
<data name="soga_hair_file" type="str16"/>
<data name="soga_hair_type_color" type="color"/>
<data name="soga_hair_type_highlight_color" type="color"/>
@ -754,11 +754,11 @@
<data name="soga_nose" type="float" size="3"/>
<data name="soga_body_size" type="float"/>
<data name="soga_body_age" type="float"/>
<data name="unknown13" type="int8" size="2"/>
<data name="unknown13" type="u8" size="2"/>
</struct>
<struct name="BadLanguageFilter" clientVersion="1" opcodeName="OP_BadLanguageFilter">
<data name="num_words" type="int16" oversizedValue="255"/>
<data name="num_words" type="u16" oversizedValue="255"/>
<data name="words_array" type="Array" arraySizeVariable="num_words">
<data name="word" type="str16"/>
</data>

View File

@ -1,5 +0,0 @@
<struct name="LS_CreateCharacterReply" version="1.0" clientVersion="1">
<data name="account_id" type="int32"/>
<data name="response" type="int8"/>
<data name="name" type="str16"/>
</struct>

View File

@ -52,8 +52,8 @@
<data name="unknown2" type="u8" size="8"/>
<data name="unknown3" type="u8" size="2"/>
<data name="version" type="u32"/>
<data name="unknown3" type="u16"/>
<data name="unknown4" type="u32"/>
<data name="unknown4" type="u16"/>
<data name="unknown5" type="u32"/>
</struct>
<struct name="LS_LoginRequest" clientVersion="1208" opcodeName="OP_LoginRequestMsg">

85
types/color.go Normal file
View File

@ -0,0 +1,85 @@
package types
import (
"encoding/binary"
"io"
)
// Color represents an RGBA color value
type Color struct {
R uint8
G uint8
B uint8
A uint8
}
// NewColor creates a new Color from RGBA values
func NewColor(r, g, b, a uint8) Color {
return Color{R: r, G: g, B: b, A: a}
}
// NewColorFromUint32 creates a Color from a packed uint32 (0xAARRGGBB)
func NewColorFromUint32(packed uint32) Color {
return Color{
R: uint8((packed >> 16) & 0xFF),
G: uint8((packed >> 8) & 0xFF),
B: uint8(packed & 0xFF),
A: uint8((packed >> 24) & 0xFF),
}
}
// ToUint32 converts the color to a packed uint32 (0xAARRGGBB)
func (c Color) ToUint32() uint32 {
return uint32(c.A)<<24 | uint32(c.R)<<16 | uint32(c.G)<<8 | uint32(c.B)
}
// Serialize writes the color as a uint32 to a writer
func (c Color) Serialize(w io.Writer) error {
return binary.Write(w, binary.LittleEndian, c.ToUint32())
}
// SerializeToBytes writes the color to a byte slice at the given offset
func (c Color) SerializeToBytes(dest []byte, offset *uint32) {
binary.LittleEndian.PutUint32(dest[*offset:], c.ToUint32())
*offset += 4
}
// Size returns the serialized size of the color (always 4 bytes)
func (c Color) Size() uint32 {
return 4
}
// Deserialize reads a color from a reader
func (c *Color) Deserialize(r io.Reader) error {
var packed uint32
if err := binary.Read(r, binary.LittleEndian, &packed); err != nil {
return err
}
*c = NewColorFromUint32(packed)
return nil
}
// EQ2Color is an alias for Color specifically for EQ2 packets
// EQ2 sometimes uses different color formats, this allows for customization
type EQ2Color Color
// Serialize writes the EQ2 color as a uint32 to a writer
func (c EQ2Color) Serialize(w io.Writer) error {
return Color(c).Serialize(w)
}
// SerializeToBytes writes the EQ2 color to a byte slice
func (c EQ2Color) SerializeToBytes(dest []byte, offset *uint32) {
Color(c).SerializeToBytes(dest, offset)
}
// Size returns the serialized size (always 4 bytes)
func (c EQ2Color) Size() uint32 {
return 4
}
// Deserialize reads an EQ2 color from a reader
func (c *EQ2Color) Deserialize(r io.Reader) error {
color := (*Color)(c)
return color.Deserialize(r)
}

40
types/equipment.go Normal file
View File

@ -0,0 +1,40 @@
package types
import (
"encoding/binary"
"io"
)
// EquipmentItem represents an equipment item with model and color information
type EquipmentItem struct {
Type uint16 // Model/item type ID
Color Color // RGB color for the item
}
// Serialize writes the equipment item to a writer
func (e *EquipmentItem) Serialize(w io.Writer) error {
if err := binary.Write(w, binary.LittleEndian, e.Type); err != nil {
return err
}
return e.Color.Serialize(w)
}
// SerializeToBytes writes the equipment item to a byte slice at the given offset
func (e *EquipmentItem) SerializeToBytes(dest []byte, offset *uint32) {
binary.LittleEndian.PutUint16(dest[*offset:], e.Type)
*offset += 2
e.Color.SerializeToBytes(dest, offset)
}
// Size returns the serialized size of the equipment item
func (e *EquipmentItem) Size() uint32 {
return 2 + e.Color.Size() // 2 bytes for type + color size
}
// Deserialize reads an equipment item from a reader
func (e *EquipmentItem) Deserialize(r io.Reader) error {
if err := binary.Read(r, binary.LittleEndian, &e.Type); err != nil {
return err
}
return e.Color.Deserialize(r)
}

71
types/fixed.go Normal file
View File

@ -0,0 +1,71 @@
package types
import (
"io"
)
// FixedString represents a fixed-length string field
type FixedString struct {
Data []byte
Size int
}
// NewFixedString creates a new fixed string with the specified size
func NewFixedString(size int) *FixedString {
return &FixedString{
Data: make([]byte, size),
Size: size,
}
}
// SetString sets the string value, truncating or padding as needed
func (f *FixedString) SetString(s string) {
copy(f.Data, []byte(s))
// Pad with zeros if string is shorter than fixed size
for i := len(s); i < f.Size; i++ {
f.Data[i] = 0
}
}
// String returns the string value, trimming null bytes
func (f *FixedString) String() string {
// Find first null byte
for i, b := range f.Data {
if b == 0 {
return string(f.Data[:i])
}
}
return string(f.Data)
}
// Serialize writes the fixed string to a writer
func (f *FixedString) Serialize(w io.Writer) error {
_, err := w.Write(f.Data)
return err
}
// SerializeToBytes writes the fixed string to a byte slice
func (f *FixedString) SerializeToBytes(dest []byte, offset *uint32) {
copy(dest[*offset:], f.Data)
*offset += uint32(f.Size)
}
// Deserialize reads a fixed string from a reader
func (f *FixedString) Deserialize(r io.Reader) error {
_, err := io.ReadFull(r, f.Data)
return err
}
// FixedArray represents a fixed-size array of primitive types
type FixedArray[T any] struct {
Data []T
Size int
}
// NewFixedArray creates a new fixed array with the specified size
func NewFixedArray[T any](size int) *FixedArray[T] {
return &FixedArray[T]{
Data: make([]T, size),
Size: size,
}
}

219
types/packet.go Normal file
View File

@ -0,0 +1,219 @@
package types
import (
"bytes"
"encoding/binary"
"io"
)
// Packet represents a basic EQ2 packet interface
type Packet interface {
Serialize(dest []byte) uint32
Size() uint32
}
// PacketWriter helps with writing packet data
type PacketWriter struct {
buffer *bytes.Buffer
offset uint32
}
// NewPacketWriter creates a new packet writer
func NewPacketWriter() *PacketWriter {
return &PacketWriter{
buffer: new(bytes.Buffer),
offset: 0,
}
}
// WriteUint8 writes a uint8 value
func (pw *PacketWriter) WriteUint8(v uint8) error {
return binary.Write(pw.buffer, binary.LittleEndian, v)
}
// WriteUint16 writes a uint16 value
func (pw *PacketWriter) WriteUint16(v uint16) error {
return binary.Write(pw.buffer, binary.LittleEndian, v)
}
// WriteUint32 writes a uint32 value
func (pw *PacketWriter) WriteUint32(v uint32) error {
return binary.Write(pw.buffer, binary.LittleEndian, v)
}
// WriteUint64 writes a uint64 value
func (pw *PacketWriter) WriteUint64(v uint64) error {
return binary.Write(pw.buffer, binary.LittleEndian, v)
}
// WriteString8 writes a string with 8-bit length prefix
func (pw *PacketWriter) WriteString8(s string) error {
str := EQ2String8(s)
return str.Serialize(pw.buffer)
}
// WriteString16 writes a string with 16-bit length prefix
func (pw *PacketWriter) WriteString16(s string) error {
str := EQ2String16(s)
return str.Serialize(pw.buffer)
}
// WriteString32 writes a string with 32-bit length prefix
func (pw *PacketWriter) WriteString32(s string) error {
str := EQ2String32(s)
return str.Serialize(pw.buffer)
}
// WriteColor writes a color value
func (pw *PacketWriter) WriteColor(c Color) error {
return c.Serialize(pw.buffer)
}
// WriteFloat32 writes a float32 value
func (pw *PacketWriter) WriteFloat32(v float32) error {
f := Float32(v)
return f.Serialize(pw.buffer)
}
// WriteFloat64 writes a float64 value
func (pw *PacketWriter) WriteFloat64(v float64) error {
f := Float64(v)
return f.Serialize(pw.buffer)
}
// Bytes returns the written bytes
func (pw *PacketWriter) Bytes() []byte {
return pw.buffer.Bytes()
}
// Size returns the current size of written data
func (pw *PacketWriter) Size() uint32 {
return uint32(pw.buffer.Len())
}
// PacketReader helps with reading packet data
type PacketReader struct {
reader io.Reader
offset uint32
}
// NewPacketReader creates a new packet reader
func NewPacketReader(data []byte) *PacketReader {
return &PacketReader{
reader: bytes.NewReader(data),
offset: 0,
}
}
// NewPacketReaderFromReader creates a packet reader from an io.Reader
func NewPacketReaderFromReader(r io.Reader) *PacketReader {
return &PacketReader{
reader: r,
offset: 0,
}
}
// ReadUint8 reads a uint8 value
func (pr *PacketReader) ReadUint8() (uint8, error) {
var v uint8
err := binary.Read(pr.reader, binary.LittleEndian, &v)
if err == nil {
pr.offset++
}
return v, err
}
// ReadUint16 reads a uint16 value
func (pr *PacketReader) ReadUint16() (uint16, error) {
var v uint16
err := binary.Read(pr.reader, binary.LittleEndian, &v)
if err == nil {
pr.offset += 2
}
return v, err
}
// ReadUint32 reads a uint32 value
func (pr *PacketReader) ReadUint32() (uint32, error) {
var v uint32
err := binary.Read(pr.reader, binary.LittleEndian, &v)
if err == nil {
pr.offset += 4
}
return v, err
}
// ReadUint64 reads a uint64 value
func (pr *PacketReader) ReadUint64() (uint64, error) {
var v uint64
err := binary.Read(pr.reader, binary.LittleEndian, &v)
if err == nil {
pr.offset += 8
}
return v, err
}
// ReadString8 reads a string with 8-bit length prefix
func (pr *PacketReader) ReadString8() (string, error) {
var s EQ2String8
err := s.Deserialize(pr.reader)
if err == nil {
pr.offset += s.Size()
}
return string(s), err
}
// ReadString16 reads a string with 16-bit length prefix
func (pr *PacketReader) ReadString16() (string, error) {
var s EQ2String16
err := s.Deserialize(pr.reader)
if err == nil {
pr.offset += s.Size()
}
return string(s), err
}
// ReadString32 reads a string with 32-bit length prefix
func (pr *PacketReader) ReadString32() (string, error) {
var s EQ2String32
err := s.Deserialize(pr.reader)
if err == nil {
pr.offset += s.Size()
}
return string(s), err
}
// ReadColor reads a color value
func (pr *PacketReader) ReadColor() (Color, error) {
var c Color
err := c.Deserialize(pr.reader)
if err == nil {
pr.offset += 4
}
return c, err
}
// ReadFloat32 reads a float32 value
func (pr *PacketReader) ReadFloat32() (float32, error) {
var f Float32
err := f.Deserialize(pr.reader)
if err == nil {
pr.offset += 4
}
return float32(f), err
}
// ReadFloat64 reads a float64 value
func (pr *PacketReader) ReadFloat64() (float64, error) {
var f Float64
err := f.Deserialize(pr.reader)
if err == nil {
pr.offset += 8
}
return float64(f), err
}
// Offset returns the current read offset
func (pr *PacketReader) Offset() uint32 {
return pr.offset
}

81
types/primitives.go Normal file
View File

@ -0,0 +1,81 @@
package types
import (
"encoding/binary"
"io"
"math"
)
// Float32 provides serialization methods for float32
type Float32 float32
func (f Float32) Serialize(w io.Writer) error {
return binary.Write(w, binary.LittleEndian, math.Float32bits(float32(f)))
}
func (f Float32) SerializeToBytes(dest []byte, offset *uint32) {
binary.LittleEndian.PutUint32(dest[*offset:], math.Float32bits(float32(f)))
*offset += 4
}
func (f Float32) Size() uint32 {
return 4
}
func (f *Float32) Deserialize(r io.Reader) error {
var bits uint32
if err := binary.Read(r, binary.LittleEndian, &bits); err != nil {
return err
}
*f = Float32(math.Float32frombits(bits))
return nil
}
// Float64 provides serialization methods for float64
type Float64 float64
func (f Float64) Serialize(w io.Writer) error {
return binary.Write(w, binary.LittleEndian, math.Float64bits(float64(f)))
}
func (f Float64) SerializeToBytes(dest []byte, offset *uint32) {
binary.LittleEndian.PutUint64(dest[*offset:], math.Float64bits(float64(f)))
*offset += 8
}
func (f Float64) Size() uint32 {
return 8
}
func (f *Float64) Deserialize(r io.Reader) error {
var bits uint64
if err := binary.Read(r, binary.LittleEndian, &bits); err != nil {
return err
}
*f = Float64(math.Float64frombits(bits))
return nil
}
// GetTypeSize returns the size in bytes for common EQ2 types
func GetTypeSize(typeName string) uint32 {
switch typeName {
case "int8", "uint8", "byte", "char":
return 1
case "int16", "uint16":
return 2
case "int32", "uint32", "float", "float32", "color":
return 4
case "int64", "uint64", "double", "float64":
return 8
default:
return 0
}
}
// Serializable interface for types that can be serialized
type Serializable interface {
Serialize(w io.Writer) error
SerializeToBytes(dest []byte, offset *uint32)
Size() uint32
Deserialize(r io.Reader) error
}

151
types/strings.go Normal file
View File

@ -0,0 +1,151 @@
package types
import (
"encoding/binary"
"io"
)
// EQ2String8 represents a string with an 8-bit length prefix
type EQ2String8 string
// Serialize writes the string with 8-bit length prefix
func (s EQ2String8) Serialize(w io.Writer) error {
if len(s) > 255 {
return io.ErrShortWrite
}
// Write length byte
if err := binary.Write(w, binary.LittleEndian, uint8(len(s))); err != nil {
return err
}
// Write string data
_, err := w.Write([]byte(s))
return err
}
// SerializeToBytes writes the string to a byte slice at the given offset
func (s EQ2String8) SerializeToBytes(dest []byte, offset *uint32) {
dest[*offset] = uint8(len(s))
*offset++
copy(dest[*offset:], []byte(s))
*offset += uint32(len(s))
}
// Size returns the serialized size (1 byte length + string data)
func (s EQ2String8) Size() uint32 {
return 1 + uint32(len(s))
}
// Deserialize reads a string with 8-bit length prefix
func (s *EQ2String8) Deserialize(r io.Reader) error {
var length uint8
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return err
}
data := make([]byte, length)
if _, err := io.ReadFull(r, data); err != nil {
return err
}
*s = EQ2String8(data)
return nil
}
// EQ2String16 represents a string with a 16-bit length prefix
type EQ2String16 string
// Serialize writes the string with 16-bit length prefix
func (s EQ2String16) Serialize(w io.Writer) error {
if len(s) > 65535 {
return io.ErrShortWrite
}
// Write length as uint16
if err := binary.Write(w, binary.LittleEndian, uint16(len(s))); err != nil {
return err
}
// Write string data
_, err := w.Write([]byte(s))
return err
}
// SerializeToBytes writes the string to a byte slice at the given offset
func (s EQ2String16) SerializeToBytes(dest []byte, offset *uint32) {
binary.LittleEndian.PutUint16(dest[*offset:], uint16(len(s)))
*offset += 2
copy(dest[*offset:], []byte(s))
*offset += uint32(len(s))
}
// Size returns the serialized size (2 bytes length + string data)
func (s EQ2String16) Size() uint32 {
return 2 + uint32(len(s))
}
// Deserialize reads a string with 16-bit length prefix
func (s *EQ2String16) Deserialize(r io.Reader) error {
var length uint16
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return err
}
data := make([]byte, length)
if _, err := io.ReadFull(r, data); err != nil {
return err
}
*s = EQ2String16(data)
return nil
}
// EQ2String32 represents a string with a 32-bit length prefix
type EQ2String32 string
// Serialize writes the string with 32-bit length prefix
func (s EQ2String32) Serialize(w io.Writer) error {
// Write length as uint32
if err := binary.Write(w, binary.LittleEndian, uint32(len(s))); err != nil {
return err
}
// Write string data
_, err := w.Write([]byte(s))
return err
}
// SerializeToBytes writes the string to a byte slice at the given offset
func (s EQ2String32) SerializeToBytes(dest []byte, offset *uint32) {
binary.LittleEndian.PutUint32(dest[*offset:], uint32(len(s)))
*offset += 4
copy(dest[*offset:], []byte(s))
*offset += uint32(len(s))
}
// Size returns the serialized size (4 bytes length + string data)
func (s EQ2String32) Size() uint32 {
return 4 + uint32(len(s))
}
// Deserialize reads a string with 32-bit length prefix
func (s *EQ2String32) Deserialize(r io.Reader) error {
var length uint32
if err := binary.Read(r, binary.LittleEndian, &length); err != nil {
return err
}
// Sanity check to prevent huge allocations
if length > 1024*1024 { // 1MB limit
return io.ErrUnexpectedEOF
}
data := make([]byte, length)
if _, err := io.ReadFull(r, data); err != nil {
return err
}
*s = EQ2String32(data)
return nil
}