ico_header.nx source
↩ module page · 160 lines · 5011 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
34import "syscalls.nx"
35
36const ICO_ERR_FORMAT: i64 = -1
37const ICO_ERR_SHORT: i64 = -2
38
39const ICO_TYPE_ICON: i64 = 1
40const ICO_TYPE_CURSOR: i64 = 2
41
42struct IcoEntry {
43 width: i64, // 0 means 256
44 height: i64, // 0 means 256
45 color_count: i64, // 0 means truecolor or >= 256 colors
46 planes: i64,
47 bpp: i64,
48 data_size: i64,
49 data_offset: i64,
50}
51
52func ico_read_u16(buf: *u8, off: i64) -> i64 {
53 return buf[off] | (buf[off + 1] << 8)
54}
55
56func ico_read_u32(buf: *u8, off: i64) -> i64 {
57 let b0: i64 = buf[off]
58 let b1: i64 = buf[off + 1]
59 let b2: i64 = buf[off + 2]
60 let b3: i64 = buf[off + 3]
61 return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
62}
63
64// Parse ICO header + up to `cap` directory entries. Returns
65// count of entries parsed (>=1) or a negative ICO_ERR_*.
66func ico_parse(buf: *u8, n: i64,
67 entries: *IcoEntry, cap: i64,
68 type_out: *i64) -> i64 {
69 if n < 6 { return ICO_ERR_SHORT }
70 if ico_read_u16(buf, 0) != 0 { return ICO_ERR_FORMAT } // reserved
71 let ty: i64 = ico_read_u16(buf, 2)
72 if ty != ICO_TYPE_ICON {
73 if ty != ICO_TYPE_CURSOR { return ICO_ERR_FORMAT }
74 }
75 *type_out = ty
76 let count: i64 = ico_read_u16(buf, 4)
77 if count == 0 { return ICO_ERR_FORMAT }
78
79 if n < 6 + count * 16 { return ICO_ERR_SHORT }
80
81 var emit: i64 = count
82 if emit > cap { emit = cap }
83
84 var i: i64 = 0
85 while i < emit {
86 let off: i64 = 6 + i * 16
87 let e: *IcoEntry = entries + i * 56
88
89 var w: i64 = buf[off]
90 if w == 0 { w = 256 }
91 var h: i64 = buf[off + 1]
92 if h == 0 { h = 256 }
93
94 e.width = w
95 e.height = h
96 e.color_count = buf[off + 2]
97 e.planes = ico_read_u16(buf, off + 4)
98 e.bpp = ico_read_u16(buf, off + 6)
99 e.data_size = ico_read_u32(buf, off + 8)
100 e.data_offset = ico_read_u32(buf, off + 12)
101
102 i = i + 1
103 }
104 return emit
105}
106
107// Detect whether an image payload at the given offset is a PNG
108// (vs embedded BMP). PNG signature 0x89 P N G -- we only check
109// the first two bytes here; callers wanting full check should use
110// png_header.nx.
111func ico_is_png_payload(buf: *u8, n: i64, off: i64) -> i64 {
112 if n < off + 2 { return 0 }
113 if buf[off] != 0x89 { return 0 }
114 if buf[off + 1] != 0x50 { return 0 } // 'P'
115 return 1
116}
117
118// Compile-only smoke -- forge a 1-image ICO with 16x16 truecolor.
119func main() -> i64 {
120 let raw: *u8 = sys_mmap(64)
121 var i: i64 = 0
122 while i < 64 { raw[i] = 0; i = i + 1 }
123
124 // ICONDIR
125 // reserved=0, type=1 (ICO), count=1
126 raw[2] = 1
127 raw[4] = 1
128
129 // ICONDIRENTRY[0] at offset 6
130 raw[6] = 16 // width
131 raw[7] = 16 // height
132 raw[8] = 0 // color_count (>=256)
133 raw[9] = 0 // reserved
134 raw[10] = 1 // planes low byte
135 raw[12] = 32 // bpp low byte
136 raw[14] = 0x00; raw[15] = 0x04 // data_size = 1024 low two bytes
137 raw[18] = 22 // data_offset low byte
138
139 let entries_raw: *u8 = sys_mmap(256)
140 let entries: *IcoEntry = entries_raw as *IcoEntry
141 let type_out: *i64 = (sys_mmap(16)) as *i64
142
143 let count: i64 = ico_parse(raw, 64, entries, 16, type_out)
144 if count != 1 { return 1 }
145 if *type_out != ICO_TYPE_ICON { return 2 }
146
147 let e0: *IcoEntry = entries
148 if e0.width != 16 { return 3 }
149 if e0.height != 16 { return 4 }
150 if e0.bpp != 32 { return 5 }
151 if e0.data_size != 1024 { return 6 }
152 if e0.data_offset != 22 { return 7 }
153
154 // Bad reserved field.
155 raw[0] = 1
156 if ico_parse(raw, 64, entries, 16, type_out) != ICO_ERR_FORMAT {
157 return 8
158 }
159 return 0
160}