using System; using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Threading.Tasks; using NitroxModel.Helper; using NitroxModel.Serialization; using NitroxModel.Server; namespace NitroxModel; public static class Extensions { public static string GetSavesFolderDir(this IKeyValueStore store) { if (store == null) { return Path.Combine(NitroxUser.AppDataPath, "saves"); } return store.GetValue("SavesFolderDir", Path.Combine(NitroxUser.AppDataPath, "saves")); } public static TAttribute GetAttribute(this Enum value) where TAttribute : Attribute { Type type = value.GetType(); string name = Enum.GetName(type, value); return type.GetField(name) .GetCustomAttributes(false) .OfType() .SingleOrDefault(); } /// /// Gets only the unique flags of the given enum value that aren't part of a different flag in the same enum type, /// excluding the 0 flag. /// public static IEnumerable GetUniqueNonCombinatoryFlags(this T flags) where T : Enum { ulong flagCursor = 1; foreach (T value in Enum.GetValues(typeof(T))) { if (!flags.HasFlag(value)) { continue; } ulong definedFlagBits = Convert.ToUInt64(value); while (flagCursor < definedFlagBits) { flagCursor <<= 1; } if (flagCursor == definedFlagBits && value.HasFlag(value)) { yield return value; } } } /// public static bool IsDefined(this TEnum value) where TEnum : Enum => Enum.IsDefined(typeof(TEnum), value); /// /// Removes all items from the list where the predicate returns true. /// /// The list to remove items from. /// An extra parameter to supply to the predicate. /// The predicate that tests each item in the list for removal. public static void RemoveAllFast(this IList list, TParameter extraParameter, Func predicate) { for (int i = list.Count - 1; i >= 0; i--) { TItem item = list[i]; if (predicate.Invoke(item, extraParameter)) { // Optimization for Unity mono: swap item to end and remove it. This reduces GC pressure for resizing arrays. list[i] = list[^1]; list.RemoveAt(list.Count - 1); } } } public static int GetIndex(this T[] list, T itemToFind) => Array.IndexOf(list, itemToFind); public static string AsByteUnitText(this uint byteSize) { // Uint can't go past 4GiB, so we don't need to worry about overflow. string[] suf = { "B", "KiB", "MiB", "GiB" }; if (byteSize == 0) { return $"0{suf[0]}"; } int place = Convert.ToInt32(Math.Floor(Math.Log(byteSize, 1024))); double num = Math.Round(byteSize / Math.Pow(1024, place), 1); return num + suf[place]; } /// /// Calls an action if an error happens. /// /// /// Use this for fire-and-forget tasks so that errors aren't hidden when they happen. /// public static Task ContinueWithHandleError(this Task task, Action onError) => task.ContinueWith(t => { if (t is not { IsFaulted: true, Exception: { } ex }) { return; } onError(ex); }); /// /// Logs any exception/error of the task. /// /// /// /// public static Task ContinueWithHandleError(this Task task, bool alsoLogIngame = false) => task.ContinueWithHandleError(ex => { Log.Error(ex); if (alsoLogIngame) { Log.InGame(ex.Message); } }); public static string GetFirstNonAggregateMessage(this Exception exception) => exception switch { AggregateException ex => ex.InnerExceptions.FirstOrDefault(e => e is not AggregateException)?.Message ?? ex.Message, _ => exception.Message }; /// ///
/// if both IEnumerables are null. ///
/// /// can't be thrown because of or /// being null. /// /// public static bool SequenceEqualOrBothNull(this IEnumerable first, IEnumerable second) { if (first != null && second != null) { return first.SequenceEqual(second); } return first == second; } public static void RemoveWhere(this IDictionary dictionary, TParameter extraParameter, Func predicate) { int toRemoveIndex = 0; TKey[] toRemove = ArrayPool.Shared.Rent(dictionary.Count); try { foreach (KeyValuePair item in dictionary) { if (predicate.Invoke(item.Value, extraParameter)) { toRemove[toRemoveIndex++] = item.Key; } } for (int i = 0; i < toRemoveIndex; i++) { dictionary.Remove(toRemove[i]); } } finally { ArrayPool.Shared.Return(toRemove, true); } } public static byte[] AsMd5Hash(this string input) { using MD5 md5 = MD5.Create(); byte[] inputBytes = Encoding.ASCII.GetBytes(input); return md5.ComputeHash(inputBytes); } /// /// Reads the exact amount of bytes from the stream. /// public static byte[] ReadStreamExactly(this Stream stream, byte[] buffer, int count) { int start; int num; for (start = 0; start < count; start += num) { num = stream.Read(buffer, start, count); if (num == 0) { throw new EndOfStreamException(); } } return buffer; } /// /// Gets the arguments passed to a command, given its name. /// /// All arguments passed to the program. /// Name of the command, include the - or -- prefix. /// All arguments passed to the given command name or empty if not found or no arguments passed. public static IEnumerable GetCommandArgs(this string[] args, string name) { for (int i = 0; i < args.Length; i++) { string arg = args[i]; if (!arg.StartsWith(name)) { continue; } if (arg.Length > name.Length && arg[name.Length] == '=') { yield return arg.Substring(name.Length + 1); continue; } for (i += 1; i < args.Length; i++) { arg = args[i]; if (arg.StartsWith("-")) { break; } yield return arg; } } } public static bool IsHardcore(this SubnauticaServerConfig config) => config.GameMode == NitroxGameMode.HARDCORE; public static bool IsPasswordRequired(this SubnauticaServerConfig config) => config.ServerPassword != ""; }