Decompiled source of WKRace v0.9.0
WKRace.dll
Decompiled 3 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using TMPro; using UnityEngine; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; using WKRace.Protocol; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially or fully created with the assistance of Generative AI (code, refactoring and documentation generation).")] [assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")] [assembly: AssemblyMetadata("AI_Model", "Claude")] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace WKRace.Protocol { public sealed class BanReady : Message { public Guid MatchId; public List<string> Pool = new List<string>(); public override MessageType Type => MessageType.BanReady; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); BanLists.Write(w, Pool); } public static BanReady Read(PacketReader r) { return new BanReady { MatchId = r.ReadGuid(), Pool = BanLists.Read(r) }; } } public sealed class BanPick : Message { public Guid MatchId; public string Item = ""; public override MessageType Type => MessageType.BanPick; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteString(Item, 48); } public static BanPick Read(PacketReader r) { return new BanPick { MatchId = r.ReadGuid(), Item = r.ReadString(48) }; } } public sealed class BanState : Message { public Guid MatchId; public List<string> Pool = new List<string>(); public List<string> Banned = new List<string>(); public List<string> BannedBy = new List<string>(); public string TurnName = ""; public bool YourTurn; public int SecondsLeft; public bool Done; public override MessageType Type => MessageType.BanState; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); BanLists.Write(w, Pool); BanLists.Write(w, Banned); BanLists.Write(w, BannedBy, 24); w.WriteString(TurnName, 24); w.WriteBool(YourTurn); w.WriteInt32(SecondsLeft); w.WriteBool(Done); } public static BanState Read(PacketReader r) { BanState banState = new BanState(); banState.MatchId = r.ReadGuid(); banState.Pool = BanLists.Read(r); banState.Banned = BanLists.Read(r); banState.BannedBy = BanLists.Read(r, 24); banState.TurnName = r.ReadString(24); banState.YourTurn = r.ReadBool(); banState.SecondsLeft = r.ReadInt32(); banState.Done = r.ReadBool(); if (banState.Banned.Count != banState.BannedBy.Count) { throw new ProtocolException("Ban lists differ in length"); } return banState; } } internal static class BanLists { public static void Write(PacketWriter w, List<string> items, int maxChars = 48) { int num = items?.Count ?? 0; if (num > 16) { throw new ProtocolException("Too many items"); } w.WriteByte((byte)num); for (int i = 0; i < num; i++) { w.WriteString(items[i], maxChars); } } public static List<string> Read(PacketReader r, int maxChars = 48) { int num = r.ReadByte(); if (num > 16) { throw new ProtocolException("Too many items"); } List<string> list = new List<string>(num); for (int i = 0; i < num; i++) { list.Add(r.ReadString(maxChars)); } return list; } } public sealed class BugReport { public const byte FormatVersion = 1; public const int MaxTitle = 120; public const int MaxDescription = 8000; public const int MaxContact = 120; public const int MaxInfo = 2000; public const int MaxFileName = 100; public const int MaxAttachments = 10; public const int MaxAttachmentBytes = 20971520; public const int MaxFileBytes = 15728640; public const int MaxLogBytes = 1048576; public const int MaxBodyBytes = 22282240; public static readonly string[] AllowedExtensions = new string[8] { ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".txt", ".log" }; public Guid PlayerId; public byte[] Token = new byte[32]; public string Title = ""; public string Description = ""; public string Contact = ""; public string Info = ""; public byte[] Log = new byte[0]; public List<BugAttachment> Attachments = new List<BugAttachment>(); public static bool IsAllowedFile(string name) { string value = Path.GetExtension(name ?? "").ToLowerInvariant(); return Array.IndexOf(AllowedExtensions, value) >= 0; } public byte[] ToBytes() { PacketWriter packetWriter = new PacketWriter(65536); packetWriter.WriteByte(1); packetWriter.WriteGuid(PlayerId); packetWriter.WriteFixedBytes(Token, 32); packetWriter.WriteString(Title, 120); packetWriter.WriteString(Description, 8000); packetWriter.WriteString(Contact, 120); packetWriter.WriteString(Info, 2000); WriteBlob(packetWriter, Log); packetWriter.WriteByte((byte)Attachments.Count); foreach (BugAttachment attachment in Attachments) { packetWriter.WriteString(attachment.Name, 100); WriteBlob(packetWriter, attachment.Data); } return packetWriter.ToArray(); } public static BugReport FromBytes(byte[] data) { PacketReader packetReader = new PacketReader(data, 0, data.Length); if (packetReader.ReadByte() != 1) { throw new ProtocolException("Unknown report format"); } BugReport bugReport = new BugReport { PlayerId = packetReader.ReadGuid(), Token = packetReader.ReadFixedBytes(32), Title = packetReader.ReadString(120), Description = packetReader.ReadString(8000), Contact = packetReader.ReadString(120), Info = packetReader.ReadString(2000), Log = ReadBlob(packetReader, 1048576) }; int num = packetReader.ReadByte(); if (num > 10) { throw new ProtocolException("Too many attachments"); } long num2 = 0L; for (int i = 0; i < num; i++) { BugAttachment bugAttachment = new BugAttachment { Name = packetReader.ReadString(100), Data = ReadBlob(packetReader, 15728640) }; num2 += bugAttachment.Data.Length; if (num2 > 20971520) { throw new ProtocolException("Attachments too large"); } if (!IsAllowedFile(bugAttachment.Name)) { throw new ProtocolException("Attachment type not allowed: " + Path.GetExtension(bugAttachment.Name)); } bugReport.Attachments.Add(bugAttachment); } if (packetReader.Remaining != 0) { throw new ProtocolException("Trailing data"); } return bugReport; } public long AttachmentBytes() { long num = 0L; foreach (BugAttachment attachment in Attachments) { num += attachment.Data.Length; } return num; } private static void WriteBlob(PacketWriter w, byte[] data) { data = data ?? new byte[0]; w.WriteInt32(data.Length); if (data.Length != 0) { w.WriteFixedBytes(data, data.Length); } } private static byte[] ReadBlob(PacketReader r, int max) { int num = r.ReadInt32(); if (num < 0 || num > max) { throw new ProtocolException("Field too large"); } if (num != 0) { return r.ReadFixedBytes(num); } return new byte[0]; } } public sealed class BugAttachment { public string Name = ""; public byte[] Data = new byte[0]; } public static class BuildTag { public static string Compose(string steamBuild, string modVersion) { List<string> list = new List<string>(); if (!string.IsNullOrEmpty(steamBuild)) { list.Add(steamBuild.StartsWith("steam-", StringComparison.Ordinal) ? steamBuild : ("steam-" + steamBuild)); } string text = ShortVersion(modVersion); if (text.Length > 0) { list.Add("mod-" + text); } return string.Join("|", list); } public static bool Compatible(string a, string b) { Dictionary<string, string> dictionary = Parse(a); Dictionary<string, string> dictionary2 = Parse(b); foreach (KeyValuePair<string, string> item in dictionary) { if (dictionary2.TryGetValue(item.Key, out var value) && value != item.Value) { return false; } } return true; } public static string ShortVersion(string version) { if (string.IsNullOrEmpty(version)) { return ""; } string[] array = version.Trim().Split('.'); if (array.Length <= 3) { return version.Trim(); } return string.Join(".", array, 0, 3); } private static Dictionary<string, string> Parse(string tag) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); if (string.IsNullOrEmpty(tag)) { return dictionary; } string[] array = tag.Split('|'); foreach (string text in array) { int num = text.IndexOf('-'); if (num > 0 && num != text.Length - 1) { dictionary[text.Substring(0, num)] = text.Substring(num + 1); } } return dictionary; } } public sealed class LobbyCode { private const string Prefix = "WKR"; private const string Alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; private const int PayloadLength = 5; public uint LobbyId; public static string Encode(uint lobbyId) { PacketWriter packetWriter = new PacketWriter(5); packetWriter.WriteUInt32(lobbyId); byte[] array = packetWriter.ToArray(); byte[] array2 = new byte[array.Length + 1]; Buffer.BlockCopy(array, 0, array2, 0, array.Length); array2[array.Length] = Check(array, array.Length); string text = ToBase32(array2); return "WKR-" + text.Substring(0, 4) + "-" + text.Substring(4); } public static bool TryDecode(string text, out LobbyCode code) { code = null; if (string.IsNullOrWhiteSpace(text)) { return false; } string text2 = text.Trim().ToUpperInvariant().Replace("-", "") .Replace(" ", ""); if (text2.StartsWith("WKR", StringComparison.Ordinal)) { text2 = text2.Substring("WKR".Length); } text2 = text2.Replace('O', '0').Replace('I', '1').Replace('L', '1'); if (text2.Length != 8 || !FromBase32(text2, out var data) || data.Length != 5) { return false; } if (data[4] != Check(data, 4)) { return false; } uint num = new PacketReader(data, 0, 4).ReadUInt32(); if (num == 0) { return false; } code = new LobbyCode { LobbyId = num }; return true; } public static bool TryParseAddress(string text, out string host, out int port) { host = null; port = 47600; if (string.IsNullOrWhiteSpace(text)) { return false; } string text2 = text.Trim(); if (text2.StartsWith("[", StringComparison.Ordinal)) { int num = text2.IndexOf(']'); if (num < 0) { return false; } host = text2.Substring(1, num - 1); string text3 = text2.Substring(num + 1); if (text3.Length != 0) { if (text3[0] == ':') { return TryPort(text3.Substring(1), out port); } return false; } return true; } int num2 = text2.LastIndexOf(':'); if (num2 >= 0 && text2.IndexOf(':') == num2) { host = text2.Substring(0, num2); if (host.Length > 0) { return TryPort(text2.Substring(num2 + 1), out port); } return false; } host = text2; return true; } private static bool TryPort(string text, out int port) { if (int.TryParse(text, NumberStyles.Integer, CultureInfo.InvariantCulture, out port) && port > 0) { return port < 65536; } return false; } private static byte Check(byte[] data, int length) { uint num = 2166136261u; for (int i = 0; i < length; i++) { num ^= data[i]; num *= 16777619; } return (byte)(num ^ (num >> 8) ^ (num >> 16) ^ (num >> 24)); } private static string ToBase32(byte[] data) { StringBuilder stringBuilder = new StringBuilder((data.Length * 8 + 4) / 5); int num = 0; int num2 = 0; foreach (byte b in data) { num = (num << 8) | b; for (num2 += 8; num2 >= 5; num2 -= 5) { stringBuilder.Append("0123456789ABCDEFGHJKMNPQRSTVWXYZ"[(num >> num2 - 5) & 0x1F]); } } if (num2 > 0) { stringBuilder.Append("0123456789ABCDEFGHJKMNPQRSTVWXYZ"[(num << 5 - num2) & 0x1F]); } return stringBuilder.ToString(); } private static bool FromBase32(string text, out byte[] data) { data = null; List<byte> list = new List<byte>(text.Length * 5 / 8); int num = 0; int num2 = 0; foreach (char value in text) { int num3 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".IndexOf(value); if (num3 < 0) { return false; } num = ((num << 5) | num3) & 0xFFFF; num2 += 5; if (num2 >= 8) { list.Add((byte)(num >> num2 - 8)); num2 -= 8; } } data = list.ToArray(); return true; } } public static class MessageCodec { public static byte[] Encode(Message message) { PacketWriter packetWriter = new PacketWriter(64); packetWriter.WriteByte((byte)message.Type); message.Write(packetWriter); if (packetWriter.Length > 2048) { throw new ProtocolException("Message too large"); } return packetWriter.ToArray(); } public static Message Decode(byte[] payload) { if (payload == null || payload.Length < 1) { throw new ProtocolException("Empty payload"); } PacketReader packetReader = new PacketReader(payload, 1, payload.Length - 1); Message message = (MessageType)payload[0] switch { MessageType.ClientHello => ClientHello.Read(packetReader), MessageType.ServerWelcome => ServerWelcome.Read(packetReader), MessageType.Error => ErrorMessage.Read(packetReader), MessageType.Ping => Ping.Read(packetReader), MessageType.Pong => Pong.Read(packetReader), MessageType.QueueJoin => QueueJoin.Read(packetReader), MessageType.QueueLeave => QueueLeave.Read(packetReader), MessageType.QueueStatus => QueueStatus.Read(packetReader), MessageType.MatchFound => MatchFound.Read(packetReader), MessageType.LobbyCreate => LobbyCreate.Read(packetReader), MessageType.LobbyCreated => LobbyCreated.Read(packetReader), MessageType.LobbyJoin => LobbyJoin.Read(packetReader), MessageType.LobbyCancel => LobbyCancel.Read(packetReader), MessageType.LobbyClosed => LobbyClosed.Read(packetReader), MessageType.MatchReady => MatchReady.Read(packetReader), MessageType.MatchStart => MatchStart.Read(packetReader), MessageType.StartCountdown => StartCountdown.Read(packetReader), MessageType.PlayerState => PlayerState.Read(packetReader), MessageType.OpponentState => OpponentState.Read(packetReader), MessageType.SplitReached => SplitReached.Read(packetReader), MessageType.OpponentSplit => OpponentSplit.Read(packetReader), MessageType.AttemptStarted => AttemptStarted.Read(packetReader), MessageType.OpponentAttempt => OpponentAttempt.Read(packetReader), MessageType.PlayerFinished => PlayerFinished.Read(packetReader), MessageType.OpponentFinished => OpponentFinished.Read(packetReader), MessageType.PlayerForfeit => PlayerForfeit.Read(packetReader), MessageType.MatchResult => MatchResult.Read(packetReader), MessageType.GameStatus => GameStatus.Read(packetReader), MessageType.MatchPaused => MatchPaused.Read(packetReader), MessageType.MatchResumed => MatchResumed.Read(packetReader), MessageType.ModHello => ModHello.Read(packetReader), MessageType.ClientStatus => ClientStatus.Read(packetReader), MessageType.RacePaused => RacePaused.Read(packetReader), MessageType.RaceResumed => RaceResumed.Read(packetReader), MessageType.PracticeMark => PracticeMark.Read(packetReader), MessageType.PracticeRestart => PracticeRestart.Read(packetReader), MessageType.PlayerDied => PlayerDied.Read(packetReader), MessageType.BanReady => BanReady.Read(packetReader), MessageType.BanPick => BanPick.Read(packetReader), MessageType.BanState => BanState.Read(packetReader), MessageType.SteamLinkStart => SteamLinkStart.Read(packetReader), MessageType.SteamLinkUrl => SteamLinkUrl.Read(packetReader), MessageType.SteamLinkResult => SteamLinkResult.Read(packetReader), MessageType.LobbyState => LobbyState.Read(packetReader), MessageType.LobbyStart => LobbyStart.Read(packetReader), MessageType.PlayerReady => PlayerReady.Read(packetReader), MessageType.MatchRoster => MatchRoster.Read(packetReader), MessageType.ModSettings => ModSettings.Read(packetReader), MessageType.ChatSend => ChatSend.Read(packetReader), MessageType.ChatMessage => ChatMessage.Read(packetReader), MessageType.VoiceHello => VoiceHello.Read(packetReader), MessageType.VoiceState => VoiceState.Read(packetReader), MessageType.VoicePeers => VoicePeers.Read(packetReader), MessageType.VoiceActivity => VoiceActivity.Read(packetReader), _ => throw new ProtocolException("Unknown message type " + payload[0]), }; if (packetReader.Remaining != 0) { throw new ProtocolException("Trailing bytes in message " + message.Type); } return message; } } public static class FrameIO { public static byte[] ReadFrame(Stream stream) { byte[] array = new byte[2]; if (!ReadExactly(stream, array, 2)) { return null; } int num = array[0] | (array[1] << 8); if (num < 1 || num > 2048) { throw new ProtocolException("Invalid frame length " + num); } byte[] array2 = new byte[num]; if (!ReadExactly(stream, array2, num)) { throw new EndOfStreamException(); } return array2; } public static async Task<byte[]> ReadFrameAsync(Stream stream, CancellationToken cancellationToken) { byte[] header = new byte[2]; if (!(await ReadExactlyAsync(stream, header, 2, cancellationToken).ConfigureAwait(continueOnCapturedContext: false))) { return null; } int num = header[0] | (header[1] << 8); if (num < 1 || num > 2048) { throw new ProtocolException("Invalid frame length " + num); } byte[] payload = new byte[num]; if (!(await ReadExactlyAsync(stream, payload, num, cancellationToken).ConfigureAwait(continueOnCapturedContext: false))) { throw new EndOfStreamException(); } return payload; } public static byte[] Pack(byte[] payload) { if (payload.Length < 1 || payload.Length > 2048) { throw new ProtocolException("Invalid payload length"); } byte[] array = new byte[payload.Length + 2]; array[0] = (byte)payload.Length; array[1] = (byte)(payload.Length >> 8); Buffer.BlockCopy(payload, 0, array, 2, payload.Length); return array; } public static void WriteFrame(Stream stream, byte[] payload) { byte[] array = Pack(payload); stream.Write(array, 0, array.Length); stream.Flush(); } public static async Task WriteFrameAsync(Stream stream, byte[] payload, CancellationToken cancellationToken) { byte[] array = Pack(payload); await stream.WriteAsync(array, 0, array.Length, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); await stream.FlushAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: false); } private static bool ReadExactly(Stream stream, byte[] buffer, int count) { int num; for (int i = 0; i < count; i += num) { num = stream.Read(buffer, i, count - i); if (num <= 0) { if (i == 0) { return false; } throw new EndOfStreamException(); } } return true; } private static async Task<bool> ReadExactlyAsync(Stream stream, byte[] buffer, int count, CancellationToken cancellationToken) { int num; for (int read = 0; read < count; read += num) { num = await stream.ReadAsync(buffer, read, count - read, cancellationToken).ConfigureAwait(continueOnCapturedContext: false); if (num <= 0) { if (read == 0) { return false; } throw new EndOfStreamException(); } } return true; } } public enum MessageType : byte { ClientHello = 1, ServerWelcome = 2, Error = 3, Ping = 4, Pong = 5, QueueJoin = 10, QueueLeave = 11, QueueStatus = 12, MatchFound = 13, LobbyCreate = 14, LobbyCreated = 15, LobbyJoin = 16, LobbyCancel = 17, LobbyClosed = 18, LobbyState = 19, MatchReady = 20, MatchStart = 21, StartCountdown = 22, LobbyStart = 23, PlayerReady = 24, MatchRoster = 25, PlayerState = 30, OpponentState = 31, SplitReached = 32, OpponentSplit = 33, AttemptStarted = 34, OpponentAttempt = 35, PlayerFinished = 36, OpponentFinished = 37, PlayerForfeit = 38, MatchResult = 39, GameStatus = 40, MatchPaused = 41, MatchResumed = 42, SteamLinkStart = 43, SteamLinkUrl = 44, SteamLinkResult = 45, PlayerDied = 46, BanReady = 50, BanPick = 51, BanState = 52, ModHello = 60, ClientStatus = 61, RacePaused = 62, RaceResumed = 63, PracticeMark = 64, PracticeRestart = 65, ModSettings = 66, ChatSend = 70, ChatMessage = 71, VoiceHello = 80, VoiceState = 81, VoicePeers = 82, VoiceActivity = 83 } public enum ErrorCode : byte { Unknown, VersionMismatch, BadToken, InvalidName, RateLimited, ServerFull, ProtocolViolation, NotInMatch, AlreadyQueued, AlreadyInMatch, ServerShuttingDown, LobbyNotFound, LobbyUnavailable, LobbyBuildMismatch, RegistrationLimited, LobbySettingsMismatch, SteamLinkRequired, LobbyFull, LobbyBusy, NotHost } public enum MatchMode : byte { Duel, Party } [Flags] public enum MatchRules : byte { None = 0, ArtifactBans = 1, DeepStorageFood = 2, GuaranteedPerks = 4, GuaranteedItems = 8, CuratedSeeds = 0xE, Ranked = 0xF, All = 0xF } public enum RacerStatus : byte { Loading, Loaded, Ready, Racing, Finished, Out, Left } public enum MatchOutcome : byte { Win = 1, Loss = 2, Aborted = 4 } public enum MatchEnd : byte { Other, Finish, Death } public enum ClientPhase : byte { Offline, Connecting, Idle, Searching, InMatch } [Flags] public enum StateFlags : byte { None = 0, Dead = 1, LeftHold = 2, RightHold = 4, Loading = 8 } public abstract class Message { public abstract MessageType Type { get; } public abstract void Write(PacketWriter w); } public sealed class ClientHello : Message { public int ProtocolVersion = 7; public Guid PlayerId; public byte[] Token = new byte[32]; public string Name = ""; public string ClientVersion = ""; public bool GameConnected = true; public override MessageType Type => MessageType.ClientHello; public override void Write(PacketWriter w) { w.WriteInt32(ProtocolVersion); w.WriteGuid(PlayerId); w.WriteFixedBytes(Token, 32); w.WriteString(Name, 24); w.WriteString(ClientVersion, 32); w.WriteBool(GameConnected); } public static ClientHello Read(PacketReader r) { return new ClientHello { ProtocolVersion = r.ReadInt32(), PlayerId = r.ReadGuid(), Token = r.ReadFixedBytes(32), Name = r.ReadString(24), ClientVersion = r.ReadString(32), GameConnected = r.ReadBool() }; } } public sealed class ServerWelcome : Message { public Guid PlayerId; public string Name = ""; public int Rating; public int Wins; public int Losses; public long ServerTimeMs; public int PlayersOnline; public int PlayersQueued; public bool SteamAvailable; public bool SteamLinked; public bool SteamRequiredForRanked; public override MessageType Type => MessageType.ServerWelcome; public override void Write(PacketWriter w) { w.WriteGuid(PlayerId); w.WriteString(Name, 24); w.WriteInt32(Rating); w.WriteInt32(Wins); w.WriteInt32(Losses); w.WriteInt64(ServerTimeMs); w.WriteInt32(PlayersOnline); w.WriteInt32(PlayersQueued); w.WriteBool(SteamAvailable); w.WriteBool(SteamLinked); w.WriteBool(SteamRequiredForRanked); } public static ServerWelcome Read(PacketReader r) { return new ServerWelcome { PlayerId = r.ReadGuid(), Name = r.ReadString(24), Rating = r.ReadInt32(), Wins = r.ReadInt32(), Losses = r.ReadInt32(), ServerTimeMs = r.ReadInt64(), PlayersOnline = r.ReadInt32(), PlayersQueued = r.ReadInt32(), SteamAvailable = r.ReadBool(), SteamLinked = r.ReadBool(), SteamRequiredForRanked = r.ReadBool() }; } } public sealed class ErrorMessage : Message { public ErrorCode Code; public string Text = ""; public override MessageType Type => MessageType.Error; public ErrorMessage() { } public ErrorMessage(ErrorCode code, string text) { Code = code; Text = text ?? ""; } public override void Write(PacketWriter w) { w.WriteByte((byte)Code); w.WriteString(Text, 200); } public static ErrorMessage Read(PacketReader r) { return new ErrorMessage { Code = (ErrorCode)r.ReadByte(), Text = r.ReadString(200) }; } } public sealed class Ping : Message { public long ClientTimeMs; public override MessageType Type => MessageType.Ping; public override void Write(PacketWriter w) { w.WriteInt64(ClientTimeMs); } public static Ping Read(PacketReader r) { return new Ping { ClientTimeMs = r.ReadInt64() }; } } public sealed class Pong : Message { public long ClientTimeMs; public long ServerTimeMs; public override MessageType Type => MessageType.Pong; public override void Write(PacketWriter w) { w.WriteInt64(ClientTimeMs); w.WriteInt64(ServerTimeMs); } public static Pong Read(PacketReader r) { return new Pong { ClientTimeMs = r.ReadInt64(), ServerTimeMs = r.ReadInt64() }; } } public sealed class QueueJoin : Message { public string GameBuild = ""; public override MessageType Type => MessageType.QueueJoin; public override void Write(PacketWriter w) { w.WriteString(GameBuild, 32); } public static QueueJoin Read(PacketReader r) { return new QueueJoin { GameBuild = r.ReadString(32) }; } } public sealed class QueueLeave : Message { public override MessageType Type => MessageType.QueueLeave; public override void Write(PacketWriter w) { } public static QueueLeave Read(PacketReader r) { return new QueueLeave(); } } public sealed class QueueStatus : Message { public bool Queued; public int PlayersQueued; public int WaitSeconds; public override MessageType Type => MessageType.QueueStatus; public override void Write(PacketWriter w) { w.WriteBool(Queued); w.WriteInt32(PlayersQueued); w.WriteInt32(WaitSeconds); } public static QueueStatus Read(PacketReader r) { return new QueueStatus { Queued = r.ReadBool(), PlayersQueued = r.ReadInt32(), WaitSeconds = r.ReadInt32() }; } } public sealed class MatchFound : Message { public Guid MatchId; public int Seed; public string OpponentName = ""; public int OpponentRating; public bool Competitive = true; public int TimeLimitSeconds; public int ResumeElapsedMs = -1; public int Attempt = 1; public int GraceSeconds = 60; public bool Ranked = true; public MatchRules Rules; public MatchMode Mode; public byte YourRacer; public int PlayerCount = 2; public int ReadySeconds; public bool ArtifactBans { get { return (Rules & MatchRules.ArtifactBans) != 0; } set { Rules = (value ? (Rules | MatchRules.ArtifactBans) : (Rules & ~MatchRules.ArtifactBans)); } } public bool Party => Mode == MatchMode.Party; public override MessageType Type => MessageType.MatchFound; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteInt32(Seed); w.WriteString(OpponentName, 24); w.WriteInt32(OpponentRating); w.WriteBool(Competitive); w.WriteInt32(TimeLimitSeconds); w.WriteInt32(ResumeElapsedMs); w.WriteInt32(Attempt); w.WriteInt32(GraceSeconds); w.WriteBool(Ranked); w.WriteByte((byte)Rules); w.WriteByte((byte)Mode); w.WriteByte(YourRacer); w.WriteInt32(PlayerCount); w.WriteInt32(ReadySeconds); } public static MatchFound Read(PacketReader r) { return new MatchFound { MatchId = r.ReadGuid(), Seed = r.ReadInt32(), OpponentName = r.ReadString(24), OpponentRating = r.ReadInt32(), Competitive = r.ReadBool(), TimeLimitSeconds = r.ReadInt32(), ResumeElapsedMs = r.ReadInt32(), Attempt = r.ReadInt32(), GraceSeconds = r.ReadInt32(), Ranked = r.ReadBool(), Rules = (MatchRules)(r.ReadByte() & 0xF), Mode = ProtocolChecks.Mode(r.ReadByte()), YourRacer = r.ReadByte(), PlayerCount = r.ReadInt32(), ReadySeconds = r.ReadInt32() }; } } public sealed class LobbyCreate : Message { public string GameBuild = ""; public MatchMode Mode; public MatchRules Rules = MatchRules.Ranked; public override MessageType Type => MessageType.LobbyCreate; public override void Write(PacketWriter w) { w.WriteString(GameBuild, 32); w.WriteByte((byte)Mode); w.WriteByte((byte)Rules); } public static LobbyCreate Read(PacketReader r) { return new LobbyCreate { GameBuild = r.ReadString(32), Mode = ProtocolChecks.Mode(r.ReadByte()), Rules = (MatchRules)(r.ReadByte() & 0xF) }; } } public sealed class LobbyCreated : Message { public uint LobbyId; public string PublicAddress = ""; public int ExpiresInSeconds; public override MessageType Type => MessageType.LobbyCreated; public override void Write(PacketWriter w) { w.WriteUInt32(LobbyId); w.WriteString(PublicAddress, 100); w.WriteInt32(ExpiresInSeconds); } public static LobbyCreated Read(PacketReader r) { return new LobbyCreated { LobbyId = r.ReadUInt32(), PublicAddress = r.ReadString(100), ExpiresInSeconds = r.ReadInt32() }; } } public sealed class LobbyJoin : Message { public uint LobbyId; public string GameBuild = ""; public override MessageType Type => MessageType.LobbyJoin; public override void Write(PacketWriter w) { w.WriteUInt32(LobbyId); w.WriteString(GameBuild, 32); } public static LobbyJoin Read(PacketReader r) { return new LobbyJoin { LobbyId = r.ReadUInt32(), GameBuild = r.ReadString(32) }; } } public sealed class LobbyCancel : Message { public override MessageType Type => MessageType.LobbyCancel; public override void Write(PacketWriter w) { } public static LobbyCancel Read(PacketReader r) { return new LobbyCancel(); } } public sealed class LobbyClosed : Message { public uint LobbyId; public string Reason = ""; public override MessageType Type => MessageType.LobbyClosed; public override void Write(PacketWriter w) { w.WriteUInt32(LobbyId); w.WriteString(Reason, 200); } public static LobbyClosed Read(PacketReader r) { return new LobbyClosed { LobbyId = r.ReadUInt32(), Reason = r.ReadString(200) }; } } public sealed class MatchReady : Message { public Guid MatchId; public uint WorldHash; public int LevelCount; public override MessageType Type => MessageType.MatchReady; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteUInt32(WorldHash); w.WriteInt32(LevelCount); } public static MatchReady Read(PacketReader r) { return new MatchReady { MatchId = r.ReadGuid(), WorldHash = r.ReadUInt32(), LevelCount = r.ReadInt32() }; } } public sealed class MatchStart : Message { public Guid MatchId; public long GoServerTimeMs; public override MessageType Type => MessageType.MatchStart; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteInt64(GoServerTimeMs); } public static MatchStart Read(PacketReader r) { return new MatchStart { MatchId = r.ReadGuid(), GoServerTimeMs = r.ReadInt64() }; } } public sealed class StartCountdown : Message { public Guid MatchId; public int MsUntilGo; public override MessageType Type => MessageType.StartCountdown; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteInt32(MsUntilGo); } public static StartCountdown Read(PacketReader r) { return new StartCountdown { MatchId = r.ReadGuid(), MsUntilGo = r.ReadInt32() }; } } public abstract class StateMessage : Message { public const float MaxCoordinate = 1000000f; public int Attempt; public int RaceTimeMs; public float X; public float Y; public float Z; public float Yaw; public float Pitch; public StateFlags Flags; public float LeftX; public float LeftY; public float LeftZ; public float RightX; public float RightY; public float RightZ; public override void Write(PacketWriter w) { w.WriteInt32(Attempt); w.WriteInt32(RaceTimeMs); w.WriteFloat(X); w.WriteFloat(Y); w.WriteFloat(Z); w.WriteFloat(Yaw); w.WriteFloat(Pitch); w.WriteByte((byte)Flags); if ((Flags & StateFlags.LeftHold) != StateFlags.None) { w.WriteFloat(LeftX); w.WriteFloat(LeftY); w.WriteFloat(LeftZ); } if ((Flags & StateFlags.RightHold) != StateFlags.None) { w.WriteFloat(RightX); w.WriteFloat(RightY); w.WriteFloat(RightZ); } } protected void ReadBody(PacketReader r) { Attempt = r.ReadInt32(); RaceTimeMs = r.ReadInt32(); X = ReadCoordinate(r); Y = ReadCoordinate(r); Z = ReadCoordinate(r); Yaw = r.ReadFiniteFloat(); Pitch = r.ReadFiniteFloat(); Flags = (StateFlags)r.ReadByte(); if ((Flags & StateFlags.LeftHold) != StateFlags.None) { LeftX = ReadCoordinate(r); LeftY = ReadCoordinate(r); LeftZ = ReadCoordinate(r); } if ((Flags & StateFlags.RightHold) != StateFlags.None) { RightX = ReadCoordinate(r); RightY = ReadCoordinate(r); RightZ = ReadCoordinate(r); } } private static float ReadCoordinate(PacketReader r) { float num = r.ReadFiniteFloat(); if (num > 1000000f || num < -1000000f) { throw new ProtocolException("Coordinate out of range"); } return num; } public void CopyFrom(StateMessage other) { Attempt = other.Attempt; RaceTimeMs = other.RaceTimeMs; X = other.X; Y = other.Y; Z = other.Z; Yaw = other.Yaw; Pitch = other.Pitch; Flags = other.Flags; LeftX = other.LeftX; LeftY = other.LeftY; LeftZ = other.LeftZ; RightX = other.RightX; RightY = other.RightY; RightZ = other.RightZ; } } public sealed class PlayerState : StateMessage { public override MessageType Type => MessageType.PlayerState; public static PlayerState Read(PacketReader r) { PlayerState playerState = new PlayerState(); playerState.ReadBody(r); return playerState; } } public sealed class OpponentState : StateMessage { public byte Racer; public override MessageType Type => MessageType.OpponentState; public override void Write(PacketWriter w) { base.Write(w); w.WriteByte(Racer); } public static OpponentState Read(PacketReader r) { OpponentState opponentState = new OpponentState(); opponentState.ReadBody(r); opponentState.Racer = (byte)((r.Remaining > 0) ? r.ReadByte() : 0); return opponentState; } } public abstract class SplitMessage : Message { public int Attempt; public int Index; public string Level = ""; public string Region = ""; public int RaceTimeMs; public override void Write(PacketWriter w) { w.WriteInt32(Attempt); w.WriteInt32(Index); w.WriteString(Level, 64); w.WriteString(Region, 64); w.WriteInt32(RaceTimeMs); } protected void ReadBody(PacketReader r) { Attempt = r.ReadInt32(); Index = r.ReadInt32(); Level = r.ReadString(64); Region = r.ReadString(64); RaceTimeMs = r.ReadInt32(); } } public sealed class SplitReached : SplitMessage { public override MessageType Type => MessageType.SplitReached; public static SplitReached Read(PacketReader r) { SplitReached splitReached = new SplitReached(); splitReached.ReadBody(r); return splitReached; } } public sealed class OpponentSplit : SplitMessage { public byte Racer; public override MessageType Type => MessageType.OpponentSplit; public override void Write(PacketWriter w) { base.Write(w); w.WriteByte(Racer); } public static OpponentSplit Read(PacketReader r) { OpponentSplit opponentSplit = new OpponentSplit(); opponentSplit.ReadBody(r); opponentSplit.Racer = (byte)((r.Remaining > 0) ? r.ReadByte() : 0); return opponentSplit; } } public abstract class AttemptMessage : Message { public int Attempt; public int RaceTimeMs; public override void Write(PacketWriter w) { w.WriteInt32(Attempt); w.WriteInt32(RaceTimeMs); } protected void ReadBody(PacketReader r) { Attempt = r.ReadInt32(); RaceTimeMs = r.ReadInt32(); } } public sealed class AttemptStarted : AttemptMessage { public override MessageType Type => MessageType.AttemptStarted; public static AttemptStarted Read(PacketReader r) { AttemptStarted attemptStarted = new AttemptStarted(); attemptStarted.ReadBody(r); return attemptStarted; } } public sealed class OpponentAttempt : AttemptMessage { public byte Racer; public override MessageType Type => MessageType.OpponentAttempt; public override void Write(PacketWriter w) { base.Write(w); w.WriteByte(Racer); } public static OpponentAttempt Read(PacketReader r) { OpponentAttempt opponentAttempt = new OpponentAttempt(); opponentAttempt.ReadBody(r); opponentAttempt.Racer = (byte)((r.Remaining > 0) ? r.ReadByte() : 0); return opponentAttempt; } } public abstract class FinishMessage : Message { public int Attempt; public int RaceTimeMs; public bool Cheated; public override void Write(PacketWriter w) { w.WriteInt32(Attempt); w.WriteInt32(RaceTimeMs); w.WriteBool(Cheated); } protected void ReadBody(PacketReader r) { Attempt = r.ReadInt32(); RaceTimeMs = r.ReadInt32(); Cheated = r.ReadBool(); } } public sealed class PlayerFinished : FinishMessage { public override MessageType Type => MessageType.PlayerFinished; public static PlayerFinished Read(PacketReader r) { PlayerFinished playerFinished = new PlayerFinished(); playerFinished.ReadBody(r); return playerFinished; } } public sealed class OpponentFinished : FinishMessage { public byte Racer; public override MessageType Type => MessageType.OpponentFinished; public override void Write(PacketWriter w) { base.Write(w); w.WriteByte(Racer); } public static OpponentFinished Read(PacketReader r) { OpponentFinished opponentFinished = new OpponentFinished(); opponentFinished.ReadBody(r); opponentFinished.Racer = (byte)((r.Remaining > 0) ? r.ReadByte() : 0); return opponentFinished; } } public sealed class PlayerDied : Message { public int Attempt; public int RaceTimeMs; public string Cause = ""; public string Text = ""; public override MessageType Type => MessageType.PlayerDied; public override void Write(PacketWriter w) { w.WriteInt32(Attempt); w.WriteInt32(RaceTimeMs); w.WriteString(Cause, 32); w.WriteString(Text, 64); } public static PlayerDied Read(PacketReader r) { return new PlayerDied { Attempt = r.ReadInt32(), RaceTimeMs = r.ReadInt32(), Cause = r.ReadString(32), Text = r.ReadString(64) }; } } public sealed class PlayerForfeit : Message { public override MessageType Type => MessageType.PlayerForfeit; public override void Write(PacketWriter w) { } public static PlayerForfeit Read(PacketReader r) { return new PlayerForfeit(); } } public sealed class MatchResult : Message { public Guid MatchId; public MatchOutcome Outcome; public int YourTimeMs = -1; public int OpponentTimeMs = -1; public int OldRating; public int NewRating; public string Reason = ""; public MatchEnd End; public int Place; public List<Standing> Standings = new List<Standing>(); public override MessageType Type => MessageType.MatchResult; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteByte((byte)Outcome); w.WriteInt32(YourTimeMs); w.WriteInt32(OpponentTimeMs); w.WriteInt32(OldRating); w.WriteInt32(NewRating); w.WriteString(Reason, 200); w.WriteByte((byte)End); w.WriteByte((byte)Place); Standing.WriteList(w, Standings); } public static MatchResult Read(PacketReader r) { return new MatchResult { MatchId = r.ReadGuid(), Outcome = (MatchOutcome)r.ReadByte(), YourTimeMs = r.ReadInt32(), OpponentTimeMs = r.ReadInt32(), OldRating = r.ReadInt32(), NewRating = r.ReadInt32(), Reason = r.ReadString(200), End = (MatchEnd)r.ReadByte(), Place = r.ReadByte(), Standings = Standing.ReadList(r) }; } } public sealed class GameStatus : Message { public bool Connected; public override MessageType Type => MessageType.GameStatus; public override void Write(PacketWriter w) { w.WriteBool(Connected); } public static GameStatus Read(PacketReader r) { return new GameStatus { Connected = r.ReadBool() }; } } public sealed class MatchPaused : Message { public Guid MatchId; public string AwayName = ""; public int SecondsLeft; public int RaceTimeMs; public override MessageType Type => MessageType.MatchPaused; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteString(AwayName, 56); w.WriteInt32(SecondsLeft); w.WriteInt32(RaceTimeMs); } public static MatchPaused Read(PacketReader r) { return new MatchPaused { MatchId = r.ReadGuid(), AwayName = r.ReadString(56), SecondsLeft = r.ReadInt32(), RaceTimeMs = r.ReadInt32() }; } } public sealed class MatchResumed : Message { public Guid MatchId; public long GoServerTimeMs; public long ResumeServerTimeMs; public override MessageType Type => MessageType.MatchResumed; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteInt64(GoServerTimeMs); w.WriteInt64(ResumeServerTimeMs); } public static MatchResumed Read(PacketReader r) { return new MatchResumed { MatchId = r.ReadGuid(), GoServerTimeMs = r.ReadInt64(), ResumeServerTimeMs = r.ReadInt64() }; } } public sealed class ModHello : Message { public int ProtocolVersion = 7; public string ModVersion = ""; public string GameVersion = ""; public override MessageType Type => MessageType.ModHello; public override void Write(PacketWriter w) { w.WriteInt32(ProtocolVersion); w.WriteString(ModVersion, 32); w.WriteString(GameVersion, 64); } public static ModHello Read(PacketReader r) { return new ModHello { ProtocolVersion = r.ReadInt32(), ModVersion = r.ReadString(32), GameVersion = r.ReadString(64) }; } } public sealed class ClientStatus : Message { public const string MismatchPrefix = "VERSION MISMATCH"; public ClientPhase Phase; public string PlayerName = ""; public int Rating; public string Info = ""; public override MessageType Type => MessageType.ClientStatus; public override void Write(PacketWriter w) { w.WriteByte((byte)Phase); w.WriteString(PlayerName, 24); w.WriteInt32(Rating); w.WriteString(Info, 200); } public static ClientStatus Read(PacketReader r) { return new ClientStatus { Phase = (ClientPhase)r.ReadByte(), PlayerName = r.ReadString(24), Rating = r.ReadInt32(), Info = r.ReadString(200) }; } } public sealed class RacePaused : Message { public Guid MatchId; public string Reason = ""; public int SecondsLeft; public int RaceTimeMs = -1; public override MessageType Type => MessageType.RacePaused; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteString(Reason, 200); w.WriteInt32(SecondsLeft); w.WriteInt32(RaceTimeMs); } public static RacePaused Read(PacketReader r) { return new RacePaused { MatchId = r.ReadGuid(), Reason = r.ReadString(200), SecondsLeft = r.ReadInt32(), RaceTimeMs = r.ReadInt32() }; } } public sealed class PracticeMark : Message { public Guid MatchId; public int BestTimeMs = -1; public bool BestFinished; public int BestLevels; public override MessageType Type => MessageType.PracticeMark; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteInt32(BestTimeMs); w.WriteBool(BestFinished); w.WriteInt32(BestLevels); } public static PracticeMark Read(PacketReader r) { return new PracticeMark { MatchId = r.ReadGuid(), BestTimeMs = r.ReadInt32(), BestFinished = r.ReadBool(), BestLevels = r.ReadInt32() }; } } public sealed class PracticeRestart : Message { public Guid MatchId; public override MessageType Type => MessageType.PracticeRestart; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); } public static PracticeRestart Read(PacketReader r) { return new PracticeRestart { MatchId = r.ReadGuid() }; } } public sealed class RaceResumed : Message { public Guid MatchId; public int MsUntilGo; public int MsUntilResume; public override MessageType Type => MessageType.RaceResumed; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteInt32(MsUntilGo); w.WriteInt32(MsUntilResume); } public static RaceResumed Read(PacketReader r) { return new RaceResumed { MatchId = r.ReadGuid(), MsUntilGo = r.ReadInt32(), MsUntilResume = r.ReadInt32() }; } } public sealed class ProtocolException : Exception { public ProtocolException(string message) : base(message) { } } [StructLayout(LayoutKind.Explicit)] internal struct FloatBits { [FieldOffset(0)] public float Float; [FieldOffset(0)] public uint Bits; } public sealed class PacketWriter { private byte[] _data; private int _length; public int Length => _length; public byte[] Data => _data; public PacketWriter(int capacity = 128) { _data = new byte[capacity]; } public void Reset() { _length = 0; } public byte[] ToArray() { byte[] array = new byte[_length]; Buffer.BlockCopy(_data, 0, array, 0, _length); return array; } private void Ensure(int extra) { int num = _length + extra; if (num > _data.Length) { int num2; for (num2 = _data.Length * 2; num2 < num; num2 *= 2) { } Array.Resize(ref _data, num2); } } public void WriteByte(byte value) { Ensure(1); _data[_length++] = value; } public void WriteBool(bool value) { WriteByte(value ? ((byte)1) : ((byte)0)); } public void WriteUInt16(ushort value) { Ensure(2); _data[_length++] = (byte)value; _data[_length++] = (byte)(value >> 8); } public void WriteUInt32(uint value) { Ensure(4); for (int i = 0; i < 4; i++) { _data[_length++] = (byte)(value >> 8 * i); } } public void WriteInt32(int value) { WriteUInt32((uint)value); } public void WriteInt64(long value) { Ensure(8); for (int i = 0; i < 8; i++) { _data[_length++] = (byte)((ulong)value >> 8 * i); } } public void WriteFloat(float value) { FloatBits floatBits = new FloatBits { Float = value }; WriteUInt32(floatBits.Bits); } public void WriteGuid(Guid value) { byte[] src = value.ToByteArray(); Ensure(16); Buffer.BlockCopy(src, 0, _data, _length, 16); _length += 16; } public void WriteFixedBytes(byte[] value, int count) { if (value == null || value.Length != count) { throw new ProtocolException("Fixed byte field has wrong length"); } Ensure(count); Buffer.BlockCopy(value, 0, _data, _length, count); _length += count; } public void WriteString(string value, int maxChars) { if (value == null) { value = string.Empty; } if (value.Length > maxChars) { value = value.Substring(0, maxChars); } byte[] bytes = Encoding.UTF8.GetBytes(value); WriteUInt16((ushort)bytes.Length); Ensure(bytes.Length); Buffer.BlockCopy(bytes, 0, _data, _length, bytes.Length); _length += bytes.Length; } } public sealed class PacketReader { private readonly byte[] _data; private readonly int _end; private int _position; public int Remaining => _end - _position; public PacketReader(byte[] data, int offset, int count) { if (data == null || offset < 0 || count < 0 || offset + count > data.Length) { throw new ProtocolException("Invalid packet bounds"); } _data = data; _position = offset; _end = offset + count; } private void Need(int count) { if (count < 0 || _end - _position < count) { throw new ProtocolException("Packet truncated"); } } public byte ReadByte() { Need(1); return _data[_position++]; } public bool ReadBool() { return ReadByte() != 0; } public ushort ReadUInt16() { Need(2); ushort result = (ushort)(_data[_position] | (_data[_position + 1] << 8)); _position += 2; return result; } public uint ReadUInt32() { Need(4); uint num = 0u; for (int i = 0; i < 4; i++) { num |= (uint)(_data[_position + i] << 8 * i); } _position += 4; return num; } public int ReadInt32() { return (int)ReadUInt32(); } public long ReadInt64() { Need(8); ulong num = 0uL; for (int i = 0; i < 8; i++) { num |= (ulong)_data[_position + i] << 8 * i; } _position += 8; return (long)num; } public float ReadFloat() { FloatBits floatBits = new FloatBits { Bits = ReadUInt32() }; return floatBits.Float; } public float ReadFiniteFloat() { float num = ReadFloat(); if (float.IsNaN(num) || float.IsInfinity(num)) { throw new ProtocolException("Non-finite float"); } return num; } public Guid ReadGuid() { Need(16); byte[] array = new byte[16]; Buffer.BlockCopy(_data, _position, array, 0, 16); _position += 16; return new Guid(array); } public byte[] ReadFixedBytes(int count) { Need(count); byte[] array = new byte[count]; Buffer.BlockCopy(_data, _position, array, 0, count); _position += count; return array; } public string ReadString(int maxChars) { int num = ReadUInt16(); if (num > maxChars * 3) { throw new ProtocolException("String too long"); } Need(num); string text = Encoding.UTF8.GetString(_data, _position, num); _position += num; if (text.Length > maxChars) { throw new ProtocolException("String too long"); } return text; } } internal static class ProtocolChecks { public static MatchMode Mode(byte value) { if (value != 0 && value != 1) { throw new ProtocolException("Unknown match mode"); } return (MatchMode)value; } public static RacerStatus Status(byte value) { if (value > 6) { throw new ProtocolException("Unknown player status"); } return (RacerStatus)value; } public static int Count(PacketReader r, int max) { byte num = r.ReadByte(); if (num > max) { throw new ProtocolException("Too many entries"); } return num; } } public sealed class LobbyMember { public string Name = ""; public int Rating; public bool Host; public bool Racing; } public sealed class LobbyState : Message { public uint LobbyId; public MatchMode Mode; public MatchRules Rules; public int MaxPlayers; public bool YouAreHost; public bool Racing; public int ExpiresInSeconds = -1; public List<LobbyMember> Members = new List<LobbyMember>(); public override MessageType Type => MessageType.LobbyState; public override void Write(PacketWriter w) { w.WriteUInt32(LobbyId); w.WriteByte((byte)Mode); w.WriteByte((byte)Rules); w.WriteByte((byte)MaxPlayers); w.WriteBool(YouAreHost); w.WriteBool(Racing); w.WriteInt32(ExpiresInSeconds); if (Members.Count > 15) { throw new ProtocolException("Too many lobby members"); } w.WriteByte((byte)Members.Count); foreach (LobbyMember member in Members) { w.WriteString(member.Name, 24); w.WriteInt32(member.Rating); w.WriteBool(member.Host); w.WriteBool(member.Racing); } } public static LobbyState Read(PacketReader r) { LobbyState lobbyState = new LobbyState { LobbyId = r.ReadUInt32(), Mode = ProtocolChecks.Mode(r.ReadByte()), Rules = (MatchRules)(r.ReadByte() & 0xF), MaxPlayers = r.ReadByte(), YouAreHost = r.ReadBool(), Racing = r.ReadBool(), ExpiresInSeconds = r.ReadInt32() }; int num = ProtocolChecks.Count(r, 15); for (int i = 0; i < num; i++) { lobbyState.Members.Add(new LobbyMember { Name = r.ReadString(24), Rating = r.ReadInt32(), Host = r.ReadBool(), Racing = r.ReadBool() }); } return lobbyState; } } public sealed class LobbyStart : Message { public override MessageType Type => MessageType.LobbyStart; public override void Write(PacketWriter w) { } public static LobbyStart Read(PacketReader r) { return new LobbyStart(); } } public sealed class PlayerReady : Message { public Guid MatchId; public override MessageType Type => MessageType.PlayerReady; public override void Write(PacketWriter w) { w.WriteGuid(MatchId); } public static PlayerReady Read(PacketReader r) { return new PlayerReady { MatchId = r.ReadGuid() }; } } public sealed class RosterEntry { public byte Racer; public string Name = ""; public int Rating; public RacerStatus Status; public int TimeMs = -1; } public sealed class MatchRoster : Message { public Guid MatchId; public byte YourRacer; public int ReadySecondsLeft = -1; public List<RosterEntry> Racers = new List<RosterEntry>(); public override MessageType Type => MessageType.MatchRoster; public RosterEntry Find(int racer) { foreach (RosterEntry racer2 in Racers) { if (racer2.Racer == racer) { return racer2; } } return null; } public override void Write(PacketWriter w) { w.WriteGuid(MatchId); w.WriteByte(YourRacer); w.WriteInt32(ReadySecondsLeft); if (Racers.Count > 15) { throw new ProtocolException("Too many players"); } w.WriteByte((byte)Racers.Count); foreach (RosterEntry racer in Racers) { w.WriteByte(racer.Racer); w.WriteString(racer.Name, 24); w.WriteInt32(racer.Rating); w.WriteByte((byte)racer.Status); w.WriteInt32(racer.TimeMs); } } public static MatchRoster Read(PacketReader r) { MatchRoster matchRoster = new MatchRoster { MatchId = r.ReadGuid(), YourRacer = r.ReadByte(), ReadySecondsLeft = r.ReadInt32() }; int num = ProtocolChecks.Count(r, 15); for (int i = 0; i < num; i++) { matchRoster.Racers.Add(new RosterEntry { Racer = r.ReadByte(), Name = r.ReadString(24), Rating = r.ReadInt32(), Status = ProtocolChecks.Status(r.ReadByte()), TimeMs = r.ReadInt32() }); } return matchRoster; } } public sealed class Standing { public byte Racer; public string Name = ""; public int Place; public RacerStatus Status; public int TimeMs = -1; public int Levels; public string Note = ""; internal static void WriteList(PacketWriter w, List<Standing> list) { int num = list?.Count ?? 0; if (num > 15) { throw new ProtocolException("Too many standings"); } w.WriteByte((byte)num); for (int i = 0; i < num; i++) { Standing standing = list[i]; w.WriteByte(standing.Racer); w.WriteString(standing.Name, 24); w.WriteByte((byte)standing.Place); w.WriteByte((byte)standing.Status); w.WriteInt32(standing.TimeMs); w.WriteInt32(standing.Levels); w.WriteString(standing.Note, 64); } } internal static List<Standing> ReadList(PacketReader r) { int num = ProtocolChecks.Count(r, 15); List<Standing> list = new List<Standing>(num); for (int i = 0; i < num; i++) { list.Add(new Standing { Racer = r.ReadByte(), Name = r.ReadString(24), Place = r.ReadByte(), Status = ProtocolChecks.Status(r.ReadByte()), TimeMs = r.ReadInt32(), Levels = r.ReadInt32(), Note = r.ReadString(64) }); } return list; } } public sealed class ModSettings : Message { public int GhostOpacityPercent = 45; public override MessageType Type => MessageType.ModSettings; public override void Write(PacketWriter w) { w.WriteByte((byte)Math.Max(0, Math.Min(100, GhostOpacityPercent))); } public static ModSettings Read(PacketReader r) { return new ModSettings { GhostOpacityPercent = Math.Min(100, (int)r.ReadByte()) }; } } public sealed class ChatSend : Message { public string Text = ""; public override MessageType Type => MessageType.ChatSend; public override void Write(PacketWriter w) { w.WriteString(Text, 120); } public static ChatSend Read(PacketReader r) { return new ChatSend { Text = r.ReadString(120) }; } } public sealed class ChatMessage : Message { public string From = ""; public string Text = ""; public bool System; public override MessageType Type => MessageType.ChatMessage; public override void Write(PacketWriter w) { w.WriteString(From, 24); w.WriteString(Text, 200); w.WriteBool(System); } public static ChatMessage Read(PacketReader r) { return new ChatMessage { From = r.ReadString(24), Text = r.ReadString(200), System = r.ReadBool() }; } } public static class ProtocolConstants { public const int ProtocolVersion = 7; public const int DefaultServerPort = 47600; public const int DefaultLocalPort = 47677; public const int MaxFrameSize = 2048; public const int MaxNameLength = 24; public const int MaxLevelNameLength = 64; public const int MaxTextLength = 200; public const int MaxAddressLength = 100; public const int MaxUrlLength = 1500; public const int MaxItemNameLength = 48; public const int MaxBanPool = 16; public const int TokenLength = 32; public const int MinSeed = 1000000; public const int MaxSeed = 9999999; public const int MaxSplitIndex = 1000; public const int MaxAttempt = 100000; public const int MaxPartyPlayers = 15; public const int MaxChatLength = 120; public const int MaxDeathCauseLength = 32; public const int MaxDeathTextLength = 64; } public static class RaceFormat { public static string LevelLabel(string raw) { if (string.IsNullOrEmpty(raw)) { return ""; } return Regex.Replace(Regex.Replace(Regex.Replace(raw, "^M\\d+_", ""), "^(Silos|Pipeworks|Habitation)_", "").Replace("Campaign_Interlude_", "").Replace('_', ' '), "(?<=[a-z])(?=[A-Z])", " ").Trim(); } public static string ItemLabel(string raw) { if (string.IsNullOrEmpty(raw)) { return ""; } return Regex.Replace(Regex.Replace(raw, "^Item_(Artifact_)?", "").Replace('_', ' '), "(?<=[a-z])(?=[A-Z])", " ").Trim(); } public static string Time(long ms, int decimals = 2) { if (ms < 0) { ms = 0L; } long num = ms / 1000; int millis = (int)(ms % 1000); long num2 = num / 3600; int num3 = (int)(num / 60 % 60); int num4 = (int)(num % 60); string text = Fraction(millis, decimals); if (num2 > 0) { return string.Format(CultureInfo.InvariantCulture, "{0}:{1:00}:{2:00}{3}", num2, num3, num4, text); } return string.Format(CultureInfo.InvariantCulture, "{0}:{1:00}{2}", num3, num4, text); } public static string Delta(long ms, int decimals = 2) { string text = ((ms < 0) ? "-" : "+"); long num = Math.Abs(ms); if (num < 60000) { return text + (num / 1000).ToString(CultureInfo.InvariantCulture) + Fraction((int)(num % 1000), decimals); } return text + Time(num, decimals); } private static string Fraction(int millis, int decimals) { if (decimals <= 0) { return string.Empty; } string text = millis.ToString("000", CultureInfo.InvariantCulture); return "." + text.Substring(0, Math.Min(3, decimals)); } } public static class Fnv1a { public const uint Offset = 2166136261u; public static uint Add(uint hash, string value) { if (value != null) { for (int i = 0; i < value.Length; i++) { hash ^= value[i]; hash *= 16777619; } } hash ^= 0xFF; hash *= 16777619; return hash; } public static uint Add(uint hash, int value) { for (int i = 0; i < 4; i++) { hash ^= (byte)(value >> 8 * i); hash *= 16777619; } return hash; } } public static class SplitKeys { public static string Key(string level, int occurrence) { string text = level ?? ""; if (occurrence > 1) { return text + "#" + occurrence; } return text; } public static List<string> Build(IEnumerable<string> levelsInOrder) { Dictionary<string, int> dictionary = new Dictionary<string, int>(); List<string> list = new List<string>(); foreach (string item in levelsInOrder) { string text = item ?? ""; dictionary.TryGetValue(text, out var value); value = (dictionary[text] = value + 1); list.Add(Key(text, value)); } return list; } } public sealed class RacerProgress { public int Racer; public string Name = ""; public bool Finished; public int FinishMs = -1; public int FinishOrder; public bool Out; public int OutMs = -1; public bool Disqualified; public readonly List<string> Route = new List<string>(); public readonly List<int> RouteMs = new List<int>(); public readonly List<string> Regions = new List<string>(); public int Levels => Route.Count; public int LastMs { get { if (RouteMs.Count != 0) { return RouteMs[RouteMs.Count - 1]; } return 0; } } public bool Done { get { if (!Finished && !Out) { return Disqualified; } return true; } } public bool OnShortcut { get { if (!Done && Regions.Count > 0) { return Standings.IsShortcut(Regions[Regions.Count - 1]); } return false; } } public void AddSplit(string level, string region, int timeMs) { Route.Add(level ?? ""); Regions.Add(region ?? ""); RouteMs.Add(timeMs); } } public static class Standings { private static readonly string[] ShortcutRegions = new string[5] { "shortcut", "Tangled Sink", "Expulsion Chute", "Rotten Duct", "Hadal Diver" }; public static bool IsShortcut(string region) { if (string.IsNullOrEmpty(region)) { return false; } string[] shortcutRegions = ShortcutRegions; for (int i = 0; i < shortcutRegions.Length; i++) { if (string.Equals(shortcutRegions[i], region.Trim(), StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } public static bool SameRoute(RacerProgress a, RacerProgress b) { int num = Math.Min(a.Route.Count, b.Route.Count); for (int i = 0; i < num; i++) { if (!string.Equals(a.Route[i], b.Route[i], StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } public static int Compare(RacerProgress a, RacerProgress b, int nowMs) { if (a.Disqualified != b.Disqualified) { if (!a.Disqualified) { return -1; } return 1; } if (a.Finished != b.Finished) { if (!a.Finished) { return 1; } return -1; } if (a.Finished) { if (a.FinishMs != b.FinishMs) { return a.FinishMs.CompareTo(b.FinishMs); } if (a.FinishOrder > 0 && b.FinishOrder > 0) { return a.FinishOrder.CompareTo(b.FinishOrder); } } if (SameRoute(a, b)) { if (a.Levels != b.Levels) { return b.Levels.CompareTo(a.Levels); } int num = a.LastMs.CompareTo(b.LastMs); if (num != 0) { return num; } } else { int num2 = (a.Out ? a.OutMs : nowMs); int num3 = (b.Out ? b.OutMs : nowMs); if (num2 != num3) { return num3.CompareTo(num2); } if (a.Levels != b.Levels) { return b.Levels.CompareTo(a.Levels); } } if (a.Out != b.Out) { if (!a.Out) { return -1; } return 1; } return a.Racer.CompareTo(b.Racer); } public static List<RacerProgress> Rank(IEnumerable<RacerProgress> racers, int nowMs) { List<RacerProgress> list = new List<RacerProgress>(); foreach (RacerProgress racer in racers) { int num = list.Count; while (num > 0 && Compare(racer, list[num - 1], nowMs) < 0) { num--; } list.Insert(num, racer); } return list; } public static string Ordinal(int place) { int num = place % 100; string text = ((num >= 11 && num <= 13) ? "th" : ((place % 10 == 1) ? "st" : ((place % 10 == 2) ? "nd" : ((place % 10 == 3) ? "rd" : "th")))); return place + text; } } public static class DeathText { public static string ForOthers(string text) { if (string.IsNullOrEmpty(text)) { return ""; } string[] array = text.Split(' '); for (int i = 0; i < array.Length; i++) { string text2 = array[i]; string text3 = text2.TrimEnd('.', ',', '!', '?'); string text4 = text2.Substring(text3.Length); string text5 = Swap(text3); if (text5 != null) { array[i] = text5 + text4; } } return string.Join(" ", array); } private static string Swap(string word) { string text; switch (word.ToLowerInvariant()) { case "your": text = "their"; break; case "yours": text = "theirs"; break; case "yourself": text = "themselves"; break; case "you": text = "they"; break; default: return null; } if (word == word.ToUpperInvariant()) { return text.ToUpperInvariant(); } if (char.IsUpper(word[0])) { return char.ToUpperInvariant(text[0]) + text.Substring(1); } return text; } } public static class RulesText { public static string Describe(MatchRules rules) { List<string> list = new List<string>(); if ((rules & MatchRules.ArtifactBans) != MatchRules.None) { list.Add("artifact bans"); } MatchRules matchRules = rules & MatchRules.CuratedSeeds; switch (matchRules) { case MatchRules.CuratedSeeds: list.Add("curated seeds"); break; default: { List<string> list2 = new List<string>(); if ((matchRules & MatchRules.DeepStorageFood) != MatchRules.None) { list2.Add("food"); } if ((matchRules & MatchRules.GuaranteedPerks) != MatchRules.None) { list2.Add("perks"); } if ((matchRules & MatchRules.GuaranteedItems) != MatchRules.None) { list2.Add("items"); } list.Add("curated seeds (" + string.Join(", ", list2) + ")"); break; } case MatchRules.None: break; } if (list.Count == 0) { return "No modifiers"; } string text = string.Join(", ", list); return char.ToUpperInvariant(text[0]) + text.Substring(1); } public static string Mode(MatchMode mode) { if (mode != MatchMode.Party) { return "1v1"; } return "Party"; } } public sealed class SteamLinkStart : Message { public override MessageType Type => MessageType.SteamLinkStart; public override void Write(PacketWriter w) { } public static SteamLinkStart Read(PacketReader r) { return new SteamLinkStart(); } } public sealed class SteamLinkUrl : Message { public string Url = ""; public override MessageType Type => MessageType.SteamLinkUrl; public override void Write(PacketWriter w) { w.WriteString(Url, 1500); } public static SteamLinkUrl Read(PacketReader r) { return new SteamLinkUrl { Url = r.ReadString(1500) }; } } public sealed class SteamLinkResult : Message { public bool Ok; public string Message = ""; public Guid PlayerId; public byte[] Token = new byte[0]; public string Name = ""; public int Rating; public int Wins; public int Losses; public override MessageType Type => MessageType.SteamLinkResult; public override void Write(PacketWriter w) { w.WriteBool(Ok); w.WriteString(Message, 200); w.WriteGuid(PlayerId); if (Token.Length != 0 && Token.Length != 32) { throw new ProtocolException("Bad token length"); } w.WriteBool(Token.Length != 0); if (Token.Length != 0) { w.WriteFixedBytes(Token, 32); } w.WriteString(Name, 24); w.WriteInt32(Rating); w.WriteInt32(Wins); w.WriteInt32(Losses); } public static SteamLinkResult Read(PacketReader r) { return new SteamLinkResult { Ok = r.ReadBool(), Message = r.ReadString(200), PlayerId = r.ReadGuid(), Token = (r.ReadBool() ? r.ReadFixedBytes(32) : new byte[0]), Name = r.ReadString(24), Rating = r.ReadInt32(), Wins = r.ReadInt32(), Losses = r.ReadInt32() }; } } public sealed class VoiceHello : Message { public int Port; public byte[] Key = new byte[16]; public ulong Token; public int RangeMeters = 65; public override MessageType Type => MessageType.VoiceHello; public override void Write(PacketWriter w) { w.WriteInt32(Port); w.WriteFixedBytes(Key, 16); w.WriteInt64((long)Token); w.WriteInt32(RangeMeters); } public static VoiceHello Read(PacketReader r) { return new VoiceHello { Port = r.ReadInt32(), Key = r.ReadFixedBytes(16), Token = (ulong)r.ReadInt64(), RangeMeters = r.ReadInt32() }; } } public sealed class VoiceState : Message { public bool Enabled; public override MessageType Type => MessageType.VoiceState; public override void Write(PacketWriter w) { w.WriteBool(Enabled); } public static VoiceState Read(PacketReader r) { return new VoiceState { Enabled = r.ReadBool() }; } } public sealed class VoicePeer { public ushort VoiceId; public string Name = ""; public byte Racer = byte.MaxValue; public const byte NoRacer = byte.MaxValue; } public sealed class VoicePeers : Message { public List<VoicePeer> Peers = new List<VoicePeer>(); public override MessageType Type => MessageType.VoicePeers; public override void Write(PacketWriter w) { if (Peers.Count > 15) { throw new ProtocolException("Too many voice peers"); } w.WriteByte((byte)Peers.Count); foreach (VoicePeer peer in Peers) { w.WriteUInt16(peer.VoiceId); w.WriteString(peer.Name, 24); w.WriteByte(peer.Racer); } } public static VoicePeers Read(PacketReader r) { VoicePeers voicePeers = new VoicePeers(); int num = ProtocolChecks.Count(r, 15); for (int i = 0; i < num; i++) { voicePeers.Peers.Add(new VoicePeer { VoiceId = r.ReadUInt16(), Name = r.ReadString(24), Racer = r.ReadByte() }); } return voicePeers; } } public sealed class VoiceActivity : Message { public bool On; public bool Me; public List<byte> Racers = new List<byte>(); public override MessageType Type => MessageType.VoiceActivity; public override void Write(PacketWriter w) { w.WriteBool(On); w.WriteBool(Me); int num = Math.Min(Racers.Count, 15); w.WriteByte((byte)num); for (int i = 0; i < num; i++) { w.WriteByte(Racers[i]); } } public static VoiceActivity Read(PacketReader r) { VoiceActivity voiceActivity = new VoiceActivity { On = r.ReadBool(), Me = r.ReadBool() }; int num = ProtocolChecks.Count(r, 15); for (int i = 0; i < num; i++) { voiceActivity.Racers.Add(r.ReadByte()); } return voiceActivity; } } public static class VoiceWire { public const byte Magic = 87; public const byte KindAudio = 1; public const byte KindKeepAlive = 2; public const byte KindRelay = 3; public const int KeyLength = 16; public const int TagLength = 16; public const int HeaderLength = 18; public const int MaxOpusBytes = 400; public const int MaxPacket = 438; public const int SampleRate = 48000; public const int FrameSamples = 960; private const byte ToServer = 1; private const byte ToClient = 2; public static byte[] NewKey() { byte[] array = new byte[16]; RandomNumberGenerator.Fill(array); return array; } public static byte[] SealToServer(byte[] key, ulong token, ulong counter, byte kind, uint seq, ReadOnlySpan<byte> opus) { return Seal(key, kind, token, counter, 1, seq, opus, kind == 1); } public static byte[] SealToClient(byte[] listenerKey, ushort speaker, ulong counter, uint seq, ReadOnlySpan<byte> opus) { return Seal(listenerKey, 3, speaker, counter, 2, seq, opus, audio: true); } private static byte[] Seal(byte[] key, byte kind, ulong id, ulong counter, byte direction, uint seq, ReadOnlySpan<byte> opus, bool audio) { int num = (audio ? (4 + opus.Length) : 0); byte[] array = new byte[18 + num + 16]; array[0] = 87; array[1] = kind; WriteU64(array, 2, id); WriteU64(array, 10, counter); byte[] array2 = new byte[num]; if (audio) { array2[0] = (byte)seq; array2[1] = (byte)(seq >> 8); array2[2] = (byte)(seq >> 16); array2[3] = (byte)(seq >> 24); opus.CopyTo(array2.AsSpan(4)); } using AesGcm aesGcm = new AesGcm(key); aesGcm.Encrypt(Nonce(direction, counter), array2, array.AsSpan(18, num), array.AsSpan(18 + num, 16), array.AsSpan(0, 18)); return array; } public static bool TryHeader(byte[] packet, int length, out byte kind, out ulong id, out ulong counter) { kind = 0; id = 0uL; counter = 0uL; if (packet == null || length < 34 || length > 438 || packet[0] != 87) { return false; } kind = packet[1]; id = ReadU64(packet, 2); counter = ReadU64(packet, 10); if (kind != 1 && kind != 2) { return kind == 3; } return true; } public static bool TryOpen(byte[] key, byte[] packet, int length, bool toServer, out byte[] body) { body = null; if (!TryHeader(packet, length, out var _, out var _, out var counter)) { return false; } int num = length - 18 - 16; if (num != 0 && num < 4) { return false; } byte[] array = new byte[num]; try { using AesGcm aesGcm = new AesGcm(key); aesGcm.Decrypt(Nonce((byte)(toServer ? 1 : 2), counter), packet.AsSpan(18, num), packet.AsSpan(18 + num, 16), array, packet.AsSpan(0, 18)); } catch (CryptographicException) { return false; } body = array; return true; } public static uint Seq(byte[] body) { return (uint)(body[0] | (body[1] << 8) | (body[2] << 16) | (body[3] << 24)); } private static byte[] Nonce(byte direction, ulong counter) { byte[] obj = new byte[12] { direction, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; WriteU64(obj, 4, counter); return obj; } private static void WriteU64(byte[] b, int at, ulong v) { for (int i = 0; i < 8; i++) { b[at + i] = (byte)(v >> 8 * i); } } private static ulong ReadU64(byte[] b, int at) { ulong num = 0uL; for (int i = 0; i < 8; i++) { num |= (ulong)b[at + i] << 8 * i; } return num; } } } namespace WKRace.Mod { internal static class ArtifactBans { private static List<string> _pool; private static readonly Dictionary<string, string> Labels = new Dictionary<string, string>(); private static readonly HashSet<string> Banned = new HashSet<string>(); public static void Apply(Harmony harmony) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ENV_ArtifactDevice), "Start", (Type[])null, (Type[])null); if (methodInfo == null) { Plugin.Log.LogError((object)"Patch target missing for artifact bans. The game may have been updated."); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ArtifactBans), "BeforeDeviceStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } public static List<string> Pool() { if (_pool != null) { return _pool; } try { List<string> list = new List<string>(); WKAssetDatabase fullCombinedAssetDatabase = CL_AssetManager.GetFullCombinedAssetDatabase(); if ((Object)(object)fullCombinedAssetDatabase == (Object)null || fullCombinedAssetDatabase.levelAssets == null || fullCombinedAssetDatabase.levelAssets.Count == 0) { return null; } foreach (LevelAssetHolder levelAsset in fullCombinedAssetDatabase.levelAssets) { M_Level val = ((levelAsset != null) ? levelAsset.GetLevel() : null); if ((Object)(object)val == (Object)null) { continue; } ENV_ArtifactDevice[] componentsInChildren = ((Component)val).GetComponentsInChildren<ENV_ArtifactDevice>(true); foreach (ENV_ArtifactDevice val2 in componentsInChildren) { if (val2.artifacts == null) { continue; } foreach (Item_Object artifact in val2.artifacts) { if (!((Object)(object)artifact == (Object)null) && !list.Contains(((Object)artifact).name)) { list.Add(((Object)artifact).name); string value = ((artifact.itemData != null) ? artifact.itemData.itemName : null); if (!string.IsNullOrEmpty(value)) { Labels[((Object)artifact).name] = value; } } } } } _pool = list; Plugin.Log.LogInfo((object)("Artifact pool: " + string.Join(", ", list))); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not read the artifact pool: " + ex.Message)); } return _pool; } public static string Label(string item) { if (item != null && Labels.TryGetValue(item, out var value)) { return value; } return RaceFormat.ItemLabel(item); } public static void Set(IEnumerable<string> items) { Banned.Clear(); if (items == null) { return; } foreach (string item in items) { Banned.Add(item); } } public static void Clear() { Banned.Clear(); } public static IEnumerable<string> Current() { return Banned.OrderBy<string, string>((string b) => b, StringComparer.Ordinal); } private static void BeforeDeviceStart(ENV_ArtifactDevice __instance) { try { if (!RaceRules.InRaceWorld || Banned.Count == 0 || __instance.artifacts == null) { return; } List<Item_Object> list = __instance.artifacts.Where((Item_Object a) => (Object)(object)a != (Object)null && !Banned.Contains(((Object)a).name)).ToList(); if (list.Count != __instance.artifacts.Count) { if (__instance.artifactSpawners != null && list.Count < __instance.artifactSpawners.Count) { __instance.artifactSpawners = __instance.artifactSpawners.Take(list.Count).ToList(); } __instance.artifacts = list; Plugin.Log.LogInfo((object)("Artifact bans applied: " + string.Join(", ", Banned) + " (" + list.Count + " left)")); } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Artifact bans failed: " + ex.Message)); } } } internal sealed class ChatBox { public struct Line { public string From; public string Text; public bool System; public float At; } public const int ShownLines = 8; public const float LineSeconds = 14f; public readonly List<Line> Lines = new List<Line>(); private int _openedFrame; public bool Typing { get; private set; } public string Draft { get; private set; } = ""; public void Add(ChatMessage m) { Lines.Add(new Line { From = (m.From ?? ""), Text = (m.Text ?? ""), System = m.System, At = Time.unscaledTime }); while (Lines.Count > 30) { Lines.RemoveAt(0); } } public void Note(string text) { Lines.Add(new Line { Text = text, System = true, At = Time.unscaledTime }); } public void Open() { if (!Typing) { Typing = true; Draft = ""; _openedFrame = Time.frameCount; ChatInputBlock.Begin(); } } public void Close() { if (Typing) { Typing = false; Draft = ""; ChatInputBlock.End(); } } public string Update() { if (!Typing) { return null; } if (Input.GetKeyDown((KeyCode)27)) { Close(); return null; } if (Time.frameCount == _openedFrame) { return null; } bool flag = Input.GetKeyDown((KeyCode)13) || Input.GetKeyDown((KeyCode)271); StringBuilder stringBuilder = new StringBuilder(Draft); string inputString = Input.inputString; foreach (char c in inputString) { switch (c) { case '\b': if (stringBuilder.Length > 0) { stringBuilder.Length--; } break; case '\n': case '\r': flag = true; break; default: if (!char.IsControl(c) && stringBuilder.Length < 120) { stringBuilder.Append(c); } break; } } Draft = stringBuilder.ToString(); if (!flag) { return null; } string text = Draft.Trim(); Close(); if (text.Length <= 0) { return null; } return text; } } internal static class ChatInputBlock { private static int _endFrame = -10; private static bool _typing; private static readonly Dictionary<ButtonInput, bool> Held = new Dictionary<ButtonInput, bool>(); private static FieldRef<InputManager, List<ButtonInput>> _buttons; private static FieldRef<InputManager, List<VectorInput>> _vectors; public static bool Active { get { if (!_typing) { return Time.frameCount <= _endFrame + 1; } return true; } } public static void Apply(Harmony harmony) { _buttons = AccessTools.FieldRefAccess<InputManager, List<ButtonInput>>("buttonInputs"); _vectors = AccessTools.FieldRefAccess<InputManager, List<VectorInput>>("vectorInputs"); PatchOne(harmony, AccessTools.Method(typeof(InputManager), "UpdateInputs", (Type[])null, (Type[])null), null, "AfterUpdateInputs"); PatchOne(harmony, AccessTools.Method(typeof(CommandConsole), "LateUpdate", (Type[])null, (Type[])null), "BeforeConsoleUpdate", null); } private static void PatchOne(Harmony harmony, MethodBase target, string prefix, string postfix) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (target == null) { Plugin.Log.LogError((object)("Chat input block: patch target missing for " + (prefix ?? postfix) + ". The game may have been updated.")); return; } try { harmony.Patch(target, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(ChatInputBlock), prefix, (Type[])null), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(ChatInputBlock), postfix, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } catch (Exception ex) { Plugin.Log.LogError((object)("Chat input block: could not patch " + target.Name + ": " + ex.Message)); } } public static void Begin() { _typing = true; Held.Clear(); InputManager instance = InputManager.instance; List<ButtonInput> list = (((Object)(object)instance != (Object)null && _buttons != null) ? _buttons.Invoke(instance) : null); if (list == null) { return; } foreach (ButtonInput item in list) { if (item != null) { Held[item] = item.Pressed; } } } public static void End() { _typing = false; _endFrame = Time.frameCount; } private static void AfterUpdateInputs(InputManager __instance) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) if (!Active) { return; } try { List<ButtonInput> list = _buttons.Invoke(__instance); if (list != null) { foreach (ButtonInput item in list) { if (item != null) { item.Pressed = Held.TryGetValue(item, out var value) && value; item.Down = false; item.Up = false; } } } List<VectorInput> list2 = _vectors.Invoke(__instance); if (list2 == null) { return; } foreach (VectorInput item2 in list2) { if (item2 != null) { item2.vector = Vector2.zero; item2.axis = 0f; item2.Pressed = false; item2.Down = false; item2.Up = false; } } } catch (Exception ex) { Plugin.Log.LogError((object)("Chat input block failed: " + ex.Message)); _typing = false; } } private static bool BeforeConsoleUpdate() { return !Active; } } internal static class ClientStarter { private const string ClientExe = "WKSR.exe"; public static void OpenIfInstalled(string pluginPath) { try { if (RaceConfig.OpenClient.Value && !string.IsNullOrEmpty(pluginPath)) { string directoryName = Path.GetDirectoryName(pluginPath); string text = ((directoryName == null) ? null : Path.Combine(directoryName, "WKSR.exe")); if (text != null && File.Exists(text) && Process.GetProcessesByName(Path.GetFileNameWithoutExtension("WKSR.exe")).Length == 0) { Process.Start(new ProcessStartInfo(text) { UseShellExecute = true, WorkingDirectory = directoryName }); Plugin.Log.LogInfo((object)("Opened WKSR (" + text + ").")); } } } catch (Exception ex) { Plugin.Log.LogWarning((object)("Could not open WKSR: " + ex.Message)); } } } internal static class GameBridge { public const string MenuScene = "Main-Menu"; public const string RunScene = "Game-Main"; public const string CampaignModeName = "Campaign"; private const string CampaignAssetName = "GM_Campaign"; public static bool Frozen; private static bool _lockedByUs; private static Vector3 _savedVelocity; private static readonly FieldRef<ENT_Player, Vector3> PlayerVelocity = SafeFieldRef("vel"); public static bool InMenu { get { //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) Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name == "Main-Menu"; } } public static bool InRunScene { get { //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) Scene activeScene = SceneManager.GetActiveScene(); return ((Scene)(ref activeScene)).name == "Game-Main"; } } public static ENT_Player Player => ENT_Player.playerObject; public static string GameVersion() { try { return DarkMachineFunctions.GetVersionName("").Trim(); } catch (Exception) { return Application.version; } } public static M_Gamemode FindCampaign() { M_Gamemode val = null; M_Gamemode[] array = Resources.FindObjectsOfTypeAll<M_Gamemode>(); foreach (M_Gamemode val2 in array) { if (((Object)val2).name == "GM_Campaign") { return val2; } if ((Object)(object)val == (Object)null && val2.baseGamemode && !val2.devOnly && val2.allowCompetitive && string.Equals(val2.gamemodeName, "Campaign", StringComparison.OrdinalIgnoreCase)) { val = val2; } } return val; } public static void BeginSeededRun(M_Gamemode gamemode, int seed) { WorldLoader.SetPresetSeed(seed.ToString(CultureInfo.InvariantCulture)); CL_GameManager.gamemode = gamemode; SceneManager.LoadScene(gamemode.gamemodeScene); } public static void ApplySeed(int seed) { WorldLoader.SetPresetSeed(seed.ToString(CultureInfo.InvariantCulture)); } public static bool IsWorldReady() { if (InRunScene && (Object)(object)WorldLoader.instance != (Object)null && WorldLoader.isLoaded && (Object)(object)CL_GameManager.gMan != (Object)null && (Object)(object)CL_GameManager.gMan.localPlayer != (Object)null) { return !CL_GameManager.IsLoading(); } return false; } public static string DeathText(string cause) { try { string localization = CL_LocalizationManager.GetLocalization("deathmessages", cause ?? ""); if (string.IsNullOrEmpty(localization)) { localization = CL_LocalizationManager.GetLocalization("deathmessages", "basic"); } return localization ?? ""; } catch (Exception) { return ""; } } public static Vector3 OriginOffset() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) CL_GameManager gMan = CL_GameManager.gMan; if (!((Object)(object)gMan == (Object)null)) { return -gMan.GetCorrectedPosition(Vector3.zero); } return Vector3.zero; } public static Vector3 ToShared(Vector3 local) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) CL_GameManager gMan = CL_GameManager.gMan; if (!((Object)(object)gMan == (Object)null)) { return gMan.GetCorrectedPosition(local); } return local; } public static string LevelNameAhead(int steps) { WorldLoader instance = WorldLoader.instance; if ((Object)(object)instance == (Object)null) { return ""; } BranchInfo currentBranch = instance.GetCurrentBranch(); LevelInfo currentLevel = instance.GetCurrentLevel(); if (currentBranch == null || currentBranch.levelTracker == null || currentLevel == null) { return ""; } int num = currentBranch.levelTracker.IndexOf(currentLevel) + steps; if (num < steps || num >= currentBranch.levelTracker.Count) { return ""; } M_Level level = currentBranch.levelTracker[num].GetLevel(); if (!((Object)(object)level != (Object)null)) { return ""; } return level.levelName; } public static uint WorldFingerprint(out int levelCount) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) levelCount = 0; WorldLoader instance = WorldLoader.instance; if ((Object)(object)instance == (Object)null) { return 0u; } BranchInfo currentBranch = instance.GetCurrentBranch(); if (currentBranch == null || currentBranch.levelTracker == null) { return 0u; } uint hash = Fnv1a.Add(2166136261u, 2); hash = Fnv1a.Add(hash, (int)RaceGuarantees.Rules); foreach (string item in ArtifactBans.Current()) { hash = Fnv1a.Add(hash, item); } foreach (LevelInfo item2 in currentBranch.levelTracker) { M_Level level = item2.GetLevel(); if (!((Object)(object)level == (Object)null)) { LevelAssetHolder levelAssetReference = item2.GetLevelAssetReference(); Vector3 val = ToShared(((Component)level).transform.position); hash = Fnv1a.Add(hash, (levelAssetReference != null) ? levelAssetReference.id : level.levelName); hash = Fnv1a.Add(hash, Mathf.RoundToInt(val.x * 10f)); hash = Fnv1a.Add(hash, Mathf.RoundToInt(val.y * 10f)); hash = Fnv1a.Add(hash, Mathf.RoundToInt(val.z * 10f)); hash = Fnv1a.Add(hash, (((Component)level).transform.localScale.x < 0f) ? 1 : 0); levelCount++; } } if (hash != 0) { return hash; } return 1u; } public static bool TryReadPlayer(PlayerState state) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: 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_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: 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) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) ENT_Player playerObject = ENT_Player.playerObject; if ((Object)(object)playerObject == (Object)null || (Object)(object)CL_GameManager.gMan == (Object)null) { return false; } Transform obj = (((Object)(object)playerObject.cam != (Object)null) ? ((Component)playerObject.cam).transform : ((Component)playerObject).transform); Vector3 val = ToShared(obj.position); state.X = val.x; state.Y = val.y; state.Z = val.z; Vector3 eulerAngles = obj.eulerAngles; state.Yaw = eulerAngles.y; state.Pitch = ((eulerAngles.x > 180f) ? (eulerAngles.x - 360f) : eulerAngles.x); StateFlags stateFlags = StateFlags.None; if (((GameEntity)playerObject).dead) { stateFlags |= StateFlags.Dead; } if (!IsWorldReady()) { stateFlags |= StateFlags.Loading; } Hand[] hands = playerObject.hands; if (hands != null && hands.Length >= 2) { if (hands[0] != null && hands[0].IsHolding()) { Vector3 val2 = ToShared(hands[0].GetHoldWorldPosition()); state.LeftX = val2.x; state.LeftY = val2.y; state.LeftZ = val2.z; stateFlags |= StateFlags.LeftHold; } if (hands[1] != null && hands[1].IsHolding()) { Vector3 val3 = ToShared(hands[1].GetHoldWorldPosition()); state.RightX = val3.x; state.RightY = val3.y; state.RightZ = val3.z; stateFlags |= StateFlags.RightHold; } } state.Flags = stateFlags; return true; } public static void FreezeWorld() { //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (Frozen) { return; } Frozen = true; ENT_Player playerObject = ENT_Player.playerObject; if ((Object)(object)playerObject != (Object)null) { if (PlayerVelocity != null) { _savedVelocity = PlayerVelocity.Invoke(playerObject); } playerObject.Lock(); _lockedByUs = true; } CL_GameManager.RefreshTimescale(); } public static void ThawWorld() { //IL_0044: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) Frozen = false; ENT_Player playerObject = ENT_Player.playerObject; if (_lockedByUs && (Object)(object)playerObject != (Object)null) { playerObject.UnLock(); if (PlayerVelocity != null) { PlayerVelocity.Invoke(playerObject) = _savedVelocity; } } _lockedByUs = false; _savedVelocity = Vector3.zero; if ((Object)(object)CL_GameManager.gMan != (Object)null) { CL_GameManager.RefreshTimescale(); } } private static FieldRef<ENT_Player, Vector3> SafeFieldRef(string name) { try { return AccessTools.FieldRefAccess<ENT_Player, Vector3>(name); } catch (Exception) { return null; } } public static void ClearFreezeFlags() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) Frozen = false; _lockedByUs = false; _savedVelocity = Vector3.zero; } } internal static class RaceRules { public static bool Active; public static bool Competitive = true; public static int Seed; public static bool InRaceWorld; private static M_Gamemode _gamemode; private static bool _originalAllowCheats; public static void Begin(M_Gamemode gamemode, int seed, bool competitive) { if (Active && (Object)(object)_gamemode == (Object)(object)gamemode) { Seed = seed; Competitive = competitive; return; } End(); Active = true; Seed = seed; Competitive = competitive; _gamemode = gamemode; _originalAllowCheats = gamemode.allowCheats; if (!RaceConfig.CheatsAllowed) { gamemode.allowCheats = false; } } public static void End() { if (Active) { Active = false; if ((Object)(object)_gamemode != (Object)null) { _gamemode.allowCheats = _originalAllowCheats; } _gamemode = null; } } } internal sealed class Ghost { private const float StaleSeconds = 4f; private const float HideBelowDistance = 0.8f; private const float FadeBelowDistance = 2.2f; private GameObject _root; private Transform _head; private Transform _body; private Transform _nose; private Transform _leftHand; private Transform _rightHand; private Material _material; private bool _hasTarget; private float _lastUpdate; private Vector3 _targetEye; private Vector3 _targetLeft; private Vector3 _targetRight; private bool _holdLeft; private bool _holdRight; private float _targetYaw; private Vector3 _eye; private Vector3 _left; private Vector3 _right; private float _yaw; private bool _dead; public bool Visible { get; private set; } public Vector3 EyeWorld => _eye; public void SetTarget(OpponentState s) { //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_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //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_009d: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references)