Merge branch 'refs/heads/Exter-N/cldapi'
Some checks are pending
.NET Build / build (push) Waiting to run

This commit is contained in:
Ottermandias 2025-09-01 15:59:26 +02:00
commit 18a6ce2a5f
5 changed files with 111 additions and 5 deletions

View 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);
}

View file

@ -1,5 +1,6 @@
using OtterGui.Services; using OtterGui.Services;
using Penumbra.Communication; using Penumbra.Communication;
using Penumbra.Interop;
using Penumbra.Mods.Editor; using Penumbra.Mods.Editor;
using Penumbra.Mods.Manager.OptionEditor; using Penumbra.Mods.Manager.OptionEditor;
using Penumbra.Services; using Penumbra.Services;
@ -303,6 +304,9 @@ public sealed class ModManager : ModStorage, IDisposable, IService
if (!firstTime && _config.ModDirectory != BasePath.FullName) if (!firstTime && _config.ModDirectory != BasePath.FullName)
TriggerModDirectoryChange(BasePath.FullName, Valid); 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) private void TriggerModDirectoryChange(string newPath, bool valid)

View file

@ -23,6 +23,7 @@ using Dalamud.Plugin.Services;
using Lumina.Excel.Sheets; using Lumina.Excel.Sheets;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.Interop;
using Penumbra.Interop.Hooks; using Penumbra.Interop.Hooks;
using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.PostProcessing;
using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.Interop.Hooks.ResourceLoading;
@ -213,6 +214,7 @@ public class Penumbra : IDalamudPlugin
{ {
var sb = new StringBuilder(10240); var sb = new StringBuilder(10240);
var exists = _config.ModDirectory.Length > 0 && Directory.Exists(_config.ModDirectory); var exists = _config.ModDirectory.Length > 0 && Directory.Exists(_config.ModDirectory);
var cloudSynced = exists && CloudApi.IsCloudSynced(_config.ModDirectory);
var hdrEnabler = _services.GetService<RenderTargetHdrEnabler>(); var hdrEnabler = _services.GetService<RenderTargetHdrEnabler>();
var drive = exists ? new DriveInfo(new DirectoryInfo(_config.ModDirectory).Root.FullName) : null; var drive = exists ? new DriveInfo(new DirectoryInfo(_config.ModDirectory).Root.FullName) : null;
sb.AppendLine("**Settings**"); sb.AppendLine("**Settings**");
@ -223,7 +225,8 @@ public class Penumbra : IDalamudPlugin
sb.Append($"> **`Operating System: `** {(Dalamud.Utility.Util.IsWine() ? "Mac/Linux (Wine)" : "Windows")}\n"); sb.Append($"> **`Operating System: `** {(Dalamud.Utility.Util.IsWine() ? "Mac/Linux (Wine)" : "Windows")}\n");
if (Dalamud.Utility.Util.IsWine()) if (Dalamud.Utility.Util.IsWine())
sb.Append($"> **`Locale Environment Variables:`** {CollectLocaleEnvironmentVariables()}\n"); 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( sb.Append(
$"> **`Free Drive Space: `** {(drive != null ? Functions.HumanReadableSize(drive.AvailableFreeSpace) : "Unknown")}\n"); $"> **`Free Drive Space: `** {(drive != null ? Functions.HumanReadableSize(drive.AvailableFreeSpace) : "Unknown")}\n");
sb.Append($"> **`Game Data Files: `** {(_gameData.HasModifiedGameDataFiles ? "Modified" : "Pristine")}\n"); sb.Append($"> **`Game Data Files: `** {(_gameData.HasModifiedGameDataFiles ? "Modified" : "Pristine")}\n");

View file

@ -9,6 +9,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.System.Resource; using FFXIVClientStructs.FFXIV.Client.System.Resource;
using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Client.UI.Agent;
using Dalamud.Bindings.ImGui; using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Colors;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
@ -41,6 +42,7 @@ using Penumbra.GameData.Data;
using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.PostProcessing;
using Penumbra.Interop.Hooks.ResourceLoading; using Penumbra.Interop.Hooks.ResourceLoading;
using Penumbra.GameData.Files.StainMapStructs; using Penumbra.GameData.Files.StainMapStructs;
using Penumbra.Interop;
using Penumbra.String.Classes; using Penumbra.String.Classes;
using Penumbra.UI.AdvancedWindow.Materials; using Penumbra.UI.AdvancedWindow.Materials;
@ -206,6 +208,7 @@ public class DebugTab : Window, ITab, IUiService
_hookOverrides.Draw(); _hookOverrides.Draw();
DrawPlayerModelInfo(); DrawPlayerModelInfo();
_globalVariablesDrawer.Draw(); _globalVariablesDrawer.Draw();
DrawCloudApi();
DrawDebugTabIpc(); 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> /// <summary> Draw information about IPC options and availability. </summary>
private void DrawDebugTabIpc() private void DrawDebugTabIpc()
{ {

View file

@ -14,6 +14,7 @@ using OtterGui.Text;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.Api; using Penumbra.Api;
using Penumbra.Collections; using Penumbra.Collections;
using Penumbra.Interop;
using Penumbra.Interop.Hooks.PostProcessing; using Penumbra.Interop.Hooks.PostProcessing;
using Penumbra.Interop.Services; using Penumbra.Interop.Services;
using Penumbra.Mods.Manager; using Penumbra.Mods.Manager;
@ -59,6 +60,9 @@ public class SettingsTab : ITab, IUiService
private readonly TagButtons _sharedTags = new(); private readonly TagButtons _sharedTags = new();
private string _lastCloudSyncTestedPath = string.Empty;
private bool _lastCloudSyncTestResult = false;
public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial, public SettingsTab(IDalamudPluginInterface pluginInterface, Configuration config, FontReloader fontReloader, TutorialService tutorial,
Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector, Penumbra penumbra, FileDialogService fileDialog, ModManager modManager, ModFileSystemSelector selector,
CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, HttpApi httpApi, CharacterUtility characterUtility, ResidentResourceManager residentResources, ModExportManager modExportManager, HttpApi httpApi,
@ -208,6 +212,15 @@ public class SettingsTab : ITab, IUiService
if (IsSubPathOf(gameDir, newName)) if (IsSubPathOf(gameDir, newName))
return ("Path is not allowed to be inside your game folder.", false); 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 return selected
? ($"Press Enter or Click Here to Save (Current Directory: {old})", true) ? ($"Press Enter or Click Here to Save (Current Directory: {old})", true)
: ($"Click Here to Save (Current Directory: {old})", true); : ($"Click Here to Save (Current Directory: {old})", true);