Add some Metadata validation.

This commit is contained in:
Ottermandias 2023-05-19 23:00:44 +02:00
parent 5567134a56
commit e14fedf59e
17 changed files with 254 additions and 158 deletions

View file

@ -69,7 +69,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return; return;
CheckInitialized(); CheckInitialized();
_communicator.CreatingCharacterBase.Subscribe(new Action<nint, string, nint, nint, nint>(value), Communication.CreatingCharacterBase.Priority.Api); _communicator.CreatingCharacterBase.Subscribe(new Action<nint, string, nint, nint, nint>(value),
Communication.CreatingCharacterBase.Priority.Api);
} }
remove remove
{ {
@ -89,7 +90,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
return; return;
CheckInitialized(); CheckInitialized();
_communicator.CreatedCharacterBase.Subscribe(new Action<nint, string, nint>(value), Communication.CreatedCharacterBase.Priority.Api); _communicator.CreatedCharacterBase.Subscribe(new Action<nint, string, nint>(value),
Communication.CreatedCharacterBase.Priority.Api);
} }
remove remove
{ {
@ -181,7 +183,8 @@ public class PenumbraApi : IDisposable, IPenumbraApi
public event ChangedItemClick? ChangedItemClicked public event ChangedItemClick? ChangedItemClicked
{ {
add => _communicator.ChangedItemClick.Subscribe(new Action<MouseButton, object?>(value!), Communication.ChangedItemClick.Priority.Default); add => _communicator.ChangedItemClick.Subscribe(new Action<MouseButton, object?>(value!),
Communication.ChangedItemClick.Priority.Default);
remove => _communicator.ChangedItemClick.Unsubscribe(new Action<MouseButton, object?>(value!)); remove => _communicator.ChangedItemClick.Unsubscribe(new Action<MouseButton, object?>(value!));
} }
@ -1120,13 +1123,14 @@ public class PenumbraApi : IDisposable, IPenumbraApi
} }
manips = new HashSet<MetaManipulation>(manipArray!.Length); manips = new HashSet<MetaManipulation>(manipArray!.Length);
foreach (var manip in manipArray.Where(m => m.ManipulationType != MetaManipulation.Type.Unknown)) foreach (var manip in manipArray.Where(m => m.Validate()))
{ {
if (!manips.Add(manip)) if (manips.Add(manip))
{ continue;
manips = null;
return false; Penumbra.Log.Warning($"Manipulation {manip} {manip.EntryToString()} is invalid and was skipped.");
} manips = null;
return false;
} }
return true; return true;

View file

@ -36,7 +36,7 @@ public readonly struct ImcCache : IDisposable
public bool ApplyMod( MetaFileManager manager, ModCollection collection, ImcManipulation manip ) public bool ApplyMod( MetaFileManager manager, ModCollection collection, ImcManipulation manip )
{ {
if( !manip.Valid ) if( !manip.Validate() )
{ {
return false; return false;
} }
@ -76,7 +76,7 @@ public readonly struct ImcCache : IDisposable
public bool RevertMod( MetaFileManager manager, ModCollection collection, ImcManipulation m ) public bool RevertMod( MetaFileManager manager, ModCollection collection, ImcManipulation m )
{ {
if( !m.Valid || !_imcManipulations.Remove( m ) ) if( !m.Validate() || !_imcManipulations.Remove( m ) )
{ {
return false; return false;
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using Penumbra.Api;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Communication; namespace Penumbra.Communication;
@ -12,7 +13,7 @@ public sealed class CreatedCharacterBase : EventWrapper<Action<nint, string, nin
{ {
public enum Priority public enum Priority
{ {
/// <seealso cref="Api.PenumbraApi.CreatedCharacterBase"/> /// <seealso cref="PenumbraApi.CreatedCharacterBase"/>
Api = 0, Api = 0,
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using Penumbra.Api;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Communication; namespace Penumbra.Communication;
@ -16,7 +17,7 @@ public sealed class CreatingCharacterBase : EventWrapper<Action<nint, string, ni
{ {
public enum Priority public enum Priority
{ {
/// <seealso cref="Api.PenumbraApi.CreatingCharacterBase"/> /// <seealso cref="PenumbraApi.CreatingCharacterBase"/>
Api = 0, Api = 0,
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using Penumbra.Api;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Communication; namespace Penumbra.Communication;
@ -13,7 +14,7 @@ public sealed class EnabledChanged : EventWrapper<Action<bool>, EnabledChanged.P
{ {
public enum Priority public enum Priority
{ {
/// <seealso cref="Api.PenumbraApi.EnabledChange"/> /// <seealso cref="Ipc.EnabledChange"/>
Api = int.MinValue, Api = int.MinValue,
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using Penumbra.Api;
using Penumbra.Util; using Penumbra.Util;
namespace Penumbra.Communication; namespace Penumbra.Communication;
@ -14,7 +15,7 @@ public sealed class ModDirectoryChanged : EventWrapper<Action<string, bool>, Mod
{ {
public enum Priority public enum Priority
{ {
/// <seealso cref="Api.PenumbraApi.ModDirectoryChanged"/> /// <seealso cref="PenumbraApi.ModDirectoryChanged"/>
Api = 0, Api = 0,
/// <seealso cref="UI.FileDialogService.OnModDirectoryChange"/> /// <seealso cref="UI.FileDialogService.OnModDirectoryChange"/>

View file

@ -1,5 +1,6 @@
using System; using System;
using System.IO; using System.IO;
using Penumbra.Api;
using Penumbra.Mods; using Penumbra.Mods;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
using Penumbra.Util; using Penumbra.Util;
@ -22,7 +23,7 @@ public sealed class ModPathChanged : EventWrapper<Action<ModPathChangeType, Mod,
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModChangeAddition"/> /// <seealso cref="Collections.Cache.CollectionCacheManager.OnModChangeAddition"/>
CollectionCacheManagerAddition = -100, CollectionCacheManagerAddition = -100,
/// <seealso cref="Api.PenumbraApi.ModPathChangeSubscriber"/> /// <seealso cref="PenumbraApi.ModPathChangeSubscriber"/>
Api = 0, Api = 0,
/// <seealso cref="Mods.Manager.ModCacheManager.OnModPathChange"/> /// <seealso cref="Mods.Manager.ModCacheManager.OnModPathChange"/>

View file

@ -1,4 +1,5 @@
using System; using System;
using Penumbra.Api;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Mods; using Penumbra.Mods;
@ -21,7 +22,7 @@ public sealed class ModSettingChanged : EventWrapper<Action<ModCollection, ModSe
{ {
public enum Priority public enum Priority
{ {
/// <seealso cref="Api.PenumbraApi.OnModSettingChange"/> /// <seealso cref="PenumbraApi.OnModSettingChange"/>
Api = int.MinValue, Api = int.MinValue,
/// <seealso cref="Collections.Cache.CollectionCacheManager.OnModSettingChange"/> /// <seealso cref="Collections.Cache.CollectionCacheManager.OnModSettingChange"/>

View file

@ -122,7 +122,7 @@ public partial class TexToolsMeta
{ {
var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot, var imc = new ImcManipulation(manip.ObjectType, manip.BodySlot, manip.PrimaryId, manip.SecondaryId, i, manip.EquipSlot,
value); value);
if (imc.Valid) if (imc.Validate())
MetaManipulations.Add(imc); MetaManipulations.Add(imc);
} }

View file

@ -9,91 +9,102 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout( LayoutKind.Sequential, Pack = 1 )] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct EqdpManipulation : IMetaManipulation< EqdpManipulation > public readonly struct EqdpManipulation : IMetaManipulation<EqdpManipulation>
{ {
public EqdpEntry Entry { get; private init; } public EqdpEntry Entry { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public Gender Gender { get; private init; } public Gender Gender { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public ModelRace Race { get; private init; } public ModelRace Race { get; private init; }
public ushort SetId { get; private init; } public ushort SetId { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public EquipSlot Slot { get; private init; } public EquipSlot Slot { get; private init; }
[JsonConstructor] [JsonConstructor]
public EqdpManipulation( EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, ushort setId ) public EqdpManipulation(EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, ushort setId)
{ {
Gender = gender; Gender = gender;
Race = race; Race = race;
SetId = setId; SetId = setId;
Slot = slot; Slot = slot;
Entry = Eqdp.Mask( Slot ) & entry; Entry = Eqdp.Mask(Slot) & entry;
} }
public EqdpManipulation Copy( EqdpManipulation entry ) public EqdpManipulation Copy(EqdpManipulation entry)
{ {
if( entry.Slot != Slot ) if (entry.Slot != Slot)
{ {
var (bit1, bit2) = entry.Entry.ToBits( entry.Slot ); var (bit1, bit2) = entry.Entry.ToBits(entry.Slot);
return new EqdpManipulation(Eqdp.FromSlotAndBits( Slot, bit1, bit2 ), Slot, Gender, Race, SetId); return new EqdpManipulation(Eqdp.FromSlotAndBits(Slot, bit1, bit2), Slot, Gender, Race, SetId);
} }
return new EqdpManipulation(entry.Entry, Slot, Gender, Race, SetId); return new EqdpManipulation(entry.Entry, Slot, Gender, Race, SetId);
} }
public EqdpManipulation Copy( EqdpEntry entry ) public EqdpManipulation Copy(EqdpEntry entry)
=> new(entry, Slot, Gender, Race, SetId); => new(entry, Slot, Gender, Race, SetId);
public override string ToString() public override string ToString()
=> $"Eqdp - {SetId} - {Slot} - {Race.ToName()} - {Gender.ToName()}"; => $"Eqdp - {SetId} - {Slot} - {Race.ToName()} - {Gender.ToName()}";
public bool Equals( EqdpManipulation other ) public bool Equals(EqdpManipulation other)
=> Gender == other.Gender => Gender == other.Gender
&& Race == other.Race && Race == other.Race
&& SetId == other.SetId && SetId == other.SetId
&& Slot == other.Slot; && Slot == other.Slot;
public override bool Equals( object? obj ) public override bool Equals(object? obj)
=> obj is EqdpManipulation other && Equals( other ); => obj is EqdpManipulation other && Equals(other);
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine( ( int )Gender, ( int )Race, SetId, ( int )Slot ); => HashCode.Combine((int)Gender, (int)Race, SetId, (int)Slot);
public int CompareTo( EqdpManipulation other ) public int CompareTo(EqdpManipulation other)
{ {
var r = Race.CompareTo( other.Race ); var r = Race.CompareTo(other.Race);
if( r != 0 ) if (r != 0)
{
return r; return r;
}
var g = Gender.CompareTo( other.Gender ); var g = Gender.CompareTo(other.Gender);
if( g != 0 ) if (g != 0)
{
return g; return g;
}
var set = SetId.CompareTo( other.SetId ); var set = SetId.CompareTo(other.SetId);
return set != 0 ? set : Slot.CompareTo( other.Slot ); return set != 0 ? set : Slot.CompareTo(other.Slot);
} }
public MetaIndex FileIndex() public MetaIndex FileIndex()
=> CharacterUtilityData.EqdpIdx( Names.CombinedRace( Gender, Race ), Slot.IsAccessory() ); => CharacterUtilityData.EqdpIdx(Names.CombinedRace(Gender, Race), Slot.IsAccessory());
public bool Apply( ExpandedEqdpFile file ) public bool Apply(ExpandedEqdpFile file)
{ {
var entry = file[ SetId ]; var entry = file[SetId];
var mask = Eqdp.Mask( Slot ); var mask = Eqdp.Mask(Slot);
if( ( entry & mask ) == Entry ) if ((entry & mask) == Entry)
{
return false; return false;
}
file[ SetId ] = ( entry & ~mask ) | Entry; file[SetId] = (entry & ~mask) | Entry;
return true; return true;
} }
}
public bool Validate()
{
var mask = Eqdp.Mask(Slot);
if (mask == 0)
return false;
if ((mask & Entry) != Entry)
return false;
if (FileIndex() == (MetaIndex)(-1))
return false;
// No check for set id.
return true;
}
}

View file

@ -67,4 +67,17 @@ public readonly struct EqpManipulation : IMetaManipulation< EqpManipulation >
file[ SetId ] = ( entry & ~mask ) | Entry; file[ SetId ] = ( entry & ~mask ) | Entry;
return true; return true;
} }
public bool Validate()
{
var mask = Eqp.Mask(Slot);
if (mask == 0)
return false;
if ((Entry & mask) != Entry)
return false;
// No check for set id.
return true;
}
} }

View file

@ -103,4 +103,14 @@ public readonly struct EstManipulation : IMetaManipulation< EstManipulation >
_ => throw new ArgumentOutOfRangeException(), _ => throw new ArgumentOutOfRangeException(),
}; };
} }
public bool Validate()
{
if (!Enum.IsDefined(Slot))
return false;
if (Names.CombinedRace(Gender, Race) == GenderRace.Unknown)
return false;
// No known check for set id or entry.
return true;
}
} }

View file

@ -51,4 +51,10 @@ public readonly struct GmpManipulation : IMetaManipulation< GmpManipulation >
file[ SetId ] = Entry; file[ SetId ] = Entry;
return true; return true;
} }
public bool Validate()
{
// No known conditions.
return true;
}
} }

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Reflection.Metadata;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters; using Newtonsoft.Json.Converters;
@ -72,15 +73,6 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
} }
} }
public bool Valid
=> ObjectType switch
{
ObjectType.Accessory => BodySlot == BodySlot.Unknown,
ObjectType.Equipment => BodySlot == BodySlot.Unknown,
ObjectType.DemiHuman => BodySlot == BodySlot.Unknown,
_ => EquipSlot == EquipSlot.Unknown,
};
public ImcManipulation Copy( ImcEntry entry ) public ImcManipulation Copy( ImcEntry entry )
=> new(ObjectType, BodySlot, PrimaryId, SecondaryId, Variant, EquipSlot, entry); => new(ObjectType, BodySlot, PrimaryId, SecondaryId, Variant, EquipSlot, entry);
@ -160,4 +152,39 @@ public readonly struct ImcManipulation : IMetaManipulation< ImcManipulation >
public bool Apply( ImcFile file ) public bool Apply( ImcFile file )
=> file.SetEntry( ImcFile.PartIndex( EquipSlot ), Variant, Entry ); => file.SetEntry( ImcFile.PartIndex( EquipSlot ), Variant, Entry );
public bool Validate()
{
switch (ObjectType)
{
case ObjectType.Accessory:
case ObjectType.Equipment:
if (BodySlot is not BodySlot.Unknown)
return false;
if (!EquipSlot.IsEquipmentPiece())
return false;
if (SecondaryId != 0)
return false;
break;
case ObjectType.DemiHuman:
if (BodySlot is not BodySlot.Unknown)
return false;
if (!EquipSlot.IsEquipmentPiece())
return false;
break;
default:
if (!Enum.IsDefined(BodySlot))
return false;
if (EquipSlot is not EquipSlot.Unknown)
return false;
if (!Enum.IsDefined(ObjectType))
return false;
break;
}
if (Entry.MaterialId == 0)
return false;
return true;
}
} }

View file

@ -12,12 +12,12 @@ public interface IMetaManipulation
public MetaIndex FileIndex(); public MetaIndex FileIndex();
} }
public interface IMetaManipulation< T > public interface IMetaManipulation<T>
: IMetaManipulation, IComparable< T >, IEquatable< T > where T : struct : IMetaManipulation, IComparable<T>, IEquatable<T> where T : struct
{ } { }
[StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )] [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 16)]
public readonly struct MetaManipulation : IEquatable< MetaManipulation >, IComparable< MetaManipulation > public readonly struct MetaManipulation : IEquatable<MetaManipulation>, IComparable<MetaManipulation>
{ {
public const int CurrentVersion = 0; public const int CurrentVersion = 0;
@ -32,33 +32,33 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >, ICompa
Rsp = 6, Rsp = 6,
} }
[FieldOffset( 0 )] [FieldOffset(0)]
[JsonIgnore] [JsonIgnore]
public readonly EqpManipulation Eqp = default; public readonly EqpManipulation Eqp = default;
[FieldOffset( 0 )] [FieldOffset(0)]
[JsonIgnore] [JsonIgnore]
public readonly GmpManipulation Gmp = default; public readonly GmpManipulation Gmp = default;
[FieldOffset( 0 )] [FieldOffset(0)]
[JsonIgnore] [JsonIgnore]
public readonly EqdpManipulation Eqdp = default; public readonly EqdpManipulation Eqdp = default;
[FieldOffset( 0 )] [FieldOffset(0)]
[JsonIgnore] [JsonIgnore]
public readonly EstManipulation Est = default; public readonly EstManipulation Est = default;
[FieldOffset( 0 )] [FieldOffset(0)]
[JsonIgnore] [JsonIgnore]
public readonly RspManipulation Rsp = default; public readonly RspManipulation Rsp = default;
[FieldOffset( 0 )] [FieldOffset(0)]
[JsonIgnore] [JsonIgnore]
public readonly ImcManipulation Imc = default; public readonly ImcManipulation Imc = default;
[FieldOffset( 15 )] [FieldOffset(15)]
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
[JsonProperty( "Type" )] [JsonProperty("Type")]
public readonly Type ManipulationType; public readonly Type ManipulationType;
public object? Manipulation public object? Manipulation
@ -76,149 +76,157 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >, ICompa
}; };
init init
{ {
switch( value ) switch (value)
{ {
case EqpManipulation m: case EqpManipulation m:
Eqp = m; Eqp = m;
ManipulationType = Type.Eqp; ManipulationType = m.Validate() ? Type.Eqp : Type.Unknown;
return; return;
case EqdpManipulation m: case EqdpManipulation m:
Eqdp = m; Eqdp = m;
ManipulationType = Type.Eqdp; ManipulationType = m.Validate() ? Type.Eqdp : Type.Unknown;
return; return;
case GmpManipulation m: case GmpManipulation m:
Gmp = m; Gmp = m;
ManipulationType = Type.Gmp; ManipulationType = m.Validate() ? Type.Gmp : Type.Unknown;
return; return;
case EstManipulation m: case EstManipulation m:
Est = m; Est = m;
ManipulationType = Type.Est; ManipulationType = m.Validate() ? Type.Est : Type.Unknown;
return; return;
case RspManipulation m: case RspManipulation m:
Rsp = m; Rsp = m;
ManipulationType = Type.Rsp; ManipulationType = m.Validate() ? Type.Rsp : Type.Unknown;
return; return;
case ImcManipulation m: case ImcManipulation m:
Imc = m; Imc = m;
ManipulationType = m.Valid ? Type.Imc : Type.Unknown; ManipulationType = m.Validate() ? Type.Imc : Type.Unknown;
return; return;
} }
} }
} }
public MetaManipulation( EqpManipulation eqp ) public bool Validate()
{
return ManipulationType switch
{
Type.Imc => Imc.Validate(),
Type.Eqdp => Eqdp.Validate(),
Type.Eqp => Eqp.Validate(),
Type.Est => Est.Validate(),
Type.Gmp => Gmp.Validate(),
Type.Rsp => Rsp.Validate(),
_ => false,
};
}
public MetaManipulation(EqpManipulation eqp)
{ {
Eqp = eqp; Eqp = eqp;
ManipulationType = Type.Eqp; ManipulationType = Type.Eqp;
} }
public MetaManipulation( GmpManipulation gmp ) public MetaManipulation(GmpManipulation gmp)
{ {
Gmp = gmp; Gmp = gmp;
ManipulationType = Type.Gmp; ManipulationType = Type.Gmp;
} }
public MetaManipulation( EqdpManipulation eqdp ) public MetaManipulation(EqdpManipulation eqdp)
{ {
Eqdp = eqdp; Eqdp = eqdp;
ManipulationType = Type.Eqdp; ManipulationType = Type.Eqdp;
} }
public MetaManipulation( EstManipulation est ) public MetaManipulation(EstManipulation est)
{ {
Est = est; Est = est;
ManipulationType = Type.Est; ManipulationType = Type.Est;
} }
public MetaManipulation( RspManipulation rsp ) public MetaManipulation(RspManipulation rsp)
{ {
Rsp = rsp; Rsp = rsp;
ManipulationType = Type.Rsp; ManipulationType = Type.Rsp;
} }
public MetaManipulation( ImcManipulation imc ) public MetaManipulation(ImcManipulation imc)
{ {
Imc = imc; Imc = imc;
ManipulationType = Type.Imc; ManipulationType = Type.Imc;
} }
public static implicit operator MetaManipulation( EqpManipulation eqp ) public static implicit operator MetaManipulation(EqpManipulation eqp)
=> new(eqp); => new(eqp);
public static implicit operator MetaManipulation( GmpManipulation gmp ) public static implicit operator MetaManipulation(GmpManipulation gmp)
=> new(gmp); => new(gmp);
public static implicit operator MetaManipulation( EqdpManipulation eqdp ) public static implicit operator MetaManipulation(EqdpManipulation eqdp)
=> new(eqdp); => new(eqdp);
public static implicit operator MetaManipulation( EstManipulation est ) public static implicit operator MetaManipulation(EstManipulation est)
=> new(est); => new(est);
public static implicit operator MetaManipulation( RspManipulation rsp ) public static implicit operator MetaManipulation(RspManipulation rsp)
=> new(rsp); => new(rsp);
public static implicit operator MetaManipulation( ImcManipulation imc ) public static implicit operator MetaManipulation(ImcManipulation imc)
=> new(imc); => new(imc);
public bool EntryEquals( MetaManipulation other ) public bool EntryEquals(MetaManipulation other)
{ {
if( ManipulationType != other.ManipulationType ) if (ManipulationType != other.ManipulationType)
{
return false; return false;
}
return ManipulationType switch return ManipulationType switch
{ {
Type.Eqp => Eqp.Entry.Equals( other.Eqp.Entry ), Type.Eqp => Eqp.Entry.Equals(other.Eqp.Entry),
Type.Gmp => Gmp.Entry.Equals( other.Gmp.Entry ), Type.Gmp => Gmp.Entry.Equals(other.Gmp.Entry),
Type.Eqdp => Eqdp.Entry.Equals( other.Eqdp.Entry ), Type.Eqdp => Eqdp.Entry.Equals(other.Eqdp.Entry),
Type.Est => Est.Entry.Equals( other.Est.Entry ), Type.Est => Est.Entry.Equals(other.Est.Entry),
Type.Rsp => Rsp.Entry.Equals( other.Rsp.Entry ), Type.Rsp => Rsp.Entry.Equals(other.Rsp.Entry),
Type.Imc => Imc.Entry.Equals( other.Imc.Entry ), Type.Imc => Imc.Entry.Equals(other.Imc.Entry),
_ => throw new ArgumentOutOfRangeException(), _ => throw new ArgumentOutOfRangeException(),
}; };
} }
public bool Equals( MetaManipulation other ) public bool Equals(MetaManipulation other)
{ {
if( ManipulationType != other.ManipulationType ) if (ManipulationType != other.ManipulationType)
{
return false; return false;
}
return ManipulationType switch return ManipulationType switch
{ {
Type.Eqp => Eqp.Equals( other.Eqp ), Type.Eqp => Eqp.Equals(other.Eqp),
Type.Gmp => Gmp.Equals( other.Gmp ), Type.Gmp => Gmp.Equals(other.Gmp),
Type.Eqdp => Eqdp.Equals( other.Eqdp ), Type.Eqdp => Eqdp.Equals(other.Eqdp),
Type.Est => Est.Equals( other.Est ), Type.Est => Est.Equals(other.Est),
Type.Rsp => Rsp.Equals( other.Rsp ), Type.Rsp => Rsp.Equals(other.Rsp),
Type.Imc => Imc.Equals( other.Imc ), Type.Imc => Imc.Equals(other.Imc),
_ => false, _ => false,
}; };
} }
public MetaManipulation WithEntryOf( MetaManipulation other ) public MetaManipulation WithEntryOf(MetaManipulation other)
{ {
if( ManipulationType != other.ManipulationType ) if (ManipulationType != other.ManipulationType)
{
return this; return this;
}
return ManipulationType switch return ManipulationType switch
{ {
Type.Eqp => Eqp.Copy( other.Eqp.Entry ), Type.Eqp => Eqp.Copy(other.Eqp.Entry),
Type.Gmp => Gmp.Copy( other.Gmp.Entry ), Type.Gmp => Gmp.Copy(other.Gmp.Entry),
Type.Eqdp => Eqdp.Copy( other.Eqdp ), Type.Eqdp => Eqdp.Copy(other.Eqdp),
Type.Est => Est.Copy( other.Est.Entry ), Type.Est => Est.Copy(other.Est.Entry),
Type.Rsp => Rsp.Copy( other.Rsp.Entry ), Type.Rsp => Rsp.Copy(other.Rsp.Entry),
Type.Imc => Imc.Copy( other.Imc.Entry ), Type.Imc => Imc.Copy(other.Imc.Entry),
_ => throw new ArgumentOutOfRangeException(), _ => throw new ArgumentOutOfRangeException(),
}; };
} }
public override bool Equals( object? obj ) public override bool Equals(object? obj)
=> obj is MetaManipulation other && Equals( other ); => obj is MetaManipulation other && Equals(other);
public override int GetHashCode() public override int GetHashCode()
=> ManipulationType switch => ManipulationType switch
@ -232,11 +240,11 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >, ICompa
_ => 0, _ => 0,
}; };
public unsafe int CompareTo( MetaManipulation other ) public unsafe int CompareTo(MetaManipulation other)
{ {
fixed( MetaManipulation* lhs = &this ) fixed (MetaManipulation* lhs = &this)
{ {
return MemoryUtility.MemCmpUnchecked( lhs, &other, sizeof( MetaManipulation ) ); return MemoryUtility.MemCmpUnchecked(lhs, &other, sizeof(MetaManipulation));
} }
} }
@ -255,12 +263,13 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >, ICompa
public string EntryToString() public string EntryToString()
=> ManipulationType switch => ManipulationType switch
{ {
Type.Imc => $"{Imc.Entry.DecalId}-{Imc.Entry.MaterialId}-{Imc.Entry.VfxId}-{Imc.Entry.SoundId}-{Imc.Entry.MaterialAnimationId}-{Imc.Entry.AttributeMask}", Type.Imc =>
Type.Eqdp => $"{( ushort )Eqdp.Entry:X}", $"{Imc.Entry.DecalId}-{Imc.Entry.MaterialId}-{Imc.Entry.VfxId}-{Imc.Entry.SoundId}-{Imc.Entry.MaterialAnimationId}-{Imc.Entry.AttributeMask}",
Type.Eqp => $"{( ulong )Eqp.Entry:X}", Type.Eqdp => $"{(ushort)Eqdp.Entry:X}",
Type.Eqp => $"{(ulong)Eqp.Entry:X}",
Type.Est => $"{Est.Entry}", Type.Est => $"{Est.Entry}",
Type.Gmp => $"{Gmp.Entry.Value}", Type.Gmp => $"{Gmp.Entry.Value}",
Type.Rsp => $"{Rsp.Entry}", Type.Rsp => $"{Rsp.Entry}",
_ => string.Empty, _ => string.Empty,
}; };
} }

View file

@ -8,59 +8,69 @@ using Penumbra.Meta.Files;
namespace Penumbra.Meta.Manipulations; namespace Penumbra.Meta.Manipulations;
[StructLayout( LayoutKind.Sequential, Pack = 1 )] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct RspManipulation : IMetaManipulation< RspManipulation > public readonly struct RspManipulation : IMetaManipulation<RspManipulation>
{ {
public float Entry { get; private init; } public float Entry { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public SubRace SubRace { get; private init; } public SubRace SubRace { get; private init; }
[JsonConverter( typeof( StringEnumConverter ) )] [JsonConverter(typeof(StringEnumConverter))]
public RspAttribute Attribute { get; private init; } public RspAttribute Attribute { get; private init; }
[JsonConstructor] [JsonConstructor]
public RspManipulation( SubRace subRace, RspAttribute attribute, float entry ) public RspManipulation(SubRace subRace, RspAttribute attribute, float entry)
{ {
Entry = entry; Entry = entry;
SubRace = subRace; SubRace = subRace;
Attribute = attribute; Attribute = attribute;
} }
public RspManipulation Copy( float entry ) public RspManipulation Copy(float entry)
=> new(SubRace, Attribute, entry); => new(SubRace, Attribute, entry);
public override string ToString() public override string ToString()
=> $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}"; => $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}";
public bool Equals( RspManipulation other ) public bool Equals(RspManipulation other)
=> SubRace == other.SubRace => SubRace == other.SubRace
&& Attribute == other.Attribute; && Attribute == other.Attribute;
public override bool Equals( object? obj ) public override bool Equals(object? obj)
=> obj is RspManipulation other && Equals( other ); => obj is RspManipulation other && Equals(other);
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine( ( int )SubRace, ( int )Attribute ); => HashCode.Combine((int)SubRace, (int)Attribute);
public int CompareTo( RspManipulation other ) public int CompareTo(RspManipulation other)
{ {
var s = SubRace.CompareTo( other.SubRace ); var s = SubRace.CompareTo(other.SubRace);
return s != 0 ? s : Attribute.CompareTo( other.Attribute ); return s != 0 ? s : Attribute.CompareTo(other.Attribute);
} }
public MetaIndex FileIndex() public MetaIndex FileIndex()
=> MetaIndex.HumanCmp; => MetaIndex.HumanCmp;
public bool Apply( CmpFile file ) public bool Apply(CmpFile file)
{ {
var value = file[ SubRace, Attribute ]; var value = file[SubRace, Attribute];
if( value == Entry ) if (value == Entry)
{
return false; return false;
}
file[ SubRace, Attribute ] = Entry; file[SubRace, Attribute] = Entry;
return true; return true;
} }
}
public bool Validate()
{
if (SubRace is SubRace.Unknown || !Enum.IsDefined(SubRace))
return false;
if (!Enum.IsDefined(Attribute))
return false;
if (Entry is <= 1e-2f or > 8f)
return false;
return true;
}
}

View file

@ -89,7 +89,7 @@ public sealed class SubMod : ISubMod
var manips = json[nameof(Manipulations)]; var manips = json[nameof(Manipulations)];
if (manips != null) if (manips != null)
foreach (var s in manips.Children().Select(c => c.ToObject<MetaManipulation>()) foreach (var s in manips.Children().Select(c => c.ToObject<MetaManipulation>())
.Where(m => m.ManipulationType != MetaManipulation.Type.Unknown)) .Where(m => m.Validate()))
ManipulationData.Add(s); ManipulationData.Add(s);
} }