mirror of
https://github.com/Mezeporta/Erupe.git
synced 2025-12-13 07:25:03 +01:00
415 lines
13 KiB
Go
415 lines
13 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"erupe-ce/common/byteframe"
|
|
"erupe-ce/common/stringsupport"
|
|
"erupe-ce/network/mhfpacket"
|
|
"erupe-ce/server/channelserver/compression/deltacomp"
|
|
"erupe-ce/server/channelserver/compression/nullcomp"
|
|
"go.uber.org/zap"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
// 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 {
|
|
dumpSaveData(s, pkt.RawDataPayload, "hunternavi")
|
|
// 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)
|
|
if pkt.Unk0 == 1 {
|
|
// Format:
|
|
// uint8 Hunts
|
|
// struct Hunt
|
|
// uint32 HuntID
|
|
// uint32 MonID
|
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
|
|
} else {
|
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0))
|
|
}
|
|
}
|
|
|
|
func handleMsgMhfEnumerateMercenaryLog(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfEnumerateMercenaryLog)
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint32(0)
|
|
// Format:
|
|
// struct Log
|
|
// uint32 Timestamp
|
|
// []byte Name (len 18)
|
|
// uint8 Unk
|
|
// uint8 Unk
|
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
|
}
|
|
|
|
func handleMsgMhfCreateMercenary(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfCreateMercenary)
|
|
|
|
bf := byteframe.NewByteFrame()
|
|
|
|
var nextID uint32
|
|
s.server.db.QueryRow("SELECT nextval('rasta_id_seq')").Scan(&nextID)
|
|
|
|
bf.WriteUint32(nextID) // New MercID
|
|
bf.WriteUint32(0xDEADBEEF) // Unk
|
|
|
|
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
|
}
|
|
|
|
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
|
|
if len(pkt.MercData) > 0 {
|
|
s.server.db.Exec("UPDATE characters SET savemercenary=$1 WHERE id=$2", pkt.MercData, s.charID)
|
|
}
|
|
s.server.db.Exec("UPDATE characters SET gcp=$1 WHERE id=$2", pkt.GCP, s.charID)
|
|
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
|
|
}
|
|
|
|
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
|
|
if pkt.Unk0 {
|
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 2))
|
|
return
|
|
}
|
|
var data []byte
|
|
var gcp uint32
|
|
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", s.charID).Scan(&data)
|
|
s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id = $1", s.charID).Scan(&gcp)
|
|
|
|
resp := byteframe.NewByteFrame()
|
|
if len(data) == 0 {
|
|
resp.WriteBytes(make([]byte, 3))
|
|
} else {
|
|
resp.WriteBytes(data[1:])
|
|
resp.WriteUint32(0) // Unk
|
|
}
|
|
resp.WriteUint32(gcp)
|
|
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
|
}
|
|
|
|
func handleMsgMhfReadMercenaryM(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfReadMercenaryM)
|
|
var data []byte
|
|
s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id = $1", pkt.CharID).Scan(&data)
|
|
resp := byteframe.NewByteFrame()
|
|
if len(data) == 0 {
|
|
resp.WriteBool(false)
|
|
} else {
|
|
resp.WriteBytes(data[4:])
|
|
}
|
|
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
|
}
|
|
|
|
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {}
|
|
|
|
///////////////////////////////////////////
|
|
|
|
///////////////////////////////////////////
|
|
/// OTOMO AIRU //
|
|
///////////////////////////////////////////
|
|
|
|
func handleMsgMhfLoadOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfLoadOtomoAirou)
|
|
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")
|
|
decomp, err := nullcomp.Decompress(pkt.RawDataPayload[1:])
|
|
if err != nil {
|
|
s.logger.Error("Failed to decompress airou", zap.Error(err))
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
bf := byteframe.NewByteFrameFromBytes(decomp)
|
|
save := byteframe.NewByteFrame()
|
|
var catsExist uint8
|
|
save.WriteUint8(0)
|
|
|
|
cats := bf.ReadUint8()
|
|
for i := 0; i < int(cats); i++ {
|
|
dataLen := bf.ReadUint32()
|
|
catID := bf.ReadUint32()
|
|
if catID == 0 {
|
|
_ = s.server.db.QueryRow("SELECT nextval('airou_id_seq')").Scan(&catID)
|
|
}
|
|
exists := bf.ReadBool()
|
|
data := bf.ReadBytes(uint(dataLen) - 5)
|
|
if exists {
|
|
catsExist++
|
|
save.WriteUint32(dataLen)
|
|
save.WriteUint32(catID)
|
|
save.WriteBool(exists)
|
|
save.WriteBytes(data)
|
|
}
|
|
}
|
|
save.WriteBytes(bf.DataFromCurrent())
|
|
save.Seek(0, 0)
|
|
save.WriteUint8(catsExist)
|
|
comp, err := nullcomp.Compress(save.Data())
|
|
if err != nil {
|
|
s.logger.Error("Failed to compress airou", zap.Error(err))
|
|
} else {
|
|
comp = append([]byte{0x01}, comp...)
|
|
s.server.db.Exec("UPDATE characters SET otomoairou=$1 WHERE id=$2", comp, s.charID)
|
|
}
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
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
|
|
}
|
|
airouList := getGuildAirouList(s)
|
|
resp.WriteUint16(uint16(len(airouList)))
|
|
resp.WriteUint16(uint16(len(airouList)))
|
|
for _, cat := range airouList {
|
|
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
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// Get cats used recently
|
|
// Retail reset at midday, 12 hours is a midpoint
|
|
tempBanDuration := 43200 - (1800) // Minus hunt time
|
|
bannedCats := make(map[uint32]int)
|
|
var csvTemp string
|
|
rows, err := s.server.db.Query(`SELECT cats_used
|
|
FROM guild_hunts gh
|
|
INNER JOIN characters c
|
|
ON gh.host_id = c.id
|
|
WHERE c.id=$1 AND gh.return+$2>$3`, s.charID, tempBanDuration, Time_Current_Adjusted().Unix())
|
|
if err != nil {
|
|
s.logger.Warn("Failed to get recently used airous", zap.Error(err))
|
|
}
|
|
for rows.Next() {
|
|
rows.Scan(&csvTemp)
|
|
for i, j := range stringsupport.CSVElems(csvTemp) {
|
|
bannedCats[uint32(j)] = i
|
|
}
|
|
}
|
|
|
|
// ellie's GetGuildMembers didn't seem to pull leader?
|
|
rows, err = s.server.db.Query(`SELECT c.otomoairou
|
|
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
|
|
err = rows.Scan(&data)
|
|
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 {
|
|
_, exists := bannedCats[cat.CatID]
|
|
if cat.CurrentTask == 4 && !exists {
|
|
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
|
|
}
|
|
|
|
///////////////////////////////////////////
|