Files
Erupe/server/signserver/dsgn_resp.go
Houmgaor b3f75232a3 refactor(signserver): replace raw SQL with repository interfaces
Extract all direct database access into three repository interfaces
(SignUserRepo, SignCharacterRepo, SignSessionRepo) matching the
pattern established in channelserver. This surfaces 9 previously
silenced errors that are now logged with structured context, and
makes the sign server testable with mock repos instead of go-sqlmock.

Security fix: GetFriends now uses parameterized ANY($1) queries
instead of string-concatenated WHERE clauses (SQL injection vector).
2026-02-22 16:30:24 +01:00

405 lines
17 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package signserver
import (
"erupe-ce/common/byteframe"
ps "erupe-ce/common/pascalstring"
"erupe-ce/common/stringsupport"
cfg "erupe-ce/config"
"erupe-ce/common/gametime"
"fmt"
"strings"
"time"
"go.uber.org/zap"
)
func (s *Session) makeSignResponse(uid uint32) []byte {
// Get the characters from the DB.
chars, err := s.server.getCharactersForUser(uid)
if len(chars) == 0 && uid != 0 {
err = s.server.newUserChara(uid)
if err == nil {
chars, err = s.server.getCharactersForUser(uid)
}
}
if err != nil {
s.logger.Warn("Error getting characters from DB", zap.Error(err))
}
bf := byteframe.NewByteFrame()
var tokenID uint32
var sessToken string
if uid == 0 && s.psn != "" {
tokenID, sessToken, err = s.server.registerPsnToken(s.psn)
} else {
tokenID, sessToken, err = s.server.registerUidToken(uid)
}
if err != nil {
bf.WriteUint8(uint8(SIGN_EABORT))
return bf.Data()
}
if s.client == PS3 && (s.server.erupeConfig.PatchServerFile == "" || s.server.erupeConfig.PatchServerManifest == "") {
bf.WriteUint8(uint8(SIGN_EABORT))
return bf.Data()
}
bf.WriteUint8(uint8(SIGN_SUCCESS))
bf.WriteUint8(2) // patch server count
bf.WriteUint8(1) // entrance server count
bf.WriteUint8(uint8(len(chars)))
bf.WriteUint32(tokenID)
bf.WriteBytes([]byte(sessToken))
bf.WriteUint32(uint32(gametime.Adjusted().Unix()))
if s.client == PS3 {
ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerManifest), false)
ps.Uint8(bf, fmt.Sprintf("%s/ps3", s.server.erupeConfig.PatchServerFile), false)
} else {
ps.Uint8(bf, s.server.erupeConfig.PatchServerManifest, false)
ps.Uint8(bf, s.server.erupeConfig.PatchServerFile, false)
}
if strings.Split(s.rawConn.RemoteAddr().String(), ":")[0] == "127.0.0.1" {
ps.Uint8(bf, fmt.Sprintf("127.0.0.1:%d", s.server.erupeConfig.Entrance.Port), false)
} else {
ps.Uint8(bf, fmt.Sprintf("%s:%d", s.server.erupeConfig.Host, s.server.erupeConfig.Entrance.Port), false)
}
lastPlayed := uint32(0)
for _, char := range chars {
if lastPlayed == 0 {
lastPlayed = char.ID
}
bf.WriteUint32(char.ID)
if s.server.erupeConfig.DebugOptions.MaxLauncherHR {
bf.WriteUint16(999)
} else {
bf.WriteUint16(char.HR)
}
bf.WriteUint16(char.WeaponType) // Weapon, 0-13.
bf.WriteUint32(char.LastLogin) // Last login date, unix timestamp in seconds.
bf.WriteBool(char.IsFemale) // Sex, 0=male, 1=female.
bf.WriteBool(char.IsNewCharacter) // Is new character, 1 replaces character name with ?????.
bf.WriteUint8(0) // Old GR
bf.WriteBool(true) // Use uint16 GR, no reason not to
bf.WriteBytes(stringsupport.PaddedString(char.Name, 16, true)) // Character name
bf.WriteBytes(stringsupport.PaddedString(char.UnkDescString, 32, false)) // unk str
if s.server.erupeConfig.RealClientMode >= cfg.G7 {
bf.WriteUint16(char.GR)
bf.WriteUint8(0) // Unk
bf.WriteUint8(0) // Unk
}
}
friends := s.server.getFriendsForCharacters(chars)
if len(friends) == 0 {
bf.WriteUint8(0)
} else {
if len(friends) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(friends)))
} else {
bf.WriteUint8(uint8(len(friends)))
}
for _, friend := range friends {
bf.WriteUint32(friend.CID)
bf.WriteUint32(friend.ID)
ps.Uint8(bf, friend.Name, true)
}
}
guildmates := s.server.getGuildmatesForCharacters(chars)
if len(guildmates) == 0 {
bf.WriteUint8(0)
} else {
if len(guildmates) > 255 {
bf.WriteUint8(255)
bf.WriteUint16(uint16(len(guildmates)))
} else {
bf.WriteUint8(uint8(len(guildmates)))
}
for _, guildmate := range guildmates {
bf.WriteUint32(guildmate.CID)
bf.WriteUint32(guildmate.ID)
ps.Uint8(bf, guildmate.Name, true)
}
}
if s.server.erupeConfig.HideLoginNotice {
bf.WriteBool(false)
} else {
bf.WriteBool(true)
bf.WriteUint8(0)
bf.WriteUint8(0)
ps.Uint16(bf, strings.Join(s.server.erupeConfig.LoginNotices[:], "<PAGE>"), true)
}
bf.WriteUint32(s.server.getLastCID(uid))
bf.WriteUint32(s.server.getUserRights(uid))
namNGWords := []string{}
msgNGWords := []string{}
filters := byteframe.NewByteFrame()
filters.SetLE()
filters.WriteNullTerminatedBytes([]byte("smc"))
smc := byteframe.NewByteFrame()
smc.SetLE()
smcData := []struct {
charGroup [][]rune
}{
{[][]rune{{'='}, {''}}},
{[][]rune{{')'}, {''}}},
{[][]rune{{'('}, {''}}},
{[][]rune{{'!'}, {''}}},
{[][]rune{{'/'}, {''}}},
{[][]rune{{'+'}, {''}}},
{[][]rune{{'&'}, {''}}},
{[][]rune{{'ぼ'}, {'ボ'}, {'ホ', '゙'}, {'ほ', '゙'}, {'ホ', '゙'}, {'ほ', '゛'}, {'ホ', '゛'}, {'ホ', '゛'}}},
{[][]rune{{'べ'}, {'ベ'}, {'ヘ', '゙'}, {'へ', '゙'}, {'ヘ', '゙'}, {'へ', '゛'}, {'ヘ', '゛'}, {'ヘ', '゛'}}},
{[][]rune{{'で'}, {'デ'}, {'テ', '゙'}, {'て', '゙'}, {'テ', '゙'}, {'て', '゛'}, {'テ', '゛'}, {'テ', '゛'}, {'〒', '゛'}, {'〒', '゙'}, {'乙', '゙'}, {'乙', '゛'}}},
{[][]rune{{'び'}, {'ビ'}, {'ヒ', '゙'}, {'ひ', '゙'}, {'ヒ', '゙'}, {'ひ', '゛'}, {'ヒ', '゛'}, {'ヒ', '゛'}}},
{[][]rune{{'ど'}, {'ド'}, {'ト', '゙'}, {'と', '゙'}, {'ト', '゙'}, {'と', '゛'}, {'ト', '゛'}, {'ト', '゛'}, {'┣', '゙'}, {'┣', '゛'}, {'├', '゙'}, {'├', '゛'}}},
{[][]rune{{'ば'}, {'バ'}, {'ハ', '゙'}, {'は', '゙'}, {'ハ', '゙'}, {'八', '゙'}, {'は', '゛'}, {'ハ', '゛'}, {'ハ', '゛'}, {'八', '゛'}}},
{[][]rune{{'つ', '゙'}, {'ヅ'}, {'ツ', '゙'}, {'つ', '゛'}, {'ツ', '゛'}, {'ツ', '゙'}, {'ツ', '゛'}, {'づ'}, {'っ', '゙'}, {'ッ', '゙'}, {'ッ', '゙'}, {'っ', '゛'}, {'ッ', '゛'}, {'ッ', '゛'}}},
{[][]rune{{'ぶ'}, {'ブ'}, {'フ', '゙'}, {'ヴ'}, {'ウ', '゙'}, {'う', '゛'}, {'う', '゙'}, {'ウ', '゙'}, {'ゥ', '゙'}, {'ぅ', '゙'}, {'ふ', '゙'}, {'フ', '゙'}, {'フ', '゛'}}},
{[][]rune{{'ぢ'}, {'ヂ'}, {'チ', '゙'}, {'ち', '゙'}, {'チ', '゙'}, {'ち', '゛'}, {'チ', '゛'}, {'チ', '゛'}, {'千', '゛'}, {'千', '゙'}}},
{[][]rune{{'だ'}, {'ダ'}, {'タ', '゙'}, {'た', '゙'}, {'タ', '゙'}, {'夕', '゙'}, {'た', '゛'}, {'タ', '゛'}, {'タ', '゛'}, {'夕', '゛'}}},
{[][]rune{{'ぞ'}, {'ゾ'}, {'ソ', '゙'}, {'そ', '゙'}, {'ソ', '゙'}, {'そ', '゛'}, {'ソ', '゛'}, {'ソ', '゛'}, {'ン', '゙'}, {'ン', '゛'}, {'ン', '゛'}, {'ン', '゙'}, {'リ', '゙'}, {'リ', '゙'}, {'リ', '゛'}, {'リ', '゛'}}},
{[][]rune{{'ぜ'}, {'セ', '゙'}, {'せ', '゙'}, {'セ', '゙'}, {'せ', '゛'}, {'セ', '゛'}, {'セ', '゛'}, {'ゼ'}}},
{[][]rune{{'ず'}, {'ズ'}, {'ス', '゙'}, {'す', '゙'}, {'ス', '゙'}, {'す', '゛'}, {'ス', '゛'}, {'ス', '゛'}}},
{[][]rune{{'じ'}, {'ジ'}, {'シ', '゙'}, {'し', '゙'}, {'シ', '゙'}, {'し', '゛'}, {'シ', '゛'}, {'シ', '゛'}}},
{[][]rune{{'ざ'}, {'ザ'}, {'サ', '゙'}, {'さ', '゙'}, {'サ', '゙'}, {'さ', '゛'}, {'サ', '゛'}, {'サ', '゛'}}},
{[][]rune{{'ご'}, {'ゴ'}, {'コ', '゙'}, {'こ', '゙'}, {'コ', '゙'}, {'こ', '゛'}, {'コ', '゛'}, {'コ', '゛'}}},
{[][]rune{{'げ'}, {'ゲ'}, {'ケ', '゙'}, {'け', '゙'}, {'ケ', '゙'}, {'け', '゛'}, {'ケ', '゛'}, {'ケ', '゛'}, {'ヶ', '゙'}, {'ヶ', '゛'}}},
{[][]rune{{'ぐ'}, {'グ'}, {'ク', '゙'}, {'く', '゙'}, {'ク', '゙'}, {'く', '゛'}, {'ク', '゛'}, {'ク', '゛'}}},
{[][]rune{{'ぎ'}, {'ギ'}, {'キ', '゙'}, {'き', '゙'}, {'キ', '゙'}, {'き', '゛'}, {'キ', '゛'}, {'キ', '゛'}}},
{[][]rune{{'が'}, {'ガ'}, {'カ', '゙'}, {'ヵ', '゙'}, {'カ', '゙'}, {'か', '゙'}, {'力', '゙'}, {'ヵ', '゛'}, {'カ', '゛'}, {'か', '゛'}, {'力', '゛'}, {'カ', '゛'}}},
{[][]rune{{'を'}, {'ヲ'}, {'ヲ'}}},
{[][]rune{{'わ'}, {'ワ'}, {'ワ'}, {'ヮ'}}},
{[][]rune{{'ろ'}, {'ロ'}, {'ロ'}, {'□'}, {'口'}}},
{[][]rune{{'れ'}, {'レ'}, {'レ'}}},
{[][]rune{{'る'}, {'ル'}, {'ル'}}},
{[][]rune{{'り'}, {'リ'}, {'リ'}}},
{[][]rune{{'ら'}, {'ラ'}, {'ラ'}}},
{[][]rune{{'よ'}, {'ヨ'}, {'ヨ'}, {'ョ'}, {'ょ'}, {'ョ'}}},
{[][]rune{{'ゆ'}, {'ユ'}, {'ユ'}, {'ュ'}, {'ゅ'}, {'ュ'}}},
{[][]rune{{'や'}, {'ヤ'}, {'ヤ'}, {'ャ'}, {'ゃ'}, {'ャ'}}},
{[][]rune{{'も'}, {'モ'}, {'モ'}}},
{[][]rune{{'め'}, {'メ'}, {'メ'}, {'M', 'E'}}},
{[][]rune{{'む'}, {'ム'}, {'ム'}}},
{[][]rune{{'み'}, {'ミ'}, {'ミ'}}},
{[][]rune{{'ま'}, {'マ'}, {'マ'}}},
{[][]rune{{'ほ'}, {'ホ'}, {'ホ'}}},
{[][]rune{{'へ'}, {'ヘ'}, {'ヘ'}}},
{[][]rune{{'ふ'}, {'フ'}, {'フ'}}},
{[][]rune{{'ひ'}, {'ヒ'}, {'ヒ'}}},
{[][]rune{{'は'}, {'ハ'}, {'ハ'}, {'八'}}},
{[][]rune{{'の'}, {''}, {'ノ'}}},
{[][]rune{{'ね'}, {'ネ'}, {'ネ'}}},
{[][]rune{{'ぬ'}, {'ヌ'}, {'ヌ'}}},
{[][]rune{{'に'}, {'ニ'}, {'ニ'}, {'二'}}},
{[][]rune{{'な'}, {'ナ'}, {'ナ'}}},
{[][]rune{{'と'}, {'ト'}, {'ト'}, {'┣'}, {'├'}}},
{[][]rune{{'て'}, {'テ'}, {'テ'}, {'〒'}, {'乙'}}},
{[][]rune{{'つ'}, {'ツ'}, {'ツ'}, {'っ'}, {'ッ'}, {'ッ'}}},
{[][]rune{{'ち'}, {'チ'}, {'チ'}, {'千'}}},
{[][]rune{{'た'}, {'タ'}, {'タ'}, {'夕'}}},
{[][]rune{{'そ'}, {'ソ'}, {'ソ'}}},
{[][]rune{{'せ'}, {'セ'}, {'セ'}}},
{[][]rune{{'す'}, {'ス'}, {'ス'}}},
{[][]rune{{'し'}, {'シ'}, {'シ'}}},
{[][]rune{{'さ'}, {'サ'}, {'サ'}}},
{[][]rune{{'こ'}, {'コ'}, {'コ'}}},
{[][]rune{{'け'}, {'ケ'}, {'ケ'}, {'ヶ'}}},
{[][]rune{{'く'}, {'ク'}, {'ク'}}},
{[][]rune{{'き'}, {'キ'}, {'キ'}}},
{[][]rune{{'か'}, {'カ'}, {'カ'}, {'ヵ'}, {'力'}}},
{[][]rune{{'お'}, {'オ'}, {'オ'}, {'ォ'}, {'ぉ'}, {'ォ'}}},
{[][]rune{{'え'}, {'エ'}, {'エ'}, {'ェ'}, {'ぇ'}, {'ェ'}, {'工'}}},
{[][]rune{{'う'}, {'ウ'}, {'ウ'}, {'ゥ'}, {'ぅ'}, {'ゥ'}}},
{[][]rune{{'い'}, {'イ'}, {'イ'}, {'ィ'}, {'ぃ'}, {'ィ'}}},
{[][]rune{{'あ'}, {'ア'}, {'ァ'}, {'ア'}, {'ぁ'}, {'ァ'}}},
{[][]rune{{'ー'}, {'―'}, {''}, {'-'}, {''}, {'ー'}, {'一'}}},
{[][]rune{{'9'}, {''}}},
{[][]rune{{'8'}, {''}}},
{[][]rune{{'7'}, {''}}},
{[][]rune{{'6'}, {''}}},
{[][]rune{{'5'}, {''}}},
{[][]rune{{'4'}, {''}}},
{[][]rune{{'3'}, {''}}},
{[][]rune{{'2'}, {''}}},
{[][]rune{{'1'}, {''}}},
{[][]rune{{'ぽ'}, {'ポ'}, {'ホ', '゚'}, {'ほ', '゚'}, {'ホ', '゚'}, {'ホ', '°'}, {'ほ', '°'}, {'ホ', '°'}}},
{[][]rune{{'ぺ'}, {'ペ'}, {'ヘ', '゚'}, {'へ', '゚'}, {'ヘ', '゚'}, {'ヘ', '°'}, {'へ', '°'}, {'ヘ', '°'}}},
{[][]rune{{'ぷ'}, {'プ'}, {'フ', '゚'}, {'ふ', '゚'}, {'フ', '゚'}, {'フ', '°'}, {'ふ', '°'}, {'フ', '°'}}},
{[][]rune{{'ぴ'}, {'ピ'}, {'ヒ', '゚'}, {'ひ', '゚'}, {'ヒ', '゚'}, {'ヒ', '°'}, {'ひ', '°'}, {'ヒ', '°'}}},
{[][]rune{{'ぱ'}, {'パ'}, {'ハ', '゚'}, {'は', '゚'}, {'ハ', '゚'}, {'ハ', '°'}, {'は', '°'}, {'ハ', '°'}, {'八', '゚'}, {'八', '゜'}}},
{[][]rune{{'z'}, {''}, {'Z'}, {''}, {'Ζ'}}},
{[][]rune{{'y'}, {''}, {'Y'}, {''}, {'Υ'}, {'У'}, {'у'}}},
{[][]rune{{'x'}, {''}, {'X'}, {''}, {'Χ'}, {'χ'}, {'Х'}, {'×'}, {'х'}}},
{[][]rune{{'w'}, {''}, {'W'}, {''}, {'ω'}, {'Ш'}, {'ш'}, {'щ'}}},
{[][]rune{{'v'}, {''}, {'V'}, {''}, {'ν'}, {'υ'}}},
{[][]rune{{'u'}, {''}, {'U'}, {''}, {'μ'}, {''}}},
{[][]rune{{'t'}, {''}, {'T'}, {''}, {'Τ'}, {'τ'}, {'Т'}, {'т'}}},
{[][]rune{{'s'}, {''}, {'S'}, {''}, {'∫'}, {''}, {'$'}}},
{[][]rune{{'r'}, {''}, {'R'}, {''}, {'Я'}, {'я'}}},
{[][]rune{{'q'}, {''}, {'Q'}, {''}}},
{[][]rune{{'p'}, {''}, {'P'}, {''}, {'Ρ'}, {'ρ'}, {'Р'}, {'р'}}},
{[][]rune{{'o'}, {''}, {'O'}, {''}, {'○'}, {'Ο'}, {'ο'}, {'О'}, {'о'}, {'◯'}, {''}, {'0'}, {''}}},
{[][]rune{{'n'}, {''}, {'N'}, {''}, {'Ν'}, {'η'}, {'ン'}, {'ん'}, {'ン'}}},
{[][]rune{{'m'}, {''}, {'M'}, {''}, {'Μ'}, {'М'}, {'м'}}},
{[][]rune{{'l'}, {''}, {'L'}, {''}, {'|'}}},
{[][]rune{{'k'}, {''}, {'K'}, {''}, {'Κ'}, {'κ'}, {'К'}, {'к'}}},
{[][]rune{{'j'}, {''}, {'J'}, {''}}},
{[][]rune{{'i'}, {''}, {'I'}, {''}, {'Ι'}}},
{[][]rune{{'h'}, {''}, {'H'}, {''}, {'Η'}, {'Н'}, {'н'}}},
{[][]rune{{'f'}, {''}, {'F'}, {''}}},
{[][]rune{{'g'}, {''}, {'G'}, {''}}},
{[][]rune{{'e'}, {''}, {'E'}, {''}, {'Ε'}, {'ε'}, {'Е'}, {'Ё'}, {'е'}, {'ё'}, {'∈'}}},
{[][]rune{{'d'}, {''}, {'D'}, {''}}},
{[][]rune{{'c'}, {''}, {'C'}, {'С'}, {'с'}, {''}, {'℃'}}},
{[][]rune{{'b'}, {''}, {''}, {'B'}, {'β'}, {'Β'}, {'В'}, {'в'}, {'ъ'}, {'ь'}, {'♭'}}},
{[][]rune{{'\''}, {''}}},
{[][]rune{{'a'}, {''}, {''}, {'A'}, {'α'}, {'@'}, {''}, {'а'}, {'Å'}, {'А'}, {'Α'}}},
{[][]rune{{'"'}, {'”'}}},
{[][]rune{{'%'}, {''}}},
}
for _, smcGroup := range smcData {
for _, smcPair := range smcGroup.charGroup {
smc.WriteUint16(stringsupport.ToNGWord(string(smcPair[0]))[0])
if len(smcPair) > 1 {
smc.WriteUint16(stringsupport.ToNGWord(string(smcPair[1]))[0])
} else {
smc.WriteUint16(0)
}
}
smc.WriteUint32(0)
}
filters.WriteUint32(uint32(len(smc.Data())))
filters.WriteBytes(smc.Data())
filters.WriteNullTerminatedBytes([]byte("nam"))
nam := byteframe.NewByteFrame()
nam.SetLE()
for _, word := range namNGWords {
parts := stringsupport.ToNGWord(word)
nam.WriteUint32(uint32(len(parts)))
for _, part := range parts {
nam.WriteUint16(part)
var i int16
j := int16(-1)
for _, smcGroup := range smcData {
if rune(part) == rune(stringsupport.ToNGWord(string(smcGroup.charGroup[0][0]))[0]) {
j = i
break
}
i += int16(len(smcGroup.charGroup) + 1)
}
nam.WriteInt16(j)
}
nam.WriteUint16(0)
nam.WriteInt16(-1)
}
filters.WriteUint32(uint32(len(nam.Data())))
filters.WriteBytes(nam.Data())
filters.WriteNullTerminatedBytes([]byte("msg"))
msg := byteframe.NewByteFrame()
msg.SetLE()
for _, word := range msgNGWords {
parts := stringsupport.ToNGWord(word)
msg.WriteUint32(uint32(len(parts)))
for _, part := range parts {
msg.WriteUint16(part)
var i int16
j := int16(-1)
for _, smcGroup := range smcData {
if rune(part) == rune(stringsupport.ToNGWord(string(smcGroup.charGroup[0][0]))[0]) {
j = i
break
}
i += int16(len(smcGroup.charGroup) + 1)
}
msg.WriteInt16(j)
}
msg.WriteUint16(0)
msg.WriteInt16(-1)
}
filters.WriteUint32(uint32(len(msg.Data())))
filters.WriteBytes(msg.Data())
bf.WriteUint16(uint16(len(filters.Data())))
bf.WriteBytes(filters.Data())
if s.client == VITA || s.client == PS3 || s.client == PS4 {
psnUser, err := s.server.userRepo.GetPSNIDForUser(uid)
if err != nil {
s.logger.Warn("Failed to get PSN ID for user", zap.Uint32("uid", uid), zap.Error(err))
}
bf.WriteBytes(stringsupport.PaddedString(psnUser, 20, true))
}
// CapLink.Values requires at least 5 elements to avoid index out of range panics
// Provide safe defaults if array is too small
capLinkValues := s.server.erupeConfig.DebugOptions.CapLink.Values
if len(capLinkValues) < 5 {
capLinkValues = []uint16{0, 0, 0, 0, 0}
}
bf.WriteUint16(capLinkValues[0])
if capLinkValues[0] == 51728 {
bf.WriteUint16(capLinkValues[1])
if capLinkValues[1] == 20000 || capLinkValues[1] == 20002 {
ps.Uint16(bf, s.server.erupeConfig.DebugOptions.CapLink.Key, false)
}
}
caStruct := []struct {
Unk0 uint8
Unk1 uint32
Unk2 string
}{}
bf.WriteUint8(uint8(len(caStruct)))
for i := range caStruct {
bf.WriteUint8(caStruct[i].Unk0)
bf.WriteUint32(caStruct[i].Unk1)
ps.Uint8(bf, caStruct[i].Unk2, false)
}
bf.WriteUint16(capLinkValues[2])
bf.WriteUint16(capLinkValues[3])
bf.WriteUint16(capLinkValues[4])
if capLinkValues[2] == 51729 && capLinkValues[3] == 1 && capLinkValues[4] == 20000 {
ps.Uint16(bf, fmt.Sprintf(`%s:%d`, s.server.erupeConfig.DebugOptions.CapLink.Host, s.server.erupeConfig.DebugOptions.CapLink.Port), false)
}
bf.WriteUint32(uint32(s.server.getReturnExpiry(uid).Unix()))
bf.WriteUint32(0)
tickets := []uint32{
s.server.erupeConfig.GameplayOptions.MezFesSoloTickets,
s.server.erupeConfig.GameplayOptions.MezFesGroupTickets,
}
stalls := []uint8{
10, 3, 6, 9, 4, 8, 5, 7,
}
if s.server.erupeConfig.GameplayOptions.MezFesSwitchMinigame {
stalls[4] = 2
}
// We can just use the start timestamp as the event ID
bf.WriteUint32(uint32(gametime.WeekStart().Unix()))
// Start time
bf.WriteUint32(uint32(gametime.WeekNext().Add(-time.Duration(s.server.erupeConfig.GameplayOptions.MezFesDuration) * time.Second).Unix()))
// End time
bf.WriteUint32(uint32(gametime.WeekNext().Unix()))
bf.WriteUint8(uint8(len(tickets)))
for i := range tickets {
bf.WriteUint32(tickets[i])
}
bf.WriteUint8(uint8(len(stalls)))
for i := range stalls {
bf.WriteUint8(stalls[i])
}
return bf.Data()
}