All features seem to work.

This commit is contained in:
2026-01-15 02:02:59 +01:00
parent 6c2cd0ced1
commit 6b5bd54f4f
2 changed files with 69 additions and 37 deletions

View File

@@ -25,6 +25,7 @@ namespace RemoteControl.Patches
{ {
try try
{ {
RemoteControl.Log("Sending update");
RemoteControl.Subscribers.SendUpdate(); RemoteControl.Subscribers.SendUpdate();
} }
catch (Exception e) catch (Exception e)

View File

@@ -3,6 +3,8 @@ using UnityEngine;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Net;
using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Assets.Scripts; using Assets.Scripts;
@@ -38,7 +40,7 @@ namespace RemoteControl
private readonly Harmony _harmony = new Harmony(pluginGuid); private readonly Harmony _harmony = new Harmony(pluginGuid);
public static WebServer WebServer { get; private set; } public static WebServer WebServer { get; private set; }
internal static SubscriptionModule Subscribers { get; private set; } internal static SubscriptionModule Subscribers { get; } = new SubscriptionModule("/subscribe");
private CancellationTokenSource _modLifecycle = new(); private CancellationTokenSource _modLifecycle = new();
@@ -46,7 +48,7 @@ namespace RemoteControl
public const string pluginGuid = "com.thequux.stationeers.RemoteControl"; public const string pluginGuid = "com.thequux.stationeers.RemoteControl";
public const string pluginName = "RemoteControl"; public const string pluginName = "RemoteControl";
public const string pluginVersion = "1.0"; public const string pluginVersion = "1.0";
public static void Log(string line) public static void Log(string line)
{ {
Debug.Log("[" + pluginName + "]: " + line); Debug.Log("[" + pluginName + "]: " + line);
@@ -54,13 +56,6 @@ namespace RemoteControl
private void StartRC() private void StartRC()
{ {
if (!_modLifecycle.IsCancellationRequested)
{
_modLifecycle.Cancel();
}
_modLifecycle = new CancellationTokenSource();
Port = Config.Bind(new ConfigDefinition("Server", "Port"), 39125, Port = Config.Bind(new ConfigDefinition("Server", "Port"), 39125,
new ConfigDescription("The port to listen on")); new ConfigDescription("The port to listen on"));
ListenOnAllInterfaces = Config.Bind( ListenOnAllInterfaces = Config.Bind(
@@ -69,29 +64,51 @@ namespace RemoteControl
new ConfigDescription("If set, listen on all interfaces. Otherwise, listen only on localhost")); new ConfigDescription("If set, listen on all interfaces. Otherwise, listen only on localhost"));
var subscriptionModule = new SubscriptionModule("/subscribe"); // No need to restart the game after changing settings :-)
Subscribers = subscriptionModule; Port.SettingChanged += WebserverSettingsChanged;
WebServer = new WebServer(o => ListenOnAllInterfaces.SettingChanged += WebserverSettingsChanged;
o.WithUrlPrefix($"http://{(ListenOnAllInterfaces.Value ? "0.0.0.0" : "localhost")}:{Port.Value}/")
.WithEmbedIOHttpListener() // Subscribers = new SubscriptionModule("/subscribe");
) RestartWebserver();
.WithWebApi("/api/v1", m => m.WithController<ApiController>())
.WithModule(subscriptionModule);
WebServer.Start(_modLifecycle.Token);
_harmony.PatchAll(); _harmony.PatchAll();
foreach (var patchedMethod in _harmony.GetPatchedMethods()) foreach (var patchedMethod in _harmony.GetPatchedMethods())
{ {
Log($"Patched {patchedMethod.FullDescription()}"); Log($"Patched {patchedMethod.FullDescription()}");
} }
Log($"Patched {_harmony.GetPatchedMethods().Count()} methods total");
Log($"Patched {_harmony.GetPatchedMethods().Count()} methods total");
} }
private void WebserverSettingsChanged(object _1, EventArgs _2)
{
RestartWebserver();
}
private void RestartWebserver()
{
if (!_modLifecycle.IsCancellationRequested)
{
_modLifecycle.Cancel();
}
var URL = $"http://{(ListenOnAllInterfaces.Value ? "*" : "localhost")}:{Port.Value}/";
Log($"Starting webserver on {URL}");
_modLifecycle = new CancellationTokenSource();
WebServer = new WebServer(o =>
{
o.WithUrlPrefix(URL)
.WithEmbedIOHttpListener();
})
.WithWebApi("/api/v1", m => m.WithController<ApiController>())
.WithModule(Subscribers);
WebServer.Start(_modLifecycle.Token);
}
public void OnLoaded(List<GameObject> prefabs) public void OnLoaded(List<GameObject> prefabs)
{ {
// Start(); Log("Starting RemoteControl");
StartRC();
#if DEVELOPMENT_BUILD #if DEVELOPMENT_BUILD
Debug.Log($"Loaded {prefabs.Count} prefabs"); Debug.Log($"Loaded {prefabs.Count} prefabs");
#endif #endif
@@ -99,7 +116,8 @@ namespace RemoteControl
public void OnUnloaded(List<GameObject> prefabs) public void OnUnloaded(List<GameObject> prefabs)
{ {
// Stop(); Log("Tearing down RemoteControl");
StopRC();
} }
public void StopRC() public void StopRC()
@@ -110,25 +128,27 @@ namespace RemoteControl
public void Awake() public void Awake()
{ {
Log("Starting RemoteControl"); // Log("Starting RemoteControl");
StartRC(); // StartRC();
} }
private void OnDestroy() private void OnDestroy()
{ {
Log("Tearing down RemoteControl"); // Log("Tearing down RemoteControl");
StopRC(); // StopRC();
} }
} }
internal class ApiController : WebApiController internal class ApiController : WebApiController
{ {
private static readonly Dictionary<string, LogicType> LtByName = EnumCollections.LogicTypes.AsLookupDict(true); private static readonly Dictionary<string, LogicType> LtByName = EnumCollections.LogicTypes.AsLookupDict(true);
private static readonly Dictionary<string, LogicSlotType> StByName = EnumCollections.LogicSlotTypes.AsLookupDict(true);
private static readonly Dictionary<string, LogicSlotType> StByName =
EnumCollections.LogicSlotTypes.AsLookupDict(true);
private static Device GetDeviceJson(GameDevice device) => new(device); private static Device GetDeviceJson(GameDevice device) => new(device);
private static Device? GetDeviceJson(long referenceId) private static Device? GetDeviceJson(long referenceId)
{ {
var dev = Referencable.Find<GameDevice>(referenceId); var dev = Referencable.Find<GameDevice>(referenceId);
@@ -153,7 +173,7 @@ namespace RemoteControl
return dev; return dev;
} }
private static List<GameDevice> GetNetworkDevice(string probeName, string deviceName) private static List<GameDevice> GetNetworkDevice(string probeName, string deviceName)
{ {
@@ -178,6 +198,9 @@ namespace RemoteControl
var result = default(T); var result = default(T);
bool isFound = false; bool isFound = false;
// deviceName = WebUtility.UrlDecode(deviceName);
// networkName = WebUtility.UrlDecode(networkName);
GameDevice.AllDevices.ForEach(anaDev => GameDevice.AllDevices.ForEach(anaDev =>
{ {
if (anaDev is not CableAnalyser analyser) return; if (anaDev is not CableAnalyser analyser) return;
@@ -202,7 +225,7 @@ namespace RemoteControl
return result!; return result!;
} }
[Route(HttpVerbs.Get, "/networks")] [Route(HttpVerbs.Get, "/networks")]
public Task<IList<string>> ListNetworks() public Task<IList<string>> ListNetworks()
{ {
@@ -241,6 +264,7 @@ namespace RemoteControl
devices.Add(device.ReferenceId, GetDeviceJson(device)); devices.Add(device.ReferenceId, GetDeviceJson(device));
} }
} }
return Task.FromResult<IDictionary<long, Device>>(devices); return Task.FromResult<IDictionary<long, Device>>(devices);
} }
} }
@@ -284,7 +308,9 @@ namespace RemoteControl
else else
{ {
throw HttpException.MethodNotAllowed(); throw HttpException.MethodNotAllowed();
}; }
;
} }
[Route(HttpVerbs.Patch, "/networks/{networkId}/device/{deviceId}")] [Route(HttpVerbs.Patch, "/networks/{networkId}/device/{deviceId}")]
@@ -308,17 +334,22 @@ namespace RemoteControl
} }
[Route(HttpVerbs.Get, "/networks/{networkId}/device/{deviceId}/code")] [Route(HttpVerbs.Get, "/networks/{networkId}/device/{deviceId}/code")]
public UniTask<string> GetCode(string networkId, string deviceId) public async Task GetCode(string networkId, string deviceId)
{ {
var dev = FindUniqueDeviceForHttp<ICircuitHolder>(networkId, deviceId); var dev = FindUniqueDeviceForHttp<ICircuitHolder>(networkId, deviceId);
return UniTask.FromResult(dev.GetSourceCode()); var code = dev.GetSourceCode();
HttpContext.Response.ContentType = "text/plain";
var codeBytes = Encoding.UTF8.GetBytes(code);
await HttpContext.Response.OutputStream.WriteAsync(codeBytes, 0, codeBytes.Length);
} }
[Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/code")] [Route(HttpVerbs.Post, "/networks/{networkId}/device/{deviceId}/code")]
public UniTask<string> SetCode(string networkId, string deviceId, string code) public async Task SetCode(string networkId, string deviceId)
{ {
var code = await HttpContext.GetRequestBodyAsByteArrayAsync();
var dev = FindUniqueDeviceForHttp<ICircuitHolder>(networkId, deviceId); var dev = FindUniqueDeviceForHttp<ICircuitHolder>(networkId, deviceId);
return UniTask.FromResult(dev.GetSourceCode()); dev.SetSourceCode(Encoding.UTF8.GetString(code));
} }
} }
} }