mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
fix(il2cpp): select universal binary slices through the shared Mach-O selection (#10661)
* fix(il2cpp): select universal binary slices through the shared Mach-O selection * test(il2cpp): assert selection facts instead of full sentences
This commit is contained in:
1 parent
ef15353c69
commit
2705aad365
11 files changed
+243
-139
No files matched your search
@@ -4,7 +4,10 @@ import { readBinaryContainer } from "../../../src/native/metadata/readBinaryCont
|
||||
import {
|
||||
openImageSteps,
|
||||
readUInt32,
|
||||
requireIl2cppBinaryImage,
|
||||
} from "../../../src/il2cpp/Il2cppBinaryImage.js";
|
||||
import { AnalysisInputError } from "../../../src/domain/analysisErrorCore.js";
|
||||
import type { Il2cppBinaryArchitecture } from "../../../src/domain/il2cpp/il2cppMetadata.js";
|
||||
import { expect, it } from "vitest";
|
||||
import { linkIl2cppBinarySteps } from "../../../src/il2cpp/Il2cppBinaryFile.js";
|
||||
import {
|
||||
@@ -51,8 +54,18 @@ const expectedAddress = (value: bigint | "no-body" | "unknown") =>
|
||||
}
|
||||
: { state: value, address: null, rva: null };
|
||||
const expectedAddresses = FIXTURE_METHOD_ADDRESSES.map(expectedAddress);
|
||||
const link = (bytes: Buffer, architecture?: string) =>
|
||||
linkIl2cppBinary(bytes, metadata, facts, architecture);
|
||||
// The provider selects a universal slice before linking, as here.
|
||||
const link = (bytes: Buffer, architecture?: Il2cppBinaryArchitecture) =>
|
||||
linkIl2cppBinary(
|
||||
requireIl2cppBinaryImage(
|
||||
"inspect_il2cpp_metadata",
|
||||
"/GameAssembly.dylib",
|
||||
bytes,
|
||||
architecture,
|
||||
).bytes,
|
||||
metadata,
|
||||
facts,
|
||||
);
|
||||
// A type-table fallback needs the metadata to reference most of the table, as
|
||||
// real builds do; referencing type 3 requires all four fixture entries.
|
||||
const wholeTable = {
|
||||
@@ -61,9 +74,9 @@ const wholeTable = {
|
||||
};
|
||||
const linkWholeTable = (bytes: Buffer) =>
|
||||
linkIl2cppBinary(bytes, metadata, wholeTable);
|
||||
const failure = (bytes: Buffer, architecture?: string) => {
|
||||
const failure = (bytes: Buffer) => {
|
||||
try {
|
||||
link(bytes, architecture);
|
||||
link(bytes);
|
||||
} catch (cause) {
|
||||
if (cause instanceof Il2cppBinaryFormatFailure)
|
||||
return { reason: cause.reason, kind: cause.kind };
|
||||
@@ -71,6 +84,19 @@ const failure = (bytes: Buffer, architecture?: string) => {
|
||||
}
|
||||
throw new Error("expected a link failure");
|
||||
};
|
||||
/** The input a slice selection asks the caller to correct, and why. */
|
||||
const selectionIssue = (
|
||||
bytes: Buffer,
|
||||
architecture?: Il2cppBinaryArchitecture,
|
||||
) => {
|
||||
try {
|
||||
link(bytes, architecture);
|
||||
} catch (cause) {
|
||||
if (cause instanceof AnalysisInputError) return cause.issues[0];
|
||||
throw cause;
|
||||
}
|
||||
throw new Error("expected a selection failure");
|
||||
};
|
||||
const addElfLoadAlias = (
|
||||
bytes: Buffer,
|
||||
fileOffset: number,
|
||||
@@ -213,7 +239,14 @@ it("does not infer universal Mach-O identity from a FAT-shaped non-Mach-O table"
|
||||
reason: "unsupported-container",
|
||||
kind: "unsupported",
|
||||
});
|
||||
expect(failure(bytes, "arm64").reason).toBe("slice-not-found");
|
||||
// The message names IL2CPP's field and value, never the shared spelling.
|
||||
const issue = selectionIssue(bytes, "arm64");
|
||||
expect(issue).toMatchObject({
|
||||
path: ["binary_architecture"],
|
||||
reason: "invalid_value",
|
||||
});
|
||||
expect(issue?.message).toContain("binary_architecture arm64");
|
||||
expect(issue?.message).not.toMatch(/(?<!binary_)architecture arm64/u);
|
||||
});
|
||||
|
||||
it("requires an explicit slice of a universal Mach-O and reads only that slice", () => {
|
||||
@@ -222,13 +255,41 @@ it("requires an explicit slice of a universal Mach-O and reads only that slice",
|
||||
{ cpu: 0x01000007, bytes: createIl2cppBinaryFixture() },
|
||||
{ cpu: 0x0100000c, bytes: thin },
|
||||
]);
|
||||
expect(failure(universal)).toEqual({
|
||||
reason: "slice-selection-required",
|
||||
kind: "unsupported",
|
||||
expect(selectionIssue(universal)).toMatchObject({
|
||||
path: ["binary_architecture"],
|
||||
reason: "invalid_value",
|
||||
message: expect.stringContaining("select one architecture"),
|
||||
});
|
||||
expect(link(universal, "arm64").method_addresses).toEqual(expectedAddresses);
|
||||
expect(failure(universal, "ppc64").reason).toBe("slice-not-found");
|
||||
expect(failure(thin, "arm64").reason).toBe("slice-not-found");
|
||||
expect(selectionIssue(thin, "arm64")).toMatchObject({
|
||||
path: ["binary_architecture"],
|
||||
});
|
||||
});
|
||||
|
||||
it("selects an exact arm64 subtype beside arm64e and names only IL2CPP selectors", () => {
|
||||
const arm64 = createIl2cppBinaryFixture();
|
||||
const arm64e = createIl2cppBinaryFixture();
|
||||
arm64e.writeUInt32LE(2, 8);
|
||||
const universal = createUniversalFixture([
|
||||
{ cpu: 0x0100000c, bytes: arm64e },
|
||||
{ cpu: 0x0100000c, bytes: arm64 },
|
||||
]);
|
||||
// Match each FAT declaration to the CPU subtype in its slice header.
|
||||
universal.writeUInt32BE(2, 8 + 4);
|
||||
expect(link(universal, "arm64").method_addresses).toEqual(expectedAddresses);
|
||||
// arm64e is not an IL2CPP value, so no selector is offered for that slice.
|
||||
expect(
|
||||
requireIl2cppBinaryImage(
|
||||
"inspect_il2cpp_metadata",
|
||||
"/GameAssembly.dylib",
|
||||
universal,
|
||||
"arm64",
|
||||
).limitations,
|
||||
).toEqual([
|
||||
expect.stringContaining(
|
||||
"arm64 (subtype 0x2) at file offset 4096 (no binary_architecture value selects it)",
|
||||
),
|
||||
]);
|
||||
});
|
||||
|
||||
it("rejects a universal Mach-O whose slices overlap before selecting one", () => {
|
||||
@@ -241,9 +302,9 @@ it("rejects a universal Mach-O whose slices overlap before selecting one", () =>
|
||||
const first = universal.readUInt32BE(8 + 8);
|
||||
const second = universal.readUInt32BE(8 + 20 + 8);
|
||||
universal.writeUInt32BE(second - first + 16, 8 + 12);
|
||||
expect(failure(universal, "arm64")).toEqual({
|
||||
reason: "malformed-container",
|
||||
kind: "malformed",
|
||||
expect(selectionIssue(universal, "arm64")).toMatchObject({
|
||||
path: ["binary_path"],
|
||||
reason: "invalid_format",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1118,17 +1179,11 @@ it("guards a shared generic expansion before concatenating unchecked text", () =
|
||||
}
|
||||
bytes.writeBigUInt64LE(prior, 0x900);
|
||||
const stopped = new Error("type text admission");
|
||||
const steps = linkIl2cppBinarySteps(
|
||||
bytes,
|
||||
expanded.metadata,
|
||||
expanded.link,
|
||||
undefined,
|
||||
{
|
||||
beforeText: (size) => {
|
||||
if (size > 1024) throw stopped;
|
||||
},
|
||||
const steps = linkIl2cppBinarySteps(bytes, expanded.metadata, expanded.link, {
|
||||
beforeText: (size) => {
|
||||
if (size > 1024) throw stopped;
|
||||
},
|
||||
);
|
||||
});
|
||||
expect(() => {
|
||||
for (;;) {
|
||||
if (steps.next().done) break;
|
||||
@@ -1194,10 +1249,7 @@ it.each([
|
||||
] as const)(
|
||||
"uses the four-byte count span for a dyld bind at %s",
|
||||
(slot, raw, expected) => {
|
||||
const steps = openImageSteps(
|
||||
createIl2cppDyldBindFixture(raw, slot),
|
||||
undefined,
|
||||
);
|
||||
const steps = openImageSteps(createIl2cppDyldBindFixture(raw, slot));
|
||||
let step = steps.next();
|
||||
while (!step.done) step = steps.next();
|
||||
expect(readUInt32(step.value, FIXTURE_BINARY_BASE + 0x600n)).toBe(expected);
|
||||
@@ -1305,13 +1357,19 @@ it("refuses an ambiguous architecture instead of choosing the first FAT slice",
|
||||
{ cpu: 0x0100000c, bytes: createIl2cppBinaryFixture() },
|
||||
{ cpu: 0x0100000c, bytes: createIl2cppBinaryFixture() },
|
||||
]);
|
||||
expect(failure(bytes, "arm64").reason).toBe("ambiguous-slice-selection");
|
||||
expect(selectionIssue(bytes, "arm64")).toMatchObject({
|
||||
path: ["binary_architecture"],
|
||||
message: expect.stringContaining("no slice was selected"),
|
||||
});
|
||||
});
|
||||
it("checks the observed slice header against the caller-selected FAT architecture", () => {
|
||||
const bytes = createUniversalFixture([
|
||||
{ cpu: 0x01000007, bytes: createIl2cppBinaryFixture() },
|
||||
]);
|
||||
expect(failure(bytes, "amd64").reason).toBe("malformed-container");
|
||||
expect(selectionIssue(bytes, "amd64")).toMatchObject({
|
||||
path: ["binary_path"],
|
||||
reason: "invalid_format",
|
||||
});
|
||||
});
|
||||
it.each([1, 0x80000000])(
|
||||
"checks the complete FAT CPU subtype including capability bits (%i)",
|
||||
@@ -1319,7 +1377,10 @@ it.each([1, 0x80000000])(
|
||||
const thin = createIl2cppBinaryFixture();
|
||||
thin.writeUInt32LE(subtype, 8);
|
||||
const bytes = createUniversalFixture([{ cpu: 0x0100000c, bytes: thin }]);
|
||||
expect(failure(bytes, "arm64").reason).toBe("malformed-container");
|
||||
expect(selectionIssue(bytes, "arm64")).toMatchObject({
|
||||
path: ["binary_path"],
|
||||
reason: "invalid_format",
|
||||
});
|
||||
bytes.writeUInt32BE(subtype, 12);
|
||||
expect(link(bytes, "arm64").method_addresses).toEqual(expectedAddresses);
|
||||
},
|
||||
@@ -1381,7 +1442,7 @@ const linkGeneric = (
|
||||
bytes = createIl2cppBinaryFixture({ genericMethods: options }),
|
||||
) => {
|
||||
const decoded = genericFacts(version);
|
||||
return linkIl2cppBinary(bytes, decoded.metadata, decoded.link, undefined, {
|
||||
return linkIl2cppBinary(bytes, decoded.metadata, decoded.link, {
|
||||
genericInstances: true,
|
||||
});
|
||||
};
|
||||
@@ -1437,7 +1498,6 @@ it("admits every generic row and issue before retention", () => {
|
||||
createIl2cppBinaryFixture({ genericMethods: { firstSpecMethod: 9 } }),
|
||||
decoded.metadata,
|
||||
decoded.link,
|
||||
undefined,
|
||||
{
|
||||
genericInstances: true,
|
||||
onRow: (row) => rows.push(row),
|
||||
@@ -1461,7 +1521,6 @@ it("admits every generic row and issue before retention", () => {
|
||||
createIl2cppBinaryFixture({ genericMethods: {} }),
|
||||
decoded.metadata,
|
||||
decoded.link,
|
||||
undefined,
|
||||
{
|
||||
genericInstances: true,
|
||||
onRow: (row) => refused.push(row),
|
||||
@@ -1597,7 +1656,6 @@ it("refuses generic linking when the type table was inferred", () => {
|
||||
...decoded.link,
|
||||
returnTypes: Int32Array.from(decoded.link.returnTypes, () => 3),
|
||||
},
|
||||
undefined,
|
||||
{ genericInstances: true },
|
||||
),
|
||||
).toThrow(
|
||||
@@ -1726,7 +1784,6 @@ it("keeps a method spec unnamed when its owner's container is out of range", ()
|
||||
createIl2cppBinaryFixture({ genericMethods: {} }),
|
||||
decoded.metadata,
|
||||
link,
|
||||
undefined,
|
||||
{ genericInstances: true },
|
||||
).generic_methods!;
|
||||
expect(generic.instances).toEqual([
|
||||
@@ -1826,7 +1883,7 @@ const linkLowBase = (
|
||||
const decoded = genericFacts(31);
|
||||
return drain(
|
||||
linkGenericMethodsSteps(
|
||||
drain(openImageSteps(fixture.bytes, undefined)),
|
||||
drain(openImageSteps(fixture.bytes)),
|
||||
decoded.metadata,
|
||||
decoded.link,
|
||||
fixture.modulesField,
|
||||
@@ -1906,7 +1963,6 @@ it("keeps a capacity refusal while naming a generic row a refusal", () => {
|
||||
createIl2cppBinaryFixture({ genericMethods: {} }),
|
||||
decoded.metadata,
|
||||
decoded.link,
|
||||
undefined,
|
||||
{
|
||||
genericInstances: true,
|
||||
beforeText: (bytes) => {
|
||||
|
||||
Reference in new issue
Block a user