mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
5fa4defcbc
* 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>
106 lines
3.0 KiB
TypeScript
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,
|
|
})),
|
|
);
|