From 8982f40d00c365ee2ac43e715bd032cf850d6469 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:25:44 +0800 Subject: [PATCH] refactor(godot): share PCK header facts across inspections --- src/artifacts/DesktopFileInspection.ts | 29 ++++++++++++------- src/artifacts/GodotPckArtifactReader.ts | 10 ++++--- src/domain/godotPckFormat.ts | 21 ++++++++++++++ .../filesystem/desktopApplication.test.ts | 14 +++++++-- 4 files changed, 56 insertions(+), 18 deletions(-) diff --git a/src/artifacts/DesktopFileInspection.ts b/src/artifacts/DesktopFileInspection.ts index d82e9d0dd2..1669cd53fe 100644 --- a/src/artifacts/DesktopFileInspection.ts +++ b/src/artifacts/DesktopFileInspection.ts @@ -1,3 +1,7 @@ +import { + hasGodotPckHeader, + readGodotPckHeaderFacts, +} from "../domain/godotPckFormat.js"; import type { FileHandle } from "node:fs/promises"; import { createHash } from "node:crypto"; import { setImmediate } from "node:timers/promises"; @@ -108,20 +112,22 @@ export const inspectDesktopFile = async ( raw_version: metadataPrefix.rawVersion, }, }); - if (prefix.length >= 20 && prefix.toString("latin1", 0, 4) === "GDPC") + if (prefix.length >= 20 && hasGodotPckHeader(prefix)) { + const pckHeader = readGodotPckHeaderFacts(prefix); fact({ kind: "godot-pck-header", offset: 0, value: { - pack_version: prefix.readUInt32LE(4), + pack_version: pckHeader.formatVersion, engine_version: [ - prefix.readUInt32LE(8), - prefix.readUInt32LE(12), - prefix.readUInt32LE(16), + pckHeader.engineMajor, + pckHeader.engineMinor, + pckHeader.enginePatch, ], placement: "standalone", }, }); + } if ( prefix.length >= 16 && prefix.toString("latin1", 0, 4) === "FORM" && @@ -320,21 +326,22 @@ const inspectPckTrailer = async ( "Embedded PCK trailer declares an invalid pack extent", ); const offset = size - 12 - Number(packSize); - const header = await read(offset, 20); - if (header.toString("latin1", 0, 4) !== "GDPC") + const bytes = await read(offset, 20); + if (!hasGodotPckHeader(bytes)) throw new ArtifactReaderFailure( "format", "Embedded PCK trailer does not locate a pack header", ); + const header = readGodotPckHeaderFacts(bytes); fact({ kind: "godot-pck-header", offset, value: { - pack_version: header.readUInt32LE(4), + pack_version: header.formatVersion, engine_version: [ - header.readUInt32LE(8), - header.readUInt32LE(12), - header.readUInt32LE(16), + header.engineMajor, + header.engineMinor, + header.enginePatch, ], placement: "embedded-trailer", }, diff --git a/src/artifacts/GodotPckArtifactReader.ts b/src/artifacts/GodotPckArtifactReader.ts index a0d63f59d4..68dddb9846 100644 --- a/src/artifacts/GodotPckArtifactReader.ts +++ b/src/artifacts/GodotPckArtifactReader.ts @@ -1,3 +1,4 @@ +import { readGodotPckHeaderFacts } from "../domain/godotPckFormat.js"; import { artifactExpansionBudgetBytes } from "../domain/artifactExpansionBudget.js"; import type { FileHandle } from "node:fs/promises"; import { setImmediate } from "node:timers/promises"; @@ -301,7 +302,8 @@ const readGodotPckHeader = async ( throw formatFailure("no standalone header or executable trailer"); const { start, end, embedded } = location; const fixed = await readPckBytes(handle, start, 40, end, signal); - const version = fixed.readUInt32LE(4); + const header = readGodotPckHeaderFacts(fixed); + const version = header.formatVersion; if (![2, 3, 4].includes(version)) throw new ArtifactReaderFailure( "unavailable", @@ -343,9 +345,9 @@ const readGodotPckHeader = async ( return { kind: "godot-pck", format_version: version, - engine_major: fixed.readUInt32LE(8), - engine_minor: fixed.readUInt32LE(12), - engine_patch: fixed.readUInt32LE(16), + engine_major: header.engineMajor, + engine_minor: header.engineMinor, + engine_patch: header.enginePatch, pack_start: start, pack_end: end, embedded, diff --git a/src/domain/godotPckFormat.ts b/src/domain/godotPckFormat.ts index 2401d61b95..23224271a0 100644 --- a/src/domain/godotPckFormat.ts +++ b/src/domain/godotPckFormat.ts @@ -8,3 +8,24 @@ export const hasGodotPckHeader = (bytes: Uint8Array): boolean => bytes[1] === 0x44 && bytes[2] === 0x50 && bytes[3] === 0x43; + +/** Common declared fields; support and pack extent admission belong to callers. */ +export interface GodotPckHeaderFacts { + readonly formatVersion: number; + readonly engineMajor: number; + readonly engineMinor: number; + readonly enginePatch: number; +} + +/** Decode declared fields after the caller admits a complete 20-byte header. */ +export const readGodotPckHeaderFacts = ( + bytes: Uint8Array, +): GodotPckHeaderFacts => { + const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); + return { + formatVersion: view.getUint32(4, true), + engineMajor: view.getUint32(8, true), + engineMinor: view.getUint32(12, true), + enginePatch: view.getUint32(16, true), + }; +}; diff --git a/tests/boundary/filesystem/desktopApplication.test.ts b/tests/boundary/filesystem/desktopApplication.test.ts index 03254e242f..59e9c56708 100644 --- a/tests/boundary/filesystem/desktopApplication.test.ts +++ b/tests/boundary/filesystem/desktopApplication.test.ts @@ -192,6 +192,8 @@ it("reads sparse embedded PCK ranges without hashing or retaining a large execut pck.write("GDPC"); pck.writeUInt32LE(3, 4); pck.writeUInt32LE(4, 8); + pck.writeUInt32LE(0x80000001, 12); + pck.writeUInt32LE(0xffffffff, 16); const trailer = Buffer.alloc(12); trailer.writeBigUInt64LE(BigInt(pck.length)); trailer.write("GDPC", 8); @@ -203,9 +205,15 @@ it("reads sparse embedded PCK ranges without hashing or retaining a large execut expect(report.candidates.map((row) => row.family)).toContain("godot"); expect(report.coverage.bytes_read).toBeLessThan(4096); expect(report.files[0]?.bytes).toBe(size); - expect(report.files[0]?.facts).toContainEqual( - expect.objectContaining({ kind: "godot-pck-header", offset: size - 32 }), - ); + expect(report.files[0]?.facts).toContainEqual({ + kind: "godot-pck-header", + offset: size - 32, + value: { + pack_version: 3, + engine_version: [4, 0x80000001, 0xffffffff], + placement: "embedded-trailer", + }, + }); }); it("does not promote filenames, arbitrary pak files or symlink targets into byte facts", async () => {