using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using FalseGods.Core.Arena; using FalseGods.Core.Bosses; using FalseGods.Core.Simulation; using FalseGods.Protocol.Arena; using FalseGods.Protocol.Arena.Internal; using Microsoft.CodeAnalysis; [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("FalseGods.Protocol")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0+6e5740a80f810ab422551028733810b527fb614d")] [assembly: AssemblyProduct("FalseGods.Protocol")] [assembly: AssemblyTitle("FalseGods.Protocol")] [assembly: AssemblyVersion("1.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } internal static class IsExternalInit { } } namespace FalseGods.Protocol.Wire { public readonly struct ArenaLevelId : IEquatable { public int Environment { get; } public int LevelIndex { get; } public ArenaLevelId(int environment, int levelIndex) { Environment = environment; LevelIndex = levelIndex; } public bool Equals(ArenaLevelId other) { if (Environment == other.Environment) { return LevelIndex == other.LevelIndex; } return false; } public override bool Equals(object? obj) { if (obj is ArenaLevelId other) { return Equals(other); } return false; } public override int GetHashCode() { return (Environment * 397) ^ LevelIndex; } public override string ToString() { return $"env {Environment} level {LevelIndex}"; } public static bool operator ==(ArenaLevelId left, ArenaLevelId right) { return left.Equals(right); } public static bool operator !=(ArenaLevelId left, ArenaLevelId right) { return !left.Equals(right); } } public sealed record ArenaLevelDeclared(ArenaLevelId Level, bool IsBossArena); public sealed record ArenaLevelRequested(ArenaLevelId Level); public sealed record ArenaSnapshot(EncounterId Encounter, string ArenaId, int ArenaVersion, ProtocolVersion ProtocolVersion, SimulationTick Tick, IReadOnlyList ActiveMechanismGroups, bool ExitUnlocked, Sequence LastProcessedArenaEventSequence); public sealed record ArenaMechanismGroupActivatedEvent(Sequence Sequence, SimulationTick Tick, MechanismGroupId Group) : IArenaWireEvent; public sealed record ArenaExitUnlockedEvent(Sequence Sequence, SimulationTick Tick) : IArenaWireEvent; public sealed record BossHitPlayer(EncounterId Encounter, int Amount); public sealed record BossSnapshot(EncounterId Encounter, BossInstanceId Boss, DefinitionId Definition, ProtocolVersion ProtocolVersion, SimulationTick Tick, int PhaseId, int StateId, SimulationTick StateStartTick, AttackInstanceId? ActiveAttack, int? ActiveAttackDefinitionId, ParticipantId? Target, SimVector2 Position, float PositionHeight, SimVector2 Facing, int Health, int MaxHealth, bool WeakPointExposed, bool Enraged, bool Begun, Sequence LastProcessedBossEventSequence); public sealed record BossAppearedEvent(Sequence Sequence, SimulationTick Tick, int PhaseId) : IBossWireEvent; public sealed record BossBeganEvent(Sequence Sequence, SimulationTick Tick) : IBossWireEvent; public sealed record BossAttackTelegraphedEvent(Sequence Sequence, SimulationTick Tick, AttackInstanceId Attack, int AttackDefinitionId, SimVector2 AimPoint, float TelegraphSeconds) : IBossWireEvent; public sealed record BossAttackCommittedEvent(Sequence Sequence, SimulationTick Tick, AttackInstanceId Attack, int AttackDefinitionId, SimVector2 AimPoint) : IBossWireEvent; public sealed record BossPhaseChangedEvent(Sequence Sequence, SimulationTick Tick, int PhaseId) : IBossWireEvent; public sealed record BossWeakPointChangedEvent(Sequence Sequence, SimulationTick Tick, bool Exposed) : IBossWireEvent; public sealed record BossDamagedEvent(Sequence Sequence, SimulationTick Tick, int Amount, int RemainingHealth, bool WeakPointHit) : IBossWireEvent; public sealed record BossRelocatedEvent(Sequence Sequence, SimulationTick Tick, int StationIndex, int AnchorIndex, SimVector2 Position, float Height) : IBossWireEvent; public sealed record BossEnragedEvent(Sequence Sequence, SimulationTick Tick, bool Enraged) : IBossWireEvent; public sealed record BossDefeatedEvent(Sequence Sequence, SimulationTick Tick) : IBossWireEvent; public sealed record ClientHitRequest(EncounterId Encounter, int RequestSequence, float DamageCandidate, WorldPosition? AttackerPosition); public sealed record CrateDropped(WorldPosition At, int PileKind, int PileIndex, bool Explosive); public sealed record CratesTaken(WorldPosition At, int PileKind, int PileIndex, int Count, float Radius); public sealed record CratesSetDown(WorldPosition From, WorldPosition At, int PileKind, int PileIndex, int Count, int Seed, int Explosives); public sealed record CrateThrown(WorldPosition From, WorldPosition To, float FlightSeconds, float ApexHeight); public sealed record CrateVolleyTarget(WorldPosition Current, WorldPosition Lead); public sealed record CrateDestroyed(int CrateId, int Death); public sealed record CrateDestroyRequested(int CrateId, int Death); public sealed record CrateVolleyFired(IReadOnlyList Targets, int PileKind, int PileIndex, int Seed, int Count, float SpreadMinRadius, float SpreadMaxRadius, float LiftHeight, float LiftSeconds, float HoldSeconds, float FlightSeconds, float ApexHeight, float LeadShare, float FireIntervalSeconds); public sealed record EncounterBaseline(EncounterId Encounter, ProtocolVersion ProtocolVersion, string ArenaId, int ArenaVersion, ContentHash ContentHash, WorldPosition ArenaOrigin, SimulationTick Tick, int EncounterPhaseId, BossSnapshot Boss, ArenaSnapshot Arena, Sequence LastProcessedBossEventSequence, Sequence LastProcessedArenaEventSequence); public enum EncounterAbortReason { Unspecified, ContentHashSchemaMismatch, VersionMismatch, ContentMismatch, LoadFailed, Timeout } public sealed record EnterArena(EncounterId Encounter, ArenaManifest Manifest, WorldPosition Origin); public sealed record ArenaReady(EncounterId Encounter, ArenaManifest Manifest); public sealed record ArenaLoadFailed(EncounterId Encounter, string Reason); public sealed record EncounterAborted(EncounterId Encounter, EncounterAbortReason Reason); public sealed record EncounterEnded(EncounterId Encounter, SimulationTick Tick); public interface IArenaWireEvent { Sequence Sequence { get; } SimulationTick Tick { get; } } public interface IBossWireEvent { Sequence Sequence { get; } SimulationTick Tick { get; } } public readonly struct ProtocolVersion : IEquatable { public static readonly ProtocolVersion Current = new ProtocolVersion(17); public int Value { get; } public ProtocolVersion(int value) { Value = value; } public bool Equals(ProtocolVersion other) { return Value == other.Value; } public override bool Equals(object? obj) { if (obj is ProtocolVersion other) { return Equals(other); } return false; } public override int GetHashCode() { return Value; } public override string ToString() { return Value.ToString(); } public static bool operator ==(ProtocolVersion left, ProtocolVersion right) { return left.Equals(right); } public static bool operator !=(ProtocolVersion left, ProtocolVersion right) { return !left.Equals(right); } } public readonly struct Sequence : IEquatable, IComparable { private readonly long _value; public long Value => _value; public Sequence(long value) { _value = value; } public Sequence Next() { return new Sequence(_value + 1); } public int CompareTo(Sequence other) { long value = _value; return value.CompareTo(other._value); } public bool Equals(Sequence other) { return _value == other._value; } public override bool Equals(object? obj) { if (obj is Sequence other) { return Equals(other); } return false; } public override int GetHashCode() { long value = _value; return value.GetHashCode(); } public override string ToString() { return $"seq:{_value}"; } public static bool operator ==(Sequence left, Sequence right) { return left.Equals(right); } public static bool operator !=(Sequence left, Sequence right) { return !left.Equals(right); } public static bool operator <(Sequence left, Sequence right) { return left.CompareTo(right) < 0; } public static bool operator >(Sequence left, Sequence right) { return left.CompareTo(right) > 0; } public static bool operator <=(Sequence left, Sequence right) { return left.CompareTo(right) <= 0; } public static bool operator >=(Sequence left, Sequence right) { return left.CompareTo(right) >= 0; } } public readonly struct SimulationTick : IEquatable, IComparable { private readonly long _value; public long Value => _value; public SimulationTick(long value) { _value = value; } public int CompareTo(SimulationTick other) { long value = _value; return value.CompareTo(other._value); } public bool Equals(SimulationTick other) { return _value == other._value; } public override bool Equals(object? obj) { if (obj is SimulationTick other) { return Equals(other); } return false; } public override int GetHashCode() { long value = _value; return value.GetHashCode(); } public override string ToString() { return $"tick:{_value}"; } public static bool operator ==(SimulationTick left, SimulationTick right) { return left.Equals(right); } public static bool operator !=(SimulationTick left, SimulationTick right) { return !left.Equals(right); } } public static class WireCodec { private const int MaxVolleyTargets = 32; private const byte BossAppearedTag = 1; private const byte BossTelegraphedTag = 2; private const byte BossCommittedTag = 3; private const byte BossPhaseChangedTag = 4; private const byte BossWeakPointTag = 5; private const byte BossDamagedTag = 6; private const byte BossDefeatedTag = 7; private const byte BossRelocatedTag = 8; private const byte BossEnragedTag = 9; private const byte BossBeganTag = 10; private const byte ArenaMechanismActivatedTag = 1; private const byte ArenaExitUnlockedTag = 2; public static byte[] Serialize(BossSnapshot snapshot) { WireWriter wireWriter = new WireWriter(); WriteBossSnapshot(wireWriter, snapshot); return wireWriter.ToArray(); } public static BossSnapshot DeserializeBossSnapshot(byte[] payload) { WireReader r = new WireReader(payload); BossSnapshot result = ReadBossSnapshot(r); RequireEnd(r); return result; } public static byte[] Serialize(ArenaSnapshot snapshot) { WireWriter wireWriter = new WireWriter(); WriteArenaSnapshot(wireWriter, snapshot); return wireWriter.ToArray(); } public static ArenaSnapshot DeserializeArenaSnapshot(byte[] payload) { WireReader r = new WireReader(payload); ArenaSnapshot result = ReadArenaSnapshot(r); RequireEnd(r); return result; } public static byte[] Serialize(EncounterBaseline baseline) { //IL_0007: 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) WireWriter wireWriter = new WireWriter(); EncounterId encounter = baseline.Encounter; WriteInt(wireWriter, ((EncounterId)(ref encounter)).Value); WriteInt(wireWriter, baseline.ProtocolVersion.Value); wireWriter.WriteString(baseline.ArenaId); WriteInt(wireWriter, baseline.ArenaVersion); WriteContentHash(wireWriter, baseline.ContentHash); WriteWorldPosition(wireWriter, baseline.ArenaOrigin); wireWriter.WriteInt64(baseline.Tick.Value); WriteInt(wireWriter, baseline.EncounterPhaseId); WriteBossSnapshot(wireWriter, baseline.Boss); WriteArenaSnapshot(wireWriter, baseline.Arena); wireWriter.WriteInt64(baseline.LastProcessedBossEventSequence.Value); wireWriter.WriteInt64(baseline.LastProcessedArenaEventSequence.Value); return wireWriter.ToArray(); } public static EncounterBaseline DeserializeBaseline(byte[] payload) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) WireReader wireReader = new WireReader(payload); EncounterBaseline result = new EncounterBaseline(new EncounterId(wireReader.ReadInt32()), new ProtocolVersion(wireReader.ReadInt32()), wireReader.ReadString(), wireReader.ReadInt32(), ReadContentHash(wireReader), ReadWorldPosition(wireReader), new SimulationTick(wireReader.ReadInt64()), wireReader.ReadInt32(), ReadBossSnapshot(wireReader), ReadArenaSnapshot(wireReader), new Sequence(wireReader.ReadInt64()), new Sequence(wireReader.ReadInt64())); RequireEnd(wireReader); return result; } public static byte[] Serialize(EnterArena message) { //IL_0007: 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) WireWriter wireWriter = new WireWriter(); EncounterId encounter = message.Encounter; WriteInt(wireWriter, ((EncounterId)(ref encounter)).Value); WriteManifest(wireWriter, message.Manifest); WriteWorldPosition(wireWriter, message.Origin); return wireWriter.ToArray(); } public static EnterArena DeserializeEnterArena(byte[] payload) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) WireReader wireReader = new WireReader(payload); EnterArena result = new EnterArena(new EncounterId(wireReader.ReadInt32()), ReadManifest(wireReader), ReadWorldPosition(wireReader)); RequireEnd(wireReader); return result; } public static byte[] Serialize(ArenaReady message) { //IL_0007: 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) WireWriter wireWriter = new WireWriter(); EncounterId encounter = message.Encounter; WriteInt(wireWriter, ((EncounterId)(ref encounter)).Value); WriteManifest(wireWriter, message.Manifest); return wireWriter.ToArray(); } public static ArenaReady DeserializeArenaReady(byte[] payload) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) WireReader wireReader = new WireReader(payload); ArenaReady result = new ArenaReady(new EncounterId(wireReader.ReadInt32()), ReadManifest(wireReader)); RequireEnd(wireReader); return result; } public static byte[] Serialize(ArenaLoadFailed message) { //IL_0007: 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) WireWriter wireWriter = new WireWriter(); EncounterId encounter = message.Encounter; WriteInt(wireWriter, ((EncounterId)(ref encounter)).Value); wireWriter.WriteString(message.Reason); return wireWriter.ToArray(); } public static ArenaLoadFailed DeserializeArenaLoadFailed(byte[] payload) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) WireReader wireReader = new WireReader(payload); ArenaLoadFailed result = new ArenaLoadFailed(new EncounterId(wireReader.ReadInt32()), wireReader.ReadString()); RequireEnd(wireReader); return result; } public static byte[] Serialize(EncounterAborted message) { //IL_0007: 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) WireWriter wireWriter = new WireWriter(); EncounterId encounter = message.Encounter; WriteInt(wireWriter, ((EncounterId)(ref encounter)).Value); WriteInt(wireWriter, (int)message.Reason); return wireWriter.ToArray(); } public static EncounterAborted DeserializeEncounterAborted(byte[] payload) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) WireReader wireReader = new WireReader(payload); EncounterId encounter = new EncounterId(wireReader.ReadInt32()); int num = wireReader.ReadInt32(); if (num < 0 || num > 5) { throw new InvalidDataException($"Unknown encounter-abort reason {num}."); } EncounterAborted result = new EncounterAborted(encounter, (EncounterAbortReason)num); RequireEnd(wireReader); return result; } public static byte[] Serialize(EncounterEnded message) { //IL_0007: 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) WireWriter wireWriter = new WireWriter(); EncounterId encounter = message.Encounter; WriteInt(wireWriter, ((EncounterId)(ref encounter)).Value); wireWriter.WriteInt64(message.Tick.Value); return wireWriter.ToArray(); } public static EncounterEnded DeserializeEncounterEnded(byte[] payload) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) WireReader wireReader = new WireReader(payload); EncounterEnded result = new EncounterEnded(new EncounterId(wireReader.ReadInt32()), new SimulationTick(wireReader.ReadInt64())); RequireEnd(wireReader); return result; } public static byte[] Serialize(ClientHitRequest message) { //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) WireWriter wireWriter = new WireWriter(); EncounterId encounter = message.Encounter; WriteInt(wireWriter, ((EncounterId)(ref encounter)).Value); WriteInt(wireWriter, message.RequestSequence); wireWriter.WriteSingle(message.DamageCandidate); wireWriter.WriteBool(message.AttackerPosition.HasValue); if (message.AttackerPosition.HasValue) { WriteWorldPosition(wireWriter, message.AttackerPosition.Value); } return wireWriter.ToArray(); } public static ClientHitRequest DeserializeClientHitRequest(byte[] payload) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) WireReader wireReader = new WireReader(payload); EncounterId encounter = new EncounterId(wireReader.ReadInt32()); int requestSequence = wireReader.ReadInt32(); float damageCandidate = wireReader.ReadSingle(); WorldPosition? attackerPosition = (wireReader.ReadBool() ? new WorldPosition?(ReadWorldPosition(wireReader)) : ((WorldPosition?)null)); ClientHitRequest result = new ClientHitRequest(encounter, requestSequence, damageCandidate, attackerPosition); RequireEnd(wireReader); return result; } public static byte[] Serialize(BossHitPlayer message) { //IL_0007: 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) WireWriter wireWriter = new WireWriter(); EncounterId encounter = message.Encounter; WriteInt(wireWriter, ((EncounterId)(ref encounter)).Value); WriteInt(wireWriter, message.Amount); return wireWriter.ToArray(); } public static BossHitPlayer DeserializeBossHitPlayer(byte[] payload) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) WireReader wireReader = new WireReader(payload); BossHitPlayer result = new BossHitPlayer(new EncounterId(wireReader.ReadInt32()), wireReader.ReadInt32()); RequireEnd(wireReader); return result; } public static byte[] Serialize(CrateDropped message) { WireWriter wireWriter = new WireWriter(); WriteWorldPosition(wireWriter, message.At); WriteInt(wireWriter, message.PileKind); WriteInt(wireWriter, message.PileIndex); wireWriter.WriteBool(message.Explosive); return wireWriter.ToArray(); } public static CrateDropped DeserializeCrateDropped(byte[] payload) { WireReader wireReader = new WireReader(payload); CrateDropped result = new CrateDropped(ReadWorldPosition(wireReader), wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadBool()); RequireEnd(wireReader); return result; } public static byte[] Serialize(CratesTaken message) { WireWriter wireWriter = new WireWriter(); WriteWorldPosition(wireWriter, message.At); WriteInt(wireWriter, message.PileKind); WriteInt(wireWriter, message.PileIndex); WriteInt(wireWriter, message.Count); wireWriter.WriteSingle(message.Radius); return wireWriter.ToArray(); } public static CratesTaken DeserializeCratesTaken(byte[] payload) { WireReader wireReader = new WireReader(payload); CratesTaken result = new CratesTaken(ReadWorldPosition(wireReader), wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadSingle()); RequireEnd(wireReader); return result; } public static byte[] Serialize(CrateDestroyed message) { WireWriter wireWriter = new WireWriter(); WriteInt(wireWriter, message.CrateId); WriteInt(wireWriter, message.Death); return wireWriter.ToArray(); } public static CrateDestroyed DeserializeCrateDestroyed(byte[] payload) { WireReader wireReader = new WireReader(payload); CrateDestroyed result = new CrateDestroyed(wireReader.ReadInt32(), wireReader.ReadInt32()); RequireEnd(wireReader); return result; } public static byte[] Serialize(CrateDestroyRequested message) { WireWriter wireWriter = new WireWriter(); WriteInt(wireWriter, message.CrateId); WriteInt(wireWriter, message.Death); return wireWriter.ToArray(); } public static CrateDestroyRequested DeserializeCrateDestroyRequested(byte[] payload) { WireReader wireReader = new WireReader(payload); CrateDestroyRequested result = new CrateDestroyRequested(wireReader.ReadInt32(), wireReader.ReadInt32()); RequireEnd(wireReader); return result; } public static byte[] Serialize(CratesSetDown message) { WireWriter wireWriter = new WireWriter(); WriteWorldPosition(wireWriter, message.From); WriteWorldPosition(wireWriter, message.At); WriteInt(wireWriter, message.PileKind); WriteInt(wireWriter, message.PileIndex); WriteInt(wireWriter, message.Count); WriteInt(wireWriter, message.Seed); WriteInt(wireWriter, message.Explosives); return wireWriter.ToArray(); } public static CratesSetDown DeserializeCratesSetDown(byte[] payload) { WireReader wireReader = new WireReader(payload); CratesSetDown result = new CratesSetDown(ReadWorldPosition(wireReader), ReadWorldPosition(wireReader), wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadInt32()); RequireEnd(wireReader); return result; } public static byte[] Serialize(CrateThrown message) { WireWriter wireWriter = new WireWriter(); WriteWorldPosition(wireWriter, message.From); WriteWorldPosition(wireWriter, message.To); wireWriter.WriteSingle(message.FlightSeconds); wireWriter.WriteSingle(message.ApexHeight); return wireWriter.ToArray(); } public static CrateThrown DeserializeCrateThrown(byte[] payload) { WireReader wireReader = new WireReader(payload); CrateThrown result = new CrateThrown(ReadWorldPosition(wireReader), ReadWorldPosition(wireReader), wireReader.ReadSingle(), wireReader.ReadSingle()); RequireEnd(wireReader); return result; } public static byte[] Serialize(CrateVolleyFired message) { WireWriter wireWriter = new WireWriter(); WriteInt(wireWriter, message.Targets.Count); for (int i = 0; i < message.Targets.Count; i++) { WriteWorldPosition(wireWriter, message.Targets[i].Current); WriteWorldPosition(wireWriter, message.Targets[i].Lead); } WriteInt(wireWriter, message.PileKind); WriteInt(wireWriter, message.PileIndex); WriteInt(wireWriter, message.Seed); WriteInt(wireWriter, message.Count); wireWriter.WriteSingle(message.SpreadMinRadius); wireWriter.WriteSingle(message.SpreadMaxRadius); wireWriter.WriteSingle(message.LiftHeight); wireWriter.WriteSingle(message.LiftSeconds); wireWriter.WriteSingle(message.HoldSeconds); wireWriter.WriteSingle(message.FlightSeconds); wireWriter.WriteSingle(message.ApexHeight); wireWriter.WriteSingle(message.LeadShare); wireWriter.WriteSingle(message.FireIntervalSeconds); return wireWriter.ToArray(); } public static CrateVolleyFired DeserializeCrateVolleyFired(byte[] payload) { WireReader wireReader = new WireReader(payload); int num = wireReader.ReadInt32(); if (num < 0 || num > 32) { throw new InvalidDataException($"CrateVolleyFired target count {num} is outside 0..{32}."); } List list = new List(num); for (int i = 0; i < num; i++) { list.Add(new CrateVolleyTarget(ReadWorldPosition(wireReader), ReadWorldPosition(wireReader))); } CrateVolleyFired result = new CrateVolleyFired(list, wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadInt32(), wireReader.ReadSingle(), wireReader.ReadSingle(), wireReader.ReadSingle(), wireReader.ReadSingle(), wireReader.ReadSingle(), wireReader.ReadSingle(), wireReader.ReadSingle(), wireReader.ReadSingle(), wireReader.ReadSingle()); RequireEnd(wireReader); return result; } public static byte[] Serialize(ArenaLevelDeclared message) { WireWriter wireWriter = new WireWriter(); WriteArenaLevelId(wireWriter, message.Level); wireWriter.WriteBool(message.IsBossArena); return wireWriter.ToArray(); } public static ArenaLevelDeclared DeserializeArenaLevelDeclared(byte[] payload) { WireReader wireReader = new WireReader(payload); ArenaLevelDeclared result = new ArenaLevelDeclared(ReadArenaLevelId(wireReader), wireReader.ReadBool()); RequireEnd(wireReader); return result; } public static byte[] Serialize(ArenaLevelRequested message) { WireWriter wireWriter = new WireWriter(); WriteArenaLevelId(wireWriter, message.Level); return wireWriter.ToArray(); } public static ArenaLevelRequested DeserializeArenaLevelRequested(byte[] payload) { WireReader r = new WireReader(payload); ArenaLevelRequested result = new ArenaLevelRequested(ReadArenaLevelId(r)); RequireEnd(r); return result; } public static byte[] Serialize(IBossWireEvent bossEvent) { WireWriter wireWriter = new WireWriter(); WriteBossEvent(wireWriter, bossEvent); return wireWriter.ToArray(); } public static IBossWireEvent DeserializeBossEvent(byte[] payload) { WireReader r = new WireReader(payload); IBossWireEvent result = ReadBossEvent(r); RequireEnd(r); return result; } public static byte[] Serialize(IArenaWireEvent arenaEvent) { WireWriter wireWriter = new WireWriter(); WriteArenaEvent(wireWriter, arenaEvent); return wireWriter.ToArray(); } public static IArenaWireEvent DeserializeArenaEvent(byte[] payload) { WireReader r = new WireReader(payload); IArenaWireEvent result = ReadArenaEvent(r); RequireEnd(r); return result; } private static void WriteBossSnapshot(WireWriter w, BossSnapshot s) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0016: 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_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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_00d0: Unknown result type (might be due to invalid IL or missing references) EncounterId encounter = s.Encounter; WriteInt(w, ((EncounterId)(ref encounter)).Value); BossInstanceId boss = s.Boss; WriteInt(w, ((BossInstanceId)(ref boss)).Value); DefinitionId definition = s.Definition; WriteInt(w, ((DefinitionId)(ref definition)).Value); WriteInt(w, s.ProtocolVersion.Value); w.WriteInt64(s.Tick.Value); WriteInt(w, s.PhaseId); WriteInt(w, s.StateId); w.WriteInt64(s.StateStartTick.Value); WriteNullableAttack(w, s.ActiveAttack); WriteNullableInt(w, s.ActiveAttackDefinitionId); WriteNullableParticipant(w, s.Target); WriteVector(w, s.Position); w.WriteSingle(s.PositionHeight); WriteVector(w, s.Facing); WriteInt(w, s.Health); WriteInt(w, s.MaxHealth); w.WriteBool(s.WeakPointExposed); w.WriteBool(s.Enraged); w.WriteBool(s.Begun); w.WriteInt64(s.LastProcessedBossEventSequence.Value); } private static BossSnapshot ReadBossSnapshot(WireReader r) { //IL_0006: 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) //IL_001c: 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: Unknown result type (might be due to invalid IL or missing references) return new BossSnapshot(new EncounterId(r.ReadInt32()), new BossInstanceId(r.ReadInt32()), new DefinitionId(r.ReadInt32()), new ProtocolVersion(r.ReadInt32()), new SimulationTick(r.ReadInt64()), r.ReadInt32(), r.ReadInt32(), new SimulationTick(r.ReadInt64()), ReadNullableAttack(r), ReadNullableInt(r), ReadNullableParticipant(r), ReadVector(r), r.ReadSingle(), ReadVector(r), r.ReadInt32(), r.ReadInt32(), r.ReadBool(), r.ReadBool(), r.ReadBool(), new Sequence(r.ReadInt64())); } private static void WriteArenaSnapshot(WireWriter w, ArenaSnapshot s) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0071: 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) EncounterId encounter = s.Encounter; WriteInt(w, ((EncounterId)(ref encounter)).Value); w.WriteString(s.ArenaId); WriteInt(w, s.ArenaVersion); WriteInt(w, s.ProtocolVersion.Value); w.WriteInt64(s.Tick.Value); WriteInt(w, s.ActiveMechanismGroups.Count); for (int i = 0; i < s.ActiveMechanismGroups.Count; i++) { MechanismGroupId val = s.ActiveMechanismGroups[i]; w.WriteString(((MechanismGroupId)(ref val)).Value); } w.WriteBool(s.ExitUnlocked); w.WriteInt64(s.LastProcessedArenaEventSequence.Value); } private static ArenaSnapshot ReadArenaSnapshot(WireReader r) { //IL_006f: 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) EncounterId encounter = default(EncounterId); ((EncounterId)(ref encounter))..ctor(r.ReadInt32()); string arenaId = r.ReadString(); int arenaVersion = r.ReadInt32(); ProtocolVersion protocolVersion = new ProtocolVersion(r.ReadInt32()); SimulationTick tick = new SimulationTick(r.ReadInt64()); int num = r.ReadInt32(); if (num < 0) { throw new InvalidDataException($"ArenaSnapshot mechanism-group count {num} is negative."); } List list = new List(num); for (int i = 0; i < num; i++) { list.Add(new MechanismGroupId(r.ReadString())); } bool exitUnlocked = r.ReadBool(); return new ArenaSnapshot(LastProcessedArenaEventSequence: new Sequence(r.ReadInt64()), Encounter: encounter, ArenaId: arenaId, ArenaVersion: arenaVersion, ProtocolVersion: protocolVersion, Tick: tick, ActiveMechanismGroups: list, ExitUnlocked: exitUnlocked); } private static void WriteBossEvent(WireWriter w, IBossWireEvent e) { //IL_00ce: 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) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) if (!(e is BossAppearedEvent bossAppearedEvent)) { AttackInstanceId attack; if (!(e is BossAttackTelegraphedEvent bossAttackTelegraphedEvent)) { if (!(e is BossAttackCommittedEvent bossAttackCommittedEvent)) { if (!(e is BossPhaseChangedEvent bossPhaseChangedEvent)) { if (!(e is BossWeakPointChangedEvent bossWeakPointChangedEvent)) { if (!(e is BossDamagedEvent bossDamagedEvent)) { if (!(e is BossRelocatedEvent bossRelocatedEvent)) { if (!(e is BossBeganEvent bossBeganEvent)) { if (!(e is BossEnragedEvent bossEnragedEvent)) { if (!(e is BossDefeatedEvent bossDefeatedEvent)) { if (e == null) { throw new ArgumentNullException("e"); } throw new ArgumentOutOfRangeException("e", e.GetType().Name, "No wire encoding for this boss event."); } WriteBossHeader(w, 7, bossDefeatedEvent.Sequence, bossDefeatedEvent.Tick); } else { WriteBossHeader(w, 9, bossEnragedEvent.Sequence, bossEnragedEvent.Tick); w.WriteBool(bossEnragedEvent.Enraged); } } else { WriteBossHeader(w, 10, bossBeganEvent.Sequence, bossBeganEvent.Tick); } } else { WriteBossHeader(w, 8, bossRelocatedEvent.Sequence, bossRelocatedEvent.Tick); WriteInt(w, bossRelocatedEvent.StationIndex); WriteInt(w, bossRelocatedEvent.AnchorIndex); WriteVector(w, bossRelocatedEvent.Position); w.WriteSingle(bossRelocatedEvent.Height); } } else { WriteBossHeader(w, 6, bossDamagedEvent.Sequence, bossDamagedEvent.Tick); WriteInt(w, bossDamagedEvent.Amount); WriteInt(w, bossDamagedEvent.RemainingHealth); w.WriteBool(bossDamagedEvent.WeakPointHit); } } else { WriteBossHeader(w, 5, bossWeakPointChangedEvent.Sequence, bossWeakPointChangedEvent.Tick); w.WriteBool(bossWeakPointChangedEvent.Exposed); } } else { WriteBossHeader(w, 4, bossPhaseChangedEvent.Sequence, bossPhaseChangedEvent.Tick); WriteInt(w, bossPhaseChangedEvent.PhaseId); } } else { WriteBossHeader(w, 3, bossAttackCommittedEvent.Sequence, bossAttackCommittedEvent.Tick); attack = bossAttackCommittedEvent.Attack; w.WriteInt64(((AttackInstanceId)(ref attack)).Value); WriteInt(w, bossAttackCommittedEvent.AttackDefinitionId); WriteVector(w, bossAttackCommittedEvent.AimPoint); } } else { WriteBossHeader(w, 2, bossAttackTelegraphedEvent.Sequence, bossAttackTelegraphedEvent.Tick); attack = bossAttackTelegraphedEvent.Attack; w.WriteInt64(((AttackInstanceId)(ref attack)).Value); WriteInt(w, bossAttackTelegraphedEvent.AttackDefinitionId); WriteVector(w, bossAttackTelegraphedEvent.AimPoint); w.WriteSingle(bossAttackTelegraphedEvent.TelegraphSeconds); } } else { WriteBossHeader(w, 1, bossAppearedEvent.Sequence, bossAppearedEvent.Tick); WriteInt(w, bossAppearedEvent.PhaseId); } } private static IBossWireEvent ReadBossEvent(WireReader r) { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) byte b = r.ReadBytes(1)[0]; Sequence sequence = new Sequence(r.ReadInt64()); SimulationTick tick = new SimulationTick(r.ReadInt64()); return b switch { 1 => new BossAppearedEvent(sequence, tick, r.ReadInt32()), 2 => new BossAttackTelegraphedEvent(sequence, tick, new AttackInstanceId(r.ReadInt64()), r.ReadInt32(), ReadVector(r), r.ReadSingle()), 3 => new BossAttackCommittedEvent(sequence, tick, new AttackInstanceId(r.ReadInt64()), r.ReadInt32(), ReadVector(r)), 4 => new BossPhaseChangedEvent(sequence, tick, r.ReadInt32()), 5 => new BossWeakPointChangedEvent(sequence, tick, r.ReadBool()), 6 => new BossDamagedEvent(sequence, tick, r.ReadInt32(), r.ReadInt32(), r.ReadBool()), 8 => new BossRelocatedEvent(sequence, tick, r.ReadInt32(), r.ReadInt32(), ReadVector(r), r.ReadSingle()), 10 => new BossBeganEvent(sequence, tick), 9 => new BossEnragedEvent(sequence, tick, r.ReadBool()), 7 => new BossDefeatedEvent(sequence, tick), _ => throw new InvalidDataException($"Unknown boss wire-event tag {b}."), }; } private static void WriteArenaEvent(WireWriter w, IArenaWireEvent e) { //IL_002e: 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) if (!(e is ArenaMechanismGroupActivatedEvent arenaMechanismGroupActivatedEvent)) { if (!(e is ArenaExitUnlockedEvent arenaExitUnlockedEvent)) { if (e == null) { throw new ArgumentNullException("e"); } throw new ArgumentOutOfRangeException("e", e.GetType().Name, "No wire encoding for this arena event."); } WriteArenaHeader(w, 2, arenaExitUnlockedEvent.Sequence, arenaExitUnlockedEvent.Tick); } else { WriteArenaHeader(w, 1, arenaMechanismGroupActivatedEvent.Sequence, arenaMechanismGroupActivatedEvent.Tick); MechanismGroupId val = arenaMechanismGroupActivatedEvent.Group; w.WriteString(((MechanismGroupId)(ref val)).Value); } } private static IArenaWireEvent ReadArenaEvent(WireReader r) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) byte b = r.ReadBytes(1)[0]; Sequence sequence = new Sequence(r.ReadInt64()); SimulationTick tick = new SimulationTick(r.ReadInt64()); return b switch { 1 => new ArenaMechanismGroupActivatedEvent(sequence, tick, new MechanismGroupId(r.ReadString())), 2 => new ArenaExitUnlockedEvent(sequence, tick), _ => throw new InvalidDataException($"Unknown arena wire-event tag {b}."), }; } private static void WriteBossHeader(WireWriter w, byte tag, Sequence sequence, SimulationTick tick) { w.WriteBytes(new byte[1] { tag }); w.WriteInt64(sequence.Value); w.WriteInt64(tick.Value); } private static void WriteArenaHeader(WireWriter w, byte tag, Sequence sequence, SimulationTick tick) { w.WriteBytes(new byte[1] { tag }); w.WriteInt64(sequence.Value); w.WriteInt64(tick.Value); } private static void WriteInt(WireWriter w, int value) { w.WriteInt32(value); } private static void WriteVector(WireWriter w, SimVector2 v) { w.WriteSingle(((SimVector2)(ref v)).X); w.WriteSingle(((SimVector2)(ref v)).Z); } private static SimVector2 ReadVector(WireReader r) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) return new SimVector2(r.ReadSingle(), r.ReadSingle()); } private static void WriteNullableAttack(WireWriter w, AttackInstanceId? value) { //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) w.WriteBool(value.HasValue); if (value.HasValue) { AttackInstanceId value2 = value.Value; w.WriteInt64(((AttackInstanceId)(ref value2)).Value); } } private static AttackInstanceId? ReadNullableAttack(WireReader r) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!r.ReadBool()) { return null; } return new AttackInstanceId(r.ReadInt64()); } private static void WriteNullableParticipant(WireWriter w, ParticipantId? value) { //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) w.WriteBool(value.HasValue); if (value.HasValue) { ParticipantId value2 = value.Value; w.WriteInt32(((ParticipantId)(ref value2)).Value); } } private static ParticipantId? ReadNullableParticipant(WireReader r) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) if (!r.ReadBool()) { return null; } return new ParticipantId(r.ReadInt32()); } private static void WriteNullableInt(WireWriter w, int? value) { w.WriteBool(value.HasValue); if (value.HasValue) { w.WriteInt32(value.Value); } } private static int? ReadNullableInt(WireReader r) { if (!r.ReadBool()) { return null; } return r.ReadInt32(); } private static void WriteManifest(WireWriter w, ArenaManifest m) { w.WriteString(m.ArenaId); WriteInt(w, m.ArenaVersion); WriteInt(w, m.ContentHashSchemaVersion.Value); WriteContentHash(w, m.ContentHash); WriteInt(w, m.ProtocolVersion); w.WriteString(m.BundleVersion); } private static ArenaManifest ReadManifest(WireReader r) { return new ArenaManifest(r.ReadString(), r.ReadInt32(), new ContentHashSchemaVersion(r.ReadInt32()), ReadContentHash(r), r.ReadInt32(), r.ReadString()); } private static void WriteWorldPosition(WireWriter w, WorldPosition p) { w.WriteSingle(p.X); w.WriteSingle(p.Y); w.WriteSingle(p.Z); } private static WorldPosition ReadWorldPosition(WireReader r) { return new WorldPosition(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); } private static void WriteArenaLevelId(WireWriter w, ArenaLevelId level) { WriteInt(w, level.Environment); WriteInt(w, level.LevelIndex); } private static ArenaLevelId ReadArenaLevelId(WireReader r) { return new ArenaLevelId(r.ReadInt32(), r.ReadInt32()); } private static void WriteContentHash(WireWriter w, ContentHash hash) { bool flag = hash.Length == 32; w.WriteBool(flag); if (flag) { w.WriteBytes(hash.ToArray()); } } private static ContentHash ReadContentHash(WireReader r) { if (!r.ReadBool()) { return default(ContentHash); } return new ContentHash(r.ReadBytes(32)); } private static void RequireEnd(WireReader r) { if (!r.AtEnd) { throw new InvalidDataException("Wire payload has trailing bytes after the decoded value."); } } } public sealed class WireReader { private readonly byte[] _buffer; private int _position; public bool AtEnd => _position == _buffer.Length; public WireReader(byte[] buffer) { _buffer = buffer ?? throw new ArgumentNullException("buffer"); } public bool ReadBool() { return ReadRawByte() != 0; } public int ReadInt32() { byte num = ReadRawByte(); byte b = ReadRawByte(); byte b2 = ReadRawByte(); byte b3 = ReadRawByte(); return num | (b << 8) | (b2 << 16) | (b3 << 24); } public long ReadInt64() { long num = 0L; for (int i = 0; i < 8; i++) { num |= (long)((ulong)ReadRawByte() << 8 * i); } return num; } public float ReadSingle() { byte[] array = ReadBytes(4); if (!BitConverter.IsLittleEndian) { Array.Reverse((Array)array); } return BitConverter.ToSingle(array, 0); } public string ReadString() { int num = ReadInt32(); if (num < 0) { throw new EndOfStreamException($"Wire string length {num} is negative."); } byte[] bytes = ReadBytes(num); return Encoding.UTF8.GetString(bytes); } public byte[] ReadBytes(int count) { if (count < 0) { throw new EndOfStreamException($"Requested {count} bytes."); } if (_position + count > _buffer.Length) { throw new EndOfStreamException($"Wire payload is truncated: needed {count} bytes at offset {_position} of {_buffer.Length}."); } byte[] array = new byte[count]; Array.Copy(_buffer, _position, array, 0, count); _position += count; return array; } private byte ReadRawByte() { if (_position >= _buffer.Length) { throw new EndOfStreamException($"Wire payload is truncated: read past the end at offset {_position} of {_buffer.Length}."); } return _buffer[_position++]; } } public sealed class WireWriter { private readonly MemoryStream _stream = new MemoryStream(); public void WriteBool(bool value) { _stream.WriteByte(value ? ((byte)1) : ((byte)0)); } public void WriteInt32(int value) { _stream.WriteByte((byte)value); _stream.WriteByte((byte)(value >> 8)); _stream.WriteByte((byte)(value >> 16)); _stream.WriteByte((byte)(value >> 24)); } public void WriteInt64(long value) { for (int i = 0; i < 8; i++) { _stream.WriteByte((byte)(value >> 8 * i)); } } public void WriteSingle(float value) { byte[] bytes = BitConverter.GetBytes(value); if (!BitConverter.IsLittleEndian) { Array.Reverse((Array)bytes); } _stream.Write(bytes, 0, bytes.Length); } public void WriteString(string value) { if (value == null) { throw new ArgumentNullException("value", "A required wire string reached the writer as null."); } byte[] bytes = Encoding.UTF8.GetBytes(value); WriteInt32(bytes.Length); _stream.Write(bytes, 0, bytes.Length); } public void WriteBytes(byte[] value) { if (value == null) { throw new ArgumentNullException("value"); } _stream.Write(value, 0, value.Length); } public byte[] ToArray() { return _stream.ToArray(); } } public readonly struct WorldPosition : IEquatable { public float X { get; } public float Y { get; } public float Z { get; } public WorldPosition(float x, float y, float z) { X = x; Y = y; Z = z; } public bool Equals(WorldPosition other) { if (X.Equals(other.X) && Y.Equals(other.Y)) { return Z.Equals(other.Z); } return false; } public override bool Equals(object? obj) { if (obj is WorldPosition other) { return Equals(other); } return false; } public override int GetHashCode() { return (((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode(); } public override string ToString() { return $"({X:0.##}, {Y:0.##}, {Z:0.##})"; } public static bool operator ==(WorldPosition left, WorldPosition right) { return left.Equals(right); } public static bool operator !=(WorldPosition left, WorldPosition right) { return !left.Equals(right); } } } namespace FalseGods.Protocol.Arena { public sealed record ArenaContentArtifact(ArenaContentDefinition Definition, ContentHashSchemaVersion SchemaVersion, int ProtocolVersion, string BundleVersion, IReadOnlyList Parity, IReadOnlyList MaterialBorrowPlacements) { public const int ArtifactFormatVersion = 2; private const string Magic = "FGARENA"; private const char Sep = '\t'; private const string Nil = "-"; public ContentHash ComputeContentHash() { return ContentHashComputer.Compute(Definition, SchemaVersion); } public ArenaManifest ToManifest() { return new ArenaManifest(Definition.ArenaId, Definition.ArenaVersion, SchemaVersion, ComputeContentHash(), ProtocolVersion, BundleVersion); } public string Serialize() { StringBuilder stringBuilder = new StringBuilder(); Row(stringBuilder, "FGARENA", 2.ToString(CultureInfo.InvariantCulture)); Row(stringBuilder, "arenaId", Definition.ArenaId); Row(stringBuilder, "arenaVersion", Int(Definition.ArenaVersion)); Row(stringBuilder, "arenaContentId", Definition.ArenaContentId); Row(stringBuilder, "schemaVersion", Int(SchemaVersion.Value)); Row(stringBuilder, "protocolVersion", Int(ProtocolVersion)); Row(stringBuilder, "bundleVersion", BundleVersion); foreach (AuthoredNode node in Definition.Nodes) { Row(stringBuilder, Fields("node", node.MarkerId.ToCanonicalString(), node.NodeKind, Marker(node.ParentMarkerId), Transform(node.LocalTransform))); } foreach (VanillaProxyDefinition vanillaProxy in Definition.VanillaProxies) { Row(stringBuilder, Fields("proxy", vanillaProxy.MarkerId.ToCanonicalString(), vanillaProxy.AddressableKeyOrGuid, Transform(vanillaProxy.LocalTransform))); } foreach (ColliderDefinition collider in Definition.Colliders) { Row(stringBuilder, Fields("collider", collider.MarkerId.ToCanonicalString(), collider.ColliderKind, collider.LayerName, Geometry(collider.GeometryParameters))); } foreach (NavDefinition navDefinition in Definition.NavDefinitions) { Row(stringBuilder, Fields("nav", navDefinition.MarkerId.ToCanonicalString(), navDefinition.NavKind, navDefinition.NavmeshPrefabContentId ?? "-", Bounds(navDefinition.Bounds))); } foreach (SpawnDefinition spawn in Definition.Spawns) { Row(stringBuilder, Fields("spawn", spawn.MarkerId.ToCanonicalString(), spawn.SpawnKind, spawn.DefinitionId, Transform(spawn.LocalTransform))); } foreach (MechanismDefinition mechanism in Definition.Mechanisms) { Row(stringBuilder, Fields("mechanism", mechanism.MarkerId.ToCanonicalString(), mechanism.MechanismDefinitionId, mechanism.MechanismGroupId, Transform(mechanism.LocalTransform))); } foreach (MaterialBorrowDefinition materialBorrow in Definition.MaterialBorrows) { Row(stringBuilder, Fields("matborrow", materialBorrow.MarkerId.ToCanonicalString(), materialBorrow.TargetMarkerId.ToCanonicalString(), Int(materialBorrow.TargetSubMaterialIndex), materialBorrow.CarrierGuid, materialBorrow.MaterialName, TargetPathFor(materialBorrow.MarkerId))); } foreach (ArenaParityNode item in Parity) { Row(stringBuilder, Fields("parity", item.Path, item.Kind, Transform(item.LocalTransform))); } return stringBuilder.ToString(); } private string TargetPathFor(StableMarkerId borrowMarkerId) { foreach (MaterialBorrowPlacement materialBorrowPlacement in MaterialBorrowPlacements) { if (materialBorrowPlacement.BorrowMarkerId == borrowMarkerId) { return materialBorrowPlacement.TargetPath; } } throw new ArenaContentExportException($"Material borrow {borrowMarkerId} has no target-path placement to serialize."); } private static string[] Fields(params object[] parts) { List list = new List(); foreach (object obj in parts) { if (obj is string[] collection) { list.AddRange(collection); } else { list.Add((string)obj); } } return list.ToArray(); } private static void Row(StringBuilder sb, params string[] fields) { for (int i = 0; i < fields.Length; i++) { if (i > 0) { sb.Append('\t'); } string text = fields[i]; if (text.IndexOf('\t') >= 0 || text.IndexOf('\n') >= 0) { throw new ArenaContentExportException("Artifact field contains a tab or newline: '" + text + "'."); } sb.Append(text); } sb.Append('\n'); } private static string Int(int value) { return value.ToString(CultureInfo.InvariantCulture); } private static string Flt(float value) { return value.ToString("R", CultureInfo.InvariantCulture); } private static string Dbl(double value) { return value.ToString("R", CultureInfo.InvariantCulture); } private static string Marker(StableMarkerId? m) { if (!m.HasValue) { return "-"; } return m.Value.ToCanonicalString(); } private static string[] Transform(AuthoredTransform t) { return new string[10] { Flt(t.Position.X), Flt(t.Position.Y), Flt(t.Position.Z), Flt(t.Rotation.X), Flt(t.Rotation.Y), Flt(t.Rotation.Z), Flt(t.Rotation.W), Flt(t.Scale.X), Flt(t.Scale.Y), Flt(t.Scale.Z) }; } private static string[] Bounds(AuthoredBounds b) { return new string[6] { Flt(b.Center.X), Flt(b.Center.Y), Flt(b.Center.Z), Flt(b.Size.X), Flt(b.Size.Y), Flt(b.Size.Z) }; } private static string[] Geometry(IReadOnlyList parameters) { return new string[1] { Int(parameters.Count) }.Concat(parameters.Select(Dbl)).ToArray(); } public static ArenaContentArtifact Parse(string text) { if (text == null) { throw new ArgumentNullException("text"); } List rows = (from line in text.Replace("\r\n", "\n").Replace('\r', '\n').Split(new char[1] { '\n' }) where line.Length > 0 select line.Split(new char[1] { '\t' })).ToList(); int cursor = 0; int num = ParseInt(Next("FGARENA", 2)[1], "artifact format version"); if (num != 2) { throw Malformed($"unsupported artifact format version {num} (this reader is {2})."); } string arenaId = Next("arenaId", 2)[1]; int arenaVersion = ParseInt(Next("arenaVersion", 2)[1], "arenaVersion"); string arenaContentId = Next("arenaContentId", 2)[1]; int value = ParseInt(Next("schemaVersion", 2)[1], "schemaVersion"); int protocolVersion = ParseInt(Next("protocolVersion", 2)[1], "protocolVersion"); string bundleVersion = Next("bundleVersion", 2)[1]; List list = new List(); List list2 = new List(); List list3 = new List(); List list4 = new List(); List list5 = new List(); List list6 = new List(); List list7 = new List(); List list8 = new List(); List list9 = new List(); for (; cursor < rows.Count; cursor++) { string[] array = rows[cursor]; switch (array[0]) { case "node": Require(array, 14, "node"); list.Add(new AuthoredNode(Marker(array[1], "node"), array[2], OptionalMarker(array[3], "node parent"), ReadTransform(array, 4, "node"))); break; case "proxy": Require(array, 13, "proxy"); list2.Add(new VanillaProxyDefinition(Marker(array[1], "proxy"), array[2], ReadTransform(array, 3, "proxy"))); break; case "collider": Require(array, 5, "collider"); list3.Add(new ColliderDefinition(Marker(array[1], "collider"), array[2], ReadGeometry(array, 4, "collider"), array[3])); break; case "nav": Require(array, 10, "nav"); list4.Add(new NavDefinition(Marker(array[1], "nav"), array[2], ReadBounds(array, 4, "nav"), (array[3] == "-") ? null : array[3])); break; case "spawn": Require(array, 14, "spawn"); list5.Add(new SpawnDefinition(Marker(array[1], "spawn"), array[2], array[3], ReadTransform(array, 4, "spawn"))); break; case "mechanism": Require(array, 14, "mechanism"); list6.Add(new MechanismDefinition(Marker(array[1], "mechanism"), array[2], array[3], ReadTransform(array, 4, "mechanism"))); break; case "matborrow": { Require(array, 7, "matborrow"); StableMarkerId stableMarkerId = Marker(array[1], "matborrow"); list7.Add(new MaterialBorrowDefinition(stableMarkerId, Marker(array[2], "matborrow target"), ParseInt(array[3], "matborrow subMaterialIndex"), array[4], array[5])); list8.Add(new MaterialBorrowPlacement(stableMarkerId, array[6])); break; } case "parity": Require(array, 13, "parity"); list9.Add(new ArenaParityNode(array[1], array[2], ReadTransform(array, 3, "parity"))); break; default: throw Malformed("unknown row tag '" + array[0] + "'."); } } return new ArenaContentArtifact(new ArenaContentDefinition(arenaId, arenaVersion, arenaContentId, list, list2, list3, list4, list5, list6, list7), new ContentHashSchemaVersion(value), protocolVersion, bundleVersion, list9, list8); string[] Next(string expectedTag, int minFields) { if (cursor >= rows.Count) { throw Malformed("expected a '" + expectedTag + "' row but the artifact ended."); } string[] array2 = rows[cursor++]; if (array2[0] != expectedTag) { throw Malformed("expected a '" + expectedTag + "' row, found '" + array2[0] + "'."); } if (array2.Length < minFields) { throw Malformed($"'{expectedTag}' row has {array2.Length} fields, needs at least {minFields}."); } return array2; } } private static void Require(string[] row, int minFields, string tag) { if (row.Length < minFields) { throw Malformed($"'{tag}' row has {row.Length} fields, needs at least {minFields}."); } } private static StableMarkerId Marker(string token, string where) { if (!Guid.TryParseExact(token, "D", out var result)) { throw Malformed(where + " marker '" + token + "' is not a canonical 'D'-form GUID."); } return new StableMarkerId(result); } private static StableMarkerId? OptionalMarker(string token, string where) { if (!(token == "-")) { return Marker(token, where); } return null; } private static AuthoredTransform ReadTransform(string[] row, int at, string where) { return new AuthoredTransform(new Vector3(ParseFloat(row[at], where), ParseFloat(row[at + 1], where), ParseFloat(row[at + 2], where)), new Quaternion(ParseFloat(row[at + 3], where), ParseFloat(row[at + 4], where), ParseFloat(row[at + 5], where), ParseFloat(row[at + 6], where)), new Vector3(ParseFloat(row[at + 7], where), ParseFloat(row[at + 8], where), ParseFloat(row[at + 9], where))); } private static AuthoredBounds ReadBounds(string[] row, int at, string where) { return new AuthoredBounds(new Vector3(ParseFloat(row[at], where), ParseFloat(row[at + 1], where), ParseFloat(row[at + 2], where)), new Vector3(ParseFloat(row[at + 3], where), ParseFloat(row[at + 4], where), ParseFloat(row[at + 5], where))); } private static IReadOnlyList ReadGeometry(string[] row, int at, string where) { int num = ParseInt(row[at], where + " geometry count"); if (num < 0 || at + 1 + num > row.Length) { throw Malformed($"{where} geometry count {num} does not fit the row."); } double[] array = new double[num]; for (int i = 0; i < num; i++) { array[i] = ParseDouble(row[at + 1 + i], $"{where} geometry[{i}]"); } return array; } private static int ParseInt(string token, string where) { if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { throw Malformed(where + " '" + token + "' is not an integer."); } return result; } private static float ParseFloat(string token, string where) { if (!float.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { throw Malformed(where + " '" + token + "' is not a float."); } return result; } private static double ParseDouble(string token, string where) { if (!double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out var result)) { throw Malformed(where + " '" + token + "' is not a number."); } return result; } private static ArenaContentExportException Malformed(string detail) { return new ArenaContentExportException("Malformed arena content artifact: " + detail); } [CompilerGenerated] private ArenaContentArtifact(ArenaContentArtifact original) { Definition = original.Definition; SchemaVersion = original.SchemaVersion; ProtocolVersion = original.ProtocolVersion; BundleVersion = original.BundleVersion; Parity = original.Parity; MaterialBorrowPlacements = original.MaterialBorrowPlacements; } } public sealed record ArenaParityNode(string Path, string Kind, AuthoredTransform LocalTransform); public sealed record MaterialBorrowPlacement(StableMarkerId BorrowMarkerId, string TargetPath); public sealed record ArenaContentDefinition(string ArenaId, int ArenaVersion, string ArenaContentId, IReadOnlyList Nodes, IReadOnlyList VanillaProxies, IReadOnlyList Colliders, IReadOnlyList NavDefinitions, IReadOnlyList Spawns, IReadOnlyList Mechanisms, IReadOnlyList MaterialBorrows) { public static ArenaContentDefinition Create(string arenaId, int arenaVersion, string arenaContentId, IReadOnlyList? nodes = null, IReadOnlyList? vanillaProxies = null, IReadOnlyList? colliders = null, IReadOnlyList? navDefinitions = null, IReadOnlyList? spawns = null, IReadOnlyList? mechanisms = null, IReadOnlyList? materialBorrows = null) { return new ArenaContentDefinition(arenaId, arenaVersion, arenaContentId, nodes ?? Array.Empty(), vanillaProxies ?? Array.Empty(), colliders ?? Array.Empty(), navDefinitions ?? Array.Empty(), spawns ?? Array.Empty(), mechanisms ?? Array.Empty(), materialBorrows ?? Array.Empty()); } } public sealed class ArenaContentExportException : Exception { public ArenaContentExportException(string message) : base(message) { } } public sealed record ArenaManifest(string ArenaId, int ArenaVersion, ContentHashSchemaVersion ContentHashSchemaVersion, ContentHash ContentHash, int ProtocolVersion, string BundleVersion); public sealed record AuthoredBounds(Vector3 Center, Vector3 Size); public sealed record AuthoredNode(StableMarkerId MarkerId, string NodeKind, StableMarkerId? ParentMarkerId, AuthoredTransform LocalTransform); public sealed record VanillaProxyDefinition(StableMarkerId MarkerId, string AddressableKeyOrGuid, AuthoredTransform LocalTransform); public sealed record MaterialBorrowDefinition(StableMarkerId MarkerId, StableMarkerId TargetMarkerId, int TargetSubMaterialIndex, string CarrierGuid, string MaterialName); public sealed record ColliderDefinition(StableMarkerId MarkerId, string ColliderKind, IReadOnlyList GeometryParameters, string LayerName); public sealed record NavDefinition(StableMarkerId MarkerId, string NavKind, AuthoredBounds Bounds, string? NavmeshPrefabContentId); public sealed record SpawnDefinition(StableMarkerId MarkerId, string SpawnKind, string DefinitionId, AuthoredTransform LocalTransform); public sealed record MechanismDefinition(StableMarkerId MarkerId, string MechanismDefinitionId, string MechanismGroupId, AuthoredTransform LocalTransform); public sealed record AuthoredTransform(Vector3 Position, Quaternion Rotation, Vector3 Scale); public readonly struct ContentHash : IEquatable { public const int Sha256Length = 32; private readonly byte[]? _bytes; public int Length { get { byte[]? bytes = _bytes; if (bytes == null) { return 0; } return bytes.Length; } } public ContentHash(byte[] bytes) { if (bytes == null) { throw new ArgumentNullException("bytes"); } if (bytes.Length != 32) { throw new ArgumentException($"A ContentHash is a full SHA-256 digest of {32} bytes, but {bytes.Length} were " + "given. The hash is never truncated (MultiplayerLoadingContract §5.2.1).", "bytes"); } _bytes = (byte[])bytes.Clone(); } public byte[] ToArray() { return (byte[])(_bytes ?? Array.Empty()).Clone(); } public string ToHex() { byte[] array = _bytes ?? Array.Empty(); char[] array2 = new char[array.Length * 2]; for (int i = 0; i < array.Length; i++) { byte b = array[i]; array2[i * 2] = ToHexChar(b >> 4); array2[i * 2 + 1] = ToHexChar(b & 0xF); } return new string(array2); } public bool Equals(ContentHash other) { byte[] array = _bytes ?? Array.Empty(); byte[] array2 = other._bytes ?? Array.Empty(); if (array.Length != array2.Length) { return false; } for (int i = 0; i < array.Length; i++) { if (array[i] != array2[i]) { return false; } } return true; } public override bool Equals(object? obj) { if (obj is ContentHash other) { return Equals(other); } return false; } public override int GetHashCode() { byte[] bytes = _bytes; if (bytes == null || bytes.Length == 0) { return 0; } int num = 17; int num2 = Math.Min(bytes.Length, 4); for (int i = 0; i < num2; i++) { num = num * 31 + bytes[i]; } return num; } public override string ToString() { return ToHex(); } public static bool operator ==(ContentHash left, ContentHash right) { return left.Equals(right); } public static bool operator !=(ContentHash left, ContentHash right) { return !left.Equals(right); } private static char ToHexChar(int nibble) { return (char)((nibble < 10) ? (48 + nibble) : (97 + (nibble - 10))); } } public static class ContentHashComputer { public static ContentHash Compute(ArenaContentDefinition content) { return Compute(content, ContentHashSchemaVersion.Current); } public static ContentHash Compute(ArenaContentDefinition content, ContentHashSchemaVersion schemaVersion) { if ((object)content == null) { throw new ArgumentNullException("content"); } Validate(content); CanonicalEncoder canonicalEncoder = new CanonicalEncoder(); canonicalEncoder.WriteInt32(schemaVersion.Value); canonicalEncoder.WriteString(content.ArenaId); canonicalEncoder.WriteInt32(content.ArenaVersion); canonicalEncoder.WriteString(content.ArenaContentId); foreach (AuthoredNode item in OrderByMarker(content.Nodes, (AuthoredNode n) => n.MarkerId)) { string context = $"node {item.MarkerId}"; canonicalEncoder.WriteMarker(item.MarkerId); canonicalEncoder.WriteString(item.NodeKind); canonicalEncoder.WriteOptionalMarker(item.ParentMarkerId); canonicalEncoder.WriteTransform(item.LocalTransform, context); } foreach (VanillaProxyDefinition item2 in OrderByMarker(content.VanillaProxies, (VanillaProxyDefinition p) => p.MarkerId)) { string context2 = $"vanilla proxy {item2.MarkerId}"; canonicalEncoder.WriteMarker(item2.MarkerId); canonicalEncoder.WriteString(item2.AddressableKeyOrGuid); canonicalEncoder.WriteTransform(item2.LocalTransform, context2); } foreach (ColliderDefinition item3 in OrderByMarker(content.Colliders, (ColliderDefinition c) => c.MarkerId)) { string context3 = $"collider {item3.MarkerId}"; canonicalEncoder.WriteMarker(item3.MarkerId); canonicalEncoder.WriteString(item3.ColliderKind); WriteGeometry(canonicalEncoder, item3.GeometryParameters, context3); canonicalEncoder.WriteString(item3.LayerName); } foreach (NavDefinition item4 in OrderByMarker(content.NavDefinitions, (NavDefinition n) => n.MarkerId)) { string context4 = $"nav {item4.MarkerId}"; canonicalEncoder.WriteMarker(item4.MarkerId); canonicalEncoder.WriteString(item4.NavKind); canonicalEncoder.WriteBounds(item4.Bounds, context4); canonicalEncoder.WriteOptionalString(item4.NavmeshPrefabContentId); } foreach (SpawnDefinition item5 in OrderByMarker(content.Spawns, (SpawnDefinition s) => s.MarkerId)) { string context5 = $"spawn {item5.MarkerId}"; canonicalEncoder.WriteMarker(item5.MarkerId); canonicalEncoder.WriteString(item5.SpawnKind); canonicalEncoder.WriteString(item5.DefinitionId); canonicalEncoder.WriteTransform(item5.LocalTransform, context5); } foreach (MechanismDefinition item6 in OrderByMarker(content.Mechanisms, (MechanismDefinition m) => m.MarkerId)) { string context6 = $"mechanism {item6.MarkerId}"; canonicalEncoder.WriteMarker(item6.MarkerId); canonicalEncoder.WriteString(item6.MechanismDefinitionId); canonicalEncoder.WriteString(item6.MechanismGroupId); canonicalEncoder.WriteTransform(item6.LocalTransform, context6); } foreach (MaterialBorrowDefinition item7 in OrderByMarker(content.MaterialBorrows, (MaterialBorrowDefinition b) => b.MarkerId)) { canonicalEncoder.WriteMarker(item7.MarkerId); canonicalEncoder.WriteMarker(item7.TargetMarkerId); canonicalEncoder.WriteInt32(item7.TargetSubMaterialIndex); canonicalEncoder.WriteString(item7.CarrierGuid); canonicalEncoder.WriteString(item7.MaterialName); } using SHA256 sHA = SHA256.Create(); return new ContentHash(sHA.ComputeHash(canonicalEncoder.ToArray())); } private static void WriteGeometry(CanonicalEncoder encoder, IReadOnlyList parameters, string context) { encoder.WriteInt32(parameters.Count); for (int i = 0; i < parameters.Count; i++) { encoder.WriteLength(parameters[i], $"{context} geometry[{i}]"); } } private static IEnumerable OrderByMarker(IReadOnlyList items, Func selector) { return items.OrderBy(selector, Comparer.Default); } private static void Validate(ArenaContentDefinition content) { RequireToken(content.ArenaId, "arena", "ArenaId"); RequireToken(content.ArenaContentId, "arena", "ArenaContentId"); RequireNoNullLists(content); RequireNoNullElements(content.Nodes, "node"); RequireNoNullElements(content.VanillaProxies, "vanilla proxy"); RequireNoNullElements(content.Colliders, "collider"); RequireNoNullElements(content.NavDefinitions, "nav"); RequireNoNullElements(content.Spawns, "spawn"); RequireNoNullElements(content.Mechanisms, "mechanism"); RequireNoNullElements(content.MaterialBorrows, "material borrow"); RequireUniqueAssignedMarkers(content); ValidateElementFields(content); ValidateParentReferences(content.Nodes); ValidateMaterialBorrowTargets(content); } private static void ValidateElementFields(ArenaContentDefinition content) { foreach (AuthoredNode node in content.Nodes) { string text = $"node {node.MarkerId}"; RequireToken(node.NodeKind, text, "NodeKind"); RequireTransform(node.LocalTransform, text); } foreach (VanillaProxyDefinition vanillaProxy in content.VanillaProxies) { string text2 = $"vanilla proxy {vanillaProxy.MarkerId}"; RequireToken(vanillaProxy.AddressableKeyOrGuid, text2, "AddressableKeyOrGuid"); RequireTransform(vanillaProxy.LocalTransform, text2); } foreach (ColliderDefinition collider in content.Colliders) { string text3 = $"collider {collider.MarkerId}"; RequireToken(collider.ColliderKind, text3, "ColliderKind"); RequireToken(collider.LayerName, text3, "LayerName"); if (collider.GeometryParameters == null) { throw new ArenaContentExportException("Null GeometryParameters on " + text3 + ": pass an empty list, not null. (Individual parameters are validated for NaN/infinity when quantised.)"); } } foreach (NavDefinition navDefinition in content.NavDefinitions) { string text4 = $"nav {navDefinition.MarkerId}"; RequireToken(navDefinition.NavKind, text4, "NavKind"); RequireBounds(navDefinition.Bounds, text4); RequireOptionalToken(navDefinition.NavmeshPrefabContentId, text4, "NavmeshPrefabContentId"); } foreach (SpawnDefinition spawn in content.Spawns) { string text5 = $"spawn {spawn.MarkerId}"; RequireToken(spawn.SpawnKind, text5, "SpawnKind"); RequireToken(spawn.DefinitionId, text5, "DefinitionId"); RequireTransform(spawn.LocalTransform, text5); } foreach (MechanismDefinition mechanism in content.Mechanisms) { string text6 = $"mechanism {mechanism.MarkerId}"; RequireToken(mechanism.MechanismDefinitionId, text6, "MechanismDefinitionId"); RequireToken(mechanism.MechanismGroupId, text6, "MechanismGroupId"); RequireTransform(mechanism.LocalTransform, text6); } foreach (MaterialBorrowDefinition materialBorrow in content.MaterialBorrows) { string text7 = $"material borrow {materialBorrow.MarkerId}"; RequireToken(materialBorrow.CarrierGuid, text7, "CarrierGuid"); RequireToken(materialBorrow.MaterialName, text7, "MaterialName"); if (materialBorrow.TargetSubMaterialIndex < 0) { throw new ArenaContentExportException("Negative TargetSubMaterialIndex on " + text7 + ": a sub-material slot index is zero or positive."); } } } private static void ValidateMaterialBorrowTargets(ArenaContentDefinition content) { HashSet hashSet = new HashSet(); foreach (AuthoredNode node in content.Nodes) { hashSet.Add(node.MarkerId); } foreach (MaterialBorrowDefinition materialBorrow in content.MaterialBorrows) { string text = $"material borrow {materialBorrow.MarkerId}"; if (materialBorrow.TargetMarkerId.IsUnassigned) { throw new ArenaContentExportException("Unassigned TargetMarkerId on " + text + ": a material borrow must target an assigned node marker."); } if (!hashSet.Contains(materialBorrow.TargetMarkerId)) { throw new ArenaContentExportException($"Dangling TargetMarkerId {materialBorrow.TargetMarkerId} on {text}: it matches no authored node, " + "so there is no renderer to paint."); } } } private static void ValidateParentReferences(IReadOnlyList nodes) { HashSet hashSet = new HashSet(); foreach (AuthoredNode node in nodes) { hashSet.Add(node.MarkerId); } foreach (AuthoredNode node2 in nodes) { StableMarkerId? parentMarkerId = node2.ParentMarkerId; if (parentMarkerId.HasValue) { string text = $"node {node2.MarkerId}"; if (parentMarkerId.Value.IsUnassigned) { throw new ArenaContentExportException("Unassigned ParentMarkerId on " + text + ": a present parent must be an assigned marker, or omitted entirely for a root node."); } if (parentMarkerId.Value == node2.MarkerId) { throw new ArenaContentExportException($"Node {node2.MarkerId} lists itself as its own parent."); } if (!hashSet.Contains(parentMarkerId.Value)) { throw new ArenaContentExportException($"Dangling ParentMarkerId {parentMarkerId.Value} on {text}: it matches no authored node, so the " + "hierarchy is broken."); } } } } private static void RequireToken(string? value, string where, string field) { if (string.IsNullOrWhiteSpace(value)) { throw new ArenaContentExportException("Missing required '" + field + "' on " + where + ": a required identifier/kind must be a non-empty, non-whitespace token authored in canonical form, never null or blank."); } } private static void RequireOptionalToken(string? value, string where, string field) { if (value != null && string.IsNullOrWhiteSpace(value)) { throw new ArenaContentExportException("Blank '" + field + "' on " + where + ": an optional token is either absent (null) or a real non-empty value. A present-but-blank value is ambiguous with absent in the hash."); } } private static void RequireTransform(AuthoredTransform? transform, string where) { if ((object)transform == null) { throw new ArenaContentExportException("Null local transform on " + where + "."); } } private static void RequireBounds(AuthoredBounds? bounds, string where) { if ((object)bounds == null) { throw new ArenaContentExportException("Null bounds on " + where + "."); } } private static void RequireNoNullElements(IReadOnlyList items, string kind) where T : class { for (int i = 0; i < items.Count; i++) { if (items[i] == null) { throw new ArenaContentExportException($"Null {kind} at index {i}: a content section must not contain null elements."); } } } private static void RequireNoNullLists(ArenaContentDefinition content) { RequireNotNull(content.Nodes, "Nodes"); RequireNotNull(content.VanillaProxies, "VanillaProxies"); RequireNotNull(content.Colliders, "Colliders"); RequireNotNull(content.NavDefinitions, "NavDefinitions"); RequireNotNull(content.Spawns, "Spawns"); RequireNotNull(content.Mechanisms, "Mechanisms"); RequireNotNull(content.MaterialBorrows, "MaterialBorrows"); static void RequireNotNull(object? list, string name) { if (list == null) { throw new ArenaContentExportException("Arena content section '" + name + "' is null. A dropped section must not silently hash the same as an empty one; pass an empty list (or use ArenaContentDefinition.Create)."); } } } private static void RequireUniqueAssignedMarkers(ArenaContentDefinition content) { HashSet hashSet = new HashSet(); foreach (var (stableMarkerId, text) in EnumerateMarkers(content)) { if (stableMarkerId.IsUnassigned) { throw new ArenaContentExportException("Unassigned StableMarkerId on " + text + ": every authored node that participates in content identity must carry an editor-assigned GUID, never a name or a default id."); } if (!hashSet.Add(stableMarkerId)) { throw new ArenaContentExportException($"Duplicate StableMarkerId {stableMarkerId} (seen again on {text}): marker ids must be " + "unique across the whole arena, or ordering and parent references become ambiguous."); } } } private static IEnumerable<(StableMarkerId Marker, string Description)> EnumerateMarkers(ArenaContentDefinition content) { foreach (AuthoredNode node in content.Nodes) { yield return (Marker: node.MarkerId, Description: "node (kind '" + node.NodeKind + "')"); } foreach (VanillaProxyDefinition vanillaProxy in content.VanillaProxies) { yield return (Marker: vanillaProxy.MarkerId, Description: "vanilla proxy ('" + vanillaProxy.AddressableKeyOrGuid + "')"); } foreach (ColliderDefinition collider in content.Colliders) { yield return (Marker: collider.MarkerId, Description: "collider (kind '" + collider.ColliderKind + "')"); } foreach (NavDefinition navDefinition in content.NavDefinitions) { yield return (Marker: navDefinition.MarkerId, Description: "nav (kind '" + navDefinition.NavKind + "')"); } foreach (SpawnDefinition spawn in content.Spawns) { yield return (Marker: spawn.MarkerId, Description: "spawn (kind '" + spawn.SpawnKind + "')"); } foreach (MechanismDefinition mechanism in content.Mechanisms) { yield return (Marker: mechanism.MarkerId, Description: "mechanism ('" + mechanism.MechanismDefinitionId + "')"); } foreach (MaterialBorrowDefinition materialBorrow in content.MaterialBorrows) { yield return (Marker: materialBorrow.MarkerId, Description: "material borrow ('" + materialBorrow.MaterialName + "')"); } } } public readonly struct ContentHashSchemaVersion : IEquatable { public static readonly ContentHashSchemaVersion Current = new ContentHashSchemaVersion(2); public int Value { get; } public ContentHashSchemaVersion(int value) { Value = value; } public bool Equals(ContentHashSchemaVersion other) { return Value == other.Value; } public override bool Equals(object? obj) { if (obj is ContentHashSchemaVersion other) { return Equals(other); } return false; } public override int GetHashCode() { return Value; } public override string ToString() { return Value.ToString(); } public static bool operator ==(ContentHashSchemaVersion left, ContentHashSchemaVersion right) { return left.Equals(right); } public static bool operator !=(ContentHashSchemaVersion left, ContentHashSchemaVersion right) { return !left.Equals(right); } } public readonly struct Quaternion : IEquatable { public static Quaternion Identity => new Quaternion(0f, 0f, 0f, 1f); public float X { get; } public float Y { get; } public float Z { get; } public float W { get; } public Quaternion(float x, float y, float z, float w) { X = x; Y = y; Z = z; W = w; } public bool Equals(Quaternion other) { if (X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z)) { return W.Equals(other.W); } return false; } public override bool Equals(object? obj) { if (obj is Quaternion other) { return Equals(other); } return false; } public override int GetHashCode() { return (((((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode()) * 397) ^ W.GetHashCode(); } public override string ToString() { return $"({X}, {Y}, {Z}, {W})"; } public static bool operator ==(Quaternion left, Quaternion right) { return left.Equals(right); } public static bool operator !=(Quaternion left, Quaternion right) { return !left.Equals(right); } } public readonly struct StableMarkerId : IEquatable, IComparable { private readonly Guid _value; public bool IsUnassigned => _value == Guid.Empty; public StableMarkerId(Guid value) { _value = value; } public string ToCanonicalString() { Guid value = _value; return value.ToString("D"); } public int CompareTo(StableMarkerId other) { return string.CompareOrdinal(ToCanonicalString(), other.ToCanonicalString()); } public bool Equals(StableMarkerId other) { Guid value = _value; return value.Equals(other._value); } public override bool Equals(object? obj) { if (obj is StableMarkerId other) { return Equals(other); } return false; } public override int GetHashCode() { return _value.GetHashCode(); } public override string ToString() { return ToCanonicalString(); } public static bool operator ==(StableMarkerId left, StableMarkerId right) { return left.Equals(right); } public static bool operator !=(StableMarkerId left, StableMarkerId right) { return !left.Equals(right); } public static bool operator <(StableMarkerId left, StableMarkerId right) { return left.CompareTo(right) < 0; } public static bool operator >(StableMarkerId left, StableMarkerId right) { return left.CompareTo(right) > 0; } public static bool operator <=(StableMarkerId left, StableMarkerId right) { return left.CompareTo(right) <= 0; } public static bool operator >=(StableMarkerId left, StableMarkerId right) { return left.CompareTo(right) >= 0; } } public readonly struct Vector3 : IEquatable { public float X { get; } public float Y { get; } public float Z { get; } public Vector3(float x, float y, float z) { X = x; Y = y; Z = z; } public bool Equals(Vector3 other) { if (X.Equals(other.X) && Y.Equals(other.Y)) { return Z.Equals(other.Z); } return false; } public override bool Equals(object? obj) { if (obj is Vector3 other) { return Equals(other); } return false; } public override int GetHashCode() { return (((X.GetHashCode() * 397) ^ Y.GetHashCode()) * 397) ^ Z.GetHashCode(); } public override string ToString() { return $"({X}, {Y}, {Z})"; } public static bool operator ==(Vector3 left, Vector3 right) { return left.Equals(right); } public static bool operator !=(Vector3 left, Vector3 right) { return !left.Equals(right); } } } namespace FalseGods.Protocol.Arena.Internal { internal sealed class CanonicalEncoder { private readonly MemoryStream _stream = new MemoryStream(); public void WriteInt32(int value) { _stream.WriteByte((byte)value); _stream.WriteByte((byte)(value >> 8)); _stream.WriteByte((byte)(value >> 16)); _stream.WriteByte((byte)(value >> 24)); } public void WriteInt64(long value) { for (int i = 0; i < 8; i++) { _stream.WriteByte((byte)(value >> 8 * i)); } } public void WriteString(string value) { if (value == null) { throw new ArgumentNullException("value", "A required string reached the encoder as null; validate it before encoding."); } byte[] bytes = Encoding.UTF8.GetBytes(value); WriteInt32(bytes.Length); _stream.Write(bytes, 0, bytes.Length); } public void WriteMarker(StableMarkerId marker) { WriteString(marker.ToCanonicalString()); } public void WriteOptionalMarker(StableMarkerId? marker) { WriteString(marker.HasValue ? marker.Value.ToCanonicalString() : string.Empty); } public void WriteOptionalString(string? value) { WriteString(value ?? string.Empty); } public void WriteLength(double value, string context) { WriteInt64(Quantizer.QuantizeLength(value, context)); } public void WriteVector(Vector3 vector, string context) { WriteLength(vector.X, context); WriteLength(vector.Y, context); WriteLength(vector.Z, context); } public void WriteTransform(AuthoredTransform transform, string context) { WriteVector(transform.Position, context); var (value, value2, value3, value4) = Quantizer.QuantizeRotation(transform.Rotation, context); WriteInt64(value); WriteInt64(value2); WriteInt64(value3); WriteInt64(value4); WriteVector(transform.Scale, context); } public void WriteBounds(AuthoredBounds bounds, string context) { WriteVector(bounds.Center, context); WriteVector(bounds.Size, context); } public byte[] ToArray() { return _stream.ToArray(); } } internal static class Quantizer { private const double LengthScale = 10000.0; private const double RotationScale = 1000000.0; public static long QuantizeLength(double value, string context) { return QuantizeAt(NormalizeZero(RequireFinite(value, context)), 10000.0); } public static (long W, long X, long Y, long Z) QuantizeRotation(Quaternion rotation, string context) { double num = RequireFinite(rotation.X, context); double num2 = RequireFinite(rotation.Y, context); double num3 = RequireFinite(rotation.Z, context); double num4 = RequireFinite(rotation.W, context); double num5 = Math.Sqrt(num * num + num2 * num2 + num3 * num3 + num4 * num4); if (num5 == 0.0) { throw new ArenaContentExportException("Zero-length quaternion at " + context + ": a rotation with no magnitude has no canonical representation. Fix the authored rotation (identity is (0,0,0,1))."); } num = NormalizeZero(num / num5); num2 = NormalizeZero(num2 / num5); num3 = NormalizeZero(num3 / num5); num4 = NormalizeZero(num4 / num5); if (((num4 != 0.0) ? num4 : ((num != 0.0) ? num : ((num2 != 0.0) ? num2 : num3))) < 0.0) { num = NormalizeZero(0.0 - num); num2 = NormalizeZero(0.0 - num2); num3 = NormalizeZero(0.0 - num3); num4 = NormalizeZero(0.0 - num4); } return (W: QuantizeAt(num4, 1000000.0), X: QuantizeAt(num, 1000000.0), Y: QuantizeAt(num2, 1000000.0), Z: QuantizeAt(num3, 1000000.0)); } private static long QuantizeAt(double value, double scale) { return (long)Math.Round(value * scale, MidpointRounding.ToEven); } private static double NormalizeZero(double value) { if (value != 0.0) { return value; } return 0.0; } private static double RequireFinite(double value, string context) { if (double.IsNaN(value) || double.IsInfinity(value)) { throw new ArenaContentExportException($"Non-finite authored value ({value}) at {context}: NaN and infinity cannot be quantised " + "into a reproducible hash and are a build-time export failure (MultiplayerLoadingContract §5.2.1)."); } return value; } } }