diff --git a/config.json b/config.json index 30164ec4c..1e09daddd 100644 --- a/config.json +++ b/config.json @@ -11,6 +11,7 @@ "PatchServerFile": "", "ScreenshotAPIURL": "", "DeleteOnSaveCorruption": false, + "ClientMode": "ZZ", "DevMode": true, "DevModeOptions": { "AutoCreateAccount": true, diff --git a/config/config.go b/config/config.go index f52cc73c5..9e96031a8 100644 --- a/config/config.go +++ b/config/config.go @@ -1,15 +1,28 @@ -package config +package _config import ( "fmt" "log" "net" "os" + "strings" "time" "github.com/spf13/viper" ) +type Mode string + +const ( + ZZ Mode = "ZZ" + Z2 Mode = "Z2" + Z1 Mode = "Z1" +) + +func (m Mode) String() string { + return string(m) +} + // Config holds the global server-wide config. type Config struct { Host string `mapstructure:"Host"` @@ -22,6 +35,7 @@ type Config struct { PatchServerFile string // File patch server override ScreenshotAPIURL string // Destination for screenshots uploaded to BBS DeleteOnSaveCorruption bool // Attempts to save corrupted data will flag the save for deletion + ClientMode Mode DevMode bool DevModeOptions DevModeOptions @@ -170,7 +184,6 @@ func init() { if err != nil { preventClose(fmt.Sprintf("Failed to load config: %s", err.Error())) } - } // getOutboundIP4 gets the preferred outbound ip4 of this machine @@ -212,6 +225,15 @@ func LoadConfig() (*Config, error) { c.Host = getOutboundIP4().To4().String() } + switch strings.ToUpper(c.ClientMode.String()) { + case "Z1": + c.ClientMode = Z1 + case "Z2": + c.ClientMode = Z2 + default: + c.ClientMode = ZZ + } + return c, nil } diff --git a/main.go b/main.go index 0f104eccf..6f1abc011 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + _config "erupe-ce/config" "fmt" "net" "os" @@ -9,7 +10,6 @@ import ( "syscall" "time" - "erupe-ce/config" "erupe-ce/server/channelserver" "erupe-ce/server/discordbot" "erupe-ce/server/entranceserver" @@ -45,7 +45,8 @@ func main() { var err error var zapLogger *zap.Logger - if config.ErupeConfig.DevMode { + config := _config.ErupeConfig + if config.DevMode { zapLogger, _ = zap.NewDevelopment() } else { zapLogger, _ = zap.NewProduction() @@ -55,20 +56,21 @@ func main() { logger := zapLogger.Named("main") logger.Info(fmt.Sprintf("Starting Erupe (9.3b-%s)", Commit())) + logger.Info(fmt.Sprintf("Client Mode: %s", config.ClientMode.String())) - if config.ErupeConfig.Database.Password == "" { + if config.Database.Password == "" { preventClose("Database password is blank") } - if net.ParseIP(config.ErupeConfig.Host) == nil { - ips, _ := net.LookupIP(config.ErupeConfig.Host) + if net.ParseIP(config.Host) == nil { + ips, _ := net.LookupIP(config.Host) for _, ip := range ips { if ip != nil { - config.ErupeConfig.Host = ip.String() + config.Host = ip.String() break } } - if net.ParseIP(config.ErupeConfig.Host) == nil { + if net.ParseIP(config.Host) == nil { preventClose("Invalid host address") } } @@ -76,10 +78,10 @@ func main() { // Discord bot var discordBot *discordbot.DiscordBot = nil - if config.ErupeConfig.Discord.Enabled { + if config.Discord.Enabled { bot, err := discordbot.NewDiscordBot(discordbot.Options{ Logger: logger, - Config: config.ErupeConfig, + Config: _config.ErupeConfig, }) if err != nil { @@ -102,11 +104,11 @@ func main() { // Create the postgres DB pool. connectString := fmt.Sprintf( "host='%s' port='%d' user='%s' password='%s' dbname='%s' sslmode=disable", - config.ErupeConfig.Database.Host, - config.ErupeConfig.Database.Port, - config.ErupeConfig.Database.User, - config.ErupeConfig.Database.Password, - config.ErupeConfig.Database.Database, + config.Database.Host, + config.Database.Port, + config.Database.User, + config.Database.Password, + config.Database.Database, ) db, err := sqlx.Open("postgres", connectString) @@ -126,7 +128,7 @@ func main() { _ = db.MustExec("DELETE FROM servers") // Clean the DB if the option is on. - if config.ErupeConfig.DevMode && config.ErupeConfig.DevModeOptions.CleanDB { + if config.DevMode && config.DevModeOptions.CleanDB { logger.Info("Database: Started clearing...") cleanDB(db) logger.Info("Database: Finished clearing") @@ -139,11 +141,11 @@ func main() { // Entrance server. var entranceServer *entranceserver.Server - if config.ErupeConfig.Entrance.Enabled { + if config.Entrance.Enabled { entranceServer = entranceserver.NewServer( &entranceserver.Config{ Logger: logger.Named("entrance"), - ErupeConfig: config.ErupeConfig, + ErupeConfig: _config.ErupeConfig, DB: db, }) err = entranceServer.Start() @@ -158,11 +160,11 @@ func main() { // Sign server. var signServer *signserver.Server - if config.ErupeConfig.Sign.Enabled { + if config.Sign.Enabled { signServer = signserver.NewServer( &signserver.Config{ Logger: logger.Named("sign"), - ErupeConfig: config.ErupeConfig, + ErupeConfig: _config.ErupeConfig, DB: db, }) err = signServer.Start() @@ -176,11 +178,11 @@ func main() { // New Sign server var newSignServer *signv2server.Server - if config.ErupeConfig.SignV2.Enabled { + if config.SignV2.Enabled { newSignServer = signv2server.NewServer( &signv2server.Config{ Logger: logger.Named("sign"), - ErupeConfig: config.ErupeConfig, + ErupeConfig: _config.ErupeConfig, DB: db, }) err = newSignServer.Start() @@ -194,23 +196,23 @@ func main() { var channels []*channelserver.Server - if config.ErupeConfig.Channel.Enabled { + if config.Channel.Enabled { channelQuery := "" si := 0 ci := 0 count := 1 - for j, ee := range config.ErupeConfig.Entrance.Entries { + for j, ee := range config.Entrance.Entries { for i, ce := range ee.Channels { sid := (4096 + si*256) + (16 + ci) c := *channelserver.NewServer(&channelserver.Config{ ID: uint16(sid), Logger: logger.Named("channel-" + fmt.Sprint(count)), - ErupeConfig: config.ErupeConfig, + ErupeConfig: _config.ErupeConfig, DB: db, DiscordBot: discordBot, }) if ee.IP == "" { - c.IP = config.ErupeConfig.Host + c.IP = config.Host } else { c.IP = ee.IP } @@ -246,7 +248,7 @@ func main() { signal.Notify(c, os.Interrupt, syscall.SIGTERM) <-c - if !config.ErupeConfig.DisableSoftCrash { + if !config.DisableSoftCrash { for i := 0; i < 10; i++ { message := fmt.Sprintf("Shutting down in %d...", 10-i) for _, c := range channels { @@ -257,21 +259,21 @@ func main() { } } - if config.ErupeConfig.Channel.Enabled { + if config.Channel.Enabled { for _, c := range channels { c.Shutdown() } } - if config.ErupeConfig.Sign.Enabled { + if config.Sign.Enabled { signServer.Shutdown() } - if config.ErupeConfig.SignV2.Enabled { + if config.SignV2.Enabled { newSignServer.Shutdown() } - if config.ErupeConfig.Entrance.Enabled { + if config.Entrance.Enabled { entranceServer.Shutdown() } @@ -285,7 +287,7 @@ func wait() { } func preventClose(text string) { - if config.ErupeConfig.DisableSoftCrash { + if _config.ErupeConfig.DisableSoftCrash { os.Exit(0) } fmt.Println("\nFailed to start Erupe:\n" + text) diff --git a/network/mhfpacket/msg_mhf_enumerate_quest.go b/network/mhfpacket/msg_mhf_enumerate_quest.go index 7bf1e1355..5961cf152 100644 --- a/network/mhfpacket/msg_mhf_enumerate_quest.go +++ b/network/mhfpacket/msg_mhf_enumerate_quest.go @@ -2,6 +2,7 @@ package mhfpacket import ( "errors" + _config "erupe-ce/config" "erupe-ce/common/byteframe" "erupe-ce/network" @@ -30,7 +31,9 @@ func (m *MsgMhfEnumerateQuest) Parse(bf *byteframe.ByteFrame, ctx *clientctx.Cli m.World = bf.ReadUint8() m.Counter = bf.ReadUint16() m.Offset = bf.ReadUint16() - m.Unk4 = bf.ReadUint8() + if _config.ErupeConfig.ClientMode != _config.Z1 { + m.Unk4 = bf.ReadUint8() + } return nil } diff --git a/server/channelserver/handlers_cast_binary.go b/server/channelserver/handlers_cast_binary.go index 7c961b5e9..3a1f9d031 100644 --- a/server/channelserver/handlers_cast_binary.go +++ b/server/channelserver/handlers_cast_binary.go @@ -35,16 +35,16 @@ const ( BroadcastTypeWorld = 0x0a ) -var commands map[string]config.Command +var commands map[string]_config.Command func init() { - commands = make(map[string]config.Command) + commands = make(map[string]_config.Command) zapConfig := zap.NewDevelopmentConfig() zapConfig.DisableCaller = true zapLogger, _ := zapConfig.Build() defer zapLogger.Sync() logger := zapLogger.Named("commands") - cmds := config.ErupeConfig.Commands + cmds := _config.ErupeConfig.Commands for _, cmd := range cmds { commands[cmd.Name] = cmd if cmd.Enabled { @@ -55,7 +55,7 @@ func init() { } } -func sendDisabledCommandMessage(s *Session, cmd config.Command) { +func sendDisabledCommandMessage(s *Session, cmd _config.Command) { sendServerChatMessage(s, fmt.Sprintf(s.server.dict["commandDisabled"], cmd.Name)) } diff --git a/server/channelserver/handlers_character.go b/server/channelserver/handlers_character.go index a53d4c42a..320d459a6 100644 --- a/server/channelserver/handlers_character.go +++ b/server/channelserver/handlers_character.go @@ -5,6 +5,7 @@ import ( "errors" "erupe-ce/common/bfutil" "erupe-ce/common/stringsupport" + _config "erupe-ce/config" "erupe-ce/network/mhfpacket" "erupe-ce/server/channelserver/compression/nullcomp" @@ -12,7 +13,8 @@ import ( ) const ( - pointerGender = 0x51 // +1 + pointerGender = 0x51 // +1 + pointerRP = 0x22D16 // +2 pointerHouseTier = 0x1FB6C // +5 pointerHouseData = 0x1FE01 // +195 @@ -26,6 +28,19 @@ const ( pointerHRP = 0x1FDF6 // +2 pointerGRP = 0x1FDFC // +4 pointerKQF = 0x23D20 // +8 + + pointerRPZ = 0x1A076 + pointerHouseTierZ = 0x16ECC + pointerHouseDataZ = 0x17161 + pointerBookshelfDataZ = 0x195F8 + pointerGalleryDataZ = 0x19680 + pointerToreDataZ = 0x17014 + pointerGardenDataZ = 0x19FB8 + pointerWeaponTypeZ = 0x16A75 + pointerWeaponIDZ = 0x1696A + pointerHRPZ = 0x17156 + pointerGRPZ = 0x1715C + pointerKQFZ = 0x1B080 ) type CharacterSaveData struct { @@ -81,10 +96,6 @@ func GetCharacterSaveData(s *Session, charID uint32) (*CharacterSaveData, error) return nil, err } - if len(saveData.decompSave) < pointerKQF { - return nil, err - } - saveData.updateStructWithSaveData() return saveData, nil @@ -137,8 +148,13 @@ func (save *CharacterSaveData) Decompress() error { func (save *CharacterSaveData) updateSaveDataWithStruct() { rpBytes := make([]byte, 2) binary.LittleEndian.PutUint16(rpBytes, save.RP) - copy(save.decompSave[pointerRP:pointerRP+2], rpBytes) - copy(save.decompSave[pointerKQF:pointerKQF+8], save.KQF) + if _config.ErupeConfig.ClientMode == _config.ZZ { + copy(save.decompSave[pointerRP:pointerRP+2], rpBytes) + copy(save.decompSave[pointerKQF:pointerKQF+8], save.KQF) + } else { + copy(save.decompSave[pointerRPZ:pointerRPZ+2], rpBytes) + copy(save.decompSave[pointerKQFZ:pointerKQFZ+8], save.KQF) + } } // This will update the save struct with the values stored in the character save @@ -150,20 +166,37 @@ func (save *CharacterSaveData) updateStructWithSaveData() { save.Gender = false } if !save.IsNewCharacter { - save.RP = binary.LittleEndian.Uint16(save.decompSave[pointerRP : pointerRP+2]) - save.HouseTier = save.decompSave[pointerHouseTier : pointerHouseTier+5] - save.HouseData = save.decompSave[pointerHouseData : pointerHouseData+195] - save.BookshelfData = save.decompSave[pointerBookshelfData : pointerBookshelfData+5576] - save.GalleryData = save.decompSave[pointerGalleryData : pointerGalleryData+1748] - save.ToreData = save.decompSave[pointerToreData : pointerToreData+240] - save.GardenData = save.decompSave[pointerGardenData : pointerGardenData+68] - save.WeaponType = save.decompSave[pointerWeaponType] - save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[pointerWeaponID : pointerWeaponID+2]) - save.HRP = binary.LittleEndian.Uint16(save.decompSave[pointerHRP : pointerHRP+2]) - if save.HRP == uint16(999) { - save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[pointerGRP : pointerGRP+4])) + if _config.ErupeConfig.ClientMode == _config.ZZ { + save.RP = binary.LittleEndian.Uint16(save.decompSave[pointerRP : pointerRP+2]) + save.HouseTier = save.decompSave[pointerHouseTier : pointerHouseTier+5] + save.HouseData = save.decompSave[pointerHouseData : pointerHouseData+195] + save.BookshelfData = save.decompSave[pointerBookshelfData : pointerBookshelfData+5576] + save.GalleryData = save.decompSave[pointerGalleryData : pointerGalleryData+1748] + save.ToreData = save.decompSave[pointerToreData : pointerToreData+240] + save.GardenData = save.decompSave[pointerGardenData : pointerGardenData+68] + save.WeaponType = save.decompSave[pointerWeaponType] + save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[pointerWeaponID : pointerWeaponID+2]) + save.HRP = binary.LittleEndian.Uint16(save.decompSave[pointerHRP : pointerHRP+2]) + if save.HRP == uint16(999) { + save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[pointerGRP : pointerGRP+4])) + } + save.KQF = save.decompSave[pointerKQF : pointerKQF+8] + } else { + save.RP = binary.LittleEndian.Uint16(save.decompSave[pointerRPZ : pointerRPZ+2]) + save.HouseTier = save.decompSave[pointerHouseTierZ : pointerHouseTierZ+5] + save.HouseData = save.decompSave[pointerHouseDataZ : pointerHouseDataZ+195] + save.BookshelfData = save.decompSave[pointerBookshelfDataZ : pointerBookshelfDataZ+5576] + save.GalleryData = save.decompSave[pointerGalleryDataZ : pointerGalleryDataZ+1748] + save.ToreData = save.decompSave[pointerToreDataZ : pointerToreDataZ+240] + save.GardenData = save.decompSave[pointerGardenDataZ : pointerGardenDataZ+68] + save.WeaponType = save.decompSave[pointerWeaponTypeZ] + save.WeaponID = binary.LittleEndian.Uint16(save.decompSave[pointerWeaponIDZ : pointerWeaponIDZ+2]) + save.HRP = binary.LittleEndian.Uint16(save.decompSave[pointerHRPZ : pointerHRPZ+2]) + if save.HRP == uint16(999) { + save.GR = grpToGR(binary.LittleEndian.Uint32(save.decompSave[pointerGRPZ : pointerGRPZ+4])) + } + save.KQF = save.decompSave[pointerKQFZ : pointerKQFZ+8] } - save.KQF = save.decompSave[pointerKQF : pointerKQF+8] } return } diff --git a/server/channelserver/handlers_diva.go b/server/channelserver/handlers_diva.go index 0daabe70c..10d0c0a2b 100644 --- a/server/channelserver/handlers_diva.go +++ b/server/channelserver/handlers_diva.go @@ -3,6 +3,7 @@ package channelserver import ( "encoding/hex" "erupe-ce/common/stringsupport" + _config "erupe-ce/config" "time" "erupe-ce/common/byteframe" @@ -80,7 +81,10 @@ func handleMsgMhfGetUdSchedule(s *Session, p mhfpacket.MHFPacket) { } bf.WriteUint32(id) - for _, timestamp := range timestamps { + for i, timestamp := range timestamps { + if s.server.erupeConfig.ClientMode == _config.Z1 && i == 4 { + continue + } bf.WriteUint32(timestamp) } diff --git a/server/channelserver/handlers_event.go b/server/channelserver/handlers_event.go index bba4c1065..ffc1b895f 100644 --- a/server/channelserver/handlers_event.go +++ b/server/channelserver/handlers_event.go @@ -2,6 +2,7 @@ package channelserver import ( "erupe-ce/common/token" + _config "erupe-ce/config" "math" "time" @@ -90,14 +91,18 @@ func handleMsgMhfGetWeeklySchedule(s *Session, p mhfpacket.MHFPacket) { } func generateFeatureWeapons(count int) activeFeature { - if count > 14 { - count = 14 + max := 14 + if _config.ErupeConfig.ClientMode != _config.ZZ { + max = 13 + } + if count > max { + count = max } nums := make([]int, 0) var result int for len(nums) < count { rng := token.RNG() - num := rng.Intn(14) + num := rng.Intn(max) exist := false for _, v := range nums { if v == num { diff --git a/server/channelserver/handlers_guild.go b/server/channelserver/handlers_guild.go index 09520d8c4..ec58c7487 100644 --- a/server/channelserver/handlers_guild.go +++ b/server/channelserver/handlers_guild.go @@ -7,6 +7,7 @@ import ( "encoding/hex" "encoding/json" "errors" + _config "erupe-ce/config" "fmt" "math" "sort" @@ -1390,7 +1391,12 @@ func handleMsgMhfEnumerateGuildMember(s *Session, p mhfpacket.MHFPacket) { bf.WriteUint32(member.CharID) bf.WriteUint16(member.HRP) bf.WriteUint16(member.GR) - bf.WriteUint16(member.WeaponID) + if s.server.erupeConfig.ClientMode != _config.ZZ { + // Magnet Spike crash workaround + bf.WriteUint16(0) + } else { + bf.WriteUint16(member.WeaponID) + } if member.WeaponType == 1 || member.WeaponType == 5 || member.WeaponType == 10 { // If weapon is ranged bf.WriteUint8(7) } else { diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 309ed1af8..dccece844 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -22,7 +22,7 @@ type Config struct { Logger *zap.Logger DB *sqlx.DB DiscordBot *discordbot.DiscordBot - ErupeConfig *config.Config + ErupeConfig *_config.Config Name string Enable bool } @@ -43,7 +43,7 @@ type Server struct { Port uint16 logger *zap.Logger db *sqlx.DB - erupeConfig *config.Config + erupeConfig *_config.Config acceptConns chan net.Conn deleteConns chan net.Conn sessions map[net.Conn]*Session diff --git a/server/discordbot/discord_bot.go b/server/discordbot/discord_bot.go index fc5c41ce8..c082faf70 100644 --- a/server/discordbot/discord_bot.go +++ b/server/discordbot/discord_bot.go @@ -9,14 +9,14 @@ import ( type DiscordBot struct { Session *discordgo.Session - config *config.Config + config *_config.Config logger *zap.Logger MainGuild *discordgo.Guild RealtimeChannel *discordgo.Channel } type Options struct { - Config *config.Config + Config *_config.Config Logger *zap.Logger } diff --git a/server/entranceserver/entrance_server.go b/server/entranceserver/entrance_server.go index c0a4e852b..8b06be0e0 100644 --- a/server/entranceserver/entrance_server.go +++ b/server/entranceserver/entrance_server.go @@ -18,7 +18,7 @@ import ( type Server struct { sync.Mutex logger *zap.Logger - erupeConfig *config.Config + erupeConfig *_config.Config db *sqlx.DB listener net.Listener isShuttingDown bool @@ -28,7 +28,7 @@ type Server struct { type Config struct { Logger *zap.Logger DB *sqlx.DB - ErupeConfig *config.Config + ErupeConfig *_config.Config } // NewServer creates a new Server type. @@ -68,7 +68,7 @@ func (s *Server) Shutdown() { s.listener.Close() } -//acceptClients handles accepting new clients in a loop. +// acceptClients handles accepting new clients in a loop. func (s *Server) acceptClients() { for { conn, err := s.listener.Accept() diff --git a/server/entranceserver/make_resp.go b/server/entranceserver/make_resp.go index 2d13709db..ac63a8ad5 100644 --- a/server/entranceserver/make_resp.go +++ b/server/entranceserver/make_resp.go @@ -3,13 +3,13 @@ package entranceserver import ( "encoding/binary" "encoding/hex" + _config "erupe-ce/config" "fmt" "net" "erupe-ce/common/stringsupport" "erupe-ce/common/byteframe" - "erupe-ce/config" "erupe-ce/server/channelserver" ) @@ -19,11 +19,17 @@ var season uint8 // Server Channels var currentplayers uint16 -func encodeServerInfo(config *config.Config, s *Server, local bool) []byte { +func encodeServerInfo(config *_config.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.ClientMode == _config.Z1 { + if si.Type == 6 { + continue + } + } sid := (4096 + serverIdx*256) + 16 err := s.db.QueryRow("SELECT season FROM servers WHERE server_id=$1", sid).Scan(&season) if err != nil { @@ -91,8 +97,17 @@ func makeHeader(data []byte, respType string, entryCount uint16, key byte) []byt return bf.Data() } -func makeSv2Resp(config *config.Config, s *Server, local bool) []byte { +func makeSv2Resp(config *_config.Config, s *Server, local bool) []byte { serverInfos := config.Entrance.Entries + // Decrease by the number of MezFes Worlds + var mf int + if config.ClientMode == _config.Z1 { + for _, si := range serverInfos { + if si.Type == 6 { + mf++ + } + } + } rawServerData := encodeServerInfo(config, s, local) if s.erupeConfig.DevMode && s.erupeConfig.DevModeOptions.LogOutboundMessages { @@ -100,7 +115,7 @@ func makeSv2Resp(config *config.Config, s *Server, local bool) []byte { } bf := byteframe.NewByteFrame() - bf.WriteBytes(makeHeader(rawServerData, "SV2", uint16(len(serverInfos)), 0x00)) + bf.WriteBytes(makeHeader(rawServerData, "SV2", uint16(len(serverInfos)-mf), 0x00)) return bf.Data() } diff --git a/server/signserver/sign_server.go b/server/signserver/sign_server.go index fd4bd9bed..f93a6459a 100644 --- a/server/signserver/sign_server.go +++ b/server/signserver/sign_server.go @@ -16,14 +16,14 @@ import ( type Config struct { Logger *zap.Logger DB *sqlx.DB - ErupeConfig *config.Config + ErupeConfig *_config.Config } // Server is a MHF sign server. type Server struct { sync.Mutex logger *zap.Logger - erupeConfig *config.Config + erupeConfig *_config.Config sessions map[int]*Session db *sqlx.DB listener net.Listener diff --git a/server/signv2server/signv2_server.go b/server/signv2server/signv2_server.go index c00a4a641..c6db00b09 100644 --- a/server/signv2server/signv2_server.go +++ b/server/signv2server/signv2_server.go @@ -18,14 +18,14 @@ import ( type Config struct { Logger *zap.Logger DB *sqlx.DB - ErupeConfig *config.Config + ErupeConfig *_config.Config } // Server is the MHF custom launcher sign server. type Server struct { sync.Mutex logger *zap.Logger - erupeConfig *config.Config + erupeConfig *_config.Config db *sqlx.DB httpServer *http.Server isShuttingDown bool