using System.Diagnostics; using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Nodes; static class Program { [DllImport("user32.dll", CharSet = CharSet.Unicode)] static extern int MessageBoxW(IntPtr hWnd, string text, string caption, uint type); static int Main(string[] args) { // Preserve a drive root (for example F:\\ on a portable USB drive). var root = Path.TrimEndingDirectorySeparator(AppContext.BaseDirectory); var rootPrefix = Path.EndsInDirectorySeparator(root) ? root : root + Path.DirectorySeparatorChar; var log = Path.Combine(root, "logs", "launcher.log"); try { Directory.CreateDirectory(Path.GetDirectoryName(log)!); Directory.CreateDirectory(Path.Combine(root, "data")); var marker = Path.Combine(root, "data", "runtime-path.txt"); var engine = File.Exists(marker) ? Path.GetFullPath(Path.Combine(root, File.ReadAllText(marker).Trim())) : Directory.EnumerateFiles(Path.Combine(root, "runtime"), "FreeCAD.exe", SearchOption.AllDirectories) .First(path => string.Equals(Path.GetFileName(Path.GetDirectoryName(path)), "bin", StringComparison.OrdinalIgnoreCase)); if (!engine.StartsWith(rootPrefix, StringComparison.OrdinalIgnoreCase) || !File.Exists(engine)) throw new IOException("Не найдены библиотеки CAD. Выполните scripts\\bootstrap.ps1."); var nativeEngine = RuntimePath.ForEngine(root, engine); var start = new ProcessStartInfo(nativeEngine) { UseShellExecute = false, WorkingDirectory = root }; start.Environment["QI_ROOT"] = root; start.Environment.Remove("QI_TEST"); var selfTest = args.Contains("--self-test", StringComparer.Ordinal); var profile = Path.Combine(root, "data"); if (selfTest) { start.Environment["QI_TEST"] = "1"; profile = Path.Combine(root, "artifacts", "test-profiles", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(profile); var report = Path.Combine(root, "artifacts", "validation.json"); if (File.Exists(report)) File.Move(report, Path.Combine(root, "artifacts", $"validation-previous-{DateTime.UtcNow:yyyyMMdd-HHmmss-fffffff}.json")); } start.ArgumentList.Add("--user-cfg"); start.ArgumentList.Add(Path.Combine(profile, "user.cfg")); start.ArgumentList.Add("--system-cfg"); start.ArgumentList.Add(Path.Combine(profile, "system.cfg")); start.ArgumentList.Add("--write-log"); start.ArgumentList.Add("--log-file"); start.ArgumentList.Add(Path.Combine(root, "logs", "cad.log")); // Apply first-run native view preferences before restored sketches // exist. Setting them afterwards would mark clean projects dirty. start.ArgumentList.Add(Path.Combine(root, "scripts", "startup.FCMacro")); foreach (var arg in args) { if (arg != "--self-test") start.ArgumentList.Add(arg); } File.AppendAllText(log, $"{DateTimeOffset.Now:O} Starting QInventor {engine}\nNative runtime path: {nativeEngine}\n"); using var process = Process.Start(start) ?? throw new IOException("Не удалось запустить CAD-процесс."); process.WaitForExit(); File.AppendAllText(log, $"{DateTimeOffset.Now:O} Exit {process.ExitCode}\n"); if (process.ExitCode != 0) throw new IOException($"CAD-процесс завершился с кодом {process.ExitCode}. Подробности: logs\\cad.log."); if (selfTest) { using var report = JsonDocument.Parse(File.ReadAllText(Path.Combine(root, "artifacts", "validation.json"))); if (report.RootElement.GetProperty("status").GetString() != "passed") { File.AppendAllText(log, $"{DateTimeOffset.Now:O} Проверка приложения завершилась с ошибками; artifacts\\validation.json\n"); return 2; } } return 0; } catch (Exception ex) { try { File.AppendAllText(log, $"{DateTimeOffset.Now:O} {ex}\n"); } catch { } if (args.Contains("--self-test", StringComparer.Ordinal)) { // Native failures can interrupt a suite before Python writes its // final status. Preserve completed checks and make that failure // explicit instead of leaving a misleading running report. try { var reportPath = Path.Combine(root, "artifacts", "validation.json"); Directory.CreateDirectory(Path.GetDirectoryName(reportPath)!); var report = new JsonObject(); if (File.Exists(reportPath)) { try { report = JsonNode.Parse(File.ReadAllText(reportPath)) as JsonObject ?? new JsonObject(); } catch (JsonException) { // A native crash can interrupt Python's live JSON // write. Preserve its remaining bytes, then still // produce a readable failure report. var damaged = Path.Combine(root, "artifacts", $"validation-truncated-{DateTime.UtcNow:yyyyMMdd-HHmmss-fffffff}.json"); File.Copy(reportPath, damaged); } } report["status"] = "failed"; var previousFailed = report["failed"] is JsonValue count && count.TryGetValue(out var failures) ? failures : 0; report["failed"] = Math.Max(1, previousFailed); report["launcher_error"] = ex.Message; report["finished"] = DateTime.UtcNow.ToString("o"); var staged = reportPath + ".launcher-tmp"; File.WriteAllText(staged, report.ToJsonString(new JsonSerializerOptions { WriteIndented = true })); File.Move(staged, reportPath, overwrite: true); } catch { } return 1; } MessageBoxW(IntPtr.Zero, "Не удалось запустить QInventor.\n\nПроверьте, что приложение перенесено вместе с папками runtime, src и scripts. Подробности ошибки сохранены в журнале.\n\nЖурнал: " + log, "QInventor", 0x10); return 1; } } }