fix: replace panic calls with proper error handling

Remove 51 panic() calls from handler code and replace with:
- Proper error logging using zap
- Appropriate client error responses (doAckBufFail, doAckSimpleFail)
- Graceful error recovery instead of server crashes

Files updated:
- handlers_guild_scout.go (9 panics)
- handlers_guild_tresure.go (10 panics)
- handlers_guild.go (7 panics + dead code removal)
- handlers_mail.go (5 panics)
- handlers.go (9 panics)
- handlers_tower.go (2 panics)
- handlers_clients.go (3 panics)
- handlers_guild_alliance.go (1 panic)
- handlers_quest.go (1 panic)
- handlers_rengoku.go (1 panic)
- handlers_stage.go (1 panic)
- handlers_data.go (1 panic)
- handlers_cafe.go (1 panic)
- signserver/sign_server.go (1 panic)

Remaining panics (3) are in test files and compression library
where panicking on programming errors is appropriate.
This commit is contained in:
Houmgaor
2026-02-02 17:14:34 +01:00
parent dbc3b21827
commit f138cb5f77
15 changed files with 203 additions and 108 deletions

View File

@@ -4,26 +4,36 @@ This document outlines prioritized improvements identified through codebase anal
---
## Progress Summary
| Area | Status |
|------|--------|
| Tier 1: Critical Stability Fixes | ✅ Complete (7/7) |
| Tier 2: Security Updates | Pending |
| Tier 3: Important Bug Fixes | Partial (3/6) |
| Tier 4: Version Compatibility | Partial (3/7) |
| Tier 5: Warehouse & Save System | Pending |
| Test Coverage (channelserver) | 25% (was 7.5%) |
| CI: gofmt + golangci-lint | ✅ Added |
| Panic Cleanup | ✅ Complete (51 removed, 3 remain in tests/lib) |
---
## Cherry-Pick from Main Branch
The `main` branch is 589 commits ahead of `9.2.0-clean` but is unstable for players. The following commits should be cherry-picked (and fixed if necessary) for 9.3.0.
### Tier 1: Critical Stability Fixes (Cherry-pick immediately)
### Tier 1: Critical Stability Fixes ✅ COMPLETE
| Commit | Description | Files Changed | Risk |
|--------|-------------|---------------|------|
| `e1a461e` | fix(stage): fix deadlock preventing stage change | handlers_stage.go, sys_session.go | Low |
| `060635e` | fix(stage): fix race condition with stages | handlers_stage.go | Low |
| `1c32be9` | fix(session): race condition | sys_session.go | Low |
| `73e874f` | fix: array bound crashes on clans | Multiple | Low |
| `5028355` | prevent nil pointer in MhfGetGuildManageRight | handlers_guild.go | Low |
| `ba1eea8` | prevent save error crashes | handlers.go, handlers_character.go | Low |
| `60e86c7` | mitigate LoadDecoMyset crashing on older versions | handlers | Low |
**Command:**
```bash
git cherry-pick e1a461e 060635e 1c32be9 73e874f 5028355 ba1eea8 60e86c7
```
| Commit | Description | Applied As | Status |
|--------|-------------|------------|--------|
| `e1a461e` | fix(stage): fix deadlock preventing stage change | 488e8fa | ✅ Done |
| `060635e` | fix(stage): fix race condition with stages | e654bc4 | ✅ Done |
| `1c32be9` | fix(session): race condition | 80c3634 | ✅ Done |
| `73e874f` | fix: array bound crashes on clans | 4201862 | ✅ Done |
| `5028355` | prevent nil pointer in MhfGetGuildManageRight | 94175e6 | ✅ Done |
| `ba1eea8` | prevent save error crashes | 633061c | ✅ Done |
| `60e86c7` | mitigate LoadDecoMyset crashing on older versions | 813cf16 | ✅ Done |
### Tier 2: Security Updates (Cherry-pick after Tier 1)
@@ -38,25 +48,26 @@ git cherry-pick e1a461e 060635e 1c32be9 73e874f 5028355 ba1eea8 60e86c7
### Tier 3: Important Bug Fixes (Review before cherry-pick)
| Commit | Description | Files | Notes |
|--------|-------------|-------|-------|
| `d1dfc3f` | packet queue fix proposal | 6 files | Review carefully - touches core networking |
| `76858bb` | bypass full Stage check if reserve slot | handlers_stage.go | Simple fix |
| `c539905` | implement SysWaitStageBinary timeout | handlers_stage.go | Simple fix |
| `7459ded` | fix guild poogie outfit unlock | handlers | Simple fix |
| `8a55c5f` | fix inflated festa rewards | handlers | Review impact |
| `7d760bd` | fix EntranceServer clan member list limits | entranceserver | Simple fix |
| Commit | Description | Files | Status |
|--------|-------------|-------|--------|
| `d1dfc3f` | packet queue fix proposal | 6 files | Pending - Review carefully |
| `76858bb` | bypass full Stage check if reserve slot | handlers_stage.go | Pending |
| `c539905` | implement SysWaitStageBinary timeout | handlers_stage.go | ✅ Done (a66b15d) |
| `7459ded` | fix guild poogie outfit unlock | handlers | ✅ Done (355c2c0) |
| `8a55c5f` | fix inflated festa rewards | handlers | Pending |
| `7d760bd` | fix EntranceServer clan member list limits | entranceserver | ✅ Done (fb14a78) |
### Tier 4: Version Compatibility Fixes
| Commit | Description | Versions Affected |
|--------|-------------|-------------------|
| `8d1c6a7` | S6 compatibility fix | Season 6.0 |
| `d26ae45` | fix G1 compatibility | G1 |
| `3d0114c` | fix MhfAcquireCafeItem cost in G1-G5.2 | G1-G5.2 |
| `8c219be` | fix InfoGuild response on <G10 | Pre-G10 |
| `183f886` | fix InfoFesta response on S6.0 | S6.0 |
| `1c4370b` | fix EnumerateFestaMember prior to Z2 | Pre-Z2 |
| Commit | Description | Versions Affected | Status |
|--------|-------------|-------------------|--------|
| `8d1c6a7` | S6 compatibility fix | Season 6.0 | Pending |
| `d26ae45` | fix G1 compatibility | G1 | Pending |
| `3d0114c` | fix MhfAcquireCafeItem cost in G1-G5.2 | G1-G5.2 | ✅ Done (e095c5a) |
| `8c219be` | fix InfoGuild response on <G10 | Pre-G10 | Done (c4036da) |
| `183f886` | fix InfoFesta response on S6.0 | S6.0 | Pending |
| `1c4370b` | fix EnumerateFestaMember prior to Z2 | Pre-Z2 | Pending |
| - | S6 quest data backporting | Season 6.0 | Done (021705c) |
### Tier 5: Warehouse & Save System Fixes (Test thoroughly)
@@ -117,10 +128,10 @@ These commits caused or may cause instability:
### Verification Checklist
After cherry-picking, verify:
- [ ] Server starts without errors
- [x] Server starts without errors
- [x] No race conditions: `go test -race ./...`
- [ ] Player can log in
- [ ] Stage changes work (test quest entry/exit)
- [ ] No race conditions: `go test -race ./...`
- [ ] Guild operations work
- [ ] Warehouse access works
- [ ] Save/load works correctly
@@ -131,9 +142,15 @@ After cherry-picking, verify:
### 1. Test Coverage
**Current state:** 7.5% coverage on core channelserver (12,351 lines of code)
**Current state:** ~25% coverage on core channelserver (improved from 7.5%)
**Recommendations:**
**Progress:**
- Expanded channelserver coverage: 7.5% 12% 16% 20% 25%
- pascalstring coverage at 100%
- Added PacketID and core packet tests
- Added unit tests for cherry-pick impacted handlers
**Remaining work:**
- Add tests for packet handlers - 400+ handlers with minimal coverage
- Focus on critical files:
- `server/channelserver/handlers_quest.go`
@@ -213,7 +230,12 @@ Files exceeding maintainability guidelines:
### 6. Enhance CI/CD Pipeline
**Current gaps:**
**Progress:**
- ✅ Added gofmt checks to test workflow
- ✅ Added golangci-lint checks to test workflow
- ✅ Added release automation workflow
**Remaining gaps:**
- No code coverage threshold enforcement
- No security scanning
- No database migration testing
@@ -307,9 +329,9 @@ go tool cover -html=coverage.out -o coverage.html
| Metric | Current | Target |
|--------|---------|--------|
| Test coverage (channelserver) | 7.5% | 40%+ |
| Test coverage (overall) | 21.4% | 50%+ |
| Panic/Fatal calls | 61 | 0 (in handlers) |
| Test coverage (channelserver) | ~25% | 40%+ |
| Test coverage (overall) | ~35% | 50%+ |
| Panic/Fatal calls | 3 (tests/lib only) | 0 (in handlers) |
| Ignored errors | ~20 | 0 |
| TODO/FIXME comments | 14 | 0 |
| Outdated dependencies | 4+ | 0 |
@@ -339,8 +361,9 @@ The following milestones are organized for the upcoming 9.3.0 release.
- [ ] Document security implications of `DisableTokenCheck` option
**Graceful Error Handling**
- [ ] Replace 9 `panic()` calls in `handlers_guild_scout.go` with proper error returns
- [ ] Replace `panic()` in `handlers_tower.go:43` (GetOwnTowerLevelV3) with stub response
- [x] Replace 9 `panic()` calls in `handlers_guild_scout.go` with proper error returns
- [x] Replace `panic()` in `handlers_tower.go:43` (GetOwnTowerLevelV3) with stub response
- [x] Convert all handler panics to recoverable errors (51 panics removed)
- [ ] Convert fatal errors to recoverable errors where possible
**Database Connection Resilience**
@@ -463,6 +486,7 @@ The following milestones are organized for the upcoming 9.3.0 release.
### Milestone 6: Multi-Version Support
**Client Version Handling**
- [x] Add RealClientMode infrastructure for multi-version support (279d8b4)
- [ ] Audit handlers for missing client version checks
- [ ] Document version-specific packet format differences
- [ ] Create version compatibility matrix
@@ -529,9 +553,9 @@ Before 9.3.0 release:
| Metric | Current | 9.3.0 Target |
|--------|---------|--------------|
| Test coverage (channelserver) | 7.5% | 40%+ |
| Test coverage (overall) | 21.4% | 50%+ |
| Panic/Fatal calls | 61 | <10 (critical paths only) |
| Test coverage (channelserver) | ~25% | 40%+ |
| Test coverage (overall) | ~35% | 50%+ |
| Panic/Fatal calls | 3 (tests/lib only) | <10 (critical paths only) |
| Ignored errors | ~20 | 0 |
| TODO/FIXME comments | 18 | <5 |
| Outdated dependencies | 4+ | 0 |
@@ -542,4 +566,4 @@ Before 9.3.0 release:
---
*Generated: 2026-02-01*
*Updated: 2026-02-01 - Added release milestones*
*Updated: 2026-02-02 - Marked completed cherry-picks, updated test coverage metrics, panic cleanup complete*

View File

@@ -140,22 +140,22 @@ func handleMsgSysLogin(s *Session, p mhfpacket.MHFPacket) {
_, err := s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID)
if err != nil {
panic(err)
s.logger.Error("failed to update server player count", zap.Error(err))
}
_, err = s.server.db.Exec("UPDATE sign_sessions SET server_id=$1, char_id=$2 WHERE token=$3", s.server.ID, s.charID, s.token)
if err != nil {
panic(err)
s.logger.Error("failed to update sign session", zap.Error(err), zap.Uint32("charID", s.charID))
}
_, err = s.server.db.Exec("UPDATE characters SET last_login=$1 WHERE id=$2", TimeAdjusted().Unix(), s.charID)
if err != nil {
panic(err)
s.logger.Error("failed to update character last login", zap.Error(err), zap.Uint32("charID", s.charID))
}
_, err = s.server.db.Exec("UPDATE users u SET last_character=$1 WHERE u.id=(SELECT c.user_id FROM characters c WHERE c.id=$1)", s.charID)
if err != nil {
panic(err)
s.logger.Error("failed to update user last character", zap.Error(err), zap.Uint32("charID", s.charID))
}
doAckSimpleSucceed(s, pkt.AckHandle, bf.Data())
@@ -199,12 +199,12 @@ func logoutPlayer(s *Session) {
_, err := s.server.db.Exec("UPDATE sign_sessions SET server_id=NULL, char_id=NULL WHERE token=$1", s.token)
if err != nil {
panic(err)
s.logger.Error("failed to clear sign session on logout", zap.Error(err))
}
_, err = s.server.db.Exec("UPDATE servers SET current_players=$1 WHERE server_id=$2", len(s.server.sessions), s.server.ID)
if err != nil {
panic(err)
s.logger.Error("failed to update server player count on logout", zap.Error(err))
}
var timePlayed int
@@ -282,7 +282,9 @@ func handleMsgSysIssueLogkey(s *Session, p mhfpacket.MHFPacket) {
logKey := make([]byte, 16)
_, err := rand.Read(logKey)
if err != nil {
panic(err)
s.logger.Error("failed to generate log key", zap.Error(err))
doAckBufFail(s, pkt.AckHandle, nil)
return
}
// TODO(Andoryuuta): In the offical client, the log key index is off by one,
@@ -1770,7 +1772,9 @@ func handleMsgMhfUpdateEquipSkinHist(s *Session, p mhfpacket.MHFPacket) {
dumpSaveData(s, data, "skinhist")
_, err = s.server.db.Exec("UPDATE characters SET skin_hist=$1 WHERE id=$2", data, s.charID)
if err != nil {
panic(err)
s.logger.Error("failed to update skin history", zap.Error(err), zap.Uint32("charID", s.charID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
doAckSimpleSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
}

View File

@@ -87,7 +87,9 @@ func handleMsgMhfGetCafeDuration(s *Session, p mhfpacket.MHFPacket) {
var cafeTime uint32
err = s.server.db.QueryRow("SELECT cafe_time FROM characters WHERE id = $1", s.charID).Scan(&cafeTime)
if err != nil {
panic(err)
s.logger.Error("failed to get cafe time", zap.Error(err), zap.Uint32("charID", s.charID))
doAckBufFail(s, pkt.AckHandle, nil)
return
}
if mhfcourse.CourseExists(30, s.courses) {
cafeTime = uint32(TimeAdjusted().Unix()) - uint32(s.sessionStart) + cafeTime

View File

@@ -61,7 +61,9 @@ func handleMsgMhfListMember(s *Session, p mhfpacket.MHFPacket) {
resp.WriteUint32(0) // Blacklist count
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
s.logger.Error("failed to get blocked list", zap.Error(err), zap.Uint32("charID", s.charID))
doAckBufSucceed(s, pkt.AckHandle, resp.Data())
return
}
cids := stringsupport.CSVElems(csv)
for _, cid := range cids {
@@ -86,7 +88,9 @@ func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) {
if pkt.Blacklist {
err := s.server.db.QueryRow("SELECT blocked FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
s.logger.Error("failed to get blocked list for operation", zap.Error(err), zap.Uint32("charID", s.charID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
if pkt.Operation {
csv = stringsupport.CSVRemove(csv, int(pkt.CharID))
@@ -97,7 +101,9 @@ func handleMsgMhfOprMember(s *Session, p mhfpacket.MHFPacket) {
} else { // Friendlist
err := s.server.db.QueryRow("SELECT friends FROM characters WHERE id=$1", s.charID).Scan(&csv)
if err != nil {
panic(err)
s.logger.Error("failed to get friends list for operation", zap.Error(err), zap.Uint32("charID", s.charID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
if pkt.Operation {
csv = stringsupport.CSVRemove(csv, int(pkt.CharID))

View File

@@ -345,7 +345,9 @@ func handleMsgMhfGetPaperData(s *Session, p mhfpacket.MHFPacket) {
s.logger.Info("GET_PAPER request for unknown type")
}
if err != nil {
panic(err)
s.logger.Error("failed to decode paper hex data", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
}

View File

@@ -453,10 +453,6 @@ func CreateGuild(s *Session, guildName string) (int32, error) {
return 0, err
}
if err != nil {
panic(err)
}
guildResult, err := transaction.Query(
"INSERT INTO guilds (name, leader_id) VALUES ($1, $2) RETURNING id",
guildName, s.charID,
@@ -745,7 +741,7 @@ func handleMsgMhfOperateGuild(s *Session, p mhfpacket.MHFPacket) {
s.server.db.QueryRow(`UPDATE guilds SET event_rp=event_rp-$1 WHERE id=$2 RETURNING event_rp`, rp, guild.ID).Scan(&balance)
bf.WriteUint32(balance)
default:
panic(fmt.Sprintf("unhandled operate guild action '%d'", pkt.Action))
s.logger.Warn("unhandled operate guild action", zap.Uint8("action", uint8(pkt.Action)))
}
if len(bf.Data()) > 0 {
@@ -1418,28 +1414,31 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) {
if guild.ID != alliance.ParentGuildID {
mems, err := GetGuildMembers(s, alliance.ParentGuildID, false)
if err != nil {
panic(err)
}
for _, m := range mems {
bf.WriteUint32(m.CharID)
s.logger.Error("failed to get parent guild members", zap.Error(err), zap.Uint32("guildID", alliance.ParentGuildID))
} else {
for _, m := range mems {
bf.WriteUint32(m.CharID)
}
}
}
if guild.ID != alliance.SubGuild1ID {
mems, err := GetGuildMembers(s, alliance.SubGuild1ID, false)
if err != nil {
panic(err)
}
for _, m := range mems {
bf.WriteUint32(m.CharID)
s.logger.Error("failed to get sub guild 1 members", zap.Error(err), zap.Uint32("guildID", alliance.SubGuild1ID))
} else {
for _, m := range mems {
bf.WriteUint32(m.CharID)
}
}
}
if guild.ID != alliance.SubGuild2ID {
mems, err := GetGuildMembers(s, alliance.SubGuild2ID, false)
if err != nil {
panic(err)
}
for _, m := range mems {
bf.WriteUint32(m.CharID)
s.logger.Error("failed to get sub guild 2 members", zap.Error(err), zap.Uint32("guildID", alliance.SubGuild2ID))
} else {
for _, m := range mems {
bf.WriteUint32(m.CharID)
}
}
}
} else {
@@ -1627,13 +1626,17 @@ func handleMsgMhfUpdateGuildIcon(s *Session, p mhfpacket.MHFPacket) {
guild, err := GetGuildInfoByID(s, pkt.GuildID)
if err != nil {
panic(err)
s.logger.Error("failed to get guild info for icon update", zap.Error(err), zap.Uint32("guildID", pkt.GuildID))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
characterInfo, err := GetCharacterGuildData(s, s.charID)
if err != nil {
panic(err)
s.logger.Error("failed to get character guild data for icon update", zap.Error(err), zap.Uint32("charID", s.charID))
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
return
}
if !characterInfo.IsSubLeader() && !characterInfo.IsLeader {
@@ -1699,7 +1702,9 @@ func handleMsgMhfGetGuildMissionList(s *Session, p mhfpacket.MHFPacket) {
decoded, err := hex.DecodeString("000694610000023E000112990023000100000200015DDD232100069462000002F30000005F000C000200000300025DDD232100069463000002EA0000005F0006000100000100015DDD23210006946400000245000000530010000200000400025DDD232100069465000002B60001129B0019000100000200015DDD232100069466000003DC0000001B0010000100000600015DDD232100069467000002DA000112A00019000100000400015DDD232100069468000002A800010DEF0032000200000200025DDD2321000694690000045500000022003C000200000600025DDD23210006946A00000080000122D90046000200000300025DDD23210006946B000001960000003B000A000100000100015DDD23210006946C0000049200000046005A000300000600035DDD23210006946D000000A4000000260018000200000600025DDD23210006946E0000017A00010DE40096000300000100035DDD23210006946F000001BE0000005E0014000200000400025DDD2355000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
if err != nil {
panic(err)
s.logger.Error("failed to decode guild mission list hex data", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
doAckBufSucceed(s, pkt.AckHandle, decoded)

View File

@@ -181,8 +181,8 @@ func handleMsgMhfOperateJoint(s *Session, p mhfpacket.MHFPacket) {
doAckSimpleFail(s, pkt.AckHandle, make([]byte, 4))
}
default:
s.logger.Warn("unhandled operate joint action", zap.Uint8("action", uint8(pkt.Action)))
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
panic(fmt.Sprintf("Unhandled operate joint action '%d'", pkt.Action))
}
}

View File

@@ -15,8 +15,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
actorCharGuildData, err := GetCharacterGuildData(s, s.charID)
if err != nil {
s.logger.Error("failed to get character guild data", zap.Error(err), zap.Uint32("charID", s.charID))
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
return
}
if actorCharGuildData == nil || !actorCharGuildData.CanRecruit() {
@@ -27,15 +28,17 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
guildInfo, err := GetGuildInfoByID(s, actorCharGuildData.GuildID)
if err != nil {
s.logger.Error("failed to get guild info", zap.Error(err), zap.Uint32("guildID", actorCharGuildData.GuildID))
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
return
}
hasApplication, err := guildInfo.HasApplicationForCharID(s, pkt.CharID)
if err != nil {
s.logger.Error("failed to check application status", zap.Error(err), zap.Uint32("charID", pkt.CharID))
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
return
}
if hasApplication {
@@ -46,15 +49,18 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
transaction, err := s.server.db.Begin()
if err != nil {
panic(err)
s.logger.Error("failed to begin transaction", zap.Error(err))
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
err = guildInfo.CreateApplication(s, pkt.CharID, GuildApplicationTypeInvited, transaction)
if err != nil {
s.logger.Error("failed to create guild application", zap.Error(err), zap.Uint32("charID", pkt.CharID))
rollbackTransaction(s, transaction)
doAckBufFail(s, pkt.AckHandle, nil)
panic(err)
return
}
mail := &Mail{
@@ -79,8 +85,9 @@ func handleMsgMhfPostGuildScout(s *Session, p mhfpacket.MHFPacket) {
err = transaction.Commit()
if err != nil {
s.logger.Error("failed to commit transaction", zap.Error(err))
doAckBufFail(s, pkt.AckHandle, nil)
panic(err)
return
}
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
@@ -92,7 +99,9 @@ func handleMsgMhfCancelGuildScout(s *Session, p mhfpacket.MHFPacket) {
guildCharData, err := GetCharacterGuildData(s, s.charID)
if err != nil {
panic(err)
s.logger.Error("failed to get character guild data", zap.Error(err), zap.Uint32("charID", s.charID))
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
return
}
if guildCharData == nil || !guildCharData.CanRecruit() {
@@ -123,7 +132,11 @@ func handleMsgMhfAnswerGuildScout(s *Session, p mhfpacket.MHFPacket) {
guild, err := GetGuildInfoByCharacterId(s, pkt.LeaderID)
if err != nil {
panic(err)
s.logger.Error("failed to get guild info by character", zap.Error(err), zap.Uint32("leaderID", pkt.LeaderID))
bf.WriteUint32(7) // Error code
bf.WriteUint32(0)
doAckBufSucceed(s, pkt.AckHandle, bf.Data())
return
}
app, err := guild.GetApplicationForCharID(s, s.charID, GuildApplicationTypeInvited)
@@ -255,7 +268,9 @@ func handleMsgMhfGetGuildScoutList(s *Session, p mhfpacket.MHFPacket) {
_, err = bf.Seek(0, io.SeekStart)
if err != nil {
panic(err)
s.logger.Error("failed to seek in byte frame", zap.Error(err))
doAckBufFail(s, pkt.AckHandle, nil)
return
}
bf.WriteUint32(count)

View File

@@ -4,6 +4,7 @@ import (
"erupe-ce/common/byteframe"
"erupe-ce/common/stringsupport"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
type TreasureHunt struct {
@@ -23,7 +24,9 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfEnumerateGuildTresure)
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
s.logger.Error("failed to get guild info", zap.Error(err), zap.Uint32("charID", s.charID))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 4))
return
}
bf := byteframe.NewByteFrame()
hunts := 0
@@ -34,7 +37,8 @@ func handleMsgMhfEnumerateGuildTresure(s *Session, p mhfpacket.MHFPacket) {
// Remove self from other hunter count
hunt.Hunters = stringsupport.CSVRemove(hunt.Hunters, int(s.charID))
if err != nil {
panic(err)
s.logger.Error("failed to scan treasure hunt row", zap.Error(err))
continue
}
if pkt.MaxHunts == 1 {
if hunt.HostID != s.charID || hunt.Acquired {
@@ -78,7 +82,9 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
huntData := byteframe.NewByteFrame()
guild, err := GetGuildInfoByCharacterId(s, s.charID)
if err != nil {
panic(err)
s.logger.Error("failed to get guild info for treasure registration", zap.Error(err), zap.Uint32("charID", s.charID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
guildCats := getGuildAirouList(s)
destination := bf.ReadUint32()
@@ -103,7 +109,9 @@ func handleMsgMhfRegistGuildTresure(s *Session, p mhfpacket.MHFPacket) {
_, err = s.server.db.Exec("INSERT INTO guild_hunts (guild_id, host_id, destination, level, return, hunt_data, cats_used) VALUES ($1, $2, $3, $4, $5, $6, $7)",
guild.ID, s.charID, destination, level, TimeAdjusted().Unix(), huntData.Data(), catsUsed)
if err != nil {
panic(err)
s.logger.Error("failed to insert guild hunt", zap.Error(err), zap.Uint32("guildID", guild.ID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
@@ -112,7 +120,9 @@ func handleMsgMhfAcquireGuildTresure(s *Session, p mhfpacket.MHFPacket) {
pkt := p.(*mhfpacket.MsgMhfAcquireGuildTresure)
_, err := s.server.db.Exec("UPDATE guild_hunts SET acquired=true WHERE id=$1", pkt.HuntID)
if err != nil {
panic(err)
s.logger.Error("failed to acquire guild treasure", zap.Error(err), zap.Uint32("huntID", pkt.HuntID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))
}
@@ -145,12 +155,16 @@ func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
// Register to selected hunt
err := s.server.db.QueryRow("SELECT hunters FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
if err != nil {
panic(err)
s.logger.Error("failed to get hunters for guild hunt", zap.Error(err), zap.Uint32("huntID", pkt.HuntID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
csv = stringsupport.CSVAdd(csv, int(s.charID))
_, err = s.server.db.Exec("UPDATE guild_hunts SET hunters=$1 WHERE id=$2", csv, pkt.HuntID)
if err != nil {
panic(err)
s.logger.Error("failed to update hunters for guild hunt", zap.Error(err), zap.Uint32("huntID", pkt.HuntID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
}
} else if pkt.State == 1 { // Collected by hunter
@@ -158,12 +172,16 @@ func handleMsgMhfOperateGuildTresureReport(s *Session, p mhfpacket.MHFPacket) {
} else if pkt.State == 2 { // Claim treasure
err := s.server.db.QueryRow("SELECT treasure FROM guild_hunts WHERE id=$1", pkt.HuntID).Scan(&csv)
if err != nil {
panic(err)
s.logger.Error("failed to get treasure for guild hunt", zap.Error(err), zap.Uint32("huntID", pkt.HuntID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
csv = stringsupport.CSVAdd(csv, int(s.charID))
_, err = s.server.db.Exec("UPDATE guild_hunts SET treasure=$1 WHERE id=$2", csv, pkt.HuntID)
if err != nil {
panic(err)
s.logger.Error("failed to update treasure for guild hunt", zap.Error(err), zap.Uint32("huntID", pkt.HuntID))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))

View File

@@ -258,15 +258,17 @@ func handleMsgMhfReadMail(s *Session, p mhfpacket.MHFPacket) {
mailId := s.mailList[pkt.AccIndex]
if mailId == 0 {
s.logger.Warn("attempting to read mail that doesn't exist in session map", zap.Uint8("accIndex", pkt.AccIndex))
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic("attempting to read mail that doesn't exist in session map")
return
}
mail, err := GetMailByID(s, mailId)
if err != nil {
s.logger.Error("failed to get mail by ID", zap.Error(err), zap.Int("mailID", mailId))
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
return
}
_ = mail.MarkRead(s)
@@ -285,8 +287,9 @@ func handleMsgMhfListMail(s *Session, p mhfpacket.MHFPacket) {
mail, err := GetMailListForCharacter(s, s.charID)
if err != nil {
s.logger.Error("failed to get mail list", zap.Error(err), zap.Uint32("charID", s.charID))
doAckBufFail(s, pkt.AckHandle, make([]byte, 4))
panic(err)
return
}
if s.mailList == nil {
@@ -354,7 +357,9 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
mail, err := GetMailByID(s, s.mailList[pkt.AccIndex])
if err != nil {
panic(err)
s.logger.Error("failed to get mail for operation", zap.Error(err), zap.Uint8("accIndex", pkt.AccIndex))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
switch pkt.Operation {
@@ -369,7 +374,9 @@ func handleMsgMhfOprtMail(s *Session, p mhfpacket.MHFPacket) {
}
if err != nil {
panic(err)
s.logger.Error("failed to perform mail operation", zap.Error(err), zap.Uint8("operation", uint8(pkt.Operation)))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
doAckSimpleSucceed(s, pkt.AckHandle, make([]byte, 4))

View File

@@ -112,7 +112,9 @@ func handleMsgSysGetFile(s *Session, p mhfpacket.MHFPacket) {
if _, err := os.Stat(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin")); err == nil {
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "quest_override.bin"))
if err != nil {
panic(err)
s.logger.Error("failed to read quest_override.bin", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0))
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
} else {

View File

@@ -94,7 +94,9 @@ func handleMsgMhfGetRengokuBinary(s *Session, p mhfpacket.MHFPacket) {
// a (massively out of date) version resides in the game's /dat/ folder or up to date can be pulled from packets
data, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, "rengoku_data.bin"))
if err != nil {
panic(err)
s.logger.Error("failed to read rengoku_data.bin", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, make([]byte, 0))
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
}

View File

@@ -287,7 +287,9 @@ func handleMsgSysBackStage(s *Session, p mhfpacket.MHFPacket) {
s.stageMoveStack.Unlock()
backStage, err := s.stageMoveStack.Pop()
if err != nil {
panic(err)
s.logger.Error("failed to pop stage from move stack", zap.Error(err))
doAckSimpleFail(s, pkt.AckHandle, nil)
return
}
if _, exists := s.stage.reservedClientSlots[s.charID]; exists {

View File

@@ -3,6 +3,7 @@ package channelserver
import (
"encoding/hex"
"erupe-ce/network/mhfpacket"
"go.uber.org/zap"
)
func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
@@ -40,7 +41,9 @@ func handleMsgMhfGetTowerInfo(s *Session, p mhfpacket.MHFPacket) {
//data, err = hex.DecodeString("0A218EAD000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000001C0000000500050000000000020000000000000000000000000000000000030003000000000003000500050000000300030003000300030003000200030001000300020002000300010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
case mhfpacket.TowerInfoTypeGetOwnTowerLevelV3:
panic("No known response values for GetOwnTowerLevelV3")
// TODO: No known response values for GetOwnTowerLevelV3
stubGetNoResults(s, pkt.AckHandle)
return
case mhfpacket.TowerInfoTypeTowerTouhaHistory:
data, err = hex.DecodeString("0A218EAD0000000000000000000000010000000000000000000000000000000000000000")
case mhfpacket.TowerInfoTypeUnk5:
@@ -72,7 +75,9 @@ func handleMsgMhfGetTenrouirai(s *Session, p mhfpacket.MHFPacket) {
s.logger.Info("GET_TENROUIRAI request for unknown type")
}
if err != nil {
panic(err)
s.logger.Error("failed to decode tenrouirai hex data", zap.Error(err))
doAckBufSucceed(s, pkt.AckHandle, []byte{0x00, 0x00, 0x00, 0x00})
return
}
doAckBufSucceed(s, pkt.AckHandle, data)
}

View File

@@ -77,7 +77,8 @@ func (s *Server) acceptClients() {
if shutdown {
break
} else {
panic(err)
s.logger.Error("failed to accept connection", zap.Error(err))
continue
}
}