Use CiByteString for anything path-related.

This commit is contained in:
Ottermandias 2024-07-30 18:53:55 +02:00
parent 9d128a4d83
commit d247f83e1d
42 changed files with 163 additions and 124 deletions

View file

@ -94,7 +94,7 @@ public class ResolveApi(
if (!config.EnableMods) if (!config.EnableMods)
return path; return path;
var gamePath = Utf8GamePath.FromString(path, out var p, true) ? p : Utf8GamePath.Empty; var gamePath = Utf8GamePath.FromString(path, out var p) ? p : Utf8GamePath.Empty;
var ret = collection.ResolvePath(gamePath); var ret = collection.ResolvePath(gamePath);
return ret?.ToString() ?? path; return ret?.ToString() ?? path;
} }

View file

@ -137,7 +137,7 @@ public class TemporaryApi(
paths = new Dictionary<Utf8GamePath, FullPath>(redirections.Count); paths = new Dictionary<Utf8GamePath, FullPath>(redirections.Count);
foreach (var (gString, fString) in redirections) foreach (var (gString, fString) in redirections)
{ {
if (!Utf8GamePath.FromString(gString, out var path, false)) if (!Utf8GamePath.FromString(gString, out var path))
{ {
paths = null; paths = null;
return false; return false;

View file

@ -4,7 +4,6 @@ using OtterGui.Services;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
using Penumbra.Communication; using Penumbra.Communication;
using Penumbra.Mods;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
using Penumbra.Services; using Penumbra.Services;
using Penumbra.String.Classes; using Penumbra.String.Classes;
@ -130,7 +129,7 @@ public class DalamudSubstitutionProvider : IDisposable, IApiService
try try
{ {
if (!Utf8GamePath.FromString(path, out var utf8Path, true)) if (!Utf8GamePath.FromString(path, out var utf8Path))
return; return;
var resolved = _activeCollectionData.Interface.ResolvePath(utf8Path); var resolved = _activeCollectionData.Interface.ResolvePath(utf8Path);

View file

@ -8,12 +8,12 @@ namespace Penumbra.Collections.Cache;
public sealed class ImcCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<ImcIdentifier, ImcEntry>(manager, collection) public sealed class ImcCache(MetaFileManager manager, ModCollection collection) : MetaCacheBase<ImcIdentifier, ImcEntry>(manager, collection)
{ {
private readonly Dictionary<ByteString, (ImcFile, HashSet<ImcIdentifier>)> _imcFiles = []; private readonly Dictionary<CiByteString, (ImcFile, HashSet<ImcIdentifier>)> _imcFiles = [];
public bool HasFile(ByteString path) public bool HasFile(CiByteString path)
=> _imcFiles.ContainsKey(path); => _imcFiles.ContainsKey(path);
public bool GetFile(ByteString path, [NotNullWhen(true)] out ImcFile? file) public bool GetFile(CiByteString path, [NotNullWhen(true)] out ImcFile? file)
{ {
if (!_imcFiles.TryGetValue(path, out var p)) if (!_imcFiles.TryGetValue(path, out var p))
{ {

View file

@ -216,10 +216,10 @@ public static class ResourceExtensions
}; };
} }
public static ResourceType Type(ByteString path) public static ResourceType Type(CiByteString path)
{ {
var extIdx = path.LastIndexOf((byte)'.'); var extIdx = path.LastIndexOf((byte)'.');
var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? ByteString.Empty : path.Substring(extIdx + 1); var ext = extIdx == -1 ? path : extIdx == path.Length - 1 ? CiByteString.Empty : path.Substring(extIdx + 1);
return ext.Length switch return ext.Length switch
{ {
@ -231,7 +231,7 @@ public static class ResourceExtensions
}; };
} }
public static ResourceCategory Category(ByteString path) public static ResourceCategory Category(CiByteString path)
{ {
if (path.Length < 3) if (path.Length < 3)
return ResourceCategory.Debug; return ResourceCategory.Debug;

View file

@ -7,6 +7,7 @@ using Penumbra.Api.Enums;
using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.Interop.PathResolving; using Penumbra.Interop.PathResolving;
using Penumbra.Interop.SafeHandles; using Penumbra.Interop.SafeHandles;
using Penumbra.String;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using CharacterUtility = Penumbra.Interop.Services.CharacterUtility; using CharacterUtility = Penumbra.Interop.Services.CharacterUtility;
@ -15,7 +16,7 @@ namespace Penumbra.Interop.Hooks.PostProcessing;
public sealed unsafe class PreBoneDeformerReplacer : IDisposable, IRequiredService public sealed unsafe class PreBoneDeformerReplacer : IDisposable, IRequiredService
{ {
public static readonly Utf8GamePath PreBoneDeformerPath = public static readonly Utf8GamePath PreBoneDeformerPath =
Utf8GamePath.FromSpan("chara/xls/boneDeformer/human.pbd"u8, out var p) ? p : Utf8GamePath.Empty; Utf8GamePath.FromSpan("chara/xls/boneDeformer/human.pbd"u8, MetaDataComputation.All, out var p) ? p : Utf8GamePath.Empty;
// Approximate name guesses. // Approximate name guesses.
private delegate void CharacterBaseSetupScalingDelegate(CharacterBase* drawObject, uint slotIndex); private delegate void CharacterBaseSetupScalingDelegate(CharacterBase* drawObject, uint slotIndex);

View file

@ -102,7 +102,7 @@ public unsafe class CreateFileWHook : IDisposable, IRequiredService
{ {
// Use static storage. // Use static storage.
var ptr = WriteFileName(name); var ptr = WriteFileName(name);
Penumbra.Log.Excessive($"[ResourceHooks] Calling CreateFileWDetour with {ByteString.FromSpanUnsafe(name, false)}."); Penumbra.Log.Excessive($"[ResourceHooks] Calling CreateFileWDetour with {CiByteString.FromSpanUnsafe(name, false)}.");
return _createFileWHook.OriginalDisposeSafe(ptr, access, shareMode, security, creation, flags, template); return _createFileWHook.OriginalDisposeSafe(ptr, access, shareMode, security, creation, flags, template);
} }

View file

@ -41,7 +41,7 @@ public unsafe class ResourceLoader : IDisposable, IService
private int PapResourceHandler(void* self, byte* path, int length) private int PapResourceHandler(void* self, byte* path, int length)
{ {
if (!_config.EnableMods || !Utf8GamePath.FromPointer(path, out var gamePath)) if (!_config.EnableMods || !Utf8GamePath.FromPointer(path, MetaDataComputation.CiCrc32, out var gamePath))
return length; return length;
var (resolvedPath, data) = _incMode.Value var (resolvedPath, data) = _incMode.Value
@ -64,7 +64,7 @@ public unsafe class ResourceLoader : IDisposable, IService
} }
/// <summary> Load a resource for a given path and a specific collection. </summary> /// <summary> Load a resource for a given path and a specific collection. </summary>
public ResourceHandle* LoadResolvedResource(ResourceCategory category, ResourceType type, ByteString path, ResolveData resolveData) public ResourceHandle* LoadResolvedResource(ResourceCategory category, ResourceType type, CiByteString path, ResolveData resolveData)
{ {
_resolvedData = resolveData; _resolvedData = resolveData;
var ret = _resources.GetResource(category, type, path); var ret = _resources.GetResource(category, type, path);
@ -73,7 +73,7 @@ public unsafe class ResourceLoader : IDisposable, IService
} }
/// <summary> Load a resource for a given path and a specific collection. </summary> /// <summary> Load a resource for a given path and a specific collection. </summary>
public SafeResourceHandle LoadResolvedSafeResource(ResourceCategory category, ResourceType type, ByteString path, ResolveData resolveData) public SafeResourceHandle LoadResolvedSafeResource(ResourceCategory category, ResourceType type, CiByteString path, ResolveData resolveData)
{ {
_resolvedData = resolveData; _resolvedData = resolveData;
var ret = _resources.GetSafeResource(category, type, path); var ret = _resources.GetSafeResource(category, type, path);
@ -98,7 +98,7 @@ public unsafe class ResourceLoader : IDisposable, IService
/// </summary> /// </summary>
public event ResourceLoadedDelegate? ResourceLoaded; public event ResourceLoadedDelegate? ResourceLoaded;
public delegate void FileLoadedDelegate(ResourceHandle* resource, ByteString path, bool returnValue, bool custom, public delegate void FileLoadedDelegate(ResourceHandle* resource, CiByteString path, bool returnValue, bool custom,
ReadOnlySpan<byte> additionalData); ReadOnlySpan<byte> additionalData);
/// <summary> /// <summary>
@ -172,7 +172,8 @@ public unsafe class ResourceLoader : IDisposable, IService
return; return;
} }
var path = ByteString.FromSpanUnsafe(actualPath, gamePath.Path.IsNullTerminated, gamePath.Path.IsAsciiLowerCase, gamePath.Path.IsAscii); var path = CiByteString.FromSpanUnsafe(actualPath, gamePath.Path.IsNullTerminated, gamePath.Path.IsAsciiLowerCase,
gamePath.Path.IsAscii);
fileDescriptor->ResourceHandle->FileNameData = path.Path; fileDescriptor->ResourceHandle->FileNameData = path.Path;
fileDescriptor->ResourceHandle->FileNameLength = path.Length; fileDescriptor->ResourceHandle->FileNameLength = path.Length;
MtrlForceSync(fileDescriptor, ref isSync); MtrlForceSync(fileDescriptor, ref isSync);
@ -184,7 +185,7 @@ public unsafe class ResourceLoader : IDisposable, IService
/// <summary> Load a resource by its path. If it is rooted, it will be loaded from the drive, otherwise from the SqPack. </summary> /// <summary> Load a resource by its path. If it is rooted, it will be loaded from the drive, otherwise from the SqPack. </summary>
private byte DefaultLoadResource(ByteString gamePath, SeFileDescriptor* fileDescriptor, int priority, private byte DefaultLoadResource(CiByteString gamePath, SeFileDescriptor* fileDescriptor, int priority,
bool isSync, ReadOnlySpan<byte> additionalData) bool isSync, ReadOnlySpan<byte> additionalData)
{ {
if (Utf8GamePath.IsRooted(gamePath)) if (Utf8GamePath.IsRooted(gamePath))
@ -265,7 +266,7 @@ public unsafe class ResourceLoader : IDisposable, IService
} }
/// <summary> Compute the CRC32 hash for a given path together with potential resource parameters. </summary> /// <summary> Compute the CRC32 hash for a given path together with potential resource parameters. </summary>
private static int ComputeHash(ByteString path, GetResourceParameters* pGetResParams) private static int ComputeHash(CiByteString path, GetResourceParameters* pGetResParams)
{ {
if (pGetResParams == null || !pGetResParams->IsPartialRead) if (pGetResParams == null || !pGetResParams->IsPartialRead)
return path.Crc32; return path.Crc32;
@ -273,11 +274,11 @@ public unsafe class ResourceLoader : IDisposable, IService
// When the game requests file only partially, crc32 includes that information, in format of: // When the game requests file only partially, crc32 includes that information, in format of:
// path/to/file.ext.hex_offset.hex_size // path/to/file.ext.hex_offset.hex_size
// ex) music/ex4/BGM_EX4_System_Title.scd.381adc.30000 // ex) music/ex4/BGM_EX4_System_Title.scd.381adc.30000
return ByteString.Join( return CiByteString.Join(
(byte)'.', (byte)'.',
path, path,
ByteString.FromStringUnsafe(pGetResParams->SegmentOffset.ToString("x"), true), CiByteString.FromString(pGetResParams->SegmentOffset.ToString("x"), out var s1, MetaDataComputation.None) ? s1 : CiByteString.Empty,
ByteString.FromStringUnsafe(pGetResParams->SegmentLength.ToString("x"), true) CiByteString.FromString(pGetResParams->SegmentLength.ToString("x"), out var s2, MetaDataComputation.None) ? s2 : CiByteString.Empty
).Crc32; ).Crc32;
} }

View file

@ -39,14 +39,14 @@ public unsafe class ResourceService : IDisposable, IRequiredService
} }
} }
public ResourceHandle* GetResource(ResourceCategory category, ResourceType type, ByteString path) public ResourceHandle* GetResource(ResourceCategory category, ResourceType type, CiByteString path)
{ {
var hash = path.Crc32; var hash = path.Crc32;
return GetResourceHandler(true, (ResourceManager*)_resourceManager.ResourceManagerAddress, return GetResourceHandler(true, (ResourceManager*)_resourceManager.ResourceManagerAddress,
&category, &type, &hash, path.Path, null, false); &category, &type, &hash, path.Path, null, false);
} }
public SafeResourceHandle GetSafeResource(ResourceCategory category, ResourceType type, ByteString path) public SafeResourceHandle GetSafeResource(ResourceCategory category, ResourceType type, CiByteString path)
=> new((CSResourceHandle*)GetResource(category, type, path), false); => new((CSResourceHandle*)GetResource(category, type, path), false);
public void Dispose() public void Dispose()
@ -102,7 +102,7 @@ public unsafe class ResourceService : IDisposable, IRequiredService
ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk) ResourceType* resourceType, int* resourceHash, byte* path, GetResourceParameters* pGetResParams, bool isUnk)
{ {
using var performance = _performance.Measure(PerformanceType.GetResourceHandler); using var performance = _performance.Measure(PerformanceType.GetResourceHandler);
if (!Utf8GamePath.FromPointer(path, out var gamePath)) if (!Utf8GamePath.FromPointer(path, MetaDataComputation.CiCrc32, out var gamePath))
{ {
Penumbra.Log.Error("[ResourceService] Could not create GamePath from resource path."); Penumbra.Log.Error("[ResourceService] Could not create GamePath from resource path.");
return isSync return isSync
@ -120,7 +120,7 @@ public unsafe class ResourceService : IDisposable, IRequiredService
} }
/// <summary> Call the original GetResource function. </summary> /// <summary> Call the original GetResource function. </summary>
public ResourceHandle* GetOriginalResource(bool sync, ResourceCategory categoryId, ResourceType type, int hash, ByteString path, public ResourceHandle* GetOriginalResource(bool sync, ResourceCategory categoryId, ResourceType type, int hash, CiByteString path,
GetResourceParameters* resourceParameters = null, bool unk = false) GetResourceParameters* resourceParameters = null, bool unk = false)
=> sync => sync
? _getResourceSyncHook.OriginalDisposeSafe(_resourceManager.ResourceManager, &categoryId, &type, &hash, path.Path, ? _getResourceSyncHook.OriginalDisposeSafe(_resourceManager.ResourceManager, &categoryId, &type, &hash, path.Path,

View file

@ -49,7 +49,10 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy
public static unsafe List<MaterialInfo> FindMaterials(IEnumerable<nint> gameObjects, string materialPath) public static unsafe List<MaterialInfo> FindMaterials(IEnumerable<nint> gameObjects, string materialPath)
{ {
var needle = ByteString.FromString(materialPath.Replace('\\', '/'), out var m, true) ? m : ByteString.Empty; var needle = CiByteString.FromString(materialPath.Replace('\\', '/'), out var m,
MetaDataComputation.CiCrc32 | MetaDataComputation.Crc32)
? m
: CiByteString.Empty;
var result = new List<MaterialInfo>(Enum.GetValues<DrawObjectType>().Length); var result = new List<MaterialInfo>(Enum.GetValues<DrawObjectType>().Length);
foreach (var objectPtr in gameObjects) foreach (var objectPtr in gameObjects)
@ -83,7 +86,7 @@ public readonly record struct MaterialInfo(ObjectIndex ObjectIndex, DrawObjectTy
continue; continue;
PathDataHandler.Split(mtrlHandle->ResourceHandle.FileName.AsSpan(), out var path, out _); PathDataHandler.Split(mtrlHandle->ResourceHandle.FileName.AsSpan(), out var path, out _);
var fileName = ByteString.FromSpanUnsafe(path, true); var fileName = CiByteString.FromSpanUnsafe(path, true);
if (fileName == needle) if (fileName == needle)
result.Add(new MaterialInfo(index, type, i, j)); result.Add(new MaterialInfo(index, type, i, j));
} }

View file

@ -31,27 +31,27 @@ public static class PathDataHandler
/// <summary> Create the encoding path for an IMC file. </summary> /// <summary> Create the encoding path for an IMC file. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FullPath CreateImc(ByteString path, ModCollection collection) public static FullPath CreateImc(CiByteString path, ModCollection collection)
=> new($"|{collection.LocalId.Id}_{collection.ImcChangeCounter}_{DiscriminatorString}|{path}"); => new($"|{collection.LocalId.Id}_{collection.ImcChangeCounter}_{DiscriminatorString}|{path}");
/// <summary> Create the encoding path for a TMB file. </summary> /// <summary> Create the encoding path for a TMB file. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FullPath CreateTmb(ByteString path, ModCollection collection) public static FullPath CreateTmb(CiByteString path, ModCollection collection)
=> CreateBase(path, collection); => CreateBase(path, collection);
/// <summary> Create the encoding path for an AVFX file. </summary> /// <summary> Create the encoding path for an AVFX file. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FullPath CreateAvfx(ByteString path, ModCollection collection) public static FullPath CreateAvfx(CiByteString path, ModCollection collection)
=> CreateBase(path, collection); => CreateBase(path, collection);
/// <summary> Create the encoding path for a MTRL file. </summary> /// <summary> Create the encoding path for a MTRL file. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static FullPath CreateMtrl(ByteString path, ModCollection collection, Utf8GamePath originalPath) public static FullPath CreateMtrl(CiByteString path, ModCollection collection, Utf8GamePath originalPath)
=> new($"|{collection.LocalId.Id}_{collection.ChangeCounter}_{originalPath.Path.Crc32:X8}_{DiscriminatorString}|{path}"); => new($"|{collection.LocalId.Id}_{collection.ChangeCounter}_{originalPath.Path.Crc32:X8}_{DiscriminatorString}|{path}");
/// <summary> The base function shared by most file types. </summary> /// <summary> The base function shared by most file types. </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static FullPath CreateBase(ByteString path, ModCollection collection) private static FullPath CreateBase(CiByteString path, ModCollection collection)
=> new($"|{collection.LocalId.Id}_{collection.ChangeCounter}_{DiscriminatorString}|{path}"); => new($"|{collection.LocalId.Id}_{collection.ChangeCounter}_{DiscriminatorString}|{path}");
/// <summary> Read an additional data blurb and parse it into usable data for all file types but Materials. </summary> /// <summary> Read an additional data blurb and parse it into usable data for all file types but Materials. </summary>

View file

@ -52,7 +52,6 @@ public class PathResolver : IDisposable, IService
if (resourceType is ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb) if (resourceType is ResourceType.Lvb or ResourceType.Lgb or ResourceType.Sgb)
return (null, ResolveData.Invalid); return (null, ResolveData.Invalid);
path = path.ToLower();
return category switch return category switch
{ {
// Only Interface collection. // Only Interface collection.

View file

@ -28,7 +28,7 @@ public sealed class PathState(CollectionResolver collectionResolver, MetaState m
_internalResolve.Dispose(); _internalResolve.Dispose();
} }
public bool Consume(ByteString _, out ResolveData collection) public bool Consume(CiByteString _, out ResolveData collection)
{ {
if (_resolveData.IsValueCreated) if (_resolveData.IsValueCreated)
{ {

View file

@ -11,6 +11,6 @@ public sealed class AvfxPathPreProcessor : IPathPreProcessor
public ResourceType Type public ResourceType Type
=> ResourceType.Avfx; => ResourceType.Avfx;
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved) public FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved)
=> nonDefault ? PathDataHandler.CreateAvfx(path, resolveData.ModCollection) : resolved; => nonDefault ? PathDataHandler.CreateAvfx(path, resolveData.ModCollection) : resolved;
} }

View file

@ -10,7 +10,7 @@ namespace Penumbra.Interop.Processing;
public interface IFilePostProcessor : IService public interface IFilePostProcessor : IService
{ {
public ResourceType Type { get; } public ResourceType Type { get; }
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData); public unsafe void PostProcess(ResourceHandle* resource, CiByteString originalGamePath, ReadOnlySpan<byte> additionalData);
} }
public unsafe class FilePostProcessService : IRequiredService, IDisposable public unsafe class FilePostProcessService : IRequiredService, IDisposable
@ -30,7 +30,7 @@ public unsafe class FilePostProcessService : IRequiredService, IDisposable
_resourceLoader.FileLoaded -= OnFileLoaded; _resourceLoader.FileLoaded -= OnFileLoaded;
} }
private void OnFileLoaded(ResourceHandle* resource, ByteString path, bool returnValue, bool custom, private void OnFileLoaded(ResourceHandle* resource, CiByteString path, bool returnValue, bool custom,
ReadOnlySpan<byte> additionalData) ReadOnlySpan<byte> additionalData)
{ {
if (_processors.TryGetValue(resource->FileType, out var processor)) if (_processors.TryGetValue(resource->FileType, out var processor))

View file

@ -11,7 +11,7 @@ public interface IPathPreProcessor : IService
{ {
public ResourceType Type { get; } public ResourceType Type { get; }
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved); public FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved);
} }
public class GamePathPreProcessService : IService public class GamePathPreProcessService : IService
@ -24,7 +24,7 @@ public class GamePathPreProcessService : IService
} }
public (FullPath? Path, ResolveData Data) PreProcess(ResolveData resolveData, ByteString path, bool nonDefault, ResourceType type, public (FullPath? Path, ResolveData Data) PreProcess(ResolveData resolveData, CiByteString path, bool nonDefault, ResourceType type,
FullPath? resolved, FullPath? resolved,
Utf8GamePath originalPath) Utf8GamePath originalPath)
{ {

View file

@ -11,7 +11,7 @@ public sealed class ImcFilePostProcessor(CollectionStorage collections) : IFileP
public ResourceType Type public ResourceType Type
=> ResourceType.Imc; => ResourceType.Imc;
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData) public unsafe void PostProcess(ResourceHandle* resource, CiByteString originalGamePath, ReadOnlySpan<byte> additionalData)
{ {
if (!PathDataHandler.Read(additionalData, out var data) || data.Discriminator != PathDataHandler.Discriminator) if (!PathDataHandler.Read(additionalData, out var data) || data.Discriminator != PathDataHandler.Discriminator)
return; return;

View file

@ -11,7 +11,7 @@ public sealed class ImcPathPreProcessor : IPathPreProcessor
public ResourceType Type public ResourceType Type
=> ResourceType.Imc; => ResourceType.Imc;
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool _, FullPath? resolved) public FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath originalGamePath, bool _, FullPath? resolved)
=> resolveData.ModCollection.MetaCache?.Imc.HasFile(originalGamePath.Path) ?? false => resolveData.ModCollection.MetaCache?.Imc.HasFile(originalGamePath.Path) ?? false
? PathDataHandler.CreateImc(path, resolveData.ModCollection) ? PathDataHandler.CreateImc(path, resolveData.ModCollection)
: resolved; : resolved;

View file

@ -10,7 +10,7 @@ public sealed class MaterialFilePostProcessor //: IFilePostProcessor
public ResourceType Type public ResourceType Type
=> ResourceType.Mtrl; => ResourceType.Mtrl;
public unsafe void PostProcess(ResourceHandle* resource, ByteString originalGamePath, ReadOnlySpan<byte> additionalData) public unsafe void PostProcess(ResourceHandle* resource, CiByteString originalGamePath, ReadOnlySpan<byte> additionalData)
{ {
if (!PathDataHandler.ReadMtrl(additionalData, out var data)) if (!PathDataHandler.ReadMtrl(additionalData, out var data))
return; return;

View file

@ -11,6 +11,6 @@ public sealed class MtrlPathPreProcessor : IPathPreProcessor
public ResourceType Type public ResourceType Type
=> ResourceType.Mtrl; => ResourceType.Mtrl;
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved) public FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath originalGamePath, bool nonDefault, FullPath? resolved)
=> nonDefault ? PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalGamePath) : resolved; => nonDefault ? PathDataHandler.CreateMtrl(path, resolveData.ModCollection, originalGamePath) : resolved;
} }

View file

@ -11,6 +11,6 @@ public sealed class TmbPathPreProcessor : IPathPreProcessor
public ResourceType Type public ResourceType Type
=> ResourceType.Tmb; => ResourceType.Tmb;
public FullPath? PreProcess(ResolveData resolveData, ByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved) public FullPath? PreProcess(ResolveData resolveData, CiByteString path, Utf8GamePath _, bool nonDefault, FullPath? resolved)
=> nonDefault ? PathDataHandler.CreateTmb(path, resolveData.ModCollection) : resolved; => nonDefault ? PathDataHandler.CreateTmb(path, resolveData.ModCollection) : resolved;
} }

View file

@ -3,7 +3,6 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.Meta;
using Penumbra.Meta.Files; using Penumbra.Meta.Files;
using Penumbra.Meta.Manipulations; using Penumbra.Meta.Manipulations;
using Penumbra.String; using Penumbra.String;
@ -99,7 +98,7 @@ internal partial record ResolveContext
Span<byte> pathBuffer = stackalloc byte[260]; Span<byte> pathBuffer = stackalloc byte[260];
pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName); pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName);
return Utf8GamePath.FromSpan(pathBuffer, out var path) ? path.Clone() : Utf8GamePath.Empty; return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
} }
[SkipLocalsInit] [SkipLocalsInit]
@ -133,7 +132,7 @@ internal partial record ResolveContext
if (weaponPosition >= 0) if (weaponPosition >= 0)
WriteZeroPaddedNumber(pathBuffer[(weaponPosition + 9)..(weaponPosition + 13)], mirroredSetId); WriteZeroPaddedNumber(pathBuffer[(weaponPosition + 9)..(weaponPosition + 13)], mirroredSetId);
return Utf8GamePath.FromSpan(pathBuffer, out var path) ? path.Clone() : Utf8GamePath.Empty; return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
} }
} }
@ -148,7 +147,7 @@ internal partial record ResolveContext
Span<byte> pathBuffer = stackalloc byte[260]; Span<byte> pathBuffer = stackalloc byte[260];
pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName); pathBuffer = AssembleMaterialPath(pathBuffer, modelPath.Path.Span, variant, fileName);
return Utf8GamePath.FromSpan(pathBuffer, out var path) ? path.Clone() : Utf8GamePath.Empty; return Utf8GamePath.FromSpan(pathBuffer, MetaDataComputation.None, out var path) ? path.Clone() : Utf8GamePath.Empty;
} }
private unsafe byte ResolveMaterialVariant(ResourceHandle* imc, Variant variant) private unsafe byte ResolveMaterialVariant(ResourceHandle* imc, Variant variant)
@ -196,7 +195,7 @@ internal partial record ResolveContext
private unsafe Utf8GamePath ResolveMaterialPathNative(byte* mtrlFileName) private unsafe Utf8GamePath ResolveMaterialPathNative(byte* mtrlFileName)
{ {
ByteString? path; CiByteString? path;
try try
{ {
path = CharacterBase->ResolveMtrlPathAsByteString(SlotIndex, mtrlFileName); path = CharacterBase->ResolveMtrlPathAsByteString(SlotIndex, mtrlFileName);

View file

@ -45,25 +45,25 @@ internal unsafe partial record ResolveContext(
public CharacterBase* CharacterBase public CharacterBase* CharacterBase
=> CharacterBasePointer.Value; => CharacterBasePointer.Value;
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true); private static readonly CiByteString ShpkPrefix = CiByteString.FromSpanUnsafe("shader/sm5/shpk"u8, true, true, true);
private ModelType ModelType private ModelType ModelType
=> CharacterBase->GetModelType(); => CharacterBase->GetModelType();
private ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, ByteString gamePath) private ResourceNode? CreateNodeFromShpk(ShaderPackageResourceHandle* resourceHandle, CiByteString gamePath)
{ {
if (resourceHandle == null) if (resourceHandle == null)
return null; return null;
if (gamePath.IsEmpty) if (gamePath.IsEmpty)
return null; return null;
if (!Utf8GamePath.FromByteString(ByteString.Join((byte)'/', ShpkPrefix, gamePath), out var path, false)) if (!Utf8GamePath.FromByteString(CiByteString.Join((byte)'/', ShpkPrefix, gamePath), out var path))
return null; return null;
return GetOrCreateNode(ResourceType.Shpk, (nint)resourceHandle->ShaderPackage, &resourceHandle->ResourceHandle, path); return GetOrCreateNode(ResourceType.Shpk, (nint)resourceHandle->ShaderPackage, &resourceHandle->ResourceHandle, path);
} }
[SkipLocalsInit] [SkipLocalsInit]
private ResourceNode? CreateNodeFromTex(TextureResourceHandle* resourceHandle, ByteString gamePath, bool dx11) private ResourceNode? CreateNodeFromTex(TextureResourceHandle* resourceHandle, CiByteString gamePath, bool dx11)
{ {
if (resourceHandle == null) if (resourceHandle == null)
return null; return null;
@ -81,7 +81,7 @@ internal unsafe partial record ResolveContext(
prefixed[lastDirectorySeparator + 2] = (byte)'-'; prefixed[lastDirectorySeparator + 2] = (byte)'-';
gamePath.Span[(lastDirectorySeparator + 1)..].CopyTo(prefixed[(lastDirectorySeparator + 3)..]); gamePath.Span[(lastDirectorySeparator + 1)..].CopyTo(prefixed[(lastDirectorySeparator + 3)..]);
if (!Utf8GamePath.FromSpan(prefixed[..(gamePath.Length + 2)], out var tmp)) if (!Utf8GamePath.FromSpan(prefixed[..(gamePath.Length + 2)], MetaDataComputation.None, out var tmp))
return null; return null;
path = tmp.Clone(); path = tmp.Clone();
@ -118,11 +118,11 @@ internal unsafe partial record ResolveContext(
throw new ArgumentNullException(nameof(resourceHandle)); throw new ArgumentNullException(nameof(resourceHandle));
var fileName = (ReadOnlySpan<byte>)resourceHandle->FileName.AsSpan(); var fileName = (ReadOnlySpan<byte>)resourceHandle->FileName.AsSpan();
var additionalData = ByteString.Empty; var additionalData = CiByteString.Empty;
if (PathDataHandler.Split(fileName, out fileName, out var data)) if (PathDataHandler.Split(fileName, out fileName, out var data))
additionalData = ByteString.FromSpanUnsafe(data, false).Clone(); additionalData = CiByteString.FromSpanUnsafe(data, false).Clone();
var fullPath = Utf8GamePath.FromSpan(fileName, out var p) ? new FullPath(p.Clone()) : FullPath.Empty; var fullPath = Utf8GamePath.FromSpan(fileName, MetaDataComputation.None, out var p) ? new FullPath(p.Clone()) : FullPath.Empty;
var node = new ResourceNode(type, objectAddress, (nint)resourceHandle, GetResourceHandleLength(resourceHandle), this) var node = new ResourceNode(type, objectAddress, (nint)resourceHandle, GetResourceHandleLength(resourceHandle), this)
{ {
@ -222,7 +222,7 @@ internal unsafe partial record ResolveContext(
return cached; return cached;
var node = CreateNode(ResourceType.Mtrl, (nint)mtrl, &resource->ResourceHandle, path, false); var node = CreateNode(ResourceType.Mtrl, (nint)mtrl, &resource->ResourceHandle, path, false);
var shpkNode = CreateNodeFromShpk(resource->ShaderPackageResourceHandle, new ByteString(resource->ShpkName)); var shpkNode = CreateNodeFromShpk(resource->ShaderPackageResourceHandle, new CiByteString(resource->ShpkName));
if (shpkNode != null) if (shpkNode != null)
{ {
if (Global.WithUiData) if (Global.WithUiData)
@ -236,7 +236,7 @@ internal unsafe partial record ResolveContext(
var alreadyProcessedSamplerIds = new HashSet<uint>(); var alreadyProcessedSamplerIds = new HashSet<uint>();
for (var i = 0; i < resource->TextureCount; i++) for (var i = 0; i < resource->TextureCount; i++)
{ {
var texNode = CreateNodeFromTex(resource->Textures[i].TextureResourceHandle, new ByteString(resource->TexturePath(i)), var texNode = CreateNodeFromTex(resource->Textures[i].TextureResourceHandle, new CiByteString(resource->TexturePath(i)),
resource->Textures[i].IsDX11); resource->Textures[i].IsDX11);
if (texNode == null) if (texNode == null)
continue; continue;

View file

@ -15,7 +15,7 @@ public class ResourceNode : ICloneable
public readonly nint ResourceHandle; public readonly nint ResourceHandle;
public Utf8GamePath[] PossibleGamePaths; public Utf8GamePath[] PossibleGamePaths;
public FullPath FullPath; public FullPath FullPath;
public ByteString AdditionalData; public CiByteString AdditionalData;
public readonly ulong Length; public readonly ulong Length;
public readonly List<ResourceNode> Children; public readonly List<ResourceNode> Children;
internal ResolveContext? ResolveContext; internal ResolveContext? ResolveContext;
@ -26,9 +26,9 @@ public class ResourceNode : ICloneable
set set
{ {
if (value.IsEmpty) if (value.IsEmpty)
PossibleGamePaths = Array.Empty<Utf8GamePath>(); PossibleGamePaths = [];
else else
PossibleGamePaths = new[] { value }; PossibleGamePaths = [value];
} }
} }
@ -40,8 +40,8 @@ public class ResourceNode : ICloneable
Type = type; Type = type;
ObjectAddress = objectAddress; ObjectAddress = objectAddress;
ResourceHandle = resourceHandle; ResourceHandle = resourceHandle;
PossibleGamePaths = Array.Empty<Utf8GamePath>(); PossibleGamePaths = [];
AdditionalData = ByteString.Empty; AdditionalData = CiByteString.Empty;
Length = length; Length = length;
Children = new List<ResourceNode>(); Children = new List<ResourceNode>();
ResolveContext = resolveContext; ResolveContext = resolveContext;
@ -90,7 +90,7 @@ public class ResourceNode : ICloneable
public readonly record struct UiData(string? Name, ChangedItemIcon Icon) public readonly record struct UiData(string? Name, ChangedItemIcon Icon)
{ {
public readonly UiData PrependName(string prefix) public UiData PrependName(string prefix)
=> Name == null ? this : new UiData(prefix + Name, Icon); => Name == null ? this : new UiData(prefix + Name, Icon);
} }
} }

View file

@ -2,6 +2,7 @@ using FFXIVClientStructs.FFXIV.Client.System.Resource;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.String;
using Penumbra.String.Classes; using Penumbra.String.Classes;
namespace Penumbra.Interop.Services; namespace Penumbra.Interop.Services;
@ -9,10 +10,10 @@ namespace Penumbra.Interop.Services;
public sealed unsafe class DecalReverter : IDisposable public sealed unsafe class DecalReverter : IDisposable
{ {
public static readonly Utf8GamePath DecalPath = public static readonly Utf8GamePath DecalPath =
Utf8GamePath.FromSpan("chara/common/texture/decal_equip/_stigma.tex"u8, out var p) ? p : Utf8GamePath.Empty; Utf8GamePath.FromSpan("chara/common/texture/decal_equip/_stigma.tex"u8, MetaDataComputation.All, out var p) ? p : Utf8GamePath.Empty;
public static readonly Utf8GamePath TransparentPath = public static readonly Utf8GamePath TransparentPath =
Utf8GamePath.FromSpan("chara/common/texture/transparent.tex"u8, out var p) ? p : Utf8GamePath.Empty; Utf8GamePath.FromSpan("chara/common/texture/transparent.tex"u8, MetaDataComputation.All, out var p) ? p : Utf8GamePath.Empty;
private readonly CharacterUtility _utility; private readonly CharacterUtility _utility;
private readonly Structs.TextureResourceHandle* _decal; private readonly Structs.TextureResourceHandle* _decal;

View file

@ -47,11 +47,11 @@ public unsafe struct ResourceHandle
public ulong DataLength; public ulong DataLength;
} }
public readonly ByteString FileName() public readonly CiByteString FileName()
=> CsHandle.FileName.AsByteString(); => CsHandle.FileName.AsByteString();
public readonly bool GamePath(out Utf8GamePath path) public readonly bool GamePath(out Utf8GamePath path)
=> Utf8GamePath.FromSpan(CsHandle.FileName.AsSpan(), out path); => Utf8GamePath.FromSpan(CsHandle.FileName.AsSpan(), MetaDataComputation.All, out path);
[FieldOffset(0x00)] [FieldOffset(0x00)]
public CsHandle.ResourceHandle CsHandle; public CsHandle.ResourceHandle CsHandle;

View file

@ -6,48 +6,48 @@ namespace Penumbra.Interop.Structs;
internal static class StructExtensions internal static class StructExtensions
{ {
public static unsafe ByteString AsByteString(in this StdString str) public static CiByteString AsByteString(in this StdString str)
=> ByteString.FromSpanUnsafe(str.AsSpan(), true); => CiByteString.FromSpanUnsafe(str.AsSpan(), true);
public static ByteString ResolveEidPathAsByteString(ref this CharacterBase character) public static CiByteString ResolveEidPathAsByteString(ref this CharacterBase character)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveEidPath(pathBuffer)); return ToOwnedByteString(character.ResolveEidPath(pathBuffer));
} }
public static ByteString ResolveImcPathAsByteString(ref this CharacterBase character, uint slotIndex) public static CiByteString ResolveImcPathAsByteString(ref this CharacterBase character, uint slotIndex)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveImcPath(pathBuffer, slotIndex)); return ToOwnedByteString(character.ResolveImcPath(pathBuffer, slotIndex));
} }
public static ByteString ResolveMdlPathAsByteString(ref this CharacterBase character, uint slotIndex) public static CiByteString ResolveMdlPathAsByteString(ref this CharacterBase character, uint slotIndex)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveMdlPath(pathBuffer, slotIndex)); return ToOwnedByteString(character.ResolveMdlPath(pathBuffer, slotIndex));
} }
public static unsafe ByteString ResolveMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex, byte* mtrlFileName) public static unsafe CiByteString ResolveMtrlPathAsByteString(ref this CharacterBase character, uint slotIndex, byte* mtrlFileName)
{ {
var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName)); return ToOwnedByteString(character.ResolveMtrlPath(pathBuffer, CharacterBase.PathBufferSize, slotIndex, mtrlFileName));
} }
public static ByteString ResolveSklbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) public static CiByteString ResolveSklbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveSklbPath(pathBuffer, partialSkeletonIndex)); return ToOwnedByteString(character.ResolveSklbPath(pathBuffer, partialSkeletonIndex));
} }
public static ByteString ResolveSkpPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) public static CiByteString ResolveSkpPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex)
{ {
Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; Span<byte> pathBuffer = stackalloc byte[CharacterBase.PathBufferSize];
return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex)); return ToOwnedByteString(character.ResolveSkpPath(pathBuffer, partialSkeletonIndex));
} }
private static unsafe ByteString ToOwnedByteString(byte* str) private static unsafe CiByteString ToOwnedByteString(byte* str)
=> str == null ? ByteString.Empty : new ByteString(str).Clone(); => str == null ? CiByteString.Empty : new CiByteString(str).Clone();
private static ByteString ToOwnedByteString(ReadOnlySpan<byte> str) private static CiByteString ToOwnedByteString(ReadOnlySpan<byte> str)
=> str.Length == 0 ? ByteString.Empty : ByteString.FromSpanUnsafe(str, true).Clone(); => str.Length == 0 ? CiByteString.Empty : CiByteString.FromSpanUnsafe(str, true).Clone();
} }

View file

@ -270,7 +270,7 @@ public partial class ModCreator(
public MultiSubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option, ModPriority priority) public MultiSubMod CreateSubMod(DirectoryInfo baseFolder, DirectoryInfo optionFolder, OptionList option, ModPriority priority)
{ {
var list = optionFolder.EnumerateNonHiddenFiles() var list = optionFolder.EnumerateNonHiddenFiles()
.Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath, true), gamePath, new FullPath(f))) .Select(f => (Utf8GamePath.FromFile(f, optionFolder, out var gamePath), gamePath, new FullPath(f)))
.Where(t => t.Item1); .Where(t => t.Item1);
var mod = MultiSubMod.WithoutGroup(option.Name, option.Description, priority); var mod = MultiSubMod.WithoutGroup(option.Name, option.Description, priority);
@ -291,7 +291,7 @@ public partial class ModCreator(
ReloadMod(mod, false, out _); ReloadMod(mod, false, out _);
foreach (var file in mod.FindUnusedFiles()) foreach (var file in mod.FindUnusedFiles())
{ {
if (Utf8GamePath.FromFile(new FileInfo(file.FullName), directory, out var gamePath, true)) if (Utf8GamePath.FromFile(new FileInfo(file.FullName), directory, out var gamePath))
mod.Default.Files.TryAdd(gamePath, file); mod.Default.Files.TryAdd(gamePath, file);
} }

View file

@ -52,7 +52,7 @@ public static class SubMod
if (files != null) if (files != null)
foreach (var property in files.Properties()) foreach (var property in files.Properties())
{ {
if (Utf8GamePath.FromString(property.Name, out var p, true)) if (Utf8GamePath.FromString(property.Name, out var p))
data.Files.TryAdd(p, new FullPath(basePath, property.Value.ToObject<Utf8RelPath>())); data.Files.TryAdd(p, new FullPath(basePath, property.Value.ToObject<Utf8RelPath>()));
} }
@ -60,7 +60,7 @@ public static class SubMod
if (swaps != null) if (swaps != null)
foreach (var property in swaps.Properties()) foreach (var property in swaps.Properties())
{ {
if (Utf8GamePath.FromString(property.Name, out var p, true)) if (Utf8GamePath.FromString(property.Name, out var p))
data.FileSwaps.TryAdd(p, new FullPath(property.Value.ToObject<string>()!)); data.FileSwaps.TryAdd(p, new FullPath(property.Value.ToObject<string>()!));
} }

View file

@ -6,6 +6,7 @@ using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Compression; using OtterGui.Compression;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Files; using Penumbra.GameData.Files;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
@ -98,7 +99,7 @@ public class FileEditor<T>(
_inInput = ImGui.IsItemActive(); _inInput = ImGui.IsItemActive();
if (ImGui.IsItemDeactivatedAfterEdit() && _defaultPath.Length > 0) if (ImGui.IsItemDeactivatedAfterEdit() && _defaultPath.Length > 0)
{ {
_isDefaultPathUtf8Valid = Utf8GamePath.FromString(_defaultPath, out _defaultPathUtf8, true); _isDefaultPathUtf8Valid = Utf8GamePath.FromString(_defaultPath, out _defaultPathUtf8);
_quickImport = null; _quickImport = null;
fileDialog.Reset(); fileDialog.Reset();
try try
@ -306,7 +307,7 @@ public class FileEditor<T>(
foreach (var (option, gamePath) in file.SubModUsage) foreach (var (option, gamePath) in file.SubModUsage)
{ {
ImGui.TableNextColumn(); ImGui.TableNextColumn();
UiHelpers.Text(gamePath.Path); ImUtf8.Text(gamePath.Path.Span);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value()); using var color = ImRaii.PushColor(ImGuiCol.Text, ColorId.ItemId.Value());
ImGui.TextUnformatted(option.GetFullName()); ImGui.TextUnformatted(option.GetFullName());

View file

@ -209,7 +209,7 @@ public partial class ModEditWindow
if (ImGui.IsItemDeactivatedAfterEdit()) if (ImGui.IsItemDeactivatedAfterEdit())
{ {
if (Utf8GamePath.FromString(_gamePathEdit, out var path, false)) if (Utf8GamePath.FromString(_gamePathEdit, out var path))
_editor.FileEditor.SetGamePath(_editor.Option!, _fileIdx, _pathIdx, path); _editor.FileEditor.SetGamePath(_editor.Option!, _fileIdx, _pathIdx, path);
_fileIdx = -1; _fileIdx = -1;
@ -217,7 +217,7 @@ public partial class ModEditWindow
} }
else if (_fileIdx == i else if (_fileIdx == i
&& _pathIdx == j && _pathIdx == j
&& (!Utf8GamePath.FromString(_gamePathEdit, out var path, false) && (!Utf8GamePath.FromString(_gamePathEdit, out var path)
|| !path.IsEmpty && !path.Equals(gamePath) && !_editor.FileEditor.CanAddGamePath(path))) || !path.IsEmpty && !path.Equals(gamePath) && !_editor.FileEditor.CanAddGamePath(path)))
{ {
ImGui.SameLine(); ImGui.SameLine();
@ -241,7 +241,7 @@ public partial class ModEditWindow
if (ImGui.IsItemDeactivatedAfterEdit()) if (ImGui.IsItemDeactivatedAfterEdit())
{ {
if (Utf8GamePath.FromString(_gamePathEdit, out var path, false) && !path.IsEmpty) if (Utf8GamePath.FromString(_gamePathEdit, out var path) && !path.IsEmpty)
_editor.FileEditor.SetGamePath(_editor.Option!, _fileIdx, _pathIdx, path); _editor.FileEditor.SetGamePath(_editor.Option!, _fileIdx, _pathIdx, path);
_fileIdx = -1; _fileIdx = -1;
@ -249,7 +249,7 @@ public partial class ModEditWindow
} }
else if (_fileIdx == i else if (_fileIdx == i
&& _pathIdx == -1 && _pathIdx == -1
&& (!Utf8GamePath.FromString(_gamePathEdit, out var path, false) && (!Utf8GamePath.FromString(_gamePathEdit, out var path)
|| !path.IsEmpty && !_editor.FileEditor.CanAddGamePath(path))) || !path.IsEmpty && !_editor.FileEditor.CanAddGamePath(path)))
{ {
ImGui.SameLine(); ImGui.SameLine();

View file

@ -25,7 +25,7 @@ public partial class ModEditWindow
{ {
private const int ShpkPrefixLength = 16; private const int ShpkPrefixLength = 16;
private static readonly ByteString ShpkPrefix = ByteString.FromSpanUnsafe("shader/sm5/shpk/"u8, true, true, true); private static readonly CiByteString ShpkPrefix = CiByteString.FromSpanUnsafe("shader/sm5/shpk/"u8, true, true, true);
private readonly ModEditWindow _edit; private readonly ModEditWindow _edit;
public readonly MtrlFile Mtrl; public readonly MtrlFile Mtrl;
@ -77,7 +77,7 @@ public partial class ModEditWindow
public FullPath FindAssociatedShpk(out string defaultPath, out Utf8GamePath defaultGamePath) public FullPath FindAssociatedShpk(out string defaultPath, out Utf8GamePath defaultGamePath)
{ {
defaultPath = GamePaths.Shader.ShpkPath(Mtrl.ShaderPackage.Name); defaultPath = GamePaths.Shader.ShpkPath(Mtrl.ShaderPackage.Name);
if (!Utf8GamePath.FromString(defaultPath, out defaultGamePath, true)) if (!Utf8GamePath.FromString(defaultPath, out defaultGamePath))
return FullPath.Empty; return FullPath.Empty;
return _edit.FindBestMatch(defaultGamePath); return _edit.FindBestMatch(defaultGamePath);

View file

@ -271,7 +271,7 @@ public partial class ModEditWindow
private byte[]? ReadFile(string path) private byte[]? ReadFile(string path)
{ {
// TODO: if cross-collection lookups are turned off, this conversion can be skipped // TODO: if cross-collection lookups are turned off, this conversion can be skipped
if (!Utf8GamePath.FromString(path, out var utf8Path, true)) if (!Utf8GamePath.FromString(path, out var utf8Path))
throw new Exception($"Resolved path {path} could not be converted to a game path."); throw new Exception($"Resolved path {path} could not be converted to a game path.");
var resolvedPath = _edit._activeCollections.Current.ResolvePath(utf8Path) ?? new FullPath(utf8Path); var resolvedPath = _edit._activeCollections.Current.ResolvePath(utf8Path) ?? new FullPath(utf8Path);

View file

@ -16,7 +16,7 @@ namespace Penumbra.UI.AdvancedWindow;
public partial class ModEditWindow public partial class ModEditWindow
{ {
private static readonly ByteString DisassemblyLabel = ByteString.FromSpanUnsafe("##disassembly"u8, true, true, true); private static readonly CiByteString DisassemblyLabel = CiByteString.FromSpanUnsafe("##disassembly"u8, true, true, true);
private readonly FileEditor<ShpkTab> _shaderPackageTab; private readonly FileEditor<ShpkTab> _shaderPackageTab;

View file

@ -562,7 +562,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService
return new FullPath(path); return new FullPath(path);
} }
private HashSet<Utf8GamePath> FindPathsStartingWith(ByteString prefix) private HashSet<Utf8GamePath> FindPathsStartingWith(CiByteString prefix)
{ {
var ret = new HashSet<Utf8GamePath>(); var ret = new HashSet<Utf8GamePath>();

View file

@ -84,7 +84,8 @@ public class ResourceTreeViewer
using (var c = ImRaii.PushColor(ImGuiCol.Text, CategoryColor(category).Value())) using (var c = ImRaii.PushColor(ImGuiCol.Text, CategoryColor(category).Value()))
{ {
var isOpen = ImGui.CollapsingHeader($"{(_incognito.IncognitoMode ? tree.AnonymizedName : tree.Name)}###{index}", index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0); var isOpen = ImGui.CollapsingHeader($"{(_incognito.IncognitoMode ? tree.AnonymizedName : tree.Name)}###{index}",
index == 0 ? ImGuiTreeNodeFlags.DefaultOpen : 0);
if (debugMode) if (debugMode)
{ {
using var _ = ImRaii.PushFont(UiBuilder.MonoFont); using var _ = ImRaii.PushFont(UiBuilder.MonoFont);
@ -149,7 +150,9 @@ public class ResourceTreeViewer
var filterChanged = false; var filterChanged = false;
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - yOffset); ImGui.SetCursorPosY(ImGui.GetCursorPosY() - yOffset);
using (ImRaii.Child("##typeFilter", new Vector2(ImGui.GetContentRegionAvail().X, ChangedItemDrawer.TypeFilterIconSize.Y))) using (ImRaii.Child("##typeFilter", new Vector2(ImGui.GetContentRegionAvail().X, ChangedItemDrawer.TypeFilterIconSize.Y)))
{
filterChanged |= _changedItemDrawer.DrawTypeFilter(ref _typeFilter); filterChanged |= _changedItemDrawer.DrawTypeFilter(ref _typeFilter);
}
var fieldWidth = (ImGui.GetContentRegionAvail().X - checkSpacing * 2.0f - ImGui.GetFrameHeightWithSpacing()) / 2.0f; var fieldWidth = (ImGui.GetContentRegionAvail().X - checkSpacing * 2.0f - ImGui.GetFrameHeightWithSpacing()) / 2.0f;
ImGui.SetNextItemWidth(fieldWidth); ImGui.SetNextItemWidth(fieldWidth);
@ -181,7 +184,8 @@ public class ResourceTreeViewer
} }
}); });
private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level, nint pathHash, ChangedItemDrawer.ChangedItemIcon parentFilterIcon) private void DrawNodes(IEnumerable<ResourceNode> resourceNodes, int level, nint pathHash,
ChangedItemDrawer.ChangedItemIcon parentFilterIcon)
{ {
var debugMode = _config.DebugMode; var debugMode = _config.DebugMode;
var frameHeight = ImGui.GetFrameHeight(); var frameHeight = ImGui.GetFrameHeight();
@ -196,9 +200,9 @@ public class ResourceTreeViewer
return true; return true;
return node.Name != null && node.Name.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) return node.Name != null && node.Name.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)
|| node.FullPath.FullName.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) || node.FullPath.FullName.Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)
|| node.FullPath.InternalName.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) || node.FullPath.InternalName.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)
|| Array.Exists(node.PossibleGamePaths, path => path.Path.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)); || Array.Exists(node.PossibleGamePaths, path => path.Path.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase));
} }
NodeVisibility CalculateNodeVisibility(nint nodePathHash, ResourceNode node, ChangedItemDrawer.ChangedItemIcon parentFilterIcon) NodeVisibility CalculateNodeVisibility(nint nodePathHash, ResourceNode node, ChangedItemDrawer.ChangedItemIcon parentFilterIcon)
@ -226,10 +230,11 @@ public class ResourceTreeViewer
visibility = CalculateNodeVisibility(nodePathHash, node, parentFilterIcon); visibility = CalculateNodeVisibility(nodePathHash, node, parentFilterIcon);
_filterCache.Add(nodePathHash, visibility); _filterCache.Add(nodePathHash, visibility);
} }
return visibility; return visibility;
} }
string GetAdditionalDataSuffix(ByteString data) string GetAdditionalDataSuffix(CiByteString data)
=> !debugMode || data.IsEmpty ? string.Empty : $"\n\nAdditional Data: {data}"; => !debugMode || data.IsEmpty ? string.Empty : $"\n\nAdditional Data: {data}";
foreach (var (resourceNode, index) in resourceNodes.WithIndex()) foreach (var (resourceNode, index) in resourceNodes.WithIndex())
@ -252,8 +257,9 @@ public class ResourceTreeViewer
var unfolded = _unfolded.Contains(nodePathHash); var unfolded = _unfolded.Contains(nodePathHash);
using (var indent = ImRaii.PushIndent(level)) using (var indent = ImRaii.PushIndent(level))
{ {
var hasVisibleChildren = resourceNode.Children.Any(child => GetNodeVisibility(unchecked(nodePathHash * 31 + child.ResourceHandle), child, filterIcon) != NodeVisibility.Hidden); var hasVisibleChildren = resourceNode.Children.Any(child
var unfoldable = hasVisibleChildren && visibility != NodeVisibility.DescendentsOnly; => GetNodeVisibility(unchecked(nodePathHash * 31 + child.ResourceHandle), child, filterIcon) != NodeVisibility.Hidden);
var unfoldable = hasVisibleChildren && visibility != NodeVisibility.DescendentsOnly;
if (unfoldable) if (unfoldable)
{ {
using var font = ImRaii.PushFont(UiBuilder.IconFont); using var font = ImRaii.PushFont(UiBuilder.IconFont);
@ -317,13 +323,15 @@ public class ResourceTreeViewer
ImGui.Selectable(resourceNode.FullPath.ToPath(), false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); ImGui.Selectable(resourceNode.FullPath.ToPath(), false, 0, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
if (ImGui.IsItemClicked()) if (ImGui.IsItemClicked())
ImGui.SetClipboardText(resourceNode.FullPath.ToPath()); ImGui.SetClipboardText(resourceNode.FullPath.ToPath());
ImGuiUtil.HoverTooltip($"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); ImGuiUtil.HoverTooltip(
$"{resourceNode.FullPath.ToPath()}\n\nClick to copy to clipboard.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}");
} }
else else
{ {
ImGui.Selectable("(unavailable)", false, ImGuiSelectableFlags.Disabled, ImGui.Selectable("(unavailable)", false, ImGuiSelectableFlags.Disabled,
new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
ImGuiUtil.HoverTooltip($"The actual path to this file is unavailable.\nIt may be managed by another plug-in.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); ImGuiUtil.HoverTooltip(
$"The actual path to this file is unavailable.\nIt may be managed by another plug-in.{GetAdditionalDataSuffix(resourceNode.AdditionalData)}");
} }
mutedColor.Dispose(); mutedColor.Dispose();

View file

@ -1,7 +1,5 @@
using System.Text.Unicode;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
using Dalamud.Memory;
using ImGuiNET; using ImGuiNET;
using OtterGui.Services; using OtterGui.Services;
using OtterGui.Text; using OtterGui.Text;

View file

@ -18,8 +18,8 @@ public enum RecordType : byte
internal unsafe struct Record internal unsafe struct Record
{ {
public DateTime Time; public DateTime Time;
public ByteString Path; public CiByteString Path;
public ByteString OriginalPath; public CiByteString OriginalPath;
public string AssociatedGameObject; public string AssociatedGameObject;
public ModCollection? Collection; public ModCollection? Collection;
public ResourceHandle* Handle; public ResourceHandle* Handle;
@ -32,12 +32,12 @@ internal unsafe struct Record
public OptionalBool CustomLoad; public OptionalBool CustomLoad;
public LoadState LoadState; public LoadState LoadState;
public static Record CreateRequest(ByteString path, bool sync) public static Record CreateRequest(CiByteString path, bool sync)
=> new() => new()
{ {
Time = DateTime.UtcNow, Time = DateTime.UtcNow,
Path = path.IsOwned ? path : path.Clone(), Path = path.IsOwned ? path : path.Clone(),
OriginalPath = ByteString.Empty, OriginalPath = CiByteString.Empty,
Collection = null, Collection = null,
Handle = null, Handle = null,
ResourceType = ResourceExtensions.Type(path).ToFlag(), ResourceType = ResourceExtensions.Type(path).ToFlag(),
@ -51,7 +51,7 @@ internal unsafe struct Record
LoadState = LoadState.None, LoadState = LoadState.None,
}; };
public static Record CreateDefaultLoad(ByteString path, ResourceHandle* handle, ModCollection collection, string associatedGameObject) public static Record CreateDefaultLoad(CiByteString path, ResourceHandle* handle, ModCollection collection, string associatedGameObject)
{ {
path = path.IsOwned ? path : path.Clone(); path = path.IsOwned ? path : path.Clone();
return new Record return new Record
@ -73,7 +73,7 @@ internal unsafe struct Record
}; };
} }
public static Record CreateLoad(ByteString path, ByteString originalPath, ResourceHandle* handle, ModCollection collection, public static Record CreateLoad(CiByteString path, CiByteString originalPath, ResourceHandle* handle, ModCollection collection,
string associatedGameObject) string associatedGameObject)
=> new() => new()
{ {
@ -100,7 +100,7 @@ internal unsafe struct Record
{ {
Time = DateTime.UtcNow, Time = DateTime.UtcNow,
Path = path, Path = path,
OriginalPath = ByteString.Empty, OriginalPath = CiByteString.Empty,
Collection = null, Collection = null,
Handle = handle, Handle = handle,
ResourceType = handle->FileType.ToFlag(), ResourceType = handle->FileType.ToFlag(),
@ -115,12 +115,12 @@ internal unsafe struct Record
}; };
} }
public static Record CreateFileLoad(ByteString path, ResourceHandle* handle, bool ret, bool custom) public static Record CreateFileLoad(CiByteString path, ResourceHandle* handle, bool ret, bool custom)
=> new() => new()
{ {
Time = DateTime.UtcNow, Time = DateTime.UtcNow,
Path = path.IsOwned ? path : path.Clone(), Path = path.IsOwned ? path : path.Clone(),
OriginalPath = ByteString.Empty, OriginalPath = CiByteString.Empty,
Collection = null, Collection = null,
Handle = handle, Handle = handle,
ResourceType = handle->FileType.ToFlag(), ResourceType = handle->FileType.ToFlag(),

View file

@ -163,7 +163,7 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService
} }
} }
private bool FilterMatch(ByteString path, out string match) private bool FilterMatch(CiByteString path, out string match)
{ {
match = path.ToString(); match = path.ToString();
return _logFilter.Length == 0 || (_logRegex?.IsMatch(match) ?? false) || match.Contains(_logFilter, StringComparison.OrdinalIgnoreCase); return _logFilter.Length == 0 || (_logRegex?.IsMatch(match) ?? false) || match.Contains(_logFilter, StringComparison.OrdinalIgnoreCase);
@ -255,7 +255,7 @@ public sealed class ResourceWatcher : IDisposable, ITab, IUiService
_newRecords.Enqueue(record); _newRecords.Enqueue(record);
} }
private unsafe void OnFileLoaded(ResourceHandle* resource, ByteString path, bool success, bool custom, ReadOnlySpan<byte> _) private unsafe void OnFileLoaded(ResourceHandle* resource, CiByteString path, bool success, bool custom, ReadOnlySpan<byte> _)
{ {
if (_ephemeral.EnableResourceLogging && FilterMatch(path, out var match)) if (_ephemeral.EnableResourceLogging && FilterMatch(path, out var match))
Penumbra.Log.Information( Penumbra.Log.Information(

View file

@ -50,7 +50,7 @@ internal sealed class ResourceWatcherTable : Table<Record>
=> DrawByteString(item.Path, 280 * UiHelpers.Scale); => DrawByteString(item.Path, 280 * UiHelpers.Scale);
} }
private static unsafe void DrawByteString(ByteString path, float length) private static unsafe void DrawByteString(CiByteString path, float length)
{ {
Vector2 vec; Vector2 vec;
ImGuiNative.igCalcTextSize(&vec, path.Path, path.Path + path.Length, 0, 0); ImGuiNative.igCalcTextSize(&vec, path.Path, path.Path + path.Length, 0, 0);
@ -61,7 +61,7 @@ internal sealed class ResourceWatcherTable : Table<Record>
else else
{ {
var fileName = path.LastIndexOf((byte)'/'); var fileName = path.LastIndexOf((byte)'/');
ByteString shortPath; CiByteString shortPath;
if (fileName != -1) if (fileName != -1)
{ {
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * UiHelpers.Scale)); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * UiHelpers.Scale));

View file

@ -15,6 +15,7 @@ using Microsoft.Extensions.DependencyInjection;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Services; using OtterGui.Services;
using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.Api; using Penumbra.Api;
using Penumbra.Collections.Manager; using Penumbra.Collections.Manager;
@ -402,6 +403,33 @@ public class DebugTab : Window, ITab, IUiService
} }
} }
} }
using (var tree = ImUtf8.TreeNode("String Memory"u8))
{
if (tree)
{
using (ImUtf8.Group())
{
ImUtf8.Text("Currently Allocated Strings"u8);
ImUtf8.Text("Total Allocated Strings"u8);
ImUtf8.Text("Free'd Allocated Strings"u8);
ImUtf8.Text("Currently Allocated Bytes"u8);
ImUtf8.Text("Total Allocated Bytes"u8);
ImUtf8.Text("Free'd Allocated Bytes"u8);
}
ImGui.SameLine();
using (ImUtf8.Group())
{
ImUtf8.Text($"{PenumbraStringMemory.CurrentStrings}");
ImUtf8.Text($"{PenumbraStringMemory.AllocatedStrings}");
ImUtf8.Text($"{PenumbraStringMemory.FreedStrings}");
ImUtf8.Text($"{PenumbraStringMemory.CurrentBytes}");
ImUtf8.Text($"{PenumbraStringMemory.AllocatedBytes}");
ImUtf8.Text($"{PenumbraStringMemory.FreedBytes}");
}
}
}
} }
private void DrawPerformanceTab() private void DrawPerformanceTab()

View file

@ -4,6 +4,7 @@ using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Services; using OtterGui.Services;
using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Collections.Cache; using Penumbra.Collections.Cache;
@ -134,12 +135,12 @@ public class EffectiveTab(CollectionManager collectionManager, CollectionSelectH
{ {
var (path, name) = pair; var (path, name) = pair;
ImGui.TableNextColumn(); ImGui.TableNextColumn();
UiHelpers.CopyOnClickSelectable(path.Path); ImUtf8.CopyOnClickSelectable(path.Path.Span);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.PrintIcon(FontAwesomeIcon.LongArrowAltLeft); ImGuiUtil.PrintIcon(FontAwesomeIcon.LongArrowAltLeft);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
UiHelpers.CopyOnClickSelectable(name.Path.InternalName); ImUtf8.CopyOnClickSelectable(name.Path.InternalName.Span);
ImGuiUtil.HoverTooltip($"\nChanged by {name.Mod.Name}."); ImGuiUtil.HoverTooltip($"\nChanged by {name.Mod.Name}.");
} }