This commit is contained in:
goat 2021-09-27 02:15:11 +02:00
commit c925611674
No known key found for this signature in database
GPG key ID: F18F057873895461
19 changed files with 540 additions and 561 deletions

View file

@ -205,6 +205,11 @@ namespace Dalamud.Configuration.Internal
/// </summary>
public string ChosenStyle { get; set; } = "Dalamud Standard";
/// <summary>
/// Gets or sets a value indicating whether or not Dalamud RMT filtering should be disabled.
/// </summary>
public bool DisableRmtFiltering { get; set; }
/// <summary>
/// Load a configuration from the provided path.
/// </summary>

View file

@ -151,13 +151,16 @@ namespace Dalamud.Game
var textVal = message.TextValue;
var matched = this.rmtRegex.IsMatch(textVal);
if (matched)
if (!configuration.DisableRmtFiltering)
{
// This seems to be a RMT ad - let's not show it
Log.Debug("Handled RMT ad: " + message.TextValue);
isHandled = true;
return;
var matched = this.rmtRegex.IsMatch(textVal);
if (matched)
{
// This seems to be a RMT ad - let's not show it
Log.Debug("Handled RMT ad: " + message.TextValue);
isHandled = true;
return;
}
}
if (configuration.BadWords != null &&

View file

@ -27,7 +27,7 @@ namespace Dalamud.Game.ClientState.Keys
// The array is accessed in a way that this limit doesn't appear to exist
// but there is other state data past this point, and keys beyond here aren't
// generally valid for most things anyway
private const int MaxKeyCodeIndex = 0xA0;
private const int MaxKeyCode = 0xF0;
private readonly IntPtr bufferBase;
private readonly IntPtr indexBase;
private VirtualKey[] validVirtualKeyCache = null;
@ -104,7 +104,7 @@ namespace Dalamud.Game.ClientState.Keys
/// <param name="vkCode">Virtual key code.</param>
/// <returns>If the code is valid.</returns>
public bool IsVirtualKeyValid(int vkCode)
=> vkCode > 0 && vkCode < MaxKeyCodeIndex && this.ConvertVirtualKey(vkCode) != 0;
=> this.ConvertVirtualKey(vkCode) != 0;
/// <inheritdoc cref="IsVirtualKeyValid(int)"/>
public bool IsVirtualKeyValid(VirtualKey vkCode)
@ -136,7 +136,7 @@ namespace Dalamud.Game.ClientState.Keys
/// <returns>Converted value.</returns>
private unsafe byte ConvertVirtualKey(int vkCode)
{
if (vkCode <= 0 || vkCode >= 240)
if (vkCode <= 0 || vkCode >= MaxKeyCode)
return 0;
return *(byte*)(this.indexBase + vkCode);
@ -149,9 +149,6 @@ namespace Dalamud.Game.ClientState.Keys
/// <returns>A reference to the indexed array.</returns>
private unsafe ref int GetRefValue(int vkCode)
{
if (vkCode < 0 || vkCode > MaxKeyCodeIndex)
throw new ArgumentException($"Keycode state is only valid up to {MaxKeyCodeIndex}");
vkCode = this.ConvertVirtualKey(vkCode);
if (vkCode == 0)

View file

@ -134,6 +134,28 @@ namespace Dalamud.Game
return baseAddress + index;
}
/// <summary>
/// Try scanning memory for a signature.
/// </summary>
/// <param name="baseAddress">The base address to scan from.</param>
/// <param name="size">The amount of bytes to scan.</param>
/// <param name="signature">The signature to search for.</param>
/// <param name="result">The offset, if found.</param>
/// <returns>true if the signature was found.</returns>
public static bool TryScan(IntPtr baseAddress, int size, string signature, out IntPtr result)
{
try
{
result = Scan(baseAddress, size, signature);
return true;
}
catch (KeyNotFoundException)
{
result = IntPtr.Zero;
return false;
}
}
/// <summary>
/// Scan for a .data address using a .text function.
/// This is intended to be used with IDA sigs.
@ -160,6 +182,29 @@ namespace Dalamud.Game
return IntPtr.Add(instrAddr, Marshal.ReadInt32(instrAddr) + 4);
}
/// <summary>
/// Try scanning for a .data address using a .text function.
/// This is intended to be used with IDA sigs.
/// Place your cursor on the line calling a static address, and create and IDA sig.
/// </summary>
/// <param name="signature">The signature of the function using the data.</param>
/// <param name="result">An IntPtr to the static memory location, if found.</param>
/// <param name="offset">The offset from function start of the instruction using the data.</param>
/// <returns>true if the signature was found.</returns>
public bool TryGetStaticAddressFromSig(string signature, out IntPtr result, int offset = 0)
{
try
{
result = this.GetStaticAddressFromSig(signature, offset);
return true;
}
catch (KeyNotFoundException)
{
result = IntPtr.Zero;
return false;
}
}
/// <summary>
/// Scan for a byte signature in the .data section.
/// </summary>
@ -175,6 +220,26 @@ namespace Dalamud.Game
return scanRet;
}
/// <summary>
/// Try scanning for a byte signature in the .data section.
/// </summary>
/// <param name="signature">The signature.</param>
/// <param name="result">The real offset of the signature, if found.</param>
/// <returns>true if the signature was found.</returns>
public bool TryScanData(string signature, out IntPtr result)
{
try
{
result = this.ScanData(signature);
return true;
}
catch (KeyNotFoundException)
{
result = IntPtr.Zero;
return false;
}
}
/// <summary>
/// Scan for a byte signature in the whole module search area.
/// </summary>
@ -190,6 +255,26 @@ namespace Dalamud.Game
return scanRet;
}
/// <summary>
/// Try scanning for a byte signature in the whole module search area.
/// </summary>
/// <param name="signature">The signature.</param>
/// <param name="result">The real offset of the signature, if found.</param>
/// <returns>true if the signature was found.</returns>
public bool TryScanModule(string signature, out IntPtr result)
{
try
{
result = this.ScanModule(signature);
return true;
}
catch (KeyNotFoundException)
{
result = IntPtr.Zero;
return false;
}
}
/// <summary>
/// Resolve a RVA address.
/// </summary>
@ -224,6 +309,26 @@ namespace Dalamud.Game
return scanRet;
}
/// <summary>
/// Try scanning for a byte signature in the .text section.
/// </summary>
/// <param name="signature">The signature.</param>
/// <param name="result">The real offset of the signature, if found.</param>
/// <returns>true if the signature was found.</returns>
public bool TryScanText(string signature, out IntPtr result)
{
try
{
result = this.ScanText(signature);
return true;
}
catch (KeyNotFoundException)
{
result = IntPtr.Zero;
return false;
}
}
/// <summary>
/// Free the memory of the copied module search area on object disposal, if applicable.
/// </summary>

View file

@ -340,8 +340,7 @@ namespace Dalamud.Interface.Internal
if (configuration.SavedStyles == null || configuration.SavedStyles.All(x => x.Name != StyleModel.DalamudStandard.Name))
{
configuration.SavedStyles = new List<StyleModel>();
configuration.SavedStyles.Add(StyleModel.DalamudStandard);
configuration.SavedStyles = new List<StyleModel> { StyleModel.DalamudStandard };
configuration.ChosenStyle = StyleModel.DalamudStandard.Name;
}

View file

@ -25,7 +25,6 @@ using Dalamud.Plugin.Internal.Types;
using Dalamud.Utility;
using ImGuiNET;
using ImGuiScene;
using Microsoft.VisualBasic;
namespace Dalamud.Interface.Internal.Windows
{
@ -56,7 +55,7 @@ namespace Dalamud.Interface.Internal.Windows
private string[] testerImagePaths = new string[5];
private string testerIconPath = string.Empty;
private TextureWrap?[]? testerImages;
private TextureWrap?[] testerImages;
private TextureWrap? testerIcon;
private bool testerError = false;
@ -76,9 +75,8 @@ namespace Dalamud.Interface.Internal.Windows
private List<AvailablePluginUpdate> pluginListUpdatable = new();
private bool hasDevPlugins = false;
private bool downloadingIcons = false;
private Dictionary<string, (bool IsDownloaded, TextureWrap?[]? Textures)> pluginImagesMap = new();
private Dictionary<string, (bool IsDownloaded, TextureWrap? Texture)> pluginIconMap = new();
private Dictionary<string, TextureWrap?> pluginIconMap = new();
private Dictionary<string, TextureWrap?[]> pluginImagesMap = new();
private string searchText = string.Empty;
@ -197,8 +195,6 @@ namespace Dalamud.Interface.Internal.Windows
{
this.pluginIconMap.Clear();
this.pluginImagesMap.Clear();
this.DownloadPluginIcons();
}
private static string? GetPluginIconUrl(PluginManifest manifest, bool isThirdParty, bool isTesting)
@ -209,10 +205,18 @@ namespace Dalamud.Interface.Internal.Windows
return MainRepoImageUrl.Format(isTesting ? "testing" : "plugins", manifest.InternalName, "icon.png");
}
private static List<string>? GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting)
private static List<string?> GetPluginImageUrls(PluginManifest manifest, bool isThirdParty, bool isTesting)
{
if (isThirdParty)
{
if (manifest.ImageUrls.Count > 5)
{
Log.Warning($"Plugin {manifest.InternalName} has too many images");
return manifest.ImageUrls.Take(5).ToList();
}
return manifest.ImageUrls;
}
var output = new List<string>();
for (var i = 1; i <= 5; i++)
@ -223,6 +227,36 @@ namespace Dalamud.Interface.Internal.Windows
return output;
}
private static FileInfo? GetPluginIconFileInfo(LocalPlugin? plugin)
{
var pluginDir = plugin.DllFile.Directory;
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", "icon.png"));
if (devUrl.Exists)
return devUrl;
return null;
}
private static List<FileInfo?> GetPluginImageFileInfos(LocalPlugin? plugin)
{
var pluginDir = plugin.DllFile.Directory;
var output = new List<FileInfo>();
for (var i = 1; i <= 5; i++)
{
var devUrl = new FileInfo(Path.Combine(pluginDir.FullName, "images", $"image{i}.png"));
if (devUrl.Exists)
{
output.Add(devUrl);
continue;
}
output.Add(null);
}
return output;
}
private void DrawHeader()
{
var style = ImGui.GetStyle();
@ -555,7 +589,6 @@ namespace Dalamud.Interface.Internal.Windows
}
}
// TODO: Technically, we really should just load images from a devplugin folder
private void DrawImageTester()
{
var sectionSize = ImGuiHelpers.GlobalScale * 66;
@ -800,7 +833,7 @@ namespace Dalamud.Interface.Internal.Windows
return ready;
}
private bool DrawPluginCollapsingHeader(string label, PluginManifest manifest, bool trouble, bool updateAvailable, bool isNew, Action drawContextMenuAction, int index)
private bool DrawPluginCollapsingHeader(string label, LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, bool trouble, bool updateAvailable, bool isNew, Action drawContextMenuAction, int index)
{
ImGui.Separator();
@ -837,12 +870,20 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.SetCursorPos(startCursor);
var hasIcon = this.pluginIconMap.TryGetValue(manifest.InternalName, out var icon);
var iconTex = this.defaultIcon;
if (hasIcon && icon.IsDownloaded && icon.Texture != null)
var hasIcon = this.pluginIconMap.TryGetValue(manifest.InternalName, out var cachedIconTex);
if (!hasIcon)
{
iconTex = icon.Texture;
this.pluginIconMap.Add(manifest.InternalName, null);
Task.Run(async () => await this.DownloadPluginIconAsync(plugin, manifest, isThirdParty));
}
else if (cachedIconTex != null)
{
iconTex = cachedIconTex;
}
else
{
// nothing
}
var iconSize = ImGuiHelpers.ScaledVector2(64, 64);
@ -868,13 +909,14 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.SameLine();
var cursor = ImGui.GetCursorPos();
// Name
ImGui.Text(label);
// Download count
var downloadCountText = manifest.DownloadCount > 0
? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount)
: Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author);
? Locs.PluginBody_AuthorWithDownloadCount(manifest.Author, manifest.DownloadCount)
: Locs.PluginBody_AuthorWithDownloadCountUnavailable(manifest.Author);
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadCountText);
@ -927,7 +969,8 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.PushID($"available{index}{manifest.InternalName}");
if (this.DrawPluginCollapsingHeader(label, manifest, false, false, !wasSeen, () => this.DrawAvailablePluginContextMenu(manifest), index))
var isThirdParty = manifest.SourceRepo.IsThirdParty;
if (this.DrawPluginCollapsingHeader(label, null, manifest, isThirdParty, false, false, !wasSeen, () => this.DrawAvailablePluginContextMenu(manifest), index))
{
if (!wasSeen)
configuration.SeenPluginInternalName.Add(manifest.InternalName);
@ -996,7 +1039,7 @@ namespace Dalamud.Interface.Internal.Windows
ImGuiHelpers.ScaledDummy(5);
if (this.DrawPluginImages(manifest, index, manifest.SourceRepo.IsThirdParty))
if (this.DrawPluginImages(null, manifest, isThirdParty, index))
ImGuiHelpers.ScaledDummy(5);
ImGui.Unindent();
@ -1124,15 +1167,22 @@ namespace Dalamud.Interface.Internal.Windows
}
// Outdated API level
if (plugin.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel)
if (plugin.IsOutdated)
{
label += Locs.PluginTitleMod_OutdatedError;
trouble = true;
}
// Banned
if (plugin.IsBanned)
{
label += Locs.PluginTitleMod_BannedError;
trouble = true;
}
ImGui.PushID($"installed{index}{plugin.Manifest.InternalName}");
if (this.DrawPluginCollapsingHeader(label, plugin.Manifest, trouble, availablePluginUpdate != default, false, () => this.DrawInstalledPluginContextMenu(plugin), index))
if (this.DrawPluginCollapsingHeader(label, plugin, plugin.Manifest, plugin.Manifest.IsThirdParty, trouble, availablePluginUpdate != default, false, () => this.DrawInstalledPluginContextMenu(plugin), index))
{
if (!this.WasPluginSeen(plugin.Manifest.InternalName))
configuration.SeenPluginInternalName.Add(plugin.Manifest.InternalName);
@ -1154,7 +1204,7 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.DalamudGrey3, downloadText);
var isThirdParty = !string.IsNullOrEmpty(manifest.InstalledFromUrl);
var isThirdParty = manifest.IsThirdParty;
// Installed from
if (plugin.IsDev)
@ -1174,13 +1224,20 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.TextWrapped(manifest.Description);
}
if (plugin.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel)
if (plugin.IsOutdated)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGui.TextWrapped(Locs.PluginBody_Outdated);
ImGui.PopStyleColor();
}
if (plugin.IsBanned)
{
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
ImGui.TextWrapped(Locs.PluginBody_Banned);
ImGui.PopStyleColor();
}
// Available commands (if loaded)
if (plugin.IsLoaded)
{
@ -1201,6 +1258,7 @@ namespace Dalamud.Interface.Internal.Windows
// Controls
this.DrawPluginControlButton(plugin);
this.DrawDevPluginButtons(plugin);
this.DrawDeletePluginButton(plugin);
this.DrawVisitRepoUrlButton(plugin.Manifest.RepoUrl);
if (availablePluginUpdate != default)
@ -1217,9 +1275,8 @@ namespace Dalamud.Interface.Internal.Windows
ImGuiHelpers.ScaledDummy(5);
this.DrawPluginImages(manifest, index, isThirdParty);
ImGuiHelpers.ScaledDummy(5);
if (this.DrawPluginImages(plugin, manifest, isThirdParty, index))
ImGuiHelpers.ScaledDummy(5);
ImGui.Unindent();
}
@ -1263,7 +1320,7 @@ namespace Dalamud.Interface.Internal.Windows
var disabled = this.updateStatus == OperationStatus.InProgress || this.installStatus == OperationStatus.InProgress;
// Disable everything if the plugin is outdated
disabled = disabled || (plugin.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel && !configuration.LoadAllApiLevels);
disabled = disabled || (plugin.IsOutdated && !configuration.LoadAllApiLevels) || plugin.IsBanned;
if (plugin.State == PluginState.InProgress)
{
@ -1411,7 +1468,6 @@ namespace Dalamud.Interface.Internal.Windows
private void DrawDevPluginButtons(LocalPlugin localPlugin)
{
var configuration = Service<DalamudConfiguration>.Get();
var pluginManager = Service<PluginManager>.Get();
if (localPlugin is LocalDevPlugin plugin)
{
@ -1454,33 +1510,40 @@ namespace Dalamud.Interface.Internal.Windows
{
ImGui.SetTooltip(Locs.PluginButtonToolTip_AutomaticReloading);
}
}
}
// Delete
if (plugin.State == PluginState.Unloaded)
private void DrawDeletePluginButton(LocalPlugin plugin)
{
var unloaded = plugin.State == PluginState.Unloaded;
var showButton = unloaded && (plugin.IsDev || plugin.IsOutdated || plugin.IsBanned);
if (!showButton)
return;
var pluginManager = Service<PluginManager>.Get();
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt))
{
try
{
ImGui.SameLine();
if (ImGuiComponents.IconButton(FontAwesomeIcon.TrashAlt))
{
try
{
plugin.DllFile.Delete();
pluginManager.RemovePlugin(plugin);
}
catch (Exception ex)
{
Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}");
this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name);
this.errorModalDrawing = true;
this.errorModalOnNextFrame = true;
}
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Locs.PluginBody_DeleteDevPlugin);
}
plugin.DllFile.Delete();
pluginManager.RemovePlugin(plugin);
}
catch (Exception ex)
{
Log.Error(ex, $"Plugin installer threw an error during removal of {plugin.Name}");
this.errorModalMessage = Locs.ErrorModal_DeleteFail(plugin.Name);
this.errorModalDrawing = true;
this.errorModalOnNextFrame = true;
}
}
if (ImGui.IsItemHovered())
{
ImGui.SetTooltip(Locs.PluginButtonToolTip_DeletePlugin);
}
}
@ -1510,18 +1573,18 @@ namespace Dalamud.Interface.Internal.Windows
}
}
private bool DrawPluginImages(PluginManifest manifest, int index, bool isThirdParty)
private bool DrawPluginImages(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty, int index)
{
if (!this.pluginImagesMap.TryGetValue(manifest.InternalName, out var images))
var hasImages = this.pluginImagesMap.TryGetValue(manifest.InternalName, out var imageTextures);
if (!hasImages)
{
Task.Run(() => this.DownloadPluginImagesAsync(manifest, isThirdParty));
this.pluginImagesMap.Add(manifest.InternalName, Array.Empty<TextureWrap>());
Task.Run(async () => await this.DownloadPluginImagesAsync(plugin, manifest, isThirdParty));
return false;
}
if (!images.IsDownloaded)
return false;
if (images.Textures == null)
if (imageTextures.Length == 0)
return false;
const float thumbFactor = 2.7f;
@ -1534,42 +1597,39 @@ namespace Dalamud.Interface.Internal.Windows
if (ImGui.BeginChild($"plugin{index}ImageScrolling", new Vector2(width - (70 * ImGuiHelpers.GlobalScale), (PluginImageHeight / thumbFactor) + scrollBarSize), false, ImGuiWindowFlags.HorizontalScrollbar | ImGuiWindowFlags.NoScrollWithMouse | ImGuiWindowFlags.NoBackground))
{
if (images.Textures != null && images.Textures is { Length: > 0 })
for (var i = 0; i < imageTextures.Length; i++)
{
for (var i = 0; i < images.Textures.Length; i++)
var image = imageTextures[i];
if (image == null)
continue;
ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
var popupId = $"plugin{index}image{i}";
if (ImGui.BeginPopup(popupId))
{
var popupId = $"plugin{index}image{i}";
var image = images.Textures[i];
if (image == null)
continue;
if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height)))
ImGui.CloseCurrentPopup();
ImGui.PushStyleVar(ImGuiStyleVar.PopupBorderSize, 0);
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
ImGui.EndPopup();
}
if (ImGui.BeginPopup(popupId))
{
if (ImGui.ImageButton(image.ImGuiHandle, new Vector2(image.Width, image.Height)))
ImGui.CloseCurrentPopup();
ImGui.PopStyleVar(3);
ImGui.EndPopup();
}
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
ImGui.PopStyleVar(3);
if (ImGui.ImageButton(image.ImGuiHandle, ImGuiHelpers.ScaledVector2(image.Width / thumbFactor, image.Height / thumbFactor)))
ImGui.OpenPopup(popupId);
ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, Vector2.Zero);
ImGui.PopStyleVar();
if (ImGui.ImageButton(image.ImGuiHandle, ImGuiHelpers.ScaledVector2(image.Width / thumbFactor, image.Height / thumbFactor)))
ImGui.OpenPopup(popupId);
ImGui.PopStyleVar();
if (i < images.Textures.Length - 1)
{
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
}
if (i < imageTextures.Length - 1)
{
ImGui.SameLine();
ImGuiHelpers.ScaledDummy(5);
ImGui.SameLine();
}
}
}
@ -1612,8 +1672,6 @@ namespace Dalamud.Interface.Internal.Windows
.ToList();
this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList();
this.ResortPlugins();
this.DownloadPluginIcons();
}
private void OnInstalledPluginsChanged()
@ -1624,8 +1682,6 @@ namespace Dalamud.Interface.Internal.Windows
this.pluginListUpdatable = pluginManager.UpdatablePlugins.ToList();
this.hasDevPlugins = this.pluginListInstalled.Any(plugin => plugin.IsDev);
this.ResortPlugins();
this.DownloadPluginIcons();
}
private void ResortPlugins()
@ -1705,133 +1761,173 @@ namespace Dalamud.Interface.Internal.Windows
this.errorModalOnNextFrame = true;
}
private void DownloadPluginIcons()
private async Task DownloadPluginIconAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
{
if (this.downloadingIcons)
var interfaceManager = Service<InterfaceManager>.Get();
var pluginManager = Service<PluginManager>.Get();
static bool ValidateIcon(TextureWrap icon, string loc)
{
Log.Error("Already downloading icons, skipping...");
if (icon == null)
return false;
if (icon.Height > PluginIconHeight || icon.Width > PluginIconWidth)
{
Log.Error($"Icon at {loc} was not of the correct resolution.");
return false;
}
if (icon.Height != icon.Width)
{
Log.Error($"Icon at {loc} was not square.");
return false;
}
return true;
}
if (plugin != null && plugin.IsDev)
{
var file = GetPluginIconFileInfo(plugin);
if (file != null)
{
Log.Verbose($"Fetching icon for {manifest.InternalName} from {file.FullName}");
var icon = interfaceManager.LoadImage(file.FullName);
if (!ValidateIcon(icon, file.FullName))
return;
this.pluginIconMap[manifest.InternalName] = icon;
Log.Verbose($"Plugin icon for {manifest.InternalName} loaded from disk");
}
return;
}
this.downloadingIcons = true;
var pluginManager = Service<PluginManager>.Get();
Log.Verbose("Start downloading plugin icons...");
Task.Run(async () =>
{
var plugins = pluginManager.AvailablePlugins.Select(x => x);
foreach (var pluginManifest in plugins)
{
var useTesting = pluginManager.UseTesting(pluginManifest);
if (!this.pluginIconMap.ContainsKey(pluginManifest.InternalName))
await this.DownloadPluginIconAsync(pluginManifest, useTesting);
}
}).ContinueWith(t =>
{
Log.Verbose($"Icon download finished, faulted: {t.IsFaulted}");
this.downloadingIcons = false;
});
}
private async Task DownloadPluginIconAsync(RemotePluginManifest manifest, bool isTesting)
{
var interfaceManager = Service<InterfaceManager>.Get();
Log.Verbose($"Downloading icon for {manifest.InternalName}");
this.pluginIconMap.Add(manifest.InternalName, (false, null));
var url = GetPluginIconUrl(manifest, manifest.SourceRepo.IsThirdParty, isTesting);
Log.Verbose($"Icon from {url}");
var useTesting = pluginManager.UseTesting(manifest);
var url = GetPluginIconUrl(manifest, isThirdParty, useTesting);
if (url != null)
{
Log.Verbose($"Downloading icon for {manifest.InternalName} from {url}");
var data = await this.httpClient.GetAsync(url);
if (data.StatusCode == HttpStatusCode.NotFound)
return;
data.EnsureSuccessStatusCode();
var icon = interfaceManager.LoadImage(await data.Content.ReadAsByteArrayAsync());
if (icon != null)
{
if (icon.Height > PluginIconHeight || icon.Width > PluginIconWidth)
{
Log.Error($"Icon at {manifest.IconUrl} was not of the correct resolution.");
return;
}
if (!ValidateIcon(icon, url))
return;
if (icon.Height != icon.Width)
{
Log.Error($"Icon at {manifest.IconUrl} was not square.");
return;
}
this.pluginIconMap[manifest.InternalName] = icon;
Log.Verbose($"Plugin icon for {manifest.InternalName} downloaded");
this.pluginIconMap[manifest.InternalName] = (true, icon);
}
return;
}
Log.Verbose($"Icon for {manifest.InternalName} is not available");
}
private async Task DownloadPluginImagesAsync(PluginManifest manifest, bool isThirdParty)
private async Task DownloadPluginImagesAsync(LocalPlugin? plugin, PluginManifest manifest, bool isThirdParty)
{
var interfaceManager = Service<InterfaceManager>.Get();
var pluginManager = Service<PluginManager>.Get();
Log.Verbose($"Downloading images for {manifest.InternalName}");
this.pluginImagesMap.Add(manifest.InternalName, (false, null));
var urls = GetPluginImageUrls(manifest, isThirdParty, pluginManager.UseTesting(manifest));
var didAny = false;
if (urls != null)
static bool ValidateImage(TextureWrap image, string loc)
{
if (urls.Count > 5)
if (image == null)
return false;
if (image.Height != PluginImageHeight || image.Width != PluginImageWidth)
{
Log.Error($"Plugin {manifest.InternalName} has too many images.");
return;
Log.Error($"Image at {loc} was not of the correct resolution.");
return false;
}
return true;
}
if (plugin != null && plugin.IsDev)
{
var files = GetPluginImageFileInfos(plugin);
if (files != null)
{
var didAny = false;
var pluginImages = new TextureWrap[files.Count];
for (var i = 0; i < files.Count; i++)
{
var file = files[i];
if (file == null)
continue;
Log.Verbose($"Loading image{i + 1} for {manifest.InternalName} from {file.FullName}");
var image = interfaceManager.LoadImage(await File.ReadAllBytesAsync(file.FullName));
if (!ValidateImage(image, file.FullName))
continue;
Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} loaded from disk");
pluginImages[i] = image;
didAny = true;
}
if (didAny)
{
Log.Verbose($"Plugin images for {manifest.InternalName} loaded from disk");
this.pluginImagesMap[manifest.InternalName] = pluginImages;
return;
}
}
// Dev plugins are loaded from disk only
return;
}
var useTesting = pluginManager.UseTesting(manifest);
var urls = GetPluginImageUrls(manifest, isThirdParty, useTesting);
if (urls != null)
{
var didAny = false;
var pluginImages = new TextureWrap[urls.Count];
for (var i = 0; i < urls.Count; i++)
{
var data = await this.httpClient.GetAsync(urls[i]);
var url = urls[i];
Serilog.Log.Information($"Download from {urls[i]}");
Log.Verbose($"Downloading image{i + 1} for {manifest.InternalName} from {url}");
var data = await this.httpClient.GetAsync(url);
if (data.StatusCode == HttpStatusCode.NotFound)
continue;
data.EnsureSuccessStatusCode();
var image = interfaceManager.LoadImage(await data.Content.ReadAsByteArrayAsync());
if (image == null)
{
return;
}
if (image.Height != PluginImageHeight || image.Width != PluginImageWidth)
{
Log.Error($"Image at {urls[i]} was not of the correct resolution.");
return;
}
didAny = true;
if (!ValidateImage(image, url))
continue;
Log.Verbose($"Plugin image{i + 1} for {manifest.InternalName} downloaded");
pluginImages[i] = image;
didAny = true;
}
if (didAny)
{
this.pluginImagesMap[manifest.InternalName] = (true, pluginImages);
Log.Verbose($"Plugin images for {manifest.InternalName} downloaded");
this.pluginImagesMap[manifest.InternalName] = pluginImages;
return;
}
}
Log.Verbose($"Plugin images for {manifest.InternalName} downloaded");
Log.Verbose($"Images for {manifest.InternalName} are not available");
}
[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1201:Elements should appear in the correct order", Justification = "Disregard here")]
@ -1919,6 +2015,8 @@ namespace Dalamud.Interface.Internal.Windows
public static string PluginTitleMod_OutdatedError => Loc.Localize("InstallerOutdatedError", " (outdated)");
public static string PluginTitleMod_BannedError => Loc.Localize("InstallerOutdatedError", " (banned)");
public static string PluginTitleMod_New => Loc.Localize("InstallerNewPlugin ", " New!");
#endregion
@ -1953,6 +2051,8 @@ namespace Dalamud.Interface.Internal.Windows
public static string PluginBody_Outdated => Loc.Localize("InstallerOutdatedPluginBody ", "This plugin is outdated and incompatible at the moment. Please wait for it to be updated by its author.");
public static string PluginBody_Banned => Loc.Localize("InstallerBannedPluginBody ", "This plugin version is banned and not available at the moment. Please wait for it to be updated by its author.");
#endregion
#region Plugin buttons

View file

@ -59,6 +59,7 @@ namespace Dalamud.Interface.Internal.Windows
private bool printPluginsWelcomeMsg;
private bool autoUpdatePlugins;
private bool doButtonsSystemMenu;
private bool disableRmtFiltering;
#region Experimental
@ -99,6 +100,7 @@ namespace Dalamud.Interface.Internal.Windows
this.printPluginsWelcomeMsg = configuration.PrintPluginsWelcomeMsg;
this.autoUpdatePlugins = configuration.AutoUpdatePlugins;
this.doButtonsSystemMenu = configuration.DoButtonsSystemMenu;
this.disableRmtFiltering = configuration.DisableRmtFiltering;
this.languages = Localization.ApplicableLangCodes.Prepend("en").ToArray();
try
@ -238,6 +240,9 @@ namespace Dalamud.Interface.Internal.Windows
ImGui.Checkbox(Loc.Localize("DalamudSettingsSystemMenu", "Dalamud buttons in system menu"), ref this.doButtonsSystemMenu);
ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingsSystemMenuMsgHint", "Add buttons for Dalamud plugins and settings to the system menu."));
ImGui.Checkbox(Loc.Localize("DalamudSettingsDisableRmtFiltering", "Disable RMT Filtering"), ref this.disableRmtFiltering);
ImGui.TextColored(this.hintTextColor, Loc.Localize("DalamudSettingsDisableRmtFilteringMsgHint", "Disable dalamud's built-in RMT ad filtering."));
}
private void DrawLookAndFeelTab()
@ -689,6 +694,7 @@ namespace Dalamud.Interface.Internal.Windows
configuration.PrintPluginsWelcomeMsg = this.printPluginsWelcomeMsg;
configuration.AutoUpdatePlugins = this.autoUpdatePlugins;
configuration.DoButtonsSystemMenu = this.doButtonsSystemMenu;
configuration.DisableRmtFiltering = this.disableRmtFiltering;
configuration.Save();

View file

@ -0,0 +1,22 @@
namespace Dalamud.Plugin.Internal.Exceptions
{
/// <summary>
/// This represents a banned plugin that attempted an operation.
/// </summary>
internal class BannedPluginException : PluginException
{
/// <summary>
/// Initializes a new instance of the <see cref="BannedPluginException"/> class.
/// </summary>
/// <param name="message">The message describing the invalid operation.</param>
public BannedPluginException(string message)
{
this.Message = message;
}
/// <summary>
/// Gets the message describing the invalid operation.
/// </summary>
public override string Message { get; }
}
}

View file

@ -145,19 +145,17 @@ namespace Dalamud.Plugin.Internal
return;
}
var notificationManager = Service<NotificationManager>.Get();
try
{
this.Reload();
Service<NotificationManager>.Get()
.AddNotification(
$"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success);
notificationManager.AddNotification($"The DevPlugin '{this.Name} was reloaded successfully.", "Plugin reloaded!", NotificationType.Success);
}
catch (Exception ex)
{
Log.Error(ex, "DevPlugin reload failed.");
Service<NotificationManager>.Get()
.AddNotification(
$"The DevPlugin '{this.Name} could not be reloaded.", "Plugin reload failed!", NotificationType.Error);
notificationManager.AddNotification($"The DevPlugin '{this.Name} could not be reloaded.", "Plugin reload failed!", NotificationType.Error);
}
},
this.fileWatcherTokenSource.Token);

View file

@ -86,10 +86,8 @@ namespace Dalamud.Plugin.Internal
var assemblyVersion = this.pluginAssembly.GetName().Version;
// Files that may or may not exist
// Although it is conditionally used here, we need to set the initial value regardless.
this.manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile);
this.testingFile = LocalPluginManifest.GetTestingFile(this.DllFile);
// If the parameter manifest was null
if (manifest == null)
@ -108,28 +106,32 @@ namespace Dalamud.Plugin.Internal
// Save the manifest to disk so there won't be any problems later.
// We'll update the name property after it can be retrieved from the instance.
var manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
this.Manifest.Save(manifestFile);
this.Manifest.Save(this.manifestFile);
}
else
{
this.Manifest = manifest;
}
// This bit converts from ".disabled" functionality to using the manifest.
// This converts from the ".disabled" file feature to the manifest instead.
this.disabledFile = LocalPluginManifest.GetDisabledFile(this.DllFile);
if (this.disabledFile.Exists)
{
this.Manifest.Disabled = true;
this.disabledFile.Delete();
}
// This bit converts from ".testing" functionality to using the manifest.
// This converts from the ".testing" file feature to the manifest instead.
this.testingFile = LocalPluginManifest.GetTestingFile(this.DllFile);
if (this.testingFile.Exists)
{
this.Manifest.Testing = true;
this.testingFile.Delete();
}
var pluginManager = Service<PluginManager>.Get();
this.IsBanned = pluginManager.IsManifestBanned(this.Manifest);
this.SaveManifest();
}
@ -157,7 +159,7 @@ namespace Dalamud.Plugin.Internal
/// Gets the AssemblyName plugin, populated during <see cref="Load(PluginLoadReason, bool)"/>.
/// </summary>
/// <returns>Plugin type.</returns>
public AssemblyName AssemblyName { get; private set; } = null;
public AssemblyName? AssemblyName { get; private set; } = null;
/// <summary>
/// Gets the plugin name, directly from the plugin or if it is not loaded from the manifest.
@ -174,11 +176,21 @@ namespace Dalamud.Plugin.Internal
/// </summary>
public bool IsDisabled => this.Manifest.Disabled;
/// <summary>
/// Gets a value indicating whether this plugin's API level is out of date.
/// </summary>
public bool IsOutdated => this.Manifest.DalamudApiLevel < PluginManager.DalamudApiLevel;
/// <summary>
/// Gets a value indicating whether the plugin is for testing use only.
/// </summary>
public bool IsTesting => this.Manifest.IsTestingExclusive || this.Manifest.Testing;
/// <summary>
/// Gets a value indicating whether this plugin has been banned.
/// </summary>
public bool IsBanned { get; }
/// <summary>
/// Gets a value indicating whether this plugin is dev plugin.
/// </summary>
@ -223,6 +235,9 @@ namespace Dalamud.Plugin.Internal
throw new InvalidPluginOperationException($"Unable to load {this.Name}, unload previously faulted, restart Dalamud");
}
if (pluginManager.IsManifestBanned(this.Manifest))
throw new BannedPluginException($"Unable to load {this.Name}, banned");
if (this.Manifest.ApplicableVersion < startInfo.GameVersion)
throw new InvalidPluginOperationException($"Unable to load {this.Name}, no applicable version");
@ -255,7 +270,9 @@ namespace Dalamud.Plugin.Internal
{
var manifestFile = LocalPluginManifest.GetManifestFile(this.DllFile);
if (manifestFile.Exists)
{
this.Manifest = LocalPluginManifest.Load(manifestFile);
}
}
}

View file

@ -475,6 +475,9 @@ namespace Dalamud.Plugin.Internal
var dllFile = LocalPluginManifest.GetPluginFile(outputDir, repoManifest);
var manifestFile = LocalPluginManifest.GetManifestFile(dllFile);
// We need to save the repoManifest due to how the repo fills in some fields that authors are not expected to use.
File.WriteAllText(manifestFile.FullName, JsonConvert.SerializeObject(repoManifest, Formatting.Indented));
// Reload as a local manifest, add some attributes, and save again.
var manifest = LocalPluginManifest.Load(manifestFile);
@ -545,26 +548,31 @@ namespace Dalamud.Plugin.Internal
}
catch (InvalidPluginException)
{
PluginLocations.Remove(plugin.AssemblyName.FullName);
PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty);
throw;
}
catch (BannedPluginException)
{
// Out of date plugins get added so they can be updated.
Log.Information($"Plugin was banned, adding anyways: {dllFile.Name}");
}
catch (Exception ex)
{
if (plugin.IsDev)
{
// Dev plugins always get added to the list so they can be fiddled with in the UI
Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}");
Log.Information(ex, $"Dev plugin failed to load, adding anyways: {dllFile.Name}");
plugin.Disable(); // Disable here, otherwise you can't enable+load later
}
else if (plugin.Manifest.DalamudApiLevel < DalamudApiLevel)
else if (plugin.IsOutdated)
{
// Out of date plugins get added so they can be updated.
Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}");
Log.Information(ex, $"Plugin was outdated, adding anyways: {dllFile.Name}");
// plugin.Disable(); // Don't disable, or it gets deleted next boot.
}
else
{
PluginLocations.Remove(plugin.AssemblyName.FullName);
PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty);
throw;
}
}
@ -584,7 +592,7 @@ namespace Dalamud.Plugin.Internal
throw new InvalidPluginOperationException($"Unable to remove {plugin.Name}, not unloaded");
this.InstalledPlugins = this.InstalledPlugins.Remove(plugin);
PluginLocations.Remove(plugin.AssemblyName.FullName);
PluginLocations.Remove(plugin.AssemblyName?.FullName ?? string.Empty);
this.NotifyInstalledPluginsChanged();
this.NotifyAvailablePluginsChanged();
@ -936,7 +944,12 @@ namespace Dalamud.Plugin.Internal
return true;
}
private bool IsManifestBanned(PluginManifest manifest)
/// <summary>
/// Determine if a plugin has been banned by inspecting the manifest.
/// </summary>
/// <param name="manifest">Manifest to inspect.</param>
/// <returns>A value indicating whether the plugin/manifest has been banned.</returns>
public bool IsManifestBanned(PluginManifest manifest)
{
return this.bannedPlugins.Any(ban => ban.Name == manifest.InternalName && ban.AssemblyVersion == manifest.AssemblyVersion);
}

View file

@ -24,10 +24,17 @@ namespace Dalamud.Plugin.Internal.Types
/// <summary>
/// Gets or sets the 3rd party repo URL that this plugin was installed from. Used to display where the plugin was
/// sourced from on the installed plugin view. This should not be included in the plugin master.
/// sourced from on the installed plugin view. This should not be included in the plugin master. This value is null
/// when installed from the main repo.
/// </summary>
public string InstalledFromUrl { get; set; }
/// <summary>
/// Gets a value indicating whether this manifest is associated with a plugin that was installed from a third party
/// repo. Unless the manifest has been manually modified, this is determined by the InstalledFromUrl being null.
/// </summary>
public bool IsThirdParty => !string.IsNullOrEmpty(this.InstalledFromUrl);
/// <summary>
/// Save a plugin manifest to file.
/// </summary>