work on packet def codegen
This commit is contained in:
parent
c592ea7b02
commit
678d653932
821
defs/gen/main.go
Normal file
821
defs/gen/main.go
Normal 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
5941
defs/generated/common.go
Normal file
File diff suppressed because it is too large
Load Diff
7722
defs/generated/login.go
Normal file
7722
defs/generated/login.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -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>
|
||||
|
@ -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>
|
@ -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
85
types/color.go
Normal 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
40
types/equipment.go
Normal 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
71
types/fixed.go
Normal 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
219
types/packet.go
Normal 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
81
types/primitives.go
Normal 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
151
types/strings.go
Normal 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
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user