ArabDesert/Assets/ReimajoBoothAssets/AdminTool/Scripts/Editor/AdminPanelEditor.cs

2158 lines
114 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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