diff --git a/FetchDependencies/FetchDependencies.cs b/FetchDependencies/FetchDependencies.cs index eb0a845..b531ca3 100644 --- a/FetchDependencies/FetchDependencies.cs +++ b/FetchDependencies/FetchDependencies.cs @@ -1,4 +1,5 @@ using System.IO.Compression; +using System.Net; namespace FetchDependencies { public class FetchDependencies { @@ -13,24 +14,24 @@ public FetchDependencies() { } public async Task GetFfxivPlugin() { - Directory.CreateDirectory(DependenciesDir); - var pluginZipPath = Path.Combine(DependenciesDir, "FFXIV_ACT_Plugin.zip"); - var pluginPath = Path.Combine(DependenciesDir, "FFXIV_ACT_Plugin.dll"); + Directory.CreateDirectory(DependenciesDir); + var pluginZipPath = Path.Combine(DependenciesDir, "FFXIV_ACT_Plugin.zip"); + var pluginPath = Path.Combine(DependenciesDir, "FFXIV_ACT_Plugin.dll"); - if (!await NeedsUpdate(pluginPath)) - return; + if (!await NeedsUpdate(pluginPath)) + return; - if (!File.Exists(pluginZipPath)) - await DownloadPlugin(pluginZipPath); + if (!File.Exists(pluginPath)) + await DownloadPlugin(pluginPath); - ZipFile.ExtractToDirectory(pluginZipPath, DependenciesDir, overwriteFiles: true); - File.Delete(pluginZipPath); + // ZipFile.ExtractToDirectory(pluginZipPath, DependenciesDir, overwriteFiles: true); + //File.Delete(pluginPath); - var patcher = new Patcher(DependenciesDir); - patcher.MainPlugin(); - patcher.LogFilePlugin(); - patcher.MemoryPlugin(); - } + var patcher = new Patcher(DependenciesDir); + patcher.MainPlugin(); + patcher.LogFilePlugin(); + patcher.MemoryPlugin(); + } private static async Task NeedsUpdate(string dllPath) { if (!File.Exists(dllPath)) return true; @@ -50,12 +51,20 @@ private static async Task NeedsUpdate(string dllPath) { } private static async Task DownloadPlugin(string path) { - var httpClient = new HttpClient(); - httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(ActUserAgent); - await using var downloadStream = await httpClient.GetStreamAsync("https://advancedcombattracker.com/download.php?id=73"); - await using var zipFileStream = new FileStream(path, FileMode.Create); - await downloadStream.CopyToAsync(zipFileStream); - zipFileStream.Close(); - } + string url = ""; + HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://github.com/TundraWork/FFXIV_ACT_Plugin_CN/releases/latest"); + req.Method = "HEAD"; + req.AllowAutoRedirect = false; + HttpWebResponse myResp = (HttpWebResponse)req.GetResponse(); + if (myResp.StatusCode == HttpStatusCode.Redirect) + url = myResp.GetResponseHeader("Location"); + var bcd = url.Split(@"/")[7]; + var httpClient = new HttpClient(); + await using var downloadStream = await httpClient.GetStreamAsync($"https://github.com/TundraWork/FFXIV_ACT_Plugin_CN/releases/download/{bcd}/FFXIV_ACT_Plugin.dll"); + await using var zipFileStream = new FileStream(path, FileMode.Create); + await downloadStream.CopyToAsync(zipFileStream); + zipFileStream.Close(); + + } } } diff --git a/FetchDependencies/FetchDependencies.csproj b/FetchDependencies/FetchDependencies.csproj index 9ab498b..bce0679 100644 --- a/FetchDependencies/FetchDependencies.csproj +++ b/FetchDependencies/FetchDependencies.csproj @@ -7,7 +7,7 @@ enable x64 win-x64 - 11 + 10 diff --git a/FetchDependencies/FetchDependencies.csproj.bak b/FetchDependencies/FetchDependencies.csproj.bak new file mode 100644 index 0000000..9ab498b --- /dev/null +++ b/FetchDependencies/FetchDependencies.csproj.bak @@ -0,0 +1,17 @@ + + + + Exe + net6.0-windows + enable + enable + x64 + win-x64 + 11 + + + + + + + diff --git a/IINACT/IINACT.csproj b/IINACT/IINACT.csproj index 25fe305..0f40240 100644 --- a/IINACT/IINACT.csproj +++ b/IINACT/IINACT.csproj @@ -9,12 +9,12 @@ x64 win-x64 Icon-IINACT-512x512@2x.ico - true + true true false full 0.4.1 - 11 + 10 diff --git a/IINACT/IINACT.csproj.bak b/IINACT/IINACT.csproj.bak new file mode 100644 index 0000000..564a0b8 --- /dev/null +++ b/IINACT/IINACT.csproj.bak @@ -0,0 +1,91 @@ + + + + WinExe + net6.0-windows + enable + True + enable + x64 + win-x64 + Icon-IINACT-512x512@2x.ico + true + true + false + full + 0.4.1 + 11 + + + + + + + + + + + + + + + + + + + + + + ..\external_dependencies\FFXIV_ACT_Plugin.dll + False + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Common.dll + False + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Config.dll + False + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Logfile.dll + False + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Memory.dll + False + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Network.dll + False + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Parse.dll + False + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Resource.dll + False + + + ..\external_dependencies\Machina.FFXIV.dll + False + + + + + + True + True + Settings.settings + + + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + \ No newline at end of file diff --git a/NotACT/FormActMain.cs b/NotACT/FormActMain.cs index 16dce5c..be851a1 100644 --- a/NotACT/FormActMain.cs +++ b/NotACT/FormActMain.cs @@ -1,6 +1,8 @@ +using System; using System.Collections.Concurrent; using System.Diagnostics; using System.Globalization; +using System.Speech.Synthesis; using System.Text.RegularExpressions; using System.Threading; @@ -40,8 +42,8 @@ public partial class FormActMain : Form { public event LogFileChangedDelegate LogFileChanged; public event CombatActionDelegate AfterCombatAction; public delegate DateTime DateTimeLogParser(string logLine); - - public FormActMain() { + public InstalledVoice Voice; + public FormActMain() { InitializeComponent(); AppDataFolder = new DirectoryInfo(Path.Combine(LogFilePath, "Data")); ActGlobals.ActLocalization.Init(); @@ -49,9 +51,23 @@ public FormActMain() { Resources.NotActMainFormatter.SetupEnvironment(); LastKnownTime = DateTime.Now; StartAfterCombatActionThread(); - } - - public void WriteExceptionLog(Exception ex, string MoreInfo) { + getVoice(); + } + public void getVoice() + { + + using (SpeechSynthesizer synth = new SpeechSynthesizer()) + { + foreach (var voice in synth.GetInstalledVoices()) + { + if (voice.VoiceInfo.Culture.Name.Contains("zh")) + { + Voice = voice; + } + } + } + } + public void WriteExceptionLog(Exception ex, string MoreInfo) { var value = $"***** {DateTime.Now.ToString("s")} - {MoreInfo}\n{ex}\n{Environment.StackTrace}\n*****"; Trace.WriteLine(value); } @@ -75,10 +91,14 @@ public void ParseRawLogLine(string logLine) { public void TTS(string message, string binary = "/usr/bin/say", string args = "") { - lock (_ttsLock) { - if (new FileInfo(binary).Exists) { - try { - var ttsProcess = new Process { + lock (_ttsLock) + { + if (new FileInfo(binary).Exists) + { + try + { + var ttsProcess = new Process + { StartInfo = { FileName = binary, CreateNoWindow = true, @@ -89,13 +109,21 @@ public void TTS(string message, string binary = "/usr/bin/say", string args = "" ttsProcess.Start(); Thread.Sleep(500 * message.Split(' ', StringSplitOptions.RemoveEmptyEntries).Length); } - catch (Exception ex) { + catch (Exception ex) + { WriteExceptionLog(ex, $"TTS failed to play back {message}"); } - } else { - Trace.WriteLine($"TTS binary {binary} not found"); } + else + { + SpeechSynthesizer synth = new SpeechSynthesizer(); + synth.Rate = 0; + synth.Volume = 100; + synth.SelectVoice(Voice.VoiceInfo.Name); + synth.SpeakAsync(message); + } } + } public Regex ZoneChangeRegex { get; set; } diff --git a/NotACT/NotACT.csproj b/NotACT/NotACT.csproj index badc898..f4109c2 100644 --- a/NotACT/NotACT.csproj +++ b/NotACT/NotACT.csproj @@ -11,13 +11,17 @@ x64 win-x64 6.9 - 11 + 10 + + + + ..\external_dependencies\FFXIV_ACT_Plugin.dll diff --git a/NotACT/NotACT.csproj.bak b/NotACT/NotACT.csproj.bak new file mode 100644 index 0000000..a06684e --- /dev/null +++ b/NotACT/NotACT.csproj.bak @@ -0,0 +1,30 @@ + + + + WinExe + net6.0-windows + enable + true + enable + Advanced Combat Tracker + Advanced_Combat_Tracker + x64 + win-x64 + 6.9 + 11 + + + + + + + + + ..\external_dependencies\FFXIV_ACT_Plugin.dll + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Logfile.dll + + + + \ No newline at end of file diff --git a/OverlayPlugin.Common/OverlayPlugin.Common.csproj b/OverlayPlugin.Common/OverlayPlugin.Common.csproj index ee05c25..630cc5b 100644 --- a/OverlayPlugin.Common/OverlayPlugin.Common.csproj +++ b/OverlayPlugin.Common/OverlayPlugin.Common.csproj @@ -7,7 +7,7 @@ true true win-x64 - 11 + 10 diff --git a/OverlayPlugin.Common/OverlayPlugin.Common.csproj.bak b/OverlayPlugin.Common/OverlayPlugin.Common.csproj.bak new file mode 100644 index 0000000..403259c --- /dev/null +++ b/OverlayPlugin.Common/OverlayPlugin.Common.csproj.bak @@ -0,0 +1,22 @@ + + + net6.0-windows + Library + RainbowMage.OverlayPlugin + false + true + true + win-x64 + 11 + + + + + 13.0.1 + + + + + + + \ No newline at end of file diff --git a/OverlayPlugin.Core/Integration/FFXIVCustomLogLines.cs b/OverlayPlugin.Core/Integration/FFXIVCustomLogLines.cs index ee02a5c..7b21c89 100644 --- a/OverlayPlugin.Core/Integration/FFXIVCustomLogLines.cs +++ b/OverlayPlugin.Core/Integration/FFXIVCustomLogLines.cs @@ -77,10 +77,10 @@ public FFXIVCustomLogLines(TinyIoCContainer container) { } } if (registry.ContainsKey(registeredCustomLogLineID)) { - var entry = registry[registeredCustomLogLineID]; - var Source = entry.Source.Replace("\r", "\\r").Replace("\n", "\\n"); - var Name = entry.Name.Replace("\r", "\\r").Replace("\n", "\\n"); - repository.WriteLogLineImpl(registeredCustomLogLineID, DateTime.Now, $"{registeredCustomLogLineID}|{Source}|{Name}|{entry.Version}"); + //var entry = registry[registeredCustomLogLineID]; + //var Source = entry.Source.Replace("\r", "\\r").Replace("\n", "\\n"); + //var Name = entry.Name.Replace("\r", "\\r").Replace("\n", "\\n"); + //repository.WriteLogLineImpl(registeredCustomLogLineID, DateTime.Now, $"{registeredCustomLogLineID}|{Source}|{Name}|{entry.Version}"); } } catch (Exception ex) { diff --git a/OverlayPlugin.Core/Integration/FFXIVRepository.cs b/OverlayPlugin.Core/Integration/FFXIVRepository.cs index 7c91525..cda4f22 100644 --- a/OverlayPlugin.Core/Integration/FFXIVRepository.cs +++ b/OverlayPlugin.Core/Integration/FFXIVRepository.cs @@ -244,22 +244,8 @@ public Language GetLanguage() { } public string GetLocaleString() { - switch (GetLanguage()) { - case Language.English: - return "en"; - case Language.French: - return "fr"; - case Language.German: - return "de"; - case Language.Japanese: - return "ja"; - case Language.Chinese: + return "cn"; - case Language.Korean: - return "ko"; - default: - return null; - } } public GameRegion GetMachinaRegion() => @@ -272,6 +258,7 @@ public DateTime EpochToDateTime(long epoch) => [MethodImpl(MethodImplOptions.NoInlining)] internal bool WriteLogLineImpl(uint ID, DateTime timestamp, string line) { + return false; if (_logOutput == null) { var plugin = GetPluginData(); _logOutput = (ILogOutput)plugin._iocContainer.GetService(typeof(ILogOutput)); diff --git a/OverlayPlugin.Core/MemoryProcessors/FFXIVProcessCn.cs b/OverlayPlugin.Core/MemoryProcessors/FFXIVProcessCn.cs index f7e450e..407cf11 100644 --- a/OverlayPlugin.Core/MemoryProcessors/FFXIVProcessCn.cs +++ b/OverlayPlugin.Core/MemoryProcessors/FFXIVProcessCn.cs @@ -1,686 +1,974 @@ -using Newtonsoft.Json.Linq; -using System; +using System; using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices; +using Newtonsoft.Json.Linq; +using RainbowMage.OverlayPlugin; +using RainbowMage.OverlayPlugin.MemoryProcessors; +using static RainbowMage.OverlayPlugin.MemoryProcessors.FFXIVProcess; -namespace RainbowMage.OverlayPlugin.MemoryProcessors { - public class FFXIVProcessCn : FFXIVProcess { - // Last updated for FFXIV 5.3 - // - // Latest CN version can be found at: - // http://ff.sdo.com/web8/index.html#/patchnote +namespace RainbowMage.OverlayPlugin.MemoryProcessors +{ + public class FFXIVProcessCn : FFXIVProcess + { + // Last updated for FFXIV 6.1 - [StructLayout(LayoutKind.Explicit)] - public unsafe struct EntityMemory { - public static int Size => Marshal.SizeOf(typeof(EntityMemory)); + [StructLayout(LayoutKind.Explicit)] + public unsafe struct EntityMemory + { + public static int Size => Marshal.SizeOf(typeof(EntityMemory)); + + // Unknown size, but this is the bytes up to the next field. + public const int nameBytes = 68; + + [FieldOffset(0x30)] + public fixed byte Name[nameBytes]; + + [FieldOffset(0x74)] + public uint id; + + [FieldOffset(0x8C)] + public EntityType type; + + [FieldOffset(0x92)] + public ushort distance; + + [FieldOffset(0xA0)] + public Single pos_x; + + [FieldOffset(0xA4)] + public Single pos_z; + + [FieldOffset(0xA8)] + public Single pos_y; + + [FieldOffset(0xB0)] + public Single rotation; + + [FieldOffset(0x1C4)] + public CharacterDetails charDetails; + + [FieldOffset(0x1AD3)] + public byte shieldPercentage; + } + + [StructLayout(LayoutKind.Explicit)] + public struct CharacterDetails + { + + [FieldOffset(0x00)] + public int hp; + + [FieldOffset(0x04)] + public int max_hp; + + [FieldOffset(0x08)] + public short mp; + + [FieldOffset(0x10)] + public short gp; + + [FieldOffset(0x12)] + public short max_gp; + + [FieldOffset(0x14)] + public short cp; + + [FieldOffset(0x16)] + public short max_cp; + + [FieldOffset(0x1C)] + public EntityJob job; + + [FieldOffset(0x1D)] + public byte level; + } + public FFXIVProcessCn(TinyIoCContainer container) : base(container) { } + + // TODO: all of this could be refactored into structures of some sort + // instead of just being loose variables everywhere. + + // A piece of code that reads the pointer to the list of all entities, that we + // refer to as the charmap. The pointer is the 4 byte ?????????. + private static String kCharmapSignature = "48c1ea0381faa9010000????8bc2488d0d"; + private static int kCharmapSignatureOffset = 0; + // The signature finds a pointer in the executable code which uses RIP addressing. + private static bool kCharmapSignatureRIP = true; + // The pointer is to a structure as: + // + // CharmapStruct* outer; // The pointer found from the signature. + // CharmapStruct { + // EntityStruct* player; + // } + private static int kCharmapStructOffsetPlayer = 0; + + // In combat boolean. + // This address is written to by "mov [rax+rcx],bl" and has three readers. + // This reader is "cmp byte ptr [ffxiv_dx11.exe+????????],00 { (0),0 }" + private static String kInCombatSignature = "803D????????000F95C04883C428"; + private static int kInCombatSignatureOffset = -12; + private static bool kInCombatSignatureRIP = true; + // Because this line is a cmp byte line, the signature is not at the end of the line. + private static int kInCombatRipOffset = 1; + + // Bait integer. + // Variable is accessed via a cmp eax,[...] line at offset=0. + private static String kBaitSignature = "4883C4305BC3498BC8E8????????3B05"; + private static int kBaitBaseOffset = 0; + private static bool kBaitBaseRIP = true; + + // A piece of code that reads the job data. + // The pointer of interest is the first ???????? in the signature. + private static String kJobDataSignature = "488B0D????????4885C90F84????????488B05????????3C03"; + private static int kJobDataSignatureOffset = -22; + // The signature finds a pointer in the executable code which uses RIP addressing. + private static bool kJobDataSignatureRIP = true; + + internal override void ReadSignatures() + { + List p; + + // TODO: for now, support multiple matches on charmap signature. + // This sig returns two matches that are identical for many, many characters. + // They both point to the same spot, so verify these have the same value. + p = SigScan(kCharmapSignature, kCharmapSignatureOffset, kCharmapSignatureRIP); + if (p.Count == 0) + { + logger_.Log(LogLevel.Error, "Charmap signature found " + p.Count + " matches"); + } + else + { + IntPtr player_ptr_value = IntPtr.Zero; + foreach (IntPtr ptr in p) + { + IntPtr addr = IntPtr.Add(ptr, kCharmapStructOffsetPlayer); + IntPtr value = ReadIntPtr(addr); + if (player_ptr_value == IntPtr.Zero || player_ptr_value == value) + { + player_ptr_value = value; + player_ptr_addr_ = addr; + } + else + { + logger_.Log(LogLevel.Error, "Charmap signature found, but conflicting match"); + } + } + } + + p = SigScan(kJobDataSignature, kJobDataSignatureOffset, kJobDataSignatureRIP); + if (p.Count != 1) + { + logger_.Log(LogLevel.Error, "Job signature found " + p.Count + " matches"); + } + else + { + job_data_outer_addr_ = IntPtr.Add(p[0], kJobDataOuterStructOffset); + } + + p = SigScan(kInCombatSignature, kInCombatSignatureOffset, kInCombatSignatureRIP, kInCombatRipOffset); + if (p.Count != 1) + { + logger_.Log(LogLevel.Error, "In combat signature found " + p.Count + " matches"); + } + else + { + in_combat_addr_ = p[0]; + } + + p = SigScan(kBaitSignature, kBaitBaseOffset, kBaitBaseRIP); + if (p.Count != 1) + { + logger_.Log(LogLevel.Error, "Bait signature found " + p.Count + " matches"); + } + else + { + bait_addr_ = p[0]; + } + } + + public unsafe override EntityData GetEntityDataFromByteArray(byte[] source) + { + fixed (byte* p = source) + { + EntityMemory mem = *(EntityMemory*)&p[0]; + + // dump '\0' string terminators + var memoryName = System.Text.Encoding.UTF8.GetString(mem.Name, EntityMemory.nameBytes).Split(new[] { '\0' }, 2)[0]; + + EntityData entity = new EntityData() + { + name = memoryName, + id = mem.id, + type = mem.type, + distance = mem.distance, + pos_x = mem.pos_x, + pos_y = mem.pos_y, + pos_z = mem.pos_z, + rotation = mem.rotation, + }; + if (entity.type == EntityType.PC || entity.type == EntityType.Monster) + { + entity.job = mem.charDetails.job; + + entity.hp = mem.charDetails.hp; + entity.max_hp = mem.charDetails.max_hp; + entity.mp = mem.charDetails.mp; + // This doesn't exist in memory, so just send the right value. + // As there are other versions that still have it, don't change the event. + entity.max_mp = 10000; + entity.shield_value = mem.shieldPercentage * entity.max_hp / 100; + + if (IsGatherer(entity.job)) + { + entity.gp = mem.charDetails.gp; + entity.max_gp = mem.charDetails.max_gp; + } + if (IsCrafter(entity.job)) + { + entity.cp = mem.charDetails.cp; + entity.max_cp = mem.charDetails.max_cp; + } + + entity.level = mem.charDetails.level; + + byte[] job_bytes = GetRawJobSpecificDataBytes(); + if (job_bytes != null) + { + for (var i = 0; i < job_bytes.Length; ++i) + { + if (entity.debug_job != "") + entity.debug_job += " "; + entity.debug_job += string.Format("{0:x2}", job_bytes[i]); + } + } + } + return entity; + } + } + + internal override EntityData GetEntityData(IntPtr entity_ptr) + { + if (entity_ptr == IntPtr.Zero) + return null; + byte[] source = Read8(entity_ptr, EntityMemory.Size); + return GetEntityDataFromByteArray(source); + } + public override EntityData GetSelfData() + { + if (!HasProcess() || player_ptr_addr_ == IntPtr.Zero) + return null; + + IntPtr entity_ptr = ReadIntPtr(player_ptr_addr_); + if (entity_ptr == IntPtr.Zero) + return null; + var data = GetEntityData(entity_ptr); + if (data.job == EntityJob.FSH) + data.bait = GetBait(); + return data; + } + + public unsafe override JObject GetJobSpecificData(EntityJob job) + { + if (!HasProcess() || job_data_outer_addr_ == IntPtr.Zero) + return null; + + IntPtr job_inner_ptr = ReadIntPtr(job_data_outer_addr_); + if (job_inner_ptr == IntPtr.Zero) + { + // The pointer can be null when not logged in. + return null; + } + job_inner_ptr = IntPtr.Add(job_inner_ptr, kJobDataInnerStructOffset); + + fixed (byte* p = Read8(job_inner_ptr, kJobDataInnerStructSize)) + { + if (p == null) + { + return null; + } + else + { + switch (job) + { + case EntityJob.RDM: + return JObject.FromObject(*(RedMageJobMemory*)&p[0]); + case EntityJob.WAR: + return JObject.FromObject(*(WarriorJobMemory*)&p[0]); + case EntityJob.DRK: + return JObject.FromObject(*(DarkKnightJobMemory*)&p[0]); + case EntityJob.PLD: + return JObject.FromObject(*(PaladinJobMemory*)&p[0]); + case EntityJob.GNB: + return JObject.FromObject(*(GunbreakerJobMemory*)&p[0]); + case EntityJob.BRD: + return JObject.FromObject(*(BardJobMemory*)&p[0]); + case EntityJob.DNC: + return JObject.FromObject(*(DancerJobMemory*)&p[0]); + case EntityJob.DRG: + return JObject.FromObject(*(DragoonJobMemory*)&p[0]); + case EntityJob.NIN: + return JObject.FromObject(*(NinjaJobMemory*)&p[0]); + case EntityJob.THM: + return JObject.FromObject(*(ThaumaturgeJobMemory*)&p[0]); + case EntityJob.BLM: + return JObject.FromObject(*(BlackMageJobMemory*)&p[0]); + case EntityJob.WHM: + return JObject.FromObject(*(WhiteMageJobMemory*)&p[0]); + case EntityJob.ACN: + return JObject.FromObject(*(ArcanistJobMemory*)&p[0]); + case EntityJob.SMN: + return JObject.FromObject(*(SummonerJobMemory*)&p[0]); + case EntityJob.SCH: + return JObject.FromObject(*(ScholarJobMemory*)&p[0]); + case EntityJob.MNK: + return JObject.FromObject(*(MonkJobMemory*)&p[0]); + case EntityJob.MCH: + return JObject.FromObject(*(MachinistJobMemory*)&p[0]); + case EntityJob.AST: + return JObject.FromObject(*(AstrologianJobMemory*)&p[0]); + case EntityJob.SAM: + return JObject.FromObject(*(SamuraiJobMemory*)&p[0]); + case EntityJob.SGE: + return JObject.FromObject(*(SageJobMemory*)&p[0]); + case EntityJob.RPR: + return JObject.FromObject(*(ReaperJobMemory*)&p[0]); + } + return null; + } + } + } + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct RedMageJobMemory + { + [FieldOffset(0x00)] + public byte whiteMana; + + [FieldOffset(0x01)] + public byte blackMana; + + [FieldOffset(0x02)] + public byte manaStacks; + }; + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct WarriorJobMemory + { + [FieldOffset(0x00)] + public byte beast; + }; + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct DarkKnightJobMemory + { + [FieldOffset(0x00)] + public byte blood; + + [FieldOffset(0x02)] + public ushort darksideMilliseconds; + + [FieldOffset(0x04)] + public byte darkArts; + + [FieldOffset(0x06)] + public ushort livingShadowMilliseconds; + }; + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct PaladinJobMemory + { + [FieldOffset(0x00)] + public byte oath; + }; + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct GunbreakerJobMemory + { + [FieldOffset(0x00)] + public byte cartridges; + + [FieldOffset(0x02)] + private ushort continuationMilliseconds; // Is 15000 if and only if continuationState is not zero. + + [FieldOffset(0x04)] + public byte continuationState; + }; + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct BardJobMemory + { + [Flags] + private enum SongFlags : byte + { + None = 0, + Ballad = 1, // Mage's Ballad. + Paeon = 1 << 1, // Army's Paeon. + Minuet = 1 | 1 << 1, // The Wanderer's Minuet. + BalladLastPlayed = 1 << 2, + PaeonLastPlayed = 1 << 3, + MinuetLastPlayed = 1 << 2 | 1 << 3, + BalladCoda = 1 << 4, + PaeonCoda = 1 << 5, + MinuetCoda = 1 << 6, + } + + [FieldOffset(0x00)] + public ushort songMilliseconds; + + [FieldOffset(0x04)] + public byte songProcs; + + [FieldOffset(0x05)] + public byte soulGauge; + + [NonSerialized] + [FieldOffset(0x06)] + private SongFlags songFlags; + + public String songName + { + get + { + if (songFlags.HasFlag(SongFlags.Minuet)) + return "Minuet"; + if (songFlags.HasFlag(SongFlags.Ballad)) + return "Ballad"; + if (songFlags.HasFlag(SongFlags.Paeon)) + return "Paeon"; + return "None"; + } + } + + public String lastPlayed + { + get + { + if (songFlags.HasFlag(SongFlags.MinuetLastPlayed)) + return "Minuet"; + if (songFlags.HasFlag(SongFlags.BalladLastPlayed)) + return "Ballad"; + if (songFlags.HasFlag(SongFlags.PaeonLastPlayed)) + return "Paeon"; + return "None"; + } + } + + public String[] coda + { + get + { + return new[] { + this.songFlags.HasFlag(SongFlags.BalladCoda) ? "Ballad" : "None", + this.songFlags.HasFlag(SongFlags.PaeonCoda) ? "Paeon" : "None", + this.songFlags.HasFlag(SongFlags.MinuetCoda) ? "Minuet" : "None", + }; + } + } + }; + + [StructLayout(LayoutKind.Explicit)] + public struct DancerJobMemory + { + private enum Step : byte + { + None = 0, + Emboite = 1, + Entrechat = 2, + Jete = 3, + Pirouette = 4, + } + + [FieldOffset(0x00)] + public byte feathers; + + [FieldOffset(0x01)] + public byte esprit; + + [NonSerialized] + [FieldOffset(0x02)] + private Step step1; // Order of steps in current Standard Step/Technical Step combo. + + [NonSerialized] + [FieldOffset(0x03)] + private Step step2; + + [NonSerialized] + [FieldOffset(0x04)] + private Step step3; + + [NonSerialized] + [FieldOffset(0x05)] + private Step step4; + + [FieldOffset(0x06)] + public byte currentStep; // Number of steps executed in current Standard Step/Technical Step combo. + + public string[] steps + { + get + { + Step[] _steps = { step1, step2, step3, step4 }; + return _steps.Select(s => s.ToString()).Where(s => s != "None").ToArray(); + } + } + }; + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct DragoonJobMemory + { + [NonSerialized] + [FieldOffset(0x00)] + private ushort blood_or_life_ms; + + [NonSerialized] + [FieldOffset(0x02)] + private byte stance; // 0 = None, 1 = Blood, 2 = Life + + [FieldOffset(0x03)] + public byte eyesAmount; + + public uint bloodMilliseconds + { + get + { + if (stance == 1) + return blood_or_life_ms; + else + return 0; + } + } + public uint lifeMilliseconds + { + get + { + if (stance == 2) + return blood_or_life_ms; + else + return 0; + } + } + + [FieldOffset(0x04)] + public byte firstmindsFocus; + }; + + [Serializable] + [StructLayout(LayoutKind.Explicit)] + public struct NinjaJobMemory + { + [FieldOffset(0x00)] + public uint hutonMilliseconds; + + [FieldOffset(0x04)] + public byte ninkiAmount; + + [FieldOffset(0x05)] + private byte hutonCount; // Why though? + }; + + [StructLayout(LayoutKind.Explicit)] + public struct ThaumaturgeJobMemory + { + [FieldOffset(0x02)] + public ushort umbralMilliseconds; // Number of ms left in umbral fire/ice. + + [FieldOffset(0x04)] + public sbyte umbralStacks; // Positive = Umbral Fire Stacks, Negative = Umbral Ice Stacks. + }; + + [StructLayout(LayoutKind.Explicit)] + public struct BlackMageJobMemory + { + [Flags] + public enum EnochianFlags : byte + { + None = 0, + Enochian = 1, + Paradox = 2, + } + [FieldOffset(0x00)] + public ushort nextPolyglotMilliseconds; // Number of ms left before polyglot proc. + + [FieldOffset(0x02)] + public ushort umbralMilliseconds; // Number of ms left in umbral fire/ice. + + [FieldOffset(0x04)] + public sbyte umbralStacks; // Positive = Umbral Fire Stacks, Negative = Umbral Ice Stacks. + + [FieldOffset(0x05)] + public byte umbralHearts; + + [FieldOffset(0x06)] + public byte polyglot; + + [NonSerialized] + [FieldOffset(0x07)] + private EnochianFlags enochian_state; + + public bool enochian + { + get + { + return enochian_state.HasFlag(EnochianFlags.Enochian); + } + } + + public bool paradox + { + get + { + return enochian_state.HasFlag(EnochianFlags.Paradox); + } + } + }; + + [StructLayout(LayoutKind.Explicit)] + public struct WhiteMageJobMemory + { + [FieldOffset(0x02)] + public ushort lilyMilliseconds; // Number of ms left before lily gain. + + [FieldOffset(0x04)] + public byte lilyStacks; + + [FieldOffset(0x05)] + public byte bloodlilyStacks; + }; + + [StructLayout(LayoutKind.Explicit)] + public struct ArcanistJobMemory + { + [FieldOffset(0x04)] + public byte aetherflowStacks; + }; + + [StructLayout(LayoutKind.Explicit)] + public struct SummonerJobMemory + { + [FieldOffset(0x00)] + public ushort tranceMilliseconds; + + [FieldOffset(0x02)] + public ushort attunementMilliseconds; + + [FieldOffset(0x06)] + public byte attunement; + + [NonSerialized] + [FieldOffset(0x07)] + private byte stance; + + public string[] usableArcanum + { + get + { + var arcanums = new List(); + if ((stance & 0x20) != 0) + arcanums.Add("Ruby"); // Fire/Ifrit + if ((stance & 0x40) != 0) + arcanums.Add("Topaz"); // Earth/Titan + if ((stance & 0x80) != 0) + arcanums.Add("Emerald"); // Wind/Garuda + + return arcanums.ToArray(); + } + } + + public string activePrimal + { + get + { + if ((stance & 0xC) == 0x4) + return "Ifrit"; + else if ((stance & 0xC) == 0x8) + return "Titan"; + else if ((stance & 0xC) == 0xC) + return "Garuda"; + else + return null; + } + } + + public String nextSummoned + { + get + { + if ((stance & 0x10) == 0) + return "Bahamut"; + else + return "Phoenix"; + } + } + + public int aetherflowStacks + { + get + { + return stance & 0x3; + } + } + }; + + [StructLayout(LayoutKind.Explicit)] + public struct ScholarJobMemory + { + [FieldOffset(0x02)] + public byte aetherflowStacks; + + [FieldOffset(0x03)] + public byte fairyGauge; + + [FieldOffset(0x04)] + public ushort fairyMilliseconds; // Seraph time left ms. + + [FieldOffset(0x06)] + public byte fairyStatus; // Varies depending on which fairy was summoned, during Seraph/Dissipation: 6 - Eos, 7 - Selene, else 0. + }; + + + [StructLayout(LayoutKind.Explicit)] + public struct MonkJobMemory + { + public enum Beast : byte + { + None = 0, + Coeurl = 1, + Opo = 2, + Raptor = 3, + } + + [FieldOffset(0x00)] + public byte chakraStacks; + + [NonSerialized] + [FieldOffset(0x01)] + private Beast beastChakra1; + + [NonSerialized] + [FieldOffset(0x02)] + private Beast beastChakra2; + + [NonSerialized] + [FieldOffset(0x03)] + private Beast beastChakra3; + + [NonSerialized] + [FieldOffset(0x04)] + private byte Nadi; + + public string[] beastChakra + { + get + { + Beast[] _beasts = { beastChakra1, beastChakra2, beastChakra3 }; + return _beasts.Select(a => a.ToString()).Where(a => a != "None").ToArray(); + } + } + + public bool solarNadi + { + get + { + if ((Nadi & 0x4) == 0x4) + return true; + else + return false; + } + } + + public bool lunarNadi + { + get + { + if ((Nadi & 0x2) == 0x2) + return true; + else + return false; + } + } + }; + + [StructLayout(LayoutKind.Explicit)] + public struct MachinistJobMemory + { + [FieldOffset(0x00)] + public ushort overheatMilliseconds; + + [FieldOffset(0x02)] + public ushort batteryMilliseconds; + + [FieldOffset(0x04)] + public byte heat; + + [FieldOffset(0x05)] + public byte battery; + + [FieldOffset(0x06)] + public byte lastBatteryAmount; + + [NonSerialized] + [FieldOffset(0x07)] + private byte chargeTimerState; + + public bool overheatActive + { + get + { + return (chargeTimerState & 0x1) == 1; + } + } + + public bool robotActive + { + get + { + return (chargeTimerState & 0x2) == 1; + } + } + }; + + [StructLayout(LayoutKind.Explicit)] + public struct AstrologianJobMemory + { + public enum Card : byte + { + None = 0, + Balance = 1, + Bole = 2, + Arrow = 3, + Spear = 4, + Ewer = 5, + Spire = 6, + Lord = 0x70, + Lady = 0x80, + } + + public enum Arcanum : byte + { + None = 0, + Solar = 1, + Lunar = 2, + Celestial = 3, + } + + [NonSerialized] + [FieldOffset(0x05)] + private byte _heldCard; + + [NonSerialized] + [FieldOffset(0x06)] + private byte _arcanumsmix; + + public string heldCard + { + get + { + return ((Card)(_heldCard & 0xF)).ToString(); + } + } + + public string crownCard + { + get + { + return ((Card)(_heldCard & 0xF0)).ToString(); + } + } + + public string[] arcanums + { + get + { + var _arcanums = new List(); + for (var i = 0; i < 3; i++) + { + int arcanum = (_arcanumsmix >> 2 * i) & 0x3; + _arcanums.Add((Arcanum)arcanum); + } + return _arcanums.Select(a => a.ToString()).Where(a => a != "None").ToArray(); + } + } + }; + + [StructLayout(LayoutKind.Explicit)] + public struct SamuraiJobMemory + { + [FieldOffset(0x03)] + public byte kenki; + + [FieldOffset(0x04)] + public byte meditationStacks; + + [NonSerialized] + [FieldOffset(0x05)] + private byte sen_bits; + + public bool setsu + { + get + { + return (sen_bits & 0x1) != 0; + } + } + + public bool getsu + { + get + { + return (sen_bits & 0x2) != 0; + } + } + + public bool ka + { + get + { + return (sen_bits & 0x4) != 0; + } + } + } + + [StructLayout(LayoutKind.Explicit)] + public struct SageJobMemory + { + [FieldOffset(0x00)] + public ushort addersgallMilliseconds; // the addersgall gauge elapsed in milliseconds, from 0 to 19999. + + [FieldOffset(0x02)] + public byte addersgall; + + [FieldOffset(0x03)] + public byte addersting; + + [FieldOffset(0x04)] + public byte eukrasia; + } + + [StructLayout(LayoutKind.Explicit)] + public struct ReaperJobMemory + { + [FieldOffset(0x00)] + public byte soul; + + [FieldOffset(0x01)] + public byte shroud; + + [FieldOffset(0x02)] + public ushort enshroudMilliseconds; + + [FieldOffset(0x04)] + public byte lemureShroud; + + [FieldOffset(0x05)] + public byte voidShroud; + } + } +} - // Unknown size, but this is the bytes up to the next field. - public const int nameBytes = 68; - - [FieldOffset(0x30)] - public fixed byte Name[nameBytes]; - - [FieldOffset(0x74)] - public uint id; - - [FieldOffset(0x8C)] - public EntityType type; - - [FieldOffset(0x92)] - public ushort distance; - - [FieldOffset(0xA0)] - public Single pos_x; - - [FieldOffset(0xA4)] - public Single pos_z; - - [FieldOffset(0xA8)] - public Single pos_y; - - [FieldOffset(0xB0)] - public Single rotation; - - [FieldOffset(0x1898)] - public CharacterDetails charDetails; - } - - [StructLayout(LayoutKind.Explicit)] - public struct CharacterDetails { - - [FieldOffset(0x00)] - public int hp; - - [FieldOffset(0x04)] - public int max_hp; - - [FieldOffset(0x08)] - public int mp; - - [FieldOffset(0x12)] - public short gp; - - [FieldOffset(0x14)] - public short max_gp; - - [FieldOffset(0x16)] - public short cp; - - [FieldOffset(0x18)] - public short max_cp; - - [FieldOffset(0x42)] - public EntityJob job; - - [FieldOffset(0x44)] - public byte level; - - [FieldOffset(0x65)] - public short shieldPercentage; - } - public FFXIVProcessCn(TinyIoCContainer container) : base(container) { } - - // TODO: all of this could be refactored into structures of some sort - // instead of just being loose variables everywhere. - - // A piece of code that reads the pointer to the list of all entities, that we - // refer to as the charmap. The pointer is the 4 byte ?????????. - private static String kCharmapSignature = "48c1ea0381faa7010000????8bc2488d0d"; - private static int kCharmapSignatureOffset = 0; - // The signature finds a pointer in the executable code which uses RIP addressing. - private static bool kCharmapSignatureRIP = true; - // The pointer is to a structure as: - // - // CharmapStruct* outer; // The pointer found from the signature. - // CharmapStruct { - // EntityStruct* player; - // } - private static int kCharmapStructOffsetPlayer = 0; - - // In combat boolean. - // Variable is set at 83FA587D70534883EC204863C2410FB6D8381C08744E (offset=0) - // via a mov [rax+rcx],bl line. - // This sig below finds the calling function that sets rax(offset) and rcx(base address). - private static String kInCombatSignature = "84C07425450FB6C7488D0D"; - private static int kInCombatBaseOffset = 0; - private static bool kInCombatBaseRIP = true; - private static int kInCombatOffsetOffset = 5; - private static bool kInCombatOffsetRIP = false; - - // Bait integer. - // Variable is accessed via a cmp eax,[...] line at offset=0. - private static String kBaitSignature = "4883C4305BC3498BC8E8????????3B05"; - private static int kBaitBaseOffset = 0; - private static bool kBaitBaseRIP = true; - - // A piece of code that reads the job data. - // The pointer of interest is the first ???????? in the signature. - private static String kJobDataSignature = "488B0D????????4885C90F84????????488B05????????3C03"; - private static int kJobDataSignatureOffset = -22; - // The signature finds a pointer in the executable code which uses RIP addressing. - private static bool kJobDataSignatureRIP = true; - - internal override void ReadSignatures() { - List p; - - // TODO: for now, support multiple matches on charmap signature. - // This sig returns two matches that are identical for many, many characters. - // They both point to the same spot, so verify these have the same value. - p = SigScan(kCharmapSignature, kCharmapSignatureOffset, kCharmapSignatureRIP); - if (p.Count == 0) { - logger_.Log(LogLevel.Error, "Charmap signature found " + p.Count + " matches"); - } else { - var player_ptr_value = IntPtr.Zero; - foreach (var ptr in p) { - var addr = IntPtr.Add(ptr, kCharmapStructOffsetPlayer); - var value = ReadIntPtr(addr); - if (player_ptr_value == IntPtr.Zero || player_ptr_value == value) { - player_ptr_value = value; - player_ptr_addr_ = addr; - } else { - logger_.Log(LogLevel.Error, "Charmap signature found, but conflicting match"); - } - } - } - - p = SigScan(kJobDataSignature, kJobDataSignatureOffset, kJobDataSignatureRIP); - if (p.Count != 1) { - logger_.Log(LogLevel.Error, "Job signature found " + p.Count + " matches"); - } else { - job_data_outer_addr_ = IntPtr.Add(p[0], kJobDataOuterStructOffset); - } - - p = SigScan(kInCombatSignature, kInCombatBaseOffset, kInCombatBaseRIP); - if (p.Count != 1) { - logger_.Log(LogLevel.Error, "In combat signature found " + p.Count + " matches"); - } else { - var baseAddress = p[0]; - p = SigScan(kInCombatSignature, kInCombatOffsetOffset, kInCombatOffsetRIP); - if (p.Count != 1) { - logger_.Log(LogLevel.Error, "In combat offset signature found " + p.Count + " matches"); - } else { - // Abuse sigscan here to return 64-bit "pointer" which we will mask into the 32-bit immediate integer we need. - // TODO: maybe sigscan should be able to return different types? - var offset = (int)(((UInt64)p[0]) & 0xFFFFFFFF); - in_combat_addr_ = IntPtr.Add(baseAddress, offset); - } - } - - p = SigScan(kBaitSignature, kBaitBaseOffset, kBaitBaseRIP); - if (p.Count != 1) { - logger_.Log(LogLevel.Error, "Bait signature found " + p.Count + " matches"); - } else { - bait_addr_ = p[0]; - } - } - - public unsafe override EntityData GetEntityDataFromByteArray(byte[] source) { - fixed (byte* p = source) { - var mem = *(EntityMemory*)&p[0]; - - // dump '\0' string terminators - var memoryName = System.Text.Encoding.UTF8.GetString(mem.Name, EntityMemory.nameBytes).Split(new[] { '\0' }, 2)[0]; - - var entity = new EntityData() { - name = memoryName, - id = mem.id, - type = mem.type, - distance = mem.distance, - pos_x = mem.pos_x, - pos_y = mem.pos_y, - pos_z = mem.pos_z, - rotation = mem.rotation, - }; - if (entity.type == EntityType.PC || entity.type == EntityType.Monster) { - entity.job = mem.charDetails.job; - - entity.hp = mem.charDetails.hp; - entity.max_hp = mem.charDetails.max_hp; - entity.mp = mem.charDetails.mp; - // This doesn't exist in memory, so just send the right value. - // As there are other versions that still have it, don't change the event. - entity.max_mp = 10000; - entity.shield_value = mem.charDetails.shieldPercentage * entity.max_hp / 100; - - if (IsGatherer(entity.job)) { - entity.gp = mem.charDetails.gp; - entity.max_gp = mem.charDetails.max_gp; - } - if (IsCrafter(entity.job)) { - entity.cp = mem.charDetails.cp; - entity.max_cp = mem.charDetails.max_cp; - } - - entity.level = mem.charDetails.level; - - var job_bytes = GetRawJobSpecificDataBytes(); - if (job_bytes != null) { - for (var i = 0; i < job_bytes.Length; ++i) { - if (entity.debug_job != "") - entity.debug_job += " "; - entity.debug_job += string.Format("{0:x2}", job_bytes[i]); - } - } - } - return entity; - } - } - - internal override EntityData GetEntityData(IntPtr entity_ptr) { - if (entity_ptr == IntPtr.Zero) - return null; - var source = Read8(entity_ptr, EntityMemory.Size); - return GetEntityDataFromByteArray(source); - } - public override EntityData GetSelfData() { - if (!HasProcess() || player_ptr_addr_ == IntPtr.Zero) - return null; - - var entity_ptr = ReadIntPtr(player_ptr_addr_); - if (entity_ptr == IntPtr.Zero) - return null; - var data = GetEntityData(entity_ptr); - if (data.job == EntityJob.FSH) - data.bait = GetBait(); - return data; - } - - public unsafe override JObject GetJobSpecificData(EntityJob job) { - if (!HasProcess() || job_data_outer_addr_ == IntPtr.Zero) - return null; - - var job_inner_ptr = ReadIntPtr(job_data_outer_addr_); - if (job_inner_ptr == IntPtr.Zero) { - // The pointer can be null when not logged in. - return null; - } - job_inner_ptr = IntPtr.Add(job_inner_ptr, kJobDataInnerStructOffset); - - fixed (byte* p = Read8(job_inner_ptr, kJobDataInnerStructSize)) { - if (p == null) { - return null; - } else { - switch (job) { - case EntityJob.RDM: - return JObject.FromObject(*(RedMageJobMemory*)&p[0]); - case EntityJob.WAR: - return JObject.FromObject(*(WarriorJobMemory*)&p[0]); - case EntityJob.DRK: - return JObject.FromObject(*(DarkKnightJobMemory*)&p[0]); - case EntityJob.PLD: - return JObject.FromObject(*(PaladinJobMemory*)&p[0]); - case EntityJob.GNB: - return JObject.FromObject(*(GunbreakerJobMemory*)&p[0]); - case EntityJob.BRD: - return JObject.FromObject(*(BardJobMemory*)&p[0]); - case EntityJob.DNC: - return JObject.FromObject(*(DancerJobMemory*)&p[0]); - case EntityJob.DRG: - return JObject.FromObject(*(DragoonJobMemory*)&p[0]); - case EntityJob.NIN: - return JObject.FromObject(*(NinjaJobMemory*)&p[0]); - case EntityJob.THM: - return JObject.FromObject(*(ThaumaturgeJobMemory*)&p[0]); - case EntityJob.BLM: - return JObject.FromObject(*(BlackMageJobMemory*)&p[0]); - case EntityJob.WHM: - return JObject.FromObject(*(WhiteMageJobMemory*)&p[0]); - case EntityJob.ACN: - return JObject.FromObject(*(ArcanistJobMemory*)&p[0]); - case EntityJob.SMN: - return JObject.FromObject(*(SummonerJobMemory*)&p[0]); - case EntityJob.SCH: - return JObject.FromObject(*(ScholarJobMemory*)&p[0]); - case EntityJob.PGL: - return JObject.FromObject(*(PugilistJobMemory*)&p[0]); - case EntityJob.MNK: - return JObject.FromObject(*(MonkJobMemory*)&p[0]); - case EntityJob.MCH: - return JObject.FromObject(*(MachinistJobMemory*)&p[0]); - case EntityJob.AST: - return JObject.FromObject(*(AstrologianJobMemory*)&p[0]); - case EntityJob.SAM: - return JObject.FromObject(*(SamuraiJobMemory*)&p[0]); - } - return null; - } - } - } - - [Serializable] - [StructLayout(LayoutKind.Explicit)] - public struct RedMageJobMemory { - [FieldOffset(0x00)] - public byte whiteMana; - - [FieldOffset(0x01)] - public byte blackMana; - }; - - [Serializable] - [StructLayout(LayoutKind.Explicit)] - public struct WarriorJobMemory { - [FieldOffset(0x00)] - public byte beast; - }; - - [Serializable] - [StructLayout(LayoutKind.Explicit)] - public struct DarkKnightJobMemory { - [FieldOffset(0x00)] - public byte blood; - - [FieldOffset(0x02)] - public ushort darksideMilliseconds; - - [FieldOffset(0x04)] - public byte darkArts; - - [FieldOffset(0x06)] - public ushort livingShadowMilliseconds; - }; - - [Serializable] - [StructLayout(LayoutKind.Explicit)] - public struct PaladinJobMemory { - [FieldOffset(0x00)] - public byte oath; - }; - - [Serializable] - [StructLayout(LayoutKind.Explicit)] - public struct GunbreakerJobMemory { - [FieldOffset(0x00)] - public byte cartridges; - - [FieldOffset(0x02)] - private ushort continuationMilliseconds; // Is 15000 if and only if continuationState is not zero. - - [FieldOffset(0x04)] - public byte continuationState; - }; - - [Serializable] - [StructLayout(LayoutKind.Explicit)] - public struct BardJobMemory { - private enum Song : byte { - None = 0, - Ballad = 5, // Mage's Ballad. - Paeon = 10, // Army's Paeon. - Minuet = 15, // The Wanderer's Minuet. - } - - [FieldOffset(0x00)] - public ushort songMilliseconds; - - [FieldOffset(0x02)] - public byte songProcs; - - [FieldOffset(0x03)] - public byte soulGauge; - - [NonSerialized] - [FieldOffset(0x04)] - private Song song_type; - - public String songName => !Enum.IsDefined(typeof(Song), song_type) ? "None" : song_type.ToString(); - }; - - [StructLayout(LayoutKind.Explicit)] - public struct DancerJobMemory { - private enum Step : byte { - None = 0, - Emboite = 1, - Entrechat = 2, - Jete = 3, - Pirouette = 4, - } - - [FieldOffset(0x00)] - public byte feathers; - - [FieldOffset(0x01)] - public byte esprit; - - [NonSerialized] - [FieldOffset(0x02)] - private Step step1; // Order of steps in current Standard Step/Technical Step combo. - - [NonSerialized] - [FieldOffset(0x03)] - private Step step2; - - [NonSerialized] - [FieldOffset(0x04)] - private Step step3; - - [NonSerialized] - [FieldOffset(0x05)] - private Step step4; - - [FieldOffset(0x06)] - public byte currentStep; // Number of steps executed in current Standard Step/Technical Step combo. - - public string steps { - get { - var _steps = step1 == Step.None ? "None" : step1.ToString(); - _steps += step2 != Step.None ? ", " + step2.ToString() : ""; - _steps += step3 != Step.None ? ", " + step3.ToString() : ""; - _steps += step4 != Step.None ? ", " + step4.ToString() : ""; - return _steps; - } - } - }; - - [Serializable] - [StructLayout(LayoutKind.Explicit)] - public struct DragoonJobMemory { - [NonSerialized] - [FieldOffset(0x00)] - private ushort blood_or_life_ms; - - [NonSerialized] - [FieldOffset(0x02)] - private byte stance; // 0 = None, 1 = Blood, 2 = Life - - [FieldOffset(0x03)] - public byte eyesAmount; - - public uint bloodMilliseconds { - get { - if (stance == 1) - return blood_or_life_ms; - else - return 0; - } - } - public uint lifeMilliseconds { - get { - if (stance == 2) - return blood_or_life_ms; - else - return 0; - } - } - }; - - [Serializable] - [StructLayout(LayoutKind.Explicit)] - public struct NinjaJobMemory { - [FieldOffset(0x00)] - public uint hutonMilliseconds; - - [FieldOffset(0x04)] - public byte ninkiAmount; - - [FieldOffset(0x05)] - private byte hutonCount; // Why though? - }; - - [StructLayout(LayoutKind.Explicit)] - public struct ThaumaturgeJobMemory { - [FieldOffset(0x02)] - public ushort umbralMilliseconds; // Number of ms left in umbral fire/ice. - - [FieldOffset(0x04)] - public sbyte umbralStacks; // Positive = Umbral Fire Stacks, Negative = Umbral Ice Stacks. - }; - - [StructLayout(LayoutKind.Explicit)] - public struct BlackMageJobMemory { - [FieldOffset(0x00)] - public ushort nextPolyglotMilliseconds; // Number of ms left before polyglot proc. - - [FieldOffset(0x02)] - public ushort umbralMilliseconds; // Number of ms left in umbral fire/ice. - - [FieldOffset(0x04)] - public sbyte umbralStacks; // Positive = Umbral Fire Stacks, Negative = Umbral Ice Stacks. - - [FieldOffset(0x05)] - public byte umbralHearts; - - [FieldOffset(0x06)] - public byte foulCount; - - [NonSerialized] - [FieldOffset(0x07)] - private byte enochian_state; // Bit 0 = Enochian active. Bit 1 = Polygot active. - - public bool enochian => (enochian_state & 0xF) == 1; - }; - - [StructLayout(LayoutKind.Explicit)] - public struct WhiteMageJobMemory { - [FieldOffset(0x02)] - public ushort lilyMilliseconds; // Number of ms left before lily gain. - - [FieldOffset(0x04)] - public byte lilyStacks; - - [FieldOffset(0x05)] - public byte bloodlilyStacks; - }; - - [StructLayout(LayoutKind.Explicit)] - public struct ArcanistJobMemory { - [FieldOffset(0x04)] - public byte aetherflowStacks; - }; - - [StructLayout(LayoutKind.Explicit)] - public struct SummonerJobMemory { - [FieldOffset(0x00)] - public ushort stanceMilliseconds; // Dreadwyrm or Bahamut/Phoenix time left in ms. - - [FieldOffset(0x02)] - public byte bahamutStance; // 5 if Bahamut/Phoenix summoned, else 0. - - [FieldOffset(0x03)] - public byte bahamutSummoned; // 1 if Bahamut/Phoenix summoned, else 0. - - [NonSerialized] - [FieldOffset(0x04)] - private byte stacks; // Bits 1-2: Aetherflow. Bits 3-4: Dreadwyrm. Bit 5: Phoenix ready. - - public int aetherflowStacks => (stacks >> 0) & 0x3; // Bottom 2 bits. - - public int dreadwyrmStacks => (stacks >> 2) & 0x3; // Bottom 2 bits. - - public bool phoenixReady => ((stacks >> 4) & 0x3) == 1; // Bottom 2 bits. - }; - - [StructLayout(LayoutKind.Explicit)] - public struct ScholarJobMemory { - [FieldOffset(0x02)] - public byte aetherflowStacks; - - [FieldOffset(0x03)] - public byte fairyGauge; - - [FieldOffset(0x04)] - public ushort fairyMilliseconds; // Seraph time left ms. - - [FieldOffset(0x06)] - public byte fairyStatus; // Varies depending on which fairy was summoned, during Seraph/Dissipation: 6 - Eos, 7 - Selene, else 0. - }; - - [StructLayout(LayoutKind.Explicit)] - public struct PugilistJobMemory { - [FieldOffset(0x00)] - public ushort lightningMilliseconds; - - [FieldOffset(0x02)] - public byte lightningStacks; - }; - - [StructLayout(LayoutKind.Explicit)] - public struct MonkJobMemory { - [FieldOffset(0x00)] - public ushort lightningMilliseconds; - - [FieldOffset(0x02)] - public byte lightningStacks; - - [FieldOffset(0x03)] - public byte chakraStacks; - - [NonSerialized] - [FieldOffset(0x04)] - private byte _lightningTimerState; - - public bool lightningTimerFrozen => (_lightningTimerState > 0); - }; - - [StructLayout(LayoutKind.Explicit)] - public struct MachinistJobMemory { - [FieldOffset(0x00)] - public ushort overheatMilliseconds; - - [FieldOffset(0x02)] - public ushort batteryMilliseconds; - - [FieldOffset(0x04)] - public byte heat; - - [FieldOffset(0x05)] - public byte battery; - - [FieldOffset(0x06)] - public byte lastBatteryAmount; - - [NonSerialized] - [FieldOffset(0x07)] - private byte chargeTimerState; - - public bool overheatActive => (chargeTimerState & 0x1) == 1; - - public bool robotActive => (chargeTimerState & 0x2) == 1; - }; - - [StructLayout(LayoutKind.Explicit)] - public struct AstrologianJobMemory { - public enum Card : byte { - None = 0, - Balance = 1, - Bole = 2, - Arrow = 3, - Spear = 4, - Ewer = 5, - Spire = 6, - } - - public enum Arcanum : byte { - None = 0, - Solar = 1, - Lunar = 2, - Celestial = 3, - } - - [FieldOffset(0x04)] - private Card _heldCard; - - [NonSerialized] - [FieldOffset(0x05)] - private Arcanum arcanum_1; - - [NonSerialized] - [FieldOffset(0x06)] - private Arcanum arcanum_2; - - [NonSerialized] - [FieldOffset(0x07)] - private Arcanum arcanum_3; - - public string heldCard => _heldCard.ToString(); - - public string arcanums { - get { - var _arcanums = arcanum_1 == Arcanum.None ? "None" : arcanum_1.ToString(); - _arcanums += arcanum_2 != Arcanum.None ? ", " + arcanum_2.ToString() : ""; - _arcanums += arcanum_3 != Arcanum.None ? ", " + arcanum_3.ToString() : ""; - return _arcanums; - } - } - }; - - [StructLayout(LayoutKind.Explicit)] - public struct SamuraiJobMemory { - [FieldOffset(0x03)] - public byte kenki; - - [FieldOffset(0x04)] - public byte meditationStacks; - - [NonSerialized] - [FieldOffset(0x05)] - private byte sen_bits; - - public bool setsu => (sen_bits & 0x1) != 0; - - public bool getsu => (sen_bits & 0x2) != 0; - - public bool ka => (sen_bits & 0x4) != 0; - } - } -} \ No newline at end of file diff --git a/OverlayPlugin.Core/OverlayPlugin.Core.csproj b/OverlayPlugin.Core/OverlayPlugin.Core.csproj index 4ad8932..1c97ab1 100644 --- a/OverlayPlugin.Core/OverlayPlugin.Core.csproj +++ b/OverlayPlugin.Core/OverlayPlugin.Core.csproj @@ -8,7 +8,7 @@ true true win-x64 - 11 + 10 MinimumRecommendedRules.ruleset diff --git a/OverlayPlugin.Core/OverlayPlugin.Core.csproj.bak b/OverlayPlugin.Core/OverlayPlugin.Core.csproj.bak new file mode 100644 index 0000000..ecb8f6e --- /dev/null +++ b/OverlayPlugin.Core/OverlayPlugin.Core.csproj.bak @@ -0,0 +1,79 @@ + + + net6.0-windows + true + Library + RainbowMage.OverlayPlugin + false + true + true + win-x64 + 11 + + + MinimumRecommendedRules.ruleset + true + + + + + MinimumRecommendedRules.ruleset + true + + + + + + + + + + + + + + UserControl + + + UserControl + + + UserControl + + + Component + + + + + + + + + + + + + 13.0.1 + + + + + + x64 + + + + ..\external_dependencies\FFXIV_ACT_Plugin.dll + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Common.dll + + + ..\external_dependencies\SDK\FFXIV_ACT_Plugin.Logfile.dll + + + ..\external_dependencies\Machina.FFXIV.dll + + + \ No newline at end of file