5.14 시작전

This commit is contained in:
2026-05-14 09:38:45 +09:00
parent 8b5c92194f
commit e76c37ef56
24 changed files with 3638 additions and 717 deletions

View File

@@ -1,7 +1,9 @@
using System;
using System.Globalization;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using KAsyncEngineLib;
@@ -319,6 +321,12 @@ if (args.Length > 0 && string.Equals(args[0], "--validate-live-cuts", StringComp
return;
}
if (args.Length > 0 && string.Equals(args[0], "--audit-party-colors-live", StringComparison.OrdinalIgnoreCase))
{
Environment.ExitCode = await LiveCutValidation.RunPartyColorAuditAsync(args[1..]).ConfigureAwait(false);
return;
}
if (args.Length > 0 && string.Equals(args[0], "--validate-current-api-cuts", StringComparison.OrdinalIgnoreCase))
{
Environment.ExitCode = await CurrentApiCutDiagnostics.RunAsync(args[1..]).ConfigureAwait(false);
@@ -876,6 +884,14 @@ static Task<SaveSceneImageProbeResult> SaveSceneImageAsync(SaveSceneImageOptions
var operationResult = ApplySceneOperation(handler, scene!, operation, options.Connection.Timeout);
if (!string.Equals(operationResult.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal))
{
if (operation.ContinueOnFailure)
{
Console.WriteLine(
$"[SAVE-IMAGE] Optional operation {operationResult.Method} failed for '{operationResult.ObjectName}': " +
$"{operationResult.Result} {operationResult.Detail}");
continue;
}
completion.TrySetResult(
new SaveSceneImageProbeResult(
true,
@@ -1001,31 +1017,38 @@ static Task<SaveSceneImageProbeResult> SaveSceneImageAsync(SaveSceneImageOptions
}
}
var positionKeyUpdates = new List<PositionKeyUpdate>();
if (options.PositionKey is not null)
{
positionKeyUpdates.Add(options.PositionKey);
}
positionKeyUpdates.AddRange(options.PositionKeys);
foreach (var positionKeyUpdate in positionKeyUpdates)
{
Console.WriteLine(
$"[SAVE-IMAGE] Setting position key object={options.PositionKey.ObjectName} index={options.PositionKey.KeyIndex} " +
$"value=({options.PositionKey.X},{options.PositionKey.Y},{options.PositionKey.Z}) vector={options.PositionKey.VectorType}...");
var sceneObject = scene.GetObject(options.PositionKey.ObjectName);
$"[SAVE-IMAGE] Setting position key object={positionKeyUpdate.ObjectName} index={positionKeyUpdate.KeyIndex} " +
$"value=({positionKeyUpdate.X},{positionKeyUpdate.Y},{positionKeyUpdate.Z}) vector={positionKeyUpdate.VectorType}...");
var sceneObject = scene.GetObject(positionKeyUpdate.ObjectName);
if (sceneObject is null)
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{options.PositionKey.ObjectName}' was not found."));
new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{positionKeyUpdate.ObjectName}' was not found."));
return;
}
handler.ResetPositionKeyTask();
sceneObject.SetPositionKey(
options.PositionKey.KeyIndex,
options.PositionKey.X,
options.PositionKey.Y,
options.PositionKey.Z,
options.PositionKey.VectorType);
positionKeyUpdate.KeyIndex,
positionKeyUpdate.X,
positionKeyUpdate.Y,
positionKeyUpdate.Z,
positionKeyUpdate.VectorType);
if (!WaitForTaskWithMessagePump(handler.PositionKeyTask, options.Connection.Timeout))
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetPositionKey timed out for '{options.PositionKey.ObjectName}'." ));
new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetPositionKey timed out for '{positionKeyUpdate.ObjectName}'." ));
return;
}
@@ -1033,7 +1056,7 @@ static Task<SaveSceneImageProbeResult> SaveSceneImageAsync(SaveSceneImageOptions
if (positionKeyResult != eKResult.RESULT_SUCCESS)
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", positionKeyResult.ToString(), options.OutputPath, $"OnSetPositionKey result={positionKeyResult} object={options.PositionKey.ObjectName}"));
new SaveSceneImageProbeResult(true, "SUCCESS", positionKeyResult.ToString(), options.OutputPath, $"OnSetPositionKey result={positionKeyResult} object={positionKeyUpdate.ObjectName}"));
return;
}
}
@@ -1179,53 +1202,161 @@ static Task<SaveSceneImageProbeResult> SaveSceneImageAsync(SaveSceneImageOptions
}
}
var outputDirectory = Path.GetDirectoryName(options.OutputPath);
if (!string.IsNullOrWhiteSpace(outputDirectory))
foreach (var positionUpdate in options.PostPositions)
{
Directory.CreateDirectory(outputDirectory);
}
if (File.Exists(options.OutputPath))
{
File.Delete(options.OutputPath);
}
Console.WriteLine("[SAVE-IMAGE] Calling SaveSceneImage()...");
handler.ResetSaveSceneImageTask();
scene.SaveSceneImage(options.OutputPath, options.Width, options.Height, options.Frame);
if (!WaitForTaskWithMessagePump(handler.SaveSceneImageTask, options.Connection.Timeout))
{
completion.TrySetResult(new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, "OnSaveSceneImage timed out."));
return;
}
var saveResult = handler.SaveSceneImageTask.Result;
if (saveResult != eKResult.RESULT_SUCCESS)
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", saveResult.ToString(), options.OutputPath, $"OnSaveSceneImage result={saveResult}"));
return;
}
var fileWaitDeadline = DateTime.UtcNow + options.Connection.Timeout;
while (DateTime.UtcNow < fileWaitDeadline)
{
if (File.Exists(options.OutputPath))
Console.WriteLine(
$"[SAVE-IMAGE] Setting post-chart position object={positionUpdate.ObjectName} " +
$"value=({positionUpdate.X},{positionUpdate.Y},{positionUpdate.Z}) vector={positionUpdate.VectorType}...");
var sceneObject = scene.GetObject(positionUpdate.ObjectName);
if (sceneObject is null)
{
var info = new FileInfo(options.OutputPath);
if (info.Length > 0)
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", "SUCCESS", options.OutputPath, $"Saved {info.Length} bytes."));
return;
}
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{positionUpdate.ObjectName}' was not found."));
return;
}
Thread.Sleep(50);
handler.ResetPositionTask();
sceneObject.SetPosition(
positionUpdate.X,
positionUpdate.Y,
positionUpdate.Z,
positionUpdate.VectorType);
if (!WaitForTaskWithMessagePump(handler.PositionTask, options.Connection.Timeout))
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetPosition timed out for '{positionUpdate.ObjectName}'." ));
return;
}
var positionResult = handler.PositionTask.Result;
if (positionResult != eKResult.RESULT_SUCCESS)
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", positionResult.ToString(), options.OutputPath, $"OnSetPosition result={positionResult} object={positionUpdate.ObjectName}"));
return;
}
}
completion.TrySetResult(new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, "Image file was not created."));
foreach (var positionKeyUpdate in options.PostPositionKeys)
{
Console.WriteLine(
$"[SAVE-IMAGE] Setting post-chart position key object={positionKeyUpdate.ObjectName} index={positionKeyUpdate.KeyIndex} " +
$"value=({positionKeyUpdate.X},{positionKeyUpdate.Y},{positionKeyUpdate.Z}) vector={positionKeyUpdate.VectorType}...");
var sceneObject = scene.GetObject(positionKeyUpdate.ObjectName);
if (sceneObject is null)
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", options.OutputPath, $"Object '{positionKeyUpdate.ObjectName}' was not found."));
return;
}
handler.ResetPositionKeyTask();
sceneObject.SetPositionKey(
positionKeyUpdate.KeyIndex,
positionKeyUpdate.X,
positionKeyUpdate.Y,
positionKeyUpdate.Z,
positionKeyUpdate.VectorType);
if (!WaitForTaskWithMessagePump(handler.PositionKeyTask, options.Connection.Timeout))
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", options.OutputPath, $"OnSetPositionKey timed out for '{positionKeyUpdate.ObjectName}'." ));
return;
}
var positionKeyResult = handler.PositionKeyTask.Result;
if (positionKeyResult != eKResult.RESULT_SUCCESS)
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", positionKeyResult.ToString(), options.OutputPath, $"OnSetPositionKey result={positionKeyResult} object={positionKeyUpdate.ObjectName}"));
return;
}
}
var captures = new List<(string OutputPath, int Frame)>();
if (options.Frames.Count > 0)
{
var captureDirectory = options.OutputDirectory ?? options.OutputPath;
foreach (var captureFrame in options.Frames)
{
captures.Add((
Path.GetFullPath(Path.Combine(
captureDirectory,
string.Format(CultureInfo.InvariantCulture, options.OutputPattern, captureFrame))),
captureFrame));
}
}
else
{
captures.Add((options.OutputPath, options.Frame));
}
long totalBytes = 0;
foreach (var capture in captures)
{
var outputDirectory = Path.GetDirectoryName(capture.OutputPath);
if (!string.IsNullOrWhiteSpace(outputDirectory))
{
Directory.CreateDirectory(outputDirectory);
}
if (File.Exists(capture.OutputPath))
{
File.Delete(capture.OutputPath);
}
Console.WriteLine($"[SAVE-IMAGE] Calling SaveSceneImage() frame={capture.Frame} output={capture.OutputPath}...");
handler.ResetSaveSceneImageTask();
scene.SaveSceneImage(capture.OutputPath, options.Width, options.Height, capture.Frame);
if (!WaitForTaskWithMessagePump(handler.SaveSceneImageTask, options.Connection.Timeout))
{
completion.TrySetResult(new SaveSceneImageProbeResult(true, "SUCCESS", "TIMEOUT", capture.OutputPath, "OnSaveSceneImage timed out."));
return;
}
var saveResult = handler.SaveSceneImageTask.Result;
if (saveResult != eKResult.RESULT_SUCCESS)
{
completion.TrySetResult(
new SaveSceneImageProbeResult(true, "SUCCESS", saveResult.ToString(), capture.OutputPath, $"OnSaveSceneImage result={saveResult}"));
return;
}
var savedThisFrame = false;
var fileWaitDeadline = DateTime.UtcNow + options.Connection.Timeout;
while (DateTime.UtcNow < fileWaitDeadline)
{
if (File.Exists(capture.OutputPath))
{
var info = new FileInfo(capture.OutputPath);
if (info.Length > 0)
{
totalBytes += info.Length;
savedThisFrame = true;
break;
}
}
Thread.Sleep(50);
}
if (!savedThisFrame)
{
completion.TrySetResult(new SaveSceneImageProbeResult(true, "SUCCESS", "FAILED", capture.OutputPath, "Image file was not created."));
return;
}
}
var resultOutput = options.Frames.Count > 0
? options.OutputDirectory ?? options.OutputPath
: options.OutputPath;
var detail = captures.Count == 1
? $"Saved {totalBytes} bytes."
: $"Saved {captures.Count} frames ({totalBytes} bytes).";
completion.TrySetResult(new SaveSceneImageProbeResult(true, "SUCCESS", "SUCCESS", resultOutput, detail));
}
catch (Exception ex)
{
@@ -3430,6 +3561,9 @@ internal sealed record SaveSceneImageOptions(
int Width,
int Height,
int Frame,
IReadOnlyList<int> Frames,
string? OutputDirectory,
string OutputPattern,
string? SetObjectName,
string? SetObjectValue,
string? VisibleObjectName,
@@ -3442,6 +3576,9 @@ internal sealed record SaveSceneImageOptions(
PositionUpdate? Position,
IReadOnlyList<PositionUpdate> Positions,
PositionKeyUpdate? PositionKey,
IReadOnlyList<PositionKeyUpdate> PositionKeys,
IReadOnlyList<PositionUpdate> PostPositions,
IReadOnlyList<PositionKeyUpdate> PostPositionKeys,
string? ChartObjectName,
string? ChartCsvPath,
IReadOnlyList<ChartCellUpdate> ChartCells,
@@ -3455,6 +3592,9 @@ internal sealed record SaveSceneImageOptions(
string? scenePath = null;
string? sceneAlias = null;
string? outputPath = null;
string? outputDirectory = null;
string outputPattern = "frame_{0:D4}.png";
IReadOnlyList<int> frames = Array.Empty<int>();
string? setObjectName = null;
string? setObjectValue = null;
string? visibleObjectName = null;
@@ -3474,6 +3614,9 @@ internal sealed record SaveSceneImageOptions(
string? positionKeyObjectName = null;
int positionKeyIndex = 1;
string? positionKeyRaw = null;
string? positionKeysRaw = null;
string? postPositionsRaw = null;
string? postPositionKeysRaw = null;
string? chartObjectName = null;
string? chartCsvPath = null;
string? chartCellsRaw = null;
@@ -3497,6 +3640,12 @@ internal sealed record SaveSceneImageOptions(
case "--output" when index + 1 < args.Length:
outputPath = args[++index];
break;
case "--output-dir" when index + 1 < args.Length:
outputDirectory = args[++index];
break;
case "--output-pattern" when index + 1 < args.Length:
outputPattern = args[++index];
break;
case "--set-object" when index + 1 < args.Length:
setObjectName = args[++index];
break;
@@ -3561,6 +3710,15 @@ internal sealed record SaveSceneImageOptions(
case "--position-key" when index + 1 < args.Length:
positionKeyRaw = args[++index];
break;
case "--position-keys" when index + 1 < args.Length:
positionKeysRaw = args[++index];
break;
case "--post-positions" when index + 1 < args.Length:
postPositionsRaw = args[++index];
break;
case "--post-position-keys" when index + 1 < args.Length:
postPositionKeysRaw = args[++index];
break;
case "--chart-object" when index + 1 < args.Length:
chartObjectName = args[++index];
break;
@@ -3591,6 +3749,9 @@ internal sealed record SaveSceneImageOptions(
frame = parsedFrame;
index++;
break;
case "--frames" when index + 1 < args.Length:
frames = ParseFrameSequence(args[++index]);
break;
}
}
@@ -3599,7 +3760,18 @@ internal sealed record SaveSceneImageOptions(
throw new ArgumentException("--scene is required.");
}
if (string.IsNullOrWhiteSpace(outputPath))
if (frames.Count > 0)
{
outputDirectory ??= outputPath;
if (string.IsNullOrWhiteSpace(outputDirectory))
{
throw new ArgumentException("--output-dir is required when --frames is provided.");
}
outputDirectory = Path.GetFullPath(outputDirectory);
outputPath ??= outputDirectory;
}
else if (string.IsNullOrWhiteSpace(outputPath))
{
throw new ArgumentException("--output is required.");
}
@@ -3618,6 +3790,9 @@ internal sealed record SaveSceneImageOptions(
width,
height,
frame,
frames,
outputDirectory,
outputPattern,
setObjectName,
setObjectValue,
visibleObjectName,
@@ -3630,6 +3805,9 @@ internal sealed record SaveSceneImageOptions(
ParsePosition(positionObjectName, positionRaw),
ParsePositions(positionsRaw),
ParsePositionKey(positionKeyObjectName, positionKeyIndex, positionKeyRaw),
ParsePositionKeys(positionKeysRaw),
ParsePositions(postPositionsRaw),
ParsePositionKeys(postPositionKeysRaw),
chartObjectName,
chartCsvPath,
ParseChartCells(chartCellsRaw),
@@ -3638,6 +3816,53 @@ internal sealed record SaveSceneImageOptions(
ParsePathModifications(modifyPathRaw));
}
private static IReadOnlyList<int> ParseFrameSequence(string value)
{
var frames = new List<int>();
foreach (var token in value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var rangeMatch = Regex.Match(token, @"^(?<start>-?\d+)-(?<end>-?\d+)(?::(?<step>\d+))?$", RegexOptions.CultureInvariant);
if (rangeMatch.Success)
{
var start = int.Parse(rangeMatch.Groups["start"].Value, CultureInfo.InvariantCulture);
var end = int.Parse(rangeMatch.Groups["end"].Value, CultureInfo.InvariantCulture);
var step = rangeMatch.Groups["step"].Success
? int.Parse(rangeMatch.Groups["step"].Value, CultureInfo.InvariantCulture)
: 1;
if (step <= 0)
{
throw new ArgumentException("--frames range step must be greater than zero.");
}
if (start <= end)
{
for (var frame = start; frame <= end; frame += step)
{
frames.Add(frame);
}
}
else
{
for (var frame = start; frame >= end; frame -= step)
{
frames.Add(frame);
}
}
continue;
}
if (!int.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out var singleFrame))
{
throw new ArgumentException($"Invalid frame token: {token}");
}
frames.Add(singleFrame);
}
return frames.Distinct().ToArray();
}
private static CloneObjectUpdate? ParseCloneObject(string? sourceObjectName, string? variableName)
{
if (string.IsNullOrWhiteSpace(sourceObjectName) || string.IsNullOrWhiteSpace(variableName))
@@ -3776,6 +4001,38 @@ internal sealed record SaveSceneImageOptions(
return new PositionKeyUpdate(objectName, keyIndex, x, y, z, vectorType);
}
private static IReadOnlyList<PositionKeyUpdate> ParsePositionKeys(string? raw)
{
if (string.IsNullOrWhiteSpace(raw))
{
return Array.Empty<PositionKeyUpdate>();
}
var updates = new List<PositionKeyUpdate>();
foreach (var token in raw.Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries))
{
var nameParts = token.Split('=', 2, StringSplitOptions.TrimEntries);
if (nameParts.Length != 2)
{
throw new ArgumentException($"Invalid position key update: {token}");
}
var objectAndKey = nameParts[0].Split('#', 2, StringSplitOptions.TrimEntries);
if (objectAndKey.Length != 2 || !int.TryParse(objectAndKey[1], out var keyIndex))
{
throw new ArgumentException($"Invalid position key object/index: {nameParts[0]}");
}
var update = ParsePositionKey(objectAndKey[0], keyIndex, nameParts[1]);
if (update is not null)
{
updates.Add(update);
}
}
return updates;
}
private static IReadOnlyList<ChartCellUpdate> ParseChartCells(string? raw)
{
if (string.IsNullOrWhiteSpace(raw))
@@ -4410,6 +4667,8 @@ internal sealed class SceneValidationOperation
public int A { get; set; } = 255;
public bool Visible { get; set; }
public bool ContinueOnFailure { get; set; }
}
internal sealed record SceneOperationValidationResult(string ObjectName, string Method, string Payload, string Result, string Detail);