Skip to content

Commit

Permalink
[metro] Rewrite patches
Browse files Browse the repository at this point in the history
  • Loading branch information
pylixonly committed Apr 2, 2024
1 parent 9809ef2 commit 01c1694
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 48 deletions.
93 changes: 47 additions & 46 deletions src/lib/metro/filters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { instead } from "@lib/api/patcher";
import { after, instead } from "@lib/api/patcher";

export type MetroModules = { [id: string]: any; };
export type PropIntellisense<P extends string | symbol> = Record<P, any> & Record<PropertyKey, any>;
Expand All @@ -17,21 +17,33 @@ const blacklist = (id: string) => Object.defineProperty(window.modules, id, {
writable: true
});

const functionToString = Function.prototype.toString;

for (const id in window.modules) {
const moduleDefinition = window.modules[id];

if (moduleDefinition.factory) {
after("factory", moduleDefinition, ({ 4: moduleObject }) => {
if (moduleObject.exports) onModuleRequire(moduleObject.exports);
});
}
}


// Blacklist any "bad-actor" modules, e.g. the dreaded null proxy, the window itself, or undefined modules
for (const id in window.modules) {
const module = window.modules[id]?.publicModule?.exports;
const module = requireModule(id);

if (!module || module === window || module.proxygone === null) {
if (!module || module === window || module["Revenge EOL when?"] === null) {
blacklist(id);
continue;
}
}

const checked = new WeakSet();
const onModuleCheck = (exports: any) => {
if (typeof exports !== "object" || checked.has(exports)) return;
checked.add(exports);
let patchedInspectSource = false;

function onModuleRequire(exports: any) {
// There are modules registering the same native component
if (exports?.default?.name === "requireNativeComponent") {
instead("default", exports, (args, orig) => {
try {
Expand All @@ -42,6 +54,7 @@ const onModuleCheck = (exports: any) => {
});
}

// Hook DeveloperExperimentStore
if (exports?.default?.constructor?.displayName === "DeveloperExperimentStore") {
exports.default = new Proxy(exports.default, {
get: (target, property, receiver) => {
Expand All @@ -55,61 +68,49 @@ const onModuleCheck = (exports: any) => {
}
});
}
};

function maybeInitializeModule(id: string) {
if (modules[id].isInitialized) return;
// Funny infinity recursion caused by a race condition
if (!patchedInspectSource && window["__core-js_shared__"]) {
const inspect = (f: Function) => typeof f === "function" && functionToString.apply(f, []);
window["__core-js_shared__"].inspectSource = inspect;
patchedInspectSource = true;
}
}

try {
// There's a dum Function.prototype.toString polyfill somewhere in Discord's codebase
const orig = Function.prototype.toString;
Object.defineProperty(Function.prototype, "toString", {
value: orig,
configurable: true,
writable: false
});
function requireModule(id: string) {
if (modules[id].isInitialized) return __r(id);

// Disable Internal Metro error reporting logic
const originalHandler = window.ErrorUtils.getGlobalHandler();
window.ErrorUtils.setGlobalHandler(null);
// Disable Internal Metro error reporting logic
const originalHandler = window.ErrorUtils.getGlobalHandler();
window.ErrorUtils.setGlobalHandler(null);

__r(id); // metroRequire(id);
try {
var exports = __r(id); // metroRequire(id);
} catch {
var exports = undefined;
}

// Done initializing! Now, revert our hacks
window.ErrorUtils.setGlobalHandler(originalHandler);
Object.defineProperty(Function.prototype, "toString", {
value: orig,
configurable: true,
writable: true
});
} catch { }
// Done initializing! Now, revert our hacks
window.ErrorUtils.setGlobalHandler(originalHandler);

return exports;
}

// Function to filter through modules
const filterModules = (modules: MetroModules, single = false) => (filter: (m: any) => boolean) => {
const found = [];

for (const id in modules) {
const module = modules[id]?.publicModule?.exports;

maybeInitializeModule(id);

if (!module) {
blacklist(id);
continue;
}

onModuleCheck(module);
const exports = requireModule(id);

if (module.default && module.__esModule && filter(module.default)) {
if (single) return module.default;
found.push(module.default);
if (exports.default && exports.__esModule && filter(exports.default)) {
if (single) return exports.default;
found.push(exports.default);
}

if (filter(module)) {
if (single) return module;
else found.push(module);
if (filter(exports)) {
if (single) return exports;
else found.push(exports);
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/lib/utils/logger.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { findByProps } from "@metro/filters";
import { findByName } from "@metro/filters";

export type LoggerFunction = (...messages: any[]) => void;
export interface Logger {
Expand All @@ -11,5 +11,5 @@ export interface Logger {
verbose: LoggerFunction;
}

export const logModule = findByProps("setLogFn").default;
export const logModule = findByName("Logger");
export const logger: Logger = new logModule("Bunny");
30 changes: 30 additions & 0 deletions src/lib/utils/onceDefined.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { LiteralUnion } from "type-fest";

type KeyOfOrAny<P, T extends object> = P extends keyof T ? T[P] : any;

export function hookDefineProperty<
T extends object,
P extends LiteralUnion<keyof T, PropertyKey>
>(target: T, property: LiteralUnion<keyof T, PropertyKey>, cb: (val: KeyOfOrAny<P, T>) => KeyOfOrAny<P, T>) {
const targetAsAny = target as any;

if (property in target) {
return void cb(targetAsAny[property]);
}

let value: any;

Object.defineProperty(targetAsAny, property, {
get: () => value,
set(v) {
value = cb(v) ?? v;
},
configurable: true,
enumerable: false
});

return () => {
delete targetAsAny[property];
targetAsAny[property] = value;
};
}

0 comments on commit 01c1694

Please sign in to comment.