Fix persisting $$vencordPatchedSource when a module is loaded again

This commit is contained in:
Nuckyz 2024-08-20 03:56:59 -03:00
parent e4bc7f2add
commit e3d9b2001f
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9

View file

@ -47,8 +47,8 @@ define(Function.prototype, "m", {
set(this: AnyWebpackRequire, originalModules: AnyWebpackRequire["m"]) { set(this: AnyWebpackRequire, originalModules: AnyWebpackRequire["m"]) {
define(this, "m", { value: originalModules }); define(this, "m", { value: originalModules });
// Ensure this is one of Discord main WebpackInstances. // Ensure this is one of Discord main Webpack instances.
// We may catch Discord bundled libs, React Devtools or other extensions WebpackInstances here. // We may catch Discord bundled libs, React Devtools or other extensions Webpack instances here.
const { stack } = new Error(); const { stack } = new Error();
if (!stack?.includes("http") || stack.match(/at \d+? \(/) || !String(this).includes("exports:{}")) { if (!stack?.includes("http") || stack.match(/at \d+? \(/) || !String(this).includes("exports:{}")) {
return; return;
@ -147,21 +147,26 @@ const moduleFactoriesHandler: ProxyHandler<AnyWebpackRequire["m"]> = {
*/ */
function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: AnyModuleFactory, ignoreExistingInTarget: boolean = false) { function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: AnyModuleFactory, ignoreExistingInTarget: boolean = false) {
let existingFactory: TypedPropertyDescriptor<AnyModuleFactory> | undefined; let existingFactory: TypedPropertyDescriptor<AnyModuleFactory> | undefined;
let moduleFactoriesWithFactory: AnyWebpackRequire["m"] | undefined;
for (const wreq of allWebpackInstances) { for (const wreq of allWebpackInstances) {
if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) continue; if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) continue;
if (Reflect.getOwnPropertyDescriptor(wreq.m, id) != null) { if (Reflect.getOwnPropertyDescriptor(wreq.m, id) != null) {
existingFactory = Reflect.getOwnPropertyDescriptor(wreq.m, id); existingFactory = Reflect.getOwnPropertyDescriptor(wreq.m, id);
moduleFactoriesWithFactory = wreq.m;
break; break;
} }
} }
if (existingFactory != null) { if (existingFactory != null) {
// If existingFactory exists in any Webpack instance, it's either wrapped in defineModuleFactoryGetter, or it has already been required. // If existingFactory exists in any Webpack instance, it's either wrapped in defineModuleFactoryGetter, or it has already been required.
// So define the descriptor of it on this current Webpack instance, call Reflect.set with the new original, // So define the descriptor of it on this current Webpack instance (if it doesn't exist already), call Reflect.set with the new original,
// and let the correct logic apply (normal set, or defineModuleFactoryGetter setter) // and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
if (moduleFactoriesWithFactory !== moduleFactoriesTarget) {
Reflect.defineProperty(moduleFactoriesTarget, id, existingFactory); Reflect.defineProperty(moduleFactoriesTarget, id, existingFactory);
}
return Reflect.set(moduleFactoriesTarget, id, newFactory, moduleFactoriesTarget); return Reflect.set(moduleFactoriesTarget, id, newFactory, moduleFactoriesTarget);
} }
@ -184,40 +189,44 @@ function notifyFactoryListeners(factory: AnyModuleFactory) {
} }
/** /**
* Define the getter for returning the patched version of the module factory. * Define the getter for returning the patched or original version of the module factory.
* This properly handles patching the factory and also making sure $$vencordPatchedSource is persisted in the original factory, if it was later loaded again.
* *
* If eagerPatches is enabled, the factory argument should already be the patched version, else it will be the original * If eagerPatches is enabled, the factory argument should already be the patched version, else it will be the original
* and only be patched when accessed for the first time. * and only be patched when accessed for the first time.
* *
* @param id The id of the module * @param id The id of the module
* @param factory The original or patched module factory * @param factory The original or patched module factory
* @param isFactoryRestore Whether we are restoring the original factory after it has been required
*/ */
function defineModulesFactoryGetter(id: PropertyKey, factory: WrappedModuleFactory) { function defineModulesFactoryGetter(id: PropertyKey, factory: WrappedModuleFactory, isFactoryRestore: boolean = false) {
// Define the getter in all the module factories objects. Patches are only executed once, so make sure all module factories object const descriptor: PropertyDescriptor = {
// have the patched version
for (const wreq of allWebpackInstances) {
define(wreq.m, id, {
get() { get() {
// $$vencordOriginal means the factory is already patched // $$vencordOriginal means the factory is already patched
if (factory.$$vencordOriginal != null) { if (factory.$$vencordOriginal != null || isFactoryRestore) {
return factory; return factory;
} }
return (factory = wrapAndPatchFactory(id, factory)); return (factory = wrapAndPatchFactory(id, factory));
}, },
set(newFactory: AnyModuleFactory) { set(newFactory: AnyModuleFactory) {
if (factory.$$vencordOriginal != null) {
factory.toString = newFactory.toString.bind(newFactory);
factory.$$vencordOriginal = newFactory;
if (factory.$$vencordPatchedSource != null) { if (factory.$$vencordPatchedSource != null) {
newFactory.$$vencordPatchedSource = factory.$$vencordPatchedSource; newFactory.$$vencordPatchedSource = factory.$$vencordPatchedSource;
} }
if (factory.$$vencordOriginal != null) {
factory.toString = newFactory.toString.bind(newFactory);
factory.$$vencordOriginal = newFactory;
} else { } else {
factory = newFactory; factory = newFactory;
} }
} }
}); };
// Define the getter in all the module factories objects. Patches are only executed once, so make sure all module factories object
// have the patched version
for (const wreq of allWebpackInstances) {
define(wreq.m, id, descriptor);
} }
} }
@ -232,10 +241,8 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
const patchedFactory = patchFactory(id, originalFactory); const patchedFactory = patchFactory(id, originalFactory);
const wrappedFactory: WrappedModuleFactory = function (...args) { const wrappedFactory: WrappedModuleFactory = function (...args) {
// Restore the original factory in all the module factories objects. We want to make sure the original factory is restored properly, no matter what is the Webpack instance // Restore the original factory
for (const wreq of allWebpackInstances) { defineModulesFactoryGetter(id, wrappedFactory.$$vencordOriginal!, true);
define(wreq.m, id, { value: wrappedFactory.$$vencordOriginal });
}
// eslint-disable-next-line prefer-const // eslint-disable-next-line prefer-const
let [module, exports, require] = args; let [module, exports, require] = args;