mirror of
https://github.com/xivdev/Penumbra.git
synced 2026-02-23 08:17:59 +01:00
Compare commits
9 commits
912020cc3f
...
18a6ce2a5f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18a6ce2a5f | ||
|
|
e68e821b2a | ||
|
|
96764b34ca | ||
|
|
2cf60b78cd | ||
|
|
d59be1e660 | ||
|
|
5503bb32e0 | ||
|
|
f3ec4b2e08 | ||
|
|
b3379a9710 | ||
|
|
8c25ef4b47 |
9 changed files with 219 additions and 90 deletions
47
Penumbra/Interop/CloudApi.cs
Normal file
47
Penumbra/Interop/CloudApi.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
namespace Penumbra.Interop;
|
||||
|
||||
public static unsafe partial class CloudApi
|
||||
{
|
||||
private const int CfSyncRootInfoBasic = 0;
|
||||
|
||||
/// <summary> Determines whether a file or directory is cloud-synced using OneDrive or other providers that use the Cloud API. </summary>
|
||||
/// <remarks> Can be expensive. Callers should cache the result when relevant. </remarks>
|
||||
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);
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using OtterGui.Services;
|
||||
using Penumbra.Communication;
|
||||
using Penumbra.Interop;
|
||||
using Penumbra.Mods.Editor;
|
||||
using Penumbra.Mods.Manager.OptionEditor;
|
||||
using Penumbra.Services;
|
||||
|
|
@ -303,6 +304,9 @@ 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)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ 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,10 +212,11 @@ public class Penumbra : IDalamudPlugin
|
|||
|
||||
public string GatherSupportInformation()
|
||||
{
|
||||
var sb = new StringBuilder(10240);
|
||||
var exists = _config.ModDirectory.Length > 0 && Directory.Exists(_config.ModDirectory);
|
||||
var hdrEnabler = _services.GetService<RenderTargetHdrEnabler>();
|
||||
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 cloudSynced = exists && CloudApi.IsCloudSynced(_config.ModDirectory);
|
||||
var hdrEnabler = _services.GetService<RenderTargetHdrEnabler>();
|
||||
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");
|
||||
|
|
@ -223,7 +225,8 @@ 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")}\n");
|
||||
sb.Append(
|
||||
$"> **`Root Directory: `** `{_config.ModDirectory}`, {(exists ? "Exists" : "Not Existing")}{(cloudSynced ? ", Cloud-Synced" : "")}\n");
|
||||
sb.Append(
|
||||
$"> **`Free Drive Space: `** {(drive != null ? Functions.HumanReadableSize(drive.AvailableFreeSpace) : "Unknown")}\n");
|
||||
sb.Append($"> **`Game Data Files: `** {(_gameData.HasModifiedGameDataFiles ? "Modified" : "Pristine")}\n");
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ public partial class ModEditWindow
|
|||
private readonly FileDialogService _fileDialog;
|
||||
private readonly ResourceTreeFactory _resourceTreeFactory;
|
||||
private readonly ResourceTreeViewer _quickImportViewer;
|
||||
private readonly Dictionary<FullPath, IWritable?> _quickImportWritables = new();
|
||||
private readonly Dictionary<(Utf8GamePath, IWritable?), QuickImportAction> _quickImportActions = new();
|
||||
|
||||
private HashSet<string> GetPlayerResourcesOfType(ResourceType type)
|
||||
|
|
@ -56,52 +55,11 @@ public partial class ModEditWindow
|
|||
|
||||
private void OnQuickImportRefresh()
|
||||
{
|
||||
_quickImportWritables.Clear();
|
||||
_quickImportActions.Clear();
|
||||
}
|
||||
|
||||
private void DrawQuickImportActions(ResourceNode resourceNode, Vector2 buttonSize)
|
||||
private void DrawQuickImportActions(ResourceNode resourceNode, IWritable? writable, 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))
|
||||
{
|
||||
|
|
@ -121,24 +79,6 @@ 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";
|
||||
|
|
|
|||
|
|
@ -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(2, OnQuickImportRefresh, DrawQuickImportActions);
|
||||
_quickImportViewer = resourceTreeViewerFactory.Create(1, OnQuickImportRefresh, DrawQuickImportActions);
|
||||
_communicator.ModPathChanged.Subscribe(OnModPathChange, ModPathChanged.Priority.ModEditWindow);
|
||||
IsOpen = _config is { OpenWindowAtStart: true, Ephemeral.AdvancedEditingOpen: true };
|
||||
if (IsOpen && selection.Mod != null)
|
||||
|
|
|
|||
|
|
@ -4,16 +4,20 @@ 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;
|
||||
|
|
@ -25,17 +29,20 @@ public class ResourceTreeViewer(
|
|||
IncognitoService incognito,
|
||||
int actionCapacity,
|
||||
Action onRefresh,
|
||||
Action<ResourceNode, Vector2> drawActions,
|
||||
Action<ResourceNode, IWritable?, Vector2> drawActions,
|
||||
CommunicatorService communicator,
|
||||
PcpService pcpService,
|
||||
IDataManager gameData)
|
||||
IDataManager gameData,
|
||||
FileDialogService fileDialog,
|
||||
FileCompactor compactor)
|
||||
{
|
||||
private const ResourceTreeFactory.Flags ResourceTreeFactoryFlags =
|
||||
ResourceTreeFactory.Flags.RedactExternalPaths | ResourceTreeFactory.Flags.WithUiData | ResourceTreeFactory.Flags.WithOwnership;
|
||||
ResourceTreeFactory.Flags.WithUiData | ResourceTreeFactory.Flags.WithOwnership;
|
||||
|
||||
private readonly HashSet<nint> _unfolded = [];
|
||||
|
||||
private readonly Dictionary<nint, NodeVisibility> _filterCache = [];
|
||||
private readonly Dictionary<nint, NodeVisibility> _filterCache = [];
|
||||
private readonly Dictionary<FullPath, IWritable?> _writableCache = [];
|
||||
|
||||
private TreeCategory _categoryFilter = AllCategories;
|
||||
private ChangedItemIconFlag _typeFilter = ChangedItemFlagExtensions.AllFlags;
|
||||
|
|
@ -115,7 +122,7 @@ public class ResourceTreeViewer(
|
|||
ImUtf8.InputText("##note"u8, ref _note, "Export note..."u8);
|
||||
|
||||
|
||||
using var table = ImRaii.Table("##ResourceTree", actionCapacity > 0 ? 4 : 3,
|
||||
using var table = ImRaii.Table("##ResourceTree", 4,
|
||||
ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
continue;
|
||||
|
|
@ -123,9 +130,8 @@ 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);
|
||||
if (actionCapacity > 0)
|
||||
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed,
|
||||
(actionCapacity - 1) * 3 * ImGuiHelpers.GlobalScale + actionCapacity * ImGui.GetFrameHeight());
|
||||
ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.WidthFixed,
|
||||
actionCapacity * 3 * ImGuiHelpers.GlobalScale + (actionCapacity + 1) * ImGui.GetFrameHeight());
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
DrawNodes(tree.Nodes, 0, unchecked(tree.DrawObjectAddress * 31), 0);
|
||||
|
|
@ -211,6 +217,7 @@ public class ResourceTreeViewer(
|
|||
finally
|
||||
{
|
||||
_filterCache.Clear();
|
||||
_writableCache.Clear();
|
||||
_unfolded.Clear();
|
||||
onRefresh();
|
||||
}
|
||||
|
|
@ -221,7 +228,6 @@ 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())
|
||||
{
|
||||
|
|
@ -291,7 +297,7 @@ public class ResourceTreeViewer(
|
|||
0 => "(none)",
|
||||
1 => resourceNode.GamePath.ToString(),
|
||||
_ => "(multiple)",
|
||||
}, false, hasGamePaths ? 0 : ImGuiSelectableFlags.Disabled, new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
|
||||
}, false, hasGamePaths ? 0 : ImGuiSelectableFlags.Disabled, new Vector2(ImGui.GetContentRegionAvail().X, frameHeight));
|
||||
if (hasGamePaths)
|
||||
{
|
||||
var allPaths = string.Join('\n', resourceNode.PossibleGamePaths);
|
||||
|
|
@ -312,17 +318,29 @@ 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, cellHeight));
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, frameHeight));
|
||||
}
|
||||
|
||||
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, cellHeight));
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, frameHeight));
|
||||
}
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
|
|
@ -336,20 +354,17 @@ public class ResourceTreeViewer(
|
|||
else
|
||||
{
|
||||
ImUtf8.Selectable(GetPathStatusLabel(resourceNode.FullPathStatus), false, ImGuiSelectableFlags.Disabled,
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, cellHeight));
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, frameHeight));
|
||||
ImGuiUtil.HoverTooltip(
|
||||
$"{GetPathStatusDescription(resourceNode.FullPathStatus)}{GetAdditionalDataSuffix(resourceNode.AdditionalData)}");
|
||||
}
|
||||
|
||||
mutedColor.Dispose();
|
||||
|
||||
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));
|
||||
}
|
||||
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);
|
||||
|
|
@ -402,6 +417,51 @@ 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<byte> GetPathStatusLabel(ResourceNode.PathStatus status)
|
||||
|
|
@ -465,4 +525,22 @@ 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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
using Dalamud.Plugin.Services;
|
||||
using OtterGui.Compression;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.Interop.ResourceTree;
|
||||
using Penumbra.Services;
|
||||
|
||||
|
|
@ -12,8 +14,11 @@ public class ResourceTreeViewerFactory(
|
|||
IncognitoService incognito,
|
||||
CommunicatorService communicator,
|
||||
PcpService pcpService,
|
||||
IDataManager gameData) : IService
|
||||
IDataManager gameData,
|
||||
FileDialogService fileDialog,
|
||||
FileCompactor compactor) : IService
|
||||
{
|
||||
public ResourceTreeViewer Create(int actionCapacity, Action onRefresh, Action<ResourceNode, Vector2> drawActions)
|
||||
=> new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions, communicator, pcpService, gameData);
|
||||
public ResourceTreeViewer Create(int actionCapacity, Action onRefresh, Action<ResourceNode, IWritable?, Vector2> drawActions)
|
||||
=> new(config, treeFactory, changedItemDrawer, incognito, actionCapacity, onRefresh, drawActions, communicator, pcpService, gameData,
|
||||
fileDialog, compactor);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ 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;
|
||||
|
|
@ -41,6 +42,7 @@ 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;
|
||||
|
||||
|
|
@ -206,6 +208,7 @@ public class DebugTab : Window, ITab, IUiService
|
|||
_hookOverrides.Draw();
|
||||
DrawPlayerModelInfo();
|
||||
_globalVariablesDrawer.Draw();
|
||||
DrawCloudApi();
|
||||
DrawDebugTabIpc();
|
||||
}
|
||||
|
||||
|
|
@ -1199,6 +1202,42 @@ 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}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary> Draw information about IPC options and availability. </summary>
|
||||
private void DrawDebugTabIpc()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ 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;
|
||||
|
|
@ -59,6 +60,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, HttpApi httpApi,
|
||||
|
|
@ -208,6 +212,15 @@ 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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue