Files
Erupe/server/entranceserver/make_resp.go
Houmgaor 82b967b715 refactor: replace raw SQL with repository interfaces in entranceserver and API server
Extract all direct database calls from entranceserver (2 calls) and
API server (17 calls) into typed repository interfaces with PostgreSQL
implementations, matching the pattern established in signserver and
channelserver.

Entranceserver: EntranceServerRepo, EntranceSessionRepo
API server: APIUserRepo, APICharacterRepo, APISessionRepo

Also fix the 3 remaining fmt.Sprintf calls inside logger invocations
in handlers_commands.go and handlers_stage.go, replacing them with
structured zap fields.

Unskip 5 TestNewAuthData* tests that previously required a real
database — they now run with mock repos.
2026-02-22 17:04:58 +01:00

182 lines
4.9 KiB
Go

package entranceserver
import (
"encoding/binary"
"encoding/hex"
"erupe-ce/common/stringsupport"
cfg "erupe-ce/config"
"net"
"erupe-ce/common/byteframe"
"erupe-ce/common/gametime"
"go.uber.org/zap"
)
func encodeServerInfo(config *cfg.Config, s *Server, local bool) []byte {
serverInfos := config.Entrance.Entries
bf := byteframe.NewByteFrame()
for serverIdx, si := range serverInfos {
// Prevent MezFes Worlds displaying on Z1
if config.RealClientMode <= cfg.Z1 {
if si.Type == 6 {
continue
}
}
if config.RealClientMode <= cfg.G6 {
if si.Type == 5 {
continue
}
}
if si.IP == "" {
si.IP = config.Host
}
if local {
bf.WriteUint32(0x0100007F) // 127.0.0.1
} else {
bf.WriteUint32(binary.LittleEndian.Uint32(net.ParseIP(si.IP).To4()))
}
bf.WriteUint16(uint16(serverIdx | 16))
bf.WriteUint16(0)
bf.WriteUint16(uint16(len(si.Channels)))
bf.WriteUint8(si.Type)
bf.WriteUint8(uint8(((gametime.Adjusted().Unix() / 86400) + int64(serverIdx)) % 3))
if s.erupeConfig.RealClientMode >= cfg.G1 {
bf.WriteUint8(si.Recommended)
}
fullName := append(append(stringsupport.UTF8ToSJIS(si.Name), []byte{0x00}...), stringsupport.UTF8ToSJIS(si.Description)...)
if s.erupeConfig.RealClientMode >= cfg.G1 && s.erupeConfig.RealClientMode <= cfg.G5 {
bf.WriteUint8(uint8(len(fullName)))
bf.WriteBytes(fullName)
} else {
if s.erupeConfig.RealClientMode >= cfg.G51 {
bf.WriteUint8(0) // Ignored
}
bf.WriteBytes(stringsupport.PaddedString(string(fullName), 65, false))
}
if s.erupeConfig.RealClientMode >= cfg.GG {
bf.WriteUint32(si.AllowedClientFlags)
}
for channelIdx, ci := range si.Channels {
sid := (serverIdx<<8 | 4096) + (channelIdx | 16)
if config.DebugOptions.ProxyPort != 0 {
bf.WriteUint16(config.DebugOptions.ProxyPort)
} else {
bf.WriteUint16(ci.Port)
}
bf.WriteUint16(uint16(channelIdx | 16))
bf.WriteUint16(ci.MaxPlayers)
var currentPlayers uint16
if s.serverRepo != nil {
currentPlayers, _ = s.serverRepo.GetCurrentPlayers(sid)
}
bf.WriteUint16(currentPlayers)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(0)
bf.WriteUint16(319) // Unk
bf.WriteUint16(254 - currentPlayers) // Unk
bf.WriteUint16(255 - currentPlayers) // Unk
bf.WriteUint16(12345)
}
}
bf.WriteUint32(uint32(gametime.Adjusted().Unix()))
// ClanMemberLimits requires at least 1 element with 2 columns to avoid index out of range panics
// Use default value (60) if array is empty or last row is too small
var maxClanMembers uint8 = 60
if len(s.erupeConfig.GameplayOptions.ClanMemberLimits) > 0 {
lastRow := s.erupeConfig.GameplayOptions.ClanMemberLimits[len(s.erupeConfig.GameplayOptions.ClanMemberLimits)-1]
if len(lastRow) > 1 {
maxClanMembers = lastRow[1]
}
}
bf.WriteUint32(uint32(maxClanMembers))
return bf.Data()
}
func makeHeader(data []byte, respType string, entryCount uint16, key byte) []byte {
bf := byteframe.NewByteFrame()
bf.WriteBytes([]byte(respType))
bf.WriteUint16(entryCount)
bf.WriteUint16(uint16(len(data)))
if len(data) > 0 {
bf.WriteUint32(CalcSum32(data))
bf.WriteBytes(data)
}
dataToEncrypt := bf.Data()
bf = byteframe.NewByteFrame()
bf.WriteUint8(key)
bf.WriteBytes(EncryptBin8(dataToEncrypt, key))
return bf.Data()
}
func makeSv2Resp(config *cfg.Config, s *Server, local bool) []byte {
serverInfos := config.Entrance.Entries
// Decrease by the number of MezFes Worlds
var mf int
if config.RealClientMode <= cfg.Z1 {
for _, si := range serverInfos {
if si.Type == 6 {
mf++
}
}
}
// and Return Worlds
var ret int
if config.RealClientMode <= cfg.G6 {
for _, si := range serverInfos {
if si.Type == 5 {
ret++
}
}
}
rawServerData := encodeServerInfo(config, s, local)
if s.erupeConfig.DebugOptions.LogOutboundMessages {
s.logger.Debug("Outbound SV2 response", zap.Int("bytes", len(rawServerData)), zap.String("data", hex.Dump(rawServerData)))
}
respType := "SV2"
if config.RealClientMode <= cfg.G32 {
respType = "SVR"
}
bf := byteframe.NewByteFrame()
bf.WriteBytes(makeHeader(rawServerData, respType, uint16(len(serverInfos)-(mf+ret)), 0x00))
return bf.Data()
}
func makeUsrResp(pkt []byte, s *Server) []byte {
bf := byteframe.NewByteFrameFromBytes(pkt)
_ = bf.ReadUint32() // ALL+
_ = bf.ReadUint8() // 0x00
userEntries := bf.ReadUint16()
resp := byteframe.NewByteFrame()
for i := 0; i < int(userEntries); i++ {
cid := bf.ReadUint32()
var sid uint16
if s.sessionRepo != nil {
sid, _ = s.sessionRepo.GetServerIDForCharacter(cid)
}
resp.WriteUint16(sid)
resp.WriteUint16(0)
}
if s.erupeConfig.DebugOptions.LogOutboundMessages {
s.logger.Debug("Outbound USR response", zap.Int("bytes", len(resp.Data())), zap.String("data", hex.Dump(resp.Data())))
}
return makeHeader(resp.Data(), "USR", userEntries, 0x00)
}