master
Liza 2024-03-21 19:56:30 +01:00
parent 24e5bafeb9
commit d6ba65e939
Signed by: liza
GPG Key ID: 7199F8D727D55F67
4 changed files with 145 additions and 126 deletions

View File

@ -1,17 +1,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO.Pipes;
using System.Linq; using System.Linq;
using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using AutoRetainerAPI; using AutoRetainerAPI;
using Dalamud; using Dalamud;
using Dalamud.Game; 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;
@ -26,6 +22,7 @@ namespace AutoShutdown;
public sealed class Plogon : IDalamudPlugin public sealed class Plogon : IDalamudPlugin
{ {
private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(1) };
private readonly nint _skipMovieAddress; private readonly nint _skipMovieAddress;
private readonly byte[] _skipMovieOriginalBytes; private readonly byte[] _skipMovieOriginalBytes;
private readonly AutoRetainerApi _autoRetainerApi; private readonly AutoRetainerApi _autoRetainerApi;
@ -69,7 +66,7 @@ public sealed class Plogon : IDalamudPlugin
private unsafe void FrameworkUpdate(IFramework _) private unsafe void FrameworkUpdate(IFramework _)
{ {
if (DateTime.Now > ShutdownAt && _gameGui.TryGetAddonByName<AtkUnitBase>("_TitleMenu", out var addon)) if (DateTime.Now > ShutdownAt && _gameGui.TryGetAddonByName<AtkUnitBase>("_TitleMenu", out var _))
{ {
Environment.Exit(0); Environment.Exit(0);
} }
@ -126,22 +123,21 @@ public sealed class Plogon : IDalamudPlugin
{ {
TimeSpan next = nextVesselTimes.Min(); TimeSpan next = nextVesselTimes.Min();
_pluginLog.Information($"Next vessel time: {next}"); _pluginLog.Information($"Next vessel time: {next}");
Task.Factory.StartNew(async () => if (next != TimeSpan.Zero)
{ {
try Task.Factory.StartNew(async () =>
{ {
await using NamedPipeClientStream pipe = try
new NamedPipeClientStream(".", "ffxiv_TheWatcher", PipeDirection.Out); {
await pipe.ConnectAsync(1000); await _httpClient.PostAsync(new Uri($"http://localhost:12994/{next.TotalSeconds:F0}"),
null);
byte[] content = Encoding.UTF8.GetBytes($"{next.TotalSeconds}"); }
await pipe.WriteAsync(content, 0, content.Length); catch (Exception e)
} {
catch (Exception e) _pluginLog.Warning(e, "Unable to send IPC");
{ }
_pluginLog.Warning(e, "Unable to send IPC"); }, TaskCreationOptions.LongRunning);
} }
}, TaskCreationOptions.LongRunning);
} }
} }

73
TheWatcher/Http.cs Normal file
View File

@ -0,0 +1,73 @@
using System.Net;
using System.Text;
namespace TheWatcher;
internal sealed class Http
{
private readonly HttpListener _listener;
private readonly byte[] _data = "OK"u8.ToArray();
public Http()
{
_listener = new HttpListener();
// netsh http add urlacl url=http://+:12994/ user=username
_listener.Prefixes.Add("http://+:12994/");
_listener.Start();
}
public async Task RunAsync(CancellationToken token)
{
token.Register(Close);
while (!token.IsCancellationRequested)
{
// wait for the next request
HttpListenerContext ctx = await _listener.GetContextAsync();
HttpListenerRequest request = ctx.Request;
HttpListenerResponse response = ctx.Response;
if (request is { HttpMethod: "POST", Url.Segments.Length: >= 2 } &&
double.TryParse(request.Url.Segments[1].TrimEnd('/'), out double duration))
{
TimeSpan timeSpan = TimeSpan.FromSeconds(duration);
if (!SleepCtrl.Enabled)
{
Console.WriteLine($"[{DateTime.Now}] Ignoring sleep, not enabled: {timeSpan}");
}
else if (timeSpan < TimeSpan.FromMinutes(10))
{
Console.WriteLine($"[{DateTime.Now}] Not enough sleep duration: {timeSpan}");
}
else
{
_ = Task.Factory.StartNew(async () =>
{
DateTime until = DateTime.Now.Add(timeSpan).Add(TimeSpan.FromMinutes(-5));
Console.WriteLine($"[{DateTime.Now}] Waiting a few seconds before sleeping for {timeSpan} until {until}");
await Task.Delay(TimeSpan.FromSeconds(35), token);
if (SleepCtrl.Enabled)
SleepCtrl.SleepUntil(until);
else
Console.WriteLine($"[{DateTime.Now}] Ignoring sleep (II), not enabled: {timeSpan}");
}, TaskCreationOptions.LongRunning);
}
}
response.ContentType = "text/plain";
response.ContentEncoding = Encoding.UTF8;
response.ContentLength64 = _data.LongLength;
await response.OutputStream.WriteAsync(_data, token);
response.Close();
}
}
private void Close()
{
_listener.Close();
}
}

View File

@ -1,28 +1,25 @@
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 CancellationToken CancellationToken { get; set; }
private static bool EnableSleep { get; set; } private static readonly Http Http = new();
public static async Task Main(string[] args) public static async Task Main()
{ {
Console.WriteLine("o.O"); Console.WriteLine("o.O");
using CancellationTokenSource cts = new CancellationTokenSource(); using CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken = cts.Token; CancellationToken = cts.Token;
_ = Task.Factory.StartNew(async () => { await RunServer(); }, TaskCreationOptions.LongRunning); _ = Task.Factory.StartNew(async () => { await Http.RunAsync(CancellationToken); }, TaskCreationOptions.LongRunning);
Console.CancelKeyPress += (_, e) => Console.CancelKeyPress += (_, e) =>
{ {
EnableSleep = !EnableSleep; SleepCtrl.Enabled = !SleepCtrl.Enabled;
Console.WriteLine($"[{DateTime.Now}] Sleep => {EnableSleep}"); Console.WriteLine($"[{DateTime.Now}] Sleep => {SleepCtrl.Enabled}");
e.Cancel = true; e.Cancel = true;
}; };
@ -102,102 +99,4 @@ 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());
}
} }

51
TheWatcher/SleepCtrl.cs Normal file
View File

@ -0,0 +1,51 @@
using System.Runtime.InteropServices;
namespace TheWatcher;
public class SleepCtrl
{
public static bool Enabled { get; set; }
public 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");
Enabled = 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;
}
}