Skip to content

Commit

Permalink
add anti cheat
Browse files Browse the repository at this point in the history
  • Loading branch information
ba0f3 committed Nov 29, 2019
1 parent 92d946b commit 38c35cd
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 0 deletions.
22 changes: 22 additions & 0 deletions config.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
auto_reconnect = 1
debug = 1
; Stam Web API key can be obtained from https://steamcommunity.com/dev/apikey
steam_api_key = XXXXXXXXXXXXXXXXXXXXXXXX
; WebRCON server address, incluing (including rcon port)
rcon_address = "127.0.0.1:28016"
; WebRCON password
rcon_password = "xxxxxxxxxxxxxxxxxxxxxx"
; Anti cheat configs
anticheat = "autoban1,autoban2,autoban3"

[autoban1]
number_of_vac_bans = 1
days_since_last_ban = 180

[autoban2]
number_of_vac_bans = 2
days_since_last_ban = 540

[autoban3]
number_of_vac_bans = 3
days_since_last_ban = 0
14 changes: 14 additions & 0 deletions rustadmin.nimble
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Package

version = "0.1.0"
author = "Huy Doan"
description = "A new awesome nimble package"
license = "MIT"
srcDir = "src"
bin = @["rustadmin"]



# Dependencies

requires "nim >= 1.0.2", "ws >= 0.1.0", "chronicles>= chronicles_runtime_filtering", "sim"
1 change: 1 addition & 0 deletions src/nim.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-d:chronicles_runtime_filtering
140 changes: 140 additions & 0 deletions src/rustadmin.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import json, ws, asyncdispatch, sim, strformat, tables, json, strutils, httpclient, pegs, chronicles

type
AntiCheat = object
numberOfVacBans: int
daysSinceLastBan: int
Config = object
autoReconnect: bool
debug: bool
steamApiKey: string
rconAddress: string
rconPassword: string
anticheat: seq[AntiCheat]
Callback = proc(node: JsonNode): Future[void]

let
PLAYER_JOINED_MESSAGE = peg"^{\letter+}\[\d+\/{\d+}\]' has entered the game'"
GET_PLAYER_BANS = "http://api.steampowered.com/ISteamUser/GetPlayerBans/v1/?key=$#&steamids=$#"

var
config: Config
s: WebSocket
callbacks = initTable[int, Callback]()
id: int = 0

proc sendCmd(cmd: string, callback: Callback = nil) {.async.} =
inc(id)
if callback != nil:
callbacks[id] = callback
debug "Execute RCON command", cmd
await s.send("{\"Identifier\":" & $id & ",\"Message\":\"" & cmd & "\",\"Name\":\"rustadm.nim\"}")

proc checkVACBan*(steamId: string) {.async.} =
let
endpoint = GET_PLAYER_BANS % [config.steamApiKey, steamId]
client = newAsyncHttpClient()
data = await client.getContent(endpoint)
node = parseJson(data)
if node["players"].len == 0:
return
for player in node["players"].items:
if player["VACBanned"].getBool:
let
steamId = player["SteamId"].getStr
numberOfVacBans = player["NumberOfVACBans"].getInt
daysSinceLastBan = player["DaysSinceLastBan"].getInt
for ac in config.anticheat:
if numberOfVacBans >= ac.numberOfVacBans:
info "Autoban user", steamId
if ac.daysSinceLastBan <= 0:
await sendCmd(fmt"banid {steamId} Player VAC bans ({numberOfVacBans}) >= {ac.daysSinceLastBan}")
break
elif daysSinceLastBan < ac.daysSinceLastBan:
await sendCmd(fmt"banid {steamId} Player days since last VAC bans ({daysSinceLastBan}) < {ac.daysSinceLastBan}")
break
else:
discard

proc checkVACBan*(steamIds: seq[string]) {.async.} =
var steamIds = steamIds
if steamIds.len > 99:
var temp: seq[string]
while steamIds.len > 99:
temp.add(steamIds.pop())
if temp.len > 99:
await checkVACBan(steamIds.join(","))
temp = @[]
await checkVACBan(steamIds.join(","))

proc onPlayerList(node: JsonNode) {.async.} =
if node.len == 0:
return
info "Checking VAC Bans for existing players", count=node.len
var ids: seq[string]
for player in node.items:
ids.add(player["SteamID"].getStr)
await checkVACBan(ids)


proc onConnect() {.async.} =
info "Connected"
await sendCmd("playerlist", onPlayerList)

proc onMessage(packet: string) {.async.} =
try:
let
data = parseJson(packet)
id = data["Identifier"].getInt
if callbacks.hasKey(id):
let
cb = callbacks[id]
message = parseJson(data["Message"].getStr)
callbacks.del(id)
await cb(message)
else:
let message = data["Message"].getStr
if message =~ PLAYER_JOINED_MESSAGE:
info "Checking VAC Bans for new player", name=matches[0], steamId=matches[1]
await checkVACBan(matches[1])
except JsonParsingError, KeyError:
let e = getCurrentException()
error "Exception caught", name=e.name, message=e.msg

proc connect() {.async.} =
while true:
try:
s = await newWebSocket(fmt"ws://{config.rconAddress}/{config.rconPassword}")
#s = await newWebSocket("ws://127.0.0.1:8080")
await onConnect()
break
except OSError, IOError:
debug "Connection error, retry after 2s.."
await sleepAsync(2000)

proc main() {.async.} =
info "Starting"
await connect()
while true:
if s.readyState == Open:
debug "Waiting for messages"
try:
let packet = await s.receiveStrPacket()
await onMessage(packet)
except WebSocketError:
error "Connection closed"
s.readyState = Closed
elif s.readyState == Closed and config.autoReconnect:
info "Reconnecting"
await connect()


when isMainModule:
config = to[Config]("config.ini")

if config.debug:
setLogLevel(DEBUG)
else:
setLogLevel(INFO)

waitFor main()

0 comments on commit 38c35cd

Please sign in to comment.