203 lines
7.0 KiB
C#
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}/");
|
|
}
|
|
}
|