2022-10-22 01:17:06 +02:00
/ *
2025-02-08 00:07:17 +01:00
* Vencord , a Discord client mod
* Copyright ( c ) 2024 Vendicated , Nuckyz , and contributors
* SPDX - License - Identifier : GPL - 3.0 - or - later
* /
2022-10-22 01:17:06 +02:00
2025-02-08 00:07:17 +01:00
import { Settings } from "@api/Settings" ;
import { makeLazy } from "@utils/lazy" ;
2023-05-06 01:36:00 +02:00
import { Logger } from "@utils/Logger" ;
2025-02-08 00:07:17 +01:00
import { interpolateIfDefined } from "@utils/misc" ;
2024-06-28 04:37:10 +02:00
import { canonicalizeReplacement } from "@utils/patches" ;
2025-02-12 18:03:18 +01:00
import { Patch , PatchReplacement } from "@utils/types" ;
2022-11-28 13:37:55 +01:00
2025-02-08 00:07:17 +01:00
import { traceFunctionWithResults } from "../debug/Tracer" ;
2025-02-12 18:03:18 +01:00
import { _initWebpack , _shouldIgnoreModule , factoryListeners , findModuleId , moduleListeners , waitForSubscriptions , wreq } from "./webpack" ;
import { AnyModuleFactory , AnyWebpackRequire , MaybePatchedModuleFactory , ModuleExports , PatchedModuleFactory , WebpackRequire } from "./wreq.d" ;
export const patches = [ ] as Patch [ ] ;
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
export const SYM_ORIGINAL_FACTORY = Symbol ( "WebpackPatcher.originalFactory" ) ;
export const SYM_PATCHED_SOURCE = Symbol ( "WebpackPatcher.patchedSource" ) ;
export const SYM_PATCHED_BY = Symbol ( "WebpackPatcher.patchedBy" ) ;
export const allWebpackInstances = new Set < AnyWebpackRequire > ( ) ;
2022-08-29 02:25:27 +02:00
2025-02-12 18:03:18 +01:00
export const patchTimings = [ ] as Array < [ plugin : string , moduleId : PropertyKey , match : PatchReplacement [ "match" ] , totalTime : number ] > ;
2022-08-29 02:25:27 +02:00
2025-02-08 00:07:17 +01:00
export const getBuildNumber = makeLazy ( ( ) = > {
try {
try {
if ( wreq . m [ 128014 ] ? . toString ( ) . includes ( "Trying to open a changelog for an invalid build number" ) ) {
const hardcodedGetBuildNumber = wreq ( 128014 ) . b as ( ) = > number ;
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
if ( typeof hardcodedGetBuildNumber === "function" && typeof hardcodedGetBuildNumber ( ) === "number" ) {
return hardcodedGetBuildNumber ( ) ;
}
2024-05-02 23:52:41 +02:00
}
2025-02-08 00:07:17 +01:00
} catch { }
const moduleId = findModuleId ( "Trying to open a changelog for an invalid build number" ) ;
if ( moduleId == null ) {
return - 1 ;
2024-05-02 23:52:41 +02:00
}
2022-08-29 20:27:47 +02:00
2025-02-08 00:07:17 +01:00
const exports = Object . values < ModuleExports > ( wreq ( moduleId ) ) ;
if ( exports . length !== 1 || typeof exports [ 0 ] !== "function" ) {
return - 1 ;
}
const buildNumber = exports [ 0 ] ( ) ;
return typeof buildNumber === "number" ? buildNumber : - 1 ;
} catch {
return - 1 ;
2024-05-02 23:52:41 +02:00
}
} ) ;
2025-02-12 18:03:18 +01:00
export function getFactoryPatchedSource ( moduleId : PropertyKey , webpackRequire = wreq as AnyWebpackRequire ) {
return webpackRequire . m [ moduleId ] ? . [ SYM_PATCHED_SOURCE ] ;
}
export function getFactoryPatchedBy ( moduleId : PropertyKey , webpackRequire = wreq as AnyWebpackRequire ) {
return webpackRequire . m [ moduleId ] ? . [ SYM_PATCHED_BY ] ;
}
const logger = new Logger ( "WebpackInterceptor" , "#8caaee" ) ;
/** Whether we tried to fallback to the WebpackRequire of the factory, or disabled patches */
let wreqFallbackApplied = false ;
const define : typeof Reflect . defineProperty = ( target , p , attributes ) = > {
2025-02-08 00:07:17 +01:00
if ( Object . hasOwn ( attributes , "value" ) ) {
attributes . writable = true ;
}
2024-06-28 23:17:38 +02:00
2025-02-08 00:07:17 +01:00
return Reflect . defineProperty ( target , p , {
configurable : true ,
enumerable : true ,
. . . attributes
} ) ;
} ;
2025-02-12 18:03:18 +01:00
// wreq.m is the Webpack object containing module factories. It is pre-populated with factories, and is also populated via webpackGlobal.push
// We use this setter to intercept when wreq.m is defined and apply patching to its factories.
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
// Factories can be patched in two ways. Eagerly or lazily.
// If we are patching eagerly, pre-populated factories are patched immediately and new factories are patched when set.
// Else, we only patch them when called.
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
// Factories are always wrapped in a proxy, which allows us to intercept the call to them, patch if they werent eagerly patched,
// and call them with our wrapper which notifies our listeners.
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
// wreq.m is also wrapped in a proxy to intercept when new factories are set, patch them eargely, if enabled, and wrap them in the factory proxy.
2025-02-08 00:07:17 +01:00
// If this is the main Webpack, we also set up the internal references to WebpackRequire.
define ( Function . prototype , "m" , {
enumerable : false ,
set ( this : AnyWebpackRequire , originalModules : AnyWebpackRequire [ "m" ] ) {
define ( this , "m" , { value : originalModules } ) ;
// Ensure this is one of Discord main Webpack instances.
// We may catch Discord bundled libs, React Devtools or other extensions Webpack instances here.
2024-05-05 18:58:23 +02:00
const { stack } = new Error ( ) ;
2025-02-08 00:07:17 +01:00
if ( ! stack ? . includes ( "http" ) || stack . match ( /at \d+? \(/ ) || ! String ( this ) . includes ( "exports:{}" ) ) {
2024-06-28 23:17:38 +02:00
return ;
}
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
const fileName = stack . match ( /\/assets\/(.+?\.js)/ ) ? . [ 1 ] ;
logger . info ( "Found Webpack module factories" + interpolateIfDefined ` in ${ fileName } ` ) ;
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
allWebpackInstances . add ( this ) ;
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
// Define a setter for the ensureChunk property of WebpackRequire. Only the main Webpack (which is the only that includes chunk loading) has this property.
2024-06-28 23:17:38 +02:00
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
2025-02-08 00:07:17 +01:00
define ( this , "e" , {
enumerable : false ,
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
set ( this : WebpackRequire , ensureChunk : WebpackRequire [ "e" ] ) {
define ( this , "e" , { value : ensureChunk } ) ;
2024-06-28 23:17:38 +02:00
clearTimeout ( setterTimeout ) ;
2024-06-28 04:37:10 +02:00
2025-02-08 00:07:17 +01:00
logger . info ( "Main WebpackInstance found" + interpolateIfDefined ` in ${ fileName } ` + ", initializing internal references to WebpackRequire" ) ;
2024-06-28 23:17:38 +02:00
_initWebpack ( this ) ;
}
2024-05-02 23:52:41 +02:00
} ) ;
2024-06-28 23:17:38 +02:00
// setImmediate to clear this property setter if this is not the main Webpack.
2025-02-08 00:07:17 +01:00
// If this is the main Webpack, wreq.e will always be set before the timeout runs.
const setterTimeout = setTimeout ( ( ) = > Reflect . deleteProperty ( this , "e" ) , 0 ) ;
// Patch the pre-populated factories
2025-02-12 18:03:18 +01:00
for ( const moduleId in originalModules ) {
const originalFactory = originalModules [ moduleId ] ;
if ( updateExistingFactory ( originalModules , moduleId , originalFactory , originalModules , true ) ) {
2025-02-08 00:07:17 +01:00
continue ;
}
2025-02-12 18:03:18 +01:00
notifyFactoryListeners ( moduleId , originalFactory ) ;
const proxiedFactory = new Proxy ( Settings . eagerPatches ? patchFactory ( moduleId , originalFactory ) : originalFactory , moduleFactoryHandler ) ;
define ( originalModules , moduleId , { value : proxiedFactory } ) ;
2025-02-08 00:07:17 +01:00
}
define ( originalModules , Symbol . toStringTag , {
value : "ModuleFactories" ,
enumerable : false
} ) ;
const proxiedModuleFactories = new Proxy ( originalModules , moduleFactoriesHandler ) ;
/ *
If Webpack ever decides to set module factories using the variable of the modules object directly , instead of wreq . m , switch the proxy to the prototype
Reflect . setPrototypeOf ( originalModules , new Proxy ( originalModules , moduleFactoriesHandler ) ) ;
* /
define ( this , "m" , { value : proxiedModuleFactories } ) ;
2024-05-02 23:52:41 +02:00
}
} ) ;
2022-08-29 02:25:27 +02:00
2025-02-12 18:03:18 +01:00
// The proxy for patching eagerly and/or wrapping factories in their proxy.
2025-02-08 00:07:17 +01:00
const moduleFactoriesHandler : ProxyHandler < AnyWebpackRequire [ " m " ] > = {
/ *
If Webpack ever decides to set module factories using the variable of the modules object directly instead of wreq . m , we need to switch the proxy to the prototype
and that requires defining additional traps for keeping the object working
// Proxies on the prototype don't intercept "get" when the property is in the object itself. But in case it isn't we need to return undefined,
// to avoid Reflect.get having no effect and causing a stack overflow
get ( target , p , receiver ) {
return undefined ;
} ,
// Same thing as get
has ( target , p ) {
return false ;
} ,
* /
set ( target , p , newValue , receiver ) {
2025-02-12 18:03:18 +01:00
if ( updateExistingFactory ( target , p , newValue , receiver ) ) {
2025-02-08 00:07:17 +01:00
return true ;
}
2025-02-12 18:03:18 +01:00
notifyFactoryListeners ( p , newValue ) ;
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
const proxiedFactory = new Proxy ( Settings . eagerPatches ? patchFactory ( p , newValue ) : newValue , moduleFactoryHandler ) ;
return Reflect . set ( target , p , proxiedFactory , receiver ) ;
}
} ;
// The proxy for patching lazily and/or running factories with our wrapper.
const moduleFactoryHandler : ProxyHandler < MaybePatchedModuleFactory > = {
apply ( target , thisArg : unknown , argArray : Parameters < AnyModuleFactory > ) {
// SAFETY: Factories have `name` as their key in the module factories object, and that is always their module id
const moduleId = target . name ;
// SYM_ORIGINAL_FACTORY means the factory has already been patched
if ( target [ SYM_ORIGINAL_FACTORY ] != null ) {
return runFactoryWithWrap ( moduleId , target as PatchedModuleFactory , thisArg , argArray ) ;
}
const patchedFactory = patchFactory ( moduleId , target ) ;
return runFactoryWithWrap ( moduleId , patchedFactory , thisArg , argArray ) ;
} ,
get ( target , p , receiver ) {
if ( target [ SYM_ORIGINAL_FACTORY ] != null && ( p === SYM_PATCHED_SOURCE || p === SYM_PATCHED_BY ) ) {
return Reflect . get ( target [ SYM_ORIGINAL_FACTORY ] , p , target [ SYM_ORIGINAL_FACTORY ] ) ;
}
const v = Reflect . get ( target , p , receiver ) ;
// Make proxied factories `toString` return their original factory `toString`
if ( p === "toString" ) {
return v . bind ( target [ SYM_ORIGINAL_FACTORY ] ? ? target ) ;
}
return v ;
2025-02-08 00:07:17 +01:00
}
} ;
/ * *
* Update a factory that exists in any Webpack instance with a new original factory .
*
2025-02-12 18:03:18 +01:00
* @param moduleFactoriesTarget The module factories where this new original factory is being set
* @param moduleId The id of the module
2025-02-08 00:07:17 +01:00
* @param newFactory The new original factory
2025-02-12 18:03:18 +01:00
* @param receiver The receiver of the new factory
2025-02-08 00:07:17 +01:00
* @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget
* @returns Whether the original factory was updated , or false if it doesn ' t exist in any Webpack instance
* /
2025-02-12 18:03:18 +01:00
function updateExistingFactory ( moduleFactoriesTarget : AnyWebpackRequire [ "m" ] , moduleId : PropertyKey , newFactory : AnyModuleFactory , receiver : any , ignoreExistingInTarget : boolean = false ) {
2025-02-08 00:07:17 +01:00
let existingFactory : TypedPropertyDescriptor < AnyModuleFactory > | undefined ;
let moduleFactoriesWithFactory : AnyWebpackRequire [ "m" ] | undefined ;
for ( const wreq of allWebpackInstances ) {
2025-02-12 18:03:18 +01:00
if ( ignoreExistingInTarget && wreq . m === moduleFactoriesTarget ) {
continue ;
}
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
if ( Object . hasOwn ( wreq . m , moduleId ) ) {
existingFactory = Reflect . getOwnPropertyDescriptor ( wreq . m , moduleId ) ;
2025-02-08 00:07:17 +01:00
moduleFactoriesWithFactory = wreq . m ;
break ;
}
}
if ( existingFactory != null ) {
2025-02-12 18:03:18 +01:00
// If existingFactory exists in any Webpack instance, it's either wrapped in our proxy, or it has already been required.
// In the case it is wrapped in our proxy, we need the Webpack instance with this new original factory to also have our proxy.
// So, define the descriptor of the existing factory on it.
2025-02-08 00:07:17 +01:00
if ( moduleFactoriesWithFactory !== moduleFactoriesTarget ) {
2025-02-12 18:03:18 +01:00
Reflect . defineProperty ( receiver , moduleId , existingFactory ) ;
}
const existingFactoryValue = moduleFactoriesWithFactory ! [ moduleId ] ;
// Update with the new original factory, if it does have a current original factory
if ( existingFactoryValue [ SYM_ORIGINAL_FACTORY ] != null ) {
existingFactoryValue [ SYM_ORIGINAL_FACTORY ] = newFactory ;
2025-02-08 00:07:17 +01:00
}
2025-02-12 18:03:18 +01:00
// Persist patched source and patched by in the new original factory
if ( IS_DEV ) {
newFactory [ SYM_PATCHED_SOURCE ] = existingFactoryValue [ SYM_PATCHED_SOURCE ] ;
newFactory [ SYM_PATCHED_BY ] = existingFactoryValue [ SYM_PATCHED_BY ] ;
2025-02-08 00:07:17 +01:00
}
2025-02-12 18:03:18 +01:00
return true ;
2025-02-08 00:07:17 +01:00
}
return false ;
}
/ * *
* Notify all factory listeners .
*
2025-02-12 18:03:18 +01:00
* @param moduleId The id of the module
2025-02-08 00:07:17 +01:00
* @param factory The original factory to notify for
* /
2025-02-12 18:03:18 +01:00
function notifyFactoryListeners ( moduleId : PropertyKey , factory : AnyModuleFactory ) {
2025-02-08 00:07:17 +01:00
for ( const factoryListener of factoryListeners ) {
2022-08-29 02:25:27 +02:00
try {
2025-02-12 18:03:18 +01:00
factoryListener ( factory , moduleId ) ;
2022-08-29 02:25:27 +02:00
} catch ( err ) {
2025-02-08 00:07:17 +01:00
logger . error ( "Error in Webpack factory listener:\n" , err , factoryListener ) ;
2022-08-29 02:25:27 +02:00
}
}
2025-02-08 00:07:17 +01:00
}
2022-08-29 02:25:27 +02:00
2025-02-08 00:07:17 +01:00
/ * *
2025-02-12 18:03:18 +01:00
* Run a ( possibly ) patched module factory with a wrapper which notifies our listeners .
2025-02-08 00:07:17 +01:00
*
2025-02-12 18:03:18 +01:00
* @param moduleId The id of the module
* @param patchedFactory The ( possibly ) patched module factory
* @param thisArg The ` value ` of the call to the factory
* @param argArray The arguments of the call to the factory
2025-02-08 00:07:17 +01:00
* /
2025-02-12 18:03:18 +01:00
function runFactoryWithWrap ( moduleId : PropertyKey , patchedFactory : PatchedModuleFactory , thisArg : unknown , argArray : Parameters < MaybePatchedModuleFactory > ) {
const originalFactory = patchedFactory [ SYM_ORIGINAL_FACTORY ] ;
2024-05-02 23:52:41 +02:00
2025-02-12 18:03:18 +01:00
if ( patchedFactory === originalFactory ) {
// @ts-expect-error Clear up ORIGINAL_FACTORY if the factory did not have any patch applied
delete patchedFactory [ SYM_ORIGINAL_FACTORY ] ;
}
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
// Restore the original factory in all the module factories objects, discarding our proxy and allowing it to be garbage collected
2025-02-08 00:07:17 +01:00
for ( const wreq of allWebpackInstances ) {
2025-02-12 18:03:18 +01:00
define ( wreq . m , moduleId , { value : originalFactory } ) ;
2025-02-08 00:07:17 +01:00
}
2025-02-12 18:03:18 +01:00
let [ module , exports , require ] = argArray ;
2023-10-26 21:21:21 +02:00
2025-02-12 18:03:18 +01:00
if ( wreq == null ) {
if ( ! wreqFallbackApplied ) {
wreqFallbackApplied = true ;
2024-05-02 23:52:41 +02:00
2025-02-12 18:03:18 +01:00
// Make sure the require argument is actually the WebpackRequire function
if ( typeof require === "function" && require . m != null ) {
const { stack } = new Error ( ) ;
const webpackInstanceFileName = stack ? . match ( /\/assets\/(.+?\.js)/ ) ? . [ 1 ] ;
2023-10-26 21:21:21 +02:00
2025-02-12 18:03:18 +01:00
logger . warn (
"WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called wrapped module factory (" +
` id: ${ String ( moduleId ) } ` + interpolateIfDefined ` , WebpackInstance origin: ${ webpackInstanceFileName } ` +
")"
) ;
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
// Could technically be wrong, but it's better than nothing
_initWebpack ( require as WebpackRequire ) ;
2025-02-08 00:07:17 +01:00
} else if ( IS_DEV ) {
2025-02-12 18:03:18 +01:00
logger . error ( "WebpackRequire was not initialized, running modules without patches instead." ) ;
return originalFactory . apply ( thisArg , argArray ) ;
2024-05-02 23:52:41 +02:00
}
2025-02-12 18:03:18 +01:00
} else if ( IS_DEV ) {
return originalFactory . apply ( thisArg , argArray ) ;
2025-02-08 00:07:17 +01:00
}
2025-02-12 18:03:18 +01:00
}
2024-05-02 23:52:41 +02:00
2025-02-12 18:03:18 +01:00
let factoryReturn : unknown ;
try {
factoryReturn = patchedFactory . apply ( thisArg , argArray ) ;
} catch ( err ) {
// Just re-throw Discord errors
if ( patchedFactory === originalFactory ) {
throw err ;
2025-02-08 00:07:17 +01:00
}
2023-10-26 21:21:21 +02:00
2025-02-12 18:03:18 +01:00
logger . error ( "Error in patched module factory:\n" , err ) ;
return originalFactory . apply ( thisArg , argArray ) ;
}
2023-10-26 21:21:21 +02:00
2025-02-12 18:03:18 +01:00
exports = module . exports ;
if ( exports == null ) {
return factoryReturn ;
}
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
if ( typeof require === "function" ) {
const shouldIgnoreModule = _shouldIgnoreModule ( exports ) ;
if ( shouldIgnoreModule ) {
if ( require . c != null ) {
Object . defineProperty ( require . c , moduleId , {
value : require.c [ moduleId ] ,
enumerable : false ,
configurable : true ,
writable : true
} ) ;
2023-10-26 21:21:21 +02:00
}
2025-02-12 18:03:18 +01:00
return factoryReturn ;
2025-02-08 00:07:17 +01:00
}
2025-02-12 18:03:18 +01:00
}
2023-10-26 21:21:21 +02:00
2025-02-12 18:03:18 +01:00
for ( const callback of moduleListeners ) {
try {
callback ( exports , moduleId ) ;
} catch ( err ) {
logger . error ( "Error in Webpack module listener:\n" , err , callback ) ;
2025-02-08 00:07:17 +01:00
}
2025-02-12 18:03:18 +01:00
}
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
for ( const [ filter , callback ] of waitForSubscriptions ) {
try {
if ( filter ( exports ) ) {
waitForSubscriptions . delete ( filter ) ;
callback ( exports , moduleId ) ;
continue ;
}
2023-10-26 21:21:21 +02:00
2025-02-12 18:03:18 +01:00
if ( typeof exports !== "object" ) {
continue ;
}
2025-01-30 18:54:41 +01:00
2025-02-12 18:03:18 +01:00
for ( const exportKey in exports ) {
const exportValue = exports [ exportKey ] ;
2025-01-30 18:54:41 +01:00
2025-02-12 18:03:18 +01:00
if ( exportValue != null && filter ( exportValue ) ) {
waitForSubscriptions . delete ( filter ) ;
callback ( exportValue , moduleId ) ;
break ;
2023-10-26 21:21:21 +02:00
}
}
2025-02-12 18:03:18 +01:00
} catch ( err ) {
logger . error ( "Error while firing callback for Webpack waitFor subscription:\n" , err , filter , callback ) ;
2025-02-08 00:07:17 +01:00
}
}
2025-02-12 18:03:18 +01:00
return factoryReturn ;
2025-02-08 00:07:17 +01:00
}
/ * *
* Patches a module factory .
*
2025-02-12 18:03:18 +01:00
* @param moduleId The id of the module
* @param originalFactory The original module factory
* @returns The patched module factory
2025-02-08 00:07:17 +01:00
* /
2025-02-12 18:03:18 +01:00
function patchFactory ( moduleId : PropertyKey , originalFactory : AnyModuleFactory ) : PatchedModuleFactory {
2025-02-08 00:07:17 +01:00
// 0, prefix to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
2025-02-12 18:03:18 +01:00
let code : string = "0," + String ( originalFactory ) ;
2025-02-08 00:07:17 +01:00
let patchedSource = code ;
2025-02-12 18:03:18 +01:00
let patchedFactory = originalFactory ;
2025-02-08 00:07:17 +01:00
const patchedBy = new Set < string > ( ) ;
for ( let i = 0 ; i < patches . length ; i ++ ) {
const patch = patches [ i ] ;
const moduleMatches = typeof patch . find === "string"
? code . includes ( patch . find )
: ( patch . find . global && ( patch . find . lastIndex = 0 ) , patch . find . test ( code ) ) ;
if ( ! moduleMatches ) {
continue ;
2024-05-02 23:52:41 +02:00
}
2025-02-12 18:03:18 +01:00
// Eager patches cannot retrieve the build number because this code runs before the module for it is loaded
const buildNumber = Settings . eagerPatches ? - 1 : getBuildNumber ( ) ;
2025-02-09 01:46:08 +01:00
const shouldCheckBuildNumber = ! Settings . eagerPatches && buildNumber !== - 1 ;
2023-11-22 07:23:21 +01:00
2025-02-08 00:07:17 +01:00
if (
2025-02-09 01:46:08 +01:00
shouldCheckBuildNumber &&
2025-02-08 00:07:17 +01:00
( patch . fromBuild != null && buildNumber < patch . fromBuild ) ||
( patch . toBuild != null && buildNumber > patch . toBuild )
) {
continue ;
}
2023-11-22 07:23:21 +01:00
2025-02-08 00:07:17 +01:00
const executePatch = traceFunctionWithResults ( ` patch by ${ patch . plugin } ` , ( match : string | RegExp , replace : string ) = > {
if ( typeof match !== "string" && match . global ) {
match . lastIndex = 0 ;
}
2023-10-26 21:21:21 +02:00
2025-02-08 00:07:17 +01:00
return code . replace ( match , replace ) ;
} ) ;
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
const previousCode = code ;
2025-02-12 18:03:18 +01:00
const previousFactory = originalFactory ;
2025-02-08 00:07:17 +01:00
let markedAsPatched = false ;
// We change all patch.replacement to array in plugins/index
for ( const replacement of patch . replacement as PatchReplacement [ ] ) {
if (
2025-02-09 01:46:08 +01:00
shouldCheckBuildNumber &&
2025-02-08 00:07:17 +01:00
( replacement . fromBuild != null && buildNumber < replacement . fromBuild ) ||
( replacement . toBuild != null && buildNumber > replacement . toBuild )
) {
continue ;
}
2024-05-02 23:52:41 +02:00
2025-02-08 00:07:17 +01:00
// TODO: remove once Vesktop has been updated to use addPatch
if ( patch . plugin === "Vesktop" ) {
canonicalizeReplacement ( replacement , "VCDP" ) ;
}
const lastCode = code ;
2025-02-12 18:03:18 +01:00
const lastFactory = originalFactory ;
2025-02-08 00:07:17 +01:00
try {
const [ newCode , totalTime ] = executePatch ( replacement . match , replacement . replace as string ) ;
if ( IS_REPORTER ) {
2025-02-12 18:03:18 +01:00
patchTimings . push ( [ patch . plugin , moduleId , replacement . match , totalTime ] ) ;
2025-02-08 00:07:17 +01:00
}
if ( newCode === code ) {
if ( ! patch . noWarn ) {
2025-02-12 18:03:18 +01:00
logger . warn ( ` Patch by ${ patch . plugin } had no effect (Module id is ${ String ( moduleId ) } ): ${ replacement . match } ` ) ;
2025-02-08 00:07:17 +01:00
if ( IS_DEV ) {
logger . debug ( "Function Source:\n" , code ) ;
}
}
2024-05-02 23:52:41 +02:00
if ( patch . group ) {
2025-02-08 00:07:17 +01:00
logger . warn ( ` Undoing patch group ${ patch . find } by ${ patch . plugin } because replacement ${ replacement . match } had no effect ` ) ;
2024-05-02 23:52:41 +02:00
code = previousCode ;
2025-02-08 00:07:17 +01:00
patchedFactory = previousFactory ;
if ( markedAsPatched ) {
patchedBy . delete ( patch . plugin ) ;
}
2024-05-02 23:52:41 +02:00
break ;
}
2025-02-08 00:07:17 +01:00
continue ;
2024-05-02 23:52:41 +02:00
}
2025-02-08 00:07:17 +01:00
code = newCode ;
2025-02-12 18:03:18 +01:00
patchedSource = ` // Webpack Module ${ String ( moduleId ) } - Patched by ${ [ . . . patchedBy , patch . plugin ] . join ( ", " ) } \ n ${ newCode } \ n//# sourceURL=WebpackModule ${ String ( moduleId ) } ` ;
2025-02-08 00:07:17 +01:00
patchedFactory = ( 0 , eval ) ( patchedSource ) ;
2024-08-18 05:26:40 +02:00
2025-02-08 00:07:17 +01:00
if ( ! patchedBy . has ( patch . plugin ) ) {
patchedBy . add ( patch . plugin ) ;
markedAsPatched = true ;
}
} catch ( err ) {
2025-02-12 18:03:18 +01:00
logger . error ( ` Patch by ${ patch . plugin } errored (Module id is ${ String ( moduleId ) } ): ${ replacement . match } \ n ` , err ) ;
2025-02-08 00:07:17 +01:00
if ( IS_DEV ) {
diffErroredPatch ( code , lastCode , lastCode . match ( replacement . match ) ! ) ;
}
if ( markedAsPatched ) {
patchedBy . delete ( patch . plugin ) ;
}
2024-08-20 09:13:21 +02:00
2025-02-08 00:07:17 +01:00
if ( patch . group ) {
logger . warn ( ` Undoing patch group ${ patch . find } by ${ patch . plugin } because replacement ${ replacement . match } errored ` ) ;
code = previousCode ;
patchedFactory = previousFactory ;
break ;
2024-08-20 09:13:21 +02:00
}
2025-02-08 00:07:17 +01:00
code = lastCode ;
patchedFactory = lastFactory ;
2024-08-20 09:13:21 +02:00
}
2024-08-18 05:26:40 +02:00
}
2025-02-08 00:07:17 +01:00
if ( ! patch . all ) {
patches . splice ( i -- , 1 ) ;
}
2023-10-26 21:21:21 +02:00
}
2025-02-08 00:07:17 +01:00
2025-02-12 18:03:18 +01:00
patchedFactory [ SYM_ORIGINAL_FACTORY ] = originalFactory ;
if ( IS_DEV && patchedFactory !== originalFactory ) {
originalFactory [ SYM_PATCHED_SOURCE ] = patchedSource ;
originalFactory [ SYM_PATCHED_BY ] = patchedBy ;
}
return patchedFactory as PatchedModuleFactory ;
2025-02-08 00:07:17 +01:00
}
function diffErroredPatch ( code : string , lastCode : string , match : RegExpMatchArray ) {
const changeSize = code . length - lastCode . length ;
// Use 200 surrounding characters of context
const start = Math . max ( 0 , match . index ! - 200 ) ;
const end = Math . min ( lastCode . length , match . index ! + match [ 0 ] . length + 200 ) ;
// (changeSize may be negative)
const endPatched = end + changeSize ;
const context = lastCode . slice ( start , end ) ;
const patchedContext = code . slice ( start , endPatched ) ;
// Inline require to avoid including it in !IS_DEV builds
const diff = ( require ( "diff" ) as typeof import ( "diff" ) ) . diffWordsWithSpace ( context , patchedContext ) ;
let fmt = "%c %s " ;
const elements : string [ ] = [ ] ;
for ( const d of diff ) {
const color = d . removed
? "red"
: d . added
? "lime"
: "grey" ;
fmt += "%c%s" ;
elements . push ( "color:" + color , d . value ) ;
}
logger . errorCustomFmt ( . . . Logger . makeTitle ( "white" , "Before" ) , context ) ;
logger . errorCustomFmt ( . . . Logger . makeTitle ( "white" , "After" ) , patchedContext ) ;
const [ titleFmt , . . . titleElements ] = Logger . makeTitle ( "white" , "Diff" ) ;
logger . errorCustomFmt ( titleFmt + fmt , . . . titleElements , . . . elements ) ;
2023-10-26 21:21:21 +02:00
}