mirror of
https://github.com/Empostor/Empostor.git
synced 2026-10-20 01:46:07 +00:00
316 lines
14 KiB
C#
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);
|
|
}
|
|
}
|
|
}
|
|
}
|