mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-03-26 09:33:02 +01:00
refactor(channelserver): add numeric column helpers and extract protocol constants
Add readCharacterInt/adjustCharacterInt helpers for single-column integer operations on the characters table. Eliminates fmt.Sprintf SQL construction in handlers_misc.go and replaces inline queries across cafe, kouryou, and mercenary handlers. Second round of protocol constant extraction: adds constants_time.go (secsPerDay, secsPerWeek), constants_raviente.go (register IDs, semaphore constants), and named constants across 14 handler files replacing raw hex/numeric literals. Updates anti-patterns doc to mark #4 (magic numbers) as substantially fixed.
This commit is contained in:
@@ -150,32 +150,16 @@ There is no repository layer, no service layer — just handlers.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. Magic Numbers Everywhere
|
## 4. ~~Magic Numbers Everywhere~~ (Substantially Fixed)
|
||||||
|
|
||||||
Binary protocol code is full of unexplained numeric literals with no named constants or comments:
|
**Status:** Two rounds of extraction have replaced the highest-impact magic numbers with named constants:
|
||||||
|
|
||||||
```go
|
- **Round 1** (commit `7c444b0`): `constants_quest.go`, `handlers_guild_info.go`, `handlers_quest.go`, `handlers_rengoku.go`, `handlers_session.go`, `model_character.go`
|
||||||
// handlers_cast_binary.go
|
- **Round 2**: `constants_time.go` (shared `secsPerDay`, `secsPerWeek`), `constants_raviente.go` (register IDs, semaphore constants), plus constants in `handlers_register.go`, `handlers_semaphore.go`, `handlers_session.go`, `handlers_festa.go`, `handlers_diva.go`, `handlers_event.go`, `handlers_mercenary.go`, `handlers_misc.go`, `handlers_plate.go`, `handlers_cast_binary.go`, `handlers_commands.go`, `handlers_reward.go`, `handlers_guild_mission.go`, `sys_channel_server.go`
|
||||||
bf.WriteUint8(0x02)
|
|
||||||
bf.WriteUint16(0x00)
|
|
||||||
bf.Seek(4, io.SeekStart)
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
**Remaining:** Unknown protocol fields (e.g., `handlers_diva.go:112-115` `0x19, 0x2D, 0x02, 0x02`) are intentionally left as literals until their meaning is understood. Data tables (monster point tables, item IDs) are data, not protocol constants. Standard empty ACK payloads (`make([]byte, 4)`) are idiomatic Go.
|
||||||
// handlers_data.go
|
|
||||||
if dataLen > 0x20000 { ... }
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
**Impact:** ~~New contributors can't understand what these values mean.~~ Most protocol-meaningful constants now have names and comments.
|
||||||
// Various handlers
|
|
||||||
bf.WriteUint32(0x0A218EAD) // What is this?
|
|
||||||
```
|
|
||||||
|
|
||||||
Packet field offsets, sizes, flags, and game constants appear as raw numbers throughout.
|
|
||||||
|
|
||||||
**Impact:** New contributors can't understand what these values mean. Protocol documentation exists only in the developer's memory. Bugs from using the wrong constant are hard to catch.
|
|
||||||
|
|
||||||
**Recommendation:** Define named constants in relevant packages (e.g., `const MaxDataChunkSize = 0x20000`, `const CastBinaryTypePosition = 0x02`).
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -318,7 +302,7 @@ Database operations use raw `database/sql` with PostgreSQL-specific syntax throu
|
|||||||
| Severity | Anti-patterns |
|
| Severity | Anti-patterns |
|
||||||
|----------|--------------|
|
|----------|--------------|
|
||||||
| **High** | ~~Missing ACK responses / softlocks (#2)~~ **Fixed**, no architectural layering (#3), tight DB coupling (#13) |
|
| **High** | ~~Missing ACK responses / softlocks (#2)~~ **Fixed**, no architectural layering (#3), tight DB coupling (#13) |
|
||||||
| **Medium** | Magic numbers (#4), ~~inconsistent binary I/O (#5)~~ **Resolved**, Session god object (#6), ~~copy-paste handlers (#8)~~ **Fixed**, raw SQL duplication (#9) |
|
| **Medium** | ~~Magic numbers (#4)~~ **Fixed**, ~~inconsistent binary I/O (#5)~~ **Resolved**, Session god object (#6), ~~copy-paste handlers (#8)~~ **Fixed**, raw SQL duplication (#9) |
|
||||||
| **Low** | God files (#1), ~~`init()` registration (#10)~~ **Fixed**, ~~inconsistent logging (#12)~~ **Fixed**, mutex granularity (#7), ~~panic-based flow (#11)~~ **Fixed** |
|
| **Low** | God files (#1), ~~`init()` registration (#10)~~ **Fixed**, ~~inconsistent logging (#12)~~ **Fixed**, mutex granularity (#7), ~~panic-based flow (#11)~~ **Fixed** |
|
||||||
|
|
||||||
### Root Cause
|
### Root Cause
|
||||||
|
|||||||
14
server/channelserver/constants_raviente.go
Normal file
14
server/channelserver/constants_raviente.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
// Raviente register type IDs (used in MsgSysLoadRegister / MsgSysNotifyRegister)
|
||||||
|
const (
|
||||||
|
raviRegisterState = uint32(0x40000)
|
||||||
|
raviRegisterSupport = uint32(0x50000)
|
||||||
|
raviRegisterGeneral = uint32(0x60000)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Raviente semaphore constants
|
||||||
|
const (
|
||||||
|
raviSemaphoreStride = 0x10000 // ID spacing between hs_l0* semaphores
|
||||||
|
raviSemaphoreMax = uint16(127) // max players per Raviente semaphore
|
||||||
|
)
|
||||||
7
server/channelserver/constants_time.go
Normal file
7
server/channelserver/constants_time.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package channelserver
|
||||||
|
|
||||||
|
// Shared time duration constants (seconds)
|
||||||
|
const (
|
||||||
|
secsPerDay = 86400 // 24 hours
|
||||||
|
secsPerWeek = 604800 // 7 days
|
||||||
|
)
|
||||||
@@ -14,25 +14,23 @@ import (
|
|||||||
|
|
||||||
func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfAcquireCafeItem(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
|
pkt := p.(*mhfpacket.MsgMhfAcquireCafeItem)
|
||||||
var netcafePoints uint32
|
netcafePoints, err := adjustCharacterInt(s, "netcafe_points", -int(pkt.PointCost))
|
||||||
err := s.server.db.QueryRow("UPDATE characters SET netcafe_points = netcafe_points - $1 WHERE id = $2 RETURNING netcafe_points", pkt.PointCost, s.charID).Scan(&netcafePoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get netcafe points from db", zap.Error(err))
|
s.logger.Error("Failed to deduct netcafe points", zap.Error(err))
|
||||||
}
|
}
|
||||||
resp := byteframe.NewByteFrame()
|
resp := byteframe.NewByteFrame()
|
||||||
resp.WriteUint32(netcafePoints)
|
resp.WriteUint32(uint32(netcafePoints))
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
|
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfUpdateCafepoint(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
|
pkt := p.(*mhfpacket.MsgMhfUpdateCafepoint)
|
||||||
var netcafePoints uint32
|
netcafePoints, err := readCharacterInt(s, "netcafe_points")
|
||||||
err := s.server.db.QueryRow("SELECT COALESCE(netcafe_points, 0) FROM characters WHERE id = $1", s.charID).Scan(&netcafePoints)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get netcate points from db", zap.Error(err))
|
s.logger.Error("Failed to get netcafe points", zap.Error(err))
|
||||||
}
|
}
|
||||||
resp := byteframe.NewByteFrame()
|
resp := byteframe.NewByteFrame()
|
||||||
resp.WriteUint32(netcafePoints)
|
resp.WriteUint32(uint32(netcafePoints))
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
|
doAckSimpleSucceed(s, pkt.AckHandle, resp.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,17 +91,16 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var cafeTime uint32
|
cafeTime, err := readCharacterInt(s, "cafe_time")
|
||||||
err = s.server.db.QueryRow("SELECT cafe_time FROM characters WHERE id = $1", s.charID).Scan(&cafeTime)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get cafe time", zap.Error(err))
|
s.logger.Error("Failed to get cafe time", zap.Error(err))
|
||||||
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if mhfcourse.CourseExists(30, s.courses) {
|
if mhfcourse.CourseExists(30, s.courses) {
|
||||||
cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime
|
cafeTime = int(TimeAdjusted().Unix()) - int(s.sessionStart) + cafeTime
|
||||||
}
|
}
|
||||||
bf.WriteUint32(cafeTime)
|
bf.WriteUint32(uint32(cafeTime))
|
||||||
if s.server.erupeConfig.RealClientMode >= _config.ZZ {
|
if s.server.erupeConfig.RealClientMode >= _config.ZZ {
|
||||||
bf.WriteUint16(0)
|
bf.WriteUint16(0)
|
||||||
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
|
ps.Uint16(bf, fmt.Sprintf(s.server.i18n.cafe.reset, int(cafeReset.Month()), cafeReset.Day()), true)
|
||||||
@@ -218,16 +215,11 @@ func handleMsgMhfPostCafeDurationBonusReceived(s *Session, p mhfpacket.MHFPacket
|
|||||||
}
|
}
|
||||||
|
|
||||||
func addPointNetcafe(s *Session, p int) error {
|
func addPointNetcafe(s *Session, p int) error {
|
||||||
var points int
|
points, err := readCharacterInt(s, "netcafe_points")
|
||||||
err := s.server.db.QueryRow("SELECT netcafe_points FROM characters WHERE id = $1", s.charID).Scan(&points)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if points+p > s.server.erupeConfig.GameplayOptions.MaximumNP {
|
points = min(points+p, s.server.erupeConfig.GameplayOptions.MaximumNP)
|
||||||
points = s.server.erupeConfig.GameplayOptions.MaximumNP
|
|
||||||
} else {
|
|
||||||
points += p
|
|
||||||
}
|
|
||||||
if _, err := s.server.db.Exec("UPDATE characters SET netcafe_points=$1 WHERE id=$2", points, s.charID); err != nil {
|
if _, err := s.server.db.Exec("UPDATE characters SET netcafe_points=$1 WHERE id=$2", points, s.charID); err != nil {
|
||||||
s.logger.Error("Failed to update netcafe points", zap.Error(err))
|
s.logger.Error("Failed to update netcafe points", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,13 @@ func handleMsgSysCastBinary(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
pkt := p.(*mhfpacket.MsgSysCastBinary)
|
pkt := p.(*mhfpacket.MsgSysCastBinary)
|
||||||
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
tmp := byteframe.NewByteFrameFromBytes(pkt.RawDataPayload)
|
||||||
|
|
||||||
if pkt.BroadcastType == BroadcastTypeStage && pkt.MessageType == BinaryMessageTypeData && len(pkt.RawDataPayload) == 0x10 {
|
const (
|
||||||
if tmp.ReadUint16() == 0x0002 && tmp.ReadUint8() == 0x18 {
|
timerPayloadSize = 0x10 // expected payload length for timer packets
|
||||||
|
timerSubtype = uint16(0x0002) // timer data subtype identifier
|
||||||
|
timerFlag = uint8(0x18) // timer flag byte
|
||||||
|
)
|
||||||
|
if pkt.BroadcastType == BroadcastTypeStage && pkt.MessageType == BinaryMessageTypeData && len(pkt.RawDataPayload) == timerPayloadSize {
|
||||||
|
if tmp.ReadUint16() == timerSubtype && tmp.ReadUint8() == timerFlag {
|
||||||
var timer bool
|
var timer bool
|
||||||
if err := s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users WHERE id=$1`, s.userID).Scan(&timer); err != nil {
|
if err := s.server.db.QueryRow(`SELECT COALESCE(timer, false) FROM users WHERE id=$1`, s.userID).Scan(&timer); err != nil {
|
||||||
s.logger.Error("Failed to get timer setting", zap.Error(err))
|
s.logger.Error("Failed to get timer setting", zap.Error(err))
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ func sendDisabledCommandMessage(s *Session, cmd _config.Command) {
|
|||||||
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name))
|
sendServerChatMessage(s, fmt.Sprintf(s.server.i18n.commands.disabled, cmd.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chatFlagServer = 0x80 // marks a message as server-originated
|
||||||
|
|
||||||
func sendServerChatMessage(s *Session, message string) {
|
func sendServerChatMessage(s *Session, message string) {
|
||||||
// Make the inside of the casted binary
|
// Make the inside of the casted binary
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
@@ -51,7 +53,7 @@ func sendServerChatMessage(s *Session, message string) {
|
|||||||
msgBinChat := &binpacket.MsgBinChat{
|
msgBinChat := &binpacket.MsgBinChat{
|
||||||
Unk0: 0,
|
Unk0: 0,
|
||||||
Type: 5,
|
Type: 5,
|
||||||
Flags: 0x80,
|
Flags: chatFlagServer,
|
||||||
Message: message,
|
Message: message,
|
||||||
SenderName: "Erupe",
|
SenderName: "Erupe",
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
divaPhaseDuration = 601200 // 6d 23h = first song phase
|
divaPhaseDuration = 601200 // 6d 23h = first song phase
|
||||||
divaInterlude = 3900 // 65 min = gap between phases
|
divaInterlude = 3900 // 65 min = gap between phases
|
||||||
divaWeekDuration = 604800 // 7 days = subsequent phase length
|
divaWeekDuration = secsPerWeek // 7 days = subsequent phase length
|
||||||
divaTotalLifespan = 2977200 // ~34.5 days = full event window
|
divaTotalLifespan = 2977200 // ~34.5 days = full event window
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,7 +76,8 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
|
pkt := p.(*mhfpacket.MsgMhfGetUdSchedule)
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
|
|
||||||
id, start := uint32(0xCAFEBEEF), uint32(0)
|
const divaIDSentinel = uint32(0xCAFEBEEF)
|
||||||
|
id, start := divaIDSentinel, uint32(0)
|
||||||
rows, err := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='diva'")
|
rows, err := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='diva'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to query diva schedule", zap.Error(err))
|
s.logger.Error("Failed to query diva schedule", zap.Error(err))
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ func handleMsgMhfGetKeepLoginBoostStatus(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boost.WeekCount = uint8((TimeAdjusted().Unix()-boost.Expiration.Unix())/604800 + 1)
|
boost.WeekCount = uint8((TimeAdjusted().Unix()-boost.Expiration.Unix())/secsPerWeek + 1)
|
||||||
|
|
||||||
if boost.WeekCount >= boost.WeekReq {
|
if boost.WeekCount >= boost.WeekReq {
|
||||||
boost.Active = true
|
boost.Active = true
|
||||||
|
|||||||
@@ -103,6 +103,13 @@ func cleanupFesta(s *Session) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Festa timing constants (all values in seconds)
|
||||||
|
const (
|
||||||
|
festaVotingDuration = 9000 // 150 min voting window
|
||||||
|
festaRewardDuration = 1240200 // ~14.35 days reward period
|
||||||
|
festaEventLifespan = 2977200 // ~34.5 days total event window
|
||||||
|
)
|
||||||
|
|
||||||
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
||||||
timestamps := make([]uint32, 5)
|
timestamps := make([]uint32, 5)
|
||||||
midnight := TimeMidnight()
|
midnight := TimeMidnight()
|
||||||
@@ -111,26 +118,26 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
|||||||
switch start {
|
switch start {
|
||||||
case 1:
|
case 1:
|
||||||
timestamps[0] = midnight
|
timestamps[0] = midnight
|
||||||
timestamps[1] = timestamps[0] + 604800
|
timestamps[1] = timestamps[0] + secsPerWeek
|
||||||
timestamps[2] = timestamps[1] + 604800
|
timestamps[2] = timestamps[1] + secsPerWeek
|
||||||
timestamps[3] = timestamps[2] + 9000
|
timestamps[3] = timestamps[2] + festaVotingDuration
|
||||||
timestamps[4] = timestamps[3] + 1240200
|
timestamps[4] = timestamps[3] + festaRewardDuration
|
||||||
case 2:
|
case 2:
|
||||||
timestamps[0] = midnight - 604800
|
timestamps[0] = midnight - secsPerWeek
|
||||||
timestamps[1] = midnight
|
timestamps[1] = midnight
|
||||||
timestamps[2] = timestamps[1] + 604800
|
timestamps[2] = timestamps[1] + secsPerWeek
|
||||||
timestamps[3] = timestamps[2] + 9000
|
timestamps[3] = timestamps[2] + festaVotingDuration
|
||||||
timestamps[4] = timestamps[3] + 1240200
|
timestamps[4] = timestamps[3] + festaRewardDuration
|
||||||
case 3:
|
case 3:
|
||||||
timestamps[0] = midnight - 1209600
|
timestamps[0] = midnight - 2*secsPerWeek
|
||||||
timestamps[1] = midnight - 604800
|
timestamps[1] = midnight - secsPerWeek
|
||||||
timestamps[2] = midnight
|
timestamps[2] = midnight
|
||||||
timestamps[3] = timestamps[2] + 9000
|
timestamps[3] = timestamps[2] + festaVotingDuration
|
||||||
timestamps[4] = timestamps[3] + 1240200
|
timestamps[4] = timestamps[3] + festaRewardDuration
|
||||||
}
|
}
|
||||||
return timestamps
|
return timestamps
|
||||||
}
|
}
|
||||||
if start == 0 || TimeAdjusted().Unix() > int64(start)+2977200 {
|
if start == 0 || TimeAdjusted().Unix() > int64(start)+festaEventLifespan {
|
||||||
cleanupFesta(s)
|
cleanupFesta(s)
|
||||||
// Generate a new festa, starting midnight tomorrow
|
// Generate a new festa, starting midnight tomorrow
|
||||||
start = uint32(midnight.Add(24 * time.Hour).Unix())
|
start = uint32(midnight.Add(24 * time.Hour).Unix())
|
||||||
@@ -139,10 +146,10 @@ func generateFestaTimestamps(s *Session, start uint32, debug bool) []uint32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
timestamps[0] = start
|
timestamps[0] = start
|
||||||
timestamps[1] = timestamps[0] + 604800
|
timestamps[1] = timestamps[0] + secsPerWeek
|
||||||
timestamps[2] = timestamps[1] + 604800
|
timestamps[2] = timestamps[1] + secsPerWeek
|
||||||
timestamps[3] = timestamps[2] + 9000
|
timestamps[3] = timestamps[2] + festaVotingDuration
|
||||||
timestamps[4] = timestamps[3] + 1240200
|
timestamps[4] = timestamps[3] + festaRewardDuration
|
||||||
return timestamps
|
return timestamps
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +181,8 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
|
pkt := p.(*mhfpacket.MsgMhfInfoFesta)
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
|
|
||||||
id, start := uint32(0xDEADBEEF), uint32(0)
|
const festaIDSentinel = uint32(0xDEADBEEF)
|
||||||
|
id, start := festaIDSentinel, uint32(0)
|
||||||
rows, err := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='festa'")
|
rows, err := s.server.db.Queryx("SELECT id, (EXTRACT(epoch FROM start_time)::int) as start_time FROM events WHERE event_type='festa'")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to query festa schedule", zap.Error(err))
|
s.logger.Error("Failed to query festa schedule", zap.Error(err))
|
||||||
@@ -342,7 +350,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
var guildID uint32
|
var guildID uint32
|
||||||
var guildName string
|
var guildName string
|
||||||
var guildTeam = FestivalColorNone
|
var guildTeam = FestivalColorNone
|
||||||
offset := 86400 * uint32(i)
|
offset := secsPerDay * uint32(i)
|
||||||
if err := s.server.db.QueryRow(`
|
if err := s.server.db.QueryRow(`
|
||||||
SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
|
SELECT fs.guild_id, g.name, fr.team, SUM(fs.souls) as _
|
||||||
FROM festa_submissions fs
|
FROM festa_submissions fs
|
||||||
@@ -351,7 +359,7 @@ func handleMsgMhfInfoFesta(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
WHERE EXTRACT(EPOCH FROM fs.timestamp)::int > $1 AND EXTRACT(EPOCH FROM fs.timestamp)::int < $2
|
WHERE EXTRACT(EPOCH FROM fs.timestamp)::int > $1 AND EXTRACT(EPOCH FROM fs.timestamp)::int < $2
|
||||||
GROUP BY fs.guild_id, g.name, fr.team
|
GROUP BY fs.guild_id, g.name, fr.team
|
||||||
ORDER BY _ DESC LIMIT 1
|
ORDER BY _ DESC LIMIT 1
|
||||||
`, timestamps[1]+offset, timestamps[1]+offset+86400).Scan(&guildID, &guildName, &guildTeam, &temp); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
`, timestamps[1]+offset, timestamps[1]+offset+secsPerDay).Scan(&guildID, &guildName, &guildTeam, &temp); err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||||
s.logger.Error("Failed to get festa daily ranking", zap.Error(err))
|
s.logger.Error("Failed to get festa daily ranking", zap.Error(err))
|
||||||
}
|
}
|
||||||
bf.WriteUint32(guildID)
|
bf.WriteUint32(guildID)
|
||||||
|
|||||||
@@ -56,8 +56,9 @@ func handleMsgMhfGetGuildMissionList(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
func handleMsgMhfGetGuildMissionRecord(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfGetGuildMissionRecord(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfGetGuildMissionRecord)
|
pkt := p.(*mhfpacket.MsgMhfGetGuildMissionRecord)
|
||||||
|
|
||||||
// No guild mission records = 0x190 empty bytes
|
const guildMissionRecordSize = 0x190
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x190))
|
// No guild mission records = empty buffer
|
||||||
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, guildMissionRecordSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMsgMhfAddGuildMissionCount(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfAddGuildMissionCount(s *Session, p mhfpacket.MHFPacket) {
|
||||||
|
|||||||
@@ -96,6 +96,22 @@ func saveCharacterData(s *Session, ackHandle uint32, column string, data []byte,
|
|||||||
doAckSimpleSucceed(s, ackHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, ackHandle, make([]byte, 4))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readCharacterInt reads a single integer column from the characters table.
|
||||||
|
// Returns 0 for NULL columns via COALESCE.
|
||||||
|
func readCharacterInt(s *Session, column string) (int, error) {
|
||||||
|
var value int
|
||||||
|
err := s.server.db.QueryRow("SELECT COALESCE("+column+", 0) FROM characters WHERE id=$1", s.charID).Scan(&value)
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjustCharacterInt atomically adds delta to an integer column and returns the new value.
|
||||||
|
// Handles NULL columns via COALESCE (NULL + delta = delta).
|
||||||
|
func adjustCharacterInt(s *Session, column string, delta int) (int, error) {
|
||||||
|
var value int
|
||||||
|
err := s.server.db.QueryRow("UPDATE characters SET "+column+"=COALESCE("+column+", 0)+$1 WHERE id=$2 RETURNING "+column, delta, s.charID).Scan(&value)
|
||||||
|
return value, err
|
||||||
|
}
|
||||||
|
|
||||||
func updateRights(s *Session) {
|
func updateRights(s *Session) {
|
||||||
rightsInt := uint32(2)
|
rightsInt := uint32(2)
|
||||||
_ = s.server.db.QueryRow("SELECT rights FROM users WHERE id=$1", s.userID).Scan(&rightsInt)
|
_ = s.server.db.QueryRow("SELECT rights FROM users WHERE id=$1", s.userID).Scan(&rightsInt)
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ func handleMsgMhfAddKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
zap.Uint32("points_to_add", pkt.KouryouPoints),
|
zap.Uint32("points_to_add", pkt.KouryouPoints),
|
||||||
)
|
)
|
||||||
|
|
||||||
var points int
|
points, err := adjustCharacterInt(s, "kouryou_point", int(pkt.KouryouPoints))
|
||||||
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=COALESCE(kouryou_point + $1, $1) WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to update KouryouPoint in db",
|
s.logger.Error("Failed to update KouryouPoint in db",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
@@ -42,8 +41,7 @@ func handleMsgMhfAddKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
func handleMsgMhfGetKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfGetKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfGetKouryouPoint)
|
pkt := p.(*mhfpacket.MsgMhfGetKouryouPoint)
|
||||||
var points int
|
points, err := readCharacterInt(s, "kouryou_point")
|
||||||
err := s.server.db.QueryRow("SELECT COALESCE(kouryou_point, 0) FROM characters WHERE id = $1", s.charID).Scan(&points)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to get kouryou_point from db",
|
s.logger.Error("Failed to get kouryou_point from db",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
@@ -70,8 +68,7 @@ func handleMsgMhfExchangeKouryouPoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
zap.Uint32("points_to_spend", pkt.KouryouPoints),
|
zap.Uint32("points_to_spend", pkt.KouryouPoints),
|
||||||
)
|
)
|
||||||
|
|
||||||
var points int
|
points, err := adjustCharacterInt(s, "kouryou_point", -int(pkt.KouryouPoints))
|
||||||
err := s.server.db.QueryRow("UPDATE characters SET kouryou_point=kouryou_point - $1 WHERE id=$2 RETURNING kouryou_point", pkt.KouryouPoints, s.charID).Scan(&points)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to exchange Koryo points",
|
s.logger.Error("Failed to exchange Koryo points",
|
||||||
zap.Error(err),
|
zap.Error(err),
|
||||||
|
|||||||
@@ -41,11 +41,17 @@ func handleMsgMhfLoadLegendDispatch(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Hunter Navi buffer sizes per game version
|
||||||
|
const (
|
||||||
|
hunterNaviSizeG8 = 552 // G8+ navi buffer size
|
||||||
|
hunterNaviSizeG7 = 280 // G7 and older navi buffer size
|
||||||
|
)
|
||||||
|
|
||||||
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfLoadHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
|
pkt := p.(*mhfpacket.MsgMhfLoadHunterNavi)
|
||||||
naviLength := 552
|
naviLength := hunterNaviSizeG8
|
||||||
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
||||||
naviLength = 280
|
naviLength = hunterNaviSizeG7
|
||||||
}
|
}
|
||||||
loadCharacterData(s, pkt.AckHandle, "hunternavi", make([]byte, naviLength))
|
loadCharacterData(s, pkt.AckHandle, "hunternavi", make([]byte, naviLength))
|
||||||
}
|
}
|
||||||
@@ -67,9 +73,9 @@ func handleMsgMhfSaveHunterNavi(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
var dataSize int
|
var dataSize int
|
||||||
if pkt.IsDataDiff {
|
if pkt.IsDataDiff {
|
||||||
naviLength := 552
|
naviLength := hunterNaviSizeG8
|
||||||
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
if s.server.erupeConfig.RealClientMode <= _config.G7 {
|
||||||
naviLength = 280
|
naviLength = hunterNaviSizeG7
|
||||||
}
|
}
|
||||||
var data []byte
|
var data []byte
|
||||||
// Load existing save
|
// Load existing save
|
||||||
@@ -203,13 +209,13 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
|
pkt := p.(*mhfpacket.MsgMhfReadMercenaryW)
|
||||||
bf := byteframe.NewByteFrame()
|
bf := byteframe.NewByteFrame()
|
||||||
|
|
||||||
var pactID, cid uint32
|
var cid uint32
|
||||||
var name string
|
var name string
|
||||||
_ = s.server.db.QueryRow("SELECT pact_id FROM characters WHERE id=$1", s.charID).Scan(&pactID)
|
pactID, _ := readCharacterInt(s, "pact_id")
|
||||||
if pactID > 0 {
|
if pactID > 0 {
|
||||||
_ = s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
|
_ = s.server.db.QueryRow("SELECT name, id FROM characters WHERE rasta_id = $1", pactID).Scan(&name, &cid)
|
||||||
bf.WriteUint8(1) // numLends
|
bf.WriteUint8(1) // numLends
|
||||||
bf.WriteUint32(pactID)
|
bf.WriteUint32(uint32(pactID))
|
||||||
bf.WriteUint32(cid)
|
bf.WriteUint32(cid)
|
||||||
bf.WriteBool(true) // Escort enabled
|
bf.WriteBool(true) // Escort enabled
|
||||||
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
bf.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||||
@@ -232,7 +238,7 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
loans++
|
loans++
|
||||||
temp.WriteUint32(pactID)
|
temp.WriteUint32(uint32(pactID))
|
||||||
temp.WriteUint32(cid)
|
temp.WriteUint32(cid)
|
||||||
temp.WriteUint32(uint32(TimeAdjusted().Unix()))
|
temp.WriteUint32(uint32(TimeAdjusted().Unix()))
|
||||||
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix()))
|
temp.WriteUint32(uint32(TimeAdjusted().Add(time.Hour * 24 * 7).Unix()))
|
||||||
@@ -244,9 +250,8 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
if pkt.Op != 1 && pkt.Op != 4 {
|
if pkt.Op != 1 && pkt.Op != 4 {
|
||||||
var data []byte
|
var data []byte
|
||||||
var gcp uint32
|
|
||||||
_ = s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
|
_ = s.server.db.QueryRow("SELECT savemercenary FROM characters WHERE id=$1", s.charID).Scan(&data)
|
||||||
_ = s.server.db.QueryRow("SELECT COALESCE(gcp, 0) FROM characters WHERE id=$1", s.charID).Scan(&gcp)
|
gcp, _ := readCharacterInt(s, "gcp")
|
||||||
|
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
bf.WriteBool(false)
|
bf.WriteBool(false)
|
||||||
@@ -254,7 +259,7 @@ func handleMsgMhfReadMercenaryW(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf.WriteBool(true)
|
bf.WriteBool(true)
|
||||||
bf.WriteBytes(data)
|
bf.WriteBytes(data)
|
||||||
}
|
}
|
||||||
bf.WriteUint32(gcp)
|
bf.WriteUint32(uint32(gcp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"erupe-ce/common/byteframe"
|
"erupe-ce/common/byteframe"
|
||||||
_config "erupe-ce/config"
|
_config "erupe-ce/config"
|
||||||
"erupe-ce/network/mhfpacket"
|
"erupe-ce/network/mhfpacket"
|
||||||
"fmt"
|
|
||||||
"math/bits"
|
"math/bits"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -23,7 +22,9 @@ func handleMsgMhfGetEtcPoints(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var bonusQuests, dailyQuests, promoPoints uint32
|
var bonusQuests, dailyQuests, promoPoints uint32
|
||||||
_ = s.server.db.QueryRow(`SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id = $1`, s.charID).Scan(&bonusQuests, &dailyQuests, &promoPoints)
|
if err := s.server.db.QueryRow(`SELECT bonus_quests, daily_quests, promo_points FROM characters WHERE id = $1`, s.charID).Scan(&bonusQuests, &dailyQuests, &promoPoints); err != nil {
|
||||||
|
s.logger.Error("Failed to get etc points", zap.Error(err))
|
||||||
|
}
|
||||||
resp := byteframe.NewByteFrame()
|
resp := byteframe.NewByteFrame()
|
||||||
resp.WriteUint8(3) // Maybe a count of uint32(s)?
|
resp.WriteUint8(3) // Maybe a count of uint32(s)?
|
||||||
resp.WriteUint32(bonusQuests)
|
resp.WriteUint32(bonusQuests)
|
||||||
@@ -48,17 +49,11 @@ func handleMsgMhfUpdateEtcPoint(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var value int16
|
value, err := readCharacterInt(s, column)
|
||||||
err := s.server.db.QueryRow(fmt.Sprintf(`SELECT %s FROM characters WHERE id = $1`, column), s.charID).Scan(&value)
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if value+pkt.Delta < 0 {
|
newVal := max(value+int(pkt.Delta), 0)
|
||||||
if _, err := s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = 0 WHERE id = $1`, column), s.charID); err != nil {
|
if _, err := s.server.db.Exec("UPDATE characters SET "+column+"=$1 WHERE id=$2", newVal, s.charID); err != nil {
|
||||||
s.logger.Error("Failed to reset etc point", zap.Error(err))
|
s.logger.Error("Failed to update etc point", zap.Error(err))
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if _, err := s.server.db.Exec(fmt.Sprintf(`UPDATE characters SET %s = %s + $1 WHERE id = $2`, column, column), pkt.Delta, s.charID); err != nil {
|
|
||||||
s.logger.Error("Failed to update etc point", zap.Error(err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
@@ -156,13 +151,20 @@ func handleMsgMhfGetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
|
|||||||
|
|
||||||
func handleMsgMhfSetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
|
func handleMsgMhfSetDailyMissionPersonal(s *Session, p mhfpacket.MHFPacket) {}
|
||||||
|
|
||||||
|
// Equip skin history buffer sizes per game version
|
||||||
|
const (
|
||||||
|
skinHistSizeZZ = 3200 // ZZ and newer
|
||||||
|
skinHistSizeZ2 = 2560 // Z2 and older
|
||||||
|
skinHistSizeZ1 = 1280 // Z1 and older
|
||||||
|
)
|
||||||
|
|
||||||
func equipSkinHistSize(mode _config.Mode) int {
|
func equipSkinHistSize(mode _config.Mode) int {
|
||||||
size := 3200
|
size := skinHistSizeZZ
|
||||||
if mode <= _config.Z2 {
|
if mode <= _config.Z2 {
|
||||||
size = 2560
|
size = skinHistSizeZ2
|
||||||
}
|
}
|
||||||
if mode <= _config.Z1 {
|
if mode <= _config.Z1 {
|
||||||
size = 1280
|
size = skinHistSizeZ1
|
||||||
}
|
}
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
@@ -245,7 +247,8 @@ func handleMsgMhfGetLobbyCrowd(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
// It can be worried about later if we ever get to the point where there are
|
// It can be worried about later if we ever get to the point where there are
|
||||||
// full servers to actually need to migrate people from and empty ones to
|
// full servers to actually need to migrate people from and empty ones to
|
||||||
pkt := p.(*mhfpacket.MsgMhfGetLobbyCrowd)
|
pkt := p.(*mhfpacket.MsgMhfGetLobbyCrowd)
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x320))
|
const lobbyCrowdResponseSize = 0x320
|
||||||
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, lobbyCrowdResponseSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrendWeapon represents trending weapon usage data.
|
// TrendWeapon represents trending weapon usage data.
|
||||||
|
|||||||
@@ -34,9 +34,19 @@ func handleMsgMhfLoadPlateData(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
loadCharacterData(s, pkt.AckHandle, "platedata", nil)
|
loadCharacterData(s, pkt.AckHandle, "platedata", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Plate data size constants
|
||||||
|
const (
|
||||||
|
plateDataMaxPayload = 262144 // max compressed platedata size
|
||||||
|
plateDataEmptySize = 140000 // empty platedata buffer
|
||||||
|
plateBoxMaxPayload = 32768 // max compressed platebox size
|
||||||
|
plateBoxEmptySize = 4800 // empty platebox buffer
|
||||||
|
plateMysetDefaultLen = 1920 // default platemyset buffer
|
||||||
|
plateMysetMaxPayload = 4096 // max platemyset payload size
|
||||||
|
)
|
||||||
|
|
||||||
func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfSavePlateData)
|
pkt := p.(*mhfpacket.MsgMhfSavePlateData)
|
||||||
if len(pkt.RawDataPayload) > 262144 {
|
if len(pkt.RawDataPayload) > plateDataMaxPayload {
|
||||||
s.logger.Warn("PlateData payload too large", zap.Int("len", len(pkt.RawDataPayload)))
|
s.logger.Warn("PlateData payload too large", zap.Int("len", len(pkt.RawDataPayload)))
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
return
|
return
|
||||||
@@ -78,7 +88,7 @@ func handleMsgMhfSavePlateData(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// create empty save if absent
|
// create empty save if absent
|
||||||
data = make([]byte, 140000)
|
data = make([]byte, plateDataEmptySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform diff and compress it to write back to db
|
// Perform diff and compress it to write back to db
|
||||||
@@ -144,7 +154,7 @@ func handleMsgMhfLoadPlateBox(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfSavePlateBox)
|
pkt := p.(*mhfpacket.MsgMhfSavePlateBox)
|
||||||
if len(pkt.RawDataPayload) > 32768 {
|
if len(pkt.RawDataPayload) > plateBoxMaxPayload {
|
||||||
s.logger.Warn("PlateBox payload too large", zap.Int("len", len(pkt.RawDataPayload)))
|
s.logger.Warn("PlateBox payload too large", zap.Int("len", len(pkt.RawDataPayload)))
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
return
|
return
|
||||||
@@ -173,7 +183,7 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// create empty save if absent
|
// create empty save if absent
|
||||||
data = make([]byte, 4800)
|
data = make([]byte, plateBoxEmptySize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform diff and compress it to write back to db
|
// Perform diff and compress it to write back to db
|
||||||
@@ -213,12 +223,12 @@ func handleMsgMhfSavePlateBox(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
|
|
||||||
func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfLoadPlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfLoadPlateMyset)
|
pkt := p.(*mhfpacket.MsgMhfLoadPlateMyset)
|
||||||
loadCharacterData(s, pkt.AckHandle, "platemyset", make([]byte, 1920))
|
loadCharacterData(s, pkt.AckHandle, "platemyset", make([]byte, plateMysetDefaultLen))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfSavePlateMyset(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfSavePlateMyset)
|
pkt := p.(*mhfpacket.MsgMhfSavePlateMyset)
|
||||||
if len(pkt.RawDataPayload) > 4096 {
|
if len(pkt.RawDataPayload) > plateMysetMaxPayload {
|
||||||
s.logger.Warn("PlateMyset payload too large", zap.Int("len", len(pkt.RawDataPayload)))
|
s.logger.Warn("PlateMyset payload too large", zap.Int("len", len(pkt.RawDataPayload)))
|
||||||
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ func handleMsgMhfRegisterEvent(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ACK error codes from the MHF client
|
||||||
|
const ackEFailed = uint8(0x41) // _ACK_EFAILED = 65
|
||||||
|
|
||||||
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
|
||||||
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
|
pkt := p.(*mhfpacket.MsgMhfReleaseEvent)
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ func handleMsgMhfReleaseEvent(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
s.QueueSendMHF(&mhfpacket.MsgSysAck{
|
s.QueueSendMHF(&mhfpacket.MsgSysAck{
|
||||||
AckHandle: pkt.AckHandle,
|
AckHandle: pkt.AckHandle,
|
||||||
IsBufferResponse: false,
|
IsBufferResponse: false,
|
||||||
ErrorCode: 0x41,
|
ErrorCode: ackEFailed,
|
||||||
AckData: []byte{0x00, 0x00, 0x00, 0x00},
|
AckData: []byte{0x00, 0x00, 0x00, 0x00},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -104,11 +107,11 @@ func handleMsgSysLoadRegister(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
bf.WriteUint8(pkt.Values)
|
bf.WriteUint8(pkt.Values)
|
||||||
for i := uint8(0); i < pkt.Values; i++ {
|
for i := uint8(0); i < pkt.Values; i++ {
|
||||||
switch pkt.RegisterID {
|
switch pkt.RegisterID {
|
||||||
case 0x40000:
|
case raviRegisterState:
|
||||||
bf.WriteUint32(s.server.raviente.state[i])
|
bf.WriteUint32(s.server.raviente.state[i])
|
||||||
case 0x50000:
|
case raviRegisterSupport:
|
||||||
bf.WriteUint32(s.server.raviente.support[i])
|
bf.WriteUint32(s.server.raviente.support[i])
|
||||||
case 0x60000:
|
case raviRegisterGeneral:
|
||||||
bf.WriteUint32(s.server.raviente.register[i])
|
bf.WriteUint32(s.server.raviente.register[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,13 +125,13 @@ func (s *Session) notifyRavi() {
|
|||||||
}
|
}
|
||||||
var temp mhfpacket.MHFPacket
|
var temp mhfpacket.MHFPacket
|
||||||
raviNotif := byteframe.NewByteFrame()
|
raviNotif := byteframe.NewByteFrame()
|
||||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x40000}
|
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: raviRegisterState}
|
||||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
_ = temp.Build(raviNotif, s.clientContext)
|
_ = temp.Build(raviNotif, s.clientContext)
|
||||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x50000}
|
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: raviRegisterSupport}
|
||||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
_ = temp.Build(raviNotif, s.clientContext)
|
_ = temp.Build(raviNotif, s.clientContext)
|
||||||
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: 0x60000}
|
temp = &mhfpacket.MsgSysNotifyRegister{RegisterID: raviRegisterGeneral}
|
||||||
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
raviNotif.WriteUint16(uint16(temp.Opcode()))
|
||||||
_ = temp.Build(raviNotif, s.clientContext)
|
_ = temp.Build(raviNotif, s.clientContext)
|
||||||
raviNotif.WriteUint16(0x0010) // End it.
|
raviNotif.WriteUint16(0x0010) // End it.
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ func handleMsgMhfGetAdditionalBeatReward(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
// Actual response in packet captures are all just giant batches of null bytes
|
// Actual response in packet captures are all just giant batches of null bytes
|
||||||
// I'm assuming this is because it used to be tied to an actual event and
|
// I'm assuming this is because it used to be tied to an actual event and
|
||||||
// they never bothered killing off the packet when they made it static
|
// they never bothered killing off the packet when they made it static
|
||||||
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0x104))
|
const beatRewardResponseSize = 0x104
|
||||||
|
doAckBufSucceed(s, pkt.AckHandle, make([]byte, beatRewardResponseSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleMsgMhfGetUdRankingRewardList(s *Session, p mhfpacket.MHFPacket) {
|
func handleMsgMhfGetUdRankingRewardList(s *Session, p mhfpacket.MHFPacket) {
|
||||||
|
|||||||
@@ -79,9 +79,9 @@ func handleMsgSysCreateAcquireSemaphore(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:])
|
suffix, _ := strconv.Atoi(pkt.SemaphoreID[len(pkt.SemaphoreID)-1:])
|
||||||
s.server.semaphore[SemaphoreID] = &Semaphore{
|
s.server.semaphore[SemaphoreID] = &Semaphore{
|
||||||
name: pkt.SemaphoreID,
|
name: pkt.SemaphoreID,
|
||||||
id: uint32((suffix + 1) * 0x10000),
|
id: uint32((suffix + 1) * raviSemaphoreStride),
|
||||||
clients: make(map[*Session]uint32),
|
clients: make(map[*Session]uint32),
|
||||||
maxPlayers: 127,
|
maxPlayers: raviSemaphoreMax,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
s.server.semaphore[SemaphoreID] = NewSemaphore(s, SemaphoreID, 1)
|
s.server.semaphore[SemaphoreID] = NewSemaphore(s, SemaphoreID, 1)
|
||||||
|
|||||||
@@ -394,6 +394,8 @@ func handleMsgSysIssueLogkey(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const localhostAddrLE = uint32(0x0100007F) // 127.0.0.1 in little-endian
|
||||||
|
|
||||||
// Kill log binary layout constants
|
// Kill log binary layout constants
|
||||||
const (
|
const (
|
||||||
killLogHeaderSize = 32 // bytes before monster kill count array
|
killLogHeaderSize = 32 // bytes before monster kill count array
|
||||||
@@ -557,7 +559,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
if !local {
|
if !local {
|
||||||
resp.WriteUint32(binary.LittleEndian.Uint32(r.ip))
|
resp.WriteUint32(binary.LittleEndian.Uint32(r.ip))
|
||||||
} else {
|
} else {
|
||||||
resp.WriteUint32(0x0100007F)
|
resp.WriteUint32(localhostAddrLE)
|
||||||
}
|
}
|
||||||
resp.WriteUint16(r.port)
|
resp.WriteUint16(r.port)
|
||||||
resp.WriteUint32(r.charID)
|
resp.WriteUint32(r.charID)
|
||||||
@@ -757,7 +759,7 @@ func handleMsgMhfTransitMessage(s *Session, p mhfpacket.MHFPacket) {
|
|||||||
if !local {
|
if !local {
|
||||||
resp.WriteUint32(binary.LittleEndian.Uint32(sr.ip))
|
resp.WriteUint32(binary.LittleEndian.Uint32(sr.ip))
|
||||||
} else {
|
} else {
|
||||||
resp.WriteUint32(0x0100007F)
|
resp.WriteUint32(localhostAddrLE)
|
||||||
}
|
}
|
||||||
resp.WriteUint16(sr.port)
|
resp.WriteUint16(sr.port)
|
||||||
|
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ func (s *Server) BroadcastChatMessage(message string) {
|
|||||||
msgBinChat := &binpacket.MsgBinChat{
|
msgBinChat := &binpacket.MsgBinChat{
|
||||||
Unk0: 0,
|
Unk0: 0,
|
||||||
Type: 5,
|
Type: 5,
|
||||||
Flags: 0x80,
|
Flags: chatFlagServer,
|
||||||
Message: message,
|
Message: message,
|
||||||
SenderName: s.name,
|
SenderName: s.name,
|
||||||
}
|
}
|
||||||
@@ -420,8 +420,15 @@ func (s *Server) HasSemaphore(ses *Session) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server ID arithmetic constants
|
||||||
|
const (
|
||||||
|
serverIDHighMask = uint16(0xFF00)
|
||||||
|
serverIDBase = 0x1000 // first server ID offset
|
||||||
|
serverIDStride = 0x100 // spacing between server IDs
|
||||||
|
)
|
||||||
|
|
||||||
// Season returns the current in-game season (0-2) based on server ID and time.
|
// Season returns the current in-game season (0-2) based on server ID and time.
|
||||||
func (s *Server) Season() uint8 {
|
func (s *Server) Season() uint8 {
|
||||||
sid := int64(((s.ID & 0xFF00) - 4096) / 256)
|
sid := int64(((s.ID & serverIDHighMask) - serverIDBase) / serverIDStride)
|
||||||
return uint8(((TimeAdjusted().Unix() / 86400) + sid) % 3)
|
return uint8(((TimeAdjusted().Unix() / secsPerDay) + sid) % 3)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user