Merge remote-tracking branch 'origin/main' into feature/restore-history

This commit is contained in:
straticspaff
2023-09-27 21:14:24 +01:00
547 changed files with 29511 additions and 6877 deletions

View File

@@ -2,6 +2,7 @@ package deltacomp
import (
"bytes"
"fmt"
"io"
)
@@ -74,6 +75,18 @@ func ApplyDataDiff(diff []byte, baseData []byte) []byte {
}
differentCount--
// Grow slice if it's required
if len(baseCopy) < dataOffset {
fmt.Printf("Slice smaller than data offset, growing slice...")
baseCopy = append(baseCopy, make([]byte, (dataOffset+differentCount)-len(baseData))...)
} else {
length := len(baseCopy[dataOffset:])
if length < differentCount {
length -= differentCount
baseCopy = append(baseCopy, make([]byte, length)...)
}
}
// Apply the patch bytes.
for i := 0; i < differentCount; i++ {
b, err := checkReadUint8(patch)

View File

@@ -7,7 +7,7 @@ import (
"io/ioutil"
"testing"
"github.com/Andoryuuta/Erupe/server/channelserver/compression/nullcomp"
"erupe-ce/server/channelserver/compression/nullcomp"
)
var tests = []struct {

View File

@@ -70,10 +70,15 @@ func Compress(rawData []byte) ([]byte, error) {
if err == io.EOF {
output = append(output, []byte{byte(nullCount)}...)
break
} else if i != 0 {
} else if i != 0 && nullCount != 0 {
r.UnreadByte()
output = append(output, []byte{byte(nullCount)}...)
break
} else if i != 0 && nullCount == 0 {
r.UnreadByte()
output = output[:len(output)-2]
output = append(output, []byte{byte(0xFF)}...)
break
} else if err != nil {
return nil, err
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,169 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"fmt"
"io"
)
var achievementCurves = [][]int32{
// 0: HR weapon use, Class use, Tore dailies
{5, 15, 30, 50, 100, 150, 200, 300},
// 1: Weapon collector, G wep enhances
{1, 5, 10, 15, 30, 50, 75, 100},
// 2: Festa wins
{1, 2, 3, 4, 5, 6, 7, 8},
// 3: GR weapon use, Sigil crafts
{10, 50, 100, 200, 350, 500, 750, 999},
}
var achievementCurveMap = map[uint8][]int32{
0: achievementCurves[0], 1: achievementCurves[0], 2: achievementCurves[0], 3: achievementCurves[0],
4: achievementCurves[0], 5: achievementCurves[0], 6: achievementCurves[0], 7: achievementCurves[1],
8: achievementCurves[2], 9: achievementCurves[0], 10: achievementCurves[0], 11: achievementCurves[0],
12: achievementCurves[0], 13: achievementCurves[0], 14: achievementCurves[0], 15: achievementCurves[0],
16: achievementCurves[3], 17: achievementCurves[3], 18: achievementCurves[3], 19: achievementCurves[3],
20: achievementCurves[3], 21: achievementCurves[3], 22: achievementCurves[3], 23: achievementCurves[3],
24: achievementCurves[3], 25: achievementCurves[3], 26: achievementCurves[3], 27: achievementCurves[1],
28: achievementCurves[1], 29: achievementCurves[3], 30: achievementCurves[3], 31: achievementCurves[3],
32: achievementCurves[3],
}
type Achievement struct {
Level uint8
Value uint32
NextValue uint16
Required uint32
Updated bool
Progress uint32
Trophy uint8
}
func GetAchData(id uint8, score int32) Achievement {
curve := achievementCurveMap[id]
var ach Achievement
for i, v := range curve {
temp := score - v
if temp < 0 {
ach.Progress = uint32(score)
ach.Required = uint32(curve[i])
switch ach.Level {
case 0:
ach.NextValue = 5
case 1, 2, 3:
ach.NextValue = 10
case 4, 5:
ach.NextValue = 15
case 6:
ach.NextValue = 15
ach.Trophy = 0x40
case 7:
ach.NextValue = 20
ach.Trophy = 0x60
}
return ach
} else {
score = temp
ach.Level++
switch ach.Level {
case 1:
ach.Value += 5
case 2, 3, 4:
ach.Value += 10
case 5, 6, 7:
ach.Value += 15
case 8:
ach.Value += 20
}
}
}
ach.Required = uint32(curve[7])
ach.Trophy = 0x7F
ach.Progress = ach.Required
return ach
}
func handleMsgMhfGetAchievement(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetAchievement)
var exists int
err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", pkt.CharID).Scan(&exists)
if err != nil {
s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", pkt.CharID)
}
var scores [33]int32
err = s.server.db.QueryRow("SELECT * FROM achievements WHERE id=$1", pkt.CharID).Scan(&scores[0],
&scores[0], &scores[1], &scores[2], &scores[3], &scores[4], &scores[5], &scores[6], &scores[7], &scores[8],
&scores[9], &scores[10], &scores[11], &scores[12], &scores[13], &scores[14], &scores[15], &scores[16],
&scores[17], &scores[18], &scores[19], &scores[20], &scores[21], &scores[22], &scores[23], &scores[24],
&scores[25], &scores[26], &scores[27], &scores[28], &scores[29], &scores[30], &scores[31], &scores[32])
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 20))
return
}
resp := byteframe.NewByteFrame()
var points uint32
resp.WriteBytes(make([]byte, 16))
resp.WriteBytes([]byte{0x02, 0x00, 0x00}) // Unk
var id uint8
entries := uint8(33)
resp.WriteUint8(entries) // Entry count
for id = 0; id < entries; id++ {
achData := GetAchData(id, scores[id])
points += achData.Value
resp.WriteUint8(id)
resp.WriteUint8(achData.Level)
resp.WriteUint16(achData.NextValue)
resp.WriteUint32(achData.Required)
resp.WriteBool(false) // TODO: Notify on rank increase since last checked, see MhfDisplayedAchievement
resp.WriteUint8(achData.Trophy)
/* Trophy bitfield
0000 0000
abcd efgh
B - Bronze (0x40)
B-C - Silver (0x60)
B-H - Gold (0x7F)
*/
resp.WriteUint16(0) // Unk
resp.WriteUint32(achData.Progress)
}
resp.Seek(0, io.SeekStart)
resp.WriteUint32(points)
resp.WriteUint32(points)
resp.WriteUint32(points)
resp.WriteUint32(points)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfSetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetCaAchievementHist)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfResetAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddAchievement(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAddAchievement)
var exists int
err := s.server.db.QueryRow("SELECT id FROM achievements WHERE id=$1", s.charID).Scan(&exists)
if err != nil {
s.server.db.Exec("INSERT INTO achievements (id) VALUES ($1)", s.charID)
}
s.server.db.Exec(fmt.Sprintf("UPDATE achievements SET ach%d=ach%d+1 WHERE id=$1", pkt.AchievementID, pkt.AchievementID), s.charID)
}
func handleMsgMhfPaymentAchievement(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfDisplayedAchievement(s *Session, p mhfpacket.MHFPacket) {
// This is how you would figure out if the rank-up notification needs to occur
}
func handleMsgMhfGetCaAchievementHist(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetCaAchievement(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,41 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/common/token"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfGetBbsUserStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBbsUserStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(200)
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetBbsSnsStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBbsSnsStatus)
bf := byteframe.NewByteFrame()
bf.WriteUint32(200)
bf.WriteUint32(401)
bf.WriteUint32(401)
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfApplyBbsArticle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyBbsArticle)
bf := byteframe.NewByteFrame()
articleToken := token.Generate(40)
bf.WriteUint32(200)
bf.WriteUint32(80)
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteBytes(stringsupport.PaddedString(articleToken, 64, false))
bf.WriteBytes(stringsupport.PaddedString(s.server.erupeConfig.ScreenshotAPIURL, 64, false))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -0,0 +1,281 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/mhfcourse"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"fmt"
"go.uber.org/zap"
"io"
"time"
)
func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
var netcafePoints uint32
err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafePoints)
if err != nil {
s.logger.Error("Failed to get netcafe points from db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(netcafePoints)
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
var netcafePoints uint32
err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafePoints)
if err != nil {
s.logger.Error("Failed to get netcate points from db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(netcafePoints)
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfCheckDailyCafepoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCheckDailyCafepoint)
midday := TimeMidnight().Add(12 * time.Hour)
if TimeAdjusted().After(midday) {
midday = midday.Add(24 * time.Hour)
}
// get time after which daily claiming would be valid from db
var dailyTime time.Time
err := s.server.db.QueryRow("SELECT COALESCE(daily_time, $2) FROM characters WHERE id = $1", s.charID, time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)).Scan(&dailyTime)
if err != nil {
s.logger.Error("Failed to get daily_time savedata from db", zap.Error(err))
}
var bondBonus, bonusQuests, dailyQuests uint32
bf := byteframe.NewByteFrame()
if midday.After(dailyTime) {
addPointNetcafe(s, 5)
bondBonus = 5 // Bond point bonus quests
bonusQuests = s.server.erupeConfig.GameplayOptions.BonusQuestAllowance
dailyQuests = s.server.erupeConfig.GameplayOptions.DailyQuestAllowance
s.server.db.Exec("UPDATE characters SET daily_time=$1, bonus_quests = $2, daily_quests = $3 WHERE id=$4", midday, bonusQuests, dailyQuests, s.charID)
bf.WriteBool(true) // Success?
} else {
bf.WriteBool(false)
}
bf.WriteUint32(bondBonus)
bf.WriteUint32(bonusQuests)
bf.WriteUint32(dailyQuests)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetCafeDuration)
bf := byteframe.NewByteFrame()
var cafeReset time.Time
err := s.server.db.QueryRow(`SELECT cafe_reset FROM characters WHERE id=$1`, s.charID).Scan(&cafeReset)
if err != nil {
cafeReset = TimeWeekNext()
s.server.db.Exec(`UPDATE characters SET cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID)
}
if TimeAdjusted().After(cafeReset) {
cafeReset = TimeWeekNext()
s.server.db.Exec(`UPDATE characters SET cafe_time=0, cafe_reset=$1 WHERE id=$2`, cafeReset, s.charID)
s.server.db.Exec(`DELETE FROM cafe_accepted WHERE character_id=$1`, s.charID)
}
var cafeTime uint32
err = s.server.db.QueryRow("SELECT cafe_time FROM characters WHERE id = $1", s.charID).Scan(&cafeTime)
if err != nil {
panic(err)
}
if mhfcourse.CourseExists(30, s.courses) {
cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime
}
bf.WriteUint32(cafeTime) // Total cafe time
bf.WriteUint16(0)
ps.Uint16(bf, fmt.Sprintf(s.server.dict["cafeReset"], int(cafeReset.Month()), cafeReset.Day()), true)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
type CafeBonus struct {
ID uint32 `db:"id"`
TimeReq uint32 `db:"time_req"`
ItemType uint32 `db:"item_type"`
ItemID uint32 `db:"item_id"`
Quantity uint32 `db:"quantity"`
Claimed bool `db:"claimed"`
}
func handleMsgMhfGetCafeDurationBonusInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetCafeDurationBonusInfo)
bf := byteframe.NewByteFrame()
var count uint32
rows, err := s.server.db.Queryx(`
SELECT cb.id, time_req, item_type, item_id, quantity,
(
SELECT count(*)
FROM cafe_accepted ca
WHERE cb.id = ca.cafe_id AND ca.character_id = $1
)::int::bool AS claimed
FROM cafebonus cb ORDER BY id ASC;`, s.charID)
if err != nil {
s.logger.Error("Error getting cafebonus", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
for rows.Next() {
count++
cafeBonus := &CafeBonus{}
err = rows.StructScan(&cafeBonus)
if err != nil {
s.logger.Error("Error scanning cafebonus", zap.Error(err))
}
bf.WriteUint32(cafeBonus.TimeReq)
bf.WriteUint32(cafeBonus.ItemType)
bf.WriteUint32(cafeBonus.ItemID)
bf.WriteUint32(cafeBonus.Quantity)
bf.WriteBool(cafeBonus.Claimed)
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
resp.WriteUint32(uint32(TimeAdjusted().Unix()))
resp.WriteUint32(count)
resp.WriteBytes(bf.Data())
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
}
func handleMsgMhfReceiveCafeDurationBonus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReceiveCafeDurationBonus)
bf := byteframe.NewByteFrame()
var count uint32
bf.WriteUint32(0)
rows, err := s.server.db.Queryx(`
SELECT c.id, time_req, item_type, item_id, quantity
FROM cafebonus c
WHERE (
SELECT count(*)
FROM cafe_accepted ca
WHERE c.id = ca.cafe_id AND ca.character_id = $1
) < 1 AND (
SELECT ch.cafe_time + $2
FROM characters ch
WHERE ch.id = $1
) >= time_req`, s.charID, TimeAdjusted().Unix()-s.sessionStart)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else {
for rows.Next() {
cafeBonus := &CafeBonus{}
err = rows.StructScan(cafeBonus)
if err != nil {
continue
}
count++
bf.WriteUint32(cafeBonus.ID)
bf.WriteUint32(cafeBonus.ItemType)
bf.WriteUint32(cafeBonus.ItemID)
bf.WriteUint32(cafeBonus.Quantity)
}
bf.Seek(0, io.SeekStart)
bf.WriteUint32(count)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
}
func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostCafeDurationBonusReceived)
var cafeBonus CafeBonus
for _, cbID := range pkt.CafeBonusID {
err := s.server.db.QueryRow(`
SELECT cb.id, item_type, quantity FROM cafebonus cb WHERE cb.id=$1
`, cbID).Scan(&cafeBonus.ID, &cafeBonus.ItemType, &cafeBonus.Quantity)
if err == nil {
if cafeBonus.ItemType == 17 {
addPointNetcafe(s, int(cafeBonus.Quantity))
}
}
s.server.db.Exec("INSERT INTO public.cafe_accepted VALUES ($1, $2)", cbID, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func addPointNetcafe(s *Session, p int) error {
var points int
err := s.server.db.QueryRow("SELECT netcafe_points FROM characters WHERE id = $1", s.charID).Scan(&points)
if err != nil {
return err
}
if points+p > s.server.erupeConfig.GameplayOptions.MaximumNP {
points = s.server.erupeConfig.GameplayOptions.MaximumNP
} else {
points += p
}
s.server.db.Exec("UPDATE characters SET netcafe_points=$1 WHERE id=$2", points, s.charID)
return nil
}
func handleMsgMhfStartBoostTime(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStartBoostTime)
bf := byteframe.NewByteFrame()
boostLimit := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.BoostTimeDuration) * time.Minute)
if s.server.erupeConfig.GameplayOptions.DisableBoostTime {
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
s.server.db.Exec("UPDATE characters SET boost_time=$1 WHERE id=$2", boostLimit, s.charID)
bf.WriteUint32(uint32(boostLimit.Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetBoostTime(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostTime)
doAckBufSucceed(s, pkt.AckHandle, []byte{})
}
func handleMsgMhfGetBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostTimeLimit)
bf := byteframe.NewByteFrame()
var boostLimit time.Time
err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
if err != nil {
bf.WriteUint32(0)
} else {
bf.WriteUint32(uint32(boostLimit.Unix()))
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetBoostRight(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoostRight)
var boostLimit time.Time
err := s.server.db.QueryRow("SELECT boost_time FROM characters WHERE id=$1", s.charID).Scan(&boostLimit)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
if boostLimit.After(TimeAdjusted()) {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x01})
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x02})
}
}
func handleMsgMhfPostBoostTimeQuestReturn(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeQuestReturn)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfPostBoostTime(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostBoostTime)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfPostBoostTimeLimit(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostBoostTimeLimit)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,176 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"time"
)
type CampaignEvent struct {
ID uint32
Unk0 uint32
MinHR int16
MaxHR int16
MinSR int16
MaxSR int16
MinGR int16
MaxGR int16
Unk1 uint16
Unk2 uint8
Unk3 uint8
Unk4 uint16
Unk5 uint16
Start time.Time
End time.Time
Unk6 uint8
String0 string
String1 string
String2 string
String3 string
Link string
Prefix string
Categories []uint16
}
type CampaignCategory struct {
ID uint16
Type uint8
Title string
Description string
}
type CampaignLink struct {
CategoryID uint16
CampaignID uint32
}
func handleMsgMhfEnumerateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateCampaign)
bf := byteframe.NewByteFrame()
events := []CampaignEvent{}
categories := []CampaignCategory{}
var campaignLinks []CampaignLink
if len(events) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(events)))
} else {
bf.WriteUint8(uint8(len(events)))
}
for _, event := range events {
bf.WriteUint32(event.ID)
bf.WriteUint32(event.Unk0)
bf.WriteInt16(event.MinHR)
bf.WriteInt16(event.MaxHR)
bf.WriteInt16(event.MinSR)
bf.WriteInt16(event.MaxSR)
if _config.ErupeConfig.RealClientMode >= _config.G3 {
bf.WriteInt16(event.MinGR)
bf.WriteInt16(event.MaxGR)
}
bf.WriteUint16(event.Unk1)
bf.WriteUint8(event.Unk2)
bf.WriteUint8(event.Unk3)
bf.WriteUint16(event.Unk4)
bf.WriteUint16(event.Unk5)
bf.WriteUint32(uint32(event.Start.Unix()))
bf.WriteUint32(uint32(event.End.Unix()))
bf.WriteUint8(event.Unk6)
ps.Uint8(bf, event.String0, true)
ps.Uint8(bf, event.String1, true)
ps.Uint8(bf, event.String2, true)
ps.Uint8(bf, event.String3, true)
ps.Uint8(bf, event.Link, true)
for i := range event.Categories {
campaignLinks = append(campaignLinks, CampaignLink{event.Categories[i], event.ID})
}
}
if len(events) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(events)))
} else {
bf.WriteUint8(uint8(len(events)))
}
for _, event := range events {
bf.WriteUint32(event.ID)
bf.WriteUint8(1) // Always 1?
bf.WriteBytes([]byte(event.Prefix))
}
if len(categories) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(categories)))
} else {
bf.WriteUint8(uint8(len(categories)))
}
for _, category := range categories {
bf.WriteUint16(category.ID)
bf.WriteUint8(category.Type)
xTitle := stringsupport.UTF8ToSJIS(category.Title)
xDescription := stringsupport.UTF8ToSJIS(category.Description)
bf.WriteUint8(uint8(len(xTitle)))
bf.WriteUint8(uint8(len(xDescription)))
bf.WriteBytes(xTitle)
bf.WriteBytes(xDescription)
}
if len(campaignLinks) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(campaignLinks)))
} else {
bf.WriteUint8(uint8(len(campaignLinks)))
}
for _, link := range campaignLinks {
bf.WriteUint16(link.CategoryID)
bf.WriteUint32(link.CampaignID)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfStateCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateCampaign)
bf := byteframe.NewByteFrame()
bf.WriteUint16(1)
bf.WriteUint16(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfApplyCampaign(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyCampaign)
bf := byteframe.NewByteFrame()
bf.WriteUint32(1)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEnumerateItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateItem)
items := []struct {
Unk0 uint32
Unk1 uint16
Unk2 uint16
Unk3 uint16
Unk4 uint32
Unk5 uint32
}{}
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(items)))
for _, item := range items {
bf.WriteUint32(item.Unk0)
bf.WriteUint16(item.Unk1)
bf.WriteUint16(item.Unk2)
bf.WriteUint16(item.Unk3)
bf.WriteUint32(item.Unk4)
bf.WriteUint32(item.Unk5)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfAcquireItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireItem)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,123 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"time"
)
type RyoudamaReward struct {
Unk0 uint8
Unk1 uint8
Unk2 uint16
Unk3 uint16
Unk4 uint16
Unk5 uint16
}
type RyoudamaKeyScore struct {
Unk0 uint8
Unk1 int32
}
type RyoudamaCharInfo struct {
CID uint32
Unk0 int32
Name string
}
type RyoudamaBoostInfo struct {
Start time.Time
End time.Time
}
type Ryoudama struct {
Reward []RyoudamaReward
KeyScore []RyoudamaKeyScore
CharInfo []RyoudamaCharInfo
BoostInfo []RyoudamaBoostInfo
Score []int32
}
func handleMsgMhfGetRyoudama(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRyoudama)
var data []*byteframe.ByteFrame
ryoudama := Ryoudama{Score: []int32{0}}
switch pkt.Request2 {
case 4:
for _, score := range ryoudama.Score {
bf := byteframe.NewByteFrame()
bf.WriteInt32(score)
data = append(data, bf)
}
case 5:
for _, info := range ryoudama.CharInfo {
bf := byteframe.NewByteFrame()
bf.WriteUint32(info.CID)
bf.WriteInt32(info.Unk0)
bf.WriteBytes(stringsupport.PaddedString(info.Name, 14, true))
data = append(data, bf)
}
case 6:
for _, info := range ryoudama.BoostInfo {
bf := byteframe.NewByteFrame()
bf.WriteUint32(uint32(info.Start.Unix()))
bf.WriteUint32(uint32(info.End.Unix()))
data = append(data, bf)
}
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfPostRyoudama(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetTinyBin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetTinyBin)
// requested after conquest quests
doAckBufSucceed(s, pkt.AckHandle, []byte{})
}
func handleMsgMhfPostTinyBin(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostTinyBin)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfCaravanMyScore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCaravanMyScore)
var data []*byteframe.ByteFrame
/*
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
*/
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfCaravanRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCaravanRanking)
var data []*byteframe.ByteFrame
/* RYOUDAN
bf.WriteInt32(1)
bf.WriteUint32(2)
bf.WriteBytes(stringsupport.PaddedString("Test", 26, true))
*/
/* PERSONAL
bf.WriteInt32(1)
bf.WriteBytes(stringsupport.PaddedString("Test", 14, true))
*/
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfCaravanMyRank(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCaravanMyRank)
var data []*byteframe.ByteFrame
/*
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
*/
doAckEarthSucceed(s, pkt.AckHandle, data)
}

View File

@@ -0,0 +1,448 @@
package channelserver
import (
"encoding/hex"
"erupe-ce/common/byteframe"
"erupe-ce/common/mhfcourse"
"erupe-ce/common/token"
"erupe-ce/config"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"fmt"
"golang.org/x/exp/slices"
"math"
"strconv"
"strings"
"time"
"go.uber.org/zap"
)
// MSG_SYS_CAST[ED]_BINARY types enum
const (
BinaryMessageTypeState = 0
BinaryMessageTypeChat = 1
BinaryMessageTypeQuest = 2
BinaryMessageTypeData = 3
BinaryMessageTypeMailNotify = 4
BinaryMessageTypeEmote = 6
)
// MSG_SYS_CAST[ED]_BINARY broadcast types enum
const (
BroadcastTypeTargeted = 0x01
BroadcastTypeStage = 0x03
BroadcastTypeServer = 0x06
BroadcastTypeWorld = 0x0a
)
var commands map[string]_config.Command
func init() {
commands = make(map[string]_config.Command)
zapConfig := zap.NewDevelopmentConfig()
zapConfig.DisableCaller = true
zapLogger, _ := zapConfig.Build()
defer zapLogger.Sync()
logger := zapLogger.Named("commands")
cmds := _config.ErupeConfig.Commands
for _, cmd := range cmds {
commands[cmd.Name] = cmd
if cmd.Enabled {
logger.Info(fmt.Sprintf("Command %s: Enabled, prefix: %s", cmd.Name, cmd.Prefix))
} else {
logger.Info(fmt.Sprintf("Command %s: Disabled", cmd.Name))
}
}
}
func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name))
}
func sendServerChatMessage(s *Session, message string) {
// Make the inside of the casted binary
bf := byteframe.NewByteFrame()
bf.SetLE()
msgBinChat := &binpacket.MsgBinChat{
Unk0: 0,
Type: 5,
Flags: 0x80,
Message: message,
SenderName: "Erupe",
}
msgBinChat.Build(bf)
castedBin := &mhfpacket.MsgSysCastedBinary{
CharID: s.charID,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}
s.QueueSendMHF(castedBin)
}
func parseChatCommand(s *Session, command string) {
args := strings.Split(command[1:], " ")
switch args[0] {
case commands["PSN"].Prefix:
if commands["PSN"].Enabled {
if len(args) > 1 {
var exists int
s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, args[1]).Scan(&exists)
if exists == 0 {
_, err := s.server.db.Exec(`UPDATE users u SET psn_id=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, args[1], s.charID)
if err == nil {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNSuccess"], args[1]))
}
} else {
sendServerChatMessage(s, s.server.dict["commandPSNExists"])
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["PSN"])
}
case commands["Reload"].Prefix:
if commands["Reload"].Enabled {
sendServerChatMessage(s, s.server.dict["commandReload"])
var temp mhfpacket.MHFPacket
deleteNotif := byteframe.NewByteFrame()
for _, object := range s.stage.objects {
if object.ownerCharID == s.charID {
continue
}
temp = &mhfpacket.MsgSysDeleteObject{ObjID: object.id}
deleteNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(deleteNotif, s.clientContext)
}
for _, session := range s.server.sessions {
if s == session {
continue
}
temp = &mhfpacket.MsgSysDeleteUser{CharID: session.charID}
deleteNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(deleteNotif, s.clientContext)
}
deleteNotif.WriteUint16(0x0010)
s.QueueSend(deleteNotif.Data())
time.Sleep(500 * time.Millisecond)
reloadNotif := byteframe.NewByteFrame()
for _, session := range s.server.sessions {
if s == session {
continue
}
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
reloadNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(reloadNotif, s.clientContext)
for i := 0; i < 3; i++ {
temp = &mhfpacket.MsgSysNotifyUserBinary{
CharID: session.charID,
BinaryType: uint8(i + 1),
}
reloadNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(reloadNotif, s.clientContext)
}
}
for _, obj := range s.stage.objects {
if obj.ownerCharID == s.charID {
continue
}
temp = &mhfpacket.MsgSysDuplicateObject{
ObjID: obj.id,
X: obj.x,
Y: obj.y,
Z: obj.z,
Unk0: 0,
OwnerCharID: obj.ownerCharID,
}
reloadNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(reloadNotif, s.clientContext)
}
reloadNotif.WriteUint16(0x0010)
s.QueueSend(reloadNotif.Data())
} else {
sendDisabledCommandMessage(s, commands["Reload"])
}
case commands["KeyQuest"].Prefix:
if commands["KeyQuest"].Enabled {
if len(args) > 1 {
if args[1] == "get" {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
} else if args[1] == "set" {
if len(args) > 2 && len(args[2]) == 16 {
hexd, _ := hex.DecodeString(args[2])
s.kqf = hexd
s.kqfOverride = true
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
}
}
}
} else {
sendDisabledCommandMessage(s, commands["KeyQuest"])
}
case commands["Rights"].Prefix:
if commands["Rights"].Enabled {
if len(args) > 1 {
v, _ := strconv.Atoi(args[1])
_, err := s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", v, s.charID)
if err == nil {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsSuccess"], v))
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Rights"])
}
case commands["Course"].Prefix:
if commands["Course"].Enabled {
if len(args) > 1 {
for _, course := range mhfcourse.Courses() {
for _, alias := range course.Aliases() {
if strings.ToLower(args[1]) == strings.ToLower(alias) {
if slices.Contains(s.server.erupeConfig.Courses, _config.Course{Name: course.Aliases()[0], Enabled: true}) {
var delta, rightsInt uint32
if mhfcourse.CourseExists(course.ID, s.courses) {
ei := slices.IndexFunc(s.courses, func(c mhfcourse.Course) bool {
for _, alias := range c.Aliases() {
if strings.ToLower(args[1]) == strings.ToLower(alias) {
return true
}
}
return false
})
if ei != -1 {
delta = uint32(-1 * math.Pow(2, float64(course.ID)))
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseDisabled"], course.Aliases()[0]))
}
} else {
delta = uint32(math.Pow(2, float64(course.ID)))
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseEnabled"], course.Aliases()[0]))
}
err := s.server.db.QueryRow("SELECT rights FROM users u INNER JOIN characters c ON u.id = c.user_id WHERE c.id = $1", s.charID).Scan(&rightsInt)
if err == nil {
s.server.db.Exec("UPDATE users u SET rights=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", rightsInt+delta, s.charID)
}
updateRights(s)
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseLocked"], course.Aliases()[0]))
}
return
}
}
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Course"])
}
case commands["Raviente"].Prefix:
if commands["Raviente"].Enabled {
if len(args) > 1 {
if s.server.getRaviSemaphore() != nil {
switch args[1] {
case "start":
if s.server.raviente.register[1] == 0 {
s.server.raviente.register[1] = s.server.raviente.register[3]
sendServerChatMessage(s, s.server.dict["commandRaviStartSuccess"])
s.notifyRavi()
} else {
sendServerChatMessage(s, s.server.dict["commandRaviStartError"])
}
case "cm", "check", "checkmultiplier", "multiplier":
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.GetRaviMultiplier()))
case "sr", "sendres", "resurrection", "ss", "sendsed", "rs", "reqsed":
if s.server.erupeConfig.RealClientMode == _config.ZZ {
switch args[1] {
case "sr", "sendres", "resurrection":
if s.server.raviente.state[28] > 0 {
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
s.server.raviente.state[28] = 0
} else {
sendServerChatMessage(s, s.server.dict["commandRaviResError"])
}
case "ss", "sendsed":
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
// Total BerRavi HP
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
s.server.raviente.support[1] = HP
case "rs", "reqsed":
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
// Total BerRavi HP
HP := s.server.raviente.state[0] + s.server.raviente.state[1] + s.server.raviente.state[2] + s.server.raviente.state[3] + s.server.raviente.state[4]
s.server.raviente.support[1] = HP + 1
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviVersion"])
}
default:
sendServerChatMessage(s, s.server.dict["commandRaviError"])
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviError"])
}
} else {
sendDisabledCommandMessage(s, commands["Raviente"])
}
case commands["Teleport"].Prefix:
if commands["Teleport"].Enabled {
if len(args) > 2 {
x, _ := strconv.Atoi(args[1])
y, _ := strconv.Atoi(args[2])
payload := byteframe.NewByteFrame()
payload.SetLE()
payload.WriteUint8(2) // SetState type(position == 2)
payload.WriteInt16(int16(x)) // X
payload.WriteInt16(int16(y)) // Y
payloadBytes := payload.Data()
s.QueueSendMHF(&mhfpacket.MsgSysCastedBinary{
CharID: s.charID,
MessageType: BinaryMessageTypeState,
RawDataPayload: payloadBytes,
})
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Teleport"])
}
}
}
func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCastBinary)
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x03 && len(pkt.RawDataPayload) == 0x10 {
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
_ = tmp.ReadBytes(9)
tmp.SetLE()
frame := tmp.ReadUint32()
sendServerChatMessage(s, fmt.Sprintf("TIME : %d'%d.%03d (%dframe)", frame/30/60, frame/30%60, int(math.Round(float64(frame%30*100)/3)), frame))
}
}
if s.server.erupeConfig.DevModeOptions.QuestDebugTools == true && s.server.erupeConfig.DevMode {
if pkt.BroadcastType == 0x03 && pkt.MessageType == 0x02 && len(pkt.RawDataPayload) > 32 {
// This is only correct most of the time
tmp.ReadBytes(20)
tmp.SetLE()
x := tmp.ReadFloat32()
y := tmp.ReadFloat32()
z := tmp.ReadFloat32()
s.logger.Debug("Coord", zap.Float32s("XYZ", []float32{x, y, z}))
}
}
// Parse out the real casted binary payload
var msgBinTargeted *binpacket.MsgBinTargeted
var authorLen, msgLen uint16
var msg []byte
isDiceCommand := false
if pkt.MessageType == BinaryMessageTypeChat {
tmp.SetLE()
tmp.Seek(int64(0), 0)
_ = tmp.ReadUint32()
authorLen = tmp.ReadUint16()
msgLen = tmp.ReadUint16()
msg = tmp.ReadNullTerminatedBytes()
}
// Customise payload
realPayload := pkt.RawDataPayload
if pkt.BroadcastType == BroadcastTypeTargeted {
tmp.SetBE()
tmp.Seek(int64(0), 0)
msgBinTargeted = &binpacket.MsgBinTargeted{}
err := msgBinTargeted.Parse(tmp)
if err != nil {
s.logger.Warn("Failed to parse targeted cast binary")
return
}
realPayload = msgBinTargeted.RawDataPayload
} else if pkt.MessageType == BinaryMessageTypeChat {
if msgLen == 6 && string(msg) == "@dice" {
isDiceCommand = true
roll := byteframe.NewByteFrame()
roll.WriteInt16(1) // Unk
roll.SetLE()
roll.WriteUint16(4) // Unk
roll.WriteUint16(authorLen)
dice := fmt.Sprintf("%d", token.RNG().Intn(100)+1)
roll.WriteUint16(uint16(len(dice) + 1))
roll.WriteNullTerminatedBytes([]byte(dice))
roll.WriteNullTerminatedBytes(tmp.ReadNullTerminatedBytes())
realPayload = roll.Data()
} else {
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
bf.SetLE()
chatMessage := &binpacket.MsgBinChat{}
chatMessage.Parse(bf)
if strings.HasPrefix(chatMessage.Message, "!") {
parseChatCommand(s, chatMessage.Message)
return
}
if (pkt.BroadcastType == BroadcastTypeStage && s.stage.id == "sl1Ns200p0a0u0") || pkt.BroadcastType == BroadcastTypeWorld {
s.server.DiscordChannelSend(chatMessage.SenderName, chatMessage.Message)
}
}
}
// Make the response to forward to the other client(s).
resp := &mhfpacket.MsgSysCastedBinary{
CharID: s.charID,
BroadcastType: pkt.BroadcastType, // (The client never uses Type0 upon receiving)
MessageType: pkt.MessageType,
RawDataPayload: realPayload,
}
// Send to the proper recipients.
switch pkt.BroadcastType {
case BroadcastTypeWorld:
s.server.WorldcastMHF(resp, s, nil)
case BroadcastTypeStage:
if isDiceCommand {
s.stage.BroadcastMHF(resp, nil) // send dice result back to caller
} else {
s.stage.BroadcastMHF(resp, s)
}
case BroadcastTypeServer:
if pkt.MessageType == 1 {
raviSema := s.server.getRaviSemaphore()
if raviSema != nil {
raviSema.BroadcastMHF(resp, s)
}
} else {
s.server.BroadcastMHF(resp, s)
}
case BroadcastTypeTargeted:
for _, targetID := range (*msgBinTargeted).TargetCharIDs {
char := s.server.FindSessionByCharID(targetID)
if char != nil {
char.QueueSendMHF(resp)
}
}
default:
s.Lock()
haveStage := s.stage != nil
if haveStage {
s.stage.BroadcastMHF(resp, s)
}
s.Unlock()
}
}
func handleMsgSysCastedBinary(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,217 @@
package channelserver
import (
"encoding/binary"
"errors"
"erupe-ce/common/bfutil"
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"erupe-ce/server/channelserver/compression/nullcomp"
"go.uber.org/zap"
)
type SavePointer int
const (
pGender = iota // +1
pRP // +2
pHouseTier // +5
pHouseData // +195
pBookshelfData // +5576
pGalleryData // +1748
pToreData // +240
pGardenData // +68
pWeaponType // +1
pWeaponID // +2
pHRP // +2
pGRP // +4
pKQF // +8
)
type CharacterSaveData struct {
CharID uint32
Name string
IsNewCharacter bool
Pointers map[SavePointer]int
Gender bool
RP uint16
HouseTier []byte
HouseData []byte
BookshelfData []byte
GalleryData []byte
ToreData []byte
GardenData []byte
WeaponType uint8
WeaponID uint16
HRP uint16
GR uint16
KQF []byte
compSave []byte
decompSave []byte
}
func getPointers() map[SavePointer]int {
pointers := map[SavePointer]int{pGender: 81}
switch _config.ErupeConfig.RealClientMode {
case _config.ZZ:
pointers[pWeaponID] = 128522
pointers[pWeaponType] = 128789
pointers[pHouseTier] = 129900
pointers[pToreData] = 130228
pointers[pHRP] = 130550
pointers[pGRP] = 130556
pointers[pHouseData] = 130561
pointers[pBookshelfData] = 139928
pointers[pGalleryData] = 140064
pointers[pGardenData] = 142424
pointers[pRP] = 142614
pointers[pKQF] = 146720
case _config.Z2, _config.Z1, _config.G101, _config.G10:
pointers[pWeaponID] = 92522
pointers[pWeaponType] = 92789
pointers[pHouseTier] = 93900
pointers[pToreData] = 94228
pointers[pHRP] = 94550
pointers[pGRP] = 94556
pointers[pHouseData] = 94561
pointers[pBookshelfData] = 103928
pointers[pGalleryData] = 104064
pointers[pGardenData] = 106424
pointers[pRP] = 106614
pointers[pKQF] = 110720
}
return pointers
}
func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) {
result, err := s.server.db.Query("SELECT id, savedata, is_new_character, name FROM characters WHERE id = $1", charID)
if err != nil {
s.logger.Error("Failed to get savedata", zap.Error(err), zap.Uint32("charID", charID))
return nil, err
}
defer result.Close()
if !result.Next() {
err = errors.New("no savedata found")
s.logger.Error("No savedata found", zap.Uint32("charID", charID))
return nil, err
}
saveData := &CharacterSaveData{
Pointers: getPointers(),
}
err = result.Scan(&saveData.CharID, &saveData.compSave, &saveData.IsNewCharacter, &saveData.Name)
if err != nil {
s.logger.Error("Failed to scan savedata", zap.Error(err), zap.Uint32("charID", charID))
return nil, err
}
if saveData.compSave == nil {
return saveData, nil
}
err = saveData.Decompress()
if err != nil {
s.logger.Error("Failed to decompress savedata", zap.Error(err))
return nil, err
}
saveData.updateStructWithSaveData()
return saveData, nil
}
func (save *CharacterSaveData) Save(s *Session) {
if !s.kqfOverride {
s.kqf = save.KQF
} else {
save.KQF = s.kqf
}
save.updateSaveDataWithStruct()
if _config.ErupeConfig.RealClientMode >= _config.G1 {
err := save.Compress()
if err != nil {
s.logger.Error("Failed to compress savedata", zap.Error(err))
return
}
} else {
// Saves were not compressed
save.compSave = save.decompSave
}
_, err := s.server.db.Exec(`UPDATE characters SET savedata=$1, is_new_character=false, hrp=$2, gr=$3, is_female=$4, weapon_type=$5, weapon_id=$6 WHERE id=$7
`, save.compSave, save.HRP, save.GR, save.Gender, save.WeaponType, save.WeaponID, save.CharID)
if err != nil {
s.logger.Error("Failed to update savedata", zap.Error(err), zap.Uint32("charID", save.CharID))
}
s.server.db.Exec(`UPDATE user_binary SET house_tier=$1, house_data=$2, bookshelf=$3, gallery=$4, tore=$5, garden=$6 WHERE id=$7
`, save.HouseTier, save.HouseData, save.BookshelfData, save.GalleryData, save.ToreData, save.GardenData, s.charID)
}
func (save *CharacterSaveData) Compress() error {
var err error
save.compSave, err = nullcomp.Compress(save.decompSave)
if err != nil {
return err
}
return nil
}
func (save *CharacterSaveData) Decompress() error {
var err error
save.decompSave, err = nullcomp.Decompress(save.compSave)
if err != nil {
return err
}
return nil
}
// This will update the character save with the values stored in the save struct
func (save *CharacterSaveData) updateSaveDataWithStruct() {
rpBytes := make([]byte, 2)
binary.LittleEndian.PutUint16(rpBytes, save.RP)
if _config.ErupeConfig.RealClientMode >= _config.G10 {
copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes)
copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF)
}
}
// This will update the save struct with the values stored in the character save
func (save *CharacterSaveData) updateStructWithSaveData() {
save.Name = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[88:100]))
if save.decompSave[save.Pointers[pGender]] == 1 {
save.Gender = true
} else {
save.Gender = false
}
if !save.IsNewCharacter {
if _config.ErupeConfig.RealClientMode >= _config.G10 {
save.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+2])
save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+5]
save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+195]
save.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+5576]
save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+1748]
save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+240]
save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+68]
save.WeaponType = save.decompSave[save.Pointers[pWeaponType]]
save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2])
save.HRP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHRP] : save.Pointers[pHRP]+2])
if save.HRP == uint16(999) {
save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))
}
save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8]
}
}
return
}
func handleMsgMhfSexChanger(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSexChanger)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,114 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysEnumerateClient)
s.server.stagesLock.RLock()
stage, ok := s.server.stages[pkt.StageID]
if !ok {
s.server.stagesLock.RUnlock()
s.logger.Warn("Can't enumerate clients for stage that doesn't exist!", zap.String("stageID", pkt.StageID))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
s.server.stagesLock.RUnlock()
// Read-lock the stage and make the response with all of the charID's in the stage.
resp := byteframe.NewByteFrame()
stage.RLock()
var clients []uint32
switch pkt.Get {
case 0: // All
for _, cid := range stage.clients {
clients = append(clients, cid)
}
case 1: // Not ready
for cid, ready := range stage.reservedClientSlots {
if !ready {
clients = append(clients, cid)
}
}
case 2: // Ready
for cid, ready := range stage.reservedClientSlots {
if ready {
clients = append(clients, cid)
}
}
}
resp.WriteUint16(uint16(len(clients)))
for _, cid := range clients {
resp.WriteUint32(cid)
}
stage.RUnlock()
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
s.logger.Debug("MsgSysEnumerateClient Done!")
}
func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfListMember)
var csv string
var count uint32
resp := byteframe.NewByteFrame()
resp.WriteUint32(0) // Blacklist count
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
}
cids := stringsupport.CSVElems(csv)
for _, cid := range cids {
var name string
err = s.server.db.QueryRow("SELECT name FROM characters WHERE id=$1", cid).Scan(&name)
if err != nil {
continue
}
count++
resp.WriteUint32(uint32(cid))
resp.WriteUint32(16)
resp.WriteBytes(stringsupport.PaddedString(name, 16, true))
}
resp.Seek(0, 0)
resp.WriteUint32(count)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOprMember)
var csv string
if pkt.Blacklist {
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
}
if pkt.Operation {
csv = stringsupport.CSVRemove(csv, int(pkt.CharID))
} else {
csv = stringsupport.CSVAdd(csv, int(pkt.CharID))
}
s.server.db.Exec("UPDATE characters SET blocked=$1 WHERE id=$2", csv, s.charID)
} else { // Friendlist
err := s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
}
if pkt.Operation {
csv = stringsupport.CSVRemove(csv, int(pkt.CharID))
} else {
csv = stringsupport.CSVAdd(csv, int(pkt.CharID))
}
s.server.db.Exec("UPDATE characters SET friends=$1 WHERE id=$2", csv, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfShutClient(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysHideClient(s *Session, p mhfpacket.MHFPacket) {}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,89 @@
package channelserver
import (
"fmt"
"github.com/bwmarrin/discordgo"
"sort"
"strings"
"unicode"
)
type Player struct {
CharName string
QuestID int
}
func getPlayerSlice(s *Server) []Player {
var p []Player
var questIndex int
for _, channel := range s.Channels {
for _, stage := range channel.stages {
if len(stage.clients) == 0 {
continue
}
questID := 0
if stage.isQuest() {
questIndex++
questID = questIndex
}
for client := range stage.clients {
p = append(p, Player{
CharName: client.Name,
QuestID: questID,
})
}
}
}
return p
}
func getCharacterList(s *Server) string {
questEmojis := []string{
":person_in_lotus_position:",
":white_circle:",
":red_circle:",
":blue_circle:",
":brown_circle:",
":green_circle:",
":purple_circle:",
":yellow_circle:",
":orange_circle:",
":black_circle:",
}
playerSlice := getPlayerSlice(s)
sort.SliceStable(playerSlice, func(i, j int) bool {
return playerSlice[i].QuestID < playerSlice[j].QuestID
})
message := fmt.Sprintf("===== Online: %d =====\n", len(playerSlice))
for _, player := range playerSlice {
message += fmt.Sprintf("%s %s", questEmojis[player.QuestID], player.CharName)
}
return message
}
// onDiscordMessage handles receiving messages from discord and forwarding them ingame.
func (s *Server) onDiscordMessage(ds *discordgo.Session, m *discordgo.MessageCreate) {
// Ignore messages from our bot, or ones that are not in the correct channel.
if m.Author.Bot || m.ChannelID != s.erupeConfig.Discord.RealtimeChannelID {
return
}
paddedName := strings.TrimSpace(strings.Map(func(r rune) rune {
if r > unicode.MaxASCII {
return -1
}
return r
}, m.Author.Username))
for i := 0; i < 8-len(m.Author.Username); i++ {
paddedName += " "
}
message := fmt.Sprintf("[D] %s > %s", paddedName, m.Content)
s.BroadcastChatMessage(s.discordBot.NormalizeDiscordMessage(message))
}

View File

@@ -0,0 +1,160 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
type ItemDist struct {
ID uint32 `db:"id"`
Deadline uint32 `db:"deadline"`
TimesAcceptable uint16 `db:"times_acceptable"`
TimesAccepted uint16 `db:"times_accepted"`
MinHR uint16 `db:"min_hr"`
MaxHR uint16 `db:"max_hr"`
MinSR uint16 `db:"min_sr"`
MaxSR uint16 `db:"max_sr"`
MinGR uint16 `db:"min_gr"`
MaxGR uint16 `db:"max_gr"`
EventName string `db:"event_name"`
Description string `db:"description"`
Data []byte `db:"data"`
}
func handleMsgMhfEnumerateDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateDistItem)
bf := byteframe.NewByteFrame()
distCount := 0
dists, err := s.server.db.Queryx(`
SELECT d.id, event_name, description, times_acceptable,
min_hr, max_hr, min_sr, max_sr, min_gr, max_gr,
(
SELECT count(*)
FROM distributions_accepted da
WHERE d.id = da.distribution_id
AND da.character_id = $1
) AS times_accepted,
CASE
WHEN (EXTRACT(epoch FROM deadline)::int) IS NULL THEN 0
ELSE (EXTRACT(epoch FROM deadline)::int)
END deadline
FROM distribution d
WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC;
`, s.charID, pkt.Unk0)
if err != nil {
s.logger.Error("Error getting distribution data from db", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
for dists.Next() {
distCount++
distData := &ItemDist{}
err = dists.StructScan(&distData)
if err != nil {
s.logger.Error("Error parsing item distribution data", zap.Error(err))
}
bf.WriteUint32(distData.ID)
bf.WriteUint32(distData.Deadline)
bf.WriteUint32(0) // Unk
bf.WriteUint16(distData.TimesAcceptable)
bf.WriteUint16(distData.TimesAccepted)
bf.WriteUint16(0) // Unk
bf.WriteUint16(distData.MinHR)
bf.WriteUint16(distData.MaxHR)
bf.WriteUint16(distData.MinSR)
bf.WriteUint16(distData.MaxSR)
bf.WriteUint16(distData.MinGR)
bf.WriteUint16(distData.MaxGR)
bf.WriteUint32(0) // Unk
bf.WriteUint32(0) // Unk
ps.Uint16(bf, distData.EventName, true)
bf.WriteBytes(make([]byte, 391))
}
resp := byteframe.NewByteFrame()
resp.WriteUint16(uint16(distCount))
resp.WriteBytes(bf.Data())
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
}
func handleMsgMhfApplyDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfApplyDistItem)
if pkt.DistributionID == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
} else {
row := s.server.db.QueryRowx("SELECT data FROM distribution WHERE id = $1", pkt.DistributionID)
dist := &ItemDist{}
err := row.StructScan(dist)
if err != nil {
s.logger.Error("Error parsing item distribution data", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
return
}
if len(dist.Data) >= 2 {
distData := byteframe.NewByteFrameFromBytes(dist.Data)
distItems := int(distData.ReadUint16())
for i := 0; i < distItems; i++ {
if len(dist.Data) >= 2+(i*13) {
itemType := distData.ReadUint8()
_ = distData.ReadBytes(6)
quantity := int(distData.ReadUint16())
_ = distData.ReadBytes(4)
switch itemType {
case 17:
_ = addPointNetcafe(s, quantity)
case 19:
s.server.db.Exec("UPDATE users u SET gacha_premium=gacha_premium+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID)
case 20:
s.server.db.Exec("UPDATE users u SET gacha_trial=gacha_trial+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID)
case 21:
s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", quantity, s.charID)
case 23:
saveData, err := GetCharacterSaveData(s, s.charID)
if err == nil {
saveData.RP += uint16(quantity)
saveData.Save(s)
}
}
}
}
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(pkt.DistributionID)
bf.WriteBytes(dist.Data)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
_, err = s.server.db.Exec(`
INSERT INTO public.distributions_accepted
VALUES ($1, $2)
`, pkt.DistributionID, s.charID)
if err != nil {
s.logger.Error("Error updating accepted dist count", zap.Error(err))
}
}
}
func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireDistItem)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetDistDescription(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetDistDescription)
var desc string
err := s.server.db.QueryRow("SELECT description FROM distribution WHERE id = $1", pkt.DistributionID).Scan(&desc)
if err != nil {
s.logger.Error("Error parsing item distribution description", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrame()
ps.Uint16(bf, desc, true)
ps.Uint16(bf, "", false)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -0,0 +1,321 @@
package channelserver
import (
"encoding/hex"
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func cleanupDiva(s *Session) {
s.server.db.Exec("DELETE FROM events WHERE event_type='diva'")
}
func generateDivaTimestamps(s *Session, start uint32, debug bool) []uint32 {
timestamps := make([]uint32, 6)
midnight := TimeMidnight()
if debug && start <= 3 {
midnight := uint32(midnight.Unix())
switch start {
case 1:
timestamps[0] = midnight
timestamps[1] = timestamps[0] + 601200
timestamps[2] = timestamps[1] + 3900
timestamps[3] = timestamps[1] + 604800
timestamps[4] = timestamps[3] + 3900
timestamps[5] = timestamps[3] + 604800
case 2:
timestamps[0] = midnight - 605100
timestamps[1] = midnight - 3900
timestamps[2] = midnight
timestamps[3] = timestamps[1] + 604800
timestamps[4] = timestamps[3] + 3900
timestamps[5] = timestamps[3] + 604800
case 3:
timestamps[0] = midnight - 1213800
timestamps[1] = midnight - 608700
timestamps[2] = midnight - 604800
timestamps[3] = midnight - 3900
timestamps[4] = midnight
timestamps[5] = timestamps[3] + 604800
}
return timestamps
}
if start == 0 || TimeAdjusted().Unix() > int64(start)+2977200 {
cleanupDiva(s)
// Generate a new diva defense, starting midnight tomorrow
start = uint32(midnight.Add(24 * time.Hour).Unix())
s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('diva', to_timestamp($1)::timestamp without time zone)", start)
}
timestamps[0] = start
timestamps[1] = timestamps[0] + 601200
timestamps[2] = timestamps[1] + 3900
timestamps[3] = timestamps[1] + 604800
timestamps[4] = timestamps[3] + 3900
timestamps[5] = timestamps[3] + 604800
return timestamps
}
func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
bf := byteframe.NewByteFrame()
id, start := uint32(0xCAFEBEEF), uint32(0)
rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='diva'")
for rows.Next() {
rows.Scan(&id, &start)
}
var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.DivaEvent >= 0 {
if s.server.erupeConfig.DevModeOptions.DivaEvent == 0 {
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 32))
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 36))
}
return
}
timestamps = generateDivaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.DivaEvent), true)
} else {
timestamps = generateDivaTimestamps(s, start, false)
}
if s.server.erupeConfig.RealClientMode <= _config.Z1 {
bf.WriteUint32(id)
}
for i := range timestamps {
bf.WriteUint32(timestamps[i])
}
bf.WriteUint16(0x19) // Unk 00011001
bf.WriteUint16(0x2D) // Unk 00101101
bf.WriteUint16(0x02) // Unk 00000010
bf.WriteUint16(0x02) // Unk 00000010
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetUdInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdInfo)
// Message that appears on the Diva Defense NPC and triggers the green exclamation mark
udInfos := []struct {
Text string
StartTime time.Time
EndTime time.Time
}{}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(udInfos)))
for _, udInfo := range udInfos {
resp.WriteBytes(stringsupport.PaddedString(udInfo.Text, 1024, true))
resp.WriteUint32(uint32(udInfo.StartTime.Unix()))
resp.WriteUint32(uint32(udInfo.EndTime.Unix()))
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetKijuInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKijuInfo)
// Temporary canned response
data, _ := hex.DecodeString("04965C959782CC8B468EEC00000000000000000000000000000000000000000000815C82A082E782B582DC82A982BA82CC82AB82B682E3815C0A965C959782C682CD96D282E98E7682A281420A95B782AD8ED282C997458B4382F0975E82A682E98142000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001018BAD8C8282CC8B468EEC00000000000000000000000000000000000000000000815C82AB82E582A482B082AB82CC82AB82B682E3815C0A8BAD8C8282C682CD8BAD82A290BA904681420A95B782AD8ED282CC97CD82F08CA482AC909F82DC82B78142200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003138C8B8F5782CC8B468EEC00000000000000000000000000000000000000000000815C82AF82C182B582E382A482CC82AB82B682E3815C0A8C8B8F5782C682CD8A6D8CC582BD82E9904D978A81420A8F5782DF82E982D982C782C98EEB906C82BD82BF82CC90B8905F97CD82C682C882E9814200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041189CC8CEC82CC8B468EEC00000000000000000000000000000000000000000000815C82A482BD82DC82E082E882CC82AB82B682E3815C0A89CC8CEC82C682CD89CC955082CC8CEC82E881420A8F5782DF82E982D982C782C98EEB906C82BD82BF82CC8E7882A682C682C882E9814220000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000212")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSetKiju(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetKiju)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfAddUdPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAddUdPoint)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetUdMyPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdMyPoint)
// Temporary canned response
data, _ := hex.DecodeString("00040000013C000000FA000000000000000000040000007E0000003C02000000000000000000000000000000000000000000000000000002000004CC00000438000000000000000000000000000000000000000000000000000000020000026E00000230000000000000000000020000007D0000007D000000000000000000000000000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetUdTotalPointInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdTotalPointInfo)
// Temporary canned response
data, _ := hex.DecodeString("00000000000007A12000000000000F424000000000001E848000000000002DC6C000000000003D090000000000004C4B4000000000005B8D8000000000006ACFC000000000007A1200000000000089544000000000009896800000000000E4E1C00000000001312D0000000000017D78400000000001C9C3800000000002160EC00000000002625A000000000002AEA5400000000002FAF0800000000003473BC0000000000393870000000000042C1D800000000004C4B40000000000055D4A800000000005F5E10000000000008954400000000001C9C3800000000003473BC00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001020300000000000000000000000000000000000000000000000000000000000000000000000000000000101F1420")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetUdSelectedColorInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdSelectedColorInfo)
// Unk
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x01, 0x01, 0x01, 0x02, 0x03, 0x02, 0x00, 0x00})
}
func handleMsgMhfGetUdMonsterPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdMonsterPoint)
monsterPoints := []struct {
MID uint8
Points uint16
}{
{MID: 0x01, Points: 0x3C}, // em1 Rathian
{MID: 0x02, Points: 0x5A}, // em2 Fatalis
{MID: 0x06, Points: 0x14}, // em6 Yian Kut-Ku
{MID: 0x07, Points: 0x50}, // em7 Lao-Shan Lung
{MID: 0x08, Points: 0x28}, // em8 Cephadrome
{MID: 0x0B, Points: 0x3C}, // em11 Rathalos
{MID: 0x0E, Points: 0x3C}, // em14 Diablos
{MID: 0x0F, Points: 0x46}, // em15 Khezu
{MID: 0x11, Points: 0x46}, // em17 Gravios
{MID: 0x14, Points: 0x28}, // em20 Gypceros
{MID: 0x15, Points: 0x3C}, // em21 Plesioth
{MID: 0x16, Points: 0x32}, // em22 Basarios
{MID: 0x1A, Points: 0x32}, // em26 Monoblos
{MID: 0x1B, Points: 0x0A}, // em27 Velocidrome
{MID: 0x1C, Points: 0x0A}, // em28 Gendrome
{MID: 0x1F, Points: 0x0A}, // em31 Iodrome
{MID: 0x21, Points: 0x50}, // em33 Kirin
{MID: 0x24, Points: 0x64}, // em36 Crimson Fatalis
{MID: 0x25, Points: 0x3C}, // em37 Pink Rathian
{MID: 0x26, Points: 0x1E}, // em38 Blue Yian Kut-Ku
{MID: 0x27, Points: 0x28}, // em39 Purple Gypceros
{MID: 0x28, Points: 0x50}, // em40 Yian Garuga
{MID: 0x29, Points: 0x5A}, // em41 Silver Rathalos
{MID: 0x2A, Points: 0x50}, // em42 Gold Rathian
{MID: 0x2B, Points: 0x3C}, // em43 Black Diablos
{MID: 0x2C, Points: 0x3C}, // em44 White Monoblos
{MID: 0x2D, Points: 0x46}, // em45 Red Khezu
{MID: 0x2E, Points: 0x3C}, // em46 Green Plesioth
{MID: 0x2F, Points: 0x50}, // em47 Black Gravios
{MID: 0x30, Points: 0x1E}, // em48 Daimyo Hermitaur
{MID: 0x31, Points: 0x3C}, // em49 Azure Rathalos
{MID: 0x32, Points: 0x50}, // em50 Ashen Lao-Shan Lung
{MID: 0x33, Points: 0x3C}, // em51 Blangonga
{MID: 0x34, Points: 0x28}, // em52 Congalala
{MID: 0x35, Points: 0x50}, // em53 Rajang
{MID: 0x36, Points: 0x6E}, // em54 Kushala Daora
{MID: 0x37, Points: 0x50}, // em55 Shen Gaoren
{MID: 0x3A, Points: 0x50}, // em58 Yama Tsukami
{MID: 0x3B, Points: 0x6E}, // em59 Chameleos
{MID: 0x40, Points: 0x64}, // em64 Lunastra
{MID: 0x41, Points: 0x6E}, // em65 Teostra
{MID: 0x43, Points: 0x28}, // em67 Shogun Ceanataur
{MID: 0x44, Points: 0x0A}, // em68 Bulldrome
{MID: 0x47, Points: 0x6E}, // em71 White Fatalis
{MID: 0x4A, Points: 0xFA}, // em74 Hypnocatrice
{MID: 0x4B, Points: 0xFA}, // em75 Lavasioth
{MID: 0x4C, Points: 0x46}, // em76 Tigrex
{MID: 0x4D, Points: 0x64}, // em77 Akantor
{MID: 0x4E, Points: 0xFA}, // em78 Bright Hypnoc
{MID: 0x4F, Points: 0xFA}, // em79 Lavasioth Subspecies
{MID: 0x50, Points: 0xFA}, // em80 Espinas
{MID: 0x51, Points: 0xFA}, // em81 Orange Espinas
{MID: 0x52, Points: 0xFA}, // em82 White Hypnoc
{MID: 0x53, Points: 0xFA}, // em83 Akura Vashimu
{MID: 0x54, Points: 0xFA}, // em84 Akura Jebia
{MID: 0x55, Points: 0xFA}, // em85 Berukyurosu
{MID: 0x59, Points: 0xFA}, // em89 Pariapuria
{MID: 0x5A, Points: 0xFA}, // em90 White Espinas
{MID: 0x5B, Points: 0xFA}, // em91 Kamu Orugaron
{MID: 0x5C, Points: 0xFA}, // em92 Nono Orugaron
{MID: 0x5E, Points: 0xFA}, // em94 Dyuragaua
{MID: 0x5F, Points: 0xFA}, // em95 Doragyurosu
{MID: 0x60, Points: 0xFA}, // em96 Gurenzeburu
{MID: 0x63, Points: 0xFA}, // em99 Rukodiora
{MID: 0x65, Points: 0xFA}, // em101 Gogomoa
{MID: 0x67, Points: 0xFA}, // em103 Taikun Zamuza
{MID: 0x68, Points: 0xFA}, // em104 Abiorugu
{MID: 0x69, Points: 0xFA}, // em105 Kuarusepusu
{MID: 0x6A, Points: 0xFA}, // em106 Odibatorasu
{MID: 0x6B, Points: 0xFA}, // em107 Disufiroa
{MID: 0x6C, Points: 0xFA}, // em108 Rebidiora
{MID: 0x6D, Points: 0xFA}, // em109 Anorupatisu
{MID: 0x6E, Points: 0xFA}, // em110 Hyujikiki
{MID: 0x6F, Points: 0xFA}, // em111 Midogaron
{MID: 0x70, Points: 0xFA}, // em112 Giaorugu
{MID: 0x72, Points: 0xFA}, // em114 Farunokku
{MID: 0x73, Points: 0xFA}, // em115 Pokaradon
{MID: 0x74, Points: 0xFA}, // em116 Shantien
{MID: 0x77, Points: 0xFA}, // em119 Goruganosu
{MID: 0x78, Points: 0xFA}, // em120 Aruganosu
{MID: 0x79, Points: 0xFA}, // em121 Baruragaru
{MID: 0x7A, Points: 0xFA}, // em122 Zerureusu
{MID: 0x7B, Points: 0xFA}, // em123 Gougarf
{MID: 0x7D, Points: 0xFA}, // em125 Forokururu
{MID: 0x7E, Points: 0xFA}, // em126 Meraginasu
{MID: 0x7F, Points: 0xFA}, // em127 Diorekkusu
{MID: 0x80, Points: 0xFA}, // em128 Garuba Daora
{MID: 0x81, Points: 0xFA}, // em129 Inagami
{MID: 0x82, Points: 0xFA}, // em130 Varusaburosu
{MID: 0x83, Points: 0xFA}, // em131 Poborubarumu
{MID: 0x8B, Points: 0xFA}, // em139 Gureadomosu
{MID: 0x8C, Points: 0xFA}, // em140 Harudomerugu
{MID: 0x8D, Points: 0xFA}, // em141 Toridcless
{MID: 0x8E, Points: 0xFA}, // em142 Gasurabazura
{MID: 0x90, Points: 0xFA}, // em144 Yama Kurai
{MID: 0x92, Points: 0x78}, // em146 Zinogre
{MID: 0x93, Points: 0x78}, // em147 Deviljho
{MID: 0x94, Points: 0x78}, // em148 Brachydios
{MID: 0x96, Points: 0xFA}, // em150 Toa Tesukatora
{MID: 0x97, Points: 0x78}, // em151 Barioth
{MID: 0x98, Points: 0x78}, // em152 Uragaan
{MID: 0x99, Points: 0x78}, // em153 Stygian Zinogre
{MID: 0x9A, Points: 0xFA}, // em154 Guanzorumu
{MID: 0x9E, Points: 0xFA}, // em158 Voljang
{MID: 0x9F, Points: 0x78}, // em159 Nargacuga
{MID: 0xA0, Points: 0xFA}, // em160 Keoaruboru
{MID: 0xA1, Points: 0xFA}, // em161 Zenaserisu
{MID: 0xA2, Points: 0x78}, // em162 Gore Magala
{MID: 0xA4, Points: 0x78}, // em164 Shagaru Magala
{MID: 0xA5, Points: 0x78}, // em165 Amatsu
{MID: 0xA6, Points: 0xFA}, // em166 Elzelion
{MID: 0xA9, Points: 0x78}, // em169 Seregios
{MID: 0xAA, Points: 0xFA}, // em170 Bogabadorumu
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(monsterPoints)))
for _, mp := range monsterPoints {
resp.WriteUint8(mp.MID)
resp.WriteUint16(mp.Points)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetUdDailyPresentList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdDailyPresentList)
// Temporary canned response
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetUdNormaPresentList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdNormaPresentList)
// Temporary canned response
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfAcquireUdItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireUdItem)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetUdRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdRanking)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetUdMyRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdMyRanking)
// Temporary canned response
data, _ := hex.DecodeString("00000515000005150000CEB4000003CE000003CE0000CEB44D49444E494748542D414E47454C0000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}

View File

@@ -0,0 +1,223 @@
package channelserver
import (
"erupe-ce/common/token"
_config "erupe-ce/config"
"math"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
type Event struct {
EventType uint16
Unk1 uint16
Unk2 uint16
Unk3 uint16
Unk4 uint16
Unk5 uint32
Unk6 uint32
QuestFileIDs []uint16
}
func handleMsgMhfEnumerateEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateEvent)
bf := byteframe.NewByteFrame()
events := []Event{}
bf.WriteUint8(uint8(len(events)))
for _, event := range events {
bf.WriteUint16(event.EventType)
bf.WriteUint16(event.Unk1)
bf.WriteUint16(event.Unk2)
bf.WriteUint16(event.Unk3)
bf.WriteUint16(event.Unk4)
bf.WriteUint32(event.Unk5)
bf.WriteUint32(event.Unk6)
if event.EventType == 2 {
bf.WriteUint8(uint8(len(event.QuestFileIDs)))
for _, qf := range event.QuestFileIDs {
bf.WriteUint16(qf)
}
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
type activeFeature struct {
StartTime time.Time `db:"start_time"`
ActiveFeatures uint32 `db:"featured"`
}
func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetWeeklySchedule)
var features []activeFeature
times := []time.Time{
TimeMidnight().Add(-24 * time.Hour),
TimeMidnight(),
TimeMidnight().Add(24 * time.Hour),
}
for _, t := range times {
var temp activeFeature
err := s.server.db.QueryRowx(`SELECT start_time, featured FROM feature_weapon WHERE start_time=$1`, t).StructScan(&temp)
if err != nil || temp.StartTime.IsZero() {
temp = generateFeatureWeapons(s.server.erupeConfig.GameplayOptions.FeaturedWeapons)
temp.StartTime = t
s.server.db.Exec(`INSERT INTO feature_weapon VALUES ($1, $2)`, temp.StartTime, temp.ActiveFeatures)
}
features = append(features, temp)
}
bf := byteframe.NewByteFrame()
bf.WriteUint8(uint8(len(features)))
bf.WriteUint32(uint32(TimeAdjusted().Add(-5 * time.Minute).Unix()))
for _, feature := range features {
bf.WriteUint32(uint32(feature.StartTime.Unix()))
bf.WriteUint32(feature.ActiveFeatures)
bf.WriteUint16(0)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func generateFeatureWeapons(count int) activeFeature {
_max := 14
if _config.ErupeConfig.RealClientMode < _config.ZZ {
_max = 13
}
if _config.ErupeConfig.RealClientMode < _config.G10 {
_max = 12
}
if _config.ErupeConfig.RealClientMode < _config.GG {
_max = 11
}
if count > _max {
count = _max
}
nums := make([]int, 0)
var result int
for len(nums) < count {
rng := token.RNG()
num := rng.Intn(_max)
exist := false
for _, v := range nums {
if v == num {
exist = true
break
}
}
if !exist {
nums = append(nums, num)
}
}
for _, num := range nums {
result += int(math.Pow(2, float64(num)))
}
return activeFeature{ActiveFeatures: uint32(result)}
}
type loginBoost struct {
WeekReq uint8 `db:"week_req"`
WeekCount uint8
Active bool
Expiration time.Time `db:"expiration"`
Reset time.Time `db:"reset"`
}
func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKeepLoginBoostStatus)
bf := byteframe.NewByteFrame()
var loginBoosts []loginBoost
rows, err := s.server.db.Queryx("SELECT week_req, expiration, reset FROM login_boost WHERE char_id=$1 ORDER BY week_req", s.charID)
if err != nil || s.server.erupeConfig.GameplayOptions.DisableLoginBoost {
rows.Close()
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 35))
return
}
for rows.Next() {
var temp loginBoost
rows.StructScan(&temp)
loginBoosts = append(loginBoosts, temp)
}
if len(loginBoosts) == 0 {
temp := TimeWeekStart()
loginBoosts = []loginBoost{
{WeekReq: 1, Expiration: temp},
{WeekReq: 2, Expiration: temp},
{WeekReq: 3, Expiration: temp},
{WeekReq: 4, Expiration: temp},
{WeekReq: 5, Expiration: temp},
}
for _, boost := range loginBoosts {
s.server.db.Exec(`INSERT INTO login_boost VALUES ($1, $2, $3, $4)`, s.charID, boost.WeekReq, boost.Expiration, time.Time{})
}
}
for _, boost := range loginBoosts {
// Reset if next week
if !boost.Reset.IsZero() && boost.Reset.Before(TimeAdjusted()) {
boost.Expiration = TimeWeekStart()
boost.Reset = time.Time{}
s.server.db.Exec(`UPDATE login_boost SET expiration=$1, reset=$2 WHERE char_id=$3 AND week_req=$4`, boost.Expiration, boost.Reset, s.charID, boost.WeekReq)
}
boost.WeekCount = uint8((TimeAdjusted().Unix()-boost.Expiration.Unix())/604800 + 1)
if boost.WeekCount >= boost.WeekReq {
boost.Active = true
boost.WeekCount = boost.WeekReq
}
// Show reset timer on expired boosts
if boost.Reset.After(TimeAdjusted()) {
boost.Active = true
boost.WeekCount = 0
}
bf.WriteUint8(boost.WeekReq)
bf.WriteBool(boost.Active)
bf.WriteUint8(boost.WeekCount)
if !boost.Reset.IsZero() {
bf.WriteUint32(uint32(boost.Expiration.Unix()))
} else {
bf.WriteUint32(0)
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUseKeepLoginBoost)
var expiration time.Time
bf := byteframe.NewByteFrame()
bf.WriteUint8(0)
switch pkt.BoostWeekUsed {
case 1:
fallthrough
case 3:
expiration = TimeAdjusted().Add(120 * time.Minute)
case 4:
expiration = TimeAdjusted().Add(180 * time.Minute)
case 2:
fallthrough
case 5:
expiration = TimeAdjusted().Add(240 * time.Minute)
}
bf.WriteUint32(uint32(expiration.Unix()))
s.server.db.Exec(`UPDATE login_boost SET expiration=$1, reset=$2 WHERE char_id=$3 AND week_req=$4`, expiration, TimeWeekNext(), s.charID, pkt.BoostWeekUsed)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfSetRestrictionEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetRestrictionEvent)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,499 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/token"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"sort"
"time"
)
func handleMsgMhfSaveMezfesData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveMezfesData)
s.server.db.Exec(`UPDATE characters SET mezfes=$1 WHERE id=$2`, pkt.RawDataPayload, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadMezfesData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadMezfesData)
var data []byte
s.server.db.QueryRow(`SELECT mezfes FROM characters WHERE id=$1`, s.charID).Scan(&data)
bf := byteframe.NewByteFrame()
if len(data) > 0 {
bf.WriteBytes(data)
} else {
bf.WriteUint32(0)
bf.WriteUint8(2)
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(0)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEnumerateRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRanking)
bf := byteframe.NewByteFrame()
state := s.server.erupeConfig.DevModeOptions.TournamentEvent
// Unk
// Unk
// Start?
// End?
midnight := TimeMidnight()
switch state {
case 1:
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(3 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(13 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(20 * 24 * time.Hour).Unix()))
case 2:
bf.WriteUint32(uint32(midnight.Add(-3 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(10 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(17 * 24 * time.Hour).Unix()))
case 3:
bf.WriteUint32(uint32(midnight.Add(-13 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Add(-10 * 24 * time.Hour).Unix()))
bf.WriteUint32(uint32(midnight.Unix()))
bf.WriteUint32(uint32(midnight.Add(7 * 24 * time.Hour).Unix()))
default:
bf.WriteBytes(make([]byte, 16))
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // TS Current Time
bf.WriteUint8(3)
bf.WriteBytes(make([]byte, 4))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
bf.WriteUint32(uint32(TimeAdjusted().Unix())) // TS Current Time
bf.WriteUint8(3)
ps.Uint8(bf, "", false)
bf.WriteUint16(0) // numEvents
bf.WriteUint8(0) // numCups
/*
struct event
uint32 eventID
uint16 unk
uint16 unk
uint32 unk
psUint8 name
struct cup
uint32 cupID
uint16 unk
uint16 unk
uint16 unk
psUint8 name
psUint16 desc
*/
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func cleanupFesta(s *Session) {
s.server.db.Exec("DELETE FROM events WHERE event_type='festa'")
s.server.db.Exec("DELETE FROM festa_registrations")
s.server.db.Exec("DELETE FROM festa_prizes_accepted")
s.server.db.Exec("UPDATE guild_characters SET souls=0")
}
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
timestamps := make([]uint32, 5)
midnight := TimeMidnight()
if debug && start <= 3 {
midnight := uint32(midnight.Unix())
switch start {
case 1:
timestamps[0] = midnight
timestamps[1] = timestamps[0] + 604800
timestamps[2] = timestamps[1] + 604800
timestamps[3] = timestamps[2] + 9000
timestamps[4] = timestamps[3] + 1240200
case 2:
timestamps[0] = midnight - 604800
timestamps[1] = midnight
timestamps[2] = timestamps[1] + 604800
timestamps[3] = timestamps[2] + 9000
timestamps[4] = timestamps[3] + 1240200
case 3:
timestamps[0] = midnight - 1209600
timestamps[1] = midnight - 604800
timestamps[2] = midnight
timestamps[3] = timestamps[2] + 9000
timestamps[4] = timestamps[3] + 1240200
}
return timestamps
}
if start == 0 || TimeAdjusted().Unix() > int64(start)+2977200 {
cleanupFesta(s)
// Generate a new festa, starting midnight tomorrow
start = uint32(midnight.Add(24 * time.Hour).Unix())
s.server.db.Exec("INSERT INTO events (event_type, start_time) VALUES ('festa', to_timestamp($1)::timestamp without time zone)", start)
}
timestamps[0] = start
timestamps[1] = timestamps[0] + 604800
timestamps[2] = timestamps[1] + 604800
timestamps[3] = timestamps[2] + 9000
timestamps[4] = timestamps[3] + 1240200
return timestamps
}
type FestaTrial struct {
ID uint32 `db:"id"`
Objective uint16 `db:"objective"`
GoalID uint32 `db:"goal_id"`
TimesReq uint16 `db:"times_req"`
Locale uint16 `db:"locale_req"`
Reward uint16 `db:"reward"`
Monopoly uint16
Unk uint16
}
type FestaReward struct {
Unk0 uint8
Unk1 uint8
ItemType uint16
Quantity uint16
ItemID uint16
Unk5 uint16
Unk6 uint16
Unk7 uint8
}
func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
bf := byteframe.NewByteFrame()
id, start := uint32(0xDEADBEEF), uint32(0)
rows, _ := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='festa'")
for rows.Next() {
rows.Scan(&id, &start)
}
var timestamps []uint32
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.FestaEvent >= 0 {
if s.server.erupeConfig.DevModeOptions.FestaEvent == 0 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
timestamps = generateFestaTimestamps(s, uint32(s.server.erupeConfig.DevModeOptions.FestaEvent), true)
} else {
timestamps = generateFestaTimestamps(s, start, false)
}
if timestamps[0] > uint32(TimeAdjusted().Unix()) {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
var blueSouls, redSouls uint32
s.server.db.QueryRow("SELECT SUM(gc.souls) FROM guild_characters gc INNER JOIN festa_registrations fr ON fr.guild_id = gc.guild_id WHERE fr.team = 'blue'").Scan(&blueSouls)
s.server.db.QueryRow("SELECT SUM(gc.souls) FROM guild_characters gc INNER JOIN festa_registrations fr ON fr.guild_id = gc.guild_id WHERE fr.team = 'red'").Scan(&redSouls)
bf.WriteUint32(id)
for _, timestamp := range timestamps {
bf.WriteUint32(timestamp)
}
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
bf.WriteUint8(4)
ps.Uint8(bf, "", false)
bf.WriteUint32(0)
bf.WriteUint32(blueSouls)
bf.WriteUint32(redSouls)
var trials []FestaTrial
var trial FestaTrial
rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials")
for rows.Next() {
err := rows.StructScan(&trial)
if err != nil {
continue
}
trials = append(trials, trial)
}
bf.WriteUint16(uint16(len(trials)))
for _, trial := range trials {
bf.WriteUint32(trial.ID)
bf.WriteUint16(trial.Objective)
bf.WriteUint32(trial.GoalID)
bf.WriteUint16(trial.TimesReq)
bf.WriteUint16(trial.Locale)
bf.WriteUint16(trial.Reward)
trial.Monopoly = 0xFFFF // NYI
bf.WriteUint16(trial.Monopoly)
bf.WriteUint16(trial.Unk)
}
// The Winner and Loser Armor IDs are missing
rewards := []FestaReward{
{1, 0, 7, 350, 1520, 0, 0, 0},
{1, 0, 7, 1000, 7011, 0, 0, 1},
{1, 0, 12, 1000, 0, 0, 0, 0},
{1, 0, 13, 0, 0, 0, 0, 0},
//{1, 0, 1, 0, 0, 0, 0, 0},
{2, 0, 7, 350, 1520, 0, 0, 0},
{2, 0, 7, 1000, 7011, 0, 0, 1},
{2, 0, 12, 1000, 0, 0, 0, 0},
{2, 0, 13, 0, 0, 0, 0, 0},
//{2, 0, 4, 0, 0, 0, 0, 0},
{3, 0, 7, 350, 1520, 0, 0, 0},
{3, 0, 7, 1000, 7011, 0, 0, 1},
{3, 0, 12, 1000, 0, 0, 0, 0},
{3, 0, 13, 0, 0, 0, 0, 0},
//{3, 0, 1, 0, 0, 0, 0, 0},
{4, 0, 7, 350, 1520, 0, 0, 0},
{4, 0, 7, 1000, 7011, 0, 0, 1},
{4, 0, 12, 1000, 0, 0, 0, 0},
{4, 0, 13, 0, 0, 0, 0, 0},
//{4, 0, 4, 0, 0, 0, 0, 0},
{5, 0, 7, 350, 1520, 0, 0, 0},
{5, 0, 7, 1000, 7011, 0, 0, 1},
{5, 0, 12, 1000, 0, 0, 0, 0},
{5, 0, 13, 0, 0, 0, 0, 0},
//{5, 0, 1, 0, 0, 0, 0, 0},
}
bf.WriteUint16(uint16(len(rewards)))
for _, reward := range rewards {
bf.WriteUint8(reward.Unk0)
bf.WriteUint8(reward.Unk1)
bf.WriteUint16(reward.ItemType)
bf.WriteUint16(reward.Quantity)
bf.WriteUint16(reward.ItemID)
bf.WriteUint16(reward.Unk5)
bf.WriteUint16(reward.Unk6)
bf.WriteUint8(reward.Unk7)
}
if _config.ErupeConfig.RealClientMode <= _config.G61 {
if s.server.erupeConfig.GameplayOptions.MaximumFP > 0xFFFF {
s.server.erupeConfig.GameplayOptions.MaximumFP = 0xFFFF
}
bf.WriteUint16(uint16(s.server.erupeConfig.GameplayOptions.MaximumFP))
} else {
bf.WriteUint32(s.server.erupeConfig.GameplayOptions.MaximumFP)
}
bf.WriteUint16(500)
categoryWinners := uint16(0) // NYI
bf.WriteUint16(categoryWinners)
for i := uint16(0); i < categoryWinners; i++ {
bf.WriteUint32(0) // Guild ID
bf.WriteUint16(i + 1) // Category ID
bf.WriteUint16(0) // Festa Team
ps.Uint8(bf, "", true) // Guild Name
}
dailyWinners := uint16(0) // NYI
bf.WriteUint16(dailyWinners)
for i := uint16(0); i < dailyWinners; i++ {
bf.WriteUint32(0) // Guild ID
bf.WriteUint16(i + 1) // Category ID
bf.WriteUint16(0) // Festa Team
ps.Uint8(bf, "", true) // Guild Name
}
// Unknown values
bf.WriteUint32(1)
bf.WriteUint32(5000)
bf.WriteUint32(2000)
bf.WriteUint32(1000)
bf.WriteUint32(100)
bf.WriteUint16(300)
bf.WriteUint16(200)
bf.WriteUint16(150)
bf.WriteUint16(100)
bf.WriteUint16(50)
ps.Uint16(bf, "", false)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// state festa (U)ser
func handleMsgMhfStateFestaU(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaU)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
applicant := false
if guild != nil {
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
}
if err != nil || guild == nil || applicant {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
var souls, exists uint32
s.server.db.QueryRow("SELECT souls FROM guild_characters WHERE character_id=$1", s.charID).Scan(&souls)
err = s.server.db.QueryRow("SELECT prize_id FROM festa_prizes_accepted WHERE prize_id=0 AND character_id=$1", s.charID).Scan(&exists)
bf := byteframe.NewByteFrame()
bf.WriteUint32(souls)
if err != nil {
bf.WriteBool(true)
bf.WriteBool(false)
} else {
bf.WriteBool(false)
bf.WriteBool(true)
}
bf.WriteUint16(0) // Unk
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
// state festa (G)uild
func handleMsgMhfStateFestaG(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfStateFestaG)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
applicant := false
if guild != nil {
applicant, _ = guild.HasApplicationForCharID(s, s.charID)
}
resp := byteframe.NewByteFrame()
if err != nil || guild == nil || applicant {
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0xFFFFFFFF)
resp.WriteUint32(0)
resp.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
return
}
resp.WriteUint32(guild.Souls)
resp.WriteUint32(1) // unk
resp.WriteUint32(1) // unk
resp.WriteUint32(1) // unk, rank?
resp.WriteUint32(1) // unk
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaMember)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil || guild == nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
members, err := GetGuildMembers(s, guild.ID, false)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(members)))
bf.WriteUint16(0) // Unk
sort.Slice(members, func(i, j int) bool {
return members[i].Souls > members[j].Souls
})
for _, member := range members {
bf.WriteUint32(member.CharID)
bf.WriteUint32(member.Souls)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfVoteFesta)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfEntryFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryFesta)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil || guild == nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
team := uint32(token.RNG().Intn(2))
switch team {
case 0:
s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'blue')", guild.ID)
case 1:
s.server.db.Exec("INSERT INTO festa_registrations VALUES ($1, 'red')", guild.ID)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(team)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfChargeFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeFesta)
s.server.db.Exec("UPDATE guild_characters SET souls=souls+$1 WHERE character_id=$2", pkt.Souls, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFesta)
s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES (0, $1)", s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaPersonalPrize)
s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES ($1, $2)", pkt.PrizeID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireFestaIntermediatePrize)
s.server.db.Exec("INSERT INTO public.festa_prizes_accepted VALUES ($1, $2)", pkt.PrizeID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
type Prize struct {
ID uint32 `db:"id"`
Tier uint32 `db:"tier"`
SoulsReq uint32 `db:"souls_req"`
ItemID uint32 `db:"item_id"`
NumItem uint32 `db:"num_item"`
Claimed int `db:"claimed"`
}
func handleMsgMhfEnumerateFestaPersonalPrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaPersonalPrize)
rows, _ := s.server.db.Queryx(`SELECT id, tier, souls_req, item_id, num_item, (SELECT count(*) FROM festa_prizes_accepted fpa WHERE fp.id = fpa.prize_id AND fpa.character_id = $1) AS claimed FROM festa_prizes fp WHERE type='personal'`, s.charID)
var count uint32
prizeData := byteframe.NewByteFrame()
for rows.Next() {
prize := &Prize{}
err := rows.StructScan(&prize)
if err != nil {
continue
}
count++
prizeData.WriteUint32(prize.ID)
prizeData.WriteUint32(prize.Tier)
prizeData.WriteUint32(prize.SoulsReq)
prizeData.WriteUint32(7) // Unk
prizeData.WriteUint32(prize.ItemID)
prizeData.WriteUint32(prize.NumItem)
prizeData.WriteBool(prize.Claimed > 0)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(count)
bf.WriteBytes(prizeData.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEnumerateFestaIntermediatePrize(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateFestaIntermediatePrize)
rows, _ := s.server.db.Queryx(`SELECT id, tier, souls_req, item_id, num_item, (SELECT count(*) FROM festa_prizes_accepted fpa WHERE fp.id = fpa.prize_id AND fpa.character_id = $1) AS claimed FROM festa_prizes fp WHERE type='guild'`, s.charID)
var count uint32
prizeData := byteframe.NewByteFrame()
for rows.Next() {
prize := &Prize{}
err := rows.StructScan(&prize)
if err != nil {
continue
}
count++
prizeData.WriteUint32(prize.ID)
prizeData.WriteUint32(prize.Tier)
prizeData.WriteUint32(prize.SoulsReq)
prizeData.WriteUint32(7) // Unk
prizeData.WriteUint32(prize.ItemID)
prizeData.WriteUint32(prize.NumItem)
prizeData.WriteBool(prize.Claimed > 0)
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(count)
bf.WriteBytes(prizeData.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,95 @@
package channelserver
import (
"time"
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
type GuildAdventure struct {
ID uint32 `db:"id"`
Destination uint32 `db:"destination"`
Charge uint32 `db:"charge"`
Depart uint32 `db:"depart"`
Return uint32 `db:"return"`
CollectedBy string `db:"collected_by"`
}
func handleMsgMhfLoadGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadGuildAdventure)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
data, err := s.server.db.Queryx("SELECT id, destination, charge, depart, return, collected_by FROM guild_adventures WHERE guild_id = $1", guild.ID)
if err != nil {
s.logger.Error("Failed to get guild adventures from db", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
temp := byteframe.NewByteFrame()
count := 0
for data.Next() {
count++
adventureData := &GuildAdventure{}
err = data.StructScan(&adventureData)
if err != nil {
continue
}
temp.WriteUint32(adventureData.ID)
temp.WriteUint32(adventureData.Destination)
temp.WriteUint32(adventureData.Charge)
temp.WriteUint32(adventureData.Depart)
temp.WriteUint32(adventureData.Return)
temp.WriteBool(stringsupport.CSVContains(adventureData.CollectedBy, int(s.charID)))
}
bf := byteframe.NewByteFrame()
bf.WriteUint8(uint8(count))
bf.WriteBytes(temp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfRegistGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventure)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, depart, return) VALUES ($1, $2, $3, $4)", guild.ID, pkt.Destination, TimeAdjusted().Unix(), TimeAdjusted().Add(6*time.Hour).Unix())
if err != nil {
s.logger.Error("Failed to register guild adventure", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireGuildAdventure)
var collectedBy string
err := s.server.db.QueryRow("SELECT collected_by FROM guild_adventures WHERE id = $1", pkt.ID).Scan(&collectedBy)
if err != nil {
s.logger.Error("Error parsing adventure collected by", zap.Error(err))
} else {
collectedBy = stringsupport.CSVAdd(collectedBy, int(s.charID))
_, err := s.server.db.Exec("UPDATE guild_adventures SET collected_by = $1 WHERE id = $2", collectedBy, pkt.ID)
if err != nil {
s.logger.Error("Failed to collect adventure in db", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfChargeGuildAdventure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfChargeGuildAdventure)
_, err := s.server.db.Exec("UPDATE guild_adventures SET charge = charge + $1 WHERE id = $2", pkt.Amount, pkt.ID)
if err != nil {
s.logger.Error("Failed to charge guild adventure", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfRegistGuildAdventureDiva(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegistGuildAdventureDiva)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
_, err := s.server.db.Exec("INSERT INTO guild_adventures (guild_id, destination, charge, depart, return) VALUES ($1, $2, $3, $4, $5)", guild.ID, pkt.Destination, pkt.Charge, TimeAdjusted().Unix(), TimeAdjusted().Add(1*time.Hour).Unix())
if err != nil {
s.logger.Error("Failed to register guild adventure", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,234 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"fmt"
"time"
"erupe-ce/network/mhfpacket"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
const allianceInfoSelectQuery = `
SELECT
ga.id,
ga.name,
created_at,
parent_id,
CASE
WHEN sub1_id IS NULL THEN 0
ELSE sub1_id
END,
CASE
WHEN sub2_id IS NULL THEN 0
ELSE sub2_id
END
FROM guild_alliances ga
`
type GuildAlliance struct {
ID uint32 `db:"id"`
Name string `db:"name"`
CreatedAt time.Time `db:"created_at"`
TotalMembers uint16
ParentGuildID uint32 `db:"parent_id"`
SubGuild1ID uint32 `db:"sub1_id"`
SubGuild2ID uint32 `db:"sub2_id"`
ParentGuild Guild
SubGuild1 Guild
SubGuild2 Guild
}
func GetAllianceData(s *Session, AllianceID uint32) (*GuildAlliance, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
WHERE ga.id = $1
`, allianceInfoSelectQuery), AllianceID)
if err != nil {
s.logger.Error("Failed to retrieve alliance data from database", zap.Error(err))
return nil, err
}
defer rows.Close()
hasRow := rows.Next()
if !hasRow {
return nil, nil
}
return buildAllianceObjectFromDbResult(rows, err, s)
}
func buildAllianceObjectFromDbResult(result *sqlx.Rows, err error, s *Session) (*GuildAlliance, error) {
alliance := &GuildAlliance{}
err = result.StructScan(alliance)
if err != nil {
s.logger.Error("failed to retrieve alliance from database", zap.Error(err))
return nil, err
}
parentGuild, err := GetGuildInfoByID(s, alliance.ParentGuildID)
if err != nil {
s.logger.Error("Failed to get parent guild info", zap.Error(err))
return nil, err
} else {
alliance.ParentGuild = *parentGuild
alliance.TotalMembers += parentGuild.MemberCount
}
if alliance.SubGuild1ID > 0 {
subGuild1, err := GetGuildInfoByID(s, alliance.SubGuild1ID)
if err != nil {
s.logger.Error("Failed to get sub guild 1 info", zap.Error(err))
return nil, err
} else {
alliance.SubGuild1 = *subGuild1
alliance.TotalMembers += subGuild1.MemberCount
}
}
if alliance.SubGuild2ID > 0 {
subGuild2, err := GetGuildInfoByID(s, alliance.SubGuild2ID)
if err != nil {
s.logger.Error("Failed to get sub guild 2 info", zap.Error(err))
return nil, err
} else {
alliance.SubGuild2 = *subGuild2
alliance.TotalMembers += subGuild2.MemberCount
}
}
return alliance, nil
}
func handleMsgMhfCreateJoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCreateJoint)
_, err := s.server.db.Exec("INSERT INTO guild_alliances (name, parent_id) VALUES ($1, $2)", pkt.Name, pkt.GuildID)
if err != nil {
s.logger.Error("Failed to create guild alliance in db", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x01, 0x01, 0x01, 0x01})
}
func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateJoint)
guild, err := GetGuildInfoByID(s, pkt.GuildID)
if err != nil {
s.logger.Error("Failed to get guild info", zap.Error(err))
}
alliance, err := GetAllianceData(s, pkt.AllianceID)
if err != nil {
s.logger.Error("Failed to get alliance info", zap.Error(err))
}
switch pkt.Action {
case mhfpacket.OPERATE_JOINT_DISBAND:
if guild.LeaderCharID == s.charID && alliance.ParentGuildID == guild.ID {
_, err = s.server.db.Exec("DELETE FROM guild_alliances WHERE id=$1", alliance.ID)
if err != nil {
s.logger.Error("Failed to disband alliance", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
s.logger.Warn(
"Non-owner of alliance attempted disband",
zap.Uint32("CharID", s.charID),
zap.Uint32("AllyID", alliance.ID),
)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
case mhfpacket.OPERATE_JOINT_LEAVE:
if guild.LeaderCharID == s.charID {
if guild.ID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 {
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID)
} else if guild.ID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 {
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, alliance.ID)
} else {
s.server.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, alliance.ID)
}
// TODO: Handle deleting Alliance applications
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
s.logger.Warn(
"Non-owner of guild attempted alliance leave",
zap.Uint32("CharID", s.charID),
)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
case mhfpacket.OPERATE_JOINT_KICK:
if alliance.ParentGuild.LeaderCharID == s.charID {
_ = pkt.UnkData.ReadUint8()
kickedGuildID := pkt.UnkData.ReadUint32()
if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID > 0 {
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = sub2_id, sub2_id = NULL WHERE id = $1`, alliance.ID)
} else if kickedGuildID == alliance.SubGuild1ID && alliance.SubGuild2ID == 0 {
s.server.db.Exec(`UPDATE guild_alliances SET sub1_id = NULL WHERE id = $1`, alliance.ID)
} else {
s.server.db.Exec(`UPDATE guild_alliances SET sub2_id = NULL WHERE id = $1`, alliance.ID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
s.logger.Warn(
"Non-owner of alliance attempted kick",
zap.Uint32("CharID", s.charID),
zap.Uint32("AllyID", alliance.ID),
)
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
default:
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
panic(fmt.Sprintf("Unhandled operate joint action '%d'", pkt.Action))
}
}
func handleMsgMhfInfoJoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoJoint)
bf := byteframe.NewByteFrame()
alliance, err := GetAllianceData(s, pkt.AllianceID)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} else {
bf.WriteUint32(alliance.ID)
bf.WriteUint32(uint32(alliance.CreatedAt.Unix()))
bf.WriteUint16(alliance.TotalMembers)
bf.WriteUint16(0x0000) // Unk
ps.Uint16(bf, alliance.Name, true)
if alliance.SubGuild1ID > 0 {
if alliance.SubGuild2ID > 0 {
bf.WriteUint8(3)
} else {
bf.WriteUint8(2)
}
} else {
bf.WriteUint8(1)
}
bf.WriteUint32(alliance.ParentGuildID)
bf.WriteUint32(alliance.ParentGuild.LeaderCharID)
bf.WriteUint16(alliance.ParentGuild.Rank())
bf.WriteUint16(alliance.ParentGuild.MemberCount)
ps.Uint16(bf, alliance.ParentGuild.Name, true)
ps.Uint16(bf, alliance.ParentGuild.LeaderName, true)
if alliance.SubGuild1ID > 0 {
bf.WriteUint32(alliance.SubGuild1ID)
bf.WriteUint32(alliance.SubGuild1.LeaderCharID)
bf.WriteUint16(alliance.SubGuild1.Rank())
bf.WriteUint16(alliance.SubGuild1.MemberCount)
ps.Uint16(bf, alliance.SubGuild1.Name, true)
ps.Uint16(bf, alliance.SubGuild1.LeaderName, true)
}
if alliance.SubGuild2ID > 0 {
bf.WriteUint32(alliance.SubGuild2ID)
bf.WriteUint32(alliance.SubGuild2.LeaderCharID)
bf.WriteUint16(alliance.SubGuild2.Rank())
bf.WriteUint16(alliance.SubGuild2.MemberCount)
ps.Uint16(bf, alliance.SubGuild2.Name, true)
ps.Uint16(bf, alliance.SubGuild2.LeaderName, true)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
}

View File

@@ -0,0 +1,153 @@
package channelserver
import (
"fmt"
"time"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
type GuildMember struct {
GuildID uint32 `db:"guild_id"`
CharID uint32 `db:"character_id"`
JoinedAt *time.Time `db:"joined_at"`
Souls uint32 `db:"souls"`
RPToday uint16 `db:"rp_today"`
RPYesterday uint16 `db:"rp_yesterday"`
Name string `db:"name"`
IsApplicant bool `db:"is_applicant"`
OrderIndex uint16 `db:"order_index"`
LastLogin uint32 `db:"last_login"`
Recruiter bool `db:"recruiter"`
AvoidLeadership bool `db:"avoid_leadership"`
IsLeader bool `db:"is_leader"`
HRP uint16 `db:"hrp"`
GR uint16 `db:"gr"`
WeaponID uint16 `db:"weapon_id"`
WeaponType uint8 `db:"weapon_type"`
}
func (gm *GuildMember) CanRecruit() bool {
if gm.Recruiter {
return true
}
if gm.OrderIndex <= 3 {
return true
}
if gm.IsLeader {
return true
}
return false
}
func (gm *GuildMember) IsSubLeader() bool {
return gm.OrderIndex <= 3
}
func (gm *GuildMember) Save(s *Session) error {
_, err := s.server.db.Exec("UPDATE guild_characters SET avoid_leadership=$1, order_index=$2 WHERE character_id=$3", gm.AvoidLeadership, gm.OrderIndex, gm.CharID)
if err != nil {
s.logger.Error(
"failed to update guild member data",
zap.Error(err),
zap.Uint32("charID", gm.CharID),
zap.Uint32("guildID", gm.GuildID),
)
return err
}
return nil
}
const guildMembersSelectSQL = `
SELECT
g.id as guild_id,
joined_at,
coalesce(souls, 0) as souls,
COALESCE(rp_today, 0) AS rp_today,
COALESCE(rp_yesterday, 0) AS rp_yesterday,
c.name,
character.character_id,
coalesce(gc.order_index, 0) as order_index,
c.last_login,
coalesce(gc.recruiter, false) as recruiter,
coalesce(gc.avoid_leadership, false) as avoid_leadership,
c.hrp,
c.gr,
c.weapon_id,
c.weapon_type,
character.is_applicant,
CASE WHEN g.leader_id = c.id THEN 1 ELSE 0 END as is_leader
FROM (
SELECT character_id, true as is_applicant, guild_id
FROM guild_applications ga
WHERE ga.application_type = 'applied'
UNION
SELECT character_id, false as is_applicant, guild_id
FROM guild_characters gc
) character
JOIN characters c on character.character_id = c.id
LEFT JOIN guild_characters gc ON gc.character_id = character.character_id
JOIN guilds g ON g.id = character.guild_id
`
func GetGuildMembers(s *Session, guildID uint32, applicants bool) ([]*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf(`
%s
WHERE character.guild_id = $1 AND is_applicant = $2
`, guildMembersSelectSQL), guildID, applicants)
if err != nil {
s.logger.Error("failed to retrieve membership data for guild", zap.Error(err), zap.Uint32("guildID", guildID))
return nil, err
}
defer rows.Close()
members := make([]*GuildMember, 0)
for rows.Next() {
member, err := buildGuildMemberObjectFromDBResult(rows, err, s)
if err != nil {
return nil, err
}
members = append(members, member)
}
return members, nil
}
func GetCharacterGuildData(s *Session, charID uint32) (*GuildMember, error) {
rows, err := s.server.db.Queryx(fmt.Sprintf("%s WHERE character.character_id=$1", guildMembersSelectSQL), charID)
if err != nil {
s.logger.Error(fmt.Sprintf("failed to retrieve membership data for character '%d'", charID))
return nil, err
}
defer rows.Close()
hasRow := rows.Next()
if !hasRow {
return nil, nil
}
return buildGuildMemberObjectFromDBResult(rows, err, s)
}
func buildGuildMemberObjectFromDBResult(rows *sqlx.Rows, err error, s *Session) (*GuildMember, error) {
memberData := &GuildMember{}
err = rows.StructScan(&memberData)
if err != nil {
s.logger.Error("failed to retrieve guild data from database", zap.Error(err))
return nil, err
}
return memberData, nil
}

View File

@@ -0,0 +1,310 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"fmt"
"go.uber.org/zap"
"io"
)
func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostGuildScout)
actorCharGuildData, err := GetCharacterGuildData(s, s.charID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
if actorCharGuildData == nil || !actorCharGuildData.CanRecruit() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
guildInfo, err := GetGuildInfoByID(s, actorCharGuildData.GuildID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
hasApplication, err := guildInfo.HasApplicationForCharID(s, pkt.CharID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
if hasApplication {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x04})
return
}
transaction, err := s.server.db.Begin()
if err != nil {
panic(err)
}
err = guildInfo.CreateApplication(s, pkt.CharID, GuildApplicationTypeInvited, transaction)
if err != nil {
rollbackTransaction(s, transaction)
doAckBufFail(s, pkt.AckHandle, nil)
panic(err)
}
mail := &Mail{
SenderID: s.charID,
RecipientID: pkt.CharID,
Subject: s.server.dict["guildInviteName"],
Body: fmt.Sprintf(
s.server.dict["guildInvite"],
guildInfo.Name,
),
IsGuildInvite: true,
}
err = mail.Send(s, transaction)
if err != nil {
rollbackTransaction(s, transaction)
doAckBufFail(s, pkt.AckHandle, nil)
return
}
err = transaction.Commit()
if err != nil {
doAckBufFail(s, pkt.AckHandle, nil)
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfCancelGuildScout)
guildCharData, err := GetCharacterGuildData(s, s.charID)
if err != nil {
panic(err)
}
if guildCharData == nil || !guildCharData.CanRecruit() {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
guild, err := GetGuildInfoByID(s, guildCharData.GuildID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
err = guild.CancelInvitation(s, pkt.InvitationID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAnswerGuildScout)
bf := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
if err != nil {
panic(err)
}
app, err := guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
if app == nil || err != nil {
s.logger.Warn(
"Guild invite missing, deleted?",
zap.Error(err),
zap.Uint32("guildID", guild.ID),
zap.Uint32("charID", s.charID),
)
bf.WriteUint32(7)
bf.WriteUint32(guild.ID)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
var mail []Mail
if pkt.Answer {
err = guild.AcceptApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
Subject: s.server.dict["guildInviteSuccessName"],
Body: fmt.Sprintf(s.server.dict["guildInviteSuccess"], guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
Subject: s.server.dict["guildInviteAcceptedName"],
Body: fmt.Sprintf(s.server.dict["guildInviteAccepted"], guild.Name),
IsSystemMessage: true,
})
} else {
err = guild.RejectApplication(s, s.charID)
mail = append(mail, Mail{
RecipientID: s.charID,
Subject: s.server.dict["guildInviteRejectName"],
Body: fmt.Sprintf(s.server.dict["guildInviteReject"], guild.Name),
IsSystemMessage: true,
})
mail = append(mail, Mail{
SenderID: s.charID,
RecipientID: pkt.LeaderID,
Subject: s.server.dict["guildInviteDeclined"],
Body: fmt.Sprintf(s.server.dict["guildInviteDeclined"], guild.Name),
IsSystemMessage: true,
})
}
if err != nil {
bf.WriteUint32(7)
bf.WriteUint32(guild.ID)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
} else {
bf.WriteUint32(0)
bf.WriteUint32(guild.ID)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
for _, m := range mail {
m.Send(s, nil)
}
}
}
func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildScoutList)
guildInfo, err := GetGuildInfoByCharacterId(s, s.charID)
if guildInfo == nil && s.prevGuildID == 0 {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else {
guildInfo, err = GetGuildInfoByID(s, s.prevGuildID)
if guildInfo == nil || err != nil {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
}
rows, err := s.server.db.Queryx(`
SELECT c.id, c.name, c.hrp, c.gr, ga.actor_id
FROM guild_applications ga
JOIN characters c ON c.id = ga.character_id
WHERE ga.guild_id = $1 AND ga.application_type = 'invited'
`, guildInfo.ID)
if err != nil {
s.logger.Error("failed to retrieve scouted characters", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
defer rows.Close()
bf := byteframe.NewByteFrame()
bf.SetBE()
// Result count, we will overwrite this later
bf.WriteUint32(0x00)
count := uint32(0)
for rows.Next() {
var charName string
var charID, actorID uint32
var hrp, gr uint16
err = rows.Scan(&charID, &charName, &hrp, &gr, &actorID)
if err != nil {
doAckSimpleFail(s, pkt.AckHandle, nil)
continue
}
// This seems to be used as a unique ID for the invitation sent
// we can just use the charID and then filter on guild_id+charID when performing operations
// this might be a problem later with mails sent referencing IDs but we'll see.
bf.WriteUint32(charID)
bf.WriteUint32(actorID)
bf.WriteUint32(charID)
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
bf.WriteUint16(hrp) // HR?
bf.WriteUint16(gr) // GR?
bf.WriteBytes(stringsupport.PaddedString(charName, 32, true))
count++
}
_, err = bf.Seek(0, io.SeekStart)
if err != nil {
panic(err)
}
bf.WriteUint32(count)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRejectGuildScout)
row := s.server.db.QueryRow("SELECT restrict_guild_scout FROM characters WHERE id=$1", s.charID)
var currentStatus bool
err := row.Scan(&currentStatus)
if err != nil {
s.logger.Error(
"failed to retrieve character guild scout status",
zap.Error(err),
zap.Uint32("charID", s.charID),
)
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
response := uint8(0x00)
if currentStatus {
response = 0x01
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, response})
}
func handleMsgMhfSetRejectGuildScout(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSetRejectGuildScout)
_, err := s.server.db.Exec("UPDATE characters SET restrict_guild_scout=$1 WHERE id=$2", pkt.Reject, s.charID)
if err != nil {
s.logger.Error(
"failed to update character guild scout status",
zap.Error(err),
zap.Uint32("charID", s.charID),
)
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
doAckSimpleSucceed(s, pkt.AckHandle, nil)
}

View File

@@ -0,0 +1,181 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
)
type TreasureHunt struct {
HuntID uint32 `db:"id"`
HostID uint32 `db:"host_id"`
Destination uint32 `db:"destination"`
Level uint32 `db:"level"`
Return uint32 `db:"return"`
Acquired bool `db:"acquired"`
Claimed bool `db:"claimed"`
Hunters string `db:"hunters"`
Treasure string `db:"treasure"`
HuntData []byte `db:"hunt_data"`
}
func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
}
bf := byteframe.NewByteFrame()
hunts := 0
rows, _ := s.server.db.Queryx("SELECT id, host_id, destination, level, return, acquired, claimed, hunters, treasure, hunt_data FROM guild_hunts WHERE guild_id=$1 AND $2 < return+604800", guild.ID, TimeAdjusted().Unix())
for rows.Next() {
hunt := &TreasureHunt{}
err = rows.StructScan(&hunt)
// Remove self from other hunter count
hunt.Hunters = stringsupport.CSVRemove(hunt.Hunters, int(s.charID))
if err != nil {
panic(err)
}
if pkt.MaxHunts == 1 {
if hunt.HostID != s.charID || hunt.Acquired {
continue
}
hunts++
bf.WriteUint32(hunt.HuntID)
bf.WriteUint32(hunt.Destination)
bf.WriteUint32(hunt.Level)
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
bf.WriteUint32(hunt.Return)
bf.WriteBool(false)
bf.WriteBool(false)
bf.WriteBytes(hunt.HuntData)
break
} else if pkt.MaxHunts == 30 && hunt.Acquired && hunt.Level == 2 {
if hunts == 30 {
break
}
hunts++
bf.WriteUint32(hunt.HuntID)
bf.WriteUint32(hunt.Destination)
bf.WriteUint32(hunt.Level)
bf.WriteUint32(uint32(stringsupport.CSVLength(hunt.Hunters)))
bf.WriteUint32(hunt.Return)
bf.WriteBool(hunt.Claimed)
bf.WriteBool(stringsupport.CSVContains(hunt.Treasure, int(s.charID)))
bf.WriteBytes(hunt.HuntData)
}
}
resp := byteframe.NewByteFrame()
resp.WriteUint16(uint16(hunts))
resp.WriteUint16(uint16(hunts))
resp.WriteBytes(bf.Data())
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegistGuildTresure)
bf := byteframe.NewByteFrameFromBytes(pkt.Data)
huntData := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
}
guildCats := getGuildAirouList(s)
destination := bf.ReadUint32()
level := bf.ReadUint32()
huntData.WriteUint32(s.charID)
huntData.WriteBytes(stringsupport.PaddedString(s.Name, 18, true))
catsUsed := ""
for i := 0; i < 5; i++ {
catID := bf.ReadUint32()
huntData.WriteUint32(catID)
if catID > 0 {
catsUsed = stringsupport.CSVAdd(catsUsed, int(catID))
for _, cat := range guildCats {
if cat.CatID == catID {
huntData.WriteBytes(cat.CatName)
break
}
}
huntData.WriteBytes(bf.ReadBytes(9))
}
}
_, err = s.server.db.Exec("INSERT INTO guild_hunts (guild_id, host_id, destination, level, return, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6, $7)",
guild.ID, s.charID, destination, level, TimeAdjusted().Unix(), huntData.Data(), catsUsed)
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure)
_, err := s.server.db.Exec("UPDATE guild_hunts SET acquired=true WHERE id=$1", pkt.HuntID)
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func treasureHuntUnregister(s *Session) {
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil || guild == nil {
return
}
var huntID int
var hunters string
rows, err := s.server.db.Queryx("SELECT id, hunters FROM guild_hunts WHERE guild_id=$1", guild.ID)
if err != nil {
return
}
for rows.Next() {
rows.Scan(&huntID, &hunters)
hunters = stringsupport.CSVRemove(hunters, int(s.charID))
s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", hunters, huntID)
}
}
func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateGuildTresureReport)
var csv string
if pkt.State == 0 { // Report registration
// Unregister from all other hunts
treasureHuntUnregister(s)
if pkt.HuntID != 0 {
// Register to selected hunt
err := s.server.db.QueryRow("SELECT hunters FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
if err != nil {
panic(err)
}
csv = stringsupport.CSVAdd(csv, int(s.charID))
_, err = s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID)
if err != nil {
panic(err)
}
}
} else if pkt.State == 1 { // Collected by hunter
s.server.db.Exec("UPDATE guild_hunts SET hunters='', claimed=true WHERE id=$1", pkt.HuntID)
} else if pkt.State == 2 { // Claim treasure
err := s.server.db.QueryRow("SELECT treasure FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
if err != nil {
panic(err)
}
csv = stringsupport.CSVAdd(csv, int(s.charID))
_, err = s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID)
if err != nil {
panic(err)
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildTresureSouvenir)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
}
func handleMsgMhfAcquireGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresureSouvenir)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,554 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"fmt"
"go.uber.org/zap"
"io"
"time"
)
const warehouseNamesQuery = `
SELECT
COALESCE(item0name, ''),
COALESCE(item1name, ''),
COALESCE(item2name, ''),
COALESCE(item3name, ''),
COALESCE(item4name, ''),
COALESCE(item5name, ''),
COALESCE(item6name, ''),
COALESCE(item7name, ''),
COALESCE(item8name, ''),
COALESCE(item9name, ''),
COALESCE(equip0name, ''),
COALESCE(equip1name, ''),
COALESCE(equip2name, ''),
COALESCE(equip3name, ''),
COALESCE(equip4name, ''),
COALESCE(equip5name, ''),
COALESCE(equip6name, ''),
COALESCE(equip7name, ''),
COALESCE(equip8name, ''),
COALESCE(equip9name, '')
FROM warehouse
`
func handleMsgMhfUpdateInterior(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateInterior)
s.server.db.Exec(`UPDATE user_binary SET house_furniture=$1 WHERE id=$2`, pkt.InteriorData, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
type HouseData struct {
CharID uint32 `db:"id"`
HRP uint16 `db:"hrp"`
GR uint16 `db:"gr"`
Name string `db:"name"`
HouseState uint8 `db:"house_state"`
HousePassword string `db:"house_password"`
}
func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateHouse)
bf := byteframe.NewByteFrame()
bf.WriteUint16(0)
var houses []HouseData
houseQuery := `SELECT c.id, hrp, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE c.id=$1`
switch pkt.Method {
case 1:
var friendsList string
s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&friendsList)
cids := stringsupport.CSVElems(friendsList)
for _, cid := range cids {
house := HouseData{}
row := s.server.db.QueryRowx(houseQuery, cid)
err := row.StructScan(&house)
if err == nil {
houses = append(houses, house)
}
}
case 2:
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil || guild == nil {
break
}
guildMembers, err := GetGuildMembers(s, guild.ID, false)
if err != nil {
break
}
for _, member := range guildMembers {
house := HouseData{}
row := s.server.db.QueryRowx(houseQuery, member.CharID)
err = row.StructScan(&house)
if err == nil {
houses = append(houses, house)
}
}
case 3:
houseQuery = `SELECT c.id, hrp, gr, name, COALESCE(ub.house_state, 2) as house_state, COALESCE(ub.house_password, '') as house_password
FROM characters c LEFT JOIN user_binary ub ON ub.id = c.id WHERE name ILIKE $1`
house := HouseData{}
rows, _ := s.server.db.Queryx(houseQuery, fmt.Sprintf(`%%%s%%`, pkt.Name))
for rows.Next() {
err := rows.StructScan(&house)
if err == nil {
houses = append(houses, house)
}
}
case 4:
house := HouseData{}
row := s.server.db.QueryRowx(houseQuery, pkt.CharID)
err := row.StructScan(&house)
if err == nil {
houses = append(houses, house)
}
case 5: // Recent visitors
break
}
for _, house := range houses {
bf.WriteUint32(house.CharID)
bf.WriteUint8(house.HouseState)
if len(house.HousePassword) > 0 {
bf.WriteUint8(3)
} else {
bf.WriteUint8(0)
}
bf.WriteUint16(house.HRP)
bf.WriteUint16(house.GR)
ps.Uint8(bf, house.Name, true)
}
bf.Seek(0, 0)
bf.WriteUint16(uint16(len(houses)))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUpdateHouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateHouse)
// 01 = closed
// 02 = open anyone
// 03 = open friends
// 04 = open guild
// 05 = open friends+guild
s.server.db.Exec(`UPDATE user_binary SET house_state=$1, house_password=$2 WHERE id=$3`, pkt.State, pkt.Password, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfLoadHouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadHouse)
bf := byteframe.NewByteFrame()
var state uint8
var password string
s.server.db.QueryRow(`SELECT COALESCE(house_state, 2) as house_state, COALESCE(house_password, '') as house_password FROM user_binary WHERE id=$1
`, pkt.CharID).Scan(&state, &password)
if pkt.Destination != 9 && len(pkt.Password) > 0 && pkt.CheckPass {
if pkt.Password != password {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
}
if pkt.Destination != 9 && state > 2 {
allowed := false
// Friends list verification
if state == 3 || state == 5 {
var friendsList string
s.server.db.QueryRow(`SELECT friends FROM characters WHERE id=$1`, pkt.CharID).Scan(&friendsList)
cids := stringsupport.CSVElems(friendsList)
for _, cid := range cids {
if uint32(cid) == s.charID {
allowed = true
break
}
}
}
// Guild verification
if state > 3 {
ownGuild, err := GetGuildInfoByCharacterId(s, s.charID)
isApplicant, _ := ownGuild.HasApplicationForCharID(s, s.charID)
if err == nil && ownGuild != nil {
othersGuild, err := GetGuildInfoByCharacterId(s, pkt.CharID)
if err == nil && othersGuild != nil {
if othersGuild.ID == ownGuild.ID && !isApplicant {
allowed = true
}
}
}
}
if !allowed {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
}
var houseTier, houseData, houseFurniture, bookshelf, gallery, tore, garden []byte
s.server.db.QueryRow(`SELECT house_tier, house_data, house_furniture, bookshelf, gallery, tore, garden FROM user_binary WHERE id=$1
`, pkt.CharID).Scan(&houseTier, &houseData, &houseFurniture, &bookshelf, &gallery, &tore, &garden)
if houseFurniture == nil {
houseFurniture = make([]byte, 20)
}
switch pkt.Destination {
case 3: // Others house
bf.WriteBytes(houseTier)
bf.WriteBytes(houseData)
bf.WriteBytes(make([]byte, 19)) // Padding?
bf.WriteBytes(houseFurniture)
case 4: // Bookshelf
bf.WriteBytes(bookshelf)
case 5: // Gallery
bf.WriteBytes(gallery)
case 8: // Tore
bf.WriteBytes(tore)
case 9: // Own house
bf.WriteBytes(houseFurniture)
case 10: // Garden
bf.WriteBytes(garden)
c, d := getGookData(s, pkt.CharID)
bf.WriteUint16(c)
bf.WriteUint16(0)
bf.WriteBytes(d)
}
if len(bf.Data()) == 0 {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} else {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
}
func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetMyhouseInfo)
var data []byte
s.server.db.QueryRow(`SELECT mission FROM user_binary WHERE id=$1`, s.charID).Scan(&data)
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 9))
}
}
func handleMsgMhfUpdateMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateMyhouseInfo)
s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Unk0, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadDecoMyset)
var data []byte
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Error("Failed to load decomyset", zap.Error(err))
}
if len(data) == 0 {
if s.server.erupeConfig.RealClientMode < _config.G10 {
data = []byte{0x00, 0x00}
}
data = []byte{0x01, 0x00}
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSaveDecoMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveDecoMyset)
// https://gist.github.com/Andoryuuta/9c524da7285e4b5ca7e52e0fc1ca1daf
var loadData []byte
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[1:]) // skip first unk byte
err := s.server.db.QueryRow("SELECT decomyset FROM characters WHERE id = $1", s.charID).Scan(&loadData)
if err != nil {
s.logger.Error("Failed to load decomyset", zap.Error(err))
} else {
numSets := bf.ReadUint8() // sets being written
// empty save
if len(loadData) == 0 {
loadData = []byte{0x01, 0x00}
}
savedSets := loadData[1] // existing saved sets
// no sets, new slice with just first 2 bytes for appends later
if savedSets == 0 {
loadData = []byte{0x01, 0x00}
}
for i := 0; i < int(numSets); i++ {
writeSet := bf.ReadUint16()
dataChunk := bf.ReadBytes(76)
setBytes := append([]byte{uint8(writeSet >> 8), uint8(writeSet & 0xff)}, dataChunk...)
for x := 0; true; x++ {
if x == int(savedSets) {
// appending set
if loadData[len(loadData)-1] == 0x10 {
// sanity check for if there was a messy manual import
loadData = append(loadData[:len(loadData)-2], setBytes...)
} else {
loadData = append(loadData, setBytes...)
}
savedSets++
break
}
currentSet := loadData[3+(x*78)]
if int(currentSet) == int(writeSet) {
// replacing a set
loadData = append(loadData[:2+(x*78)], append(setBytes, loadData[2+((x+1)*78):]...)...)
break
} else if int(currentSet) > int(writeSet) {
// inserting before current set
loadData = append(loadData[:2+((x)*78)], append(setBytes, loadData[2+((x)*78):]...)...)
savedSets++
break
}
}
loadData[1] = savedSets // update set count
}
dumpSaveData(s, loadData, "decomyset")
_, err := s.server.db.Exec("UPDATE characters SET decomyset=$1 WHERE id=$2", loadData, s.charID)
if err != nil {
s.logger.Error("Failed to save decomyset", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
type Title struct {
ID uint16 `db:"id"`
Acquired time.Time `db:"unlocked_at"`
Updated time.Time `db:"updated_at"`
}
func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateTitle)
var count uint16
bf := byteframe.NewByteFrame()
bf.WriteUint16(0)
bf.WriteUint16(0) // Unk
rows, err := s.server.db.Queryx("SELECT id, unlocked_at, updated_at FROM titles WHERE char_id=$1", s.charID)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
for rows.Next() {
title := &Title{}
err = rows.StructScan(&title)
if err != nil {
continue
}
count++
bf.WriteUint16(title.ID)
bf.WriteUint16(0) // Unk
bf.WriteUint32(uint32(title.Acquired.Unix()))
bf.WriteUint32(uint32(title.Updated.Unix()))
}
bf.Seek(0, io.SeekStart)
bf.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfAcquireTitle(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireTitle)
var exists int
err := s.server.db.QueryRow("SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2", pkt.TitleID, s.charID).Scan(&exists)
if err != nil || exists == 0 {
s.server.db.Exec("INSERT INTO titles VALUES ($1, $2, now(), now())", pkt.TitleID, s.charID)
} else {
s.server.db.Exec("UPDATE titles SET updated_at=now()")
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfResetTitle(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfOperateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOperateWarehouse)
var t int
err := s.server.db.QueryRow("SELECT character_id FROM warehouse WHERE character_id=$1", s.charID).Scan(&t)
if err != nil {
s.server.db.Exec("INSERT INTO warehouse (character_id) VALUES ($1)", s.charID)
}
bf := byteframe.NewByteFrame()
bf.WriteUint8(pkt.Operation)
switch pkt.Operation {
case 0:
var count uint8
itemNames := make([]string, 10)
equipNames := make([]string, 10)
s.server.db.QueryRow(fmt.Sprintf("%s WHERE character_id=$1", warehouseNamesQuery), s.charID).Scan(&itemNames[0],
&itemNames[1], &itemNames[2], &itemNames[3], &itemNames[4], &itemNames[5], &itemNames[6], &itemNames[7], &itemNames[8], &itemNames[9], &equipNames[0],
&equipNames[1], &equipNames[2], &equipNames[3], &equipNames[4], &equipNames[5], &equipNames[6], &equipNames[7], &equipNames[8], &equipNames[9])
bf.WriteUint32(0)
bf.WriteUint16(10000) // Usages
temp := byteframe.NewByteFrame()
for i, name := range itemNames {
if len(name) > 0 {
count++
temp.WriteUint8(0)
temp.WriteUint8(uint8(i))
ps.Uint8(temp, name, true)
}
}
for i, name := range equipNames {
if len(name) > 0 {
count++
temp.WriteUint8(1)
temp.WriteUint8(uint8(i))
ps.Uint8(temp, name, true)
}
}
bf.WriteUint8(count)
bf.WriteBytes(temp.Data())
case 1:
bf.WriteUint8(0)
case 2:
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%dname=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), pkt.Name, s.charID)
case 3:
bf.WriteUint32(0) // Usage renewal time, >1 = disabled
bf.WriteUint16(10000) // Usages
case 4:
bf.WriteUint32(0)
bf.WriteUint16(10000) // Usages
bf.WriteUint8(0)
}
// Opcodes
// 0 = Get box names
// 1 = Commit usage
// 2 = Rename
// 3 = Get usage limit
// 4 = Get gift box names (doesn't do anything?)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func addWarehouseGift(s *Session, boxType string, giftStack mhfpacket.WarehouseStack) {
giftBox := getWarehouseBox(s, boxType, 10)
if boxType == "item" {
exists := false
for i, stack := range giftBox {
if stack.ItemID == giftStack.ItemID {
exists = true
giftBox[i].Quantity += giftStack.Quantity
break
}
}
if exists == false {
giftBox = append(giftBox, giftStack)
}
} else {
giftBox = append(giftBox, giftStack)
}
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s10=$1 WHERE character_id=$2", boxType), boxToBytes(giftBox, boxType), s.charID)
}
func getWarehouseBox(s *Session, boxType string, boxIndex uint8) []mhfpacket.WarehouseStack {
var data []byte
s.server.db.QueryRow(fmt.Sprintf("SELECT %s%d FROM warehouse WHERE character_id=$1", boxType, boxIndex), s.charID).Scan(&data)
if len(data) > 0 {
box := byteframe.NewByteFrameFromBytes(data)
numStacks := box.ReadUint16()
stacks := make([]mhfpacket.WarehouseStack, numStacks)
for i := 0; i < int(numStacks); i++ {
if boxType == "item" {
stacks[i].ID = box.ReadUint32()
stacks[i].Index = box.ReadUint16()
stacks[i].ItemID = box.ReadUint16()
stacks[i].Quantity = box.ReadUint16()
box.ReadUint16()
} else {
stacks[i].ID = box.ReadUint32()
stacks[i].Index = box.ReadUint16()
stacks[i].EquipType = box.ReadUint16()
stacks[i].ItemID = box.ReadUint16()
stacks[i].Data = box.ReadBytes(56)
}
}
return stacks
} else {
return make([]mhfpacket.WarehouseStack, 0)
}
}
func boxToBytes(stacks []mhfpacket.WarehouseStack, boxType string) []byte {
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(len(stacks)))
for i, stack := range stacks {
if boxType == "item" {
bf.WriteUint32(stack.ID)
bf.WriteUint16(uint16(i + 1))
bf.WriteUint16(stack.ItemID)
bf.WriteUint16(stack.Quantity)
bf.WriteUint16(0)
} else {
bf.WriteUint32(stack.ID)
bf.WriteUint16(uint16(i + 1))
bf.WriteUint16(stack.EquipType)
bf.WriteUint16(stack.ItemID)
bf.WriteBytes(stack.Data)
}
}
bf.WriteUint16(0)
return bf.Data()
}
func handleMsgMhfEnumerateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateWarehouse)
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
if len(box) > 0 {
doAckBufSucceed(s, pkt.AckHandle, boxToBytes(box, pkt.BoxType))
} else {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgMhfUpdateWarehouse(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateWarehouse)
box := getWarehouseBox(s, pkt.BoxType, pkt.BoxIndex)
// Update existing stacks
var newStacks []mhfpacket.WarehouseStack
for _, update := range pkt.Updates {
exists := false
if pkt.BoxType == "item" {
for i, stack := range box {
if stack.Index == update.Index {
exists = true
box[i].Quantity = update.Quantity
break
}
}
} else {
for i, stack := range box {
if stack.Index == update.Index {
exists = true
box[i].ItemID = update.ItemID
break
}
}
}
if exists == false {
newStacks = append(newStacks, update)
}
}
// Append new stacks
for _, stack := range newStacks {
box = append(box, stack)
}
// Slice empty stacks
var cleanedBox []mhfpacket.WarehouseStack
for _, stack := range box {
if pkt.BoxType == "item" {
if stack.Quantity > 0 {
cleanedBox = append(cleanedBox, stack)
}
} else {
if stack.ItemID != 0 {
cleanedBox = append(cleanedBox, stack)
}
}
}
s.server.db.Exec(fmt.Sprintf("UPDATE warehouse SET %s%d=$1 WHERE character_id=$2", pkt.BoxType, pkt.BoxIndex), boxToBytes(cleanedBox, pkt.BoxType), s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,45 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgMhfAddKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
// hunting with both ranks maxed gets you these
pkt := p.(*mhfpacket.MsgMhfAddKouryouPoint)
var points int
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=COALESCE(kouryou_point + $1, $1) WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
if err != nil {
s.logger.Error("Failed to update KouryouPoint in db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(uint32(points))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfGetKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetKouryouPoint)
var points int
err := s.server.db.QueryRow("SELECT COALESCE(kouryou_point, 0) FROM characters WHERE id = $1", s.charID).Scan(&points)
if err != nil {
s.logger.Error("Failed to get kouryou_point savedata from db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(uint32(points))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfExchangeKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
// spent at the guildmaster, 10000 a roll
var points int
pkt := p.(*mhfpacket.MsgMhfExchangeKouryouPoint)
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=kouryou_point - $1 WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
if err != nil {
s.logger.Error("Failed to update platemyset savedata in db", zap.Error(err))
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(uint32(points))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}

View File

@@ -0,0 +1,413 @@
package channelserver
import (
"database/sql"
"erupe-ce/common/stringsupport"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
type Mail struct {
ID int `db:"id"`
SenderID uint32 `db:"sender_id"`
RecipientID uint32 `db:"recipient_id"`
Subject string `db:"subject"`
Body string `db:"body"`
Read bool `db:"read"`
Deleted bool `db:"deleted"`
Locked bool `db:"locked"`
AttachedItemReceived bool `db:"attached_item_received"`
AttachedItemID uint16 `db:"attached_item"`
AttachedItemAmount uint16 `db:"attached_item_amount"`
CreatedAt time.Time `db:"created_at"`
IsGuildInvite bool `db:"is_guild_invite"`
IsSystemMessage bool `db:"is_sys_message"`
SenderName string `db:"sender_name"`
}
func (m *Mail) Send(s *Session, transaction *sql.Tx) error {
query := `
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite, is_sys_message)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
`
var err error
if transaction == nil {
_, err = s.server.db.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
} else {
_, err = transaction.Exec(query, m.SenderID, m.RecipientID, m.Subject, m.Body, m.AttachedItemID, m.AttachedItemAmount, m.IsGuildInvite, m.IsSystemMessage)
}
if err != nil {
s.logger.Error(
"failed to send mail",
zap.Error(err),
zap.Uint32("senderID", m.SenderID),
zap.Uint32("recipientID", m.RecipientID),
zap.String("subject", m.Subject),
zap.String("body", m.Body),
zap.Uint16("itemID", m.AttachedItemID),
zap.Uint16("itemAmount", m.AttachedItemAmount),
zap.Bool("isGuildInvite", m.IsGuildInvite),
zap.Bool("isSystemMessage", m.IsSystemMessage),
)
return err
}
return nil
}
func (m *Mail) MarkRead(s *Session) error {
_, err := s.server.db.Exec(`
UPDATE mail SET read = true WHERE id = $1
`, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail as read",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func (m *Mail) MarkDeleted(s *Session) error {
_, err := s.server.db.Exec(`
UPDATE mail SET deleted = true WHERE id = $1
`, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail as deleted",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func (m *Mail) MarkAcquired(s *Session) error {
_, err := s.server.db.Exec(`
UPDATE mail SET attached_item_received = true WHERE id = $1
`, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail item as claimed",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func (m *Mail) MarkLocked(s *Session, locked bool) error {
_, err := s.server.db.Exec(`
UPDATE mail SET locked = $1 WHERE id = $2
`, locked, m.ID)
if err != nil {
s.logger.Error(
"failed to mark mail as locked",
zap.Error(err),
zap.Int("mailID", m.ID),
)
return err
}
return nil
}
func GetMailListForCharacter(s *Session, charID uint32) ([]Mail, error) {
rows, err := s.server.db.Queryx(`
SELECT
m.id,
m.sender_id,
m.recipient_id,
m.subject,
m.read,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
FROM mail m
JOIN characters c ON c.id = m.sender_id
WHERE recipient_id = $1 AND m.deleted = false
ORDER BY m.created_at DESC, id DESC
LIMIT 32
`, charID)
if err != nil {
s.logger.Error("failed to get mail for character", zap.Error(err), zap.Uint32("charID", charID))
return nil, err
}
defer rows.Close()
allMail := make([]Mail, 0)
for rows.Next() {
mail := Mail{}
err := rows.StructScan(&mail)
if err != nil {
return nil, err
}
allMail = append(allMail, mail)
}
return allMail, nil
}
func GetMailByID(s *Session, ID int) (*Mail, error) {
row := s.server.db.QueryRowx(`
SELECT
m.id,
m.sender_id,
m.recipient_id,
m.subject,
m.read,
m.body,
m.attached_item_received,
m.attached_item,
m.attached_item_amount,
m.created_at,
m.is_guild_invite,
m.is_sys_message,
m.deleted,
m.locked,
c.name as sender_name
FROM mail m
JOIN characters c ON c.id = m.sender_id
WHERE m.id = $1
LIMIT 1
`, ID)
mail := &Mail{}
err := row.StructScan(mail)
if err != nil {
s.logger.Error(
"failed to retrieve mail",
zap.Error(err),
zap.Int("mailID", ID),
)
return nil, err
}
return mail, nil
}
func SendMailNotification(s *Session, m *Mail, recipient *Session) {
bf := byteframe.NewByteFrame()
notification := &binpacket.MsgBinMailNotify{
SenderName: getCharacterName(s, m.SenderID),
}
notification.Build(bf)
castedBinary := &mhfpacket.MsgSysCastedBinary{
CharID: m.SenderID,
BroadcastType: 0x00,
MessageType: BinaryMessageTypeMailNotify,
RawDataPayload: bf.Data(),
}
castedBinary.Build(bf, s.clientContext)
recipient.QueueSendMHF(castedBinary)
}
func getCharacterName(s *Session, charID uint32) string {
row := s.server.db.QueryRow("SELECT name FROM characters WHERE id = $1", charID)
charName := ""
err := row.Scan(&charName)
if err != nil {
return ""
}
return charName
}
func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMail)
mailId := s.mailList[pkt.AccIndex]
if mailId == 0 {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic("attempting to read mail that doesn't exist in session map")
}
mail, err := GetMailByID(s, mailId)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
_ = mail.MarkRead(s)
bf := byteframe.NewByteFrame()
body := stringsupport.UTF8ToSJIS(mail.Body)
bf.WriteNullTerminatedBytes(body)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfListMail)
mail, err := GetMailListForCharacter(s, s.charID)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
}
if s.mailList == nil {
s.mailList = make([]int, 256)
}
msg := byteframe.NewByteFrame()
msg.WriteUint32(uint32(len(mail)))
startIndex := s.mailAccIndex
for i, m := range mail {
accIndex := startIndex + uint8(i)
s.mailList[accIndex] = m.ID
s.mailAccIndex++
itemAttached := m.AttachedItemID != 0
msg.WriteUint32(m.SenderID)
msg.WriteUint32(uint32(m.CreatedAt.Unix()))
msg.WriteUint8(accIndex)
msg.WriteUint8(uint8(i))
flags := uint8(0x00)
if m.Read {
flags |= 0x01
}
if m.Locked {
flags |= 0x02
}
if m.IsSystemMessage {
flags |= 0x04
}
if m.AttachedItemReceived {
flags |= 0x08
}
if m.IsGuildInvite {
flags |= 0x10
}
msg.WriteUint8(flags)
msg.WriteBool(itemAttached)
msg.WriteUint8(16)
msg.WriteUint8(21)
msg.WriteBytes(stringsupport.PaddedString(m.Subject, 16, true))
msg.WriteBytes(stringsupport.PaddedString(m.SenderName, 21, true))
if itemAttached {
msg.WriteUint16(m.AttachedItemAmount)
msg.WriteUint16(m.AttachedItemID)
}
}
doAckBufSucceed(s, pkt.AckHandle, msg.Data())
}
func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfOprtMail)
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
if err != nil {
panic(err)
}
switch pkt.Operation {
case mhfpacket.OPERATE_MAIL_DELETE:
err = mail.MarkDeleted(s)
case mhfpacket.OPERATE_MAIL_LOCK:
err = mail.MarkLocked(s, true)
case mhfpacket.OPERATE_MAIL_UNLOCK:
err = mail.MarkLocked(s, false)
case mhfpacket.OPERATE_MAIL_ACQUIRE_ITEM:
err = mail.MarkAcquired(s)
}
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfSendMail(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSendMail)
query := `
INSERT INTO mail (sender_id, recipient_id, subject, body, attached_item, attached_item_amount, is_guild_invite)
VALUES ($1, $2, $3, $4, $5, $6, $7)
`
if pkt.RecipientID == 0 { // Guild mail
g, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
s.logger.Error("Failed to get guild info for mail")
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
gm, err := GetGuildMembers(s, g.ID, false)
if err != nil {
s.logger.Error("Failed to get guild members for mail")
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
for i := 0; i < len(gm); i++ {
_, err := s.server.db.Exec(query, s.charID, gm[i].CharID, pkt.Subject, pkt.Body, 0, 0, false)
if err != nil {
s.logger.Error("Failed to send mail")
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
}
} else {
_, err := s.server.db.Exec(query, s.charID, pkt.RecipientID, pkt.Subject, pkt.Body, pkt.ItemID, pkt.Quantity, false)
if err != nil {
s.logger.Error("Failed to send mail")
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,437 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"erupe-ce/server/channelserver/compression/deltacomp"
"erupe-ce/server/channelserver/compression/nullcomp"
"go.uber.org/zap"
"io"
"os"
"path/filepath"
"time"
)
func handleMsgMhfLoadPartner(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPartner)
var data []byte
err := s.server.db.QueryRow("SELECT partner FROM characters WHERE id = $1", s.charID).Scan(&data)
if len(data) == 0 {
s.logger.Error("Failed to load partner", zap.Error(err))
data = make([]byte, 9)
}
doAckBufSucceed(s, pkt.AckHandle, data)
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.Error("Failed to save partner", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadLegendDispatch)
bf := byteframe.NewByteFrame()
legendDispatch := []struct {
Unk uint32
Timestamp uint32
}{
{0, uint32(TimeMidnight().Add(-12 * time.Hour).Unix())},
{0, uint32(TimeMidnight().Add(12 * time.Hour).Unix())},
{0, uint32(TimeMidnight().Add(36 * time.Hour).Unix())},
}
bf.WriteUint8(uint8(len(legendDispatch)))
for _, dispatch := range legendDispatch {
bf.WriteUint32(dispatch.Unk)
bf.WriteUint32(dispatch.Timestamp)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
naviLength := 552
if s.server.erupeConfig.RealClientMode <= _config.G7 {
naviLength = 280
}
var data []byte
err := s.server.db.QueryRow("SELECT hunternavi FROM characters WHERE id = $1", s.charID).Scan(&data)
if len(data) == 0 {
s.logger.Error("Failed to load hunternavi", zap.Error(err))
data = make([]byte, naviLength)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveHunterNavi)
if pkt.IsDataDiff {
naviLength := 552
if s.server.erupeConfig.RealClientMode <= _config.G7 {
naviLength = 280
}
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.Error("Failed to load hunternavi", 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, naviLength)
}
// 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.Error("Failed to save hunternavi", 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.Error("Failed to save hunternavi", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
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)
s.server.db.Exec("UPDATE characters SET rasta_id=$1 WHERE id=$2", nextID, s.charID)
bf.WriteUint32(nextID)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfSaveMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveMercenary)
dumpSaveData(s, pkt.MercData, "mercenary")
if len(pkt.MercData) > 0 {
temp := byteframe.NewByteFrameFromBytes(pkt.MercData)
s.server.db.Exec("UPDATE characters SET savemercenary=$1, rasta_id=$2 WHERE id=$3", pkt.MercData, temp.ReadUint32(), s.charID)
}
s.server.db.Exec("UPDATE characters SET gcp=$1, pact_id=$2 WHERE id=$3", pkt.GCP, pkt.PactMercID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
if pkt.Op > 0 {
bf := byteframe.NewByteFrame()
var pactID uint32
var name string
var cid uint32
s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
if pactID > 0 {
s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
bf.WriteUint8(1) // numLends
bf.WriteUint32(pactID)
bf.WriteUint32(cid)
bf.WriteBool(false) // ?
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix()))
bf.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix()))
bf.WriteBytes(stringsupport.PaddedString(name, 18, true))
} else {
bf.WriteUint8(0)
}
if pkt.Op < 2 {
var loans uint8
temp := byteframe.NewByteFrame()
rows, _ := s.server.db.Query("SELECT name, id, pact_id FROM characters WHERE pact_id=(SELECT rasta_id FROM characters WHERE id=$1)", s.charID)
for rows.Next() {
loans++
rows.Scan(&name, &cid, &pactID)
temp.WriteUint32(pactID)
temp.WriteUint32(cid)
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -8).Unix()))
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * -1).Unix()))
temp.WriteBytes(stringsupport.PaddedString(name, 18, true))
}
bf.WriteUint8(loans)
bf.WriteBytes(temp.Data())
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
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()
resp.WriteUint16(0)
if len(data) == 0 {
resp.WriteBool(false)
} else {
resp.WriteBool(true)
resp.WriteBytes(data)
}
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)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfContractMercenary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfContractMercenary)
switch pkt.Op {
case 0: // Form loan
s.server.db.Exec("UPDATE characters SET pact_id=$1 WHERE id=$2", pkt.PactMercID, pkt.CID)
case 1: // Cancel lend
s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", s.charID)
case 2: // Cancel loan
s.server.db.Exec("UPDATE characters SET pact_id=0 WHERE id=$1", pkt.CID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
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 len(data) == 0 {
s.logger.Error("Failed to load otomoairou", zap.Error(err))
data = make([]byte, 10)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
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, _ := os.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, TimeAdjusted().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
}

View File

@@ -0,0 +1,13 @@
package channelserver
import "erupe-ce/network/mhfpacket"
func handleMsgSysCreateMutex(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysCreateOpenMutex(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDeleteMutex(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysOpenMutex(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysCloseMutex(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,96 @@
package channelserver
import (
"fmt"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateObject)
s.stage.Lock()
newObj := &Object{
id: s.NextObjectID(),
ownerCharID: s.charID,
x: pkt.X,
y: pkt.Y,
z: pkt.Z,
}
s.stage.objects[s.charID] = newObj
s.stage.Unlock()
// Response to our requesting client.
resp := byteframe.NewByteFrame()
resp.WriteUint32(newObj.id) // New local obj handle.
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
// Duplicate the object creation to all sessions in the same stage.
dupObjUpdate := &mhfpacket.MsgSysDuplicateObject{
ObjID: newObj.id,
X: newObj.x,
Y: newObj.y,
Z: newObj.z,
OwnerCharID: newObj.ownerCharID,
}
s.logger.Info(fmt.Sprintf("Broadcasting new object: %s (%d)", s.Name, newObj.id))
s.stage.BroadcastMHF(dupObjUpdate, s)
}
func handleMsgSysDeleteObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysPositionObject(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysPositionObject)
if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogInboundMessages {
fmt.Printf("[%s] with objectID [%d] move to (%f,%f,%f)\n\n", s.Name, pkt.ObjID, pkt.X, pkt.Y, pkt.Z)
}
s.stage.Lock()
object, ok := s.stage.objects[s.charID]
if ok {
object.x = pkt.X
object.y = pkt.Y
object.z = pkt.Z
}
s.stage.Unlock()
// One of the few packets we can just re-broadcast directly.
s.stage.BroadcastMHF(pkt, s)
}
func handleMsgSysRotateObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDuplicateObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysSetObjectBinary(s *Session, p mhfpacket.MHFPacket) {
_ = p.(*mhfpacket.MsgSysSetObjectBinary)
/* This causes issues with PS3 as this actually sends with endiness!
for _, session := range s.server.sessions {
if session.charID == s.charID {
s.server.userBinaryPartsLock.Lock()
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: 3}] = pkt.RawDataPayload
s.server.userBinaryPartsLock.Unlock()
msg := &mhfpacket.MsgSysNotifyUserBinary{
CharID: s.charID,
BinaryType: 3,
}
s.server.BroadcastMHF(msg, s)
}
}
*/
}
func handleMsgSysGetObjectBinary(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysGetObjectOwner(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysUpdateObjectBinary(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysCleanupObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysAddObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDelObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDispObject(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysHideObject(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,164 @@
package channelserver
import (
"erupe-ce/network/mhfpacket"
"erupe-ce/server/channelserver/compression/deltacomp"
"erupe-ce/server/channelserver/compression/nullcomp"
"go.uber.org/zap"
)
func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPlateData)
var data []byte
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Error("Failed to load platedata", zap.Error(err))
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePlateData)
if pkt.IsDataDiff {
var data []byte
// Load existing save
err := s.server.db.QueryRow("SELECT platedata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Error("Failed to load platedata", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
if len(data) > 0 {
// Decompress
s.logger.Info("Decompressing...")
data, err = nullcomp.Decompress(data)
if err != nil {
s.logger.Error("Failed to decompress platedata", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
} else {
// create empty save if absent
data = make([]byte, 0x1AF20)
}
// Perform diff and compress it to write back to db
s.logger.Info("Diffing...")
saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data))
if err != nil {
s.logger.Error("Failed to diff and compress platedata", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
_, err = s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", saveOutput, s.charID)
if err != nil {
s.logger.Error("Failed to save platedata", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
s.logger.Info("Wrote recompressed platedata back to DB")
} else {
dumpSaveData(s, pkt.RawDataPayload, "platedata")
// simply update database, no extra processing
_, err := s.server.db.Exec("UPDATE characters SET platedata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Error("Failed to save platedata", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPlateBox)
var data []byte
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Error("Failed to load platebox", zap.Error(err))
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePlateBox)
if pkt.IsDataDiff {
var data []byte
// Load existing save
err := s.server.db.QueryRow("SELECT platebox FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Error("Failed to load platebox", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
// Decompress
if len(data) > 0 {
// Decompress
s.logger.Info("Decompressing...")
data, err = nullcomp.Decompress(data)
if err != nil {
s.logger.Error("Failed to decompress platebox", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
} else {
// create empty save if absent
data = make([]byte, 0x820)
}
// Perform diff and compress it to write back to db
s.logger.Info("Diffing...")
saveOutput, err := nullcomp.Compress(deltacomp.ApplyDataDiff(pkt.RawDataPayload, data))
if err != nil {
s.logger.Error("Failed to diff and compress platebox", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
_, err = s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", saveOutput, s.charID)
if err != nil {
s.logger.Error("Failed to save platebox", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
s.logger.Info("Wrote recompressed platebox back to DB")
} else {
dumpSaveData(s, pkt.RawDataPayload, "platebox")
// simply update database, no extra processing
_, err := s.server.db.Exec("UPDATE characters SET platebox=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Error("Failed to save platebox", zap.Error(err))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadPlateMyset)
var data []byte
err := s.server.db.QueryRow("SELECT platemyset FROM characters WHERE id = $1", s.charID).Scan(&data)
if len(data) == 0 {
s.logger.Error("Failed to load platemyset", zap.Error(err))
data = make([]byte, 0x780)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSavePlateMyset)
// looks to always return the full thing, simply update database, no extra processing
dumpSaveData(s, pkt.RawDataPayload, "platemyset")
_, err := s.server.db.Exec("UPDATE characters SET platemyset=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Error("Failed to save platemyset", zap.Error(err))
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}

View File

@@ -0,0 +1,729 @@
package channelserver
import (
"database/sql"
"erupe-ce/common/byteframe"
"erupe-ce/common/decryption"
ps "erupe-ce/common/pascalstring"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"fmt"
"io"
"os"
"path/filepath"
"time"
"go.uber.org/zap"
)
func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysGetFile)
if pkt.IsScenario {
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
s.logger.Debug(
"Scenario",
zap.Uint8("CategoryID", pkt.ScenarioIdentifer.CategoryID),
zap.Uint32("MainID", pkt.ScenarioIdentifer.MainID),
zap.Uint8("ChapterID", pkt.ScenarioIdentifer.ChapterID),
zap.Uint8("Flags", pkt.ScenarioIdentifer.Flags),
)
}
filename := fmt.Sprintf("%d_0_0_0_S%d_T%d_C%d", pkt.ScenarioIdentifer.CategoryID, pkt.ScenarioIdentifer.MainID, pkt.ScenarioIdentifer.Flags, pkt.ScenarioIdentifer.ChapterID)
// Read the scenario file.
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("scenarios/%s.bin", filename)))
if err != nil {
s.logger.Error(fmt.Sprintf("Failed to open file: %s/scenarios/%s.bin", s.server.erupeConfig.BinPath, filename))
// This will crash the game.
doAckBufSucceed(s, pkt.AckHandle, data)
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
if s.server.erupeConfig.DevModeOptions.QuestDebugTools && s.server.erupeConfig.DevMode {
s.logger.Debug(
"Quest",
zap.String("Filename", pkt.Filename),
)
}
if s.server.erupeConfig.GameplayOptions.SeasonOverride {
pkt.Filename = seasonConversion(s, pkt.Filename)
}
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", pkt.Filename)))
if err != nil {
s.logger.Error(fmt.Sprintf("Failed to open file: %s/quests/%s.bin", s.server.erupeConfig.BinPath, pkt.Filename))
// This will crash the game.
doAckBufSucceed(s, pkt.AckHandle, data)
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
}
func questSuffix(s *Session) string {
// Determine the letter to append for day / night
var timeSet string
if TimeGameAbsolute() > 2880 {
timeSet = "d"
} else {
timeSet = "n"
}
return fmt.Sprintf("%s%d", timeSet, s.server.Season())
}
func seasonConversion(s *Session, questFile string) string {
filename := fmt.Sprintf("%s%s", questFile[:5], questSuffix(s))
// Return original file if file doesn't exist
if _, err := os.Stat(filename); err == nil {
return filename
} else {
return questFile
}
}
func handleMsgMhfLoadFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadFavoriteQuest)
var data []byte
err := s.server.db.QueryRow("SELECT savefavoritequest FROM characters WHERE id = $1", s.charID).Scan(&data)
if err == nil && len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
doAckBufSucceed(s, pkt.AckHandle, []byte{0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfSaveFavoriteQuest)
dumpSaveData(s, pkt.Data, "favquest")
s.server.db.Exec("UPDATE characters SET savefavoritequest=$1 WHERE id=$2", pkt.Data, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func loadQuestFile(s *Session, questId int) []byte {
file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%05dd0.bin", questId)))
if err != nil {
return nil
}
decrypted := decryption.UnpackSimple(file)
fileBytes := byteframe.NewByteFrameFromBytes(decrypted)
fileBytes.SetLE()
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
// The 320 bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers.
questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(320))
questBody.SetLE()
// Find the master quest string pointer
questBody.Seek(40, 0)
fileBytes.Seek(int64(questBody.ReadUint32()), 0)
questBody.Seek(40, 0)
// Overwrite it
questBody.WriteUint32(320)
questBody.Seek(0, 2)
// Rewrite the quest strings and their pointers
var tempString []byte
newStrings := byteframe.NewByteFrame()
tempPointer := 352
for i := 0; i < 8; i++ {
questBody.WriteUint32(uint32(tempPointer))
temp := int64(fileBytes.Index())
fileBytes.Seek(int64(fileBytes.ReadUint32()), 0)
tempString = fileBytes.ReadNullTerminatedBytes()
fileBytes.Seek(temp+4, 0)
tempPointer += len(tempString) + 1
newStrings.WriteNullTerminatedBytes(tempString)
}
questBody.WriteBytes(newStrings.Data())
return questBody.Data()
}
func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
var id, mark uint32
var questId int
var maxPlayers, questType uint8
rows.Scan(&id, &maxPlayers, &questType, &questId, &mark)
data := loadQuestFile(s, questId)
if data == nil {
return nil, fmt.Errorf("failed to load quest file")
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(id)
bf.WriteUint32(0)
bf.WriteUint8(0) // Indexer
switch questType {
case 16:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers)
case 22:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ViolentRavienteMaxPlayers)
case 40:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.BerserkRavienteMaxPlayers)
case 50:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ExtremeRavienteMaxPlayers)
case 51:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.SmallBerserkRavienteMaxPlayers)
default:
bf.WriteUint8(maxPlayers)
}
bf.WriteUint8(questType)
if questType == 9 {
bf.WriteBool(false)
} else {
bf.WriteBool(true)
}
bf.WriteUint16(0)
if _config.ErupeConfig.RealClientMode >= _config.G1 {
bf.WriteUint32(mark)
}
bf.WriteUint16(0)
bf.WriteUint16(uint16(len(data)))
bf.WriteBytes(data)
ps.Uint8(bf, "", true) // What is this string for?
return bf.Data(), nil
}
func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateQuest)
var totalCount, returnedCount uint16
bf := byteframe.NewByteFrame()
bf.WriteUint16(0)
rows, _ := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark FROM event_quests ORDER BY quest_id")
for rows.Next() {
data, err := makeEventQuest(s, rows)
if err != nil {
continue
} else {
if len(data) > 896 || len(data) < 352 {
continue
} else {
totalCount++
if _config.ErupeConfig.RealClientMode == _config.F5 {
if totalCount > pkt.Offset && len(bf.Data()) < 21550 {
returnedCount++
bf.WriteBytes(data)
continue
}
} else {
if totalCount > pkt.Offset && len(bf.Data()) < 60000 {
returnedCount++
bf.WriteBytes(data)
continue
}
}
}
}
}
type tuneValue struct {
ID uint16
Value uint16
}
tuneValues := []tuneValue{
{ID: 20, Value: 1},
{ID: 26, Value: 1},
{ID: 27, Value: 1},
{ID: 33, Value: 1},
{ID: 40, Value: 1},
{ID: 49, Value: 1},
{ID: 53, Value: 1},
{ID: 59, Value: 1},
{ID: 67, Value: 1},
{ID: 80, Value: 1},
{ID: 94, Value: 1},
{ID: 1010, Value: 300},
{ID: 1011, Value: 300},
{ID: 1012, Value: 300},
{ID: 1013, Value: 300},
{ID: 1014, Value: 200},
{ID: 1015, Value: 200},
{ID: 1021, Value: 400},
{ID: 1023, Value: 8},
{ID: 1024, Value: 150},
{ID: 1025, Value: 1},
{ID: 1026, Value: 999}, // get_grank_cap
{ID: 1027, Value: 100},
{ID: 1028, Value: 100},
{ID: 1030, Value: 8},
{ID: 1031, Value: 100},
{ID: 1032, Value: 0}, // isValid_partner
{ID: 1044, Value: 200}, // get_rate_tload_time_out
{ID: 1045, Value: 0}, // get_rate_tower_treasure_preset
{ID: 1046, Value: 99},
{ID: 1048, Value: 0}, // get_rate_tower_log_disable
{ID: 1049, Value: 10}, // get_rate_tower_gem_max
{ID: 1050, Value: 1}, // get_rate_tower_gem_set
{ID: 1051, Value: 200},
{ID: 1052, Value: 200},
{ID: 1063, Value: 50000},
{ID: 1064, Value: 50000},
{ID: 1065, Value: 25000},
{ID: 1066, Value: 25000},
{ID: 1067, Value: 90}, // get_lobby_member_upper_for_making_room Lv1?
{ID: 1068, Value: 80}, // get_lobby_member_upper_for_making_room Lv2?
{ID: 1069, Value: 70}, // get_lobby_member_upper_for_making_room Lv3?
{ID: 1072, Value: 300}, // get_rate_premium_ravi_tama
{ID: 1073, Value: 300}, // get_rate_premium_ravi_ax_tama
{ID: 1074, Value: 300}, // get_rate_premium_ravi_g_tama
{ID: 1078, Value: 0},
{ID: 1079, Value: 1},
{ID: 1080, Value: 1},
{ID: 1081, Value: 1},
{ID: 1082, Value: 4},
{ID: 1083, Value: 2},
{ID: 1084, Value: 10},
{ID: 1085, Value: 1},
{ID: 1086, Value: 4},
{ID: 1087, Value: 2},
{ID: 1088, Value: 10},
{ID: 1089, Value: 1},
{ID: 1090, Value: 3},
{ID: 1091, Value: 2},
{ID: 1092, Value: 10},
{ID: 1093, Value: 2},
{ID: 1094, Value: 5},
{ID: 1095, Value: 2},
{ID: 1096, Value: 10},
{ID: 1097, Value: 2},
{ID: 1098, Value: 5},
{ID: 1099, Value: 2},
{ID: 1100, Value: 10},
{ID: 1101, Value: 2},
{ID: 1102, Value: 5},
{ID: 1103, Value: 2},
{ID: 1104, Value: 10},
{ID: 1145, Value: 200},
{ID: 1146, Value: 0}, // isTower_invisible
{ID: 1147, Value: 0}, // isVenom_playable
{ID: 1149, Value: 20},
{ID: 1152, Value: 1130},
{ID: 1154, Value: 0}, // isDisabled_object_season
{ID: 1158, Value: 1},
{ID: 1160, Value: 300},
{ID: 1162, Value: 1},
{ID: 1163, Value: 3},
{ID: 1164, Value: 5},
{ID: 1165, Value: 1},
{ID: 1166, Value: 5},
{ID: 1167, Value: 1},
{ID: 1168, Value: 3},
{ID: 1169, Value: 3},
{ID: 1170, Value: 5},
{ID: 1171, Value: 1},
{ID: 1172, Value: 1},
{ID: 1173, Value: 1},
{ID: 1174, Value: 2},
{ID: 1175, Value: 4},
{ID: 1176, Value: 10},
{ID: 1177, Value: 4},
{ID: 1178, Value: 10},
{ID: 1179, Value: 2},
{ID: 1180, Value: 5},
{ID: 3000, Value: 100},
{ID: 3001, Value: 100},
{ID: 3002, Value: 100},
{ID: 3003, Value: 100},
{ID: 3004, Value: 100},
{ID: 3005, Value: 100},
{ID: 3006, Value: 100},
{ID: 3007, Value: 100},
{ID: 3008, Value: 100},
{ID: 3009, Value: 100},
{ID: 3010, Value: 100},
{ID: 3011, Value: 100},
{ID: 3012, Value: 100},
{ID: 3013, Value: 100},
{ID: 3014, Value: 100},
{ID: 3015, Value: 100},
{ID: 3016, Value: 100},
{ID: 3017, Value: 100},
{ID: 3018, Value: 100},
{ID: 3019, Value: 100},
{ID: 3020, Value: 100},
{ID: 3021, Value: 100},
{ID: 3022, Value: 100},
{ID: 3023, Value: 100},
{ID: 3024, Value: 100},
{ID: 3025, Value: 100},
{ID: 3286, Value: 200},
{ID: 3287, Value: 200},
{ID: 3288, Value: 200},
{ID: 3289, Value: 200},
{ID: 3290, Value: 200},
{ID: 3291, Value: 200},
{ID: 3292, Value: 200},
{ID: 3293, Value: 200},
{ID: 3294, Value: 200},
{ID: 3295, Value: 200},
{ID: 3296, Value: 200},
{ID: 3297, Value: 200},
{ID: 3298, Value: 200},
{ID: 3299, Value: 200},
{ID: 3300, Value: 200},
{ID: 3301, Value: 200},
{ID: 3302, Value: 200},
{ID: 3303, Value: 200},
{ID: 3304, Value: 200},
{ID: 3305, Value: 200},
{ID: 3306, Value: 200},
{ID: 3307, Value: 200},
{ID: 3308, Value: 200},
{ID: 3309, Value: 200},
{ID: 3310, Value: 200},
{ID: 3311, Value: 200},
{ID: 3312, Value: 300},
{ID: 3313, Value: 300},
{ID: 3314, Value: 300},
{ID: 3315, Value: 300},
{ID: 3316, Value: 300},
{ID: 3317, Value: 300},
{ID: 3318, Value: 300},
{ID: 3319, Value: 300},
{ID: 3320, Value: 300},
{ID: 3321, Value: 300},
{ID: 3322, Value: 300},
{ID: 3323, Value: 300},
{ID: 3324, Value: 300},
{ID: 3325, Value: 300},
{ID: 3326, Value: 300},
{ID: 3327, Value: 300},
{ID: 3328, Value: 300},
{ID: 3329, Value: 300},
{ID: 3330, Value: 300},
{ID: 3331, Value: 300},
{ID: 3332, Value: 300},
{ID: 3333, Value: 300},
{ID: 3334, Value: 300},
{ID: 3335, Value: 300},
{ID: 3336, Value: 300},
{ID: 3337, Value: 300},
{ID: 3338, Value: 100},
{ID: 3339, Value: 100},
{ID: 3340, Value: 100},
{ID: 3341, Value: 100},
{ID: 3342, Value: 100},
{ID: 3343, Value: 100},
{ID: 3344, Value: 100},
{ID: 3345, Value: 100},
{ID: 3346, Value: 100},
{ID: 3347, Value: 100},
{ID: 3348, Value: 100},
{ID: 3349, Value: 100},
{ID: 3350, Value: 100},
{ID: 3351, Value: 100},
{ID: 3352, Value: 100},
{ID: 3353, Value: 100},
{ID: 3354, Value: 100},
{ID: 3355, Value: 100},
{ID: 3356, Value: 100},
{ID: 3357, Value: 100},
{ID: 3358, Value: 100},
{ID: 3359, Value: 100},
{ID: 3360, Value: 100},
{ID: 3361, Value: 100},
{ID: 3362, Value: 100},
{ID: 3363, Value: 100},
{ID: 3364, Value: 100},
{ID: 3365, Value: 100},
{ID: 3366, Value: 100},
{ID: 3367, Value: 100},
{ID: 3368, Value: 100},
{ID: 3369, Value: 100},
{ID: 3370, Value: 100},
{ID: 3371, Value: 100},
{ID: 3372, Value: 100},
{ID: 3373, Value: 100},
{ID: 3374, Value: 100},
{ID: 3375, Value: 100},
{ID: 3376, Value: 100},
{ID: 3377, Value: 100},
{ID: 3378, Value: 100},
{ID: 3379, Value: 100},
{ID: 3380, Value: 100},
{ID: 3381, Value: 100},
{ID: 3382, Value: 100},
{ID: 3383, Value: 100},
{ID: 3384, Value: 100},
{ID: 3385, Value: 100},
{ID: 3386, Value: 100},
{ID: 3387, Value: 100},
{ID: 3388, Value: 100},
{ID: 3389, Value: 100},
{ID: 3390, Value: 100},
{ID: 3391, Value: 100},
{ID: 3392, Value: 100},
{ID: 3393, Value: 100},
{ID: 3394, Value: 100},
{ID: 3395, Value: 100},
{ID: 3396, Value: 100},
{ID: 3397, Value: 100},
{ID: 3398, Value: 100},
{ID: 3399, Value: 100},
{ID: 3400, Value: 100},
{ID: 3401, Value: 100},
{ID: 3402, Value: 100},
{ID: 3416, Value: 100},
{ID: 3417, Value: 100},
{ID: 3418, Value: 100},
{ID: 3419, Value: 100},
{ID: 3420, Value: 100},
{ID: 3421, Value: 100},
{ID: 3422, Value: 100},
{ID: 3423, Value: 100},
{ID: 3424, Value: 100},
{ID: 3425, Value: 100},
{ID: 3426, Value: 100},
{ID: 3427, Value: 100},
{ID: 3428, Value: 100},
{ID: 3442, Value: 100},
{ID: 3443, Value: 100},
{ID: 3444, Value: 100},
{ID: 3445, Value: 100},
{ID: 3446, Value: 100},
{ID: 3447, Value: 100},
{ID: 3448, Value: 100},
{ID: 3449, Value: 100},
{ID: 3450, Value: 100},
{ID: 3451, Value: 100},
{ID: 3452, Value: 100},
{ID: 3453, Value: 100},
{ID: 3454, Value: 100},
{ID: 3468, Value: 100},
{ID: 3469, Value: 100},
{ID: 3470, Value: 100},
{ID: 3471, Value: 100},
{ID: 3472, Value: 100},
{ID: 3473, Value: 100},
{ID: 3474, Value: 100},
{ID: 3475, Value: 100},
{ID: 3476, Value: 100},
{ID: 3477, Value: 100},
{ID: 3478, Value: 100},
{ID: 3479, Value: 100},
{ID: 3480, Value: 100},
{ID: 3494, Value: 0},
{ID: 3495, Value: 0},
{ID: 3496, Value: 0},
{ID: 3497, Value: 0},
{ID: 3498, Value: 0},
{ID: 3499, Value: 0},
{ID: 3500, Value: 0},
{ID: 3501, Value: 0},
{ID: 3502, Value: 0},
{ID: 3503, Value: 0},
{ID: 3504, Value: 0},
{ID: 3505, Value: 0},
{ID: 3506, Value: 0},
{ID: 3520, Value: 0},
{ID: 3521, Value: 0},
{ID: 3522, Value: 0},
{ID: 3523, Value: 0},
{ID: 3524, Value: 0},
{ID: 3525, Value: 0},
{ID: 3526, Value: 0},
{ID: 3527, Value: 0},
{ID: 3528, Value: 0},
{ID: 3529, Value: 0},
{ID: 3530, Value: 0},
{ID: 3531, Value: 0},
{ID: 3532, Value: 0},
{ID: 3546, Value: 0},
{ID: 3547, Value: 0},
{ID: 3548, Value: 0},
{ID: 3549, Value: 0},
{ID: 3550, Value: 0},
{ID: 3551, Value: 0},
{ID: 3552, Value: 0},
{ID: 3553, Value: 0},
{ID: 3554, Value: 0},
{ID: 3555, Value: 0},
{ID: 3556, Value: 0},
{ID: 3557, Value: 0},
{ID: 3558, Value: 0},
{ID: 3572, Value: 0},
{ID: 3573, Value: 0},
{ID: 3574, Value: 0},
{ID: 3575, Value: 0},
{ID: 3576, Value: 0},
{ID: 3577, Value: 0},
{ID: 3578, Value: 0},
{ID: 3579, Value: 0},
{ID: 3580, Value: 0},
{ID: 3581, Value: 0},
{ID: 3582, Value: 0},
{ID: 3583, Value: 0},
{ID: 3584, Value: 0},
}
tuneValues = append(tuneValues, tuneValue{1020, uint16(s.server.erupeConfig.GameplayOptions.GCPMultiplier * 100)})
tuneValues = append(tuneValues, tuneValue{1029, s.server.erupeConfig.GameplayOptions.GUrgentRate})
if s.server.erupeConfig.GameplayOptions.DisableHunterNavi {
tuneValues = append(tuneValues, tuneValue{1037, 1})
}
if s.server.erupeConfig.GameplayOptions.EnableKaijiEvent {
tuneValues = append(tuneValues, tuneValue{1106, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1106, 0})
}
if s.server.erupeConfig.GameplayOptions.EnableHiganjimaEvent {
tuneValues = append(tuneValues, tuneValue{1144, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1144, 0})
}
if s.server.erupeConfig.GameplayOptions.EnableNierEvent {
tuneValues = append(tuneValues, tuneValue{1153, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1153, 0})
}
if s.server.erupeConfig.GameplayOptions.DisableRoad {
tuneValues = append(tuneValues, tuneValue{1155, 1})
} else {
tuneValues = append(tuneValues, tuneValue{1155, 0})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3026, uint16(s.server.erupeConfig.GameplayOptions.GRPMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3039, uint16(s.server.erupeConfig.GameplayOptions.GSRPMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3052, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3078, uint16(s.server.erupeConfig.GameplayOptions.GZennyMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3104, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3130, uint16(s.server.erupeConfig.GameplayOptions.MaterialMultiplier * 100)})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3156, s.server.erupeConfig.GameplayOptions.ExtraCarves})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3182, s.server.erupeConfig.GameplayOptions.ExtraCarves})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3208, s.server.erupeConfig.GameplayOptions.ExtraCarves})
}
for i := uint16(0); i < 13; i++ {
tuneValues = append(tuneValues, tuneValue{i + 3234, s.server.erupeConfig.GameplayOptions.ExtraCarves})
}
offset := uint16(time.Now().Unix())
bf.WriteUint16(offset)
tuneLimit := 770
if _config.ErupeConfig.RealClientMode <= _config.F5 {
tuneLimit = 256
} else if _config.ErupeConfig.RealClientMode <= _config.G3 {
tuneLimit = 283
} else if _config.ErupeConfig.RealClientMode <= _config.GG {
tuneLimit = 315
} else if _config.ErupeConfig.RealClientMode <= _config.G61 {
tuneLimit = 332
} else if _config.ErupeConfig.RealClientMode <= _config.G7 {
tuneLimit = 339
} else if _config.ErupeConfig.RealClientMode <= _config.G81 {
tuneLimit = 396
} else if _config.ErupeConfig.RealClientMode <= _config.G91 {
tuneLimit = 694
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
tuneLimit = 704
} else if _config.ErupeConfig.RealClientMode <= _config.Z2 {
tuneLimit = 750
}
if len(tuneValues) > tuneLimit {
tuneValues = tuneValues[:tuneLimit]
}
bf.WriteUint16(uint16(len(tuneValues)))
for i := range tuneValues {
bf.WriteUint16(tuneValues[i].ID ^ offset)
bf.WriteUint16(offset)
bf.WriteBytes(make([]byte, 4))
bf.WriteUint16(tuneValues[i].Value ^ offset)
}
vsQuestItems := []uint16{1580, 1581, 1582, 1583, 1584, 1585, 1587, 1588, 1589, 1595, 1596, 1597, 1598, 1599, 1600, 1601, 1602, 1603, 1604}
vsQuestBets := []struct {
IsTicket bool
Quantity uint32
}{
{true, 5},
{false, 1000},
{false, 5000},
{false, 10000},
}
bf.WriteUint16(uint16(len(vsQuestItems)))
bf.WriteUint16(0) // Unk array of uint16s
bf.WriteUint16(uint16(len(vsQuestBets)))
bf.WriteUint16(0) // Unk
for i := range vsQuestItems {
bf.WriteUint16(vsQuestItems[i])
}
for i := range vsQuestBets {
bf.WriteBool(vsQuestBets[i].IsTicket)
bf.WriteUint8(9)
bf.WriteUint16(7)
bf.WriteUint32(vsQuestBets[i].Quantity)
}
bf.WriteUint16(totalCount)
bf.WriteUint16(pkt.Offset)
bf.Seek(0, io.SeekStart)
bf.WriteUint16(returnedCount)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEnterTournamentQuest(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfGetUdBonusQuestInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdBonusQuestInfo)
udBonusQuestInfos := []struct {
Unk0 uint8
Unk1 uint8
StartTime uint32 // Unix timestamp (seconds)
EndTime uint32 // Unix timestamp (seconds)
Unk4 uint32
Unk5 uint8
Unk6 uint8
}{} // Blank stub array.
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(udBonusQuestInfos)))
for _, q := range udBonusQuestInfos {
resp.WriteUint8(q.Unk0)
resp.WriteUint8(q.Unk1)
resp.WriteUint32(q.StartTime)
resp.WriteUint32(q.EndTime)
resp.WriteUint32(q.Unk4)
resp.WriteUint8(q.Unk5)
resp.WriteUint8(q.Unk6)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}

View File

@@ -0,0 +1,152 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"strings"
)
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegisterEvent)
bf := byteframe.NewByteFrame()
// Some kind of check if there's already a session
if pkt.Unk3 > 0 && s.server.getRaviSemaphore() == nil {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf.WriteUint8(uint8(pkt.WorldID))
bf.WriteUint8(uint8(pkt.LandID))
bf.WriteUint16(s.server.raviente.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
// Do this ack manually because it uses a non-(0|1) error code
/*
_ACK_SUCCESS = 0
_ACK_ERROR = 1
_ACK_EINPROGRESS = 16
_ACK_ENOENT = 17
_ACK_ENOSPC = 18
_ACK_ETIMEOUT = 19
_ACK_EINVALID = 64
_ACK_EFAILED = 65
_ACK_ENOMEM = 66
_ACK_ENOTEXIT = 67
_ACK_ENOTREADY = 68
_ACK_EALREADY = 69
_ACK_DISABLE_WORK = 71
*/
s.QueueSendMHF(&mhfpacket.MsgSysAck{
AckHandle: pkt.AckHandle,
IsBufferResponse: false,
ErrorCode: 0x41,
AckData: []byte{0x00, 0x00, 0x00, 0x00},
})
}
type RaviUpdate struct {
Op uint8
Dest uint8
Data uint32
}
func handleMsgSysOperateRegister(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysOperateRegister)
var raviUpdates []RaviUpdate
var raviUpdate RaviUpdate
// Strip null terminator
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload[:len(pkt.RawDataPayload)-1])
for i := len(pkt.RawDataPayload) / 6; i > 0; i-- {
raviUpdate.Op = bf.ReadUint8()
raviUpdate.Dest = bf.ReadUint8()
raviUpdate.Data = bf.ReadUint32()
raviUpdates = append(raviUpdates, raviUpdate)
}
bf = byteframe.NewByteFrame()
var _old, _new uint32
s.server.raviente.Lock()
for _, update := range raviUpdates {
switch update.Op {
case 2:
_old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, true)
case 13, 14:
_old, _new = s.server.UpdateRavi(pkt.SemaphoreID, update.Dest, update.Data, false)
}
bf.WriteUint8(1)
bf.WriteUint8(update.Dest)
bf.WriteUint32(_old)
bf.WriteUint32(_new)
}
s.server.raviente.Unlock()
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
s.notifyRavi()
}
}
func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLoadRegister)
bf := byteframe.NewByteFrame()
bf.WriteUint8(0)
bf.WriteUint8(pkt.Values)
for i := uint8(0); i < pkt.Values; i++ {
switch pkt.RegisterID {
case 0x40000:
bf.WriteUint32(s.server.raviente.state[i])
case 0x50000:
bf.WriteUint32(s.server.raviente.support[i])
case 0x60000:
bf.WriteUint32(s.server.raviente.register[i])
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func (s *Session) notifyRavi() {
sema := s.server.getRaviSemaphore()
if sema == nil {
return
}
var temp mhfpacket.MHFPacket
raviNotif := byteframe.NewByteFrame()
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x40000}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x50000}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x60000}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
raviNotif.WriteUint16(0x0010) // End it.
if s.server.erupeConfig.GameplayOptions.LowLatencyRaviente {
for session := range sema.clients {
session.QueueSend(raviNotif.Data())
}
} else {
for session := range sema.clients {
if session.charID == s.charID {
session.QueueSend(raviNotif.Data())
}
}
}
}
func (s *Server) getRaviSemaphore() *Semaphore {
for _, semaphore := range s.semaphore {
if strings.HasPrefix(semaphore.name, "hs_l0") && strings.HasSuffix(semaphore.name, "3") {
return semaphore
}
}
return nil
}
func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,188 @@
package channelserver
import (
ps "erupe-ce/common/pascalstring"
"fmt"
"github.com/jmoiron/sqlx"
"os"
"path/filepath"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) {
// saved every floor on road, holds values such as floors progressed, points etc.
// can be safely handled by the client
pkt := p.(*mhfpacket.MsgMhfSaveRengokuData)
dumpSaveData(s, pkt.RawDataPayload, "rengoku")
_, err := s.server.db.Exec("UPDATE characters SET rengokudata=$1 WHERE id=$2", pkt.RawDataPayload, s.charID)
if err != nil {
s.logger.Error("Failed to save rengokudata", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
bf.Seek(71, 0)
maxStageMp := bf.ReadUint32()
maxScoreMp := bf.ReadUint32()
bf.Seek(4, 1)
maxStageSp := bf.ReadUint32()
maxScoreSp := bf.ReadUint32()
var t int
err = s.server.db.QueryRow("SELECT character_id FROM rengoku_score WHERE character_id=$1", s.charID).Scan(&t)
if err != nil {
s.server.db.Exec("INSERT INTO rengoku_score (character_id) VALUES ($1)", s.charID)
}
s.server.db.Exec("UPDATE rengoku_score SET max_stages_mp=$1, max_points_mp=$2, max_stages_sp=$3, max_points_sp=$4 WHERE character_id=$5", maxStageMp, maxScoreMp, maxStageSp, maxScoreSp, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfLoadRengokuData(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfLoadRengokuData)
var data []byte
err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id = $1", s.charID).Scan(&data)
if err != nil {
s.logger.Error("Failed to load rengokudata", zap.Error(err))
}
if len(data) > 0 {
doAckBufSucceed(s, pkt.AckHandle, data)
} else {
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint16(0)
resp.WriteUint32(0)
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint32(0)
resp.WriteUint32(0) // an extra 4 bytes were missing based on pcaps
resp.WriteUint8(3) // Count of next 3
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint8(3) // Count of next 3
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint8(3) // Count of next 3
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
resp.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
}
func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRengokuBinary)
// a (massively out of date) version resides in the game's /dat/ folder or up to date can be pulled from packets
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin"))
if err != nil {
panic(err)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}
const rengokuScoreQuery = `, c.name FROM rengoku_score rs
LEFT JOIN characters c ON c.id = rs.character_id
LEFT JOIN guild_characters gc ON gc.character_id = rs.character_id `
type RengokuScore struct {
Name string `db:"name"`
Score uint32 `db:"score"`
}
func handleMsgMhfEnumerateRengokuRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateRengokuRanking)
guild, _ := GetGuildInfoByCharacterId(s, s.charID)
isApplicant, _ := guild.HasApplicationForCharID(s, s.charID)
if isApplicant {
guild = nil
}
if pkt.Leaderboard == 2 || pkt.Leaderboard == 3 || pkt.Leaderboard == 6 || pkt.Leaderboard == 7 {
if guild == nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 11))
return
}
}
var score RengokuScore
var selfExist bool
i := uint32(1)
bf := byteframe.NewByteFrame()
scoreData := byteframe.NewByteFrame()
var rows *sqlx.Rows
switch pkt.Leaderboard {
case 0:
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_mp AS score %s ORDER BY max_stages_mp DESC", rengokuScoreQuery))
case 1:
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_mp AS score %s ORDER BY max_points_mp DESC", rengokuScoreQuery))
case 2:
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_mp AS score %s WHERE guild_id=$1 ORDER BY max_stages_mp DESC", rengokuScoreQuery), guild.ID)
case 3:
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_mp AS score %s WHERE guild_id=$1 ORDER BY max_points_mp DESC", rengokuScoreQuery), guild.ID)
case 4:
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_sp AS score %s ORDER BY max_stages_sp DESC", rengokuScoreQuery))
case 5:
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_sp AS score %s ORDER BY max_points_sp DESC", rengokuScoreQuery))
case 6:
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_stages_sp AS score %s WHERE guild_id=$1 ORDER BY max_stages_sp DESC", rengokuScoreQuery), guild.ID)
case 7:
rows, _ = s.server.db.Queryx(fmt.Sprintf("SELECT max_points_sp AS score %s WHERE guild_id=$1 ORDER BY max_points_sp DESC", rengokuScoreQuery), guild.ID)
}
for rows.Next() {
rows.StructScan(&score)
if score.Name == s.Name {
bf.WriteUint32(i)
bf.WriteUint32(score.Score)
ps.Uint8(bf, s.Name, true)
ps.Uint8(bf, "", false)
selfExist = true
}
if i > 100 {
i++
continue
}
scoreData.WriteUint32(i)
scoreData.WriteUint32(score.Score)
ps.Uint8(scoreData, score.Name, true)
ps.Uint8(scoreData, "", false)
i++
}
if !selfExist {
bf.WriteBytes(make([]byte, 10))
}
bf.WriteUint8(uint8(i) - 1)
bf.WriteBytes(scoreData.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetRengokuRankingRank(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRengokuRankingRank)
// What is this for?
bf := byteframe.NewByteFrame()
bf.WriteUint32(0) // Max stage overall MP rank
bf.WriteUint32(0) // Max RdP overall MP rank
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -0,0 +1,129 @@
package channelserver
import "erupe-ce/network/mhfpacket"
func handleMsgSysReserve188(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserve188)
// Left as raw bytes because I couldn't easily find the request or resp parser function in the binary.
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgSysReserve18B(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserve18B)
// Left as raw bytes because I couldn't easily find the request or resp parser function in the binary.
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x3C})
}
func handleMsgSysReserve55(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve56(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve57(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve01(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve02(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve03(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve04(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve05(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve06(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve07(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve0C(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve0D(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve0E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4A(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4B(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4C(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4D(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve4F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve5C(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve5E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve5F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve71(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve72(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve73(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve74(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve75(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve76(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve77(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve78(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve79(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve7A(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve7B(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve7C(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve7E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfReserve10F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve180(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve18E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve18F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve19E(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve19F(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A4(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A6(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A7(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A8(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1A9(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AA(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AB(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AC(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AD(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AE(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve1AF(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve19B(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve192(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve193(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysReserve194(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,45 @@
package channelserver
import (
"encoding/hex"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfGetAdditionalBeatReward(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetAdditionalBeatReward)
// Actual response in packet captures are all just giant batches of null bytes
// I'm assuming this is because it used to be tied to an actual event and
// they never bothered killing off the packet when they made it static
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x104))
}
func handleMsgMhfGetUdRankingRewardList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetUdRankingRewardList)
// Temporary canned response
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetRewardSong(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetRewardSong)
// Temporary canned response
data, _ := hex.DecodeString("0100001600000A5397DF00000000000000000000000000000000")
doAckBufSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfUseRewardSong(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAddRewardSongCount(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAcquireMonthlyReward(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireMonthlyReward)
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfAcceptReadReward(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,106 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfGetBreakSeibatuLevelReward(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBreakSeibatuLevelReward)
bf := byteframe.NewByteFrame()
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
type WeeklySeibatuRankingReward struct {
Unk0 int32
Unk1 int32
Unk2 uint32
Unk3 int32
Unk4 int32
Unk5 int32
}
func handleMsgMhfGetWeeklySeibatuRankingReward(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetWeeklySeibatuRankingReward)
var data []*byteframe.ByteFrame
weeklySeibatuRankingRewards := []WeeklySeibatuRankingReward{
{0, 0, 0, 0, 0, 0},
}
for _, reward := range weeklySeibatuRankingRewards {
bf := byteframe.NewByteFrame()
bf.WriteInt32(reward.Unk0)
bf.WriteInt32(reward.Unk1)
bf.WriteUint32(reward.Unk2)
bf.WriteInt32(reward.Unk3)
bf.WriteInt32(reward.Unk4)
bf.WriteInt32(reward.Unk5)
data = append(data, bf)
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfGetFixedSeibatuRankingTable(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetFixedSeibatuRankingTable)
bf := byteframe.NewByteFrame()
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteBytes(make([]byte, 32))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfReadBeatLevel(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadBeatLevel)
// This response is fixed and will never change on JP,
// but I've left it dynamic for possible other client differences.
resp := byteframe.NewByteFrame()
for i := 0; i < int(pkt.ValidIDCount); i++ {
resp.WriteUint32(pkt.IDs[i])
resp.WriteUint32(1)
resp.WriteUint32(1)
resp.WriteUint32(1)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfReadLastWeekBeatRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadLastWeekBeatRanking)
bf := byteframe.NewByteFrame()
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfUpdateBeatLevel(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateBeatLevel)
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfReadBeatLevelAllRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadBeatLevelAllRanking)
bf := byteframe.NewByteFrame()
bf.WriteUint32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
for i := 0; i < 100; i++ {
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteBytes(make([]byte, 32))
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfReadBeatLevelMyRanking(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReadBeatLevelMyRanking)
bf := byteframe.NewByteFrame()
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -0,0 +1,128 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"go.uber.org/zap"
"strconv"
"strings"
"erupe-ce/network/mhfpacket"
)
func removeSessionFromSemaphore(s *Session) {
s.server.semaphoreLock.Lock()
for _, semaphore := range s.server.semaphore {
if _, exists := semaphore.clients[s]; exists {
delete(semaphore.clients, s)
}
}
s.server.semaphoreLock.Unlock()
}
func handleMsgSysCreateSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateSemaphore)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x03, 0x00, 0x0d})
}
func destructEmptySemaphores(s *Session) {
s.server.semaphoreLock.Lock()
for id, sema := range s.server.semaphore {
if len(sema.clients) == 0 {
delete(s.server.semaphore, id)
if strings.HasPrefix(id, "hs_l0") {
s.server.resetRaviente()
}
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
}
}
s.server.semaphoreLock.Unlock()
}
func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysDeleteSemaphore)
destructEmptySemaphores(s)
s.server.semaphoreLock.Lock()
for id, sema := range s.server.semaphore {
if sema.id == pkt.SemaphoreID {
for session := range sema.clients {
if s == session {
delete(sema.clients, s)
}
}
if len(sema.clients) == 0 {
delete(s.server.semaphore, id)
if strings.HasPrefix(id, "hs_l0") {
s.server.resetRaviente()
}
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
}
}
}
s.server.semaphoreLock.Unlock()
}
func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateAcquireSemaphore)
SemaphoreID := pkt.SemaphoreID
newSemaphore, exists := s.server.semaphore[SemaphoreID]
if !exists {
s.server.semaphoreLock.Lock()
if strings.HasPrefix(SemaphoreID, "hs_l0") {
suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:])
s.server.semaphore[SemaphoreID] = &Semaphore{
name: pkt.SemaphoreID,
id: uint32((suffix + 1) * 0x10000),
clients: make(map[*Session]uint32),
maxPlayers: 127,
}
} else {
s.server.semaphore[SemaphoreID] = NewSemaphore(s.server, SemaphoreID, 1)
}
newSemaphore = s.server.semaphore[SemaphoreID]
s.server.semaphoreLock.Unlock()
}
newSemaphore.Lock()
defer newSemaphore.Unlock()
bf := byteframe.NewByteFrame()
if _, exists := newSemaphore.clients[s]; exists {
bf.WriteUint32(newSemaphore.id)
} else if uint16(len(newSemaphore.clients)) < newSemaphore.maxPlayers {
newSemaphore.clients[s] = s.charID
s.Lock()
s.semaphore = newSemaphore
s.Unlock()
bf.WriteUint32(newSemaphore.id)
} else {
bf.WriteUint32(0)
}
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysAcquireSemaphore)
if sema, exists := s.server.semaphore[pkt.SemaphoreID]; exists {
sema.clients[s] = s.charID
bf := byteframe.NewByteFrame()
bf.WriteUint32(sema.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) {
//pkt := p.(*mhfpacket.MsgSysReleaseSemaphore)
}
func handleMsgSysCheckSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCheckSemaphore)
resp := []byte{0x00, 0x00, 0x00, 0x00}
s.server.semaphoreLock.Lock()
if _, exists := s.server.semaphore[pkt.SemaphoreID]; exists {
resp = []byte{0x00, 0x00, 0x00, 0x01}
}
s.server.semaphoreLock.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, resp)
}

View File

@@ -0,0 +1,690 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
_config "erupe-ce/config"
"erupe-ce/network/mhfpacket"
"math/rand"
)
type ShopItem struct {
ID uint32 `db:"id"`
ItemID uint16 `db:"item_id"`
Cost uint32 `db:"cost"`
Quantity uint16 `db:"quantity"`
MinHR uint16 `db:"min_hr"`
MinSR uint16 `db:"min_sr"`
MinGR uint16 `db:"min_gr"`
StoreLevel uint16 `db:"store_level"`
MaxQuantity uint16 `db:"max_quantity"`
UsedQuantity uint16 `db:"used_quantity"`
RoadFloors uint16 `db:"road_floors"`
RoadFatalis uint16 `db:"road_fatalis"`
}
type Gacha struct {
ID uint32 `db:"id"`
MinGR uint32 `db:"min_gr"`
MinHR uint32 `db:"min_hr"`
Name string `db:"name"`
URLBanner string `db:"url_banner"`
URLFeature string `db:"url_feature"`
URLThumbnail string `db:"url_thumbnail"`
Wide bool `db:"wide"`
Recommended bool `db:"recommended"`
GachaType uint8 `db:"gacha_type"`
Hidden bool `db:"hidden"`
}
type GachaEntry struct {
EntryType uint8 `db:"entry_type"`
ID uint32 `db:"id"`
ItemType uint8 `db:"item_type"`
ItemNumber uint32 `db:"item_number"`
ItemQuantity uint16 `db:"item_quantity"`
Weight float64 `db:"weight"`
Rarity uint8 `db:"rarity"`
Rolls uint8 `db:"rolls"`
FrontierPoints uint16 `db:"frontier_points"`
DailyLimit uint8 `db:"daily_limit"`
Name string `db:"name"`
}
type GachaItem struct {
ItemType uint8 `db:"item_type"`
ItemID uint16 `db:"item_id"`
Quantity uint16 `db:"quantity"`
}
func writeShopItems(bf *byteframe.ByteFrame, items []ShopItem) {
bf.WriteUint16(uint16(len(items)))
bf.WriteUint16(uint16(len(items)))
for _, item := range items {
bf.WriteUint32(item.ID)
bf.WriteUint16(0)
bf.WriteUint16(item.ItemID)
bf.WriteUint32(item.Cost)
bf.WriteUint16(item.Quantity)
bf.WriteUint16(item.MinHR)
bf.WriteUint16(item.MinSR)
bf.WriteUint16(item.MinGR)
bf.WriteUint16(item.StoreLevel)
bf.WriteUint16(item.MaxQuantity)
bf.WriteUint16(item.UsedQuantity)
bf.WriteUint16(item.RoadFloors)
bf.WriteUint16(item.RoadFatalis)
}
}
func getShopItems(s *Session, shopType uint8, shopID uint32) []ShopItem {
var items []ShopItem
var temp ShopItem
rows, err := s.server.db.Queryx(`SELECT id, item_id, cost, quantity, min_hr, min_sr, min_gr, store_level, max_quantity,
COALESCE((SELECT bought FROM shop_items_bought WHERE shop_item_id=si.id AND character_id=$3), 0) as used_quantity,
road_floors, road_fatalis FROM shop_items si WHERE shop_type=$1 AND shop_id=$2
`, shopType, shopID, s.charID)
if err == nil {
for rows.Next() {
err = rows.StructScan(&temp)
if err != nil {
continue
}
items = append(items, temp)
}
}
return items
}
func handleMsgMhfEnumerateShop(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateShop)
// Generic Shop IDs
// 0: basic item
// 1: gatherables
// 2: hr1-4 materials
// 3: hr5-7 materials
// 4: decos
// 5: other item
// 6: g mats
// 7: limited item
// 8: special item
switch pkt.ShopType {
case 1: // Running gachas
// Fundamentally, gacha works completely differently, just hide it for now.
if _config.ErupeConfig.RealClientMode <= _config.G7 {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
var count uint16
shopEntries, err := s.server.db.Queryx("SELECT id, min_gr, min_hr, name, url_banner, url_feature, url_thumbnail, wide, recommended, gacha_type, hidden FROM gacha_shop")
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
var gacha Gacha
for shopEntries.Next() {
err = shopEntries.StructScan(&gacha)
if err != nil {
continue
}
resp.WriteUint32(gacha.ID)
resp.WriteBytes(make([]byte, 16)) // Rank restriction
resp.WriteUint32(gacha.MinGR)
resp.WriteUint32(gacha.MinHR)
resp.WriteUint32(0) // only 0 in known packet
ps.Uint8(resp, gacha.Name, true)
ps.Uint8(resp, gacha.URLBanner, false)
ps.Uint8(resp, gacha.URLFeature, false)
resp.WriteBool(gacha.Wide)
ps.Uint8(resp, gacha.URLThumbnail, false)
resp.WriteUint8(0) // Unk
if gacha.Recommended {
resp.WriteUint8(2)
} else {
resp.WriteUint8(0)
}
resp.WriteUint8(gacha.GachaType)
resp.WriteBool(gacha.Hidden)
count++
}
resp.Seek(0, 0)
resp.WriteUint16(count)
resp.WriteUint16(count)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 2: // Actual gacha
bf := byteframe.NewByteFrame()
bf.WriteUint32(pkt.ShopID)
var gachaType int
s.server.db.QueryRow(`SELECT gacha_type FROM gacha_shop WHERE id = $1`, pkt.ShopID).Scan(&gachaType)
entries, err := s.server.db.Queryx(`SELECT entry_type, id, item_type, item_number, item_quantity, weight, rarity, rolls, daily_limit, frontier_points, name FROM gacha_entries WHERE gacha_id = $1 ORDER BY weight DESC`, pkt.ShopID)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
var divisor float64
s.server.db.QueryRow(`SELECT COALESCE(SUM(weight) / 100000.0, 0) AS chance FROM gacha_entries WHERE gacha_id = $1`, pkt.ShopID).Scan(&divisor)
var entryCount uint16
bf.WriteUint16(0)
gachaEntry := GachaEntry{}
gachaItem := GachaItem{}
for entries.Next() {
entryCount++
entries.StructScan(&gachaEntry)
bf.WriteUint8(gachaEntry.EntryType)
bf.WriteUint32(gachaEntry.ID)
bf.WriteUint8(gachaEntry.ItemType)
bf.WriteUint32(gachaEntry.ItemNumber)
bf.WriteUint16(gachaEntry.ItemQuantity)
if gachaType >= 4 { // If box
bf.WriteUint16(1)
} else {
bf.WriteUint16(uint16(gachaEntry.Weight / divisor))
}
bf.WriteUint8(gachaEntry.Rarity)
bf.WriteUint8(gachaEntry.Rolls)
var itemCount uint8
temp := byteframe.NewByteFrame()
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id=$1`, gachaEntry.ID)
if err != nil {
bf.WriteUint8(0)
} else {
for items.Next() {
itemCount++
items.StructScan(&gachaItem)
temp.WriteUint16(uint16(gachaItem.ItemType))
temp.WriteUint16(gachaItem.ItemID)
temp.WriteUint16(gachaItem.Quantity)
}
bf.WriteUint8(itemCount)
}
bf.WriteUint16(gachaEntry.FrontierPoints)
bf.WriteUint8(gachaEntry.DailyLimit)
if gachaEntry.EntryType < 10 {
ps.Uint8(bf, gachaEntry.Name, true)
} else {
bf.WriteUint8(0)
}
bf.WriteBytes(temp.Data())
}
bf.Seek(4, 0)
bf.WriteUint16(entryCount)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
case 3: // Hunting Festival Exchange
fallthrough
case 4: // N Points, 0-6
fallthrough
case 5: // GCP->Item, 0-6
fallthrough
case 6: // Gacha coin->Item
fallthrough
case 7: // Item->GCP
fallthrough
case 8: // Diva
fallthrough
case 9: // Diva song shop
fallthrough
case 10: // Item shop, 0-8
bf := byteframe.NewByteFrame()
items := getShopItems(s, pkt.ShopType, pkt.ShopID)
writeShopItems(bf, items)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
}
func handleMsgMhfAcquireExchangeShop(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireExchangeShop)
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
exchanges := int(bf.ReadUint16())
for i := 0; i < exchanges; i++ {
itemHash := bf.ReadUint32()
if itemHash == 0 {
continue
}
buyCount := bf.ReadUint32()
s.server.db.Exec(`INSERT INTO shop_items_bought (character_id, shop_item_id, bought)
VALUES ($1,$2,$3) ON CONFLICT (character_id, shop_item_id)
DO UPDATE SET bought = bought + $3
WHERE EXCLUDED.character_id=$1 AND EXCLUDED.shop_item_id=$2
`, s.charID, itemHash, buyCount)
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgMhfGetGachaPlayHistory(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGachaPlayHistory)
bf := byteframe.NewByteFrame()
bf.WriteUint8(1)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetGachaPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGachaPoint)
var fp, gp, gt uint32
s.server.db.QueryRow("SELECT COALESCE(frontier_points, 0), COALESCE(gacha_premium, 0), COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)", s.charID).Scan(&fp, &gp, &gt)
resp := byteframe.NewByteFrame()
resp.WriteUint32(gp)
resp.WriteUint32(gt)
resp.WriteUint32(fp)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfUseGachaPoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUseGachaPoint)
if pkt.TrialCoins > 0 {
s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.TrialCoins, s.charID)
}
if pkt.PremiumCoins > 0 {
s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, pkt.PremiumCoins, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func spendGachaCoin(s *Session, quantity uint16) {
var gt uint16
s.server.db.QueryRow(`SELECT COALESCE(gacha_trial, 0) FROM users u WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)`, s.charID).Scan(&gt)
if quantity <= gt {
s.server.db.Exec(`UPDATE users u SET gacha_trial=gacha_trial-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID)
} else {
s.server.db.Exec(`UPDATE users u SET gacha_premium=gacha_premium-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)`, quantity, s.charID)
}
}
func transactGacha(s *Session, gachaID uint32, rollID uint8) (error, int) {
var itemType uint8
var itemNumber uint16
var rolls int
err := s.server.db.QueryRowx(`SELECT item_type, item_number, rolls FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, gachaID, rollID).Scan(&itemType, &itemNumber, &rolls)
if err != nil {
return err, 0
}
switch itemType {
/*
valid types that need manual savedata manipulation:
- Ryoudan Points
- Bond Points
- Image Change Points
valid types that work (no additional code needed):
- Tore Points
- Festa Points
*/
case 17:
_ = addPointNetcafe(s, int(itemNumber)*-1)
case 19:
fallthrough
case 20:
spendGachaCoin(s, itemNumber)
case 21:
s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points-$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2)", itemNumber, s.charID)
}
return nil, rolls
}
func getGuaranteedItems(s *Session, gachaID uint32, rollID uint8) []GachaItem {
var rewards []GachaItem
var reward GachaItem
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = (SELECT id FROM gacha_entries WHERE entry_type = $1 AND gacha_id = $2)`, rollID, gachaID)
if err == nil {
for items.Next() {
items.StructScan(&reward)
rewards = append(rewards, reward)
}
}
return rewards
}
func addGachaItem(s *Session, items []GachaItem) {
var data []byte
s.server.db.QueryRow(`SELECT gacha_items FROM characters WHERE id = $1`, s.charID).Scan(&data)
if len(data) > 0 {
numItems := int(data[0])
data = data[1:]
oldItem := byteframe.NewByteFrameFromBytes(data)
for i := 0; i < numItems; i++ {
items = append(items, GachaItem{
ItemType: oldItem.ReadUint8(),
ItemID: oldItem.ReadUint16(),
Quantity: oldItem.ReadUint16(),
})
}
}
newItem := byteframe.NewByteFrame()
newItem.WriteUint8(uint8(len(items)))
for i := range items {
newItem.WriteUint8(items[i].ItemType)
newItem.WriteUint16(items[i].ItemID)
newItem.WriteUint16(items[i].Quantity)
}
s.server.db.Exec(`UPDATE characters SET gacha_items = $1 WHERE id = $2`, newItem.Data(), s.charID)
}
func getRandomEntries(entries []GachaEntry, rolls int, isBox bool) ([]GachaEntry, error) {
var chosen []GachaEntry
var totalWeight float64
for i := range entries {
totalWeight += entries[i].Weight
}
for {
if rolls == len(chosen) {
break
}
if !isBox {
result := rand.Float64() * totalWeight
for _, entry := range entries {
result -= entry.Weight
if result < 0 {
chosen = append(chosen, entry)
break
}
}
} else {
result := rand.Intn(len(entries))
chosen = append(chosen, entries[result])
entries[result] = entries[len(entries)-1]
entries = entries[:len(entries)-1]
}
}
return chosen, nil
}
func handleMsgMhfReceiveGachaItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfReceiveGachaItem)
var data []byte
err := s.server.db.QueryRow("SELECT COALESCE(gacha_items, $2) FROM characters WHERE id = $1", s.charID, []byte{0x00}).Scan(&data)
if err != nil {
data = []byte{0x00}
}
// I think there are still some edge cases where rewards can be nulled via overflow
if data[0] > 36 || len(data) > 181 {
resp := byteframe.NewByteFrame()
resp.WriteUint8(36)
resp.WriteBytes(data[1:181])
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
} else {
doAckBufSucceed(s, pkt.AckHandle, data)
}
if !pkt.Freeze {
if data[0] > 36 || len(data) > 181 {
update := byteframe.NewByteFrame()
update.WriteUint8(uint8(len(data[181:]) / 5))
update.WriteBytes(data[181:])
s.server.db.Exec("UPDATE characters SET gacha_items = $1 WHERE id = $2", update.Data(), s.charID)
} else {
s.server.db.Exec("UPDATE characters SET gacha_items = null WHERE id = $1", s.charID)
}
}
}
func handleMsgMhfPlayNormalGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayNormalGacha)
bf := byteframe.NewByteFrame()
var gachaEntries []GachaEntry
var entry GachaEntry
var rewards []GachaItem
var reward GachaItem
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
temp := byteframe.NewByteFrame()
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
for entries.Next() {
entries.StructScan(&entry)
gachaEntries = append(gachaEntries, entry)
}
rewardEntries, err := getRandomEntries(gachaEntries, rolls, false)
for i := range rewardEntries {
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
if err != nil {
continue
}
for items.Next() {
items.StructScan(&reward)
rewards = append(rewards, reward)
temp.WriteUint8(reward.ItemType)
temp.WriteUint16(reward.ItemID)
temp.WriteUint16(reward.Quantity)
temp.WriteUint8(entry.Rarity)
}
}
bf.WriteUint8(uint8(len(rewards)))
bf.WriteBytes(temp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
addGachaItem(s, rewards)
}
func handleMsgMhfPlayStepupGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayStepupGacha)
bf := byteframe.NewByteFrame()
var gachaEntries []GachaEntry
var entry GachaEntry
var rewards []GachaItem
var reward GachaItem
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
s.server.db.Exec("UPDATE users u SET frontier_points=frontier_points+(SELECT frontier_points FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.GachaID, pkt.RollType, s.charID)
s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
s.server.db.Exec(`INSERT INTO gacha_stepup (gacha_id, step, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, pkt.RollType+1, s.charID)
temp := byteframe.NewByteFrame()
guaranteedItems := getGuaranteedItems(s, pkt.GachaID, pkt.RollType)
for _, item := range guaranteedItems {
temp.WriteUint8(item.ItemType)
temp.WriteUint16(item.ItemID)
temp.WriteUint16(item.Quantity)
temp.WriteUint8(0)
}
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
for entries.Next() {
entries.StructScan(&entry)
gachaEntries = append(gachaEntries, entry)
}
rewardEntries, err := getRandomEntries(gachaEntries, rolls, false)
for i := range rewardEntries {
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
if err != nil {
continue
}
for items.Next() {
items.StructScan(&reward)
rewards = append(rewards, reward)
temp.WriteUint8(reward.ItemType)
temp.WriteUint16(reward.ItemID)
temp.WriteUint16(reward.Quantity)
temp.WriteUint8(entry.Rarity)
}
}
bf.WriteUint8(uint8(len(rewards) + len(guaranteedItems)))
bf.WriteUint8(uint8(len(rewards)))
bf.WriteBytes(temp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
addGachaItem(s, rewards)
addGachaItem(s, guaranteedItems)
}
func handleMsgMhfGetStepupStatus(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetStepupStatus)
// TODO: Reset daily (noon)
var step uint8
s.server.db.QueryRow(`SELECT step FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID).Scan(&step)
var stepCheck int
s.server.db.QueryRow(`SELECT COUNT(1) FROM gacha_entries WHERE gacha_id = $1 AND entry_type = $2`, pkt.GachaID, step).Scan(&stepCheck)
if stepCheck == 0 {
s.server.db.Exec(`DELETE FROM gacha_stepup WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
step = 0
}
bf := byteframe.NewByteFrame()
bf.WriteUint8(step)
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetBoxGachaInfo)
entries, err := s.server.db.Queryx(`SELECT entry_id FROM gacha_box WHERE gacha_id = $1 AND character_id = $2`, pkt.GachaID, s.charID)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
var entryIDs []uint32
for entries.Next() {
var entryID uint32
entries.Scan(&entryID)
entryIDs = append(entryIDs, entryID)
}
bf := byteframe.NewByteFrame()
bf.WriteUint8(uint8(len(entryIDs)))
for i := range entryIDs {
bf.WriteUint32(entryIDs[i])
bf.WriteBool(true)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfPlayBoxGacha(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPlayBoxGacha)
bf := byteframe.NewByteFrame()
var gachaEntries []GachaEntry
var entry GachaEntry
var rewards []GachaItem
var reward GachaItem
err, rolls := transactGacha(s, pkt.GachaID, pkt.RollType)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
temp := byteframe.NewByteFrame()
entries, err := s.server.db.Queryx(`SELECT id, weight, rarity FROM gacha_entries WHERE gacha_id = $1 AND entry_type = 100 ORDER BY weight DESC`, pkt.GachaID)
if err != nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
for entries.Next() {
entries.StructScan(&entry)
gachaEntries = append(gachaEntries, entry)
}
rewardEntries, err := getRandomEntries(gachaEntries, rolls, true)
for i := range rewardEntries {
items, err := s.server.db.Queryx(`SELECT item_type, item_id, quantity FROM gacha_items WHERE entry_id = $1`, rewardEntries[i].ID)
if err != nil {
continue
}
s.server.db.Exec(`INSERT INTO gacha_box (gacha_id, entry_id, character_id) VALUES ($1, $2, $3)`, pkt.GachaID, rewardEntries[i].ID, s.charID)
for items.Next() {
items.StructScan(&reward)
rewards = append(rewards, reward)
temp.WriteUint8(reward.ItemType)
temp.WriteUint16(reward.ItemID)
temp.WriteUint16(reward.Quantity)
temp.WriteUint8(0)
}
}
bf.WriteUint8(uint8(len(rewards)))
bf.WriteBytes(temp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
addGachaItem(s, rewards)
}
func handleMsgMhfResetBoxGachaInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfResetBoxGachaInfo)
s.server.db.Exec("DELETE FROM gacha_box WHERE gacha_id = $1 AND character_id = $2", pkt.GachaID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfExchangeFpoint2Item(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeFpoint2Item)
var balance uint32
var itemValue, quantity int
s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
cost := (int(pkt.Quantity) * quantity) * itemValue
s.server.db.QueryRow("UPDATE users u SET frontier_points=frontier_points::int - $1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
bf := byteframe.NewByteFrame()
bf.WriteUint32(balance)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfExchangeItem2Fpoint(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfExchangeItem2Fpoint)
var balance uint32
var itemValue, quantity int
s.server.db.QueryRow("SELECT quantity, fpoints FROM fpoint_items WHERE id=$1", pkt.TradeID).Scan(&quantity, &itemValue)
cost := (int(pkt.Quantity) / quantity) * itemValue
s.server.db.QueryRow("UPDATE users u SET frontier_points=COALESCE(frontier_points::int + $1, $1) WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$2) RETURNING frontier_points", cost, s.charID).Scan(&balance)
bf := byteframe.NewByteFrame()
bf.WriteUint32(balance)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetFpointExchangeList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetFpointExchangeList)
resp := byteframe.NewByteFrame()
resp.WriteUint32(0)
var buyables, sellables uint16
var id uint32
var itemType uint8
var itemID, quantity, fPoints uint16
buyRows, err := s.server.db.Query("SELECT id,item_type,item_id,quantity,fpoints FROM fpoint_items WHERE trade_type=0")
if err == nil {
for buyRows.Next() {
err = buyRows.Scan(&id, &itemType, &itemID, &quantity, &fPoints)
if err != nil {
continue
}
resp.WriteUint32(id)
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint8(itemType)
resp.WriteUint16(itemID)
resp.WriteUint16(quantity)
resp.WriteUint16(fPoints)
buyables++
}
}
sellRows, err := s.server.db.Query("SELECT id,item_type,item_id,quantity,fpoints FROM fpoint_items WHERE trade_type=1")
if err == nil {
for sellRows.Next() {
err = sellRows.Scan(&id, &itemType, &itemID, &quantity, &fPoints)
if err != nil {
continue
}
resp.WriteUint32(id)
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint16(0)
resp.WriteUint8(itemType)
resp.WriteUint16(itemID)
resp.WriteUint16(quantity)
resp.WriteUint16(fPoints)
sellables++
}
}
resp.Seek(0, 0)
resp.WriteUint16(buyables)
resp.WriteUint16(sellables)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
func handleMsgMhfPlayFreeGacha(s *Session, p mhfpacket.MHFPacket) {
// not sure this is used anywhere, free gachas use the MSG_MHF_PLAY_NORMAL_GACHA method in captures
}

View File

@@ -0,0 +1,405 @@
package channelserver
import (
"fmt"
"strings"
"time"
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgSysCreateStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysCreateStage)
s.server.Lock()
defer s.server.Unlock()
if _, exists := s.server.stages[pkt.StageID]; exists {
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
} else {
stage := NewStage(pkt.StageID)
stage.host = s
stage.maxPlayers = uint16(pkt.PlayerCount)
s.server.stages[stage.id] = stage
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
}
func handleMsgSysStageDestruct(s *Session, p mhfpacket.MHFPacket) {}
func doStageTransfer(s *Session, ackHandle uint32, stageID string) {
s.server.Lock()
stage, exists := s.server.stages[stageID]
s.server.Unlock()
if exists {
stage.Lock()
stage.clients[s] = s.charID
stage.Unlock()
} else { // Create new stage object
s.server.Lock()
s.server.stages[stageID] = NewStage(stageID)
stage = s.server.stages[stageID]
s.server.Unlock()
stage.Lock()
stage.host = s
stage.clients[s] = s.charID
stage.Unlock()
}
// Ensure this session no longer belongs to reservations.
if s.stage != nil {
removeSessionFromStage(s)
}
// Save our new stage ID and pointer to the new stage itself.
s.Lock()
s.stageID = stageID
s.stage = s.server.stages[stageID]
s.Unlock()
// Tell the client to cleanup its current stage objects.
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
// Confirm the stage entry.
doAckSimpleSucceed(s, ackHandle, []byte{0x00, 0x00, 0x00, 0x00})
var temp mhfpacket.MHFPacket
newNotif := byteframe.NewByteFrame()
// Cast existing user data to new user
if !s.userEnteredStage {
s.userEnteredStage = true
for _, session := range s.server.sessions {
if s == session {
continue
}
temp = &mhfpacket.MsgSysInsertUser{CharID: session.charID}
newNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(newNotif, s.clientContext)
for i := 0; i < 3; i++ {
temp = &mhfpacket.MsgSysNotifyUserBinary{
CharID: session.charID,
BinaryType: uint8(i + 1),
}
newNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(newNotif, s.clientContext)
}
}
}
if s.stage != nil { // avoids lock up when using bed for dream quests
// Notify the client to duplicate the existing objects.
s.logger.Info(fmt.Sprintf("Sending existing stage objects to %s", s.Name))
s.stage.RLock()
var temp mhfpacket.MHFPacket
for _, obj := range s.stage.objects {
if obj.ownerCharID == s.charID {
continue
}
temp = &mhfpacket.MsgSysDuplicateObject{
ObjID: obj.id,
X: obj.x,
Y: obj.y,
Z: obj.z,
Unk0: 0,
OwnerCharID: obj.ownerCharID,
}
newNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(newNotif, s.clientContext)
}
s.stage.RUnlock()
}
newNotif.WriteUint16(0x0010) // End it.
if len(newNotif.Data()) > 2 {
s.QueueSend(newNotif.Data())
}
}
func destructEmptyStages(s *Session) {
s.server.Lock()
defer s.server.Unlock()
for _, stage := range s.server.stages {
// Destroy empty Quest/My series/Guild stages.
if stage.id[3:5] == "Qs" || stage.id[3:5] == "Ms" || stage.id[3:5] == "Gs" || stage.id[3:5] == "Ls" {
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
delete(s.server.stages, stage.id)
s.logger.Debug("Destructed stage", zap.String("stage.id", stage.id))
}
}
}
}
func removeSessionFromStage(s *Session) {
// Remove client from old stage.
delete(s.stage.clients, s)
// Delete old stage objects owned by the client.
s.logger.Info("Sending notification to old stage clients")
for _, object := range s.stage.objects {
if object.ownerCharID == s.charID {
s.stage.BroadcastMHF(&mhfpacket.MsgSysDeleteObject{ObjID: object.id}, s)
delete(s.stage.objects, object.ownerCharID)
}
}
destructEmptyStages(s)
destructEmptySemaphores(s)
}
func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysEnterStage)
// Push our current stage ID to the movement stack before entering another one.
if s.stageID == "" {
s.stageMoveStack.Set(pkt.StageID)
} else {
s.stage.Lock()
s.stage.reservedClientSlots[s.charID] = false
s.stage.Unlock()
s.stageMoveStack.Push(s.stageID)
s.stageMoveStack.Lock()
}
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
if s.reservationStage != nil {
s.reservationStage = nil
}
doStageTransfer(s, pkt.AckHandle, pkt.StageID)
}
func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysBackStage)
// Transfer back to the saved stage ID before the previous move or enter.
s.stageMoveStack.Unlock()
backStage, err := s.stageMoveStack.Pop()
if err != nil {
panic(err)
}
if _, exists := s.stage.reservedClientSlots[s.charID]; exists {
delete(s.stage.reservedClientSlots, s.charID)
}
if _, exists := s.server.stages[backStage].reservedClientSlots[s.charID]; exists {
delete(s.server.stages[backStage].reservedClientSlots, s.charID)
}
doStageTransfer(s, pkt.AckHandle, backStage)
}
func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysMoveStage)
// Set a new move stack from the given stage ID if unlocked
if !s.stageMoveStack.Locked {
s.stageMoveStack.Set(pkt.StageID)
}
doStageTransfer(s, pkt.AckHandle, pkt.StageID)
}
func handleMsgSysLeaveStage(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysLockStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLockStage)
// TODO(Andoryuuta): What does this packet _actually_ do?
// I think this is supposed to mark a stage as no longer able to accept client reservations
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}
func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
if s.reservationStage != nil {
s.reservationStage.RLock()
defer s.reservationStage.RUnlock()
for charID := range s.reservationStage.reservedClientSlots {
session := s.server.FindSessionByCharID(charID)
session.QueueSendMHF(&mhfpacket.MsgSysStageDestruct{})
}
delete(s.server.stages, s.reservationStage.id)
}
destructEmptyStages(s)
}
func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserveStage)
if stage, exists := s.server.stages[pkt.StageID]; exists {
stage.Lock()
defer stage.Unlock()
if _, exists := stage.reservedClientSlots[s.charID]; exists {
switch pkt.Ready {
case 1: // 0x01
stage.reservedClientSlots[s.charID] = false
case 17: // 0x11
stage.reservedClientSlots[s.charID] = true
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers {
if len(stage.password) > 0 {
if stage.password != s.stagePass {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
}
stage.reservedClientSlots[s.charID] = false
// Save the reservation stage in the session for later use in MsgSysUnreserveStage.
s.Lock()
s.reservationStage = stage
s.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
} else {
s.logger.Error("Failed to get stage", zap.String("StageID", pkt.StageID))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) {
s.Lock()
stage := s.reservationStage
s.reservationStage = nil
s.Unlock()
if stage != nil {
stage.Lock()
if _, exists := stage.reservedClientSlots[s.charID]; exists {
delete(stage.reservedClientSlots, s.charID)
}
stage.Unlock()
}
}
func handleMsgSysSetStagePass(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysSetStagePass)
s.Lock()
stage := s.reservationStage
s.Unlock()
if stage != nil {
stage.Lock()
// Will only exist if host.
if _, exists := stage.reservedClientSlots[s.charID]; exists {
stage.password = pkt.Password
}
stage.Unlock()
} else {
// Store for use on next ReserveStage.
s.Lock()
s.stagePass = pkt.Password
s.Unlock()
}
}
func handleMsgSysSetStageBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysSetStageBinary)
if stage, exists := s.server.stages[pkt.StageID]; exists {
stage.Lock()
stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}] = pkt.RawDataPayload
stage.Unlock()
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
}
}
func handleMsgSysGetStageBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysGetStageBinary)
if stage, exists := s.server.stages[pkt.StageID]; exists {
stage.Lock()
if binaryData, exists := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]; exists {
doAckBufSucceed(s, pkt.AckHandle, binaryData)
} else if pkt.BinaryType1 == 4 {
// Unknown binary type that is supposedly generated server side
// Temporary response
doAckBufSucceed(s, pkt.AckHandle, []byte{})
} else {
s.logger.Warn("Failed to get stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
s.logger.Warn("Sending blank stage binary")
doAckBufSucceed(s, pkt.AckHandle, []byte{})
}
stage.Unlock()
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
}
s.logger.Debug("MsgSysGetStageBinary Done!")
}
func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysWaitStageBinary)
if stage, exists := s.server.stages[pkt.StageID]; exists {
if pkt.BinaryType0 == 1 && pkt.BinaryType1 == 12 {
// This might contain the hunter count, or max player count?
doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return
}
for {
s.logger.Debug("MsgSysWaitStageBinary before lock and get stage")
stage.Lock()
stageBinary, gotBinary := stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}]
stage.Unlock()
s.logger.Debug("MsgSysWaitStageBinary after lock and get stage")
if gotBinary {
doAckBufSucceed(s, pkt.AckHandle, stageBinary)
break
} else {
s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
time.Sleep(1 * time.Second)
continue
}
}
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", pkt.StageID))
}
s.logger.Debug("MsgSysWaitStageBinary Done!")
}
func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysEnumerateStage)
// Read-lock the server stage map.
s.server.stagesLock.RLock()
defer s.server.stagesLock.RUnlock()
// Build the response
resp := byteframe.NewByteFrame()
bf := byteframe.NewByteFrame()
var joinable int
for sid, stage := range s.server.stages {
stage.RLock()
defer stage.RUnlock()
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
continue
}
if !strings.Contains(stage.id, pkt.StagePrefix) {
continue
}
joinable++
resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Reserved players.
resp.WriteUint16(0) // Unk
resp.WriteUint8(0) // Unk
resp.WriteBool(len(stage.clients) > 0) // Has departed.
resp.WriteUint16(stage.maxPlayers) // Max players.
if len(stage.password) > 0 {
// This byte has also been seen as 1
// The quest is also recognised as locked when this is 2
resp.WriteUint8(3)
} else {
resp.WriteUint8(0)
}
ps.Uint8(resp, sid, false)
}
bf.WriteUint16(uint16(joinable))
bf.WriteBytes(resp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -0,0 +1,446 @@
package channelserver
import (
"erupe-ce/network"
"erupe-ce/network/mhfpacket"
)
type handlerFunc func(s *Session, p mhfpacket.MHFPacket)
var handlerTable map[network.PacketID]handlerFunc
func init() {
handlerTable = make(map[network.PacketID]handlerFunc)
handlerTable[network.MSG_HEAD] = handleMsgHead
handlerTable[network.MSG_SYS_reserve01] = handleMsgSysReserve01
handlerTable[network.MSG_SYS_reserve02] = handleMsgSysReserve02
handlerTable[network.MSG_SYS_reserve03] = handleMsgSysReserve03
handlerTable[network.MSG_SYS_reserve04] = handleMsgSysReserve04
handlerTable[network.MSG_SYS_reserve05] = handleMsgSysReserve05
handlerTable[network.MSG_SYS_reserve06] = handleMsgSysReserve06
handlerTable[network.MSG_SYS_reserve07] = handleMsgSysReserve07
handlerTable[network.MSG_SYS_ADD_OBJECT] = handleMsgSysAddObject
handlerTable[network.MSG_SYS_DEL_OBJECT] = handleMsgSysDelObject
handlerTable[network.MSG_SYS_DISP_OBJECT] = handleMsgSysDispObject
handlerTable[network.MSG_SYS_HIDE_OBJECT] = handleMsgSysHideObject
handlerTable[network.MSG_SYS_reserve0C] = handleMsgSysReserve0C
handlerTable[network.MSG_SYS_reserve0D] = handleMsgSysReserve0D
handlerTable[network.MSG_SYS_reserve0E] = handleMsgSysReserve0E
handlerTable[network.MSG_SYS_EXTEND_THRESHOLD] = handleMsgSysExtendThreshold
handlerTable[network.MSG_SYS_END] = handleMsgSysEnd
handlerTable[network.MSG_SYS_NOP] = handleMsgSysNop
handlerTable[network.MSG_SYS_ACK] = handleMsgSysAck
handlerTable[network.MSG_SYS_TERMINAL_LOG] = handleMsgSysTerminalLog
handlerTable[network.MSG_SYS_LOGIN] = handleMsgSysLogin
handlerTable[network.MSG_SYS_LOGOUT] = handleMsgSysLogout
handlerTable[network.MSG_SYS_SET_STATUS] = handleMsgSysSetStatus
handlerTable[network.MSG_SYS_PING] = handleMsgSysPing
handlerTable[network.MSG_SYS_CAST_BINARY] = handleMsgSysCastBinary
handlerTable[network.MSG_SYS_HIDE_CLIENT] = handleMsgSysHideClient
handlerTable[network.MSG_SYS_TIME] = handleMsgSysTime
handlerTable[network.MSG_SYS_CASTED_BINARY] = handleMsgSysCastedBinary
handlerTable[network.MSG_SYS_GET_FILE] = handleMsgSysGetFile
handlerTable[network.MSG_SYS_ISSUE_LOGKEY] = handleMsgSysIssueLogkey
handlerTable[network.MSG_SYS_RECORD_LOG] = handleMsgSysRecordLog
handlerTable[network.MSG_SYS_ECHO] = handleMsgSysEcho
handlerTable[network.MSG_SYS_CREATE_STAGE] = handleMsgSysCreateStage
handlerTable[network.MSG_SYS_STAGE_DESTRUCT] = handleMsgSysStageDestruct
handlerTable[network.MSG_SYS_ENTER_STAGE] = handleMsgSysEnterStage
handlerTable[network.MSG_SYS_BACK_STAGE] = handleMsgSysBackStage
handlerTable[network.MSG_SYS_MOVE_STAGE] = handleMsgSysMoveStage
handlerTable[network.MSG_SYS_LEAVE_STAGE] = handleMsgSysLeaveStage
handlerTable[network.MSG_SYS_LOCK_STAGE] = handleMsgSysLockStage
handlerTable[network.MSG_SYS_UNLOCK_STAGE] = handleMsgSysUnlockStage
handlerTable[network.MSG_SYS_RESERVE_STAGE] = handleMsgSysReserveStage
handlerTable[network.MSG_SYS_UNRESERVE_STAGE] = handleMsgSysUnreserveStage
handlerTable[network.MSG_SYS_SET_STAGE_PASS] = handleMsgSysSetStagePass
handlerTable[network.MSG_SYS_WAIT_STAGE_BINARY] = handleMsgSysWaitStageBinary
handlerTable[network.MSG_SYS_SET_STAGE_BINARY] = handleMsgSysSetStageBinary
handlerTable[network.MSG_SYS_GET_STAGE_BINARY] = handleMsgSysGetStageBinary
handlerTable[network.MSG_SYS_ENUMERATE_CLIENT] = handleMsgSysEnumerateClient
handlerTable[network.MSG_SYS_ENUMERATE_STAGE] = handleMsgSysEnumerateStage
handlerTable[network.MSG_SYS_CREATE_MUTEX] = handleMsgSysCreateMutex
handlerTable[network.MSG_SYS_CREATE_OPEN_MUTEX] = handleMsgSysCreateOpenMutex
handlerTable[network.MSG_SYS_DELETE_MUTEX] = handleMsgSysDeleteMutex
handlerTable[network.MSG_SYS_OPEN_MUTEX] = handleMsgSysOpenMutex
handlerTable[network.MSG_SYS_CLOSE_MUTEX] = handleMsgSysCloseMutex
handlerTable[network.MSG_SYS_CREATE_SEMAPHORE] = handleMsgSysCreateSemaphore
handlerTable[network.MSG_SYS_CREATE_ACQUIRE_SEMAPHORE] = handleMsgSysCreateAcquireSemaphore
handlerTable[network.MSG_SYS_DELETE_SEMAPHORE] = handleMsgSysDeleteSemaphore
handlerTable[network.MSG_SYS_ACQUIRE_SEMAPHORE] = handleMsgSysAcquireSemaphore
handlerTable[network.MSG_SYS_RELEASE_SEMAPHORE] = handleMsgSysReleaseSemaphore
handlerTable[network.MSG_SYS_LOCK_GLOBAL_SEMA] = handleMsgSysLockGlobalSema
handlerTable[network.MSG_SYS_UNLOCK_GLOBAL_SEMA] = handleMsgSysUnlockGlobalSema
handlerTable[network.MSG_SYS_CHECK_SEMAPHORE] = handleMsgSysCheckSemaphore
handlerTable[network.MSG_SYS_OPERATE_REGISTER] = handleMsgSysOperateRegister
handlerTable[network.MSG_SYS_LOAD_REGISTER] = handleMsgSysLoadRegister
handlerTable[network.MSG_SYS_NOTIFY_REGISTER] = handleMsgSysNotifyRegister
handlerTable[network.MSG_SYS_CREATE_OBJECT] = handleMsgSysCreateObject
handlerTable[network.MSG_SYS_DELETE_OBJECT] = handleMsgSysDeleteObject
handlerTable[network.MSG_SYS_POSITION_OBJECT] = handleMsgSysPositionObject
handlerTable[network.MSG_SYS_ROTATE_OBJECT] = handleMsgSysRotateObject
handlerTable[network.MSG_SYS_DUPLICATE_OBJECT] = handleMsgSysDuplicateObject
handlerTable[network.MSG_SYS_SET_OBJECT_BINARY] = handleMsgSysSetObjectBinary
handlerTable[network.MSG_SYS_GET_OBJECT_BINARY] = handleMsgSysGetObjectBinary
handlerTable[network.MSG_SYS_GET_OBJECT_OWNER] = handleMsgSysGetObjectOwner
handlerTable[network.MSG_SYS_UPDATE_OBJECT_BINARY] = handleMsgSysUpdateObjectBinary
handlerTable[network.MSG_SYS_CLEANUP_OBJECT] = handleMsgSysCleanupObject
handlerTable[network.MSG_SYS_reserve4A] = handleMsgSysReserve4A
handlerTable[network.MSG_SYS_reserve4B] = handleMsgSysReserve4B
handlerTable[network.MSG_SYS_reserve4C] = handleMsgSysReserve4C
handlerTable[network.MSG_SYS_reserve4D] = handleMsgSysReserve4D
handlerTable[network.MSG_SYS_reserve4E] = handleMsgSysReserve4E
handlerTable[network.MSG_SYS_reserve4F] = handleMsgSysReserve4F
handlerTable[network.MSG_SYS_INSERT_USER] = handleMsgSysInsertUser
handlerTable[network.MSG_SYS_DELETE_USER] = handleMsgSysDeleteUser
handlerTable[network.MSG_SYS_SET_USER_BINARY] = handleMsgSysSetUserBinary
handlerTable[network.MSG_SYS_GET_USER_BINARY] = handleMsgSysGetUserBinary
handlerTable[network.MSG_SYS_NOTIFY_USER_BINARY] = handleMsgSysNotifyUserBinary
handlerTable[network.MSG_SYS_reserve55] = handleMsgSysReserve55
handlerTable[network.MSG_SYS_reserve56] = handleMsgSysReserve56
handlerTable[network.MSG_SYS_reserve57] = handleMsgSysReserve57
handlerTable[network.MSG_SYS_UPDATE_RIGHT] = handleMsgSysUpdateRight
handlerTable[network.MSG_SYS_AUTH_QUERY] = handleMsgSysAuthQuery
handlerTable[network.MSG_SYS_AUTH_DATA] = handleMsgSysAuthData
handlerTable[network.MSG_SYS_AUTH_TERMINAL] = handleMsgSysAuthTerminal
handlerTable[network.MSG_SYS_reserve5C] = handleMsgSysReserve5C
handlerTable[network.MSG_SYS_RIGHTS_RELOAD] = handleMsgSysRightsReload
handlerTable[network.MSG_SYS_reserve5E] = handleMsgSysReserve5E
handlerTable[network.MSG_SYS_reserve5F] = handleMsgSysReserve5F
handlerTable[network.MSG_MHF_SAVEDATA] = handleMsgMhfSavedata
handlerTable[network.MSG_MHF_LOADDATA] = handleMsgMhfLoaddata
handlerTable[network.MSG_MHF_LIST_MEMBER] = handleMsgMhfListMember
handlerTable[network.MSG_MHF_OPR_MEMBER] = handleMsgMhfOprMember
handlerTable[network.MSG_MHF_ENUMERATE_DIST_ITEM] = handleMsgMhfEnumerateDistItem
handlerTable[network.MSG_MHF_APPLY_DIST_ITEM] = handleMsgMhfApplyDistItem
handlerTable[network.MSG_MHF_ACQUIRE_DIST_ITEM] = handleMsgMhfAcquireDistItem
handlerTable[network.MSG_MHF_GET_DIST_DESCRIPTION] = handleMsgMhfGetDistDescription
handlerTable[network.MSG_MHF_SEND_MAIL] = handleMsgMhfSendMail
handlerTable[network.MSG_MHF_READ_MAIL] = handleMsgMhfReadMail
handlerTable[network.MSG_MHF_LIST_MAIL] = handleMsgMhfListMail
handlerTable[network.MSG_MHF_OPRT_MAIL] = handleMsgMhfOprtMail
handlerTable[network.MSG_MHF_LOAD_FAVORITE_QUEST] = handleMsgMhfLoadFavoriteQuest
handlerTable[network.MSG_MHF_SAVE_FAVORITE_QUEST] = handleMsgMhfSaveFavoriteQuest
handlerTable[network.MSG_MHF_REGISTER_EVENT] = handleMsgMhfRegisterEvent
handlerTable[network.MSG_MHF_RELEASE_EVENT] = handleMsgMhfReleaseEvent
handlerTable[network.MSG_MHF_TRANSIT_MESSAGE] = handleMsgMhfTransitMessage
handlerTable[network.MSG_SYS_reserve71] = handleMsgSysReserve71
handlerTable[network.MSG_SYS_reserve72] = handleMsgSysReserve72
handlerTable[network.MSG_SYS_reserve73] = handleMsgSysReserve73
handlerTable[network.MSG_SYS_reserve74] = handleMsgSysReserve74
handlerTable[network.MSG_SYS_reserve75] = handleMsgSysReserve75
handlerTable[network.MSG_SYS_reserve76] = handleMsgSysReserve76
handlerTable[network.MSG_SYS_reserve77] = handleMsgSysReserve77
handlerTable[network.MSG_SYS_reserve78] = handleMsgSysReserve78
handlerTable[network.MSG_SYS_reserve79] = handleMsgSysReserve79
handlerTable[network.MSG_SYS_reserve7A] = handleMsgSysReserve7A
handlerTable[network.MSG_SYS_reserve7B] = handleMsgSysReserve7B
handlerTable[network.MSG_SYS_reserve7C] = handleMsgSysReserve7C
handlerTable[network.MSG_CA_EXCHANGE_ITEM] = handleMsgCaExchangeItem
handlerTable[network.MSG_SYS_reserve7E] = handleMsgSysReserve7E
handlerTable[network.MSG_MHF_PRESENT_BOX] = handleMsgMhfPresentBox
handlerTable[network.MSG_MHF_SERVER_COMMAND] = handleMsgMhfServerCommand
handlerTable[network.MSG_MHF_SHUT_CLIENT] = handleMsgMhfShutClient
handlerTable[network.MSG_MHF_ANNOUNCE] = handleMsgMhfAnnounce
handlerTable[network.MSG_MHF_SET_LOGINWINDOW] = handleMsgMhfSetLoginwindow
handlerTable[network.MSG_SYS_TRANS_BINARY] = handleMsgSysTransBinary
handlerTable[network.MSG_SYS_COLLECT_BINARY] = handleMsgSysCollectBinary
handlerTable[network.MSG_SYS_GET_STATE] = handleMsgSysGetState
handlerTable[network.MSG_SYS_SERIALIZE] = handleMsgSysSerialize
handlerTable[network.MSG_SYS_ENUMLOBBY] = handleMsgSysEnumlobby
handlerTable[network.MSG_SYS_ENUMUSER] = handleMsgSysEnumuser
handlerTable[network.MSG_SYS_INFOKYSERVER] = handleMsgSysInfokyserver
handlerTable[network.MSG_MHF_GET_CA_UNIQUE_ID] = handleMsgMhfGetCaUniqueID
handlerTable[network.MSG_MHF_SET_CA_ACHIEVEMENT] = handleMsgMhfSetCaAchievement
handlerTable[network.MSG_MHF_CARAVAN_MY_SCORE] = handleMsgMhfCaravanMyScore
handlerTable[network.MSG_MHF_CARAVAN_RANKING] = handleMsgMhfCaravanRanking
handlerTable[network.MSG_MHF_CARAVAN_MY_RANK] = handleMsgMhfCaravanMyRank
handlerTable[network.MSG_MHF_CREATE_GUILD] = handleMsgMhfCreateGuild
handlerTable[network.MSG_MHF_OPERATE_GUILD] = handleMsgMhfOperateGuild
handlerTable[network.MSG_MHF_OPERATE_GUILD_MEMBER] = handleMsgMhfOperateGuildMember
handlerTable[network.MSG_MHF_INFO_GUILD] = handleMsgMhfInfoGuild
handlerTable[network.MSG_MHF_ENUMERATE_GUILD] = handleMsgMhfEnumerateGuild
handlerTable[network.MSG_MHF_UPDATE_GUILD] = handleMsgMhfUpdateGuild
handlerTable[network.MSG_MHF_ARRANGE_GUILD_MEMBER] = handleMsgMhfArrangeGuildMember
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_MEMBER] = handleMsgMhfEnumerateGuildMember
handlerTable[network.MSG_MHF_ENUMERATE_CAMPAIGN] = handleMsgMhfEnumerateCampaign
handlerTable[network.MSG_MHF_STATE_CAMPAIGN] = handleMsgMhfStateCampaign
handlerTable[network.MSG_MHF_APPLY_CAMPAIGN] = handleMsgMhfApplyCampaign
handlerTable[network.MSG_MHF_ENUMERATE_ITEM] = handleMsgMhfEnumerateItem
handlerTable[network.MSG_MHF_ACQUIRE_ITEM] = handleMsgMhfAcquireItem
handlerTable[network.MSG_MHF_TRANSFER_ITEM] = handleMsgMhfTransferItem
handlerTable[network.MSG_MHF_MERCENARY_HUNTDATA] = handleMsgMhfMercenaryHuntdata
handlerTable[network.MSG_MHF_ENTRY_ROOKIE_GUILD] = handleMsgMhfEntryRookieGuild
handlerTable[network.MSG_MHF_ENUMERATE_QUEST] = handleMsgMhfEnumerateQuest
handlerTable[network.MSG_MHF_ENUMERATE_EVENT] = handleMsgMhfEnumerateEvent
handlerTable[network.MSG_MHF_ENUMERATE_PRICE] = handleMsgMhfEnumeratePrice
handlerTable[network.MSG_MHF_ENUMERATE_RANKING] = handleMsgMhfEnumerateRanking
handlerTable[network.MSG_MHF_ENUMERATE_ORDER] = handleMsgMhfEnumerateOrder
handlerTable[network.MSG_MHF_ENUMERATE_SHOP] = handleMsgMhfEnumerateShop
handlerTable[network.MSG_MHF_GET_EXTRA_INFO] = handleMsgMhfGetExtraInfo
handlerTable[network.MSG_MHF_UPDATE_INTERIOR] = handleMsgMhfUpdateInterior
handlerTable[network.MSG_MHF_ENUMERATE_HOUSE] = handleMsgMhfEnumerateHouse
handlerTable[network.MSG_MHF_UPDATE_HOUSE] = handleMsgMhfUpdateHouse
handlerTable[network.MSG_MHF_LOAD_HOUSE] = handleMsgMhfLoadHouse
handlerTable[network.MSG_MHF_OPERATE_WAREHOUSE] = handleMsgMhfOperateWarehouse
handlerTable[network.MSG_MHF_ENUMERATE_WAREHOUSE] = handleMsgMhfEnumerateWarehouse
handlerTable[network.MSG_MHF_UPDATE_WAREHOUSE] = handleMsgMhfUpdateWarehouse
handlerTable[network.MSG_MHF_ACQUIRE_TITLE] = handleMsgMhfAcquireTitle
handlerTable[network.MSG_MHF_ENUMERATE_TITLE] = handleMsgMhfEnumerateTitle
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_ITEM] = handleMsgMhfEnumerateGuildItem
handlerTable[network.MSG_MHF_UPDATE_GUILD_ITEM] = handleMsgMhfUpdateGuildItem
handlerTable[network.MSG_MHF_ENUMERATE_UNION_ITEM] = handleMsgMhfEnumerateUnionItem
handlerTable[network.MSG_MHF_UPDATE_UNION_ITEM] = handleMsgMhfUpdateUnionItem
handlerTable[network.MSG_MHF_CREATE_JOINT] = handleMsgMhfCreateJoint
handlerTable[network.MSG_MHF_OPERATE_JOINT] = handleMsgMhfOperateJoint
handlerTable[network.MSG_MHF_INFO_JOINT] = handleMsgMhfInfoJoint
handlerTable[network.MSG_MHF_UPDATE_GUILD_ICON] = handleMsgMhfUpdateGuildIcon
handlerTable[network.MSG_MHF_INFO_FESTA] = handleMsgMhfInfoFesta
handlerTable[network.MSG_MHF_ENTRY_FESTA] = handleMsgMhfEntryFesta
handlerTable[network.MSG_MHF_CHARGE_FESTA] = handleMsgMhfChargeFesta
handlerTable[network.MSG_MHF_ACQUIRE_FESTA] = handleMsgMhfAcquireFesta
handlerTable[network.MSG_MHF_STATE_FESTA_U] = handleMsgMhfStateFestaU
handlerTable[network.MSG_MHF_STATE_FESTA_G] = handleMsgMhfStateFestaG
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_MEMBER] = handleMsgMhfEnumerateFestaMember
handlerTable[network.MSG_MHF_VOTE_FESTA] = handleMsgMhfVoteFesta
handlerTable[network.MSG_MHF_ACQUIRE_CAFE_ITEM] = handleMsgMhfAcquireCafeItem
handlerTable[network.MSG_MHF_UPDATE_CAFEPOINT] = handleMsgMhfUpdateCafepoint
handlerTable[network.MSG_MHF_CHECK_DAILY_CAFEPOINT] = handleMsgMhfCheckDailyCafepoint
handlerTable[network.MSG_MHF_GET_COG_INFO] = handleMsgMhfGetCogInfo
handlerTable[network.MSG_MHF_CHECK_MONTHLY_ITEM] = handleMsgMhfCheckMonthlyItem
handlerTable[network.MSG_MHF_ACQUIRE_MONTHLY_ITEM] = handleMsgMhfAcquireMonthlyItem
handlerTable[network.MSG_MHF_CHECK_WEEKLY_STAMP] = handleMsgMhfCheckWeeklyStamp
handlerTable[network.MSG_MHF_EXCHANGE_WEEKLY_STAMP] = handleMsgMhfExchangeWeeklyStamp
handlerTable[network.MSG_MHF_CREATE_MERCENARY] = handleMsgMhfCreateMercenary
handlerTable[network.MSG_MHF_SAVE_MERCENARY] = handleMsgMhfSaveMercenary
handlerTable[network.MSG_MHF_READ_MERCENARY_W] = handleMsgMhfReadMercenaryW
handlerTable[network.MSG_MHF_READ_MERCENARY_M] = handleMsgMhfReadMercenaryM
handlerTable[network.MSG_MHF_CONTRACT_MERCENARY] = handleMsgMhfContractMercenary
handlerTable[network.MSG_MHF_ENUMERATE_MERCENARY_LOG] = handleMsgMhfEnumerateMercenaryLog
handlerTable[network.MSG_MHF_ENUMERATE_GUACOT] = handleMsgMhfEnumerateGuacot
handlerTable[network.MSG_MHF_UPDATE_GUACOT] = handleMsgMhfUpdateGuacot
handlerTable[network.MSG_MHF_INFO_TOURNAMENT] = handleMsgMhfInfoTournament
handlerTable[network.MSG_MHF_ENTRY_TOURNAMENT] = handleMsgMhfEntryTournament
handlerTable[network.MSG_MHF_ENTER_TOURNAMENT_QUEST] = handleMsgMhfEnterTournamentQuest
handlerTable[network.MSG_MHF_ACQUIRE_TOURNAMENT] = handleMsgMhfAcquireTournament
handlerTable[network.MSG_MHF_GET_ACHIEVEMENT] = handleMsgMhfGetAchievement
handlerTable[network.MSG_MHF_RESET_ACHIEVEMENT] = handleMsgMhfResetAchievement
handlerTable[network.MSG_MHF_ADD_ACHIEVEMENT] = handleMsgMhfAddAchievement
handlerTable[network.MSG_MHF_PAYMENT_ACHIEVEMENT] = handleMsgMhfPaymentAchievement
handlerTable[network.MSG_MHF_DISPLAYED_ACHIEVEMENT] = handleMsgMhfDisplayedAchievement
handlerTable[network.MSG_MHF_INFO_SCENARIO_COUNTER] = handleMsgMhfInfoScenarioCounter
handlerTable[network.MSG_MHF_SAVE_SCENARIO_DATA] = handleMsgMhfSaveScenarioData
handlerTable[network.MSG_MHF_LOAD_SCENARIO_DATA] = handleMsgMhfLoadScenarioData
handlerTable[network.MSG_MHF_GET_BBS_SNS_STATUS] = handleMsgMhfGetBbsSnsStatus
handlerTable[network.MSG_MHF_APPLY_BBS_ARTICLE] = handleMsgMhfApplyBbsArticle
handlerTable[network.MSG_MHF_GET_ETC_POINTS] = handleMsgMhfGetEtcPoints
handlerTable[network.MSG_MHF_UPDATE_ETC_POINT] = handleMsgMhfUpdateEtcPoint
handlerTable[network.MSG_MHF_GET_MYHOUSE_INFO] = handleMsgMhfGetMyhouseInfo
handlerTable[network.MSG_MHF_UPDATE_MYHOUSE_INFO] = handleMsgMhfUpdateMyhouseInfo
handlerTable[network.MSG_MHF_GET_WEEKLY_SCHEDULE] = handleMsgMhfGetWeeklySchedule
handlerTable[network.MSG_MHF_ENUMERATE_INV_GUILD] = handleMsgMhfEnumerateInvGuild
handlerTable[network.MSG_MHF_OPERATION_INV_GUILD] = handleMsgMhfOperationInvGuild
handlerTable[network.MSG_MHF_STAMPCARD_STAMP] = handleMsgMhfStampcardStamp
handlerTable[network.MSG_MHF_STAMPCARD_PRIZE] = handleMsgMhfStampcardPrize
handlerTable[network.MSG_MHF_UNRESERVE_SRG] = handleMsgMhfUnreserveSrg
handlerTable[network.MSG_MHF_LOAD_PLATE_DATA] = handleMsgMhfLoadPlateData
handlerTable[network.MSG_MHF_SAVE_PLATE_DATA] = handleMsgMhfSavePlateData
handlerTable[network.MSG_MHF_LOAD_PLATE_BOX] = handleMsgMhfLoadPlateBox
handlerTable[network.MSG_MHF_SAVE_PLATE_BOX] = handleMsgMhfSavePlateBox
handlerTable[network.MSG_MHF_READ_GUILDCARD] = handleMsgMhfReadGuildcard
handlerTable[network.MSG_MHF_UPDATE_GUILDCARD] = handleMsgMhfUpdateGuildcard
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL] = handleMsgMhfReadBeatLevel
handlerTable[network.MSG_MHF_UPDATE_BEAT_LEVEL] = handleMsgMhfUpdateBeatLevel
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL_ALL_RANKING] = handleMsgMhfReadBeatLevelAllRanking
handlerTable[network.MSG_MHF_READ_BEAT_LEVEL_MY_RANKING] = handleMsgMhfReadBeatLevelMyRanking
handlerTable[network.MSG_MHF_READ_LAST_WEEK_BEAT_RANKING] = handleMsgMhfReadLastWeekBeatRanking
handlerTable[network.MSG_MHF_ACCEPT_READ_REWARD] = handleMsgMhfAcceptReadReward
handlerTable[network.MSG_MHF_GET_ADDITIONAL_BEAT_REWARD] = handleMsgMhfGetAdditionalBeatReward
handlerTable[network.MSG_MHF_GET_FIXED_SEIBATU_RANKING_TABLE] = handleMsgMhfGetFixedSeibatuRankingTable
handlerTable[network.MSG_MHF_GET_BBS_USER_STATUS] = handleMsgMhfGetBbsUserStatus
handlerTable[network.MSG_MHF_KICK_EXPORT_FORCE] = handleMsgMhfKickExportForce
handlerTable[network.MSG_MHF_GET_BREAK_SEIBATU_LEVEL_REWARD] = handleMsgMhfGetBreakSeibatuLevelReward
handlerTable[network.MSG_MHF_GET_WEEKLY_SEIBATU_RANKING_REWARD] = handleMsgMhfGetWeeklySeibatuRankingReward
handlerTable[network.MSG_MHF_GET_EARTH_STATUS] = handleMsgMhfGetEarthStatus
handlerTable[network.MSG_MHF_LOAD_PARTNER] = handleMsgMhfLoadPartner
handlerTable[network.MSG_MHF_SAVE_PARTNER] = handleMsgMhfSavePartner
handlerTable[network.MSG_MHF_GET_GUILD_MISSION_LIST] = handleMsgMhfGetGuildMissionList
handlerTable[network.MSG_MHF_GET_GUILD_MISSION_RECORD] = handleMsgMhfGetGuildMissionRecord
handlerTable[network.MSG_MHF_ADD_GUILD_MISSION_COUNT] = handleMsgMhfAddGuildMissionCount
handlerTable[network.MSG_MHF_SET_GUILD_MISSION_TARGET] = handleMsgMhfSetGuildMissionTarget
handlerTable[network.MSG_MHF_CANCEL_GUILD_MISSION_TARGET] = handleMsgMhfCancelGuildMissionTarget
handlerTable[network.MSG_MHF_LOAD_OTOMO_AIROU] = handleMsgMhfLoadOtomoAirou
handlerTable[network.MSG_MHF_SAVE_OTOMO_AIROU] = handleMsgMhfSaveOtomoAirou
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_TRESURE] = handleMsgMhfEnumerateGuildTresure
handlerTable[network.MSG_MHF_ENUMERATE_AIROULIST] = handleMsgMhfEnumerateAiroulist
handlerTable[network.MSG_MHF_REGIST_GUILD_TRESURE] = handleMsgMhfRegistGuildTresure
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_TRESURE] = handleMsgMhfAcquireGuildTresure
handlerTable[network.MSG_MHF_OPERATE_GUILD_TRESURE_REPORT] = handleMsgMhfOperateGuildTresureReport
handlerTable[network.MSG_MHF_GET_GUILD_TRESURE_SOUVENIR] = handleMsgMhfGetGuildTresureSouvenir
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_TRESURE_SOUVENIR] = handleMsgMhfAcquireGuildTresureSouvenir
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_INTERMEDIATE_PRIZE] = handleMsgMhfEnumerateFestaIntermediatePrize
handlerTable[network.MSG_MHF_ACQUIRE_FESTA_INTERMEDIATE_PRIZE] = handleMsgMhfAcquireFestaIntermediatePrize
handlerTable[network.MSG_MHF_LOAD_DECO_MYSET] = handleMsgMhfLoadDecoMyset
handlerTable[network.MSG_MHF_SAVE_DECO_MYSET] = handleMsgMhfSaveDecoMyset
handlerTable[network.MSG_MHF_reserve10F] = handleMsgMhfReserve10F
handlerTable[network.MSG_MHF_LOAD_GUILD_COOKING] = handleMsgMhfLoadGuildCooking
handlerTable[network.MSG_MHF_REGIST_GUILD_COOKING] = handleMsgMhfRegistGuildCooking
handlerTable[network.MSG_MHF_LOAD_GUILD_ADVENTURE] = handleMsgMhfLoadGuildAdventure
handlerTable[network.MSG_MHF_REGIST_GUILD_ADVENTURE] = handleMsgMhfRegistGuildAdventure
handlerTable[network.MSG_MHF_ACQUIRE_GUILD_ADVENTURE] = handleMsgMhfAcquireGuildAdventure
handlerTable[network.MSG_MHF_CHARGE_GUILD_ADVENTURE] = handleMsgMhfChargeGuildAdventure
handlerTable[network.MSG_MHF_LOAD_LEGEND_DISPATCH] = handleMsgMhfLoadLegendDispatch
handlerTable[network.MSG_MHF_LOAD_HUNTER_NAVI] = handleMsgMhfLoadHunterNavi
handlerTable[network.MSG_MHF_SAVE_HUNTER_NAVI] = handleMsgMhfSaveHunterNavi
handlerTable[network.MSG_MHF_REGIST_SPABI_TIME] = handleMsgMhfRegistSpabiTime
handlerTable[network.MSG_MHF_GET_GUILD_WEEKLY_BONUS_MASTER] = handleMsgMhfGetGuildWeeklyBonusMaster
handlerTable[network.MSG_MHF_GET_GUILD_WEEKLY_BONUS_ACTIVE_COUNT] = handleMsgMhfGetGuildWeeklyBonusActiveCount
handlerTable[network.MSG_MHF_ADD_GUILD_WEEKLY_BONUS_EXCEPTIONAL_USER] = handleMsgMhfAddGuildWeeklyBonusExceptionalUser
handlerTable[network.MSG_MHF_GET_TOWER_INFO] = handleMsgMhfGetTowerInfo
handlerTable[network.MSG_MHF_POST_TOWER_INFO] = handleMsgMhfPostTowerInfo
handlerTable[network.MSG_MHF_GET_GEM_INFO] = handleMsgMhfGetGemInfo
handlerTable[network.MSG_MHF_POST_GEM_INFO] = handleMsgMhfPostGemInfo
handlerTable[network.MSG_MHF_GET_EARTH_VALUE] = handleMsgMhfGetEarthValue
handlerTable[network.MSG_MHF_DEBUG_POST_VALUE] = handleMsgMhfDebugPostValue
handlerTable[network.MSG_MHF_GET_PAPER_DATA] = handleMsgMhfGetPaperData
handlerTable[network.MSG_MHF_GET_NOTICE] = handleMsgMhfGetNotice
handlerTable[network.MSG_MHF_POST_NOTICE] = handleMsgMhfPostNotice
handlerTable[network.MSG_MHF_GET_BOOST_TIME] = handleMsgMhfGetBoostTime
handlerTable[network.MSG_MHF_POST_BOOST_TIME] = handleMsgMhfPostBoostTime
handlerTable[network.MSG_MHF_GET_BOOST_TIME_LIMIT] = handleMsgMhfGetBoostTimeLimit
handlerTable[network.MSG_MHF_POST_BOOST_TIME_LIMIT] = handleMsgMhfPostBoostTimeLimit
handlerTable[network.MSG_MHF_ENUMERATE_FESTA_PERSONAL_PRIZE] = handleMsgMhfEnumerateFestaPersonalPrize
handlerTable[network.MSG_MHF_ACQUIRE_FESTA_PERSONAL_PRIZE] = handleMsgMhfAcquireFestaPersonalPrize
handlerTable[network.MSG_MHF_GET_RAND_FROM_TABLE] = handleMsgMhfGetRandFromTable
handlerTable[network.MSG_MHF_GET_CAFE_DURATION] = handleMsgMhfGetCafeDuration
handlerTable[network.MSG_MHF_GET_CAFE_DURATION_BONUS_INFO] = handleMsgMhfGetCafeDurationBonusInfo
handlerTable[network.MSG_MHF_RECEIVE_CAFE_DURATION_BONUS] = handleMsgMhfReceiveCafeDurationBonus
handlerTable[network.MSG_MHF_POST_CAFE_DURATION_BONUS_RECEIVED] = handleMsgMhfPostCafeDurationBonusReceived
handlerTable[network.MSG_MHF_GET_GACHA_POINT] = handleMsgMhfGetGachaPoint
handlerTable[network.MSG_MHF_USE_GACHA_POINT] = handleMsgMhfUseGachaPoint
handlerTable[network.MSG_MHF_EXCHANGE_FPOINT_2_ITEM] = handleMsgMhfExchangeFpoint2Item
handlerTable[network.MSG_MHF_EXCHANGE_ITEM_2_FPOINT] = handleMsgMhfExchangeItem2Fpoint
handlerTable[network.MSG_MHF_GET_FPOINT_EXCHANGE_LIST] = handleMsgMhfGetFpointExchangeList
handlerTable[network.MSG_MHF_PLAY_STEPUP_GACHA] = handleMsgMhfPlayStepupGacha
handlerTable[network.MSG_MHF_RECEIVE_GACHA_ITEM] = handleMsgMhfReceiveGachaItem
handlerTable[network.MSG_MHF_GET_STEPUP_STATUS] = handleMsgMhfGetStepupStatus
handlerTable[network.MSG_MHF_PLAY_FREE_GACHA] = handleMsgMhfPlayFreeGacha
handlerTable[network.MSG_MHF_GET_TINY_BIN] = handleMsgMhfGetTinyBin
handlerTable[network.MSG_MHF_POST_TINY_BIN] = handleMsgMhfPostTinyBin
handlerTable[network.MSG_MHF_GET_SENYU_DAILY_COUNT] = handleMsgMhfGetSenyuDailyCount
handlerTable[network.MSG_MHF_GET_GUILD_TARGET_MEMBER_NUM] = handleMsgMhfGetGuildTargetMemberNum
handlerTable[network.MSG_MHF_GET_BOOST_RIGHT] = handleMsgMhfGetBoostRight
handlerTable[network.MSG_MHF_START_BOOST_TIME] = handleMsgMhfStartBoostTime
handlerTable[network.MSG_MHF_POST_BOOST_TIME_QUEST_RETURN] = handleMsgMhfPostBoostTimeQuestReturn
handlerTable[network.MSG_MHF_GET_BOX_GACHA_INFO] = handleMsgMhfGetBoxGachaInfo
handlerTable[network.MSG_MHF_PLAY_BOX_GACHA] = handleMsgMhfPlayBoxGacha
handlerTable[network.MSG_MHF_RESET_BOX_GACHA_INFO] = handleMsgMhfResetBoxGachaInfo
handlerTable[network.MSG_MHF_GET_SEIBATTLE] = handleMsgMhfGetSeibattle
handlerTable[network.MSG_MHF_POST_SEIBATTLE] = handleMsgMhfPostSeibattle
handlerTable[network.MSG_MHF_GET_RYOUDAMA] = handleMsgMhfGetRyoudama
handlerTable[network.MSG_MHF_POST_RYOUDAMA] = handleMsgMhfPostRyoudama
handlerTable[network.MSG_MHF_GET_TENROUIRAI] = handleMsgMhfGetTenrouirai
handlerTable[network.MSG_MHF_POST_TENROUIRAI] = handleMsgMhfPostTenrouirai
handlerTable[network.MSG_MHF_POST_GUILD_SCOUT] = handleMsgMhfPostGuildScout
handlerTable[network.MSG_MHF_CANCEL_GUILD_SCOUT] = handleMsgMhfCancelGuildScout
handlerTable[network.MSG_MHF_ANSWER_GUILD_SCOUT] = handleMsgMhfAnswerGuildScout
handlerTable[network.MSG_MHF_GET_GUILD_SCOUT_LIST] = handleMsgMhfGetGuildScoutList
handlerTable[network.MSG_MHF_GET_GUILD_MANAGE_RIGHT] = handleMsgMhfGetGuildManageRight
handlerTable[network.MSG_MHF_SET_GUILD_MANAGE_RIGHT] = handleMsgMhfSetGuildManageRight
handlerTable[network.MSG_MHF_PLAY_NORMAL_GACHA] = handleMsgMhfPlayNormalGacha
handlerTable[network.MSG_MHF_GET_DAILY_MISSION_MASTER] = handleMsgMhfGetDailyMissionMaster
handlerTable[network.MSG_MHF_GET_DAILY_MISSION_PERSONAL] = handleMsgMhfGetDailyMissionPersonal
handlerTable[network.MSG_MHF_SET_DAILY_MISSION_PERSONAL] = handleMsgMhfSetDailyMissionPersonal
handlerTable[network.MSG_MHF_GET_GACHA_PLAY_HISTORY] = handleMsgMhfGetGachaPlayHistory
handlerTable[network.MSG_MHF_GET_REJECT_GUILD_SCOUT] = handleMsgMhfGetRejectGuildScout
handlerTable[network.MSG_MHF_SET_REJECT_GUILD_SCOUT] = handleMsgMhfSetRejectGuildScout
handlerTable[network.MSG_MHF_GET_CA_ACHIEVEMENT_HIST] = handleMsgMhfGetCaAchievementHist
handlerTable[network.MSG_MHF_SET_CA_ACHIEVEMENT_HIST] = handleMsgMhfSetCaAchievementHist
handlerTable[network.MSG_MHF_GET_KEEP_LOGIN_BOOST_STATUS] = handleMsgMhfGetKeepLoginBoostStatus
handlerTable[network.MSG_MHF_USE_KEEP_LOGIN_BOOST] = handleMsgMhfUseKeepLoginBoost
handlerTable[network.MSG_MHF_GET_UD_SCHEDULE] = handleMsgMhfGetUdSchedule
handlerTable[network.MSG_MHF_GET_UD_INFO] = handleMsgMhfGetUdInfo
handlerTable[network.MSG_MHF_GET_KIJU_INFO] = handleMsgMhfGetKijuInfo
handlerTable[network.MSG_MHF_SET_KIJU] = handleMsgMhfSetKiju
handlerTable[network.MSG_MHF_ADD_UD_POINT] = handleMsgMhfAddUdPoint
handlerTable[network.MSG_MHF_GET_UD_MY_POINT] = handleMsgMhfGetUdMyPoint
handlerTable[network.MSG_MHF_GET_UD_TOTAL_POINT_INFO] = handleMsgMhfGetUdTotalPointInfo
handlerTable[network.MSG_MHF_GET_UD_BONUS_QUEST_INFO] = handleMsgMhfGetUdBonusQuestInfo
handlerTable[network.MSG_MHF_GET_UD_SELECTED_COLOR_INFO] = handleMsgMhfGetUdSelectedColorInfo
handlerTable[network.MSG_MHF_GET_UD_MONSTER_POINT] = handleMsgMhfGetUdMonsterPoint
handlerTable[network.MSG_MHF_GET_UD_DAILY_PRESENT_LIST] = handleMsgMhfGetUdDailyPresentList
handlerTable[network.MSG_MHF_GET_UD_NORMA_PRESENT_LIST] = handleMsgMhfGetUdNormaPresentList
handlerTable[network.MSG_MHF_GET_UD_RANKING_REWARD_LIST] = handleMsgMhfGetUdRankingRewardList
handlerTable[network.MSG_MHF_ACQUIRE_UD_ITEM] = handleMsgMhfAcquireUdItem
handlerTable[network.MSG_MHF_GET_REWARD_SONG] = handleMsgMhfGetRewardSong
handlerTable[network.MSG_MHF_USE_REWARD_SONG] = handleMsgMhfUseRewardSong
handlerTable[network.MSG_MHF_ADD_REWARD_SONG_COUNT] = handleMsgMhfAddRewardSongCount
handlerTable[network.MSG_MHF_GET_UD_RANKING] = handleMsgMhfGetUdRanking
handlerTable[network.MSG_MHF_GET_UD_MY_RANKING] = handleMsgMhfGetUdMyRanking
handlerTable[network.MSG_MHF_ACQUIRE_MONTHLY_REWARD] = handleMsgMhfAcquireMonthlyReward
handlerTable[network.MSG_MHF_GET_UD_GUILD_MAP_INFO] = handleMsgMhfGetUdGuildMapInfo
handlerTable[network.MSG_MHF_GENERATE_UD_GUILD_MAP] = handleMsgMhfGenerateUdGuildMap
handlerTable[network.MSG_MHF_GET_UD_TACTICS_POINT] = handleMsgMhfGetUdTacticsPoint
handlerTable[network.MSG_MHF_ADD_UD_TACTICS_POINT] = handleMsgMhfAddUdTacticsPoint
handlerTable[network.MSG_MHF_GET_UD_TACTICS_RANKING] = handleMsgMhfGetUdTacticsRanking
handlerTable[network.MSG_MHF_GET_UD_TACTICS_REWARD_LIST] = handleMsgMhfGetUdTacticsRewardList
handlerTable[network.MSG_MHF_GET_UD_TACTICS_LOG] = handleMsgMhfGetUdTacticsLog
handlerTable[network.MSG_MHF_GET_EQUIP_SKIN_HIST] = handleMsgMhfGetEquipSkinHist
handlerTable[network.MSG_MHF_UPDATE_EQUIP_SKIN_HIST] = handleMsgMhfUpdateEquipSkinHist
handlerTable[network.MSG_MHF_GET_UD_TACTICS_FOLLOWER] = handleMsgMhfGetUdTacticsFollower
handlerTable[network.MSG_MHF_SET_UD_TACTICS_FOLLOWER] = handleMsgMhfSetUdTacticsFollower
handlerTable[network.MSG_MHF_GET_UD_SHOP_COIN] = handleMsgMhfGetUdShopCoin
handlerTable[network.MSG_MHF_USE_UD_SHOP_COIN] = handleMsgMhfUseUdShopCoin
handlerTable[network.MSG_MHF_GET_ENHANCED_MINIDATA] = handleMsgMhfGetEnhancedMinidata
handlerTable[network.MSG_MHF_SET_ENHANCED_MINIDATA] = handleMsgMhfSetEnhancedMinidata
handlerTable[network.MSG_MHF_SEX_CHANGER] = handleMsgMhfSexChanger
handlerTable[network.MSG_MHF_GET_LOBBY_CROWD] = handleMsgMhfGetLobbyCrowd
handlerTable[network.MSG_SYS_reserve180] = handleMsgSysReserve180
handlerTable[network.MSG_MHF_GUILD_HUNTDATA] = handleMsgMhfGuildHuntdata
handlerTable[network.MSG_MHF_ADD_KOURYOU_POINT] = handleMsgMhfAddKouryouPoint
handlerTable[network.MSG_MHF_GET_KOURYOU_POINT] = handleMsgMhfGetKouryouPoint
handlerTable[network.MSG_MHF_EXCHANGE_KOURYOU_POINT] = handleMsgMhfExchangeKouryouPoint
handlerTable[network.MSG_MHF_GET_UD_TACTICS_BONUS_QUEST] = handleMsgMhfGetUdTacticsBonusQuest
handlerTable[network.MSG_MHF_GET_UD_TACTICS_FIRST_QUEST_BONUS] = handleMsgMhfGetUdTacticsFirstQuestBonus
handlerTable[network.MSG_MHF_GET_UD_TACTICS_REMAINING_POINT] = handleMsgMhfGetUdTacticsRemainingPoint
handlerTable[network.MSG_SYS_reserve188] = handleMsgSysReserve188
handlerTable[network.MSG_MHF_LOAD_PLATE_MYSET] = handleMsgMhfLoadPlateMyset
handlerTable[network.MSG_MHF_SAVE_PLATE_MYSET] = handleMsgMhfSavePlateMyset
handlerTable[network.MSG_SYS_reserve18B] = handleMsgSysReserve18B
handlerTable[network.MSG_MHF_GET_RESTRICTION_EVENT] = handleMsgMhfGetRestrictionEvent
handlerTable[network.MSG_MHF_SET_RESTRICTION_EVENT] = handleMsgMhfSetRestrictionEvent
handlerTable[network.MSG_SYS_reserve18E] = handleMsgSysReserve18E
handlerTable[network.MSG_SYS_reserve18F] = handleMsgSysReserve18F
handlerTable[network.MSG_MHF_GET_TREND_WEAPON] = handleMsgMhfGetTrendWeapon
handlerTable[network.MSG_MHF_UPDATE_USE_TREND_WEAPON_LOG] = handleMsgMhfUpdateUseTrendWeaponLog
handlerTable[network.MSG_SYS_reserve192] = handleMsgSysReserve192
handlerTable[network.MSG_SYS_reserve193] = handleMsgSysReserve193
handlerTable[network.MSG_SYS_reserve194] = handleMsgSysReserve194
handlerTable[network.MSG_MHF_SAVE_RENGOKU_DATA] = handleMsgMhfSaveRengokuData
handlerTable[network.MSG_MHF_LOAD_RENGOKU_DATA] = handleMsgMhfLoadRengokuData
handlerTable[network.MSG_MHF_GET_RENGOKU_BINARY] = handleMsgMhfGetRengokuBinary
handlerTable[network.MSG_MHF_ENUMERATE_RENGOKU_RANKING] = handleMsgMhfEnumerateRengokuRanking
handlerTable[network.MSG_MHF_GET_RENGOKU_RANKING_RANK] = handleMsgMhfGetRengokuRankingRank
handlerTable[network.MSG_MHF_ACQUIRE_EXCHANGE_SHOP] = handleMsgMhfAcquireExchangeShop
handlerTable[network.MSG_SYS_reserve19B] = handleMsgSysReserve19B
handlerTable[network.MSG_MHF_SAVE_MEZFES_DATA] = handleMsgMhfSaveMezfesData
handlerTable[network.MSG_MHF_LOAD_MEZFES_DATA] = handleMsgMhfLoadMezfesData
handlerTable[network.MSG_SYS_reserve19E] = handleMsgSysReserve19E
handlerTable[network.MSG_SYS_reserve19F] = handleMsgSysReserve19F
handlerTable[network.MSG_MHF_UPDATE_FORCE_GUILD_RANK] = handleMsgMhfUpdateForceGuildRank
handlerTable[network.MSG_MHF_RESET_TITLE] = handleMsgMhfResetTitle
handlerTable[network.MSG_MHF_ENUMERATE_GUILD_MESSAGE_BOARD] = handleMsgMhfEnumerateGuildMessageBoard
handlerTable[network.MSG_MHF_UPDATE_GUILD_MESSAGE_BOARD] = handleMsgMhfUpdateGuildMessageBoard
handlerTable[network.MSG_SYS_reserve1A4] = handleMsgSysReserve1A4
handlerTable[network.MSG_MHF_REGIST_GUILD_ADVENTURE_DIVA] = handleMsgMhfRegistGuildAdventureDiva
handlerTable[network.MSG_SYS_reserve1A6] = handleMsgSysReserve1A6
handlerTable[network.MSG_SYS_reserve1A7] = handleMsgSysReserve1A7
handlerTable[network.MSG_SYS_reserve1A8] = handleMsgSysReserve1A8
handlerTable[network.MSG_SYS_reserve1A9] = handleMsgSysReserve1A9
handlerTable[network.MSG_SYS_reserve1AA] = handleMsgSysReserve1AA
handlerTable[network.MSG_SYS_reserve1AB] = handleMsgSysReserve1AB
handlerTable[network.MSG_SYS_reserve1AC] = handleMsgSysReserve1AC
handlerTable[network.MSG_SYS_reserve1AD] = handleMsgSysReserve1AD
handlerTable[network.MSG_SYS_reserve1AE] = handleMsgSysReserve1AE
handlerTable[network.MSG_SYS_reserve1AF] = handleMsgSysReserve1AF
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,130 @@
package channelserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"time"
)
type TournamentInfo0 struct {
ID uint32
MaxPlayers uint32
CurrentPlayers uint32
Unk1 uint16
TextColor uint16
Unk2 uint32
Time1 time.Time
Time2 time.Time
Time3 time.Time
Time4 time.Time
Time5 time.Time
Time6 time.Time
Unk3 uint8
Unk4 uint8
MinHR uint32
MaxHR uint32
Unk5 string
Unk6 string
}
type TournamentInfo21 struct {
Unk0 uint32
Unk1 uint32
Unk2 uint32
Unk3 uint8
}
type TournamentInfo22 struct {
Unk0 uint32
Unk1 uint32
Unk2 uint32
Unk3 uint8
Unk4 string
}
func handleMsgMhfInfoTournament(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoTournament)
bf := byteframe.NewByteFrame()
tournamentInfo0 := []TournamentInfo0{}
tournamentInfo21 := []TournamentInfo21{}
tournamentInfo22 := []TournamentInfo22{}
switch pkt.Unk0 {
case 0:
bf.WriteUint32(0)
bf.WriteUint32(uint32(len(tournamentInfo0)))
for _, tinfo := range tournamentInfo0 {
bf.WriteUint32(tinfo.ID)
bf.WriteUint32(tinfo.MaxPlayers)
bf.WriteUint32(tinfo.CurrentPlayers)
bf.WriteUint16(tinfo.Unk1)
bf.WriteUint16(tinfo.TextColor)
bf.WriteUint32(tinfo.Unk2)
bf.WriteUint32(uint32(tinfo.Time1.Unix()))
bf.WriteUint32(uint32(tinfo.Time2.Unix()))
bf.WriteUint32(uint32(tinfo.Time3.Unix()))
bf.WriteUint32(uint32(tinfo.Time4.Unix()))
bf.WriteUint32(uint32(tinfo.Time5.Unix()))
bf.WriteUint32(uint32(tinfo.Time6.Unix()))
bf.WriteUint8(tinfo.Unk3)
bf.WriteUint8(tinfo.Unk4)
bf.WriteUint32(tinfo.MinHR)
bf.WriteUint32(tinfo.MaxHR)
ps.Uint8(bf, tinfo.Unk5, true)
ps.Uint16(bf, tinfo.Unk6, true)
}
case 1:
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
bf.WriteUint32(0) // Registered ID
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint8(0)
bf.WriteUint32(0)
ps.Uint8(bf, "", true)
case 2:
bf.WriteUint32(0)
bf.WriteUint32(uint32(len(tournamentInfo21)))
for _, info := range tournamentInfo21 {
bf.WriteUint32(info.Unk0)
bf.WriteUint32(info.Unk1)
bf.WriteUint32(info.Unk2)
bf.WriteUint8(info.Unk3)
}
bf.WriteUint32(uint32(len(tournamentInfo22)))
for _, info := range tournamentInfo22 {
bf.WriteUint32(info.Unk0)
bf.WriteUint32(info.Unk1)
bf.WriteUint32(info.Unk2)
bf.WriteUint8(info.Unk3)
ps.Uint8(bf, info.Unk4, true)
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfEntryTournament(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEntryTournament)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
type TournamentReward struct {
Unk0 uint16
Unk1 uint16
Unk2 uint16
}
func handleMsgMhfAcquireTournament(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireTournament)
rewards := []TournamentReward{}
bf := byteframe.NewByteFrame()
bf.WriteUint8(uint8(len(rewards)))
for _, reward := range rewards {
bf.WriteUint16(reward.Unk0)
bf.WriteUint16(reward.Unk1)
bf.WriteUint16(reward.Unk2)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -0,0 +1,477 @@
package channelserver
import (
"fmt"
"go.uber.org/zap"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
)
type TowerInfoTRP struct {
TR int32
TRP int32
}
type TowerInfoSkill struct {
TSP int32
Unk1 []int16 // 40
}
type TowerInfoHistory struct {
Unk0 []int16 // 5
Unk1 []int16 // 5
}
type TowerInfoLevel struct {
Floors int32
Unk1 int32
Unk2 int32
Unk3 int32
}
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetTowerInfo)
var data []*byteframe.ByteFrame
type TowerInfo struct {
TRP []TowerInfoTRP
Skill []TowerInfoSkill
History []TowerInfoHistory
Level []TowerInfoLevel
}
towerInfo := TowerInfo{
TRP: []TowerInfoTRP{{0, 0}},
Skill: []TowerInfoSkill{{0, make([]int16, 40)}},
History: []TowerInfoHistory{{make([]int16, 5), make([]int16, 5)}},
Level: []TowerInfoLevel{{0, 0, 0, 0}, {0, 0, 0, 0}},
}
tempSkills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
err := s.server.db.QueryRow(`SELECT COALESCE(tr, 0), COALESCE(trp, 0), COALESCE(tsp, 0), COALESCE(block1, 0), COALESCE(block2, 0), skills FROM tower WHERE char_id=$1
`, s.charID).Scan(&towerInfo.TRP[0].TR, &towerInfo.TRP[0].TRP, &towerInfo.Skill[0].TSP, &towerInfo.Level[0].Floors, &towerInfo.Level[1].Floors, &tempSkills)
if err != nil {
s.server.db.Exec(`INSERT INTO tower (char_id) VALUES ($1)`, s.charID)
}
for i, skill := range stringsupport.CSVElems(tempSkills) {
towerInfo.Skill[0].Unk1[i] = int16(skill)
}
switch pkt.InfoType {
case 1:
for _, trp := range towerInfo.TRP {
bf := byteframe.NewByteFrame()
bf.WriteInt32(trp.TR)
bf.WriteInt32(trp.TRP)
data = append(data, bf)
}
case 2:
for _, skills := range towerInfo.Skill {
bf := byteframe.NewByteFrame()
bf.WriteInt32(skills.TSP)
for i := range skills.Unk1 {
bf.WriteInt16(skills.Unk1[i])
}
data = append(data, bf)
}
case 4:
for _, history := range towerInfo.History {
bf := byteframe.NewByteFrame()
for i := range history.Unk0 {
bf.WriteInt16(history.Unk0[i])
}
for i := range history.Unk1 {
bf.WriteInt16(history.Unk1[i])
}
data = append(data, bf)
}
case 5:
for _, level := range towerInfo.Level {
bf := byteframe.NewByteFrame()
bf.WriteInt32(level.Floors)
bf.WriteInt32(level.Unk1)
bf.WriteInt32(level.Unk2)
bf.WriteInt32(level.Unk3)
data = append(data, bf)
}
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfPostTowerInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostTowerInfo)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
s.logger.Debug(
p.Opcode().String(),
zap.Uint32("InfoType", pkt.InfoType),
zap.Uint32("Unk1", pkt.Unk1),
zap.Int32("Skill", pkt.Skill),
zap.Int32("TR", pkt.TR),
zap.Int32("TRP", pkt.TRP),
zap.Int32("Cost", pkt.Cost),
zap.Int32("Unk6", pkt.Unk6),
zap.Int32("Unk7", pkt.Unk7),
zap.Int32("Block1", pkt.Block1),
zap.Int64("Unk9", pkt.Unk9),
)
}
switch pkt.InfoType {
case 2:
skills := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
s.server.db.QueryRow(`SELECT skills FROM tower WHERE char_id=$1`, s.charID).Scan(&skills)
s.server.db.Exec(`UPDATE tower SET skills=$1, tsp=tsp-$2 WHERE char_id=$3`, stringsupport.CSVSetIndex(skills, int(pkt.Skill), stringsupport.CSVGetIndex(skills, int(pkt.Skill))+1), pkt.Cost, s.charID)
case 7:
s.server.db.Exec(`UPDATE tower SET tr=$1, trp=trp+$2, block1=block1+$3 WHERE char_id=$4`, pkt.TR, pkt.TRP, pkt.Block1, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
// Default missions
var tenrouiraiData = []TenrouiraiData{
{1, 1, 80, 0, 2, 2, 1, 1, 2, 2},
{1, 4, 16, 0, 2, 2, 1, 1, 2, 2},
{1, 6, 50, 0, 2, 2, 1, 0, 2, 2},
{1, 4, 12, 50, 2, 2, 1, 1, 2, 2},
{1, 3, 50, 0, 2, 2, 1, 1, 2, 2},
{2, 5, 40000, 0, 2, 2, 1, 0, 2, 2},
{1, 5, 50000, 50, 2, 2, 1, 1, 2, 2},
{2, 1, 60, 0, 2, 2, 1, 1, 2, 2},
{2, 3, 50, 0, 2, 1, 1, 0, 1, 2},
{2, 3, 40, 50, 2, 1, 1, 1, 1, 2},
{2, 4, 12, 0, 2, 1, 1, 1, 1, 2},
{2, 6, 40, 0, 2, 1, 1, 0, 1, 2},
{1, 1, 60, 50, 2, 1, 2, 1, 1, 2},
{1, 5, 50000, 0, 3, 1, 2, 1, 1, 2},
{1, 6, 50, 0, 3, 1, 2, 0, 1, 2},
{1, 4, 16, 50, 3, 1, 2, 1, 1, 2},
{1, 5, 50000, 0, 3, 1, 2, 1, 1, 2},
{2, 3, 40, 0, 3, 1, 2, 0, 1, 2},
{1, 3, 50, 50, 3, 1, 2, 1, 1, 2},
{2, 5, 40000, 0, 3, 1, 2, 1, 1, 1},
{2, 6, 40, 0, 3, 1, 2, 0, 1, 1},
{2, 1, 60, 50, 3, 1, 2, 1, 1, 1},
{2, 6, 50, 0, 3, 1, 2, 1, 1, 1},
{2, 4, 12, 0, 3, 1, 2, 0, 1, 1},
{1, 1, 80, 50, 3, 1, 2, 1, 1, 1},
{1, 5, 40000, 0, 3, 1, 2, 1, 1, 1},
{1, 3, 50, 0, 3, 1, 2, 0, 1, 1},
{1, 4, 16, 50, 3, 1, 0, 1, 1, 1},
{1, 6, 50, 0, 3, 1, 0, 1, 1, 1},
{2, 3, 40, 0, 3, 1, 0, 1, 1, 1},
{1, 1, 80, 50, 3, 1, 0, 0, 1, 1},
{2, 5, 40000, 0, 3, 1, 0, 0, 1, 1},
{2, 6, 40, 0, 3, 1, 0, 0, 1, 1},
}
type TenrouiraiProgress struct {
Page uint8
Mission1 uint16
Mission2 uint16
Mission3 uint16
}
type TenrouiraiReward struct {
Index uint8
Item []uint16 // 5
Quantity []uint8 // 5
}
type TenrouiraiKeyScore struct {
Unk0 uint8
Unk1 int32
}
type TenrouiraiData struct {
Block uint8
Mission uint8
// 1 = Floors climbed
// 2 = Collect antiques
// 3 = Open chests
// 4 = Cats saved
// 5 = TRP acquisition
// 6 = Monster slays
Goal uint16
Cost uint16
Skill1 uint8 // 80
Skill2 uint8 // 40
Skill3 uint8 // 40
Skill4 uint8 // 20
Skill5 uint8 // 40
Skill6 uint8 // 50
}
type TenrouiraiCharScore struct {
Score int32
Name string
}
type TenrouiraiTicket struct {
Unk0 uint8
RP uint32
Unk2 uint32
}
type Tenrouirai struct {
Progress []TenrouiraiProgress
Reward []TenrouiraiReward
KeyScore []TenrouiraiKeyScore
Data []TenrouiraiData
CharScore []TenrouiraiCharScore
Ticket []TenrouiraiTicket
}
func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetTenrouirai)
var data []*byteframe.ByteFrame
tenrouirai := Tenrouirai{
Progress: []TenrouiraiProgress{{1, 0, 0, 0}},
Data: tenrouiraiData,
Ticket: []TenrouiraiTicket{{0, 0, 0}},
}
switch pkt.Unk1 {
case 1:
for _, tdata := range tenrouirai.Data {
bf := byteframe.NewByteFrame()
bf.WriteUint8(tdata.Block)
bf.WriteUint8(tdata.Mission)
bf.WriteUint16(tdata.Goal)
bf.WriteUint16(tdata.Cost)
bf.WriteUint8(tdata.Skill1)
bf.WriteUint8(tdata.Skill2)
bf.WriteUint8(tdata.Skill3)
bf.WriteUint8(tdata.Skill4)
bf.WriteUint8(tdata.Skill5)
bf.WriteUint8(tdata.Skill6)
data = append(data, bf)
}
case 2:
for _, reward := range tenrouirai.Reward {
bf := byteframe.NewByteFrame()
bf.WriteUint8(reward.Index)
bf.WriteUint16(reward.Item[0])
bf.WriteUint16(reward.Item[1])
bf.WriteUint16(reward.Item[2])
bf.WriteUint16(reward.Item[3])
bf.WriteUint16(reward.Item[4])
bf.WriteUint8(reward.Quantity[0])
bf.WriteUint8(reward.Quantity[1])
bf.WriteUint8(reward.Quantity[2])
bf.WriteUint8(reward.Quantity[3])
bf.WriteUint8(reward.Quantity[4])
data = append(data, bf)
}
case 4:
s.server.db.QueryRow(`SELECT tower_mission_page FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&tenrouirai.Progress[0].Page)
s.server.db.QueryRow(`SELECT SUM(tower_mission_1) AS _, SUM(tower_mission_2) AS _, SUM(tower_mission_3) AS _ FROM guild_characters WHERE guild_id=$1
`, pkt.GuildID).Scan(&tenrouirai.Progress[0].Mission1, &tenrouirai.Progress[0].Mission2, &tenrouirai.Progress[0].Mission3)
if tenrouirai.Progress[0].Mission1 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-3].Goal {
tenrouirai.Progress[0].Mission1 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-3].Goal
}
if tenrouirai.Progress[0].Mission2 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-2].Goal {
tenrouirai.Progress[0].Mission2 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-2].Goal
}
if tenrouirai.Progress[0].Mission3 > tenrouiraiData[(tenrouirai.Progress[0].Page*3)-1].Goal {
tenrouirai.Progress[0].Mission3 = tenrouiraiData[(tenrouirai.Progress[0].Page*3)-1].Goal
}
for _, progress := range tenrouirai.Progress {
bf := byteframe.NewByteFrame()
bf.WriteUint8(progress.Page)
bf.WriteUint16(progress.Mission1)
bf.WriteUint16(progress.Mission2)
bf.WriteUint16(progress.Mission3)
data = append(data, bf)
}
case 5:
if pkt.Unk3 > 3 {
pkt.Unk3 %= 3
if pkt.Unk3 == 0 {
pkt.Unk3 = 3
}
}
rows, _ := s.server.db.Query(fmt.Sprintf(`SELECT name, tower_mission_%d FROM guild_characters gc INNER JOIN characters c ON gc.character_id = c.id WHERE guild_id=$1 AND tower_mission_%d IS NOT NULL ORDER BY tower_mission_%d DESC`, pkt.Unk3, pkt.Unk3, pkt.Unk3), pkt.GuildID)
for rows.Next() {
temp := TenrouiraiCharScore{}
rows.Scan(&temp.Name, &temp.Score)
tenrouirai.CharScore = append(tenrouirai.CharScore, temp)
}
for _, charScore := range tenrouirai.CharScore {
bf := byteframe.NewByteFrame()
bf.WriteInt32(charScore.Score)
bf.WriteBytes(stringsupport.PaddedString(charScore.Name, 14, true))
data = append(data, bf)
}
case 6:
s.server.db.QueryRow(`SELECT tower_rp FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&tenrouirai.Ticket[0].RP)
for _, ticket := range tenrouirai.Ticket {
bf := byteframe.NewByteFrame()
bf.WriteUint8(ticket.Unk0)
bf.WriteUint32(ticket.RP)
bf.WriteUint32(ticket.Unk2)
data = append(data, bf)
}
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfPostTenrouirai(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostTenrouirai)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
s.logger.Debug(
p.Opcode().String(),
zap.Uint8("Unk0", pkt.Unk0),
zap.Uint8("Op", pkt.Op),
zap.Uint32("GuildID", pkt.GuildID),
zap.Uint8("Unk1", pkt.Unk1),
zap.Uint16("Floors", pkt.Floors),
zap.Uint16("Antiques", pkt.Antiques),
zap.Uint16("Chests", pkt.Chests),
zap.Uint16("Cats", pkt.Cats),
zap.Uint16("TRP", pkt.TRP),
zap.Uint16("Slays", pkt.Slays),
)
}
if pkt.Op == 2 {
var page, requirement, donated int
s.server.db.QueryRow(`SELECT tower_mission_page, tower_rp FROM guilds WHERE id=$1`, pkt.GuildID).Scan(&page, &donated)
for i := 0; i < (page*3)+1; i++ {
requirement += int(tenrouiraiData[i].Cost)
}
bf := byteframe.NewByteFrame()
sd, err := GetCharacterSaveData(s, s.charID)
if err == nil && sd != nil {
sd.RP -= pkt.DonatedRP
sd.Save(s)
if donated+int(pkt.DonatedRP) >= requirement {
s.server.db.Exec(`UPDATE guilds SET tower_mission_page=tower_mission_page+1 WHERE id=$1`, pkt.GuildID)
s.server.db.Exec(`UPDATE guild_characters SET tower_mission_1=NULL, tower_mission_2=NULL, tower_mission_3=NULL WHERE guild_id=$1`, pkt.GuildID)
pkt.DonatedRP = uint16(requirement - donated)
}
bf.WriteUint32(uint32(pkt.DonatedRP))
s.server.db.Exec(`UPDATE guilds SET tower_rp=tower_rp+$1 WHERE id=$2`, pkt.DonatedRP, pkt.GuildID)
} else {
bf.WriteUint32(0)
}
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgMhfPresentBox(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPresentBox)
var data []*byteframe.ByteFrame
/*
bf.WriteUint32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
bf.WriteInt32(0)
*/
doAckEarthSucceed(s, pkt.AckHandle, data)
}
type GemInfo struct {
Gem uint16
Quantity uint16
}
type GemHistory struct {
Gem uint16
Message uint16
Timestamp time.Time
Sender string
}
func handleMsgMhfGetGemInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGemInfo)
var data []*byteframe.ByteFrame
gemInfo := []GemInfo{}
gemHistory := []GemHistory{}
tempGems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&tempGems)
for i, v := range stringsupport.CSVElems(tempGems) {
gemInfo = append(gemInfo, GemInfo{uint16(((i / 5) * 256) + ((i % 5) + 1)), uint16(v)})
}
switch pkt.Unk0 {
case 1:
for _, info := range gemInfo {
bf := byteframe.NewByteFrame()
bf.WriteUint16(info.Gem)
bf.WriteUint16(info.Quantity)
data = append(data, bf)
}
case 2:
for _, history := range gemHistory {
bf := byteframe.NewByteFrame()
bf.WriteUint16(history.Gem)
bf.WriteUint16(history.Message)
bf.WriteUint32(uint32(history.Timestamp.Unix()))
bf.WriteBytes(stringsupport.PaddedString(history.Sender, 14, true))
data = append(data, bf)
}
}
doAckEarthSucceed(s, pkt.AckHandle, data)
}
func handleMsgMhfPostGemInfo(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostGemInfo)
if s.server.erupeConfig.DevModeOptions.QuestDebugTools {
s.logger.Debug(
p.Opcode().String(),
zap.Uint32("Op", pkt.Op),
zap.Uint32("Unk1", pkt.Unk1),
zap.Int32("Gem", pkt.Gem),
zap.Int32("Quantity", pkt.Quantity),
zap.Int32("CID", pkt.CID),
zap.Int32("Message", pkt.Message),
zap.Int32("Unk6", pkt.Unk6),
)
}
gems := "0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0"
s.server.db.QueryRow(`SELECT gems FROM tower WHERE char_id=$1`, s.charID).Scan(&gems)
switch pkt.Op {
case 1: // Add gem
i := int(((pkt.Gem / 256) * 5) + (((pkt.Gem - ((pkt.Gem / 256) * 256)) - 1) % 5))
s.server.db.Exec(`UPDATE tower SET gems=$1 WHERE char_id=$2`, stringsupport.CSVSetIndex(gems, i, stringsupport.CSVGetIndex(gems, i)+int(pkt.Quantity)), s.charID)
case 2: // Transfer gem
// no way im doing this for now
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfGetNotice(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetNotice)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfPostNotice(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfPostNotice)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -0,0 +1,56 @@
package channelserver
import (
"fmt"
"erupe-ce/network/mhfpacket"
)
func handleMsgSysInsertUser(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysDeleteUser(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysSetUserBinary)
s.server.userBinaryPartsLock.Lock()
s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload
s.server.userBinaryPartsLock.Unlock()
var exists []byte
err := s.server.db.QueryRow("SELECT type2 FROM user_binary WHERE id=$1", s.charID).Scan(&exists)
if err != nil {
s.server.db.Exec("INSERT INTO user_binary (id) VALUES ($1)", s.charID)
}
s.server.db.Exec(fmt.Sprintf("UPDATE user_binary SET type%d=$1 WHERE id=$2", pkt.BinaryType), pkt.RawDataPayload, s.charID)
msg := &mhfpacket.MsgSysNotifyUserBinary{
CharID: s.charID,
BinaryType: pkt.BinaryType,
}
s.server.BroadcastMHF(msg, s)
}
func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysGetUserBinary)
// Try to get the data.
s.server.userBinaryPartsLock.RLock()
defer s.server.userBinaryPartsLock.RUnlock()
data, ok := s.server.userBinaryParts[userBinaryPartID{charID: pkt.CharID, index: pkt.BinaryType}]
// If we can't get the real data, try to get it from the database.
if !ok {
err := s.server.db.QueryRow(fmt.Sprintf("SELECT type%d FROM user_binary WHERE id=$1", pkt.BinaryType), pkt.CharID).Scan(&data)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
} else {
doAckBufSucceed(s, pkt.AckHandle, data)
}
} else {
doAckBufSucceed(s, pkt.AckHandle, data)
}
}
func handleMsgSysNotifyUserBinary(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -0,0 +1,424 @@
package channelserver
import (
"fmt"
"net"
"strings"
"sync"
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/config"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"erupe-ce/server/discordbot"
"github.com/jmoiron/sqlx"
"go.uber.org/zap"
)
// Config struct allows configuring the server.
type Config struct {
ID uint16
Logger *zap.Logger
DB *sqlx.DB
DiscordBot *discordbot.DiscordBot
ErupeConfig *_config.Config
Name string
Enable bool
}
// Map key type for a user binary part.
type userBinaryPartID struct {
charID uint32
index uint8
}
// Server is a MHF channel server.
type Server struct {
sync.Mutex
Channels []*Server
ID uint16
GlobalID string
IP string
Port uint16
logger *zap.Logger
db *sqlx.DB
erupeConfig *_config.Config
acceptConns chan net.Conn
deleteConns chan net.Conn
sessions map[net.Conn]*Session
objectIDs map[*Session]uint16
listener net.Listener // Listener that is created when Server.Start is called.
isShuttingDown bool
stagesLock sync.RWMutex
stages map[string]*Stage
// Used to map different languages
dict map[string]string
// UserBinary
userBinaryPartsLock sync.RWMutex
userBinaryParts map[userBinaryPartID][]byte
// Semaphore
semaphoreLock sync.RWMutex
semaphore map[string]*Semaphore
semaphoreIndex uint32
// Discord chat integration
discordBot *discordbot.DiscordBot
name string
raviente *Raviente
}
type Raviente struct {
sync.Mutex
id uint16
register []uint32
state []uint32
support []uint32
}
func (s *Server) resetRaviente() {
for _, semaphore := range s.semaphore {
if strings.HasPrefix(semaphore.name, "hs_l0") {
return
}
}
s.logger.Debug("All Raviente Semaphores empty, resetting")
s.raviente.id = s.raviente.id + 1
s.raviente.register = make([]uint32, 30)
s.raviente.state = make([]uint32, 30)
s.raviente.support = make([]uint32, 30)
}
func (s *Server) GetRaviMultiplier() float64 {
raviSema := s.getRaviSemaphore()
if raviSema != nil {
var minPlayers int
if s.raviente.register[9] > 8 {
minPlayers = 24
} else {
minPlayers = 4
}
if len(raviSema.clients) > minPlayers {
return 1
}
return float64(minPlayers / len(raviSema.clients))
}
return 0
}
func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update bool) (uint32, uint32) {
var prev uint32
switch semaID {
case 0x40000:
switch index {
case 17, 28: // Ignore res and poison
break
default:
value = uint32(float64(value) * s.GetRaviMultiplier())
}
prev = s.raviente.state[index]
if prev != 0 && !update {
return prev, prev
}
s.raviente.state[index] += value
return prev, s.raviente.state[index]
case 0x50000:
prev = s.raviente.support[index]
if prev != 0 && !update {
return prev, prev
}
s.raviente.support[index] += value
return prev, s.raviente.support[index]
case 0x60000:
prev = s.raviente.register[index]
if prev != 0 && !update {
return prev, prev
}
s.raviente.register[index] += value
return prev, s.raviente.register[index]
}
return 0, 0
}
// NewServer creates a new Server type.
func NewServer(config *Config) *Server {
s := &Server{
ID: config.ID,
logger: config.Logger,
db: config.DB,
erupeConfig: config.ErupeConfig,
acceptConns: make(chan net.Conn),
deleteConns: make(chan net.Conn),
sessions: make(map[net.Conn]*Session),
objectIDs: make(map[*Session]uint16),
stages: make(map[string]*Stage),
userBinaryParts: make(map[userBinaryPartID][]byte),
semaphore: make(map[string]*Semaphore),
semaphoreIndex: 7,
discordBot: config.DiscordBot,
name: config.Name,
raviente: &Raviente{
id: 1,
register: make([]uint32, 30),
state: make([]uint32, 30),
support: make([]uint32, 30),
},
}
// Mezeporta
s.stages["sl1Ns200p0a0u0"] = NewStage("sl1Ns200p0a0u0")
// Rasta bar stage
s.stages["sl1Ns211p0a0u0"] = NewStage("sl1Ns211p0a0u0")
// Pallone Carvan
s.stages["sl1Ns260p0a0u0"] = NewStage("sl1Ns260p0a0u0")
// Pallone Guest House 1st Floor
s.stages["sl1Ns262p0a0u0"] = NewStage("sl1Ns262p0a0u0")
// Pallone Guest House 2nd Floor
s.stages["sl1Ns263p0a0u0"] = NewStage("sl1Ns263p0a0u0")
// Diva fountain / prayer fountain.
s.stages["sl2Ns379p0a0u0"] = NewStage("sl2Ns379p0a0u0")
// MezFes
s.stages["sl1Ns462p0a0u0"] = NewStage("sl1Ns462p0a0u0")
s.dict = getLangStrings(s)
return s
}
// Start starts the server in a new goroutine.
func (s *Server) Start() error {
l, err := net.Listen("tcp", fmt.Sprintf(":%d", s.Port))
if err != nil {
return err
}
s.listener = l
go s.acceptClients()
go s.manageSessions()
// Start the discord bot for chat integration.
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
s.discordBot.Session.AddHandler(s.onDiscordMessage)
}
return nil
}
// Shutdown tries to shut down the server gracefully.
func (s *Server) Shutdown() {
s.Lock()
s.isShuttingDown = true
s.Unlock()
s.listener.Close()
close(s.acceptConns)
}
func (s *Server) acceptClients() {
for {
conn, err := s.listener.Accept()
if err != nil {
s.Lock()
shutdown := s.isShuttingDown
s.Unlock()
if shutdown {
break
} else {
s.logger.Warn("Error accepting client", zap.Error(err))
continue
}
}
s.acceptConns <- conn
}
}
func (s *Server) manageSessions() {
for {
select {
case newConn := <-s.acceptConns:
// Gracefully handle acceptConns channel closing.
if newConn == nil {
s.Lock()
shutdown := s.isShuttingDown
s.Unlock()
if shutdown {
return
}
}
session := NewSession(s, newConn)
s.Lock()
s.sessions[newConn] = session
s.Unlock()
session.Start()
case delConn := <-s.deleteConns:
s.Lock()
delete(s.sessions, delConn)
s.Unlock()
}
}
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions.
func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data.
s.Lock()
defer s.Unlock()
for _, session := range s.sessions {
if session == ignoredSession {
continue
}
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}
func (s *Server) WorldcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session, ignoredChannel *Server) {
for _, c := range s.Channels {
if c == ignoredChannel {
continue
}
c.BroadcastMHF(pkt, ignoredSession)
}
}
// BroadcastChatMessage broadcasts a simple chat message to all the sessions.
func (s *Server) BroadcastChatMessage(message string) {
bf := byteframe.NewByteFrame()
bf.SetLE()
msgBinChat := &binpacket.MsgBinChat{
Unk0: 0,
Type: 5,
Flags: 0x80,
Message: message,
SenderName: s.name,
}
msgBinChat.Build(bf)
s.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{
CharID: 0xFFFFFFFF,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}, nil)
}
func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type uint8) {
bf := byteframe.NewByteFrame()
bf.SetLE()
bf.WriteUint16(0) // Unk
bf.WriteUint16(0x43) // Data len
bf.WriteUint16(3) // Unk len
var text string
switch _type {
case 2:
text = s.dict["ravienteBerserk"]
case 3:
text = s.dict["ravienteExtreme"]
case 4:
text = s.dict["ravienteExtremeLimited"]
case 5:
text = s.dict["ravienteBerserkSmall"]
default:
s.logger.Error("Unk raviente type", zap.Uint8("_type", _type))
}
ps.Uint16(bf, text, true)
bf.WriteBytes([]byte{0x5F, 0x53, 0x00})
bf.WriteUint32(ip) // IP address
bf.WriteUint16(port) // Port
bf.WriteUint16(0) // Unk
bf.WriteBytes(stage)
s.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{
CharID: 0x00000000,
BroadcastType: BroadcastTypeServer,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}, nil, s)
}
func (s *Server) DiscordChannelSend(charName string, content string) {
if s.erupeConfig.Discord.Enabled && s.discordBot != nil {
message := fmt.Sprintf("**%s**: %s", charName, content)
s.discordBot.RealtimeChannelSend(message)
}
}
func (s *Server) FindSessionByCharID(charID uint32) *Session {
for _, c := range s.Channels {
for _, session := range c.sessions {
if session.charID == charID {
return session
}
}
}
return nil
}
func (s *Server) FindObjectByChar(charID uint32) *Object {
s.stagesLock.RLock()
defer s.stagesLock.RUnlock()
for _, stage := range s.stages {
stage.RLock()
for objId := range stage.objects {
obj := stage.objects[objId]
if obj.ownerCharID == charID {
stage.RUnlock()
return obj
}
}
stage.RUnlock()
}
return nil
}
func (s *Server) NextSemaphoreID() uint32 {
for {
exists := false
s.semaphoreIndex = s.semaphoreIndex + 1
if s.semaphoreIndex > 0xFFFF {
s.semaphoreIndex = 1
}
for _, semaphore := range s.semaphore {
if semaphore.id == s.semaphoreIndex {
exists = true
break
}
}
if !exists {
break
}
}
return s.semaphoreIndex
}
func (s *Server) Season() uint8 {
sid := int64(((s.ID & 0xFF00) - 4096) / 256)
return uint8(((TimeAdjusted().Unix() / 86400) + sid) % 3)
}

View File

@@ -0,0 +1,112 @@
package channelserver
func getLangStrings(s *Server) map[string]string {
strings := make(map[string]string)
switch s.erupeConfig.Language {
case "jp":
strings["language"] = "日本語"
strings["cafeReset"] = "%d/%dにリセット"
strings["commandDisabled"] = "%sのコマンドは無効です"
strings["commandReload"] = "リロードします"
strings["commandKqfGet"] = "現在のキークエストフラグ:%x"
strings["commandKqfSetError"] = "キークエコマンドエラー 例:%s set xxxxxxxxxxxxxxxx"
strings["commandKqfSetSuccess"] = "キークエストのフラグが更新されました。ワールド/ランドを移動してください"
strings["commandRightsError"] = "コース更新コマンドエラー 例:%s x"
strings["commandRightsSuccess"] = "コース情報を更新しました:%d"
strings["commandCourseError"] = "コース確認コマンドエラー 例:%s <name>"
strings["commandCourseDisabled"] = "%sコースは無効です"
strings["commandCourseEnabled"] = "%sコースは有効です"
strings["commandCourseLocked"] = "%sコースはロックされています"
strings["commandTeleportError"] = "テレポートコマンドエラー 構文:%s x y"
strings["commandTeleportSuccess"] = "%d %dにテレポート"
strings["commandPSNError"] = "PSN連携コマンドエラー %s <psn id>"
strings["commandPSNSuccess"] = "PSN「%s」が連携されています"
strings["commandPSNExists"] = "PSNは既存のユーザに接続されています"
strings["commandRaviNoCommand"] = "ラヴィコマンドが指定されていません"
strings["commandRaviStartSuccess"] = "大討伐を開始します"
strings["commandRaviStartError"] = "大討伐は既に開催されています"
strings["commandRaviMultiplier"] = "ラヴィダメージ倍率:x%.2f"
strings["commandRaviResSuccess"] = "復活支援を実行します"
strings["commandRaviResError"] = "復活支援は実行されませんでした"
strings["commandRaviSedSuccess"] = "鎮静支援を実行します"
strings["commandRaviRequest"] = "鎮静支援を要請します"
strings["commandRaviError"] = "ラヴィコマンドが認識されません"
strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません"
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
strings["ravienteExtremeLimited"] = "<大討伐:猛狂期【極】(制限付)>が開催されました!"
strings["ravienteBerserkSmall"] = "<大討伐:猛狂期(小数)>が開催されました!"
strings["guildInviteName"] = "猟団勧誘のご案内"
strings["guildInvite"] = "猟団「%s」からの勧誘通知です。\n「勧誘に返答」より、返答を行ってください。"
strings["guildInviteSuccessName"] = "成功"
strings["guildInviteSuccess"] = "あなたは「%s」に参加できました。"
strings["guildInviteAcceptedName"] = "承諾されました"
strings["guildInviteAccepted"] = "招待した狩人が「%s」への招待を承諾しました。"
strings["guildInviteRejectName"] = "却下しました"
strings["guildInviteReject"] = "あなたは「%s」への参加を却下しました。"
strings["guildInviteDeclinedName"] = "辞退しました"
strings["guildInviteDeclined"] = "招待した狩人が「%s」への招待を辞退しました。"
default:
strings["language"] = "English"
strings["cafeReset"] = "Resets on %d/%d"
strings["commandDisabled"] = "%s command is disabled"
strings["commandReload"] = "Reloading players..."
strings["commandKqfGet"] = "KQF: %x"
strings["commandKqfSetError"] = "Error in command. Format: %s set xxxxxxxxxxxxxxxx"
strings["commandKqfSetSuccess"] = "KQF set, please switch Land/World"
strings["commandRightsError"] = "Error in command. Format: %s x"
strings["commandRightsSuccess"] = "Set rights integer: %d"
strings["commandCourseError"] = "Error in command. Format: %s <name>"
strings["commandCourseDisabled"] = "%s Course disabled"
strings["commandCourseEnabled"] = "%s Course enabled"
strings["commandCourseLocked"] = "%s Course is locked"
strings["commandTeleportError"] = "Error in command. Format: %s x y"
strings["commandTeleportSuccess"] = "Teleporting to %d %d"
strings["commandPSNError"] = "Error in command. Format: %s <psn id>"
strings["commandPSNSuccess"] = "Connected PSN ID: %s"
strings["commandPSNExists"] = "PSN ID is connected to another account!"
strings["commandRaviNoCommand"] = "No Raviente command specified!"
strings["commandRaviStartSuccess"] = "The Great Slaying will begin in a moment"
strings["commandRaviStartError"] = "The Great Slaying has already begun!"
strings["commandRaviMultiplier"] = "Raviente multiplier is currently %.2fx"
strings["commandRaviResSuccess"] = "Sending resurrection support!"
strings["commandRaviResError"] = "Resurrection support has not been requested!"
strings["commandRaviSedSuccess"] = "Sending sedation support if requested!"
strings["commandRaviRequest"] = "Requesting sedation support!"
strings["commandRaviError"] = "Raviente command not recognised!"
strings["commandRaviNoPlayers"] = "No one has joined the Great Slaying!"
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
strings["ravienteBerserk"] = "<Great Slaying: Berserk> is being held!"
strings["ravienteExtreme"] = "<Great Slaying: Extreme> is being held!"
strings["ravienteExtremeLimited"] = "<Great Slaying: Extreme (Limited)> is being held!"
strings["ravienteBerserkSmall"] = "<Great Slaying: Berserk (Small)> is being held!"
strings["guildInviteName"] = "Invitation!"
strings["guildInvite"] = "You have been invited to join\n「%s」\nDo you want to accept?"
strings["guildInviteSuccessName"] = "Success!"
strings["guildInviteSuccess"] = "You have successfully joined\n「%s」."
strings["guildInviteAcceptedName"] = "Accepted"
strings["guildInviteAccepted"] = "The recipient accepted your invitation to join\n「%s」."
strings["guildInviteRejectName"] = "Rejected"
strings["guildInviteReject"] = "You rejected the invitation to join\n「%s」."
strings["guildInviteDeclinedName"] = "Declined"
strings["guildInviteDeclined"] = "The recipient declined your invitation to join\n「%s」."
}
return strings
}

View File

@@ -0,0 +1,56 @@
package channelserver
import (
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"sync"
)
// Semaphore holds Semaphore-specific information
type Semaphore struct {
sync.RWMutex
// Semaphore ID string
name string
id uint32
// Map of session -> charID.
// These are clients that are registered to the Semaphore
clients map[*Session]uint32
// Max Players for Semaphore
maxPlayers uint16
}
// NewSemaphore creates a new Semaphore with intialized values
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore {
sema := &Semaphore{
name: ID,
id: s.NextSemaphoreID(),
clients: make(map[*Session]uint32),
maxPlayers: MaxPlayers,
}
return sema
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the Semaphore
func (s *Semaphore) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
// Broadcast the data.
for session := range s.clients {
if session == ignoredSession {
continue
}
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}

View File

@@ -0,0 +1,298 @@
package channelserver
import (
"encoding/binary"
"encoding/hex"
"erupe-ce/common/mhfcourse"
"fmt"
"io"
"net"
"sync"
"time"
"erupe-ce/common/byteframe"
"erupe-ce/common/stringstack"
"erupe-ce/network"
"erupe-ce/network/clientctx"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
type packet struct {
data []byte
nonBlocking bool
}
// Session holds state for the channel server connection.
type Session struct {
sync.Mutex
logger *zap.Logger
server *Server
rawConn net.Conn
cryptConn *network.CryptConn
sendPackets chan packet
clientContext *clientctx.ClientContext
lastPacket time.Time
objectIndex uint16
userEnteredStage bool // If the user has entered a stage before
stageID string
stage *Stage
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
stagePass string // Temporary storage
prevGuildID uint32 // Stores the last GuildID used in InfoGuild
charID uint32
logKey []byte
sessionStart int64
courses []mhfcourse.Course
token string
kqf []byte
kqfOverride bool
semaphore *Semaphore // Required for the stateful MsgSysUnreserveStage packet.
// A stack containing the stage movement history (push on enter/move, pop on back)
stageMoveStack *stringstack.StringStack
// Accumulated index used for identifying mail for a client
// I'm not certain why this is used, but since the client is sending it
// I want to rely on it for now as it might be important later.
mailAccIndex uint8
// Contains the mail list that maps accumulated indexes to mail IDs
mailList []int
// For Debuging
Name string
closed bool
}
// NewSession creates a new Session type.
func NewSession(server *Server, conn net.Conn) *Session {
s := &Session{
logger: server.logger.Named(conn.RemoteAddr().String()),
server: server,
rawConn: conn,
cryptConn: network.NewCryptConn(conn),
sendPackets: make(chan packet, 20),
clientContext: &clientctx.ClientContext{}, // Unused
lastPacket: time.Now(),
sessionStart: TimeAdjusted().Unix(),
stageMoveStack: stringstack.New(),
}
s.SetObjectID()
return s
}
// Start starts the session packet send and recv loop(s).
func (s *Session) Start() {
go func() {
s.logger.Debug("New connection", zap.String("RemoteAddr", s.rawConn.RemoteAddr().String()))
// Unlike the sign and entrance server,
// the client DOES NOT initalize the channel connection with 8 NULL bytes.
go s.sendLoop()
s.recvLoop()
}()
}
// QueueSend queues a packet (raw []byte) to be sent.
func (s *Session) QueueSend(data []byte) {
s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
select {
case s.sendPackets <- packet{data, false}:
// Enqueued data
default:
s.logger.Warn("Packet queue too full, flushing!")
var tempPackets []packet
for len(s.sendPackets) > 0 {
tempPacket := <-s.sendPackets
if !tempPacket.nonBlocking {
tempPackets = append(tempPackets, tempPacket)
}
}
for _, tempPacket := range tempPackets {
s.sendPackets <- tempPacket
}
s.sendPackets <- packet{data, false}
}
}
// QueueSendNonBlocking queues a packet (raw []byte) to be sent, dropping the packet entirely if the queue is full.
func (s *Session) QueueSendNonBlocking(data []byte) {
select {
case s.sendPackets <- packet{data, true}:
s.logMessage(binary.BigEndian.Uint16(data[0:2]), data, "Server", s.Name)
default:
s.logger.Warn("Packet queue too full, dropping!")
}
}
// QueueSendMHF queues a MHFPacket to be sent.
func (s *Session) QueueSendMHF(pkt mhfpacket.MHFPacket) {
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, s.clientContext)
// Queue it.
s.QueueSend(bf.Data())
}
// QueueAck is a helper function to queue an MSG_SYS_ACK with the given ack handle and data.
func (s *Session) QueueAck(ackHandle uint32, data []byte) {
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(network.MSG_SYS_ACK))
bf.WriteUint32(ackHandle)
bf.WriteBytes(data)
s.QueueSend(bf.Data())
}
func (s *Session) sendLoop() {
for {
if s.closed {
return
}
pkt := <-s.sendPackets
err := s.cryptConn.SendPacket(append(pkt.data, []byte{0x00, 0x10}...))
if err != nil {
s.logger.Warn("Failed to send packet")
}
time.Sleep(10 * time.Millisecond)
}
}
func (s *Session) recvLoop() {
for {
if time.Now().Add(-30 * time.Second).After(s.lastPacket) {
logoutPlayer(s)
return
}
if s.closed {
logoutPlayer(s)
return
}
pkt, err := s.cryptConn.ReadPacket()
if err == io.EOF {
s.logger.Info(fmt.Sprintf("[%s] Disconnected", s.Name))
logoutPlayer(s)
return
}
if err != nil {
s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err))
logoutPlayer(s)
return
}
s.handlePacketGroup(pkt)
time.Sleep(10 * time.Millisecond)
}
}
func (s *Session) handlePacketGroup(pktGroup []byte) {
s.lastPacket = time.Now()
bf := byteframe.NewByteFrameFromBytes(pktGroup)
opcodeUint16 := bf.ReadUint16()
opcode := network.PacketID(opcodeUint16)
// This shouldn't be needed, but it's better to recover and let the connection die than to panic the server.
defer func() {
if r := recover(); r != nil {
fmt.Printf("[%s]", s.Name)
fmt.Println("Recovered from panic", r)
}
}()
s.logMessage(opcodeUint16, pktGroup, s.Name, "Server")
if opcode == network.MSG_SYS_LOGOUT {
s.closed = true
return
}
// Get the packet parser and handler for this opcode.
mhfPkt := mhfpacket.FromOpcode(opcode)
if mhfPkt == nil {
fmt.Println("Got opcode which we don't know how to parse, can't parse anymore for this group")
return
}
// Parse the packet.
err := mhfPkt.Parse(bf, s.clientContext)
if err != nil {
fmt.Printf("\n!!! [%s] %s NOT IMPLEMENTED !!! \n\n\n", s.Name, opcode)
return
}
// Handle the packet.
handlerTable[opcode](s, mhfPkt)
// If there is more data on the stream that the .Parse method didn't read, then read another packet off it.
remainingData := bf.DataFromCurrent()
if len(remainingData) >= 2 {
s.handlePacketGroup(remainingData)
}
}
func ignored(opcode network.PacketID) bool {
ignoreList := []network.PacketID{
network.MSG_SYS_END,
network.MSG_SYS_PING,
network.MSG_SYS_NOP,
network.MSG_SYS_TIME,
network.MSG_SYS_EXTEND_THRESHOLD,
network.MSG_SYS_POSITION_OBJECT,
network.MSG_MHF_SAVEDATA,
}
set := make(map[network.PacketID]struct{}, len(ignoreList))
for _, s := range ignoreList {
set[s] = struct{}{}
}
_, r := set[opcode]
return r
}
func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipient string) {
if !s.server.erupeConfig.DevMode {
return
}
if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
return
} else if !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
return
}
opcodePID := network.PacketID(opcode)
if ignored(opcodePID) {
return
}
fmt.Printf("[%s] -> [%s]\n", sender, recipient)
fmt.Printf("Opcode: %s\n", opcodePID)
if len(data) <= s.server.erupeConfig.DevModeOptions.MaxHexdumpLength {
fmt.Printf("Data [%d bytes]:\n%s\n", len(data), hex.Dump(data))
} else {
fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data))
}
}
func (s *Session) SetObjectID() {
for i := uint16(1); i < 127; i++ {
exists := false
for _, j := range s.server.objectIDs {
if i == j {
exists = true
break
}
}
if !exists {
s.server.objectIDs[s] = i
return
}
}
s.server.objectIDs[s] = 0
}
func (s *Session) NextObjectID() uint32 {
bf := byteframe.NewByteFrame()
bf.WriteUint16(s.server.objectIDs[s])
s.objectIndex++
bf.WriteUint16(s.objectIndex)
bf.Seek(0, 0)
return bf.ReadUint32()
}

View File

@@ -0,0 +1,97 @@
package channelserver
import (
"sync"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
)
// Object holds infomation about a specific object.
type Object struct {
sync.RWMutex
id uint32
ownerCharID uint32
x, y, z float32
}
// stageBinaryKey is a struct used as a map key for identifying a stage binary part.
type stageBinaryKey struct {
id0 uint8
id1 uint8
}
// Stage holds stage-specific information
type Stage struct {
sync.RWMutex
// Stage ID string
id string
// Objects
objects map[uint32]*Object
objectIndex uint8
// Map of session -> charID.
// These are clients that are CURRENTLY in the stage
clients map[*Session]uint32
// Map of charID -> bool, key represents whether they are ready
// These are clients that aren't in the stage, but have reserved a slot (for quests, etc).
reservedClientSlots map[uint32]bool
// These are raw binary blobs that the stage owner sets,
// other clients expect the server to echo them back in the exact same format.
rawBinaryData map[stageBinaryKey][]byte
host *Session
maxPlayers uint16
password string
}
// NewStage creates a new stage with intialized values.
func NewStage(ID string) *Stage {
s := &Stage{
id: ID,
clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]bool),
objects: make(map[uint32]*Object),
objectIndex: 0,
rawBinaryData: make(map[stageBinaryKey][]byte),
maxPlayers: 4,
}
return s
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
func (s *Stage) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) {
s.Lock()
defer s.Unlock()
for session := range s.clients {
if session == ignoredSession {
continue
}
// Make the header
bf := byteframe.NewByteFrame()
bf.WriteUint16(uint16(pkt.Opcode()))
// Build the packet onto the byteframe.
pkt.Build(bf, session.clientContext)
// Enqueue in a non-blocking way that drops the packet if the connections send buffer channel is full.
session.QueueSendNonBlocking(bf.Data())
}
}
func (s *Stage) isCharInQuestByID(charID uint32) bool {
if _, exists := s.reservedClientSlots[charID]; exists {
return exists
}
return false
}
func (s *Stage) isQuest() bool {
return len(s.reservedClientSlots) > 0
}

View File

@@ -0,0 +1,29 @@
package channelserver
import (
"time"
)
func TimeAdjusted() time.Time {
baseTime := time.Now().In(time.FixedZone("UTC+9", 9*60*60))
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), baseTime.Hour(), baseTime.Minute(), baseTime.Second(), baseTime.Nanosecond(), baseTime.Location())
}
func TimeMidnight() time.Time {
baseTime := time.Now().In(time.FixedZone("UTC+9", 9*60*60))
return time.Date(baseTime.Year(), baseTime.Month(), baseTime.Day(), 0, 0, 0, 0, baseTime.Location())
}
func TimeWeekStart() time.Time {
midnight := TimeMidnight()
offset := (int(midnight.Weekday()) - 1) * -24
return midnight.Add(time.Hour * time.Duration(offset))
}
func TimeWeekNext() time.Time {
return TimeWeekStart().Add(time.Hour * 24 * 7)
}
func TimeGameAbsolute() uint32 {
return uint32((TimeAdjusted().Unix() - 2160) % 5760)
}