Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of Forsaken Ascension v0.0.38
BepInEx/plugins/ForsakenAscension/ForsakenAscension.dll
Decompiled 14 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Xml; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ForsakenAscension.Core; using HarmonyLib; using Jotunn.Entities; using Jotunn.Managers; using Jotunn.Utils; using SoftReferenceableAssets; using UnityEngine; using UnityEngine.Rendering; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] namespace ForsakenAscension.Core { internal static class AtomicFile { internal static void Write(string path, byte[] data) { Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(path))); string text = path + "." + Guid.NewGuid().ToString("N") + ".tmp"; try { using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fileStream.Write(data, 0, data.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(path)) { File.Replace(text, path, path + ".bak"); } else { File.Move(text, path); } } finally { if (File.Exists(text)) { File.Delete(text); } } } } public enum CombatObservationKind : byte { BossDamage, DefenseHealthLoss, DefenseBlocked, Healing } public sealed class BossDamagePacket { public const int Size = 73; public CombatObservationKind Kind; public long World; public long Session; public long Sequence; public long BossCreator; public long AttackerCreator; public uint BossId; public uint AttackerId; public long RecipientCreator; public uint RecipientId; public float Before; public float After; private void Validate() { if (!Enum.IsDefined(typeof(CombatObservationKind), Kind)) { throw new ArgumentException("Invalid combat observation kind"); } if (Session == 0 || Sequence <= 0 || BossCreator == 0 || BossId == 0 || AttackerCreator == 0 || AttackerId == 0 || float.IsNaN(Before) || float.IsInfinity(Before) || float.IsNaN(After) || float.IsInfinity(After)) { throw new ArgumentException("Invalid combat observation"); } if (Kind == CombatObservationKind.Healing) { if (Before <= 0f || After <= Before || RecipientCreator == 0 || RecipientId == 0 || (RecipientCreator == AttackerCreator && RecipientId == AttackerId)) { throw new ArgumentException("Invalid healing observation"); } } else if (Before <= 0f || After < 0f || After >= Before || RecipientCreator != 0 || RecipientId != 0) { throw new ArgumentException("Invalid boss damage observation"); } } public byte[] Encode() { Validate(); using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(4); binaryWriter.Write(World); binaryWriter.Write(Session); binaryWriter.Write(Sequence); binaryWriter.Write(BossCreator); binaryWriter.Write(BossId); binaryWriter.Write(AttackerCreator); binaryWriter.Write(AttackerId); binaryWriter.Write(Before); binaryWriter.Write(After); binaryWriter.Write((byte)Kind); binaryWriter.Write(RecipientCreator); binaryWriter.Write(RecipientId); return memoryStream.ToArray(); } public static BossDamagePacket Decode(byte[] bytes, long world) { if (bytes == null || bytes.Length != 73) { throw new InvalidDataException("Invalid damage packet size"); } using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes)); if (binaryReader.ReadInt32() != 4) { throw new InvalidDataException("Unknown damage protocol"); } BossDamagePacket bossDamagePacket = new BossDamagePacket(); bossDamagePacket.World = binaryReader.ReadInt64(); bossDamagePacket.Session = binaryReader.ReadInt64(); bossDamagePacket.Sequence = binaryReader.ReadInt64(); bossDamagePacket.BossCreator = binaryReader.ReadInt64(); bossDamagePacket.BossId = binaryReader.ReadUInt32(); bossDamagePacket.AttackerCreator = binaryReader.ReadInt64(); bossDamagePacket.AttackerId = binaryReader.ReadUInt32(); bossDamagePacket.Before = binaryReader.ReadSingle(); bossDamagePacket.After = binaryReader.ReadSingle(); BossDamagePacket bossDamagePacket2 = bossDamagePacket; bossDamagePacket2.Kind = (CombatObservationKind)binaryReader.ReadByte(); bossDamagePacket2.RecipientCreator = binaryReader.ReadInt64(); bossDamagePacket2.RecipientId = binaryReader.ReadUInt32(); if (bossDamagePacket2.World != world) { throw new InvalidDataException("Wrong observation world"); } bossDamagePacket2.Validate(); return bossDamagePacket2; } } public sealed class BossDamageAck { public long World; public long Session; public long Sequence; public bool Accepted; public byte[] Encode() { if (Session == 0 || Sequence <= 0) { throw new ArgumentException("Invalid damage acknowledgement"); } using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(1); binaryWriter.Write(World); binaryWriter.Write(Session); binaryWriter.Write(Sequence); binaryWriter.Write(Accepted); return memoryStream.ToArray(); } public static BossDamageAck Decode(byte[] bytes, long world, long session) { if (bytes == null || bytes.Length != 29) { throw new InvalidDataException("Invalid damage acknowledgement size"); } using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes)); if (binaryReader.ReadInt32() != 1) { throw new InvalidDataException("Unknown acknowledgement protocol"); } BossDamageAck bossDamageAck = new BossDamageAck(); bossDamageAck.World = binaryReader.ReadInt64(); bossDamageAck.Session = binaryReader.ReadInt64(); bossDamageAck.Sequence = binaryReader.ReadInt64(); BossDamageAck bossDamageAck2 = bossDamageAck; byte b = binaryReader.ReadByte(); if (bossDamageAck2.World != world || bossDamageAck2.Session != session || bossDamageAck2.Session == 0 || bossDamageAck2.Sequence <= 0 || b > 1) { throw new InvalidDataException("Invalid damage acknowledgement identity"); } bossDamageAck2.Accepted = b == 1; return bossDamageAck2; } } public enum BossMove : byte { Rootbreaker = 1, SplinterFan, BrambleMaze, HeartwoodWard, ForestGuard, DoubleBackhand, RotTrail, BoneVolley, CarrionCall, PutridDetonation, ClawCombo, RakingBreath, FrozenCrown, MountainGust, ShatteringWinter, CinderRing, SweepingDoom, MeteorMarch, ScorchedBrand, EmberRitual, RendingFollowup, AcidFan, PredatorsRush, BurrowAmbush, RoyalGuard, ScorchingCleave, ForkedFissures, EmberBreath, CinderGate, AshenJudgment } public enum HazardShape : byte { Projectile = 1, Cone, Pool, Root, Ward, Crystal, Stone, ExpandingRing, Meteor, Brand, Ritual, Wall, ContractingRing, Trail, Combo, Rush, Burrow, Summon, SweepingCone, Strip } public static class BossMoves { public static Boss BossOf(BossMove move) { Check(move); return (Boss)(1 + (int)(move - 1) / 5); } public static int Star(BossMove move) { Check(move); return 1 + (int)(move - 1) % 5; } public static bool Allows(Boss boss, int tier, int version, BossMove move) { if (version == 4 && tier >= 1 && tier <= 5 && Enum.IsDefined(typeof(BossMove), move) && BossOf(move) == boss) { return Star(move) <= tier; } return false; } private static void Check(BossMove move) { if (!Enum.IsDefined(typeof(BossMove), move)) { throw new ArgumentException("Unknown boss move"); } } public static float Warning(BossMove move) { Check(move); return move switch { BossMove.ScorchedBrand => 3f, BossMove.AshenJudgment => 3f, BossMove.EmberRitual => 6f, _ => 1.6f, }; } public static float Cooldown(BossMove move) { Check(move); if (Star(move) != 5) { if (Star(move) != 4) { if (Star(move) != 3) { return 18f; } return 24f; } return 30f; } return 40f; } public static float BaseDamage(BossMove move) { return BossOf(move) switch { Boss.Elder => 18f, Boss.Bonemass => 25f, Boss.Moder => 30f, Boss.Yagluth => 35f, Boss.Queen => 40f, _ => 45f, }; } public static bool IsProp(HazardShape shape) { if (shape != HazardShape.Root && shape != HazardShape.Ward && shape != HazardShape.Crystal) { return shape == HazardShape.Stone; } return true; } public static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } public static PulsePosition Lerp(PulsePosition a, PulsePosition b, float t) { return new PulsePosition { X = a.X + (b.X - a.X) * t, Y = a.Y + (b.Y - a.Y) * t, Z = a.Z + (b.Z - a.Z) * t }; } public static bool Circle(PulsePosition point, PulsePosition center, float radius) { if (ValidPoint(point) && ValidPoint(center) && Math.Abs(point.Y - center.Y) <= 3f) { return Square(point.X - center.X) + Square(point.Z - center.Z) <= radius * radius; } return false; } public static bool Cone(PulsePosition point, PulsePosition origin, PulsePosition end, float radius, float halfAngle) { if (!Circle(point, origin, radius)) { return false; } float num = point.X - origin.X; float num2 = point.Z - origin.Z; float num3 = end.X - origin.X; float num4 = end.Z - origin.Z; double num5 = Math.Sqrt(num * num + num2 * num2); double num6 = Math.Sqrt(num3 * num3 + num4 * num4); if (num6 > 0.01) { if (!(num5 < 0.01)) { return (double)(num * num3 + num2 * num4) / (num5 * num6) >= Math.Cos((double)halfAngle * Math.PI / 180.0); } return true; } return false; } public static bool Ring(PulsePosition point, PulsePosition center, float radius, float width) { if (!ValidPoint(point) || !ValidPoint(center) || Math.Abs(point.Y - center.Y) > 3f) { return false; } double num = Math.Sqrt(Square(point.X - center.X) + Square(point.Z - center.Z)); return Math.Abs(num - (double)radius) <= (double)width; } public static PulsePosition Rotate(PulsePosition origin, PulsePosition end, float degrees) { double num = (double)degrees * Math.PI / 180.0; float num2 = end.X - origin.X; float num3 = end.Z - origin.Z; return new PulsePosition { X = origin.X + num2 * (float)Math.Cos(num) + num3 * (float)Math.Sin(num), Y = end.Y, Z = origin.Z - num2 * (float)Math.Sin(num) + num3 * (float)Math.Cos(num) }; } public static bool Wall(PulsePosition point, PulsePosition a, PulsePosition b, float t, float halfLength, float gap, float width) { if (!ValidPoint(point) || !ValidPoint(a) || !ValidPoint(b)) { return false; } PulsePosition pulsePosition = Lerp(a, b, t); float num = b.X - a.X; float num2 = b.Z - a.Z; double num3 = Math.Sqrt(num * num + num2 * num2); if (num3 < 0.01 || Math.Abs(point.Y - pulsePosition.Y) > 3f) { return false; } double value = (double)((point.X - pulsePosition.X) * num + (point.Z - pulsePosition.Z) * num2) / num3; double value2 = (double)((0f - (point.X - pulsePosition.X)) * num2 + (point.Z - pulsePosition.Z) * num) / num3; if (Math.Abs(value) <= (double)width && Math.Abs(value2) >= (double)gap) { return Math.Abs(value2) <= (double)halfLength; } return false; } private static float Square(float value) { return value * value; } internal static bool ValidPoint(PulsePosition p) { if (Finite(p.X) && Finite(p.Y) && Finite(p.Z) && Math.Abs(p.X) < 100000f && Math.Abs(p.Y) < 10000f) { return Math.Abs(p.Z) < 100000f; } return false; } } public sealed class BossHazard { public int Id; public int Group; public BossMove Move; public HazardShape Shape; public long Due; public long End; public long Owner; public long TargetCreator; public uint TargetId; public PulsePosition A; public PulsePosition B; public float Radius; public float Width; public float Health; public float Parameter; public void Validate() { if (Id <= 0 || Group <= 0 || !Enum.IsDefined(typeof(BossMove), Move) || !Enum.IsDefined(typeof(HazardShape), Shape) || Due <= 0 || End < Due || (double)(End - Due) / 10000000.0 > 40.0 || Owner == 0 || !BossMoves.ValidPoint(A) || !BossMoves.ValidPoint(B) || !BossMoves.Finite(Radius) || Radius < 0f || Radius > 45f || !BossMoves.Finite(Width) || Width < 0f || Width > 20f || !BossMoves.Finite(Health) || Health < 0f || Health > 100000f || !BossMoves.Finite(Parameter) || Math.Abs(Parameter) > 360f || TargetCreator == 0 != (TargetId == 0)) { throw new InvalidDataException("Invalid boss hazard"); } } public float Progress(long now) { if (End != Due) { return Math.Max(0f, Math.Min(1f, (float)((double)(now - Due) / (double)(End - Due)))); } return 1f; } } public static class BossHazardPacket { public const int MaximumCount = 32; public const int MaximumBytes = 4096; public static byte[] Encode(IList<BossHazard> hazards) { if (hazards == null || hazards.Count > 32) { throw new InvalidDataException("Too many boss hazards"); } HashSet<int> hashSet = new HashSet<int>(); using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(1); binaryWriter.Write((byte)hazards.Count); foreach (BossHazard hazard in hazards) { if (hazard == null) { throw new InvalidDataException("Null boss hazard"); } hazard.Validate(); if (!hashSet.Add(hazard.Id)) { throw new InvalidDataException("Duplicate boss hazard"); } binaryWriter.Write(hazard.Id); binaryWriter.Write(hazard.Group); binaryWriter.Write((byte)hazard.Move); binaryWriter.Write((byte)hazard.Shape); binaryWriter.Write(hazard.Due); binaryWriter.Write(hazard.End); binaryWriter.Write(hazard.Owner); binaryWriter.Write(hazard.TargetCreator); binaryWriter.Write(hazard.TargetId); Write(binaryWriter, hazard.A); Write(binaryWriter, hazard.B); binaryWriter.Write(hazard.Radius); binaryWriter.Write(hazard.Width); binaryWriter.Write(hazard.Health); binaryWriter.Write(hazard.Parameter); } return memoryStream.ToArray(); } public static List<BossHazard> Decode(byte[] bytes, Boss boss, int tier) { if (bytes == null || bytes.Length < 5 || bytes.Length > 4096) { throw new InvalidDataException("Invalid boss hazard size"); } using MemoryStream input = new MemoryStream(bytes); using BinaryReader binaryReader = new BinaryReader(input); if (binaryReader.ReadInt32() != 1) { throw new InvalidDataException("Unknown boss hazard protocol"); } int num = binaryReader.ReadByte(); if (num > 32 || bytes.Length != 5 + num * 86) { throw new InvalidDataException("Invalid boss hazard count"); } List<BossHazard> list = new List<BossHazard>(); HashSet<int> hashSet = new HashSet<int>(); for (int i = 0; i < num; i++) { BossHazard bossHazard = new BossHazard(); bossHazard.Id = binaryReader.ReadInt32(); bossHazard.Group = binaryReader.ReadInt32(); bossHazard.Move = (BossMove)binaryReader.ReadByte(); bossHazard.Shape = (HazardShape)binaryReader.ReadByte(); bossHazard.Due = binaryReader.ReadInt64(); bossHazard.End = binaryReader.ReadInt64(); bossHazard.Owner = binaryReader.ReadInt64(); bossHazard.TargetCreator = binaryReader.ReadInt64(); bossHazard.TargetId = binaryReader.ReadUInt32(); bossHazard.A = Read(binaryReader); bossHazard.B = Read(binaryReader); bossHazard.Radius = binaryReader.ReadSingle(); bossHazard.Width = binaryReader.ReadSingle(); bossHazard.Health = binaryReader.ReadSingle(); bossHazard.Parameter = binaryReader.ReadSingle(); BossHazard bossHazard2 = bossHazard; bossHazard2.Validate(); if (!hashSet.Add(bossHazard2.Id) || !BossMoves.Allows(boss, tier, 4, bossHazard2.Move)) { throw new InvalidDataException("Unexpected boss hazard"); } list.Add(bossHazard2); } return list; } private static void Write(BinaryWriter writer, PulsePosition p) { writer.Write(p.X); writer.Write(p.Y); writer.Write(p.Z); } private static PulsePosition Read(BinaryReader reader) { return new PulsePosition { X = reader.ReadSingle(), Y = reader.ReadSingle(), Z = reader.ReadSingle() }; } } public sealed class ChallengeDamage { private readonly double[][] values; public ChallengeDamage(double[][] source) { if (source == null || source.Length != 7) { throw new ArgumentException("Expected damage curves for seven bosses"); } values = new double[7][]; for (int i = 0; i < 7; i++) { if (source[i] == null || source[i].Length != 6) { throw new ArgumentException("Expected six damage multipliers"); } values[i] = (double[])source[i].Clone(); double[] array = values[i]; foreach (double num in array) { if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.01 || num > 1000.0) { throw new ArgumentException("Damage multipliers must be between 0.01 and 1000"); } } } } public static ChallengeDamage Defaults() { return new ChallengeDamage((from i in Enumerable.Range(0, 7) select new double[6] { 1.0, 1.508, 1.664, 1.82, 1.976, 2.145 }).ToArray()); } public float Multiplier(Boss boss, int tier) { Progression.CheckBoss(boss); Progression.CheckTier(tier); return (float)values[(int)boss][tier]; } } public sealed class ChallengeHealth { private readonly double[][] values; public ChallengeHealth(double[][] source) { if (source == null || source.Length != 7) { throw new ArgumentException("Expected health curves for seven bosses"); } values = new double[7][]; for (int i = 0; i < 7; i++) { if (source[i] == null || source[i].Length != 6) { throw new ArgumentException("Expected six health multipliers"); } values[i] = (double[])source[i].Clone(); double[] array = values[i]; foreach (double value in array) { ValidateMultiplier(value); } } } public static ChallengeHealth Defaults() { return new ChallengeHealth((from i in Enumerable.Range(0, 7) select new double[6] { 1.0, 1.45, 1.95, 2.55, 3.25, 4.0 }).ToArray()); } public float Multiplier(Boss boss, int tier) { Progression.CheckBoss(boss); Progression.CheckTier(tier); return (float)values[(int)boss][tier]; } public static float Maximum(float baseHealth, float multiplier) { if (float.IsNaN(multiplier) || float.IsInfinity(multiplier) || multiplier < 0.01f || multiplier > 1000f) { throw new ArgumentException("Health multipliers must be between 0.01 and 1000"); } double num = (double)baseHealth * (double)multiplier; if (float.IsNaN(baseHealth) || float.IsInfinity(baseHealth) || baseHealth <= 0f || num > 3.4028234663852886E+38 || (float)num <= 0f) { throw new ArgumentException("Invalid challenge base health"); } return (float)num; } private static void ValidateMultiplier(double value) { if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.01 || value > 1000.0) { throw new ArgumentException("Health multipliers must be between 0.01 and 1000"); } } } [DataContract] public sealed class ProgressionScalingSnapshot { [DataMember] public int Stage; [DataMember] public bool Enabled; [DataMember] public float Strength; [DataMember] public float HealthBaseline; [DataMember] public float DamageBaseline; public void Validate() { ChallengeProgression.CheckStage(Stage); ChallengeProgression.CheckStrength(Strength); float[] array = new float[2] { HealthBaseline, DamageBaseline }; foreach (float num in array) { if (float.IsNaN(num) || float.IsInfinity(num) || num < 1f || num > 20f) { throw new ArgumentException("Invalid progression baseline snapshot"); } } if (!Enabled && (HealthBaseline != 1f || DamageBaseline != 1f)) { throw new ArgumentException("Disabled progression cannot retain an increased baseline"); } } public ProgressionScalingSnapshot Copy() { Validate(); ProgressionScalingSnapshot progressionScalingSnapshot = new ProgressionScalingSnapshot(); progressionScalingSnapshot.Stage = Stage; progressionScalingSnapshot.Enabled = Enabled; progressionScalingSnapshot.Strength = Strength; progressionScalingSnapshot.HealthBaseline = HealthBaseline; progressionScalingSnapshot.DamageBaseline = DamageBaseline; return progressionScalingSnapshot; } public static bool Same(ProgressionScalingSnapshot a, ProgressionScalingSnapshot b) { if (a != null) { if (b != null && a.Stage == b.Stage && a.Enabled == b.Enabled && a.Strength == b.Strength && a.HealthBaseline == b.HealthBaseline) { return a.DamageBaseline == b.DamageBaseline; } return false; } return b == null; } public string Description() { Validate(); return (Enabled ? (ChallengeProgression.StageName(Stage) + " scaling") : "Progression scaling disabled") + " — challenge strength " + Strength.ToString("0.##", CultureInfo.InvariantCulture) + "×"; } } public sealed class ChallengeProgression { private static readonly float[] health = new float[8] { 1f, 1.45f, 2.1f, 3.05f, 4.4f, 6.4f, 9.3f, 13.5f }; private static readonly float[] damage = new float[8] { 1f, 1.25f, 1.6f, 2f, 2.55f, 3.2f, 4f, 5f }; private static readonly string[] names = new string[8] { "Meadows", "Black Forest", "Swamp", "Mountains", "Plains", "Mistlands", "Ashlands", "Deep North" }; private readonly bool enabled; private readonly float strength; private readonly int stageOverride; public ChallengeProgression(bool enabled = true, float strength = 1f, int stageOverride = -1) { CheckStrength(strength); if (stageOverride != -1) { CheckStage(stageOverride); } this.enabled = enabled; this.strength = strength; this.stageOverride = stageOverride; } public static void CheckStage(int stage) { if (stage < 0 || stage > 7) { throw new ArgumentOutOfRangeException("stage"); } } public static void CheckStrength(float strength) { if (float.IsNaN(strength) || float.IsInfinity(strength) || strength < 0.5f || strength > 3f) { throw new ArgumentOutOfRangeException("strength"); } } public static string StageName(int stage) { CheckStage(stage); return names[stage]; } public static int StageAfter(Boss boss) { Progression.CheckBoss(boss); return (int)(boss + 1); } public FightScaling For(Boss boss, int tier, int worldStage, float starHealth, float starDamage) { Progression.CheckBoss(boss); Progression.CheckTier(tier); CheckStage(worldStage); FightScaling fightScaling = new FightScaling(); fightScaling.Health = starHealth; fightScaling.Damage = starDamage; FightScaling fightScaling2 = fightScaling; fightScaling2.Validate(); if (tier == 0) { return fightScaling2; } int num = Math.Max((int)boss, (stageOverride < 0) ? worldStage : stageOverride); int num2 = (int)(enabled ? (num - boss) : Boss.Eikthyr); fightScaling2.Progression = new ProgressionScalingSnapshot { Stage = num, Enabled = enabled, Strength = strength, HealthBaseline = health[num2], DamageBaseline = damage[num2] }; fightScaling2.Health *= health[num2] * strength; fightScaling2.Damage *= damage[num2] * strength; fightScaling2.Validate(); return fightScaling2; } } public enum EscrowDecision { AwaitHost, Consume, Return } [DataContract] public sealed class EscrowEntry { [DataMember] public string Operation; [DataMember] public string Receipt; [DataMember] public string Prefab; [DataMember] public byte[] ItemBytes; [DataMember] public byte[] RequestBytes; [DataMember] public EscrowDecision Decision; [DataMember] public bool InventorySaved; [DataMember] public bool RemovalStarted; [DataMember(EmitDefaultValue = false)] public int MarkerVersion; } [DataContract] public sealed class EscrowState { [DataMember] public int Schema = 2; [DataMember] public long World; [DataMember] public long Player; [DataMember] public List<EscrowEntry> Entries = new List<EscrowEntry>(); } public sealed class ClientEscrow { private readonly string path; private EscrowState state; public ClientEscrow(string savePath, long world, long player) { if (player <= 0) { throw new ArgumentOutOfRangeException("player"); } path = savePath; state = (File.Exists(path) ? Decode(File.ReadAllBytes(path)) : new EscrowState { World = world, Player = player }); if (state.World != world || state.Player != player) { throw new InvalidDataException("Escrow belongs to another world or character"); } } public EscrowEntry[] Pending() { return Clone().Entries.Where((EscrowEntry e) => !e.InventorySaved).ToArray(); } public EscrowEntry Read(string operation) { return Find(Clone(), operation); } public void Store(string operation, TrophyIdentity identity, string prefab, byte[] serializedItem, byte[] requestBytes = null) { Progression.CheckId(operation); if (identity == null || identity.WorldId != state.World || prefab != TrophyIdentity.PrefabName(identity.Boss, identity.Tier)) { throw new ArgumentException("Invalid escrow identity"); } identity.Write(new Dictionary<string, string>()); if (serializedItem == null || serializedItem.Length == 0 || serializedItem.Length > 1048576) { throw new ArgumentException("Invalid item snapshot"); } if (requestBytes != null) { ValidateRequest(requestBytes, state.World, operation, identity.Receipt, prefab); } Commit(delegate(EscrowState next) { EscrowEntry escrowEntry = next.Entries.Find((EscrowEntry e) => e.Operation == operation); if (escrowEntry != null) { if (escrowEntry.Receipt != identity.Receipt || escrowEntry.Prefab != prefab || !escrowEntry.ItemBytes.SequenceEqual(serializedItem)) { throw new InvalidOperationException("Escrow replay changed item"); } if (escrowEntry.RequestBytes == null != (requestBytes == null) || (requestBytes != null && !escrowEntry.RequestBytes.SequenceEqual(requestBytes))) { throw new InvalidOperationException("Escrow replay changed request"); } if (escrowEntry.InventorySaved || escrowEntry.Decision != EscrowDecision.AwaitHost) { throw new InvalidOperationException("Resolved escrow cannot remove an item again"); } } else { if (next.Entries.Any((EscrowEntry e) => e.Receipt == identity.Receipt && !e.InventorySaved)) { throw new InvalidOperationException("Trophy already escrowed"); } next.Entries.Add(new EscrowEntry { Operation = operation, Receipt = identity.Receipt, Prefab = prefab, ItemBytes = (byte[])serializedItem.Clone(), RequestBytes = ((requestBytes == null) ? null : ((byte[])requestBytes.Clone())), MarkerVersion = 1 }); } }); } public void BeginRemoval(string operation) { Commit(delegate(EscrowState next) { EscrowEntry escrowEntry = Find(next, operation); if (escrowEntry.InventorySaved || escrowEntry.Decision != EscrowDecision.AwaitHost) { throw new InvalidOperationException("Resolved escrow cannot be removed"); } escrowEntry.RemovalStarted = true; }); } public void EnableCharacterMarker(string operation) { Commit(delegate(EscrowState next) { EscrowEntry escrowEntry = Find(next, operation); if (escrowEntry.MarkerVersion != 1) { if (escrowEntry.InventorySaved || escrowEntry.RemovalStarted || escrowEntry.Decision == EscrowDecision.Consume) { throw new InvalidOperationException("Legacy removal requires recovery review"); } escrowEntry.MarkerVersion = 1; } }); } public void RecordHostOutcome(string operation, OfferPhase phase) { if (!Enum.IsDefined(typeof(OfferPhase), phase)) { throw new ArgumentException("Unknown host outcome"); } Commit(delegate(EscrowState next) { EscrowEntry escrowEntry = Find(next, operation); if (phase != OfferPhase.Prepared) { EscrowDecision escrowDecision = ((phase == OfferPhase.Committed) ? EscrowDecision.Consume : EscrowDecision.Return); if (escrowEntry.Decision != EscrowDecision.AwaitHost && escrowEntry.Decision != escrowDecision) { throw new InvalidOperationException("Host outcome conflicts with durable outcome"); } escrowEntry.Decision = escrowDecision; } }); } public void ConfirmInventorySaved(string operation) { Commit(delegate(EscrowState next) { EscrowEntry escrowEntry = Find(next, operation); if (escrowEntry.Decision == EscrowDecision.AwaitHost) { throw new InvalidOperationException("Cannot settle without a host decision"); } escrowEntry.InventorySaved = true; }); } private static EscrowEntry Find(EscrowState value, string operation) { Progression.CheckId(operation); EscrowEntry escrowEntry = value.Entries.Find((EscrowEntry e) => e.Operation == operation); if (escrowEntry == null) { throw new InvalidOperationException("Unknown escrow operation"); } return escrowEntry; } private EscrowState Clone() { return Decode(Encode(state)); } private void Commit(Action<EscrowState> transition) { EscrowState escrowState = Clone(); transition(escrowState); AtomicFile.Write(path, Encode(escrowState)); state = escrowState; } private static byte[] Encode(EscrowState value) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Validate(value); using MemoryStream memoryStream = new MemoryStream(); ((XmlObjectSerializer)new DataContractJsonSerializer(typeof(EscrowState))).WriteObject((Stream)memoryStream, (object)value); return memoryStream.ToArray(); } private static EscrowState Decode(byte[] bytes) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(bytes); EscrowState escrowState = (EscrowState)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(EscrowState))).ReadObject((Stream)memoryStream); if (escrowState != null && escrowState.Schema == 1) { escrowState.Schema = 2; } Validate(escrowState); return escrowState; } private static void Validate(EscrowState value) { if (value == null || value.Schema != 2 || value.Player <= 0 || value.Entries == null || value.Entries.Any((EscrowEntry e) => e == null)) { throw new InvalidDataException("Invalid escrow file"); } if ((from e in value.Entries group e by e.Operation).Any((IGrouping<string, EscrowEntry> g) => g.Count() != 1) || (from e in value.Entries where !e.InventorySaved group e by e.Receipt).Any((IGrouping<string, EscrowEntry> g) => g.Count() != 1)) { throw new InvalidDataException("Duplicate escrow record"); } foreach (EscrowEntry entry in value.Entries) { Progression.CheckId(entry.Operation); if (!Guid.TryParseExact(entry.Receipt, "N", out var _) || string.IsNullOrEmpty(entry.Prefab) || entry.ItemBytes == null || entry.ItemBytes.Length == 0 || entry.ItemBytes.Length > 1048576 || !Enum.IsDefined(typeof(EscrowDecision), entry.Decision) || (entry.InventorySaved && entry.Decision == EscrowDecision.AwaitHost) || entry.MarkerVersion < 0 || entry.MarkerVersion > 1) { throw new InvalidDataException("Invalid escrow entry"); } if (entry.RequestBytes != null) { ValidateRequest(entry.RequestBytes, value.World, entry.Operation, entry.Receipt, entry.Prefab); } } } private static void ValidateRequest(byte[] bytes, long world, string operation, string receipt, string prefab) { OfferingRequest offeringRequest = OfferingRequest.Decode(bytes, world); if (offeringRequest.Action != OfferingAction.Prepare || offeringRequest.Operation != operation || offeringRequest.Receipt != receipt || TrophyIdentity.PrefabName(offeringRequest.Boss, offeringRequest.Tier) != prefab) { throw new InvalidDataException("Escrow request does not match saved item"); } } } public sealed class CombatDrainBarrier { private readonly HashSet<long> peers = new HashSet<long>(); private readonly HashSet<long> ready = new HashSet<long>(); public long Token { get; private set; } public bool Complete { get { if (peers.Count > 0) { return peers.SetEquals(ready); } return false; } } public CombatDrainBarrier(IEnumerable<long> connected) { NewRound(); Refresh(connected); } private void NewRound() { long token = Token; do { Token = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); } while (Token == 0 || Token == token); ready.Clear(); } public void Refresh(IEnumerable<long> connected) { if (connected == null) { throw new ArgumentNullException("connected"); } HashSet<long> hashSet = new HashSet<long>(connected); if (hashSet.Count == 0 || hashSet.Count > 128 || hashSet.Contains(0L)) { throw new ArgumentException("Invalid combat drain peers"); } if (hashSet.Except(peers).Any()) { NewRound(); } peers.Clear(); peers.UnionWith(hashSet); ready.IntersectWith(peers); } public bool ObserveReport(long peer) { if (!peers.Contains(peer)) { return false; } NewRound(); return true; } public bool Acknowledge(long peer, long token) { if (token != Token || !peers.Contains(peer)) { return false; } ready.Add(peer); return true; } public long[] Pending() { return (from p in peers.Except(ready) orderby p select p).ToArray(); } } public sealed class CombatDrainPacket { public const int Size = 32; public long World; public long BossCreator; public long Token; public uint BossId; public byte[] Encode() { Validate(); using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(1); binaryWriter.Write(World); binaryWriter.Write(BossCreator); binaryWriter.Write(BossId); binaryWriter.Write(Token); return memoryStream.ToArray(); } public static CombatDrainPacket Decode(byte[] bytes, long world) { if (bytes == null || bytes.Length != 32) { throw new InvalidDataException("Invalid combat drain packet size"); } using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes)); if (binaryReader.ReadInt32() != 1) { throw new InvalidDataException("Unknown combat drain protocol"); } CombatDrainPacket combatDrainPacket = new CombatDrainPacket(); combatDrainPacket.World = binaryReader.ReadInt64(); combatDrainPacket.BossCreator = binaryReader.ReadInt64(); combatDrainPacket.BossId = binaryReader.ReadUInt32(); combatDrainPacket.Token = binaryReader.ReadInt64(); CombatDrainPacket combatDrainPacket2 = combatDrainPacket; combatDrainPacket2.Validate(); if (combatDrainPacket2.World != world) { throw new InvalidDataException("Wrong combat drain world"); } return combatDrainPacket2; } private void Validate() { if (World == 0 || BossCreator == 0 || BossId == 0 || Token == 0) { throw new ArgumentException("Invalid combat drain identity"); } } } public static class CombatReportSource { public static string For(long reporter, long session) { if (reporter < 0 || session == 0) { throw new ArgumentException("Invalid combat report source"); } return ((reporter == 0) ? "host" : ("player:" + reporter.ToString(CultureInfo.InvariantCulture))) + ":" + session.ToString(CultureInfo.InvariantCulture); } } public sealed class CombatReportJournal : IDisposable { private sealed class State { internal long World; internal long Reporter; internal long Session; internal long Sequence; internal List<BossDamagePacket> Pending = new List<BossDamagePacket>(); internal List<BossDamagePacket> Rejected = new List<BossDamagePacket>(); } public const int MaximumPending = 4096; public const int MaximumRejected = 256; private const int HeaderSize = 44; private const int DigestSize = 32; public const int MaximumBytes = 317772; private readonly string path; private readonly Action<string, byte[]> write; private FileStream lease; private State state; public long World => state.World; public long Reporter => state.Reporter; public long Session => state.Session; public int PendingCount => state.Pending.Count; public int RejectedCount => state.Rejected.Count; public CombatReportJournal(string path, long world, long reporter) : this(path, world, reporter, AtomicFile.Write) { } internal CombatReportJournal(string path, long world, long reporter, Action<string, byte[]> writer) { if (string.IsNullOrWhiteSpace(path) || world == 0 || reporter < 0 || writer == null) { throw new ArgumentException("Invalid combat journal identity"); } this.path = Path.GetFullPath(path); write = writer; Directory.CreateDirectory(Path.GetDirectoryName(this.path)); try { lease = new FileStream(this.path + ".lock", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); if (File.Exists(this.path)) { state = ReadState(this.path); if (state.World != world || state.Reporter != reporter) { throw new InvalidDataException("Combat recovery belongs to another world or reporter"); } return; } long num; do { num = BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0); } while (num == 0); State value = new State { World = world, Reporter = reporter, Session = num }; write(this.path, Encode(value)); state = value; } catch { Dispose(); throw; } } public BossDamagePacket First() { Alive(); if (state.Pending.Count != 0) { return Copy(state.Pending[0], state.World); } return null; } public BossDamagePacket[] Rejected() { Alive(); return state.Rejected.Select((BossDamagePacket p) => Copy(p, state.World)).ToArray(); } public bool HasPending(long creator, uint id) { Alive(); return state.Pending.Any((BossDamagePacket p) => p.BossCreator == creator && p.BossId == id); } public static CombatReportSnapshot Inspect(string path) { State value = ReadState(Path.GetFullPath(path)); CombatReportSnapshot combatReportSnapshot = new CombatReportSnapshot(); combatReportSnapshot.World = value.World; combatReportSnapshot.Reporter = value.Reporter; combatReportSnapshot.Session = value.Session; combatReportSnapshot.LastSequence = value.Sequence; combatReportSnapshot.Pending = value.Pending.Select((BossDamagePacket p) => Copy(p, value.World)).ToArray(); combatReportSnapshot.Rejected = value.Rejected.Select((BossDamagePacket p) => Copy(p, value.World)).ToArray(); return combatReportSnapshot; } private static State ReadState(string path) { using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); if (fileStream.Length < 76 || fileStream.Length > 317772) { throw new InvalidDataException("Combat recovery file exceeds bounds"); } byte[] array = new byte[(int)fileStream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = fileStream.Read(array, i, array.Length - i); if (num == 0) { throw new InvalidDataException("Truncated combat recovery file"); } } if (fileStream.ReadByte() != -1) { throw new InvalidDataException("Combat recovery file changed size during read"); } return Decode(array); } public BossDamagePacket Enqueue(BossDamagePacket observation) { Alive(); if (observation == null || observation.World != this.state.World || observation.Session != 0 || observation.Sequence != 0) { throw new ArgumentException("Observation already has a transport identity or wrong world"); } if (this.state.Pending.Count >= 4096) { throw new InvalidOperationException("Unacknowledged combat queue is full"); } BossDamagePacket bossDamagePacket = new BossDamagePacket(); bossDamagePacket.World = observation.World; bossDamagePacket.Session = this.state.Session; bossDamagePacket.Sequence = checked(this.state.Sequence + 1); bossDamagePacket.BossCreator = observation.BossCreator; bossDamagePacket.BossId = observation.BossId; bossDamagePacket.AttackerCreator = observation.AttackerCreator; bossDamagePacket.AttackerId = observation.AttackerId; bossDamagePacket.Before = observation.Before; bossDamagePacket.After = observation.After; bossDamagePacket.Kind = observation.Kind; bossDamagePacket.RecipientCreator = observation.RecipientCreator; bossDamagePacket.RecipientId = observation.RecipientId; BossDamagePacket packet = bossDamagePacket; packet = Copy(packet, this.state.World); State state = Clone(); state.Sequence = packet.Sequence; state.Pending.Add(packet); Commit(state); return Copy(packet, this.state.World); } public bool Acknowledge(BossDamageAck ack) { Alive(); if (ack == null || ack.World != this.state.World || ack.Session != this.state.Session || this.state.Pending.Count == 0 || ack.Sequence != this.state.Pending[0].Sequence) { return false; } State state = Clone(); if (!ack.Accepted) { if (state.Rejected.Count >= 256) { throw new InvalidOperationException("Combat rejection audit is full; saved observation retained for review"); } state.Rejected.Add(state.Pending[0]); } state.Pending.RemoveAt(0); Commit(state); return true; } private void Alive() { if (lease == null) { throw new InvalidOperationException("Combat journal is closed"); } } private State Clone() { return Decode(Encode(state)); } private void Commit(State next) { byte[] arg = Encode(next); write(path, arg); state = next; } private static BossDamagePacket Copy(BossDamagePacket packet, long world) { return BossDamagePacket.Decode(packet.Encode(), world); } private static byte[] Encode(State value) { Validate(value); using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(1); binaryWriter.Write(value.World); binaryWriter.Write(value.Reporter); binaryWriter.Write(value.Session); binaryWriter.Write(value.Sequence); binaryWriter.Write(value.Pending.Count); binaryWriter.Write(value.Rejected.Count); foreach (BossDamagePacket item in value.Pending.Concat(value.Rejected)) { binaryWriter.Write(item.Encode()); } binaryWriter.Flush(); byte[] buffer = memoryStream.ToArray(); using (SHA256 sHA = SHA256.Create()) { binaryWriter.Write(sHA.ComputeHash(buffer)); } return memoryStream.ToArray(); } private static State Decode(byte[] bytes) { if (bytes == null || bytes.Length < 76 || bytes.Length > 317772) { throw new InvalidDataException("Invalid combat recovery size"); } using (SHA256 sHA = SHA256.Create()) { if (!sHA.ComputeHash(bytes, 0, bytes.Length - 32).SequenceEqual(bytes.Skip(bytes.Length - 32))) { throw new InvalidDataException("Combat recovery checksum mismatch"); } } using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes)); if (binaryReader.ReadInt32() != 1) { throw new InvalidDataException("Unknown combat recovery schema"); } State state = new State(); state.World = binaryReader.ReadInt64(); state.Reporter = binaryReader.ReadInt64(); state.Session = binaryReader.ReadInt64(); state.Sequence = binaryReader.ReadInt64(); State state2 = state; int num = binaryReader.ReadInt32(); int num2 = binaryReader.ReadInt32(); if (num < 0 || num > 4096 || num2 < 0 || num2 > 256 || bytes.Length != 44 + (num + num2) * 73 + 32) { throw new InvalidDataException("Invalid combat recovery counts"); } for (int i = 0; i < num + num2; i++) { BossDamagePacket item = BossDamagePacket.Decode(binaryReader.ReadBytes(73), state2.World); ((i < num) ? state2.Pending : state2.Rejected).Add(item); } Validate(state2); return state2; } private static void Validate(State value) { if (value.World == 0 || value.Reporter < 0 || value.Session == 0 || value.Sequence < 0 || value.Pending.Count > 4096 || value.Rejected.Count > 256) { throw new InvalidDataException("Invalid combat recovery identity"); } HashSet<long> hashSet = new HashSet<long>(); List<BossDamagePacket>[] array = new List<BossDamagePacket>[2] { value.Pending, value.Rejected }; foreach (List<BossDamagePacket> list in array) { long num = 0L; foreach (BossDamagePacket item in list) { item.Encode(); if (item.World != value.World || item.Session != value.Session || item.Sequence <= num || item.Sequence > value.Sequence || !hashSet.Add(item.Sequence)) { throw new InvalidDataException("Changed or unordered combat recovery observation"); } num = item.Sequence; } } if (value.Pending.Count > 0 && value.Rejected.Any((BossDamagePacket p) => p.Sequence >= value.Pending[0].Sequence)) { throw new InvalidDataException("Combat rejection bypassed an earlier pending report"); } } public void Dispose() { if (lease != null) { lease.Dispose(); lease = null; } } } public sealed class CombatReportSnapshot { public long World; public long Reporter; public long Session; public long LastSequence; public BossDamagePacket[] Pending; public BossDamagePacket[] Rejected; } public static class DotDamageAllocation { public static Dictionary<string, double> Allocate(double loss, double total, double[] channels, IDictionary<string, double>[] fractions) { Validate(loss); Validate(total); if (channels == null || fractions == null || channels.Length != fractions.Length) { throw new ArgumentException("Invalid dot channels"); } Dictionary<string, double> dictionary = new Dictionary<string, double>(); double num = 0.0; foreach (double num2 in channels) { Validate(num2); num += num2; } Validate(num); double num3 = Math.Max(total, num); for (int j = 0; j < channels.Length; j++) { IDictionary<string, double> dictionary2 = fractions[j]; if (dictionary2 == null) { continue; } double num4 = 0.0; foreach (KeyValuePair<string, double> item in dictionary2) { Validate(item.Value); if (string.IsNullOrEmpty(item.Key) || item.Value > 1.0) { throw new ArgumentException("Invalid dot source fraction"); } num4 += item.Value; } if (num4 > 1.000000001) { throw new ArgumentException("Dot fractions exceed available damage"); } if (num3 == 0.0 || loss == 0.0 || channels[j] == 0.0) { continue; } double num5 = loss * (channels[j] / num3) / Math.Max(1.0, num4); foreach (KeyValuePair<string, double> item2 in dictionary2) { dictionary.TryGetValue(item2.Key, out var value); dictionary[item2.Key] = value + num5 * item2.Value; } } return dictionary; } private static void Validate(double value) { if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.0) { throw new ArgumentException("Invalid applied dot damage"); } } } public sealed class DotDamagePool { private readonly Dictionary<string, double> remaining = new Dictionary<string, double>(); public double Total { get; private set; } public void Synchronize(double nativeRemaining) { Validate(nativeRemaining); if (nativeRemaining == 0.0) { remaining.Clear(); Total = 0.0; } else if (Total > nativeRemaining) { double num = nativeRemaining / Total; foreach (string item in new List<string>(remaining.Keys)) { remaining[item] *= num; } Total = nativeRemaining; } else if (Total < nativeRemaining) { Add("", nativeRemaining - Total); } } public void RecordAddition(string source, double before, double after) { Validate(before); Validate(after); Synchronize(before); if (after > before) { Add(source ?? "", after - before); } else { Synchronize(after); } } public void RecordReplacement(string source, double amount, double before, double after) { Validate(amount); Validate(before); Validate(after); Synchronize(before); if (amount >= before && after == amount) { remaining.Clear(); Total = 0.0; Add(source ?? "", after); } else { Synchronize(after); } } public Dictionary<string, double> Take(double amount, double nativeRemaining) { Validate(amount); Synchronize(nativeRemaining); Dictionary<string, double> dictionary = new Dictionary<string, double>(); if (Total == 0.0 || amount == 0.0) { return dictionary; } foreach (KeyValuePair<string, double> item in remaining) { if (item.Key.Length != 0) { dictionary.Add(item.Key, item.Value / Total); } } Synchronize(Math.Max(0.0, Total - amount)); return dictionary; } private void Add(string source, double amount) { if (amount != 0.0) { remaining.TryGetValue(source, out var value); remaining[source] = value + amount; Total += amount; } } private static void Validate(double value) { if (double.IsNaN(value) || double.IsInfinity(value) || value < 0.0) { throw new ArgumentException("Invalid native damage pool"); } } } public enum EikthyrPulse : byte { Echo = 1, Storm, Charge, Fork, Call, Sweep } public static class EikthyrMoves { public const float SweepHalfAngle = 75f; public static bool Allows(int tier, EikthyrPulse kind, int moveset = 2) { if (tier < 0 || tier > 5) { return false; } switch (moveset) { case 1: return kind switch { EikthyrPulse.Storm => tier >= 5, EikthyrPulse.Echo => tier >= 3, _ => false, }; default: return false; case 2: case 3: switch (kind) { case EikthyrPulse.Charge: if (moveset == 2) { return tier >= 1; } return false; case EikthyrPulse.Sweep: if (moveset == 3) { return tier >= 1; } return false; case EikthyrPulse.Fork: return tier >= 2; case EikthyrPulse.Echo: return tier >= 3; case EikthyrPulse.Storm: return tier >= 4; case EikthyrPulse.Call: return tier == 5; default: return false; } } } public static float Delay(EikthyrPulse kind) { switch (kind) { default: return 1.8f; case EikthyrPulse.Echo: return 1.4f; case EikthyrPulse.Fork: return 1.2f; case EikthyrPulse.Charge: case EikthyrPulse.Sweep: return 1.3f; } } public static float Radius(EikthyrPulse kind) { return kind switch { EikthyrPulse.Call => 1.5f, EikthyrPulse.Echo => 9f, EikthyrPulse.Fork => 0.65f, EikthyrPulse.Charge => 1.8f, EikthyrPulse.Sweep => 6f, _ => 2.5f, }; } public static float Cooldown(EikthyrPulse kind) { switch (kind) { default: return 14f; case EikthyrPulse.Call: return 40f; case EikthyrPulse.Echo: return 8f; case EikthyrPulse.Fork: return 12f; case EikthyrPulse.Charge: case EikthyrPulse.Sweep: return 16f; } } public static float BaseDamage(EikthyrPulse kind) { switch (kind) { default: return 12f; case EikthyrPulse.Call: return 0f; case EikthyrPulse.Echo: return 9.75f; case EikthyrPulse.Fork: return 7.5f; case EikthyrPulse.Charge: case EikthyrPulse.Sweep: return 12f; } } public static int MaximumPositions(EikthyrPulse kind) { switch (kind) { default: return 3; case EikthyrPulse.Charge: case EikthyrPulse.Call: case EikthyrPulse.Sweep: return 2; case EikthyrPulse.Fork: return 4; case EikthyrPulse.Echo: return 1; } } public static float LightningMultiplier(int tier) { if (tier < 2 || tier > 5) { return 1f; } return 1.15f; } public static string Description(int tier, int moveset = 2) { if (moveset == 2 || moveset == 3) { return ((tier < 1) ? "Native attacks" : ((moveset == 3) ? "Antler sweep" : "Antler rush")) + ((tier >= 2) ? "; forked lightning" : "") + ((tier >= 3) ? "; echo stomp" : "") + ((tier >= 4) ? "; storm marks" : "") + ((tier >= 5) ? "; call of the forest" : "") + "."; } return "Lightning attacks: +15% damage" + ((tier >= 3) ? "; echo stomp (1.4s warning)" : "") + ((tier >= 5) ? "; storm marks (1.8s warning)" : "") + "."; } public static bool Contains(float dx, float dy, float dz, EikthyrPulse kind) { if (Finite(dx) && Finite(dy) && Finite(dz) && Math.Abs(dy) <= 3f) { return dx * dx + dz * dz <= Radius(kind) * Radius(kind); } return false; } public static bool ContainsSegment(PulsePosition point, PulsePosition start, PulsePosition end, float radius) { if (!Valid(point) || !Valid(start) || !Valid(end) || !Finite(radius) || radius < 0f) { return false; } float num = end.X - start.X; float num2 = end.Z - start.Z; float num3 = num * num + num2 * num2; float num4 = ((num3 < 0.0001f) ? 0f : Math.Max(0f, Math.Min(1f, ((point.X - start.X) * num + (point.Z - start.Z) * num2) / num3))); float num5 = point.X - start.X - num4 * num; float num6 = point.Z - start.Z - num4 * num2; if (Math.Abs(point.Y - start.Y - num4 * (end.Y - start.Y)) <= 3f) { return num5 * num5 + num6 * num6 <= radius * radius; } return false; } internal static bool Valid(PulsePosition p) { if (Finite(p.X) && Finite(p.Y)) { return Finite(p.Z); } return false; } public static bool ContainsSweep(PulsePosition point, PulsePosition origin, PulsePosition facing) { if (!Valid(point) || !Valid(origin) || !Valid(facing) || Math.Abs(point.Y - origin.Y) > 3f) { return false; } float num = point.X - origin.X; float num2 = point.Z - origin.Z; float num3 = facing.X - origin.X; float num4 = facing.Z - origin.Z; float num5 = num * num + num2 * num2; float num6 = num3 * num3 + num4 * num4; if (num5 > Radius(EikthyrPulse.Sweep) * Radius(EikthyrPulse.Sweep) || num6 < 1f) { return false; } if (num5 < 1E-06f) { return true; } return (double)(num * num3 + num2 * num4) / Math.Sqrt(num5 * num6) >= Math.Cos(Math.PI * 5.0 / 12.0) - 1E-06; } internal static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public struct PulsePosition { public float X; public float Y; public float Z; } public sealed class EikthyrPulsePacket { public int Sequence; public long DueTicks; public EikthyrPulse Kind; public PulsePosition[] Positions; public void Validate() { if (Sequence > 0 && DueTicks > 0) { long dueTicks = DueTicks; DateTime maxValue = DateTime.MaxValue; if (dueTicks <= maxValue.Ticks && Enum.IsDefined(typeof(EikthyrPulse), Kind) && Positions != null && Positions.Length >= 1 && Positions.Length <= EikthyrMoves.MaximumPositions(Kind) && ((Kind != EikthyrPulse.Charge && Kind != EikthyrPulse.Call && Kind != EikthyrPulse.Fork && Kind != EikthyrPulse.Sweep) || Positions.Length == EikthyrMoves.MaximumPositions(Kind))) { PulsePosition[] positions = Positions; for (int i = 0; i < positions.Length; i++) { PulsePosition pulsePosition = positions[i]; if (!EikthyrMoves.Finite(pulsePosition.X) || !EikthyrMoves.Finite(pulsePosition.Y) || !EikthyrMoves.Finite(pulsePosition.Z)) { throw new InvalidDataException("Invalid Eikthyr pulse position"); } } if (Kind != EikthyrPulse.Charge && Kind != EikthyrPulse.Fork && Kind != EikthyrPulse.Sweep) { return; } for (int j = 1; j < Positions.Length; j++) { float num = Positions[j].X - Positions[0].X; float num2 = Positions[j].Z - Positions[0].Z; if (num * num + num2 * num2 < 1f || num * num + num2 * num2 > 225f || Math.Abs(Positions[j].Y - Positions[0].Y) > 5f) { throw new InvalidDataException("Invalid Eikthyr corridor"); } } return; } } throw new InvalidDataException("Invalid Eikthyr pulse"); } public byte[] Encode() { Validate(); using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(3); binaryWriter.Write(Sequence); binaryWriter.Write(DueTicks); binaryWriter.Write((byte)Kind); binaryWriter.Write((byte)Positions.Length); PulsePosition[] positions = Positions; for (int i = 0; i < positions.Length; i++) { PulsePosition pulsePosition = positions[i]; binaryWriter.Write(pulsePosition.X); binaryWriter.Write(pulsePosition.Y); binaryWriter.Write(pulsePosition.Z); } return memoryStream.ToArray(); } public static EikthyrPulsePacket Decode(byte[] bytes) { if (bytes == null || bytes.Length < 30 || bytes.Length > 66) { throw new InvalidDataException("Invalid pulse size"); } using MemoryStream input = new MemoryStream(bytes); using BinaryReader binaryReader = new BinaryReader(input); int num = binaryReader.ReadInt32(); if (num < 1 || num > 3) { throw new InvalidDataException("Unknown pulse protocol"); } EikthyrPulsePacket eikthyrPulsePacket = new EikthyrPulsePacket(); eikthyrPulsePacket.Sequence = binaryReader.ReadInt32(); eikthyrPulsePacket.DueTicks = binaryReader.ReadInt64(); eikthyrPulsePacket.Kind = (EikthyrPulse)binaryReader.ReadByte(); EikthyrPulsePacket eikthyrPulsePacket2 = eikthyrPulsePacket; int num2 = binaryReader.ReadByte(); if (num2 < 1 || num2 > ((num == 1) ? 3 : 4) || bytes.Length != 18 + 12 * num2 || (num == 1 && eikthyrPulsePacket2.Kind != EikthyrPulse.Echo && eikthyrPulsePacket2.Kind != EikthyrPulse.Storm) || (num < 3 && eikthyrPulsePacket2.Kind == EikthyrPulse.Sweep)) { throw new InvalidDataException("Invalid pulse length"); } eikthyrPulsePacket2.Positions = new PulsePosition[num2]; for (int i = 0; i < num2; i++) { ref PulsePosition reference = ref eikthyrPulsePacket2.Positions[i]; reference = new PulsePosition { X = binaryReader.ReadSingle(), Y = binaryReader.ReadSingle(), Z = binaryReader.ReadSingle() }; } eikthyrPulsePacket2.Validate(); return eikthyrPulsePacket2; } } public static class EscrowScope { private static readonly HashSet<string> trophies = CreateTrophies(); private static HashSet<string> CreateTrophies() { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); for (int i = 0; i < 7; i++) { for (int j = 0; j < 6; j++) { hashSet.Add(TrophyIdentity.PrefabName((Boss)i, j)); } } return hashSet; } public static bool Trophy(string prefab, string hold) { if (prefab != null && trophies.Contains(prefab)) { return !string.IsNullOrEmpty(hold); } return false; } public static string ProtectionLock(string standLock, string prefab, string trophyHold) { if (string.IsNullOrEmpty(standLock)) { if (!Trophy(prefab, trophyHold)) { return ""; } return trophyHold; } return standLock; } } [DataContract] public sealed class MigrationPlan { [DataMember(IsRequired = true)] public int Schema = 1; [DataMember(IsRequired = true)] public string Id; [DataMember(IsRequired = true)] public long WorldId; [DataMember(IsRequired = true)] public string Reason; [DataMember(IsRequired = true)] public int[] Altars = PlayerProgress.Fresh(); [DataMember(IsRequired = true)] public List<PlayerProgress> Players = new List<PlayerProgress>(); public void Validate() { if (Schema != 1 || WorldId == 0 || !Guid.TryParseExact(Id, "N", out var result) || result == Guid.Empty || result.ToString("N") != Id || string.IsNullOrWhiteSpace(Reason) || Reason.Length > 512 || Reason.Any(char.IsControl)) { throw new InvalidDataException("Invalid migration identity or reason"); } if (Altars == null || Altars.Length != 7 || Altars.Any((int t) => t < -1 || t > 5) || Players == null || Players.Count > 128 || Players.Any((PlayerProgress p) => p == null) || (from p in Players group p by p.PlayerId).Any((IGrouping<long, PlayerProgress> g) => g.Count() != 1)) { throw new InvalidDataException("Invalid migration altar/player list"); } foreach (PlayerProgress player in Players) { if (player.PlayerId <= 0 || player.Eligible == null || player.Claimed == null || player.Eligible.Length != 7 || player.Claimed.Length != 7) { throw new InvalidDataException("Invalid migration character"); } for (int num = 0; num < 7; num++) { if (player.Eligible[num] < -1 || player.Eligible[num] > 5 || player.Claimed[num] < -1 || player.Claimed[num] > player.Eligible[num] || player.Claimed[num] > Altars[num]) { throw new InvalidDataException("Migration claims must fit explicit victories and altars"); } } } if (Altars.All((int t) => t == -1) && Players.All((PlayerProgress p) => p.Eligible.All((int t) => t == -1))) { throw new InvalidDataException("Migration plan contains no grants"); } } public MigrationPlan Copy() { Validate(); MigrationPlan migrationPlan = new MigrationPlan(); migrationPlan.Schema = Schema; migrationPlan.Id = Id; migrationPlan.WorldId = WorldId; migrationPlan.Reason = Reason; migrationPlan.Altars = (int[])Altars.Clone(); migrationPlan.Players = (from p in Players orderby p.PlayerId select new PlayerProgress { PlayerId = p.PlayerId, Eligible = (int[])p.Eligible.Clone(), Claimed = (int[])p.Claimed.Clone() }).ToList(); return migrationPlan; } public byte[] Encode() { //IL_0017: Unknown result type (might be due to invalid IL or missing references) MigrationPlan migrationPlan = Copy(); using MemoryStream memoryStream = new MemoryStream(); ((XmlObjectSerializer)new DataContractJsonSerializer(typeof(MigrationPlan))).WriteObject((Stream)memoryStream, (object)migrationPlan); return memoryStream.ToArray(); } public string Fingerprint() { return Hash(Encode()); } internal static string Hash(byte[] bytes) { using SHA256 sHA = SHA256.Create(); return BitConverter.ToString(sHA.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant(); } internal static bool IsHash(string hash) { if (hash != null && hash.Length == 64) { return hash.All((char c) => (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')); } return false; } public static MigrationPlan Decode(byte[] bytes, long expectedWorld) { //IL_0114: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) if (bytes == null || bytes.Length == 0 || bytes.Length > 262144) { throw new InvalidDataException("Migration plan file exceeds bounds"); } if (bytes.Length >= 3 && bytes[0] == 239 && bytes[1] == 187 && bytes[2] == 191) { bytes = bytes.Skip(3).ToArray(); } StrictJson.Validate(bytes); try { XmlDictionaryReaderQuotas val = new XmlDictionaryReaderQuotas(); val.MaxDepth = 16; val.MaxArrayLength = 2048; val.MaxStringContentLength = 4096; val.MaxBytesPerRead = 4096; val.MaxNameTableCharCount = 4096; XmlDictionaryReaderQuotas val2 = val; XmlDictionaryReader val3 = JsonReaderWriterFactory.CreateJsonReader(bytes, val2); try { MigrationPlan migrationPlan = (MigrationPlan)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(MigrationPlan))).ReadObject(val3); if (((XmlReader)val3).Read()) { throw new InvalidDataException("Trailing migration plan content"); } if (migrationPlan == null) { throw new InvalidDataException("Missing migration plan"); } migrationPlan.Validate(); if (migrationPlan.WorldId != expectedWorld) { throw new InvalidDataException("Migration plan belongs to another world"); } return migrationPlan.Copy(); } finally { ((IDisposable)val3)?.Dispose(); } } catch (SerializationException innerException) { throw new InvalidDataException("Invalid migration plan JSON", innerException); } catch (XmlException ex) { XmlException innerException2 = ex; throw new InvalidDataException("Invalid migration plan JSON", (Exception?)(object)innerException2); } } } [DataContract] public sealed class MigrationRecord { [DataMember] public MigrationPlan Plan; [DataMember] public string Fingerprint; [DataMember] public string AppliedUtc; [DataMember] public string BeforeHash; public void Validate(long world) { if (Plan == null) { throw new InvalidDataException("Missing migration receipt plan"); } Plan.Validate(); if (Plan.WorldId != world || Fingerprint != Plan.Fingerprint() || !MigrationPlan.IsHash(BeforeHash) || !DateTime.TryParseExact(AppliedUtc, "yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var _)) { throw new InvalidDataException("Invalid migration receipt"); } } } public sealed class MigrationPlayerPreview { public long PlayerId; public int[] BeforeEligible; public int[] AfterEligible; public int[] BeforeClaimed; public int[] AfterClaimed; } public sealed class MigrationPreview { public string Id; public string Fingerprint; public bool AlreadyApplied; public int[] BeforeAltars; public int[] AfterAltars; public MigrationPlayerPreview[] Players; } public static class ExistingWorldMigration { public static MigrationPreview Preview(WorldProgress state, MigrationPlan plan) { MigrationPlan migrationPlan = plan.Copy(); WorldProgress after = WorldStore.Decode(WorldStore.Encode(state), state.WorldId); bool flag = Apply(after, migrationPlan); MigrationPreview migrationPreview = new MigrationPreview(); migrationPreview.Id = migrationPlan.Id; migrationPreview.Fingerprint = migrationPlan.Fingerprint(); migrationPreview.AlreadyApplied = !flag; migrationPreview.BeforeAltars = (int[])state.Altars.Clone(); migrationPreview.AfterAltars = (int[])after.Altars.Clone(); migrationPreview.Players = migrationPlan.Players.Select(delegate(PlayerProgress p) { PlayerProgress playerProgress = state.Players.Find((PlayerProgress q) => q.PlayerId == p.PlayerId); PlayerProgress playerProgress2 = after.Players.Single((PlayerProgress q) => q.PlayerId == p.PlayerId); return new MigrationPlayerPreview { PlayerId = p.PlayerId, BeforeEligible = ((playerProgress == null) ? PlayerProgress.Fresh() : ((int[])playerProgress.Eligible.Clone())), AfterEligible = (int[])playerProgress2.Eligible.Clone(), BeforeClaimed = ((playerProgress == null) ? PlayerProgress.Fresh() : ((int[])playerProgress.Claimed.Clone())), AfterClaimed = (int[])playerProgress2.Claimed.Clone() }; }).ToArray(); return migrationPreview; } public static bool Apply(WorldProgress state, MigrationPlan plan, string beforeHash = null) { WorldStore.Validate(state); MigrationPlan accepted = plan.Copy(); if (accepted.WorldId != state.WorldId) { throw new InvalidOperationException("Migration world changed"); } string text = accepted.Fingerprint(); MigrationRecord migrationRecord = state.Migrations.Find((MigrationRecord m) => m.Plan.Id == accepted.Id); if (migrationRecord != null) { if (migrationRecord.Fingerprint != text) { throw new InvalidOperationException("Applied migration ID cannot be reused for changed grants"); } return false; } if (state.Migrations.Count >= 1024) { throw new InvalidOperationException("Migration receipt limit reached"); } if (beforeHash == null) { beforeHash = MigrationPlan.Hash(WorldStore.Encode(state)); } if (!MigrationPlan.IsHash(beforeHash)) { throw new InvalidDataException("Invalid migration baseline hash"); } for (int num = 0; num < 7; num++) { state.Altars[num] = Math.Max(state.Altars[num], accepted.Altars[num]); } foreach (PlayerProgress player in accepted.Players) { PlayerProgress playerProgress = Progression.Player(state, player.PlayerId); for (int num2 = 0; num2 < 7; num2++) { playerProgress.Eligible[num2] = Math.Max(playerProgress.Eligible[num2], player.Eligible[num2]); playerProgress.Claimed[num2] = Math.Max(playerProgress.Claimed[num2], player.Claimed[num2]); } } state.Migrations.Add(new MigrationRecord { Plan = accepted, Fingerprint = text, BeforeHash = beforeHash, AppliedUtc = DateTime.UtcNow.ToString("yyyy-MM-dd'T'HH:mm:ss'Z'", CultureInfo.InvariantCulture) }); return true; } } [DataContract] public sealed class FightRewards { [DataMember] public float Materials; [DataMember] public int EpicRolls; [DataMember] public int EpicLevel; public void Validate() { if (float.IsNaN(Materials) || float.IsInfinity(Materials) || Materials < 0f || Materials > 10f || EpicRolls < 0 || EpicRolls > 10 || EpicLevel < 1 || EpicLevel > 6) { throw new ArgumentException("Invalid challenge reward snapshot"); } } public FightRewards Copy() { Validate(); FightRewards fightRewards = new FightRewards(); fightRewards.Materials = Materials; fightRewards.EpicRolls = EpicRolls; fightRewards.EpicLevel = EpicLevel; return fightRewards; } public static bool Same(FightRewards a, FightRewards b) { if (!object.ReferenceEquals(a, b)) { if (a != null && b != null && a.Materials == b.Materials && a.EpicRolls == b.EpicRolls) { return a.EpicLevel == b.EpicLevel; } return false; } return true; } public int MaterialCount(int nativeCount) { Validate(); if (nativeCount < 0 || nativeCount > 100000) { throw new ArgumentException("Invalid native material count"); } return checked((int)Math.Floor((double)nativeCount * (double)Materials + 0.5)); } public string Encode() { Validate(); return "1|" + Materials.ToString("R", CultureInfo.InvariantCulture) + "|" + EpicRolls.ToString(CultureInfo.InvariantCulture) + "|" + EpicLevel.ToString(CultureInfo.InvariantCulture); } public static FightRewards Decode(string text) { if (text == null || text.Length > 80) { throw new ArgumentException("Invalid reward marker"); } string[] array = text.Split('|'); if (array.Length != 4 || array[0] != "1" || !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[2], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || !int.TryParse(array[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result3)) { throw new ArgumentException("Invalid reward marker"); } FightRewards fightRewards = new FightRewards(); fightRewards.Materials = result; fightRewards.EpicRolls = result2; fightRewards.EpicLevel = result3; FightRewards fightRewards2 = fightRewards; fightRewards2.Validate(); if (fightRewards2.Encode() != text) { throw new ArgumentException("Noncanonical reward marker"); } return fightRewards2; } } public sealed class ChallengeRewards { private readonly FightRewards[][] values; public ChallengeRewards(double[][] materials, double[][] rolls, double[][] levels) { if (materials == null || rolls == null || levels == null || materials.Length != 7 || rolls.Length != 7 || levels.Length != 7) { throw new ArgumentException("Every boss needs a reward table"); } values = new FightRewards[7][]; for (int i = 0; i < 7; i++) { if (materials[i] == null || rolls[i] == null || levels[i] == null || materials[i].Length != 6 || rolls[i].Length != 6 || levels[i].Length != 6) { throw new ArgumentException("Every reward table needs six tiers"); } values[i] = new FightRewards[6]; for (int j = 0; j < 6; j++) { double num = materials[i][j]; double num2 = rolls[i][j]; double num3 = levels[i][j]; if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.0 || num > 10.0 || double.IsNaN(num2) || num2 < 0.0 || num2 > 10.0 || num2 != Math.Truncate(num2) || double.IsNaN(num3) || num3 < 1.0 || num3 > 6.0 || num3 != Math.Truncate(num3)) { throw new ArgumentException("Invalid reward tier value"); } values[i][j] = new FightRewards { Materials = (float)num, EpicRolls = (int)num2, EpicLevel = (int)num3 }; values[i][j].Validate(); } } } public FightRewards For(Boss boss, int tier) { Progression.CheckBoss(boss); Progression.CheckTier(tier); return values[(int)boss][tier].Copy(); } public static ChallengeRewards Defaults() { return new ChallengeRewards((from i in Enumerable.Range(0, 7) select new double[6] { 1.0, 1.0, 1.25, 1.5, 1.75, 2.0 }).ToArray(), (from i in Enumerable.Range(0, 7) select new double[6] { 1.0, 1.0, 1.0, 2.0, 2.0, 3.0 }).ToArray(), (from i in Enumerable.Range(0, 7) select new double[6] { 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 }).ToArray()); } } [DataContract] public sealed class FightScaling { [DataMember] public float Health; [DataMember] public float Damage; [DataMember(EmitDefaultValue = false)] public int Moveset; [DataMember(EmitDefaultValue = false)] public ProgressionScalingSnapshot Progression; public void Validate() { float[] array = new float[2] { Health, Damage }; foreach (float num in array) { if (float.IsNaN(num) || float.IsInfinity(num) || num < 0.01f || num > 1000f) { throw new ArgumentException("Invalid saved challenge scaling"); } } if (Progression != null) { Progression.Validate(); } if (Moveset < 0 || Moveset > 4) { throw new ArgumentException("Unknown saved challenge moveset"); } } public FightScaling Copy() { Validate(); FightScaling fightScaling = new FightScaling(); fightScaling.Health = Health; fightScaling.Damage = Damage; fightScaling.Moveset = Moveset; fightScaling.Progression = ((Progression == null) ? null : Progression.Copy()); return fightScaling; } public void ValidateFor(Boss boss, int tier) { Validate(); ForsakenAscension.Core.Progression.CheckBoss(boss); ForsakenAscension.Core.Progression.CheckTier(tier); if (Progression != null && (tier == 0 || Progression.Stage < (int)boss)) { throw new ArgumentException("Progression snapshot is not valid for this challenge"); } if (Moveset != 0 && (tier < ((Moveset != 1) ? 1 : 2) || ((Moveset <= 3) ? ((byte)boss != 0) : (boss == Boss.Eikthyr)))) { throw new ArgumentException("Moveset is not valid for this challenge"); } } public bool Matches(float health, float damage) { if (Health == health) { return Damage == damage; } return false; } public static bool Same(FightScaling a, FightScaling b) { if (a != null) { if (b != null && a.Moveset == b.Moveset && a.Matches(b.Health, b.Damage)) { return ProgressionScalingSnapshot.Same(a.Progression, b.Progression); } return false; } return b == null; } } public sealed class GameplaySettings { private readonly List<Benefit> benefits; public bool Simultaneous { get; private set; } public GameplaySettings(bool simultaneous, IEnumerable<Benefit> source) { if (source == null) { throw new ArgumentNullException("source"); } benefits = new List<Benefit>(); List<Benefit> list = PassiveCatalog.Defaults(); foreach (Benefit b in source) { if (b == null || b.Values == null || b.Values.Length != 6 || !list.Any((Benefit e) => e.Key == b.Key && e.Boss == b.Boss) || benefits.Any((Benefit e) => e.Key == b.Key)) { throw new ArgumentException("Unknown, duplicate or incomplete passive setting"); } double[] array = (double[])b.Values.Clone(); double[] array2 = array; foreach (double value in array2) { Dictionary<string, double> dictionary = new Dictionary<string, double>(); dictionary.Add(b.Key, value); new PassiveSnapshot(dictionary); } benefits.Add(new Benefit(b.Boss, b.Key, array)); } if (benefits.Count != list.Count) { throw new ArgumentException("Settings must contain every passive"); } benefits = list.Select((Benefit e) => benefits.Single((Benefit benefit) => benefit.Key == e.Key)).ToList(); Simultaneous = simultaneous; } public static GameplaySettings Defaults() { return new GameplaySettings(simultaneous: true, PassiveCatalog.Defaults()); } public List<Benefit> Export() { return benefits.Select((Benefit b) => new Benefit(b.Boss, b.Key, (double[])b.Values.Clone())).ToList(); } public PassiveSnapshot Evaluate(PlayerView player, Boss selected) { ProgressPacket.ValidateView(player); Progression.CheckBoss(selected); Dictionary<string, double> dictionary = new Dictionary<string, double>(); foreach (Benefit benefit in benefits) { int num = player.Effective[(int)benefit.Boss]; if (num >= 0 && (Simultaneous || benefit.Boss == selected)) { dictionary.Add(benefit.Key, benefit.Values[num]); } } return new PassiveSnapshot(dictionary); } public static double[] ParseTiers(string text) { if (text == null || text.Length > 256) { throw new ArgumentException("Invalid tier list"); } string[] array = text.Split(','); if (array.Length != 6) { throw new ArgumentException("Expected six comma-separated values, normal through five stars"); } double[] array2 = new double[6]; for (int i = 0; i < 6; i++) { if (!double.TryParse(array[i].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out array2[i])) { throw new ArgumentException("Invalid tier number"); } } return array2; } } public sealed class InputBlockLease { private readonly Action<bool> change; private bool held; public InputBlockLease(Action<bool> change) { if (change == null) { throw new ArgumentNullException("change"); } this.change = change; } public void Set(bool block) { if (held != block) { change(block); held = block; } } } [DataContract] public sealed class MigrationBackup { [DataMember] public int Schema = 1; [DataMember] public MigrationPlan Plan; [DataMember] public WorldProgress Before; [DataMember] public string BeforeHash; private void Validate() { if (Schema != 1 || Plan == null || Before == null) { throw new InvalidDataException("Invalid migration baseline backup"); } Plan.Validate(); WorldStore.Validate(Before); if (Plan.WorldId != Before.WorldId || Before.Migrations.Any((MigrationRecord m) => m.Plan.Id == Plan.Id) || BeforeHash != MigrationPlan.Hash(WorldStore.Encode(Before))) { throw new InvalidDataException("Migration baseline backup identity or contents changed"); } } public byte[] Encode() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Validate(); using MemoryStream memoryStream = new MemoryStream(); ((XmlObjectSerializer)new DataContractJsonSerializer(typeof(MigrationBackup))).WriteObject((Stream)memoryStream, (object)this); return memoryStream.ToArray(); } public static MigrationBackup Decode(byte[] bytes, MigrationPlan expected) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) try { MigrationBackup migrationBackup; using (MemoryStream memoryStream = new MemoryStream(bytes)) { migrationBackup = (MigrationBackup)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(MigrationBackup))).ReadObject((Stream)memoryStream); } if (migrationBackup == null) { throw new InvalidDataException("Missing migration baseline backup"); } migrationBackup.Validate(); if (migrationBackup.Plan.Fingerprint() != expected.Fingerprint() || !bytes.SequenceEqual(migrationBackup.Encode())) { throw new InvalidDataException("Migration backup policy or canonical bytes changed"); } return migrationBackup; } catch (SerializationException innerException) { throw new InvalidDataException("Invalid migration baseline backup JSON", innerException); } } internal static string PathFor(string savePath, MigrationPlan plan) { plan.Validate(); return savePath + ".migration-" + plan.Id + "-before.json"; } internal static string Ensure(string savePath, WorldProgress state, MigrationPlan plan) { string text = PathFor(savePath, plan); if (File.Exists(text)) { return Decode(File.ReadAllBytes(text), plan).BeforeHash; } WorldProgress worldProgress = WorldStore.Decode(WorldStore.Encode(state), state.WorldId); MigrationBackup migrationBackup = new MigrationBackup(); migrationBackup.Plan = plan.Copy(); migrationBackup.Before = worldProgress; migrationBackup.BeforeHash = MigrationPlan.Hash(WorldStore.Encode(worldProgress)); MigrationBackup migrationBackup2 = migrationBackup; byte[] array = migrationBackup2.Encode(); string text2 = text + "." + Guid.NewGuid().ToString("N") + ".tmp"; Directory.CreateDirectory(Path.GetDirectoryName(Path.GetFullPath(text))); try { using (FileStream fileStream = new FileStream(text2, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fileStream.Write(array, 0, array.Length); fileStream.Flush(flushToDisk: true); } try { File.Move(text2, text); } catch (IOException) { if (!File.Exists(text)) { throw; } } return Decode(File.ReadAllBytes(text), plan).BeforeHash; } finally { if (File.Exists(text2)) { File.Delete(text2); } } } } [DataContract] public sealed class NaturalQueenSource { public const string RoomName = "dvergr_new_bossroom_ENTRANCE02"; [DataMember] public string DungeonToken; [DataMember] public float BirthX; [DataMember] public float BirthY; [DataMember] public float BirthZ; public void Validate() { if (DungeonToken == null || DungeonToken.Length != 32 || !Guid.TryParseExact(DungeonToken, "N", out var result) || result == Guid.Empty || result.ToString("N") != DungeonToken) { throw new ArgumentException("Invalid native Queen dungeon token"); } if (!Coordinate(BirthX) || !Coordinate(BirthY) || !Coordinate(BirthZ)) { throw new ArgumentException("Invalid native Queen birth position"); } } internal static bool Coordinate(float value) { if (!float.IsNaN(value) && !float.IsInfinity(value)) { return Math.Abs(value) <= 1000000f; } return false; } public NaturalQueenSource Copy() { Validate(); NaturalQueenSource naturalQueenSource = new NaturalQueenSource(); naturalQueenSource.DungeonToken = DungeonToken; naturalQueenSource.BirthX = BirthX; naturalQueenSource.BirthY = BirthY; naturalQueenSource.BirthZ = BirthZ; return naturalQueenSource; } public bool SameAs(NaturalQueenSource other) { if (other != null && DungeonToken == other.DungeonToken && BirthX == other.BirthX && BirthY == other.BirthY) { return BirthZ == other.BirthZ; } return false; } } public sealed class NativeRoomPlacement { public int Hash; public float X; public float Y; public float Z; public float RX; public float RY; public float RZ; } public static class NativeDungeonRooms { public static NativeRoomPlacement[] Decode(byte[] data) { if (data == null || data.Length < 4 || data.Length > 114692) { throw new ArgumentException("Invalid native dungeon room data"); } using BinaryReader binaryReader = new BinaryReader(new MemoryStream(data, writable: false)); int num = binaryReader.ReadInt32(); if (num < 1 || num > 4096 || data.Length != 4 + num * 28) { throw new ArgumentException("Invalid native dungeon room count"); } NativeRoomPlacement[] array = new NativeRoomPlacement[num]; for (int i = 0; i < num; i++) { NativeRoomPlacement nativeRoomPlacement = new NativeRoomPlacement(); nativeRoomPlacement.Hash = binaryReader.ReadInt32(); nativeRoomPlacement.X = binaryReader.ReadSingle(); nativeRoomPlacement.Y = binaryReader.ReadSingle(); nativeRoomPlacement.Z = binaryReader.ReadSingle(); nativeRoomPlacement.RX = binaryReader.ReadSingle(); nativeRoomPlacement.RY = binaryReader.ReadSingle(); nativeRoomPlacement.RZ = binaryReader.ReadSingle(); NativeRoomPlacement nativeRoomPlacement2 = nativeRoomPlacement; if (!NaturalQueenSource.Coordinate(nativeRoomPlacement2.X) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.Y) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.Z) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.RX) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.RY) || !NaturalQueenSource.Coordinate(nativeRoomPlacement2.RZ)) { throw new ArgumentException("Invalid native dungeon placement"); } array[i] = nativeRoomPlacement2; } return array; } } public static class NaturalQueenEncounter { public static string FightId(NaturalQueenSource source) { source.Validate(); return "native-queen-" + source.DungeonToken; } public static FightRecord Register(WorldProgress world, NaturalQueenSource source, long creator, uint number, double health, ParticipationRules rules, double now, bool previousSpawnAbsent) { if (source == null || rules == null) { throw new ArgumentNullException("Native Queen evidence"); } source.Validate(); rules.Validate(); if (creator == 0 || number == 0 || double.IsNaN(health) || double.IsInfinity(health) || health <= 0.0 || double.IsNaN(now) || double.IsInfinity(now) || now < 0.0) { throw new ArgumentException("Invalid native Queen encounter"); } string id = FightId(source); FightRecord fightRecord = world.Fights.SingleOrDefault((FightRecord f) => f.Id == id); if (fightRecord != null) { if (fightRecord.Boss != Boss.Queen || fightRecord.Tier != 0 || fightRecord.NaturalQueen == null || !fightRecord.NaturalQueen.SameAs(source) || fightRecord.Scaling != null || fightRecord.Rewards != null) { throw new InvalidOperationException("Native Queen evidence conflicts with the saved encounter"); } if (fightRecord.SpawnCreator == creator && fightRecord.SpawnId == number) { return fightRecord; } if (!previousSpawnAbsent || world.Fights.Any((FightRecord f) => f.Id != id && f.SpawnCreator == creator && f.SpawnId == number)) { throw new InvalidOperationException("Another boss still occupies this encounter"); } fightRecord.SpawnCreator = creator; fightRecord.SpawnId = number; return fightRecord; } if (world.Fights.Any((FightRecord f) => f.SpawnCreator == creator && f.SpawnId == number)) { throw new InvalidOperationException("Queen already belongs to another fight"); } ParticipationLedger participationLedger = new ParticipationLedger(health, rules, now); FightRecord fightRecord2 = Progression.BeginFight(world, id, Boss.Queen, 0); fightRecord2.NaturalQueen = source.Copy(); fightRecord2.ReservedRules = rules.Copy(); fightRecord2.Participation = participationLedger.Export(); Progression.BindSpawn(world, id, creator, number); return fightRecord2; } } public enum OfferPhase { Prepared, Committed, Cancelled } [DataContract] public sealed class OfferTransaction { [DataMember] public string OperationId; [DataMember] public string TrophyId; [DataMember] public long PlayerId; [DataMember] public OfferPhase Phase; } public static class OfferingJournal { public static OfferPhase Prepare(WorldProgress state, long player, TrophyIdentity trophy, string prefab, string operation) { Progression.CheckId(operation); if (trophy == null) { throw new ArgumentNullException("trophy"); } OfferTransaction previous = state.Transactions.Find((OfferTransaction t) => t.OperationId == operation); if (previous != null) { VerifyActor(previous, player); TrophyRecord record = state.Trophies.Find((TrophyRecord t) => t.Id == previous.TrophyId); if (trophy.Receipt != previous.TrophyId || !trophy.Matches(state.WorldId, record, prefab)) { throw new InvalidOperationException("Offering request changed during replay"); } return previous.Phase; } if (state.Offerings.Any((OfferingRecord o) => o.OperationId == operation)) { throw new InvalidOperationException("Operation already used by an earlier offering"); } TrophyRecord record2 = Progression.ValidateOffering(state, player, trophy.Receipt, operation); if (!trophy.Matches(state.WorldId, record2, prefab)) { throw new InvalidOperationException("Trophy identity does not match victory"); } state.Transactions.Add(new OfferTransaction { OperationId = operation, TrophyId = trophy.Receipt, PlayerId = player, Phase = OfferPhase.Prepared }); return OfferPhase.Prepared; } public static OfferPhase Commit(WorldProgress state, long player, string operation) { OfferTransaction transaction = Find(state, player, operation); if (transaction.Phase != OfferPhase.Prepared) { return transaction.Phase; } TrophyRecord trophyRecord = state.Trophies.Single((TrophyRecord t) => t.Id == transaction.TrophyId); if (trophyRecord.Tier <= state.Altars[(int)trophyRecord.Boss]) { transaction.Phase = OfferPhase.Cancelled; return transaction.Phase; } Progression.Offer(state, player, transaction.TrophyId, operation); transaction.Phase = OfferPhase.Committed; return transaction.Phase; } public static OfferPhase Cancel(WorldProgress state, long player, string operation) { OfferTransaction offerTransaction = Find(state, player, operation); if (offerTransaction.Phase == OfferPhase.Prepared) { offerTransaction.Phase = OfferPhase.Cancelled; } return offerTransaction.Phase; } public static OfferPhase Query(WorldProgress state, long player, string operation) { return Find(state, player, operation).Phase; } public static OfferPhase CancelBound(WorldProgress state, long player, TrophyIdentity identity, string prefab, string operation) { Progression.CheckId(operation); if (identity == null) { throw new ArgumentNullException("identity"); } TrophyRecord record = state.Trophies.Find((TrophyRecord t) => t.Id == identity.Receipt); if (!identity.Matches(state.WorldId, record, prefab) || !state.Players.Any((PlayerProgress p) => p.PlayerId == player && p.Eligible[(int)identity.Boss] >= identity.Tier)) { throw new InvalidOperationException("Invalid cancellation identity"); } OfferTransaction offerTransaction = state.Transactions.Find((OfferTransaction t) => t.OperationId == operation); if (offerTransaction != null) { VerifyActor(offerTransaction, player); if (offerTransaction.TrophyId != identity.Receipt) { throw new InvalidOperationException("Cancellation receipt changed"); } return Cancel(state, player, operation); } if (state.Offerings.Any((OfferingRecord o) => o.OperationId == operation)) { throw new InvalidOperationException("Operation already belongs to an offering"); } state.Transactions.Add(new OfferTransaction { OperationId = operation, TrophyId = identity.Receipt, PlayerId = player, Phase = OfferPhase.Cancelled }); return OfferPhase.Cancelled; } private static OfferTransaction Find(WorldProgress state, long player, string operation) { Progression.CheckId(operation); OfferTransaction offerTransaction = state.Transactions.Find((OfferTransaction t) => t.OperationId == operation); if (offerTransaction == null) { throw new InvalidOperationException("Unknown offering transaction"); } VerifyActor(offerTransaction, player); return offerTransaction; } private static void VerifyActor(OfferTransaction transaction, long player) { if (player <= 0 || transaction.PlayerId != player) { throw new InvalidOperationException("Offering belongs to a different player"); } } } public enum OfferingAction : byte { Prepare, Commit, Cancel, Query } public sealed class OfferingRequest { public long World; public OfferingAction Action; public Boss Boss; public int Tier; public string Operation; public string Receipt; public long AltarOwner; public uint AltarId; public byte[] Encode() { Progression.CheckBoss(Boss); Progression.CheckTier(Tier); if (!Enum.IsDefined(typeof(OfferingAction), Action)) { throw new ArgumentException("Unknown offering action"); } using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(1); binaryWriter.Write(World); binaryWriter.Write((byte)Action); binaryWriter.Write((byte)Boss); binaryWriter.Write((byte)Tier); binaryWriter.Write(Guid.ParseExact(Operation, "N").ToByteArray()); binaryWriter.Write(Guid.ParseExact(Receipt, "N").ToByteArray()); binaryWriter.Write(AltarOwner); binaryWriter.Write(AltarId); return memoryStream.ToArray(); } public static OfferingRequest Decode(byte[] bytes, long expectedWorld) { if (bytes == null || bytes.Length != 59) { throw new InvalidDataException("Invalid offering packet size"); } using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes)); if (binaryReader.ReadInt32() != 1) { throw new InvalidDataException("Unknown offering protocol"); } OfferingRequest offeringRequest = new OfferingRequest(); offeringRequest.World = binaryReader.ReadInt64(); offeringRequest.Action = (OfferingAction)binaryReader.ReadByte(); offeringRequest.Boss = (Boss)binaryReader.ReadByte(); offeringRequest.Tier = binaryReader.ReadByte(); offeringRequest.Operation = new Guid(binaryReader.ReadBytes(16)).ToString("N"); offeringRequest.Receipt = new Guid(binaryReader.ReadBytes(16)).ToString("N"); offeringRequest.AltarOwner = binaryReader.ReadInt64(); offeringRequest.AltarId = binaryReader.ReadUInt32(); OfferingRequest offeringRequest2 = offeringRequest; if (offeringRequest2.World != expectedWorld || !Enum.IsDefined(typeof(OfferingAction), offeringRequest2.Action)) { throw new InvalidDataException("Invalid offering world or action"); } Progression.CheckBoss(offeringRequest2.Boss); Progression.CheckTier(offeringRequest2.Tier); return offeringRequest2; } } public sealed class OfferingReply { public long World; public string Operation; public bool Accepted; public OfferPhase Phase; public byte[] Encode() { using MemoryStream memoryStream = new MemoryStream(); using BinaryWriter binaryWriter = new BinaryWriter(memoryStream); binaryWriter.Write(1); binaryWriter.Write(World); binaryWriter.Write(Guid.ParseExact(Operation, "N").ToByteArray()); binaryWriter.Write(Accepted); binaryWriter.Write((byte)Phase); return memoryStream.ToArray(); } public static OfferingReply Decode(byte[] bytes, long world) { if (bytes == null || bytes.Length != 30) { throw new InvalidDataException("Invalid offering reply size"); } using BinaryReader binaryReader = new BinaryReader(new MemoryStream(bytes)); if (binaryReader.ReadInt32() != 1) { throw new InvalidDataException("Unknown offering reply protocol"); } OfferingReply offeringReply = new OfferingReply(); offeringReply.World = binaryReader.ReadInt64(); offeringReply.Operation = new Guid(binaryReader.ReadBytes(16)).ToString("N"); OfferingReply offeringReply2 = offeringReply; byte b = binaryReader.ReadByte(); offeringReply2.Phase = (OfferPhase)binaryReader.ReadByte(); if (offeringReply2.World != world || b > 1 || !Enum.IsDefined(typeof(OfferPhase), offeringReply2.Phase)) { throw new InvalidDataException("Invalid offering reply"); } offeringReply2.Accepted = b == 1; return offeringReply2; } } public enum OfferingRecoveryAction { AwaitHost, Remove, SaveHeld, ReturnHeld, ConsumeHeld, SaveReturned, SaveConsumed, PreserveUnremoved, Repair } public static class OfferingRecovery { public static OfferingRecoveryAction Hold(EscrowEntry entry, string marker) { Validate(entry); if (entry.InventorySaved || entry.Decision != EscrowDecision.AwaitHost || entry.MarkerVersion != 1) { return OfferingRecoveryAction.Repair; } if (marker == null) { return OfferingRecoveryAction.Remove; } if (marker == "held" && entry.RemovalStarted) { return OfferingRecoveryAction.SaveHeld; } return OfferingRecoveryAction.Repair; } public static OfferingRecoveryAction Outcome(EscrowEntry entry, string marker) { Validate(entry); if (entry.Decision == EscrowDecision.AwaitHost) { return OfferingRecoveryAction.AwaitHost; } if (entry.MarkerVersion == 0) { if (marker != null || entry.RemovalStarted || entry.Decision != EscrowDecision.Return) { return OfferingRecoveryAction.Repair; } return OfferingRecoveryAction.PreserveUnremoved; } if (entry.Decision == EscrowDecision.Consume) { if (!entry.RemovalStarted) { return OfferingRecoveryAction.Repair; } if (marker == "held") { return OfferingRecoveryAction.ConsumeHeld; } if (marker == "consumed") { return OfferingRecoveryAction.SaveConsumed; } } else { if (marker == "held" && entry.RemovalStarted) { return OfferingRecoveryAction.ReturnHeld; } if (marker == "returned") { return OfferingRecoveryAction.SaveReturned; } if (marker == null) { return OfferingRecoveryAction.PreserveUnremoved; } } return OfferingRecoveryAction.Repair; } private static void Validate(EscrowEntry entry) { if (entry == null || entry.MarkerVersion < 0 || entry.MarkerVersion > 1 || !Enum.IsDefined(typeof(EscrowDecision), entry.Decision)) { throw new ArgumentException("Invalid trophy recovery evidence"); } } } [DataContract] public sealed class ParticipationRules { [DataMember] public double Radius = 100.0; [DataMember] public double GraceSeconds = 180.0; [DataMember] public double DamageFraction = 0.01; [DataMember] public double HealingFraction = 0.2; [DataMember] public double DefenseFraction = 0.2; public ParticipationRules Copy() { Validate(); ParticipationRules participationRules = new ParticipationRules(); participationRules.Radius = Radius; participationRules.GraceSeconds = GraceSeconds; participationRules.DamageFraction = DamageFraction; participationRules.HealingFraction = HealingFraction; participationRules.DefenseFraction = DefenseFraction; return participationRules; } public bool SameAs(ParticipationRules other) { if (other != null && Radius == other.Radius && GraceSeconds == other.GraceSeconds && DamageFraction == other.DamageFraction && HealingFraction == other.HealingFraction) { return DefenseFraction == other.DefenseFraction; } return false; } public void Validate() { double[] array = new double[5] { Radius, GraceSeconds, DamageFraction, HealingFraction, DefenseFraction }; foreach (double num in array) { if (double.IsNaN(num) || double.IsInfinity(num) || num <= 0.0) { throw new ArgumentException("Invalid participation setting"); } } if (DamageFraction > 1.0 || HealingFraction > 10.0 || DefenseFraction > 10.0 || Radius > 1000.0 || GraceSeconds > 3600.0) { throw new ArgumentException("Participation setting exceeds supported range"); } } } [DataContract] public sealed class ParticipationEntry { [DataMember] public long Player; [DataMember] public double Damage; [DataMember] public double Healing; [DataMember] public double Defense; [DataMember] public double? LastPresent; [DataMember] public bool Present; } [DataContract] public sealed class ParticipationSnapshot { [DataMember] public double BossMaxHealth; [DataMember] public double LastTime; [DataMember] public ParticipationRules Rules; [DataMember] public ParticipationEntry[] Entries; [DataMember] public long[] Result; [DataMember]