Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Clan v1.0.1
Clan.dll
Decompiled a day ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Jotunn.GUI; using Jotunn.Managers; using Jotunn.Utils; using Microsoft.CodeAnalysis; using ServerSync; using Splatform; using TMPro; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.TextCore; using UnityEngine.UI; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("Clan")] [assembly: AssemblyDescription("Server-authoritative Valheim clan system")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("Clan")] [assembly: AssemblyCopyright("Copyright © sighsorry 2026")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.1")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.1.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace Clan { public enum ClanRole { Leader, Officer, Member, Guest } internal enum ClanRequestType { RequestSnapshot, CreateClan, Invite, Apply, AcceptInvite, DeclineInvite, AcceptApplication, RejectApplication, KickPlayer, TransferLeadership, SetRole, LeaveClan, SendClanChat, SendClanPing, UpdatePosition, CancelApplication, UpdateClanProfile, RequestDirectory, RenameClan, RequestHud } internal enum ClanOperationResultCode { None, Success, Unchanged, InvalidName, IdentityUnavailable, Unauthorized, ClanChanged, NameTaken, RateLimited, Unavailable, Failed, SnapshotRateLimited, IdentityRejected } internal enum ClanResponseType { Snapshot, Chat, MapPing, PositionUpdate, Directory, DirectoryInvalidated, Hud } internal enum ClanDirectoryPlayerState { None, Clan, Pending, Invited } internal enum ClanValidationField { None, ClanName, ClanDescription, ClanEmblemKey } internal enum ClanValidationError { Required, TooLong, RichText, ControlCharacters, InvalidUnicode, InvalidCharacters, TrailingSeparator, MissingLetterOrNumber, InvalidStartOrEnd } internal static class ClanDataRules { public const int MaxPlatformIdLength = 128; public const int MaxPlayerKeyLength = 160; public const int MaxPlayerNameLength = 64; public const int ClanIdLength = 32; public const int MaxClanNameLength = 40; public const int MaxClanDescriptionLength = 160; public const int MaxClanEmblemKeyLength = 32; public const int MaxChatMessageLength = 400; public const int MaxStatusLength = 1024; public const int MaxInviteIdLength = 64; public const int MaxClans = 1024; public const int MaxMembersPerClan = 512; public const int MaxApplicationsPerClan = 512; public const int MaxInvites = 4096; public const int MaxDirectoryPlayers = 1024; public const int MaxHudPlayers = 10; public const float MaxHudHealth = 1000000f; private static readonly object ValidationErrorDataKey = new object(); public static string RequireText(string? value, int maxLength, string fieldName, bool allowEmpty = true, bool allowLineBreaks = false, ClanValidationField validationField = ClanValidationField.None) { string text = (value ?? "").Trim(); if (!allowEmpty && text.Length == 0) { throw ValidationFailure(validationField, ClanValidationError.Required, fieldName + " is required."); } if (text.Length > maxLength) { throw ValidationFailure(validationField, ClanValidationError.TooLong, $"{fieldName} exceeds {maxLength} characters."); } if (text.IndexOf('<') >= 0 || text.IndexOf('>') >= 0) { throw ValidationFailure(validationField, ClanValidationError.RichText, fieldName + " cannot contain rich-text tags."); } string text2 = text; foreach (char c in text2) { if (char.IsControl(c) && (!allowLineBreaks || (c != '\r' && c != '\n' && c != '\t'))) { throw ValidationFailure(validationField, ClanValidationError.ControlCharacters, fieldName + " contains unsupported control characters."); } } return text; } public static string RequireClanDescription(string? value, string fieldName = "clan description") { return RequireText(value, 160, fieldName, allowEmpty: true, allowLineBreaks: false, ClanValidationField.ClanDescription); } public static string ReadClanDescription(ZPackage package, string fieldName = "clan description") { return RequireClanDescription(package.ReadString(), fieldName); } public static void WriteClanDescription(ZPackage package, string? value, string fieldName = "clan description") { package.Write(RequireClanDescription(value, fieldName)); } public static string RequireClanName(string? value, string fieldName = "clan name") { string text; try { text = (value ?? "").Normalize(NormalizationForm.FormC); } catch (ArgumentException innerException) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.InvalidUnicode, fieldName + " contains invalid Unicode data.", innerException); } StringBuilder stringBuilder = new StringBuilder(text.Length); bool flag = false; string text2 = text; foreach (char c in text2) { if (CharUnicodeInfo.GetUnicodeCategory(c) == UnicodeCategory.SpaceSeparator) { if (stringBuilder.Length > 0 && !flag) { stringBuilder.Append(' '); } flag = true; } else { stringBuilder.Append(c); flag = false; } } string text3 = stringBuilder.ToString().Trim(new char[1] { ' ' }); if (text3.Length == 0) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.Required, fieldName + " is required."); } int num = 0; bool flag2 = false; bool flag3 = true; bool flag4 = false; int num2; for (int j = 0; j < text3.Length; j += num2) { char c2 = text3[j]; num2 = 1; if (char.IsHighSurrogate(c2)) { if (j + 1 >= text3.Length || !char.IsLowSurrogate(text3[j + 1])) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.InvalidUnicode, fieldName + " contains invalid Unicode data."); } num2 = 2; } else if (char.IsLowSurrogate(c2)) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.InvalidUnicode, fieldName + " contains invalid Unicode data."); } UnicodeCategory unicodeCategory = CharUnicodeInfo.GetUnicodeCategory(text3, j); bool flag5 = (uint)unicodeCategory <= 4u; bool flag6 = flag5; flag5 = (uint)(unicodeCategory - 8) <= 2u; bool flag7 = flag5; flag5 = (uint)(unicodeCategory - 5) <= 2u; bool flag8 = flag5; bool flag9 = num2 == 1 && (c2 == ' ' || c2 == '-' || c2 == '_' || c2 == '·'); if (flag6 || flag7) { flag2 = true; flag3 = false; flag4 = true; } else if (flag8 && flag4) { flag3 = false; } else { if (!flag9 || flag3) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.InvalidCharacters, fieldName + " can contain only letters, numbers, marks, spaces, '-', '_', or '·'."); } flag3 = true; flag4 = false; } num++; if (num > 40) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.TooLong, $"{fieldName} exceeds {40} Unicode characters."); } } if (flag3) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.TrailingSeparator, fieldName + " cannot end with a separator."); } if (!flag2) { throw ValidationFailure(ClanValidationField.ClanName, ClanValidationError.MissingLetterOrNumber, fieldName + " must contain a letter or number."); } return text3; } public static string RequireOptionalClanName(string? value, string fieldName = "clan name") { if (!string.IsNullOrWhiteSpace(value)) { return RequireClanName(value, fieldName); } return ""; } public static string ReadClanName(ZPackage package, string fieldName = "clan name") { return RequireClanName(package.ReadString(), fieldName); } public static string ReadOptionalClanName(ZPackage package, string fieldName = "clan name") { return RequireOptionalClanName(package.ReadString(), fieldName); } public static void WriteClanName(ZPackage package, string? value, string fieldName = "clan name") { package.Write(RequireClanName(value, fieldName)); } public static void WriteOptionalClanName(ZPackage package, string? value, string fieldName = "clan name") { package.Write(RequireOptionalClanName(value, fieldName)); } public static string ReadText(ZPackage package, int maxLength, string fieldName, bool allowEmpty = true, bool allowLineBreaks = false) { return RequireText(package.ReadString(), maxLength, fieldName, allowEmpty, allowLineBreaks); } public static void WriteText(ZPackage package, string? value, int maxLength, string fieldName, bool allowEmpty = true, bool allowLineBreaks = false) { package.Write(RequireText(value, maxLength, fieldName, allowEmpty, allowLineBreaks)); } public static string RequirePlayerKey(string? value, string fieldName = "player key") { string text = RequireText(value, 160, fieldName, allowEmpty: false); if (!TryParsePlayerKey(text, out string _, out long _)) { throw new InvalidDataException(fieldName + " is invalid."); } return text; } public static string ReadPlayerKey(ZPackage package, string fieldName = "player key") { return RequirePlayerKey(package.ReadString(), fieldName); } public static void WritePlayerKey(ZPackage package, string? value, string fieldName = "player key") { package.Write(RequirePlayerKey(value, fieldName)); } public static string RequirePlatformId(string? value, string fieldName = "platform id") { return RequireText(value, 128, fieldName, allowEmpty: false); } public static long RequireCharacterPlayerId(long value, string fieldName = "character player id") { if (value == 0L) { throw new InvalidDataException(fieldName + " must be non-zero."); } return value; } public static string BuildPlayerKey(string? platformId, long playerId) { string text = RequirePlatformId(platformId); long num = RequireCharacterPlayerId(playerId); return text.Length.ToString(CultureInfo.InvariantCulture) + ":" + text + ":" + num.ToString(CultureInfo.InvariantCulture); } public static bool TryParsePlayerKey(string? value, out string platformId, out long playerId) { platformId = ""; playerId = 0L; string text = value ?? ""; int num = text.IndexOf(':'); if (num <= 0 || !int.TryParse(text.Substring(0, num), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result <= 0 || result > 128) { return false; } int num2 = num + 1; int num3 = num2 + result; if (num3 >= text.Length || text[num3] != ':') { return false; } string text2 = text.Substring(num2, result); if (!long.TryParse(text.Substring(num3 + 1), NumberStyles.Integer, CultureInfo.InvariantCulture, out var result2) || result2 == 0L || NormalizePlatformId(text2).Length == 0) { return false; } string x; try { x = BuildPlayerKey(text2, result2); } catch (InvalidDataException) { return false; } if (!StringComparer.Ordinal.Equals(x, text)) { return false; } platformId = text2; playerId = result2; return true; } public static string ReadPlayerName(ZPackage package, string fieldName = "player name") { return ReadText(package, 64, fieldName); } public static void WritePlayerName(ZPackage package, string? value, string fieldName = "player name") { WriteText(package, value, 64, fieldName); } public static string RequireClanId(string? value, string fieldName = "clan id") { string text = RequireText(value, 32, fieldName, allowEmpty: false); if (text.Length != 32 || !Guid.TryParseExact(text, "N", out var result) || result == Guid.Empty || !StringComparer.Ordinal.Equals(result.ToString("N"), text)) { throw new InvalidDataException(fieldName + " is invalid."); } return text; } public static string RequireOptionalClanId(string? value, string fieldName = "clan id") { if (!string.IsNullOrWhiteSpace(value)) { return RequireClanId(value, fieldName); } return ""; } public static string ReadClanId(ZPackage package, string fieldName = "clan id") { return RequireClanId(package.ReadString(), fieldName); } public static string ReadOptionalClanId(ZPackage package, string fieldName = "clan id") { return RequireOptionalClanId(package.ReadString(), fieldName); } public static void WriteClanId(ZPackage package, string? value, string fieldName = "clan id") { package.Write(RequireClanId(value, fieldName)); } public static void WriteOptionalClanId(ZPackage package, string? value, string fieldName = "clan id") { package.Write(RequireOptionalClanId(value, fieldName)); } public static string RequireClanEmblemKey(string? value, string fieldName = "clan emblem key") { string text = RequireText(value, 32, fieldName, allowEmpty: true, allowLineBreaks: false, ClanValidationField.ClanEmblemKey); if (text.Length == 0) { return text; } if (!IsLowerAsciiLetterOrDigit(text[0]) || !IsLowerAsciiLetterOrDigit(text[text.Length - 1])) { throw ValidationFailure(ClanValidationField.ClanEmblemKey, ClanValidationError.InvalidStartOrEnd, fieldName + " must start and end with a lowercase ASCII letter or digit."); } string text2 = text; foreach (char c in text2) { if (!IsLowerAsciiLetterOrDigit(c) && c != '_' && c != '-') { throw ValidationFailure(ClanValidationField.ClanEmblemKey, ClanValidationError.InvalidCharacters, fieldName + " can contain only lowercase ASCII letters, digits, '_' or '-'."); } } return text; } public static string ReadClanEmblemKey(ZPackage package, string fieldName = "clan emblem key") { return RequireClanEmblemKey(package.ReadString(), fieldName); } public static void WriteClanEmblemKey(ZPackage package, string? value, string fieldName = "clan emblem key") { package.Write(RequireClanEmblemKey(value, fieldName)); } public static T RequireEnum<T>(T value, string fieldName) where T : struct, Enum { if (!Enum.IsDefined(typeof(T), value)) { throw new InvalidDataException($"{fieldName} has an unknown value ({Convert.ToInt32(value)})."); } return value; } public static T ReadEnum<T>(ZPackage package, string fieldName) where T : struct, Enum { int num = package.ReadInt(); if (!Enum.IsDefined(typeof(T), num)) { throw new InvalidDataException($"{fieldName} has an unknown value ({num})."); } return (T)Enum.ToObject(typeof(T), num); } public static ClanRole RequireAssignableRole(ClanRole role, string fieldName) { RequireEnum(role, fieldName); if (role != ClanRole.Officer && role != ClanRole.Member && role != ClanRole.Guest) { throw new InvalidDataException(fieldName + " must be Officer, Member, or Guest."); } return role; } public static ClanRole ReadAssignableRole(ZPackage package, string fieldName) { return RequireAssignableRole(ReadEnum<ClanRole>(package, fieldName), fieldName); } public static int GetRolePower(ClanRole role) { return RequireEnum(role, "clan role") switch { ClanRole.Leader => 3, ClanRole.Officer => 2, ClanRole.Member => 1, ClanRole.Guest => 0, _ => throw new InvalidDataException("Clan role is unsupported."), }; } public static int RequireRange(int value, int minimum, int maximum, string fieldName) { if (value < minimum || value > maximum) { throw new InvalidDataException(fieldName + " is outside the supported range."); } return value; } public static long RequireRequestId(long value, string fieldName = "request id") { if (value <= 0) { throw new InvalidDataException(fieldName + " must be positive."); } return value; } public static long RequireOptionalRequestId(long value, string fieldName = "response request id") { if (value < 0) { throw new InvalidDataException(fieldName + " cannot be negative."); } return value; } public static int ReadCount(ZPackage package, int maximum, string fieldName) { return RequireRange(package.ReadInt(), 0, maximum, fieldName + " count"); } public static void RequireCount(int count, int maximum, string fieldName) { RequireRange(count, 0, maximum, fieldName + " count"); } public static Vector3 ReadFiniteVector(ZPackage package, string fieldName) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) Vector3 val = package.ReadVector3(); RequireFiniteVector(val, fieldName); return val; } public static void RequireFiniteVector(Vector3 value, string fieldName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (!IsFinite(value.x) || !IsFinite(value.y) || !IsFinite(value.z)) { throw new InvalidDataException(fieldName + " must contain finite coordinates."); } } public static string NormalizePlayerKey(string? value) { try { return RequirePlayerKey(value); } catch (InvalidDataException) { return ""; } } public static string NormalizePlatformId(string? value) { try { return RequirePlatformId(value); } catch (InvalidDataException) { return ""; } } public static string SanitizePlayerName(string? value) { string text = (value ?? "").Replace("<", " ").Replace(">", " "); StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, 64)); for (int i = 0; i < text.Length; i++) { if (stringBuilder.Length >= 64) { break; } char c = text[i]; if (char.IsControl(c)) { continue; } bool flag = char.IsHighSurrogate(c) && i + 1 < text.Length && char.IsLowSurrogate(text[i + 1]); if (!char.IsSurrogate(c) || flag) { int num = ((!flag) ? 1 : 2); if (stringBuilder.Length > 64 - num) { break; } stringBuilder.Append(c); if (flag) { stringBuilder.Append(text[++i]); } } } return stringBuilder.ToString().Trim(); } public static bool TryGetValidationError(InvalidDataException exception, out ClanValidationField field, out ClanValidationError error) { if (exception.Data[ValidationErrorDataKey] is (ClanValidationField, ClanValidationError) tuple) { (field, error) = tuple; return true; } field = ClanValidationField.None; error = ClanValidationError.Required; return false; } private static InvalidDataException ValidationFailure(ClanValidationField field, ClanValidationError error, string message, Exception? innerException = null) { InvalidDataException ex = new InvalidDataException(message, innerException); if (field != ClanValidationField.None) { ex.Data[ValidationErrorDataKey] = (field, error); } return ex; } private static bool IsFinite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool IsLowerAsciiLetterOrDigit(char value) { switch (value) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': return true; default: return false; } } } internal readonly struct ClanPlayerRef : IEquatable<ClanPlayerRef> { public readonly string Id; public readonly string PlatformId; public readonly long CharacterPlayerId; public readonly string Name; public bool IsValid { get { if (!string.IsNullOrWhiteSpace(PlatformId) && CharacterPlayerId != 0L) { return !string.IsNullOrWhiteSpace(Id); } return false; } } public ClanPlayerRef(string? platformId, long playerId, string? name) { PlatformId = ClanDataRules.NormalizePlatformId(platformId); CharacterPlayerId = playerId; Name = ClanDataRules.SanitizePlayerName(name); Id = ((PlatformId.Length == 0 || CharacterPlayerId == 0L) ? "" : ClanDataRules.BuildPlayerKey(PlatformId, CharacterPlayerId)); } public static ClanPlayerRef Local() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) IDistributionPlatform distributionPlatform = PlatformManager.DistributionPlatform; string platformId = ((((distributionPlatform != null) ? distributionPlatform.LocalUser : null) != null) ? ((object)((IUser)distributionPlatform.LocalUser).PlatformUserID/*cast due to .constrained prefix*/).ToString() : ""); Player localPlayer = Player.m_localPlayer; long num2; if (localPlayer == null) { Game instance = Game.instance; long? obj; if (instance == null) { obj = null; } else { PlayerProfile playerProfile = instance.GetPlayerProfile(); obj = ((playerProfile != null) ? new long?(playerProfile.GetPlayerID()) : ((long?)null)); } long? num = obj; num2 = num.GetValueOrDefault(); } else { num2 = localPlayer.GetPlayerID(); } long playerId = num2; Game instance2 = Game.instance; object obj2; if (instance2 == null) { obj2 = null; } else { PlayerProfile playerProfile2 = instance2.GetPlayerProfile(); obj2 = ((playerProfile2 != null) ? playerProfile2.GetName() : null); } if (obj2 == null) { obj2 = "Unknown"; } string name = (string)obj2; return new ClanPlayerRef(platformId, playerId, name); } public void Write(ZPackage package) { ClanDataRules.WriteText(package, PlatformId, 128, "platform id", allowEmpty: false); package.Write(ClanDataRules.RequireCharacterPlayerId(CharacterPlayerId)); ClanDataRules.WritePlayerName(package, Name); } public static ClanPlayerRef Read(ZPackage package) { return new ClanPlayerRef(ClanDataRules.ReadText(package, 128, "platform id", allowEmpty: false), ClanDataRules.RequireCharacterPlayerId(package.ReadLong()), ClanDataRules.ReadPlayerName(package)); } public bool Equals(ClanPlayerRef other) { return StringComparer.Ordinal.Equals(Id, other.Id); } public override bool Equals(object? obj) { if (obj is ClanPlayerRef other) { return Equals(other); } return false; } public override int GetHashCode() { if (Id != null) { return StringComparer.Ordinal.GetHashCode(Id); } return 0; } public override string ToString() { string text = (IsValid ? $"{PlatformId}/{CharacterPlayerId}" : ""); if (!string.IsNullOrWhiteSpace(Name)) { return Name + " (" + text + ")"; } return text; } public static bool operator ==(ClanPlayerRef left, ClanPlayerRef right) { return left.Equals(right); } public static bool operator !=(ClanPlayerRef left, ClanPlayerRef right) { return !left.Equals(right); } } internal sealed class ClanMember { public ClanPlayerRef Player; public ClanRole Role = ClanRole.Member; public float LastClanChatTime = float.NegativeInfinity; public float LastClanPingTime = float.NegativeInfinity; } internal sealed class ClanInvite { public string InviteId = Guid.NewGuid().ToString("N"); public string ClanId = ""; public string FromName = ""; public ClanPlayerRef Target; } internal sealed class ClanState { public long CreationOrder; public string Name = ""; public string Description = ""; public string EmblemKey = ""; public readonly Dictionary<string, ClanMember> Members = new Dictionary<string, ClanMember>(StringComparer.Ordinal); public readonly Dictionary<string, ClanPlayerRef> Applications = new Dictionary<string, ClanPlayerRef>(StringComparer.Ordinal); public string ClanId { get; } public ClanState(string clanId) { ClanId = ClanDataRules.RequireClanId(clanId); } public bool IsLeader(ClanPlayerRef player) { if (player.IsValid && Members.TryGetValue(player.Id, out ClanMember value)) { return value.Role == ClanRole.Leader; } return false; } } internal sealed class ClanRequest { public ClanRequestType Type; public string ClanId = ""; public string ClanName = ""; public string TargetId = ""; public string InviteId = ""; public string Message = ""; public string Description = ""; public string EmblemKey = ""; public ClanRole Role = ClanRole.Member; public long RequestId; public long HudSelectionRevision; public long HudStateRevision; public Vector3 Position = Vector3.zero; public static ClanRequest Simple(ClanRequestType type) { return new ClanRequest { Type = type }; } public void Write(ZPackage package) { //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) ClanDataRules.RequireEnum(Type, "clan request type"); package.Write((int)Type); switch (Type) { case ClanRequestType.RequestSnapshot: case ClanRequestType.CancelApplication: break; case ClanRequestType.LeaveClan: ClanDataRules.WriteClanId(package, ClanId); break; case ClanRequestType.RequestHud: ClanDataRules.WriteClanId(package, ClanId); package.Write(ClanDataRules.RequireOptionalRequestId(HudSelectionRevision, "HUD selection revision")); package.Write(ClanDataRules.RequireOptionalRequestId(HudStateRevision, "HUD state revision")); break; case ClanRequestType.RequestDirectory: package.Write(ClanDataRules.RequireRequestId(RequestId)); break; case ClanRequestType.CreateClan: package.Write(ClanDataRules.RequireRequestId(RequestId)); ClanDataRules.WriteClanName(package, ClanName); ClanDataRules.WriteClanDescription(package, Description); ClanDataRules.WriteClanEmblemKey(package, EmblemKey); break; case ClanRequestType.Invite: case ClanRequestType.AcceptApplication: case ClanRequestType.RejectApplication: case ClanRequestType.KickPlayer: case ClanRequestType.TransferLeadership: ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WritePlayerKey(package, TargetId, "target player key"); break; case ClanRequestType.Apply: ClanDataRules.WriteClanId(package, ClanId); break; case ClanRequestType.AcceptInvite: case ClanRequestType.DeclineInvite: ClanDataRules.WriteText(package, InviteId, 64, "invite id", allowEmpty: false); break; case ClanRequestType.SetRole: ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WritePlayerKey(package, TargetId, "target player key"); package.Write((int)ClanDataRules.RequireAssignableRole(Role, "clan role")); break; case ClanRequestType.UpdateClanProfile: package.Write(ClanDataRules.RequireRequestId(RequestId)); ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteClanName(package, ClanName); ClanDataRules.WriteClanDescription(package, Description); ClanDataRules.WriteClanEmblemKey(package, EmblemKey); break; case ClanRequestType.RenameClan: package.Write(ClanDataRules.RequireRequestId(RequestId)); ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteClanName(package, ClanName); break; case ClanRequestType.SendClanChat: ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteText(package, Message, 400, "clan chat message", allowEmpty: false); break; case ClanRequestType.SendClanPing: case ClanRequestType.UpdatePosition: ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.RequireFiniteVector(Position, "position"); package.Write(Position); break; default: throw new InvalidDataException($"Unknown clan request type ({(int)Type})."); } } public static ClanRequest Read(ZPackage package) { //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) ClanRequest clanRequest = new ClanRequest { Type = ClanDataRules.ReadEnum<ClanRequestType>(package, "clan request type") }; switch (clanRequest.Type) { case ClanRequestType.LeaveClan: clanRequest.ClanId = ClanDataRules.ReadClanId(package); break; case ClanRequestType.RequestHud: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.HudSelectionRevision = ClanDataRules.RequireOptionalRequestId(package.ReadLong(), "HUD selection revision"); clanRequest.HudStateRevision = ClanDataRules.RequireOptionalRequestId(package.ReadLong(), "HUD state revision"); break; case ClanRequestType.RequestDirectory: clanRequest.RequestId = ClanDataRules.RequireRequestId(package.ReadLong()); break; case ClanRequestType.CreateClan: clanRequest.RequestId = ClanDataRules.RequireRequestId(package.ReadLong()); clanRequest.ClanName = ClanDataRules.ReadClanName(package); clanRequest.Description = ClanDataRules.ReadClanDescription(package); clanRequest.EmblemKey = ClanDataRules.ReadClanEmblemKey(package); break; case ClanRequestType.Invite: case ClanRequestType.AcceptApplication: case ClanRequestType.RejectApplication: case ClanRequestType.KickPlayer: case ClanRequestType.TransferLeadership: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.TargetId = ClanDataRules.ReadPlayerKey(package, "target player key"); break; case ClanRequestType.Apply: clanRequest.ClanId = ClanDataRules.ReadClanId(package); break; case ClanRequestType.AcceptInvite: case ClanRequestType.DeclineInvite: clanRequest.InviteId = ClanDataRules.ReadText(package, 64, "invite id", allowEmpty: false); break; case ClanRequestType.SetRole: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.TargetId = ClanDataRules.ReadPlayerKey(package, "target player key"); clanRequest.Role = ClanDataRules.ReadAssignableRole(package, "clan role"); break; case ClanRequestType.UpdateClanProfile: clanRequest.RequestId = ClanDataRules.RequireRequestId(package.ReadLong()); clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.ClanName = ClanDataRules.ReadClanName(package); clanRequest.Description = ClanDataRules.ReadClanDescription(package); clanRequest.EmblemKey = ClanDataRules.ReadClanEmblemKey(package); break; case ClanRequestType.RenameClan: clanRequest.RequestId = ClanDataRules.RequireRequestId(package.ReadLong()); clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.ClanName = ClanDataRules.ReadClanName(package); break; case ClanRequestType.SendClanChat: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.Message = ClanDataRules.ReadText(package, 400, "clan chat message", allowEmpty: false); break; case ClanRequestType.SendClanPing: case ClanRequestType.UpdatePosition: clanRequest.ClanId = ClanDataRules.ReadClanId(package); clanRequest.Position = ClanDataRules.ReadFiniteVector(package, "position"); break; } if (package.GetPos() != package.Size()) { throw new InvalidDataException("Clan request contains unexpected trailing data."); } return clanRequest; } } internal sealed class ClanPlayerSummary { public ClanPlayerRef Player; public ClanRole Role = ClanRole.Member; public bool IsSelf; public bool IsOnline; public string Id => Player.Id; public string Name => Player.Name; public void Write(ZPackage package) { if (!Player.IsValid) { throw new InvalidDataException("roster player identity is invalid."); } Player.Write(package); package.Write((int)ClanDataRules.RequireEnum(Role, "clan role")); package.Write(IsSelf); package.Write(IsOnline); } public static ClanPlayerSummary Read(ZPackage package) { return new ClanPlayerSummary { Player = ClanPlayerRef.Read(package), Role = ClanDataRules.ReadEnum<ClanRole>(package, "clan role"), IsSelf = package.ReadBool(), IsOnline = package.ReadBool() }; } } internal sealed class ClanHudPlayerSummary { public string PlayerId = ""; public bool HasHealth; public float CurrentHealth; public float MaxHealth; public void Write(ZPackage package) { ClanDataRules.WritePlayerKey(package, PlayerId, "HUD player key"); package.Write(HasHealth); if (HasHealth) { ValidateHealth(CurrentHealth, MaxHealth); package.Write(CurrentHealth); package.Write(MaxHealth); } } public static ClanHudPlayerSummary Read(ZPackage package) { ClanHudPlayerSummary clanHudPlayerSummary = new ClanHudPlayerSummary { PlayerId = ClanDataRules.ReadPlayerKey(package, "HUD player key"), HasHealth = package.ReadBool() }; if (clanHudPlayerSummary.HasHealth) { clanHudPlayerSummary.CurrentHealth = package.ReadSingle(); clanHudPlayerSummary.MaxHealth = package.ReadSingle(); ValidateHealth(clanHudPlayerSummary.CurrentHealth, clanHudPlayerSummary.MaxHealth); } return clanHudPlayerSummary; } private static void ValidateHealth(float currentHealth, float maxHealth) { if (float.IsNaN(currentHealth) || float.IsInfinity(currentHealth) || float.IsNaN(maxHealth) || float.IsInfinity(maxHealth) || maxHealth <= 0f || maxHealth > 1000000f || currentHealth < 0f || currentHealth > maxHealth) { throw new InvalidDataException("HUD health is outside the supported range."); } } } internal sealed class ClanHudSnapshot { public string ClanId = ""; public long SelectionRevision; public long StateRevision; public bool ReplaceSelection; public readonly List<ClanHudPlayerSummary> Players = new List<ClanHudPlayerSummary>(); public void Write(ZPackage package) { ClanDataRules.WriteOptionalClanId(package, ClanId, "HUD clan id"); package.Write(ClanDataRules.RequireOptionalRequestId(SelectionRevision, "HUD selection revision")); package.Write(ClanDataRules.RequireOptionalRequestId(StateRevision, "HUD state revision")); package.Write(ReplaceSelection); ClanDataRules.RequireCount(Players.Count, 10, "HUD player"); package.Write(Players.Count); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (ClanHudPlayerSummary player in Players) { if (!hashSet.Add(player.PlayerId)) { throw new InvalidDataException("HUD snapshot contains a duplicate player."); } player.Write(package); } } public static ClanHudSnapshot Read(ZPackage package) { ClanHudSnapshot clanHudSnapshot = new ClanHudSnapshot { ClanId = ClanDataRules.ReadOptionalClanId(package, "HUD clan id"), SelectionRevision = ClanDataRules.RequireOptionalRequestId(package.ReadLong(), "HUD selection revision"), StateRevision = ClanDataRules.RequireOptionalRequestId(package.ReadLong(), "HUD state revision"), ReplaceSelection = package.ReadBool() }; int num = ClanDataRules.ReadCount(package, 10, "HUD player"); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); for (int i = 0; i < num; i++) { ClanHudPlayerSummary clanHudPlayerSummary = ClanHudPlayerSummary.Read(package); if (!hashSet.Add(clanHudPlayerSummary.PlayerId)) { throw new InvalidDataException("HUD snapshot contains a duplicate player."); } clanHudSnapshot.Players.Add(clanHudPlayerSummary); } return clanHudSnapshot; } } internal sealed class ClanPublicSummary { public string ClanId = ""; public string Name = ""; public string Description = ""; public string EmblemKey = ""; public string LeaderName = ""; public int MemberCount; public void Write(ZPackage package) { ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteClanName(package, Name); ClanDataRules.WriteClanDescription(package, Description); ClanDataRules.WriteClanEmblemKey(package, EmblemKey); ClanDataRules.WritePlayerName(package, LeaderName, "clan leader name"); package.Write(ClanDataRules.RequireRange(MemberCount, 0, 512, "clan member count")); } public static ClanPublicSummary Read(ZPackage package) { return new ClanPublicSummary { ClanId = ClanDataRules.ReadClanId(package), Name = ClanDataRules.ReadClanName(package), Description = ClanDataRules.ReadClanDescription(package), EmblemKey = ClanDataRules.ReadClanEmblemKey(package), LeaderName = ClanDataRules.ReadPlayerName(package, "clan leader name"), MemberCount = ClanDataRules.RequireRange(package.ReadInt(), 0, 512, "clan member count") }; } } internal sealed class ClanApplicationSummary { public string PlayerId = ""; public string PlayerName = ""; public void Write(ZPackage package) { ClanDataRules.WritePlayerKey(package, PlayerId, "applicant key"); ClanDataRules.WritePlayerName(package, PlayerName, "applicant name"); } public static ClanApplicationSummary Read(ZPackage package) { return new ClanApplicationSummary { PlayerId = ClanDataRules.ReadPlayerKey(package, "applicant key"), PlayerName = ClanDataRules.ReadPlayerName(package, "applicant name") }; } } internal sealed class ClanInviteSummary { public string InviteId = ""; public string ClanId = ""; public string ClanName = ""; public string FromName = ""; public void Write(ZPackage package) { ClanDataRules.WriteText(package, InviteId, 64, "invite id", allowEmpty: false); ClanDataRules.WriteClanId(package, ClanId); ClanDataRules.WriteClanName(package, ClanName); ClanDataRules.WritePlayerName(package, FromName, "inviter name"); } public static ClanInviteSummary Read(ZPackage package) { return new ClanInviteSummary { InviteId = ClanDataRules.ReadText(package, 64, "invite id", allowEmpty: false), ClanId = ClanDataRules.ReadClanId(package), ClanName = ClanDataRules.ReadClanName(package), FromName = ClanDataRules.ReadPlayerName(package, "inviter name") }; } } internal sealed class ClanClientSnapshot { public string Status = ""; public long ResponseRequestId; public ClanOperationResultCode ResponseResultCode; public string ClanId = ""; public string ClanName = ""; public string ClanDescription = ""; public string ClanEmblemKey = ""; public ClanRole SelfRole = ClanRole.Member; public string PrimaryClanId = ""; public string PrimaryClanName = ""; public ClanRole PrimaryRole = ClanRole.Member; public string GuestClanId = ""; public string GuestClanName = ""; public readonly List<ClanPlayerSummary> Roster = new List<ClanPlayerSummary>(); public readonly List<ClanApplicationSummary> Applications = new List<ClanApplicationSummary>(); public ClanInviteSummary? Invite; public string OwnApplicationClanId = ""; public string OwnApplicationClanName = ""; public bool HasClan => !string.IsNullOrWhiteSpace(ClanId); public bool IsLeader { get { if (HasClan) { return SelfRole == ClanRole.Leader; } return false; } } public bool CanModerate { get { bool flag = HasClan; if (flag) { ClanRole selfRole = SelfRole; bool flag2 = (uint)selfRole <= 1u; flag = flag2; } return flag; } } public bool HasPrimaryClan => !string.IsNullOrWhiteSpace(PrimaryClanId); public bool HasGuestClan => !string.IsNullOrWhiteSpace(GuestClanId); public bool HasAnyClan { get { if (!HasPrimaryClan) { return HasGuestClan; } return true; } } public bool HasOwnApplication => !string.IsNullOrWhiteSpace(OwnApplicationClanId); public bool IsConnectedToClan(string clanId) { if (!string.IsNullOrWhiteSpace(clanId)) { if (!StringComparer.Ordinal.Equals(PrimaryClanId, clanId)) { return StringComparer.Ordinal.Equals(GuestClanId, clanId); } return true; } return false; } public ClanRole? GetRoleForClan(string clanId) { if (!string.IsNullOrWhiteSpace(clanId) && StringComparer.Ordinal.Equals(PrimaryClanId, clanId)) { return PrimaryRole; } if (!string.IsNullOrWhiteSpace(clanId) && StringComparer.Ordinal.Equals(GuestClanId, clanId)) { return ClanRole.Guest; } return null; } public void Write(ZPackage package) { ClanDataRules.RequireCount(Roster.Count, 512, "roster"); ClanDataRules.RequireCount(Applications.Count, 512, "application"); ClanDataRules.WriteText(package, Status, 1024, "snapshot status", allowEmpty: true, allowLineBreaks: true); package.Write(ClanDataRules.RequireOptionalRequestId(ResponseRequestId)); package.Write((int)ClanDataRules.RequireEnum(ResponseResultCode, "operation result")); ClanDataRules.WriteOptionalClanId(package, ClanId); ClanDataRules.WriteOptionalClanName(package, ClanName); ClanDataRules.WriteClanDescription(package, ClanDescription); ClanDataRules.WriteClanEmblemKey(package, ClanEmblemKey); package.Write((int)ClanDataRules.RequireEnum(SelfRole, "self role")); ClanDataRules.WriteOptionalClanId(package, PrimaryClanId, "primary clan id"); ClanDataRules.WriteOptionalClanName(package, PrimaryClanName, "primary clan name"); package.Write((int)ClanDataRules.RequireEnum(PrimaryRole, "primary clan role")); ClanDataRules.WriteOptionalClanId(package, GuestClanId, "guest clan id"); ClanDataRules.WriteOptionalClanName(package, GuestClanName, "guest clan name"); package.Write(Roster.Count); foreach (ClanPlayerSummary item in Roster) { item.Write(package); } package.Write(Applications.Count); foreach (ClanApplicationSummary application in Applications) { application.Write(package); } package.Write(Invite != null); Invite?.Write(package); package.Write(HasOwnApplication); if (HasOwnApplication) { ClanDataRules.WriteClanId(package, OwnApplicationClanId, "own application clan id"); ClanDataRules.WriteClanName(package, OwnApplicationClanName, "own application clan name"); } } public static ClanClientSnapshot Read(ZPackage package) { ClanClientSnapshot clanClientSnapshot = new ClanClientSnapshot { Status = ClanDataRules.ReadText(package, 1024, "snapshot status", allowEmpty: true, allowLineBreaks: true), ResponseRequestId = ClanDataRules.RequireOptionalRequestId(package.ReadLong()), ResponseResultCode = ClanDataRules.ReadEnum<ClanOperationResultCode>(package, "operation result"), ClanId = ClanDataRules.ReadOptionalClanId(package), ClanName = ClanDataRules.ReadOptionalClanName(package), ClanDescription = ClanDataRules.ReadClanDescription(package), ClanEmblemKey = ClanDataRules.ReadClanEmblemKey(package), SelfRole = ClanDataRules.ReadEnum<ClanRole>(package, "self role"), PrimaryClanId = ClanDataRules.ReadOptionalClanId(package, "primary clan id"), PrimaryClanName = ClanDataRules.ReadOptionalClanName(package, "primary clan name"), PrimaryRole = ClanDataRules.ReadEnum<ClanRole>(package, "primary clan role"), GuestClanId = ClanDataRules.ReadOptionalClanId(package, "guest clan id"), GuestClanName = ClanDataRules.ReadOptionalClanName(package, "guest clan name") }; ReadList(package, clanClientSnapshot.Roster, 512, "roster", ClanPlayerSummary.Read); ReadList(package, clanClientSnapshot.Applications, 512, "application", ClanApplicationSummary.Read); if (package.ReadBool()) { clanClientSnapshot.Invite = ClanInviteSummary.Read(package); } if (package.ReadBool()) { clanClientSnapshot.OwnApplicationClanId = ClanDataRules.ReadClanId(package, "own application clan id"); clanClientSnapshot.OwnApplicationClanName = ClanDataRules.ReadClanName(package, "own application clan name"); } return clanClientSnapshot; } public bool ContainsClanPlayer(ClanPlayerRef player) { if (!player.IsValid) { return false; } foreach (ClanPlayerSummary item in Roster) { if (item.IsOnline && StringComparer.Ordinal.Equals(item.Id, player.Id)) { return true; } } return false; } private static void ReadList<T>(ZPackage package, List<T> items, int maximum, string fieldName, Func<ZPackage, T> read) { int num = ClanDataRules.ReadCount(package, maximum, fieldName); for (int i = 0; i < num; i++) { items.Add(read(package)); } } } internal sealed class ClanDirectoryPlayerSummary { public string PlayerId = ""; public string PlayerName = ""; public ClanDirectoryPlayerState State; public string ClanName = ""; public bool IsOnline; public bool IsSelf; public bool CanInvite; public bool CanResolveApplication; public long LastSeenUtcTicks; public void Write(ZPackage package) { ClanDataRules.WritePlayerKey(package, PlayerId, "directory player key"); ClanDataRules.WritePlayerName(package, PlayerName, "directory player name"); package.Write((int)ClanDataRules.RequireEnum(State, "directory player state")); ClanDataRules.WriteOptionalClanName(package, ClanName, "directory clan name"); package.Write(IsOnline); package.Write(IsSelf); package.Write(CanInvite); package.Write(CanResolveApplication); if (LastSeenUtcTicks >= 0) { long lastSeenUtcTicks = LastSeenUtcTicks; DateTime maxValue = DateTime.MaxValue; if (lastSeenUtcTicks <= maxValue.Ticks) { package.Write(LastSeenUtcTicks); return; } } throw new InvalidDataException("directory last-seen timestamp is invalid."); } public static ClanDirectoryPlayerSummary Read(ZPackage package) { ClanDirectoryPlayerSummary clanDirectoryPlayerSummary = new ClanDirectoryPlayerSummary { PlayerId = ClanDataRules.ReadPlayerKey(package, "directory player key"), PlayerName = ClanDataRules.ReadPlayerName(package, "directory player name"), State = ClanDataRules.ReadEnum<ClanDirectoryPlayerState>(package, "directory player state"), ClanName = ClanDataRules.ReadOptionalClanName(package, "directory clan name"), IsOnline = package.ReadBool(), IsSelf = package.ReadBool(), CanInvite = package.ReadBool(), CanResolveApplication = package.ReadBool() }; clanDirectoryPlayerSummary.LastSeenUtcTicks = package.ReadLong(); if (clanDirectoryPlayerSummary.LastSeenUtcTicks >= 0) { long lastSeenUtcTicks = clanDirectoryPlayerSummary.LastSeenUtcTicks; DateTime maxValue = DateTime.MaxValue; if (lastSeenUtcTicks <= maxValue.Ticks) { return clanDirectoryPlayerSummary; } } throw new InvalidDataException("directory last-seen timestamp is invalid."); } } internal sealed class ClanDirectorySnapshot { public long RequestId; public ClanOperationResultCode ResultCode; public string Status = ""; public bool IsTruncated; public readonly List<ClanPublicSummary> PublicClans = new List<ClanPublicSummary>(); public readonly List<ClanDirectoryPlayerSummary> Players = new List<ClanDirectoryPlayerSummary>(); internal void Write(ZPackage package, int publicClanCount, int playerCount) { publicClanCount = ClanDataRules.RequireRange(publicClanCount, 0, Math.Min(PublicClans.Count, 1024), "directory clan count"); playerCount = ClanDataRules.RequireRange(playerCount, 0, Math.Min(Players.Count, 1024), "directory player count"); package.Write(ClanDataRules.RequireRequestId(RequestId)); package.Write((int)ClanDataRules.RequireEnum(ResultCode, "directory result")); ClanDataRules.WriteText(package, Status, 1024, "directory status", allowEmpty: true, allowLineBreaks: true); package.Write(IsTruncated); package.Write(publicClanCount); for (int i = 0; i < publicClanCount; i++) { PublicClans[i].Write(package); } package.Write(playerCount); for (int j = 0; j < playerCount; j++) { Players[j].Write(package); } } public static ClanDirectorySnapshot Read(ZPackage package) { ClanDirectorySnapshot clanDirectorySnapshot = new ClanDirectorySnapshot { RequestId = ClanDataRules.RequireRequestId(package.ReadLong()), ResultCode = ClanDataRules.ReadEnum<ClanOperationResultCode>(package, "directory result"), Status = ClanDataRules.ReadText(package, 1024, "directory status", allowEmpty: true, allowLineBreaks: true), IsTruncated = package.ReadBool() }; int num = ClanDataRules.ReadCount(package, 1024, "directory clan"); for (int i = 0; i < num; i++) { clanDirectorySnapshot.PublicClans.Add(ClanPublicSummary.Read(package)); } int num2 = ClanDataRules.ReadCount(package, 1024, "directory player"); for (int j = 0; j < num2; j++) { clanDirectorySnapshot.Players.Add(ClanDirectoryPlayerSummary.Read(package)); } return clanDirectorySnapshot; } } internal static class ClanLocalizationManager { private static readonly string[] SupportedExtensions = new string[2] { ".yml", ".json" }; private const long MaximumExternalTranslationBytes = 1048576L; private const int MaximumTranslationValueLength = 4096; private static readonly object Sync = new object(); private static readonly HashSet<string> LoggedWarnings = new HashSet<string>(StringComparer.Ordinal); private static BaseUnityPlugin? _plugin; private static ManualLogSource? _logger; private static Dictionary<string, string> _embeddedEnglish = new Dictionary<string, string>(StringComparer.Ordinal); private static Dictionary<string, string> _currentTexts = new Dictionary<string, string>(StringComparer.Ordinal); private static string _currentLanguage = "English"; private static bool _hasLoadedLanguage; private static bool _initialized; internal static event Action? LanguageChanged; internal static void Initialize(BaseUnityPlugin plugin, Harmony harmony) { //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown if (!_initialized) { _plugin = plugin; _logger = ClanPlugin.ClanLogger; _embeddedEnglish = LoadEmbeddedTranslation("English") ?? throw new InvalidOperationException(plugin.Info.Metadata.Name + " has no embedded translations/English.yml or translations/English.json resource."); _currentTexts = new Dictionary<string, string>(_embeddedEnglish, StringComparer.Ordinal); _initialized = true; harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(Localization), "SetupLanguage", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ClanLocalizationManager), "AfterSetupLanguage", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.DeclaredMethod(typeof(FejdStartup), "SetupGui", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(ClanLocalizationManager), "AfterSetupGui", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Localization instance = Localization.instance; if (instance != null) { Reload(SafeSelectedLanguage(instance)); } } } internal static void Dispose() { lock (Sync) { _plugin = null; _logger = null; _embeddedEnglish = new Dictionary<string, string>(StringComparer.Ordinal); _currentTexts = new Dictionary<string, string>(StringComparer.Ordinal); _currentLanguage = "English"; _hasLoadedLanguage = false; LoggedWarnings.Clear(); ClanLocalizationManager.LanguageChanged = null; _initialized = false; } } internal static string GetText(string key) { string text = NormalizeKey(key); lock (Sync) { if (_currentTexts.TryGetValue(text, out string value)) { return value; } if (_embeddedEnglish.TryGetValue(text, out string value2)) { return value2; } } WarnOnce("missing:" + text, "Missing Clan localization key '" + text + "'."); return text; } private static void AfterSetupLanguage(string language) { Reload(language); } private static void AfterSetupGui() { Localization instance = Localization.instance; if (instance != null && NeedsReload(SafeSelectedLanguage(instance))) { Reload(SafeSelectedLanguage(instance)); } } private static bool NeedsReload(string language) { lock (Sync) { return !_hasLoadedLanguage || !_currentLanguage.Equals(language, StringComparison.OrdinalIgnoreCase); } } private static void Reload(string language) { if (!_initialized || (Object)(object)_plugin == (Object)null) { return; } string text = (string.IsNullOrWhiteSpace(language) ? "English" : language.Trim()); Dictionary<string, List<string>> externalFiles = FindExternalTranslations(); Dictionary<string, string> dictionary = new Dictionary<string, string>(_embeddedEnglish, StringComparer.Ordinal); MergeExternalTranslation(dictionary, externalFiles, "English"); if (!text.Equals("English", StringComparison.OrdinalIgnoreCase)) { Dictionary<string, string> dictionary2 = LoadEmbeddedTranslation(text); if (dictionary2 != null) { Merge(dictionary, ValidateTranslation("embedded " + text + " translation", dictionary2)); } MergeExternalTranslation(dictionary, externalFiles, text); } lock (Sync) { _currentTexts = dictionary; _currentLanguage = text; _hasLoadedLanguage = true; } InvokeLanguageChanged(); } private static Dictionary<string, List<string>> FindExternalTranslations() { BaseUnityPlugin plugin = _plugin; if ((Object)(object)plugin == (Object)null) { return new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); } string name = plugin.Info.Metadata.Name; string text = Directory.GetParent(Paths.PluginPath)?.FullName; if (string.IsNullOrWhiteSpace(text) || !Directory.Exists(text)) { WarnOnce("missing-bepinex-root", "Could not locate the BepInEx root while searching for Clan translations."); return new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); } Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); foreach (string item in EnumerateFilesSafely(text)) { string extension = Path.GetExtension(item); if (!SupportedExtensions.Contains<string>(extension, StringComparer.OrdinalIgnoreCase)) { continue; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item); string text2 = name + "."; if (fileNameWithoutExtension.StartsWith(text2, StringComparison.OrdinalIgnoreCase) && fileNameWithoutExtension.Length != text2.Length) { string key = fileNameWithoutExtension.Substring(text2.Length); if (!dictionary.TryGetValue(key, out var value)) { value = (dictionary[key] = new List<string>()); } value.Add(item); } } foreach (List<string> value2 in dictionary.Values) { value2.Sort(CompareExternalCandidates); } return dictionary; } private static IEnumerable<string> EnumerateFilesSafely(string root) { Stack<string> pending = new Stack<string>(); pending.Push(Path.GetFullPath(root)); while (pending.Count > 0) { string directory = pending.Pop(); string[] files; try { files = Directory.GetFiles(directory); } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException) ? 1 : 0) != 0) { WarnOnce("scan-files:" + directory, "Could not scan '" + directory + "' for Clan translations: " + ex.Message); continue; } Array.Sort(files, (IComparer<string>?)StringComparer.OrdinalIgnoreCase); string[] array = files; for (int i = 0; i < array.Length; i++) { yield return array[i]; } string[] directories; try { directories = Directory.GetDirectories(directory); } catch (Exception ex2) when (((ex2 is IOException || ex2 is UnauthorizedAccessException) ? 1 : 0) != 0) { WarnOnce("scan-directories:" + directory, "Could not scan subdirectories of '" + directory + "' for Clan translations: " + ex2.Message); continue; } Array.Sort(directories, (IComparer<string>?)StringComparer.OrdinalIgnoreCase); for (int num = directories.Length - 1; num >= 0; num--) { string text = directories[num]; try { if ((File.GetAttributes(text) & FileAttributes.ReparsePoint) == 0) { pending.Push(text); } } catch (Exception ex3) when (((ex3 is IOException || ex3 is UnauthorizedAccessException) ? 1 : 0) != 0) { WarnOnce("scan-attributes:" + text, "Could not inspect '" + text + "' while searching for Clan translations: " + ex3.Message); } } } } private static int CompareExternalCandidates(string left, string right) { int num = IsUnderDirectory(left, Paths.ConfigPath).CompareTo(IsUnderDirectory(right, Paths.ConfigPath)); if (num != 0) { return -num; } int num2 = ExtensionPriority(left).CompareTo(ExtensionPriority(right)); if (num2 == 0) { return StringComparer.OrdinalIgnoreCase.Compare(Path.GetFullPath(left), Path.GetFullPath(right)); } return num2; } private static int ExtensionPriority(string path) { return (!Path.GetExtension(path).Equals(".yml", StringComparison.OrdinalIgnoreCase)) ? 1 : 0; } private static bool IsUnderDirectory(string path, string directory) { string fullPath = Path.GetFullPath(path); string text = Path.GetFullPath(directory).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); char directorySeparatorChar = Path.DirectorySeparatorChar; string value = text + directorySeparatorChar; return fullPath.StartsWith(value, StringComparison.OrdinalIgnoreCase); } private static void MergeExternalTranslation(IDictionary<string, string> target, IReadOnlyDictionary<string, List<string>> externalFiles, string language) { if (!externalFiles.TryGetValue(language, out List<string> value)) { return; } for (int i = 0; i < value.Count; i++) { string text = value[i]; Dictionary<string, string> dictionary = TryLoadExternalTranslation(text); if (dictionary != null) { Merge(target, dictionary); for (int j = i + 1; j < value.Count; j++) { WarnOnce("duplicate:" + language + ":" + value[j], "Ignoring duplicate Clan " + language + " translation '" + value[j] + "'; using '" + text + "'."); } break; } } } private static Dictionary<string, string>? TryLoadExternalTranslation(string path) { try { long length = new FileInfo(path).Length; if (length <= 0 || length > 1048576) { throw new InvalidDataException($"translation size must be between 1 and {1048576L} bytes"); } Dictionary<string, string> dictionary = ParseTranslation(File.ReadAllText(path, Encoding.UTF8)); if (dictionary == null || dictionary.Count == 0) { throw new InvalidDataException("the translation file is empty"); } dictionary = ValidateTranslation(path, dictionary); if (dictionary.Count == 0) { throw new InvalidDataException("the translation file has no valid Clan keys"); } ManualLogSource? logger = _logger; if (logger != null) { logger.LogInfo((object)("Loaded external Clan translation '" + path + "'.")); } return dictionary; } catch (Exception ex) { WarnOnce("external:" + path + ":" + ex.GetType().FullName + ":" + ex.Message, "Could not load external Clan translation '" + path + "': " + ex.Message + ". Falling back to the next valid source."); return null; } } private static Dictionary<string, string> ValidateTranslation(string source, IReadOnlyDictionary<string, string> translation) { Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); foreach (KeyValuePair<string, string> item in translation) { if (!_embeddedEnglish.TryGetValue(item.Key, out string value)) { WarnOnce("unknown-key:" + source + ":" + item.Key, "Ignored unknown Clan translation key '" + item.Key + "' in '" + source + "'."); } else if (item.Value.Length > 4096) { WarnOnce("long-value:" + source + ":" + item.Key, $"Ignored Clan translation key '{item.Key}' in '{source}' because its value exceeds {4096} characters."); } else if (!HasCompatiblePlaceholders(value, item.Value)) { WarnOnce("placeholders:" + source + ":" + item.Key, "Ignored Clan translation key '" + item.Key + "' in '" + source + "' because its format placeholders do not match English."); } else { dictionary[item.Key] = item.Value; } } return dictionary; } private static bool HasCompatiblePlaceholders(string english, string candidate) { if (TryReadPlaceholderIndexes(english, out HashSet<int> indexes) && TryReadPlaceholderIndexes(candidate, out HashSet<int> indexes2)) { return indexes.SetEquals(indexes2); } return false; } private static bool TryReadPlaceholderIndexes(string template, out HashSet<int> indexes) { indexes = new HashSet<int>(); int num = 0; while (num < template.Length) { switch (template[num]) { case '{': { if (num + 1 < template.Length && template[num + 1] == '{') { num += 2; break; } int num2 = template.IndexOf('}', num + 1); if (num2 < 0 || !int.TryParse(template.Substring(num + 1, num2 - num - 1), NumberStyles.None, CultureInfo.InvariantCulture, out var result)) { return false; } indexes.Add(result); num = num2 + 1; break; } case '}': if (num + 1 >= template.Length || template[num + 1] != '}') { return false; } num += 2; break; default: num++; break; } } return true; } private static Dictionary<string, string>? LoadEmbeddedTranslation(string language) { Assembly executingAssembly = Assembly.GetExecutingAssembly(); string[] supportedExtensions = SupportedExtensions; foreach (string text in supportedExtensions) { string suffix = "translations." + language + text; string text2 = executingAssembly.GetManifestResourceNames().OrderBy<string, string>((string name) => name, StringComparer.Ordinal).FirstOrDefault((string name) => name.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)); if (text2 == null) { continue; } using Stream stream = executingAssembly.GetManifestResourceStream(text2); if (stream == null) { continue; } using StreamReader streamReader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); return ParseTranslation(streamReader.ReadToEnd()); } return null; } private static Dictionary<string, string>? ParseTranslation(string data) { Dictionary<string, string> dictionary = new DeserializerBuilder().IgnoreFields().WithDuplicateKeyChecking().Build() .Deserialize<Dictionary<string, string>>(data); if (dictionary == null) { return null; } Dictionary<string, string> dictionary2 = new Dictionary<string, string>(StringComparer.Ordinal); foreach (KeyValuePair<string, string> item in dictionary) { string text = NormalizeKey(item.Key); if (!string.IsNullOrWhiteSpace(text) && item.Value != null) { dictionary2[text] = item.Value; } } return dictionary2; } private static void Merge(IDictionary<string, string> target, IReadOnlyDictionary<string, string>? source) { if (source == null) { return; } foreach (KeyValuePair<string, string> item in source) { target[item.Key] = item.Value; } } private static string SafeSelectedLanguage(Localization localization) { try { return localization.GetSelectedLanguage(); } catch (Exception ex) { WarnOnce("selected-language", "Could not read the selected language: " + ex.Message + ". Using English."); return _currentLanguage; } } private static string NormalizeKey(string key) { return (key ?? string.Empty).Trim().TrimStart(new char[1] { '$' }); } private static void InvokeLanguageChanged() { Delegate[] array = ClanLocalizationManager.LanguageChanged?.GetInvocationList() ?? Array.Empty<Delegate>(); foreach (Delegate obj in array) { try { ((Action)obj)(); } catch (Exception ex) { ManualLogSource? logger = _logger; if (logger != null) { logger.LogWarning((object)("Clan language-change subscriber failed: " + ex.GetBaseException().Message)); } } } } private static void WarnOnce(string identity, string message) { lock (Sync) { if (!LoggedWarnings.Add(identity)) { return; } } ManualLogSource? logger = _logger; if (logger != null) { logger.LogWarning((object)message); } } } internal static class ClanLocalization { private const string WireStatusPrefix = "@ClanL10n1:"; private const int MaximumWireStatusLength = 1024; private const int MaximumWireArgumentCount = 8; private const int MaximumWireArgumentUtf8Bytes = 256; private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static bool _malformedWireStatusLogged; internal static event Action? LanguageChanged { add { ClanLocalizationManager.LanguageChanged += value; } remove { ClanLocalizationManager.LanguageChanged -= value; } } internal static void Initialize(BaseUnityPlugin plugin, Harmony harmony) { ClanLocalizationManager.Initialize(plugin, harmony); } internal static void Dispose() { ClanLocalizationManager.Dispose(); } internal static string Text(string key) { return ClanLocalizationManager.GetText(key); } internal static string Format(string key, params object[] arguments) { string text = Text(key); try { return string.Format(CultureInfo.CurrentCulture, text, arguments); } catch (FormatException ex) { ClanPlugin.ClanLogger.LogWarning((object)("Invalid format string for Clan localization key '" + key + "': " + ex.Message)); return text; } } internal static string EncodeStatus(string key, params object[] arguments) { string text = (key ?? string.Empty).Trim().TrimStart(new char[1] { '$' }); if (text.Length == 0) { return string.Empty; } if (text.Length > 96 || !IsSafeWireKey(text)) { ClanPlugin.ClanLogger.LogWarning((object)("Refused to encode invalid Clan status localization key '" + text + "'.")); return text; } object[] array = arguments ?? Array.Empty<object>(); int num = Math.Min(array.Length, 8); StringBuilder stringBuilder = new StringBuilder("@ClanL10n1:".Length + text.Length + num * 24); stringBuilder.Append("@ClanL10n1:").Append(text); for (int i = 0; i < num; i++) { string s = LimitUtf8(Convert.ToString(array[i], CultureInfo.InvariantCulture) ?? string.Empty, 256); stringBuilder.Append('|').Append(Convert.ToBase64String(Encoding.UTF8.GetBytes(s))); } if (stringBuilder.Length <= 1024) { return stringBuilder.ToString(); } ClanPlugin.ClanLogger.LogWarning((object)("Clan status localization payload for '" + text + "' exceeded its wire limit.")); return "@ClanL10n1:" + text; } internal static string ResolveStatus(string value) { if (string.IsNullOrEmpty(value) || !value.StartsWith("@ClanL10n1:", StringComparison.Ordinal)) { return value ?? string.Empty; } try { if (value.Length > 1024) { throw new FormatException("status payload exceeds its wire limit"); } string[] array = value.Substring("@ClanL10n1:".Length).Split(new char[1] { '|' }); if (array.Length == 0 || array.Length > 9 || array[0].Length == 0 || array[0].Length > 96 || !IsSafeWireKey(array[0])) { throw new FormatException("status payload contains an invalid localization key or argument count"); } if (array.Length == 1) { return Text(array[0]); } List<object> list = new List<object>(array.Length - 1); for (int i = 1; i < array.Length; i++) { byte[] array2 = Convert.FromBase64String(array[i]); if (array2.Length > 256) { throw new FormatException("status argument exceeds its wire limit"); } list.Add(StrictUtf8.GetString(array2)); } return Format(array[0], list.ToArray()); } catch (Exception ex) when (((ex is FormatException || ex is DecoderFallbackException) ? 1 : 0) != 0) { if (!_malformedWireStatusLogged) { _malformedWireStatusLogged = true; ClanPlugin.ClanLogger.LogWarning((object)("Ignored a malformed localized Clan status payload: " + ex.Message)); } return Text("clan_status_unavailable"); } } internal static string Role(ClanRole role) { return role switch { ClanRole.Leader => Text("clan_role_leader"), ClanRole.Officer => Text("clan_role_officer"), ClanRole.Member => Text("clan_role_member"), ClanRole.Guest => Text("clan_role_guest"), _ => role.ToString(), }; } private static bool IsSafeWireKey(string key) { foreach (char c in key) { if ((c < 'a' || c > 'z') && (c < '0' || c > '9') && c != '_' && c != '-' && c != '.') { return false; } } return true; } private static string LimitUtf8(string value, int maximumBytes) { if (Encoding.UTF8.GetByteCount(value) <= maximumBytes) { return value; } StringBuilder stringBuilder = new StringBuilder(value.Length); int num = 0; int num2; for (num2 = 0; num2 < value.Length; num2++) { int num3 = ((!char.IsHighSurrogate(value[num2]) || num2 + 1 >= value.Length || !char.IsLowSurrogate(value[num2 + 1])) ? 1 : 2); string text = value.Substring(num2, num3); int byteCount = Encoding.UTF8.GetByteCount(text); if (num + byteCount > maximumBytes) { break; } stringBuilder.Append(text); num += byteCount; num2 += num3 - 1; } return stringBuilder.ToString(); } } public enum ClanWardAuthorizationResolution { Unavailable, ResolvedNoAuthorization, Authorized } public enum ClanMembershipResolution { Unavailable, Resolved } public sealed class ClanInfo { public string ClanId { get; } public string Name { get; } public string Description { get; } public string EmblemKey { get; } public string LeaderName { get; } public int MemberCount { get; } internal ClanInfo(string clanId, string name, string description, string emblemKey, string leaderName, int memberCount) { ClanId = clanId; Name = name; Description = description; EmblemKey = emblemKey; LeaderName = leaderName; MemberCount = memberCount; } } public sealed class ClanMemberInfo { public string PlayerKey { get; } public string PlatformId { get; } public long PlayerId { get; } public string Name { get; } public ClanRole Role { get; } public bool IsSelf { get; } public bool IsOnline { get; } internal ClanMemberInfo(ClanPlayerSummary source) { PlayerKey = source.Player.Id; PlatformId = source.Player.PlatformId; PlayerId = source.Player.CharacterPlayerId; Name = source.Player.Name; Role = source.Role; IsSelf = source.IsSelf; IsOnline = source.IsOnline; } } public sealed class ClanLocalSnapshot { private static readonly IReadOnlyList<ClanMemberInfo> NoMembers = new ReadOnlyCollection<ClanMemberInfo>(Array.Empty<ClanMemberInfo>()); public bool IsReady { get; } public bool HasClan => Clan != null; public bool HasPrimaryClan => !string.IsNullOrWhiteSpace(PrimaryClanId); public bool HasGuestClan => !string.IsNullOrWhiteSpace(GuestClanId); public bool HasAnyClan { get { if (!HasPrimaryClan) { return HasGuestClan; } return true; } } public string EffectiveClanId => Clan?.ClanId ?? ""; public string PrimaryClanId { get; } public string PrimaryClanName { get; } public ClanRole? PrimaryRole { get; } public string GuestClanId { get; } public string GuestClanName { get; } public ClanInfo? Clan { get; } public ClanRole? SelfRole { get; } public IReadOnlyList<ClanMemberInfo> Members { get; } internal static ClanLocalSnapshot Empty { get; } = new ClanLocalSnapshot(isReady: false, "", "", null, "", "", null, null, NoMembers); internal ClanLocalSnapshot(bool isReady, string primaryClanId, string primaryClanName, ClanRole? primaryRole, string guestClanId, string guestClanName, ClanInfo? clan, ClanRole? selfRole, IReadOnlyList<ClanMemberInfo> members) { IsReady = isReady; PrimaryClanId = primaryClanId ?? ""; PrimaryClanName = primaryClanName ?? ""; PrimaryRole = primaryRole; GuestClanId = guestClanId ?? ""; GuestClanName = guestClanName ?? ""; Clan = clan; SelfRole = selfRole; Members = members; } } public enum ClanRenameResultCode { Unknown, Success, Unchanged, InvalidName, NotConnected, IdentityUnavailable, NotLeader, ClanChanged, NameAlreadyExists, RateLimited, TimedOut, SessionEnded, Failed } public sealed class ClanRenameResult { public long RequestId { get; } public ClanRenameResultCode Code { get; } public bool Succeeded { get { ClanRenameResultCode code = Code; if ((uint)(code - 1) <= 1u) { return true; } return false; } } public string RequestedName { get; } public string Message { get; } public ClanLocalSnapshot State { get; } internal ClanRenameResult(long requestId, ClanRenameResultCode code, string requestedName, string message, ClanLocalSnapshot state) { RequestId = requestId; Code = code; RequestedName = requestedName; Message = message; State = state; } } public sealed class ClanRenameRequestHandle { private readonly object _gate = new object(); private Action<ClanRenameResult>? _completed; private ClanRenameResult? _result; public long RequestId { get; } public bool IsCompleted { get { lock (_gate) { return _result != null; } } } public ClanRenameResult? Result { get { lock (_gate) { return _result; } } } public event Action<ClanRenameResult>? Completed { add { if (value == null) { return; } ClanRenameResult result; lock (_gate) { result = _result; if (result == null) { _completed = (Action<ClanRenameResult>)Delegate.Combine(_completed, value); return; } } InvokeSubscriber(value, result); } remove { lock (_gate) { _completed = (Action<ClanRenameResult>)Delegate.Remove(_completed, value); } } } internal ClanRenameRequestHandle(long requestId) { RequestId = requestId; } public bool TryGetResult(out ClanRenameResult? result) { lock (_gate) { result = _result; return result != null; } } internal bool Complete(ClanRenameResult result) { Action<ClanRenameResult> completed; lock (_gate) { if (_result != null) { return false; } _result = result; completed = _completed; _completed = null; } if (completed != null) { Delegate[] invocationList = completed.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { InvokeSubscriber((Action<ClanRenameResult>)invocationList[i], result); } } return true; } private static void InvokeSubscriber(Action<ClanRenameResult> subscriber, ClanRenameResult result) { try { subscriber(result); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan API rename completion subscriber failed: {arg}"); } } } public static class ClanApi { private sealed class PendingRename { public ClanRenameRequestHandle Handle { get; } public string RequestedName { get; } public float Deadline { get; } public PendingRename(ClanRenameRequestHandle handle, string requestedName, float deadline) { Handle = handle; RequestedName = requestedName; Deadline = deadline; } } public const int ApiVersion = 4; private const float RenameTimeoutSeconds = 15f; private static readonly object PendingLock = new object(); private static readonly Dictionary<long, PendingRename> PendingRenames = new Dictionary<long, PendingRename>(); private static readonly IReadOnlyList<ClanInfo> NoClans = new ReadOnlyCollection<ClanInfo>(Array.Empty<ClanInfo>()); private static bool _initialized; private static long _registryRevision; public static ClanLocalSnapshot Current { get; private set; } = ClanLocalSnapshot.Empty; public static IReadOnlyList<ClanInfo> KnownClans { get; private set; } = NoClans; public static long RegistryRevision => Interlocked.Read(in _registryRevision); public static event Action<ClanLocalSnapshot>? StateChanged; public static event Action<IReadOnlyList<ClanInfo>>? DirectoryChanged; public static event Action<ClanRenameResult>? RenameCompleted; public static event Action? WardAuthorizationChanged; public static event Action? RegistryChanged; public static ClanWardAuthorizationResolution ResolveWardAuthorization(string platformId, long characterPlayerId, out string clanId, out string clanName) { return ClanRegistry.ResolveWardAuthorization(platformId, characterPlayerId, out clanId, out clanName); } public static ClanMembershipResolution ResolveMemberships(string platformId, long characterPlayerId, out string primaryClanId, out string primaryClanName, out string guestClanId, out string guestClanName) { return ClanRegistry.ResolveMemberships(platformId, characterPlayerId, out primaryClanId, out primaryClanName, out guestClanId, out guestClanName); } internal static void Initialize() { if (!_initialized) { _initialized = true; ClanRpc.SnapshotChanged += OnSnapshotChanged; ClanRpc.DirectoryChanged += OnDirectoryChanged; Current = CreateLocalSnapshot(ClanRpc.CurrentSnapshot); KnownClans = CreateDirectory(ClanRpc.CurrentDirectory); } } internal static void Dispose() { if (_initialized) { ClanRpc.SnapshotChanged -= OnSnapshotChanged; ClanRpc.DirectoryChanged -= OnDirectoryChanged; CompleteAll(ClanRenameResultCode.SessionEnded, "Clan API was shut down."); _initialized = false; Current = ClanLocalSnapshot.Empty; KnownClans = NoClans; ClanApi.StateChanged = null; ClanApi.DirectoryChanged = null; ClanApi.RenameCompleted = null; ClanApi.WardAuthorizationChanged = null; ClanApi.RegistryChanged = null; } } internal static void NotifyRegistryChanged() { Interlocked.Increment(ref _registryRevision); InvokeInvalidationSubscribers(ClanApi.WardAuthorizationChanged, "ward authorization"); InvokeInvalidationSubscribers(ClanApi.RegistryChanged, "registry"); } private static void InvokeInvalidationSubscribers(Action? subscribers, string eventName) { if (subscribers == null) { return; } Delegate[] invocationList = subscribers.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action action = (Action)invocationList[i]; try { action(); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan API {eventName} subscriber failed: {arg}"); } } } internal static void ResetSession() { ClanLocalSnapshot clanLocalSnapshot = CreateLocalSnapshot(ClanRpc.CurrentSnapshot); bool num = !HasSameState(Current, clanLocalSnapshot); Current = clanLocalSnapshot; if (num) { InvokeSubscribers(ClanApi.StateChanged, Current, "state"); } bool num2 = KnownClans.Count != 0; KnownClans = NoClans; if (num2) { InvokeSubscribers<IReadOnlyList<ClanInfo>>(ClanApi.DirectoryChanged, KnownClans, "directory"); } CompleteAll(ClanRenameResultCode.SessionEnded, "The clan session ended."); } internal static void Tick() { List<PendingRename> list = null; float realtimeSinceStartup = Time.realtimeSinceStartup; lock (PendingLock) { foreach (PendingRename value in PendingRenames.Values) { if (realtimeSinceStartup >= value.Deadline) { if (list == null) { list = new List<PendingRename>(); } list.Add(value); } } if (list != null) { foreach (PendingRename item in list) { PendingRenames.Remove(item.Handle.RequestId); } } } if (list == null) { return; } foreach (PendingRename item2 in list) { Complete(item2, ClanRenameResultCode.TimedOut, "The clan rename request timed out."); } } public static ClanInfo? GetClan(string clanId) { if (Current.Clan != null && StringComparer.Ordinal.Equals(Current.Clan.ClanId, clanId)) { return Current.Clan; } return KnownClans.FirstOrDefault((ClanInfo clan) => StringComparer.Ordinal.Equals(clan.ClanId, clanId)); } public static IReadOnlyList<ClanMemberInfo>? GetMembers(string clanId) { if (Current.Clan == null || !StringComparer.Ordinal.Equals(Current.Clan.ClanId, clanId)) { return null; } return Current.Members; } public static long RequestDirectoryRefresh() { return ClanRpc.RequestDirectory(); } public static ClanRenameRequestHandle RequestRenameClan(string newName) { long num = ClanRpc.NextRequestId(); ClanRenameRequestHandle clanRenameRequestHandle = new ClanRenameRequestHandle(num); string text; try { text = ClanDataRules.RequireClanName(newName); } catch (InvalidDataException ex) { CompleteLocal(clanRenameRequestHandle, ClanRenameResultCode.InvalidName, newName ?? "", ex.Message); return clanRenameRequestHandle; } if (!Current.IsReady) { CompleteLocal(clanRenameRequestHandle, ClanRenameResultCode.IdentityUnavailable, text, "Clan identity is not ready."); return clanRenameRequestHandle; } if (Current.Clan == null) { CompleteLocal(clanRenameRequestHandle, ClanRenameResultCode.ClanChanged, text, "The local character does not belong to a clan."); return clanRenameRequestHandle; } PendingRename pendingRename = new PendingRename(clanRenameRequestHandle, text, Time.realtimeSinceStartup + 15f); lock (PendingLock) { PendingRenames.Add(num, pendingRename); } if (!ClanRpc.Send(new ClanRequest { Type = ClanRequestType.RenameClan, RequestId = num, ClanId = Current.Clan.ClanId, ClanName = text })) { bool flag; lock (PendingLock) { flag = PendingRenames.Remove(num); } if (flag) { Complete(pendingRename, ClanRenameResultCode.NotConnected, "Clan server is not connected."); } } return clanRenameRequestHandle; } private static void OnSnapshotChanged(ClanClientSnapshot snapshot) { ClanLocalSnapshot clanLocalSnapshot = CreateLocalSnapshot(snapshot); bool num = !HasSameState(Current, clanLocalSnapshot); Current = clanLocalSnapshot; if (num) { InvokeSubscribers(ClanApi.StateChanged, Current, "state"); } if (snapshot.ResponseRequestId <= 0) { return; } PendingRename value; lock (PendingLock) { if (!PendingRenames.TryGetValue(snapshot.ResponseRequestId, out value)) { return; } PendingRenames.Remove(snapshot.ResponseRequestId); } Complete(value, MapResult(snapshot), snapshot.Status); } private static void OnDirectoryChanged(ClanDirectorySnapshot directory) { IReadOnlyList<ClanInfo> readOnlyList = CreateDirectory(directory); bool num = !HasSameDirectory(KnownClans, readOnlyList); KnownClans = readOnlyList; if (num) { InvokeSubscribers<IReadOnlyList<ClanInfo>>(ClanApi.DirectoryChanged, KnownClans, "directory"); } } private static ClanLocalSnapshot CreateLocalSnapshot(ClanClientSnapshot source) { IReadOnlyList<ClanMemberInfo> members = new ReadOnlyCollection<ClanMemberInfo>(source.Roster.Select((ClanPlayerSummary member) => new ClanMemberInfo(member)).ToList()); ClanInfo clan = null; if (source.HasClan) { string leaderName = source.Roster.FirstOrDefault((ClanPlayerSummary member) => member.Role == ClanRole.Leader)?.Name ?? ""; clan = new ClanInfo(source.ClanId, source.ClanName, source.ClanDescription, source.ClanEmblemKey, leaderName, source.Roster.Count); } return new ClanLocalSnapshot(ClanRpc.IsIdentityReady, source.PrimaryClanId, source.PrimaryClanName, source.HasPrimaryClan ? new ClanRole?(source.PrimaryRole) : ((ClanRole?)null), source.GuestClanId, source.GuestClanName, clan, source.HasClan ? new ClanRole?(source.SelfRole) : ((ClanRole?)null), members); } private static IReadOnlyList<ClanInfo> CreateDirectory(ClanDirectorySnapshot source) { return new ReadOnlyCollection<ClanInfo>(source.PublicClans.Select((ClanPublicSummary clan) => new ClanInfo(clan.ClanId, clan.Name, clan.Description, clan.EmblemKey, clan.LeaderName, clan.MemberCount)).ToList()); } private static ClanRenameResultCode MapResult(ClanClientSnapshot snapshot) { return snapshot.ResponseResultCode switch { ClanOperationResultCode.Success => ClanRenameResultCode.Success, ClanOperationResultCode.Unchanged => ClanRenameResultCode.Unchanged, ClanOperationResultCode.InvalidName => ClanRenameResultCode.InvalidName, ClanOperationResultCode.IdentityUnavailable => ClanRenameResultCode.IdentityUnavailable, ClanOperationResultCode.IdentityRejected => ClanRenameResultCode.IdentityUnavailable, ClanOperationResultCode.Unauthorized => ClanRenameResultCode.NotLeader, ClanOperationResultCode.ClanChanged => ClanRenameResultCode.ClanChanged, ClanOperationResultCode.NameTaken => ClanRenameResultCode.NameAlreadyExists, ClanOperationResultCode.RateLimited => ClanRenameResultCode.RateLimited, ClanOperationResultCode.Unavailable => ClanRenameResultCode.Failed, _ => ClanRenameResultCode.Failed, }; } private static void CompleteLocal(ClanRenameRequestHandle handle, ClanRenameResultCode code, string requestedName, string message) { ClanRenameResult clanRenameResult = new ClanRenameResult(handle.RequestId, code, requestedName, message, Current); if (handle.Complete(clanRenameResult)) { InvokeSubscribers(ClanApi.RenameCompleted, clanRenameResult, "rename completion"); } } private static void Complete(PendingRename pending, ClanRenameResultCode code, string message) { CompleteLocal(pending.Handle, code, pending.RequestedName, message); } private static void CompleteAll(ClanRenameResultCode code, string message) { PendingRename[] array; lock (PendingLock) { array = PendingRenames.Values.ToArray(); PendingRenames.Clear(); } PendingRename[] array2 = array; for (int i = 0; i < array2.Length; i++) { Complete(array2[i], code, message); } } private static bool HasSameState(ClanLocalSnapshot left, ClanLocalSnapshot right) { if (left.IsReady != right.IsReady || !StringComparer.Ordinal.Equals(left.PrimaryClanId, right.PrimaryClanId) || !StringComparer.Ordinal.Equals(left.PrimaryClanName, right.PrimaryClanName) || left.PrimaryRole != right.PrimaryRole || !StringComparer.Ordinal.Equals(left.GuestClanId, right.GuestClanId) || !StringComparer.Ordinal.Equals(left.GuestClanName, right.GuestClanName) || left.SelfRole != right.SelfRole || !HasSameClan(left.Clan, right.Clan) || left.Members.Count != right.Members.Count) { return false; } for (int i = 0; i < left.Members.Count; i++) { ClanMemberInfo clanMemberInfo = left.Members[i]; ClanMemberInfo clanMemberInfo2 = right.Members[i]; if (!StringComparer.Ordinal.Equals(clanMemberInfo.PlayerKey, clanMemberInfo2.PlayerKey) || !StringComparer.Ordinal.Equals(clanMemberInfo.Name, clanMemberInfo2.Name) || clanMemberInfo.Role != clanMemberInfo2.Role || clanMemberInfo.IsSelf != clanMemberInfo2.IsSelf || clanMemberInfo.IsOnline != clanMemberInfo2.IsOnline) { return false; } } return true; } private static bool HasSameDirectory(IReadOnlyList<ClanInfo> left, IReadOnlyList<ClanInfo> right) { if (left.Count != right.Count) { return false; } for (int i = 0; i < left.Count; i++) { if (!HasSameClan(left[i], right[i])) { return false; } } return true; } private static bool HasSameClan(ClanInfo? left, ClanInfo? right) { if (left == right) { return true; } if (left != null && right != null && StringComparer.Ordinal.Equals(left.ClanId, right.ClanId) && StringComparer.Ordinal.Equals(left.Name, right.Name) && StringComparer.Ordinal.Equals(left.Description, right.Description) && StringComparer.Ordinal.Equals(left.EmblemKey, right.EmblemKey) && StringComparer.Ordinal.Equals(left.LeaderName, right.LeaderName)) { return left.MemberCount == right.MemberCount; } return false; } private static void InvokeSubscribers<T>(Action<T>? subscribers, T value, string eventName) { if (subscribers == null) { return; } Delegate[] invocationList = subscribers.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { Action<T> action = (Action<T>)invocationList[i]; try { action(value); } catch (Exception arg) { ClanPlugin.ClanLogger.LogWarning((object)$"Clan API {eventName} subscriber failed: {arg}"); } } } } internal static class ClanIdentity { private static readonly Platform SteamPlatform = new Platform("Steam"); public static ClanPlayerRef FromPeer(ZNetPeer? peer) { bool retryable; return FromPeer(peer, out retryable); } public static ClanPlayerRef FromPeer(ZNetPeer? peer, out bool retryable) { //IL_0083: Unknown result type (might be due to invalid IL or missing references) retryable = true; if (peer == null) { ClanPlayerRef result = ClanPlayerRef.Local(); retryable = !result.IsValid; return result; } if (!peer.IsReady() || ((ZDOID)(ref peer.m_characterID)).IsNone() || (Object)(object)ZNet.instance == (Object)null) { return default(ClanPlayerRef); } if (((ZDOID)(ref peer.m_characterID)).UserID != peer.m_uid) { retryable = false; return default(ClanPlayerRef); } string text = ResolvePeerPlatformId(peer); if (string.IsNullOrWhiteSpace(text)) { return default(ClanPlayerRef); } long playerId = ReadCharacterPlayerId(peer.m_characterID); ClanPlayerRef result2 = new ClanPlayerRef(text, playerId, peer.m_playerName); retryable = !result2.IsValid; return result2; } public static ClanPlayerRef FromPlayer(Player player) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)player == (Object)null)) { return FromCharacterId(((Character)player).GetZDOID()); } return default(ClanPlayerRef); } public unsafe static ClanPlayerRef FromPlayerInfo(PlayerInfo info) { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref info.m_characterID)).IsNone()) { return default(ClanPlayerRef); } return new ClanPlayerRef(((object)(*(PlatformUserID*)(&info.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString(), ReadCharacterPlayerId(info.m_characterID), info.m_name); } public unsafe static bool TryMatchRosterPlayer(PlayerInfo info, IReadOnlyList<ClanPlayerSummary> roster, out ClanPlayerRef player) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) player = default(ClanPlayerRef); ClanPlayerRef exact = FromPlayerInfo(info); if (exact.IsValid && roster.Any((ClanPlayerSummary member) => member.Id == exact.Id)) { player = exact; return true; } string text = ClanDataRules.NormalizePlatformId(((object)(*(PlatformUserID*)(&info.m_userInfo.m_id))/*cast due to .constrained prefix*/).ToString()); if (text.Length == 0) { return false; } ClanPlayerRef clanPlayerRef = default(ClanPlayerRef); int num = 0; foreach (ClanPlayerSummary item in roster) { if (item.Player.IsValid && StringComparer.Ordinal.Equals(item.Player.PlatformId, text) && item.IsOnline) { num++; clanPlayerRef = item.Player; } } if (num == 1) { player = clanPlayerRef; return true; } return false; } public static ZNetPeer? FindPeer(ClanPlayerRef player) { if (!player.IsValid) { return null; } foreach (ZNetPeer connectedPeer in GetConnectedPeers()) { ClanPlayerRef player2; ClanPlayerRef clanPlayerRef = (ClanRpc.TryGetPinnedPeerIdentity(connectedPeer, out player2) ? player2 : FromPeer(connectedPeer)); if (clanPlayerRef.IsValid && StringComparer.Ordinal.Equals(clanPlayerRef.Id, player.Id)) { return connectedPeer; } } return null; } public static IEnumerable<ClanPlayerRef> GetOnlinePlayerRefs() { HashSet<string> yieldedPlayerIds = new HashSet<string>(StringComparer.Ordinal); ClanPlayerRef clanPlayerRef = (((Object)(object)Player.m_localPlayer != (Object)null) ? ClanPlayerRef.Local() : default(ClanPlayerRef)); if (clanPlayerRef.IsValid && yieldedPlayerIds.Add(clanPlayerRef.Id)) { yield return clanPlayerRef; } foreach (ZNetPeer connectedPeer in GetConnectedPeers()) { ClanPlayerRef player; ClanPlayerRef clanPlayerRef2 = (ClanRpc.TryGetPinnedPeerIdentity(connectedPeer, out player) ? player : FromPeer(connectedPeer)); if (clanPlayerRef2.IsValid && yieldedPlayerIds.Add(clanPlayerRef2.Id)) { yield return clanPlayerRef2; } } } private static ClanPlayerRef FromCharacterId(ZDOID characterId) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) if (characterId == ZDOID.None) { return default(ClanPlayerRef); } foreach (PlayerInfo onlinePlayer in GetOnlinePlayers()) { if (onlinePlayer.m_characterID == characterId) { return FromPlayerInfo(onlinePlayer); } } return default(ClanPlayerRef); } private static long ReadCharacterPlayerId(ZDOID characterId) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (((ZDOID)(ref characterId)).IsNone() || ZDOMan.instance == null) { return 0L; } ZDO zDO = ZDOMan.instance.GetZDO(characterId); if (zDO == null) { return 0L; } return zDO.GetLong(ZDOVars.s_playerID, 0L); } private unsafe static string ResolvePeerPlatformId(ZNetPeer peer) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) foreach (PlayerInfo onlinePlayer in GetOnlinePlayers()) { if (onlinePlayer.m_characterID == peer.m_characterID) { PlatformUserID id = onlinePlayer.m_userInfo.m_id; string text = ((object)(*(PlatformUserID*)(&id))/*cast due to .constrained prefix*/).ToString(); if (!string.IsNullOrWhiteSpace(text)) { return text; } } } ISocket socket = peer.m_socket; string text2 = ((socket != null) ? socket.GetHostName() : null) ?? ""; if (string.IsNullOrWhiteSpace(text2) || (Object)(object)ZNet.instance == (Object)null) { return ""; } return ((object)(((int)ZNet.m_onlineBackend == 0) ? new PlatformUserID(SteamPlatform, text2) : new PlatformUserID(text2))/*cast due to .constrained prefix*/).ToString(); } private static IEnumerable<ZNetPeer> GetConnectedPeers() { ZNet instance = ZNet.instance; IEnumerable<ZNetPeer> enumerable = ((instance != null) ? instance.GetConnectedPeers() : null); return enumerable ?? Enumerable.Empty<ZNetPeer>(); } internal static IEnumerable<PlayerInfo> GetOnlinePlayers() { ZNet instance = ZNet.instance; IEnumerable<PlayerInfo> enumerable = ((instance != null) ? instance.GetPlayerList() : null); return enumerable ?? Enumerable.Empty<PlayerInfo>(); } } internal static class ClanRegistry { private readonly struct OperationOutcome { public readonly ClanOperationResultCode Code; public readonly string Status; public OperationOutcome(ClanOperation