mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-13 12:14:17 +01:00
Add functions to re-export meta changes to TexTools .meta and .rgsp formats.
This commit is contained in:
parent
7a09d561e9
commit
3391a8ce71
7 changed files with 383 additions and 24 deletions
|
|
@ -7,7 +7,6 @@ using System.Text.RegularExpressions;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.String;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.Data;
|
namespace Penumbra.GameData.Data;
|
||||||
|
|
||||||
|
|
|
||||||
242
Penumbra/Import/TexToolsMeta.Export.cs
Normal file
242
Penumbra/Import/TexToolsMeta.Export.cs
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using Penumbra.Meta.Files;
|
||||||
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
namespace Penumbra.Import;
|
||||||
|
|
||||||
|
public partial class TexToolsMeta
|
||||||
|
{
|
||||||
|
public static Dictionary< string, byte[] > ConvertToTexTools( IEnumerable< MetaManipulation > manips )
|
||||||
|
{
|
||||||
|
var ret = new Dictionary< string, byte[] >();
|
||||||
|
foreach( var group in manips.GroupBy( ManipToPath ) )
|
||||||
|
{
|
||||||
|
if( group.Key.Length == 0 )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytes = group.Key.EndsWith( ".rgsp" )
|
||||||
|
? WriteRgspFile( group.Key, group )
|
||||||
|
: WriteMetaFile( group.Key, group );
|
||||||
|
if( bytes.Length == 0 )
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret.Add( group.Key, bytes );
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] WriteRgspFile( string path, IEnumerable< MetaManipulation > manips )
|
||||||
|
{
|
||||||
|
var list = manips.GroupBy( m => m.Rsp.Attribute ).ToDictionary( m => m.Key, m => m.Last().Rsp );
|
||||||
|
using var m = new MemoryStream( 45 );
|
||||||
|
using var b = new BinaryWriter( m );
|
||||||
|
// Version
|
||||||
|
b.Write( byte.MaxValue );
|
||||||
|
b.Write( ( ushort )2 );
|
||||||
|
|
||||||
|
var race = list.First().Value.SubRace;
|
||||||
|
var gender = list.First().Value.Attribute.ToGender();
|
||||||
|
b.Write( ( byte )(race - 1) ); // offset by one due to Unknown
|
||||||
|
b.Write( ( byte )(gender - 1) ); // offset by one due to Unknown
|
||||||
|
|
||||||
|
void Add( params RspAttribute[] attributes )
|
||||||
|
{
|
||||||
|
foreach( var attribute in attributes )
|
||||||
|
{
|
||||||
|
var value = list.TryGetValue( attribute, out var tmp ) ? tmp.Entry : CmpFile.GetDefault( race, attribute );
|
||||||
|
b.Write( value );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( gender == Gender.Male )
|
||||||
|
{
|
||||||
|
Add( RspAttribute.MaleMinSize, RspAttribute.MaleMaxSize, RspAttribute.MaleMinTail, RspAttribute.MaleMaxTail );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Add( RspAttribute.FemaleMinSize, RspAttribute.FemaleMaxSize, RspAttribute.FemaleMinTail, RspAttribute.FemaleMaxTail );
|
||||||
|
Add( RspAttribute.BustMinX, RspAttribute.BustMinY, RspAttribute.BustMinZ, RspAttribute.BustMaxX, RspAttribute.BustMaxY, RspAttribute.BustMaxZ );
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.GetBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static byte[] WriteMetaFile( string path, IEnumerable< MetaManipulation > manips )
|
||||||
|
{
|
||||||
|
var filteredManips = manips.GroupBy( m => m.ManipulationType ).ToDictionary( p => p.Key, p => p.Select( x => x ) );
|
||||||
|
|
||||||
|
using var m = new MemoryStream();
|
||||||
|
using var b = new BinaryWriter( m );
|
||||||
|
|
||||||
|
// Header
|
||||||
|
// Current TT Metadata version.
|
||||||
|
b.Write( 2u );
|
||||||
|
|
||||||
|
// Null-terminated ASCII path.
|
||||||
|
var utf8Path = Encoding.ASCII.GetBytes( path );
|
||||||
|
b.Write( utf8Path );
|
||||||
|
b.Write( ( byte )0 );
|
||||||
|
|
||||||
|
// Number of Headers
|
||||||
|
b.Write( ( uint )filteredManips.Count );
|
||||||
|
// Current TT Size of Headers
|
||||||
|
b.Write( ( uint )12 );
|
||||||
|
|
||||||
|
// Start of Header Entries for some reason, which is absolutely useless.
|
||||||
|
var headerStart = b.BaseStream.Position + 4;
|
||||||
|
b.Write( ( uint )headerStart );
|
||||||
|
|
||||||
|
var offset = ( uint )( b.BaseStream.Position + 12 * filteredManips.Count );
|
||||||
|
foreach( var (header, data) in filteredManips )
|
||||||
|
{
|
||||||
|
b.Write( ( uint )header );
|
||||||
|
b.Write( offset );
|
||||||
|
|
||||||
|
var size = WriteData( b, offset, header, data );
|
||||||
|
b.Write( size );
|
||||||
|
offset += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint WriteData( BinaryWriter b, uint offset, MetaManipulation.Type type, IEnumerable< MetaManipulation > manips )
|
||||||
|
{
|
||||||
|
var oldPos = b.BaseStream.Position;
|
||||||
|
b.Seek( ( int )offset, SeekOrigin.Begin );
|
||||||
|
|
||||||
|
switch( type )
|
||||||
|
{
|
||||||
|
case MetaManipulation.Type.Imc:
|
||||||
|
var allManips = manips.ToList();
|
||||||
|
var baseFile = new ImcFile( allManips[ 0 ].Imc );
|
||||||
|
foreach( var manip in allManips )
|
||||||
|
{
|
||||||
|
manip.Imc.Apply( baseFile );
|
||||||
|
}
|
||||||
|
|
||||||
|
var partIdx = allManips[ 0 ].Imc.ObjectType is ObjectType.Equipment or ObjectType.Accessory
|
||||||
|
? ImcFile.PartIndex( allManips[ 0 ].Imc.EquipSlot )
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
for( var i = 0; i <= baseFile.Count; ++i )
|
||||||
|
{
|
||||||
|
var entry = baseFile.GetEntry( partIdx, i );
|
||||||
|
b.Write( entry.MaterialId );
|
||||||
|
b.Write( entry.DecalId );
|
||||||
|
b.Write( entry.AttributeAndSound );
|
||||||
|
b.Write( entry.VfxId );
|
||||||
|
b.Write( entry.MaterialAnimationId );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Eqdp:
|
||||||
|
foreach( var manip in manips )
|
||||||
|
{
|
||||||
|
b.Write( ( uint )Names.CombinedRace( manip.Eqdp.Gender, manip.Eqdp.Race ) );
|
||||||
|
var entry = ( byte )(( ( uint )manip.Eqdp.Entry >> Eqdp.Offset( manip.Eqdp.Slot ) ) & 0x03);
|
||||||
|
b.Write( entry );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Eqp:
|
||||||
|
foreach( var manip in manips )
|
||||||
|
{
|
||||||
|
var bytes = BitConverter.GetBytes( (ulong) manip.Eqp.Entry );
|
||||||
|
var (numBytes, byteOffset) = Eqp.BytesAndOffset( manip.Eqp.Slot );
|
||||||
|
for( var i = byteOffset; i < numBytes + byteOffset; ++i )
|
||||||
|
b.Write( bytes[ i ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Est:
|
||||||
|
foreach( var manip in manips )
|
||||||
|
{
|
||||||
|
b.Write( ( ushort )Names.CombinedRace( manip.Est.Gender, manip.Est.Race ) );
|
||||||
|
b.Write( manip.Est.SetId );
|
||||||
|
b.Write( manip.Est.Entry );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case MetaManipulation.Type.Gmp:
|
||||||
|
foreach( var manip in manips )
|
||||||
|
{
|
||||||
|
b.Write( ( uint )manip.Gmp.Entry.Value );
|
||||||
|
b.Write( manip.Gmp.Entry.UnknownTotal );
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var size = b.BaseStream.Position - offset;
|
||||||
|
b.Seek( ( int )oldPos, SeekOrigin.Begin );
|
||||||
|
return ( uint )size;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ManipToPath( MetaManipulation manip )
|
||||||
|
=> manip.ManipulationType switch
|
||||||
|
{
|
||||||
|
MetaManipulation.Type.Imc => ManipToPath( manip.Imc ),
|
||||||
|
MetaManipulation.Type.Eqdp => ManipToPath( manip.Eqdp ),
|
||||||
|
MetaManipulation.Type.Eqp => ManipToPath( manip.Eqp ),
|
||||||
|
MetaManipulation.Type.Est => ManipToPath( manip.Est ),
|
||||||
|
MetaManipulation.Type.Gmp => ManipToPath( manip.Gmp ),
|
||||||
|
MetaManipulation.Type.Rsp => ManipToPath( manip.Rsp ),
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
private static string ManipToPath( ImcManipulation manip )
|
||||||
|
{
|
||||||
|
var path = manip.GamePath().ToString();
|
||||||
|
var replacement = manip.ObjectType switch
|
||||||
|
{
|
||||||
|
ObjectType.Accessory => $"_{manip.EquipSlot.ToSuffix()}.meta",
|
||||||
|
ObjectType.Equipment => $"_{manip.EquipSlot.ToSuffix()}.meta",
|
||||||
|
ObjectType.Character => $"_{manip.BodySlot.ToSuffix()}.meta",
|
||||||
|
_ => ".meta",
|
||||||
|
};
|
||||||
|
|
||||||
|
return path.Replace( ".imc", replacement );
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ManipToPath( EqdpManipulation manip )
|
||||||
|
=> manip.Slot.IsAccessory()
|
||||||
|
? $"chara/accessory/a{manip.SetId:D4}/a{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta"
|
||||||
|
: $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta";
|
||||||
|
|
||||||
|
private static string ManipToPath( EqpManipulation manip )
|
||||||
|
=> manip.Slot.IsAccessory()
|
||||||
|
? $"chara/accessory/a{manip.SetId:D4}/a{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta"
|
||||||
|
: $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{manip.Slot.ToSuffix()}.meta";
|
||||||
|
|
||||||
|
private static string ManipToPath( EstManipulation manip )
|
||||||
|
{
|
||||||
|
var raceCode = Names.CombinedRace( manip.Gender, manip.Race ).ToRaceCode();
|
||||||
|
return manip.Slot switch
|
||||||
|
{
|
||||||
|
EstManipulation.EstType.Hair => $"chara/human/c{raceCode}/obj/hair/h{manip.SetId:D4}/c{raceCode}h{manip.SetId:D4}_hir.meta",
|
||||||
|
EstManipulation.EstType.Face => $"chara/human/c{raceCode}/obj/face/h{manip.SetId:D4}/c{raceCode}f{manip.SetId:D4}_fac.meta",
|
||||||
|
EstManipulation.EstType.Body => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Body.ToSuffix()}.meta",
|
||||||
|
EstManipulation.EstType.Head => $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Head.ToSuffix()}.meta",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ManipToPath( GmpManipulation manip )
|
||||||
|
=> $"chara/equipment/e{manip.SetId:D4}/e{manip.SetId:D4}_{EquipSlot.Head.ToSuffix()}.meta";
|
||||||
|
|
||||||
|
|
||||||
|
private static string ManipToPath( RspManipulation manip )
|
||||||
|
=> $"chara/xls/charamake/rgsp/{( int )manip.SubRace - 1}-{( int )manip.Attribute.ToGender() - 1}.rgsp";
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
namespace Penumbra.Import;
|
namespace Penumbra.Import;
|
||||||
|
|
@ -18,7 +19,7 @@ namespace Penumbra.Import;
|
||||||
public partial class TexToolsMeta
|
public partial class TexToolsMeta
|
||||||
{
|
{
|
||||||
// An empty TexToolsMeta.
|
// An empty TexToolsMeta.
|
||||||
public static readonly TexToolsMeta Invalid = new( string.Empty, 0 );
|
public static readonly TexToolsMeta Invalid = new(string.Empty, 0);
|
||||||
|
|
||||||
// The info class determines the files or table locations the changes need to apply to from the filename.
|
// The info class determines the files or table locations the changes need to apply to from the filename.
|
||||||
|
|
||||||
|
|
@ -84,7 +85,7 @@ public partial class TexToolsMeta
|
||||||
// Read a null terminated string from a binary reader.
|
// Read a null terminated string from a binary reader.
|
||||||
private static string ReadNullTerminated( BinaryReader reader )
|
private static string ReadNullTerminated( BinaryReader reader )
|
||||||
{
|
{
|
||||||
var builder = new System.Text.StringBuilder();
|
var builder = new StringBuilder();
|
||||||
for( var c = reader.ReadChar(); c != 0; c = reader.ReadChar() )
|
for( var c = reader.ReadChar(); c != 0; c = reader.ReadChar() )
|
||||||
{
|
{
|
||||||
builder.Append( c );
|
builder.Append( c );
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OtterGui;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
@ -14,26 +13,26 @@ public readonly struct ImcEntry : IEquatable< ImcEntry >
|
||||||
{
|
{
|
||||||
public byte MaterialId { get; init; }
|
public byte MaterialId { get; init; }
|
||||||
public byte DecalId { get; init; }
|
public byte DecalId { get; init; }
|
||||||
private readonly ushort _attributeAndSound;
|
public readonly ushort AttributeAndSound;
|
||||||
public byte VfxId { get; init; }
|
public byte VfxId { get; init; }
|
||||||
public byte MaterialAnimationId { get; init; }
|
public byte MaterialAnimationId { get; init; }
|
||||||
|
|
||||||
public ushort AttributeMask
|
public ushort AttributeMask
|
||||||
{
|
{
|
||||||
get => ( ushort )( _attributeAndSound & 0x3FF );
|
get => ( ushort )( AttributeAndSound & 0x3FF );
|
||||||
init => _attributeAndSound = ( ushort )( ( _attributeAndSound & ~0x3FF ) | ( value & 0x3FF ) );
|
init => AttributeAndSound = ( ushort )( ( AttributeAndSound & ~0x3FF ) | ( value & 0x3FF ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte SoundId
|
public byte SoundId
|
||||||
{
|
{
|
||||||
get => ( byte )( _attributeAndSound >> 10 );
|
get => ( byte )( AttributeAndSound >> 10 );
|
||||||
init => _attributeAndSound = ( ushort )( AttributeMask | ( value << 10 ) );
|
init => AttributeAndSound = ( ushort )( AttributeMask | ( value << 10 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals( ImcEntry other )
|
public bool Equals( ImcEntry other )
|
||||||
=> MaterialId == other.MaterialId
|
=> MaterialId == other.MaterialId
|
||||||
&& DecalId == other.DecalId
|
&& DecalId == other.DecalId
|
||||||
&& _attributeAndSound == other._attributeAndSound
|
&& AttributeAndSound == other.AttributeAndSound
|
||||||
&& VfxId == other.VfxId
|
&& VfxId == other.VfxId
|
||||||
&& MaterialAnimationId == other.MaterialAnimationId;
|
&& MaterialAnimationId == other.MaterialAnimationId;
|
||||||
|
|
||||||
|
|
@ -41,14 +40,14 @@ public readonly struct ImcEntry : IEquatable< ImcEntry >
|
||||||
=> obj is ImcEntry other && Equals( other );
|
=> obj is ImcEntry other && Equals( other );
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
=> HashCode.Combine( MaterialId, DecalId, _attributeAndSound, VfxId, MaterialAnimationId );
|
=> HashCode.Combine( MaterialId, DecalId, AttributeAndSound, VfxId, MaterialAnimationId );
|
||||||
|
|
||||||
[JsonConstructor]
|
[JsonConstructor]
|
||||||
public ImcEntry( byte materialId, byte decalId, ushort attributeMask, byte soundId, byte vfxId, byte materialAnimationId )
|
public ImcEntry( byte materialId, byte decalId, ushort attributeMask, byte soundId, byte vfxId, byte materialAnimationId )
|
||||||
{
|
{
|
||||||
MaterialId = materialId;
|
MaterialId = materialId;
|
||||||
DecalId = decalId;
|
DecalId = decalId;
|
||||||
_attributeAndSound = 0;
|
AttributeAndSound = 0;
|
||||||
VfxId = vfxId;
|
VfxId = vfxId;
|
||||||
MaterialAnimationId = materialAnimationId;
|
MaterialAnimationId = materialAnimationId;
|
||||||
AttributeMask = attributeMask;
|
AttributeMask = attributeMask;
|
||||||
|
|
|
||||||
|
|
@ -232,4 +232,16 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >, ICompa
|
||||||
Type.Imc => Imc.ToString(),
|
Type.Imc => Imc.ToString(),
|
||||||
_ => throw new ArgumentOutOfRangeException(),
|
_ => throw new ArgumentOutOfRangeException(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public string EntryToString()
|
||||||
|
=> ManipulationType switch
|
||||||
|
{
|
||||||
|
Type.Imc => $"{Imc.Entry.DecalId}-{Imc.Entry.MaterialId}-{Imc.Entry.VfxId}-{Imc.Entry.SoundId}-{Imc.Entry.MaterialAnimationId}-{Imc.Entry.AttributeMask}",
|
||||||
|
Type.Eqdp => $"{(ushort) Eqdp.Entry:X}",
|
||||||
|
Type.Eqp => $"{(ulong)Eqp.Entry:X}",
|
||||||
|
Type.Est => $"{Est.Entry}",
|
||||||
|
Type.Gmp => $"{Gmp.Entry.Value}",
|
||||||
|
Type.Rsp => $"{Rsp.Entry}",
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
|
@ -59,6 +60,37 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteAllTexToolsMeta()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_default.WriteTexToolsMeta( ModPath );
|
||||||
|
foreach( var group in Groups )
|
||||||
|
{
|
||||||
|
var dir = NewOptionDirectory( ModPath, group.Name );
|
||||||
|
if( !dir.Exists )
|
||||||
|
{
|
||||||
|
dir.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var option in group.OfType< SubMod >() )
|
||||||
|
{
|
||||||
|
var optionDir = NewOptionDirectory( dir, option.Name );
|
||||||
|
if( !optionDir.Exists )
|
||||||
|
{
|
||||||
|
optionDir.Create();
|
||||||
|
}
|
||||||
|
|
||||||
|
option.WriteTexToolsMeta( optionDir );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error( $"Error writing TexToolsMeta:\n{e}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// A sub mod is a collection of
|
// A sub mod is a collection of
|
||||||
// - file replacements
|
// - file replacements
|
||||||
|
|
@ -197,5 +229,70 @@ public partial class Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void WriteTexToolsMeta( DirectoryInfo basePath, bool test = false )
|
||||||
|
{
|
||||||
|
var files = TexToolsMeta.ConvertToTexTools( Manipulations );
|
||||||
|
|
||||||
|
foreach( var (file, data) in files )
|
||||||
|
{
|
||||||
|
var path = Path.Combine( basePath.FullName, file );
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory( Path.GetDirectoryName( path )! );
|
||||||
|
File.WriteAllBytes( path, data );
|
||||||
|
}
|
||||||
|
catch( Exception e )
|
||||||
|
{
|
||||||
|
Penumbra.Log.Error( $"Could not write meta file {path}:\n{e}" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( test )
|
||||||
|
{
|
||||||
|
TestMetaWriting( files );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private void TestMetaWriting( Dictionary< string, byte[] > files )
|
||||||
|
{
|
||||||
|
var meta = new HashSet< MetaManipulation >( Manipulations.Count );
|
||||||
|
foreach( var (file, data) in files )
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var x = file.EndsWith( "rgsp" ) ? TexToolsMeta.FromRgspFile( file, data ) : new TexToolsMeta( data );
|
||||||
|
meta.UnionWith( x.MetaManipulations );
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !Manipulations.SetEquals( meta ) )
|
||||||
|
{
|
||||||
|
Penumbra.Log.Information( "Meta Sets do not equal." );
|
||||||
|
foreach( var (m1, m2) in Manipulations.Zip( meta ) )
|
||||||
|
{
|
||||||
|
Penumbra.Log.Information( $"{m1} {m1.EntryToString()} | {m2} {m2.EntryToString()}" );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var m in Manipulations.Skip( meta.Count ) )
|
||||||
|
{
|
||||||
|
Penumbra.Log.Information( $"{m} {m.EntryToString()} " );
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var m in meta.Skip( Manipulations.Count ) )
|
||||||
|
{
|
||||||
|
Penumbra.Log.Information( $"{m} {m.EntryToString()} " );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Penumbra.Log.Information( "Meta Sets are equal." );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,8 +17,12 @@ namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
public partial class ModEditWindow
|
public partial class ModEditWindow
|
||||||
{
|
{
|
||||||
private const string ModelSetIdTooltip = "Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
|
private const string ModelSetIdTooltip =
|
||||||
private const string PrimaryIdTooltip = "Primary ID - You can usually find this as the 'x####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
|
"Model Set ID - You can usually find this as the 'e####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
|
||||||
|
|
||||||
|
private const string PrimaryIdTooltip =
|
||||||
|
"Primary ID - You can usually find this as the 'x####' part of an item path.\nThis should generally not be left <= 1 unless you explicitly want that.";
|
||||||
|
|
||||||
private const string ModelSetIdTooltipShort = "Model Set ID";
|
private const string ModelSetIdTooltipShort = "Model Set ID";
|
||||||
private const string EquipSlotTooltip = "Equip Slot";
|
private const string EquipSlotTooltip = "Equip Slot";
|
||||||
private const string ModelRaceTooltip = "Model Race";
|
private const string ModelRaceTooltip = "Model Race";
|
||||||
|
|
@ -61,6 +65,11 @@ public partial class ModEditWindow
|
||||||
SetFromClipboardButton();
|
SetFromClipboardButton();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
CopyToClipboardButton( "Copy all current manipulations to clipboard.", _iconSize, _editor.Meta.Recombine() );
|
CopyToClipboardButton( "Copy all current manipulations to clipboard.", _iconSize, _editor.Meta.Recombine() );
|
||||||
|
ImGui.SameLine();
|
||||||
|
if( ImGui.Button( "Write as TexTools Files" ) )
|
||||||
|
{
|
||||||
|
_mod!.WriteAllTexToolsMeta();
|
||||||
|
}
|
||||||
|
|
||||||
using var child = ImRaii.Child( "##meta", -Vector2.One, true );
|
using var child = ImRaii.Child( "##meta", -Vector2.One, true );
|
||||||
if( !child )
|
if( !child )
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue