diff --git a/common/mhfmon/mhfmon.go b/common/mhfmon/mhfmon.go index 183a75bd8..e192844fe 100644 --- a/common/mhfmon/mhfmon.go +++ b/common/mhfmon/mhfmon.go @@ -82,7 +82,7 @@ const ( BrightHypnoc RedLavasioth Espinas - OrangeEspinas + BurningEspinas WhiteHypnoc AqraVashimu AqraJebia @@ -91,7 +91,7 @@ const ( Mon87 Mon88 Pariapuria - WhiteEspinas + PearlEspinas KamuOrugaron NonoOrugaron Raviente @@ -267,7 +267,7 @@ var Monsters = []Monster{ {"Bright Hypnocatrice", true}, {"Red Lavasioth", true}, {"Espinas", true}, - {"Orange Espinas", true}, + {"Burning Espinas", true}, {"White Hypnocatrice", true}, {"Aqra Vashimu", true}, {"Aqra Jebia", true}, @@ -276,7 +276,7 @@ var Monsters = []Monster{ {"Mon87", false}, {"Mon88", false}, {"Pariapuria", true}, - {"White Espinas", true}, + {"Pearl Espinas", true}, {"Kamu Orugaron", true}, {"Nono Orugaron", true}, {"Raviente", true}, // + Violent diff --git a/common/stringstack/stringstack.go b/common/stringstack/stringstack.go index 2e45fd7d1..9f6b646ae 100644 --- a/common/stringstack/stringstack.go +++ b/common/stringstack/stringstack.go @@ -6,7 +6,8 @@ import ( // StringStack is a basic LIFO "stack" for storing strings. type StringStack struct { - stack []string + Locked bool + stack []string } // New creates a new instance of StringStack @@ -19,6 +20,20 @@ func (s *StringStack) Set(v string) { s.stack = []string{v} } +// Lock freezes the StringStack +func (s *StringStack) Lock() { + if !s.Locked { + s.Locked = true + } +} + +// Unlock unfreezes the StringStack +func (s *StringStack) Unlock() { + if s.Locked { + s.Locked = false + } +} + // Push pushes a string onto the stack. func (s *StringStack) Push(v string) { s.stack = append(s.stack, v) @@ -26,11 +41,12 @@ func (s *StringStack) Push(v string) { // Pop pops a string from the stack. func (s *StringStack) Pop() (string, error) { + var x string if len(s.stack) == 0 { - return "", errors.New("no items on stack") + return x, errors.New("no items on stack") } - x := s.stack[len(s.stack)-1] + x = s.stack[len(s.stack)-1] s.stack = s.stack[:len(s.stack)-1] return x, nil diff --git a/config.json b/config.json index cc74da8de..159388f49 100644 --- a/config.json +++ b/config.json @@ -34,6 +34,7 @@ "EarthMonsterOverride": [0, 0, 0, 0], "SaveDumps": { "Enabled": true, + "RawEnabled": false, "OutputDir": "save-backups" } }, @@ -46,7 +47,8 @@ "DisableLoginBoost": false, "DisableBoostTime": false, "BoostTimeDuration": 7200, - "GuildMealDuration": 3600, + "ClanMealDuration": 3600, + "ClanMemberLimits": [[0, 30], [3, 40], [7, 50], [10, 60]], "BonusQuestAllowance": 3, "DailyQuestAllowance": 1, "MezfesSoloTickets": 10, diff --git a/config/config.go b/config/config.go index aea353d25..3aaa7e708 100644 --- a/config/config.go +++ b/config/config.go @@ -117,27 +117,29 @@ type DevModeOptions struct { } type SaveDumpOptions struct { - Enabled bool - OutputDir string + Enabled bool + RawEnabled bool + OutputDir string } // GameplayOptions has various gameplay modifiers type GameplayOptions struct { - FeaturedWeapons int // Number of Active Feature weapons to generate daily - MaximumNP int // Maximum number of NP held by a player - MaximumRP uint16 // Maximum number of RP held by a player - MaximumFP uint32 // Maximum number of FP held by a player - TreasureHuntExpiry uint32 // Seconds until a Clan Treasure Hunt will expire - TreasureHuntPartnyaCooldown uint32 // Seconds until a Partnya can be assigned to another Clan Treasure Hunt - DisableLoginBoost bool // Disables the Login Boost system - DisableBoostTime bool // Disables the daily NetCafe Boost Time - BoostTimeDuration int // Second that the NetCafe Boost Time lasts - GuildMealDuration int // Second that a Guild Meal can be activated for after cooking - BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily - DailyQuestAllowance uint32 // Number of Daily Quests to allow daily - MezfesSoloTickets uint32 // Number of solo tickets given weekly - MezfesGroupTickets uint32 // Number of group tickets given weekly - LowLatencyRaviente bool // Toggles low latency mode for Raviente, can be network intensive + FeaturedWeapons int // Number of Active Feature weapons to generate daily + MaximumNP int // Maximum number of NP held by a player + MaximumRP uint16 // Maximum number of RP held by a player + MaximumFP uint32 // Maximum number of FP held by a player + TreasureHuntExpiry uint32 // Seconds until a Clan Treasure Hunt will expire + TreasureHuntPartnyaCooldown uint32 // Seconds until a Partnya can be assigned to another Clan Treasure Hunt + DisableLoginBoost bool // Disables the Login Boost system + DisableBoostTime bool // Disables the daily NetCafe Boost Time + BoostTimeDuration int // Second that the NetCafe Boost Time lasts + ClanMealDuration int // Second that a Clan Meal can be activated for after cooking + ClanMemberLimits [][]uint8 // Array of maximum Clan Members -> [Rank, Members] + BonusQuestAllowance uint32 // Number of Bonus Point Quests to allow daily + DailyQuestAllowance uint32 // Number of Daily Quests to allow daily + MezfesSoloTickets uint32 // Number of solo tickets given weekly + MezfesGroupTickets uint32 // Number of group tickets given weekly + LowLatencyRaviente bool // Toggles low latency mode for Raviente, can be network intensive RegularRavienteMaxPlayers uint8 ViolentRavienteMaxPlayers uint8 BerserkRavienteMaxPlayers uint8 diff --git a/network/mhfpacket/msg_mhf_opr_member.go b/network/mhfpacket/msg_mhf_opr_member.go index b0dceaba5..186ccc44d 100644 --- a/network/mhfpacket/msg_mhf_opr_member.go +++ b/network/mhfpacket/msg_mhf_opr_member.go @@ -13,7 +13,6 @@ type MsgMhfOprMember struct { AckHandle uint32 Blacklist bool Operation bool - Unk uint16 CharIDs []uint32 } diff --git a/network/mhfpacket/msg_mhf_unreserve_srg.go b/network/mhfpacket/msg_mhf_unreserve_srg.go index f273662aa..9f545dabd 100644 --- a/network/mhfpacket/msg_mhf_unreserve_srg.go +++ b/network/mhfpacket/msg_mhf_unreserve_srg.go @@ -1,15 +1,17 @@ package mhfpacket -import ( - "errors" +import ( + "errors" - "erupe-ce/network/clientctx" - "erupe-ce/network" "erupe-ce/common/byteframe" + "erupe-ce/network" + "erupe-ce/network/clientctx" ) // MsgMhfUnreserveSrg represents the MSG_MHF_UNRESERVE_SRG -type MsgMhfUnreserveSrg struct{} +type MsgMhfUnreserveSrg struct { + AckHandle uint32 +} // Opcode returns the ID associated with this packet type. func (m *MsgMhfUnreserveSrg) Opcode() network.PacketID { @@ -18,7 +20,8 @@ func (m *MsgMhfUnreserveSrg) Opcode() network.PacketID { // Parse parses the packet from binary func (m *MsgMhfUnreserveSrg) Parse(bf *byteframe.ByteFrame, ctx *clientctx.ClientContext) error { - return errors.New("NOT IMPLEMENTED") + m.AckHandle = bf.ReadUint32() + return nil } // Build builds a binary packet from the current data. diff --git a/server/channelserver/handlers.go b/server/channelserver/handlers.go index 7445d0f2b..193ccdd9f 100644 --- a/server/channelserver/handlers.go +++ b/server/channelserver/handlers.go @@ -2,7 +2,6 @@ package channelserver import ( "encoding/binary" - "encoding/hex" "erupe-ce/common/mhfcourse" "erupe-ce/common/mhfmon" ps "erupe-ce/common/pascalstring" @@ -130,7 +129,7 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) { if !s.server.erupeConfig.DevModeOptions.DisableTokenCheck { var token string - err := s.server.db.QueryRow("SELECT token FROM sign_sessions WHERE token=$1", pkt.LoginTokenString).Scan(&token) + err := s.server.db.QueryRow("SELECT token FROM sign_sessions ss INNER JOIN public.users u on ss.user_id = u.id WHERE token=$1 AND ss.id=$2 AND u.id=(SELECT c.user_id FROM characters c WHERE c.id=$3)", pkt.LoginTokenString, pkt.LoginTokenNumber, pkt.CharID0).Scan(&token) if err != nil { s.rawConn.Close() s.logger.Warn(fmt.Sprintf("Invalid login token, offending CID: (%d)", pkt.CharID0)) @@ -366,143 +365,117 @@ func handleMsgSysRightsReload(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfTransitMessage) + + local := false + if strings.Split(s.rawConn.RemoteAddr().String(), ":")[0] == "127.0.0.1" { + local = true + } + + var maxResults, port, count uint16 + var cid uint32 + var term, ip string + bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) + switch pkt.SearchType { + case 1: + maxResults = 1 + cid = bf.ReadUint32() + case 2: + bf.ReadUint16() // term length + maxResults = bf.ReadUint16() + bf.ReadUint8() // Unk + term = stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) + case 3: + _ip := bf.ReadBytes(4) + ip = fmt.Sprintf("%d.%d.%d.%d", _ip[3], _ip[2], _ip[1], _ip[0]) + port = bf.ReadUint16() + bf.ReadUint16() // term length + maxResults = bf.ReadUint16() + bf.ReadUint8() + term = string(bf.ReadNullTerminatedBytes()) + } + resp := byteframe.NewByteFrame() resp.WriteUint16(0) - var count uint16 switch pkt.SearchType { - case 1: // CID - bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) - CharID := bf.ReadUint32() + case 1, 2, 3: // usersearchidx, usersearchname, lobbysearchname for _, c := range s.server.Channels { for _, session := range c.sessions { - if session.charID == CharID { - count++ - sessionName := stringsupport.UTF8ToSJIS(session.Name) - sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) - resp.WriteUint16(c.Port) - resp.WriteUint32(session.charID) - resp.WriteBool(true) - resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]))) - resp.WriteBytes(make([]byte, 40)) - resp.WriteUint8(uint8(len(sessionStage) + 1)) - resp.WriteBytes(make([]byte, 8)) - resp.WriteNullTerminatedBytes(sessionName) - resp.WriteBytes(c.userBinaryParts[userBinaryPartID{session.charID, 3}]) - resp.WriteNullTerminatedBytes(sessionStage) - } - } - } - case 2: // Name - bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) - bf.ReadUint16() // lenSearchTerm - bf.ReadUint16() // maxResults - bf.ReadUint8() // Unk - searchTerm := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - for _, c := range s.server.Channels { - for _, session := range c.sessions { - if count == 100 { + if count == maxResults { break } - if strings.Contains(session.Name, searchTerm) { - count++ - sessionName := stringsupport.UTF8ToSJIS(session.Name) - sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) + if pkt.SearchType == 1 && session.charID != cid { + continue + } + if pkt.SearchType == 2 && !strings.Contains(session.Name, term) { + continue + } + if pkt.SearchType == 3 && session.server.IP != ip && session.server.Port != port && session.stage.id != term { + continue + } + count++ + sessionName := stringsupport.UTF8ToSJIS(session.Name) + sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) + if !local { resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) - resp.WriteUint16(c.Port) - resp.WriteUint32(session.charID) - resp.WriteBool(true) - resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{session.charID, 3}]))) - resp.WriteBytes(make([]byte, 40)) - resp.WriteUint8(uint8(len(sessionStage) + 1)) + } else { + resp.WriteUint32(0x0100007F) + } + resp.WriteUint16(c.Port) + resp.WriteUint32(session.charID) + resp.WriteUint8(uint8(len(sessionStage) + 1)) + resp.WriteUint8(uint8(len(sessionName) + 1)) + resp.WriteUint16(uint16(len(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]))) + + // TODO: This case might be <=G2 + if _config.ErupeConfig.RealClientMode <= _config.G1 { resp.WriteBytes(make([]byte, 8)) - resp.WriteNullTerminatedBytes(sessionName) - resp.WriteBytes(c.userBinaryParts[userBinaryPartID{charID: session.charID, index: 3}]) - resp.WriteNullTerminatedBytes(sessionStage) + } else { + resp.WriteBytes(make([]byte, 40)) } + resp.WriteBytes(make([]byte, 8)) + + resp.WriteNullTerminatedBytes(sessionStage) + resp.WriteNullTerminatedBytes(sessionName) + resp.WriteBytes(c.userBinaryParts[userBinaryPartID{session.charID, 3}]) } } - case 3: // Enumerate Party - bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) - ip := bf.ReadBytes(4) - ipString := fmt.Sprintf("%d.%d.%d.%d", ip[3], ip[2], ip[1], ip[0]) - port := bf.ReadUint16() - bf.ReadUint16() // lenStage - maxResults := bf.ReadUint16() - bf.ReadBytes(1) - stageID := stringsupport.SJISToUTF8(bf.ReadNullTerminatedBytes()) - for _, c := range s.server.Channels { - if c.IP == ipString && c.Port == port { - for _, stage := range c.stages { - if stage.id == stageID { - if count == maxResults { - break - } - for session := range stage.clients { - count++ - hrp := uint16(1) - gr := uint16(0) - s.server.db.QueryRow("SELECT hrp, gr FROM characters WHERE id=$1", session.charID).Scan(&hrp, &gr) - sessionStage := stringsupport.UTF8ToSJIS(session.stage.id) - sessionName := stringsupport.UTF8ToSJIS(session.Name) - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) - resp.WriteUint16(c.Port) - resp.WriteUint32(session.charID) - resp.WriteUint8(uint8(len(sessionStage) + 1)) - resp.WriteUint8(uint8(len(sessionName) + 1)) - resp.WriteUint8(0) - resp.WriteUint8(7) // lenBinary - resp.WriteBytes(make([]byte, 48)) - resp.WriteNullTerminatedBytes(sessionStage) - resp.WriteNullTerminatedBytes(sessionName) - resp.WriteUint16(hrp) - resp.WriteUint16(gr) - resp.WriteBytes([]byte{0x06, 0x10, 0x00}) // Unk - } - } - } - } - } - case 4: // Find Party + case 4: // lobbysearch type FindPartyParams struct { StagePrefix string - RankRestriction uint16 - Targets []uint16 - Unk0 []uint16 - Unk1 []uint16 - QuestID []uint16 + RankRestriction int16 + Targets []int16 + Unk0 []int16 + Unk1 []int16 + QuestID []int16 } findPartyParams := FindPartyParams{ StagePrefix: "sl2Ls210", } - bf := byteframe.NewByteFrameFromBytes(pkt.MessageData) - numParams := int(bf.ReadUint8()) - maxResults := bf.ReadUint16() - for i := 0; i < numParams; i++ { + numParams := bf.ReadUint8() + maxResults = bf.ReadUint16() + for i := uint8(0); i < numParams; i++ { switch bf.ReadUint8() { case 0: - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.RankRestriction = bf.ReadUint16() + findPartyParams.RankRestriction = bf.ReadInt16() } else { - findPartyParams.RankRestriction = uint16(bf.ReadInt8()) + findPartyParams.RankRestriction = int16(bf.ReadInt8()) } } case 1: - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.Targets = append(findPartyParams.Targets, bf.ReadUint16()) + findPartyParams.Targets = append(findPartyParams.Targets, bf.ReadInt16()) } else { - findPartyParams.Targets = append(findPartyParams.Targets, uint16(bf.ReadInt8())) + findPartyParams.Targets = append(findPartyParams.Targets, int16(bf.ReadInt8())) } } case 2: - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { var value int16 if _config.ErupeConfig.RealClientMode >= _config.Z1 { value = bf.ReadInt16() @@ -523,30 +496,30 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } } case 3: // Unknown - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.Unk0 = append(findPartyParams.Unk0, bf.ReadUint16()) + findPartyParams.Unk0 = append(findPartyParams.Unk0, bf.ReadInt16()) } else { - findPartyParams.Unk0 = append(findPartyParams.Unk0, uint16(bf.ReadInt8())) + findPartyParams.Unk0 = append(findPartyParams.Unk0, int16(bf.ReadInt8())) } } case 4: // Looking for n or already have n - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.Unk1 = append(findPartyParams.Unk1, bf.ReadUint16()) + findPartyParams.Unk1 = append(findPartyParams.Unk1, bf.ReadInt16()) } else { - findPartyParams.Unk1 = append(findPartyParams.Unk1, uint16(bf.ReadInt8())) + findPartyParams.Unk1 = append(findPartyParams.Unk1, int16(bf.ReadInt8())) } } case 5: - values := int(bf.ReadUint8()) - for i := 0; i < values; i++ { + values := bf.ReadUint8() + for i := uint8(0); i < values; i++ { if _config.ErupeConfig.RealClientMode >= _config.Z1 { - findPartyParams.QuestID = append(findPartyParams.QuestID, bf.ReadUint16()) + findPartyParams.QuestID = append(findPartyParams.QuestID, bf.ReadInt16()) } else { - findPartyParams.QuestID = append(findPartyParams.QuestID, uint16(bf.ReadInt8())) + findPartyParams.QuestID = append(findPartyParams.QuestID, int16(bf.ReadInt8())) } } } @@ -559,47 +532,81 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { if strings.HasPrefix(stage.id, findPartyParams.StagePrefix) { sb3 := byteframe.NewByteFrameFromBytes(stage.rawBinaryData[stageBinaryKey{1, 3}]) sb3.Seek(4, 0) - stageRankRestriction := sb3.ReadUint16() - stageTarget := sb3.ReadUint16() - if stageRankRestriction > findPartyParams.RankRestriction { - continue + + stageDataParams := 7 + if _config.ErupeConfig.RealClientMode <= _config.G10 { + stageDataParams = 4 + } else if _config.ErupeConfig.RealClientMode <= _config.Z1 { + stageDataParams = 6 } + + var stageData []int16 + for i := 0; i < stageDataParams; i++ { + if _config.ErupeConfig.RealClientMode >= _config.Z1 { + stageData = append(stageData, sb3.ReadInt16()) + } else { + stageData = append(stageData, int16(sb3.ReadInt8())) + } + } + + if findPartyParams.RankRestriction >= 0 { + if stageData[0] > findPartyParams.RankRestriction { + continue + } + } + + var hasTarget bool if len(findPartyParams.Targets) > 0 { for _, target := range findPartyParams.Targets { - if target == stageTarget { + if target == stageData[1] { + hasTarget = true break } } - continue + if !hasTarget { + continue + } } + count++ - sessionStage := stringsupport.UTF8ToSJIS(stage.id) - resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + if !local { + resp.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(c.IP).To4())) + } else { + resp.WriteUint32(0x0100007F) + } resp.WriteUint16(c.Port) + resp.WriteUint16(0) // Static? - resp.WriteUint16(0) // Unk - resp.WriteUint16(uint16(len(stage.clients))) - resp.WriteUint16(stage.maxPlayers) - resp.WriteUint16(0) // Num clients entered from stage + resp.WriteUint16(0) // Unk, [0 1 2] + resp.WriteUint16(uint16(len(stage.clients) + len(stage.reservedClientSlots))) resp.WriteUint16(stage.maxPlayers) + // TODO: Retail returned the number of clients in quests, not workshop/my series + resp.WriteUint16(uint16(len(stage.reservedClientSlots))) + + resp.WriteUint8(0) // Static? + resp.WriteUint8(uint8(stage.maxPlayers)) resp.WriteUint8(1) // Static? - resp.WriteUint8(uint8(len(sessionStage) + 1)) + resp.WriteUint8(uint8(len(stage.id) + 1)) resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 0}]))) resp.WriteUint8(uint8(len(stage.rawBinaryData[stageBinaryKey{1, 1}]))) - resp.WriteUint16(stageRankRestriction) - resp.WriteUint16(stageTarget) - resp.WriteBytes(make([]byte, 12)) - resp.WriteNullTerminatedBytes(sessionStage) + + for i := range stageData { + if _config.ErupeConfig.RealClientMode >= _config.Z1 { + resp.WriteInt16(stageData[i]) + } else { + resp.WriteInt8(int8(stageData[i])) + } + } + resp.WriteUint8(0) // Unk + resp.WriteUint8(0) // Unk + + resp.WriteNullTerminatedBytes([]byte(stage.id)) resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 0}]) resp.WriteBytes(stage.rawBinaryData[stageBinaryKey{1, 1}]) } } } } - if (pkt.SearchType == 1 || pkt.SearchType == 3) && count == 0 { - doAckBufFail(s, pkt.AckHandle, make([]byte, 4)) - return - } resp.Seek(0, io.SeekStart) resp.WriteUint16(count) doAckBufSucceed(s, pkt.AckHandle, resp.Data()) @@ -640,12 +647,167 @@ func handleMsgMhfTransferItem(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfEnumeratePrice(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfEnumeratePrice) - //resp := byteframe.NewByteFrame() - //resp.WriteUint16(0) // Entry type 1 count - //resp.WriteUint16(0) // Entry type 2 count - // directly lifted for now because lacking it crashes the counter on having actual events present - data, _ := hex.DecodeString("0000000066000003E800000000007300640100000320000000000006006401000003200000000000300064010000044C00000000007200640100000384000000000034006401000003840000000000140064010000051400000000006E006401000003E8000000000016006401000003E8000000000001006401000003200000000000430064010000057800000000006F006401000003840000000000330064010000044C00000000000B006401000003E800000000000F006401000006400000000000700064010000044C0000000000110064010000057800000000004C006401000003E8000000000059006401000006A400000000006D006401000005DC00000000004B006401000005DC000000000050006401000006400000000000350064010000070800000000006C0064010000044C000000000028006401000005DC00000000005300640100000640000000000060006401000005DC00000000005E0064010000051400000000007B006401000003E80000000000740064010000070800000000006B0064010000025800000000001B0064010000025800000000001C006401000002BC00000000001F006401000006A400000000007900640100000320000000000008006401000003E80000000000150064010000070800000000007A0064010000044C00000000000E00640100000640000000000055006401000007D0000000000002006401000005DC00000000002F0064010000064000000000002A0064010000076C00000000007E006401000002BC0000000000440064010000038400000000005C0064010000064000000000005B006401000006A400000000007D0064010000076C00000000007F006401000005DC0000000000540064010000064000000000002900640100000960000000000024006401000007D0000000000081006401000008340000000000800064010000038400000000001A006401000003E800000000002D0064010000038400000000004A006401000006A400000000005A00640100000384000000000027006401000007080000000000830064010000076C000000000040006401000006400000000000690064010000044C000000000025006401000004B000000000003100640100000708000000000082006401000003E800000000006500640100000640000000000051006401000007D000000000008C0064010000070800000000004D0064010000038400000000004E0064010000089800000000008B006401000004B000000000002E006401000009600000000000920064010000076C00000000008E00640100000514000000000068006401000004B000000000002B006401000003E800000000002C00640100000BB8000000000093006401000008FC00000000009000640100000AF0000000000094006401000006A400000000008D0064010000044C000000000052006401000005DC00000000004F006401000008980000000000970064010000070800000000006A0064010000064000000000005F00640100000384000000000026006401000008FC000000000096006401000007D00000000000980064010000076C000000000041006401000006A400000000003B006401000007080000000000360064010000083400000000009F00640100000A2800000000009A0064010000076C000000000021006401000007D000000000006300640100000A8C0000000000990064010000089800000000009E006401000007080000000000A100640100000C1C0000000000A200640100000C800000000000A400640100000DAC0000000000A600640100000C800000000000A50064010010") - doAckBufSucceed(s, pkt.AckHandle, data) + bf := byteframe.NewByteFrame() + var lbPrices []struct { + Unk0 uint16 + Unk1 uint16 + Unk2 uint32 + } + var wantedList []struct { + Unk0 uint32 + Unk1 uint32 + Unk2 uint32 + Unk3 uint16 + Unk4 uint16 + Unk5 uint16 + Unk6 uint16 + Unk7 uint16 + Unk8 uint16 + Unk9 uint16 + } + gzPrices := []struct { + Unk0 uint16 + Gz uint16 + Unk1 uint16 + Unk2 uint16 + MonID uint16 + Unk3 uint16 + Unk4 uint8 + }{ + {0, 1000, 0, 0, mhfmon.Pokaradon, 100, 1}, + {0, 800, 0, 0, mhfmon.YianKutKu, 100, 1}, + {0, 800, 0, 0, mhfmon.DaimyoHermitaur, 100, 1}, + {0, 1100, 0, 0, mhfmon.Farunokku, 100, 1}, + {0, 900, 0, 0, mhfmon.Congalala, 100, 1}, + {0, 900, 0, 0, mhfmon.Gypceros, 100, 1}, + {0, 1300, 0, 0, mhfmon.Hyujikiki, 100, 1}, + {0, 1000, 0, 0, mhfmon.Basarios, 100, 1}, + {0, 1000, 0, 0, mhfmon.Rathian, 100, 1}, + {0, 800, 0, 0, mhfmon.ShogunCeanataur, 100, 1}, + {0, 1400, 0, 0, mhfmon.Midogaron, 100, 1}, + {0, 900, 0, 0, mhfmon.Blangonga, 100, 1}, + {0, 1100, 0, 0, mhfmon.Rathalos, 100, 1}, + {0, 1000, 0, 0, mhfmon.Khezu, 100, 1}, + {0, 1600, 0, 0, mhfmon.Giaorugu, 100, 1}, + {0, 1100, 0, 0, mhfmon.Gravios, 100, 1}, + {0, 1400, 0, 0, mhfmon.Tigrex, 100, 1}, + {0, 1000, 0, 0, mhfmon.Pariapuria, 100, 1}, + {0, 1700, 0, 0, mhfmon.Anorupatisu, 100, 1}, + {0, 1500, 0, 0, mhfmon.Lavasioth, 100, 1}, + {0, 1500, 0, 0, mhfmon.Espinas, 100, 1}, + {0, 1600, 0, 0, mhfmon.Rajang, 100, 1}, + {0, 1800, 0, 0, mhfmon.Rebidiora, 100, 1}, + {0, 1100, 0, 0, mhfmon.YianGaruga, 100, 1}, + {0, 1500, 0, 0, mhfmon.AqraVashimu, 100, 1}, + {0, 1600, 0, 0, mhfmon.Gurenzeburu, 100, 1}, + {0, 1500, 0, 0, mhfmon.Dyuragaua, 100, 1}, + {0, 1300, 0, 0, mhfmon.Gougarf, 100, 1}, + {0, 1000, 0, 0, mhfmon.Shantien, 100, 1}, + {0, 1800, 0, 0, mhfmon.Disufiroa, 100, 1}, + {0, 600, 0, 0, mhfmon.Velocidrome, 100, 1}, + {0, 600, 0, 0, mhfmon.Gendrome, 100, 1}, + {0, 700, 0, 0, mhfmon.Iodrome, 100, 1}, + {0, 1700, 0, 0, mhfmon.Baruragaru, 100, 1}, + {0, 800, 0, 0, mhfmon.Cephadrome, 100, 1}, + {0, 1000, 0, 0, mhfmon.Plesioth, 100, 1}, + {0, 1800, 0, 0, mhfmon.Zerureusu, 100, 1}, + {0, 1100, 0, 0, mhfmon.Diablos, 100, 1}, + {0, 1600, 0, 0, mhfmon.Berukyurosu, 100, 1}, + {0, 2000, 0, 0, mhfmon.Fatalis, 100, 1}, + {0, 1500, 0, 0, mhfmon.BlackGravios, 100, 1}, + {0, 1600, 0, 0, mhfmon.GoldRathian, 100, 1}, + {0, 1900, 0, 0, mhfmon.Meraginasu, 100, 1}, + {0, 700, 0, 0, mhfmon.Bulldrome, 100, 1}, + {0, 900, 0, 0, mhfmon.NonoOrugaron, 100, 1}, + {0, 1600, 0, 0, mhfmon.KamuOrugaron, 100, 1}, + {0, 1700, 0, 0, mhfmon.Forokururu, 100, 1}, + {0, 1900, 0, 0, mhfmon.Diorex, 100, 1}, + {0, 1500, 0, 0, mhfmon.AqraJebia, 100, 1}, + {0, 1600, 0, 0, mhfmon.SilverRathalos, 100, 1}, + {0, 2400, 0, 0, mhfmon.CrimsonFatalis, 100, 1}, + {0, 2000, 0, 0, mhfmon.Inagami, 100, 1}, + {0, 2100, 0, 0, mhfmon.GarubaDaora, 100, 1}, + {0, 900, 0, 0, mhfmon.Monoblos, 100, 1}, + {0, 1000, 0, 0, mhfmon.RedKhezu, 100, 1}, + {0, 900, 0, 0, mhfmon.Hypnocatrice, 100, 1}, + {0, 1700, 0, 0, mhfmon.PearlEspinas, 100, 1}, + {0, 900, 0, 0, mhfmon.PurpleGypceros, 100, 1}, + {0, 1800, 0, 0, mhfmon.Poborubarumu, 100, 1}, + {0, 1900, 0, 0, mhfmon.Lunastra, 100, 1}, + {0, 1600, 0, 0, mhfmon.Kuarusepusu, 100, 1}, + {0, 1100, 0, 0, mhfmon.PinkRathian, 100, 1}, + {0, 1200, 0, 0, mhfmon.AzureRathalos, 100, 1}, + {0, 1800, 0, 0, mhfmon.Varusaburosu, 100, 1}, + {0, 1000, 0, 0, mhfmon.Gogomoa, 100, 1}, + {0, 1600, 0, 0, mhfmon.BurningEspinas, 100, 1}, + {0, 2000, 0, 0, mhfmon.Harudomerugu, 100, 1}, + {0, 1800, 0, 0, mhfmon.Akantor, 100, 1}, + {0, 900, 0, 0, mhfmon.BrightHypnoc, 100, 1}, + {0, 2200, 0, 0, mhfmon.Gureadomosu, 100, 1}, + {0, 1200, 0, 0, mhfmon.GreenPlesioth, 100, 1}, + {0, 2400, 0, 0, mhfmon.Zinogre, 100, 1}, + {0, 1900, 0, 0, mhfmon.Gasurabazura, 100, 1}, + {0, 1300, 0, 0, mhfmon.Abiorugu, 100, 1}, + {0, 1200, 0, 0, mhfmon.BlackDiablos, 100, 1}, + {0, 1000, 0, 0, mhfmon.WhiteMonoblos, 100, 1}, + {0, 3000, 0, 0, mhfmon.Deviljho, 100, 1}, + {0, 2300, 0, 0, mhfmon.YamaKurai, 100, 1}, + {0, 2800, 0, 0, mhfmon.Brachydios, 100, 1}, + {0, 1700, 0, 0, mhfmon.Toridcless, 100, 1}, + {0, 1100, 0, 0, mhfmon.WhiteHypnoc, 100, 1}, + {0, 1500, 0, 0, mhfmon.RedLavasioth, 100, 1}, + {0, 2200, 0, 0, mhfmon.Barioth, 100, 1}, + {0, 1800, 0, 0, mhfmon.Odibatorasu, 100, 1}, + {0, 1600, 0, 0, mhfmon.Doragyurosu, 100, 1}, + {0, 900, 0, 0, mhfmon.BlueYianKutKu, 100, 1}, + {0, 2300, 0, 0, mhfmon.ToaTesukatora, 100, 1}, + {0, 2000, 0, 0, mhfmon.Uragaan, 100, 1}, + {0, 1900, 0, 0, mhfmon.Teostra, 100, 1}, + {0, 1700, 0, 0, mhfmon.Chameleos, 100, 1}, + {0, 1800, 0, 0, mhfmon.KushalaDaora, 100, 1}, + {0, 2100, 0, 0, mhfmon.Nargacuga, 100, 1}, + {0, 2600, 0, 0, mhfmon.Guanzorumu, 100, 1}, + {0, 1900, 0, 0, mhfmon.Kirin, 100, 1}, + {0, 2000, 0, 0, mhfmon.Rukodiora, 100, 1}, + {0, 2700, 0, 0, mhfmon.StygianZinogre, 100, 1}, + {0, 2200, 0, 0, mhfmon.Voljang, 100, 1}, + {0, 1800, 0, 0, mhfmon.Zenaserisu, 100, 1}, + {0, 3100, 0, 0, mhfmon.GoreMagala, 100, 1}, + {0, 3200, 0, 0, mhfmon.ShagaruMagala, 100, 1}, + {0, 3500, 0, 0, mhfmon.Eruzerion, 100, 1}, + {0, 3200, 0, 0, mhfmon.Amatsu, 100, 1}, + } + + bf.WriteUint16(uint16(len(lbPrices))) + for _, lb := range lbPrices { + bf.WriteUint16(lb.Unk0) + bf.WriteUint16(lb.Unk1) + bf.WriteUint32(lb.Unk2) + } + bf.WriteUint16(uint16(len(wantedList))) + for _, wanted := range wantedList { + bf.WriteUint32(wanted.Unk0) + bf.WriteUint32(wanted.Unk1) + bf.WriteUint32(wanted.Unk2) + bf.WriteUint16(wanted.Unk3) + bf.WriteUint16(wanted.Unk4) + bf.WriteUint16(wanted.Unk5) + bf.WriteUint16(wanted.Unk6) + bf.WriteUint16(wanted.Unk7) + bf.WriteUint16(wanted.Unk8) + bf.WriteUint16(wanted.Unk9) + } + bf.WriteUint8(uint8(len(gzPrices))) + for _, gz := range gzPrices { + bf.WriteUint16(gz.Unk0) + bf.WriteUint16(gz.Gz) + bf.WriteUint16(gz.Unk1) + bf.WriteUint16(gz.Unk2) + bf.WriteUint16(gz.MonID) + bf.WriteUint16(gz.Unk3) + bf.WriteUint8(gz.Unk4) + } + doAckBufSucceed(s, pkt.AckHandle, bf.Data()) } func handleMsgMhfEnumerateOrder(s *Session, p mhfpacket.MHFPacket) { @@ -938,10 +1100,10 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) { column = "promo_points" } - var value int + var value int16 err := s.server.db.QueryRow(fmt.Sprintf(`SELECT %s FROM characters WHERE id = $1`, column), s.charID).Scan(&value) if err == nil { - if value-int(pkt.Delta) < 0 { + if value+pkt.Delta < 0 { s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = 0 WHERE id = $1`, column), s.charID) } else { s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = %s + $1 WHERE id = $2`, column, column), pkt.Delta, s.charID) @@ -981,7 +1143,10 @@ func handleMsgMhfStampcardStamp(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfStampcardPrize(s *Session, p mhfpacket.MHFPacket) {} -func handleMsgMhfUnreserveSrg(s *Session, p mhfpacket.MHFPacket) {} +func handleMsgMhfUnreserveSrg(s *Session, p mhfpacket.MHFPacket) { + pkt := p.(*mhfpacket.MsgMhfUnreserveSrg) + doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) +} func handleMsgMhfKickExportForce(s *Session, p mhfpacket.MHFPacket) {} diff --git a/server/channelserver/handlers_clients.go b/server/channelserver/handlers_clients.go index e1d2c4a49..4add1e9eb 100644 --- a/server/channelserver/handlers_clients.go +++ b/server/channelserver/handlers_clients.go @@ -29,6 +29,9 @@ func handleMsgSysEnumerateClient(s *Session, p mhfpacket.MHFPacket) { for _, cid := range stage.clients { clients = append(clients, cid) } + for cid := range stage.reservedClientSlots { + clients = append(clients, cid) + } case 1: // Not ready for cid, ready := range stage.reservedClientSlots { if !ready { diff --git a/server/channelserver/handlers_data.go b/server/channelserver/handlers_data.go index 5de1c4f65..805fa59f5 100644 --- a/server/channelserver/handlers_data.go +++ b/server/channelserver/handlers_data.go @@ -45,6 +45,9 @@ func handleMsgMhfSavedata(s *Session, p mhfpacket.MHFPacket) { doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return } + if s.server.erupeConfig.DevModeOptions.SaveDumps.RawEnabled { + dumpSaveData(s, saveData, "raw-savedata") + } s.logger.Info("Updating save with blob") characterSaveData.decompSave = saveData } diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 30a150eef..ad5221281 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -989,20 +989,21 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint32(guild.PugiOutfits) - if guild.Rank() >= 3 { - bf.WriteUint8(40) - } else if guild.Rank() >= 7 { - bf.WriteUint8(50) - } else if guild.Rank() >= 10 { - bf.WriteUint8(60) - } else { - bf.WriteUint8(30) + limit := s.server.erupeConfig.GameplayOptions.ClanMemberLimits[0][1] + for _, j := range s.server.erupeConfig.GameplayOptions.ClanMemberLimits { + if guild.Rank() >= uint16(j[0]) { + limit = j[1] + } } + if limit > 100 { + limit = 100 + } + bf.WriteUint8(limit) bf.WriteUint32(55000) bf.WriteUint32(0) bf.WriteUint16(0) // Changing Room RP - bf.WriteUint16(0) + bf.WriteUint16(0) // Ignored if guild.AllianceID > 0 { alliance, err := GetAllianceData(s, guild.AllianceID) @@ -1012,7 +1013,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(alliance.ID) bf.WriteUint32(uint32(alliance.CreatedAt.Unix())) bf.WriteUint16(alliance.TotalMembers) - bf.WriteUint8(0) + bf.WriteUint8(0) // Ignored bf.WriteUint8(0) ps.Uint16(bf, alliance.Name, true) if alliance.SubGuild1ID > 0 { @@ -1831,7 +1832,7 @@ func handleMsgMhfLoadGuildCooking(s *Session, p mhfpacket.MHFPacket) { func handleMsgMhfRegistGuildCooking(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfRegistGuildCooking) guild, _ := GetGuildInfoByCharacterId(s, s.charID) - startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.GuildMealDuration-3600) * time.Second) + startTime := TimeAdjusted().Add(time.Duration(s.server.erupeConfig.GameplayOptions.ClanMealDuration-3600) * time.Second) if pkt.OverwriteID != 0 { s.server.db.Exec("UPDATE guild_meals SET meal_id = $1, level = $2, created_at = $3 WHERE id = $4", pkt.MealID, pkt.Success, startTime, pkt.OverwriteID) } else { diff --git a/server/channelserver/handlers_stage.go b/server/channelserver/handlers_stage.go index abd9d3ba5..a9de55b28 100644 --- a/server/channelserver/handlers_stage.go +++ b/server/channelserver/handlers_stage.go @@ -157,6 +157,7 @@ func handleMsgSysEnterStage(s *Session, p mhfpacket.MHFPacket) { s.stage.reservedClientSlots[s.charID] = false s.stage.Unlock() s.stageMoveStack.Push(s.stage.id) + s.stageMoveStack.Lock() } if s.reservationStage != nil { @@ -170,6 +171,7 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysBackStage) // Transfer back to the saved stage ID before the previous move or enter. + s.stageMoveStack.Unlock() backStage, err := s.stageMoveStack.Pop() if backStage == "" || err != nil { backStage = "sl1Ns200p0a0u0" @@ -190,7 +192,9 @@ func handleMsgSysMoveStage(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgSysMoveStage) // Set a new move stack from the given stage ID - s.stageMoveStack.Set(pkt.StageID) + if !s.stageMoveStack.Locked { + s.stageMoveStack.Set(pkt.StageID) + } doStageTransfer(s, pkt.AckHandle, pkt.StageID) } @@ -386,7 +390,11 @@ func handleMsgSysEnumerateStage(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint16(uint16(len(stage.reservedClientSlots))) bf.WriteUint16(uint16(len(stage.clients))) - bf.WriteUint16(uint16(len(stage.clients))) + if strings.HasPrefix(stage.id, "sl2Ls") { + bf.WriteUint16(uint16(len(stage.clients) + len(stage.reservedClientSlots))) + } else { + bf.WriteUint16(uint16(len(stage.clients))) + } bf.WriteUint16(stage.maxPlayers) var flags uint8 if stage.locked { diff --git a/server/signv2server/dbutils.go b/server/signv2server/dbutils.go index dde729ac9..f5ea57846 100644 --- a/server/signv2server/dbutils.go +++ b/server/signv2server/dbutils.go @@ -32,13 +32,14 @@ func (s *Server) createNewUser(ctx context.Context, username string, password st return id, rights, err } -func (s *Server) createLoginToken(ctx context.Context, uid uint32) (string, error) { +func (s *Server) createLoginToken(ctx context.Context, uid uint32) (uint32, string, error) { loginToken := token.Generate(16) - _, err := s.db.ExecContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2)", uid, loginToken) + var tid uint32 + err := s.db.QueryRowContext(ctx, "INSERT INTO sign_sessions (user_id, token) VALUES ($1, $2) RETURNING id", uid, loginToken).Scan(&tid) if err != nil { - return "", err + return 0, "", err } - return loginToken, nil + return tid, loginToken, nil } func (s *Server) userIDFromToken(ctx context.Context, token string) (uint32, error) { diff --git a/server/signv2server/endpoints.go b/server/signv2server/endpoints.go index ae5959809..b6cc03515 100644 --- a/server/signv2server/endpoints.go +++ b/server/signv2server/endpoints.go @@ -27,8 +27,9 @@ type LauncherResponse struct { } type User struct { - Token string `json:"token"` - Rights uint32 `json:"rights"` + TokenID uint32 `json:"tokenId"` + Token string `json:"token"` + Rights uint32 `json:"rights"` } type Character struct { @@ -65,14 +66,15 @@ type ExportData struct { Character map[string]interface{} `json:"character"` } -func (s *Server) newAuthData(userID uint32, userRights uint32, userToken string, characters []Character) AuthData { +func (s *Server) newAuthData(userID uint32, userRights uint32, userTokenID 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, + Rights: userRights, + TokenID: userTokenID, + Token: userToken, }, Characters: characters, PatchServer: s.erupeConfig.SignV2.PatchServer, @@ -142,7 +144,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { return } - userToken, err := s.createLoginToken(ctx, userID) + userTokenID, userToken, err := s.createLoginToken(ctx, userID) if err != nil { s.logger.Warn("Error registering login token", zap.Error(err)) w.WriteHeader(500) @@ -157,7 +159,7 @@ func (s *Server) Login(w http.ResponseWriter, r *http.Request) { if characters == nil { characters = []Character{} } - respData := s.newAuthData(userID, userRights, userToken, characters) + respData := s.newAuthData(userID, userRights, userTokenID, userToken, characters) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) } @@ -191,13 +193,13 @@ func (s *Server) Register(w http.ResponseWriter, r *http.Request) { return } - userToken, err := s.createLoginToken(ctx, userID) + userTokenID, 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{}) + respData := s.newAuthData(userID, userRights, userTokenID, userToken, []Character{}) w.Header().Add("Content-Type", "application/json") json.NewEncoder(w).Encode(respData) }