2158 lines
114 KiB
C#
2158 lines
114 KiB
C#
#region Usings
|
||
using System;
|
||
using System.Collections.Generic;
|
||
using System.IO;
|
||
using System.Linq;
|
||
using System.Security.Cryptography;
|
||
using System.Text;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
#endregion Usings
|
||
/// <summary>
|
||
/// Script from Reimajo purchased at https://reimajo.booth.pm/
|
||
/// Make sure to join my discord to receive update notifications for this asset and support: https://discord.gg/SWkNA394Mm
|
||
/// If you have any issues; please contact me on Discord (https://discord.gg/SWkNA394Mm) or Booth or Twitter https://twitter.com/ReimajoChan
|
||
/// </summary>
|
||
namespace ReimajoBoothAssetsEditorScripts
|
||
{
|
||
#region AffectedScripts
|
||
[CustomEditor(typeof(ReimajoBoothAssets.AdminPanel), true, isFallback = true)]
|
||
[CanEditMultipleObjects]
|
||
public class AdminPanelEditor : AdminPanelEditorBase
|
||
{
|
||
}
|
||
[CustomEditor(typeof(ReimajoBoothAssets.PickupSync), true, isFallback = true)]
|
||
[CanEditMultipleObjects]
|
||
public class PickupSyncEditor : AdminPanelEditorBase
|
||
{
|
||
}
|
||
[CustomEditor(typeof(ReimajoBoothAssets.AdminPanelDesktopButton), true, isFallback = true)]
|
||
[CanEditMultipleObjects]
|
||
public class AdminPanelDesktopButtonEditor : AdminPanelEditorBase
|
||
{
|
||
}
|
||
[CustomEditor(typeof(ReimajoBoothAssets.ResetObjectPositionButton), true, isFallback = true)]
|
||
[CanEditMultipleObjects]
|
||
public class ResetObjectPositionButtonEditor : AdminPanelEditorBase
|
||
{
|
||
}
|
||
#endregion AffectedScripts
|
||
public class AdminPanelEditorBase : Editor
|
||
{
|
||
//############################################
|
||
private const string VERSION = "V4.3g (UNTESTED BETA VERSION)";
|
||
private const string PRODUCT_NAME = "Admin Panel";
|
||
private const string DOCUMENTATION = @"https://docs.google.com/document/d/1gh1osC2njNwgyel48KboBF9rc8KfogPUR0vS_KUE2DI/";
|
||
//############################################
|
||
#region BaseEditor
|
||
#region PrivateFields
|
||
private const string UNITY_FOLDER = "Assets";
|
||
private const string RMB_FOLDER = "ReimajoBoothAssets";
|
||
private const string ASSET_FOLDER = "AdminTool";
|
||
private const string SETTINGS_FOLDER = "YOUR_SETTINGS";
|
||
private const string AUTH_FOLDER = "Auth";
|
||
private const string PASSWORD_UNKNOWN = "***********************";
|
||
private const string PASSWORD_UNKNOWN_IN_FILE = "*** password was not stored in cleartext for this user ***";
|
||
private const string AUTH_NOT_SET_IN_FILE = "*** auth info not set for this user yet ***";
|
||
|
||
private static readonly string ASSET_FOLDER_PATH = $"{UNITY_FOLDER}/{RMB_FOLDER}/{ASSET_FOLDER}/";
|
||
private static readonly string SCRIPT_FILE_PATH = $"{ASSET_FOLDER_PATH}/Scripts/PickupSync.cs";
|
||
|
||
private static readonly string SETTINGS_FOLDER_PATH = $"{UNITY_FOLDER}/{RMB_FOLDER}/{SETTINGS_FOLDER}/{ASSET_FOLDER}";
|
||
private static readonly string CACHE_FILE_PATH = $"{SETTINGS_FOLDER_PATH}/_Cache.JSON";
|
||
private static string ADMIN_FILE_PATH = $"{SETTINGS_FOLDER_PATH}/Admins.txt";
|
||
private static string MODERATOR_FILE_PATH = $"{SETTINGS_FOLDER_PATH}/Moderators.txt";
|
||
private static string PERMA_BAN_FILE_PATH = $"{SETTINGS_FOLDER_PATH}/PermaBans.txt";
|
||
private static string SETTINGS_FILE_PATH = $"{SETTINGS_FOLDER_PATH}/Settings.JSON";
|
||
|
||
private static string SALT_FILE_PATH = $"{SETTINGS_FOLDER_PATH}/{AUTH_FOLDER}/Salt.txt";
|
||
private static string HASH_FILE_PATH = $"{SETTINGS_FOLDER_PATH}/{AUTH_FOLDER}/Hash.txt";
|
||
private static string PASSWORD_FILE_PATH = $"{SETTINGS_FOLDER_PATH}/{AUTH_FOLDER}/Password.txt";
|
||
|
||
private static AdminPanelCache _loadedCache;
|
||
|
||
private static bool _scriptIsOutOfDate;
|
||
private static AdminPanelSettings _loadedSettings;
|
||
|
||
private static List<string> _loadedAdminList;
|
||
private static List<string> _loadedModeratorList;
|
||
private static List<string> _loadedPermaBanList;
|
||
private static List<string> _loadedPasswordList;
|
||
private static List<string> _loadedHashList;
|
||
private static List<string> _loadedSaltList;
|
||
|
||
private static List<string> _displayedAdminList;
|
||
private static List<string> _displayedModeratorList;
|
||
private static List<string> _displayedPermaBanList;
|
||
private static List<string> _displayedPasswordList;
|
||
private static List<string> _displayedHashList;
|
||
private static List<string> _displayedSaltList;
|
||
|
||
private static bool _lastEditFailed = false;
|
||
private static bool _currentEditFailed = false;
|
||
|
||
private static string _editorState_REMOTE_LOADING_URL;
|
||
private static int _editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS;
|
||
#region ToogleStates
|
||
//New since 06.05.2023
|
||
private static bool _toggleState_DESYNC_BANNED_PLAYERS;
|
||
private static bool _toggleState_STEALTH_PANEL;
|
||
private static bool _toggleState_NO_BAN_EFFECTS;
|
||
//-------------------------
|
||
//private bool _toggleState_#1#;
|
||
private static bool _toggleState_VRC_GUIDE_COMPLICANCE;
|
||
private static bool _toggleState_VRC_GUIDE_DEBUG;
|
||
private static bool _toggleState_PASSWORD_AUTHENTICATION;
|
||
private static bool _toggleState_MINIMAL_BAN_DEBUG;
|
||
private static bool _toggleState_ANTI_NAME_SPOOF;
|
||
private static bool _toggleState_MARK_BANNED_PLAYERS;
|
||
private static bool _toggleState_MUTE_BANNED_PLAYERS;
|
||
private static bool _toggleState_BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS;
|
||
private static bool _toggleState_ANTI_PICKUP_SUMMON_MOD;
|
||
private static bool _toggleState_ANTI_NO_TELEPORT_MOD;
|
||
private static bool _toggleState_USE_HONEYPOTS;
|
||
private static bool _toggleState_PERMA_BAN_LIST;
|
||
private static bool _toggleState_SURPRESS_WHITESPACE_CHARS;
|
||
private static bool _toggleState_ADMIN_ONLY_OBJECTS;
|
||
private static bool _toggleState_MODERATOR_AND_ADMIN_ONLY_OBJECTS;
|
||
private static bool _toggleState_DESTROY_FOR_OTHER_PLAYERS;
|
||
private static bool _toggleState_USE_CUSTOM_ALT_ACCOUNT_DETECTION;
|
||
private static bool _toggleState_ADMIN_CAN_FLY;
|
||
private static bool _toggleState_MODERATOR_CAN_FLY;
|
||
private static bool _toggleState_EVERYONE_CAN_FLY;
|
||
private static bool _toggleState_MODERATOR_CAN_BAN;
|
||
private static bool _toggleState_CAN_BAN_ADMINS;
|
||
private static bool _toggleState_SUMMON_PANEL_FUNCTION;
|
||
private static bool _toggleState_ACCURATE_CAPSULE_POSITIONS;
|
||
private static bool _toggleState_UNITY_SHOW_PASSWORD;
|
||
private static bool _toggleState_UNITY_SHOW_HASH_AND_SALT;
|
||
private static bool _toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS;
|
||
private static bool _toggleState_DEBUG_ADMIN_NAME_DETECTION;
|
||
private static bool _toggleState_REMOTE_STRING_LOADING;
|
||
private static bool _toggleState_REMOTE_BAN_LIST;
|
||
private static bool _toggleState_REMOTE_ADMIN_LIST;
|
||
private static bool _toggleState_REMOTE_MODERATOR_LIST;
|
||
private static bool _toggleState_HASH_USERNAMES;
|
||
//#NEW_PROPERTY#
|
||
#endregion ToogleStates
|
||
#endregion PrivateFields
|
||
#region SetupEnvironment
|
||
/// <summary>
|
||
/// Ensures that the project has the needed path setup to store the settings files
|
||
/// </summary>
|
||
private static void SetupStoragePath()
|
||
{
|
||
if (!AssetDatabase.IsValidFolder($"{UNITY_FOLDER}/{RMB_FOLDER}"))
|
||
{
|
||
AssetDatabase.CreateFolder($"{UNITY_FOLDER}", RMB_FOLDER);
|
||
}
|
||
if (!AssetDatabase.IsValidFolder($"{UNITY_FOLDER}/{RMB_FOLDER}/{SETTINGS_FOLDER}"))
|
||
{
|
||
AssetDatabase.CreateFolder($"{UNITY_FOLDER}/{RMB_FOLDER}", SETTINGS_FOLDER);
|
||
}
|
||
if (!AssetDatabase.IsValidFolder($"{UNITY_FOLDER}/{RMB_FOLDER}/{SETTINGS_FOLDER}/{ASSET_FOLDER}"))
|
||
{
|
||
AssetDatabase.CreateFolder($"{UNITY_FOLDER}/{RMB_FOLDER}/{SETTINGS_FOLDER}", ASSET_FOLDER);
|
||
}
|
||
if (!AssetDatabase.IsValidFolder($"{UNITY_FOLDER}/{RMB_FOLDER}/{SETTINGS_FOLDER}/{ASSET_FOLDER}/{AUTH_FOLDER}"))
|
||
{
|
||
AssetDatabase.CreateFolder($"{UNITY_FOLDER}/{RMB_FOLDER}/{SETTINGS_FOLDER}/{ASSET_FOLDER}", AUTH_FOLDER);
|
||
}
|
||
}
|
||
#endregion SetupEnvironment
|
||
#region CreateDefaultObjects
|
||
/// <summary>
|
||
/// Returns a list of default admins (people who worked with me on this asset)
|
||
/// </summary>
|
||
private static List<string> GetDefaultAdminList()
|
||
{
|
||
return new List<string> { "Reimajo", "Alex Lotor", "JoRoFox" };
|
||
}
|
||
/// <summary>
|
||
/// Returns a list of default moderators (people who worked with me on this asset)
|
||
/// </summary>
|
||
private static List<string> GetDefaultModeratorList()
|
||
{
|
||
return new List<string> { "NotFish" };
|
||
}
|
||
/// <summary>
|
||
/// Returns a list of default perma bans (here are 3 accounts that are already banned by VRChat permanently, only as an example list)
|
||
/// </summary>
|
||
private static List<string> GetDefaultPermaBanList()
|
||
{
|
||
return new List<string> { "Kirai Chanǃ", "xKirai Chan", "Kirai Chanǃǃ" };
|
||
}
|
||
/// <summary>
|
||
/// Returns a list of default hashes
|
||
/// </summary>
|
||
private static List<string> GetDefaultHashList()
|
||
{
|
||
List<string> emptyList = GetDefaultEmptyAuthList();
|
||
if (emptyList.Count == 4)
|
||
{
|
||
emptyList[0] = @"32e7bfc9de933cb36d725ae600c9feee564208048cfc27dfdca9b16efd22ac0077051a5d1807aa1a7625a673bbaf20dc12923c2165c142686dcb986bd5904f99"; // for Reimajo
|
||
emptyList[1] = @"41110cd8c17378448d1f2073c8ed7ba5fd9abb1ead3c604b848fa3fbaa51d7938c275dbbdf215976f6ec72dab6e7c42ceee5400396ba9ff248f36d808842c26d"; // for Alex Lotor
|
||
emptyList[2] = @"31e35e68b52262aae08909d772c4406415745b65e0656972cc1d4c16bd99c9657f1252abdfa51ac0db16cb9f2cdad126299298cd1ef43a3b4eae0c9911e08413"; // for JoRoFox
|
||
emptyList[3] = @"31db234dcb50ddab089fbf74877ede21e62e247be075545f3be132da0db62dbe7d7881fe7b5d18a80d53b2ad15268dc7e83834bc4987c2063f7c3eb97460967c"; // for NotFish
|
||
}
|
||
return emptyList;
|
||
}
|
||
/// <summary>
|
||
/// Returns a list of default salts
|
||
/// </summary>
|
||
private static List<string> GetDefaultSaltList()
|
||
{
|
||
List<string> emptyList = GetDefaultEmptyAuthList();
|
||
if (emptyList.Count == 4)
|
||
{
|
||
emptyList[0] = @"JsgenVX0IjJlMfxlvH4hwUV5W6QnS89L"; // for Reimajo
|
||
emptyList[1] = @"remSB7L7ThqOapsUi56MdWdHQP4GTQcx"; // for Alex Lotor
|
||
emptyList[2] = @"uB0I9IEHvISgYF8rvuA531IAzeIxQwJJ"; // for JoRoFox
|
||
emptyList[3] = @"AcORRd71xYid6P57fJJNvjo0zlqBRdoX"; // for NotFish
|
||
}
|
||
return emptyList;
|
||
}
|
||
/// <summary>
|
||
/// Returns the defaulz list of passwords
|
||
/// </summary>
|
||
private static List<string> GetDefaultPasswordList()
|
||
{
|
||
List<string> emptyList = GetDefaultEmptyAuthList();
|
||
if (emptyList.Count == 4)
|
||
{
|
||
emptyList[0] = PASSWORD_UNKNOWN; // for Reimajo
|
||
emptyList[1] = PASSWORD_UNKNOWN; // for Alex Lotor
|
||
emptyList[2] = PASSWORD_UNKNOWN; // for JoRoFox
|
||
emptyList[3] = PASSWORD_UNKNOWN; // for NotFish
|
||
}
|
||
return emptyList;
|
||
}
|
||
/// <summary>
|
||
/// Returns a cache object with the default values after import
|
||
/// </summary>
|
||
private static AdminPanelCache GetDefaultCache()
|
||
{
|
||
return new AdminPanelCache()
|
||
{
|
||
SETTINGS_PATH_OVERRIDE = String.Empty,
|
||
//setting them all to zero will enforce a re-write of them all if the cache is missing
|
||
DateOfLastSettingsFileChange = 0,
|
||
DateOfLastScriptChange = 0,
|
||
DateOfLastAdminFileChange = 0,
|
||
DateOfLastModeratorFileChange = 0,
|
||
DateOfLastPermaBanFileChange = 0,
|
||
DateOfLastPasswordFileChange = 0,
|
||
DateOfLastHashFileChange = 0,
|
||
DateOfLastSaltFileChange = 0
|
||
};
|
||
}
|
||
/// <summary>
|
||
/// Returns a settings object with the default settings after import
|
||
/// </summary>
|
||
private static AdminPanelSettings GetDefaultSettings()
|
||
{
|
||
return new AdminPanelSettings()
|
||
{
|
||
//basic
|
||
VRC_GUIDE_COMPLICANCE = true,
|
||
VRC_GUIDE_DEBUG = false,
|
||
//safety
|
||
PASSWORD_AUTHENTICATION = false,
|
||
MINIMAL_BAN_DEBUG = false,
|
||
ANTI_NAME_SPOOF = true,
|
||
MARK_BANNED_PLAYERS = true,
|
||
MUTE_BANNED_PLAYERS = true,
|
||
BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS = true,
|
||
//risky
|
||
ANTI_PICKUP_SUMMON_MOD = false,
|
||
ANTI_NO_TELEPORT_MOD = false,
|
||
USE_HONEYPOTS = false,
|
||
PERMA_BAN_LIST = false,
|
||
SURPRESS_WHITESPACE_CHARS = false,
|
||
//add-ons
|
||
ADMIN_ONLY_OBJECTS = false,
|
||
MODERATOR_AND_ADMIN_ONLY_OBJECTS = false,
|
||
DESTROY_FOR_OTHER_PLAYERS = false,
|
||
USE_CUSTOM_ALT_ACCOUNT_DETECTION = false,
|
||
//settings
|
||
ADMIN_CAN_FLY = true,
|
||
MODERATOR_CAN_FLY = false,
|
||
EVERYONE_CAN_FLY = false,
|
||
MODERATOR_CAN_BAN = true,
|
||
CAN_BAN_ADMINS = true,
|
||
SUMMON_PANEL_FUNCTION = true,
|
||
ACCURATE_CAPSULE_POSITIONS = true,
|
||
//unity
|
||
UNITY_SHOW_HASH_AND_SALT = false,
|
||
UNITY_SHOW_PASSWORD = true,
|
||
UNITY_STORE_CLEARTEXT_PASSWORD = true,
|
||
//New since 06.05.2023
|
||
DESYNC_BANNED_PLAYERS = false,
|
||
STEALTH_PANEL = false,
|
||
NO_BAN_EFFECTS = false,
|
||
DEBUG_ADMIN_NAME_DETECTION = false,
|
||
REMOTE_STRING_LOADING = false,
|
||
REMOTE_BAN_LIST = false,
|
||
REMOTE_ADMIN_LIST = false,
|
||
REMOTE_MODERATOR_LIST = false,
|
||
HASH_USERNAMES = false,
|
||
REMOTE_LOADING_URL = @"https://pastebin.com/raw/vTRKDYGw",
|
||
REMOTE_LOADING_REPEAT_EACH_SECONDS = 60 * 5
|
||
//#NEW_PROPERTY#
|
||
//-------------------------
|
||
};
|
||
}
|
||
#endregion CreateDefaultObjects
|
||
#region LoadSettings
|
||
/// <summary>
|
||
/// Loads the settings from disk, creates a default settings file if there was no such file already
|
||
/// </summary>
|
||
private AdminPanelSettings LoadSettings()
|
||
{
|
||
if (!AssetDatabase.IsValidFolder(SETTINGS_FOLDER_PATH))
|
||
{
|
||
SetupStoragePath();
|
||
}
|
||
|
||
if (_loadedCache == null)
|
||
{
|
||
//It's a bit of a nightmare to do this check inside the asset-DB https://forum.unity.com/threads/how-to-check-if-assetdatabase-exists.246956/
|
||
//so we disregard the DB and check the filesystem instead and hope for the best
|
||
if (!File.Exists(CACHE_FILE_PATH))
|
||
{
|
||
//create a new JSON file with the default cache
|
||
WriteTextFile(CACHE_FILE_PATH, JsonUtility.ToJson(GetDefaultCache(), true));
|
||
}
|
||
//load the settings from disk
|
||
string jsonString = ReadTextFile(CACHE_FILE_PATH);
|
||
_loadedCache = JsonUtility.FromJson<AdminPanelCache>(jsonString);
|
||
|
||
if (_loadedCache.SETTINGS_PATH_OVERRIDE != null && _loadedCache.SETTINGS_PATH_OVERRIDE.Trim() != String.Empty)
|
||
{
|
||
string overridePath = _loadedCache.SETTINGS_PATH_OVERRIDE;
|
||
if (Directory.Exists(overridePath))
|
||
{
|
||
SETTINGS_FILE_PATH = Path.Combine(overridePath, @"Settings.JSON");
|
||
ADMIN_FILE_PATH = Path.Combine(overridePath, @"Admins.txt");
|
||
MODERATOR_FILE_PATH = Path.Combine(overridePath, @"Moderators.txt");
|
||
PERMA_BAN_FILE_PATH = Path.Combine(overridePath, @"PermaBans.txt");
|
||
SALT_FILE_PATH = Path.Combine(overridePath, $"{AUTH_FOLDER}/Salt.txt");
|
||
HASH_FILE_PATH = Path.Combine(overridePath, $"{AUTH_FOLDER}/Hash.txt");
|
||
PASSWORD_FILE_PATH = Path.Combine(overridePath, $"{AUTH_FOLDER}/Password.txt");
|
||
}
|
||
}
|
||
}
|
||
|
||
if (_loadedSettings != null && _loadedCache.DateOfLastSettingsFileChange != File.GetLastWriteTimeUtc(SETTINGS_FILE_PATH).Ticks)
|
||
{
|
||
//force reload
|
||
_loadedSettings = null;
|
||
//force script rewrite
|
||
_scriptIsOutOfDate = true;
|
||
}
|
||
|
||
if (_loadedSettings == null)
|
||
{
|
||
if (!File.Exists(SETTINGS_FILE_PATH))
|
||
{
|
||
//create a new JSON file with the default settings
|
||
WriteTextFile(SETTINGS_FILE_PATH, JsonUtility.ToJson(GetDefaultSettings(), true));
|
||
}
|
||
//load the settings from disk
|
||
string jsonString = ReadTextFile(SETTINGS_FILE_PATH);
|
||
//check if the loaded file is from an older version
|
||
AdminPanelSettings tmpDefaults = GetDefaultSettings();
|
||
_loadedSettings = JsonUtility.FromJson<AdminPanelSettings>(jsonString);
|
||
//set correct default for new value from disk
|
||
if (!jsonString.Contains(nameof(tmpDefaults.MODERATOR_CAN_BAN)))
|
||
{
|
||
_loadedSettings.MODERATOR_CAN_BAN = true;
|
||
Debug.Log($"[AdminPanelEditor] Loaded older file from disk, added new {nameof(tmpDefaults.MODERATOR_CAN_BAN)} default.");
|
||
}
|
||
//set correct default for new value from disk
|
||
if (!jsonString.Contains(nameof(tmpDefaults.REMOTE_LOADING_URL)))
|
||
{
|
||
_loadedSettings.REMOTE_LOADING_URL = tmpDefaults.REMOTE_LOADING_URL;
|
||
Debug.Log($"[AdminPanelEditor] Loaded older file from disk, added new {nameof(tmpDefaults.REMOTE_LOADING_URL)} default.");
|
||
}
|
||
//set correct default for new value from disk
|
||
if (!jsonString.Contains(nameof(tmpDefaults.REMOTE_LOADING_REPEAT_EACH_SECONDS)))
|
||
{
|
||
_loadedSettings.REMOTE_LOADING_REPEAT_EACH_SECONDS = tmpDefaults.REMOTE_LOADING_REPEAT_EACH_SECONDS;
|
||
Debug.Log($"[AdminPanelEditor] Loaded older file from disk, added new {nameof(tmpDefaults.REMOTE_LOADING_REPEAT_EACH_SECONDS)} default.");
|
||
}
|
||
|
||
ApplySettingsToGUI(_loadedSettings);
|
||
|
||
_loadedCache.DateOfLastSettingsFileChange = File.GetLastWriteTimeUtc(SETTINGS_FILE_PATH).Ticks;
|
||
}
|
||
|
||
long lastScriptChange = File.GetLastWriteTimeUtc(SCRIPT_FILE_PATH).Ticks;
|
||
if (lastScriptChange != _loadedCache.DateOfLastScriptChange)
|
||
{
|
||
_scriptIsOutOfDate = true;
|
||
}
|
||
|
||
//any of these can set _reloadAuthFiles = true;
|
||
LoadAdminList();
|
||
LoadModeratorList();
|
||
|
||
LoadPermaBanList();
|
||
|
||
LoadPasswordList();
|
||
LoadHashList();
|
||
LoadSaltList();
|
||
|
||
// reset _reloadAuthFiles to avoid another reload next time
|
||
_reloadAuthFiles = false;
|
||
|
||
//return the loaded and applied state
|
||
return _loadedSettings;
|
||
}
|
||
/// <summary>
|
||
/// Loads the list of admins from a text file into <see cref="_loadedAdminList"/> and also <see cref="_displayedAdminList"/>
|
||
/// if the loaded list is null or if the list file on disk was edited since it was loaded last time
|
||
/// </summary>
|
||
private void LoadAdminList()
|
||
{
|
||
if (_loadedAdminList != null)
|
||
{
|
||
_oldCountLoadedAdmins = _loadedAdminList.Count;
|
||
}
|
||
else
|
||
{
|
||
_oldCountLoadedModerators = -1;
|
||
}
|
||
if (LoadUsernameList(ref _loadedAdminList, ref _displayedAdminList, GetDefaultAdminList(), ADMIN_FILE_PATH, ref _loadedCache.DateOfLastAdminFileChange))
|
||
{
|
||
_reloadAuthFiles = true;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Loads the list of moderators from a text file into <see cref="_loadedModeratorList"/> and also <see cref="_displayedModeratorList"/>
|
||
/// if the loaded list is null or if the list file on disk was edited since it was loaded last time
|
||
/// </summary>
|
||
private void LoadModeratorList()
|
||
{
|
||
if (_loadedModeratorList != null)
|
||
{
|
||
_oldCountLoadedModerators = _loadedModeratorList.Count;
|
||
}
|
||
else
|
||
{
|
||
_oldCountLoadedModerators = -1;
|
||
}
|
||
if (LoadUsernameList(ref _loadedModeratorList, ref _displayedModeratorList, GetDefaultModeratorList(), MODERATOR_FILE_PATH, ref _loadedCache.DateOfLastModeratorFileChange))
|
||
{
|
||
_reloadAuthFiles = true;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Loads the list of perma bans from a text file into <see cref="_loadedPermaBanList"/> and also <see cref="_displayedPermaBanList"/>
|
||
/// if the loaded list is null or if the list file on disk was edited since it was loaded last time
|
||
/// </summary>
|
||
private void LoadPermaBanList()
|
||
{
|
||
LoadUsernameList(ref _loadedPermaBanList, ref _displayedPermaBanList, GetDefaultPermaBanList(), PERMA_BAN_FILE_PATH, ref _loadedCache.DateOfLastPermaBanFileChange);
|
||
}
|
||
/// <summary>
|
||
/// Loads the list of passwords from a text file into <see cref="_loadedPasswordList"/> and also <see cref="_displayedPasswordList"/>
|
||
/// if the loaded list is null or if the list file on disk was edited since it was loaded last time
|
||
/// </summary>
|
||
private void LoadPasswordList()
|
||
{
|
||
if (!_reloadAuthFiles && (_loadedPasswordList != null && _loadedCache.DateOfLastPasswordFileChange == File.GetLastWriteTimeUtc(PASSWORD_FILE_PATH).Ticks))
|
||
return;
|
||
_loadedPasswordList = ConvertToDisplayValues(LoadListFromDisk(PASSWORD_FILE_PATH, ref _loadedCache.DateOfLastPasswordFileChange, GetDefaultPasswordList()));
|
||
Debug.Log($"[AdminPanelEditor] Loaded Password list from disk with {_loadedPasswordList.Count} values.");
|
||
_displayedPasswordList = new List<string>(_loadedPasswordList);
|
||
//ensure we have enough values to cover all users
|
||
AddNewEmptyEntries(ref _displayedPasswordList, false);
|
||
}
|
||
/// <summary>
|
||
/// Loads the list of hashes from a text file into <see cref="_loadedHashList"/> and also <see cref="_displayedHashList"/>
|
||
/// if the loaded list is null or if the list file on disk was edited since it was loaded last time
|
||
/// </summary>
|
||
private void LoadHashList()
|
||
{
|
||
if (!_reloadAuthFiles && (_loadedHashList != null && _loadedCache.DateOfLastHashFileChange == File.GetLastWriteTimeUtc(HASH_FILE_PATH).Ticks))
|
||
return;
|
||
_loadedHashList = ConvertToDisplayValues(LoadListFromDisk(HASH_FILE_PATH, ref _loadedCache.DateOfLastHashFileChange, GetDefaultHashList()));
|
||
Debug.Log($"[AdminPanelEditor] Loaded Hash list from disk with {_loadedHashList.Count} values.");
|
||
_displayedHashList = new List<string>(_loadedHashList);
|
||
//ensure we have enough values to cover all users
|
||
AddNewEmptyEntries(ref _displayedPasswordList, false);
|
||
}
|
||
/// <summary>
|
||
/// Loads the list of salts from a text file into <see cref="_loadedSaltList"/> and also <see cref="_displayedSaltList"/>
|
||
/// if the loaded list is null or if the list file on disk was edited since it was loaded last time
|
||
/// </summary>
|
||
private void LoadSaltList()
|
||
{
|
||
if (!_reloadAuthFiles && (_loadedSaltList != null && _loadedCache.DateOfLastSaltFileChange == File.GetLastWriteTimeUtc(SALT_FILE_PATH).Ticks))
|
||
return;
|
||
_loadedSaltList = ConvertToDisplayValues(LoadListFromDisk(SALT_FILE_PATH, ref _loadedCache.DateOfLastSaltFileChange, GetDefaultSaltList()));
|
||
Debug.Log($"[AdminPanelEditor] Loaded Salt list from disk with {_loadedSaltList.Count} values.");
|
||
_displayedSaltList = new List<string>(_loadedSaltList);
|
||
//add random salts where no hash is set yet
|
||
for (int i = 0; i < _displayedSaltList.Count; i++)
|
||
{
|
||
if (_displayedSaltList[i] == String.Empty)
|
||
_displayedSaltList[i] = GetRandomReadableSalt();
|
||
}
|
||
//ensure we have enough values to cover all users
|
||
AddNewEmptyEntries(ref _displayedPasswordList, true);
|
||
}
|
||
/// <summary>
|
||
/// Adds or inserts new default values for needed auth entries
|
||
/// </summary>
|
||
private void AddNewEmptyEntries(ref List<string> list, bool isSalt)
|
||
{
|
||
//first insert new values for added admins
|
||
if (_reloadAuthFiles && _oldCountLoadedAdmins != -1)
|
||
{
|
||
if (list.Count < _displayedAdminList.Count + _displayedModeratorList.Count)
|
||
{
|
||
int diff = _displayedAdminList.Count - _oldCountLoadedAdmins;
|
||
while (diff > 0)
|
||
{
|
||
if (isSalt)
|
||
{
|
||
list.Insert(_oldCountLoadedAdmins, GetRandomReadableSalt());
|
||
}
|
||
else
|
||
{
|
||
list.Insert(_oldCountLoadedAdmins, String.Empty);
|
||
}
|
||
diff--;
|
||
}
|
||
}
|
||
}
|
||
//ensure we have enough values to cover all users
|
||
while (list.Count < _displayedAdminList.Count + _displayedModeratorList.Count)
|
||
{
|
||
list.Add(String.Empty);
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Converts an auth info list to the displayable values
|
||
/// </summary>
|
||
private static List<string> ConvertToDisplayValues(in List<string> input)
|
||
{
|
||
List<string> newList = new List<string>(input);
|
||
for (int i = 0; i < newList.Count; i++)
|
||
{
|
||
if (newList[i] == AUTH_NOT_SET_IN_FILE)
|
||
newList[i] = String.Empty;
|
||
if (newList[i] == PASSWORD_UNKNOWN_IN_FILE)
|
||
newList[i] = PASSWORD_UNKNOWN;
|
||
}
|
||
return newList;
|
||
}
|
||
/// <summary>
|
||
/// Converts an auth info list to the storageable values
|
||
/// </summary>
|
||
private static List<string> ConvertToStorageValues(in List<string> input)
|
||
{
|
||
List<string> newList = new List<string>(input);
|
||
for (int i = 0; i < newList.Count; i++)
|
||
{
|
||
if (newList[i] == String.Empty)
|
||
newList[i] = AUTH_NOT_SET_IN_FILE;
|
||
if (newList[i] == PASSWORD_UNKNOWN)
|
||
newList[i] = PASSWORD_UNKNOWN_IN_FILE;
|
||
}
|
||
return newList;
|
||
}
|
||
/// <summary>
|
||
/// Returns a list with one empty string per moderator and admin
|
||
/// </summary>
|
||
private static List<string> GetDefaultEmptyAuthList()
|
||
{
|
||
List<string> pwList = new List<string>();
|
||
foreach (String admin in _loadedAdminList)
|
||
{
|
||
pwList.Add(String.Empty);
|
||
}
|
||
foreach (String moderator in _loadedModeratorList)
|
||
{
|
||
pwList.Add(String.Empty);
|
||
}
|
||
return pwList;
|
||
}
|
||
private bool _reloadAuthFiles;
|
||
private int _oldCountLoadedAdmins;
|
||
private int _oldCountLoadedModerators;
|
||
/// <summary>
|
||
/// Does nothing if loadedlist is not null and also the list on disk hasn't changed since the last known time.
|
||
/// Else loads the <see cref="defaultList "/> if there is no file at the specified <see cref="path"/>.
|
||
/// Else it loads a list of strings from the text file there into <see cref="loadedlist"/>
|
||
/// </summary>
|
||
private bool LoadUsernameList(ref List<string> loadedlist, ref List<string> displayedlist, List<string> defaultList, string path, ref long lastKnownChange)
|
||
{
|
||
if (loadedlist != null && lastKnownChange == File.GetLastWriteTimeUtc(path).Ticks)
|
||
return false;
|
||
loadedlist = LoadListFromDisk(path, ref lastKnownChange, defaultList);
|
||
Debug.Log($"[AdminPanelEditor] Loaded Username list from disk with {loadedlist.Count} values.");
|
||
displayedlist = new List<string>(loadedlist);
|
||
return true;
|
||
}
|
||
/// <summary>
|
||
/// Loads a list of strings (separated by line breaks) from a text file from the specified path
|
||
/// if the
|
||
/// </summary>
|
||
private List<string> LoadListFromDisk(string path, ref long lastChangedDateFromSettings, List<string> defaultList)
|
||
{
|
||
//load the list from disk
|
||
if (File.Exists(path))
|
||
{
|
||
long lastFileChange = File.GetLastWriteTimeUtc(path).Ticks;
|
||
//this check happens here as well to ensure that it is loaded either way (at start) but without setting the flag unless it changed since last time
|
||
if (lastFileChange != lastChangedDateFromSettings)
|
||
{
|
||
_scriptIsOutOfDate = true;
|
||
lastChangedDateFromSettings = lastFileChange;
|
||
}
|
||
return RemoveEmptyLinesAndTrimValues(TextToLineArray(ReadTextFile(path)));
|
||
}
|
||
else
|
||
{
|
||
WriteTextFile(path, ListToText(defaultList));
|
||
return defaultList;
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Converts a list of strings to a single text with line breaks between each element, while removing empty elements
|
||
/// </summary>
|
||
private string ListToText(List<string> list)
|
||
{
|
||
string result = string.Empty;
|
||
foreach (string entry in list)
|
||
{
|
||
if (entry.Trim() == string.Empty)
|
||
continue;
|
||
result += entry.Trim() + '\n';
|
||
}
|
||
return result;
|
||
}
|
||
#endregion LoadSettings
|
||
#region GUI
|
||
public override void OnInspectorGUI()
|
||
{
|
||
ReimajoEditorBase.AddStandardHeader(PRODUCT_NAME, VERSION, DOCUMENTATION, target);
|
||
|
||
if (!File.Exists(SCRIPT_FILE_PATH))
|
||
{
|
||
EditorGUILayout.Space();
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "ERROR: The asset path has changed from the default path");
|
||
ReimajoEditorBase.DrawLabelField(Color.red, SCRIPT_FILE_PATH);
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "This is currently not supported.");
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "Please move the asset back to the original path.");
|
||
EditorGUILayout.Space();
|
||
return;
|
||
}
|
||
|
||
if (_lastEditFailed)
|
||
{
|
||
EditorGUILayout.Space();
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "ERROR: Editing the script failed.");
|
||
ReimajoEditorBase.DrawLabelField(Color.red, SCRIPT_FILE_PATH);
|
||
EditorGUILayout.Space();
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "This is because certain fields were not found inside the script.");
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "It is most likely that you accidentally made changes to the script directly.");
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "This is not supported for name lists and compiler options.");
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "Please restore the original script state by reimporting the asset.");
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "Only use the inspector in the future to edit those lists & options.");
|
||
EditorGUILayout.Space();
|
||
ReimajoEditorBase.DrawUILine(Color.gray);
|
||
}
|
||
|
||
AdminPanelSettings settings = LoadSettings();
|
||
|
||
if (CheckUnappliedSettings(settings))
|
||
{
|
||
ShowSettingsApplyButton(label1: "The script has pending changes.",
|
||
label2: "Click the button below to apply the changes to the script.",
|
||
buttonText: "Apply Settings");
|
||
}
|
||
else if (_scriptIsOutOfDate)
|
||
{
|
||
ShowSettingsApplyButton(label1: "File(s) got changed on disk. The script maybe no longer has your settings.",
|
||
label2: "Simply click the button below to (re-)apply your settings to the script.",
|
||
buttonText: "Re-Apply Settings");
|
||
}
|
||
|
||
DisplaySettings(settings);
|
||
|
||
ReimajoEditorBase.DrawUILine(Color.gray);
|
||
ReimajoEditorBase.DrawLabelField(Color.yellow, "The fields below are part of UdonSharp. Do not modify them unless you know what you are doing.");
|
||
ReimajoEditorBase.DrawUILine(Color.gray);
|
||
bool lastHasLineDrawn = true;
|
||
|
||
Color cachedGuiColor = GUI.color;
|
||
serializedObject.Update();
|
||
|
||
if (AreValuesMissing(serializedObject))
|
||
{
|
||
if (!lastHasLineDrawn) ReimajoEditorBase.DrawUILine(Color.gray);
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "ERROR: Some mandatory references are missing; so the asset wont work (see the red fields below)");
|
||
ReimajoEditorBase.DrawUILine(Color.gray);
|
||
lastHasLineDrawn = true;
|
||
}
|
||
|
||
SerializedProperty property = serializedObject.GetIterator();
|
||
bool isVisible = property.NextVisible(true);
|
||
if (isVisible)
|
||
do
|
||
{
|
||
GUI.color = cachedGuiColor;
|
||
if (IsPropertyValueMissing(property))
|
||
{
|
||
lastHasLineDrawn = HandlePropertyWithMissingRefs(property, lastHasLineDrawn);
|
||
}
|
||
else
|
||
{
|
||
lastHasLineDrawn = ReimajoEditorBase.Default_HandleProperty(property, lastHasLineDrawn);
|
||
}
|
||
} while (property.NextVisible(false));
|
||
|
||
EditorGUILayout.Space();
|
||
serializedObject.ApplyModifiedProperties();
|
||
}
|
||
#endregion GUI
|
||
#region SettingsGUI
|
||
private void DisplaySettings(AdminPanelSettings settings)
|
||
{
|
||
// _toggleState_#1# = EditorGUILayoutToggle("#1#", _toggleState_#1#);
|
||
#region ComplicanceSettings
|
||
if (_toggleState_VRC_GUIDE_COMPLICANCE)
|
||
{
|
||
ReimajoEditorBase.DrawLabelField(Color.green, "This tool is currently fully compliant with the Udon moderation tool guide");
|
||
ReimajoEditorBase.DrawLabelField(Color.white, "https://docs.vrchat.com/docs/udon-moderation-tool-guidelines");
|
||
EditorGUILayout.Space();
|
||
ReimajoEditorBase.DrawLabelField(Color.yellow, "Hint: Disable compliance to see more safety options & features");
|
||
}
|
||
_toggleState_VRC_GUIDE_COMPLICANCE = EditorGUILayoutToggle("VRC GUIDE COMPLICANCE", _toggleState_VRC_GUIDE_COMPLICANCE);
|
||
if (!_toggleState_VRC_GUIDE_COMPLICANCE)
|
||
{
|
||
_toggleState_VRC_GUIDE_DEBUG = EditorGUILayoutToggle("VRC GUIDE DEBUG", _toggleState_VRC_GUIDE_DEBUG);
|
||
DrawSectionHeader("=== Risky Extensions ===");
|
||
ReimajoEditorBase.DrawLabelField(Color.yellow, "Enable the following options at your own risk");
|
||
if (!_toggleState_ANTI_PICKUP_SUMMON_MOD || !_toggleState_USE_HONEYPOTS || !_toggleState_ANTI_NO_TELEPORT_MOD)
|
||
ReimajoEditorBase.DrawLabelField(Color.yellow, "I recommend to enable the first 3 options below");
|
||
_toggleState_ANTI_PICKUP_SUMMON_MOD = EditorGUILayoutToggle("ANTI PICKUP SUMMON MOD", _toggleState_ANTI_PICKUP_SUMMON_MOD);
|
||
_toggleState_ANTI_NO_TELEPORT_MOD = EditorGUILayoutToggle("ANTI NO TELEPORT MOD", _toggleState_ANTI_NO_TELEPORT_MOD);
|
||
_toggleState_USE_HONEYPOTS = EditorGUILayoutToggle("USE HONEYPOTS", _toggleState_USE_HONEYPOTS);
|
||
_toggleState_PERMA_BAN_LIST = EditorGUILayoutToggle("PERMA BAN LIST", _toggleState_PERMA_BAN_LIST);
|
||
_toggleState_SURPRESS_WHITESPACE_CHARS = EditorGUILayoutToggle("SURPRESS WHITESPACE CHARS", _toggleState_SURPRESS_WHITESPACE_CHARS);
|
||
_toggleState_USE_CUSTOM_ALT_ACCOUNT_DETECTION = EditorGUILayoutToggle("USE CUSTOM ALT ACCOUNT DETECTION", _toggleState_USE_CUSTOM_ALT_ACCOUNT_DETECTION);
|
||
}
|
||
#endregion ComplicanceSettings
|
||
|
||
#region SafetySettings
|
||
DrawSectionHeader("=== Safety Settings ===");
|
||
|
||
if (!_toggleState_PASSWORD_AUTHENTICATION)
|
||
{
|
||
ReimajoEditorBase.DrawLabelField(Color.yellow, "I recommend to use PASSWORD_AUTHENTICATION to prevent name spoofing locally");
|
||
}
|
||
_toggleState_PASSWORD_AUTHENTICATION = EditorGUILayoutToggle("PASSWORD AUTHENTICATION", _toggleState_PASSWORD_AUTHENTICATION);
|
||
//if (_toggleState_PASSWORD_AUTHENTICATION)
|
||
//{
|
||
// ReimajoEditorBase.DrawLabelField(Color.yellow, "Don't forget to set up passwords for the users (check the documentation).");
|
||
//}
|
||
EditorGUILayout.Space();
|
||
_toggleState_ANTI_NAME_SPOOF = EditorGUILayoutToggle("ANTI NAME SPOOF", _toggleState_ANTI_NAME_SPOOF);
|
||
_toggleState_MARK_BANNED_PLAYERS = EditorGUILayoutToggle("MARK BANNED PLAYERS", _toggleState_MARK_BANNED_PLAYERS);
|
||
_toggleState_MUTE_BANNED_PLAYERS = EditorGUILayoutToggle("MUTE BANNED PLAYERS", _toggleState_MUTE_BANNED_PLAYERS);
|
||
_toggleState_BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS = EditorGUILayoutToggle("BANNED PLAYERS CAN TALK WITH MODERATORS", _toggleState_BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS);
|
||
#endregion SafetySettings
|
||
|
||
#region AddOnSettings
|
||
DrawSectionHeader("=== Add-Ons ===");
|
||
|
||
_toggleState_ADMIN_ONLY_OBJECTS = EditorGUILayoutToggle("ADMIN ONLY OBJECTS", _toggleState_ADMIN_ONLY_OBJECTS);
|
||
_toggleState_MODERATOR_AND_ADMIN_ONLY_OBJECTS = EditorGUILayoutToggle("MODERATOR AND ADMIN ONLY OBJECTS", _toggleState_MODERATOR_AND_ADMIN_ONLY_OBJECTS);
|
||
_toggleState_DESTROY_FOR_OTHER_PLAYERS = EditorGUILayoutToggle("DESTROY FOR OTHER PLAYERS", _toggleState_DESTROY_FOR_OTHER_PLAYERS);
|
||
#endregion AddOnSettings
|
||
|
||
#region GeneralSettings
|
||
DrawSectionHeader("=== General Settings ===");
|
||
|
||
_toggleState_ADMIN_CAN_FLY = EditorGUILayoutToggle("ADMIN CAN FLY", _toggleState_ADMIN_CAN_FLY);
|
||
_toggleState_MODERATOR_CAN_FLY = EditorGUILayoutToggle("MODERATOR CAN FLY", _toggleState_MODERATOR_CAN_FLY);
|
||
_toggleState_EVERYONE_CAN_FLY = EditorGUILayoutToggle("EVERYONE CAN FLY", _toggleState_EVERYONE_CAN_FLY);
|
||
_toggleState_CAN_BAN_ADMINS = EditorGUILayoutToggle("CAN BAN ADMINS", _toggleState_CAN_BAN_ADMINS);
|
||
_toggleState_MODERATOR_CAN_BAN = EditorGUILayoutToggle("MODERATOR CAN BAN", _toggleState_MODERATOR_CAN_BAN);
|
||
_toggleState_SUMMON_PANEL_FUNCTION = EditorGUILayoutToggle("SUMMON PANEL FUNCTION", _toggleState_SUMMON_PANEL_FUNCTION);
|
||
_toggleState_ACCURATE_CAPSULE_POSITIONS = EditorGUILayoutToggle("ACCURATE CAPSULE POSITIONS", _toggleState_ACCURATE_CAPSULE_POSITIONS);
|
||
#endregion GeneralSettings
|
||
|
||
#region NotRecommendedSettings
|
||
DrawSectionHeader("=== Not recommended ===");
|
||
_toggleState_MINIMAL_BAN_DEBUG = EditorGUILayoutToggle("MINIMAL BAN DEBUG", _toggleState_MINIMAL_BAN_DEBUG);
|
||
#endregion NotRecommendedSettings
|
||
|
||
//New since 06.05.2023
|
||
#region ExperimentalSettings
|
||
DrawSectionHeader("=== Experimental / Untested Features ===");
|
||
_toggleState_STEALTH_PANEL = EditorGUILayoutToggle("STEALTH PANEL", _toggleState_STEALTH_PANEL);
|
||
_toggleState_NO_BAN_EFFECTS = EditorGUILayoutToggle("NO BAN EFFECTS", _toggleState_NO_BAN_EFFECTS);
|
||
//_toggleState_DESYNC_BANNED_PLAYERS = EditorGUILayoutToggle("DESYNC BANNED PLAYERS", _toggleState_DESYNC_BANNED_PLAYERS);
|
||
//New since 29.05.2023
|
||
//_toggleState_HASH_USERNAMES = EditorGUILayoutToggle("HASH USERNAMES", _toggleState_HASH_USERNAMES);
|
||
_toggleState_REMOTE_STRING_LOADING = EditorGUILayoutToggle("REMOTE STRING LOADING", _toggleState_REMOTE_STRING_LOADING);
|
||
if (_toggleState_REMOTE_STRING_LOADING)
|
||
{
|
||
_toggleState_REMOTE_ADMIN_LIST = EditorGUILayoutToggle("REMOTE ADMIN LIST", _toggleState_REMOTE_ADMIN_LIST);
|
||
_toggleState_REMOTE_MODERATOR_LIST = EditorGUILayoutToggle("REMOTE MODERATOR LIST", _toggleState_REMOTE_MODERATOR_LIST);
|
||
if (!_toggleState_VRC_GUIDE_COMPLICANCE)
|
||
{
|
||
_toggleState_REMOTE_BAN_LIST = EditorGUILayoutToggle("REMOTE BAN LIST", _toggleState_REMOTE_BAN_LIST);
|
||
}
|
||
float labelWidthDefault = EditorGUIUtility.labelWidth;
|
||
//-------- Remote loading URL label and field -------------
|
||
GUILayout.BeginHorizontal(GUIStyle.none, GUILayout.Height(20));
|
||
EditorGUIUtility.labelWidth = 240;
|
||
EditorGUILayout.LabelField(FormatLabel("REMOTE LOADING URL"), GUILayout.Width(EditorGUIUtility.labelWidth - 4));
|
||
GUILayout.Space(10);
|
||
_editorState_REMOTE_LOADING_URL = EditorGUILayout.DelayedTextField(_editorState_REMOTE_LOADING_URL);
|
||
GUILayout.EndHorizontal();
|
||
//-------- Remote loading delay label and field -------------
|
||
GUILayout.BeginHorizontal(GUIStyle.none, GUILayout.Height(20));
|
||
EditorGUILayout.LabelField(FormatLabel("LOADING REPEAT EACH SECONDS"), GUILayout.Width(EditorGUIUtility.labelWidth - 4));
|
||
GUILayout.Space(10);
|
||
_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS = EditorGUILayout.DelayedIntField(_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS);
|
||
if (_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS < 10)
|
||
_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS = 10;
|
||
GUILayout.EndHorizontal();
|
||
//-----------------------------------------------------------
|
||
EditorGUIUtility.labelWidth = labelWidthDefault;
|
||
}
|
||
#endregion ExperimentalSettings
|
||
|
||
#region DebugSettings
|
||
DrawSectionHeader("=== Only for testing / Disable in live build ===");
|
||
_toggleState_DEBUG_ADMIN_NAME_DETECTION = EditorGUILayoutToggle("DEBUG ADMIN NAME DETECTION", _toggleState_DEBUG_ADMIN_NAME_DETECTION);
|
||
if (_toggleState_DEBUG_ADMIN_NAME_DETECTION)
|
||
ReimajoEditorBase.DrawLabelField(Color.red, "WARNING: DO NOT UPLOAD YOUR WORLD TO PUBLIC WITH THIS ENABLED, this is dangerous. Only use it for testing.");
|
||
#endregion ExperimentalSettings
|
||
//#NEW_PROPERTY#
|
||
//-------------------------
|
||
|
||
#region AdminList
|
||
DrawSectionHeader("=== Admins ===", withLineAbove: true);
|
||
if (_toggleState_PASSWORD_AUTHENTICATION)
|
||
{
|
||
_toggleState_UNITY_SHOW_PASSWORD = EditorGUILayoutToggle("Show passwords", _toggleState_UNITY_SHOW_PASSWORD, keepCasing: true);
|
||
_toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS = EditorGUILayoutToggle("Store cleartext passwords (in Unity)", _toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS, keepCasing: true);
|
||
_toggleState_UNITY_SHOW_HASH_AND_SALT = EditorGUILayoutToggle("[EXPERT] Show hash / salt", _toggleState_UNITY_SHOW_HASH_AND_SALT, keepCasing: true);
|
||
EditorGUILayout.Space();
|
||
}
|
||
try
|
||
{
|
||
DrawList(roleName: "Admin", ADMIN_FILE_PATH, ref _loadedAdminList, ref _displayedAdminList, showAuthInfo: true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError("Unable to display Admin list in editor.");
|
||
Debug.LogException(ex);
|
||
}
|
||
#endregion AdminList
|
||
|
||
#region ModeratorList
|
||
DrawSectionHeader("=== Moderators ===", withLineAbove: true);
|
||
if (_toggleState_PASSWORD_AUTHENTICATION)
|
||
{
|
||
_toggleState_UNITY_SHOW_PASSWORD = EditorGUILayoutToggle("Show passwords", _toggleState_UNITY_SHOW_PASSWORD, keepCasing: true);
|
||
_toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS = EditorGUILayoutToggle("Store cleartext passwords", _toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS, keepCasing: true);
|
||
_toggleState_UNITY_SHOW_HASH_AND_SALT = EditorGUILayoutToggle("[EXPERT] Show hash / salt", _toggleState_UNITY_SHOW_HASH_AND_SALT, keepCasing: true);
|
||
EditorGUILayout.Space();
|
||
}
|
||
try
|
||
{
|
||
DrawList(roleName: "Moderator", MODERATOR_FILE_PATH, ref _loadedModeratorList, ref _displayedModeratorList, showAuthInfo: true, addOffset: true);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
Debug.LogError("Unable to display Moderator list in editor.");
|
||
Debug.LogException(ex);
|
||
}
|
||
#endregion AdminList
|
||
|
||
#region PermaBanList
|
||
if (!_toggleState_VRC_GUIDE_COMPLICANCE && _toggleState_PERMA_BAN_LIST)
|
||
{
|
||
DrawSectionHeader("=== Perma Bans ===", withLineAbove: true);
|
||
DrawList(roleName: "Perma ban", PERMA_BAN_FILE_PATH, ref _loadedPermaBanList, ref _displayedPermaBanList);
|
||
}
|
||
#endregion PermaBanList
|
||
}
|
||
#endregion SettingsGUI
|
||
#region ListDrawing
|
||
/// <summary>
|
||
/// Draws a list of specified VRChat usernames
|
||
/// </summary>
|
||
/// <param name="roleName">Name of the role</param>
|
||
/// <param name="filePath">Path to the file with the usernames in it</param>
|
||
/// <param name="loadedList">The loaded list of usernames</param>
|
||
/// <param name="displayedList">The dispalyed list of usernames</param>
|
||
/// <param name="showAuthInfo">If the auth info should be displayed next to it</param>
|
||
/// <param name="addOffset">Offset in the auth info lists for this user</param>
|
||
private void DrawList(string roleName, string filePath, ref List<string> loadedList, ref List<string> displayedList, bool showAuthInfo = false, bool addOffset = false)
|
||
{
|
||
//since both admin and mod has auth info, but both is stored in the same auth file, they contain first the admins and then the moderators info
|
||
int offsetInAuthLists = addOffset ? _displayedAdminList.Count : 0;
|
||
for (int i = 0; i < displayedList.Count; i++)
|
||
{
|
||
bool isLocked = showAuthInfo && (displayedList[i] == "Reimajo" || displayedList[i] == "JoRoFox" || displayedList[i] == "Alex Lotor" || displayedList[i] == "NotFish");
|
||
GUILayout.BeginHorizontal(GUIStyle.none, GUILayout.Height(20));
|
||
|
||
if (_toggleState_PASSWORD_AUTHENTICATION && showAuthInfo)
|
||
{
|
||
float labelWidthDefault = EditorGUIUtility.labelWidth;
|
||
EditorGUIUtility.labelWidth = 110;
|
||
displayedList[i] = LockableTextField($"VRChat Name:", displayedList[i], isLocked);
|
||
GUILayout.Space(10);
|
||
if (_toggleState_UNITY_SHOW_PASSWORD)
|
||
{
|
||
//color section yellow if there is no password set yet
|
||
if (_displayedPasswordList[i + offsetInAuthLists].Trim() == String.Empty)
|
||
{
|
||
GUI.color = Color.yellow;
|
||
}
|
||
EditorGUIUtility.labelWidth = 70;
|
||
_displayedPasswordList[i + offsetInAuthLists] = LockableTextField($"Password:", _displayedPasswordList[i + offsetInAuthLists], isLocked);
|
||
GUI.color = Color.white;
|
||
GUILayout.Space(10);
|
||
}
|
||
if (_toggleState_UNITY_SHOW_HASH_AND_SALT)
|
||
{
|
||
EditorGUIUtility.labelWidth = 40;
|
||
_displayedHashList[i + offsetInAuthLists] = LockableTextField($"Hash:", _displayedHashList[i + offsetInAuthLists], isLocked);
|
||
GUILayout.Space(10);
|
||
_displayedSaltList[i + offsetInAuthLists] = LockableTextField($"Salt:", _displayedSaltList[i + offsetInAuthLists], isLocked);
|
||
GUILayout.Space(10);
|
||
}
|
||
EditorGUIUtility.labelWidth = labelWidthDefault;
|
||
}
|
||
else
|
||
{
|
||
displayedList[i] = LockableTextField($"VRChat Name:", displayedList[i], isLocked);
|
||
GUILayout.Space(10);
|
||
}
|
||
if (GUILayout.Button($"Remove", GUILayout.Height(20f), GUILayout.Width(70f)))
|
||
{
|
||
bool cancelRemove = false;
|
||
if (displayedList[i] == "Reimajo" && showAuthInfo)
|
||
{
|
||
cancelRemove = !EditorUtility.DisplayDialog("Removing the asset creator from the admin list",
|
||
"Are you sure you want to remove the creator of this asset from the admin list? " +
|
||
"This will make it really hard for them to give you support if any issues occur.", "Yes, remove", "No, I'm a nice person");
|
||
|
||
if (!cancelRemove)
|
||
{
|
||
cancelRemove = !EditorUtility.DisplayDialog("Removing the asset creator from the admin list",
|
||
"You clicked the wrong option. This one makes me sad, and I don't want to be sad. " +
|
||
"I'm sure you only clicked it by accident, right? I'll give you another chance." +
|
||
"The right answer is \"No\" by the way.\n\n" +
|
||
|
||
"Are you sure you want to remove the creator of this asset from the admin list? " +
|
||
"This will make it really hard for them to give you support if any issues occur.", "Yes, remove", "No, I'm a nice person");
|
||
}
|
||
if (!cancelRemove)
|
||
{
|
||
cancelRemove = EditorUtility.DisplayDialog("Removing the asset creator from the admin list",
|
||
"Whoops, you clicked the wrong option again. No sane person would ever want to remove me from the list. " +
|
||
"But I'm sure you only clicked it by accident, right? I'll make this game easier for you:\n\n" +
|
||
|
||
"To prevent the wrong click, I swapped the options around this time, because you probably just enjoy " +
|
||
"clicking the default options, so now you can do so without removing me!\n\n" +
|
||
|
||
"Are you sure you want to remove the creator of this asset from the admin list? " +
|
||
"This will make it really hard for them to give you support if any issues occur.", "No, I'm a nice person", "Yes, remove");
|
||
}
|
||
if (!cancelRemove)
|
||
{
|
||
cancelRemove = EditorUtility.DisplayDialog("Removing the asset creator from the admin list",
|
||
"Oh wow, you are really mean to me. " +
|
||
"I'm starting to question our relationship at this point... " +
|
||
"Why would anyone ever want to not have me as an Admin in their world? " +
|
||
"Maybe I didn't made the answer options clear enough, so let me try it again.\n\n" +
|
||
|
||
"Are you really really sure you want to be a super mean person and make the creator of this asset really sad by removing them from the admin list? " +
|
||
"This will make it really hard for them to give you support if any issues occur.", "No, because I'm a super nice person", "Yes, I'm mean and I hate the creator of this asset");
|
||
}
|
||
if (!cancelRemove)
|
||
{
|
||
cancelRemove = !EditorUtility.DisplayDialog("Removing the asset creator from the admin list",
|
||
"You tried really hard to get to this point here, wow. " +
|
||
"You must hate me a lot. I think all hope is lost then. " +
|
||
"All I wanted to have is the ability to fly in your world and you won't give it to me :(\n\n" +
|
||
|
||
"I'd never ban anyone, just flying around, wheeeeeeeeeeeee! It would make me so happy! " +
|
||
"Don't you want me to be happy? Maybe add me as a moderator then so I can at least fly? " +
|
||
"Okay okay, I won't stop you anymore, but I'll go now and cry in a corner.\n\n" +
|
||
|
||
"Are you really really sure you want to be a super mean person and don't allow the creator of this asset to have fun flying around by removing them from the admin list? " +
|
||
"This will make it really hard for them to give you support if any issues occur.", "Yes, I'm mean and I hate the creator of this asset", "No, because I'm a super nice person");
|
||
}
|
||
}
|
||
if (!cancelRemove)
|
||
{
|
||
displayedList.RemoveAt(i);
|
||
Debug.Log($"[AdminPanelEditor] Removed {roleName} at pos {i} / auth pos {i + offsetInAuthLists}.");
|
||
if (showAuthInfo)
|
||
{
|
||
_displayedPasswordList.RemoveAt(i + offsetInAuthLists);
|
||
_displayedHashList.RemoveAt(i + offsetInAuthLists);
|
||
_displayedSaltList.RemoveAt(i + offsetInAuthLists);
|
||
}
|
||
}
|
||
}
|
||
GUILayout.EndHorizontal();
|
||
//warn about missing password
|
||
if (_toggleState_PASSWORD_AUTHENTICATION && showAuthInfo && _displayedPasswordList[i + offsetInAuthLists].Trim() == String.Empty)
|
||
{
|
||
ReimajoEditorBase.DrawLabelField(Color.yellow, $"User needs a password to authenticate in game." + (!_toggleState_UNITY_SHOW_PASSWORD ? " Click \"show passwords\" above to add one." : ""));
|
||
EditorGUILayout.Space();
|
||
}
|
||
}
|
||
if (ListHasChanged(loadedList, displayedList) || _toggleState_PASSWORD_AUTHENTICATION && (ListHasChanged(_loadedPasswordList, _displayedPasswordList) ||
|
||
ListHasChanged(_loadedHashList, _displayedHashList) || ListHasChanged(_loadedSaltList, _displayedSaltList)))
|
||
{
|
||
ReimajoEditorBase.DrawLabelField(Color.red, $"Changes are not applied yet, click the button on top to do so");
|
||
}
|
||
EditorGUILayout.Space();
|
||
GUILayout.BeginHorizontal(GUIStyle.none, GUILayout.Height(25));
|
||
if (GUILayout.Button($"Add new {roleName}", GUILayout.Height(25f)))
|
||
{
|
||
displayedList.Add(String.Empty);
|
||
Debug.Log($"[AdminPanelEditor] Added new {roleName} at pos {displayedList.Count} / auth pos {displayedList.Count + offsetInAuthLists - 1}.");
|
||
if (showAuthInfo)
|
||
{
|
||
_displayedPasswordList.Insert(displayedList.Count + offsetInAuthLists - 1, String.Empty);
|
||
_displayedHashList.Insert(displayedList.Count + offsetInAuthLists - 1, String.Empty);
|
||
_displayedSaltList.Insert(displayedList.Count + offsetInAuthLists - 1, GetRandomReadableSalt());
|
||
}
|
||
}
|
||
GUILayout.Space(10);
|
||
if (!_toggleState_PASSWORD_AUTHENTICATION || !showAuthInfo)
|
||
{
|
||
ReimajoEditorBase.DrawLabelField(Color.white, $"Hint: You can also add {roleName}s in /{filePath.Substring(UNITY_FOLDER.Length + 1)}");
|
||
}
|
||
GUILayout.EndHorizontal();
|
||
|
||
EditorGUILayout.Space();
|
||
}
|
||
/// <summary>
|
||
/// A text field that can be locked, always returns the original content if it is locked, else the changed content
|
||
/// </summary>
|
||
private string LockableTextField(string label, string content, bool isLocked)
|
||
{
|
||
if (isLocked)
|
||
{
|
||
EditorGUILayout.BeginHorizontal();
|
||
{
|
||
EditorGUILayout.LabelField(label, GUILayout.Width(EditorGUIUtility.labelWidth - 4));
|
||
EditorGUILayout.SelectableLabel(content, EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight));
|
||
}
|
||
EditorGUILayout.EndHorizontal();
|
||
return content;
|
||
}
|
||
else
|
||
{
|
||
return EditorGUILayout.TextField(label, content);
|
||
}
|
||
}
|
||
private void DrawSectionHeader(string text, bool withLineAbove = false)
|
||
{
|
||
if (withLineAbove)
|
||
{
|
||
EditorGUILayout.Space();
|
||
ReimajoEditorBase.DrawUILine(Color.gray);
|
||
}
|
||
EditorGUILayout.Space();
|
||
ReimajoEditorBase.DrawLabelField(Color.white, text);
|
||
EditorGUILayout.Space();
|
||
}
|
||
/// <summary>
|
||
/// Creates a standard GUI toggle but formats the label
|
||
/// </summary>
|
||
private bool EditorGUILayoutToggle(string label, bool value, bool keepCasing = false)
|
||
{
|
||
if (!keepCasing)
|
||
{
|
||
label = FormatLabel(label);
|
||
}
|
||
return EditorGUILayout.Toggle(label, value);
|
||
}
|
||
#endregion ListDrawing
|
||
#region MissingProperties
|
||
/// <summary>
|
||
/// Returns true if a mandatory serialized property is missing
|
||
/// </summary>
|
||
/// <param name="serializedObject"></param>
|
||
/// <returns></returns>
|
||
private bool AreValuesMissing(SerializedObject serializedObject)
|
||
{
|
||
SerializedProperty property = serializedObject.GetIterator();
|
||
bool isVisible = property.NextVisible(true);
|
||
if (isVisible)
|
||
do
|
||
{
|
||
if (IsPropertyValueMissing(property))
|
||
{
|
||
return true;
|
||
}
|
||
} while (property.NextVisible(false));
|
||
return false;
|
||
}
|
||
/// <summary>
|
||
/// Draws a property in red because references are missing
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
private static bool HandlePropertyWithMissingRefs(SerializedProperty property, bool lastHasLineDrawn)
|
||
{
|
||
if (property.isArray && property.propertyType != SerializedPropertyType.String)
|
||
{
|
||
DrawArrayWithMissingRefs(lastHasLineDrawn, property);
|
||
return true;
|
||
}
|
||
else
|
||
{
|
||
GUI.color = Color.red;
|
||
EditorGUILayout.PropertyField(property, property.isExpanded);
|
||
GUI.color = Color.white;
|
||
return false;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Draws a property array between two lines and draws missing references red
|
||
/// </summary>
|
||
/// <param name="lastHasLineDrawn">if a line has been drawn after the last property</param>
|
||
/// <param name="property">the array property</param>
|
||
private static void DrawArrayWithMissingRefs(bool lastHasLineDrawn, SerializedProperty property)
|
||
{
|
||
if (!lastHasLineDrawn)
|
||
{
|
||
ReimajoEditorBase.DrawUILine(Color.gray);
|
||
}
|
||
if (property.arraySize > 0)
|
||
{
|
||
property.isExpanded = true;
|
||
}
|
||
GUI.color = Color.red; //Set the color of the GUI
|
||
EditorGUILayout.PropertyField(property, property.isExpanded);
|
||
GUI.color = Color.white; //Reset the color of the GUI to white
|
||
|
||
ReimajoEditorBase.DrawUILine(Color.gray);
|
||
}
|
||
/// <summary>
|
||
/// Returns true if a property has a missing mandatory value
|
||
/// </summary>
|
||
/// <param name="property"></param>
|
||
/// <returns></returns>
|
||
private static bool IsPropertyValueMissing(SerializedProperty property)
|
||
{
|
||
bool isdefaultScriptProperty = property.name.Equals("m_Script") && property.type.Equals("PPtr<MonoScript>") && property.propertyType == SerializedPropertyType.ObjectReference && property.propertyPath.Equals("m_Script");
|
||
if (isdefaultScriptProperty)
|
||
return false;
|
||
if (property.name.ToLower().StartsWith("_optional"))
|
||
return false;
|
||
if (property.isArray && property.propertyType != SerializedPropertyType.String)
|
||
{
|
||
for (int i = 0; i < property.arraySize; i++)
|
||
{
|
||
if (property.GetArrayElementAtIndex(i).propertyType == SerializedPropertyType.ObjectReference
|
||
&& property.GetArrayElementAtIndex(i).objectReferenceValue == null)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
else
|
||
{
|
||
if (property.propertyType == SerializedPropertyType.ObjectReference && property.objectReferenceValue == null)
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
#endregion MissingProperties
|
||
#region CheckUnappliedSettings
|
||
/// <summary>
|
||
/// Returns true if there are unapplied settings
|
||
/// </summary>
|
||
private bool CheckUnappliedSettings(AdminPanelSettings settings)
|
||
{
|
||
//New since 06.05.2023
|
||
if (_toggleState_DESYNC_BANNED_PLAYERS != settings.DESYNC_BANNED_PLAYERS) return true;
|
||
if (_toggleState_STEALTH_PANEL != settings.STEALTH_PANEL) return true;
|
||
if (_toggleState_NO_BAN_EFFECTS != settings.NO_BAN_EFFECTS) return true;
|
||
if (_toggleState_DEBUG_ADMIN_NAME_DETECTION != settings.DEBUG_ADMIN_NAME_DETECTION) return true;
|
||
if (_toggleState_REMOTE_STRING_LOADING != settings.REMOTE_STRING_LOADING) return true;
|
||
if (_toggleState_REMOTE_BAN_LIST != settings.REMOTE_BAN_LIST) return true;
|
||
if (_toggleState_REMOTE_ADMIN_LIST != settings.REMOTE_ADMIN_LIST) return true;
|
||
if (_toggleState_REMOTE_MODERATOR_LIST != settings.REMOTE_MODERATOR_LIST) return true;
|
||
if (_toggleState_HASH_USERNAMES != settings.HASH_USERNAMES) return true;
|
||
if (_editorState_REMOTE_LOADING_URL != settings.REMOTE_LOADING_URL) return true;
|
||
if (_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS != settings.REMOTE_LOADING_REPEAT_EACH_SECONDS) return true;
|
||
//#NEW_PROPERTY#
|
||
//-------------------------
|
||
|
||
// if (_toggleState_#1# != settings.#1#) return true;
|
||
if (_toggleState_VRC_GUIDE_COMPLICANCE != settings.VRC_GUIDE_COMPLICANCE) return true;
|
||
if (_toggleState_VRC_GUIDE_DEBUG != settings.VRC_GUIDE_DEBUG) return true;
|
||
if (_toggleState_PASSWORD_AUTHENTICATION != settings.PASSWORD_AUTHENTICATION) return true;
|
||
if (_toggleState_MINIMAL_BAN_DEBUG != settings.MINIMAL_BAN_DEBUG) return true;
|
||
if (_toggleState_ANTI_NAME_SPOOF != settings.ANTI_NAME_SPOOF) return true;
|
||
if (_toggleState_MARK_BANNED_PLAYERS != settings.MARK_BANNED_PLAYERS) return true;
|
||
if (_toggleState_MUTE_BANNED_PLAYERS != settings.MUTE_BANNED_PLAYERS) return true;
|
||
if (_toggleState_BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS != settings.BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS) return true;
|
||
if (_toggleState_ANTI_PICKUP_SUMMON_MOD != settings.ANTI_PICKUP_SUMMON_MOD) return true;
|
||
if (_toggleState_ANTI_NO_TELEPORT_MOD != settings.ANTI_NO_TELEPORT_MOD) return true;
|
||
if (_toggleState_USE_HONEYPOTS != settings.USE_HONEYPOTS) return true;
|
||
if (_toggleState_PERMA_BAN_LIST != settings.PERMA_BAN_LIST) return true;
|
||
if (_toggleState_SURPRESS_WHITESPACE_CHARS != settings.SURPRESS_WHITESPACE_CHARS) return true;
|
||
if (_toggleState_ADMIN_ONLY_OBJECTS != settings.ADMIN_ONLY_OBJECTS) return true;
|
||
if (_toggleState_MODERATOR_AND_ADMIN_ONLY_OBJECTS != settings.MODERATOR_AND_ADMIN_ONLY_OBJECTS) return true;
|
||
if (_toggleState_DESTROY_FOR_OTHER_PLAYERS != settings.DESTROY_FOR_OTHER_PLAYERS) return true;
|
||
if (_toggleState_USE_CUSTOM_ALT_ACCOUNT_DETECTION != settings.USE_CUSTOM_ALT_ACCOUNT_DETECTION) return true;
|
||
if (_toggleState_ADMIN_CAN_FLY != settings.ADMIN_CAN_FLY) return true;
|
||
if (_toggleState_MODERATOR_CAN_FLY != settings.MODERATOR_CAN_FLY) return true;
|
||
if (_toggleState_EVERYONE_CAN_FLY != settings.EVERYONE_CAN_FLY) return true;
|
||
if (_toggleState_MODERATOR_CAN_BAN != settings.MODERATOR_CAN_BAN) return true;
|
||
if (_toggleState_CAN_BAN_ADMINS != settings.CAN_BAN_ADMINS) return true;
|
||
if (_toggleState_SUMMON_PANEL_FUNCTION != settings.SUMMON_PANEL_FUNCTION) return true;
|
||
if (_toggleState_ACCURATE_CAPSULE_POSITIONS != settings.ACCURATE_CAPSULE_POSITIONS) return true;
|
||
|
||
if (_toggleState_UNITY_SHOW_PASSWORD != settings.UNITY_SHOW_PASSWORD) return true;
|
||
if (_toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS != settings.UNITY_STORE_CLEARTEXT_PASSWORD) return true;
|
||
if (_toggleState_UNITY_SHOW_HASH_AND_SALT != settings.UNITY_SHOW_HASH_AND_SALT) return true;
|
||
|
||
if (ListHasChanged(_loadedAdminList, _displayedAdminList)) return true;
|
||
if (ListHasChanged(_loadedModeratorList, _displayedModeratorList)) return true;
|
||
if (ListHasChanged(_loadedPermaBanList, _displayedPermaBanList)) return true;
|
||
|
||
if (ListHasChanged(_loadedPasswordList, _displayedPasswordList)) return true;
|
||
if (ListHasChanged(_loadedHashList, _displayedHashList)) return true;
|
||
if (ListHasChanged(_loadedSaltList, _displayedSaltList)) return true;
|
||
|
||
return false;
|
||
}
|
||
#endregion CheckUnappliedSettings
|
||
#region ApplySettings
|
||
/// <summary>
|
||
/// Writes the loaded settings to the inspector window fields
|
||
/// </summary>
|
||
private void ApplySettingsToGUI(AdminPanelSettings settings)
|
||
{
|
||
//apply the settings to the editor state
|
||
_toggleState_VRC_GUIDE_COMPLICANCE = _loadedSettings.VRC_GUIDE_COMPLICANCE;
|
||
_toggleState_VRC_GUIDE_DEBUG = _loadedSettings.VRC_GUIDE_DEBUG;
|
||
_toggleState_PASSWORD_AUTHENTICATION = _loadedSettings.PASSWORD_AUTHENTICATION;
|
||
_toggleState_MINIMAL_BAN_DEBUG = _loadedSettings.MINIMAL_BAN_DEBUG;
|
||
_toggleState_ANTI_NAME_SPOOF = _loadedSettings.ANTI_NAME_SPOOF;
|
||
_toggleState_MARK_BANNED_PLAYERS = _loadedSettings.MARK_BANNED_PLAYERS;
|
||
_toggleState_MUTE_BANNED_PLAYERS = _loadedSettings.MUTE_BANNED_PLAYERS;
|
||
_toggleState_BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS = _loadedSettings.BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS;
|
||
_toggleState_ANTI_PICKUP_SUMMON_MOD = _loadedSettings.ANTI_PICKUP_SUMMON_MOD;
|
||
_toggleState_ANTI_NO_TELEPORT_MOD = _loadedSettings.ANTI_NO_TELEPORT_MOD;
|
||
_toggleState_USE_HONEYPOTS = _loadedSettings.USE_HONEYPOTS;
|
||
_toggleState_PERMA_BAN_LIST = _loadedSettings.PERMA_BAN_LIST;
|
||
_toggleState_SURPRESS_WHITESPACE_CHARS = _loadedSettings.SURPRESS_WHITESPACE_CHARS;
|
||
_toggleState_ADMIN_ONLY_OBJECTS = _loadedSettings.ADMIN_ONLY_OBJECTS;
|
||
_toggleState_MODERATOR_AND_ADMIN_ONLY_OBJECTS = _loadedSettings.MODERATOR_AND_ADMIN_ONLY_OBJECTS;
|
||
_toggleState_DESTROY_FOR_OTHER_PLAYERS = _loadedSettings.DESTROY_FOR_OTHER_PLAYERS;
|
||
_toggleState_USE_CUSTOM_ALT_ACCOUNT_DETECTION = _loadedSettings.USE_CUSTOM_ALT_ACCOUNT_DETECTION;
|
||
_toggleState_ADMIN_CAN_FLY = _loadedSettings.ADMIN_CAN_FLY;
|
||
_toggleState_MODERATOR_CAN_FLY = _loadedSettings.MODERATOR_CAN_FLY;
|
||
_toggleState_EVERYONE_CAN_FLY = _loadedSettings.EVERYONE_CAN_FLY;
|
||
_toggleState_MODERATOR_CAN_BAN = _loadedSettings.MODERATOR_CAN_BAN;
|
||
_toggleState_CAN_BAN_ADMINS = _loadedSettings.CAN_BAN_ADMINS;
|
||
_toggleState_SUMMON_PANEL_FUNCTION = _loadedSettings.SUMMON_PANEL_FUNCTION;
|
||
_toggleState_ACCURATE_CAPSULE_POSITIONS = _loadedSettings.ACCURATE_CAPSULE_POSITIONS;
|
||
_toggleState_UNITY_SHOW_HASH_AND_SALT = _loadedSettings.UNITY_SHOW_HASH_AND_SALT;
|
||
_toggleState_UNITY_SHOW_PASSWORD = _loadedSettings.UNITY_SHOW_PASSWORD;
|
||
_toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS = _loadedSettings.UNITY_STORE_CLEARTEXT_PASSWORD;
|
||
//New since 06.05.2023
|
||
_toggleState_DESYNC_BANNED_PLAYERS = _loadedSettings.DESYNC_BANNED_PLAYERS;
|
||
_toggleState_STEALTH_PANEL = _loadedSettings.STEALTH_PANEL;
|
||
_toggleState_NO_BAN_EFFECTS = _loadedSettings.NO_BAN_EFFECTS;
|
||
_toggleState_DEBUG_ADMIN_NAME_DETECTION = _loadedSettings.DEBUG_ADMIN_NAME_DETECTION;
|
||
_toggleState_REMOTE_STRING_LOADING = _loadedSettings.REMOTE_STRING_LOADING;
|
||
_toggleState_REMOTE_BAN_LIST = _loadedSettings.REMOTE_BAN_LIST;
|
||
_toggleState_REMOTE_ADMIN_LIST = _loadedSettings.REMOTE_ADMIN_LIST;
|
||
_toggleState_REMOTE_MODERATOR_LIST = _loadedSettings.REMOTE_MODERATOR_LIST;
|
||
_toggleState_HASH_USERNAMES = _loadedSettings.HASH_USERNAMES;
|
||
_editorState_REMOTE_LOADING_URL = _loadedSettings.REMOTE_LOADING_URL;
|
||
_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS = _loadedSettings.REMOTE_LOADING_REPEAT_EACH_SECONDS;
|
||
if (_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS == 0) //0 isn't a valid value, this comes from migration. We set it to the default value instead.
|
||
_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS = 5 * 60;
|
||
//#NEW_PROPERTY#
|
||
//-------------------------
|
||
|
||
}
|
||
/// <summary>
|
||
/// Draws a button to allow applying the changes to the script
|
||
/// </summary>
|
||
private void ShowSettingsApplyButton(string label1, string label2, string buttonText)
|
||
{
|
||
EditorGUILayout.Space();
|
||
GUI.color = Color.red;
|
||
if (label1 != string.Empty)
|
||
ReimajoEditorBase.DrawLabelField(Color.white, label1);
|
||
if (label2 != string.Empty)
|
||
ReimajoEditorBase.DrawLabelField(Color.white, label2);
|
||
EditorGUILayout.Space();
|
||
GUILayout.BeginHorizontal(GUIStyle.none, GUILayout.Height(25));
|
||
if (GUILayout.Button(buttonText, GUILayout.Height(25f)))
|
||
{
|
||
Debug.Log("[AdminPanelEditor] User clicked \"Apply settings\"");
|
||
ApplySettings();
|
||
}
|
||
GUI.color = Color.white;
|
||
GUILayout.EndHorizontal();
|
||
EditorGUILayout.Space();
|
||
ReimajoEditorBase.DrawUILine(Color.gray);
|
||
EditorGUILayout.Space();
|
||
}
|
||
#endregion ApplySettings
|
||
#region SaveSettings
|
||
/// <summary>
|
||
/// Writes all changes done in the inspector back to the script file and updates the settings file
|
||
/// </summary>
|
||
private void ApplySettings()
|
||
{
|
||
_currentEditFailed = false;
|
||
string[] newScript = TextToLineArray(ReadTextFile(SCRIPT_FILE_PATH));
|
||
|
||
//Check if the whitespace char surpression option changed
|
||
if (_toggleState_SURPRESS_WHITESPACE_CHARS != _loadedSettings.SURPRESS_WHITESPACE_CHARS)
|
||
{
|
||
_scriptIsOutOfDate = true; //force regeneration of user name lists because this option changes how they are generated
|
||
}
|
||
|
||
#region ApplyToggleChanges
|
||
/* BLUEPRINT:
|
||
if (_toggleState_#1# != settings.#1#)
|
||
{
|
||
settings.#1# = _toggleState_#1#;
|
||
ChangeCompilerOptionStateInScript("#1#", _toggleState_#1#);
|
||
}
|
||
*/
|
||
|
||
//New since 06.05.2023
|
||
if (_scriptIsOutOfDate || _toggleState_DESYNC_BANNED_PLAYERS != _loadedSettings.DESYNC_BANNED_PLAYERS)
|
||
{
|
||
_loadedSettings.DESYNC_BANNED_PLAYERS = _toggleState_DESYNC_BANNED_PLAYERS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "DESYNC_BANNED_PLAYERS", _toggleState_DESYNC_BANNED_PLAYERS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_STEALTH_PANEL != _loadedSettings.STEALTH_PANEL)
|
||
{
|
||
_loadedSettings.STEALTH_PANEL = _toggleState_STEALTH_PANEL;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "STEALTH_PANEL", _toggleState_STEALTH_PANEL);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_NO_BAN_EFFECTS != _loadedSettings.NO_BAN_EFFECTS)
|
||
{
|
||
_loadedSettings.NO_BAN_EFFECTS = _toggleState_NO_BAN_EFFECTS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "NO_BAN_EFFECTS", _toggleState_NO_BAN_EFFECTS);
|
||
}
|
||
//-------------------------
|
||
if (_scriptIsOutOfDate || _toggleState_VRC_GUIDE_COMPLICANCE != _loadedSettings.VRC_GUIDE_COMPLICANCE)
|
||
{
|
||
_loadedSettings.VRC_GUIDE_COMPLICANCE = _toggleState_VRC_GUIDE_COMPLICANCE;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "VRC_GUIDE_COMPLICANCE", _toggleState_VRC_GUIDE_COMPLICANCE);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_VRC_GUIDE_DEBUG != _loadedSettings.VRC_GUIDE_DEBUG)
|
||
{
|
||
_loadedSettings.VRC_GUIDE_DEBUG = _toggleState_VRC_GUIDE_DEBUG;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "VRC_GUIDE_DEBUG", _toggleState_VRC_GUIDE_DEBUG);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_PASSWORD_AUTHENTICATION != _loadedSettings.PASSWORD_AUTHENTICATION)
|
||
{
|
||
_loadedSettings.PASSWORD_AUTHENTICATION = _toggleState_PASSWORD_AUTHENTICATION;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "PASSWORD_AUTHENTICATION", _toggleState_PASSWORD_AUTHENTICATION);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_MINIMAL_BAN_DEBUG != _loadedSettings.MINIMAL_BAN_DEBUG)
|
||
{
|
||
_loadedSettings.MINIMAL_BAN_DEBUG = _toggleState_MINIMAL_BAN_DEBUG;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "MINIMAL_BAN_DEBUG", _toggleState_MINIMAL_BAN_DEBUG);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_ANTI_NAME_SPOOF != _loadedSettings.ANTI_NAME_SPOOF)
|
||
{
|
||
_loadedSettings.ANTI_NAME_SPOOF = _toggleState_ANTI_NAME_SPOOF;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "ANTI_NAME_SPOOF", _toggleState_ANTI_NAME_SPOOF);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_MARK_BANNED_PLAYERS != _loadedSettings.MARK_BANNED_PLAYERS)
|
||
{
|
||
_loadedSettings.MARK_BANNED_PLAYERS = _toggleState_MARK_BANNED_PLAYERS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "MARK_BANNED_PLAYERS", _toggleState_MARK_BANNED_PLAYERS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_MUTE_BANNED_PLAYERS != _loadedSettings.MUTE_BANNED_PLAYERS)
|
||
{
|
||
_loadedSettings.MUTE_BANNED_PLAYERS = _toggleState_MUTE_BANNED_PLAYERS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "MUTE_BANNED_PLAYERS", _toggleState_MUTE_BANNED_PLAYERS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS != _loadedSettings.BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS)
|
||
{
|
||
_loadedSettings.BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS = _toggleState_BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS", _toggleState_BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_ANTI_PICKUP_SUMMON_MOD != _loadedSettings.ANTI_PICKUP_SUMMON_MOD)
|
||
{
|
||
_loadedSettings.ANTI_PICKUP_SUMMON_MOD = _toggleState_ANTI_PICKUP_SUMMON_MOD;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "ANTI_PICKUP_SUMMON_MOD", _toggleState_ANTI_PICKUP_SUMMON_MOD);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_ANTI_NO_TELEPORT_MOD != _loadedSettings.ANTI_NO_TELEPORT_MOD)
|
||
{
|
||
_loadedSettings.ANTI_NO_TELEPORT_MOD = _toggleState_ANTI_NO_TELEPORT_MOD;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "ANTI_NO_TELEPORT_MOD", _toggleState_ANTI_NO_TELEPORT_MOD);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_USE_HONEYPOTS != _loadedSettings.USE_HONEYPOTS)
|
||
{
|
||
_loadedSettings.USE_HONEYPOTS = _toggleState_USE_HONEYPOTS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "USE_HONEYPOTS", _toggleState_USE_HONEYPOTS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_PERMA_BAN_LIST != _loadedSettings.PERMA_BAN_LIST)
|
||
{
|
||
_loadedSettings.PERMA_BAN_LIST = _toggleState_PERMA_BAN_LIST;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "PERMA_BAN_LIST", _toggleState_PERMA_BAN_LIST);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_SURPRESS_WHITESPACE_CHARS != _loadedSettings.SURPRESS_WHITESPACE_CHARS)
|
||
{
|
||
_loadedSettings.SURPRESS_WHITESPACE_CHARS = _toggleState_SURPRESS_WHITESPACE_CHARS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "SURPRESS_WHITESPACE_CHARS", _toggleState_SURPRESS_WHITESPACE_CHARS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_ADMIN_ONLY_OBJECTS != _loadedSettings.ADMIN_ONLY_OBJECTS)
|
||
{
|
||
_loadedSettings.ADMIN_ONLY_OBJECTS = _toggleState_ADMIN_ONLY_OBJECTS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "ADMIN_ONLY_OBJECTS", _toggleState_ADMIN_ONLY_OBJECTS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_MODERATOR_AND_ADMIN_ONLY_OBJECTS != _loadedSettings.MODERATOR_AND_ADMIN_ONLY_OBJECTS)
|
||
{
|
||
_loadedSettings.MODERATOR_AND_ADMIN_ONLY_OBJECTS = _toggleState_MODERATOR_AND_ADMIN_ONLY_OBJECTS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "MODERATOR_AND_ADMIN_ONLY_OBJECTS", _toggleState_MODERATOR_AND_ADMIN_ONLY_OBJECTS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_DESTROY_FOR_OTHER_PLAYERS != _loadedSettings.DESTROY_FOR_OTHER_PLAYERS)
|
||
{
|
||
_loadedSettings.DESTROY_FOR_OTHER_PLAYERS = _toggleState_DESTROY_FOR_OTHER_PLAYERS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "DESTROY_FOR_OTHER_PLAYERS", _toggleState_DESTROY_FOR_OTHER_PLAYERS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_USE_CUSTOM_ALT_ACCOUNT_DETECTION != _loadedSettings.USE_CUSTOM_ALT_ACCOUNT_DETECTION)
|
||
{
|
||
_loadedSettings.USE_CUSTOM_ALT_ACCOUNT_DETECTION = _toggleState_USE_CUSTOM_ALT_ACCOUNT_DETECTION;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "USE_CUSTOM_ALT_ACCOUNT_DETECTION", _toggleState_USE_CUSTOM_ALT_ACCOUNT_DETECTION);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_ADMIN_CAN_FLY != _loadedSettings.ADMIN_CAN_FLY)
|
||
{
|
||
_loadedSettings.ADMIN_CAN_FLY = _toggleState_ADMIN_CAN_FLY;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "ADMIN_CAN_FLY", _toggleState_ADMIN_CAN_FLY);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_MODERATOR_CAN_FLY != _loadedSettings.MODERATOR_CAN_FLY)
|
||
{
|
||
_loadedSettings.MODERATOR_CAN_FLY = _toggleState_MODERATOR_CAN_FLY;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "MODERATOR_CAN_FLY", _toggleState_MODERATOR_CAN_FLY);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_EVERYONE_CAN_FLY != _loadedSettings.EVERYONE_CAN_FLY)
|
||
{
|
||
_loadedSettings.EVERYONE_CAN_FLY = _toggleState_EVERYONE_CAN_FLY;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "EVERYONE_CAN_FLY", _toggleState_EVERYONE_CAN_FLY);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_MODERATOR_CAN_BAN != _loadedSettings.MODERATOR_CAN_BAN)
|
||
{
|
||
_loadedSettings.MODERATOR_CAN_BAN = _toggleState_MODERATOR_CAN_BAN;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "MODERATOR_CAN_BAN", _toggleState_MODERATOR_CAN_BAN);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_CAN_BAN_ADMINS != _loadedSettings.CAN_BAN_ADMINS)
|
||
{
|
||
_loadedSettings.CAN_BAN_ADMINS = _toggleState_CAN_BAN_ADMINS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "CAN_BAN_ADMINS", _toggleState_CAN_BAN_ADMINS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_SUMMON_PANEL_FUNCTION != _loadedSettings.SUMMON_PANEL_FUNCTION)
|
||
{
|
||
_loadedSettings.SUMMON_PANEL_FUNCTION = _toggleState_SUMMON_PANEL_FUNCTION;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "SUMMON_PANEL_FUNCTION", _toggleState_SUMMON_PANEL_FUNCTION);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_ACCURATE_CAPSULE_POSITIONS != _loadedSettings.ACCURATE_CAPSULE_POSITIONS)
|
||
{
|
||
_loadedSettings.ACCURATE_CAPSULE_POSITIONS = _toggleState_ACCURATE_CAPSULE_POSITIONS;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "ACCURATE_CAPSULE_POSITIONS", _toggleState_ACCURATE_CAPSULE_POSITIONS);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_DEBUG_ADMIN_NAME_DETECTION != _loadedSettings.DEBUG_ADMIN_NAME_DETECTION)
|
||
{
|
||
_loadedSettings.DEBUG_ADMIN_NAME_DETECTION = _toggleState_DEBUG_ADMIN_NAME_DETECTION;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "DEBUG_ADMIN_NAME_DETECTION", _toggleState_DEBUG_ADMIN_NAME_DETECTION);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_REMOTE_STRING_LOADING != _loadedSettings.REMOTE_STRING_LOADING)
|
||
{
|
||
_loadedSettings.REMOTE_STRING_LOADING = _toggleState_REMOTE_STRING_LOADING;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "REMOTE_STRING_LOADING", _toggleState_REMOTE_STRING_LOADING);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_REMOTE_BAN_LIST != _loadedSettings.REMOTE_BAN_LIST)
|
||
{
|
||
_loadedSettings.REMOTE_BAN_LIST = _toggleState_REMOTE_BAN_LIST;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "REMOTE_BAN_LIST", _toggleState_REMOTE_BAN_LIST);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_REMOTE_ADMIN_LIST != _loadedSettings.REMOTE_ADMIN_LIST)
|
||
{
|
||
_loadedSettings.REMOTE_ADMIN_LIST = _toggleState_REMOTE_ADMIN_LIST;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "REMOTE_ADMIN_LIST", _toggleState_REMOTE_ADMIN_LIST);
|
||
}
|
||
if (_scriptIsOutOfDate || _toggleState_REMOTE_MODERATOR_LIST != _loadedSettings.REMOTE_MODERATOR_LIST)
|
||
{
|
||
_loadedSettings.REMOTE_MODERATOR_LIST = _toggleState_REMOTE_MODERATOR_LIST;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "REMOTE_MODERATOR_LIST", _toggleState_REMOTE_MODERATOR_LIST);
|
||
}
|
||
|
||
if (_scriptIsOutOfDate || _toggleState_HASH_USERNAMES != _loadedSettings.HASH_USERNAMES)
|
||
{
|
||
_loadedSettings.HASH_USERNAMES = _toggleState_HASH_USERNAMES;
|
||
newScript = ChangeCompilerOptionStateInScript(newScript, "HASH_USERNAMES", _toggleState_HASH_USERNAMES);
|
||
}
|
||
if (_scriptIsOutOfDate || _editorState_REMOTE_LOADING_URL != _loadedSettings.REMOTE_LOADING_URL)
|
||
{
|
||
_loadedSettings.REMOTE_LOADING_URL = _editorState_REMOTE_LOADING_URL;
|
||
newScript = ChangeVariableDeclarationInScript(newScript, "private const string URL_REMOTE_LOADING =", $"\t\tprivate const string URL_REMOTE_LOADING = \"{_editorState_REMOTE_LOADING_URL}\";");
|
||
}
|
||
if (_scriptIsOutOfDate || _editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS != _loadedSettings.REMOTE_LOADING_REPEAT_EACH_SECONDS)
|
||
{
|
||
if (_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS < 10)
|
||
_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS = 10;
|
||
_loadedSettings.REMOTE_LOADING_REPEAT_EACH_SECONDS = _editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS;
|
||
newScript = ChangeVariableDeclarationInScript(newScript, "private const int REMOTE_LOADING_REPEAT_DELAY_ON_SUCCESS =", $"\t\tprivate const int REMOTE_LOADING_REPEAT_DELAY_ON_SUCCESS = {_editorState_REMOTE_LOADING_REPEAT_EACH_SECONDS} * 1000;");
|
||
}
|
||
//#NEW_PROPERTY#
|
||
#endregion ApplyToggleChanges
|
||
|
||
#region RemoveEmptyNames
|
||
RemoveEmptyPermUserEntry(roleName: "Admin", ref _displayedAdminList, isModeratorList: false);
|
||
RemoveEmptyPermUserEntry(roleName: "Moderator", ref _displayedModeratorList, isModeratorList: true);
|
||
RemoveLeftoverAuthInfo();
|
||
#endregion RemoveEmptyNames
|
||
|
||
#region ApplyListChanges
|
||
if (_scriptIsOutOfDate || ListHasChanged(_loadedAdminList, _displayedAdminList))
|
||
{
|
||
_displayedAdminList = TrimValues(_displayedAdminList);
|
||
Debug.Log($"[AdminPanelEditor] Writing new Admin list with {_displayedAdminList.Count} admins to disk.");
|
||
//write the new admin list file
|
||
WriteTextFile(ADMIN_FILE_PATH, string.Join(Environment.NewLine, _displayedAdminList));
|
||
_loadedAdminList = new List<string>(_displayedAdminList);
|
||
newScript = ChangeNameListInScript(newScript, "_superSpecialSnowflakes", _loadedAdminList);
|
||
}
|
||
if (_scriptIsOutOfDate || ListHasChanged(_loadedModeratorList, _displayedModeratorList))
|
||
{
|
||
_displayedModeratorList = TrimValues(_displayedModeratorList);
|
||
//write the new moderator list file
|
||
Debug.Log($"[AdminPanelEditor] Writing new Moderator list with {_displayedModeratorList.Count} moderators to disk.");
|
||
WriteTextFile(MODERATOR_FILE_PATH, string.Join(Environment.NewLine, _displayedModeratorList));
|
||
_loadedModeratorList = new List<string>(_displayedModeratorList);
|
||
newScript = ChangeNameListInScript(newScript, "_specialSnowflakes", _loadedModeratorList);
|
||
}
|
||
if (_scriptIsOutOfDate || ListHasChanged(_loadedPermaBanList, _displayedModeratorList))
|
||
{
|
||
_displayedPermaBanList = RemoveEmptyLinesAndTrimValues(_displayedPermaBanList, _toggleState_SURPRESS_WHITESPACE_CHARS);
|
||
//write the new permaban player list file
|
||
Debug.Log($"[AdminPanelEditor] Writing new permaban list with {_displayedPermaBanList.Count} players to disk.");
|
||
WriteTextFile(PERMA_BAN_FILE_PATH, string.Join(Environment.NewLine, _displayedPermaBanList));
|
||
_loadedPermaBanList = new List<string>(_displayedPermaBanList);
|
||
newScript = ChangeNameListInScript(newScript, "_permaBannedPlayers", _loadedPermaBanList);
|
||
}
|
||
|
||
//remove auth entries that have no user
|
||
RemoveLeftoverAuthInfo();
|
||
|
||
//first we remove potential user errors
|
||
_displayedPasswordList = TrimValues(_displayedPasswordList);
|
||
_displayedHashList = TrimValues(_displayedHashList);
|
||
_displayedSaltList = TrimValues(_displayedSaltList);
|
||
//if any of the credential lists changes we do a full re-write
|
||
if (_scriptIsOutOfDate
|
||
|| ListHasChanged(_loadedPasswordList, _displayedPasswordList)
|
||
|| ListHasChanged(_loadedHashList, _displayedHashList)
|
||
|| ListHasChanged(_loadedSaltList, _displayedSaltList))
|
||
{
|
||
//calculate hashes etc.
|
||
for (int i = 0; i < _displayedPasswordList.Count; i++)
|
||
{
|
||
bool pwIsSecret = _displayedPasswordList[i] == PASSWORD_UNKNOWN;
|
||
bool pw_set = _displayedPasswordList[i] != string.Empty;
|
||
bool hash_set = _displayedHashList[i] != string.Empty;
|
||
bool salt_set = _displayedSaltList[i] != string.Empty;
|
||
|
||
//remove invalid entry where the hash is missing
|
||
if (_displayedPasswordList[i] == PASSWORD_UNKNOWN && !hash_set)
|
||
{
|
||
Debug.Log($"[AdminPanelEditor] Hash was missing for entry {i} with unknown password, removed it.");
|
||
_displayedPasswordList[i] = string.Empty;
|
||
pw_set = false;
|
||
_displayedSaltList[i] = GetRandomReadableSalt();
|
||
hash_set = true;
|
||
}
|
||
//if no password is set but hash was set, we assume the user wants no cleatext pw to be stored
|
||
if (!pw_set && hash_set)
|
||
{
|
||
Debug.Log($"[AdminPanelEditor] Password not set for entry {i} so we set it to unknown, since a hash is supplied instead.");
|
||
_displayedPasswordList[i] = PASSWORD_UNKNOWN;
|
||
}
|
||
//else if password & hash is set, but no salt, we add a salt and recalculate the hash
|
||
else if (pw_set && hash_set && !salt_set)
|
||
{
|
||
if (!pwIsSecret)
|
||
{
|
||
Debug.Log($"[AdminPanelEditor] Salt not set for entry {i} so we set it to a random one and recalculate the hash.");
|
||
_displayedSaltList[i] = GetRandomReadableSalt();
|
||
_displayedHashList[i] = GetHash(_displayedPasswordList[i], _displayedSaltList[i]);
|
||
}
|
||
}
|
||
//else if password is set, we recalculate hash (and set the salt if needed)
|
||
else if (pw_set)
|
||
{
|
||
if (!pwIsSecret)
|
||
{
|
||
if (!salt_set)
|
||
{
|
||
_displayedSaltList[i] = GetRandomReadableSalt();
|
||
}
|
||
_displayedHashList[i] = GetHash(_displayedPasswordList[i], _displayedSaltList[i]);
|
||
}
|
||
}
|
||
|
||
//now purge the password if wanted
|
||
if (pw_set && !_toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS)
|
||
{
|
||
_displayedPasswordList[i] = PASSWORD_UNKNOWN;
|
||
}
|
||
}
|
||
|
||
//write the new password list file
|
||
Debug.Log($"[AdminPanelEditor] Writing new password list with {_displayedPasswordList.Count} entries to disk.");
|
||
WriteTextFile(PASSWORD_FILE_PATH, string.Join(Environment.NewLine, ConvertToStorageValues(_displayedPasswordList)));
|
||
Debug.Log($"[AdminPanelEditor] Writing new hash list with {_displayedHashList.Count} entries to disk.");
|
||
WriteTextFile(HASH_FILE_PATH, string.Join(Environment.NewLine, ConvertToStorageValues(_displayedHashList)));
|
||
Debug.Log($"[AdminPanelEditor] Writing new salt list with {_displayedSaltList.Count} entries to disk.");
|
||
WriteTextFile(SALT_FILE_PATH, string.Join(Environment.NewLine, ConvertToStorageValues(_displayedSaltList)));
|
||
|
||
//remember when we did the last file changes
|
||
_loadedCache.DateOfLastPasswordFileChange = File.GetLastWriteTimeUtc(PASSWORD_FILE_PATH).Ticks;
|
||
_loadedCache.DateOfLastHashFileChange = File.GetLastWriteTimeUtc(HASH_FILE_PATH).Ticks;
|
||
_loadedCache.DateOfLastSaltFileChange = File.GetLastWriteTimeUtc(SALT_FILE_PATH).Ticks;
|
||
|
||
_loadedPasswordList = new List<string>(_displayedPasswordList);
|
||
_loadedHashList = new List<string>(_displayedHashList);
|
||
_loadedSaltList = new List<string>(_displayedSaltList);
|
||
|
||
//create a list of all users that have credentials
|
||
List<string> userList = new List<string>(_displayedAdminList); userList.AddRange(_displayedModeratorList);
|
||
newScript = ChangeCredentialsObjectInScript(newScript, "_credentials", userList, _displayedSaltList, _displayedHashList);
|
||
}
|
||
|
||
#endregion ApplyListChanges
|
||
|
||
#region ApplyEditorSettingsChange
|
||
if (_toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS != _loadedSettings.UNITY_STORE_CLEARTEXT_PASSWORD)
|
||
{
|
||
_loadedSettings.UNITY_STORE_CLEARTEXT_PASSWORD = _toggleState_UNITY_STORE_CLEARTEXT_PASSWORDS;
|
||
}
|
||
if (_toggleState_UNITY_SHOW_PASSWORD != _loadedSettings.UNITY_SHOW_PASSWORD)
|
||
{
|
||
_loadedSettings.UNITY_SHOW_PASSWORD = _toggleState_UNITY_SHOW_PASSWORD;
|
||
}
|
||
if (_toggleState_UNITY_SHOW_HASH_AND_SALT != _loadedSettings.UNITY_SHOW_HASH_AND_SALT)
|
||
{
|
||
_loadedSettings.UNITY_SHOW_HASH_AND_SALT = _toggleState_UNITY_SHOW_HASH_AND_SALT;
|
||
}
|
||
#endregion ApplyEditorSettingsChange
|
||
|
||
//write the new script file
|
||
WriteTextFile(SCRIPT_FILE_PATH, string.Join(Environment.NewLine, newScript));
|
||
|
||
//remember when we did the last file changes
|
||
_loadedCache.DateOfLastScriptChange = File.GetLastWriteTimeUtc(SCRIPT_FILE_PATH).Ticks;
|
||
_loadedCache.DateOfLastAdminFileChange = File.GetLastWriteTimeUtc(ADMIN_FILE_PATH).Ticks;
|
||
_loadedCache.DateOfLastModeratorFileChange = File.GetLastWriteTimeUtc(MODERATOR_FILE_PATH).Ticks;
|
||
_loadedCache.DateOfLastPermaBanFileChange = File.GetLastWriteTimeUtc(PERMA_BAN_FILE_PATH).Ticks;
|
||
_scriptIsOutOfDate = false;
|
||
|
||
//write the new settings to the same settings file
|
||
WriteTextFile(SETTINGS_FILE_PATH, JsonUtility.ToJson(_loadedSettings, true));
|
||
|
||
//read the time of the settings file change
|
||
_loadedCache.DateOfLastSettingsFileChange = File.GetLastWriteTimeUtc(SETTINGS_FILE_PATH).Ticks;
|
||
|
||
//write the new cache values to the same cache file
|
||
WriteTextFile(CACHE_FILE_PATH, JsonUtility.ToJson(_loadedCache, true));
|
||
|
||
AssetDatabase.Refresh();
|
||
|
||
_lastEditFailed = _currentEditFailed;
|
||
}
|
||
/// <summary>
|
||
/// Removes an empty username entry in the moderator or admin user list (and their auth info)
|
||
/// </summary>
|
||
private static void RemoveEmptyPermUserEntry(string roleName, ref List<string> displayedList, bool isModeratorList = false)
|
||
{
|
||
//since both admin and mod has auth info, but both is stored in the same auth file, they contain first the admins and then the moderators info
|
||
int offsetInAuthLists = isModeratorList ? _displayedAdminList.Count : 0;
|
||
for (int i = 0; i < displayedList.Count; i++)
|
||
{
|
||
if (displayedList[i].Trim() == String.Empty)
|
||
{
|
||
displayedList.RemoveAt(i);
|
||
Debug.Log($"[AdminPanelEditor] Removed empty {roleName} entry at pos {i} / auth pos {i + offsetInAuthLists}.");
|
||
_displayedPasswordList.RemoveAt(i + offsetInAuthLists);
|
||
_displayedHashList.RemoveAt(i + offsetInAuthLists);
|
||
_displayedSaltList.RemoveAt(i + offsetInAuthLists);
|
||
}
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Removes auth info that has no user assigned
|
||
/// </summary>
|
||
private static void RemoveLeftoverAuthInfo()
|
||
{
|
||
int totalUserCount = _displayedAdminList.Count + _displayedModeratorList.Count;
|
||
if (_displayedPasswordList.Count > totalUserCount)
|
||
{
|
||
_displayedPasswordList.RemoveRange(totalUserCount, _displayedPasswordList.Count - totalUserCount);
|
||
}
|
||
if (_displayedHashList.Count > totalUserCount)
|
||
{
|
||
_displayedHashList.RemoveRange(totalUserCount, _displayedHashList.Count - totalUserCount);
|
||
}
|
||
if (_displayedSaltList.Count > totalUserCount)
|
||
{
|
||
_displayedSaltList.RemoveRange(totalUserCount, _displayedSaltList.Count - totalUserCount);
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Changes the values of a string array inside the script
|
||
/// </summary>
|
||
private string[] ChangeNameListInScript(string[] newScript, string fieldName, List<string> newListContent)
|
||
{
|
||
for (int i = 0; i < newScript.Length; i++)
|
||
{
|
||
if (newScript[i].Trim().StartsWith("private string[] " + fieldName))
|
||
{
|
||
if (newScript[i + 1].Trim().StartsWith("{") && newScript[i + 1].Trim().EndsWith("};"))
|
||
{
|
||
newScript[i + 1] = CreateStringArrayAsText(newListContent);
|
||
return newScript;
|
||
}
|
||
}
|
||
}
|
||
_currentEditFailed = true;
|
||
return newScript;
|
||
}
|
||
/// <summary>
|
||
/// Changes the values of a string array inside the script
|
||
/// </summary>
|
||
private string[] ChangeCredentialsObjectInScript(string[] newScript, string fieldName, List<string> usernames, List<string> salts, List<string> hashes)
|
||
{
|
||
for (int i = 0; i < newScript.Length; i++)
|
||
{
|
||
if (newScript[i].Trim().StartsWith("private object[] " + fieldName))
|
||
{
|
||
if (newScript[i + 1].Trim().StartsWith("{") && newScript[i + 1].Trim().EndsWith("};"))
|
||
{
|
||
newScript[i + 1] = CreateObjectArrayAsText(usernames, salts, hashes);
|
||
return newScript;
|
||
}
|
||
}
|
||
}
|
||
_currentEditFailed = true;
|
||
return newScript;
|
||
}
|
||
/// <summary>
|
||
/// Creates a string object array literal from a list of strings, e.g.
|
||
///
|
||
/// new object[]
|
||
/// {
|
||
/// new string[] { @"c1", @"c2", @"c3" },
|
||
/// new string[] { @"c1_1", @"c2_1", @"c3_1" },
|
||
/// new string[] { @"c1_2", @"c2_2", @"c3_2" }
|
||
/// };
|
||
///
|
||
/// to be inserted into a source code file (but formated as one line)
|
||
/// </summary>
|
||
private static string CreateObjectArrayAsText(List<string> usernames, List<string> salts, List<string> hashes)
|
||
{
|
||
string output = "\t\t{ ";
|
||
int addedCount = 0;
|
||
for (int i = 0; i < usernames.Count; i++)
|
||
{
|
||
if (hashes[i] != String.Empty)
|
||
{
|
||
output += "new string[] { @\"" + EscapeQuotationCharacters(usernames[i]) + "\", @\"" + EscapeQuotationCharacters(salts[i]) + "\", @\"" + EscapeQuotationCharacters(hashes[i]) + "\" }, ";
|
||
addedCount++;
|
||
}
|
||
}
|
||
if (addedCount > 0)
|
||
{
|
||
output = output.Substring(0, output.Length - 2);
|
||
}
|
||
return output + @" };";
|
||
}
|
||
/// <summary>
|
||
/// Adjusts the compiler option state to the state in the user settings
|
||
/// and returns the changed script array
|
||
/// </summary>
|
||
private string[] ChangeCompilerOptionStateInScript(string[] newScript, string settingsName, bool newState)
|
||
{
|
||
for (int i = 0; i < newScript.Length; i++)
|
||
{
|
||
if (newScript[i].StartsWith("#define " + settingsName) || newScript[i].StartsWith("//#define " + settingsName))
|
||
{
|
||
//already enabled and should be enabled
|
||
if (newScript[i].StartsWith("#define " + settingsName) && newState)
|
||
return newScript;
|
||
//already disabled and should be disabled
|
||
if (newScript[i].StartsWith("//#define " + settingsName) && !newState)
|
||
return newScript;
|
||
//already enabled but should be disabled
|
||
if (newScript[i].StartsWith("#define " + settingsName) && !newState)
|
||
{
|
||
newScript[i] = @"//" + newScript[i];
|
||
return newScript;
|
||
}
|
||
//already disabled but should be enabled
|
||
if (newScript[i].StartsWith("//#define " + settingsName) && newState)
|
||
{
|
||
newScript[i] = newScript[i].Substring(2);
|
||
return newScript;
|
||
}
|
||
}
|
||
}
|
||
//if the compiler setting couldn't be found
|
||
_currentEditFailed = true;
|
||
return newScript;
|
||
}
|
||
/// <summary>
|
||
/// Adjusts the variable value to the state in the user settings
|
||
/// and returns the changed script array
|
||
/// </summary>
|
||
private string[] ChangeVariableDeclarationInScript(string[] newScript, string settingsDeclarationStartsWith, string newSettingsDeclaration)
|
||
{
|
||
for (int i = 0; i < newScript.Length; i++)
|
||
{
|
||
if (newScript[i].Trim().StartsWith(settingsDeclarationStartsWith))
|
||
{
|
||
//replace the whole line with the new declaration
|
||
newScript[i] = newSettingsDeclaration;
|
||
return newScript;
|
||
}
|
||
}
|
||
//if the compiler setting couldn't be found
|
||
_currentEditFailed = true;
|
||
return newScript;
|
||
}
|
||
#endregion SaveSettings
|
||
#region HelperFunctions
|
||
/// <summary>
|
||
/// Essentially Convert.ToHexString() from .NET5
|
||
/// </summary>
|
||
public static string ByteArrayToString(byte[] input)
|
||
{
|
||
StringBuilder hexResult = new StringBuilder(input.Length * 2);
|
||
foreach (byte b in input)
|
||
hexResult.AppendFormat("{0:x2}", b);
|
||
return hexResult.ToString();
|
||
}
|
||
/// <summary>
|
||
/// Creates a SHA512 hash with password+salt as an input, using UTF8 as a format
|
||
/// </summary>
|
||
private string GetHash(string password, string salt)
|
||
{
|
||
byte[] inputBytes = System.Text.Encoding.UTF8.GetBytes(password + salt);
|
||
using (SHA512 sha512Managed = new SHA512Managed())
|
||
{
|
||
return ByteArrayToString(sha512Managed.ComputeHash(inputBytes));
|
||
}
|
||
}
|
||
/// <summary>
|
||
/// Lenght of a randomly generated salt
|
||
/// </summary>
|
||
private static int RANDOM_SALT_LENGHT = 32;
|
||
private const string READABLE_CHARS = @"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||
/// <summary>
|
||
/// Returns a random salt of the max lenght <see cref="MAX_SALT_LENGHT"/>
|
||
/// with readable characters from <see cref="READABLE_CHARS"/> only
|
||
/// </summary>
|
||
public static string GetRandomReadableSalt()
|
||
{
|
||
RNGCryptoServiceProvider cryptoServiceProvider = new RNGCryptoServiceProvider();
|
||
byte[] randomBytes = new byte[RANDOM_SALT_LENGHT];
|
||
cryptoServiceProvider.GetNonZeroBytes(randomBytes);
|
||
char[] bufferCharArray = new char[RANDOM_SALT_LENGHT];
|
||
char[] usableCharArray = READABLE_CHARS.ToCharArray();
|
||
for (int index = 0; index < RANDOM_SALT_LENGHT; index++)
|
||
{
|
||
bufferCharArray[index] = usableCharArray[randomBytes[index] % usableCharArray.Length];
|
||
}
|
||
return new string(bufferCharArray);
|
||
}
|
||
/// <summary>
|
||
/// Returns true if the two lists are not equal
|
||
/// </summary>
|
||
private static bool ListHasChanged(List<string> loadedAdminList, List<string> displayedAdminList)
|
||
{
|
||
return !loadedAdminList.SequenceEqual(displayedAdminList);
|
||
}
|
||
/// <summary>
|
||
/// Reads a textfile as Unicode
|
||
/// </summary>
|
||
private static string ReadTextFile(string path)
|
||
{
|
||
return File.ReadAllText(path, System.Text.Encoding.UTF8);
|
||
}
|
||
/// <summary>
|
||
/// Writes a textfile as Unicode
|
||
/// </summary>
|
||
private static void WriteTextFile(string path, string content)
|
||
{
|
||
File.WriteAllText(path, content, System.Text.Encoding.UTF8);
|
||
}
|
||
/// <summary>
|
||
/// Formats the text from a label like Unity does in the inspector, so THIS_IS_A_LABEL becomes "This Is A Label"
|
||
/// </summary>
|
||
private static string FormatLabel(string label)
|
||
{
|
||
string newLabel = String.Empty;
|
||
bool isCaps = true;
|
||
foreach (char c in label)
|
||
{
|
||
if (c == ' ')
|
||
{
|
||
isCaps = true;
|
||
newLabel += ' ';
|
||
}
|
||
else if (c == '_')
|
||
{
|
||
isCaps = true;
|
||
newLabel += ' ';
|
||
}
|
||
else
|
||
{
|
||
if (isCaps)
|
||
{
|
||
newLabel += c.ToString().ToUpper();
|
||
isCaps = false;
|
||
}
|
||
else
|
||
{
|
||
newLabel += c.ToString().ToLower();
|
||
}
|
||
}
|
||
}
|
||
return newLabel;
|
||
}
|
||
/// <summary>
|
||
/// Converts a text to an array, split by newline breaks
|
||
/// </summary>
|
||
private static string[] TextToLineArray(string input)
|
||
{
|
||
return input.Split(
|
||
new string[] { "\r\n", "\r", "\n" },
|
||
StringSplitOptions.None
|
||
);
|
||
}
|
||
/// <summary>
|
||
/// Trims all lines and then also removes empty lines from an array
|
||
/// </summary>
|
||
private static List<string> RemoveEmptyLinesAndTrimValues(string[] input)
|
||
{
|
||
List<string> result = new List<string>();
|
||
foreach (string line in input)
|
||
{
|
||
if (!string.IsNullOrEmpty(line.Trim()))
|
||
result.Add(line.Trim());
|
||
}
|
||
return result;
|
||
}
|
||
/// <summary>
|
||
/// Trims all lines and then also removes empty lines from a List
|
||
/// </summary>
|
||
private static List<string> RemoveEmptyLinesAndTrimValues(List<string> input, bool cleanNames = false)
|
||
{
|
||
List<string> result = new List<string>();
|
||
foreach (string line in input)
|
||
{
|
||
if (!string.IsNullOrEmpty(line.Trim()))
|
||
{
|
||
if (cleanNames)
|
||
result.Add(CleanNameFromWhitespaceCharacters(line.Trim()));
|
||
else
|
||
result.Add(line.Trim());
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
/// <summary>
|
||
/// Trims all lines and then also removes empty lines from a List
|
||
/// </summary>
|
||
private static List<string> TrimValues(List<string> input, bool cleanNames = false)
|
||
{
|
||
List<string> result = new List<string>();
|
||
foreach (string line in input)
|
||
{
|
||
if (cleanNames)
|
||
result.Add(CleanNameFromWhitespaceCharacters(line.Trim()));
|
||
else
|
||
result.Add(line.Trim());
|
||
}
|
||
return result;
|
||
}
|
||
/// <summary>
|
||
/// Creates a string array literal from a list of strings, e.g. { @"line1", @"line2", @"line3" };
|
||
/// to be inserted into a source code file
|
||
/// </summary>
|
||
private static string CreateStringArrayAsText(List<string> list)
|
||
{
|
||
string output = "\t\t{ ";
|
||
|
||
foreach (string s in list)
|
||
{
|
||
output += "@\"" + EscapeQuotationCharacters(s) + "\", ";
|
||
}
|
||
if (list.Count > 0)
|
||
{
|
||
output = output.Substring(0, output.Length - 2);
|
||
}
|
||
return output + @" };";
|
||
}
|
||
/// <summary>
|
||
/// Replaces " chars with "" so they are escaped in a literal @ string added to a source code file
|
||
/// </summary>
|
||
private static string EscapeQuotationCharacters(string input)
|
||
{
|
||
return input.Replace("\"", "\"\"");
|
||
}
|
||
/// <summary>
|
||
/// Removes known alternative whitespace characters except the original space character
|
||
/// </summary>
|
||
private static string CleanNameFromWhitespaceCharacters(string playerName)
|
||
{
|
||
playerName = playerName.Trim(); //Note: I assume VRChat does that already
|
||
int lenght = playerName.Length;
|
||
char[] nameAsCharArray = playerName.ToCharArray();
|
||
int newMaxIndex = 0;
|
||
for (var i = 0; i < lenght; i++)
|
||
{
|
||
char c = nameAsCharArray[i];
|
||
//this list represents all unicode whitespace characters that we want to remove
|
||
switch (c)
|
||
{
|
||
//case '\u0020': //regular space character; let's allow that one
|
||
case '\u00A0':
|
||
case '\u1680':
|
||
case '\u2000':
|
||
case '\u2001':
|
||
case '\u2002':
|
||
case '\u2003':
|
||
case '\u2004':
|
||
case '\u2005':
|
||
case '\u2006':
|
||
case '\u2007':
|
||
case '\u2008':
|
||
case '\u2009':
|
||
case '\u200A':
|
||
case '\u202F':
|
||
case '\u205F':
|
||
case '\u3000':
|
||
case '\u2028': //line separator
|
||
case '\u2029': //paragram seperator
|
||
case '\u0009':
|
||
case '\u000A':
|
||
case '\u000B':
|
||
case '\u000C': //form feed
|
||
case '\u000D':
|
||
case '\u0085': //next line
|
||
//technically not part of the whitespace chars; but similar
|
||
case '\uFEFF':
|
||
case '\u180E':
|
||
case '\u200B':
|
||
break;
|
||
default:
|
||
nameAsCharArray[newMaxIndex++] = c;
|
||
break;
|
||
}
|
||
}
|
||
return new string(nameAsCharArray, 0, newMaxIndex); ;
|
||
}
|
||
#endregion HelperFunctions
|
||
#endregion BaseEditor
|
||
}
|
||
#region JSON
|
||
/// <summary>
|
||
/// Format of the JSON file where the cache info is stored
|
||
/// </summary>
|
||
[System.Serializable]
|
||
public class AdminPanelCache
|
||
{
|
||
public string SETTINGS_PATH_OVERRIDE;
|
||
public long DateOfLastSettingsFileChange;
|
||
public long DateOfLastScriptChange;
|
||
public long DateOfLastAdminFileChange;
|
||
public long DateOfLastModeratorFileChange;
|
||
public long DateOfLastPermaBanFileChange;
|
||
public long DateOfLastPasswordFileChange;
|
||
public long DateOfLastHashFileChange;
|
||
public long DateOfLastSaltFileChange;
|
||
}
|
||
/// <summary>
|
||
/// Format of the JSON file where the user settings are stored
|
||
/// </summary>
|
||
[System.Serializable]
|
||
public class AdminPanelSettings
|
||
{
|
||
//New since 06.05.2023
|
||
public bool DESYNC_BANNED_PLAYERS;
|
||
public bool STEALTH_PANEL;
|
||
public bool NO_BAN_EFFECTS;
|
||
public bool DEBUG_ADMIN_NAME_DETECTION;
|
||
public bool REMOTE_STRING_LOADING;
|
||
public bool REMOTE_BAN_LIST;
|
||
public bool REMOTE_ADMIN_LIST;
|
||
public bool REMOTE_MODERATOR_LIST;
|
||
public bool HASH_USERNAMES;
|
||
public string REMOTE_LOADING_URL;
|
||
public int REMOTE_LOADING_REPEAT_EACH_SECONDS;
|
||
//#NEW_PROPERTY#
|
||
//-------------------------
|
||
public bool VRC_GUIDE_COMPLICANCE;
|
||
public bool VRC_GUIDE_DEBUG;
|
||
public bool PASSWORD_AUTHENTICATION;
|
||
public bool MINIMAL_BAN_DEBUG;
|
||
public bool ANTI_NAME_SPOOF;
|
||
public bool MARK_BANNED_PLAYERS;
|
||
public bool MUTE_BANNED_PLAYERS;
|
||
public bool BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS;
|
||
public bool ANTI_PICKUP_SUMMON_MOD;
|
||
public bool ANTI_NO_TELEPORT_MOD;
|
||
public bool USE_HONEYPOTS;
|
||
public bool PERMA_BAN_LIST;
|
||
public bool SURPRESS_WHITESPACE_CHARS;
|
||
public bool ADMIN_ONLY_OBJECTS;
|
||
public bool MODERATOR_AND_ADMIN_ONLY_OBJECTS;
|
||
public bool DESTROY_FOR_OTHER_PLAYERS;
|
||
public bool USE_CUSTOM_ALT_ACCOUNT_DETECTION;
|
||
public bool ADMIN_CAN_FLY;
|
||
public bool MODERATOR_CAN_FLY;
|
||
public bool EVERYONE_CAN_FLY;
|
||
public bool MODERATOR_CAN_BAN;
|
||
public bool CAN_BAN_ADMINS;
|
||
public bool SUMMON_PANEL_FUNCTION;
|
||
public bool ACCURATE_CAPSULE_POSITIONS;
|
||
public bool UNITY_SHOW_PASSWORD;
|
||
public bool UNITY_STORE_CLEARTEXT_PASSWORD;
|
||
public bool UNITY_SHOW_HASH_AND_SALT;
|
||
}
|
||
#endregion JSON
|
||
}
|
||
#region DevNotes
|
||
/*
|
||
(Just a template for my code generator, don't mind this)
|
||
VRC_GUIDE_COMPLICANCE,VRC_GUIDE_DEBUG,PASSWORD_AUTHENTICATION,MINIMAL_BAN_DEBUG,ANTI_NAME_SPOOF,MARK_CRASHERS,MUTE_BANNED_PLAYERS,BANNED_PLAYERS_CAN_TALK_WITH_MODERATORS,ANTI_PICKUP_SUMMON_MOD,ANTI_NO_TELEPORT_MOD,USE_HONEYPOTS,PERMA_BAN_LIST,SURPRESS_WHITESPACE_CHARS,ADMIN_ONLY_OBJECTS,MODERATOR_AND_ADMIN_ONLY_OBJECTS,DESTROY_FOR_OTHER_PLAYERS,USE_CUSTOM_ALT_ACCOUNT_DETECTION,ADMIN_CAN_FLY,EVERYONE_CAN_FLY,CAN_BAN_ADMINS,SUMMON_PANEL_FUNCTION,ACCURATE_CAPSULE_POSITIONS
|
||
*/
|
||
#endregion DevNotes |