diff --git a/network/mhfpacket/msg_sys_casted_binary.go b/network/mhfpacket/msg_sys_casted_binary.go index d2c0145d4..e849126a5 100644 --- a/network/mhfpacket/msg_sys_casted_binary.go +++ b/network/mhfpacket/msg_sys_casted_binary.go @@ -20,14 +20,19 @@ func (m *MsgSysCastedBinary) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysCastedBinary) Parse(bf *byteframe.ByteFrame) error { - panic("Not implemented") + m.CharID = bf.ReadUint32() + m.Type0 = bf.ReadUint8() + m.Type1 = bf.ReadUint8() + dataSize := bf.ReadUint16() + m.RawDataPayload = bf.ReadBytes(uint(dataSize)) + return nil } // Build builds a binary packet from the current data. func (m *MsgSysCastedBinary) Build(bf *byteframe.ByteFrame) error { bf.WriteUint32(m.CharID) bf.WriteUint8(m.Type0) - bf.WriteUint8(m.Type0) + bf.WriteUint8(m.Type1) bf.WriteUint16(uint16(len(m.RawDataPayload))) bf.WriteBytes(m.RawDataPayload) return nil diff --git a/network/mhfpacket/msg_sys_unreserve_stage.go b/network/mhfpacket/msg_sys_unreserve_stage.go index 10e4c76d5..536b7a795 100644 --- a/network/mhfpacket/msg_sys_unreserve_stage.go +++ b/network/mhfpacket/msg_sys_unreserve_stage.go @@ -6,7 +6,9 @@ import ( ) // MsgSysUnreserveStage represents the MSG_SYS_UNRESERVE_STAGE -type MsgSysUnreserveStage struct{} +type MsgSysUnreserveStage struct { + // Contains no fields. +} // Opcode returns the ID associated with this packet type. func (m *MsgSysUnreserveStage) Opcode() network.PacketID { @@ -15,7 +17,7 @@ func (m *MsgSysUnreserveStage) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgSysUnreserveStage) Parse(bf *byteframe.ByteFrame) error { - panic("Not implemented") + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 01be1d962..4efdfb592 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -193,17 +193,18 @@ func handleMsgSysPing(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysCastBinary) + // Simply forward the packet to all the other clients. + // (The client never uses Type0 upon receiving) + // TODO(Andoryuuta): Does this broadcast need to be limited? (world, stage, guild, etc). + resp := &mhfpacket.MsgSysCastedBinary{ + CharID: s.charID, + Type0: pkt.Type0, + Type1: pkt.Type1, + RawDataPayload: pkt.RawDataPayload, + } + s.server.BroadcastMHF(resp, s) + if pkt.Type0 == 3 && pkt.Type1 == 1 { - fmt.Println("Got chat message!") - - resp := &mhfpacket.MsgSysCastedBinary{ - CharID: s.charID, - Type0: 1, - Type1: 1, - RawDataPayload: pkt.RawDataPayload, - } - s.server.BroadcastMHF(resp, s) - /* // Made the inside of the casted binary payload := byteframe.NewByteFrame() @@ -538,16 +539,60 @@ func handleMsgSysUnlockStage(s *Session, p mhfpacket.MHFPacket) {} func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysReserveStage) - fmt.Printf("Got reserve stage req, Unk0:%v, StageID:%q\n", pkt.Unk0, pkt.StageID) + stageID := stripNullTerminator(pkt.StageID) + fmt.Printf("Got reserve stage req, Unk0:%v, StageID:%v\n", pkt.Unk0, stageID) - // TODO(Andoryuuta): Add proper player-slot reservations for stages. + // Try to get the stage + s.server.stagesLock.Lock() + stage, gotStage := s.server.stages[stageID] + s.server.stagesLock.Unlock() - s.QueueAck(pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + if !gotStage { + s.logger.Fatal("Failed to get stage", zap.String("StageID", stageID)) + } + + // Try to reserve a slot, fail if full. + stage.Lock() + defer stage.Unlock() + + if uint16(len(stage.reservedClientSlots)) < stage.maxPlayers { + // Add the charID to the stage's reservation map + stage.reservedClientSlots[s.charID] = nil + + // Save the reservation stage in the session for later use in MsgSysUnreserveStage. + s.Lock() + s.reservationStage = stage + s.Unlock() + + s.QueueAck(pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + } else { + s.QueueAck(pkt.AckHandle, []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) + } } -func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) { + // Clear the saved reservation stage + s.Lock() + stage := s.reservationStage + if stage != nil { + s.reservationStage = nil + } + s.Unlock() -func handleMsgSysSetStagePass(s *Session, p mhfpacket.MHFPacket) {} + // Remove the charID from the stage's reservation map + if stage != nil { + stage.Lock() + _, exists := stage.reservedClientSlots[s.charID] + if exists { + delete(stage.reservedClientSlots, s.charID) + } + stage.Unlock() + } +} + +func handleMsgSysSetStagePass(s *Session, p mhfpacket.MHFPacket) { + // TODO(Andoryuuta): Implement me! +} func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysWaitStageBinary) @@ -559,19 +604,31 @@ func handleMsgSysWaitStageBinary(s *Session, p mhfpacket.MHFPacket) { 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? + doSizedAckResp(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() stageBinary, gotBinary = stage.rawBinaryData[stageBinaryKey{pkt.BinaryType0, pkt.BinaryType1}] stage.Unlock() + s.logger.Debug("MsgSysWaitStageBinary after lock and get stage") if gotBinary { doSizedAckResp(s, pkt.AckHandle, stageBinary) break } else { + s.logger.Debug("Waiting stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1)) + // Couldn't get binary, sleep for some time and try again. time.Sleep(2 * time.Second) continue @@ -661,16 +718,24 @@ func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) { resp := byteframe.NewByteFrame() stage.RLock() - // TODO(Andoryuuta): Add proper player-slot reservations for stages. - if len(stage.clients) >= 1 { - resp.WriteUint16(uint16(len(stage.clients))) // Client count - for session := range stage.clients { - resp.WriteUint32(session.charID) // Client represented by charID - } - } else { - // Just give our client. - resp.WriteUint16(1) - resp.WriteUint32(s.charID) + // TODO(Andoryuuta): Is only the reservations needed? Do clients send this packet for mezeporta as well? + + // Make a map to deduplicate the charIDs between the unreserved clients and the reservations. + deduped := make(map[uint32]interface{}) + + // Add the charIDs + for session := range stage.clients { + deduped[session.charID] = nil + } + + for charid := range stage.reservedClientSlots { + deduped[charid] = nil + } + + // Write the deduplicated response + resp.WriteUint16(uint16(len(deduped))) // Client count + for charid := range deduped { + resp.WriteUint32(charid) } stage.RUnlock() @@ -682,21 +747,28 @@ func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) { func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnumerateStage) - // Read-lock the stages. + // Read-lock the server stage map. s.server.stagesLock.RLock() defer s.server.stagesLock.RUnlock() // Build the response resp := byteframe.NewByteFrame() resp.WriteUint16(uint16(len(s.server.stages))) - for sid := range s.server.stages { - // Found parsing code, field sizes are correct, but unknown purposes still. - //resp.WriteBytes([]byte{0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x04, 0x00}) - resp.WriteUint16(1) // Current players. - resp.WriteUint16(0) // Unknown value - resp.WriteUint16(0) // HasDeparted. - resp.WriteUint16(4) // Max players. - resp.WriteUint8(2) // Password protected. + for sid, stage := range s.server.stages { + stage.RLock() + defer stage.RUnlock() + + resp.WriteUint16(uint16(len(stage.reservedClientSlots))) // Current players. + resp.WriteUint16(0) // Unknown value + + var hasDeparted uint16 + if stage.hasDeparted { + hasDeparted = 1 + } + + resp.WriteUint16(hasDeparted) // HasDeparted. + resp.WriteUint16(stage.maxPlayers) // Max players. + resp.WriteBool(len(stage.password) > 0) // Password protected. resp.WriteUint8(uint8(len(sid))) resp.WriteBytes([]byte(sid)) } diff --git a/server/channelserver/session.go b/server/channelserver/session.go index 002d04c93..26bec24ed 100644 --- a/server/channelserver/session.go +++ b/server/channelserver/session.go @@ -22,10 +22,11 @@ type Session struct { cryptConn *network.CryptConn sendPackets chan []byte - stageID string - stage *Stage - charID uint32 - logKey []byte + stageID string + stage *Stage + reservationStage *Stage // Required for the stateful MsgSysUnreserveStage packet. + charID uint32 + logKey []byte // A stack containing the stage movement history (push on enter/move, pop on back) stageMoveStack *stringstack.StringStack diff --git a/server/channelserver/stage.go b/server/channelserver/stage.go index 1b996b1a1..acf57d233 100644 --- a/server/channelserver/stage.go +++ b/server/channelserver/stage.go @@ -15,6 +15,7 @@ type StageObject struct { x, y, z float32 } +// stageBinaryKey is a struct used as a map key for identifying a stage binary part. type stageBinaryKey struct { id0 uint8 id1 uint8 @@ -23,21 +24,43 @@ type stageBinaryKey struct { // Stage holds stage-specific information type Stage struct { sync.RWMutex - id string // Stage ID string - gameObjectCount uint32 // Total count of objects ever created for this stage. Used for ObjID generation. - objects map[uint32]*StageObject // Map of ObjID -> StageObject - clients map[*Session]uint32 // Map of session -> charID - rawBinaryData map[stageBinaryKey][]byte // Raw binary data set by the client. + + // Stage ID string + id string + + // Total count of objects ever created for this stage. Used for ObjID generation. + gameObjectCount uint32 + + // Map of ObjID -> StageObject + objects map[uint32]*StageObject + + // Map of session -> charID. + // These are clients that are CURRENTLY in the stage + clients map[*Session]uint32 + + // Map of charID -> interface{}, only the key is used, value is always nil. + // These are clients that aren't in the stage, but have reserved a slot (for quests, etc). + reservedClientSlots map[uint32]interface{} + + // These are raw binary blobs that the stage owner sets, + // other clients expect the server to echo them back in the exact same format. + rawBinaryData map[stageBinaryKey][]byte + + maxPlayers uint16 + hasDeparted bool + password string } // NewStage creates a new stage with intialized values. func NewStage(ID string) *Stage { s := &Stage{ - id: ID, - objects: make(map[uint32]*StageObject), - clients: make(map[*Session]uint32), - rawBinaryData: make(map[stageBinaryKey][]byte), - gameObjectCount: 1, + id: ID, + objects: make(map[uint32]*StageObject), + clients: make(map[*Session]uint32), + reservedClientSlots: make(map[uint32]interface{}), + rawBinaryData: make(map[stageBinaryKey][]byte), + maxPlayers: 4, + gameObjectCount: 1, } return s