1
0

Compare commits

...

2 Commits

Author SHA1 Message Date
678d653932 work on packet def codegen 2025-09-05 13:17:32 -05:00
c592ea7b02 add cleaned up xml packet definitions 2025-09-05 11:39:59 -05:00
13 changed files with 17030 additions and 2280 deletions

View File

@ -1,824 +0,0 @@
# EQPacket System Documentation
## Table of Contents
1. [Overview](#overview)
2. [Architecture](#architecture)
3. [Packet Class Hierarchy](#packet-class-hierarchy)
4. [Packet Types and Opcodes](#packet-types-and-opcodes)
5. [Packet Processing Pipeline](#packet-processing-pipeline)
6. [Compression and Encryption](#compression-and-encryption)
7. [Packet Combining](#packet-combining)
8. [CRC Validation](#crc-validation)
9. [Serialization Formats](#serialization-formats)
10. [Memory Management](#memory-management)
11. [Debug and Diagnostic Tools](#debug-and-diagnostic-tools)
## Overview
The EQPacket system is the core packet handling infrastructure for the EverQuest 2 network protocol. It provides a hierarchical class structure for managing different packet types, from low-level protocol packets to high-level application messages. The system handles packet construction, serialization, compression, encryption, validation, and debugging.
### Key Features
- **Hierarchical packet types** - Protocol, Application, and EQ2-specific packets
- **Automatic opcode management** - Conversion between emulator and network opcodes
- **Compression support** - Zlib and simple encoding
- **Packet combining** - Efficient bundling of multiple packets
- **CRC validation** - Integrity checking with CRC16
- **Chat encryption** - XOR-based rolling key encryption
- **Debug utilities** - Comprehensive packet dumping and analysis
## Architecture
```
┌─────────────────────────────────────────────────┐
│ EQPacket │
│ Base class for all packet types │
│ - Raw buffer management │
│ - Network information (IP, port) │
│ - Timestamp tracking │
│ - Basic serialization │
└────────────┬───────────────────┬────────────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌────────────────────────┐
│ EQProtocolPacket │ │ EQApplicationPacket │
│ Low-level protocol │ │ High-level application │
│ - Compression │ │ - Game opcodes │
│ - Sequencing │ │ - Emulator opcodes │
│ - Reliability │ │ - Opcode conversion │
└──────────┬──────────┘ └────────────────────────┘
┌─────────────────────┐
│ EQ2Packet │
│ EQ2-specific packet │
│ - Login opcodes │
│ - App combining │
└─────────────────────┘
```
## Packet Class Hierarchy
### EQPacket (Base Class)
The foundation class for all packet types, managing raw packet data and metadata.
```cpp
class EQPacket {
public:
// Core data
unsigned char* pBuffer; // Raw packet data buffer
uint32 size; // Size of packet data
// Network information
uint32 src_ip, dst_ip; // Source and destination IPs
uint16 src_port, dst_port; // Source and destination ports
uint32 priority; // Processing priority
timeval timestamp; // Packet timestamp
int16 version; // Protocol version
// Methods
void DumpRaw(FILE* to = stdout) const;
const char* GetOpcodeName();
uint32 Size() const { return size + 2; } // Total size with opcode
uint16 GetRawOpcode() const { return opcode; }
protected:
uint16 opcode; // Packet operation code
};
```
**Key Features:**
- **Buffer Management**: Dynamic allocation and ownership of packet data
- **Network Tracking**: Complete IP:port addressing for routing
- **Timestamp Support**: Precise timing for latency and ordering
- **Version Control**: Protocol version tracking for compatibility
### EQProtocolPacket
Handles low-level protocol operations including compression, encryption, and reliability.
```cpp
class EQProtocolPacket : public EQPacket {
public:
// State flags
bool eq2_compressed; // Packet is compressed
bool packet_prepared; // Ready for transmission
bool packet_encrypted; // Packet is encrypted
bool acked; // Acknowledgment received
// Reliability tracking
int32 sent_time; // When packet was sent
int8 attempt_count; // Retransmission attempts
int32 sequence; // Sequence number
// Core operations
bool combine(const EQProtocolPacket* rhs);
uint32 serialize(unsigned char* dest, int8 offset = 0) const;
EQApplicationPacket* MakeApplicationPacket(uint8 opcode_size = 0) const;
// Static utilities
static bool ValidateCRC(const unsigned char* buffer, int length, uint32 Key);
static uint32 Decompress(const unsigned char* buffer, uint32 length,
unsigned char* newbuf, uint32 newbufsize);
static uint32 Compress(const unsigned char* buffer, uint32 length,
unsigned char* newbuf, uint32 newbufsize);
static void ChatDecode(unsigned char* buffer, int size, int DecodeKey);
static void ChatEncode(unsigned char* buffer, int size, int EncodeKey);
};
```
**Key Features:**
- **Compression**: Zlib for large packets, simple encoding for small
- **Reliability**: Sequence numbers, timestamps, acknowledgments
- **Packet Combining**: Bundle multiple packets for efficiency
- **CRC Validation**: Integrity checking with configurable keys
- **Chat Encryption**: XOR-based rolling key encryption
### EQApplicationPacket
Manages application-level game packets with emulator opcode abstraction.
```cpp
class EQApplicationPacket : public EQPacket {
public:
static uint8 default_opcode_size; // Global default (usually 2)
// Constructors for different use cases
EQApplicationPacket(); // Empty packet
EQApplicationPacket(EmuOpcode op); // Opcode only
EQApplicationPacket(EmuOpcode op, uint32 len); // Pre-allocated
EQApplicationPacket(EmuOpcode op, const unsigned char* buf, uint32 len);
// Operations
bool combine(const EQApplicationPacket* rhs);
uint32 serialize(unsigned char* dest) const;
EQApplicationPacket* Copy() const;
// Opcode management
void SetOpcode(EmuOpcode op);
EmuOpcode GetOpcode(); // Caching version
const EmuOpcode GetOpcode() const; // Const version
protected:
EmuOpcode emu_opcode; // Cached emulator opcode
private:
uint8 app_opcode_size; // Opcode size (1 or 2 bytes)
};
```
**Key Features:**
- **Opcode Abstraction**: Automatic conversion between emulator and network opcodes
- **Flexible Construction**: Multiple constructors for different scenarios
- **Deep Copy Support**: Safe packet duplication with memory management
- **Variable Opcode Size**: Support for 1-byte and 2-byte opcodes
### EQ2Packet
EverQuest 2 specific packet handling with login opcodes and advanced combining.
```cpp
class EQ2Packet : public EQProtocolPacket {
public:
EmuOpcode login_op; // EQ2 login/application opcode
// EQ2-specific operations
bool AppCombine(EQ2Packet* rhs);
int8 PreparePacket(int16 MaxLen);
const char* GetOpcodeName();
EQ2Packet* Copy();
};
```
**Key Features:**
- **Login Opcodes**: Special handling for login server packets
- **Application Combining**: EQ2-specific packet bundling protocol
- **Packet Preparation**: Convert emulator format to network format
## Packet Types and Opcodes
### Protocol Opcodes
Low-level protocol control packets:
```cpp
enum ProtocolOpcodes {
OP_SessionRequest = 0x0001, // Initialize new session
OP_SessionResponse = 0x0002, // Accept session
OP_Combined = 0x0003, // Multiple packets bundled
OP_SessionDisconnect = 0x0005, // Terminate session
OP_KeepAlive = 0x0006, // Heartbeat/ping
OP_SessionStatRequest = 0x0007, // Request statistics
OP_SessionStatResponse = 0x0008, // Statistics reply
OP_Packet = 0x0009, // Data packet
OP_Fragment = 0x000D, // Fragmented packet piece
OP_OutOfOrderAck = 0x0011, // Out-of-order acknowledgment
OP_Ack = 0x0015, // Standard acknowledgment
OP_AppCombined = 0x0019, // Application-level combining
OP_OutOfSession = 0x001D // Not in session error
};
```
### Emulator Opcodes
High-level game opcodes (partial list):
```cpp
enum EmuOpcodes {
OP_Unknown = 0x0000, // Unknown/invalid opcode
OP_LoginRequestMsg = 0x0001, // Client login request
OP_LoginByNumRequestMsg = 0x0002, // Login by number
OP_WSLoginRequestMsg = 0x0003, // World server login
OP_ESLoginRequestMsg = 0x0004, // Entity server login
OP_LoginReplyMsg = 0x0005, // Login response
OP_WSStatusReplyMsg = 0x0006, // World status
// ... hundreds more game-specific opcodes
};
```
### Opcode Conversion
The system maintains bidirectional mappings between emulator and network opcodes:
```cpp
// Conversion flow:
// Game Logic → EmuOpcode → Network Opcode → Wire Format
// Wire Format → Network Opcode → EmuOpcode → Game Logic
// Example:
SetOpcode(OP_LoginRequestMsg); // Set emulator opcode
// Internally converts to network opcode 0x00B3 for version 1193
```
## Packet Processing Pipeline
### 1. Packet Reception
```cpp
// Network → Protocol Packet
unsigned char buffer[MAX_PACKET_SIZE];
int bytes_received = recv(socket, buffer, MAX_PACKET_SIZE, 0);
// Create protocol packet from raw data
EQProtocolPacket* proto = new EQProtocolPacket(buffer, bytes_received);
```
### 2. Decompression
```cpp
// Check for compression flags
if (buffer[offset] == 0x5a) { // Zlib compressed
unsigned char decompressed[MAX_PACKET_SIZE];
uint32 new_len = EQProtocolPacket::Decompress(
buffer, bytes_received,
decompressed, MAX_PACKET_SIZE
);
// Process decompressed data
} else if (buffer[offset] == 0xa5) { // Simple encoding
// Remove encoding flag
memmove(buffer + offset, buffer + offset + 1, length - offset - 1);
}
```
### 3. CRC Validation
```cpp
// Validate packet integrity
bool valid = EQProtocolPacket::ValidateCRC(buffer, length, crc_key);
if (!valid) {
// Handle corruption - request retransmission
SendAck(sequence, false); // NAK
return;
}
```
### 4. Protocol to Application Conversion
```cpp
// Convert protocol packet to application packet
EQApplicationPacket* app = proto->MakeApplicationPacket();
if (app) {
// Extract emulator opcode
EmuOpcode op = app->GetOpcode();
// Process based on opcode
switch (op) {
case OP_LoginRequestMsg:
HandleLoginRequest(app);
break;
// ... handle other opcodes
}
}
```
### 5. Packet Transmission
```cpp
// Create application packet
EQApplicationPacket* response = new EQApplicationPacket(
OP_LoginReplyMsg,
response_data,
response_size
);
// Convert to protocol packet for transmission
EQProtocolPacket* proto_out = CreateProtocolPacket(response);
// Compress if beneficial
if (proto_out->size > 30) {
unsigned char compressed[MAX_PACKET_SIZE];
uint32 comp_len = EQProtocolPacket::Compress(
proto_out->pBuffer, proto_out->size,
compressed, MAX_PACKET_SIZE
);
// Update packet with compressed data
}
// Add CRC
uint16 crc = CRC16(proto_out->pBuffer, proto_out->size, crc_key);
*(uint16*)(proto_out->pBuffer + proto_out->size) = htons(crc);
// Send to network
send(socket, proto_out->pBuffer, proto_out->size + 2, 0);
```
## Compression and Encryption
### Compression Methods
The system uses two compression strategies based on packet size:
#### Zlib Compression (packets > 30 bytes)
```cpp
uint32 EQProtocolPacket::Compress(...) {
if (length > 30) {
// Use zlib deflate for larger packets
newlength = Deflate(
buffer + flag_offset, // Skip opcode
length - flag_offset, // Data size
newbuf + flag_offset + 1, // Leave room for flag
newbufsize
);
*(newbuf + flag_offset) = 0x5a; // Zlib flag
return newlength + flag_offset + 1;
}
}
```
**Compression Process:**
1. Skip opcode bytes (1 or 2 bytes)
2. Insert compression flag (0x5a)
3. Deflate packet data using zlib
4. Append CRC to compressed data
#### Simple Encoding (packets ≤ 30 bytes)
```cpp
// For small packets, just add encoding flag
memmove(newbuf + flag_offset + 1, buffer + flag_offset, length - flag_offset);
*(newbuf + flag_offset) = 0xa5; // Simple encoding flag
return length + 1; // Original size + flag byte
```
**Benefits:**
- Avoids zlib overhead for small packets
- Still marks packet as "encoded" for consistency
- Minimal CPU overhead
### Chat Encryption
XOR-based rolling key encryption for chat messages:
```cpp
void EQProtocolPacket::ChatEncode(unsigned char* buffer, int size, int EncodeKey) {
int Key = EncodeKey;
buffer += 2; // Skip opcode
size -= 2;
// Process 4-byte blocks with rolling key
for (int i = 0; i + 4 <= size; i += 4) {
int pt = (*(int*)&buffer[i]) ^ Key;
Key = pt; // Update key with encrypted data
*(int*)&test[i] = pt;
}
// Handle remaining bytes
unsigned char KC = Key & 0xFF;
for (; i < size; i++) {
test[i] = buffer[i] ^ KC;
}
}
```
**Encryption Features:**
- **Rolling Key**: Key updates with each 4-byte block
- **Opcode Exemption**: First 2 bytes (opcode) remain unencrypted
- **Partial Block Handling**: Remaining bytes use last key byte
- **Bidirectional**: Same algorithm for encode/decode (XOR property)
## Packet Combining
### Protocol-Level Combining
Bundles multiple protocol packets into a single transmission:
```cpp
bool EQProtocolPacket::combine(const EQProtocolPacket* rhs) {
// Check size constraints (max 256 bytes total)
if (opcode == OP_Combined && size + rhs->size + 5 < 256) {
// Already combined - append new packet
auto tmpbuffer = new unsigned char[size + rhs->size + 3];
memcpy(tmpbuffer, pBuffer, size);
// Add size prefix and packet data
tmpbuffer[size] = rhs->Size();
rhs->serialize(tmpbuffer + size + 1);
// Update packet
delete[] pBuffer;
pBuffer = tmpbuffer;
size = size + rhs->size + 3;
return true;
}
}
```
**Combined Packet Format:**
```
[OP_Combined(2)] [Size1(1)] [Packet1] [Size2(1)] [Packet2] ... [CRC(2)]
```
### Application-Level Combining (EQ2)
More sophisticated combining for application packets:
```cpp
bool EQ2Packet::AppCombine(EQ2Packet* rhs) {
// Handle oversized packets (> 255 bytes)
if (tmp_size >= 255) {
*ptr++ = 255; // Oversized marker
tmp_size = htons(tmp_size);
memcpy(ptr, &tmp_size, sizeof(int16));
ptr += sizeof(int16);
} else {
*ptr++ = static_cast<uint8>(tmp_size);
}
}
```
**EQ2 Combined Format:**
```
[OP_AppCombined(2)] [0x00] [0x19] [Size1] [Data1] [Size2] [Data2] ...
Where Size can be:
- 1 byte: size 0-254
- 3 bytes: [0xFF] [Size_High] [Size_Low] for size >= 255
```
## CRC Validation
### CRC16 Implementation
The system uses CRC16 for packet integrity:
```cpp
bool EQProtocolPacket::ValidateCRC(const unsigned char* buffer, int length, uint32 Key) {
// Exempt packets (no CRC required)
if (buffer[0] == 0x00 &&
(buffer[1] == OP_SessionRequest ||
buffer[1] == OP_SessionResponse ||
buffer[1] == OP_OutOfSession)) {
return true; // Session packets exempt
}
// Calculate and compare CRC
uint16 comp_crc = CRC16(buffer, length - 2, Key);
uint16 packet_crc = ntohs(*(uint16*)(buffer + length - 2));
return (!packet_crc || comp_crc == packet_crc);
}
```
**CRC Properties:**
- **Algorithm**: CRC16-CCITT polynomial
- **Key-based**: Uses session-specific key for security
- **Position**: Last 2 bytes of packet
- **Exemptions**: Session control packets don't require CRC
## Serialization Formats
### Protocol Packet Serialization
```cpp
uint32 EQProtocolPacket::serialize(unsigned char* dest, int8 offset) const {
// Handle variable opcode sizes
if (opcode > 0xff) {
*(uint16*)dest = opcode; // 2-byte opcode
} else {
*(dest) = 0; // High byte = 0
*(dest + 1) = opcode; // Low byte = opcode
}
// Copy packet data
memcpy(dest + 2, pBuffer + offset, size - offset);
return size + 2;
}
```
### Application Packet Serialization
```cpp
uint32 EQApplicationPacket::serialize(unsigned char* dest) const {
uint8 OpCodeBytes = app_opcode_size;
if (app_opcode_size == 1) {
*(unsigned char*)dest = opcode;
} else {
// Special encoding for opcodes with low byte = 0x00
if ((opcode & 0x00ff) == 0) {
*(uint8*)dest = 0;
*(uint16*)(dest + 1) = opcode;
++OpCodeBytes; // Extra byte needed
} else {
*(uint16*)dest = opcode;
}
}
memcpy(dest + app_opcode_size, pBuffer, size);
return size + OpCodeBytes;
}
```
### Wire Format Examples
**Simple Data Packet:**
```
[Opcode(2)] [Data(N)] [CRC(2)]
Example: [0x00 0x09] [Hello World] [0x3A 0x5C]
```
**Compressed Packet:**
```
[Opcode(2)] [Flag(1)] [Compressed Data(N)] [CRC(2)]
Example: [0x00 0x09] [0x5A] [Zlib data...] [0x3A 0x5C]
```
**Combined Packet:**
```
[OP_Combined(2)] [Size1(1)] [Packet1] [Size2(1)] [Packet2] [CRC(2)]
Example: [0x00 0x03] [0x08] [0x00 0x09 Data1] [0x0A] [0x00 0x06 Data2] [CRC]
```
## Memory Management
### Buffer Ownership
The packet classes follow strict ownership rules:
```cpp
// Constructor takes ownership of buffer
EQPacket::EQPacket(uint16 op, const unsigned char* buf, uint32 len) {
if (len > 0) {
pBuffer = new unsigned char[len];
if (buf) {
memcpy(pBuffer, buf, len); // Copy data
} else {
memset(pBuffer, 0, len); // Zero-initialize
}
}
}
// Destructor releases buffer
EQPacket::~EQPacket() {
safe_delete_array(pBuffer); // delete[] with null check
pBuffer = nullptr;
}
```
### Deep Copy Operations
```cpp
EQApplicationPacket* EQApplicationPacket::Copy() const {
auto it = std::make_unique<EQApplicationPacket>();
try {
if (size > 0) {
it->pBuffer = new unsigned char[size];
memcpy(it->pBuffer, pBuffer, size);
}
it->size = size;
it->opcode = opcode;
it->emu_opcode = emu_opcode;
it->version = version;
return it.release();
}
catch (const std::bad_alloc& ba) {
return nullptr; // Allocation failed
}
}
```
### Buffer Reallocation
When packets are modified (compressed, combined, etc.):
```cpp
// Safe buffer replacement pattern
unsigned char* new_buffer = new unsigned char[new_size];
// ... populate new_buffer ...
delete[] pBuffer; // Release old buffer
pBuffer = new_buffer; // Adopt new buffer
size = new_size;
```
## Debug and Diagnostic Tools
### Packet Dumping Functions
```cpp
// Raw hex dump with network info
void EQPacket::DumpRaw(FILE* to = stdout) const {
// Print header: [IP:port->IP:port] [Seq=N] [OpCode 0xXXXX (Name) Size=N]
DumpRawHeader();
// Dump packet contents in hex columns
if (pBuffer && size) {
dump_message_column(pBuffer, size, " ", to);
}
}
// Application packet with info
void DumpPacket(const EQApplicationPacket* app, bool iShowInfo) {
if (iShowInfo) {
cout << "Dumping Applayer: 0x" << hex << setfill('0') << setw(4)
<< app->GetOpcode() << dec;
cout << " size:" << app->size << endl;
}
DumpPacketHex(app->pBuffer, app->size);
}
```
### Output Formats
**Hex Dump:**
```
[192.168.1.100:5555->192.168.1.1:9000] [Seq=42] [OpCode 0x0009 (OP_Packet) Size=20]
00000000: 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 00 00 00 00 |Hello World!....|
00000010: 01 02 03 04 |....|
```
**ASCII Dump:**
```
Hello World!....
```
**Binary Dump:**
```
01001000 01100101 01101100 01101100 01101111 ...
```
### Diagnostic Information
The system tracks extensive metadata for debugging:
- **Timestamp**: Precise packet timing (microsecond resolution)
- **Network Info**: Complete IP:port source and destination
- **Sequence Numbers**: For tracking packet order and loss
- **Attempt Count**: Retransmission attempts for reliability analysis
- **Version Info**: Protocol version for compatibility debugging
- **Compression State**: Whether packet is compressed/encrypted
- **CRC Validation**: Integrity check results
## Performance Considerations
### Optimization Strategies
1. **Selective Compression**: Only compress packets > 30 bytes
2. **Packet Combining**: Reduce network overhead by bundling
3. **Opcode Caching**: Cache emulator opcodes to avoid repeated lookups
4. **Memory Pooling**: Reuse buffers where possible
5. **Lazy Evaluation**: Defer expensive operations until needed
### Memory Footprint
Typical packet sizes:
- **Control Packets**: 4-20 bytes
- **Chat Messages**: 20-200 bytes
- **Movement Updates**: 30-60 bytes
- **Item Data**: 100-500 bytes
- **Zone Data**: 1000-8000 bytes (fragmented)
### CPU Impact
Operation costs (relative):
- **CRC Calculation**: Low (lookup table)
- **XOR Encryption**: Very Low
- **Zlib Compression**: Medium-High
- **Packet Combining**: Low
- **Opcode Conversion**: Low (hash lookup)
## Common Usage Patterns
### Creating and Sending a Packet
```cpp
// Create application packet
EQApplicationPacket* packet = new EQApplicationPacket(
OP_ChatMessage,
message_data,
message_length
);
// Set version for opcode conversion
packet->setVersion(client_version);
// Queue for sending (stream handles conversion/compression)
stream->QueuePacket(packet);
```
### Receiving and Processing
```cpp
// Stream delivers application packets
EQApplicationPacket* packet = stream->PopPacket();
if (packet) {
EmuOpcode op = packet->GetOpcode();
switch (op) {
case OP_LoginRequestMsg:
ProcessLogin(packet);
break;
// ... handle other opcodes
}
delete packet; // Clean up after processing
}
```
### Error Handling
```cpp
// Check for packet validity
if (!EQProtocolPacket::ValidateCRC(buffer, length, key)) {
LogWrite(PACKET__ERROR, 0, "Packet", "CRC validation failed");
return false;
}
// Handle compression errors
uint32 decompressed_size = EQProtocolPacket::Decompress(
compressed, comp_len, decompressed, MAX_SIZE
);
if (decompressed_size == (uint32)-1) {
LogWrite(PACKET__ERROR, 0, "Packet", "Decompression failed");
// Fall back to uncompressed processing
}
```
## Thread Safety
The packet classes themselves are **not thread-safe**. Thread safety must be handled at a higher level:
```cpp
// Example thread-safe packet queue
class PacketQueue {
std::mutex mutex_;
std::queue<EQApplicationPacket*> packets_;
public:
void Push(EQApplicationPacket* packet) {
std::lock_guard<std::mutex> lock(mutex_);
packets_.push(packet);
}
EQApplicationPacket* Pop() {
std::lock_guard<std::mutex> lock(mutex_);
if (packets_.empty()) return nullptr;
auto packet = packets_.front();
packets_.pop();
return packet;
}
};
```
## Integration with EQStream
The packet system integrates tightly with EQStream for network operations:
1. **Packet Creation**: EQStream creates packets from network data
2. **Sequencing**: EQStream manages packet sequence numbers
3. **Reliability**: EQStream handles acknowledgments and retransmission
4. **Fragmentation**: EQStream splits/reassembles large packets
5. **Flow Control**: EQStream manages packet rate and congestion
## Summary
The EQPacket system provides a robust, efficient framework for handling EverQuest 2 network packets. Its hierarchical design separates protocol concerns from application logic, while features like compression, combining, and encryption optimize network usage. The extensive debugging capabilities and clear separation of concerns make it maintainable and extensible for ongoing development.
Key strengths:
- **Flexible Architecture**: Clean separation between protocol and application layers
- **Performance Optimized**: Selective compression, packet combining, caching
- **Robust Error Handling**: CRC validation, safe memory management
- **Comprehensive Debugging**: Multiple dump formats, extensive metadata
- **Protocol Agnostic**: Opcode abstraction allows version independence
The system successfully handles the complex requirements of an MMORPG protocol while maintaining code clarity and performance.

File diff suppressed because it is too large Load Diff

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

765
defs/xml/common.xml Normal file
View File

@ -0,0 +1,765 @@
<struct name="CreateCharacter" clientVersion="1" opcodeName="OP_CreateCharacterRequestMsg">
<data name="account_id" type="u32"/>
<data name="server_id" type="u32"/>
<data name="name" type="str16"/>
<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="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"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="float" size="3"/>
<data name="hair_face_highlight_color" type="float" size="3"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="float" size="3"/>
<data name="unknown_chest_color" type="float" size="3"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="float" size="3"/>
<data name="unknown_legs_color" type="float" size="3"/>
<data name="unknown9" type="float" size="3"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="373" opcodeName="OP_CreateCharacterRequestMsg">
<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="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="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"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="float" size="3"/>
<data name="hair_face_highlight_color" type="float" size="3"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="float" size="3"/>
<data name="unknown_chest_color" type="float" size="3"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="float" size="3"/>
<data name="unknown_legs_color" type="float" size="3"/>
<data name="unknown9" type="float" size="3"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="546" opcodeName="OP_CreateCharacterRequestMsg">
<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="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="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"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="float" size="3"/>
<data name="hair_face_highlight_color" type="float" size="3"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="float" size="3"/>
<data name="unknown_chest_color" type="float" size="3"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="float" size="3"/>
<data name="unknown_legs_color" type="float" size="3"/>
<data name="unknown9" type="float" size="3"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="561" opcodeName="OP_CreateCharacterRequestMsg">
<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="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="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"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="float" size="3"/>
<data name="hair_face_highlight_color" type="float" size="3"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="float" size="3"/>
<data name="unknown_chest_color" type="float" size="3"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="float" size="3"/>
<data name="unknown_legs_color" type="float" size="3"/>
<data name="unknown9" type="float" size="3"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="562" opcodeName="OP_CreateCharacterRequestMsg">
<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="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="u8" size="26"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="color"/>
<data name="hair_face_highlight_color" type="color"/>
<data name="wing_file" type="str16"/>
<data name="wing_color1" type="color"/>
<data name="wing_color2" type="color"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="color"/>
<data name="unknown_chest_color" type="color"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="color"/>
<data name="unknown_legs_color" type="color"/>
<data name="unknown9" type="color"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<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="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"/>
<data name="soga_face_file" type="str16"/>
<data name="soga_hair_face_color" type="color"/>
<data name="soga_hair_face_highlight_color" type="color"/>
<data name="soga_wing_file" type="str16"/>
<data name="soga_wing_color1" type="color"/>
<data name="soga_wing_color2" type="color"/>
<data name="soga_chest_file" type="str16"/>
<data name="soga_shirt_color" type="color"/>
<data name="soga_unknown_chest_color" type="color"/>
<data name="soga_legs_file" type="str16"/>
<data name="soga_pants_color" type="color"/>
<data name="soga_unknown_legs_color" type="color"/>
<data name="soga_unknown12" type="color"/>
<data name="soga_eyes2" type="float" size="3"/>
<data name="soga_ears" type="float" size="3"/>
<data name="soga_eye_brows" type="float" size="3"/>
<data name="soga_cheeks" type="float" size="3"/>
<data name="soga_lips" type="float" size="3"/>
<data name="soga_chin" type="float" size="3"/>
<data name="soga_nose" type="float" size="3"/>
<data name="soga_body_size" type="float"/>
<data name="soga_body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="869" opcodeName="OP_CreateCharacterRequestMsg">
<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="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="hair_highlight" type="color"/>
<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"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="color"/>
<data name="hair_face_highlight_color" type="color"/>
<data name="wing_file" type="str16"/>
<data name="wing_color1" type="color"/>
<data name="wing_color2" type="color"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="color"/>
<data name="unknown_chest_color" type="color"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="color"/>
<data name="unknown_legs_color" type="color"/>
<data name="unknown9" type="color"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<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_unknown_color1" type="color"/>
<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"/>
<data name="soga_face_file" type="str16"/>
<data name="soga_hair_face_color" type="color"/>
<data name="soga_hair_face_highlight_color" type="color"/>
<data name="soga_wing_file" type="str16"/>
<data name="soga_wing_color1" type="color"/>
<data name="soga_wing_color2" type="color"/>
<data name="soga_chest_file" type="str16"/>
<data name="soga_shirt_color" type="color"/>
<data name="soga_unknown_chest_color" type="color"/>
<data name="soga_legs_file" type="str16"/>
<data name="soga_pants_color" type="color"/>
<data name="soga_unknown_legs_color" type="color"/>
<data name="soga_unknown12" type="color"/>
<data name="soga_eyes2" type="float" size="3"/>
<data name="soga_ears" type="float" size="3"/>
<data name="soga_eye_brows" type="float" size="3"/>
<data name="soga_cheeks" type="float" size="3"/>
<data name="soga_lips" type="float" size="3"/>
<data name="soga_chin" type="float" size="3"/>
<data name="soga_nose" type="float" size="3"/>
<data name="soga_body_size" type="float"/>
<data name="soga_body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="1096" opcodeName="OP_CreateCharacterRequestMsg">
<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="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="hair_highlight" type="color"/>
<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"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="color"/>
<data name="hair_face_highlight_color" type="color"/>
<data name="wing_file" type="str16"/>
<data name="wing_color1" type="color"/>
<data name="wing_color2" type="color"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="color"/>
<data name="unknown_chest_color" type="color"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="color"/>
<data name="unknown_legs_color" type="color"/>
<data name="unknown9" type="color"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<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_unknown_color" type="color"/>
<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"/>
<data name="soga_face_file" type="str16"/>
<data name="soga_hair_face_color" type="color"/>
<data name="soga_hair_face_highlight_color" type="color"/>
<data name="soga_wing_file" type="str16"/>
<data name="soga_wing_color1" type="color"/>
<data name="soga_wing_color2" type="color"/>
<data name="soga_chest_file" type="str16"/>
<data name="soga_shirt_color" type="color"/>
<data name="soga_unknown_chest_color" type="color"/>
<data name="soga_legs_file" type="str16"/>
<data name="soga_pants_color" type="color"/>
<data name="soga_unknown_legs_color" type="color"/>
<data name="soga_unknown12" type="color"/>
<data name="soga_eyes2" type="float" size="3"/>
<data name="soga_ears" type="float" size="3"/>
<data name="soga_eye_brows" type="float" size="3"/>
<data name="soga_cheeks" type="float" size="3"/>
<data name="soga_lips" type="float" size="3"/>
<data name="soga_chin" type="float" size="3"/>
<data name="soga_nose" type="float" size="3"/>
<data name="soga_body_size" type="float"/>
<data name="soga_body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="57080" opcodeName="OP_CreateCharacterRequestMsg">
<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="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"/>
<data name="unknown_skin_color2" type="color"/>
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="hair_highlight" type="color"/>
<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"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="color"/>
<data name="hair_face_highlight_color" type="color"/>
<data name="wing_file" type="str16"/>
<data name="wing_color1" type="color"/>
<data name="wing_color2" type="color"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="color"/>
<data name="unknown_chest_color" type="color"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="color"/>
<data name="unknown_legs_color" type="color"/>
<data name="unknown9" type="color"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<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_unknown_color" type="color"/>
<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"/>
<data name="soga_face_file" type="str16"/>
<data name="soga_hair_face_color" type="color"/>
<data name="soga_hair_face_highlight_color" type="color"/>
<data name="soga_wing_file" type="str16"/>
<data name="soga_wing_color1" type="color"/>
<data name="soga_wing_color2" type="color"/>
<data name="soga_chest_file" type="str16"/>
<data name="soga_shirt_color" type="color"/>
<data name="soga_unknown_chest_color" type="color"/>
<data name="soga_legs_file" type="str16"/>
<data name="soga_pants_color" type="color"/>
<data name="soga_unknown_legs_color" type="color"/>
<data name="soga_unknown12" type="color"/>
<data name="soga_eyes2" type="float" size="3"/>
<data name="soga_ears" type="float" size="3"/>
<data name="soga_eye_brows" type="float" size="3"/>
<data name="soga_cheeks" type="float" size="3"/>
<data name="soga_lips" type="float" size="3"/>
<data name="soga_chin" type="float" size="3"/>
<data name="soga_nose" type="float" size="3"/>
<data name="soga_body_size" type="float"/>
<data name="soga_body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="60085" opcodeName="OP_CreateCharacterRequestMsg" >
<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="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"/>
<data name="unknown_skin_color2" type="color"/>
<data name="hair_color1" type="color"/>
<data name="hair_color2" type="color"/>
<data name="hair_highlight" type="color"/>
<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"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="color"/>
<data name="hair_face_highlight_color" type="color"/>
<data name="wing_file" type="str16"/>
<data name="wing_color1" type="color"/>
<data name="wing_color2" type="color"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="color"/>
<data name="unknown_chest_color" type="color"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="color"/>
<data name="unknown_legs_color" type="color"/>
<data name="unknown9" type="color"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<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_unknown_color" type="color"/>
<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"/>
<data name="soga_face_file" type="str16"/>
<data name="soga_hair_face_color" type="color"/>
<data name="soga_hair_face_highlight_color" type="color"/>
<data name="soga_wing_file" type="str16"/>
<data name="soga_wing_color1" type="color"/>
<data name="soga_wing_color2" type="color"/>
<data name="soga_chest_file" type="str16"/>
<data name="soga_shirt_color" type="color"/>
<data name="soga_unknown_chest_color" type="color"/>
<data name="soga_legs_file" type="str16"/>
<data name="soga_pants_color" type="color"/>
<data name="soga_unknown_legs_color" type="color"/>
<data name="soga_unknown12" type="color"/>
<data name="soga_eyes2" type="float" size="3"/>
<data name="soga_ears" type="float" size="3"/>
<data name="soga_eye_brows" type="float" size="3"/>
<data name="soga_cheeks" type="float" size="3"/>
<data name="soga_lips" type="float" size="3"/>
<data name="soga_chin" type="float" size="3"/>
<data name="soga_nose" type="float" size="3"/>
<data name="soga_body_size" type="float"/>
<data name="soga_body_age" type="float"/>
</struct>
<struct name="CreateCharacter" clientVersion="64659" opcodeName="OP_CreateCharacterRequestMsg" >
<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="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="u8" size="38"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="color"/>
<data name="hair_face_highlight_color" type="color"/>
<data name="wing_file" type="str16"/>
<data name="wing_color1" type="color"/>
<data name="wing_color2" type="color"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="color"/>
<data name="unknown_chest_color" type="color"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="color"/>
<data name="unknown_legs_color" type="color"/>
<data name="unknown9" type="color"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<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="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"/>
<data name="soga_face_file" type="str16"/>
<data name="soga_hair_face_color" type="color"/>
<data name="soga_hair_face_highlight_color" type="color"/>
<data name="soga_wing_file" type="str16"/>
<data name="soga_wing_color1" type="color"/>
<data name="soga_wing_color2" type="color"/>
<data name="soga_chest_file" type="str16"/>
<data name="soga_shirt_color" type="color"/>
<data name="soga_unknown_chest_color" type="color"/>
<data name="soga_legs_file" type="str16"/>
<data name="soga_pants_color" type="color"/>
<data name="soga_unknown_legs_color" type="color"/>
<data name="soga_unknown12" type="color"/>
<data name="soga_eyes2" type="float" size="3"/>
<data name="soga_ears" type="float" size="3"/>
<data name="soga_eye_brows" type="float" size="3"/>
<data name="soga_cheeks" type="float" size="3"/>
<data name="soga_lips" type="float" size="3"/>
<data name="soga_chin" type="float" size="3"/>
<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="u8" size="2"/>
</struct>
<struct name="CreateCharacter" clientVersion="65534" opcodeName="OP_CreateCharacterRequestMsg" >
<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="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="u8" size="38"/>
<data name="hair_file" type="str16"/>
<data name="hair_type_color" type="color"/>
<data name="hair_type_highlight_color" type="color"/>
<data name="face_file" type="str16"/>
<data name="hair_face_color" type="color"/>
<data name="hair_face_highlight_color" type="color"/>
<data name="wing_file" type="str16"/>
<data name="wing_color1" type="color"/>
<data name="wing_color2" type="color"/>
<data name="chest_file" type="str16"/>
<data name="shirt_color" type="color"/>
<data name="unknown_chest_color" type="color"/>
<data name="legs_file" type="str16"/>
<data name="pants_color" type="color"/>
<data name="unknown_legs_color" type="color"/>
<data name="unknown9" type="color"/>
<data name="eyes2" type="float" size="3"/>
<data name="ears" type="float" size="3"/>
<data name="eye_brows" type="float" size="3"/>
<data name="cheeks" type="float" size="3"/>
<data name="lips" type="float" size="3"/>
<data name="chin" type="float" size="3"/>
<data name="nose" type="float" size="3"/>
<data name="body_size" type="float"/>
<data name="body_age" type="float"/>
<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="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"/>
<data name="soga_face_file" type="str16"/>
<data name="soga_hair_face_color" type="color"/>
<data name="soga_hair_face_highlight_color" type="color"/>
<data name="soga_wing_file" type="str16"/>
<data name="soga_wing_color1" type="color"/>
<data name="soga_wing_color2" type="color"/>
<data name="soga_chest_file" type="str16"/>
<data name="soga_shirt_color" type="color"/>
<data name="soga_unknown_chest_color" type="color"/>
<data name="soga_legs_file" type="str16"/>
<data name="soga_pants_color" type="color"/>
<data name="soga_unknown_legs_color" type="color"/>
<data name="soga_unknown12" type="color"/>
<data name="soga_eyes2" type="float" size="3"/>
<data name="soga_ears" type="float" size="3"/>
<data name="soga_eye_brows" type="float" size="3"/>
<data name="soga_cheeks" type="float" size="3"/>
<data name="soga_lips" type="float" size="3"/>
<data name="soga_chin" type="float" size="3"/>
<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="u8" size="2"/>
</struct>
<struct name="BadLanguageFilter" clientVersion="1" opcodeName="OP_BadLanguageFilter">
<data name="num_words" type="u16" oversizedValue="255"/>
<data name="words_array" type="Array" arraySizeVariable="num_words">
<data name="word" type="str16"/>
</data>
</struct>

1134
defs/xml/login.xml Normal file

File diff suppressed because it is too large Load Diff

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
}