Mod metadata, load screens and mod content is all now sourced from ftl files, allowing these items to be translated. Translations are now initialized as part of ModData creation, as currently they are made available too late for the usage we need here. The "modcontent" mod learns a new parameter for "Content.TranslationFile" - this allows a mod to provide the path of a translation file to the mod which it can load. This allows mods such as ra, cnc, d2k, ts to own the translations for their ModContent, yet still make them accessible to the modcontent mod. CheckFluentReference learns to validate all these new fields to ensure translations have been set.
122 lines
3.9 KiB
C#
122 lines
3.9 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright (c) The OpenRA Developers and Contributors
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Threading;
|
|
using OpenRA.Network;
|
|
|
|
namespace OpenRA.Server
|
|
{
|
|
sealed class Program
|
|
{
|
|
static void Main(string[] args)
|
|
{
|
|
try
|
|
{
|
|
Run(args);
|
|
}
|
|
catch
|
|
{
|
|
// Flush logs before rethrowing, i.e. allowing the exception to go unhandled.
|
|
// try-finally won't work - an unhandled exception kills our process without running the finally block!
|
|
Log.Dispose();
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
Log.Dispose();
|
|
}
|
|
}
|
|
|
|
static void Run(string[] args)
|
|
{
|
|
var arguments = new Arguments(args);
|
|
|
|
var engineDirArg = arguments.GetValue("Engine.EngineDir", null);
|
|
if (!string.IsNullOrEmpty(engineDirArg))
|
|
Platform.OverrideEngineDir(engineDirArg);
|
|
|
|
var supportDirArg = arguments.GetValue("Engine.SupportDir", null);
|
|
if (!string.IsNullOrEmpty(supportDirArg))
|
|
Platform.OverrideSupportDir(supportDirArg);
|
|
|
|
Log.AddChannel("debug", "dedicated-debug.log", true);
|
|
Log.AddChannel("perf", "dedicated-perf.log", true);
|
|
Log.AddChannel("server", "dedicated-server.log", true);
|
|
Log.AddChannel("nat", "dedicated-nat.log", true);
|
|
Log.AddChannel("geoip", "dedicated-geoip.log", true);
|
|
|
|
// Special case handling of Game.Mod argument: if it matches a real filesystem path
|
|
// then we use this to override the mod search path, and replace it with the mod id
|
|
var modID = arguments.GetValue("Game.Mod", null);
|
|
var explicitModPaths = Array.Empty<string>();
|
|
if (modID != null && (File.Exists(modID) || Directory.Exists(modID)))
|
|
{
|
|
explicitModPaths = new[] { modID };
|
|
modID = Path.GetFileNameWithoutExtension(modID);
|
|
}
|
|
|
|
if (modID == null)
|
|
throw new InvalidOperationException("Game.Mod argument missing or mod could not be found.");
|
|
|
|
// HACK: The engine code assumes that Game.Settings is set.
|
|
// This isn't nearly as bad as ModData, but is still not very nice.
|
|
Game.InitializeSettings(arguments);
|
|
var settings = Game.Settings.Server;
|
|
|
|
Nat.Initialize();
|
|
|
|
var envModSearchPaths = Environment.GetEnvironmentVariable("MOD_SEARCH_PATHS");
|
|
var modSearchPaths = !string.IsNullOrWhiteSpace(envModSearchPaths) ?
|
|
FieldLoader.GetValue<string[]>("MOD_SEARCH_PATHS", envModSearchPaths) :
|
|
new[] { Path.Combine(Platform.EngineDir, "mods") };
|
|
|
|
var mods = new InstalledMods(modSearchPaths, explicitModPaths);
|
|
|
|
WriteLineWithTimeStamp($"Starting dedicated server for mod: {modID}");
|
|
while (true)
|
|
{
|
|
// HACK: The engine code *still* assumes that Game.ModData is set
|
|
var modData = Game.ModData = new ModData(mods[modID], mods);
|
|
modData.MapCache.LoadPreviewImages = false; // PERF: Server doesn't need previews, save memory by not loading them.
|
|
modData.MapCache.LoadMaps();
|
|
|
|
var endpoints = new List<IPEndPoint> { new(IPAddress.IPv6Any, settings.ListenPort), new(IPAddress.Any, settings.ListenPort) };
|
|
var server = new Server(endpoints, settings, modData, ServerType.Dedicated);
|
|
|
|
GC.Collect();
|
|
while (true)
|
|
{
|
|
Thread.Sleep(1000);
|
|
if (server.State == ServerState.GameStarted && server.Conns.Count < 1)
|
|
{
|
|
WriteLineWithTimeStamp("No one is playing, shutting down...");
|
|
server.Shutdown();
|
|
break;
|
|
}
|
|
}
|
|
|
|
modData.Dispose();
|
|
WriteLineWithTimeStamp("Starting a new server instance...");
|
|
}
|
|
}
|
|
|
|
static void WriteLineWithTimeStamp(string line)
|
|
{
|
|
Console.WriteLine($"[{DateTime.Now.ToString(Game.Settings.Server.TimestampFormat, CultureInfo.CurrentCulture)}] {line}");
|
|
}
|
|
}
|
|
}
|