code wiki / (root) / ico_header.nx

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}