public static IEnumerable <APerkEffect> ParseEffects(IMutagenReadStream stream) { while (stream.TryReadSubrecordFrame(RecordTypes.PRKE, out var prkeFrame)) { var type = (Perk.EffectType)prkeFrame.Content[0]; var rank = prkeFrame.Content[1]; var priority = prkeFrame.Content[2]; APerkEffect effect; if (stream.TryReadSubrecordFrame(RecordTypes.DATA, out var dataFrame)) { switch (type) { case Perk.EffectType.Quest: effect = new PerkQuestEffect() { Quest = new FormLink <IQuestGetter>(FormKeyBinaryTranslation.Instance.Parse(dataFrame.Content, stream.MetaData.MasterReferences !)), Stage = dataFrame.Content[4], Unknown = dataFrame.Content.Slice(5, 3).ToArray(), }; effect.Conditions.SetTo( ListBinaryTranslation <PerkCondition> .Instance.Parse( reader: new MutagenFrame(stream), transl: (MutagenFrame r, out PerkCondition listSubItem) => { return(LoquiBinaryTranslation <PerkCondition> .Instance.Parse( frame: r, item: out listSubItem !)); })); break; case Perk.EffectType.Ability: effect = new PerkAbilityEffect() { Ability = new FormLink <ISpellGetter>(FormKeyBinaryTranslation.Instance.Parse(dataFrame.Content, stream.MetaData.MasterReferences !)), }; effect.Conditions.SetTo( ListBinaryTranslation <PerkCondition> .Instance.Parse( reader: new MutagenFrame(stream), transl: (MutagenFrame r, out PerkCondition listSubItem) => { return(LoquiBinaryTranslation <PerkCondition> .Instance.Parse( frame: r, item: out listSubItem !)); })); break; case Perk.EffectType.EntryPoint: var entryPt = (APerkEntryPointEffect.EntryType)dataFrame.Content[0]; var func = (APerkEntryPointEffect.FunctionType)dataFrame.Content[1]; var tabCount = dataFrame.Content[2]; var conditions = ListBinaryTranslation <PerkCondition> .Instance.Parse( reader : new MutagenFrame(stream), transl : (MutagenFrame r, out PerkCondition listSubItem) => { return(LoquiBinaryTranslation <PerkCondition> .Instance.Parse( frame: r, item: out listSubItem !)); }) .ToList(); ReadOnlyMemorySlice <byte>?epf2 = null; ReadOnlyMemorySlice <byte>?epf3 = null; ReadOnlyMemorySlice <byte>?epfd = null; ReadOnlyMemorySlice <byte>?epft = null; while (stream.TryReadSubrecordFrame(out var subFrame)) { switch (subFrame.RecordTypeInt) { case RecordTypeInts.EPF2: epf2 = subFrame.Content; break; case RecordTypeInts.EPF3: epf3 = subFrame.Content; break; case RecordTypeInts.EPFD: epfd = subFrame.Content; break; case RecordTypeInts.EPFT: epft = subFrame.Content; break; default: stream.Position -= subFrame.Content.Length; goto searchDone; } } searchDone: APerkEntryPointEffect entryPointEffect; switch (func) { case APerkEntryPointEffect.FunctionType.SetValue: case APerkEntryPointEffect.FunctionType.AddValue: case APerkEntryPointEffect.FunctionType.MultiplyValue: if (epf2.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF2, $"{nameof(PerkEntryPointModifyValue)} had EPF2 unexpectedly"); } if (epf3.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF3, $"{nameof(PerkEntryPointModifyValue)} had EPF3 unexpectedly"); } float?f; if (epft == null && epfd == null) { f = null; } else { if (!epft.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointModifyValue)} did not have expected EPFT record"); } if (!epfd.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointModifyValue)} did not have expected EPFD record"); } if (epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.Float) { throw new ArgumentException($"{nameof(PerkEntryPointModifyValue)} did not have expected parameter type flag: {epft.Value[0]}"); } f = epfd.Value.Float(); } entryPointEffect = new PerkEntryPointModifyValue() { Value = f, Modification = func switch { APerkEntryPointEffect.FunctionType.SetValue => PerkEntryPointModifyValue.ModificationType.Set, APerkEntryPointEffect.FunctionType.MultiplyValue => PerkEntryPointModifyValue.ModificationType.Multiply, APerkEntryPointEffect.FunctionType.AddValue => PerkEntryPointModifyValue.ModificationType.Add, _ => throw new ArgumentException(), } }; break; case APerkEntryPointEffect.FunctionType.AddRangeToValue: if (epf2.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF2, $"{nameof(PerkEntryPointModifyValue)} had EPF2 unexpectedly"); } if (epf3.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF3, $"{nameof(PerkEntryPointModifyValue)} had EPF3 unexpectedly"); } if (!epft.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointAddRangeToValue)} did not have expected EPFT record"); } if (!epfd.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointAddRangeToValue)} did not have expected EPFD record"); } if (epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.FloatFloat) { throw new ArgumentException($"{nameof(PerkEntryPointAddRangeToValue)} did not have expected parameter type flag: {epft.Value[0]}"); } entryPointEffect = new PerkEntryPointAddRangeToValue() { From = epfd.Value.Float(), To = epfd.Value.Slice(4).Float(), }; break; case APerkEntryPointEffect.FunctionType.SetToActorValueMult: case APerkEntryPointEffect.FunctionType.MultiplyActorValueMult: case APerkEntryPointEffect.FunctionType.MultiplyOnePlusActorValueMult: case APerkEntryPointEffect.FunctionType.AddActorValueMult: if (epf2.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF2, $"{nameof(PerkEntryPointModifyValue)} had EPF2 unexpectedly"); } if (epf3.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF3, $"{nameof(PerkEntryPointModifyValue)} had EPF3 unexpectedly"); } if (!epft.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointModifyActorValue)} did not have expected EPFT record"); } if (!epfd.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointModifyActorValue)} did not have expected EPFD record"); } if (epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.FloatFloat) { throw new ArgumentException($"{nameof(PerkEntryPointModifyActorValue)} did not have expected parameter type flag: {epft.Value[0]}"); } entryPointEffect = new PerkEntryPointModifyActorValue() { ActorValue = (ActorValue)BinaryPrimitives.ReadInt32LittleEndian(epfd.Value), Value = epfd.Value.Slice(4).Float(), Modification = func switch { APerkEntryPointEffect.FunctionType.SetToActorValueMult => PerkEntryPointModifyActorValue.ModificationType.SetToAVMult, APerkEntryPointEffect.FunctionType.AddActorValueMult => PerkEntryPointModifyActorValue.ModificationType.AddAVMult, APerkEntryPointEffect.FunctionType.MultiplyActorValueMult => PerkEntryPointModifyActorValue.ModificationType.MultiplyAVMult, APerkEntryPointEffect.FunctionType.MultiplyOnePlusActorValueMult => PerkEntryPointModifyActorValue.ModificationType.MultiplyOnePlusAVMult, _ => throw new ArgumentException(), } }; break; case APerkEntryPointEffect.FunctionType.AbsoluteValue: case APerkEntryPointEffect.FunctionType.NegativeAbsoluteValue: if (epf2.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF2, $"{nameof(PerkEntryPointModifyValue)} had EPF2 unexpectedly"); } if (epf3.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF3, $"{nameof(PerkEntryPointModifyValue)} had EPF3 unexpectedly"); } if (epft.HasValue && epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.None) { throw new ArgumentException($"{nameof(PerkEntryPointAbsoluteValue)} did not have expected parameter type flag: {epft.Value[0]}"); } entryPointEffect = new PerkEntryPointAbsoluteValue() { Negative = func == APerkEntryPointEffect.FunctionType.NegativeAbsoluteValue }; break; case APerkEntryPointEffect.FunctionType.AddLeveledList: if (epf2.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF2, $"{nameof(PerkEntryPointModifyValue)} had EPF2 unexpectedly"); } if (epf3.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF3, $"{nameof(PerkEntryPointModifyValue)} had EPF3 unexpectedly"); } if (!epft.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointAddLeveledItem)} did not have expected EPFT record"); } if (epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.LeveledItem) { throw new ArgumentException($"{nameof(PerkEntryPointAddLeveledItem)} did not have expected parameter type flag: {epft.Value[0]}"); } entryPointEffect = new PerkEntryPointAddLeveledItem() { Item = new FormLink <ILeveledItemGetter>(epfd.HasValue ? FormKeyBinaryTranslation.Instance.Parse(epfd.Value, stream.MetaData.MasterReferences !) : FormKey.Null) }; break; case APerkEntryPointEffect.FunctionType.AddActivateChoice: if (!epft.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointAddActivateChoice)} did not have expected EPFT record"); } if (!epf3.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointAddActivateChoice)} did not have expected EPF3 record"); } if (epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.SpellWithStrings) { throw new ArgumentException($"{nameof(PerkEntryPointAddActivateChoice)} did not have expected parameter type flag: {epft.Value[0]}"); } entryPointEffect = new PerkEntryPointAddActivateChoice() { Spell = new FormLinkNullable <ISpellGetter>(epfd.HasValue ? FormKeyBinaryTranslation.Instance.Parse(epfd.Value, stream.MetaData.MasterReferences !) : default(FormKey?)), ButtonLabel = epf2.HasValue ? StringBinaryTranslation.Instance.Parse(epf2.Value, StringsSource.Normal, stream.MetaData) : null, Flags = new PerkScriptFlag() { Flags = (PerkScriptFlag.Flag)BinaryPrimitives.ReadInt16LittleEndian(epf3.Value), FragmentIndex = BinaryPrimitives.ReadUInt16LittleEndian(epf3.Value.Slice(2)) }, }; break; case APerkEntryPointEffect.FunctionType.SelectSpell: if (epf2.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF2, $"{nameof(PerkEntryPointModifyValue)} had EPF2 unexpectedly"); } if (epf3.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF3, $"{nameof(PerkEntryPointModifyValue)} had EPF3 unexpectedly"); } if (!epft.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointSelectSpell)} did not have expected EPFT record"); } if (epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.Spell) { throw new ArgumentException($"{nameof(PerkEntryPointSelectSpell)} did not have expected parameter type flag: {epft.Value[0]}"); } entryPointEffect = new PerkEntryPointSelectSpell() { Spell = new FormLink <ISpellGetter>(epfd.HasValue ? FormKeyBinaryTranslation.Instance.Parse(epfd.Value, stream.MetaData.MasterReferences !) : FormKey.Null), }; break; case APerkEntryPointEffect.FunctionType.SelectText: if (epf2.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF2, $"{nameof(PerkEntryPointModifyValue)} had EPF2 unexpectedly"); } if (epf3.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF3, $"{nameof(PerkEntryPointModifyValue)} had EPF3 unexpectedly"); } if (!epft.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointSelectText)} did not have expected EPFT record"); } if (epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.String) { throw new ArgumentException($"{nameof(PerkEntryPointSelectText)} did not have expected parameter type flag: {epft.Value[0]}"); } entryPointEffect = new PerkEntryPointSelectText() { Text = epfd.HasValue ? BinaryStringUtility.ProcessWholeToZString(epfd.Value, stream.MetaData.Encodings.NonTranslated) : string.Empty }; break; case APerkEntryPointEffect.FunctionType.SetText: if (epf2.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF2, $"{nameof(PerkEntryPointModifyValue)} had EPF2 unexpectedly"); } if (epf3.HasValue) { stream.MetaData.ReportIssue(RecordTypes.EPF3, $"{nameof(PerkEntryPointModifyValue)} had EPF3 unexpectedly"); } if (!epft.HasValue) { throw new ArgumentException($"{nameof(PerkEntryPointSetText)} did not have expected EPFT record"); } if (epft.Value[0] != (byte)APerkEntryPointEffect.ParameterType.LString) { throw new ArgumentException($"{nameof(PerkEntryPointSetText)} did not have expected parameter type flag: {epft.Value[0]}"); } entryPointEffect = new PerkEntryPointSetText() { Text = epfd.HasValue ? StringBinaryTranslation.Instance.Parse(epfd.Value, StringsSource.Normal, stream.MetaData) : (TranslatedString)string.Empty, }; break; default: throw new NotImplementedException(); } entryPointEffect.EntryPoint = entryPt; entryPointEffect.PerkConditionTabCount = tabCount; entryPointEffect.Conditions.SetTo(conditions); effect = entryPointEffect; break; default: throw new NotImplementedException(); } } else { effect = type switch { Perk.EffectType.Quest => new PerkQuestEffect(), Perk.EffectType.Ability => new PerkAbilityEffect(), _ => throw new ArgumentException($"Expected DATA subrecord that did not exist."), }; } effect.Rank = rank; effect.Priority = priority; if (stream.TryReadSubrecordFrame(RecordTypes.EPFT, out var epftFrame) && epftFrame.ContentLength != 1 && epftFrame.Content[0] != (byte)APerkEntryPointEffect.ParameterType.None) { throw new ArgumentException($"Encountered an unexpected epft frame."); } stream.TryReadSubrecordFrame(RecordTypes.PRKF, out var _); yield return(effect); } }