| 237 | }; |
| 238 | |
| 239 | function guessByNullPattern(u8: Uint8Array, size = u8.length): string | null { |
| 240 | if (size < 64) return null; |
| 241 | |
| 242 | const n = [0, 0, 0, 0]; // n[0..3] = count of nulls at pos % 4 |
| 243 | |
| 244 | for (let i = 0; i < size; i++) { |
| 245 | if (u8[i] === 0) n[i & 3]++; |
| 246 | } |
| 247 | const total = n[0] + n[1] + n[2] + n[3]; |
| 248 | |
| 249 | if (total < size * 0.07) return null; |
| 250 | |
| 251 | const density = total / size; |
| 252 | |
| 253 | // UTF-32 — expect ~75% nulls (even with many CJK chars still ~70%+) |
| 254 | if (u8.length % 4 === 0 && density > 0.54) { |
| 255 | const laneSize = size / 4; |
| 256 | const mostlyNull = laneSize * 0.65; |
| 257 | const mostlyText = laneSize * 0.35; |
| 258 | |
| 259 | if (n[0] < mostlyText && n[1] > mostlyNull && n[2] > mostlyNull && n[3] > mostlyNull) return "utf-32le"; |
| 260 | if (n[0] > mostlyNull && n[1] > mostlyNull && n[2] > mostlyNull && n[3] < mostlyText) return "utf-32be"; |
| 261 | } |
| 262 | |
| 263 | // UTF-16 — expect ~25–50% nulls depending on script |
| 264 | if (u8.length % 2 === 0 && density > 0.1) { |
| 265 | const even = n[0] + n[2]; |
| 266 | const odd = n[1] + n[3]; |
| 267 | |
| 268 | if (even > odd * 4.2) return "utf-16be"; |
| 269 | if (odd > even * 4.2) return "utf-16le"; |
| 270 | if (even > odd * 2.1 && density > 0.2) return "utf-16be"; |
| 271 | if (odd > even * 2.1 && density > 0.2) return "utf-16le"; |
| 272 | } |
| 273 | |
| 274 | return null; |
| 275 | } |
| 276 | |
| 277 | const validatesHeuristicEncoding = (encoding: string, data: Uint8Array): boolean => { |
| 278 | try { |