diff --git a/server/channelserver/constants_quest.go b/server/channelserver/constants_quest.go index 0e0e8c5f0..4fe81b1d7 100644 --- a/server/channelserver/constants_quest.go +++ b/server/channelserver/constants_quest.go @@ -12,8 +12,8 @@ const ( // Event quest binary frame offsets const ( - questFrameTimeFlagOffset = 25 - questFrameVariant3Offset = 175 + questFrameTimeFlagOffset = 25 + questFrameVariant3Offset = 175 ) // Quest body lengths per game version diff --git a/server/channelserver/handlers_guild_info.go b/server/channelserver/handlers_guild_info.go index dea46ab29..4dafab021 100644 --- a/server/channelserver/handlers_guild_info.go +++ b/server/channelserver/handlers_guild_info.go @@ -12,6 +12,12 @@ import ( "github.com/jmoiron/sqlx" ) +// Guild sentinel and cost constants +const ( + guildNotJoinedSentinel = uint32(0xFFFFFFFF) + guildRoomMaxRP = uint32(55000) +) + func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { pkt := p.(*mhfpacket.MsgMhfInfoGuild) @@ -32,7 +38,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { guildLeaderName := stringsupport.UTF8ToSJIS(guild.LeaderName) characterGuildData, err := GetCharacterGuildData(s, s.charID) - characterJoinedAt := uint32(0xFFFFFFFF) + characterJoinedAt := guildNotJoinedSentinel if characterGuildData != nil && characterGuildData.JoinedAt != nil { characterJoinedAt = uint32(characterGuildData.JoinedAt.Unix()) @@ -123,7 +129,7 @@ func handleMsgMhfInfoGuild(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint8(limit) - bf.WriteUint32(55000) + bf.WriteUint32(guildRoomMaxRP) bf.WriteUint32(uint32(guild.RoomExpiry.Unix())) bf.WriteUint16(guild.RoomRP) bf.WriteUint16(0) // Ignored diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index f4b81608d..f52b14868 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -223,24 +223,24 @@ func loadQuestFile(s *Session, questId int) []byte { fileBytes.SetLE() _, _ = fileBytes.Seek(int64(fileBytes.ReadUint32()), 0) - bodyLength := 320 + bodyLength := questBodyLenZZ if s.server.erupeConfig.RealClientMode <= _config.S6 { - bodyLength = 160 + bodyLength = questBodyLenS6 } else if s.server.erupeConfig.RealClientMode <= _config.F5 { - bodyLength = 168 + bodyLength = questBodyLenF5 } else if s.server.erupeConfig.RealClientMode <= _config.G101 { - bodyLength = 192 + bodyLength = questBodyLenG101 } else if s.server.erupeConfig.RealClientMode <= _config.Z1 { - bodyLength = 224 + bodyLength = questBodyLenZ1 } // The n bytes directly following the data pointer must go directly into the event's body, after the header and before the string pointers. questBody := byteframe.NewByteFrameFromBytes(fileBytes.ReadBytes(uint(bodyLength))) questBody.SetLE() // Find the master quest string pointer - _, _ = questBody.Seek(40, 0) + _, _ = questBody.Seek(questStringPointerOff, 0) _, _ = fileBytes.Seek(int64(questBody.ReadUint32()), 0) - _, _ = questBody.Seek(40, 0) + _, _ = questBody.Seek(questStringPointerOff, 0) // Overwrite it questBody.WriteUint32(uint32(bodyLength)) _, _ = questBody.Seek(0, 2) @@ -248,8 +248,8 @@ func loadQuestFile(s *Session, questId int) []byte { // Rewrite the quest strings and their pointers var tempString []byte newStrings := byteframe.NewByteFrame() - tempPointer := bodyLength + 32 - for i := 0; i < 8; i++ { + tempPointer := bodyLength + questStringTablePadding + for i := 0; i < questStringCount; i++ { questBody.WriteUint32(uint32(tempPointer)) temp := int64(fileBytes.Index()) _, _ = fileBytes.Seek(int64(fileBytes.ReadUint32()), 0) @@ -284,21 +284,21 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { bf.WriteUint32(0) // Unk bf.WriteUint8(0) // Unk switch questType { - case 16: + case QuestTypeRegularRaviente: bf.WriteUint8(s.server.erupeConfig.GameplayOptions.RegularRavienteMaxPlayers) - case 22: + case QuestTypeViolentRaviente: bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ViolentRavienteMaxPlayers) - case 40: + case QuestTypeBerserkRaviente: bf.WriteUint8(s.server.erupeConfig.GameplayOptions.BerserkRavienteMaxPlayers) - case 50: + case QuestTypeExtremeRaviente: bf.WriteUint8(s.server.erupeConfig.GameplayOptions.ExtremeRavienteMaxPlayers) - case 51: + case QuestTypeSmallBerserkRavi: bf.WriteUint8(s.server.erupeConfig.GameplayOptions.SmallBerserkRavienteMaxPlayers) default: bf.WriteUint8(maxPlayers) } bf.WriteUint8(questType) - if questType == 9 { + if questType == QuestTypeSpecialTool { bf.WriteBool(false) } else { bf.WriteBool(true) @@ -314,9 +314,9 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { // 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) + _, _ = bf.Seek(questFrameTimeFlagOffset, 0) flagByte := bf.ReadUint8() - _, _ = bf.Seek(25, 0) + _, _ = bf.Seek(questFrameTimeFlagOffset, 0) if s.server.erupeConfig.GameplayOptions.SeasonOverride { bf.WriteUint8(flagByte & 0b11100000) } else { @@ -332,10 +332,10 @@ func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { // 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) + _, _ = bf.Seek(questFrameVariant3Offset, 0) questVariant3 := bf.ReadUint8() questVariant3 &= 0b11011111 // disable Interception flag - _, _ = bf.Seek(175, 0) + _, _ = bf.Seek(questFrameVariant3Offset, 0) bf.WriteUint8(questVariant3) _, _ = bf.Seek(0, 2) @@ -400,7 +400,7 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { s.logger.Error("Failed to make event quest", zap.Error(err)) continue } else { - if len(data) > 896 || len(data) < 352 { + if len(data) > questDataMaxLen || len(data) < questDataMinLen { s.logger.Error("Invalid quest data length", zap.Int("len", len(data))) continue } else { @@ -601,25 +601,25 @@ func handleMsgMhfEnumerateQuest(s *Session, p mhfpacket.MHFPacket) { } tuneValues = temp - tuneLimit := 770 + tuneLimit := tuneLimitZZ if s.server.erupeConfig.RealClientMode <= _config.G1 { - tuneLimit = 256 + tuneLimit = tuneLimitG1 } else if s.server.erupeConfig.RealClientMode <= _config.G3 { - tuneLimit = 283 + tuneLimit = tuneLimitG3 } else if s.server.erupeConfig.RealClientMode <= _config.GG { - tuneLimit = 315 + tuneLimit = tuneLimitGG } else if s.server.erupeConfig.RealClientMode <= _config.G61 { - tuneLimit = 332 + tuneLimit = tuneLimitG61 } else if s.server.erupeConfig.RealClientMode <= _config.G7 { - tuneLimit = 339 + tuneLimit = tuneLimitG7 } else if s.server.erupeConfig.RealClientMode <= _config.G81 { - tuneLimit = 396 + tuneLimit = tuneLimitG81 } else if s.server.erupeConfig.RealClientMode <= _config.G91 { - tuneLimit = 694 + tuneLimit = tuneLimitG91 } else if s.server.erupeConfig.RealClientMode <= _config.G101 { - tuneLimit = 704 + tuneLimit = tuneLimitG101 } else if s.server.erupeConfig.RealClientMode <= _config.Z2 { - tuneLimit = 750 + tuneLimit = tuneLimitZ2 } if len(tuneValues) > tuneLimit { tuneValues = tuneValues[:tuneLimit] diff --git a/server/channelserver/handlers_rengoku.go b/server/channelserver/handlers_rengoku.go index 207589de0..d34ff71eb 100644 --- a/server/channelserver/handlers_rengoku.go +++ b/server/channelserver/handlers_rengoku.go @@ -14,18 +14,31 @@ import ( "go.uber.org/zap" ) +// Rengoku save blob layout offsets +const ( + rengokuSkillSlotsStart = 0x1B + rengokuSkillSlotsEnd = 0x21 + rengokuSkillValuesStart = 0x2E + rengokuSkillValuesEnd = 0x3A + rengokuPointsStart = 0x3B + rengokuPointsEnd = 0x47 + rengokuMaxStageMpOffset = 71 + rengokuMinPayloadSize = 91 + rengokuMaxPayloadSize = 4096 +) + // rengokuSkillsZeroed checks if the skill slot IDs (offsets 0x1B-0x20) and // equipped skill values (offsets 0x2E-0x39) are all zero in a rengoku save blob. func rengokuSkillsZeroed(data []byte) bool { - if len(data) < 0x3A { + if len(data) < rengokuSkillValuesEnd { return true } - for _, b := range data[0x1B:0x21] { + for _, b := range data[rengokuSkillSlotsStart:rengokuSkillSlotsEnd] { if b != 0 { return false } } - for _, b := range data[0x2E:0x3A] { + for _, b := range data[rengokuSkillValuesStart:rengokuSkillValuesEnd] { if b != 0 { return false } @@ -35,10 +48,10 @@ func rengokuSkillsZeroed(data []byte) bool { // rengokuHasPoints checks if any skill point allocation (offsets 0x3B-0x46) is nonzero. func rengokuHasPoints(data []byte) bool { - if len(data) < 0x47 { + if len(data) < rengokuPointsEnd { return false } - for _, b := range data[0x3B:0x47] { + for _, b := range data[rengokuPointsStart:rengokuPointsEnd] { if b != 0 { return true } @@ -51,15 +64,15 @@ func rengokuHasPoints(data []byte) bool { // preserving the skills that the client failed to populate due to a race // condition during area transitions (see issue #85). func rengokuMergeSkills(dst, src []byte) { - copy(dst[0x1B:0x21], src[0x1B:0x21]) - copy(dst[0x2E:0x3A], src[0x2E:0x3A]) + copy(dst[rengokuSkillSlotsStart:rengokuSkillSlotsEnd], src[rengokuSkillSlotsStart:rengokuSkillSlotsEnd]) + copy(dst[rengokuSkillValuesStart:rengokuSkillValuesEnd], src[rengokuSkillValuesStart:rengokuSkillValuesEnd]) } func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { // Saved every floor on road, holds values such as floors progressed, points etc. // Can be safely handled by the client. pkt := p.(*mhfpacket.MsgMhfSaveRengokuData) - if len(pkt.RawDataPayload) < 91 || len(pkt.RawDataPayload) > 4096 { + if len(pkt.RawDataPayload) < rengokuMinPayloadSize || len(pkt.RawDataPayload) > rengokuMaxPayloadSize { s.logger.Warn("Rengoku payload size out of range", zap.Int("len", len(pkt.RawDataPayload))) doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4)) return @@ -72,10 +85,10 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { // path triggers a rengoku save BEFORE the load response has been parsed into // the character data area. This produces a save with zeroed skill fields but // preserved point totals. Detect this pattern and merge existing skill data. - if len(saveData) >= 0x47 && rengokuSkillsZeroed(saveData) && rengokuHasPoints(saveData) { + if len(saveData) >= rengokuPointsEnd && rengokuSkillsZeroed(saveData) && rengokuHasPoints(saveData) { var existing []byte if err := s.server.db.QueryRow("SELECT rengokudata FROM characters WHERE id=$1", s.charID).Scan(&existing); err == nil { - if len(existing) >= 0x47 && !rengokuSkillsZeroed(existing) { + if len(existing) >= rengokuPointsEnd && !rengokuSkillsZeroed(existing) { s.logger.Info("Rengoku save has zeroed skills with invested points, preserving existing skills", zap.Uint32("charID", s.charID)) merged := make([]byte, len(saveData)) @@ -106,7 +119,7 @@ func handleMsgMhfSaveRengokuData(s *Session, p mhfpacket.MHFPacket) { return } bf := byteframe.NewByteFrameFromBytes(saveData) - _, _ = bf.Seek(71, 0) + _, _ = bf.Seek(rengokuMaxStageMpOffset, 0) maxStageMp := bf.ReadUint32() maxScoreMp := bf.ReadUint32() _, _ = bf.Seek(4, 1) diff --git a/server/channelserver/handlers_session.go b/server/channelserver/handlers_session.go index 8353e70b3..4a2571800 100644 --- a/server/channelserver/handlers_session.go +++ b/server/channelserver/handlers_session.go @@ -500,12 +500,12 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { case 1, 2, 3: // usersearchidx, usersearchname, lobbysearchname // Snapshot matching sessions under lock, then build response outside locks. type sessionResult struct { - charID uint32 - name []byte - stageID []byte - ip net.IP - port uint16 - userBin3 []byte + charID uint32 + name []byte + stageID []byte + ip net.IP + port uint16 + userBin3 []byte } var results []sessionResult @@ -656,15 +656,15 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) { } // Snapshot matching stages under lock, then build response outside locks. type stageResult struct { - ip net.IP - port uint16 - clientCount int - reserved int - maxPlayers uint16 - stageID string - stageData []int16 - rawBinData0 []byte - rawBinData1 []byte + ip net.IP + port uint16 + clientCount int + reserved int + maxPlayers uint16 + stageID string + stageData []int16 + rawBinData0 []byte + rawBinData1 []byte } var stageResults []stageResult diff --git a/server/channelserver/model_character.go b/server/channelserver/model_character.go index f84d02875..f732b1042 100644 --- a/server/channelserver/model_character.go +++ b/server/channelserver/model_character.go @@ -13,20 +13,20 @@ import ( type SavePointer int const ( - pGender = iota // +1 - pRP // +2 - pHouseTier // +5 - pHouseData // +195 - pBookshelfData // +lBookshelfData - pGalleryData // +1748 - pToreData // +240 - pGardenData // +68 - pPlaytime // +4 - pWeaponType // +1 - pWeaponID // +2 - pHR // +2 - pGRP // +4 - pKQF // +8 + pGender = iota + pRP + pHouseTier + pHouseData + pBookshelfData + pGalleryData + pToreData + pGardenData + pPlaytime + pWeaponType + pWeaponID + pHR + pGRP + pKQF lBookshelfData ) @@ -146,16 +146,33 @@ func (save *CharacterSaveData) updateSaveDataWithStruct() { rpBytes := make([]byte, 2) binary.LittleEndian.PutUint16(rpBytes, save.RP) if save.Mode >= _config.F4 { - copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+2], rpBytes) + copy(save.decompSave[save.Pointers[pRP]:save.Pointers[pRP]+saveFieldRP], rpBytes) } if save.Mode >= _config.G10 { - copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+8], save.KQF) + copy(save.decompSave[save.Pointers[pKQF]:save.Pointers[pKQF]+saveFieldKQF], save.KQF) } } // This will update the save struct with the values stored in the character save +// Save data field sizes +const ( + saveFieldRP = 2 + saveFieldHouseTier = 5 + saveFieldHouseData = 195 + saveFieldGallery = 1748 + saveFieldTore = 240 + saveFieldGarden = 68 + saveFieldPlaytime = 4 + saveFieldWeaponID = 2 + saveFieldHR = 2 + saveFieldGRP = 4 + saveFieldKQF = 8 + saveFieldNameOffset = 88 + saveFieldNameLen = 12 +) + func (save *CharacterSaveData) updateStructWithSaveData() { - save.Name, _ = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[88:100])) + save.Name, _ = stringsupport.SJISToUTF8(bfutil.UpToNull(save.decompSave[saveFieldNameOffset : saveFieldNameOffset+saveFieldNameLen])) if save.decompSave[save.Pointers[pGender]] == 1 { save.Gender = true } else { @@ -163,24 +180,24 @@ func (save *CharacterSaveData) updateStructWithSaveData() { } if !save.IsNewCharacter { if save.Mode >= _config.S6 { - 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.RP = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pRP] : save.Pointers[pRP]+saveFieldRP]) + save.HouseTier = save.decompSave[save.Pointers[pHouseTier] : save.Pointers[pHouseTier]+saveFieldHouseTier] + save.HouseData = save.decompSave[save.Pointers[pHouseData] : save.Pointers[pHouseData]+saveFieldHouseData] 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.Playtime = binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pPlaytime] : save.Pointers[pPlaytime]+4]) + save.GalleryData = save.decompSave[save.Pointers[pGalleryData] : save.Pointers[pGalleryData]+saveFieldGallery] + save.ToreData = save.decompSave[save.Pointers[pToreData] : save.Pointers[pToreData]+saveFieldTore] + save.GardenData = save.decompSave[save.Pointers[pGardenData] : save.Pointers[pGardenData]+saveFieldGarden] + save.Playtime = binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pPlaytime] : save.Pointers[pPlaytime]+saveFieldPlaytime]) save.WeaponType = save.decompSave[save.Pointers[pWeaponType]] - save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+2]) - save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+2]) + save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pWeaponID] : save.Pointers[pWeaponID]+saveFieldWeaponID]) + save.HR = binary.LittleEndian.Uint16(save.decompSave[save.Pointers[pHR] : save.Pointers[pHR]+saveFieldHR]) if save.Mode >= _config.G1 { if save.HR == uint16(999) { - save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+4]))) + save.GR = grpToGR(int(binary.LittleEndian.Uint32(save.decompSave[save.Pointers[pGRP] : save.Pointers[pGRP]+saveFieldGRP]))) } } if save.Mode >= _config.G10 { - save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+8] + save.KQF = save.decompSave[save.Pointers[pKQF] : save.Pointers[pKQF]+saveFieldKQF] } } }