remove old c# server impl

This commit is contained in:
ppc
2026-01-09 20:36:39 +00:00
parent 0b44d9bf84
commit ded0109486
7 changed files with 0 additions and 519 deletions
-40
View File
@@ -1,40 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<PublishAot>true</PublishAot>
<OutputType>Library</OutputType>
<TargetFramework>net9.0-windows</TargetFramework>
<OptimizationPreference>Size</OptimizationPreference>
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
</PropertyGroup>
<PropertyGroup>
<Product>AMNet</Product>
<CompanyName>ppc</CompanyName>
<AssemblyName>amnet</AssemblyName>
<AssemblyTitle>AMNet Server</AssemblyTitle>
<Copyright>Copyright (c) 2024-25 ppc</Copyright>
<Description>Server component for the AMNet IC Card switcher</Description>
</PropertyGroup>
<PropertyGroup>
<DebugType>None</DebugType>
<DebugSymbols>false</DebugSymbols>
<MetricsSupport>false</MetricsSupport>
<DebuggerSupport>false</DebuggerSupport>
<EventSourceSupport>false</EventSourceSupport>
<InvariantGlobalization>true</InvariantGlobalization>
<MetadataUpdaterSupport>false</MetadataUpdaterSupport>
<EnableUnsafeUTF7Encoding>false</EnableUnsafeUTF7Encoding>
<HttpActivityPropagationSupport>false</HttpActivityPropagationSupport>
<EnableUnsafeBinaryFormatterSerialization>false</EnableUnsafeBinaryFormatterSerialization>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.183">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
</Project>
-92
View File
@@ -1,92 +0,0 @@
using System;
using System.Globalization;
using System.Linq;
using System.Threading;
namespace AMNet.Server;
/// <summary>
/// Represents a storage location to load/read cards from
/// </summary>
internal class CardPresenter
{
private readonly Lock _cardLock = new();
private StoredCard _currentCard;
public record StoredCard(byte[] AccessCode, byte[] IDm, long ExpiresAt)
{
public bool Expired => Environment.TickCount64 >= ExpiresAt;
public override string ToString() => FormatHexCode(AccessCode);
}
/// <summary>
/// Returns the currently active card without clearing it
/// </summary>
public StoredCard PeekActiveCard()
{
lock (_cardLock)
{
return _currentCard;
}
}
public StoredCard TakeActiveCard(bool? requireIDm = null)
{
lock (_cardLock)
{
// IDm and requireIDm are mutually exclusive
if (_currentCard == null || (requireIDm.HasValue && (_currentCard.IDm != null) ^ requireIDm.Value))
{
return null;
}
var card = _currentCard;
_currentCard = null;
return card;
}
}
/// <summary>
/// Sets the card
/// </summary>
/// <param name="accessCode">The access code of the card to use</param>
/// <param name="idmHex">The FeliCa card IDm, if a physical card was used</param>
/// <param name="validFor">How long the card should be presented to the game for in milliseconds</param>
/// <returns>Whether the card was set successfully</returns>
public StoredCard SetCard(string accessCode, string idmHex, long validFor = 5000)
{
// ensure the matrix code is 20-digits long, otherwise pad with zeros
var matrixCode = accessCode.Replace(" ", "").PadLeft(20, '0');
var bytes = new byte[10];
for (var i = 0; i < bytes.Length; i++)
{
var value = matrixCode.Substring(i * 2, 2);
if (byte.TryParse(value, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out var b))
{
bytes[i] = b;
}
else
{
return null;
}
}
var idmBytes = !string.IsNullOrWhiteSpace(idmHex) && idmHex.Length <= 16 && idmHex.All(char.IsAsciiHexDigit) ? Convert.FromHexString(idmHex.PadLeft(16, '0')) : null;
var card = new StoredCard(bytes, idmBytes, Environment.TickCount64 + validFor);
lock (_cardLock)
{
_currentCard = card;
}
return card;
}
/// <summary>
/// Formats a byte array as a hex string, inserting a space every 2 characters
/// </summary>
public static string FormatHexCode(byte[] code) => string.Join(" ", code.Chunk(2).Select(Convert.ToHexString));
}
-64
View File
@@ -1,64 +0,0 @@
using System;
using System.Reflection;
using Windows.Win32;
using Windows.Win32.Foundation;
namespace AMNet.Server;
internal static class Config
{
internal const string IOSection = "aimeio";
static Config()
{
GameId = ReadKey(IOSection, "gameId", 4);
ServerName = ReadKey(IOSection, "serverName", 26, Environment.MachineName);
PhysicalCardUseIDm = PInvoke.GetPrivateProfileInt(IOSection, "useAimeDBForPhysicalCards", 0, ConfigFilePath) > 0;
EnableAimeTxt = PInvoke.GetPrivateProfileInt(IOSection, "enableKeyboardMode", 1, ConfigFilePath) > 0;
AimeTxtPath = ReadKey("aime", "aimePath", PInvoke.MAX_PATH, @"DEVICE\aime.txt");
}
public static string GameId { get; }
/// <summary>
/// The server name to show on devices. Defaults to the computer name.
/// </summary>
public static string ServerName { get; }
/// <summary>
/// Whether physically scanned cards should have their IDm forwarded to the game instead of the access code.
/// </summary>
public static bool PhysicalCardUseIDm { get; }
/// <summary>
/// Whether holding the enter key should trigger a card scan using the code from the aime.txt file
/// </summary>
public static bool EnableAimeTxt { get; }
/// <summary>
/// Path to the aime.txt file. Defaults to .\DEVICE\aime.txt
/// </summary>
public static string AimeTxtPath { get; }
public static Version ServerVersion { get; } = new(Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyFileVersionAttribute>()?.Version ?? "1.0");
private static string ConfigFilePath { get; } = Environment.GetEnvironmentVariable("SEGATOOLS_CONFIG_PATH") ?? ".\\segatools.ini";
internal static unsafe string ReadKey(string section, string key, uint maxLength, string @default = null)
{
// +1 for null terminator
var buffer = stackalloc char[(int)maxLength + 1];
var bufferStr = new PWSTR(buffer);
fixed (char* pSection = section)
fixed (char* pKey = key)
fixed (char* pDefault = @default ?? string.Empty)
fixed (char* pDefaultPath = ConfigFilePath)
{
PInvoke.GetPrivateProfileString(new PCWSTR(pSection), new PCWSTR(pKey), new PCWSTR(pDefault), bufferStr, maxLength + 1, new PCWSTR(pDefaultPath));
}
return bufferStr.ToString();
}
}
-144
View File
@@ -1,144 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Windows.Win32;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace AMNet.Server;
public static class DllMain
{
private const string WebAddress = "http://card.ppc.moe";
// instance metrics
internal static long LastPollTime = -1;
internal static long ServerStartedAt;
static DllMain()
{
PInvoke.AllocConsole();
App = WebServer.BuildServer(Config.ReadKey(Config.IOSection, "serverAddress", 1024, "http://+:6070").Split(';'));
}
private static WebApplication App { get; }
[UnmanagedCallersOnly(EntryPoint = "aime_io_get_api_version")]
public static ushort GetApiVersion() => 0x0100;
[UnmanagedCallersOnly(EntryPoint = "aime_io_init")]
public static int Init()
{
CancellationTokenRegistration? cancellationRegistration = null;
cancellationRegistration = App.Lifetime.ApplicationStarted.Register(() =>
{
App.Logger.LogInformation("AMNet Server v{version} ({gameId}) started.", Config.ServerVersion.ToString(Config.ServerVersion.Build > 0 ? 3 : 2), Config.GameId ?? "SXXX");
App.Logger.LogInformation("Visit {addr} from a mobile device on the same network to get started.", WebAddress);
// ReSharper disable once AccessToModifiedClosure
cancellationRegistration?.Dispose();
});
Volatile.Write(ref ServerStartedAt, Environment.TickCount64);
App.RunAsync();
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_nfc_poll")]
public static int NfcPoll(byte unitNo)
{
Volatile.Write(ref LastPollTime, Environment.TickCount64);
if (!Config.EnableAimeTxt)
{
return 1;
}
// check keyboard input for the enter key being held down (0x0D)
var enterPressed = (PInvoke.GetAsyncKeyState(0x0D) & 0x8000) != 0;
if (!enterPressed)
{
return 0;
}
try
{
using var aimeTxtReader = new StreamReader(File.OpenRead(Config.AimeTxtPath), Encoding.UTF8);
var fileName = Path.GetFileName(Config.AimeTxtPath);
var cardResult = App.Services.GetRequiredService<CardPresenter>().SetCard(aimeTxtReader.ReadLine(), null);
if (cardResult == null)
{
App.Logger.LogWarning("Failed to read card from {file}: invalid format", fileName);
}
else
{
App.Logger.LogInformation("Card loaded from {fileName}", fileName);
}
}
catch (IOException e)
{
App.Logger.LogWarning("Failed to read '{path}': {message}", Path.GetFullPath(Config.AimeTxtPath), e.Message);
}
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_nfc_get_aime_id")]
public static int GetAimeId(byte unitNo, IntPtr luid, nint luidSize)
{
if (unitNo != 0)
{
return 1;
}
// if physical cards are being directed to
var card = App.Services.GetRequiredService<CardPresenter>().TakeActiveCard(Config.PhysicalCardUseIDm ? false : null);
if (card?.Expired != false)
{
return 1;
}
Marshal.Copy(card.AccessCode, 0, luid, (int)luidSize);
App.Logger.LogInformation("Access Code read: {cardId}", CardPresenter.FormatHexCode(card.AccessCode));
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_nfc_get_felica_id")]
public static unsafe int GetFelicaId(byte unitNo, ulong* idm)
{
if (!Config.PhysicalCardUseIDm || unitNo != 0)
{
return 1;
}
var card = App.Services.GetRequiredService<CardPresenter>().TakeActiveCard(true);
if (card?.Expired != false)
{
return 1;
}
ulong idmValue = 0;
for (var i = 0 ; i < 8 ; i++)
{
idmValue = (idmValue << 8) | card.IDm[i];
}
*idm = idmValue;
App.Logger.LogInformation("FeliCa IDm read: {idm}", CardPresenter.FormatHexCode(card.IDm));
return 0;
}
[UnmanagedCallersOnly(EntryPoint = "aime_io_led_set_color")]
public static void SetLedColour(byte unitNo, byte r, byte g, byte b)
{
// do nothing
}
}
-5
View File
@@ -1,5 +0,0 @@
GetPrivateProfileStringW
GetPrivateProfileInt
GetAsyncKeyState
AllocConsole
MAX_PATH
-158
View File
@@ -1,158 +0,0 @@
using System;
using System.Globalization;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace AMNet.Server;
internal static partial class WebServer
{
private const int ApiVersion = 1;
public record SystemState(
[property: JsonPropertyName("apiVersion")] int ApiVersion,
[property: JsonPropertyName("gameId")] string GameId,
[property: JsonPropertyName("serverName")] string ServerName,
[property: JsonPropertyName("sessionUptime")] long SessionUptime,
[property: JsonPropertyName("timeSinceLastPoll")] long? TimeSinceLastPoll);
public record CardReadRequest(
[property: JsonPropertyName("cardId")] string AccessCode,
[property: JsonPropertyName("physicalCardIDm")] string PhysicalCardIDm);
public record ServerErrorMessage(
[property: JsonPropertyName("message")] string Message);
public static WebApplication BuildServer(params string[] listenAddresses)
{
var builder = WebApplication.CreateSlimBuilder([]);
builder.WebHost.UseUrls(listenAddresses);
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.Services.AddCors(cors =>
{
cors.AddDefaultPolicy(policy =>
{
policy.AllowAnyMethod();
policy.AllowAnyOrigin();
policy.AllowAnyHeader();
policy.SetPreflightMaxAge(TimeSpan.FromHours(6));
});
});
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.TypeInfoResolverChain.Insert(0, SerializerContext.Default);
});
builder.Services.AddSingleton<CardPresenter>();
var app = builder.Build();
app.UseCors();
app.MapGet("/amnet/info", ServerInfo);
app.MapPost("/amnet/signin", ProcessCard);
return app;
}
private static IResult ServerInfo()
{
var startedAt = Volatile.Read(ref DllMain.ServerStartedAt);
var lastPollAt = Volatile.Read(ref DllMain.LastPollTime);
var state = new SystemState(
ApiVersion,
Config.GameId,
Config.ServerName,
Environment.TickCount64 - startedAt,
lastPollAt < 0 ? null : Environment.TickCount64 - lastPollAt);
return Results.Ok(state);
}
private static async Task ProcessCard(HttpContext ctx)
{
CardReadRequest request;
try
{
request = await ctx.Request.ReadFromJsonAsync<CardReadRequest>();
}
catch (JsonException e)
{
await WriteErrorResponse(ctx, StatusCodes.Status400BadRequest, e.Message);
return;
}
if (string.IsNullOrEmpty(request?.AccessCode))
{
await WriteErrorResponse(ctx, StatusCodes.Status400BadRequest, "Access Code not provided.");
return;
}
if (request.AccessCode.Length is 0 or < 20 || !request.AccessCode.Any(char.IsAsciiDigit))
{
await WriteErrorResponse(ctx, StatusCodes.Status422UnprocessableEntity, "Invalid Access Code.");
return;
}
if (request.AccessCode.All(x => x == '0'))
{
await WriteErrorResponse(ctx, StatusCodes.Status403Forbidden, "All-zero access codes are forbidden.");
return;
}
var cardPresenter = ctx.RequestServices.GetRequiredService<CardPresenter>();
var currentCard = cardPresenter.PeekActiveCard();
if (currentCard?.Expired == false)
{
ctx.Response.StatusCode = StatusCodes.Status429TooManyRequests;
ctx.Response.Headers.RetryAfter = Math.Ceiling(TimeSpan.FromMilliseconds(currentCard.ExpiresAt - Environment.TickCount64).TotalSeconds).ToString(CultureInfo.InvariantCulture);
return;
}
if (cardPresenter.SetCard(request.AccessCode, request.PhysicalCardIDm) != null)
{
ctx.Response.StatusCode = StatusCodes.Status202Accepted;
}
else
{
await WriteErrorResponse(ctx, StatusCodes.Status422UnprocessableEntity, "Invalid card id format.");
}
}
private static Task WriteErrorResponse(HttpContext ctx, int code, string message)
{
ctx.Response.StatusCode = code;
return ctx.Response.WriteAsJsonAsync(new ServerErrorMessage(message), SerializerContext.Default.ServerErrorMessage);
}
[JsonSerializable(typeof(SystemState))]
[JsonSerializable(typeof(CardReadRequest))]
[JsonSerializable(typeof(ServerErrorMessage))]
[JsonSourceGenerationOptions(WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never)]
private partial class SerializerContext : JsonSerializerContext;
}
-16
View File
@@ -1,16 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AMNet.Server", "AMNet.Server\AMNet.Server.csproj", "{C9FA2545-F15C-4517-B3AB-55B8FC23FA04}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C9FA2545-F15C-4517-B3AB-55B8FC23FA04}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C9FA2545-F15C-4517-B3AB-55B8FC23FA04}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C9FA2545-F15C-4517-B3AB-55B8FC23FA04}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C9FA2545-F15C-4517-B3AB-55B8FC23FA04}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal