Files
ppc_amnet/AimeNet/DllMain.cs
T
2024-08-08 23:46:56 +01:00

209 lines
6.5 KiB
C#

using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.Json;
using System.Threading;
using Windows.Win32;
using Windows.Win32.Foundation;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace AimeNet;
public static class DllMain
{
private const int ApiVersion = 1;
private const string WebAddress = "http://amnet.ppc.moe";
private static ILogger _serverLogger;
private static string[] _serverAddresses;
private static string _serverName;
private static (byte[] IdBytes, string OriginalId, long Expires)? _currentCard;
private static readonly object CardLock = new();
static DllMain()
{
PInvoke.AllocConsole();
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_get_api_version")]
public static ushort GetApiVersion() => 0x0100;
[UnmanagedCallersOnly(EntryPoint = "aime_io_init")]
public static int Init()
{
LoadConfiguration();
var builder = WebApplication.CreateSlimBuilder([]);
builder.Logging.ClearProviders();
builder.Logging.AddSimpleConsole(o =>
{
o.SingleLine = true;
o.IncludeScopes = false;
});
builder.Logging.SetMinimumLevel(LogLevel.Information);
builder.Logging.AddFilter("Microsoft.AspNetCore.Http.Result", LogLevel.None);
builder.Logging.AddFilter("Microsoft.AspNetCore.Routing.EndpointMiddleware", LogLevel.Error);
builder.WebHost.UseUrls(_serverAddresses);
builder.Services.AddCors(cors =>
{
cors.AddDefaultPolicy(policy =>
{
policy.AllowAnyMethod();
policy.AllowAnyOrigin();
policy.SetPreflightMaxAge(TimeSpan.FromHours(6));
});
});
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});
var app = builder.Build();
app.UseCors();
app.MapGet("/", () => Results.Ok(new SystemState(ApiVersion, _serverName)));
app.MapPost("/card", async ctx =>
{
CardReadRequest request;
try
{
request = await ctx.Request.ReadFromJsonAsync<CardReadRequest>();
}
catch (JsonException e)
{
await using var writer = new StreamWriter(ctx.Response.Body, Encoding.UTF8, leaveOpen: true);
await writer.WriteLineAsync(e.Message);
ctx.Response.StatusCode = 400;
return;
}
lock (CardLock)
{
// don't allow multiple writes in quick succession
if (_currentCard?.Expires > Environment.TickCount64)
{
ctx.Response.StatusCode = 429;
ctx.Response.Headers.RetryAfter = ((int)TimeSpan.FromTicks(_currentCard.Value.Expires - Environment.TickCount64).TotalSeconds).ToString();
return;
}
if (request.MatrixCode.Length < 20)
{
ctx.Response.StatusCode = 400;
return;
}
// ensure the matrix code is 20-digits long, otherwise pad with zeros
var matrixCode = request.MatrixCode.PadLeft(20, '0');
var bytes = new byte[10];
for (var i = 0; i < 10; i++)
{
var value = matrixCode.Substring(i * 2, 2);
bytes[i] = byte.Parse(value, NumberStyles.HexNumber);
}
_currentCard = (bytes, matrixCode, Environment.TickCount64 + 5000000);
}
ctx.Response.StatusCode = 204;
});
_serverLogger = app.Logger;
CancellationTokenRegistration cancellationRegistration = default;
cancellationRegistration = app.Lifetime.ApplicationStarted.Register(() =>
{
_serverLogger.LogInformation("AimeNet started successfully.");
_serverLogger.LogInformation("Visit {addr} from a mobile device on the same network to get started.", WebAddress);
cancellationRegistration.Dispose();
});
app.RunAsync();
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_nfc_poll")]
public static int NfcPoll(byte unitNo)
{
if (_currentCard.HasValue && _currentCard.Value.Expires < Environment.TickCount64)
{
lock (CardLock)
{
_currentCard = null;
}
}
// there's no polling here (handled by the webserver)
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_nfc_get_aime_id")]
public static int GetAimeId(byte unitNo, nint luid, nint luidSize)
{
lock (CardLock)
{
if (unitNo != 0 || _currentCard == null)
{
return 1;
}
Marshal.Copy(_currentCard.Value.IdBytes, 0, luid, (int)luidSize);
_serverLogger.LogInformation("Aime Card read: {0}", string.Join(" ", Enumerable.Range(0, 5).Select(x => _currentCard.Value.OriginalId.Substring(x * 4, 4))));
_currentCard = null;
}
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_nfc_get_felica_id")]
public static unsafe int GetFelicaId(byte unitNo, ulong* idm)
{
// felica not supported
return 1;
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_led_set_color")]
public static void SetLedColour(byte unitNo, byte r, byte g, byte b)
{
// do nothing
}
private static unsafe void LoadConfiguration()
{
const string configFileName = @".\segatools.ini";
var serverName = stackalloc char[64];
var serverNameStr = new PWSTR(serverName);
var serverAddress = stackalloc char[1024];
var serverAddressStr = new PWSTR(serverAddress);
PInvoke.GetPrivateProfileString("aimeio", "serverName", Environment.MachineName, serverNameStr, 64, configFileName);
PInvoke.GetPrivateProfileString("aimeio", "serverAddress", "http://+:6070", serverAddressStr, 1024, configFileName);
_serverName = serverNameStr.ToString();
_serverAddresses = serverAddressStr.ToString().Split(';');
}
}