mirror of
https://github.com/xivdev/Penumbra.git
synced 2025-12-12 10:17:22 +01:00
Extract Strings to separate submodule.
This commit is contained in:
parent
bc901f3ff6
commit
35baba18bf
75 changed files with 751 additions and 1657 deletions
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -6,3 +6,7 @@
|
||||||
path = Penumbra.Api
|
path = Penumbra.Api
|
||||||
url = git@github.com:Ottermandias/Penumbra.Api.git
|
url = git@github.com:Ottermandias/Penumbra.Api.git
|
||||||
branch = main
|
branch = main
|
||||||
|
[submodule "Penumbra.String"]
|
||||||
|
path = Penumbra.String
|
||||||
|
url = git@github.com:Ottermandias/Penumbra.String.git
|
||||||
|
branch = main
|
||||||
|
|
|
||||||
152
Penumbra.GameData/Actors/ActorIdentifier.cs
Normal file
152
Penumbra.GameData/Actors/ActorIdentifier.cs
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.String;
|
||||||
|
|
||||||
|
namespace Penumbra.GameData.Actors;
|
||||||
|
|
||||||
|
[StructLayout( LayoutKind.Explicit )]
|
||||||
|
public readonly struct ActorIdentifier : IEquatable< ActorIdentifier >
|
||||||
|
{
|
||||||
|
public static ActorManager? Manager;
|
||||||
|
|
||||||
|
public static readonly ActorIdentifier Invalid = new(IdentifierType.Invalid, 0, 0, 0, ByteString.Empty);
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
[FieldOffset( 0 )] public readonly IdentifierType Type; // All
|
||||||
|
[FieldOffset( 1 )] public readonly ObjectKind Kind; // Npc, Owned
|
||||||
|
[FieldOffset( 2 )] public readonly ushort HomeWorld; // Player, Owned
|
||||||
|
[FieldOffset( 2 )] public readonly ushort Index; // NPC
|
||||||
|
[FieldOffset( 2 )] public readonly SpecialActor Special; // Special
|
||||||
|
[FieldOffset( 4 )] public readonly uint DataId; // Owned, NPC
|
||||||
|
[FieldOffset( 8 )] public readonly ByteString PlayerName; // Player, Owned
|
||||||
|
// @formatter:on
|
||||||
|
|
||||||
|
public ActorIdentifier CreatePermanent()
|
||||||
|
=> new(Type, Kind, Index, DataId, PlayerName.Clone());
|
||||||
|
|
||||||
|
public bool Equals( ActorIdentifier other )
|
||||||
|
{
|
||||||
|
if( Type != other.Type )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Type switch
|
||||||
|
{
|
||||||
|
IdentifierType.Player => HomeWorld == other.HomeWorld && PlayerName.EqualsCi( other.PlayerName ),
|
||||||
|
IdentifierType.Owned => HomeWorld == other.HomeWorld && PlayerName.EqualsCi( other.PlayerName ) && Manager.DataIdEquals( this, other ),
|
||||||
|
IdentifierType.Special => Special == other.Special,
|
||||||
|
IdentifierType.Npc => Index == other.Index && DataId == other.DataId && Manager.DataIdEquals( this, other ),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals( object? obj )
|
||||||
|
=> obj is ActorIdentifier other && Equals( other );
|
||||||
|
|
||||||
|
public bool IsValid
|
||||||
|
=> Type != IdentifierType.Invalid;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> Manager?.ToString( this )
|
||||||
|
?? Type switch
|
||||||
|
{
|
||||||
|
IdentifierType.Player => $"{PlayerName} ({HomeWorld})",
|
||||||
|
IdentifierType.Owned => $"{PlayerName}s {Kind} {DataId} ({HomeWorld})",
|
||||||
|
IdentifierType.Special => ActorManager.ToName( Special ),
|
||||||
|
IdentifierType.Npc =>
|
||||||
|
Index == ushort.MaxValue
|
||||||
|
? $"{Kind} #{DataId}"
|
||||||
|
: $"{Kind} #{DataId} at {Index}",
|
||||||
|
_ => "Invalid",
|
||||||
|
};
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> Type switch
|
||||||
|
{
|
||||||
|
IdentifierType.Player => HashCode.Combine( IdentifierType.Player, PlayerName, HomeWorld ),
|
||||||
|
IdentifierType.Owned => HashCode.Combine( IdentifierType.Owned, Kind, PlayerName, HomeWorld, DataId ),
|
||||||
|
IdentifierType.Special => HashCode.Combine( IdentifierType.Special, Special ),
|
||||||
|
IdentifierType.Npc => HashCode.Combine( IdentifierType.Npc, Kind, Index, DataId ),
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
internal ActorIdentifier( IdentifierType type, ObjectKind kind, ushort index, uint data, ByteString playerName )
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Kind = kind;
|
||||||
|
Special = ( SpecialActor )index;
|
||||||
|
HomeWorld = Index = index;
|
||||||
|
DataId = data;
|
||||||
|
PlayerName = playerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public JObject ToJson()
|
||||||
|
{
|
||||||
|
var ret = new JObject { { nameof( Type ), Type.ToString() } };
|
||||||
|
switch( Type )
|
||||||
|
{
|
||||||
|
case IdentifierType.Player:
|
||||||
|
ret.Add( nameof( PlayerName ), PlayerName.ToString() );
|
||||||
|
ret.Add( nameof( HomeWorld ), HomeWorld );
|
||||||
|
return ret;
|
||||||
|
case IdentifierType.Owned:
|
||||||
|
ret.Add( nameof( PlayerName ), PlayerName.ToString() );
|
||||||
|
ret.Add( nameof( HomeWorld ), HomeWorld );
|
||||||
|
ret.Add( nameof( Kind ), Kind.ToString() );
|
||||||
|
ret.Add( nameof( DataId ), DataId );
|
||||||
|
return ret;
|
||||||
|
case IdentifierType.Special:
|
||||||
|
ret.Add( nameof( Special ), Special.ToString() );
|
||||||
|
return ret;
|
||||||
|
case IdentifierType.Npc:
|
||||||
|
ret.Add( nameof( Kind ), Kind.ToString() );
|
||||||
|
ret.Add( nameof( Index ), Index );
|
||||||
|
ret.Add( nameof( DataId ), DataId );
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ActorManagerExtensions
|
||||||
|
{
|
||||||
|
public static bool DataIdEquals( this ActorManager? manager, ActorIdentifier lhs, ActorIdentifier rhs )
|
||||||
|
{
|
||||||
|
if( lhs.Kind != rhs.Kind )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( lhs.DataId == rhs.DataId )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( manager == null )
|
||||||
|
{
|
||||||
|
return lhs.Kind == rhs.Kind && lhs.DataId == rhs.DataId || lhs.DataId == uint.MaxValue || rhs.DataId == uint.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lhs.Kind switch
|
||||||
|
{
|
||||||
|
ObjectKind.MountType => manager.Mounts.TryGetValue( lhs.DataId, out var lhsName )
|
||||||
|
&& manager.Mounts.TryGetValue( rhs.DataId, out var rhsName )
|
||||||
|
&& lhsName.Equals( rhsName, StringComparison.OrdinalIgnoreCase ),
|
||||||
|
ObjectKind.Companion => manager.Companions.TryGetValue( lhs.DataId, out var lhsName )
|
||||||
|
&& manager.Companions.TryGetValue( rhs.DataId, out var rhsName )
|
||||||
|
&& lhsName.Equals( rhsName, StringComparison.OrdinalIgnoreCase ),
|
||||||
|
ObjectKind.BattleNpc => manager.BNpcs.TryGetValue( lhs.DataId, out var lhsName )
|
||||||
|
&& manager.BNpcs.TryGetValue( rhs.DataId, out var rhsName )
|
||||||
|
&& lhsName.Equals( rhsName, StringComparison.OrdinalIgnoreCase ),
|
||||||
|
ObjectKind.EventNpc => manager.ENpcs.TryGetValue( lhs.DataId, out var lhsName )
|
||||||
|
&& manager.ENpcs.TryGetValue( rhs.DataId, out var rhsName )
|
||||||
|
&& lhsName.Equals( rhsName, StringComparison.OrdinalIgnoreCase ),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
352
Penumbra.GameData/Actors/ActorManager.cs
Normal file
352
Penumbra.GameData/Actors/ActorManager.cs
Normal file
|
|
@ -0,0 +1,352 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Dalamud.Data;
|
||||||
|
using Dalamud.Game.ClientState;
|
||||||
|
using Dalamud.Game.ClientState.Objects;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
|
using Dalamud.Utility;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using Penumbra.String;
|
||||||
|
|
||||||
|
namespace Penumbra.GameData.Actors;
|
||||||
|
|
||||||
|
public class ActorManager
|
||||||
|
{
|
||||||
|
private readonly ObjectTable _objects;
|
||||||
|
private readonly ClientState _clientState;
|
||||||
|
|
||||||
|
public readonly IReadOnlyDictionary< ushort, string > Worlds;
|
||||||
|
public readonly IReadOnlyDictionary< uint, string > Mounts;
|
||||||
|
public readonly IReadOnlyDictionary< uint, string > Companions;
|
||||||
|
public readonly IReadOnlyDictionary< uint, string > BNpcs;
|
||||||
|
public readonly IReadOnlyDictionary< uint, string > ENpcs;
|
||||||
|
|
||||||
|
public IEnumerable< KeyValuePair< ushort, string > > AllWorlds
|
||||||
|
=> Worlds.OrderBy( kvp => kvp.Key ).Prepend( new KeyValuePair< ushort, string >( ushort.MaxValue, "Any World" ) );
|
||||||
|
|
||||||
|
private readonly Func< ushort, short > _toParentIdx;
|
||||||
|
|
||||||
|
public ActorManager( ObjectTable objects, ClientState state, DataManager gameData, Func< ushort, short > toParentIdx )
|
||||||
|
{
|
||||||
|
_objects = objects;
|
||||||
|
_clientState = state;
|
||||||
|
Worlds = gameData.GetExcelSheet< World >()!
|
||||||
|
.Where( w => w.IsPublic && !w.Name.RawData.IsEmpty )
|
||||||
|
.ToDictionary( w => ( ushort )w.RowId, w => w.Name.ToString() );
|
||||||
|
|
||||||
|
Mounts = gameData.GetExcelSheet< Mount >()!
|
||||||
|
.Where( m => m.Singular.RawData.Length > 0 && m.Order >= 0 )
|
||||||
|
.ToDictionary( m => m.RowId, m => CultureInfo.InvariantCulture.TextInfo.ToTitleCase( m.Singular.ToDalamudString().ToString() ) );
|
||||||
|
Companions = gameData.GetExcelSheet< Companion >()!
|
||||||
|
.Where( c => c.Singular.RawData.Length > 0 && c.Order < ushort.MaxValue )
|
||||||
|
.ToDictionary( c => c.RowId, c => CultureInfo.InvariantCulture.TextInfo.ToTitleCase( c.Singular.ToDalamudString().ToString() ) );
|
||||||
|
|
||||||
|
BNpcs = gameData.GetExcelSheet< BNpcName >()!
|
||||||
|
.Where( n => n.Singular.RawData.Length > 0 )
|
||||||
|
.ToDictionary( n => n.RowId, n => CultureInfo.InvariantCulture.TextInfo.ToTitleCase( n.Singular.ToDalamudString().ToString() ) );
|
||||||
|
|
||||||
|
ENpcs = gameData.GetExcelSheet< ENpcResident >()!
|
||||||
|
.Where( e => e.Singular.RawData.Length > 0 )
|
||||||
|
.ToDictionary( e => e.RowId, e => CultureInfo.InvariantCulture.TextInfo.ToTitleCase( e.Singular.ToDalamudString().ToString() ) );
|
||||||
|
|
||||||
|
_toParentIdx = toParentIdx;
|
||||||
|
|
||||||
|
ActorIdentifier.Manager = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorIdentifier FromJson( JObject data )
|
||||||
|
{
|
||||||
|
var type = data[ nameof( ActorIdentifier.Type ) ]?.Value< IdentifierType >() ?? IdentifierType.Invalid;
|
||||||
|
switch( type )
|
||||||
|
{
|
||||||
|
case IdentifierType.Player:
|
||||||
|
{
|
||||||
|
var name = ByteString.FromStringUnsafe( data[ nameof( ActorIdentifier.PlayerName ) ]?.Value< string >(), false );
|
||||||
|
var homeWorld = data[ nameof( ActorIdentifier.HomeWorld ) ]?.Value< ushort >() ?? 0;
|
||||||
|
return CreatePlayer( name, homeWorld );
|
||||||
|
}
|
||||||
|
case IdentifierType.Owned:
|
||||||
|
{
|
||||||
|
var name = ByteString.FromStringUnsafe( data[ nameof( ActorIdentifier.PlayerName ) ]?.Value< string >(), false );
|
||||||
|
var homeWorld = data[ nameof( ActorIdentifier.HomeWorld ) ]?.Value< ushort >() ?? 0;
|
||||||
|
var kind = data[ nameof( ActorIdentifier.Kind ) ]?.Value< ObjectKind >() ?? ObjectKind.CardStand;
|
||||||
|
var dataId = data[ nameof( ActorIdentifier.DataId ) ]?.Value< uint >() ?? 0;
|
||||||
|
return CreateOwned( name, homeWorld, kind, dataId );
|
||||||
|
}
|
||||||
|
case IdentifierType.Special:
|
||||||
|
{
|
||||||
|
var special = data[ nameof( ActorIdentifier.Special ) ]?.Value< SpecialActor >() ?? 0;
|
||||||
|
return CreateSpecial( special );
|
||||||
|
}
|
||||||
|
case IdentifierType.Npc:
|
||||||
|
{
|
||||||
|
var index = data[ nameof( ActorIdentifier.Index ) ]?.Value< ushort >() ?? 0;
|
||||||
|
var kind = data[ nameof( ActorIdentifier.Kind ) ]?.Value< ObjectKind >() ?? ObjectKind.CardStand;
|
||||||
|
var dataId = data[ nameof( ActorIdentifier.DataId ) ]?.Value< uint >() ?? 0;
|
||||||
|
return CreateNpc( kind, index, dataId );
|
||||||
|
}
|
||||||
|
case IdentifierType.Invalid:
|
||||||
|
default:
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToString( ActorIdentifier id )
|
||||||
|
{
|
||||||
|
return id.Type switch
|
||||||
|
{
|
||||||
|
IdentifierType.Player => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
|
||||||
|
? $"{id.PlayerName} ({Worlds[ id.HomeWorld ]})"
|
||||||
|
: id.PlayerName.ToString(),
|
||||||
|
IdentifierType.Owned => id.HomeWorld != _clientState.LocalPlayer?.HomeWorld.Id
|
||||||
|
? $"{id.PlayerName} ({Worlds[ id.HomeWorld ]})'s {ToName( id.Kind, id.DataId )}"
|
||||||
|
: $"{id.PlayerName}s {ToName( id.Kind, id.DataId )}",
|
||||||
|
IdentifierType.Special => ToName( id.Special ),
|
||||||
|
IdentifierType.Npc =>
|
||||||
|
id.Index == ushort.MaxValue
|
||||||
|
? ToName( id.Kind, id.DataId )
|
||||||
|
: $"{ToName( id.Kind, id.DataId )} at {id.Index}",
|
||||||
|
_ => "Invalid",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string ToName( SpecialActor actor )
|
||||||
|
=> actor switch
|
||||||
|
{
|
||||||
|
SpecialActor.CharacterScreen => "Character Screen Actor",
|
||||||
|
SpecialActor.ExamineScreen => "Examine Screen Actor",
|
||||||
|
SpecialActor.FittingRoom => "Fitting Room Actor",
|
||||||
|
SpecialActor.DyePreview => "Dye Preview Actor",
|
||||||
|
SpecialActor.Portrait => "Portrait Actor",
|
||||||
|
_ => "Invalid",
|
||||||
|
};
|
||||||
|
|
||||||
|
public string ToName( ObjectKind kind, uint dataId )
|
||||||
|
=> TryGetName( kind, dataId, out var ret ) ? ret : "Invalid";
|
||||||
|
|
||||||
|
public bool TryGetName( ObjectKind kind, uint dataId, [NotNullWhen( true )] out string? name )
|
||||||
|
{
|
||||||
|
name = null;
|
||||||
|
return kind switch
|
||||||
|
{
|
||||||
|
ObjectKind.MountType => Mounts.TryGetValue( dataId, out name ),
|
||||||
|
ObjectKind.Companion => Companions.TryGetValue( dataId, out name ),
|
||||||
|
ObjectKind.BattleNpc => BNpcs.TryGetValue( dataId, out name ),
|
||||||
|
ObjectKind.EventNpc => ENpcs.TryGetValue( dataId, out name ),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe ActorIdentifier FromObject( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* actor )
|
||||||
|
{
|
||||||
|
if( actor == null )
|
||||||
|
{
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx = actor->ObjectIndex;
|
||||||
|
if( idx is >= ( ushort )SpecialActor.CutsceneStart and < ( ushort )SpecialActor.CutsceneEnd )
|
||||||
|
{
|
||||||
|
var parentIdx = _toParentIdx( idx );
|
||||||
|
if( parentIdx >= 0 )
|
||||||
|
{
|
||||||
|
return FromObject( ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )_objects.GetObjectAddress( parentIdx ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if( idx is >= ( ushort )SpecialActor.CharacterScreen and <= ( ushort )SpecialActor.Portrait )
|
||||||
|
{
|
||||||
|
return CreateSpecial( ( SpecialActor )idx );
|
||||||
|
}
|
||||||
|
|
||||||
|
switch( ( ObjectKind )actor->ObjectKind )
|
||||||
|
{
|
||||||
|
case ObjectKind.Player:
|
||||||
|
{
|
||||||
|
var name = new ByteString( actor->Name );
|
||||||
|
var homeWorld = ( ( FFXIVClientStructs.FFXIV.Client.Game.Character.Character* )actor )->HomeWorld;
|
||||||
|
return CreatePlayer( name, homeWorld );
|
||||||
|
}
|
||||||
|
case ObjectKind.BattleNpc:
|
||||||
|
{
|
||||||
|
var ownerId = actor->OwnerID;
|
||||||
|
if( ownerId != 0xE0000000 )
|
||||||
|
{
|
||||||
|
var owner = ( FFXIVClientStructs.FFXIV.Client.Game.Character.Character* )( _objects.SearchById( ownerId )?.Address ?? IntPtr.Zero );
|
||||||
|
if( owner == null )
|
||||||
|
{
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = new ByteString( owner->GameObject.Name );
|
||||||
|
var homeWorld = owner->HomeWorld;
|
||||||
|
return CreateOwned( name, homeWorld, ObjectKind.BattleNpc, ( ( FFXIVClientStructs.FFXIV.Client.Game.Character.Character* )actor )->NameID );
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateNpc( ObjectKind.BattleNpc, actor->ObjectIndex, ( ( FFXIVClientStructs.FFXIV.Client.Game.Character.Character* )actor )->NameID );
|
||||||
|
}
|
||||||
|
case ObjectKind.EventNpc: return CreateNpc( ObjectKind.EventNpc, actor->ObjectIndex, actor->DataID );
|
||||||
|
case ObjectKind.MountType:
|
||||||
|
case ObjectKind.Companion:
|
||||||
|
{
|
||||||
|
if( actor->ObjectIndex % 2 == 0 )
|
||||||
|
{
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var owner = ( FFXIVClientStructs.FFXIV.Client.Game.Character.Character* )_objects.GetObjectAddress( actor->ObjectIndex - 1 );
|
||||||
|
if( owner == null )
|
||||||
|
{
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataId = GetCompanionId( actor, &owner->GameObject );
|
||||||
|
return CreateOwned( new ByteString( owner->GameObject.Name ), owner->HomeWorld, ( ObjectKind )actor->ObjectKind, dataId );
|
||||||
|
}
|
||||||
|
default: return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe uint GetCompanionId( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* actor, FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* owner )
|
||||||
|
{
|
||||||
|
return ( ObjectKind )actor->ObjectKind switch
|
||||||
|
{
|
||||||
|
ObjectKind.MountType => *( ushort* )( ( byte* )owner + 0x668 ),
|
||||||
|
ObjectKind.Companion => *( ushort* )( ( byte* )actor + 0x1AAC ),
|
||||||
|
_ => actor->DataID,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe ActorIdentifier FromObject( GameObject? actor )
|
||||||
|
=> FromObject( ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )( actor?.Address ?? IntPtr.Zero ) );
|
||||||
|
|
||||||
|
|
||||||
|
public ActorIdentifier CreatePlayer( ByteString name, ushort homeWorld )
|
||||||
|
{
|
||||||
|
if( !VerifyWorld( homeWorld ) || !VerifyPlayerName( name ) )
|
||||||
|
{
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActorIdentifier( IdentifierType.Player, ObjectKind.Player, homeWorld, 0, name );
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorIdentifier CreateSpecial( SpecialActor actor )
|
||||||
|
{
|
||||||
|
if( !VerifySpecial( actor ) )
|
||||||
|
{
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActorIdentifier( IdentifierType.Special, ObjectKind.Player, ( ushort )actor, 0, ByteString.Empty );
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorIdentifier CreateNpc( ObjectKind kind, ushort index = ushort.MaxValue, uint data = uint.MaxValue )
|
||||||
|
{
|
||||||
|
if( !VerifyIndex( index ) || !VerifyNpcData( kind, data ) )
|
||||||
|
{
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActorIdentifier( IdentifierType.Npc, kind, index, data, ByteString.Empty );
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActorIdentifier CreateOwned( ByteString ownerName, ushort homeWorld, ObjectKind kind, uint dataId )
|
||||||
|
{
|
||||||
|
if( !VerifyWorld( homeWorld ) || !VerifyPlayerName( ownerName ) || !VerifyOwnedData( kind, dataId ) )
|
||||||
|
{
|
||||||
|
return ActorIdentifier.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ActorIdentifier( IdentifierType.Owned, kind, homeWorld, dataId, ownerName );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary> Checks SE naming rules. </summary>
|
||||||
|
private static bool VerifyPlayerName( ByteString name )
|
||||||
|
{
|
||||||
|
// Total no more than 20 characters + space.
|
||||||
|
if( name.Length is < 5 or > 21 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var split = name.Split( ( byte )' ' );
|
||||||
|
|
||||||
|
// Forename and surname, no more spaces.
|
||||||
|
if( split.Count != 2 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool CheckNamePart( ByteString part )
|
||||||
|
{
|
||||||
|
// Each name part at least 2 and at most 15 characters.
|
||||||
|
if( part.Length is < 2 or > 15 )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Each part starting with capitalized letter.
|
||||||
|
if( part[ 0 ] is < ( byte )'A' or > ( byte )'Z' )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every other symbol needs to be lowercase letter, hyphen or apostrophe.
|
||||||
|
if( part.Skip( 1 ).Any( c => c != ( byte )'\'' && c != ( byte )'-' && c is < ( byte )'a' or > ( byte )'z' ) )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hyphens = part.Split( ( byte )'-' );
|
||||||
|
// Apostrophes can not be used in succession, after or before apostrophes.
|
||||||
|
return !hyphens.Any( p => p.Length == 0 || p[ 0 ] == ( byte )'\'' || p.Last() == ( byte )'\'' );
|
||||||
|
}
|
||||||
|
|
||||||
|
return CheckNamePart( split[ 0 ] ) && CheckNamePart( split[ 1 ] );
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Checks if the world is a valid public world or ushort.MaxValue (any world). </summary>
|
||||||
|
private bool VerifyWorld( ushort worldId )
|
||||||
|
=> Worlds.ContainsKey( worldId );
|
||||||
|
|
||||||
|
/// <summary> Verify that the enum value is a specific actor and return the name if it is. </summary>
|
||||||
|
private static bool VerifySpecial( SpecialActor actor )
|
||||||
|
=> actor is >= SpecialActor.CharacterScreen and <= SpecialActor.Portrait;
|
||||||
|
|
||||||
|
/// <summary> Verify that the object index is a valid index for an NPC. </summary>
|
||||||
|
private static bool VerifyIndex( ushort index )
|
||||||
|
{
|
||||||
|
return index switch
|
||||||
|
{
|
||||||
|
< 200 => index % 2 == 0,
|
||||||
|
> ( ushort )SpecialActor.Portrait => index < 426,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary> Verify that the object kind is a valid owned object, and the corresponding data Id. </summary>
|
||||||
|
private bool VerifyOwnedData( ObjectKind kind, uint dataId )
|
||||||
|
{
|
||||||
|
return kind switch
|
||||||
|
{
|
||||||
|
ObjectKind.MountType => Mounts.ContainsKey( dataId ),
|
||||||
|
ObjectKind.Companion => Companions.ContainsKey( dataId ),
|
||||||
|
ObjectKind.BattleNpc => BNpcs.ContainsKey( dataId ),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool VerifyNpcData( ObjectKind kind, uint dataId )
|
||||||
|
=> kind switch
|
||||||
|
{
|
||||||
|
ObjectKind.BattleNpc => BNpcs.ContainsKey( dataId ),
|
||||||
|
ObjectKind.EventNpc => ENpcs.ContainsKey( dataId ),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
10
Penumbra.GameData/Actors/IdentifierType.cs
Normal file
10
Penumbra.GameData/Actors/IdentifierType.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
namespace Penumbra.GameData.Actors;
|
||||||
|
|
||||||
|
public enum IdentifierType : byte
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
Player,
|
||||||
|
Owned,
|
||||||
|
Special,
|
||||||
|
Npc,
|
||||||
|
};
|
||||||
12
Penumbra.GameData/Actors/SpecialActor.cs
Normal file
12
Penumbra.GameData/Actors/SpecialActor.cs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace Penumbra.GameData.Actors;
|
||||||
|
|
||||||
|
public enum SpecialActor : ushort
|
||||||
|
{
|
||||||
|
CutsceneStart = 200,
|
||||||
|
CutsceneEnd = 240,
|
||||||
|
CharacterScreen = 240,
|
||||||
|
ExamineScreen = 241,
|
||||||
|
FittingRoom = 242,
|
||||||
|
DyePreview = 243,
|
||||||
|
Portrait = 244,
|
||||||
|
}
|
||||||
|
|
@ -1,105 +0,0 @@
|
||||||
using System.Linq;
|
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
public static unsafe partial class ByteStringFunctions
|
|
||||||
{
|
|
||||||
private static readonly byte[] AsciiLowerCaseBytes = Enumerable.Range( 0, 256 )
|
|
||||||
.Select( i => ( byte )char.ToLowerInvariant( ( char )i ) )
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
private static readonly byte[] AsciiUpperCaseBytes = Enumerable.Range( 0, 256 )
|
|
||||||
.Select( i => ( byte )char.ToUpperInvariant( ( char )i ) )
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
// Convert a byte to its ASCII-lowercase version.
|
|
||||||
public static byte AsciiToLower( byte b )
|
|
||||||
=> AsciiLowerCaseBytes[ b ];
|
|
||||||
|
|
||||||
// Check if a byte is ASCII-lowercase.
|
|
||||||
public static bool AsciiIsLower( byte b )
|
|
||||||
=> AsciiToLower( b ) == b;
|
|
||||||
|
|
||||||
// Convert a byte to its ASCII-uppercase version.
|
|
||||||
public static byte AsciiToUpper( byte b )
|
|
||||||
=> AsciiUpperCaseBytes[ b ];
|
|
||||||
|
|
||||||
// Check if a byte is ASCII-uppercase.
|
|
||||||
public static bool AsciiIsUpper( byte b )
|
|
||||||
=> AsciiToUpper( b ) == b;
|
|
||||||
|
|
||||||
// Check if a byte array of given length is ASCII-lowercase.
|
|
||||||
public static bool IsAsciiLowerCase( byte* path, int length )
|
|
||||||
{
|
|
||||||
var end = path + length;
|
|
||||||
for( ; path < end; ++path )
|
|
||||||
{
|
|
||||||
if( *path != AsciiLowerCaseBytes[*path] )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compare two byte arrays of given lengths ASCII-case-insensitive.
|
|
||||||
public static int AsciiCaselessCompare( byte* lhs, int lhsLength, byte* rhs, int rhsLength )
|
|
||||||
{
|
|
||||||
if( lhsLength == rhsLength )
|
|
||||||
{
|
|
||||||
return lhs == rhs ? 0 : Functions.MemCmpCaseInsensitiveUnchecked( lhs, rhs, rhsLength );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( lhsLength < rhsLength )
|
|
||||||
{
|
|
||||||
var cmp = Functions.MemCmpCaseInsensitiveUnchecked( lhs, rhs, lhsLength );
|
|
||||||
return cmp != 0 ? cmp : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmp2 = Functions.MemCmpCaseInsensitiveUnchecked( lhs, rhs, rhsLength );
|
|
||||||
return cmp2 != 0 ? cmp2 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check two byte arrays of given lengths for ASCII-case-insensitive equality.
|
|
||||||
public static bool AsciiCaselessEquals( byte* lhs, int lhsLength, byte* rhs, int rhsLength )
|
|
||||||
{
|
|
||||||
if( lhsLength != rhsLength )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( lhs == rhs || lhsLength == 0 )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Functions.MemCmpCaseInsensitiveUnchecked( lhs, rhs, lhsLength ) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a byte array of given length consists purely of ASCII characters.
|
|
||||||
public static bool IsAscii( byte* path, int length )
|
|
||||||
{
|
|
||||||
var length8 = length / 8;
|
|
||||||
var end8 = ( ulong* )path + length8;
|
|
||||||
for( var ptr8 = ( ulong* )path; ptr8 < end8; ++ptr8 )
|
|
||||||
{
|
|
||||||
if( ( *ptr8 & 0x8080808080808080ul ) != 0 )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var end = path + length;
|
|
||||||
for( path += length8 * 8; path < end; ++path )
|
|
||||||
{
|
|
||||||
if( *path > 127 )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
public static unsafe partial class ByteStringFunctions
|
|
||||||
{
|
|
||||||
// Lexicographically compare two byte arrays of given length.
|
|
||||||
public static int Compare( byte* lhs, int lhsLength, byte* rhs, int rhsLength )
|
|
||||||
{
|
|
||||||
if( lhsLength == rhsLength )
|
|
||||||
{
|
|
||||||
return lhs == rhs ? 0 : Functions.MemCmpUnchecked( lhs, rhs, rhsLength );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( lhsLength < rhsLength )
|
|
||||||
{
|
|
||||||
var cmp = Functions.MemCmpUnchecked( lhs, rhs, lhsLength );
|
|
||||||
return cmp != 0 ? cmp : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmp2 = Functions.MemCmpUnchecked( lhs, rhs, rhsLength );
|
|
||||||
return cmp2 != 0 ? cmp2 : 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lexicographically compare one byte array of given length with a null-terminated byte array of unknown length.
|
|
||||||
public static int Compare( byte* lhs, int lhsLength, byte* rhs )
|
|
||||||
{
|
|
||||||
var end = lhs + lhsLength;
|
|
||||||
for( var tmp = lhs; tmp < end; ++tmp, ++rhs )
|
|
||||||
{
|
|
||||||
if( *rhs == 0 )
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var diff = *tmp - *rhs;
|
|
||||||
if( diff != 0 )
|
|
||||||
{
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lexicographically compare two null-terminated byte arrays of unknown length not larger than maxLength.
|
|
||||||
public static int Compare( byte* lhs, byte* rhs, int maxLength = int.MaxValue )
|
|
||||||
{
|
|
||||||
var end = lhs + maxLength;
|
|
||||||
for( var tmp = lhs; tmp < end; ++tmp, ++rhs )
|
|
||||||
{
|
|
||||||
if( *lhs == 0 )
|
|
||||||
{
|
|
||||||
return *rhs == 0 ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( *rhs == 0 )
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var diff = *tmp - *rhs;
|
|
||||||
if( diff != 0 )
|
|
||||||
{
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check two byte arrays of given length for equality.
|
|
||||||
public static bool Equals( byte* lhs, int lhsLength, byte* rhs, int rhsLength )
|
|
||||||
{
|
|
||||||
if( lhsLength != rhsLength )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( lhs == rhs || lhsLength == 0 )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Functions.MemCmpUnchecked( lhs, rhs, lhsLength ) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check one byte array of given length for equality against a null-terminated byte array of unknown length.
|
|
||||||
private static bool Equal( byte* lhs, int lhsLength, byte* rhs )
|
|
||||||
=> Compare( lhs, lhsLength, rhs ) == 0;
|
|
||||||
|
|
||||||
// Check two null-terminated byte arrays of unknown length not larger than maxLength for equality.
|
|
||||||
private static bool Equal( byte* lhs, byte* rhs, int maxLength = int.MaxValue )
|
|
||||||
=> Compare( lhs, rhs, maxLength ) == 0;
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
public static unsafe partial class ByteStringFunctions
|
|
||||||
{
|
|
||||||
// Used for static null-terminators.
|
|
||||||
public class NullTerminator
|
|
||||||
{
|
|
||||||
public readonly byte* NullBytePtr;
|
|
||||||
|
|
||||||
public NullTerminator()
|
|
||||||
{
|
|
||||||
NullBytePtr = ( byte* )Marshal.AllocHGlobal( 1 );
|
|
||||||
*NullBytePtr = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
~NullTerminator()
|
|
||||||
=> Marshal.FreeHGlobal( ( IntPtr )NullBytePtr );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a C# unicode-string to an unmanaged UTF8-byte array and return the pointer.
|
|
||||||
// If the length would exceed the given maxLength, return a nullpointer instead.
|
|
||||||
public static byte* Utf8FromString( string s, out int length, int maxLength = int.MaxValue )
|
|
||||||
{
|
|
||||||
length = Encoding.UTF8.GetByteCount( s );
|
|
||||||
if( length >= maxLength )
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var path = ( byte* )Marshal.AllocHGlobal( length + 1 );
|
|
||||||
fixed( char* ptr = s )
|
|
||||||
{
|
|
||||||
Encoding.UTF8.GetBytes( ptr, s.Length, path, length + 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
path[ length ] = 0;
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a copy of a given string and return the pointer.
|
|
||||||
public static byte* CopyString( byte* path, int length )
|
|
||||||
{
|
|
||||||
var ret = ( byte* )Marshal.AllocHGlobal( length + 1 );
|
|
||||||
Functions.MemCpyUnchecked( ret, path, length );
|
|
||||||
ret[ length ] = 0;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the length of a null-terminated byte array no longer than the given maxLength.
|
|
||||||
public static int CheckLength( byte* path, int maxLength = int.MaxValue )
|
|
||||||
{
|
|
||||||
var end = path + maxLength;
|
|
||||||
for( var it = path; it < end; ++it )
|
|
||||||
{
|
|
||||||
if( *it == 0 )
|
|
||||||
{
|
|
||||||
return ( int )( it - path );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentOutOfRangeException( "Null-terminated path too long" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
public static unsafe partial class ByteStringFunctions
|
|
||||||
{
|
|
||||||
// Replace all occurrences of from in a byte array of known length with to.
|
|
||||||
public static int Replace( byte* ptr, int length, byte from, byte to )
|
|
||||||
{
|
|
||||||
var end = ptr + length;
|
|
||||||
var numReplaced = 0;
|
|
||||||
for( ; ptr < end; ++ptr )
|
|
||||||
{
|
|
||||||
if( *ptr == from )
|
|
||||||
{
|
|
||||||
*ptr = to;
|
|
||||||
++numReplaced;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return numReplaced;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert a byte array of given length to ASCII-lowercase.
|
|
||||||
public static void AsciiToLowerInPlace( byte* path, int length )
|
|
||||||
{
|
|
||||||
for( var i = 0; i < length; ++i )
|
|
||||||
{
|
|
||||||
path[ i ] = AsciiLowerCaseBytes[ path[ i ] ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy a byte array and convert the copy to ASCII-lowercase.
|
|
||||||
public static byte* AsciiToLower( byte* path, int length )
|
|
||||||
{
|
|
||||||
var ptr = ( byte* )Marshal.AllocHGlobal( length + 1 );
|
|
||||||
ptr[ length ] = 0;
|
|
||||||
for( var i = 0; i < length; ++i )
|
|
||||||
{
|
|
||||||
ptr[ i ] = AsciiLowerCaseBytes[ path[ i ] ];
|
|
||||||
}
|
|
||||||
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
[JsonConverter( typeof( FullPathConverter ) )]
|
|
||||||
public readonly struct FullPath : IComparable, IEquatable< FullPath >
|
|
||||||
{
|
|
||||||
public readonly string FullName;
|
|
||||||
public readonly Utf8String InternalName;
|
|
||||||
public readonly ulong Crc64;
|
|
||||||
|
|
||||||
public static readonly FullPath Empty = new(string.Empty);
|
|
||||||
|
|
||||||
public FullPath( DirectoryInfo baseDir, Utf8RelPath relPath )
|
|
||||||
: this( Path.Combine( baseDir.FullName, relPath.ToString() ) )
|
|
||||||
{ }
|
|
||||||
|
|
||||||
public FullPath( FileInfo file )
|
|
||||||
: this( file.FullName )
|
|
||||||
{ }
|
|
||||||
|
|
||||||
|
|
||||||
public FullPath( string s )
|
|
||||||
{
|
|
||||||
FullName = s;
|
|
||||||
InternalName = Utf8String.FromString( FullName.Replace( '\\', '/' ), out var name, true ) ? name : Utf8String.Empty;
|
|
||||||
Crc64 = Functions.ComputeCrc64( InternalName.Span );
|
|
||||||
}
|
|
||||||
|
|
||||||
public FullPath( Utf8GamePath path )
|
|
||||||
{
|
|
||||||
FullName = path.ToString().Replace( '/', '\\' );
|
|
||||||
InternalName = path.Path;
|
|
||||||
Crc64 = Functions.ComputeCrc64( InternalName.Span );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Exists
|
|
||||||
=> File.Exists( FullName );
|
|
||||||
|
|
||||||
public string Extension
|
|
||||||
=> Path.GetExtension( FullName );
|
|
||||||
|
|
||||||
public string Name
|
|
||||||
=> Path.GetFileName( FullName );
|
|
||||||
|
|
||||||
public bool ToGamePath( DirectoryInfo dir, out Utf8GamePath path )
|
|
||||||
{
|
|
||||||
path = Utf8GamePath.Empty;
|
|
||||||
if( !InternalName.IsAscii || !FullName.StartsWith( dir.FullName ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var substring = InternalName.Substring( dir.FullName.Length + 1 );
|
|
||||||
|
|
||||||
path = new Utf8GamePath( substring );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ToRelPath( DirectoryInfo dir, out Utf8RelPath path )
|
|
||||||
{
|
|
||||||
path = Utf8RelPath.Empty;
|
|
||||||
if( !FullName.StartsWith( dir.FullName ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var substring = InternalName.Substring( dir.FullName.Length + 1 );
|
|
||||||
|
|
||||||
path = new Utf8RelPath( substring.Replace( ( byte )'/', ( byte )'\\' ) );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareTo( object? obj )
|
|
||||||
=> obj switch
|
|
||||||
{
|
|
||||||
FullPath p => InternalName?.CompareTo( p.InternalName ) ?? -1,
|
|
||||||
FileInfo f => string.Compare( FullName, f.FullName, StringComparison.OrdinalIgnoreCase ),
|
|
||||||
Utf8String u => InternalName?.CompareTo( u ) ?? -1,
|
|
||||||
string s => string.Compare( FullName, s, StringComparison.OrdinalIgnoreCase ),
|
|
||||||
_ => -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
public bool Equals( FullPath other )
|
|
||||||
{
|
|
||||||
if( Crc64 != other.Crc64 )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( FullName.Length == 0 || other.FullName.Length == 0 )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return InternalName.Equals( other.InternalName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsRooted
|
|
||||||
=> new Utf8GamePath( InternalName ).IsRooted();
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
=> InternalName.Crc32;
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
=> FullName;
|
|
||||||
|
|
||||||
public class FullPathConverter : JsonConverter
|
|
||||||
{
|
|
||||||
public override bool CanConvert( Type objectType )
|
|
||||||
=> objectType == typeof( FullPath );
|
|
||||||
|
|
||||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
|
||||||
{
|
|
||||||
var token = JToken.Load( reader ).ToString();
|
|
||||||
return new FullPath( token );
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite
|
|
||||||
=> true;
|
|
||||||
|
|
||||||
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
|
||||||
{
|
|
||||||
if( value is FullPath p )
|
|
||||||
{
|
|
||||||
serializer.Serialize( writer, p.ToString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,168 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using Dalamud.Utility;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
// NewGamePath wrap some additional validity checking around Utf8String,
|
|
||||||
// provide some filesystem helpers, and conversion to Json.
|
|
||||||
[JsonConverter( typeof( Utf8GamePathConverter ) )]
|
|
||||||
public readonly struct Utf8GamePath : IEquatable< Utf8GamePath >, IComparable< Utf8GamePath >, IDisposable
|
|
||||||
{
|
|
||||||
public const int MaxGamePathLength = 256;
|
|
||||||
|
|
||||||
public readonly Utf8String Path;
|
|
||||||
public static readonly Utf8GamePath Empty = new(Utf8String.Empty);
|
|
||||||
|
|
||||||
internal Utf8GamePath( Utf8String s )
|
|
||||||
=> Path = s;
|
|
||||||
|
|
||||||
public int Length
|
|
||||||
=> Path.Length;
|
|
||||||
|
|
||||||
public bool IsEmpty
|
|
||||||
=> Path.IsEmpty;
|
|
||||||
|
|
||||||
public Utf8GamePath ToLower()
|
|
||||||
=> new(Path.AsciiToLower());
|
|
||||||
|
|
||||||
public static unsafe bool FromPointer( byte* ptr, out Utf8GamePath path, bool lower = false )
|
|
||||||
{
|
|
||||||
var utf = new Utf8String( ptr );
|
|
||||||
return ReturnChecked( utf, out path, lower );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool FromSpan( ReadOnlySpan< byte > data, out Utf8GamePath path, bool lower = false )
|
|
||||||
{
|
|
||||||
var utf = Utf8String.FromSpanUnsafe( data, false, null, null );
|
|
||||||
return ReturnChecked( utf, out path, lower );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does not check for Forward/Backslashes due to assuming that SE-strings use the correct one.
|
|
||||||
// Does not check for initial slashes either, since they are assumed to be by choice.
|
|
||||||
// Checks for maxlength, ASCII and lowercase.
|
|
||||||
private static bool ReturnChecked( Utf8String utf, out Utf8GamePath path, bool lower = false )
|
|
||||||
{
|
|
||||||
path = Empty;
|
|
||||||
if( !utf.IsAscii || utf.Length > MaxGamePathLength )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
path = new Utf8GamePath( lower ? utf.AsciiToLower() : utf );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Utf8GamePath Clone()
|
|
||||||
=> new(Path.Clone());
|
|
||||||
|
|
||||||
public static explicit operator Utf8GamePath( string s )
|
|
||||||
=> FromString( s, out var p, true ) ? p : Empty;
|
|
||||||
|
|
||||||
public static bool FromString( string? s, out Utf8GamePath path, bool toLower = false )
|
|
||||||
{
|
|
||||||
path = Empty;
|
|
||||||
if( s.IsNullOrEmpty() )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var substring = s!.Replace( '\\', '/' ).TrimStart( '/' );
|
|
||||||
if( substring.Length > MaxGamePathLength )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( substring.Length == 0 )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !Utf8String.FromString( substring, out var ascii, toLower ) || !ascii.IsAscii )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
path = new Utf8GamePath( ascii );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out Utf8GamePath path, bool toLower = false )
|
|
||||||
{
|
|
||||||
path = Empty;
|
|
||||||
if( !file.FullName.StartsWith( baseDir.FullName ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var substring = file.FullName[ ( baseDir.FullName.Length + 1 ).. ];
|
|
||||||
return FromString( substring, out path, toLower );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Utf8String Filename()
|
|
||||||
{
|
|
||||||
var idx = Path.LastIndexOf( ( byte )'/' );
|
|
||||||
return idx == -1 ? Path : Path.Substring( idx + 1 );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Utf8String Extension()
|
|
||||||
{
|
|
||||||
var idx = Path.LastIndexOf( ( byte )'.' );
|
|
||||||
return idx == -1 ? Utf8String.Empty : Path.Substring( idx );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals( Utf8GamePath other )
|
|
||||||
=> Path.Equals( other.Path );
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
=> Path.GetHashCode();
|
|
||||||
|
|
||||||
public int CompareTo( Utf8GamePath other )
|
|
||||||
=> Path.CompareTo( other.Path );
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
=> Path.ToString();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
=> Path.Dispose();
|
|
||||||
|
|
||||||
public bool IsRooted()
|
|
||||||
=> IsRooted( Path );
|
|
||||||
|
|
||||||
public static bool IsRooted( Utf8String path )
|
|
||||||
=> path.Length >= 1 && ( path[ 0 ] == '/' || path[ 0 ] == '\\' )
|
|
||||||
|| path.Length >= 2
|
|
||||||
&& ( path[ 0 ] >= 'A' && path[ 0 ] <= 'Z' || path[ 0 ] >= 'a' && path[ 0 ] <= 'z' )
|
|
||||||
&& path[ 1 ] == ':';
|
|
||||||
|
|
||||||
public class Utf8GamePathConverter : JsonConverter
|
|
||||||
{
|
|
||||||
public override bool CanConvert( Type objectType )
|
|
||||||
=> objectType == typeof( Utf8GamePath );
|
|
||||||
|
|
||||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
|
||||||
{
|
|
||||||
var token = JToken.Load( reader ).ToString();
|
|
||||||
return FromString( token, out var p, true )
|
|
||||||
? p
|
|
||||||
: throw new JsonException( $"Could not convert \"{token}\" to {nameof( Utf8GamePath )}." );
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite
|
|
||||||
=> true;
|
|
||||||
|
|
||||||
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
|
||||||
{
|
|
||||||
if( value is Utf8GamePath p )
|
|
||||||
{
|
|
||||||
serializer.Serialize( writer, p.ToString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public GamePath ToGamePath()
|
|
||||||
=> GamePath.GenerateUnchecked( ToString() );
|
|
||||||
}
|
|
||||||
|
|
@ -1,143 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using Dalamud.Utility;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
[JsonConverter( typeof( Utf8RelPathConverter ) )]
|
|
||||||
public readonly struct Utf8RelPath : IEquatable< Utf8RelPath >, IComparable< Utf8RelPath >, IDisposable
|
|
||||||
{
|
|
||||||
public const int MaxRelPathLength = 250;
|
|
||||||
|
|
||||||
public readonly Utf8String Path;
|
|
||||||
public static readonly Utf8RelPath Empty = new(Utf8String.Empty);
|
|
||||||
|
|
||||||
internal Utf8RelPath( Utf8String path )
|
|
||||||
=> Path = path;
|
|
||||||
|
|
||||||
|
|
||||||
public static explicit operator Utf8RelPath( string s )
|
|
||||||
{
|
|
||||||
if( !FromString( s, out var p ) )
|
|
||||||
{
|
|
||||||
return Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Utf8RelPath( p.Path.AsciiToLower() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool FromString( string? s, out Utf8RelPath path )
|
|
||||||
{
|
|
||||||
path = Empty;
|
|
||||||
if( s.IsNullOrEmpty() )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var substring = s.Replace( '/', '\\' ).TrimStart('\\');
|
|
||||||
if( substring.Length > MaxRelPathLength )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( substring.Length == 0 )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( !Utf8String.FromString( substring, out var ascii, true ) || !ascii.IsAscii )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
path = new Utf8RelPath( ascii );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool FromFile( FileInfo file, DirectoryInfo baseDir, out Utf8RelPath path )
|
|
||||||
{
|
|
||||||
path = Empty;
|
|
||||||
if( !file.FullName.StartsWith( baseDir.FullName ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var substring = file.FullName[ (baseDir.FullName.Length + 1).. ];
|
|
||||||
return FromString( substring, out path );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool FromFile( FullPath file, DirectoryInfo baseDir, out Utf8RelPath path )
|
|
||||||
{
|
|
||||||
path = Empty;
|
|
||||||
if( !file.FullName.StartsWith( baseDir.FullName ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var substring = file.FullName[ (baseDir.FullName.Length + 1).. ];
|
|
||||||
return FromString( substring, out path );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Utf8RelPath( Utf8GamePath gamePath )
|
|
||||||
=> Path = gamePath.Path.Replace( ( byte )'/', ( byte )'\\' );
|
|
||||||
|
|
||||||
public unsafe Utf8GamePath ToGamePath( int skipFolders = 0 )
|
|
||||||
{
|
|
||||||
var idx = 0;
|
|
||||||
while( skipFolders > 0 )
|
|
||||||
{
|
|
||||||
idx = Path.IndexOf( ( byte )'\\', idx ) + 1;
|
|
||||||
--skipFolders;
|
|
||||||
if( idx <= 0 )
|
|
||||||
{
|
|
||||||
return Utf8GamePath.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var length = Path.Length - idx;
|
|
||||||
var ptr = ByteStringFunctions.CopyString( Path.Path + idx, length );
|
|
||||||
ByteStringFunctions.Replace( ptr, length, ( byte )'\\', ( byte )'/' );
|
|
||||||
ByteStringFunctions.AsciiToLowerInPlace( ptr, length );
|
|
||||||
var utf = new Utf8String().Setup( ptr, length, null, true, true, true, true );
|
|
||||||
return new Utf8GamePath( utf );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareTo( Utf8RelPath rhs )
|
|
||||||
=> Path.CompareTo( rhs.Path );
|
|
||||||
|
|
||||||
public bool Equals( Utf8RelPath other )
|
|
||||||
=> Path.Equals( other.Path );
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
=> Path.ToString();
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
=> Path.Dispose();
|
|
||||||
|
|
||||||
public class Utf8RelPathConverter : JsonConverter
|
|
||||||
{
|
|
||||||
public override bool CanConvert( Type objectType )
|
|
||||||
=> objectType == typeof( Utf8RelPath );
|
|
||||||
|
|
||||||
public override object ReadJson( JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer )
|
|
||||||
{
|
|
||||||
var token = JToken.Load( reader ).ToString();
|
|
||||||
return FromString( token, out var p )
|
|
||||||
? p
|
|
||||||
: throw new JsonException( $"Could not convert \"{token}\" to {nameof( Utf8RelPath )}." );
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool CanWrite
|
|
||||||
=> true;
|
|
||||||
|
|
||||||
public override void WriteJson( JsonWriter writer, object? value, JsonSerializer serializer )
|
|
||||||
{
|
|
||||||
if( value is Utf8RelPath p )
|
|
||||||
{
|
|
||||||
serializer.Serialize( writer, p.ToString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
// Utf8String is a wrapper around unsafe byte strings.
|
|
||||||
// It may be used to store owned strings in unmanaged space,
|
|
||||||
// as well as refer to unowned strings.
|
|
||||||
// Unowned strings may change their value and thus become corrupt,
|
|
||||||
// so they should never be stored, just used locally or with great care.
|
|
||||||
// The string keeps track of whether it is owned or not, it also can keep track
|
|
||||||
// of some other information, like the string being pure ASCII, ASCII-lowercase or null-terminated.
|
|
||||||
// Owned strings are always null-terminated.
|
|
||||||
// Any constructed string will compute its own CRC32-value (as long as the string itself is not changed).
|
|
||||||
public sealed unsafe partial class Utf8String : IEnumerable< byte >
|
|
||||||
{
|
|
||||||
// We keep information on some of the state of the Utf8String in specific bits.
|
|
||||||
// This costs some potential max size, but that is not relevant for our case.
|
|
||||||
// Except for destruction/dispose, or if the non-owned pointer changes values,
|
|
||||||
// the CheckedFlag, AsciiLowerCaseFlag and AsciiFlag are the only things that are mutable.
|
|
||||||
private const uint NullTerminatedFlag = 0x80000000;
|
|
||||||
private const uint OwnedFlag = 0x40000000;
|
|
||||||
private const uint AsciiCheckedFlag = 0x04000000;
|
|
||||||
private const uint AsciiFlag = 0x08000000;
|
|
||||||
private const uint AsciiLowerCheckedFlag = 0x10000000;
|
|
||||||
private const uint AsciiLowerFlag = 0x20000000;
|
|
||||||
private const uint FlagMask = 0x03FFFFFF;
|
|
||||||
|
|
||||||
public bool IsNullTerminated
|
|
||||||
=> ( _length & NullTerminatedFlag ) != 0;
|
|
||||||
|
|
||||||
public bool IsOwned
|
|
||||||
=> ( _length & OwnedFlag ) != 0;
|
|
||||||
|
|
||||||
public bool IsAscii
|
|
||||||
=> CheckAscii();
|
|
||||||
|
|
||||||
public bool IsAsciiLowerCase
|
|
||||||
=> CheckAsciiLower();
|
|
||||||
|
|
||||||
public byte* Path
|
|
||||||
=> _path;
|
|
||||||
|
|
||||||
public int Crc32
|
|
||||||
=> _crc32;
|
|
||||||
|
|
||||||
public int Length
|
|
||||||
=> ( int )( _length & FlagMask );
|
|
||||||
|
|
||||||
public bool IsEmpty
|
|
||||||
=> Length == 0;
|
|
||||||
|
|
||||||
public ReadOnlySpan< byte > Span
|
|
||||||
=> new(_path, Length);
|
|
||||||
|
|
||||||
public byte this[ int idx ]
|
|
||||||
=> ( uint )idx < Length ? _path[ idx ] : throw new IndexOutOfRangeException();
|
|
||||||
|
|
||||||
public IEnumerator< byte > GetEnumerator()
|
|
||||||
{
|
|
||||||
for( var i = 0; i < Length; ++i )
|
|
||||||
{
|
|
||||||
yield return Span[ i ];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
=> GetEnumerator();
|
|
||||||
|
|
||||||
// Only not readonly due to dispose.
|
|
||||||
// ReSharper disable once NonReadonlyMemberInGetHashCode
|
|
||||||
public override int GetHashCode()
|
|
||||||
=> _crc32;
|
|
||||||
}
|
|
||||||
|
|
@ -1,140 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
public sealed unsafe partial class Utf8String : IEquatable< Utf8String >, IComparable< Utf8String >
|
|
||||||
{
|
|
||||||
public bool Equals( Utf8String? other )
|
|
||||||
{
|
|
||||||
if( ReferenceEquals( null, other ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ReferenceEquals( this, other ) )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _crc32 == other._crc32 && ByteStringFunctions.Equals( _path, Length, other._path, other.Length );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EqualsCi( Utf8String? other )
|
|
||||||
{
|
|
||||||
if( ReferenceEquals( null, other ) )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ReferenceEquals( this, other ) )
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ( IsAsciiLowerInternal ?? false ) && ( other.IsAsciiLowerInternal ?? false ) )
|
|
||||||
{
|
|
||||||
return _crc32 == other._crc32 && ByteStringFunctions.Equals( _path, Length, other._path, other.Length );
|
|
||||||
}
|
|
||||||
|
|
||||||
return ByteStringFunctions.AsciiCaselessEquals( _path, Length, other._path, other.Length );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareTo( Utf8String? other )
|
|
||||||
{
|
|
||||||
if( ReferenceEquals( this, other ) )
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ReferenceEquals( null, other ) )
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ByteStringFunctions.Compare( _path, Length, other._path, other.Length );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareToCi( Utf8String? other )
|
|
||||||
{
|
|
||||||
if( ReferenceEquals( null, other ) )
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ReferenceEquals( this, other ) )
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ( IsAsciiLowerInternal ?? false ) && ( other.IsAsciiLowerInternal ?? false ) )
|
|
||||||
{
|
|
||||||
return ByteStringFunctions.Compare( _path, Length, other._path, other.Length );
|
|
||||||
}
|
|
||||||
|
|
||||||
return ByteStringFunctions.AsciiCaselessCompare( _path, Length, other._path, other.Length );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool StartsWith( Utf8String other )
|
|
||||||
{
|
|
||||||
var otherLength = other.Length;
|
|
||||||
return otherLength <= Length && ByteStringFunctions.Equals( other.Path, otherLength, Path, otherLength );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EndsWith( Utf8String other )
|
|
||||||
{
|
|
||||||
var otherLength = other.Length;
|
|
||||||
var offset = Length - otherLength;
|
|
||||||
return offset >= 0 && ByteStringFunctions.Equals( other.Path, otherLength, Path + offset, otherLength );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool StartsWith( params char[] chars )
|
|
||||||
{
|
|
||||||
if( chars.Length > Length )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ptr = _path;
|
|
||||||
return chars.All( t => *ptr++ == ( byte )t );
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool EndsWith( params char[] chars )
|
|
||||||
{
|
|
||||||
if( chars.Length > Length )
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ptr = _path + Length - chars.Length;
|
|
||||||
return chars.All( c => *ptr++ == ( byte )c );
|
|
||||||
}
|
|
||||||
|
|
||||||
public int IndexOf( byte b, int from = 0 )
|
|
||||||
{
|
|
||||||
var end = _path + Length;
|
|
||||||
for( var tmp = _path + from; tmp < end; ++tmp )
|
|
||||||
{
|
|
||||||
if( *tmp == b )
|
|
||||||
{
|
|
||||||
return ( int )( tmp - _path );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int LastIndexOf( byte b, int to = 0 )
|
|
||||||
{
|
|
||||||
var end = _path + to;
|
|
||||||
for( var tmp = _path + Length - 1; tmp >= end; --tmp )
|
|
||||||
{
|
|
||||||
if( *tmp == b )
|
|
||||||
{
|
|
||||||
return ( int )( tmp - _path );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
public sealed unsafe partial class Utf8String : IDisposable
|
|
||||||
{
|
|
||||||
// statically allocated null-terminator for empty strings to point to.
|
|
||||||
private static readonly ByteStringFunctions.NullTerminator Null = new();
|
|
||||||
|
|
||||||
public static readonly Utf8String Empty = new();
|
|
||||||
|
|
||||||
// actual data members.
|
|
||||||
private byte* _path;
|
|
||||||
private uint _length;
|
|
||||||
private int _crc32;
|
|
||||||
|
|
||||||
// Create an empty string.
|
|
||||||
public Utf8String()
|
|
||||||
{
|
|
||||||
_path = Null.NullBytePtr;
|
|
||||||
_length |= AsciiCheckedFlag | AsciiFlag | AsciiLowerCheckedFlag | AsciiLowerFlag | NullTerminatedFlag | AsciiFlag;
|
|
||||||
_crc32 = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a temporary Utf8String from a byte pointer.
|
|
||||||
// This computes CRC, checks for ASCII and AsciiLower and assumes Null-Termination.
|
|
||||||
public Utf8String( byte* path )
|
|
||||||
{
|
|
||||||
var length = Functions.ComputeCrc32AsciiLowerAndSize( path, out var crc32, out var lower, out var ascii );
|
|
||||||
Setup( path, length, crc32, true, false, lower, ascii );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct a temporary Utf8String from a given byte string of known size.
|
|
||||||
// Other known attributes can also be provided and are not computed.
|
|
||||||
// Can throw ArgumentOutOfRange if length is higher than max length.
|
|
||||||
// The Crc32 will be computed.
|
|
||||||
public static Utf8String FromByteStringUnsafe( byte* path, int length, bool isNullTerminated, bool? isLower = null, bool? isAscii = false )
|
|
||||||
=> new Utf8String().Setup( path, length, null, isNullTerminated, false, isLower, isAscii );
|
|
||||||
|
|
||||||
// Same as above, just with a span.
|
|
||||||
public static Utf8String FromSpanUnsafe( ReadOnlySpan< byte > path, bool isNullTerminated, bool? isLower = null, bool? isAscii = false )
|
|
||||||
{
|
|
||||||
fixed( byte* ptr = path )
|
|
||||||
{
|
|
||||||
return FromByteStringUnsafe( ptr, path.Length, isNullTerminated, isLower, isAscii );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct a Utf8String from a given unicode string, possibly converted to ascii lowercase.
|
|
||||||
// Only returns false if the length exceeds the max length.
|
|
||||||
public static bool FromString( string? path, out Utf8String ret, bool toAsciiLower = false )
|
|
||||||
{
|
|
||||||
if( string.IsNullOrEmpty( path ) )
|
|
||||||
{
|
|
||||||
ret = Empty;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var p = ByteStringFunctions.Utf8FromString( path, out var l, ( int )FlagMask );
|
|
||||||
if( p == null )
|
|
||||||
{
|
|
||||||
ret = Empty;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( toAsciiLower )
|
|
||||||
{
|
|
||||||
ByteStringFunctions.AsciiToLowerInPlace( p, l );
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = new Utf8String().Setup( p, l, null, true, true, toAsciiLower ? true : null, l == path.Length );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Does not check for length and just assumes the isLower state from the second argument.
|
|
||||||
public static Utf8String FromStringUnsafe( string? path, bool? isLower )
|
|
||||||
{
|
|
||||||
if( string.IsNullOrEmpty( path ) )
|
|
||||||
{
|
|
||||||
return Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var p = ByteStringFunctions.Utf8FromString( path, out var l );
|
|
||||||
var ret = new Utf8String().Setup( p, l, null, true, true, isLower, l == path.Length );
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Free memory if the string is owned.
|
|
||||||
private void ReleaseUnmanagedResources()
|
|
||||||
{
|
|
||||||
if( !IsOwned )
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Marshal.FreeHGlobal( ( IntPtr )_path );
|
|
||||||
GC.RemoveMemoryPressure( Length + 1 );
|
|
||||||
_length = AsciiCheckedFlag | AsciiFlag | AsciiLowerCheckedFlag | AsciiLowerFlag | NullTerminatedFlag;
|
|
||||||
_path = Null.NullBytePtr;
|
|
||||||
_crc32 = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manually free memory. Sets the string to an empty string.
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
ReleaseUnmanagedResources();
|
|
||||||
GC.SuppressFinalize( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
~Utf8String()
|
|
||||||
{
|
|
||||||
ReleaseUnmanagedResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup from all given values.
|
|
||||||
// Only called from constructors or factory functions in this library.
|
|
||||||
[MethodImpl( MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization )]
|
|
||||||
internal Utf8String Setup( byte* path, int length, int? crc32, bool isNullTerminated, bool isOwned,
|
|
||||||
bool? isLower = null, bool? isAscii = null )
|
|
||||||
{
|
|
||||||
if( length > FlagMask )
|
|
||||||
{
|
|
||||||
throw new ArgumentOutOfRangeException( nameof( length ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
_path = path;
|
|
||||||
_length = ( uint )length;
|
|
||||||
_crc32 = crc32 ?? ( int )~Lumina.Misc.Crc32.Get( new ReadOnlySpan< byte >( path, length ) );
|
|
||||||
if( isNullTerminated )
|
|
||||||
{
|
|
||||||
_length |= NullTerminatedFlag;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( isOwned )
|
|
||||||
{
|
|
||||||
GC.AddMemoryPressure( length + 1 );
|
|
||||||
_length |= OwnedFlag;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( isLower != null )
|
|
||||||
{
|
|
||||||
_length |= AsciiLowerCheckedFlag;
|
|
||||||
if( isLower.Value )
|
|
||||||
{
|
|
||||||
_length |= AsciiLowerFlag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if( isAscii != null )
|
|
||||||
{
|
|
||||||
_length |= AsciiCheckedFlag;
|
|
||||||
if( isAscii.Value )
|
|
||||||
{
|
|
||||||
_length |= AsciiFlag;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CheckAscii()
|
|
||||||
{
|
|
||||||
switch( _length & ( AsciiCheckedFlag | AsciiFlag ) )
|
|
||||||
{
|
|
||||||
case AsciiCheckedFlag: return false;
|
|
||||||
case AsciiCheckedFlag | AsciiFlag: return true;
|
|
||||||
default:
|
|
||||||
_length |= AsciiCheckedFlag;
|
|
||||||
var isAscii = ByteStringFunctions.IsAscii( _path, Length );
|
|
||||||
if( isAscii )
|
|
||||||
{
|
|
||||||
_length |= AsciiFlag;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isAscii;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool CheckAsciiLower()
|
|
||||||
{
|
|
||||||
switch( _length & ( AsciiLowerCheckedFlag | AsciiLowerFlag ) )
|
|
||||||
{
|
|
||||||
case AsciiLowerCheckedFlag: return false;
|
|
||||||
case AsciiLowerCheckedFlag | AsciiLowerFlag: return true;
|
|
||||||
default:
|
|
||||||
_length |= AsciiLowerCheckedFlag;
|
|
||||||
var isAsciiLower = ByteStringFunctions.IsAsciiLowerCase( _path, Length );
|
|
||||||
if( isAsciiLower )
|
|
||||||
{
|
|
||||||
_length |= AsciiLowerFlag;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isAsciiLower;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool? IsAsciiInternal
|
|
||||||
=> ( _length & ( AsciiCheckedFlag | AsciiFlag ) ) switch
|
|
||||||
{
|
|
||||||
AsciiCheckedFlag => false,
|
|
||||||
AsciiFlag => true,
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
|
|
||||||
private bool? IsAsciiLowerInternal
|
|
||||||
=> ( _length & ( AsciiLowerCheckedFlag | AsciiLowerFlag ) ) switch
|
|
||||||
{
|
|
||||||
AsciiLowerCheckedFlag => false,
|
|
||||||
AsciiLowerFlag => true,
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,168 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.ByteString;
|
|
||||||
|
|
||||||
public sealed unsafe partial class Utf8String
|
|
||||||
{
|
|
||||||
// Create a C# Unicode string from this string.
|
|
||||||
// If the string is known to be pure ASCII, use that encoding, otherwise UTF8.
|
|
||||||
public override string ToString()
|
|
||||||
=> Length == 0
|
|
||||||
? string.Empty
|
|
||||||
: ( _length & AsciiFlag ) != 0
|
|
||||||
? Encoding.ASCII.GetString( _path, Length )
|
|
||||||
: Encoding.UTF8.GetString( _path, Length );
|
|
||||||
|
|
||||||
|
|
||||||
// Convert the ascii portion of the string to lowercase.
|
|
||||||
// Only creates a new string and copy if the string is not already known to be lowercase.
|
|
||||||
public Utf8String AsciiToLower()
|
|
||||||
=> ( _length & AsciiLowerFlag ) == 0
|
|
||||||
? new Utf8String().Setup( ByteStringFunctions.AsciiToLower( _path, Length ), Length, null, true, true, true, IsAsciiInternal )
|
|
||||||
: this;
|
|
||||||
|
|
||||||
// Convert the ascii portion of the string to mixed case (i.e. capitalize every first letter in a word)
|
|
||||||
// Clones the string.
|
|
||||||
public Utf8String AsciiToMixed()
|
|
||||||
{
|
|
||||||
var length = Length;
|
|
||||||
if( length == 0 )
|
|
||||||
{
|
|
||||||
return Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
var ret = Clone();
|
|
||||||
var previousWhitespace = true;
|
|
||||||
var end = ret.Path + length;
|
|
||||||
for( var ptr = ret.Path; ptr < end; ++ptr )
|
|
||||||
{
|
|
||||||
if( previousWhitespace )
|
|
||||||
{
|
|
||||||
*ptr = ByteStringFunctions.AsciiToUpper( *ptr );
|
|
||||||
}
|
|
||||||
|
|
||||||
previousWhitespace = char.IsWhiteSpace( ( char )*ptr );
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert the ascii portion of the string to lowercase.
|
|
||||||
// Guaranteed to create an owned copy.
|
|
||||||
public Utf8String AsciiToLowerClone()
|
|
||||||
=> ( _length & AsciiLowerFlag ) == 0
|
|
||||||
? new Utf8String().Setup( ByteStringFunctions.AsciiToLower( _path, Length ), Length, null, true, true, true, IsAsciiInternal )
|
|
||||||
: Clone();
|
|
||||||
|
|
||||||
// Create an owned copy of the given string.
|
|
||||||
public Utf8String Clone()
|
|
||||||
{
|
|
||||||
var ret = new Utf8String();
|
|
||||||
ret._length = _length | OwnedFlag | NullTerminatedFlag;
|
|
||||||
ret._path = ByteStringFunctions.CopyString( Path, Length );
|
|
||||||
ret._crc32 = Crc32;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a non-owning substring from the given position.
|
|
||||||
// If from is negative or too large, the returned string will be the empty string.
|
|
||||||
public Utf8String Substring( int from )
|
|
||||||
=> ( uint )from < Length
|
|
||||||
? FromByteStringUnsafe( _path + from, Length - from, IsNullTerminated, IsAsciiLowerInternal, IsAsciiInternal )
|
|
||||||
: Empty;
|
|
||||||
|
|
||||||
// Create a non-owning substring from the given position of the given length.
|
|
||||||
// If from is negative or too large, the returned string will be the empty string.
|
|
||||||
// If from + length is too large, it will be the same as if length was not specified.
|
|
||||||
public Utf8String Substring( int from, int length )
|
|
||||||
{
|
|
||||||
var maxLength = Length - ( uint )from;
|
|
||||||
if( maxLength <= 0 )
|
|
||||||
{
|
|
||||||
return Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return length < maxLength
|
|
||||||
? FromByteStringUnsafe( _path + from, length, false, IsAsciiLowerInternal, IsAsciiInternal )
|
|
||||||
: Substring( from );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a owned copy of the string and replace all occurences of from with to in it.
|
|
||||||
public Utf8String Replace( byte from, byte to )
|
|
||||||
{
|
|
||||||
var length = Length;
|
|
||||||
var newPtr = ByteStringFunctions.CopyString( _path, length );
|
|
||||||
var numReplaced = ByteStringFunctions.Replace( newPtr, length, from, to );
|
|
||||||
return new Utf8String().Setup( newPtr, length, numReplaced == 0 ? _crc32 : null, true, true, IsAsciiLowerInternal, IsAsciiInternal );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join a number of strings with a given byte between them.
|
|
||||||
public static Utf8String Join( byte splitter, params Utf8String[] strings )
|
|
||||||
{
|
|
||||||
var length = strings.Sum( s => s.Length ) + strings.Length;
|
|
||||||
var data = ( byte* )Marshal.AllocHGlobal( length );
|
|
||||||
|
|
||||||
var ptr = data;
|
|
||||||
bool? isLower = ByteStringFunctions.AsciiIsLower( splitter );
|
|
||||||
bool? isAscii = splitter < 128;
|
|
||||||
foreach( var s in strings )
|
|
||||||
{
|
|
||||||
Functions.MemCpyUnchecked( ptr, s.Path, s.Length );
|
|
||||||
ptr += s.Length;
|
|
||||||
*ptr++ = splitter;
|
|
||||||
isLower = Combine( isLower, s.IsAsciiLowerInternal );
|
|
||||||
isAscii &= s.IsAscii;
|
|
||||||
}
|
|
||||||
|
|
||||||
--length;
|
|
||||||
data[ length ] = 0;
|
|
||||||
var ret = FromByteStringUnsafe( data, length, true, isLower, isAscii );
|
|
||||||
ret._length |= OwnedFlag;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split a string and return a list of the substrings delimited by b.
|
|
||||||
// You can specify the maximum number of splits (if the maximum is reached, the last substring may contain delimiters).
|
|
||||||
// You can also specify to ignore empty substrings inside delimiters. Those are also not counted for max splits.
|
|
||||||
public List< Utf8String > Split( byte b, int maxSplits = int.MaxValue, bool removeEmpty = true )
|
|
||||||
{
|
|
||||||
var ret = new List< Utf8String >();
|
|
||||||
var start = 0;
|
|
||||||
for( var idx = IndexOf( b, start ); idx >= 0; idx = IndexOf( b, start ) )
|
|
||||||
{
|
|
||||||
if( start != idx || !removeEmpty )
|
|
||||||
{
|
|
||||||
ret.Add( Substring( start, idx - start ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
start = idx + 1;
|
|
||||||
if( ret.Count == maxSplits - 1 )
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ret.Add( Substring( start ) );
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool? Combine( bool? val1, bool? val2 )
|
|
||||||
{
|
|
||||||
return ( val1, val2 ) switch
|
|
||||||
{
|
|
||||||
(null, null) => null,
|
|
||||||
(null, true) => null,
|
|
||||||
(null, false) => false,
|
|
||||||
(true, null) => null,
|
|
||||||
(true, true) => true,
|
|
||||||
(true, false) => false,
|
|
||||||
(false, null) => false,
|
|
||||||
(false, true) => false,
|
|
||||||
(false, false) => false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Enums;
|
namespace Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
|
@ -93,10 +94,10 @@ public static class ResourceTypeExtensions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResourceType FromString( Utf8String path )
|
public static ResourceType FromString( ByteString path )
|
||||||
{
|
{
|
||||||
var extIdx = path.LastIndexOf( ( byte )'.' );
|
var extIdx = path.LastIndexOf( ( byte )'.' );
|
||||||
var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? Utf8String.Empty : path.Substring( extIdx + 1 );
|
var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? ByteString.Empty : path.Substring( extIdx + 1 );
|
||||||
|
|
||||||
return ext.Length switch
|
return ext.Length switch
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
||||||
|
<ProjectReference Include="..\Penumbra.String\Penumbra.String.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -51,6 +52,10 @@
|
||||||
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
</Reference>
|
</Reference>
|
||||||
|
<Reference Include="FFXIVClientStructs">
|
||||||
|
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
<Reference Include="Newtonsoft.Json">
|
<Reference Include="Newtonsoft.Json">
|
||||||
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Structs;
|
namespace Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -107,9 +108,9 @@ public readonly unsafe struct CharacterEquip
|
||||||
|
|
||||||
public void Load( CharacterEquip source )
|
public void Load( CharacterEquip source )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( _armor, source._armor, sizeof( CharacterArmor ) * 10 );
|
MemoryUtility.MemCpyUnchecked( _armor, source._armor, sizeof( CharacterArmor ) * 10 );
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals( CharacterEquip other )
|
public bool Equals( CharacterEquip other )
|
||||||
=> Functions.MemCmpUnchecked( ( void* )_armor, ( void* )other._armor, sizeof( CharacterArmor ) * 10 ) == 0;
|
=> MemoryUtility.MemCmpUnchecked( ( void* )_armor, ( void* )other._armor, sizeof( CharacterArmor ) * 10 ) == 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.GameData.Structs;
|
namespace Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
|
@ -13,7 +13,7 @@ public unsafe struct CustomizeData : IEquatable< CustomizeData >
|
||||||
{
|
{
|
||||||
fixed( byte* ptr = Data )
|
fixed( byte* ptr = Data )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( ptr, source, Size );
|
MemoryUtility.MemCpyUnchecked( ptr, source, Size );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ public unsafe struct CustomizeData : IEquatable< CustomizeData >
|
||||||
{
|
{
|
||||||
fixed( byte* ptr = Data )
|
fixed( byte* ptr = Data )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( target, ptr, Size );
|
MemoryUtility.MemCpyUnchecked( target, ptr, Size );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,12 +36,12 @@ public unsafe struct CustomizeData : IEquatable< CustomizeData >
|
||||||
{
|
{
|
||||||
fixed( byte* ptr = Data )
|
fixed( byte* ptr = Data )
|
||||||
{
|
{
|
||||||
return Functions.MemCmpUnchecked( ptr, other.Data, Size ) == 0;
|
return MemoryUtility.MemCmpUnchecked( ptr, other.Data, Size ) == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool Equals( CustomizeData* lhs, CustomizeData* rhs )
|
public static bool Equals( CustomizeData* lhs, CustomizeData* rhs )
|
||||||
=> Functions.MemCmpUnchecked( lhs, rhs, Size ) == 0;
|
=> MemoryUtility.MemCmpUnchecked( lhs, rhs, Size ) == 0;
|
||||||
|
|
||||||
public override bool Equals( object? obj )
|
public override bool Equals( object? obj )
|
||||||
=> obj is CustomizeData other && Equals( other );
|
=> obj is CustomizeData other && Equals( other );
|
||||||
|
|
|
||||||
|
|
@ -1,157 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using ByteStringFunctions = Penumbra.GameData.ByteString.ByteStringFunctions;
|
|
||||||
|
|
||||||
namespace Penumbra.GameData.Util;
|
|
||||||
|
|
||||||
public static class Functions
|
|
||||||
{
|
|
||||||
public static ulong ComputeCrc64( string name )
|
|
||||||
{
|
|
||||||
if( name.Length == 0 )
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastSlash = name.LastIndexOf( '/' );
|
|
||||||
if( lastSlash == -1 )
|
|
||||||
{
|
|
||||||
return Lumina.Misc.Crc32.Get( name );
|
|
||||||
}
|
|
||||||
|
|
||||||
var folder = name[ ..lastSlash ];
|
|
||||||
var file = name[ ( lastSlash + 1 ).. ];
|
|
||||||
return ( ( ulong )Lumina.Misc.Crc32.Get( folder ) << 32 ) | Lumina.Misc.Crc32.Get( file );
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ulong ComputeCrc64( ReadOnlySpan< byte > name )
|
|
||||||
{
|
|
||||||
if( name.Length == 0 )
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastSlash = name.LastIndexOf( ( byte )'/' );
|
|
||||||
if( lastSlash == -1 )
|
|
||||||
{
|
|
||||||
return Lumina.Misc.Crc32.Get( name );
|
|
||||||
}
|
|
||||||
|
|
||||||
var folder = name[ ..lastSlash ];
|
|
||||||
var file = name[ ( lastSlash + 1 ).. ];
|
|
||||||
return ( ( ulong )Lumina.Misc.Crc32.Get( folder ) << 32 ) | Lumina.Misc.Crc32.Get( file );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly uint[] CrcTable =
|
|
||||||
typeof( Lumina.Misc.Crc32 ).GetField( "CrcTable", BindingFlags.Static | BindingFlags.NonPublic )?.GetValue( null ) as uint[]
|
|
||||||
?? throw new Exception( "Could not fetch CrcTable from Lumina." );
|
|
||||||
|
|
||||||
|
|
||||||
public static unsafe int ComputeCrc64LowerAndSize( byte* ptr, out ulong crc64, out int crc32Ret, out bool isLower, out bool isAscii )
|
|
||||||
{
|
|
||||||
var tmp = ptr;
|
|
||||||
uint crcFolder = 0;
|
|
||||||
uint crcFile = 0;
|
|
||||||
var crc32 = uint.MaxValue;
|
|
||||||
crc64 = 0;
|
|
||||||
isLower = true;
|
|
||||||
isAscii = true;
|
|
||||||
while( true )
|
|
||||||
{
|
|
||||||
var value = *tmp;
|
|
||||||
if( value == 0 )
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ByteStringFunctions.AsciiToLower( *tmp ) != *tmp )
|
|
||||||
{
|
|
||||||
isLower = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( value > 0x80 )
|
|
||||||
{
|
|
||||||
isAscii = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( value == ( byte )'/' )
|
|
||||||
{
|
|
||||||
crcFolder = crc32;
|
|
||||||
crcFile = uint.MaxValue;
|
|
||||||
crc32 = CrcTable[ ( byte )( crc32 ^ value ) ] ^ ( crc32 >> 8 );
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
crcFile = CrcTable[ ( byte )( crcFolder ^ value ) ] ^ ( crcFolder >> 8 );
|
|
||||||
crc32 = CrcTable[ ( byte )( crc32 ^ value ) ] ^ ( crc32 >> 8 );
|
|
||||||
}
|
|
||||||
|
|
||||||
++tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var size = ( int )( tmp - ptr );
|
|
||||||
crc64 = ~( ( ulong )crcFolder << 32 ) | crcFile;
|
|
||||||
crc32Ret = ( int )~crc32;
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static unsafe int ComputeCrc32AsciiLowerAndSize( byte* ptr, out int crc32Ret, out bool isLower, out bool isAscii )
|
|
||||||
{
|
|
||||||
var tmp = ptr;
|
|
||||||
var crc32 = uint.MaxValue;
|
|
||||||
isLower = true;
|
|
||||||
isAscii = true;
|
|
||||||
while( true )
|
|
||||||
{
|
|
||||||
var value = *tmp;
|
|
||||||
if( value == 0 )
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( ByteStringFunctions.AsciiToLower( *tmp ) != *tmp )
|
|
||||||
{
|
|
||||||
isLower = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if( value > 0x80 )
|
|
||||||
{
|
|
||||||
isAscii = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
crc32 = CrcTable[ ( byte )( crc32 ^ value ) ] ^ ( crc32 >> 8 );
|
|
||||||
++tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
var size = ( int )( tmp - ptr );
|
|
||||||
crc32Ret = ( int )~crc32;
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
[DllImport( "msvcrt.dll", EntryPoint = "memcpy", CallingConvention = CallingConvention.Cdecl, SetLastError = false )]
|
|
||||||
private static extern unsafe IntPtr memcpy( void* dest, void* src, int count );
|
|
||||||
|
|
||||||
public static unsafe void MemCpyUnchecked( void* dest, void* src, int count )
|
|
||||||
=> memcpy( dest, src, count );
|
|
||||||
|
|
||||||
|
|
||||||
[DllImport( "msvcrt.dll", EntryPoint = "memcmp", CallingConvention = CallingConvention.Cdecl, SetLastError = false )]
|
|
||||||
private static extern unsafe int memcmp( void* b1, void* b2, 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( void* b1, void* b2, 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 );
|
|
||||||
}
|
|
||||||
1
Penumbra.String
Submodule
1
Penumbra.String
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit f41af0fb88626f1579d3c4370b32b901f3c4d3c2
|
||||||
|
|
@ -16,6 +16,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterG
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
|
@ -38,6 +40,10 @@ Global
|
||||||
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|Any CPU.Build.0 = Release|Any CPU
|
{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5549BAFD-6357-4B1A-800C-75AC36E5B76D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using Dalamud.Plugin;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
@ -12,6 +11,8 @@ using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Api.Helpers;
|
using Penumbra.Api.Helpers;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Api;
|
namespace Penumbra.Api;
|
||||||
|
|
||||||
|
|
@ -536,7 +537,7 @@ public class IpcTester : IDisposable
|
||||||
private unsafe void UpdateLastCreated( IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4 )
|
private unsafe void UpdateLastCreated( IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4 )
|
||||||
{
|
{
|
||||||
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
|
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
|
||||||
_lastCreatedGameObjectName = new Utf8String( obj->GetName() ).ToString();
|
_lastCreatedGameObjectName = new ByteString( obj->GetName() ).ToString();
|
||||||
_lastCreatedGameObjectTime = DateTimeOffset.Now;
|
_lastCreatedGameObjectTime = DateTimeOffset.Now;
|
||||||
_lastCreatedDrawObject = IntPtr.Zero;
|
_lastCreatedDrawObject = IntPtr.Zero;
|
||||||
}
|
}
|
||||||
|
|
@ -544,7 +545,7 @@ public class IpcTester : IDisposable
|
||||||
private unsafe void UpdateLastCreated2( IntPtr gameObject, string _, IntPtr drawObject )
|
private unsafe void UpdateLastCreated2( IntPtr gameObject, string _, IntPtr drawObject )
|
||||||
{
|
{
|
||||||
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
|
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
|
||||||
_lastCreatedGameObjectName = new Utf8String( obj->GetName() ).ToString();
|
_lastCreatedGameObjectName = new ByteString( obj->GetName() ).ToString();
|
||||||
_lastCreatedGameObjectTime = DateTimeOffset.Now;
|
_lastCreatedGameObjectTime = DateTimeOffset.Now;
|
||||||
_lastCreatedDrawObject = drawObject;
|
_lastCreatedDrawObject = drawObject;
|
||||||
}
|
}
|
||||||
|
|
@ -552,7 +553,7 @@ public class IpcTester : IDisposable
|
||||||
private unsafe void UpdateGameObjectResourcePath( IntPtr gameObject, string gamePath, string fullPath )
|
private unsafe void UpdateGameObjectResourcePath( IntPtr gameObject, string gamePath, string fullPath )
|
||||||
{
|
{
|
||||||
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
|
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
|
||||||
_lastResolvedObject = obj != null ? new Utf8String( obj->GetName() ).ToString() : "Unknown";
|
_lastResolvedObject = obj != null ? new ByteString( obj->GetName() ).ToString() : "Unknown";
|
||||||
_lastResolvedGamePath = gamePath;
|
_lastResolvedGamePath = gamePath;
|
||||||
_lastResolvedFullPath = fullPath;
|
_lastResolvedFullPath = fullPath;
|
||||||
_lastResolvedGamePathTime = DateTimeOffset.Now;
|
_lastResolvedGamePathTime = DateTimeOffset.Now;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ using Lumina.Data;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Interop.Resolver;
|
using Penumbra.Interop.Resolver;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
|
@ -15,6 +14,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Api;
|
namespace Penumbra.Api;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Api;
|
namespace Penumbra.Api;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -143,8 +143,8 @@ public partial class ModCollection
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_specialCollections[ ( int )collectionType ] = Empty;
|
_specialCollections[ ( int )collectionType ] = Default;
|
||||||
CollectionChanged.Invoke( collectionType, null, Empty, null );
|
CollectionChanged.Invoke( collectionType, null, Default, null );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,8 +172,8 @@ public partial class ModCollection
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_characters[ characterName ] = Empty;
|
_characters[ characterName ] = Default;
|
||||||
CollectionChanged.Invoke( CollectionType.Character, null, Empty, characterName );
|
CollectionChanged.Invoke( CollectionType.Character, null, Default, characterName );
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Meta.Manager;
|
using Penumbra.Meta.Manager;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
|
@ -8,8 +7,8 @@ using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Penumbra.Interop;
|
using Penumbra.Interop;
|
||||||
using Penumbra.Meta.Files;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Collections;
|
namespace Penumbra.Collections;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manager;
|
using Penumbra.Meta.Manager;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
|
@ -8,6 +7,8 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.GameData.Util;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Collections;
|
namespace Penumbra.Collections;
|
||||||
|
|
||||||
|
|
@ -240,7 +241,7 @@ public partial class ModCollection
|
||||||
if( addMetaChanges )
|
if( addMetaChanges )
|
||||||
{
|
{
|
||||||
++_collection.ChangeCounter;
|
++_collection.ChangeCounter;
|
||||||
if( _collection == Penumbra.CollectionManager.Default && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods )
|
if( _collection == Penumbra.CollectionManager.Default && Penumbra.CharacterUtility.Ready && Penumbra.Config.EnableMods )
|
||||||
{
|
{
|
||||||
Penumbra.ResidentResources.Reload();
|
Penumbra.ResidentResources.Reload();
|
||||||
MetaManipulations.SetFiles();
|
MetaManipulations.SetFiles();
|
||||||
|
|
@ -413,7 +414,7 @@ public partial class ModCollection
|
||||||
// Add the same conflict list to both conflict directions.
|
// Add the same conflict list to both conflict directions.
|
||||||
var conflictList = new List< object > { data };
|
var conflictList = new List< object > { data };
|
||||||
_conflicts[ addedMod ] = addedConflicts.Append( new ModConflicts( existingMod, conflictList, existingPriority < addedPriority,
|
_conflicts[ addedMod ] = addedConflicts.Append( new ModConflicts( existingMod, conflictList, existingPriority < addedPriority,
|
||||||
existingPriority != addedPriority ) );
|
existingPriority != addedPriority ) );
|
||||||
_conflicts[ existingMod ] = existingConflicts.Append( new ModConflicts( addedMod, conflictList,
|
_conflicts[ existingMod ] = existingConflicts.Append( new ModConflicts( addedMod, conflictList,
|
||||||
existingPriority >= addedPriority,
|
existingPriority >= addedPriority,
|
||||||
existingPriority != addedPriority ) );
|
existingPriority != addedPriority ) );
|
||||||
|
|
@ -474,9 +475,9 @@ public partial class ModCollection
|
||||||
// Skip IMCs because they would result in far too many false-positive items,
|
// Skip IMCs because they would result in far too many false-positive items,
|
||||||
// since they are per set instead of per item-slot/item/variant.
|
// since they are per set instead of per item-slot/item/variant.
|
||||||
var identifier = GameData.GameData.GetIdentifier();
|
var identifier = GameData.GameData.GetIdentifier();
|
||||||
foreach( var (resolved, modPath) in ResolvedFiles.Where( file => !file.Key.Path.EndsWith( 'i', 'm', 'c' ) ) )
|
foreach( var (resolved, modPath) in ResolvedFiles.Where( file => !file.Key.Path.EndsWith( "imc"u8 ) ) )
|
||||||
{
|
{
|
||||||
foreach( var (name, obj) in identifier.Identify( resolved.ToGamePath() ) )
|
foreach( var (name, obj) in identifier.Identify( new GamePath( resolved.ToString() ) ) )
|
||||||
{
|
{
|
||||||
if( !_changedItems.TryGetValue( name, out var data ) )
|
if( !_changedItems.TryGetValue( name, out var data ) )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using Dalamud.Interface.ImGuiFileDialog;
|
using Dalamud.Interface.ImGuiFileDialog;
|
||||||
|
|
@ -11,7 +10,7 @@ using Lumina.Data.Files;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterTex;
|
using OtterTex;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
using SixLabors.ImageSharp.PixelFormats;
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
using Image = SixLabors.ImageSharp.Image;
|
using Image = SixLabors.ImageSharp.Image;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop;
|
namespace Penumbra.Interop;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,9 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
using FFXIVClientStructs.STD;
|
using FFXIVClientStructs.STD;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Loader;
|
namespace Penumbra.Interop.Loader;
|
||||||
|
|
||||||
|
|
@ -19,7 +20,7 @@ public unsafe partial class ResourceLoader
|
||||||
private readonly Hook< ResourceHandleDecRef > _decRefHook;
|
private readonly Hook< ResourceHandleDecRef > _decRefHook;
|
||||||
|
|
||||||
public delegate IntPtr ResourceHandleDestructor( ResourceHandle* handle );
|
public delegate IntPtr ResourceHandleDestructor( ResourceHandle* handle );
|
||||||
|
|
||||||
[Signature( "48 89 5C 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 B8",
|
[Signature( "48 89 5C 24 ?? 57 48 83 EC ?? 48 8D 05 ?? ?? ?? ?? 48 8B D9 48 89 01 B8",
|
||||||
DetourName = nameof( ResourceHandleDestructorDetour ) )]
|
DetourName = nameof( ResourceHandleDestructorDetour ) )]
|
||||||
public static Hook< ResourceHandleDestructor >? ResourceHandleDestructorHook;
|
public static Hook< ResourceHandleDestructor >? ResourceHandleDestructorHook;
|
||||||
|
|
@ -28,7 +29,7 @@ public unsafe partial class ResourceLoader
|
||||||
{
|
{
|
||||||
if( handle != null )
|
if( handle != null )
|
||||||
{
|
{
|
||||||
Penumbra.Log.Information( $"[ResourceLoader] Destructing Resource Handle {handle->FileName} at 0x{( ulong )handle:X} (Refcount {handle->RefCount}).");
|
Penumbra.Log.Information( $"[ResourceLoader] Destructing Resource Handle {handle->FileName} at 0x{( ulong )handle:X} (Refcount {handle->RefCount})." );
|
||||||
}
|
}
|
||||||
|
|
||||||
return ResourceHandleDestructorHook!.Original( handle );
|
return ResourceHandleDestructorHook!.Original( handle );
|
||||||
|
|
@ -248,7 +249,7 @@ public unsafe partial class ResourceLoader
|
||||||
Penumbra.Log.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" );
|
Penumbra.Log.Information( $"[ResourceLoader] Loaded {pathString} to 0x{( ulong )handle:X}. (Refcount {handle->RefCount})" );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void LogLoadedFile( Utf8String path, bool success, bool custom )
|
private static void LogLoadedFile( ByteString path, bool success, bool custom )
|
||||||
=> Penumbra.Log.Information( success
|
=> Penumbra.Log.Information( success
|
||||||
? $"[ResourceLoader] Loaded {path} from {( custom ? "local files" : "SqPack" )}"
|
? $"[ResourceLoader] Loaded {path} from {( custom ? "local files" : "SqPack" )}"
|
||||||
: $"[ResourceLoader] Failed to load {path} from {( custom ? "local files" : "SqPack" )}." );
|
: $"[ResourceLoader] Failed to load {path} from {( custom ? "local files" : "SqPack" )}." );
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,10 @@ using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
using FileMode = Penumbra.Interop.Structs.FileMode;
|
using FileMode = Penumbra.Interop.Structs.FileMode;
|
||||||
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
using ResourceHandle = FFXIVClientStructs.FFXIV.Client.System.Resource.Handle.ResourceHandle;
|
||||||
|
|
||||||
|
|
@ -71,7 +72,7 @@ public unsafe partial class ResourceLoader
|
||||||
|
|
||||||
private event Action< Utf8GamePath, ResourceType, FullPath?, object? >? PathResolved;
|
private event Action< Utf8GamePath, ResourceType, FullPath?, object? >? PathResolved;
|
||||||
|
|
||||||
public ResourceHandle* ResolvePathSync( ResourceCategory category, ResourceType type, Utf8String path )
|
public ResourceHandle* ResolvePathSync( ResourceCategory category, ResourceType type, ByteString path )
|
||||||
{
|
{
|
||||||
var hash = path.Crc32;
|
var hash = path.Crc32;
|
||||||
return GetResourceHandler( true, *ResourceManager, &category, &type, &hash, path.Path, null, false );
|
return GetResourceHandler( true, *ResourceManager, &category, &type, &hash, path.Path, null, false );
|
||||||
|
|
@ -209,7 +210,7 @@ public unsafe partial class ResourceLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the resource from an SqPack and trigger the FileLoaded event.
|
// Load the resource from an SqPack and trigger the FileLoaded event.
|
||||||
private byte DefaultResourceLoad( Utf8String path, ResourceManager* resourceManager,
|
private byte DefaultResourceLoad( ByteString path, ResourceManager* resourceManager,
|
||||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
|
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
|
||||||
{
|
{
|
||||||
var ret = Penumbra.ResourceLoader.ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
|
var ret = Penumbra.ResourceLoader.ReadSqPackHook.Original( resourceManager, fileDescriptor, priority, isSync );
|
||||||
|
|
@ -218,7 +219,7 @@ public unsafe partial class ResourceLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load the resource from a path on the users hard drives.
|
// Load the resource from a path on the users hard drives.
|
||||||
private byte DefaultRootedResourceLoad( Utf8String gamePath, ResourceManager* resourceManager,
|
private byte DefaultRootedResourceLoad( ByteString gamePath, ResourceManager* resourceManager,
|
||||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
|
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
|
||||||
{
|
{
|
||||||
// Specify that we are loading unpacked files from the drive.
|
// Specify that we are loading unpacked files from the drive.
|
||||||
|
|
@ -246,7 +247,7 @@ public unsafe partial class ResourceLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load a resource by its path. If it is rooted, it will be loaded from the drive, otherwise from the SqPack.
|
// Load a resource by its path. If it is rooted, it will be loaded from the drive, otherwise from the SqPack.
|
||||||
internal byte DefaultLoadResource( Utf8String gamePath, ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority,
|
internal byte DefaultLoadResource( ByteString gamePath, ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority,
|
||||||
bool isSync )
|
bool isSync )
|
||||||
=> Utf8GamePath.IsRooted( gamePath )
|
=> Utf8GamePath.IsRooted( gamePath )
|
||||||
? DefaultRootedResourceLoad( gamePath, resourceManager, fileDescriptor, priority, isSync )
|
? DefaultRootedResourceLoad( gamePath, resourceManager, fileDescriptor, priority, isSync )
|
||||||
|
|
@ -262,7 +263,7 @@ public unsafe partial class ResourceLoader
|
||||||
_incRefHook.Dispose();
|
_incRefHook.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int ComputeHash( Utf8String path, GetResourceParameters* pGetResParams )
|
private static int ComputeHash( ByteString path, GetResourceParameters* pGetResParams )
|
||||||
{
|
{
|
||||||
if( pGetResParams == null || !pGetResParams->IsPartialRead )
|
if( pGetResParams == null || !pGetResParams->IsPartialRead )
|
||||||
{
|
{
|
||||||
|
|
@ -272,11 +273,11 @@ public unsafe partial class ResourceLoader
|
||||||
// When the game requests file only partially, crc32 includes that information, in format of:
|
// When the game requests file only partially, crc32 includes that information, in format of:
|
||||||
// path/to/file.ext.hex_offset.hex_size
|
// path/to/file.ext.hex_offset.hex_size
|
||||||
// ex) music/ex4/BGM_EX4_System_Title.scd.381adc.30000
|
// ex) music/ex4/BGM_EX4_System_Title.scd.381adc.30000
|
||||||
return Utf8String.Join(
|
return ByteString.Join(
|
||||||
( byte )'.',
|
( byte )'.',
|
||||||
path,
|
path,
|
||||||
Utf8String.FromStringUnsafe( pGetResParams->SegmentOffset.ToString( "x" ), true ),
|
ByteString.FromStringUnsafe( pGetResParams->SegmentOffset.ToString( "x" ), true ),
|
||||||
Utf8String.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true )
|
ByteString.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true )
|
||||||
).Crc32;
|
).Crc32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ using System.Collections.Generic;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Loader;
|
namespace Penumbra.Interop.Loader;
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ public unsafe partial class ResourceLoader
|
||||||
// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag.
|
// We use it to check against our stored CRC64s and if it corresponds, we return the custom flag.
|
||||||
public delegate IntPtr CheckFileStatePrototype( IntPtr unk1, ulong crc64 );
|
public delegate IntPtr CheckFileStatePrototype( IntPtr unk1, ulong crc64 );
|
||||||
|
|
||||||
[Signature( "E8 ?? ?? ?? ?? 48 85 c0 74 ?? 45 0f b6 ce 48 89 44 24", DetourName = nameof(CheckFileStateDetour) )]
|
[Signature( "E8 ?? ?? ?? ?? 48 85 c0 74 ?? 45 0f b6 ce 48 89 44 24", DetourName = nameof( CheckFileStateDetour ) )]
|
||||||
public Hook< CheckFileStatePrototype > CheckFileStateHook = null!;
|
public Hook< CheckFileStatePrototype > CheckFileStateHook = null!;
|
||||||
|
|
||||||
private IntPtr CheckFileStateDetour( IntPtr ptr, ulong crc64 )
|
private IntPtr CheckFileStateDetour( IntPtr ptr, ulong crc64 )
|
||||||
|
|
@ -48,7 +48,7 @@ public unsafe partial class ResourceLoader
|
||||||
// We hook the extern functions to just return the local one if given the custom flag as last argument.
|
// We hook the extern functions to just return the local one if given the custom flag as last argument.
|
||||||
public delegate byte LoadTexFileExternPrototype( ResourceHandle* handle, int unk1, IntPtr unk2, bool unk3, IntPtr unk4 );
|
public delegate byte LoadTexFileExternPrototype( ResourceHandle* handle, int unk1, IntPtr unk2, bool unk3, IntPtr unk4 );
|
||||||
|
|
||||||
[Signature( "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8", DetourName = nameof(LoadTexFileExternDetour) )]
|
[Signature( "E8 ?? ?? ?? ?? 0F B6 E8 48 8B CB E8", DetourName = nameof( LoadTexFileExternDetour ) )]
|
||||||
public Hook< LoadTexFileExternPrototype > LoadTexFileExternHook = null!;
|
public Hook< LoadTexFileExternPrototype > LoadTexFileExternHook = null!;
|
||||||
|
|
||||||
private byte LoadTexFileExternDetour( ResourceHandle* resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr ptr )
|
private byte LoadTexFileExternDetour( ResourceHandle* resourceHandle, int unk1, IntPtr unk2, bool unk3, IntPtr ptr )
|
||||||
|
|
@ -59,7 +59,7 @@ public unsafe partial class ResourceLoader
|
||||||
public delegate byte LoadMdlFileExternPrototype( ResourceHandle* handle, IntPtr unk1, bool unk2, IntPtr unk3 );
|
public delegate byte LoadMdlFileExternPrototype( ResourceHandle* handle, IntPtr unk1, bool unk2, IntPtr unk3 );
|
||||||
|
|
||||||
|
|
||||||
[Signature( "E8 ?? ?? ?? ?? EB 02 B0 F1", DetourName = nameof(LoadMdlFileExternDetour) )]
|
[Signature( "E8 ?? ?? ?? ?? EB 02 B0 F1", DetourName = nameof( LoadMdlFileExternDetour ) )]
|
||||||
public Hook< LoadMdlFileExternPrototype > LoadMdlFileExternHook = null!;
|
public Hook< LoadMdlFileExternPrototype > LoadMdlFileExternHook = null!;
|
||||||
|
|
||||||
private byte LoadMdlFileExternDetour( ResourceHandle* resourceHandle, IntPtr unk1, bool unk2, IntPtr ptr )
|
private byte LoadMdlFileExternDetour( ResourceHandle* resourceHandle, IntPtr unk1, bool unk2, IntPtr ptr )
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,10 @@ using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Loader;
|
namespace Penumbra.Interop.Loader;
|
||||||
|
|
||||||
|
|
@ -127,7 +128,7 @@ public unsafe partial class ResourceLoader : IDisposable
|
||||||
// Event fired whenever a resource is newly loaded.
|
// Event fired whenever a resource is newly loaded.
|
||||||
// Success indicates the return value of the loading function (which does not imply that the resource was actually successfully loaded)
|
// Success indicates the return value of the loading function (which does not imply that the resource was actually successfully loaded)
|
||||||
// custom is true if the file was loaded from local files instead of the default SqPacks.
|
// custom is true if the file was loaded from local files instead of the default SqPacks.
|
||||||
public delegate void FileLoadedDelegate( Utf8String path, bool success, bool custom );
|
public delegate void FileLoadedDelegate( ByteString path, bool success, bool custom );
|
||||||
public event FileLoadedDelegate? FileLoaded;
|
public event FileLoadedDelegate? FileLoaded;
|
||||||
|
|
||||||
// Customization point to control how path resolving is handled.
|
// Customization point to control how path resolving is handled.
|
||||||
|
|
@ -140,7 +141,7 @@ public unsafe partial class ResourceLoader : IDisposable
|
||||||
|
|
||||||
// Customize file loading for any GamePaths that start with "|".
|
// Customize file loading for any GamePaths that start with "|".
|
||||||
// Same procedure as above.
|
// Same procedure as above.
|
||||||
public delegate bool ResourceLoadCustomizationDelegate( Utf8String split, Utf8String path, ResourceManager* resourceManager,
|
public delegate bool ResourceLoadCustomizationDelegate( ByteString split, ByteString path, ResourceManager* resourceManager,
|
||||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte retValue );
|
SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte retValue );
|
||||||
|
|
||||||
public event ResourceLoadCustomizationDelegate? ResourceLoadCustomization;
|
public event ResourceLoadCustomizationDelegate? ResourceLoadCustomization;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Loader;
|
namespace Penumbra.Interop.Loader;
|
||||||
|
|
||||||
|
|
@ -84,7 +85,7 @@ public class ResourceLogger : IDisposable
|
||||||
|
|
||||||
// Returns the converted string if the filter matches, and null otherwise.
|
// Returns the converted string if the filter matches, and null otherwise.
|
||||||
// The filter matches if it is empty, if it is a valid and matching regex or if the given string contains it.
|
// The filter matches if it is empty, if it is a valid and matching regex or if the given string contains it.
|
||||||
private string? Match( Utf8String data )
|
private string? Match( ByteString data )
|
||||||
{
|
{
|
||||||
var s = data.ToString();
|
var s = data.ToString();
|
||||||
return Filter.Length == 0 || ( _filterRegex?.IsMatch( s ) ?? s.Contains( Filter, StringComparison.OrdinalIgnoreCase ) )
|
return Filter.Length == 0 || ( _filterRegex?.IsMatch( s ) ?? s.Contains( Filter, StringComparison.OrdinalIgnoreCase ) )
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ using System;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Resolver;
|
namespace Penumbra.Interop.Resolver;
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using Penumbra.Api;
|
using Penumbra.Api;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Resolver;
|
namespace Penumbra.Interop.Resolver;
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ public unsafe partial class PathResolver
|
||||||
{
|
{
|
||||||
if( type == ResourceType.Tex
|
if( type == ResourceType.Tex
|
||||||
&& LastCreatedCollection.Valid
|
&& LastCreatedCollection.Valid
|
||||||
&& gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( 'd', 'e', 'c', 'a', 'l' ) )
|
&& gamePath.Path.Substring( "chara/common/texture/".Length ).StartsWith( "decal"u8 ) )
|
||||||
{
|
{
|
||||||
resolveData = LastCreatedCollection;
|
resolveData = LastCreatedCollection;
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
using FFXIVClientStructs.FFXIV.Component.GUI;
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using Lumina.Excel.GeneratedSheets;
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.String;
|
||||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
|
||||||
|
|
||||||
|
|
@ -66,7 +66,7 @@ public unsafe partial class PathResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
var block = data + 0x7A;
|
var block = data + 0x7A;
|
||||||
return new Utf8String( block ).ToString();
|
return new ByteString( block ).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain the name of the player character if the glamour plate edit window is open.
|
// Obtain the name of the player character if the glamour plate edit window is open.
|
||||||
|
|
@ -130,7 +130,7 @@ public unsafe partial class PathResolver
|
||||||
|
|
||||||
if( owner != null )
|
if( owner != null )
|
||||||
{
|
{
|
||||||
return new Utf8String( owner->Name ).ToString();
|
return new ByteString( owner->Name ).ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -169,7 +169,7 @@ public unsafe partial class PathResolver
|
||||||
if( Penumbra.Config.PreferNamedCollectionsOverOwners )
|
if( Penumbra.Config.PreferNamedCollectionsOverOwners )
|
||||||
{
|
{
|
||||||
// Early return if we prefer the actors own name over its owner.
|
// Early return if we prefer the actors own name over its owner.
|
||||||
actorName = new Utf8String( gameObject->Name ).ToString();
|
actorName = new ByteString( gameObject->Name ).ToString();
|
||||||
if( actorName.Length > 0
|
if( actorName.Length > 0
|
||||||
&& CollectionByActorName( actorName, out var actorCollection ) )
|
&& CollectionByActorName( actorName, out var actorCollection ) )
|
||||||
{
|
{
|
||||||
|
|
@ -189,7 +189,7 @@ public unsafe partial class PathResolver
|
||||||
>= CutsceneCharacters.CutsceneStartIdx and < CutsceneCharacters.CutsceneEndIdx => GetCutsceneName( gameObject ),
|
>= CutsceneCharacters.CutsceneStartIdx and < CutsceneCharacters.CutsceneEndIdx => GetCutsceneName( gameObject ),
|
||||||
_ => null,
|
_ => null,
|
||||||
}
|
}
|
||||||
?? GetOwnerName( gameObject ) ?? actorName ?? new Utf8String( gameObject->Name ).ToString();
|
?? GetOwnerName( gameObject ) ?? actorName ?? new ByteString( gameObject->Name ).ToString();
|
||||||
|
|
||||||
// First check temporary character collections, then the own configuration, then special collections.
|
// First check temporary character collections, then the own configuration, then special collections.
|
||||||
var collection = CollectionByActorName( actualName, out var c )
|
var collection = CollectionByActorName( actualName, out var c )
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Resolver;
|
namespace Penumbra.Interop.Resolver;
|
||||||
|
|
||||||
|
|
@ -78,7 +79,7 @@ public unsafe partial class PathResolver
|
||||||
|
|
||||||
// We need to set the correct collection for the actual material path that is loaded
|
// We need to set the correct collection for the actual material path that is loaded
|
||||||
// before actually loading the file.
|
// before actually loading the file.
|
||||||
public bool MtrlLoadHandler( Utf8String split, Utf8String path, ResourceManager* resourceManager,
|
public bool MtrlLoadHandler( ByteString split, ByteString path, ResourceManager* resourceManager,
|
||||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte ret )
|
SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte ret )
|
||||||
{
|
{
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
@ -149,7 +150,7 @@ public unsafe partial class PathResolver
|
||||||
}
|
}
|
||||||
|
|
||||||
var mtrl = ( MtrlResource* )mtrlResourceHandle;
|
var mtrl = ( MtrlResource* )mtrlResourceHandle;
|
||||||
var mtrlPath = Utf8String.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true );
|
var mtrlPath = ByteString.FromSpanUnsafe( mtrl->Handle.FileNameSpan(), true, null, true );
|
||||||
_mtrlData = _paths.TryGetValue( mtrlPath, out var c ) ? c : ResolveData.Invalid;
|
_mtrlData = _paths.TryGetValue( mtrlPath, out var c ) ? c : ResolveData.Invalid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.String;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Resolver;
|
namespace Penumbra.Interop.Resolver;
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ public unsafe partial class PathResolver
|
||||||
private readonly ResolverHooks _monster;
|
private readonly ResolverHooks _monster;
|
||||||
|
|
||||||
// This map links files to their corresponding collection, if it is non-default.
|
// This map links files to their corresponding collection, if it is non-default.
|
||||||
private readonly ConcurrentDictionary< Utf8String, ResolveData > _pathCollections = new();
|
private readonly ConcurrentDictionary< ByteString, ResolveData > _pathCollections = new();
|
||||||
|
|
||||||
public PathState( PathResolver parent )
|
public PathState( PathResolver parent )
|
||||||
{
|
{
|
||||||
|
|
@ -69,13 +69,13 @@ public unsafe partial class PathResolver
|
||||||
public int Count
|
public int Count
|
||||||
=> _pathCollections.Count;
|
=> _pathCollections.Count;
|
||||||
|
|
||||||
public IEnumerable< KeyValuePair< Utf8String, ResolveData > > Paths
|
public IEnumerable< KeyValuePair< ByteString, ResolveData > > Paths
|
||||||
=> _pathCollections;
|
=> _pathCollections;
|
||||||
|
|
||||||
public bool TryGetValue( Utf8String path, out ResolveData collection )
|
public bool TryGetValue( ByteString path, out ResolveData collection )
|
||||||
=> _pathCollections.TryGetValue( path, out collection );
|
=> _pathCollections.TryGetValue( path, out collection );
|
||||||
|
|
||||||
public bool Consume( Utf8String path, out ResolveData collection )
|
public bool Consume( ByteString path, out ResolveData collection )
|
||||||
=> _pathCollections.TryRemove( path, out collection );
|
=> _pathCollections.TryRemove( path, out collection );
|
||||||
|
|
||||||
// Just add or remove the resolved path.
|
// Just add or remove the resolved path.
|
||||||
|
|
@ -87,13 +87,13 @@ public unsafe partial class PathResolver
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
var gamePath = new Utf8String( ( byte* )path );
|
var gamePath = new ByteString( ( byte* )path );
|
||||||
SetCollection( gameObject, gamePath, collection );
|
SetCollection( gameObject, gamePath, collection );
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling for paths so that we do not store non-owned temporary strings in the dictionary.
|
// Special handling for paths so that we do not store non-owned temporary strings in the dictionary.
|
||||||
public void SetCollection( IntPtr gameObject, Utf8String path, ModCollection collection )
|
public void SetCollection( IntPtr gameObject, ByteString path, ModCollection collection )
|
||||||
{
|
{
|
||||||
if( _pathCollections.ContainsKey( path ) || path.IsOwned )
|
if( _pathCollections.ContainsKey( path ) || path.IsOwned )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,10 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Loader;
|
using Penumbra.Interop.Loader;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Interop.Resolver;
|
namespace Penumbra.Interop.Resolver;
|
||||||
|
|
||||||
|
|
@ -151,7 +152,7 @@ public partial class PathResolver : IDisposable
|
||||||
return resolveData;
|
return resolveData;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal IEnumerable< KeyValuePair< Utf8String, ResolveData > > PathCollections
|
internal IEnumerable< KeyValuePair< ByteString, ResolveData > > PathCollections
|
||||||
=> _paths.Paths;
|
=> _paths.Paths;
|
||||||
|
|
||||||
internal IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjectMap
|
internal IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjectMap
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using Penumbra.GameData.Structs;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ public sealed unsafe class CmpFile : MetaBaseFile
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Reset()
|
public override void Reset()
|
||||||
=> Functions.MemCpyUnchecked( Data, ( byte* )DefaultData.Data, DefaultData.Length );
|
=> MemoryUtility.MemCpyUnchecked( Data, ( byte* )DefaultData.Data, DefaultData.Length );
|
||||||
|
|
||||||
public void Reset( IEnumerable< (SubRace, RspAttribute) > entries )
|
public void Reset( IEnumerable< (SubRace, RspAttribute) > entries )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
|
|
@ -63,7 +64,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
||||||
public override void Reset()
|
public override void Reset()
|
||||||
{
|
{
|
||||||
var def = ( byte* )DefaultData.Data;
|
var def = ( byte* )DefaultData.Data;
|
||||||
Functions.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize );
|
MemoryUtility.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize );
|
||||||
|
|
||||||
var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize );
|
var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize );
|
||||||
var dataBasePtr = controlPtr + BlockCount;
|
var dataBasePtr = controlPtr + BlockCount;
|
||||||
|
|
@ -73,18 +74,18 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
|
||||||
{
|
{
|
||||||
if( controlPtr[ i ] == CollapsedBlock )
|
if( controlPtr[ i ] == CollapsedBlock )
|
||||||
{
|
{
|
||||||
Functions.MemSet( myDataPtr, 0, BlockSize * EqdpEntrySize );
|
MemoryUtility.MemSet( myDataPtr, 0, BlockSize * EqdpEntrySize );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize );
|
MemoryUtility.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize );
|
||||||
}
|
}
|
||||||
|
|
||||||
myControlPtr[ i ] = ( ushort )( i * BlockSize );
|
myControlPtr[ i ] = ( ushort )( i * BlockSize );
|
||||||
myDataPtr += BlockSize;
|
myDataPtr += BlockSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Functions.MemSet( myDataPtr, 0, Length - ( int )( ( byte* )myDataPtr - Data ) );
|
MemoryUtility.MemSet( myDataPtr, 0, Length - ( int )( ( byte* )myDataPtr - Data ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Reset( IEnumerable< int > entries )
|
public void Reset( IEnumerable< int > entries )
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Numerics;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
|
|
@ -49,7 +50,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
||||||
|
|
||||||
protected virtual void SetEmptyBlock( int idx )
|
protected virtual void SetEmptyBlock( int idx )
|
||||||
{
|
{
|
||||||
Functions.MemSet( Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize );
|
MemoryUtility.MemSet( Data + idx * BlockSize * EntrySize, 0, BlockSize * EntrySize );
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed override void Reset()
|
public sealed override void Reset()
|
||||||
|
|
@ -62,7 +63,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
|
||||||
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
|
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
|
||||||
if( !collapsed )
|
if( !collapsed )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize, BlockSize * EntrySize );
|
MemoryUtility.MemCpyUnchecked( Data + i * BlockSize * EntrySize, ptr + expandedBlocks * BlockSize * EntrySize, BlockSize * EntrySize );
|
||||||
expandedBlocks++;
|
expandedBlocks++;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
|
|
@ -170,8 +171,8 @@ public sealed unsafe class EstFile : MetaBaseFile
|
||||||
{
|
{
|
||||||
var (d, length) = DefaultData;
|
var (d, length) = DefaultData;
|
||||||
var data = ( byte* )d;
|
var data = ( byte* )d;
|
||||||
Functions.MemCpyUnchecked( Data, data, length );
|
MemoryUtility.MemCpyUnchecked( Data, data, length );
|
||||||
Functions.MemSet( Data + length, 0, Length - length );
|
MemoryUtility.MemSet( Data + length, 0, Length - length );
|
||||||
}
|
}
|
||||||
|
|
||||||
public EstFile( EstManipulation.EstType estType )
|
public EstFile( EstManipulation.EstType estType )
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Penumbra.GameData.ByteString;
|
using OtterGui;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
||||||
|
|
@ -150,7 +151,7 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
var defaultPtr = ( ImcEntry* )( Data + PreambleSize );
|
var defaultPtr = ( ImcEntry* )( Data + PreambleSize );
|
||||||
for( var i = oldCount + 1; i < numVariants + 1; ++i )
|
for( var i = oldCount + 1; i < numVariants + 1; ++i )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( defaultPtr + i * NumParts, defaultPtr, NumParts * sizeof( ImcEntry ) );
|
MemoryUtility.MemCpyUnchecked( defaultPtr + i * NumParts, defaultPtr, NumParts * sizeof( ImcEntry ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
Penumbra.Log.Verbose( $"Expanded IMC {Path} from {oldCount} to {numVariants} variants." );
|
Penumbra.Log.Verbose( $"Expanded IMC {Path} from {oldCount} to {numVariants} variants." );
|
||||||
|
|
@ -188,8 +189,8 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
var file = Dalamud.GameData.GetFile( Path.ToString() );
|
var file = Dalamud.GameData.GetFile( Path.ToString() );
|
||||||
fixed( byte* ptr = file!.Data )
|
fixed( byte* ptr = file!.Data )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
MemoryUtility.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
||||||
Functions.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
|
MemoryUtility.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,7 +208,7 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
{
|
{
|
||||||
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
|
NumParts = BitOperations.PopCount( *( ushort* )( ptr + 2 ) );
|
||||||
AllocateData( file.Data.Length );
|
AllocateData( file.Data.Length );
|
||||||
Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
MemoryUtility.MemCpyUnchecked( Data, ptr, file.Data.Length );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,7 +244,7 @@ public unsafe class ImcFile : MetaBaseFile
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Functions.MemCpyUnchecked( newData, Data, ActualLength );
|
MemoryUtility.MemCpyUnchecked( newData, Data, ActualLength );
|
||||||
|
|
||||||
Penumbra.MetaFileManager.Free( data, length );
|
Penumbra.MetaFileManager.Free( data, length );
|
||||||
resource->SetData( ( IntPtr )newData, ActualLength );
|
resource->SetData( ( IntPtr )newData, ActualLength );
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using Dalamud.Memory;
|
using Dalamud.Memory;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Files;
|
namespace Penumbra.Meta.Files;
|
||||||
|
|
@ -57,12 +58,12 @@ public unsafe class MetaBaseFile : IDisposable
|
||||||
var data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( ( ulong )newLength );
|
var data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( ( ulong )newLength );
|
||||||
if( newLength > Length )
|
if( newLength > Length )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( data, Data, Length );
|
MemoryUtility.MemCpyUnchecked( data, Data, Length );
|
||||||
Functions.MemSet( data + Length, 0, newLength - Length );
|
MemoryUtility.MemSet( data + Length, 0, newLength - Length );
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( data, Data, newLength );
|
MemoryUtility.MemCpyUnchecked( data, Data, newLength );
|
||||||
}
|
}
|
||||||
|
|
||||||
ReleaseUnmanagedResources();
|
ReleaseUnmanagedResources();
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Files;
|
using Penumbra.Meta.Files;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manager;
|
namespace Penumbra.Meta.Manager;
|
||||||
|
|
||||||
|
|
@ -149,7 +150,7 @@ public partial class MetaManager
|
||||||
=> new($"|{_collection.Name}_{_collection.ChangeCounter}|{path}");
|
=> new($"|{_collection.Name}_{_collection.ChangeCounter}|{path}");
|
||||||
|
|
||||||
|
|
||||||
private static unsafe bool ImcLoadHandler( Utf8String split, Utf8String path, ResourceManager* resourceManager,
|
private static unsafe bool ImcLoadHandler( ByteString split, ByteString path, ResourceManager* resourceManager,
|
||||||
SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte ret )
|
SeFileDescriptor* fileDescriptor, int priority, bool isSync, out byte ret )
|
||||||
{
|
{
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
using Penumbra.Meta.Files;
|
using Penumbra.Meta.Files;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manipulations;
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Converters;
|
using Newtonsoft.Json.Converters;
|
||||||
using Penumbra.GameData.Util;
|
using Penumbra.GameData.Util;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.Meta.Manipulations;
|
namespace Penumbra.Meta.Manipulations;
|
||||||
|
|
||||||
|
|
@ -217,7 +218,7 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >, ICompa
|
||||||
{
|
{
|
||||||
fixed( MetaManipulation* lhs = &this )
|
fixed( MetaManipulation* lhs = &this )
|
||||||
{
|
{
|
||||||
return Functions.MemCmpUnchecked( lhs, &other, sizeof( MetaManipulation ) );
|
return MemoryUtility.MemCmpUnchecked( lhs, &other, sizeof( MetaManipulation ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -124,7 +124,7 @@ public partial class Mod
|
||||||
foreach( var file in files )
|
foreach( var file in files )
|
||||||
{
|
{
|
||||||
// Skip any UI Files because deduplication causes weird crashes for those.
|
// Skip any UI Files because deduplication causes weird crashes for those.
|
||||||
if( file.SubModUsage.Any( f => f.Item2.Path.StartsWith( 'u', 'i', '/' ) ) )
|
if( file.SubModUsage.Any( f => f.Item2.Path.StartsWith( "ui/"u8 ) ) )
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,9 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ using System.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Penumbra.GameData.Util;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -14,7 +15,7 @@ public sealed partial class Mod
|
||||||
ChangedItems.Clear();
|
ChangedItems.Clear();
|
||||||
foreach( var gamePath in AllRedirects )
|
foreach( var gamePath in AllRedirects )
|
||||||
{
|
{
|
||||||
identifier.Identify( ChangedItems, gamePath.ToGamePath() );
|
identifier.Identify( ChangedItems, new GamePath(gamePath.ToString()) );
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: manipulations
|
// TODO: manipulations
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ using Dalamud.Utility;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Filesystem;
|
using OtterGui.Filesystem;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Import;
|
using Penumbra.Import;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ using System.Linq;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
@ -59,7 +59,7 @@ public sealed partial class Mod
|
||||||
var defaultMod = mod._default;
|
var defaultMod = mod._default;
|
||||||
foreach( var (gamePath, fullPath) in collection.ResolvedFiles )
|
foreach( var (gamePath, fullPath) in collection.ResolvedFiles )
|
||||||
{
|
{
|
||||||
if( gamePath.Path.EndsWith( '.', 'i', 'm', 'c' ) )
|
if( gamePath.Path.EndsWith( ".imc"u8 ) )
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Import;
|
using Penumbra.Import;
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.Mods;
|
namespace Penumbra.Mods;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Dalamud.Game.Command;
|
using Dalamud.Game.Command;
|
||||||
using Dalamud.Interface.Windowing;
|
using Dalamud.Interface.Windowing;
|
||||||
|
|
@ -21,6 +22,8 @@ using Penumbra.Interop;
|
||||||
using Penumbra.UI;
|
using Penumbra.UI;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
|
using Penumbra.GameData;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.Interop.Loader;
|
using Penumbra.Interop.Loader;
|
||||||
using Penumbra.Interop.Resolver;
|
using Penumbra.Interop.Resolver;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
|
@ -55,6 +58,8 @@ public class Penumbra : IDalamudPlugin
|
||||||
public static TempModManager TempMods { get; private set; } = null!;
|
public static TempModManager TempMods { get; private set; } = null!;
|
||||||
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
public static ResourceLoader ResourceLoader { get; private set; } = null!;
|
||||||
public static FrameworkManager Framework { get; private set; } = null!;
|
public static FrameworkManager Framework { get; private set; } = null!;
|
||||||
|
public static ActorManager Actors { get; private set; } = null!;
|
||||||
|
|
||||||
public static readonly List< Exception > ImcExceptions = new();
|
public static readonly List< Exception > ImcExceptions = new();
|
||||||
|
|
||||||
public readonly ResourceLogger ResourceLogger;
|
public readonly ResourceLogger ResourceLogger;
|
||||||
|
|
@ -98,6 +103,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
ModFileSystem = ModFileSystem.Load();
|
ModFileSystem = ModFileSystem.Load();
|
||||||
ObjectReloader = new ObjectReloader();
|
ObjectReloader = new ObjectReloader();
|
||||||
PathResolver = new PathResolver( ResourceLoader );
|
PathResolver = new PathResolver( ResourceLoader );
|
||||||
|
Actors = new ActorManager( Dalamud.Objects, Dalamud.ClientState, Dalamud.GameData, u => ( short )PathResolver.CutsceneActor( u ) );
|
||||||
|
|
||||||
Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
|
Dalamud.Commands.AddHandler( CommandName, new CommandInfo( OnCommand )
|
||||||
{
|
{
|
||||||
|
|
@ -144,7 +150,7 @@ public class Penumbra : IDalamudPlugin
|
||||||
{
|
{
|
||||||
Log.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." );
|
Log.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." );
|
||||||
}
|
}
|
||||||
|
|
||||||
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
||||||
|
|
||||||
OtterTex.NativeDll.Initialize( Dalamud.PluginInterface.AssemblyLocation.DirectoryName );
|
OtterTex.NativeDll.Initialize( Dalamud.PluginInterface.AssemblyLocation.DirectoryName );
|
||||||
|
|
@ -283,6 +289,8 @@ public class Penumbra : IDalamudPlugin
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
Dalamud.PluginInterface.RelinquishData( "test1" );
|
||||||
|
Framework?.Dispose();
|
||||||
ShutdownWebServer();
|
ShutdownWebServer();
|
||||||
DisposeInterface();
|
DisposeInterface();
|
||||||
IpcProviders?.Dispose();
|
IpcProviders?.Dispose();
|
||||||
|
|
@ -290,7 +298,6 @@ public class Penumbra : IDalamudPlugin
|
||||||
ObjectReloader?.Dispose();
|
ObjectReloader?.Dispose();
|
||||||
ModFileSystem?.Dispose();
|
ModFileSystem?.Dispose();
|
||||||
CollectionManager?.Dispose();
|
CollectionManager?.Dispose();
|
||||||
Framework?.Dispose();
|
|
||||||
|
|
||||||
Dalamud.Commands.RemoveHandler( CommandName );
|
Dalamud.Commands.RemoveHandler( CommandName );
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
using Functions = Penumbra.GameData.Util.Functions;
|
using Penumbra.String.Classes;
|
||||||
|
using Penumbra.String.Functions;
|
||||||
|
|
||||||
namespace Penumbra.UI.Classes;
|
namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
|
|
@ -459,14 +459,14 @@ public partial class ModEditWindow
|
||||||
ref var rows = ref file.ColorSets[ colorSetIdx ].Rows;
|
ref var rows = ref file.ColorSets[ colorSetIdx ].Rows;
|
||||||
fixed( void* ptr = data, output = &rows )
|
fixed( void* ptr = data, output = &rows )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( output, ptr, Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() );
|
MemoryUtility.MemCpyUnchecked( output, ptr, Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() );
|
||||||
if( data.Length >= Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() + Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >()
|
if( data.Length >= Marshal.SizeOf< MtrlFile.ColorSet.RowArray >() + Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >()
|
||||||
&& file.ColorDyeSets.Length > colorSetIdx )
|
&& file.ColorDyeSets.Length > colorSetIdx )
|
||||||
{
|
{
|
||||||
ref var dyeRows = ref file.ColorDyeSets[ colorSetIdx ].Rows;
|
ref var dyeRows = ref file.ColorDyeSets[ colorSetIdx ].Rows;
|
||||||
fixed( void* output2 = &dyeRows )
|
fixed( void* output2 = &dyeRows )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( output2, ( byte* )ptr + Marshal.SizeOf< MtrlFile.ColorSet.RowArray >(), Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >() );
|
MemoryUtility.MemCpyUnchecked( output2, ( byte* )ptr + Marshal.SizeOf< MtrlFile.ColorSet.RowArray >(), Marshal.SizeOf< MtrlFile.ColorDyeSet.RowArray >() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -489,8 +489,8 @@ public partial class ModEditWindow
|
||||||
var data = new byte[MtrlFile.ColorSet.Row.Size + 2];
|
var data = new byte[MtrlFile.ColorSet.Row.Size + 2];
|
||||||
fixed( byte* ptr = data )
|
fixed( byte* ptr = data )
|
||||||
{
|
{
|
||||||
Functions.MemCpyUnchecked( ptr, &row, MtrlFile.ColorSet.Row.Size );
|
MemoryUtility.MemCpyUnchecked( ptr, &row, MtrlFile.ColorSet.Row.Size );
|
||||||
Functions.MemCpyUnchecked( ptr + MtrlFile.ColorSet.Row.Size, &dye, 2 );
|
MemoryUtility.MemCpyUnchecked( ptr + MtrlFile.ColorSet.Row.Size, &dye, 2 );
|
||||||
}
|
}
|
||||||
|
|
||||||
var text = Convert.ToBase64String( data );
|
var text = Convert.ToBase64String( data );
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,8 @@ using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Util;
|
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI.Classes;
|
namespace Penumbra.UI.Classes;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ using Dalamud.Interface.Windowing;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Files;
|
using Penumbra.GameData.Files;
|
||||||
using Penumbra.Import.Textures;
|
using Penumbra.Import.Textures;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.Util;
|
using Penumbra.Util;
|
||||||
using static Penumbra.Mods.Mod;
|
using static Penumbra.Mods.Mod;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
SubscribeRightClickFolder( InheritDescendants, 15 );
|
SubscribeRightClickFolder( InheritDescendants, 15 );
|
||||||
SubscribeRightClickFolder( OwnDescendants, 15 );
|
SubscribeRightClickFolder( OwnDescendants, 15 );
|
||||||
SubscribeRightClickFolder( SetDefaultImportFolder, 100 );
|
SubscribeRightClickFolder( SetDefaultImportFolder, 100 );
|
||||||
|
SubscribeRightClickLeaf( ToggleLeafFavorite, 0 );
|
||||||
SubscribeRightClickMain( ClearDefaultImportFolder, 100 );
|
SubscribeRightClickMain( ClearDefaultImportFolder, 100 );
|
||||||
AddButton( AddNewModButton, 0 );
|
AddButton( AddNewModButton, 0 );
|
||||||
AddButton( AddImportModButton, 1 );
|
AddButton( AddImportModButton, 1 );
|
||||||
|
|
@ -117,9 +118,10 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
|
|
||||||
protected override void DrawLeafName( FileSystem< Mod >.Leaf leaf, in ModState state, bool selected )
|
protected override void DrawLeafName( FileSystem< Mod >.Leaf leaf, in ModState state, bool selected )
|
||||||
{
|
{
|
||||||
var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
|
var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
|
||||||
using var c = ImRaii.PushColor( ImGuiCol.Text, state.Color.Value() );
|
using var c = ImRaii.PushColor( ImGuiCol.Text, state.Color.Value() )
|
||||||
using var id = ImRaii.PushId( leaf.Value.Index );
|
.Push( ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite );
|
||||||
|
using var id = ImRaii.PushId( leaf.Value.Index );
|
||||||
ImRaii.TreeNode( leaf.Value.Name, flags ).Dispose();
|
ImRaii.TreeNode( leaf.Value.Name, flags ).Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,6 +159,14 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void ToggleLeafFavorite( FileSystem< Mod >.Leaf mod )
|
||||||
|
{
|
||||||
|
if( ImGui.MenuItem( mod.Value.Favorite ? "Remove Favorite" : "Mark as Favorite" ) )
|
||||||
|
{
|
||||||
|
Penumbra.ModManager.ChangeModFavorite( mod.Value.Index, !mod.Value.Favorite );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void SetDefaultImportFolder( ModFileSystem.Folder folder )
|
private static void SetDefaultImportFolder( ModFileSystem.Folder folder )
|
||||||
{
|
{
|
||||||
if( ImGui.MenuItem( "Set As Default Import Folder" ) )
|
if( ImGui.MenuItem( "Set As Default Import Folder" ) )
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Interop.Loader;
|
using Penumbra.Interop.Loader;
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
using CharacterUtility = Penumbra.Interop.CharacterUtility;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
@ -56,6 +56,8 @@ public partial class ConfigWindow
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawPathResolverDebug();
|
DrawPathResolverDebug();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
|
DrawActorsDebug();
|
||||||
|
ImGui.NewLine();
|
||||||
DrawDebugCharacterUtility();
|
DrawDebugCharacterUtility();
|
||||||
ImGui.NewLine();
|
ImGui.NewLine();
|
||||||
DrawDebugTabMetaLists();
|
DrawDebugTabMetaLists();
|
||||||
|
|
@ -148,6 +150,31 @@ public partial class ConfigWindow
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static unsafe void DrawActorsDebug()
|
||||||
|
{
|
||||||
|
if( !ImGui.CollapsingHeader( "Actors" ) )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var table = ImRaii.Table( "##actors", 4, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingFixedFit,
|
||||||
|
-Vector2.UnitX );
|
||||||
|
if( !table )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach( var obj in Dalamud.Objects )
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn( $"{( ( GameObject* )obj.Address )->ObjectIndex}" );
|
||||||
|
ImGuiUtil.DrawTableColumn( $"0x{obj.Address:X}" );
|
||||||
|
var identifier = Penumbra.Actors.FromObject( obj );
|
||||||
|
ImGuiUtil.DrawTableColumn( Penumbra.Actors.ToString( identifier ) );
|
||||||
|
ImGuiUtil.DrawTableColumn( identifier.DataId.ToString() );
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Draw information about which draw objects correspond to which game objects
|
// Draw information about which draw objects correspond to which game objects
|
||||||
// and which paths are due to be loaded by which collection.
|
// and which paths are due to be loaded by which collection.
|
||||||
private unsafe void DrawPathResolverDebug()
|
private unsafe void DrawPathResolverDebug()
|
||||||
|
|
@ -173,7 +200,7 @@ public partial class ConfigWindow
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx );
|
var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx );
|
||||||
var (address, name) =
|
var (address, name) =
|
||||||
obj != null ? ( $"0x{( ulong )obj:X}", new Utf8String( obj->Name ).ToString() ) : ( "NULL", "NULL" );
|
obj != null ? ( $"0x{( ulong )obj:X}", new ByteString( obj->Name ).ToString() ) : ( "NULL", "NULL" );
|
||||||
ImGui.TextUnformatted( address );
|
ImGui.TextUnformatted( address );
|
||||||
ImGui.TableNextColumn();
|
ImGui.TableNextColumn();
|
||||||
ImGui.TextUnformatted( name );
|
ImGui.TextUnformatted( name );
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,16 @@ using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.Api.Enums;
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.Collections;
|
using Penumbra.Collections;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
using Penumbra.Interop.Structs;
|
using Penumbra.Interop.Structs;
|
||||||
|
using Penumbra.String;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
public partial class ConfigWindow
|
public partial class ConfigWindow
|
||||||
{
|
{
|
||||||
// Draw text given by a Utf8String.
|
// Draw text given by a ByteString.
|
||||||
internal static unsafe void Text( Utf8String s )
|
internal static unsafe void Text( ByteString s )
|
||||||
=> ImGuiNative.igTextUnformatted( s.Path, s.Path + s.Length );
|
=> ImGuiNative.igTextUnformatted( s.Path, s.Path + s.Length );
|
||||||
|
|
||||||
// Draw text given by a byte pointer.
|
// Draw text given by a byte pointer.
|
||||||
|
|
@ -30,8 +29,8 @@ public partial class ConfigWindow
|
||||||
private static unsafe void Text( ResourceHandle* resource )
|
private static unsafe void Text( ResourceHandle* resource )
|
||||||
=> Text( resource->FileName(), resource->FileNameLength );
|
=> Text( resource->FileName(), resource->FileNameLength );
|
||||||
|
|
||||||
// Draw a Utf8String as a selectable.
|
// Draw a ByteString as a selectable.
|
||||||
internal static unsafe bool Selectable( Utf8String s, bool selected )
|
internal static unsafe bool Selectable( ByteString s, bool selected )
|
||||||
{
|
{
|
||||||
var tmp = ( byte )( selected ? 1 : 0 );
|
var tmp = ( byte )( selected ? 1 : 0 );
|
||||||
return ImGuiNative.igSelectable_Bool( s.Path, tmp, ImGuiSelectableFlags.None, Vector2.Zero ) != 0;
|
return ImGuiNative.igSelectable_Bool( s.Path, tmp, ImGuiSelectableFlags.None, Vector2.Zero ) != 0;
|
||||||
|
|
@ -77,8 +76,8 @@ public partial class ConfigWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
// A selectable that copies its text to clipboard on selection and provides a on-hover tooltip about that,
|
// A selectable that copies its text to clipboard on selection and provides a on-hover tooltip about that,
|
||||||
// using an Utf8String.
|
// using an ByteString.
|
||||||
private static unsafe void CopyOnClickSelectable( Utf8String text )
|
private static unsafe void CopyOnClickSelectable( ByteString text )
|
||||||
{
|
{
|
||||||
if( ImGuiNative.igSelectable_Bool( text.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ) != 0 )
|
if( ImGuiNative.igSelectable_Bool( text.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ) != 0 )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,10 @@ using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Meta.Manipulations;
|
using Penumbra.Meta.Manipulations;
|
||||||
using Penumbra.Mods;
|
using Penumbra.Mods;
|
||||||
|
using Penumbra.String;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
@ -32,11 +33,11 @@ public partial class ConfigWindow
|
||||||
private Tabs _availableTabs = 0;
|
private Tabs _availableTabs = 0;
|
||||||
|
|
||||||
// Required to use tabs that can not be closed but have a flag to set them open.
|
// Required to use tabs that can not be closed but have a flag to set them open.
|
||||||
private static readonly Utf8String ConflictTabHeader = Utf8String.FromStringUnsafe( "Conflicts", false );
|
private static readonly ByteString ConflictTabHeader = ByteString.FromSpanUnsafe( "Conflicts"u8, true, false, true );
|
||||||
private static readonly Utf8String DescriptionTabHeader = Utf8String.FromStringUnsafe( "Description", false );
|
private static readonly ByteString DescriptionTabHeader = ByteString.FromSpanUnsafe( "Description"u8, true, false, true );
|
||||||
private static readonly Utf8String SettingsTabHeader = Utf8String.FromStringUnsafe( "Settings", false );
|
private static readonly ByteString SettingsTabHeader = ByteString.FromSpanUnsafe( "Settings"u8, true, false, true );
|
||||||
private static readonly Utf8String ChangedItemsTabHeader = Utf8String.FromStringUnsafe( "Changed Items", false );
|
private static readonly ByteString ChangedItemsTabHeader = ByteString.FromSpanUnsafe( "Changed Items"u8, true, false, true );
|
||||||
private static readonly Utf8String EditModTabHeader = Utf8String.FromStringUnsafe( "Edit Mod", false );
|
private static readonly ByteString EditModTabHeader = ByteString.FromSpanUnsafe( "Edit Mod"u8, true, false, true );
|
||||||
|
|
||||||
private readonly TagButtons _modTags = new();
|
private readonly TagButtons _modTags = new();
|
||||||
|
|
||||||
|
|
@ -147,6 +148,7 @@ public partial class ConfigWindow
|
||||||
{
|
{
|
||||||
Penumbra.ModManager.ChangeLocalTag( _mod.Index, tagIdx, editedTag );
|
Penumbra.ModManager.ChangeLocalTag( _mod.Index, tagIdx, editedTag );
|
||||||
}
|
}
|
||||||
|
|
||||||
if( _mod.ModTags.Count > 0 )
|
if( _mod.ModTags.Count > 0 )
|
||||||
{
|
{
|
||||||
_modTags.Draw( "Mod Tags: ", "Tags assigned by the mod creator and saved with the mod data. To edit these, look at Edit Mod.", _mod.ModTags, out var _, false,
|
_modTags.Draw( "Mod Tags: ", "Tags assigned by the mod creator and saved with the mod data. To edit these, look at Edit Mod.", _mod.ModTags, out var _, false,
|
||||||
|
|
@ -226,7 +228,7 @@ public partial class ConfigWindow
|
||||||
|
|
||||||
|
|
||||||
// Draw a tab by given name if it is available, and deal with changing the preferred tab.
|
// Draw a tab by given name if it is available, and deal with changing the preferred tab.
|
||||||
private ImRaii.IEndObject DrawTab( Utf8String name, Tabs flag )
|
private ImRaii.IEndObject DrawTab( ByteString name, Tabs flag )
|
||||||
{
|
{
|
||||||
if( !_availableTabs.HasFlag( flag ) )
|
if( !_availableTabs.HasFlag( flag ) )
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ using FFXIVClientStructs.STD;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Interop.Loader;
|
using Penumbra.Interop.Loader;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ using Dalamud.Interface;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
using Penumbra.GameData.ByteString;
|
|
||||||
using Penumbra.Interop;
|
using Penumbra.Interop;
|
||||||
|
using Penumbra.String.Classes;
|
||||||
using Penumbra.UI.Classes;
|
using Penumbra.UI.Classes;
|
||||||
|
|
||||||
namespace Penumbra.UI;
|
namespace Penumbra.UI;
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,12 @@
|
||||||
"penumbra.gamedata": {
|
"penumbra.gamedata": {
|
||||||
"type": "Project",
|
"type": "Project",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Penumbra.Api": "[1.0.0, )"
|
"Penumbra.Api": "[1.0.0, )",
|
||||||
|
"Penumbra.String": "[1.0.0, )"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"penumbra.string": {
|
||||||
|
"type": "Project"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue