Files
rea/tests/fixtures/godotPck.ts
T
TheArchitect 5fa4defcbc feat(artifacts): inspect and extract Godot PCK with retained facts (#3634)
* feat(artifacts): read Godot PCK packs

Add a Godot Engine PCK artifact reader for standalone packs and packs
embedded behind an executable trailer (formats 2 and 3). Members are
uncompressed byte ranges verified against their declared MD5 on read.

Encrypted directories, sparse bundles, patch removal entries, unknown
flags and format 1 fail closed as unavailable; encrypted members are
reported without being read. inspect-artifact and extract-artifact
recognize standalone packs by their GDPC magic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artifacts): bound Godot PCK metadata and own member streams

Cap member paths at 4096 bytes and the retained directory at 64 MiB
before allocation. Read members with positional chunk reads through
pipeline so cancellation is typed, abandoned consumers release their
source, and the shared descriptor stays reader-owned. Retain the root
descriptor for godot-pck inventory, as for ZIP and universal Mach-O.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(artifacts): complete Godot PCK facts, integrity and selected views

* fix(artifacts): preserve atomic-root inventory Evidence

* fix(artifacts): bound aggregate PCK member expansion

---------

Co-authored-by: Samucahub <151155051+Samucahub@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-20 09:53:53 +08:00

106 lines
3.0 KiB
TypeScript

import { createHash } from "node:crypto";
export interface GodotPckMember {
readonly path: string;
readonly data: string;
readonly flags?: number;
readonly md5?: Buffer;
readonly size?: bigint;
readonly offset?: bigint;
}
export const pckU32 = (value: number): Buffer => {
const bytes = Buffer.alloc(4);
bytes.writeUInt32LE(value);
return bytes;
};
export const pckU64 = (value: bigint | number): Buffer => {
const bytes = Buffer.alloc(8);
bytes.writeBigUInt64LE(BigInt(value));
return bytes;
};
const pad4 = (bytes: Buffer): Buffer =>
Buffer.concat([bytes, Buffer.alloc((4 - (bytes.length % 4)) % 4)]);
/**
* Build a pack with the layout Godot's exporter writes (and GDRE Tools
* reproduces): format 2 places the directory after a 96-byte header and
* members after it; format 3 stores members first and a pack-relative
* directory offset.
*/
export const buildGodotPck = (
version: 2 | 3 | 4,
members: readonly GodotPckMember[],
options: { packFlags?: number; fileCount?: number } = {},
): Buffer => {
const data: Buffer[] = [];
const offsets: number[] = [];
let dataBytes = 0;
for (const member of members) {
offsets.push(dataBytes);
const bytes = pad4(Buffer.from(member.data));
data.push(bytes);
dataBytes += bytes.length;
}
const directory = Buffer.concat([
pckU32(options.fileCount ?? members.length),
...members.flatMap((member, index) => {
const path = pad4(Buffer.from(member.path));
return [
pckU32(path.length),
path,
pckU64(member.offset ?? BigInt(offsets[index] ?? 0)),
pckU64(member.size ?? BigInt(Buffer.byteLength(member.data))),
member.md5 ?? createHash("md5").update(member.data).digest(),
pckU32(member.flags ?? 0),
];
}),
]);
const headerBytes = 96;
const fileBase = version === 2 ? headerBytes + directory.length : headerBytes;
const header = Buffer.concat([
Buffer.from("GDPC"),
pckU32(version),
pckU32(4),
pckU32(6),
pckU32(1),
pckU32(options.packFlags ?? (version >= 3 ? 2 : 0)),
pckU64(fileBase),
pckU64(version >= 3 ? headerBytes + dataBytes : 0),
]);
const paddedHeader = Buffer.concat([
header,
Buffer.alloc(headerBytes - header.length),
]);
return version === 2
? Buffer.concat([paddedHeader, directory, ...data])
: Buffer.concat([paddedHeader, ...data, directory]);
};
export const embedGodotPck = (pack: Buffer): Buffer =>
Buffer.concat([
Buffer.from("\x7fELF fake engine bytes"),
pack,
pckU64(pack.length),
Buffer.from("GDPC"),
]);
/** Repeated physical ranges are valid aliases but multiply plaintext I/O per path. */
export const buildAliasedGodotPck = (
version: 2 | 3 | 4,
count: number,
entryBytes = 256 * 1024,
flags = 0,
): Buffer =>
buildGodotPck(
version,
Array.from({ length: count }, (_, index) => ({
path: `res://aliases/${String(index)}.bin`,
data: index === 0 ? "x".repeat(entryBytes) : "",
offset: 0n,
size: BigInt(entryBytes),
md5: Buffer.alloc(16),
flags,
})),
);