mirror of
https://github.com/CPunch/gopenfusion.git
synced 2024-12-04 14:36:02 +00:00
Compare commits
6 Commits
7ebe80d6e3
...
a78dedcb89
Author | SHA1 | Date | |
---|---|---|---|
a78dedcb89 | |||
dcb86e2518 | |||
458e907c99 | |||
74b68863b1 | |||
83b664da93 | |||
670d4a514c |
@ -1,10 +1,10 @@
|
||||
# gopenfusion
|
||||
|
||||
A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pages/fusionfall-openfusion/) written in Go.
|
||||
A toy implementation of the [Fusionfall Packet Protocol](https://openpunk.com/pages/fusionfall-openfusion/) and accompanying services, written in Go.
|
||||
|
||||
## Landwalker demo
|
||||
|
||||
An implementation of a landwalker server is located in `login/` && `shard/`. This includes a functional login server and a dummy shard (supporting the minimum amount of packets necessary). The DB implementation in `core/db/` matches the OpenFusion 1.4 SQLite tables, which the login server uses. There's no support for NPCs nor other players, and is liable to softlock the client.
|
||||
An implementation of a landwalker server is located in `login/` && `shard/`. This includes a functional login server and a dummy shard (supporting the minimum amount of packets necessary). There is minimal support for NPCs, and minimal support for player interaction (chat & player movement being mostly it).
|
||||
|
||||
Startup the environment using
|
||||
|
||||
@ -13,6 +13,8 @@ $ chmod +x ./build.sh && ./build.sh
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
The environment consists of a shard service, login service, redis && postgres containers. redis is used to pass login metadata between the login and shard services, while postgres is just used to store player accounts and characters.
|
||||
|
||||
login server is hosted at `127.0.0.1:23000`, just join from your [favorite client](https://github.com/OpenFusionProject/OpenFusion/releases/latest)
|
||||
|
||||
## Generating structures
|
||||
|
@ -7,8 +7,8 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/core/db"
|
||||
"github.com/CPunch/gopenfusion/core/redis"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
|
||||
"github.com/google/subcommands"
|
||||
)
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/georgysavva/scany/v2/sqlscan"
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ package db
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
type Inventory struct {
|
@ -4,8 +4,8 @@ import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/core/entity"
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/blockloop/scan"
|
||||
)
|
||||
|
@ -1,6 +1,6 @@
|
||||
package entity
|
||||
|
||||
import "github.com/CPunch/gopenfusion/core/protocol"
|
||||
import "github.com/CPunch/gopenfusion/internal/protocol"
|
||||
|
||||
type EntityKind int
|
||||
|
@ -3,7 +3,7 @@ package entity
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
type NPC struct {
|
@ -2,7 +2,7 @@ package entity
|
||||
|
||||
import (
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
type Player struct {
|
@ -8,7 +8,7 @@ import (
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/CPunch/gopenfusion/core/protocol/pool"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol/pool"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -51,6 +51,9 @@ func (peer *CNPeer) Send(typeID uint32, data ...interface{}) error {
|
||||
buf := pool.Get()
|
||||
defer pool.Put(buf)
|
||||
|
||||
// allocate space for packet size
|
||||
buf.Write(make([]byte, 4))
|
||||
|
||||
// body start
|
||||
pkt := NewPacket(buf)
|
||||
|
||||
@ -66,25 +69,22 @@ func (peer *CNPeer) Send(typeID uint32, data ...interface{}) error {
|
||||
}
|
||||
}
|
||||
|
||||
// prepend the packet size
|
||||
binary.LittleEndian.PutUint32(buf.Bytes()[:4], uint32(buf.Len()-4))
|
||||
|
||||
// encrypt body
|
||||
switch peer.whichKey {
|
||||
case USE_E:
|
||||
EncryptData(buf.Bytes(), peer.E_key)
|
||||
EncryptData(buf.Bytes()[4:], peer.E_key)
|
||||
case USE_FE:
|
||||
EncryptData(buf.Bytes(), peer.FE_key)
|
||||
EncryptData(buf.Bytes()[4:], peer.FE_key)
|
||||
}
|
||||
|
||||
// write packet size
|
||||
if err := binary.Write(peer.conn, binary.LittleEndian, uint32(buf.Len())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// write packet body
|
||||
log.Printf("Sending %#v, sizeof: %d", data, buf.Len())
|
||||
// send full packet
|
||||
log.Printf("Sending %#v, sizeof: %d, buffer: %v", data, buf.Len(), buf.Bytes())
|
||||
if _, err := peer.conn.Write(buf.Bytes()); err != nil {
|
||||
return fmt.Errorf("[FATAL] failed to write packet body! %v", err)
|
||||
return fmt.Errorf("failed to write packet body! %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ func (pkt Packet) encodeStructField(field reflect.StructField, value reflect.Val
|
||||
case reflect.String: // all strings in fusionfall packets are encoded as utf16, we'll need to encode it
|
||||
sz, err := strconv.Atoi(field.Tag.Get("size"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to grab string 'size' tag!!")
|
||||
return fmt.Errorf("failed to grab string 'size' tag")
|
||||
}
|
||||
|
||||
buf16 := utf16.Encode([]rune(value.String()))
|
||||
@ -100,7 +100,7 @@ func (pkt Packet) decodeStructField(field reflect.StructField, value reflect.Val
|
||||
case reflect.String: // all strings in fusionfall packets are encoded as utf16, we'll need to decode it
|
||||
sz, err := strconv.Atoi(field.Tag.Get("size"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to grab string 'size' tag!!")
|
||||
return fmt.Errorf("failed to grab string 'size' tag")
|
||||
}
|
||||
|
||||
buf16 := make([]uint16, sz)
|
@ -7,9 +7,9 @@ import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/core/db"
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/core/redis"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -76,7 +76,7 @@ func (server *LoginServer) Login(peer *protocol.CNPeer, pkt protocol.Packet) err
|
||||
// client is resending a login packet??
|
||||
if peer.AccountID != -1 {
|
||||
SendError(LOGIN_ERROR)
|
||||
return fmt.Errorf("Out of order P_CL2LS_REQ_LOGIN!")
|
||||
return fmt.Errorf("out of order P_CL2LS_REQ_LOGIN")
|
||||
}
|
||||
|
||||
// attempt login
|
||||
@ -155,7 +155,7 @@ func (server *LoginServer) SaveCharacterName(peer *protocol.CNPeer, pkt protocol
|
||||
|
||||
if peer.AccountID == -1 {
|
||||
peer.Send(protocol.P_LS2CL_REP_SAVE_CHAR_NAME_FAIL, protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_FAIL{})
|
||||
return fmt.Errorf("Out of order P_LS2CL_REP_SAVE_CHAR_NAME_FAIL!")
|
||||
return fmt.Errorf("out of order P_LS2CL_REP_SAVE_CHAR_NAME_FAIL")
|
||||
}
|
||||
|
||||
// TODO: sanity check SzFirstName && SzLastName
|
||||
@ -260,7 +260,7 @@ func (server *LoginServer) ShardSelect(peer *protocol.CNPeer, pkt protocol.Packe
|
||||
shards := server.redisHndlr.GetShards()
|
||||
if len(shards) == 0 {
|
||||
SendFail(peer)
|
||||
return fmt.Errorf("LoginServer has found no linked shards!")
|
||||
return fmt.Errorf("loginServer has found no linked shards")
|
||||
}
|
||||
|
||||
key, err := protocol.GenSerialKey()
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/core/db"
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/core/protocol/pool"
|
||||
"github.com/CPunch/gopenfusion/core/redis"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol/pool"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
)
|
||||
|
||||
type PacketHandler func(peer *protocol.CNPeer, pkt protocol.Packet) error
|
||||
@ -81,20 +81,18 @@ func (server *LoginServer) Start() {
|
||||
}
|
||||
|
||||
func (server *LoginServer) handleEvents() {
|
||||
for {
|
||||
select {
|
||||
case event := <-server.eRecv:
|
||||
switch event.Type {
|
||||
case protocol.EVENT_CLIENT_DISCONNECT:
|
||||
server.disconnect(event.Peer)
|
||||
case protocol.EVENT_CLIENT_PACKET:
|
||||
defer pool.Put(event.Pkt)
|
||||
|
||||
if err := server.handlePacket(event.Peer, event.PktID, protocol.NewPacket(event.Pkt)); err != nil {
|
||||
log.Printf("Error handling packet: %v", err)
|
||||
event.Peer.Kill()
|
||||
}
|
||||
for event := range server.eRecv {
|
||||
switch event.Type {
|
||||
case protocol.EVENT_CLIENT_DISCONNECT:
|
||||
server.disconnect(event.Peer)
|
||||
case protocol.EVENT_CLIENT_PACKET:
|
||||
if err := server.handlePacket(event.Peer, event.PktID, protocol.NewPacket(event.Pkt)); err != nil {
|
||||
log.Printf("Error handling packet: %v", err)
|
||||
event.Peer.Kill()
|
||||
}
|
||||
|
||||
// the packet is given to us by the event, so we'll need to make sure to return it to the pool
|
||||
pool.Put(event.Pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package shard
|
||||
|
||||
import "github.com/CPunch/gopenfusion/core/protocol"
|
||||
import "github.com/CPunch/gopenfusion/internal/protocol"
|
||||
|
||||
func (server *ShardServer) freeChat(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
||||
var chat protocol.SP_CL2FE_REQ_SEND_FREECHAT_MESSAGE
|
||||
|
@ -1,7 +1,7 @@
|
||||
package shard
|
||||
|
||||
import (
|
||||
"github.com/CPunch/gopenfusion/core/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
)
|
||||
|
||||
func (server *ShardServer) addEntity(e entity.Entity) {
|
||||
|
@ -4,16 +4,16 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/CPunch/gopenfusion/core/entity"
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/core/redis"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
)
|
||||
|
||||
func (server *ShardServer) attachPlayer(peer *protocol.CNPeer, meta redis.LoginMetadata) (*entity.Player, error) {
|
||||
// resending a shard enter packet?
|
||||
old, err := server.getPlayer(peer)
|
||||
old, _ := server.getPlayer(peer)
|
||||
if old != nil {
|
||||
return nil, fmt.Errorf("resent enter packet!")
|
||||
return nil, fmt.Errorf("resent enter packet")
|
||||
}
|
||||
|
||||
// attach player
|
||||
|
@ -1,8 +1,8 @@
|
||||
package shard
|
||||
|
||||
import (
|
||||
"github.com/CPunch/gopenfusion/core/entity"
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
)
|
||||
|
||||
func (server *ShardServer) updatePlayerPosition(plr *entity.Player, X, Y, Z, Angle int) {
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/core/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
)
|
||||
|
||||
type NPCData struct {
|
||||
|
@ -7,17 +7,15 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/CPunch/gopenfusion/config"
|
||||
"github.com/CPunch/gopenfusion/core/db"
|
||||
"github.com/CPunch/gopenfusion/core/entity"
|
||||
"github.com/CPunch/gopenfusion/core/protocol"
|
||||
"github.com/CPunch/gopenfusion/core/protocol/pool"
|
||||
"github.com/CPunch/gopenfusion/core/redis"
|
||||
"github.com/CPunch/gopenfusion/internal/db"
|
||||
"github.com/CPunch/gopenfusion/internal/entity"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol"
|
||||
"github.com/CPunch/gopenfusion/internal/protocol/pool"
|
||||
"github.com/CPunch/gopenfusion/internal/redis"
|
||||
)
|
||||
|
||||
type PacketHandler func(peer *protocol.CNPeer, pkt protocol.Packet) error
|
||||
|
||||
func stubbedPacket(_ *protocol.CNPeer, _ protocol.Packet) error { /* stubbed */ return nil }
|
||||
|
||||
type ShardServer struct {
|
||||
listener net.Listener
|
||||
port int
|
||||
@ -67,18 +65,17 @@ func NewShardServer(dbHndlr *db.DBHandler, redisHndlr *redis.RedisHandler, port
|
||||
}
|
||||
|
||||
func (server *ShardServer) handleEvents() {
|
||||
for {
|
||||
select {
|
||||
case event := <-server.eRecv:
|
||||
switch event.Type {
|
||||
case protocol.EVENT_CLIENT_DISCONNECT:
|
||||
server.disconnect(event.Peer)
|
||||
case protocol.EVENT_CLIENT_PACKET:
|
||||
defer pool.Put(event.Pkt)
|
||||
if err := server.handlePacket(event.Peer, event.PktID, protocol.NewPacket(event.Pkt)); err != nil {
|
||||
event.Peer.Kill()
|
||||
}
|
||||
for event := range server.eRecv {
|
||||
switch event.Type {
|
||||
case protocol.EVENT_CLIENT_DISCONNECT:
|
||||
server.disconnect(event.Peer)
|
||||
case protocol.EVENT_CLIENT_PACKET:
|
||||
if err := server.handlePacket(event.Peer, event.PktID, protocol.NewPacket(event.Pkt)); err != nil {
|
||||
event.Peer.Kill()
|
||||
}
|
||||
|
||||
// the packet is given to us by the event, so we'll need to make sure to return it to the pool
|
||||
pool.Put(event.Pkt)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,7 +140,7 @@ func (server *ShardServer) connect(peer *protocol.CNPeer) {
|
||||
func (server *ShardServer) getPlayer(peer *protocol.CNPeer) (*entity.Player, error) {
|
||||
plr, ok := server.peers[peer]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Player not found")
|
||||
return nil, fmt.Errorf("player not found")
|
||||
}
|
||||
|
||||
return plr, nil
|
||||
|
Loading…
Reference in New Issue
Block a user