nx_ico_header.nx source
↩ module page · 167 lines · 5158 B
1// ico_header.nx -- parse Windows ICO / CUR icon container headers.
2//
3// ICO is a container for multiple icon images of different sizes
4// + color depths. Every browser needs ICO parsing for favicon.ico.
5// Every Windows app ships one. Nishi sites need to emit and
6// detect their own.
7//
8// Format:
9// ICONDIR (6 bytes, fixed)
10// 0 2 reserved (0)
11// 2 2 image_type (1 = ICO, 2 = CUR cursor)
12// 4 2 num_images
13//
14// then num_images × ICONDIRENTRY (16 bytes):
15// 0 1 width (0 means 256)
16// 1 1 height (0 means 256)
17// 2 1 color_count (0 if >= 256 colors)
18// 3 1 reserved (0)
19// 4 2 planes (ICO) / hotspot_x (CUR)
20// 6 2 bpp (ICO) / hotspot_y (CUR)
21// 8 4 data_size
22// 12 4 data_offset
23//
24// Image payloads are then either:
25// - BMP without the BITMAPFILEHEADER (just DIB + pixel data),
26// OR
27// - complete PNG (detect via PNG signature)
28//
29// Invariants:
30// I1 image_type must be 1 (ICO) or 2 (CUR).
31// I2 num_images >= 1.
32// I3 Zero-alloc: entries[] passes in by caller; we fill offsets.
33
34// nx_safety_envelope:
35// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
36// sil_target: SIL1
37// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
38// verdict: NOT_YET_EVALUATED
39
40import "nx_syscalls.nx"
41const ICO_MAGIC_1024: i64 = 1024
42
43const ICO_ERR_FORMAT: i64 = -1
44const ICO_ERR_SHORT: i64 = -2
45
46const ICO_TYPE_ICON: i64 = 1
47const ICO_TYPE_CURSOR: i64 = 2
48
49struct IcoEntry {
50 width: i64, // 0 means 256
51 height: i64, // 0 means 256
52 color_count: i64, // 0 means truecolor or >= 256 colors
53 planes: i64,
54 bpp: i64,
55 data_size: i64,
56 data_offset: i64,
57}
58
59func ico_read_u16(buf: *u8, off: i64) -> i64 {
60 return buf[off] | (buf[off + 1] << 8)
61}
62
63func ico_read_u32(buf: *u8, off: i64) -> i64 {
64 let b0: i64 = buf[off]
65 let b1: i64 = buf[off + 1]
66 let b2: i64 = buf[off + 2]
67 let b3: i64 = buf[off + 3]
68 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
69}
70
71// Parse ICO header + up to `cap` directory entries. Returns
72// count of entries parsed (>=1) or a negative ICO_ERR_*.
73func ico_parse(buf: *u8, n: i64,
74 entries: *IcoEntry, cap: i64,
75 type_out: *i64) -> i64 {
76 if n < 6 { return ICO_ERR_SHORT }
77 if ico_read_u16(buf, 0) != 0 { return ICO_ERR_FORMAT } // reserved
78 let ty: i64 = ico_read_u16(buf, 2)
79 if ty != ICO_TYPE_ICON {
80 if ty != ICO_TYPE_CURSOR { return ICO_ERR_FORMAT }
81 }
82 *type_out = ty
83 let count: i64 = ico_read_u16(buf, 4)
84 if count == 0 { return ICO_ERR_FORMAT }
85
86 if n < 6 + count * 16 { return ICO_ERR_SHORT }
87
88 var emit: i64 = count
89 if emit > cap { emit = cap }
90
91 var i: i64 = 0
92 while i < emit {
93 let off: i64 = 6 + i * 16
94 let e: *IcoEntry = entries + i * 56
95
96 var w: i64 = buf[off]
97 if w == 0 { w = 256 }
98 var h: i64 = buf[off + 1]
99 if h == 0 { h = 256 }
100
101 e.width = w
102 e.height = h
103 e.color_count = buf[off + 2]
104 e.planes = ico_read_u16(buf, off + 4)
105 e.bpp = ico_read_u16(buf, off + 6)
106 e.data_size = ico_read_u32(buf, off + 8)
107 e.data_offset = ico_read_u32(buf, off + 12)
108
109 i = i + 1
110 }
111 return emit
112}
113
114// Detect whether an image payload at the given offset is a PNG
115// (vs embedded BMP). PNG signature 0x89 P N G -- we only check
116// the first two bytes here; callers wanting full check should use
117// png_header.nx.
118func ico_is_png_payload(buf: *u8, n: i64, off: i64) -> i64 {
119 if n < off + 2 { return 0 }
120 if buf[off] != 0x89 { return 0 }
121 if buf[off + 1] != 0x50 { return 0 } // 'P'
122 return 1
123}
124
125// Compile-only smoke -- forge a 1-image ICO with 16x16 truecolor.
126func main() -> i64 {
127 let raw: *u8 = sys_mmap(64)
128 var i: i64 = 0
129 while i < 64 { raw[i] = 0; i = i + 1 }
130
131 // ICONDIR
132 // reserved=0, type=1 (ICO), count=1
133 raw[2] = 1
134 raw[4] = 1
135
136 // ICONDIRENTRY[0] at offset 6
137 raw[6] = 16 // width
138 raw[7] = 16 // height
139 raw[8] = 0 // color_count (>=256)
140 raw[9] = 0 // reserved
141 raw[10] = 1 // planes low byte
142 raw[12] = 32 // bpp low byte
143 raw[14] = 0x00; raw[15] = 0x04 // data_size = ICO_MAGIC_1024 low two bytes
144 raw[18] = 22 // data_offset low byte
145
146 let entries_raw: *u8 = sys_mmap(256)
147 let entries: *IcoEntry = entries_raw as *IcoEntry
148 let type_out: *i64 = (sys_mmap(16)) as *i64
149
150 let count: i64 = ico_parse(raw, 64, entries, 16, type_out)
151 if count != 1 { return 1 }
152 if *type_out != ICO_TYPE_ICON { return 2 }
153
154 let e0: *IcoEntry = entries
155 if e0.width != 16 { return 3 }
156 if e0.height != 16 { return 4 }
157 if e0.bpp != 32 { return 5 }
158 if e0.data_size != ICO_MAGIC_1024 { return 6 }
159 if e0.data_offset != 22 { return 7 }
160
161 // Bad reserved field.
162 raw[0] = 1
163 if ico_parse(raw, 64, entries, 16, type_out) != ICO_ERR_FORMAT {
164 return 8
165 }
166 return 0
167}