Files
Beyhan Oğur d5f52cc3d3 first commit
2026-09-03 04:45:36 +03:00

29 KiB
Raw Permalink Blame History

Protocol Handling

**Referenced Files in This Document** - [BasePacket.cs](file://ClassicAssist.Shared/UO/Network/Packets/BasePacket.cs) - [IncomingPacketHandlers.cs](file://ClassicAssist.Shared/UO/Network/IncomingPacketHandlers.cs) - [OutgoingPacketHandlers.cs](file://ClassicAssist.Shared/UO/Network/OutgoingPacketHandlers.cs) - [PacketHandler.cs](file://ClassicAssist.Shared/UO/Network/PacketHandler.cs) - [Compression.cs](file://ClassicAssist.Shared/UO/Compression.cs) - [PacketReader.cs](file://ClassicAssist.Shared/UO/Data/PacketReader.cs) - [PacketWriter.cs](file://ClassicAssist.Shared/UO/Data/PacketWriter.cs) - [MobileIncoming.cs](file://ClassicAssist.Shared/UO/Network/Packets/MobileIncoming.cs) - [MovementRequest.cs](file://ClassicAssist.Shared/UO/Network/Packets/MovementRequest.cs) - [CastSpell.cs](file://ClassicAssist.Shared/UO/Network/Packets/CastSpell.cs) - [UseObject.cs](file://ClassicAssist.Shared/UO/Network/Packets/UseObject.cs) - [GumpButtonClick.cs](file://ClassicAssist.Shared/UO/Network/Packets/GumpButtonClick.cs) - [UnicodeText.cs](file://ClassicAssist.Shared/UO/Network/Packets/UnicodeText.cs) - [Ping.cs](file://ClassicAssist.Shared/UO/Network/Packets/Ping.cs) - [ContainerContentsPacket.cs](file://ClassicAssist.Shared/UO/Network/Packets/ContainerContentsPacket.cs) - [IncomingPacketFilters.cs](file://ClassicAssist.Shared/UO/Network/IncomingPacketFilters.cs) - [OutgoingPacketFilters.cs](file://ClassicAssist.Shared/UO/Network/OutgoingPacketFilters.cs) - [ActionPacketQueue.cs](file://ClassicAssist.Shared/UO/Network/ActionPacketQueue.cs) - [PacketFilter.cs](file://ClassicAssist.Shared/UO/Network/PacketFilter/PacketFilter.cs) - [PacketWaitEntry.cs](file://ClassicAssist.Shared/UO/Network/PacketFilter/PacketWaitEntry.cs) - [PacketDirection.cs](file://ClassicAssist.Shared/UO/Data/PacketDirection.cs)

Table of Contents

  1. Introduction
  2. Project Structure
  3. Core Components
  4. Architecture Overview
  5. Detailed Component Analysis
  6. Dependency Analysis
  7. Performance Considerations
  8. Troubleshooting Guide
  9. Conclusion
  10. Appendices

Introduction

This document explains how Ultima Online (UO) protocol handling is implemented in ClassicAssist.Avalonia, focusing on packet structure, direction handling (incoming/outgoing), command identification, compression algorithms, base packet class hierarchy, custom packet implementation, protocol version detection and compatibility, server negotiation processes, examples for extending the protocol, security considerations, and validation strategies. The goal is to provide both a high-level understanding and detailed guidance for developers integrating or extending UO networking features.

Project Structure

The UO networking stack is primarily located under ClassicAssist.Shared/UO. Key areas include:

  • Data I/O: PacketReader and PacketWriter for reading/writing network payloads
  • Compression: Dedicated module for decompressing compressed packets
  • Packet types: A rich set of incoming and outgoing packet classes under Network/Packets
  • Handlers: Incoming and outgoing handler registries and dispatchers
  • Filters and waits: Filtering, conditional processing, and request/response waiting utilities
  • Direction enum: Centralized definition of packet directions
graph TB
subgraph "I/O"
PR["PacketReader"]
PW["PacketWriter"]
end
subgraph "Packets"
BP["BasePacket"]
MI["MobileIncoming"]
MR["MovementRequest"]
CS["CastSpell"]
UO["UseObject"]
GB["GumpButtonClick"]
UT["UnicodeText"]
PN["Ping"]
CC["ContainerContentsPacket"]
end
subgraph "Handlers"
IPH["IncomingPacketHandlers"]
OPH["OutgoingPacketHandlers"]
PH["PacketHandler"]
end
subgraph "Filters & Wait"
IPF["IncomingPacketFilters"]
OPF["OutgoingPacketFilters"]
PF["PacketFilter"]
PWE["PacketWaitEntry"]
end
subgraph "Misc"
COMP["Compression"]
PD["PacketDirection"]
APQ["ActionPacketQueue"]
end
PR --> BP
PW --> BP
BP --> MI
BP --> MR
BP --> CS
BP --> UO
BP --> GB
BP --> UT
BP --> PN
BP --> CC
IPH --> PF
IPH --> PWE
OPH --> OPF
PH --> IPH
PH --> OPH
PR --> COMP
PW --> COMP
IPH --> APQ
OPH --> APQ

Diagram sources

Section sources

Core Components

  • Base packet class hierarchy: All concrete packet types derive from a common base that encapsulates shared fields such as header, length, and direction metadata. This enables uniform serialization/deserialization and consistent routing through handlers.
  • Packet I/O: PacketReader and PacketWriter provide typed read/write operations for primitive types used by UO packets (e.g., integers, strings, entities). They are used by packet constructors and Write methods.
  • Compression: Decompression logic is applied before parsing incoming packets when indicated by headers or negotiated capabilities.
  • Direction handling: Incoming and outgoing flows are separated via dedicated handler registries and filters. Each packet type declares its direction and command ID for routing.
  • Command identification: Each packet has a unique command identifier used by the dispatcher to route to the appropriate handler method.
  • Filters and waits: Incoming and outgoing filters allow selective processing, logging, or blocking. Packet wait entries support request-response matching.

Section sources

Architecture Overview

The networking architecture separates concerns into layers:

  • Transport layer: Reads raw bytes and applies decompression if needed.
  • Parsing layer: Converts byte streams into strongly-typed packet instances using PacketReader.
  • Dispatch layer: Routes packets to registered handlers based on command IDs and direction.
  • Filter layer: Applies optional filtering and transformation before and after handler execution.
  • Queueing layer: ActionPacketQueue can serialize time-sensitive actions to avoid race conditions.
sequenceDiagram
participant Net as "Network Layer"
participant IO as "PacketReader/Writer"
participant Decomp as "Compression"
participant Parse as "Packet Types"
participant Disp as "PacketHandler"
participant InH as "IncomingPacketHandlers"
participant OutH as "OutgoingPacketHandlers"
participant Q as "ActionPacketQueue"
Note over Net,Q : Incoming Flow
Net->>IO : Read raw bytes
IO->>Decomp : Decompress if flagged
Decomp-->>IO : Uncompressed payload
IO->>Parse : Construct typed packet
Parse-->>Disp : Packet instance with command ID
Disp->>InH : Route by command ID
InH->>Q : Enqueue action if needed
Q-->>InH : Execute handler thread-safe
Note over Net,Q : Outgoing Flow
OutH->>Parse : Build typed packet
Parse->>IO : Serialize to bytes
IO->>Net : Send bytes

Diagram sources

Detailed Component Analysis

Base Packet Class Hierarchy

All packet types inherit from a common base that provides:

  • Shared header and length management
  • Direction metadata (incoming/outgoing)
  • Common serialization hooks
  • Utility accessors for command ID and version gating

Concrete packet implementations override read/write behavior specific to their wire format. Examples include mobile updates, movement requests, spell casting, object usage, gump interactions, chat text, pings, and container contents.

classDiagram
class BasePacket {
+CommandId
+Direction
+Read(reader)
+Write(writer)
}
class MobileIncoming
class MovementRequest
class CastSpell
class UseObject
class GumpButtonClick
class UnicodeText
class Ping
class ContainerContentsPacket
BasePacket <|-- MobileIncoming
BasePacket <|-- MovementRequest
BasePacket <|-- CastSpell
BasePacket <|-- UseObject
BasePacket <|-- GumpButtonClick
BasePacket <|-- UnicodeText
BasePacket <|-- Ping
BasePacket <|-- ContainerContentsPacket

Diagram sources

Section sources

Incoming Packet Processing

Incoming packets flow through:

  • Decompression (if applicable)
  • Parsing into typed packet objects
  • Filtering (optional)
  • Dispatch to registered handlers by command ID
  • Optional queueing for thread safety
flowchart TD
Start(["Receive Bytes"]) --> CheckComp["Check Compression Flag"]
CheckComp --> |Compressed| Decompress["Decompress Payload"]
CheckComp --> |Not Compressed| Parse["Parse Typed Packet"]
Decompress --> Parse
Parse --> Filter["Apply Incoming Filters"]
Filter --> Route["Route by Command ID"]
Route --> Handle["Invoke Handler Method"]
Handle --> Queue{"Needs Queue?"}
Queue --> |Yes| Enq["Enqueue Action"]
Queue --> |No| Done(["Complete"])
Enq --> Done

Diagram sources

Section sources

Outgoing Packet Construction

Outgoing packets are built by:

  • Instantiating typed packet classes
  • Writing fields using PacketWriter
  • Applying outgoing filters
  • Sending serialized bytes to the network
sequenceDiagram
participant App as "Application Logic"
participant OutH as "OutgoingPacketHandlers"
participant Writer as "PacketWriter"
participant Net as "Network Layer"
App->>OutH : Request send(packetType, data)
OutH->>Writer : Serialize packet fields
Writer-->>OutH : Byte array
OutH->>OutH : Apply Outgoing Filters
OutH->>Net : Send bytes

Diagram sources

Section sources

Compression Algorithms and Implementation Details

Compression is applied to reduce bandwidth for large payloads. The typical pattern:

  • Detect compression flag in packet header or session state
  • Decompress incoming payloads before parsing
  • Optionally compress outgoing payloads if negotiated
flowchart TD
InStart(["Incoming Packet"]) --> Header["Read Header"]
Header --> Flag{"Compression Flag Set?"}
Flag --> |Yes| DeC["Decompress Using Algorithm"]
Flag --> |No| Skip["Skip Decompression"]
DeC --> Parse["Parse Fields"]
Skip --> Parse
Parse --> End(["Typed Packet"])

Diagram sources

Section sources

Command Identification System

Each packet type carries a command identifier used by the dispatcher to route to the correct handler. The system supports:

  • Unique command IDs per packet type
  • Version-aware routing for compatibility across client/server versions
  • Direction-based registration (incoming vs outgoing)
classDiagram
class PacketHandler {
+RegisterIncoming(commandId, handler)
+RegisterOutgoing(commandId, handler)
+DispatchIncoming(packet)
+DispatchOutgoing(packet)
}
class IncomingPacketHandlers {
+HandleMobileIncoming()
+HandleMovementRequest()
+HandleCastSpell()
+HandleUseObject()
+HandleGumpButtonClick()
+HandleUnicodeText()
+HandlePing()
+HandleContainerContents()
}
class OutgoingPacketHandlers {
+SendMovementRequest()
+SendCastSpell()
+SendUseObject()
+SendGumpButtonClick()
+SendUnicodeText()
+SendPing()
}
PacketHandler --> IncomingPacketHandlers : "routes to"
PacketHandler --> OutgoingPacketHandlers : "routes to"

Diagram sources

Section sources

Protocol Version Detection and Compatibility Layers

Version detection ensures compatibility across different UO client/server versions:

  • Negotiation phase determines supported packet formats and features
  • Version-gated routing allows older/newer packet variants to coexist
  • Feature flags enable graceful degradation or extension points
stateDiagram-v2
[*] --> Connecting
Connecting --> Negotiate : "Initiate handshake"
Negotiate --> VersionDetected : "Server responds with version"
VersionDetected --> Compatible : "Supported range"
VersionDetected --> Incompatible : "Outside supported range"
Compatible --> Connected : "Proceed"
Incompatible --> Disconnected : "Abort connection"
Connected --> [*]
Disconnected --> [*]

[No sources needed since this diagram shows conceptual workflow, not actual code structure]

Server Negotiation Processes

Negotiation typically involves:

  • Client announces capabilities and preferred version
  • Server replies with agreed protocol version and feature flags
  • Both sides adjust packet parsing and sending accordingly
sequenceDiagram
participant Client as "Client"
participant Server as "Server"
participant Handshake as "Negotiation Logic"
Client->>Handshake : "Send capability/version"
Handshake->>Server : "Forward to server"
Server-->>Handshake : "Reply with version/features"
Handshake->>Client : "Confirm negotiated settings"
Handshake-->>Client : "Ready to exchange packets"

[No sources needed since this diagram shows conceptual workflow, not actual code structure]

Creating New Packet Types

To implement a new packet:

  • Derive from the base packet class
  • Implement read/write logic using PacketReader/PacketWriter
  • Assign a unique command ID
  • Register an incoming handler (if applicable)
  • Provide outgoing helpers in OutgoingPacketHandlers
  • Add any necessary filters or waits
flowchart TD
Define["Define new packet class"] --> ReadWrite["Implement Read/Write"]
ReadWrite --> CmdID["Assign Command ID"]
CmdID --> RegIn["Register Incoming Handler (if needed)"]
CmdIn["Add Outgoing Helper"] --> Test["Unit test serialization"]
RegIn --> Test
Test --> Deploy["Deploy and integrate"]

Section sources

Handling Different UO Client Versions

Strategies:

  • Maintain multiple packet variants for different versions
  • Use version checks during parsing/sending to select the right variant
  • Employ feature flags to conditionally include/exclude fields

Section sources

Implementing Protocol Extensions

Extensions should:

  • Introduce new command IDs carefully
  • Preserve backward compatibility where possible
  • Use optional fields and version gating
  • Document expected server/client behavior

Section sources

Dependency Analysis

Key dependencies and relationships:

  • Packet types depend on PacketReader/PacketWriter for serialization
  • Handlers depend on PacketHandler for dispatch
  • Filters depend on PacketFilter infrastructure
  • Compression depends on algorithm implementations
  • Direction enum centralizes packet direction semantics
graph TB
PR["PacketReader"] --> BP["BasePacket"]
PW["PacketWriter"] --> BP
BP --> MI["MobileIncoming"]
BP --> MR["MovementRequest"]
BP --> CS["CastSpell"]
BP --> UO["UseObject"]
BP --> GB["GumpButtonClick"]
BP --> UT["UnicodeText"]
BP --> PN["Ping"]
BP --> CC["ContainerContentsPacket"]
PH["PacketHandler"] --> IPH["IncomingPacketHandlers"]
PH --> OPH["OutgoingPacketHandlers"]
IPH --> IPF["IncomingPacketFilters"]
OPH --> OPF["OutgoingPacketFilters"]
PR --> COMP["Compression"]
PW --> COMP
PD["PacketDirection"] --> BP

Diagram sources

Section sources

Performance Considerations

  • Minimize allocations by reusing buffers where safe
  • Avoid excessive copying; prefer span-like patterns if available
  • Batch small outgoing packets when appropriate
  • Use compression selectively for large payloads
  • Keep handler logic lightweight; offload heavy work to background tasks
  • Use ActionPacketQueue to serialize time-sensitive operations

[No sources needed since this section provides general guidance]

Troubleshooting Guide

Common issues and strategies:

  • Mismatched command IDs: Ensure unique IDs and correct registration
  • Incorrect compression handling: Verify flags and decompression paths
  • Serialization errors: Validate field order and sizes against spec
  • Race conditions: Use ActionPacketQueue for ordered execution
  • Filtering misbehavior: Inspect filter conditions and priorities
  • Request-response mismatches: Use PacketWaitEntry to correlate pairs

Section sources

Conclusion

ClassicAssist.Avalonia’s UO protocol handling is structured around a clear separation of concerns: typed packet classes, robust I/O utilities, centralized dispatch, flexible filtering, and optional compression. By following the patterns outlined here—deriving from the base packet, implementing precise read/write logic, registering handlers, and applying version-aware routing—you can extend the protocol safely and efficiently while maintaining compatibility across client/server versions.

[No sources needed since this section summarizes without analyzing specific files]

Appendices

Security Considerations and Validation Strategies

  • Validate all incoming fields (ranges, lengths, enums) before processing
  • Reject malformed or unexpected packets early
  • Limit resource consumption per packet (size caps, timeouts)
  • Sanitize string inputs and avoid unsafe conversions
  • Log suspicious activity for auditing
  • Apply outgoing filters to prevent accidental leakage of sensitive data

Section sources