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

@ -14,14 +14,19 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Feature"> <PropertyGroup Label="Feature">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>4.9.2.0</AssemblyVersion> <AssemblyVersion>4.9.3.0</AssemblyVersion>
<FileVersion>4.9.2.0</FileVersion> <FileVersion>4.9.3.0</FileVersion>
<Description>XIVLauncher addon injection</Description> <Description>XIVLauncher addon injection</Description>
<Version>4.9.2.0</Version> <Version>4.9.3.0</Version>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile></DocumentationFile> <DocumentationFile></DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
<PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\injector\</PathMap>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup> <PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<PackageIcon></PackageIcon> <PackageIcon></PackageIcon>

View file

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

View file

@ -11,27 +11,21 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<DebugSymbols>true</DebugSymbols> <DebugSymbols>true</DebugSymbols>
<DebugType>Portable</DebugType> <DebugType>Portable</DebugType>
<DocumentationFile>$(SolutionDir)\bin\Dalamud.xml</DocumentationFile>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Feature"> <PropertyGroup Label="Feature">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<AssemblyVersion>4.9.2.0</AssemblyVersion> <AssemblyVersion>4.9.3.0</AssemblyVersion>
<Version>4.9.2.0</Version> <Version>4.9.3.0</Version>
<FileVersion>4.9.2.0</FileVersion> <FileVersion>4.9.3.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup Label="Resources"> <ItemGroup Label="Resources">
<None Include="$(SolutionDir)/Resources/**/*" CopyToOutputDirectory="PreserveNewest" Visible="false" /> <None Include="$(SolutionDir)/Resources/**/*" CopyToOutputDirectory="PreserveNewest" Visible="false" />
</ItemGroup> </ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'"> <PropertyGroup Condition="'$(Configuration)'=='Release'">
<DocumentationFile>$(SolutionDir)\bin\Dalamud.xml</DocumentationFile> <AppOutputBase>$(MSBuildProjectDirectory)\</AppOutputBase>
</PropertyGroup> <PathMap>$(AppOutputBase)=C:\goatsoft\companysecrets\dalamud\</PathMap>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> <Deterministic>true</Deterministic>
<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>
<PropertyGroup> <PropertyGroup>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>

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. /// This class provides data for Dalamud-internal features, but can also be used by plugins if needed.
/// </summary> /// </summary>
public class DataManager { public class DataManager {
/// <summary>
/// OpCodes sent by the server to the client.
/// </summary>
public ReadOnlyDictionary<string, ushort> ServerOpCodes { get; private set; } 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> /// <summary>
/// An <see cref="ExcelModule"/> object which gives access to any of the game's sheet data. /// An <see cref="ExcelModule"/> object which gives access to any of the game's sheet data.
@ -55,13 +62,19 @@ namespace Dalamud.Data
{ {
try 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"))); 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 var luminaOptions = new LuminaOptions

View file

@ -214,7 +214,7 @@ namespace Dalamud.DiscordBot {
var avatarUrl = string.Empty; var avatarUrl = string.Empty;
var lodestoneId = string.Empty; var lodestoneId = string.Empty;
if (!this.config.DisableEmbeds) { if (!this.config.DisableEmbeds && !string.IsNullOrEmpty(senderName)) {
var searchResult = await GetCharacterInfo(senderName, senderWorld); var searchResult = await GetCharacterInfo(senderName, senderWorld);
lodestoneId = searchResult.LodestoneId; 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 X;
public float Z; public float Z;
public float Y; 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> /// </summary>
public Position3 Position => this.actorStruct.Position; 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> /// <summary>
/// Displayname of this <see cref="Actor">Actor</see>. /// Displayname of this <see cref="Actor">Actor</see>.
/// </summary> /// </summary>
@ -49,5 +55,15 @@ namespace Dalamud.Game.ClientState.Actors.Types {
/// possible values. /// possible values.
/// </summary> /// </summary>
public ObjectKind ObjectKind => this.actorStruct.ObjectKind; 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> /// <summary>
/// The maximum MP of this Chara. /// The maximum MP of this Chara.
/// </summary> /// </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> /// </summary>
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public struct Actor { 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(116)] public int ActorId;
[FieldOffset(128)] public int DataId; [FieldOffset(128)] public int DataId;
[FieldOffset(132)] public int OwnerId; [FieldOffset(132)] public int OwnerId;
[FieldOffset(140)] public ObjectKind ObjectKind; [FieldOffset(140)] public ObjectKind ObjectKind;
[FieldOffset(141)] public byte SubKind; [FieldOffset(141)] public byte SubKind;
[FieldOffset(142)] public bool IsFriendly; [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(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(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; [FieldOffset(0x17F8)] public int TargetActorId;
// This field can't be correctly aligned, so we have to cut it manually. // 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(0x1868)] public int NameId;
[FieldOffset(0x1884)] public byte CurrentWorld; [FieldOffset(0x1884)] public byte CurrentWorld;
[FieldOffset(0x1886)] public byte HomeWorld; [FieldOffset(0x1886)] public byte HomeWorld;
[FieldOffset(6328)] public int CurrentHp; [FieldOffset(0x1898)] public int CurrentHp;
[FieldOffset(6332)] public int MaxHp; [FieldOffset(0x189C)] public int MaxHp;
[FieldOffset(6336)] public int CurrentMp; [FieldOffset(0x18A0)] public int CurrentMp;
[FieldOffset(6340)] public int MaxMp; // 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(6358)] public byte ClassJob;
[FieldOffset(6360)] public byte Level; [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); Gui = new GameGui(Address.GuiManager, scanner, dalamud);
Network = new GameNetwork(dalamud, scanner); Network = new GameNetwork(scanner);
//Resource = new ResourceManager(dalamud, scanner); //Resource = new ResourceManager(dalamud, scanner);
} }

View file

@ -3,6 +3,7 @@ using System.Runtime.InteropServices;
using Dalamud.Game.Chat.SeStringHandling.Payloads; using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Dalamud.Hooking; using Dalamud.Hooking;
using Serilog; using Serilog;
using SharpDX;
namespace Dalamud.Game.Internal.Gui { namespace Dalamud.Game.Internal.Gui {
public sealed class GameGui : IDisposable { public sealed class GameGui : IDisposable {
@ -34,6 +35,14 @@ namespace Dalamud.Game.Internal.Gui {
private delegate bool OpenMapWithFlagDelegate(IntPtr UIMapObject, string flag); private delegate bool OpenMapWithFlagDelegate(IntPtr UIMapObject, string flag);
private OpenMapWithFlagDelegate openMapWithFlag; 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> /// <summary>
/// The item ID that is currently hovered by the player. 0 when no item is hovered. /// 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 /// If > 1.000.000, subtract 1.000.000 and treat it as HQ
@ -74,6 +83,12 @@ namespace Dalamud.Game.Internal.Gui {
this); this);
this.getUIObject = Marshal.GetDelegateForFunctionPointer<GetUIObjectDelegate>(Address.GetUIObject); 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) { 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; 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) public bool OpenMapWithMapLink(MapLinkPayload mapLink)
{ {
var uiObjectPtr = getUIObject(); var uiObjectPtr = getUIObject();
@ -158,6 +178,45 @@ namespace Dalamud.Game.Internal.Gui {
return this.openMapWithFlag(uiMapObjectPtr, mapLinkString); 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 SetBgm(ushort bgmKey) => this.setGlobalBgmHook.Original(bgmKey, 0, 0, 0, 0, 0);
public void Enable() { public void Enable() {

View file

@ -12,6 +12,8 @@ namespace Dalamud.Game.Internal.Gui {
public IntPtr HandleItemHover { get; set; } public IntPtr HandleItemHover { get; set; }
public IntPtr HandleItemOut { get; set; } public IntPtr HandleItemOut { get; set; }
public IntPtr GetUIObject { get; private set; } public IntPtr GetUIObject { get; private set; }
public IntPtr GetMatrixSingleton { get; private set; }
public IntPtr ScreenToWorld { get; private set; }
public GameGuiAddressResolver(IntPtr baseAddress) { public GameGuiAddressResolver(IntPtr baseAddress) {
BaseAddress = baseAddress; BaseAddress = baseAddress;
@ -31,6 +33,8 @@ namespace Dalamud.Game.Internal.Gui {
HandleItemHover = sig.ScanText("E8 ?? ?? ?? ?? 48 8B 5C 24 ?? 48 89 AE ?? ?? ?? ??"); 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"); 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"); 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 System.Runtime.InteropServices;
using Dalamud.Hooking; using Dalamud.Hooking;
using Serilog; using Serilog;
using SharpDX.DXGI;
namespace Dalamud.Game.Internal.Network { namespace Dalamud.Game.Internal.Network {
public sealed class GameNetwork : IDisposable { public sealed class GameNetwork : IDisposable {
[UnmanagedFunctionPointer(CallingConvention.ThisCall)] #region Hooks
private delegate void ProcessZonePacketDelegate(IntPtr a, IntPtr b, IntPtr dataPtr);
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 GameNetworkAddressResolver Address { get; }
private IntPtr baseAddress; 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[]>(); private readonly Queue<byte[]> zoneInjectQueue = new Queue<byte[]>();
public GameNetwork(Dalamud dalamud, SigScanner scanner) { public GameNetwork(SigScanner scanner) {
this.dalamud = dalamud;
Address = new GameNetworkAddressResolver(); Address = new GameNetworkAddressResolver();
Address.Setup(scanner); Address.Setup(scanner);
Log.Verbose("===== G A M E N E T W O R K ====="); 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 = this.processZonePacketDownHook =
new Hook<ProcessZonePacketDelegate>(Address.ProcessZonePacket, new Hook<ProcessZonePacketDownDelegate>(Address.ProcessZonePacketDown,
new ProcessZonePacketDelegate(ProcessZonePacketDetour), new ProcessZonePacketDownDelegate(ProcessZonePacketDownDetour),
this);
this.processZonePacketUpHook =
new Hook<ProcessZonePacketUpDelegate>(Address.ProcessZonePacketUp,
new ProcessZonePacketUpDelegate(ProcessZonePacketUpDetour),
this); this);
} }
public void Enable() { public void Enable() {
this.processZonePacketHook.Enable(); this.processZonePacketDownHook.Enable();
this.processZonePacketUpHook.Enable();
} }
public void Dispose() { 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; this.baseAddress = a;
// Call events
this.OnZonePacket?.Invoke(dataPtr);
try { 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) { } catch (Exception ex) {
string header; string header;
try { try {
@ -63,12 +81,45 @@ namespace Dalamud.Game.Internal.Network {
header = "failed"; 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 #if DEBUG
public void InjectZoneProtoPacket(byte[] data) { public void InjectZoneProtoPacket(byte[] data) {
this.zoneInjectQueue.Enqueue(data); this.zoneInjectQueue.Enqueue(data);
@ -102,7 +153,7 @@ namespace Dalamud.Game.Internal.Network {
Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length); Marshal.Copy(packetData, 0, unmanagedPacketData, packetData.Length);
if (this.baseAddress != IntPtr.Zero) { if (this.baseAddress != IntPtr.Zero) {
this.processZonePacketHook.Original(this.baseAddress, IntPtr.Zero, unmanagedPacketData); this.processZonePacketDownHook.Original(this.baseAddress, 0, unmanagedPacketData);
} }
Marshal.FreeHGlobal(unmanagedPacketData); Marshal.FreeHGlobal(unmanagedPacketData);

View file

@ -2,12 +2,15 @@ using System;
namespace Dalamud.Game.Internal.Network { namespace Dalamud.Game.Internal.Network {
public sealed class GameNetworkAddressResolver : BaseAddressResolver { 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) { 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 ?? ?? 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 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.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dalamud.Data.TransientSheet; using Dalamud.Data.TransientSheet;
using Dalamud.Game.Internal.Network;
using Dalamud.Game.Network.MarketBoardUploaders; using Dalamud.Game.Network.MarketBoardUploaders;
using Dalamud.Game.Network.Structures; using Dalamud.Game.Network.Structures;
using Dalamud.Game.Network.Universalis.MarketBoardUploaders; using Dalamud.Game.Network.Universalis.MarketBoardUploaders;
@ -32,22 +33,23 @@ namespace Dalamud.Game.Network {
this.uploader = new UniversalisMarketBoardUploader(dalamud); this.uploader = new UniversalisMarketBoardUploader(dalamud);
dalamud.Framework.Network.OnZonePacket += OnZonePacket; dalamud.Framework.Network.OnNetworkMessage += OnNetworkMessage;
} }
private void OnZonePacket(IntPtr dataPtr) { private void OnNetworkMessage(IntPtr dataPtr, ushort opCode, uint targetId, NetworkMessageDirection direction) {
if (!this.dalamud.Data.IsDataReady) if (direction != NetworkMessageDirection.ZoneDown)
return; return;
var opCode = (ushort) Marshal.ReadInt16(dataPtr, 2); if (!this.dalamud.Data.IsDataReady)
return;
if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"]) { if (opCode == this.dalamud.Data.ServerOpCodes["CfNotifyPop"]) {
var data = new byte[64]; var data = new byte[64];
Marshal.Copy(dataPtr, data, 0, 64); Marshal.Copy(dataPtr, data, 0, 64);
var notifyType = data[16]; var notifyType = data[0];
var contentFinderConditionId = BitConverter.ToUInt16(data, 36); var contentFinderConditionId = BitConverter.ToUInt16(data, 0x14);
if (notifyType != 3) if (notifyType != 3)
return; return;
@ -65,14 +67,16 @@ namespace Dalamud.Game.Network {
contentFinderCondition.Image = 112324; contentFinderCondition.Image = 112324;
} }
if (!NativeFunctions.ApplicationIsActivated()) {
var flashInfo = new NativeFunctions.FLASHWINFO(); var flashInfo = new NativeFunctions.FLASHWINFO();
flashInfo.cbSize = (uint) Marshal.SizeOf<NativeFunctions.FLASHWINFO>(); flashInfo.cbSize = (uint)Marshal.SizeOf<NativeFunctions.FLASHWINFO>();
flashInfo.uCount = uint.MaxValue; flashInfo.uCount = uint.MaxValue;
flashInfo.dwTimeout = 0; flashInfo.dwTimeout = 0;
flashInfo.dwFlags = NativeFunctions.FlashWindow.FLASHW_TRAY | flashInfo.dwFlags = NativeFunctions.FlashWindow.FLASHW_ALL |
NativeFunctions.FlashWindow.FLASHW_TIMERNOFG; NativeFunctions.FlashWindow.FLASHW_TIMERNOFG;
flashInfo.hwnd = Process.GetCurrentProcess().MainWindowHandle; flashInfo.hwnd = Process.GetCurrentProcess().MainWindowHandle;
NativeFunctions.FlashWindowEx(ref flashInfo); NativeFunctions.FlashWindowEx(ref flashInfo);
}
Task.Run(async () => { Task.Run(async () => {
this.dalamud.Framework.Gui.Chat.Print("Duty pop: " + contentFinderCondition.Name); this.dalamud.Framework.Gui.Chat.Print("Duty pop: " + contentFinderCondition.Name);
@ -97,8 +101,8 @@ namespace Dalamud.Game.Network {
Task.Run(async () => { Task.Run(async () => {
for (var rouletteIndex = 1; rouletteIndex < 11; rouletteIndex++) { for (var rouletteIndex = 1; rouletteIndex < 11; rouletteIndex++) {
var currentRoleKey = data[16 + rouletteIndex]; var currentRoleKey = data[rouletteIndex];
var prevRoleKey = this.lastPreferredRole[16 + rouletteIndex]; var prevRoleKey = this.lastPreferredRole[rouletteIndex];
Log.Verbose("CfPreferredRole: {0} - {1} => {2}", rouletteIndex, prevRoleKey, currentRoleKey); Log.Verbose("CfPreferredRole: {0} - {1} => {2}", rouletteIndex, prevRoleKey, currentRoleKey);
@ -139,8 +143,8 @@ namespace Dalamud.Game.Network {
if (!this.optOutMbUploads) { if (!this.optOutMbUploads) {
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"]) { if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardItemRequestStart"]) {
var catalogId = (uint) Marshal.ReadInt32(dataPtr + 0x10); var catalogId = (uint) Marshal.ReadInt32(dataPtr);
var amount = Marshal.ReadByte(dataPtr + 0x1B); var amount = Marshal.ReadByte(dataPtr + 0xB);
this.marketBoardRequests.Add(new MarketBoardItemRequest { this.marketBoardRequests.Add(new MarketBoardItemRequest {
CatalogId = catalogId, CatalogId = catalogId,
@ -154,7 +158,7 @@ namespace Dalamud.Game.Network {
} }
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"]) { if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardOfferings"]) {
var listing = MarketBoardCurrentOfferings.Read(dataPtr + 0x10); var listing = MarketBoardCurrentOfferings.Read(dataPtr);
var request = var request =
this.marketBoardRequests.LastOrDefault( this.marketBoardRequests.LastOrDefault(
@ -209,7 +213,7 @@ namespace Dalamud.Game.Network {
} }
if (opCode == this.dalamud.Data.ServerOpCodes["MarketBoardHistory"]) { 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); 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"]) 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}", 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); 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> { private static readonly Dictionary<string, string> AssetDictionary = new Dictionary<string, string> {
{AssetStoreUrl + "UIRes/serveropcode.json", "UIRes/serveropcode.json" }, {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/NotoSansCJKjp-Medium.otf", "UIRes/NotoSansCJKjp-Medium.otf" },
{AssetStoreUrl + "UIRes/logo.png", "UIRes/logo.png" }, {AssetStoreUrl + "UIRes/logo.png", "UIRes/logo.png" },
{AssetStoreUrl + "UIRes/loc/dalamud/dalamud_de.json", "UIRes/loc/dalamud/dalamud_de.json" }, {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) { public static async Task EnsureAssets(string baseDir) {
using var client = new HttpClient(); 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"); Log.Verbose("Starting asset download");
foreach (var entry in AssetDictionary) { foreach (var entry in AssetDictionary) {
@ -45,7 +35,7 @@ namespace Dalamud.Interface
Directory.CreateDirectory(Path.GetDirectoryName(filePath)); Directory.CreateDirectory(Path.GetDirectoryName(filePath));
if (!File.Exists(filePath) || forceRedownload) { if (!File.Exists(filePath)) {
Log.Verbose("Downloading {0} to {1}...", entry.Key, entry.Value); Log.Verbose("Downloading {0} to {1}...", entry.Key, entry.Value);
try { try {
File.WriteAllBytes(filePath, await client.GetByteArrayAsync(entry.Key)); 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 // If another game is running, we don't want to just fail in here
Log.Error(ex, "Could not download asset."); 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;
using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Game.Internal; using Dalamud.Game.Internal;
using ImGuiNET; using ImGuiNET;
@ -7,10 +8,10 @@ using ImGuiScene;
namespace Dalamud.Interface namespace Dalamud.Interface
{ {
class DalamudCreditsWindow : IDisposable { class DalamudCreditsWindow : IDisposable {
private string creditsText = @$" private const string CreditsTextTempl = @"
Dalamud Dalamud
A FFXIV Hooking Framework A FFXIV Hooking Framework
Version {typeof(Dalamud).Assembly.GetName().Version} Version {0}
created by: created by:
@ -44,6 +45,11 @@ gucciBane
Your plugins were made by:
{1}
Special thanks: Special thanks:
Adam Adam
@ -64,14 +70,23 @@ Contribute at: https://github.com/goaaats/Dalamud
Thank you for using XIVLauncher! Thank you for using XIVLauncher!
"; ";
private readonly Dalamud dalamud;
private TextureWrap logoTexture; private TextureWrap logoTexture;
private Framework framework; 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.logoTexture = logoTexture;
this.framework = framework; this.framework = framework;
framework.Gui.SetBgm(132); 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() { public void Dispose() {

View file

@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Game.Chat; using Dalamud.Game.Chat;
@ -17,6 +18,9 @@ namespace Dalamud.Interface
private int currentKind; private int currentKind;
private bool drawActors = false;
private float maxActorDrawDistance = 20;
public DalamudDataWindow(Dalamud dalamud) { public DalamudDataWindow(Dalamud dalamud) {
this.dalamud = dalamud; this.dalamud = dalamud;
@ -84,6 +88,9 @@ namespace Dalamud.Interface
stateString += $"LastLinkedItem: {this.dalamud.Framework.Gui.Chat.LastLinkedItemId.ToString()}\n"; stateString += $"LastLinkedItem: {this.dalamud.Framework.Gui.Chat.LastLinkedItemId.ToString()}\n";
stateString += $"TerritoryType: {this.dalamud.ClientState.TerritoryType}\n\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++) { for (var i = 0; i < this.dalamud.ClientState.Actors.Length; i++) {
var actor = this.dalamud.ClientState.Actors[i]; var actor = this.dalamud.ClientState.Actors[i];
@ -91,18 +98,36 @@ namespace Dalamud.Interface
continue; continue;
stateString += 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) if (actor is Npc npc)
stateString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n"; stateString += $" DataId: {npc.DataId} NameId:{npc.NameId}\n";
if (actor is Chara chara) if (actor is Chara chara)
stateString += 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) if (actor is PlayerCharacter pc)
stateString += stateString +=
$" HomeWorld: {pc.HomeWorld.GameData.Name} CurrentWorld: {pc.CurrentWorld.GameData.Name} FC: {pc.CompanyTag}\n"; $" 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 ImGuiIOPtr LastImGuiIoPtr;
public Action OnBuildFonts;
private bool isRebuildingFonts = false;
/// <summary> /// <summary>
/// This event gets called by a plugin UiBuilder when read /// This event gets called by a plugin UiBuilder when read
/// </summary> /// </summary>
public event RawDX11Scene.BuildUIDelegate OnDraw; public event RawDX11Scene.BuildUIDelegate OnDraw;
public InterfaceManager(Dalamud dalamud, SigScanner scanner) public InterfaceManager(Dalamud dalamud, SigScanner scanner)
{ {
this.dalamud = dalamud; this.dalamud = dalamud;
@ -200,7 +204,18 @@ namespace Dalamud.Interface
return null; 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) if (this.scene == null)
{ {
@ -209,30 +224,7 @@ namespace Dalamud.Interface
this.scene.OnBuildUI += Display; this.scene.OnBuildUI += Display;
this.scene.OnNewInputFrame += OnNewInputFrame; this.scene.OnNewInputFrame += OnNewInputFrame;
ImFontConfigPtr fontConfig = ImGuiNative.ImFontConfig_ImFontConfig(); SetupFonts();
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();
ImGui.GetStyle().GrabRounding = 3f; ImGui.GetStyle().GrabRounding = 3f;
ImGui.GetStyle().FrameRounding = 4f; ImGui.GetStyle().FrameRounding = 4f;
@ -268,6 +260,49 @@ namespace Dalamud.Interface
return this.presentHook.Original(swapChain, syncInterval, presentFlags); 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) private IntPtr ResizeBuffersDetour(IntPtr swapChain, uint bufferCount, uint width, uint height, uint newFormat, uint swapChainFlags)
{ {
Log.Verbose($"Calling resizebuffers {bufferCount} {width} {height} {newFormat} {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) => public TextureWrap LoadImageRaw(byte[] imageData, int width, int height, int numChannels) =>
this.interfaceManager.LoadImageRaw(imageData, width, height, 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> /// <summary>
/// Event that is fired when the plugin should open its configuration interface. /// Event that is fired when the plugin should open its configuration interface.
/// </summary> /// </summary>

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
@ -58,6 +59,28 @@ namespace Dalamud
#endregion #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")] [DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)] [return: MarshalAs(UnmanagedType.Bool)]
public static extern bool FlashWindowEx(ref FLASHWINFO pwfi); 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 try
{ {
var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory, definition.InternalName, definition.AssemblyVersion)); var outputDir = new DirectoryInfo(Path.Combine(this.pluginDirectory, definition.InternalName, definition.AssemblyVersion));
var dllFile = new FileInfo(Path.Combine(outputDir.FullName, $"{definition.InternalName}.dll")); var dllFile = new FileInfo(Path.Combine(outputDir.FullName, $"{definition.InternalName}.dll"));
var disabledFile = new FileInfo(Path.Combine(outputDir.FullName, ".disabled")); var disabledFile = new FileInfo(Path.Combine(outputDir.FullName, ".disabled"));
var wasDisabled = disabledFile.Exists;
if (dllFile.Exists) if (dllFile.Exists && enableAfterInstall)
{ {
if (disabledFile.Exists) if (disabledFile.Exists)
disabledFile.Delete(); disabledFile.Delete();
@ -73,9 +74,17 @@ namespace Dalamud.Plugin
return this.manager.LoadPluginFromAssembly(dllFile, false); return this.manager.LoadPluginFromAssembly(dllFile, false);
} }
if (dllFile.Exists && !enableAfterInstall) {
return true;
}
try {
if (outputDir.Exists) if (outputDir.Exists)
outputDir.Delete(true); outputDir.Delete(true);
outputDir.Create(); outputDir.Create();
} catch {
// ignored, since the plugin may be loaded already
}
var path = Path.GetTempFileName(); var path = Path.GetTempFileName();
Log.Information("Downloading plugin to {0}", path); Log.Information("Downloading plugin to {0}", path);
@ -86,6 +95,11 @@ namespace Dalamud.Plugin
ZipFile.ExtractToDirectory(path, outputDir.FullName); ZipFile.ExtractToDirectory(path, outputDir.FullName);
if (wasDisabled || !enableAfterInstall) {
disabledFile.Create();
return true;
}
return this.manager.LoadPluginFromAssembly(dllFile, false); return this.manager.LoadPluginFromAssembly(dllFile, false);
} }
catch (Exception e) catch (Exception e)
@ -145,6 +159,12 @@ namespace Dalamud.Plugin
if (!dryRun) 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 to disable plugin if it is loaded
try try
{ {
@ -153,7 +173,7 @@ namespace Dalamud.Plugin
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "Plugin disable failed"); Log.Error(ex, "Plugin disable failed");
hasError = true; //hasError = true;
} }
try { try {
@ -165,10 +185,10 @@ namespace Dalamud.Plugin
disabledFile.Create(); disabledFile.Create();
} }
} catch (Exception ex) { } 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) if (installSuccess)
{ {

View file

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

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