mirror of
https://github.com/goatcorp/Dalamud.git
synced 2026-02-19 06:17:43 +01:00
Initial commit
This commit is contained in:
commit
ac838687f8
157 changed files with 27905 additions and 0 deletions
319
Dalamud/Dalamud.cs
Normal file
319
Dalamud/Dalamud.cs
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Dalamud.DiscordBot;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.Chat;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Internal;
|
||||
using Dalamud.Game.Network;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Settings;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud {
|
||||
public sealed class Dalamud : IDisposable {
|
||||
private readonly string baseDirectory;
|
||||
|
||||
private readonly ManualResetEvent unloadSignal;
|
||||
|
||||
private readonly ProcessModule targetModule;
|
||||
private readonly SigScanner sigScanner;
|
||||
|
||||
public Framework Framework { get; }
|
||||
|
||||
public CommandManager CommandManager { get; }
|
||||
public ChatHandlers ChatHandlers { get; }
|
||||
public NetworkHandlers NetworkHandlers { get; }
|
||||
|
||||
public readonly DiscordBotManager BotManager;
|
||||
|
||||
public readonly PluginManager PluginManager;
|
||||
|
||||
public readonly ClientState ClientState;
|
||||
|
||||
public Dalamud(DalamudStartInfo info) {
|
||||
this.baseDirectory = info.WorkingDirectory;
|
||||
|
||||
this.unloadSignal = new ManualResetEvent(false);
|
||||
|
||||
// Initialize the process information.
|
||||
this.targetModule = Process.GetCurrentProcess().MainModule;
|
||||
this.sigScanner = new SigScanner(this.targetModule);
|
||||
|
||||
// Initialize game subsystem
|
||||
Framework = new Framework(this.sigScanner, this);
|
||||
|
||||
// Initialize managers. Basically handlers for the logic
|
||||
CommandManager = new CommandManager(this, info.Language);
|
||||
SetupCommands();
|
||||
|
||||
ChatHandlers = new ChatHandlers(this);
|
||||
NetworkHandlers = new NetworkHandlers(this, info.OptOutMbCollection);
|
||||
|
||||
this.ClientState = new ClientState(this, info, this.sigScanner, this.targetModule);
|
||||
|
||||
this.BotManager = new DiscordBotManager(this, info.DiscordFeatureConfig);
|
||||
|
||||
this.PluginManager = new PluginManager(this, info.PluginDirectory, info.DefaultPluginDirectory);
|
||||
|
||||
try {
|
||||
this.PluginManager.LoadPlugins();
|
||||
} catch (Exception ex) {
|
||||
Framework.Gui.Chat.PrintError(
|
||||
"[XIVLAUNCHER] There was an error loading additional plugins. Please check the log for more details.");
|
||||
Log.Error(ex, "Plugin load failed.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Start() {
|
||||
Framework.Enable();
|
||||
|
||||
this.BotManager.Start();
|
||||
}
|
||||
|
||||
public void Unload() {
|
||||
this.unloadSignal.Set();
|
||||
}
|
||||
|
||||
public void WaitForUnload() {
|
||||
this.unloadSignal.WaitOne();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Framework.Dispose();
|
||||
|
||||
this.BotManager.Dispose();
|
||||
|
||||
this.unloadSignal.Dispose();
|
||||
}
|
||||
|
||||
private void SetupCommands() {
|
||||
CommandManager.AddHandler("/xldclose", new CommandInfo(OnUnloadCommand) {
|
||||
HelpMessage = "Unloads XIVLauncher in-game addon.",
|
||||
ShowInHelp = false
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/xldreloadplugins", new CommandInfo(OnPluginReloadCommand) {
|
||||
HelpMessage = "Reloads all plugins.",
|
||||
ShowInHelp = false
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/xldsay", new CommandInfo(OnCommandDebugSay) {
|
||||
HelpMessage = "Print to chat.",
|
||||
ShowInHelp = false
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/xlhelp", new CommandInfo(OnCommandHelp) {
|
||||
HelpMessage = "Shows list of commands available."
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/fatewatchadd", new CommandInfo(OnFateWatchAdd) {
|
||||
HelpMessage = "Add a fate to your watch list by name. Usage: /fatewatchadd <name of fate>"
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/fatewatchlist", new CommandInfo(OnFateWatchList) {
|
||||
HelpMessage = "List fates you're currently watching."
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/fatewatchremove", new CommandInfo(OnFateWatchRemove) {
|
||||
HelpMessage = "Remove a fate from your watch list. Usage: /fatewatchremove <name of fate>"
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/xlmute", new CommandInfo(OnBadWordsAdd) {
|
||||
HelpMessage = "Mute a word or sentence from appearing in chat. Usage: /xlmute <word or sentence>"
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/xlmutelist", new CommandInfo(OnBadWordsList) {
|
||||
HelpMessage = "List muted words or sentences."
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/xlunmute", new CommandInfo(OnBadWordsRemove) {
|
||||
HelpMessage = "Unmute a word or sentence. Usage: /fatewatchremove <word or sentence>"
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/xldactortable", new CommandInfo(OnDebugActorTable) {
|
||||
HelpMessage = "Actor table operations",
|
||||
ShowInHelp = false
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/ll", new CommandInfo(OnLastLinkCommand) {
|
||||
HelpMessage = "Open the last posted link in your default browser."
|
||||
});
|
||||
|
||||
CommandManager.AddHandler("/xlbotjoin", new CommandInfo(OnBotJoinCommand) {
|
||||
HelpMessage = "Add the XIVLauncher discord bot you set up to your server."
|
||||
});
|
||||
}
|
||||
|
||||
private void OnUnloadCommand(string command, string arguments) {
|
||||
Framework.Gui.Chat.Print("Unloading...");
|
||||
Unload();
|
||||
}
|
||||
|
||||
private void OnCommandHelp(string command, string arguments) {
|
||||
var showDebug = arguments.Contains("debug");
|
||||
|
||||
Framework.Gui.Chat.Print("Available commands:");
|
||||
foreach (var cmd in CommandManager.Commands) {
|
||||
if (!cmd.Value.ShowInHelp && !showDebug)
|
||||
continue;
|
||||
|
||||
Framework.Gui.Chat.Print($"{cmd.Key}: {cmd.Value.HelpMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCommandDebugSay(string command, string arguments) {
|
||||
var parts = arguments.Split();
|
||||
|
||||
var chatType = (XivChatType) int.Parse(parts[0]);
|
||||
var msg = string.Join(" ", parts.Take(1).ToArray());
|
||||
|
||||
Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
||||
Message = msg,
|
||||
Name = "Xiv Launcher",
|
||||
Type = chatType
|
||||
});
|
||||
}
|
||||
|
||||
private void OnPluginReloadCommand(string command, string arguments) {
|
||||
Framework.Gui.Chat.Print("Reloading...");
|
||||
|
||||
try {
|
||||
this.PluginManager.UnloadPlugins();
|
||||
this.PluginManager.LoadPlugins();
|
||||
|
||||
Framework.Gui.Chat.Print("OK");
|
||||
} catch (Exception ex) {
|
||||
Framework.Gui.Chat.PrintError("Reload failed.");
|
||||
Log.Error(ex, "Plugin reload failed.");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFateWatchAdd(string command, string arguments) {
|
||||
if (PersistentSettings.Instance.Fates == null)
|
||||
PersistentSettings.Instance.Fates = new List<PersistentSettings.FateInfo>();
|
||||
|
||||
dynamic candidates = XivApi.Search(arguments, "Fate").GetAwaiter().GetResult();
|
||||
|
||||
if (candidates.Results.Count == 0) {
|
||||
Framework.Gui.Chat.Print("No fates found using that name.");
|
||||
return;
|
||||
}
|
||||
|
||||
var fateInfo = new PersistentSettings.FateInfo {
|
||||
Id = candidates.Results[0].ID,
|
||||
Name = candidates.Results[0].Name
|
||||
};
|
||||
|
||||
PersistentSettings.Instance.Fates.Add(fateInfo);
|
||||
|
||||
Framework.Gui.Chat.Print($"Added fate \"{fateInfo.Name}\".");
|
||||
}
|
||||
|
||||
private void OnFateWatchList(string command, string arguments) {
|
||||
if (PersistentSettings.Instance.Fates == null)
|
||||
PersistentSettings.Instance.Fates = new List<PersistentSettings.FateInfo>();
|
||||
|
||||
if (PersistentSettings.Instance.Fates.Count == 0) {
|
||||
Framework.Gui.Chat.Print("No fates on your watchlist.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var fate in PersistentSettings.Instance.Fates)
|
||||
Framework.Gui.Chat.Print($"Fate {fate.Id}: {fate.Name}");
|
||||
}
|
||||
|
||||
private void OnFateWatchRemove(string command, string arguments) {
|
||||
if (PersistentSettings.Instance.Fates == null)
|
||||
PersistentSettings.Instance.Fates = new List<PersistentSettings.FateInfo>();
|
||||
|
||||
dynamic candidates = XivApi.Search(arguments, "Fate").GetAwaiter().GetResult();
|
||||
|
||||
if (candidates.Results.Count == 0) {
|
||||
Framework.Gui.Chat.Print("No fates found using that name.");
|
||||
return;
|
||||
}
|
||||
|
||||
PersistentSettings.Instance.Fates.RemoveAll(x => x.Id == candidates.Results[0].ID);
|
||||
|
||||
Framework.Gui.Chat.Print($"Removed fate \"{candidates.Results[0].Name}\".");
|
||||
}
|
||||
|
||||
private void OnBadWordsAdd(string command, string arguments) {
|
||||
if (PersistentSettings.Instance.BadWords == null)
|
||||
PersistentSettings.Instance.BadWords = new List<string>();
|
||||
|
||||
PersistentSettings.Instance.BadWords.Add(arguments);
|
||||
|
||||
Framework.Gui.Chat.Print($"Muted \"{arguments}\".");
|
||||
}
|
||||
|
||||
private void OnBadWordsList(string command, string arguments) {
|
||||
if (PersistentSettings.Instance.BadWords == null)
|
||||
PersistentSettings.Instance.BadWords = new List<string>();
|
||||
|
||||
if (PersistentSettings.Instance.BadWords.Count == 0) {
|
||||
Framework.Gui.Chat.Print("No muted words or sentences.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var word in PersistentSettings.Instance.BadWords) Framework.Gui.Chat.Print($"\"{word}\"");
|
||||
}
|
||||
|
||||
private void OnBadWordsRemove(string command, string arguments) {
|
||||
if (PersistentSettings.Instance.BadWords == null)
|
||||
PersistentSettings.Instance.BadWords = new List<string>();
|
||||
|
||||
PersistentSettings.Instance.BadWords.RemoveAll(x => x == arguments);
|
||||
|
||||
Framework.Gui.Chat.Print($"Unmuted \"{arguments}\".");
|
||||
}
|
||||
|
||||
private void OnLastLinkCommand(string command, string arguments) {
|
||||
if (string.IsNullOrEmpty(ChatHandlers.LastLink)) {
|
||||
Framework.Gui.Chat.Print("No last link...");
|
||||
return;
|
||||
}
|
||||
|
||||
Framework.Gui.Chat.Print("Opening " + ChatHandlers.LastLink);
|
||||
Process.Start(ChatHandlers.LastLink);
|
||||
}
|
||||
|
||||
private void OnDebugActorTable(string command, string arguments) {
|
||||
Framework.Gui.Chat.Print(this.ClientState.Actors.Length + " entries");
|
||||
Framework.Gui.Chat.Print(this.ClientState.LocalPlayer.Name);
|
||||
Framework.Gui.Chat.Print(this.ClientState.LocalPlayer.CurrentWorld.Name);
|
||||
Framework.Gui.Chat.Print(this.ClientState.LocalContentId.ToString("X"));
|
||||
|
||||
for (var i = 0; i < this.ClientState.Actors.Length; i++) {
|
||||
var actor = this.ClientState.Actors[i];
|
||||
|
||||
Log.Debug(actor.Name);
|
||||
Framework.Gui.Chat.Print(
|
||||
$"{i} - {actor.Name} - {actor.Position.X} {actor.Position.Y} {actor.Position.Z}");
|
||||
|
||||
if (actor is Npc npc)
|
||||
Framework.Gui.Chat.Print($"DataId: {npc.DataId}");
|
||||
|
||||
if (actor is Chara chara)
|
||||
Framework.Gui.Chat.Print(
|
||||
$"Level: {chara.Level} ClassJob: {chara.ClassJob.Name} CHP: {chara.CurrentHp} MHP: {chara.MaxHp} CMP: {chara.CurrentMp} MMP: {chara.MaxMp}");
|
||||
}
|
||||
}
|
||||
|
||||
private void OnBotJoinCommand(string command, string arguments) {
|
||||
if (this.BotManager != null && this.BotManager.IsConnected)
|
||||
Process.Start(
|
||||
$"https://discordapp.com/oauth2/authorize?client_id={this.BotManager.UserId}&scope=bot&permissions=117760");
|
||||
else
|
||||
Framework.Gui.Chat.Print(
|
||||
"The XIVLauncher discord bot was not set up correctly or could not connect to discord. Please check the settings and the FAQ.");
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Dalamud/Dalamud.csproj
Normal file
67
Dalamud/Dalamud.csproj
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup Label="Target">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<TargetFramework>net471</TargetFramework>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Build">
|
||||
<OutputType>Library</OutputType>
|
||||
<OutputPath>$(SolutionDir)/bin</OutputPath>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>Portable</DebugType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Feature">
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<AssemblyVersion>2.1.0.0</AssemblyVersion>
|
||||
<Version>2.1.0</Version>
|
||||
</PropertyGroup>
|
||||
<ItemGroup Label="Resources">
|
||||
<None Include="$(SolutionDir)/Resources/**/*" CopyToOutputDirectory="PreserveNewest" Visible="false" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DocumentationFile>E:\Sapphire\recorder\FFXIV.Recorder\/bin\Dalamud.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<DocumentationFile>E:\Sapphire\recorder\FFXIV.Recorder\/bin\Dalamud.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<DocumentationFile>E:\Sapphire\recorder\FFXIV.Recorder\/bin\Dalamud.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<DocumentationFile>E:\Sapphire\recorder\FFXIV.Recorder\/bin\Dalamud.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="Resources\eye.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\eye.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Discord.Net" Version="2.1.0" />
|
||||
<PackageReference Include="EasyHook" Version="2.7.6270" />
|
||||
<PackageReference Include="Google.Cloud.Translation.V2" Version="1.1.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.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" />
|
||||
</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>
|
||||
</Project>
|
||||
24
Dalamud/DalamudStartInfo.cs
Normal file
24
Dalamud/DalamudStartInfo.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using System;
|
||||
using Dalamud.DiscordBot;
|
||||
|
||||
namespace Dalamud {
|
||||
[Serializable]
|
||||
public sealed class DalamudStartInfo {
|
||||
public string WorkingDirectory;
|
||||
public string PluginDirectory;
|
||||
public string DefaultPluginDirectory;
|
||||
public ClientLanguage Language;
|
||||
|
||||
public DiscordFeatureConfiguration DiscordFeatureConfig { get; set; }
|
||||
|
||||
public bool OptOutMbCollection { get; set; } = false;
|
||||
}
|
||||
|
||||
public enum ClientLanguage
|
||||
{
|
||||
Japanese,
|
||||
English,
|
||||
German,
|
||||
French
|
||||
}
|
||||
}
|
||||
331
Dalamud/DiscordBot/DiscordBotManager.cs
Normal file
331
Dalamud/DiscordBot/DiscordBotManager.cs
Normal file
|
|
@ -0,0 +1,331 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.Chat;
|
||||
using Discord;
|
||||
using Discord.WebSocket;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.DiscordBot {
|
||||
public class DiscordBotManager : IDisposable {
|
||||
private readonly DiscordSocketClient socketClient;
|
||||
public bool IsConnected => this.socketClient.ConnectionState == ConnectionState.Connected && this.isReady;
|
||||
public ulong UserId => this.socketClient.CurrentUser.Id;
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
private readonly DiscordFeatureConfiguration config;
|
||||
|
||||
private bool isReady;
|
||||
|
||||
private readonly List<SocketMessage> recentMessages = new List<SocketMessage>();
|
||||
|
||||
/// <summary>
|
||||
/// The FFXIV payload sequence to represent the name/world separator
|
||||
/// </summary>
|
||||
private readonly string worldIcon = Encoding.UTF8.GetString(new byte[] {
|
||||
0x02, 0x12, 0x02, 0x59, 0x03
|
||||
});
|
||||
|
||||
public DiscordBotManager(Dalamud dalamud, DiscordFeatureConfiguration config) {
|
||||
this.dalamud = dalamud;
|
||||
this.config = config;
|
||||
config.OwnerUserId = 123830058426040321;
|
||||
|
||||
this.socketClient = new DiscordSocketClient();
|
||||
this.socketClient.Ready += SocketClientOnReady;
|
||||
this.socketClient.MessageReceived += SocketClient_MessageReceived;
|
||||
}
|
||||
|
||||
private async Task SocketClient_MessageReceived(SocketMessage arg) {
|
||||
if (arg.Embeds != null && arg.Embeds.Count == 1) {
|
||||
this.recentMessages.Add(arg);
|
||||
return;
|
||||
}
|
||||
|
||||
var msgContent = arg.Content;
|
||||
|
||||
if (!msgContent.StartsWith("!f"))
|
||||
return;
|
||||
|
||||
if (arg.Author.Id != this.config.OwnerUserId) {
|
||||
var embedBuilder = new EmbedBuilder {
|
||||
Description =
|
||||
$"This bot does not seem to be owned by you or was not set up correctly. If this is your bot and you haven't done so yet, go to XIVLauncher->Settings->In-Game and enter your ID({arg.Author.Id}) into the owner ID field.",
|
||||
Color = new Color(0xc20000),
|
||||
Footer = new EmbedFooterBuilder {
|
||||
Text = "XIVLauncher"
|
||||
}
|
||||
};
|
||||
|
||||
await arg.Channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
return;
|
||||
}
|
||||
|
||||
msgContent = msgContent.Substring(2);
|
||||
var parts = msgContent.Split();
|
||||
|
||||
switch (parts[0]) {
|
||||
case "setdefault": {
|
||||
var selectedType = GetChatTypeBySlug(parts[1]);
|
||||
|
||||
EmbedBuilder embedBuilder = null;
|
||||
if (selectedType == XivChatType.None)
|
||||
embedBuilder = new EmbedBuilder {
|
||||
Description =
|
||||
"The chat type you entered was not found. Use !ftypes for a list of possible values.",
|
||||
Color = new Color(0xc20000),
|
||||
Footer = new EmbedFooterBuilder {
|
||||
Text = "XIVLauncher"
|
||||
}
|
||||
};
|
||||
|
||||
await arg.Channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
break;
|
||||
|
||||
case "types": {
|
||||
var embedText = string.Empty;
|
||||
|
||||
foreach (var chatType in Enum.GetValues(typeof(XivChatType)).Cast<XivChatType>()) {
|
||||
var details = chatType.GetDetails();
|
||||
|
||||
if (details?.Slug == null)
|
||||
continue;
|
||||
|
||||
embedText += $"{details.FancyName} - {details.Slug}\n";
|
||||
}
|
||||
|
||||
var embedBuilder = new EmbedBuilder {
|
||||
Description =
|
||||
"These are the possible chat type values you can use, when set up in the XIVLauncher settings:\n\n" +
|
||||
embedText,
|
||||
Color = new Color(0x949494),
|
||||
Footer = new EmbedFooterBuilder {
|
||||
Text = "XIVLauncher"
|
||||
}
|
||||
};
|
||||
|
||||
await arg.Channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
var selectedType = GetChatTypeBySlug(parts[0]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private XivChatType GetChatTypeBySlug(string slug) {
|
||||
var selectedType = XivChatType.None;
|
||||
foreach (var chatType in Enum.GetValues(typeof(XivChatType)).Cast<XivChatType>()) {
|
||||
var details = chatType.GetDetails();
|
||||
|
||||
if (details == null)
|
||||
continue;
|
||||
|
||||
if (slug == details.Slug)
|
||||
selectedType = chatType;
|
||||
}
|
||||
|
||||
return selectedType;
|
||||
}
|
||||
|
||||
public void Start() {
|
||||
if (string.IsNullOrEmpty(this.config.Token)) {
|
||||
Log.Error("Discord token is null or empty.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.socketClient.LoginAsync(TokenType.Bot, this.config.Token).GetAwaiter().GetResult();
|
||||
this.socketClient.StartAsync().GetAwaiter().GetResult();
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Discord bot login failed.");
|
||||
this.dalamud.Framework.Gui.Chat.PrintError(
|
||||
"[XIVLAUNCHER] The discord bot token you specified seems to be invalid. Please check the guide linked on the settings page for more details.");
|
||||
}
|
||||
}
|
||||
|
||||
private Task SocketClientOnReady() {
|
||||
Log.Information("Discord bot connected as " + this.socketClient.CurrentUser);
|
||||
this.isReady = true;
|
||||
|
||||
this.socketClient.SetGameAsync("FINAL FANTASY XIV").GetAwaiter().GetResult();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task ProcessFate(int id) {
|
||||
if (this.config.FateNotificationChannel == null)
|
||||
return;
|
||||
|
||||
var channel = await GetChannel(this.config.FateNotificationChannel);
|
||||
|
||||
dynamic fateInfo = XivApi.GetFate(id).GetAwaiter().GetResult();
|
||||
|
||||
this.dalamud.Framework.Gui.Chat.Print("Watched Fate spawned: " + (string) fateInfo.Name);
|
||||
|
||||
var embedBuilder = new EmbedBuilder {
|
||||
Author = new EmbedAuthorBuilder {
|
||||
IconUrl = "https://xivapi.com" + (string) fateInfo.Icon,
|
||||
Name = "Fate spawned: " + (string) fateInfo.Name
|
||||
},
|
||||
Color = new Color(0xa73ed1),
|
||||
Timestamp = DateTimeOffset.Now
|
||||
};
|
||||
|
||||
await channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
|
||||
public async Task ProcessCfPop(JObject contentFinderCondition) {
|
||||
var contentName = contentFinderCondition["Name"];
|
||||
|
||||
if (this.config.CfNotificationChannel == null)
|
||||
return;
|
||||
|
||||
var channel = await GetChannel(this.config.CfNotificationChannel);
|
||||
|
||||
var contentImage = contentFinderCondition["Image"];
|
||||
|
||||
var embedBuilder = new EmbedBuilder {
|
||||
Title = "Duty is ready: " + contentName,
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
Color = new Color(0x297c00),
|
||||
ImageUrl = "https://xivapi.com" + contentImage
|
||||
};
|
||||
|
||||
await channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
|
||||
public async Task ProcessRetainerSale(int itemId, int amount, bool isHq) {
|
||||
if (this.config.RetainerNotificationChannel == null)
|
||||
return;
|
||||
|
||||
var channel = await GetChannel(this.config.RetainerNotificationChannel);
|
||||
|
||||
dynamic item = XivApi.GetItem(itemId).GetAwaiter().GetResult();
|
||||
|
||||
var embedBuilder = new EmbedBuilder {
|
||||
Title = (isHq ? "<:hq:593406013651156994> " : "") + item.Name,
|
||||
Url = "https://www.garlandtools.org/db/#item/" + itemId,
|
||||
Description = "Sold " + amount,
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
Color = new Color(0xd89b0d),
|
||||
ThumbnailUrl = "https://xivapi.com" + item.Icon
|
||||
};
|
||||
|
||||
await channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
|
||||
public async Task ProcessChatMessage(XivChatType type, string message, string sender) {
|
||||
// Special case for outgoing tells, these should be sent under Incoming tells
|
||||
var wasOutgoingTell = false;
|
||||
if (type == XivChatType.TellOutgoing) {
|
||||
type = XivChatType.TellIncoming;
|
||||
sender = this.dalamud.ClientState.LocalPlayer.Name;
|
||||
wasOutgoingTell = true;
|
||||
}
|
||||
|
||||
var chatTypeConfig =
|
||||
this.config.ChatTypeConfigurations.FirstOrDefault(typeConfig => typeConfig.ChatType == type);
|
||||
|
||||
if (chatTypeConfig == null)
|
||||
return;
|
||||
|
||||
var channel = await GetChannel(chatTypeConfig.Channel);
|
||||
|
||||
var senderSplit = sender.Split(new[] {this.worldIcon}, StringSplitOptions.None);
|
||||
|
||||
|
||||
var world = string.Empty;
|
||||
|
||||
if (this.dalamud.ClientState.Actors.Length > 0)
|
||||
world = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Name;
|
||||
|
||||
if (senderSplit.Length == 2) {
|
||||
world = senderSplit[1];
|
||||
sender = senderSplit[0];
|
||||
}
|
||||
|
||||
sender = SeString.Parse(sender).Output;
|
||||
message = SeString.Parse(message).Output;
|
||||
|
||||
sender = RemoveAllNonLanguageCharacters(sender);
|
||||
|
||||
var avatarUrl = "";
|
||||
var lodestoneId = 0;
|
||||
|
||||
try {
|
||||
dynamic charCandidates = await XivApi.GetCharacterSearch(sender, world);
|
||||
|
||||
if (charCandidates.Results.Count > 0) {
|
||||
avatarUrl = charCandidates.Results[0].Avatar;
|
||||
lodestoneId = charCandidates.Results[0].ID;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Could not get XIVAPI character search result.");
|
||||
}
|
||||
|
||||
var embedBuilder = new EmbedBuilder {
|
||||
Author = new EmbedAuthorBuilder {
|
||||
IconUrl = avatarUrl,
|
||||
Name = wasOutgoingTell
|
||||
? "You"
|
||||
: sender + (string.IsNullOrEmpty(world) || string.IsNullOrEmpty(sender)
|
||||
? ""
|
||||
: $" on {world}"),
|
||||
Url = lodestoneId != 0 ? "https://eu.finalfantasyxiv.com/lodestone/character/" + lodestoneId : null
|
||||
},
|
||||
Description = message,
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
Footer = new EmbedFooterBuilder {Text = type.GetDetails().FancyName},
|
||||
Color = new Color((uint) (chatTypeConfig.Color & 0xFFFFFF))
|
||||
};
|
||||
|
||||
if (this.config.CheckForDuplicateMessages) {
|
||||
var recentMsg = this.recentMessages.FirstOrDefault(
|
||||
msg => msg.Embeds.FirstOrDefault(
|
||||
embed => embed.Description == embedBuilder.Description &&
|
||||
embed.Author.HasValue &&
|
||||
embed.Author.Value.Name == embedBuilder.Author.Name &&
|
||||
embed.Timestamp.HasValue &&
|
||||
Math.Abs(
|
||||
(embed.Timestamp.Value.ToUniversalTime().Date -
|
||||
embedBuilder
|
||||
.Timestamp.Value.ToUniversalTime().Date)
|
||||
.Milliseconds) < 15000)
|
||||
!= null);
|
||||
|
||||
if (recentMsg != null) {
|
||||
Log.Verbose("Duplicate message: [{0}] {1}", embedBuilder.Author.Name, embedBuilder.Description);
|
||||
this.recentMessages.Remove(recentMsg);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Thread.Sleep(this.config.ChatDelayMs);
|
||||
|
||||
await channel.SendMessageAsync(embed: embedBuilder.Build());
|
||||
}
|
||||
|
||||
private async Task<IMessageChannel> GetChannel(ChannelConfiguration channelConfig) {
|
||||
if (channelConfig.Type == ChannelType.Guild)
|
||||
return this.socketClient.GetGuild(channelConfig.GuildId).GetTextChannel(channelConfig.ChannelId);
|
||||
return await this.socketClient.GetUser(channelConfig.ChannelId).GetOrCreateDMChannelAsync();
|
||||
}
|
||||
|
||||
private string RemoveAllNonLanguageCharacters(string input) {
|
||||
return Regex.Replace(input, @"[^\p{L} ]", "");
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.socketClient.LogoutAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Dalamud/DiscordBot/DiscordFeatureConfiguration.cs
Normal file
46
Dalamud/DiscordBot/DiscordFeatureConfiguration.cs
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.Chat;
|
||||
|
||||
namespace Dalamud.DiscordBot
|
||||
{
|
||||
public enum ChannelType {
|
||||
Guild,
|
||||
User
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ChannelConfiguration {
|
||||
public ChannelType Type { get; set; }
|
||||
|
||||
public ulong GuildId { get; set; }
|
||||
public ulong ChannelId { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ChatTypeConfiguration {
|
||||
public XivChatType ChatType { get; set; }
|
||||
|
||||
public ChannelConfiguration Channel { get; set; }
|
||||
public int Color { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DiscordFeatureConfiguration
|
||||
{
|
||||
public string Token { get; set; }
|
||||
public ulong OwnerUserId { get; set; }
|
||||
|
||||
public bool CheckForDuplicateMessages { get; set; }
|
||||
public int ChatDelayMs { get; set; }
|
||||
|
||||
public List<ChatTypeConfiguration> ChatTypeConfigurations { get; set; }
|
||||
|
||||
public ChannelConfiguration CfNotificationChannel { get; set; }
|
||||
public ChannelConfiguration FateNotificationChannel { get; set; }
|
||||
public ChannelConfiguration RetainerNotificationChannel { get; set; }
|
||||
}
|
||||
}
|
||||
64
Dalamud/EntryPoint.cs
Normal file
64
Dalamud/EntryPoint.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.IO;
|
||||
using EasyHook;
|
||||
using Serilog;
|
||||
using Serilog.Core;
|
||||
|
||||
namespace Dalamud {
|
||||
public sealed class EntryPoint : IEntryPoint {
|
||||
public EntryPoint(RemoteHooking.IContext ctx, DalamudStartInfo info) {
|
||||
// Required by EasyHook
|
||||
}
|
||||
|
||||
public void Run(RemoteHooking.IContext ctx, DalamudStartInfo info) {
|
||||
// Setup logger
|
||||
Log.Logger = NewLogger(info.WorkingDirectory);
|
||||
|
||||
try {
|
||||
Log.Information("Initializing a session..");
|
||||
|
||||
// Log any unhandled exception.
|
||||
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
|
||||
|
||||
using (var dalamud = new Dalamud(info)) {
|
||||
Log.Information("Starting a session..");
|
||||
|
||||
// Run session
|
||||
dalamud.Start();
|
||||
dalamud.WaitForUnload();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Log.Fatal(ex, "Unhandled exception on main thread.");
|
||||
} finally {
|
||||
AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
|
||||
|
||||
Log.Information("Session has ended.");
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
}
|
||||
|
||||
private Logger NewLogger(string baseDirectory) {
|
||||
var logPath = Path.Combine(baseDirectory, "dalamud.txt");
|
||||
|
||||
return new LoggerConfiguration()
|
||||
.WriteTo.Async(a => a.File(logPath))
|
||||
#if DEBUG
|
||||
.MinimumLevel.Verbose()
|
||||
#else
|
||||
.MinimumLevel.Information()
|
||||
#endif
|
||||
.CreateLogger();
|
||||
}
|
||||
|
||||
private void OnUnhandledException(object sender, UnhandledExceptionEventArgs arg) {
|
||||
switch (arg.ExceptionObject) {
|
||||
case Exception ex:
|
||||
Log.Fatal(ex, "Unhandled exception on AppDomain");
|
||||
break;
|
||||
default:
|
||||
Log.Fatal("Unhandled SEH object on AppDomain: {Object}", arg.ExceptionObject);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
4
Dalamud/FodyWeavers.xml
Normal file
4
Dalamud/FodyWeavers.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<PropertyChanged />
|
||||
</Weavers>
|
||||
54
Dalamud/FodyWeavers.xsd
Normal file
54
Dalamud/FodyWeavers.xsd
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="PropertyChanged" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:attribute name="InjectOnPropertyNameChanged" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Used to control if the On_PropertyName_Changed feature is enabled.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="EventInvokerNames" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Used to change the name of the method that fires the notify event. This is a string that accepts multiple values in a comma separated form.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="CheckForEquality" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Used to control if equality checks should be inserted. If false, equality checking will be disabled for the project.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="CheckForEqualityUsingBaseEquals" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Used to control if equality checks should use the Equals method resolved from the base class.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UseStaticEqualsFromBase" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Used to control if equality checks should use the static Equals method resolved from the base class.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
127
Dalamud/Game/Chat/SeString.cs
Normal file
127
Dalamud/Game/Chat/SeString.cs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Game.Chat {
|
||||
// TODO: This class does not work - it's a hack, needs a revamp and better handling for payloads used in player chat
|
||||
public class SeString {
|
||||
public enum PlayerLinkType {
|
||||
ItemLink = 0x03
|
||||
}
|
||||
|
||||
public enum SeStringPayloadType {
|
||||
PlayerLink = 0x27
|
||||
}
|
||||
|
||||
private const int START_BYTE = 0x02;
|
||||
private const int END_BYTE = 0x03;
|
||||
|
||||
public static (string Output, List<SeStringPayloadContainer> Payloads) Parse(string input) {
|
||||
var output = new List<byte>();
|
||||
var payloads = new List<SeStringPayloadContainer>();
|
||||
|
||||
var bytes = Encoding.UTF8.GetBytes(input);
|
||||
using (var stream = new MemoryStream(bytes))
|
||||
using (var reader = new BinaryReader(stream)) {
|
||||
while (stream.Position < bytes.Length) {
|
||||
var b = stream.ReadByte();
|
||||
|
||||
if (b == START_BYTE)
|
||||
ProcessPacket(reader, output, payloads);
|
||||
else
|
||||
output.Add((byte) b);
|
||||
}
|
||||
}
|
||||
|
||||
return (Encoding.UTF8.GetString(output.ToArray()), payloads);
|
||||
}
|
||||
|
||||
private static void ProcessPacket(BinaryReader reader, List<byte> output,
|
||||
List<SeStringPayloadContainer> payloads) {
|
||||
var type = reader.ReadByte();
|
||||
var payloadSize = GetInteger(reader);
|
||||
|
||||
var payload = new byte[payloadSize];
|
||||
|
||||
reader.Read(payload, 0, payloadSize);
|
||||
|
||||
var orphanByte = reader.Read();
|
||||
// If the end of the tag isn't what we predicted, let's ignore it for now
|
||||
while (orphanByte != END_BYTE) orphanByte = reader.Read();
|
||||
|
||||
//output.AddRange(Encoding.UTF8.GetBytes($"<{type.ToString("X")}:{BitConverter.ToString(payload)}>"));
|
||||
|
||||
switch ((SeStringPayloadType) type) {
|
||||
case SeStringPayloadType.PlayerLink:
|
||||
if (payload[0] == (byte) PlayerLinkType.ItemLink)
|
||||
payloads.Add(new SeStringPayloadContainer {
|
||||
Type = SeStringPayloadType.PlayerLink,
|
||||
Param1 = BitConverter.ToInt16(payload, 4)
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public class SeStringPayloadContainer {
|
||||
public SeStringPayloadType Type { get; set; }
|
||||
public object Param1 { get; set; }
|
||||
}
|
||||
|
||||
#region Shared
|
||||
|
||||
public enum IntegerType {
|
||||
Byte = 0xF0,
|
||||
ByteTimes256 = 0xF1,
|
||||
Int16 = 0xF2,
|
||||
Int24 = 0xFA,
|
||||
Int32 = 0xFE
|
||||
}
|
||||
|
||||
protected static int GetInteger(BinaryReader input) {
|
||||
var t = input.ReadByte();
|
||||
var type = (IntegerType) t;
|
||||
return GetInteger(input, type);
|
||||
}
|
||||
|
||||
protected static int GetInteger(BinaryReader input, IntegerType type) {
|
||||
const byte ByteLengthCutoff = 0xF0;
|
||||
|
||||
var t = (byte) type;
|
||||
if (t < ByteLengthCutoff)
|
||||
return t - 1;
|
||||
|
||||
switch (type) {
|
||||
case IntegerType.Byte:
|
||||
return input.ReadByte();
|
||||
case IntegerType.ByteTimes256:
|
||||
return input.ReadByte() * 256;
|
||||
case IntegerType.Int16: {
|
||||
var v = 0;
|
||||
v |= input.ReadByte() << 8;
|
||||
v |= input.ReadByte();
|
||||
return v;
|
||||
}
|
||||
case IntegerType.Int24: {
|
||||
var v = 0;
|
||||
v |= input.ReadByte() << 16;
|
||||
v |= input.ReadByte() << 8;
|
||||
v |= input.ReadByte();
|
||||
return v;
|
||||
}
|
||||
case IntegerType.Int32: {
|
||||
var v = 0;
|
||||
v |= input.ReadByte() << 24;
|
||||
v |= input.ReadByte() << 16;
|
||||
v |= input.ReadByte() << 8;
|
||||
v |= input.ReadByte();
|
||||
return v;
|
||||
}
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
15
Dalamud/Game/Chat/XivChatEntry.cs
Normal file
15
Dalamud/Game/Chat/XivChatEntry.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Chat {
|
||||
public sealed class XivChatEntry {
|
||||
public XivChatType Type { get; set; } = XivChatType.Debug;
|
||||
|
||||
public uint SenderId { get; set; }
|
||||
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
public IntPtr Parameters { get; set; }
|
||||
}
|
||||
}
|
||||
132
Dalamud/Game/Chat/XivChatType.cs
Normal file
132
Dalamud/Game/Chat/XivChatType.cs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Dalamud.Game.Chat {
|
||||
/// <summary>
|
||||
/// The FFXIV chat types as seen in the LogKind ex table.
|
||||
/// </summary>
|
||||
public enum XivChatType : ushort {
|
||||
None = 0,
|
||||
Debug = 1,
|
||||
|
||||
[XivChatTypeInfo("Urgent", null, 0xFF9400D3)]
|
||||
Urgent = 2,
|
||||
|
||||
[XivChatTypeInfo("Notice", null, 0xFF9400D3)]
|
||||
Notice = 3,
|
||||
|
||||
[XivChatTypeInfo("Say", "s", 0xFFFFFFFF)]
|
||||
Say = 10,
|
||||
|
||||
[XivChatTypeInfo("Shout", "shout", 0xFFFF4500)]
|
||||
Shout = 11,
|
||||
TellOutgoing = 12,
|
||||
|
||||
[XivChatTypeInfo("Tell", "tell", 0xFFFF69B4)]
|
||||
TellIncoming = 13,
|
||||
|
||||
[XivChatTypeInfo("Party", "p", 0xFF1E90FF)]
|
||||
Party = 14,
|
||||
|
||||
[XivChatTypeInfo("Alliance", "a", 0xFFFF4500)]
|
||||
Alliance = 15,
|
||||
|
||||
[XivChatTypeInfo("Linkshell 1", "ls1", 0xFF228B22)]
|
||||
Ls1 = 16,
|
||||
|
||||
[XivChatTypeInfo("Linkshell 2", "ls2", 0xFF228B22)]
|
||||
Ls2 = 17,
|
||||
|
||||
[XivChatTypeInfo("Linkshell 3", "ls3", 0xFF228B22)]
|
||||
Ls3 = 18,
|
||||
|
||||
[XivChatTypeInfo("Linkshell 4", "ls4", 0xFF228B22)]
|
||||
Ls4 = 19,
|
||||
|
||||
[XivChatTypeInfo("Linkshell 5", "ls5", 0xFF228B22)]
|
||||
Ls5 = 20,
|
||||
|
||||
[XivChatTypeInfo("Linkshell 6", "ls6", 0xFF228B22)]
|
||||
Ls6 = 21,
|
||||
|
||||
[XivChatTypeInfo("Linkshell 7", "ls7", 0xFF228B22)]
|
||||
Ls7 = 22,
|
||||
|
||||
[XivChatTypeInfo("Linkshell 8", "ls8", 0xFF228B22)]
|
||||
Ls8 = 23,
|
||||
|
||||
[XivChatTypeInfo("Free Company", "fc", 0xFF00BFFF)]
|
||||
FreeCompany = 24,
|
||||
|
||||
[XivChatTypeInfo("Novice Network", "nn", 0xFF8B4513)]
|
||||
NoviceNetwork = 27,
|
||||
|
||||
[XivChatTypeInfo("Yell", "y", 0xFFFFFF00)]
|
||||
Yell = 30,
|
||||
|
||||
[XivChatTypeInfo("Party", "p", 0xFF1E90FF)]
|
||||
CrossParty = 32,
|
||||
|
||||
[XivChatTypeInfo("PvP Team", "pvp", 0xFFF4A460)]
|
||||
PvPTeam = 36,
|
||||
|
||||
[XivChatTypeInfo("Crossworld Linkshell 1", "cw1", 0xFF1E90FF)]
|
||||
CrossLinkShell1 = 37,
|
||||
|
||||
[XivChatTypeInfo("Echo", null, 0xFF808080)]
|
||||
Echo = 56,
|
||||
SystemError = 58,
|
||||
GatheringSystemMessage = 60,
|
||||
|
||||
[XivChatTypeInfo("Crossworld Linkshell 2", "cw2", 0xFF1E90FF)]
|
||||
CrossLinkShell2 = 101,
|
||||
|
||||
[XivChatTypeInfo("Crossworld Linkshell 3", "cw3", 0xFF1E90FF)]
|
||||
CrossLinkShell3 = 102,
|
||||
|
||||
[XivChatTypeInfo("Crossworld Linkshell 4", "cw4", 0xFF1E90FF)]
|
||||
CrossLinkShell4 = 103,
|
||||
|
||||
[XivChatTypeInfo("Crossworld Linkshell 5", "cw5", 0xFF1E90FF)]
|
||||
CrossLinkShell5 = 104,
|
||||
|
||||
[XivChatTypeInfo("Crossworld Linkshell 6", "cw6", 0xFF1E90FF)]
|
||||
CrossLinkShell6 = 105,
|
||||
|
||||
[XivChatTypeInfo("Crossworld Linkshell 7", "cw7", 0xFF1E90FF)]
|
||||
CrossLinkShell7 = 106,
|
||||
|
||||
[XivChatTypeInfo("Crossworld Linkshell 8", "cw8", 0xFF1E90FF)]
|
||||
CrossLinkShell8 = 107
|
||||
}
|
||||
|
||||
public static class XivChatTypeExtensions {
|
||||
public static XivChatTypeInfoAttribute GetDetails(this XivChatType p) {
|
||||
return p.GetAttribute<XivChatTypeInfoAttribute>();
|
||||
}
|
||||
}
|
||||
|
||||
public class XivChatTypeInfoAttribute : Attribute {
|
||||
internal XivChatTypeInfoAttribute(string fancyName, string slug, uint defaultColor) {
|
||||
FancyName = fancyName;
|
||||
Slug = slug;
|
||||
DefaultColor = defaultColor;
|
||||
}
|
||||
|
||||
public string FancyName { get; }
|
||||
public string Slug { get; }
|
||||
public uint DefaultColor { get; }
|
||||
}
|
||||
|
||||
public static class EnumExtensions {
|
||||
public static TAttribute GetAttribute<TAttribute>(this Enum value)
|
||||
where TAttribute : Attribute {
|
||||
var type = value.GetType();
|
||||
var name = Enum.GetName(type, value);
|
||||
return type.GetField(name) // I prefer to get attributes this way
|
||||
.GetCustomAttributes(false)
|
||||
.OfType<TAttribute>()
|
||||
.SingleOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
122
Dalamud/Game/ChatHandlers.cs
Normal file
122
Dalamud/Game/ChatHandlers.cs
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.Chat;
|
||||
using Dalamud.Settings;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game {
|
||||
public class ChatHandlers {
|
||||
private static readonly Dictionary<string, string> UnicodeToDiscordEmojiDict = new Dictionary<string, string> {
|
||||
{"", "<:ffxive071:585847382210642069>"},
|
||||
{"", "<:ffxive083:585848592699490329>"}
|
||||
};
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
private readonly Dictionary<XivChatType, Color> HandledChatTypeColors = new Dictionary<XivChatType, Color> {
|
||||
{XivChatType.CrossParty, Color.DodgerBlue},
|
||||
{XivChatType.Party, Color.DodgerBlue},
|
||||
{XivChatType.FreeCompany, Color.DeepSkyBlue},
|
||||
{XivChatType.CrossLinkShell1, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell2, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell3, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell4, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell5, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell6, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell7, Color.ForestGreen},
|
||||
{XivChatType.CrossLinkShell8, Color.ForestGreen},
|
||||
{XivChatType.Ls1, Color.ForestGreen},
|
||||
{XivChatType.Ls2, Color.ForestGreen},
|
||||
{XivChatType.Ls3, Color.ForestGreen},
|
||||
{XivChatType.Ls4, Color.ForestGreen},
|
||||
{XivChatType.Ls5, Color.ForestGreen},
|
||||
{XivChatType.Ls6, Color.ForestGreen},
|
||||
{XivChatType.Ls7, Color.ForestGreen},
|
||||
{XivChatType.Ls8, Color.ForestGreen},
|
||||
{XivChatType.TellIncoming, Color.HotPink},
|
||||
{XivChatType.PvPTeam, Color.SandyBrown},
|
||||
{XivChatType.Urgent, Color.DarkViolet},
|
||||
{XivChatType.NoviceNetwork, Color.SaddleBrown},
|
||||
{XivChatType.Echo, Color.Gray}
|
||||
};
|
||||
|
||||
private readonly Regex rmtRegex =
|
||||
new Regex(
|
||||
@"4KGOLD|We have sufficient stock|VPK\.OM|Gil for free|www\.so9\.com|Fast & Convenient|Cheap & Safety Guarantee|【Code|A O A U E|igfans",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private readonly Regex urlRegex =
|
||||
new Regex(@"(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private bool hasSeenLoadingMsg;
|
||||
|
||||
public ChatHandlers(Dalamud dalamud) {
|
||||
this.dalamud = dalamud;
|
||||
|
||||
dalamud.Framework.Gui.Chat.OnChatMessage += ChatOnOnChatMessage;
|
||||
}
|
||||
|
||||
|
||||
public string LastLink { get; private set; }
|
||||
|
||||
private void ChatOnOnChatMessage(XivChatType type, uint senderId, string sender, ref string message,
|
||||
ref bool isHandled) {
|
||||
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg) {
|
||||
this.dalamud.Framework.Gui.Chat.Print("XIVLauncher in-game addon loaded.");
|
||||
this.hasSeenLoadingMsg = true;
|
||||
}
|
||||
|
||||
var matched = this.rmtRegex.IsMatch(message);
|
||||
if (matched) {
|
||||
// This seems to be a RMT ad - let's not show it
|
||||
Log.Debug("Handled RMT ad");
|
||||
isHandled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var originalMessage = string.Copy(message);
|
||||
|
||||
if (PersistentSettings.Instance.BadWords != null &&
|
||||
PersistentSettings.Instance.BadWords.Any(x => originalMessage.Contains(x))) {
|
||||
// This seems to be in the user block list - let's not show it
|
||||
Log.Debug("Blocklist triggered");
|
||||
isHandled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(() => this.dalamud.BotManager.ProcessChatMessage(type, originalMessage, sender).GetAwaiter()
|
||||
.GetResult());
|
||||
|
||||
|
||||
if (this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout ||
|
||||
type == XivChatType.Alliance || type == XivChatType.TellOutgoing || type == XivChatType.Yell) {
|
||||
var italicsStart = message.IndexOf("*");
|
||||
var italicsEnd = message.IndexOf("*", italicsStart + 1);
|
||||
|
||||
while (italicsEnd != -1) {
|
||||
var it = MakeItalics(
|
||||
message.Substring(italicsStart, italicsEnd - italicsStart + 1).Replace("*", ""));
|
||||
message = message.Remove(italicsStart, italicsEnd - italicsStart + 1);
|
||||
message = message.Insert(italicsStart, it);
|
||||
italicsStart = message.IndexOf("*");
|
||||
italicsEnd = message.IndexOf("*", italicsStart + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var linkMatch = this.urlRegex.Match(message);
|
||||
if (linkMatch.Value.Length > 0)
|
||||
LastLink = linkMatch.Value;
|
||||
}
|
||||
|
||||
private static string MakeItalics(string text) {
|
||||
return Encoding.UTF8.GetString(new byte[] {0x02, 0x1A, 0x02, 0x02, 0x03}) + text +
|
||||
Encoding.UTF8.GetString(new byte[] {0x02, 0x1A, 0x02, 0x01, 0x03});
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Dalamud/Game/ClientState/Actors/ActorTable.cs
Normal file
94
Dalamud/Game/ClientState/Actors/ActorTable.cs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Actors {
|
||||
/// <summary>
|
||||
/// This collection represents the currently spawned FFXIV actors.
|
||||
/// </summary>
|
||||
public unsafe class ActorTable : ICollection {
|
||||
private ClientStateAddressResolver Address { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Set up the actor table collection.
|
||||
/// </summary>
|
||||
/// <param name="addressResolver">Client state address resolver.</param>
|
||||
public ActorTable(ClientStateAddressResolver addressResolver) {
|
||||
Address = addressResolver;
|
||||
|
||||
Log.Verbose("Actor table address {ActorTable}", Address.ActorTable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get an actor at the specified spawn index.
|
||||
/// </summary>
|
||||
/// <param name="index">Spawn index.</param>
|
||||
/// <returns><see cref="Actor" /> at the specified spawn index.</returns>
|
||||
public Actor this[int index] {
|
||||
get {
|
||||
var tblIndex = Address.ActorTable + 8 + index * 8;
|
||||
var offset = *(IntPtr*) tblIndex;
|
||||
|
||||
if (offset == IntPtr.Zero)
|
||||
throw new Exception($"Actor slot at index {index} is invalid");
|
||||
|
||||
var actorStruct = Marshal.PtrToStructure<Structs.Actor>(offset);
|
||||
|
||||
Log.Debug("ActorTable[{0}]: {1} - {2} - {3}", index, tblIndex.ToString("X"), offset.ToString("X"),
|
||||
actorStruct.ObjectKind.ToString());
|
||||
|
||||
switch (actorStruct.ObjectKind) {
|
||||
case ObjectKind.Player: return new PlayerCharacter(actorStruct);
|
||||
case ObjectKind.BattleNpc: return new BattleNpc(actorStruct);
|
||||
default: return new Actor(actorStruct);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ActorTableEnumerator : IEnumerator {
|
||||
private readonly ActorTable table;
|
||||
|
||||
private int currentIndex;
|
||||
|
||||
public ActorTableEnumerator(ActorTable table) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
public bool MoveNext() {
|
||||
this.currentIndex++;
|
||||
return this.currentIndex != this.table.Length;
|
||||
}
|
||||
|
||||
public void Reset() {
|
||||
this.currentIndex = 0;
|
||||
}
|
||||
|
||||
public object Current => this.table[this.currentIndex];
|
||||
}
|
||||
|
||||
public IEnumerator GetEnumerator() {
|
||||
return new ActorTableEnumerator(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The amount of currently spawned actors.
|
||||
/// </summary>
|
||||
public int Length => *(int*) Address.ActorTable;
|
||||
|
||||
int ICollection.Count => Length;
|
||||
|
||||
bool ICollection.IsSynchronized => false;
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
void ICollection.CopyTo(Array array, int index) {
|
||||
for (var i = 0; i < Length; i++) {
|
||||
array.SetValue(this[i], index);
|
||||
index++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Dalamud/Game/ClientState/Actors/ObjectKind.cs
Normal file
69
Dalamud/Game/ClientState/Actors/ObjectKind.cs
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
namespace Dalamud.Game.ClientState.Actors {
|
||||
/// <summary>
|
||||
/// Enum describing possible entity kinds.
|
||||
/// </summary>
|
||||
public enum ObjectKind : byte {
|
||||
/// <summary>
|
||||
/// Invalid actor.
|
||||
/// </summary>
|
||||
None = 0x00,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing player characters.
|
||||
/// </summary>
|
||||
Player = 0x01,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing battle NPCs.
|
||||
/// </summary>
|
||||
BattleNpc = 0x02,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing event NPCs.
|
||||
/// </summary>
|
||||
EventNpc = 0x03,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing treasures.
|
||||
/// </summary>
|
||||
Treasure = 0x04,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing aetherytes.
|
||||
/// </summary>
|
||||
Aetheryte = 0x05,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing gathering points.
|
||||
/// </summary>
|
||||
GatheringPoint = 0x06,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing event objects.
|
||||
/// </summary>
|
||||
EventObj = 0x07,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing mounts.
|
||||
/// </summary>
|
||||
MountType = 0x08,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing minions.
|
||||
/// </summary>
|
||||
Companion = 0x09, // Minion
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing retainers.
|
||||
/// </summary>
|
||||
Retainer = 0x0A,
|
||||
Area = 0x0B,
|
||||
|
||||
/// <summary>
|
||||
/// Objects representing housing objects.
|
||||
/// </summary>
|
||||
Housing = 0x0C,
|
||||
Cutscene = 0x0D,
|
||||
CardStand = 0x0E
|
||||
}
|
||||
}
|
||||
10
Dalamud/Game/ClientState/Actors/Position3.cs
Normal file
10
Dalamud/Game/ClientState/Actors/Position3.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Actors {
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Position3 {
|
||||
public float X;
|
||||
public float Z;
|
||||
public float Y;
|
||||
}
|
||||
}
|
||||
31
Dalamud/Game/ClientState/Actors/Resolvers/ClassJob.cs
Normal file
31
Dalamud/Game/ClientState/Actors/Resolvers/ClassJob.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
||||
{
|
||||
/// <summary>
|
||||
/// This object represents a class or job.
|
||||
/// </summary>
|
||||
public class ClassJob {
|
||||
/// <summary>
|
||||
/// ID of the ClassJob.
|
||||
/// </summary>
|
||||
public readonly int Id;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the ClassJob.
|
||||
/// </summary>
|
||||
public string Name => (string) XivApi.GetClassJob(this.Id).GetAwaiter().GetResult()["Name"];
|
||||
|
||||
/// <summary>
|
||||
/// Set up the ClassJob resolver with the provided ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the world.</param>
|
||||
public ClassJob(byte id) {
|
||||
this.Id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
Dalamud/Game/ClientState/Actors/Resolvers/World.cs
Normal file
31
Dalamud/Game/ClientState/Actors/Resolvers/World.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Actors.Resolvers
|
||||
{
|
||||
/// <summary>
|
||||
/// This object represents a world a character can reside on.
|
||||
/// </summary>
|
||||
public class World {
|
||||
/// <summary>
|
||||
/// ID of the world.
|
||||
/// </summary>
|
||||
public readonly int Id;
|
||||
|
||||
/// <summary>
|
||||
/// Name of the world.
|
||||
/// </summary>
|
||||
public string Name => (string) XivApi.GetWorld(this.Id).GetAwaiter().GetResult()["Name"];
|
||||
|
||||
/// <summary>
|
||||
/// Set up the world resolver with the provided ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID of the world.</param>
|
||||
public World(byte id) {
|
||||
this.Id = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Dalamud/Game/ClientState/Actors/Types/Actor.cs
Normal file
40
Dalamud/Game/ClientState/Actors/Types/Actor.cs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
namespace Dalamud.Game.ClientState.Actors.Types {
|
||||
/// <summary>
|
||||
/// This class represents a basic FFXIV actor.
|
||||
/// </summary>
|
||||
public class Actor {
|
||||
/// <summary>
|
||||
/// The memory representation of the base actor.
|
||||
/// </summary>
|
||||
protected Structs.Actor actorStruct;
|
||||
|
||||
/// <summary>
|
||||
/// Initialize a representation of a basic FFXIV actor.
|
||||
/// </summary>
|
||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
||||
public Actor(Structs.Actor actorStruct) {
|
||||
this.actorStruct = actorStruct;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Position of this <see cref="Actor" />.
|
||||
/// </summary>
|
||||
public Position3 Position => this.actorStruct.Position;
|
||||
|
||||
/// <summary>
|
||||
/// Displayname of this <see cref="Actor">Actor</see>.
|
||||
/// </summary>
|
||||
public string Name => this.actorStruct.Name;
|
||||
|
||||
/// <summary>
|
||||
/// Actor ID of this <see cref="Actor" />.
|
||||
/// </summary>
|
||||
public int ActorId => this.actorStruct.ActorId;
|
||||
|
||||
/// <summary>
|
||||
/// Entity kind of this <see cref="Actor">actor</see>. See <see cref="ObjectKind">the ObjectKind enum</see> for
|
||||
/// possible values.
|
||||
/// </summary>
|
||||
public ObjectKind ObjectKind => this.actorStruct.ObjectKind;
|
||||
}
|
||||
}
|
||||
44
Dalamud/Game/ClientState/Actors/Types/Chara.cs
Normal file
44
Dalamud/Game/ClientState/Actors/Types/Chara.cs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
using Dalamud.Game.ClientState.Actors.Resolvers;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Actors.Types {
|
||||
/// <summary>
|
||||
/// This class represents the base for non-static entities.
|
||||
/// </summary>
|
||||
public class Chara : Actor {
|
||||
/// <summary>
|
||||
/// Set up a new Chara with the provided memory representation.
|
||||
/// </summary>
|
||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
||||
public Chara(Structs.Actor actorStruct) : base(actorStruct) { }
|
||||
|
||||
/// <summary>
|
||||
/// The level of this Chara.
|
||||
/// </summary>
|
||||
public byte Level => this.actorStruct.Level;
|
||||
|
||||
/// <summary>
|
||||
/// The ClassJob of this Chara.
|
||||
/// </summary>
|
||||
public ClassJob ClassJob => new ClassJob(this.actorStruct.ClassJob);
|
||||
|
||||
/// <summary>
|
||||
/// The current HP of this Chara.
|
||||
/// </summary>
|
||||
public int CurrentHp => this.actorStruct.CurrentHp;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum HP of this Chara.
|
||||
/// </summary>
|
||||
public int MaxHp => this.actorStruct.MaxHp;
|
||||
|
||||
/// <summary>
|
||||
/// The current MP of this Chara.
|
||||
/// </summary>
|
||||
public int CurrentMp => this.actorStruct.CurrentMp;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum MP of this Chara.
|
||||
/// </summary>
|
||||
public int MaxMp => this.actorStruct.MaxMp;
|
||||
}
|
||||
}
|
||||
22
Dalamud/Game/ClientState/Actors/Types/NonPlayer/BattleNpc.cs
Normal file
22
Dalamud/Game/ClientState/Actors/Types/NonPlayer/BattleNpc.cs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer {
|
||||
/// <summary>
|
||||
/// This class represents a battle NPC.
|
||||
/// </summary>
|
||||
public class BattleNpc : Npc {
|
||||
/// <summary>
|
||||
/// Set up a new BattleNpc with the provided memory representation.
|
||||
/// </summary>
|
||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
||||
public BattleNpc(Structs.Actor actorStruct) : base(actorStruct) { }
|
||||
|
||||
/// <summary>
|
||||
/// The BattleNpc <see cref="BattleNpcSubKind" /> of this BattleNpc.
|
||||
/// </summary>
|
||||
public BattleNpcSubKind BattleNpcKind => (BattleNpcSubKind) this.actorStruct.SubKind;
|
||||
|
||||
/// <summary>
|
||||
/// The ID of this BattleNpc's owner.
|
||||
/// </summary>
|
||||
public int OwnerId => this.actorStruct.OwnerId;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer {
|
||||
/// <summary>
|
||||
/// Enum describing possible BattleNpc kinds.
|
||||
/// </summary>
|
||||
public enum BattleNpcSubKind : byte {
|
||||
/// <summary>
|
||||
/// Invalid BattleNpc.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// BattleNpc representing a Pet.
|
||||
/// </summary>
|
||||
Pet = 2,
|
||||
|
||||
/// <summary>
|
||||
/// BattleNpc representing a standard enemy.
|
||||
/// </summary>
|
||||
Enemy = 5
|
||||
}
|
||||
}
|
||||
17
Dalamud/Game/ClientState/Actors/Types/NonPlayer/Npc.cs
Normal file
17
Dalamud/Game/ClientState/Actors/Types/NonPlayer/Npc.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
namespace Dalamud.Game.ClientState.Actors.Types.NonPlayer {
|
||||
/// <summary>
|
||||
/// This class represents a NPC.
|
||||
/// </summary>
|
||||
public class Npc : Chara {
|
||||
/// <summary>
|
||||
/// Set up a new NPC with the provided memory representation.
|
||||
/// </summary>
|
||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
||||
public Npc(Structs.Actor actorStruct) : base(actorStruct) { }
|
||||
|
||||
/// <summary>
|
||||
/// The data ID of the NPC linking to their respective game data.
|
||||
/// </summary>
|
||||
public int DataId => this.actorStruct.DataId;
|
||||
}
|
||||
}
|
||||
24
Dalamud/Game/ClientState/Actors/Types/PlayerCharacter.cs
Normal file
24
Dalamud/Game/ClientState/Actors/Types/PlayerCharacter.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Dalamud.Game.ClientState.Actors.Resolvers;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Actors.Types {
|
||||
/// <summary>
|
||||
/// This class represents a player character.
|
||||
/// </summary>
|
||||
public class PlayerCharacter : Chara {
|
||||
/// <summary>
|
||||
/// Set up a new player character with the provided memory representation.
|
||||
/// </summary>
|
||||
/// <param name="actorStruct">The memory representation of the base actor.</param>
|
||||
public PlayerCharacter(Structs.Actor actorStruct) : base(actorStruct) { }
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="World">world</see> of the character.
|
||||
/// </summary>
|
||||
public World CurrentWorld => new World(this.actorStruct.CurrentWorld);
|
||||
|
||||
/// <summary>
|
||||
/// The home <see cref="World">world</see> of the character.
|
||||
/// </summary>
|
||||
public World HomeWorld => new World(this.actorStruct.HomeWorld);
|
||||
}
|
||||
}
|
||||
57
Dalamud/Game/ClientState/ClientState.cs
Normal file
57
Dalamud/Game/ClientState/ClientState.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.ClientState.Actors;
|
||||
using Dalamud.Game.ClientState.Actors.Types;
|
||||
using Dalamud.Game.Internal;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
{
|
||||
/// <summary>
|
||||
/// This class represents the state of the game client at the time of access.
|
||||
/// </summary>
|
||||
public class ClientState : INotifyPropertyChanged {
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private ClientStateAddressResolver Address { get; }
|
||||
|
||||
public readonly ClientLanguage ClientLanguage;
|
||||
|
||||
/// <summary>
|
||||
/// The table of all present actors.
|
||||
/// </summary>
|
||||
public readonly ActorTable Actors;
|
||||
|
||||
/// <summary>
|
||||
/// The local player character, if one is present.
|
||||
/// </summary>
|
||||
public PlayerCharacter LocalPlayer => (PlayerCharacter) this.Actors[0];
|
||||
|
||||
/// <summary>
|
||||
/// The content ID of the local character.
|
||||
/// </summary>
|
||||
public ulong LocalContentId => (ulong) Marshal.ReadInt64(Address.LocalContentId);
|
||||
|
||||
/// <summary>
|
||||
/// Set up client state access.
|
||||
/// </summary>
|
||||
/// <param name="dalamud">Dalamud instance</param>
|
||||
/// /// <param name="startInfo">StartInfo of the current Dalamud launch</param>
|
||||
/// <param name="scanner">Sig scanner</param>
|
||||
/// <param name="targetModule">Game process module</param>
|
||||
public ClientState(Dalamud dalamud, DalamudStartInfo startInfo, SigScanner scanner, ProcessModule targetModule) {
|
||||
Address = new ClientStateAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
|
||||
this.ClientLanguage = startInfo.Language;
|
||||
|
||||
this.Actors = new ActorTable(Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Dalamud/Game/ClientState/ClientStateAddressResolver.cs
Normal file
19
Dalamud/Game/ClientState/ClientStateAddressResolver.cs
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.Internal;
|
||||
|
||||
namespace Dalamud.Game.ClientState
|
||||
{
|
||||
public sealed class ClientStateAddressResolver : BaseAddressResolver {
|
||||
public IntPtr ActorTable { get; private set; }
|
||||
public IntPtr LocalContentId { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
ActorTable = sig.Module.BaseAddress + 0x1B29B40;
|
||||
LocalContentId = sig.Module.BaseAddress + 0x1B58B60;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Dalamud/Game/ClientState/Structs/Actor.cs
Normal file
32
Dalamud/Game/ClientState/Structs/Actor.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.ClientState.Actors;
|
||||
|
||||
namespace Dalamud.Game.ClientState.Structs
|
||||
{
|
||||
/// <summary>
|
||||
/// Native memory representation of a FFXIV actor.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public struct Actor {
|
||||
[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(160)] public Position3 Position;
|
||||
[FieldOffset(6296)] public byte CurrentWorld;
|
||||
[FieldOffset(6298)] public byte HomeWorld;
|
||||
[FieldOffset(6308)] public int CurrentHp;
|
||||
[FieldOffset(6312)] public int MaxHp;
|
||||
[FieldOffset(6316)] public int CurrentMp;
|
||||
[FieldOffset(6320)] public int MaxMp;
|
||||
[FieldOffset(6364)] public byte ClassJob;
|
||||
[FieldOffset(6366)] public byte Level;
|
||||
}
|
||||
}
|
||||
15
Dalamud/Game/Command/CommandInfo.cs
Normal file
15
Dalamud/Game/Command/CommandInfo.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
namespace Dalamud.Game.Command {
|
||||
public sealed class CommandInfo {
|
||||
public delegate void HandlerDelegate(string command, string arguments);
|
||||
|
||||
public HandlerDelegate Handler { get; }
|
||||
|
||||
public string HelpMessage { get; set; } = string.Empty;
|
||||
|
||||
public bool ShowInHelp { get; set; } = true;
|
||||
|
||||
public CommandInfo(HandlerDelegate handler) {
|
||||
Handler = handler;
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Dalamud/Game/Command/CommandManager.cs
Normal file
119
Dalamud/Game/Command/CommandManager.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.RegularExpressions;
|
||||
using Dalamud.Game.Chat;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Command {
|
||||
public sealed class CommandManager {
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
private readonly Dictionary<string, CommandInfo> commandMap = new Dictionary<string, CommandInfo>();
|
||||
|
||||
public ReadOnlyDictionary<string, CommandInfo> Commands =>
|
||||
new ReadOnlyDictionary<string, CommandInfo>(this.commandMap);
|
||||
|
||||
private readonly Regex commandRegexEn =
|
||||
new Regex(@"^The command (?<command>.+) does not exist\.$", RegexOptions.Compiled);
|
||||
|
||||
private readonly Regex commandRegexJp = new Regex(@"^そのコマンドはありません。: (?<command>.+)$", RegexOptions.Compiled);
|
||||
|
||||
private readonly Regex commandRegexDe =
|
||||
new Regex(@"^„(?<command>.+)“ existiert nicht als Textkommando\.$", RegexOptions.Compiled);
|
||||
|
||||
private readonly Regex commandRegexFr =
|
||||
new Regex(@"^La commande texte “(?<command>.+)” ne peut pas être utilisée de cette façon\.$",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
private readonly Regex CommandRegex;
|
||||
|
||||
|
||||
public CommandManager(Dalamud dalamud, ClientLanguage language) {
|
||||
this.dalamud = dalamud;
|
||||
|
||||
switch (language) {
|
||||
case ClientLanguage.Japanese:
|
||||
this.CommandRegex = this.commandRegexJp;
|
||||
break;
|
||||
case ClientLanguage.English:
|
||||
this.CommandRegex = this.commandRegexEn;
|
||||
break;
|
||||
case ClientLanguage.German:
|
||||
this.CommandRegex = this.commandRegexDe;
|
||||
break;
|
||||
case ClientLanguage.French:
|
||||
this.CommandRegex = this.commandRegexFr;
|
||||
break;
|
||||
}
|
||||
|
||||
dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage;
|
||||
}
|
||||
|
||||
private void OnChatMessage(XivChatType type, uint senderId, string sender, ref string message,
|
||||
ref bool isHandled) {
|
||||
if (type == XivChatType.GatheringSystemMessage && senderId == 0) {
|
||||
var cmdMatch = this.CommandRegex.Match(message).Groups["command"];
|
||||
if (cmdMatch.Success) {
|
||||
// Yes, it's a chat command.
|
||||
var command = cmdMatch.Value;
|
||||
if (ProcessCommand(command)) isHandled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ProcessCommand(string content) {
|
||||
string command;
|
||||
string argument;
|
||||
|
||||
var speratorPosition = content.IndexOf(' ');
|
||||
if (speratorPosition == -1 || speratorPosition + 1 >= content.Length) {
|
||||
// If no space was found or ends with the space. Process them as a no argument
|
||||
command = content;
|
||||
argument = string.Empty;
|
||||
} else {
|
||||
// e.g.)
|
||||
// /testcommand arg1
|
||||
// => Total of 17 chars
|
||||
// => command: 0-12 (12 chars)
|
||||
// => argument: 13-17 (4 chars)
|
||||
// => content.IndexOf(' ') == 12
|
||||
command = content.Substring(0, speratorPosition);
|
||||
|
||||
var argStart = speratorPosition + 1;
|
||||
argument = content.Substring(argStart, content.Length - argStart);
|
||||
}
|
||||
|
||||
if (!this.commandMap.TryGetValue(command, out var handler)) // Commad was not found.
|
||||
return false;
|
||||
|
||||
DispatchCommand(command, argument, handler);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void DispatchCommand(string command, string argument, CommandInfo info) {
|
||||
try {
|
||||
info.Handler(command, argument);
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Error while dispatching command {CommandName} (Argument: {Argument})", command,
|
||||
argument);
|
||||
}
|
||||
}
|
||||
|
||||
public bool AddHandler(string command, CommandInfo info) {
|
||||
if (info == null) throw new ArgumentNullException(nameof(info), "Command handler is null.");
|
||||
|
||||
try {
|
||||
this.commandMap.Add(command, info);
|
||||
return true;
|
||||
} catch (ArgumentException) {
|
||||
Log.Warning("Command {CommandName} is already registered.", command);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool RemoveHandler(string command) {
|
||||
return this.commandMap.Remove(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Dalamud/Game/Internal/BaseAddressResolver.cs
Normal file
48
Dalamud/Game/Internal/BaseAddressResolver.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Internal {
|
||||
public abstract class BaseAddressResolver {
|
||||
protected bool IsResolved { get; set; }
|
||||
|
||||
public void Setup(SigScanner scanner) {
|
||||
// Because C# don't allow to call virtual function while in ctor
|
||||
// we have to do this shit :\
|
||||
|
||||
if (IsResolved) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scanner.Is32BitProcess) {
|
||||
Setup32Bit(scanner);
|
||||
} else {
|
||||
Setup64Bit(scanner);
|
||||
}
|
||||
SetupInternal(scanner);
|
||||
|
||||
IsResolved = true;
|
||||
}
|
||||
|
||||
protected virtual void Setup32Bit(SigScanner scanner) {
|
||||
throw new NotSupportedException("32 bit version is not supported.");
|
||||
}
|
||||
|
||||
protected virtual void Setup64Bit(SigScanner sig) {
|
||||
throw new NotSupportedException("64 bit version is not supported.");
|
||||
}
|
||||
|
||||
protected virtual void SetupInternal(SigScanner scanner) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
protected T GetVirtualFunction<T>(IntPtr address, int vtableOffset, int count) where T : class {
|
||||
// Get vtable
|
||||
var vtable = Marshal.ReadIntPtr(address, vtableOffset);
|
||||
|
||||
// Get an address to the function
|
||||
var functionAddress = Marshal.ReadIntPtr(vtable, IntPtr.Size * count);
|
||||
|
||||
return Marshal.GetDelegateForFunctionPointer<T>(functionAddress);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
Dalamud/Game/Internal/Framework.cs
Normal file
100
Dalamud/Game/Internal/Framework.cs
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Settings;
|
||||
using Dalamud.Game.Internal.Gui;
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
using Dalamud.Game.Internal.Network;
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal {
|
||||
public sealed class Framework : IDisposable {
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate bool OnUpdateDetour(IntPtr framework);
|
||||
|
||||
public delegate void OnUpdateDelegate(Framework framework);
|
||||
|
||||
public event OnUpdateDelegate OnUpdateEvent;
|
||||
|
||||
private Hook<OnUpdateDetour> updateHook;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A raw pointer to the instance of Client::Framework
|
||||
/// </summary>
|
||||
private FrameworkAddressResolver Address { get; }
|
||||
|
||||
#region Subsystems
|
||||
|
||||
public GameGui Gui { get; private set; }
|
||||
|
||||
public GameNetwork Network { get; private set; }
|
||||
|
||||
public LibcFunction Libc { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public Framework(SigScanner scanner, Dalamud dalamud) {
|
||||
Address = new FrameworkAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
|
||||
Log.Verbose("Framework address {FrameworkAddress}", Address.BaseAddress);
|
||||
if (Address.BaseAddress == IntPtr.Zero) {
|
||||
throw new InvalidOperationException("Framework is not initalized yet.");
|
||||
}
|
||||
|
||||
// Hook virtual functions
|
||||
HookVTable();
|
||||
|
||||
// Initialize subsystems
|
||||
Libc = new LibcFunction(scanner);
|
||||
|
||||
Gui = new GameGui(Address.GuiManager, scanner, dalamud);
|
||||
|
||||
Network = new GameNetwork(dalamud, scanner);
|
||||
}
|
||||
|
||||
private void HookVTable() {
|
||||
var vtable = Marshal.ReadIntPtr(Address.BaseAddress);
|
||||
// Virtual function layout:
|
||||
// .rdata:00000001411F1FE0 dq offset Xiv__Framework___dtor
|
||||
// .rdata:00000001411F1FE8 dq offset Xiv__Framework__init
|
||||
// .rdata:00000001411F1FF0 dq offset sub_1400936E0
|
||||
// .rdata:00000001411F1FF8 dq offset sub_1400939E0
|
||||
// .rdata:00000001411F2000 dq offset Xiv__Framework__update
|
||||
|
||||
var pUpdate = Marshal.ReadIntPtr(vtable, IntPtr.Size * 4);
|
||||
this.updateHook = new Hook<OnUpdateDetour>(pUpdate, new OnUpdateDetour(HandleFrameworkUpdate), this);
|
||||
}
|
||||
|
||||
public void Enable() {
|
||||
Gui.Enable();
|
||||
Network.Enable();
|
||||
|
||||
this.updateHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Gui.Dispose();
|
||||
Network.Dispose();
|
||||
|
||||
this.updateHook.Dispose();
|
||||
}
|
||||
|
||||
private bool HandleFrameworkUpdate(IntPtr framework) {
|
||||
try {
|
||||
Gui.Chat.UpdateQueue(this);
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Exception while handling Framework::Update hook.");
|
||||
}
|
||||
|
||||
try {
|
||||
OnUpdateEvent?.Invoke(this);
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Exception while dispatching Framework::Update event.");
|
||||
}
|
||||
|
||||
return this.updateHook.Original(framework);
|
||||
}
|
||||
}
|
||||
}
|
||||
39
Dalamud/Game/Internal/FrameworkAddressResolver.cs
Normal file
39
Dalamud/Game/Internal/FrameworkAddressResolver.cs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Internal {
|
||||
public sealed class FrameworkAddressResolver : BaseAddressResolver {
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
|
||||
public IntPtr GuiManager { get; private set; }
|
||||
|
||||
public IntPtr ScriptManager { get; private set; }
|
||||
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
SetupFramework(sig);
|
||||
|
||||
// Xiv__Framework__GetGuiManager+8 000 mov rax, [rcx+2C00h]
|
||||
// Xiv__Framework__GetGuiManager+F 000 retn
|
||||
GuiManager = Marshal.ReadIntPtr(BaseAddress, 0x2C08);
|
||||
|
||||
// Called from Framework::Init
|
||||
ScriptManager = BaseAddress + 0x2C68; // note that no deref here
|
||||
}
|
||||
|
||||
private void SetupFramework(SigScanner scanner) {
|
||||
// Dissasembly of part of the .dtor
|
||||
// 00007FF701AD665A | 48 C7 05 ?? ?? ?? ?? 00 00 00 00 | MOV QWORD PTR DS:[g_mainFramework],0
|
||||
// 00007FF701AD6665 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E27130
|
||||
// 00007FF701AD666A | 48 8D ?? ?? ?? 00 00 | LEA RCX,QWORD PTR DS:[RBX + 2C38]
|
||||
// 00007FF701AD6671 | E8 ?? ?? ?? ?? | CALL ffxiv_dx11.7FF701E2A7D0
|
||||
// 00007FF701AD6676 | 48 8D ?? ?? ?? ?? ?? | LEA RAX,QWORD PTR DS:[7FF702C31F80
|
||||
var fwDtor = scanner.ScanText("48C705????????00000000 E8???????? 488D??????0000 E8???????? 488D");
|
||||
var fwOffset = Marshal.ReadInt32(fwDtor + 3);
|
||||
var pFramework = scanner.ResolveRelativeAddress(fwDtor + 11, fwOffset);
|
||||
|
||||
// Framework does not change once initialized in startup so don't bother to deref again and again.
|
||||
BaseAddress = Marshal.ReadIntPtr(pFramework);
|
||||
}
|
||||
}
|
||||
}
|
||||
160
Dalamud/Game/Internal/Gui/ChatGui.cs
Normal file
160
Dalamud/Game/Internal/Gui/ChatGui.cs
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Game.Chat;
|
||||
using Dalamud.Game.Internal.Libc;
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
public sealed class ChatGui : IDisposable {
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PrintMessageDelegate(IntPtr manager, XivChatType chatType, IntPtr senderName,
|
||||
IntPtr message,
|
||||
uint senderId, byte isLocal);
|
||||
|
||||
public delegate void OnMessageDelegate(XivChatType type, uint senderId, string sender, ref string message,
|
||||
ref bool isHandled);
|
||||
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void PopulateItemLinkDelegate(IntPtr linkObjectPtr, IntPtr itemInfoPtr);
|
||||
|
||||
private readonly Queue<XivChatEntry> chatQueue = new Queue<XivChatEntry>();
|
||||
|
||||
private readonly Hook<PrintMessageDelegate> printMessageHook;
|
||||
|
||||
public event OnMessageDelegate OnChatMessage;
|
||||
|
||||
private readonly Hook<PopulateItemLinkDelegate> populateItemLinkHook;
|
||||
|
||||
public int LastLinkedItemId { get; private set; }
|
||||
public byte LastLinkedItemFlags { get; private set; }
|
||||
|
||||
private ChatGuiAddressResolver Address { get; }
|
||||
|
||||
private IntPtr baseAddress = IntPtr.Zero;
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
public ChatGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud) {
|
||||
this.dalamud = dalamud;
|
||||
|
||||
Address = new ChatGuiAddressResolver(baseAddress);
|
||||
Address.Setup(scanner);
|
||||
|
||||
Log.Verbose("Chat manager address {ChatManager}", Address.BaseAddress);
|
||||
|
||||
this.printMessageHook =
|
||||
new Hook<PrintMessageDelegate>(Address.PrintMessage, new PrintMessageDelegate(HandlePrintMessageDetour),
|
||||
this);
|
||||
this.populateItemLinkHook =
|
||||
new Hook<PopulateItemLinkDelegate>(Address.PopulateItemLinkObject,
|
||||
new PopulateItemLinkDelegate(HandlePopulateItemLinkDetour),
|
||||
this);
|
||||
}
|
||||
|
||||
public void Enable() {
|
||||
this.printMessageHook.Enable();
|
||||
this.populateItemLinkHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.printMessageHook.Dispose();
|
||||
this.populateItemLinkHook.Dispose();
|
||||
}
|
||||
|
||||
private void HandlePopulateItemLinkDetour(IntPtr linkObjectPtr, IntPtr itemInfoPtr) {
|
||||
try {
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
|
||||
LastLinkedItemId = Marshal.ReadInt32(itemInfoPtr, 8);
|
||||
LastLinkedItemFlags = Marshal.ReadByte(itemInfoPtr, 0x14);
|
||||
|
||||
Log.Debug($"HandlePopulateItemLinkDetour {linkObjectPtr} {itemInfoPtr} - linked:{LastLinkedItemId}");
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Exception onPopulateItemLink hook.");
|
||||
this.populateItemLinkHook.Original(linkObjectPtr, itemInfoPtr);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandlePrintMessageDetour(IntPtr manager, XivChatType chattype, IntPtr pSenderName, IntPtr pMessage,
|
||||
uint senderid, byte isLocal) {
|
||||
try {
|
||||
var senderName = StdString.ReadFromPointer(pSenderName);
|
||||
var message = StdString.ReadFromPointer(pMessage);
|
||||
|
||||
//Log.Debug($"HandlePrintMessageDetour {manager} - [{chattype}] [{BitConverter.ToString(Encoding.UTF8.GetBytes(message))}] {message} from {senderName}");
|
||||
|
||||
var originalMessage = string.Copy(message);
|
||||
|
||||
// Call events
|
||||
var isHandled = false;
|
||||
OnChatMessage?.Invoke(chattype, senderid, senderName, ref message, ref isHandled);
|
||||
|
||||
var messagePtr = pMessage;
|
||||
OwnedStdString allocatedString = null;
|
||||
|
||||
if (originalMessage != message) {
|
||||
allocatedString = this.dalamud.Framework.Libc.NewString(message);
|
||||
Log.Debug(
|
||||
$"HandlePrintMessageDetour String modified: {originalMessage}({messagePtr}) -> {message}({allocatedString.Address})");
|
||||
messagePtr = allocatedString.Address;
|
||||
}
|
||||
|
||||
// Print the original chat if it's handled.
|
||||
if (!isHandled)
|
||||
this.printMessageHook.Original(manager, chattype, pSenderName, messagePtr, senderid, isLocal);
|
||||
|
||||
if (this.baseAddress == IntPtr.Zero)
|
||||
this.baseAddress = manager;
|
||||
|
||||
allocatedString?.Dispose();
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Exception on OnChatMessage hook.");
|
||||
this.printMessageHook.Original(manager, chattype, pSenderName, pMessage, senderid, isLocal);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queue a chat message. While method is named as PrintChat, it only add a entry to the queue,
|
||||
/// later to be processed when UpdateQueue() is called.
|
||||
/// </summary>
|
||||
/// <param name="chat">A message to send.</param>
|
||||
public void PrintChat(XivChatEntry chat) {
|
||||
this.chatQueue.Enqueue(chat);
|
||||
}
|
||||
|
||||
public void Print(string message) {
|
||||
PrintChat(new XivChatEntry {
|
||||
Message = message
|
||||
});
|
||||
}
|
||||
|
||||
public void PrintError(string message) {
|
||||
PrintChat(new XivChatEntry {
|
||||
Message = message,
|
||||
Type = XivChatType.Urgent
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Process a chat queue.
|
||||
/// </summary>
|
||||
public void UpdateQueue(Framework framework) {
|
||||
while (this.chatQueue.Count > 0) {
|
||||
var chat = this.chatQueue.Dequeue();
|
||||
|
||||
var sender = chat.Name ?? "";
|
||||
var message = chat.Message ?? "";
|
||||
|
||||
if (this.baseAddress != IntPtr.Zero)
|
||||
using (var senderVec = framework.Libc.NewString(sender))
|
||||
using (var messageVec = framework.Libc.NewString(message)) {
|
||||
this.printMessageHook.Original(this.baseAddress, chat.Type, senderVec.Address,
|
||||
messageVec.Address, chat.SenderId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
88
Dalamud/Game/Internal/Gui/ChatGuiAddressResolver.cs
Normal file
88
Dalamud/Game/Internal/Gui/ChatGuiAddressResolver.cs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
public sealed class ChatGuiAddressResolver : BaseAddressResolver {
|
||||
public IntPtr BaseAddress { get; }
|
||||
|
||||
public IntPtr PrintMessage { get; private set; }
|
||||
public IntPtr PopulateItemLinkObject { get; private set; }
|
||||
|
||||
public ChatGuiAddressResolver(IntPtr baseAddres) {
|
||||
BaseAddress = baseAddres;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
--- for reference: 4.57 ---
|
||||
|
||||
.text:00000001405CD210 ; __int64 __fastcall Xiv::Gui::ChatGui::PrintMessage(__int64 handler, unsigned __int16 chatType, __int64 senderName, __int64 message, int senderActorId, char isLocal)
|
||||
.text:00000001405CD210 Xiv__Gui__ChatGui__PrintMessage proc near
|
||||
.text:00000001405CD210 ; CODE XREF: sub_1401419F0+201↑p
|
||||
.text:00000001405CD210 ; sub_140141D10+220↑p ...
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 var_220 = qword ptr -220h
|
||||
.text:00000001405CD210 var_218 = byte ptr -218h
|
||||
.text:00000001405CD210 var_210 = word ptr -210h
|
||||
.text:00000001405CD210 var_208 = byte ptr -208h
|
||||
.text:00000001405CD210 var_200 = word ptr -200h
|
||||
.text:00000001405CD210 var_1FC = dword ptr -1FCh
|
||||
.text:00000001405CD210 var_1F8 = qword ptr -1F8h
|
||||
.text:00000001405CD210 var_1F0 = qword ptr -1F0h
|
||||
.text:00000001405CD210 var_1E8 = qword ptr -1E8h
|
||||
.text:00000001405CD210 var_1E0 = dword ptr -1E0h
|
||||
.text:00000001405CD210 var_1DC = word ptr -1DCh
|
||||
.text:00000001405CD210 var_1DA = word ptr -1DAh
|
||||
.text:00000001405CD210 var_1D8 = qword ptr -1D8h
|
||||
.text:00000001405CD210 var_1D0 = byte ptr -1D0h
|
||||
.text:00000001405CD210 var_1C8 = qword ptr -1C8h
|
||||
.text:00000001405CD210 var_1B0 = dword ptr -1B0h
|
||||
.text:00000001405CD210 var_1AC = dword ptr -1ACh
|
||||
.text:00000001405CD210 var_1A8 = dword ptr -1A8h
|
||||
.text:00000001405CD210 var_1A4 = dword ptr -1A4h
|
||||
.text:00000001405CD210 var_1A0 = dword ptr -1A0h
|
||||
.text:00000001405CD210 var_160 = dword ptr -160h
|
||||
.text:00000001405CD210 var_15C = dword ptr -15Ch
|
||||
.text:00000001405CD210 var_140 = dword ptr -140h
|
||||
.text:00000001405CD210 var_138 = dword ptr -138h
|
||||
.text:00000001405CD210 var_130 = byte ptr -130h
|
||||
.text:00000001405CD210 var_C0 = byte ptr -0C0h
|
||||
.text:00000001405CD210 var_50 = qword ptr -50h
|
||||
.text:00000001405CD210 var_38 = qword ptr -38h
|
||||
.text:00000001405CD210 var_30 = qword ptr -30h
|
||||
.text:00000001405CD210 var_28 = qword ptr -28h
|
||||
.text:00000001405CD210 var_20 = qword ptr -20h
|
||||
.text:00000001405CD210 senderActorId = dword ptr 30h
|
||||
.text:00000001405CD210 isLocal = byte ptr 38h
|
||||
.text:00000001405CD210
|
||||
.text:00000001405CD210 ; __unwind { // __GSHandlerCheck
|
||||
.text:00000001405CD210 push rbp
|
||||
.text:00000001405CD212 push rdi
|
||||
.text:00000001405CD213 push r14
|
||||
.text:00000001405CD215 push r15
|
||||
.text:00000001405CD217 lea rbp, [rsp-128h]
|
||||
.text:00000001405CD21F sub rsp, 228h
|
||||
.text:00000001405CD226 mov rax, cs:__security_cookie
|
||||
.text:00000001405CD22D xor rax, rsp
|
||||
.text:00000001405CD230 mov [rbp+140h+var_50], rax
|
||||
.text:00000001405CD237 xor r10b, r10b
|
||||
.text:00000001405CD23A mov [rsp+240h+var_1F8], rcx
|
||||
.text:00000001405CD23F xor eax, eax
|
||||
.text:00000001405CD241 mov r11, r9
|
||||
.text:00000001405CD244 mov r14, r8
|
||||
.text:00000001405CD247 mov r9d, eax
|
||||
.text:00000001405CD24A movzx r15d, dx
|
||||
.text:00000001405CD24E lea r8, [rcx+0C10h]
|
||||
.text:00000001405CD255 mov rdi, rcx
|
||||
*/
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24D8FEFFFF 4881EC28020000 488B05???????? 4833C4 488985F0000000 4532D2 48894C2448");
|
||||
//PrintMessage = sig.ScanText("4055 57 41 ?? 41 ?? 488DAC24E8FEFFFF 4881EC18020000 488B05???????? 4833C4 488985E0000000 4532D2 48894C2438"); old
|
||||
|
||||
//PrintMessage = sig.ScanText("40 55 57 41 56 41 57 48 8D AC 24 D8 FE FF FF 48 81 EC 28 02 00 00 48 8B 05 63 47 4A 01 48 33 C4 48 89 85 F0 00 00 00 45 32 D2 48 89 4C 24 48 33");
|
||||
|
||||
//PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 FA F2 B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
PopulateItemLinkObject = sig.ScanText("48 89 5C 24 08 57 48 83 EC 20 80 7A 06 00 48 8B DA 48 8B F9 74 14 48 8B CA E8 32 03 00 00 48 8B C8 E8 ?? ?? B0 FF 8B C8 EB 1D 0F B6 42 14 8B 4A");
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Dalamud/Game/Internal/Gui/GameGui.cs
Normal file
27
Dalamud/Game/Internal/Gui/GameGui.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
public sealed class GameGui : IDisposable {
|
||||
private GameGuiAddressResolver Address { get; }
|
||||
|
||||
public ChatGui Chat { get; private set; }
|
||||
|
||||
public GameGui(IntPtr baseAddress, SigScanner scanner, Dalamud dalamud) {
|
||||
Address = new GameGuiAddressResolver(baseAddress);
|
||||
Address.Setup(scanner);
|
||||
|
||||
Log.Verbose("GameGuiManager address {Address}", Address.BaseAddress);
|
||||
|
||||
Chat = new ChatGui(Address.ChatManager, scanner, dalamud);
|
||||
}
|
||||
|
||||
public void Enable() {
|
||||
Chat.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Chat.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs
Normal file
27
Dalamud/Game/Internal/Gui/GameGuiAddressResolver.cs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Dalamud.Game.Internal.Gui {
|
||||
public sealed class GameGuiAddressResolver : BaseAddressResolver {
|
||||
public IntPtr BaseAddress { get; private set; }
|
||||
|
||||
public IntPtr ChatManager { get; private set; }
|
||||
|
||||
public GameGuiAddressResolver(IntPtr baseAddress) {
|
||||
BaseAddress = baseAddress;
|
||||
}
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr GetChatManagerDelegate(IntPtr guiManager);
|
||||
|
||||
protected override void SetupInternal(SigScanner scanner) {
|
||||
// Xiv__UiManager__GetChatManager 000 lea rax, [rcx+13E0h]
|
||||
// Xiv__UiManager__GetChatManager+7 000 retn
|
||||
ChatManager = BaseAddress + 0x13E0;
|
||||
}
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
// Do nothing, still required.
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Dalamud/Game/Internal/Libc/LibcFunction.cs
Normal file
48
Dalamud/Game/Internal/Libc/LibcFunction.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc {
|
||||
public sealed class LibcFunction {
|
||||
// TODO: prolly callconv is not okay in x86
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr StdStringFromCStringDelegate(IntPtr pStdString, [MarshalAs(UnmanagedType.LPUTF8Str)]string content, IntPtr size);
|
||||
|
||||
// TODO: prolly callconv is not okay in x86
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate IntPtr StdStringDeallocateDelegate(IntPtr address);
|
||||
|
||||
private LibcFunctionAddressResolver Address { get; }
|
||||
|
||||
private readonly StdStringFromCStringDelegate stdStringCtorCString;
|
||||
private readonly StdStringDeallocateDelegate stdStringDeallocate;
|
||||
|
||||
public LibcFunction(SigScanner scanner) {
|
||||
Address = new LibcFunctionAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
|
||||
this.stdStringCtorCString = Marshal.GetDelegateForFunctionPointer<StdStringFromCStringDelegate>(Address.StdStringFromCstring);
|
||||
this.stdStringDeallocate = Marshal.GetDelegateForFunctionPointer<StdStringDeallocateDelegate>(Address.StdStringDeallocate);
|
||||
}
|
||||
|
||||
public OwnedStdString NewString(string content) {
|
||||
Log.Verbose("Allocating");
|
||||
|
||||
// While 0x70 bytes in the memory should be enough in DX11 version,
|
||||
// I don't trust my analysis so we're just going to allocate almost two times more than that.
|
||||
var pString = Marshal.AllocHGlobal(256);
|
||||
|
||||
// Initialize a string
|
||||
var npos = new IntPtr(0xFFFFFFFF); // assumed to be -1 (0xFFFFFFFF in x86, 0xFFFFFFFF_FFFFFFFF in amd64)
|
||||
var pReallocString = this.stdStringCtorCString(pString, content, npos);
|
||||
|
||||
Log.Verbose("Prev: {Prev} Now: {Now}", pString, pReallocString);
|
||||
|
||||
return new OwnedStdString(pReallocString, DeallocateStdString);
|
||||
}
|
||||
|
||||
private void DeallocateStdString(IntPtr address) {
|
||||
this.stdStringDeallocate(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Dalamud/Game/Internal/Libc/LibcFunctionAddressResolver.cs
Normal file
16
Dalamud/Game/Internal/Libc/LibcFunctionAddressResolver.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System;
|
||||
using System.Security.Policy;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc {
|
||||
public sealed class LibcFunctionAddressResolver : BaseAddressResolver {
|
||||
private delegate IntPtr StringFromCString();
|
||||
|
||||
public IntPtr StdStringFromCstring { get; private set; }
|
||||
public IntPtr StdStringDeallocate { get; private set; }
|
||||
|
||||
protected override void Setup64Bit(SigScanner sig) {
|
||||
StdStringFromCstring = sig.ScanText("48895C2408 4889742410 57 4883EC20 488D4122 66C741200101 488901 498BD8");
|
||||
StdStringDeallocate = sig.ScanText("80792100 7512 488B5108 41B833000000 488B09 E9??????00 C3");
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Dalamud/Game/Internal/Libc/OwnedStdString.cs
Normal file
70
Dalamud/Game/Internal/Libc/OwnedStdString.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc {
|
||||
public sealed class OwnedStdString : IDisposable {
|
||||
internal delegate void DeallocatorDelegate(IntPtr address);
|
||||
|
||||
// ala. the drop flag
|
||||
private bool isDisposed;
|
||||
|
||||
private readonly DeallocatorDelegate dealloc;
|
||||
|
||||
public IntPtr Address { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Construct a wrapper around std::string
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Violating any of these might cause an undefined hehaviour.
|
||||
/// 1. This function takes the ownership of the address.
|
||||
/// 2. A memory pointed by address argument is assumed to be allocated by Marshal.AllocHGlobal thus will try to call Marshal.FreeHGlobal on the address.
|
||||
/// 3. std::string object pointed by address must be initialized before calling this function.
|
||||
/// </remarks>
|
||||
/// <param name="address"></param>
|
||||
/// <param name="dealloc">A deallocator function.</param>
|
||||
/// <returns></returns>
|
||||
internal OwnedStdString(IntPtr address, DeallocatorDelegate dealloc) {
|
||||
Address = address;
|
||||
this.dealloc = dealloc;
|
||||
}
|
||||
|
||||
~OwnedStdString() {
|
||||
ReleaseUnmanagedResources();
|
||||
}
|
||||
|
||||
private void ReleaseUnmanagedResources() {
|
||||
if (Address == IntPtr.Zero) {
|
||||
// Something got seriously fucked.
|
||||
throw new AccessViolationException();
|
||||
}
|
||||
|
||||
Log.Verbose("Deallocting {Addr}", Address);
|
||||
|
||||
// Deallocate inner string first
|
||||
this.dealloc(Address);
|
||||
|
||||
// Free the heap
|
||||
Marshal.FreeHGlobal(Address);
|
||||
|
||||
// Better safe (running on a nullptr) than sorry. (running on a dangling pointer)
|
||||
Address = IntPtr.Zero;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
// No double free plz, kthx.
|
||||
if (this.isDisposed) {
|
||||
return;
|
||||
}
|
||||
this.isDisposed = true;
|
||||
|
||||
ReleaseUnmanagedResources();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public string Read() {
|
||||
return StdString.ReadFromPointer(Address);
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Dalamud/Game/Internal/Libc/StdString.cs
Normal file
33
Dalamud/Game/Internal/Libc/StdString.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Game.Internal.Libc {
|
||||
/// <summary>
|
||||
/// Interation with std::string
|
||||
/// </summary>
|
||||
public static class StdString {
|
||||
public static string ReadFromPointer(IntPtr cstring) {
|
||||
unsafe {
|
||||
if (cstring == IntPtr.Zero) {
|
||||
throw new ArgumentNullException(nameof(cstring));
|
||||
}
|
||||
|
||||
var innerAddress = Marshal.ReadIntPtr(cstring);
|
||||
if (innerAddress == IntPtr.Zero) {
|
||||
throw new NullReferenceException("Inner reference to the cstring is null.");
|
||||
}
|
||||
|
||||
var pInner = (sbyte*) innerAddress.ToPointer();
|
||||
var count = 0;
|
||||
|
||||
// Count the number of chars. String is assumed to be zero-terminated.
|
||||
while (*(pInner + count) != 0) {
|
||||
count += 1;
|
||||
}
|
||||
|
||||
return new string(pInner, 0, count, Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Dalamud/Game/Internal/Network/GameNetwork.cs
Normal file
64
Dalamud/Game/Internal/Network/GameNetwork.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Internal.Network {
|
||||
public sealed class GameNetwork : IDisposable {
|
||||
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
|
||||
private delegate void ProcessZonePacketDelegate(IntPtr a, IntPtr b, IntPtr dataPtr);
|
||||
|
||||
private readonly Hook<ProcessZonePacketDelegate> processZonePacketHook;
|
||||
|
||||
private GameNetworkAddressResolver Address { get; }
|
||||
|
||||
public delegate void OnZonePacketDelegate(IntPtr dataPtr);
|
||||
|
||||
public OnZonePacketDelegate OnZonePacket;
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
public GameNetwork(Dalamud dalamud, SigScanner scanner) {
|
||||
this.dalamud = dalamud;
|
||||
Address = new GameNetworkAddressResolver();
|
||||
Address.Setup(scanner);
|
||||
|
||||
Log.Verbose("ProcessZonePacket address {ProcessZonePacket}", Address.ProcessZonePacket);
|
||||
|
||||
this.processZonePacketHook =
|
||||
new Hook<ProcessZonePacketDelegate>(Address.ProcessZonePacket,
|
||||
new ProcessZonePacketDelegate(ProcessZonePacketDetour),
|
||||
this);
|
||||
}
|
||||
|
||||
public void Enable() {
|
||||
this.processZonePacketHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.processZonePacketHook.Dispose();
|
||||
}
|
||||
|
||||
private void ProcessZonePacketDetour(IntPtr a, IntPtr b, IntPtr dataPtr) {
|
||||
// Call events
|
||||
this.OnZonePacket?.Invoke(dataPtr);
|
||||
|
||||
try {
|
||||
this.processZonePacketHook.Original(a, b, dataPtr);
|
||||
} 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 ProcessZonePacket hook. Header: " + header);
|
||||
|
||||
this.processZonePacketHook.Original(a, b, dataPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Dalamud/Game/Internal/Network/GameNetworkAddressResolver.cs
Normal file
11
Dalamud/Game/Internal/Network/GameNetworkAddressResolver.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
|
||||
namespace Dalamud.Game.Internal.Network {
|
||||
public sealed class GameNetworkAddressResolver : BaseAddressResolver {
|
||||
public IntPtr ProcessZonePacket { 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Dalamud/Game/Network/MarketBoardItemRequest.cs
Normal file
16
Dalamud/Game/Network/MarketBoardItemRequest.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud.Game.Network.Structures;
|
||||
|
||||
namespace Dalamud.Game.Network {
|
||||
internal class MarketBoardItemRequest {
|
||||
public uint CatalogId { get; set; }
|
||||
public byte AmountToArrive { get; set; }
|
||||
|
||||
public List<MarketBoardCurrentOfferings.MarketBoardItemListing> Listings { get; set; }
|
||||
public List<MarketBoardHistory.MarketBoardHistoryListing> History { get; set; }
|
||||
|
||||
public int ListingsRequestId { get; set; } = -1;
|
||||
|
||||
public bool IsDone => Listings.Count == AmountToArrive && History.Count != 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
namespace Dalamud.Game.Network.MarketBoardUploaders {
|
||||
internal interface IMarketBoardUploader {
|
||||
void Upload(MarketBoardItemRequest itemRequest);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisHistoryEntry {
|
||||
[JsonProperty("hq")]
|
||||
public bool Hq { get; set; }
|
||||
|
||||
[JsonProperty("pricePerUnit")]
|
||||
public uint PricePerUnit { get; set; }
|
||||
|
||||
[JsonProperty("quantity")]
|
||||
public uint Quantity { get; set; }
|
||||
|
||||
[JsonProperty("buyerName")]
|
||||
public string BuyerName { get; set; }
|
||||
|
||||
[JsonProperty("onMannequin")]
|
||||
public bool OnMannequin { get; set; }
|
||||
|
||||
[JsonProperty("sellerID")]
|
||||
public ulong SellerId { get; set; }
|
||||
|
||||
[JsonProperty("buyerID")]
|
||||
public ulong BuyerId { get; set; }
|
||||
|
||||
[JsonProperty("timestamp")]
|
||||
public long Timestamp { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisHistoryUploadRequest {
|
||||
[JsonProperty("worldID")]
|
||||
public int WorldId { get; set; }
|
||||
|
||||
[JsonProperty("itemID")]
|
||||
public uint ItemId { get; set; }
|
||||
|
||||
[JsonProperty("entries")]
|
||||
public List<UniversalisHistoryEntry> Entries { get; set; }
|
||||
|
||||
[JsonProperty("uploaderID")]
|
||||
public ulong UploaderId { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisItemListingsEntry {
|
||||
[JsonProperty("listingID")]
|
||||
public ulong ListingId { get; set; }
|
||||
|
||||
[JsonProperty("hq")]
|
||||
public bool Hq { get; set; }
|
||||
|
||||
[JsonProperty("pricePerUnit")]
|
||||
public uint PricePerUnit { get; set; }
|
||||
|
||||
[JsonProperty("quantity")]
|
||||
public uint Quantity { get; set; }
|
||||
|
||||
[JsonProperty("retainerName")]
|
||||
public string RetainerName { get; set; }
|
||||
|
||||
[JsonProperty("retainerID")]
|
||||
public ulong RetainerId { get; set; }
|
||||
|
||||
[JsonProperty("creatorName")]
|
||||
public string CreatorName { get; set; }
|
||||
|
||||
[JsonProperty("onMannequin")]
|
||||
public bool OnMannequin { get; set; }
|
||||
|
||||
[JsonProperty("sellerID")]
|
||||
public ulong SellerId { get; set; }
|
||||
|
||||
[JsonProperty("creatorID")]
|
||||
public ulong CreatorId { get; set; }
|
||||
|
||||
[JsonProperty("stainID")]
|
||||
public int StainId { get; set; }
|
||||
|
||||
[JsonProperty("retainerCity")]
|
||||
public int RetainerCity { get; set; }
|
||||
|
||||
[JsonProperty("lastReviewTime")]
|
||||
public long LastReviewTime { get; set; }
|
||||
|
||||
[JsonProperty("materia")]
|
||||
public List<UniversalisItemMateria> Materia { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisItemListingsUploadRequest {
|
||||
[JsonProperty("worldID")]
|
||||
public int WorldId { get; set; }
|
||||
|
||||
[JsonProperty("itemID")]
|
||||
public uint ItemId { get; set; }
|
||||
|
||||
[JsonProperty("listings")]
|
||||
public List<UniversalisItemListingsEntry> Listings { get; set; }
|
||||
|
||||
[JsonProperty("uploaderID")]
|
||||
public ulong UploaderId { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Game.Network.MarketBoardUploaders.Universalis {
|
||||
internal class UniversalisItemMateria {
|
||||
[JsonProperty("slotID")]
|
||||
public int SlotId { get; set; }
|
||||
|
||||
[JsonProperty("materiaID")]
|
||||
public int MateriaId { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using Dalamud.Game.Network.MarketBoardUploaders;
|
||||
using Dalamud.Game.Network.MarketBoardUploaders.Universalis;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Network.Universalis.MarketBoardUploaders {
|
||||
internal class UniversalisMarketBoardUploader : IMarketBoardUploader {
|
||||
private const string ApiBase = "https://universalis.app";
|
||||
|
||||
//private const string ApiBase = "https://127.0.0.1:443";
|
||||
private const string ApiKey = "GGD6RdSfGyRiHM5WDnAo0Nj9Nv7aC5NDhMj3BebT";
|
||||
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
public UniversalisMarketBoardUploader(Dalamud dalamud) {
|
||||
this.dalamud = dalamud;
|
||||
}
|
||||
|
||||
public void Upload(MarketBoardItemRequest request) {
|
||||
using (var client = new WebClient()) {
|
||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
||||
|
||||
Log.Verbose("Starting Universalis upload.");
|
||||
var uploader = this.dalamud.ClientState.LocalContentId;
|
||||
|
||||
var listingsRequestObject = new UniversalisItemListingsUploadRequest();
|
||||
listingsRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Id;
|
||||
listingsRequestObject.UploaderId = uploader;
|
||||
listingsRequestObject.ItemId = request.CatalogId;
|
||||
|
||||
listingsRequestObject.Listings = new List<UniversalisItemListingsEntry>();
|
||||
foreach (var marketBoardItemListing in request.Listings) {
|
||||
var universalisListing = new UniversalisItemListingsEntry {
|
||||
Hq = marketBoardItemListing.IsHq,
|
||||
SellerId = marketBoardItemListing.RetainerOwnerId,
|
||||
RetainerName = marketBoardItemListing.RetainerName,
|
||||
RetainerId = marketBoardItemListing.RetainerId,
|
||||
CreatorId = marketBoardItemListing.ArtisanId,
|
||||
CreatorName = marketBoardItemListing.PlayerName,
|
||||
OnMannequin = marketBoardItemListing.OnMannequin,
|
||||
LastReviewTime = ((DateTimeOffset) marketBoardItemListing.LastReviewTime).ToUnixTimeSeconds(),
|
||||
PricePerUnit = marketBoardItemListing.PricePerUnit,
|
||||
Quantity = marketBoardItemListing.ItemQuantity,
|
||||
RetainerCity = marketBoardItemListing.RetainerCityId
|
||||
};
|
||||
|
||||
universalisListing.Materia = new List<UniversalisItemMateria>();
|
||||
foreach (var itemMateria in marketBoardItemListing.Materia)
|
||||
universalisListing.Materia.Add(new UniversalisItemMateria {
|
||||
MateriaId = itemMateria.MateriaId,
|
||||
SlotId = itemMateria.Index
|
||||
});
|
||||
|
||||
listingsRequestObject.Listings.Add(universalisListing);
|
||||
}
|
||||
|
||||
var upload = JsonConvert.SerializeObject(listingsRequestObject);
|
||||
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", upload);
|
||||
Log.Verbose(upload);
|
||||
|
||||
var historyRequestObject = new UniversalisHistoryUploadRequest();
|
||||
historyRequestObject.WorldId = this.dalamud.ClientState.LocalPlayer.CurrentWorld.Id;
|
||||
historyRequestObject.UploaderId = uploader;
|
||||
historyRequestObject.ItemId = request.CatalogId;
|
||||
|
||||
historyRequestObject.Entries = new List<UniversalisHistoryEntry>();
|
||||
foreach (var marketBoardHistoryListing in request.History)
|
||||
historyRequestObject.Entries.Add(new UniversalisHistoryEntry {
|
||||
BuyerName = marketBoardHistoryListing.BuyerName,
|
||||
Hq = marketBoardHistoryListing.IsHq,
|
||||
OnMannequin = marketBoardHistoryListing.OnMannequin,
|
||||
PricePerUnit = marketBoardHistoryListing.SalePrice,
|
||||
Quantity = marketBoardHistoryListing.Quantity,
|
||||
Timestamp = ((DateTimeOffset) marketBoardHistoryListing.PurchaseTime).ToUnixTimeSeconds()
|
||||
});
|
||||
|
||||
client.Headers.Add(HttpRequestHeader.ContentType, "application/json");
|
||||
|
||||
var historyUpload = JsonConvert.SerializeObject(historyRequestObject);
|
||||
client.UploadString(ApiBase + $"/upload/{ApiKey}", "POST", historyUpload);
|
||||
Log.Verbose(historyUpload);
|
||||
|
||||
Log.Verbose("Universalis data upload for item#{0} completed.", request.CatalogId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
192
Dalamud/Game/Network/NetworkHandlers.cs
Normal file
192
Dalamud/Game/Network/NetworkHandlers.cs
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.Network.MarketBoardUploaders;
|
||||
using Dalamud.Game.Network.Structures;
|
||||
using Dalamud.Game.Network.Universalis.MarketBoardUploaders;
|
||||
using Dalamud.Settings;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game.Network {
|
||||
public class NetworkHandlers {
|
||||
private readonly Dalamud dalamud;
|
||||
|
||||
private readonly List<MarketBoardItemRequest> marketBoardRequests = new List<MarketBoardItemRequest>();
|
||||
|
||||
private readonly bool optOutMbUploads;
|
||||
private readonly IMarketBoardUploader uploader;
|
||||
|
||||
public NetworkHandlers(Dalamud dalamud, bool optOutMbUploads) {
|
||||
this.dalamud = dalamud;
|
||||
this.optOutMbUploads = optOutMbUploads;
|
||||
|
||||
this.uploader = new UniversalisMarketBoardUploader(dalamud);
|
||||
|
||||
dalamud.Framework.Network.OnZonePacket += OnZonePacket;
|
||||
}
|
||||
|
||||
private void OnZonePacket(IntPtr dataPtr) {
|
||||
var opCode = (ZoneOpCode) Marshal.ReadInt16(dataPtr, 2);
|
||||
|
||||
if (opCode == ZoneOpCode.RetainerSaleItemId) {
|
||||
var itemId = Marshal.ReadInt32(dataPtr + 16);
|
||||
var amount = Marshal.ReadByte(dataPtr + 32);
|
||||
var isHq = false;
|
||||
|
||||
if (itemId > 1000000) {
|
||||
itemId -= 1000000;
|
||||
isHq = true;
|
||||
}
|
||||
|
||||
Task.Run(() => this.dalamud.BotManager.ProcessRetainerSale(itemId, amount, isHq));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (opCode == ZoneOpCode.FateSpawn) {
|
||||
if (PersistentSettings.Instance.Fates == null)
|
||||
return;
|
||||
|
||||
var data = new byte[64];
|
||||
Marshal.Copy(dataPtr, data, 0, 64);
|
||||
|
||||
var fateId = data[16];
|
||||
if (PersistentSettings.Instance.Fates.Any(x => x.Id == fateId) &&
|
||||
this.dalamud.BotManager.IsConnected)
|
||||
Task.Run(() => this.dalamud.BotManager.ProcessFate(fateId));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (opCode == ZoneOpCode.CfNotify) {
|
||||
var data = new byte[64];
|
||||
Marshal.Copy(dataPtr, data, 0, 64);
|
||||
|
||||
var notifyType = data[16];
|
||||
var contentFinderConditionId = BitConverter.ToInt16(data, 38);
|
||||
|
||||
|
||||
Task.Run(async () => {
|
||||
if (notifyType != 4)
|
||||
return;
|
||||
|
||||
var contentFinderCondition =
|
||||
await XivApi.GetContentFinderCondition(contentFinderConditionId);
|
||||
|
||||
this.dalamud.Framework.Gui.Chat.Print("Duty Finder pop: " + contentFinderCondition["Name"]);
|
||||
|
||||
if (this.dalamud.BotManager.IsConnected)
|
||||
await this.dalamud.BotManager.ProcessCfPop(contentFinderCondition);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.optOutMbUploads) {
|
||||
if (opCode == ZoneOpCode.MarketBoardItemRequestStart) {
|
||||
var catalogId = (uint) Marshal.ReadInt32(dataPtr + 0x10);
|
||||
var amount = Marshal.ReadByte(dataPtr + 0x1B);
|
||||
|
||||
this.marketBoardRequests.Add(new MarketBoardItemRequest {
|
||||
CatalogId = catalogId,
|
||||
AmountToArrive = amount,
|
||||
Listings = new List<MarketBoardCurrentOfferings.MarketBoardItemListing>(),
|
||||
History = new List<MarketBoardHistory.MarketBoardHistoryListing>()
|
||||
});
|
||||
|
||||
Log.Verbose($"NEW MB REQUEST START: item#{catalogId} amount#{amount}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (opCode == ZoneOpCode.MarketBoardOfferings) {
|
||||
var listing = MarketBoardCurrentOfferings.Read(dataPtr + 0x10);
|
||||
|
||||
var request =
|
||||
this.marketBoardRequests.LastOrDefault(
|
||||
r => r.CatalogId == listing.ItemListings[0].CatalogId && !r.IsDone);
|
||||
|
||||
if (request == null) {
|
||||
Log.Error(
|
||||
$"Market Board data arrived without a corresponding request: item#{listing.ItemListings[0].CatalogId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.Listings.Count + listing.ItemListings.Count > request.AmountToArrive) {
|
||||
Log.Error(
|
||||
$"Too many Market Board listings received for request: {request.Listings.Count + listing.ItemListings.Count} > {request.AmountToArrive} item#{listing.ItemListings[0].CatalogId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.ListingsRequestId != -1 && request.ListingsRequestId != listing.RequestId) {
|
||||
Log.Error(
|
||||
$"Non-matching RequestIds for Market Board data request: {request.ListingsRequestId}, {listing.RequestId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.ListingsRequestId == -1 && request.Listings.Count > 0) {
|
||||
Log.Error(
|
||||
$"Market Board data request sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.ListingsRequestId == -1) {
|
||||
request.ListingsRequestId = listing.RequestId;
|
||||
Log.Verbose($"First Market Board packet in sequence: {listing.RequestId}");
|
||||
}
|
||||
|
||||
request.Listings.AddRange(listing.ItemListings);
|
||||
|
||||
Log.Verbose("Added {0} ItemListings to request#{1}, now {2}/{3}, item#{4}",
|
||||
listing.ItemListings.Count, request.ListingsRequestId, request.Listings.Count,
|
||||
request.AmountToArrive, request.CatalogId);
|
||||
|
||||
if (request.IsDone) {
|
||||
Log.Verbose("Market Board request finished, starting upload: request#{0} item#{1} amount#{2}",
|
||||
request.ListingsRequestId, request.CatalogId, request.AmountToArrive);
|
||||
try {
|
||||
Task.Run(() => this.uploader.Upload(request));
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex, "Market Board data upload failed.");
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (opCode == ZoneOpCode.MarketBoardHistory) {
|
||||
var listing = MarketBoardHistory.Read(dataPtr + 0x10);
|
||||
|
||||
var request = this.marketBoardRequests.LastOrDefault(r => r.CatalogId == listing.CatalogId);
|
||||
|
||||
if (request == null) {
|
||||
Log.Error(
|
||||
$"Market Board data arrived without a corresponding request: item#{listing.CatalogId}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (request.ListingsRequestId != -1) {
|
||||
Log.Error(
|
||||
$"Market Board data history sequence break: {request.ListingsRequestId}, {request.Listings.Count}");
|
||||
return;
|
||||
}
|
||||
|
||||
request.History.AddRange(listing.HistoryListings);
|
||||
|
||||
Log.Verbose("Added history for item#{0}", listing.CatalogId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum ZoneOpCode {
|
||||
CfNotify = 0x78,
|
||||
RetainerSaleItemId = 0x13F, // TODO these are probably not accurate
|
||||
RetainerSaleFinish = 0x138,
|
||||
FateSpawn = 0x226,
|
||||
MarketBoardItemRequestStart = 0x13B,
|
||||
MarketBoardOfferings = 0x13C,
|
||||
MarketBoardHistory = 0x140
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs
Normal file
106
Dalamud/Game/Network/Structures/MarketBoardCurrentOfferings.cs
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Game.Network.Structures {
|
||||
public class MarketBoardCurrentOfferings {
|
||||
public List<MarketBoardItemListing> ItemListings;
|
||||
|
||||
public int ListingIndexEnd;
|
||||
public int ListingIndexStart;
|
||||
public int RequestId;
|
||||
|
||||
public static unsafe MarketBoardCurrentOfferings Read(IntPtr dataPtr) {
|
||||
var output = new MarketBoardCurrentOfferings();
|
||||
|
||||
using (var stream = new UnmanagedMemoryStream((byte*) dataPtr.ToPointer(), 1544)) {
|
||||
using (var reader = new BinaryReader(stream)) {
|
||||
output.ItemListings = new List<MarketBoardItemListing>();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var listingEntry = new MarketBoardItemListing();
|
||||
|
||||
listingEntry.ListingId = reader.ReadUInt64();
|
||||
listingEntry.RetainerId = reader.ReadUInt64();
|
||||
listingEntry.RetainerOwnerId = reader.ReadUInt64();
|
||||
listingEntry.ArtisanId = reader.ReadUInt64();
|
||||
listingEntry.PricePerUnit = reader.ReadUInt32();
|
||||
listingEntry.TotalTax = reader.ReadUInt32();
|
||||
listingEntry.ItemQuantity = reader.ReadUInt32();
|
||||
listingEntry.CatalogId = reader.ReadUInt32();
|
||||
listingEntry.LastReviewTime = DateTimeOffset.UtcNow.AddSeconds(-reader.ReadUInt16()).DateTime;
|
||||
|
||||
reader.ReadUInt16(); // container
|
||||
reader.ReadUInt32(); // slot
|
||||
reader.ReadUInt16(); // durability
|
||||
reader.ReadUInt16(); // spiritbond
|
||||
|
||||
listingEntry.Materia = new List<MarketBoardItemListing.ItemMateria>();
|
||||
|
||||
for (var materiaIndex = 0; materiaIndex < 5; materiaIndex++) {
|
||||
var materiaVal = reader.ReadUInt16();
|
||||
|
||||
var materiaEntry = new MarketBoardItemListing.ItemMateria();
|
||||
materiaEntry.MateriaId = (materiaVal & 0xFF0) >> 4;
|
||||
materiaEntry.Index = materiaVal & 0xF;
|
||||
|
||||
if (materiaEntry.MateriaId != 0)
|
||||
listingEntry.Materia.Add(materiaEntry);
|
||||
}
|
||||
|
||||
reader.ReadUInt16();
|
||||
reader.ReadUInt32();
|
||||
|
||||
listingEntry.RetainerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
|
||||
listingEntry.PlayerName = Encoding.UTF8.GetString(reader.ReadBytes(32)).TrimEnd('\u0000');
|
||||
listingEntry.IsHq = reader.ReadBoolean();
|
||||
listingEntry.MateriaCount = reader.ReadByte();
|
||||
listingEntry.OnMannequin = reader.ReadBoolean();
|
||||
listingEntry.RetainerCityId = reader.ReadByte();
|
||||
listingEntry.StainId = reader.ReadUInt16();
|
||||
|
||||
reader.ReadUInt16();
|
||||
reader.ReadUInt32();
|
||||
|
||||
if (listingEntry.CatalogId != 0)
|
||||
output.ItemListings.Add(listingEntry);
|
||||
}
|
||||
|
||||
output.ListingIndexEnd = reader.ReadByte();
|
||||
output.ListingIndexStart = reader.ReadByte();
|
||||
output.RequestId = reader.ReadUInt16();
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public class MarketBoardItemListing {
|
||||
public ulong ArtisanId;
|
||||
public uint CatalogId;
|
||||
public bool IsHq;
|
||||
public uint ItemQuantity;
|
||||
public DateTime LastReviewTime;
|
||||
public ulong ListingId;
|
||||
|
||||
public List<ItemMateria> Materia;
|
||||
public int MateriaCount;
|
||||
public bool OnMannequin;
|
||||
public string PlayerName;
|
||||
public uint PricePerUnit;
|
||||
public int RetainerCityId;
|
||||
public ulong RetainerId;
|
||||
|
||||
public string RetainerName;
|
||||
public ulong RetainerOwnerId;
|
||||
public int StainId;
|
||||
public uint TotalTax;
|
||||
|
||||
public class ItemMateria {
|
||||
public int Index;
|
||||
public int MateriaId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Dalamud/Game/Network/Structures/MarketBoardHistory.cs
Normal file
57
Dalamud/Game/Network/Structures/MarketBoardHistory.cs
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Dalamud.Game.Network.Structures {
|
||||
public class MarketBoardHistory {
|
||||
public uint CatalogId;
|
||||
public uint CatalogId2;
|
||||
|
||||
public List<MarketBoardHistoryListing> HistoryListings;
|
||||
|
||||
public static unsafe MarketBoardHistory Read(IntPtr dataPtr) {
|
||||
var output = new MarketBoardHistory();
|
||||
|
||||
using (var stream = new UnmanagedMemoryStream((byte*) dataPtr.ToPointer(), 1544)) {
|
||||
using (var reader = new BinaryReader(stream)) {
|
||||
output.CatalogId = reader.ReadUInt32();
|
||||
output.CatalogId2 = reader.ReadUInt32();
|
||||
|
||||
output.HistoryListings = new List<MarketBoardHistoryListing>();
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
var listingEntry = new MarketBoardHistoryListing();
|
||||
|
||||
listingEntry.SalePrice = reader.ReadUInt32();
|
||||
listingEntry.PurchaseTime = DateTimeOffset.FromUnixTimeSeconds(reader.ReadUInt32()).UtcDateTime;
|
||||
listingEntry.Quantity = reader.ReadUInt32();
|
||||
listingEntry.IsHq = reader.ReadBoolean();
|
||||
|
||||
reader.ReadBoolean();
|
||||
|
||||
listingEntry.OnMannequin = reader.ReadBoolean();
|
||||
listingEntry.BuyerName = Encoding.UTF8.GetString(reader.ReadBytes(33)).TrimEnd('\u0000');
|
||||
listingEntry.CatalogId = reader.ReadUInt32();
|
||||
|
||||
if (listingEntry.CatalogId != 0)
|
||||
output.HistoryListings.Add(listingEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
public class MarketBoardHistoryListing {
|
||||
public string BuyerName;
|
||||
|
||||
public uint CatalogId;
|
||||
public bool IsHq;
|
||||
public bool OnMannequin;
|
||||
public DateTime PurchaseTime;
|
||||
public uint Quantity;
|
||||
public uint SalePrice;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Dalamud/Game/SigScanner.cs
Normal file
148
Dalamud/Game/SigScanner.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Game {
|
||||
public sealed class SigScanner {
|
||||
public SigScanner(ProcessModule module) {
|
||||
Module = module;
|
||||
Is32BitProcess = !Environment.Is64BitProcess;
|
||||
|
||||
// Limit the search space to .text section.
|
||||
SetupSearchSpace(module);
|
||||
|
||||
Log.Verbose("Module base: {Address}", TextSectionBase);
|
||||
Log.Verbose("Moudle size: {Size}", TextSectionSize);
|
||||
}
|
||||
|
||||
public bool Is32BitProcess { get; }
|
||||
|
||||
public IntPtr TextSectionBase { get; private set; }
|
||||
public int TextSectionSize { get; private set; }
|
||||
|
||||
public IntPtr DataSectionBase { get; private set; }
|
||||
public int DataSectionSize { get; private set; }
|
||||
|
||||
public ProcessModule Module { get; }
|
||||
|
||||
private IntPtr TextSectionTop => TextSectionBase + TextSectionSize;
|
||||
|
||||
private void SetupSearchSpace(ProcessModule module) {
|
||||
var baseAddress = module.BaseAddress;
|
||||
|
||||
// We don't want to read all of IMAGE_DOS_HEADER or IMAGE_NT_HEADER stuff so we cheat here.
|
||||
var ntNewOffset = Marshal.ReadInt32(baseAddress, 0x3C);
|
||||
var ntHeader = baseAddress + ntNewOffset;
|
||||
|
||||
// IMAGE_NT_HEADER
|
||||
var fileHeader = ntHeader + 4;
|
||||
var numSections = Marshal.ReadInt16(ntHeader, 6);
|
||||
|
||||
// IMAGE_OPTIONAL_HEADER
|
||||
var optionalHeader = fileHeader + 20;
|
||||
|
||||
IntPtr sectionHeader;
|
||||
if (Is32BitProcess) // IMAGE_OPTIONAL_HEADER32
|
||||
sectionHeader = optionalHeader + 224;
|
||||
else // IMAGE_OPTIONAL_HEADER64
|
||||
sectionHeader = optionalHeader + 240;
|
||||
|
||||
// IMAGE_SECTION_HEADER
|
||||
var sectionCursor = sectionHeader;
|
||||
for (var i = 0; i < numSections; i++) {
|
||||
var sectionName = Marshal.ReadInt64(sectionCursor);
|
||||
|
||||
// .text
|
||||
switch (sectionName) {
|
||||
case 0x747865742E: // .text
|
||||
TextSectionBase = baseAddress + Marshal.ReadInt32(sectionCursor, 12);
|
||||
TextSectionSize = Marshal.ReadInt32(sectionCursor, 8);
|
||||
break;
|
||||
case 0x617461642E: // .data
|
||||
DataSectionBase = baseAddress + Marshal.ReadInt32(sectionCursor, 12);
|
||||
DataSectionSize = Marshal.ReadInt32(sectionCursor, 8);
|
||||
break;
|
||||
}
|
||||
|
||||
sectionCursor += 40;
|
||||
}
|
||||
}
|
||||
|
||||
public IntPtr ScanText(string signature) {
|
||||
return Scan(TextSectionBase, TextSectionSize, signature);
|
||||
}
|
||||
|
||||
public IntPtr ScanData(string signature) {
|
||||
return Scan(DataSectionBase, DataSectionSize, signature);
|
||||
}
|
||||
|
||||
public IntPtr ScanModule(string signature) {
|
||||
return Scan(Module.BaseAddress, Module.ModuleMemorySize, signature);
|
||||
}
|
||||
|
||||
public IntPtr Scan(IntPtr baseAddress, int size, string signature) {
|
||||
var needle = SigToNeedle(signature);
|
||||
|
||||
unsafe {
|
||||
var pCursor = (byte*) baseAddress.ToPointer();
|
||||
var pTop = (byte*) (baseAddress + size - needle.Length);
|
||||
while (pCursor < pTop) {
|
||||
if (IsMatch(pCursor, needle)) return (IntPtr) pCursor;
|
||||
|
||||
// Advance an offset
|
||||
pCursor += 1;
|
||||
}
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException($"Can't find a signature of {signature}");
|
||||
}
|
||||
|
||||
public IntPtr ResolveRelativeAddress(IntPtr nextInstAddr, int relOffset) {
|
||||
if (Is32BitProcess) throw new NotSupportedException("32 bit is not supported.");
|
||||
|
||||
return nextInstAddr + relOffset;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private unsafe bool IsMatch(byte* pCursor, byte?[] needle) {
|
||||
for (var i = 0; i < needle.Length; i++) {
|
||||
var expected = needle[i];
|
||||
if (expected == null) continue;
|
||||
|
||||
var actual = *(pCursor + i);
|
||||
if (expected != actual) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private byte?[] SigToNeedle(string signature) {
|
||||
// Strip all whitespaces
|
||||
signature = signature.Replace(" ", "");
|
||||
|
||||
if (signature.Length % 2 != 0)
|
||||
throw new ArgumentException("Signature without whitespaces must be divisible by two.",
|
||||
nameof(signature));
|
||||
|
||||
var needleLength = signature.Length / 2;
|
||||
var needle = new byte?[needleLength];
|
||||
|
||||
for (var i = 0; i < needleLength; i++) {
|
||||
var hexString = signature.Substring(i * 2, 2);
|
||||
if (hexString == "??" || hexString == "**") {
|
||||
needle[i] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
needle[i] = byte.Parse(hexString, NumberStyles.AllowHexSpecifier);
|
||||
}
|
||||
|
||||
return needle;
|
||||
}
|
||||
}
|
||||
}
|
||||
112
Dalamud/Hooking/Hook.cs
Normal file
112
Dalamud/Hooking/Hook.cs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using EasyHook;
|
||||
|
||||
namespace Dalamud.Hooking {
|
||||
/// <summary>
|
||||
/// Manages a hook which can be used to intercept a call to native function.
|
||||
/// This class is basically a thin wrapper around the LocalHook type to provide helper functions.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Delegate type to represents a function prototype. This must be the same prototype as original function do.</typeparam>
|
||||
public sealed class Hook<T> : IDisposable where T : class {
|
||||
private bool isDisposed;
|
||||
|
||||
private readonly IntPtr address;
|
||||
|
||||
private readonly T original;
|
||||
|
||||
private readonly LocalHook hookInfo;
|
||||
|
||||
/// <summary>
|
||||
/// A memory address of the target function.
|
||||
/// </summary>
|
||||
/// <exception cref="ObjectDisposedException">Hook is already disposed.</exception>
|
||||
public IntPtr Address {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get {
|
||||
CheckDisposed();
|
||||
return this.address;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate function that can be used to call the actual function as if function is not hooked yet.
|
||||
/// </summary>
|
||||
/// <remarks></remarks>
|
||||
/// <exception cref="ObjectDisposedException">Hook is already disposed.</exception>
|
||||
public T Original {
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get {
|
||||
CheckDisposed();
|
||||
return this.original;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a hook. Hooking address is inferred by calling to GetProcAddress() function. Hook is not activated until Enable() method is called.
|
||||
/// </summary>
|
||||
/// <param name="moduleName">A name of the module currently loaded in the memory. (e.g. ws2_32.dll)</param>
|
||||
/// <param name="exportName">A name of the exported function name (e.g. send)</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <param name="callbackParam">A callback object which can be accessed within the detour.</param>
|
||||
/// <returns></returns>
|
||||
public static Hook<T> FromSymbol(string moduleName, string exportName, Delegate detour, object callbackParam = null) {
|
||||
// Get a function address from the symbol name.
|
||||
var address = LocalHook.GetProcAddress(moduleName, exportName);
|
||||
|
||||
return new Hook<T>(address, detour, callbackParam);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Createss a hook. Hook is not activated until Enable() method is called.
|
||||
/// </summary>
|
||||
/// <param name="address">A memory address to install a hook.</param>
|
||||
/// <param name="detour">Callback function. Delegate must have a same original function prototype.</param>
|
||||
/// <param name="callbackParam">A callback object which can be accessed within the detour.</param>
|
||||
public Hook(IntPtr address, Delegate detour, object callbackParam = null) {
|
||||
this.hookInfo = LocalHook.Create(address, detour, callbackParam); // Installs a hook here
|
||||
this.address = address;
|
||||
this.original = Marshal.GetDelegateForFunctionPointer<T>(this.hookInfo.HookBypassAddress);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove a hook from the current process.
|
||||
/// </summary>
|
||||
public void Dispose() {
|
||||
if (this.isDisposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.hookInfo.Dispose();
|
||||
|
||||
this.isDisposed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Enable() {
|
||||
CheckDisposed();
|
||||
|
||||
this.hookInfo.ThreadACL.SetExclusiveACL(null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops intercepting a call to the function.
|
||||
/// </summary>
|
||||
public void Disable() {
|
||||
CheckDisposed();
|
||||
|
||||
this.hookInfo.ThreadACL.SetInclusiveACL(null);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void CheckDisposed() {
|
||||
if (this.isDisposed) {
|
||||
throw new ObjectDisposedException("Hook is already disposed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
42
Dalamud/Plugin/DalamudPluginInterface.cs
Normal file
42
Dalamud/Plugin/DalamudPluginInterface.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Game.Internal;
|
||||
using Dalamud.Game.Internal.Gui;
|
||||
|
||||
namespace Dalamud.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// This class acts as an interface to various objects needed to interact with Dalamud and the game.
|
||||
/// </summary>
|
||||
public class DalamudPluginInterface {
|
||||
/// <summary>
|
||||
/// The CommandManager object that allows you to add and remove custom chat commands.
|
||||
/// </summary>
|
||||
public readonly CommandManager CommandManager;
|
||||
|
||||
/// <summary>
|
||||
/// The ClientState object that allows you to access current client memory information like actors, territories, etc.
|
||||
/// </summary>
|
||||
public readonly ClientState ClientState;
|
||||
|
||||
/// <summary>
|
||||
/// The Framework object that allows you to interact with the client.
|
||||
/// </summary>
|
||||
public readonly Framework Framework;
|
||||
|
||||
/// <summary>
|
||||
/// Set up the interface and populate all fields needed.
|
||||
/// </summary>
|
||||
/// <param name="dalamud"></param>
|
||||
public DalamudPluginInterface(Dalamud dalamud) {
|
||||
this.CommandManager = dalamud.CommandManager;
|
||||
this.Framework = dalamud.Framework;
|
||||
this.ClientState = dalamud.ClientState;
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Dalamud/Plugin/IDalamudPlugin.cs
Normal file
25
Dalamud/Plugin/IDalamudPlugin.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Dalamud.Plugin
|
||||
{
|
||||
/// <summary>
|
||||
/// This interface represents a basic Dalamud plugin. All plugins have to implement this interface.
|
||||
/// </summary>
|
||||
public interface IDalamudPlugin : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the plugin.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a Dalamud plugin.
|
||||
/// </summary>
|
||||
/// <param name="pluginInterface">The <see cref="DalamudPluginInterface"/> needed to access various Dalamud objects.</param>
|
||||
void Initialize(DalamudPluginInterface pluginInterface);
|
||||
}
|
||||
}
|
||||
89
Dalamud/Plugin/PluginManager.cs
Normal file
89
Dalamud/Plugin/PluginManager.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
|
||||
namespace Dalamud.Plugin
|
||||
{
|
||||
public class PluginManager {
|
||||
private readonly Dalamud dalamud;
|
||||
private readonly string pluginDirectory;
|
||||
private readonly string defaultPluginDirectory;
|
||||
|
||||
private readonly DalamudPluginInterface dalamudInterface;
|
||||
|
||||
private List<IDalamudPlugin> plugins;
|
||||
|
||||
public PluginManager(Dalamud dalamud, string pluginDirectory, string defaultPluginDirectory) {
|
||||
this.dalamud = dalamud;
|
||||
this.pluginDirectory = pluginDirectory;
|
||||
this.defaultPluginDirectory = defaultPluginDirectory;
|
||||
|
||||
this.dalamudInterface = new DalamudPluginInterface(dalamud);
|
||||
}
|
||||
|
||||
public void UnloadPlugins() {
|
||||
if (this.plugins == null)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < this.plugins.Count; i++) {
|
||||
this.plugins[i].Dispose();
|
||||
this.plugins[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadPlugins() {
|
||||
LoadPluginsAt(this.defaultPluginDirectory);
|
||||
LoadPluginsAt(this.pluginDirectory);
|
||||
}
|
||||
|
||||
private void LoadPluginsAt(string folder) {
|
||||
if (Directory.Exists(folder))
|
||||
{
|
||||
Log.Debug("Loading plugins at {0}", folder);
|
||||
|
||||
var pluginFileNames = Directory.GetFiles(folder, "*.dll");
|
||||
|
||||
var assemblies = new List<Assembly>(pluginFileNames.Length);
|
||||
foreach (var dllFile in pluginFileNames)
|
||||
{
|
||||
Log.Debug("Loading assembly at {0}", dllFile);
|
||||
var assemblyName = AssemblyName.GetAssemblyName(dllFile);
|
||||
var pluginAssembly = Assembly.Load(assemblyName);
|
||||
assemblies.Add(pluginAssembly);
|
||||
}
|
||||
|
||||
var interfaceType = typeof(IDalamudPlugin);
|
||||
var foundImplementations = new List<Type>();
|
||||
foreach (var assembly in assemblies) {
|
||||
if (assembly != null) {
|
||||
Log.Debug("Loading types for {0}", assembly.FullName);
|
||||
var types = assembly.GetTypes();
|
||||
foreach (var type in types) {
|
||||
if (type.IsInterface || type.IsAbstract) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type.GetInterface(interfaceType.FullName) != null) {
|
||||
foundImplementations.Add(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.plugins = new List<IDalamudPlugin>(foundImplementations.Count);
|
||||
foreach (var pluginType in foundImplementations)
|
||||
{
|
||||
var plugin = (IDalamudPlugin)Activator.CreateInstance(pluginType);
|
||||
plugin.Initialize(this.dalamudInterface);
|
||||
Log.Information("Loaded plugin: {0}", plugin.Name);
|
||||
this.plugins.Add(plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Dalamud/Properties/Resources.Designer.cs
generated
Normal file
73
Dalamud/Properties/Resources.Designer.cs
generated
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Dalamud.Properties {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dalamud.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Drawing.Bitmap.
|
||||
/// </summary>
|
||||
internal static System.Drawing.Bitmap eye {
|
||||
get {
|
||||
object obj = ResourceManager.GetObject("eye", resourceCulture);
|
||||
return ((System.Drawing.Bitmap)(obj));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
969
Dalamud/Properties/Resources.resx
Normal file
969
Dalamud/Properties/Resources.resx
Normal file
|
|
@ -0,0 +1,969 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="eye" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
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
|
||||
9gAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
</root>
|
||||
BIN
Dalamud/Resources/eye.png
Normal file
BIN
Dalamud/Resources/eye.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
50
Dalamud/Settings/PersistentSettings.cs
Normal file
50
Dalamud/Settings/PersistentSettings.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Dalamud.Settings
|
||||
{
|
||||
public class PersistentSettings {
|
||||
private static PersistentSettings _instance = null;
|
||||
|
||||
private static readonly string ConfigPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "settings.json");
|
||||
|
||||
public static PersistentSettings Instance {
|
||||
get {
|
||||
if (_instance == null) {
|
||||
if (!File.Exists(ConfigPath)) {
|
||||
_instance = new PersistentSettings();
|
||||
return _instance;
|
||||
}
|
||||
|
||||
_instance = JsonConvert.DeserializeObject<PersistentSettings>(File.ReadAllText(ConfigPath));
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
public class FateInfo {
|
||||
public string Name { get; set; }
|
||||
public int Id { get; set; }
|
||||
}
|
||||
|
||||
public List<FateInfo> Fates;
|
||||
|
||||
public List<string> BadWords;
|
||||
|
||||
public void Save() {
|
||||
File.WriteAllText(ConfigPath, JsonConvert.SerializeObject(this));
|
||||
}
|
||||
|
||||
public static void Reset() {
|
||||
_instance = new PersistentSettings();
|
||||
Instance.Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
86
Dalamud/XivApi.cs
Normal file
86
Dalamud/XivApi.cs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CSharp.RuntimeBinder;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Dalamud
|
||||
{
|
||||
class XivApi
|
||||
{
|
||||
private const string URL = "http://xivapi.com/";
|
||||
|
||||
private static readonly Dictionary<string, JObject> cachedResponses = new Dictionary<string, JObject>();
|
||||
|
||||
public static async Task<JObject> GetWorld(int world)
|
||||
{
|
||||
var res = await Get("World/" + world);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static async Task<JObject> GetClassJob(int id)
|
||||
{
|
||||
var res = await Get("ClassJob/" + id);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static async Task<JObject> GetFate(int id)
|
||||
{
|
||||
var res = await Get("Fate/" + id);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static async Task<JObject> GetCharacterSearch(string name, string world)
|
||||
{
|
||||
var res = await Get("character/search" + $"?name={name}&server={world}");
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static async Task<JObject> GetContentFinderCondition(int contentFinderCondition) {
|
||||
return await Get("ContentFinderCondition/" + contentFinderCondition);
|
||||
}
|
||||
|
||||
public static async Task<JObject> Search(string query, string indexes, int limit = 100) {
|
||||
query = System.Net.WebUtility.UrlEncode(query);
|
||||
|
||||
return await Get("search" + $"?string={query}&indexes={indexes}&limit={limit}");
|
||||
}
|
||||
|
||||
public static async Task<JObject> GetMarketInfoWorld(int itemId, string worldName) {
|
||||
return await Get($"market/{worldName}/item/{itemId}", true);
|
||||
}
|
||||
|
||||
public static async Task<JObject> GetMarketInfoDc(int itemId, string dcName) {
|
||||
return await Get($"market/item/{itemId}?dc={dcName}", true);
|
||||
}
|
||||
|
||||
public static async Task<JObject> GetItem(int itemId) {
|
||||
return await Get($"Item/{itemId}", true);
|
||||
}
|
||||
|
||||
public static async Task<dynamic> Get(string endpoint, bool noCache = false)
|
||||
{
|
||||
if (cachedResponses.ContainsKey(endpoint) && !noCache)
|
||||
return cachedResponses[endpoint];
|
||||
|
||||
var client = new HttpClient();
|
||||
var response = await client.GetAsync(URL + endpoint);
|
||||
var result = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var obj = JObject.Parse(result);
|
||||
|
||||
if (!noCache)
|
||||
cachedResponses.Add(endpoint, obj);
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue