fix(il2cpp): accept names in native type-reference views

This commit is contained in:
morluto
2026-10-24 16:33:40 +08:00
parent 8d72e2d75e
commit 172503355d
4 changed files with 126 additions and 7 deletions
+5 -4
View File
@@ -62,10 +62,11 @@ Collections are `sections`, `images`, `types`, `methods`, `fields`,
`generic_method_instances` and `generic_method_issues`. Every returned row includes
its source `index`, scoped by artifact SHA-256 and collection. All collections
accept batch `indices` and `fields`. Exact `names` filtering is supported for
`sections`, `images`, `types`, `methods`, `fields`, `properties`, `events` and
`parameters`; native `return_types`, `field_types` and `parameter_types` rows
are selected by source indices (or `method_indices` for `parameter_types`),
not rendered names. Types also accept `image_indices`, `namespaces` and
`sections`, `images`, `types`, `methods`, `fields`, `properties`, `events`,
`parameters`, `return_types`, `field_types`, `parameter_types` and
`generic_method_instances`. Native type names match the retained rendered display value exactly; every same-name row
keeps its source index, and an unavailable (`null`) name never matches a string.
Types also accept `image_indices`, `namespaces` and
`declaring_types`; methods, fields, properties and events accept
`declaring_types`. Parameters accept exact `method_indices`, so a batch of
method indices retrieves their parameters without reading the entire parameter
File diff suppressed because one or more lines are too long
@@ -227,13 +227,13 @@ export const il2cppMetadataViewRequestSchema = z.discriminatedUnion(
}),
...(["return_types", "field_types"] as const).map((collection) =>
z.strictObject({
...indexPage,
...page,
collection: z.literal(collection),
fields: z.array(typeReferenceRowSchema.keyof()).optional(),
}),
),
z.strictObject({
...indexPage,
...page,
collection: z.literal("parameter_types"),
// Owners come from the retained parameters collection.
method_indices: z.array(indexSchema).optional(),
+118
View File
@@ -46,6 +46,15 @@ import {
environmentStateCaptureFixture,
} from "../../fixtures/analysisView.js";
import { createTestBinarySession } from "../../fixtures/binarySession.js";
import { createLinkedIl2cppMetadataFixture } from "../../fixtures/il2cpp/metadata.js";
import {
createIl2cppBinaryFixture,
FIXTURE_RETURN_TYPE_NAME,
} from "../../fixtures/il2cpp/binary.js";
import {
analysisViewResultSchema,
il2cppMetadataSummaryViewResultSchema,
} from "../../../src/domain/analysisView/analysisView.js";
import { BINARY_LAYOUT_TEST_PROVIDER } from "../../fixtures/binaryDiagnostics/layout.js";
import { wasmArtifactFixture } from "../../fixtures/wasm/artifact.js";
import { evmInterfaceFixture } from "../../fixtures/evm/interface.js";
@@ -86,6 +95,115 @@ const connect = async (
return { client, session };
};
it("selects every IL2CPP native type reference by rendered name with stable row identity", async () => {
const { client } = await connect();
const root = await createTestTempDirectory("rea-il2cpp-native-names-");
const path = join(root, "global-metadata.dat");
const binaryPath = join(root, "GameAssembly.dylib");
await writeFile(binaryPath, createIl2cppBinaryFixture());
const inspect = async (version: 27 | 31) => {
await writeFile(path, createLinkedIl2cppMetadataFixture({ version }).bytes);
const inspected = await client.callTool({
name: "inspect_il2cpp_metadata",
arguments: { path, binary_path: binaryPath },
});
expect(inspected.isError, JSON.stringify(inspected)).not.toBe(true);
const summary = il2cppMetadataSummaryViewResultSchema.parse(
parseEvidence(inspected.structuredContent).normalized_result,
);
return {
kind: "retained-evidence",
evidence_id: summary.parent_evidence_id,
};
};
let source = await inspect(31);
const select = async (
collection: "return_types" | "field_types" | "parameter_types",
filters: Record<string, unknown>,
) => {
const response = await client.callTool({
name: "inspect_analysis_view",
arguments: {
source,
view: { kind: "il2cpp-metadata", collection, ...filters },
},
});
expect(response.isError, JSON.stringify(response)).not.toBe(true);
const page = analysisViewResultSchema.parse(
parseEvidence(response.structuredContent).normalized_result,
);
if (page.kind !== "il2cpp-metadata")
throw new Error("Expected IL2CPP page");
return page;
};
for (const [collection, count] of [
["return_types", 5],
["field_types", 2],
["parameter_types", 1],
] as const) {
const names = [FIXTURE_RETURN_TYPE_NAME];
const expected = Array.from({ length: count }, (_, index) => ({
index,
type: 0,
name: FIXTURE_RETURN_TYPE_NAME,
}));
const unfiltered = await select(collection, {});
expect(unfiltered.items).toEqual(expected);
const all = await select(collection, { names });
expect(all.items).toEqual(expected);
expect(all.coverage).toMatchObject({ total: count, exhausted: true });
const selected = await select(collection, {
names,
indices: [count - 1, 0, count - 1],
offset: 1,
limit: 1,
fields: ["type"],
});
expect(selected.items).toEqual(
count === 1 ? [] : [{ index: count - 1, type: 0 }],
);
expect(selected.coverage).toMatchObject({
total: Math.min(count, 2),
exhausted: true,
});
for (const missing of [[], ["absent"], ["system.Größe<int>[]"]]) {
const empty = await select(collection, { names: missing });
expect(empty.items).toEqual([]);
expect(empty.coverage).toMatchObject({ total: 0, exhausted: true });
}
}
const owned = await select("parameter_types", {
names: [FIXTURE_RETURN_TYPE_NAME],
method_indices: [2],
});
expect(owned.items).toEqual([
{ index: 0, type: 0, name: FIXTURE_RETURN_TYPE_NAME },
]);
const otherOwner = await select("parameter_types", {
names: [FIXTURE_RETURN_TYPE_NAME],
method_indices: [0],
});
expect(otherOwner.items).toEqual([]);
// Version 27 intentionally retains unavailable native display names as null.
source = await inspect(27);
for (const collection of [
"return_types",
"field_types",
"parameter_types",
] as const) {
const unfiltered = await select(collection, {});
expect(unfiltered.items.length).toBeGreaterThan(0);
expect(
unfiltered.items.every((row) => "name" in row && row.name === null),
).toBe(true);
const unavailable = await select(collection, {
names: [FIXTURE_RETURN_TYPE_NAME, "null", ""],
});
expect(unavailable.items).toEqual([]);
expect(unavailable.coverage).toMatchObject({ total: 0, exhausted: true });
}
});
it("preserves native relationship coverage through inline and retained MCP views", async () => {
const { client, session } = await connect();
const { ajv, outputSchema } = await advertisedMcpToolSchemas(