mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
tmp
This commit is contained in:
parent
f5fccb0235
commit
46581780e0
37 changed files with 2343 additions and 2444 deletions
|
|
@ -136,6 +136,7 @@ public sealed unsafe partial class Utf8String : IDisposable
|
|||
|
||||
if( isOwned )
|
||||
{
|
||||
GC.AddMemoryPressure( length + 1 );
|
||||
_length |= OwnedFlag;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
|||
using System.ComponentModel;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.GameData.Structs
|
||||
{
|
||||
namespace Penumbra.GameData.Structs;
|
||||
|
||||
[Flags]
|
||||
public enum EqpEntry : ulong
|
||||
{
|
||||
|
|
@ -86,6 +86,9 @@ namespace Penumbra.GameData.Structs
|
|||
|
||||
public static class Eqp
|
||||
{
|
||||
// cf. Client::Graphics::Scene::CharacterUtility.GetSlotEqpFlags
|
||||
public const EqpEntry DefaultEntry = ( EqpEntry )0x3fe00070603f00;
|
||||
|
||||
public static (int, int) BytesAndOffset( EquipSlot slot )
|
||||
{
|
||||
return slot switch
|
||||
|
|
@ -305,4 +308,3 @@ namespace Penumbra.GameData.Structs
|
|||
[ EquipSlot.Head ] = EqpAttributesHead,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@ namespace Penumbra.GameData.Structs
|
|||
{
|
||||
public struct GmpEntry
|
||||
{
|
||||
public static readonly GmpEntry Default = new ();
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => ( Value & 1 ) == 1;
|
||||
|
|
|
|||
|
|
@ -137,15 +137,21 @@ public static class Functions
|
|||
|
||||
|
||||
[DllImport( "msvcrt.dll", EntryPoint = "memcmp", CallingConvention = CallingConvention.Cdecl, SetLastError = false )]
|
||||
private static extern unsafe int memcmp( byte* b1, byte* b2, int count );
|
||||
private static extern unsafe int memcmp( void* b1, void* b2, int count );
|
||||
|
||||
public static unsafe int MemCmpUnchecked( byte* ptr1, byte* ptr2, int count )
|
||||
public static unsafe int MemCmpUnchecked( void* ptr1, void* ptr2, int count )
|
||||
=> memcmp( ptr1, ptr2, count );
|
||||
|
||||
|
||||
[DllImport( "msvcrt.dll", EntryPoint = "_memicmp", CallingConvention = CallingConvention.Cdecl, SetLastError = false )]
|
||||
private static extern unsafe int memicmp( byte* b1, byte* b2, int count );
|
||||
private static extern unsafe int memicmp( void* b1, void* b2, int count );
|
||||
|
||||
public static unsafe int MemCmpCaseInsensitiveUnchecked( byte* ptr1, byte* ptr2, int count )
|
||||
public static unsafe int MemCmpCaseInsensitiveUnchecked( void* ptr1, void* ptr2, int count )
|
||||
=> memicmp( ptr1, ptr2, count );
|
||||
|
||||
[DllImport( "msvcrt.dll", EntryPoint = "memset", CallingConvention = CallingConvention.Cdecl, SetLastError = false )]
|
||||
private static extern unsafe void* memset( void* dest, int c, int count );
|
||||
|
||||
public static unsafe void* MemSet( void* dest, byte value, int count )
|
||||
=> memset( dest, value, count );
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ using Dalamud.Logging;
|
|||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Importer.Models;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ using Lumina.Data.Files;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Importer
|
||||
{
|
||||
namespace Penumbra.Importer;
|
||||
|
||||
// TexTools provices custom generated *.meta files for its modpacks, that contain changes to
|
||||
// - imc files
|
||||
// - eqp files
|
||||
|
|
@ -22,6 +22,7 @@ namespace Penumbra.Importer
|
|||
// made by the mod. The filename determines to what the changes are applied, and the binary file itself contains changes.
|
||||
// We parse every *.meta file in a mod and combine all actual changes that do not keep data on default values and that can be applied to the game in a .json.
|
||||
// TexTools may also generate files that contain non-existing changes, e.g. *.imc files for weapon offhands, which will be ignored.
|
||||
// TexTools also provides .rgsp files, that contain changes to the racial scaling parameters in the human.cmp file.
|
||||
public class TexToolsMeta
|
||||
{
|
||||
// The info class determines the files or table locations the changes need to apply to from the filename.
|
||||
|
|
@ -39,8 +40,8 @@ namespace Penumbra.Importer
|
|||
private const string Ext = @"\.meta";
|
||||
|
||||
// These are the valid regexes for .meta files that we are able to support at the moment.
|
||||
private static readonly Regex HousingMeta = new($"bgcommon/hou/{Pt}/general/{Pi}/{Pir}{Ext}");
|
||||
private static readonly Regex CharaMeta = new($"chara/{Pt}/{Pp}{Pi}(/obj/{St}/{Sp}{Si})?/{File}{Slot}{Ext}");
|
||||
private static readonly Regex HousingMeta = new($"bgcommon/hou/{Pt}/general/{Pi}/{Pir}{Ext}", RegexOptions.Compiled);
|
||||
private static readonly Regex CharaMeta = new($"chara/{Pt}/{Pp}{Pi}(/obj/{St}/{Sp}{Si})?/{File}{Slot}{Ext}", RegexOptions.Compiled);
|
||||
|
||||
public readonly ObjectType PrimaryType;
|
||||
public readonly BodySlot SecondaryType;
|
||||
|
|
@ -138,33 +139,12 @@ namespace Penumbra.Importer
|
|||
|
||||
public readonly uint Version;
|
||||
public readonly string FilePath;
|
||||
public readonly List< MetaManipulation > Manipulations = new();
|
||||
|
||||
private static string ReadNullTerminated( BinaryReader reader )
|
||||
{
|
||||
var builder = new System.Text.StringBuilder();
|
||||
for( var c = reader.ReadChar(); c != 0; c = reader.ReadChar() )
|
||||
{
|
||||
builder.Append( c );
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void AddIfNotDefault( MetaManipulation manipulation )
|
||||
{
|
||||
try
|
||||
{
|
||||
if( Penumbra.MetaDefaults.CheckAgainstDefault( manipulation ) )
|
||||
{
|
||||
Manipulations.Add( manipulation );
|
||||
}
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Debug( "Skipped {Type}-manipulation:\n{e:l}", manipulation.Type, e );
|
||||
}
|
||||
}
|
||||
public readonly List< EqpManipulation > EqpManipulations = new();
|
||||
public readonly List< GmpManipulation > GmpManipulations = new();
|
||||
public readonly List< EqdpManipulation > EqdpManipulations = new();
|
||||
public readonly List< EstManipulation > EstManipulations = new();
|
||||
public readonly List< RspManipulation > RspManipulations = new();
|
||||
public readonly List< ImcManipulation > ImcManipulations = new();
|
||||
|
||||
private void DeserializeEqpEntry( Info info, byte[]? data )
|
||||
{
|
||||
|
|
@ -173,14 +153,13 @@ namespace Penumbra.Importer
|
|||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var value = Eqp.FromSlotAndBytes( info.EquipSlot, data );
|
||||
|
||||
AddIfNotDefault( MetaManipulation.Eqp( info.EquipSlot, info.PrimaryId, value ) );
|
||||
var def = new EqpManipulation( ExpandedEqpFile.GetDefault( info.PrimaryId ), info.EquipSlot, info.PrimaryId );
|
||||
var manip = new EqpManipulation( value, info.EquipSlot, info.PrimaryId );
|
||||
if( def.Entry != manip.Entry )
|
||||
{
|
||||
EqpManipulations.Add( manip );
|
||||
}
|
||||
catch( ArgumentException )
|
||||
{ }
|
||||
}
|
||||
|
||||
private void DeserializeEqdpEntries( Info info, byte[]? data )
|
||||
|
|
@ -202,7 +181,13 @@ namespace Penumbra.Importer
|
|||
}
|
||||
|
||||
var value = Eqdp.FromSlotAndBits( info.EquipSlot, ( byteValue & 1 ) == 1, ( byteValue & 2 ) == 2 );
|
||||
AddIfNotDefault( MetaManipulation.Eqdp( info.EquipSlot, gr, info.PrimaryId, value ) );
|
||||
var def = new EqdpManipulation( ExpandedEqdpFile.GetDefault( gr, info.EquipSlot.IsAccessory(), info.PrimaryId ), info.EquipSlot,
|
||||
gr.Split().Item1, gr.Split().Item2, info.PrimaryId );
|
||||
var manip = new EqdpManipulation( value, info.EquipSlot, gr.Split().Item1, gr.Split().Item2, info.PrimaryId );
|
||||
if( def.Entry != manip.Entry )
|
||||
{
|
||||
EqdpManipulations.Add( manip );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,7 +201,11 @@ namespace Penumbra.Importer
|
|||
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
var value = ( GmpEntry )reader.ReadUInt32();
|
||||
value.UnknownTotal = reader.ReadByte();
|
||||
AddIfNotDefault( MetaManipulation.Gmp( info.PrimaryId, value ) );
|
||||
var def = ExpandedGmpFile.GetDefault( info.PrimaryId );
|
||||
if( value != def )
|
||||
{
|
||||
GmpManipulations.Add( new GmpManipulation( value, info.PrimaryId ) );
|
||||
}
|
||||
}
|
||||
|
||||
private void DeserializeEstEntries( Info info, byte[]? data )
|
||||
|
|
@ -233,14 +222,24 @@ namespace Penumbra.Importer
|
|||
var gr = ( GenderRace )reader.ReadUInt16();
|
||||
var id = reader.ReadUInt16();
|
||||
var value = reader.ReadUInt16();
|
||||
if( !gr.IsValid()
|
||||
|| info.PrimaryType == ObjectType.Character && info.SecondaryType != BodySlot.Face && info.SecondaryType != BodySlot.Hair
|
||||
|| info.PrimaryType == ObjectType.Equipment && info.EquipSlot != EquipSlot.Head && info.EquipSlot != EquipSlot.Body )
|
||||
var type = ( info.SecondaryType, info.EquipSlot ) switch
|
||||
{
|
||||
(BodySlot.Face, _) => EstManipulation.EstType.Face,
|
||||
(BodySlot.Hair, _) => EstManipulation.EstType.Hair,
|
||||
(_, EquipSlot.Head) => EstManipulation.EstType.Head,
|
||||
(_, EquipSlot.Body) => EstManipulation.EstType.Body,
|
||||
_ => ( EstManipulation.EstType )0,
|
||||
};
|
||||
if( !gr.IsValid() || type == 0 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddIfNotDefault( MetaManipulation.Est( info.PrimaryType, info.EquipSlot, gr, info.SecondaryType, id, value ) );
|
||||
var def = EstFile.GetDefault( type, gr, id );
|
||||
if( def != value )
|
||||
{
|
||||
EstManipulations.Add( new EstManipulation( gr.Split().Item1, gr.Split().Item2, type, id, value ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -253,17 +252,26 @@ namespace Penumbra.Importer
|
|||
|
||||
var num = data.Length / 6;
|
||||
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
for( var i = 0; i < num; ++i )
|
||||
var values = reader.ReadStructures< ImcEntry >( num );
|
||||
ushort i = 0;
|
||||
if( info.PrimaryType is ObjectType.Equipment or ObjectType.Accessory )
|
||||
{
|
||||
var value = ImcFile.ImageChangeData.Read( reader );
|
||||
if( info.PrimaryType == ObjectType.Equipment || info.PrimaryType == ObjectType.Accessory )
|
||||
foreach( var value in values )
|
||||
{
|
||||
AddIfNotDefault( MetaManipulation.Imc( info.EquipSlot, info.PrimaryId, ( ushort )i, value ) );
|
||||
ImcEntry def;
|
||||
if( !value.Equals( def ) )
|
||||
ImcManipulations.Add(new ImcManipulation(info.EquipSlot, i, info.PrimaryId, value) );
|
||||
++i;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfNotDefault( MetaManipulation.Imc( info.PrimaryType, info.SecondaryType, info.PrimaryId
|
||||
, info.SecondaryId, ( ushort )i, value ) );
|
||||
foreach( var value in values )
|
||||
{
|
||||
ImcEntry def;
|
||||
if( !value.Equals( def ) )
|
||||
ImcManipulations.Add( new ImcManipulation( info.PrimaryType, info.SecondaryType, info.PrimaryId, info.SecondaryId, i, value ) );
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -272,43 +280,43 @@ namespace Penumbra.Importer
|
|||
{
|
||||
try
|
||||
{
|
||||
using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
Version = reader.ReadUInt32();
|
||||
FilePath = ReadNullTerminated( reader );
|
||||
var metaInfo = new Info( FilePath );
|
||||
var numHeaders = reader.ReadUInt32();
|
||||
var headerSize = reader.ReadUInt32();
|
||||
var headerStart = reader.ReadUInt32();
|
||||
reader.BaseStream.Seek( headerStart, SeekOrigin.Begin );
|
||||
|
||||
List< (MetaType type, uint offset, int size) > entries = new();
|
||||
for( var i = 0; i < numHeaders; ++i )
|
||||
{
|
||||
var currentOffset = reader.BaseStream.Position;
|
||||
var type = ( MetaType )reader.ReadUInt32();
|
||||
var offset = reader.ReadUInt32();
|
||||
var size = reader.ReadInt32();
|
||||
entries.Add( ( type, offset, size ) );
|
||||
reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin );
|
||||
}
|
||||
|
||||
byte[]? ReadEntry( MetaType type )
|
||||
{
|
||||
var idx = entries.FindIndex( t => t.type == type );
|
||||
if( idx < 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin );
|
||||
return reader.ReadBytes( entries[ idx ].size );
|
||||
}
|
||||
|
||||
DeserializeEqpEntry( metaInfo, ReadEntry( MetaType.Eqp ) );
|
||||
DeserializeGmpEntry( metaInfo, ReadEntry( MetaType.Gmp ) );
|
||||
DeserializeEqdpEntries( metaInfo, ReadEntry( MetaType.Eqdp ) );
|
||||
DeserializeEstEntries( metaInfo, ReadEntry( MetaType.Est ) );
|
||||
DeserializeImcEntries( metaInfo, ReadEntry( MetaType.Imc ) );
|
||||
//using var reader = new BinaryReader( new MemoryStream( data ) );
|
||||
//Version = reader.ReadUInt32();
|
||||
//FilePath = ReadNullTerminated( reader );
|
||||
//var metaInfo = new Info( FilePath );
|
||||
//var numHeaders = reader.ReadUInt32();
|
||||
//var headerSize = reader.ReadUInt32();
|
||||
//var headerStart = reader.ReadUInt32();
|
||||
//reader.BaseStream.Seek( headerStart, SeekOrigin.Begin );
|
||||
//
|
||||
//List< (MetaType type, uint offset, int size) > entries = new();
|
||||
//for( var i = 0; i < numHeaders; ++i )
|
||||
//{
|
||||
// var currentOffset = reader.BaseStream.Position;
|
||||
// var type = ( MetaType )reader.ReadUInt32();
|
||||
// var offset = reader.ReadUInt32();
|
||||
// var size = reader.ReadInt32();
|
||||
// entries.Add( ( type, offset, size ) );
|
||||
// reader.BaseStream.Seek( currentOffset + headerSize, SeekOrigin.Begin );
|
||||
//}
|
||||
//
|
||||
//byte[]? ReadEntry( MetaType type )
|
||||
//{
|
||||
// var idx = entries.FindIndex( t => t.type == type );
|
||||
// if( idx < 0 )
|
||||
// {
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// reader.BaseStream.Seek( entries[ idx ].offset, SeekOrigin.Begin );
|
||||
// return reader.ReadBytes( entries[ idx ].size );
|
||||
//}
|
||||
//
|
||||
//DeserializeEqpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Eqp ) );
|
||||
//DeserializeGmpEntry( metaInfo, ReadEntry( MetaManipulation.Type.Gmp ) );
|
||||
//DeserializeEqdpEntries( metaInfo, ReadEntry( MetaManipulation.Type.Eqdp ) );
|
||||
//DeserializeEstEntries( metaInfo, ReadEntry( MetaManipulation.Type.Est ) );
|
||||
//DeserializeImcEntries( metaInfo, ReadEntry( MetaManipulation.Type.Imc ) );
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
|
|
@ -354,29 +362,28 @@ namespace Penumbra.Importer
|
|||
return Invalid;
|
||||
}
|
||||
|
||||
if( gender == 1 )
|
||||
{
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinSize, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxSize, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinTail, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxTail, br.ReadSingle() ) );
|
||||
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinX, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinY, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinZ, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxX, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxY, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxZ, br.ReadSingle() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinSize, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxSize, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinTail, br.ReadSingle() ) );
|
||||
ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxTail, br.ReadSingle() ) );
|
||||
}
|
||||
|
||||
//if( gender == 1 )
|
||||
//{
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinSize, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxSize, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMinTail, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.FemaleMaxTail, br.ReadSingle() ) );
|
||||
//
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinX, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinY, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMinZ, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxX, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxY, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.BustMaxZ, br.ReadSingle() ) );
|
||||
//}
|
||||
//else
|
||||
//{
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinSize, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxSize, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMinTail, br.ReadSingle() ) );
|
||||
// ret.AddIfNotDefault( MetaManipulation.Rsp( subRace, RspAttribute.MaleMaxTail, br.ReadSingle() ) );
|
||||
//}
|
||||
//
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.Interop.Structs;
|
||||
|
||||
|
|
@ -14,10 +15,11 @@ public unsafe struct CharacterUtility
|
|||
public const int HairEstIdx = 65;
|
||||
public const int BodyEstIdx = 66;
|
||||
public const int HeadEstIdx = 67;
|
||||
public const int NumEqdpFiles = 2 * 28;
|
||||
|
||||
public static int EqdpIdx( ushort raceCode, bool accessory )
|
||||
public static int EqdpIdx( GenderRace raceCode, bool accessory )
|
||||
=> ( accessory ? 28 : 0 )
|
||||
+ raceCode switch
|
||||
+ ( int )raceCode switch
|
||||
{
|
||||
0101 => 2,
|
||||
0201 => 3,
|
||||
|
|
@ -65,7 +67,7 @@ public unsafe struct CharacterUtility
|
|||
public ResourceHandle* Resource( int idx )
|
||||
=> ( ResourceHandle* )Resources[ idx ];
|
||||
|
||||
public ResourceHandle* EqdpResource( ushort raceCode, bool accessory )
|
||||
public ResourceHandle* EqdpResource( GenderRace raceCode, bool accessory )
|
||||
=> Resource( EqdpIdx( raceCode, accessory ) );
|
||||
|
||||
[FieldOffset( 8 + HumanCmpIdx * 8 )]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ public unsafe struct ResourceHandle
|
|||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct DataIndirection
|
||||
{
|
||||
[FieldOffset( 0x00 )]
|
||||
public void** VTable;
|
||||
|
||||
[FieldOffset( 0x10 )]
|
||||
public byte* DataPtr;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.Meta
|
||||
{
|
||||
public static class EqdpEntryExtensions
|
||||
{
|
||||
public static bool Apply( this ref EqdpEntry entry, MetaManipulation manipulation )
|
||||
{
|
||||
if( manipulation.Type != MetaType.Eqdp )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var mask = Eqdp.Mask( manipulation.EqdpIdentifier.Slot );
|
||||
var result = ( entry & ~mask ) | manipulation.EqdpValue;
|
||||
var ret = result == entry;
|
||||
entry = result;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static EqdpEntry Reduce( this EqdpEntry entry, EquipSlot slot )
|
||||
=> entry & Eqdp.Mask( slot );
|
||||
}
|
||||
|
||||
|
||||
public static class EqpEntryExtensions
|
||||
{
|
||||
public static bool Apply( this ref EqpEntry entry, MetaManipulation manipulation )
|
||||
{
|
||||
if( manipulation.Type != MetaType.Eqp )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var mask = Eqp.Mask( manipulation.EqpIdentifier.Slot );
|
||||
var result = ( entry & ~mask ) | manipulation.EqpValue;
|
||||
var ret = result != entry;
|
||||
entry = result;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static EqpEntry Reduce( this EqpEntry entry, EquipSlot slot )
|
||||
=> entry & Eqp.Mask( slot );
|
||||
}
|
||||
|
||||
public static class GmpEntryExtension
|
||||
{
|
||||
public static GmpEntry Apply( this ref GmpEntry entry, MetaManipulation manipulation )
|
||||
{
|
||||
if( manipulation.Type != MetaType.Gmp )
|
||||
{
|
||||
return entry;
|
||||
}
|
||||
|
||||
entry.Value = manipulation.GmpValue.Value;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop.Structs;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
public class CmpFile
|
||||
namespace Penumbra.Meta.Files;
|
||||
|
||||
public sealed unsafe class CmpFile : MetaBaseFile
|
||||
{
|
||||
private const int RacialScalingStart = 0x2A800;
|
||||
|
||||
private readonly byte[] _byteData = new byte[RacialScalingStart];
|
||||
private readonly RspEntry[] _rspEntries;
|
||||
|
||||
public CmpFile( byte[] bytes )
|
||||
public float this[ SubRace subRace, RspAttribute attribute ]
|
||||
{
|
||||
if( bytes.Length < RacialScalingStart )
|
||||
get => *( float* )( Data + RacialScalingStart + subRace.ToRspIndex() * RspEntry.ByteSize + ( int )attribute * 4 );
|
||||
set => *( float* )( Data + RacialScalingStart + subRace.ToRspIndex() * RspEntry.ByteSize + ( int )attribute * 4 ) = value;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
=> Functions.MemCpyUnchecked( Data, ( byte* )DefaultData.Data, DefaultData.Length );
|
||||
|
||||
public void Reset( IEnumerable< (SubRace, RspAttribute) > entries )
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
Array.Copy( bytes, _byteData, RacialScalingStart );
|
||||
var rspEntryNum = ( bytes.Length - RacialScalingStart ) / RspEntry.ByteSize;
|
||||
var tmp = new List< RspEntry >( rspEntryNum );
|
||||
for( var i = 0; i < rspEntryNum; ++i )
|
||||
foreach( var (r, a) in entries )
|
||||
{
|
||||
tmp.Add( new RspEntry( bytes, RacialScalingStart + i * RspEntry.ByteSize ) );
|
||||
this[ r, a ] = GetDefault( r, a );
|
||||
}
|
||||
}
|
||||
|
||||
_rspEntries = tmp.ToArray();
|
||||
}
|
||||
|
||||
public RspEntry this[ SubRace subRace ]
|
||||
=> _rspEntries[ subRace.ToRspIndex() ];
|
||||
|
||||
public bool Set( SubRace subRace, RspAttribute attribute, float value )
|
||||
public CmpFile()
|
||||
: base( CharacterUtility.HumanCmpIdx )
|
||||
{
|
||||
var entry = _rspEntries[ subRace.ToRspIndex() ];
|
||||
var oldValue = entry[ attribute ];
|
||||
if( oldValue == value )
|
||||
AllocateData( DefaultData.Length );
|
||||
Reset();
|
||||
}
|
||||
|
||||
public static float GetDefault( SubRace subRace, RspAttribute attribute )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
entry[ attribute ] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] WriteBytes()
|
||||
{
|
||||
using var s = new MemoryStream( RacialScalingStart + _rspEntries.Length * RspEntry.ByteSize );
|
||||
s.Write( _byteData, 0, _byteData.Length );
|
||||
foreach( var entry in _rspEntries )
|
||||
{
|
||||
var bytes = entry.ToBytes();
|
||||
s.Write( bytes, 0, bytes.Length );
|
||||
}
|
||||
|
||||
return s.ToArray();
|
||||
}
|
||||
|
||||
private CmpFile( byte[] data, RspEntry[] entries )
|
||||
{
|
||||
_byteData = data.ToArray();
|
||||
_rspEntries = entries.Select( e => new RspEntry( e ) ).ToArray();
|
||||
}
|
||||
|
||||
public CmpFile Clone()
|
||||
=> new( _byteData, _rspEntries );
|
||||
var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ CharacterUtility.HumanCmpIdx ].Address;
|
||||
return *( float* )( data + RacialScalingStart + subRace.ToRspIndex() * RspEntry.ByteSize + ( int )attribute * 4 );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,21 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Lumina.Data;
|
||||
using System.Collections.Generic;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
// EQDP file structure:
|
||||
// [Identifier][BlockSize:ushort][BlockCount:ushort]
|
||||
// BlockCount x [BlockHeader:ushort]
|
||||
// Containing offsets for blocks, ushort.Max means collapsed.
|
||||
// Offsets are based on the end of the header, so 0 means IdentifierSize + 4 + BlockCount x 2.
|
||||
// ExpandedBlockCount x [Entry]
|
||||
public class EqdpFile
|
||||
|
||||
// Expanded Eqdp File just expands all blocks for easy read and write access to single entries and to keep the same memory for it.
|
||||
public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
||||
{
|
||||
private const ushort BlockHeaderSize = 2;
|
||||
private const ushort PreambleSize = 4;
|
||||
|
|
@ -21,194 +24,114 @@ namespace Penumbra.Meta.Files
|
|||
private const ushort EqdpEntrySize = 2;
|
||||
private const int FileAlignment = 1 << 9;
|
||||
|
||||
private EqdpFile( EqdpFile clone )
|
||||
public readonly int DataOffset;
|
||||
|
||||
public ushort Identifier
|
||||
=> *( ushort* )Data;
|
||||
|
||||
public ushort BlockSize
|
||||
=> *( ushort* )( Data + 2 );
|
||||
|
||||
public ushort BlockCount
|
||||
=> *( ushort* )( Data + 4 );
|
||||
|
||||
public int Count
|
||||
=> ( Length - DataOffset ) / EqdpEntrySize;
|
||||
|
||||
public EqdpEntry this[ int idx ]
|
||||
{
|
||||
Identifier = clone.Identifier;
|
||||
BlockSize = clone.BlockSize;
|
||||
TotalBlockCount = clone.TotalBlockCount;
|
||||
ExpandedBlockCount = clone.ExpandedBlockCount;
|
||||
Blocks = new EqdpEntry[clone.TotalBlockCount][];
|
||||
for( var i = 0; i < TotalBlockCount; ++i )
|
||||
get
|
||||
{
|
||||
if( clone.Blocks[ i ] != null )
|
||||
if( idx >= Count || idx < 0 )
|
||||
{
|
||||
Blocks[ i ] = ( EqdpEntry[] )clone.Blocks[ i ]!.Clone();
|
||||
}
|
||||
}
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
public ref EqdpEntry this[ ushort setId ]
|
||||
=> ref GetTrueEntry( setId );
|
||||
|
||||
|
||||
public EqdpFile Clone()
|
||||
=> new( this );
|
||||
|
||||
private ushort Identifier { get; }
|
||||
private ushort BlockSize { get; }
|
||||
private ushort TotalBlockCount { get; }
|
||||
private ushort ExpandedBlockCount { get; set; }
|
||||
private EqdpEntry[]?[] Blocks { get; }
|
||||
|
||||
private int BlockIdx( ushort id )
|
||||
=> ( ushort )( id / BlockSize );
|
||||
|
||||
private int SubIdx( ushort id )
|
||||
=> ( ushort )( id % BlockSize );
|
||||
|
||||
private bool ExpandBlock( int idx )
|
||||
return *( EqdpEntry* )( Data + DataOffset + EqdpEntrySize * idx );
|
||||
}
|
||||
set
|
||||
{
|
||||
if( idx < TotalBlockCount && Blocks[ idx ] == null )
|
||||
if( idx >= Count || idx < 0 )
|
||||
{
|
||||
Blocks[ idx ] = new EqdpEntry[BlockSize];
|
||||
++ExpandedBlockCount;
|
||||
return true;
|
||||
throw new IndexOutOfRangeException();
|
||||
}
|
||||
|
||||
return false;
|
||||
*( EqdpEntry* )( Data + DataOffset + EqdpEntrySize * idx ) = value;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CollapseBlock( int idx )
|
||||
public override void Reset()
|
||||
{
|
||||
if( idx >= TotalBlockCount || Blocks[ idx ] == null )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var def = ( byte* )DefaultData.Data;
|
||||
Functions.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize );
|
||||
|
||||
Blocks[ idx ] = null;
|
||||
--ExpandedBlockCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetEntry( ushort idx, EqdpEntry entry )
|
||||
var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize );
|
||||
var dataBasePtr = ( byte* )( controlPtr + BlockCount );
|
||||
var myDataPtr = ( ushort* )( Data + IdentifierSize + PreambleSize + 2 * BlockCount );
|
||||
for( var i = 0; i < BlockCount; ++i )
|
||||
{
|
||||
var block = BlockIdx( idx );
|
||||
if( block >= TotalBlockCount )
|
||||
if( controlPtr[ i ] == CollapsedBlock )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( entry != 0 )
|
||||
{
|
||||
ExpandBlock( block );
|
||||
if( Blocks[ block ]![ SubIdx( idx ) ] != entry )
|
||||
{
|
||||
Blocks[ block ]![ SubIdx( idx ) ] = entry;
|
||||
return true;
|
||||
}
|
||||
Functions.MemSet( myDataPtr, 0, BlockSize * EqdpEntrySize );
|
||||
}
|
||||
else
|
||||
{
|
||||
var array = Blocks[ block ];
|
||||
if( array != null )
|
||||
{
|
||||
array[ SubIdx( idx ) ] = entry;
|
||||
if( array.All( e => e == 0 ) )
|
||||
{
|
||||
CollapseBlock( block );
|
||||
Functions.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize );
|
||||
}
|
||||
|
||||
return true;
|
||||
myDataPtr += BlockSize;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public EqdpEntry GetEntry( ushort idx )
|
||||
public void Reset( IEnumerable< int > entries )
|
||||
{
|
||||
var block = BlockIdx( idx );
|
||||
var array = block < Blocks.Length ? Blocks[ block ] : null;
|
||||
return array?[ SubIdx( idx ) ] ?? 0;
|
||||
}
|
||||
|
||||
private ref EqdpEntry GetTrueEntry( ushort idx )
|
||||
foreach( var entry in entries )
|
||||
{
|
||||
var block = BlockIdx( idx );
|
||||
if( block >= TotalBlockCount )
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
ExpandBlock( block );
|
||||
var array = Blocks[ block ]!;
|
||||
return ref array[ SubIdx( idx ) ];
|
||||
}
|
||||
|
||||
private void WriteHeaders( BinaryWriter bw )
|
||||
{
|
||||
ushort offset = 0;
|
||||
foreach( var block in Blocks )
|
||||
{
|
||||
if( block == null )
|
||||
{
|
||||
bw.Write( CollapsedBlock );
|
||||
continue;
|
||||
}
|
||||
|
||||
bw.Write( offset );
|
||||
offset += BlockSize;
|
||||
this[ entry ] = GetDefault( entry );
|
||||
}
|
||||
}
|
||||
|
||||
private static void WritePadding( BinaryWriter bw, int paddingSize )
|
||||
public ExpandedEqdpFile( GenderRace raceCode, bool accessory )
|
||||
: base( CharacterUtility.EqdpIdx( raceCode, accessory ) )
|
||||
{
|
||||
var buffer = new byte[paddingSize];
|
||||
bw.Write( buffer, 0, paddingSize );
|
||||
var def = ( byte* )DefaultData.Data;
|
||||
var blockSize = *( ushort* )( def + IdentifierSize );
|
||||
var totalBlockCount = *( ushort* )( def + IdentifierSize + 2 );
|
||||
var totalBlockSize = blockSize * EqdpEntrySize;
|
||||
|
||||
DataOffset = IdentifierSize + PreambleSize + totalBlockCount * BlockHeaderSize;
|
||||
|
||||
var fullLength = DataOffset + totalBlockCount * totalBlockSize;
|
||||
fullLength += ( FileAlignment - ( Length & ( FileAlignment - 1 ) ) ) & ( FileAlignment - 1 );
|
||||
AllocateData( fullLength );
|
||||
Reset();
|
||||
}
|
||||
|
||||
private void WriteBlocks( BinaryWriter bw )
|
||||
public EqdpEntry GetDefault( int setIdx )
|
||||
=> GetDefault( Index, setIdx );
|
||||
|
||||
public static EqdpEntry GetDefault( int fileIdx, int setIdx )
|
||||
{
|
||||
foreach( var entry in Blocks.Where( block => block != null )
|
||||
.SelectMany( block => block! ) )
|
||||
var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ fileIdx ].Address;
|
||||
var blockSize = *( ushort* )( data + IdentifierSize );
|
||||
var totalBlockCount = *( ushort* )( data + IdentifierSize + 2 );
|
||||
|
||||
var blockIdx = setIdx / blockSize;
|
||||
if( blockIdx >= totalBlockCount )
|
||||
{
|
||||
bw.Write( ( ushort )entry );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public byte[] WriteBytes()
|
||||
var block = ( ( ushort* )( data + IdentifierSize + PreambleSize ) )[ blockIdx ];
|
||||
if( block == CollapsedBlock )
|
||||
{
|
||||
var dataSize = PreambleSize + IdentifierSize + BlockHeaderSize * TotalBlockCount + ExpandedBlockCount * BlockSize * EqdpEntrySize;
|
||||
var paddingSize = FileAlignment - ( dataSize & ( FileAlignment - 1 ) );
|
||||
using var mem =
|
||||
new MemoryStream( dataSize + paddingSize );
|
||||
using var bw = new BinaryWriter( mem );
|
||||
bw.Write( Identifier );
|
||||
bw.Write( BlockSize );
|
||||
bw.Write( TotalBlockCount );
|
||||
|
||||
WriteHeaders( bw );
|
||||
WriteBlocks( bw );
|
||||
WritePadding( bw, paddingSize );
|
||||
|
||||
return mem.ToArray();
|
||||
return 0;
|
||||
}
|
||||
|
||||
public EqdpFile( FileResource file )
|
||||
{
|
||||
file.Reader.BaseStream.Seek( 0, SeekOrigin.Begin );
|
||||
|
||||
Identifier = file.Reader.ReadUInt16();
|
||||
BlockSize = file.Reader.ReadUInt16();
|
||||
TotalBlockCount = file.Reader.ReadUInt16();
|
||||
Blocks = new EqdpEntry[TotalBlockCount][];
|
||||
ExpandedBlockCount = 0;
|
||||
for( var i = 0; i < TotalBlockCount; ++i )
|
||||
{
|
||||
var offset = file.Reader.ReadUInt16();
|
||||
if( offset != CollapsedBlock )
|
||||
{
|
||||
ExpandBlock( ( ushort )i );
|
||||
}
|
||||
var blockData = ( EqdpEntry* )( data + IdentifierSize + PreambleSize + totalBlockCount * 2 + block );
|
||||
return *( blockData + blockIdx % blockSize );
|
||||
}
|
||||
|
||||
foreach( var array in Blocks.Where( array => array != null ) )
|
||||
{
|
||||
for( var i = 0; i < BlockSize; ++i )
|
||||
{
|
||||
array![ i ] = ( EqdpEntry )file.Reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static EqdpEntry GetDefault( GenderRace raceCode, bool accessory, int setIdx )
|
||||
=> GetDefault( CharacterUtility.EqdpIdx( raceCode, accessory ), setIdx );
|
||||
}
|
||||
|
|
@ -1,216 +0,0 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Lumina.Data;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
// EQP Structure:
|
||||
// 64 x [Block collapsed or not bit]
|
||||
// 159 x [EquipmentParameter:ulong]
|
||||
// (CountSetBits(Block Collapsed or not) - 1) x 160 x [EquipmentParameter:ulong]
|
||||
// Item 0 does not exist and is sent to Item 1 instead.
|
||||
public sealed class EqpFile : EqpGmpBase
|
||||
{
|
||||
private readonly EqpEntry[]?[] _entries = new EqpEntry[TotalBlockCount][];
|
||||
|
||||
protected override ulong ControlBlock
|
||||
{
|
||||
get => ( ulong )_entries[ 0 ]![ 0 ];
|
||||
set => _entries[ 0 ]![ 0 ] = ( EqpEntry )value;
|
||||
}
|
||||
|
||||
private EqpFile( EqpFile clone )
|
||||
{
|
||||
ExpandedBlockCount = clone.ExpandedBlockCount;
|
||||
_entries = clone.Clone( clone._entries );
|
||||
}
|
||||
|
||||
public byte[] WriteBytes()
|
||||
=> WriteBytes( _entries, e => ( ulong )e );
|
||||
|
||||
public EqpFile Clone()
|
||||
=> new( this );
|
||||
|
||||
public EqpFile( FileResource file )
|
||||
=> ReadFile( _entries, file, I => ( EqpEntry )I );
|
||||
|
||||
public EqpEntry GetEntry( ushort setId )
|
||||
=> GetEntry( _entries, setId, ( EqpEntry )0 );
|
||||
|
||||
public bool SetEntry( ushort setId, EqpEntry entry )
|
||||
=> SetEntry( _entries, setId, entry, e => e == 0, ( e1, e2 ) => e1 == e2 );
|
||||
|
||||
public ref EqpEntry this[ ushort setId ]
|
||||
=> ref GetTrueEntry( _entries, setId );
|
||||
}
|
||||
|
||||
public class EqpGmpBase
|
||||
{
|
||||
protected const ushort ParameterSize = 8;
|
||||
protected const ushort BlockSize = 160;
|
||||
protected const ushort TotalBlockCount = 64;
|
||||
|
||||
protected int ExpandedBlockCount { get; set; }
|
||||
|
||||
private static int BlockIdx( ushort idx )
|
||||
=> idx / BlockSize;
|
||||
|
||||
private static int SubIdx( ushort idx )
|
||||
=> idx % BlockSize;
|
||||
|
||||
protected virtual ulong ControlBlock { get; set; }
|
||||
|
||||
protected T[]?[] Clone< T >( T[]?[] clone )
|
||||
{
|
||||
var ret = new T[TotalBlockCount][];
|
||||
for( var i = 0; i < TotalBlockCount; ++i )
|
||||
{
|
||||
if( clone[ i ] != null )
|
||||
{
|
||||
ret[ i ] = ( T[] )clone[ i ]!.Clone();
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
protected EqpGmpBase()
|
||||
{ }
|
||||
|
||||
protected bool ExpandBlock< T >( T[]?[] blocks, int idx )
|
||||
{
|
||||
if( idx >= TotalBlockCount || blocks[ idx ] != null )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
blocks[ idx ] = new T[BlockSize];
|
||||
++ExpandedBlockCount;
|
||||
ControlBlock |= 1ul << idx;
|
||||
return true;
|
||||
}
|
||||
|
||||
protected bool CollapseBlock< T >( T[]?[] blocks, int idx )
|
||||
{
|
||||
if( idx >= TotalBlockCount || blocks[ idx ] == null )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
blocks[ idx ] = null;
|
||||
--ExpandedBlockCount;
|
||||
ControlBlock &= ~( 1ul << idx );
|
||||
return true;
|
||||
}
|
||||
|
||||
protected T GetEntry< T >( T[]?[] blocks, ushort idx, T defaultEntry )
|
||||
{
|
||||
// Skip the zeroth item.
|
||||
idx = idx == 0 ? ( ushort )1 : idx;
|
||||
var block = BlockIdx( idx );
|
||||
var array = block < blocks.Length ? blocks[ block ] : null;
|
||||
if( array == null )
|
||||
{
|
||||
return defaultEntry;
|
||||
}
|
||||
|
||||
return array[ SubIdx( idx ) ];
|
||||
}
|
||||
|
||||
protected ref T GetTrueEntry< T >( T[]?[] blocks, ushort idx )
|
||||
{
|
||||
// Skip the zeroth item.
|
||||
idx = idx == 0 ? ( ushort )1 : idx;
|
||||
var block = BlockIdx( idx );
|
||||
if( block >= TotalBlockCount )
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
ExpandBlock( blocks, block );
|
||||
var array = blocks[ block ]!;
|
||||
return ref array[ SubIdx( idx ) ];
|
||||
}
|
||||
|
||||
protected byte[] WriteBytes< T >( T[]?[] blocks, Func< T, ulong > transform )
|
||||
{
|
||||
var dataSize = ExpandedBlockCount * BlockSize * ParameterSize;
|
||||
using var mem = new MemoryStream( dataSize );
|
||||
using var bw = new BinaryWriter( mem );
|
||||
|
||||
foreach( var parameter in blocks.Where( array => array != null )
|
||||
.SelectMany( array => array! ) )
|
||||
{
|
||||
bw.Write( transform( parameter ) );
|
||||
}
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
|
||||
protected void ReadFile< T >( T[]?[] blocks, FileResource file, Func< ulong, T > convert )
|
||||
{
|
||||
file.Reader.BaseStream.Seek( 0, SeekOrigin.Begin );
|
||||
var blockBits = file.Reader.ReadUInt64();
|
||||
// reset to 0 and just put the bitmask in the first block
|
||||
// item 0 is not accessible and it simplifies printing.
|
||||
file.Reader.BaseStream.Seek( 0, SeekOrigin.Begin );
|
||||
|
||||
ExpandedBlockCount = 0;
|
||||
for( var i = 0; i < TotalBlockCount; ++i )
|
||||
{
|
||||
var flag = 1ul << i;
|
||||
if( ( blockBits & flag ) != flag )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
++ExpandedBlockCount;
|
||||
|
||||
var tmp = new T[BlockSize];
|
||||
for( var j = 0; j < BlockSize; ++j )
|
||||
{
|
||||
tmp[ j ] = convert( file.Reader.ReadUInt64() );
|
||||
}
|
||||
|
||||
blocks[ i ] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool SetEntry< T >( T[]?[] blocks, ushort idx, T entry, Func< T, bool > isDefault, Func< T, T, bool > isEqual )
|
||||
{
|
||||
var block = BlockIdx( idx );
|
||||
if( block >= TotalBlockCount )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !isDefault( entry ) )
|
||||
{
|
||||
ExpandBlock( blocks, block );
|
||||
if( !isEqual( entry, blocks[ block ]![ SubIdx( idx ) ] ) )
|
||||
{
|
||||
blocks[ block ]![ SubIdx( idx ) ] = entry;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var array = blocks[ block ];
|
||||
if( array != null )
|
||||
{
|
||||
array[ SubIdx( idx ) ] = entry;
|
||||
if( array.All( e => e!.Equals( 0ul ) ) )
|
||||
{
|
||||
CollapseBlock( blocks, block );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
164
Penumbra/Meta/Files/EqpGmpFile.cs
Normal file
164
Penumbra/Meta/Files/EqpGmpFile.cs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Interop.Structs;
|
||||
|
||||
namespace Penumbra.Meta.Files;
|
||||
|
||||
// EQP/GMP Structure:
|
||||
// 64 x [Block collapsed or not bit]
|
||||
// 159 x [EquipmentParameter:ulong]
|
||||
// (CountSetBits(Block Collapsed or not) - 1) x 160 x [EquipmentParameter:ulong]
|
||||
// Item 0 does not exist and is sent to Item 1 instead.
|
||||
public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
||||
{
|
||||
protected const int BlockSize = 160;
|
||||
protected const int NumBlocks = 64;
|
||||
protected const int EntrySize = 8;
|
||||
protected const int MaxSize = BlockSize * NumBlocks * EntrySize;
|
||||
|
||||
public const int Count = BlockSize * NumBlocks;
|
||||
|
||||
public ulong ControlBlock
|
||||
=> *( ulong* )Data;
|
||||
|
||||
protected T Get< T >( int idx ) where T : unmanaged
|
||||
{
|
||||
return idx switch
|
||||
{
|
||||
>= Count => throw new IndexOutOfRangeException(),
|
||||
<= 1 => *( ( T* )Data + 1 ),
|
||||
_ => *( ( T* )Data + idx ),
|
||||
};
|
||||
}
|
||||
|
||||
protected void Set< T >( int idx, T value ) where T : unmanaged
|
||||
{
|
||||
idx = idx switch
|
||||
{
|
||||
>= Count => throw new IndexOutOfRangeException(),
|
||||
<= 0 => 1,
|
||||
_ => idx,
|
||||
};
|
||||
|
||||
*( ( T* )Data + idx ) = value;
|
||||
}
|
||||
|
||||
protected virtual void SetEmptyBlock( int idx )
|
||||
{
|
||||
Functions.MemSet( Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize );
|
||||
}
|
||||
|
||||
public sealed override void Reset()
|
||||
{
|
||||
var ptr = ( byte* )DefaultData.Data;
|
||||
var controlBlock = *( ulong* )ptr;
|
||||
*( ulong* )ptr = ulong.MaxValue;
|
||||
for( var i = 0; i < 64; ++i )
|
||||
{
|
||||
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
|
||||
if( !collapsed )
|
||||
{
|
||||
Functions.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + i * BlockSize * EntrySize, BlockSize * EntrySize );
|
||||
}
|
||||
else
|
||||
{
|
||||
SetEmptyBlock( i );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ExpandedEqpGmpBase( bool gmp )
|
||||
: base( gmp ? CharacterUtility.GmpIdx : CharacterUtility.EqpIdx )
|
||||
{
|
||||
AllocateData( MaxSize );
|
||||
Reset();
|
||||
}
|
||||
|
||||
protected static T GetDefault< T >( int fileIdx, int setIdx, T def ) where T : unmanaged
|
||||
{
|
||||
var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ fileIdx ].Address;
|
||||
if( setIdx == 0 )
|
||||
{
|
||||
setIdx = 1;
|
||||
}
|
||||
|
||||
var blockIdx = setIdx / BlockSize;
|
||||
if( blockIdx >= NumBlocks )
|
||||
{
|
||||
return def;
|
||||
}
|
||||
|
||||
var control = *( ulong* )data;
|
||||
var blockBit = 1ul << blockIdx;
|
||||
if( ( control & blockBit ) == 0 )
|
||||
{
|
||||
return def;
|
||||
}
|
||||
|
||||
var count = BitOperations.PopCount( control & ( blockBit - 1 ) );
|
||||
var idx = setIdx % BlockSize;
|
||||
var ptr = ( T* )data + BlockSize * count + idx;
|
||||
return *ptr;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ExpandedEqpFile : ExpandedEqpGmpBase
|
||||
{
|
||||
public ExpandedEqpFile()
|
||||
: base( false )
|
||||
{ }
|
||||
|
||||
public EqpEntry this[ int idx ]
|
||||
{
|
||||
get => Get< EqpEntry >( idx );
|
||||
set => Set( idx, value );
|
||||
}
|
||||
|
||||
public static EqpEntry GetDefault( int setIdx )
|
||||
=> GetDefault( CharacterUtility.EqpIdx, setIdx, Eqp.DefaultEntry );
|
||||
|
||||
protected override unsafe void SetEmptyBlock( int idx )
|
||||
{
|
||||
var blockPtr = ( ulong* )( Data + idx * BlockSize * EntrySize );
|
||||
var endPtr = blockPtr + BlockSize;
|
||||
for( var ptr = blockPtr; ptr < endPtr; ++ptr )
|
||||
{
|
||||
*ptr = ( ulong )Eqp.DefaultEntry;
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset( IEnumerable< int > entries )
|
||||
{
|
||||
foreach( var entry in entries )
|
||||
{
|
||||
this[ entry ] = GetDefault( entry );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ExpandedGmpFile : ExpandedEqpGmpBase
|
||||
{
|
||||
public ExpandedGmpFile()
|
||||
: base( true )
|
||||
{ }
|
||||
|
||||
public GmpEntry this[ int idx ]
|
||||
{
|
||||
get => Get< GmpEntry >( idx );
|
||||
set => Set( idx, value );
|
||||
}
|
||||
|
||||
public static GmpEntry GetDefault( int setIdx )
|
||||
=> GetDefault( CharacterUtility.GmpIdx, setIdx, GmpEntry.Default );
|
||||
|
||||
public void Reset( IEnumerable< int > entries )
|
||||
{
|
||||
foreach( var entry in entries )
|
||||
{
|
||||
this[ entry ] = GetDefault( entry );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Lumina.Data;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Meta.Files;
|
||||
|
||||
|
|
@ -11,152 +11,192 @@ namespace Penumbra.Meta.Files;
|
|||
// Apparently entries need to be sorted.
|
||||
// #NumEntries x [SetId : UInt16] [RaceId : UInt16]
|
||||
// #NumEntries x [SkeletonId : UInt16]
|
||||
public class EstFile
|
||||
public sealed unsafe class EstFile : MetaBaseFile
|
||||
{
|
||||
private const ushort EntryDescSize = 4;
|
||||
private const ushort EntrySize = 2;
|
||||
private const int IncreaseSize = 100;
|
||||
|
||||
private readonly SortedList< GenderRace, SortedList< ushort, ushort > > _entries = new();
|
||||
private uint NumEntries { get; set; }
|
||||
public int Count
|
||||
=> *( int* )Data;
|
||||
|
||||
private EstFile( EstFile clone )
|
||||
private int Size
|
||||
=> 4 + Count * ( EntryDescSize + EntrySize );
|
||||
|
||||
public enum EstEntryChange
|
||||
{
|
||||
NumEntries = clone.NumEntries;
|
||||
_entries = new SortedList< GenderRace, SortedList< ushort, ushort > >( clone._entries.Count );
|
||||
foreach( var (genderRace, data) in clone._entries )
|
||||
{
|
||||
var dict = new SortedList< ushort, ushort >( data.Count );
|
||||
foreach( var (setId, value) in data )
|
||||
{
|
||||
dict.Add( setId, value );
|
||||
Unchanged,
|
||||
Changed,
|
||||
Added,
|
||||
Removed,
|
||||
}
|
||||
|
||||
_entries.Add( genderRace, dict );
|
||||
}
|
||||
}
|
||||
|
||||
public EstFile Clone()
|
||||
=> new(this);
|
||||
|
||||
private bool DeleteEntry( GenderRace gr, ushort setId )
|
||||
public ushort this[ GenderRace genderRace, ushort setId ]
|
||||
{
|
||||
if( !_entries.TryGetValue( gr, out var setDict ) )
|
||||
get
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !setDict.ContainsKey( setId ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
setDict.Remove( setId );
|
||||
if( setDict.Count == 0 )
|
||||
{
|
||||
_entries.Remove( gr );
|
||||
}
|
||||
|
||||
--NumEntries;
|
||||
return true;
|
||||
}
|
||||
|
||||
private (bool, bool) AddEntry( GenderRace gr, ushort setId, ushort entry )
|
||||
{
|
||||
if( !_entries.TryGetValue( gr, out var setDict ) )
|
||||
{
|
||||
_entries[ gr ] = new SortedList< ushort, ushort >();
|
||||
setDict = _entries[ gr ];
|
||||
}
|
||||
|
||||
if( setDict.TryGetValue( setId, out var oldEntry ) )
|
||||
{
|
||||
if( oldEntry == entry )
|
||||
{
|
||||
return ( false, false );
|
||||
}
|
||||
|
||||
setDict[ setId ] = entry;
|
||||
return ( false, true );
|
||||
}
|
||||
|
||||
setDict[ setId ] = entry;
|
||||
return ( true, true );
|
||||
}
|
||||
|
||||
public bool SetEntry( GenderRace gr, ushort setId, ushort entry )
|
||||
{
|
||||
if( entry == 0 )
|
||||
{
|
||||
return DeleteEntry( gr, setId );
|
||||
}
|
||||
|
||||
var (addedNew, changed) = AddEntry( gr, setId, entry );
|
||||
if( !addedNew )
|
||||
{
|
||||
return changed;
|
||||
}
|
||||
|
||||
++NumEntries;
|
||||
return true;
|
||||
}
|
||||
|
||||
public ushort GetEntry( GenderRace gr, ushort setId )
|
||||
{
|
||||
if( !_entries.TryGetValue( gr, out var setDict ) )
|
||||
var (idx, exists) = FindEntry( genderRace, setId );
|
||||
if( !exists )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return !setDict.TryGetValue( setId, out var entry ) ? ( ushort )0 : entry;
|
||||
return *( ushort* )( Data + EntryDescSize * ( Count + 1 ) + EntrySize * idx );
|
||||
}
|
||||
set => SetEntry( genderRace, setId, value );
|
||||
}
|
||||
|
||||
public byte[] WriteBytes()
|
||||
private void InsertEntry( int idx, GenderRace genderRace, ushort setId, ushort skeletonId )
|
||||
{
|
||||
using MemoryStream mem = new(( int )( 4 + ( EntryDescSize + EntrySize ) * NumEntries ));
|
||||
using BinaryWriter bw = new(mem);
|
||||
|
||||
bw.Write( NumEntries );
|
||||
foreach( var kvp1 in _entries )
|
||||
if( Length < Size + EntryDescSize + EntrySize )
|
||||
{
|
||||
foreach( var kvp2 in kvp1.Value )
|
||||
var data = Data;
|
||||
var length = Length;
|
||||
AllocateData( length + IncreaseSize * ( EntryDescSize + EntrySize ) );
|
||||
Functions.MemCpyUnchecked( Data, data, length );
|
||||
Functions.MemSet( Data + length, 0, IncreaseSize * ( EntryDescSize + EntrySize ) );
|
||||
GC.RemoveMemoryPressure( length );
|
||||
Marshal.FreeHGlobal( ( IntPtr )data );
|
||||
}
|
||||
|
||||
var control = ( uint* )( Data + 4 );
|
||||
var entries = ( ushort* )( Data + 4 * ( Count + 1 ) );
|
||||
|
||||
for( var i = Count; i > idx; --i )
|
||||
{
|
||||
bw.Write( kvp2.Key );
|
||||
bw.Write( ( ushort )kvp1.Key );
|
||||
}
|
||||
*( entries + i + 2 ) = entries[ i - 1 ];
|
||||
}
|
||||
|
||||
foreach( var kvp2 in _entries.SelectMany( kvp1 => kvp1.Value ) )
|
||||
for( var i = idx - 1; i >= 0; --i )
|
||||
{
|
||||
bw.Write( kvp2.Value );
|
||||
*( entries + i + 2 ) = entries[ i ];
|
||||
}
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
|
||||
|
||||
public EstFile( FileResource file )
|
||||
for( var i = Count; i > idx; --i )
|
||||
{
|
||||
file.Reader.BaseStream.Seek( 0, SeekOrigin.Begin );
|
||||
NumEntries = file.Reader.ReadUInt32();
|
||||
*( control + i ) = control[ i - 1 ];
|
||||
}
|
||||
|
||||
var currentEntryDescOffset = 4;
|
||||
var currentEntryOffset = 4 + EntryDescSize * NumEntries;
|
||||
for( var i = 0; i < NumEntries; ++i )
|
||||
*( int* )Data = Count + 1;
|
||||
|
||||
*( ushort* )control = setId;
|
||||
*( ( ushort* )control + 1 ) = ( ushort )genderRace;
|
||||
control[ idx ] = skeletonId;
|
||||
}
|
||||
|
||||
private void RemoveEntry( int idx )
|
||||
{
|
||||
file.Reader.BaseStream.Seek( currentEntryDescOffset, SeekOrigin.Begin );
|
||||
currentEntryDescOffset += EntryDescSize;
|
||||
var setId = file.Reader.ReadUInt16();
|
||||
var raceId = ( GenderRace )file.Reader.ReadUInt16();
|
||||
if( !raceId.IsValid() )
|
||||
var entries = ( ushort* )( Data + 4 * Count );
|
||||
var control = ( uint* )( Data + 4 );
|
||||
*( int* )Data = Count - 1;
|
||||
var count = Count;
|
||||
|
||||
for( var i = idx; i < count; ++i )
|
||||
{
|
||||
continue;
|
||||
control[ i ] = control[ i + 1 ];
|
||||
}
|
||||
|
||||
file.Reader.BaseStream.Seek( currentEntryOffset, SeekOrigin.Begin );
|
||||
currentEntryOffset += EntrySize;
|
||||
var entry = file.Reader.ReadUInt16();
|
||||
for( var i = 0; i < count; ++i )
|
||||
{
|
||||
entries[ i ] = entries[ i + 1 ];
|
||||
}
|
||||
|
||||
AddEntry( raceId, setId, entry );
|
||||
entries[ count ] = 0;
|
||||
entries[ count + 1 ] = 0;
|
||||
entries[ count + 2 ] = 0;
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Sequential, Size = 4 )]
|
||||
private struct Info : IComparable< Info >
|
||||
{
|
||||
public readonly ushort SetId;
|
||||
public readonly GenderRace GenderRace;
|
||||
|
||||
public Info( GenderRace gr, ushort setId )
|
||||
{
|
||||
GenderRace = gr;
|
||||
SetId = setId;
|
||||
}
|
||||
|
||||
public int CompareTo( Info other )
|
||||
{
|
||||
var genderRaceComparison = GenderRace.CompareTo( other.GenderRace );
|
||||
return genderRaceComparison != 0 ? genderRaceComparison : SetId.CompareTo( other.SetId );
|
||||
}
|
||||
}
|
||||
|
||||
private static (int, bool) FindEntry( ReadOnlySpan< Info > data, GenderRace genderRace, ushort setId )
|
||||
{
|
||||
var idx = data.BinarySearch( new Info( genderRace, setId ) );
|
||||
return idx < 0 ? ( ~idx, false ) : ( idx, true );
|
||||
}
|
||||
|
||||
private (int, bool) FindEntry( GenderRace genderRace, ushort setId )
|
||||
{
|
||||
var span = new ReadOnlySpan< Info >( Data + 4, Count );
|
||||
return FindEntry( span, genderRace, setId );
|
||||
}
|
||||
|
||||
public EstEntryChange SetEntry( GenderRace genderRace, ushort setId, ushort skeletonId )
|
||||
{
|
||||
var (idx, exists) = FindEntry( genderRace, setId );
|
||||
if( exists )
|
||||
{
|
||||
var value = *( ushort* )( Data + 4 * ( Count + 1 ) + 2 * idx );
|
||||
if( value == skeletonId )
|
||||
{
|
||||
return EstEntryChange.Unchanged;
|
||||
}
|
||||
|
||||
if( skeletonId == 0 )
|
||||
{
|
||||
RemoveEntry( idx );
|
||||
return EstEntryChange.Removed;
|
||||
}
|
||||
|
||||
*( ushort* )( Data + 4 * ( Count + 1 ) + 2 * idx ) = skeletonId;
|
||||
return EstEntryChange.Changed;
|
||||
}
|
||||
|
||||
if( skeletonId == 0 )
|
||||
{
|
||||
return EstEntryChange.Unchanged;
|
||||
}
|
||||
|
||||
InsertEntry( idx, genderRace, setId, skeletonId );
|
||||
return EstEntryChange.Added;
|
||||
}
|
||||
|
||||
public override void Reset()
|
||||
{
|
||||
var (d, length) = DefaultData;
|
||||
var data = ( byte* )d;
|
||||
Functions.MemCpyUnchecked( Data, data, length );
|
||||
Functions.MemSet( Data + length, 0, Length - length );
|
||||
}
|
||||
|
||||
public EstFile( EstManipulation.EstType estType )
|
||||
: base( ( int )estType )
|
||||
{
|
||||
var length = DefaultData.Length;
|
||||
AllocateData( length + IncreaseSize * ( EntryDescSize + EntrySize ) );
|
||||
Reset();
|
||||
}
|
||||
|
||||
public ushort GetDefault( GenderRace genderRace, ushort setId )
|
||||
=> GetDefault( ( EstManipulation.EstType )Index, genderRace, setId );
|
||||
|
||||
public static ushort GetDefault( EstManipulation.EstType estType, GenderRace genderRace, ushort setId )
|
||||
{
|
||||
var data = ( byte* )Penumbra.CharacterUtility.DefaultResources[ ( int )estType ].Address;
|
||||
var count = *( int* )data;
|
||||
var span = new ReadOnlySpan< Info >( data + 4, count );
|
||||
var (idx, found) = FindEntry( span, genderRace, setId );
|
||||
if( !found )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return *( ushort* )( data + 4 + count * EntryDescSize + idx * EntrySize );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
using Lumina.Data;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
// GmpFiles use the same structure as Eqp Files.
|
||||
// Entries are also one ulong.
|
||||
public sealed class GmpFile : EqpGmpBase
|
||||
{
|
||||
private readonly GmpEntry[]?[] _entries = new GmpEntry[TotalBlockCount][];
|
||||
|
||||
protected override ulong ControlBlock
|
||||
{
|
||||
get => _entries[ 0 ]![ 0 ];
|
||||
set => _entries[ 0 ]![ 0 ] = ( GmpEntry )value;
|
||||
}
|
||||
|
||||
private GmpFile( GmpFile clone )
|
||||
{
|
||||
ExpandedBlockCount = clone.ExpandedBlockCount;
|
||||
_entries = clone.Clone( clone._entries );
|
||||
}
|
||||
|
||||
public byte[] WriteBytes()
|
||||
=> WriteBytes( _entries, e => ( ulong )e );
|
||||
|
||||
public GmpFile Clone()
|
||||
=> new( this );
|
||||
|
||||
public GmpFile( FileResource file )
|
||||
=> ReadFile( _entries, file, i => ( GmpEntry )i );
|
||||
|
||||
public GmpEntry GetEntry( ushort setId )
|
||||
=> GetEntry( _entries, setId, ( GmpEntry )0 );
|
||||
|
||||
public bool SetEntry( ushort setId, GmpEntry entry )
|
||||
=> SetEntry( _entries, setId, entry, e => e == 0, ( e1, e2 ) => e1 == e2 );
|
||||
|
||||
public ref GmpEntry this[ ushort setId ]
|
||||
=> ref GetTrueEntry( _entries, setId );
|
||||
}
|
||||
}
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Lumina.Data.Files;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
public class InvalidImcVariantException : ArgumentOutOfRangeException
|
||||
{
|
||||
public InvalidImcVariantException()
|
||||
: base( "Trying to manipulate invalid variant." )
|
||||
{ }
|
||||
}
|
||||
|
||||
// Imc files are already supported in Lumina, but changing the provided data is not supported.
|
||||
// We use reflection and extension methods to support changing the data of a given Imc file.
|
||||
public static class ImcExtensions
|
||||
{
|
||||
public static ulong ToInteger( this ImcFile.ImageChangeData imc )
|
||||
{
|
||||
ulong ret = imc.MaterialId;
|
||||
ret |= ( ulong )imc.DecalId << 8;
|
||||
ret |= ( ulong )imc.AttributeMask << 16;
|
||||
ret |= ( ulong )imc.SoundId << 16;
|
||||
ret |= ( ulong )imc.VfxId << 32;
|
||||
ret |= ( ulong )imc.ActualMaterialAnimationId() << 40;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static byte ActualMaterialAnimationId( this ImcFile.ImageChangeData imc )
|
||||
{
|
||||
var tmp = imc.GetType().GetField( "_MaterialAnimationIdMask",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
|
||||
return ( byte )( tmp?.GetValue( imc ) ?? 0 );
|
||||
}
|
||||
|
||||
public static ImcFile.ImageChangeData FromValues( byte materialId, byte decalId, ushort attributeMask, byte soundId, byte vfxId,
|
||||
byte materialAnimationId )
|
||||
{
|
||||
var ret = new ImcFile.ImageChangeData()
|
||||
{
|
||||
DecalId = decalId,
|
||||
MaterialId = materialId,
|
||||
VfxId = vfxId,
|
||||
};
|
||||
ret.GetType().GetField( "_AttributeAndSound",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance )!
|
||||
.SetValue( ret, ( ushort )( ( attributeMask & 0x3FF ) | ( soundId << 10 ) ) );
|
||||
ret.GetType().GetField( "_AttributeAndSound",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance )!.SetValue( ret, materialAnimationId );
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static bool Equal( this ImcFile.ImageChangeData lhs, ImcFile.ImageChangeData rhs )
|
||||
=> lhs.MaterialId == rhs.MaterialId
|
||||
&& lhs.DecalId == rhs.DecalId
|
||||
&& lhs.AttributeMask == rhs.AttributeMask
|
||||
&& lhs.SoundId == rhs.SoundId
|
||||
&& lhs.VfxId == rhs.VfxId
|
||||
&& lhs.MaterialAnimationId == rhs.MaterialAnimationId;
|
||||
|
||||
private static void WriteBytes( this ImcFile.ImageChangeData variant, BinaryWriter bw )
|
||||
{
|
||||
bw.Write( variant.MaterialId );
|
||||
bw.Write( variant.DecalId );
|
||||
bw.Write( ( ushort )( variant.AttributeMask | variant.SoundId ) );
|
||||
bw.Write( variant.VfxId );
|
||||
bw.Write( variant.ActualMaterialAnimationId() );
|
||||
}
|
||||
|
||||
public static byte[] WriteBytes( this ImcFile file )
|
||||
{
|
||||
var parts = file.PartMask == 31 ? 5 : 1;
|
||||
var dataSize = 4 + 6 * parts * ( 1 + file.Count );
|
||||
using var mem = new MemoryStream( dataSize );
|
||||
using var bw = new BinaryWriter( mem );
|
||||
|
||||
bw.Write( file.Count );
|
||||
bw.Write( file.PartMask );
|
||||
for( var i = 0; i < parts; ++i )
|
||||
{
|
||||
file.GetDefaultVariant( i ).WriteBytes( bw );
|
||||
}
|
||||
|
||||
for( var i = 0; i < file.Count; ++i )
|
||||
{
|
||||
for( var j = 0; j < parts; ++j )
|
||||
{
|
||||
file.GetVariant( j, i ).WriteBytes( bw );
|
||||
}
|
||||
}
|
||||
|
||||
return mem.ToArray();
|
||||
}
|
||||
|
||||
public static ref ImcFile.ImageChangeData GetValue( this ImcFile file, MetaManipulation manipulation )
|
||||
{
|
||||
var parts = file.GetParts();
|
||||
var imc = manipulation.ImcIdentifier;
|
||||
var idx = 0;
|
||||
if( imc.ObjectType == ObjectType.Equipment || imc.ObjectType == ObjectType.Accessory )
|
||||
{
|
||||
idx = imc.EquipSlot switch
|
||||
{
|
||||
EquipSlot.Head => 0,
|
||||
EquipSlot.Ears => 0,
|
||||
EquipSlot.Body => 1,
|
||||
EquipSlot.Neck => 1,
|
||||
EquipSlot.Hands => 2,
|
||||
EquipSlot.Wrists => 2,
|
||||
EquipSlot.Legs => 3,
|
||||
EquipSlot.RFinger => 3,
|
||||
EquipSlot.Feet => 4,
|
||||
EquipSlot.LFinger => 4,
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
if( imc.Variant == 0 )
|
||||
{
|
||||
return ref parts[ idx ].DefaultVariant;
|
||||
}
|
||||
|
||||
if( imc.Variant > parts[ idx ].Variants.Length )
|
||||
{
|
||||
throw new InvalidImcVariantException();
|
||||
}
|
||||
|
||||
return ref parts[ idx ].Variants[ imc.Variant - 1 ];
|
||||
}
|
||||
|
||||
public static ImcFile Clone( this ImcFile file )
|
||||
{
|
||||
var ret = new ImcFile
|
||||
{
|
||||
Count = file.Count,
|
||||
PartMask = file.PartMask,
|
||||
};
|
||||
var parts = file.GetParts().Select( p => new ImcFile.ImageChangeParts()
|
||||
{
|
||||
DefaultVariant = p.DefaultVariant,
|
||||
Variants = ( ImcFile.ImageChangeData[] )p.Variants.Clone(),
|
||||
} ).ToArray();
|
||||
var prop = ret.GetType().GetField( "Parts", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance );
|
||||
prop!.SetValue( ret, parts );
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
166
Penumbra/Meta/Files/ImcFile.cs
Normal file
166
Penumbra/Meta/Files/ImcFile.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Logging;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.Meta.Files;
|
||||
|
||||
public struct ImcEntry : IEquatable< ImcEntry >
|
||||
{
|
||||
public byte MaterialId;
|
||||
public byte DecalId;
|
||||
private ushort _attributeAndSound;
|
||||
public byte VfxId;
|
||||
public byte MaterialAnimationId;
|
||||
|
||||
public ushort AttributeMask
|
||||
=> ( ushort )( _attributeAndSound & 0x3FF );
|
||||
|
||||
public byte SoundId
|
||||
=> ( byte )( _attributeAndSound >> 10 );
|
||||
|
||||
public bool Equals( ImcEntry other )
|
||||
=> MaterialId == other.MaterialId
|
||||
&& DecalId == other.DecalId
|
||||
&& _attributeAndSound == other._attributeAndSound
|
||||
&& VfxId == other.VfxId
|
||||
&& MaterialAnimationId == other.MaterialAnimationId;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is ImcEntry other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine( MaterialId, DecalId, _attributeAndSound, VfxId, MaterialAnimationId );
|
||||
}
|
||||
|
||||
public unsafe class ImcFile : MetaBaseFile
|
||||
{
|
||||
private const int PreambleSize = 4;
|
||||
|
||||
public int ActualLength
|
||||
=> NumParts * sizeof( ImcEntry ) * ( Count + 1 ) + PreambleSize;
|
||||
|
||||
public int Count
|
||||
=> *( ushort* )Data;
|
||||
|
||||
public ushort PartMask
|
||||
=> *( ushort* )( Data + 2 );
|
||||
|
||||
public readonly int NumParts;
|
||||
public readonly Utf8GamePath Path;
|
||||
|
||||
public ImcEntry* DefaultPartPtr( int partIdx )
|
||||
{
|
||||
var flag = 1 << partIdx;
|
||||
if( ( PartMask & flag ) == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ( ImcEntry* )( Data + PreambleSize ) + partIdx;
|
||||
}
|
||||
|
||||
public ImcEntry* VariantPtr( int partIdx, int variantIdx )
|
||||
{
|
||||
var flag = 1 << partIdx;
|
||||
if( ( PartMask & flag ) == 0 || variantIdx >= Count )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var numParts = NumParts;
|
||||
var ptr = ( ImcEntry* )( Data + PreambleSize );
|
||||
ptr += numParts;
|
||||
ptr += variantIdx * numParts;
|
||||
ptr += partIdx;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public static int PartIndex( EquipSlot slot )
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.Head => 0,
|
||||
EquipSlot.Ears => 0,
|
||||
EquipSlot.Body => 1,
|
||||
EquipSlot.Neck => 1,
|
||||
EquipSlot.Hands => 2,
|
||||
EquipSlot.Wrists => 2,
|
||||
EquipSlot.Legs => 3,
|
||||
EquipSlot.RFinger => 3,
|
||||
EquipSlot.Feet => 4,
|
||||
EquipSlot.LFinger => 4,
|
||||
_ => 0,
|
||||
};
|
||||
|
||||
public bool EnsureVariantCount( int numVariants )
|
||||
{
|
||||
if( numVariants <= Count )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var numParts = NumParts;
|
||||
if( ActualLength > Length )
|
||||
{
|
||||
PluginLog.Warning( "Adding too many variants to IMC, size exceeded." );
|
||||
return false;
|
||||
}
|
||||
|
||||
var defaultPtr = ( ImcEntry* )( Data + PreambleSize );
|
||||
var endPtr = defaultPtr + ( numVariants + 1 ) * numParts;
|
||||
for( var ptr = defaultPtr + numParts; ptr < endPtr; ptr += numParts )
|
||||
{
|
||||
Functions.MemCpyUnchecked( ptr, defaultPtr, numParts * sizeof( ImcEntry ) );
|
||||
}
|
||||
|
||||
PluginLog.Verbose( "Expanded imc from {Count} to {NewCount} variants.", Count, numVariants );
|
||||
*( ushort* )Count = ( ushort )numVariants;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SetEntry( int partIdx, int variantIdx, ImcEntry entry )
|
||||
{
|
||||
var numParts = NumParts;
|
||||
if( partIdx >= numParts )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsureVariantCount( variantIdx + 1 );
|
||||
|
||||
var variantPtr = VariantPtr( partIdx, variantIdx );
|
||||
if( variantPtr == null )
|
||||
{
|
||||
PluginLog.Error( "Error during expansion of imc file." );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( variantPtr->Equals( entry ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
*variantPtr = entry;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public ImcFile( Utf8GamePath path )
|
||||
: base( 0 )
|
||||
{
|
||||
var file = Dalamud.GameData.GetFile( path.ToString() );
|
||||
if( file == null )
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
fixed( byte* ptr = file.Data )
|
||||
{
|
||||
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
|
||||
AllocateData( file.Data.Length + sizeof( ImcEntry ) * 100 * NumParts );
|
||||
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Penumbra/Meta/Files/MetaBaseFile.cs
Normal file
50
Penumbra/Meta/Files/MetaBaseFile.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Penumbra.Meta.Files;
|
||||
|
||||
public unsafe class MetaBaseFile : IDisposable
|
||||
{
|
||||
public byte* Data { get; private set; }
|
||||
public int Length { get; private set; }
|
||||
public int Index { get; }
|
||||
|
||||
public MetaBaseFile( int idx )
|
||||
=> Index = idx;
|
||||
|
||||
protected (IntPtr Data, int Length) DefaultData
|
||||
=> Penumbra.CharacterUtility.DefaultResources[ Index ];
|
||||
|
||||
// Reset to default values.
|
||||
public virtual void Reset()
|
||||
{}
|
||||
|
||||
// Obtain memory.
|
||||
protected void AllocateData( int length )
|
||||
{
|
||||
Length = length;
|
||||
Data = ( byte* )Marshal.AllocHGlobal( length );
|
||||
GC.AddMemoryPressure( length );
|
||||
}
|
||||
|
||||
// Free memory.
|
||||
protected void ReleaseUnmanagedResources()
|
||||
{
|
||||
Marshal.FreeHGlobal( ( IntPtr )Data );
|
||||
GC.RemoveMemoryPressure( Length );
|
||||
Length = 0;
|
||||
Data = null;
|
||||
}
|
||||
|
||||
// Manually free memory.
|
||||
public void Dispose()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize( this );
|
||||
}
|
||||
|
||||
~MetaBaseFile()
|
||||
{
|
||||
ReleaseUnmanagedResources();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,196 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Data;
|
||||
using Lumina.Data.Files;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Util;
|
||||
|
||||
namespace Penumbra.Meta.Files
|
||||
{
|
||||
// This class manages the default meta files obtained via lumina from the game files themselves.
|
||||
// On first call, the default version of any supported file will be cached and can be returned without reparsing.
|
||||
public class MetaDefaults
|
||||
{
|
||||
private readonly Dictionary< GamePath, object > _defaultFiles = new();
|
||||
|
||||
private object CreateNewFile( string path )
|
||||
{
|
||||
if( path.EndsWith( ".imc" ) )
|
||||
{
|
||||
return GetImcFile( path );
|
||||
}
|
||||
|
||||
var rawFile = FetchFile( path );
|
||||
if( path.EndsWith( ".eqp" ) )
|
||||
{
|
||||
return new EqpFile( rawFile );
|
||||
}
|
||||
|
||||
if( path.EndsWith( ".gmp" ) )
|
||||
{
|
||||
return new GmpFile( rawFile );
|
||||
}
|
||||
|
||||
if( path.EndsWith( ".eqdp" ) )
|
||||
{
|
||||
return new EqdpFile( rawFile );
|
||||
}
|
||||
|
||||
if( path.EndsWith( ".est" ) )
|
||||
{
|
||||
return new EstFile( rawFile );
|
||||
}
|
||||
|
||||
if( path.EndsWith( ".cmp" ) )
|
||||
{
|
||||
return new CmpFile( rawFile.Data );
|
||||
}
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private T? GetDefaultFile< T >( GamePath path, string error = "" ) where T : class
|
||||
{
|
||||
try
|
||||
{
|
||||
if( _defaultFiles.TryGetValue( path, out var file ) )
|
||||
{
|
||||
return ( T )file;
|
||||
}
|
||||
|
||||
var newFile = CreateNewFile( path );
|
||||
_defaultFiles.Add( path, newFile );
|
||||
return ( T )_defaultFiles[ path ];
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"{error}{e}" );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private EqdpFile? GetDefaultEqdpFile( EquipSlot slot, GenderRace gr )
|
||||
=> GetDefaultFile< EqdpFile >( MetaFileNames.Eqdp( slot, gr ),
|
||||
$"Could not obtain Eqdp file for {slot} {gr}:\n" );
|
||||
|
||||
private GmpFile? GetDefaultGmpFile()
|
||||
=> GetDefaultFile< GmpFile >( MetaFileNames.Gmp(), "Could not obtain Gmp file:\n" );
|
||||
|
||||
private EqpFile? GetDefaultEqpFile()
|
||||
=> GetDefaultFile< EqpFile >( MetaFileNames.Eqp(), "Could not obtain Eqp file:\n" );
|
||||
|
||||
private EstFile? GetDefaultEstFile( ObjectType type, EquipSlot equip, BodySlot body )
|
||||
=> GetDefaultFile< EstFile >( MetaFileNames.Est( type, equip, body ), $"Could not obtain Est file for {type} {equip} {body}:\n" );
|
||||
|
||||
private ImcFile? GetDefaultImcFile( ObjectType type, ushort primarySetId, ushort secondarySetId = 0 )
|
||||
=> GetDefaultFile< ImcFile >( MetaFileNames.Imc( type, primarySetId, secondarySetId ),
|
||||
$"Could not obtain Imc file for {type}, {primarySetId} {secondarySetId}:\n" );
|
||||
|
||||
private CmpFile? GetDefaultCmpFile()
|
||||
=> GetDefaultFile< CmpFile >( MetaFileNames.Cmp(), "Could not obtain Cmp file:\n" );
|
||||
|
||||
public EqdpFile? GetNewEqdpFile( EquipSlot slot, GenderRace gr )
|
||||
=> GetDefaultEqdpFile( slot, gr )?.Clone();
|
||||
|
||||
public GmpFile? GetNewGmpFile()
|
||||
=> GetDefaultGmpFile()?.Clone();
|
||||
|
||||
public EqpFile? GetNewEqpFile()
|
||||
=> GetDefaultEqpFile()?.Clone();
|
||||
|
||||
public EstFile? GetNewEstFile( ObjectType type, EquipSlot equip, BodySlot body )
|
||||
=> GetDefaultEstFile( type, equip, body )?.Clone();
|
||||
|
||||
public ImcFile? GetNewImcFile( ObjectType type, ushort primarySetId, ushort secondarySetId = 0 )
|
||||
=> GetDefaultImcFile( type, primarySetId, secondarySetId )?.Clone();
|
||||
|
||||
public CmpFile? GetNewCmpFile()
|
||||
=> GetDefaultCmpFile()?.Clone();
|
||||
|
||||
private static ImcFile GetImcFile( string path )
|
||||
=> Dalamud.GameData.GetFile< ImcFile >( path )!;
|
||||
|
||||
private static FileResource FetchFile( string name )
|
||||
=> Dalamud.GameData.GetFile( name )!;
|
||||
|
||||
// Check that a given meta manipulation is an actual change to the default value. We don't need to keep changes to default.
|
||||
public bool CheckAgainstDefault( MetaManipulation m )
|
||||
{
|
||||
try
|
||||
{
|
||||
return m.Type switch
|
||||
{
|
||||
MetaType.Imc => GetDefaultImcFile( m.ImcIdentifier.ObjectType, m.ImcIdentifier.PrimaryId, m.ImcIdentifier.SecondaryId )
|
||||
?.GetValue( m ).Equal( m.ImcValue )
|
||||
?? true,
|
||||
MetaType.Gmp => GetDefaultGmpFile()?.GetEntry( m.GmpIdentifier.SetId )
|
||||
== m.GmpValue,
|
||||
MetaType.Eqp => GetDefaultEqpFile()?.GetEntry( m.EqpIdentifier.SetId )
|
||||
.Reduce( m.EqpIdentifier.Slot )
|
||||
== m.EqpValue,
|
||||
MetaType.Eqdp => GetDefaultEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace )
|
||||
?.GetEntry( m.EqdpIdentifier.SetId )
|
||||
.Reduce( m.EqdpIdentifier.Slot )
|
||||
== m.EqdpValue,
|
||||
MetaType.Est => GetDefaultEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot )
|
||||
?.GetEntry( m.EstIdentifier.GenderRace, m.EstIdentifier.PrimaryId )
|
||||
== m.EstValue,
|
||||
MetaType.Rsp => GetDefaultCmpFile()?[ m.RspIdentifier.SubRace ][ m.RspIdentifier.Attribute ]
|
||||
== m.RspValue,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not obtain default value for {m.CorrespondingFilename()} - {m.IdentifierString()}:\n{e}" );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public object? GetDefaultValue( MetaManipulation m )
|
||||
{
|
||||
try
|
||||
{
|
||||
return m.Type switch
|
||||
{
|
||||
MetaType.Imc => GetDefaultImcFile( m.ImcIdentifier.ObjectType, m.ImcIdentifier.PrimaryId, m.ImcIdentifier.SecondaryId )
|
||||
?.GetValue( m ),
|
||||
MetaType.Gmp => GetDefaultGmpFile()?.GetEntry( m.GmpIdentifier.SetId ),
|
||||
MetaType.Eqp => GetDefaultEqpFile()?.GetEntry( m.EqpIdentifier.SetId )
|
||||
.Reduce( m.EqpIdentifier.Slot ),
|
||||
MetaType.Eqdp => GetDefaultEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace )
|
||||
?.GetEntry( m.EqdpIdentifier.SetId )
|
||||
.Reduce( m.EqdpIdentifier.Slot ),
|
||||
MetaType.Est => GetDefaultEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot )
|
||||
?.GetEntry( m.EstIdentifier.GenderRace, m.EstIdentifier.PrimaryId ),
|
||||
MetaType.Rsp => GetDefaultCmpFile()?[ m.RspIdentifier.SubRace ][ m.RspIdentifier.Attribute ],
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not obtain default value for {m.CorrespondingFilename()} - {m.IdentifierString()}:\n{e}" );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a deep copy of a default file as a new file.
|
||||
public object? CreateNewFile( MetaManipulation m )
|
||||
{
|
||||
return m.Type switch
|
||||
{
|
||||
MetaType.Imc => GetNewImcFile( m.ImcIdentifier.ObjectType, m.ImcIdentifier.PrimaryId, m.ImcIdentifier.SecondaryId ),
|
||||
MetaType.Gmp => GetNewGmpFile(),
|
||||
MetaType.Eqp => GetNewEqpFile(),
|
||||
MetaType.Eqdp => GetNewEqdpFile( m.EqdpIdentifier.Slot, m.EqdpIdentifier.GenderRace ),
|
||||
MetaType.Est => GetNewEstFile( m.EstIdentifier.ObjectType, m.EstIdentifier.EquipSlot, m.EstIdentifier.BodySlot ),
|
||||
MetaType.Rsp => GetNewCmpFile(),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
// A struct for each type of meta change that contains all relevant information,
|
||||
// to uniquely identify the corresponding file and location for the change.
|
||||
// The first byte is guaranteed to be the MetaType enum for each case.
|
||||
namespace Penumbra.Meta
|
||||
{
|
||||
public enum MetaType : byte
|
||||
{
|
||||
Unknown = 0,
|
||||
Imc = 1,
|
||||
Eqdp = 2,
|
||||
Eqp = 3,
|
||||
Est = 4,
|
||||
Gmp = 5,
|
||||
Rsp = 6,
|
||||
};
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct EqpIdentifier
|
||||
{
|
||||
[FieldOffset( 0 )]
|
||||
public ulong Value;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public MetaType Type;
|
||||
|
||||
[FieldOffset( 1 )]
|
||||
public EquipSlot Slot;
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public ushort SetId;
|
||||
|
||||
public override string ToString()
|
||||
=> $"Eqp - {SetId} - {Slot}";
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct EqdpIdentifier
|
||||
{
|
||||
[FieldOffset( 0 )]
|
||||
public ulong Value;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public MetaType Type;
|
||||
|
||||
[FieldOffset( 1 )]
|
||||
public EquipSlot Slot;
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public GenderRace GenderRace;
|
||||
|
||||
[FieldOffset( 4 )]
|
||||
public ushort SetId;
|
||||
|
||||
public override string ToString()
|
||||
=> $"Eqdp - {SetId} - {Slot} - {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()}";
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct GmpIdentifier
|
||||
{
|
||||
[FieldOffset( 0 )]
|
||||
public ulong Value;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public MetaType Type;
|
||||
|
||||
[FieldOffset( 1 )]
|
||||
public ushort SetId;
|
||||
|
||||
public override string ToString()
|
||||
=> $"Gmp - {SetId}";
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct EstIdentifier
|
||||
{
|
||||
[FieldOffset( 0 )]
|
||||
public ulong Value;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public MetaType Type;
|
||||
|
||||
[FieldOffset( 1 )]
|
||||
public ObjectType ObjectType;
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public EquipSlot EquipSlot;
|
||||
|
||||
[FieldOffset( 3 )]
|
||||
public BodySlot BodySlot;
|
||||
|
||||
[FieldOffset( 4 )]
|
||||
public GenderRace GenderRace;
|
||||
|
||||
[FieldOffset( 6 )]
|
||||
public ushort PrimaryId;
|
||||
|
||||
public override string ToString()
|
||||
=> ObjectType == ObjectType.Equipment
|
||||
? $"Est - {PrimaryId} - {EquipSlot} - {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()}"
|
||||
: $"Est - {PrimaryId} - {BodySlot} - {GenderRace.Split().Item2.ToName()} {GenderRace.Split().Item1.ToName()}";
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct ImcIdentifier
|
||||
{
|
||||
[FieldOffset( 0 )]
|
||||
public ulong Value;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public MetaType Type;
|
||||
|
||||
[FieldOffset( 1 )]
|
||||
public byte _objectAndBody;
|
||||
|
||||
public ObjectType ObjectType
|
||||
{
|
||||
get => ( ObjectType )( _objectAndBody & 0b00011111 );
|
||||
set => _objectAndBody = ( byte )( ( _objectAndBody & 0b11100000 ) | ( byte )value );
|
||||
}
|
||||
|
||||
public BodySlot BodySlot
|
||||
{
|
||||
get => ( BodySlot )( _objectAndBody >> 5 );
|
||||
set => _objectAndBody = ( byte )( ( _objectAndBody & 0b00011111 ) | ( ( byte )value << 5 ) );
|
||||
}
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public ushort PrimaryId;
|
||||
|
||||
[FieldOffset( 4 )]
|
||||
public ushort Variant;
|
||||
|
||||
[FieldOffset( 6 )]
|
||||
public ushort SecondaryId;
|
||||
|
||||
[FieldOffset( 6 )]
|
||||
public EquipSlot EquipSlot;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ObjectType switch
|
||||
{
|
||||
ObjectType.Accessory => $"Imc - {PrimaryId} - {EquipSlot} - {Variant}",
|
||||
ObjectType.Equipment => $"Imc - {PrimaryId} - {EquipSlot} - {Variant}",
|
||||
_ => $"Imc - {PrimaryId} - {ObjectType} - {SecondaryId} - {BodySlot} - {Variant}",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
public struct RspIdentifier
|
||||
{
|
||||
[FieldOffset( 0 )]
|
||||
public ulong Value;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public MetaType Type;
|
||||
|
||||
[FieldOffset( 1 )]
|
||||
public SubRace SubRace;
|
||||
|
||||
[FieldOffset( 2 )]
|
||||
public RspAttribute Attribute;
|
||||
|
||||
public override string ToString()
|
||||
=> $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}";
|
||||
}
|
||||
}
|
||||
56
Penumbra/Meta/Manipulations/EqdpManipulation.cs
Normal file
56
Penumbra/Meta/Manipulations/EqdpManipulation.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly struct EqdpManipulation : IEquatable< EqdpManipulation >
|
||||
{
|
||||
public readonly EqdpEntry Entry;
|
||||
public readonly Gender Gender;
|
||||
public readonly ModelRace Race;
|
||||
public readonly ushort SetId;
|
||||
public readonly EquipSlot Slot;
|
||||
|
||||
public EqdpManipulation( EqdpEntry entry, EquipSlot slot, Gender gender, ModelRace race, ushort setId )
|
||||
{
|
||||
Entry = Eqdp.Mask( slot ) & entry;
|
||||
Gender = gender;
|
||||
Race = race;
|
||||
SetId = setId;
|
||||
Slot = slot;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"Eqdp - {SetId} - {Slot} - {Race.ToName()} - {Gender.ToName()}";
|
||||
|
||||
public bool Equals( EqdpManipulation other )
|
||||
=> Gender == other.Gender
|
||||
&& Race == other.Race
|
||||
&& SetId == other.SetId
|
||||
&& Slot == other.Slot;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is EqdpManipulation other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine( ( int )Gender, ( int )Race, SetId, ( int )Slot );
|
||||
|
||||
public int FileIndex()
|
||||
=> CharacterUtility.EqdpIdx( Names.CombinedRace( Gender, Race ), Slot.IsAccessory() );
|
||||
|
||||
public bool Apply( ExpandedEqdpFile file )
|
||||
{
|
||||
var entry = file[ SetId ];
|
||||
var mask = Eqdp.Mask( Slot );
|
||||
if( ( entry & mask ) == Entry )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file[ SetId ] = ( entry & ~mask ) | Entry;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
50
Penumbra/Meta/Manipulations/EqpManipulation.cs
Normal file
50
Penumbra/Meta/Manipulations/EqpManipulation.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly struct EqpManipulation : IEquatable< EqpManipulation >
|
||||
{
|
||||
public readonly EqpEntry Entry;
|
||||
public readonly ushort SetId;
|
||||
public readonly EquipSlot Slot;
|
||||
|
||||
public EqpManipulation( EqpEntry entry, EquipSlot slot, ushort setId )
|
||||
{
|
||||
Slot = slot;
|
||||
SetId = setId;
|
||||
Entry = Eqp.Mask( slot ) & entry;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"Eqp - {SetId} - {Slot}";
|
||||
|
||||
public bool Equals( EqpManipulation other )
|
||||
=> Slot == other.Slot
|
||||
&& SetId == other.SetId;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is EqpManipulation other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine( ( int )Slot, SetId );
|
||||
|
||||
public int FileIndex()
|
||||
=> CharacterUtility.EqpIdx;
|
||||
|
||||
public bool Apply( ExpandedEqpFile file )
|
||||
{
|
||||
var entry = file[ SetId ];
|
||||
var mask = Eqp.Mask( Slot );
|
||||
if( ( entry & mask ) == Entry )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file[ SetId ] = ( entry & ~mask ) | Entry;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
63
Penumbra/Meta/Manipulations/EstManipulation.cs
Normal file
63
Penumbra/Meta/Manipulations/EstManipulation.cs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly struct EstManipulation : IEquatable< EstManipulation >
|
||||
{
|
||||
public enum EstType : byte
|
||||
{
|
||||
Hair = CharacterUtility.HairEstIdx,
|
||||
Face = CharacterUtility.FaceEstIdx,
|
||||
Body = CharacterUtility.BodyEstIdx,
|
||||
Head = CharacterUtility.HeadEstIdx,
|
||||
}
|
||||
|
||||
public readonly ushort SkeletonIdx;
|
||||
public readonly Gender Gender;
|
||||
public readonly ModelRace Race;
|
||||
public readonly ushort SetId;
|
||||
public readonly EstType Type;
|
||||
|
||||
public EstManipulation( Gender gender, ModelRace race, EstType estType, ushort setId, ushort skeletonIdx )
|
||||
{
|
||||
SkeletonIdx = skeletonIdx;
|
||||
Gender = gender;
|
||||
Race = race;
|
||||
SetId = setId;
|
||||
Type = estType;
|
||||
}
|
||||
|
||||
|
||||
public override string ToString()
|
||||
=> $"Est - {SetId} - {Type} - {Race.ToName()} {Gender.ToName()}";
|
||||
|
||||
public bool Equals( EstManipulation other )
|
||||
=> Gender == other.Gender
|
||||
&& Race == other.Race
|
||||
&& SetId == other.SetId
|
||||
&& Type == other.Type;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is EstManipulation other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine( ( int )Gender, ( int )Race, SetId, ( int )Type );
|
||||
|
||||
public int FileIndex()
|
||||
=> ( int )Type;
|
||||
|
||||
public bool Apply( EstFile file )
|
||||
{
|
||||
return file.SetEntry( Names.CombinedRace( Gender, Race ), SetId, SkeletonIdx ) switch
|
||||
{
|
||||
EstFile.EstEntryChange.Unchanged => false,
|
||||
EstFile.EstEntryChange.Changed => true,
|
||||
EstFile.EstEntryChange.Added => true,
|
||||
EstFile.EstEntryChange.Removed => true,
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
45
Penumbra/Meta/Manipulations/GmpManipulation.cs
Normal file
45
Penumbra/Meta/Manipulations/GmpManipulation.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly struct GmpManipulation : IEquatable< GmpManipulation >
|
||||
{
|
||||
public readonly GmpEntry Entry;
|
||||
public readonly ushort SetId;
|
||||
|
||||
public GmpManipulation( GmpEntry entry, ushort setId )
|
||||
{
|
||||
Entry = entry;
|
||||
SetId = setId;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"Gmp - {SetId}";
|
||||
|
||||
public bool Equals( GmpManipulation other )
|
||||
=> SetId == other.SetId;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is GmpManipulation other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> SetId.GetHashCode();
|
||||
|
||||
public int FileIndex()
|
||||
=> CharacterUtility.GmpIdx;
|
||||
|
||||
public bool Apply( ExpandedGmpFile file )
|
||||
{
|
||||
var entry = file[ SetId ];
|
||||
if( entry == Entry )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file[ SetId ] = Entry;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
61
Penumbra/Meta/Manipulations/ImcManipulation.cs
Normal file
61
Penumbra/Meta/Manipulations/ImcManipulation.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Meta.Files;
|
||||
using ImcFile = Lumina.Data.Files.ImcFile;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[StructLayout( LayoutKind.Sequential )]
|
||||
public readonly struct ImcManipulation : IEquatable< ImcManipulation >
|
||||
{
|
||||
public readonly ImcEntry Entry;
|
||||
public readonly ushort PrimaryId;
|
||||
public readonly ushort Variant;
|
||||
public readonly ushort SecondaryId;
|
||||
public readonly ObjectType ObjectType;
|
||||
public readonly EquipSlot EquipSlot;
|
||||
public readonly BodySlot BodySlot;
|
||||
|
||||
public ImcManipulation( EquipSlot equipSlot, ushort variant, ushort primaryId, ImcEntry entry )
|
||||
{
|
||||
Entry = entry;
|
||||
PrimaryId = primaryId;
|
||||
Variant = variant;
|
||||
SecondaryId = 0;
|
||||
ObjectType = equipSlot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment;
|
||||
EquipSlot = equipSlot;
|
||||
BodySlot = BodySlot.Unknown;
|
||||
}
|
||||
|
||||
public ImcManipulation( ObjectType objectType, BodySlot bodySlot, ushort primaryId, ushort secondaryId, ushort variant,
|
||||
ImcEntry entry )
|
||||
{
|
||||
Entry = entry;
|
||||
ObjectType = objectType;
|
||||
BodySlot = bodySlot;
|
||||
SecondaryId = secondaryId;
|
||||
PrimaryId = primaryId;
|
||||
Variant = variant;
|
||||
EquipSlot = EquipSlot.Unknown;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> ObjectType is ObjectType.Equipment or ObjectType.Accessory
|
||||
? $"Imc - {PrimaryId} - {EquipSlot} - {Variant}"
|
||||
: $"Imc - {PrimaryId} - {ObjectType} - {SecondaryId} - {BodySlot} - {Variant}";
|
||||
|
||||
public bool Equals( ImcManipulation other )
|
||||
=> PrimaryId == other.PrimaryId
|
||||
&& Variant == other.Variant
|
||||
&& SecondaryId == other.SecondaryId
|
||||
&& ObjectType == other.ObjectType
|
||||
&& EquipSlot == other.EquipSlot
|
||||
&& BodySlot == other.BodySlot;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is ImcManipulation other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine( PrimaryId, Variant, SecondaryId, ( int )ObjectType, ( int )EquipSlot, ( int )BodySlot );
|
||||
}
|
||||
109
Penumbra/Meta/Manipulations/MetaManipulation.cs
Normal file
109
Penumbra/Meta/Manipulations/MetaManipulation.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
[StructLayout( LayoutKind.Explicit, Pack = 1, Size = 16 )]
|
||||
public readonly struct MetaManipulation : IEquatable< MetaManipulation >
|
||||
{
|
||||
public enum Type : byte
|
||||
{
|
||||
Eqp,
|
||||
Gmp,
|
||||
Eqdp,
|
||||
Est,
|
||||
Rsp,
|
||||
Imc,
|
||||
}
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly EqpManipulation Eqp = default;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly GmpManipulation Gmp = default;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly EqdpManipulation Eqdp = default;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly EstManipulation Est = default;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly RspManipulation Rsp = default;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly ImcManipulation Imc = default;
|
||||
|
||||
[FieldOffset( 15 )]
|
||||
public readonly Type ManipulationType;
|
||||
|
||||
public MetaManipulation( EqpManipulation eqp )
|
||||
=> ( ManipulationType, Eqp ) = ( Type.Eqp, eqp );
|
||||
|
||||
public MetaManipulation( GmpManipulation gmp )
|
||||
=> ( ManipulationType, Gmp ) = ( Type.Gmp, gmp );
|
||||
|
||||
public MetaManipulation( EqdpManipulation eqdp )
|
||||
=> ( ManipulationType, Eqdp ) = ( Type.Eqdp, eqdp );
|
||||
|
||||
public MetaManipulation( EstManipulation est )
|
||||
=> ( ManipulationType, Est ) = ( Type.Est, est );
|
||||
|
||||
public MetaManipulation( RspManipulation rsp )
|
||||
=> ( ManipulationType, Rsp ) = ( Type.Rsp, rsp );
|
||||
|
||||
public MetaManipulation( ImcManipulation imc )
|
||||
=> ( ManipulationType, Imc ) = ( Type.Imc, imc );
|
||||
|
||||
public static implicit operator MetaManipulation( EqpManipulation eqp )
|
||||
=> new(eqp);
|
||||
|
||||
public static implicit operator MetaManipulation( GmpManipulation gmp )
|
||||
=> new(gmp);
|
||||
|
||||
public static implicit operator MetaManipulation( EqdpManipulation eqdp )
|
||||
=> new(eqdp);
|
||||
|
||||
public static implicit operator MetaManipulation( EstManipulation est )
|
||||
=> new(est);
|
||||
|
||||
public static implicit operator MetaManipulation( RspManipulation rsp )
|
||||
=> new(rsp);
|
||||
|
||||
public static implicit operator MetaManipulation( ImcManipulation imc )
|
||||
=> new(imc);
|
||||
|
||||
public bool Equals( MetaManipulation other )
|
||||
{
|
||||
if( ManipulationType != other.ManipulationType )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return ManipulationType switch
|
||||
{
|
||||
Type.Eqp => Eqp.Equals( other.Eqp ),
|
||||
Type.Gmp => Gmp.Equals( other.Gmp ),
|
||||
Type.Eqdp => Eqdp.Equals( other.Eqdp ),
|
||||
Type.Est => Est.Equals( other.Est ),
|
||||
Type.Rsp => Rsp.Equals( other.Rsp ),
|
||||
Type.Imc => Imc.Equals( other.Imc ),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is MetaManipulation other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> ManipulationType switch
|
||||
{
|
||||
Type.Eqp => Eqp.GetHashCode(),
|
||||
Type.Gmp => Gmp.GetHashCode(),
|
||||
Type.Eqdp => Eqdp.GetHashCode(),
|
||||
Type.Est => Est.GetHashCode(),
|
||||
Type.Rsp => Rsp.GetHashCode(),
|
||||
Type.Imc => Imc.GetHashCode(),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
}
|
||||
48
Penumbra/Meta/Manipulations/RspManipulation.cs
Normal file
48
Penumbra/Meta/Manipulations/RspManipulation.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
|
||||
namespace Penumbra.Meta.Manipulations;
|
||||
|
||||
public readonly struct RspManipulation : IEquatable< RspManipulation >
|
||||
{
|
||||
public readonly float Entry;
|
||||
public readonly SubRace SubRace;
|
||||
public readonly RspAttribute Attribute;
|
||||
|
||||
public RspManipulation( SubRace subRace, RspAttribute attribute, float entry )
|
||||
{
|
||||
Entry = entry;
|
||||
SubRace = subRace;
|
||||
Attribute = attribute;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> $"Rsp - {SubRace.ToName()} - {Attribute.ToFullString()}";
|
||||
|
||||
public bool Equals( RspManipulation other )
|
||||
=> SubRace == other.SubRace
|
||||
&& Attribute == other.Attribute;
|
||||
|
||||
public override bool Equals( object? obj )
|
||||
=> obj is RspManipulation other && Equals( other );
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine( ( int )SubRace, ( int )Attribute );
|
||||
|
||||
public int FileIndex()
|
||||
=> CharacterUtility.HumanCmpIdx;
|
||||
|
||||
public bool Apply( CmpFile file )
|
||||
{
|
||||
var value = file[ SubRace, Attribute ];
|
||||
if( value == Entry )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
file[ SubRace, Attribute ] = Entry;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,8 @@ using Dalamud.Logging;
|
|||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Importer;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Mod;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Meta;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,273 @@ using System.Linq;
|
|||
using Dalamud.Logging;
|
||||
using Lumina.Data.Files;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.Interop.Structs;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.Util;
|
||||
|
||||
namespace Penumbra.Meta;
|
||||
|
||||
public struct TemporaryImcFile : IDisposable
|
||||
{
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class MetaManager2 : IDisposable
|
||||
{
|
||||
public readonly List< MetaBaseFile > ChangedData = new(7 + CharacterUtility.NumEqdpFiles);
|
||||
|
||||
public ExpandedEqpFile? EqpFile;
|
||||
public ExpandedGmpFile? GmpFile;
|
||||
public ExpandedEqdpFile?[] EqdpFile = new ExpandedEqdpFile?[CharacterUtility.NumEqdpFiles];
|
||||
public EstFile? FaceEstFile;
|
||||
public EstFile? HairEstFile;
|
||||
public EstFile? BodyEstFile;
|
||||
public EstFile? HeadEstFile;
|
||||
public CmpFile? CmpFile;
|
||||
|
||||
public readonly Dictionary< EqpManipulation, Mod.Mod > EqpManipulations = new();
|
||||
public readonly Dictionary< EstManipulation, Mod.Mod > EstManipulations = new();
|
||||
public readonly Dictionary< GmpManipulation, Mod.Mod > GmpManipulations = new();
|
||||
public readonly Dictionary< RspManipulation, Mod.Mod > RspManipulations = new();
|
||||
public readonly Dictionary< EqdpManipulation, Mod.Mod > EqdpManipulations = new();
|
||||
|
||||
public readonly Dictionary< ImcManipulation, Mod.Mod > ImcManipulations = new();
|
||||
public readonly List< TemporaryImcFile > ImcFiles = new();
|
||||
|
||||
public void ResetEqp()
|
||||
{
|
||||
if( EqpFile != null )
|
||||
{
|
||||
EqpFile.Reset( EqpManipulations.Keys.Select( m => ( int )m.SetId ) );
|
||||
EqpManipulations.Clear();
|
||||
ChangedData.Remove( EqpFile );
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetGmp()
|
||||
{
|
||||
if( GmpFile != null )
|
||||
{
|
||||
GmpFile.Reset( GmpManipulations.Keys.Select( m => ( int )m.SetId ) );
|
||||
GmpManipulations.Clear();
|
||||
ChangedData.Remove( GmpFile );
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetCmp()
|
||||
{
|
||||
if( CmpFile != null )
|
||||
{
|
||||
CmpFile.Reset( RspManipulations.Keys.Select( m => ( m.SubRace, m.Attribute ) ) );
|
||||
RspManipulations.Clear();
|
||||
ChangedData.Remove( CmpFile );
|
||||
}
|
||||
}
|
||||
|
||||
public void ResetEst()
|
||||
{
|
||||
FaceEstFile?.Reset();
|
||||
HairEstFile?.Reset();
|
||||
BodyEstFile?.Reset();
|
||||
HeadEstFile?.Reset();
|
||||
RspManipulations.Clear();
|
||||
ChangedData.RemoveAll( f => f is EstFile );
|
||||
}
|
||||
|
||||
public void ResetEqdp()
|
||||
{
|
||||
foreach( var file in EqdpFile )
|
||||
{
|
||||
file?.Reset( EqdpManipulations.Keys.Where( m => m.FileIndex() == file.Index ).Select( m => ( int )m.SetId ) );
|
||||
}
|
||||
|
||||
ChangedData.RemoveAll( f => f is ExpandedEqdpFile );
|
||||
EqdpManipulations.Clear();
|
||||
}
|
||||
|
||||
public void ResetImc()
|
||||
{
|
||||
foreach( var file in ImcFiles )
|
||||
file.Dispose();
|
||||
ImcFiles.Clear();
|
||||
ImcManipulations.Clear();
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
ChangedData.Clear();
|
||||
ResetEqp();
|
||||
ResetGmp();
|
||||
ResetCmp();
|
||||
ResetEst();
|
||||
ResetEqdp();
|
||||
ResetImc();
|
||||
}
|
||||
|
||||
private static void Dispose< T >( ref T? file ) where T : class, IDisposable
|
||||
{
|
||||
if( file != null )
|
||||
{
|
||||
file.Dispose();
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ChangedData.Clear();
|
||||
EqpManipulations.Clear();
|
||||
EstManipulations.Clear();
|
||||
GmpManipulations.Clear();
|
||||
RspManipulations.Clear();
|
||||
EqdpManipulations.Clear();
|
||||
Dispose( ref EqpFile );
|
||||
Dispose( ref GmpFile );
|
||||
Dispose( ref FaceEstFile );
|
||||
Dispose( ref HairEstFile );
|
||||
Dispose( ref BodyEstFile );
|
||||
Dispose( ref HeadEstFile );
|
||||
Dispose( ref CmpFile );
|
||||
for( var i = 0; i < CharacterUtility.NumEqdpFiles; ++i )
|
||||
{
|
||||
Dispose( ref EqdpFile[ i ] );
|
||||
}
|
||||
|
||||
ResetImc();
|
||||
}
|
||||
|
||||
private void AddFile( MetaBaseFile file )
|
||||
{
|
||||
if( !ChangedData.Contains( file ) )
|
||||
{
|
||||
ChangedData.Add( file );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool ApplyMod( EqpManipulation m, Mod.Mod mod )
|
||||
{
|
||||
if( !EqpManipulations.TryAdd( m, mod ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
EqpFile ??= new ExpandedEqpFile();
|
||||
if( !m.Apply( EqpFile ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddFile( EqpFile );
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ApplyMod( GmpManipulation m, Mod.Mod mod )
|
||||
{
|
||||
if( !GmpManipulations.TryAdd( m, mod ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
GmpFile ??= new ExpandedGmpFile();
|
||||
if( !m.Apply( GmpFile ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddFile( GmpFile );
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ApplyMod( EstManipulation m, Mod.Mod mod )
|
||||
{
|
||||
if( !EstManipulations.TryAdd( m, mod ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var file = m.Type switch
|
||||
{
|
||||
EstManipulation.EstType.Hair => HairEstFile ??= new EstFile( EstManipulation.EstType.Hair ),
|
||||
EstManipulation.EstType.Face => FaceEstFile ??= new EstFile( EstManipulation.EstType.Face ),
|
||||
EstManipulation.EstType.Body => BodyEstFile ??= new EstFile( EstManipulation.EstType.Body ),
|
||||
EstManipulation.EstType.Head => HeadEstFile ??= new EstFile( EstManipulation.EstType.Head ),
|
||||
_ => throw new ArgumentOutOfRangeException(),
|
||||
};
|
||||
if( !m.Apply( file ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddFile( file );
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ApplyMod( RspManipulation m, Mod.Mod mod )
|
||||
{
|
||||
if( !RspManipulations.TryAdd( m, mod ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
CmpFile ??= new CmpFile();
|
||||
if( !m.Apply( CmpFile ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddFile( CmpFile );
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ApplyMod( EqdpManipulation m, Mod.Mod mod )
|
||||
{
|
||||
if( !EqdpManipulations.TryAdd( m, mod ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var file = EqdpFile[ m.FileIndex() - 2 ] ??= new ExpandedEqdpFile( Names.CombinedRace( m.Gender, m.Race ), m.Slot.IsAccessory() );
|
||||
if( !m.Apply( file ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
AddFile( file );
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ApplyMod( ImcManipulation m, Mod.Mod mod )
|
||||
{
|
||||
if( !ImcManipulations.TryAdd( m, mod ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ApplyMod( MetaManipulation m, Mod.Mod mod )
|
||||
{
|
||||
return m.ManipulationType switch
|
||||
{
|
||||
MetaManipulation.Type.Eqp => ApplyMod( m.Eqp, mod ),
|
||||
MetaManipulation.Type.Gmp => ApplyMod( m.Gmp, mod ),
|
||||
MetaManipulation.Type.Eqdp => ApplyMod( m.Eqdp, mod ),
|
||||
MetaManipulation.Type.Est => ApplyMod( m.Est, mod ),
|
||||
MetaManipulation.Type.Rsp => ApplyMod( m.Rsp, mod ),
|
||||
MetaManipulation.Type.Imc => ApplyMod( m.Imc, mod ),
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class MetaManager : IDisposable
|
||||
{
|
||||
internal class FileInformation
|
||||
|
|
@ -27,12 +288,7 @@ public class MetaManager : IDisposable
|
|||
{
|
||||
ByteData = Data switch
|
||||
{
|
||||
EqdpFile eqdp => eqdp.WriteBytes(),
|
||||
EqpFile eqp => eqp.WriteBytes(),
|
||||
GmpFile gmp => gmp.WriteBytes(),
|
||||
EstFile est => est.WriteBytes(),
|
||||
ImcFile imc => imc.WriteBytes(),
|
||||
CmpFile cmp => cmp.WriteBytes(),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
DisposeFile( CurrentFile );
|
||||
|
|
@ -138,50 +394,6 @@ public class MetaManager : IDisposable
|
|||
{
|
||||
value.Write( _dir, key );
|
||||
_resolvedFiles[ key ] = value.CurrentFile!.Value;
|
||||
if( value.Data is EqpFile )
|
||||
{
|
||||
EqpData = value.ByteData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ApplyMod( MetaManipulation m, Mod.Mod mod )
|
||||
{
|
||||
if( _currentManipulations.ContainsKey( m ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_currentManipulations.Add( m, mod );
|
||||
var gamePath = Utf8GamePath.FromString(m.CorrespondingFilename(), out var p, false) ? p : Utf8GamePath.Empty; // TODO
|
||||
try
|
||||
{
|
||||
if( !_currentFiles.TryGetValue( gamePath, out var file ) )
|
||||
{
|
||||
file = new FileInformation( Penumbra.MetaDefaults.CreateNewFile( m ) ?? throw new IOException() )
|
||||
{
|
||||
Changed = true,
|
||||
CurrentFile = null,
|
||||
};
|
||||
_currentFiles[ gamePath ] = file;
|
||||
}
|
||||
|
||||
file.Changed |= m.Type switch
|
||||
{
|
||||
MetaType.Eqp => m.Apply( ( EqpFile )file.Data ),
|
||||
MetaType.Eqdp => m.Apply( ( EqdpFile )file.Data ),
|
||||
MetaType.Gmp => m.Apply( ( GmpFile )file.Data ),
|
||||
MetaType.Est => m.Apply( ( EstFile )file.Data ),
|
||||
MetaType.Imc => m.Apply( ( ImcFile )file.Data ),
|
||||
MetaType.Rsp => m.Apply( ( CmpFile )file.Data ),
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
return true;
|
||||
}
|
||||
catch( Exception e )
|
||||
{
|
||||
PluginLog.Error( $"Could not obtain default file for manipulation {m.CorrespondingFilename()}:\n{e}" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,259 +0,0 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using Newtonsoft.Json;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.GameData.Util;
|
||||
using Penumbra.Meta.Files;
|
||||
using Swan;
|
||||
using ImcFile = Lumina.Data.Files.ImcFile;
|
||||
|
||||
namespace Penumbra.Meta
|
||||
{
|
||||
// Write a single meta manipulation as a Base64string of the 16 bytes defining it.
|
||||
public class MetaManipulationConverter : JsonConverter< MetaManipulation >
|
||||
{
|
||||
public override void WriteJson( JsonWriter writer, MetaManipulation manip, JsonSerializer serializer )
|
||||
{
|
||||
var s = Convert.ToBase64String( manip.ToBytes() );
|
||||
writer.WriteValue( s );
|
||||
}
|
||||
|
||||
public override MetaManipulation ReadJson( JsonReader reader, Type objectType, MetaManipulation existingValue, bool hasExistingValue,
|
||||
JsonSerializer serializer )
|
||||
|
||||
{
|
||||
if( reader.TokenType != JsonToken.String )
|
||||
{
|
||||
throw new JsonReaderException();
|
||||
}
|
||||
|
||||
var bytes = Convert.FromBase64String( ( string )reader.Value! );
|
||||
using MemoryStream m = new( bytes );
|
||||
using BinaryReader br = new( m );
|
||||
var i = br.ReadUInt64();
|
||||
var v = br.ReadUInt64();
|
||||
return new MetaManipulation( i, v );
|
||||
}
|
||||
}
|
||||
|
||||
// A MetaManipulation is a union of a type of Identifier (first 8 bytes, cf. Identifier.cs)
|
||||
// and the appropriate Value to change the meta entry to (the other 8 bytes).
|
||||
// Its comparison for sorting and hashes depends only on the identifier.
|
||||
// The first byte is guaranteed to be a MetaType enum value in any case, so Type can always be read.
|
||||
[StructLayout( LayoutKind.Explicit )]
|
||||
[JsonConverter( typeof( MetaManipulationConverter ) )]
|
||||
public struct MetaManipulation : IComparable
|
||||
{
|
||||
public static MetaManipulation Eqp( EquipSlot equipSlot, ushort setId, EqpEntry value )
|
||||
=> new()
|
||||
{
|
||||
EqpIdentifier = new EqpIdentifier()
|
||||
{
|
||||
Type = MetaType.Eqp,
|
||||
Slot = equipSlot,
|
||||
SetId = setId,
|
||||
},
|
||||
EqpValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Eqdp( EquipSlot equipSlot, GenderRace gr, ushort setId, EqdpEntry value )
|
||||
=> new()
|
||||
{
|
||||
EqdpIdentifier = new EqdpIdentifier()
|
||||
{
|
||||
Type = MetaType.Eqdp,
|
||||
Slot = equipSlot,
|
||||
GenderRace = gr,
|
||||
SetId = setId,
|
||||
},
|
||||
EqdpValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Gmp( ushort setId, GmpEntry value )
|
||||
=> new()
|
||||
{
|
||||
GmpIdentifier = new GmpIdentifier()
|
||||
{
|
||||
Type = MetaType.Gmp,
|
||||
SetId = setId,
|
||||
},
|
||||
GmpValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Est( ObjectType type, EquipSlot equipSlot, GenderRace gr, BodySlot bodySlot, ushort setId,
|
||||
ushort value )
|
||||
=> new()
|
||||
{
|
||||
EstIdentifier = new EstIdentifier()
|
||||
{
|
||||
Type = MetaType.Est,
|
||||
ObjectType = type,
|
||||
GenderRace = gr,
|
||||
EquipSlot = equipSlot,
|
||||
BodySlot = bodySlot,
|
||||
PrimaryId = setId,
|
||||
},
|
||||
EstValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Imc( ObjectType type, BodySlot secondaryType, ushort primaryId, ushort secondaryId
|
||||
, ushort idx, ImcFile.ImageChangeData value )
|
||||
=> new()
|
||||
{
|
||||
ImcIdentifier = new ImcIdentifier()
|
||||
{
|
||||
Type = MetaType.Imc,
|
||||
ObjectType = type,
|
||||
BodySlot = secondaryType,
|
||||
PrimaryId = primaryId,
|
||||
SecondaryId = secondaryId,
|
||||
Variant = idx,
|
||||
},
|
||||
ImcValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Imc( EquipSlot slot, ushort primaryId, ushort idx, ImcFile.ImageChangeData value )
|
||||
=> new()
|
||||
{
|
||||
ImcIdentifier = new ImcIdentifier()
|
||||
{
|
||||
Type = MetaType.Imc,
|
||||
ObjectType = slot.IsAccessory() ? ObjectType.Accessory : ObjectType.Equipment,
|
||||
EquipSlot = slot,
|
||||
PrimaryId = primaryId,
|
||||
Variant = idx,
|
||||
},
|
||||
ImcValue = value,
|
||||
};
|
||||
|
||||
public static MetaManipulation Rsp( SubRace subRace, RspAttribute attribute, float value )
|
||||
=> new()
|
||||
{
|
||||
RspIdentifier = new RspIdentifier()
|
||||
{
|
||||
Type = MetaType.Rsp,
|
||||
SubRace = subRace,
|
||||
Attribute = attribute,
|
||||
},
|
||||
RspValue = value,
|
||||
};
|
||||
|
||||
internal MetaManipulation( ulong identifier, ulong value )
|
||||
: this()
|
||||
{
|
||||
Identifier = identifier;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public readonly ulong Identifier;
|
||||
|
||||
[FieldOffset( 8 )]
|
||||
public readonly ulong Value;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public MetaType Type;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public EqpIdentifier EqpIdentifier;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public GmpIdentifier GmpIdentifier;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public EqdpIdentifier EqdpIdentifier;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public EstIdentifier EstIdentifier;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public ImcIdentifier ImcIdentifier;
|
||||
|
||||
[FieldOffset( 0 )]
|
||||
public RspIdentifier RspIdentifier;
|
||||
|
||||
|
||||
[FieldOffset( 8 )]
|
||||
public EqpEntry EqpValue;
|
||||
|
||||
[FieldOffset( 8 )]
|
||||
public GmpEntry GmpValue;
|
||||
|
||||
[FieldOffset( 8 )]
|
||||
public EqdpEntry EqdpValue;
|
||||
|
||||
[FieldOffset( 8 )]
|
||||
public ushort EstValue;
|
||||
|
||||
[FieldOffset( 8 )]
|
||||
public ImcFile.ImageChangeData ImcValue; // 6 bytes.
|
||||
|
||||
[FieldOffset( 8 )]
|
||||
public float RspValue;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Identifier.GetHashCode();
|
||||
|
||||
public int CompareTo( object? rhs )
|
||||
=> Identifier.CompareTo( rhs is MetaManipulation m ? m.Identifier : null );
|
||||
|
||||
public GamePath CorrespondingFilename()
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
MetaType.Eqp => MetaFileNames.Eqp(),
|
||||
MetaType.Eqdp => MetaFileNames.Eqdp( EqdpIdentifier.Slot, EqdpIdentifier.GenderRace ),
|
||||
MetaType.Est => MetaFileNames.Est( EstIdentifier.ObjectType, EstIdentifier.EquipSlot, EstIdentifier.BodySlot ),
|
||||
MetaType.Gmp => MetaFileNames.Gmp(),
|
||||
MetaType.Imc => MetaFileNames.Imc( ImcIdentifier.ObjectType, ImcIdentifier.PrimaryId, ImcIdentifier.SecondaryId ),
|
||||
MetaType.Rsp => MetaFileNames.Cmp(),
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
|
||||
// No error checking.
|
||||
public bool Apply( EqpFile file )
|
||||
=> file[ EqpIdentifier.SetId ].Apply( this );
|
||||
|
||||
public bool Apply( EqdpFile file )
|
||||
=> file[ EqdpIdentifier.SetId ].Apply( this );
|
||||
|
||||
public bool Apply( GmpFile file )
|
||||
=> file.SetEntry( GmpIdentifier.SetId, GmpValue );
|
||||
|
||||
public bool Apply( EstFile file )
|
||||
=> file.SetEntry( EstIdentifier.GenderRace, EstIdentifier.PrimaryId, EstValue );
|
||||
|
||||
public bool Apply( ImcFile file )
|
||||
{
|
||||
ref var value = ref file.GetValue( this );
|
||||
if( ImcValue.Equal( value ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
value = ImcValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Apply( CmpFile file )
|
||||
=> file.Set( RspIdentifier.SubRace, RspIdentifier.Attribute, RspValue );
|
||||
|
||||
public string IdentifierString()
|
||||
{
|
||||
return Type switch
|
||||
{
|
||||
MetaType.Eqp => EqpIdentifier.ToString(),
|
||||
MetaType.Eqdp => EqdpIdentifier.ToString(),
|
||||
MetaType.Est => EstIdentifier.ToString(),
|
||||
MetaType.Gmp => GmpIdentifier.ToString(),
|
||||
MetaType.Imc => ImcIdentifier.ToString(),
|
||||
MetaType.Rsp => RspIdentifier.ToString(),
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra.Mod;
|
||||
|
||||
|
|
|
|||
|
|
@ -304,7 +304,7 @@ public class ModCollectionCache
|
|||
{
|
||||
if( !MetaManipulations.TryGetValue( manip, out var oldMod ) )
|
||||
{
|
||||
MetaManipulations.ApplyMod( manip, mod );
|
||||
//MetaManipulations.ApplyMod( manip, mod );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,9 +15,13 @@ using Penumbra.PlayerWatch;
|
|||
using Penumbra.UI;
|
||||
using Penumbra.Util;
|
||||
using System.Linq;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
|
||||
namespace Penumbra;
|
||||
public class MetaDefaults
|
||||
{
|
||||
|
||||
}
|
||||
public class Penumbra : IDalamudPlugin
|
||||
{
|
||||
public string Name
|
||||
|
|
@ -33,6 +37,7 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
public static ResidentResourceManager ResidentResources { get; private set; } = null!;
|
||||
public static CharacterUtility CharacterUtility { get; private set; } = null!;
|
||||
|
||||
public static MetaDefaults MetaDefaults { get; private set; } = null!;
|
||||
public static ModManager ModManager { get; private set; } = null!;
|
||||
|
||||
|
|
@ -114,6 +119,17 @@ public class Penumbra : IDalamudPlugin
|
|||
ResourceLoader.EnableDebug();
|
||||
if (Config.EnableFullResourceLogging)
|
||||
ResourceLoader.EnableFullLogging();
|
||||
|
||||
unsafe
|
||||
{
|
||||
PluginLog.Information( $"MetaManipulation: {sizeof( MetaManipulation )}" );
|
||||
PluginLog.Information( $"EqpManipulation: {sizeof( EqpManipulation )}" );
|
||||
PluginLog.Information( $"GmpManipulation: {sizeof( GmpManipulation )}" );
|
||||
PluginLog.Information( $"EqdpManipulation: {sizeof( EqdpManipulation )}" );
|
||||
PluginLog.Information( $"EstManipulation: {sizeof( EstManipulation )}" );
|
||||
PluginLog.Information( $"RspManipulation: {sizeof( RspManipulation )}" );
|
||||
PluginLog.Information( $"ImcManipulation: {sizeof( ImcManipulation )}" );
|
||||
}
|
||||
}
|
||||
|
||||
public bool Enable()
|
||||
|
|
|
|||
|
|
@ -108,12 +108,12 @@ public partial class SettingsInterface
|
|||
DrawLine( gp, fp );
|
||||
}
|
||||
|
||||
foreach( var (mp, mod, _) in cache.MetaManipulations.Manipulations
|
||||
.Select( p => ( p.Item1.IdentifierString(), p.Item2.Data.Meta.Name, p.Item2.Data.Meta.LowerName ) )
|
||||
.Where( CheckFilters ) )
|
||||
{
|
||||
DrawLine( mp, mod );
|
||||
}
|
||||
//foreach( var (mp, mod, _) in cache.MetaManipulations.Manipulations
|
||||
// .Select( p => ( p.Item1.IdentifierString(), p.Item2.Data.Meta.Name, p.Item2.Data.Meta.LowerName ) )
|
||||
// .Where( CheckFilters ) )
|
||||
//{
|
||||
// DrawLine( mp, mod );
|
||||
//}
|
||||
}
|
||||
|
||||
if( active != null )
|
||||
|
|
@ -193,7 +193,7 @@ public partial class SettingsInterface
|
|||
else if( ( row -= activeResolved ) < activeMeta )
|
||||
{
|
||||
var (manip, mod) = activeCollection!.MetaManipulations.Manipulations.ElementAt( row );
|
||||
DrawLine( manip.IdentifierString(), mod.Data.Meta.Name );
|
||||
DrawLine( manip.ToString(), mod.Data.Meta.Name );
|
||||
}
|
||||
else if( ( row -= activeMeta ) < forcedResolved )
|
||||
{
|
||||
|
|
@ -204,7 +204,7 @@ public partial class SettingsInterface
|
|||
{
|
||||
row -= forcedResolved;
|
||||
var (manip, mod) = forcedCollection!.MetaManipulations.Manipulations.ElementAt( row );
|
||||
DrawLine( manip.IdentifierString(), mod.Data.Meta.Name );
|
||||
DrawLine( manip.ToString(), mod.Data.Meta.Name );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ public partial class SettingsInterface
|
|||
|
||||
foreach( var manip in manipulations )
|
||||
{
|
||||
ImGui.Text( manip.IdentifierString() );
|
||||
//ImGui.Text( manip.IdentifierString() );
|
||||
}
|
||||
|
||||
indent.Pop( 15f );
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Penumbra.GameData.Enums;
|
|||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.Meta;
|
||||
using Penumbra.Meta.Files;
|
||||
using Penumbra.Meta.Manipulations;
|
||||
using Penumbra.UI.Custom;
|
||||
using Penumbra.Util;
|
||||
using ObjectType = Penumbra.GameData.Enums.ObjectType;
|
||||
|
|
@ -223,32 +224,32 @@ namespace Penumbra.UI
|
|||
private bool DrawEqpRow( int manipIdx, IList< MetaManipulation > list )
|
||||
{
|
||||
var ret = false;
|
||||
var id = list[ manipIdx ].EqpIdentifier;
|
||||
var val = list[ manipIdx ].EqpValue;
|
||||
|
||||
if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
{
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
var defaults = ( EqpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
||||
var attributes = Eqp.EqpAttributes[ id.Slot ];
|
||||
|
||||
foreach( var flag in attributes )
|
||||
{
|
||||
var name = flag.ToLocalName();
|
||||
var tmp = val.HasFlag( flag );
|
||||
if( PrintCheckBox( $"{name}##manip", ref tmp, defaults.HasFlag( flag ) ) && _editMode && tmp != val.HasFlag( flag ) )
|
||||
{
|
||||
list[ manipIdx ] = MetaManipulation.Eqp( id.Slot, id.SetId, tmp ? val | flag : val & ~flag );
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Text( ObjectType.Equipment.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.SetId.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.Slot.ToString() );
|
||||
//var id = list[ manipIdx ].EqpIdentifier;
|
||||
//var val = list[ manipIdx ].EqpValue;
|
||||
//
|
||||
//if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
//{
|
||||
// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
// var defaults = ( EqpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
||||
// var attributes = Eqp.EqpAttributes[ id.Slot ];
|
||||
//
|
||||
// foreach( var flag in attributes )
|
||||
// {
|
||||
// var name = flag.ToLocalName();
|
||||
// var tmp = val.HasFlag( flag );
|
||||
// if( PrintCheckBox( $"{name}##manip", ref tmp, defaults.HasFlag( flag ) ) && _editMode && tmp != val.HasFlag( flag ) )
|
||||
// {
|
||||
// list[ manipIdx ] = MetaManipulation.Eqp( id.Slot, id.SetId, tmp ? val | flag : val & ~flag );
|
||||
// ret = true;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//ImGui.Text( ObjectType.Equipment.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.SetId.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.Slot.ToString() );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -256,42 +257,42 @@ namespace Penumbra.UI
|
|||
{
|
||||
var defaults = ( GmpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
||||
var ret = false;
|
||||
var id = list[ manipIdx ].GmpIdentifier;
|
||||
var val = list[ manipIdx ].GmpValue;
|
||||
|
||||
if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
{
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
var enabled = val.Enabled;
|
||||
var animated = val.Animated;
|
||||
var rotationA = val.RotationA;
|
||||
var rotationB = val.RotationB;
|
||||
var rotationC = val.RotationC;
|
||||
ushort unk = val.UnknownTotal;
|
||||
|
||||
ret |= PrintCheckBox( "Visor Enabled##manip", ref enabled, defaults.Enabled ) && enabled != val.Enabled;
|
||||
ret |= PrintCheckBox( "Visor Animated##manip", ref animated, defaults.Animated );
|
||||
ret |= DrawInputWithDefault( "Rotation A##manip", ref rotationA, defaults.RotationA, 0x3FF );
|
||||
ret |= DrawInputWithDefault( "Rotation B##manip", ref rotationB, defaults.RotationB, 0x3FF );
|
||||
ret |= DrawInputWithDefault( "Rotation C##manip", ref rotationC, defaults.RotationC, 0x3FF );
|
||||
ret |= DrawInputWithDefault( "Unknown Byte##manip", ref unk, defaults.UnknownTotal, 0xFF );
|
||||
|
||||
if( ret && _editMode )
|
||||
{
|
||||
list[ manipIdx ] = MetaManipulation.Gmp( id.SetId,
|
||||
new GmpEntry
|
||||
{
|
||||
Animated = animated, Enabled = enabled, UnknownTotal = ( byte )unk,
|
||||
RotationA = rotationA, RotationB = rotationB, RotationC = rotationC,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Text( ObjectType.Equipment.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.SetId.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( EquipSlot.Head.ToString() );
|
||||
//var id = list[ manipIdx ].GmpIdentifier;
|
||||
//var val = list[ manipIdx ].GmpValue;
|
||||
//
|
||||
//if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
//{
|
||||
// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
// var enabled = val.Enabled;
|
||||
// var animated = val.Animated;
|
||||
// var rotationA = val.RotationA;
|
||||
// var rotationB = val.RotationB;
|
||||
// var rotationC = val.RotationC;
|
||||
// ushort unk = val.UnknownTotal;
|
||||
//
|
||||
// ret |= PrintCheckBox( "Visor Enabled##manip", ref enabled, defaults.Enabled ) && enabled != val.Enabled;
|
||||
// ret |= PrintCheckBox( "Visor Animated##manip", ref animated, defaults.Animated );
|
||||
// ret |= DrawInputWithDefault( "Rotation A##manip", ref rotationA, defaults.RotationA, 0x3FF );
|
||||
// ret |= DrawInputWithDefault( "Rotation B##manip", ref rotationB, defaults.RotationB, 0x3FF );
|
||||
// ret |= DrawInputWithDefault( "Rotation C##manip", ref rotationC, defaults.RotationC, 0x3FF );
|
||||
// ret |= DrawInputWithDefault( "Unknown Byte##manip", ref unk, defaults.UnknownTotal, 0xFF );
|
||||
//
|
||||
// if( ret && _editMode )
|
||||
// {
|
||||
// list[ manipIdx ] = MetaManipulation.Gmp( id.SetId,
|
||||
// new GmpEntry
|
||||
// {
|
||||
// Animated = animated, Enabled = enabled, UnknownTotal = ( byte )unk,
|
||||
// RotationA = rotationA, RotationB = rotationB, RotationC = rotationC,
|
||||
// } );
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//ImGui.Text( ObjectType.Equipment.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.SetId.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( EquipSlot.Head.ToString() );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -366,36 +367,36 @@ namespace Penumbra.UI
|
|||
{
|
||||
var defaults = ( EqdpEntry )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
||||
var ret = false;
|
||||
var id = list[ manipIdx ].EqdpIdentifier;
|
||||
var val = list[ manipIdx ].EqdpValue;
|
||||
|
||||
if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
{
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
var (bit1, bit2) = GetEqdpBits( id.Slot, val );
|
||||
var (defBit1, defBit2) = GetEqdpBits( id.Slot, defaults );
|
||||
|
||||
ret |= PrintCheckBox( "Bit 1##manip", ref bit1, defBit1 );
|
||||
ret |= PrintCheckBox( "Bit 2##manip", ref bit2, defBit2 );
|
||||
|
||||
if( ret && _editMode )
|
||||
{
|
||||
list[ manipIdx ] = MetaManipulation.Eqdp( id.Slot, id.GenderRace, id.SetId, SetEqdpBits( id.Slot, val, bit1, bit2 ) );
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Text( id.Slot.IsAccessory()
|
||||
? ObjectType.Accessory.ToString()
|
||||
: ObjectType.Equipment.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.SetId.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.Slot.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
var (gender, race) = id.GenderRace.Split();
|
||||
ImGui.Text( race.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( gender.ToString() );
|
||||
//var id = list[ manipIdx ].EqdpIdentifier;
|
||||
//var val = list[ manipIdx ].EqdpValue;
|
||||
//
|
||||
//if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
//{
|
||||
// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
// var (bit1, bit2) = GetEqdpBits( id.Slot, val );
|
||||
// var (defBit1, defBit2) = GetEqdpBits( id.Slot, defaults );
|
||||
//
|
||||
// ret |= PrintCheckBox( "Bit 1##manip", ref bit1, defBit1 );
|
||||
// ret |= PrintCheckBox( "Bit 2##manip", ref bit2, defBit2 );
|
||||
//
|
||||
// if( ret && _editMode )
|
||||
// {
|
||||
// list[ manipIdx ] = MetaManipulation.Eqdp( id.Slot, id.GenderRace, id.SetId, SetEqdpBits( id.Slot, val, bit1, bit2 ) );
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//ImGui.Text( id.Slot.IsAccessory()
|
||||
// ? ObjectType.Accessory.ToString()
|
||||
// : ObjectType.Equipment.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.SetId.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.Slot.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//var (gender, race) = id.GenderRace.Split();
|
||||
//ImGui.Text( race.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( gender.ToString() );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -403,30 +404,30 @@ namespace Penumbra.UI
|
|||
{
|
||||
var defaults = ( ushort )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
||||
var ret = false;
|
||||
var id = list[ manipIdx ].EstIdentifier;
|
||||
var val = list[ manipIdx ].EstValue;
|
||||
if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
{
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
if( DrawInputWithDefault( "No Idea what this does!##manip", ref val, defaults, ushort.MaxValue ) && _editMode )
|
||||
{
|
||||
list[ manipIdx ] = new MetaManipulation( id.Value, val );
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Text( id.ObjectType.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.PrimaryId.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.ObjectType == ObjectType.Equipment
|
||||
? id.EquipSlot.ToString()
|
||||
: id.BodySlot.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
var (gender, race) = id.GenderRace.Split();
|
||||
ImGui.Text( race.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( gender.ToString() );
|
||||
//var id = list[ manipIdx ].EstIdentifier;
|
||||
//var val = list[ manipIdx ].EstValue;
|
||||
//if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
//{
|
||||
// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
// if( DrawInputWithDefault( "No Idea what this does!##manip", ref val, defaults, ushort.MaxValue ) && _editMode )
|
||||
// {
|
||||
// list[ manipIdx ] = new MetaManipulation( id.Value, val );
|
||||
// ret = true;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//ImGui.Text( id.ObjectType.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.PrimaryId.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.ObjectType == ObjectType.Equipment
|
||||
// ? id.EquipSlot.ToString()
|
||||
// : id.BodySlot.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//var (gender, race) = id.GenderRace.Split();
|
||||
//ImGui.Text( race.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( gender.ToString() );
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -435,58 +436,58 @@ namespace Penumbra.UI
|
|||
{
|
||||
var defaults = ( ImcFile.ImageChangeData )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
||||
var ret = false;
|
||||
var id = list[ manipIdx ].ImcIdentifier;
|
||||
var val = list[ manipIdx ].ImcValue;
|
||||
|
||||
if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
{
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
ushort materialId = val.MaterialId;
|
||||
ushort vfxId = val.VfxId;
|
||||
ushort decalId = val.DecalId;
|
||||
var soundId = ( ushort )( val.SoundId >> 10 );
|
||||
var attributeMask = val.AttributeMask;
|
||||
var materialAnimationId = ( ushort )( val.MaterialAnimationId >> 12 );
|
||||
ret |= DrawInputWithDefault( "Material Id", ref materialId, defaults.MaterialId, byte.MaxValue );
|
||||
ret |= DrawInputWithDefault( "Vfx Id", ref vfxId, defaults.VfxId, byte.MaxValue );
|
||||
ret |= DrawInputWithDefault( "Decal Id", ref decalId, defaults.DecalId, byte.MaxValue );
|
||||
ret |= DrawInputWithDefault( "Sound Id", ref soundId, defaults.SoundId, 0x3F );
|
||||
ret |= DrawInputWithDefault( "Attribute Mask", ref attributeMask, defaults.AttributeMask, 0x3FF );
|
||||
ret |= DrawInputWithDefault( "Material Animation Id", ref materialAnimationId, defaults.MaterialAnimationId,
|
||||
byte.MaxValue );
|
||||
|
||||
if( ret && _editMode )
|
||||
{
|
||||
var value = ImcExtensions.FromValues( ( byte )materialId, ( byte )decalId, attributeMask, ( byte )soundId,
|
||||
( byte )vfxId, ( byte )materialAnimationId );
|
||||
list[ manipIdx ] = new MetaManipulation( id.Value, value.ToInteger() );
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Text( id.ObjectType.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.PrimaryId.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
if( id.ObjectType == ObjectType.Accessory
|
||||
|| id.ObjectType == ObjectType.Equipment )
|
||||
{
|
||||
ImGui.Text( id.ObjectType == ObjectType.Equipment
|
||||
|| id.ObjectType == ObjectType.Accessory
|
||||
? id.EquipSlot.ToString()
|
||||
: id.BodySlot.ToString() );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
if( id.ObjectType != ObjectType.Equipment
|
||||
&& id.ObjectType != ObjectType.Accessory )
|
||||
{
|
||||
ImGui.Text( id.SecondaryId.ToString() );
|
||||
}
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.Variant.ToString() );
|
||||
//var id = list[ manipIdx ].ImcIdentifier;
|
||||
//var val = list[ manipIdx ].ImcValue;
|
||||
//
|
||||
//if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
//{
|
||||
// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
// ushort materialId = val.MaterialId;
|
||||
// ushort vfxId = val.VfxId;
|
||||
// ushort decalId = val.DecalId;
|
||||
// var soundId = ( ushort )( val.SoundId >> 10 );
|
||||
// var attributeMask = val.AttributeMask;
|
||||
// var materialAnimationId = ( ushort )( val.MaterialAnimationId >> 12 );
|
||||
// ret |= DrawInputWithDefault( "Material Id", ref materialId, defaults.MaterialId, byte.MaxValue );
|
||||
// ret |= DrawInputWithDefault( "Vfx Id", ref vfxId, defaults.VfxId, byte.MaxValue );
|
||||
// ret |= DrawInputWithDefault( "Decal Id", ref decalId, defaults.DecalId, byte.MaxValue );
|
||||
// ret |= DrawInputWithDefault( "Sound Id", ref soundId, defaults.SoundId, 0x3F );
|
||||
// ret |= DrawInputWithDefault( "Attribute Mask", ref attributeMask, defaults.AttributeMask, 0x3FF );
|
||||
// ret |= DrawInputWithDefault( "Material Animation Id", ref materialAnimationId, defaults.MaterialAnimationId,
|
||||
// byte.MaxValue );
|
||||
//
|
||||
// if( ret && _editMode )
|
||||
// {
|
||||
// var value = ImcExtensions.FromValues( ( byte )materialId, ( byte )decalId, attributeMask, ( byte )soundId,
|
||||
// ( byte )vfxId, ( byte )materialAnimationId );
|
||||
// list[ manipIdx ] = new MetaManipulation( id.Value, value.ToInteger() );
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//ImGui.Text( id.ObjectType.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.PrimaryId.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//if( id.ObjectType == ObjectType.Accessory
|
||||
// || id.ObjectType == ObjectType.Equipment )
|
||||
//{
|
||||
// ImGui.Text( id.ObjectType == ObjectType.Equipment
|
||||
// || id.ObjectType == ObjectType.Accessory
|
||||
// ? id.EquipSlot.ToString()
|
||||
// : id.BodySlot.ToString() );
|
||||
//}
|
||||
//
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.TableNextColumn();
|
||||
//if( id.ObjectType != ObjectType.Equipment
|
||||
// && id.ObjectType != ObjectType.Accessory )
|
||||
//{
|
||||
// ImGui.Text( id.SecondaryId.ToString() );
|
||||
//}
|
||||
//
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.Variant.ToString() );
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -494,45 +495,45 @@ namespace Penumbra.UI
|
|||
{
|
||||
var defaults = ( float )Penumbra.MetaDefaults.GetDefaultValue( list[ manipIdx ] )!;
|
||||
var ret = false;
|
||||
var id = list[ manipIdx ].RspIdentifier;
|
||||
var val = list[ manipIdx ].RspValue;
|
||||
|
||||
if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
{
|
||||
using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
if( DefaultButton(
|
||||
$"{( _editMode ? "Set to " : "" )}Default: {defaults:F3}##scaleManip", ref val, defaults )
|
||||
&& _editMode )
|
||||
{
|
||||
list[ manipIdx ] = MetaManipulation.Rsp( id.SubRace, id.Attribute, defaults );
|
||||
ret = true;
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale );
|
||||
if( ImGui.InputFloat( "Scale###manip", ref val, 0, 0, "%.3f",
|
||||
_editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly )
|
||||
&& val >= 0
|
||||
&& val <= 5
|
||||
&& _editMode )
|
||||
{
|
||||
list[ manipIdx ] = MetaManipulation.Rsp( id.SubRace, id.Attribute, val );
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Text( id.Attribute.ToUngenderedString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.SubRace.ToString() );
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.Text( id.Attribute.ToGender().ToString() );
|
||||
//var id = list[ manipIdx ].RspIdentifier;
|
||||
//var val = list[ manipIdx ].RspValue;
|
||||
//
|
||||
//if( ImGui.BeginPopup( $"##MetaPopup{manipIdx}" ) )
|
||||
//{
|
||||
// using var raii = ImGuiRaii.DeferredEnd( ImGui.EndPopup );
|
||||
// if( DefaultButton(
|
||||
// $"{( _editMode ? "Set to " : "" )}Default: {defaults:F3}##scaleManip", ref val, defaults )
|
||||
// && _editMode )
|
||||
// {
|
||||
// list[ manipIdx ] = MetaManipulation.Rsp( id.SubRace, id.Attribute, defaults );
|
||||
// ret = true;
|
||||
// }
|
||||
//
|
||||
// ImGui.SetNextItemWidth( 50 * ImGuiHelpers.GlobalScale );
|
||||
// if( ImGui.InputFloat( "Scale###manip", ref val, 0, 0, "%.3f",
|
||||
// _editMode ? ImGuiInputTextFlags.EnterReturnsTrue : ImGuiInputTextFlags.ReadOnly )
|
||||
// && val >= 0
|
||||
// && val <= 5
|
||||
// && _editMode )
|
||||
// {
|
||||
// list[ manipIdx ] = MetaManipulation.Rsp( id.SubRace, id.Attribute, val );
|
||||
// ret = true;
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//ImGui.Text( id.Attribute.ToUngenderedString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.SubRace.ToString() );
|
||||
//ImGui.TableNextColumn();
|
||||
//ImGui.Text( id.Attribute.ToGender().ToString() );
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawManipulationRow( ref int manipIdx, IList< MetaManipulation > list, ref int count )
|
||||
{
|
||||
var type = list[ manipIdx ].Type;
|
||||
var type = list[ manipIdx ].ManipulationType;
|
||||
|
||||
if( _editMode )
|
||||
{
|
||||
|
|
@ -553,40 +554,40 @@ namespace Penumbra.UI
|
|||
ImGui.TableNextColumn();
|
||||
|
||||
var changes = false;
|
||||
switch( type )
|
||||
{
|
||||
case MetaType.Eqp:
|
||||
changes = DrawEqpRow( manipIdx, list );
|
||||
break;
|
||||
case MetaType.Gmp:
|
||||
changes = DrawGmpRow( manipIdx, list );
|
||||
break;
|
||||
case MetaType.Eqdp:
|
||||
changes = DrawEqdpRow( manipIdx, list );
|
||||
break;
|
||||
case MetaType.Est:
|
||||
changes = DrawEstRow( manipIdx, list );
|
||||
break;
|
||||
case MetaType.Imc:
|
||||
changes = DrawImcRow( manipIdx, list );
|
||||
break;
|
||||
case MetaType.Rsp:
|
||||
changes = DrawRspRow( manipIdx, list );
|
||||
break;
|
||||
}
|
||||
|
||||
ImGui.TableSetColumnIndex( 9 );
|
||||
if( ImGui.Selectable( $"{list[ manipIdx ].Value:X}##{manipIdx}" ) )
|
||||
{
|
||||
ImGui.OpenPopup( $"##MetaPopup{manipIdx}" );
|
||||
}
|
||||
|
||||
ImGui.TableNextRow();
|
||||
//switch( type )
|
||||
//{
|
||||
// case MetaType.Eqp:
|
||||
// changes = DrawEqpRow( manipIdx, list );
|
||||
// break;
|
||||
// case MetaType.Gmp:
|
||||
// changes = DrawGmpRow( manipIdx, list );
|
||||
// break;
|
||||
// case MetaType.Eqdp:
|
||||
// changes = DrawEqdpRow( manipIdx, list );
|
||||
// break;
|
||||
// case MetaType.Est:
|
||||
// changes = DrawEstRow( manipIdx, list );
|
||||
// break;
|
||||
// case MetaType.Imc:
|
||||
// changes = DrawImcRow( manipIdx, list );
|
||||
// break;
|
||||
// case MetaType.Rsp:
|
||||
// changes = DrawRspRow( manipIdx, list );
|
||||
// break;
|
||||
//}
|
||||
//
|
||||
//ImGui.TableSetColumnIndex( 9 );
|
||||
//if( ImGui.Selectable( $"{list[ manipIdx ].Value:X}##{manipIdx}" ) )
|
||||
//{
|
||||
// ImGui.OpenPopup( $"##MetaPopup{manipIdx}" );
|
||||
//}
|
||||
//
|
||||
//ImGui.TableNextRow();
|
||||
return changes;
|
||||
}
|
||||
|
||||
|
||||
private MetaType DrawNewTypeSelection()
|
||||
private MetaManipulation.Type DrawNewTypeSelection()
|
||||
{
|
||||
ImGui.RadioButton( "IMC##newManipType", ref _newManipTypeIdx, 1 );
|
||||
ImGui.SameLine();
|
||||
|
|
@ -599,7 +600,7 @@ namespace Penumbra.UI
|
|||
ImGui.RadioButton( "GMP##newManipType", ref _newManipTypeIdx, 5 );
|
||||
ImGui.SameLine();
|
||||
ImGui.RadioButton( "RSP##newManipType", ref _newManipTypeIdx, 6 );
|
||||
return ( MetaType )_newManipTypeIdx;
|
||||
return ( MetaManipulation.Type )_newManipTypeIdx;
|
||||
}
|
||||
|
||||
private bool DrawNewManipulationPopup( string popupName, IList< MetaManipulation > list, ref int count )
|
||||
|
|
@ -615,7 +616,7 @@ namespace Penumbra.UI
|
|||
MetaManipulation? newManip = null;
|
||||
switch( manipType )
|
||||
{
|
||||
case MetaType.Imc:
|
||||
case MetaManipulation.Type.Imc:
|
||||
{
|
||||
RestrictedInputInt( "Set Id##newManipImc", ref _newManipSetId, 0, ushort.MaxValue );
|
||||
RestrictedInputInt( "Variant##newManipImc", ref _newManipVariant, 0, byte.MaxValue );
|
||||
|
|
@ -625,39 +626,39 @@ namespace Penumbra.UI
|
|||
{
|
||||
case ObjectType.Equipment:
|
||||
CustomCombo( "Equipment Slot", EqdpEquipSlots, out equipSlot, ref _newManipEquipSlot );
|
||||
newManip = MetaManipulation.Imc( equipSlot, _newManipSetId, _newManipVariant,
|
||||
new ImcFile.ImageChangeData() );
|
||||
//newManip = MetaManipulation.Imc( equipSlot, _newManipSetId, _newManipVariant,
|
||||
// new ImcFile.ImageChangeData() );
|
||||
break;
|
||||
case ObjectType.DemiHuman:
|
||||
case ObjectType.Weapon:
|
||||
case ObjectType.Monster:
|
||||
RestrictedInputInt( "Secondary Id##newManipImc", ref _newManipSecondaryId, 0, ushort.MaxValue );
|
||||
CustomCombo( "Body Slot", ImcBodySlots, out var bodySlot, ref _newManipBodySlot );
|
||||
newManip = MetaManipulation.Imc( objectType, bodySlot, _newManipSetId, _newManipSecondaryId,
|
||||
_newManipVariant, new ImcFile.ImageChangeData() );
|
||||
//newManip = MetaManipulation.Imc( objectType, bodySlot, _newManipSetId, _newManipSecondaryId,
|
||||
// _newManipVariant, new ImcFile.ImageChangeData() );
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case MetaType.Eqdp:
|
||||
case MetaManipulation.Type.Eqdp:
|
||||
{
|
||||
RestrictedInputInt( "Set Id##newManipEqdp", ref _newManipSetId, 0, ushort.MaxValue );
|
||||
CustomCombo( "Equipment Slot", EqdpEquipSlots, out var equipSlot, ref _newManipEquipSlot );
|
||||
CustomCombo( "Race", Races, out var race, ref _newManipRace );
|
||||
CustomCombo( "Gender", Genders, out var gender, ref _newManipGender );
|
||||
newManip = MetaManipulation.Eqdp( equipSlot, Names.CombinedRace( gender, race ), ( ushort )_newManipSetId,
|
||||
new EqdpEntry() );
|
||||
//newManip = MetaManipulation.Eqdp( equipSlot, Names.CombinedRace( gender, race ), ( ushort )_newManipSetId,
|
||||
// new EqdpEntry() );
|
||||
break;
|
||||
}
|
||||
case MetaType.Eqp:
|
||||
case MetaManipulation.Type.Eqp:
|
||||
{
|
||||
RestrictedInputInt( "Set Id##newManipEqp", ref _newManipSetId, 0, ushort.MaxValue );
|
||||
CustomCombo( "Equipment Slot", EqpEquipSlots, out var equipSlot, ref _newManipEquipSlot );
|
||||
newManip = MetaManipulation.Eqp( equipSlot, ( ushort )_newManipSetId, 0 );
|
||||
//newManip = MetaManipulation.Eqp( equipSlot, ( ushort )_newManipSetId, 0 );
|
||||
break;
|
||||
}
|
||||
case MetaType.Est:
|
||||
case MetaManipulation.Type.Est:
|
||||
{
|
||||
RestrictedInputInt( "Set Id##newManipEst", ref _newManipSetId, 0, ushort.MaxValue );
|
||||
CustomCombo( "Object Type", ObjectTypes, out var objectType, ref _newManipObjectType );
|
||||
|
|
@ -675,47 +676,47 @@ namespace Penumbra.UI
|
|||
|
||||
CustomCombo( "Race", Races, out var race, ref _newManipRace );
|
||||
CustomCombo( "Gender", Genders, out var gender, ref _newManipGender );
|
||||
newManip = MetaManipulation.Est( objectType, equipSlot, Names.CombinedRace( gender, race ), bodySlot,
|
||||
( ushort )_newManipSetId, 0 );
|
||||
//newManip = MetaManipulation.Est( objectType, equipSlot, Names.CombinedRace( gender, race ), bodySlot,
|
||||
// ( ushort )_newManipSetId, 0 );
|
||||
break;
|
||||
}
|
||||
case MetaType.Gmp:
|
||||
case MetaManipulation.Type.Gmp:
|
||||
RestrictedInputInt( "Set Id##newManipGmp", ref _newManipSetId, 0, ushort.MaxValue );
|
||||
newManip = MetaManipulation.Gmp( ( ushort )_newManipSetId, new GmpEntry() );
|
||||
//newManip = MetaManipulation.Gmp( ( ushort )_newManipSetId, new GmpEntry() );
|
||||
break;
|
||||
case MetaType.Rsp:
|
||||
case MetaManipulation.Type.Rsp:
|
||||
CustomCombo( "Subrace", Subraces, out var subRace, ref _newManipSubrace );
|
||||
CustomCombo( "Attribute", RspAttributes, out var rspAttribute, ref _newManipAttribute );
|
||||
newManip = MetaManipulation.Rsp( subRace, rspAttribute, 1f );
|
||||
//newManip = MetaManipulation.Rsp( subRace, rspAttribute, 1f );
|
||||
break;
|
||||
}
|
||||
|
||||
if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 )
|
||||
&& newManip != null
|
||||
&& list.All( m => m.Identifier != newManip.Value.Identifier ) )
|
||||
{
|
||||
var def = Penumbra.MetaDefaults.GetDefaultValue( newManip.Value );
|
||||
if( def != null )
|
||||
{
|
||||
var manip = newManip.Value.Type switch
|
||||
{
|
||||
MetaType.Est => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
|
||||
MetaType.Eqp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
|
||||
MetaType.Eqdp => new MetaManipulation( newManip.Value.Identifier, (ushort) def ),
|
||||
MetaType.Gmp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
|
||||
MetaType.Imc => new MetaManipulation( newManip.Value.Identifier,
|
||||
( ( ImcFile.ImageChangeData )def ).ToInteger() ),
|
||||
MetaType.Rsp => MetaManipulation.Rsp( newManip.Value.RspIdentifier.SubRace,
|
||||
newManip.Value.RspIdentifier.Attribute, ( float )def ),
|
||||
_ => throw new InvalidEnumArgumentException(),
|
||||
};
|
||||
list.Add( manip );
|
||||
change = true;
|
||||
++count;
|
||||
}
|
||||
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
//if( ImGui.Button( "Create Manipulation##newManip", Vector2.UnitX * -1 )
|
||||
// && newManip != null
|
||||
// && list.All( m => m.Identifier != newManip.Value.Identifier ) )
|
||||
//{
|
||||
// var def = Penumbra.MetaDefaults.GetDefaultValue( newManip.Value );
|
||||
// if( def != null )
|
||||
// {
|
||||
// var manip = newManip.Value.Type switch
|
||||
// {
|
||||
// MetaType.Est => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
|
||||
// MetaType.Eqp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
|
||||
// MetaType.Eqdp => new MetaManipulation( newManip.Value.Identifier, (ushort) def ),
|
||||
// MetaType.Gmp => new MetaManipulation( newManip.Value.Identifier, ( ulong )def ),
|
||||
// MetaType.Imc => new MetaManipulation( newManip.Value.Identifier,
|
||||
// ( ( ImcFile.ImageChangeData )def ).ToInteger() ),
|
||||
// MetaType.Rsp => MetaManipulation.Rsp( newManip.Value.RspIdentifier.SubRace,
|
||||
// newManip.Value.RspIdentifier.Attribute, ( float )def ),
|
||||
// _ => throw new InvalidEnumArgumentException(),
|
||||
// };
|
||||
// list.Add( manip );
|
||||
// change = true;
|
||||
// ++count;
|
||||
// }
|
||||
//
|
||||
// ImGui.CloseCurrentPopup();
|
||||
//}
|
||||
|
||||
return change;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ public partial class SettingsInterface
|
|||
if( ImGui.IsItemClicked() )
|
||||
{
|
||||
var data = ( ( Interop.Structs.ResourceHandle* )r )->GetData();
|
||||
ImGui.SetClipboardText( string.Join( " ",
|
||||
ImGui.SetClipboardText( ((IntPtr)( ( Interop.Structs.ResourceHandle* )r )->Data->VTable).ToString("X") + string.Join( " ",
|
||||
new ReadOnlySpan< byte >( ( byte* )data.Data, data.Length ).ToArray().Select( b => b.ToString( "X2" ) ) ) );
|
||||
//ImGuiNative.igSetClipboardText( ( byte* )Structs.ResourceHandle.GetData( ( IntPtr )r ) );
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue