code wiki / (root) / nx_ico.nx

nx_ico.nx source

↩ module page · 208 lines · 7328 B

1// nx_ico.nx -- ICO/CUR icon container: a COMPLETE format, writer and reader. 2// 3// Every site has a favicon and the tree could not read or write one. ICO is a 4// directory of images -- historically DIB bitmaps, and since about 2010 5// usually PNG for the larger sizes -- so a reader has to dispatch on the 6// payload's own magic rather than on anything in the directory. 7// 8// ZERO MEANS 256. The width and height fields are ONE BYTE each, so 256 -- 9// the standard modern favicon size -- is encoded as 0. A reader that takes 10// the byte at face value reports a 256x256 icon as 0x0 and usually then 11// divides by it or skips it. This is the single most common ICO bug and it 12// only appears at the one size that matters most today. 13// 14// THE DIRECTORY DOES NOT KNOW THE FORMAT. Nothing in an ICONDIRENTRY says 15// whether the payload is a DIB or a PNG; both are legal in the same file, and 16// the bitCount field is frequently zero or wrong for PNG entries. The only 17// reliable discriminator is the payload's first bytes, which is why this 18// module sniffs rather than trusting the header. 19// 20// genealogy_id: microsoft_ico_cur_format 21// lineage_id: nx_ico_v1 22// license_tier: ORIGINAL 23 24import "nx_syscalls.nx" 25 26const NX_ICO_DIR_LEN: i64 = 6 27const NX_ICO_ENTRY_LEN: i64 = 16 28const NX_ICO_TYPE_ICON: i64 = 1 29const NX_ICO_TYPE_CUR: i64 = 2 30const NX_ICO_MAX_DIM: i64 = 256 31 32const NX_ICO_FLD_WIDTH: i64 = 0 33const NX_ICO_FLD_HEIGHT: i64 = 1 34const NX_ICO_FLD_COLORS: i64 = 2 35const NX_ICO_FLD_PLANES: i64 = 3 36const NX_ICO_FLD_BITS: i64 = 4 37const NX_ICO_FLD_BYTES: i64 = 5 38const NX_ICO_FLD_OFFSET: i64 = 6 39const NX_ICO_FLD_ISPNG: i64 = 7 40 41func nx_ico_at(d: *u8, i: i64) -> i64 { return (d[i] as i64) & 255 } 42 43func nx_ico_r16(d: *u8, o: i64) -> i64 { 44 return nx_ico_at(d, o) | (nx_ico_at(d, o + 1) << 8) 45} 46 47func nx_ico_r32(d: *u8, o: i64) -> i64 { 48 return nx_ico_at(d, o) | (nx_ico_at(d, o+1) << 8) 49 | (nx_ico_at(d, o+2) << 16) | (nx_ico_at(d, o+3) << 24) 50} 51 52func nx_ico_w16(o: *u8, p: i64, v: i64) -> i64 { 53 o[p] = (v & 255) as u8 54 o[p+1] = ((v >> 8) & 255) as u8 55 return 2 56} 57 58func nx_ico_w32(o: *u8, p: i64, v: i64) -> i64 { 59 o[p] = (v & 255) as u8 60 o[p+1] = ((v >> 8) & 255) as u8 61 o[p+2] = ((v >> 16) & 255) as u8 62 o[p+3] = ((v >> 24) & 255) as u8 63 return 4 64} 65 66// ===== the zero-means-256 encoding ================================ 67 68func nx_ico_dim_decode(b: i64) -> i64 { 69 if b == 0 { return NX_ICO_MAX_DIM } 70 return b & 255 71} 72 73func nx_ico_dim_encode(v: i64) -> i64 { 74 if v == NX_ICO_MAX_DIM { return 0 } 75 if v < 1 { return 0 - 1 } 76 if v > 255 { return 0 - 1 } 77 return v 78} 79 80// ===== payload sniffing =========================================== 81// 82// The directory cannot tell you this; only the bytes can. 83 84func nx_ico_is_png(d: *u8, n: i64, off: i64) -> i64 { 85 if off + 8 > n { return 0 } 86 if nx_ico_at(d, off) != 0x89 { return 0 } 87 if nx_ico_at(d, off+1) != 0x50 { return 0 } 88 if nx_ico_at(d, off+2) != 0x4e { return 0 } 89 if nx_ico_at(d, off+3) != 0x47 { return 0 } 90 return 1 91} 92 93// ===== write ====================================================== 94// 95// Two passes: the directory is sized from the entry count, then payloads are 96// appended and their offsets patched back in. Entry i's offset is only known 97// once every earlier payload has been placed. 98 99func nx_ico_write_begin(o: *u8, cap: i64, count: i64) -> i64 { 100 if count <= 0 { return 0 } 101 if count > 255 { return 0 } 102 let need: i64 = NX_ICO_DIR_LEN + count * NX_ICO_ENTRY_LEN 103 if cap < need { return 0 } 104 nx_ico_w16(o, 0, 0) // reserved 105 nx_ico_w16(o, 2, NX_ICO_TYPE_ICON) 106 nx_ico_w16(o, 4, count) 107 return need 108} 109 110func nx_ico_write_entry(o: *u8, cap: i64, index: i64, data_off: i64, 111 width: i64, height: i64, bits: i64, 112 payload: *u8, plen: i64) -> i64 { 113 if index < 0 { return 0 } 114 if plen < 0 { return 0 } 115 let w: i64 = nx_ico_dim_encode(width) 116 let h: i64 = nx_ico_dim_encode(height) 117 if w < 0 { return 0 } 118 if h < 0 { return 0 } 119 let ep: i64 = NX_ICO_DIR_LEN + index * NX_ICO_ENTRY_LEN 120 if ep + NX_ICO_ENTRY_LEN > cap { return 0 } 121 if data_off + plen > cap { return 0 } 122 123 o[ep] = w as u8 124 o[ep+1] = h as u8 125 o[ep+2] = 0 as u8 // colour count, 0 for >8bpp 126 o[ep+3] = 0 as u8 // reserved 127 nx_ico_w16(o, ep + 4, 1) // planes 128 nx_ico_w16(o, ep + 6, bits) 129 nx_ico_w32(o, ep + 8, plen) 130 nx_ico_w32(o, ep + 12, data_off) 131 132 var i: i64 = 0 133 while i < plen { o[data_off + i] = payload[i]; i = i + 1 } 134 return data_off + plen 135} 136 137// ===== read ======================================================= 138 139func nx_ico_count(d: *u8, n: i64) -> i64 { 140 if n < NX_ICO_DIR_LEN { return 0 - 1 } 141 if nx_ico_r16(d, 0) != 0 { return 0 - 1 } // reserved must be zero 142 let t: i64 = nx_ico_r16(d, 2) 143 if t != NX_ICO_TYPE_ICON { if t != NX_ICO_TYPE_CUR { return 0 - 1 } } 144 let c: i64 = nx_ico_r16(d, 4) 145 if c <= 0 { return 0 - 1 } 146 if n < NX_ICO_DIR_LEN + c * NX_ICO_ENTRY_LEN { return 0 - 1 } 147 return c 148} 149 150func nx_ico_read_entry(d: *u8, n: i64, index: i64, fld: *i64) -> i64 { 151 let c: i64 = nx_ico_count(d, n) 152 if c < 0 { return 0 } 153 if index < 0 { return 0 } 154 if index >= c { return 0 } 155 let ep: i64 = NX_ICO_DIR_LEN + index * NX_ICO_ENTRY_LEN 156 157 let bytes: i64 = nx_ico_r32(d, ep + 8) 158 let off: i64 = nx_ico_r32(d, ep + 12) 159 if bytes < 0 { return 0 } 160 if off < 0 { return 0 } 161 // the payload must lie inside the buffer -- an offset past the end is how 162 // a crafted icon walks a reader off the allocation 163 if off + bytes > n { return 0 } 164 if off < NX_ICO_DIR_LEN + c * NX_ICO_ENTRY_LEN { return 0 } 165 166 fld[NX_ICO_FLD_WIDTH] = nx_ico_dim_decode(nx_ico_at(d, ep)) 167 fld[NX_ICO_FLD_HEIGHT] = nx_ico_dim_decode(nx_ico_at(d, ep + 1)) 168 fld[NX_ICO_FLD_COLORS] = nx_ico_at(d, ep + 2) 169 fld[NX_ICO_FLD_PLANES] = nx_ico_r16(d, ep + 4) 170 fld[NX_ICO_FLD_BITS] = nx_ico_r16(d, ep + 6) 171 fld[NX_ICO_FLD_BYTES] = bytes 172 fld[NX_ICO_FLD_OFFSET] = off 173 fld[NX_ICO_FLD_ISPNG] = nx_ico_is_png(d, n, off) 174 return 1 175} 176 177// ===== pick the best entry for a target size ====================== 178// 179// Prefers an exact match, then the smallest entry at least as large as the 180// target, then the largest available. Returns the entry index, or -1. 181 182func nx_ico_best(d: *u8, n: i64, target: i64) -> i64 { 183 let c: i64 = nx_ico_count(d, n) 184 if c < 0 { return 0 - 1 } 185 let fld: *i64 = sys_mmap(128) as *i64 186 var best_up: i64 = 0 - 1 187 var best_up_w: i64 = 0 188 var best_any: i64 = 0 - 1 189 var best_any_w: i64 = 0 190 var i: i64 = 0 191 while i < c { 192 if nx_ico_read_entry(d, n, i, fld) == 1 { 193 let w: i64 = fld[NX_ICO_FLD_WIDTH] 194 if w == target { return i } 195 if w > target { 196 if best_up < 0 { best_up = i; best_up_w = w } else { 197 if w < best_up_w { best_up = i; best_up_w = w } 198 } 199 } 200 if best_any < 0 { best_any = i; best_any_w = w } else { 201 if w > best_any_w { best_any = i; best_any_w = w } 202 } 203 } 204 i = i + 1 205 } 206 if best_up >= 0 { return best_up } 207 return best_any 208}