using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Reflection; using System.Threading.Tasks; using System.Windows.Forms; using AutoRetainerAPI; using Dalamud; using Dalamud.Game; using Dalamud.Plugin; using Dalamud.Plugin.Services; using ECommons; using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.UI.Agent; using FFXIVClientStructs.FFXIV.Component.GUI; using LLib; using LLib.GameUI; using Task = System.Threading.Tasks.Task; namespace AutoShutdown; public sealed class Plogon : IDalamudPlugin { private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(1) }; private readonly nint _skipMovieAddress; private readonly byte[] _skipMovieOriginalBytes; private readonly AutoRetainerApi _autoRetainerApi; private readonly DalamudReflector _reflector; private readonly IPluginLog _pluginLog; private readonly IClientState _clientState; private readonly IGameGui _gameGui; private readonly IFramework _framework; public Plogon(IDalamudPluginInterface pluginInterface, IFramework framework, IPluginLog pluginLog, IClientState clientState, IGameGui gameGui, ISigScanner sigScanner) { _skipMovieAddress = sigScanner.ScanText("48 01 8E ?? ?? ?? ?? 48 81 BE ?? ?? ?? ?? 60 EA 00 00"); SafeMemory.ReadBytes(_skipMovieAddress, 7, out _skipMovieOriginalBytes); ECommonsMain.Init(pluginInterface, this); _autoRetainerApi = new AutoRetainerApi(); _reflector = new DalamudReflector(pluginInterface, framework, pluginLog); _framework = framework; _pluginLog = pluginLog; _clientState = clientState; _gameGui = gameGui; _clientState.Logout += OnLogout; _framework.Update += FrameworkUpdate; _framework.RunOnTick(MoveToBackground, TimeSpan.FromSeconds(10)); // we don't want to watch movies, regardless of how long we're gone - 7 byte NOP for the 'add [rsi+1110h], rcx' SafeMemory.WriteBytes(_skipMovieAddress, [0x0F, 0x1F, 0x80, 0x00, 0x00, 0x00, 0x00]); unsafe { var lobby = AgentLobby.Instance(); if (lobby != null) lobby->IdleTime = 0; } } private DateTime ShutdownAt { get; } = DateTime.Now.AddDays(2); private unsafe void FrameworkUpdate(IFramework _) { if (DateTime.Now > ShutdownAt && _gameGui.TryGetAddonByName("_TitleMenu", out var _)) { Environment.Exit(0); } } private bool IsMultiAndNightMode() { if (_autoRetainerApi.Ready && _reflector.TryGetDalamudPlugin("AutoRetainer", out var autoRetainer, false, true)) { var mm = autoRetainer.GetType().Assembly.GetType("AutoRetainer.Modules.Multi.MultiMode")!; bool multi = (bool)mm.GetProperty("Active", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!; var config = autoRetainer.GetType().GetProperty("C", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!; bool nightMode = (bool)config.GetType().GetField("NightMode", BindingFlags.Instance | BindingFlags.Public)! .GetValue(config)!; //_pluginLog.Information($"AR: multi={multi}, night={nightMode}"); return multi && nightMode; } return false; } private void MoveToBackground() { if (IsMultiAndNightMode()) { // move to background using alt+esc unsafe { if (!Framework.Instance()->WindowInactive) SendKeys.SendWait("%({esc})"); } } } private void OnLogout() { if (IsMultiAndNightMode()) { List nextVesselTimes = _autoRetainerApi.GetRegisteredCharacters().SelectMany(localContentId => { var data = _autoRetainerApi.GetOfflineCharacterData(localContentId); return data.OfflineSubmarineData.Where(x => data.EnabledSubs.Contains(x.Name)) .Select(x => TimeSpan.FromSeconds(x.ReturnTime - Framework.GetServerTime())) .Select(x => x <= TimeSpan.Zero ? TimeSpan.Zero : x) .ToList(); }) .ToList(); if (nextVesselTimes.Count > 0) { TimeSpan next = nextVesselTimes.Min(); _pluginLog.Information($"Next vessel time: {next}"); if (next != TimeSpan.Zero) { Task.Factory.StartNew(async () => { try { await _httpClient.PostAsync(new Uri($"http://localhost:12994/{next.TotalSeconds:F0}"), null); } catch (Exception e) { _pluginLog.Warning(e, "Unable to send IPC"); } }, TaskCreationOptions.LongRunning); } } } MoveToBackground(); } public void Dispose() { SafeMemory.WriteBytes(_skipMovieAddress, _skipMovieOriginalBytes); _framework.Update -= FrameworkUpdate; _clientState.Logout -= OnLogout; _reflector.Dispose(); _autoRetainerApi.Dispose(); ECommonsMain.Dispose(); } }