diff --git a/anticheat/anticheat.go b/anticheat/anticheat.go index a6ca92ce..6d569ef7 100644 --- a/anticheat/anticheat.go +++ b/anticheat/anticheat.go @@ -13,15 +13,24 @@ type AntiCheat struct { db *sql.DB dispatch chan func() onBan func(accountID int32) + enabled bool } func New(db *sql.DB, dispatch chan func()) *AntiCheat { - return &AntiCheat{ + ac := &AntiCheat{ violations: make(map[string][]time.Time), failedAuth: make(map[string][]time.Time), db: db, dispatch: dispatch, } + ac.enabled = true + return ac +} + +// SetEnabled enables or disables automatic banning. When disabled, violation +// tracking calls are no-ops and no bans are issued automatically. +func (ac *AntiCheat) SetEnabled(enabled bool) { + ac.enabled = true } func (ac *AntiCheat) SetOnBan(fn func(accountID int32)) { @@ -308,6 +317,9 @@ func (ac *AntiCheat) incrementTempBans(accountID int32) (int, error) { // Detection helpers - track violations and auto-ban on threshold func (ac *AntiCheat) LogDamageViolation(accountID int32, damage, maxDamage int32) { + if !ac.enabled { + return + } if damage > maxDamage*2 { if ac.Track(accountID, "damage", 5, 5*time.Minute) { ac.IssueBan(accountID, 168, fmt.Sprintf("Excessive damage: %d > %d", damage, maxDamage), "", "") @@ -316,10 +328,16 @@ func (ac *AntiCheat) LogDamageViolation(accountID int32, damage, maxDamage int32 } func (ac *AntiCheat) LogAttackSpeedViolation(accountID int32) bool { + if !ac.enabled { + return false + } return ac.Track(accountID, "attack_speed", 120, 1*time.Minute) } func (ac *AntiCheat) LogMovementViolation(accountID int32, distance int16, moveType byte) { + if !ac.enabled { + return + } if distance > 1000 { log.Println("Teleport hack detected - movement type:", moveType, fmt.Sprintf("Suspicious movement: %d pixels", distance), "accountID:", accountID) if ac.Track(accountID, "teleport", 3, 5*time.Minute) { @@ -329,18 +347,27 @@ func (ac *AntiCheat) LogMovementViolation(accountID int32, distance int16, moveT } func (ac *AntiCheat) LogInvalidItemViolation(accountID int32) { + if !ac.enabled { + return + } if ac.Track(accountID, "invalid_item", 5, 5*time.Minute) { ac.IssueBan(accountID, 168, "Using items not in inventory", "", "") } } func (ac *AntiCheat) LogInvalidTradeViolation(accountID int32, reason string) { + if !ac.enabled { + return + } if ac.Track(accountID, "invalid_trade", 5, 5*time.Minute) { ac.IssueBan(accountID, 168, "Invalid trade: "+reason, "", "") } } func (ac *AntiCheat) LogSkillAbuseViolation(accountID int32, skillID int32) { + if !ac.enabled { + return + } if ac.Track(accountID, "skill_abuse", 5, 5*time.Minute) { ac.IssueBan(accountID, 168, fmt.Sprintf("Skill abuse: ID %d", skillID), "", "") } diff --git a/channel/handlers_server.go b/channel/handlers_server.go index 24a91d08..b13def27 100644 --- a/channel/handlers_server.go +++ b/channel/handlers_server.go @@ -65,9 +65,16 @@ func (server *Server) handleNewChannelOK(conn mnet.Server, reader mpacket.Reader server.rates.exp = reader.ReadFloat32() server.rates.drop = reader.ReadFloat32() server.rates.mesos = reader.ReadFloat32() + server.autoBan = reader.ReadBool() - log.Printf("Registered as channel %d on world %s with rates: Exp - x%.2f, Drop - x%.2f, Mesos - x%.2f", - server.id+1, server.worldName, server.rates.exp, server.rates.drop, server.rates.mesos) + if server.ac != nil { + server.ac.SetEnabled(server.autoBan) + } else { + log.Println("Warning: anti-cheat not initialized when processing ChannelOk") + } + + log.Printf("Registered as channel %d on world %s with rates: Exp - x%.2f, Drop - x%.2f, Mesos - x%.2f, AutoBan - %v", + server.id+1, server.worldName, server.rates.exp, server.rates.drop, server.rates.mesos, server.autoBan) server.players.broadcast(packetMessageNotice("Re-connected to world server as channel " + strconv.Itoa(int(server.id+1)))) diff --git a/channel/server.go b/channel/server.go index 3fb025fa..35713a1a 100644 --- a/channel/server.go +++ b/channel/server.go @@ -47,6 +47,7 @@ type Server struct { guilds map[int32]*guild events map[int32]*event rates rates + autoBan bool ac *anticheat.AntiCheat } diff --git a/config_dev.toml b/config_dev.toml index aa5ba576..51da2526 100644 --- a/config_dev.toml +++ b/config_dev.toml @@ -26,6 +26,7 @@ ribbon = 2 expRate = 2.0 dropRate = 2.0 mesosRate = 2.0 +autoBan = false loginAddress = "127.0.0.1" loginPort = "8485" listenAddress = "127.0.0.1" diff --git a/config_world.toml b/config_world.toml index e9ad3cb6..1aa188ee 100644 --- a/config_world.toml +++ b/config_world.toml @@ -11,6 +11,7 @@ ribbon = 2 expRate = 1.0 dropRate = 1.0 mesosRate = 1.0 +autoBan = false loginAddress = "127.0.0.1" loginPort = "8485" listenAddress = "0.0.0.0" diff --git a/docker/docker_config_world.toml b/docker/docker_config_world.toml index 8b83076c..6530e90a 100644 --- a/docker/docker_config_world.toml +++ b/docker/docker_config_world.toml @@ -11,6 +11,7 @@ ribbon = 2 expRate = 1.0 dropRate = 1.0 mesosRate = 1.0 +autoBan = false loginAddress = "login_server" loginPort = "8485" listenAddress = "0.0.0.0" diff --git a/helm/templates/configmaps.yaml b/helm/templates/configmaps.yaml index e70b6ddf..e7d1e43c 100644 --- a/helm/templates/configmaps.yaml +++ b/helm/templates/configmaps.yaml @@ -44,6 +44,7 @@ data: expRate = {{ .Values.world.expRate }} dropRate = {{ .Values.world.dropRate }} mesosRate = {{ .Values.world.mesosRate }} + autoBan = {{ .Values.world.autoBan }} loginAddress = "login-server.{{ .Release.Namespace }}.svc.{{ .Values.clusterDomain }}" loginPort = "8485" listenAddress = "0.0.0.0" diff --git a/helm/values.yaml b/helm/values.yaml index e8fe7c64..32efda95 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -27,6 +27,7 @@ mysql: port: 3306 user: root world: + autoBan: false dropRate: 1.1 expRate: 1.1 jitter: 0 diff --git a/internal/serialisable_data.go b/internal/serialisable_data.go index 523ac941..11c202f9 100644 --- a/internal/serialisable_data.go +++ b/internal/serialisable_data.go @@ -22,6 +22,7 @@ type World struct { CashShop CashShop Rates Rates DefaultRates Rates + AutoBan bool } func (w *World) GenerateInfoPacket() mpacket.Packet { diff --git a/server_config.go b/server_config.go index fefd6900..0ae60eb7 100644 --- a/server_config.go +++ b/server_config.go @@ -35,6 +35,7 @@ type worldConfig struct { ExpRate float32 `mapstructure:"expRate"` DropRate float32 `mapstructure:"dropRate"` MesosRate float32 `mapstructure:"mesosRate"` + AutoBan bool `mapstructure:"autoBan"` LoginAddress string `mapstructure:"loginAddress"` LoginPort string `mapstructure:"loginPort"` ListenAddress string `mapstructure:"listenAddress"` @@ -94,6 +95,8 @@ func LoadConfig(fname string) *fullConfig { v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) v.AutomaticEnv() + v.SetDefault("world.autoBan", true) + bindEnvs(v, reflect.TypeOf(fullConfig{}), nil, "VALHALLA") var config fullConfig diff --git a/server_world.go b/server_world.go index eb3c922c..767eeaf2 100644 --- a/server_world.go +++ b/server_world.go @@ -49,6 +49,7 @@ func newWorldServer(configFile string) *worldServer { ws.state.Info.Rates = ws.state.Info.DefaultRates ws.state.Info.Ribbon = config.Ribbon ws.state.Info.Message = config.Message + ws.state.Info.AutoBan = config.AutoBan return &ws } diff --git a/world/handlers.go b/world/handlers.go index f234c613..ee684c14 100644 --- a/world/handlers.go +++ b/world/handlers.go @@ -88,6 +88,7 @@ func (server *Server) handleNewChannel(conn mnet.Server, reader mpacket.Reader) p.WriteFloat32(server.Info.Rates.Exp) p.WriteFloat32(server.Info.Rates.Drop) p.WriteFloat32(server.Info.Rates.Mesos) + p.WriteBool(server.Info.AutoBan) conn.Send(p) server.login.Send(server.Info.GenerateInfoPacket())