diff --git a/OtterGui b/OtterGui index a63f6735..4a9b71a9 160000 --- a/OtterGui +++ b/OtterGui @@ -1 +1 @@ -Subproject commit a63f6735cf4bed4f7502a022a10378607082b770 +Subproject commit 4a9b71a93e76aa5eed818542288329e34ec0dd89 diff --git a/Penumbra.Api b/Penumbra.Api index 3d6cee1a..af41b178 160000 --- a/Penumbra.Api +++ b/Penumbra.Api @@ -1 +1 @@ -Subproject commit 3d6cee1a11922ccd426f36060fd026bc1a698adf +Subproject commit af41b1787acef9df7dc83619fe81e63a36443ee5 diff --git a/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj b/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj index 1b1f0a28..abcb8e3d 100644 --- a/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj +++ b/Penumbra.CrashHandler/Penumbra.CrashHandler.csproj @@ -1,4 +1,4 @@ - + Exe diff --git a/Penumbra.GameData b/Penumbra.GameData index d889f9ef..73010350 160000 --- a/Penumbra.GameData +++ b/Penumbra.GameData @@ -1 +1 @@ -Subproject commit d889f9ef918514a46049725052d378b441915b00 +Subproject commit 73010350338ecd7b98ad85d127bed08d7d8718d4 diff --git a/Penumbra.String b/Penumbra.String index c8611a0c..878acce4 160000 --- a/Penumbra.String +++ b/Penumbra.String @@ -1 +1 @@ -Subproject commit c8611a0c546b6b2ec29214ab319fc2c38fe74793 +Subproject commit 878acce46e286867d6ef1f8ecedb390f7bac34fd diff --git a/Penumbra/Api/Api/PenumbraApi.cs b/Penumbra/Api/Api/PenumbraApi.cs index c4026c72..9e7eb964 100644 --- a/Penumbra/Api/Api/PenumbraApi.cs +++ b/Penumbra/Api/Api/PenumbraApi.cs @@ -17,7 +17,7 @@ public class PenumbraApi( UiApi ui) : IDisposable, IApiService, IPenumbraApi { public const int BreakingVersion = 5; - public const int FeatureVersion = 13; + public const int FeatureVersion = 11; public void Dispose() { diff --git a/Penumbra/Api/Api/RedrawApi.cs b/Penumbra/Api/Api/RedrawApi.cs index 08f1f9df..ec4de892 100644 --- a/Penumbra/Api/Api/RedrawApi.cs +++ b/Penumbra/Api/Api/RedrawApi.cs @@ -2,14 +2,11 @@ using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Plugin.Services; using OtterGui.Services; using Penumbra.Api.Enums; -using Penumbra.Collections; -using Penumbra.Collections.Manager; -using Penumbra.GameData.Interop; using Penumbra.Interop.Services; -namespace Penumbra.Api.Api; - -public class RedrawApi(RedrawService redrawService, IFramework framework, CollectionManager collections, ObjectManager objects, ApiHelpers helpers) : IPenumbraApiRedraw, IApiService +namespace Penumbra.Api.Api; + +public class RedrawApi(RedrawService redrawService, IFramework framework) : IPenumbraApiRedraw, IApiService { public void RedrawObject(int gameObjectIndex, RedrawType setting) { @@ -31,27 +28,9 @@ public class RedrawApi(RedrawService redrawService, IFramework framework, Collec framework.RunOnFrameworkThread(() => redrawService.RedrawAll(setting)); } - public void RedrawCollectionMembers(Guid collectionId, RedrawType setting) - { - - if (!collections.Storage.ById(collectionId, out var collection)) - collection = ModCollection.Empty; - framework.RunOnFrameworkThread(() => - { - foreach (var actor in objects.Objects) - { - helpers.AssociatedCollection(actor.ObjectIndex, out var modCollection); - if (collection == modCollection) - { - redrawService.RedrawObject(actor.ObjectIndex, setting); - } - } - }); - } - public event GameObjectRedrawnDelegate? GameObjectRedrawn { add => redrawService.GameObjectRedrawn += value; remove => redrawService.GameObjectRedrawn -= value; } -} +} diff --git a/Penumbra/Api/HttpApi.cs b/Penumbra/Api/HttpApi.cs index 79348a88..8f8b44f4 100644 --- a/Penumbra/Api/HttpApi.cs +++ b/Penumbra/Api/HttpApi.cs @@ -5,7 +5,6 @@ using EmbedIO.WebApi; using OtterGui.Services; using Penumbra.Api.Api; using Penumbra.Api.Enums; -using Penumbra.Mods.Settings; namespace Penumbra.Api; @@ -14,15 +13,13 @@ public class HttpApi : IDisposable, IApiService private partial class Controller : WebApiController { // @formatter:off - [Route( HttpVerbs.Get, "/moddirectory" )] public partial string GetModDirectory(); - [Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods(); - [Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw(); - [Route( HttpVerbs.Post, "/redrawAll" )] public partial Task RedrawAll(); - [Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod(); - [Route( HttpVerbs.Post, "/installmod" )] public partial Task InstallMod(); - [Route( HttpVerbs.Post, "/openwindow" )] public partial void OpenWindow(); - [Route( HttpVerbs.Post, "/focusmod" )] public partial Task FocusMod(); - [Route( HttpVerbs.Post, "/setmodsettings")] public partial Task SetModSettings(); + [Route( HttpVerbs.Get, "/mods" )] public partial object? GetMods(); + [Route( HttpVerbs.Post, "/redraw" )] public partial Task Redraw(); + [Route( HttpVerbs.Post, "/redrawAll" )] public partial Task RedrawAll(); + [Route( HttpVerbs.Post, "/reloadmod" )] public partial Task ReloadMod(); + [Route( HttpVerbs.Post, "/installmod" )] public partial Task InstallMod(); + [Route( HttpVerbs.Post, "/openwindow" )] public partial void OpenWindow(); + [Route( HttpVerbs.Post, "/focusmod" )] public partial Task FocusMod(); // @formatter:on } @@ -68,12 +65,6 @@ public class HttpApi : IDisposable, IApiService private partial class Controller(IPenumbraApi api, IFramework framework) { - public partial string GetModDirectory() - { - Penumbra.Log.Debug($"[HTTP] {nameof(GetModDirectory)} triggered."); - return api.PluginState.GetModDirectory(); - } - public partial object? GetMods() { Penumbra.Log.Debug($"[HTTP] {nameof(GetMods)} triggered."); @@ -125,7 +116,6 @@ public class HttpApi : IDisposable, IApiService Penumbra.Log.Debug($"[HTTP] {nameof(OpenWindow)} triggered."); api.Ui.OpenMainWindow(TabType.Mods, string.Empty, string.Empty); } - public async partial Task FocusMod() { var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false); @@ -134,30 +124,6 @@ public class HttpApi : IDisposable, IApiService api.Ui.OpenMainWindow(TabType.Mods, data.Path, data.Name); } - public async partial Task SetModSettings() - { - var data = await HttpContext.GetRequestDataAsync().ConfigureAwait(false); - Penumbra.Log.Debug($"[HTTP] {nameof(SetModSettings)} triggered."); - await framework.RunOnFrameworkThread(() => - { - var collection = data.CollectionId ?? api.Collection.GetCollection(ApiCollectionType.Current)!.Value.Id; - if (data.Inherit.HasValue) - { - api.ModSettings.TryInheritMod(collection, data.ModPath, data.ModName, data.Inherit.Value); - if (data.Inherit.Value) - return; - } - - if (data.State.HasValue) - api.ModSettings.TrySetMod(collection, data.ModPath, data.ModName, data.State.Value); - if (data.Priority.HasValue) - api.ModSettings.TrySetModPriority(collection, data.ModPath, data.ModName, data.Priority.Value); - foreach (var (group, settings) in data.Settings ?? []) - api.ModSettings.TrySetModSettings(collection, data.ModPath, data.ModName, group, settings); - } - ).ConfigureAwait(false); - } - private record ModReloadData(string Path, string Name) { public ModReloadData() @@ -185,19 +151,5 @@ public class HttpApi : IDisposable, IApiService : this(string.Empty, RedrawType.Redraw, -1) { } } - - private record SetModSettingsData( - Guid? CollectionId, - string ModPath, - string ModName, - bool? Inherit, - bool? State, - int? Priority, - Dictionary>? Settings) - { - public SetModSettingsData() - : this(null, string.Empty, string.Empty, null, null, null, null) - {} - } } } diff --git a/Penumbra/Api/IpcProviders.cs b/Penumbra/Api/IpcProviders.cs index 5f04540f..0c80626f 100644 --- a/Penumbra/Api/IpcProviders.cs +++ b/Penumbra/Api/IpcProviders.cs @@ -88,7 +88,6 @@ public sealed class IpcProviders : IDisposable, IApiService IpcSubscribers.RedrawObject.Provider(pi, api.Redraw), IpcSubscribers.RedrawAll.Provider(pi, api.Redraw), IpcSubscribers.GameObjectRedrawn.Provider(pi, api.Redraw), - IpcSubscribers.RedrawCollectionMembers.Provider(pi, api.Redraw), IpcSubscribers.ResolveDefaultPath.Provider(pi, api.Resolve), IpcSubscribers.ResolveInterfacePath.Provider(pi, api.Resolve), diff --git a/Penumbra/Api/IpcTester/CollectionsIpcTester.cs b/Penumbra/Api/IpcTester/CollectionsIpcTester.cs index f033b7c3..c06bdeb4 100644 --- a/Penumbra/Api/IpcTester/CollectionsIpcTester.cs +++ b/Penumbra/Api/IpcTester/CollectionsIpcTester.cs @@ -121,10 +121,6 @@ public class CollectionsIpcTester(IDalamudPluginInterface pi) : IUiService }).ToArray(); ImGui.OpenPopup("Changed Item List"); } - IpcTester.DrawIntro(RedrawCollectionMembers.Label, "Redraw Collection Members"); - if (ImGui.Button("Redraw##ObjectCollection")) - new RedrawCollectionMembers(pi).Invoke(collectionList[0].Id, RedrawType.Redraw); - } private void DrawChangedItemPopup() diff --git a/Penumbra/Configuration.cs b/Penumbra/Configuration.cs index 2991230e..f9cad217 100644 --- a/Penumbra/Configuration.cs +++ b/Penumbra/Configuration.cs @@ -53,7 +53,6 @@ public class Configuration : IPluginConfiguration, ISavable, IService public string ModDirectory { get; set; } = string.Empty; public string ExportDirectory { get; set; } = string.Empty; - public string WatchDirectory { get; set; } = string.Empty; public bool? UseCrashHandler { get; set; } = null; public bool OpenWindowAtStart { get; set; } = false; @@ -77,8 +76,6 @@ public class Configuration : IPluginConfiguration, ISavable, IService public bool HideRedrawBar { get; set; } = false; public bool HideMachinistOffhandFromChangedItems { get; set; } = true; public bool DefaultTemporaryMode { get; set; } = false; - public bool EnableDirectoryWatch { get; set; } = false; - public bool EnableAutomaticModImport { get; set; } = false; public bool EnableCustomShapes { get; set; } = true; public PcpSettings PcpSettings = new(); public RenameField ShowRename { get; set; } = RenameField.BothDataPrio; diff --git a/Penumbra/Interop/CloudApi.cs b/Penumbra/Interop/CloudApi.cs deleted file mode 100644 index 603d4c9f..00000000 --- a/Penumbra/Interop/CloudApi.cs +++ /dev/null @@ -1,47 +0,0 @@ -namespace Penumbra.Interop; - -public static unsafe partial class CloudApi -{ - private const int CfSyncRootInfoBasic = 0; - - /// Determines whether a file or directory is cloud-synced using OneDrive or other providers that use the Cloud API. - /// Can be expensive. Callers should cache the result when relevant. - public static bool IsCloudSynced(string path) - { - var buffer = stackalloc long[1]; - int hr; - uint length; - try - { - hr = CfGetSyncRootInfoByPath(path, CfSyncRootInfoBasic, buffer, sizeof(long), out length); - } - catch (DllNotFoundException) - { - Penumbra.Log.Debug($"{nameof(CfGetSyncRootInfoByPath)} threw DllNotFoundException"); - return false; - } - catch (EntryPointNotFoundException) - { - Penumbra.Log.Debug($"{nameof(CfGetSyncRootInfoByPath)} threw EntryPointNotFoundException"); - return false; - } - - Penumbra.Log.Debug($"{nameof(CfGetSyncRootInfoByPath)} returned HRESULT 0x{hr:X8}"); - if (hr < 0) - return false; - - if (length != sizeof(long)) - { - Penumbra.Log.Debug($"Expected {nameof(CfGetSyncRootInfoByPath)} to return {sizeof(long)} bytes, got {length} bytes"); - return false; - } - - Penumbra.Log.Debug($"{nameof(CfGetSyncRootInfoByPath)} returned {{ SyncRootFileId = 0x{*buffer:X16} }}"); - - return true; - } - - [LibraryImport("cldapi.dll", StringMarshalling = StringMarshalling.Utf16)] - private static partial int CfGetSyncRootInfoByPath(string filePath, int infoClass, void* infoBuffer, uint infoBufferLength, - out uint returnedLength); -} diff --git a/Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs b/Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs index cdd82b95..e0eb7ec5 100644 --- a/Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs +++ b/Penumbra/Interop/Hooks/Animation/LoadTimelineResources.cs @@ -63,7 +63,8 @@ public sealed unsafe class LoadTimelineResources : FastHookGetOwningGameObjectIndex(); + // TODO: Clientstructify + var idx = ((delegate* unmanaged**)timeline)[0][29](timeline); if (idx >= 0 && idx < objects.TotalCount) { var obj = objects[idx]; diff --git a/Penumbra/Interop/PathResolving/CollectionResolver.cs b/Penumbra/Interop/PathResolving/CollectionResolver.cs index 136393d4..10795e6d 100644 --- a/Penumbra/Interop/PathResolving/CollectionResolver.cs +++ b/Penumbra/Interop/PathResolving/CollectionResolver.cs @@ -137,7 +137,7 @@ public sealed unsafe class CollectionResolver( { var item = charaEntry.Value; var identifier = actors.CreatePlayer(new ByteString(item->Name), item->HomeWorldId); - Penumbra.Log.Excessive( + Penumbra.Log.Verbose( $"Identified {identifier.Incognito(null)} in cutscene for actor {idx + 200} at 0x{(ulong)gameObject:X} of race {(gameObject->IsCharacter() ? ((Character*)gameObject)->DrawData.CustomizeData.Race.ToString() : "Unknown")}."); if (identifier.IsValid && CollectionByIdentifier(identifier) is { } coll) { diff --git a/Penumbra/Interop/PathResolving/CutsceneService.cs b/Penumbra/Interop/PathResolving/CutsceneService.cs index 97e64f84..6be19c46 100644 --- a/Penumbra/Interop/PathResolving/CutsceneService.cs +++ b/Penumbra/Interop/PathResolving/CutsceneService.cs @@ -75,7 +75,6 @@ public sealed class CutsceneService : IRequiredService, IDisposable return false; _copiedCharacters[copyIdx - CutsceneStartIdx] = (short)parentIdx; - _objects.InvokeRequiredUpdates(); return true; } diff --git a/Penumbra/Interop/Processing/SkinMtrlPathEarlyProcessing.cs b/Penumbra/Interop/Processing/SkinMtrlPathEarlyProcessing.cs index bd066d83..6be1b959 100644 --- a/Penumbra/Interop/Processing/SkinMtrlPathEarlyProcessing.cs +++ b/Penumbra/Interop/Processing/SkinMtrlPathEarlyProcessing.cs @@ -38,9 +38,10 @@ public static unsafe class SkinMtrlPathEarlyProcessing if (character is null) return null; - if (character->PerSlotStagingArea is not null) + if (character->TempSlotData is not null) { - var handle = character->PerSlotStagingArea[slotIndex].ModelResourceHandle; + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1564) + var handle = *(ModelResourceHandle**)((nint)character->TempSlotData + 0xE0 * slotIndex + 0x8); if (handle != null) return handle; } diff --git a/Penumbra/Interop/ResourceTree/ResourceTree.cs b/Penumbra/Interop/ResourceTree/ResourceTree.cs index 1ebfe53d..23fe26b8 100644 --- a/Penumbra/Interop/ResourceTree/ResourceTree.cs +++ b/Penumbra/Interop/ResourceTree/ResourceTree.cs @@ -242,10 +242,10 @@ public class ResourceTree( } private unsafe void AddSkeleton(List nodes, ResolveContext context, CharacterBase* model, string prefix = "") - => AddSkeleton(nodes, context, model->EID, model->Skeleton, model->BonePhysicsModule, model->BoneKineDriverModule, prefix); + => AddSkeleton(nodes, context, model->EID, model->Skeleton, model->BonePhysicsModule, *(void**)((nint)model + 0x160), prefix); private unsafe void AddSkeleton(List nodes, ResolveContext context, void* eid, Skeleton* skeleton, BonePhysicsModule* physics, - BoneKineDriverModule* kineDriver, string prefix = "") + void* kineDriver, string prefix = "") { var eidNode = context.CreateNodeFromEid((ResourceHandle*)eid); if (eidNode != null) @@ -261,7 +261,8 @@ public class ResourceTree( for (var i = 0; i < skeleton->PartialSkeletonCount; ++i) { var phybHandle = physics != null ? physics->BonePhysicsResourceHandles[i] : null; - var kdbHandle = kineDriver != null ? kineDriver->PartialSkeletonEntries[i].KineDriverResourceHandle : null; + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1562) + var kdbHandle = kineDriver != null ? *(ResourceHandle**)((nint)kineDriver + 0x20 + 0x18 * i) : null; if (context.CreateNodeFromPartialSkeleton(&skeleton->PartialSkeletons[i], phybHandle, kdbHandle, (uint)i) is { } sklbNode) { if (context.Global.WithUiData) diff --git a/Penumbra/Interop/Services/RedrawService.cs b/Penumbra/Interop/Services/RedrawService.cs index 2d741277..08e9ddf5 100644 --- a/Penumbra/Interop/Services/RedrawService.cs +++ b/Penumbra/Interop/Services/RedrawService.cs @@ -421,9 +421,9 @@ public sealed unsafe partial class RedrawService : IDisposable return; - foreach (ref var f in currentTerritory->FurnitureManager.FurnitureMemory) + foreach (ref var f in currentTerritory->Furniture) { - var gameObject = f.Index >= 0 ? currentTerritory->FurnitureManager.ObjectManager.ObjectArray.Objects[f.Index].Value : null; + var gameObject = f.Index >= 0 ? currentTerritory->HousingObjectManager.Objects[f.Index].Value : null; if (gameObject == null) continue; diff --git a/Penumbra/Interop/Structs/StructExtensions.cs b/Penumbra/Interop/Structs/StructExtensions.cs index 7349f6cc..5a29bb6f 100644 --- a/Penumbra/Interop/Structs/StructExtensions.cs +++ b/Penumbra/Interop/Structs/StructExtensions.cs @@ -66,8 +66,11 @@ internal static class StructExtensions public static unsafe CiByteString ResolveKdbPathAsByteString(ref this CharacterBase character, uint partialSkeletonIndex) { + // TODO ClientStructs-ify (aers/FFXIVClientStructs#1561) + var vf80 = (delegate* unmanaged)((nint*)character.VirtualTable)[80]; var pathBuffer = stackalloc byte[CharacterBase.PathBufferSize]; - return ToOwnedByteString(character.ResolveKdbPath(pathBuffer, CharacterBase.PathBufferSize, partialSkeletonIndex)); + return ToOwnedByteString(vf80((CharacterBase*)Unsafe.AsPointer(ref character), pathBuffer, CharacterBase.PathBufferSize, + partialSkeletonIndex)); } private static unsafe CiByteString ToOwnedByteString(CStringPointer str) diff --git a/Penumbra/Mods/Editor/ModMerger.cs b/Penumbra/Mods/Editor/ModMerger.cs index eb270e13..bb84173a 100644 --- a/Penumbra/Mods/Editor/ModMerger.cs +++ b/Penumbra/Mods/Editor/ModMerger.cs @@ -372,6 +372,7 @@ public class ModMerger : IDisposable, IService } else { + // TODO DataContainer <> Option. var (group, _, _) = _editor.FindOrAddModGroup(result, originalGroup.Type, originalGroup.Name); var (option, _, _) = _editor.FindOrAddOption(group!, originalOption.GetName()); var folder = Path.Combine(dir.FullName, group!.Name, option!.Name); diff --git a/Penumbra/Mods/Manager/ModManager.cs b/Penumbra/Mods/Manager/ModManager.cs index 77385bbd..32dac049 100644 --- a/Penumbra/Mods/Manager/ModManager.cs +++ b/Penumbra/Mods/Manager/ModManager.cs @@ -1,6 +1,5 @@ using OtterGui.Services; using Penumbra.Communication; -using Penumbra.Interop; using Penumbra.Mods.Editor; using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Services; @@ -304,9 +303,6 @@ public sealed class ModManager : ModStorage, IDisposable, IService if (!firstTime && _config.ModDirectory != BasePath.FullName) TriggerModDirectoryChange(BasePath.FullName, Valid); } - - if (CloudApi.IsCloudSynced(BasePath.FullName)) - Penumbra.Log.Warning($"Mod base directory {BasePath.FullName} is cloud-synced. This may cause issues."); } private void TriggerModDirectoryChange(string newPath, bool valid) diff --git a/Penumbra/Penumbra.cs b/Penumbra/Penumbra.cs index d433a0fb..b22d049d 100644 --- a/Penumbra/Penumbra.cs +++ b/Penumbra/Penumbra.cs @@ -21,8 +21,8 @@ using Penumbra.UI; using ResidentResourceManager = Penumbra.Interop.Services.ResidentResourceManager; using Dalamud.Plugin.Services; using Lumina.Excel.Sheets; +using Penumbra.GameData; using Penumbra.GameData.Data; -using Penumbra.Interop; using Penumbra.Interop.Hooks; using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.ResourceLoading; @@ -211,11 +211,10 @@ public class Penumbra : IDalamudPlugin public string GatherSupportInformation() { - var sb = new StringBuilder(10240); - var exists = _config.ModDirectory.Length > 0 && Directory.Exists(_config.ModDirectory); - var cloudSynced = exists && CloudApi.IsCloudSynced(_config.ModDirectory); - var hdrEnabler = _services.GetService(); - var drive = exists ? new DriveInfo(new DirectoryInfo(_config.ModDirectory).Root.FullName) : null; + var sb = new StringBuilder(10240); + var exists = _config.ModDirectory.Length > 0 && Directory.Exists(_config.ModDirectory); + var hdrEnabler = _services.GetService(); + var drive = exists ? new DriveInfo(new DirectoryInfo(_config.ModDirectory).Root.FullName) : null; sb.AppendLine("**Settings**"); sb.Append($"> **`Plugin Version: `** {_validityChecker.Version}\n"); sb.Append($"> **`Commit Hash: `** {_validityChecker.CommitHash}\n"); @@ -224,8 +223,7 @@ public class Penumbra : IDalamudPlugin sb.Append($"> **`Operating System: `** {(Dalamud.Utility.Util.IsWine() ? "Mac/Linux (Wine)" : "Windows")}\n"); if (Dalamud.Utility.Util.IsWine()) sb.Append($"> **`Locale Environment Variables:`** {CollectLocaleEnvironmentVariables()}\n"); - sb.Append( - $"> **`Root Directory: `** `{_config.ModDirectory}`, {(exists ? "Exists" : "Not Existing")}{(cloudSynced ? ", Cloud-Synced" : "")}\n"); + sb.Append($"> **`Root Directory: `** `{_config.ModDirectory}`, {(exists ? "Exists" : "Not Existing")}\n"); sb.Append( $"> **`Free Drive Space: `** {(drive != null ? Functions.HumanReadableSize(drive.AvailableFreeSpace) : "Unknown")}\n"); sb.Append($"> **`Game Data Files: `** {(_gameData.HasModifiedGameDataFiles ? "Modified" : "Pristine")}\n"); diff --git a/Penumbra/Penumbra.csproj b/Penumbra/Penumbra.csproj index fa45ffbf..3159b736 100644 --- a/Penumbra/Penumbra.csproj +++ b/Penumbra/Penumbra.csproj @@ -1,4 +1,4 @@ - + Penumbra absolute gangstas diff --git a/Penumbra/Penumbra.json b/Penumbra/Penumbra.json index 32032282..bd9a2479 100644 --- a/Penumbra/Penumbra.json +++ b/Penumbra/Penumbra.json @@ -1,5 +1,5 @@ { - "Author": "Ottermandias, Nylfae, Adam, Wintermute", + "Author": "Ottermandias, Adam, Wintermute", "Name": "Penumbra", "Punchline": "Runtime mod loader and manager.", "Description": "Runtime mod loader and manager.", diff --git a/Penumbra/Services/FileWatcher.cs b/Penumbra/Services/FileWatcher.cs deleted file mode 100644 index 1d572f05..00000000 --- a/Penumbra/Services/FileWatcher.cs +++ /dev/null @@ -1,209 +0,0 @@ -using OtterGui.Services; -using Penumbra.Mods.Manager; - -namespace Penumbra.Services; - -public class FileWatcher : IDisposable, IService -{ - // TODO: use ConcurrentSet when it supports comparers in Luna. - private readonly ConcurrentDictionary _pending = new(StringComparer.OrdinalIgnoreCase); - private readonly ModImportManager _modImportManager; - private readonly MessageService _messageService; - private readonly Configuration _config; - - private bool _pausedConsumer; - private FileSystemWatcher? _fsw; - private CancellationTokenSource? _cts = new(); - private Task? _consumer; - - public FileWatcher(ModImportManager modImportManager, MessageService messageService, Configuration config) - { - _modImportManager = modImportManager; - _messageService = messageService; - _config = config; - - if (_config.EnableDirectoryWatch) - { - SetupFileWatcher(_config.WatchDirectory); - SetupConsumerTask(); - } - } - - public void Toggle(bool value) - { - if (_config.EnableDirectoryWatch == value) - return; - - _config.EnableDirectoryWatch = value; - _config.Save(); - if (value) - { - SetupFileWatcher(_config.WatchDirectory); - SetupConsumerTask(); - } - else - { - EndFileWatcher(); - EndConsumerTask(); - } - } - - internal void PauseConsumer(bool pause) - => _pausedConsumer = pause; - - private void EndFileWatcher() - { - if (_fsw is null) - return; - - _fsw.Dispose(); - _fsw = null; - } - - private void SetupFileWatcher(string directory) - { - EndFileWatcher(); - _fsw = new FileSystemWatcher - { - IncludeSubdirectories = false, - NotifyFilter = NotifyFilters.FileName | NotifyFilters.CreationTime, - InternalBufferSize = 32 * 1024, - }; - - // Only wake us for the exact patterns we care about - _fsw.Filters.Add("*.pmp"); - _fsw.Filters.Add("*.pcp"); - _fsw.Filters.Add("*.ttmp"); - _fsw.Filters.Add("*.ttmp2"); - - _fsw.Created += OnPath; - _fsw.Renamed += OnPath; - UpdateDirectory(directory); - } - - - private void EndConsumerTask() - { - if (_cts is not null) - { - _cts.Cancel(); - _cts = null; - } - _consumer = null; - } - - private void SetupConsumerTask() - { - EndConsumerTask(); - _cts = new CancellationTokenSource(); - _consumer = Task.Factory.StartNew( - () => ConsumerLoopAsync(_cts.Token), - _cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap(); - } - - public void UpdateDirectory(string newPath) - { - if (_config.WatchDirectory != newPath) - { - _config.WatchDirectory = newPath; - _config.Save(); - } - - if (_fsw is null) - return; - - _fsw.EnableRaisingEvents = false; - if (!Directory.Exists(newPath) || newPath.Length is 0) - { - _fsw.Path = string.Empty; - } - else - { - _fsw.Path = newPath; - _fsw.EnableRaisingEvents = true; - } - } - - private void OnPath(object? sender, FileSystemEventArgs e) - => _pending.TryAdd(e.FullPath, 0); - - private async Task ConsumerLoopAsync(CancellationToken token) - { - while (true) - { - var (path, _) = _pending.FirstOrDefault(); - if (path is null || _pausedConsumer) - { - await Task.Delay(500, token).ConfigureAwait(false); - continue; - } - - try - { - await ProcessOneAsync(path, token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - Penumbra.Log.Debug("[FileWatcher] Canceled via Token."); - } - catch (Exception ex) - { - Penumbra.Log.Warning($"[FileWatcher] Error during Processing: {ex}"); - } - finally - { - _pending.TryRemove(path, out _); - } - } - } - - private async Task ProcessOneAsync(string path, CancellationToken token) - { - // Downloads often finish via rename; file may be locked briefly. - // Wait until it exists and is readable; also require two stable size checks. - const int maxTries = 40; - long lastLen = -1; - - for (var i = 0; i < maxTries && !token.IsCancellationRequested; i++) - { - if (!File.Exists(path)) - { - await Task.Delay(100, token); - continue; - } - - try - { - var fi = new FileInfo(path); - var len = fi.Length; - if (len > 0 && len == lastLen) - { - if (_config.EnableAutomaticModImport) - _modImportManager.AddUnpack(path); - else - _messageService.AddMessage(new InstallNotification(_modImportManager, path), false); - return; - } - - lastLen = len; - } - catch (IOException) - { - Penumbra.Log.Debug($"[FileWatcher] File is still being written to."); - } - catch (UnauthorizedAccessException) - { - Penumbra.Log.Debug($"[FileWatcher] File is locked."); - } - - await Task.Delay(150, token); - } - } - - - public void Dispose() - { - EndConsumerTask(); - EndFileWatcher(); - } -} diff --git a/Penumbra/Services/InstallNotification.cs b/Penumbra/Services/InstallNotification.cs deleted file mode 100644 index e3956076..00000000 --- a/Penumbra/Services/InstallNotification.cs +++ /dev/null @@ -1,39 +0,0 @@ -using Dalamud.Bindings.ImGui; -using Dalamud.Interface.ImGuiNotification; -using Dalamud.Interface.ImGuiNotification.EventArgs; -using OtterGui.Text; -using Penumbra.Mods.Manager; - -namespace Penumbra.Services; - -public class InstallNotification(ModImportManager modImportManager, string filePath) : OtterGui.Classes.MessageService.IMessage -{ - public string Message - => "A new mod has been found!"; - - public NotificationType NotificationType - => NotificationType.Info; - - public uint NotificationDuration - => uint.MaxValue; - - public string NotificationTitle { get; } = Path.GetFileNameWithoutExtension(filePath); - - public string LogMessage - => $"A new mod has been found: {Path.GetFileName(filePath)}"; - - public void OnNotificationActions(INotificationDrawArgs args) - { - var region = ImGui.GetContentRegionAvail(); - var buttonSize = new Vector2((region.X - ImGui.GetStyle().ItemSpacing.X) / 2, 0); - if (ImUtf8.ButtonEx("Install"u8, ""u8, buttonSize)) - { - modImportManager.AddUnpack(filePath); - args.Notification.DismissNow(); - } - - ImGui.SameLine(); - if (ImUtf8.ButtonEx("Ignore"u8, ""u8, buttonSize)) - args.Notification.DismissNow(); - } -} diff --git a/Penumbra/Services/PcpService.cs b/Penumbra/Services/PcpService.cs index 17646564..63b8eab3 100644 --- a/Penumbra/Services/PcpService.cs +++ b/Penumbra/Services/PcpService.cs @@ -82,9 +82,9 @@ public class PcpService : IApiService, IDisposable public void CleanPcpCollections() { var collections = _collections.Storage.Where(c => c.Identity.Name.StartsWith("PCP/")).ToList(); - Penumbra.Log.Information($"[PCPService] Deleting {collections.Count} collections starting with PCP/."); + Penumbra.Log.Information($"[PCPService] Deleting {collections.Count} mods containing the tag PCP."); foreach (var collection in collections) - _collections.Storage.RemoveCollection(collection); + _collections.Storage.Delete(collection); } private void OnModPathChange(ModPathChangeType type, Mod mod, DirectoryInfo? oldDirectory, DirectoryInfo? newDirectory) diff --git a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.cs b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.cs index 2c7c889e..e15d1c90 100644 --- a/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.cs +++ b/Penumbra/UI/AdvancedWindow/Materials/MtrlTab.cs @@ -216,7 +216,7 @@ public sealed partial class MtrlTab : IWritable, IDisposable } public bool Valid - => Mtrl.Valid; // FIXME This should be _shadersKnown && Mtrl.Valid but the algorithm for _shadersKnown is flawed as of 7.2. + => _shadersKnown && Mtrl.Valid; public byte[] Write() { diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs index 63c99b8a..87d7487b 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.Files.cs @@ -287,17 +287,6 @@ public partial class ModEditWindow using var font = ImRaii.PushFont(UiBuilder.IconFont); ImGuiUtil.TextColored(0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString()); } - else if (tmp.Length > 0 && Path.GetExtension(tmp) != registry.File.Extension) - { - ImGui.SameLine(); - ImGui.SetCursorPosX(pos); - using (var font = ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGuiUtil.TextColored(0xFF00B0B0, FontAwesomeIcon.ExclamationCircle.ToIconString()); - } - - ImUtf8.HoverTooltip("The game path and the file do not have the same extension."u8); - } } private void PrintNewGamePath(int i, FileRegistry registry, IModDataContainer subMod) @@ -330,17 +319,6 @@ public partial class ModEditWindow using var font = ImRaii.PushFont(UiBuilder.IconFont); ImGuiUtil.TextColored(0xFF0000FF, FontAwesomeIcon.TimesCircle.ToIconString()); } - else if (tmp.Length > 0 && Path.GetExtension(tmp) != registry.File.Extension) - { - ImGui.SameLine(); - ImGui.SetCursorPosX(pos); - using (var font = ImRaii.PushFont(UiBuilder.IconFont)) - { - ImGuiUtil.TextColored(0xFF00B0B0, FontAwesomeIcon.ExclamationCircle.ToIconString()); - } - - ImUtf8.HoverTooltip("The game path and the file do not have the same extension."u8); - } } private void DrawButtonHeader() diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs index f55ae576..72350857 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.QuickImport.cs @@ -17,6 +17,7 @@ public partial class ModEditWindow private readonly FileDialogService _fileDialog; private readonly ResourceTreeFactory _resourceTreeFactory; private readonly ResourceTreeViewer _quickImportViewer; + private readonly Dictionary _quickImportWritables = new(); private readonly Dictionary<(Utf8GamePath, IWritable?), QuickImportAction> _quickImportActions = new(); private HashSet GetPlayerResourcesOfType(ResourceType type) @@ -55,11 +56,52 @@ public partial class ModEditWindow private void OnQuickImportRefresh() { + _quickImportWritables.Clear(); _quickImportActions.Clear(); } - private void DrawQuickImportActions(ResourceNode resourceNode, IWritable? writable, Vector2 buttonSize) + private void DrawQuickImportActions(ResourceNode resourceNode, Vector2 buttonSize) { + if (!_quickImportWritables!.TryGetValue(resourceNode.FullPath, out var writable)) + { + var path = resourceNode.FullPath.ToPath(); + if (resourceNode.FullPath.IsRooted) + { + writable = new RawFileWritable(path); + } + else + { + var file = _gameData.GetFile(path); + writable = file is null ? null : new RawGameFileWritable(file); + } + + _quickImportWritables.Add(resourceNode.FullPath, writable); + } + + if (ImUtf8.IconButton(FontAwesomeIcon.Save, "Export this file."u8, buttonSize, + resourceNode.FullPath.FullName.Length is 0 || writable is null)) + { + var fullPathStr = resourceNode.FullPath.FullName; + var ext = resourceNode.PossibleGamePaths.Length == 1 + ? Path.GetExtension(resourceNode.GamePath.ToString()) + : Path.GetExtension(fullPathStr); + _fileDialog.OpenSavePicker($"Export {Path.GetFileName(fullPathStr)} to...", ext, Path.GetFileNameWithoutExtension(fullPathStr), ext, + (success, name) => + { + if (!success) + return; + + try + { + _editor.Compactor.WriteAllBytes(name, writable!.Write()); + } + catch (Exception e) + { + Penumbra.Log.Error($"Could not export {fullPathStr}:\n{e}"); + } + }, null, false); + } + ImGui.SameLine(); if (!_quickImportActions!.TryGetValue((resourceNode.GamePath, writable), out var quickImport)) { @@ -79,6 +121,24 @@ public partial class ModEditWindow } } + private record RawFileWritable(string Path) : IWritable + { + public bool Valid + => true; + + public byte[] Write() + => File.ReadAllBytes(Path); + } + + private record RawGameFileWritable(FileResource FileResource) : IWritable + { + public bool Valid + => true; + + public byte[] Write() + => FileResource.Data; + } + public class QuickImportAction { public const string FallbackOptionName = "the current option"; diff --git a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs index 5a0fb849..952d8489 100644 --- a/Penumbra/UI/AdvancedWindow/ModEditWindow.cs +++ b/Penumbra/UI/AdvancedWindow/ModEditWindow.cs @@ -667,7 +667,7 @@ public partial class ModEditWindow : Window, IDisposable, IUiService _center = new CombinedTexture(_left, _right); _textureSelectCombo = new TextureDrawer.PathSelectCombo(textures, editor, () => GetPlayerResourcesOfType(ResourceType.Tex)); _resourceTreeFactory = resourceTreeFactory; - _quickImportViewer = resourceTreeViewerFactory.Create(1, OnQuickImportRefresh, DrawQuickImportActions); + _quickImportViewer = resourceTreeViewerFactory.Create(2, OnQuickImportRefresh, DrawQuickImportActions); _communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModEditWindow); IsOpen = _config is { OpenWindowAtStart: true, Ephemeral.AdvancedEditingOpen: true }; if (IsOpen && selection.Mod != null) diff --git a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs index ae450bec..617ba30f 100644 --- a/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs +++ b/Penumbra/UI/AdvancedWindow/ResourceTreeViewer.cs @@ -4,20 +4,16 @@ using Dalamud.Interface.Colors; using Dalamud.Interface.ImGuiNotification; using Dalamud.Interface.Utility; using Dalamud.Plugin.Services; -using Lumina.Data; using OtterGui; using OtterGui.Classes; -using OtterGui.Compression; using OtterGui.Extensions; using OtterGui.Raii; using OtterGui.Text; using Penumbra.Api.Enums; -using Penumbra.GameData.Files; using Penumbra.GameData.Structs; using Penumbra.Interop.ResourceTree; using Penumbra.Services; using Penumbra.String; -using Penumbra.String.Classes; using Penumbra.UI.Classes; namespace Penumbra.UI.AdvancedWindow; @@ -29,20 +25,17 @@ public class ResourceTreeViewer( IncognitoService incognito, int actionCapacity, Action onRefresh, - Action drawActions, + Action drawActions, CommunicatorService communicator, PcpService pcpService, - IDataManager gameData, - FileDialogService fileDialog, - FileCompactor compactor) + IDataManager gameData) { private const ResourceTreeFactory.Flags ResourceTreeFactoryFlags = - ResourceTreeFactory.Flags.WithUiData | ResourceTreeFactory.Flags.WithOwnership; + ResourceTreeFactory.Flags.RedactExternalPaths | ResourceTreeFactory.Flags.WithUiData | ResourceTreeFactory.Flags.WithOwnership; private readonly HashSet _unfolded = []; - private readonly Dictionary _filterCache = []; - private readonly Dictionary _writableCache = []; + private readonly Dictionary _filterCache = []; private TreeCategory _categoryFilter = AllCategories; private ChangedItemIconFlag _typeFilter = ChangedItemFlagExtensions.AllFlags; @@ -122,7 +115,7 @@ public class ResourceTreeViewer( ImUtf8.InputText("##note"u8, ref _note, "Export note..."u8); - using var table = ImRaii.Table("##ResourceTree", 4, + using var table = ImRaii.Table("##ResourceTree", actionCapacity > 0 ? 4 : 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); if (!table) continue; @@ -130,8 +123,9 @@ public class ResourceTreeViewer( ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthStretch, 0.2f); ImGui.TableSetupColumn("Game Path", ImGuiTableColumnFlags.WidthStretch, 0.3f); ImGui.TableSetupColumn("Actual Path", ImGuiTableColumnFlags.WidthStretch, 0.5f); - ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, - actionCapacity * 3 * ImGuiHelpers.GlobalScale + (actionCapacity + 1) * ImGui.GetFrameHeight()); + if (actionCapacity > 0) + ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed, + (actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + actionCapacity * ImGui.GetFrameHeight()); ImGui.TableHeadersRow(); DrawNodes(tree.Nodes, 0, unchecked(tree.DrawObjectAddress * 31), 0); @@ -217,7 +211,6 @@ public class ResourceTreeViewer( finally { _filterCache.Clear(); - _writableCache.Clear(); _unfolded.Clear(); onRefresh(); } @@ -228,6 +221,7 @@ public class ResourceTreeViewer( { var debugMode = config.DebugMode; var frameHeight = ImGui.GetFrameHeight(); + var cellHeight = actionCapacity > 0 ? frameHeight : 0.0f; foreach (var (resourceNode, index) in resourceNodes.WithIndex()) { @@ -297,7 +291,7 @@ public class ResourceTreeViewer( 0 => "(none)", 1 => resourceNode.GamePath.ToString(), _ => "(multiple)", - }, false, hasGamePaths ? 0 : ImGuiSelectableFlags.Disabled, new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); + }, false, hasGamePaths ? 0 : ImGuiSelectableFlags.Disabled, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); if (hasGamePaths) { var allPaths = string.Join('\n', resourceNode.PossibleGamePaths); @@ -318,29 +312,17 @@ public class ResourceTreeViewer( using (var color = ImRaii.PushColor(ImGuiCol.Text, (hasMod ? ColorId.NewMod : ColorId.DisabledMod).Value())) { ImUtf8.Selectable(modName, false, ImGuiSelectableFlags.AllowItemOverlap, - new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); + new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); } ImGui.SameLine(); ImGui.SetCursorPosX(textPos); ImUtf8.Text(resourceNode.ModRelativePath); } - else if (resourceNode.FullPath.IsRooted) - { - var path = resourceNode.FullPath.FullName; - var lastDirectorySeparator = path.LastIndexOf('\\'); - var secondLastDirectorySeparator = lastDirectorySeparator > 0 - ? path.LastIndexOf('\\', lastDirectorySeparator - 1) - : -1; - if (secondLastDirectorySeparator >= 0) - path = $"…{path.AsSpan(secondLastDirectorySeparator)}"; - ImGui.Selectable(path.AsSpan(), false, ImGuiSelectableFlags.AllowItemOverlap, - new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); - } else { ImGui.Selectable(resourceNode.FullPath.ToPath(), false, ImGuiSelectableFlags.AllowItemOverlap, - new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); + new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); } if (ImGui.IsItemClicked()) @@ -354,17 +336,20 @@ public class ResourceTreeViewer( else { ImUtf8.Selectable(GetPathStatusLabel(resourceNode.FullPathStatus), false, ImGuiSelectableFlags.Disabled, - new Vector2(ImGui.GetContentRegionAvail().X, frameHeight)); + new Vector2(ImGui.GetContentRegionAvail().X, cellHeight)); ImGuiUtil.HoverTooltip( $"{GetPathStatusDescription(resourceNode.FullPathStatus)}{GetAdditionalDataSuffix(resourceNode.AdditionalData)}"); } mutedColor.Dispose(); - ImGui.TableNextColumn(); - using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, - ImGui.GetStyle().ItemSpacing with { X = 3 * ImGuiHelpers.GlobalScale }); - DrawActions(resourceNode, new Vector2(frameHeight)); + if (actionCapacity > 0) + { + ImGui.TableNextColumn(); + using var spacing = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, + ImGui.GetStyle().ItemSpacing with { X = 3 * ImGuiHelpers.GlobalScale }); + drawActions(resourceNode, new Vector2(frameHeight)); + } if (unfolded) DrawNodes(resourceNode.Children, level + 1, unchecked(nodePathHash * 31), filterIcon); @@ -417,51 +402,6 @@ public class ResourceTreeViewer( || node.FullPath.InternalName.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase) || Array.Exists(node.PossibleGamePaths, path => path.Path.ToString().Contains(_nodeFilter, StringComparison.OrdinalIgnoreCase)); } - - void DrawActions(ResourceNode resourceNode, Vector2 buttonSize) - { - if (!_writableCache!.TryGetValue(resourceNode.FullPath, out var writable)) - { - var path = resourceNode.FullPath.ToPath(); - if (resourceNode.FullPath.IsRooted) - { - writable = new RawFileWritable(path); - } - else - { - var file = gameData.GetFile(path); - writable = file is null ? null : new RawGameFileWritable(file); - } - - _writableCache.Add(resourceNode.FullPath, writable); - } - - if (ImUtf8.IconButton(FontAwesomeIcon.Save, "Export this file."u8, buttonSize, - resourceNode.FullPath.FullName.Length is 0 || writable is null)) - { - var fullPathStr = resourceNode.FullPath.FullName; - var ext = resourceNode.PossibleGamePaths.Length == 1 - ? Path.GetExtension(resourceNode.GamePath.ToString()) - : Path.GetExtension(fullPathStr); - fileDialog.OpenSavePicker($"Export {Path.GetFileName(fullPathStr)} to...", ext, Path.GetFileNameWithoutExtension(fullPathStr), ext, - (success, name) => - { - if (!success) - return; - - try - { - compactor.WriteAllBytes(name, writable!.Write()); - } - catch (Exception e) - { - Penumbra.Log.Error($"Could not export {fullPathStr}:\n{e}"); - } - }, null, false); - } - - drawActions(resourceNode, writable, new Vector2(frameHeight)); - } } private static ReadOnlySpan GetPathStatusLabel(ResourceNode.PathStatus status) @@ -525,22 +465,4 @@ public class ResourceTreeViewer( Visible = 1, DescendentsOnly = 2, } - - private record RawFileWritable(string Path) : IWritable - { - public bool Valid - => true; - - public byte[] Write() - => File.ReadAllBytes(Path); - } - - private record RawGameFileWritable(FileResource FileResource) : IWritable - { - public bool Valid - => true; - - public byte[] Write() - => FileResource.Data; - } } diff --git a/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs b/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs index 6518ae67..43b60716 100644 --- a/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs +++ b/Penumbra/UI/AdvancedWindow/ResourceTreeViewerFactory.cs @@ -1,7 +1,5 @@ using Dalamud.Plugin.Services; -using OtterGui.Compression; using OtterGui.Services; -using Penumbra.GameData.Files; using Penumbra.Interop.ResourceTree; using Penumbra.Services; @@ -14,11 +12,8 @@ public class ResourceTreeViewerFactory( IncognitoService incognito, CommunicatorService communicator, PcpService pcpService, - IDataManager gameData, - FileDialogService fileDialog, - FileCompactor compactor) : IService + IDataManager gameData) : IService { - public ResourceTreeViewer Create(int actionCapacity, Action onRefresh, Action drawActions) - => new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions, communicator, pcpService, gameData, - fileDialog, compactor); + public ResourceTreeViewer Create(int actionCapacity, Action onRefresh, Action drawActions) + => new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions, communicator, pcpService, gameData); } diff --git a/Penumbra/UI/Changelog.cs b/Penumbra/UI/Changelog.cs index 306dcc79..4b487104 100644 --- a/Penumbra/UI/Changelog.cs +++ b/Penumbra/UI/Changelog.cs @@ -63,27 +63,9 @@ public class PenumbraChangelog : IUiService Add1_3_6_4(Changelog); Add1_4_0_0(Changelog); Add1_5_0_0(Changelog); - Add1_5_1_0(Changelog); - } - - #region Changelogs + } - private static void Add1_5_1_0(Changelog log) - => log.NextVersion("Version 1.5.1.0") - .RegisterHighlight("Added the option to export a characters current data as a .pcp modpack in the On-Screen tab.") - .RegisterEntry("Other plugins can attach to this functionality and package and interpret their own data.", 1) - .RegisterEntry("When a .pcp modpack is installed, it can create and assign collections for the corresponding character it was created for.", 1) - .RegisterEntry("This basically provides an easier way to manually synchronize other players, but does not contain any automation.", 1) - .RegisterEntry("The settings provide some fine control about what happens when a PCP is installed, as well as buttons to cleanup any PCP-created data.", 1) - .RegisterEntry("Added a warning message when the game's integrity is corrupted to the On-Screen tab.") - .RegisterEntry("Added .kdb files to the On-Screen tab and associated functionality (thanks Ny!).") - .RegisterEntry("Updated the creation of temporary collections to require a passed identity.") - .RegisterEntry("Added the option to change the skin material suffix in models using the stockings shader by adding specific attributes (thanks Ny!).") - .RegisterEntry("Added predefined tag utility to the multi-mod selection.") - .RegisterEntry("Fixed an issue with the automatic collection selection on character login when no mods are assigned.") - .RegisterImportant( - "Fixed issue with new deformer data that makes modded deformers not containing this data work implicitly. Updates are still recommended (1.5.0.5).") - .RegisterEntry("Fixed various issues after patch (1.5.0.1 - 1.5.0.4)."); + #region Changelogs private static void Add1_5_0_0(Changelog log) => log.NextVersion("Version 1.5.0.0") diff --git a/Penumbra/UI/CollectionTab/CollectionPanel.cs b/Penumbra/UI/CollectionTab/CollectionPanel.cs index e41ceade..26fa2b14 100644 --- a/Penumbra/UI/CollectionTab/CollectionPanel.cs +++ b/Penumbra/UI/CollectionTab/CollectionPanel.cs @@ -11,7 +11,6 @@ using OtterGui; using OtterGui.Classes; using OtterGui.Extensions; using OtterGui.Raii; -using OtterGui.Text; using Penumbra.Collections; using Penumbra.Collections.Manager; using Penumbra.GameData.Actors; @@ -223,31 +222,26 @@ public sealed class CollectionPanel( ImGui.EndGroup(); ImGui.SameLine(); ImGui.BeginGroup(); - var width = ImGui.GetContentRegionAvail().X; - using (ImRaii.Disabled(_collections.DefaultNamed == collection)) + using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); + var name = _newName ?? collection.Identity.Name; + var identifier = collection.Identity.Identifier; + var width = ImGui.GetContentRegionAvail().X; + var fileName = saveService.FileNames.CollectionFile(collection); + ImGui.SetNextItemWidth(width); + if (ImGui.InputText("##name", ref name, 128)) + _newName = name; + if (ImGui.IsItemDeactivatedAfterEdit() && _newName != null && _newName != collection.Identity.Name) { - using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f)); - var name = _newName ?? collection.Identity.Name; - ImGui.SetNextItemWidth(width); - if (ImGui.InputText("##name", ref name, 128)) - _newName = name; - if (ImGui.IsItemDeactivatedAfterEdit() && _newName != null && _newName != collection.Identity.Name) - { - collection.Identity.Name = _newName; - saveService.QueueSave(new ModCollectionSave(mods, collection)); - selector.RestoreCollections(); - _newName = null; - } - else if (ImGui.IsItemDeactivated()) - { - _newName = null; - } + collection.Identity.Name = _newName; + saveService.QueueSave(new ModCollectionSave(mods, collection)); + selector.RestoreCollections(); + _newName = null; + } + else if (ImGui.IsItemDeactivated()) + { + _newName = null; } - if (_collections.DefaultNamed == collection) - ImUtf8.HoverTooltip(ImGuiHoveredFlags.AllowWhenDisabled, "The Default collection can not be renamed."u8); - var identifier = collection.Identity.Identifier; - var fileName = saveService.FileNames.CollectionFile(collection); using (ImRaii.PushFont(UiBuilder.MonoFont)) { if (ImGui.Button(collection.Identity.Identifier, new Vector2(width, 0))) @@ -381,7 +375,9 @@ public sealed class CollectionPanel( ImGuiUtil.TextWrapped(type.ToDescription()); switch (type) { - case CollectionType.Default: ImGui.TextUnformatted("Overruled by any other Assignment."); break; + case CollectionType.Default: + ImGui.TextUnformatted("Overruled by any other Assignment."); + break; case CollectionType.Yourself: ImGuiUtil.DrawColoredText(("Overruled by ", 0), ("Individual ", ColorId.NewMod.Value()), ("Assignments.", 0)); break; diff --git a/Penumbra/UI/CollectionTab/CollectionSelector.cs b/Penumbra/UI/CollectionTab/CollectionSelector.cs index 79254090..e54f994e 100644 --- a/Penumbra/UI/CollectionTab/CollectionSelector.cs +++ b/Penumbra/UI/CollectionTab/CollectionSelector.cs @@ -116,8 +116,7 @@ public sealed class CollectionSelector : ItemSelector, IDisposabl public void RestoreCollections() { Items.Clear(); - Items.Add(_storage.DefaultNamed); - foreach (var c in _storage.OrderBy(c => c.Identity.Name).Where(c => c != _storage.DefaultNamed)) + foreach (var c in _storage.OrderBy(c => c.Identity.Name)) Items.Add(c); SetFilterDirty(); SetCurrent(_active.Current); diff --git a/Penumbra/UI/Tabs/Debug/DebugTab.cs b/Penumbra/UI/Tabs/Debug/DebugTab.cs index 05f77e29..d41dd25a 100644 --- a/Penumbra/UI/Tabs/Debug/DebugTab.cs +++ b/Penumbra/UI/Tabs/Debug/DebugTab.cs @@ -9,7 +9,6 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using Dalamud.Bindings.ImGui; -using Dalamud.Interface.Colors; using Microsoft.Extensions.DependencyInjection; using OtterGui; using OtterGui.Classes; @@ -42,7 +41,6 @@ using Penumbra.GameData.Data; using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.GameData.Files.StainMapStructs; -using Penumbra.Interop; using Penumbra.String.Classes; using Penumbra.UI.AdvancedWindow.Materials; @@ -208,7 +206,6 @@ public class DebugTab : Window, ITab, IUiService _hookOverrides.Draw(); DrawPlayerModelInfo(); _globalVariablesDrawer.Draw(); - DrawCloudApi(); DrawDebugTabIpc(); } @@ -1202,42 +1199,6 @@ public class DebugTab : Window, ITab, IUiService } - private string _cloudTesterPath = string.Empty; - private bool? _cloudTesterReturn; - private Exception? _cloudTesterError; - - private void DrawCloudApi() - { - if (!ImUtf8.CollapsingHeader("Cloud API"u8)) - return; - - using var id = ImRaii.PushId("CloudApiTester"u8); - - if (ImUtf8.InputText("Path"u8, ref _cloudTesterPath, flags: ImGuiInputTextFlags.EnterReturnsTrue)) - { - try - { - _cloudTesterReturn = CloudApi.IsCloudSynced(_cloudTesterPath); - _cloudTesterError = null; - } - catch (Exception e) - { - _cloudTesterReturn = null; - _cloudTesterError = e; - } - } - - if (_cloudTesterReturn.HasValue) - ImUtf8.Text($"Is Cloud Synced? {_cloudTesterReturn}"); - - if (_cloudTesterError is not null) - { - using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed); - ImUtf8.Text($"{_cloudTesterError}"); - } - } - - /// Draw information about IPC options and availability. private void DrawDebugTabIpc() { diff --git a/Penumbra/UI/Tabs/SettingsTab.cs b/Penumbra/UI/Tabs/SettingsTab.cs index 86c01cb2..ded56bb1 100644 --- a/Penumbra/UI/Tabs/SettingsTab.cs +++ b/Penumbra/UI/Tabs/SettingsTab.cs @@ -14,7 +14,6 @@ using OtterGui.Text; using OtterGui.Widgets; using Penumbra.Api; using Penumbra.Collections; -using Penumbra.Interop; using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Services; using Penumbra.Mods.Manager; @@ -37,7 +36,6 @@ public class SettingsTab : ITab, IUiService private readonly Penumbra _penumbra; private readonly FileDialogService _fileDialog; private readonly ModManager _modManager; - private readonly FileWatcher _fileWatcher; private readonly ModExportManager _modExportManager; private readonly ModFileSystemSelector _selector; private readonly CharacterUtility _characterUtility; @@ -61,13 +59,9 @@ public class SettingsTab : ITab, IUiService private readonly TagButtons _sharedTags = new(); - private string _lastCloudSyncTestedPath = string.Empty; - private bool _lastCloudSyncTestResult = false; - public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial, Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector, - CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, - FileWatcher fileWatcher, HttpApi httpApi, + CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, HttpApi httpApi, DalamudSubstitutionProvider dalamudSubstitutionProvider, FileCompactor compactor, DalamudConfigService dalamudConfig, IDataManager gameData, PredefinedTagManager predefinedTagConfig, CrashHandlerService crashService, MigrationSectionDrawer migrationDrawer, CollectionAutoSelector autoSelector, CleanupService cleanupService, @@ -84,7 +78,6 @@ public class SettingsTab : ITab, IUiService _characterUtility = characterUtility; _residentResources = residentResources; _modExportManager = modExportManager; - _fileWatcher = fileWatcher; _httpApi = httpApi; _dalamudSubstitutionProvider = dalamudSubstitutionProvider; _compactor = compactor; @@ -215,15 +208,6 @@ public class SettingsTab : ITab, IUiService if (IsSubPathOf(gameDir, newName)) return ("Path is not allowed to be inside your game folder.", false); - if (_lastCloudSyncTestedPath != newName) - { - _lastCloudSyncTestResult = CloudApi.IsCloudSynced(newName); - _lastCloudSyncTestedPath = newName; - } - - if (_lastCloudSyncTestResult) - return ("Path is not allowed to be cloud-synced.", false); - return selected ? ($"Press Enter or Click Here to Save (Current Directory: {old})", true) : ($"Click Here to Save (Current Directory: {old})", true); @@ -650,13 +634,6 @@ public class SettingsTab : ITab, IUiService DrawDefaultModImportFolder(); DrawPcpFolder(); DrawDefaultModExportPath(); - Checkbox("Enable Directory Watcher", - "Enables a File Watcher that automatically listens for Mod files that enter a specified directory, causing Penumbra to open a popup to import these mods.", - _config.EnableDirectoryWatch, _fileWatcher.Toggle); - Checkbox("Enable Fully Automatic Import", - "Uses the File Watcher in order to skip the query popup and automatically import any new mods.", - _config.EnableAutomaticModImport, v => _config.EnableAutomaticModImport = v); - DrawFileWatcherPath(); } @@ -736,46 +713,6 @@ public class SettingsTab : ITab, IUiService + "Keep this empty to use the root directory."); } - private string? _tempWatchDirectory; - - /// Draw input for the Automatic Mod import path. - private void DrawFileWatcherPath() - { - var tmp = _tempWatchDirectory ?? _config.WatchDirectory; - var spacing = new Vector2(UiHelpers.ScaleX3); - using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); - ImGui.SetNextItemWidth(UiHelpers.InputTextMinusButton3); - if (ImGui.InputText("##fileWatchPath", ref tmp, 256)) - _tempWatchDirectory = tmp; - - if (ImGui.IsItemDeactivated() && _tempWatchDirectory is not null) - { - if (ImGui.IsItemDeactivatedAfterEdit()) - _fileWatcher.UpdateDirectory(_tempWatchDirectory); - _tempWatchDirectory = null; - } - - ImGui.SameLine(); - if (ImGuiUtil.DrawDisabledButton($"{FontAwesomeIcon.Folder.ToIconString()}##fileWatch", UiHelpers.IconButtonSize, - "Select a directory via dialog.", false, true)) - { - var startDir = _config.WatchDirectory.Length > 0 && Directory.Exists(_config.WatchDirectory) - ? _config.WatchDirectory - : Directory.Exists(_config.ModDirectory) - ? _config.ModDirectory - : null; - _fileDialog.OpenFolderPicker("Choose Automatic Import Directory", (b, s) => - { - if (b) - _fileWatcher.UpdateDirectory(s); - }, startDir, false); - } - - style.Pop(); - ImGuiUtil.LabeledHelpMarker("Automatic Import Director", - "Choose the Directory the File Watcher listens to."); - } - /// Draw input for the default name to input as author into newly generated mods. private void DrawDefaultModAuthor() { diff --git a/repo.json b/repo.json index 7ddffd7c..446932b5 100644 --- a/repo.json +++ b/repo.json @@ -1,12 +1,12 @@ [ { - "Author": "Ottermandias, Nylfae, Adam, Wintermute", + "Author": "Ottermandias, Adam, Wintermute", "Name": "Penumbra", "Punchline": "Runtime mod loader and manager.", "Description": "Runtime mod loader and manager.", "InternalName": "Penumbra", - "AssemblyVersion": "1.5.1.8", - "TestingAssemblyVersion": "1.5.1.8", + "AssemblyVersion": "1.5.0.6", + "TestingAssemblyVersion": "1.5.0.9", "RepoUrl": "https://github.com/xivdev/Penumbra", "ApplicableVersion": "any", "DalamudApiLevel": 13, @@ -18,9 +18,9 @@ "LoadPriority": 69420, "LoadRequiredState": 2, "LoadSync": true, - "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.8/Penumbra.zip", - "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.8/Penumbra.zip", - "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.5.1.8/Penumbra.zip", + "DownloadLinkInstall": "https://github.com/xivdev/Penumbra/releases/download/1.5.0.6/Penumbra.zip", + "DownloadLinkTesting": "https://github.com/xivdev/Penumbra/releases/download/testing_1.5.0.9/Penumbra.zip", + "DownloadLinkUpdate": "https://github.com/xivdev/Penumbra/releases/download/1.5.0.6/Penumbra.zip", "IconUrl": "https://raw.githubusercontent.com/xivdev/Penumbra/master/images/icon.png" } ]