storebound/tests/build-fixtures.ts

65 lines
4.2 KiB
TypeScript

// Real (tiny) PNG, ZIP, IPA and AAB files for the build, signing and originality tests. Sanitized: example.com ids only.
import { deflateRawSync, deflateSync } from 'node:zlib';
import { encodeProtoManifest } from '../cli/lib/gradle';
/** A real PNG (8 bit RGBA, RGB or grey) from a pixel function. filter 1 writes Sub-filtered rows. */
export function encodePng(w: number, h: number, px: (x: number, y: number) => [number, number, number, number?], o: { colorType?: 0 | 2 | 6; filter?: 0 | 1 } = {}): Buffer {
const ct = o.colorType ?? 6, ch = ct === 0 ? 1 : ct === 2 ? 3 : 4;
const stride = w * ch + 1;
const raw = Buffer.alloc(stride * h);
for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const [r, g, b, a = 255] = px(x, y);
const v = ct === 0 ? [Math.round(0.299 * r + 0.587 * g + 0.114 * b)] : ct === 2 ? [r, g, b] : [r, g, b, a];
v.forEach((c, i) => { raw[y * stride + 1 + x * ch + i] = c; });
}
if (o.filter === 1) {
raw[y * stride] = 1;
for (let i = w * ch; i > ch; i--) raw[y * stride + i] = (raw[y * stride + i]! - raw[y * stride + i - ch]! + 256) & 255;
}
}
const table = Array.from({ length: 256 }, (_, n) => { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; return c >>> 0; });
const crc = (b: Buffer) => { let c = 0xffffffff; for (const x of b) c = table[(c ^ x) & 255]! ^ (c >>> 8); return (c ^ 0xffffffff) >>> 0; };
const chunk = (type: string, data: Buffer) => {
const len = Buffer.alloc(4); len.writeUInt32BE(data.length);
const td = Buffer.concat([Buffer.from(type, 'latin1'), data]);
const c = Buffer.alloc(4); c.writeUInt32BE(crc(td));
return Buffer.concat([len, td, c]);
};
const ihdr = Buffer.alloc(13); ihdr.writeUInt32BE(w, 0); ihdr.writeUInt32BE(h, 4); ihdr[8] = 8; ihdr[9] = ct;
return Buffer.concat([Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]), chunk('IHDR', ihdr), chunk('IDAT', deflateSync(raw)), chunk('IEND', Buffer.alloc(0))]);
}
/** A ZIP archive (deflated entries, or stored with store: true). */
export function makeZip(entries: { name: string; data: Buffer | string; store?: boolean }[]): Buffer {
const locals: Buffer[] = [], central: Buffer[] = [];
let off = 0;
for (const e of entries) {
const data = Buffer.from(e.data), comp = e.store ? data : deflateRawSync(data), name = Buffer.from(e.name, 'utf8');
const lh = Buffer.alloc(30); lh.writeUInt32LE(0x04034b50, 0); lh.writeUInt16LE(20, 4); lh.writeUInt16LE(e.store ? 0 : 8, 8);
lh.writeUInt32LE(comp.length, 18); lh.writeUInt32LE(data.length, 22); lh.writeUInt16LE(name.length, 26);
const ch = Buffer.alloc(46); ch.writeUInt32LE(0x02014b50, 0); ch.writeUInt16LE(20, 4); ch.writeUInt16LE(20, 6); ch.writeUInt16LE(e.store ? 0 : 8, 10);
ch.writeUInt32LE(comp.length, 20); ch.writeUInt32LE(data.length, 24); ch.writeUInt16LE(name.length, 28); ch.writeUInt32LE(off, 42);
locals.push(lh, name, comp); central.push(ch, name);
off += 30 + name.length + comp.length;
}
const cd = Buffer.concat(central);
const end = Buffer.alloc(22); end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(entries.length, 8); end.writeUInt16LE(entries.length, 10);
end.writeUInt32LE(cd.length, 12); end.writeUInt32LE(off, 16);
return Buffer.concat([...locals, cd, end]);
}
/** An IPA with the given Info.plist bytes. */
export const makeIpa = (infoPlist: Buffer | string, app = 'Example.app'): Buffer => makeZip([
{ name: 'Payload/', data: '', store: true },
{ name: `Payload/${app}/Example`, data: Buffer.alloc(2048, 7) },
{ name: `Payload/${app}/Info.plist`, data: infoPlist },
]);
/** An AAB with an aapt2 proto manifest. */
export const makeAab = (pkg: string, versionCode: number, versionName = '1.0.0', signed = true): Buffer => makeZip([
{ name: 'BundleConfig.pb', data: Buffer.from([8, 1]) },
{ name: 'base/manifest/AndroidManifest.xml', data: encodeProtoManifest([{ name: 'manifest', attrs: { package: pkg, versionCode: String(versionCode), versionName } }, { name: 'uses-sdk', attrs: { minSdkVersion: '24', targetSdkVersion: '36' } }]) },
{ name: 'base/dex/classes.dex', data: Buffer.alloc(512, 1) },
...(signed ? [{ name: 'META-INF/UPLOAD.SF', data: 'sf' }, { name: 'META-INF/UPLOAD.RSA', data: 'rsa' }] : []),
]);