mirror of
https://github.com/BtbN/ClanServer.git
synced 2026-09-07 16:05:14 -05:00
Add barebone Kestrel based server
This commit is contained in:
21
ClanServer/ClanServer.csproj
Normal file
21
ClanServer/ClanServer.csproj
Normal file
@@ -0,0 +1,21 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
|
||||
<StartupObject>ClanServer.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\eAmuseCore\eAmuseCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
9
ClanServer/ClanServer.csproj.user
Normal file
9
ClanServer/ClanServer.csproj.user
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<DebuggerFlavor>ProjectDebugger</DebuggerFlavor>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ActiveDebugProfile>IIS Express</ActiveDebugProfile>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
33
ClanServer/Controllers/CoreController.cs
Normal file
33
ClanServer/Controllers/CoreController.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using eAmuseCore.KBinXML;
|
||||
using ClanServer.Formatters;
|
||||
using ClanServer.Routing;
|
||||
|
||||
namespace ClanServer.Controllers
|
||||
{
|
||||
[ApiController, Route("core")]
|
||||
public class ValuesController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public IActionResult Get()
|
||||
{
|
||||
return new BadRequestResult();
|
||||
}
|
||||
|
||||
[HttpPost, XrpcCall("services.get")]
|
||||
public EamuseXrpcData GetServices([FromBody] EamuseXrpcData data, [FromQuery] string model)
|
||||
{
|
||||
Console.WriteLine("Model: " + model);
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(data.Data.ToString());
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
176
ClanServer/Formatters/EamuseXrpcFormatters.cs
Normal file
176
ClanServer/Formatters/EamuseXrpcFormatters.cs
Normal file
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.Formatters;
|
||||
|
||||
using eAmuseCore.Compression;
|
||||
using eAmuseCore.Crypto;
|
||||
using eAmuseCore.KBinXML;
|
||||
using System.IO;
|
||||
|
||||
namespace ClanServer.Formatters
|
||||
{
|
||||
public class EamuseXrpcData
|
||||
{
|
||||
public KBinXML Data;
|
||||
public string EamuseInfo;
|
||||
}
|
||||
|
||||
public class EamuseXrpcInputFormatter : InputFormatter
|
||||
{
|
||||
public EamuseXrpcInputFormatter()
|
||||
{
|
||||
SupportedMediaTypes.Add("application/octet-stream");
|
||||
}
|
||||
|
||||
public override bool CanRead(InputFormatterContext context)
|
||||
{
|
||||
var contentType = context.HttpContext.Request.ContentType;
|
||||
|
||||
if (!string.IsNullOrEmpty(contentType) && contentType != "application/octet-stream")
|
||||
return false;
|
||||
|
||||
if (!context.HttpContext.Request.Headers.TryGetValue("User-Agent", out var ua))
|
||||
return false;
|
||||
|
||||
string userAgent = ua.ToString().ToUpper();
|
||||
if (userAgent != "EAMUSE.XRPC/1.0")
|
||||
return false;
|
||||
|
||||
if (!context.HttpContext.Request.Headers.ContainsKey("X-Eamuse-Info"))
|
||||
return false;
|
||||
|
||||
if (!context.HttpContext.Request.Headers.TryGetValue("X-Compress", out ua))
|
||||
return false;
|
||||
|
||||
string compAlgo = ua.ToString().ToLower();
|
||||
|
||||
switch (compAlgo)
|
||||
{
|
||||
case "lz77":
|
||||
case "none":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool CanReadType(Type type)
|
||||
{
|
||||
return type == typeof(EamuseXrpcData);
|
||||
}
|
||||
|
||||
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
|
||||
{
|
||||
var request = context.HttpContext.Request;
|
||||
|
||||
if (!context.HttpContext.Request.Headers.TryGetValue("X-Eamuse-Info", out var header))
|
||||
return await InputFormatterResult.FailureAsync();
|
||||
|
||||
string eAmuseInfo = header.ToString();
|
||||
|
||||
if (!context.HttpContext.Request.Headers.TryGetValue("X-Compress", out header))
|
||||
return await InputFormatterResult.FailureAsync();
|
||||
|
||||
string compAlgo = header.ToString();
|
||||
|
||||
byte[] data;
|
||||
|
||||
using (var ms = new MemoryStream((int)(request.ContentLength ?? 512L)))
|
||||
{
|
||||
await request.Body.CopyToAsync(ms);
|
||||
data = ms.ToArray();
|
||||
}
|
||||
|
||||
return await ProcessInputData(data, eAmuseInfo, compAlgo);
|
||||
}
|
||||
|
||||
private async Task<InputFormatterResult> ProcessInputData(byte[] data, string eAmuseInfo, string compAlgo)
|
||||
{
|
||||
data = await Task.Run(() =>
|
||||
{
|
||||
var decrypted = RC4.ApplyEAmuseInfo(eAmuseInfo, data);
|
||||
switch (compAlgo.ToLower())
|
||||
{
|
||||
case "lz77":
|
||||
return LZ77.Decompress(decrypted).ToArray();
|
||||
case "none":
|
||||
return decrypted.ToArray();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (data == null)
|
||||
return await InputFormatterResult.FailureAsync();
|
||||
|
||||
KBinXML result = await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return new KBinXML(data);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
if (result == null)
|
||||
return await InputFormatterResult.FailureAsync();
|
||||
|
||||
return await InputFormatterResult.SuccessAsync(new EamuseXrpcData()
|
||||
{
|
||||
Data = result,
|
||||
EamuseInfo = eAmuseInfo
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public class EamuseXrpcOutputFormatter : OutputFormatter
|
||||
{
|
||||
public EamuseXrpcOutputFormatter()
|
||||
{
|
||||
SupportedMediaTypes.Add("application/octet-stream");
|
||||
}
|
||||
|
||||
protected override bool CanWriteType(Type type)
|
||||
{
|
||||
return type == typeof(EamuseXrpcData);
|
||||
}
|
||||
|
||||
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
|
||||
{
|
||||
var response = context.HttpContext.Response;
|
||||
|
||||
if (!(context.Object is EamuseXrpcData data))
|
||||
throw new ArgumentNullException("Input EamuseXrpcData is null");
|
||||
|
||||
(byte[] rawData, string compAlgo) = await Task.Run(() =>
|
||||
{
|
||||
byte[] resData = data.Data.Bytes;
|
||||
string algo = "none";
|
||||
|
||||
var compressed = LZ77.Compress(resData).ToArray();
|
||||
if (compressed.Length < resData.Length)
|
||||
{
|
||||
resData = compressed;
|
||||
algo = "lz77";
|
||||
}
|
||||
compressed = null;
|
||||
|
||||
resData = RC4.ApplyEAmuseInfo(data.EamuseInfo, resData).ToArray();
|
||||
|
||||
return (resData, algo);
|
||||
});
|
||||
|
||||
response.Headers.Add("X-Eamuse-Info", data.EamuseInfo);
|
||||
response.Headers.Add("X-Compress", compAlgo);
|
||||
response.ContentType = "application/octet-stream";
|
||||
response.ContentLength = rawData.Length;
|
||||
|
||||
await response.Body.WriteAsync(rawData, 0, rawData.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
34
ClanServer/Program.cs
Normal file
34
ClanServer/Program.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ClanServer
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main()
|
||||
{
|
||||
var builder = new WebHostBuilder()
|
||||
.UseContentRoot(AppContext.BaseDirectory)
|
||||
.UseStartup<Startup>()
|
||||
.UseKestrel()
|
||||
.ConfigureKestrel(ConfigureKestrel);
|
||||
|
||||
var host = builder.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
|
||||
private static void ConfigureKestrel(WebHostBuilderContext context, KestrelServerOptions options)
|
||||
{
|
||||
options.ListenLocalhost(9091);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
ClanServer/Properties/launchSettings.json
Normal file
10
ClanServer/Properties/launchSettings.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"profiles": {
|
||||
"ClanServer": {
|
||||
"commandName": "Project",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
ClanServer/Routing/XrpcCallAttribute.cs
Normal file
53
ClanServer/Routing/XrpcCallAttribute.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc.ActionConstraints;
|
||||
using Microsoft.AspNetCore.Mvc.ApplicationModels;
|
||||
|
||||
namespace ClanServer.Routing
|
||||
{
|
||||
public class XrpcCallAttribute : Attribute
|
||||
{
|
||||
public string XrpcMethod { get; set; }
|
||||
|
||||
public XrpcCallAttribute(string method)
|
||||
{
|
||||
XrpcMethod = method;
|
||||
}
|
||||
}
|
||||
|
||||
public class XrpcCallActionConstraint : IActionConstraint, IActionConstraintMetadata
|
||||
{
|
||||
private readonly string method;
|
||||
|
||||
public XrpcCallActionConstraint(string method)
|
||||
{
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public int Order => 0;
|
||||
|
||||
public bool Accept(ActionConstraintContext context)
|
||||
{
|
||||
var query = context.RouteContext.HttpContext.Request.Query;
|
||||
|
||||
return query.TryGetValue("f", out var val) && val == method;
|
||||
}
|
||||
}
|
||||
|
||||
public class XrpcCallConvention : IActionModelConvention
|
||||
{
|
||||
public void Apply(ActionModel action)
|
||||
{
|
||||
var xrpcCall = action.Attributes.OfType<XrpcCallAttribute>().FirstOrDefault();
|
||||
if (xrpcCall == null)
|
||||
return;
|
||||
|
||||
foreach (var selector in action.Selectors)
|
||||
{
|
||||
selector.ActionConstraints.Add(new XrpcCallActionConstraint(xrpcCall.XrpcMethod));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
ClanServer/Startup.cs
Normal file
53
ClanServer/Startup.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace ClanServer
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services
|
||||
.AddMvc(options =>
|
||||
{
|
||||
options.InputFormatters.Insert(0, new Formatters.EamuseXrpcInputFormatter());
|
||||
options.OutputFormatters.Insert(0, new Formatters.EamuseXrpcOutputFormatter());
|
||||
|
||||
options.Conventions.Add(new Routing.XrpcCallConvention());
|
||||
})
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
|
||||
}
|
||||
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseMvc();
|
||||
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
Console.WriteLine($"[{context.Connection.RemoteIpAddress}] | {context.Request.Path}");
|
||||
await next.Invoke();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
9
ClanServer/appsettings.Development.json
Normal file
9
ClanServer/appsettings.Development.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
ClanServer/appsettings.json
Normal file
8
ClanServer/appsettings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
@@ -5,7 +5,9 @@ VisualStudioVersion = 16.0.28803.156
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "eAmuseCore", "eAmuseCore\eAmuseCore.csproj", "{016891F1-140A-4DDF-B036-BDFAF98D4414}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "eAmuseTest", "eAmuseTest\eAmuseTest.csproj", "{0BB29C0C-2ED9-4D1A-BD03-908BEAAD463C}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "eAmuseTest", "eAmuseTest\eAmuseTest.csproj", "{0BB29C0C-2ED9-4D1A-BD03-908BEAAD463C}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClanServer", "ClanServer\ClanServer.csproj", "{DBFCE27D-2AAE-4765-8435-55EB08745C04}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -21,6 +23,10 @@ Global
|
||||
{0BB29C0C-2ED9-4D1A-BD03-908BEAAD463C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0BB29C0C-2ED9-4D1A-BD03-908BEAAD463C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0BB29C0C-2ED9-4D1A-BD03-908BEAAD463C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DBFCE27D-2AAE-4765-8435-55EB08745C04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DBFCE27D-2AAE-4765-8435-55EB08745C04}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DBFCE27D-2AAE-4765-8435-55EB08745C04}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DBFCE27D-2AAE-4765-8435-55EB08745C04}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user