first commit
This commit is contained in:
30
NitroxModel/Helper/CultureManager.cs
Normal file
30
NitroxModel/Helper/CultureManager.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Globalization;
|
||||
using System.Threading;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
public static class CultureManager
|
||||
{
|
||||
public static readonly CultureInfo CultureInfo = new("en-US");
|
||||
|
||||
/// <summary>
|
||||
/// Internal Subnautica files are setup using US english number formats and dates. To ensure
|
||||
/// that we parse all of these appropriately, we will set the default cultureInfo to en-US.
|
||||
/// This must best done for any thread that is spun up and needs to read from files (unless
|
||||
/// we were to migrate to 4.5.) Failure to set the context can result in very strange behaviour
|
||||
/// throughout the entire application. This originally manifested itself as a duplicate spawning
|
||||
/// issue for players in Europe. This was due to incorrect parsing of probability tables.
|
||||
/// </summary>
|
||||
public static void ConfigureCultureInfo()
|
||||
{
|
||||
// Although we loaded the en-US cultureInfo, let's make sure to set these in case the
|
||||
// default was overriden by the user.
|
||||
CultureInfo.NumberFormat.NumberDecimalSeparator = ".";
|
||||
CultureInfo.NumberFormat.NumberGroupSeparator = ",";
|
||||
|
||||
Thread.CurrentThread.CurrentCulture = CultureInfo;
|
||||
Thread.CurrentThread.CurrentUICulture = CultureInfo;
|
||||
CultureInfo.DefaultThreadCurrentCulture = CultureInfo;
|
||||
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo;
|
||||
}
|
||||
}
|
34
NitroxModel/Helper/IKeyValueStore.cs
Normal file
34
NitroxModel/Helper/IKeyValueStore.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
public interface IKeyValueStore
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value for a key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to get value of.</param>
|
||||
/// <param name="defaultValue">Default value to return if key does not exist or type conversion failed.</param>
|
||||
/// <returns>The value or null if key was not found or conversion failed.</returns>
|
||||
T GetValue<T>(string key, T defaultValue = default);
|
||||
|
||||
/// <summary>
|
||||
/// Sets a value for a key.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to set value of.</param>
|
||||
/// <param name="value">Value to set for the key.</param>
|
||||
/// <returns>True if the value was found.</returns>
|
||||
bool SetValue<T>(string key, T value);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a key along with its value.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to delete.</param>
|
||||
/// <returns>True if the key was deleted.</returns>
|
||||
bool DeleteKey(string key);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a key exists.
|
||||
/// </summary>
|
||||
/// <param name="key">Key to check.</param>
|
||||
/// <returns>True if the key exists.</returns>
|
||||
bool KeyExists(string key);
|
||||
}
|
20
NitroxModel/Helper/IMap.cs
Normal file
20
NitroxModel/Helper/IMap.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using NitroxModel.DataStructures;
|
||||
using NitroxModel.DataStructures.GameLogic;
|
||||
|
||||
namespace NitroxModel.Helper
|
||||
{
|
||||
public interface IMap
|
||||
{
|
||||
public int BatchSize { get; }
|
||||
/// <summary>
|
||||
/// AKA LargeWorldStreamer.blocksPerBatch
|
||||
/// </summary>
|
||||
public NitroxInt3 BatchDimensions { get; }
|
||||
public NitroxInt3 DimensionsInMeters { get; }
|
||||
public NitroxInt3 DimensionsInBatches { get; }
|
||||
public NitroxInt3 BatchDimensionCenter { get; }
|
||||
public List<NitroxTechType> GlobalRootTechTypes { get; }
|
||||
public int ItemLevelOfDetail { get; }
|
||||
}
|
||||
}
|
39
NitroxModel/Helper/KeyValueStore.cs
Normal file
39
NitroxModel/Helper/KeyValueStore.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using NitroxModel.Platforms.OS.Shared;
|
||||
using NitroxModel.Platforms.OS.Windows;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
/// <summary>
|
||||
/// Simple Key-Value store that works cross-platform. <br />
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// On <b>Windows</b>:<br />
|
||||
/// Backend is <see cref="RegistryKeyValueStore" />, which uses the registry.
|
||||
/// If you want to view/edit the KeyStore, open regedit and navigate to HKEY_CURRENT_USER\SOFTWARE\Nitrox\(keyname)
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// On <b>Linux</b>:<br />
|
||||
/// Backend is <see cref="ConfigFileKeyValueStore" />, which uses a file.
|
||||
/// If you want to view/edit the KeyStore, open $HOME/.config/Nitrox/nitrox.cfg in your favourite text editor.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static class KeyValueStore
|
||||
{
|
||||
public static IKeyValueStore Instance { get; } = GetKeyValueStoreForPlatform();
|
||||
|
||||
private static IKeyValueStore GetKeyValueStoreForPlatform()
|
||||
{
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
// Use registry on Windows
|
||||
return new RegistryKeyValueStore();
|
||||
}
|
||||
|
||||
// if platform isn't Windows, it doesn't have a registry
|
||||
// use a config file for storage this should work on most platforms
|
||||
return new ConfigFileKeyValueStore();
|
||||
}
|
||||
}
|
31
NitroxModel/Helper/LinqExtensions.cs
Normal file
31
NitroxModel/Helper/LinqExtensions.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
public static class LinqExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the items until the predicate stops matching, then includes the next non-matching result as well.
|
||||
/// </summary>
|
||||
/// <param name="source">Input enumerable.</param>
|
||||
/// <param name="predicate">Predicate to match against the items in the enumerable.</param>
|
||||
/// <typeparam name="T">Type of items to return.</typeparam>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<T> TakeUntilInclusive<T>(this IEnumerable<T> source, Func<T, bool> predicate)
|
||||
{
|
||||
foreach (T item in source)
|
||||
{
|
||||
if (predicate(item))
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Also self
|
||||
yield return item;
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
82
NitroxModel/Helper/Mathf.cs
Normal file
82
NitroxModel/Helper/Mathf.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
|
||||
namespace NitroxModel.Helper
|
||||
{
|
||||
public class Mathf
|
||||
{
|
||||
public const float RAD2DEG = 57.29578f;
|
||||
public const float PI = 3.14159274f;
|
||||
public const float DEG2RAD = 0.0174532924f;
|
||||
|
||||
public static float Sqrt(float ls)
|
||||
{
|
||||
return (float)Math.Sqrt(ls);
|
||||
}
|
||||
|
||||
public static float Atan2(float p1, float p2)
|
||||
{
|
||||
return (float)Math.Atan2(p1, p2);
|
||||
}
|
||||
|
||||
public static float Asin(float p)
|
||||
{
|
||||
return (float)Math.Asin(p);
|
||||
}
|
||||
|
||||
public static float Pow(float p1, float p2)
|
||||
{
|
||||
return (float)Math.Pow(p1, p2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clamps the given value between 0 and 1.
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
public static float Clamp01(float value)
|
||||
{
|
||||
// Not using Clamp as an optimization.
|
||||
if (value < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (value > 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public static T Clamp<T>(T val, T min, T max) where T : IComparable<T>
|
||||
{
|
||||
if (val.CompareTo(min) < 0)
|
||||
{
|
||||
return min;
|
||||
}
|
||||
if (val.CompareTo(max) > 0)
|
||||
{
|
||||
return max;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="digits" /> is less than 0 or greater than 15.</exception>
|
||||
public static float Round(float value, int digits = 0)
|
||||
{
|
||||
return (float)Math.Round(value, digits);
|
||||
}
|
||||
|
||||
public static float Lerp(float a, float b, float t)
|
||||
{
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reciprocal function of <see cref="Lerp"/>. Unlerp(a, b, Lerp(a, b, t)) = t
|
||||
/// </summary>
|
||||
public static float Unlerp(float a, float b, float lerpedResult)
|
||||
{
|
||||
return (lerpedResult - a) / (b - a);
|
||||
}
|
||||
}
|
||||
}
|
40
NitroxModel/Helper/Matrix4x4Extension.cs
Normal file
40
NitroxModel/Helper/Matrix4x4Extension.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using NitroxModel.DataStructures.Unity;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static class Matrix4x4Extension
|
||||
{
|
||||
public static Matrix4x4 Compose(NitroxVector3 localPosition, NitroxQuaternion localRotation, NitroxVector3 localScale)
|
||||
{
|
||||
Matrix4x4 translationMatrix = Matrix4x4.CreateTranslation(localPosition.X, localPosition.Y, localPosition.Z);
|
||||
Matrix4x4 rotationMatrix = Matrix4x4.CreateFromQuaternion((Quaternion)localRotation);
|
||||
Matrix4x4 scaleMatrix = Matrix4x4.CreateScale(localScale.X, localScale.Y, localScale.Z);
|
||||
return scaleMatrix * rotationMatrix * translationMatrix;
|
||||
}
|
||||
|
||||
public static Matrix4x4 Invert(this Matrix4x4 m)
|
||||
{
|
||||
Matrix4x4.Invert(m, out Matrix4x4 result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static NitroxVector3 Transform(this Matrix4x4 m, NitroxVector3 v)
|
||||
{
|
||||
float x = v.X * m.M11 + v.Y * m.M21 + v.Z * m.M31 + m.M41;
|
||||
float y = v.X * m.M12 + v.Y * m.M22 + v.Z * m.M32 + m.M42;
|
||||
float z = v.X * m.M13 + v.Y * m.M23 + v.Z * m.M33 + m.M43;
|
||||
float w = v.X * m.M14 + v.Y * m.M24 + v.Z * m.M34 + m.M44;
|
||||
|
||||
if (w == 0)
|
||||
{
|
||||
throw new ArithmeticException($"Tried to divide by zero with Matrix {m} and vector {v}");
|
||||
}
|
||||
|
||||
w = 1f / w;
|
||||
|
||||
return new NitroxVector3(x * w, y * w, z * w);
|
||||
}
|
||||
}
|
216
NitroxModel/Helper/NatHelper.cs
Normal file
216
NitroxModel/Helper/NatHelper.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Mono.Nat;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
public static class NatHelper
|
||||
{
|
||||
public static async Task<IPAddress> GetExternalIpAsync() => await MonoNatHelper.GetFirstAsync(static async device =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await device.GetExternalIPAsync().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
public static async Task<bool> DeletePortMappingAsync(ushort port, Protocol protocol, CancellationToken ct = default)
|
||||
{
|
||||
int tries = 3;
|
||||
while (tries-- >= 0)
|
||||
{
|
||||
if (await TryRemoveAsync(port, protocol, ct))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
await Task.Delay(250, ct);
|
||||
}
|
||||
return false;
|
||||
|
||||
static async Task<bool> TryRemoveAsync(ushort port, Protocol protocol, CancellationToken ct)
|
||||
{
|
||||
return await MonoNatHelper.GetFirstAsync(static async (device, mapping) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await device.DeletePortMapAsync(mapping).ConfigureAwait(false) != null;
|
||||
}
|
||||
catch (MappingException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}, new Mapping(protocol, port, port), ct).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Mapping> GetPortMappingAsync(ushort port, Protocol protocol, CancellationToken ct = default)
|
||||
{
|
||||
return await MonoNatHelper.GetFirstAsync(static async (device, protocolAndPort) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await device.GetSpecificMappingAsync(protocolAndPort.protocol, protocolAndPort.port).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}, (port, protocol), ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public static async Task<ResultCodes> AddPortMappingAsync(ushort port, Protocol protocol, CancellationToken ct = default)
|
||||
{
|
||||
Mapping mapping = new(protocol, port, port);
|
||||
return await MonoNatHelper.GetFirstAsync(static async (device, mapping) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return await device.CreatePortMapAsync(mapping).ConfigureAwait(false) != null ? ResultCodes.SUCCESS : ResultCodes.UNKNOWN_ERROR;
|
||||
}
|
||||
catch (MappingException ex)
|
||||
{
|
||||
return ExceptionToCode(ex);
|
||||
}
|
||||
}, mapping, ct).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public enum ResultCodes
|
||||
{
|
||||
SUCCESS,
|
||||
CONFLICT_IN_MAPPING_ENTRY,
|
||||
UNKNOWN_ERROR
|
||||
}
|
||||
|
||||
private static ResultCodes ExceptionToCode(MappingException exception) => exception.ErrorCode switch
|
||||
{
|
||||
ErrorCode.ConflictInMappingEntry => ResultCodes.CONFLICT_IN_MAPPING_ENTRY,
|
||||
_ => ResultCodes.UNKNOWN_ERROR
|
||||
};
|
||||
|
||||
private static class MonoNatHelper
|
||||
{
|
||||
private static readonly ConcurrentDictionary<EndPoint, INatDevice> discoveredDevices = new();
|
||||
private static readonly object discoverTaskLocker = new();
|
||||
private static Task<IEnumerable<INatDevice>> discoverTaskCache;
|
||||
|
||||
public static Task<IEnumerable<INatDevice>> DiscoverAsync()
|
||||
{
|
||||
// Singleton discovery task. Same task is reused to cache the result of the discovery broadcast.
|
||||
lock (discoverTaskLocker)
|
||||
{
|
||||
if (discoverTaskCache != null)
|
||||
{
|
||||
return discoverTaskCache;
|
||||
}
|
||||
|
||||
return discoverTaskCache = DiscoveryUncachedAsync(60000, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
private static DateTime lastFoundDeviceTime;
|
||||
private static readonly object lastFoundDeviceTimeLock = new();
|
||||
private static async Task<IEnumerable<INatDevice>> DiscoveryUncachedAsync(int timeoutInMs, int timeoutNoMoreDevicesMs)
|
||||
{
|
||||
void Handler(object sender, DeviceEventArgs args)
|
||||
{
|
||||
lock (lastFoundDeviceTimeLock)
|
||||
{
|
||||
lastFoundDeviceTime = DateTime.UtcNow;
|
||||
}
|
||||
discoveredDevices.TryAdd(args.Device.DeviceEndpoint, args.Device);
|
||||
}
|
||||
|
||||
NatUtility.DeviceFound += Handler;
|
||||
NatUtility.StartDiscovery();
|
||||
try
|
||||
{
|
||||
CancellationTokenSource cancellation = new(timeoutInMs);
|
||||
|
||||
lock (lastFoundDeviceTimeLock)
|
||||
{
|
||||
lastFoundDeviceTime = DateTime.UtcNow;
|
||||
}
|
||||
bool hasFoundDeviceRecently = true;
|
||||
while (!cancellation.IsCancellationRequested && hasFoundDeviceRecently)
|
||||
{
|
||||
lock (lastFoundDeviceTimeLock)
|
||||
{
|
||||
hasFoundDeviceRecently = (DateTime.UtcNow - lastFoundDeviceTime).TotalMilliseconds <= timeoutNoMoreDevicesMs;
|
||||
}
|
||||
|
||||
await Task.Delay(10, cancellation.Token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
NatUtility.StopDiscovery();
|
||||
NatUtility.DeviceFound -= Handler;
|
||||
}
|
||||
|
||||
return discoveredDevices.Values;
|
||||
}
|
||||
|
||||
public static async Task<TResult> GetFirstAsync<TResult>(Func<INatDevice, Task<TResult>> predicate) => await GetFirstAsync(static (device, p) => p(device), predicate);
|
||||
|
||||
public static async Task<TResult> GetFirstAsync<TResult, TExtraParam>(Func<INatDevice, TExtraParam, Task<TResult>> predicate, TExtraParam parameter, CancellationToken ct = default)
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// Start NAT discovery (if it hasn't started yet).
|
||||
Task<IEnumerable<INatDevice>> discoverTask = DiscoverAsync();
|
||||
if (discoverTask.IsCompleted && discoveredDevices.IsEmpty)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// Progressively handle devices until first not-null/false result or when discovery times out.
|
||||
ConcurrentDictionary<EndPoint, INatDevice> handledDevices = new();
|
||||
do
|
||||
{
|
||||
IEnumerable<KeyValuePair<EndPoint, INatDevice>> unhandledDevices = discoveredDevices.Except(handledDevices).ToArray();
|
||||
if (!unhandledDevices.Any())
|
||||
{
|
||||
try
|
||||
{
|
||||
await Task.Delay(10, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<EndPoint, INatDevice> pair in unhandledDevices)
|
||||
{
|
||||
if (ct.IsCancellationRequested)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
if (handledDevices.TryAdd(pair.Key, pair.Value))
|
||||
{
|
||||
TResult result = await predicate(pair.Value, parameter);
|
||||
if (result is true or not null)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!ct.IsCancellationRequested && !discoverTask.IsCompleted);
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
206
NitroxModel/Helper/NetHelper.cs
Normal file
206
NitroxModel/Helper/NetHelper.cs
Normal file
@@ -0,0 +1,206 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading.Tasks;
|
||||
#if RELEASE
|
||||
using System.Net.Http;
|
||||
using System.Text.RegularExpressions;
|
||||
#endif
|
||||
|
||||
namespace NitroxModel.Helper
|
||||
{
|
||||
public static class NetHelper
|
||||
{
|
||||
private static readonly string[] privateNetworks =
|
||||
{
|
||||
"10.0.0.0/8",
|
||||
"127.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.0.0.0/24 ",
|
||||
"192.168.0.0/16",
|
||||
"198.18.0.0/15",
|
||||
};
|
||||
|
||||
private static IPAddress wanIpCache;
|
||||
private static IPAddress lanIpCache;
|
||||
private static readonly object wanIpLock = new();
|
||||
private static readonly object lanIpLock = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the network interfaces used for going onto the internet.
|
||||
/// This is done by filtering for "Ethernet" and "Wi-Fi" network interfaces where "Ethernet" is returned earlier.
|
||||
/// </summary>
|
||||
/// <returns>Network interfaces used to go onto the internet.</returns>
|
||||
public static IEnumerable<NetworkInterface> GetInternetInterfaces()
|
||||
{
|
||||
return NetworkInterface.GetAllNetworkInterfaces()
|
||||
.Where(n => n.OperationalStatus is OperationalStatus.Up
|
||||
&& n.NetworkInterfaceType is not (NetworkInterfaceType.Tunnel or NetworkInterfaceType.Loopback)
|
||||
&& n.NetworkInterfaceType is (NetworkInterfaceType.Wireless80211 or NetworkInterfaceType.Ethernet))
|
||||
.OrderBy(n => n.NetworkInterfaceType is NetworkInterfaceType.Ethernet ? 1 : 0)
|
||||
.ThenBy(n => n.Name);
|
||||
}
|
||||
|
||||
public static IPAddress GetLanIp()
|
||||
{
|
||||
lock (lanIpLock)
|
||||
{
|
||||
if (lanIpCache != null)
|
||||
{
|
||||
return lanIpCache;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (NetworkInterface ni in GetInternetInterfaces())
|
||||
{
|
||||
foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
||||
{
|
||||
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
lock (lanIpLock)
|
||||
{
|
||||
return lanIpCache = ip.Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<IPAddress> GetWanIpAsync()
|
||||
{
|
||||
lock (wanIpLock)
|
||||
{
|
||||
if (wanIpCache != null)
|
||||
{
|
||||
return wanIpCache;
|
||||
}
|
||||
}
|
||||
|
||||
IPAddress ip = await NatHelper.GetExternalIpAsync();
|
||||
#if RELEASE
|
||||
if (ip == null || ip.IsPrivate())
|
||||
{
|
||||
Regex regex = new(@"(?:[0-2]??[0-9]{1,2}\.){3}[0-2]??[0-9]+");
|
||||
string[] sites =
|
||||
{
|
||||
"https://ipv4.icanhazip.com/",
|
||||
"https://checkip.amazonaws.com/",
|
||||
"https://api.ipify.org/",
|
||||
"https://api4.my-ip.io/ip",
|
||||
"https://ifconfig.me/",
|
||||
"https://showmyip.com/",
|
||||
};
|
||||
using HttpClient client = new();
|
||||
foreach (string site in sites)
|
||||
{
|
||||
try
|
||||
{
|
||||
using HttpResponseMessage response = await client.GetAsync(site);
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
ip = IPAddress.Parse(regex.Match(content).Value);
|
||||
break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
lock (wanIpLock)
|
||||
{
|
||||
return wanIpCache = ip;
|
||||
}
|
||||
}
|
||||
|
||||
public static IPAddress GetHamachiIp()
|
||||
{
|
||||
foreach (NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces())
|
||||
{
|
||||
if (ni.Name != "Hamachi")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
||||
{
|
||||
if (ip.Address.AddressFamily == AddressFamily.InterNetwork)
|
||||
{
|
||||
return ip.Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static async Task<bool> HasInternetConnectivityAsync()
|
||||
{
|
||||
if (!NetworkInterface.GetIsNetworkAvailable())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
using Ping ping = new();
|
||||
PingReply reply = await ping.SendPingAsync(new IPAddress([8, 8, 8, 8]),2000);
|
||||
return reply.Status == IPStatus.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given IP address is reserved for private networks.
|
||||
/// </summary>
|
||||
public static bool IsPrivate(this IPAddress address)
|
||||
{
|
||||
static bool IsInRange(IPAddress ipAddress, string mask)
|
||||
{
|
||||
string[] parts = mask.Split('/');
|
||||
|
||||
int ipNum = BitConverter.ToInt32(ipAddress.GetAddressBytes(), 0);
|
||||
int cidrAddress = BitConverter.ToInt32(IPAddress.Parse(parts[0]).GetAddressBytes(), 0);
|
||||
int cidrMask = IPAddress.HostToNetworkOrder(-1 << (32 - int.Parse(parts[1])));
|
||||
|
||||
return (ipNum & cidrMask) == (cidrAddress & cidrMask);
|
||||
}
|
||||
|
||||
foreach (string privateSubnet in privateNetworks)
|
||||
{
|
||||
if (IsInRange(address, privateSubnet))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the IP address points to the executing machine.
|
||||
/// </summary>
|
||||
public static bool IsLocalhost(this IPAddress address)
|
||||
{
|
||||
if (address == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (IPAddress.IsLoopback(address))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (NetworkInterface ni in GetInternetInterfaces())
|
||||
{
|
||||
foreach (UnicastIPAddressInformation ip in ni.GetIPProperties().UnicastAddresses)
|
||||
{
|
||||
if (address.Equals(ip.Address))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
262
NitroxModel/Helper/NitroxUser.cs
Normal file
262
NitroxModel/Helper/NitroxUser.cs
Normal file
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using NitroxModel.Discovery;
|
||||
using NitroxModel.Discovery.InstallationFinders.Core;
|
||||
using NitroxModel.Platforms.OS.Shared;
|
||||
using NitroxModel.Platforms.Store;
|
||||
using NitroxModel.Platforms.Store.Interfaces;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
public static class NitroxUser
|
||||
{
|
||||
public const string LAUNCHER_PATH_ENV_KEY = "NITROX_LAUNCHER_PATH";
|
||||
private const string PREFERRED_GAMEPATH_KEY = "PreferredGamePath";
|
||||
private static string appDataPath;
|
||||
private static string launcherPath;
|
||||
private static string gamePath;
|
||||
private static string executableRootPath;
|
||||
private static string executablePath;
|
||||
private static string assetsPath;
|
||||
|
||||
private static readonly IEnumerable<Func<string>> launcherPathDataSources = new List<Func<string>>
|
||||
{
|
||||
() => Environment.GetEnvironmentVariable(LAUNCHER_PATH_ENV_KEY),
|
||||
() =>
|
||||
{
|
||||
Assembly currentAsm = Assembly.GetEntryAssembly();
|
||||
if (currentAsm?.GetName().Name.Equals("Nitrox.Launcher") ?? false)
|
||||
{
|
||||
return Path.GetDirectoryName(currentAsm.Location);
|
||||
}
|
||||
|
||||
Assembly execAsm = Assembly.GetExecutingAssembly();
|
||||
string execDir = string.IsNullOrEmpty(execAsm.Location) ? Directory.GetCurrentDirectory() : execAsm.Location;
|
||||
DirectoryInfo execParentDir = Directory.GetParent(execDir);
|
||||
|
||||
// When running tests LanguageFiles is in same directory
|
||||
if (execParentDir != null && Directory.Exists(Path.Combine(execParentDir.FullName, "LanguageFiles")))
|
||||
{
|
||||
return execParentDir.FullName;
|
||||
}
|
||||
|
||||
// NitroxModel, NitroxServer and other assemblies are stored in Nitrox.Launcher/lib
|
||||
if (execParentDir?.Parent != null && Directory.Exists(Path.Combine(execParentDir.Parent.FullName, "Resources", "LanguageFiles")))
|
||||
{
|
||||
return execParentDir.Parent.FullName;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
() =>
|
||||
{
|
||||
using ProcessEx proc = ProcessEx.GetFirstProcess("Nitrox.Launcher");
|
||||
string executable = proc?.MainModule?.FileName;
|
||||
return !string.IsNullOrWhiteSpace(executable) ? Path.GetDirectoryName(executable) : null;
|
||||
}
|
||||
};
|
||||
|
||||
public static string AppDataPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (appDataPath != null)
|
||||
{
|
||||
return appDataPath;
|
||||
}
|
||||
|
||||
string applicationData = null;
|
||||
|
||||
// On linux Environment.SpecialFolder.ApplicationData returns the Windows version inside wine, this bypasses that behaviour
|
||||
string homeInWineEnv = Environment.GetEnvironmentVariable("WINEHOMEDIR");
|
||||
if (homeInWineEnv is { Length: > 4 })
|
||||
{
|
||||
string homeInWine = homeInWineEnv[4..]; // WINEHOMEDIR is prefixed with \??\
|
||||
if (Directory.Exists(homeInWine))
|
||||
{
|
||||
applicationData = Path.Combine(homeInWine, ".config");
|
||||
Directory.CreateDirectory(applicationData); // Create it if it's not there (which should not happen in normal setups)
|
||||
}
|
||||
}
|
||||
|
||||
if (!Directory.Exists(applicationData))
|
||||
{
|
||||
applicationData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
|
||||
}
|
||||
|
||||
return appDataPath = Path.Combine(applicationData, "Nitrox");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the launcher path that was previously saved by other Nitrox code.
|
||||
/// </summary>
|
||||
public static string LauncherPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (launcherPath != null)
|
||||
{
|
||||
return launcherPath;
|
||||
}
|
||||
|
||||
foreach (Func<string> retriever in launcherPathDataSources)
|
||||
{
|
||||
string path = retriever();
|
||||
if (!string.IsNullOrWhiteSpace(path) && Directory.Exists(path))
|
||||
{
|
||||
return launcherPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string AssetBundlePath => Path.Combine(LauncherPath, "Resources", "AssetBundles");
|
||||
public static string LanguageFilesPath => Path.Combine(LauncherPath, "Resources", "LanguageFiles");
|
||||
|
||||
public static string PreferredGamePath
|
||||
{
|
||||
get => KeyValueStore.Instance.GetValue<string>(PREFERRED_GAMEPATH_KEY);
|
||||
set => KeyValueStore.Instance.SetValue(PREFERRED_GAMEPATH_KEY, value);
|
||||
}
|
||||
|
||||
private static IGamePlatform gamePlatform;
|
||||
public static event Action GamePlatformChanged;
|
||||
|
||||
public static IGamePlatform GamePlatform
|
||||
{
|
||||
get
|
||||
{
|
||||
if (gamePlatform == null)
|
||||
{
|
||||
_ = GamePath; // Ensure gamePath is set
|
||||
}
|
||||
return gamePlatform;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (gamePlatform != value)
|
||||
{
|
||||
gamePlatform = value;
|
||||
GamePlatformChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GamePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrEmpty(gamePath))
|
||||
{
|
||||
return gamePath;
|
||||
}
|
||||
|
||||
List<GameFinderResult> finderResults = GameInstallationFinder.Instance.FindGame(GameInfo.Subnautica).TakeUntilInclusive(r => r is { IsOk: false }).ToList();
|
||||
GameFinderResult potentiallyValidResult = finderResults.LastOrDefault();
|
||||
if (potentiallyValidResult?.IsOk == true)
|
||||
{
|
||||
Log.Debug($"Game installation was found by {potentiallyValidResult.FinderName} at '{potentiallyValidResult.Path}'");
|
||||
gamePath = potentiallyValidResult.Path;
|
||||
GamePlatform = GamePlatforms.GetPlatformByGameDir(gamePath);
|
||||
return gamePath;
|
||||
}
|
||||
|
||||
Log.Error($"Could not locate Subnautica installation directory: {Environment.NewLine}{string.Join(Environment.NewLine, finderResults.Select(i => $"{i.FinderName}: {i.ErrorMessage}"))}");
|
||||
return string.Empty;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!Directory.Exists(value))
|
||||
{
|
||||
throw new ArgumentException("Given path is an invalid directory");
|
||||
}
|
||||
|
||||
// Ensures the path looks alright (no mixed / and \ path separators)
|
||||
gamePath = Path.GetFullPath(value);
|
||||
GamePlatform = GamePlatforms.GetPlatformByGameDir(gamePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExecutableRootPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(executableRootPath))
|
||||
{
|
||||
return executableRootPath;
|
||||
}
|
||||
string exePath = ExecutableFilePath;
|
||||
if (exePath == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return executableRootPath = Path.GetDirectoryName(exePath);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ExecutableFilePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(executablePath))
|
||||
{
|
||||
return executablePath;
|
||||
}
|
||||
|
||||
Assembly entryAssembly = Assembly.GetEntryAssembly();
|
||||
if (entryAssembly == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string path = entryAssembly.Location;
|
||||
// File URI works different on Linux/OSX, so only do uri parsing on Windows.
|
||||
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
path = new Uri(path).LocalPath;
|
||||
}
|
||||
return executablePath = path;
|
||||
}
|
||||
}
|
||||
|
||||
public static string AssetsPath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(assetsPath))
|
||||
{
|
||||
return assetsPath;
|
||||
}
|
||||
|
||||
string nitroxAssets;
|
||||
if (NitroxEnvironment.IsTesting)
|
||||
{
|
||||
nitroxAssets = Directory.GetCurrentDirectory();
|
||||
while (nitroxAssets != null && Path.GetFileName(nitroxAssets) != "Nitrox.Test")
|
||||
{
|
||||
nitroxAssets = Directory.GetParent(nitroxAssets)?.FullName;
|
||||
}
|
||||
if (nitroxAssets != null)
|
||||
{
|
||||
nitroxAssets = Path.Combine(Directory.GetParent(nitroxAssets)?.FullName ?? throw new Exception("Failed to get Nitrox assets during tests"), "Nitrox.Assets.Subnautica");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nitroxAssets = LauncherPath ?? ExecutableRootPath;
|
||||
}
|
||||
return assetsPath = nitroxAssets;
|
||||
}
|
||||
}
|
||||
}
|
66
NitroxModel/Helper/PirateDetection.cs
Normal file
66
NitroxModel/Helper/PirateDetection.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using NitroxModel.Platforms.OS.Shared;
|
||||
|
||||
namespace NitroxModel.Helper
|
||||
{
|
||||
public static class PirateDetection
|
||||
{
|
||||
public static bool HasTriggered { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Event that calls subscribers if the pirate detection triggered successfully.
|
||||
/// New subscribers are immediately invoked if the pirate flag has been set at the time of subscription.
|
||||
/// </summary>
|
||||
public static event EventHandler PirateDetected
|
||||
{
|
||||
add
|
||||
{
|
||||
pirateDetected += value;
|
||||
|
||||
// Invoke new subscriber immediately if pirate has already been detected.
|
||||
if (HasTriggered)
|
||||
{
|
||||
value?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
remove => pirateDetected -= value;
|
||||
}
|
||||
|
||||
public static bool TriggerOnDirectory(string subnauticaRoot)
|
||||
{
|
||||
if (!IsPirateByDirectory(subnauticaRoot))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
OnPirateDetected();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static event EventHandler pirateDetected;
|
||||
|
||||
private static bool IsPirateByDirectory(string subnauticaRoot)
|
||||
{
|
||||
string subdirDll = Path.Combine(subnauticaRoot, GameInfo.Subnautica.DataFolder, "Plugins", "x86_64", "steam_api64.dll");
|
||||
if (File.Exists(subdirDll) && !FileSystem.Instance.IsTrustedFile(subdirDll))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Dlls might be in root if cracked game (to override DLLs in sub directories).
|
||||
string rootDll = Path.Combine(subnauticaRoot, "steam_api64.dll");
|
||||
if (File.Exists(rootDll) && !FileSystem.Instance.IsTrustedFile(rootDll))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void OnPirateDetected()
|
||||
{
|
||||
pirateDetected?.Invoke(null, EventArgs.Empty);
|
||||
HasTriggered = true;
|
||||
}
|
||||
}
|
||||
}
|
147
NitroxModel/Helper/Reflect.cs
Normal file
147
NitroxModel/Helper/Reflect.cs
Normal file
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
/// <summary>
|
||||
/// Utility class for reflection API.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This class should be used when requiring <see cref="MethodInfo" /> or <see cref="MemberInfo" /> like information from code. This will ensure that compilation only succeeds
|
||||
/// when reflection is used properly.
|
||||
/// </remarks>
|
||||
public static class Reflect
|
||||
{
|
||||
private static readonly BindingFlags BINDING_FLAGS_ALL = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
public static ConstructorInfo Constructor(Expression<Action> expression)
|
||||
{
|
||||
return (ConstructorInfo)GetMemberInfo(expression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a lambda expression that calls a method, returns the method info.
|
||||
/// If method has parameters then anything can be supplied, the actual method won't be called.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="expression">The expression.</param>
|
||||
/// <returns></returns>
|
||||
public static MethodInfo Method(Expression<Action> expression)
|
||||
{
|
||||
return (MethodInfo)GetMemberInfo(expression);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a lambda expression that calls a method, returns the method info.
|
||||
/// If method has parameters then anything can be supplied, the actual method won't be called.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="expression">The expression.</param>
|
||||
/// <returns></returns>
|
||||
public static MethodInfo Method<T>(Expression<Action<T>> expression)
|
||||
{
|
||||
return (MethodInfo)GetMemberInfo(expression, typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Given a lambda expression that calls a method, returns the method info.
|
||||
/// If method has parameters then anything can be supplied, the actual method won't be called.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <typeparam name="TResult"></typeparam>
|
||||
/// <param name="expression">The expression.</param>
|
||||
/// <returns></returns>
|
||||
public static MethodInfo Method<T, TResult>(Expression<Func<T, TResult>> expression)
|
||||
{
|
||||
return (MethodInfo)GetMemberInfo(expression, typeof(T));
|
||||
}
|
||||
|
||||
public static FieldInfo Field<T>(Expression<Func<T>> expression)
|
||||
{
|
||||
return (FieldInfo)GetMemberInfo(expression);
|
||||
}
|
||||
|
||||
public static FieldInfo Field<T>(Expression<Func<T, object>> expression)
|
||||
{
|
||||
return (FieldInfo)GetMemberInfo(expression);
|
||||
}
|
||||
|
||||
public static PropertyInfo Property<T>(Expression<Func<T>> expression)
|
||||
{
|
||||
return (PropertyInfo)GetMemberInfo(expression);
|
||||
}
|
||||
|
||||
public static PropertyInfo Property<T>(Expression<Func<T, object>> expression)
|
||||
{
|
||||
return (PropertyInfo)GetMemberInfo(expression);
|
||||
}
|
||||
|
||||
private static MemberInfo GetMemberInfo(LambdaExpression expression, Type implementingType = null)
|
||||
{
|
||||
Expression currentExpression = expression.Body;
|
||||
while (true)
|
||||
{
|
||||
switch (currentExpression.NodeType)
|
||||
{
|
||||
case ExpressionType.MemberAccess:
|
||||
// If it cannot be unwrapped further, return this member.
|
||||
MemberExpression exp = (MemberExpression)currentExpression;
|
||||
if (exp.Expression is null or ParameterExpression)
|
||||
{
|
||||
return exp.Member;
|
||||
}
|
||||
currentExpression = exp.Expression;
|
||||
break;
|
||||
case ExpressionType.UnaryPlus:
|
||||
currentExpression = ((UnaryExpression)currentExpression).Operand;
|
||||
break;
|
||||
case ExpressionType.New:
|
||||
return ((NewExpression)currentExpression).Constructor;
|
||||
case ExpressionType.Call:
|
||||
MethodInfo method = ((MethodCallExpression)currentExpression).Method;
|
||||
if (implementingType == null)
|
||||
{
|
||||
return method;
|
||||
}
|
||||
if (implementingType == method.ReflectedType)
|
||||
{
|
||||
return method;
|
||||
}
|
||||
// If method target is an interface, lookup the implementation in the implementingType.
|
||||
if (method.ReflectedType?.IsInterface ?? false)
|
||||
{
|
||||
InterfaceMapping interfaceMap = implementingType.GetInterfaceMap(method.ReflectedType);
|
||||
int i = Array.IndexOf(interfaceMap.InterfaceMethods, method);
|
||||
return interfaceMap.TargetMethods[i];
|
||||
}
|
||||
|
||||
// Expression does not know which type the MethodInfo belongs to if it's virtual; MethodInfo of base type/interface is returned instead.
|
||||
ParameterInfo[] parameters = method.GetParameters();
|
||||
Type[] args = new Type[parameters.Length];
|
||||
for (int i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
args[i] = parameters[i].ParameterType;
|
||||
}
|
||||
return implementingType.GetMethod(method.Name, BINDING_FLAGS_ALL, null, args, null) ?? throw new Exception($"Unable to find method {method} on type {implementingType.FullName}");
|
||||
case ExpressionType.Convert:
|
||||
case ExpressionType.ConvertChecked:
|
||||
currentExpression = ((UnaryExpression)currentExpression).Operand;
|
||||
break;
|
||||
case ExpressionType.Invoke:
|
||||
currentExpression = ((InvocationExpression)currentExpression).Expression;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentException($"Lambda expression '{expression}' does not target a member");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to supplement ref/out/in parameters when using the <see cref="Reflect" /> API.
|
||||
/// </summary>
|
||||
public struct Ref<T>
|
||||
{
|
||||
public static T Field;
|
||||
}
|
||||
}
|
24
NitroxModel/Helper/StringHelper.cs
Normal file
24
NitroxModel/Helper/StringHelper.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace NitroxModel.Helper
|
||||
{
|
||||
public static class StringHelper
|
||||
{
|
||||
private static readonly Random random = new Random();
|
||||
|
||||
public static string GenerateRandomString(int size)
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
char ch;
|
||||
for (int i = 0; i < size; i++)
|
||||
{
|
||||
ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65)));
|
||||
builder.Append(ch);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
57
NitroxModel/Helper/Validate.cs
Normal file
57
NitroxModel/Helper/Validate.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
extern alias JB;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using JB::JetBrains.Annotations;
|
||||
using NitroxModel.DataStructures.Util;
|
||||
|
||||
namespace NitroxModel.Helper;
|
||||
|
||||
public static class Validate
|
||||
{
|
||||
// "where T : class" prevents non-nullable valuetypes from getting boxed to objects.
|
||||
// In other words: Error when trying to assert non-null on something that can't be null in the first place.
|
||||
[ContractAnnotation("o:null => halt")]
|
||||
public static void NotNull<T>(T o, [CallerArgumentExpression("o")] string argumentExpression = null) where T : class
|
||||
{
|
||||
if (o != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ArgumentNullException(argumentExpression);
|
||||
}
|
||||
|
||||
public static void IsTrue(bool b, [CallerArgumentExpression("b")] string argumentExpression = null)
|
||||
{
|
||||
if (!b)
|
||||
{
|
||||
throw new ArgumentException(argumentExpression);
|
||||
}
|
||||
}
|
||||
|
||||
public static void IsFalse(bool b, [CallerArgumentExpression("b")] string argumentExpression = null)
|
||||
{
|
||||
if (b)
|
||||
{
|
||||
throw new ArgumentException(argumentExpression);
|
||||
}
|
||||
}
|
||||
|
||||
public static T IsPresent<T>(Optional<T> opt) where T : class
|
||||
{
|
||||
if (!opt.HasValue)
|
||||
{
|
||||
throw new OptionalEmptyException<T>();
|
||||
}
|
||||
return opt.Value;
|
||||
}
|
||||
|
||||
public static T IsPresent<T>(Optional<T> opt, string message) where T : class
|
||||
{
|
||||
if (!opt.HasValue)
|
||||
{
|
||||
throw new OptionalEmptyException<T>(message);
|
||||
}
|
||||
return opt.Value;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user