mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-22 07:32:32 +01:00
Add SJISToUTF8Lossy() that wraps SJISToUTF8() and logs decode errors at slog.Debug level. Replace all 31 call sites across 17 files that previously discarded the error with `_, _ =`. This makes garbled text from malformed SJIS client data debuggable without adding noise at default log levels.
325 lines
11 KiB
Go
325 lines
11 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
"erupe-ce/common/byteframe"
|
|
"erupe-ce/common/stringsupport"
|
|
"erupe-ce/network/mhfpacket"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfOperateGuild)
|
|
|
|
guild, err := s.server.guildRepo.GetByID(pkt.GuildID)
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
characterGuildInfo, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
bf := byteframe.NewByteFrame()
|
|
|
|
switch pkt.Action {
|
|
case mhfpacket.OperateGuildDisband:
|
|
response := 1
|
|
if guild.LeaderCharID != s.charID {
|
|
s.logger.Warn("Unauthorized guild management attempt", zap.Uint32("charID", s.charID), zap.Uint32("guildID", guild.ID))
|
|
response = 0
|
|
} else {
|
|
err = s.server.guildRepo.Disband(guild.ID)
|
|
if err != nil {
|
|
response = 0
|
|
}
|
|
}
|
|
bf.WriteUint32(uint32(response))
|
|
case mhfpacket.OperateGuildResign:
|
|
guildMembers, err := s.server.guildRepo.GetMembers(guild.ID, false)
|
|
if err == nil {
|
|
sort.Slice(guildMembers[:], func(i, j int) bool {
|
|
return guildMembers[i].OrderIndex < guildMembers[j].OrderIndex
|
|
})
|
|
for i := 1; i < len(guildMembers); i++ {
|
|
if !guildMembers[i].AvoidLeadership {
|
|
guild.LeaderCharID = guildMembers[i].CharID
|
|
guildMembers[0].OrderIndex = guildMembers[i].OrderIndex
|
|
guildMembers[i].OrderIndex = 1
|
|
if err := s.server.guildRepo.SaveMember(guildMembers[0]); err != nil {
|
|
s.logger.Error("Failed to save former leader member data", zap.Error(err))
|
|
}
|
|
if err := s.server.guildRepo.SaveMember(guildMembers[i]); err != nil {
|
|
s.logger.Error("Failed to save new leader member data", zap.Error(err))
|
|
}
|
|
bf.WriteUint32(guildMembers[i].CharID)
|
|
break
|
|
}
|
|
}
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild after leadership resign", zap.Error(err))
|
|
}
|
|
}
|
|
case mhfpacket.OperateGuildApply:
|
|
err = s.server.guildRepo.CreateApplication(guild.ID, s.charID, s.charID, GuildApplicationTypeApplied)
|
|
if err == nil {
|
|
bf.WriteUint32(guild.LeaderCharID)
|
|
} else {
|
|
bf.WriteUint32(0)
|
|
}
|
|
case mhfpacket.OperateGuildLeave:
|
|
if characterGuildInfo.IsApplicant {
|
|
err = s.server.guildRepo.RejectApplication(guild.ID, s.charID)
|
|
} else {
|
|
err = s.server.guildRepo.RemoveCharacter(s.charID)
|
|
}
|
|
response := 1
|
|
if err != nil {
|
|
response = 0
|
|
} else {
|
|
if err := s.server.mailRepo.SendMail(0, s.charID, "Withdrawal",
|
|
fmt.Sprintf("You have withdrawn from 「%s」.", guild.Name),
|
|
0, 0, false, true); err != nil {
|
|
s.logger.Warn("Failed to send guild withdrawal notification", zap.Error(err))
|
|
}
|
|
}
|
|
bf.WriteUint32(uint32(response))
|
|
case mhfpacket.OperateGuildDonateRank:
|
|
bf.WriteBytes(handleDonateRP(s, uint16(pkt.Data1.ReadUint32()), guild, 0))
|
|
case mhfpacket.OperateGuildSetApplicationDeny:
|
|
if err := s.server.guildRepo.SetRecruiting(guild.ID, false); err != nil {
|
|
s.logger.Error("Failed to deny guild applications", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildSetApplicationAllow:
|
|
if err := s.server.guildRepo.SetRecruiting(guild.ID, true); err != nil {
|
|
s.logger.Error("Failed to allow guild applications", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildSetAvoidLeadershipTrue:
|
|
handleAvoidLeadershipUpdate(s, pkt, true)
|
|
case mhfpacket.OperateGuildSetAvoidLeadershipFalse:
|
|
handleAvoidLeadershipUpdate(s, pkt, false)
|
|
case mhfpacket.OperateGuildUpdateComment:
|
|
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
guild.Comment = stringsupport.SJISToUTF8Lossy(pkt.Data2.ReadNullTerminatedBytes())
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild comment", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildUpdateMotto:
|
|
if !characterGuildInfo.IsLeader && !characterGuildInfo.IsSubLeader() {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
_ = pkt.Data1.ReadUint16()
|
|
guild.SubMotto = pkt.Data1.ReadUint8()
|
|
guild.MainMotto = pkt.Data1.ReadUint8()
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild motto", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildRenamePugi1:
|
|
handleRenamePugi(s, pkt.Data2, guild, 1)
|
|
case mhfpacket.OperateGuildRenamePugi2:
|
|
handleRenamePugi(s, pkt.Data2, guild, 2)
|
|
case mhfpacket.OperateGuildRenamePugi3:
|
|
handleRenamePugi(s, pkt.Data2, guild, 3)
|
|
case mhfpacket.OperateGuildChangePugi1:
|
|
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 1)
|
|
case mhfpacket.OperateGuildChangePugi2:
|
|
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 2)
|
|
case mhfpacket.OperateGuildChangePugi3:
|
|
handleChangePugi(s, uint8(pkt.Data1.ReadUint32()), guild, 3)
|
|
case mhfpacket.OperateGuildUnlockOutfit:
|
|
if err := s.server.guildRepo.SetPugiOutfits(guild.ID, pkt.Data1.ReadUint32()); err != nil {
|
|
s.logger.Error("Failed to unlock guild pugi outfit", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildDonateRoom:
|
|
quantity := uint16(pkt.Data1.ReadUint32())
|
|
bf.WriteBytes(handleDonateRP(s, quantity, guild, 2))
|
|
case mhfpacket.OperateGuildDonateEvent:
|
|
quantity := uint16(pkt.Data1.ReadUint32())
|
|
bf.WriteBytes(handleDonateRP(s, quantity, guild, 1))
|
|
// TODO: Move this value onto rp_yesterday and reset to 0... daily?
|
|
if err := s.server.guildRepo.AddMemberDailyRP(s.charID, quantity); err != nil {
|
|
s.logger.Error("Failed to update guild character daily RP", zap.Error(err))
|
|
}
|
|
case mhfpacket.OperateGuildEventExchange:
|
|
rp := uint16(pkt.Data1.ReadUint32())
|
|
balance, err := s.server.guildRepo.ExchangeEventRP(guild.ID, rp)
|
|
if err != nil {
|
|
s.logger.Error("Failed to exchange guild event RP", zap.Error(err))
|
|
}
|
|
bf.WriteUint32(balance)
|
|
default:
|
|
s.logger.Error("unhandled operate guild action", zap.Uint8("action", uint8(pkt.Action)))
|
|
}
|
|
|
|
if len(bf.Data()) > 0 {
|
|
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
|
} else {
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
}
|
|
|
|
func handleRenamePugi(s *Session, bf *byteframe.ByteFrame, guild *Guild, num int) {
|
|
name := stringsupport.SJISToUTF8Lossy(bf.ReadNullTerminatedBytes())
|
|
switch num {
|
|
case 1:
|
|
guild.PugiName1 = name
|
|
case 2:
|
|
guild.PugiName2 = name
|
|
default:
|
|
guild.PugiName3 = name
|
|
}
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild pugi name", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func handleChangePugi(s *Session, outfit uint8, guild *Guild, num int) {
|
|
switch num {
|
|
case 1:
|
|
guild.PugiOutfit1 = outfit
|
|
case 2:
|
|
guild.PugiOutfit2 = outfit
|
|
case 3:
|
|
guild.PugiOutfit3 = outfit
|
|
}
|
|
if err := s.server.guildRepo.Save(guild); err != nil {
|
|
s.logger.Error("Failed to save guild pugi outfit", zap.Error(err))
|
|
}
|
|
}
|
|
|
|
func handleDonateRP(s *Session, amount uint16, guild *Guild, _type int) []byte {
|
|
bf := byteframe.NewByteFrame()
|
|
bf.WriteUint32(0)
|
|
saveData, err := GetCharacterSaveData(s, s.charID)
|
|
if err != nil {
|
|
return bf.Data()
|
|
}
|
|
var resetRoom bool
|
|
if _type == 2 {
|
|
currentRP, err := s.server.guildRepo.GetRoomRP(guild.ID)
|
|
if err != nil {
|
|
s.logger.Error("Failed to get guild room RP", zap.Error(err))
|
|
}
|
|
if currentRP+amount >= 30 {
|
|
amount = 30 - currentRP
|
|
resetRoom = true
|
|
}
|
|
}
|
|
saveData.RP -= amount
|
|
saveData.Save(s)
|
|
switch _type {
|
|
case 0:
|
|
if err := s.server.guildRepo.AddRankRP(guild.ID, amount); err != nil {
|
|
s.logger.Error("Failed to update guild rank RP", zap.Error(err))
|
|
}
|
|
case 1:
|
|
if err := s.server.guildRepo.AddEventRP(guild.ID, amount); err != nil {
|
|
s.logger.Error("Failed to update guild event RP", zap.Error(err))
|
|
}
|
|
case 2:
|
|
if resetRoom {
|
|
if err := s.server.guildRepo.SetRoomRP(guild.ID, 0); err != nil {
|
|
s.logger.Error("Failed to reset guild room RP", zap.Error(err))
|
|
}
|
|
if err := s.server.guildRepo.SetRoomExpiry(guild.ID, TimeAdjusted().Add(time.Hour*24*7)); err != nil {
|
|
s.logger.Error("Failed to update guild room expiry", zap.Error(err))
|
|
}
|
|
} else {
|
|
if err := s.server.guildRepo.AddRoomRP(guild.ID, amount); err != nil {
|
|
s.logger.Error("Failed to update guild room RP", zap.Error(err))
|
|
}
|
|
}
|
|
}
|
|
_, _ = bf.Seek(0, 0)
|
|
bf.WriteUint32(uint32(saveData.RP))
|
|
return bf.Data()
|
|
}
|
|
|
|
func handleAvoidLeadershipUpdate(s *Session, pkt *mhfpacket.MsgMhfOperateGuild, avoidLeadership bool) {
|
|
characterGuildData, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
|
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
characterGuildData.AvoidLeadership = avoidLeadership
|
|
|
|
err = s.server.guildRepo.SaveMember(characterGuildData)
|
|
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
|
|
func handleMsgMhfOperateGuildMember(s *Session, p mhfpacket.MHFPacket) {
|
|
pkt := p.(*mhfpacket.MsgMhfOperateGuildMember)
|
|
|
|
guild, err := s.server.guildRepo.GetByCharID(pkt.CharID)
|
|
|
|
if err != nil || guild == nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
actorCharacter, err := s.server.guildRepo.GetCharacterMembership(s.charID)
|
|
|
|
if err != nil || (!actorCharacter.IsSubLeader() && guild.LeaderCharID != s.charID) {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
return
|
|
}
|
|
|
|
var mail Mail
|
|
switch pkt.Action {
|
|
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_ACCEPT:
|
|
err = s.server.guildRepo.AcceptApplication(guild.ID, pkt.CharID)
|
|
mail = Mail{
|
|
RecipientID: pkt.CharID,
|
|
Subject: "Accepted!",
|
|
Body: fmt.Sprintf("Your application to join 「%s」 was accepted.", guild.Name),
|
|
IsSystemMessage: true,
|
|
}
|
|
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_REJECT:
|
|
err = s.server.guildRepo.RejectApplication(guild.ID, pkt.CharID)
|
|
mail = Mail{
|
|
RecipientID: pkt.CharID,
|
|
Subject: "Rejected",
|
|
Body: fmt.Sprintf("Your application to join 「%s」 was rejected.", guild.Name),
|
|
IsSystemMessage: true,
|
|
}
|
|
case mhfpacket.OPERATE_GUILD_MEMBER_ACTION_KICK:
|
|
err = s.server.guildRepo.RemoveCharacter(pkt.CharID)
|
|
mail = Mail{
|
|
RecipientID: pkt.CharID,
|
|
Subject: "Kicked",
|
|
Body: fmt.Sprintf("You were kicked from 「%s」.", guild.Name),
|
|
IsSystemMessage: true,
|
|
}
|
|
default:
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
s.logger.Warn("Unhandled operateGuildMember action", zap.Uint8("action", pkt.Action))
|
|
}
|
|
|
|
if err != nil {
|
|
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
|
|
} else {
|
|
if err := s.server.mailRepo.SendMail(mail.SenderID, mail.RecipientID, mail.Subject, mail.Body, 0, 0, false, true); err != nil {
|
|
s.logger.Warn("Failed to send guild member operation mail", zap.Error(err))
|
|
}
|
|
s.server.Registry.NotifyMailToCharID(pkt.CharID, s, &mail)
|
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
|
}
|
|
}
|