diff --git a/.gitmodules b/.gitmodules
index b5eb77bb..94049366 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -6,3 +6,7 @@
path = Penumbra.Api
url = git@github.com:Ottermandias/Penumbra.Api.git
branch = main
+[submodule "Penumbra.String"]
+ path = Penumbra.String
+ url = git@github.com:Ottermandias/Penumbra.String.git
+ branch = main
diff --git a/Penumbra.GameData/Actors/ActorIdentifier.cs b/Penumbra.GameData/Actors/ActorIdentifier.cs
new file mode 100644
index 00000000..5651382e
--- /dev/null
+++ b/Penumbra.GameData/Actors/ActorIdentifier.cs
@@ -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,
+ };
+ }
+}
\ No newline at end of file
diff --git a/Penumbra.GameData/Actors/ActorManager.cs b/Penumbra.GameData/Actors/ActorManager.cs
new file mode 100644
index 00000000..f3a1936e
--- /dev/null
+++ b/Penumbra.GameData/Actors/ActorManager.cs
@@ -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 );
+ }
+
+
+ /// Checks SE naming rules.
+ 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 ] );
+ }
+
+ /// Checks if the world is a valid public world or ushort.MaxValue (any world).
+ private bool VerifyWorld( ushort worldId )
+ => Worlds.ContainsKey( worldId );
+
+ /// Verify that the enum value is a specific actor and return the name if it is.
+ private static bool VerifySpecial( SpecialActor actor )
+ => actor is >= SpecialActor.CharacterScreen and <= SpecialActor.Portrait;
+
+ /// Verify that the object index is a valid index for an NPC.
+ private static bool VerifyIndex( ushort index )
+ {
+ return index switch
+ {
+ < 200 => index % 2 == 0,
+ > ( ushort )SpecialActor.Portrait => index < 426,
+ _ => false,
+ };
+ }
+
+ /// Verify that the object kind is a valid owned object, and the corresponding data Id.
+ 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,
+ };
+}
\ No newline at end of file
diff --git a/Penumbra.GameData/Actors/IdentifierType.cs b/Penumbra.GameData/Actors/IdentifierType.cs
new file mode 100644
index 00000000..a582aa14
--- /dev/null
+++ b/Penumbra.GameData/Actors/IdentifierType.cs
@@ -0,0 +1,10 @@
+namespace Penumbra.GameData.Actors;
+
+public enum IdentifierType : byte
+{
+ Invalid,
+ Player,
+ Owned,
+ Special,
+ Npc,
+};
\ No newline at end of file
diff --git a/Penumbra.GameData/Actors/SpecialActor.cs b/Penumbra.GameData/Actors/SpecialActor.cs
new file mode 100644
index 00000000..5319c747
--- /dev/null
+++ b/Penumbra.GameData/Actors/SpecialActor.cs
@@ -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,
+}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/ByteStringFunctions.Case.cs b/Penumbra.GameData/ByteString/ByteStringFunctions.Case.cs
deleted file mode 100644
index aeda4bfd..00000000
--- a/Penumbra.GameData/ByteString/ByteStringFunctions.Case.cs
+++ /dev/null
@@ -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;
- }
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/ByteStringFunctions.Comparison.cs b/Penumbra.GameData/ByteString/ByteStringFunctions.Comparison.cs
deleted file mode 100644
index 3e7382c9..00000000
--- a/Penumbra.GameData/ByteString/ByteStringFunctions.Comparison.cs
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/ByteStringFunctions.Construction.cs b/Penumbra.GameData/ByteString/ByteStringFunctions.Construction.cs
deleted file mode 100644
index 18cc3a81..00000000
--- a/Penumbra.GameData/ByteString/ByteStringFunctions.Construction.cs
+++ /dev/null
@@ -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" );
- }
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/ByteStringFunctions.Manipulation.cs b/Penumbra.GameData/ByteString/ByteStringFunctions.Manipulation.cs
deleted file mode 100644
index 7d21593a..00000000
--- a/Penumbra.GameData/ByteString/ByteStringFunctions.Manipulation.cs
+++ /dev/null
@@ -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;
- }
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/FullPath.cs b/Penumbra.GameData/ByteString/FullPath.cs
deleted file mode 100644
index 6d3cc0bd..00000000
--- a/Penumbra.GameData/ByteString/FullPath.cs
+++ /dev/null
@@ -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() );
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/Utf8GamePath.cs b/Penumbra.GameData/ByteString/Utf8GamePath.cs
deleted file mode 100644
index 79386002..00000000
--- a/Penumbra.GameData/ByteString/Utf8GamePath.cs
+++ /dev/null
@@ -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() );
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/Utf8RelPath.cs b/Penumbra.GameData/ByteString/Utf8RelPath.cs
deleted file mode 100644
index cef27b6f..00000000
--- a/Penumbra.GameData/ByteString/Utf8RelPath.cs
+++ /dev/null
@@ -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() );
- }
- }
- }
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/Utf8String.Access.cs b/Penumbra.GameData/ByteString/Utf8String.Access.cs
deleted file mode 100644
index a74dd672..00000000
--- a/Penumbra.GameData/ByteString/Utf8String.Access.cs
+++ /dev/null
@@ -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;
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/Utf8String.Comparison.cs b/Penumbra.GameData/ByteString/Utf8String.Comparison.cs
deleted file mode 100644
index 6d96dce5..00000000
--- a/Penumbra.GameData/ByteString/Utf8String.Comparison.cs
+++ /dev/null
@@ -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;
- }
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/Utf8String.Construction.cs b/Penumbra.GameData/ByteString/Utf8String.Construction.cs
deleted file mode 100644
index 1e6ab325..00000000
--- a/Penumbra.GameData/ByteString/Utf8String.Construction.cs
+++ /dev/null
@@ -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,
- };
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs b/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs
deleted file mode 100644
index c4332a1d..00000000
--- a/Penumbra.GameData/ByteString/Utf8String.Manipulation.cs
+++ /dev/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,
- };
- }
-}
\ No newline at end of file
diff --git a/Penumbra.GameData/Enums/ResourceType.cs b/Penumbra.GameData/Enums/ResourceType.cs
index dedaf3c2..42783c97 100644
--- a/Penumbra.GameData/Enums/ResourceType.cs
+++ b/Penumbra.GameData/Enums/ResourceType.cs
@@ -1,6 +1,7 @@
using System;
using System.IO;
-using Penumbra.GameData.ByteString;
+using Penumbra.String;
+using Penumbra.String.Functions;
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 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
{
diff --git a/Penumbra.GameData/Penumbra.GameData.csproj b/Penumbra.GameData/Penumbra.GameData.csproj
index cb51d6dc..0aaf9c89 100644
--- a/Penumbra.GameData/Penumbra.GameData.csproj
+++ b/Penumbra.GameData/Penumbra.GameData.csproj
@@ -36,6 +36,7 @@
+
@@ -51,6 +52,10 @@
$(DalamudLibPath)Lumina.Excel.dll
False
+
+ $(DalamudLibPath)FFXIVClientStructs.dll
+ False
+
$(DalamudLibPath)Newtonsoft.Json.dll
False
diff --git a/Penumbra.GameData/Structs/CharacterEquip.cs b/Penumbra.GameData/Structs/CharacterEquip.cs
index a8744fff..58a3b317 100644
--- a/Penumbra.GameData/Structs/CharacterEquip.cs
+++ b/Penumbra.GameData/Structs/CharacterEquip.cs
@@ -1,6 +1,7 @@
using System;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
+using Penumbra.String.Functions;
namespace Penumbra.GameData.Structs;
@@ -107,9 +108,9 @@ public readonly unsafe struct CharacterEquip
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 )
- => Functions.MemCmpUnchecked( ( void* )_armor, ( void* )other._armor, sizeof( CharacterArmor ) * 10 ) == 0;
+ => MemoryUtility.MemCmpUnchecked( ( void* )_armor, ( void* )other._armor, sizeof( CharacterArmor ) * 10 ) == 0;
}
\ No newline at end of file
diff --git a/Penumbra.GameData/Structs/CustomizeData.cs b/Penumbra.GameData/Structs/CustomizeData.cs
index 3f184bb5..1524ae11 100644
--- a/Penumbra.GameData/Structs/CustomizeData.cs
+++ b/Penumbra.GameData/Structs/CustomizeData.cs
@@ -1,5 +1,5 @@
using System;
-using Penumbra.GameData.Util;
+using Penumbra.String.Functions;
namespace Penumbra.GameData.Structs;
@@ -13,7 +13,7 @@ public unsafe struct CustomizeData : IEquatable< CustomizeData >
{
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 )
{
- Functions.MemCpyUnchecked( target, ptr, Size );
+ MemoryUtility.MemCpyUnchecked( target, ptr, Size );
}
}
@@ -36,12 +36,12 @@ public unsafe struct CustomizeData : IEquatable< CustomizeData >
{
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 )
- => Functions.MemCmpUnchecked( lhs, rhs, Size ) == 0;
+ => MemoryUtility.MemCmpUnchecked( lhs, rhs, Size ) == 0;
public override bool Equals( object? obj )
=> obj is CustomizeData other && Equals( other );
diff --git a/Penumbra.GameData/Util/Functions.cs b/Penumbra.GameData/Util/Functions.cs
deleted file mode 100644
index 81e3dc28..00000000
--- a/Penumbra.GameData/Util/Functions.cs
+++ /dev/null
@@ -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 );
-}
\ No newline at end of file
diff --git a/Penumbra.String b/Penumbra.String
new file mode 160000
index 00000000..f41af0fb
--- /dev/null
+++ b/Penumbra.String
@@ -0,0 +1 @@
+Subproject commit f41af0fb88626f1579d3c4370b32b901f3c4d3c2
diff --git a/Penumbra.sln b/Penumbra.sln
index 33e5a03d..5c11aaea 100644
--- a/Penumbra.sln
+++ b/Penumbra.sln
@@ -16,6 +16,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OtterGui", "OtterGui\OtterG
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{1FE4D8DF-B56A-464F-B39E-CDC0ED4167D4}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.String", "Penumbra.String\Penumbra.String.csproj", "{5549BAFD-6357-4B1A-800C-75AC36E5B76D}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
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}.Release|Any CPU.ActiveCfg = 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
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/Penumbra/Api/IpcTester.cs b/Penumbra/Api/IpcTester.cs
index 7ccad1ce..d6b0b51c 100644
--- a/Penumbra/Api/IpcTester.cs
+++ b/Penumbra/Api/IpcTester.cs
@@ -3,7 +3,6 @@ using Dalamud.Plugin;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
-using Penumbra.GameData.ByteString;
using Penumbra.Mods;
using System;
using System.Collections.Generic;
@@ -12,6 +11,8 @@ using System.Linq;
using System.Numerics;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
+using Penumbra.String;
+using Penumbra.String.Classes;
namespace Penumbra.Api;
@@ -536,7 +537,7 @@ public class IpcTester : IDisposable
private unsafe void UpdateLastCreated( IntPtr gameObject, string _, IntPtr _2, IntPtr _3, IntPtr _4 )
{
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
- _lastCreatedGameObjectName = new Utf8String( obj->GetName() ).ToString();
+ _lastCreatedGameObjectName = new ByteString( obj->GetName() ).ToString();
_lastCreatedGameObjectTime = DateTimeOffset.Now;
_lastCreatedDrawObject = IntPtr.Zero;
}
@@ -544,7 +545,7 @@ public class IpcTester : IDisposable
private unsafe void UpdateLastCreated2( IntPtr gameObject, string _, IntPtr drawObject )
{
var obj = ( FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* )gameObject;
- _lastCreatedGameObjectName = new Utf8String( obj->GetName() ).ToString();
+ _lastCreatedGameObjectName = new ByteString( obj->GetName() ).ToString();
_lastCreatedGameObjectTime = DateTimeOffset.Now;
_lastCreatedDrawObject = drawObject;
}
@@ -552,7 +553,7 @@ public class IpcTester : IDisposable
private unsafe void UpdateGameObjectResourcePath( IntPtr gameObject, string gamePath, string fullPath )
{
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;
_lastResolvedFullPath = fullPath;
_lastResolvedGamePathTime = DateTimeOffset.Now;
diff --git a/Penumbra/Api/PenumbraApi.cs b/Penumbra/Api/PenumbraApi.cs
index 0484f986..8cc799f1 100644
--- a/Penumbra/Api/PenumbraApi.cs
+++ b/Penumbra/Api/PenumbraApi.cs
@@ -3,7 +3,6 @@ using Lumina.Data;
using Newtonsoft.Json;
using OtterGui;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.Interop.Resolver;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations;
@@ -15,6 +14,7 @@ using System.IO;
using System.Linq;
using System.Reflection;
using Penumbra.Api.Enums;
+using Penumbra.String.Classes;
namespace Penumbra.Api;
diff --git a/Penumbra/Api/TempModManager.cs b/Penumbra/Api/TempModManager.cs
index 263b892f..bb41f6f1 100644
--- a/Penumbra/Api/TempModManager.cs
+++ b/Penumbra/Api/TempModManager.cs
@@ -1,11 +1,11 @@
using OtterGui;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using Penumbra.String.Classes;
namespace Penumbra.Api;
diff --git a/Penumbra/Collections/CollectionManager.Active.cs b/Penumbra/Collections/CollectionManager.Active.cs
index 75bfcef5..1e211076 100644
--- a/Penumbra/Collections/CollectionManager.Active.cs
+++ b/Penumbra/Collections/CollectionManager.Active.cs
@@ -143,8 +143,8 @@ public partial class ModCollection
return false;
}
- _specialCollections[ ( int )collectionType ] = Empty;
- CollectionChanged.Invoke( collectionType, null, Empty, null );
+ _specialCollections[ ( int )collectionType ] = Default;
+ CollectionChanged.Invoke( collectionType, null, Default, null );
return true;
}
@@ -172,8 +172,8 @@ public partial class ModCollection
return false;
}
- _characters[ characterName ] = Empty;
- CollectionChanged.Invoke( CollectionType.Character, null, Empty, characterName );
+ _characters[ characterName ] = Default;
+ CollectionChanged.Invoke( CollectionType.Character, null, Default, characterName );
return true;
}
diff --git a/Penumbra/Collections/ModCollection.Cache.Access.cs b/Penumbra/Collections/ModCollection.Cache.Access.cs
index 5b636966..a4d1619c 100644
--- a/Penumbra/Collections/ModCollection.Cache.Access.cs
+++ b/Penumbra/Collections/ModCollection.Cache.Access.cs
@@ -1,5 +1,4 @@
using OtterGui.Classes;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Meta.Manager;
using Penumbra.Mods;
@@ -8,8 +7,8 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading;
using Penumbra.Interop;
-using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
namespace Penumbra.Collections;
diff --git a/Penumbra/Collections/ModCollection.Cache.cs b/Penumbra/Collections/ModCollection.Cache.cs
index 2d2262cc..acf3b9b6 100644
--- a/Penumbra/Collections/ModCollection.Cache.cs
+++ b/Penumbra/Collections/ModCollection.Cache.cs
@@ -1,6 +1,5 @@
using OtterGui;
using OtterGui.Classes;
-using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manager;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
@@ -8,6 +7,8 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Penumbra.Api.Enums;
+using Penumbra.GameData.Util;
+using Penumbra.String.Classes;
namespace Penumbra.Collections;
@@ -240,7 +241,7 @@ public partial class ModCollection
if( addMetaChanges )
{
++_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();
MetaManipulations.SetFiles();
@@ -413,7 +414,7 @@ public partial class ModCollection
// Add the same conflict list to both conflict directions.
var conflictList = new List< object > { data };
_conflicts[ addedMod ] = addedConflicts.Append( new ModConflicts( existingMod, conflictList, existingPriority < addedPriority,
- existingPriority != addedPriority ) );
+ existingPriority != addedPriority ) );
_conflicts[ existingMod ] = existingConflicts.Append( new ModConflicts( addedMod, conflictList,
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,
// since they are per set instead of per item-slot/item/variant.
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 ) )
{
diff --git a/Penumbra/Import/Textures/Texture.cs b/Penumbra/Import/Textures/Texture.cs
index b3df03f0..c54218d5 100644
--- a/Penumbra/Import/Textures/Texture.cs
+++ b/Penumbra/Import/Textures/Texture.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.ImGuiFileDialog;
@@ -11,7 +10,7 @@ using Lumina.Data.Files;
using OtterGui;
using OtterGui.Raii;
using OtterTex;
-using Penumbra.GameData.ByteString;
+using Penumbra.String.Classes;
using Penumbra.UI.Classes;
using SixLabors.ImageSharp.PixelFormats;
using Image = SixLabors.ImageSharp.Image;
diff --git a/Penumbra/Interop/CharacterUtility.DecalReverter.cs b/Penumbra/Interop/CharacterUtility.DecalReverter.cs
index a439ac48..5aee657a 100644
--- a/Penumbra/Interop/CharacterUtility.DecalReverter.cs
+++ b/Penumbra/Interop/CharacterUtility.DecalReverter.cs
@@ -1,8 +1,8 @@
using System;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
+using Penumbra.String.Classes;
namespace Penumbra.Interop;
diff --git a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs
index f04f2930..ba2f2962 100644
--- a/Penumbra/Interop/Loader/ResourceLoader.Debug.cs
+++ b/Penumbra/Interop/Loader/ResourceLoader.Debug.cs
@@ -7,8 +7,9 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using FFXIVClientStructs.STD;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
+using Penumbra.String;
+using Penumbra.String.Classes;
namespace Penumbra.Interop.Loader;
@@ -19,7 +20,7 @@ public unsafe partial class ResourceLoader
private readonly Hook< ResourceHandleDecRef > _decRefHook;
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",
DetourName = nameof( ResourceHandleDestructorDetour ) )]
public static Hook< ResourceHandleDestructor >? ResourceHandleDestructorHook;
@@ -28,7 +29,7 @@ public unsafe partial class ResourceLoader
{
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 );
@@ -248,7 +249,7 @@ public unsafe partial class ResourceLoader
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
? $"[ResourceLoader] Loaded {path} from {( custom ? "local files" : "SqPack" )}"
: $"[ResourceLoader] Failed to load {path} from {( custom ? "local files" : "SqPack" )}." );
diff --git a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs
index a0ca377f..8e28d2ce 100644
--- a/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs
+++ b/Penumbra/Interop/Loader/ResourceLoader.Replacement.cs
@@ -7,9 +7,10 @@ using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
+using Penumbra.String;
+using Penumbra.String.Classes;
using FileMode = Penumbra.Interop.Structs.FileMode;
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;
- public ResourceHandle* ResolvePathSync( ResourceCategory category, ResourceType type, Utf8String path )
+ public ResourceHandle* ResolvePathSync( ResourceCategory category, ResourceType type, ByteString path )
{
var hash = path.Crc32;
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.
- private byte DefaultResourceLoad( Utf8String path, ResourceManager* resourceManager,
+ private byte DefaultResourceLoad( ByteString path, ResourceManager* resourceManager,
SeFileDescriptor* fileDescriptor, int priority, bool 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.
- private byte DefaultRootedResourceLoad( Utf8String gamePath, ResourceManager* resourceManager,
+ private byte DefaultRootedResourceLoad( ByteString gamePath, ResourceManager* resourceManager,
SeFileDescriptor* fileDescriptor, int priority, bool isSync )
{
// 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.
- internal byte DefaultLoadResource( Utf8String gamePath, ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority,
+ internal byte DefaultLoadResource( ByteString gamePath, ResourceManager* resourceManager, SeFileDescriptor* fileDescriptor, int priority,
bool isSync )
=> Utf8GamePath.IsRooted( gamePath )
? DefaultRootedResourceLoad( gamePath, resourceManager, fileDescriptor, priority, isSync )
@@ -262,7 +263,7 @@ public unsafe partial class ResourceLoader
_incRefHook.Dispose();
}
- private static int ComputeHash( Utf8String path, GetResourceParameters* pGetResParams )
+ private static int ComputeHash( ByteString path, GetResourceParameters* pGetResParams )
{
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:
// path/to/file.ext.hex_offset.hex_size
// ex) music/ex4/BGM_EX4_System_Title.scd.381adc.30000
- return Utf8String.Join(
+ return ByteString.Join(
( byte )'.',
path,
- Utf8String.FromStringUnsafe( pGetResParams->SegmentOffset.ToString( "x" ), true ),
- Utf8String.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true )
+ ByteString.FromStringUnsafe( pGetResParams->SegmentOffset.ToString( "x" ), true ),
+ ByteString.FromStringUnsafe( pGetResParams->SegmentLength.ToString( "x" ), true )
).Crc32;
}
diff --git a/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs b/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs
index 67454300..415b18f7 100644
--- a/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs
+++ b/Penumbra/Interop/Loader/ResourceLoader.TexMdl.cs
@@ -3,8 +3,8 @@ using System.Collections.Generic;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
+using Penumbra.String.Classes;
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.
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!;
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.
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!;
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 );
- [Signature( "E8 ?? ?? ?? ?? EB 02 B0 F1", DetourName = nameof(LoadMdlFileExternDetour) )]
+ [Signature( "E8 ?? ?? ?? ?? EB 02 B0 F1", DetourName = nameof( LoadMdlFileExternDetour ) )]
public Hook< LoadMdlFileExternPrototype > LoadMdlFileExternHook = null!;
private byte LoadMdlFileExternDetour( ResourceHandle* resourceHandle, IntPtr unk1, bool unk2, IntPtr ptr )
diff --git a/Penumbra/Interop/Loader/ResourceLoader.cs b/Penumbra/Interop/Loader/ResourceLoader.cs
index 44d55214..de91fb2a 100644
--- a/Penumbra/Interop/Loader/ResourceLoader.cs
+++ b/Penumbra/Interop/Loader/ResourceLoader.cs
@@ -3,9 +3,10 @@ using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
+using Penumbra.String;
+using Penumbra.String.Classes;
namespace Penumbra.Interop.Loader;
@@ -127,7 +128,7 @@ public unsafe partial class ResourceLoader : IDisposable
// 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)
// 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;
// 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 "|".
// 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 );
public event ResourceLoadCustomizationDelegate? ResourceLoadCustomization;
diff --git a/Penumbra/Interop/Loader/ResourceLogger.cs b/Penumbra/Interop/Loader/ResourceLogger.cs
index 25d8ea59..dceaad36 100644
--- a/Penumbra/Interop/Loader/ResourceLogger.cs
+++ b/Penumbra/Interop/Loader/ResourceLogger.cs
@@ -1,6 +1,7 @@
using System;
using System.Text.RegularExpressions;
-using Penumbra.GameData.ByteString;
+using Penumbra.String;
+using Penumbra.String.Classes;
namespace Penumbra.Interop.Loader;
@@ -84,7 +85,7 @@ public class ResourceLogger : IDisposable
// 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.
- private string? Match( Utf8String data )
+ private string? Match( ByteString data )
{
var s = data.ToString();
return Filter.Length == 0 || ( _filterRegex?.IsMatch( s ) ?? s.Contains( Filter, StringComparison.OrdinalIgnoreCase ) )
diff --git a/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs
index 9a38bf56..3744bb67 100644
--- a/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs
+++ b/Penumbra/Interop/Resolver/PathResolver.AnimationState.cs
@@ -2,8 +2,8 @@ using System;
using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
+using Penumbra.String.Classes;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace Penumbra.Interop.Resolver;
diff --git a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs
index aa6e7ed4..3742a51e 100644
--- a/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs
+++ b/Penumbra/Interop/Resolver/PathResolver.DrawObjectState.cs
@@ -8,8 +8,8 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.Api;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using OtterGui.Classes;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
+using Penumbra.String.Classes;
namespace Penumbra.Interop.Resolver;
@@ -57,7 +57,7 @@ public unsafe partial class PathResolver
{
if( type == ResourceType.Tex
&& 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;
return true;
diff --git a/Penumbra/Interop/Resolver/PathResolver.Identification.cs b/Penumbra/Interop/Resolver/PathResolver.Identification.cs
index ef09c616..ec975dfa 100644
--- a/Penumbra/Interop/Resolver/PathResolver.Identification.cs
+++ b/Penumbra/Interop/Resolver/PathResolver.Identification.cs
@@ -7,8 +7,8 @@ using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using FFXIVClientStructs.FFXIV.Component.GUI;
using Lumina.Excel.GeneratedSheets;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
+using Penumbra.String;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
using ObjectKind = Dalamud.Game.ClientState.Objects.Enums.ObjectKind;
@@ -66,7 +66,7 @@ public unsafe partial class PathResolver
}
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.
@@ -130,7 +130,7 @@ public unsafe partial class PathResolver
if( owner != null )
{
- return new Utf8String( owner->Name ).ToString();
+ return new ByteString( owner->Name ).ToString();
}
return null;
@@ -169,7 +169,7 @@ public unsafe partial class PathResolver
if( Penumbra.Config.PreferNamedCollectionsOverOwners )
{
// 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
&& CollectionByActorName( actorName, out var actorCollection ) )
{
@@ -189,7 +189,7 @@ public unsafe partial class PathResolver
>= CutsceneCharacters.CutsceneStartIdx and < CutsceneCharacters.CutsceneEndIdx => GetCutsceneName( gameObject ),
_ => 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.
var collection = CollectionByActorName( actualName, out var c )
diff --git a/Penumbra/Interop/Resolver/PathResolver.Material.cs b/Penumbra/Interop/Resolver/PathResolver.Material.cs
index baf333f2..90893c20 100644
--- a/Penumbra/Interop/Resolver/PathResolver.Material.cs
+++ b/Penumbra/Interop/Resolver/PathResolver.Material.cs
@@ -4,9 +4,10 @@ using Dalamud.Hooking;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
+using Penumbra.String;
+using Penumbra.String.Classes;
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
// 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 )
{
ret = 0;
@@ -149,7 +150,7 @@ public unsafe partial class PathResolver
}
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;
}
}
diff --git a/Penumbra/Interop/Resolver/PathResolver.PathState.cs b/Penumbra/Interop/Resolver/PathResolver.PathState.cs
index a9bd1ee0..42c458c3 100644
--- a/Penumbra/Interop/Resolver/PathResolver.PathState.cs
+++ b/Penumbra/Interop/Resolver/PathResolver.PathState.cs
@@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Dalamud.Utility.Signatures;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
+using Penumbra.String;
namespace Penumbra.Interop.Resolver;
@@ -31,7 +31,7 @@ public unsafe partial class PathResolver
private readonly ResolverHooks _monster;
// 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 )
{
@@ -69,13 +69,13 @@ public unsafe partial class PathResolver
public int Count
=> _pathCollections.Count;
- public IEnumerable< KeyValuePair< Utf8String, ResolveData > > Paths
+ public IEnumerable< KeyValuePair< ByteString, ResolveData > > Paths
=> _pathCollections;
- public bool TryGetValue( Utf8String path, out ResolveData collection )
+ public bool TryGetValue( ByteString path, out ResolveData 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 );
// Just add or remove the resolved path.
@@ -87,13 +87,13 @@ public unsafe partial class PathResolver
return path;
}
- var gamePath = new Utf8String( ( byte* )path );
+ var gamePath = new ByteString( ( byte* )path );
SetCollection( gameObject, gamePath, collection );
return path;
}
// 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 )
{
diff --git a/Penumbra/Interop/Resolver/PathResolver.cs b/Penumbra/Interop/Resolver/PathResolver.cs
index 17311ed3..c2f4f3ef 100644
--- a/Penumbra/Interop/Resolver/PathResolver.cs
+++ b/Penumbra/Interop/Resolver/PathResolver.cs
@@ -5,9 +5,10 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Loader;
+using Penumbra.String;
+using Penumbra.String.Classes;
namespace Penumbra.Interop.Resolver;
@@ -151,7 +152,7 @@ public partial class PathResolver : IDisposable
return resolveData;
}
- internal IEnumerable< KeyValuePair< Utf8String, ResolveData > > PathCollections
+ internal IEnumerable< KeyValuePair< ByteString, ResolveData > > PathCollections
=> _paths.Paths;
internal IEnumerable< KeyValuePair< IntPtr, (ResolveData, int) > > DrawObjectMap
diff --git a/Penumbra/Meta/Files/CmpFile.cs b/Penumbra/Meta/Files/CmpFile.cs
index 0f308bd6..5a4919bf 100644
--- a/Penumbra/Meta/Files/CmpFile.cs
+++ b/Penumbra/Meta/Files/CmpFile.cs
@@ -4,6 +4,7 @@ using Penumbra.GameData.Structs;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
using System.Collections.Generic;
+using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
@@ -23,7 +24,7 @@ public sealed unsafe class CmpFile : MetaBaseFile
}
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 )
{
diff --git a/Penumbra/Meta/Files/EqdpFile.cs b/Penumbra/Meta/Files/EqdpFile.cs
index d9a22c41..5290fb04 100644
--- a/Penumbra/Meta/Files/EqdpFile.cs
+++ b/Penumbra/Meta/Files/EqdpFile.cs
@@ -4,6 +4,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
+using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
@@ -63,7 +64,7 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
public override void Reset()
{
var def = ( byte* )DefaultData.Data;
- Functions.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize );
+ MemoryUtility.MemCpyUnchecked( Data, def, IdentifierSize + PreambleSize );
var controlPtr = ( ushort* )( def + IdentifierSize + PreambleSize );
var dataBasePtr = controlPtr + BlockCount;
@@ -73,18 +74,18 @@ public sealed unsafe class ExpandedEqdpFile : MetaBaseFile
{
if( controlPtr[ i ] == CollapsedBlock )
{
- Functions.MemSet( myDataPtr, 0, BlockSize * EqdpEntrySize );
+ MemoryUtility.MemSet( myDataPtr, 0, BlockSize * EqdpEntrySize );
}
else
{
- Functions.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize );
+ MemoryUtility.MemCpyUnchecked( myDataPtr, dataBasePtr + controlPtr[ i ], BlockSize * EqdpEntrySize );
}
myControlPtr[ i ] = ( ushort )( i * 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 )
diff --git a/Penumbra/Meta/Files/EqpGmpFile.cs b/Penumbra/Meta/Files/EqpGmpFile.cs
index 60649952..5d15031b 100644
--- a/Penumbra/Meta/Files/EqpGmpFile.cs
+++ b/Penumbra/Meta/Files/EqpGmpFile.cs
@@ -5,6 +5,7 @@ using System.Numerics;
using Penumbra.GameData.Structs;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
+using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
@@ -49,7 +50,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
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()
@@ -62,7 +63,7 @@ public unsafe class ExpandedEqpGmpBase : MetaBaseFile
var collapsed = ( ( controlBlock >> i ) & 1 ) == 0;
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++;
}
else
diff --git a/Penumbra/Meta/Files/EstFile.cs b/Penumbra/Meta/Files/EstFile.cs
index 7c6b3591..5c76ff71 100644
--- a/Penumbra/Meta/Files/EstFile.cs
+++ b/Penumbra/Meta/Files/EstFile.cs
@@ -4,6 +4,7 @@ using Penumbra.GameData.Enums;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations;
+using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
@@ -170,8 +171,8 @@ public sealed unsafe class EstFile : MetaBaseFile
{
var (d, length) = DefaultData;
var data = ( byte* )d;
- Functions.MemCpyUnchecked( Data, data, length );
- Functions.MemSet( Data + length, 0, Length - length );
+ MemoryUtility.MemCpyUnchecked( Data, data, length );
+ MemoryUtility.MemSet( Data + length, 0, Length - length );
}
public EstFile( EstManipulation.EstType estType )
diff --git a/Penumbra/Meta/Files/ImcFile.cs b/Penumbra/Meta/Files/ImcFile.cs
index f1309e64..8f36507e 100644
--- a/Penumbra/Meta/Files/ImcFile.cs
+++ b/Penumbra/Meta/Files/ImcFile.cs
@@ -1,11 +1,12 @@
using System;
using System.Numerics;
using Newtonsoft.Json;
-using Penumbra.GameData.ByteString;
+using OtterGui;
using Penumbra.GameData.Enums;
-using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
+using Penumbra.String.Functions;
namespace Penumbra.Meta.Files;
@@ -150,7 +151,7 @@ public unsafe class ImcFile : MetaBaseFile
var defaultPtr = ( ImcEntry* )( Data + PreambleSize );
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." );
@@ -188,8 +189,8 @@ public unsafe class ImcFile : MetaBaseFile
var file = Dalamud.GameData.GetFile( Path.ToString() );
fixed( byte* ptr = file!.Data )
{
- Functions.MemCpyUnchecked( Data, ptr, file.Data.Length );
- Functions.MemSet( Data + file.Data.Length, 0, Length - file.Data.Length );
+ MemoryUtility.MemCpyUnchecked( Data, ptr, 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 ) );
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;
}
- Functions.MemCpyUnchecked( newData, Data, ActualLength );
+ MemoryUtility.MemCpyUnchecked( newData, Data, ActualLength );
Penumbra.MetaFileManager.Free( data, length );
resource->SetData( ( IntPtr )newData, ActualLength );
diff --git a/Penumbra/Meta/Files/MetaBaseFile.cs b/Penumbra/Meta/Files/MetaBaseFile.cs
index 8a167216..dbe8a35b 100644
--- a/Penumbra/Meta/Files/MetaBaseFile.cs
+++ b/Penumbra/Meta/Files/MetaBaseFile.cs
@@ -1,6 +1,7 @@
using System;
using Dalamud.Memory;
using Penumbra.GameData.Util;
+using Penumbra.String.Functions;
using CharacterUtility = Penumbra.Interop.CharacterUtility;
namespace Penumbra.Meta.Files;
@@ -57,12 +58,12 @@ public unsafe class MetaBaseFile : IDisposable
var data = ( byte* )Penumbra.MetaFileManager.AllocateFileMemory( ( ulong )newLength );
if( newLength > Length )
{
- Functions.MemCpyUnchecked( data, Data, Length );
- Functions.MemSet( data + Length, 0, newLength - Length );
+ MemoryUtility.MemCpyUnchecked( data, Data, Length );
+ MemoryUtility.MemSet( data + Length, 0, newLength - Length );
}
else
{
- Functions.MemCpyUnchecked( data, Data, newLength );
+ MemoryUtility.MemCpyUnchecked( data, Data, newLength );
}
ReleaseUnmanagedResources();
diff --git a/Penumbra/Meta/Manager/MetaManager.Imc.cs b/Penumbra/Meta/Manager/MetaManager.Imc.cs
index 5887aba2..d61f158f 100644
--- a/Penumbra/Meta/Manager/MetaManager.Imc.cs
+++ b/Penumbra/Meta/Manager/MetaManager.Imc.cs
@@ -2,11 +2,12 @@ using System;
using System.Collections.Generic;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using OtterGui.Filesystem;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations;
+using Penumbra.String;
+using Penumbra.String.Classes;
namespace Penumbra.Meta.Manager;
@@ -149,7 +150,7 @@ public partial class MetaManager
=> 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 )
{
ret = 0;
diff --git a/Penumbra/Meta/Manipulations/ImcManipulation.cs b/Penumbra/Meta/Manipulations/ImcManipulation.cs
index 5bd505df..682fe3af 100644
--- a/Penumbra/Meta/Manipulations/ImcManipulation.cs
+++ b/Penumbra/Meta/Manipulations/ImcManipulation.cs
@@ -2,10 +2,10 @@ using System;
using System.Runtime.InteropServices;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
using Penumbra.Meta.Files;
+using Penumbra.String.Classes;
namespace Penumbra.Meta.Manipulations;
diff --git a/Penumbra/Meta/Manipulations/MetaManipulation.cs b/Penumbra/Meta/Manipulations/MetaManipulation.cs
index 4380e0a0..9029800a 100644
--- a/Penumbra/Meta/Manipulations/MetaManipulation.cs
+++ b/Penumbra/Meta/Manipulations/MetaManipulation.cs
@@ -4,6 +4,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Penumbra.GameData.Util;
using Penumbra.Interop.Structs;
+using Penumbra.String.Functions;
namespace Penumbra.Meta.Manipulations;
@@ -217,7 +218,7 @@ public readonly struct MetaManipulation : IEquatable< MetaManipulation >, ICompa
{
fixed( MetaManipulation* lhs = &this )
{
- return Functions.MemCmpUnchecked( lhs, &other, sizeof( MetaManipulation ) );
+ return MemoryUtility.MemCmpUnchecked( lhs, &other, sizeof( MetaManipulation ) );
}
}
diff --git a/Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs b/Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs
index b0a136af..d60356ac 100644
--- a/Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs
+++ b/Penumbra/Mods/Editor/Mod.Editor.Duplicates.cs
@@ -5,7 +5,7 @@ using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Threading.Tasks;
-using Penumbra.GameData.ByteString;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
@@ -124,7 +124,7 @@ public partial class Mod
foreach( var file in files )
{
// 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;
}
diff --git a/Penumbra/Mods/Editor/Mod.Editor.Edit.cs b/Penumbra/Mods/Editor/Mod.Editor.Edit.cs
index 29f5389b..c2221ecc 100644
--- a/Penumbra/Mods/Editor/Mod.Editor.Edit.cs
+++ b/Penumbra/Mods/Editor/Mod.Editor.Edit.cs
@@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Linq;
-using Penumbra.GameData.ByteString;
+using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods;
diff --git a/Penumbra/Mods/Editor/Mod.Editor.Files.cs b/Penumbra/Mods/Editor/Mod.Editor.Files.cs
index 114359db..583442fd 100644
--- a/Penumbra/Mods/Editor/Mod.Editor.Files.cs
+++ b/Penumbra/Mods/Editor/Mod.Editor.Files.cs
@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
-using Penumbra.GameData.ByteString;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
diff --git a/Penumbra/Mods/Editor/Mod.Editor.MdlMaterials.cs b/Penumbra/Mods/Editor/Mod.Editor.MdlMaterials.cs
index 3061d390..45c6707c 100644
--- a/Penumbra/Mods/Editor/Mod.Editor.MdlMaterials.cs
+++ b/Penumbra/Mods/Editor/Mod.Editor.MdlMaterials.cs
@@ -5,9 +5,9 @@ using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using OtterGui;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
diff --git a/Penumbra/Mods/Manager/Mod.Manager.Options.cs b/Penumbra/Mods/Manager/Mod.Manager.Options.cs
index 704d1285..82e171eb 100644
--- a/Penumbra/Mods/Manager/Mod.Manager.Options.cs
+++ b/Penumbra/Mods/Manager/Mod.Manager.Options.cs
@@ -4,8 +4,8 @@ using System.Linq;
using OtterGui;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
-using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
using Penumbra.Util;
namespace Penumbra.Mods;
diff --git a/Penumbra/Mods/Mod.ChangedItems.cs b/Penumbra/Mods/Mod.ChangedItems.cs
index d2996b71..1057bf0d 100644
--- a/Penumbra/Mods/Mod.ChangedItems.cs
+++ b/Penumbra/Mods/Mod.ChangedItems.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using Penumbra.GameData.Util;
namespace Penumbra.Mods;
@@ -14,7 +15,7 @@ public sealed partial class Mod
ChangedItems.Clear();
foreach( var gamePath in AllRedirects )
{
- identifier.Identify( ChangedItems, gamePath.ToGamePath() );
+ identifier.Identify( ChangedItems, new GamePath(gamePath.ToString()) );
}
// TODO: manipulations
diff --git a/Penumbra/Mods/Mod.Creation.cs b/Penumbra/Mods/Mod.Creation.cs
index 699fceaa..a566b795 100644
--- a/Penumbra/Mods/Mod.Creation.cs
+++ b/Penumbra/Mods/Mod.Creation.cs
@@ -6,8 +6,8 @@ using Dalamud.Utility;
using OtterGui.Classes;
using OtterGui.Filesystem;
using Penumbra.Api.Enums;
-using Penumbra.GameData.ByteString;
using Penumbra.Import;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
diff --git a/Penumbra/Mods/Mod.Files.cs b/Penumbra/Mods/Mod.Files.cs
index b09f680c..a12b29fc 100644
--- a/Penumbra/Mods/Mod.Files.cs
+++ b/Penumbra/Mods/Mod.Files.cs
@@ -5,8 +5,8 @@ using System.Linq;
using Newtonsoft.Json.Linq;
using OtterGui;
using Penumbra.Api.Enums;
-using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
diff --git a/Penumbra/Mods/Mod.Meta.Migration.cs b/Penumbra/Mods/Mod.Meta.Migration.cs
index 8d907be9..fa16f4c2 100644
--- a/Penumbra/Mods/Mod.Meta.Migration.cs
+++ b/Penumbra/Mods/Mod.Meta.Migration.cs
@@ -7,7 +7,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
using Penumbra.Api.Enums;
-using Penumbra.GameData.ByteString;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
diff --git a/Penumbra/Mods/Mod.TemporaryMod.cs b/Penumbra/Mods/Mod.TemporaryMod.cs
index c41dcf57..802852e9 100644
--- a/Penumbra/Mods/Mod.TemporaryMod.cs
+++ b/Penumbra/Mods/Mod.TemporaryMod.cs
@@ -4,8 +4,8 @@ using System.IO;
using System.Linq;
using OtterGui.Classes;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
@@ -59,7 +59,7 @@ public sealed partial class Mod
var defaultMod = mod._default;
foreach( var (gamePath, fullPath) in collection.ResolvedFiles )
{
- if( gamePath.Path.EndsWith( '.', 'i', 'm', 'c' ) )
+ if( gamePath.Path.EndsWith( ".imc"u8 ) )
{
continue;
}
diff --git a/Penumbra/Mods/Subclasses/ISubMod.cs b/Penumbra/Mods/Subclasses/ISubMod.cs
index 01bfefc6..89bf2053 100644
--- a/Penumbra/Mods/Subclasses/ISubMod.cs
+++ b/Penumbra/Mods/Subclasses/ISubMod.cs
@@ -1,8 +1,8 @@
using System.Collections.Generic;
using System.IO;
using Newtonsoft.Json;
-using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
diff --git a/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs b/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs
index 47c777cd..883f74e6 100644
--- a/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs
+++ b/Penumbra/Mods/Subclasses/Mod.Files.SubMod.cs
@@ -4,9 +4,9 @@ using System.IO;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-using Penumbra.GameData.ByteString;
using Penumbra.Import;
using Penumbra.Meta.Manipulations;
+using Penumbra.String.Classes;
namespace Penumbra.Mods;
diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs
index 791bac47..e0ed235b 100644
--- a/Penumbra/Penumbra.cs
+++ b/Penumbra/Penumbra.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
using System.Text;
using Dalamud.Game.Command;
using Dalamud.Interface.Windowing;
@@ -21,6 +22,8 @@ using Penumbra.Interop;
using Penumbra.UI;
using Penumbra.Util;
using Penumbra.Collections;
+using Penumbra.GameData;
+using Penumbra.GameData.Actors;
using Penumbra.Interop.Loader;
using Penumbra.Interop.Resolver;
using Penumbra.Mods;
@@ -55,6 +58,8 @@ public class Penumbra : IDalamudPlugin
public static TempModManager TempMods { get; private set; } = null!;
public static ResourceLoader ResourceLoader { 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 readonly ResourceLogger ResourceLogger;
@@ -98,6 +103,7 @@ public class Penumbra : IDalamudPlugin
ModFileSystem = ModFileSystem.Load();
ObjectReloader = new ObjectReloader();
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 )
{
@@ -144,7 +150,7 @@ public class Penumbra : IDalamudPlugin
{
Log.Information( $"Penumbra Version {Version}, Commit #{CommitHash} successfully Loaded." );
}
-
+
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
OtterTex.NativeDll.Initialize( Dalamud.PluginInterface.AssemblyLocation.DirectoryName );
@@ -283,6 +289,8 @@ public class Penumbra : IDalamudPlugin
public void Dispose()
{
+ Dalamud.PluginInterface.RelinquishData( "test1" );
+ Framework?.Dispose();
ShutdownWebServer();
DisposeInterface();
IpcProviders?.Dispose();
@@ -290,7 +298,6 @@ public class Penumbra : IDalamudPlugin
ObjectReloader?.Dispose();
ModFileSystem?.Dispose();
CollectionManager?.Dispose();
- Framework?.Dispose();
Dalamud.Commands.RemoveHandler( CommandName );
diff --git a/Penumbra/UI/Classes/ModEditWindow.FileEdit.cs b/Penumbra/UI/Classes/ModEditWindow.FileEdit.cs
index 42727936..d8c13c81 100644
--- a/Penumbra/UI/Classes/ModEditWindow.FileEdit.cs
+++ b/Penumbra/UI/Classes/ModEditWindow.FileEdit.cs
@@ -9,10 +9,10 @@ using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Files;
using Penumbra.Mods;
-using Functions = Penumbra.GameData.Util.Functions;
+using Penumbra.String.Classes;
+using Penumbra.String.Functions;
namespace Penumbra.UI.Classes;
@@ -459,14 +459,14 @@ public partial class ModEditWindow
ref var rows = ref file.ColorSets[ colorSetIdx ].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 >()
&& file.ColorDyeSets.Length > colorSetIdx )
{
ref var dyeRows = ref file.ColorDyeSets[ colorSetIdx ].Rows;
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];
fixed( byte* ptr = data )
{
- Functions.MemCpyUnchecked( ptr, &row, MtrlFile.ColorSet.Row.Size );
- Functions.MemCpyUnchecked( ptr + MtrlFile.ColorSet.Row.Size, &dye, 2 );
+ MemoryUtility.MemCpyUnchecked( ptr, &row, MtrlFile.ColorSet.Row.Size );
+ MemoryUtility.MemCpyUnchecked( ptr + MtrlFile.ColorSet.Row.Size, &dye, 2 );
}
var text = Convert.ToBase64String( data );
diff --git a/Penumbra/UI/Classes/ModEditWindow.Files.cs b/Penumbra/UI/Classes/ModEditWindow.Files.cs
index dff8eaa6..9b9c16d2 100644
--- a/Penumbra/UI/Classes/ModEditWindow.Files.cs
+++ b/Penumbra/UI/Classes/ModEditWindow.Files.cs
@@ -7,9 +7,8 @@ using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
-using Penumbra.GameData.ByteString;
-using Penumbra.GameData.Util;
using Penumbra.Mods;
+using Penumbra.String.Classes;
namespace Penumbra.UI.Classes;
diff --git a/Penumbra/UI/Classes/ModEditWindow.cs b/Penumbra/UI/Classes/ModEditWindow.cs
index 36aae29d..8a00234a 100644
--- a/Penumbra/UI/Classes/ModEditWindow.cs
+++ b/Penumbra/UI/Classes/ModEditWindow.cs
@@ -8,11 +8,11 @@ using Dalamud.Interface.Windowing;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
-using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.Import.Textures;
using Penumbra.Mods;
+using Penumbra.String.Classes;
using Penumbra.Util;
using static Penumbra.Mods.Mod;
diff --git a/Penumbra/UI/Classes/ModFileSystemSelector.cs b/Penumbra/UI/Classes/ModFileSystemSelector.cs
index f5ffdcf9..10311595 100644
--- a/Penumbra/UI/Classes/ModFileSystemSelector.cs
+++ b/Penumbra/UI/Classes/ModFileSystemSelector.cs
@@ -32,6 +32,7 @@ public sealed partial class ModFileSystemSelector : FileSystemSelector< Mod, Mod
SubscribeRightClickFolder( InheritDescendants, 15 );
SubscribeRightClickFolder( OwnDescendants, 15 );
SubscribeRightClickFolder( SetDefaultImportFolder, 100 );
+ SubscribeRightClickLeaf( ToggleLeafFavorite, 0 );
SubscribeRightClickMain( ClearDefaultImportFolder, 100 );
AddButton( AddNewModButton, 0 );
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 )
{
- var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
- using var c = ImRaii.PushColor( ImGuiCol.Text, state.Color.Value() );
- using var id = ImRaii.PushId( leaf.Value.Index );
+ var flags = selected ? ImGuiTreeNodeFlags.Selected | LeafFlags : LeafFlags;
+ using var c = ImRaii.PushColor( ImGuiCol.Text, state.Color.Value() )
+ .Push( ImGuiCol.HeaderHovered, 0x4000FFFF, leaf.Value.Favorite );
+ using var id = ImRaii.PushId( leaf.Value.Index );
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 )
{
if( ImGui.MenuItem( "Set As Default Import Folder" ) )
diff --git a/Penumbra/UI/ConfigWindow.DebugTab.cs b/Penumbra/UI/ConfigWindow.DebugTab.cs
index c9cf6284..0d64c1b8 100644
--- a/Penumbra/UI/ConfigWindow.DebugTab.cs
+++ b/Penumbra/UI/ConfigWindow.DebugTab.cs
@@ -9,9 +9,9 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
-using Penumbra.GameData.ByteString;
using Penumbra.Interop.Loader;
using Penumbra.Interop.Structs;
+using Penumbra.String;
using CharacterUtility = Penumbra.Interop.CharacterUtility;
namespace Penumbra.UI;
@@ -56,6 +56,8 @@ public partial class ConfigWindow
ImGui.NewLine();
DrawPathResolverDebug();
ImGui.NewLine();
+ DrawActorsDebug();
+ ImGui.NewLine();
DrawDebugCharacterUtility();
ImGui.NewLine();
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
// and which paths are due to be loaded by which collection.
private unsafe void DrawPathResolverDebug()
@@ -173,7 +200,7 @@ public partial class ConfigWindow
ImGui.TableNextColumn();
var obj = ( GameObject* )Dalamud.Objects.GetObjectAddress( idx );
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.TableNextColumn();
ImGui.TextUnformatted( name );
diff --git a/Penumbra/UI/ConfigWindow.EffectiveTab.cs b/Penumbra/UI/ConfigWindow.EffectiveTab.cs
index d096d3ee..a27307d9 100644
--- a/Penumbra/UI/ConfigWindow.EffectiveTab.cs
+++ b/Penumbra/UI/ConfigWindow.EffectiveTab.cs
@@ -7,9 +7,9 @@ using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
+using Penumbra.String.Classes;
namespace Penumbra.UI;
diff --git a/Penumbra/UI/ConfigWindow.Misc.cs b/Penumbra/UI/ConfigWindow.Misc.cs
index e767f755..bf4f44b6 100644
--- a/Penumbra/UI/ConfigWindow.Misc.cs
+++ b/Penumbra/UI/ConfigWindow.Misc.cs
@@ -9,17 +9,16 @@ using OtterGui;
using OtterGui.Raii;
using Penumbra.Api.Enums;
using Penumbra.Collections;
-using Penumbra.GameData.ByteString;
-using Penumbra.GameData.Enums;
using Penumbra.Interop.Structs;
+using Penumbra.String;
using Penumbra.UI.Classes;
namespace Penumbra.UI;
public partial class ConfigWindow
{
- // Draw text given by a Utf8String.
- internal static unsafe void Text( Utf8String s )
+ // Draw text given by a ByteString.
+ internal static unsafe void Text( ByteString s )
=> ImGuiNative.igTextUnformatted( s.Path, s.Path + s.Length );
// Draw text given by a byte pointer.
@@ -30,8 +29,8 @@ public partial class ConfigWindow
private static unsafe void Text( ResourceHandle* resource )
=> Text( resource->FileName(), resource->FileNameLength );
- // Draw a Utf8String as a selectable.
- internal static unsafe bool Selectable( Utf8String s, bool selected )
+ // Draw a ByteString as a selectable.
+ internal static unsafe bool Selectable( ByteString s, bool selected )
{
var tmp = ( byte )( selected ? 1 : 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,
- // using an Utf8String.
- private static unsafe void CopyOnClickSelectable( Utf8String text )
+ // using an ByteString.
+ private static unsafe void CopyOnClickSelectable( ByteString text )
{
if( ImGuiNative.igSelectable_Bool( text.Path, 0, ImGuiSelectableFlags.None, Vector2.Zero ) != 0 )
{
diff --git a/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs b/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs
index 5fc8dcea..28015b79 100644
--- a/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs
+++ b/Penumbra/UI/ConfigWindow.ModPanel.Tabs.cs
@@ -6,9 +6,10 @@ using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using OtterGui.Widgets;
-using Penumbra.GameData.ByteString;
using Penumbra.Meta.Manipulations;
using Penumbra.Mods;
+using Penumbra.String;
+using Penumbra.String.Classes;
using Penumbra.UI.Classes;
namespace Penumbra.UI;
@@ -32,11 +33,11 @@ public partial class ConfigWindow
private Tabs _availableTabs = 0;
// 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 Utf8String DescriptionTabHeader = Utf8String.FromStringUnsafe( "Description", false );
- private static readonly Utf8String SettingsTabHeader = Utf8String.FromStringUnsafe( "Settings", false );
- private static readonly Utf8String ChangedItemsTabHeader = Utf8String.FromStringUnsafe( "Changed Items", false );
- private static readonly Utf8String EditModTabHeader = Utf8String.FromStringUnsafe( "Edit Mod", false );
+ private static readonly ByteString ConflictTabHeader = ByteString.FromSpanUnsafe( "Conflicts"u8, true, false, true );
+ private static readonly ByteString DescriptionTabHeader = ByteString.FromSpanUnsafe( "Description"u8, true, false, true );
+ private static readonly ByteString SettingsTabHeader = ByteString.FromSpanUnsafe( "Settings"u8, true, false, true );
+ private static readonly ByteString ChangedItemsTabHeader = ByteString.FromSpanUnsafe( "Changed Items"u8, true, false, true );
+ private static readonly ByteString EditModTabHeader = ByteString.FromSpanUnsafe( "Edit Mod"u8, true, false, true );
private readonly TagButtons _modTags = new();
@@ -147,6 +148,7 @@ public partial class ConfigWindow
{
Penumbra.ModManager.ChangeLocalTag( _mod.Index, tagIdx, editedTag );
}
+
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,
@@ -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.
- private ImRaii.IEndObject DrawTab( Utf8String name, Tabs flag )
+ private ImRaii.IEndObject DrawTab( ByteString name, Tabs flag )
{
if( !_availableTabs.HasFlag( flag ) )
{
diff --git a/Penumbra/UI/ConfigWindow.ResourceTab.cs b/Penumbra/UI/ConfigWindow.ResourceTab.cs
index 801878cf..ad128ee9 100644
--- a/Penumbra/UI/ConfigWindow.ResourceTab.cs
+++ b/Penumbra/UI/ConfigWindow.ResourceTab.cs
@@ -8,8 +8,8 @@ using FFXIVClientStructs.STD;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
-using Penumbra.GameData.ByteString;
using Penumbra.Interop.Loader;
+using Penumbra.String.Classes;
namespace Penumbra.UI;
diff --git a/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs
index bf8c0de9..2d47e290 100644
--- a/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs
+++ b/Penumbra/UI/ConfigWindow.SettingsTab.Advanced.cs
@@ -3,8 +3,8 @@ using Dalamud.Interface;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
-using Penumbra.GameData.ByteString;
using Penumbra.Interop;
+using Penumbra.String.Classes;
using Penumbra.UI.Classes;
namespace Penumbra.UI;
diff --git a/Penumbra/packages.lock.json b/Penumbra/packages.lock.json
index b68a4adc..38fcd25f 100644
--- a/Penumbra/packages.lock.json
+++ b/Penumbra/packages.lock.json
@@ -67,8 +67,12 @@
"penumbra.gamedata": {
"type": "Project",
"dependencies": {
- "Penumbra.Api": "[1.0.0, )"
+ "Penumbra.Api": "[1.0.0, )",
+ "Penumbra.String": "[1.0.0, )"
}
+ },
+ "penumbra.string": {
+ "type": "Project"
}
}
}