mirror of
https://github.com/morluto/rea.git
synced 2026-10-27 01:45:32 +00:00
751 lines
26 KiB
TypeScript
751 lines
26 KiB
TypeScript
import { z } from "zod";
|
|
import { analysisViewInputError } from "./analysisViewErrors.js";
|
|
import type { AnalysisError } from "../analysisErrorBase.js";
|
|
import type { JsonValue } from "../jsonValue.js";
|
|
import { isImmutableJsonSnapshot } from "../immutableJson.js";
|
|
import {
|
|
il2cppMetadataImageSchema,
|
|
il2cppMetadataMemberSchema,
|
|
il2cppMetadataMethodSchema,
|
|
il2cppMetadataSectionSchema,
|
|
il2cppMetadataParameterSchema,
|
|
il2cppMetadataStringLiteralSchema,
|
|
il2cppMethodAddressSchema,
|
|
il2cppGenericMethodInstanceSchema,
|
|
il2cppGenericMethodIssueSchema,
|
|
il2cppGenericMethodLinkSchema,
|
|
il2cppBinaryLinkSchema,
|
|
il2cppCodeGenModuleSchema,
|
|
il2cppTypeReferenceSchema,
|
|
il2cppMetadataObjectSchema,
|
|
il2cppMetadataParentSchema,
|
|
il2cppMetadataMemberCollections,
|
|
il2cppMetadataBinaryCollections,
|
|
il2cppMetadataTypeSchema,
|
|
} from "../il2cpp/il2cppMetadata.js";
|
|
import { err, ok, type Result } from "../result.js";
|
|
import { createAnalysisViewResultReader } from "./analysisViewParent.js";
|
|
import type {
|
|
AnalysisViewParent,
|
|
AnalysisViewRequest,
|
|
UnsignedAnalysisView,
|
|
} from "./analysisView.js";
|
|
import {
|
|
completeWithinViewCoverage,
|
|
pageViewCoverage,
|
|
} from "./analysisViewCoverage.js";
|
|
|
|
const countsSchema = z.strictObject({
|
|
sections: z.number().int().nonnegative(),
|
|
images: z.number().int().nonnegative(),
|
|
types: z.number().int().nonnegative(),
|
|
methods: z.number().int().nonnegative(),
|
|
nested_types: z.number().int().nonnegative(),
|
|
fields: z.number().int().nonnegative().nullable(),
|
|
properties: z.number().int().nonnegative().nullable(),
|
|
events: z.number().int().nonnegative().nullable(),
|
|
parameters: z.number().int().nonnegative().nullable(),
|
|
string_literals: z.number().int().nonnegative().nullable(),
|
|
code_gen_modules: z.number().int().nonnegative().nullable(),
|
|
method_addresses: z.number().int().nonnegative().nullable(),
|
|
/** method_addresses rows per state; null when native observations were not collected. */
|
|
method_address_states: z
|
|
.strictObject({
|
|
code: z.number().int().nonnegative(),
|
|
no_body: z.number().int().nonnegative(),
|
|
unknown: z.number().int().nonnegative(),
|
|
})
|
|
.nullable(),
|
|
return_types: z.number().int().nonnegative().nullable(),
|
|
field_types: z.number().int().nonnegative().nullable(),
|
|
parameter_types: z.number().int().nonnegative().nullable(),
|
|
/** Null unless generic_instances was requested, or before it was collected. */
|
|
generic_method_instances: z.number().int().nonnegative().nullable(),
|
|
/** generic_method_instances rows per state; null as above. */
|
|
generic_method_instance_states: z
|
|
.strictObject({
|
|
code: z.number().int().nonnegative(),
|
|
no_body: z.number().int().nonnegative(),
|
|
unknown: z.number().int().nonnegative(),
|
|
})
|
|
.nullable(),
|
|
/** generic_methods.issues rows; null as above. */
|
|
generic_method_issues: z.number().int().nonnegative().nullable(),
|
|
});
|
|
|
|
export const il2cppMetadataSummarySchema = il2cppMetadataObjectSchema
|
|
.omit({
|
|
sections: true,
|
|
images: true,
|
|
types: true,
|
|
methods: true,
|
|
fields: true,
|
|
properties: true,
|
|
events: true,
|
|
parameters: true,
|
|
string_literals: true,
|
|
method_addresses: true,
|
|
return_types: true,
|
|
field_types: true,
|
|
parameter_types: true,
|
|
generic_method_instances: true,
|
|
binary: true,
|
|
})
|
|
.extend({
|
|
// Links retained before generic instances existed omit generic_methods.
|
|
// Issues page through generic_method_issues; the summary keeps a count.
|
|
binary: il2cppBinaryLinkSchema
|
|
.omit({ code_gen_modules: true })
|
|
.extend({
|
|
generic_methods: il2cppGenericMethodLinkSchema
|
|
.omit({ issues: true })
|
|
.nullable(),
|
|
})
|
|
.partial({ generic_methods: true })
|
|
.nullable()
|
|
.optional(),
|
|
counts: countsSchema,
|
|
});
|
|
|
|
const indexSchema = z.number().int().nonnegative();
|
|
const sectionRowSchema = il2cppMetadataSectionSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const imageRowSchema = il2cppMetadataImageSchema.extend({ index: indexSchema });
|
|
const typeRowSchema = il2cppMetadataTypeSchema.extend({ index: indexSchema });
|
|
const methodRowSchema = il2cppMetadataMethodSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const memberRowSchema = il2cppMetadataMemberSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const parameterRowSchema = il2cppMetadataParameterSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const stringLiteralRowSchema = il2cppMetadataStringLiteralSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const methodAddressRowSchema = il2cppMethodAddressSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const typeReferenceRowSchema = il2cppTypeReferenceSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const genericInstanceRowSchema = il2cppGenericMethodInstanceSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const codeGenModuleRowSchema = il2cppCodeGenModuleSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const genericIssueRowSchema = il2cppGenericMethodIssueSchema.extend({
|
|
index: indexSchema,
|
|
});
|
|
const indexPage = {
|
|
kind: z.literal("il2cpp-metadata"),
|
|
indices: z.array(indexSchema).optional(),
|
|
offset: indexSchema.default(0),
|
|
limit: z.number().int().positive().default(64),
|
|
};
|
|
const page = { ...indexPage, names: z.array(z.string()).optional() };
|
|
const declaringTypes = z.array(z.number().int().min(-1)).optional();
|
|
|
|
/** Filters intersect; pages use metadata order and always retain the source row index. */
|
|
export const il2cppMetadataViewRequestSchema = z.discriminatedUnion(
|
|
"collection",
|
|
[
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("sections"),
|
|
fields: z.array(sectionRowSchema.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("images"),
|
|
fields: z.array(imageRowSchema.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("types"),
|
|
image_indices: z.array(indexSchema).optional(),
|
|
namespaces: z.array(z.string()).optional(),
|
|
declaring_types: declaringTypes,
|
|
fields: z.array(typeRowSchema.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("methods"),
|
|
declaring_types: declaringTypes,
|
|
fields: z.array(methodRowSchema.keyof()).optional(),
|
|
}),
|
|
...(["fields", "properties", "events"] as const).map((collection) =>
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal(collection),
|
|
declaring_types: declaringTypes,
|
|
fields: z.array(memberRowSchema.keyof()).optional(),
|
|
}),
|
|
),
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("parameters"),
|
|
method_indices: z.array(indexSchema).optional(),
|
|
fields: z.array(parameterRowSchema.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...indexPage,
|
|
collection: z.literal("string_literals"),
|
|
fields: z.array(stringLiteralRowSchema.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...indexPage,
|
|
collection: z.literal("method_addresses"),
|
|
addresses: z.array(z.string().regex(/^0x[0-9a-fA-F]{1,16}$/u)).optional(),
|
|
states: z.array(il2cppMethodAddressSchema.shape.state).optional(),
|
|
fields: z.array(methodAddressRowSchema.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("generic_method_instances"),
|
|
/** Metadata method indices of the generic definitions. */
|
|
method_indices: z.array(indexSchema).optional(),
|
|
addresses: z.array(z.string().regex(/^0x[0-9a-fA-F]{1,16}$/u)).optional(),
|
|
states: z.array(il2cppGenericMethodInstanceSchema.shape.state).optional(),
|
|
fields: z.array(genericInstanceRowSchema.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...indexPage,
|
|
collection: z.literal("generic_method_issues"),
|
|
/** genericMethodTable rows the issues leave unknown. */
|
|
rows: z.array(indexSchema).optional(),
|
|
fields: z.array(genericIssueRowSchema.keyof()).optional(),
|
|
}),
|
|
z.strictObject({
|
|
...indexPage,
|
|
collection: z.literal("code_gen_modules"),
|
|
image_indices: z.array(indexSchema).optional(),
|
|
fields: z.array(codeGenModuleRowSchema.keyof()).optional(),
|
|
}),
|
|
...(["return_types", "field_types"] as const).map((collection) =>
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal(collection),
|
|
fields: z.array(typeReferenceRowSchema.keyof()).optional(),
|
|
}),
|
|
),
|
|
z.strictObject({
|
|
...page,
|
|
collection: z.literal("parameter_types"),
|
|
// Owners come from the retained parameters collection.
|
|
method_indices: z.array(indexSchema).optional(),
|
|
fields: z.array(typeReferenceRowSchema.keyof()).optional(),
|
|
}),
|
|
],
|
|
);
|
|
|
|
export const il2cppMetadataViewItemsSchema = z.union([
|
|
z.array(sectionRowSchema.partial().required({ index: true })),
|
|
z.array(imageRowSchema.partial().required({ index: true })),
|
|
z.array(typeRowSchema.partial().required({ index: true })),
|
|
z.array(methodRowSchema.partial().required({ index: true })),
|
|
z.array(memberRowSchema.partial().required({ index: true })),
|
|
z.array(parameterRowSchema.partial().required({ index: true })),
|
|
z.array(stringLiteralRowSchema.partial().required({ index: true })),
|
|
z.array(methodAddressRowSchema.partial().required({ index: true })),
|
|
z.array(typeReferenceRowSchema.partial().required({ index: true })),
|
|
z.array(codeGenModuleRowSchema.partial().required({ index: true })),
|
|
z.array(genericInstanceRowSchema.partial().required({ index: true })),
|
|
z.array(genericIssueRowSchema.partial().required({ index: true })),
|
|
]);
|
|
|
|
export const il2cppMetadataViewContextSchema = z.strictObject({
|
|
metadata_version: il2cppMetadataObjectSchema.shape.metadata_version,
|
|
counts: countsSchema,
|
|
binary_source: il2cppBinaryLinkSchema
|
|
.pick({
|
|
artifact: true,
|
|
format: true,
|
|
architecture: true,
|
|
image_base: true,
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
type Report = z.infer<typeof il2cppMetadataParentSchema>;
|
|
type Request = z.infer<typeof il2cppMetadataViewRequestSchema>;
|
|
type BinaryLink = NonNullable<Report["binary"]>;
|
|
type CollectionRows = Omit<Report, "binary"> & {
|
|
code_gen_modules: BinaryLink["code_gen_modules"];
|
|
generic_method_issues: NonNullable<BinaryLink["generic_methods"]>["issues"];
|
|
};
|
|
type Row = NonNullable<CollectionRows[Request["collection"]]>[number];
|
|
interface CheckedReport {
|
|
readonly report: Report;
|
|
readonly counts: z.infer<typeof countsSchema>;
|
|
}
|
|
|
|
const readMetadataReport = createAnalysisViewResultReader(
|
|
il2cppMetadataParentSchema,
|
|
);
|
|
// The validated immutable report owns its derived counts for its lifetime.
|
|
const checkedReports = new WeakMap<object, CheckedReport>();
|
|
|
|
const parseMetadataReport = (
|
|
parent: AnalysisViewParent,
|
|
): Result<CheckedReport, AnalysisError> => {
|
|
const parsed = readMetadataReport(parent);
|
|
if (!parsed.ok) return parsed;
|
|
const report = parsed.value;
|
|
const reusable = isImmutableJsonSnapshot(report);
|
|
const cached = reusable ? checkedReports.get(report) : undefined;
|
|
if (cached !== undefined) return ok(cached);
|
|
const checked = {
|
|
report,
|
|
counts: {
|
|
sections: report.sections.length,
|
|
images: report.images.length,
|
|
types: report.types.length,
|
|
methods: report.methods.length,
|
|
nested_types: report.types.reduce(
|
|
(count, type) => count + Number(type.declaring_type !== -1),
|
|
0,
|
|
),
|
|
fields: report.fields?.length ?? null,
|
|
properties: report.properties?.length ?? null,
|
|
events: report.events?.length ?? null,
|
|
parameters: report.parameters?.length ?? null,
|
|
string_literals: report.string_literals?.length ?? null,
|
|
code_gen_modules: report.binary?.code_gen_modules.length ?? null,
|
|
method_addresses:
|
|
report.binary == null
|
|
? null
|
|
: (report.method_addresses?.length ?? null),
|
|
method_address_states:
|
|
(report.binary == null ? undefined : report.method_addresses)?.reduce(
|
|
(states, row) => {
|
|
if (row.state === "code") states.code++;
|
|
else if (row.state === "no-body") states.no_body++;
|
|
else states.unknown++;
|
|
return states;
|
|
},
|
|
{ code: 0, no_body: 0, unknown: 0 },
|
|
) ?? null,
|
|
return_types:
|
|
report.binary == null ? null : (report.return_types?.length ?? null),
|
|
field_types:
|
|
report.binary == null ? null : (report.field_types?.length ?? null),
|
|
parameter_types:
|
|
report.binary == null ? null : (report.parameter_types?.length ?? null),
|
|
generic_method_instances: report.generic_method_instances?.length ?? null,
|
|
generic_method_instance_states:
|
|
report.generic_method_instances?.reduce(
|
|
(states, row) => {
|
|
if (row.state === "code") states.code++;
|
|
else if (row.state === "no-body") states.no_body++;
|
|
else states.unknown++;
|
|
return states;
|
|
},
|
|
{ code: 0, no_body: 0, unknown: 0 },
|
|
) ?? null,
|
|
generic_method_issues:
|
|
report.binary?.generic_methods?.issues.length ?? null,
|
|
},
|
|
};
|
|
if (reusable) checkedReports.set(report, checked);
|
|
return ok(checked);
|
|
};
|
|
|
|
/** Direct batch lookup visits selected indices only; metadata order is stable. */
|
|
function* rowIndices(length: number, indices: number[] | undefined) {
|
|
if (indices === undefined) {
|
|
for (let index = 0; index < length; index++) yield index;
|
|
} else {
|
|
for (const index of [...new Set(indices)].sort((a, b) => a - b))
|
|
if (index < length) yield index;
|
|
}
|
|
}
|
|
|
|
const selectFields = (
|
|
row: Row,
|
|
index: number,
|
|
fields: Set<string> | undefined,
|
|
) => {
|
|
const item: { index: number } & Record<string, JsonValue> = { index };
|
|
const values: Readonly<Record<string, JsonValue>> = row;
|
|
for (const key of Object.keys(values)) {
|
|
const value = values[key];
|
|
if (value !== undefined && (fields === undefined || fields.has(key)))
|
|
item[key] = value;
|
|
}
|
|
return item;
|
|
};
|
|
|
|
/** Compile caller selections once; large batches do not multiply per-row work. */
|
|
const selection = (view: Request, report: Report) => {
|
|
const names =
|
|
!("names" in view) || view.names === undefined
|
|
? undefined
|
|
: new Set(view.names);
|
|
const owners =
|
|
!("declaring_types" in view) || view.declaring_types === undefined
|
|
? undefined
|
|
: new Set(view.declaring_types);
|
|
const addresses =
|
|
(view.collection === "method_addresses" ||
|
|
view.collection === "generic_method_instances") &&
|
|
view.addresses !== undefined
|
|
? new Set(view.addresses.map((value) => BigInt(value).toString(16)))
|
|
: undefined;
|
|
const states =
|
|
(view.collection === "method_addresses" ||
|
|
view.collection === "generic_method_instances") &&
|
|
view.states !== undefined
|
|
? new Set<string>(view.states)
|
|
: undefined;
|
|
const methods =
|
|
(view.collection === "parameters" ||
|
|
view.collection === "parameter_types" ||
|
|
view.collection === "generic_method_instances") &&
|
|
view.method_indices !== undefined
|
|
? new Set(view.method_indices)
|
|
: undefined;
|
|
// A parameter type row is owned by the method of the parameter at its
|
|
// index; parameter and generic instance rows name their method.
|
|
const parameterOwner = (row: Row, index: number) =>
|
|
"method" in row
|
|
? row.method
|
|
: view.collection === "parameter_types"
|
|
? report.parameters?.[index]?.method
|
|
: undefined;
|
|
const namespaces =
|
|
view.collection !== "types" || view.namespaces === undefined
|
|
? undefined
|
|
: new Set(view.namespaces);
|
|
const imageIndices =
|
|
view.collection === "types" ? view.image_indices : undefined;
|
|
const ranges: { start: number; end: number }[] = [];
|
|
if (imageIndices !== undefined) {
|
|
const selected = [...new Set(imageIndices)]
|
|
.flatMap((index) => {
|
|
const image = report.images[index];
|
|
return image === undefined || image.type_count === 0
|
|
? []
|
|
: [
|
|
{
|
|
start: image.type_start,
|
|
end: image.type_start + image.type_count,
|
|
},
|
|
];
|
|
})
|
|
.sort((a, b) => a.start - b.start);
|
|
for (const range of selected) {
|
|
const last = ranges.at(-1);
|
|
if (last !== undefined && range.start <= last.end)
|
|
last.end = Math.max(last.end, range.end);
|
|
else ranges.push(range);
|
|
}
|
|
}
|
|
const issueRows =
|
|
view.collection === "generic_method_issues" && view.rows !== undefined
|
|
? new Set(view.rows)
|
|
: undefined;
|
|
const moduleImages =
|
|
view.collection === "code_gen_modules" && view.image_indices !== undefined
|
|
? new Set(view.image_indices)
|
|
: undefined;
|
|
if (
|
|
[
|
|
moduleImages,
|
|
issueRows,
|
|
addresses,
|
|
states,
|
|
names,
|
|
owners,
|
|
namespaces,
|
|
methods,
|
|
imageIndices,
|
|
].every((filter) => filter === undefined)
|
|
)
|
|
return undefined;
|
|
return (row: Row, index: number) => {
|
|
if (
|
|
moduleImages !== undefined &&
|
|
(!("image" in row) || !moduleImages.has(row.image))
|
|
)
|
|
return false;
|
|
if (issueRows !== undefined && (!("row" in row) || !issueRows.has(row.row)))
|
|
return false;
|
|
if (
|
|
addresses !== undefined &&
|
|
(!("address" in row) ||
|
|
row.address === null ||
|
|
!addresses.has(BigInt(row.address).toString(16)))
|
|
)
|
|
return false;
|
|
if (states !== undefined && (!("state" in row) || !states.has(row.state)))
|
|
return false;
|
|
if (
|
|
names !== undefined &&
|
|
(!("name" in row) || row.name === null || !names.has(row.name))
|
|
)
|
|
return false;
|
|
if (
|
|
owners !== undefined &&
|
|
(!("declaring_type" in row) || !owners.has(row.declaring_type))
|
|
)
|
|
return false;
|
|
if (
|
|
namespaces !== undefined &&
|
|
(!("namespace" in row) || !namespaces.has(row.namespace))
|
|
)
|
|
return false;
|
|
if (methods !== undefined) {
|
|
const method = parameterOwner(row, index);
|
|
if (method === null || method === undefined || !methods.has(method))
|
|
return false;
|
|
}
|
|
if (imageIndices === undefined) return true;
|
|
let lower = 0;
|
|
let upper = ranges.length;
|
|
while (lower < upper) {
|
|
const middle = lower + Math.floor((upper - lower) / 2);
|
|
if (ranges[middle]!.start <= index) lower = middle + 1;
|
|
else upper = middle;
|
|
}
|
|
const range = ranges[lower - 1];
|
|
return range !== undefined && index < range.end;
|
|
};
|
|
};
|
|
|
|
/** Reuse retained facts; source indices stay distinct from filtered page offsets. */
|
|
export const projectIl2cppMetadataView = (
|
|
parent: AnalysisViewParent,
|
|
view: AnalysisViewRequest,
|
|
): Result<UnsignedAnalysisView, AnalysisError> => {
|
|
if (view.kind !== "summary" && view.kind !== "il2cpp-metadata")
|
|
return err(
|
|
analysisViewInputError([
|
|
{
|
|
path: ["view"],
|
|
reason: "invalid_value",
|
|
message:
|
|
"IL2CPP metadata Evidence supports summary and selected sections, images, types, methods, member, parameter, string literal, method address, type reference, generic method instance and generic method issue pages.",
|
|
},
|
|
]),
|
|
);
|
|
const parsed = parseMetadataReport(parent);
|
|
if (!parsed.ok) return parsed;
|
|
const { report, counts } = parsed.value;
|
|
if (
|
|
parent.subject != null &&
|
|
(parent.subject.format !== "file" ||
|
|
parent.subject.local_path !== report.artifact.path ||
|
|
parent.subject.digest.sha256 !== report.artifact.sha256)
|
|
)
|
|
return err(
|
|
analysisViewInputError([
|
|
{
|
|
path: ["source", "subject"],
|
|
reason: "invalid_value",
|
|
message:
|
|
"IL2CPP metadata artifact identity does not match its parent subject.",
|
|
},
|
|
]),
|
|
);
|
|
const {
|
|
sections,
|
|
images,
|
|
types,
|
|
methods,
|
|
fields,
|
|
properties,
|
|
events,
|
|
parameters,
|
|
string_literals,
|
|
method_addresses,
|
|
return_types,
|
|
field_types,
|
|
parameter_types,
|
|
generic_method_instances,
|
|
...identity
|
|
} = report;
|
|
const allRows = {
|
|
code_gen_modules: report.binary?.code_gen_modules,
|
|
generic_method_issues:
|
|
report.binary?.generic_methods === null
|
|
? null
|
|
: report.binary?.generic_methods?.issues,
|
|
sections,
|
|
images,
|
|
types,
|
|
methods,
|
|
fields,
|
|
properties,
|
|
events,
|
|
parameters,
|
|
string_literals,
|
|
method_addresses,
|
|
return_types,
|
|
field_types,
|
|
parameter_types,
|
|
generic_method_instances,
|
|
};
|
|
const shared = {
|
|
parent_evidence_id: parent.evidenceId,
|
|
parent_operation: "inspect_il2cpp_metadata" as const,
|
|
parent_digest: parent.evidenceId.slice(3),
|
|
artifact: { path: report.artifact.path, sha256: report.artifact.sha256 },
|
|
limitations: [...parent.limitations],
|
|
unknowns: [
|
|
...(report.binary != null && report.binary.code_gen_modules_field === null
|
|
? [
|
|
"Module-array association is inferred from executable byte patterns; instruction boundaries and runtime use remain unverified.",
|
|
]
|
|
: []),
|
|
...(report.binary != null && report.binary.types_field === null
|
|
? [
|
|
"Type-table association and types_count are inferred from executable byte patterns and Il2CppType-shaped records; instruction boundaries and runtime use remain unverified.",
|
|
]
|
|
: []),
|
|
...[
|
|
...il2cppMetadataMemberCollections,
|
|
...il2cppMetadataBinaryCollections,
|
|
]
|
|
.filter((name) => report[name] === undefined)
|
|
.map(
|
|
(name) =>
|
|
`Retained Evidence did not collect ${name}; its count is unknown. Reinspect the metadata to acquire these facts.`,
|
|
),
|
|
...(report.binary != null && report.generic_method_instances === undefined
|
|
? [
|
|
"Retained Evidence did not collect generic_method_instances; its count is unknown. Reinspect with generic_instances to acquire these facts.",
|
|
]
|
|
: report.generic_method_instances === null
|
|
? [
|
|
"Generic method instances were not requested; their count is unknown.",
|
|
]
|
|
: []),
|
|
report.binary == null
|
|
? "Names come from unencrypted metadata only; native code addresses and runtime registration remain unknown."
|
|
: report.binary.generic_methods == null
|
|
? "Addresses come from static binary tables; runtime behavior and generic instance code remain unknown."
|
|
: "Addresses come from static binary tables; runtime behavior and generic instances outside genericMethodTable remain unknown.",
|
|
...(parent.subject == null
|
|
? [
|
|
"The parent has no recorded subject; its normalized artifact identity cannot be cross-checked against a subject digest.",
|
|
]
|
|
: []),
|
|
],
|
|
};
|
|
if (view.kind === "summary") {
|
|
const binary =
|
|
report.binary == null
|
|
? report.binary
|
|
: (({ code_gen_modules: _modules, generic_methods, ...facts }) => ({
|
|
...facts,
|
|
...(generic_methods === undefined
|
|
? {}
|
|
: {
|
|
generic_methods:
|
|
generic_methods === null
|
|
? null
|
|
: (({ issues: _issues, ...registration }) =>
|
|
registration)(generic_methods),
|
|
}),
|
|
}))(report.binary);
|
|
return ok({
|
|
kind: "summary",
|
|
view,
|
|
...shared,
|
|
summary: {
|
|
...identity,
|
|
...(binary === undefined ? {} : { binary }),
|
|
counts,
|
|
},
|
|
coverage: completeWithinViewCoverage(1, 1),
|
|
});
|
|
}
|
|
const nativeUnavailable =
|
|
report.binary == null &&
|
|
[
|
|
"method_addresses",
|
|
"return_types",
|
|
"field_types",
|
|
"parameter_types",
|
|
].includes(view.collection);
|
|
const rows = nativeUnavailable ? undefined : allRows[view.collection];
|
|
if (rows === null)
|
|
return err(
|
|
analysisViewInputError([
|
|
{
|
|
path: ["view", "collection"],
|
|
reason: "invalid_value",
|
|
message:
|
|
"Generic method instances were not requested for this Evidence, so they are unknown; inspect again with generic_instances and binary_path.",
|
|
},
|
|
]),
|
|
);
|
|
if (rows === undefined)
|
|
return err(
|
|
analysisViewInputError([
|
|
{
|
|
path: ["view", "collection"],
|
|
reason: "invalid_value",
|
|
message: `Retained Evidence did not collect ${view.collection}; inspect the metadata again to acquire these facts.`,
|
|
},
|
|
]),
|
|
);
|
|
if (
|
|
view.collection === "parameter_types" &&
|
|
view.method_indices !== undefined &&
|
|
report.parameters === undefined
|
|
)
|
|
return err(
|
|
analysisViewInputError([
|
|
{
|
|
path: ["view", "method_indices"],
|
|
reason: "invalid_value",
|
|
message:
|
|
"Retained Evidence did not collect parameters, so parameter owners are unknown; inspect the metadata again to select parameter types by method.",
|
|
},
|
|
]),
|
|
);
|
|
const matches = selection(view, report);
|
|
const selectedFields =
|
|
view.fields === undefined ? undefined : new Set<string>(view.fields);
|
|
const items = [];
|
|
let total = 0;
|
|
if (matches === undefined && view.indices === undefined) {
|
|
total = rows.length;
|
|
const end = Math.min(rows.length, view.offset + view.limit);
|
|
for (let index = Math.min(view.offset, rows.length); index < end; index++)
|
|
items.push(selectFields(rows[index]!, index, selectedFields));
|
|
} else {
|
|
for (const index of rowIndices(rows.length, view.indices)) {
|
|
const row = rows[index]!;
|
|
if (matches !== undefined && !matches(row, index)) continue;
|
|
if (total >= view.offset && items.length < view.limit)
|
|
items.push(selectFields(row, index, selectedFields));
|
|
total++;
|
|
}
|
|
}
|
|
return ok({
|
|
kind: "il2cpp-metadata",
|
|
view,
|
|
...shared,
|
|
items,
|
|
metadata_version: report.metadata_version,
|
|
...(report.binary == null
|
|
? {}
|
|
: {
|
|
binary_source: {
|
|
artifact: report.binary.artifact,
|
|
format: report.binary.format,
|
|
architecture: report.binary.architecture,
|
|
image_base: report.binary.image_base,
|
|
},
|
|
}),
|
|
counts,
|
|
coverage: pageViewCoverage(view.offset, items.length, total),
|
|
});
|
|
};
|