Sleep?
This commit is contained in:
parent
f1373fcbfa
commit
24e5bafeb9
@ -1 +1 @@
|
|||||||
Subproject commit 75a0db11d7cc982b54f9cacb8a2b9c17b023b718
|
Subproject commit 6f0aaa55bce6ec79fd4d72f84f21597b39e5445d
|
@ -1,8 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0-windows</TargetFramework>
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
<Version>0.1</Version>
|
<Version>0.1</Version>
|
||||||
<LangVersion>11.0</LangVersion>
|
<LangVersion>12</LangVersion>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
@ -13,6 +13,7 @@
|
|||||||
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
<DebugType>portable</DebugType>
|
<DebugType>portable</DebugType>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
@ -1,63 +1,104 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms;
|
using System.Windows.Forms;
|
||||||
using AutoRetainerAPI;
|
using AutoRetainerAPI;
|
||||||
|
using Dalamud;
|
||||||
|
using Dalamud.Game;
|
||||||
|
using Dalamud.Game.Addon.Lifecycle;
|
||||||
|
using Dalamud.Game.Addon.Lifecycle.AddonArgTypes;
|
||||||
|
using Dalamud.Memory;
|
||||||
using Dalamud.Plugin;
|
using Dalamud.Plugin;
|
||||||
using Dalamud.Plugin.Services;
|
using Dalamud.Plugin.Services;
|
||||||
using ECommons;
|
using ECommons;
|
||||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.UI.Agent;
|
||||||
|
using FFXIVClientStructs.FFXIV.Component.GUI;
|
||||||
using LLib;
|
using LLib;
|
||||||
|
using LLib.GameUI;
|
||||||
|
using Task = System.Threading.Tasks.Task;
|
||||||
|
|
||||||
namespace AutoShutdown;
|
namespace AutoShutdown;
|
||||||
|
|
||||||
public sealed class Plogon : IDalamudPlugin
|
public sealed class Plogon : IDalamudPlugin
|
||||||
{
|
{
|
||||||
|
private readonly nint _skipMovieAddress;
|
||||||
|
private readonly byte[] _skipMovieOriginalBytes;
|
||||||
private readonly AutoRetainerApi _autoRetainerApi;
|
private readonly AutoRetainerApi _autoRetainerApi;
|
||||||
private readonly DalamudReflector _reflector;
|
private readonly DalamudReflector _reflector;
|
||||||
private readonly IPluginLog _pluginLog;
|
private readonly IPluginLog _pluginLog;
|
||||||
private readonly IClientState _clientState;
|
private readonly IClientState _clientState;
|
||||||
|
private readonly IGameGui _gameGui;
|
||||||
|
private readonly IFramework _framework;
|
||||||
|
|
||||||
public Plogon(DalamudPluginInterface pluginInterface, IFramework framework, IPluginLog pluginLog,
|
public Plogon(DalamudPluginInterface pluginInterface, IFramework framework, IPluginLog pluginLog,
|
||||||
IClientState clientState)
|
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);
|
ECommonsMain.Init(pluginInterface, this);
|
||||||
_autoRetainerApi = new AutoRetainerApi();
|
_autoRetainerApi = new AutoRetainerApi();
|
||||||
_reflector = new DalamudReflector(pluginInterface, framework, pluginLog);
|
_reflector = new DalamudReflector(pluginInterface, framework, pluginLog);
|
||||||
|
|
||||||
|
_framework = framework;
|
||||||
_pluginLog = pluginLog;
|
_pluginLog = pluginLog;
|
||||||
_clientState = clientState;
|
_clientState = clientState;
|
||||||
|
_gameGui = gameGui;
|
||||||
|
|
||||||
_clientState.Logout += SetupShutdown;
|
_clientState.Logout += OnLogout;
|
||||||
|
_framework.Update += FrameworkUpdate;
|
||||||
|
|
||||||
framework.RunOnTick(() => SetupShutdown(), TimeSpan.FromSeconds(10));
|
_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 long StartedAt { get; } = Environment.TickCount64;
|
private DateTime ShutdownAt { get; } = DateTime.Now.AddDays(2);
|
||||||
private long ShutdownAt => StartedAt + (long)TimeSpan.FromDays(2).TotalMilliseconds;
|
|
||||||
|
|
||||||
private void SetupShutdown()
|
private unsafe void FrameworkUpdate(IFramework _)
|
||||||
|
{
|
||||||
|
if (DateTime.Now > ShutdownAt && _gameGui.TryGetAddonByName<AtkUnitBase>("_TitleMenu", out var addon))
|
||||||
|
{
|
||||||
|
Environment.Exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsMultiAndNightMode()
|
||||||
{
|
{
|
||||||
if (_autoRetainerApi.Ready && _reflector.TryGetDalamudPlugin("AutoRetainer", out var autoRetainer, false, true))
|
if (_autoRetainerApi.Ready && _reflector.TryGetDalamudPlugin("AutoRetainer", out var autoRetainer, false, true))
|
||||||
{
|
{
|
||||||
var shutdown = autoRetainer.GetType().Assembly.GetType("AutoRetainer.Modules.Shutdown")!;
|
var mm = autoRetainer.GetType().Assembly.GetType("AutoRetainer.Modules.Multi.MultiMode")!;
|
||||||
|
bool multi = (bool)mm.GetProperty("Active", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!;
|
||||||
|
|
||||||
var shutdownAt = shutdown.GetField("ShutdownAt", BindingFlags.Static | BindingFlags.NonPublic)!;
|
var config =
|
||||||
var forceShutdownAt = shutdown.GetField("ForceShutdownAt", BindingFlags.Static | BindingFlags.NonPublic)!;
|
autoRetainer.GetType().GetProperty("C", BindingFlags.Static | BindingFlags.NonPublic)!.GetValue(null)!;
|
||||||
|
bool nightMode = (bool)config.GetType().GetField("NightMode", BindingFlags.Instance | BindingFlags.Public)!
|
||||||
|
.GetValue(config)!;
|
||||||
|
|
||||||
var currentShutdownAt = (long?)shutdownAt.GetValue(null) ?? 0;
|
//_pluginLog.Information($"AR: multi={multi}, night={nightMode}");
|
||||||
if (currentShutdownAt == 0)
|
|
||||||
|
return multi && nightMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MoveToBackground()
|
||||||
{
|
{
|
||||||
_pluginLog.Information(
|
if (IsMultiAndNightMode())
|
||||||
$"Setting shutdown date to {new DateTime(TimeSpan.FromMilliseconds(ShutdownAt).Ticks)}");
|
|
||||||
shutdownAt.SetValue(null, ShutdownAt);
|
|
||||||
forceShutdownAt.SetValue(null, ShutdownAt + (long)TimeSpan.FromMinutes(10).TotalMilliseconds);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
_pluginLog.Information(
|
|
||||||
$"Shutdown is already set to {new DateTime(TimeSpan.FromMilliseconds(currentShutdownAt).Ticks)}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// move to background using alt+esc
|
// move to background using alt+esc
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
@ -65,10 +106,54 @@ public sealed class Plogon : IDalamudPlugin
|
|||||||
SendKeys.SendWait("%({esc})");
|
SendKeys.SendWait("%({esc})");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLogout()
|
||||||
|
{
|
||||||
|
if (IsMultiAndNightMode())
|
||||||
|
{
|
||||||
|
List<TimeSpan> 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}");
|
||||||
|
Task.Factory.StartNew(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using NamedPipeClientStream pipe =
|
||||||
|
new NamedPipeClientStream(".", "ffxiv_TheWatcher", PipeDirection.Out);
|
||||||
|
await pipe.ConnectAsync(1000);
|
||||||
|
|
||||||
|
byte[] content = Encoding.UTF8.GetBytes($"{next.TotalSeconds}");
|
||||||
|
await pipe.WriteAsync(content, 0, content.Length);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_pluginLog.Warning(e, "Unable to send IPC");
|
||||||
|
}
|
||||||
|
}, TaskCreationOptions.LongRunning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveToBackground();
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_clientState.Logout -= SetupShutdown;
|
SafeMemory.WriteBytes(_skipMovieAddress, _skipMovieOriginalBytes);
|
||||||
|
_framework.Update -= FrameworkUpdate;
|
||||||
|
_clientState.Logout -= OnLogout;
|
||||||
|
|
||||||
_reflector.Dispose();
|
_reflector.Dispose();
|
||||||
_autoRetainerApi.Dispose();
|
_autoRetainerApi.Dispose();
|
||||||
ECommonsMain.Dispose();
|
ECommonsMain.Dispose();
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"net7.0-windows7.0": {
|
"net8.0-windows7.0": {
|
||||||
"DalamudPackager": {
|
"DalamudPackager": {
|
||||||
"type": "Direct",
|
"type": "Direct",
|
||||||
"requested": "[2.1.12, )",
|
"requested": "[2.1.12, )",
|
||||||
|
2
ECommons
2
ECommons
@ -1 +1 @@
|
|||||||
Subproject commit f1c688a0599b41d70230021328a575da7351cf91
|
Subproject commit d238d4188e8b47b11252d75cb5e4b678b8da2756
|
2
LLib
2
LLib
@ -1 +1 @@
|
|||||||
Subproject commit 865a6080319f8ccbcd5fd5b0004404822b6e60d4
|
Subproject commit 3792244261a9f5426a7916f5a6dd1966238ba84a
|
22
TheWatcher/NativeMethods.cs
Normal file
22
TheWatcher/NativeMethods.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace TheWatcher;
|
||||||
|
|
||||||
|
internal static class NativeMethods
|
||||||
|
{
|
||||||
|
[DllImport("powrprof.dll", SetLastError = true)]
|
||||||
|
[return: MarshalAs(UnmanagedType.Bool)]
|
||||||
|
public static extern bool SetSuspendState(bool hibernate, bool forceCritical, bool disableWakeEvent);
|
||||||
|
|
||||||
|
public delegate void TimerCompleteDelegate();
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
public static extern IntPtr CreateWaitableTimer(IntPtr lpTimerAttributes, bool bManualReset, string lpTimerName);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
public static extern bool SetWaitableTimer(IntPtr hTimer, [In] ref long pDueTime, int lPeriod,
|
||||||
|
TimerCompleteDelegate? pfnCompletionRoutine, IntPtr pArgToCompletionRoutine, bool fResume);
|
||||||
|
|
||||||
|
[DllImport("kernel32.dll", SetLastError = true)]
|
||||||
|
public static extern bool CancelWaitableTimer(IntPtr hTimer);
|
||||||
|
}
|
@ -1,25 +1,28 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO.Pipes;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace TheWatcher;
|
namespace TheWatcher;
|
||||||
|
|
||||||
internal sealed class Program
|
internal sealed class Program
|
||||||
{
|
{
|
||||||
|
private static CancellationToken CancellationToken { get; set; }
|
||||||
|
private static bool EnableSleep { get; set; }
|
||||||
|
|
||||||
public static async Task Main(string[] args)
|
public static async Task Main(string[] args)
|
||||||
{
|
{
|
||||||
Console.WriteLine("o.O");
|
Console.WriteLine("o.O");
|
||||||
|
|
||||||
using CancellationTokenSource cts = new CancellationTokenSource();
|
using CancellationTokenSource cts = new CancellationTokenSource();
|
||||||
|
CancellationToken = cts.Token;
|
||||||
|
|
||||||
|
_ = Task.Factory.StartNew(async () => { await RunServer(); }, TaskCreationOptions.LongRunning);
|
||||||
|
|
||||||
Console.CancelKeyPress += (_, e) =>
|
Console.CancelKeyPress += (_, e) =>
|
||||||
{
|
{
|
||||||
try
|
EnableSleep = !EnableSleep;
|
||||||
{
|
Console.WriteLine($"[{DateTime.Now}] Sleep => {EnableSleep}");
|
||||||
// ReSharper disable once AccessToDisposedClosure
|
|
||||||
cts.Cancel();
|
|
||||||
}
|
|
||||||
catch (ObjectDisposedException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Cancel = true;
|
e.Cancel = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -99,4 +102,102 @@ internal sealed class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SleepUntil(DateTime dt)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Sleeping until {dt}...");
|
||||||
|
|
||||||
|
nint wakeTimer = SetWakeAt(dt);
|
||||||
|
if (wakeTimer != nint.Zero)
|
||||||
|
{
|
||||||
|
bool suspended = NativeMethods.SetSuspendState(false, false, false);
|
||||||
|
if (suspended)
|
||||||
|
{
|
||||||
|
if (DateTime.Now < dt)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Resumed from suspend, but expected to sleep until {dt} => deactivating sleep");
|
||||||
|
EnableSleep = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Resumed from suspend");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[{DateTime.Now}] Not Suspended / {Marshal.GetLastPInvokeError()} - {Marshal.GetLastPInvokeErrorMessage()}");
|
||||||
|
NativeMethods.CancelWaitableTimer(wakeTimer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[{DateTime.Now}] Not able to set wait timer / {Marshal.GetLastPInvokeError()} - {Marshal.GetLastPInvokeErrorMessage()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IntPtr SetWakeAt(DateTime dt)
|
||||||
|
{
|
||||||
|
NativeMethods.TimerCompleteDelegate? timerComplete = null;
|
||||||
|
|
||||||
|
// read the manual for SetWaitableTimer to understand how this number is interpreted.
|
||||||
|
long interval = dt.ToFileTimeUtc();
|
||||||
|
IntPtr handle = NativeMethods.CreateWaitableTimer(IntPtr.Zero, true, "WaitableTimer");
|
||||||
|
NativeMethods.SetWaitableTimer(handle, ref interval, 0, timerComplete, IntPtr.Zero, true);
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task RunServer()
|
||||||
|
{
|
||||||
|
while (!CancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
await using NamedPipeServerStream pipeServer =
|
||||||
|
new NamedPipeServerStream("ffxiv_TheWatcher", PipeDirection.In, 1, PipeTransmissionMode.Message);
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Waiting for client...");
|
||||||
|
|
||||||
|
await pipeServer.WaitForConnectionAsync();
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Client connected...");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string message = await ReadMessage(pipeServer);
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Client message: {message}");
|
||||||
|
|
||||||
|
if (!EnableSleep)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Ignoring sleep");
|
||||||
|
}
|
||||||
|
else if (double.TryParse(message, out double duration))
|
||||||
|
{
|
||||||
|
TimeSpan timeSpan = TimeSpan.FromSeconds(duration);
|
||||||
|
if (timeSpan >= TimeSpan.FromMinutes(10))
|
||||||
|
{
|
||||||
|
DateTime sleep = DateTime.Now.Add(timeSpan).Add(TimeSpan.FromMinutes(-5));
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(35));
|
||||||
|
SleepUntil(sleep);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Not enough sleep duration");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
Console.WriteLine("ERROR: {0}", e.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
Console.WriteLine($"[{DateTime.Now}] Finished client loop...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static async Task<string> ReadMessage(PipeStream pipe)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
using var ms = new MemoryStream();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
var readBytes = await pipe.ReadAsync(buffer, 0, buffer.Length);
|
||||||
|
await ms.WriteAsync(buffer, 0, readBytes);
|
||||||
|
} while (!pipe.IsMessageComplete);
|
||||||
|
|
||||||
|
return Encoding.UTF8.GetString(ms.ToArray());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
<TargetPlatformIdentifier>windows</TargetPlatformIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user