mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Initial Commit
This commit is contained in:
commit
164f304cf6
38 changed files with 2796 additions and 0 deletions
23
Glamourer/CmpFile.cs
Normal file
23
Glamourer/CmpFile.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using Dalamud.Plugin;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public class CmpFile
|
||||
{
|
||||
public readonly Lumina.Data.FileResource File;
|
||||
public readonly uint[] RgbaColors;
|
||||
|
||||
public CmpFile(DalamudPluginInterface pi)
|
||||
{
|
||||
File = pi.Data.GetFile("chara/xls/charamake/human.cmp");
|
||||
RgbaColors = new uint[File.Data.Length >> 2];
|
||||
for (var i = 0; i < File.Data.Length; i += 4)
|
||||
{
|
||||
RgbaColors[i >> 2] = File.Data[i]
|
||||
| (uint) (File.Data[i + 1] << 8)
|
||||
| (uint) (File.Data[i + 2] << 16)
|
||||
| (uint) (File.Data[i + 3] << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
Glamourer/Glamourer.csproj
Normal file
98
Glamourer/Glamourer.csproj
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<RootNamespace>Glamourer</RootNamespace>
|
||||
<AssemblyName>Glamourer</AssemblyName>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<Company>SoftOtter</Company>
|
||||
<Product>Glamourer</Product>
|
||||
<Copyright>Copyright © 2020</Copyright>
|
||||
<Deterministic>true</Deterministic>
|
||||
<OutputType>Library</OutputType>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<RunPostBuildEvent>OnOutputUpdated</RunPostBuildEvent>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\ImGui.NET.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="SDL2-CS">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\SDL2-CS.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.GameData">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.GameData.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.Api">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.Api.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.PlayerWatch">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.PlayerWatch.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Numerics" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<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>
|
||||
<PackageReference Include="Newtonsoft.Json">
|
||||
<Version>12.0.3</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.Memory" Version="4.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="Glamourer.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
||||
</ItemGroup>
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll $(SolutionDir)$(SolutionName).zip" />
|
||||
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
||||
</Target>
|
||||
</Project>
|
||||
11
Glamourer/Glamourer.json
Normal file
11
Glamourer/Glamourer.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"Author": "Ottermandias",
|
||||
"Name": "Glamourer",
|
||||
"Description": "Adds functionality to change appearance of actors. Requires Penumbra to be installed and activated to work.",
|
||||
"InternalName": "Glamourer",
|
||||
"AssemblyVersion": "1.0.0.0",
|
||||
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
|
||||
"ApplicableVersion": "any",
|
||||
"DalamudApiLevel": 3,
|
||||
"LoadPriority": -100
|
||||
}
|
||||
178
Glamourer/Gui/ComboWithFilter.cs
Normal file
178
Glamourer/Gui/ComboWithFilter.cs
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
public class ComboWithFilter<T>
|
||||
{
|
||||
private readonly string _label;
|
||||
private readonly string _filterLabel;
|
||||
private readonly string _listLabel;
|
||||
private string _currentFilter = string.Empty;
|
||||
private string _currentFilterLower = string.Empty;
|
||||
private bool _focus;
|
||||
private readonly float _size;
|
||||
private readonly IReadOnlyList<T> _items;
|
||||
private readonly IReadOnlyList<string> _itemNamesLower;
|
||||
private readonly Func<T, string> _itemToName;
|
||||
|
||||
public Action? PrePreview = null;
|
||||
public Action? PostPreview = null;
|
||||
public Func<T, bool>? CreateSelectable = null;
|
||||
public Action? PreList = null;
|
||||
public Action? PostList = null;
|
||||
public float? HeightPerItem = null;
|
||||
|
||||
private float _heightPerItem;
|
||||
|
||||
public ImGuiComboFlags Flags { get; set; } = ImGuiComboFlags.None;
|
||||
public int ItemsAtOnce { get; set; } = 12;
|
||||
|
||||
public ComboWithFilter(string label, float size, IReadOnlyList<T> items, Func<T, string> itemToName)
|
||||
{
|
||||
_label = label;
|
||||
_filterLabel = $"##_{label}_filter";
|
||||
_listLabel = $"##_{label}_list";
|
||||
_itemToName = itemToName;
|
||||
_items = items;
|
||||
_size = size;
|
||||
|
||||
_itemNamesLower = _items.Select(i => _itemToName(i).ToLowerInvariant()).ToList();
|
||||
}
|
||||
|
||||
public ComboWithFilter(string label, ComboWithFilter<T> other)
|
||||
{
|
||||
_label = label;
|
||||
_filterLabel = $"##_{label}_filter";
|
||||
_listLabel = $"##_{label}_list";
|
||||
_itemToName = other._itemToName;
|
||||
_items = other._items;
|
||||
_itemNamesLower = other._itemNamesLower;
|
||||
_size = other._size;
|
||||
PrePreview = other.PrePreview;
|
||||
PostPreview = other.PostPreview;
|
||||
CreateSelectable = other.CreateSelectable;
|
||||
PreList = other.PreList;
|
||||
PostList = other.PostList;
|
||||
HeightPerItem = other.HeightPerItem;
|
||||
Flags = other.Flags;
|
||||
}
|
||||
|
||||
private bool DrawList(string currentName, out int numItems, out int nodeIdx, ref T? value)
|
||||
{
|
||||
numItems = ItemsAtOnce;
|
||||
nodeIdx = -1;
|
||||
if (!ImGui.BeginChild(_listLabel, new Vector2(_size, ItemsAtOnce * _heightPerItem)))
|
||||
return false;
|
||||
|
||||
var ret = false;
|
||||
try
|
||||
{
|
||||
if (!_focus)
|
||||
{
|
||||
ImGui.SetScrollY(0);
|
||||
_focus = true;
|
||||
}
|
||||
|
||||
|
||||
var scrollY = Math.Max((int) (ImGui.GetScrollY() / _heightPerItem) - 1, 0);
|
||||
var restHeight = scrollY * _heightPerItem;
|
||||
numItems = 0;
|
||||
nodeIdx = 0;
|
||||
|
||||
if (restHeight > 0)
|
||||
ImGui.Dummy(Vector2.UnitY * restHeight);
|
||||
|
||||
for (var i = scrollY; i < _items.Count; ++i)
|
||||
{
|
||||
if (!_itemNamesLower[i].Contains(_currentFilterLower))
|
||||
continue;
|
||||
|
||||
++numItems;
|
||||
if (numItems <= ItemsAtOnce + 2)
|
||||
{
|
||||
nodeIdx = i;
|
||||
var item = _items[i]!;
|
||||
var success = false;
|
||||
if (CreateSelectable != null)
|
||||
{
|
||||
success = CreateSelectable(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
var name = _itemToName(item);
|
||||
success = ImGui.Selectable(name, name == currentName);
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
value = item;
|
||||
ImGui.CloseCurrentPopup();
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (numItems > ItemsAtOnce + 2)
|
||||
ImGui.Dummy(Vector2.UnitY * (numItems - ItemsAtOnce - 2) * _heightPerItem);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool Draw(string currentName, out T? value)
|
||||
{
|
||||
value = default;
|
||||
ImGui.SetNextItemWidth(_size);
|
||||
PrePreview?.Invoke();
|
||||
if (!ImGui.BeginCombo(_label, currentName, Flags))
|
||||
{
|
||||
_focus = false;
|
||||
_currentFilter = string.Empty;
|
||||
_currentFilterLower = string.Empty;
|
||||
PostPreview?.Invoke();
|
||||
return false;
|
||||
}
|
||||
|
||||
PostPreview?.Invoke();
|
||||
|
||||
_heightPerItem = HeightPerItem ?? ImGui.GetTextLineHeightWithSpacing();
|
||||
|
||||
var ret = false;
|
||||
try
|
||||
{
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
if (ImGui.InputTextWithHint(_filterLabel, "Filter...", ref _currentFilter, 255))
|
||||
_currentFilterLower = _currentFilter.ToLowerInvariant();
|
||||
|
||||
var isFocused = ImGui.IsItemActive();
|
||||
if (!_focus)
|
||||
ImGui.SetKeyboardFocusHere();
|
||||
|
||||
PreList?.Invoke();
|
||||
ret = DrawList(currentName, out var numItems, out var nodeIdx, ref value);
|
||||
PostList?.Invoke();
|
||||
|
||||
if (!isFocused && numItems <= 1 && nodeIdx >= 0)
|
||||
{
|
||||
value = _items[nodeIdx];
|
||||
ret = true;
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
154
Glamourer/Gui/ImGuiRaii.cs
Normal file
154
Glamourer/Gui/ImGuiRaii.cs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using ImGuiNET;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
public sealed class ImGuiRaii : IDisposable
|
||||
{
|
||||
private int _colorStack = 0;
|
||||
private int _fontStack = 0;
|
||||
private int _styleStack = 0;
|
||||
private float _indentation = 0f;
|
||||
|
||||
private Stack<Action>? _onDispose = null;
|
||||
|
||||
public ImGuiRaii()
|
||||
{ }
|
||||
|
||||
public static ImGuiRaii NewGroup()
|
||||
=> new ImGuiRaii().Group();
|
||||
|
||||
public ImGuiRaii Group()
|
||||
=> Begin(ImGui.BeginGroup, ImGui.EndGroup);
|
||||
|
||||
public static ImGuiRaii NewTooltip()
|
||||
=> new ImGuiRaii().Tooltip();
|
||||
|
||||
public ImGuiRaii Tooltip()
|
||||
=> Begin(ImGui.BeginTooltip, ImGui.EndTooltip);
|
||||
|
||||
public ImGuiRaii PushColor(ImGuiCol which, uint color)
|
||||
{
|
||||
ImGui.PushStyleColor(which, color);
|
||||
++_colorStack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii PushColor(ImGuiCol which, Vector4 color)
|
||||
{
|
||||
ImGui.PushStyleColor(which, color);
|
||||
++_colorStack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii PopColors(int n = 1)
|
||||
{
|
||||
var actualN = Math.Min(n, _colorStack);
|
||||
if (actualN > 0)
|
||||
{
|
||||
ImGui.PopStyleColor(actualN);
|
||||
_colorStack -= actualN;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii PushStyle(ImGuiStyleVar style, Vector2 value)
|
||||
{
|
||||
ImGui.PushStyleVar(style, value);
|
||||
++_styleStack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii PushStyle(ImGuiStyleVar style, float value)
|
||||
{
|
||||
ImGui.PushStyleVar(style, value);
|
||||
++_styleStack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii PopStyles(int n = 1)
|
||||
{
|
||||
var actualN = Math.Min(n, _styleStack);
|
||||
if (actualN > 0)
|
||||
{
|
||||
ImGui.PopStyleVar(actualN);
|
||||
_styleStack -= actualN;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii PushFont(ImFontPtr font)
|
||||
{
|
||||
ImGui.PushFont(font);
|
||||
++_fontStack;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii PopFonts(int n = 1)
|
||||
{
|
||||
var actualN = Math.Min(n, _fontStack);
|
||||
|
||||
while (actualN-- > 0)
|
||||
{
|
||||
ImGui.PopFont();
|
||||
--_fontStack;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii Indent(float width)
|
||||
{
|
||||
if (width != 0)
|
||||
{
|
||||
ImGui.Indent(width);
|
||||
_indentation += width;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ImGuiRaii Unindent(float width)
|
||||
=> Indent(-width);
|
||||
|
||||
public bool Begin(Func<bool> begin, Action end)
|
||||
{
|
||||
if (begin())
|
||||
{
|
||||
_onDispose ??= new Stack<Action>();
|
||||
_onDispose.Push(end);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ImGuiRaii Begin(Action begin, Action end)
|
||||
{
|
||||
begin();
|
||||
_onDispose ??= new Stack<Action>();
|
||||
_onDispose.Push(end);
|
||||
return this;
|
||||
}
|
||||
|
||||
public void End(int n = 1)
|
||||
{
|
||||
var actualN = Math.Min(n, _onDispose?.Count ?? 0);
|
||||
while(actualN-- > 0)
|
||||
_onDispose!.Pop()();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Unindent(_indentation);
|
||||
PopColors(_colorStack);
|
||||
PopStyles(_styleStack);
|
||||
PopFonts(_fontStack);
|
||||
if (_onDispose != null)
|
||||
{
|
||||
End(_onDispose.Count);
|
||||
_onDispose = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
326
Glamourer/Gui/Interface.cs
Normal file
326
Glamourer/Gui/Interface.cs
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Data.LuminaExtensions;
|
||||
using Dalamud.Game.ClientState.Actors;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.PlayerWatch;
|
||||
using SDL2;
|
||||
|
||||
namespace Glamourer.Gui
|
||||
{
|
||||
internal class Interface : IDisposable
|
||||
{
|
||||
public const int GPoseActorId = 201;
|
||||
private const string PluginName = "Glamourer";
|
||||
private readonly string _glamourerHeader;
|
||||
|
||||
private const int ColorButtonWidth = 140;
|
||||
|
||||
private readonly IReadOnlyDictionary<byte, Stain> _stains;
|
||||
private readonly IReadOnlyDictionary<EquipSlot, List<Item>> _equip;
|
||||
private readonly ActorTable _actors;
|
||||
private readonly IObjectIdentifier _identifier;
|
||||
private readonly Dictionary<EquipSlot, (ComboWithFilter<Item>, ComboWithFilter<Stain>)> _combos;
|
||||
private readonly IPlayerWatcher _playerWatcher;
|
||||
|
||||
private bool _visible = false;
|
||||
|
||||
private Actor? _player;
|
||||
|
||||
private static readonly Vector2 FeatureIconSize = new(80, 80);
|
||||
|
||||
|
||||
public Interface()
|
||||
{
|
||||
_glamourerHeader = GlamourerPlugin.Version.Length > 0
|
||||
? $"{PluginName} v{GlamourerPlugin.Version}###{PluginName}Main"
|
||||
: $"{PluginName}###{PluginName}Main";
|
||||
GlamourerPlugin.PluginInterface.UiBuilder.OnBuildUi += Draw;
|
||||
GlamourerPlugin.PluginInterface.UiBuilder.OnOpenConfigUi += ToggleVisibility;
|
||||
|
||||
_stains = GameData.Stains(GlamourerPlugin.PluginInterface);
|
||||
_equip = GameData.ItemsBySlot(GlamourerPlugin.PluginInterface);
|
||||
_identifier = Penumbra.GameData.GameData.GetIdentifier(GlamourerPlugin.PluginInterface);
|
||||
_actors = GlamourerPlugin.PluginInterface.ClientState.Actors;
|
||||
_playerWatcher = PlayerWatchFactory.Create(GlamourerPlugin.PluginInterface);
|
||||
|
||||
var stainCombo = new ComboWithFilter<Stain>("##StainCombo", ColorButtonWidth, _stains.Values.ToArray(),
|
||||
s => s.Name.ToString())
|
||||
{
|
||||
Flags = ImGuiComboFlags.NoArrowButton | ImGuiComboFlags.HeightLarge,
|
||||
PreList = () =>
|
||||
{
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, Vector2.Zero);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.WindowPadding, Vector2.Zero);
|
||||
ImGui.PushStyleVar(ImGuiStyleVar.FrameRounding, 0);
|
||||
},
|
||||
PostList = () => { ImGui.PopStyleVar(3); },
|
||||
CreateSelectable = s =>
|
||||
{
|
||||
var push = PushColor(s);
|
||||
var ret = ImGui.Button($"{s.Name}##Stain{(byte) s.RowIndex}",
|
||||
Vector2.UnitX * (ColorButtonWidth - ImGui.GetStyle().ScrollbarSize));
|
||||
ImGui.PopStyleColor(push);
|
||||
return ret;
|
||||
},
|
||||
ItemsAtOnce = 12,
|
||||
};
|
||||
|
||||
_combos = _equip.ToDictionary(kvp => kvp.Key,
|
||||
kvp => (new ComboWithFilter<Item>($"{kvp.Key}##Equip", 300, kvp.Value, i => i.Name) { Flags = ImGuiComboFlags.HeightLarge }
|
||||
, new ComboWithFilter<Stain>($"##{kvp.Key}Stain", stainCombo))
|
||||
);
|
||||
}
|
||||
|
||||
public void ToggleVisibility(object _, object _2)
|
||||
=> _visible = !_visible;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_playerWatcher?.Dispose();
|
||||
GlamourerPlugin.PluginInterface.UiBuilder.OnBuildUi -= Draw;
|
||||
GlamourerPlugin.PluginInterface.UiBuilder.OnOpenConfigUi -= ToggleVisibility;
|
||||
}
|
||||
|
||||
private string _currentActorName = "";
|
||||
|
||||
private static int PushColor(Stain stain, ImGuiCol type = ImGuiCol.Button)
|
||||
{
|
||||
ImGui.PushStyleColor(type, stain.RgbaColor);
|
||||
if (stain.Intensity > 127)
|
||||
{
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, 0xFF101010);
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
private bool DrawColorSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
|
||||
{
|
||||
var name = string.Empty;
|
||||
stainCombo.PostPreview = null;
|
||||
if (_stains.TryGetValue((byte) stainIdx, out var stain))
|
||||
{
|
||||
name = stain.Name;
|
||||
var previewPush = PushColor(stain, ImGuiCol.FrameBg);
|
||||
stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
|
||||
}
|
||||
|
||||
if (stainCombo.Draw(name, out var newStain) && _player != null)
|
||||
{
|
||||
newStain.Write(_player.Address, slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool DrawItemSelector(ComboWithFilter<Item> equipCombo, Lumina.Excel.GeneratedSheets.Item? item)
|
||||
{
|
||||
var currentName = item?.Name.ToString() ?? "Nothing";
|
||||
if (equipCombo.Draw(currentName, out var newItem) && _player != null)
|
||||
{
|
||||
newItem.Write(_player.Address);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool DrawEquip(EquipSlot slot, ActorArmor equip)
|
||||
{
|
||||
var (equipCombo, stainCombo) = _combos[slot];
|
||||
|
||||
var ret = false;
|
||||
ret = DrawColorSelector(stainCombo, slot, equip.Stain);
|
||||
ImGui.SameLine();
|
||||
var item = _identifier.Identify(equip.Set, new WeaponType(), equip.Variant, slot);
|
||||
ret |= DrawItemSelector(equipCombo, item);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private bool DrawWeapon(EquipSlot slot, ActorWeapon weapon)
|
||||
{
|
||||
var (equipCombo, stainCombo) = _combos[slot];
|
||||
|
||||
var ret = DrawColorSelector(stainCombo, slot, weapon.Stain);
|
||||
ImGui.SameLine();
|
||||
|
||||
|
||||
var item = _identifier.Identify(weapon.Set, weapon.Type, weapon.Variant, slot);
|
||||
ret |= DrawItemSelector(equipCombo, item);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void UpdateActors(Actor actor)
|
||||
{
|
||||
var newEquip = _playerWatcher.UpdateActorWithoutEvent(actor);
|
||||
GlamourerPlugin.Penumbra?.RedrawActor(actor, RedrawType.WithSettings);
|
||||
|
||||
var gPose = _actors[GPoseActorId];
|
||||
var player = _actors[0];
|
||||
if (gPose != null && actor.Address == gPose.Address && player != null)
|
||||
newEquip.Write(player.Address);
|
||||
}
|
||||
|
||||
private SubRace _currentSubRace = SubRace.Midlander;
|
||||
private Gender _currentGender = Gender.Male;
|
||||
private CustomizationId _currentCustomization = CustomizationId.Hairstyle;
|
||||
|
||||
private static readonly string[]
|
||||
SubRaceNames = ((SubRace[]) Enum.GetValues(typeof(SubRace))).Skip(1).Select(s => s.ToName()).ToArray();
|
||||
|
||||
|
||||
private void DrawStuff()
|
||||
{
|
||||
if (ImGui.BeginCombo("SubRace", _currentSubRace.ToString()))
|
||||
{
|
||||
for (var i = 0; i < SubRaceNames.Length; ++i)
|
||||
{
|
||||
if (ImGui.Selectable(SubRaceNames[i], (int) _currentSubRace == i + 1))
|
||||
_currentSubRace = (SubRace) (i + 1);
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
if (ImGui.BeginCombo("Gender", _currentGender.ToName()))
|
||||
{
|
||||
if (ImGui.Selectable(Gender.Male.ToName(), _currentGender == Gender.Male))
|
||||
_currentGender = Gender.Male;
|
||||
if (ImGui.Selectable(Gender.Female.ToName(), _currentGender == Gender.Female))
|
||||
_currentGender = Gender.Female;
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
|
||||
var set = GlamourerPlugin.Customization.GetList(_currentSubRace, _currentGender);
|
||||
if (ImGui.BeginCombo("Customization", _currentCustomization.ToString()))
|
||||
{
|
||||
foreach (CustomizationId customizationId in Enum.GetValues(typeof(CustomizationId)))
|
||||
{
|
||||
if (!set.IsAvailable(customizationId))
|
||||
continue;
|
||||
|
||||
if (ImGui.Selectable(customizationId.ToString(), customizationId == _currentCustomization))
|
||||
_currentCustomization = customizationId;
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
var count = set.Count(_currentCustomization);
|
||||
var tmp = 0;
|
||||
switch (_currentCustomization.ToType(_currentSubRace.ToRace() == Race.Hrothgar))
|
||||
{
|
||||
case CharaMakeParams.MenuType.ColorPicker:
|
||||
{
|
||||
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0f);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var data = set.Data(_currentCustomization, i);
|
||||
ImGui.ColorButton($"{data.Value}", ImGui.ColorConvertU32ToFloat4(data.Color));
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case CharaMakeParams.MenuType.Percentage:
|
||||
ImGui.SliderInt("Percentage", ref tmp, 0, 100);
|
||||
break;
|
||||
case CharaMakeParams.MenuType.ListSelector:
|
||||
ImGui.Combo("List", ref tmp, Enumerable.Range(0, count).Select(i => $"{_currentCustomization} #{i}").ToArray(), count);
|
||||
break;
|
||||
case CharaMakeParams.MenuType.IconSelector:
|
||||
case CharaMakeParams.MenuType.MultiIconSelector:
|
||||
{
|
||||
using var raii = new ImGuiRaii().PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.PushStyle(ImGuiStyleVar.FrameRounding, 0f);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var data = set.Data(_currentCustomization, i);
|
||||
var texture = GlamourerPlugin.Customization.GetIcon(data.IconId);
|
||||
ImGui.ImageButton(texture.ImGuiHandle, FeatureIconSize * ImGui.GetIO().FontGlobalScale);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using var tooltip = ImGuiRaii.NewTooltip();
|
||||
ImGui.Image(texture.ImGuiHandle, new Vector2(texture.Width, texture.Height));
|
||||
}
|
||||
|
||||
if (i % 4 != 3)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Draw()
|
||||
{
|
||||
ImGui.SetNextWindowSizeConstraints(Vector2.One * 600, Vector2.One * 5000);
|
||||
if (!_visible || !ImGui.Begin(_glamourerHeader))
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
if (ImGui.BeginCombo("Actor", _currentActorName))
|
||||
{
|
||||
var idx = 0;
|
||||
foreach (var actor in GlamourerPlugin.PluginInterface.ClientState.Actors.Where(a => a.ObjectKind == ObjectKind.Player))
|
||||
{
|
||||
if (ImGui.Selectable($"{actor.Name}##{idx++}"))
|
||||
_currentActorName = actor.Name;
|
||||
}
|
||||
|
||||
ImGui.EndCombo();
|
||||
}
|
||||
|
||||
_player = _actors[GPoseActorId] ?? _actors[0];
|
||||
if (_player == null || !GlamourerPlugin.PluginInterface.ClientState.Condition.Any())
|
||||
{
|
||||
ImGui.TextColored(new Vector4(0.4f, 0.1f, 0.1f, 1f),
|
||||
"No player character available.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var equip = new ActorEquipment(_player);
|
||||
|
||||
var changes = false;
|
||||
changes |= DrawWeapon(EquipSlot.MainHand, equip.MainHand);
|
||||
changes |= DrawWeapon(EquipSlot.OffHand, equip.OffHand);
|
||||
changes |= DrawEquip(EquipSlot.Head, equip.Head);
|
||||
changes |= DrawEquip(EquipSlot.Body, equip.Body);
|
||||
changes |= DrawEquip(EquipSlot.Hands, equip.Hands);
|
||||
changes |= DrawEquip(EquipSlot.Legs, equip.Legs);
|
||||
changes |= DrawEquip(EquipSlot.Feet, equip.Feet);
|
||||
changes |= DrawEquip(EquipSlot.Ears, equip.Ears);
|
||||
changes |= DrawEquip(EquipSlot.Neck, equip.Neck);
|
||||
changes |= DrawEquip(EquipSlot.Wrists, equip.Wrists);
|
||||
changes |= DrawEquip(EquipSlot.RFinger, equip.RFinger);
|
||||
changes |= DrawEquip(EquipSlot.LFinger, equip.LFinger);
|
||||
|
||||
if (changes)
|
||||
UpdateActors(_player);
|
||||
}
|
||||
|
||||
DrawStuff();
|
||||
}
|
||||
finally
|
||||
{
|
||||
ImGui.End();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
170
Glamourer/Main.cs
Normal file
170
Glamourer/Main.cs
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Gui;
|
||||
using ImGuiNET;
|
||||
using Penumbra.Api;
|
||||
using CommandManager = Glamourer.Managers.CommandManager;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
internal class Glamourer
|
||||
{
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly CommandManager _commands;
|
||||
|
||||
public Glamourer(DalamudPluginInterface pi)
|
||||
{
|
||||
_pluginInterface = pi;
|
||||
_commands = new CommandManager(_pluginInterface);
|
||||
}
|
||||
}
|
||||
|
||||
public class GlamourerPlugin : IDalamudPlugin
|
||||
{
|
||||
public const int RequiredPenumbraShareVersion = 1;
|
||||
|
||||
public string Name
|
||||
=> "Glamourer";
|
||||
|
||||
public static DalamudPluginInterface PluginInterface = null!;
|
||||
private Glamourer _glamourer = null!;
|
||||
private Interface _interface = null!;
|
||||
public static ICustomizationManager Customization = null!;
|
||||
|
||||
public static string Version = string.Empty;
|
||||
|
||||
public static IPenumbraApi? Penumbra;
|
||||
|
||||
private Dalamud.Dalamud _dalamud = null!;
|
||||
private List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, bool IsRaw)> _plugins = null!;
|
||||
|
||||
private void SetDalamud(DalamudPluginInterface pi)
|
||||
{
|
||||
var dalamud = (Dalamud.Dalamud?) pi.GetType()
|
||||
?.GetField("dalamud", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?.GetValue(pi);
|
||||
|
||||
_dalamud = dalamud ?? throw new Exception("Could not obtain Dalamud.");
|
||||
}
|
||||
|
||||
private void PenumbraTooltip(object? it)
|
||||
{
|
||||
if (it is Lumina.Excel.GeneratedSheets.Item)
|
||||
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
||||
}
|
||||
|
||||
private void PenumbraRightClick(MouseButton button, object? it)
|
||||
{
|
||||
if (button == MouseButton.Right && it is Lumina.Excel.GeneratedSheets.Item item)
|
||||
{
|
||||
var actors = PluginInterface.ClientState.Actors;
|
||||
var player = actors[Interface.GPoseActorId] ?? actors[0];
|
||||
if (player != null)
|
||||
{
|
||||
var writeItem = new Item(item, string.Empty);
|
||||
writeItem.Write(player.Address);
|
||||
_interface.UpdateActors(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterFunctions()
|
||||
{
|
||||
if (Penumbra == null || !Penumbra.Valid)
|
||||
return;
|
||||
|
||||
Penumbra!.ChangedItemTooltip += PenumbraTooltip;
|
||||
Penumbra!.ChangedItemClicked += PenumbraRightClick;
|
||||
}
|
||||
|
||||
private void UnregisterFunctions()
|
||||
{
|
||||
if (Penumbra == null || !Penumbra.Valid)
|
||||
return;
|
||||
|
||||
Penumbra!.ChangedItemTooltip -= PenumbraTooltip;
|
||||
Penumbra!.ChangedItemClicked -= PenumbraRightClick;
|
||||
}
|
||||
|
||||
private void SetPlugins(DalamudPluginInterface pi)
|
||||
{
|
||||
var pluginManager = _dalamud?.GetType()
|
||||
?.GetProperty("PluginManager", BindingFlags.Instance | BindingFlags.NonPublic)
|
||||
?.GetValue(_dalamud);
|
||||
|
||||
if (pluginManager == null)
|
||||
throw new Exception("Could not obtain plugin manager.");
|
||||
|
||||
var pluginsList =
|
||||
(List<(IDalamudPlugin Plugin, PluginDefinition Definition, DalamudPluginInterface PluginInterface, bool IsRaw)>?) pluginManager
|
||||
?.GetType()
|
||||
?.GetProperty("Plugins", BindingFlags.Instance | BindingFlags.Public)
|
||||
?.GetValue(pluginManager);
|
||||
|
||||
_plugins = pluginsList ?? throw new Exception("Could not obtain Dalamud.");
|
||||
}
|
||||
|
||||
private bool GetPenumbra()
|
||||
{
|
||||
if (Penumbra?.Valid ?? false)
|
||||
return true;
|
||||
|
||||
var plugin = _plugins.Find(p
|
||||
=> p.Definition.InternalName == "Penumbra"
|
||||
&& string.Compare(p.Definition.AssemblyVersion, "0.4.0.3", StringComparison.Ordinal) >= 0).Plugin;
|
||||
|
||||
var penumbra = (IPenumbraApiBase?) plugin?.GetType().GetProperty("Api", BindingFlags.Instance | BindingFlags.Public)
|
||||
?.GetValue(plugin);
|
||||
if (penumbra != null && penumbra.Valid && penumbra.ApiVersion >= RequiredPenumbraShareVersion)
|
||||
{
|
||||
Penumbra = (IPenumbraApi) penumbra!;
|
||||
RegisterFunctions();
|
||||
}
|
||||
else
|
||||
{
|
||||
Penumbra = null;
|
||||
}
|
||||
|
||||
return Penumbra != null;
|
||||
}
|
||||
|
||||
public void Initialize(DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
Version = Assembly.GetExecutingAssembly()?.GetName().Version.ToString() ?? "";
|
||||
PluginInterface = pluginInterface;
|
||||
Customization = CustomizationManager.Create(PluginInterface);
|
||||
SetDalamud(PluginInterface);
|
||||
SetPlugins(PluginInterface);
|
||||
GetPenumbra();
|
||||
|
||||
PluginInterface.CommandManager.AddHandler("/glamour", new CommandInfo(OnCommand)
|
||||
{
|
||||
HelpMessage = "/penumbra - toggle ui\n/penumbra reload - reload mod file lists & discover any new mods",
|
||||
});
|
||||
|
||||
_glamourer = new Glamourer(PluginInterface);
|
||||
_interface = new Interface();
|
||||
}
|
||||
|
||||
public void OnCommand(string command, string arguments)
|
||||
{
|
||||
if (GetPenumbra())
|
||||
Penumbra!.RedrawAll(RedrawType.WithSettings);
|
||||
else
|
||||
PluginLog.Information("Could not get Penumbra.");
|
||||
}
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
UnregisterFunctions();
|
||||
_interface?.Dispose();
|
||||
PluginInterface.CommandManager.RemoveHandler("/glamour");
|
||||
PluginInterface.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Glamourer/Managers/CommandManager.cs
Normal file
73
Glamourer/Managers/CommandManager.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.SeFunctions;
|
||||
|
||||
namespace Glamourer.Managers
|
||||
{
|
||||
public class CommandManager
|
||||
{
|
||||
private readonly ProcessChatBox _processChatBox;
|
||||
private readonly Dalamud.Game.Command.CommandManager _dalamudCommands;
|
||||
|
||||
private readonly IntPtr _uiModulePtr;
|
||||
|
||||
public CommandManager(DalamudPluginInterface pi, BaseUiObject baseUiObject, GetUiModule getUiModule, ProcessChatBox processChatBox)
|
||||
{
|
||||
_dalamudCommands = pi.CommandManager;
|
||||
_processChatBox = processChatBox;
|
||||
_uiModulePtr = getUiModule.Invoke(Marshal.ReadIntPtr(baseUiObject.Address));
|
||||
}
|
||||
|
||||
public CommandManager(DalamudPluginInterface pi)
|
||||
: this(pi, new BaseUiObject(pi.TargetModuleScanner), new GetUiModule(pi.TargetModuleScanner),
|
||||
new ProcessChatBox(pi.TargetModuleScanner))
|
||||
{ }
|
||||
|
||||
public bool Execute(string message)
|
||||
{
|
||||
// First try to process the command through Dalamud.
|
||||
if (_dalamudCommands.ProcessCommand(message))
|
||||
{
|
||||
PluginLog.Verbose("Executed Dalamud command \"{Message:l}\".", message);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_uiModulePtr == IntPtr.Zero)
|
||||
{
|
||||
PluginLog.Error("Can not execute \"{Message:l}\" because no uiModulePtr is available.", message);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Then prepare a string to send to the game itself.
|
||||
var (text, length) = PrepareString(message);
|
||||
var payload = PrepareContainer(text, length);
|
||||
|
||||
_processChatBox.Invoke(_uiModulePtr, payload, IntPtr.Zero, (byte) 0);
|
||||
|
||||
Marshal.FreeHGlobal(payload);
|
||||
Marshal.FreeHGlobal(text);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static (IntPtr, long) PrepareString(string message)
|
||||
{
|
||||
var bytes = Encoding.UTF8.GetBytes(message);
|
||||
var mem = Marshal.AllocHGlobal(bytes.Length + 30);
|
||||
Marshal.Copy(bytes, 0, mem, bytes.Length);
|
||||
Marshal.WriteByte(mem + bytes.Length, 0);
|
||||
return (mem, bytes.Length + 1);
|
||||
}
|
||||
|
||||
private static IntPtr PrepareContainer(IntPtr message, long length)
|
||||
{
|
||||
var mem = Marshal.AllocHGlobal(400);
|
||||
Marshal.WriteInt64(mem, message.ToInt64());
|
||||
Marshal.WriteInt64(mem + 0x8, 64);
|
||||
Marshal.WriteInt64(mem + 0x10, length);
|
||||
Marshal.WriteInt64(mem + 0x18, 0);
|
||||
return mem;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Glamourer/SeFunctions/BaseUiObject.cs
Normal file
11
Glamourer/SeFunctions/BaseUiObject.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using Dalamud.Game;
|
||||
|
||||
namespace Glamourer.SeFunctions
|
||||
{
|
||||
public sealed class BaseUiObject : SeAddressBase
|
||||
{
|
||||
public BaseUiObject(SigScanner sigScanner)
|
||||
: base(sigScanner, "48 8B 0D ?? ?? ?? ?? 48 8D 54 24 ?? 48 83 C1 10 E8")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
14
Glamourer/SeFunctions/GetUiModule.cs
Normal file
14
Glamourer/SeFunctions/GetUiModule.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using Dalamud.Game;
|
||||
|
||||
namespace Glamourer.SeFunctions
|
||||
{
|
||||
public delegate IntPtr GetUiModuleDelegate(IntPtr baseUiObj);
|
||||
|
||||
public sealed class GetUiModule : SeFunctionBase<GetUiModuleDelegate>
|
||||
{
|
||||
public GetUiModule(SigScanner sigScanner)
|
||||
: base(sigScanner, "E8 ?? ?? ?? ?? 48 83 7F ?? 00 48 8B F0")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
14
Glamourer/SeFunctions/ProcessChatBox.cs
Normal file
14
Glamourer/SeFunctions/ProcessChatBox.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using System;
|
||||
using Dalamud.Game;
|
||||
|
||||
namespace Glamourer.SeFunctions
|
||||
{
|
||||
public delegate IntPtr ProcessChatBoxDelegate(IntPtr uiModule, IntPtr message, IntPtr unk1, byte unk2);
|
||||
|
||||
public sealed class ProcessChatBox : SeFunctionBase<ProcessChatBoxDelegate>
|
||||
{
|
||||
public ProcessChatBox(SigScanner sigScanner)
|
||||
: base(sigScanner, "48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 45 84 C9")
|
||||
{ }
|
||||
}
|
||||
}
|
||||
20
Glamourer/SeFunctions/SeAddressBase.cs
Normal file
20
Glamourer/SeFunctions/SeAddressBase.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using System;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace Glamourer.SeFunctions
|
||||
{
|
||||
public class SeAddressBase
|
||||
{
|
||||
public readonly IntPtr Address;
|
||||
|
||||
public SeAddressBase(SigScanner sigScanner, string signature, int offset = 0)
|
||||
{
|
||||
Address = sigScanner.GetStaticAddressFromSig(signature);
|
||||
if (Address != IntPtr.Zero)
|
||||
Address += offset;
|
||||
var baseOffset = (ulong) Address.ToInt64() - (ulong) sigScanner.Module.BaseAddress.ToInt64();
|
||||
PluginLog.Debug($"{GetType().Name} address 0x{Address.ToInt64():X16}, baseOffset 0x{baseOffset:X16}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
75
Glamourer/SeFunctions/SeFunctionBase.cs
Normal file
75
Glamourer/SeFunctions/SeFunctionBase.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace Glamourer.SeFunctions
|
||||
{
|
||||
public class SeFunctionBase<T> where T : Delegate
|
||||
{
|
||||
public IntPtr Address;
|
||||
protected T? FuncDelegate;
|
||||
|
||||
public SeFunctionBase(SigScanner sigScanner, int offset)
|
||||
{
|
||||
Address = sigScanner.Module.BaseAddress + offset;
|
||||
PluginLog.Debug($"{GetType().Name} address 0x{Address.ToInt64():X16}, baseOffset 0x{offset:X16}.");
|
||||
}
|
||||
|
||||
public SeFunctionBase(SigScanner sigScanner, string signature, int offset = 0)
|
||||
{
|
||||
Address = sigScanner.ScanText(signature);
|
||||
if (Address != IntPtr.Zero)
|
||||
Address += offset;
|
||||
var baseOffset = (ulong) Address.ToInt64() - (ulong) sigScanner.Module.BaseAddress.ToInt64();
|
||||
PluginLog.Debug($"{GetType().Name} address 0x{Address.ToInt64():X16}, baseOffset 0x{baseOffset:X16}.");
|
||||
}
|
||||
|
||||
public T? Delegate()
|
||||
{
|
||||
if (FuncDelegate != null)
|
||||
return FuncDelegate;
|
||||
|
||||
if (Address != IntPtr.Zero)
|
||||
{
|
||||
FuncDelegate = Marshal.GetDelegateForFunctionPointer<T>(Address);
|
||||
return FuncDelegate;
|
||||
}
|
||||
|
||||
PluginLog.Error($"Trying to generate delegate for {GetType().Name}, but no pointer available.");
|
||||
return null;
|
||||
}
|
||||
|
||||
public dynamic? Invoke(params dynamic[] parameters)
|
||||
{
|
||||
if (FuncDelegate != null)
|
||||
return FuncDelegate.DynamicInvoke(parameters);
|
||||
|
||||
if (Address != IntPtr.Zero)
|
||||
{
|
||||
FuncDelegate = Marshal.GetDelegateForFunctionPointer<T>(Address);
|
||||
return FuncDelegate!.DynamicInvoke(parameters);
|
||||
}
|
||||
else
|
||||
{
|
||||
PluginLog.Error($"Trying to call {GetType().Name}, but no pointer available.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Hook<T>? CreateHook(T detour)
|
||||
{
|
||||
if (Address != IntPtr.Zero)
|
||||
{
|
||||
var hook = new Hook<T>(Address, detour);
|
||||
hook.Enable();
|
||||
PluginLog.Debug($"Hooked onto {GetType().Name} at address 0x{Address.ToInt64():X16}.");
|
||||
return hook;
|
||||
}
|
||||
|
||||
PluginLog.Error($"Trying to create Hook for {GetType().Name}, but no pointer available.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue