fix: crash when chat is processed before init, use DI for SeString stuff

This commit is contained in:
goat 2020-06-29 23:41:33 +02:00
parent 26ad4ce74b
commit 740ccfde11
14 changed files with 110 additions and 111 deletions

View file

@ -61,6 +61,8 @@ namespace Dalamud {
public DataManager Data { get; private set; }
internal SeStringManager SeStringManager { get; private set; }
internal Localization LocalizationManager;
@ -122,8 +124,7 @@ namespace Dalamud {
this.Data = new DataManager(this.StartInfo.Language);
await this.Data.Initialize(this.baseDirectory);
// TODO: better way to do this? basically for lumina injection
SeString.Dalamud = this;
SeStringManager = new SeStringManager(Data);
this.NetworkHandlers = new NetworkHandlers(this, this.Configuration.OptOutMbCollection);
@ -145,6 +146,9 @@ namespace Dalamud {
Log.Error(ex, "Plugin load failed.");
}
this.Framework.Enable();
this.ClientState.Enable();
IsReady = true;
});
}
@ -153,9 +157,6 @@ namespace Dalamud {
#if DEBUG
ReplaceExceptionHandler();
#endif
this.Framework.Enable();
this.ClientState.Enable();
}
public void Unload() {

View file

@ -162,7 +162,10 @@ namespace Dalamud.DiscordBot {
await channel.SendMessageAsync(embed: embedBuilder.Build());
}
public async Task ProcessChatMessage(XivChatType type, StdString message, StdString sender) {
public async Task ProcessChatMessage(XivChatType type, SeString message, SeString sender) {
if (this.dalamud.SeStringManager == null)
return;
// Special case for outgoing tells, these should be sent under Incoming tells
var wasOutgoingTell = false;
if (type == XivChatType.TellOutgoing) {
@ -180,8 +183,7 @@ namespace Dalamud.DiscordBot {
var channels = chatTypeConfigs.Select(c => GetChannel(c.Channel).GetAwaiter().GetResult());
var parsedSender = SeString.Parse(sender.RawData);
var playerLink = parsedSender.Payloads.FirstOrDefault(x => x.Type == PayloadType.Player) as PlayerPayload;
var playerLink = sender.Payloads.FirstOrDefault(x => x.Type == PayloadType.Player) as PlayerPayload;
string senderName;
string senderWorld;
@ -192,15 +194,15 @@ namespace Dalamud.DiscordBot {
// Special case 2 - When the local player talks in party/alliance, the name comes through as raw text,
// but prefixed by their position number in the party (which for local player may always be 1)
if (parsedSender.TextValue.EndsWith(this.dalamud.ClientState.LocalPlayer.Name))
if (sender.TextValue.EndsWith(this.dalamud.ClientState.LocalPlayer.Name))
{
senderName = this.dalamud.ClientState.LocalPlayer.Name;
}
else
{
Log.Error("playerLink was null. Sender: {0}", BitConverter.ToString(sender.RawData));
Log.Error("playerLink was null. Sender: {0}", BitConverter.ToString(sender.Encode()));
senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : parsedSender.TextValue;
senderName = wasOutgoingTell ? this.dalamud.ClientState.LocalPlayer.Name : sender.TextValue;
}
senderWorld = this.dalamud.ClientState.LocalPlayer.HomeWorld.GameData.Name;
@ -209,7 +211,7 @@ namespace Dalamud.DiscordBot {
senderWorld = playerLink.World.Name;
}
var rawMessage = SeString.Parse(message.RawData).TextValue;
var rawMessage = message.TextValue;
var avatarUrl = string.Empty;
var lodestoneId = string.Empty;

View file

@ -51,22 +51,12 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// <summary>
/// The Lumina instance to use for any necessary data lookups.
/// </summary>
protected DataManager dataResolver;
protected DataManager DataResolver;
// private for now, since subclasses shouldn't interact with this
// To force-invalidate it, Dirty can be set to true
private byte[] encodedData;
protected Payload()
{
// this is not a good way to do this, but I don't want to have to include a dalamud
// reference on multiple methods in every payload class
// We could also just directly reference this static where we use it, but this at least
// allows for more easily changing how this is injected later, without affecting code
// that makes use of it
this.dataResolver = SeString.Dalamud.Data;
}
/// <summary>
/// Encode this payload object into a byte[] useable in-game for things like the chat log.
/// </summary>
@ -88,7 +78,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// </summary>
/// <param name="reader">A reader positioned at the start of the payload, and containing at least one entire payload.</param>
/// <returns>The constructed Payload-derived object that was decoded from the binary data.</returns>
public static Payload Decode(BinaryReader reader)
public static Payload Decode(BinaryReader reader, DataManager data)
{
var payloadStartPos = reader.BaseStream.Position;
@ -105,6 +95,8 @@ namespace Dalamud.Game.Chat.SeStringHandling
payload = DecodeChunk(reader);
}
payload.DataResolver = data;
// for now, cache off the actual binary data for this payload, so we don't have to
// regenerate it if the payload isn't modified
// TODO: probably better ways to handle this

View file

@ -90,7 +90,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
string value = null;
var sheet = this.dataResolver.GetExcelSheet<Completion>();
var sheet = this.DataResolver.GetExcelSheet<Completion>();
Completion row = null;
try
@ -119,24 +119,24 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
var name = actualTableName switch
{
"Action" => this.dataResolver.GetExcelSheet<Lumina.Excel.GeneratedSheets.Action>().GetRow(this.key).Name,
"ActionComboRoute" => this.dataResolver.GetExcelSheet<ActionComboRoute>().GetRow(this.key).Name,
"BuddyAction" => this.dataResolver.GetExcelSheet<BuddyAction>().GetRow(this.key).Name,
"ClassJob" => this.dataResolver.GetExcelSheet<ClassJob>().GetRow(this.key).Name,
"Companion" => this.dataResolver.GetExcelSheet<Companion>().GetRow(this.key).Singular,
"CraftAction" => this.dataResolver.GetExcelSheet<CraftAction>().GetRow(this.key).Name,
"GeneralAction" => this.dataResolver.GetExcelSheet<GeneralAction>().GetRow(this.key).Name,
"GuardianDeity" => this.dataResolver.GetExcelSheet<GuardianDeity>().GetRow(this.key).Name,
"MainCommand" => this.dataResolver.GetExcelSheet<MainCommand>().GetRow(this.key).Name,
"Mount" => this.dataResolver.GetExcelSheet<Mount>().GetRow(this.key).Singular,
"Pet" => this.dataResolver.GetExcelSheet<Pet>().GetRow(this.key).Name,
"PetAction" => this.dataResolver.GetExcelSheet<PetAction>().GetRow(this.key).Name,
"PetMirage" => this.dataResolver.GetExcelSheet<PetMirage>().GetRow(this.key).Name,
"PlaceName" => this.dataResolver.GetExcelSheet<PlaceName>().GetRow(this.key).Name,
"Race" => this.dataResolver.GetExcelSheet<Race>().GetRow(this.key).Masculine,
"TextCommand" => this.dataResolver.GetExcelSheet<TextCommand>().GetRow(this.key).Command,
"Tribe" => this.dataResolver.GetExcelSheet<Tribe>().GetRow(this.key).Masculine,
"Weather" => this.dataResolver.GetExcelSheet<Weather>().GetRow(this.key).Name,
"Action" => this.DataResolver.GetExcelSheet<Lumina.Excel.GeneratedSheets.Action>().GetRow(this.key).Name,
"ActionComboRoute" => this.DataResolver.GetExcelSheet<ActionComboRoute>().GetRow(this.key).Name,
"BuddyAction" => this.DataResolver.GetExcelSheet<BuddyAction>().GetRow(this.key).Name,
"ClassJob" => this.DataResolver.GetExcelSheet<ClassJob>().GetRow(this.key).Name,
"Companion" => this.DataResolver.GetExcelSheet<Companion>().GetRow(this.key).Singular,
"CraftAction" => this.DataResolver.GetExcelSheet<CraftAction>().GetRow(this.key).Name,
"GeneralAction" => this.DataResolver.GetExcelSheet<GeneralAction>().GetRow(this.key).Name,
"GuardianDeity" => this.DataResolver.GetExcelSheet<GuardianDeity>().GetRow(this.key).Name,
"MainCommand" => this.DataResolver.GetExcelSheet<MainCommand>().GetRow(this.key).Name,
"Mount" => this.DataResolver.GetExcelSheet<Mount>().GetRow(this.key).Singular,
"Pet" => this.DataResolver.GetExcelSheet<Pet>().GetRow(this.key).Name,
"PetAction" => this.DataResolver.GetExcelSheet<PetAction>().GetRow(this.key).Name,
"PetMirage" => this.DataResolver.GetExcelSheet<PetMirage>().GetRow(this.key).Name,
"PlaceName" => this.DataResolver.GetExcelSheet<PlaceName>().GetRow(this.key).Name,
"Race" => this.DataResolver.GetExcelSheet<Race>().GetRow(this.key).Masculine,
"TextCommand" => this.DataResolver.GetExcelSheet<TextCommand>().GetRow(this.key).Command,
"Tribe" => this.DataResolver.GetExcelSheet<Tribe>().GetRow(this.key).Masculine,
"Weather" => this.DataResolver.GetExcelSheet<Weather>().GetRow(this.key).Name,
_ => throw new Exception(actualTableName)
};

View file

@ -25,7 +25,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
get
{
this.item ??= this.dataResolver.GetExcelSheet<Item>().GetRow(this.itemId);
this.item ??= this.DataResolver.GetExcelSheet<Item>().GetRow(this.itemId);
return this.item;
}
}

View file

@ -23,7 +23,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
get
{
this.map ??= this.dataResolver.GetExcelSheet<Map>().GetRow(this.mapId);
this.map ??= this.DataResolver.GetExcelSheet<Map>().GetRow(this.mapId);
return this.map;
}
}
@ -39,7 +39,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
get
{
this.territoryType ??= this.dataResolver.GetExcelSheet<TerritoryType>().GetRow(this.territoryTypeId);
this.territoryType ??= this.DataResolver.GetExcelSheet<TerritoryType>().GetRow(this.territoryTypeId);
return this.territoryType;
}
}

View file

@ -38,7 +38,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
get
{
this.world ??= this.dataResolver.GetExcelSheet<World>().GetRow(this.serverId);
this.world ??= this.DataResolver.GetExcelSheet<World>().GetRow(this.serverId);
return this.world;
}
}

View file

@ -23,7 +23,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
get
{
status ??= this.dataResolver.GetExcelSheet<Status>().GetRow(this.statusId);
status ??= this.DataResolver.GetExcelSheet<Status>().GetRow(this.statusId);
return status;
}
}

View file

@ -33,7 +33,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
get
{
this.color ??= this.dataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
this.color ??= this.DataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
return this.color;
}
}

View file

@ -33,7 +33,7 @@ namespace Dalamud.Game.Chat.SeStringHandling.Payloads
{
get
{
this.color ??= this.dataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
this.color ??= this.DataResolver.GetExcelSheet<UIColor>().GetRow(this.colorKey);
return this.color;
}
}

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
namespace Dalamud.Game.Chat.SeStringHandling
{
@ -11,9 +12,6 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// </summary>
public class SeString
{
// TODO: probably change how this is done/where it comes from
internal static Dalamud Dalamud { get; set; }
/// <summary>
/// The ordered list of payloads included in this SeString.
/// </summary>
@ -36,29 +34,6 @@ namespace Dalamud.Game.Chat.SeStringHandling
}
}
/// <summary>
/// Parse a binary game message into an SeString.
/// </summary>
/// <param name="bytes">Binary message payload data in SE's internal format.</param>
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
public static SeString Parse(byte[] bytes)
{
var payloads = new List<Payload>();
using (var stream = new MemoryStream(bytes))
using (var reader = new BinaryReader(stream))
{
while (stream.Position < bytes.Length)
{
var payload = Payload.Decode(reader);
if (payload != null)
payloads.Add(payload);
}
}
return new SeString(payloads);
}
/// <summary>
/// Creates a new SeString from an ordered list of payloads.
/// </summary>

View file

@ -1,16 +1,47 @@
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Lumina.Excel.GeneratedSheets;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using Dalamud.Data;
using Dalamud.Game.Chat.SeStringHandling.Payloads;
using Lumina.Excel.GeneratedSheets;
namespace Dalamud.Game.Chat.SeStringHandling
{
/// <summary>
/// A utility class for working with common SeString variants.
/// </summary>
public static class SeStringUtils
class SeStringManager
{
private readonly DataManager data;
public SeStringManager(DataManager Data) {
this.data = Data;
}
/// <summary>
/// Parse a binary game message into an SeString.
/// </summary>
/// <param name="bytes">Binary message payload data in SE's internal format.</param>
/// <returns>An SeString containing parsed Payload objects for each payload in the data.</returns>
public SeString Parse(byte[] bytes)
{
var payloads = new List<Payload>();
using (var stream = new MemoryStream(bytes))
using (var reader = new BinaryReader(stream))
{
while (stream.Position < bytes.Length)
{
var payload = Payload.Decode(reader, this.data);
if (payload != null)
payloads.Add(payload);
}
}
return new SeString(payloads);
}
/// <summary>
/// Creates an SeString representing an entire Payload chain that can be used to link an item in the chat log.
/// </summary>
@ -18,9 +49,9 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// <param name="isHQ">Whether to link the high-quality variant of the item.</param>
/// <param name="displayNameOverride">An optional name override to display, instead of the actual item name.</param>
/// <returns>An SeString containing all the payloads necessary to display an item link in the chat log.</returns>
public static SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null)
public SeString CreateItemLink(uint itemId, bool isHQ, string displayNameOverride = null)
{
string displayName = displayNameOverride ?? SeString.Dalamud.Data.GetExcelSheet<Item>().GetRow(itemId).Name;
string displayName = displayNameOverride ?? this.data.GetExcelSheet<Item>().GetRow(itemId).Name;
if (isHQ)
{
displayName += $" {(char)SeIconChar.HighQuality}";
@ -50,12 +81,12 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// <param name="isHQ">Whether to link the high-quality variant of the item.</param>
/// <param name="displayNameOverride">An optional name override to display, instead of the actual item name.</param>
/// <returns>An SeString containing all the payloads necessary to display an item link in the chat log.</returns>
public static SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null)
public SeString CreateItemLink(Item item, bool isHQ, string displayNameOverride = null)
{
return CreateItemLink((uint)item.RowId, isHQ, displayNameOverride ?? item.Name);
}
public static SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY)
public SeString CreateMapLink(uint territoryId, uint mapId, int rawX, int rawY)
{
var mapPayload = new MapLinkPayload(territoryId, mapId, rawX, rawY);
var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}";
@ -81,7 +112,7 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// <param name="yCoord">The human-readable y-coordinate for this link.</param>
/// <param name="fudgeFactor">An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases.</param>
/// <returns>An SeString containing all of the payloads necessary to display a map link in the chat log.</returns>
public static SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f)
public SeString CreateMapLink(uint territoryId, uint mapId, float xCoord, float yCoord, float fudgeFactor = 0.05f)
{
var mapPayload = new MapLinkPayload(territoryId, mapId, xCoord, yCoord, fudgeFactor);
var nameString = $"{mapPayload.PlaceName} {mapPayload.CoordinateString}";
@ -106,13 +137,13 @@ namespace Dalamud.Game.Chat.SeStringHandling
/// <param name="yCoord">The human-readable y-coordinate for this link.</param>
/// <param name="fudgeFactor">An optional offset to account for rounding and truncation errors; it is best to leave this untouched in most cases.</param>
/// <returns>An SeString containing all of the payloads necessary to display a map link in the chat log.</returns>
public static SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f)
public SeString CreateMapLink(string placeName, float xCoord, float yCoord, float fudgeFactor = 0.05f)
{
var mapSheet = SeString.Dalamud.Data.GetExcelSheet<Map>();
var mapSheet = this.data.GetExcelSheet<Map>();
var matches = SeString.Dalamud.Data.GetExcelSheet<PlaceName>().GetRows()
.Where(row => row.Name.ToLowerInvariant() == placeName.ToLowerInvariant())
.ToArray();
var matches = this.data.GetExcelSheet<PlaceName>().GetRows()
.Where(row => row.Name.ToLowerInvariant() == placeName.ToLowerInvariant())
.ToArray();
foreach (var place in matches)
{

View file

@ -93,7 +93,7 @@ namespace Dalamud.Game {
this.dalamud = dalamud;
dalamud.Framework.Gui.Chat.OnCheckMessageHandled += OnCheckMessageHandled;
dalamud.Framework.Gui.Chat.OnChatMessageRaw += OnChatMessage;
dalamud.Framework.Gui.Chat.OnChatMessage += OnChatMessage;
}
private void OnCheckMessageHandled(XivChatType type, uint senderid, ref SeString sender, ref SeString message, ref bool isHandled) {
@ -121,8 +121,8 @@ namespace Dalamud.Game {
public string LastLink { get; private set; }
private void OnChatMessage(XivChatType type, uint senderId, ref StdString sender,
ref StdString message, ref bool isHandled) {
private void OnChatMessage(XivChatType type, uint senderId, ref SeString sender,
ref SeString message, ref bool isHandled) {
if (type == XivChatType.Notice && !this.hasSeenLoadingMsg)
PrintWelcomeMessage();
@ -136,14 +136,11 @@ namespace Dalamud.Game {
return;
#endif
var messageVal = message.Value;
var senderVal = sender.Value;
if (type == XivChatType.RetainerSale)
{
foreach (var regex in retainerSaleRegexes[dalamud.StartInfo.Language])
{
var matchInfo = regex.Match(message.Value);
var matchInfo = regex.Match(message.TextValue);
// we no longer really need to do/validate the item matching since we read the id from the byte array
// but we'd be checking the main match anyway
@ -152,10 +149,10 @@ namespace Dalamud.Game {
continue;
var itemLink =
SeString.Parse(message.RawData).Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload;
message.Payloads.First(x => x.Type == PayloadType.Item) as ItemPayload;
if (itemLink == null) {
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.RawData));
Log.Error("itemLink was null. Msg: {0}", BitConverter.ToString(message.Encode()));
break;
}
@ -176,12 +173,13 @@ namespace Dalamud.Game {
Task.Run(() => this.dalamud.BotManager.ProcessChatMessage(type, messageCopy, senderCopy));
// Handle all of this with SeString some day
/*
if ((this.HandledChatTypeColors.ContainsKey(type) || type == XivChatType.Say || type == XivChatType.Shout ||
type == XivChatType.Alliance || type == XivChatType.TellOutgoing || type == XivChatType.Yell) && !message.Value.Contains((char)0x02)) {
var italicsStart = message.Value.IndexOf("*");
var italicsEnd = message.Value.IndexOf("*", italicsStart + 1);
type == XivChatType.Alliance || type == XivChatType.TellOutgoing || type == XivChatType.Yell)) {
var italicsStart = message.TextValue.IndexOf("*", StringComparison.InvariantCulture);
var italicsEnd = message.TextValue.IndexOf("*", italicsStart + 1, StringComparison.InvariantCulture);
var messageString = message.Value;
var messageString = message.TextValue;
while (italicsEnd != -1) {
var it = MakeItalics(
@ -194,9 +192,9 @@ namespace Dalamud.Game {
message.RawData = Encoding.UTF8.GetBytes(messageString);
}
*/
var linkMatch = this.urlRegex.Match(message.Value);
var linkMatch = this.urlRegex.Match(message.TextValue);
if (linkMatch.Value.Length > 0)
LastLink = linkMatch.Value;
}
@ -244,7 +242,7 @@ namespace Dalamud.Game {
}
}
private static string MakeItalics(string text) {
private static SeString MakeItalics(string text) {
// TODO: when the above code is switched to SeString, this can be a straight insertion of the
// italics payloads only, and be a lot cleaner
var italicString = new SeString(new List<Payload>(new Payload[]
@ -254,7 +252,7 @@ namespace Dalamud.Game {
EmphasisItalicPayload.ItalicsOff
}));
return Encoding.UTF8.GetString(italicString.Encode());
return italicString;
}
}
}

View file

@ -116,8 +116,8 @@ namespace Dalamud.Game.Internal.Gui {
var sender = StdString.ReadFromPointer(pSenderName);
var message = StdString.ReadFromPointer(pMessage);
var parsedSender = SeString.Parse(sender.RawData);
var parsedMessage = SeString.Parse(message.RawData);
var parsedSender = this.dalamud.SeStringManager.Parse(sender.RawData);
var parsedMessage = this.dalamud.SeStringManager.Parse(message.RawData);
Log.Verbose("[CHATGUI][{0}][{1}]", parsedSender.TextValue, parsedMessage.TextValue);