diff --git a/.gitignore b/.gitignore index deb249051..ebaafb9f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ www/tw/ www/jp/ +vendor/ bin/*.bin bin/quests/*.bin bin/questlists/*.bin diff --git a/config/config.go b/config/config.go index d44b3cb89..a71b07706 100644 --- a/config/config.go +++ b/config/config.go @@ -14,18 +14,19 @@ type Config struct { DevMode bool DevModeOptions DevModeOptions - Database Database - Launcher Launcher - Sign Sign - Channel Channel - Entrance Entrance + Database Database + Launcher Launcher + Sign Sign + Channel Channel + Entrance Entrance } // DevModeOptions holds various debug/temporary options for use while developing Erupe. type DevModeOptions struct { - CleanDB bool // Automatically wipes the DB on server reset. - MaxLauncherHR bool // Sets the HR returned in the launcher to HR9 so that you can join non-beginner worlds. - FixedStageID bool // Causes all move_stage to use the ID sl1Ns200p0a0u0 to get you into all stages + CleanDB bool // Automatically wipes the DB on server reset. + MaxLauncherHR bool // Sets the HR returned in the launcher to HR9 so that you can join non-beginner worlds. + FixedStageID bool // Causes all move_stage to use the ID sl1Ns200p0a0u0 to get you into all stages + LogOutboundMessages bool // Log all messages sent to the clients } // Database holds the postgres database config. diff --git a/network/binpacket/msg_cast_bin_private_message.go b/network/binpacket/msg_cast_bin_private_message.go new file mode 100644 index 000000000..53a3bdac7 --- /dev/null +++ b/network/binpacket/msg_cast_bin_private_message.go @@ -0,0 +1,48 @@ +package binpacket + +import ( + "github.com/Andoryuuta/Erupe/network" + "github.com/Andoryuuta/byteframe" +) + +type ChatTargetType uint16 + +const ( + CHAT_TARGET_PRIVATE = 0x05 + CHAT_TARGET_PARTY = 0x04 +) + +type MsgBinTargetedChatMessage struct { + // I can't see a reason if this is indeed the number of targets, that + // it should use 2 bytes + TargetCount uint16 + TargetCharIDs []uint32 + TargetType uint16 + RawDataPayload []byte +} + +// Opcode returns the ID associated with this packet type. +func (m *MsgBinTargetedChatMessage) Opcode() network.PacketID { + return network.MSG_SYS_CAST_BINARY +} + +func (m *MsgBinTargetedChatMessage) Parse(bf *byteframe.ByteFrame) error { + m.TargetCount = bf.ReadUint16() + i := uint16(0) + + m.TargetCharIDs = make([]uint32, m.TargetCount) + + for ; i < m.TargetCount; i++ { + m.TargetCharIDs[i] = bf.ReadUint32() + } + + m.TargetType = bf.ReadUint16() + m.RawDataPayload = bf.DataFromCurrent() + + return nil +} + +// Build builds a binary packet from the current data. +func (m *MsgBinTargetedChatMessage) Build(bf *byteframe.ByteFrame) error { + panic("Not implemented") +} diff --git a/network/mhfpacket/msg_sys_delete_user.go b/network/mhfpacket/msg_sys_delete_user.go index 0a3584763..a84589897 100644 --- a/network/mhfpacket/msg_sys_delete_user.go +++ b/network/mhfpacket/msg_sys_delete_user.go @@ -6,7 +6,9 @@ import ( ) // MsgSysDeleteUser represents the MSG_SYS_DELETE_USER -type MsgSysDeleteUser struct{} +type MsgSysDeleteUser struct { + CharID uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgSysDeleteUser) Opcode() network.PacketID { @@ -20,5 +22,7 @@ func (m *MsgSysDeleteUser) Parse(bf *byteframe.ByteFrame) error { // Build builds a binary packet from the current data. func (m *MsgSysDeleteUser) Build(bf *byteframe.ByteFrame) error { - panic("Not implemented") + bf.WriteUint32(m.CharID) + + return nil } diff --git a/server/channelserver/channel_server.go b/server/channelserver/channel_server.go index 500a6bc3f..6369721ca 100644 --- a/server/channelserver/channel_server.go +++ b/server/channelserver/channel_server.go @@ -167,3 +167,20 @@ func (s *Server) BroadcastMHF(pkt mhfpacket.MHFPacket, ignoredSession *Session) session.QueueSendNonBlocking(bf.Data()) } } + +func (s *Server) FindSessionByCharID(charID uint32) *Session { + s.stagesLock.RLock() + defer s.stagesLock.RUnlock() + for _, stage := range s.stages { + stage.RLock() + for client := range stage.clients { + if client.charID == charID { + stage.RUnlock() + return client + } + } + stage.RUnlock() + } + + return nil +} diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index bcc1b640f..33061acd3 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -6,6 +6,8 @@ import ( "encoding/base64" "encoding/hex" "fmt" + "github.com/Andoryuuta/Erupe/network/binpacket" + "io" "io/ioutil" "os" "path/filepath" @@ -177,7 +179,9 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { s.QueueAck(pkt.AckHandle, bf.Data()) } -func handleMsgSysLogout(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgSysLogout(s *Session, p mhfpacket.MHFPacket) { + logoutPlayer(s) +} func handleMsgSysSetStatus(s *Session, p mhfpacket.MHFPacket) {} @@ -190,21 +194,71 @@ func handleMsgSysPing(s *Session, p mhfpacket.MHFPacket) { s.QueueAck(pkt.AckHandle, bf.Data()) } +const ( + BINARY_MESSAGE_TYPE_CHAT = 1 + BINARY_MESSAGE_TYPE_EMOTE = 6 +) + +const ( + CHAT_TYPE_WORLD = 0x0a + CHAT_TYPE_STAGE = 0x03 + CHAT_TYPE_TARGETED = 0x01 +) + 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 { + if pkt.Type1 == BINARY_MESSAGE_TYPE_CHAT { + bf := byteframe.NewByteFrame() + bf.WriteBytes(pkt.RawDataPayload) + bf.Seek(0, io.SeekStart) + + fmt.Println("Got chat message!") + + switch pkt.Type0 { + case CHAT_TYPE_WORLD: + s.server.BroadcastMHF(resp, s) + case CHAT_TYPE_STAGE: + s.stage.BroadcastMHF(resp, s) + case CHAT_TYPE_TARGETED: + chatMessage := &binpacket.MsgBinTargetedChatMessage{} + err := chatMessage.Parse(bf) + + if err != nil { + s.logger.Warn("failed to parse chat message") + break + } + + chatBf := byteframe.NewByteFrame() + + chatBf.WriteUint16(chatMessage.TargetType) + chatBf.WriteBytes(chatMessage.RawDataPayload) + + resp = &mhfpacket.MsgSysCastedBinary{ + CharID: s.charID, + Type0: pkt.Type0, + Type1: pkt.Type1, + RawDataPayload: chatBf.Data(), + } + + for _, targetID := range chatMessage.TargetCharIDs { + char := s.server.FindSessionByCharID(targetID) + + if char != nil { + char.QueueSendMHF(resp) + } + } + default: + s.stage.BroadcastMHF(resp, s) + } + /* // Made the inside of the casted binary payload := byteframe.NewByteFrame() @@ -237,6 +291,11 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) { bfw.WriteUint16(uint16(len(payloadBytes))) bfw.WriteBytes(payloadBytes) */ + } else { + // 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). + s.server.BroadcastMHF(resp, s) } } @@ -348,6 +407,7 @@ func handleMsgSysCreateStage(s *Session, p mhfpacket.MHFPacket) { s.server.stagesLock.Lock() stage := NewStage(stripNullTerminator(pkt.StageID)) + stage.maxPlayers = uint16(pkt.PlayerCount) s.server.stages[stage.id] = stage s.server.stagesLock.Unlock() @@ -364,27 +424,7 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { s.server.stagesLock.Unlock() if s.stage != nil { - s.stage.Lock() - - // Remove client from old stage. - delete(s.stage.clients, s) - - // Delete old stage objects owned by the client. - s.logger.Info("Sending MsgSysDeleteObject to old stage clients") - for objID, stageObject := range s.stage.objects { - if stageObject.ownerCharID == s.charID { - // Broadcast the deletion to clients in the stage. - s.stage.BroadcastMHF(&mhfpacket.MsgSysDeleteObject{ - ObjID: stageObject.id, - }, s) - // TODO(Andoryuuta): Should this be sent to the owner's client as well? it currently isn't. - - // Actually delete it form the objects map. - delete(s.stage.objects, objID) - } - } - - s.stage.Unlock() + removeSessionFromStage(s) } // Add the new stage. @@ -407,25 +447,28 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { s.QueueAck(ackHandle, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) // Notify existing stage clients that this new client has entered. - s.logger.Info("Sending MsgSysInsertUser & MsgSysNotifyUserBinary") + s.logger.Info("Sending MsgSysInsertUser") s.stage.BroadcastMHF(&mhfpacket.MsgSysInsertUser{ CharID: s.charID, }, s) - s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{ - CharID: s.charID, - BinaryType: 1, - }, s) - s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{ - CharID: s.charID, - BinaryType: 2, - }, s) - s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{ - CharID: s.charID, - BinaryType: 3, - }, s) + // It seems to be acceptable to recast all MSG_SYS_SET_USER_BINARY messages so far, + // players are still notified when a new player has joined the stage. + // These extra messages may not be needed + //s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{ + // CharID: s.charID, + // BinaryType: 1, + //}, s) + //s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{ + // CharID: s.charID, + // BinaryType: 2, + //}, s) + //s.stage.BroadcastMHF(&mhfpacket.MsgSysNotifyUserBinary{ + // CharID: s.charID, + // BinaryType: 3, + //}, s) - // Notify the entree client about all of the existing clients in the stage. + //Notify the entree client about all of the existing clients in the stage. s.logger.Info("Notifying entree about existing stage clients") s.stage.RLock() clientNotif := byteframe.NewByteFrame() @@ -483,6 +526,54 @@ func doStageTransfer(s *Session, ackHandle uint32, stageID string) { s.QueueSend(clientDupObjNotif.Data()) } +func removeSessionFromStage(s *Session) { + s.stage.Lock() + defer s.stage.Unlock() + + // Remove client from old stage. + delete(s.stage.clients, s) + + // Delete old stage objects owned by the client. + s.logger.Info("Sending MsgSysDeleteObject to old stage clients") + for objID, stageObject := range s.stage.objects { + if stageObject.ownerCharID == s.charID { + // Broadcast the deletion to clients in the stage. + s.stage.BroadcastMHF(&mhfpacket.MsgSysDeleteObject{ + ObjID: stageObject.id, + }, s) + // TODO(Andoryuuta): Should this be sent to the owner's client as well? it currently isn't. + + // Actually delete it form the objects map. + delete(s.stage.objects, objID) + } + } +} + +func stageContainsSession(stage *Stage, s *Session) bool { + stage.RLock() + defer stage.RUnlock() + + for session := range stage.clients { + if session == s { + return true + } + } + + return false +} + +func logoutPlayer(s *Session) { + s.stage.RLock() + for client := range s.stage.clients { + client.QueueSendMHF(&mhfpacket.MsgSysDeleteUser{ + CharID: s.charID, + }) + } + s.stage.RUnlock() + + removeSessionFromStage(s) +} + func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysEnterStage) @@ -540,7 +631,7 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysReserveStage) stageID := stripNullTerminator(pkt.StageID) - fmt.Printf("Got reserve stage req, Unk0:%v, StageID:%v\n", pkt.Unk0, stageID) + fmt.Printf("Got reserve stage req, TargetCount:%v, StageID:%v\n", pkt.Unk0, stageID) // Try to get the stage s.server.stagesLock.Lock() @@ -568,6 +659,7 @@ func handleMsgSysReserveStage(s *Session, p mhfpacket.MHFPacket) { } else { s.QueueAck(pkt.AckHandle, []byte{0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}) } + } func handleMsgSysUnreserveStage(s *Session, p mhfpacket.MHFPacket) { @@ -691,6 +783,11 @@ func handleMsgSysGetStageBinary(s *Session, p mhfpacket.MHFPacket) { if gotBinary { doSizedAckResp(s, pkt.AckHandle, stageBinary) + + } else if pkt.BinaryType1 == 4 { + // This particular type seems to be expecting data that isn't set + // is it required before the party joining can be completed + s.QueueAck(pkt.AckHandle, []byte{0x01, 0x00, 0x00, 0x00, 0x10}) } else { s.logger.Warn("Failed to get stage binary", zap.Uint8("BinaryType0", pkt.BinaryType0), zap.Uint8("pkt.BinaryType1", pkt.BinaryType1)) s.logger.Warn("Sending blank stage binary") @@ -846,7 +943,7 @@ func handleMsgSysCreateObject(s *Session, p mhfpacket.MHFPacket) { // Response to our requesting client. resp := byteframe.NewByteFrame() - resp.WriteUint32(0) // Unk, is this echoed back from pkt.Unk0? + resp.WriteUint32(0) // Unk, is this echoed back from pkt.TargetCount? resp.WriteUint32(objID) // New local obj handle. s.QueueAck(pkt.AckHandle, resp.Data()) @@ -909,6 +1006,13 @@ func handleMsgSysSetUserBinary(s *Session, p mhfpacket.MHFPacket) { s.server.userBinaryPartsLock.Lock() s.server.userBinaryParts[userBinaryPartID{charID: s.charID, index: pkt.BinaryType}] = pkt.RawDataPayload s.server.userBinaryPartsLock.Unlock() + + msg := &mhfpacket.MsgSysNotifyUserBinary{ + CharID: s.charID, + BinaryType: pkt.BinaryType, + } + + s.stage.BroadcastMHF(msg, s) } func handleMsgSysGetUserBinary(s *Session, p mhfpacket.MHFPacket) { diff --git a/server/channelserver/session.go b/server/channelserver/session.go index 26bec24ed..0f3329c37 100644 --- a/server/channelserver/session.go +++ b/server/channelserver/session.go @@ -3,6 +3,7 @@ package channelserver import ( "encoding/hex" "fmt" + "io" "net" "sync" @@ -59,6 +60,11 @@ func (s *Session) Start() { // QueueSend queues a packet (raw []byte) to be sent. func (s *Session) QueueSend(data []byte) { + if s.server.erupeConfig.DevMode && s.server.erupeConfig.DevModeOptions.LogOutboundMessages { + fmt.Printf("Sending To CharID: '%x'\n", s.charID) + fmt.Printf("Sent Data:\n%s\n", hex.Dump(data)) + } + s.sendPackets <- data } @@ -119,6 +125,13 @@ func (s *Session) sendLoop() { func (s *Session) recvLoop() { for { pkt, err := s.cryptConn.ReadPacket() + + if err == io.EOF { + s.logger.Info(fmt.Sprintf("Character(%d) disconnected", s.charID)) + logoutPlayer(s) + return + } + if err != nil { s.logger.Warn("Error on ReadPacket, exiting recv loop", zap.Error(err)) return @@ -145,6 +158,7 @@ func (s *Session) handlePacketGroup(pktGroup []byte) { opcode != network.MSG_SYS_NOP && opcode != network.MSG_SYS_TIME && opcode != network.MSG_SYS_EXTEND_THRESHOLD { + fmt.Printf("CharID: '%x'\n", s.charID) fmt.Printf("Opcode: %s\n", opcode) fmt.Printf("Data:\n%s\n", hex.Dump(pktGroup)) }