mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 18:27:24 +01:00
Add some Metadata validation.
This commit is contained in:
parent
5567134a56
commit
e14fedf59e
17 changed files with 254 additions and 158 deletions
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"/>
|
||||||
|
|
|
||||||
|
|
@ -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"/>
|
||||||
|
|
|
||||||
|
|
@ -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"/>
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue