Иконка ресурса

FREE RustLauncher : 1.4.4 1.4.4

Нет прав для скачивания
C#:
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Oxide.Core;
using Oxide.Core.Configuration;
using Oxide.Core.Plugins;
using UnityEngine;

namespace Oxide.Plugins
{
    [Info("RustLauncher", "Arturka", "1.4.4")]
    [Description("Интеграция Rust-сервера с HTTP-сервером SABR RUST Launcher и системой банов")]
    public class RustLauncher : RustPlugin
    {
        private const string LauncherSourceName = "SabrRustLauncher";
        private const string UnknownLauncherName = "NoNameLauncher";
        private const string ErrorCheckHwid = "ErrorCheckHwid";
        private const string ErrorCheckLauncher = "ErrorCheckLauncher";

        #region Configuration

        private Configuration _config;

        private class ConnectionSettings
        {
            [JsonProperty("Адрес сервера лаунчера")]
            public string LauncherServerUrl = "http://localhost:3000";

            [JsonProperty("Интервал проверки подключения (секунды)")]
            public int ConnectionCheckIntervalSeconds = 900;

            [JsonProperty("Задержка перед первой проверкой связи (секунды)")]
            public float ConnectionCheckStartupDelaySeconds = 0f;

            [JsonProperty("Порт HTTP API плагина на сервере Rust")]
            public int PluginHttpApiPort = 8082;

            [JsonProperty("API Ключ сервера")]
            public string ServerApiKey = "ENTER_API_KEY_SERVER";
        }

        private class LoggingSettings
        {
            [JsonProperty("Логировать вход игрока и источник лаунчера в консоль")]
            public bool LogPlayerJoinAndLauncherSource = false;

            [JsonProperty("Задержка перед проверкой источника лаунчера (секунды)")]
            public float LauncherSourceCheckDelaySeconds = 2f;
        }

        private class SecuritySettings
        {
            [JsonProperty("Разрешить вход только с своего лаунчера")]
            public bool AllowJoinOnlyFromLauncher = false;

            [JsonProperty("Пермишен с которым не будет кикать игрока если тот без лаунчера")]
            public string PermissionBypassLauncherKick = "rustlauncher.noneedlauncher";

            [JsonProperty("Пермишен с которым игроку нужно иметь лаунчер не зависимо от того, нужен ли он всем")]
            public string PermissionRequireLauncher = "rustlauncher.needlauncher";

            [JsonProperty("Задержка проверки безопасности перед киком (секунды)")]
            public float SecurityKickCheckDelaySeconds = 2f;
        }

        private class BonusSettings
        {
            [JsonProperty("Включить бонус игроку за вход с своего лаунчера")]
            public bool EnableLauncherJoinBonus = false;

            [JsonProperty("Выберите, что игроку будет выдано (1 - Group, 2 - Permissions, 3 - Group and Permissions)")]
            public int BonusGrantMode = 1;

            [JsonProperty("Группы которые будут выданы")]
            public List<string> BonusGroups = new List<string>();

            [JsonProperty("Пермишены которые будут выданы")]
            public List<string> BonusPermissions = new List<string>();

            [JsonProperty("Задержка перед проверкой бонуса (секунды)")]
            public float BonusCheckDelaySeconds = 2f;
        }

        private string ConfigFilePath
        {
            get { return Path.Combine(Interface.Oxide.ConfigDirectory, Name + ".json"); }
        }

        private class SerializableConfiguration
        {
            public string ToJson()
            {
                return JsonConvert.SerializeObject(this);
            }

            public Dictionary<string, object> ToDictionary()
            {
                return JsonHelper.Deserialize(ToJson()) as Dictionary<string, object>;
            }
        }

        private static class JsonHelper
        {
            public static object Deserialize(string json)
            {
                return ToObject(JToken.Parse(json));
            }

            private static object ToObject(JToken token)
            {
                switch (token.Type)
                {
                    case JTokenType.Object:
                        return token.Children<JProperty>()
                            .ToDictionary(prop => prop.Name, prop => ToObject(prop.Value));

                    case JTokenType.Array:
                        return token.Select(ToObject).ToList();

                    default:
                        return ((JValue)token).Value;
                }
            }
        }

        private class Configuration : SerializableConfiguration
        {
            [JsonProperty("Настройка подключения")]
            public ConnectionSettings ConnectionSettings = new ConnectionSettings();

            [JsonProperty("Настройка логирования")]
            public LoggingSettings LoggingSettings = new LoggingSettings();

            [JsonProperty("Настройка безопасности")]
            public SecuritySettings SecuritySettings = new SecuritySettings();

            [JsonProperty("Настройка бонусов")]
            public BonusSettings BonusSettings = new BonusSettings();
        }

        private string _serverUrl = "http://localhost:3000";
        private int _checkInterval = 900;
        private int _pluginApiPort = 8082;
        private string _serverApiKey = "ENTER_API_KEY_SERVER";
        private string _serverCategory = "";
        private bool _apiKeyValidated = false;
        private bool _launcherUnreachableLogged = false;
        private readonly HashSet<ulong> _onlineSteamIds = new HashSet<ulong>();

        private bool HasConfiguredApiKey =>
            !string.IsNullOrEmpty(_serverApiKey)
            && !string.Equals(_serverApiKey, "ENTER_API_KEY_SERVER", StringComparison.Ordinal);

        private Configuration GetDefaultConfiguration()
        {
            return new Configuration();
        }

        private Configuration GetConfigStructureTemplate()
        {
            return GetDefaultConfiguration();
        }

        private void ApplyConfiguration()
        {
            if (_config == null)
                _config = GetDefaultConfiguration();

            if (_config.ConnectionSettings == null)
                _config.ConnectionSettings = new ConnectionSettings();
            if (_config.LoggingSettings == null)
                _config.LoggingSettings = new LoggingSettings();
            if (_config.SecuritySettings == null)
                _config.SecuritySettings = new SecuritySettings();
            if (_config.BonusSettings == null)
                _config.BonusSettings = new BonusSettings();
            if (_config.BonusSettings.BonusGroups == null)
                _config.BonusSettings.BonusGroups = new List<string>();
            if (_config.BonusSettings.BonusPermissions == null)
                _config.BonusSettings.BonusPermissions = new List<string>();

            _serverUrl = (_config.ConnectionSettings.LauncherServerUrl ?? "http://localhost:3000").TrimEnd('/');
            _checkInterval = _config.ConnectionSettings.ConnectionCheckIntervalSeconds;
            _pluginApiPort = _config.ConnectionSettings.PluginHttpApiPort;
            _serverApiKey = (_config.ConnectionSettings.ServerApiKey ?? "ENTER_API_KEY_SERVER").Trim();

            RegisterConfiguredPermissions();
        }

        private void RegisterConfiguredPermissions()
        {
            if (_config?.SecuritySettings == null)
                return;

            string bypassPerm = (_config.SecuritySettings.PermissionBypassLauncherKick ?? string.Empty).Trim();
            if (!string.IsNullOrEmpty(bypassPerm))
                permission.RegisterPermission(bypassPerm, this);

            string requirePerm = (_config.SecuritySettings.PermissionRequireLauncher ?? string.Empty).Trim();
            if (!string.IsNullOrEmpty(requirePerm))
                permission.RegisterPermission(requirePerm, this);
        }

        private Dictionary<string, object> ReadConfigFileDictionary()
        {
            try
            {
                return JsonHelper.Deserialize(File.ReadAllText(ConfigFilePath)) as Dictionary<string, object>;
            }
            catch
            {
                return null;
            }
        }

        private bool IsConfigOutdated()
        {
            Dictionary<string, object> fileDict = ReadConfigFileDictionary();
            if (fileDict == null)
                return false;

            return IsConfigMissingKeys(GetConfigStructureTemplate().ToDictionary(), fileDict);
        }

        private static bool IsConfigMissingKeys(Dictionary<string, object> defaults, Dictionary<string, object> current)
        {
            foreach (KeyValuePair<string, object> pair in defaults)
            {
                object currentValue;
                if (!current.TryGetValue(pair.Key, out currentValue))
                    return true;

                Dictionary<string, object> defaultNested = pair.Value as Dictionary<string, object>;
                if (defaultNested == null)
                    continue;

                Dictionary<string, object> currentNested = AsStringObjectDictionary(currentValue);
                if (currentNested == null || IsConfigMissingKeys(defaultNested, currentNested))
                    return true;
            }

            return false;
        }

        private static Dictionary<string, object> AsStringObjectDictionary(object value)
        {
            Dictionary<string, object> dict = value as Dictionary<string, object>;
            if (dict != null)
                return dict;

            IDictionary genericDict = value as IDictionary;
            if (genericDict != null)
            {
                Dictionary<string, object> result = new Dictionary<string, object>();
                foreach (DictionaryEntry entry in genericDict)
                    result[entry.Key.ToString()] = entry.Value;

                return result;
            }

            return null;
        }

        private void LogConfigCreated()
        {
            Puts("Создан новый конфигурационный файл: " + ConfigFilePath);
        }

        private void LogConfigUpdated()
        {
            Puts("Конфигурация устарела — добавлены новые настройки, файл обновлён: " + ConfigFilePath);
        }

        private void TryMigrateLegacyConfigFile()
        {
            string legacyPath = Path.Combine(Interface.Oxide.ConfigDirectory, "OkeyRustLauncher.json");
            if (!File.Exists(ConfigFilePath) && File.Exists(legacyPath))
            {
                File.Copy(legacyPath, ConfigFilePath);
                Puts("Конфиг перенесён из OkeyRustLauncher.json в " + Name + ".json");
            }
        }

        protected override void LoadDefaultConfig()
        {
            _config = GetDefaultConfiguration();
        }

        protected override void LoadConfig()
        {
            TryMigrateLegacyConfigFile();
            bool isNewConfig = !File.Exists(ConfigFilePath);

            base.LoadConfig();

            try
            {
                if (isNewConfig)
                {
                    _config = GetDefaultConfiguration();
                    LogConfigCreated();
                    SaveConfig();
                    ApplyConfiguration();
                    return;
                }

                _config = Config.ReadObject<Configuration>();
                if (_config == null)
                    throw new JsonException("Конфиг пустой.");

                if (IsConfigOutdated())
                {
                    LogConfigUpdated();
                    SaveConfig();
                }

                ApplyConfiguration();
            }
            catch (Exception ex)
            {
                PrintError("Ошибка чтения конфига (" + ConfigFilePath + "): " + ex.Message);
                LoadDefaultConfig();
                LogConfigCreated();
                SaveConfig();
                ApplyConfiguration();
            }
        }

        protected override void SaveConfig()
        {
            if (_config == null)
                _config = GetDefaultConfiguration();

            Config.WriteObject(_config, true);
        }

        protected override void LoadDefaultMessages()
        {
            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["KickOnlyLauncher"] = "На сервер можно войти только с SABR RUST Launcher. Перезайдите с него в игру!",
                ["BonusLauncherGranted"] = "Вы зашли с SABR RUST Launcher, вам был выдан бонус!",
                ["BanKick"] = "Доступ ограничен! Причина: {0}",
                ["BanDefaultReason"] = "Заблокирован",
                ["LbanNoPermission"] = "<color=#FF2D2D>Нет доступа (rustlauncher.lban)</color>",
                ["UnlbanNoPermission"] = "<color=#FF2D2D>Нет доступа (rustlauncher.unlban)</color>",
                ["LbanUsage"] = "<color=#FF2D2D>Использование: /lban <SteamID/Ник/HWID/IP> <причина></color>",
                ["UnlbanUsage"] = "<color=#FF2D2D>Использование: /unlban <SteamID/Ник/HWID/IP></color>",
                ["BanSuccess"] = "<color=#4ADE80>Игрок забанен.</color>",
                ["UnbanSuccess"] = "<color=#4ADE80>Бан снят.</color>",
                ["BanError"] = "<color=#FF2D2D>Ошибка: {0}</color>",
                ["BanErrUnknown"] = "Неизвестная ошибка",
                ["BanErrTargetRequired"] = "Укажите цель бана",
                ["BanErrPlayerNotFound"] = "Игрок не найден: {0}",
                ["BanErrAlreadyBanned"] = "Уже в бане: {0}",
                ["BanErrResolveFailed"] = "Не удалось определить SteamID, IP или HWID",
                ["BanErrNotFound"] = "Бан не найден: {0}",
                ["BanHelpTitle"] = "<color=#FF2D2D>SABR RUST Launcher — баны</color>",
                ["BanHelpLban"] = "<color=#E8E8EC>/lban</color> <SteamID/Ник/HWID/IP> <причина> — забанить (SteamID, IP с сервера, HWID с лаунчера)",
                ["BanHelpUnlban"] = "<color=#E8E8EC>/unlban</color> <SteamID/Ник/HWID/IP> — снять бан",
                ["BanHelpLbanPerm"] = "Право: <color=#FFC107>rustlauncher.lban</color>",
                ["BanHelpUnlbanPerm"] = "Право: <color=#FFC107>rustlauncher.unlban</color>",
                ["BanHelpIgnorePerm"] = "Обход банов: <color=#FFC107>rustlauncher.ignoreban</color>",
                ["BanHelpKick"] = "При входе: кик с текстом «Доступ ограничен! Причина: …»"
            }, this, "ru");

            lang.RegisterMessages(new Dictionary<string, string>
            {
                ["KickOnlyLauncher"] = "You can only join this server using SABR RUST Launcher. Please launch the game from the launcher!",
                ["BonusLauncherGranted"] = "You joined from SABR RUST Launcher — your bonus has been granted!",
                ["BanKick"] = "Access denied! Reason: {0}",
                ["BanDefaultReason"] = "Banned",
                ["LbanNoPermission"] = "<color=#FF2D2D>No permission (rustlauncher.lban)</color>",
                ["UnlbanNoPermission"] = "<color=#FF2D2D>No permission (rustlauncher.unlban)</color>",
                ["LbanUsage"] = "<color=#FF2D2D>Usage: /lban <SteamID/Nick/HWID/IP> <reason></color>",
                ["UnlbanUsage"] = "<color=#FF2D2D>Usage: /unlban <SteamID/Nick/HWID/IP></color>",
                ["BanSuccess"] = "<color=#4ADE80>Player banned.</color>",
                ["UnbanSuccess"] = "<color=#4ADE80>Ban removed.</color>",
                ["BanError"] = "<color=#FF2D2D>Error: {0}</color>",
                ["BanErrUnknown"] = "Unknown error",
                ["BanErrTargetRequired"] = "Ban target is required",
                ["BanErrPlayerNotFound"] = "Player not found: {0}",
                ["BanErrAlreadyBanned"] = "Already banned: {0}",
                ["BanErrResolveFailed"] = "Could not resolve SteamID, IP or HWID",
                ["BanErrNotFound"] = "Ban not found: {0}",
                ["BanHelpTitle"] = "<color=#FF2D2D>SABR RUST Launcher — bans</color>",
                ["BanHelpLban"] = "<color=#E8E8EC>/lban</color> <SteamID/Nick/HWID/IP> <reason> — ban (SteamID, game IP, launcher HWID)",
                ["BanHelpUnlban"] = "<color=#E8E8EC>/unlban</color> <SteamID/Nick/HWID/IP> — remove ban",
                ["BanHelpLbanPerm"] = "Permission: <color=#FFC107>rustlauncher.lban</color>",
                ["BanHelpUnlbanPerm"] = "Permission: <color=#FFC107>rustlauncher.unlban</color>",
                ["BanHelpIgnorePerm"] = "Bypass bans: <color=#FFC107>rustlauncher.ignoreban</color>",
                ["BanHelpKick"] = "On join: kick with «Access denied! Reason: …»"
            }, this, "en");
        }

        private string GetMsg(string key, string userId = null, params object[] args)
        {
            string message;
            if (!string.IsNullOrEmpty(userId) && IsRussianRustLanguage(lang.GetLanguage(userId)))
                message = lang.GetMessage(key, this, userId);
            else
                message = lang.GetMessage(key, this, null);

            if (args != null && args.Length > 0)
                return string.Format(message, args);

            return message;
        }

        private void SendBanChat(BasePlayer player, string key, params object[] args)
        {
            if (player == null)
                return;

            player.ChatMessage(GetMsg(key, player.UserIDString, args));
        }

        private void SendBanHelp(BasePlayer player)
        {
            if (player == null)
                return;

            SendBanChat(player, "BanHelpTitle");
            SendBanChat(player, "BanHelpLban");
            SendBanChat(player, "BanHelpUnlban");
            SendBanChat(player, "BanHelpLbanPerm");
            SendBanChat(player, "BanHelpUnlbanPerm");
            SendBanChat(player, "BanHelpIgnorePerm");
            SendBanChat(player, "BanHelpKick");
        }

        private string ResolveBanResultMessage(BasePlayer player, Dictionary<string, object> result)
        {
            if (result == null)
                return GetMsg("BanErrUnknown", player?.UserIDString);

            object keyObj;
            if (result.TryGetValue("messageKey", out keyObj) && keyObj != null)
            {
                object[] msgArgs = null;
                object argsObj;
                if (result.TryGetValue("messageArgs", out argsObj) && argsObj is object[])
                    msgArgs = (object[])argsObj;

                return GetMsg(keyObj.ToString(), player?.UserIDString, msgArgs ?? new object[0]);
            }

            object msgObj;
            if (result.TryGetValue("message", out msgObj) && msgObj != null)
                return msgObj.ToString();

            return GetMsg("BanErrUnknown", player?.UserIDString);
        }

        private Dictionary<string, object> ApiFailKey(string key, params object[] args)
        {
            var dict = new Dictionary<string, object>
            {
                ["status"] = "error",
                ["success"] = false,
                ["messageKey"] = key,
                ["message"] = GetMsg(key, null, args)
            };

            if (args != null && args.Length > 0)
                dict["messageArgs"] = args;

            return dict;
        }

        private static bool IsRussianRustLanguage(string code)
        {
            if (string.IsNullOrEmpty(code))
                return false;

            code = code.Trim();
            if (code.Equals("ru", StringComparison.OrdinalIgnoreCase))
                return true;

            return code.StartsWith("ru-", StringComparison.OrdinalIgnoreCase);
        }

        private string GetKickOnlyLauncherMessage(BasePlayer player)
        {
            if (IsRussianRustLanguage(lang.GetLanguage(player.UserIDString)))
                return lang.GetMessage("KickOnlyLauncher", this, player.UserIDString);

            return lang.GetMessage("KickOnlyLauncher", this, null);
        }

        private string GetBonusChatMessage(BasePlayer player)
        {
            if (IsRussianRustLanguage(lang.GetLanguage(player.UserIDString)))
                return lang.GetMessage("BonusLauncherGranted", this, player.UserIDString);

            return lang.GetMessage("BonusLauncherGranted", this, null);
        }

        private static List<string> NormalizeBonusList(IList<string> list)
        {
            if (list == null || list.Count == 0)
                return new List<string>();

            return list
                .Select(v => (v ?? string.Empty).Trim())
                .Where(v => !string.IsNullOrEmpty(v))
                .Distinct()
                .ToList();
        }

        /// <summary>
        /// Обход кика без лаунчера (rustlauncher.noneedlauncher) — при любом режиме проверки.
        /// </summary>
        private bool CanBypassLauncherKick(BasePlayer player)
        {
            if (player == null || _config?.SecuritySettings == null)
                return false;

            string perm = (_config.SecuritySettings.PermissionBypassLauncherKick ?? string.Empty).Trim();
            if (string.IsNullOrEmpty(perm))
                return false;

            return permission.UserHasPermission(player.UserIDString, perm);
        }

        /// <summary>
        /// Пермишенный режим: кик только у игроков с PermissionRequireLauncher, когда глобальный кик выключен.
        /// </summary>
        private bool IsPermissionBasedLauncherRequiredActive()
        {
            if (_config?.SecuritySettings == null)
                return false;

            if (_config.SecuritySettings.AllowJoinOnlyFromLauncher)
                return false;

            string perm = (_config.SecuritySettings.PermissionRequireLauncher ?? string.Empty).Trim();
            return !string.IsNullOrEmpty(perm);
        }

        private bool IsLauncherSecurityCheckActive()
        {
            if (_config?.SecuritySettings == null)
                return false;

            return _config.SecuritySettings.AllowJoinOnlyFromLauncher || IsPermissionBasedLauncherRequiredActive();
        }

        /// <summary>
        /// Кик без лаунчера: глобальный режим или пермишен needlauncher (если глобальный выключен).
        /// </summary>
        private bool ShouldKickPlayerWithoutLauncher(BasePlayer player, bool fromOk)
        {
            if (player == null || fromOk || !_isConnected || _config?.SecuritySettings == null)
                return false;

            if (CanBypassLauncherKick(player))
                return false;

            SecuritySettings sec = _config.SecuritySettings;

            if (sec.AllowJoinOnlyFromLauncher)
                return true;

            string requirePerm = (sec.PermissionRequireLauncher ?? string.Empty).Trim();
            if (string.IsNullOrEmpty(requirePerm))
                return false;

            return permission.UserHasPermission(player.UserIDString, requirePerm);
        }

        private void ApplyOrStripLauncherBonus(BasePlayer player, bool fromOk)
        {
            if (!_isConnected)
                return;

            BonusSettings b = _config != null ? _config.BonusSettings : null;
            if (b == null)
                return;

            string steamId = player.UserIDString;
            int mode = Mathf.Clamp(b.BonusGrantMode, 1, 3);
            List<string> groups = NormalizeBonusList(b.BonusGroups);
            List<string> perms = NormalizeBonusList(b.BonusPermissions);

            bool give = fromOk && b.EnableLauncherJoinBonus;

            if (!give)
            {
                StripLauncherBonus(steamId, mode, groups, perms);
                return;
            }

            bool canGrant = (mode == 1 && groups.Count > 0) ||
                            (mode == 2 && perms.Count > 0) ||
                            (mode == 3 && (groups.Count > 0 || perms.Count > 0));

            if (!canGrant)
                return;

            GrantLauncherBonus(steamId, mode, groups, perms);
            player.ChatMessage("<color=#4ADE80>" + GetBonusChatMessage(player) + "</color>");
        }

        private void GrantLauncherBonus(string steamId, int mode, List<string> groups, List<string> perms)
        {
            try
            {
                if (mode == 1 || mode == 3)
                {
                    foreach (string g in groups)
                    {
                        if (!permission.UserHasGroup(steamId, g))
                            permission.AddUserGroup(steamId, g);
                    }
                }

                if (mode == 2 || mode == 3)
                {
                    foreach (string p in perms)
                    {
                        if (!permission.UserHasPermission(steamId, p))
                            permission.GrantUserPermission(steamId, p, this);
                    }
                }
            }
            catch (Exception ex)
            {
                PrintError("[Bonus] Grant error for " + steamId + ": " + ex.Message);
            }
        }

        private void StripLauncherBonus(string steamId, int mode, List<string> groups, List<string> perms)
        {
            try
            {
                if (mode == 1 || mode == 3)
                {
                    foreach (string g in groups)
                    {
                        if (permission.UserHasGroup(steamId, g))
                            permission.RemoveUserGroup(steamId, g);
                    }
                }

                if (mode == 2 || mode == 3)
                {
                    foreach (string p in perms)
                    {
                        if (permission.UserHasPermission(steamId, p))
                            permission.RevokeUserPermission(steamId, p);
                    }
                }
            }
            catch (Exception ex)
            {
                PrintError("[Bonus] Strip error for " + steamId + ": " + ex.Message);
            }
        }

        #endregion

        #region Variables

        private Timer _checkTimer;
        private bool _isConnected;
        private HttpListener _httpListener;
        private Thread _httpListenerThread;
        private bool _isRunning;
        private string _httpListenerPrefix = string.Empty;

        #endregion

        #region Ban System

        private const string PermLban = "rustlauncher.lban";
        private const string PermUnlban = "rustlauncher.unlban";
        private const string PermIgnoreBan = "rustlauncher.ignoreban";

        private const string BanDataRoot = "ASystem";
        private const string BanDataPlugin = "RustLauncher";
        private static readonly DateTime BanEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

        private string BanStorageScope => GetBanStorageScope();

        private string BanListPath => $"{BanDataRoot}/{BanDataPlugin}/{BanStorageScope}/BanList";
        private string BanIdPath => $"{BanDataRoot}/{BanDataPlugin}/{BanStorageScope}/BanId";
        private string LegacyBanListPath => $"{BanDataRoot}/{BanDataPlugin}/BanList";
        private string LegacyBanIdPath => $"{BanDataRoot}/{BanDataPlugin}/BanId";

        private DynamicConfigFile _banIdFile;
        private int _banIdCounter;
        private StoredBanData _storedBans;
        private readonly Dictionary<int, LauncherBanData> _banCache = new Dictionary<int, LauncherBanData>();

        private class LauncherBanData
        {
            public int id;
            public string steamid = "";
            public string ip = "";
            public string name = "";
            public string hwid = "";
            public string source = "";
            public string serverScope = "";
            public double date;
            public string reason = "";
        }

        private string GetBanStorageScope()
        {
            // Отдельный список банов на каждый API-ключ (проект / партнёр не пересекаются).
            if (!string.IsNullOrEmpty(_serverApiKey)
                && !string.Equals(_serverApiKey, "ENTER_API_KEY_SERVER", StringComparison.Ordinal))
                return GetApiKeyScopeKey(_serverApiKey);

            return "unconfigured";
        }

        private static string GetApiKeyScopeKey(string apiKey)
        {
            if (string.IsNullOrEmpty(apiKey))
                return "unconfigured";

            using (SHA256 sha = SHA256.Create())
            {
                byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(apiKey.Trim()));
                StringBuilder sb = new StringBuilder(16);
                for (int i = 0; i < 8; i++)
                    sb.Append(hash[i].ToString("x2"));
                return sb.ToString();
            }
        }

        private class StoredBanData
        {
            public HashSet<string> Banlist = new HashSet<string>();
        }

        private class PlayerProfileDto
        {
            public bool ok;
            public bool registered;
            public string steamId = "";
            public string steamName = "";
            public string hwid = "";
            public string lastIp = "";
            public string ip = "";
        }

        private class LauncherPlayerData
        {
            public string Nick = "";
            public string SteamID64 = "";
            public string IP = "";
            public string HWID = ""; // пусто до первого успешного входа с лаунчера
            public string Launcher = ErrorCheckLauncher; // "true" | "false" | ErrorCheckLauncher
            public string PlayerStatus = "offline"; // online | offline
            public double UpdatedAt;
        }

        private class StoredPlayersData
        {
            public Dictionary<string, LauncherPlayerData> Players = new Dictionary<string, LauncherPlayerData>();
        }

        private string PlayersDataPath => $"{BanDataRoot}/{BanDataPlugin}/{BanStorageScope}/Players";
        private StoredPlayersData _storedPlayers = new StoredPlayersData();

        private void LoadPlayersData()
        {
            EnsureBanDirectories();
            try
            {
                _storedPlayers = Interface.Oxide.DataFileSystem.ReadObject<StoredPlayersData>(PlayersDataPath) ?? new StoredPlayersData();
            }
            catch
            {
                _storedPlayers = new StoredPlayersData();
            }

            if (_storedPlayers.Players == null)
                _storedPlayers.Players = new Dictionary<string, LauncherPlayerData>();
        }

        private void SavePlayersData()
        {
            if (_storedPlayers == null)
                _storedPlayers = new StoredPlayersData();
            if (_storedPlayers.Players == null)
                _storedPlayers.Players = new Dictionary<string, LauncherPlayerData>();
            EnsureBanDirectories();
            Interface.Oxide.DataFileSystem.WriteObject(PlayersDataPath, _storedPlayers);
        }

        private static string NormalizeGameIp(string value)
        {
            var ip = (value ?? string.Empty).Trim();
            if (ip.Contains(":"))
                ip = ip.Split(':')[0];
            return ip;
        }

        private LauncherPlayerData UpsertPlayerData(
            string steamId,
            string nick,
            string ip,
            string hwid = null,
            string launcher = null,
            string status = null)
        {
            if (!_apiKeyValidated)
                return null;

            if (string.IsNullOrEmpty(steamId))
                return null;

            LauncherPlayerData rec;
            bool isNew = false;
            if (!_storedPlayers.Players.TryGetValue(steamId, out rec) || rec == null)
            {
                rec = new LauncherPlayerData { SteamID64 = steamId };
                _storedPlayers.Players[steamId] = rec;
                isNew = true;
            }

            if (!string.IsNullOrEmpty(nick))
                rec.Nick = nick;
            if (!string.IsNullOrEmpty(ip))
                rec.IP = NormalizeGameIp(ip);
            if (!string.IsNullOrEmpty(hwid))
                rec.HWID = hwid;
            if (!string.IsNullOrEmpty(launcher))
                rec.Launcher = launcher;
            if (!string.IsNullOrEmpty(status))
                rec.PlayerStatus = status;

            rec.SteamID64 = steamId;
            rec.UpdatedAt = BanLogTime();
            SavePlayersData();
            if (isNew)
                Puts($"Игрок {rec.Nick} ({rec.SteamID64}) добавлен в data плагина");
            return rec;
        }

        private bool TryGetPlayerRecord(string steamId, out LauncherPlayerData record)
        {
            record = null;
            if (string.IsNullOrEmpty(steamId) || _storedPlayers?.Players == null)
                return false;
            return _storedPlayers.Players.TryGetValue(steamId, out record) && record != null;
        }

        private void MarkPlayerOffline(string steamId)
        {
            if (!_apiKeyValidated)
                return;

            LauncherPlayerData rec;
            if (!TryGetPlayerRecord(steamId, out rec))
                return;
            rec.PlayerStatus = "offline";
            rec.UpdatedAt = BanLogTime();
            SavePlayersData();
        }

        private void InitBanSystem()
        {
            permission.RegisterPermission(PermLban, this);
            permission.RegisterPermission(PermUnlban, this);
            permission.RegisterPermission(PermIgnoreBan, this);
            LoadBanData();
            LoadPlayersData();
        }

        private static double BanLogTime()
        {
            return DateTime.UtcNow.Subtract(BanEpoch).TotalSeconds;
        }

        private void EnsureBanDirectories()
        {
            Directory.CreateDirectory(Path.Combine(Interface.Oxide.DataDirectory, BanDataRoot, BanDataPlugin, BanStorageScope));
        }

        private void LoadBanId()
        {
            try
            {
                _banIdFile = Interface.Oxide.DataFileSystem.GetDatafile(BanIdPath);
                _banIdCounter = Convert.ToInt32(_banIdFile["id"] ?? 0);
            }
            catch
            {
                _banIdCounter = 0;
                _banIdFile = Interface.Oxide.DataFileSystem.GetDatafile(BanIdPath);
                _banIdFile["id"] = _banIdCounter;
                SaveBanId();
            }
        }

        private void SaveBanId()
        {
            EnsureBanDirectories();
            _banIdFile["id"] = _banIdCounter;
            Interface.Oxide.DataFileSystem.SaveDatafile(BanIdPath);
        }

        private int GetNewBanId()
        {
            _banIdCounter++;
            SaveBanId();
            return _banIdCounter;
        }

        private void LoadBanData()
        {
            EnsureBanDirectories();
            LoadBanId();
            _banCache.Clear();

            try
            {
                _storedBans = Interface.Oxide.DataFileSystem.ReadObject<StoredBanData>(BanListPath) ?? new StoredBanData();
            }
            catch
            {
                _storedBans = new StoredBanData();
            }

            if (_storedBans.Banlist == null)
                _storedBans.Banlist = new HashSet<string>();

            foreach (string entry in _storedBans.Banlist)
            {
                try
                {
                    LauncherBanData bd = JsonConvert.DeserializeObject<LauncherBanData>(entry);
                    if (bd != null && bd.id > 0 && !_banCache.ContainsKey(bd.id))
                        _banCache[bd.id] = bd;
                }
                catch { }
            }

            if (_banCache.Count == 0)
                TryMigrateLegacyBanData();

            Puts($"[Ban] Loaded {_banCache.Count} ban record(s) for scope '{BanStorageScope}'.");
        }

        private void TryMigrateLegacyBanData()
        {
            if (BanListPath == LegacyBanListPath)
                return;

            try
            {
                StoredBanData legacy = Interface.Oxide.DataFileSystem.ReadObject<StoredBanData>(LegacyBanListPath);
                if (legacy?.Banlist == null || legacy.Banlist.Count == 0)
                    return;

                foreach (string entry in legacy.Banlist)
                {
                    try
                    {
                        LauncherBanData bd = JsonConvert.DeserializeObject<LauncherBanData>(entry);
                        if (bd == null || bd.id <= 0 || _banCache.ContainsKey(bd.id))
                            continue;
                        bd.serverScope = BanStorageScope;
                        _banCache[bd.id] = bd;
                        _storedBans.Banlist.Add(JsonConvert.SerializeObject(bd));
                        if (bd.id > _banIdCounter)
                            _banIdCounter = bd.id;
                    }
                    catch { }
                }

                if (_banCache.Count > 0)
                {
                    SaveBanId();
                    SaveBanData();
                    Puts($"[Ban] Migrated {_banCache.Count} record(s) from legacy list to scope '{BanStorageScope}'.");
                }
            }
            catch { }
        }

        private void SaveBanData()
        {
            if (_storedBans == null)
                _storedBans = new StoredBanData();
            if (_storedBans.Banlist == null)
                _storedBans.Banlist = new HashSet<string>();

            EnsureBanDirectories();
            Interface.Oxide.DataFileSystem.WriteObject(BanListPath, _storedBans);
        }

        private bool CanIgnoreBan(BasePlayer player)
        {
            return player != null && permission.UserHasPermission(player.UserIDString, PermIgnoreBan);
        }

        private static string NormalizeBanKey(string value)
        {
            return (value ?? string.Empty).Trim().ToLowerInvariant();
        }

        private static bool LooksLikeSteamId(string value)
        {
            if (string.IsNullOrEmpty(value)) return false;
            ulong id;
            return ulong.TryParse(value, out id) && value.Length >= 15;
        }

        private static bool LooksLikeIp(string value)
        {
            if (string.IsNullOrEmpty(value)) return false;
            string[] parts = value.Split('.');
            if (parts.Length != 4) return false;
            foreach (string p in parts)
            {
                int n;
                if (!int.TryParse(p, out n) || n < 0 || n > 255) return false;
            }
            return true;
        }

        private void FetchPlayerProfile(string steamId, Action<PlayerProfileDto> callback)
        {
            if (string.IsNullOrEmpty(steamId) || !_isConnected)
            {
                callback(new PlayerProfileDto { ok = false, steamId = steamId ?? "" });
                return;
            }

            string url = _serverUrl + "/api/rust-plugin/player-profile?steam_id=" + Uri.EscapeDataString(steamId);
            webrequest.Enqueue(url, null, (code, response) =>
            {
                PlayerProfileDto dto = new PlayerProfileDto { ok = false, steamId = steamId };
                if (code == 200 && !string.IsNullOrEmpty(response))
                {
                    try
                    {
                        dto = JsonConvert.DeserializeObject<PlayerProfileDto>(response) ?? dto;
                    }
                    catch { }
                }
                callback(dto);
            }, this);
        }

        private class LauncherCheckResult
        {
            public bool Ok;
            public bool FromLauncher;
            public bool SessionOpen;
            public bool InGame;
            public string Hwid = "";
            public string Error = "";
        }

        private const int LauncherVerifyMaxAttempts = 5;
        private const float LauncherVerifyRetrySeconds = 2f;

        private void RequestLauncherAndProfile(string steamId, Action<LauncherCheckResult> callback)
        {
            if (string.IsNullOrEmpty(steamId) || !_isConnected)
            {
                callback(new LauncherCheckResult
                {
                    Ok = false,
                    Error = "Launcher server unavailable"
                });
                return;
            }

            string q = Uri.EscapeDataString(steamId);
            string url = _serverUrl + "/api/rust-plugin/player-from-launcher?steam_id=" + q;
            webrequest.Enqueue(url, null, (code, response) =>
            {
                if (code != 200 || string.IsNullOrEmpty(response))
                {
                    callback(new LauncherCheckResult
                    {
                        Ok = false,
                        Error = "ErrorCheckLauncher"
                    });
                    return;
                }

                bool fromOk;
                bool sessionOpen;
                bool inGame;
                try
                {
                    JObject jr = JObject.Parse(response);
                    JToken fromToken = jr["fromOkeyRustLauncher"] ?? jr["fromSabrRustLauncher"];
                    if (fromToken == null)
                    {
                        callback(new LauncherCheckResult { Ok = false, Error = "ErrorCheckLauncher" });
                        return;
                    }
                    fromOk = fromToken.Value<bool>();
                    sessionOpen = jr["sessionOpen"] != null && jr["sessionOpen"].Value<bool>();
                    inGame = jr["inGame"] != null && jr["inGame"].Value<bool>();
                }
                catch
                {
                    callback(new LauncherCheckResult { Ok = false, Error = "ErrorCheckLauncher" });
                    return;
                }

                FetchPlayerProfile(steamId, profile =>
                {
                    callback(new LauncherCheckResult
                    {
                        Ok = true,
                        FromLauncher = fromOk,
                        SessionOpen = sessionOpen,
                        InGame = inGame,
                        Hwid = profile?.hwid ?? string.Empty
                    });
                });
            }, this);
        }

        private bool TryFindBanMatch(string steamId, string ip, string hwid, string name, out LauncherBanData match)
        {
            match = null;
            string sSteam = NormalizeBanKey(steamId);
            string sIp = NormalizeBanKey(ip);
            string sHwid = NormalizeBanKey(hwid);
            string sName = NormalizeBanKey(name);

            foreach (LauncherBanData bd in _banCache.Values)
            {
                if (bd == null) continue;

                if (!string.IsNullOrEmpty(sSteam) && NormalizeBanKey(bd.steamid) == sSteam)
                {
                    match = bd;
                    return true;
                }
                if (!string.IsNullOrEmpty(sIp) && !string.IsNullOrEmpty(bd.ip) && NormalizeBanKey(bd.ip) == sIp)
                {
                    match = bd;
                    return true;
                }
                if (!string.IsNullOrEmpty(sHwid) && !string.IsNullOrEmpty(bd.hwid) && NormalizeBanKey(bd.hwid) == sHwid)
                {
                    match = bd;
                    return true;
                }
                if (!string.IsNullOrEmpty(sName) && !string.IsNullOrEmpty(bd.name) && NormalizeBanKey(bd.name) == sName)
                {
                    match = bd;
                    return true;
                }
            }

            return false;
        }

        private Dictionary<string, object> ApiCheckBan(string steamId, string ip, string hwid, string name)
        {
            if (!_apiKeyValidated)
            {
                return new Dictionary<string, object>
                {
                    ["status"] = "error",
                    ["success"] = false,
                    ["message"] = "API key not validated"
                };
            }

            LauncherBanData match;
            if (TryFindBanMatch(steamId, ip, hwid, name, out match))
            {
                return new Dictionary<string, object>
                {
                    ["status"] = "success",
                    ["success"] = true,
                    ["banned"] = true,
                    ["reason"] = match.reason ?? "Banned",
                    ["banId"] = match.id
                };
            }

            return new Dictionary<string, object>
            {
                ["status"] = "success",
                ["success"] = true,
                ["banned"] = false
            };
        }

        private bool EnsureBanSystemReady(BasePlayer player = null)
        {
            if (_apiKeyValidated)
                return true;

            if (player != null)
                SendBanChat(player, "BanError", "API ключ сервера не подтверждён — баны недоступны.");
            return false;
        }

        private Dictionary<string, object> ApiAddBan(string sourceName, string target, string reason)
        {
            if (!_apiKeyValidated)
                return ApiFail("API key not validated");

            if (string.IsNullOrWhiteSpace(target))
                return ApiFailKey("BanErrTargetRequired");

            if (string.IsNullOrWhiteSpace(reason))
                reason = GetMsg("BanDefaultReason");

            return ExecuteBan(sourceName, target.Trim(), reason.Trim(), null, null, null);
        }

        private Dictionary<string, object> ApiRemoveBan(string sourceName, string target)
        {
            if (!_apiKeyValidated)
                return ApiFail("API key not validated");

            if (string.IsNullOrWhiteSpace(target))
                return ApiFailKey("BanErrTargetRequired");

            return ExecuteUnban(sourceName, target.Trim());
        }

        private static Dictionary<string, object> ApiFail(string message)
        {
            return new Dictionary<string, object>
            {
                ["status"] = "error",
                ["success"] = false,
                ["message"] = message
            };
        }

        private Dictionary<string, object> ExecuteBan(string sourceName, string target, string reason, string presetSteamId, string presetIp, string presetHwid)
        {
            string steamId = presetSteamId ?? "";
            string ip = presetIp ?? "";
            string hwid = presetHwid ?? "";
            string name = "";

            if (LooksLikeSteamId(target))
                steamId = target;
            else if (LooksLikeIp(target))
                ip = target;
            else if (target.Length >= 16 && !target.Contains(" "))
                hwid = target;
            else
            {
                BasePlayer online = BasePlayer.Find(target) ?? BasePlayer.FindSleeping(target);
                if (online != null)
                {
                    steamId = online.UserIDString;
                    name = online.displayName ?? "";
                    ip = online.net?.connection?.ipaddress ?? ip;
                }
                else
                {
                    LauncherBanData byName;
                    if (TryFindBanMatch("", "", "", target, out byName))
                    {
                        steamId = byName.steamid;
                        ip = byName.ip;
                        hwid = byName.hwid;
                        name = byName.name;
                    }
                    else
                    {
                        return ApiFailKey("BanErrPlayerNotFound", target);
                    }
                }
            }

            if (!string.IsNullOrEmpty(steamId))
            {
                BasePlayer bp = BasePlayer.Find(steamId) ?? BasePlayer.
// ... [Code preview truncated due to length. Full source in attached file] ...

Автор
15FPS
Скачиваний
0
Просмотры
1
Первый выпуск
Обновление
Оценка
0.00 звёзд 0 оценок
Сверху