Files
bd2/go/internal/server/feature/feature.go
T
Flechazo d8771caef2 feat(all): add hunting and events, commerce and rewards, binary calendars, GameData caching and login diagnostics
Implement hunting AP and progress, event tasks, exchanges, games, stories and actions; connect battle and reward handling.

Add CashShop plugin and installation, server purchases and entitlements, recharge rules and plugin version metadata.

Distribute binary calendars independently; cache GameData in memory and fix single-connection query deadlocks.

Improve automatic login and recovery diagnostics; document configuration and release flows.
2026-10-05 11:50:31 +08:00

94 lines
3.3 KiB
Go

// Package feature contains deliberately small, stateless protocol handlers.
//
// These endpoints were observed to return an empty protobuf for a new local
// account. Keeping that fact here (rather than treating every unknown route
// as successful) lets the session dispatcher remain fail-closed.
package feature
import (
"errors"
"fmt"
"bd2server/internal/server/wire"
)
// ErrInvalidRequest means a known endpoint was sent a malformed request.
var ErrInvalidRequest = errors.New("feature: invalid protobuf request")
// emptyResponses is the stateless subset of the new-player batch whose
// successful response protobuf has zero bytes. Stateful routes leave this map
// as soon as their owning domain persists them. Packet codes are protocol
// values, not ordinals derived from the request or response order.
var emptyResponses = map[string]int{
"/AvatarShopWishListInfo": 470,
"/CafeteriaInfo": 351,
"/CashBonusInfo": 588,
"/CharPartnerInfo": 65,
"/CommunityRewardInfo": 289,
"/DailyStoryInfo": 538,
"/DatingInfo": 361,
"/DispatchInfo": 0,
"/EquipPresetInfo": 253,
"/EvilCastleDailyRewardState": 441,
"/EvilCastleTowerInfo": 200,
"/FieldTrapInfo": 171,
"/FishingCollectionInfo": 465,
"/FishingTrapInfo": 457,
"/FriendInfoList": 204,
"/GuildInitInfo": 332,
"/IdCardPresetInfo": 450,
"/LifeUserInfo": 594,
"/MyLikeInfo": 218,
"/MyRoomItemInfo": 232,
"/PersonalInfo": 199,
"/QuestMaxClearInfo": 137,
"/SpineInteractionAchievementInfo": 532,
"/TotalWarRewardState": 252,
"/TutorialInfo": 101,
}
// Handle recognizes only audited, stateless empty-response endpoints. The
// request must be a structurally valid protobuf with its normal nonzero
// request sequence in field 1. Unknown paths return ok=false and no response.
// proto is nil on success: protobuf's canonical empty-message encoding is zero
// bytes.
func Handle(path string, request []byte) (packetCode int, proto []byte, ok bool, err error) {
packetCode, ok = emptyResponses[path]
if !ok {
packetCode, proto, ok = initialResponse(path)
}
if !ok {
packetCode, ok = standaloneDefaults[path]
}
if !ok {
packetCode, ok = commandSuccess[path]
if !ok {
return 0, nil, false, nil
}
}
sequence, present, walkErr := wire.Varint(request, 1)
if walkErr != nil || !present || sequence == 0 {
if walkErr != nil {
return 0, nil, true, fmt.Errorf("%w: %s: %v", ErrInvalidRequest, path, walkErr)
}
return 0, nil, true, fmt.Errorf("%w: %s has no request sequence", ErrInvalidRequest, path)
}
return packetCode, proto, true, nil
}
// Service adapts this registry to session.Handler without importing session.
type Service struct{}
func (Service) Handle(path string, request []byte) (int, []byte, bool, error) {
return Handle(path, request)
}
// EmptyPacketCodes returns a detached copy for diagnostics and protocol tests.
func EmptyPacketCodes() map[string]int {
result := make(map[string]int, len(emptyResponses))
for path, code := range emptyResponses {
result[path] = code
}
return result
}