using System; using System.Net.Sockets; using System.Runtime.InteropServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; using KAsyncEngineLib; if (args.Length > 0 && string.Equals(args[0], "--reflect-api", StringComparison.OrdinalIgnoreCase)) { PrintApiSurface(); return; } if (args.Length > 0 && string.Equals(args[0], "--test-counter", StringComparison.OrdinalIgnoreCase)) { var counterOptions = CounterProbeOptions.Parse(args[1..]); Console.WriteLine( $"Karisma counter probe starting. target={counterOptions.Connection.Host}:{counterOptions.Connection.Port} " + $"scene={counterOptions.ScenePath} object={counterOptions.ObjectName} keyIndex={counterOptions.KeyIndex} value={counterOptions.Number:0.###}"); var counterResult = await ProbeCounterAsync(counterOptions).ConfigureAwait(false); Console.WriteLine(); Console.WriteLine("Summary"); Console.WriteLine($"- SDK Connect(): {(counterResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}"); Console.WriteLine($"- Scene Load: {counterResult.SceneLoadOutcome}"); Console.WriteLine($"- Counter Update: {counterResult.CounterOutcome}"); Console.WriteLine($"- Detail: {counterResult.Detail}"); Environment.ExitCode = counterResult.ConnectRequestAccepted && counterResult.SceneLoadOutcome == "SUCCESS" && counterResult.CounterOutcome == "SUCCESS" ? 0 : 1; return; } if (args.Length > 0 && string.Equals(args[0], "--catalog-scenes", StringComparison.OrdinalIgnoreCase)) { var catalogOptions = SceneCatalogOptions.Parse(args[1..]); Console.WriteLine( $"Karisma scene catalog starting. target={catalogOptions.Connection.Host}:{catalogOptions.Connection.Port} " + $"root={catalogOptions.RootPath} output={catalogOptions.OutputPath}"); var catalogResult = await CatalogScenesAsync(catalogOptions).ConfigureAwait(false); Console.WriteLine(); Console.WriteLine("Summary"); Console.WriteLine($"- SDK Connect(): {(catalogResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}"); Console.WriteLine($"- Scenes Found: {catalogResult.SceneCount}"); Console.WriteLine($"- Scenes Cataloged: {catalogResult.CatalogedCount}"); Console.WriteLine($"- Failures: {catalogResult.Failures.Count}"); Console.WriteLine($"- Output: {catalogResult.OutputPath}"); if (catalogResult.Failures.Count > 0) { Console.WriteLine("- Failure Details:"); foreach (var failure in catalogResult.Failures.Take(20)) { Console.WriteLine($" - {failure.ScenePath}: {failure.Reason}"); } } Environment.ExitCode = catalogResult.ConnectRequestAccepted && catalogResult.Failures.Count == 0 ? 0 : 1; return; } if (args.Length > 0 && string.Equals(args[0], "--validate-scene-values", StringComparison.OrdinalIgnoreCase)) { var validationOptions = SceneValidationOptions.Parse(args[1..]); Console.WriteLine( $"Karisma scene validation starting. target={validationOptions.Connection.Host}:{validationOptions.Connection.Port} " + $"scene={validationOptions.ScenePath} ops={validationOptions.OperationsPath} output={validationOptions.OutputPath}"); var validationResult = await ValidateSceneOperationsAsync(validationOptions).ConfigureAwait(false); Console.WriteLine(); Console.WriteLine("Summary"); Console.WriteLine($"- SDK Connect(): {(validationResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}"); Console.WriteLine($"- Scene Load: {validationResult.SceneLoadOutcome}"); Console.WriteLine($"- Success Count: {validationResult.SuccessCount}"); Console.WriteLine($"- Failure Count: {validationResult.FailureCount}"); Console.WriteLine($"- Output: {validationResult.OutputPath}"); if (!string.IsNullOrWhiteSpace(validationResult.Detail)) { Console.WriteLine($"- Detail: {validationResult.Detail}"); } Environment.ExitCode = validationResult.ConnectRequestAccepted && validationResult.SceneLoadOutcome == "SUCCESS" && validationResult.FailureCount == 0 ? 0 : 1; return; } if (args.Length > 0 && string.Equals(args[0], "--inspect-tscn-folder", StringComparison.OrdinalIgnoreCase)) { var inspectionOptions = FolderInspectionOptions.Parse(args[1..]); Console.WriteLine( $"Karisma tscn inspection starting. target={inspectionOptions.Connection.Host}:{inspectionOptions.Connection.Port} " + $"root={inspectionOptions.RootPath} output={inspectionOptions.OutputPath}"); var inspectionResult = await InspectTscnFolderAsync(inspectionOptions).ConfigureAwait(false); Console.WriteLine(); Console.WriteLine("Summary"); Console.WriteLine($"- SDK Connect(): {(inspectionResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}"); Console.WriteLine($"- Scenes Found: {inspectionResult.SceneCount}"); Console.WriteLine($"- Scenes Processed: {inspectionResult.ProcessedSceneCount}"); Console.WriteLine($"- Variables Found: {inspectionResult.DiscoveredVariableCount}"); Console.WriteLine($"- Failures: {inspectionResult.FailureCount}"); Console.WriteLine($"- Output: {inspectionResult.OutputPath}"); if (!string.IsNullOrWhiteSpace(inspectionResult.Detail)) { Console.WriteLine($"- Detail: {inspectionResult.Detail}"); } Environment.ExitCode = inspectionResult.ConnectRequestAccepted && inspectionResult.FailureCount == 0 ? 0 : 1; return; } var options = ProbeOptions.Parse(args); Console.WriteLine($"Karisma TCP probe starting. target={options.Host}:{options.Port} timeout={options.Timeout.TotalSeconds:0}s"); var rawTcpOk = await ProbeRawTcpAsync(options).ConfigureAwait(false); var sdkResult = await ProbeSdkAsync(options).ConfigureAwait(false); Console.WriteLine(); Console.WriteLine("Summary"); Console.WriteLine($"- Raw TCP: {(rawTcpOk ? "OK" : "FAILED")}"); Console.WriteLine($"- SDK Connect(): {(sdkResult.ConnectRequestAccepted ? "ACCEPTED" : "REJECTED")}"); Console.WriteLine($"- SDK OnConnect: {sdkResult.ConnectOutcome}"); Console.WriteLine($"- SDK Detail: {sdkResult.Detail}"); Environment.ExitCode = rawTcpOk && sdkResult.ConnectRequestAccepted && sdkResult.ConnectOutcome == "SUCCESS" ? 0 : 1; static void PrintApiSurface() { PrintTypeMethods(typeof(IKAObject)); PrintTypeMethods(typeof(KAObject)); PrintTypeMethods(typeof(IKAScene)); PrintTypeMethods(typeof(KAScene)); PrintTypeMethods(typeof(IKAEngine)); PrintTypeMethods(typeof(KAEngine)); PrintTypeMethods(typeof(IKACounter)); PrintTypeMethods(typeof(KACounter)); PrintTypeMethods(typeof(IKAObjectInfos)); PrintTypeMethods(typeof(KAObjectInfos)); PrintStructFields(typeof(sKObjectInfo)); PrintEnumValues(typeof(eKObjectType)); } static void PrintTypeMethods(Type type) { Console.WriteLine(type.FullName); foreach (var method in type.GetMethods()) { if (!method.Name.Contains("Counter", StringComparison.OrdinalIgnoreCase) && !string.Equals(method.Name, "SetValue", StringComparison.OrdinalIgnoreCase) && !method.Name.Contains("GetObject", StringComparison.OrdinalIgnoreCase) && !method.Name.Contains("QueryObjectInfos", StringComparison.OrdinalIgnoreCase) && !method.Name.Contains("GetCount", StringComparison.OrdinalIgnoreCase)) { continue; } var parameters = string.Join( ", ", method.GetParameters().Select(parameter => $"{parameter.ParameterType.Name} {parameter.Name}")); Console.WriteLine($"- {method.ReturnType.Name} {method.Name}({parameters})"); } Console.WriteLine(); } static void PrintStructFields(Type type) { Console.WriteLine(type.FullName); foreach (var field in type.GetFields()) { Console.WriteLine($"- {field.FieldType.Name} {field.Name}"); } Console.WriteLine(); } static void PrintEnumValues(Type type) { Console.WriteLine(type.FullName); foreach (var name in Enum.GetNames(type)) { Console.WriteLine($"- {name}"); } Console.WriteLine(); } static async Task ProbeRawTcpAsync(ProbeOptions options) { try { using var client = new TcpClient(); using var cts = new CancellationTokenSource(options.Timeout); await client.ConnectAsync(options.Host, options.Port, cts.Token).ConfigureAwait(false); Console.WriteLine($"[RAW] Connected local={client.Client.LocalEndPoint} remote={client.Client.RemoteEndPoint}"); return true; } catch (Exception ex) { Console.WriteLine($"[RAW] Failed: {ex.Message}"); return false; } } static Task ProbeCounterAsync(CounterProbeOptions options) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var thread = new Thread(() => { try { var handler = new ProbeEventHandler(); var engine = (IKAEngine)new KAEngineClass(); Console.WriteLine("[COUNTER] Calling Connect()..."); var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler); Console.WriteLine($"[COUNTER] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}"); if (connectRequested == 0) { completion.TrySetResult(new CounterProbeResult(false, "NOT_RUN", "NOT_RUN", "Connect() returned 0.")); return; } if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout)) { completion.TrySetResult(new CounterProbeResult(true, "NOT_RUN", "TIMEOUT", "OnConnect timed out.")); return; } if (handler.ConnectTask.Result != 0) { completion.TrySetResult( new CounterProbeResult(true, "NOT_RUN", "FAILED", $"OnConnect errorCode={handler.ConnectTask.Result}")); return; } Console.WriteLine("[COUNTER] Loading scene..."); var scene = engine.LoadScene(options.ScenePath, options.SceneAlias); if (scene is null) { completion.TrySetResult(new CounterProbeResult(true, "FAILED", "NOT_RUN", "LoadScene returned null.")); return; } if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout)) { completion.TrySetResult(new CounterProbeResult(true, "TIMEOUT", "NOT_RUN", "OnLoadScene timed out.")); return; } var loadSceneResult = handler.LoadSceneTask.Result; if (loadSceneResult != eKResult.RESULT_SUCCESS) { completion.TrySetResult( new CounterProbeResult(true, loadSceneResult.ToString(), "NOT_RUN", $"OnLoadScene result={loadSceneResult}")); return; } Console.WriteLine("[COUNTER] Resolving object..."); var sceneObject = scene.GetObject(options.ObjectName); if (sceneObject is not IKACounter counter) { completion.TrySetResult( new CounterProbeResult(true, "SUCCESS", "FAILED", $"Object '{options.ObjectName}' is not an IKACounter.")); return; } Console.WriteLine("[COUNTER] Calling SetCounterNumberKey()..."); counter.SetCounterNumberKey(options.KeyIndex, options.Number); if (!WaitForTaskWithMessagePump(handler.CounterNumberKeyTask, options.Connection.Timeout)) { completion.TrySetResult( new CounterProbeResult(true, "SUCCESS", "TIMEOUT", "OnSetCounterNumberKey timed out.")); return; } var counterResult = handler.CounterNumberKeyTask.Result; completion.TrySetResult( new CounterProbeResult( true, "SUCCESS", counterResult == eKResult.RESULT_SUCCESS ? "SUCCESS" : counterResult.ToString(), $"OnSetCounterNumberKey result={counterResult}")); try { engine.Disconnect(); handler.CloseTask.Wait(TimeSpan.FromSeconds(2)); } catch { } } catch (Exception ex) { completion.TrySetResult(new CounterProbeResult(false, "EXCEPTION", "EXCEPTION", ex.ToString())); } }) { IsBackground = true, Name = "KarismaCounterProbe" }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return completion.Task; } static Task CatalogScenesAsync(SceneCatalogOptions options) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var thread = new Thread(() => { var entries = new List(); var failures = new List(); try { var handler = new ProbeEventHandler(); var engine = (IKAEngine)new KAEngineClass(); Console.WriteLine("[CATALOG] Calling Connect()..."); var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler); Console.WriteLine($"[CATALOG] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}"); if (connectRequested == 0) { completion.TrySetResult(new SceneCatalogProbeResult(false, 0, 0, options.OutputPath, failures)); return; } if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout)) { failures.Add(new SceneCatalogFailure("(connect)", "OnConnect timed out.")); completion.TrySetResult(new SceneCatalogProbeResult(true, 0, 0, options.OutputPath, failures)); return; } if (handler.ConnectTask.Result != 0) { failures.Add(new SceneCatalogFailure("(connect)", $"OnConnect errorCode={handler.ConnectTask.Result}")); completion.TrySetResult(new SceneCatalogProbeResult(true, 0, 0, options.OutputPath, failures)); return; } var scenePaths = Directory .EnumerateFiles(options.RootPath, "*.tscn", SearchOption.AllDirectories) .Where(path => string.IsNullOrWhiteSpace(options.SceneFilter) || path.Contains(options.SceneFilter, StringComparison.OrdinalIgnoreCase)) .OrderBy(path => path, StringComparer.OrdinalIgnoreCase) .Take(options.MaxScenes ?? int.MaxValue) .ToArray(); for (var index = 0; index < scenePaths.Length; index++) { var scenePath = scenePaths[index]; var sceneAlias = $"catalog_{index:D4}"; Console.WriteLine($"[CATALOG] ({index + 1}/{scenePaths.Length}) {scenePath}"); try { handler.ResetLoadSceneTask(); handler.ResetObjectInfosTask(); var scene = engine.LoadScene(scenePath, sceneAlias); if (scene is null) { failures.Add(new SceneCatalogFailure(scenePath, "LoadScene returned null.")); continue; } handler.ConfigureQueryObjectInfosOnLoad(scene); if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout)) { failures.Add(new SceneCatalogFailure(scenePath, "OnLoadScene timed out.")); continue; } if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS) { failures.Add(new SceneCatalogFailure(scenePath, $"OnLoadScene result={handler.LoadSceneTask.Result}")); TryUnloadScene(handler, scene, options.Connection.Timeout); continue; } if (!WaitForTaskWithMessagePump(handler.ObjectInfosTask, options.Connection.Timeout)) { failures.Add(new SceneCatalogFailure(scenePath, "OnQueryObjectInfos timed out.")); TryUnloadScene(handler, scene, options.Connection.Timeout); continue; } var objectInfosResult = handler.ObjectInfosTask.Result; if (objectInfosResult.Result != eKResult.RESULT_SUCCESS) { var reason = string.IsNullOrWhiteSpace(objectInfosResult.Detail) ? $"OnQueryObjectInfos result={objectInfosResult.Result}" : $"OnQueryObjectInfos result={objectInfosResult.Result} detail={objectInfosResult.Detail}"; failures.Add(new SceneCatalogFailure(scenePath, reason)); TryUnloadScene(handler, scene, options.Connection.Timeout); continue; } entries.Add(new SceneCatalogEntry( scenePath, Path.GetRelativePath(options.RootPath, scenePath), objectInfosResult.Objects)); TryUnloadScene(handler, scene, options.Connection.Timeout); } catch (Exception ex) { failures.Add(new SceneCatalogFailure(scenePath, ex.Message)); } } WriteCatalogMarkdown(options, entries, failures); completion.TrySetResult(new SceneCatalogProbeResult(true, scenePaths.Length, entries.Count, options.OutputPath, failures)); try { engine.Disconnect(); handler.CloseTask.Wait(TimeSpan.FromSeconds(2)); } catch { } } catch (Exception ex) { failures.Add(new SceneCatalogFailure("(exception)", ex.ToString())); completion.TrySetResult(new SceneCatalogProbeResult(false, entries.Count + failures.Count, entries.Count, options.OutputPath, failures)); } }) { IsBackground = true, Name = "KarismaSceneCatalog" }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return completion.Task; } static Task ValidateSceneOperationsAsync(SceneValidationOptions options) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var operations = LoadValidationOperations(options); var thread = new Thread(() => { try { var handler = new ProbeEventHandler(); var engine = (IKAEngine)new KAEngineClass(); var results = new List(); Console.WriteLine("[VALIDATE] Calling Connect()..."); var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler); Console.WriteLine($"[VALIDATE] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}"); if (connectRequested == 0) { completion.TrySetResult(new SceneValidationProbeResult(false, "NOT_RUN", 0, operations.Count, options.OutputPath, "Connect() returned 0.")); return; } if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout)) { completion.TrySetResult(new SceneValidationProbeResult(true, "NOT_RUN", 0, operations.Count, options.OutputPath, "OnConnect timed out.")); return; } if (handler.ConnectTask.Result != 0) { completion.TrySetResult(new SceneValidationProbeResult(true, "NOT_RUN", 0, operations.Count, options.OutputPath, $"OnConnect errorCode={handler.ConnectTask.Result}")); return; } handler.ResetLoadSceneTask(); var scene = engine.LoadScene(options.ScenePath, options.SceneAlias); if (scene is null) { completion.TrySetResult(new SceneValidationProbeResult(true, "FAILED", 0, operations.Count, options.OutputPath, "LoadScene returned null.")); return; } if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout)) { completion.TrySetResult(new SceneValidationProbeResult(true, "TIMEOUT", 0, operations.Count, options.OutputPath, "OnLoadScene timed out.")); return; } var loadSceneResult = handler.LoadSceneTask.Result; if (loadSceneResult != eKResult.RESULT_SUCCESS) { completion.TrySetResult( new SceneValidationProbeResult(true, loadSceneResult.ToString(), 0, operations.Count, options.OutputPath, $"OnLoadScene result={loadSceneResult}")); return; } foreach (var operation in operations) { Console.WriteLine($"[VALIDATE] {operation.Method} object={operation.ObjectName}"); var sceneObject = scene.GetObject(operation.ObjectName); if (sceneObject is null) { results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), "OBJECT_NOT_FOUND", "scene.GetObject returned null.")); continue; } if (string.Equals(operation.Method, "SetCounterNumberKey", StringComparison.OrdinalIgnoreCase)) { if (sceneObject is not IKACounter counter) { results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), "NOT_A_COUNTER", "Object does not implement IKACounter.")); continue; } handler.ResetCounterNumberKeyTask(); counter.SetCounterNumberKey(operation.KeyIndex, operation.Number); if (!WaitForTaskWithMessagePump(handler.CounterNumberKeyTask, options.Connection.Timeout)) { results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), "TIMEOUT", "OnSetCounterNumberKey timed out.")); continue; } var callbackResult = handler.CounterNumberKeyTask.Result; results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), callbackResult.ToString(), string.Empty)); continue; } if (string.Equals(operation.Method, "SetVisible", StringComparison.OrdinalIgnoreCase)) { handler.ResetVisibleTask(); sceneObject.SetVisible(operation.Visible ? 1 : 0); if (!WaitForTaskWithMessagePump(handler.VisibleTask, options.Connection.Timeout)) { results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), "TIMEOUT", "OnSetVisible timed out.")); continue; } var callbackResult = handler.VisibleTask.Result; results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), callbackResult.ToString(), string.Empty)); continue; } if (string.Equals(operation.Method, "SetStyleColor", StringComparison.OrdinalIgnoreCase)) { if (sceneObject is not IKAStyle style) { results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), "NOT_A_STYLE_OBJECT", "Object does not implement IKAStyle.")); continue; } handler.ResetStyleColorTask(); style.SetStyleColor( ParseStyleType(operation.StyleType), operation.Order, operation.R, operation.G, operation.B, operation.A); if (!WaitForTaskWithMessagePump(handler.StyleColorTask, options.Connection.Timeout)) { results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), "TIMEOUT", "OnSetStyleColor timed out.")); continue; } var callbackResult = handler.StyleColorTask.Result; results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), callbackResult.ToString(), string.Empty)); continue; } handler.ResetSetValueTask(); sceneObject.SetValue(operation.Value ?? string.Empty); if (!WaitForTaskWithMessagePump(handler.SetValueTask, options.Connection.Timeout)) { results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), "TIMEOUT", "OnSetValue timed out.")); continue; } var setValueResult = handler.SetValueTask.Result; results.Add(new SceneOperationValidationResult( operation.ObjectName, operation.Method, DescribeOperationPayload(operation), setValueResult.ToString(), string.Empty)); } WriteSceneValidationMarkdown(options, results); try { scene.UnloadScene(); } catch { } try { engine.Disconnect(); handler.CloseTask.Wait(TimeSpan.FromSeconds(2)); } catch { } completion.TrySetResult(new SceneValidationProbeResult( true, "SUCCESS", results.Count(result => string.Equals(result.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal)), results.Count(result => !string.Equals(result.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal)), options.OutputPath, string.Empty)); } catch (Exception ex) { completion.TrySetResult(new SceneValidationProbeResult(false, "EXCEPTION", 0, operations.Count, options.OutputPath, ex.ToString())); } }) { IsBackground = true, Name = "KarismaSceneValidator" }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return completion.Task; } static Task InspectTscnFolderAsync(FolderInspectionOptions options) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var thread = new Thread(() => { try { var handler = new ProbeEventHandler(); var engine = (IKAEngine)new KAEngineClass(); var sceneResults = new List(); var failures = new List(); var imagePngPath = FindFirstFile(options.RootPath, "*.png"); var imageVrvPath = FindFirstFile(options.RootPath, "*.vrv"); Console.WriteLine($"[INSPECT] Sample PNG={(imagePngPath ?? "(none)")}"); Console.WriteLine($"[INSPECT] Sample VRV={(imageVrvPath ?? "(none)")}"); Console.WriteLine("[INSPECT] Calling Connect()..."); var connectRequested = engine.Connect(options.Connection.Host, options.Connection.Port, handler); Console.WriteLine($"[INSPECT] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}"); if (connectRequested == 0) { completion.TrySetResult(new FolderInspectionProbeResult(false, 0, 0, 0, 1, options.OutputPath, "Connect() returned 0.")); return; } if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Connection.Timeout)) { completion.TrySetResult(new FolderInspectionProbeResult(true, 0, 0, 0, 1, options.OutputPath, "OnConnect timed out.")); return; } if (handler.ConnectTask.Result != 0) { completion.TrySetResult(new FolderInspectionProbeResult(true, 0, 0, 0, 1, options.OutputPath, $"OnConnect errorCode={handler.ConnectTask.Result}")); return; } var scenePaths = Directory .EnumerateFiles(options.RootPath, "*.tscn", SearchOption.AllDirectories) .Where(path => string.IsNullOrWhiteSpace(options.SceneFilter) || path.Contains(options.SceneFilter, StringComparison.OrdinalIgnoreCase)) .OrderBy(path => path, StringComparer.OrdinalIgnoreCase) .Take(options.MaxScenes ?? int.MaxValue) .ToArray(); for (var sceneIndex = 0; sceneIndex < scenePaths.Length; sceneIndex++) { var scenePath = scenePaths[sceneIndex]; var sceneAlias = $"inspect_{sceneIndex:D4}"; Console.WriteLine($"[INSPECT] ({sceneIndex + 1}/{scenePaths.Length}) {scenePath}"); try { handler.ResetLoadSceneTask(); var scene = engine.LoadScene(scenePath, sceneAlias); if (scene is null) { failures.Add(new SceneCatalogFailure(scenePath, "LoadScene returned null.")); continue; } if (!WaitForTaskWithMessagePump(handler.LoadSceneTask, options.Connection.Timeout)) { failures.Add(new SceneCatalogFailure(scenePath, "OnLoadScene timed out.")); TryUnloadScene(handler, scene, options.Connection.Timeout); continue; } if (handler.LoadSceneTask.Result != eKResult.RESULT_SUCCESS) { failures.Add(new SceneCatalogFailure(scenePath, $"OnLoadScene result={handler.LoadSceneTask.Result}")); TryUnloadScene(handler, scene, options.Connection.Timeout); continue; } var candidates = ExtractCandidateNames(scenePath); var discoveries = new List(); foreach (var candidate in candidates) { KAObject? sceneObject; try { sceneObject = scene.GetObject(candidate); } catch { continue; } if (sceneObject is null) { continue; } var textAttempt = TrySetValueOnObject(handler, sceneObject, "__TCP_VALIDATE__", options.Connection.Timeout); if (textAttempt == eKResult.RESULT_SUCCESS) { discoveries.Add(new SceneVariableDiscovery(candidate, "SetValue", "__TCP_VALIDATE__", textAttempt.ToString())); continue; } if (textAttempt == eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT) { continue; } if (!string.IsNullOrWhiteSpace(imagePngPath)) { var pngAttempt = TrySetValueOnObject(handler, sceneObject, imagePngPath, options.Connection.Timeout); if (pngAttempt == eKResult.RESULT_SUCCESS) { discoveries.Add(new SceneVariableDiscovery(candidate, "SetValue", imagePngPath, pngAttempt.ToString())); continue; } } if (!string.IsNullOrWhiteSpace(imageVrvPath)) { var vrvAttempt = TrySetValueOnObject(handler, sceneObject, imageVrvPath, options.Connection.Timeout); if (vrvAttempt == eKResult.RESULT_SUCCESS) { discoveries.Add(new SceneVariableDiscovery(candidate, "SetValue", imageVrvPath, vrvAttempt.ToString())); continue; } } if (sceneObject is IKACounter counter) { handler.ResetCounterNumberKeyTask(); counter.SetCounterNumberKey(1, 1d); if (!WaitForTaskWithMessagePump(handler.CounterNumberKeyTask, options.Connection.Timeout)) { continue; } var counterResult = handler.CounterNumberKeyTask.Result; if (counterResult == eKResult.RESULT_SUCCESS) { discoveries.Add(new SceneVariableDiscovery( candidate, "SetCounterNumberKey", "keyIndex=1, number=1", counterResult.ToString())); } } } sceneResults.Add(new SceneInspectionResult( scenePath, Path.GetRelativePath(options.RootPath, scenePath), candidates.Count, discoveries.OrderBy(discovery => discovery.VariableName, StringComparer.Ordinal).ToArray())); TryUnloadScene(handler, scene, options.Connection.Timeout); } catch (Exception ex) { failures.Add(new SceneCatalogFailure(scenePath, ex.Message)); } } WriteFolderInspectionMarkdown(options, sceneResults, failures); try { engine.Disconnect(); handler.CloseTask.Wait(TimeSpan.FromSeconds(2)); } catch { } completion.TrySetResult(new FolderInspectionProbeResult( true, scenePaths.Length, sceneResults.Count, sceneResults.Sum(result => result.Discoveries.Count), failures.Count, options.OutputPath, string.Empty)); } catch (Exception ex) { completion.TrySetResult(new FolderInspectionProbeResult(false, 0, 0, 0, 1, options.OutputPath, ex.ToString())); } }) { IsBackground = true, Name = "KarismaTscnInspector" }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return completion.Task; } static eKResult TrySetValueOnObject(ProbeEventHandler handler, KAObject sceneObject, string payload, TimeSpan timeout) { handler.ResetSetValueTask(); sceneObject.SetValue(payload); return WaitForTaskWithMessagePump(handler.SetValueTask, timeout) ? handler.SetValueTask.Result : eKResult.RESULT_FAILURE; } static string? FindFirstFile(string rootPath, string pattern) { try { return Directory.EnumerateFiles(rootPath, pattern, SearchOption.AllDirectories) .OrderBy(path => path, StringComparer.OrdinalIgnoreCase) .FirstOrDefault(); } catch { return null; } } static IReadOnlyList ExtractCandidateNames(string scenePath) { var data = File.ReadAllBytes(scenePath); var candidates = new HashSet(StringComparer.Ordinal); for (var index = 0; index + 8 <= data.Length; index++) { var length = BitConverter.ToInt32(data, index); if (length < 2 || length > 48) { continue; } var byteLength = length * 2; var start = index + 4; var end = start + byteLength; if (end > data.Length) { continue; } string value; try { value = System.Text.Encoding.Unicode.GetString(data, start, byteLength); } catch { continue; } if (IsCandidateName(value)) { candidates.Add(value); } } return candidates.OrderBy(value => value, StringComparer.Ordinal).ToArray(); } static bool IsCandidateName(string value) { if (string.IsNullOrWhiteSpace(value)) { return false; } var trimmed = value.Trim(); if (trimmed.Length < 2 || trimmed.Length > 48) { return false; } if (trimmed.Contains('\\') || trimmed.Contains('/') || trimmed.Contains(':') || trimmed.Contains('.') || trimmed.Contains(' ')) { return false; } if (trimmed.All(char.IsDigit)) { return false; } if (trimmed.StartsWith("OBJECT_TYPE_", StringComparison.Ordinal) || trimmed.StartsWith("RESULT_", StringComparison.Ordinal) || trimmed.StartsWith("EFFECT_", StringComparison.Ordinal)) { return false; } if (trimmed is "Tornado3" or "Out" or "Default" or "Image" or "Text" or "Counter" or "Rect" or "Paragraph" or "Char" or "Arial" or "Path" or "Group" or "Change") { return false; } return trimmed.All(ch => ch is >= '0' and <= '9' || ch is >= 'A' and <= 'Z' || ch is >= 'a' and <= 'z' || ch is >= '\uAC00' and <= '\uD7A3' || ch is '_' or '-'); } static void WriteFolderInspectionMarkdown( FolderInspectionOptions options, IReadOnlyList sceneResults, IReadOnlyList failures) { Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!); using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false)); writer.WriteLine("# TSCN Variable Discovery"); writer.WriteLine(); writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); writer.WriteLine($"- Root: `{options.RootPath}`"); writer.WriteLine($"- Scene Count: {sceneResults.Count}"); writer.WriteLine($"- Discovered Variable Count: {sceneResults.Sum(result => result.Discoveries.Count)}"); writer.WriteLine($"- Failure Count: {failures.Count}"); writer.WriteLine(); writer.WriteLine("## Method"); writer.WriteLine(); writer.WriteLine("- Candidate names are extracted from each `.tscn` as UTF-16LE strings."); writer.WriteLine("- Each candidate is verified through Karisma TCP callbacks."); writer.WriteLine("- `SetValue(__TCP_VALIDATE__)`, valid `.png`, valid `.vrv`, and `SetCounterNumberKey(1, 1)` are tried as applicable."); writer.WriteLine("- Only callbacks that returned `RESULT_SUCCESS` are listed as discovered variables."); writer.WriteLine(); if (failures.Count > 0) { writer.WriteLine("## Failures"); writer.WriteLine(); foreach (var failure in failures) { writer.WriteLine($"- `{failure.ScenePath}`: {EscapeInline(failure.Reason)}"); } writer.WriteLine(); } writer.WriteLine("## Scenes"); writer.WriteLine(); foreach (var sceneResult in sceneResults) { writer.WriteLine($"### `{sceneResult.RelativePath}`"); writer.WriteLine(); writer.WriteLine($"- Candidate Count: {sceneResult.CandidateCount}"); writer.WriteLine($"- Discovered Variables: {sceneResult.Discoveries.Count}"); writer.WriteLine(); if (sceneResult.Discoveries.Count == 0) { writer.WriteLine("- No variables discovered with the current TCP validation heuristics."); writer.WriteLine(); continue; } writer.WriteLine("| Variable | Method | Payload | Result |"); writer.WriteLine("| --- | --- | --- | --- |"); foreach (var discovery in sceneResult.Discoveries) { writer.WriteLine( $"| {EscapeCell(discovery.VariableName)} | {EscapeCell(discovery.Method)} | {EscapeCell(discovery.Payload)} | {EscapeCell(discovery.Result)} |"); } writer.WriteLine(); } } static List LoadValidationOperations(SceneValidationOptions options) { var json = File.ReadAllText(options.OperationsPath); var operations = JsonSerializer.Deserialize>(json, new JsonSerializerOptions { PropertyNameCaseInsensitive = true }) ?? new List(); foreach (var operation in operations) { if (!string.IsNullOrWhiteSpace(operation.Value)) { operation.Value = operation.Value.Replace("${SCENE_DIR}", Path.GetDirectoryName(options.ScenePath) ?? string.Empty, StringComparison.Ordinal); } } return operations; } static string DescribeOperationPayload(SceneValidationOperation operation) { if (string.Equals(operation.Method, "SetVisible", StringComparison.OrdinalIgnoreCase)) { return $"visible={operation.Visible}"; } if (string.Equals(operation.Method, "SetStyleColor", StringComparison.OrdinalIgnoreCase)) { return $"styleType={operation.StyleType}, order={operation.Order}, rgba=({operation.R}, {operation.G}, {operation.B}, {operation.A})"; } return string.Equals(operation.Method, "SetCounterNumberKey", StringComparison.OrdinalIgnoreCase) ? $"keyIndex={operation.KeyIndex}, number={operation.Number:0.###}" : operation.Value ?? string.Empty; } static eKStyleType ParseStyleType(string? value) { return (value ?? string.Empty).Trim().ToLowerInvariant() switch { "face" => eKStyleType.STYLE_TYPE_FACE, "edge" => eKStyleType.STYLE_TYPE_EDGE, "shadow" => eKStyleType.STYLE_TYPE_SHADOW, "underline" => eKStyleType.STYLE_TYPE_UNDERLINE, "frame" => eKStyleType.STYLE_TYPE_FRAME, _ => throw new ArgumentException($"Unsupported style type: {value}") }; } static void WriteSceneValidationMarkdown(SceneValidationOptions options, IReadOnlyList results) { Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!); using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false)); writer.WriteLine("# Scene Variable Validation"); writer.WriteLine(); writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); writer.WriteLine($"- Scene: `{options.ScenePath}`"); writer.WriteLine($"- Operations: `{options.OperationsPath}`"); writer.WriteLine($"- Success Count: {results.Count(result => string.Equals(result.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal))}"); writer.WriteLine($"- Failure Count: {results.Count(result => !string.Equals(result.Result, eKResult.RESULT_SUCCESS.ToString(), StringComparison.Ordinal))}"); writer.WriteLine(); writer.WriteLine("| Object | Method | Payload | Result | Detail |"); writer.WriteLine("| --- | --- | --- | --- | --- |"); foreach (var result in results) { writer.WriteLine( $"| {EscapeCell(result.ObjectName)} | {EscapeCell(result.Method)} | {EscapeCell(result.Payload)} | {EscapeCell(result.Result)} | {EscapeCell(result.Detail)} |"); } } static void TryUnloadScene(ProbeEventHandler handler, IKAScene scene, TimeSpan timeout) { try { handler.ResetUnloadSceneTask(); scene.UnloadScene(); WaitForTaskWithMessagePump(handler.UnloadSceneTask, timeout); } catch { } } static void WriteCatalogMarkdown( SceneCatalogOptions options, IReadOnlyList entries, IReadOnlyList failures) { Directory.CreateDirectory(Path.GetDirectoryName(options.OutputPath)!); using var writer = new StreamWriter(options.OutputPath, false, new System.Text.UTF8Encoding(false)); writer.WriteLine("# Scene Object Catalog"); writer.WriteLine(); writer.WriteLine($"- Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); writer.WriteLine($"- Root: `{options.RootPath}`"); writer.WriteLine($"- Scene Count: {entries.Count}"); writer.WriteLine($"- Failure Count: {failures.Count}"); writer.WriteLine(); if (failures.Count > 0) { writer.WriteLine("## Failures"); writer.WriteLine(); foreach (var failure in failures) { writer.WriteLine($"- `{failure.ScenePath}`: {EscapeInline(failure.Reason)}"); } writer.WriteLine(); } writer.WriteLine("## Scenes"); writer.WriteLine(); foreach (var entry in entries) { writer.WriteLine($"### `{entry.RelativePath}`"); writer.WriteLine(); writer.WriteLine($"- Object Count: {entry.Objects.Count}"); writer.WriteLine(); writer.WriteLine("| Name | Type | Value | Visible |"); writer.WriteLine("| --- | --- | --- | --- |"); foreach (var obj in entry.Objects) { writer.WriteLine( $"| {EscapeCell(obj.Name)} | {obj.ObjectType} | {EscapeCell(obj.Value)} | {(obj.Visible ? "true" : "false")} |"); } writer.WriteLine(); } } static string EscapeCell(string? value) { if (string.IsNullOrEmpty(value)) { return string.Empty; } return value.Replace("\\", "\\\\", StringComparison.Ordinal) .Replace("|", "\\|", StringComparison.Ordinal) .Replace("\r", " ", StringComparison.Ordinal) .Replace("\n", "
", StringComparison.Ordinal); } static string EscapeInline(string? value) => EscapeCell(value); static Task ProbeSdkAsync(ProbeOptions options) { var completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var thread = new Thread(() => { try { var handler = new ProbeEventHandler(); var engine = (IKAEngine)new KAEngineClass(); Console.WriteLine("[SDK] Calling Connect()..."); var connectRequested = engine.Connect(options.Host, options.Port, handler); Console.WriteLine($"[SDK] Connect() returned {(connectRequested != 0 ? "TRUE" : "FALSE")} raw={connectRequested}"); if (connectRequested == 0) { completion.TrySetResult(new SdkProbeResult(false, "FAILED", "Connect() returned 0.")); return; } if (!WaitForTaskWithMessagePump(handler.ConnectTask, options.Timeout)) { completion.TrySetResult(new SdkProbeResult(true, "TIMEOUT", $"OnConnect was not received within {options.Timeout.TotalSeconds:0} seconds.")); return; } var errorCode = handler.ConnectTask.Result; var outcome = errorCode == 0 ? "SUCCESS" : "FAILED"; completion.TrySetResult(new SdkProbeResult(true, outcome, $"OnConnect errorCode={errorCode}")); try { engine.Disconnect(); handler.CloseTask.Wait(TimeSpan.FromSeconds(2)); } catch { } } catch (Exception ex) { completion.TrySetResult(new SdkProbeResult(false, "EXCEPTION", ex.ToString())); } }) { IsBackground = true, Name = "KarismaTcpProbe" }; thread.SetApartmentState(ApartmentState.STA); thread.Start(); return completion.Task; } static bool WaitForTaskWithMessagePump(Task task, TimeSpan timeout) { User32.PeekMessage(out _, IntPtr.Zero, 0, 0, 0); var deadline = DateTime.UtcNow + timeout; while (!task.IsCompleted) { while (User32.PeekMessage(out var message, IntPtr.Zero, 0, 0, 1)) { User32.TranslateMessage(ref message); User32.DispatchMessage(ref message); } if (DateTime.UtcNow >= deadline) { return false; } Thread.Sleep(10); } return true; } internal sealed record ProbeOptions(string Host, int Port, TimeSpan Timeout) { public static ProbeOptions Parse(string[] args) { var host = "127.0.0.1"; var port = 30001; var timeout = TimeSpan.FromSeconds(5); for (var index = 0; index < args.Length; index++) { switch (args[index]) { case "--host" when index + 1 < args.Length: host = args[++index]; break; case "--port" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedPort): port = parsedPort; index++; break; case "--timeout" when index + 1 < args.Length && double.TryParse(args[index + 1], out var parsedTimeoutSeconds): timeout = TimeSpan.FromSeconds(parsedTimeoutSeconds); index++; break; } } return new ProbeOptions(host, port, timeout); } } internal sealed record CounterProbeOptions( ProbeOptions Connection, string ScenePath, string SceneAlias, string ObjectName, int KeyIndex, double Number) { public static CounterProbeOptions Parse(string[] args) { var connection = ProbeOptions.Parse(args); string? scenePath = null; string? sceneAlias = null; string? objectName = null; var keyIndex = 1; var number = 0d; for (var index = 0; index < args.Length; index++) { switch (args[index]) { case "--scene" when index + 1 < args.Length: scenePath = args[++index]; break; case "--alias" when index + 1 < args.Length: sceneAlias = args[++index]; break; case "--object" when index + 1 < args.Length: objectName = args[++index]; break; case "--key-index" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedKeyIndex): keyIndex = parsedKeyIndex; index++; break; case "--number" when index + 1 < args.Length && double.TryParse(args[index + 1], out var parsedNumber): number = parsedNumber; index++; break; } } if (string.IsNullOrWhiteSpace(scenePath)) { throw new ArgumentException("--scene is required."); } if (string.IsNullOrWhiteSpace(objectName)) { throw new ArgumentException("--object is required."); } sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath); return new CounterProbeOptions(connection, scenePath, sceneAlias, objectName, keyIndex, number); } } internal sealed record SceneCatalogOptions( ProbeOptions Connection, string RootPath, string OutputPath, string? SceneFilter, int? MaxScenes) { public static SceneCatalogOptions Parse(string[] args) { var connection = ProbeOptions.Parse(args); string? rootPath = null; string? outputPath = null; string? sceneFilter = null; int? maxScenes = null; for (var index = 0; index < args.Length; index++) { switch (args[index]) { case "--root" when index + 1 < args.Length: rootPath = args[++index]; break; case "--output" when index + 1 < args.Length: outputPath = args[++index]; break; case "--filter" when index + 1 < args.Length: sceneFilter = args[++index]; break; case "--max-scenes" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedMaxScenes): maxScenes = parsedMaxScenes; index++; break; } } rootPath ??= Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "Tornado3 Data", "T3_Cut", "T3_Cut"); if (!Directory.Exists(rootPath)) { throw new DirectoryNotFoundException($"Catalog root path does not exist: {rootPath}"); } outputPath ??= Path.Combine(Environment.CurrentDirectory, "SCENE_OBJECT_CATALOG.md"); outputPath = Path.GetFullPath(outputPath); return new SceneCatalogOptions(connection, Path.GetFullPath(rootPath), outputPath, sceneFilter, maxScenes); } } internal sealed record SceneValidationOptions( ProbeOptions Connection, string ScenePath, string SceneAlias, string OperationsPath, string OutputPath) { public static SceneValidationOptions Parse(string[] args) { var connection = ProbeOptions.Parse(args); string? scenePath = null; string? sceneAlias = null; string? operationsPath = null; string? outputPath = null; for (var index = 0; index < args.Length; index++) { switch (args[index]) { case "--scene" when index + 1 < args.Length: scenePath = args[++index]; break; case "--alias" when index + 1 < args.Length: sceneAlias = args[++index]; break; case "--operations" when index + 1 < args.Length: operationsPath = args[++index]; break; case "--output" when index + 1 < args.Length: outputPath = args[++index]; break; } } if (string.IsNullOrWhiteSpace(scenePath)) { throw new ArgumentException("--scene is required."); } if (string.IsNullOrWhiteSpace(operationsPath)) { throw new ArgumentException("--operations is required."); } scenePath = Path.GetFullPath(scenePath); operationsPath = Path.GetFullPath(operationsPath); sceneAlias ??= Path.GetFileNameWithoutExtension(scenePath); outputPath ??= Path.Combine(Environment.CurrentDirectory, "SCENE_VARIABLE_VALIDATION.md"); outputPath = Path.GetFullPath(outputPath); return new SceneValidationOptions(connection, scenePath, sceneAlias, operationsPath, outputPath); } } internal sealed record FolderInspectionOptions(ProbeOptions Connection, string RootPath, string OutputPath, string? SceneFilter, int? MaxScenes) { public static FolderInspectionOptions Parse(string[] args) { var connection = ProbeOptions.Parse(args); string? rootPath = null; string? outputPath = null; string? sceneFilter = null; int? maxScenes = null; for (var index = 0; index < args.Length; index++) { switch (args[index]) { case "--root" when index + 1 < args.Length: rootPath = args[++index]; break; case "--output" when index + 1 < args.Length: outputPath = args[++index]; break; case "--filter" when index + 1 < args.Length: sceneFilter = args[++index]; break; case "--max-scenes" when index + 1 < args.Length && int.TryParse(args[index + 1], out var parsedMaxScenes): maxScenes = parsedMaxScenes; index++; break; } } if (string.IsNullOrWhiteSpace(rootPath)) { throw new ArgumentException("--root is required."); } rootPath = Path.GetFullPath(rootPath); outputPath ??= Path.Combine(Environment.CurrentDirectory, "TSCN_VARIABLE_DISCOVERY.md"); outputPath = Path.GetFullPath(outputPath); return new FolderInspectionOptions(connection, rootPath, outputPath, sceneFilter, maxScenes); } } internal sealed record SdkProbeResult(bool ConnectRequestAccepted, string ConnectOutcome, string Detail); internal sealed record CounterProbeResult(bool ConnectRequestAccepted, string SceneLoadOutcome, string CounterOutcome, string Detail); internal sealed record SceneCatalogProbeResult( bool ConnectRequestAccepted, int SceneCount, int CatalogedCount, string OutputPath, IReadOnlyList Failures); internal sealed record SceneCatalogFailure(string ScenePath, string Reason); internal sealed record SceneCatalogEntry(string ScenePath, string RelativePath, IReadOnlyList Objects); internal sealed record SceneObjectCatalogItem(string Name, eKObjectType ObjectType, string Value, bool Visible); internal sealed record ObjectInfosProbeResult(eKResult Result, string SceneName, IReadOnlyList Objects, string Detail); internal sealed record SceneValidationProbeResult( bool ConnectRequestAccepted, string SceneLoadOutcome, int SuccessCount, int FailureCount, string OutputPath, string Detail); internal sealed record FolderInspectionProbeResult( bool ConnectRequestAccepted, int SceneCount, int ProcessedSceneCount, int DiscoveredVariableCount, int FailureCount, string OutputPath, string Detail); internal sealed class SceneValidationOperation { public string ObjectName { get; set; } = string.Empty; public string Method { get; set; } = "SetValue"; public string? Value { get; set; } public int KeyIndex { get; set; } public double Number { get; set; } public string? StyleType { get; set; } public int Order { get; set; } public int R { get; set; } public int G { get; set; } public int B { get; set; } public int A { get; set; } = 255; public bool Visible { get; set; } } internal sealed record SceneOperationValidationResult(string ObjectName, string Method, string Payload, string Result, string Detail); internal sealed record SceneInspectionResult(string ScenePath, string RelativePath, int CandidateCount, IReadOnlyList Discoveries); internal sealed record SceneVariableDiscovery(string VariableName, string Method, string Payload, string Result); internal sealed class ProbeEventHandler : KAEventHandler { private readonly TaskCompletionSource _connectTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly TaskCompletionSource _closeTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource _loadSceneTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource _unloadSceneTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource _counterNumberKeyTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource _styleColorTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource _visibleTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource _setValueTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private TaskCompletionSource _objectInfosTask = new(TaskCreationOptions.RunContinuationsAsynchronously); private IKAScene? _sceneToQueryOnLoad; public Task ConnectTask => _connectTask.Task; public Task CloseTask => _closeTask.Task; public Task LoadSceneTask => _loadSceneTask.Task; public Task UnloadSceneTask => _unloadSceneTask.Task; public Task CounterNumberKeyTask => _counterNumberKeyTask.Task; public Task StyleColorTask => _styleColorTask.Task; public Task VisibleTask => _visibleTask.Task; public Task SetValueTask => _setValueTask.Task; public Task ObjectInfosTask => _objectInfosTask.Task; public void ResetLoadSceneTask() => _loadSceneTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public void ResetUnloadSceneTask() => _unloadSceneTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public void ResetCounterNumberKeyTask() => _counterNumberKeyTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public void ResetStyleColorTask() => _styleColorTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public void ResetVisibleTask() => _visibleTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public void ResetSetValueTask() => _setValueTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public void ResetObjectInfosTask() => _objectInfosTask = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); public void ConfigureQueryObjectInfosOnLoad(IKAScene scene) => _sceneToQueryOnLoad = scene; public void OnConnect(int ErrorCode) { Console.WriteLine($"[SDK] OnConnect errorCode={ErrorCode}"); _connectTask.TrySetResult(ErrorCode); } public void OnClose(int ErrorCode) { Console.WriteLine($"[SDK] OnClose errorCode={ErrorCode}"); _closeTask.TrySetResult(ErrorCode); } public void OnLoadScene(eKResult Result, string SceneName) { Console.WriteLine($"[SDK] OnLoadScene result={Result} scene={SceneName}"); _loadSceneTask.TrySetResult(Result); if (Result == eKResult.RESULT_SUCCESS && _sceneToQueryOnLoad is not null) { try { _sceneToQueryOnLoad.QueryObjectInfos(); } catch (Exception ex) { _objectInfosTask.TrySetResult(new ObjectInfosProbeResult( eKResult.RESULT_FAILURE, SceneName, Array.Empty(), ex.Message)); } finally { _sceneToQueryOnLoad = null; } } } public void OnLoadSceneForce(eKResult Result, string SceneName) { } public void OnLogMessage(string LogMessage) { } public void OnMessageNo(uint MessageNo) { } public void OnBeginTransaction(eKResult Result) { } public void OnEndTransaction(eKResult Result) { } public void OnHeartBeat(eKResult Result) { } public void OnUnloadAll(eKResult Result) { } public void OnSetTrialPlayoutMode(eKResult Result) { } public void OnCheckVersion(eKResult Result, string ServerVersion, string SDKVersion) { } public void OnSetAudioOutput(eKResult Result) { } public void OnScenePrepare(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnScenePrepareEx(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnPlay(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnPlayOut(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnStop(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnStopAll(eKResult Result) { } public void OnPause(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnResume(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnQueryIsOnAir(eKResult Result, int OutputChannelIndex, int LayerNo, int bOnAir) { } public void OnTrigger(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnScenePlayingStarted(string SceneName, int OutputChannelIndex, int LayerNo) { } public void OnScenePlayed(string SceneName, int OutputChannelIndex, int LayerNo) { } public void OnSceneAnimationPlayed(string SceneName, int OutputChannelIndex, int LayerNo, string AnimationName) { } public void OnScenePaused(string SceneName, int OutputChannelIndex, int LayerNo, int bLastPause) { } public void OnSceneSaved(eKResult Result, string FileName) { } public void OnTriggerObject(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnResumeBackground(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnSaveMixedPreviewImage(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnPlayDirect(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnCutIn(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnCutOut(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnClearNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnPlayRange(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnQueryPlaybackRangeCount(eKResult Result, string SceneName, int PlaybackRangeCount) { } public void OnQueryPlaybackRange(eKResult Result, string SceneName, int PlaybackRangeNo, int Start, int End) { } public void OnQueryOutputChannelIndex(eKResult Result, string SceneName, int OutputChannelIndex) { } public void OnPlayInNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnPlayOutNextPreview(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnSetBackgroundFill(eKResult Result, string SceneName) { } public void OnSetBackgroundTexture(eKResult Result, string SceneName) { } public void OnSetBackgroundVideo(eKResult Result, string SceneName) { } public void OnSetBackgroundLiveIn(eKResult Result, string SceneName) { } public void OnUseBackground(eKResult Result, string SceneName) { } public void OnSetBackgroundVideoPlayInfo(eKResult Result, string SceneName) { } public void OnQueryBackgroundVideoPlayInfo(eKResult Result, string SceneName, ref sKVideoPlayInfo pVideoPlayInfo) { } public void OnSetSceneEffectType(eKResult Result, string SceneName) { } public void OnSaveSceneImage(eKResult Result, string SceneName) { } public void OnSaveScene(eKResult Result, string SceneName) { } public void OnUnloadScene(eKResult Result, string SceneName) { _unloadSceneTask.TrySetResult(Result); } public void OnReloadScene(eKResult Result, string SceneName) { } public void OnUpdateTextures(eKResult Result, string SceneName) { } public void OnSetSceneAudioFile(eKResult Result, string SceneName) { } public void OnEnableSceneAudio(eKResult Result, string SceneName) { } public void OnSetSceneDuration(eKResult Result, string SceneName) { } public void OnSetBackgroundPauseType(eKResult Result, string SceneName) { } public void OnSetBackgroundChangeType(eKResult Result, string SceneName) { } public void OnSetBackgroundPauseAtZeroFrameAsStandBy(eKResult Result, string SceneName) { } public void OnResetDuration(eKResult Result, string SceneName) { } public void OnSetDuration(eKResult Result, string SceneName) { } public void OnAddObject(eKResult Result, string SceneName) { } public void OnAddCloneObject(eKResult Result, string SceneName) { } public void OnUpdateThumbnail(eKResult Result, string SceneName) { } public void OnExportVideo(eKResult Result, string SceneName) { } public void OnStopVideoExporting(eKResult Result) { } public void OnQueryVideoExportingProgress(eKResult Result, string TargetName, int CurrentFrame, int TotalFrame) { } public void OnFinishedVideoExporting(eKResult Result, string FileName) { } public void OnAddPause(eKResult Result, string SceneName) { } public void OnDeletePause(eKResult Result, string SceneName) { } public void OnSetPause(eKResult Result, string SceneName) { } public void OnSetPauseWithIndex(eKResult Result, string SceneName) { } public void OnDeletePauseWithIndex(eKResult Result, string SceneName) { } public void OnQueryPauseCount(eKResult Result, string SceneName, int PauseCount) { } public void OnQueryObjectInfos(eKResult Result, string SceneName, KAObjectInfos pObjectInfos) { var objects = new List(); if (Result == eKResult.RESULT_SUCCESS) { var count = pObjectInfos.GetCount(); for (var index = 0; index < count; index++) { var objectInfo = pObjectInfos.GetObjectInfo(index); objects.Add(new SceneObjectCatalogItem( objectInfo.Name ?? string.Empty, objectInfo.ObjectType, objectInfo.Value ?? string.Empty, objectInfo.bVisible != 0)); } } _objectInfosTask.TrySetResult(new ObjectInfosProbeResult(Result, SceneName, objects, string.Empty)); } public void OnQueryAnimationNames(eKResult Result, string SceneName, KAStrings pAnimationNames) { } public void OnQueryAnimationCount(eKResult Result, string SceneName, int AnimationCount) { } public void OnQueryObjectInfosByScreenPoint(eKResult Result, KAObjectInfos pObjectInfos) { } public void OnQuerySceneEffectType(eKResult Result, string SceneName, int bInEffect, eKEffectType EffectType, int Duration) { } public void OnQueryDuration(eKResult Result, string SceneName, string AnimationName, int Duration) { } public void OnQueryContentsOfTextObjects(eKResult Result, string SceneName, KAStrings pTexts) { } public void OnSetStyleColor(eKResult Result, string SceneName, string ObjectName) { if (Result != eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT) { Console.WriteLine($"[SDK] OnSetStyleColor result={Result} scene={SceneName} object={ObjectName}"); } _styleColorTask.TrySetResult(Result); } public void OnSetStyleTexture(eKResult Result, string SceneName, string ObjectName) { } public void OnSetFaceTextColor(eKResult Result, string SceneName, string ObjectName) { } public void OnSetEdgeTextColor(eKResult Result, string SceneName, string ObjectName) { } public void OnSetShadowTextColor(eKResult Result, string SceneName, string ObjectName) { } public void OnSetVisible(eKResult Result, string SceneName, string ObjectName) { if (Result != eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT) { Console.WriteLine($"[SDK] OnSetVisible result={Result} scene={SceneName} object={ObjectName}"); } _visibleTask.TrySetResult(Result); } public void OnSetValue(eKResult Result, string SceneName, string ObjectName) { if (Result != eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT) { Console.WriteLine($"[SDK] OnSetValue result={Result} scene={SceneName} object={ObjectName}"); } _setValueTask.TrySetResult(Result); } public void OnAddText(eKResult Result, string SceneName, string ObjectName) { } public void OnStoreTextStyle(eKResult Result, string SceneName, string ObjectName) { } public void OnSetTextStyle(eKResult Result, string SceneName, string ObjectName) { } public void OnEditText(eKResult Result, string SceneName, string ObjectName) { } public void OnSetFont(eKResult Result, string SceneName, string ObjectName) { } public void OnSetTextRange(eKResult Result, string SceneName, string ObjectName) { } public void OnResetTextRange(eKResult Result, string SceneName, string ObjectName) { } public void OnQueryObjectType(eKResult Result, string SceneName, string ObjectName, eKObjectType ObjectType) { } public void OnSetChartCSVFile(eKResult Result, string SceneName, string ObjectName) { } public void OnSetChartCellData(eKResult Result, string SceneName, string ObjectName) { } public void OnQueryChartDataTable(eKResult Result, string SceneName, string ObjectName, KAChartDataTable Table) { } public void OnQuerySize(eKResult Result, string SceneName, string ObjectName, float Width, float Height) { } public void OnSetSize(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCounterNumberKey(eKResult Result, string SceneName, string ObjectName) { if (Result != eKResult.RESULT_ERROR_NO_VARIABLE_OBJECT) { Console.WriteLine($"[SDK] OnSetCounterNumberKey result={Result} scene={SceneName} object={ObjectName}"); } _counterNumberKeyTask.TrySetResult(Result); } public void OnSetPositionKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetRotationKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetScaleKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCylinderAngleKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetSphereAngleKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCircleAngleKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCropKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCountDown(eKResult Result, string SceneName, string ObjectName) { } public void OnSetPosition(eKResult Result, string SceneName, string ObjectName) { } public void OnSetRotation(eKResult Result, string SceneName, string ObjectName) { } public void OnSetScale(eKResult Result, string SceneName, string ObjectName) { } public void OnAddPathPoint(eKResult Result, string SceneName, string ObjectName) { } public void OnClearPathPoints(eKResult Result, string SceneName, string ObjectName) { } public void OnAddPathShapePoint(eKResult Result, string SceneName, string ObjectName) { } public void OnClearPathShapePoints(eKResult Result, string SceneName, string ObjectName) { } public void OnQueryScrollRemainingDistance(eKResult Result, string SceneName, string ObjectName, int ScrollRemainingDistance) { } public void OnQueryScrollChildRemainingDistance(eKResult Result, string SceneName, string ObjectName, string ChildName, int ScrollRemainingDistance) { } public void OnAddScrollObject(eKResult Result, string SceneName, string ObjectName) { } public void OnAdjustScrollSpeed(eKResult Result, string SceneName, string ObjectName) { } public void OnSetScrollSpeed(eKResult Result, string SceneName, string ObjectName) { } public void OnSetVariableName(eKResult Result, string SceneName, string ObjectName) { } public void OnSetLoftPositionKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetChangeOut(eKResult Result, string SceneName) { } public void OnModifyPathPoint(eKResult Result, string SceneName, string ObjectName) { } public void OnInitScrollObject(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCounterInfo(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCounterNumber(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCounterRange(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCounterRemainingTime(eKResult Result, string SceneName, string ObjectName) { } public void OnSetCounterElapsedTime(eKResult Result, string SceneName, string ObjectName) { } public void OnSaveObjectImage(eKResult Result, string SceneName, string ObjectName) { } public void OnSetPositionOfPathAnimation(eKResult Result, string SceneName, string ObjectName) { } public void OnSetPositionKeyOfPathAnimation(eKResult Result, string SceneName, string ObjectName) { } public void OnSetStartFrame(eKResult Result, string SceneName, string ObjectName) { } public void OnSetObjectEffectType(eKResult Result, string SceneName, string ObjectName) { } public void OnSetObjectOutEffectDelay(eKResult Result, string SceneName, string ObjectName) { } public void OnSetColor(eKResult Result, string SceneName, string ObjectName) { } public void OnSetColorKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetEmissiveColor(eKResult Result, string SceneName, string ObjectName) { } public void OnSetEmissiveColorKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetTransparencyOpacity(eKResult Result, string SceneName, string ObjectName) { } public void OnSetTransparencyOpacityKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetExposure(eKResult Result, string SceneName, string ObjectName) { } public void OnSetExposureKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureType(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureFile(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureOffset(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureOffsetKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureTiling(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureTilingKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureRotation(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureRotationKey(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureOpacity(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureOpacityKey(eKResult Result, string SceneName, string ObjectName) { } public void OnQueryGroupType(eKResult Result, string SceneName, string ObjectName, eKGroupType GroupType) { } public void OnQueryImageType(eKResult Result, string SceneName, string ObjectName, eKImageType ImageType) { } public void OnSetVideoPlayInfo(eKResult Result, string SceneName, string ObjectName) { } public void OnQueryVideoPlayInfo(eKResult Result, string SceneName, string ObjectName, ref sKVideoPlayInfo pVideoPlayInfo) { } public void OnQueryIs3D(eKResult Result, string SceneName, string ObjectName, int b3D) { } public void OnQueryPosition(eKResult Result, string SceneName, string ObjectName, float X, float Y, float Z) { } public void OnSetImageType(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMemo(eKResult Result, string SceneName, string ObjectName) { } public void OnQueryMemo(eKResult Result, string SceneName, string ObjectName, string Memo) { } public void OnQueryFont(eKResult Result, string SceneName, string ObjectName, ref sKFont Param) { } public void OnSetImageOriginalSize(eKResult Result, string SceneName, string ObjectName) { } public void OnApplyChangeEffectLibrary(eKResult Result, string SceneName) { } public void OnApplyObjectLibrary(eKResult Result, string SceneName, string ObjectName) { } public void OnApplyTextureEffectLibrary(eKResult Result, string SceneName, string ObjectName) { } public void OnSetTableValue(eKResult Result, string SceneName, string ObjectName) { } public void OnSetTableColor(eKResult Result, string SceneName, string ObjectName) { } public void OnQueryTableValue(eKResult Result, string SceneName, string ObjectName, int Row, int Column, string Value) { } public void OnQueryTableValues(eKResult Result, string SceneName, string ObjectName, KATableValues pValues) { } public void OnSetPathShapeOutlineThickness(eKResult Result, string SceneName, string ObjectName) { } public void OnEnablePathShapeOutline(eKResult Result, string SceneName, string ObjectName) { } public void OnSetPlaybackCamera(eKResult Result, string SceneName) { } public void OnSetMaterialTextureVideoPlayInfo(eKResult Result, string SceneName, string ObjectName) { } public void OnQueryMaterialTextureVideoPlayInfo(eKResult Result, string SceneName, string ObjectName, ref sKVideoPlayInfo VideoPlayInfo) { } public void OnQueryVideoFormat(eKResult Result, ref sKVideoFormat VideoFormat) { } public void OnQueryLiveStreamingStatus(eKResult Result, string StreamingURI, eKLiveStreamingStatus Status) { } public void OnPreloadLiveStreaming(eKResult Result, string StreamingURI) { } public void OnReleaseLiveStreaming(eKResult Result, string StreamingURI) { } public void OnUpdateImageResource(eKResult Result) { } public void OnQueryLayerCount(eKResult Result, int LayerCount) { } public void OnSetLayerViewportRate(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnSetLayerViewportRateEx(eKResult Result, int OutputChannelIndex, int LayerNo) { } public void OnSetFitting(eKResult Result, string SceneName) { } public void OnSetFittingOffset(eKResult Result, string SceneName) { } public void OnSetFittingScale(eKResult Result, string SceneName) { } public void OnSetLightColor(eKResult Result, string SceneName, string ObjectName) { } public void OnEnableLight(eKResult Result, string SceneName, string ObjectName) { } public void OnSetDirectionalLight(eKResult Result, string SceneName, string ObjectName) { } public void OnSetPointLight(eKResult Result, string SceneName, string ObjectName) { } public void OnSetSpotLight(eKResult Result, string SceneName, string ObjectName) { } public void OnSetInfinitePointLight(eKResult Result, string SceneName, string ObjectName) { } public void OnSetMaterialTextureLiveStreamingURI(eKResult Result, string SceneName, string ObjectName) { } public void OnSetBackgroundLiveStreamingURI(eKResult Result, string SceneName) { } public void OnLoadProject(eKResult Result, string FilePath, string AliasName) { } public void OnNewProject(eKResult Result, string AliasName) { } public void OnUnloadAllProject(eKResult Result) { } public void OnSaveProject(eKResult Result, string AliasName) { } public void OnQuerySceneItemCount(eKResult Result, string AliasName, int SceneItemCount) { } public void OnQuerySceneItemInfos(eKResult Result, string AliasName, KASceneItemInfos SceneItemInfos) { } public void OnAddSceneItem(eKResult Result, string AliasName, int Index) { } public void OnInsertSceneItem(eKResult Result, string AliasName) { } public void OnDeleteSceneItem(eKResult Result, string AliasNAme) { } public void OnQueryProjectFormat(eKResult Result, ref sKVideoFormat ProjectFormat) { } public void OnSetTimecode(eKResult Result, string AliasName) { } public void OnSetTimecodeInOut(eKResult Result, string AliasName) { } public void OnSetTimecodeTrack(eKResult Result, string AliasName) { } public void OnSetTimecodeInOutType(eKResult Result, string AliasName) { } public void OnDeleteTimecode(eKResult Result, string AliasName) { } public void OnQueryTimecode(eKResult Result, int TrackNo, int In, int Out, int bOnTrack) { } public void OnUnloadProject(eKResult Result, string AliasName) { } public void OnEnableSyncWithSceneEffect(eKResult Result, string AliasName) { } public void OnExportProjectVideo(eKResult Result, string AliasName) { } public void OnExportSceneImage(eKResult Result, string SceneName) { } public void OnStartVideoCapture(eKResult Result) { } public void OnStopVideoCapture(eKResult Result) { } public void OnCaptureImage(eKResult Result) { } } [StructLayout(LayoutKind.Sequential)] internal struct NativeMessage { public IntPtr hwnd; public uint message; public UIntPtr wParam; public IntPtr lParam; public uint time; public NativePoint pt; public uint lPrivate; } [StructLayout(LayoutKind.Sequential)] internal struct NativePoint { public int x; public int y; } internal static class User32 { [DllImport("user32.dll")] internal static extern bool PeekMessage(out NativeMessage lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax, uint wRemoveMsg); [DllImport("user32.dll")] internal static extern bool TranslateMessage([In] ref NativeMessage lpMsg); [DllImport("user32.dll")] internal static extern IntPtr DispatchMessage([In] ref NativeMessage lpMsg); }