Files
Erupe/Erupe/server/channelserver/handlers_mercenary.go
2022-07-06 07:43:51 +10:00

368 lines
12 KiB
Go

package channelserver
import (
"math/rand"
"os"
"io"
"io/ioutil"
"path/filepath"
"erupe-ce/network/mhfpacket"
"erupe-ce/server/channelserver/compression/deltacomp"
"erupe-ce/server/channelserver/compression/nullcomp"
"github.com/Andoryuuta/byteframe"
"go.uber.org/zap"
)
// THERE ARE [PARTENER] [MERCENARY] [OTOMO AIRU]
///////////////////////////////////////////
/// PARTENER //
///////////////////////////////////////////
func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPartner)
// load partner from database
var data []byte
err := s.server.db.QueryRow("SELECT partner FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get partner savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
// TODO(Andoryuuta): Figure out unusual double ack. One sized, one not.
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfSavePartner(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePartner)
dumpSaveData(s, pkt.RawDataPayload, "_partner")
_, err := s.server.db.Exec("UPDATE characters SET partner=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update partner savedata in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadLegendDispatch)
data := []byte{0x03, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x01, 0x8d, 0x40, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x02, 0xde, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x5e, 0x04, 0x30, 0x40}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
var data []byte
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get hunter navigation savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
// set first byte to 1 to avoid pop up every time without save
body := make([]byte, 0x226)
body[0] = 1
doAckBufSucceed(s, pkt.AckHandle, body)
}
}
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
dumpSaveData(s, pkt.RawDataPayload, "_hunternavi")
if pkt.IsDataDiff {
var data []byte
// Load existing save
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get hunternavi savedata from db", zap.Error(err))
}
// Check if we actually had any hunternavi data, using a blank buffer if not.
// This is requried as the client will try to send a diff after character creation without a prior MsgMhfSaveHunterNavi packet.
if len(data) == 0 {
data = make([]byte, 0x226)
data[0] = 1 // set first byte to 1 to avoid pop up every time without save
}
// Perform diff and compress it to write back to db
s.logger.Info("Diffing...")
saveOutput := deltacomp.ApplyDataDiff(pkt.RawDataPayload, data)
_, err = s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", saveOutput, s.charID)
if err != nil {
s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err))
}
s.logger.Info("Wrote recompressed hunternavi back to DB.")
} else {
// simply update database, no extra processing
_, err := s.server.db.Exec("UPDATE characters SET hunternavi=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update hunternavi savedata in db", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
///////////////////////////////////////////
///////////////////////////////////////////
/// MERCENARY //
///////////////////////////////////////////
func handleMsgMhfMercenaryHuntdata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfMercenaryHuntdata)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x0A))
}
func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0x00) // Unk
bf.WriteUint32(rand.Uint32()) // Partner ID?
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
GCPValue := bf.ReadUint32()
_ = bf.ReadUint32() // unk
MercDataSize := bf.ReadUint32()
MercData := bf.ReadBytes(uint(MercDataSize))
_ = bf.ReadUint32() // unk
if MercDataSize > 0 {
// the save packet has an extra null byte after its size
_, err := s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", MercData[:MercDataSize], s.charID)
if err != nil {
s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
}
}
// gcp value is always present regardless
_, err := s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", GCPValue, s.charID)
if err != nil {
s.logger.Fatal("Failed to update savemercenary and gcp in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
var data []byte
var gcp uint32
// still has issues
err := s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get savemercenary data from db", zap.Error(err))
}
err = s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
if err != nil {
panic(err)
}
if len(data) == 0 {
data = []byte{0x00}
}
resp := byteframe.NewByteFrame()
resp.WriteBytes(data)
resp.WriteUint16(0)
resp.WriteUint32(gcp)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
// accessing actual rasta data of someone else still unsure of the formatting of this
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {}
///////////////////////////////////////////
///////////////////////////////////////////
/// OTOMO AIRU //
///////////////////////////////////////////
func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadOtomoAirou)
// load partnyaa from database
var data []byte
err := s.server.db.QueryRow("SELECT otomoairou FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Fatal("Failed to get partnyaa savedata from db", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveOtomoAirou)
dumpSaveData(s, pkt.RawDataPayload, "_otomoairou")
_, err := s.server.db.Exec("UPDATE characters SET otomoairou=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Fatal("Failed to update partnyaa savedata in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateAiroulist)
resp := byteframe.NewByteFrame()
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin")); err == nil {
data, _ := ioutil.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "airoulist.bin"))
resp.WriteBytes(data)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
return
}
// Guild's Palico count. It seems we have to put the value on both ¯\_(ツ)_/¯
airouList := getGuildAirouList(s)
resp.WriteUint16(uint16(len(airouList)))
resp.WriteUint16(uint16(len(airouList)))
for k, cat := range airouList {
// an id of 0 breaks everything pretty badly
// erupe does not currently ever assign cats IDs
// these presumably need to be added for the fatigue expiration for the final uint32
// seems like it should happen in MSG_MHF_LOAD_OTOMO_AIROU requests as the initial creation operation is saving straight into a load
// and the client is obviously not aware of global ID availability
if cat.CatID == 0 {
resp.WriteUint32(uint32(k + 1))
} else {
resp.WriteUint32(cat.CatID)
}
resp.WriteBytes(cat.CatName)
resp.WriteUint32(cat.Experience)
resp.WriteUint8(cat.Personality)
resp.WriteUint8(cat.Class)
resp.WriteUint8(cat.WeaponType)
resp.WriteUint16(cat.WeaponID)
resp.WriteUint32(0) // 32 bit unix timestamp, either time at which the cat stops being fatigued or the time at which it started
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
// CatDefinition holds values needed to populate the guild cat list
type CatDefinition struct {
CatID uint32
CatName []byte
CurrentTask uint8
Personality uint8
Class uint8
Experience uint32
WeaponType uint8
WeaponID uint16
// there is a unix timestamp at the end of the cat for fatigue status
// it's -probably- not pulled from cat saves as that would allow someone other than the owner to actively manipulate a save for another session
}
func getGuildAirouList(s *Session) []CatDefinition {
var guild *Guild
var err error
var guildCats []CatDefinition
// returning 0 cats on any guild issues
// can probably optimise all of the guild queries pretty heavily
guild, err = GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
return guildCats
}
// ellie's GetGuildMembers didn't seem to pull leader?
rows, err := s.server.db.Query(`SELECT c.otomoairou, c.name
FROM characters c
INNER JOIN guild_characters gc
ON gc.character_id = c.id
WHERE gc.guild_id = $1 AND c.otomoairou IS NOT NULL
ORDER BY c.id ASC
LIMIT 60;`, guild.ID)
if err != nil {
s.logger.Warn("Selecting otomoairou based on guild failed", zap.Error(err))
return guildCats
}
for rows.Next() {
var data []byte
var charName string
err = rows.Scan(&data, &charName)
if err != nil {
s.logger.Warn("select failure", zap.Error(err))
continue
} else if len(data) == 0 {
// non extant cats that aren't null in DB
continue
}
// first byte has cat existence in general, can skip if 0
if data[0] == 1 {
decomp, err := nullcomp.Decompress(data[1:])
if err != nil {
s.logger.Warn("decomp failure", zap.Error(err))
continue
}
bf := byteframe.NewByteFrameFromBytes(decomp)
cats := GetCatDetails(bf)
for _, cat := range cats {
if cat.CurrentTask == 4 {
guildCats = append(guildCats, cat)
}
}
}
}
return guildCats
}
func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition {
catCount := bf.ReadUint8()
cats := make([]CatDefinition, catCount)
for x := 0; x < int(catCount); x++ {
var catDef CatDefinition
// cat sometimes has additional bytes for whatever reason, gift items? timestamp?
// until actual variance is known we can just seek to end based on start
catDefLen := bf.ReadUint32()
catStart, _ := bf.Seek(0, io.SeekCurrent)
catDef.CatID = bf.ReadUint32()
bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool
catDef.CatName = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest
catDef.CurrentTask = bf.ReadUint8()
bf.Seek(16, io.SeekCurrent) // appearance data and what is seemingly null bytes
catDef.Personality = bf.ReadUint8()
catDef.Class = bf.ReadUint8()
bf.Seek(5, io.SeekCurrent) // affection and colour sliders
catDef.Experience = bf.ReadUint32() // raw cat rank points, doesn't have a rank
bf.Seek(1, io.SeekCurrent) // bool for weapon being equipped
catDef.WeaponType = bf.ReadUint8() // weapon type, presumably always 6 for melee?
catDef.WeaponID = bf.ReadUint16() // weapon id
bf.Seek(catStart+int64(catDefLen), io.SeekStart)
cats[x] = catDef
}
return cats
}
///////////////////////////////////////////