├── version ├── images ├── icon.ico └── icon.png ├── NotACT ├── TimerFrame.cs ├── packages.lock.json ├── IActPluginV1.cs ├── Datatypes │ ├── LogLineEntry.cs │ ├── StrDouble.cs │ ├── LocalizationObject.cs │ ├── LogLineEventArgs.cs │ ├── HistoryRecord.cs │ ├── ZoneData.cs │ └── CombatActionEventArgs.cs ├── README.md ├── FormActMain.Designer.cs ├── ActGlobals.cs ├── NotACT.csproj └── FormActMain.resx ├── IINACT ├── NativeMethods.txt ├── IINACT.json ├── Network │ ├── SpanExtensions.cs │ ├── PacketDispatcher.cs │ ├── GameServerTime.cs │ ├── SimpleBuffer.cs │ └── Packets.cs ├── PluginLogTraceListener.cs ├── Configuration.cs ├── TextToSpeechProvider.cs ├── PrivateHelpers.cs └── IpcProviders.cs ├── external_dependencies ├── FFXIV_ACT_Plugin.dll ├── SafeMemoryReader.dll └── SDK │ ├── FFXIV_ACT_Plugin.Common.dll │ ├── FFXIV_ACT_Plugin.Config.dll │ ├── FFXIV_ACT_Plugin.Logfile.dll │ ├── FFXIV_ACT_Plugin.Memory.dll │ ├── FFXIV_ACT_Plugin.Network.dll │ ├── FFXIV_ACT_Plugin.Parse.dll │ └── FFXIV_ACT_Plugin.Resource.dll ├── FetchDependencies ├── packages.lock.json ├── ApiVersion.cs ├── Costura.cs ├── FetchDependencies.csproj └── FetchDependencies.cs ├── OverlayPlugin.Common ├── IOverlayAddonV2.cs ├── IOverlayTemplate.cs ├── IEventSource.cs ├── IPluginConfig.cs ├── IOverlayConfig.cs ├── packages.lock.json ├── ILogger.cs ├── IOverlay.cs ├── OverlayPlugin.Common.csproj ├── LICENSE.txt ├── OverlayConfigList.cs └── Registry.cs ├── OverlayPlugin.Core ├── JSApi │ ├── IApiBase.cs │ └── OverlayApi.cs ├── IEventReceiver.cs ├── Handlers │ ├── IHandler.cs │ ├── WebSocket │ │ ├── ISocketHandler.cs │ │ ├── SocketHandler.cs │ │ └── LegacySocketHandler.cs │ ├── Ipc │ │ ├── IpcHandler.cs │ │ ├── LegacyIpcHandler.cs │ │ ├── IHandlerFactory.cs │ │ └── IpcHandlerController.cs │ ├── LegacyHandler.cs │ └── Handler.cs ├── MemoryProcessors │ ├── EnmityHud │ │ ├── Common.cs │ │ ├── EnmityHudMemoryManager.cs │ │ ├── EnmityHudMemory70.cs │ │ └── EnmityHudMemory73.cs │ ├── Enmity │ │ ├── Common.cs │ │ ├── EnmityMemory60.cs │ │ └── EnmityMemoryManager.cs │ ├── Aggro │ │ ├── AggroMemory60.cs │ │ ├── Common.cs │ │ └── AggroMemoryManager.cs │ ├── InCombat │ │ ├── InCombatMemory70.cs │ │ ├── InCombatMemory73.cs │ │ ├── InCombatMemoryManager.cs │ │ └── InCombatMemory.cs │ ├── AtkStage │ │ ├── AtkStageMemory.cs │ │ └── AtkStageMemoryManager.cs │ ├── Target │ │ ├── TargetMemory70.cs │ │ └── TargetMemoryManager.cs │ ├── Party │ │ └── PartyMemoryManager.cs │ ├── ContentFinderSettings │ │ ├── ContentFinderSettings71.cs │ │ ├── ContentFinderSettings70.cs │ │ ├── ContentFinderSettingsMemoryManager.cs │ │ └── LineContentFinderSettings.cs │ ├── JobGauge │ │ ├── JobGauge70.cs │ │ ├── JobGauge655.cs │ │ └── JobGauge74.cs │ └── Combatant │ │ └── CombatantMemoryManager.cs ├── WebSocket │ ├── OverlayServer.cs │ └── OverlaySession.cs ├── NetworkProcessors │ ├── LineActorControlCommon.cs │ ├── LineNpcYell.cs │ ├── LineRSV.cs │ ├── LineActorCastExtra.cs │ ├── PacketHelper │ │ ├── Common.cs │ │ ├── LineBaseSubMachina.cs │ │ ├── LineBaseCustomMachina.cs │ │ └── LineBaseCustom.cs │ ├── LineActorSetPos.cs │ ├── LineActorMove.cs │ ├── LineBattleTalk2.cs │ ├── LineCountdownCancel.cs │ ├── LineActorControlSelfExtra.cs │ ├── LineCEDirector.cs │ ├── LineFateControl.cs │ └── LineCountdown.cs ├── ConfigCreationConverter.cs ├── packages.lock.json ├── Integration │ └── TriggIntegration.cs ├── Logger.cs ├── LICENSE.txt ├── OverlayTemplateConfig.cs └── EventSources │ └── MiniParseLoglineReader.cs ├── .gitmodules ├── .github ├── release-notices.md └── workflows │ ├── pr-build.yml │ └── build.yml ├── repo.json └── README.md /version: -------------------------------------------------------------------------------- 1 | 1.1 2 | -------------------------------------------------------------------------------- /images/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/images/icon.ico -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/images/icon.png -------------------------------------------------------------------------------- /NotACT/TimerFrame.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | internal class TimerFrame { } 4 | -------------------------------------------------------------------------------- /IINACT/NativeMethods.txt: -------------------------------------------------------------------------------- 1 | MapViewOfFile 2 | UnmapViewOfFile 3 | CreateFileMappingW 4 | CreateFileW 5 | ReadFile -------------------------------------------------------------------------------- /external_dependencies/FFXIV_ACT_Plugin.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/FFXIV_ACT_Plugin.dll -------------------------------------------------------------------------------- /external_dependencies/SafeMemoryReader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/SafeMemoryReader.dll -------------------------------------------------------------------------------- /external_dependencies/SDK/FFXIV_ACT_Plugin.Common.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/SDK/FFXIV_ACT_Plugin.Common.dll -------------------------------------------------------------------------------- /external_dependencies/SDK/FFXIV_ACT_Plugin.Config.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/SDK/FFXIV_ACT_Plugin.Config.dll -------------------------------------------------------------------------------- /external_dependencies/SDK/FFXIV_ACT_Plugin.Logfile.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/SDK/FFXIV_ACT_Plugin.Logfile.dll -------------------------------------------------------------------------------- /external_dependencies/SDK/FFXIV_ACT_Plugin.Memory.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/SDK/FFXIV_ACT_Plugin.Memory.dll -------------------------------------------------------------------------------- /external_dependencies/SDK/FFXIV_ACT_Plugin.Network.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/SDK/FFXIV_ACT_Plugin.Network.dll -------------------------------------------------------------------------------- /external_dependencies/SDK/FFXIV_ACT_Plugin.Parse.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/SDK/FFXIV_ACT_Plugin.Parse.dll -------------------------------------------------------------------------------- /NotACT/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net10.0-windows7.0": {}, 5 | "net10.0-windows7.0/win-x64": {} 6 | } 7 | } -------------------------------------------------------------------------------- /external_dependencies/SDK/FFXIV_ACT_Plugin.Resource.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marzent/IINACT/HEAD/external_dependencies/SDK/FFXIV_ACT_Plugin.Resource.dll -------------------------------------------------------------------------------- /FetchDependencies/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net10.0-windows7.0": {}, 5 | "net10.0-windows7.0/win-x64": {} 6 | } 7 | } -------------------------------------------------------------------------------- /OverlayPlugin.Common/IOverlayAddonV2.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowMage.OverlayPlugin 2 | { 3 | public interface IOverlayAddonV2 4 | { 5 | void Init(); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /NotACT/IActPluginV1.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public interface IActPluginV1 4 | { 5 | void InitPlugin(TabPage pluginScreenSpace, Label pluginStatusText); 6 | 7 | void DeInitPlugin(); 8 | } 9 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/JSApi/IApiBase.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowMage.OverlayPlugin 2 | { 3 | internal interface IApiBase : IEventReceiver 4 | { 5 | void OverlayMessage(string msg); 6 | 7 | void InitModernAPI(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "machina"] 2 | path = machina 3 | url = https://github.com/marzent/machina 4 | [submodule "OverlayPlugin.Core/resources/act-overlays"] 5 | path = OverlayPlugin.Core/resources/act-overlays 6 | url = https://github.com/cking/act-overlays 7 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/IEventReceiver.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace RainbowMage.OverlayPlugin 4 | { 5 | internal interface IEventReceiver 6 | { 7 | string Name { get; } 8 | 9 | void HandleEvent(JObject e); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/IHandler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace RainbowMage.OverlayPlugin.Handlers; 6 | 7 | internal interface IHandler : IDisposable 8 | { 9 | public void DataReceived(JObject data); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /FetchDependencies/ApiVersion.cs: -------------------------------------------------------------------------------- 1 | namespace FetchDependencies; 2 | 3 | public static class ApiVersion 4 | { 5 | public static readonly Version IinactApiVersion = new(1, 5, 0); 6 | public static readonly string NamespaceIdentifier = 7 | $"IINACT_API_V{IinactApiVersion.ToString().Replace(".", "_")}"; 8 | } 9 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/WebSocket/ISocketHandler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Net.Sockets; 5 | 6 | namespace RainbowMage.OverlayPlugin.Handlers.WebSocket; 7 | 8 | internal interface ISocketHandler : IDisposable 9 | { 10 | public void OnMessage(string message); 11 | public void OnError(SocketError error); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/EnmityHud/Common.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.EnmityHud 4 | { 5 | [Serializable] 6 | public class EnmityHudEntry 7 | { 8 | public int Order; 9 | public uint ID; 10 | public uint HPPercent; 11 | public uint EnmityPercent; 12 | public uint CastPercent; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/release-notices.md: -------------------------------------------------------------------------------- 1 | ## What's Changed 2 | * Minor fixes 3 | 4 | > **Warning** 5 | > No support will be provided on any Dalamud official support channel. Please use the [Issues](https://github.com/marzent/IINACT/issues) page or [Discord](https://discord.gg/pcexJC8YPG) for any support requests. Do NOT ask for support on the [XIVLauncher & Dalamud Discord](https://discord.gg/holdshift), as support for 3rd-party plugins is not provided there. 6 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Enmity/Common.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Enmity 4 | { 5 | [Serializable] 6 | public class EnmityEntry 7 | { 8 | public uint ID; 9 | public uint OwnerID; 10 | public string Name; 11 | public uint Enmity; 12 | public bool isMe; 13 | public int HateRate; 14 | public byte Job; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/IOverlayTemplate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RainbowMage.OverlayPlugin 5 | { 6 | public interface IOverlayTemplate 7 | { 8 | public string Name { get; } 9 | public string Uri { get; } 10 | public string PlaintextUri { get; } 11 | public int? SuggestedWidth { get; } 12 | public int? SuggestedHeight { get; } 13 | public List Features { get; } 14 | 15 | public abstract Uri ToOverlayUri(Uri webSocketServer); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /IINACT/IINACT.json: -------------------------------------------------------------------------------- 1 | { 2 | "Author": "Usagi", 3 | "Name": "IINACT", 4 | "Punchline": "A parsing plugin.", 5 | "RepoUrl": "https://github.com/marzent/IINACT", 6 | "Description": "A plugin to run the FFXIV_ACT_Plugin in an ACT-like environment with a heavily modified port of Overlay Plugin for modern .NET", 7 | "InternalName": "IINACT", 8 | "ApplicableVersion": "2025.12.18.0000.0000", 9 | "Tags": [ 10 | "parsing", 11 | "plugin", 12 | "ACT" 13 | ], 14 | "IconUrl": "https://raw.githubusercontent.com/marzent/IINACT/master/images/icon.png" 15 | } 16 | -------------------------------------------------------------------------------- /NotACT/Datatypes/LogLineEntry.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public class LogLineEntry 4 | { 5 | public LogLineEntry(DateTime Time, string LogLine, int ParsedType, int GlobalTimeSorter) 6 | { 7 | this.LogLine = LogLine; 8 | Type = ParsedType; 9 | SearchSelected = false; 10 | this.Time = Time; 11 | this.GlobalTimeSorter = GlobalTimeSorter; 12 | } 13 | 14 | public int GlobalTimeSorter { get; } 15 | 16 | public DateTime Time { get; set; } 17 | 18 | public string LogLine { get; } 19 | 20 | public int Type { get; } 21 | 22 | public bool SearchSelected { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/IEventSource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin 4 | { 5 | public interface IEventSource : IDisposable 6 | { 7 | /// 8 | /// ユーザーが設定したオーバーレイの名前を取得します。 9 | /// 10 | string Name { get; } 11 | 12 | /// 13 | /// オーバーレイの更新を開始します。 14 | /// 15 | void Start(); 16 | 17 | /// 18 | /// オーバーレイの更新を停止します。 19 | /// 20 | void Stop(); 21 | 22 | void LoadConfig(IPluginConfig config); 23 | 24 | void SaveConfig(IPluginConfig config); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Aggro/AggroMemory60.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Aggro 4 | { 5 | interface IAggroMemory60 : IAggroMemory { } 6 | 7 | class AggroMemory60 : AggroMemory, IAggroMemory60 8 | { 9 | private const int aggroEnmityOffset = -2336; 10 | 11 | // Aggro uses the same signature as Enmity 12 | public AggroMemory60(TinyIoCContainer container) 13 | : base(container, Enmity.EnmityMemory60.enmitySignature, aggroEnmityOffset) { } 14 | 15 | public override Version GetVersion() 16 | { 17 | return new Version(6, 0); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Enmity/EnmityMemory60.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Enmity 4 | { 5 | interface IEnmityMemory60 : IEnmityMemory { } 6 | 7 | class EnmityMemory60 : EnmityMemory, IEnmityMemory60 8 | { 9 | public const string enmitySignature = "83f9ff7412448b048e8bd3488d0d"; 10 | private const int enmitySignatureOffset = -2608; 11 | 12 | public EnmityMemory60(TinyIoCContainer container) 13 | : base(container, enmitySignature, enmitySignatureOffset) { } 14 | 15 | public override Version GetVersion() 16 | { 17 | return new Version(6, 0); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /NotACT/Datatypes/StrDouble.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public class StrDouble : IComparable, IEquatable 4 | { 5 | public StrDouble(string Name, double Val) 6 | { 7 | this.Name = Name; 8 | this.Val = Val; 9 | } 10 | 11 | public string Name { get; } 12 | 13 | public double Val { get; } 14 | 15 | public int CompareTo(object? obj) 16 | { 17 | if (obj is not StrDouble other) 18 | throw new ArgumentException("Object is not a StrDouble."); 19 | 20 | return Val.CompareTo(other.Val); 21 | } 22 | 23 | 24 | public bool Equals(StrDouble? other) 25 | { 26 | return Name == other!.Name && Val == other!.Val; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/IPluginConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace RainbowMage.OverlayPlugin 6 | { 7 | public interface IPluginConfig 8 | { 9 | OverlayConfigList Overlays { get; set; } 10 | bool HideOverlaysWhenNotActive { get; set; } 11 | bool HideOverlayDuringCutscene { get; set; } 12 | string WSServerIP { get; set; } 13 | int WSServerPort { get; set; } 14 | bool WSServerSSL { get; set; } 15 | bool WSServerRunning { get; set; } 16 | Version Version { get; set; } 17 | Dictionary EventSourceConfigs { get; set; } 18 | 19 | void MarkDirty(); 20 | void Save(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/IOverlayConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | 4 | namespace RainbowMage.OverlayPlugin 5 | { 6 | /// 7 | /// の設定に必要なプロパティを定義します。 8 | /// 9 | /// 10 | /// アドオンを作成する場合はこのインターフェイスを実装するのではなく、 11 | /// 抽象クラスを継承してください。 12 | /// 13 | public interface IOverlayConfig 14 | { 15 | string Name { get; set; } 16 | bool IsVisible { get; set; } 17 | bool HideOutOfCombat { get; set; } 18 | Size Size { get; set; } 19 | string Url { get; set; } 20 | 21 | // IOverlayConfig 実装型 → IOverlay 実装型の逆引き用 22 | Type OverlayType { get; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/InCombat/InCombatMemory70.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.InCombat 4 | { 5 | interface IInCombatMemory70 : IInCombatMemory { } 6 | 7 | class InCombatMemory70 : InCombatMemory, IInCombatMemory70 8 | { 9 | private const string inCombatSignature = "803D??????????74??488B03488BCBFF50"; 10 | private const int inCombatSignatureOffset = -15; 11 | private const int inCombatRIPOffset = 1; 12 | public InCombatMemory70(TinyIoCContainer container) : base(container, inCombatSignature, inCombatSignatureOffset, inCombatRIPOffset) { } 13 | 14 | public override Version GetVersion() 15 | { 16 | return new Version(7, 0); 17 | } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/InCombat/InCombatMemory73.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.InCombat 4 | { 5 | interface IInCombatMemory73 : IInCombatMemory { } 6 | 7 | class InCombatMemory73 : InCombatMemory, IInCombatMemory73 8 | { 9 | private const string inCombatSignature = "74??803D??????????74??488B03488BCBFF50"; 10 | private const int inCombatSignatureOffset = -15; 11 | private const int inCombatRIPOffset = 1; 12 | public InCombatMemory73(TinyIoCContainer container) : base(container, inCombatSignature, inCombatSignatureOffset, inCombatRIPOffset) { } 13 | 14 | public override Version GetVersion() 15 | { 16 | return new Version(7, 3); 17 | } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net10.0-windows7.0": { 5 | "Microsoft.CSharp": { 6 | "type": "Direct", 7 | "requested": "[4.7.0, )", 8 | "resolved": "4.7.0", 9 | "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" 10 | }, 11 | "System.Data.DataSetExtensions": { 12 | "type": "Direct", 13 | "requested": "[4.5.0, )", 14 | "resolved": "4.5.0", 15 | "contentHash": "221clPs1445HkTBZPL+K9sDBdJRB8UN8rgjO3ztB0CQ26z//fmJXtlsr6whGatscsKGBrhJl5bwJuKSA8mwFOw==" 16 | }, 17 | "Advanced Combat Tracker": { 18 | "type": "Project" 19 | } 20 | }, 21 | "net10.0-windows7.0/win-x64": {} 22 | } 23 | } -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Aggro/Common.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RainbowMage.OverlayPlugin.MemoryProcessors.Combatant; 4 | using RainbowMage.OverlayPlugin.MemoryProcessors.Enmity; 5 | 6 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Aggro 7 | { 8 | [Serializable] 9 | public class AggroEntry 10 | { 11 | public uint ID; 12 | public string Name; 13 | public int HateRate; 14 | public int Order; 15 | public bool isCurrentTarget; 16 | public bool IsTargetable; 17 | 18 | public int CurrentHP; 19 | public int MaxHP; 20 | 21 | // Target of Enemy 22 | public EnmityEntry Target; 23 | 24 | // Effects 25 | public List Effects; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /NotACT/Datatypes/LocalizationObject.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public class LocalizationObject 4 | { 5 | public LocalizationObject(string DisplayedText, string LocalizationDescription) 6 | { 7 | this.DisplayedText = DisplayedText; 8 | this.LocalizationDescription = LocalizationDescription; 9 | } 10 | 11 | public string DisplayedText { get; set; } 12 | 13 | public string LocalizationDescription { get; } 14 | 15 | internal string S => DisplayedText; 16 | 17 | public override string ToString() => DisplayedText; 18 | 19 | public static implicit operator string(LocalizationObject val) => val.DisplayedText; 20 | 21 | public static implicit operator LocalizationObject(string val) => 22 | new LocalizationObject(val, string.Empty); 23 | } 24 | -------------------------------------------------------------------------------- /IINACT/Network/SpanExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace IINACT.Network; 4 | 5 | public static class SpanExtensions 6 | { 7 | public static U Cast(this Span input) where T : struct where U : struct 8 | { 9 | return MemoryMarshal.Cast(input)[0]; 10 | } 11 | 12 | public static U Cast(this ReadOnlySpan input) where T : struct where U : struct 13 | { 14 | return MemoryMarshal.Cast(input)[0]; 15 | } 16 | 17 | public static T CastTo(this Span input) where T : struct 18 | { 19 | return MemoryMarshal.Cast(input)[0]; 20 | } 21 | 22 | public static T CastTo(this ReadOnlySpan input) where T : struct 23 | { 24 | return MemoryMarshal.Cast(input)[0]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/AtkStage/AtkStageMemory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.AtkStage 6 | { 7 | public abstract class AtkStageMemory 8 | { 9 | protected FFXIVMemory memory; 10 | protected ILogger logger; 11 | 12 | public AtkStageMemory(TinyIoCContainer container) 13 | { 14 | logger = container.Resolve(); 15 | memory = container.Resolve(); 16 | } 17 | 18 | public bool IsValid() 19 | { 20 | if (!memory.IsValid()) 21 | return false; 22 | 23 | return true; 24 | } 25 | 26 | public void ScanPointers() { } 27 | 28 | public abstract Version GetVersion(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/WebSocket/OverlayServer.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Net; 4 | using System.Net.Sockets; 5 | using NetCoreServer; 6 | 7 | namespace RainbowMage.OverlayPlugin.WebSocket; 8 | 9 | internal class OverlayServer : WsServer 10 | { 11 | private TinyIoCContainer Container { get; } 12 | private ILogger Logger { get; } 13 | 14 | public OverlayServer(IPAddress address, int port, TinyIoCContainer container) : base(address, port) 15 | { 16 | Container = container; 17 | Logger = container.Resolve(); 18 | } 19 | 20 | protected override TcpSession CreateSession() 21 | { 22 | return new OverlaySession(this, Container); 23 | } 24 | 25 | protected override void OnError(SocketError error) 26 | { 27 | Logger.Log(LogLevel.Error, $"Overlay WebSocket server caught an error with code {error}"); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /IINACT/PluginLogTraceListener.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | namespace IINACT; 4 | 5 | public class PluginLogTraceListener : TraceListener 6 | { 7 | public override void Write(string? message) { } 8 | 9 | public override void WriteLine(string? message) { } 10 | 11 | public override void WriteLine(string? message, string? category) 12 | { 13 | if (message is null) return; 14 | 15 | if (category?.Equals("ffxiv_act_plugin", StringComparison.OrdinalIgnoreCase) ?? false) 16 | Plugin.Log.Information($"[FFXIV_ACT_PLUGIN] {message}"); 17 | 18 | if (category?.Equals("machina", StringComparison.OrdinalIgnoreCase) ?? false) 19 | Plugin.Log.Information($"[MACHINA] {message}"); 20 | 21 | if (category?.Equals("debug-machina", StringComparison.OrdinalIgnoreCase) ?? false) 22 | Plugin.Log.Debug($"[MACHINA] {message}"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Target/TargetMemory70.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Target 4 | { 5 | interface ITargetMemory70 : ITargetMemory { } 6 | 7 | class TargetMemory70 : TargetMemory, ITargetMemory70 8 | { 9 | private const string targetSignature = "E8????????483BC374??488D0D"; 10 | 11 | // Offsets from the targetAddress to find the correct target type. 12 | private const int targetTargetOffset = 176; 13 | private const int focusTargetOffset = 248; 14 | private const int hoverTargetOffset = 208; 15 | 16 | public TargetMemory70(TinyIoCContainer container) 17 | : base(container, targetSignature, targetTargetOffset, focusTargetOffset, hoverTargetOffset) 18 | { } 19 | 20 | public override Version GetVersion() 21 | { 22 | return new Version(7, 0); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineActorControlCommon.cs: -------------------------------------------------------------------------------- 1 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 2 | { 3 | public enum Server_ActorControlCategory : ushort 4 | { 5 | VfxUnknown49 = 0x0031, // 49 6 | SetAnimationState = 0x003E, // 62 7 | SetModelState = 0x003F, // 63 8 | PlayActionTimeline = 0x0197, // 407 9 | EObjAnimation = 0x19D, // 413 10 | StatusUpdate = 0x01F8, // 504 11 | // Name is a guess 12 | DisplayLogMessage = 0x020F, // 527 13 | DisplayLogMessageParams = 0x0210, // 528 14 | DisplayPublicContentTextMessage = 0x0834, // 2100 15 | // Note that these names are used directly as strings in `LineFateControl` 16 | // Changing them will necessitate updating the logic in that class 17 | FateAdd = 0x0942, // 2370 18 | FateRemove = 0x0935, // 2357 19 | FateUpdate = 0x093C, // 2364 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/Ipc/IpcHandler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | using Dalamud.Plugin.Ipc; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace RainbowMage.OverlayPlugin.Handlers.Ipc; 7 | 8 | internal class IpcHandler : Handler 9 | { 10 | private ICallGateProvider Receiver { get; } 11 | private ICallGateSubscriber Sender { get; } 12 | 13 | public IpcHandler(string name, ICallGateProvider receiver, ICallGateSubscriber sender, 14 | ILogger logger, EventDispatcher eventDispatcher) : base(name, logger, eventDispatcher) 15 | { 16 | Receiver = receiver; 17 | Sender = sender; 18 | 19 | Receiver.RegisterAction(DataReceived); 20 | } 21 | 22 | protected override void Send(JObject e) => Sender.InvokeFunc(e); 23 | 24 | public override void Dispose() 25 | { 26 | Receiver.UnregisterAction(); 27 | base.Dispose(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /NotACT/README.md: -------------------------------------------------------------------------------- 1 | # Disclaimer 2 | 3 | This project implements the [ACT](https://advancedcombattracker.com/)-like environment mentioned in the main readme. 4 | 5 | It is the result of reverse engineering, analyzing dependencies of FFXIV_ACT_Plugin at runtime and decompilation output of the original `Advanced Combat Tracker.exe`. 6 | 7 | Ideally this component is supposed to be kept to a minimum and only represent the used API surface by OverlayPlugin and FFXIV_ACT_Plugin. If you can find anything in here that is not strictly needed, any PRs to reimplement or remove parts are greatly appreciated. 8 | 9 | I believe this is fine due to being mainly an API surface with almost all calculations re-implemented in LINQ (also see [Google vs Oracle](https://en.wikipedia.org/wiki/Google_LLC_v._Oracle_America,_Inc.)). Under most jurisdictions reverse engineering of software is permissible, especially for compatibility purposes. 10 | 11 | There is no copyright claim being made on the public ACT API here or on any of the implementations and behaviour of it. 12 | -------------------------------------------------------------------------------- /repo.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Author": "Usagi", 4 | "Name": "IINACT", 5 | "InternalName": "IINACT", 6 | "AssemblyVersion": "2.9.5.1", 7 | "TestingAssemblyVersion": "2.9.5.1", 8 | "Description": "A plugin to run the FFXIV_ACT_Plugin in an ACT-like environment with a heavily modified port of Overlay Plugin for modern .NET", 9 | "ApplicableVersion": "any", 10 | "RepoUrl": "https://github.com/marzent/IINACT", 11 | "Tags": [ 12 | "parsing", 13 | "plugin", 14 | "ACT" 15 | ], 16 | "DalamudApiLevel": 14, 17 | "LoadPriority": 0, 18 | "IconUrl": "https://raw.githubusercontent.com/marzent/IINACT/master/images/icon.png", 19 | "Punchline": "A parsing plugin.", 20 | "IsHide": "False", 21 | "IsTestingExclusive": "False", 22 | "DownloadLinkInstall": "https://github.com/marzent/IINACT/releases/download/v2.9.5.1/IINACT.zip", 23 | "DownloadLinkTesting": "https://github.com/marzent/IINACT/releases/download/v2.9.5.1/IINACT.zip", 24 | "DownloadLinkUpdate": "https://github.com/marzent/IINACT/releases/download/v2.9.5.1/IINACT.zip" 25 | } 26 | ] 27 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/Ipc/LegacyIpcHandler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using Dalamud.Plugin.Ipc; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace RainbowMage.OverlayPlugin.Handlers.Ipc; 6 | 7 | internal class LegacyIpcHandler : LegacyHandler 8 | { 9 | private ICallGateProvider Receiver { get; } 10 | private ICallGateSubscriber Sender { get; } 11 | 12 | public LegacyIpcHandler( 13 | string name, ICallGateProvider receiver, ICallGateSubscriber sender, 14 | ILogger logger, EventDispatcher eventDispatcher, FFXIVRepository repository) : base( 15 | name, logger, eventDispatcher, repository) 16 | { 17 | Receiver = receiver; 18 | Sender = sender; 19 | 20 | Receiver.RegisterAction(DataReceived); 21 | 22 | Start(); 23 | } 24 | 25 | protected override void Send(JObject e) => Sender.InvokeFunc(e); 26 | 27 | public override void Dispose() 28 | { 29 | Receiver.UnregisterAction(); 30 | base.Dispose(); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /.github/workflows/pr-build.yml: -------------------------------------------------------------------------------- 1 | name: IINACT build 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | env: 10 | DALAMUD_HOME: /tmp/dalamud 11 | DOTNET_NOLOGO: true 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | submodules: recursive 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v3 18 | with: 19 | dotnet-version: 9.0.x 20 | - name: Download Dalamud 21 | shell: pwsh 22 | run: | 23 | Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip 24 | Expand-Archive -Force latest.zip /tmp/dalamud 25 | - name: Build 26 | run: dotnet build -c release 27 | - name: Prepare Build Artifact 28 | shell: pwsh 29 | run: | 30 | Copy-Item "IINACT/bin/Release/win-x64/IINACT/latest.zip" -Destination "IINACT.zip" 31 | Expand-Archive -Force IINACT.zip Artifact 32 | - name: Upload IINACT 33 | uses: actions/upload-artifact@v4 34 | with: 35 | name: IINACT 36 | path: Artifact/* 37 | -------------------------------------------------------------------------------- /FetchDependencies/Costura.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.IO.Compression; 3 | 4 | namespace FetchDependencies; 5 | 6 | internal static class Costura 7 | { 8 | public static bool CheckForPlugin(string name) 9 | { 10 | return name.Contains("act"); 11 | } 12 | 13 | public static string Fix(string name) 14 | { 15 | if (name.Contains("act")) 16 | return "FFXIV_ACT_" + name.Substring(18, name.Length - 33).ToTitleCase() + ".dll"; 17 | if (name.Contains("machina.ffxiv")) 18 | return "Machina.FFXIV.dll"; 19 | return name.Substring(8, name.Length - 23).ToTitleCase() + ".dll"; 20 | } 21 | 22 | public static void Decompress(Stream stream, string destinationFileName) 23 | { 24 | using var destinationFileStream = File.Create(destinationFileName); 25 | using var decompressionStream = new DeflateStream(stream, CompressionMode.Decompress); 26 | decompressionStream.CopyTo(destinationFileStream); 27 | } 28 | 29 | private static string ToTitleCase(this string title) 30 | { 31 | return CultureInfo.CurrentCulture.TextInfo.ToTitleCase(title); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /IINACT/Network/PacketDispatcher.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable CS0169 // Field is never used 2 | namespace IINACT.Network; 3 | 4 | public unsafe struct PacketDispatcher 5 | { 6 | private void* Unknown1; 7 | private void* Unknown2; 8 | private void* NetworkModuleProxy; 9 | 10 | public uint GameRandom; 11 | public uint LastPacketRandom; 12 | public uint Key0; 13 | public uint Key1; 14 | public uint Key2; 15 | public uint Unknown_32; 16 | 17 | public static PacketDispatcher* GetInstance() 18 | { 19 | var framework = FFXIVClientStructs.FFXIV.Client.System.Framework.Framework.Instance(); 20 | if (framework == null) return null; 21 | var nmp = framework->NetworkModuleProxy; 22 | if (nmp == null) return null; 23 | var rcb = nmp->ReceiverCallback; 24 | if (rcb == null) return null; 25 | return (PacketDispatcher*)&rcb->PacketDispatcher; 26 | } 27 | 28 | public static nint GetOnReceivePacketAddress() 29 | { 30 | var vtable = FFXIVClientStructs.FFXIV.Client.Network.PacketDispatcher.StaticVirtualTablePointer; 31 | return (nint)vtable->OnReceivePacket; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /IINACT/Network/GameServerTime.cs: -------------------------------------------------------------------------------- 1 | namespace IINACT.Network; 2 | 3 | internal static class GameServerTime 4 | { 5 | public static long LastSeverTimestamp { get; private set; } 6 | 7 | private static long LastSeverTimestampTicks { get; set; } 8 | 9 | private static readonly DateTime Date1970 = DateTime.MinValue.AddYears(1969); 10 | 11 | public static DateTime LastServerTime => LastSeverTimestamp > 0 12 | ? Date1970.AddTicks((long)LastSeverTimestamp * 10_000L).ToLocalTime() 13 | : DateTime.Now; 14 | 15 | public static DateTime CurrentServerTime => LastSeverTimestamp > 0 16 | ? LastServerTime.AddMilliseconds( 17 | Environment.TickCount64 - LastSeverTimestampTicks) 18 | : DateTime.Now; 19 | 20 | internal static void SetLastServerTimestamp(ulong timestamp) 21 | { 22 | if (timestamp > 0) 23 | { 24 | LastSeverTimestamp = (long)timestamp; 25 | LastSeverTimestampTicks = Environment.TickCount64; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /IINACT/Network/SimpleBuffer.cs: -------------------------------------------------------------------------------- 1 | namespace IINACT.Network; 2 | 3 | internal class SimpleBuffer(int size) 4 | { 5 | private readonly byte[] buffer = new byte[size]; 6 | private int offset = 0; 7 | 8 | public int Size => offset; 9 | 10 | public Span Get(int start, int length) 11 | { 12 | return buffer.AsSpan()[start..(start + length)]; 13 | } 14 | 15 | public void Write(ReadOnlySpan src) 16 | { 17 | if (offset + src.Length > buffer.Length) 18 | throw new ArgumentException("Src length must be less than the remaining size of the buffer."); 19 | 20 | var dstSlice = buffer.AsSpan().Slice(offset, src.Length); 21 | src.CopyTo(dstSlice); 22 | offset += src.Length; 23 | } 24 | 25 | public void WriteNull(int count) 26 | { 27 | if (offset + count > buffer.Length) 28 | throw new ArgumentException("Src length must be less than the remaining size of the buffer."); 29 | 30 | var dstSlice = buffer.AsSpan().Slice(offset, count); 31 | for (var i = 0; i < count; i++) dstSlice[i] = 0; 32 | offset += count; 33 | } 34 | 35 | public void Clear() 36 | { 37 | offset = 0; 38 | } 39 | 40 | public ReadOnlySpan GetBuffer() 41 | { 42 | return buffer.AsSpan()[..offset]; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/ConfigCreationConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Converters; 2 | using System; 3 | 4 | namespace RainbowMage.OverlayPlugin 5 | { 6 | internal class ConfigCreationConverter : CustomCreationConverter 7 | { 8 | private TinyIoCContainer _container; 9 | 10 | public ConfigCreationConverter(TinyIoCContainer container) : base() 11 | { 12 | _container = container; 13 | } 14 | 15 | public override IOverlayConfig Create(Type objectType) 16 | { 17 | var construct = objectType.GetConstructor(new Type[] { typeof(TinyIoCContainer), typeof(string) }); 18 | if (construct == null) 19 | { 20 | construct = objectType.GetConstructor(new Type[] { typeof(string) }); 21 | if (construct == null) 22 | { 23 | throw new Exception("No valid constructor found for config type " + objectType.ToString() + "!"); 24 | } 25 | 26 | return (IOverlayConfig)construct.Invoke(new object[] { null }); 27 | } 28 | else 29 | { 30 | return (IOverlayConfig)construct.Invoke(new object[] { _container, null }); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin 4 | { 5 | public interface ILogger 6 | { 7 | void Log(LogLevel level, string message); 8 | void Log(LogLevel level, string format, params object[] args); 9 | void RegisterListener(Action listener); 10 | void ClearListener(); 11 | } 12 | 13 | public class LogEntry 14 | { 15 | public string Message { get; set; } 16 | public LogLevel Level { get; set; } 17 | public DateTime Time { get; set; } 18 | 19 | public LogEntry(LogLevel level, DateTime time, string message) 20 | { 21 | this.Message = message; 22 | this.Level = level; 23 | this.Time = time; 24 | } 25 | } 26 | 27 | public class LogEventArgs : EventArgs 28 | { 29 | public string Message { get; private set; } 30 | public LogLevel Level { get; private set; } 31 | 32 | public LogEventArgs(LogLevel level, string message) 33 | { 34 | this.Message = message; 35 | this.Level = level; 36 | } 37 | } 38 | 39 | public enum LogLevel 40 | { 41 | Trace, 42 | Debug, 43 | Info, 44 | Warning, 45 | Error 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/WebSocket/SocketHandler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | using System.Net.Sockets; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using RainbowMage.OverlayPlugin.WebSocket; 7 | 8 | namespace RainbowMage.OverlayPlugin.Handlers.WebSocket; 9 | 10 | internal class SocketHandler : Handler, ISocketHandler 11 | { 12 | private OverlaySession Session { get; } 13 | 14 | public SocketHandler( 15 | ILogger logger, EventDispatcher eventDispatcher, OverlaySession session) : base( 16 | "WSHandler", logger, eventDispatcher) 17 | { 18 | Session = session; 19 | } 20 | 21 | protected override void Send(JObject e) => Session.SendTextAsync(e.ToString(Formatting.None)); 22 | 23 | public void OnError(SocketError error) 24 | { 25 | Logger.Log(LogLevel.Error, Resources.WSMessageSendFailed, Enum.GetName(error)); 26 | Dispose(); 27 | } 28 | 29 | public void OnMessage(string message) 30 | { 31 | JObject data; 32 | 33 | try 34 | { 35 | data = JObject.Parse(message); 36 | } 37 | catch (JsonException ex) 38 | { 39 | Logger.Log(LogLevel.Error, Resources.WSInvalidDataRecv, ex, message); 40 | return; 41 | } 42 | 43 | DataReceived(data); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/WebSocket/LegacySocketHandler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System.Net.Sockets; 3 | using Newtonsoft.Json; 4 | using Newtonsoft.Json.Linq; 5 | using RainbowMage.OverlayPlugin.WebSocket; 6 | 7 | namespace RainbowMage.OverlayPlugin.Handlers.WebSocket; 8 | 9 | internal class LegacySocketHandler : LegacyHandler, ISocketHandler 10 | { 11 | private OverlaySession Session { get; } 12 | 13 | public LegacySocketHandler( 14 | ILogger logger, EventDispatcher eventDispatcher, FFXIVRepository repository, OverlaySession session) : base( 15 | "WSLegacyHandler", logger, eventDispatcher, repository) 16 | { 17 | Session = session; 18 | 19 | Start(); 20 | } 21 | 22 | protected override void Send(JObject data) => Session.SendTextAsync(data.ToString(Formatting.None)); 23 | 24 | 25 | public void OnError(SocketError error) 26 | { 27 | Logger.Log(LogLevel.Error, "Failed to send legacy WS message: {0}", error); 28 | Dispose(); 29 | } 30 | 31 | public void OnMessage(string message) 32 | { 33 | JObject data; 34 | 35 | try 36 | { 37 | data = JObject.Parse(message); 38 | } 39 | catch (JsonException ex) 40 | { 41 | Logger.Log(LogLevel.Error, Resources.WSInvalidDataRecv, ex, message); 42 | return; 43 | } 44 | 45 | DataReceived(data); 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /IINACT/Network/Packets.cs: -------------------------------------------------------------------------------- 1 | namespace IINACT.Network; 2 | 3 | #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value 4 | 5 | internal struct PacketElementHeader 6 | { 7 | public readonly uint Size; 8 | public uint SrcEntity; 9 | public uint DstEntity; 10 | public PacketType Type; 11 | public ushort Padding; 12 | } 13 | 14 | internal struct FrameHeader 15 | { 16 | public unsafe fixed byte Prefix[16]; 17 | 18 | public ulong TimeValue; 19 | public uint TotalSize; 20 | public PacketProtocol Protocol; 21 | public ushort Count; 22 | public byte Version; 23 | public CompressionType Compression; 24 | public ushort Unknown; 25 | public uint DecompressedLength; 26 | } 27 | 28 | #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value 29 | 30 | internal enum PacketType : ushort { 31 | None = 0x0, 32 | SessionInit = 0x1, 33 | Unknown2 = 0x2, 34 | Ipc = 0x3, 35 | Unknown4 = 0x4, 36 | Unknown5 = 0x5, 37 | Unknown6 = 0x6, 38 | KeepAlive = 0x7, 39 | KeepAliveResponse = 0x8, 40 | EncryptionInit = 0x9, 41 | UnknownA = 0xA, 42 | UnknownB = 0xB, 43 | } 44 | 45 | internal enum PacketProtocol : ushort { 46 | None = 0x0, 47 | Zone = 0x1, 48 | Chat = 0x2, 49 | Lobby = 0x3, 50 | } 51 | 52 | internal enum CompressionType : byte { 53 | None = 0x0, 54 | Zlib = 0x1, 55 | Oodle = 0x2, 56 | } 57 | -------------------------------------------------------------------------------- /NotACT/Datatypes/LogLineEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public delegate void LogLineEventDelegate(bool isImport, LogLineEventArgs logInfo); 4 | 5 | public delegate void LogFileChangedDelegate(bool IsImport, string NewLogFileName); 6 | 7 | public class LogLineEventArgs : EventArgs 8 | { 9 | public readonly string companionLogName; 10 | 11 | public readonly DateTime detectedTime; 12 | 13 | public readonly string detectedZone; 14 | 15 | public readonly bool inCombat; 16 | 17 | public readonly string originalLogLine; 18 | 19 | public int detectedType; 20 | public string logLine; 21 | 22 | public LogLineEventArgs( 23 | string LogLine, int DetectedType, DateTime DetectedTime, string DetectedZone, bool InCombat) 24 | { 25 | originalLogLine = LogLine; 26 | logLine = LogLine; 27 | detectedType = DetectedType; 28 | detectedTime = DetectedTime; 29 | detectedZone = DetectedZone; 30 | inCombat = InCombat; 31 | companionLogName = string.Empty; 32 | } 33 | 34 | public LogLineEventArgs( 35 | string LogLine, int DetectedType, DateTime DetectedTime, string DetectedZone, bool InCombat, 36 | string CompanionLogName) 37 | { 38 | originalLogLine = LogLine; 39 | logLine = LogLine; 40 | detectedType = DetectedType; 41 | detectedTime = DetectedTime; 42 | detectedZone = DetectedZone; 43 | inCombat = InCombat; 44 | companionLogName = CompanionLogName; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /NotACT/FormActMain.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker { 2 | partial class FormActMain { 3 | /// 4 | /// Required designer variable. 5 | /// 6 | private System.ComponentModel.IContainer components = null; 7 | 8 | /// 9 | /// Clean up any resources being used. 10 | /// 11 | /// true if managed resources should be disposed; otherwise, false. 12 | protected override void Dispose(bool disposing) { 13 | if (disposing && (components != null)) { 14 | components.Dispose(); 15 | } 16 | base.Dispose(disposing); 17 | } 18 | 19 | #region Windows Form Designer generated code 20 | 21 | /// 22 | /// Required method for Designer support - do not modify 23 | /// the contents of this method with the code editor. 24 | /// 25 | private void InitializeComponent() { 26 | this.SuspendLayout(); 27 | // 28 | // FormActMain 29 | // 30 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); 31 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 32 | this.ClientSize = new System.Drawing.Size(345, 250); 33 | this.MaximizeBox = false; 34 | this.MinimizeBox = false; 35 | this.Name = "FormActMain"; 36 | this.Text = "Super Advanced Combat Tracker"; 37 | this.ResumeLayout(false); 38 | 39 | } 40 | 41 | #endregion 42 | } 43 | } -------------------------------------------------------------------------------- /NotACT/ActGlobals.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public static partial class ActGlobals 4 | { 5 | private static ActLocalization.LocalizationStringsHelper? _trans; 6 | 7 | public static bool mainTableShowCommas = true; 8 | 9 | public static bool calcRealAvgDelay = true; 10 | 11 | internal static SortedDictionary selectiveList; 12 | 13 | public static bool longDuration = false; 14 | 15 | public static bool blockIsHit = true; 16 | 17 | public static bool restrictToAll = false; 18 | 19 | public static string charName = "YOU"; 20 | 21 | public static string eDSort = "EncDPS"; 22 | 23 | public static string mDSort = "Damage"; 24 | 25 | public static string aTSort = "Time"; 26 | 27 | public static string eDSort2 = "EncDPS"; 28 | 29 | public static string mDSort2 = "Damage"; 30 | 31 | public static string aTSort2 = "Time"; 32 | 33 | public static FormActMain oFormActMain = null!; 34 | 35 | internal static object ActionDataLock; 36 | 37 | internal static ActLocalization.LocalizationStringsHelper Trans => _trans!; 38 | 39 | public static void Init() 40 | { 41 | _trans = new ActLocalization.LocalizationStringsHelper(); 42 | selectiveList = new SortedDictionary(); 43 | ActionDataLock = new object(); 44 | } 45 | 46 | public static void Dispose() 47 | { 48 | oFormActMain.Exit(); 49 | oFormActMain.Dispose(); 50 | oFormActMain = null!; 51 | _trans = null!; 52 | selectiveList.Clear(); 53 | selectiveList = null!; 54 | ActionDataLock = null!; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/IOverlay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RainbowMage.OverlayPlugin 4 | { 5 | /// 6 | /// オーバーレイに必要な機能を定義します。 7 | /// 8 | /// 9 | /// アドオンを作成する場合はこのインターフェイスを実装するのではなく、 10 | /// 11 | public interface IOverlay : IDisposable 12 | { 13 | /// 14 | /// ユーザーが設定したオーバーレイの名前を取得します。 15 | /// 16 | string Name { get; } 17 | 18 | IOverlayConfig Config { get; set; } 19 | 20 | bool Visible { get; set; } 21 | 22 | IntPtr Handle { get; } 23 | 24 | /// 25 | /// オーバーレイの更新を開始します。 26 | /// 27 | void Start(); 28 | 29 | /// 30 | /// オーバーレイの更新を停止します。 31 | /// 32 | void Stop(); 33 | 34 | void Reload(); 35 | 36 | /// 37 | /// 指定した URL を表示します。 38 | /// 39 | /// 表示する URL。 40 | void Navigate(string url); 41 | 42 | /// 43 | /// オーバーレイの位置と大きさを保存します。 44 | /// 45 | void SavePositionAndSize(); 46 | 47 | void ExecuteScript(string script); 48 | 49 | /// 50 | /// オーバーレイにメッセージを送信します。 51 | /// 52 | /// メッセージの内容。 53 | void SendMessage(string message); 54 | 55 | /// 56 | /// A message from javascript for the overlay plugin to consume. 57 | /// 58 | /// A string message created by the plugin javascript. 59 | void OverlayMessage(string message); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/packages.lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "dependencies": { 4 | "net10.0-windows7.0": { 5 | "Microsoft.CSharp": { 6 | "type": "Direct", 7 | "requested": "[4.7.0, )", 8 | "resolved": "4.7.0", 9 | "contentHash": "pTj+D3uJWyN3My70i2Hqo+OXixq3Os2D1nJ2x92FFo6sk8fYS1m1WLNTs0Dc1uPaViH0YvEEwvzddQ7y4rhXmA==" 10 | }, 11 | "Microsoft.Extensions.ObjectPool": { 12 | "type": "Direct", 13 | "requested": "[10.0.1, )", 14 | "resolved": "10.0.1", 15 | "contentHash": "HqAEbtoAhgvH53c54IV5e4vQ60PYvl7Z/WIHsbet+UGGE7n+7dwVNXw1mb9LZlWbsxnupCevvtgIne5P//ZKpQ==" 16 | }, 17 | "NetCoreServer": { 18 | "type": "Direct", 19 | "requested": "[8.0.7, )", 20 | "resolved": "8.0.7", 21 | "contentHash": "BhNiJ6EVxKS1eKqlHINy7BruLDc+xCH59oZxu7d2Hve8CzFki69PumVlf6vyAQvIVfxbZiHw/FI3UHNnMajuVg==" 22 | }, 23 | "System.Data.DataSetExtensions": { 24 | "type": "Direct", 25 | "requested": "[4.5.0, )", 26 | "resolved": "4.5.0", 27 | "contentHash": "221clPs1445HkTBZPL+K9sDBdJRB8UN8rgjO3ztB0CQ26z//fmJXtlsr6whGatscsKGBrhJl5bwJuKSA8mwFOw==" 28 | }, 29 | "Advanced Combat Tracker": { 30 | "type": "Project" 31 | }, 32 | "machina": { 33 | "type": "Project" 34 | }, 35 | "machina.ffxiv": { 36 | "type": "Project", 37 | "dependencies": { 38 | "Machina": "[2.3.1.3, )" 39 | } 40 | }, 41 | "overlayplugin.common": { 42 | "type": "Project", 43 | "dependencies": { 44 | "Advanced Combat Tracker": "[6.9.0, )", 45 | "Microsoft.CSharp": "[4.7.0, )", 46 | "System.Data.DataSetExtensions": "[4.5.0, )" 47 | } 48 | } 49 | }, 50 | "net10.0-windows7.0/win-x64": {} 51 | } 52 | } -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/Ipc/IHandlerFactory.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Plugin; 2 | using Dalamud.Plugin.Ipc; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace RainbowMage.OverlayPlugin.Handlers.Ipc; 6 | 7 | internal interface IHandlerFactory 8 | { 9 | IHandler Create(string name, TinyIoCContainer container) 10 | { 11 | var pluginInterface = container.Resolve(); 12 | var receiver = pluginInterface.GetIpcProvider($"IINACT.IpcProvider.{name}"); 13 | var sender = pluginInterface.GetIpcSubscriber(name); 14 | var logger = container.Resolve(); 15 | var dispatcher = container.Resolve(); 16 | var repository = container.Resolve(); 17 | return CreateHandler(name, receiver, sender, logger, dispatcher, repository); 18 | } 19 | 20 | protected IHandler CreateHandler( 21 | string name, ICallGateProvider receiver, ICallGateSubscriber sender, 22 | ILogger logger, EventDispatcher eventDispatcher, FFXIVRepository repository); 23 | } 24 | 25 | internal class HandlerFactory : IHandlerFactory 26 | { 27 | public IHandler CreateHandler( 28 | string name, ICallGateProvider receiver, ICallGateSubscriber sender, 29 | ILogger logger, EventDispatcher eventDispatcher, FFXIVRepository repository) => 30 | new IpcHandler(name, receiver, sender, logger, eventDispatcher); 31 | } 32 | 33 | internal class LegacyHandlerFactory : IHandlerFactory 34 | { 35 | public IHandler CreateHandler( 36 | string name, ICallGateProvider receiver, ICallGateSubscriber sender, 37 | ILogger logger, EventDispatcher eventDispatcher, FFXIVRepository repository) => 38 | new LegacyIpcHandler(name, receiver, sender, logger, eventDispatcher, repository); 39 | } 40 | -------------------------------------------------------------------------------- /IINACT/Configuration.cs: -------------------------------------------------------------------------------- 1 | using Dalamud.Configuration; 2 | using Dalamud.Plugin; 3 | using Newtonsoft.Json; 4 | 5 | namespace IINACT; 6 | 7 | [Serializable] 8 | public class Configuration : IPluginConfiguration 9 | { 10 | [JsonIgnore] 11 | public string DefaultLogFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "IINACT"); 12 | private string? logFilePath; 13 | 14 | [JsonIgnore] 15 | private IDalamudPluginInterface? PluginInterface { get; set; } 16 | 17 | public int ParseFilterMode { get; set; } 18 | 19 | public bool DisableDamageShield { get; set; } 20 | 21 | public bool DisableCombinePets { get; set; } 22 | 23 | public bool DisablePvp { get; set; } 24 | 25 | public bool SimulateIndividualDoTCrits { get; set; } 26 | 27 | public bool ShowRealDoTTicks { get; set; } 28 | 29 | public bool ShowDebug { get; set; } 30 | 31 | public string LogFilePath 32 | { 33 | get => Directory.Exists(logFilePath) ? logFilePath : DefaultLogFilePath; 34 | set => logFilePath = value; 35 | } 36 | 37 | public bool WriteLogFile 38 | { 39 | get => Advanced_Combat_Tracker.ActGlobals.oFormActMain.WriteLogFile; 40 | set => Advanced_Combat_Tracker.ActGlobals.oFormActMain.WriteLogFile = value; 41 | } 42 | 43 | public bool DisableWritingPvpLogFile 44 | { 45 | get => Advanced_Combat_Tracker.ActGlobals.oFormActMain.DisableWritingPvpLogFile; 46 | set => Advanced_Combat_Tracker.ActGlobals.oFormActMain.DisableWritingPvpLogFile = value; 47 | } 48 | 49 | public int Version { get; set; } = 1; 50 | 51 | public string? SelectedOverlay { get; set; } 52 | 53 | public void Initialize(IDalamudPluginInterface pluginInterface) 54 | { 55 | PluginInterface = pluginInterface; 56 | } 57 | 58 | public void Save() 59 | { 60 | PluginInterface?.SavePluginConfig(this); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineNpcYell.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 3 | 4 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 5 | { 6 | class LineNpcYell : LineBaseCustom< 7 | Server_MessageHeader_Global, LineNpcYell.NpcYell_v655, 8 | Server_MessageHeader_CN, LineNpcYell.NpcYell_v655, 9 | Server_MessageHeader_KR, LineNpcYell.NpcYell_v655> 10 | { 11 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 12 | internal unsafe struct NpcYell_v655 : IPacketStruct 13 | { 14 | // 00|2024-02-22T22:35:03.0000000-05:00|0044|Shanoa|Meow!♪|1d173e4a0eacfd95 15 | // 6.5.5 packet data (minus header): 16 | // 8A6B0140 00000000 0624 0000 D624 00000000 00000000 00000000 00000000 0000 17 | // AAAAAAAA BBBBBBBB CCCC DDDD EEEE FFFFFFFF GGGGGGGG HHHHHHHH IIIIIIII JJJJ 18 | // 0x0 0x4 0x8 0xA 0xC 19 | // Actor ID NameID YellID 20 | 21 | public const int structSize = 32; 22 | [FieldOffset(0x0)] 23 | public uint actorID; 24 | [FieldOffset(0x8)] 25 | public ushort nameID; 26 | [FieldOffset(0xC)] 27 | public ushort yellID; 28 | 29 | public string ToString(long epoch, uint ActorID) 30 | { 31 | return 32 | $"{actorID:X8}|" + 33 | $"{nameID:X4}|" + 34 | $"{yellID:X4}"; 35 | } 36 | } 37 | 38 | public const uint LogFileLineID = 266; 39 | public const string logLineName = "NpcYell"; 40 | public const string MachinaPacketName = "NpcYell"; 41 | 42 | public LineNpcYell(TinyIoCContainer container) 43 | : base(container, LogFileLineID, logLineName, MachinaPacketName) { } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/WebSocket/OverlaySession.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System.Net.Sockets; 4 | using System.Text; 5 | using NetCoreServer; 6 | using RainbowMage.OverlayPlugin.Handlers.WebSocket; 7 | 8 | namespace RainbowMage.OverlayPlugin.WebSocket; 9 | 10 | internal class OverlaySession : WsSession 11 | { 12 | private EventDispatcher Dispatcher { get; } 13 | private FFXIVRepository Repository { get; } 14 | private ILogger Logger { get; } 15 | private ISocketHandler? Handler { get; set; } 16 | 17 | public OverlaySession(WsServer server, TinyIoCContainer container) : base(server) 18 | { 19 | Dispatcher = container.Resolve(); 20 | Repository = container.Resolve(); 21 | Logger = container.Resolve(); 22 | } 23 | 24 | public override void OnWsConnected(HttpRequest request) 25 | { 26 | Logger.Log(LogLevel.Debug, $"Overlay WebSocket session with Id {Id} connected!"); 27 | 28 | switch (request.Url) 29 | { 30 | case "/ws": 31 | Handler = new SocketHandler(Logger, Dispatcher, this); 32 | break; 33 | case "/MiniParse": 34 | case "/BeforeLogLineRead": 35 | Handler = new LegacySocketHandler(Logger, Dispatcher, Repository, this); 36 | break; 37 | } 38 | } 39 | 40 | public override void OnWsDisconnected() 41 | { 42 | Logger.Log(LogLevel.Debug, $"Overlay WebSocket session with Id {Id} disconnected!"); 43 | Handler?.Dispose(); 44 | } 45 | 46 | public override void OnWsReceived(byte[] buffer, long offset, long size) 47 | { 48 | var message = Encoding.UTF8.GetString(buffer, (int)offset, (int)size); 49 | #if DEBUG 50 | Logger.Log(LogLevel.Trace, $"Overlay WebSocket {Id} received message: {message}"); 51 | #endif 52 | Handler?.OnMessage(message); 53 | } 54 | 55 | protected override void OnError(SocketError error) 56 | { 57 | Handler?.OnError(error); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Integration/TriggIntegration.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | 3 | namespace RainbowMage.OverlayPlugin 4 | { 5 | internal class TriggIntegration 6 | { 7 | private PluginMain _plugin; 8 | 9 | public delegate void CustomCallbackDelegate(object o, string param); 10 | 11 | private object GetPluginData() 12 | { 13 | return null; 14 | } 15 | 16 | public TriggIntegration(TinyIoCContainer container) 17 | { 18 | var logger = container.Resolve(); 19 | _plugin = container.Resolve(); 20 | } 21 | 22 | public void SendOverlayMessage(object _, string msg) 23 | { 24 | var pos = msg.IndexOf('|'); 25 | if (pos < 1) return; 26 | 27 | var overlayName = msg[..pos]; 28 | msg = msg[(pos + 1)..]; 29 | 30 | foreach (var overlay in _plugin.Overlays) 31 | { 32 | if (overlay.Name == overlayName) 33 | { 34 | ((IEventReceiver)overlay).HandleEvent(JObject.FromObject(new 35 | { 36 | type = "Triggernometry", 37 | message = msg 38 | })); 39 | break; 40 | } 41 | } 42 | } 43 | 44 | public void HideOverlay(object _, string msg) 45 | { 46 | foreach (var overlay in _plugin.Overlays) 47 | { 48 | if (overlay.Name == msg) 49 | { 50 | overlay.Config.IsVisible = false; 51 | break; 52 | } 53 | } 54 | } 55 | 56 | public void ShowOverlay(object _, string msg) 57 | { 58 | foreach (var overlay in _plugin.Overlays) 59 | { 60 | if (overlay.Name == msg) 61 | { 62 | overlay.Config.IsVisible = true; 63 | break; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineRSV.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using RainbowMage.OverlayPlugin.MemoryProcessors; 4 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 5 | 6 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 7 | { 8 | class LineRSV : LineBaseCustom< 9 | Server_MessageHeader_Global, LineRSV.RSV_v62, 10 | Server_MessageHeader_CN, LineRSV.RSV_v62, 11 | Server_MessageHeader_KR, LineRSV.RSV_v62> 12 | { 13 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 14 | internal unsafe struct RSV_v62 : IPacketStruct 15 | { 16 | public const int structSize = 1080; 17 | public const int keySize = 0x30; 18 | public const int valueSize = 0x404; 19 | [FieldOffset(0x0)] 20 | public int valueByteCount; 21 | [FieldOffset(0x4)] 22 | public fixed byte key[keySize]; 23 | [FieldOffset(0x34)] 24 | public fixed byte value[valueSize]; 25 | 26 | public string ToString(long epoch, uint ActorID) 27 | { 28 | fixed (byte* key = this.key) fixed (byte* value = this.value) 29 | { 30 | int valSize = Math.Min(valueByteCount, valueSize); 31 | return 32 | $"{ffxiv.GetLocaleString()}|" + 33 | $"{valueByteCount:X8}|" + 34 | $"{FFXIVMemory.GetStringFromBytes(key, keySize).Replace("\r", "\\r").Replace("\n", "\\n")}|" + 35 | $"{FFXIVMemory.GetStringFromBytes(value, valSize, valSize).Replace("\r", "\\r").Replace("\n", "\\n")}"; 36 | } 37 | } 38 | } 39 | 40 | public const uint LogFileLineID = 262; 41 | public const string logLineName = "RSVData"; 42 | public const string MachinaPacketName = "RSVData"; 43 | 44 | public LineRSV(TinyIoCContainer container) 45 | : base(container, LogFileLineID, logLineName, MachinaPacketName) { } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineActorCastExtra.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 6 | { 7 | class LineActorCastExtra : LineBaseSubMachina 8 | { 9 | public const uint LogFileLineID = 263; 10 | public const string LogLineName = "ActorCastExtra"; 11 | public const string MachinaPacketName = "ActorCast"; 12 | 13 | internal class ActorCastExtraPacket : MachinaPacketWrapper 14 | { 15 | public override string ToString(long epoch, uint ActorID) 16 | { 17 | UInt16 abilityId = Get("ActionID"); 18 | 19 | // for x/y/x, subtract 7FFF then divide by (2^15 - 1) / 100 20 | float x = FFXIVRepository.ConvertUInt16Coordinate(Get("PosX")); 21 | // In-game uses Y as elevation and Z as north-south, but ACT convention is to use 22 | // Z as elevation and Y as north-south. 23 | float y = FFXIVRepository.ConvertUInt16Coordinate(Get("PosZ")); 24 | float z = FFXIVRepository.ConvertUInt16Coordinate(Get("PosY")); 25 | // for rotation, the packet uses '0' as north, and each increment is 1/65536 of a CCW turn, while 26 | // in-game uses 0=south, pi/2=west, +/-pi=north 27 | // Machina thinks this is a float but that appears to be incorrect, so we have to reinterpret as 28 | // a UInt16 29 | double h = FFXIVRepository.ConvertHeading(FFXIVRepository.InterpretFloatAsUInt16(Get("Rotation"))); 30 | 31 | return string.Format(CultureInfo.InvariantCulture, 32 | "{0:X8}|{1:X4}|{2:F3}|{3:F3}|{4:F3}|{5:F3}", 33 | ActorID, abilityId, x, y, z, h); 34 | } 35 | } 36 | public LineActorCastExtra(TinyIoCContainer container) 37 | : base(container, LogFileLineID, LogLineName, MachinaPacketName) { } 38 | } 39 | } -------------------------------------------------------------------------------- /OverlayPlugin.Common/OverlayPlugin.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net10.0-windows 4 | true 5 | Library 6 | RainbowMage.OverlayPlugin 7 | true 8 | true 9 | win-x64 10 | 11 11 | false 12 | false 13 | true 14 | 15 | 16 | 17 | $(appdata)\XIVLauncher\addon\Hooks\dev\ 18 | true 19 | 20 | 21 | 22 | $(DALAMUD_HOME)/ 23 | 24 | 25 | 26 | $(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/ 27 | 28 | 29 | 30 | 31 | $(DalamudLibPath)Newtonsoft.Json.dll 32 | false 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Logger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dalamud.Plugin.Services; 3 | 4 | namespace RainbowMage.OverlayPlugin 5 | { 6 | /// 7 | /// ログを記録する機能を提供するクラス。 8 | /// 9 | public class Logger : ILogger 10 | { 11 | public Logger(IPluginLog pluginLog) 12 | { 13 | PluginLog = pluginLog; 14 | } 15 | 16 | private IPluginLog PluginLog { get; } 17 | 18 | /// 19 | /// メッセージを指定してログを記録します。 20 | /// 21 | /// ログレベル。 22 | /// メッセージ。 23 | public void Log(LogLevel level, string message) 24 | { 25 | switch (level) 26 | { 27 | case LogLevel.Trace: 28 | PluginLog.Verbose($"[OverlayPlugin] {message}"); 29 | break; 30 | case LogLevel.Debug: 31 | PluginLog.Debug($"[OverlayPlugin] {message}"); 32 | break; 33 | case LogLevel.Info: 34 | PluginLog.Information($"[OverlayPlugin] {message}"); 35 | break; 36 | case LogLevel.Warning: 37 | PluginLog.Warning($"[OverlayPlugin] {message}"); 38 | break; 39 | case LogLevel.Error: 40 | PluginLog.Error($"[OverlayPlugin] {message}"); 41 | break; 42 | default: 43 | throw new ArgumentOutOfRangeException(nameof(level), level, null); 44 | } 45 | } 46 | 47 | /// 48 | /// 書式指定子を用いたメッセージを指定してログを記録します。 49 | /// 50 | /// ログレベル。 51 | /// 複合書式指定文字列。 52 | /// 書式指定するオブジェクト。 53 | public void Log(LogLevel level, string format, params object[] args) => Log(level, string.Format(format, args)); 54 | 55 | public void RegisterListener(Action listener) { } 56 | 57 | public void ClearListener() { } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /NotACT/Datatypes/HistoryRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public class HistoryRecord : IComparable, IEquatable 4 | { 5 | public HistoryRecord( 6 | int Type, DateTime StartTime, DateTime EndTime, string Label, string CharName, string FolderHint = "") 7 | { 8 | this.Type = Type; 9 | this.StartTime = StartTime; 10 | this.EndTime = EndTime; 11 | this.Label = Label; 12 | this.CharName = CharName; 13 | this.FolderHint = FolderHint; 14 | } 15 | 16 | public TimeSpan Duration => EndTime - StartTime; 17 | 18 | public string CharName { get; set; } 19 | 20 | public DateTime StartTime { get; set; } 21 | 22 | public DateTime EndTime { get; set; } 23 | 24 | public int Type { get; set; } 25 | 26 | public string Label { get; set; } 27 | 28 | public string FolderHint { get; set; } 29 | 30 | public int CompareTo(HistoryRecord? other) 31 | { 32 | return StartTime.CompareTo(other!.StartTime); 33 | } 34 | 35 | public bool Equals(HistoryRecord? other) 36 | { 37 | return StartTime.Equals(other!.StartTime); 38 | } 39 | 40 | public override bool Equals(object? obj) 41 | { 42 | if (obj == DBNull.Value) return false; 43 | 44 | if (obj == null) return false; 45 | 46 | var historyRecord = (HistoryRecord)obj; 47 | return StartTime.Equals(historyRecord.StartTime); 48 | } 49 | 50 | public override string ToString() 51 | { 52 | var duration = EndTime - StartTime; 53 | var durationString = duration.ToString(duration.TotalHours >= 1 ? @"hh\:mm\:ss" : @"mm\:ss"); 54 | var folderHint = ActGlobals.oFormActMain.LogFilePath.Equals(FolderHint, StringComparison.OrdinalIgnoreCase) 55 | ? string.Empty 56 | : FolderHint.ToLower(); 57 | var labelPrefix = Type == 1 ? " " : string.Empty; 58 | return $"{labelPrefix}{Label} - {StartTime:MM/dd/yyyy h:mm:ss tt} [{durationString}] {folderHint}"; 59 | } 60 | 61 | 62 | public override int GetHashCode() => ToString().GetHashCode(); 63 | } 64 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/PacketHelper/Common.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | 3 | namespace RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper 4 | { 5 | // This is a copy/paste from Machina, and hasn't changed basically ever. 6 | // We don't pull this reference directly from Machina to avoid linking and runtime DLL loading issues due to version differences. 7 | // While this has never changed in the past, we shouldn't assume it won't change in the future, so allow for regional differences 8 | // if it changes at some point. 9 | [StructLayout(LayoutKind.Explicit)] 10 | public struct Server_MessageHeader 11 | { 12 | [FieldOffset(0)] 13 | public uint MessageLength; 14 | [FieldOffset(4)] 15 | public uint ActorID; 16 | [FieldOffset(8)] 17 | public uint LoginUserID; 18 | [FieldOffset(12)] 19 | public uint Unknown1; 20 | [FieldOffset(16)] 21 | public ushort Unknown2; 22 | [FieldOffset(18)] 23 | public ushort MessageType; 24 | [FieldOffset(20)] 25 | public uint Unknown3; 26 | [FieldOffset(24)] 27 | public uint Seconds; 28 | [FieldOffset(28)] 29 | public uint Unknown4; 30 | } 31 | 32 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 33 | public struct Server_MessageHeader_Global : IHeaderStruct 34 | { 35 | public Server_MessageHeader header; 36 | 37 | public uint ActorID => header.ActorID; 38 | 39 | public uint Opcode => header.MessageType; 40 | } 41 | 42 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 43 | public struct Server_MessageHeader_CN : IHeaderStruct 44 | { 45 | public Server_MessageHeader header; 46 | 47 | public uint ActorID => header.ActorID; 48 | 49 | public uint Opcode => header.MessageType; 50 | } 51 | 52 | [StructLayout(LayoutKind.Sequential, Pack = 1)] 53 | public struct Server_MessageHeader_KR : IHeaderStruct 54 | { 55 | public Server_MessageHeader header; 56 | 57 | public uint ActorID => header.ActorID; 58 | 59 | public uint Opcode => header.MessageType; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /NotACT/Datatypes/ZoneData.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public class ZoneData : IComparable 4 | { 5 | public ZoneData(DateTime Start, string ZoneName, bool PopulateAll, bool FullSelective, bool IgnoreEnemies) 6 | { 7 | StartTime = Start; 8 | this.ZoneName = ZoneName; 9 | Items = new List 10 | { 11 | FullSelective 12 | ? new EncounterData(ActGlobals.charName, ActGlobals.Trans["mergedEncounterTerm-all"], IgnoreEnemies, 13 | this) 14 | : new EncounterData(ActGlobals.charName, ActGlobals.Trans["mergedEncounterTerm-all"], this) 15 | }; 16 | this.PopulateAll = PopulateAll; 17 | } 18 | 19 | public bool PopulateAll { get; set; } 20 | 21 | public DateTime StartTime { get; set; } 22 | 23 | public string ZoneName { get; set; } 24 | 25 | public List Items { get; set; } 26 | 27 | public EncounterData ActiveEncounter { get; set; } 28 | 29 | public Dictionary Tags { get; set; } = new(); 30 | 31 | public int CompareTo(ZoneData? other) => StartTime.CompareTo(other?.StartTime); 32 | 33 | public void AddCombatAction(MasterSwing action) 34 | { 35 | if (PopulateAll) 36 | { 37 | if (!Items[0].Active) 38 | { 39 | Items[0].StartTimes.Add(action.Time); 40 | Items[0].Active = true; 41 | } 42 | 43 | Items[0].AddCombatAction(action); 44 | } 45 | 46 | if (!Items[^1].Active) 47 | { 48 | Items[^1].StartTimes.Add(action.Time); 49 | Items[^1].Active = true; 50 | } 51 | 52 | Items[^1].AddCombatAction(action); 53 | } 54 | 55 | public override string ToString() 56 | { 57 | return ZoneName == ActGlobals.Trans["zoneDataTerm-import"] 58 | ? string.Format(ActGlobals.Trans["zoneDataTerm-importMerge"], Items.Count) 59 | : !PopulateAll 60 | ? string.Format("{0} - [{2}] {1}", ZoneName, StartTime.ToLongTimeString(), Items.Count) 61 | : string.Format("{0} - [{2}] {1}", ZoneName, StartTime.ToLongTimeString(), Items.Count - 1); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /FetchDependencies/FetchDependencies.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | false 6 | true 7 | net10.0-windows 8 | true 9 | enable 10 | enable 11 | x64 12 | win-x64 13 | 11 14 | 15 | 16 | 17 | $(appdata)\XIVLauncher\addon\Hooks\dev\ 18 | true 19 | 20 | 21 | 22 | $(DALAMUD_HOME)/ 23 | 24 | 25 | 26 | $(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/ 27 | 28 | 29 | 30 | 31 | $(DalamudLibPath)Mono.Cecil.dll 32 | false 33 | 34 | 35 | $(DalamudLibPath)Mono.Cecil.Mdb.dll 36 | false 37 | 38 | 39 | $(DalamudLibPath)Mono.Cecil.Pdb.dll 40 | false 41 | 42 | 43 | $(DalamudLibPath)Mono.Cecil.Rocks.dll 44 | false 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Aggro/AggroMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Aggro 6 | { 7 | public interface IAggroMemory : IVersionedMemory 8 | { 9 | List GetAggroList(List combatantList); 10 | } 11 | 12 | public class AggroMemoryManager : IAggroMemory 13 | { 14 | private readonly TinyIoCContainer container; 15 | private readonly FFXIVRepository repository; 16 | private IAggroMemory memory = null; 17 | 18 | public AggroMemoryManager(TinyIoCContainer container) 19 | { 20 | this.container = container; 21 | container.Register(); 22 | repository = container.Resolve(); 23 | 24 | var memory = container.Resolve(); 25 | memory.RegisterOnProcessChangeHandler(FindMemory); 26 | } 27 | 28 | private void FindMemory(object sender, Process p) 29 | { 30 | memory = null; 31 | if (p == null) 32 | { 33 | return; 34 | } 35 | 36 | ScanPointers(); 37 | } 38 | 39 | public void ScanPointers() 40 | { 41 | List candidates = new List(); 42 | candidates.Add(container.Resolve()); 43 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 44 | } 45 | 46 | public bool IsValid() 47 | { 48 | if (memory == null || !memory.IsValid()) 49 | { 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | 56 | Version IVersionedMemory.GetVersion() 57 | { 58 | if (!IsValid()) 59 | return null; 60 | return memory.GetVersion(); 61 | } 62 | 63 | public List GetAggroList(List combatantList) 64 | { 65 | if (!IsValid()) 66 | { 67 | return null; 68 | } 69 | 70 | return memory.GetAggroList(combatantList); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Enmity/EnmityMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Enmity 6 | { 7 | public interface IEnmityMemory : IVersionedMemory 8 | { 9 | List GetEnmityEntryList(List combatantList); 10 | } 11 | 12 | public class EnmityMemoryManager : IEnmityMemory 13 | { 14 | private readonly TinyIoCContainer container; 15 | private readonly FFXIVRepository repository; 16 | private IEnmityMemory memory = null; 17 | 18 | public EnmityMemoryManager(TinyIoCContainer container) 19 | { 20 | this.container = container; 21 | container.Register(); 22 | repository = container.Resolve(); 23 | 24 | var memory = container.Resolve(); 25 | memory.RegisterOnProcessChangeHandler(FindMemory); 26 | } 27 | 28 | private void FindMemory(object sender, Process p) 29 | { 30 | memory = null; 31 | if (p == null) 32 | { 33 | return; 34 | } 35 | 36 | ScanPointers(); 37 | } 38 | 39 | public void ScanPointers() 40 | { 41 | List candidates = new List(); 42 | candidates.Add(container.Resolve()); 43 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 44 | } 45 | 46 | public bool IsValid() 47 | { 48 | if (memory == null || !memory.IsValid()) 49 | { 50 | return false; 51 | } 52 | 53 | return true; 54 | } 55 | 56 | Version IVersionedMemory.GetVersion() 57 | { 58 | if (!IsValid()) 59 | return null; 60 | return memory.GetVersion(); 61 | } 62 | 63 | public List GetEnmityEntryList(List combatantList) 64 | { 65 | if (!IsValid()) 66 | { 67 | return null; 68 | } 69 | 70 | return memory.GetEnmityEntryList(combatantList); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/Ipc/IpcHandlerController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | 5 | namespace RainbowMage.OverlayPlugin.Handlers.Ipc; 6 | 7 | public class IpcHandlerController : IDisposable 8 | { 9 | public IpcHandlerController(TinyIoCContainer container) 10 | { 11 | Container = container; 12 | Logger = container.Resolve(); 13 | Handlers = new ConcurrentDictionary(); 14 | HandlerFactory = new HandlerFactory(); 15 | LegacyHandlerFactory = new LegacyHandlerFactory(); 16 | } 17 | 18 | private TinyIoCContainer Container { get; } 19 | private ILogger Logger { get; } 20 | private ConcurrentDictionary Handlers { get; } 21 | private IHandlerFactory HandlerFactory { get; } 22 | private IHandlerFactory LegacyHandlerFactory { get; } 23 | 24 | public bool CreateSubscriber(string name) => CreateSubscriber(name, HandlerFactory); 25 | public bool CreateLegacySubscriber(string name) => CreateSubscriber(name, LegacyHandlerFactory); 26 | 27 | private bool CreateSubscriber(string name, IHandlerFactory handlerFactory) 28 | { 29 | try 30 | { 31 | var handler = handlerFactory.Create(name, Container); 32 | if (Handlers.TryAdd(name, handler)) 33 | { 34 | Logger.Log(LogLevel.Debug, $"Successfully added IPC handler {name}"); 35 | return true; 36 | } 37 | Logger.Log(LogLevel.Error, $"Failed adding already existing IPC handler {name}"); 38 | handler.Dispose(); 39 | return false; 40 | } 41 | catch (Exception ex) 42 | { 43 | Logger.Log(LogLevel.Error, $"Failed creating IPC handler {name}: {ex}"); 44 | return false; 45 | } 46 | } 47 | 48 | public bool Unsubscribe(string name) 49 | { 50 | if (!Handlers.Remove(name, out var handler)) 51 | { 52 | Logger.Log(LogLevel.Warning, $"Cannot unsubscribe from non-existing IPC handler {name}"); 53 | return false; 54 | } 55 | handler.Dispose(); 56 | return true; 57 | } 58 | 59 | public void Dispose() 60 | { 61 | foreach (var (_, handler) in Handlers) handler.Dispose(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Party/PartyMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Party 6 | { 7 | public interface IPartyMemory : IVersionedMemory 8 | { 9 | PartyListsStruct GetPartyLists(); 10 | } 11 | 12 | class PartyMemoryManager : IPartyMemory 13 | { 14 | private readonly TinyIoCContainer container; 15 | private readonly FFXIVRepository repository; 16 | private IPartyMemory memory = null; 17 | 18 | public PartyMemoryManager(TinyIoCContainer container) 19 | { 20 | this.container = container; 21 | container.Register(); 22 | container.Register(); 23 | repository = container.Resolve(); 24 | 25 | var memory = container.Resolve(); 26 | memory.RegisterOnProcessChangeHandler(FindMemory); 27 | } 28 | 29 | private void FindMemory(object sender, Process p) 30 | { 31 | memory = null; 32 | if (p == null) 33 | { 34 | return; 35 | } 36 | ScanPointers(); 37 | } 38 | 39 | public void ScanPointers() 40 | { 41 | List candidates = new List(); 42 | candidates.Add(container.Resolve()); 43 | candidates.Add(container.Resolve()); 44 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 45 | } 46 | 47 | public bool IsValid() 48 | { 49 | if (memory == null || !memory.IsValid()) 50 | { 51 | return false; 52 | } 53 | return true; 54 | } 55 | 56 | Version IVersionedMemory.GetVersion() 57 | { 58 | if (!IsValid()) 59 | return null; 60 | return memory.GetVersion(); 61 | } 62 | 63 | public PartyListsStruct GetPartyLists() 64 | { 65 | if (!IsValid()) 66 | { 67 | return new PartyListsStruct(); 68 | } 69 | return memory.GetPartyLists(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/InCombat/InCombatMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.InCombat 6 | { 7 | public interface IInCombatMemory : IVersionedMemory 8 | { 9 | bool GetInCombat(); 10 | } 11 | 12 | class InCombatMemoryManager : IInCombatMemory 13 | { 14 | private readonly TinyIoCContainer container; 15 | private readonly FFXIVRepository repository; 16 | private IInCombatMemory memory = null; 17 | 18 | public InCombatMemoryManager(TinyIoCContainer container) 19 | { 20 | this.container = container; 21 | container.Register(); 22 | container.Register(); 23 | repository = container.Resolve(); 24 | 25 | var memory = container.Resolve(); 26 | memory.RegisterOnProcessChangeHandler(FindMemory); 27 | } 28 | 29 | private void FindMemory(object sender, Process p) 30 | { 31 | memory = null; 32 | if (p == null) 33 | { 34 | return; 35 | } 36 | 37 | ScanPointers(); 38 | } 39 | 40 | public void ScanPointers() 41 | { 42 | List candidates = new List(); 43 | candidates.Add(container.Resolve()); 44 | candidates.Add(container.Resolve()); 45 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 46 | } 47 | 48 | public bool IsValid() 49 | { 50 | if (memory == null || !memory.IsValid()) 51 | { 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | Version IVersionedMemory.GetVersion() 59 | { 60 | if (!IsValid()) 61 | return null; 62 | return memory.GetVersion(); 63 | } 64 | 65 | public bool GetInCombat() 66 | { 67 | if (!IsValid()) 68 | { 69 | return false; 70 | } 71 | 72 | return memory.GetInCombat(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /NotACT/NotACT.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net10.0-windows 5 | true 6 | enable 7 | true 8 | enable 9 | Advanced Combat Tracker 10 | Advanced_Combat_Tracker 11 | x64 12 | win-x64 13 | 6.9 14 | 11 15 | false 16 | false 17 | true 18 | 1701;1702;CS8618;IDE1006 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | $(appdata)\XIVLauncher\addon\Hooks\dev\ 27 | true 28 | 29 | 30 | 31 | $(DALAMUD_HOME)/ 32 | 33 | 34 | 35 | $(HOME)/Library/Application Support/XIV on Mac/dalamud/Hooks/dev/ 36 | 37 | 38 | 39 | 40 | $(DalamudLibPath)Dalamud.dll 41 | false 42 | 43 | 44 | 45 | 46 | 47 | ..\external_dependencies\FFXIV_ACT_Plugin.dll 48 | 49 | 50 | ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Logfile.dll 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/EnmityHud/EnmityHudMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.EnmityHud 6 | { 7 | public interface IEnmityHudMemory : IVersionedMemory 8 | { 9 | List GetEnmityHudEntries(); 10 | } 11 | 12 | public class EnmityHudMemoryManager : IEnmityHudMemory 13 | { 14 | private readonly TinyIoCContainer container; 15 | private readonly FFXIVRepository repository; 16 | private IEnmityHudMemory memory = null; 17 | 18 | public EnmityHudMemoryManager(TinyIoCContainer container) 19 | { 20 | this.container = container; 21 | container.Register(); 22 | container.Register(); 23 | repository = container.Resolve(); 24 | 25 | var memory = container.Resolve(); 26 | memory.RegisterOnProcessChangeHandler(FindMemory); 27 | } 28 | 29 | private void FindMemory(object sender, Process p) 30 | { 31 | memory = null; 32 | if (p == null) 33 | { 34 | return; 35 | } 36 | 37 | ScanPointers(); 38 | } 39 | 40 | public void ScanPointers() 41 | { 42 | List candidates = new List(); 43 | candidates.Add(container.Resolve()); 44 | candidates.Add(container.Resolve()); 45 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 46 | } 47 | 48 | public bool IsValid() 49 | { 50 | if (memory == null || !memory.IsValid()) 51 | { 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | Version IVersionedMemory.GetVersion() 59 | { 60 | if (!IsValid()) 61 | return null; 62 | return memory.GetVersion(); 63 | } 64 | 65 | public List GetEnmityHudEntries() 66 | { 67 | if (!IsValid()) 68 | { 69 | return null; 70 | } 71 | 72 | return memory.GetEnmityHudEntries(); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /IINACT/TextToSpeechProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Speech.Synthesis; 2 | using System.Web; 3 | using NAudio.Wave; 4 | 5 | namespace IINACT; 6 | 7 | internal class TextToSpeechProvider 8 | { 9 | private readonly object speechLock = new(); 10 | private readonly HttpClient client = new(); 11 | private readonly SpeechSynthesizer? speechSynthesizer; 12 | 13 | public TextToSpeechProvider() 14 | { 15 | if (!Dalamud.Utility.Util.IsWine()) 16 | { 17 | try 18 | { 19 | speechSynthesizer = new SpeechSynthesizer(); 20 | speechSynthesizer?.SetOutputToDefaultAudioDevice(); 21 | } 22 | catch (Exception ex) 23 | { 24 | Plugin.Log.Warning(ex, "Failed to initialize SAPI TTS engine"); 25 | speechSynthesizer = null; 26 | } 27 | } 28 | 29 | Advanced_Combat_Tracker.ActGlobals.oFormActMain.TextToSpeech += Speak; 30 | } 31 | 32 | public void Speak(string message) 33 | { 34 | Task.Run(() => 35 | { 36 | try 37 | { 38 | if (speechSynthesizer == null) 39 | SpeakGoogle(message); 40 | else 41 | SpeakSapi(message); 42 | } 43 | catch (Exception ex) 44 | { 45 | Plugin.Log.Error(ex, $"TTS failed to play back {message}"); 46 | } 47 | }); 48 | } 49 | 50 | private void SpeakGoogle(string message) 51 | { 52 | var query = HttpUtility.UrlEncode(message); 53 | const string lang = "en"; 54 | var url = $"https://translate.google.com/translate_tts?ie=UTF-8&client=tw-ob&tl={lang}&q={query}"; 55 | var mp3Data = client.GetByteArrayAsync(url).Result; 56 | 57 | using var stream = new MemoryStream(mp3Data); 58 | using var reader = new Mp3FileReader(stream); 59 | using var waveOut = new WaveOutEvent(); 60 | waveOut.Init(reader); 61 | var waitHandle = new ManualResetEventSlim(false); 62 | 63 | lock (speechLock) 64 | { 65 | waveOut.Play(); 66 | waveOut.PlaybackStopped += (s, e) => waitHandle.Set(); 67 | waitHandle.Wait(); 68 | } 69 | } 70 | 71 | private void SpeakSapi(string message) 72 | { 73 | lock (speechLock) 74 | speechSynthesizer?.Speak(message); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 RainbowMage, hibiyasleep, ngld 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | ---------------- 22 | 23 | Libraries integrated into OverlayPlugin: 24 | 25 | * ACT_EnmityPlugin 26 | https://github.com/xtuaok/ACT_EnmityPlugin 27 | 28 | Copyright © 2015-2019 Tomonori Tamagawa, TamanegiMage, quisquous, qitana 29 | 30 | Permission is hereby granted, free of charge, to any person obtaining a 31 | copy of this software and associated documentation files (the 32 | "Software"), to deal in the Software without restriction, including 33 | without limitation the rights to use, copy, modify, merge, publish, 34 | distribute, sublicense, and/or sell copies of the Software, and to 35 | permit persons to whom the Software is furnished to do so, subject to 36 | the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included 39 | in all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 42 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 43 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 44 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 45 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 46 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 47 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 RainbowMage, hibiyasleep, ngld 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | ---------------- 22 | 23 | Libraries integrated into OverlayPlugin: 24 | 25 | * ACT_EnmityPlugin 26 | https://github.com/xtuaok/ACT_EnmityPlugin 27 | 28 | Copyright © 2015-2019 Tomonori Tamagawa, TamanegiMage, quisquous, qitana 29 | 30 | Permission is hereby granted, free of charge, to any person obtaining a 31 | copy of this software and associated documentation files (the 32 | "Software"), to deal in the Software without restriction, including 33 | without limitation the rights to use, copy, modify, merge, publish, 34 | distribute, sublicense, and/or sell copies of the Software, and to 35 | permit persons to whom the Software is furnished to do so, subject to 36 | the following conditions: 37 | 38 | The above copyright notice and this permission notice shall be included 39 | in all copies or substantial portions of the Software. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 42 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 43 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 44 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 45 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 46 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 47 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineActorSetPos.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Runtime.InteropServices; 3 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 6 | { 7 | class LineActorSetPos : LineBaseCustom< 8 | Server_MessageHeader_Global, LineActorSetPos.ActorSetPos_v655, 9 | Server_MessageHeader_CN, LineActorSetPos.ActorSetPos_v655, 10 | Server_MessageHeader_KR, LineActorSetPos.ActorSetPos_v655> 11 | { 12 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 13 | internal unsafe struct ActorSetPos_v655 : IPacketStruct 14 | { 15 | // 6.5.5 packet data (minus header): 16 | // 6AD3 0F 02 00000000 233E3BC1 00000000 D06AF840 00000000 17 | // AAAA BB CC DDDDDDDD EEEEEEEE FFFFFFFF GGGGGGGG HHHHHHHH 18 | // 0x0 0x2 0x3 0x4 0x8 0xC 0x10 0x14 19 | // Rot unk unk unk X Y Z unk 20 | 21 | // Have never seen data in 0x4 or 0x14, probably just padding? 22 | 23 | public const int structSize = 24; 24 | [FieldOffset(0x0)] 25 | public ushort rotation; 26 | 27 | [FieldOffset(0x2)] 28 | public byte unknown1; 29 | 30 | [FieldOffset(0x3)] 31 | public byte unknown2; 32 | 33 | // Yes, these are actually floats, and not some janky ushort that needs converted through ConvertUInt16Coordinate 34 | [FieldOffset(0x8)] 35 | public float x; 36 | [FieldOffset(0xC)] 37 | public float y; 38 | [FieldOffset(0x10)] 39 | public float z; 40 | 41 | public string ToString(long epoch, uint ActorID) 42 | { 43 | return 44 | string.Format(CultureInfo.InvariantCulture, 45 | "{0:X8}|{1:F4}|{2:X2}|{3:X2}|{4:F4}|{5:F4}|{6:F4}", 46 | ActorID, FFXIVRepository.ConvertHeading(rotation), unknown1, unknown2, x, z, y); 47 | } 48 | } 49 | 50 | public const uint LogFileLineID = 271; 51 | public const string logLineName = "ActorSetPos"; 52 | public const string MachinaPacketName = "ActorSetPos"; 53 | 54 | public LineActorSetPos(TinyIoCContainer container) 55 | : base(container, LogFileLineID, logLineName, MachinaPacketName) { } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /IINACT/PrivateHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace IINACT; 4 | 5 | public static class PrivateHelpers 6 | { 7 | public static T GetProperty(this object obj, string propName) 8 | { 9 | var pi = obj.GetType() 10 | .GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 11 | return (T)pi?.GetValue(obj, null)!; 12 | } 13 | 14 | public static T GetField(this object obj, string propName) 15 | { 16 | var t = obj.GetType(); 17 | FieldInfo? fi = null; 18 | while (fi == null && t != null) 19 | { 20 | fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 21 | t = t.BaseType; 22 | } 23 | 24 | return (T)fi?.GetValue(obj)!; 25 | } 26 | 27 | public static void SetProperty(this object obj, string propName, T val) 28 | { 29 | var t = obj.GetType(); 30 | if (t.GetProperty(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) == null) 31 | { 32 | throw new ArgumentOutOfRangeException(nameof(propName), 33 | $@"Property {propName} was not found in Type {obj.GetType().FullName}"); 34 | } 35 | 36 | t.InvokeMember(propName, 37 | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.SetProperty | BindingFlags.Instance, 38 | null, obj, 39 | new object?[] { val }); 40 | } 41 | 42 | public static void SetField(this object obj, string propName, T val) 43 | { 44 | var t = obj.GetType(); 45 | FieldInfo? fi = null; 46 | while (fi == null && t != null) 47 | { 48 | fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 49 | t = t.BaseType; 50 | } 51 | 52 | fi?.SetValue(obj, val); 53 | } 54 | 55 | public static MethodInfo? GetMethod(this object obj, string methodName) 56 | { 57 | var type = obj.GetType(); 58 | var method = type.GetMethod(methodName); 59 | return method ?? type.GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance); 60 | } 61 | 62 | public static object? CallMethod(this object obj, string methodName, object?[]? parameters) 63 | { 64 | var method = GetMethod(obj, methodName); 65 | return method?.Invoke(obj, parameters); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/OverlayTemplateConfig.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Web; 8 | 9 | namespace RainbowMage.OverlayPlugin 10 | { 11 | public class OverlayTemplateConfig 12 | { 13 | public int Version { get; set; } 14 | public List Overlays { get; set; } 15 | 16 | } 17 | 18 | public class OverlayTemplate : IOverlayTemplate 19 | { 20 | public string Name { get; set; } 21 | public string Uri { get; set; } 22 | 23 | [JsonProperty("plaintext_uri")] 24 | public string PlaintextUri { get; set; } 25 | [JsonProperty("suggested_width")] 26 | public int? SuggestedWidth { get; set; } 27 | [JsonProperty("suggested_height")] 28 | public int? SuggestedHeight { get; set; } 29 | public List Features { get; set; } = new List(); 30 | 31 | public Uri ToOverlayUri(Uri webSocketServer) 32 | { 33 | Uri overlayUri; 34 | 35 | if (webSocketServer == null) 36 | { 37 | return null; 38 | } 39 | 40 | if (webSocketServer.Scheme == "wss") 41 | { 42 | overlayUri = new Uri(Uri); 43 | } 44 | else if (webSocketServer.Scheme == "ws") 45 | { 46 | overlayUri = new Uri(PlaintextUri ?? Uri); 47 | } 48 | else 49 | { 50 | return null; 51 | } 52 | 53 | if (Features.Contains("overlay_ws")) 54 | { 55 | overlayUri = setQueryStringParameter(overlayUri, "OVERLAY_WS", webSocketServer.ToString()); 56 | } 57 | else if (Features.Contains("host_port")) 58 | { 59 | overlayUri = setQueryStringParameter(overlayUri, "HOST_PORT", webSocketServer.GetComponents(UriComponents.SchemeAndServer, UriFormat.SafeUnescaped)); 60 | } 61 | 62 | return overlayUri; 63 | } 64 | 65 | private static Uri setQueryStringParameter(Uri source, string parameter, string value) 66 | { 67 | var qs = HttpUtility.ParseQueryString(source.Query); 68 | qs.Set(parameter, value); 69 | 70 | var uriBuilder = new UriBuilder(source); 71 | uriBuilder.Query = HttpUtility.UrlDecode(qs.ToString()); 72 | return uriBuilder.Uri; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /NotACT/Datatypes/CombatActionEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Advanced_Combat_Tracker; 2 | 3 | public delegate void CombatActionDelegate(bool isImport, CombatActionEventArgs actionInfo); 4 | 5 | public class CombatActionEventArgs : EventArgs 6 | { 7 | public readonly MasterSwing combatAction; 8 | 9 | public string attacker; 10 | 11 | public bool cancelAction; 12 | 13 | public bool critical; 14 | 15 | public Dnum damage; 16 | 17 | public string special; 18 | public int swingType; 19 | 20 | public Dictionary tags; 21 | 22 | public string theAttackType; 23 | 24 | public string theDamageType; 25 | 26 | public DateTime time; 27 | 28 | public int timeSorter; 29 | 30 | public string victim; 31 | 32 | public CombatActionEventArgs(MasterSwing CombatAction) 33 | { 34 | combatAction = CombatAction; 35 | swingType = CombatAction.SwingType; 36 | critical = CombatAction.Critical; 37 | attacker = CombatAction.Attacker; 38 | theAttackType = CombatAction.AttackType; 39 | damage = CombatAction.Damage; 40 | time = CombatAction.Time; 41 | timeSorter = CombatAction.TimeSorter; 42 | victim = CombatAction.Victim; 43 | theDamageType = CombatAction.DamageType; 44 | special = CombatAction.Special; 45 | tags = CombatAction.Tags; 46 | } 47 | 48 | [Obsolete] 49 | public CombatActionEventArgs( 50 | int SwingType, bool Critical, string Special, string Attacker, string TheAttackType, Dnum Damage, 51 | DateTime Time, int TimeSorter, string Victim, string TheDamageType) 52 | { 53 | swingType = SwingType; 54 | critical = Critical; 55 | attacker = Attacker; 56 | theAttackType = TheAttackType; 57 | damage = Damage; 58 | time = Time; 59 | timeSorter = TimeSorter; 60 | victim = Victim; 61 | theDamageType = TheDamageType; 62 | special = Special; 63 | } 64 | 65 | [Obsolete] 66 | public CombatActionEventArgs( 67 | int SwingType, bool Critical, string Attacker, string TheAttackType, Dnum Damage, DateTime Time, 68 | int TimeSorter, string Victim, string TheDamageType) 69 | { 70 | swingType = SwingType; 71 | critical = Critical; 72 | attacker = Attacker; 73 | theAttackType = TheAttackType; 74 | damage = Damage; 75 | time = Time; 76 | timeSorter = TimeSorter; 77 | victim = Victim; 78 | theDamageType = TheDamageType; 79 | special = "specialAttackTerm-none"; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineActorMove.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using System.Runtime.InteropServices; 3 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 6 | { 7 | class LineActorMove : LineBaseCustom< 8 | Server_MessageHeader_Global, LineActorMove.ActorMove_v655, 9 | Server_MessageHeader_CN, LineActorMove.ActorMove_v655, 10 | Server_MessageHeader_KR, LineActorMove.ActorMove_v655> 11 | { 12 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 13 | internal unsafe struct ActorMove_v655 : IPacketStruct 14 | { 15 | // 6.5.5 packet data (minus header): 16 | // 1897 3004 3C00 B681 AA80 9F83 00000000 17 | // AAAA BBBB CCCC DDDD EEEE FFFF GGGGGGGG 18 | // 0x0 0x2 0x4 0x6 0x8 0xA 0xC 19 | // Rot Unk Unk X Y Z Unk 20 | 21 | // Have never seen data in 0xC, probably padding? 22 | 23 | public const int structSize = 16; 24 | 25 | [FieldOffset(0x0)] 26 | public ushort rotation; 27 | 28 | [FieldOffset(0x2)] 29 | public ushort unknown1; 30 | [FieldOffset(0x4)] 31 | public ushort unknown2; 32 | 33 | [FieldOffset(0x6)] 34 | public ushort x; 35 | [FieldOffset(0x8)] 36 | public ushort y; 37 | [FieldOffset(0xA)] 38 | public ushort z; 39 | 40 | public string ToString(long epoch, uint ActorID) 41 | { 42 | // Only emit for non-player actors 43 | if (ActorID < 0x40000000) 44 | { 45 | return null; 46 | } 47 | 48 | return 49 | string.Format(CultureInfo.InvariantCulture, 50 | "{0:X8}|{1:F4}|{2:X4}|{3:X4}|{4:F4}|{5:F4}|{6:F4}", 51 | ActorID, FFXIVRepository.ConvertHeading(rotation), unknown1, unknown2, 52 | FFXIVRepository.ConvertUInt16Coordinate(x), FFXIVRepository.ConvertUInt16Coordinate(z), 53 | FFXIVRepository.ConvertUInt16Coordinate(y)); 54 | } 55 | } 56 | 57 | public const uint LogFileLineID = 270; 58 | public const string logLineName = "ActorMove"; 59 | public const string MachinaPacketName = "ActorMove"; 60 | 61 | public LineActorMove(TinyIoCContainer container) 62 | : base(container, LogFileLineID, logLineName, MachinaPacketName) { } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/PacketHelper/LineBaseSubMachina.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Machina.FFXIV; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper 6 | { 7 | abstract class LineBaseSubMachina 8 | where PacketType : MachinaPacketWrapper, new() 9 | { 10 | protected static FFXIVRepository ffxiv; 11 | 12 | protected readonly Func logWriter; 13 | protected MachinaRegionalizedPacketHelper packetHelper; 14 | protected GameRegion? currentRegion; 15 | 16 | public LineBaseSubMachina(TinyIoCContainer container, uint logFileLineID, string logLineName, string machinaPacketName) 17 | { 18 | ffxiv = ffxiv ?? container.Resolve(); 19 | ffxiv.RegisterNetworkParser(MessageReceived); 20 | ffxiv.RegisterProcessChangedHandler(ProcessChanged); 21 | 22 | if (MachinaRegionalizedPacketHelper.Create(machinaPacketName, out packetHelper)) 23 | { 24 | var customLogLines = container.Resolve(); 25 | logWriter = customLogLines.RegisterCustomLogLine(new LogLineRegistryEntry() 26 | { 27 | Name = logLineName, 28 | Source = "OverlayPlugin", 29 | ID = logFileLineID, 30 | Version = 1, 31 | }); 32 | } 33 | else 34 | { 35 | var logger = container.Resolve(); 36 | logger.Log(LogLevel.Error, $"Failed to initialize {logFileLineID}: Failed to create {machinaPacketName} packet helper from Machina structs"); 37 | } 38 | } 39 | 40 | protected virtual void ProcessChanged(Process process) 41 | { 42 | if (!ffxiv.IsFFXIVPluginPresent()) 43 | return; 44 | 45 | currentRegion = null; 46 | } 47 | 48 | protected virtual unsafe void MessageReceived(string id, long epoch, byte[] message) 49 | { 50 | if (packetHelper == null) 51 | return; 52 | 53 | if (currentRegion == null) 54 | currentRegion = ffxiv.GetMachinaRegion(); 55 | 56 | if (currentRegion == null) 57 | return; 58 | 59 | var line = packetHelper[currentRegion.Value].ToString(epoch, message); 60 | 61 | if (line != null) 62 | { 63 | DateTime serverTime = ffxiv.EpochToDateTime(epoch); 64 | logWriter(line, serverTime); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/EnmityHud/EnmityHudMemory70.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.EnmityHud 5 | { 6 | interface IEnmityHudMemory70 : IEnmityHudMemory { } 7 | 8 | class EnmityHudMemory70 : EnmityHudMemory, IEnmityHudMemory70 9 | { 10 | private const string enmityHudSignature = "488B8F????????488B01FF50??4C8B1D"; 11 | private static readonly int[] enmityHudPointerPath = new int[] { 0x30, 0x58, 0xA8, 0x20 }; 12 | 13 | // Offsets from the enmityHudAddress to find various enmity HUD data structures. 14 | private const int enmityHudCountOffset = 4; 15 | private const int enmityHudEntryOffset = 16; 16 | 17 | public EnmityHudMemory70(TinyIoCContainer container) 18 | : base(container, enmityHudSignature, enmityHudPointerPath, enmityHudCountOffset, enmityHudEntryOffset, EnmityHudEntryMemory.Size) 19 | { } 20 | 21 | public override Version GetVersion() 22 | { 23 | return new Version(6, 2); 24 | } 25 | 26 | [StructLayout(LayoutKind.Explicit, Size = Size)] 27 | struct EnmityHudEntryMemory 28 | { 29 | public const int Size = 24; 30 | 31 | [FieldOffset(0x00)] 32 | public uint Unknown01; 33 | 34 | [FieldOffset(0x04)] 35 | public uint HPPercent; 36 | 37 | [FieldOffset(0x08)] 38 | public uint EnmityPercent; 39 | 40 | [FieldOffset(0x0C)] 41 | public uint CastPercent; 42 | 43 | [FieldOffset(0x10)] 44 | public uint ID; 45 | 46 | [FieldOffset(0x14)] 47 | public uint Unknown02; 48 | } 49 | 50 | 51 | protected override unsafe EnmityHudEntry GetEnmityHudEntryFromBytes(byte[] source, int num = 0) 52 | { 53 | if (num < 0) throw new ArgumentException(); 54 | if (num > 8) throw new ArgumentException(); 55 | 56 | fixed (byte* p = source) 57 | { 58 | EnmityHudEntryMemory mem = *(EnmityHudEntryMemory*)&p[num * EnmityHudEntryMemory.Size]; 59 | 60 | EnmityHudEntry enmityHudEntry = new EnmityHudEntry() 61 | { 62 | Order = num, 63 | ID = mem.ID, 64 | HPPercent = mem.HPPercent > 100 ? 0 : mem.HPPercent, 65 | EnmityPercent = mem.EnmityPercent > 100 ? 0 : mem.EnmityPercent, 66 | CastPercent = mem.CastPercent > 100 ? 0 : mem.CastPercent, 67 | }; 68 | 69 | return enmityHudEntry; 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/AtkStage/AtkStageMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.AtkStage 6 | { 7 | public interface IAtkStageMemory : IVersionedMemory 8 | { 9 | IntPtr GetAddonAddress(string name); 10 | T? GetAddon() where T : struct; 11 | object GetAddon(string name); 12 | } 13 | 14 | class AtkStageMemoryManager : IAtkStageMemory 15 | { 16 | private readonly TinyIoCContainer container; 17 | private readonly FFXIVRepository repository; 18 | private IAtkStageMemory memory = null; 19 | 20 | public AtkStageMemoryManager(TinyIoCContainer container) 21 | { 22 | this.container = container; 23 | container.Register(); 24 | repository = container.Resolve(); 25 | 26 | var memory = container.Resolve(); 27 | memory.RegisterOnProcessChangeHandler(FindMemory); 28 | } 29 | 30 | private void FindMemory(object sender, Process p) 31 | { 32 | memory = null; 33 | if (p == null) 34 | { 35 | return; 36 | } 37 | 38 | ScanPointers(); 39 | } 40 | 41 | public void ScanPointers() 42 | { 43 | List candidates = new List(); 44 | candidates.Add(container.Resolve()); 45 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 46 | } 47 | 48 | public bool IsValid() 49 | { 50 | if (memory == null || !memory.IsValid()) 51 | { 52 | return false; 53 | } 54 | 55 | return true; 56 | } 57 | 58 | Version IVersionedMemory.GetVersion() 59 | { 60 | if (!IsValid()) 61 | return null; 62 | return memory.GetVersion(); 63 | } 64 | 65 | public IntPtr GetAddonAddress(string name) 66 | { 67 | if (!IsValid()) 68 | { 69 | return IntPtr.Zero; 70 | } 71 | 72 | return memory.GetAddonAddress(name); 73 | } 74 | 75 | public T? GetAddon() where T : struct 76 | { 77 | if (!IsValid()) 78 | return null; 79 | 80 | return memory.GetAddon(); 81 | } 82 | 83 | public object GetAddon(string name) 84 | { 85 | if (!IsValid()) 86 | return null; 87 | 88 | return memory.GetAddon(name); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Target/TargetMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Target 6 | { 7 | public interface ITargetMemory : IVersionedMemory 8 | { 9 | Combatant.Combatant GetTargetCombatant(); 10 | 11 | Combatant.Combatant GetFocusCombatant(); 12 | 13 | Combatant.Combatant GetHoverCombatant(); 14 | } 15 | 16 | class TargetMemoryManager : ITargetMemory 17 | { 18 | private readonly TinyIoCContainer container; 19 | private readonly FFXIVRepository repository; 20 | private ITargetMemory memory = null; 21 | 22 | public TargetMemoryManager(TinyIoCContainer container) 23 | { 24 | this.container = container; 25 | container.Register(); 26 | repository = container.Resolve(); 27 | 28 | var memory = container.Resolve(); 29 | memory.RegisterOnProcessChangeHandler(FindMemory); 30 | } 31 | 32 | private void FindMemory(object sender, Process p) 33 | { 34 | memory = null; 35 | if (p == null) 36 | { 37 | return; 38 | } 39 | 40 | ScanPointers(); 41 | } 42 | 43 | public void ScanPointers() 44 | { 45 | List candidates = new List(); 46 | candidates.Add(container.Resolve()); 47 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 48 | } 49 | 50 | public bool IsValid() 51 | { 52 | if (memory == null || !memory.IsValid()) 53 | { 54 | return false; 55 | } 56 | 57 | return true; 58 | } 59 | 60 | Version IVersionedMemory.GetVersion() 61 | { 62 | if (!IsValid()) 63 | return null; 64 | return memory.GetVersion(); 65 | } 66 | 67 | public Combatant.Combatant GetTargetCombatant() 68 | { 69 | if (!IsValid()) 70 | { 71 | return null; 72 | } 73 | 74 | return memory.GetTargetCombatant(); 75 | } 76 | 77 | public Combatant.Combatant GetFocusCombatant() 78 | { 79 | if (!IsValid()) 80 | { 81 | return null; 82 | } 83 | 84 | return memory.GetFocusCombatant(); 85 | } 86 | 87 | public Combatant.Combatant GetHoverCombatant() 88 | { 89 | if (!IsValid()) 90 | { 91 | return null; 92 | } 93 | 94 | return memory.GetHoverCombatant(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/OverlayConfigList.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.Linq; 4 | using System.Xml.Serialization; 5 | 6 | namespace RainbowMage.OverlayPlugin 7 | { 8 | /// 9 | /// XmlSerializer でシリアライズ可能な IOverlayConfig のコレクション。 10 | /// 11 | [Serializable] 12 | public class OverlayConfigList : Collection, IXmlSerializable 13 | { 14 | public int MissingTypes { get; private set; } 15 | 16 | [NonSerialized] 17 | private ILogger _logger; 18 | 19 | public OverlayConfigList(ILogger logger) 20 | { 21 | _logger = logger; 22 | } 23 | 24 | public System.Xml.Schema.XmlSchema GetSchema() 25 | { 26 | return null; 27 | } 28 | 29 | public void ReadXml(System.Xml.XmlReader reader) 30 | { 31 | MissingTypes = 0; 32 | 33 | if (reader.IsEmptyElement) 34 | { 35 | return; 36 | } 37 | 38 | reader.ReadToDescendant("Overlay"); 39 | do 40 | { 41 | var typeName = reader.GetAttribute("Type"); 42 | 43 | reader.Read(); 44 | 45 | var type = GetType(typeName); 46 | 47 | if (type != null) 48 | { 49 | try 50 | { 51 | var serializer = new XmlSerializer(type); 52 | var config = (T)serializer.Deserialize(reader); 53 | this.Add(config); 54 | } 55 | catch (Exception e) 56 | { 57 | _logger.Log(LogLevel.Error, e.ToString()); 58 | } 59 | } 60 | else 61 | { 62 | reader.Skip(); 63 | MissingTypes++; 64 | } 65 | } 66 | while (reader.ReadToNextSibling("Overlay")); 67 | 68 | reader.ReadEndElement(); 69 | } 70 | 71 | private Type GetType(string fullName) => AppDomain.CurrentDomain.GetAssemblies() 72 | .Select(asm => asm.GetType(fullName, false)) 73 | .FirstOrDefault(type => type != null); 74 | 75 | public void WriteXml(System.Xml.XmlWriter writer) 76 | { 77 | foreach (var config in this) 78 | { 79 | writer.WriteStartElement("Overlay"); 80 | writer.WriteAttributeString("Type", config.GetType().FullName); 81 | var serializer = new XmlSerializer(config.GetType()); 82 | serializer.Serialize(writer, config); 83 | writer.WriteEndElement(); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/EventSources/MiniParseLoglineReader.cs: -------------------------------------------------------------------------------- 1 | using Advanced_Combat_Tracker; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | 5 | namespace RainbowMage.OverlayPlugin.EventSources 6 | { 7 | internal partial class MiniParseEventSource : EventSourceBase 8 | { 9 | // Part of ACTWebSocket 10 | // Copyright (c) 2016 ZCube; Licensed under MIT license. 11 | public enum MessageType 12 | { 13 | LogLine = 0, 14 | ChangeZone = 1, 15 | ChangePrimaryPlayer = 2, 16 | AddCombatant = 3, 17 | RemoveCombatant = 4, 18 | AddBuff = 5, 19 | RemoveBuff = 6, 20 | FlyingText = 7, 21 | OutgoingAbility = 8, 22 | IncomingAbility = 10, 23 | PartyList = 11, 24 | PlayerStats = 12, 25 | CombatantHP = 13, 26 | NetworkStartsCasting = 20, 27 | NetworkAbility = 21, 28 | NetworkAOEAbility = 22, 29 | NetworkCancelAbility = 23, 30 | NetworkDoT = 24, 31 | NetworkDeath = 25, 32 | NetworkBuff = 26, 33 | NetworkTargetIcon = 27, 34 | NetworkRaidMarker = 28, 35 | NetworkTargetMarker = 29, 36 | NetworkBuffRemove = 30, 37 | Debug = 251, 38 | PacketDump = 252, 39 | Version = 253, 40 | Error = 254, 41 | Timer = 255 42 | } 43 | 44 | private void LogLineReader(bool isImported, LogLineEventArgs e) 45 | { 46 | Log(LogLevel.Info, e.logLine); 47 | 48 | var d = e.logLine.Split('|'); 49 | 50 | if (d == null || d.Length < 2) // DataErr0r: null or 1-section 51 | { 52 | return; 53 | } 54 | 55 | var type = (MessageType)Convert.ToInt32(d[0]); 56 | 57 | switch (type) 58 | { 59 | case MessageType.LogLine: 60 | if (d.Length < 5) // Invalid 61 | { 62 | break; 63 | } 64 | 65 | var logType = Convert.ToInt32(d[2], 16); 66 | 67 | if (logType == 56) // type:echo 68 | { 69 | sendEchoEvent(isImported, "echo", d[4]); 70 | } 71 | 72 | break; 73 | } 74 | } 75 | 76 | private void sendEchoEvent(bool isImported, string type, string text) 77 | { 78 | var message = new JObject(); 79 | message["isImported"] = isImported; 80 | message["type"] = type; 81 | message["message"] = text; 82 | 83 | var e = new JObject(); 84 | e["type"] = "LogLine"; 85 | e["detail"] = message; 86 | 87 | DispatchEvent(e); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/ContentFinderSettings/ContentFinderSettings71.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.ContentFinderSettings 5 | { 6 | interface IContentFinderSettingsMemory71 : IContentFinderSettingsMemory { } 7 | 8 | class ContentFinderSettingsMemory71 : ContentFinderSettingsMemory, IContentFinderSettingsMemory71 9 | { 10 | // FUN_14184cea0:14184ceaa, DAT_142780dd0 11 | private const string settingsSignature = "488D0D????????E8????????488BF84885C00F84????????488B4B??4889AC24"; 12 | 13 | // FUN_1400aa840:1400aa862 14 | // IsLocalPlayerInParty:1400aa862 (after rename) 15 | private const string inContentFinderSignature = "803D??????????74??E8????????488BC8"; 16 | 17 | public ContentFinderSettingsMemory71(TinyIoCContainer container) 18 | : base(container, settingsSignature, inContentFinderSignature, -1) 19 | { } 20 | 21 | // For 7.0 and onwards, handle this properly. 22 | // TODO: Once CN and KR are on 7.0, move this logic up to `ContentFinderSettings` for common use 23 | public override void ScanPointers() 24 | { 25 | ResetPointers(); 26 | if (!memory.IsValid()) 27 | return; 28 | 29 | List fail = new List(); 30 | 31 | List list = memory.SigScan(settingsSignature, -29, true); 32 | if (list != null && list.Count > 0) 33 | { 34 | settingsAddress = list[0] + 0xA8; 35 | } 36 | else 37 | { 38 | settingsAddress = IntPtr.Zero; 39 | fail.Add(nameof(settingsAddress)); 40 | } 41 | 42 | logger.Log(LogLevel.Debug, "settingsAddress: 0x{0:X}", settingsAddress.ToInt64()); 43 | 44 | list = memory.SigScan(inContentFinderSignature, -15, true, 1); 45 | if (list != null && list.Count > 0) 46 | { 47 | inContentFinderAddress = list[0]; 48 | } 49 | else 50 | { 51 | inContentFinderAddress = IntPtr.Zero; 52 | fail.Add(nameof(inContentFinderAddress)); 53 | } 54 | 55 | logger.Log(LogLevel.Debug, "inContentFinderAddress: 0x{0:X}", inContentFinderAddress.ToInt64()); 56 | 57 | if (fail.Count == 0) 58 | { 59 | logger.Log(LogLevel.Info, $"Found content finder settings memory via {GetType().Name}."); 60 | return; 61 | } 62 | 63 | logger.Log(LogLevel.Error, $"Failed to find content finder settings memory via {GetType().Name}: {string.Join(", ", fail)}."); 64 | return; 65 | } 66 | 67 | public override Version GetVersion() 68 | { 69 | return new Version(7, 1); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/LegacyHandler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using Advanced_Combat_Tracker; 3 | using Newtonsoft.Json.Linq; 4 | 5 | namespace RainbowMage.OverlayPlugin.Handlers; 6 | 7 | internal abstract class LegacyHandler : IHandler, IEventReceiver 8 | { 9 | public string Name { get; } 10 | protected ILogger Logger { get; } 11 | private EventDispatcher Dispatcher { get; } 12 | private FFXIVRepository Repository { get; } 13 | 14 | protected LegacyHandler(string name, ILogger logger, EventDispatcher eventDispatcher, FFXIVRepository repository) 15 | { 16 | Name = name; 17 | Logger = logger; 18 | Dispatcher = eventDispatcher; 19 | Repository = repository; 20 | } 21 | 22 | protected void Start() 23 | { 24 | Dispatcher.Subscribe("CombatData", this); 25 | Dispatcher.Subscribe("LogLine", this); 26 | Dispatcher.Subscribe("ChangeZone", this); 27 | Dispatcher.Subscribe("ChangePrimaryPlayer", this); 28 | 29 | Send((JObject)JToken.FromObject(new 30 | { 31 | type = "broadcast", 32 | msgtype = "SendCharName", 33 | msg = new 34 | { 35 | charName = Repository.GetPlayerName() ?? "YOU", 36 | charID = Repository.GetPlayerID() 37 | } 38 | })); 39 | } 40 | 41 | protected abstract void Send(JObject data); 42 | 43 | public virtual void Dispose() => Dispatcher.UnsubscribeAll(this); 44 | 45 | public void HandleEvent(JObject e) 46 | { 47 | var data = (JObject)JToken.FromObject(new {type = "broadcast"}); 48 | 49 | switch ( e["type"]?.ToString()) 50 | { 51 | case "CombatData": 52 | data["msgtype"] = "CombatData"; 53 | data["msg"] = e; 54 | break; 55 | case "LogLine": 56 | data["msgtype"] = "Chat"; 57 | data["msg"] = e["rawLine"]; 58 | break; 59 | case "ChangeZone": 60 | data["msgtype"] = "ChangeZone"; 61 | data["msg"] = e; 62 | break; 63 | case "ChangePrimaryPlayer": 64 | data["msgtype"] = "SendCharName"; 65 | data["msg"] = e; 66 | break; 67 | default: 68 | return; 69 | } 70 | 71 | Send(data); 72 | } 73 | 74 | public void DataReceived(JObject data) 75 | { 76 | if (!data.ContainsKey("type") || !data.ContainsKey("msgtype")) return; 77 | 78 | switch (data["msgtype"]?.ToString()) 79 | { 80 | case "Capture": 81 | Logger.Log(LogLevel.Warning, "ACTWS Capture is not supported outside of overlays."); 82 | break; 83 | case "RequestEnd": 84 | ActGlobals.oFormActMain.EndCombat(true); 85 | break; 86 | } 87 | } 88 | } 89 | 90 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/ContentFinderSettings/ContentFinderSettings70.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.ContentFinderSettings 5 | { 6 | interface IContentFinderSettingsMemory70 : IContentFinderSettingsMemory { } 7 | 8 | class ContentFinderSettingsMemory70 : ContentFinderSettingsMemory, IContentFinderSettingsMemory70 9 | { 10 | // FUN_14184cea0:14184ceaa, DAT_142780dd0 11 | private const string settingsSignature = "488D0D????????E8????????488BF84885C00F84????????488B4B??4889AC24"; 12 | 13 | // FUN_1400aa840:1400aa862 14 | // IsLocalPlayerInParty:1400aa862 (after rename) 15 | private const string inContentFinderSignature = "803D??????????0F85????????33D2488D0D????????E8????????80B8??????????0F87"; 16 | 17 | public ContentFinderSettingsMemory70(TinyIoCContainer container) 18 | : base(container, settingsSignature, inContentFinderSignature, -1) 19 | { } 20 | 21 | // For 7.0 and onwards, handle this properly. 22 | // TODO: Once CN and KR are on 7.0, move this logic up to `ContentFinderSettings` for common use 23 | public override void ScanPointers() 24 | { 25 | ResetPointers(); 26 | if (!memory.IsValid()) 27 | return; 28 | 29 | List fail = new List(); 30 | 31 | List list = memory.SigScan(settingsSignature, -29, true); 32 | if (list != null && list.Count > 0) 33 | { 34 | settingsAddress = list[0] + 0xA8; 35 | } 36 | else 37 | { 38 | settingsAddress = IntPtr.Zero; 39 | fail.Add(nameof(settingsAddress)); 40 | } 41 | 42 | logger.Log(LogLevel.Debug, "settingsAddress: 0x{0:X}", settingsAddress.ToInt64()); 43 | 44 | list = memory.SigScan(inContentFinderSignature, -34, true, 1); 45 | if (list != null && list.Count > 0) 46 | { 47 | inContentFinderAddress = list[0]; 48 | } 49 | else 50 | { 51 | inContentFinderAddress = IntPtr.Zero; 52 | fail.Add(nameof(inContentFinderAddress)); 53 | } 54 | 55 | logger.Log(LogLevel.Debug, "inContentFinderAddress: 0x{0:X}", inContentFinderAddress.ToInt64()); 56 | 57 | if (fail.Count == 0) 58 | { 59 | logger.Log(LogLevel.Info, $"Found content finder settings memory via {GetType().Name}."); 60 | return; 61 | } 62 | 63 | logger.Log(LogLevel.Error, $"Failed to find content finder settings memory via {GetType().Name}: {string.Join(", ", fail)}."); 64 | return; 65 | } 66 | 67 | public override Version GetVersion() 68 | { 69 | return new Version(7, 0); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/EnmityHud/EnmityHudMemory73.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.EnmityHud 5 | { 6 | interface IEnmityHudMemory73 : IEnmityHudMemory { } 7 | 8 | class EnmityHudMemory73 : EnmityHudMemory, IEnmityHudMemory73 9 | { 10 | // This signature might not be stable - If broken, this is looking for the location of `g_Component::GUI::AtkStage_Instance` 11 | // The current version looks at `Component::GUI::AtkComponentDropDownList.SetEnabledState` for its location 12 | private const string enmityHudSignature = "488B8F????????48895C24??48896C24??488B2D"; 13 | private static readonly int[] enmityHudPointerPath = new int[] { 0x30, 0x58, 0xA8, 0x20 }; 14 | 15 | // Offsets from the enmityHudAddress to find various enmity HUD data structures. 16 | private const int enmityHudCountOffset = 4; 17 | private const int enmityHudEntryOffset = 16; 18 | 19 | public EnmityHudMemory73(TinyIoCContainer container) 20 | : base(container, enmityHudSignature, enmityHudPointerPath, enmityHudCountOffset, enmityHudEntryOffset, EnmityHudEntryMemory.Size) 21 | { } 22 | 23 | public override Version GetVersion() 24 | { 25 | return new Version(7, 3); 26 | } 27 | 28 | [StructLayout(LayoutKind.Explicit, Size = Size)] 29 | struct EnmityHudEntryMemory 30 | { 31 | public const int Size = 24; 32 | 33 | [FieldOffset(0x00)] 34 | public uint Unknown01; 35 | 36 | [FieldOffset(0x04)] 37 | public uint HPPercent; 38 | 39 | [FieldOffset(0x08)] 40 | public uint EnmityPercent; 41 | 42 | [FieldOffset(0x0C)] 43 | public uint CastPercent; 44 | 45 | [FieldOffset(0x10)] 46 | public uint ID; 47 | 48 | [FieldOffset(0x14)] 49 | public uint Unknown02; 50 | } 51 | 52 | 53 | protected override unsafe EnmityHudEntry GetEnmityHudEntryFromBytes(byte[] source, int num = 0) 54 | { 55 | if (num < 0) throw new ArgumentException(); 56 | if (num > 8) throw new ArgumentException(); 57 | 58 | fixed (byte* p = source) 59 | { 60 | EnmityHudEntryMemory mem = *(EnmityHudEntryMemory*)&p[num * EnmityHudEntryMemory.Size]; 61 | 62 | EnmityHudEntry enmityHudEntry = new EnmityHudEntry() 63 | { 64 | Order = num, 65 | ID = mem.ID, 66 | HPPercent = mem.HPPercent > 100 ? 0 : mem.HPPercent, 67 | EnmityPercent = mem.EnmityPercent > 100 ? 0 : mem.EnmityPercent, 68 | CastPercent = mem.CastPercent > 100 ? 0 : mem.CastPercent, 69 | }; 70 | 71 | return enmityHudEntry; 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineBattleTalk2.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 3 | 4 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 5 | { 6 | class LineBattleTalk2 : LineBaseCustom< 7 | Server_MessageHeader_Global, LineBattleTalk2.BattleTalk2_v655, 8 | Server_MessageHeader_CN, LineBattleTalk2.BattleTalk2_v655, 9 | Server_MessageHeader_KR, LineBattleTalk2.BattleTalk2_v655> 10 | { 11 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 12 | internal unsafe struct BattleTalk2_v655 : IPacketStruct 13 | { 14 | // 00|2024-02-25T15:13:29.0000000-05:00|0044|Whiskerwall Kupdi Koop|Mogglesguard, assemble! We must drive them out together, kupo!|e9f836e9767bed2e 15 | // Pre-processed data from packet (sorry, no raw packet for this one, instead it's my scuffed debugging packet dump's data) 16 | // 00000000|00000000|80034E2B|000002CE|33804|5000|0|2|0|0 17 | // first 4 bytes are actor ID, not always set 18 | // 0x80034E2B = instance content ID 19 | // 0x2CE = entry on `BNpcName` table 20 | // 33804 = entry on `InstanceContentTextData` table 21 | // 5000 = display time in ms 22 | // 2 = some sort of flags for display settings? 23 | 24 | public const int structSize = 40; 25 | [FieldOffset(0x0)] 26 | public uint actorID; 27 | [FieldOffset(0x8)] 28 | public uint instanceContentID; 29 | [FieldOffset(0xC)] 30 | public uint npcNameID; 31 | [FieldOffset(0x10)] 32 | public uint instanceContentTextID; 33 | [FieldOffset(0x14)] 34 | public uint displayMS; 35 | [FieldOffset(0x18)] 36 | public uint param1; 37 | [FieldOffset(0x1C)] 38 | public uint param2; 39 | [FieldOffset(0x20)] 40 | public uint param3; 41 | [FieldOffset(0x24)] 42 | public uint param4; 43 | 44 | public string ToString(long epoch, uint ActorID) 45 | { 46 | return 47 | $"{actorID:X8}|" + 48 | $"{instanceContentID:X8}|" + 49 | $"{npcNameID:X4}|" + 50 | $"{instanceContentTextID:X4}|" + 51 | $"{displayMS}|" + 52 | $"{param1:X}|" + 53 | $"{param2:X}|" + 54 | $"{param3:X}|" + 55 | $"{param4:X}"; 56 | } 57 | } 58 | 59 | public const uint LogFileLineID = 267; 60 | public const string logLineName = "BattleTalk2"; 61 | public const string MachinaPacketName = "BattleTalk2"; 62 | 63 | public LineBattleTalk2(TinyIoCContainer container) 64 | : base(container, LogFileLineID, logLineName, MachinaPacketName) { } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: IINACT build 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | env: 11 | RELEASE_VERSION: '0.0.0.0' 12 | DALAMUD_HOME: /tmp/dalamud 13 | DOTNET_NOLOGO: true 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | submodules: recursive 18 | - name: Setup .NET 19 | uses: actions/setup-dotnet@v3 20 | with: 21 | dotnet-version: 9.0.x 22 | - name: Download Dalamud 23 | shell: pwsh 24 | run: | 25 | Invoke-WebRequest -Uri https://goatcorp.github.io/dalamud-distrib/stg/latest.zip -OutFile latest.zip 26 | Expand-Archive -Force latest.zip /tmp/dalamud 27 | - name: Build 28 | run: dotnet build -c release 29 | - name: Prepare Build Artifact 30 | shell: pwsh 31 | run: | 32 | Copy-Item "IINACT/bin/Release/win-x64/IINACT/latest.zip" -Destination "IINACT.zip" 33 | Expand-Archive -Force IINACT.zip Artifact 34 | - name: Upload IINACT 35 | uses: actions/upload-artifact@v4 36 | with: 37 | name: IINACT 38 | path: Artifact/* 39 | - name: Update repo.json if needed 40 | shell: pwsh 41 | run: | 42 | $repo = Get-Content 'repo.json' -raw | ConvertFrom-Json 43 | $build = Get-Content 'IINACT/bin/Release/win-x64/IINACT/IINACT.json' -raw | ConvertFrom-Json 44 | if ($repo.AssemblyVersion -eq $build.AssemblyVersion) { 45 | Exit 46 | } 47 | Write-Output "RELEASE_VERSION=$($build.AssemblyVersion)" >> $env:GITHUB_ENV 48 | $repo.AssemblyVersion = $repo.TestingAssemblyVersion = $build.AssemblyVersion 49 | $repo.DownloadLinkInstall = 'https://github.com/marzent/IINACT/releases/download/v' + $repo.AssemblyVersion + '/IINACT.zip' 50 | $repo.DownloadLinkTesting = 'https://github.com/marzent/IINACT/releases/download/v' + $repo.TestingAssemblyVersion + '/IINACT.zip' 51 | $repo.DownloadLinkUpdate = $repo.DownloadLinkInstall 52 | $repo | ConvertTo-Json | % { "[`n" + $_ + "`n]" } | Set-Content 'repo.json' 53 | - name: Create Release 54 | if: ${{ env.RELEASE_VERSION != '0.0.0.0' }} 55 | uses: softprops/action-gh-release@v1 56 | with: 57 | files: IINACT.zip 58 | name: IINACT ${{ env.RELEASE_VERSION }} 59 | tag_name: v${{ env.RELEASE_VERSION }} 60 | prerelease: false 61 | append_body: true 62 | body_path: .github/release-notices.md 63 | generate_release_notes: true 64 | fail_on_unmatched_files: true 65 | - name: Commit repo.json 66 | if: ${{ env.RELEASE_VERSION != '0.0.0.0' }} 67 | run: | 68 | git config --global user.name "Actions User" 69 | git config --global user.email "actions@github.com" 70 | git fetch origin main 71 | git checkout main 72 | git add repo.json 73 | git commit -m "[CI] updating repo.json for ${{ env.RELEASE_VERSION }}" || true 74 | git push origin main 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/ContentFinderSettings/ContentFinderSettingsMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.ContentFinderSettings 6 | { 7 | public interface ContentFinderSettings 8 | { 9 | bool inContentFinderContent { get; } 10 | byte unrestrictedParty { get; } 11 | byte minimalItemLevel { get; } 12 | byte silenceEcho { get; } 13 | byte explorerMode { get; } 14 | byte levelSync { get; } 15 | 16 | // TODO: Maybe we can track these down if they're ever actually needed? 17 | // byte lootRules { get; } 18 | // byte limitedLevelingRoulette { get; } 19 | } 20 | 21 | public interface IContentFinderSettingsMemory : IVersionedMemory 22 | { 23 | ContentFinderSettings GetContentFinderSettings(); 24 | } 25 | 26 | class ContentFinderSettingsMemoryManager : IContentFinderSettingsMemory 27 | { 28 | private readonly TinyIoCContainer container; 29 | private readonly FFXIVRepository repository; 30 | private IContentFinderSettingsMemory memory = null; 31 | 32 | public ContentFinderSettingsMemoryManager(TinyIoCContainer container) 33 | { 34 | this.container = container; 35 | container.Register(); 36 | container.Register(); 37 | repository = container.Resolve(); 38 | 39 | var memory = container.Resolve(); 40 | memory.RegisterOnProcessChangeHandler(FindMemory); 41 | } 42 | 43 | private void FindMemory(object sender, Process p) 44 | { 45 | memory = null; 46 | if (p == null) 47 | { 48 | return; 49 | } 50 | ScanPointers(); 51 | } 52 | 53 | public void ScanPointers() 54 | { 55 | List candidates = new List(); 56 | candidates.Add(container.Resolve()); 57 | candidates.Add(container.Resolve()); 58 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 59 | } 60 | 61 | public bool IsValid() 62 | { 63 | if (memory == null || !memory.IsValid()) 64 | { 65 | return false; 66 | } 67 | return true; 68 | } 69 | 70 | Version IVersionedMemory.GetVersion() 71 | { 72 | if (!IsValid()) 73 | return null; 74 | return memory.GetVersion(); 75 | } 76 | 77 | public ContentFinderSettings GetContentFinderSettings() 78 | { 79 | if (!IsValid()) 80 | return null; 81 | return memory.GetContentFinderSettings(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/InCombat/InCombatMemory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | 5 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.InCombat 6 | { 7 | public abstract class InCombatMemory 8 | { 9 | protected FFXIVMemory memory; 10 | protected ILogger logger; 11 | 12 | protected IntPtr inCombatAddress = IntPtr.Zero; 13 | 14 | private string inCombatSignature; 15 | 16 | private int inCombatSignatureOffset; 17 | private int inCombatRIPOffset; 18 | 19 | public InCombatMemory( 20 | TinyIoCContainer container, string inCombatSignature, int inCombatSignatureOffset, int inCombatRIPOffset) 21 | { 22 | this.inCombatSignature = inCombatSignature; 23 | this.inCombatSignatureOffset = inCombatSignatureOffset; 24 | this.inCombatRIPOffset = inCombatRIPOffset; 25 | logger = container.Resolve(); 26 | memory = container.Resolve(); 27 | } 28 | 29 | private void ResetPointers() 30 | { 31 | inCombatAddress = IntPtr.Zero; 32 | } 33 | 34 | private bool HasValidPointers() 35 | { 36 | if (inCombatAddress == IntPtr.Zero) 37 | return false; 38 | return true; 39 | } 40 | 41 | public bool IsValid() 42 | { 43 | if (!memory.IsValid()) 44 | return false; 45 | 46 | if (!HasValidPointers()) 47 | return false; 48 | 49 | return true; 50 | } 51 | 52 | public void ScanPointers() 53 | { 54 | ResetPointers(); 55 | if (!memory.IsValid()) 56 | return; 57 | 58 | List fail = new List(); 59 | 60 | List list = memory.SigScan(inCombatSignature, inCombatSignatureOffset, true, inCombatRIPOffset); 61 | 62 | if (list != null && list.Count > 0) 63 | { 64 | inCombatAddress = list[0]; 65 | } 66 | else 67 | { 68 | inCombatAddress = IntPtr.Zero; 69 | fail.Add(nameof(inCombatAddress)); 70 | } 71 | 72 | 73 | logger.Log(LogLevel.Debug, "inCombatAddress: 0x{0:X}", inCombatAddress.ToInt64()); 74 | 75 | if (fail.Count == 0) 76 | { 77 | logger.Log(LogLevel.Info, $"Found in combat memory via {GetType().Name}."); 78 | return; 79 | } 80 | 81 | logger.Log(LogLevel.Error, 82 | $"Failed to find in combat memory via {GetType().Name}: {string.Join(", ", fail)}."); 83 | return; 84 | } 85 | 86 | public abstract Version GetVersion(); 87 | 88 | public bool GetInCombat() 89 | { 90 | if (!IsValid()) 91 | return false; 92 | byte[] bytes = memory.Read8(inCombatAddress, 1); 93 | return bytes[0] != 0; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/Handlers/Handler.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Newtonsoft.Json.Linq; 7 | 8 | namespace RainbowMage.OverlayPlugin.Handlers; 9 | 10 | internal abstract class Handler : IHandler, IEventReceiver 11 | { 12 | public string Name { get; } 13 | protected ILogger Logger { get; } 14 | private EventDispatcher Dispatcher { get; } 15 | 16 | protected Handler(string name, ILogger logger, EventDispatcher eventDispatcher) 17 | { 18 | Name = name; 19 | Logger = logger; 20 | Dispatcher = eventDispatcher; 21 | } 22 | 23 | protected abstract void Send(JObject data); 24 | public void HandleEvent(JObject e) => Send(e); 25 | 26 | public void DataReceived(JObject data) 27 | { 28 | if (!data.ContainsKey("call")) return; 29 | 30 | var msgType = data["call"]?.ToString(); 31 | switch (msgType) 32 | { 33 | case "subscribe": 34 | try 35 | { 36 | foreach (var item in data["events"]?.ToList() ?? new List()) 37 | Dispatcher.Subscribe(item.ToString(), this); 38 | } 39 | catch (Exception ex) 40 | { 41 | Logger.Log(LogLevel.Error, Resources.WSNewSubFail, ex); 42 | } 43 | 44 | return; 45 | case "unsubscribe": 46 | try 47 | { 48 | foreach (var item in data["events"]?.ToList() ?? new List()) 49 | Dispatcher.Unsubscribe(item.ToString(), this); 50 | } 51 | catch (Exception ex) 52 | { 53 | Logger.Log(LogLevel.Error, Resources.WSUnsubFail, ex); 54 | } 55 | 56 | return; 57 | default: 58 | Task.Run(() => 59 | { 60 | try 61 | { 62 | var response = Dispatcher.CallHandler(data); 63 | 64 | if (response != null && response.Type != JTokenType.Object) 65 | throw new Exception("Handler response must be an object or null"); 66 | 67 | if (response == null) 68 | { 69 | response = new JObject(); 70 | response["$isNull"] = true; 71 | } 72 | 73 | if (data.ContainsKey("rseq")) response["rseq"] = data["rseq"]; 74 | 75 | var jObject = response.ToObject()!; 76 | 77 | Send(jObject); 78 | } 79 | catch (Exception ex) 80 | { 81 | Logger.Log(LogLevel.Error, Resources.WSHandlerException, ex); 82 | } 83 | }); 84 | break; 85 | } 86 | } 87 | 88 | public virtual void Dispose() => Dispatcher.UnsubscribeAll(this); 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![icon](https://github.com/marzent/IINACT/blob/main/images/icon.ico?raw=true) 2 | 3 | # IINACT 4 | 5 | A [Dalamud](https://github.com/goatcorp/Dalamud) plugin to run the [FFXIV_ACT_Plugin](https://github.com/ravahn/FFXIV_ACT_Plugin) in an [ACT](https://advancedcombattracker.com/)-like enviroment with a heavily modified port of [Overlay Plugin](https://github.com/OverlayPlugin/OverlayPlugin) for modern .NET. 6 | 7 | The data source here is only based on [Unscrambler](https://github.com/perchbirdd/Unscrambler) and does not require any extra injection with [Deucalion](https://github.com/ff14wed/deucalion) or network capture with elevated privileges. 8 | 9 | This will **not** render overlays by itself, use something like [Browsingway](https://github.com/Styr1x/Browsingway), [Next UI](https://github.com/kaminaris/Next-UI), [hudkit](https://github.com/valarnin/hudkit) (Linux only) or [Bunny HUD](https://github.com/marzent/Bunny-HUD) (macOS only) to display Overlays. 10 | 11 | 12 | ## Why 13 | 14 | - ACT is too inconvenient IMHO for just wanting to have the game data parsed and served via a WebSocket server 15 | - Drastically more efficent than ACT, in part to .NET 7.0, in part to a more sane log line processing (disk I/O is not blocking LogLineEvents and happening on a separate lower priority thread) 16 | - Due to the above and running fully inside the game process CPU usage will be orders of magnitude (not exaggerating here) lower when running under Wine compared to network-based capture 17 | - Uses an ultra fast and low latency WebSocket server based on [NetCoreServer](https://github.com/chronoxor/NetCoreServer) 18 | - Doesn't use legacy technology that hurts Linux and macOS users 19 | - Follows the Unix philosophy of just doing one thing and doing it well 20 | 21 | ## Installing 22 | 23 | > **Warning** 24 | > No support will be provided on any Dalamud official support channel. Please use the [Issues](https://github.com/marzent/IINACT/issues) page or [Discord](https://discord.gg/pcexJC8YPG) for any support requests. Do NOT ask for support on the [XIVLauncher & Dalamud Discord](https://discord.gg/holdshift), as support for 3rd-party plugins is not provided there. 25 | 26 | Install instructions can be found [here](https://www.iinact.com/installation/), but are indentical to any other 3rd-party plugin repository. 27 | 28 | ## How to build 29 | 30 | Just run 31 | ``` 32 | git clone --recurse-submodules https://github.com/marzent/IINACT.git 33 | cd IINACT 34 | dotnet build 35 | ``` 36 | on a Linux, macOS or Windows machine with the [.NET 7 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/7.0). 37 | 38 | You will need to be able to reference Dalamud as well, meaning having an install of [XL](https://github.com/goatcorp/FFXIVQuickLauncher) or [XOM](https://github.com/marzent/XIV-on-Mac) on Windows and macOS respectively. On Linux `DALAMUD_HOME` needs to be correctly set (for example `$HOME/.xlcore/dalamud/Hooks/dev`). 39 | 40 | ## FAQ 41 | 42 | **Where are my logs?** 43 | 44 | - In your Documents folder. For Windows users, `C:\Users\[user]\Documents\IINACT`. For Mac/Linux users, same thing, but relative to your wine prefix. 45 | 46 | **Are these logs compatible with FFLogs? Can I use the FFLogs Uploader?** 47 | 48 | - Yes! 100% compatible. 49 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineCountdownCancel.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using RainbowMage.OverlayPlugin.MemoryProcessors; 3 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 6 | { 7 | class LineCountdownCancel : LineBaseCustom< 8 | Server_MessageHeader_Global, LineCountdownCancel.CountdownCancel_v730, 9 | Server_MessageHeader_CN, LineCountdownCancel.CountdownCancel_v655, 10 | Server_MessageHeader_KR, LineCountdownCancel.CountdownCancel_v655> 11 | { 12 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 13 | internal unsafe struct CountdownCancel_v655 : IPacketStruct 14 | { 15 | // 6.5.5 packet data (minus header): 16 | // 34120010 4F00 0000 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20 17 | // AAAAAAAA BBBB CCCC DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD 18 | // 0x0 0x4 0x6 0x8 19 | // Actor ID Wrld Unk Name 20 | 21 | public const int structSize = 40; 22 | [FieldOffset(0x0)] 23 | public uint countdownCancellerActorID; 24 | [FieldOffset(0x4)] 25 | public ushort countdownCancellerWorldId; 26 | 27 | [FieldOffset(0x8)] 28 | public fixed byte countdownCancellerName[32]; 29 | 30 | public string ToString(long epoch, uint ActorID) 31 | { 32 | fixed (byte* name = countdownCancellerName) 33 | { 34 | return 35 | $"{countdownCancellerActorID:X8}|" + 36 | $"{countdownCancellerWorldId:X4}|" + 37 | $"{FFXIVMemory.GetStringFromBytes(name, 32)}"; 38 | } 39 | } 40 | } 41 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 42 | internal unsafe struct CountdownCancel_v730 : IPacketStruct 43 | { 44 | // As of 7.3, there are an extra 0x10 bytes at the start of the packet 45 | 46 | public const int structSize = 56; 47 | [FieldOffset(0x10)] 48 | public uint countdownCancellerActorID; 49 | [FieldOffset(0x14)] 50 | public ushort countdownCancellerWorldId; 51 | 52 | [FieldOffset(0x18)] 53 | public fixed byte countdownCancellerName[32]; 54 | 55 | public string ToString(long epoch, uint ActorID) 56 | { 57 | fixed (byte* name = countdownCancellerName) 58 | { 59 | return 60 | $"{countdownCancellerActorID:X8}|" + 61 | $"{countdownCancellerWorldId:X4}|" + 62 | $"{FFXIVMemory.GetStringFromBytes(name, 32)}"; 63 | } 64 | } 65 | } 66 | public const uint LogFileLineID = 269; 67 | public const string logLineName = "CountdownCancel"; 68 | public const string MachinaPacketName = "CountdownCancel"; 69 | 70 | public LineCountdownCancel(TinyIoCContainer container) 71 | : base(container, LogFileLineID, logLineName, MachinaPacketName) { } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/JobGauge/JobGauge70.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.JobGauge 5 | { 6 | interface IJobGaugeMemory70 : IJobGaugeMemory { } 7 | 8 | partial class JobGaugeMemory70 : JobGaugeMemory, IJobGaugeMemory70 9 | { 10 | private static string jobDataSignature = "488B3D????????33ED"; 11 | private static int jobDataSignatureOffset = -6; 12 | 13 | public JobGaugeMemory70(TinyIoCContainer container) 14 | : base(container, jobDataSignature, jobDataSignatureOffset) 15 | { } 16 | 17 | public override Version GetVersion() 18 | { 19 | return new Version(7, 0); 20 | } 21 | 22 | public override IJobGauge GetJobGauge() 23 | { 24 | if (!IsValid()) return null; 25 | 26 | var jobGaugeManager = GetJobGaugeManager(); 27 | 28 | var ret = new JobGaugeImpl(); 29 | 30 | ret.baseObject = jobGaugeManager; 31 | ret.job = (JobGaugeJob)jobGaugeManager.ClassJobID; 32 | ret.rawData = jobGaugeManager.GetRawGaugeData; 33 | 34 | switch (ret.job) 35 | { 36 | case JobGaugeJob.WHM: ret.data = jobGaugeManager.WhiteMage; break; 37 | case JobGaugeJob.SCH: ret.data = jobGaugeManager.Scholar; break; 38 | case JobGaugeJob.AST: ret.data = jobGaugeManager.Astrologian; break; 39 | case JobGaugeJob.SGE: ret.data = jobGaugeManager.Sage; break; 40 | 41 | case JobGaugeJob.BRD: ret.data = jobGaugeManager.Bard; break; 42 | case JobGaugeJob.MCH: ret.data = jobGaugeManager.Machinist; break; 43 | case JobGaugeJob.DNC: ret.data = jobGaugeManager.Dancer; break; 44 | 45 | case JobGaugeJob.BLM: ret.data = jobGaugeManager.BlackMage; break; 46 | case JobGaugeJob.SMN: ret.data = jobGaugeManager.Summoner; break; 47 | case JobGaugeJob.RDM: ret.data = jobGaugeManager.RedMage; break; 48 | 49 | case JobGaugeJob.MNK: ret.data = jobGaugeManager.Monk; break; 50 | case JobGaugeJob.DRG: ret.data = jobGaugeManager.Dragoon; break; 51 | case JobGaugeJob.NIN: ret.data = jobGaugeManager.Ninja; break; 52 | case JobGaugeJob.SAM: ret.data = jobGaugeManager.Samurai; break; 53 | case JobGaugeJob.RPR: ret.data = jobGaugeManager.Reaper; break; 54 | 55 | case JobGaugeJob.DRK: ret.data = jobGaugeManager.DarkKnight; break; 56 | case JobGaugeJob.PLD: ret.data = jobGaugeManager.Paladin; break; 57 | case JobGaugeJob.WAR: ret.data = jobGaugeManager.Warrior; break; 58 | case JobGaugeJob.GNB: ret.data = jobGaugeManager.Gunbreaker; break; 59 | } 60 | 61 | return ret; 62 | } 63 | 64 | private unsafe JobGaugeManager GetJobGaugeManager() 65 | { 66 | var rawData = memory.GetByteArray(jobGaugeAddress, sizeof(JobGaugeManager)); 67 | fixed (byte* buffer = rawData) 68 | { 69 | return (JobGaugeManager)Marshal.PtrToStructure(new IntPtr(buffer), typeof(JobGaugeManager)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/JobGauge/JobGauge655.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.JobGauge 5 | { 6 | interface IJobGaugeMemory655 : IJobGaugeMemory { } 7 | 8 | partial class JobGaugeMemory655 : JobGaugeMemory, IJobGaugeMemory655 9 | { 10 | private static string jobDataSignature = "488B3D????????33ED"; 11 | private static int jobDataSignatureOffset = -6; 12 | 13 | public JobGaugeMemory655(TinyIoCContainer container) 14 | : base(container, jobDataSignature, jobDataSignatureOffset) 15 | { } 16 | 17 | public override Version GetVersion() 18 | { 19 | return new Version(6, 5, 5); 20 | } 21 | 22 | public override IJobGauge GetJobGauge() 23 | { 24 | if (!IsValid()) return null; 25 | 26 | var jobGaugeManager = GetJobGaugeManager(); 27 | 28 | var ret = new JobGaugeImpl(); 29 | 30 | ret.baseObject = jobGaugeManager; 31 | ret.job = (JobGaugeJob)jobGaugeManager.ClassJobID; 32 | ret.rawData = jobGaugeManager.GetRawGaugeData; 33 | 34 | switch (ret.job) 35 | { 36 | case JobGaugeJob.WHM: ret.data = jobGaugeManager.WhiteMage; break; 37 | case JobGaugeJob.SCH: ret.data = jobGaugeManager.Scholar; break; 38 | case JobGaugeJob.AST: ret.data = jobGaugeManager.Astrologian; break; 39 | case JobGaugeJob.SGE: ret.data = jobGaugeManager.Sage; break; 40 | 41 | case JobGaugeJob.BRD: ret.data = jobGaugeManager.Bard; break; 42 | case JobGaugeJob.MCH: ret.data = jobGaugeManager.Machinist; break; 43 | case JobGaugeJob.DNC: ret.data = jobGaugeManager.Dancer; break; 44 | 45 | case JobGaugeJob.BLM: ret.data = jobGaugeManager.BlackMage; break; 46 | case JobGaugeJob.SMN: ret.data = jobGaugeManager.Summoner; break; 47 | case JobGaugeJob.RDM: ret.data = jobGaugeManager.RedMage; break; 48 | 49 | case JobGaugeJob.MNK: ret.data = jobGaugeManager.Monk; break; 50 | case JobGaugeJob.DRG: ret.data = jobGaugeManager.Dragoon; break; 51 | case JobGaugeJob.NIN: ret.data = jobGaugeManager.Ninja; break; 52 | case JobGaugeJob.SAM: ret.data = jobGaugeManager.Samurai; break; 53 | case JobGaugeJob.RPR: ret.data = jobGaugeManager.Reaper; break; 54 | 55 | case JobGaugeJob.DRK: ret.data = jobGaugeManager.DarkKnight; break; 56 | case JobGaugeJob.PLD: ret.data = jobGaugeManager.Paladin; break; 57 | case JobGaugeJob.WAR: ret.data = jobGaugeManager.Warrior; break; 58 | case JobGaugeJob.GNB: ret.data = jobGaugeManager.Gunbreaker; break; 59 | } 60 | 61 | return ret; 62 | } 63 | 64 | private unsafe JobGaugeManager GetJobGaugeManager() 65 | { 66 | var rawData = memory.GetByteArray(jobGaugeAddress, sizeof(JobGaugeManager)); 67 | fixed (byte* buffer = rawData) 68 | { 69 | return (JobGaugeManager)Marshal.PtrToStructure(new IntPtr(buffer), typeof(JobGaugeManager)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/JobGauge/JobGauge74.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.JobGauge 5 | { 6 | interface IJobGaugeMemory74 : IJobGaugeMemory { } 7 | 8 | partial class JobGaugeMemory74 : JobGaugeMemory, IJobGaugeMemory74 9 | { 10 | private static string jobDataSignature = "488D0D????????0F95C2E8????????488B8D"; 11 | private static int jobDataSignatureOffset = -15; 12 | 13 | public JobGaugeMemory74(TinyIoCContainer container) 14 | : base(container, jobDataSignature, jobDataSignatureOffset) 15 | { } 16 | 17 | public override Version GetVersion() 18 | { 19 | return new Version(7, 4); 20 | } 21 | 22 | public override IJobGauge GetJobGauge() 23 | { 24 | if (!IsValid()) return null; 25 | 26 | var jobGaugeManager = GetJobGaugeManager(); 27 | 28 | var ret = new JobGaugeImpl(); 29 | 30 | ret.baseObject = jobGaugeManager; 31 | ret.job = (JobGaugeJob)jobGaugeManager.ClassJobID; 32 | ret.rawData = jobGaugeManager.GetRawGaugeData; 33 | 34 | switch (ret.job) 35 | { 36 | case JobGaugeJob.WHM: ret.data = jobGaugeManager.WhiteMage; break; 37 | case JobGaugeJob.SCH: ret.data = jobGaugeManager.Scholar; break; 38 | case JobGaugeJob.AST: ret.data = jobGaugeManager.Astrologian; break; 39 | case JobGaugeJob.SGE: ret.data = jobGaugeManager.Sage; break; 40 | 41 | case JobGaugeJob.BRD: ret.data = jobGaugeManager.Bard; break; 42 | case JobGaugeJob.MCH: ret.data = jobGaugeManager.Machinist; break; 43 | case JobGaugeJob.DNC: ret.data = jobGaugeManager.Dancer; break; 44 | 45 | case JobGaugeJob.BLM: ret.data = jobGaugeManager.BlackMage; break; 46 | case JobGaugeJob.SMN: ret.data = jobGaugeManager.Summoner; break; 47 | case JobGaugeJob.RDM: ret.data = jobGaugeManager.RedMage; break; 48 | 49 | case JobGaugeJob.MNK: ret.data = jobGaugeManager.Monk; break; 50 | case JobGaugeJob.DRG: ret.data = jobGaugeManager.Dragoon; break; 51 | case JobGaugeJob.NIN: ret.data = jobGaugeManager.Ninja; break; 52 | case JobGaugeJob.SAM: ret.data = jobGaugeManager.Samurai; break; 53 | case JobGaugeJob.RPR: ret.data = jobGaugeManager.Reaper; break; 54 | 55 | case JobGaugeJob.DRK: ret.data = jobGaugeManager.DarkKnight; break; 56 | case JobGaugeJob.PLD: ret.data = jobGaugeManager.Paladin; break; 57 | case JobGaugeJob.WAR: ret.data = jobGaugeManager.Warrior; break; 58 | case JobGaugeJob.GNB: ret.data = jobGaugeManager.Gunbreaker; break; 59 | } 60 | 61 | return ret; 62 | } 63 | 64 | private unsafe JobGaugeManager GetJobGaugeManager() 65 | { 66 | var rawData = memory.GetByteArray(jobGaugeAddress, sizeof(JobGaugeManager)); 67 | fixed (byte* buffer = rawData) 68 | { 69 | return (JobGaugeManager)Marshal.PtrToStructure(new IntPtr(buffer), typeof(JobGaugeManager)); 70 | } 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineActorControlSelfExtra.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.Linq; 4 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 5 | 6 | // To test `DisplayLogMessage`, you can: 7 | // Open a treasure coffer that gives you item(s): 8 | // 274|2024-01-10T19:28:37.5000000-05:00|10001234|020F|04D0|0|93E0|0|0|0|d274429622d0c27e 9 | // 274|2024-01-10T19:28:37.5000000-05:00|10001234|020F|04D0|0|93F3|0|0|0|d274429622d0c27e 10 | // 00|2024-01-10T19:28:36.0000000-05:00|0A3E||You obtain a windswept shamshir.|92337ce2a33e52f8 11 | // 00|2024-01-10T19:28:36.0000000-05:00|0A3E||You obtain a windswept shield.|a48cbf20d0255d4e 12 | // Sell TT cards for MGP: 13 | // 274|2024-01-10T20:08:35.3520000-05:00|10001234|020F|129D|0|320|0|0|0|d274429622d0c27e 14 | // 00|2024-01-10T20:08:35.0000000-05:00|0A3E||You obtain 800 MGP.|f768dc4f098c15a6 15 | // Die in Eureka with a `Spirit of the Remembered` active: 16 | // 274|2024-02-15T19:35:41.9950000-05:00|10001234|020F|236D|0|669|0|0|0|d274429622d0c27e 17 | // 00|2024-02-15T19:35:41.0000000-05:00|0939||The memories of heroes past live on again!|bb3bfbfc487ad4e9 18 | 19 | // To test `DisplayLogMessageParams`, you can play a Gold Saucer minigame: 20 | // 274|2024-03-21T20:45:41.3680000-04:00|10001234|0210|129D|10001234|F|0|0|0|d274429622d0c27e 21 | // 00|2024-03-21T20:45:40.0000000-04:00|08BE||You obtain 15 MGP.|97702e809544a633 22 | 23 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 24 | { 25 | class LineActorControlSelfExtra : LineBaseSubMachina 26 | { 27 | public const uint LogFileLineID = 274; 28 | public const string LogLineName = "ActorControlSelfExtra"; 29 | public const string MachinaPacketName = "ActorControlSelf"; 30 | 31 | // Any category defined in this array will be allowed as an emitted line 32 | public static readonly Server_ActorControlCategory[] AllowedActorControlCategories = { 33 | // Some `LogMessage` messages can be triggered by both 0x020F and 0x0210 categories, not sure what the difference is 34 | // except that 0x0210 messages usually have another actor ID in the parameters 35 | Server_ActorControlCategory.DisplayLogMessage, 36 | Server_ActorControlCategory.DisplayLogMessageParams, 37 | }; 38 | 39 | internal class ActorControlSelfExtraPacket : MachinaPacketWrapper 40 | { 41 | public override string ToString(long epoch, uint ActorID) 42 | { 43 | var category = Get("category"); 44 | 45 | if (!AllowedActorControlCategories.Contains(category)) return null; 46 | 47 | var param1 = Get("param1"); 48 | var param2 = Get("param2"); 49 | var param3 = Get("param3"); 50 | var param4 = Get("param4"); 51 | var param5 = Get("param5"); 52 | var param6 = Get("param6"); 53 | 54 | return $"{ActorID:X8}|{(ushort)category:X4}|{param1:X}|{param2:X}|{param3:X}|{param4:X}|{param5:X}|{param6:X}"; 55 | } 56 | } 57 | public LineActorControlSelfExtra(TinyIoCContainer container) 58 | : base(container, LogFileLineID, LogLineName, MachinaPacketName) { } 59 | } 60 | } -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/PacketHelper/LineBaseCustomMachina.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Machina.FFXIV; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper 6 | { 7 | abstract class LineBaseCustomMachina< 8 | HeaderStruct_Global, PacketStruct_Global, 9 | HeaderStruct_CN, PacketStruct_CN, 10 | HeaderStruct_KR, PacketStruct_KR> 11 | where HeaderStruct_Global : struct, IHeaderStruct 12 | where PacketStruct_Global : struct, IPacketStruct 13 | where HeaderStruct_CN : struct, IHeaderStruct 14 | where PacketStruct_CN : struct, IPacketStruct 15 | where HeaderStruct_KR : struct, IHeaderStruct 16 | where PacketStruct_KR : struct, IPacketStruct 17 | { 18 | protected static FFXIVRepository ffxiv; 19 | 20 | protected readonly Func logWriter; 21 | protected readonly RegionalizedPacketHelper< 22 | HeaderStruct_Global, PacketStruct_Global, 23 | HeaderStruct_CN, PacketStruct_CN, 24 | HeaderStruct_KR, PacketStruct_KR> packetHelper; 25 | protected GameRegion? currentRegion; 26 | 27 | public LineBaseCustomMachina(TinyIoCContainer container, uint logFileLineID, string logLineName, string machinaPacketName) 28 | { 29 | ffxiv = ffxiv ?? container.Resolve(); 30 | ffxiv.RegisterNetworkParser(MessageReceived); 31 | ffxiv.RegisterProcessChangedHandler(ProcessChanged); 32 | 33 | packetHelper = RegionalizedPacketHelper< 34 | HeaderStruct_Global, PacketStruct_Global, 35 | HeaderStruct_CN, PacketStruct_CN, 36 | HeaderStruct_KR, PacketStruct_KR>.CreateFromMachina(machinaPacketName); 37 | 38 | if (packetHelper == null) 39 | { 40 | var logger = container.Resolve(); 41 | logger.Log(LogLevel.Error, $"Failed to initialize {logLineName}: Failed to create {machinaPacketName} packet helper from Machina structs"); 42 | return; 43 | } 44 | 45 | var customLogLines = container.Resolve(); 46 | logWriter = customLogLines.RegisterCustomLogLine(new LogLineRegistryEntry() 47 | { 48 | Name = logLineName, 49 | Source = "OverlayPlugin", 50 | ID = logFileLineID, 51 | Version = 1, 52 | }); 53 | } 54 | 55 | protected virtual void ProcessChanged(Process process) 56 | { 57 | if (!ffxiv.IsFFXIVPluginPresent()) 58 | return; 59 | 60 | currentRegion = null; 61 | } 62 | 63 | protected virtual unsafe void MessageReceived(string id, long epoch, byte[] message) 64 | { 65 | if (packetHelper == null) 66 | return; 67 | 68 | if (currentRegion == null) 69 | currentRegion = ffxiv.GetMachinaRegion(); 70 | 71 | if (currentRegion == null) 72 | return; 73 | 74 | var line = packetHelper[currentRegion.Value].ToString(epoch, message); 75 | 76 | if (line != null) 77 | { 78 | DateTime serverTime = ffxiv.EpochToDateTime(epoch); 79 | logWriter(line, serverTime); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineCEDirector.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Runtime.InteropServices; 3 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 6 | { 7 | class LineCEDirector : LineBaseCustom< 8 | Server_MessageHeader_Global, LineCEDirector.CEDirector_v62, 9 | Server_MessageHeader_CN, LineCEDirector.CEDirector_v62, 10 | Server_MessageHeader_KR, LineCEDirector.CEDirector_v62> 11 | { 12 | [StructLayout(LayoutKind.Explicit)] 13 | internal struct CEDirector_v62 : IPacketStruct 14 | { 15 | [FieldOffset(0x0)] 16 | public uint popTime; 17 | [FieldOffset(0x4)] 18 | public ushort timeRemaining; 19 | [FieldOffset(0x6)] 20 | public ushort unk9; 21 | [FieldOffset(0x8)] 22 | public byte ceKey; 23 | [FieldOffset(0x9)] 24 | public byte numPlayers; 25 | [FieldOffset(0xA)] 26 | public byte status; 27 | [FieldOffset(0xB)] 28 | public byte unk10; 29 | [FieldOffset(0xC)] 30 | public byte progress; 31 | [FieldOffset(0xD)] 32 | public byte unk11; 33 | [FieldOffset(0xE)] 34 | public byte unk12; 35 | [FieldOffset(0xF)] 36 | public byte unk13; 37 | 38 | public string ToString(long epoch, uint ActorID) 39 | { 40 | string line = 41 | $"{popTime:X8}|" + 42 | $"{timeRemaining:X4}|" + 43 | $"{unk9:X4}|" + 44 | $"{ceKey:X2}|" + 45 | $"{numPlayers:X2}|" + 46 | $"{status:X2}|" + 47 | $"{unk10:X2}|" + 48 | $"{progress:X2}|" + 49 | $"{unk11:X2}|" + 50 | $"{unk12:X2}|" + 51 | $"{unk13:X2}"; 52 | 53 | var isBeingRemoved = status == 0; 54 | if (isBeingRemoved) 55 | { 56 | if (!ces.Remove(ceKey)) 57 | { 58 | return null; 59 | } 60 | } 61 | else 62 | { 63 | string oldData; 64 | if (ces.TryGetValue(ceKey, out oldData)) 65 | { 66 | if (oldData == line) 67 | { 68 | return null; 69 | } 70 | } 71 | ces[ceKey] = line; 72 | } 73 | 74 | return line; 75 | } 76 | } 77 | public const uint LogFileLineID = 259; 78 | public const string logLineName = "CEDirector"; 79 | public const string MachinaPacketName = "CEDirector"; 80 | 81 | // Used to reduce spam of these packets to log file 82 | // Only emit a line if it doesn't match the last line for this CE ID 83 | private static Dictionary ces = new Dictionary(); 84 | 85 | public LineCEDirector(TinyIoCContainer container) 86 | : base(container, LogFileLineID, logLineName, MachinaPacketName) 87 | { 88 | ffxiv.RegisterZoneChangeDelegate((zoneID, zoneName) => ces.Clear()); 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/ContentFinderSettings/LineContentFinderSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Advanced_Combat_Tracker; 3 | 4 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.ContentFinderSettings 5 | { 6 | class LineContentFinderSettings 7 | { 8 | public const uint LogFileLineID = 265; 9 | private readonly FFXIVRepository ffxiv; 10 | 11 | private Func logWriter; 12 | 13 | private IContentFinderSettingsMemory contentFinderSettingsMemory; 14 | 15 | public LineContentFinderSettings(TinyIoCContainer container) 16 | { 17 | ffxiv = container.Resolve(); 18 | if (!ffxiv.IsFFXIVPluginPresent()) 19 | return; 20 | contentFinderSettingsMemory = container.Resolve(); 21 | var customLogLines = container.Resolve(); 22 | this.logWriter = customLogLines.RegisterCustomLogLine(new LogLineRegistryEntry() 23 | { 24 | Name = "ContentFinderSettings", 25 | Source = "OverlayPlugin", 26 | ID = LogFileLineID, 27 | Version = 1, 28 | }); 29 | 30 | ffxiv.RegisterZoneChangeDelegate(OnZoneChange); 31 | 32 | // Theoretically we should be able to check `ffxiv.GetCurrentTerritoryID()` for a value and log it here. 33 | // However, this returns `0`, whether checking before or after registering the zone change delegate 34 | // and the zone change delegate doesn't get called if the game's already running when starting ACT 35 | 36 | // Instead, use a janky workaround here. Register a log line listener and then once we write our first line, unregister it. 37 | 38 | ActGlobals.oFormActMain.BeforeLogLineRead += LogLineHandler; 39 | } 40 | private void LogLineHandler(bool isImport, LogLineEventArgs args) 41 | { 42 | if (!contentFinderSettingsMemory.IsValid()) 43 | return; 44 | 45 | var currentZoneId = ffxiv.GetCurrentTerritoryID(); 46 | if (currentZoneId.HasValue && currentZoneId.Value > 0) 47 | { 48 | var currentZoneName = ActGlobals.oFormActMain.CurrentZone; 49 | WriteInContentFinderSettingsLine(args.detectedTime, $"{currentZoneId.Value:X4}", currentZoneName); 50 | ActGlobals.oFormActMain.BeforeLogLineRead -= LogLineHandler; 51 | } 52 | } 53 | 54 | private void OnZoneChange(uint zoneId, string zoneName) 55 | { 56 | if (!contentFinderSettingsMemory.IsValid()) 57 | return; 58 | WriteInContentFinderSettingsLine(DateTime.Now, $"{zoneId:X}", zoneName); 59 | } 60 | 61 | private void WriteInContentFinderSettingsLine(DateTime dateTime, string zoneID, string zoneName) 62 | { 63 | var settings = contentFinderSettingsMemory.GetContentFinderSettings(); 64 | 65 | logWriter.Invoke( 66 | $"{zoneID}|" + 67 | $"{zoneName}|" + 68 | $"{settings.inContentFinderContent}|" + 69 | $"{settings.unrestrictedParty}|" + 70 | $"{settings.minimalItemLevel}|" + 71 | $"{settings.silenceEcho}|" + 72 | $"{settings.explorerMode}|" + 73 | $"{settings.levelSync}", 74 | dateTime); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/PacketHelper/LineBaseCustom.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Machina.FFXIV; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper 6 | { 7 | abstract class LineBaseCustom< 8 | HeaderStruct_Global, PacketStruct_Global, 9 | HeaderStruct_CN, PacketStruct_CN, 10 | HeaderStruct_KR, PacketStruct_KR> 11 | where HeaderStruct_Global : struct, IHeaderStruct 12 | where PacketStruct_Global : struct, IPacketStruct 13 | where HeaderStruct_CN : struct, IHeaderStruct 14 | where PacketStruct_CN : struct, IPacketStruct 15 | where HeaderStruct_KR : struct, IHeaderStruct 16 | where PacketStruct_KR : struct, IPacketStruct 17 | { 18 | protected static FFXIVRepository ffxiv; 19 | 20 | protected readonly Func logWriter; 21 | protected readonly RegionalizedPacketHelper< 22 | HeaderStruct_Global, PacketStruct_Global, 23 | HeaderStruct_CN, PacketStruct_CN, 24 | HeaderStruct_KR, PacketStruct_KR> packetHelper; 25 | protected GameRegion? currentRegion; 26 | 27 | protected LineBaseCustom(TinyIoCContainer container, uint logFileLineID, string logLineName, string opcodeName) 28 | { 29 | ffxiv = ffxiv ?? container.Resolve(); 30 | ffxiv.RegisterNetworkParser(MessageReceived); 31 | ffxiv.RegisterProcessChangedHandler(ProcessChanged); 32 | 33 | var opcodeConfig = container.Resolve(); 34 | 35 | packetHelper = RegionalizedPacketHelper< 36 | HeaderStruct_Global, PacketStruct_Global, 37 | HeaderStruct_CN, PacketStruct_CN, 38 | HeaderStruct_KR, PacketStruct_KR>.CreateFromOpcodeConfig(opcodeConfig, opcodeName); 39 | 40 | if (packetHelper == null) 41 | { 42 | var logger = container.Resolve(); 43 | logger.Log(LogLevel.Error, $"Failed to initialize {logLineName}: Failed to create {opcodeName} packet helper from opcode configs and native structs"); 44 | return; 45 | } 46 | 47 | var customLogLines = container.Resolve(); 48 | logWriter = customLogLines.RegisterCustomLogLine(new LogLineRegistryEntry() 49 | { 50 | Name = logLineName, 51 | Source = "OverlayPlugin", 52 | ID = logFileLineID, 53 | Version = 1, 54 | }); 55 | } 56 | 57 | protected virtual void ProcessChanged(Process process) 58 | { 59 | if (!ffxiv.IsFFXIVPluginPresent()) 60 | return; 61 | 62 | currentRegion = null; 63 | } 64 | 65 | protected virtual unsafe void MessageReceived(string id, long epoch, byte[] message) 66 | { 67 | if (packetHelper == null) 68 | return; 69 | 70 | if (currentRegion == null) 71 | currentRegion = ffxiv.GetMachinaRegion(); 72 | 73 | if (currentRegion == null) 74 | return; 75 | 76 | var line = packetHelper[currentRegion.Value].ToString(epoch, message); 77 | 78 | if (line != null) 79 | { 80 | DateTime serverTime = ffxiv.EpochToDateTime(epoch); 81 | logWriter(line, serverTime); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /NotACT/FormActMain.resx: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | text/microsoft-resx 51 | 52 | 53 | 2.0 54 | 55 | 56 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, 57 | PublicKeyToken=b77a5c561934e089 58 | 59 | 60 | 61 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, 62 | PublicKeyToken=b77a5c561934e089 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /IINACT/IpcProviders.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Dalamud.Plugin; 3 | using Dalamud.Plugin.Ipc; 4 | 5 | namespace IINACT; 6 | 7 | internal class IpcProviders : IDisposable 8 | { 9 | internal static Version IpcVersion => new(2, 1, 0); 10 | internal readonly ICallGateProvider GetVersion; 11 | internal readonly ICallGateProvider GetIpcVersion; 12 | 13 | public RainbowMage.OverlayPlugin.WebSocket.ServerController? Server { get; set; } 14 | public RainbowMage.OverlayPlugin.Handlers.Ipc.IpcHandlerController? OverlayIpcHandler { get; set; } 15 | 16 | internal readonly ICallGateProvider CreateSubscriber; 17 | internal readonly ICallGateProvider CreateLegacySubscriber; 18 | internal readonly ICallGateProvider Unsubscribe; 19 | internal readonly ICallGateProvider GetServerRunning; 20 | internal readonly ICallGateProvider GetServerPort; 21 | internal readonly ICallGateProvider GetServerIp; 22 | internal readonly ICallGateProvider GetServerSslEnabled; 23 | internal readonly ICallGateProvider GetServerUri; 24 | 25 | internal IpcProviders(IDalamudPluginInterface pluginInterface) 26 | { 27 | GetVersion = pluginInterface.GetIpcProvider("IINACT.Version"); 28 | GetIpcVersion = pluginInterface.GetIpcProvider("IINACT.IpcVersion"); 29 | 30 | CreateSubscriber = pluginInterface.GetIpcProvider("IINACT.CreateSubscriber"); 31 | CreateLegacySubscriber = pluginInterface.GetIpcProvider("IINACT.CreateLegacySubscriber"); 32 | Unsubscribe = pluginInterface.GetIpcProvider("IINACT.Unsubscribe"); 33 | 34 | GetServerRunning = pluginInterface.GetIpcProvider("IINACT.Server.Listening"); 35 | GetServerPort = pluginInterface.GetIpcProvider("IINACT.Server.Port"); 36 | GetServerIp = pluginInterface.GetIpcProvider("IINACT.Server.Ip"); 37 | GetServerSslEnabled = pluginInterface.GetIpcProvider("IINACT.Server.SslEnabled"); 38 | GetServerUri = pluginInterface.GetIpcProvider("IINACT.Server.Uri"); 39 | 40 | Register(); 41 | } 42 | 43 | private void Register() 44 | { 45 | GetVersion.RegisterFunc(() => Assembly.GetExecutingAssembly().GetName().Version!); 46 | GetIpcVersion.RegisterFunc(() => IpcVersion); 47 | 48 | CreateSubscriber.RegisterFunc(name => OverlayIpcHandler?.CreateSubscriber(name) ?? false); 49 | CreateLegacySubscriber.RegisterFunc(name => OverlayIpcHandler?.CreateLegacySubscriber(name) ?? false); 50 | Unsubscribe.RegisterFunc(name => OverlayIpcHandler?.Unsubscribe(name) ?? false); 51 | 52 | GetServerRunning.RegisterFunc(() => Server?.Running ?? false); 53 | GetServerPort.RegisterFunc(() => Server?.Port ?? 0); 54 | GetServerIp.RegisterFunc(() => Server?.Address ?? ""); 55 | GetServerSslEnabled.RegisterFunc(() => Server?.Secure ?? false); 56 | GetServerUri.RegisterFunc(() => Server?.Uri); 57 | } 58 | 59 | public void Dispose() 60 | { 61 | GetVersion.UnregisterFunc(); 62 | GetIpcVersion.UnregisterFunc(); 63 | 64 | CreateSubscriber.UnregisterFunc(); 65 | CreateLegacySubscriber.UnregisterFunc(); 66 | Unsubscribe.UnregisterFunc(); 67 | 68 | GetServerRunning.UnregisterFunc(); 69 | GetServerPort.UnregisterFunc(); 70 | GetServerIp.UnregisterFunc(); 71 | GetServerSslEnabled.UnregisterFunc(); 72 | GetServerUri.UnregisterFunc(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineFateControl.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 5 | 6 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 7 | { 8 | class LineFateControl : LineBaseSubMachina 9 | { 10 | public static readonly Server_ActorControlCategory[] FateActorControlCategories = { 11 | Server_ActorControlCategory.FateAdd, 12 | Server_ActorControlCategory.FateRemove, 13 | Server_ActorControlCategory.FateUpdate, 14 | }; 15 | 16 | internal class FateControlPacket : MachinaPacketWrapper 17 | { 18 | public override string ToString(long epoch, uint ActorID) 19 | { 20 | var category = Get("category"); 21 | 22 | if (!FateActorControlCategories.Contains(category)) return null; 23 | 24 | var padding = Get("padding"); 25 | var fateID = Get("param1"); 26 | var progress = Get("param2"); 27 | var param3 = Get("param3"); 28 | var param4 = Get("param4"); 29 | var param5 = Get("param5"); 30 | var param6 = Get("param6"); 31 | var padding1 = Get("padding1"); 32 | 33 | var categoryStr = category.ToString().Replace("Fate", ""); 34 | 35 | // Do some basic filtering on fate data to avoid spamming the log needlessly. 36 | if (category == Server_ActorControlCategory.FateAdd) 37 | { 38 | if (fates.ContainsKey(fateID)) 39 | { 40 | return null; 41 | } 42 | fates.Add(fateID, 0); 43 | } 44 | else if (category == Server_ActorControlCategory.FateRemove) 45 | { 46 | if (!fates.Remove(fateID)) 47 | { 48 | return null; 49 | } 50 | } 51 | else if (category == Server_ActorControlCategory.FateUpdate) 52 | { 53 | if (fates.TryGetValue(fateID, out var oldProgress)) 54 | { 55 | if (progress == oldProgress) 56 | { 57 | return null; 58 | } 59 | } 60 | fates[fateID] = progress; 61 | } 62 | 63 | return $"{categoryStr}|" + 64 | $"{padding:X4}|" + 65 | $"{fateID:X8}|" + 66 | $"{progress:X8}|" + 67 | $"{param3:X8}|" + 68 | $"{param4:X8}|" + 69 | $"{param5:X8}|" + 70 | $"{param6:X8}|" + 71 | $"{padding1:X8}"; 72 | } 73 | } 74 | 75 | private static Dictionary fates = new Dictionary(); 76 | 77 | public const uint LogFileLineID = 258; 78 | 79 | public const string LogLineName = "FateDirector"; 80 | public const string MachinaPacketName = "ActorControlSelf"; 81 | 82 | public LineFateControl(TinyIoCContainer container) 83 | : base(container, LogFileLineID, LogLineName, MachinaPacketName) 84 | { 85 | ffxiv.RegisterZoneChangeDelegate((zoneID, zoneName) => fates.Clear()); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /OverlayPlugin.Common/Registry.cs: -------------------------------------------------------------------------------- 1 | using Advanced_Combat_Tracker; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace RainbowMage.OverlayPlugin 6 | { 7 | public class Registry 8 | { 9 | private readonly TinyIoCContainer _container; 10 | private readonly List _overlays; 11 | private readonly List _eventSources; 12 | private readonly List _overlayTemplates; 13 | 14 | public IEnumerable Overlays => _overlays; 15 | 16 | public IEnumerable EventSources => _eventSources; 17 | 18 | public IReadOnlyList OverlayTemplates => _overlayTemplates; 19 | 20 | public event EventHandler EventSourceRegistered; 21 | public event EventHandler EventSourcesStarted; 22 | 23 | public Registry(TinyIoCContainer container) 24 | { 25 | _container = container; 26 | _overlays = new List(); 27 | _eventSources = new List(); 28 | _overlayTemplates = new List(); 29 | } 30 | 31 | public void RegisterOverlay() 32 | where T : class, IOverlay 33 | { 34 | _overlays.Add(typeof(T)); 35 | _container.Register(); 36 | } 37 | 38 | public void UnregisterOverlay() 39 | where T : class, IOverlay 40 | { 41 | _overlays.Remove(typeof(T)); 42 | } 43 | 44 | public void StartEventSource(T source) 45 | where T : class, IEventSource 46 | { 47 | _container.BuildUp(source); 48 | _eventSources.Add(source); 49 | _container.Register(source); 50 | 51 | source.LoadConfig(_container.Resolve()); 52 | source.Start(); 53 | 54 | EventSourceRegistered?.Invoke(null, new EventSourceRegisteredEventArgs(source)); 55 | } 56 | 57 | [Obsolete("Please call StartEventSource() on the Registry object instead.")] 58 | public static void RegisterEventSource() 59 | where T : class, IEventSource 60 | { 61 | var container = GetContainer(); 62 | var logger = container.Resolve(); 63 | var obj = (T)typeof(T).GetConstructor(new Type[] { typeof(ILogger) })?.Invoke(new object[] { logger }); 64 | container.Resolve().StartEventSource(obj); 65 | } 66 | 67 | public void RegisterOverlayPreset2(IOverlayTemplate preset) 68 | { 69 | _overlayTemplates.Add(preset); 70 | } 71 | 72 | [Obsolete("Please call RegisterOverlayPreset2() on the Registry object instead.")] 73 | public static void RegisterOverlayPreset(IOverlayTemplate preset) 74 | { 75 | GetContainer().Resolve().RegisterOverlayPreset2(preset); 76 | } 77 | 78 | public void StartEventSources() 79 | { 80 | if (EventSourcesStarted == null) 81 | return; 82 | EventSourcesStarted(null, null); 83 | } 84 | 85 | // For backwards compat only!! 86 | public static TinyIoCContainer GetContainer() 87 | { 88 | return (TinyIoCContainer)ActGlobals.oFormActMain.OverlayPluginContainer; 89 | } 90 | } 91 | 92 | public class EventSourceRegisteredEventArgs : EventArgs 93 | { 94 | public IEventSource EventSource { get; private set; } 95 | 96 | public EventSourceRegisteredEventArgs(IEventSource eventSource) 97 | { 98 | this.EventSource = eventSource; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/JSApi/OverlayApi.cs: -------------------------------------------------------------------------------- 1 | using Advanced_Combat_Tracker; 2 | using System; 3 | using System.Threading.Tasks; 4 | 5 | namespace RainbowMage.OverlayPlugin 6 | { 7 | internal class OverlayApi 8 | { 9 | public static event EventHandler BroadcastMessage; 10 | public static event EventHandler SendMessage; 11 | public static event EventHandler OverlayMessage; 12 | 13 | private readonly EventDispatcher dispatcher; 14 | private readonly IApiBase receiver; 15 | private readonly ILogger logger; 16 | 17 | public OverlayApi(TinyIoCContainer container, IApiBase receiver) 18 | { 19 | this.dispatcher = container.Resolve(); 20 | this.receiver = receiver; 21 | this.logger = container.Resolve(); 22 | } 23 | 24 | public void broadcastMessage(string msg) 25 | { 26 | logger.Log(LogLevel.Error, 27 | $"{receiver.Name}: OverlayPluginApi.broadcastMessage() is deprecated and will be removed in future OverlayPlugin versions!"); 28 | BroadcastMessage(this, new BroadcastMessageEventArgs(msg)); 29 | } 30 | 31 | public void sendMessage(string target, string msg) 32 | { 33 | logger.Log(LogLevel.Error, 34 | $"{receiver.Name}: OverlayPluginApi.sendMessage() is deprecated and will be removed in future OverlayPlugin versions!"); 35 | SendMessage(this, new SendMessageEventArgs(target, msg)); 36 | } 37 | 38 | public void overlayMessage(string target, string msg) 39 | { 40 | logger.Log(LogLevel.Error, 41 | $"{receiver.Name}: OverlayPluginApi.overlayMessage() is deprecated and will be removed in future OverlayPlugin versions!"); 42 | if (target == receiver.Name) 43 | { 44 | receiver.OverlayMessage(msg); 45 | } 46 | else 47 | { 48 | OverlayMessage(this, new SendMessageEventArgs(target, msg)); 49 | } 50 | } 51 | 52 | public void endEncounter() 53 | { 54 | ActGlobals.oFormActMain.EndCombat(true); 55 | } 56 | 57 | // Also handles (un)subscription to make switching between this and WS easier. 58 | public void callHandler(string data, object callback) 59 | { 60 | // Tell the overlay that the page is using the modern API. 61 | receiver.InitModernAPI(); 62 | 63 | Task.Run(() => 64 | { 65 | var result = dispatcher.ProcessHandlerMessage(receiver, data); 66 | if (callback != null) 67 | { 68 | //Renderer.ExecuteCallback(callback, result?.ToString(Newtonsoft.Json.Formatting.None)); 69 | } 70 | }); 71 | } 72 | } 73 | 74 | public class BroadcastMessageEventArgs : EventArgs 75 | { 76 | public string Message { get; private set; } 77 | 78 | public BroadcastMessageEventArgs(string message) 79 | { 80 | this.Message = message; 81 | } 82 | } 83 | 84 | public class SendMessageEventArgs : EventArgs 85 | { 86 | public string Target { get; private set; } 87 | public string Message { get; private set; } 88 | 89 | public SendMessageEventArgs(string target, string message) 90 | { 91 | this.Target = target; 92 | this.Message = message; 93 | } 94 | } 95 | 96 | public class EndEncounterEventArgs : EventArgs 97 | { 98 | public EndEncounterEventArgs() { } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /FetchDependencies/FetchDependencies.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Compression; 2 | 3 | namespace FetchDependencies; 4 | 5 | public class FetchDependencies 6 | { 7 | private const string VersionUrlGlobal = "https://www.iinact.com/updater/version"; 8 | private const string VersionUrlChinese = "https://cninact.diemoe.net/CN解析/版本.txt"; 9 | private const string PluginUrlGlobal = "https://www.iinact.com/updater/download"; 10 | private const string PluginUrlChinese = "https://cninact.diemoe.net/CN解析/FFXIV_ACT_Plugin.dll"; 11 | 12 | private Version PluginVersion { get; } 13 | private string DependenciesDir { get; } 14 | private bool IsChinese { get; } 15 | private HttpClient HttpClient { get; } 16 | 17 | public FetchDependencies(Version version, string assemblyDir, bool isChinese, HttpClient httpClient) 18 | { 19 | PluginVersion = version; 20 | DependenciesDir = assemblyDir; 21 | IsChinese = isChinese; 22 | HttpClient = httpClient; 23 | } 24 | 25 | public void GetFfxivPlugin() 26 | { 27 | var pluginZipPath = Path.Combine(DependenciesDir, "FFXIV_ACT_Plugin.zip"); 28 | var pluginPath = Path.Combine(DependenciesDir, "FFXIV_ACT_Plugin.dll"); 29 | var deucalionPath = Path.Combine(DependenciesDir, "deucalion-1.1.0.distrib.dll"); 30 | 31 | if (!NeedsUpdate(pluginPath)) 32 | return; 33 | 34 | if (IsChinese) 35 | DownloadFile(PluginUrlChinese, pluginPath); 36 | else 37 | { 38 | if (!File.Exists(pluginZipPath)) 39 | DownloadFile(PluginUrlGlobal, pluginZipPath); 40 | try 41 | { 42 | ZipFile.ExtractToDirectory(pluginZipPath, DependenciesDir, true); 43 | } 44 | catch (InvalidDataException) 45 | { 46 | File.Delete(pluginZipPath); 47 | DownloadFile(PluginUrlGlobal, pluginZipPath); 48 | ZipFile.ExtractToDirectory(pluginZipPath, DependenciesDir, true); 49 | } 50 | File.Delete(pluginZipPath); 51 | 52 | foreach (var deucalionDll in Directory.GetFiles(DependenciesDir, "deucalion*.dll")) 53 | File.Delete(deucalionDll); 54 | } 55 | 56 | var patcher = new Patcher(PluginVersion, DependenciesDir); 57 | patcher.MainPlugin(); 58 | patcher.LogFilePlugin(); 59 | patcher.MemoryPlugin(); 60 | } 61 | 62 | private bool NeedsUpdate(string dllPath) 63 | { 64 | if (!File.Exists(dllPath)) return true; 65 | try 66 | { 67 | using var plugin = new TargetAssembly(dllPath); 68 | 69 | if (!plugin.ApiVersionMatches()) 70 | return true; 71 | 72 | using var cancelAfterDelay = new CancellationTokenSource(TimeSpan.FromSeconds(3)); 73 | var remoteVersionString = HttpClient 74 | .GetStringAsync(IsChinese ? VersionUrlChinese : VersionUrlGlobal, 75 | cancelAfterDelay.Token).Result; 76 | var remoteVersion = new Version(remoteVersionString); 77 | return remoteVersion > plugin.Version; 78 | } 79 | catch 80 | { 81 | return false; 82 | } 83 | } 84 | 85 | private void DownloadFile(string url, string path) 86 | { 87 | using var cancelAfterDelay = new CancellationTokenSource(TimeSpan.FromSeconds(30)); 88 | using var downloadStream = HttpClient 89 | .GetStreamAsync(url, 90 | cancelAfterDelay.Token).Result; 91 | using var zipFileStream = new FileStream(path, FileMode.Create); 92 | downloadStream.CopyTo(zipFileStream); 93 | zipFileStream.Close(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/NetworkProcessors/LineCountdown.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using RainbowMage.OverlayPlugin.MemoryProcessors; 3 | using RainbowMage.OverlayPlugin.NetworkProcessors.PacketHelper; 4 | 5 | namespace RainbowMage.OverlayPlugin.NetworkProcessors 6 | { 7 | class LineCountdown : LineBaseCustom< 8 | Server_MessageHeader_Global, LineCountdown.Countdown_v730, 9 | Server_MessageHeader_CN, LineCountdown.Countdown_v655, 10 | Server_MessageHeader_KR, LineCountdown.Countdown_v655> 11 | { 12 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 13 | internal unsafe struct Countdown_v655 : IPacketStruct 14 | { 15 | // 6.5.5 packet data (minus header): 16 | // 34120010 4F00 1500 53 00 00 0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F20 00000000 17 | // AAAAAAAA CCCC BBBB DD EE FF GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG HHHHHHHH 18 | // 0x0 0x4 0x6 0x8 0x9 0xA 0xB 19 | // Actor ID Wrld Time Res Unk Unk Name 20 | 21 | public const int structSize = 48; 22 | [FieldOffset(0x0)] 23 | public uint countdownStarterActorID; 24 | [FieldOffset(0x4)] 25 | public ushort countdownStarterWorldId; 26 | 27 | [FieldOffset(0x6)] 28 | public ushort countdownTimeSeconds; 29 | [FieldOffset(0x8)] 30 | public byte countdownResultCode; 31 | 32 | [FieldOffset(0xB)] 33 | public fixed byte countdownStarterName[32]; 34 | 35 | public string ToString(long epoch, uint ActorID) 36 | { 37 | fixed (byte* name = countdownStarterName) 38 | { 39 | return 40 | $"{countdownStarterActorID:X8}|" + 41 | $"{countdownStarterWorldId:X4}|" + 42 | $"{countdownTimeSeconds}|" + 43 | $"{countdownResultCode:X2}|" + 44 | $"{FFXIVMemory.GetStringFromBytes(name, 32)}"; 45 | } 46 | } 47 | } 48 | 49 | [StructLayout(LayoutKind.Explicit, Size = structSize, Pack = 1)] 50 | internal unsafe struct Countdown_v730 : IPacketStruct 51 | { 52 | // As of 7.3, there are an extra 0x10 bytes at the start of the packet 53 | 54 | public const int structSize = 64; 55 | [FieldOffset(0x10)] 56 | public uint countdownStarterActorID; 57 | [FieldOffset(0x14)] 58 | public ushort countdownStarterWorldId; 59 | 60 | [FieldOffset(0x16)] 61 | public ushort countdownTimeSeconds; 62 | [FieldOffset(0x18)] 63 | public byte countdownResultCode; 64 | 65 | [FieldOffset(0x1B)] 66 | public fixed byte countdownStarterName[32]; 67 | 68 | 69 | 70 | public string ToString(long epoch, uint ActorID) 71 | { 72 | fixed (byte* name = countdownStarterName) 73 | { 74 | return 75 | $"{countdownStarterActorID:X8}|" + 76 | $"{countdownStarterWorldId:X4}|" + 77 | $"{countdownTimeSeconds}|" + 78 | $"{countdownResultCode:X2}|" + 79 | $"{FFXIVMemory.GetStringFromBytes(name, 32)}"; 80 | } 81 | } 82 | } 83 | 84 | public const uint LogFileLineID = 268; 85 | public const string logLineName = "Countdown"; 86 | public const string MachinaPacketName = "Countdown"; 87 | 88 | public LineCountdown(TinyIoCContainer container) 89 | : base(container, LogFileLineID, logLineName, MachinaPacketName) { } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /OverlayPlugin.Core/MemoryProcessors/Combatant/CombatantMemoryManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using RainbowMage.OverlayPlugin.MemoryProcessors.Aggro; 5 | 6 | namespace RainbowMage.OverlayPlugin.MemoryProcessors.Combatant 7 | { 8 | public interface ICombatantMemory : IVersionedMemory 9 | { 10 | Combatant GetSelfCombatant(); 11 | Combatant GetCombatantFromAddress(IntPtr address, uint selfCharID); 12 | List GetCombatantList(); 13 | void ReturnCombatant(Combatant combatant); 14 | } 15 | 16 | public class CombatantMemoryManager : ICombatantMemory 17 | { 18 | private readonly TinyIoCContainer container; 19 | private readonly FFXIVRepository repository; 20 | private ICombatantMemory memory = null; 21 | 22 | public CombatantMemoryManager(TinyIoCContainer container) 23 | { 24 | this.container = container; 25 | container.Register(); 26 | container.Register(); 27 | container.Register(); 28 | container.Register(); 29 | repository = container.Resolve(); 30 | 31 | var memory = container.Resolve(); 32 | memory.RegisterOnProcessChangeHandler(FindMemory); 33 | } 34 | 35 | private void FindMemory(object sender, Process p) 36 | { 37 | memory = null; 38 | if (p == null) 39 | { 40 | return; 41 | } 42 | 43 | ScanPointers(); 44 | } 45 | 46 | public void ScanPointers() 47 | { 48 | List candidates = new List(); 49 | candidates.Add(container.Resolve()); 50 | candidates.Add(container.Resolve()); 51 | candidates.Add(container.Resolve()); 52 | candidates.Add(container.Resolve()); 53 | memory = FFXIVMemory.FindCandidate(candidates, repository.GetMachinaRegion()); 54 | } 55 | 56 | public bool IsValid() 57 | { 58 | if (memory == null || !memory.IsValid()) 59 | { 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | Version IVersionedMemory.GetVersion() 67 | { 68 | if (!IsValid()) 69 | return null; 70 | return memory.GetVersion(); 71 | } 72 | 73 | public Combatant GetCombatantFromAddress(IntPtr address, uint selfCharID) 74 | { 75 | if (!IsValid()) 76 | { 77 | return null; 78 | } 79 | 80 | return memory.GetCombatantFromAddress(address, selfCharID); 81 | } 82 | 83 | public List GetCombatantList() 84 | { 85 | if (!IsValid()) 86 | { 87 | return new List(); 88 | } 89 | 90 | return memory.GetCombatantList(); 91 | } 92 | 93 | public Combatant GetSelfCombatant() 94 | { 95 | if (!IsValid()) 96 | { 97 | return null; 98 | } 99 | 100 | return memory.GetSelfCombatant(); 101 | } 102 | 103 | public void ReturnCombatant(Combatant combatant) 104 | { 105 | if (!IsValid()) 106 | { 107 | return; 108 | } 109 | 110 | memory.ReturnCombatant(combatant); 111 | } 112 | } 113 | } 114 | --------------------------------------------------------------------------------