mirror of
https://github.com/morluto/rea
synced 2026-10-27 06:33:32 -04:00
feat(il2cpp): link native methods and export Ghidra seeds
Link IL2CPP metadata names to native addresses and types through shared CLI/MCP workflows, with bounded queries and atomic Ghidra seed export. Preserve the original implementation by 8rulerstar and follow-up correctness, resource, provenance, and CI dependency repairs. Verified against real Windows/Android artifacts, independent references, actual Ghidra, and focused CLI/MCP tests. Fixes #5112.
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
import type { Il2cppMethodAddress } from "../domain/il2cpp/il2cppMetadata.js";
|
||||
import { Il2cppBinaryFormatFailure } from "./Il2cppBinaryFormat.js";
|
||||
import {
|
||||
openImageSteps,
|
||||
readPointer,
|
||||
readSlot,
|
||||
isExecutable,
|
||||
} from "./Il2cppBinaryImage.js";
|
||||
import {
|
||||
findModules,
|
||||
findCodeRegistration,
|
||||
findMetadataRegistration,
|
||||
} from "./Il2cppBinaryRegistration.js";
|
||||
import { createTypeNamer } from "./Il2cppBinaryTypes.js";
|
||||
import type {
|
||||
Il2cppMetadataDecoded,
|
||||
Il2cppMetadataLinkFacts,
|
||||
Il2cppMetadataReadOptions,
|
||||
} from "./Il2cppMetadataFile.js";
|
||||
/** One linked code-generation module; its name matches a metadata image. */
|
||||
export interface Il2cppCodeGenModule {
|
||||
readonly image: number;
|
||||
readonly address: bigint;
|
||||
readonly method_pointer_count: number;
|
||||
readonly method_pointers: bigint;
|
||||
}
|
||||
|
||||
type Il2cppMethodCode = bigint | "no-body" | "unknown";
|
||||
|
||||
export interface Il2cppBinaryLink {
|
||||
readonly format: "elf" | "macho" | "pe";
|
||||
readonly architecture: string;
|
||||
readonly image_base: bigint;
|
||||
/**
|
||||
* Address of the CodeRegistration codeGenModulesCount field, or null when the
|
||||
* static registration pair was unresolved and an executable byte-pattern
|
||||
* candidate identified the array; instruction boundaries remain unverified.
|
||||
*/
|
||||
readonly code_gen_modules_field: bigint | null;
|
||||
/** Address of the code-generation module pointer array. */
|
||||
readonly code_gen_modules_array: bigint;
|
||||
readonly code_gen_modules: readonly Il2cppCodeGenModule[];
|
||||
/**
|
||||
* Native code address per metadata method index; "no-body" when its module
|
||||
* slot is zero, "unknown" when its owner or relocated pointer is unresolved.
|
||||
*/
|
||||
readonly method_addresses: Il2cppMethodAddress[];
|
||||
/** Address of MetadataRegistration's typesCount field. */
|
||||
readonly types_field: bigint;
|
||||
readonly types_count: number;
|
||||
/** Il2CppType table index and display name per method return, field and parameter. */
|
||||
readonly return_types: Il2cppTypeReference[];
|
||||
readonly field_types: Il2cppTypeReference[];
|
||||
readonly parameter_types: Il2cppTypeReference[];
|
||||
}
|
||||
|
||||
export type Il2cppTypeReference = {
|
||||
readonly type: number;
|
||||
readonly name: string | null;
|
||||
};
|
||||
|
||||
export type Il2cppBinaryReadOptions = Pick<
|
||||
Il2cppMetadataReadOptions,
|
||||
"beforeText" | "onRow"
|
||||
>;
|
||||
|
||||
/**
|
||||
* Link metadata methods to native code addresses without executing the image.
|
||||
* A method token's row id selects its module's methodPointers slot.
|
||||
*/
|
||||
export function* linkIl2cppBinarySteps(
|
||||
bytes: Buffer,
|
||||
metadata: Il2cppMetadataDecoded,
|
||||
facts: Il2cppMetadataLinkFacts,
|
||||
architecture?: string,
|
||||
options: Il2cppBinaryReadOptions = {},
|
||||
): Generator<void, Il2cppBinaryLink> {
|
||||
const version = metadata.metadata_version;
|
||||
yield;
|
||||
const image = yield* openImageSteps(bytes, architecture);
|
||||
const modules = yield* findModules(image, metadata);
|
||||
const registration_ = yield* findCodeRegistration(image, modules);
|
||||
const methodImage = new Int32Array(metadata.methods.length).fill(-1);
|
||||
for (const [index, entry] of metadata.images.entries())
|
||||
for (let offset = 0; offset < entry.type_count; offset++) {
|
||||
const type = metadata.types[entry.type_start + offset]!;
|
||||
if (offset % LINK_BATCH === 0) yield;
|
||||
for (let method = 0; method < type.method_count; method++) {
|
||||
if (method % LINK_BATCH === 0) yield;
|
||||
methodImage[type.method_start + method] = index;
|
||||
}
|
||||
}
|
||||
yield;
|
||||
const methodAddress = (index: number): Il2cppMethodCode => {
|
||||
const owner = methodImage[index]!;
|
||||
// No image range holds this method, so no module slot describes it.
|
||||
if (owner === -1) return "unknown";
|
||||
const module = modules[owner]!;
|
||||
const token = facts.methodTokens[index]!;
|
||||
const row = token & 0xffffff;
|
||||
if (token >>> 24 !== 0x06 || row === 0 || row > module.method_pointer_count)
|
||||
throw new Il2cppBinaryFormatFailure(
|
||||
"invalid-method-token",
|
||||
`method ${index} token 0x${token.toString(16)} is outside module ${metadata.images[owner]!.name}'s ${module.method_pointer_count} method pointers`,
|
||||
);
|
||||
const address = readSlot(
|
||||
image,
|
||||
module.method_pointers + BigInt((row - 1) * 8),
|
||||
);
|
||||
if (address === null || address === undefined)
|
||||
throw new Il2cppBinaryFormatFailure(
|
||||
"invalid-method-pointer",
|
||||
`module ${metadata.images[owner]!.name} method pointer ${row - 1} is not file mapped`,
|
||||
);
|
||||
if (address === "unknown") return "unknown";
|
||||
if (address === 0n) return "no-body";
|
||||
if (!isExecutable(image, address))
|
||||
throw new Il2cppBinaryFormatFailure(
|
||||
"invalid-method-pointer",
|
||||
`method ${index} points outside unambiguous executable file data at 0x${address.toString(16)}`,
|
||||
);
|
||||
return address;
|
||||
};
|
||||
const method_addresses: Il2cppMethodAddress[] = [];
|
||||
for (let index = 0; index < metadata.methods.length; index++) {
|
||||
if (index % LINK_BATCH === 0) yield;
|
||||
const code = methodAddress(index);
|
||||
if (typeof code === "bigint" && code < image.imageBase)
|
||||
throw new Il2cppBinaryFormatFailure(
|
||||
"invalid-method-pointer",
|
||||
"method address precedes the link image base",
|
||||
);
|
||||
const row: Il2cppMethodAddress =
|
||||
typeof code === "bigint"
|
||||
? {
|
||||
state: "code",
|
||||
address: `0x${code.toString(16)}`,
|
||||
rva: `0x${(code - image.imageBase).toString(16)}`,
|
||||
}
|
||||
: { state: code, address: null, rva: null };
|
||||
options.onRow?.(row);
|
||||
method_addresses.push(row);
|
||||
}
|
||||
const registration = yield* findMetadataRegistration(
|
||||
image,
|
||||
metadata.types.length,
|
||||
);
|
||||
const typeName = createTypeNamer(
|
||||
image,
|
||||
metadata,
|
||||
facts,
|
||||
version,
|
||||
options.beforeText,
|
||||
);
|
||||
function* reference(type: number): Generator<void, Il2cppTypeReference> {
|
||||
if (type === -1) return { type, name: null };
|
||||
if (type < 0 || type >= registration.count)
|
||||
throw new Il2cppBinaryFormatFailure(
|
||||
"invalid-type",
|
||||
`type index ${type} is outside the ${registration.count} binary types`,
|
||||
);
|
||||
// 27.0/27.1 and 27.2 share header version 27 but place byref differently.
|
||||
if (version === 27) return { type, name: null };
|
||||
const address =
|
||||
readPointer(image, registration.types + BigInt(type * 8)) ?? 0n;
|
||||
if (address === 0n)
|
||||
throw new Il2cppBinaryFormatFailure(
|
||||
"invalid-type",
|
||||
`type ${type} has no Il2CppType record`,
|
||||
);
|
||||
return { type, name: yield* typeName(address, 0) };
|
||||
}
|
||||
function* column(types: Int32Array) {
|
||||
const rows: Il2cppTypeReference[] = [];
|
||||
for (let index = 0; index < types.length; index++) {
|
||||
if (index % LINK_BATCH === 0) yield;
|
||||
const row = yield* reference(types[index]!);
|
||||
options.onRow?.(row);
|
||||
rows.push(row);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
const return_types = yield* column(facts.returnTypes);
|
||||
const field_types = yield* column(facts.fieldTypes);
|
||||
const parameter_types = yield* column(facts.parameterTypes);
|
||||
return {
|
||||
format: image.container.format,
|
||||
architecture: image.container.architecture,
|
||||
image_base: image.imageBase,
|
||||
code_gen_modules_field: registration_.field,
|
||||
code_gen_modules_array: registration_.array,
|
||||
code_gen_modules: modules,
|
||||
method_addresses,
|
||||
types_field: registration.field,
|
||||
types_count: registration.count,
|
||||
return_types,
|
||||
field_types,
|
||||
parameter_types,
|
||||
};
|
||||
}
|
||||
|
||||
/** Rows between cooperative yields while linking. */
|
||||
const LINK_BATCH = 256;
|
||||
|
||||
/** Synchronous linking for byte-level callers. */
|
||||
export const linkIl2cppBinary = (
|
||||
bytes: Buffer,
|
||||
metadata: Il2cppMetadataDecoded,
|
||||
facts: Il2cppMetadataLinkFacts,
|
||||
architecture?: string,
|
||||
): Il2cppBinaryLink => {
|
||||
const steps = linkIl2cppBinarySteps(bytes, metadata, facts, architecture);
|
||||
for (;;) {
|
||||
const step = steps.next();
|
||||
if (step.done) return step.value;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user