Skip to content
Merged
29 changes: 28 additions & 1 deletion anticheat/anticheat.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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), "", "")
Expand All @@ -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) {
Expand All @@ -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), "", "")
}
Expand Down
11 changes: 9 additions & 2 deletions channel/handlers_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))))

Expand Down
1 change: 1 addition & 0 deletions channel/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type Server struct {
guilds map[int32]*guild
events map[int32]*event
rates rates
autoBan bool
ac *anticheat.AntiCheat
}

Expand Down
1 change: 1 addition & 0 deletions config_dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions config_world.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions docker/docker_config_world.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions helm/templates/configmaps.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ mysql:
port: 3306
user: root
world:
autoBan: false
dropRate: 1.1
expRate: 1.1
jitter: 0
Expand Down
1 change: 1 addition & 0 deletions internal/serialisable_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type World struct {
CashShop CashShop
Rates Rates
DefaultRates Rates
AutoBan bool
}

func (w *World) GenerateInfoPacket() mpacket.Packet {
Expand Down
3 changes: 3 additions & 0 deletions server_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions server_world.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions world/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Loading