server: started ShardServer

- protocol: added GenSerialKey() which securely generates an EnterSerialKey
- login server accepts a shard via LoginServer.AddShard()
- login server will pass LoginMetaData to the selected shard via ShardServer.QueueLogin()
- misc. refactoring
This commit is contained in:
CPunch 2023-03-17 16:27:47 -05:00
parent 7a26ffdcf7
commit 1357de99aa
11 changed files with 260 additions and 55 deletions

View File

@ -8,6 +8,10 @@ var (
AEQUIP_COUNT = 9
AINVEN_COUNT = 50
ABANK_COUNT = 119
LOGIN_PORT = 23000
SHARD_PORT = 23001
SHARD_IP = "127.0.0.1"
)
func GetMaxHP(level int) int {

View File

@ -39,15 +39,14 @@ type Player struct {
SkywayLocationFlag []byte
FirstUseFlag []byte
Quests []byte
/* Appearance tbl */
Body int `db:"Body"`
EyeColor int `db:"EyeColor"`
FaceStyle int `db:"FaceStyle"`
Gender int `db:"Gender"`
HairColor int `db:"HairColor"`
HairStyle int `db:"HairStyle"`
Height int `db:"Height"`
SkinColor int `db:"SkinColor"`
Body int /* Appearance tbl start */
EyeColor int
FaceStyle int
Gender int
HairColor int
HairStyle int
Height int
SkinColor int /* Appearance tbl end */
}
// returns PlayerID, error

View File

@ -20,10 +20,6 @@ type DBHandler struct {
//go:embed migrations/new.sql
var createDBQuery string
var (
DefaultDB *DBHandler
)
func OpenLiteDB(dbPath string) (*DBHandler, error) {
sqliteFmt := fmt.Sprintf("%s", dbPath)

28
main.go
View File

@ -1,14 +1,32 @@
package main
import (
"log"
"github.com/CPunch/gopenfusion/config"
"github.com/CPunch/gopenfusion/db"
"github.com/CPunch/gopenfusion/server"
)
func main() {
db.DefaultDB, _ = db.OpenLiteDB("test.db")
db.DefaultDB.Setup()
server := server.NewLoginServer()
server.Start()
dbHndlr, err := db.OpenLiteDB("test.db")
if err != nil {
log.Panicf("failed to open db: %v", err)
}
dbHndlr.Setup()
loginServer, err := server.NewLoginServer(dbHndlr, config.LOGIN_PORT)
if err != nil {
log.Panicf("failed to create login server: %v", err)
}
shardServer, err := server.NewShardServer(dbHndlr, config.SHARD_PORT)
if err != nil {
log.Panicf("failed to create shard server: %v", err)
}
loginServer.AddShard(shardServer)
go loginServer.Start()
shardServer.Start()
}

View File

@ -1,6 +1,7 @@
package protocol
import (
"crypto/rand"
"encoding/binary"
)
@ -58,3 +59,13 @@ func CreateNewKey(uTime uint64, iv1, iv2 uint64) []byte {
binary.LittleEndian.PutUint64(buf, uint64(key))
return buf
}
func GenSerialKey() (int64, error) {
tmp := [8]byte{}
if _, err := rand.Read(tmp[:]); err != nil {
return 0, err
}
// convert to uint64 && return
return int64(binary.LittleEndian.Uint64(tmp[:])), nil
}

8
server/join.go Normal file
View File

@ -0,0 +1,8 @@
package server
import "github.com/CPunch/gopenfusion/protocol"
func (server *ShardServer) RequestEnter(peer *Peer, pkt protocol.Packet) error {
return nil
}

View File

@ -79,10 +79,10 @@ func (server *LoginServer) Login(peer *Peer, pkt protocol.Packet) error {
}
// attempt login
account, err := db.DefaultDB.TryLogin(loginPkt.SzID, loginPkt.SzPassword)
account, err := server.dbHndlr.TryLogin(loginPkt.SzID, loginPkt.SzPassword)
if err == db.LoginErrorInvalidID {
// this is the default behavior, auto create the account if the ID isn't in use
account, err = db.DefaultDB.NewAccount(loginPkt.SzID, loginPkt.SzPassword)
account, err = server.dbHndlr.NewAccount(loginPkt.SzID, loginPkt.SzPassword)
if err != nil {
// respond with a dummy login error packet so the client doesn't get softlocked
SendError(LOGIN_DATABASE_ERROR)
@ -99,7 +99,7 @@ func (server *LoginServer) Login(peer *Peer, pkt protocol.Packet) error {
// grab player data
peer.AccountID = account.AccountID
plrs, err := db.DefaultDB.GetPlayers(account.AccountID)
plrs, err := server.dbHndlr.GetPlayers(account.AccountID)
if err != nil {
SendError(LOGIN_DATABASE_ERROR)
return err
@ -124,7 +124,7 @@ func (server *LoginServer) Login(peer *Peer, pkt protocol.Packet) error {
IZ: int32(plr.ZCoordinate),
}
AEquip, err := db.DefaultDB.GetPlayerInventorySlots(plr.PlayerID, 0, config.AEQUIP_COUNT-1)
AEquip, err := server.dbHndlr.GetPlayerInventorySlots(plr.PlayerID, 0, config.AEQUIP_COUNT-1)
if err != nil {
SendError(LOGIN_DATABASE_ERROR)
return err
@ -158,7 +158,7 @@ func (server *LoginServer) SaveCharacterName(peer *Peer, pkt protocol.Packet) er
}
// TODO: sanity check SzFirstName && SzLastName
PlayerID, err := db.DefaultDB.NewPlayer(peer.AccountID, charPkt.SzFirstName, charPkt.SzLastName, int(charPkt.ISlotNum))
PlayerID, err := server.dbHndlr.NewPlayer(peer.AccountID, charPkt.SzFirstName, charPkt.SzLastName, int(charPkt.ISlotNum))
if err != nil {
peer.Send(protocol.P_LS2CL_REP_SAVE_CHAR_NAME_FAIL, protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_FAIL{})
return err
@ -218,11 +218,11 @@ func (server *LoginServer) CharacterCreate(peer *Peer, pkt protocol.Packet) erro
return SendFail(peer)
}
if err := db.DefaultDB.FinishPlayer(&charPkt, peer.AccountID); err != nil {
if err := server.dbHndlr.FinishPlayer(&charPkt, peer.AccountID); err != nil {
return SendFail(peer)
}
plr, err := db.DefaultDB.GetPlayer(int(charPkt.PCStyle.IPC_UID))
plr, err := server.dbHndlr.GetPlayer(int(charPkt.PCStyle.IPC_UID))
if err != nil {
return SendFail(peer)
}
@ -240,7 +240,7 @@ func (server *LoginServer) CharacterDelete(peer *Peer, pkt protocol.Packet) erro
var charPkt protocol.SP_CL2LS_REQ_CHAR_DELETE
pkt.Decode(&charPkt)
slot, err := db.DefaultDB.DeletePlayer(int(charPkt.IPC_UID), peer.AccountID)
slot, err := server.dbHndlr.DeletePlayer(int(charPkt.IPC_UID), peer.AccountID)
if err != nil {
return SendFail(peer)
}
@ -250,11 +250,43 @@ func (server *LoginServer) CharacterDelete(peer *Peer, pkt protocol.Packet) erro
})
}
func (server *LoginServer) ShardSelect(peer *Peer, pkt protocol.Packet) error {
var selection protocol.SP_CL2LS_REQ_CHAR_SELECT
pkt.Decode(&selection)
if server.shard == nil {
return fmt.Errorf("LoginServer currently has no linked shard")
}
key, err := protocol.GenSerialKey()
if err != nil {
return err
}
// TODO: verify peer->AccountID and selection->IPC_UID are valid!!!!
resp := protocol.SP_LS2CL_REP_SHARD_SELECT_SUCC{
G_FE_ServerPort: int32(server.shard.port),
IEnterSerialKey: key,
}
// the rest of the bytes in G_FE_ServerIP will be zero'd, so there's no need to write the NULL byte
copy(resp.G_FE_ServerIP[:], []byte(config.SHARD_IP))
server.shard.QueueLogin(key, &LoginMetadata{
FEKey: peer.FE_key,
Timestamp: time.Now(),
PlayerID: int32(selection.IPC_UID),
})
return peer.Send(protocol.P_LS2CL_REP_SHARD_SELECT_SUCC, resp)
}
func (server *LoginServer) FinishTutorial(peer *Peer, pkt protocol.Packet) error {
var charPkt protocol.SP_CL2LS_REQ_SAVE_CHAR_TUTOR
pkt.Decode(&charPkt)
if err := db.DefaultDB.FinishTutorial(int(charPkt.IPC_UID), peer.AccountID); err != nil {
if err := server.dbHndlr.FinishTutorial(int(charPkt.IPC_UID), peer.AccountID); err != nil {
return SendFail(peer)
}

View File

@ -1,61 +1,60 @@
package server
import (
"fmt"
"log"
"net"
"sync"
"github.com/CPunch/gopenfusion/db"
"github.com/CPunch/gopenfusion/protocol"
)
type PacketHandler func(peer *Peer, pkt protocol.Packet) error
type LoginServer struct {
listener net.Listener
port int
dbHndlr *db.DBHandler
packetHandlers map[uint32]PacketHandler
peers map[*Peer]bool
peersLock sync.Mutex
shard *ShardServer
}
func stubbedPacket(_ *Peer, _ protocol.Packet) error { /* stubbed */ return nil }
func NewLoginServer() *LoginServer {
listener, err := net.Listen("tcp", ":23000")
func NewLoginServer(dbHndlr *db.DBHandler, port int) (*LoginServer, error) {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
log.Fatal(err)
return nil, err
}
loginServer := &LoginServer{
server := &LoginServer{
listener: listener,
port: port,
dbHndlr: dbHndlr,
peers: make(map[*Peer]bool),
}
loginServer.packetHandlers = map[uint32]PacketHandler{
protocol.P_CL2LS_REQ_LOGIN: loginServer.Login,
protocol.P_CL2LS_REQ_CHECK_CHAR_NAME: loginServer.CheckCharacterName,
protocol.P_CL2LS_REQ_SAVE_CHAR_NAME: loginServer.SaveCharacterName,
protocol.P_CL2LS_REQ_CHAR_CREATE: loginServer.CharacterCreate,
protocol.P_CL2LS_REQ_CHAR_SELECT: stubbedPacket,
protocol.P_CL2LS_REQ_CHAR_DELETE: loginServer.CharacterDelete,
server.packetHandlers = map[uint32]PacketHandler{
protocol.P_CL2LS_REQ_LOGIN: server.Login,
protocol.P_CL2LS_REQ_CHECK_CHAR_NAME: server.CheckCharacterName,
protocol.P_CL2LS_REQ_SAVE_CHAR_NAME: server.SaveCharacterName,
protocol.P_CL2LS_REQ_CHAR_CREATE: server.CharacterCreate,
protocol.P_CL2LS_REQ_CHAR_SELECT: server.ShardSelect,
protocol.P_CL2LS_REQ_CHAR_DELETE: server.CharacterDelete,
protocol.P_CL2LS_REQ_SHARD_SELECT: stubbedPacket,
protocol.P_CL2LS_REQ_SHARD_LIST_INFO: stubbedPacket,
protocol.P_CL2LS_CHECK_NAME_LIST: stubbedPacket,
protocol.P_CL2LS_REQ_SAVE_CHAR_TUTOR: loginServer.FinishTutorial,
protocol.P_CL2LS_REQ_SAVE_CHAR_TUTOR: server.FinishTutorial,
protocol.P_CL2LS_REQ_PC_EXIT_DUPLICATE: stubbedPacket,
protocol.P_CL2LS_REP_LIVE_CHECK: stubbedPacket,
protocol.P_CL2LS_REQ_CHANGE_CHAR_NAME: stubbedPacket,
protocol.P_CL2LS_REQ_SERVER_SELECT: stubbedPacket,
}
return loginServer
}
func (server *LoginServer) RegisterPacketHandler(typeID uint32, hndlr PacketHandler) {
server.packetHandlers[typeID] = hndlr
return server, nil
}
func (server *LoginServer) Start() {
log.Print("Server hosted on 127.0.0.1:23000")
log.Printf("Server hosted on 127.0.0.1:%d\n", server.port)
for {
conn, err := server.listener.Accept()
@ -84,12 +83,18 @@ func (server *LoginServer) HandlePacket(peer *Peer, typeID uint32, pkt protocol.
func (server *LoginServer) Disconnect(peer *Peer) {
server.peersLock.Lock()
log.Printf("Peer %p disconnected from LOGIN\n", peer)
delete(server.peers, peer)
server.peersLock.Unlock()
}
func (server *LoginServer) Connect(peer *Peer) {
server.peersLock.Lock()
log.Printf("New peer %p connected to LOGIN\n", peer)
server.peers[peer] = true
server.peersLock.Unlock()
}
func (server *LoginServer) AddShard(shard *ShardServer) {
server.shard = shard
}

View File

@ -7,7 +7,6 @@ import (
"log"
"net"
"github.com/CPunch/gopenfusion/db"
"github.com/CPunch/gopenfusion/protocol"
"github.com/CPunch/gopenfusion/protocol/pool"
)
@ -24,7 +23,6 @@ type PeerHandler interface {
}
type Peer struct {
Player *db.Player
conn net.Conn
handler PeerHandler
SzID string
@ -37,15 +35,14 @@ type Peer struct {
func NewPeer(handler PeerHandler, conn net.Conn) *Peer {
return &Peer{
conn: conn,
handler: handler,
SzID: "",
E_key: []byte(protocol.DEFAULT_KEY),
FE_key: nil,
SzID: "",
AccountID: -1,
Player: nil,
handler: handler,
conn: conn,
alive: true,
whichKey: USE_E,
alive: true,
}
}

126
server/shardServer.go Normal file
View File

@ -0,0 +1,126 @@
package server
import (
"fmt"
"log"
"net"
"sync"
"time"
"github.com/CPunch/gopenfusion/db"
"github.com/CPunch/gopenfusion/protocol"
)
type LoginMetadata struct {
FEKey []byte
Timestamp time.Time
PlayerID int32
}
type ShardServer struct {
listener net.Listener
port int
dbHndlr *db.DBHandler
packetHandlers map[uint32]PacketHandler
peers sync.Map // [*Peer]*db.Player
loginMetadataQueue sync.Map // [int64]*LoginMetadata w/ int64 = serialKey
}
func NewShardServer(dbHndlr *db.DBHandler, port int) (*ShardServer, error) {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return nil, err
}
server := &ShardServer{
listener: listener,
port: port,
dbHndlr: dbHndlr,
packetHandlers: make(map[uint32]PacketHandler),
}
server.packetHandlers = map[uint32]PacketHandler{}
return server, nil
}
func (server *ShardServer) RegisterPacketHandler(typeID uint32, hndlr PacketHandler) {
server.packetHandlers[typeID] = hndlr
}
func (server *ShardServer) Start() {
log.Printf("Server hosted on 127.0.0.1:%d\n", server.port)
for {
conn, err := server.listener.Accept()
if err != nil {
log.Println("Connection error: ", err)
return
}
client := NewPeer(server, conn)
server.Connect(client)
go client.Handler()
}
}
func (server *ShardServer) HandlePacket(peer *Peer, typeID uint32, pkt protocol.Packet) error {
if hndlr, ok := server.packetHandlers[typeID]; ok {
if err := hndlr(peer, pkt); err != nil {
return err
}
} else {
log.Printf("[WARN] invalid packet ID: %x\n", typeID)
}
return nil
}
func (server *ShardServer) Disconnect(peer *Peer) {
log.Printf("Peer %p disconnected from SHARD\n", peer)
server.peers.Delete(peer)
}
func (server *ShardServer) Connect(peer *Peer) {
log.Printf("New peer %p connected to SHARD\n", peer)
server.peers.Store(peer, nil)
}
func (server *ShardServer) JoinPlayer(peer *Peer, player *db.Player) {
server.peers.Store(peer, player)
}
// Simple wrapper for server.peers.Range, if f returns false the iteration is stopped.
func (server *ShardServer) RangePeers(f func(peer *Peer, player *db.Player) bool) {
server.peers.Range(func(key, value any) bool { // simple wrapper to cast the datatypes
peer, ok := key.(*Peer)
if !ok { // this should never happen
panic(fmt.Errorf("ShardServer.peers has an invalid key: peers[%#v] = %#v", key, value))
}
player, ok := value.(*db.Player)
if !ok { // this should also never happen
panic(fmt.Errorf("ShardServer.peers has an invalid value: peers[%#v] = %#v", key, value))
}
return f(peer, player)
})
}
func (server *ShardServer) QueueLogin(serialKey int64, meta *LoginMetadata) {
server.loginMetadataQueue.Store(serialKey, meta)
}
func (server *ShardServer) CheckLogin(serialKey int64) (*LoginMetadata, error) {
value, ok := server.loginMetadataQueue.Load(serialKey)
if !ok {
return nil, fmt.Errorf("serialKey %x is not valid!", serialKey)
}
meta, ok := value.(*LoginMetadata)
if !ok { // should never happen
panic(fmt.Errorf("ShardServer.loginMetadataQueue has an invalid value: loginMetadataQueue[%x] = %#v", serialKey, value))
}
return meta, nil
}

9
server/shared.go Normal file
View File

@ -0,0 +1,9 @@
package server
import (
"github.com/CPunch/gopenfusion/protocol"
)
type PacketHandler func(peer *Peer, pkt protocol.Packet) error
func stubbedPacket(_ *Peer, _ protocol.Packet) error { /* stubbed */ return nil }