@zogien/native-sdk 0.11.0-fork.2 → 0.11.0-fork.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/README.md +1 -1
  2. package/build/app.zig +47 -23
  3. package/build/cli.zig +22 -0
  4. package/native-sdk.d.ts +2 -0
  5. package/package.json +10 -10
  6. package/packages/core/package-lock.json +2 -2
  7. package/packages/core/package.json +1 -1
  8. package/schemas/app.schema.json +9 -1
  9. package/skill-data/ts-core/SKILL.md +2 -0
  10. package/src/app_runner/app_assets.zig +85 -1
  11. package/src/app_runner/root.zig +63 -3
  12. package/src/app_runner/ts_core_main.zig +8 -0
  13. package/src/platform/macos/appkit_host.h +9 -0
  14. package/src/platform/macos/appkit_host.m +366 -0
  15. package/src/platform/macos/root.zig +29 -6
  16. package/src/platform/null_platform.zig +66 -0
  17. package/src/platform/root.zig +1 -0
  18. package/src/platform/types.zig +46 -0
  19. package/src/platform/windows/root.zig +174 -0
  20. package/src/platform/windows/scriptc_mingw_compat.c +248 -0
  21. package/src/platform/windows/webview2_host.cpp +500 -26
  22. package/src/primitives/app_manifest/root.zig +1 -0
  23. package/src/primitives/app_manifest/tests.zig +35 -0
  24. package/src/primitives/app_manifest/types.zig +12 -0
  25. package/src/primitives/app_manifest/validation.zig +44 -6
  26. package/src/root.zig +1 -0
  27. package/src/runtime/bridge_responses.zig +2 -0
  28. package/src/runtime/channel_sources.zig +49 -1
  29. package/src/runtime/core.zig +5 -0
  30. package/src/runtime/effects.zig +12 -1
  31. package/src/runtime/flow.zig +6 -0
  32. package/src/runtime/flow_reference.zig +6 -0
  33. package/src/runtime/root.zig +4 -0
  34. package/src/runtime/single_instance.zig +893 -0
  35. package/src/runtime/tests.zig +1 -0
  36. package/src/runtime/window_controls.zig +73 -0
  37. package/src/runtime/window_controls_tests.zig +192 -0
  38. package/src/runtime/window_storage.zig +3 -0
  39. package/src/tooling/manifest.zig +111 -3
  40. package/src/tooling/package.zig +98 -8
  41. package/src/tooling/raw_manifest.zig +7 -0
  42. package/src/tooling/root.zig +2 -0
  43. package/src/tooling/templates.zig +53 -3
  44. package/src/tooling/update.zig +228 -10
  45. package/src/tooling/windows_installer.zig +245 -0
  46. package/src/tooling/zip_writer.zig +211 -0
  47. package/src/updater/engine.zig +936 -0
  48. package/src/updater/feed.zig +261 -12
  49. package/src/updater/macos.zig +187 -0
  50. package/src/updater/root.zig +41 -0
  51. package/src/updater/windows.zig +470 -0
  52. package/src/windows_install/config.zig +247 -0
  53. package/src/windows_install/main.zig +690 -0
  54. package/src/windows_install/payload.zig +146 -0
  55. package/src/windows_install/switch.zig +184 -0
  56. package/src/windows_install/tests.zig +11 -0
  57. package/src/windows_install/win32.zig +248 -0
  58. package/tools/native-sdk/main.zig +10 -9
@@ -125,6 +125,9 @@ const WindowsEvent = extern struct {
125
125
  /// selection).
126
126
  widget_id: u64,
127
127
  menu_item_id: u32,
128
+ /// Window events: nonzero while the window is maximized. Every emit
129
+ /// carries the live value, so any frame event reports it.
130
+ maximized: c_int,
128
131
  };
129
132
 
130
133
  const WindowsCallback = *const fn (context: ?*anyopaque, event: *const WindowsEvent) callconv(.c) void;
@@ -166,6 +169,9 @@ extern fn native_sdk_windows_minimize_window(host: *WindowsHost, window_id: u64)
166
169
  extern fn native_sdk_windows_hide_window(host: *WindowsHost, window_id: u64) c_int;
167
170
  extern fn native_sdk_windows_show_window(host: *WindowsHost, window_id: u64) c_int;
168
171
  extern fn native_sdk_windows_set_window_close_policy(host: *WindowsHost, window_id: u64, close_policy: c_int) c_int;
172
+ extern fn native_sdk_windows_maximize_window(host: *WindowsHost, window_id: u64) c_int;
173
+ extern fn native_sdk_windows_unmaximize_window(host: *WindowsHost, window_id: u64) c_int;
174
+ extern fn native_sdk_windows_request_close_window(host: *WindowsHost, window_id: u64) c_int;
169
175
  extern fn native_sdk_windows_create_view(host: *WindowsHost, window_id: u64, label: [*]const u8, label_len: usize, kind: c_int, gpu_backend_request: c_int, parent: [*]const u8, parent_len: usize, x: f64, y: f64, width: f64, height: f64, layer: c_int, visible: c_int, enabled: c_int, role: [*]const u8, role_len: usize, accessibility_label: [*]const u8, accessibility_label_len: usize, text: [*]const u8, text_len: usize, command: [*]const u8, command_len: usize) c_int;
170
176
  extern fn native_sdk_windows_update_view(host: *WindowsHost, window_id: u64, label: [*]const u8, label_len: usize, has_frame: c_int, x: f64, y: f64, width: f64, height: f64, has_layer: c_int, layer: c_int, has_visible: c_int, visible: c_int, has_enabled: c_int, enabled: c_int, has_role: c_int, role: [*]const u8, role_len: usize, has_accessibility_label: c_int, accessibility_label: [*]const u8, accessibility_label_len: usize, has_text: c_int, text: [*]const u8, text_len: usize, has_command: c_int, command: [*]const u8, command_len: usize) c_int;
171
177
  extern fn native_sdk_windows_set_view_frame(host: *WindowsHost, window_id: u64, label: [*]const u8, label_len: usize, x: f64, y: f64, width: f64, height: f64) c_int;
@@ -438,6 +444,9 @@ pub const WindowsPlatform = struct {
438
444
  .focus_window_fn = focusWindow,
439
445
  .close_window_fn = closeWindow,
440
446
  .minimize_window_fn = minimizeWindow,
447
+ .maximize_window_fn = maximizeWindow,
448
+ .unmaximize_window_fn = unmaximizeWindow,
449
+ .request_close_window_fn = requestCloseWindow,
441
450
  .hide_window_fn = hideWindow,
442
451
  .show_window_fn = showWindow,
443
452
  .quit_app_fn = quitApp,
@@ -658,6 +667,7 @@ fn windowsCallback(context: ?*anyopaque, event: *const WindowsEvent) callconv(.c
658
667
  .open = event.open != 0,
659
668
  .focused = event.focused != 0,
660
669
  .hidden = event.hidden != 0,
670
+ .maximized = event.maximized != 0,
661
671
  } });
662
672
  },
663
673
  .view_focused => state.emit(.{ .view_focused = .{
@@ -1036,6 +1046,36 @@ fn minimizeWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerr
1036
1046
  if (native_sdk_windows_minimize_window(self.host, window_id) == 0) return error.WindowNotFound;
1037
1047
  }
1038
1048
 
1049
+ fn maximizeWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerror!void {
1050
+ const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
1051
+ return windowControlResult(native_sdk_windows_maximize_window(self.host, window_id));
1052
+ }
1053
+
1054
+ fn unmaximizeWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerror!void {
1055
+ const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
1056
+ return windowControlResult(native_sdk_windows_unmaximize_window(self.host, window_id));
1057
+ }
1058
+
1059
+ fn requestCloseWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerror!void {
1060
+ const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
1061
+ return switch (native_sdk_windows_request_close_window(self.host, window_id)) {
1062
+ 1 => {},
1063
+ 0 => error.WindowNotFound,
1064
+ else => error.CloseFailed,
1065
+ };
1066
+ }
1067
+
1068
+ /// The maximize/unmaximize host status codes (see webview2_host.cpp).
1069
+ fn windowControlResult(status: c_int) anyerror!void {
1070
+ return switch (status) {
1071
+ 1 => {},
1072
+ 0 => error.WindowNotFound,
1073
+ -1 => error.WindowNotResizable,
1074
+ -2 => error.WindowHidden,
1075
+ else => error.Unexpected,
1076
+ };
1077
+ }
1078
+
1039
1079
  fn hideWindow(context: ?*anyopaque, window_id: platform_mod.WindowId) anyerror!void {
1040
1080
  const self: *WindowsPlatform = @ptrCast(@alignCast(context.?));
1041
1081
  if (native_sdk_windows_hide_window(self.host, window_id) == 0) return error.WindowNotFound;
@@ -2687,6 +2727,43 @@ test "windows webview focus reports the focused child label" {
2687
2727
  try std.testing.expect(std.mem.indexOf(u8, host_source, "event.view_label = focused->second.label.c_str();") != null);
2688
2728
  }
2689
2729
 
2730
+ test "windows webview2 keeps its user data with the app's per-user data" {
2731
+ // WebView2's default user data folder sits beside the executable,
2732
+ // which is read-only under Program Files and is swapped away with
2733
+ // the program files on update. Every environment the host creates
2734
+ // names the folder beside the app_dirs roots instead, and an earlier
2735
+ // "<exe>.WebView2" folder moves there once.
2736
+ const host_source = @embedFile("webview2_host.cpp");
2737
+ try std.testing.expect(std.mem.indexOf(u8, host_source, "factory(nullptr, webViewUserDataFolder(host), nullptr,") != null);
2738
+ try std.testing.expect(std.mem.indexOf(u8, host_source, "factory(nullptr, nullptr, nullptr,") == null);
2739
+ const resolve_at = std.mem.indexOf(u8, host_source, "static const wchar_t *webViewUserDataFolder(Host *host) {") orelse return error.TestExpectedEqual;
2740
+ const resolve = host_source[resolve_at..];
2741
+ try std.testing.expect(std.mem.indexOf(u8, resolve, "GetEnvironmentVariableW(L\"LOCALAPPDATA\"") != null);
2742
+ try std.testing.expect(std.mem.indexOf(u8, resolve, "const std::wstring folder = app_root + L\"\\\\WebView2\";") != null);
2743
+ try std.testing.expect(std.mem.indexOf(u8, resolve, "MoveFileExW(legacy.c_str(), folder.c_str(), 0)") != null);
2744
+ try std.testing.expect(std.mem.indexOf(u8, host_source, "return path + L\".WebView2\";") != null);
2745
+ }
2746
+
2747
+ test "windows webview assets resolve against the packaged resources beside bin" {
2748
+ // `native package` stages bin\<exe> and the app-relative asset tree
2749
+ // in resources\; a relative asset root names that tree when the app
2750
+ // runs from a package and the working directory otherwise.
2751
+ const host_source = @embedFile("webview2_host.cpp");
2752
+ try std.testing.expect(std.mem.indexOf(u8, host_source, "std::string path = assetRootPath(webview.asset_root);") != null);
2753
+ const resources_at = std.mem.indexOf(u8, host_source, "static const std::wstring &packagedResourcesDirectory() {") orelse return error.TestExpectedEqual;
2754
+ const resources = host_source[resources_at..];
2755
+ try std.testing.expect(std.mem.indexOf(u8, resources, "CompareStringOrdinal(bin.c_str() + bin_slash + 1, -1, L\"bin\", -1, TRUE)") != null);
2756
+ try std.testing.expect(std.mem.indexOf(u8, resources, "return bin.substr(0, bin_slash) + L\"\\\\resources\";") != null);
2757
+ }
2758
+
2759
+ test "windows names the process by its bundle id for the taskbar" {
2760
+ // The installer's Start menu shortcut carries the bundle id as its
2761
+ // AppUserModelID; the process must claim the same id.
2762
+ const host_source = @embedFile("webview2_host.cpp");
2763
+ const create_at = std.mem.indexOf(u8, host_source, "host->bundle_id = slice(bundle_id, bundle_id_len);") orelse return error.TestExpectedEqual;
2764
+ try std.testing.expect(std.mem.indexOf(u8, host_source[create_at..], "SetCurrentProcessExplicitAppUserModelID(app_user_model_id.c_str());") != null);
2765
+ }
2766
+
2690
2767
  test "windows audio event maps kinds and payload" {
2691
2768
  var event = std.mem.zeroes(WindowsEvent);
2692
2769
  event.audio_kind = 1;
@@ -2760,3 +2837,100 @@ fn flattenFilters(filters: []const platform_mod.FileFilter, buffer: []u8) []cons
2760
2837
  test "windows platform module exports type" {
2761
2838
  _ = WindowsPlatform;
2762
2839
  }
2840
+
2841
+ test "windows event declares the same fields in the same order as the C host" {
2842
+ // WindowsEvent is written twice, here and in webview2_host.cpp, and
2843
+ // crosses the C ABI by layout. A field added on one side only, or at
2844
+ // a different position, shifts every later field without a compile
2845
+ // error, so the two declarations are compared by name and order.
2846
+ const field_names = comptime blk: {
2847
+ const fields = @typeInfo(WindowsEvent).@"struct".fields;
2848
+ var names: [fields.len][]const u8 = undefined;
2849
+ for (fields, 0..) |field, index| names[index] = field.name;
2850
+ break :blk names;
2851
+ };
2852
+ const host_source = @embedFile("webview2_host.cpp");
2853
+ const header = "struct WindowsEvent {";
2854
+ const start = (std.mem.indexOf(u8, host_source, header) orelse return error.TestExpectedEqual) + header.len;
2855
+ const end = std.mem.indexOfPos(u8, host_source, start, "};") orelse return error.TestExpectedEqual;
2856
+ var lines = std.mem.splitScalar(u8, host_source[start..end], '\n');
2857
+ var count: usize = 0;
2858
+ var in_comment = false;
2859
+ while (lines.next()) |raw| {
2860
+ const line = std.mem.trim(u8, raw, " \t\r");
2861
+ if (in_comment) {
2862
+ if (std.mem.indexOf(u8, line, "*/") != null) in_comment = false;
2863
+ continue;
2864
+ }
2865
+ if (std.mem.startsWith(u8, line, "/*")) {
2866
+ if (std.mem.indexOf(u8, line, "*/") == null) in_comment = true;
2867
+ continue;
2868
+ }
2869
+ if (!std.mem.endsWith(u8, line, ";")) continue;
2870
+ var declaration = line[0 .. line.len - 1];
2871
+ if (std.mem.indexOfScalar(u8, declaration, '[')) |bracket| declaration = declaration[0..bracket];
2872
+ const name_start = (std.mem.lastIndexOfAny(u8, declaration, " *") orelse return error.TestExpectedEqual) + 1;
2873
+ try std.testing.expect(count < field_names.len);
2874
+ try std.testing.expectEqualStrings(field_names[count], declaration[name_start..]);
2875
+ count += 1;
2876
+ }
2877
+ try std.testing.expectEqual(field_names.len, count);
2878
+ }
2879
+
2880
+ /// One exported host function's source: from `start` up to the next
2881
+ /// exported `native_sdk_windows_` definition.
2882
+ fn hostFunction(host_source: []const u8, start: usize) ?[]const u8 {
2883
+ const end = std.mem.indexOfPos(u8, host_source, start + 1, "int native_sdk_windows_") orelse return null;
2884
+ return host_source[start..end];
2885
+ }
2886
+
2887
+ test "windows maximize status codes map to explicit errors" {
2888
+ try windowControlResult(1);
2889
+ try std.testing.expectError(error.WindowNotFound, windowControlResult(0));
2890
+ try std.testing.expectError(error.WindowNotResizable, windowControlResult(-1));
2891
+ try std.testing.expectError(error.WindowHidden, windowControlResult(-2));
2892
+ }
2893
+
2894
+ test "windows maximize refuses what the system would not maximize and reports every transition" {
2895
+ // The host verbs are C++ this suite cannot execute; pin their shape
2896
+ // textually, like the WM_CLOSE hook test above.
2897
+ const host_source = @embedFile("webview2_host.cpp");
2898
+ const maximize_at = std.mem.indexOf(u8, host_source, "int native_sdk_windows_maximize_window(") orelse return error.TestExpectedEqual;
2899
+ const maximize_fn = hostFunction(host_source, maximize_at) orelse return error.TestExpectedEqual;
2900
+ const resizable_at = std.mem.indexOf(u8, maximize_fn, "if (!found->second.resizable) return -1;") orelse return error.TestExpectedEqual;
2901
+ const visible_at = std.mem.indexOf(u8, maximize_fn, "if (!IsWindowVisible(found->second.hwnd)) return -2;") orelse return error.TestExpectedEqual;
2902
+ const show_at = std.mem.indexOf(u8, maximize_fn, "ShowWindow(found->second.hwnd, SW_MAXIMIZE);") orelse return error.TestExpectedEqual;
2903
+ try std.testing.expect(resizable_at < show_at and visible_at < show_at);
2904
+
2905
+ // Every frame event carries the live state, and the top-level window
2906
+ // procedure's WM_SIZE reports each transition, whoever caused it.
2907
+ try std.testing.expect(std.mem.indexOf(u8, host_source, "event.maximized = window.hwnd && IsZoomed(window.hwnd) ? 1 : 0;") != null);
2908
+ const proc_at = std.mem.indexOf(u8, host_source, "static LRESULT CALLBACK windowProc(") orelse return error.TestExpectedEqual;
2909
+ const size_at = std.mem.indexOfPos(u8, host_source, proc_at, "case WM_SIZE:") orelse return error.TestExpectedEqual;
2910
+ const next_case_at = std.mem.indexOfPos(u8, host_source, size_at, "case WM_SETFOCUS:") orelse return error.TestExpectedEqual;
2911
+ try std.testing.expect(std.mem.indexOf(u8, host_source[size_at..next_case_at], "emitMaximizedTransition(host, hwnd);") != null);
2912
+
2913
+ // A maximized chromeless window keeps the taskbar visible: without
2914
+ // a caption the system would size it to the whole monitor. The
2915
+ // min/max answer sets the size, and the position change re-derives
2916
+ // it from the monitor the window lands on, which the min/max answer
2917
+ // alone can get wrong on a secondary monitor larger than the primary.
2918
+ const minmax_at = std.mem.indexOfPos(u8, host_source, proc_at, "case WM_GETMINMAXINFO:") orelse return error.TestExpectedEqual;
2919
+ try std.testing.expect(std.mem.startsWith(u8, std.mem.trimStart(u8, host_source[minmax_at + "case WM_GETMINMAXINFO:".len ..], " \t\r\n"), "applyChromelessMaximizedBounds(host, hwnd, reinterpret_cast<MINMAXINFO *>(lparam));"));
2920
+ const changing_at = std.mem.indexOfPos(u8, host_source, proc_at, "case WM_WINDOWPOSCHANGING:") orelse return error.TestExpectedEqual;
2921
+ try std.testing.expect(std.mem.startsWith(u8, std.mem.trimStart(u8, host_source[changing_at + "case WM_WINDOWPOSCHANGING:".len ..], " \t\r\n"), "clampChromelessMaximizedFrame(host, hwnd, reinterpret_cast<WINDOWPOS *>(lparam));"));
2922
+ }
2923
+
2924
+ test "windows close request posts the user's WM_CLOSE while the app's close stays direct" {
2925
+ const host_source = @embedFile("webview2_host.cpp");
2926
+ const request_at = std.mem.indexOf(u8, host_source, "int native_sdk_windows_request_close_window(") orelse return error.TestExpectedEqual;
2927
+ const request_fn = hostFunction(host_source, request_at) orelse return error.TestExpectedEqual;
2928
+ try std.testing.expect(std.mem.indexOf(u8, request_fn, "PostMessageW(found->second.hwnd, WM_CLOSE, 0, 0)") != null);
2929
+ try std.testing.expect(std.mem.indexOf(u8, request_fn, "DestroyWindow") == null);
2930
+
2931
+ // The app's own close keeps bypassing the WM_CLOSE close-policy hook.
2932
+ const close_at = std.mem.indexOf(u8, host_source, "int native_sdk_windows_close_window(") orelse return error.TestExpectedEqual;
2933
+ const close_fn = hostFunction(host_source, close_at) orelse return error.TestExpectedEqual;
2934
+ try std.testing.expect(std.mem.indexOf(u8, close_fn, "DestroyWindow(found->second.hwnd);") != null);
2935
+ try std.testing.expect(std.mem.indexOf(u8, close_fn, "WM_CLOSE") == null);
2936
+ }
@@ -0,0 +1,248 @@
1
+ /* MinGW startup and CRT pieces for scriptc's Windows runtime on MSVC targets.
2
+ *
3
+ * The pinned scriptc builds its Windows runtime pack against Zig's MinGW
4
+ * sysroot (packages/runtime-win32-x64-msvc/scripts/build.mjs passes
5
+ * -target x86_64-windows-gnu). The runtime is already Win32-native for
6
+ * threads, child processes and its event loop; what remains is what MinGW
7
+ * layers over the UCRT. The MSVC build links the same UCRT statically, so
8
+ * this file restates exactly that, with the MinGW behaviour the runtime was
9
+ * compiled against: running its .ctors constructors, the dllimport slots for
10
+ * UCRT functions, stat64i32, the ___chkstk_ms stack probe, and <dirent.h>.
11
+ * POSIX names such as open/read/close come from MSVC's oldnames.lib instead.
12
+ *
13
+ * Delete this file once scriptc ships a runtime pack built for the MSVC CRT.
14
+ */
15
+
16
+ /* The import slots below only take the address of the UCRT's "insecure"
17
+ * functions (_wfopen, _wopen); nothing here calls them. */
18
+ #define _CRT_SECURE_NO_WARNINGS
19
+
20
+ #include <ctype.h>
21
+ #include <errno.h>
22
+ #include <io.h>
23
+ #include <process.h>
24
+ #include <stdint.h>
25
+ #include <stdio.h>
26
+ #include <stdlib.h>
27
+ #include <string.h>
28
+ #include <sys/stat.h>
29
+ #include <sys/utime.h>
30
+
31
+ #define WIN32_LEAN_AND_MEAN
32
+ #include <windows.h>
33
+
34
+ /* clang places a MinGW object's __attribute__((constructor)) functions in
35
+ * .ctors, which only MinGW's startup runs; the MSVC CRT runs .CRT$XCU. The
36
+ * runtime initializes state that way (scr_json.c's small-int table, for one),
37
+ * so walk this image's .ctors from a .CRT$XCU initializer, back to front as
38
+ * MinGW's __do_global_ctors does, skipping its 0/-1 list markers. */
39
+ typedef void (*scriptc_ctor_fn)(void);
40
+ extern IMAGE_DOS_HEADER __ImageBase;
41
+
42
+ static void __cdecl scriptc_run_mingw_ctors(void) {
43
+ const char *base = (const char *)&__ImageBase;
44
+ const IMAGE_NT_HEADERS *nt = (const IMAGE_NT_HEADERS *)(base + __ImageBase.e_lfanew);
45
+ const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nt);
46
+ for (WORD index = 0; index < nt->FileHeader.NumberOfSections; index++, section++) {
47
+ if (memcmp(section->Name, ".ctors\0\0", IMAGE_SIZEOF_SHORT_NAME) != 0) continue;
48
+ scriptc_ctor_fn *begin = (scriptc_ctor_fn *)(base + section->VirtualAddress);
49
+ scriptc_ctor_fn *end = begin + section->Misc.VirtualSize / sizeof(scriptc_ctor_fn);
50
+ while (end > begin) {
51
+ scriptc_ctor_fn ctor = *--end;
52
+ if (ctor && ctor != (scriptc_ctor_fn)(intptr_t)-1) ctor();
53
+ }
54
+ }
55
+ }
56
+
57
+ __attribute__((section(".CRT$XCU"), used)) static void(__cdecl *const scriptc_mingw_ctors)(void) =
58
+ scriptc_run_mingw_ctors;
59
+
60
+ /* MinGW's headers declare UCRT functions dllimport, so the runtime calls them
61
+ * through __imp_ slots that the static UCRT does not define. lld would bind
62
+ * most of them to the static definitions anyway, but with one LNK4217 warning
63
+ * each (and tolower not at all), so define every slot the runtime pack's
64
+ * library objects reference. A slot added by a future pack shows up as an
65
+ * undefined symbol or a new LNK4217 warning in the MSVC link. */
66
+ #define SCRIPTC_UCRT_IMPORT(name) __typeof__(&name) const __imp_##name = &name;
67
+ /* The UCRT exports _assert, but its <assert.h> declares only _wassert. */
68
+ void __cdecl _assert(const char *message, const char *file, unsigned line);
69
+ SCRIPTC_UCRT_IMPORT(__acrt_iob_func)
70
+ SCRIPTC_UCRT_IMPORT(_assert)
71
+ SCRIPTC_UCRT_IMPORT(_chsize_s)
72
+ SCRIPTC_UCRT_IMPORT(_commit)
73
+ SCRIPTC_UCRT_IMPORT(_errno)
74
+ SCRIPTC_UCRT_IMPORT(_fstat64)
75
+ SCRIPTC_UCRT_IMPORT(_fullpath)
76
+ SCRIPTC_UCRT_IMPORT(_get_osfhandle)
77
+ SCRIPTC_UCRT_IMPORT(_getpid)
78
+ SCRIPTC_UCRT_IMPORT(_stricmp)
79
+ SCRIPTC_UCRT_IMPORT(_umask_s)
80
+ SCRIPTC_UCRT_IMPORT(_utime64)
81
+ SCRIPTC_UCRT_IMPORT(_wfopen)
82
+ SCRIPTC_UCRT_IMPORT(_wopen)
83
+ SCRIPTC_UCRT_IMPORT(tolower)
84
+
85
+ /* MinGW-UCRT aliases stat64i32 to the UCRT's _stat64i32. */
86
+ int __cdecl stat64i32(const char *path, struct _stat64i32 *buf) {
87
+ return _stat64i32(path, buf);
88
+ }
89
+
90
+ /* MinGW's stack probe: rax holds the frame size; touch every page below the
91
+ * caller's stack pointer without moving it, preserving all registers. */
92
+ __attribute__((naked)) void ___chkstk_ms(void) {
93
+ __asm__ volatile(
94
+ "pushq %rcx\n\t"
95
+ "pushq %rax\n\t"
96
+ "cmpq $0x1000, %rax\n\t"
97
+ "leaq 24(%rsp), %rcx\n\t"
98
+ "jb 1f\n"
99
+ "2:\n\t"
100
+ "subq $0x1000, %rcx\n\t"
101
+ "testq %rcx, (%rcx)\n\t"
102
+ "subq $0x1000, %rax\n\t"
103
+ "cmpq $0x1000, %rax\n\t"
104
+ "ja 2b\n"
105
+ "1:\n\t"
106
+ "subq %rax, %rcx\n\t"
107
+ "testq %rcx, (%rcx)\n\t"
108
+ "popq %rax\n\t"
109
+ "popq %rcx\n\t"
110
+ "retq");
111
+ }
112
+
113
+ /* <dirent.h>, ported from mingw-w64's public-domain misc/dirent.c (char
114
+ * variant). struct dirent and DIR keep mingw-w64's layout because the
115
+ * runtime was compiled against its header. */
116
+
117
+ struct dirent {
118
+ long d_ino;
119
+ unsigned short d_reclen;
120
+ unsigned short d_namlen;
121
+ char d_name[260];
122
+ };
123
+
124
+ typedef struct {
125
+ struct _finddata_t dd_dta;
126
+ struct dirent dd_dir;
127
+ intptr_t dd_handle;
128
+ int dd_stat;
129
+ char dd_name[1];
130
+ } DIR;
131
+
132
+ DIR *__cdecl opendir(const char *path) {
133
+ errno = 0;
134
+ if (!path) {
135
+ errno = EFAULT;
136
+ return NULL;
137
+ }
138
+ if (path[0] == '\0') {
139
+ errno = ENOTDIR;
140
+ return NULL;
141
+ }
142
+ DWORD attributes = GetFileAttributesA(path);
143
+ if (attributes == INVALID_FILE_ATTRIBUTES) {
144
+ errno = ENOENT;
145
+ return NULL;
146
+ }
147
+ if (!(attributes & FILE_ATTRIBUTE_DIRECTORY)) {
148
+ errno = ENOTDIR;
149
+ return NULL;
150
+ }
151
+ char full[MAX_PATH];
152
+ if (!_fullpath(full, path, MAX_PATH)) return NULL;
153
+ size_t len = strlen(full);
154
+ /* The search pattern: the absolute path, a separator, "*", and NUL. */
155
+ DIR *dir = malloc(sizeof(DIR) + len + 3);
156
+ if (!dir) {
157
+ errno = ENOMEM;
158
+ return NULL;
159
+ }
160
+ memcpy(dir->dd_name, full, len);
161
+ if (len > 0 && full[len - 1] != '/' && full[len - 1] != '\\') dir->dd_name[len++] = '\\';
162
+ dir->dd_name[len++] = '*';
163
+ dir->dd_name[len] = '\0';
164
+ dir->dd_handle = -1;
165
+ dir->dd_stat = 0;
166
+ dir->dd_dir.d_ino = 0;
167
+ dir->dd_dir.d_reclen = 0;
168
+ dir->dd_dir.d_namlen = 0;
169
+ memset(dir->dd_dir.d_name, 0, sizeof(dir->dd_dir.d_name));
170
+ return dir;
171
+ }
172
+
173
+ struct dirent *__cdecl readdir(DIR *dir) {
174
+ errno = 0;
175
+ if (!dir) {
176
+ errno = EFAULT;
177
+ return NULL;
178
+ }
179
+ if (dir->dd_stat < 0) return NULL;
180
+ if (dir->dd_stat == 0) {
181
+ dir->dd_handle = _findfirst(dir->dd_name, &dir->dd_dta);
182
+ dir->dd_stat = dir->dd_handle == -1 ? -1 : 1;
183
+ } else if (_findnext(dir->dd_handle, &dir->dd_dta)) {
184
+ /* _findnext reports the end as ENOENT; the end is not an error. */
185
+ if (GetLastError() == ERROR_NO_MORE_FILES) errno = 0;
186
+ _findclose(dir->dd_handle);
187
+ dir->dd_handle = -1;
188
+ dir->dd_stat = -1;
189
+ } else {
190
+ dir->dd_stat++;
191
+ }
192
+ if (dir->dd_stat <= 0) return NULL;
193
+ /* Both buffers are MAX_PATH chars, so the name and its NUL always fit. */
194
+ size_t len = strlen(dir->dd_dta.name);
195
+ dir->dd_dir.d_namlen = (unsigned short)len;
196
+ memcpy(dir->dd_dir.d_name, dir->dd_dta.name, len + 1);
197
+ return &dir->dd_dir;
198
+ }
199
+
200
+ int __cdecl closedir(DIR *dir) {
201
+ errno = 0;
202
+ if (!dir) {
203
+ errno = EFAULT;
204
+ return -1;
205
+ }
206
+ int rc = dir->dd_handle != -1 ? _findclose(dir->dd_handle) : 0;
207
+ free(dir);
208
+ return rc;
209
+ }
210
+
211
+ void __cdecl rewinddir(DIR *dir) {
212
+ errno = 0;
213
+ if (!dir) {
214
+ errno = EFAULT;
215
+ return;
216
+ }
217
+ if (dir->dd_handle != -1) _findclose(dir->dd_handle);
218
+ dir->dd_handle = -1;
219
+ dir->dd_stat = 0;
220
+ }
221
+
222
+ long __cdecl telldir(DIR *dir) {
223
+ errno = 0;
224
+ if (!dir) {
225
+ errno = EFAULT;
226
+ return -1;
227
+ }
228
+ return dir->dd_stat;
229
+ }
230
+
231
+ void __cdecl seekdir(DIR *dir, long position) {
232
+ errno = 0;
233
+ if (!dir) {
234
+ errno = EFAULT;
235
+ return;
236
+ }
237
+ if (position < -1) {
238
+ errno = EINVAL;
239
+ } else if (position == -1) {
240
+ if (dir->dd_handle != -1) _findclose(dir->dd_handle);
241
+ dir->dd_handle = -1;
242
+ dir->dd_stat = -1;
243
+ } else {
244
+ rewinddir(dir);
245
+ while (dir->dd_stat < position && readdir(dir)) {
246
+ }
247
+ }
248
+ }