Files
Empostor/src/Empostor.Server/Program.cs
T
FangkuaiYa 2fa1a2e060 Merge branch 'main' into anticheat
# Conflicts:
#	marketplace/Empostor.Plugins.PlayerLog.dll
2026-10-02 10:13:44 +08:00

316 lines
14 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.Loader;
using Empostor.Api.Config;
using Empostor.Api.Events;
using Empostor.Api.Events.Managers;
using Empostor.Api.Games;
using Empostor.Api.Games.Managers;
using Empostor.Api.Languages;
using Empostor.Api.Net.Custom;
using Empostor.Api.Net.Manager;
using Empostor.Api.Plugins;
using Empostor.Api.Utils;
using Empostor.Server.Commands;
using Empostor.Server.Commands.Commands;
using Empostor.Server.Events;
using Empostor.Server.Http;
using Empostor.Server.Net;
using Empostor.Server.Net.Custom;
using Empostor.Server.Net.Factories;
using Empostor.Server.Net.Manager;
using Empostor.Server.Net.Messages;
using Empostor.Server.Plugins;
using Empostor.Server.Recorder;
using Empostor.Server.Service.Shared;
using Empostor.Server.Service.Api;
using Empostor.Api.Service;
using Empostor.Server.Service.Auth;
using Empostor.Server.Service.Firewall;
using Empostor.Server.Utils;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Next.Hazel.Extensions;
using Serilog;
using Serilog.Events;
using Serilog.Settings.Configuration;
using Empostor.Server.Service;
namespace Empostor.Server
{
internal static class Program
{
private static int Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.WriteTo.Console(formatter: new EmpostorConsoleFormatter())
.Enrich.With<ShortSourceEnricher>()
.CreateBootstrapLogger();
try
{
Log.Information("Empostor v{Version} starting", DotnetUtils.Version);
var host = CreateHostBuilder(args).Build();
StartupBanner.Print(host.Services.GetRequiredService<IConfiguration>());
host.Run();
return 0;
}
catch (Exception ex)
{
Log.Fatal(ex, "Empostor terminated unexpectedly");
return 1;
}
finally
{
Log.CloseAndFlush();
}
}
private static IConfiguration CreateConfiguration(string[] args) =>
new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("config.json", true)
.AddJsonFile("config.Development.json", true)
.AddEnvironmentVariables(prefix: "EMPOSTOR_")
.AddCommandLine(args)
.Build();
private static IHostBuilder CreateHostBuilder(string[] args)
{
var configuration = CreateConfiguration(args);
var pluginConfig = configuration.GetSection("PluginLoader").Get<PluginConfig>() ?? new PluginConfig();
var httpConfig = configuration.GetSection(HttpServerConfig.Section).Get<HttpServerConfig>() ?? new HttpServerConfig();
var hostBuilder = Host.CreateDefaultBuilder(args)
.UseContentRoot(Directory.GetCurrentDirectory())
#if DEBUG
.UseEnvironment(Environment.GetEnvironmentVariable("EMPOSTOR_ENV") ?? "Development")
#else
.UseEnvironment("Production")
#endif
.ConfigureAppConfiguration(b => b.AddConfiguration(configuration))
.ConfigureServices((host, services) =>
{
var debug = host.Configuration.GetSection(DebugConfig.Section).Get<DebugConfig>() ?? new DebugConfig();
services.AddSingleton<ServerEnvironment>();
services.AddSingleton<IServerEnvironment>(p => p.GetRequiredService<ServerEnvironment>());
services.AddSingleton<IDateTimeProvider, RealDateTimeProvider>();
services.Configure<DebugConfig>(host.Configuration.GetSection(DebugConfig.Section));
services.Configure<AntiCheatConfig>(host.Configuration.GetSection(AntiCheatConfig.Section));
services.Configure<CompatibilityConfig>(host.Configuration.GetSection(CompatibilityConfig.Section));
services.Configure<ServerConfig>(host.Configuration.GetSection(ServerConfig.Section));
services.Configure<TimeoutConfig>(host.Configuration.GetSection(TimeoutConfig.Section));
services.Configure<HttpServerConfig>(host.Configuration.GetSection(HttpServerConfig.Section));
services.Configure<AdminConfig>(host.Configuration.GetSection(AdminConfig.Section));
services.Configure<AuthApiConfig>(host.Configuration.GetSection(AuthApiConfig.Section));
services.AddSingleton<AuthCacheService>();
services.AddSingleton<PlayerConnectStore>();
services.AddSingleton<PortPoolService>();
services.AddSingleton<IFirewallService>(sp =>
{
var cfg = sp.GetRequiredService<IOptions<ServerConfig>>().Value;
if (cfg.UseUfw)
{
return ActivatorUtilities.CreateInstance<UfwFirewallService>(sp);
}
if (cfg.UseFirewalld)
{
return ActivatorUtilities.CreateInstance<FirewalldFirewallService>(sp);
}
return ActivatorUtilities.CreateInstance<NoopFirewallService>(sp);
});
services.AddHttpClient("innersloth", client =>
{
client.Timeout = TimeSpan.FromSeconds(15);
client.DefaultRequestHeaders.Add("User-Agent",
"UnityPlayer/2022.3.44f1 (UnityWebRequest/1.0, libcurl/7.84.0-DEV)");
client.DefaultRequestHeaders.Add("X-Unity-Version", "2022.3.44f1");
});
services.AddHttpClient("ume", client =>
{
client.Timeout = TimeSpan.FromSeconds(15);
});
services.AddHttpClient("ipgeo", client =>
{
client.Timeout = TimeSpan.FromSeconds(5);
});
services.AddSingleton<IpGeolocationService>();
services.AddSingleton<IModuleTagRegistry>(ModuleTagRegistry.Instance);
services.AddSingleton<ICompatibilityManager, CompatibilityManager>();
services.AddSingleton<ClientIdStore>();
services.AddSingleton<ClientManager>();
services.AddSingleton<IClientManager>(p => p.GetRequiredService<ClientManager>());
if (debug.GameRecorderEnabled)
{
services.AddSingleton<ObjectPoolProvider>(new DefaultObjectPoolProvider());
services.AddSingleton<ObjectPool<PacketSerializationContext>>(sp =>
sp.GetRequiredService<ObjectPoolProvider>()
.Create(new PacketSerializationContextPooledObjectPolicy()));
services.AddSingleton<PacketRecorder>();
services.AddHostedService(sp => sp.GetRequiredService<PacketRecorder>());
services.AddSingleton<IClientFactory, ClientFactory<ClientRecorder>>();
}
else
{
services.AddSingleton<IClientFactory, ClientFactory<Client>>();
}
services.AddSingleton<GameManager>();
services.AddSingleton<IGameManager>(p => p.GetRequiredService<GameManager>());
services.AddSingleton<ListingManager>();
services.AddSingleton<IpRateLimitService>();
services.AddEventPools();
// Command
services.AddSingleton<CommandService>();
services.AddHostedService<CommandBootstrapper>();
// Admin
services.AddSingleton<BanStore>();
services.AddSingleton<ReportStore>();
services.AddSingleton<IEventListener, BanEnforcementListener>();
services.AddSingleton<IEventListener, ReactorHandshakeListener>();
services.AddSingleton<BanCommand>();
services.AddSingleton<HplpStore>();
services.AddSingleton<Http.Admin.AdminExtensionRegistry>();
services.AddSingleton<Http.Admin.AdminThemeRegistry>();
// Language
services.AddSingleton<LanguageService>();
services.AddHazel();
services.AddSingleton<ICustomMessageManager<ICustomRootMessage>, CustomMessageManager<ICustomRootMessage>>();
services.AddSingleton<ICustomMessageManager<ICustomRpc>, CustomMessageManager<ICustomRpc>>();
services.AddSingleton<IMessageWriterProvider, MessageWriterProvider>();
services.AddSingleton<IGameCodeFactory, GameCodeFactory>();
services.AddSingleton<IEventManager, EventManager>();
services.AddSingleton<Matchmaker>();
services.AddSingleton<IDeltaListenerManager>(sp => sp.GetRequiredService<Matchmaker>());
services.AddHostedService<MatchmakerService>();
})
.UseSerilog((context, logCfg) =>
{
#if DEBUG
var logLevel = LogEventLevel.Debug;
#else
var logLevel = LogEventLevel.Information;
#endif
if (args.Contains("--verbose")) logLevel = LogEventLevel.Verbose;
else if (args.Contains("--errors-only"))
{
logLevel = LogEventLevel.Error;
}
static Assembly? TryLoad(AssemblyLoadContext ctx, AssemblyName name)
{
foreach (var p in new[] { AppDomain.CurrentDomain.BaseDirectory, Directory.GetCurrentDirectory() })
{
try { return ctx.LoadFromAssemblyPath(Path.Combine(p, name.Name + ".dll")); }
catch (FileNotFoundException) { }
}
return null;
}
AssemblyLoadContext.Default.Resolving += TryLoad;
logCfg
.MinimumLevel.Is(logLevel)
#if DEBUG
.MinimumLevel.Override("Microsoft", LogEventLevel.Debug)
#else
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
#endif
.MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.With<ShortSourceEnricher>()
.WriteTo.Console(formatter: new EmpostorConsoleFormatter())
.WriteTo.File(
"Log/empostor-.log",
rollingInterval: RollingInterval.Day,
retainedFileCountLimit: 31,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] [{Module}] {Message}{NewLine}{Exception}")
.ReadFrom.Configuration(context.Configuration,
new ConfigurationReaderOptions(ConfigurationAssemblySource.AlwaysScanDllFiles));
AssemblyLoadContext.Default.Resolving -= TryLoad;
})
.UseConsoleLifetime()
.UsePluginLoader(pluginConfig);
if (httpConfig.Enabled)
{
hostBuilder.ConfigureWebHostDefaults(builder =>
{
builder.ConfigureServices(s =>
{
s.AddControllers()
.ConfigureApplicationPartManager(EnsureServerApplicationPart);
});
builder.Configure(app =>
{
// Anticheat IP request rate limit — runs before every
// HTTP (TCP) endpoint, including the plugin ones.
app.UseMiddleware<IpRateLimitMiddleware>();
foreach (var p in app.ApplicationServices.GetRequiredService<PluginLoaderService>().Plugins)
if (p.Startup is IPluginHttpStartup s)
{
s.ConfigureWebApplication(app);
}
app.UseRouting();
app.UseEndpoints(e => e.MapControllers());
});
builder.ConfigureKestrel(opt =>
opt.Listen(IPAddress.Parse(httpConfig.ListenIp), httpConfig.ListenPort));
});
}
return hostBuilder;
}
private static void EnsureServerApplicationPart(ApplicationPartManager manager)
{
EnsureAssemblyPart(manager, typeof(Program).Assembly, insertFirst: true);
foreach (var assembly in PluginLoader.LoadedPluginAssemblies)
{
EnsureAssemblyPart(manager, assembly, insertFirst: false);
}
}
private static void EnsureAssemblyPart(ApplicationPartManager manager, Assembly assembly, bool insertFirst)
{
if (manager.ApplicationParts.OfType<AssemblyPart>().Any(p => p.Assembly == assembly))
{
return;
}
var part = new AssemblyPart(assembly);
if (insertFirst)
{
manager.ApplicationParts.Insert(0, part);
}
else
{
manager.ApplicationParts.Add(part);
}
}
}
}