refactor: update to sdk and use DalamudPackager

This commit is contained in:
Anna 2020-12-27 12:13:57 -05:00
parent a3affe916a
commit d14f3fcd2c
Signed by: anna
GPG Key ID: 0B391D8F06FCD9E0
9 changed files with 117 additions and 183 deletions

1
.gitignore vendored
View File

@ -34,6 +34,7 @@ bld/
# Visual Studio 2015/2017 cache/options directory # Visual Studio 2015/2017 cache/options directory
.vs/ .vs/
.idea/
# Uncomment if you have tasks that create the project's static files in wwwroot # Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/ #wwwroot/

View File

@ -4,15 +4,15 @@ using System.Linq;
namespace GoodMemory { namespace GoodMemory {
public static class ActionTypeExt { public static class ActionTypeExt {
private static readonly ActionType[] VALID = (ActionType[])Enum.GetValues(typeof(ActionType)); private static readonly ActionType[] Valid = (ActionType[])Enum.GetValues(typeof(ActionType));
public static bool IsValidAction(ItemAction action) { public static bool IsValidAction(ItemAction? action) {
if (action == null || action.RowId == 0) { if (action == null || action.RowId == 0) {
return false; return false;
} }
ActionType type = (ActionType)action.Type; var type = (ActionType)action.Type;
return VALID.Contains(type); return Valid.Contains(type);
} }
} }

View File

@ -0,0 +1,10 @@
<Project>
<Target Name="PackagePlugin" AfterTargets="Build" Condition="'$(Configuration)' == 'Release'">
<DalamudPackager ProjectDir="$(ProjectDir)"
OutputPath="$(OutputPath)"
AssemblyName="$(AssemblyName)"
VersionComponents="3"
ManifestType="yaml"
MakeZip="true"/>
</Target>
</Project>

View File

@ -4,58 +4,57 @@ using System.Runtime.InteropServices;
namespace GoodMemory { namespace GoodMemory {
public class GameFunctions { public class GameFunctions {
private readonly Plugin plugin; private Plugin Plugin { get; }
private delegate byte HasItemActionUnlockedDelegate(long itemActionId); private delegate byte HasItemActionUnlockedDelegate(long itemActionId);
private readonly HasItemActionUnlockedDelegate hasItemActionUnlocked;
private readonly HasItemActionUnlockedDelegate _hasItemActionUnlocked;
private delegate byte HasCardDelegate(IntPtr localPlayer, ushort cardId); private delegate byte HasCardDelegate(IntPtr localPlayer, ushort cardId);
private readonly HasCardDelegate hasCard;
private readonly IntPtr cardStaticAddr; private readonly HasCardDelegate _hasCard;
private readonly IntPtr _cardStaticAddr;
public GameFunctions(Plugin plugin) { public GameFunctions(Plugin plugin) {
this.plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null"); this.Plugin = plugin ?? throw new ArgumentNullException(nameof(plugin), "Plugin cannot be null");
IntPtr hasIAUnlockedPtr = plugin.Interface.TargetModuleScanner.ScanText("48 83 EC 28 E8 ?? ?? ?? ?? 48 85 C0 0F 84 ?? ?? ?? ??"); var hasIaUnlockedPtr = plugin.Interface.TargetModuleScanner.ScanText("48 83 EC 28 E8 ?? ?? ?? ?? 48 85 C0 0F 84 ?? ?? ?? ??");
IntPtr hasCardPtr = plugin.Interface.TargetModuleScanner.ScanText("40 53 48 83 EC 20 48 8B D9 66 85 D2 74 ??"); var hasCardPtr = plugin.Interface.TargetModuleScanner.ScanText("40 53 48 83 EC 20 48 8B D9 66 85 D2 74 ??");
this.cardStaticAddr = plugin.Interface.TargetModuleScanner.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 84 C0 74 ?? 48 8B 53 ?? 48 8D 4B ?? 48 83 C2 0C 48 8D 14 ?? E8 ?? ?? ?? ?? 40 FE C7 40 3A FD 72 ?? 48 8B 5C 24 ??"); this._cardStaticAddr = plugin.Interface.TargetModuleScanner.GetStaticAddressFromSig("48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? 84 C0 74 ?? 48 8B 53 ?? 48 8D 4B ?? 48 83 C2 0C 48 8D 14 ?? E8 ?? ?? ?? ?? 40 FE C7 40 3A FD 72 ?? 48 8B 5C 24 ??");
if (hasIAUnlockedPtr == IntPtr.Zero || hasCardPtr == IntPtr.Zero || this.cardStaticAddr == IntPtr.Zero) { if (hasIaUnlockedPtr == IntPtr.Zero || hasCardPtr == IntPtr.Zero || this._cardStaticAddr == IntPtr.Zero) {
throw new ApplicationException("Could not get pointers for game functions"); throw new ApplicationException("Could not get pointers for game functions");
} }
this.hasItemActionUnlocked = Marshal.GetDelegateForFunctionPointer<HasItemActionUnlockedDelegate>(hasIAUnlockedPtr); this._hasItemActionUnlocked = Marshal.GetDelegateForFunctionPointer<HasItemActionUnlockedDelegate>(hasIaUnlockedPtr);
this.hasCard = Marshal.GetDelegateForFunctionPointer<HasCardDelegate>(hasCardPtr); this._hasCard = Marshal.GetDelegateForFunctionPointer<HasCardDelegate>(hasCardPtr);
} }
public bool HasAcquired(Item item) { public bool HasAcquired(Item item) {
ItemAction action = item.ItemAction.Value; var action = item.ItemAction.Value;
if (action == null) { if (action == null) {
return false; return false;
} }
ActionType type = (ActionType)action.Type; var type = (ActionType)action.Type;
if (type == ActionType.Cards) { if (type != ActionType.Cards) {
uint cardId = item.AdditionalData; return this.HasItemActionUnlocked(action.RowId);
TripleTriadCard card = this.plugin.Interface.Data.GetExcelSheet<TripleTriadCard>().GetRow(cardId);
if (card == null) {
return false;
}
return this.HasCard((ushort)card.RowId);
} }
return this.HasItemActionUnlocked(action.RowId); var cardId = item.AdditionalData;
var card = this.Plugin.Interface.Data.GetExcelSheet<TripleTriadCard>().GetRow(cardId);
return card != null && this.HasCard((ushort)card.RowId);
} }
private bool HasItemActionUnlocked(long itemActionId) { private bool HasItemActionUnlocked(long itemActionId) {
return this.hasItemActionUnlocked(itemActionId) == 1; return this._hasItemActionUnlocked(itemActionId) == 1;
} }
private bool HasCard(ushort cardId) { private bool HasCard(ushort cardId) {
return this.hasCard(this.cardStaticAddr, cardId) == 1; return this._hasCard(this._cardStaticAddr, cardId) == 1;
} }
} }
} }

View File

@ -1,64 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup>
<PropertyGroup> <LangVersion>8</LangVersion>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Nullable>enable</Nullable>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <AssemblyVersion>1.0.4</AssemblyVersion>
<ProjectGuid>{2B53DAAE-5F20-4EAF-8BC5-97FCE68C1738}</ProjectGuid> <FileVersion>1.0.4</FileVersion>
<OutputType>Library</OutputType> <TargetFramework>net48</TargetFramework>
<AppDesignerFolder>Properties</AppDesignerFolder> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>GoodMemory</RootNamespace> <PlatformTarget>x64</PlatformTarget>
<AssemblyName>GoodMemory</AssemblyName> </PropertyGroup>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion> <ItemGroup>
<FileAlignment>512</FileAlignment> <Reference Include="Dalamud, Version=5.2.1.1, Culture=neutral, PublicKeyToken=null">
<Deterministic>true</Deterministic> <HintPath>$(AppData)\XIVLauncher\addon\Hooks\Dalamud.dll</HintPath>
</PropertyGroup> <Private>false</Private>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> </Reference>
<DebugSymbols>true</DebugSymbols> <Reference Include="Lumina, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null">
<DebugType>full</DebugType> <HintPath>$(AppData)\XIVLauncher\addon\Hooks\Lumina.dll</HintPath>
<Optimize>false</Optimize> <Private>false</Private>
<OutputPath>bin\Debug\</OutputPath> </Reference>
<DefineConstants>DEBUG;TRACE</DefineConstants> <Reference Include="Lumina.Excel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<ErrorReport>prompt</ErrorReport> <HintPath>$(AppData)\XIVLauncher\addon\Hooks\Lumina.Excel.dll</HintPath>
<WarningLevel>4</WarningLevel> <Private>false</Private>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> </Reference>
</PropertyGroup> </ItemGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <ItemGroup>
<DebugType>pdbonly</DebugType> <PackageReference Include="DalamudPackager" Version="1.0.0" />
<Optimize>true</Optimize> </ItemGroup>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Dalamud.dll</HintPath>
</Reference>
<Reference Include="Lumina">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Lumina.dll</HintPath>
</Reference>
<Reference Include="Lumina.Excel, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\Lumina.Excel.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ActionType.cs" />
<Compile Include="GameFunctions.cs" />
<Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="GoodMemory.json" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View File

@ -1,10 +0,0 @@
{
"Author": "ascclemens",
"Name": "Good Memory",
"Description": "Adds an indicator in item tooltips to show whether you have acquired that item.",
"InternalName": "GoodMemory",
"AssemblyVersion": "1.0.4",
"RepoUrl": "https://git.sr.ht/~jkcclemens/GoodMemory",
"ApplicableVersion": "any",
"DalamudApiLevel": 2
}

View File

@ -0,0 +1,8 @@
author: ascclemens
name: GoodMemory
description: |-
Adds an indicator in item tooltips to show whether you have acquired that item.
This indicator is in the item's description near the bottom and only appears for
items that are unlockable, such as orchestrion rolls, minions, etc.
repo_url: https://git.sr.ht/~jkcclemens/GoodMemory

View File

@ -9,14 +9,14 @@ using System.Text;
namespace GoodMemory { namespace GoodMemory {
public class Plugin : IDalamudPlugin { public class Plugin : IDalamudPlugin {
private bool disposedValue; private bool _disposedValue;
public string Name => "Good Memory"; public string Name => "Good Memory";
public DalamudPluginInterface Interface { get; private set; } public DalamudPluginInterface Interface { get; private set; } = null!;
private GameFunctions Functions { get; set; } private GameFunctions Functions { get; set; } = null!;
private readonly IntPtr alloc = Marshal.AllocHGlobal(4096); private readonly IntPtr _alloc = Marshal.AllocHGlobal(4096);
private Hook<TooltipDelegate> tooltipHook; private Hook<TooltipDelegate>? _tooltipHook;
private unsafe delegate IntPtr TooltipDelegate(IntPtr a1, uint** a2, byte*** a3); private unsafe delegate IntPtr TooltipDelegate(IntPtr a1, uint** a2, byte*** a3);
@ -27,46 +27,48 @@ namespace GoodMemory {
} }
protected virtual void Dispose(bool disposing) { protected virtual void Dispose(bool disposing) {
if (!this.disposedValue) { if (this._disposedValue) {
if (disposing) { return;
this.tooltipHook?.Dispose();
Marshal.FreeHGlobal(this.alloc);
}
this.disposedValue = true;
} }
if (disposing) {
this._tooltipHook?.Dispose();
Marshal.FreeHGlobal(this._alloc);
}
this._disposedValue = true;
} }
public void Dispose() { public void Dispose() {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true); Dispose(true);
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
private void SetUpHook() { private void SetUpHook() {
IntPtr tooltipPtr = this.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 83 EC 50 48 8B 42 ??"); var tooltipPtr = this.Interface.TargetModuleScanner.ScanText("48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 83 EC 50 48 8B 42 ??");
if (tooltipPtr == IntPtr.Zero) { if (tooltipPtr == IntPtr.Zero) {
throw new ApplicationException("Could not set up tooltip hook because of null pointer"); throw new ApplicationException("Could not set up tooltip hook because of null pointer");
} }
unsafe { unsafe {
this.tooltipHook = new Hook<TooltipDelegate>(tooltipPtr, new TooltipDelegate(this.OnTooltip)); this._tooltipHook = new Hook<TooltipDelegate>(tooltipPtr, new TooltipDelegate(this.OnTooltip));
} }
this.tooltipHook.Enable(); this._tooltipHook.Enable();
} }
private unsafe void TooltipLogic(IntPtr a1, uint** a2, byte*** a3) { private unsafe void TooltipLogic(uint** a2, byte*** a3) {
// this can be replaced with a mid-func hook when reloaded hooks is in dalamud // this can be replaced with a mid-func hook when reloaded hooks is in dalamud
// but for now, do the same logic the func does and replace the text after // but for now, do the same logic the func does and replace the text after
uint* v3 = *(a2 + 4); var v3 = *(a2 + 4);
uint v9 = *(v3 + 4); var v9 = *(v3 + 4);
if ((v9 & 2) == 0) { if ((v9 & 2) == 0) {
return; return;
} }
ulong itemId = this.Interface.Framework.Gui.HoveredItem; var itemId = this.Interface.Framework.Gui.HoveredItem;
if (itemId > 2_000_000) { if (itemId > 2_000_000) {
return; return;
} }
@ -75,19 +77,19 @@ namespace GoodMemory {
itemId -= 1_000_000; itemId -= 1_000_000;
} }
Item item = this.Interface.Data.GetExcelSheet<Item>().GetRow((uint)itemId); var item = this.Interface.Data.GetExcelSheet<Item>().GetRow((uint)itemId);
if (item == null) { if (item == null) {
return; return;
} }
// get the pointer to the text // get the pointer to the text
byte** startPtr = *(a3 + 4) + 13; var startPtr = *(a3 + 4) + 13;
// get the text pointer // get the text pointer
byte* start = *startPtr; var start = *startPtr;
// work around function being called twice // work around function being called twice
if (start == (byte*)this.alloc) { if (start == (byte*)this._alloc) {
return; return;
} }
@ -95,7 +97,7 @@ namespace GoodMemory {
// Faded Copies // Faded Copies
if (item.FilterGroup == 12 && item.ItemUICategory.Value?.RowId == 94 && item.LevelItem.Value?.RowId == 1) { if (item.FilterGroup == 12 && item.ItemUICategory.Value?.RowId == 94 && item.LevelItem.Value?.RowId == 1) {
Item[] recipeResults = this.Interface.Data.GetExcelSheet<Recipe>() var recipeResults = this.Interface.Data.GetExcelSheet<Recipe>()
.Where(recipe => recipe.UnkStruct5.Any(ritem => ritem.ItemIngredient == item.RowId)) .Where(recipe => recipe.UnkStruct5.Any(ritem => ritem.ItemIngredient == item.RowId))
.Select(recipe => recipe.ItemResult.Value) .Select(recipe => recipe.ItemResult.Value)
.Where(result => result != null) .Where(result => result != null)
@ -103,16 +105,16 @@ namespace GoodMemory {
overwrite = ReadString(start); overwrite = ReadString(start);
foreach (Item result in recipeResults) { foreach (var result in recipeResults) {
ItemAction resultAction = result.ItemAction?.Value; var resultAction = result.ItemAction?.Value;
if (!ActionTypeExt.IsValidAction(resultAction)) { if (!ActionTypeExt.IsValidAction(resultAction)) {
continue; continue;
} }
Debug.Assert(resultAction != null, nameof(resultAction) + " != null"); Debug.Assert(resultAction != null, nameof(resultAction) + " != null");
uint orchId = resultAction.Data[0]; uint orchId = resultAction!.Data[0];
Orchestrion orch = this.Interface.Data.GetExcelSheet<Orchestrion>().GetRow(orchId); var orch = this.Interface.Data.GetExcelSheet<Orchestrion>().GetRow(orchId);
if (orch == null) { if (orch == null) {
continue; continue;
} }
@ -120,7 +122,7 @@ namespace GoodMemory {
this.AppendIfAcquired(ref overwrite, result, orch.Name); this.AppendIfAcquired(ref overwrite, result, orch.Name);
} }
} else { } else {
ItemAction action = item.ItemAction?.Value; var action = item.ItemAction?.Value;
if (!ActionTypeExt.IsValidAction(action)) { if (!ActionTypeExt.IsValidAction(action)) {
return; return;
@ -134,23 +136,23 @@ namespace GoodMemory {
} }
// write our replacement text into our own managed memory (4096 bytes) // write our replacement text into our own managed memory (4096 bytes)
WriteString((byte*)this.alloc, overwrite, true); WriteString((byte*)this._alloc, overwrite, true);
// overwrite the original pointer with our own // overwrite the original pointer with our own
*startPtr = (byte*)this.alloc; *startPtr = (byte*)this._alloc;
} }
private unsafe IntPtr OnTooltip(IntPtr a1, uint** a2, byte*** a3) { private unsafe IntPtr OnTooltip(IntPtr a1, uint** a2, byte*** a3) {
try { try {
this.TooltipLogic(a1, a2, a3); this.TooltipLogic(a2, a3);
} catch (Exception ex) { } catch (Exception ex) {
PluginLog.Error($"Could not modify tooltip:\n{ex.Message}\n{ex.StackTrace}"); PluginLog.Error($"Could not modify tooltip:\n{ex.Message}\n{ex.StackTrace}");
} }
return this.tooltipHook.Original(a1, a2, a3); return this._tooltipHook!.Original(a1, a2, a3);
} }
private void AppendIfAcquired(ref string txt, Item item, string name = null) { private void AppendIfAcquired(ref string txt, Item item, string? name = null) {
string yes; string yes;
string no; string no;
string acquired; string acquired;
@ -158,7 +160,6 @@ namespace GoodMemory {
string parenL; string parenL;
string parenR; string parenR;
switch (this.Interface.ClientState.ClientLanguage) { switch (this.Interface.ClientState.ClientLanguage) {
case Dalamud.ClientLanguage.English:
default: default:
acquired = "Acquired"; acquired = "Acquired";
colon = ": "; colon = ": ";
@ -193,18 +194,16 @@ namespace GoodMemory {
break; break;
} }
string has = this.Functions.HasAcquired(item) ? yes : no; var has = this.Functions.HasAcquired(item) ? yes : no;
if (name != null) { txt = name == null
txt = $"{txt}\n{acquired}{parenL}{name}{parenR}{colon}{has}"; ? $"{txt}\n{acquired}{colon}{has}"
} else { : $"{txt}\n{acquired}{parenL}{name}{parenR}{colon}{has}";
txt = $"{txt}\n{acquired}{colon}{has}";
}
} }
private unsafe static string ReadString(byte* ptr) { private unsafe static string ReadString(byte* ptr) {
int offset = 0; var offset = 0;
while (true) { while (true) {
byte b = *(ptr + offset); var b = *(ptr + offset);
if (b == 0) { if (b == 0) {
break; break;
} }
@ -216,8 +215,8 @@ namespace GoodMemory {
} }
private unsafe static void WriteString(byte* dst, string s, bool finalise = false) { private unsafe static void WriteString(byte* dst, string s, bool finalise = false) {
byte[] bytes = Encoding.UTF8.GetBytes(s); var bytes = Encoding.UTF8.GetBytes(s);
for (int i = 0; i < bytes.Length; i++) { for (var i = 0; i < bytes.Length; i++) {
*(dst + i) = bytes[i]; *(dst + i) = bytes[i];
} }

View File

@ -1,38 +0,0 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("GoodMemory")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GoodMemory")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("2b53daae-5f20-4eaf-8bc5-97fce68c1738")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.4")]
[assembly: AssemblyFileVersion("1.0.4")]
[assembly: NeutralResourcesLanguage("en-GB")]