code wiki / (root) / nx_ico_header.nx

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}