#![allow(dead_code)]
use std::path::{Path, PathBuf};
pub fn brush_archive(name: &str) -> Vec<u8> {
use plist::{Dictionary, Uid, Value};
let mut settings = Dictionary::new();
settings.insert("name".into(), Value::Uid(Uid::new(2)));
let mut top = Dictionary::new();
top.insert("root".into(), Value::Uid(Uid::new(1)));
let mut root = Dictionary::new();
root.insert("$version".into(), Value::Integer(100_000.into()));
root.insert("$archiver".into(), Value::String("NSKeyedArchiver".into()));
root.insert(
"$objects".into(),
Value::Array(vec![
Value::String("$null".into()),
Value::Dictionary(settings),
Value::String(name.into()),
]),
);
root.insert("$top".into(), Value::Dictionary(top));
let mut out = Vec::new();
Value::Dictionary(root)
.to_writer_binary(&mut out)
.expect("binary plist");
out
}
pub fn brushset_plist(name: &str, uuids: &[&str]) -> Vec<u8> {
use plist::{Dictionary, Value};
let mut dict = Dictionary::new();
dict.insert("name".into(), Value::String(name.into()));
dict.insert(
"brushes".into(),
Value::Array(uuids.iter().map(|u| Value::String((*u).into())).collect()),
);
let mut out = Vec::new();
Value::Dictionary(dict)
.to_writer_binary(&mut out)
.expect("binary plist");
out
}
pub fn crc32(data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFF_FFFF;
for &b in data {
crc ^= b as u32;
for _ in 0..8 {
let mask = (crc & 1).wrapping_neg();
crc = (crc >> 1) ^ (0xEDB8_8320 & mask);
}
}
!crc
}
pub fn adler32(data: &[u8]) -> u32 {
let (mut a, mut b) = (1u32, 0u32);
for &byte in data {
a = (a + byte as u32) % 65521;
b = (b + a) % 65521;
}
(b << 16) | a
}
const GRAY: u8 = 0;
const RGBA: u8 = 6;
fn png_file(width: u32, height: u32, bit_depth: u8, color_type: u8, scanlines: &[u8]) -> Vec<u8> {
let len = u16::try_from(scanlines.len()).expect("scanlines fit one stored block");
let mut zlib = vec![0x78, 0x01, 1];
zlib.extend_from_slice(&len.to_le_bytes());
zlib.extend_from_slice(&(!len).to_le_bytes());
zlib.extend_from_slice(scanlines);
zlib.extend_from_slice(&adler32(scanlines).to_be_bytes());
let mut ihdr = Vec::new();
ihdr.extend_from_slice(&width.to_be_bytes());
ihdr.extend_from_slice(&height.to_be_bytes());
ihdr.extend_from_slice(&[bit_depth, color_type, 0, 0, 0]);
let mut png = b"\x89PNG\r\n\x1a\n".to_vec();
for (kind, data) in [(b"IHDR", ihdr), (b"IDAT", zlib), (b"IEND", Vec::new())] {
png.extend_from_slice(&(data.len() as u32).to_be_bytes());
let start = png.len();
png.extend_from_slice(kind);
png.extend_from_slice(&data);
let crc = crc32(&png[start..]);
png.extend_from_slice(&crc.to_be_bytes());
}
png
}
fn gray_scanlines(width: u32, height: u32, fill: u8) -> Vec<u8> {
let mut scanlines = Vec::new();
for _ in 0..height {
scanlines.push(0);
scanlines.extend(std::iter::repeat_n(fill, width as usize));
}
scanlines
}
pub fn gray_png(width: u32, height: u32, fill: u8) -> Vec<u8> {
png_file(width, height, 8, GRAY, &gray_scanlines(width, height, fill))
}
pub fn real_4x4_png() -> Vec<u8> {
gray_png(4, 4, 128)
}
fn bomb_png(w: u32, h: u32, bit_depth: u8, color_type: u8) -> Vec<u8> {
png_file(w, h, bit_depth, color_type, &gray_scanlines(4, 4, 128))
}
pub fn dimension_bomb_png(w: u32, h: u32) -> Vec<u8> {
bomb_png(w, h, 8, GRAY)
}
pub fn rgba_dimension_bomb_png(w: u32, h: u32, bit_depth: u8) -> Vec<u8> {
bomb_png(w, h, bit_depth, RGBA)
}
pub fn baseline_jpeg(width: u32, height: u32, components: u8) -> Vec<u8> {
partial_scan_jpeg(
width,
height,
&vec![0x11; usize::from(components)],
components,
)
}
pub fn partial_scan_jpeg(width: u32, height: u32, sampling: &[u8], scan: u8) -> Vec<u8> {
hand_written_jpeg(0xC0, width, height, sampling, scan)
}
pub fn progressive_jpeg(width: u32, height: u32, sampling: &[u8]) -> Vec<u8> {
let components = u8::try_from(sampling.len()).expect("a few components");
hand_written_jpeg(0xC2, width, height, sampling, components)
}
pub fn hand_written_jpeg(sof: u8, width: u32, height: u32, sampling: &[u8], scan: u8) -> Vec<u8> {
let components = u8::try_from(sampling.len()).expect("a few components");
assert!(matches!(components, 1 | 3), "grayscale or YCbCr only");
let progressive = sof == 0xC2;
let width = u16::try_from(width).expect("JPEG width is 16-bit");
let height = u16::try_from(height).expect("JPEG height is 16-bit");
let mut jpeg = vec![0xFF, 0xD8];
jpeg.extend_from_slice(&[0xFF, 0xDB, 0x00, 0x43, 0x00]);
jpeg.extend_from_slice(&[1; 64]);
jpeg.extend_from_slice(&[0xFF, sof, 0x00, 8 + 3 * components, 8]);
jpeg.extend_from_slice(&height.to_be_bytes());
jpeg.extend_from_slice(&width.to_be_bytes());
jpeg.push(components);
for (id, &factors) in (1..).zip(sampling) {
jpeg.extend_from_slice(&[id, factors, 0]);
}
for class in [0x00, 0x10] {
jpeg.extend_from_slice(&[0xFF, 0xC4, 0x00, 0x14, class, 1]);
jpeg.extend_from_slice(&[0; 15]);
jpeg.push(0);
}
jpeg.extend_from_slice(&[0xFF, 0xDA, 0x00, 6 + 2 * scan, scan]);
for id in 1..=scan {
jpeg.extend_from_slice(&[id, 0x00]);
}
let spectral_end = if progressive { 0 } else { 0x3F };
jpeg.extend_from_slice(&[0, spectral_end, 0]);
let bits_per_block = if progressive { 1 } else { 2 };
let blocks: u32 = sampling[..usize::from(scan)]
.iter()
.map(|&factors| u32::from(factors >> 4) * u32::from(factors & 0xF))
.sum();
let bits = bits_per_block * blocks;
let bytes = bits.div_ceil(8);
jpeg.extend(std::iter::repeat_n(0, bytes as usize - 1));
jpeg.push((0xFFu16 >> (bits - 8 * (bytes - 1))) as u8);
jpeg.extend_from_slice(&[0xFF, 0xD9]);
jpeg
}
pub fn depth_bomb_plist_xml(depth: usize) -> Vec<u8> {
let mut s = String::from(
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\
<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \
\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\
<plist version=\"1.0\">\n",
);
s.push_str(&"<array>".repeat(depth));
s.into_bytes()
}
pub fn shared_data_plist(count: usize, bytes_each: usize) -> Vec<u8> {
use plist::{Dictionary, Value};
let mut dict = Dictionary::new();
dict.insert("name".into(), Value::String("Shared data".into()));
dict.insert(
"brushes".into(),
Value::Array(vec![Value::String("a".into())]),
);
dict.insert(
"payload".into(),
Value::Array(vec![Value::Data(vec![b'x'; bytes_each]); count]),
);
let mut out = Vec::new();
Value::Dictionary(dict)
.to_writer_binary(&mut out)
.expect("binary plist");
out
}
pub const fn shared_array_values(levels: usize, fanout: usize) -> usize {
let mut values = 7;
let mut level = 0;
while level <= levels {
values += fanout.pow(level as u32);
level += 1;
}
values
}
pub fn shared_arrays_plist(levels: usize, fanout: usize) -> Vec<u8> {
fn marker(kind: u8, count: usize) -> Vec<u8> {
if count < 15 {
vec![kind | count as u8]
} else {
let mut out = vec![kind | 0xF, 0x12];
out.extend_from_slice(&u32::try_from(count).expect("count fits").to_be_bytes());
out
}
}
let string = |s: &str| {
let mut out = marker(0x50, s.len());
out.extend_from_slice(s.as_bytes());
out
};
let reference = |index: usize| u16::try_from(index).expect("reference fits").to_be_bytes();
let array = |refs: &[usize]| {
let mut out = marker(0xA0, refs.len());
for r in refs {
out.extend_from_slice(&reference(*r));
}
out
};
let dict = |pairs: &[(usize, usize)]| {
let mut out = marker(0xD0, pairs.len());
for (k, _) in pairs {
out.extend_from_slice(&reference(*k));
}
for (_, v) in pairs {
out.extend_from_slice(&reference(*v));
}
out
};
let top = 7 + levels;
let mut objects = vec![
dict(&[(1, 2), (3, 4), (6, top)]),
string("name"),
string("Shared arrays"),
string("brushes"),
array(&[5]),
string("a"),
string("payload"),
string("x"),
];
for below in 7..top {
objects.push(array(&vec![below; fanout]));
}
let mut out = b"bplist00".to_vec();
let mut offsets = Vec::new();
for object in &objects {
offsets.extend_from_slice(&(out.len() as u32).to_be_bytes());
out.extend_from_slice(object);
}
let table = out.len() as u64;
out.extend_from_slice(&offsets);
out.extend_from_slice(&[0; 6]);
out.extend_from_slice(&[4, 2]); out.extend_from_slice(&(objects.len() as u64).to_be_bytes());
out.extend_from_slice(&0u64.to_be_bytes()); out.extend_from_slice(&table.to_be_bytes());
out
}
pub fn zip_with(entries: &[(&str, &[u8])]) -> Vec<u8> {
let mut zip = Vec::new();
let mut directory = Vec::new();
for (path, bytes) in entries {
let offset = u32::try_from(zip.len()).expect("zip fits in 4 GiB");
let size = u32::try_from(bytes.len()).expect("entry fits in 4 GiB");
let mut fields = Vec::new();
for half in [10u16, 0, 0, 0, 0x21] {
fields.extend_from_slice(&half.to_le_bytes());
}
for word in [crc32(bytes), size, size] {
fields.extend_from_slice(&word.to_le_bytes());
}
fields.extend_from_slice(&(path.len() as u16).to_le_bytes());
fields.extend_from_slice(&0u16.to_le_bytes());
zip.extend_from_slice(b"PK\x03\x04");
zip.extend_from_slice(&fields);
zip.extend_from_slice(path.as_bytes());
zip.extend_from_slice(bytes);
directory.extend_from_slice(b"PK\x01\x02");
directory.extend_from_slice(&10u16.to_le_bytes()); directory.extend_from_slice(&fields);
directory.extend_from_slice(&[0; 2 + 2 + 2 + 4]);
directory.extend_from_slice(&offset.to_le_bytes());
directory.extend_from_slice(path.as_bytes());
}
let count = u16::try_from(entries.len()).expect("entry count fits in u16");
let directory_offset = zip.len() as u32;
let directory_size = directory.len() as u32;
zip.extend_from_slice(&directory);
zip.extend_from_slice(b"PK\x05\x06");
zip.extend_from_slice(&[0; 4]); zip.extend_from_slice(&count.to_le_bytes());
zip.extend_from_slice(&count.to_le_bytes());
zip.extend_from_slice(&directory_size.to_le_bytes());
zip.extend_from_slice(&directory_offset.to_le_bytes());
zip.extend_from_slice(&0u16.to_le_bytes()); zip
}
pub struct SampTip {
pub width: u32,
pub height: u32,
pub fill: u8,
pub corrupt: bool,
}
pub fn samp_abr(tips: &[SampTip]) -> Vec<u8> {
let mut payload = Vec::new();
for tip in tips {
let mut entry = Vec::new();
entry.extend_from_slice(&0u32.to_be_bytes());
for bound in [0, 0, tip.height as i32, tip.width as i32] {
entry.extend_from_slice(&bound.to_be_bytes());
}
entry.extend_from_slice(&8u16.to_be_bytes());
entry.push(u8::from(tip.corrupt));
entry.extend(std::iter::repeat_n(
tip.fill,
(tip.width * tip.height) as usize,
));
payload.extend_from_slice(&(entry.len() as u32).to_be_bytes());
payload.extend_from_slice(&entry);
payload.resize(payload.len().next_multiple_of(4), 0);
}
let mut file = Vec::new();
file.extend_from_slice(&6u16.to_be_bytes());
file.extend_from_slice(&2u16.to_be_bytes());
file.extend_from_slice(b"8BIMsamp");
file.extend_from_slice(&(payload.len() as u32).to_be_bytes());
file.extend_from_slice(&payload);
file
}
pub fn legacy_abr(tips: &[SampTip]) -> Vec<u8> {
let mut file = Vec::new();
file.extend_from_slice(&2u16.to_be_bytes());
file.extend_from_slice(&(tips.len() as u16).to_be_bytes());
for tip in tips {
let mut entry = Vec::new();
entry.extend_from_slice(&[0; 4 + 2 + 4 + 1 + 8]);
for bound in [0, 0, tip.height as i32, tip.width as i32] {
entry.extend_from_slice(&bound.to_be_bytes());
}
entry.extend_from_slice(&8u16.to_be_bytes());
entry.push(1);
for _ in 0..tip.height {
entry.extend_from_slice(&2u16.to_be_bytes());
}
if !tip.corrupt {
for _ in 0..tip.height {
entry.extend_from_slice(&[(1 - tip.width as i32) as u8, tip.fill]);
}
}
file.extend_from_slice(&2u16.to_be_bytes());
file.extend_from_slice(&(entry.len() as u32).to_be_bytes());
file.extend_from_slice(&entry);
}
file
}
pub const TIP_A: &str = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
pub const TIP_B: &str = "a1b2c3d4-e5f6-7890-abcd-ef1234567891";
pub const TIP_C: &str = "a1b2c3d4-e5f6-7890-abcd-ef1234567892";
pub enum DescPreset<'a> {
Sampled(&'a str, &'a str),
Computed(&'a str),
}
pub fn desc_abr(records: &[(&str, bool)], presets: &[DescPreset]) -> Vec<u8> {
fn u32_be(buf: &mut Vec<u8>, value: u32) {
buf.extend_from_slice(&value.to_be_bytes());
}
fn ostype(buf: &mut Vec<u8>, key: &[u8; 4]) {
u32_be(buf, 0);
buf.extend_from_slice(key);
}
fn named(buf: &mut Vec<u8>, key: &[u8]) {
u32_be(buf, key.len() as u32);
buf.extend_from_slice(key);
}
fn text(buf: &mut Vec<u8>, value: &str) {
buf.extend_from_slice(b"TEXT");
let units: Vec<u16> = value.encode_utf16().collect();
u32_be(buf, units.len() as u32 + 1);
for unit in units.iter().chain([&0]) {
buf.extend_from_slice(&unit.to_be_bytes());
}
}
fn object(buf: &mut Vec<u8>, class_id: &[u8], item_count: u32) {
buf.extend_from_slice(b"Objc");
u32_be(buf, 1);
buf.extend_from_slice(&0u16.to_be_bytes());
named(buf, class_id);
u32_be(buf, item_count);
}
fn block(file: &mut Vec<u8>, kind: &[u8; 4], payload: &[u8]) {
file.extend_from_slice(b"8BIM");
file.extend_from_slice(kind);
u32_be(file, payload.len() as u32);
file.extend_from_slice(payload);
file.resize(file.len().next_multiple_of(4), 0);
}
let mut samp = Vec::new();
for &(uuid, readable) in records {
let (side, depth): (u32, u16) = (4, if readable { 8 } else { 0x20 });
let mut body = vec![b'$'];
body.extend_from_slice(uuid.as_bytes());
body.push(0);
body.extend_from_slice(&[0; 200]);
for bound in [0, 0, side, side] {
u32_be(&mut body, bound);
}
body.extend_from_slice(&depth.to_be_bytes());
body.push(0);
body.extend(std::iter::repeat_n(0x80, (side * side) as usize));
u32_be(&mut samp, body.len() as u32);
samp.extend_from_slice(&body);
samp.resize(samp.len().next_multiple_of(4), 0);
}
let mut desc = Vec::new();
u32_be(&mut desc, 16);
u32_be(&mut desc, 1);
desc.extend_from_slice(&0u16.to_be_bytes());
ostype(&mut desc, b"null");
u32_be(&mut desc, 1);
ostype(&mut desc, b"Brsh");
desc.extend_from_slice(b"VlLs");
u32_be(&mut desc, presets.len() as u32);
for preset in presets {
object(&mut desc, b"brushPreset", 2);
ostype(&mut desc, b"Nm ");
match preset {
DescPreset::Sampled(name, uuid) => {
text(&mut desc, name);
named(&mut desc, b"sampledData");
text(&mut desc, uuid);
}
DescPreset::Computed(name) => {
text(&mut desc, name);
ostype(&mut desc, b"Brsh");
object(&mut desc, b"computedBrush", 1);
ostype(&mut desc, b"Dmtr");
desc.extend_from_slice(b"UntF#Pxl");
desc.extend_from_slice(&30f64.to_be_bytes());
}
}
}
let mut file = Vec::new();
file.extend_from_slice(&10u16.to_be_bytes());
file.extend_from_slice(&2u16.to_be_bytes());
if !records.is_empty() {
block(&mut file, b"samp", &samp);
}
block(&mut file, b"desc", &desc);
file
}
pub fn corpus_files(directory: &Path, files: &mut Vec<PathBuf>) {
for entry in std::fs::read_dir(directory).expect("read corpus directory") {
let path = entry.unwrap().path();
if path.is_dir() {
corpus_files(&path, files);
} else if path
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| matches!(ext.to_lowercase().as_str(), "abr" | "brush" | "brushset"))
{
files.push(path);
}
}
}