Merge branch 'main' into main

This commit is contained in:
Matthew
2023-11-19 14:34:30 -05:00
committed by GitHub
120 changed files with 2321 additions and 2663 deletions

View File

@@ -4,6 +4,7 @@ import (
"encoding/binary"
"encoding/hex"
"erupe-ce/common/mhfcourse"
"erupe-ce/common/mhfmon"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
@@ -16,8 +17,9 @@ import (
"crypto/rand"
"erupe-ce/common/byteframe"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
"math/bits"
"go.uber.org/zap"
)
// Temporary function to just return no results for a MSG_MHF_ENUMERATE* packet
@@ -231,7 +233,7 @@ func logoutPlayer(s *Session) {
s.server.db.Exec("UPDATE characters SET time_played = $1 WHERE id = $2", timePlayed, s.charID)
treasureHuntUnregister(s)
s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=NULL WHERE character_id=$1`, s.charID)
if s.stage == nil {
return
@@ -304,9 +306,20 @@ func handleMsgSysIssueLogkey(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysRecordLog(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysRecordLog)
if _config.ErupeConfig.RealClientMode == _config.ZZ {
bf := byteframe.NewByteFrameFromBytes(pkt.Data)
bf.Seek(32, 0)
var val uint8
for i := 0; i < 176; i++ {
val = bf.ReadUint8()
if val > 0 && mhfmon.Monsters[i].Large {
s.server.db.Exec(`INSERT INTO kill_logs (character_id, monster, quantity, timestamp) VALUES ($1, $2, $3, $4)`, s.charID, i, val, TimeAdjusted())
}
}
}
// remove a client returning to town from reserved slots to make sure the stage is hidden from board
delete(s.stage.reservedClientSlots, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgSysEcho(s *Session, p mhfpacket.MHFPacket) {}
@@ -598,7 +611,7 @@ func handleMsgMhfServerCommand(s *Session, p mhfpacket.MHFPacket) {}
func handleMsgMhfAnnounce(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAnnounce)
s.server.BroadcastRaviente(pkt.IPAddress, pkt.Port, pkt.StageID, pkt.Type)
s.server.BroadcastRaviente(pkt.IPAddress, pkt.Port, pkt.StageID, pkt.Data.ReadUint8())
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
@@ -692,16 +705,16 @@ func handleMsgMhfUpdateUnionItem(s *Session, p mhfpacket.MHFPacket) {
// Update item stacks
newItems := make([]Item, len(oldItems))
copy(newItems, oldItems)
for i := 0; i < int(pkt.Amount); i++ {
for i := 0; i < len(pkt.Items); i++ {
for j := 0; j <= len(oldItems); j++ {
if j == len(oldItems) {
var newItem Item
newItem.ItemId = pkt.Items[i].ItemId
newItem.ItemId = pkt.Items[i].ItemID
newItem.Amount = pkt.Items[i].Amount
newItems = append(newItems, newItem)
break
}
if pkt.Items[i].ItemId == oldItems[j].ItemId {
if pkt.Items[i].ItemID == oldItems[j].ItemId {
newItems[j].Amount = pkt.Items[i].Amount
break
}
@@ -789,20 +802,20 @@ func getGookData(s *Session, cid uint32) (uint16, []byte) {
var count uint16
bf := byteframe.NewByteFrame()
for i := 0; i < 5; i++ {
err := s.server.db.QueryRow(fmt.Sprintf("SELECT gook%d FROM gook WHERE id=$1", i), cid).Scan(&data)
err := s.server.db.QueryRow(fmt.Sprintf("SELECT goocoo%d FROM goocoo WHERE id=$1", i), cid).Scan(&data)
if err != nil {
s.server.db.Exec("INSERT INTO gook (id) VALUES ($1)", s.charID)
s.server.db.Exec("INSERT INTO goocoo (id) VALUES ($1)", s.charID)
return 0, bf.Data()
}
if err == nil && data != nil {
count++
if s.charID == cid && count == 1 {
gook := byteframe.NewByteFrameFromBytes(data)
bf.WriteBytes(gook.ReadBytes(4))
d := gook.ReadBytes(2)
goocoo := byteframe.NewByteFrameFromBytes(data)
bf.WriteBytes(goocoo.ReadBytes(4))
d := goocoo.ReadBytes(2)
bf.WriteBytes(d)
bf.WriteBytes(d)
bf.WriteBytes(gook.DataFromCurrent())
bf.WriteBytes(goocoo.DataFromCurrent())
} else {
bf.WriteBytes(data)
}
@@ -822,744 +835,75 @@ func handleMsgMhfEnumerateGuacot(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfUpdateGuacot(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfUpdateGuacot)
for _, gook := range pkt.Gooks {
if !gook.Exists {
s.server.db.Exec(fmt.Sprintf("UPDATE gook SET gook%d=NULL WHERE id=$1", gook.Index), s.charID)
for _, goocoo := range pkt.Goocoos {
if goocoo.Data1[0] == 0 {
s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=NULL WHERE id=$1", goocoo.Index), s.charID)
} else {
bf := byteframe.NewByteFrame()
bf.WriteUint32(gook.Index)
bf.WriteUint16(gook.Type)
bf.WriteBytes(gook.Data)
bf.WriteUint8(gook.NameLen)
bf.WriteBytes(gook.Name)
s.server.db.Exec(fmt.Sprintf("UPDATE gook SET gook%d=$1 WHERE id=$2", gook.Index), bf.Data(), s.charID)
dumpSaveData(s, bf.Data(), fmt.Sprintf("goocoo-%d", gook.Index))
bf.WriteUint32(goocoo.Index)
for i := range goocoo.Data1 {
bf.WriteUint16(goocoo.Data1[i])
}
for i := range goocoo.Data2 {
bf.WriteUint32(goocoo.Data2[i])
}
bf.WriteUint8(uint8(len(goocoo.Name)))
bf.WriteBytes(goocoo.Name)
s.server.db.Exec(fmt.Sprintf("UPDATE goocoo SET goocoo%d=$1 WHERE id=$2", goocoo.Index), bf.Data(), s.charID)
dumpSaveData(s, bf.Data(), fmt.Sprintf("goocoo-%d", goocoo.Index))
}
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
type Scenario struct {
MainID uint32
// 0 = Basic
// 1 = Veteran
// 3 = Other
// 6 = Pallone
// 7 = Diva
CategoryID uint8
}
func handleMsgMhfInfoScenarioCounter(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfInfoScenarioCounter)
scenarioCounter := []struct {
MainID uint32
Unk1 uint8 // Bool item exchange?
// 0 = basic, 1 = veteran, 3 = other, 6 = pallone, 7 = diva
CategoryID uint8
}{
//000000110000
{
MainID: 0x00000011, Unk1: 0, CategoryID: 0,
},
// 0000005D0001
{
MainID: 0x0000005D, Unk1: 0, CategoryID: 1,
},
// 0000005C0001
{
MainID: 0x0000005C, Unk1: 0, CategoryID: 1,
},
// 000000510001
{
MainID: 0x00000051, Unk1: 0, CategoryID: 1,
},
// 0000005B0001
{
MainID: 0x0000005B, Unk1: 0, CategoryID: 1,
},
// 0000005A0001
{
MainID: 0x0000005A, Unk1: 0, CategoryID: 1,
},
// 000000590001
{
MainID: 0x00000059, Unk1: 0, CategoryID: 1,
},
// 000000580001
{
MainID: 0x00000058, Unk1: 0, CategoryID: 1,
},
// 000000570001
{
MainID: 0x00000057, Unk1: 0, CategoryID: 1,
},
// 000000560001
{
MainID: 0x00000056, Unk1: 0, CategoryID: 1,
},
// 000000550001
{
MainID: 0x00000055, Unk1: 0, CategoryID: 1,
},
// 000000540001
{
MainID: 0x00000054, Unk1: 0, CategoryID: 1,
},
// 000000530001
{
MainID: 0x00000053, Unk1: 0, CategoryID: 1,
},
// 000000520001
{
MainID: 0x00000052, Unk1: 0, CategoryID: 1,
},
// 000000570103
{
MainID: 0x00000057, Unk1: 1, CategoryID: 3,
},
// 000000580103
{
MainID: 0x00000058, Unk1: 1, CategoryID: 3,
},
// 000000590103
{
MainID: 0x00000059, Unk1: 1, CategoryID: 3,
},
// 0000005A0103
{
MainID: 0x0000005A, Unk1: 1, CategoryID: 3,
},
// 0000005B0103
{
MainID: 0x0000005B, Unk1: 1, CategoryID: 3,
},
// 0000005C0103
{
MainID: 0x0000005C, Unk1: 1, CategoryID: 3,
},
// 000000530103
{
MainID: 0x00000053, Unk1: 1, CategoryID: 3,
},
// 000000560103
{
MainID: 0x00000056, Unk1: 1, CategoryID: 3,
},
// 0000003C0103
{
MainID: 0x0000003C, Unk1: 1, CategoryID: 3,
},
// 0000003A0103
{
MainID: 0x0000003A, Unk1: 1, CategoryID: 3,
},
// 0000003B0103
{
MainID: 0x0000003B, Unk1: 1, CategoryID: 3,
},
// 0000001B0103
{
MainID: 0x0000001B, Unk1: 1, CategoryID: 3,
},
// 000000190103
{
MainID: 0x00000019, Unk1: 1, CategoryID: 3,
},
// 0000001A0103
{
MainID: 0x0000001A, Unk1: 1, CategoryID: 3,
},
// 000000170103
{
MainID: 0x00000017, Unk1: 1, CategoryID: 3,
},
// 000000020103
{
MainID: 0x00000002, Unk1: 1, CategoryID: 3,
},
// 000000030103
{
MainID: 0x00000003, Unk1: 1, CategoryID: 3,
},
// 000000040103
{
MainID: 0x00000004, Unk1: 1, CategoryID: 3,
},
// 0000001F0103
{
MainID: 0x0000001F, Unk1: 1, CategoryID: 3,
},
// 000000200103
{
MainID: 0x00000020, Unk1: 1, CategoryID: 3,
},
// 000000210103
{
MainID: 0x00000021, Unk1: 1, CategoryID: 3,
},
// 000000220103
{
MainID: 0x00000022, Unk1: 1, CategoryID: 3,
},
// 000000230103
{
MainID: 0x00000023, Unk1: 1, CategoryID: 3,
},
// 000000240103
{
MainID: 0x00000024, Unk1: 1, CategoryID: 3,
},
// 000000250103
{
MainID: 0x00000025, Unk1: 1, CategoryID: 3,
},
// 000000280103
{
MainID: 0x00000028, Unk1: 1, CategoryID: 3,
},
// 000000260103
{
MainID: 0x00000026, Unk1: 1, CategoryID: 3,
},
// 000000270103
{
MainID: 0x00000027, Unk1: 1, CategoryID: 3,
},
// 000000300103
{
MainID: 0x00000030, Unk1: 1, CategoryID: 3,
},
// 0000000C0103
{
MainID: 0x0000000C, Unk1: 1, CategoryID: 3,
},
// 0000000D0103
{
MainID: 0x0000000D, Unk1: 1, CategoryID: 3,
},
// 0000001E0103
{
MainID: 0x0000001E, Unk1: 1, CategoryID: 3,
},
// 0000001D0103
{
MainID: 0x0000001D, Unk1: 1, CategoryID: 3,
},
// 0000002E0003
{
MainID: 0x0000002E, Unk1: 0, CategoryID: 3,
},
// 000000000004
{
MainID: 0x00000000, Unk1: 0, CategoryID: 4,
},
// 000000010004
{
MainID: 0x00000001, Unk1: 0, CategoryID: 4,
},
// 000000020004
{
MainID: 0x00000002, Unk1: 0, CategoryID: 4,
},
// 000000030004
{
MainID: 0x00000003, Unk1: 0, CategoryID: 4,
},
// 000000040004
{
MainID: 0x00000004, Unk1: 0, CategoryID: 4,
},
// 000000050004
{
MainID: 0x00000005, Unk1: 0, CategoryID: 4,
},
// 000000060004
{
MainID: 0x00000006, Unk1: 0, CategoryID: 4,
},
// 000000070004
{
MainID: 0x00000007, Unk1: 0, CategoryID: 4,
},
// 000000080004
{
MainID: 0x00000008, Unk1: 0, CategoryID: 4,
},
// 000000090004
{
MainID: 0x00000009, Unk1: 0, CategoryID: 4,
},
// 0000000A0004
{
MainID: 0x0000000A, Unk1: 0, CategoryID: 4,
},
// 0000000B0004
{
MainID: 0x0000000B, Unk1: 0, CategoryID: 4,
},
// 0000000C0004
{
MainID: 0x0000000C, Unk1: 0, CategoryID: 4,
},
// 0000000D0004
{
MainID: 0x0000000D, Unk1: 0, CategoryID: 4,
},
// 0000000E0004
{
MainID: 0x0000000E, Unk1: 0, CategoryID: 4,
},
// 000000320005
{
MainID: 0x00000032, Unk1: 0, CategoryID: 5,
},
// 000000330005
{
MainID: 0x00000033, Unk1: 0, CategoryID: 5,
},
// 000000340005
{
MainID: 0x00000034, Unk1: 0, CategoryID: 5,
},
// 000000350005
{
MainID: 0x00000035, Unk1: 0, CategoryID: 5,
},
// 000000360005
{
MainID: 0x00000036, Unk1: 0, CategoryID: 5,
},
// 000000370005
{
MainID: 0x00000037, Unk1: 0, CategoryID: 5,
},
// 000000380005
{
MainID: 0x00000038, Unk1: 0, CategoryID: 5,
},
// 0000003A0005
{
MainID: 0x0000003A, Unk1: 0, CategoryID: 5,
},
// 0000003F0005
{
MainID: 0x0000003F, Unk1: 0, CategoryID: 5,
},
// 000000400005
{
MainID: 0x00000040, Unk1: 0, CategoryID: 5,
},
// 000000410005
{
MainID: 0x00000041, Unk1: 0, CategoryID: 5,
},
// 000000430005
{
MainID: 0x00000043, Unk1: 0, CategoryID: 5,
},
// 000000470005
{
MainID: 0x00000047, Unk1: 0, CategoryID: 5,
},
// 0000004B0005
{
MainID: 0x0000004B, Unk1: 0, CategoryID: 5,
},
// 0000003D0005
{
MainID: 0x0000003D, Unk1: 0, CategoryID: 5,
},
// 000000440005
{
MainID: 0x00000044, Unk1: 0, CategoryID: 5,
},
// 000000420005
{
MainID: 0x00000042, Unk1: 0, CategoryID: 5,
},
// 0000004C0005
{
MainID: 0x0000004C, Unk1: 0, CategoryID: 5,
},
// 000000460005
{
MainID: 0x00000046, Unk1: 0, CategoryID: 5,
},
// 0000004D0005
{
MainID: 0x0000004D, Unk1: 0, CategoryID: 5,
},
// 000000480005
{
MainID: 0x00000048, Unk1: 0, CategoryID: 5,
},
// 0000004A0005
{
MainID: 0x0000004A, Unk1: 0, CategoryID: 5,
},
// 000000490005
{
MainID: 0x00000049, Unk1: 0, CategoryID: 5,
},
// 0000004E0005
{
MainID: 0x0000004E, Unk1: 0, CategoryID: 5,
},
// 000000450005
{
MainID: 0x00000045, Unk1: 0, CategoryID: 5,
},
// 0000003E0005
{
MainID: 0x0000003E, Unk1: 0, CategoryID: 5,
},
// 0000004F0005
{
MainID: 0x0000004F, Unk1: 0, CategoryID: 5,
},
// 000000000106
{
MainID: 0x00000000, Unk1: 1, CategoryID: 6,
},
// 000000010106
{
MainID: 0x00000001, Unk1: 1, CategoryID: 6,
},
// 000000020106
{
MainID: 0x00000002, Unk1: 1, CategoryID: 6,
},
// 000000030106
{
MainID: 0x00000003, Unk1: 1, CategoryID: 6,
},
// 000000040106
{
MainID: 0x00000004, Unk1: 1, CategoryID: 6,
},
// 000000050106
{
MainID: 0x00000005, Unk1: 1, CategoryID: 6,
},
// 000000060106
{
MainID: 0x00000006, Unk1: 1, CategoryID: 6,
},
// 000000070106
{
MainID: 0x00000007, Unk1: 1, CategoryID: 6,
},
// 000000080106
{
MainID: 0x00000008, Unk1: 1, CategoryID: 6,
},
// 000000090106
{
MainID: 0x00000009, Unk1: 1, CategoryID: 6,
},
// 000000110106
{
MainID: 0x00000011, Unk1: 1, CategoryID: 6,
},
// 0000000A0106
{
MainID: 0x0000000A, Unk1: 1, CategoryID: 6,
},
// 0000000B0106
{
MainID: 0x0000000B, Unk1: 1, CategoryID: 6,
},
// 0000000C0106
{
MainID: 0x0000000C, Unk1: 1, CategoryID: 6,
},
// 0000000D0106
{
MainID: 0x0000000D, Unk1: 1, CategoryID: 6,
},
// 0000000E0106
{
MainID: 0x0000000E, Unk1: 1, CategoryID: 6,
},
// 0000000F0106
{
MainID: 0x0000000F, Unk1: 1, CategoryID: 6,
},
// 000000100106
{
MainID: 0x00000010, Unk1: 1, CategoryID: 6,
},
// 000000320107
{
MainID: 0x00000032, Unk1: 1, CategoryID: 7,
},
// 000000350107
{
MainID: 0x00000035, Unk1: 1, CategoryID: 7,
},
// 0000003E0107
{
MainID: 0x0000003E, Unk1: 1, CategoryID: 7,
},
// 000000340107
{
MainID: 0x00000034, Unk1: 1, CategoryID: 7,
},
// 000000380107
{
MainID: 0x00000038, Unk1: 1, CategoryID: 7,
},
// 000000330107
{
MainID: 0x00000033, Unk1: 1, CategoryID: 7,
},
// 000000310107
{
MainID: 0x00000031, Unk1: 1, CategoryID: 7,
},
// 000000360107
{
MainID: 0x00000036, Unk1: 1, CategoryID: 7,
},
// 000000390107
{
MainID: 0x00000039, Unk1: 1, CategoryID: 7,
},
// 000000370107
{
MainID: 0x00000037, Unk1: 1, CategoryID: 7,
},
// 0000003D0107
{
MainID: 0x0000003D, Unk1: 1, CategoryID: 7,
},
// 0000003A0107
{
MainID: 0x0000003A, Unk1: 1, CategoryID: 7,
},
// 0000003C0107
{
MainID: 0x0000003C, Unk1: 1, CategoryID: 7,
},
// 0000003B0107
{
MainID: 0x0000003B, Unk1: 1, CategoryID: 7,
},
// 0000002A0107
{
MainID: 0x0000002A, Unk1: 1, CategoryID: 7,
},
// 000000300107
{
MainID: 0x00000030, Unk1: 1, CategoryID: 7,
},
// 000000280107
{
MainID: 0x00000028, Unk1: 1, CategoryID: 7,
},
// 000000270107
{
MainID: 0x00000027, Unk1: 1, CategoryID: 7,
},
// 0000002B0107
{
MainID: 0x0000002B, Unk1: 1, CategoryID: 7,
},
// 0000002E0107
{
MainID: 0x0000002E, Unk1: 1, CategoryID: 7,
},
// 000000290107
{
MainID: 0x00000029, Unk1: 1, CategoryID: 7,
},
// 0000002C0107
{
MainID: 0x0000002C, Unk1: 1, CategoryID: 7,
},
// 0000002D0107
{
MainID: 0x0000002D, Unk1: 1, CategoryID: 7,
},
// 0000002F0107
{
MainID: 0x0000002F, Unk1: 1, CategoryID: 7,
},
// 000000250107
{
MainID: 0x00000025, Unk1: 1, CategoryID: 7,
},
// 000000220107
{
MainID: 0x00000022, Unk1: 1, CategoryID: 7,
},
// 000000210107
{
MainID: 0x00000021, Unk1: 1, CategoryID: 7,
},
// 000000200107
{
MainID: 0x00000020, Unk1: 1, CategoryID: 7,
},
// 0000001C0107
{
MainID: 0x0000001C, Unk1: 1, CategoryID: 7,
},
// 0000001A0107
{
MainID: 0x0000001A, Unk1: 1, CategoryID: 7,
},
// 000000240107
{
MainID: 0x00000024, Unk1: 1, CategoryID: 7,
},
// 000000260107
{
MainID: 0x00000026, Unk1: 1, CategoryID: 7,
},
// 000000230107
{
MainID: 0x00000023, Unk1: 1, CategoryID: 7,
},
// 0000001B0107
{
MainID: 0x0000001B, Unk1: 1, CategoryID: 7,
},
// 0000001E0107
{
MainID: 0x0000001E, Unk1: 1, CategoryID: 7,
},
// 0000001F0107
{
MainID: 0x0000001F, Unk1: 1, CategoryID: 7,
},
// 0000001D0107
{
MainID: 0x0000001D, Unk1: 1, CategoryID: 7,
},
// 000000180107
{
MainID: 0x00000018, Unk1: 1, CategoryID: 7,
},
// 000000170107
{
MainID: 0x00000017, Unk1: 1, CategoryID: 7,
},
// 000000160107
{
MainID: 0x00000016, Unk1: 1, CategoryID: 7,
},
// 000000150107
// Missing file
// {
// MainID: 0x00000015, Unk1: 1, CategoryID: 7,
// },
// 000000190107
{
MainID: 0x00000019, Unk1: 1, CategoryID: 7,
},
// 000000140107
// Missing file
// {
// MainID: 0x00000014, Unk1: 1, CategoryID: 7,
// },
// 000000070107
// Missing file
// {
// MainID: 0x00000007, Unk1: 1, CategoryID: 7,
// },
// 000000090107
// Missing file
// {
// MainID: 0x00000009, Unk1: 1, CategoryID: 7,
// },
// 0000000D0107
// Missing file
// {
// MainID: 0x0000000D, Unk1: 1, CategoryID: 7,
// },
// 000000100107
// Missing file
// {
// MainID: 0x00000010, Unk1: 1, CategoryID: 7,
// },
// 0000000C0107
// Missing file
// {
// MainID: 0x0000000C, Unk1: 1, CategoryID: 7,
// },
// 0000000E0107
// Missing file
// {
// MainID: 0x0000000E, Unk1: 1, CategoryID: 7,
// },
// 0000000F0107
// Missing file
// {
// MainID: 0x0000000F, Unk1: 1, CategoryID: 7,
// },
// 000000130107
// Missing file
// {
// MainID: 0x00000013, Unk1: 1, CategoryID: 7,
// },
// 0000000A0107
// Missing file
// {
// MainID: 0x0000000A, Unk1: 1, CategoryID: 7,
// },
// 000000080107
// Missing file
// {
// MainID: 0x00000008, Unk1: 1, CategoryID: 7,
// },
// 0000000B0107
// Missing file
// {
// MainID: 0x0000000B, Unk1: 1, CategoryID: 7,
// },
// 000000120107
// Missing file
// {
// MainID: 0x00000012, Unk1: 1, CategoryID: 7,
// },
// 000000110107
// Missing file
// {
// MainID: 0x00000011, Unk1: 1, CategoryID: 7,
// },
// 000000060107
// Missing file
// {
// MainID: 0x00000006, Unk1: 1, CategoryID: 7,
// },
// 000000050107
// Missing file
// {
// MainID: 0x00000005, Unk1: 1, CategoryID: 7,
// },
// 000000040107
// Missing file
// {
// MainID: 0x00000004, Unk1: 1, CategoryID: 7,
// },
// 000000030107
{
MainID: 0x00000003, Unk1: 1, CategoryID: 7,
},
// 000000020107
{
MainID: 0x00000002, Unk1: 1, CategoryID: 7,
},
// 000000010107
{
MainID: 0x00000001, Unk1: 1, CategoryID: 7,
},
// 000000000107
{
MainID: 0x00000000, Unk1: 1, CategoryID: 7,
},
var scenarios []Scenario
var scenario Scenario
scenarioData, err := s.server.db.Queryx("SELECT scenario_id, category_id FROM scenario_counter")
if err != nil {
scenarioData.Close()
s.logger.Error("Failed to get scenario counter info from db", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 1))
return
}
for scenarioData.Next() {
err = scenarioData.Scan(&scenario.MainID, &scenario.CategoryID)
if err != nil {
continue
}
scenarios = append(scenarios, scenario)
}
resp := byteframe.NewByteFrame()
resp.WriteUint8(uint8(len(scenarioCounter))) // Entry count
for _, entry := range scenarioCounter {
resp.WriteUint32(entry.MainID)
resp.WriteUint8(entry.Unk1)
resp.WriteUint8(entry.CategoryID)
// Trim excess scenarios
if len(scenarios) > 128 {
scenarios = scenarios[:128]
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
bf := byteframe.NewByteFrame()
bf.WriteUint8(uint8(len(scenarios)))
for _, scenario := range scenarios {
bf.WriteUint32(scenario.MainID)
// If item exchange
switch scenario.CategoryID {
case 3, 6, 7:
bf.WriteBool(true)
default:
bf.WriteBool(false)
}
bf.WriteUint8(scenario.CategoryID)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -6,11 +6,13 @@ import (
"erupe-ce/common/mhfcourse"
"erupe-ce/common/token"
"erupe-ce/config"
"erupe-ce/network"
"erupe-ce/network/binpacket"
"erupe-ce/network/mhfpacket"
"fmt"
"golang.org/x/exp/slices"
"math"
"strconv"
"strings"
"time"
@@ -82,29 +84,28 @@ func sendServerChatMessage(s *Session, message string) {
}
func parseChatCommand(s *Session, command string) {
if strings.HasPrefix(command, commands["PSN"].Prefix) {
args := strings.Split(command[1:], " ")
switch args[0] {
case commands["PSN"].Prefix:
if commands["PSN"].Enabled {
var id string
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%s", commands["PSN"].Prefix), &id)
if err != nil || n != 1 {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandPSNError"], commands["PSN"].Prefix))
} else {
if len(args) > 1 {
var exists int
s.server.db.QueryRow(`SELECT count(*) FROM users WHERE psn_id = $1`, id).Scan(&exists)
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)`, id, s.charID)
_, 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"], id))
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"])
}
}
if strings.HasPrefix(command, commands["Reload"].Prefix) {
// Flush all objects and users and reload
case commands["Reload"].Prefix:
if commands["Reload"].Enabled {
sendServerChatMessage(s, s.server.dict["commandReload"])
var temp mhfpacket.MHFPacket
@@ -125,7 +126,7 @@ func parseChatCommand(s *Session, command string) {
deleteNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(deleteNotif, s.clientContext)
}
deleteNotif.WriteUint16(0x0010)
deleteNotif.WriteUint16(uint16(network.MSG_SYS_END))
s.QueueSend(deleteNotif.Data())
time.Sleep(500 * time.Millisecond)
reloadNotif := byteframe.NewByteFrame()
@@ -160,69 +161,58 @@ func parseChatCommand(s *Session, command string) {
reloadNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(reloadNotif, s.clientContext)
}
reloadNotif.WriteUint16(0x0010)
reloadNotif.WriteUint16(uint16(network.MSG_SYS_END))
s.QueueSend(reloadNotif.Data())
} else {
sendDisabledCommandMessage(s, commands["Reload"])
}
}
if strings.HasPrefix(command, commands["KeyQuest"].Prefix) {
case commands["KeyQuest"].Prefix:
if commands["KeyQuest"].Enabled {
if strings.HasPrefix(command, fmt.Sprintf("%s get", commands["KeyQuest"].Prefix)) {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfGet"], s.kqf))
} else if strings.HasPrefix(command, fmt.Sprintf("%s set", commands["KeyQuest"].Prefix)) {
var hexs string
n, numerr := fmt.Sscanf(command, fmt.Sprintf("%s set %%s", commands["KeyQuest"].Prefix), &hexs)
if numerr != nil || n != 1 || len(hexs) != 16 {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandKqfSetError"], commands["KeyQuest"].Prefix))
} else {
hexd, _ := hex.DecodeString(hexs)
s.kqf = hexd
s.kqfOverride = true
sendServerChatMessage(s, s.server.dict["commandKqfSetSuccess"])
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"])
}
}
if strings.HasPrefix(command, commands["Rights"].Prefix) {
// Set account rights
case commands["Rights"].Prefix:
if commands["Rights"].Enabled {
var v uint32
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%d", commands["Rights"].Prefix), &v)
if err != nil || n != 1 {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRightsError"], commands["Rights"].Prefix))
} else {
_, 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 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"])
}
}
if strings.HasPrefix(command, commands["Course"].Prefix) {
case commands["Course"].Prefix:
if commands["Course"].Enabled {
var name string
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%s", commands["Course"].Prefix), &name)
if err != nil || n != 1 {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
} else {
name = strings.ToLower(name)
if len(args) > 1 {
for _, course := range mhfcourse.Courses() {
for _, alias := range course.Aliases() {
if strings.ToLower(name) == strings.ToLower(alias) {
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(name) == strings.ToLower(alias) {
if strings.ToLower(args[1]) == strings.ToLower(alias) {
return true
}
}
@@ -236,7 +226,7 @@ func parseChatCommand(s *Session, command string) {
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)
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)
}
@@ -248,82 +238,82 @@ func parseChatCommand(s *Session, command string) {
}
}
}
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandCourseError"], commands["Course"].Prefix))
}
} else {
sendDisabledCommandMessage(s, commands["Course"])
}
}
if strings.HasPrefix(command, commands["Raviente"].Prefix) {
case commands["Raviente"].Prefix:
if commands["Raviente"].Enabled {
if getRaviSemaphore(s.server) != nil {
s.server.raviente.Lock()
if !strings.HasPrefix(command, "!ravi ") {
sendServerChatMessage(s, s.server.dict["commandRaviNoCommand"])
} else {
if strings.HasPrefix(command, "!ravi start") {
if s.server.raviente.register.startTime == 0 {
s.server.raviente.register.startTime = s.server.raviente.register.postTime
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"])
}
} else if strings.HasPrefix(command, "!ravi cm") || strings.HasPrefix(command, "!ravi checkmultiplier") {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandRaviMultiplier"], s.server.raviente.GetRaviMultiplier(s.server)))
} else if strings.HasPrefix(command, "!ravi sr") || strings.HasPrefix(command, "!ravi sendres") {
if s.server.raviente.state.stateData[28] > 0 {
sendServerChatMessage(s, s.server.dict["commandRaviResSuccess"])
s.server.raviente.state.stateData[28] = 0
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["commandRaviResError"])
sendServerChatMessage(s, s.server.dict["commandRaviVersion"])
}
} else if strings.HasPrefix(command, "!ravi ss") || strings.HasPrefix(command, "!ravi sendsed") {
sendServerChatMessage(s, s.server.dict["commandRaviSedSuccess"])
// Total BerRavi HP
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
s.server.raviente.support.supportData[1] = HP
} else if strings.HasPrefix(command, "!ravi rs") || strings.HasPrefix(command, "!ravi reqsed") {
sendServerChatMessage(s, s.server.dict["commandRaviRequest"])
// Total BerRavi HP
HP := s.server.raviente.state.stateData[0] + s.server.raviente.state.stateData[1] + s.server.raviente.state.stateData[2] + s.server.raviente.state.stateData[3] + s.server.raviente.state.stateData[4]
s.server.raviente.support.supportData[1] = HP + 12
} else {
default:
sendServerChatMessage(s, s.server.dict["commandRaviError"])
}
} else {
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
}
s.server.raviente.Unlock()
} else {
sendServerChatMessage(s, s.server.dict["commandRaviNoPlayers"])
sendServerChatMessage(s, s.server.dict["commandRaviError"])
}
} else {
sendDisabledCommandMessage(s, commands["Raviente"])
}
}
if strings.HasPrefix(command, commands["Teleport"].Prefix) {
case commands["Teleport"].Prefix:
if commands["Teleport"].Enabled {
var x, y int16
n, err := fmt.Sscanf(command, fmt.Sprintf("%s %%d %%d", commands["Teleport"].Prefix), &x, &y)
if err != nil || n != 2 {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportError"], commands["Teleport"].Prefix))
} else {
sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandTeleportSuccess"], x, y))
// Make the inside of the casted binary
if len(args) > 2 {
x, _ := strconv.ParseInt(args[1], 10, 16)
y, _ := strconv.ParseInt(args[2], 10, 16)
payload := byteframe.NewByteFrame()
payload.SetLE()
payload.WriteUint8(2) // SetState type(position == 2)
payload.WriteInt16(x) // X
payload.WriteInt16(y) // Y
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"])
@@ -431,8 +421,9 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
}
case BroadcastTypeServer:
if pkt.MessageType == 1 {
if getRaviSemaphore(s.server) != nil {
s.server.BroadcastMHF(resp, s)
raviSema := s.server.getRaviSemaphore()
if raviSema != nil {
raviSema.BroadcastMHF(resp, s)
}
} else {
s.server.BroadcastMHF(resp, s)

View File

@@ -19,7 +19,7 @@ const (
pRP // +2
pHouseTier // +5
pHouseData // +195
pBookshelfData // +5576
pBookshelfData // +lBookshelfData
pGalleryData // +1748
pToreData // +240
pGardenData // +68
@@ -28,6 +28,7 @@ const (
pHRP // +2
pGRP // +4
pKQF // +8
lBookshelfData
)
type CharacterSaveData struct {
@@ -55,7 +56,7 @@ type CharacterSaveData struct {
}
func getPointers() map[SavePointer]int {
pointers := map[SavePointer]int{pGender: 81}
pointers := map[SavePointer]int{pGender: 81, lBookshelfData: 5576}
switch _config.ErupeConfig.RealClientMode {
case _config.ZZ:
pointers[pWeaponID] = 128522
@@ -83,6 +84,22 @@ func getPointers() map[SavePointer]int {
pointers[pGardenData] = 106424
pointers[pRP] = 106614
pointers[pKQF] = 110720
case _config.F5, _config.F4:
pointers[pWeaponID] = 60522
pointers[pWeaponType] = 60789
pointers[pHouseTier] = 61900
pointers[pToreData] = 62228
pointers[pHRP] = 62550
pointers[pHouseData] = 62561
pointers[pBookshelfData] = 57118 // This pointer only half works
pointers[pGalleryData] = 72064
pointers[pGardenData] = 74424
pointers[pRP] = 74614
}
if _config.ErupeConfig.RealClientMode == _config.G5 {
pointers[lBookshelfData] = 5548
} else if _config.ErupeConfig.RealClientMode <= _config.GG {
pointers[lBookshelfData] = 4520
}
return pointers
}
@@ -179,6 +196,8 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() {
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)
} else if _config.ErupeConfig.RealClientMode == _config.F5 || _config.ErupeConfig.RealClientMode == _config.F4 {
copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes)
}
}
@@ -191,21 +210,24 @@ func (save *CharacterSaveData) updateStructWithSaveData() {
save.Gender = false
}
if !save.IsNewCharacter {
if _config.ErupeConfig.RealClientMode >= _config.G10 {
if (_config.ErupeConfig.RealClientMode >= _config.F4 && _config.ErupeConfig.RealClientMode <= _config.F5) || _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.BookshelfData = save.decompSave[save.Pointers[pBookshelfData] : save.Pointers[pBookshelfData]+save.Pointers[lBookshelfData]]
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]))
}
}
if _config.ErupeConfig.RealClientMode >= _config.G10 {
save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8]
if save.HRP == uint16(999) {
save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4])))
}
}
}
return

View File

@@ -60,20 +60,19 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
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
if err == nil {
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))
}
count++
resp.WriteUint32(uint32(cid))
resp.WriteUint32(16)
resp.WriteBytes(stringsupport.PaddedString(name, 16, true))
}
resp.Seek(0, 0)
resp.WriteUint32(count)
@@ -83,28 +82,28 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
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)
for _, cid := range pkt.CharIDs {
if pkt.Blacklist {
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err == nil {
if pkt.Operation {
csv = stringsupport.CSVRemove(csv, int(cid))
} else {
csv = stringsupport.CSVAdd(csv, int(cid))
}
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 {
if pkt.Operation {
csv = stringsupport.CSVRemove(csv, int(cid))
} else {
csv = stringsupport.CSVAdd(csv, int(cid))
}
s.server.db.Exec("UPDATE characters SET friends=$1 WHERE id=$2", csv, s.charID)
}
}
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))
}

View File

@@ -2,6 +2,7 @@ package channelserver
import (
"erupe-ce/common/stringsupport"
_config "erupe-ce/config"
"fmt"
"io"
"os"
@@ -54,7 +55,7 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
s.Name = characterSaveData.Name
}
if characterSaveData.Name == s.Name {
if characterSaveData.Name == s.Name || _config.ErupeConfig.RealClientMode <= _config.S10 {
characterSaveData.Save(s)
s.logger.Info("Wrote recompressed savedata back to DB.")
} else {
@@ -72,162 +73,39 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func grpToGR(n uint32) uint16 {
var gr uint16
gr = 1
switch grp := int(n); {
case grp < 208750: // Up to 50
i := 0
for {
grp -= 500
if grp <= 500 {
if grp < 0 {
i--
func grpToGR(n int) uint16 {
var gr int
a := []int{208750, 593400, 993400, 1400900, 2315900, 3340900, 4505900, 5850900, 7415900, 9230900, 11345900, 100000000}
b := []int{7850, 8000, 8150, 9150, 10250, 11650, 13450, 15650, 18150, 21150, 23950}
c := []int{51, 100, 150, 200, 300, 400, 500, 600, 700, 800, 900}
for i := 0; i < len(a); i++ {
if n < a[i] {
if i == 0 {
for {
n -= 500
if n <= 500 {
if n < 0 {
i--
}
break
} else {
i++
for j := 0; j < i; j++ {
n -= 150
}
}
}
break
gr = i + 2
} else {
i++
for j := 0; j < i; j++ {
grp -= 150
}
n -= a[i-1]
gr = c[i-1]
gr += n / b[i-1]
}
break
}
gr = uint16(i + 2)
break
case grp < 593400: // 51-99
grp -= 208750
i := 51
for {
if grp < 7850 {
break
}
i++
grp -= 7850
}
gr = uint16(i)
break
case grp < 993400: // 100-149
grp -= 593400
i := 100
for {
if grp < 8000 {
break
}
i++
grp -= 8000
}
gr = uint16(i)
break
case grp < 1400900: // 150-199
grp -= 993400
i := 150
for {
if grp < 8150 {
break
}
i++
grp -= 8150
}
gr = uint16(i)
break
case grp < 2315900: // 200-299
grp -= 1400900
i := 200
for {
if grp < 9150 {
break
}
i++
grp -= 9150
}
gr = uint16(i)
break
case grp < 3340900: // 300-399
grp -= 2315900
i := 300
for {
if grp < 10250 {
break
}
i++
grp -= 10250
}
gr = uint16(i)
break
case grp < 4505900: // 400-499
grp -= 3340900
i := 400
for {
if grp < 11650 {
break
}
i++
grp -= 11650
}
gr = uint16(i)
break
case grp < 5850900: // 500-599
grp -= 4505900
i := 500
for {
if grp < 13450 {
break
}
i++
grp -= 13450
}
gr = uint16(i)
break
case grp < 7415900: // 600-699
grp -= 5850900
i := 600
for {
if grp < 15650 {
break
}
i++
grp -= 15650
}
gr = uint16(i)
break
case grp < 9230900: // 700-799
grp -= 7415900
i := 700
for {
if grp < 18150 {
break
}
i++
grp -= 18150
}
gr = uint16(i)
break
case grp < 11345900: // 800-899
grp -= 9230900
i := 800
for {
if grp < 21150 {
break
}
i++
grp -= 21150
}
gr = uint16(i)
break
default: // 900+
grp -= 11345900
i := 900
for {
if grp < 23950 {
break
}
i++
grp -= 23950
}
gr = uint16(i)
break
}
return gr
return uint16(gr)
}
func dumpSaveData(s *Session, data []byte, suffix string) {
@@ -301,7 +179,7 @@ func handleMsgMhfLoadScenarioData(s *Session, p mhfpacket.MHFPacket) {
var scenarioData []byte
bf := byteframe.NewByteFrame()
err := s.server.db.QueryRow("SELECT scenariodata FROM characters WHERE id = $1", s.charID).Scan(&scenarioData)
if err != nil {
if err != nil || len(scenarioData) < 10 {
s.logger.Error("Failed to load scenariodata", zap.Error(err))
bf.WriteBytes(make([]byte, 10))
} else {

View File

@@ -4,143 +4,161 @@ import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/network/mhfpacket"
"time"
"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"`
type Distribution struct {
ID uint32 `db:"id"`
Deadline time.Time `db:"deadline"`
TimesAcceptable uint16 `db:"times_acceptable"`
TimesAccepted uint16 `db:"times_accepted"`
MinHR int16 `db:"min_hr"`
MaxHR int16 `db:"max_hr"`
MinSR int16 `db:"min_sr"`
MaxSR int16 `db:"max_sr"`
MinGR int16 `db:"min_gr"`
MaxGR int16 `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)
var itemDists []Distribution
bf := byteframe.NewByteFrame()
distCount := 0
dists, err := s.server.db.Queryx(`
rows, 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,
COALESCE(min_hr, -1) AS min_hr, COALESCE(max_hr, -1) AS max_hr,
COALESCE(min_sr, -1) AS min_sr, COALESCE(max_sr, -1) AS max_sr,
COALESCE(min_gr, -1) AS min_gr, COALESCE(max_gr, -1) AS max_gr,
(
SELECT count(*)
FROM distributions_accepted da
WHERE d.id = da.distribution_id
AND da.character_id = $1
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
COALESCE(deadline, TO_TIMESTAMP(0)) AS 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)
WHERE character_id = $1 AND type = $2 OR character_id IS NULL AND type = $2 ORDER BY id DESC
`, s.charID, pkt.DistType)
if err == nil {
var itemDist Distribution
for rows.Next() {
err = rows.StructScan(&itemDist)
if err != nil {
s.logger.Error("Error parsing item distribution data", zap.Error(err))
continue
}
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))
itemDists = append(itemDists, itemDist)
}
resp := byteframe.NewByteFrame()
resp.WriteUint16(uint16(distCount))
resp.WriteBytes(bf.Data())
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
bf.WriteUint16(uint16(len(itemDists)))
for _, dist := range itemDists {
bf.WriteUint32(dist.ID)
bf.WriteUint32(uint32(dist.Deadline.Unix()))
bf.WriteUint32(0) // Unk
bf.WriteUint16(dist.TimesAcceptable)
bf.WriteUint16(dist.TimesAccepted)
bf.WriteUint16(0) // Unk
bf.WriteInt16(dist.MinHR)
bf.WriteInt16(dist.MaxHR)
bf.WriteInt16(dist.MinSR)
bf.WriteInt16(dist.MaxSR)
bf.WriteInt16(dist.MinGR)
bf.WriteInt16(dist.MaxGR)
bf.WriteUint8(0)
bf.WriteUint16(0)
bf.WriteUint8(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint8(0)
ps.Uint8(bf, dist.EventName, true)
for i := 0; i < 6; i++ {
for j := 0; j < 13; j++ {
bf.WriteUint8(0)
bf.WriteUint32(0)
}
}
i := uint8(0)
bf.WriteUint8(i)
if i <= 10 {
for j := uint8(0); j < i; j++ {
bf.WriteUint32(0)
bf.WriteUint32(0)
bf.WriteUint32(0)
}
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
type DistributionItem struct {
ItemType uint8 `db:"item_type"`
ID uint32 `db:"id"`
ItemID uint32 `db:"item_id"`
Quantity uint32 `db:"quantity"`
}
func getDistributionItems(s *Session, i uint32) []DistributionItem {
var distItems []DistributionItem
rows, err := s.server.db.Queryx(`SELECT id, item_type, COALESCE(item_id, 0) AS item_id, COALESCE(quantity, 0) AS quantity FROM distribution_items WHERE distribution_id=$1`, i)
if err == nil {
var distItem DistributionItem
for rows.Next() {
err = rows.StructScan(&distItem)
if err != nil {
continue
}
distItems = append(distItems, distItem)
}
}
return distItems
}
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))
}
bf := byteframe.NewByteFrame()
bf.WriteUint32(pkt.DistributionID)
distItems := getDistributionItems(s, pkt.DistributionID)
bf.WriteUint16(uint16(len(distItems)))
for _, item := range distItems {
bf.WriteUint8(item.ItemType)
bf.WriteUint32(item.ItemID)
bf.WriteUint32(item.Quantity)
bf.WriteUint32(item.ID)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfAcquireDistItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireDistItem)
if pkt.DistributionID > 0 {
_, err := s.server.db.Exec(`INSERT INTO public.distributions_accepted VALUES ($1, $2)`, pkt.DistributionID, s.charID)
if err == nil {
distItems := getDistributionItems(s, pkt.DistributionID)
for _, item := range distItems {
switch item.ItemType {
case 17:
_ = addPointNetcafe(s, int(item.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)", item.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)", item.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)", item.Quantity, s.charID)
case 23:
saveData, err := GetCharacterSaveData(s, s.charID)
if err == nil {
saveData.RP += uint16(item.Quantity)
saveData.Save(s)
}
}
}
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -10,44 +10,6 @@ import (
"erupe-ce/network/mhfpacket"
)
func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfRegisterEvent)
bf := byteframe.NewByteFrame()
bf.WriteUint8(pkt.Unk2)
bf.WriteUint8(pkt.Unk4)
bf.WriteUint16(0x1142)
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 Event struct {
EventType uint16
Unk1 uint16
@@ -237,15 +199,11 @@ func handleMsgMhfUseKeepLoginBoost(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrame()
bf.WriteUint8(0)
switch pkt.BoostWeekUsed {
case 1:
fallthrough
case 3:
case 1, 3:
expiration = TimeAdjusted().Add(120 * time.Minute)
case 4:
expiration = TimeAdjusted().Add(180 * time.Minute)
case 2:
fallthrough
case 5:
case 2, 5:
expiration = TimeAdjusted().Add(240 * time.Minute)
}
bf.WriteUint32(uint32(expiration.Unix()))

View File

@@ -96,7 +96,7 @@ 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")
s.server.db.Exec("UPDATE guild_characters SET souls=0, trial_vote=NULL")
}
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
@@ -141,13 +141,13 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
}
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
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 FestivalColour `db:"monopoly"`
Unk uint16
}
@@ -205,7 +205,19 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
var trials []FestaTrial
var trial FestaTrial
rows, _ = s.server.db.Queryx("SELECT * FROM festa_trials")
rows, _ = s.server.db.Queryx(`SELECT ft.*,
COALESCE(CASE
WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id) >
COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id)
THEN CAST('blue' AS public.festival_colour)
WHEN COUNT(gc.id) FILTER (WHERE fr.team = 'red' AND gc.trial_vote = ft.id) >
COUNT(gc.id) FILTER (WHERE fr.team = 'blue' AND gc.trial_vote = ft.id)
THEN CAST('red' AS public.festival_colour)
END, CAST('none' AS public.festival_colour)) AS monopoly
FROM public.festa_trials ft
LEFT JOIN public.guild_characters gc ON ft.id = gc.trial_vote
LEFT JOIN public.festa_registrations fr ON gc.guild_id = fr.guild_id
GROUP BY ft.id`)
for rows.Next() {
err := rows.StructScan(&trial)
if err != nil {
@@ -221,12 +233,12 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(trial.TimesReq)
bf.WriteUint16(trial.Locale)
bf.WriteUint16(trial.Reward)
trial.Monopoly = 0xFFFF // NYI
bf.WriteUint16(trial.Monopoly)
bf.WriteInt16(int16(FestivalColourCodes[trial.Monopoly]))
bf.WriteUint16(trial.Unk)
}
// The Winner and Loser Armor IDs are missing
// Item 7011 may not exist in older versions, remove to prevent crashes
rewards := []FestaReward{
{1, 0, 7, 350, 1520, 0, 0, 0},
{1, 0, 7, 1000, 7011, 0, 0, 1},
@@ -254,6 +266,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
{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)
@@ -261,11 +274,13 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint16(reward.ItemType)
bf.WriteUint16(reward.Quantity)
bf.WriteUint16(reward.ItemID)
bf.WriteUint16(reward.Unk5)
bf.WriteUint16(reward.Unk6)
bf.WriteUint8(reward.Unk7)
// Not confirmed to be G1 but exists in G3
if _config.ErupeConfig.RealClientMode >= _config.G1 {
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
@@ -391,6 +406,7 @@ func handleMsgMhfEnumerateFestaMember(s *Session, p mhfpacket.MHFPacket) {
func handleMsgMhfVoteFesta(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfVoteFesta)
s.server.db.Exec(`UPDATE guild_characters SET trial_vote=$1 WHERE character_id=$2`, pkt.TrialID, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -4,7 +4,6 @@ import (
"database/sql"
"database/sql/driver"
"encoding/binary"
"encoding/hex"
"encoding/json"
"errors"
_config "erupe-ce/config"
@@ -26,14 +25,14 @@ type FestivalColour string
const (
FestivalColourNone FestivalColour = "none"
FestivalColourRed FestivalColour = "red"
FestivalColourBlue FestivalColour = "blue"
FestivalColourRed FestivalColour = "red"
)
var FestivalColourCodes = map[FestivalColour]uint8{
FestivalColourBlue: 0x00,
FestivalColourRed: 0x01,
FestivalColourNone: 0xFF,
var FestivalColourCodes = map[FestivalColour]int8{
FestivalColourNone: -1,
FestivalColourBlue: 0,
FestivalColourRed: 1,
}
type GuildApplicationType string
@@ -968,7 +967,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(uint8(len(guildLeaderName)))
bf.WriteBytes(guildName)
bf.WriteBytes(guildComment)
bf.WriteUint8(FestivalColourCodes[guild.FestivalColour])
bf.WriteInt8(FestivalColourCodes[guild.FestivalColour])
bf.WriteUint32(guild.RankRP)
bf.WriteBytes(guildLeaderName)
bf.WriteUint32(0) // Unk
@@ -1148,7 +1147,6 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
var alliances []*GuildAlliance
var rows *sqlx.Rows
var err error
bf := byteframe.NewByteFrameFromBytes(pkt.Data1)
if pkt.Type <= 8 {
var tempGuilds []*Guild
@@ -1165,20 +1163,20 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
switch pkt.Type {
case mhfpacket.ENUMERATE_GUILD_TYPE_GUILD_NAME:
for _, guild := range tempGuilds {
if strings.Contains(guild.Name, pkt.Data2) {
if strings.Contains(guild.Name, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) {
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_NAME:
for _, guild := range tempGuilds {
if strings.Contains(guild.LeaderName, pkt.Data2) {
if strings.Contains(guild.LeaderName, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) {
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_LEADER_ID:
ID := bf.ReadUint32()
CID := pkt.Data1.ReadUint32()
for _, guild := range tempGuilds {
if guild.LeaderCharID == ID {
if guild.LeaderCharID == CID {
guilds = append(guilds, guild)
}
}
@@ -1216,15 +1214,15 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
}
guilds = tempGuilds
case mhfpacket.ENUMERATE_GUILD_TYPE_MOTTO:
mainMotto := uint8(bf.ReadUint16())
subMotto := uint8(bf.ReadUint16())
mainMotto := uint8(pkt.Data1.ReadUint16())
subMotto := uint8(pkt.Data1.ReadUint16())
for _, guild := range tempGuilds {
if guild.MainMotto == mainMotto && guild.SubMotto == subMotto {
guilds = append(guilds, guild)
}
}
case mhfpacket.ENUMERATE_GUILD_TYPE_RECRUITING:
recruitingMotto := uint8(bf.ReadUint16())
recruitingMotto := uint8(pkt.Data1.ReadUint16())
for _, guild := range tempGuilds {
if guild.MainMotto == recruitingMotto {
guilds = append(guilds, guild)
@@ -1245,20 +1243,20 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
switch pkt.Type {
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_ALLIANCE_NAME:
for _, alliance := range tempAlliances {
if strings.Contains(alliance.Name, pkt.Data2) {
if strings.Contains(alliance.Name, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) {
alliances = append(alliances, alliance)
}
}
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_NAME:
for _, alliance := range tempAlliances {
if strings.Contains(alliance.ParentGuild.LeaderName, pkt.Data2) {
if strings.Contains(alliance.ParentGuild.LeaderName, stringsupport.SJISToUTF8(pkt.Data2.ReadNullTerminatedBytes())) {
alliances = append(alliances, alliance)
}
}
case mhfpacket.ENUMERATE_ALLIANCE_TYPE_LEADER_ID:
ID := bf.ReadUint32()
CID := pkt.Data1.ReadUint32()
for _, alliance := range tempAlliances {
if alliance.ParentGuild.LeaderCharID == ID {
if alliance.ParentGuild.LeaderCharID == CID {
alliances = append(alliances, alliance)
}
}
@@ -1292,7 +1290,7 @@ func handleMsgMhfEnumerateGuild(s *Session, p mhfpacket.MHFPacket) {
return
}
bf = byteframe.NewByteFrame()
bf := byteframe.NewByteFrame()
if pkt.Type > 8 {
hasNextPage := false
@@ -1437,7 +1435,7 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
for _, member := range guildMembers {
bf.WriteUint32(member.CharID)
bf.WriteUint16(member.HRP)
if s.server.erupeConfig.RealClientMode > _config.G7 {
if s.server.erupeConfig.RealClientMode >= _config.G10 {
bf.WriteUint16(member.GR)
}
if s.server.erupeConfig.RealClientMode < _config.ZZ {
@@ -1559,7 +1557,7 @@ func handleMsgMhfEnumerateGuildItem(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildItem)
var boxContents []byte
bf := byteframe.NewByteFrame()
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents)
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", pkt.GuildID).Scan(&boxContents)
if err != nil {
s.logger.Error("Failed to get guild item box contents from db", zap.Error(err))
bf.WriteBytes(make([]byte, 4))
@@ -1593,7 +1591,7 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
// Get item cache from DB
var boxContents []byte
var oldItems []Item
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", int(pkt.GuildId)).Scan(&boxContents)
err := s.server.db.QueryRow("SELECT item_box FROM guilds WHERE id = $1", pkt.GuildID).Scan(&boxContents)
if err != nil {
s.logger.Error("Failed to get guild item box contents from db", zap.Error(err))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
@@ -1610,16 +1608,16 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
// Update item stacks
newItems := make([]Item, len(oldItems))
copy(newItems, oldItems)
for i := 0; i < int(pkt.Amount); i++ {
for i := 0; i < len(pkt.Items); i++ {
for j := 0; j <= len(oldItems); j++ {
if j == len(oldItems) {
var newItem Item
newItem.ItemId = pkt.Items[i].ItemId
newItem.ItemId = pkt.Items[i].ItemID
newItem.Amount = pkt.Items[i].Amount
newItems = append(newItems, newItem)
break
}
if pkt.Items[i].ItemId == oldItems[j].ItemId {
if pkt.Items[i].ItemID == oldItems[j].ItemId {
newItems[j].Amount = pkt.Items[i].Amount
break
}
@@ -1643,7 +1641,7 @@ func handleMsgMhfUpdateGuildItem(s *Session, p mhfpacket.MHFPacket) {
}
// Upload new item cache
_, err = s.server.db.Exec("UPDATE guilds SET item_box = $1 WHERE id = $2", bf.Data(), int(pkt.GuildId))
_, err = s.server.db.Exec("UPDATE guilds SET item_box = $1 WHERE id = $2", bf.Data(), pkt.GuildID)
if err != nil {
s.logger.Error("Failed to update guild item box contents in db", zap.Error(err))
}
@@ -1678,7 +1676,7 @@ func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
icon := &GuildIcon{}
icon.Parts = make([]GuildIconPart, pkt.PartCount)
icon.Parts = make([]GuildIconPart, len(pkt.IconParts))
for i, p := range pkt.IconParts {
icon.Parts[i] = GuildIconPart{
@@ -1723,16 +1721,51 @@ func handleMsgMhfReadGuildcard(s *Session, p mhfpacket.MHFPacket) {
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
type GuildMission struct {
ID uint32
Unk uint32
Type uint16
Goal uint16
Quantity uint16
SkipTickets uint16
GR bool
RewardType uint16
RewardLevel uint16
}
func handleMsgMhfGetGuildMissionList(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildMissionList)
decoded, err := hex.DecodeString("000694610000023E000112990023000100000200015DDD232100069462000002F30000005F000C000200000300025DDD232100069463000002EA0000005F0006000100000100015DDD23210006946400000245000000530010000200000400025DDD232100069465000002B60001129B0019000100000200015DDD232100069466000003DC0000001B0010000100000600015DDD232100069467000002DA000112A00019000100000400015DDD232100069468000002A800010DEF0032000200000200025DDD2321000694690000045500000022003C000200000600025DDD23210006946A00000080000122D90046000200000300025DDD23210006946B000001960000003B000A000100000100015DDD23210006946C0000049200000046005A000300000600035DDD23210006946D000000A4000000260018000200000600025DDD23210006946E0000017A00010DE40096000300000100035DDD23210006946F000001BE0000005E0014000200000400025DDD2355000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
if err != nil {
panic(err)
bf := byteframe.NewByteFrame()
missions := []GuildMission{
{431201, 574, 1, 4761, 35, 1, false, 2, 1},
{431202, 755, 0, 95, 12, 2, false, 3, 2},
{431203, 746, 0, 95, 6, 1, false, 1, 1},
{431204, 581, 0, 83, 16, 2, false, 4, 2},
{431205, 694, 1, 4763, 25, 1, false, 2, 1},
{431206, 988, 0, 27, 16, 1, false, 6, 1},
{431207, 730, 1, 4768, 25, 1, false, 4, 1},
{431208, 680, 1, 3567, 50, 2, false, 2, 2},
{431209, 1109, 0, 34, 60, 2, false, 6, 2},
{431210, 128, 1, 8921, 70, 2, false, 3, 2},
{431211, 406, 0, 59, 10, 1, false, 1, 1},
{431212, 1170, 0, 70, 90, 3, false, 6, 3},
{431213, 164, 0, 38, 24, 2, false, 6, 2},
{431214, 378, 1, 3556, 150, 3, false, 1, 3},
{431215, 446, 0, 94, 20, 2, false, 4, 2},
}
doAckBufSucceed(s, pkt.AckHandle, decoded)
for _, mission := range missions {
bf.WriteUint32(mission.ID)
bf.WriteUint32(mission.Unk)
bf.WriteUint16(mission.Type)
bf.WriteUint16(mission.Goal)
bf.WriteUint16(mission.Quantity)
bf.WriteUint16(mission.SkipTickets)
bf.WriteBool(mission.GR)
bf.WriteUint16(mission.RewardType)
bf.WriteUint16(mission.RewardLevel)
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfGetGuildMissionRecord(s *Session, p mhfpacket.MHFPacket) {
@@ -1817,14 +1850,14 @@ func handleMsgMhfGetGuildWeeklyBonusMaster(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusMaster)
// Values taken from brand new guild capture
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x28))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 40))
}
func handleMsgMhfGetGuildWeeklyBonusActiveCount(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildWeeklyBonusActiveCount)
bf := byteframe.NewByteFrame()
bf.WriteUint8(0x3C) // Active count
bf.WriteUint8(0x3C) // Current active count
bf.WriteUint8(0x00) // New active count
bf.WriteUint8(60) // Active count
bf.WriteUint8(60) // Current active count
bf.WriteUint8(0) // New active count
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -1832,18 +1865,54 @@ func handleMsgMhfGuildHuntdata(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGuildHuntdata)
bf := byteframe.NewByteFrame()
switch pkt.Operation {
case 0: // Unk
doAckBufSucceed(s, pkt.AckHandle, []byte{})
case 1: // Get Huntdata
case 0: // Acquire
s.server.db.Exec(`UPDATE guild_characters SET box_claimed=$1 WHERE character_id=$2`, TimeAdjusted(), s.charID)
case 1: // Enumerate
bf.WriteUint8(0) // Entries
/* Entry format
uint32 UnkID
uint32 MonID
*/
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
case 2: // Unk, controls glow
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00})
rows, err := s.server.db.Query(`SELECT kl.id, kl.monster FROM kill_logs kl
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
WHERE gc.guild_id=$1
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
`, pkt.GuildID, s.charID)
if err == nil {
var count uint8
var huntID, monID uint32
for rows.Next() {
err = rows.Scan(&huntID, &monID)
if err != nil {
continue
}
count++
if count > 255 {
count = 255
rows.Close()
break
}
bf.WriteUint32(huntID)
bf.WriteUint32(monID)
}
bf.Seek(0, 0)
bf.WriteUint8(count)
}
case 2: // Check
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err == nil {
var count uint8
err = s.server.db.QueryRow(`SELECT COUNT(*) FROM kill_logs kl
INNER JOIN guild_characters gc ON kl.character_id = gc.character_id
WHERE gc.guild_id=$1
AND kl.timestamp >= (SELECT box_claimed FROM guild_characters WHERE character_id=$2)
`, guild.ID, s.charID).Scan(&count)
if err == nil && count > 0 {
bf.WriteBool(true)
} else {
bf.WriteBool(false)
}
} else {
bf.WriteBool(false)
}
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
type MessageBoardPost struct {

View File

@@ -162,8 +162,7 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
}
case mhfpacket.OPERATE_JOINT_KICK:
if alliance.ParentGuild.LeaderCharID == s.charID {
_ = pkt.UnkData.ReadUint8()
kickedGuildID := pkt.UnkData.ReadUint32()
kickedGuildID := pkt.Data1.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 {

View File

@@ -4,72 +4,79 @@ import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"time"
)
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"`
HuntID uint32 `db:"id"`
HostID uint32 `db:"host_id"`
Destination uint32 `db:"destination"`
Level uint32 `db:"level"`
Start time.Time `db:"start"`
Acquired bool `db:"acquired"`
Collected bool `db:"collected"`
HuntData []byte `db:"hunt_data"`
Hunters uint32 `db:"hunters"`
Claimed bool `db:"claimed"`
}
func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
if err != nil || guild == nil {
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
var hunts []TreasureHunt
var hunt TreasureHunt
switch pkt.MaxHunts {
case 1:
err = s.server.db.QueryRowx(`SELECT id, host_id, destination, level, start, hunt_data FROM guild_hunts WHERE host_id=$1 AND acquired=FALSE`, s.charID).StructScan(&hunt)
if err == nil {
hunts = append(hunts, hunt)
}
case 30:
rows, err := s.server.db.Queryx(`SELECT gh.id, gh.host_id, gh.destination, gh.level, gh.start, gh.collected, gh.hunt_data,
(SELECT COUNT(*) FROM guild_characters gc WHERE gc.treasure_hunt = gh.id AND gc.character_id <> $1) AS hunters,
CASE
WHEN ghc.character_id IS NOT NULL THEN true
ELSE false
END AS claimed
FROM guild_hunts gh
LEFT JOIN guild_hunts_claimed ghc ON gh.id = ghc.hunt_id AND ghc.character_id = $1
WHERE gh.guild_id=$2 AND gh.level=2 AND gh.acquired=TRUE
`, s.charID, guild.ID)
if err != nil {
rows.Close()
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
} else {
for rows.Next() {
err = rows.StructScan(&hunt)
if err == nil && hunt.Start.Add(time.Second*time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntExpiry)).After(TimeAdjusted()) {
hunts = append(hunts, hunt)
}
}
}
if len(hunts) > 30 {
hunts = hunts[:30]
}
}
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)
}
bf.WriteUint16(uint16(len(hunts)))
bf.WriteUint16(uint16(len(hunts)))
for _, h := range hunts {
bf.WriteUint32(h.HuntID)
bf.WriteUint32(h.Destination)
bf.WriteUint32(h.Level)
bf.WriteUint32(h.Hunters)
bf.WriteUint32(uint32(h.Start.Unix()))
bf.WriteBool(h.Collected)
bf.WriteBool(h.Claimed)
bf.WriteBytes(h.HuntData)
}
resp := byteframe.NewByteFrame()
resp.WriteUint16(uint16(hunts))
resp.WriteUint16(uint16(hunts))
resp.WriteBytes(bf.Data())
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
@@ -77,8 +84,9 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
bf := byteframe.NewByteFrameFromBytes(pkt.Data)
huntData := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
if err != nil || guild == nil {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
guildCats := getGuildAirouList(s)
destination := bf.ReadUint32()
@@ -92,87 +100,55 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
if catID > 0 {
catsUsed = stringsupport.CSVAdd(catsUsed, int(catID))
for _, cat := range guildCats {
if cat.CatID == catID {
huntData.WriteBytes(cat.CatName)
if cat.ID == catID {
huntData.WriteBytes(cat.Name)
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)
}
s.server.db.Exec(`INSERT INTO guild_hunts (guild_id, host_id, destination, level, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6)
`, guild.ID, s.charID, destination, level, huntData.Data(), catsUsed)
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)
}
s.server.db.Exec(`UPDATE guild_hunts SET acquired=true WHERE id=$1`, pkt.HuntID)
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)
}
switch pkt.State {
case 0: // Report registration
s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=$1 WHERE character_id=$2`, pkt.HuntID, s.charID)
case 1: // Collected by hunter
s.server.db.Exec(`UPDATE guild_hunts SET collected=true WHERE id=$1`, pkt.HuntID)
s.server.db.Exec(`UPDATE guild_characters SET treasure_hunt=NULL WHERE treasure_hunt=$1`, pkt.HuntID)
case 2: // Claim treasure
s.server.db.Exec(`INSERT INTO guild_hunts_claimed VALUES ($1, $2)`, pkt.HuntID, s.charID)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
type TreasureSouvenir struct {
Destination uint32
Quantity uint32
}
func handleMsgMhfGetGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfGetGuildTresureSouvenir)
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 6))
bf := byteframe.NewByteFrame()
bf.WriteUint32(0)
souvenirs := []TreasureSouvenir{}
bf.WriteUint16(uint16(len(souvenirs)))
for _, souvenir := range souvenirs {
bf.WriteUint32(souvenir.Destination)
bf.WriteUint32(souvenir.Quantity)
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgMhfAcquireGuildTresureSouvenir(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -119,7 +119,9 @@ func handleMsgMhfEnumerateHouse(s *Session, p mhfpacket.MHFPacket) {
bf.WriteUint8(0)
}
bf.WriteUint16(house.HRP)
bf.WriteUint16(house.GR)
if _config.ErupeConfig.RealClientMode >= _config.G10 {
bf.WriteUint16(house.GR)
}
ps.Uint8(bf, house.Name, true)
}
bf.Seek(0, 0)
@@ -238,8 +240,8 @@ func handleMsgMhfGetMyhouseInfo(s *Session, p mhfpacket.MHFPacket) {
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})
s.server.db.Exec("UPDATE user_binary SET mission=$1 WHERE id=$2", pkt.Data, s.charID)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
@@ -250,7 +252,7 @@ func handleMsgMhfLoadDecoMyset(s *Session, p mhfpacket.MHFPacket) {
s.logger.Error("Failed to load decomyset", zap.Error(err))
}
if len(data) == 0 {
if s.server.erupeConfig.RealClientMode <= _config.G7 {
if s.server.erupeConfig.RealClientMode < _config.G10 {
data = []byte{0x00, 0x00}
}
data = []byte{0x01, 0x00}
@@ -353,12 +355,14 @@ func handleMsgMhfEnumerateTitle(s *Session, p mhfpacket.MHFPacket) {
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()")
for _, title := range pkt.TitleIDs {
var exists int
err := s.server.db.QueryRow(`SELECT count(*) FROM titles WHERE id=$1 AND char_id=$2`, title, s.charID).Scan(&exists)
if err != nil || exists == 0 {
s.server.db.Exec(`INSERT INTO titles VALUES ($1, $2, now(), now())`, title, s.charID)
} else {
s.server.db.Exec(`UPDATE titles SET updated_at=now() WHERE id=$1 AND char_id=$2`, title, s.charID)
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -79,57 +79,6 @@ func (m *Mail) MarkRead(s *Session) error {
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
@@ -256,26 +205,21 @@ 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")
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
return
}
mail, err := GetMailByID(s, mailId)
if err != nil {
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
return
}
_ = mail.MarkRead(s)
s.server.db.Exec(`UPDATE mail SET read = true WHERE id = $1`, mail.ID)
bf := byteframe.NewByteFrame()
body := stringsupport.UTF8ToSJIS(mail.Body)
bf.WriteNullTerminatedBytes(body)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
@@ -283,10 +227,9 @@ 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)
doAckBufSucceed(s, pkt.AckHandle, []byte{0})
return
}
if s.mailList == nil {
@@ -354,24 +297,20 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
if err != nil {
panic(err)
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
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)
case mhfpacket.OperateMailDelete:
s.server.db.Exec(`UPDATE mail SET deleted = true WHERE id = $1`, mail.ID)
case mhfpacket.OperateMailLock:
s.server.db.Exec(`UPDATE mail SET locked = TRUE WHERE id = $1`, mail.ID)
case mhfpacket.OperateMailUnlock:
s.server.db.Exec(`UPDATE mail SET locked = FALSE WHERE id = $1`, mail.ID)
case mhfpacket.OperateMailAcquireItem:
s.server.db.Exec(`UPDATE mail SET attached_item_received = TRUE WHERE id = $1`, mail.ID)
}
if err != nil {
panic(err)
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}

View File

@@ -9,8 +9,6 @@ import (
"erupe-ce/server/channelserver/compression/nullcomp"
"go.uber.org/zap"
"io"
"os"
"path/filepath"
"time"
)
@@ -299,18 +297,12 @@ func handleMsgMhfSaveOtomoAirou(s *Session, p mhfpacket.MHFPacket) {
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.ID)
resp.WriteBytes(cat.Name)
resp.WriteUint32(cat.Experience)
resp.WriteUint8(cat.Personality)
resp.WriteUint8(cat.Class)
@@ -321,11 +313,10 @@ func handleMsgMhfEnumerateAiroulist(s *Session, p mhfpacket.MHFPacket) {
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
type Airou struct {
ID uint32
Name []byte
Task uint8
Personality uint8
Class uint8
Experience uint32
@@ -333,46 +324,39 @@ type CatDefinition struct {
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)
func getGuildAirouList(s *Session) []Airou {
var guildCats []Airou
bannedCats := make(map[uint32]int)
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())
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
`, s.charID)
if err != nil {
s.logger.Warn("Failed to get recently used airous", zap.Error(err))
return guildCats
}
var csvTemp string
var startTemp time.Time
for rows.Next() {
rows.Scan(&csvTemp)
for i, j := range stringsupport.CSVElems(csvTemp) {
bannedCats[uint32(j)] = i
err = rows.Scan(&csvTemp, &startTemp)
if err != nil {
continue
}
if startTemp.Add(time.Second * time.Duration(s.server.erupeConfig.GameplayOptions.TreasureHuntPartnyaCooldown)).Before(TimeAdjusted()) {
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
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)
ORDER BY c.id LIMIT 60`, guild.ID)
if err != nil {
s.logger.Warn("Selecting otomoairou based on guild failed", zap.Error(err))
return guildCats
@@ -381,11 +365,7 @@ func getGuildAirouList(s *Session) []CatDefinition {
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
if err != nil || len(data) == 0 {
continue
}
// first byte has cat existence in general, can skip if 0
@@ -396,10 +376,10 @@ func getGuildAirouList(s *Session) []CatDefinition {
continue
}
bf := byteframe.NewByteFrameFromBytes(decomp)
cats := GetCatDetails(bf)
cats := GetAirouDetails(bf)
for _, cat := range cats {
_, exists := bannedCats[cat.CatID]
if cat.CurrentTask == 4 && !exists {
_, exists := bannedCats[cat.ID]
if cat.Task == 4 && !exists {
guildCats = append(guildCats, cat)
}
}
@@ -408,20 +388,20 @@ func getGuildAirouList(s *Session) []CatDefinition {
return guildCats
}
func GetCatDetails(bf *byteframe.ByteFrame) []CatDefinition {
func GetAirouDetails(bf *byteframe.ByteFrame) []Airou {
catCount := bf.ReadUint8()
cats := make([]CatDefinition, catCount)
cats := make([]Airou, catCount)
for x := 0; x < int(catCount); x++ {
var catDef CatDefinition
var catDef Airou
// 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()
catDef.ID = bf.ReadUint32()
bf.Seek(1, io.SeekCurrent) // unknown value, probably a bool
catDef.Name = bf.ReadBytes(18) // always 18 len, reads first null terminated string out of section and discards rest
catDef.Task = bf.ReadUint8()
bf.Seek(16, io.SeekCurrent) // appearance data and what is seemingly null bytes
catDef.Personality = bf.ReadUint8()
catDef.Class = bf.ReadUint8()

View File

@@ -42,7 +42,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
}
} else {
// create empty save if absent
data = make([]byte, 0x1AF20)
data = make([]byte, 140000)
}
// Perform diff and compress it to write back to db
@@ -110,7 +110,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
}
} else {
// create empty save if absent
data = make([]byte, 0x820)
data = make([]byte, 4800)
}
// Perform diff and compress it to write back to db
@@ -147,7 +147,7 @@ func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
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)
data = make([]byte, 1920)
}
doAckBufSucceed(s, pkt.AckHandle, data)
}

View File

@@ -62,25 +62,30 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
}
}
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))
filename := fmt.Sprintf("%s%d", questFile[:6], s.server.Season())
// Return original file if file doesn't exist
if _, err := os.Stat(filename); err == nil {
// Return the seasonal file
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", filename))); err == nil {
return filename
} else {
return questFile
// Attempt to return the requested quest file if the seasonal file doesn't exist
if _, err = os.Stat(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%s.bin", questFile))); err == nil {
return questFile
}
// If the code reaches this point, it's most likely a custom quest with no seasonal variations in the files.
// Since event quests when seasonal pick day or night and the client requests either one, we need to differentiate between the two to prevent issues.
var _time string
if TimeGameAbsolute() > 2880 {
_time = "d"
} else {
_time = "n"
}
// Request a d0 or n0 file depending on the time of day. The time of day matters and issues will occur if it's different to the one it requests.
return fmt.Sprintf("%s%s%d", questFile[:5], _time, 0)
}
}
@@ -103,6 +108,11 @@ func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) {
}
func loadQuestFile(s *Session, questId int) []byte {
data, exists := s.server.questCacheData[questId]
if exists && s.server.questCacheTime[questId].Add(time.Duration(s.server.erupeConfig.QuestCacheExpiry)*time.Second).After(time.Now()) {
return data
}
file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%05dd0.bin", questId)))
if err != nil {
return nil
@@ -139,15 +149,17 @@ func loadQuestFile(s *Session, questId int) []byte {
}
questBody.WriteBytes(newStrings.Data())
s.server.questCacheData[questId] = questBody.Data()
s.server.questCacheTime[questId] = time.Now()
return questBody.Data()
}
func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
var id, mark uint32
var questId, activeDuration, inactiveDuration int
var questId, activeDuration, inactiveDuration, flags int
var maxPlayers, questType uint8
var startTime time.Time
rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &startTime, &activeDuration, &inactiveDuration)
rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDuration, &inactiveDuration)
data := loadQuestFile(s, questId)
if data == nil {
@@ -156,8 +168,8 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
bf := byteframe.NewByteFrame()
bf.WriteUint32(id)
bf.WriteUint32(0)
bf.WriteUint8(0) // Indexer
bf.WriteUint32(0) // Unk
bf.WriteUint8(0) // Unk
switch questType {
case 16:
bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers)
@@ -178,15 +190,43 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) {
} else {
bf.WriteBool(true)
}
bf.WriteUint16(0)
bf.WriteUint16(0) // Unk
if _config.ErupeConfig.RealClientMode >= _config.G1 {
bf.WriteUint32(mark)
}
bf.WriteUint16(0)
bf.WriteUint16(0) // Unk
bf.WriteUint16(uint16(len(data)))
bf.WriteBytes(data)
ps.Uint8(bf, "", true) // What is this string for?
// Time Flag Replacement
// Bitset Structure: b8 UNK, b7 Required Objective, b6 UNK, b5 Night, b4 Day, b3 Cold, b2 Warm, b1 Spring
// if the byte is set to 0 the game choses the quest file corresponding to whatever season the game is on
bf.Seek(25, 0)
flagByte := bf.ReadUint8()
bf.Seek(25, 0)
if s.server.erupeConfig.GameplayOptions.SeasonOverride {
bf.WriteUint8(flagByte & 0b11100000)
} else {
// Allow for seasons to be specified in database, otherwise use the one in the file.
if flags < 0 {
bf.WriteUint8(flagByte)
} else {
bf.WriteUint8(uint8(flags))
}
}
// Bitset Structure Quest Variant 1: b8 UL Fixed, b7 UNK, b6 UNK, b5 UNK, b4 G Rank, b3 HC to UL, b2 Fix HC, b1 Hiden
// Bitset Structure Quest Variant 2: b8 Road, b7 High Conquest, b6 Fixed Difficulty, b5 No Active Feature, b4 Timer, b3 No Cuff, b2 No Halk Pots, b1 Low Conquest
// Bitset Structure Quest Variant 3: b8 No Sigils, b7 UNK, b6 Interception, b5 Zenith, b4 No GP Skills, b3 No Simple Mode?, b2 GSR to GR, b1 No Reward Skills
bf.Seek(175, 0)
questVariant3 := bf.ReadUint8()
questVariant3 &= 0b11011111 // disable Interception flag
bf.Seek(175, 0)
bf.WriteUint8(questVariant3)
bf.Seek(0, 2)
ps.Uint8(bf, "", true) // Debug/Notes string for quest
return bf.Data(), nil
}
@@ -205,7 +245,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
currentTime := time.Now()
// Check the event_quests table to load the quests with rotation system
rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, start_time, COALESCE(active_duration, 1) AS active_duration, COALESCE(inactive_duration, 0) AS inactive_duration FROM event_quests")
rows, err := s.server.db.Query("SELECT id, COALESCE(max_players, 4) AS max_players, quest_type, quest_id, COALESCE(mark, 0) AS mark, COALESCE(flags, -1), start_time, COALESCE(active_duration, 1) AS active_duration, COALESCE(inactive_duration, 0) AS inactive_duration FROM event_quests ORDER BY quest_id")
if err != nil {
return
}
@@ -213,14 +253,14 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
// Commit event quest changes to a transaction instead of doing it one by one for to help with performance
transaction, _ := s.server.db.Begin()
for rows.Next() {
var id, mark uint32
var questId int
var maxPlayers, questType, activeDuration, inactiveDuration uint8
var maxPlayers, flags, questType, activeDuration, inactiveDuration uint8
var startTime time.Time
err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &startTime, &activeDuration, &inactiveDuration)
err := rows.Scan(&id, &maxPlayers, &questType, &questId, &mark, &flags, &startTime, &activeDuration, &inactiveDuration)
if err != nil {
continue
}
@@ -609,7 +649,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
tuneValues = append(tuneValues, tuneValue{1020, uint16(s.server.erupeConfig.GameplayOptions.GCPMultiplier * 100)})
tuneValues = append(tuneValues, tuneValue{1029, s.server.erupeConfig.GameplayOptions.GUrgentRate})
tuneValues = append(tuneValues, tuneValue{1029, uint16(s.server.erupeConfig.GameplayOptions.GUrgentRate * 100)})
if s.server.erupeConfig.GameplayOptions.DisableHunterNavi {
tuneValues = append(tuneValues, tuneValue{1037, 1})
@@ -677,24 +717,28 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) {
offset := uint16(time.Now().Unix())
bf.WriteUint16(offset)
tuneLimit := 770
if _config.ErupeConfig.RealClientMode <= _config.F5 {
tuneValues = tuneValues[:256]
tuneLimit = 256
} else if _config.ErupeConfig.RealClientMode <= _config.G3 {
tuneValues = tuneValues[:283]
tuneLimit = 283
} else if _config.ErupeConfig.RealClientMode <= _config.GG {
tuneValues = tuneValues[:315]
tuneLimit = 315
} else if _config.ErupeConfig.RealClientMode <= _config.G61 {
tuneValues = tuneValues[:332]
tuneLimit = 332
} else if _config.ErupeConfig.RealClientMode <= _config.G7 {
tuneValues = tuneValues[:339]
tuneLimit = 339
} else if _config.ErupeConfig.RealClientMode <= _config.G81 {
tuneValues = tuneValues[:396]
tuneLimit = 396
} else if _config.ErupeConfig.RealClientMode <= _config.G91 {
tuneValues = tuneValues[:694]
tuneLimit = 694
} else if _config.ErupeConfig.RealClientMode <= _config.G101 {
tuneValues = tuneValues[:704]
tuneLimit = 704
} else if _config.ErupeConfig.RealClientMode <= _config.Z2 {
tuneValues = tuneValues[:750]
tuneLimit = 750
}
if len(tuneValues) > tuneLimit {
tuneValues = tuneValues[:tuneLimit]
}
bf.WriteUint16(uint16(len(tuneValues)))

View File

@@ -6,256 +6,124 @@ import (
"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.Unk1 && 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)
bf := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
s.server.raviente.Lock()
switch pkt.SemaphoreID {
case 4:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
op := bf.ReadUint8()
dest := bf.ReadUint8()
data := bf.ReadUint32()
resp.WriteUint8(1)
resp.WriteUint8(dest)
ref := &s.server.raviente.state.stateData[dest]
damageMultiplier := s.server.raviente.GetRaviMultiplier(s.server)
switch op {
case 2:
resp.WriteUint32(*ref)
if dest == 28 { // Berserk resurrection tracker
resp.WriteUint32(*ref + data)
*ref += data
} else if dest == 17 { // Berserk poison tracker
if damageMultiplier == 1 {
resp.WriteUint32(*ref + data)
*ref += data
} else {
resp.WriteUint32(*ref)
}
} else {
data = uint32(float64(data) * damageMultiplier)
resp.WriteUint32(*ref + data)
*ref += data
}
case 13:
fallthrough
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
}
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 5:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
op := bf.ReadUint8()
dest := bf.ReadUint8()
data := bf.ReadUint32()
resp.WriteUint8(1)
resp.WriteUint8(dest)
ref := &s.server.raviente.support.supportData[dest]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + data)
*ref += data
case 13:
fallthrough
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
}
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 6:
resp := byteframe.NewByteFrame()
size := 6
for i := 0; i < len(bf.Data())-1; i += size {
op := bf.ReadUint8()
dest := bf.ReadUint8()
data := bf.ReadUint32()
resp.WriteUint8(1)
resp.WriteUint8(dest)
switch dest {
case 0:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.nextTime = data
case 1:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.startTime = data
case 2:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.killedTime = data
case 3:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.postTime = data
case 4:
ref := &s.server.raviente.register.register[0]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + data)
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
case 5:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.carveQuest = data
case 6:
ref := &s.server.raviente.register.register[1]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + data)
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
case 7:
ref := &s.server.raviente.register.register[2]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + data)
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
case 8:
ref := &s.server.raviente.register.register[3]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + data)
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
case 9:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.maxPlayers = data
case 10:
resp.WriteUint32(0)
resp.WriteUint32(data)
s.server.raviente.register.ravienteType = data
case 11:
ref := &s.server.raviente.register.register[4]
switch op {
case 2:
resp.WriteUint32(*ref)
resp.WriteUint32(*ref + data)
*ref += data
case 13:
resp.WriteUint32(0)
resp.WriteUint32(data)
*ref = data
case 14:
resp.WriteUint32(0)
resp.WriteUint32(data)
}
default:
resp.WriteUint32(0)
resp.WriteUint32(0)
}
}
resp.WriteUint8(0)
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
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()
}
s.server.raviente.Unlock()
}
func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysLoadRegister)
r := pkt.Unk1
switch r {
case 12:
resp := byteframe.NewByteFrame()
resp.WriteUint8(0)
resp.WriteUint8(12)
resp.WriteUint32(s.server.raviente.register.nextTime)
resp.WriteUint32(s.server.raviente.register.startTime)
resp.WriteUint32(s.server.raviente.register.killedTime)
resp.WriteUint32(s.server.raviente.register.postTime)
resp.WriteUint32(s.server.raviente.register.register[0])
resp.WriteUint32(s.server.raviente.register.carveQuest)
resp.WriteUint32(s.server.raviente.register.register[1])
resp.WriteUint32(s.server.raviente.register.register[2])
resp.WriteUint32(s.server.raviente.register.register[3])
resp.WriteUint32(s.server.raviente.register.maxPlayers)
resp.WriteUint32(s.server.raviente.register.ravienteType)
resp.WriteUint32(s.server.raviente.register.register[4])
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
case 29:
resp := byteframe.NewByteFrame()
resp.WriteUint8(0)
resp.WriteUint8(29)
for _, v := range s.server.raviente.state.stateData {
resp.WriteUint32(v)
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, resp.Data())
case 25:
resp := byteframe.NewByteFrame()
resp.WriteUint8(0)
resp.WriteUint8(25)
for _, v := range s.server.raviente.support.supportData {
resp.WriteUint32(v)
}
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
}
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}
func (s *Session) notifyRavi() {
sema := getRaviSemaphore(s.server)
sema := s.server.getRaviSemaphore()
if sema == nil {
return
}
var temp mhfpacket.MHFPacket
raviNotif := byteframe.NewByteFrame()
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 4}
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x40000}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 5}
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x50000}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 6}
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x60000}
raviNotif.WriteUint16(uint16(temp.Opcode()))
temp.Build(raviNotif, s.clientContext)
raviNotif.WriteUint16(0x0010) // End it.
@@ -272,28 +140,13 @@ func (s *Session) notifyRavi() {
}
}
func getRaviSemaphore(s *Server) *Semaphore {
func (s *Server) getRaviSemaphore() *Semaphore {
for _, semaphore := range s.semaphore {
if strings.HasPrefix(semaphore.id_semaphore, "hs_l0u3B5") && strings.HasSuffix(semaphore.id_semaphore, "3") {
if strings.HasPrefix(semaphore.name, "hs_l0") && strings.HasSuffix(semaphore.name, "3") {
return semaphore
}
}
return nil
}
func resetRavi(s *Session) {
s.server.raviente.Lock()
s.server.raviente.register.nextTime = 0
s.server.raviente.register.startTime = 0
s.server.raviente.register.killedTime = 0
s.server.raviente.register.postTime = 0
s.server.raviente.register.ravienteType = 0
s.server.raviente.register.maxPlayers = 0
s.server.raviente.register.carveQuest = 0
s.server.raviente.register.register = []uint32{0, 0, 0, 0, 0}
s.server.raviente.state.stateData = []uint32{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.raviente.support.supportData = []uint32{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.raviente.Unlock()
}
func handleMsgSysNotifyRegister(s *Session, p mhfpacket.MHFPacket) {}

View File

@@ -2,7 +2,6 @@ package channelserver
import (
"erupe-ce/common/byteframe"
"fmt"
"go.uber.org/zap"
"strconv"
"strings"
@@ -13,9 +12,6 @@ import (
func removeSessionFromSemaphore(s *Session) {
s.server.semaphoreLock.Lock()
for _, semaphore := range s.server.semaphore {
if _, exists := semaphore.reservedClientSlots[s.charID]; exists {
delete(semaphore.reservedClientSlots, s.charID)
}
if _, exists := semaphore.clients[s]; exists {
delete(semaphore.clients, s)
}
@@ -31,48 +27,38 @@ func handleMsgSysCreateSemaphore(s *Session, p mhfpacket.MHFPacket) {
func destructEmptySemaphores(s *Session) {
s.server.semaphoreLock.Lock()
for id, sema := range s.server.semaphore {
if len(sema.reservedClientSlots) == 0 && len(sema.clients) == 0 {
s.server.semaphoreLock.Unlock()
if len(sema.clients) == 0 {
delete(s.server.semaphore, id)
s.server.semaphoreLock.Lock()
if strings.HasPrefix(id, "hs_l0u3B5") {
releaseRaviSemaphore(s, sema)
if strings.HasPrefix(id, "hs_l0") {
s.server.resetRaviente()
}
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
}
}
s.server.semaphoreLock.Unlock()
}
func releaseRaviSemaphore(s *Session, sema *Semaphore) {
delete(sema.reservedClientSlots, s.charID)
delete(sema.clients, s)
if strings.HasSuffix(sema.id_semaphore, "2") && len(sema.clients) == 0 {
s.logger.Debug("Main raviente semaphore is empty, resetting")
resetRavi(s)
}
}
func handleMsgSysDeleteSemaphore(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysDeleteSemaphore)
if s.server.semaphore != nil {
destructEmptySemaphores(s)
s.server.semaphoreLock.Lock()
for id, sema := range s.server.semaphore {
if sema.id == pkt.SemaphoreID {
if strings.HasPrefix(id, "hs_l0u3B5") {
releaseRaviSemaphore(s, sema)
s.server.semaphoreLock.Unlock()
return
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)
}
s.server.semaphoreLock.Unlock()
}
if len(sema.clients) == 0 {
delete(s.server.semaphore, id)
s.logger.Debug("Destructed semaphore", zap.String("sema.id_semaphore", id))
return
if strings.HasPrefix(id, "hs_l0") {
s.server.resetRaviente()
}
s.logger.Debug("Destructed semaphore", zap.String("sema.name", id))
}
}
s.server.semaphoreLock.Unlock()
}
s.server.semaphoreLock.Unlock()
}
func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
@@ -80,18 +66,15 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
SemaphoreID := pkt.SemaphoreID
newSemaphore, exists := s.server.semaphore[SemaphoreID]
fmt.Printf("Got reserve stage req, StageID: %v\n\n", SemaphoreID)
if !exists {
s.server.semaphoreLock.Lock()
if strings.HasPrefix(SemaphoreID, "hs_l0u3B5") {
if strings.HasPrefix(SemaphoreID, "hs_l0") {
suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:])
s.server.semaphore[SemaphoreID] = &Semaphore{
id_semaphore: pkt.SemaphoreID,
id: uint32(suffix + 1),
clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}),
maxPlayers: 127,
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)
@@ -102,22 +85,19 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
newSemaphore.Lock()
defer newSemaphore.Unlock()
if _, exists := newSemaphore.reservedClientSlots[s.charID]; exists {
bf := byteframe.NewByteFrame()
bf := byteframe.NewByteFrame()
if _, exists := newSemaphore.clients[s]; exists {
bf.WriteUint32(newSemaphore.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else if uint16(len(newSemaphore.reservedClientSlots)) < newSemaphore.maxPlayers {
newSemaphore.reservedClientSlots[s.charID] = nil
} else if uint16(len(newSemaphore.clients)) < newSemaphore.maxPlayers {
newSemaphore.clients[s] = s.charID
s.Lock()
s.semaphore = newSemaphore
s.Unlock()
bf := byteframe.NewByteFrame()
bf.WriteUint32(newSemaphore.id)
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
} else {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
bf.WriteUint32(0)
}
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
}
func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
@@ -130,7 +110,6 @@ func handleMsgSysAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
} else {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
}
func handleMsgSysReleaseSemaphore(s *Session, p mhfpacket.MHFPacket) {

View File

@@ -163,7 +163,6 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) {
s.stageMoveStack.Lock()
}
s.QueueSendMHF(&mhfpacket.MsgSysCleanupObject{})
if s.reservationStage != nil {
s.reservationStage = nil
}
@@ -367,39 +366,42 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
defer s.server.stagesLock.RUnlock()
// Build the response
resp := byteframe.NewByteFrame()
bf := byteframe.NewByteFrame()
var joinable int
var joinable uint16
bf.WriteUint16(0)
for sid, stage := range s.server.stages {
stage.RLock()
defer stage.RUnlock()
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
stage.RUnlock()
continue
}
if !strings.Contains(stage.id, pkt.StagePrefix) {
stage.RUnlock()
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.
bf.WriteUint16(uint16(len(stage.reservedClientSlots)))
bf.WriteUint16(0) // Unk
if len(stage.clients) > 0 {
bf.WriteUint16(1)
} else {
bf.WriteUint16(0)
}
bf.WriteUint16(stage.maxPlayers)
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)
bf.WriteUint8(2)
} else {
resp.WriteUint8(0)
bf.WriteUint8(0)
}
ps.Uint8(resp, sid, false)
ps.Uint8(bf, sid, false)
stage.RUnlock()
}
bf.WriteUint16(uint16(joinable))
bf.WriteBytes(resp.Data())
bf.Seek(0, 0)
bf.WriteUint16(joinable)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
}

View File

@@ -3,7 +3,9 @@ package channelserver
import (
"fmt"
"net"
"strings"
"sync"
"time"
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
@@ -72,65 +74,37 @@ type Server struct {
name string
raviente *Raviente
questCacheData map[int][]byte
questCacheTime map[int]time.Time
}
type Raviente struct {
sync.Mutex
register *RavienteRegister
state *RavienteState
support *RavienteSupport
id uint16
register []uint32
state []uint32
support []uint32
}
type RavienteRegister struct {
nextTime uint32
startTime uint32
postTime uint32
killedTime uint32
ravienteType uint32
maxPlayers uint32
carveQuest uint32
register []uint32
}
type RavienteState struct {
stateData []uint32
}
type RavienteSupport struct {
supportData []uint32
}
// Set up the Raviente variables for the server
func NewRaviente() *Raviente {
ravienteRegister := &RavienteRegister{
nextTime: 0,
startTime: 0,
killedTime: 0,
postTime: 0,
ravienteType: 0,
maxPlayers: 0,
carveQuest: 0,
func (s *Server) resetRaviente() {
for _, semaphore := range s.semaphore {
if strings.HasPrefix(semaphore.name, "hs_l0") {
return
}
}
ravienteState := &RavienteState{}
ravienteSupport := &RavienteSupport{}
ravienteRegister.register = []uint32{0, 0, 0, 0, 0}
ravienteState.stateData = []uint32{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}
ravienteSupport.supportData = []uint32{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}
raviente := &Raviente{
register: ravienteRegister,
state: ravienteState,
support: ravienteSupport,
}
return raviente
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 (r *Raviente) GetRaviMultiplier(s *Server) float64 {
raviSema := getRaviSemaphore(s)
func (s *Server) GetRaviMultiplier() float64 {
raviSema := s.getRaviSemaphore()
if raviSema != nil {
var minPlayers int
if r.register.maxPlayers > 8 {
if s.raviente.register[9] > 8 {
minPlayers = 24
} else {
minPlayers = 4
@@ -143,6 +117,33 @@ func (r *Raviente) GetRaviMultiplier(s *Server) float64 {
return 0
}
func (s *Server) UpdateRavi(semaID uint32, index uint8, value uint32, update bool) (uint32, uint32) {
var prev uint32
var dest *[]uint32
switch semaID {
case 0x40000:
switch index {
case 17, 28: // Ignore res and poison
break
default:
value = uint32(float64(value) * s.GetRaviMultiplier())
}
dest = &s.raviente.state
case 0x50000:
dest = &s.raviente.support
case 0x60000:
dest = &s.raviente.register
default:
return 0, 0
}
if update {
(*dest)[index] += value
} else {
(*dest)[index] = value
}
return prev, (*dest)[index]
}
// NewServer creates a new Server type.
func NewServer(config *Config) *Server {
s := &Server{
@@ -160,7 +161,14 @@ func NewServer(config *Config) *Server {
semaphoreIndex: 7,
discordBot: config.DiscordBot,
name: config.Name,
raviente: NewRaviente(),
raviente: &Raviente{
id: 1,
register: make([]uint32, 30),
state: make([]uint32, 30),
support: make([]uint32, 30),
},
questCacheData: make(map[int][]byte),
questCacheTime: make(map[int]time.Time),
}
// Mezeporta
@@ -314,7 +322,6 @@ func (s *Server) BroadcastChatMessage(message string) {
msgBinChat.Build(bf)
s.BroadcastMHF(&mhfpacket.MsgSysCastedBinary{
CharID: 0xFFFFFFFF,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
}, nil)
@@ -346,7 +353,6 @@ func (s *Server) BroadcastRaviente(ip uint32, port uint16, stage []byte, _type u
bf.WriteUint16(0) // Unk
bf.WriteBytes(stage)
s.WorldcastMHF(&mhfpacket.MsgSysCastedBinary{
CharID: 0x00000000,
BroadcastType: BroadcastTypeServer,
MessageType: BinaryMessageTypeChat,
RawDataPayload: bf.Data(),
@@ -393,15 +399,16 @@ func (s *Server) NextSemaphoreID() uint32 {
for {
exists := false
s.semaphoreIndex = s.semaphoreIndex + 1
if s.semaphoreIndex == 0 {
s.semaphoreIndex = 7 // Skip reserved indexes
if s.semaphoreIndex > 0xFFFF {
s.semaphoreIndex = 1
}
for _, semaphore := range s.semaphore {
if semaphore.id == s.semaphoreIndex {
exists = true
break
}
}
if exists == false {
if !exists {
break
}
}

View File

@@ -34,6 +34,7 @@ func getLangStrings(s *Server) map[string]string {
strings["commandRaviRequest"] = "鎮静支援を要請します"
strings["commandRaviError"] = "ラヴィコマンドが認識されません"
strings["commandRaviNoPlayers"] = "誰も大討伐に参加していません"
strings["commandRaviVersion"] = "This command is disabled outside of MHFZZ"
strings["ravienteBerserk"] = "<大討伐:猛狂期>が開催されました!"
strings["ravienteExtreme"] = "<大討伐:猛狂期【極】>が開催されました!"
@@ -85,6 +86,7 @@ func getLangStrings(s *Server) map[string]string {
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!"

View File

@@ -7,55 +7,35 @@ import (
"sync"
)
// Stage holds stage-specific information
// Semaphore holds Semaphore-specific information
type Semaphore struct {
sync.RWMutex
// Stage ID string
id_semaphore string
// Semaphore ID string
name string
id uint32
// Map of session -> charID.
// These are clients that are CURRENTLY in the stage
// These are clients that are registered to the Semaphore
clients map[*Session]uint32
// Map of charID -> interface{}, only the key is used, value is always nil.
reservedClientSlots map[uint32]interface{}
// Max Players for Semaphore
maxPlayers uint16
}
// NewStage creates a new stage with intialized values.
// NewSemaphore creates a new Semaphore with intialized values
func NewSemaphore(s *Server, ID string, MaxPlayers uint16) *Semaphore {
sema := &Semaphore{
id_semaphore: ID,
id: s.NextSemaphoreID(),
clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}),
maxPlayers: MaxPlayers,
name: ID,
id: s.NextSemaphoreID(),
clients: make(map[*Session]uint32),
maxPlayers: MaxPlayers,
}
return sema
}
func (s *Semaphore) BroadcastRavi(pkt mhfpacket.MHFPacket) {
// Broadcast the data.
for session := range s.clients {
// 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())
}
}
// BroadcastMHF queues a MHFPacket to be sent to all sessions in the stage.
// 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 {

View File

@@ -62,8 +62,9 @@ type Session struct {
mailList []int
// For Debuging
Name string
closed bool
Name string
closed bool
ackStart map[uint32]time.Time
}
// NewSession creates a new Session type.
@@ -78,6 +79,7 @@ func NewSession(server *Server, conn net.Conn) *Session {
lastPacket: time.Now(),
sessionStart: TimeAdjusted().Unix(),
stageMoveStack: stringstack.New(),
ackStart: make(map[uint32]time.Time),
}
s.SetObjectID()
return s
@@ -192,6 +194,10 @@ func (s *Session) handlePacketGroup(pktGroup []byte) {
s.lastPacket = time.Now()
bf := byteframe.NewByteFrameFromBytes(pktGroup)
opcodeUint16 := bf.ReadUint16()
if len(bf.Data()) >= 6 {
s.ackStart[bf.ReadUint32()] = time.Now()
bf.Seek(2, io.SeekStart)
}
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.
@@ -254,7 +260,7 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
if sender == "Server" && !s.server.erupeConfig.DevModeOptions.LogOutboundMessages {
return
} else if !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
} else if sender != "Server" && !s.server.erupeConfig.DevModeOptions.LogInboundMessages {
return
}
@@ -262,12 +268,24 @@ func (s *Session) logMessage(opcode uint16, data []byte, sender string, recipien
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))
var ackHandle uint32
if len(data) >= 6 {
ackHandle = binary.BigEndian.Uint32(data[2:6])
}
if t, ok := s.ackStart[ackHandle]; ok {
fmt.Printf("[%s] -> [%s] (%fs)\n", sender, recipient, float64(time.Now().UnixNano()-t.UnixNano())/1000000000)
} else {
fmt.Printf("Data [%d bytes]:\n(Too long!)\n\n", len(data))
fmt.Printf("[%s] -> [%s]\n", sender, recipient)
}
fmt.Printf("Opcode: %s\n", opcodePID)
if s.server.erupeConfig.DevModeOptions.LogMessageData {
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]: (Too long!)\n\n", len(data))
}
} else {
fmt.Printf("\n")
}
}

View File

@@ -10,26 +10,29 @@ import (
"golang.org/x/crypto/bcrypt"
)
func (s *Server) createNewUser(ctx context.Context, username string, password string) (int, error) {
func (s *Server) createNewUser(ctx context.Context, username string, password string) (uint32, uint32, error) {
// Create salted hash of user password
passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return 0, err
return 0, 0, err
}
var id int
var (
id uint32
rights uint32
)
err = s.db.QueryRowContext(
ctx, `
INSERT INTO users (username, password, return_expires)
VALUES ($1, $2, $3)
RETURNING id
RETURNING id, rights
`,
username, string(passwordHash), time.Now().Add(time.Hour*24*30),
).Scan(&id)
return id, err
).Scan(&id, &rights)
return id, rights, err
}
func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error) {
func (s *Server) createLoginToken(ctx context.Context, uid uint32) (string, error) {
loginToken := token.Generate(16)
_, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken)
if err != nil {
@@ -38,8 +41,8 @@ func (s *Server) createLoginToken(ctx context.Context, uid int) (string, error)
return loginToken, nil
}
func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error) {
var userID int
func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) {
var userID uint32
err := s.db.QueryRowContext(ctx, "SELECT user_id FROM sign_sessions WHERE token = $1", token).Scan(&userID)
if err == sql.ErrNoRows {
return 0, fmt.Errorf("invalid login token")
@@ -49,65 +52,47 @@ func (s *Server) userIDFromToken(ctx context.Context, token string) (int, error)
return userID, nil
}
func (s *Server) createCharacter(ctx context.Context, userID int) (int, error) {
var charID int
err := s.db.QueryRowContext(ctx,
"SELECT id FROM characters WHERE is_new_character = true AND user_id = $1",
func (s *Server) createCharacter(ctx context.Context, userID uint32) (Character, error) {
var character Character
err := s.db.GetContext(ctx, &character,
"SELECT id, name, is_female, weapon_type, hrp, gr, last_login FROM characters WHERE is_new_character = true AND user_id = $1 LIMIT 1",
userID,
).Scan(&charID)
)
if err == sql.ErrNoRows {
err = s.db.QueryRowContext(ctx, `
var count int
s.db.QueryRowContext(ctx, "SELECT COUNT(*) FROM characters WHERE user_id = $1", userID).Scan(&count)
if count >= 16 {
return character, fmt.Errorf("cannot have more than 16 characters")
}
err = s.db.GetContext(ctx, &character, `
INSERT INTO characters (
user_id, is_female, is_new_character, name, unk_desc_string,
hrp, gr, weapon_type, last_login
)
VALUES ($1, false, true, '', '', 0, 0, 0, $2)
RETURNING id`,
RETURNING id, name, is_female, weapon_type, hrp, gr, last_login`,
userID, uint32(time.Now().Unix()),
).Scan(&charID)
)
}
return charID, err
return character, err
}
func (s *Server) deleteCharacter(ctx context.Context, userID int, charID int) error {
tx, err := s.db.BeginTx(ctx, nil)
func (s *Server) deleteCharacter(ctx context.Context, userID uint32, charID uint32) error {
var isNew bool
err := s.db.QueryRow("SELECT is_new_character FROM characters WHERE id = $1", charID).Scan(&isNew)
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.ExecContext(
ctx, `
DELETE FROM login_boost_state
WHERE char_id = $1`,
charID,
)
if err != nil {
return err
if isNew {
_, err = s.db.Exec("DELETE FROM characters WHERE id = $1", charID)
} else {
_, err = s.db.Exec("UPDATE characters SET deleted = true WHERE id = $1", charID)
}
_, err = tx.ExecContext(
ctx, `
DELETE FROM guild_characters
WHERE character_id = $1`,
charID,
)
if err != nil {
return err
}
_, err = tx.ExecContext(
ctx, `
DELETE FROM characters
WHERE user_id = $1 AND id = $2`,
userID, charID,
)
if err != nil {
return err
}
return tx.Commit()
return err
}
func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character, error) {
characters := make([]Character, 0)
func (s *Server) getCharactersForUser(ctx context.Context, uid uint32) ([]Character, error) {
var characters []Character
err := s.db.SelectContext(
ctx, &characters, `
SELECT id, name, is_female, weapon_type, hrp, gr, last_login
@@ -120,3 +105,20 @@ func (s *Server) getCharactersForUser(ctx context.Context, uid int) ([]Character
}
return characters, nil
}
func (s *Server) getReturnExpiry(uid uint32) time.Time {
var returnExpiry, lastLogin time.Time
s.db.Get(&lastLogin, "SELECT COALESCE(last_login, now()) FROM users WHERE id=$1", uid)
if time.Now().Add((time.Hour * 24) * -90).After(lastLogin) {
returnExpiry = time.Now().Add(time.Hour * 24 * 30)
s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid)
} else {
err := s.db.Get(&returnExpiry, "SELECT return_expires FROM users WHERE id=$1", uid)
if err != nil {
returnExpiry = time.Now()
s.db.Exec("UPDATE users SET return_expires=$1 WHERE id=$2", returnExpiry, uid)
}
}
s.db.Exec("UPDATE users SET last_login=$1 WHERE id=$2", time.Now(), uid)
return returnExpiry
}

View File

@@ -4,7 +4,9 @@ import (
"database/sql"
"encoding/json"
"errors"
"erupe-ce/server/channelserver"
"net/http"
"strings"
"time"
"github.com/lib/pq"
@@ -18,21 +20,78 @@ type LauncherMessage struct {
Link string `json:"link"`
}
type LauncherResponse struct {
Important []LauncherMessage `json:"important"`
Normal []LauncherMessage `json:"normal"`
}
type User struct {
Token string `json:"token"`
Rights uint32 `json:"rights"`
}
type Character struct {
ID int `json:"id"`
ID uint32 `json:"id"`
Name string `json:"name"`
IsFemale bool `json:"isFemale" db:"is_female"`
Weapon int `json:"weapon" db:"weapon_type"`
HR int `json:"hr" db:"hrp"`
GR int `json:"gr"`
Weapon uint32 `json:"weapon" db:"weapon_type"`
HR uint32 `json:"hr" db:"hrp"`
GR uint32 `json:"gr"`
LastLogin int64 `json:"lastLogin" db:"last_login"`
}
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
var respData struct {
Important []LauncherMessage `json:"important"`
Normal []LauncherMessage `json:"normal"`
type MezFes struct {
ID uint32 `json:"id"`
Start uint32 `json:"start"`
End uint32 `json:"end"`
SoloTickets uint32 `json:"soloTickets"`
GroupTickets uint32 `json:"groupTickets"`
Stalls []uint32 `json:"stalls"`
}
type AuthData struct {
CurrentTS uint32 `json:"currentTs"`
ExpiryTS uint32 `json:"expiryTs"`
EntranceCount uint32 `json:"entranceCount"`
Notifications []string `json:"notifications"`
User User `json:"user"`
Characters []Character `json:"characters"`
MezFes *MezFes `json:"mezFes"`
}
func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData {
resp := AuthData{
CurrentTS: uint32(channelserver.TimeAdjusted().Unix()),
ExpiryTS: uint32(s.getReturnExpiry(userID).Unix()),
EntranceCount: 1,
User: User{
Rights: userRights,
Token: userToken,
},
Characters: characters,
}
if s.erupeConfig.DevModeOptions.MezFesEvent {
stalls := []uint32{10, 3, 6, 9, 4, 8, 5, 7}
if s.erupeConfig.DevModeOptions.MezFesAlt {
stalls[4] = 2
}
resp.MezFes = &MezFes{
ID: uint32(channelserver.TimeWeekStart().Unix()),
Start: uint32(channelserver.TimeWeekStart().Unix()),
End: uint32(channelserver.TimeWeekNext().Unix()),
SoloTickets: s.erupeConfig.GameplayOptions.MezfesSoloTickets,
GroupTickets: s.erupeConfig.GameplayOptions.MezfesGroupTickets,
Stalls: stalls,
}
}
if !s.erupeConfig.HideLoginNotice {
resp.Notifications = append(resp.Notifications, strings.Join(s.erupeConfig.LoginNotices[:], "<PAGE>"))
}
return resp
}
func (s *Server) Launcher(w http.ResponseWriter, r *http.Request) {
var respData LauncherResponse
respData.Important = []LauncherMessage{
{
Message: "Server Update 9 Released!",
@@ -75,10 +134,11 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
return
}
var (
userID int
password string
userID uint32
userRights uint32
password string
)
err := s.db.QueryRow("SELECT id, password FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password)
err := s.db.QueryRow("SELECT id, password, rights FROM users WHERE username = $1", reqData.Username).Scan(&userID, &password, &userRights)
if err == sql.ErrNoRows {
w.WriteHeader(400)
w.Write([]byte("Username does not exist"))
@@ -94,22 +154,19 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
return
}
var respData struct {
Token string `json:"token"`
Characters []Character `json:"characters"`
}
respData.Token, err = s.createLoginToken(ctx, userID)
userToken, err := s.createLoginToken(ctx, userID)
if err != nil {
s.logger.Warn("Error registering login token", zap.Error(err))
w.WriteHeader(500)
return
}
respData.Characters, err = s.getCharactersForUser(ctx, userID)
characters, err := s.getCharactersForUser(ctx, userID)
if err != nil {
s.logger.Warn("Error getting characters from DB", zap.Error(err))
w.WriteHeader(500)
return
}
respData := s.newAuthData(userID, userRights, userToken, characters)
w.WriteHeader(200)
w.Header().Add("Content-Type", "application/json")
json.NewEncoder(w).Encode(respData)
@@ -128,7 +185,7 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
return
}
s.logger.Info("Creating account", zap.String("username", reqData.Username))
userID, err := s.createNewUser(ctx, reqData.Username, reqData.Password)
userID, userRights, err := s.createNewUser(ctx, reqData.Username, reqData.Password)
if err != nil {
var pqErr *pq.Error
if errors.As(err, &pqErr) && pqErr.Constraint == "users_username_key" {
@@ -141,15 +198,13 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) {
return
}
var respData struct {
Token string `json:"token"`
}
respData.Token, err = s.createLoginToken(ctx, userID)
userToken, err := s.createLoginToken(ctx, userID)
if err != nil {
s.logger.Error("Error registering login token", zap.Error(err))
w.WriteHeader(500)
return
}
respData := s.newAuthData(userID, userRights, userToken, []Character{})
json.NewEncoder(w).Encode(respData)
}
@@ -165,28 +220,25 @@ func (s *Server) CreateCharacter(w http.ResponseWriter, r *http.Request) {
return
}
var respData struct {
CharID int `json:"id"`
}
userID, err := s.userIDFromToken(ctx, reqData.Token)
if err != nil {
w.WriteHeader(401)
return
}
respData.CharID, err = s.createCharacter(ctx, userID)
character, err := s.createCharacter(ctx, userID)
if err != nil {
s.logger.Error("Failed to create character", zap.Error(err), zap.String("token", reqData.Token))
w.WriteHeader(500)
return
}
json.NewEncoder(w).Encode(respData)
json.NewEncoder(w).Encode(character)
}
func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var reqData struct {
Token string `json:"token"`
CharID int `json:"id"`
CharID uint32 `json:"charId"`
}
if err := json.NewDecoder(r.Body).Decode(&reqData); err != nil {
s.logger.Error("JSON decode error", zap.Error(err))
@@ -200,7 +252,7 @@ func (s *Server) DeleteCharacter(w http.ResponseWriter, r *http.Request) {
return
}
if err := s.deleteCharacter(ctx, userID, reqData.CharID); err != nil {
s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Int("charID", reqData.CharID))
s.logger.Error("Failed to delete character", zap.Error(err), zap.String("token", reqData.Token), zap.Uint32("charID", reqData.CharID))
w.WriteHeader(500)
return
}