Merge branch 'refs/heads/master' into apiX

# Conflicts:
#	lib/FFXIVClientStructs
This commit is contained in:
Kaz Wolfe 2024-06-01 15:13:32 -07:00
commit 61f47449fd
No known key found for this signature in database
GPG key ID: 258813F53A16EBB4
20 changed files with 316 additions and 207 deletions

View file

@ -398,7 +398,7 @@ public sealed class DalamudPluginInterface : IDisposable
if (currentConfig == null)
return;
this.configs.Save(currentConfig, this.plugin.InternalName, this.plugin.Manifest.WorkingPluginId);
this.configs.Save(currentConfig, this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId);
}
/// <summary>
@ -425,7 +425,7 @@ public sealed class DalamudPluginInterface : IDisposable
}
// this shouldn't be a thing, I think, but just in case
return this.configs.Load(this.plugin.InternalName, this.plugin.Manifest.WorkingPluginId);
return this.configs.Load(this.plugin.InternalName, this.plugin.EffectiveWorkingPluginId);
}
/// <summary>

View file

@ -1029,7 +1029,7 @@ internal class PluginManager : IInternalDisposableService
{
var plugin = metadata.InstalledPlugin;
var workingPluginId = metadata.InstalledPlugin.Manifest.WorkingPluginId;
var workingPluginId = metadata.InstalledPlugin.EffectiveWorkingPluginId;
if (workingPluginId == Guid.Empty)
throw new Exception("Existing plugin had no WorkingPluginId");
@ -1331,16 +1331,16 @@ internal class PluginManager : IInternalDisposableService
foreach (var installedPlugin in this.InstalledPlugins)
{
if (installedPlugin.Manifest.WorkingPluginId == Guid.Empty)
if (installedPlugin.EffectiveWorkingPluginId == Guid.Empty)
throw new Exception($"{(installedPlugin is LocalDevPlugin ? "DevPlugin" : "Plugin")} '{installedPlugin.Manifest.InternalName}' has an empty WorkingPluginId.");
if (seenIds.Contains(installedPlugin.Manifest.WorkingPluginId))
if (seenIds.Contains(installedPlugin.EffectiveWorkingPluginId))
{
throw new Exception(
$"{(installedPlugin is LocalDevPlugin ? "DevPlugin" : "Plugin")} '{installedPlugin.Manifest.InternalName}' has a duplicate WorkingPluginId '{installedPlugin.Manifest.WorkingPluginId}'");
$"{(installedPlugin is LocalDevPlugin ? "DevPlugin" : "Plugin")} '{installedPlugin.Manifest.InternalName}' has a duplicate WorkingPluginId '{installedPlugin.EffectiveWorkingPluginId}'");
}
seenIds.Add(installedPlugin.Manifest.WorkingPluginId);
seenIds.Add(installedPlugin.EffectiveWorkingPluginId);
}
this.profileManager.ParanoiaValidateProfiles();
@ -1388,7 +1388,7 @@ internal class PluginManager : IInternalDisposableService
{
// Only remove entries from the default profile that are NOT currently tied to an active LocalPlugin
var guidsToRemove = this.profileManager.DefaultProfile.Plugins
.Where(x => this.InstalledPlugins.All(y => y.Manifest.WorkingPluginId != x.WorkingPluginId))
.Where(x => this.InstalledPlugins.All(y => y.EffectiveWorkingPluginId != x.WorkingPluginId))
.Select(x => x.WorkingPluginId)
.ToArray();
@ -1560,9 +1560,9 @@ internal class PluginManager : IInternalDisposableService
// This will also happen if you are installing a plugin with the installer, and that's intended!
// It means that, if you have a profile which has unsatisfied plugins, installing a matching plugin will
// enter it into the profiles it can match.
if (plugin.Manifest.WorkingPluginId == Guid.Empty)
if (plugin.EffectiveWorkingPluginId == Guid.Empty)
throw new Exception("Plugin should have a WorkingPluginId at this point");
this.profileManager.MigrateProfilesToGuidsForPlugin(plugin.Manifest.InternalName, plugin.Manifest.WorkingPluginId);
this.profileManager.MigrateProfilesToGuidsForPlugin(plugin.Manifest.InternalName, plugin.EffectiveWorkingPluginId);
var wantedByAnyProfile = false;
@ -1573,7 +1573,7 @@ internal class PluginManager : IInternalDisposableService
loadPlugin &= !isBoot;
var wantsInDefaultProfile =
this.profileManager.DefaultProfile.WantsPlugin(plugin.Manifest.WorkingPluginId);
this.profileManager.DefaultProfile.WantsPlugin(plugin.EffectiveWorkingPluginId);
if (wantsInDefaultProfile == null)
{
// We don't know about this plugin, so we don't want to do anything here.
@ -1582,7 +1582,7 @@ internal class PluginManager : IInternalDisposableService
// Check if any profile wants this plugin. We need to do this here, since we want to allow loading a dev plugin if a non-default profile wants it active.
// Note that this will not add the plugin to the default profile. That's done below in any other case.
wantedByAnyProfile = await this.profileManager.GetWantStateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false);
wantedByAnyProfile = await this.profileManager.GetWantStateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false);
// If it is wanted by any other profile, we do want to load it.
if (wantedByAnyProfile)
@ -1592,28 +1592,28 @@ internal class PluginManager : IInternalDisposableService
{
// We didn't want this plugin, and StartOnBoot is on. That means we don't want it and it should stay off until manually enabled.
Log.Verbose("DevPlugin {Name} disabled and StartOnBoot => disable", plugin.Manifest.InternalName);
await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false);
await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false);
loadPlugin = false;
}
else if (wantsInDefaultProfile == true && devPlugin.StartOnBoot)
{
// We wanted this plugin, and StartOnBoot is on. That means we actually do want it.
Log.Verbose("DevPlugin {Name} enabled and StartOnBoot => enable", plugin.Manifest.InternalName);
await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, true, false);
await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, true, false);
loadPlugin = !doNotLoad;
}
else if (wantsInDefaultProfile == true && !devPlugin.StartOnBoot)
{
// We wanted this plugin, but StartOnBoot is off. This means we don't want it anymore.
Log.Verbose("DevPlugin {Name} enabled and !StartOnBoot => disable", plugin.Manifest.InternalName);
await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false);
await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false);
loadPlugin = false;
}
else if (wantsInDefaultProfile == false && !devPlugin.StartOnBoot)
{
// We didn't want this plugin, and StartOnBoot is off. We don't want it.
Log.Verbose("DevPlugin {Name} disabled and !StartOnBoot => disable", plugin.Manifest.InternalName);
await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, false, false);
await this.profileManager.DefaultProfile.AddOrUpdateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, false, false);
loadPlugin = false;
}
@ -1626,7 +1626,7 @@ internal class PluginManager : IInternalDisposableService
// Plugins that aren't in any profile will be added to the default profile with this call.
// We are skipping a double-lookup for dev plugins that are wanted by non-default profiles, as noted above.
wantedByAnyProfile = wantedByAnyProfile || await this.profileManager.GetWantStateAsync(plugin.Manifest.WorkingPluginId, plugin.Manifest.InternalName, defaultState);
wantedByAnyProfile = wantedByAnyProfile || await this.profileManager.GetWantStateAsync(plugin.EffectiveWorkingPluginId, plugin.Manifest.InternalName, defaultState);
Log.Information("{Name} defaultState: {State} wantedByAnyProfile: {WantedByAny} loadPlugin: {LoadPlugin}", plugin.Manifest.InternalName, defaultState, wantedByAnyProfile, loadPlugin);
if (loadPlugin)

View file

@ -183,8 +183,8 @@ internal class ProfileManager : IServiceType
var installedPlugin = pm.InstalledPlugins.FirstOrDefault(x => x.Manifest.InternalName == plugin.InternalName);
if (installedPlugin != null)
{
Log.Information("Satisfying plugin {InternalName} for profile {Name} with {Guid}", plugin.InternalName, newModel.Name, installedPlugin.Manifest.WorkingPluginId);
plugin.WorkingPluginId = installedPlugin.Manifest.WorkingPluginId;
Log.Information("Satisfying plugin {InternalName} for profile {Name} with {Guid}", plugin.InternalName, newModel.Name, installedPlugin.EffectiveWorkingPluginId);
plugin.WorkingPluginId = installedPlugin.EffectiveWorkingPluginId;
}
else
{
@ -237,7 +237,7 @@ internal class ProfileManager : IServiceType
var pm = Service<PluginManager>.Get();
foreach (var installedPlugin in pm.InstalledPlugins)
{
var wantThis = wantActive.Any(x => x.WorkingPluginId == installedPlugin.Manifest.WorkingPluginId);
var wantThis = wantActive.Any(x => x.WorkingPluginId == installedPlugin.EffectiveWorkingPluginId);
switch (wantThis)
{
case true when !installedPlugin.IsLoaded:

View file

@ -50,14 +50,6 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
Log.Verbose("{InternalName} was assigned new devPlugin GUID {Guid}", this.InternalName, this.devSettings.WorkingPluginId);
configuration.QueueSave();
}
// If the ID in the manifest is wrong, force the good one
if (this.DevImposedWorkingPluginId != this.manifest.WorkingPluginId)
{
Debug.Assert(this.DevImposedWorkingPluginId != Guid.Empty, "Empty guid for devPlugin");
this.manifest.WorkingPluginId = this.DevImposedWorkingPluginId;
this.SaveManifest("dev imposed working plugin id");
}
if (this.AutomaticReload)
{
@ -99,7 +91,10 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
/// Gets an ID uniquely identifying this specific instance of a devPlugin.
/// </summary>
public Guid DevImposedWorkingPluginId => this.devSettings.WorkingPluginId;
/// <inheritdoc/>
public override Guid EffectiveWorkingPluginId => this.DevImposedWorkingPluginId;
/// <summary>
/// Gets a list of validation problems that have been dismissed by the user.
/// </summary>
@ -148,6 +143,23 @@ internal class LocalDevPlugin : LocalPlugin, IDisposable
}
}
/// <summary>
/// Reload the manifest if it exists, to update possible changes.
/// </summary>
/// <exception cref="Exception">Thrown if the manifest could not be loaded.</exception>
public void ReloadManifest()
{
var manifestPath = LocalPluginManifest.GetManifestFile(this.DllFile);
if (manifestPath.Exists)
this.manifest = LocalPluginManifest.Load(manifestPath) ?? throw new Exception("Could not reload manifest.");
}
/// <inheritdoc/>
protected override void OnPreReload()
{
this.ReloadManifest();
}
private void OnFileChanged(object sender, FileSystemEventArgs args)
{
var current = Interlocked.Increment(ref this.reloadCounter);

View file

@ -91,7 +91,7 @@ internal class LocalPlugin : IDisposable
}
// Create an installation instance ID for this plugin, if it doesn't have one yet
if (this.manifest.WorkingPluginId == Guid.Empty)
if (this.manifest.WorkingPluginId == Guid.Empty && !this.IsDev)
{
this.manifest.WorkingPluginId = Guid.NewGuid();
@ -162,7 +162,7 @@ internal class LocalPlugin : IDisposable
/// INCLUDES the default profile.
/// </summary>
public bool IsWantedByAnyProfile =>
Service<ProfileManager>.Get().GetWantStateAsync(this.manifest.WorkingPluginId, this.Manifest.InternalName, false, false).GetAwaiter().GetResult();
Service<ProfileManager>.Get().GetWantStateAsync(this.EffectiveWorkingPluginId, this.Manifest.InternalName, false, false).GetAwaiter().GetResult();
/// <summary>
/// Gets a value indicating whether this plugin's API level is out of date.
@ -215,6 +215,11 @@ internal class LocalPlugin : IDisposable
/// </summary>
public Version EffectiveVersion => this.manifest.EffectiveVersion;
/// <summary>
/// Gets the effective working plugin ID for this plugin.
/// </summary>
public virtual Guid EffectiveWorkingPluginId => this.manifest.WorkingPluginId;
/// <summary>
/// Gets the service scope for this plugin.
/// </summary>
@ -271,11 +276,8 @@ internal class LocalPlugin : IDisposable
await this.pluginLoadStateLock.WaitAsync();
try
{
if (reloading && this.IsDev)
{
// Reload the manifest in-case there were changes here too.
this.ReloadManifest();
}
if (reloading)
this.OnPreReload();
// If we reload a plugin we don't want to delete it. Makes sense, right?
if (this.manifest.ScheduledForDeletion)
@ -578,24 +580,6 @@ internal class LocalPlugin : IDisposable
this.SaveManifest("scheduling for deletion");
}
/// <summary>
/// Reload the manifest if it exists, preserve the internal Disabled state.
/// </summary>
public void ReloadManifest()
{
var manifestPath = LocalPluginManifest.GetManifestFile(this.DllFile);
if (manifestPath.Exists)
{
// Save some state that we do actually want to carry over
var guid = this.manifest.WorkingPluginId;
this.manifest = LocalPluginManifest.Load(manifestPath) ?? throw new Exception("Could not reload manifest.");
this.manifest.WorkingPluginId = guid;
this.SaveManifest("dev reload");
}
}
/// <summary>
/// Get the repository this plugin was installed from.
/// </summary>
@ -620,6 +604,13 @@ internal class LocalPlugin : IDisposable
/// </summary>
/// <param name="reason">Why it should be saved.</param>
protected void SaveManifest(string reason) => this.manifest.Save(this.manifestFile, reason);
/// <summary>
/// Called before a plugin is reloaded.
/// </summary>
protected virtual void OnPreReload()
{
}
private static void SetupLoaderConfig(LoaderConfig config)
{

View file

@ -48,7 +48,7 @@ public interface IGameInteropProvider
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
/// <returns>The hook with the supplied parameters.</returns>
/// <typeparam name="T">Delegate of detour.</typeparam>
public Hook<T> HookFromFunctionPointerVariable<T>(IntPtr address, T detour) where T : Delegate;
public Hook<T> HookFromFunctionPointerVariable<T>(nint address, T detour) where T : Delegate;
/// <summary>
/// Creates a hook by rewriting import table address.
@ -85,7 +85,31 @@ public interface IGameInteropProvider
/// <param name="backend">Hooking library to use.</param>
/// <returns>The hook with the supplied parameters.</returns>
/// <typeparam name="T">Delegate of detour.</typeparam>
Hook<T> HookFromAddress<T>(IntPtr procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
Hook<T> HookFromAddress<T>(nint procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
/// <summary>
/// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function.
/// The hook is not activated until Enable() method is called.
/// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work.
/// </summary>
/// <param name="procAddress">A memory address to install a hook.</param>
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
/// <param name="backend">Hooking library to use.</param>
/// <returns>The hook with the supplied parameters.</returns>
/// <typeparam name="T">Delegate of detour.</typeparam>
Hook<T> HookFromAddress<T>(nuint procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
/// <summary>
/// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function.
/// The hook is not activated until Enable() method is called.
/// Please do not use MinHook unless you have thoroughly troubleshot why Reloaded does not work.
/// </summary>
/// <param name="procAddress">A memory address to install a hook.</param>
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
/// <param name="backend">Hooking library to use.</param>
/// <returns>The hook with the supplied parameters.</returns>
/// <typeparam name="T">Delegate of detour.</typeparam>
unsafe Hook<T> HookFromAddress<T>(void* procAddress, T detour, HookBackend backend = HookBackend.Automatic) where T : Delegate;
/// <summary>
/// Creates a hook from a signature into the Dalamud target module.