stage improvements

This commit is contained in:
wish
2022-07-21 18:54:24 +10:00
parent 52579df8c6
commit e7f318a04a
7 changed files with 97 additions and 132 deletions

View File

@@ -1,6 +1,7 @@
package mhfpacket package mhfpacket
import ( import (
"errors"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/bfutil" "erupe-ce/common/bfutil"
"erupe-ce/network" "erupe-ce/network"
@@ -32,5 +33,5 @@ func (m *MsgSysCreateStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Client
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.
func (m *MsgSysCreateStage) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgSysCreateStage) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
panic("Not implemented") return errors.New("NOT IMPLEMENTED")
} }

View File

@@ -1,6 +1,7 @@
package mhfpacket package mhfpacket
import ( import (
"errors"
"erupe-ce/common/byteframe" "erupe-ce/common/byteframe"
"erupe-ce/common/bfutil" "erupe-ce/common/bfutil"
"erupe-ce/network" "erupe-ce/network"
@@ -10,7 +11,7 @@ import (
// MsgSysReserveStage represents the MSG_SYS_RESERVE_STAGE // MsgSysReserveStage represents the MSG_SYS_RESERVE_STAGE
type MsgSysReserveStage struct { type MsgSysReserveStage struct {
AckHandle uint32 AckHandle uint32
Unk0 uint8 // Made with: `16 * x | 1;`, unknown `x` values. Ready uint8 // Bitfield but hex (0x11 or 0x01)
StageID string // NULL terminated string. StageID string // NULL terminated string.
} }
@@ -22,7 +23,7 @@ func (m *MsgSysReserveStage) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgSysReserveStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgSysReserveStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.AckHandle = bf.ReadUint32() m.AckHandle = bf.ReadUint32()
m.Unk0 = bf.ReadUint8() m.Ready = bf.ReadUint8()
stageIDLength := bf.ReadUint8() stageIDLength := bf.ReadUint8()
m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength)))) m.StageID = string(bfutil.UpToNull(bf.ReadBytes(uint(stageIDLength))))
return nil return nil
@@ -30,5 +31,5 @@ func (m *MsgSysReserveStage) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Clien
// Build builds a binary packet from the current data. // Build builds a binary packet from the current data.
func (m *MsgSysReserveStage) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgSysReserveStage) Build(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
panic("Not implemented") return errors.New("NOT IMPLEMENTED")
} }

View File

@@ -11,7 +11,6 @@ import (
// MsgSysSetStagePass represents the MSG_SYS_SET_STAGE_PASS // MsgSysSetStagePass represents the MSG_SYS_SET_STAGE_PASS
type MsgSysSetStagePass struct { type MsgSysSetStagePass struct {
Unk0 uint8 // Hardcoded 0 in the binary Unk0 uint8 // Hardcoded 0 in the binary
PasswordLength uint8
Password string // NULL-terminated string Password string // NULL-terminated string
} }
@@ -23,8 +22,8 @@ func (m *MsgSysSetStagePass) Opcode() network.PacketID {
// Parse parses the packet from binary // Parse parses the packet from binary
func (m *MsgSysSetStagePass) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { func (m *MsgSysSetStagePass) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error {
m.Unk0 = bf.ReadUint8() m.Unk0 = bf.ReadUint8()
m.PasswordLength = bf.ReadUint8() _ = bf.ReadUint8() // Password length
m.Password = string(bf.ReadBytes(uint(m.PasswordLength))) m.Password = string(bf.ReadNullTerminatedBytes())
return nil return nil
} }

View File

@@ -10,41 +10,35 @@ import (
func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysEnumerateClient) pkt := p.(*mhfpacket.MsgSysEnumerateClient)
// Read-lock the stages map.
s.server.stagesLock.RLock() s.server.stagesLock.RLock()
stage, ok := s.server.stages[pkt.StageID] stage, ok := s.server.stages[pkt.StageID]
if !ok { if !ok {
s.logger.Fatal("Can't enumerate clients for stage that doesn't exist!", zap.String("stageID", pkt.StageID)) s.logger.Fatal("Can't enumerate clients for stage that doesn't exist!", zap.String("stageID", pkt.StageID))
} }
// Unlock the stages map.
s.server.stagesLock.RUnlock() s.server.stagesLock.RUnlock()
// Read-lock the stage and make the response with all of the charID's in the stage. // Read-lock the stage and make the response with all of the charID's in the stage.
resp := byteframe.NewByteFrame() resp := byteframe.NewByteFrame()
stage.RLock() stage.RLock()
var clients []uint32
// TODO(Andoryuuta): Is only the reservations needed? Do clients send this packet for mezeporta as well? switch pkt.Unk1 {
case 1: // Not ready
// Make a map to deduplicate the charIDs between the unreserved clients and the reservations. for cid, ready := range stage.reservedClientSlots {
deduped := make(map[uint32]interface{}) if !ready {
clients = append(clients, cid)
// Add the charIDs
for session := range stage.clients {
deduped[session.charID] = nil
} }
for charid := range stage.reservedClientSlots {
deduped[charid] = nil
} }
case 2: // Ready
// Write the deduplicated response for cid, ready := range stage.reservedClientSlots {
resp.WriteUint16(uint16(len(deduped))) // Client count if ready {
for charid := range deduped { clients = append(clients, cid)
resp.WriteUint32(charid) }
}
}
resp.WriteUint16(uint16(len(clients)))
for _, cid := range clients {
resp.WriteUint32(cid)
} }
stage.RUnlock() stage.RUnlock()
doAckBufSucceed(s, pkt.AckHandle, resp.Data()) doAckBufSucceed(s, pkt.AckHandle, resp.Data())

View File

@@ -247,119 +247,76 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {
func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysReserveStage) pkt := p.(*mhfpacket.MsgSysReserveStage)
if stage, exists := s.server.stages[pkt.StageID]; exists {
stageID := pkt.StageID
fmt.Printf("Got reserve stage req, TargetCount:%v, StageID:%v\n", pkt.Unk0, stageID)
// Try to get the stage
s.server.stagesLock.Lock()
stage, gotStage := s.server.stages[stageID]
s.server.stagesLock.Unlock()
if !gotStage {
s.logger.Error("Failed to get stage", zap.String("StageID", stageID))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
// Try to reserve a slot, fail if full.
stage.Lock() stage.Lock()
defer stage.Unlock() defer stage.Unlock()
// Quick fix to allow readying up while party is full, more investigation needed
// Reserve stage is also sent when a player is ready, probably need to parse the
// request a little more thoroughly.
if _, exists := stage.reservedClientSlots[s.charID]; exists { if _, exists := stage.reservedClientSlots[s.charID]; exists {
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) switch pkt.Ready {
case 1: // 0x01
stage.reservedClientSlots[s.charID] = false
case 17: // 0x11
stage.reservedClientSlots[s.charID] = true
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
} else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers { } else if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers {
// Add the charID to the stage's reservation map if len(stage.password) > 0 {
stage.reservedClientSlots[s.charID] = nil // s.logger.Debug("", zap.String("stgpw", stage.password), zap.String("usrpw", s.stagePass))
if stage.password == s.stagePass {
stage.reservedClientSlots[s.charID] = false
s.Lock()
s.reservationStage = stage
s.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} else {
stage.reservedClientSlots[s.charID] = false
// Save the reservation stage in the session for later use in MsgSysUnreserveStage. // Save the reservation stage in the session for later use in MsgSysUnreserveStage.
s.Lock() s.Lock()
s.reservationStage = stage s.reservationStage = stage
s.Unlock() s.Unlock()
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) }
} else { } else {
doAckSimpleFail(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00}) doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
} else {
s.logger.Error("Failed to get stage", zap.String("StageID", pkt.StageID))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
} }
} }
func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) {
// Clear the saved reservation stage
s.Lock() s.Lock()
stage := s.reservationStage stage := s.reservationStage
if stage != nil {
s.reservationStage = nil s.reservationStage = nil
}
s.Unlock() s.Unlock()
// Remove the charID from the stage's reservation map
if stage != nil { if stage != nil {
stage.Lock() stage.Lock()
_, exists := stage.reservedClientSlots[s.charID] if _, exists := stage.reservedClientSlots[s.charID]; exists {
if exists {
delete(stage.reservedClientSlots, s.charID) delete(stage.reservedClientSlots, s.charID)
} }
stage.Unlock() stage.Unlock()
} }
} }
func handleMsgSysSetStagePass(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysSetStagePass(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgSysSetStagePass)
func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) { s.Lock()
pkt := p.(*mhfpacket.MsgSysWaitStageBinary) stage := s.reservationStage
defer s.logger.Debug("MsgSysWaitStageBinary Done!") s.Unlock()
if stage != nil {
// Try to get the stage
stageID := pkt.StageID
s.server.stagesLock.Lock()
stage, gotStage := s.server.stages[stageID]
s.server.stagesLock.Unlock()
// TODO(Andoryuuta): This is a hack for a binary part that none of the clients set, figure out what it represents.
// In the packet captures, it seemingly comes out of nowhere, so presumably the server makes it.
if pkt.BinaryType0 == 1 && pkt.BinaryType1 == 12 {
// This might contain the hunter count, or max player count?
doAckBufSucceed(s, pkt.AckHandle, []byte{0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})
return
}
// If we got the stage, lock and try to get the data.
var stageBinary []byte
var gotBinary bool
if gotStage {
for {
s.logger.Debug("MsgSysWaitStageBinary before lock and get stage")
stage.Lock() stage.Lock()
stageBinary, gotBinary = stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}] if _, exists := stage.reservedClientSlots[s.charID]; exists {
stage.password = pkt.Password
}
stage.Unlock() stage.Unlock()
s.logger.Debug("MsgSysWaitStageBinary after lock and get stage")
if gotBinary {
doAckBufSucceed(s, pkt.AckHandle, stageBinary)
break
} else { } else {
s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1)) // Store for use on next ReserveStage
s.Lock()
// Couldn't get binary, sleep for some time and try again. s.stagePass = pkt.Password
time.Sleep(2 * time.Second) s.Unlock()
continue
}
// TODO(Andoryuuta): Figure out what the game sends on timeout and implement it!
/*
if timeout {
s.logger.Warn("Failed to get stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1))
s.logger.Warn("Sending blank stage binary")
doAckBufSucceed(s, pkt.AckHandle, []byte{})
return
}
*/
}
} else {
s.logger.Warn("Failed to get stage", zap.String("StageID", stageID))
} }
} }
@@ -439,9 +396,16 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
for sid, stage := range s.server.stages { for sid, stage := range s.server.stages {
stage.RLock() stage.RLock()
defer stage.RUnlock() defer stage.RUnlock()
if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 { if len(stage.reservedClientSlots) == 0 && len(stage.clients) == 0 {
continue continue
} }
// Check for valid stage type
if sid[3:5] != "Qs" && sid[3:5] != "Ms" {
continue
}
joinable++ joinable++
resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Reserved players. resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Reserved players.
@@ -453,9 +417,14 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) {
} }
resp.WriteUint16(hasDeparted) // HasDeparted. resp.WriteUint16(hasDeparted) // HasDeparted.
resp.WriteUint16(stage.maxPlayers) // Max players. resp.WriteUint16(stage.maxPlayers) // Max players.
resp.WriteBool(len(stage.password) > 0) // Password protected. if len(stage.password) > 0 {
resp.WriteUint8(uint8(len(sid))) // This byte has also been seen as 1
resp.WriteBytes([]byte(sid)) // The quest is also recognised as locked when this is 2
resp.WriteUint8(3)
} else {
resp.WriteUint8(0)
}
ps.Uint8(resp, sid, false)
} }
bf.WriteUint16(uint16(joinable)) bf.WriteUint16(uint16(joinable))
bf.WriteBytes(resp.Data()) bf.WriteBytes(resp.Data())

View File

@@ -30,6 +30,7 @@ type Session struct {
stageID string stageID string
stage *Stage stage *Stage
reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet. reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet.
stagePass string // Temporary storage
charID uint32 charID uint32
logKey []byte logKey []byte
sessionStart int64 sessionStart int64

View File

@@ -47,9 +47,9 @@ type Stage struct {
// These are clients that are CURRENTLY in the stage // These are clients that are CURRENTLY in the stage
clients map[*Session]uint32 clients map[*Session]uint32
// Map of charID -> interface{}, only the key is used, value is always nil. // Map of charID -> bool, key represents whether they are ready
// These are clients that aren't in the stage, but have reserved a slot (for quests, etc). // These are clients that aren't in the stage, but have reserved a slot (for quests, etc).
reservedClientSlots map[uint32]interface{} reservedClientSlots map[uint32]bool
// These are raw binary blobs that the stage owner sets, // These are raw binary blobs that the stage owner sets,
// other clients expect the server to echo them back in the exact same format. // other clients expect the server to echo them back in the exact same format.
@@ -66,7 +66,7 @@ func NewStage(ID string) *Stage {
s := &Stage{ s := &Stage{
id: ID, id: ID,
clients: make(map[*Session]uint32), clients: make(map[*Session]uint32),
reservedClientSlots: make(map[uint32]interface{}), reservedClientSlots: make(map[uint32]bool),
objects: make(map[uint32]*StageObject), objects: make(map[uint32]*StageObject),
rawBinaryData: make(map[stageBinaryKey][]byte), rawBinaryData: make(map[stageBinaryKey][]byte),
maxPlayers: 4, maxPlayers: 4,