Running Factorio as a Windows service


This article shows how to run Factorio as a Windows service on the local machine, so that Factorio starts when the computer boots and exits gracefully on shutdown. You can locally connect to this running instance and play the game. It may work with a remote connection, but i did not test that.

Download Project and exe: FactorioService.zip. Dont trust the exe, build it by yourself.

Worker.cs:
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace FactorioService
{
   public class Worker : BackgroundService
   {
      private readonly ILogger Logger;
      public Worker(ILogger Logger) => this.Logger = Logger;
      Process P = null;
      string LogFile1;
      protected override async Task ExecuteAsync(CancellationToken stoppingToken)
      {
         try
         {
            // Logger.LogWarning("Factorio ExecuteAsync Starting");
            string FileName = Process.GetCurrentProcess().MainModule.FileName;
            string ServiceFolder = Path.GetDirectoryName(FileName);
            string BaseLogFile = ServiceFolder + @"\FactorioService";
            LogFile1 = BaseLogFile+"1.log";
            string LogFile2 = BaseLogFile+"2.log";
            string LogFile3 = BaseLogFile+"3.log";
            if (File.Exists(LogFile2)) File.Copy(LogFile2, LogFile3, true);
            if (File.Exists(LogFile1)) File.Copy(LogFile1, LogFile2, true);
            if (File.Exists(LogFile1)) File.Delete(LogFile1);
            LogText("Factorio ExecuteAsync Prolog");
            string GameFolder = new DirectoryInfo(ServiceFolder).Parent.FullName;
            string Arguments = "--start-server " + GameFolder + @"\saves\ServerGame.zip --server-settings " + GameFolder + @"\data\server-settings.json";
            P = new Process();
            P.StartInfo.FileName = GameFolder + @"\bin\x64\factorio.exe";
            P.StartInfo.UseShellExecute = false;
            P.StartInfo.Arguments = Arguments;
            P.StartInfo.RedirectStandardOutput = true;
            P.StartInfo.RedirectStandardInput = true;
            P.Start();
            LogText("Factorio ExecuteAsync LoopReadingStandardOutput");
            new Task(delegate { LogOutput(stoppingToken); }).Start();
            while (!stoppingToken.IsCancellationRequested) await Task.Delay(1000,stoppingToken);
            LogText("Factorio ExecuteAsync Stopped");
         }
         catch { P=null; };
      }
      object Locker = new object();
      void LogText(string Text)
      {
         lock(Locker)
         {
            using (StreamWriter sw = File.AppendText(LogFile1)) sw.WriteLine(Text);
         }
      }
      void LogOutput(CancellationToken stoppingToken)
      {
         try
         {
            while (!stoppingToken.IsCancellationRequested)
            {
               int Delay = 1000;
               string Text = P?.StandardOutput.ReadLineAsync().Result;
               if (Text!=null) if (Text.Length>0)
               {
                  LogText(Text);
                  Delay=100;
               }
               Task.Delay(Delay);
            }
            LogText("LogOutput Close");
         }
         catch { };
      }
      public override async Task StopAsync(CancellationToken cancellationToken)
      {
         // Set STRING: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\WaitToKillServiceTimeout to "20000" = 20 Seconds
         // Logger.LogWarning("Factorio StopAsync Send Ctrl-C");
         // P?.StandardInput.WriteLine("\x3"); // Send Ctrl-C to Factorio - doesnt work this way
         try
         {
            LogText("Factorio StopAsync SendQuit");
            P?.StandardInput.WriteLine("/quit");
            LogText("Factorio StopAsync WaitForExit");
            P?.WaitForExit();
            LogText("Factorio StopAsync Delay");
            await Task.Delay(1000); // Do we need this?
            LogText("Factorio StopAsync Base");
            await base.StopAsync(cancellationToken);
            LogText("Factorio StopAsync Epilog");
         }
         catch { };
      }
   }
}