Files
reverse-engineering-anything/tests/boundary/mcp/unityFsMcp.test.ts
T
blankymandgensokyo-dev e78ac1a55b feat(artifacts): read UnityFS AssetBundle containers (#7699)
* feat(artifacts): read UnityFS AssetBundle containers

inspect-artifact and extract-artifact now recognize the UnityFS signature
and treat format versions 6 through 9 as containers. Header facts (format
version, player/engine strings, declared size, dataflags, blocks-info
bounds and the declared 16-byte data hash) are retained alongside a
directory of CAB-* and .resS members. Member bytes stream on demand:
only the blocks covering the requested member are read and decompressed
with bounded expansion and cancellation.

Bundles declaring asset-bundle encryption keep header facts and report
unknown directory coverage. LZMA blocks-info reports unknown coverage;
LZMA member reads fail as unavailable while the directory stays
readable. Malformed counts, out-of-stream members and declared sizes
beyond the file fail as format errors. UnityFS classifies by bytes in
root detection, inventory graph construction and desktop inspection,
which emits a unityfs-header fact.

Validation: tsc --noEmit, oxlint (no new findings on touched files),
17 reader tests covering LZ4HC/uncompressed tables, blocks-info at end,
format 6 without padding alignment, multi-block members, malformed
input, unsupported compression, cancellation and abandoned streams;
full artifacts/domain/conformance suites pass. Verified against one
real YooAsset bundle (Unity 2021.3.32f1, format 8, LZ4HC): the
streamed SerializedFile contained its managed assembly byte-identical
to a UnityPy extraction. Other producer variants unverified.

* fix(unityfs): own bounded member reads and preserve partial evidence

* refactor(unityfs): clarify producer revision flag decisions

* fix(artifacts): preserve coverage authority and bound UnityFS block work

---------

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-28 16:47:39 +08:00

100 lines
3.9 KiB
TypeScript

import { readFile, rm, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { artifactInspectionResultSchema } from "../../../src/domain/artifactInspection.js";
import { artifactExtractionResultSchema } from "../../../src/domain/artifactGraph.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { buildUnityFs } from "../../fixtures/unityfs.js";
it.each(["full", "compact"] as const)(
"retains UnityFS facts and unsupported content through compiled MCP (%s)",
async (profile) => {
const root = await createTestTempDirectory("rea-unity-mcp-");
const path = join(root, "bundle");
const payload = Buffer.from("serialized bytes");
await writeFile(path, buildUnityFs([{ path: "CAB", data: payload }]));
const { call } = await connectLocalToolsMcp({
REA_MCP_INPUT_SCHEMA_PROFILE: profile,
});
let outputRoot: string | undefined;
try {
expect((await call("open_binary", { path })).isError).not.toBe(true);
const reply = await call("inspect_artifact", {});
expect(reply.isError, JSON.stringify(reply)).not.toBe(true);
const inspection = artifactInspectionResultSchema.parse(
parseEvidence(reply.structuredContent).normalized_result,
);
expect(
inspection.substeps[0]!.evidence.normalized_result.occurrences,
).toContainEqual(
expect.objectContaining({
logical_path: "CAB",
hash_status: "verified",
source_location: null,
}),
);
const extracted = await call("extract_artifact", {});
expect(extracted.isError, JSON.stringify(extracted)).not.toBe(true);
const result = artifactExtractionResultSchema.parse(
parseEvidence(extracted.structuredContent).normalized_result,
);
outputRoot = result.output_root;
expect((await readFile(join(outputRoot, "CAB"))).equals(payload)).toBe(
true,
);
await call("close_binary", {});
await writeFile(
path,
buildUnityFs([{ path: "CAB", data: payload }], {
engineVersion: "0.0.0",
flags: 0x243,
}),
);
await call("open_binary", { path });
const unknownReply = await call("inspect_artifact", {});
expect(unknownReply.isError, JSON.stringify(unknownReply)).not.toBe(true);
const unknown = artifactInspectionResultSchema.parse(
parseEvidence(unknownReply.structuredContent).normalized_result,
).substeps[0]!.evidence.normalized_result;
expect(unknown.coverage).toMatchObject({ status: "unknown" });
expect(unknown.occurrences).toMatchObject([
{
format_metadata: {
directory_status: "unsupported-layout",
block_count: null,
data_start: null,
},
},
]);
await call("close_binary", {});
await writeFile(
path,
buildUnityFs([{ path: "CAB-lzma", data: payload }], {
blockCompression: 1,
}),
);
await call("open_binary", { path });
const partialReply = await call("inspect_artifact", {});
expect(partialReply.isError, JSON.stringify(partialReply)).not.toBe(true);
const partial = artifactInspectionResultSchema.parse(
parseEvidence(partialReply.structuredContent).normalized_result,
).substeps[0]!.evidence.normalized_result;
expect(partial.coverage).toMatchObject({ status: "unknown" });
expect(partial.occurrences).toContainEqual(
expect.objectContaining({
logical_path: "CAB-lzma",
hash_status: "unavailable",
artifact_id: null,
}),
);
await call("close_binary", {});
} finally {
if (outputRoot !== undefined)
await rm(outputRoot, { recursive: true, force: true });
}
},
60_000,
);