mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-12 18:27:23 +01:00
467 lines
18 KiB
C#
467 lines
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
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.Internal.Gui;
|
|
using Dalamud.Game.Network;
|
|
using Dalamud.Interface;
|
|
using Dalamud.Plugin;
|
|
using ImGuiNET;
|
|
using Serilog;
|
|
|
|
namespace Dalamud {
|
|
public sealed class Dalamud : IDisposable {
|
|
private readonly string baseDirectory;
|
|
|
|
private readonly ManualResetEvent unloadSignal;
|
|
|
|
public 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 readonly DalamudStartInfo StartInfo;
|
|
|
|
public readonly DalamudConfiguration Configuration;
|
|
|
|
internal readonly WinSockHandlers WinSock2;
|
|
|
|
public readonly InterfaceManager InterfaceManager;
|
|
|
|
private readonly string assemblyVersion = Assembly.GetAssembly(typeof(ChatHandlers)).GetName().Version.ToString();
|
|
|
|
public Dalamud(DalamudStartInfo info) {
|
|
this.StartInfo = info;
|
|
this.Configuration = DalamudConfiguration.Load(info.ConfigurationPath);
|
|
|
|
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, this.Configuration.OptOutMbCollection);
|
|
|
|
this.ClientState = new ClientState(this, info, this.sigScanner, this.TargetModule);
|
|
|
|
this.BotManager = new DiscordBotManager(this, this.Configuration.DiscordFeatureConfig);
|
|
|
|
this.PluginManager = new PluginManager(this, info.PluginDirectory, info.DefaultPluginDirectory);
|
|
|
|
this.WinSock2 = new WinSockHandlers();
|
|
|
|
this.InterfaceManager = new InterfaceManager(this.sigScanner);
|
|
this.InterfaceManager.OnDraw += BuildDalamudUi;
|
|
this.InterfaceManager.Enable();
|
|
|
|
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.");
|
|
}
|
|
}
|
|
|
|
private bool isImguiDrawDemoWindow = true;
|
|
private bool isImguiDrawWelcome = true;
|
|
|
|
private bool neverDrawWelcome = false;
|
|
|
|
private void BuildDalamudUi() {
|
|
if (this.isImguiDrawDemoWindow)
|
|
ImGui.ShowDemoWindow();
|
|
|
|
if (this.isImguiDrawWelcome) {
|
|
if (!ImGui.Begin("Welcome to XIVLauncher", ImGuiWindowFlags.Modal | ImGuiWindowFlags.NoCollapse | ImGuiWindowFlags.NoResize))
|
|
{
|
|
// Early out if the window is collapsed, as an optimization.
|
|
ImGui.End();
|
|
} else {
|
|
ImGui.Text($"dalamud says hello. ({this.assemblyVersion})");
|
|
ImGui.Checkbox("Don't show this message again", ref this.neverDrawWelcome);
|
|
ImGui.Spacing();
|
|
|
|
if (ImGui.Button("Close")) {
|
|
this.isImguiDrawWelcome = false;
|
|
}
|
|
|
|
ImGui.End();
|
|
}
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
this.WinSock2.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(OnHelpCommand) {
|
|
HelpMessage = "Shows list of commands available."
|
|
});
|
|
|
|
CommandManager.AddHandler("/xlmute", new CommandInfo(OnBadWordsAddCommand) {
|
|
HelpMessage = "Mute a word or sentence from appearing in chat. Usage: /xlmute <word or sentence>"
|
|
});
|
|
|
|
CommandManager.AddHandler("/xlmutelist", new CommandInfo(OnBadWordsListCommand) {
|
|
HelpMessage = "List muted words or sentences."
|
|
});
|
|
|
|
CommandManager.AddHandler("/xlunmute", new CommandInfo(OnBadWordsRemoveCommand) {
|
|
HelpMessage = "Unmute a word or sentence. Usage: /xlunmute <word or sentence>"
|
|
});
|
|
|
|
CommandManager.AddHandler("/xldstate", new CommandInfo(OnDebugPrintGameStateCommand) {
|
|
HelpMessage = "Print parsed game state",
|
|
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."
|
|
});
|
|
|
|
CommandManager.AddHandler("/xlbgmset", new CommandInfo(OnBgmSetCommand)
|
|
{
|
|
HelpMessage = "Set the Game background music. Usage: /xlbgmset <BGM ID>"
|
|
});
|
|
|
|
CommandManager.AddHandler("/xlitem", new CommandInfo(OnItemLinkCommand)
|
|
{
|
|
HelpMessage = "Link an item by name. Usage: /xlitem <Item name>. For matching an item exactly, use /xlitem +<Item name>"
|
|
});
|
|
|
|
#if DEBUG
|
|
CommandManager.AddHandler("/xldzpi", new CommandInfo(OnDebugZoneDownInjectCommand)
|
|
{
|
|
HelpMessage = "Inject zone down channel",
|
|
ShowInHelp = false
|
|
});
|
|
#endif
|
|
|
|
CommandManager.AddHandler("/xlbonus", new CommandInfo(OnRouletteBonusNotifyCommand)
|
|
{
|
|
HelpMessage = "Notify when a roulette has a bonus you specified. Run without parameters for more info. Usage: /xlbonus <roulette name> <role name>"
|
|
});
|
|
|
|
CommandManager.AddHandler("/xlduidrawdemo", new CommandInfo(OnDebugDrawImGuiDemo) {
|
|
HelpMessage = "Open ImGUI demo window",
|
|
ShowInHelp = false
|
|
});
|
|
}
|
|
|
|
private void OnUnloadCommand(string command, string arguments) {
|
|
Framework.Gui.Chat.Print("Unloading...");
|
|
Unload();
|
|
}
|
|
|
|
private void OnHelpCommand(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 {
|
|
MessageBytes = Encoding.UTF8.GetBytes(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 OnBadWordsAddCommand(string command, string arguments) {
|
|
if (this.Configuration.BadWords == null)
|
|
this.Configuration.BadWords = new List<string>();
|
|
|
|
this.Configuration.BadWords.Add(arguments);
|
|
|
|
this.Configuration.Save(this.StartInfo.ConfigurationPath);
|
|
|
|
Framework.Gui.Chat.Print($"Muted \"{arguments}\".");
|
|
}
|
|
|
|
private void OnBadWordsListCommand(string command, string arguments) {
|
|
if (this.Configuration.BadWords == null)
|
|
this.Configuration.BadWords = new List<string>();
|
|
|
|
if (this.Configuration.BadWords.Count == 0) {
|
|
Framework.Gui.Chat.Print("No muted words or sentences.");
|
|
return;
|
|
}
|
|
|
|
this.Configuration.Save(this.StartInfo.ConfigurationPath);
|
|
|
|
foreach (var word in this.Configuration.BadWords) Framework.Gui.Chat.Print($"\"{word}\"");
|
|
}
|
|
|
|
private void OnBadWordsRemoveCommand(string command, string arguments) {
|
|
if (this.Configuration.BadWords == null)
|
|
this.Configuration.BadWords = new List<string>();
|
|
|
|
this.Configuration.BadWords.RemoveAll(x => x == arguments);
|
|
|
|
this.Configuration.Save(this.StartInfo.ConfigurationPath);
|
|
|
|
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 OnDebugPrintGameStateCommand(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.LocalPlayer.HomeWorld.Name);
|
|
Framework.Gui.Chat.Print(this.ClientState.LocalContentId.ToString("X"));
|
|
Framework.Gui.Chat.Print(Framework.Gui.Chat.LastLinkedItemId.ToString());
|
|
|
|
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.");
|
|
}
|
|
|
|
private void OnBgmSetCommand(string command, string arguments)
|
|
{
|
|
Framework.Gui.SetBgm(ushort.Parse(arguments));
|
|
}
|
|
|
|
private void OnItemLinkCommand(string command, string arguments) {
|
|
var exactSearch = false;
|
|
if (arguments.StartsWith("+"))
|
|
{
|
|
exactSearch = true;
|
|
arguments = arguments.Substring(1);
|
|
}
|
|
|
|
Task.Run(async () => {
|
|
try {
|
|
dynamic results = await XivApi.Search(arguments, "Item", 1, exactSearch);
|
|
var itemId = (short) results.Results[0].ID;
|
|
var itemName = (string)results.Results[0].Name;
|
|
|
|
var hexData = new byte[] {
|
|
0x02, 0x13, 0x06, 0xFE, 0xFF, 0xF3, 0xF3, 0xF3, 0x03, 0x02, 0x27, 0x07, 0x03, 0xF2, 0x3A, 0x2F,
|
|
0x02, 0x01, 0x03, 0x02, 0x13, 0x06, 0xFE, 0xFF, 0xFF, 0x7B, 0x1A, 0x03, 0xEE, 0x82, 0xBB, 0x02,
|
|
0x13, 0x02, 0xEC, 0x03
|
|
};
|
|
|
|
var endTag = new byte[] {
|
|
0x02, 0x27, 0x07, 0xCF, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03, 0x02, 0x13, 0x02, 0xEC, 0x03
|
|
};
|
|
|
|
BitConverter.GetBytes(itemId).Reverse().ToArray().CopyTo(hexData, 14);
|
|
|
|
hexData = hexData.Concat(Encoding.UTF8.GetBytes(itemName)).Concat(endTag).ToArray();
|
|
|
|
Framework.Gui.Chat.PrintChat(new XivChatEntry {
|
|
MessageBytes = hexData
|
|
});
|
|
}
|
|
catch {
|
|
Framework.Gui.Chat.PrintError("Could not find item.");
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
#if DEBUG
|
|
private void OnDebugZoneDownInjectCommand(string command, string arguments) {
|
|
var data = File.ReadAllBytes(arguments);
|
|
|
|
Framework.Network.InjectZoneProtoPacket(data);
|
|
Framework.Gui.Chat.Print($"{arguments} OK with {data.Length} bytes");
|
|
}
|
|
#endif
|
|
|
|
private void OnRouletteBonusNotifyCommand(string command, string arguments)
|
|
{
|
|
if (this.Configuration.DiscordFeatureConfig.CfPreferredRoleChannel == null)
|
|
Framework.Gui.Chat.PrintError("You have not set up a discord channel for these notifications - you will only receive them in chat. To do this, please use the XIVLauncher in-game settings.");
|
|
|
|
if (string.IsNullOrEmpty(arguments))
|
|
goto InvalidArgs;
|
|
|
|
var argParts = arguments.Split();
|
|
if (argParts.Length < 2)
|
|
goto InvalidArgs;
|
|
|
|
|
|
if (this.Configuration.PreferredRoleReminders == null)
|
|
this.Configuration.PreferredRoleReminders = new Dictionary<int, DalamudConfiguration.PreferredRole>();
|
|
|
|
var rouletteIndex = RouletteSlugToKey(argParts[0]);
|
|
|
|
if (rouletteIndex == 0)
|
|
goto InvalidArgs;
|
|
|
|
if (!Enum.TryParse(argParts[1].First().ToString().ToUpper() + argParts[1].ToLower().Substring(1), out DalamudConfiguration.PreferredRole role))
|
|
goto InvalidArgs;
|
|
|
|
if (this.Configuration.PreferredRoleReminders.ContainsKey(rouletteIndex))
|
|
this.Configuration.PreferredRoleReminders[rouletteIndex] = role;
|
|
else
|
|
this.Configuration.PreferredRoleReminders.Add(rouletteIndex, role);
|
|
|
|
Framework.Gui.Chat.Print($"Set bonus notifications for {argParts[0]}({rouletteIndex}) to {role}");
|
|
this.Configuration.Save(this.StartInfo.ConfigurationPath);
|
|
|
|
return;
|
|
|
|
InvalidArgs:
|
|
Framework.Gui.Chat.PrintError("Unrecognized arguments.");
|
|
Framework.Gui.Chat.Print("Possible values for roulette: leveling, 506070, msq, guildhests, expert, trials, mentor, alliance, normal\n" +
|
|
"Possible values for role: tank, dps, healer, all, none/reset");
|
|
}
|
|
|
|
private void OnDebugDrawImGuiDemo(string command, string arguments) {
|
|
this.isImguiDrawDemoWindow = true;
|
|
}
|
|
|
|
private int RouletteSlugToKey(string slug) => slug.ToLower() switch {
|
|
"leveling" => 1,
|
|
"506070" => 2,
|
|
"msq" => 3,
|
|
"guildhests" => 4,
|
|
"expert" => 5,
|
|
"trials" => 6,
|
|
"mentor" => 8,
|
|
"alliance" => 9,
|
|
"normal" => 10,
|
|
_ => 0
|
|
};
|
|
|
|
private DalamudConfiguration.PreferredRole RoleNameToPreferredRole(string name) => name.ToLower() switch
|
|
{
|
|
"Tank" => DalamudConfiguration.PreferredRole.Tank,
|
|
"Healer" => DalamudConfiguration.PreferredRole.Healer,
|
|
"Dps" => DalamudConfiguration.PreferredRole.Dps,
|
|
"All" => DalamudConfiguration.PreferredRole.All,
|
|
_ => DalamudConfiguration.PreferredRole.None
|
|
};
|
|
}
|
|
}
|