mirror of
https://github.com/CPunch/gopenfusion.git
synced 2024-11-12 19:20:06 +00:00
CPunch
f1552830a0
- Packet struct is small enough that allocation for it is really unnecessary, and can be passed around by value fairly easily
245 lines
7.6 KiB
Go
245 lines
7.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 *Peer, SzID string, IClientVerC int32, ISlotNum int8, data []protocol.SP_LS2CL_REP_CHAR_INFO) {
|
|
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()),
|
|
}
|
|
|
|
// swap keys
|
|
peer.Send(resp, protocol.P_LS2CL_REP_LOGIN_SUCC)
|
|
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++ {
|
|
peer.Send(&data[i], protocol.P_LS2CL_REP_CHAR_INFO)
|
|
}
|
|
}
|
|
|
|
func (server *LoginServer) Login(peer *Peer, pkt protocol.Packet) {
|
|
var loginPkt protocol.SP_CL2LS_REQ_LOGIN
|
|
pkt.Decode(&loginPkt)
|
|
|
|
SendError := func(e int32) {
|
|
peer.Send(&protocol.SP_LS2CL_REP_LOGIN_FAIL{
|
|
IErrorCode: e,
|
|
SzID: loginPkt.SzID,
|
|
}, protocol.P_LS2CL_REP_LOGIN_FAIL)
|
|
}
|
|
|
|
// client is resending a login packet??
|
|
if peer.AccountID != -1 {
|
|
SendError(LOGIN_ERROR)
|
|
panic(fmt.Errorf("Out of order P_CL2LS_REQ_LOGIN!"))
|
|
}
|
|
|
|
// attempt login
|
|
account, err := db.DefaultDB.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)
|
|
if err != nil {
|
|
// respond with a dummy login error packet so the client doesn't get softlocked
|
|
SendError(LOGIN_DATABASE_ERROR)
|
|
panic(err)
|
|
}
|
|
} else if err == db.LoginErrorInvalidPassword {
|
|
// respond with invalid password
|
|
SendError(LOGIN_ID_AND_PASSWORD_DO_NOT_MATCH)
|
|
return
|
|
} else if err != nil { // wtf?
|
|
SendError(LOGIN_DATABASE_ERROR)
|
|
panic(err)
|
|
}
|
|
|
|
// grab player data
|
|
peer.AccountID = account.AccountID
|
|
plrs, err := db.DefaultDB.GetPlayers(account.AccountID)
|
|
if err != nil {
|
|
SendError(LOGIN_DATABASE_ERROR)
|
|
panic(err)
|
|
}
|
|
|
|
// build character list
|
|
charInfo := make([]protocol.SP_LS2CL_REP_CHAR_INFO, 0)
|
|
for _, 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 := db.DefaultDB.GetPlayerInventorySlots(plr.PlayerID, 0, config.AEQUIP_COUNT-1)
|
|
if err != nil {
|
|
SendError(LOGIN_DATABASE_ERROR)
|
|
panic(err)
|
|
}
|
|
|
|
copy(info.AEquip[:], AEquip)
|
|
charInfo = append(charInfo, info)
|
|
}
|
|
|
|
server.AcceptLogin(peer, loginPkt.SzID, loginPkt.IClientVerC, 1, charInfo)
|
|
}
|
|
|
|
func (server *LoginServer) CheckCharacterName(peer *Peer, pkt protocol.Packet) {
|
|
var charPkt protocol.SP_CL2LS_REQ_CHECK_CHAR_NAME
|
|
pkt.Decode(&charPkt)
|
|
|
|
// just auto accept, client resends this data later
|
|
peer.Send(&protocol.SP_LS2CL_REP_CHECK_CHAR_NAME_SUCC{
|
|
SzFirstName: charPkt.SzFirstName,
|
|
SzLastName: charPkt.SzLastName,
|
|
}, protocol.P_LS2CL_REP_CHECK_CHAR_NAME_SUCC)
|
|
}
|
|
|
|
func (server *LoginServer) SaveCharacterName(peer *Peer, pkt protocol.Packet) {
|
|
var charPkt protocol.SP_CL2LS_REQ_SAVE_CHAR_NAME
|
|
pkt.Decode(&charPkt)
|
|
|
|
if peer.AccountID == -1 {
|
|
peer.Send(&protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_FAIL{}, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_FAIL)
|
|
panic(fmt.Errorf("Out of order P_LS2CL_REP_SAVE_CHAR_NAME_FAIL!"))
|
|
}
|
|
|
|
// TODO: sanity check SzFirstName && SzLastName
|
|
PlayerID, err := db.DefaultDB.NewPlayer(peer.AccountID, charPkt.SzFirstName, charPkt.SzLastName, int(charPkt.ISlotNum))
|
|
if err != nil {
|
|
peer.Send(&protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_FAIL{}, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_FAIL)
|
|
panic(err)
|
|
}
|
|
|
|
peer.Send(&protocol.SP_LS2CL_REP_SAVE_CHAR_NAME_SUCC{
|
|
IPC_UID: int64(PlayerID),
|
|
ISlotNum: charPkt.ISlotNum,
|
|
IGender: charPkt.IGender,
|
|
SzFirstName: charPkt.SzFirstName,
|
|
SzLastName: charPkt.SzLastName,
|
|
}, protocol.P_LS2CL_REP_SAVE_CHAR_NAME_SUCC)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (server *LoginServer) CharacterCreate(peer *Peer, pkt protocol.Packet) {
|
|
var charPkt protocol.SP_CL2LS_REQ_CHAR_CREATE
|
|
pkt.Decode(&charPkt)
|
|
|
|
if !validateCharacterCreation(&charPkt) {
|
|
peer.Send(&protocol.SP_LS2CL_REP_SHARD_SELECT_FAIL{IErrorCode: 2}, protocol.P_LS2CL_REP_SHARD_SELECT_FAIL)
|
|
panic(fmt.Errorf("invalid SP_CL2LS_REQ_CHAR_CREATE!"))
|
|
}
|
|
|
|
if err := db.DefaultDB.FinishPlayer(&charPkt, peer.AccountID); err != nil {
|
|
peer.Send(&protocol.SP_LS2CL_REP_SHARD_SELECT_FAIL{IErrorCode: 2}, protocol.P_LS2CL_REP_SHARD_SELECT_FAIL)
|
|
panic(err)
|
|
}
|
|
|
|
plr, err := db.DefaultDB.GetPlayer(int(charPkt.PCStyle.IPC_UID))
|
|
if err != nil {
|
|
peer.Send(&protocol.SP_LS2CL_REP_SHARD_SELECT_FAIL{IErrorCode: 2}, protocol.P_LS2CL_REP_SHARD_SELECT_FAIL)
|
|
panic(err)
|
|
}
|
|
|
|
PCStyle, PCStyle2 := util.Player2PCStyle(plr)
|
|
peer.Send(&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
|
|
}, protocol.P_LS2CL_REP_CHAR_CREATE_SUCC)
|
|
}
|
|
|
|
func (server *LoginServer) CharacterDelete(peer *Peer, pkt protocol.Packet) {
|
|
var charPkt protocol.SP_CL2LS_REQ_CHAR_DELETE
|
|
pkt.Decode(&charPkt)
|
|
|
|
slot, err := db.DefaultDB.DeletePlayer(int(charPkt.IPC_UID), peer.AccountID)
|
|
if err != nil {
|
|
peer.Send(&protocol.SP_LS2CL_REP_SHARD_SELECT_FAIL{IErrorCode: 2}, protocol.P_LS2CL_REP_SHARD_SELECT_FAIL)
|
|
panic(err)
|
|
}
|
|
|
|
peer.Send(&protocol.SP_LS2CL_REP_CHAR_DELETE_SUCC{
|
|
ISlotNum: int8(slot),
|
|
}, protocol.P_LS2CL_REP_CHAR_DELETE_SUCC)
|
|
}
|
|
|
|
func (server *LoginServer) FinishTutorial(peer *Peer, pkt protocol.Packet) {
|
|
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 {
|
|
peer.Send(&protocol.SP_LS2CL_REP_SHARD_SELECT_FAIL{IErrorCode: 2}, protocol.P_LS2CL_REP_SHARD_SELECT_FAIL)
|
|
panic(err)
|
|
}
|
|
|
|
// no response
|
|
}
|