1
0
2025-07-02 13:14:42 -05:00

301 lines
7.6 KiB
Go

package main
import (
"encoding/binary"
"log"
"time"
"github.com/goccy/go-json"
"github.com/panjf2000/gnet/v2"
)
type TCPServer struct {
gnet.BuiltinEventEngine
server *LoginServer
clients *ClientManager
worlds *WorldManager
}
func (s *TCPServer) OnBoot(eng gnet.Engine) gnet.Action {
log.Printf("TCP server started on %s", eng.Addr())
return gnet.None
}
func (s *TCPServer) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
addr := c.RemoteAddr().String()
log.Printf("New connection from %s", addr)
client := &Client{
conn: c,
address: addr,
connected: time.Now(),
state: StateConnected,
version: 0,
}
s.clients.AddClient(c.Fd(), client)
return nil, gnet.None
}
func (s *TCPServer) OnClose(c gnet.Conn, err error) gnet.Action {
s.clients.RemoveClient(c.Fd())
if err != nil {
log.Printf("Connection closed with error: %v", err)
}
return gnet.None
}
func (s *TCPServer) OnTraffic(c gnet.Conn) gnet.Action {
client := s.clients.GetClient(c.Fd())
if client == nil {
return gnet.Close
}
for {
packet, err := s.readPacket(c)
if err != nil {
if err != ErrIncompletePacket {
log.Printf("Error reading packet: %v", err)
return gnet.Close
}
break
}
if err := s.handlePacket(client, packet); err != nil {
log.Printf("Error handling packet: %v", err)
return gnet.Close
}
}
return gnet.None
}
func (s *TCPServer) readPacket(c gnet.Conn) (*EQ2Packet, error) {
// Read packet header (2 bytes length + 2 bytes opcode)
if c.InboundBuffered() < 4 {
return nil, ErrIncompletePacket
}
header := make([]byte, 4)
if _, err := c.Peek(header); err != nil {
return nil, err
}
length := binary.LittleEndian.Uint16(header[0:2])
opcode := binary.LittleEndian.Uint16(header[2:4])
totalLength := int(length) + 4 // Add header size
if c.InboundBuffered() < totalLength {
return nil, ErrIncompletePacket
}
// Read complete packet
data := make([]byte, totalLength)
if _, err := c.Read(data); err != nil {
return nil, err
}
return &EQ2Packet{
Opcode: opcode,
Size: length,
Data: data[4:], // Skip header
}, nil
}
func (s *TCPServer) handlePacket(client *Client, packet *EQ2Packet) error {
switch packet.Opcode {
case OpLoginRequest:
return s.handleLoginRequest(client, packet)
case OpWorldListRequest:
return s.handleWorldListRequest(client, packet)
case OpCharacterListRequest:
return s.handleCharacterListRequest(client, packet)
case OpCreateCharacter:
return s.handleCreateCharacter(client, packet)
case OpDeleteCharacter:
return s.handleDeleteCharacter(client, packet)
case OpPlayCharacter:
return s.handlePlayCharacter(client, packet)
default:
log.Printf("Unknown opcode: 0x%04X", packet.Opcode)
}
return nil
}
func (s *TCPServer) handleLoginRequest(client *Client, packet *EQ2Packet) error {
var loginReq LoginRequest
if err := json.Unmarshal(packet.Data, &loginReq); err != nil {
return s.sendLoginDenied(client, "Invalid login data")
}
account, err := s.server.db.Get(s.server.ctx)
if err != nil {
return s.sendLoginDenied(client, "Database error")
}
defer s.server.db.Put(account)
// Authenticate user
authenticated, accountID, err := authenticateUser(account, loginReq.Username, loginReq.Password, s.server.config.Game.AllowAccountCreation)
if err != nil {
return s.sendLoginDenied(client, "Authentication failed")
}
if !authenticated {
return s.sendLoginDenied(client, "Invalid credentials")
}
client.accountID = accountID
client.accountName = loginReq.Username
client.state = StateAuthenticated
client.version = loginReq.Version
return s.sendLoginAccepted(client)
}
func (s *TCPServer) sendLoginAccepted(client *Client) error {
response := LoginResponse{
Response: 1,
AccountID: client.accountID,
SubLevel: s.server.config.Game.DefaultSubscriptionLevel,
RaceFlag: s.server.config.Game.EnabledRaces,
ClassFlag: 0x7FFFFFE,
Username: client.accountName,
Unknown5: s.server.config.Game.ExpansionFlag,
CitiesFlag: s.server.config.Game.CitiesFlag,
}
return s.sendPacket(client, OpLoginReply, &response)
}
func (s *TCPServer) sendLoginDenied(client *Client, reason string) error {
response := LoginResponse{
Response: 0,
Reason: reason,
}
return s.sendPacket(client, OpLoginReply, &response)
}
func (s *TCPServer) handleWorldListRequest(client *Client, packet *EQ2Packet) error {
if client.state != StateAuthenticated {
return s.sendError(client, "Not authenticated")
}
worlds := s.worlds.GetWorldList()
worldList := WorldListResponse{
NumWorlds: uint32(len(worlds)),
Worlds: worlds,
}
return s.sendPacket(client, OpWorldListReply, &worldList)
}
func (s *TCPServer) handleCharacterListRequest(client *Client, packet *EQ2Packet) error {
if client.state != StateAuthenticated {
return s.sendError(client, "Not authenticated")
}
conn := s.server.db.Get(s.server.ctx)
defer s.server.db.Put(conn)
characters, err := getCharacterList(conn, client.accountID)
if err != nil {
return s.sendError(client, "Failed to load characters")
}
charList := CharacterListResponse{
NumCharacters: uint32(len(characters)),
Characters: characters,
AccountID: client.accountID,
MaxChars: uint32(s.server.config.Game.MaxCharactersPerAccount),
}
return s.sendPacket(client, OpCharacterListReply, &charList)
}
func (s *TCPServer) sendPacket(client *Client, opcode uint16, data any) error {
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
packet := make([]byte, 4+len(jsonData))
binary.LittleEndian.PutUint16(packet[0:2], uint16(len(jsonData)))
binary.LittleEndian.PutUint16(packet[2:4], opcode)
copy(packet[4:], jsonData)
_, err = client.conn.Write(packet)
return err
}
func (s *TCPServer) sendError(client *Client, message string) error {
return s.sendPacket(client, OpError, map[string]string{"error": message})
}
func (s *TCPServer) handleCreateCharacter(client *Client, packet *EQ2Packet) error {
var createReq CreateCharacterRequest
if err := json.Unmarshal(packet.Data, &createReq); err != nil {
return s.sendError(client, "Invalid character data")
}
conn := s.server.db.Get(s.server.ctx)
defer s.server.db.Put(conn)
charID, err := createCharacter(conn, client.accountID, &createReq)
if err != nil {
return s.sendError(client, "Failed to create character")
}
response := CreateCharacterResponse{
Success: true,
CharacterID: charID,
Name: createReq.Name,
}
return s.sendPacket(client, OpCreateCharacterReply, &response)
}
func (s *TCPServer) handleDeleteCharacter(client *Client, packet *EQ2Packet) error {
var deleteReq DeleteCharacterRequest
if err := json.Unmarshal(packet.Data, &deleteReq); err != nil {
return s.sendError(client, "Invalid delete request")
}
conn := s.server.db.Get(s.server.ctx)
defer s.server.db.Put(conn)
if err := deleteCharacter(conn, client.accountID, deleteReq.CharacterID); err != nil {
return s.sendError(client, "Failed to delete character")
}
response := DeleteCharacterResponse{
Success: true,
CharacterID: deleteReq.CharacterID,
}
return s.sendPacket(client, OpDeleteCharacterReply, &response)
}
func (s *TCPServer) handlePlayCharacter(client *Client, packet *EQ2Packet) error {
var playReq PlayCharacterRequest
if err := json.Unmarshal(packet.Data, &playReq); err != nil {
return s.sendError(client, "Invalid play request")
}
world := s.worlds.GetWorld(playReq.ServerID)
if world == nil {
return s.sendError(client, "World server not available")
}
// Generate session key and send to world server
sessionKey := generateSessionKey()
response := PlayCharacterResponse{
Success: true,
ServerIP: world.Address,
ServerPort: world.Port,
SessionKey: sessionKey,
}
return s.sendPacket(client, OpPlayCharacterReply, &response)
}