package main import ( "bufio" "encoding/binary" "fmt" "net" "os" "regexp" "strconv" "time" ) type ClientTTD struct { ClientID uint32 Name string Address string CompanyID uint8 Paused bool LastSeen time.Time } type CompanyTTD struct { CompanyID uint8 CompanyExtlID uint Name string Protected bool ClientID uint32 FirstSeen time.Time LastSeen time.Time } type ServerTTD struct { Config *ServerConfig Status *ServerStatusTTD Data *ServerDataTTD } type ServerStatusTTD struct { Connected bool Paused bool UpdateCompanies time.Time UpdateClients time.Time UpdateDate time.Time Initialized bool GameDate time.Time Clients map[uint32]*ClientTTD Companies map[uint8]*CompanyTTD } type ServerDataTTD struct { LastClientCompute time.Time Conn net.Conn Stop chan struct{} } var updateHeartBeat = 5 * time.Second func (s *ServerTTD) Connect() (err error) { t := time.Now() s.Status.Connected = false s.Data.Conn, err = net.Dial("tcp", s.Config.Addr) logErrorDebug(err, "Server.Connect() : net.Dial") if err != nil { return err } logInfoDebug("Server.Connect() : Connected to " + s.Config.Addr) s.Data.LastClientCompute = time.Now() s.Status.Connected = true s.Status.Initialized = false s.Status.UpdateDate = t s.Status.UpdateClients = t s.Status.UpdateCompanies = t s.Status.Clients = make(map[uint32]*ClientTTD) s.Status.Companies = make(map[uint8]*CompanyTTD) return err } func (s *ServerTTD) Send(b []byte) (err error) { if !s.Status.Connected { return fmt.Errorf("not connected") } _, err = s.Data.Conn.Write(b) return err } func (s *ServerTTD) Start() { for { err := s.Connect() if err != nil { time.Sleep(5 * time.Second) } break } //send auth p := PacketAdminJoin{ Packet: Packet{PType: AdminPacketAdminJoin}, Password: s.Config.Passwd, AppName: "gottdad", AppVersion: version, } err := s.Send(p.Bytes()) failError(err, "Server.Start() : Cannot send authentication packet") stopPoll := make(chan struct{}) go s.Poll(stopPoll) stopHeartBeat := make(chan struct{}) go s.HeartBeat(stopHeartBeat) for { select { // call to stop polling case <-s.Data.Stop: close(stopPoll) close(stopHeartBeat) return } } } // Stop gracefully shuts the poller down. func (s *ServerTTD) Stop() { s.Data.Stop <- struct{}{} } func (s *ServerTTD) HeartBeat(stop chan struct{}) { for { select { case <-stop: return default: } s.UpdateDate() s.UpdateClients() s.UpdateCompanies() s.PruneClients() s.PruneCompanies() s.ComputeClientTime() if !s.Status.Paused && s.NeedPause() { s.Pause() } else if s.Status.Paused && !s.NeedPause() { s.Unpause() } cfg.Save(*configFlag) time.Sleep(updateHeartBeat) } } func (s *ServerTTD) Poll(stop chan struct{}) { var err error reader := bufio.NewReader(s.Data.Conn) buffer := make([]byte, 0xFFFF) var n, read int for { select { case <-stop: return default: } p := Packet{} for { if read >= 3 { //logInfoDebug("Server.Poll() : packet read") break } n, err = reader.Read(buffer[read:]) logErrorDebug(err, "Server.Poll() : reader.Read") read += n //logInfoDebug("Server.Poll() : waiting for packet, read %d bytes.", read) } p.PLength = binary.LittleEndian.Uint16(buffer[0:]) p.PType = buffer[2] if p.PLength <= 3 { logInfoAlert("Server.Poll() : wrong packet length") break } //logInfoDebug("Server.Poll() : waiting for packet data : len : %d / type : %d", p.PLength, p.PType) for { if read >= int(p.PLength) { //logInfoDebug("Server.Poll() : data read") break } n, err = reader.Read(buffer[read:]) logErrorDebug(err, "Server.Poll() : reader.Read") read += n //logInfoDebug("Server.Poll() : waiting for data, read %d/%d bytes.", read, p.PLength) } switch p.PType { case AdminPacketServerProtocol: sp := PacketServerProtocol{ Packet: p, } sp.Read(buffer[:p.PLength]) //logInfoDebug("Server.Poll() : AdminPacketServerProtocol :\n- ProtocolVersion: %v\n- FurtherData: %v\n- UpdatePacketType: %v\n- FrequenciesAllowed: %b", sp.ProtocolVersion, sp.FurtherData, sp.UpdatePacketType, sp.FrequenciesAllowed) case AdminPacketServerWelcome: sp := PacketServerWelcome{ Packet: p, } sp.Read(buffer[:p.PLength]) //logInfoDebug("Server.Poll() : AdminPacketServerWelcome :\n- ServerName: %v\n- OpenTTDVersion: %v\n- Dedicated: %v\n- MapSeed: %x\n- MapLandscape: %v\n- MapStartDate: %v\n- Size: %v x %v", sp.ServerName, sp.OpenTTDVersion, sp.Dedicated, sp.MapSeed, sp.MapLandscape, sp.MapStartDate, sp.MapX, sp.MapY) s.Initialize() case AdminPacketServerDate: sp := PacketServerDate{ Packet: p, } sp.Read(buffer[:p.PLength]) //logInfoDebug("Server.Poll() : AdminPacketServerDate :\n- Date: %d\n- RealDate : %v", sp.Date, toDate(sp.Date)) gameDate := toDate(sp.Date) if gameDate.Day() == 1 && gameDate.Month() == 1 && gameDate != s.Status.GameDate { bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("Year %d.", gameDate.Year())) } s.Status.GameDate = gameDate s.Status.UpdateDate = time.Now() case AdminPacketServerClientJoin: sp := PacketServerClientJoin{ Packet: p, } sp.Read(buffer[:p.PLength]) logInfoDebug("Server.Poll() : AdminPacketServerClientJoin :\n- ClientID: %d", sp.ClientID) case AdminPacketServerClientInfo: sp := PacketServerClientInfo{ Packet: p, } sp.Read(buffer[:p.PLength]) //logInfoDebug("Server.Poll() : AdminPacketServerClientInfo :\n- ClientID: %d\n- Address: %s\n- Name: %s\n- Lang: %d\n- Date: %d\n- CompanyID: %d", sp.ClientID, sp.Address, sp.Name, sp.Lang, sp.Date, sp.CompanyID) clt := &ClientTTD{ ClientID: sp.ClientID, CompanyID: 255, } if _, ok := s.Status.Clients[sp.ClientID]; ok { clt = s.Status.Clients[sp.ClientID] } else { s.Status.Clients[sp.ClientID] = clt } clt.Address = sp.Address clt.Name = sp.Name clt.CompanyID = sp.CompanyID clt.LastSeen = time.Now() if clt.CompanyID != 255 { if co, ok := s.Status.Companies[clt.CompanyID]; ok { if co.ClientID == 0 { co.ClientID = clt.ClientID } } else { //FIXME company doesn't exist ? } if cfg.CompanyIsRegistered(clt.CompanyID) { cc := cfg.GetCompanyClient(clt.CompanyID) if !cc.Online { cc.Online = true bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s now playing.", cc.Username)) } } } case AdminPacketServerClientUpdate: sp := PacketServerClientUpdate{ Packet: p, } sp.Read(buffer[:p.PLength]) logInfoDebug("Server.Poll() : AdminPacketServerClientUpdate :\n- ClientID: %d\n- Name: %s\n- CompanyID: %d", sp.ClientID, sp.Name, sp.CompanyID) clt := s.Status.Clients[sp.ClientID] clt.Name = sp.Name if sp.CompanyID != 255 { if cfg.CompanyIsRegistered(sp.CompanyID) { cc := cfg.GetCompanyClient(sp.CompanyID) cc.Online = true bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s playing.", cc.Username)) } else { if co, ok := s.Status.Companies[sp.CompanyID]; ok { if co.ClientID == 0 { bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("Company '%s' unclaimed. Please /register to claim the company", s.Status.Companies[sp.CompanyID].Name)) } } } } else if clt.CompanyID != 255 && cfg.CompanyIsRegistered(clt.CompanyID) { cc := cfg.GetCompanyClient(clt.CompanyID) bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s taking a break.", cc.Username)) cc.Online = false } clt.LastSeen = time.Now() clt.CompanyID = sp.CompanyID s.Unpause() s.Pause() case AdminPacketServerClientError: sp := PacketServerClientError{ Packet: p, } sp.Read(buffer[:p.PLength]) logInfoDebug("Server.Poll() : AdminPacketServerClientError :\n- ClientID: %d\n- ErrorID: %d", sp.ClientID, sp.ErrorID) case AdminPacketServerClientQuit: sp := PacketServerClientQuit{ Packet: p, } sp.Read(buffer[:p.PLength]) logInfoDebug("Server.Poll() : AdminPacketServerClientQuit :\n- ClientID: %d", sp.ClientID) if cfg.CompanyIsRegistered(s.Status.Clients[sp.ClientID].CompanyID) { cc := cfg.GetCompanyClient(s.Status.Clients[sp.ClientID].CompanyID) bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s leaving.", cc.Username)) cc.Online = false } else { bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("%s leaving.", s.Status.Clients[sp.ClientID].Name)) } delete(s.Status.Clients, sp.ClientID) case AdminPacketServerCompanyNew: sp := PacketServerCompanyNew{ Packet: p, } sp.Read(buffer[:p.PLength]) logInfoDebug("Server.Poll() : PacketServerCompanyNew :\n- CompanyID: %d", sp.CompanyID) c := &CompanyTTD{ CompanyID: sp.CompanyID, CompanyExtlID: 256, FirstSeen: time.Now(), ClientID: 0, } s.Status.Companies[sp.CompanyID] = c case AdminPacketServerCompanyInfo: sp := PacketServerCompanyInfo{ Packet: p, } sp.Read(buffer[:p.PLength]) //logInfoDebug("Server.Poll() : PacketServerCompanyInfo :\n- CompanyID: %d\n- Name: %s\n- President: %s\n- Protected: %t\n- Inauguration: %d\n- AI: %t", sp.CompanyID, sp.Name, sp.President, sp.Protected, sp.Inauguration, sp.AI) c := &CompanyTTD{ CompanyID: sp.CompanyID, CompanyExtlID: 256, FirstSeen: time.Now(), ClientID: 0, } if _, ok := s.Status.Companies[sp.CompanyID]; !ok { s.Status.Companies[sp.CompanyID] = c } else { c = s.Status.Companies[sp.CompanyID] } c.Name = sp.Name c.LastSeen = time.Now() c.Protected = sp.Protected case AdminPacketServerCompanyUpdate: sp := PacketServerCompanyUpdate{ Packet: p, } sp.Read(buffer[:p.PLength]) logInfoDebug("Server.Poll() : PacketServerCompanyUpdate :\n- CompanyID: %d\n- Name: %s\n- President: %s\n- Protected: %t", sp.CompanyID, sp.Name, sp.President, sp.Protected) c := &CompanyTTD{ CompanyID: sp.CompanyID, CompanyExtlID: 256, FirstSeen: time.Now(), ClientID: 0, } if _, ok := s.Status.Companies[sp.CompanyID]; !ok { s.Status.Companies[sp.CompanyID] = c } else { c = s.Status.Companies[sp.CompanyID] } c.Name = sp.Name c.FirstSeen = time.Now() c.LastSeen = time.Now() c.Protected = sp.Protected case AdminPacketServerCompanyRemove: sp := PacketServerCompanyRemove{ Packet: p, } sp.Read(buffer[:p.PLength]) logInfoDebug("Server.Poll() : PacketServerCompanyRemove :\n- CompanyID: %d\n- Reason: %d", sp.CompanyID, sp.Reason) bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("Company #%d deleted (%s)", sp.CompanyID, s.Status.Companies[sp.CompanyID].Name)) delete(s.Status.Companies, sp.CompanyID) case AdminPacketServerChat: sp := PacketServerChat{ Packet: p, } sp.Read(buffer[:p.PLength]) if sp.Message == "!unpause" { logInfoDebug("Server.Poll() : AdminPacketServerChat : Unpausing") clt := s.Status.Clients[sp.ClientID] clt.Paused = false s.Unpause() } else if sp.Message == "!pause" { logInfoDebug("Server.Poll() : AdminPacketServerChat : Pausing") clt := s.Status.Clients[sp.ClientID] clt.Paused = true s.Pause() } else { logInfoDebug("Server.Poll() : AdminPacketServerChat :\n- ActionID: %d\n- DestinationID: %d\n- ClientID: %d\n- Message: %s\n- Amount: %d", sp.ActionID, sp.DestinationID, sp.ClientID, sp.Message, sp.Amount) } case AdminPacketServerConsole: sp := PacketServerConsole{ Packet: p, } sp.Read(buffer[:p.PLength]) udp_queried_from, err := regexp.MatchString("\\[udp\\] queried from .*", sp.Text) logErrorDebug(err, "Server.Poll() : queried from") if udp_queried_from && sp.Origin == "net" { break } joined_company, err := regexp.MatchString("\\*\\*\\* .* has joined company #[0-9]+", sp.Text) logErrorDebug(err, "Server.Poll() : joined company") if joined_company && sp.Origin == "console" { r := regexp.MustCompile("\\*\\*\\* (?P.*) has joined company #(?P[0-9]+)") clientName := r.ReplaceAllString(sp.Text, "${Client}") ID8, _ := strconv.ParseInt(r.ReplaceAllString(sp.Text, "${CompanyID}"), 10, 8) companyID := uint8(ID8) - 1 for _, clt := range s.Status.Clients { if clt.Name == clientName { clt.CompanyID = companyID } } break } joined_spectators, err := regexp.MatchString("\\*\\*\\* .* has joined spectators", sp.Text) if joined_spectators && sp.Origin == "console" { r := regexp.MustCompile("\\*\\*\\* (?P.*) has joined spectators") clientName := r.ReplaceAllString(sp.Text, "${Client}") for _, clt := range s.Status.Clients { if clt.Name == clientName { clt.CompanyID = 255 } } break } /* command, err := regexp.MatchString("\\[admin\\] Rcon command from 'gottdad' \\(.*\\): .*", sp.Text) if command && sp.Origin == "net" { break } */ logInfoDebug("Server.Poll() : AdminPacketServerConsole :\n- Origin: %q\n- Text: %s", sp.Origin, sp.Text) case AdminPacketServerRCon: sp := PacketServerRCon{ Packet: p, } sp.Read(buffer[:p.PLength]) logInfoDebug("Server.Poll() : AdminPacketServerRCon :\n- ColorID: %d\n- Output: %s", sp.ColorID, sp.Output) case AdminPacketServerRConEnd: sp := PacketServerRConEnd{ Packet: p, } sp.Read(buffer[:p.PLength]) switch sp.Command { case "clients": for k, v := range s.Status.Clients { logInfoDebug("Server.Poll() : Client[%d] : %s - %d (%s)", k, v.Name, v.CompanyID, v.Address) } case "companies": for k, v := range s.Status.Companies { logInfoDebug("Server.Poll() : Company[%d] : %s", k, v.Name) } case "pause": default: logInfoDebug("Server.Poll() : AdminPacketServerRConEnd :\n- Command: %s", sp.Command) } default: logInfoDebug("Server.Poll() : Packet fully read : len : %d / type : %d", p.PLength, p.PType) os.Exit(0) } c := make([]byte, 0xFFFF) copy(c, buffer[p.PLength:]) buffer = c read -= int(p.PLength) } } func (s *ServerTTD) UpdateDate() { //logInfoDebug("Server.UpdateDate") px := PacketAdminPoll{ Packet: Packet{PType: AdminPacketAdminPoll}, UpdateType: AdminUpdateDate, UpdateID: 0, } err := s.Send(px.Bytes()) logErrorDebug(err, "Server.UpdateDate() : Send(PacketAdminPoll) : AdminUpdateDate") s.Status.UpdateDate = time.Now() } func (s *ServerTTD) UpdateClients() { //logInfoDebug("Server.UpdateClients") px := PacketAdminPoll{ Packet: Packet{PType: AdminPacketAdminPoll}, UpdateType: AdminUpdateClientInfo, UpdateID: uint32(4294967295), } err := s.Send(px.Bytes()) logErrorDebug(err, "Server.UpdateClients() : Send(PacketAdminPoll) : AdminUpdateClientInfo") s.Status.UpdateClients = time.Now() } func (s *ServerTTD) UpdateCompanies() { //logInfoDebug("Server.UpdateCompanies") px := PacketAdminPoll{ Packet: Packet{PType: AdminPacketAdminPoll}, UpdateType: AdminUpdateCompanyInfo, UpdateID: uint32(4294967295), } err := s.Send(px.Bytes()) logErrorDebug(err, "Server.UpdateCompanies() : Send(UpdateCompanies) : AdminUpdateClientInfo") s.Status.UpdateCompanies = time.Now() } func (s *ServerTTD) PruneClients() { for cltID, clt := range s.Status.Clients { if clt.LastSeen.Add(2 * updateHeartBeat).Before(time.Now()) { logInfoDebug("ServerTTD.PruneClients : deleting client #%d", cltID) delete(s.Status.Clients, cltID) } } } func (s *ServerTTD) PruneCompanies() { for coID, co := range s.Status.Companies { if co.LastSeen.Add(2 * updateHeartBeat).Before(time.Now()) { logInfoDebug("ServerTTD.PruneCompanies : deleting company #%d", coID) delete(s.Status.Companies, coID) } } } func (s *ServerTTD) ComputeClientTime() { t := time.Now() daysNow := int(t.Sub(cfg.Game.StartDate).Hours() / 24) daysLast := int(s.Data.LastClientCompute.Sub(cfg.Game.StartDate).Hours() / 24) if daysLast != daysNow { bot.SendChat(cfg.Telegram.ChatID, "New daily allotment available.") logInfoDebug("Server.ComputeClientTime : newDay : now : %d vs %d : last", daysNow, daysLast) for _, cc := range cfg.Clients { cc.TimeLeft = cc.TimeLeft + cfg.Game.DailyAllotment } } if !s.Status.Paused { diff := t.Sub(s.Data.LastClientCompute) for _, cc := range cfg.Clients { if cc.Online { if cc.TimeLeft > 0 && cc.TimeLeft < diff { bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s got not time left. Grace period of %s.", cc.Username, cfg.Game.Threshold)) } if (cc.TimeLeft+cfg.Game.Threshold) > 0 && (cc.TimeLeft+cfg.Game.Threshold) < diff { bot.SendChat(cfg.Telegram.ChatID, fmt.Sprintf("@%s got not time left. Grace period over.", cc.Username)) } cc.TimeLeft = cc.TimeLeft - diff } } } s.Data.LastClientCompute = t return } func (s *ServerTTD) DeleteCompany(id uint8) { if _, ok := s.Status.Companies[id]; ok { logInfoDebug("Server.DeleteCompany : deleting #%d", id) px := PacketAdminRCon{ Packet: Packet{PType: AdminPacketAdminRCon}, Command: fmt.Sprintf("reset_company %d", id), } s.Send(px.Bytes()) px = PacketAdminRCon{ Packet: Packet{PType: AdminPacketAdminRCon}, Command: "companies", } s.Send(px.Bytes()) } else { logInfoDebug("Server.DeleteCompany : cannot find company #%d", id) px := PacketAdminRCon{ Packet: Packet{PType: AdminPacketAdminRCon}, Command: "companies", } s.Send(px.Bytes()) } } func (s *ServerTTD) Pause() { if s.Status.Paused { return } if !s.NeedPause() { return } px := PacketAdminRCon{ Packet: Packet{PType: AdminPacketAdminRCon}, Command: "pause", } err := s.Send(px.Bytes()) s.ComputeClientTime() bot.SendChat(cfg.Telegram.ChatID, "Game is paused.") s.Status.Paused = true logErrorDebug(err, "Server.Pause : Send()") logInfoDebug("Server.Pause : pausing") } func (s *ServerTTD) Unpause() { if !s.Status.Paused { return } if s.NeedPause() { return } px := PacketAdminRCon{ Packet: Packet{PType: AdminPacketAdminRCon}, Command: "unpause", } err := s.Send(px.Bytes()) s.ComputeClientTime() bot.SendChat(cfg.Telegram.ChatID, "Game is unpaused.") s.Status.Paused = false logErrorDebug(err, "Server.Unpause : Send()") logInfoDebug("Server.Unpause : unpausing") } func (s *ServerTTD) NeedPause() bool { if !s.Status.Initialized { logInfoDebug("Server.NeedPause : not initialized yet") return false } reason := s.NeedPauseReason() if reason == "" { return false } else { //logInfoDebug("Server.NeedPause : %s", reason) return true } } func (s *ServerTTD) NeedPauseReason() string { if !s.Status.Initialized { return "" } dupl := make(map[uint8]struct{}) online := 0 for cltID, clt := range s.Status.Clients { if cltID != 1 && clt.CompanyID != 255 { online++ if _, ok := s.Status.Companies[clt.CompanyID]; !ok { return "company unidentified" } } if clt.Paused { return "user paused" } if _, ok := dupl[clt.CompanyID]; ok && clt.CompanyID != 255 { return "more than one user in company" } dupl[clt.CompanyID] = struct{}{} } for coID, co := range s.Status.Companies { if !co.Protected { return "company unprotected" } if !cfg.CompanyIsRegistered(coID) { return "company unregistered" } } for _, cc := range cfg.Clients { if cc.Online && (cc.TimeLeft+cfg.Game.Threshold) < 0 { return "no time left" } } if online == 0 { return "no players online" } return "" } func (s *ServerTTD) Initialize() { var err error if s.Status.Initialized { return } px := PacketAdminUpdateFrequency{ Packet: Packet{PType: AdminPacketAdminUpdateFrequency}, UpdateType: AdminUpdateDate, UpdateFrequency: AdminFrequencyDaily, } err = s.Send(px.Bytes()) logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateDate)") px = PacketAdminUpdateFrequency{ Packet: Packet{PType: AdminPacketAdminUpdateFrequency}, UpdateType: AdminUpdateClientInfo, UpdateFrequency: AdminFrequencyAutomatic, } err = s.Send(px.Bytes()) logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateClientInfo)") px = PacketAdminUpdateFrequency{ Packet: Packet{PType: AdminPacketAdminUpdateFrequency}, UpdateType: AdminUpdateCompanyInfo, UpdateFrequency: AdminFrequencyAutomatic, } err = s.Send(px.Bytes()) logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateCompanyInfo)") px = PacketAdminUpdateFrequency{ Packet: Packet{PType: AdminPacketAdminUpdateFrequency}, UpdateType: AdminUpdateChat, UpdateFrequency: AdminFrequencyAutomatic, } err = s.Send(px.Bytes()) logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateChat)") px = PacketAdminUpdateFrequency{ Packet: Packet{PType: AdminPacketAdminUpdateFrequency}, UpdateType: AdminUpdateConsole, UpdateFrequency: AdminFrequencyAutomatic, } err = s.Send(px.Bytes()) logErrorDebug(err, "Server.Initialize() : Send(AdminUpdateConsole)") s.Status.Initialized = true s.UpdateCompanies() s.UpdateClients() } func (s *ServerTTD) SetPasswd(id uint8, passwd string) { px := PacketAdminRCon{ Packet: Packet{PType: AdminPacketAdminRCon}, Command: fmt.Sprintf("company_pw %d %s", id, passwd), } err := s.Send(px.Bytes()) logErrorDebug(err, "Server.SetPasswd() : Send(AdminPacketAdminRCon)") return }