Files
yeorinhieut cba5243ce4
Some checks failed
build / build-test-publish (push) Has been cancelled
Add Ehwrj clean-room live map
2026-06-02 22:49:24 +09:00

203 lines
7.0 KiB
C#

using System.Globalization;
using System.Net.Http.Headers;
using Ehwrj.Core.Services;
namespace Ehwrj.Tools.Capture;
internal static class Program
{
private static readonly Endpoint[] Endpoints =
{
new("map_info.json", "map_info.json", Required: true),
new("map_obj.json", "map_obj.json", Required: true),
new("map.img", "map.img", Required: true),
new("state", "state.json", Required: false),
new("hudmsg", "hudmsg.json", Required: false),
new("gamechat", "gamechat.json", Required: false)
};
private static async Task<int> Main(string[] args)
{
var options = CaptureOptions.Parse(args);
if (options.ShowHelp)
{
CaptureOptions.PrintHelp();
return 0;
}
if (!string.IsNullOrWhiteSpace(options.ValidateDirectory))
{
return WriteCaptureReport(options.ValidateDirectory);
}
var captureDir = options.OutputDirectory ?? Path.Combine(
"captures",
DateTimeOffset.Now.ToString("yyyyMMdd-HHmmss", CultureInfo.InvariantCulture));
try
{
LoopbackGuard.EnsureLoopbackHttp(options.BaseAddress, "--base-url");
}
catch (ArgumentException ex)
{
Console.Error.WriteLine($"error: {ex.Message}");
return 1;
}
Directory.CreateDirectory(captureDir);
using var client = new HttpClient
{
BaseAddress = options.BaseAddress,
Timeout = TimeSpan.FromMilliseconds(options.TimeoutMs)
};
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("EhwrjCapture", "1.0"));
Console.WriteLine($"Capturing War Thunder local API from {client.BaseAddress}");
Console.WriteLine($"Output: {Path.GetFullPath(captureDir)}");
var failures = 0;
foreach (var endpoint in Endpoints)
{
var ok = await CaptureEndpointAsync(client, endpoint, captureDir).ConfigureAwait(false);
if (!ok && endpoint.Required)
{
failures++;
}
}
File.WriteAllText(
Path.Combine(captureDir, "README.txt"),
$"""
Ehwrj local API capture
Captured at: {DateTimeOffset.Now:O}
Source: {client.BaseAddress}
Files:
- map_info.json
- map_obj.json
- map.img
- state.json, hudmsg.json, gamechat.json when available
Use with:
scripts/run-local-api-stub.sh 8111 {Path.GetFullPath(captureDir)}
""");
WriteCaptureReport(captureDir);
if (failures > 0)
{
Console.Error.WriteLine($"{failures} required endpoint(s) failed.");
return 1;
}
Console.WriteLine("Capture complete.");
return 0;
}
private static int WriteCaptureReport(string captureDir)
{
if (!Directory.Exists(captureDir))
{
Console.Error.WriteLine($"error: capture directory not found: {captureDir}");
return 1;
}
var report = CaptureFixtureAnalyzer.AnalyzeDirectory(captureDir);
var reportPath = Path.Combine(report.Directory, "capture-report.txt");
File.WriteAllText(reportPath, report.ToText());
Console.WriteLine($"report: {reportPath}");
if (report.Warnings.Count > 0)
{
Console.WriteLine($"{report.Warnings.Count} capture warning(s); see capture-report.txt.");
}
return report.HasRequiredFiles ? 0 : 1;
}
private static async Task<bool> CaptureEndpointAsync(HttpClient client, Endpoint endpoint, string captureDir)
{
try
{
using var response = await client.GetAsync(endpoint.Path).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
var level = endpoint.Required ? "error" : "skip";
Console.WriteLine($"{level}: {endpoint.Path} returned {(int)response.StatusCode}");
return false;
}
var bytes = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false);
var outputPath = Path.Combine(captureDir, endpoint.FileName);
await File.WriteAllBytesAsync(outputPath, bytes).ConfigureAwait(false);
Console.WriteLine($"saved: {endpoint.Path} -> {outputPath} ({bytes.Length} bytes)");
return true;
}
catch (Exception ex) when (ex is HttpRequestException or TaskCanceledException)
{
var level = endpoint.Required ? "error" : "skip";
Console.WriteLine($"{level}: {endpoint.Path} failed: {ex.Message}");
return false;
}
}
}
internal sealed record Endpoint(string Path, string FileName, bool Required);
internal sealed record CaptureOptions(Uri BaseAddress, string? OutputDirectory, string? ValidateDirectory, int TimeoutMs, bool ShowHelp)
{
public static CaptureOptions Parse(IReadOnlyList<string> args)
{
var baseAddress = new Uri("http://127.0.0.1:8111/");
string? output = null;
string? validate = null;
var timeoutMs = 1200;
for (var i = 0; i < args.Count; i++)
{
switch (args[i])
{
case "-h":
case "--help":
return new CaptureOptions(baseAddress, output, validate, timeoutMs, ShowHelp: true);
case "--base-url" when i + 1 < args.Count && Uri.TryCreate(args[++i], UriKind.Absolute, out var uri):
baseAddress = uri;
break;
case "--out" when i + 1 < args.Count:
output = args[++i];
break;
case "--validate" when i + 1 < args.Count:
validate = args[++i];
break;
case "--timeout-ms" when i + 1 < args.Count && int.TryParse(args[++i], NumberStyles.None, CultureInfo.InvariantCulture, out var parsed):
timeoutMs = Math.Clamp(parsed, 200, 10_000);
break;
default:
Console.Error.WriteLine($"Ignoring unknown argument: {args[i]}");
break;
}
}
return new CaptureOptions(EnsureTrailingSlash(baseAddress), output, validate, timeoutMs, ShowHelp: false);
}
public static void PrintHelp()
{
Console.WriteLine("""
Usage:
dotnet run --project tools/Ehwrj.Tools.Capture -- [--base-url http://127.0.0.1:8111/] [--out captures/name] [--timeout-ms 1200]
dotnet run --project tools/Ehwrj.Tools.Capture -- --validate captures/name
Captures War Thunder local map, telemetry, and message endpoints into a fixture directory.
Writes capture-report.txt with parser coverage and replay readiness.
""");
}
private static Uri EnsureTrailingSlash(Uri uri)
{
var text = uri.ToString();
return text.EndsWith('/') ? uri : new Uri($"{text}/");
}
}