using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using Splatform; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Deathboard.Client")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Client companion for Deathboard realm servers. Inert everywhere else.")] [assembly: AssemblyFileVersion("3.0.0.0")] [assembly: AssemblyInformationalVersion("3.0.0")] [assembly: AssemblyProduct("Deathboard.Client")] [assembly: AssemblyTitle("Deathboard.Client")] [assembly: AssemblyVersion("3.0.0.0")] namespace Deathboard { internal struct BoardRow { public string Name; public int Deaths; public bool HasSkills; public float Combat; public float Acro; public float Hand; public float Power; public bool Online; } internal struct BoardRequest { public int Protocol; public byte Kind; public byte Sort; } internal sealed class BoardView { public int Protocol; public byte Kind; public byte Sort; public string Title = ""; public string[] Labels = new string[3] { "Combat", "Agility", "Trades" }; public bool ShowPower = true; public string Formula = ""; public int TotalDeaths; public int WorldDay; public List Rows = new List(); } internal static class BoardPayload { internal static class BoardKindWire { public const byte Deaths = 0; public const byte Power = 1; public const byte Combat = 2; public const byte Acro = 3; public const byte Hand = 4; public const byte Who = 5; public const byte Count = 6; } public const int MaxRows = 200; public const int MaxNameChars = 64; public const byte SortAscending = 0; public const byte SortDescending = 1; public static ZPackage WriteRequest(byte kind, byte sort) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(kind); val.Write((sort != 0) ? ((byte)1) : ((byte)0)); return val; } public static bool TryReadRequest(ZPackage pkg, out BoardRequest request) { request = default(BoardRequest); if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } request.Protocol = pkg.ReadInt(); request.Kind = pkg.ReadByte(); request.Sort = ((pkg.ReadByte() != 0) ? ((byte)1) : ((byte)0)); return true; } catch (Exception) { return false; } } public static ZPackage WriteBoard(byte kind, byte sort, string title, string[] labels, bool showPower, string formula, int totalDeaths, int worldDay, IList rows) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(kind); val.Write((sort != 0) ? ((byte)1) : ((byte)0)); val.Write(title ?? ""); string[] array = ((labels != null && labels.Length == 3) ? labels : new string[3] { "Combat", "Agility", "Trades" }); val.Write(array[0] ?? ""); val.Write(array[1] ?? ""); val.Write(array[2] ?? ""); val.Write(showPower); val.Write(formula ?? ""); val.Write(totalDeaths); val.Write(worldDay); int num = ((rows != null) ? Math.Min(rows.Count, 200) : 0); val.Write(num); for (int i = 0; i < num; i++) { BoardRow boardRow = rows[i]; val.Write(Trim(boardRow.Name)); val.Write(boardRow.Deaths); val.Write(boardRow.HasSkills); val.Write(boardRow.Combat); val.Write(boardRow.Acro); val.Write(boardRow.Hand); val.Write(boardRow.Power); val.Write(boardRow.Online); } return val; } public static bool TryReadBoard(ZPackage pkg, out BoardView view) { view = null; if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } BoardView boardView = new BoardView(); boardView.Protocol = pkg.ReadInt(); boardView.Kind = pkg.ReadByte(); boardView.Sort = ((pkg.ReadByte() != 0) ? ((byte)1) : ((byte)0)); boardView.Title = pkg.ReadString() ?? ""; boardView.Labels = new string[3] { pkg.ReadString() ?? "Combat", pkg.ReadString() ?? "Agility", pkg.ReadString() ?? "Trades" }; boardView.ShowPower = pkg.ReadBool(); boardView.Formula = pkg.ReadString() ?? ""; boardView.TotalDeaths = pkg.ReadInt(); boardView.WorldDay = pkg.ReadInt(); int num = pkg.ReadInt(); if (num < 0 || num > 200) { return false; } boardView.Rows = new List(num); for (int i = 0; i < num; i++) { BoardRow item = new BoardRow { Name = Trim(pkg.ReadString()), Deaths = pkg.ReadInt(), HasSkills = pkg.ReadBool(), Combat = pkg.ReadSingle(), Acro = pkg.ReadSingle(), Hand = pkg.ReadSingle(), Power = pkg.ReadSingle(), Online = pkg.ReadBool() }; boardView.Rows.Add(item); } view = boardView; return true; } catch (Exception) { return false; } } public static string Trim(string name) { if (string.IsNullOrEmpty(name)) { return "?"; } int num = Math.Min(name.Length, 64); char[] array = new char[num]; for (int i = 0; i < num; i++) { char c = name[i]; array[i] = ((c < ' ' || c == '\u007f') ? ' ' : c); } return new string(array); } } internal static class BuildInfo { public const string ServerName = "Deathboard"; public const string ServerGuid = "com.deathboard.valheim"; public const string ClientName = "Deathboard.Client"; public const string ClientGuid = "com.deathboard.valheim.client"; public const string DisplayName = "The Great Hall"; public const string Author = "Zomax"; public const string Version = "3.0.0"; public const int Build = 35; public static string ServerBanner => "Deathboard v3.0.0 build " + 35; public static string ClientBanner => "Deathboard.Client v3.0.0 build " + 35; public static string PublicBanner => "The Great Hall v3.0.0 build " + 35; } internal sealed class FchAppearance { public string Beard = ""; public string Hair = ""; public float SkinR = 1f; public float SkinG = 1f; public float SkinB = 1f; public float HairR = 1f; public float HairG = 1f; public float HairB = 1f; public int ModelIndex; } internal static class FchAppearanceReader { private sealed class Cursor { private readonly byte[] _data; private int _at; public Cursor(byte[] data) { _data = data; } private void Need(int bytes) { if (bytes < 0 || _at + bytes > _data.Length) { throw new FormatException("past the end"); } } public void Skip(int bytes) { Need(bytes); _at += bytes; } public int Int() { Need(4); int result = BitConverter.ToInt32(_data, _at); _at += 4; return result; } public float Float() { Need(4); float result = BitConverter.ToSingle(_data, _at); _at += 4; return result; } private int StringLength() { int num = 0; int num2 = 0; byte b; do { if (num2 >= 35) { throw new FormatException("bad string length"); } Need(1); b = _data[_at++]; num |= (b & 0x7F) << num2; num2 += 7; } while ((b & 0x80) != 0); if (num < 0 || num > 1024) { throw new FormatException("string too long"); } return num; } public string String() { int num = StringLength(); Need(num); string result = Encoding.UTF8.GetString(_data, _at, num); _at += num; return result; } public bool SkipString() { Skip(StringLength()); return true; } } public const int PlayerSaveVersion = 29; public const int InventorySaveVersion = 106; private const int MaxCount = 65536; private const int MaxStringBytes = 1024; private const int MaxModelIndex = 16; private const float MaxColour = 4f; public static FchAppearance Read(byte[] data) { if (data == null || data.Length < 8) { return null; } Cursor cursor = new Cursor(data); try { if (cursor.Int() != 29) { return null; } cursor.Skip(16); if (!cursor.SkipString()) { return null; } cursor.Skip(4); if (!SkipInventory(cursor)) { return null; } if (!SkipStrings(cursor)) { return null; } if (!SkipStringIntPairs(cursor)) { return null; } if (!SkipStrings(cursor)) { return null; } if (!SkipStrings(cursor)) { return null; } if (!SkipStrings(cursor)) { return null; } if (!SkipStrings(cursor)) { return null; } if (!SkipInts(cursor)) { return null; } if (!SkipStringPairs(cursor)) { return null; } FchAppearance fchAppearance = new FchAppearance(); fchAppearance.Beard = cursor.String(); fchAppearance.Hair = cursor.String(); if (fchAppearance.Beard == null || fchAppearance.Hair == null) { return null; } fchAppearance.SkinR = cursor.Float(); fchAppearance.SkinG = cursor.Float(); fchAppearance.SkinB = cursor.Float(); fchAppearance.HairR = cursor.Float(); fchAppearance.HairG = cursor.Float(); fchAppearance.HairB = cursor.Float(); fchAppearance.ModelIndex = cursor.Int(); return Plausible(fchAppearance) ? fchAppearance : null; } catch (Exception) { return null; } } private static bool Plausible(FchAppearance look) { if (look.ModelIndex < 0 || look.ModelIndex > 16) { return false; } float[] array = new float[6] { look.SkinR, look.SkinG, look.SkinB, look.HairR, look.HairG, look.HairB }; foreach (float num in array) { if (float.IsNaN(num) || num < 0f || num > 4f) { return false; } } if (NameLike(look.Beard)) { return NameLike(look.Hair); } return false; } private static bool NameLike(string value) { if (value == null) { return false; } if (value.Length == 0) { return true; } if (value.Length > 64) { return false; } foreach (char c in value) { if (!char.IsLetterOrDigit(c) && c != '_' && c != '-') { return false; } } return true; } private static bool SkipInventory(Cursor c) { if (c.Int() != 106) { return false; } int num = c.Int(); if (num < 0 || num > 65536) { return false; } for (int i = 0; i < num; i++) { if (!c.SkipString()) { return false; } c.Skip(4); c.Skip(4); c.Skip(8); c.Skip(1); c.Skip(4); c.Skip(4); c.Skip(8); if (!c.SkipString()) { return false; } if (!SkipStringPairs(c)) { return false; } c.Skip(4); c.Skip(1); } return true; } private static bool SkipStrings(Cursor c) { int num = c.Int(); if (num < 0 || num > 65536) { return false; } for (int i = 0; i < num; i++) { if (!c.SkipString()) { return false; } } return true; } private static bool SkipInts(Cursor c) { int num = c.Int(); if (num < 0 || num > 65536) { return false; } c.Skip(num * 4); return true; } private static bool SkipStringPairs(Cursor c) { int num = c.Int(); if (num < 0 || num > 65536) { return false; } for (int i = 0; i < num; i++) { if (!c.SkipString()) { return false; } if (!c.SkipString()) { return false; } } return true; } private static bool SkipStringIntPairs(Cursor c) { int num = c.Int(); if (num < 0 || num > 65536) { return false; } for (int i = 0; i < num; i++) { if (!c.SkipString()) { return false; } c.Skip(4); } return true; } } internal static class FchSkillReader { private const int SkillsSaveVersion = 2; private const int MaxSkills = 64; private const float MaxLevel = 100f; private const float MaxAccumulator = 600f; public static Dictionary FindSkillBlock(byte[] data, Func isValidSkillId) { if (data == null || data.Length < 8 || isValidSkillId == null) { return null; } Dictionary result = null; int num = -1; for (int i = 0; i + 8 <= data.Length; i++) { if (BitConverter.ToInt32(data, i) != 2) { continue; } int num2 = BitConverter.ToInt32(data, i + 4); if (num2 < 1 || num2 > 64) { continue; } int num3 = i + 8 + num2 * 12; if (num3 > data.Length) { continue; } Dictionary dictionary = ReadTriples(data, i + 8, num2, isValidSkillId); if (dictionary == null) { continue; } int num4 = num2 * 2; if (num3 + 4 <= data.Length) { int num5 = BitConverter.ToInt32(data, num3); if (num5 >= 0 && num5 <= 65536) { num4++; } } if (num4 > num) { num = num4; result = dictionary; } } return result; } private static Dictionary ReadTriples(byte[] data, int offset, int count, Func isValidSkillId) { Dictionary dictionary = new Dictionary(count); for (int i = 0; i < count; i++) { int num = offset + i * 12; int num2 = BitConverter.ToInt32(data, num); if (!isValidSkillId(num2)) { return null; } if (dictionary.ContainsKey(num2)) { return null; } float num3 = BitConverter.ToSingle(data, num + 4); if (float.IsNaN(num3) || num3 < 0f || num3 > 100f) { return null; } float num4 = BitConverter.ToSingle(data, num + 8); if (float.IsNaN(num4) || num4 < 0f || num4 > 600f) { return null; } dictionary[num2] = num3; } return dictionary; } } internal static class MiniJson { public static void WriteString(StringBuilder sb, string value) { if (value == null) { sb.Append("null"); return; } sb.Append('"'); foreach (char c in value) { switch (c) { case '"': sb.Append("\\\""); continue; case '\\': sb.Append("\\\\"); continue; case '\b': sb.Append("\\b"); continue; case '\f': sb.Append("\\f"); continue; case '\n': sb.Append("\\n"); continue; case '\r': sb.Append("\\r"); continue; case '\t': sb.Append("\\t"); continue; } if (c < ' ') { StringBuilder stringBuilder = sb.Append("\\u"); int num = c; stringBuilder.Append(num.ToString("x4", CultureInfo.InvariantCulture)); } else { sb.Append(c); } } sb.Append('"'); } public static object Parse(string json) { int i = 0; object result = ParseValue(json, ref i); SkipWhitespace(json, ref i); return result; } public static Dictionary AsObject(object value) { return value as Dictionary; } public static List AsArray(object value) { return value as List; } public static string GetString(Dictionary obj, string key, string fallback = null) { if (obj != null && obj.TryGetValue(key, out var value) && value is string result) { return result; } return fallback; } public static int GetInt(Dictionary obj, string key, int fallback = 0) { if (obj != null && obj.TryGetValue(key, out var value)) { if (value is double a) { return (int)Math.Round(a); } if (value is string s && int.TryParse(s, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } } return fallback; } public static float GetFloat(Dictionary obj, string key, float fallback = 0f) { if (obj != null && obj.TryGetValue(key, out var value)) { if (value is double num) { return (float)num; } if (value is string s && float.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { return result; } } return fallback; } public static bool GetBool(Dictionary obj, string key, bool fallback = false) { if (obj != null && obj.TryGetValue(key, out var value)) { if (value is bool) { return (bool)value; } if (value is string value2 && bool.TryParse(value2, out var result)) { return result; } if (value is double value3) { return Math.Abs(value3) > double.Epsilon; } } return fallback; } private static object ParseValue(string s, ref int i) { SkipWhitespace(s, ref i); if (i >= s.Length) { throw new FormatException("Unexpected end of JSON input."); } switch (s[i]) { case '{': return ParseObject(s, ref i); case '[': return ParseArray(s, ref i); case '"': return ParseString(s, ref i); case 't': Expect(s, ref i, "true"); return true; case 'f': Expect(s, ref i, "false"); return false; case 'n': Expect(s, ref i, "null"); return null; default: return ParseNumber(s, ref i); } } private static Dictionary ParseObject(string s, ref int i) { Dictionary dictionary = new Dictionary(StringComparer.Ordinal); i++; SkipWhitespace(s, ref i); if (i < s.Length && s[i] == '}') { i++; return dictionary; } while (true) { SkipWhitespace(s, ref i); if (i >= s.Length || s[i] != '"') { throw new FormatException("Expected an object key at offset " + i + "."); } string key = ParseString(s, ref i); SkipWhitespace(s, ref i); if (i >= s.Length || s[i] != ':') { throw new FormatException("Expected ':' at offset " + i + "."); } i++; dictionary[key] = ParseValue(s, ref i); SkipWhitespace(s, ref i); if (i >= s.Length) { throw new FormatException("Unterminated object."); } if (s[i] != ',') { break; } i++; } if (s[i] == '}') { i++; return dictionary; } throw new FormatException("Expected ',' or '}' at offset " + i + "."); } private static List ParseArray(string s, ref int i) { List list = new List(); i++; SkipWhitespace(s, ref i); if (i < s.Length && s[i] == ']') { i++; return list; } while (true) { list.Add(ParseValue(s, ref i)); SkipWhitespace(s, ref i); if (i >= s.Length) { throw new FormatException("Unterminated array."); } if (s[i] != ',') { break; } i++; } if (s[i] == ']') { i++; return list; } throw new FormatException("Expected ',' or ']' at offset " + i + "."); } private static string ParseString(string s, ref int i) { StringBuilder stringBuilder = new StringBuilder(); i++; while (i < s.Length) { char c = s[i++]; switch (c) { case '"': return stringBuilder.ToString(); default: stringBuilder.Append(c); continue; case '\\': break; } if (i >= s.Length) { break; } char c2 = s[i++]; switch (c2) { case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; case '/': stringBuilder.Append('/'); break; case 'b': stringBuilder.Append('\b'); break; case 'f': stringBuilder.Append('\f'); break; case 'n': stringBuilder.Append('\n'); break; case 'r': stringBuilder.Append('\r'); break; case 't': stringBuilder.Append('\t'); break; case 'u': if (i + 4 > s.Length) { throw new FormatException("Truncated unicode escape."); } stringBuilder.Append((char)ushort.Parse(s.Substring(i, 4), NumberStyles.HexNumber, CultureInfo.InvariantCulture)); i += 4; break; default: throw new FormatException("Unknown escape character '" + c2 + "'."); } } throw new FormatException("Unterminated string."); } private static double ParseNumber(string s, ref int i) { int num = i; while (i < s.Length && "+-0123456789.eE".IndexOf(s[i]) >= 0) { i++; } string text = s.Substring(num, i - num); if (!double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { throw new FormatException("Invalid number '" + text + "'."); } return result; } private static void Expect(string s, ref int i, string literal) { if (i + literal.Length > s.Length || string.CompareOrdinal(s, i, literal, 0, literal.Length) != 0) { throw new FormatException("Expected '" + literal + "' at offset " + i + "."); } i += literal.Length; } private static void SkipWhitespace(string s, ref int i) { while (i < s.Length && char.IsWhiteSpace(s[i])) { i++; } } } internal struct SkillProfile { public bool HasData; public float Combat; public float Acro; public float Hand; public static readonly SkillProfile Unknown; } internal sealed class PowerWeights { public float Combat = 0.5f; public float Acro = 0.25f; public float Hand = 0.25f; public static readonly PowerWeights Default = new PowerWeights(); public static PowerWeights Parse(string value, out string error) { error = null; if (string.IsNullOrEmpty(value)) { return Default; } string[] array = value.Split(new char[3] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 3) { error = "expected three comma-separated numbers, got \"" + value + "\""; return Default; } PowerWeights powerWeights = new PowerWeights(); if (!float.TryParse(array[0], NumberStyles.Float, CultureInfo.InvariantCulture, out powerWeights.Combat) || !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out powerWeights.Acro) || !float.TryParse(array[2], NumberStyles.Float, CultureInfo.InvariantCulture, out powerWeights.Hand)) { error = "could not parse \"" + value + "\" as three numbers"; return Default; } return powerWeights; } } internal static class PowerRating { public const string DefaultFormat = "{name} — {deaths} {P} ({C},{A},{H})"; public const string DefaultLabels = "Combat,Agility,Trades"; public static float Compute(SkillProfile profile, PowerWeights weights) { if (!profile.HasData) { return 0f; } PowerWeights powerWeights = weights ?? PowerWeights.Default; return powerWeights.Combat * profile.Combat + powerWeights.Acro * profile.Acro + powerWeights.Hand * profile.Hand; } public static int Display(float value) { return (int)Math.Round(value, MidpointRounding.AwayFromZero); } private static string Number(float value) { return Display(value).ToString(CultureInfo.InvariantCulture); } public static bool HasVisibleRating(SkillProfile profile, PowerWeights weights) { if (!profile.HasData) { return false; } if (Display(Compute(profile, weights)) == 0 && Display(profile.Combat) == 0 && Display(profile.Acro) == 0) { return Display(profile.Hand) != 0; } return true; } public static string StatsBlock(SkillProfile profile, PowerWeights weights) { if (!HasVisibleRating(profile, weights)) { return ""; } return Number(Compute(profile, weights)) + " (" + Number(profile.Combat) + "," + Number(profile.Acro) + "," + Number(profile.Hand) + ")"; } public static string Line(string format, string name, int deaths, SkillProfile profile, PowerWeights weights, bool showPower) { string text = (string.IsNullOrEmpty(format) ? "{name} — {deaths} {P} ({C},{A},{H})" : format); if (!showPower || !HasVisibleRating(profile, weights)) { text = TrimAtPower(text); } return text.Replace("{name}", name ?? "?").Replace("{deaths}", deaths.ToString(CultureInfo.InvariantCulture)).Replace("{P}", Number(Compute(profile, weights))) .Replace("{C}", Number(profile.Combat)) .Replace("{A}", Number(profile.Acro)) .Replace("{H}", Number(profile.Hand)); } public static string LegendLine(string format, string[] labels, bool showPower) { string text = (string.IsNullOrEmpty(format) ? "{name} — {deaths} {P} ({C},{A},{H})" : format); if (!showPower) { text = TrimAtPower(text); } string[] array = ((labels != null && labels.Length == 3) ? labels : ParseLabels("Combat,Agility,Trades")); return text.Replace("{name}", "NAME").Replace("{deaths}", "DEATHS").Replace("{P}", "P") .Replace("{C}", array[0]) .Replace("{A}", array[1]) .Replace("{H}", array[2]); } public static string FormulaText(PowerWeights weights, string[] labels) { PowerWeights powerWeights = weights ?? PowerWeights.Default; string[] array = ((labels != null && labels.Length == 3) ? labels : ParseLabels("Combat,Agility,Trades")); return "Power Level = " + Percent(powerWeights.Combat) + " " + array[0] + " + " + Percent(powerWeights.Acro) + " " + array[1] + " + " + Percent(powerWeights.Hand) + " " + array[2]; } private static string Percent(float weight) { float num = weight * 100f; return ((Math.Abs(num - (float)Math.Round(num)) < 0.05f) ? ((int)Math.Round(num)).ToString(CultureInfo.InvariantCulture) : num.ToString("0.#", CultureInfo.InvariantCulture)) + "%"; } public static string[] ParseLabels(string value) { string[] array = (value ?? "").Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length != 3) { array = "Combat,Agility,Trades".Split(new char[1] { ',' }); } string[] array2 = new string[3]; for (int i = 0; i < 3; i++) { array2[i] = array[i].Trim(); } return array2; } private static string TrimAtPower(string format) { int num = format.IndexOf("{P}", StringComparison.Ordinal); if (num >= 0) { return format.Substring(0, num).TrimEnd(Array.Empty()); } return format; } public static float BucketSum(IList bucket, IDictionary levels) { if (bucket == null || bucket.Count == 0) { return 0f; } float num = 0f; for (int i = 0; i < bucket.Count; i++) { if (levels != null && levels.TryGetValue(bucket[i], out var value)) { num += value; } } return num; } public static void AppendLine(StringBuilder sb, string line) { if (sb.Length > 0) { sb.Append('\n'); } sb.Append(line); } } internal static class RealmProtocol { internal struct Hello { public int Protocol; public int ClientBuild; public string SteamId; public string CharacterName; } internal struct HelloAck { public int Protocol; public int ServerBuild; public bool RealmMode; public bool KickUnmodded; public bool RemoteBoard; public bool AcceptsSkills; public bool AcceptsCharUploads; public string ServerLabel; } internal struct SkillsReport { public int Protocol; public string SteamId; public string CharacterName; public Dictionary Levels; } internal enum UploadReason : byte { Interval, Death, Logout, Inventory } internal struct CharUpload { public int Protocol; public string SteamId; public string CharacterName; public int Generation; public UploadReason Reason; public byte[] Blob; public byte[] World; } internal struct CharOffer { public int Protocol; public string CharacterName; } internal struct CharPush { public int Protocol; public int Generation; public byte[] Blob; public byte[] World; } internal struct CharApplied { public int Protocol; public int Generation; public bool Ok; public string Note; } public const byte PacketVersion = 6; public const int ProtocolVersion = 6; public const string RpcHello = "Deathboard_Hello"; public const string RpcHelloAck = "Deathboard_HelloAck"; public const string RpcCmd = "Deathboard_Cmd"; public const string RpcSkills = "Deathboard_Skills"; public const string RpcCharOffer = "Deathboard_CharOffer"; public const string RpcCharPush = "Deathboard_CharPush"; public const string RpcCharApplied = "Deathboard_CharApplied"; public const string RpcCharUpload = "Deathboard_CharUpload"; public const string RpcCharReject = "Deathboard_CharReject"; public const string RpcBoardReq = "Deathboard_BoardReq"; public const string RpcBoard = "Deathboard_Board"; public const int MaxReportedSkills = 128; public const int MaxSnapshotBytes = 4194304; public static ZPackage WriteHello(string steamId, string characterName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_001b: 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_003c: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(35); val.Write(steamId ?? ""); val.Write(characterName ?? ""); return val; } public static bool TryReadHello(ZPackage pkg, out Hello hello) { hello = default(Hello); if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } hello.Protocol = pkg.ReadInt(); hello.ClientBuild = pkg.ReadInt(); hello.SteamId = pkg.ReadString(); hello.CharacterName = pkg.ReadString(); return true; } catch (Exception) { return false; } } public static ZPackage WriteHelloAck(bool realmMode, bool kickUnmodded, bool remoteBoard, bool acceptsSkills, bool acceptsCharUploads, string serverLabel) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(35); val.Write(realmMode); val.Write(kickUnmodded); val.Write(remoteBoard); val.Write(acceptsSkills); val.Write(acceptsCharUploads); val.Write(serverLabel ?? ""); return val; } public static bool TryReadHelloAck(ZPackage pkg, out HelloAck ack) { ack = default(HelloAck); if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } ack.Protocol = pkg.ReadInt(); ack.ServerBuild = pkg.ReadInt(); ack.RealmMode = pkg.ReadBool(); ack.KickUnmodded = pkg.ReadBool(); ack.RemoteBoard = pkg.ReadBool(); ack.AcceptsSkills = pkg.ReadBool(); ack.AcceptsCharUploads = pkg.ReadBool(); ack.ServerLabel = pkg.ReadString(); return true; } catch (Exception) { return false; } } public static ZPackage WriteSkills(string steamId, string characterName, Dictionary levels) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(steamId ?? ""); val.Write(characterName ?? ""); int num = ((levels != null) ? Math.Min(levels.Count, 128) : 0); val.Write(num); if (levels != null) { int num2 = 0; foreach (KeyValuePair level in levels) { if (num2 >= num) { break; } val.Write(level.Key); val.Write(level.Value); num2++; } } return val; } public static bool TryReadSkills(ZPackage pkg, out SkillsReport report) { report = default(SkillsReport); if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } report.Protocol = pkg.ReadInt(); report.SteamId = pkg.ReadString(); report.CharacterName = pkg.ReadString(); int num = pkg.ReadInt(); if (num < 0 || num > 128) { return false; } Dictionary dictionary = new Dictionary(num); for (int i = 0; i < num; i++) { int key = pkg.ReadInt(); float num2 = pkg.ReadSingle(); if (float.IsNaN(num2) || num2 < 0f) { num2 = 0f; } if (num2 > 100f) { num2 = 100f; } dictionary[key] = num2; } report.Levels = dictionary; return true; } catch (Exception) { return false; } } public static ZPackage WriteCharUpload(string steamId, string characterName, int generation, UploadReason reason, byte[] blob, byte[] world) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0033: 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) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(steamId ?? ""); val.Write(characterName ?? ""); val.Write(generation); val.Write((byte)reason); val.Write(blob ?? new byte[0]); val.Write(world ?? new byte[0]); return val; } public static bool TryReadCharUpload(ZPackage pkg, out CharUpload upload) { upload = default(CharUpload); if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } upload.Protocol = pkg.ReadInt(); upload.SteamId = pkg.ReadString(); upload.CharacterName = pkg.ReadString(); upload.Generation = pkg.ReadInt(); byte b = pkg.ReadByte(); upload.Reason = (UploadReason)((b <= 3) ? b : 0); byte[] array = pkg.ReadByteArray(); if (array == null || array.Length == 0) { return false; } if (array.Length > 4194304) { return false; } upload.Blob = array; byte[] array2 = pkg.ReadByteArray(); upload.World = ((array2 != null && array2.Length <= 1048576) ? array2 : null); return true; } catch (Exception) { return false; } } public static ZPackage WriteCharOffer(string characterName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_0024: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(characterName ?? ""); return val; } public static bool TryReadCharOffer(ZPackage pkg, out CharOffer offer) { offer = default(CharOffer); if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } offer.Protocol = pkg.ReadInt(); offer.CharacterName = pkg.ReadString(); return true; } catch (Exception) { return false; } } public static ZPackage WriteCharPush(int generation, byte[] blob, byte[] world) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_001a: 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_003d: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(generation); val.Write(blob ?? new byte[0]); val.Write(world ?? new byte[0]); return val; } public static bool TryReadCharPush(ZPackage pkg, out CharPush push) { push = default(CharPush); if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } push.Protocol = pkg.ReadInt(); push.Generation = pkg.ReadInt(); byte[] array = pkg.ReadByteArray(); if (array == null || array.Length == 0) { return false; } if (array.Length > 4194304) { return false; } push.Blob = array; byte[] array2 = pkg.ReadByteArray(); push.World = ((array2 != null && array2.Length <= 1048576) ? array2 : null); return true; } catch (Exception) { return false; } } public static ZPackage WriteCharApplied(int generation, bool ok, string note) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000c: 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_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown ZPackage val = new ZPackage(); val.Write((byte)6); val.Write(6); val.Write(generation); val.Write(ok); val.Write(note ?? ""); return val; } public static bool TryReadCharApplied(ZPackage pkg, out CharApplied applied) { applied = default(CharApplied); if (pkg == null) { return false; } try { pkg.SetPos(0); if (pkg.ReadByte() != 6) { return false; } applied.Protocol = pkg.ReadInt(); applied.Generation = pkg.ReadInt(); applied.Ok = pkg.ReadBool(); applied.Note = pkg.ReadString(); return true; } catch (Exception) { return false; } } } internal static class RealmWorldData { private const byte Version = 1; public const int MaxBytes = 1048576; public static byte[] Capture(PlayerProfile profile) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Expected O, but got Unknown //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) if (profile == null) { return null; } try { profile.SaveLogoutPoint(); if ((Object)(object)Minimap.instance != (Object)null) { Minimap.instance.SaveMapData(); } } catch (Exception) { } try { ZPackage val = new ZPackage(); val.Write((byte)1); WritePoint(val, profile.HaveLogoutPoint(), profile.HaveLogoutPoint() ? profile.GetLogoutPoint() : Vector3.zero); WritePoint(val, profile.HaveDeathPoint(), profile.HaveDeathPoint() ? profile.GetDeathPoint() : Vector3.zero); WritePoint(val, profile.HaveCustomSpawnPoint(), profile.HaveCustomSpawnPoint() ? profile.GetCustomSpawnPoint() : Vector3.zero); val.Write(profile.GetHomePoint()); byte[] mapData = profile.GetMapData(); val.Write(mapData ?? new byte[0]); byte[] array = val.GetArray(); return (array != null && array.Length <= 1048576) ? array : null; } catch (Exception) { return null; } } public static bool Apply(PlayerProfile profile, byte[] blob, out byte[] mapData) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) mapData = null; if (profile == null || blob == null || blob.Length == 0) { return false; } try { ZPackage val = new ZPackage(blob); if (val.ReadByte() != 1) { return false; } if (ReadPoint(val, out var point)) { profile.SetLogoutPoint(point); } else { profile.ClearLoguoutPoint(); } if (ReadPoint(val, out var point2)) { profile.SetDeathPoint(point2); } if (ReadPoint(val, out var point3)) { profile.SetCustomSpawnPoint(point3); } else { profile.ClearCustomSpawnPoint(); } profile.SetHomePoint(val.ReadVector3()); byte[] array = val.ReadByteArray(); if (array != null && array.Length != 0) { profile.SetMapData(array); mapData = array; } return true; } catch (Exception) { return false; } } private static void WritePoint(ZPackage pkg, bool have, Vector3 point) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) pkg.Write(have); pkg.Write(point); } private static bool ReadPoint(ZPackage pkg, out Vector3 point) { //IL_0008: 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) bool result = pkg.ReadBool(); point = pkg.ReadVector3(); return result; } } internal static class SkillBuckets { public static readonly SkillType[] Combat; public static readonly SkillType[] Athletics; public static readonly SkillType[] Hand; public static readonly int[] CombatIds; public static readonly int[] AthleticsIds; public static readonly int[] HandIds; private static int[] ToIds(SkillType[] types) { int[] array = new int[types.Length]; for (int i = 0; i < types.Length; i++) { array[i] = (int)types[i]; } return array; } public static SkillProfile Profile(IDictionary levels) { if (levels == null) { return SkillProfile.Unknown; } return new SkillProfile { HasData = true, Combat = PowerRating.BucketSum(CombatIds, levels), Acro = PowerRating.BucketSum(AthleticsIds, levels), Hand = PowerRating.BucketSum(HandIds, levels) }; } public static bool IsRealSkillId(int id) { if (id == 0 || id == 999) { return false; } return Enum.IsDefined(typeof(SkillType), id); } static SkillBuckets() { SkillType[] array = new SkillType[13]; RuntimeHelpers.InitializeArray(array, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); Combat = (SkillType[])(object)array; SkillType[] array2 = new SkillType[5]; RuntimeHelpers.InitializeArray(array2, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); Athletics = (SkillType[])(object)array2; SkillType[] array3 = new SkillType[6]; RuntimeHelpers.InitializeArray(array3, (RuntimeFieldHandle)/*OpCode not supported: LdMemberToken*/); Hand = (SkillType[])(object)array3; CombatIds = ToIds(Combat); AthleticsIds = ToIds(Athletics); HandIds = ToIds(Hand); } } } namespace Deathboard.Client { internal sealed class ClientConfig { public readonly ConfigEntry Enabled; public readonly ConfigEntry HandshakeTimeoutSeconds; public readonly ConfigEntry LadderKey; public readonly ConfigEntry LadderRefreshSeconds; public readonly ConfigEntry LadderBlocksInput; public readonly ConfigEntry LadderScale; public ClientConfig(ConfigFile config) { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_0104: Expected O, but got Unknown Enabled = config.Bind("General", "Enabled", true, "Master switch. False makes this plugin do nothing at all, on every server."); HandshakeTimeoutSeconds = config.Bind("General", "HandshakeTimeoutSeconds", 8f, new ConfigDescription("How long to wait for a realm server to answer before deciding this is an ordinary server and going inert for the session.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 60f), Array.Empty())); LadderKey = config.Bind("Ladder", "Key", (KeyCode)288, "Key that opens and closes the leaderboard window. Vanilla Valheim binds F5 (console), F2, F9, F11, Ctrl+F1 and Ctrl+F3, and leaves the rest alone - but mods do claim F-keys, PlantEasily takes F6/F8/F10 for one. F7 is clear of all of those. Change it if something on your setup wants F7."); LadderRefreshSeconds = config.Bind("Ladder", "RefreshSeconds", 5f, new ConfigDescription("How often the open window asks the server for a fresh board. The server rate-limits requests to one per second per player regardless of what is set here.", (AcceptableValueBase)(object)new AcceptableValueRange(1f, 60f), Array.Empty())); LadderBlocksInput = config.Bind("Ladder", "BlocksInput", true, "True stops your character moving, looking and attacking while the window is open, the same way the inventory does. False leaves the game fully playable behind the window, but then the mouse is shared between looking around and clicking the tabs."); LadderScale = config.Bind("Ladder", "Scale", 1f, new ConfigDescription("Size multiplier for the window. The window already scales with screen height; this is for taste.", (AcceptableValueBase)(object)new AcceptableValueRange(0.5f, 2.5f), Array.Empty())); } } [HarmonyPatch] internal static class ClientPatches { private const int LeaseFrames = 1; private static int _openOnFrame = -1; private static int _blockedOnFrame = -1; private static bool WindowOpen { get { if (_openOnFrame >= 0) { return Time.frameCount - _openOnFrame <= 1; } return false; } } private static bool InputBlocked { get { if (_blockedOnFrame >= 0) { return Time.frameCount - _blockedOnFrame <= 1; } return false; } } public static void RenewOpen() { _openOnFrame = Time.frameCount; } public static void RenewInputBlock() { _blockedOnFrame = Time.frameCount; } public static void Release() { _openOnFrame = -1; _blockedOnFrame = -1; } [HarmonyPostfix] [HarmonyPatch(typeof(PlayerController), "TakeInput")] private static void PlayerControllerTakeInput(ref bool __result) { if (InputBlocked) { __result = false; } } [HarmonyPostfix] [HarmonyPatch(typeof(Player), "TakeInput")] private static void PlayerTakeInput(Player __instance, ref bool __result) { if (InputBlocked && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer)) { __result = false; } } [HarmonyPostfix] [HarmonyPatch(typeof(GameCamera), "UpdateMouseCapture")] private static void GameCameraUpdateMouseCapture() { if (WindowOpen) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } } } [BepInPlugin("com.deathboard.valheim.client", "The Great Hall", "3.0.0")] public sealed class DeathboardClientPlugin : BaseUnityPlugin { private ClientConfig _config; private RealmSession _session; private LadderClient _ladderClient; private LadderGui _ladder; private SkillReporter _skills; private SnapshotUploader _uploader; private RealmProfile _realmProfile; private RealmConsentGui _consent; private Harmony _harmony; private Harmony _logoutHarmony; private Harmony _spawnGateHarmony; internal static DeathboardClientPlugin Instance { get; private set; } internal static bool IsHeadless => (int)SystemInfo.graphicsDeviceType == 4; internal RealmSession Session => _session; internal SnapshotUploader Uploader => _uploader; internal RealmProfile RealmProfile => _realmProfile; private void Awake() { //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_01ff: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Expected O, but got Unknown //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Expected O, but got Unknown //IL_01b2: Unknown result type (might be due to invalid IL or missing references) //IL_01bc: Expected O, but got Unknown Instance = this; LogInfo("===== " + BuildInfo.ClientBanner + " starting ====="); if (IsHeadless) { LogInfo("Headless build detected; standing down. The server half of Deathboard runs from Deathboard.dll."); return; } _config = new ClientConfig(((BaseUnityPlugin)this).Config); if (!_config.Enabled.Value) { LogInfo("Disabled by config; doing nothing."); return; } _session = new RealmSession(() => _config.HandshakeTimeoutSeconds.Value, LogInfo, LogWarn); _ladderClient = new LadderClient(_session, () => _config.LadderRefreshSeconds.Value, LogWarn); _ladder = new LadderGui(_ladderClient, _session, () => _config.LadderKey.Value, () => _config.LadderScale.Value, () => _config.LadderBlocksInput.Value); _skills = new SkillReporter(_session, LogInfo, LogWarn); _uploader = new SnapshotUploader(_session, LogInfo, LogWarn); _consent = new RealmConsentGui(() => (!((Object)(object)ZNet.instance == (Object)null)) ? ZNet.instance.GetWorldName() : null, LogWarn); _realmProfile = new RealmProfile(_session, _consent, LogInfo, LogWarn); try { _harmony = new Harmony("com.deathboard.valheim.client"); _harmony.PatchAll(typeof(ClientPatches)); } catch (Exception ex) { LogWarn("Could not install the leaderboard input patches: " + ex.Message + ". The window will still open, but the mouse stays captured."); _harmony = null; } try { _logoutHarmony = new Harmony("com.deathboard.valheim.client.logout"); _logoutHarmony.PatchAll(typeof(SnapshotLogoutPatch)); } catch (Exception ex2) { LogWarn("Could not install the logout upload patch: " + ex2.Message + ". Snapshots still upload on the interval and on death."); _logoutHarmony = null; } try { _spawnGateHarmony = new Harmony("com.deathboard.valheim.client.spawngate"); _spawnGateHarmony.PatchAll(typeof(RealmSpawnGate)); } catch (Exception ex3) { LogWarn("Could not install the spawn gate: " + ex3.Message + ". Realm characters still work; a very slow handshake will disconnect instead of waiting."); _spawnGateHarmony = null; } LogInfo("Ready. Will greet each dedicated server once and go inert if it does not answer. Press " + ((object)_config.LadderKey.Value/*cast due to .constrained prefix*/).ToString() + " in game for the leaderboard."); } private void Update() { if (_session == null) { return; } try { _session.Tick(); if (_ladder != null) { _ladder.Tick(); } if (_skills != null) { _skills.Tick(); } if (_realmProfile != null) { _realmProfile.Tick(); } if (_uploader != null) { _uploader.Tick(); } } catch (Exception ex) { LogWarn("Realm tick failed, standing down: " + ex); _session = null; _ladder = null; _ladderClient = null; _skills = null; _uploader = null; _realmProfile = null; if (_consent != null) { _consent.Prompt = null; } ClientPatches.Release(); } } private void OnGUI() { if (_consent != null && _consent.Showing) { try { _consent.Draw(); } catch (Exception ex) { LogWarn("Consent window draw failed: " + ex); } } if (_ladder == null) { return; } try { _ladder.Draw(); } catch (Exception ex2) { LogWarn("Leaderboard draw failed; closing it: " + ex2); _ladder.Close(); } } private void OnDestroy() { Instance = null; ClientPatches.Release(); if (_harmony != null) { _harmony.UnpatchSelf(); _harmony = null; } if (_logoutHarmony != null) { _logoutHarmony.UnpatchSelf(); _logoutHarmony = null; } if (_spawnGateHarmony != null) { _spawnGateHarmony.UnpatchSelf(); _spawnGateHarmony = null; } } private void LogInfo(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("[Deathboard.Client] " + message)); } private void LogWarn(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[Deathboard.Client] " + message)); } } internal sealed class LadderClient { private const float MinRequestIntervalSeconds = 1f; private const float AnswerTimeoutSeconds = 6f; private readonly RealmSession _session; private readonly Func _refreshSeconds; private readonly Action _logWarning; private ZRoutedRpc _registeredOn; private float _lastRequestAt = -999f; private float _lastAnswerAt = -999f; private bool _awaitingAnswer; private bool _wantRefresh; public BoardView View { get; private set; } public byte Kind { get; private set; } public byte Sort { get; private set; } public bool Available { get { if (_session != null) { return _session.BoardAvailable; } return false; } } public float AgeSeconds { get { if (View != null) { return Time.realtimeSinceStartup - _lastAnswerAt; } return -1f; } } public string Status { get { if (_session == null) { return "Deathboard client is disabled."; } switch (_session.State) { case RealmState.Idle: if (!((Object)(object)ZNet.instance == (Object)null)) { return "Connecting..."; } return "Not connected to a server."; case RealmState.Waiting: return "Asking the server for its leaderboard..."; case RealmState.Inert: return "This server does not run Deathboard."; default: if (!Available) { return "This server runs Deathboard, but its leaderboard is switched off."; } if (View != null) { return ""; } if (!_awaitingAnswer || !(Time.realtimeSinceStartup - _lastRequestAt < 6f)) { return "The server did not answer."; } return "Loading..."; } } } public LadderClient(RealmSession session, Func refreshSeconds, Action logWarning) { _session = session; _refreshSeconds = refreshSeconds; _logWarning = logWarning; Kind = 0; Sort = DefaultSort(Kind); } public static byte DefaultSort(byte kind) { return (kind != 0) ? ((byte)1) : ((byte)0); } public void Select(byte kind) { if (kind != Kind) { Kind = kind; Sort = DefaultSort(kind); View = null; _wantRefresh = true; } } public void ToggleSort() { Sort = ((Sort == 0) ? ((byte)1) : ((byte)0)); View = null; _wantRefresh = true; } public void RequestRefresh() { _wantRefresh = true; } public void Tick(bool windowOpen) { EnsureRegistered(); if (!Available) { View = null; _awaitingAnswer = false; _wantRefresh = false; } else if (windowOpen) { float realtimeSinceStartup = Time.realtimeSinceStartup; float num = Mathf.Max(1f, _refreshSeconds()); if ((_wantRefresh || realtimeSinceStartup - _lastRequestAt >= num) && realtimeSinceStartup - _lastRequestAt >= 1f) { Send(); } } } public void OnWindowOpened() { _wantRefresh = true; } private void Send() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null) { try { instance.InvokeRoutedRPC("Deathboard_BoardReq", new object[1] { BoardPayload.WriteRequest(Kind, Sort) }); } catch (Exception ex) { _logWarning("Could not send Deathboard_BoardReq: " + ex.Message); _wantRefresh = false; return; } _lastRequestAt = Time.realtimeSinceStartup; _awaitingAnswer = true; _wantRefresh = false; } } private void EnsureRegistered() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredOn) { instance.Register("Deathboard_Board", (Action)OnBoard); _registeredOn = instance; View = null; _awaitingAnswer = false; } } private void OnBoard(long sender, ZPackage payload) { if (Available && _awaitingAnswer) { if (!BoardPayload.TryReadBoard(payload, out var view)) { _logWarning("Unreadable Deathboard_Board from the server; ignoring."); _awaitingAnswer = false; } else if (view.Protocol != 6) { _logWarning("Board packet speaks protocol " + view.Protocol + ", this client speaks " + 6 + "; ignoring."); _awaitingAnswer = false; } else { Kind = view.Kind; Sort = view.Sort; View = view; _lastAnswerAt = Time.realtimeSinceStartup; _awaitingAnswer = false; } } } } internal sealed class LadderGui { private const int WindowId = 233470721; private readonly LadderClient _client; private readonly RealmSession _session; private readonly Func _key; private readonly Func _scale; private readonly Func _blocksInput; private Rect _rect; private Vector2 _scroll; private bool _placed; private bool _styled; private float _styledForUi; private GUIStyle _window; private GUIStyle _title; private GUIStyle _close; private GUIStyle _tab; private GUIStyle _tabOn; private GUIStyle _sortButton; private GUIStyle _footer; private GUIStyle _column; private GUIStyle _columnRight; private GUIStyle _rowHeader; private GUIStyle _rank; private GUIStyle _statBox; private GUIStyle _statLabel; private GUIStyle _statValue; private GUIStyle _statSub; private GUIStyle _legend; private GUIStyle _deathsZero; private GUIStyle _deathsHigh; private GUIStyle _cellPower; private Texture2D _statTex; private GUIStyle _cell; private GUIStyle _cellOnline; private GUIStyle _cellMine; private GUIStyle _cellNum; private GUIStyle _cellDim; private GUIStyle _rowEven; private GUIStyle _rowOdd; private Texture2D _windowTex; private Texture2D _rowEvenTex; private Texture2D _rowOddTex; private Texture2D _tabTex; private Texture2D _tabOnTex; private static readonly Color Accent = new Color(0.85f, 0.72f, 0.42f); private static readonly Color OnlineColor = new Color(0.55f, 0.85f, 0.55f); private static readonly Color DimColor = new Color(0.62f, 0.6f, 0.56f); private static readonly Color TextColor = new Color(0.9f, 0.88f, 0.84f); public static bool IsOpen { get; private set; } private float Ui => Mathf.Max(1f, (float)Screen.height / 1080f) * Mathf.Clamp(_scale(), 0.5f, 2.5f); public LadderGui(LadderClient client, RealmSession session, Func key, Func scale, Func blocksInput) { _client = client; _session = session; _key = key; _scale = scale; _blocksInput = blocksInput; } public void Tick() { //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Invalid comparison between Unknown and I4 //IL_0069: Unknown result type (might be due to invalid IL or missing references) bool flag = (Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)ZNet.instance != (Object)null; bool flag2 = VanillaUiVisible(); if (IsOpen && (!flag || flag2)) { Close(); } if (IsOpen && ZInput.GetKeyDown((KeyCode)27, false)) { Close(); } KeyCode val = _key(); if ((int)val > 0 && flag && !ChatHasFocus() && ZInput.GetKeyDown(val, false)) { if (IsOpen) { Close(); } else if (!flag2) { Open(); } } if (IsOpen) { ClientPatches.RenewOpen(); if (_blocksInput()) { ClientPatches.RenewInputBlock(); } } _client.Tick(IsOpen); } public void Open() { IsOpen = true; _client.OnWindowOpened(); } public void Close() { IsOpen = false; ClientPatches.Release(); } private static bool VanillaUiVisible() { if (!Menu.IsVisible() && !InventoryGui.IsVisible() && !Console.IsVisible() && !TextInput.IsVisible() && !Minimap.IsOpen() && !StoreGui.IsVisible()) { return Hud.IsPieceSelectionVisible(); } return true; } private static bool ChatHasFocus() { if ((Object)(object)Chat.instance != (Object)null) { return Chat.instance.HasFocus(); } return false; } public void Draw() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) if (IsOpen) { EnsureStyles(); PlaceWindow(); GUI.depth = -100; _rect = GUI.Window(233470721, _rect, new WindowFunction(DrawWindow), GUIContent.none, _window); } } private void PlaceWindow() { //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) float ui = Ui; float num = Mathf.Min((float)Screen.width * 0.9f, 860f * ui); float num2 = Mathf.Min((float)Screen.height * 0.88f, 700f * ui); if (!_placed) { _rect = new Rect(((float)Screen.width - num) * 0.5f, ((float)Screen.height - num2) * 0.5f, num, num2); _placed = true; return; } ((Rect)(ref _rect)).width = num; ((Rect)(ref _rect)).height = num2; ((Rect)(ref _rect)).x = Mathf.Clamp(((Rect)(ref _rect)).x, (0f - num) * 0.5f, (float)Screen.width - num * 0.5f); ((Rect)(ref _rect)).y = Mathf.Clamp(((Rect)(ref _rect)).y, 0f, (float)Screen.height - 40f * ui); } private void DrawWindow(int id) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) float ui = Ui; float num = 12f * ui; GUILayout.BeginArea(new Rect(num, num, ((Rect)(ref _rect)).width - num * 2f, ((Rect)(ref _rect)).height - num * 2f)); BoardView view = _client.View; string[] labels = ((view != null) ? view.Labels : new string[3] { "Combat", "Agility", "Trades" }); DrawTitleBar(view, ui); GUILayout.Space(6f * ui); DrawStatBoxes(view, ui); GUILayout.Space(8f * ui); DrawTabs(labels, ui); GUILayout.Space(6f * ui); DrawColumnHeadings(view, labels, ui); DrawRows(view, ui); GUILayout.FlexibleSpace(); DrawFooter(ui); GUILayout.EndArea(); GUI.DragWindow(new Rect(0f, 0f, ((Rect)(ref _rect)).width - 44f * ui, 34f * ui)); } private void DrawTitleBar(BoardView view, float ui) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label((view != null) ? view.Title : "Deathboard Leaderboard", _title, Array.Empty()); GUILayout.FlexibleSpace(); if (GUILayout.Button("X", _close, (GUILayoutOption[])(object)new GUILayoutOption[2] { GUILayout.Width(26f * ui), GUILayout.Height(24f * ui) })) { Close(); } GUILayout.EndHorizontal(); } private void DrawTabs(string[] labels, float ui) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.Label("Sort by", _statLabel, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(52f * ui) }); GUILayout.Space(4f * ui); Tab("Deaths", 0, ui); Tab("Power", 1, ui); Tab(labels[0], 2, ui); Tab(labels[1], 3, ui); Tab(labels[2], 4, ui); Tab("Online", 5, ui); GUILayout.FlexibleSpace(); if (GUILayout.Button(SortLabel(), _sortButton, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f * ui) })) { _client.ToggleSort(); } GUILayout.EndHorizontal(); } private void Tab(string text, byte kind, float ui) { bool flag = _client.Kind == kind; if (GUILayout.Button(text, flag ? _tabOn : _tab, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(24f * ui) }) && !flag) { _client.Select(kind); } } private string SortLabel() { bool flag = _client.Sort == 0; if (_client.Kind == 0) { if (!flag) { return "most first"; } return "fewest first"; } if (!flag) { return "highest first"; } return "lowest first"; } private void DrawStatBoxes(BoardView view, float ui) { GUILayout.BeginHorizontal(Array.Empty()); StatBox("DAYS SURVIVED", (view == null) ? "-" : Thousands(view.WorldDay), "since the first settlement", ui); GUILayout.Space(10f * ui); StatBox("TOTAL GRIM DEATHS", (view == null) ? "-" : Thousands(view.TotalDeaths), "across every viking on the server", ui); GUILayout.EndHorizontal(); } private void StatBox(string caption, string value, string sub, float ui) { GUILayout.BeginVertical(_statBox, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); GUILayout.Label(caption, _statLabel, Array.Empty()); GUILayout.Space(2f * ui); GUILayout.Label(value, _statValue, Array.Empty()); GUILayout.Label(sub, _statSub, Array.Empty()); GUILayout.EndVertical(); } private static string Thousands(int value) { return value.ToString("N0", CultureInfo.InvariantCulture); } private void DrawColumnHeadings(BoardView view, string[] labels, float ui) { bool flag = view?.ShowPower ?? true; GUILayout.BeginHorizontal(_rowHeader, Array.Empty()); GUILayout.Label("#", _column, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(34f * ui) }); GUILayout.Label("PLAYER NAME", _column, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (flag) { GUILayout.Label("POWER LEVEL", _columnRight, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(112f * ui) }); } GUILayout.Label(labels[0].ToUpperInvariant(), _columnRight, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f * ui) }); GUILayout.Label(labels[1].ToUpperInvariant(), _columnRight, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f * ui) }); GUILayout.Label(labels[2].ToUpperInvariant(), _columnRight, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f * ui) }); GUILayout.Label("DEATHS", _columnRight, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f * ui) }); GUILayout.Space(GUI.skin.verticalScrollbar.fixedWidth + 4f); GUILayout.EndHorizontal(); } private void DrawRows(BoardView view, float ui) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //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) _scroll = GUILayout.BeginScrollView(_scroll, false, true, Array.Empty()); if (view == null || view.Rows.Count == 0) { string text = ((view == null) ? _client.Status : ((_client.Kind == 5) ? "Nobody is online." : "No vikings recorded yet.")); GUILayout.Space(12f * ui); GUILayout.Label(string.IsNullOrEmpty(text) ? "Loading..." : text, _cellDim, Array.Empty()); GUILayout.EndScrollView(); return; } string text2 = LocalCharacterName(); bool showPower = view.ShowPower; for (int i = 0; i < view.Rows.Count; i++) { BoardRow row = view.Rows[i]; bool flag = HasVisibleRating(row); GUILayout.BeginHorizontal((i % 2 == 0) ? _rowEven : _rowOdd, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(22f * ui) }); GUILayout.Label((i + 1).ToString(CultureInfo.InvariantCulture), _rank, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(34f * ui) }); bool flag2 = !string.IsNullOrEmpty(text2) && string.Equals(text2, row.Name, StringComparison.Ordinal); GUILayout.Label((row.Online ? "* " : " ") + row.Name + (flag2 ? " (you)" : ""), flag2 ? _cellMine : (row.Online ? _cellOnline : _cell), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) }); if (showPower) { GUILayout.Label(flag ? Num(row.Power) : "-", flag ? _cellPower : _cellDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(112f * ui) }); } GUILayout.Label(flag ? Num(row.Combat) : "-", flag ? _cellNum : _cellDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f * ui) }); GUILayout.Label(flag ? Num(row.Acro) : "-", flag ? _cellNum : _cellDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f * ui) }); GUILayout.Label(flag ? Num(row.Hand) : "-", flag ? _cellNum : _cellDim, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(72f * ui) }); GUIStyle val = ((row.Deaths == 0) ? _deathsZero : ((row.Deaths >= 10) ? _deathsHigh : _cellNum)); GUILayout.Label(row.Deaths.ToString(), val, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Width(70f * ui) }); GUILayout.EndHorizontal(); } GUILayout.EndScrollView(); } private void DrawFooter(float ui) { //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) GUILayout.Space(4f * ui); BoardView view = _client.View; if (view != null && !string.IsNullOrEmpty(view.Formula)) { GUILayout.BeginHorizontal(Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.Label(view.Formula, _legend, Array.Empty()); GUILayout.EndHorizontal(); GUILayout.Space(2f * ui); } string status = _client.Status; float ageSeconds = _client.AgeSeconds; string text = BuildInfo.PublicBanner; if (_session != null && !string.IsNullOrEmpty(_session.Server.ServerLabel)) { text = text + " | " + _session.Server.ServerLabel; } text += " by Zomax"; GUILayout.BeginHorizontal(Array.Empty()); if (!string.IsNullOrEmpty(status)) { GUILayout.Label(status, _footer, Array.Empty()); } else if (ageSeconds >= 0f) { GUILayout.Label("updated " + Mathf.Max(0, Mathf.RoundToInt(ageSeconds)) + "s ago | " + ((object)_key()/*cast due to .constrained prefix*/).ToString() + " closes", _footer, Array.Empty()); } GUILayout.FlexibleSpace(); GUILayout.Label(text, _footer, Array.Empty()); GUILayout.EndHorizontal(); } private static string Num(float value) { return PowerRating.Display(value).ToString(CultureInfo.InvariantCulture); } private static bool HasVisibleRating(BoardRow row) { if (!row.HasSkills) { return false; } if (PowerRating.Display(row.Power) == 0 && PowerRating.Display(row.Combat) == 0 && PowerRating.Display(row.Acro) == 0) { return PowerRating.Display(row.Hand) != 0; } return true; } private static string LocalCharacterName() { try { Game instance = Game.instance; if ((Object)(object)instance == (Object)null) { return null; } PlayerProfile playerProfile = instance.GetPlayerProfile(); return (playerProfile == null) ? null : playerProfile.GetName(); } catch (Exception) { return null; } } private void EnsureStyles() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Expected O, but got Unknown //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Expected O, but got Unknown //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Expected O, but got Unknown //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Expected O, but got Unknown //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected O, but got Unknown //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Expected O, but got Unknown //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_025b: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Expected O, but got Unknown //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Expected O, but got Unknown //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Expected O, but got Unknown //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_02f7: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Expected O, but got Unknown //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_032b: Expected O, but got Unknown //IL_0361: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Expected O, but got Unknown //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0380: Expected O, but got Unknown //IL_0397: Unknown result type (might be due to invalid IL or missing references) //IL_03ac: Unknown result type (might be due to invalid IL or missing references) //IL_03b6: Expected O, but got Unknown //IL_03e7: Unknown result type (might be due to invalid IL or missing references) //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_0406: Expected O, but got Unknown //IL_041d: Unknown result type (might be due to invalid IL or missing references) //IL_0432: Unknown result type (might be due to invalid IL or missing references) //IL_043c: Expected O, but got Unknown //IL_045f: Unknown result type (might be due to invalid IL or missing references) //IL_0474: Unknown result type (might be due to invalid IL or missing references) //IL_047e: Expected O, but got Unknown //IL_04bc: Unknown result type (might be due to invalid IL or missing references) //IL_04d1: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Expected O, but got Unknown //IL_04fe: Unknown result type (might be due to invalid IL or missing references) //IL_051b: Unknown result type (might be due to invalid IL or missing references) //IL_0525: Expected O, but got Unknown //IL_0530: Unknown result type (might be due to invalid IL or missing references) //IL_0541: Unknown result type (might be due to invalid IL or missing references) //IL_054b: Expected O, but got Unknown //IL_0556: Unknown result type (might be due to invalid IL or missing references) //IL_0573: Unknown result type (might be due to invalid IL or missing references) //IL_057d: Expected O, but got Unknown //IL_059c: Unknown result type (might be due to invalid IL or missing references) //IL_05a6: Expected O, but got Unknown //IL_05b1: Unknown result type (might be due to invalid IL or missing references) //IL_05c2: Unknown result type (might be due to invalid IL or missing references) //IL_05cc: Expected O, but got Unknown //IL_05ef: Unknown result type (might be due to invalid IL or missing references) //IL_0600: Unknown result type (might be due to invalid IL or missing references) //IL_060a: Expected O, but got Unknown //IL_0639: Unknown result type (might be due to invalid IL or missing references) //IL_0643: Expected O, but got Unknown //IL_0666: Unknown result type (might be due to invalid IL or missing references) //IL_0671: Unknown result type (might be due to invalid IL or missing references) //IL_067b: Expected O, but got Unknown //IL_069b: Unknown result type (might be due to invalid IL or missing references) //IL_06a5: Expected O, but got Unknown //IL_06ac: Unknown result type (might be due to invalid IL or missing references) //IL_06b6: Expected O, but got Unknown //IL_06d3: Unknown result type (might be due to invalid IL or missing references) //IL_06dd: Expected O, but got Unknown //IL_06f5: Unknown result type (might be due to invalid IL or missing references) //IL_06ff: Expected O, but got Unknown //IL_070a: Unknown result type (might be due to invalid IL or missing references) //IL_071b: Unknown result type (might be due to invalid IL or missing references) //IL_0725: Expected O, but got Unknown //IL_073f: Unknown result type (might be due to invalid IL or missing references) //IL_0750: Unknown result type (might be due to invalid IL or missing references) //IL_075a: Expected O, but got Unknown //IL_02dd: Unknown result type (might be due to invalid IL or missing references) float ui = Ui; if (!_styled || !Mathf.Approximately(_styledForUi, ui)) { _styled = true; _styledForUi = ui; _windowTex = _windowTex ?? Solid(new Color(0.08f, 0.07f, 0.06f, 0.94f)); _rowEvenTex = _rowEvenTex ?? Solid(new Color(1f, 1f, 1f, 0.03f)); _rowOddTex = _rowOddTex ?? Solid(new Color(0f, 0f, 0f, 0.12f)); _tabTex = _tabTex ?? Solid(new Color(1f, 1f, 1f, 0.06f)); _tabOnTex = _tabOnTex ?? Solid(new Color(0.85f, 0.72f, 0.42f, 0.22f)); int fontSize = Mathf.RoundToInt(15f * ui); int fontSize2 = Mathf.RoundToInt(20f * ui); int fontSize3 = Mathf.RoundToInt(12f * ui); _window = new GUIStyle(GUI.skin.box); _window.normal.background = _windowTex; _window.border = new RectOffset(2, 2, 2, 2); _window.padding = new RectOffset(0, 0, 0, 0); _title = new GUIStyle(GUI.skin.label); _title.fontSize = fontSize2; _title.fontStyle = (FontStyle)1; _title.normal.textColor = Accent; _close = new GUIStyle(GUI.skin.button); _close.fontSize = fontSize; _tab = new GUIStyle(GUI.skin.button); _tab.fontSize = fontSize; _tab.normal.background = _tabTex; _tab.normal.textColor = DimColor; _tab.padding = new RectOffset(Mathf.RoundToInt(10f * ui), Mathf.RoundToInt(10f * ui), 2, 2); _tabOn = new GUIStyle(_tab); _tabOn.normal.background = _tabOnTex; _tabOn.normal.textColor = Accent; _tabOn.fontStyle = (FontStyle)1; _sortButton = new GUIStyle(_tab); _statTex = _statTex ?? Solid(new Color(1f, 1f, 1f, 0.045f)); _statBox = new GUIStyle(GUI.skin.box); _statBox.normal.background = _statTex; _statBox.border = new RectOffset(2, 2, 2, 2); _statBox.padding = new RectOffset(Mathf.RoundToInt(14f * ui), Mathf.RoundToInt(14f * ui), Mathf.RoundToInt(10f * ui), Mathf.RoundToInt(10f * ui)); _statLabel = new GUIStyle(GUI.skin.label); _statLabel.fontSize = fontSize3; _statLabel.normal.textColor = Accent; _statValue = new GUIStyle(GUI.skin.label); _statValue.fontSize = Mathf.RoundToInt(30f * ui); _statValue.normal.textColor = new Color(0.96f, 0.93f, 0.86f); _statSub = new GUIStyle(GUI.skin.label); _statSub.fontSize = fontSize3; _statSub.normal.textColor = DimColor; _rank = new GUIStyle(GUI.skin.label); _rank.fontSize = fontSize; _rank.alignment = (TextAnchor)4; _rank.normal.textColor = Accent; _legend = new GUIStyle(GUI.skin.label); _legend.fontSize = fontSize3; _legend.fontStyle = (FontStyle)2; _legend.alignment = (TextAnchor)5; _legend.normal.textColor = new Color(0.72f, 0.66f, 0.52f); _cell = new GUIStyle(GUI.skin.label); _cell.fontSize = fontSize; _cell.richText = false; _cell.normal.textColor = TextColor; _cell.alignment = (TextAnchor)3; _cellOnline = new GUIStyle(_cell); _cellOnline.normal.textColor = OnlineColor; _cellMine = new GUIStyle(_cell); _cellMine.normal.textColor = Accent; _cellMine.fontStyle = (FontStyle)1; _cellNum = new GUIStyle(_cell); _cellNum.fontStyle = (FontStyle)1; _cellNum.alignment = (TextAnchor)5; _cellDim = new GUIStyle(_cell); _cellDim.normal.textColor = DimColor; _column = new GUIStyle(_cell); _column.fontSize = fontSize3; _column.fontStyle = (FontStyle)1; _column.normal.textColor = DimColor; _columnRight = new GUIStyle(_column); _columnRight.alignment = (TextAnchor)5; _columnRight.wordWrap = false; _column.wordWrap = false; _footer = new GUIStyle(GUI.skin.label); _footer.fontSize = fontSize3; _footer.richText = false; _footer.normal.textColor = DimColor; _rowEven = new GUIStyle(); _rowEven.normal.background = _rowEvenTex; _rowEven.padding = new RectOffset(4, 4, 1, 1); _rowOdd = new GUIStyle(_rowEven); _rowOdd.normal.background = _rowOddTex; _rowHeader = new GUIStyle(_rowEven); _rowHeader.normal.background = null; _deathsZero = new GUIStyle(_cellNum); _deathsZero.normal.textColor = Accent; _deathsHigh = new GUIStyle(_cellNum); _deathsHigh.normal.textColor = new Color(0.85f, 0.42f, 0.38f); _cellPower = new GUIStyle(_cellNum); _cellPower.fontSize = Mathf.RoundToInt(17f * ui); _cellPower.wordWrap = false; } } private static Texture2D Solid(Color color) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown Texture2D val = new Texture2D(1, 1, (TextureFormat)4, false); val.SetPixel(0, 0, color); val.Apply(); ((Object)val).hideFlags = (HideFlags)61; return val; } } internal sealed class RealmConsentGui { private const int WindowId = 56097; private const char Separator = '\t'; private readonly Func _worldName; private readonly Action _logWarning; private GUIStyle _window; private GUIStyle _title; private GUIStyle _body; private GUIStyle _button; private GUIStyle _toggle; private bool _stylesBuilt; private Rect _rect; public string Prompt; public bool IsFresh; public string CharacterName = ""; public bool MirrorChoice; public Action OnAccept; public Action OnDecline; public bool Showing => !string.IsNullOrEmpty(Prompt); private static string AcceptedPath => Path.Combine(Path.Combine(Paths.ConfigPath, "Deathboard.Client"), "accepted-realms.txt"); public RealmConsentGui(Func worldName, Action logWarning) { _worldName = worldName; _logWarning = logWarning; } public void Draw() { //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Expected O, but got Unknown //IL_0092: Unknown result type (might be due to invalid IL or missing references) if (Showing) { BuildStyles(); ClientPatches.RenewOpen(); ClientPatches.RenewInputBlock(); float num = Mathf.Min(760f, (float)Screen.width * 0.8f); float num2 = Mathf.Min(500f, (float)Screen.height * 0.78f); _rect = new Rect(((float)Screen.width - num) * 0.5f, ((float)Screen.height - num2) * 0.5f, num, num2); GUI.ModalWindow(56097, _rect, new WindowFunction(DrawWindow), GUIContent.none, _window); } } private void DrawWindow(int id) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) GUILayout.BeginArea(new Rect(22f, 22f, ((Rect)(ref _rect)).width - 44f, ((Rect)(ref _rect)).height - 44f)); GUILayout.Label(IsFresh ? "A new character for this realm" : "This realm owns your character", _title, Array.Empty()); GUILayout.Space(12f); GUILayout.Label(Prompt, _body, Array.Empty()); GUILayout.Space(16f); string text = (string.IsNullOrEmpty(CharacterName) ? "this character" : CharacterName); MirrorChoice = GUILayout.Toggle(MirrorChoice, " Keep the character-select slot for " + text + " updated with my realm progress", _toggle, Array.Empty()); GUILayout.Label("Off by default, and asked separately for every character. Turning it on REPLACES your local " + text + " with your realm character when you log out, so the slot shows the right gear instead of staying as it is now. Leave it off if that local character matters to you - there is no undo.", _body, Array.Empty()); GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(Array.Empty()); if (GUILayout.Button(IsFresh ? "Start fresh here" : "Load my realm character", _button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) })) { Action onAccept = OnAccept; Prompt = null; ClientPatches.Release(); onAccept?.Invoke(); } GUILayout.Space(12f); if (GUILayout.Button("Disconnect", _button, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(38f) })) { Action onDecline = OnDecline; Prompt = null; ClientPatches.Release(); onDecline?.Invoke(); } GUILayout.EndHorizontal(); GUILayout.EndArea(); } private void BuildStyles() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown if (!_stylesBuilt) { _window = new GUIStyle(GUI.skin.box); _title = new GUIStyle(GUI.skin.label) { fontSize = 22, fontStyle = (FontStyle)1, wordWrap = true }; _body = new GUIStyle(GUI.skin.label) { fontSize = 15, wordWrap = true }; _button = new GUIStyle(GUI.skin.button) { fontSize = 15 }; _toggle = new GUIStyle(GUI.skin.toggle) { fontSize = 15, wordWrap = true }; _stylesBuilt = true; } } public bool AlreadyAccepted(string steamId, string characterName, out bool mirror) { mirror = false; string text = KeyFor(steamId, characterName); if (text == null) { return false; } try { if (!File.Exists(AcceptedPath)) { return false; } string[] array = File.ReadAllLines(AcceptedPath); for (int i = 0; i < array.Length; i++) { string text2 = array[i].TrimEnd(Array.Empty()); if (text2.Length != 0) { string[] array2 = text2.Split(new char[1] { '\t' }); if (array2.Length >= 3 && string.Equals(array2[0] + "\t" + array2[1] + "\t" + array2[2], text, StringComparison.Ordinal)) { mirror = array2.Length >= 4 && string.Equals(array2[3].Trim(), "mirror", StringComparison.Ordinal); return true; } } } } catch (Exception ex) { _logWarning("Could not read the accepted-realms list: " + ex.Message + ". Asking again."); } return false; } public void Remember(string steamId, string characterName, bool mirror) { string text = KeyFor(steamId, characterName); if (text == null) { return; } try { string acceptedPath = AcceptedPath; string directoryName = Path.GetDirectoryName(acceptedPath); if (!string.IsNullOrEmpty(directoryName) && !Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } List list = new List(); if (File.Exists(acceptedPath)) { string[] array = File.ReadAllLines(acceptedPath); for (int i = 0; i < array.Length; i++) { string text2 = array[i].TrimEnd(Array.Empty()); if (text2.Length != 0) { string[] array2 = text2.Split(new char[1] { '\t' }); if (array2.Length >= 3 && !string.Equals(array2[0] + "\t" + array2[1] + "\t" + array2[2], text, StringComparison.Ordinal)) { list.Add(text2); } } } } list.Add(mirror ? (text + "\tmirror") : text); File.WriteAllText(acceptedPath, string.Join(Environment.NewLine, list.ToArray()) + Environment.NewLine); } catch (Exception ex) { _logWarning("Could not remember your choices for this realm: " + ex.Message); } } private string KeyFor(string steamId, string characterName) { string text = ((_worldName != null) ? _worldName() : null); if (string.IsNullOrEmpty(text)) { return null; } if (string.IsNullOrEmpty(steamId) || string.IsNullOrEmpty(characterName)) { return null; } return text + "\t" + steamId + "\t" + characterName; } } internal sealed class RealmProfile { private const float HandoverTimeoutSeconds = 20f; internal static RealmProfile Instance; private static readonly FieldInfo ProfileField = AccessTools.Field(typeof(Game), "m_playerProfile"); private static readonly FieldInfo PlayerDataField = AccessTools.Field(typeof(PlayerProfile), "m_playerData"); private static readonly FieldInfo QueuedIntroField = AccessTools.Field(typeof(Game), "m_queuedIntro"); private static readonly MethodInfo MinimapSetMapData = AccessTools.Method(typeof(Minimap), "SetMapData", new Type[1] { typeof(byte[]) }, (Type[])null); private static readonly FieldInfo MinimapHasGenerated = AccessTools.Field(typeof(Minimap), "m_hasGenerated"); private readonly RealmSession _session; private readonly Action _logInfo; private readonly Action _logWarning; private PlayerProfile _original; private PlayerProfile _realm; private bool _swapped; private bool _gaveUp; private ZRoutedRpc _registeredOn; private float _activeSince; private bool _handoverReady; private bool _handoverSeen; private readonly RealmConsentGui _consent; private bool _consentAsked; private bool _pendingFresh; private bool _mirror; private FchAppearance _pendingLook; private byte[] _pendingMap; private float _mapWaitingSince; private byte[] _pendingBlob; private byte[] _pendingWorld; private int _pendingGeneration; public bool Swapped => _swapped; public bool ShouldHoldSpawn { get { if (_gaveUp || _session == null) { return false; } if (_session.SecondsWaiting >= 0f) { return true; } if (_consent != null && _consent.Showing) { return true; } if (_session.Active && !_handoverReady) { return Time.realtimeSinceStartup - _activeSince < 20f; } return false; } } public RealmProfile(RealmSession session, RealmConsentGui consent, Action logInfo, Action logWarning) { _session = session; _consent = consent; _logInfo = logInfo; _logWarning = logWarning; Instance = this; } public void Tick() { if (_session == null) { return; } EnsureRegistered(); if (!_session.Active) { RestoreIfIdle(); return; } if (_activeSince <= 0f) { _activeSince = Time.realtimeSinceStartup; } if (_gaveUp) { return; } if (!_swapped && (Object)(object)Player.m_localPlayer != (Object)null) { FailClosed("could not take ownership of your character in time"); return; } if (!_swapped) { Swap(); } if (!_swapped || (_consent != null && _consent.Showing)) { return; } if (!_handoverReady && !_consentAsked && (_pendingFresh || _pendingBlob != null)) { RequestConsent(); return; } if (_pendingMap != null) { TryApplyMap(); } if (_pendingLook != null && (Object)(object)Player.m_localPlayer != (Object)null) { ApplyAppearance(); } if ((_handoverReady || !_handoverSeen) && !_handoverReady && Time.realtimeSinceStartup - _activeSince >= 20f) { FailClosed("the server did not send your character"); } } private void EnsureRegistered() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredOn) { instance.Register("Deathboard_CharPush", (Action)OnCharPush); instance.Register("Deathboard_CharOffer", (Action)OnCharOffer); _registeredOn = instance; } } private void OnCharPush(long sender, ZPackage payload) { if (!_gaveUp) { if (!RealmProtocol.TryReadCharPush(payload, out var push)) { _logWarning("The server sent a character we could not read. Not applying anything."); FailClosed("the character the server sent was unreadable"); return; } _handoverSeen = true; _pendingBlob = push.Blob; _pendingWorld = push.World; _pendingGeneration = push.Generation; } } private void OnCharOffer(long sender, ZPackage payload) { if (!_gaveUp && RealmProtocol.TryReadCharOffer(payload, out var _)) { _handoverSeen = true; _pendingFresh = true; } } private void Swap() { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown Game instance = Game.instance; if ((Object)(object)instance == (Object)null || ProfileField == null) { return; } PlayerProfile playerProfile = instance.GetPlayerProfile(); if (playerProfile == null) { return; } string name = playerProfile.GetName(); if (string.IsNullOrEmpty(name)) { return; } try { PlayerProfile val = new PlayerProfile((string)null, (FileSource)1); val.SetName(name); _original = playerProfile; _realm = val; ProfileField.SetValue(instance, val); _swapped = true; _logInfo("Realm character active for this session as \"" + name + "\". Nothing is written to disk for it - the server holds the copy that counts. Your own character file is untouched."); } catch (Exception ex) { _logWarning("Could not swap in the realm character: " + ex.Message); FailClosed("the realm character could not be prepared"); } } private void RequestConsent() { _consentAsked = true; if (_consent == null) { GrantConsent(remember: false); return; } string steamId = LocalSteamId(); string text = ((_original != null) ? _original.GetName() : null); if (_consent.AlreadyAccepted(steamId, text, out var mirror)) { _mirror = mirror; GrantConsent(remember: false); return; } _consent.IsFresh = _pendingFresh; _consent.CharacterName = text ?? ""; _consent.MirrorChoice = false; _consent.Prompt = (_pendingFresh ? "This server keeps its own characters. You are about to start a NEW one here - no gear, no skills, nothing carried in from elsewhere.\n\nYour own character is not affected. This mod never reads, writes or deletes your personal character file; the realm character is a separate file of its own.\n\nEverything you earn here is stored on the server and given back to you next time you play on it." : "This server keeps its own characters, and it is holding one for you.\n\nThe server's copy is about to be loaded. Anything this character did somewhere else is not part of it and will not carry over.\n\nYour own character is not affected. This mod never reads, writes or deletes your personal character file."); _consent.OnAccept = delegate { _mirror = _consent.MirrorChoice; GrantConsent(remember: true); }; _consent.OnDecline = delegate { FailClosed("you chose not to hand your character to this realm"); }; } private void GrantConsent(bool remember) { if (remember && _consent != null) { _consent.Remember(LocalSteamId(), (_original != null) ? _original.GetName() : null, _mirror); } if (_mirror) { _logInfo("Your character-select slot will be updated with your realm progress when you log out. The local character you picked will be replaced by it."); } if (_pendingFresh) { _pendingFresh = false; _handoverReady = true; _pendingLook = ReadSourceAppearance(); _logInfo("This server has no character for you yet: starting a brand new one. Anything you arrived with stays in your own character file, untouched." + ((_pendingLook != null) ? " Your face, hair and beard come with you; nothing else does." : " Your looks could not be read from this character, so you start with default ones - you can change them in game.")); Report(0, ok: true, (_pendingLook != null) ? "fresh character, looks carried" : "fresh character"); } else if (_pendingBlob != null) { ApplyPending(); } } private FchAppearance ReadSourceAppearance() { if (_original == null || PlayerDataField == null) { return null; } try { if (!(PlayerDataField.GetValue(_original) is byte[] array) || array.Length == 0) { return null; } return FchAppearanceReader.Read(array); } catch (Exception ex) { _logWarning("Could not read your character's appearance: " + ex.Message + ". Starting with default looks."); return null; } } private void TryApplyMap() { if (MinimapSetMapData == null || MinimapHasGenerated == null) { _pendingMap = null; return; } if (Time.realtimeSinceStartup - _mapWaitingSince > 120f) { _logWarning("The map never became ready to restore; you start with only what you can see."); _pendingMap = null; return; } Minimap instance = Minimap.instance; if ((Object)(object)instance == (Object)null) { return; } try { if ((bool)MinimapHasGenerated.GetValue(instance)) { byte[] pendingMap = _pendingMap; _pendingMap = null; MinimapSetMapData.Invoke(instance, new object[1] { pendingMap }); _logInfo("Restored your explored map (" + pendingMap.Length + " bytes)."); } } catch (Exception ex) { _pendingMap = null; _logWarning("Could not restore your explored map: " + ex.Message); } } private void ApplyAppearance() { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) FchAppearance pendingLook = _pendingLook; _pendingLook = null; Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null || pendingLook == null) { return; } try { ((Humanoid)localPlayer).SetBeard(pendingLook.Beard); ((Humanoid)localPlayer).SetHair(pendingLook.Hair); localPlayer.SetSkinColor(new Vector3(pendingLook.SkinR, pendingLook.SkinG, pendingLook.SkinB)); localPlayer.SetHairColor(new Vector3(pendingLook.HairR, pendingLook.HairG, pendingLook.HairB)); localPlayer.SetPlayerModel(pendingLook.ModelIndex); _logInfo("Carried your appearance onto the new realm character."); } catch (Exception ex) { _logWarning("Could not apply your appearance: " + ex.Message); } } private void ApplyPending() { byte[] pendingBlob = _pendingBlob; byte[] pendingWorld = _pendingWorld; int pendingGeneration = _pendingGeneration; _pendingBlob = null; _pendingWorld = null; if (_realm == null || PlayerDataField == null) { FailClosed("the realm character could not be prepared"); return; } try { RenameAside(); PlayerDataField.SetValue(_realm, pendingBlob); byte[] mapData; bool flag = RealmWorldData.Apply(_realm, pendingWorld, out mapData); if (mapData != null) { _pendingMap = mapData; _mapWaitingSince = Time.realtimeSinceStartup; } _handoverReady = true; SuppressIntro(); _logInfo("Applied the server's character, generation " + pendingGeneration + " (" + pendingBlob.Length + " bytes)" + (flag ? ", with your position and map" : ", but no stored position or map") + ". This is the copy this server holds; anything this character did elsewhere is not part of it."); Report(pendingGeneration, ok: true, pendingBlob.Length + " bytes"); } catch (Exception ex) { _logWarning("Could not apply the server's character: " + ex.Message); Report(pendingGeneration, ok: false, ex.Message); FailClosed("the server's character could not be applied"); } } private void RenameAside() { } public void MirrorOnLogout() { if (!_mirror || !_swapped || _original == null || _realm == null || PlayerDataField == null) { return; } try { Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null) { _realm.SavePlayerData(localPlayer); } if (PlayerDataField.GetValue(_realm) is byte[] array && array.Length != 0) { PlayerDataField.SetValue(_original, array); _original.m_firstSpawn = false; _original.Save(); _logInfo("Updated your character-select slot with this session's realm progress (" + array.Length + " bytes)."); } } catch (Exception ex) { _logWarning("Could not update your character-select slot: " + ex.Message + ". Your realm character on the server is unaffected."); } } private static string LocalSteamId() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { PlatformUserID platformUserID = ((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID; return ((PlatformUserID)(ref platformUserID)).IsValid ? platformUserID.m_userID : null; } catch (Exception) { return null; } } private void SuppressIntro() { Game instance = Game.instance; if ((Object)(object)instance == (Object)null) { return; } try { if (QueuedIntroField != null) { QueuedIntroField.SetValue(instance, false); } if (instance.InIntro(false)) { instance.SkipIntro(); } } catch (Exception ex) { _logWarning("Could not suppress the intro: " + ex.Message); } } private void Report(int generation, bool ok, string note) { try { ZRoutedRpc.instance.InvokeRoutedRPC("Deathboard_CharApplied", new object[1] { RealmProtocol.WriteCharApplied(generation, ok, note) }); } catch (Exception) { } } private void RestoreIfIdle() { _activeSince = 0f; _handoverReady = false; _handoverSeen = false; _pendingBlob = null; _pendingWorld = null; _pendingMap = null; _consentAsked = false; _pendingFresh = false; _pendingLook = null; _mirror = false; if (_consent != null) { _consent.Prompt = null; } if (!_swapped || _original == null || (Object)(object)Player.m_localPlayer != (Object)null) { return; } Game instance = Game.instance; if ((Object)(object)instance == (Object)null || ProfileField == null) { _original = null; _realm = null; _swapped = false; return; } try { ProfileField.SetValue(instance, _original); _logInfo("Realm character released; your own character file is selected again."); } catch (Exception ex) { _logWarning("Could not restore your own profile: " + ex.Message); } finally { _original = null; _realm = null; _swapped = false; } } private void FailClosed(string reason) { if (_gaveUp) { return; } _gaveUp = true; string text = "Deathboard: " + reason + ". Disconnecting rather than risking a divergent character - please rejoin. Your own character file is untouched."; _logWarning(text); try { if ((Object)(object)MessageHud.instance != (Object)null) { MessageHud.instance.ShowMessage((MessageType)2, text, 0, (Sprite)null, false); } } catch (Exception) { } try { Game instance = Game.instance; if ((Object)(object)instance != (Object)null) { instance.Logout(true, true); } } catch (Exception ex2) { _logWarning("Could not disconnect cleanly: " + ex2.Message); } } } [HarmonyPatch] internal static class RealmSpawnGate { [HarmonyPrefix] [HarmonyPatch(typeof(Game), "UpdateRespawn")] private static bool HoldRespawn() { try { RealmProfile instance = RealmProfile.Instance; return instance == null || !instance.ShouldHoldSpawn; } catch (Exception) { return true; } } } internal enum RealmState { Idle, Waiting, Active, BoardOnly, Inert } internal sealed class RealmSession { private readonly Func _timeoutSeconds; private readonly Action _logInfo; private readonly Action _logWarning; private ZRoutedRpc _registeredOn; private float _waitingSince; public RealmState State { get; private set; } public RealmProtocol.HelloAck Server { get; private set; } public bool Active => State == RealmState.Active; public float SecondsWaiting { get { if (State != RealmState.Waiting) { return -1f; } return Time.realtimeSinceStartup - _waitingSince; } } public bool BoardAvailable { get { if (State != RealmState.Active) { return State == RealmState.BoardOnly; } return true; } } public RealmSession(Func timeoutSeconds, Action logInfo, Action logWarning) { _timeoutSeconds = timeoutSeconds; _logInfo = logInfo; _logWarning = logWarning; State = RealmState.Idle; } public void Tick() { ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || instance.IsServer()) { if (State != RealmState.Idle) { _logInfo("Session ended; realm state reset."); State = RealmState.Idle; } return; } EnsureRegistered(); switch (State) { case RealmState.Idle: TrySendHello(instance); break; case RealmState.Waiting: if (Time.realtimeSinceStartup - _waitingSince >= Math.Max(1f, _timeoutSeconds())) { State = RealmState.Inert; _logInfo("No realm answer within the timeout - this is an ordinary server. The Great Hall is inert for this session."); } break; } } private void EnsureRegistered() { ZRoutedRpc instance = ZRoutedRpc.instance; if (instance != null && instance != _registeredOn) { instance.Register("Deathboard_HelloAck", (Action)OnHelloAck); _registeredOn = instance; } } private void TrySendHello(ZNet net) { if (_registeredOn == null) { return; } ZNetPeer serverPeer = net.GetServerPeer(); if (serverPeer == null || !serverPeer.IsReady()) { return; } Game instance = Game.instance; if ((Object)(object)instance == (Object)null) { return; } PlayerProfile playerProfile = instance.GetPlayerProfile(); if (playerProfile == null) { return; } string name = playerProfile.GetName(); if (string.IsNullOrEmpty(name)) { return; } string text = LocalSteamId(); if (!string.IsNullOrEmpty(text)) { try { ZRoutedRpc.instance.InvokeRoutedRPC("Deathboard_Hello", new object[1] { RealmProtocol.WriteHello(text, name) }); } catch (Exception ex) { _logWarning("Could not send Deathboard_Hello: " + ex.Message + ". Standing down."); State = RealmState.Inert; return; } _waitingSince = Time.realtimeSinceStartup; State = RealmState.Waiting; _logInfo("Sent realm hello as " + name + "; waiting up to " + Math.Max(1f, _timeoutSeconds()) + "s for an answer."); } } private static string LocalSteamId() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { PlatformUserID platformUserID = ((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID; return ((PlatformUserID)(ref platformUserID)).IsValid ? platformUserID.m_userID : null; } catch (Exception) { return null; } } private void OnHelloAck(long sender, ZPackage payload) { if (State != RealmState.Waiting) { return; } if (!RealmProtocol.TryReadHelloAck(payload, out var ack)) { State = RealmState.Inert; _logWarning("Unreadable realm answer; standing down for this session."); return; } Server = ack; if (ack.Protocol != 6) { State = RealmState.Inert; _logWarning("Realm protocol mismatch: server speaks " + ack.Protocol + ", this client speaks " + 6 + ". Standing down. Update The Great Hall to match the server (" + ack.ServerLabel + ")."); } else if (!ack.RealmMode) { if (!ack.RemoteBoard) { State = RealmState.Inert; _logInfo("Server runs Deathboard but both realm mode and the remote board are off; nothing to do this session."); return; } State = RealmState.BoardOnly; _logInfo("Leaderboard available from " + ack.ServerLabel + " (server build " + ack.ServerBuild + ", this client build " + 35 + "). Realm mode is off, so no character data is touched."); } else { State = RealmState.Active; _logInfo("Realm session active with " + ack.ServerLabel + " (server build " + ack.ServerBuild + ", this client build " + 35 + ")."); } } } internal sealed class SkillReporter { private readonly RealmSession _session; private readonly Action _logInfo; private readonly Action _logWarning; private const float ReportIntervalSeconds = 30f; private const float FirstReportDelaySeconds = 5f; private const float ForceResendSeconds = 180f; private float _nextReportAt; private float _lastAcceptedAt; private bool _announced; private string _lastSignature; public SkillReporter(RealmSession session, Action logInfo, Action logWarning) { _session = session; _logInfo = logInfo; _logWarning = logWarning; } public void Tick() { if (_session == null || !_session.Server.AcceptsSkills || !_session.BoardAvailable) { _nextReportAt = 0f; _announced = false; _lastSignature = null; _lastAcceptedAt = 0f; return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (_nextReportAt <= 0f) { _nextReportAt = realtimeSinceStartup + 5f; } else if (!(realtimeSinceStartup < _nextReportAt)) { _nextReportAt = realtimeSinceStartup + 30f; Send(); } } private void Send() { Dictionary dictionary = ReadLocalSkills(); if (dictionary == null || dictionary.Count == 0) { return; } string text = Signature(dictionary); bool num = string.Equals(text, _lastSignature, StringComparison.Ordinal); bool flag = Time.realtimeSinceStartup - _lastAcceptedAt >= 180f; if (num && !flag) { return; } string text2 = LocalSteamId(); string text3 = LocalCharacterName(); if (!string.IsNullOrEmpty(text2) && !string.IsNullOrEmpty(text3)) { try { ZRoutedRpc.instance.InvokeRoutedRPC("Deathboard_Skills", new object[1] { RealmProtocol.WriteSkills(text2, text3, dictionary) }); } catch (Exception ex) { _logWarning("Could not send skill report: " + ex.Message); return; } _lastSignature = text; _lastAcceptedAt = Time.realtimeSinceStartup; if (!_announced) { _announced = true; _logInfo("Reporting " + dictionary.Count + " skill levels to the server so the leaderboard can show real ratings. Nothing else about your character is sent."); } } } private static Dictionary ReadLocalSkills() { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Expected I4, but got Unknown Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } Skills skills = ((Character)localPlayer).GetSkills(); if ((Object)(object)skills == (Object)null) { return null; } List skillList = skills.GetSkillList(); if (skillList == null || skillList.Count == 0) { return null; } Dictionary dictionary = new Dictionary(skillList.Count); for (int i = 0; i < skillList.Count; i++) { Skill val = skillList[i]; if (val != null && val.m_info != null) { dictionary[(int)val.m_info.m_skill] = val.m_level; } } return dictionary; } private static string Signature(Dictionary levels) { StringBuilder stringBuilder = new StringBuilder(levels.Count * 8); foreach (KeyValuePair level in levels) { stringBuilder.Append(level.Key).Append(':').Append(Mathf.FloorToInt(level.Value)) .Append(';'); } return stringBuilder.ToString(); } private static string LocalCharacterName() { Game instance = Game.instance; if ((Object)(object)instance == (Object)null) { return null; } PlayerProfile playerProfile = instance.GetPlayerProfile(); if (playerProfile != null) { return playerProfile.GetName(); } return null; } private static string LocalSteamId() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { PlatformUserID platformUserID = ((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID; return ((PlatformUserID)(ref platformUserID)).IsValid ? platformUserID.m_userID : null; } catch (Exception) { return null; } } } [HarmonyPatch] internal static class SnapshotLogoutPatch { [HarmonyPrefix] [HarmonyPatch(typeof(Game), "Logout")] private static void OnLogout() { try { DeathboardClientPlugin instance = DeathboardClientPlugin.Instance; if (!((Object)(object)instance == (Object)null)) { if (instance.Uploader != null) { instance.Uploader.SendOnLogout(); } if (instance.RealmProfile != null) { instance.RealmProfile.MirrorOnLogout(); } } } catch (Exception) { } } } internal sealed class SnapshotUploader { private const float UploadIntervalSeconds = 300f; private const float FirstUploadDelaySeconds = 20f; private const float MinimumGapSeconds = 5f; private const float InventoryDebounceSeconds = 5f; private readonly RealmSession _session; private readonly Action _logInfo; private readonly Action _logWarning; private float _nextUploadAt; private float _lastSentAt; private bool _announced; private bool _wasDead; private int _generation; private Inventory _watching; private float _inventoryDueAt; private bool Available { get { if (_session != null && _session.Server.AcceptsCharUploads) { return _session.BoardAvailable; } return false; } } public SnapshotUploader(RealmSession session, Action logInfo, Action logWarning) { _session = session; _logInfo = logInfo; _logWarning = logWarning; } public void Tick() { if (!Available) { Unwatch(); _nextUploadAt = 0f; _inventoryDueAt = 0f; _announced = false; _wasDead = false; return; } float realtimeSinceStartup = Time.realtimeSinceStartup; WatchInventory(); if (_inventoryDueAt > 0f && realtimeSinceStartup >= _inventoryDueAt) { if (_lastSentAt > 0f && realtimeSinceStartup - _lastSentAt < 5f) { _inventoryDueAt = _lastSentAt + 5f; return; } _inventoryDueAt = 0f; Send(RealmProtocol.UploadReason.Inventory); return; } Player localPlayer = Player.m_localPlayer; bool flag = (Object)(object)localPlayer != (Object)null && ((Character)localPlayer).IsDead(); if (flag && !_wasDead) { _wasDead = true; Send(RealmProtocol.UploadReason.Death); return; } if (!flag) { _wasDead = false; } if (_nextUploadAt <= 0f) { _nextUploadAt = realtimeSinceStartup + 20f; } else if (!(realtimeSinceStartup < _nextUploadAt)) { _nextUploadAt = realtimeSinceStartup + 300f; Send(RealmProtocol.UploadReason.Interval); } } public void SendOnLogout() { if (Available) { Send(RealmProtocol.UploadReason.Logout); } } private void Send(RealmProtocol.UploadReason reason) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_lastSentAt > 0f && realtimeSinceStartup - _lastSentAt < 5f && reason != RealmProtocol.UploadReason.Logout) { return; } byte[] array = BuildBlob(); if (array == null || array.Length == 0) { return; } if (array.Length > 4194304) { _logWarning("Not uploading: this character serialises to " + array.Length + " bytes, over the " + 4194304 + "-byte limit."); return; } string text = LocalSteamId(); string text2 = LocalCharacterName(); if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(text2)) { try { Game instance = Game.instance; byte[] world = (((Object)(object)instance == (Object)null) ? null : RealmWorldData.Capture(instance.GetPlayerProfile())); ZRoutedRpc.instance.InvokeRoutedRPC("Deathboard_CharUpload", new object[1] { RealmProtocol.WriteCharUpload(text, text2, _generation + 1, reason, array, world) }); } catch (Exception ex) { _logWarning("Could not upload character snapshot: " + ex.Message); return; } _generation++; _lastSentAt = realtimeSinceStartup; WriteRescueCopy(text, text2, array); if (!_announced) { _announced = true; _logInfo("This server owns your character: a full snapshot (" + array.Length + " bytes - skills, inventory, food, recipes, appearance) is uploaded every " + 5 + " minutes, on death and on logout. Your local character file is never read, written or deleted by this mod."); } } } private void WatchInventory() { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null)) { Inventory inventory = ((Humanoid)localPlayer).GetInventory(); if (inventory != null && inventory != _watching) { Unwatch(); inventory.m_onChanged = (Action)Delegate.Combine(inventory.m_onChanged, new Action(OnInventoryChanged)); _watching = inventory; } } } private void Unwatch() { if (_watching != null) { try { Inventory watching = _watching; watching.m_onChanged = (Action)Delegate.Remove(watching.m_onChanged, new Action(OnInventoryChanged)); } catch (Exception) { } _watching = null; } } private void OnInventoryChanged() { _inventoryDueAt = Time.realtimeSinceStartup + 5f; } private static byte[] BuildBlob() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { return null; } try { ZPackage val = new ZPackage(); localPlayer.Save(val); return val.GetArray(); } catch (Exception) { return null; } } private void WriteRescueCopy(string steamId, string characterName, byte[] blob) { try { string text = Path.Combine(Path.Combine(Paths.ConfigPath, "Deathboard.Client"), "cache"); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } string path = Sanitize(steamId) + "_" + Sanitize(characterName) + ".fch"; string text2 = Path.Combine(text, path); string text3 = text2 + ".tmp"; File.WriteAllBytes(text3, blob); if (File.Exists(text2)) { File.Delete(text2); } File.Move(text3, text2); } catch (Exception ex) { _logWarning("Could not write the local rescue copy: " + ex.Message); } } private static string Sanitize(string value) { if (string.IsNullOrEmpty(value)) { return "_"; } StringBuilder stringBuilder = new StringBuilder(value.Length); foreach (char c in value) { stringBuilder.Append((char.IsLetterOrDigit(c) || c == '-' || c == '_') ? c : '_'); } return stringBuilder.ToString(); } private static string LocalCharacterName() { Game instance = Game.instance; if ((Object)(object)instance == (Object)null) { return null; } PlayerProfile playerProfile = instance.GetPlayerProfile(); if (playerProfile != null) { return playerProfile.GetName(); } return null; } private static string LocalSteamId() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) try { PlatformUserID platformUserID = ((IUser)PlatformManager.DistributionPlatform.LocalUser).PlatformUserID; return ((PlatformUserID)(ref platformUserID)).IsValid ? platformUserID.m_userID : null; } catch (Exception) { return null; } } } }