mirror of
https://github.com/CPunch/gopenfusion.git
synced 2024-11-14 03:50:05 +00:00
CPunch
735bdc5b36
- moved Peer from the server package to the protocol package, it was also renamed to CNPeer as most fusionfall specific constants in the client use the 'CN' prefix.
296 lines
8.6 KiB
Go
296 lines
8.6 KiB
Go
package server
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/CPunch/gopenfusion/config"
|
|
"github.com/CPunch/gopenfusion/db"
|
|
"github.com/CPunch/gopenfusion/protocol"
|
|
"github.com/CPunch/gopenfusion/util"
|
|
)
|
|
|
|
const (
|
|
LOGIN_DATABASE_ERROR = 0
|
|
LOGIN_ID_DOESNT_EXIST = 1
|
|
LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH = 2
|
|
LOGIN_ID_ALREADY_IN_USE = 3
|
|
LOGIN_ERROR = 4
|
|
LOGIN_CLIENT_VERSION_OUTDATED = 6
|
|
LOGIN_YOU_ARE_NOT_AN_AUTHORIZED_BETA_TESTER = 7
|
|
LOGIN_AUTHENTICATION_CONNECTION_ERROR = 8
|
|
LOGIN_UPDATED_EUALA_REQUIRED = 9
|
|
)
|
|
|
|
func (server *LoginServer) AcceptLogin(peer *protocol.CNPeer, SzID string, IClientVerC int32, ISlotNum int8, data []protocol.SP_LS2CL_REP_CHAR_INFO) error {
|
|
peer.SzID = SzID
|
|
|
|
resp := protocol.SP_LS2CL_REP_LOGIN_SUCC{
|
|
SzID: SzID,
|
|
ICharCount: int8(len(data)),
|
|
ISlotNum: ISlotNum,
|
|
IPaymentFlag: 1,
|
|
IOpenBetaFlag: 0,
|
|
UiSvrTime: uint64(time.Now().Unix()),
|
|
}
|
|
|
|
if err := peer.Send(protocol.P_LS2CL_REP_LOGIN_SUCC, resp); err != nil {
|
|
return err
|
|
}
|
|
|
|
// swap keys
|
|
peer.E_key = protocol.CreateNewKey(
|
|
resp.UiSvrTime,
|
|
uint64(resp.ICharCount+1),
|
|
uint64(resp.ISlotNum+1),
|
|
)
|
|
peer.FE_key = protocol.CreateNewKey(
|
|
binary.LittleEndian.Uint64([]byte(protocol.DEFAULT_KEY)),
|
|
uint64(IClientVerC),
|
|
1,
|
|
)
|
|
|
|
// send characters (if any)
|
|
for i := 0; i < len(data); i++ {
|
|
if err := peer.Send(protocol.P_LS2CL_REP_CHAR_INFO, &data[i]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (server *LoginServer) Login(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
|
var loginPkt protocol.SP_CL2LS_REQ_LOGIN
|
|
pkt.Decode(&loginPkt)
|
|
|
|
SendError := func(e int32) {
|
|
peer.Send(protocol.P_LS2CL_REP_LOGIN_FAIL, protocol.SP_LS2CL_REP_LOGIN_FAIL{
|
|
IErrorCode: e,
|
|
SzID: loginPkt.SzID,
|
|
})
|
|
}
|
|
|
|
// client is resending a login packet??
|
|
if peer.AccountID != -1 {
|
|
SendError(LOGIN_ERROR)
|
|
return fmt.Errorf("Out of order P_CL2LS_REQ_LOGIN!")
|
|
}
|
|
|
|
// attempt login
|
|
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 = 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)
|
|
return err
|
|
}
|
|
} else if err == db.LoginErrorInvalidPassword {
|
|
// respond with invalid password
|
|
SendError(LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH)
|
|
return nil
|
|
} else if err != nil { // wtf?
|
|
SendError(LOGIN_DATABASE_ERROR)
|
|
return err
|
|
}
|
|
|
|
// grab player data
|
|
peer.AccountID = account.AccountID
|
|
plrs, err := server.dbHndlr.GetPlayers(account.AccountID)
|
|
if err != nil {
|
|
SendError(LOGIN_DATABASE_ERROR)
|
|
return err
|
|
}
|
|
|
|
// truncate plrs
|
|
if len(plrs) > 3 {
|
|
plrs = plrs[:4]
|
|
}
|
|
|
|
// build character list
|
|
charInfo := [4]protocol.SP_LS2CL_REP_CHAR_INFO{}
|
|
for i, plr := range plrs {
|
|
PCStyle, PCStyle2 := util.Player2PCStyle(&plr)
|
|
info := protocol.SP_LS2CL_REP_CHAR_INFO{
|
|
ISlot: int8(plr.Slot),
|
|
ILevel: int16(plr.Level),
|
|
SPC_Style: PCStyle,
|
|
SPC_Style2: PCStyle2,
|
|
IX: int32(plr.XCoordinate),
|
|
IY: int32(plr.YCoordinate),
|
|
IZ: int32(plr.ZCoordinate),
|
|
}
|
|
|
|
AEquip, err := server.dbHndlr.GetPlayerInventorySlots(plr.PlayerID, 0, config.AEQUIP_COUNT-1)
|
|
if err != nil {
|
|
SendError(LOGIN_DATABASE_ERROR)
|
|
return err
|
|
}
|
|
|
|
copy(info.AEquip[:], AEquip)
|
|
charInfo[i] = info
|
|
}
|
|
|
|
return server.AcceptLogin(peer, loginPkt.SzID, loginPkt.IClientVerC, 1, charInfo[:len(plrs)])
|
|
}
|
|
|
|
func (server *LoginServer) CheckCharacterName(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
|
var charPkt protocol.SP_CL2LS_REQ_CHECK_CHAR_NAME
|
|
pkt.Decode(&charPkt)
|
|
|
|
// just auto accept, client resends this data later
|
|
return peer.Send(protocol.P_LS2CL_REP_CHECK_CHAR_NAME_SUCC, protocol.SP_LS2CL_REP_CHECK_CHAR_NAME_SUCC{
|
|
SzFirstName: charPkt.SzFirstName,
|
|
SzLastName: charPkt.SzLastName,
|
|
})
|
|
}
|
|
|
|
func (server *LoginServer) SaveCharacterName(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
|
var charPkt protocol.SP_CL2LS_REQ_SAVE_CHAR_NAME
|
|
pkt.Decode(&charPkt)
|
|
|
|
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!")
|
|
}
|
|
|
|
// TODO: sanity check SzFirstName && SzLastName
|
|
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
|
|
}
|
|
|
|
return peer.Send(protocol.P_LS2CL_REP_SAVE_CHAR_NAME_SUCC, protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_SUCC{
|
|
IPC_UID: int64(PlayerID),
|
|
ISlotNum: charPkt.ISlotNum,
|
|
IGender: charPkt.IGender,
|
|
SzFirstName: charPkt.SzFirstName,
|
|
SzLastName: charPkt.SzLastName,
|
|
})
|
|
}
|
|
|
|
func validateCharacterCreation(character *protocol.SP_CL2LS_REQ_CHAR_CREATE) bool {
|
|
// thanks openfusion!
|
|
// all the values have been determined from analyzing client code and xdt
|
|
// and double checked using cheat engine
|
|
|
|
// check base parameters
|
|
style := &character.PCStyle
|
|
if !(style.IBody >= 0 && style.IBody <= 2 &&
|
|
style.IEyeColor >= 1 && style.IEyeColor <= 5 &&
|
|
style.IGender >= 1 && style.IGender <= 2 &&
|
|
style.IHairColor >= 1 && style.IHairColor <= 18) &&
|
|
style.IHeight >= 0 && style.IHeight <= 4 &&
|
|
style.INameCheck >= 0 && style.INameCheck <= 2 &&
|
|
style.ISkinColor >= 1 && style.ISkinColor <= 12 {
|
|
return false
|
|
}
|
|
|
|
// facestyle and hairstyle are gender dependent
|
|
if !(style.IGender == 1 && style.IFaceStyle >= 1 && style.IFaceStyle <= 5 && style.IHairStyle >= 1 && style.IHairStyle <= 23) &&
|
|
!(style.IGender == 2 && style.IFaceStyle >= 6 && style.IFaceStyle <= 10 && style.IHairStyle >= 25 && style.IHairStyle <= 45) {
|
|
return false
|
|
}
|
|
|
|
// TODO: sanity check items in SOn_Item; see db.FinishPlayer()
|
|
return true
|
|
}
|
|
|
|
func SendFail(peer *protocol.CNPeer) error {
|
|
if err := peer.Send(protocol.P_LS2CL_REP_SHARD_SELECT_FAIL, protocol.SP_LS2CL_REP_SHARD_SELECT_FAIL{
|
|
IErrorCode: 2,
|
|
}); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (server *LoginServer) CharacterCreate(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
|
var charPkt protocol.SP_CL2LS_REQ_CHAR_CREATE
|
|
pkt.Decode(&charPkt)
|
|
|
|
if !validateCharacterCreation(&charPkt) {
|
|
return SendFail(peer)
|
|
}
|
|
|
|
if err := server.dbHndlr.FinishPlayer(&charPkt, peer.AccountID); err != nil {
|
|
return SendFail(peer)
|
|
}
|
|
|
|
plr, err := server.dbHndlr.GetPlayer(int(charPkt.PCStyle.IPC_UID))
|
|
if err != nil {
|
|
return SendFail(peer)
|
|
}
|
|
|
|
PCStyle, PCStyle2 := util.Player2PCStyle(plr)
|
|
return peer.Send(protocol.P_LS2CL_REP_CHAR_CREATE_SUCC, protocol.SP_LS2CL_REP_CHAR_CREATE_SUCC{
|
|
ILevel: int16(plr.Level),
|
|
SPC_Style: PCStyle,
|
|
SPC_Style2: PCStyle2,
|
|
SOn_Item: charPkt.SOn_Item, // if items were faked, we don't really care since the db only stores the sanitized fields
|
|
})
|
|
}
|
|
|
|
func (server *LoginServer) CharacterDelete(peer *protocol.CNPeer, pkt protocol.Packet) error {
|
|
var charPkt protocol.SP_CL2LS_REQ_CHAR_DELETE
|
|
pkt.Decode(&charPkt)
|
|
|
|
slot, err := server.dbHndlr.DeletePlayer(int(charPkt.IPC_UID), peer.AccountID)
|
|
if err != nil {
|
|
return SendFail(peer)
|
|
}
|
|
|
|
return peer.Send(protocol.P_LS2CL_REP_CHAR_DELETE_SUCC, protocol.SP_LS2CL_REP_CHAR_DELETE_SUCC{
|
|
ISlotNum: int8(slot),
|
|
})
|
|
}
|
|
|
|
func (server *LoginServer) ShardSelect(peer *protocol.CNPeer, 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 *protocol.CNPeer, pkt protocol.Packet) error {
|
|
var charPkt protocol.SP_CL2LS_REQ_SAVE_CHAR_TUTOR
|
|
pkt.Decode(&charPkt)
|
|
|
|
if err := server.dbHndlr.FinishTutorial(int(charPkt.IPC_UID), peer.AccountID); err != nil {
|
|
return SendFail(peer)
|
|
}
|
|
|
|
// no response
|
|
return nil
|
|
}
|