Skip to content

Instantly share code, notes, and snippets.

@ethanmoffat
Last active August 5, 2023 03:10
Show Gist options
  • Save ethanmoffat/95eed4ef0eeb524c8a505acb1bcbf956 to your computer and use it in GitHub Desktop.
Save ethanmoffat/95eed4ef0eeb524c8a505acb1bcbf956 to your computer and use it in GitHub Desktop.
EO server skeleton in go
package main
import (
"bufio"
"fmt"
"io"
"math/rand"
"net"
"os"
"os/signal"
"sync"
"time"
"github.com/ethanmoffat/eolib-go/pkg/eolib/data"
"github.com/ethanmoffat/eolib-go/pkg/eolib/encrypt"
"github.com/ethanmoffat/eolib-go/pkg/eolib/packet"
eonet "github.com/ethanmoffat/eolib-go/pkg/eolib/protocol/net"
eoclient "github.com/ethanmoffat/eolib-go/pkg/eolib/protocol/net/client"
"github.com/ethanmoffat/eolib-go/pkg/eolib/protocol/net/server"
)
var (
shutdown bool
localRand *rand.Rand
clients map[int]*client
clientsmut sync.Mutex
)
type client struct {
conn net.Conn
sequence packet.PacketSequencer
needPong bool
clientMulti int
serverMulti int
id int
upcomingSequence *packet.PingSequence
}
func main() {
localRand = rand.New(rand.NewSource(time.Now().UnixNano()))
clients = make(map[int]*client)
listener, err := net.Listen("tcp", "127.0.0.1:8078")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer listener.Close()
fmt.Println("listening")
interruptChan := make(chan os.Signal, 1)
signal.Notify(interruptChan, os.Interrupt)
defer close(interruptChan)
cancelSignal := make(chan bool, 1)
defer close(cancelSignal)
go acceptLoop(listener, cancelSignal)
pingTick := *time.NewTicker(time.Minute * 1)
go pingThread(pingTick.C)
sig := <-interruptChan
shutdown = true
fmt.Println("Exiting due to signal:", sig)
clientsmut.Lock()
for _, c := range clients {
c.conn.Close()
cancelSignal <- true
}
clientsmut.Unlock()
}
func acceptLoop(listener net.Listener, cancelSignal <-chan bool) {
if conn, err := listener.Accept(); err != nil {
if !shutdown {
fmt.Println("Exiting due to error:", err)
os.Exit(1)
}
} else {
client := &client{
conn: conn,
sequence: packet.NewPacketSequencer(packet.NewZeroSequence()),
needPong: false,
clientMulti: 0,
serverMulti: 0,
id: rand.Intn(10000) + 20000,
upcomingSequence: nil,
}
clients[client.id] = client
go client.handleConnection(cancelSignal)
}
}
func pingThread(tick <-chan time.Time) {
for range tick {
clientsmut.Lock()
for _, c := range clients {
if c.needPong {
c.close()
continue
}
c.upcomingSequence = packet.GeneratePingSequence(localRand)
c.needPong = true
pkt := &server.ConnectionPlayerServerPacket{
Seq1: c.upcomingSequence.Seq1(),
Seq2: c.upcomingSequence.Seq2(),
}
if err := c.send(pkt); err != nil {
fmt.Println("failed to send CONNECTION_PLAYER packet to client:", c.conn.RemoteAddr().String())
}
}
clientsmut.Unlock()
}
}
func (client *client) handleConnection(cancelSignal <-chan bool) {
fmt.Println("New connection from", client.conn.RemoteAddr().String())
defer client.close()
netReader := bufio.NewReader(client.conn)
loop:
for {
select {
case <-cancelSignal:
break loop
default:
var len1, len2 byte
var err error
if len1, err = netReader.ReadByte(); err != nil {
fmt.Println("Error reading len1 byte:", err)
break loop
}
if len2, err = netReader.ReadByte(); err != nil {
fmt.Println("error reading len2 byte:", err)
break loop
}
decodedLen := data.DecodeNumber([]byte{len1, len2})
bytes := make([]byte, decodedLen)
if _, err = io.ReadFull(netReader, bytes); err != nil {
fmt.Println("error reading data:", err)
break loop
}
var decodedBytes []byte
if client.clientMulti != 0 {
decodedBytes, _ = encrypt.SwapMultiples(encrypt.Deinterleave(encrypt.FlipMsb(bytes)), client.clientMulti)
} else {
decodedBytes = bytes
}
eoReader := data.NewEoReader(decodedBytes)
action := eonet.PacketAction(eoReader.GetByte())
family := eonet.PacketFamily(eoReader.GetByte())
if err = client.handleSequence(family, action, eoReader); err != nil {
fmt.Println("error handling sequence:", err)
break loop
}
dataReader, err := eoReader.SliceFromCurrent()
if err != nil {
fmt.Println(err)
break loop
}
var pkt eonet.Packet
if pkt, err = eoclient.PacketFromId(family, action); err != nil {
fmt.Println("unrecognized packet type:", err)
}
if err = pkt.Deserialize(dataReader); err != nil {
fmt.Println("could not deserialize packet:", err)
}
var response eonet.Packet = nil
switch v := pkt.(type) {
case *eoclient.InitInitClientPacket:
sequence := packet.GenerateInitSequence(localRand)
client.sequence.SetSequenceStart(*sequence)
client.clientMulti = rand.Intn(7) + 6
client.serverMulti = rand.Intn(7) + 6
response = &server.InitInitServerPacket{
ReplyCode: server.InitReply_Ok,
ReplyCodeData: &server.InitInitReplyCodeDataOk{
Seq1: sequence.Seq1(),
Seq2: sequence.Seq2(),
ClientEncryptionMultiple: client.clientMulti,
ServerEncryptionMultiple: client.serverMulti,
PlayerId: client.id,
ChallengeResponse: encrypt.ServerVerificationHash(v.Challenge),
},
}
fmt.Printf("got init packet from %s: v%d.%d.%d\n", client.conn.RemoteAddr().String(), v.Version.Major, v.Version.Minor, v.Version.Patch)
case *eoclient.ConnectionAcceptClientPacket:
if v.ClientEncryptionMultiple != client.clientMulti || v.ServerEncryptionMultiple != client.serverMulti {
fmt.Println("mismatch in expected client/server multiples, dropping connection")
break loop
}
if v.PlayerId != client.id {
fmt.Println("mismatch in expected client id, dropping connection")
break loop
}
fmt.Println("Got expected connection accept packet from", client.conn.RemoteAddr().String())
case *eoclient.ConnectionPingClientPacket:
client.needPong = false
}
if response != nil {
if err = client.send(response); err != nil {
break loop
}
}
}
}
}
func (client *client) handleSequence(family eonet.PacketFamily, action eonet.PacketAction, reader *data.EoReader) error {
if family == eonet.PacketFamily_Init && action == eonet.PacketAction_Init {
client.sequence.NextSequence()
return nil
}
if family == eonet.PacketFamily_Connection && action == eonet.PacketAction_Ping {
client.sequence.SetSequenceStart(client.upcomingSequence)
}
serverSequence := client.sequence.NextSequence()
var clientSequence int
if serverSequence >= data.CHAR_MAX {
clientSequence = reader.GetShort()
} else {
clientSequence = reader.GetChar()
}
if clientSequence != serverSequence {
return fmt.Errorf("expected sequence %d, got %d", serverSequence, clientSequence)
}
return nil
}
func (client *client) send(response eonet.Packet) (err error) {
writer := data.NewEoWriter()
writer.AddByte(int(response.Action()))
writer.AddByte(int(response.Family()))
if err = response.Serialize(writer); err != nil {
return
}
var encodedBytes []byte
switch response.(type) {
case *server.InitInitServerPacket:
encodedBytes = writer.Array()
default:
encodedBytes, _ = encrypt.SwapMultiples(writer.Array(), client.serverMulti)
encodedBytes = encrypt.Interleave(encodedBytes)
encodedBytes = encrypt.FlipMsb(encodedBytes)
}
encodedLen := data.EncodeNumber(len(encodedBytes))
fullBytes := append(encodedLen[:2], encodedBytes...)
totalWritten := 0
for {
var nextWritten int
nextWritten, err = client.conn.Write(fullBytes[totalWritten:])
if err != nil {
return
}
totalWritten += nextWritten
if totalWritten == len(fullBytes) {
break
}
}
return
}
func (client *client) close() {
clientsmut.Lock()
client.conn.Close()
delete(clients, client.id)
clientsmut.Unlock()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment