diff --git a/config/config.go b/config/config.go index 774f645..25ae6ab 100644 --- a/config/config.go +++ b/config/config.go @@ -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 { diff --git a/db/players.go b/db/players.go index 2534dad..c9b08c7 100644 --- a/db/players.go +++ b/db/players.go @@ -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 diff --git a/db/schema.go b/db/schema.go index 854b592..d54f198 100644 --- a/db/schema.go +++ b/db/schema.go @@ -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) diff --git a/main.go b/main.go index 05d1fef..350ef11 100644 --- a/main.go +++ b/main.go @@ -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() + dbHndlr, err := db.OpenLiteDB("test.db") + if err != nil { + log.Panicf("failed to open db: %v", err) + } + dbHndlr.Setup() - server := server.NewLoginServer() - server.Start() + 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() } diff --git a/protocol/encrypt.go b/protocol/encrypt.go index 6b30fa1..8a5d611 100644 --- a/protocol/encrypt.go +++ b/protocol/encrypt.go @@ -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 +} diff --git a/server/join.go b/server/join.go new file mode 100644 index 0000000..9220cd2 --- /dev/null +++ b/server/join.go @@ -0,0 +1,8 @@ +package server + +import "github.com/CPunch/gopenfusion/protocol" + +func (server *ShardServer) RequestEnter(peer *Peer, pkt protocol.Packet) error { + + return nil +} diff --git a/server/login.go b/server/login.go index 92a40c0..2726dcb 100644 --- a/server/login.go +++ b/server/login.go @@ -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) } diff --git a/server/loginserver.go b/server/loginServer.go similarity index 58% rename from server/loginserver.go rename to server/loginServer.go index 09a8105..d648c20 100644 --- a/server/loginserver.go +++ b/server/loginServer.go @@ -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 +} diff --git a/server/peer.go b/server/peer.go index 4b17382..acd0cdb 100644 --- a/server/peer.go +++ b/server/peer.go @@ -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, } } diff --git a/server/shardServer.go b/server/shardServer.go new file mode 100644 index 0000000..b8aafb0 --- /dev/null +++ b/server/shardServer.go @@ -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 +} diff --git a/server/shared.go b/server/shared.go new file mode 100644 index 0000000..181f0f1 --- /dev/null +++ b/server/shared.go @@ -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 }