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