Add Ehwrj clean-room live map
Some checks failed
build / build-test-publish (push) Has been cancelled
Some checks failed
build / build-test-publish (push) Has been cancelled
This commit is contained in:
202
tools/Ehwrj.Tools.Capture/Program.cs
Normal file
202
tools/Ehwrj.Tools.Capture/Program.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
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}/");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user