fix(parsers): preserve format boundaries and query occurrence identity

Normalize parsed facts at their owning boundaries across PE exports and certificates, ELF section names, GPU bundles, IL2CPP generic types, dyld slice selection, source maps, environment replay, and JavaScript mapped arguments. Preserve partial facts and explicit unsupported cases; clarify the corresponding documentation.

Validation: 831 affected tests passed with one existing skip across 23 files; build, typecheck/lint, documentation, formatting, and 2133-file test discovery passed. Format controls are synthetic; real Unity, GPU, and ARM-device execution is unverified.

Fixes #6709
Fixes #6688
Fixes #6719
Fixes #6718
Fixes #6704
Fixes #6703
Fixes #6692
Fixes #6723
Fixes #6687
Fixes #6726
Fixes #6696
Fixes #6691
Fixes #6727
This commit is contained in:
morluto committed 2026-10-23 22:01:16 +08:00
1 parent 4193bc5bf8
commit 5f702dd6cc
60 files changed
+2069 -211

No files matched your search

+70 -5
View File
@@ -87,6 +87,59 @@ export const createTypeNamer = (
const name = join(parts.reverse(), "+");
return namespace === "" ? name : join([namespace, name], ".");
}
function* genericDefinitionName(
index: number,
args: readonly string[],
): Generator<void, string> {
if (!Number.isInteger(index) || index < 0 || index >= metadata.types.length)
fail(`type definition ${index} is out of range`);
const chain: number[] = [];
const seen = new Set<number>();
for (let at = index; at !== -1;) {
if (seen.has(at)) fail(`cyclic declaring type at ${at}`);
if (++work % 256 === 0) yield;
const type = metadata.types[at];
if (type === undefined)
return fail(`type definition ${at} is out of range`);
seen.add(at);
chain.push(at);
at = type.declaring_type;
}
let previousArgumentCount = 0;
const parts: string[] = [];
for (const typeIndex of chain.reverse()) {
if (++work % 256 === 0) yield;
const type = metadata.types[typeIndex]!;
const argumentCount = facts.genericTypeArgumentCounts[typeIndex];
if (
argumentCount === undefined ||
argumentCount < previousArgumentCount ||
argumentCount > args.length
)
return fail(
`generic argument count does not match nested type definition ${typeIndex}`,
);
const componentArguments = args.slice(
previousArgumentCount,
argumentCount,
);
previousArgumentCount = argumentCount;
const name = type.name.replace(/`\d+$/u, "");
parts.push(
componentArguments.length === 0
? name
: join([name, "<", join(componentArguments, ", "), ">"]),
);
}
if (previousArgumentCount !== args.length)
fail(
`generic argument count ${args.length} does not match nested type definition ${index}`,
);
const name = join(parts, "+");
const namespace = metadata.types[chain[0]!]!.namespace;
return namespace === "" ? name : join([namespace, name], ".");
}
const byAddress = new Map<bigint, string>();
const active = new Set<bigint>();
const byrefBit = version >= 29 ? 29 : 30;
@@ -145,10 +198,22 @@ export const createTypeNamer = (
}
}
function* generic(data: bigint, depth: number): Generator<void, string> {
const definition =
version >= 27
? yield* name(pointer(data, "generic class type"), depth + 1)
: yield* definitionName(u32(data, "generic class definition"));
let definitionIndex: number;
if (version >= 27) {
const definitionType = pointer(data, "generic class type");
const definitionData = pointer(
definitionType,
"generic class definition",
);
const definitionKind =
(u32(definitionType + 8n, "generic class type attributes") >>> 16) &
0xff;
if (definitionKind !== 0x11 && definitionKind !== 0x12)
fail("generic class type is not a class or value type definition");
definitionIndex = Number(
BigInt.asIntN(32, definitionData & 0xffff_ffffn),
);
} else definitionIndex = u32(data, "generic class definition");
const instance = pointer(data + 8n, "generic class instance");
const count = u32(instance, "generic argument count");
const argv = pointer(instance + 8n, "generic arguments");
@@ -167,7 +232,7 @@ export const createTypeNamer = (
guard(bytes);
args.push(argument);
}
return join([definition.replace(/`\d+$/u, ""), "<", join(args, ", "), ">"]);
return yield* genericDefinitionName(definitionIndex, args);
}
return name;
};
+36
View File
@@ -526,6 +526,10 @@ function* readTypes(tables: MetadataTables): Generator<
properties: [] as MetadataOwnerRange[],
events: [] as MetadataOwnerRange[],
};
const genericContainerCount =
tables.linkFacts === undefined
? 0
: entriesIn(section("genericContainers"), GENERIC_CONTAINER_RECORD_BYTES);
for (let index = 0; index < counts.typeDefinitions; index++) {
if (index % 256 === 0) yield;
const at = typeDefinitions.offset + index * sizes.typeDefinitions;
@@ -536,6 +540,33 @@ function* readTypes(tables: MetadataTables): Generator<
12 +
typeDefinitionTypeIndices * widths.type +
widths.generic_container;
if (tables.linkFacts !== undefined) {
const genericContainerIndex = readIndex(
bytes,
at + 8 + typeDefinitionTypeIndices * widths.type,
widths.generic_container,
);
if (
genericContainerIndex < -1 ||
genericContainerIndex >= genericContainerCount
)
throw new Il2cppMetadataFormatFailure(
"index-out-of-range",
`type ${index} generic container ${genericContainerIndex} is outside the ${genericContainerCount} available rows`,
);
if (genericContainerIndex !== -1) {
const genericContainerAt =
section("genericContainers").offset +
genericContainerIndex * GENERIC_CONTAINER_RECORD_BYTES;
const argumentCount = bytes.readInt32LE(genericContainerAt + 4);
if (argumentCount < 0)
throw new Il2cppMetadataFormatFailure(
"index-out-of-range",
`type ${index} generic container ${genericContainerIndex} has negative argument count ${argumentCount}`,
);
tables.linkFacts.genericTypeArgumentCounts[index] = argumentCount;
}
}
const countsAt = starts + 32;
const type = {
name: readString(bytes.readInt32LE(at), `type ${index} name`),
@@ -730,6 +761,8 @@ export interface Il2cppMetadataLinkFacts {
readonly returnTypes: Int32Array;
readonly fieldTypes: Int32Array;
readonly parameterTypes: Int32Array;
/** Total class generic argument count per TypeDefinition, including enclosing types. */
readonly genericTypeArgumentCounts: Int32Array;
/** Demand-read names from this stable metadata snapshot, by GenericParameterIndex. */
readonly genericParameterName: (index: number) => string | undefined;
}
@@ -835,6 +868,9 @@ function* readMetadataCoreSteps(
returnTypes: new Int32Array(tables.counts.methods),
fieldTypes: new Int32Array(tables.members.counts.fields),
parameterTypes: new Int32Array(tables.members.counts.parameters),
genericTypeArgumentCounts: new Int32Array(
tables.counts.typeDefinitions,
),
genericParameterName: createGenericParameterReader(tables),
},
};
+1 -1
View File
@@ -534,7 +534,7 @@ export class Il2cppMetadataProvider implements Il2cppMetadataPort {
? "Field, parameter and return types are not recovered without binary_path; type indices refer to the native binary."
: decoded.metadata_version === 27
? "Field, parameter and return type names are null for metadata version 27: versions 27.0 to 27.2 share that header number but place the Il2CppType byref flag differently, so a name could gain or lose & without warning; type indices are still reported."
: "Field, parameter and return type names are rendered from the binary's Il2CppType table in a C#-like form (keywords for primitives, Namespace.Outer+Inner, generic parameters by metadata name); custom modifiers, array bounds and attributes are not reported.",
: "Field, parameter and return type names are rendered from the binary's Il2CppType table in a C#-like form (keywords for primitives, Namespace.Outer+Inner, generic parameters by metadata name); nested generic arguments stay with the type component that declares them. Custom modifiers, array bounds and attributes are not reported.",
...(linked.binary !== null &&
linked.binary.code_gen_modules_field === null
? [