286 lines
8.6 KiB
Elixir
286 lines
8.6 KiB
Elixir
defmodule Odinsea.AntiCheat do
|
|
@moduledoc """
|
|
Anti-Cheat system for Odinsea.
|
|
|
|
Ported from Java:
|
|
- client.anticheat.CheatTracker
|
|
- client.anticheat.CheatingOffense
|
|
- client.anticheat.CheatingOffenseEntry
|
|
- server.AutobanManager
|
|
|
|
This module provides:
|
|
- Damage validation
|
|
- Movement validation
|
|
- Item validation
|
|
- EXP validation
|
|
- Autoban system with threshold-based banning
|
|
- Offense tracking with expiration
|
|
"""
|
|
|
|
alias Odinsea.AntiCheat.{CheatTracker, CheatingOffense, CheatingOffenseEntry}
|
|
|
|
# Re-export main modules
|
|
defdelegate start_tracker(character_id, character_pid), to: CheatTracker
|
|
defdelegate stop_tracker(character_id), to: CheatTracker
|
|
defdelegate register_offense(character_id, offense, param \\ nil), to: CheatTracker
|
|
defdelegate get_points(character_id), to: CheatTracker
|
|
defdelegate get_summary(character_id), to: CheatTracker
|
|
defdelegate check_attack(character_id, skill_id, tick_count), to: CheatTracker
|
|
defdelegate check_take_damage(character_id, damage), to: CheatTracker
|
|
defdelegate check_same_damage(character_id, damage, expected), to: CheatTracker
|
|
defdelegate check_drop(character_id, dc \\ false), to: CheatTracker
|
|
defdelegate check_message(character_id), to: CheatTracker
|
|
defdelegate can_smega(character_id), to: CheatTracker
|
|
defdelegate can_avatar_smega(character_id), to: CheatTracker
|
|
defdelegate can_bbs(character_id), to: CheatTracker
|
|
defdelegate update_tick(character_id, new_tick), to: CheatTracker
|
|
defdelegate check_summon_attack(character_id), to: CheatTracker
|
|
defdelegate reset_summon_attack(character_id), to: CheatTracker
|
|
defdelegate check_familiar_attack(character_id), to: CheatTracker
|
|
defdelegate reset_familiar_attack(character_id), to: CheatTracker
|
|
defdelegate set_attacks_without_hit(character_id, increase), to: CheatTracker
|
|
defdelegate get_attacks_without_hit(character_id), to: CheatTracker
|
|
defdelegate check_move_monsters(character_id, position), to: CheatTracker
|
|
defdelegate get_offenses(character_id), to: CheatTracker
|
|
|
|
# CheatingOffense access
|
|
def get_offense_types do
|
|
CheatingOffense.all_offenses()
|
|
end
|
|
|
|
def get_offense_points(offense_type) do
|
|
CheatingOffense.get_points(offense_type)
|
|
end
|
|
|
|
def should_autoban?(offense_type, count) do
|
|
CheatingOffense.should_autoban?(offense_type, count)
|
|
end
|
|
|
|
def get_ban_type(offense_type) do
|
|
CheatingOffense.get_ban_type(offense_type)
|
|
end
|
|
|
|
def is_enabled?(offense_type) do
|
|
CheatingOffense.is_enabled?(offense_type)
|
|
end
|
|
|
|
def get_validity_duration(offense_type) do
|
|
CheatingOffense.get_validity_duration(offense_type)
|
|
end
|
|
end
|
|
|
|
defmodule Odinsea.AntiCheat.CheatingOffense do
|
|
@moduledoc """
|
|
Defines all cheating offenses and their properties.
|
|
|
|
Ported from: client.anticheat.CheatingOffense.java
|
|
|
|
Each offense has:
|
|
- points: Points added per occurrence
|
|
- validity_duration: How long the offense stays active (ms)
|
|
- autoban_count: Number of occurrences before autoban (-1 = disabled)
|
|
- ban_type: 0 = disabled, 1 = ban, 2 = DC
|
|
"""
|
|
|
|
@type offense_type :: atom()
|
|
@type ban_type :: :disabled | :ban | :disconnect
|
|
|
|
@offenses %{
|
|
# Offense type => {points, validity_duration_ms, autoban_count, ban_type}
|
|
# ban_type: 0 = disabled, 1 = ban, 2 = disconnect
|
|
|
|
# Attack speed offenses
|
|
:fast_summon_attack => {5, 6_000, 50, :disconnect},
|
|
:fast_attack => {5, 6_000, 200, :disconnect},
|
|
:fast_attack_2 => {5, 6_000, 500, :disconnect},
|
|
|
|
# Movement offenses
|
|
:move_monsters => {5, 30_000, 500, :disconnect},
|
|
:high_jump => {1, 60_000, -1, :disabled},
|
|
:using_faraway_portal => {1, 60_000, 100, :disabled},
|
|
|
|
# Regen offenses
|
|
:fast_hp_mp_regen => {5, 20_000, 100, :disconnect},
|
|
:regen_high_hp => {10, 30_000, 1000, :disconnect},
|
|
:regen_high_mp => {10, 30_000, 1000, :disconnect},
|
|
|
|
# Damage offenses
|
|
:same_damage => {5, 180_000, -1, :disconnect},
|
|
:attack_without_getting_hit => {1, 30_000, 1200, :disabled},
|
|
:high_damage_magic => {5, 30_000, -1, :disabled},
|
|
:high_damage_magic_2 => {10, 180_000, -1, :ban},
|
|
:high_damage => {5, 30_000, -1, :disabled},
|
|
:high_damage_2 => {10, 180_000, -1, :ban},
|
|
:exceed_damage_cap => {5, 60_000, 800, :disabled},
|
|
:attack_faraway_monster => {5, 180_000, -1, :disabled},
|
|
:attack_faraway_monster_summon => {5, 180_000, 200, :disconnect},
|
|
|
|
# Item offenses
|
|
:itemvac_client => {3, 10_000, 100, :disabled},
|
|
:itemvac_server => {2, 10_000, 100, :disconnect},
|
|
:pet_itemvac_client => {3, 10_000, 100, :disabled},
|
|
:pet_itemvac_server => {2, 10_000, 100, :disconnect},
|
|
:using_unavailable_item => {1, 300_000, -1, :ban},
|
|
|
|
# Combat offenses
|
|
:fast_take_damage => {1, 60_000, 100, :disabled},
|
|
:high_avoid => {5, 180_000, 100, :disabled},
|
|
:mismatching_bulletcount => {1, 300_000, -1, :ban},
|
|
:etc_explosion => {1, 300_000, -1, :ban},
|
|
:attacking_while_dead => {1, 300_000, -1, :ban},
|
|
:exploding_nonexistant => {1, 300_000, -1, :ban},
|
|
:summon_hack => {1, 300_000, -1, :ban},
|
|
:summon_hack_mobs => {1, 300_000, -1, :ban},
|
|
:aran_combo_hack => {1, 600_000, 50, :disconnect},
|
|
:heal_attacking_undead => {20, 30_000, 100, :disabled},
|
|
|
|
# Social offenses
|
|
:faming_self => {1, 300_000, -1, :ban},
|
|
:faming_under_15 => {1, 300_000, -1, :ban}
|
|
}
|
|
|
|
@doc """
|
|
Returns all offense types.
|
|
"""
|
|
@spec all_offenses() :: list(offense_type())
|
|
def all_offenses do
|
|
Map.keys(@offenses)
|
|
end
|
|
|
|
@doc """
|
|
Get the points for an offense type.
|
|
"""
|
|
@spec get_points(offense_type()) :: integer()
|
|
def get_points(offense_type) do
|
|
case Map.get(@offenses, offense_type) do
|
|
{points, _, _, _} -> points
|
|
nil -> 0
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Get the validity duration for an offense type.
|
|
"""
|
|
@spec get_validity_duration(offense_type()) :: integer()
|
|
def get_validity_duration(offense_type) do
|
|
case Map.get(@offenses, offense_type) do
|
|
{_, duration, _, _} -> duration
|
|
nil -> 0
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Check if an offense should trigger autoban at the given count.
|
|
"""
|
|
@spec should_autoban?(offense_type(), integer()) :: boolean()
|
|
def should_autoban?(offense_type, count) do
|
|
case Map.get(@offenses, offense_type) do
|
|
{_, _, autoban_count, _} when autoban_count > 0 -> count >= autoban_count
|
|
_ -> false
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Get the ban type for an offense.
|
|
"""
|
|
@spec get_ban_type(offense_type()) :: ban_type()
|
|
def get_ban_type(offense_type) do
|
|
case Map.get(@offenses, offense_type) do
|
|
{_, _, _, ban_type} -> ban_type
|
|
nil -> :disabled
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Check if an offense type is enabled (ban_type >= 1).
|
|
"""
|
|
@spec is_enabled?(offense_type()) :: boolean()
|
|
def is_enabled?(offense_type) do
|
|
case get_ban_type(offense_type) do
|
|
:disabled -> false
|
|
_ -> true
|
|
end
|
|
end
|
|
end
|
|
|
|
defmodule Odinsea.AntiCheat.CheatingOffenseEntry do
|
|
@moduledoc """
|
|
Represents a single cheating offense entry for a character.
|
|
|
|
Ported from: client.anticheat.CheatingOffenseEntry.java
|
|
"""
|
|
|
|
alias Odinsea.AntiCheat.CheatingOffense
|
|
|
|
defstruct [
|
|
:offense_type,
|
|
:character_id,
|
|
:count,
|
|
:last_offense_time,
|
|
:param
|
|
]
|
|
|
|
@type t :: %__MODULE__{
|
|
offense_type: CheatingOffense.offense_type(),
|
|
character_id: integer(),
|
|
count: integer(),
|
|
last_offense_time: integer() | nil,
|
|
param: String.t() | nil
|
|
}
|
|
|
|
@doc """
|
|
Creates a new offense entry.
|
|
"""
|
|
@spec new(CheatingOffense.offense_type(), integer()) :: t()
|
|
def new(offense_type, character_id) do
|
|
%__MODULE__{
|
|
offense_type: offense_type,
|
|
character_id: character_id,
|
|
count: 0,
|
|
last_offense_time: nil,
|
|
param: nil
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Increments the offense count and updates timestamp.
|
|
"""
|
|
@spec increment(t()) :: t()
|
|
def increment(entry) do
|
|
%__MODULE__{entry |
|
|
count: entry.count + 1,
|
|
last_offense_time: System.monotonic_time(:millisecond)
|
|
}
|
|
end
|
|
|
|
@doc """
|
|
Sets a parameter for the offense (additional info).
|
|
"""
|
|
@spec set_param(t(), String.t()) :: t()
|
|
def set_param(entry, param) do
|
|
%__MODULE__{entry | param: param}
|
|
end
|
|
|
|
@doc """
|
|
Checks if the offense entry has expired.
|
|
"""
|
|
@spec expired?(t()) :: boolean()
|
|
def expired?(entry) do
|
|
if entry.last_offense_time == nil do
|
|
false
|
|
else
|
|
validity_duration = CheatingOffense.get_validity_duration(entry.offense_type)
|
|
now = System.monotonic_time(:millisecond)
|
|
now - entry.last_offense_time > validity_duration
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Calculates the total points for this offense entry.
|
|
"""
|
|
@spec get_points(t()) :: integer()
|
|
def get_points(entry) do
|
|
entry.count * CheatingOffense.get_points(entry.offense_type)
|
|
end
|
|
end
|