Merge branch 'master' into sestring_payloads_refactor

This commit is contained in:
meli 2020-05-01 12:13:05 -07:00
commit 67baa81dbe
27 changed files with 623 additions and 268 deletions

View file

@ -1,41 +1,46 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetFramework>net48</TargetFramework>
<LangVersion>8.0</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Label="Build">
<OutputType>Exe</OutputType>
<OutputPath>$(SolutionDir)/bin</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<DebugSymbols>true</DebugSymbols>
<DebugType>Portable</DebugType>
</PropertyGroup>
<PropertyGroup Label="Feature">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>4.9.2.0</AssemblyVersion>
<FileVersion>4.9.2.0</FileVersion>
<Description>XIVLauncher addon injection</Description>
<Version>4.9.2.0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile></DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageIcon></PackageIcon>
<PackageIconUrl />
<ApplicationIcon>dalamud.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EasyHook" Version="2.7.6270" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dalamud\Dalamud.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
</ItemGroup>
<PropertyGroup Label="Target">
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetFramework>net48</TargetFramework>
<LangVersion>8.0</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Label="Build">
<OutputType>Exe</OutputType>
<OutputPath>$(SolutionDir)/bin</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<DebugSymbols>true</DebugSymbols>
<DebugType>Portable</DebugType>
</PropertyGroup>
<PropertyGroup Label="Feature">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>4.9.3.0</AssemblyVersion>
<FileVersion>4.9.3.0</FileVersion>
<Description>XIVLauncher addon injection</Description>
<Version>4.9.3.0</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile></DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\injector\</PathMap>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageIcon></PackageIcon>
<PackageIconUrl />
<ApplicationIcon>dalamud.ico</ApplicationIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="EasyHook" Version="2.7.6270" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dalamud\Dalamud.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Windows.Forms" />
</ItemGroup>
</Project>

View file

@ -241,11 +241,7 @@ namespace Dalamud {
this.isImguiDrawDataWindow = true;
}
if (ImGui.MenuItem("Open Credits window")) {
var logoGraphic =
this.InterfaceManager.LoadImage(
Path.Combine(this.StartInfo.WorkingDirectory, "UIRes", "logo.png"));
this.creditsWindow = new DalamudCreditsWindow(logoGraphic, this.Framework);
this.isImguiDrawCreditsWindow = true;
OnOpenCreditsCommand(null, null);
}
ImGui.MenuItem("Draw ImGui demo", "", ref this.isImguiDrawDemoWindow);
if (ImGui.MenuItem("Dump ImGui info"))
@ -662,7 +658,7 @@ namespace Dalamud {
var logoGraphic =
this.InterfaceManager.LoadImage(
Path.Combine(this.StartInfo.WorkingDirectory, "UIRes", "logo.png"));
this.creditsWindow = new DalamudCreditsWindow(logoGraphic, this.Framework);
this.creditsWindow = new DalamudCreditsWindow(this, logoGraphic, this.Framework);
this.isImguiDrawCreditsWindow = true;
}

View file

@ -1,79 +1,73 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="Target">
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetFramework>net472</TargetFramework>
<LangVersion>8.0</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Label="Build">
<OutputType>Library</OutputType>
<OutputPath></OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<DebugSymbols>true</DebugSymbols>
<DebugType>Portable</DebugType>
</PropertyGroup>
<PropertyGroup Label="Feature">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>4.9.2.0</AssemblyVersion>
<Version>4.9.2.0</Version>
<FileVersion>4.9.2.0</FileVersion>
</PropertyGroup>
<ItemGroup Label="Resources">
<None Include="$(SolutionDir)/Resources/**/*" CopyToOutputDirectory="PreserveNewest" Visible="false" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>$(SolutionDir)\bin\Dalamud.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DocumentationFile>$(SolutionDir)\bin\Dalamud.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile>$(SolutionDir)\bin\Dalamud.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<DocumentationFile>$(SolutionDir)bin\Dalamud.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Resources\**" />
<EmbeddedResource Remove="Resources\**" />
<None Remove="Resources\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CheapLoc" Version="1.1.3" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="Lumina" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="PropertyChanged.Fody" Version="2.6.1" />
<PackageReference Include="Serilog" Version="2.6.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="EasyHook" Version="2.7.6270" />
<PackageReference Include="SharpDX.Desktop" Version="4.2.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\lib\Discord.Net\src\Discord.Net.Rest\Discord.Net.Rest.csproj" />
<ProjectReference Include="..\lib\Discord.Net\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj" />
</ItemGroup>
<PropertyGroup Label="Target">
<PlatformTarget>AnyCPU</PlatformTarget>
<TargetFramework>net472</TargetFramework>
<LangVersion>8.0</LangVersion>
<Platforms>AnyCPU;x64</Platforms>
</PropertyGroup>
<PropertyGroup Label="Build">
<OutputType>Library</OutputType>
<OutputPath></OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<DebugSymbols>true</DebugSymbols>
<DebugType>Portable</DebugType>
<DocumentationFile>$(SolutionDir)\bin\Dalamud.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Label="Feature">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>4.9.3.0</AssemblyVersion>
<Version>4.9.3.0</Version>
<FileVersion>4.9.3.0</FileVersion>
</PropertyGroup>
<ItemGroup Label="Resources">
<None Include="$(SolutionDir)/Resources/**/*" CopyToOutputDirectory="PreserveNewest" Visible="false" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\dalamud\</PathMap>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Resources\**" />
<EmbeddedResource Remove="Resources\**" />
<None Remove="Resources\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CheapLoc" Version="1.1.3" />
<PackageReference Include="JetBrains.Annotations" Version="2020.1.0" />
<PackageReference Include="Lumina" Version="1.1.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="PropertyChanged.Fody" Version="2.6.1" />
<PackageReference Include="Serilog" Version="2.6.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
<PackageReference Include="EasyHook" Version="2.7.6270" />
<PackageReference Include="SharpDX.Desktop" Version="4.2.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\lib\Discord.Net\src\Discord.Net.Rest\Discord.Net.Rest.csproj" />
<ProjectReference Include="..\lib\Discord.Net\src\Discord.Net.WebSocket\Discord.Net.WebSocket.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\ImGui.NET\src\ImGui.NET-472\ImGui.NET-472.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\deps\SDL2-CS\SDL2-CS.csproj" />
<ProjectReference Include="..\lib\ImGuiScene\ImGuiScene\ImGuiScene.csproj" />
</ItemGroup>
</Project>

View file

@ -22,7 +22,14 @@ namespace Dalamud.Data
/// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
/// </summary>
public class DataManager {
/// <summary>
/// OpCodes sent by the server to the client.
/// </summary>
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; }
/// <summary>
/// OpCodes sent by the client to the server.
/// </summary>
public ReadOnlyDictionary<string, ushort> ClientOpCodes { get; private set; }
/// <summary>
/// An <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
@ -55,13 +62,19 @@ namespace Dalamud.Data
{
try
{
Log.Verbose("Starting data download...");
Log.Verbose("Starting data load...");
var opCodeDict =
var zoneOpCodeDict =
JsonConvert.DeserializeObject<Dictionary<string, ushort>>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "serveropcode.json")));
this.ServerOpCodes = new ReadOnlyDictionary<string, ushort>(opCodeDict);
ServerOpCodes = new ReadOnlyDictionary<string, ushort>(zoneOpCodeDict);
Log.Verbose("Loaded {0} ServerOpCodes.", opCodeDict.Count);
Log.Verbose("Loaded {0} ServerOpCodes.", zoneOpCodeDict.Count);
var clientOpCodeDict =
JsonConvert.DeserializeObject<Dictionary<string, ushort>>(File.ReadAllText(Path.Combine(baseDir, "UIRes", "clientopcode.json")));
ClientOpCodes = new ReadOnlyDictionary<string, ushort>(clientOpCodeDict);
Log.Verbose("Loaded {0} ClientOpCodes.", clientOpCodeDict.Count);
var luminaOptions = new LuminaOptions

View file

@ -214,7 +214,7 @@ namespace Dalamud.DiscordBot {
var avatarUrl = string.Empty;
var lodestoneId = string.Empty;
if (!this.config.DisableEmbeds) {
if (!this.config.DisableEmbeds && !string.IsNullOrEmpty(senderName)) {
var searchResult = await GetCharacterInfo(senderName, senderWorld);
lodestoneId = searchResult.LodestoneId;

View file

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Dalamud.Game.ClientState.Actors
{
/// <summary>
/// This enum describes the indices of the Customize array.
/// </summary>
// TODO: This may need some rework since it may not be entirely accurate (stolen from Sapphire)
public enum CustomizeIndex {
Race = 0x00,
Gender = 0x01,
Tribe = 0x04,
Height = 0x03,
ModelType = 0x02, // Au Ra: changes horns/tails, everything else: seems to drastically change appearance (flip between two sets, odd/even numbers). sometimes retains hairstyle and other features
FaceType = 0x05,
HairStyle = 0x06,
HasHighlights = 0x07, // negative to enable, positive to disable
SkinColor = 0x08,
EyeColor = 0x09, // color of character's right eye
HairColor = 0x0A, // main color
HairColor2 = 0x0B, // highlights color
FaceFeatures = 0x0C, // seems to be a toggle, (-odd and +even for large face covering), opposite for small
FaceFeaturesColor = 0x0D,
Eyebrows = 0x0E,
EyeColor2 = 0x0F, // color of character's left eye
EyeShape = 0x10,
NoseShape = 0x11,
JawShape = 0x12,
LipStyle = 0x13, // lip colour depth and shape (negative values around -120 darker/more noticeable, positive no colour)
LipColor = 0x14,
RaceFeatureSize = 0x15,
RaceFeatureType = 0x16, // negative or out of range tail shapes for race result in no tail (e.g. Au Ra has max of 4 tail shapes), incorrect value can crash client
BustSize = 0x17, // char creator allows up to max of 100, i set to 127 cause who wouldnt but no visible difference
Facepaint = 0x18,
FacepaintColor = 0x19,
}
}

View file

@ -6,5 +6,17 @@ namespace Dalamud.Game.ClientState.Actors {
public float X;
public float Z;
public float Y;
/// <summary>
/// Convert this Position3 to a System.Numerics.Vector3
/// </summary>
/// <param name="pos">Position to convert.</param>
public static implicit operator System.Numerics.Vector3(Position3 pos) => new System.Numerics.Vector3(pos.X, pos.Y, pos.Z);
/// <summary>
/// Convert this Position3 to a SharpDX.Vector3
/// </summary>
/// <param name="pos">Position to convert.</param>
public static implicit operator SharpDX.Vector3(Position3 pos) => new SharpDX.Vector3(pos.X, pos.Z, pos.Y);
}
}

View file

@ -34,6 +34,12 @@ namespace Dalamud.Game.ClientState.Actors.Types {
/// </summary>
public Position3 Position => this.actorStruct.Position;
/// <summary>
/// Rotation of this <see cref="Actor"/>.<br/>
/// This ranges from -pi to pi radians.
/// </summary>
public float Rotation => this.actorStruct.Rotation;
/// <summary>
/// Displayname of this <see cref="Actor">Actor</see>.
/// </summary>
@ -49,5 +55,15 @@ namespace Dalamud.Game.ClientState.Actors.Types {
/// possible values.
/// </summary>
public ObjectKind ObjectKind => this.actorStruct.ObjectKind;
/// <summary>
/// The X distance from the local player in yalms.
/// </summary>
public byte YalmDistanceX => this.actorStruct.YalmDistanceFromPlayerX;
/// <summary>
/// The Y distance from the local player in yalms.
/// </summary>
public byte YalmDistanceY => this.actorStruct.YalmDistanceFromPlayerY;
}
}

View file

@ -42,6 +42,11 @@ namespace Dalamud.Game.ClientState.Actors.Types {
/// <summary>
/// The maximum MP of this Chara.
/// </summary>
public int MaxMp => this.actorStruct.MaxMp;
public int MaxMp => 10000; // Currently hardcoded because the value in actorStruct is very questionable.
/// <summary>
/// Byte array describing the visual appearance of this Chara. Indexed by <see cref="CustomizeIndex"/>.
/// </summary>
public byte[] Customize => this.actorStruct.Customize;
}
}

View file

@ -13,29 +13,42 @@ namespace Dalamud.Game.ClientState.Structs
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct Actor {
[FieldOffset(0x30)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)] public string Name;
[FieldOffset(0x30)] [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
public string Name;
[FieldOffset(116)] public int ActorId;
[FieldOffset(128)] public int DataId;
[FieldOffset(132)] public int OwnerId;
[FieldOffset(140)] public ObjectKind ObjectKind;
[FieldOffset(141)] public byte SubKind;
[FieldOffset(142)] public bool IsFriendly;
[FieldOffset(144)] public byte YalmDistanceFromPlayer1; // Demo says one of these is x distance
[FieldOffset(144)] public byte YalmDistanceFromPlayerX; // Demo says one of these is x distance
[FieldOffset(145)] public byte PlayerTargetStatus; // This is some kind of enum
[FieldOffset(146)] public byte YalmDistanceFromPlayer2; // and the other is z distance
[FieldOffset(146)] public byte YalmDistanceFromPlayerY; // and the other is z distance
[FieldOffset(160)] public Position3 Position;
[FieldOffset(176)] public float Rotation; // Rotation around the vertical axis (yaw), from -pi to pi radians
[FieldOffset(0x17B8)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)] public byte[] Customize;
[FieldOffset(0x17F8)] public int TargetActorId;
// This field can't be correctly aligned, so we have to cut it manually.
[FieldOffset(0x17d0)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)] public byte[] CompanyTag;
[FieldOffset(0x17d0)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 7)]
public byte[] CompanyTag;
[FieldOffset(0x1868)] public int NameId;
[FieldOffset(0x1884)] public byte CurrentWorld;
[FieldOffset(0x1886)] public byte HomeWorld;
[FieldOffset(6328)] public int CurrentHp;
[FieldOffset(6332)] public int MaxHp;
[FieldOffset(6336)] public int CurrentMp;
[FieldOffset(6340)] public int MaxMp;
[FieldOffset(0x1898)] public int CurrentHp;
[FieldOffset(0x189C)] public int MaxHp;
[FieldOffset(0x18A0)] public int CurrentMp;
// This value is weird. It seems to change semi-randomly between 0 and 10k, definitely
// in response to mp-using events, but it doesn't often have a value and the changing seems
// somewhat arbitrary.
[FieldOffset(0x18AA)] public int MaxMp;
[FieldOffset(6358)] public byte ClassJob;
[FieldOffset(6360)] public byte Level;
[FieldOffset(0x1958)][MarshalAs(UnmanagedType.ByValArray, SizeConst = 20)] public StatusEffect[] UIStatusEffects;
}
}

View file

@ -0,0 +1,18 @@
using System;
using System.Runtime.InteropServices;
namespace Dalamud.Game.ClientState.Structs
{
/// <summary>
/// Native memory representation of a FFXIV status effect.
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct StatusEffect
{
public short EffectId;
public byte StackCount;
public byte Param;
public float Duration;
public int OwnerId;
}
}

View file

@ -65,7 +65,7 @@ namespace Dalamud.Game.Internal {
Gui = new GameGui(Address.GuiManager, scanner, dalamud);
Network = new GameNetwork(dalamud, scanner);
Network = new GameNetwork(scanner);
//Resource = new ResourceManager(dalamud, scanner);
}

View file

@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Dalamud.Hooking;
using Serilog;
using SharpDX;
namespace Dalamud.Game.Internal.Gui {
public sealed class GameGui : IDisposable {
@ -34,6 +35,14 @@ namespace Dalamud.Game.Internal.Gui {
private delegate bool OpenMapWithFlagDelegate(IntPtr UIMapObject, string flag);
private OpenMapWithFlagDelegate openMapWithFlag;
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate IntPtr GetMatrixSingletonDelegate();
internal readonly GetMatrixSingletonDelegate getMatrixSingleton;
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private unsafe delegate IntPtr ScreenToWorldNativeDelegate(float *camPosition, float *clipCoords, float rayDistance, float *worldCoords, float *unknown);
private readonly ScreenToWorldNativeDelegate screenToWorldNative;
/// <summary>
/// The item ID that is currently hovered by the player. 0 when no item is hovered.
/// If > 1.000.000, subtract 1.000.000 and treat it as HQ
@ -74,6 +83,12 @@ namespace Dalamud.Game.Internal.Gui {
this);
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(Address.GetUIObject);
this.getMatrixSingleton =
Marshal.GetDelegateForFunctionPointer<GetMatrixSingletonDelegate>(Address.GetMatrixSingleton);
this.screenToWorldNative =
Marshal.GetDelegateForFunctionPointer<ScreenToWorldNativeDelegate>(Address.ScreenToWorld);
}
private IntPtr HandleSetGlobalBgmDetour(UInt16 bgmKey, byte a2, UInt32 a3, UInt32 a4, UInt32 a5, byte a6) {
@ -126,6 +141,11 @@ namespace Dalamud.Game.Internal.Gui {
return retVal;
}
/// <summary>
/// Opens the in-game map with a flag on the location of the parameter
/// </summary>
/// <param name="mapLink">Link to the map to be opened</param>
/// <returns>True if there were no errors and it could open the map</returns>
public bool OpenMapWithMapLink(MapLinkPayload mapLink)
{
var uiObjectPtr = getUIObject();
@ -158,6 +178,45 @@ namespace Dalamud.Game.Internal.Gui {
return this.openMapWithFlag(uiMapObjectPtr, mapLinkString);
}
/// <summary>
/// Converts in-world coordinates to screen coordinates (upper left corner origin).
/// </summary>
/// <param name="worldPos">Coordinates in the world</param>
/// <param name="screenPos">Converted coordinates</param>
/// <returns>True if worldPos corresponds to a position in front of the camera</returns>
public bool WorldToScreen(Vector3 worldPos, out Vector2 screenPos)
{
// Get base object with matrices
var matrixSingleton = this.getMatrixSingleton();
// Read current ViewProjectionMatrix plus game window size
var viewProjectionMatrix = new Matrix();
float width, height;
unsafe {
var rawMatrix = (float*) (matrixSingleton + 0x1b4).ToPointer();
for (var i = 0; i < 16; i++, rawMatrix += 1) {
viewProjectionMatrix[i] = *rawMatrix;
}
width = *rawMatrix;
height = *(rawMatrix + 1);
}
Vector3.Transform( ref worldPos, ref viewProjectionMatrix, out Vector3 pCoords);
screenPos = new Vector2(pCoords.X / pCoords.Z, pCoords.Y / pCoords.Z);
screenPos.X = 0.5f * width * (screenPos.X + 1f);
screenPos.Y = 0.5f * height * (1f - screenPos.Y);
return pCoords.Z > 0;
}
public Vector3 ScreenToWorld(Vector2 screenCoords) {
return new Vector3();
}
public void SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
public void Enable() {

View file

@ -12,6 +12,8 @@ namespace Dalamud.Game.Internal.Gui {
public IntPtr HandleItemHover { get; set; }
public IntPtr HandleItemOut { get; set; }
public IntPtr GetUIObject { get; private set; }
public IntPtr GetMatrixSingleton { get; private set; }
public IntPtr ScreenToWorld { get; private set; }
public GameGuiAddressResolver(IntPtr baseAddress) {
BaseAddress = baseAddress;
@ -31,6 +33,8 @@ namespace Dalamud.Game.Internal.Gui {
HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??");
HandleItemOut = sig.ScanText("48 89 5C 24 ?? 57 48 83 EC 20 48 8B FA 48 8B D9 4D");
GetUIObject = sig.ScanText("E8 ?? ?? ?? ?? 48 8B C8 48 8B 10 FF 52 40 80 88 ?? ?? ?? ?? 01 E9");
GetMatrixSingleton = sig.ScanText("E8 ?? ?? ?? ?? 48 8D 4C 24 ?? 48 89 4c 24 ?? 4C 8D 4D ?? 4C 8D 44 24 ??");
ScreenToWorld = sig.ScanText("48 83 EC 48 48 8B 05 ?? ?? ?? ?? 4D 8B D1");
}
}
}

View file

@ -4,55 +4,73 @@ using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Dalamud.Hooking;
using Serilog;
using SharpDX.DXGI;
namespace Dalamud.Game.Internal.Network {
public sealed class GameNetwork : IDisposable {
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void ProcessZonePacketDelegate(IntPtr a, IntPtr b, IntPtr dataPtr);
#region Hooks
private readonly Hook<ProcessZonePacketDelegate> processZonePacketHook;
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate void ProcessZonePacketDownDelegate(IntPtr a, uint targetId, IntPtr dataPtr);
private readonly Hook<ProcessZonePacketDownDelegate> processZonePacketDownHook;
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
private delegate byte ProcessZonePacketUpDelegate(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4);
private readonly Hook<ProcessZonePacketUpDelegate> processZonePacketUpHook;
#endregion
private GameNetworkAddressResolver Address { get; }
private IntPtr baseAddress;
public delegate void OnZonePacketDelegate(IntPtr dataPtr);
public delegate void OnNetworkMessageDelegate(IntPtr dataPtr, ushort opCode, uint targetId, NetworkMessageDirection direction);
/// <summary>
/// Event that is called when a network message is sent/received.
/// </summary>
public OnNetworkMessageDelegate OnNetworkMessage;
public OnZonePacketDelegate OnZonePacket;
private readonly Dalamud dalamud;
private readonly Queue<byte[]> zoneInjectQueue = new Queue<byte[]>();
public GameNetwork(Dalamud dalamud, SigScanner scanner) {
this.dalamud = dalamud;
public GameNetwork(SigScanner scanner) {
Address = new GameNetworkAddressResolver();
Address.Setup(scanner);
Log.Verbose("===== G A M E N E T W O R K =====");
Log.Verbose("ProcessZonePacket address {ProcessZonePacket}", Address.ProcessZonePacket);
Log.Verbose("ProcessZonePacketDown address {ProcessZonePacketDown}", Address.ProcessZonePacketDown);
Log.Verbose("ProcessZonePacketUp address {ProcessZonePacketUp}", Address.ProcessZonePacketUp);
this.processZonePacketHook =
new Hook<ProcessZonePacketDelegate>(Address.ProcessZonePacket,
new ProcessZonePacketDelegate(ProcessZonePacketDetour),
this.processZonePacketDownHook =
new Hook<ProcessZonePacketDownDelegate>(Address.ProcessZonePacketDown,
new ProcessZonePacketDownDelegate(ProcessZonePacketDownDetour),
this);
this.processZonePacketUpHook =
new Hook<ProcessZonePacketUpDelegate>(Address.ProcessZonePacketUp,
new ProcessZonePacketUpDelegate(ProcessZonePacketUpDetour),
this);
}
public void Enable() {
this.processZonePacketHook.Enable();
this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable();
}
public void Dispose() {
this.processZonePacketHook.Dispose();
this.processZonePacketDownHook.Dispose();
this.processZonePacketUpHook.Dispose();
}
private void ProcessZonePacketDetour(IntPtr a, IntPtr b, IntPtr dataPtr) {
private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr) {
this.baseAddress = a;
// Call events
this.OnZonePacket?.Invoke(dataPtr);
try {
this.processZonePacketHook.Original(a, b, dataPtr);
// Call events
this.OnNetworkMessage?.Invoke(dataPtr + 0x10, (ushort) Marshal.ReadInt16(dataPtr, 2), targetId, NetworkMessageDirection.ZoneDown);
this.processZonePacketDownHook.Original(a, targetId, dataPtr);
} catch (Exception ex) {
string header;
try {
@ -63,12 +81,45 @@ namespace Dalamud.Game.Internal.Network {
header = "failed";
}
Log.Error(ex, "Exception on ProcessZonePacket hook. Header: " + header);
Log.Error(ex, "Exception on ProcessZonePacketDown hook. Header: " + header);
this.processZonePacketHook.Original(a, b, dataPtr);
this.processZonePacketDownHook.Original(a, targetId, dataPtr);
}
}
private byte ProcessZonePacketUpDetour(IntPtr a1, IntPtr dataPtr, IntPtr a3, byte a4) {
try
{
// Call events
this.OnNetworkMessage?.Invoke(dataPtr + 0x20, (ushort) Marshal.ReadInt16(dataPtr), 0x0, NetworkMessageDirection.ZoneUp);
var op = Marshal.ReadInt16(dataPtr);
var length = Marshal.ReadInt16(dataPtr, 8);
Log.Verbose("[ZONEUP] op: {0} len: {1}", op.ToString("X"), length);
Util.DumpMemory(dataPtr + 0x20, length);
}
catch (Exception ex)
{
string header;
try
{
var data = new byte[32];
Marshal.Copy(dataPtr, data, 0, 32);
header = BitConverter.ToString(data);
}
catch (Exception)
{
header = "failed";
}
Log.Error(ex, "Exception on ProcessZonePacketUp hook. Header: " + header);
}
return this.processZonePacketUpHook.Original(a1, dataPtr, a3, a4);
}
#if DEBUG
public void InjectZoneProtoPacket(byte[] data) {
this.zoneInjectQueue.Enqueue(data);
@ -102,7 +153,7 @@ namespace Dalamud.Game.Internal.Network {
Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length);
if (this.baseAddress != IntPtr.Zero) {
this.processZonePacketHook.Original(this.baseAddress, IntPtr.Zero, unmanagedPacketData);
this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData);
}
Marshal.FreeHGlobal(unmanagedPacketData);

View file

@ -2,12 +2,15 @@ using System;
namespace Dalamud.Game.Internal.Network {
public sealed class GameNetworkAddressResolver : BaseAddressResolver {
public IntPtr ProcessZonePacket { get; private set; }
public IntPtr ProcessZonePacketDown { get; private set; }
public IntPtr ProcessZonePacketUp { get; private set; }
protected override void Setup64Bit(SigScanner sig) {
//ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 7A FF 0F B7 57 02 8D 42 89 3D 5F 02 00 00 0F 87 60 01 00 00 4C 8D 05");
//ProcessZonePacket = sig.ScanText("48 89 74 24 18 57 48 83 EC 50 8B F2 49 8B F8 41 0F B7 50 02 8B CE E8 ?? ?? 73 FF 0F B7 57 02 8D 42 ?? 3D ?? ?? 00 00 0F 87 60 01 00 00 4C 8D 05");
ProcessZonePacket = sig.ScanText("48 89 74 24 ?? 57 48 83 EC 50 8B FA 49 8B F0");
ProcessZonePacketDown = sig.ScanText("48 89 74 24 ?? 57 48 83 EC 50 8B FA 49 8B F0");
ProcessZonePacketUp =
sig.ScanText("48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC 70 8B 81 ?? ?? ?? ??");
}
}
}

View file

@ -0,0 +1,6 @@
namespace Dalamud.Game.Internal.Network {
public enum NetworkMessageDirection {
ZoneDown,
ZoneUp
}
}

View file

@ -5,6 +5,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Dalamud.Data.TransientSheet;
using Dalamud.Game.Internal.Network;
using Dalamud.Game.Network.MarketBoardUploaders;
using Dalamud.Game.Network.Structures;
using Dalamud.Game.Network.Universalis.MarketBoardUploaders;
@ -32,22 +33,23 @@ namespace Dalamud.Game.Network {
this.uploader = new UniversalisMarketBoardUploader(dalamud);
dalamud.Framework.Network.OnZonePacket += OnZonePacket;
dalamud.Framework.Network.OnNetworkMessage += OnNetworkMessage;
}
private void OnZonePacket(IntPtr dataPtr) {
if (!this.dalamud.Data.IsDataReady)
private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint targetId, NetworkMessageDirection direction) {
if (direction != NetworkMessageDirection.ZoneDown)
return;
var opCode = (ushort) Marshal.ReadInt16(dataPtr, 2);
if (!this.dalamud.Data.IsDataReady)
return;
if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"]) {
var data = new byte[64];
Marshal.Copy(dataPtr, data, 0, 64);
var notifyType = data[16];
var contentFinderConditionId = BitConverter.ToUInt16(data, 36);
var notifyType = data[0];
var contentFinderConditionId = BitConverter.ToUInt16(data, 0x14);
if (notifyType != 3)
return;
@ -65,14 +67,16 @@ namespace Dalamud.Game.Network {
contentFinderCondition.Image = 112324;
}
var flashInfo = new NativeFunctions.FLASHWINFO();
flashInfo.cbSize = (uint) Marshal.SizeOf<NativeFunctions.FLASHWINFO>();
flashInfo.uCount = uint.MaxValue;
flashInfo.dwTimeout = 0;
flashInfo.dwFlags = NativeFunctions.FlashWindow.FLASHW_TRAY |
NativeFunctions.FlashWindow.FLASHW_TIMERNOFG;
flashInfo.hwnd = Process.GetCurrentProcess().MainWindowHandle;
NativeFunctions.FlashWindowEx(ref flashInfo);
if (!NativeFunctions.ApplicationIsActivated()) {
var flashInfo = new NativeFunctions.FLASHWINFO();
flashInfo.cbSize = (uint)Marshal.SizeOf<NativeFunctions.FLASHWINFO>();
flashInfo.uCount = uint.MaxValue;
flashInfo.dwTimeout = 0;
flashInfo.dwFlags = NativeFunctions.FlashWindow.FLASHW_ALL |
NativeFunctions.FlashWindow.FLASHW_TIMERNOFG;
flashInfo.hwnd = Process.GetCurrentProcess().MainWindowHandle;
NativeFunctions.FlashWindowEx(ref flashInfo);
}
Task.Run(async () => {
this.dalamud.Framework.Gui.Chat.Print("Duty pop: " + contentFinderCondition.Name);
@ -97,8 +101,8 @@ namespace Dalamud.Game.Network {
Task.Run(async () => {
for (var rouletteIndex = 1; rouletteIndex < 11; rouletteIndex++) {
var currentRoleKey = data[16 + rouletteIndex];
var prevRoleKey = this.lastPreferredRole[16 + rouletteIndex];
var currentRoleKey = data[rouletteIndex];
var prevRoleKey = this.lastPreferredRole[rouletteIndex];
Log.Verbose("CfPreferredRole: {0} - {1} => {2}", rouletteIndex, prevRoleKey, currentRoleKey);
@ -139,8 +143,8 @@ namespace Dalamud.Game.Network {
if (!this.optOutMbUploads) {
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"]) {
var catalogId = (uint) Marshal.ReadInt32(dataPtr + 0x10);
var amount = Marshal.ReadByte(dataPtr + 0x1B);
var catalogId = (uint) Marshal.ReadInt32(dataPtr);
var amount = Marshal.ReadByte(dataPtr + 0xB);
this.marketBoardRequests.Add(new MarketBoardItemRequest {
CatalogId = catalogId,
@ -154,7 +158,7 @@ namespace Dalamud.Game.Network {
}
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"]) {
var listing = MarketBoardCurrentOfferings.Read(dataPtr + 0x10);
var listing = MarketBoardCurrentOfferings.Read(dataPtr);
var request =
this.marketBoardRequests.LastOrDefault(
@ -209,7 +213,7 @@ namespace Dalamud.Game.Network {
}
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"]) {
var listing = MarketBoardHistory.Read(dataPtr + 0x10);
var listing = MarketBoardHistory.Read(dataPtr);
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId);
@ -232,7 +236,7 @@ namespace Dalamud.Game.Network {
if (opCode == this.dalamud.Data.ServerOpCodes["MarketTaxRates"])
{
var taxes = MarketTaxRates.Read(dataPtr + 0x10);
var taxes = MarketTaxRates.Read(dataPtr);
Log.Verbose("MarketTaxRates: limsa#{0} grid#{1} uldah#{2} ish#{3} kugane#{4} cr#{5}",
taxes.LimsaLominsaTax, taxes.GridaniaTax, taxes.UldahTax, taxes.IshgardTax, taxes.KuganeTax, taxes.CrystariumTax);

View file

@ -14,6 +14,7 @@ namespace Dalamud.Interface
private static readonly Dictionary<string, string> AssetDictionary = new Dictionary<string, string> {
{AssetStoreUrl + "UIRes/serveropcode.json", "UIRes/serveropcode.json" },
{AssetStoreUrl + "UIRes/clientopcode.json", "UIRes/clientopcode.json" },
{AssetStoreUrl + "UIRes/NotoSansCJKjp-Medium.otf", "UIRes/NotoSansCJKjp-Medium.otf" },
{AssetStoreUrl + "UIRes/logo.png", "UIRes/logo.png" },
{AssetStoreUrl + "UIRes/loc/dalamud/dalamud_de.json", "UIRes/loc/dalamud/dalamud_de.json" },
@ -27,17 +28,6 @@ namespace Dalamud.Interface
public static async Task EnsureAssets(string baseDir) {
using var client = new HttpClient();
var assetVerRemote = await client.GetStringAsync(AssetStoreUrl + "version");
var assetVerPath = Path.Combine(baseDir, "assetver");
var assetVerLocal = "0";
if (File.Exists(assetVerPath))
assetVerLocal = File.ReadAllText(assetVerPath);
var forceRedownload = assetVerLocal != assetVerRemote;
if (forceRedownload)
Log.Information("Assets need redownload");
Log.Verbose("Starting asset download");
foreach (var entry in AssetDictionary) {
@ -45,7 +35,7 @@ namespace Dalamud.Interface
Directory.CreateDirectory(Path.GetDirectoryName(filePath));
if (!File.Exists(filePath) || forceRedownload) {
if (!File.Exists(filePath)) {
Log.Verbose("Downloading {0} to {1}...", entry.Key, entry.Value);
try {
File.WriteAllBytes(filePath, await client.GetByteArrayAsync(entry.Key));
@ -53,16 +43,8 @@ namespace Dalamud.Interface
// If another game is running, we don't want to just fail in here
Log.Error(ex, "Could not download asset.");
}
}
}
try {
File.WriteAllText(assetVerPath, assetVerRemote);
} catch (Exception ex) {
Log.Error(ex, "Could not write asset version.");
}
}
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Numerics;
using Dalamud.Game.Internal;
using ImGuiNET;
@ -7,10 +8,10 @@ using ImGuiScene;
namespace Dalamud.Interface
{
class DalamudCreditsWindow : IDisposable {
private string creditsText = @$"
private const string CreditsTextTempl = @"
Dalamud
A FFXIV Hooking Framework
Version {typeof(Dalamud).Assembly.GetName().Version}
Version {0}
created by:
@ -44,6 +45,11 @@ gucciBane
Your plugins were made by:
{1}
Special thanks:
Adam
@ -64,14 +70,23 @@ Contribute at: https://github.com/goaaats/Dalamud
Thank you for using XIVLauncher!
";
private readonly Dalamud dalamud;
private TextureWrap logoTexture;
private Framework framework;
public DalamudCreditsWindow(TextureWrap logoTexture, Framework framework) {
private string creditsText;
public DalamudCreditsWindow(Dalamud dalamud, TextureWrap logoTexture, Framework framework) {
this.dalamud = dalamud;
this.logoTexture = logoTexture;
this.framework = framework;
framework.Gui.SetBgm(132);
var pluginCredits = dalamud.PluginManager.Plugins.Where(x => x.Definition != null).Aggregate(string.Empty, (current, plugin) => current + $"{plugin.Definition.Name} by {plugin.Definition.Author}\n");
this.creditsText =
string.Format(CreditsTextTempl, typeof(Dalamud).Assembly.GetName().Version, pluginCredits);
}
public void Dispose() {

View file

@ -1,3 +1,4 @@
using System;
using System.Linq;
using System.Numerics;
using Dalamud.Game.Chat;
@ -17,6 +18,9 @@ namespace Dalamud.Interface
private int currentKind;
private bool drawActors = false;
private float maxActorDrawDistance = 20;
public DalamudDataWindow(Dalamud dalamud) {
this.dalamud = dalamud;
@ -84,6 +88,9 @@ namespace Dalamud.Interface
stateString += $"LastLinkedItem: {this.dalamud.Framework.Gui.Chat.LastLinkedItemId.ToString()}\n";
stateString += $"TerritoryType: {this.dalamud.ClientState.TerritoryType}\n\n";
ImGui.Checkbox("Draw actors on screen", ref this.drawActors);
ImGui.SliderFloat("Draw Distance", ref this.maxActorDrawDistance, 2f, 40f);
for (var i = 0; i < this.dalamud.ClientState.Actors.Length; i++) {
var actor = this.dalamud.ClientState.Actors[i];
@ -91,18 +98,36 @@ namespace Dalamud.Interface
continue;
stateString +=
$"{actor.Address.ToInt64():X}:{actor.ActorId:X}[{i}] - {actor.ObjectKind} - {actor.Name} - {actor.Position.X} {actor.Position.Y} {actor.Position.Z}\n";
$"{actor.Address.ToInt64():X}:{actor.ActorId:X}[{i}] - {actor.ObjectKind} - {actor.Name} - X{actor.Position.X} Y{actor.Position.Y} Z{actor.Position.Z} D{actor.YalmDistanceX} R{actor.Rotation}\n";
if (actor is Npc npc)
stateString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n";
if (actor is Chara chara)
stateString +=
$" Level: {chara.Level} ClassJob: {chara.ClassJob.GameData.Name} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n";
$" Level: {chara.Level} ClassJob: {chara.ClassJob.GameData.Name} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}\n Customize: {BitConverter.ToString(chara.Customize).Replace("-", " ")}\n";
if (actor is PlayerCharacter pc)
stateString +=
$" HomeWorld: {pc.HomeWorld.GameData.Name} CurrentWorld: {pc.CurrentWorld.GameData.Name} FC: {pc.CompanyTag}\n";
if (this.drawActors && this.dalamud.Framework.Gui.WorldToScreen(actor.Position, out var screenCoords)) {
ImGui.PushID("ActorWindow" + i);
ImGui.SetNextWindowPos(new Vector2(screenCoords.X, screenCoords.Y));
if (actor.YalmDistanceX > this.maxActorDrawDistance)
continue;
ImGui.SetNextWindowBgAlpha(Math.Max(1f - (actor.YalmDistanceX / this.maxActorDrawDistance), 0.2f));
if (ImGui.Begin("Actor" + i,
ImGuiWindowFlags.NoDecoration | ImGuiWindowFlags.AlwaysAutoResize |
ImGuiWindowFlags.NoSavedSettings | ImGuiWindowFlags.NoMove | ImGuiWindowFlags.NoMouseInputs |
ImGuiWindowFlags.NoFocusOnAppearing | ImGuiWindowFlags.NoNav)) {
ImGui.Text($"{actor.Address.ToInt64():X}:{actor.ActorId:X}[{i}] - {actor.ObjectKind} - {actor.Name}");
ImGui.End();
}
ImGui.PopID();
}
}
}

View file

@ -54,11 +54,15 @@ namespace Dalamud.Interface
public ImGuiIOPtr LastImGuiIoPtr;
public Action OnBuildFonts;
private bool isRebuildingFonts = false;
/// <summary>
/// This event gets called by a plugin UiBuilder when read
/// </summary>
public event RawDX11Scene.BuildUIDelegate OnDraw;
public InterfaceManager(Dalamud dalamud, SigScanner scanner)
{
this.dalamud = dalamud;
@ -200,7 +204,18 @@ namespace Dalamud.Interface
return null;
}
private unsafe IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
// Sets up a deferred invocation of font rebuilding, before the next render frame
public void RebuildFonts()
{
// don't invoke this multiple times per frame, in case multiple plugins call it
if (!this.isRebuildingFonts)
{
this.isRebuildingFonts = true;
this.scene.OnNewRenderFrame += RebuildFontsInternal;
}
}
private IntPtr PresentDetour(IntPtr swapChain, uint syncInterval, uint presentFlags)
{
if (this.scene == null)
{
@ -209,30 +224,7 @@ namespace Dalamud.Interface
this.scene.OnBuildUI += Display;
this.scene.OnNewInputFrame += OnNewInputFrame;
ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
fontConfig.MergeMode = true;
fontConfig.PixelSnapH = true;
var fontPathJp = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "UIRes", "NotoSansCJKjp-Medium.otf");
ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathJp, 17.0f, null, ImGui.GetIO().Fonts.GetGlyphRangesJapanese());
var fontPathGame = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "UIRes", "gamesym.ttf");
Log.Verbose(fontPathGame);
var rangeHandle = GCHandle.Alloc(new ushort[]
{
0xE020,
0xE0DB,
0
}, GCHandleType.Pinned);
ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathGame, 17.0f, fontConfig, rangeHandle.AddrOfPinnedObject());
ImGui.GetIO().Fonts.Build();
fontConfig.Destroy();
rangeHandle.Free();
SetupFonts();
ImGui.GetStyle().GrabRounding = 3f;
ImGui.GetStyle().FrameRounding = 4f;
@ -268,6 +260,49 @@ namespace Dalamud.Interface
return this.presentHook.Original(swapChain, syncInterval, presentFlags);
}
private unsafe void SetupFonts()
{
ImGui.GetIO().Fonts.Clear();
ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig();
fontConfig.MergeMode = true;
fontConfig.PixelSnapH = true;
var fontPathJp = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "UIRes", "NotoSansCJKjp-Medium.otf");
ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathJp, 17.0f, null, ImGui.GetIO().Fonts.GetGlyphRangesJapanese());
var fontPathGame = Path.Combine(this.dalamud.StartInfo.WorkingDirectory, "UIRes", "gamesym.ttf");
Log.Verbose(fontPathGame);
var rangeHandle = GCHandle.Alloc(new ushort[]
{
0xE020,
0xE0DB,
0
}, GCHandleType.Pinned);
ImGui.GetIO().Fonts.AddFontFromFileTTF(fontPathGame, 17.0f, fontConfig, rangeHandle.AddrOfPinnedObject());
OnBuildFonts?.Invoke();
ImGui.GetIO().Fonts.Build();
fontConfig.Destroy();
rangeHandle.Free();
}
// This is intended to only be called as a handler attached to scene.OnNewRenderFrame
private void RebuildFontsInternal()
{
SetupFonts();
this.scene.OnNewRenderFrame -= RebuildFontsInternal;
this.scene.InvalidateFonts();
this.isRebuildingFonts = false;
}
private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
{
Log.Verbose($"Calling resizebuffers {bufferCount} {width} {height} {newFormat} {swapChainFlags}");

View file

@ -70,6 +70,27 @@ namespace Dalamud.Interface
public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) =>
this.interfaceManager.LoadImageRaw(imageData, width, height, numChannels);
/// <summary>
/// An event that is called any time ImGui fonts need to be rebuilt.<br/>
/// Any ImFontPtr objects that you store <strong>can be invalidated</strong> when fonts are rebuilt
/// (at any time), so you should both reload your custom fonts and restore those
/// pointers inside this handler.<br/>
/// <strong>PLEASE remove this handler inside Dipose, or when you no longer need your fonts!</strong>
/// </summary>
public Action OnBuildFonts
{
get { return this.interfaceManager.OnBuildFonts; }
set { this.interfaceManager.OnBuildFonts = value; }
}
/// <summary>
/// Call this to queue a rebuild of the font atlas.<br/>
/// This will invoke any <see cref="OnBuildFonts"/> handlers and ensure that any loaded fonts are
/// ready to be used on the next UI frame.
/// </summary>
public void RebuildFonts() =>
this.interfaceManager.RebuildFonts();
/// <summary>
/// Event that is fired when the plugin should open its configuration interface.
/// </summary>

View file

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
@ -58,6 +59,28 @@ namespace Dalamud
#endregion
/// <summary>Returns true if the current application has focus, false otherwise</summary>
public static bool ApplicationIsActivated()
{
var activatedHandle = GetForegroundWindow();
if (activatedHandle == IntPtr.Zero)
{
return false; // No window is currently activated
}
var procId = Process.GetCurrentProcess().Id;
int activeProcId;
GetWindowThreadProcessId(activatedHandle, out activeProcId);
return activeProcId == procId;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FlashWindowEx(ref FLASHWINFO pwfi);

View file

@ -58,14 +58,15 @@ namespace Dalamud.Plugin
}
}
public bool InstallPlugin(PluginDefinition definition) {
public bool InstallPlugin(PluginDefinition definition, bool enableAfterInstall = true) {
try
{
var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory, definition.InternalName, definition.AssemblyVersion));
var dllFile = new FileInfo(Path.Combine(outputDir.FullName, $"{definition.InternalName}.dll"));
var disabledFile = new FileInfo(Path.Combine(outputDir.FullName, ".disabled"));
var wasDisabled = disabledFile.Exists;
if (dllFile.Exists)
if (dllFile.Exists && enableAfterInstall)
{
if (disabledFile.Exists)
disabledFile.Delete();
@ -73,9 +74,17 @@ namespace Dalamud.Plugin
return this.manager.LoadPluginFromAssembly(dllFile, false);
}
if (outputDir.Exists)
outputDir.Delete(true);
outputDir.Create();
if (dllFile.Exists && !enableAfterInstall) {
return true;
}
try {
if (outputDir.Exists)
outputDir.Delete(true);
outputDir.Create();
} catch {
// ignored, since the plugin may be loaded already
}
var path = Path.GetTempFileName();
Log.Information("Downloading plugin to {0}", path);
@ -86,6 +95,11 @@ namespace Dalamud.Plugin
ZipFile.ExtractToDirectory(path, outputDir.FullName);
if (wasDisabled || !enableAfterInstall) {
disabledFile.Create();
return true;
}
return this.manager.LoadPluginFromAssembly(dllFile, false);
}
catch (Exception e)
@ -145,6 +159,12 @@ namespace Dalamud.Plugin
if (!dryRun)
{
var wasEnabled =
this.manager.Plugins.Where(x => x.Definition != null).Any(
x => x.Definition.InternalName == info.InternalName); ;
Log.Verbose("wasEnabled: {0}", wasEnabled);
// Try to disable plugin if it is loaded
try
{
@ -153,7 +173,7 @@ namespace Dalamud.Plugin
catch (Exception ex)
{
Log.Error(ex, "Plugin disable failed");
hasError = true;
//hasError = true;
}
try {
@ -165,10 +185,10 @@ namespace Dalamud.Plugin
disabledFile.Create();
}
} catch (Exception ex) {
Log.Error(ex, "Plugin disable failed");
Log.Error(ex, "Plugin disable old versions failed");
}
var installSuccess = InstallPlugin(remoteInfo);
var installSuccess = InstallPlugin(remoteInfo, wasEnabled);
if (installSuccess)
{

View file

@ -1,19 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Serilog;
namespace Dalamud
{
static class Util
{
public static string ByteArrayToHex(byte[] bytes, int offset = 0, int bytesPerLine = 16)
{
if (bytes == null)
{
return String.Empty;
}
namespace Dalamud {
internal static class Util {
public static void DumpMemory(IntPtr offset, int len = 512) {
var data = new byte[len];
Marshal.Copy(offset, data, 0, len);
Log.Information(ByteArrayToHex(data));
}
public static string ByteArrayToHex(byte[] bytes, int offset = 0, int bytesPerLine = 16) {
if (bytes == null) return string.Empty;
var hexChars = "0123456789ABCDEF".ToCharArray();
@ -26,8 +28,7 @@ namespace Dalamud
var sb = new StringBuilder(numLines * lineLength);
for (var i = 0; i < bytes.Length; i += bytesPerLine)
{
for (var i = 0; i < bytes.Length; i += bytesPerLine) {
var h = i + offset;
line[0] = hexChars[(h >> 28) & 0xF];
@ -42,25 +43,18 @@ namespace Dalamud
var hexColumn = offsetBlock;
var charColumn = byteBlock;
for (var j = 0; j < bytesPerLine; j++)
{
if (j > 0 && (j & 7) == 0)
{
hexColumn++;
}
for (var j = 0; j < bytesPerLine; j++) {
if (j > 0 && (j & 7) == 0) hexColumn++;
if (i + j >= bytes.Length)
{
if (i + j >= bytes.Length) {
line[hexColumn] = ' ';
line[hexColumn + 1] = ' ';
line[charColumn] = ' ';
}
else
{
} else {
var by = bytes[i + j];
line[hexColumn] = hexChars[(@by >> 4) & 0xF];
line[hexColumn + 1] = hexChars[@by & 0xF];
line[charColumn] = @by < 32 ? '.' : (char)@by;
line[hexColumn] = hexChars[(by >> 4) & 0xF];
line[hexColumn + 1] = hexChars[by & 0xF];
line[charColumn] = by < 32 ? '.' : (char) by;
}
hexColumn += 3;

@ -1 +1 @@
Subproject commit aaa037938d6fe835a15542a3451d12108e3f83b6
Subproject commit d5b9345dc1463d746b832843bd7c81b753d4e5b0