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">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<TargetFramework>net8.0-windows</TargetFramework>
|
||||
<Version>0.1</Version>
|
||||
<LangVersion>11.0</LangVersion>
|
||||
<LangVersion>12</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
@ -13,6 +13,7 @@
|
||||
<PathMap Condition="$(SolutionDir) != ''">$(SolutionDir)=X:\</PathMap>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<DebugType>portable</DebugType>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
@ -1,74 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Pipes;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
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.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 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(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);
|
||||
_autoRetainerApi = new AutoRetainerApi();
|
||||
_reflector = new DalamudReflector(pluginInterface, framework, pluginLog);
|
||||
|
||||
_framework = framework;
|
||||
_pluginLog = pluginLog;
|
||||
_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 long ShutdownAt => StartedAt + (long)TimeSpan.FromDays(2).TotalMilliseconds;
|
||||
private DateTime ShutdownAt { get; } = DateTime.Now.AddDays(2);
|
||||
|
||||
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))
|
||||
{
|
||||
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 forceShutdownAt = shutdown.GetField("ForceShutdownAt", BindingFlags.Static | BindingFlags.NonPublic)!;
|
||||
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)!;
|
||||
|
||||
var currentShutdownAt = (long?)shutdownAt.GetValue(null) ?? 0;
|
||||
if (currentShutdownAt == 0)
|
||||
{
|
||||
_pluginLog.Information(
|
||||
$"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)}");
|
||||
}
|
||||
//_pluginLog.Information($"AR: multi={multi}, night={nightMode}");
|
||||
|
||||
return multi && nightMode;
|
||||
}
|
||||
|
||||
// move to background using alt+esc
|
||||
unsafe
|
||||
return false;
|
||||
}
|
||||
|
||||
private void MoveToBackground()
|
||||
{
|
||||
if (IsMultiAndNightMode())
|
||||
{
|
||||
if (!Framework.Instance()->WindowInactive)
|
||||
SendKeys.SendWait("%({esc})");
|
||||
// move to background using alt+esc
|
||||
unsafe
|
||||
{
|
||||
if (!Framework.Instance()->WindowInactive)
|
||||
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()
|
||||
{
|
||||
_clientState.Logout -= SetupShutdown;
|
||||
SafeMemory.WriteBytes(_skipMovieAddress, _skipMovieOriginalBytes);
|
||||
_framework.Update -= FrameworkUpdate;
|
||||
_clientState.Logout -= OnLogout;
|
||||
|
||||
_reflector.Dispose();
|
||||
_autoRetainerApi.Dispose();
|
||||
ECommonsMain.Dispose();
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net7.0-windows7.0": {
|
||||
"net8.0-windows7.0": {
|
||||
"DalamudPackager": {
|
||||
"type": "Direct",
|
||||
"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.IO.Pipes;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace TheWatcher;
|
||||
|
||||
internal sealed class Program
|
||||
{
|
||||
private static CancellationToken CancellationToken { get; set; }
|
||||
private static bool EnableSleep { get; set; }
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("o.O");
|
||||
|
||||
using CancellationTokenSource cts = new CancellationTokenSource();
|
||||
CancellationToken = cts.Token;
|
||||
|
||||
_ = Task.Factory.StartNew(async () => { await RunServer(); }, TaskCreationOptions.LongRunning);
|
||||
|
||||
Console.CancelKeyPress += (_, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// ReSharper disable once AccessToDisposedClosure
|
||||
cts.Cancel();
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
|
||||
EnableSleep = !EnableSleep;
|
||||
Console.WriteLine($"[{DateTime.Now}] Sleep => {EnableSleep}");
|
||||
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>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetPlatformIdentifier>windows</TargetPlatformIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user