2022-10-22 01:17:06 +02:00
/ *
2024-05-07 21:44:26 -03:00
* Vencord , a Discord client mod
2024-05-26 20:38:57 -03:00
* Copyright ( c ) 2024 Vendicated , Nuckyz , and contributors
2024-05-07 21:44:26 -03:00
* SPDX - License - Identifier : GPL - 3.0 - or - later
* /
2022-10-22 01:17:06 +02:00
2024-05-23 06:04:21 -03:00
import { Settings } from "@api/Settings" ;
2023-05-06 01:36:00 +02:00
import { Logger } from "@utils/Logger" ;
2024-05-26 05:19:52 -03:00
import { interpolateIfDefined } from "@utils/misc" ;
2024-05-24 05:14:27 -03:00
import { canonicalizeReplacement } from "@utils/patches" ;
2022-12-19 17:59:54 -05:00
import { PatchReplacement } from "@utils/types" ;
2022-11-28 13:37:55 +01:00
2023-02-08 17:54:11 -03:00
import { traceFunction } from "../debug/Tracer" ;
2024-05-02 18:52:41 -03:00
import { patches } from "../plugins" ;
2024-06-16 19:57:31 -03:00
import { _initWebpack , AnyModuleFactory , AnyWebpackRequire , factoryListeners , moduleListeners , waitForSubscriptions , WebpackRequire , WrappedModuleFactory , wreq } from "." ;
2024-05-02 18:52:41 -03:00
const logger = new Logger ( "WebpackInterceptor" , "#8caaee" ) ;
2022-08-29 02:25:27 +02:00
2024-05-28 17:11:17 -03:00
/** A set with all the Webpack instances */
export const allWebpackInstances = new Set < AnyWebpackRequire > ( ) ;
2024-05-26 20:22:03 -03:00
/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
let wreqFallbackApplied = false ;
2024-05-28 17:11:17 -03:00
type Define = typeof Reflect . defineProperty ;
const define : Define = ( target , p , attributes ) = > {
if ( Object . hasOwn ( attributes , "value" ) ) {
attributes . writable = true ;
}
return Reflect . defineProperty ( target , p , {
configurable : true ,
enumerable : true ,
. . . attributes
} ) ;
} ;
2024-06-01 01:40:33 -03:00
// wreq.O is the Webpack onChunksLoaded function.
// It is pretty likely that all important Discord Webpack instances will have this property set,
// because Discord bundled code is chunked.
// As of the time of writing, only the main and sentry Webpack instances have this property, and they are the only ones we care about.
2024-06-01 05:03:47 -03:00
// We use this setter to intercept when wreq.O is defined, so we can patch the modules factories (wreq.m).
2024-05-26 20:22:03 -03:00
// wreq.m is pre-populated with module factories, and is also populated via webpackGlobal.push
2024-06-01 05:03:47 -03:00
// The sentry module also has their own Webpack with a pre-populated wreq.m, so this also patches those.
2024-06-01 01:40:33 -03:00
// We wrap wreq.m with our proxy, which is responsible for patching the module factories when they are set, or definining getters for the patched versions.
// If this is the main Webpack, we also set up the internal references to WebpackRequire.
define ( Function . prototype , "O" , {
2024-05-28 17:11:17 -03:00
enumerable : false ,
2024-05-26 20:22:03 -03:00
2024-06-02 18:46:03 -03:00
set ( this : AnyWebpackRequire , onChunksLoaded : AnyWebpackRequire [ "O" ] ) {
2024-06-01 01:40:33 -03:00
define ( this , "O" , { value : onChunksLoaded } ) ;
2024-05-26 20:22:03 -03:00
const { stack } = new Error ( ) ;
2024-06-01 01:44:18 -03:00
if ( this . m == null || ! ( stack ? . includes ( "discord.com" ) || stack ? . includes ( "discordapp.com" ) ) ) {
2024-05-28 17:11:17 -03:00
return ;
}
2024-05-23 06:04:21 -03:00
2024-05-28 17:11:17 -03:00
const fileName = stack ? . match ( /\/assets\/(.+?\.js)/ ) ? . [ 1 ] ;
logger . info ( "Found Webpack module factories" + interpolateIfDefined ` in ${ fileName } ` ) ;
2024-05-26 20:22:03 -03:00
2024-05-28 17:11:17 -03:00
allWebpackInstances . add ( this ) ;
2024-05-26 20:22:03 -03:00
2024-05-28 17:11:17 -03:00
// Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property.
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
define ( this , "p" , {
enumerable : false ,
set ( this : WebpackRequire , bundlePath : WebpackRequire [ "p" ] ) {
2024-05-31 06:16:01 -03:00
define ( this , "p" , { value : bundlePath } ) ;
2024-05-31 06:19:57 -03:00
clearTimeout ( setterTimeout ) ;
2024-05-31 06:16:01 -03:00
2024-05-28 17:11:17 -03:00
logger . info ( "Main Webpack found" + interpolateIfDefined ` in ${ fileName } ` + ", initializing internal references to WebpackRequire" ) ;
_initWebpack ( this ) ;
}
} ) ;
// setImmediate to clear this property setter if this is not the main Webpack.
2024-06-01 05:03:47 -03:00
// If this is the main Webpack, wreq.p will always be set before the timeout runs.
2024-05-28 17:11:17 -03:00
const setterTimeout = setTimeout ( ( ) = > Reflect . deleteProperty ( this , "p" ) , 0 ) ;
2024-06-01 01:40:33 -03:00
// Patch the pre-populated factories
for ( const id in this . m ) {
2024-06-01 19:52:17 -03:00
if ( updateExistingFactory ( this . m , id , this . m [ id ] , true ) ) {
2024-06-01 19:38:01 -03:00
continue ;
}
2024-06-12 17:30:26 -03:00
notifyFactoryListeners ( this . m [ id ] ) ;
2024-06-16 19:53:31 -03:00
defineModulesFactoryGetter ( id , Settings . eagerPatches ? wrapAndPatchFactory ( id , this . m [ id ] ) : this . m [ id ] ) ;
2024-06-01 01:40:33 -03:00
}
define ( this . m , Symbol . toStringTag , {
2024-05-28 17:11:17 -03:00
value : "ModuleFactories" ,
enumerable : false
2024-05-26 20:22:03 -03:00
} ) ;
2024-05-28 17:11:17 -03:00
// The proxy responsible for patching the module factories when they are set, or definining getters for the patched versions
2024-06-01 01:40:33 -03:00
const proxiedModuleFactories = new Proxy ( this . m , moduleFactoriesHandler ) ;
2024-05-28 17:11:17 -03:00
/ *
If Discord ever decides to set module factories using the variable of the modules object directly , instead of wreq . m , switch the proxy to the prototype
2024-06-02 18:46:03 -03:00
define ( this , "m" , { value : Reflect.setPrototypeOf ( this . m , new Proxy ( this . m , moduleFactoriesHandler ) ) } ) ;
2024-05-28 17:11:17 -03:00
* /
2024-05-28 17:14:35 -03:00
define ( this , "m" , { value : proxiedModuleFactories } ) ;
2024-05-26 20:22:03 -03:00
}
} ) ;
2024-06-16 19:53:31 -03:00
const moduleFactoriesHandler : ProxyHandler < AnyWebpackRequire [ "m" ] > = {
2024-06-12 17:33:26 -03:00
/ *
If Discord 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 dont 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 ;
}
* /
// The set trap for patching or defining getters for the module factories when new module factories are loaded
set : ( target , p , newValue , receiver ) = > {
// If the property is not a number, we are not dealing with a module factory
if ( Number . isNaN ( Number ( p ) ) ) {
return define ( target , p , { value : newValue } ) ;
}
if ( updateExistingFactory ( target , p , newValue ) ) {
return true ;
}
notifyFactoryListeners ( newValue ) ;
2024-06-16 19:53:31 -03:00
defineModulesFactoryGetter ( p , Settings . eagerPatches ? wrapAndPatchFactory ( p , newValue ) : newValue ) ;
2024-06-12 17:33:26 -03:00
return true ;
}
} ;
2024-06-01 19:38:01 -03:00
/ * *
* Update a factory that exists in any Webpack instance with a new original factory .
*
* @target The module factories where this new original factory is being set
* @param id The id of the module
* @param newFactory The new original factory
2024-06-01 19:52:17 -03:00
* @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget
2024-06-01 19:38:01 -03:00
* @returns Whether the original factory was updated , or false if it doesn ' t exist in any Webpack instance
* /
2024-06-02 18:46:03 -03:00
function updateExistingFactory ( moduleFactoriesTarget : AnyWebpackRequire [ "m" ] , id : PropertyKey , newFactory : AnyModuleFactory , ignoreExistingInTarget : boolean = false ) {
2024-06-16 19:53:31 -03:00
let existingFactory : TypedPropertyDescriptor < AnyModuleFactory > | undefined ;
2024-06-01 19:38:01 -03:00
for ( const wreq of allWebpackInstances ) {
2024-06-01 19:52:17 -03:00
if ( ignoreExistingInTarget && wreq . m === moduleFactoriesTarget ) continue ;
2024-06-01 19:38:01 -03:00
if ( Reflect . getOwnPropertyDescriptor ( wreq . m , id ) != null ) {
existingFactory = Reflect . getOwnPropertyDescriptor ( wreq . m , id ) ;
break ;
}
}
if ( existingFactory != null ) {
// If existingFactory exists in any Webpack instance, its 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,
// and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
2024-06-01 19:52:17 -03:00
Reflect . defineProperty ( moduleFactoriesTarget , id , existingFactory ) ;
return Reflect . set ( moduleFactoriesTarget , id , newFactory , moduleFactoriesTarget ) ;
2024-06-01 19:38:01 -03:00
}
return false ;
}
2024-06-12 17:30:26 -03:00
/ * *
2024-06-12 17:31:05 -03:00
* Notify all factory listeners .
*
2024-06-12 17:30:26 -03:00
* @param factory The original factory to notify for
* /
function notifyFactoryListeners ( factory : AnyModuleFactory ) {
for ( const factoryListener of factoryListeners ) {
try {
factoryListener ( factory ) ;
} catch ( err ) {
logger . error ( "Error in Webpack factory listener:\n" , err , factoryListener ) ;
}
}
}
2024-06-12 17:36:43 -03:00
/ * *
* Define the getter for returning the patched version of the module factory .
*
* 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 .
*
* @param id The id of the module
* @param factory The original or patched module factory
* /
2024-06-16 19:53:31 -03:00
function defineModulesFactoryGetter ( id : PropertyKey , factory : WrappedModuleFactory ) {
2024-06-12 17:36:43 -03:00
// 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 , {
get ( ) {
// $$vencordOriginal means the factory is already patched
if ( factory . $ $vencordOriginal != null ) {
return factory ;
}
2024-06-16 19:53:31 -03:00
return ( factory = wrapAndPatchFactory ( id , factory ) ) ;
2024-06-12 17:36:43 -03:00
} ,
set ( v : AnyModuleFactory ) {
if ( factory . $ $vencordOriginal != null ) {
2024-06-12 22:14:52 -03:00
factory . toString = v . toString . bind ( v ) ;
2024-06-12 17:36:43 -03:00
factory . $ $vencordOriginal = v ;
} else {
factory = v ;
}
}
} ) ;
}
}
2024-05-26 20:22:03 -03:00
/ * *
2024-06-16 19:53:31 -03:00
* Wraps and patches a module factory .
2024-05-26 20:22:03 -03:00
*
* @param id The id of the module
* @param factory The original or patched module factory
* @returns The wrapper for the patched module factory
* /
2024-06-16 19:53:31 -03:00
function wrapAndPatchFactory ( id : PropertyKey , originalFactory : AnyModuleFactory ) {
const patchedFactory = patchFactory ( id , originalFactory ) ;
2024-05-26 00:27:13 -03:00
2024-06-16 19:53:31 -03:00
// The patched factory wrapper, define it in an object to preserve the name after minification
const wrappedFactory : WrappedModuleFactory = {
PatchedFactory ( . . . args : Parameters < AnyModuleFactory > ) {
// 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
for ( const wreq of allWebpackInstances ) {
define ( wreq . m , id , { value : wrappedFactory.$$vencordOriginal } ) ;
}
// eslint-disable-next-line prefer-const
let [ module , exports , require ] = args ;
if ( wreq == null ) {
if ( ! wreqFallbackApplied ) {
wreqFallbackApplied = true ;
// 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 ] ;
logger . warn (
"WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
` id: ${ String ( id ) } ` + interpolateIfDefined ` , WebpackInstance origin: ${ webpackInstanceFileName } ` +
")"
) ;
_initWebpack ( require as WebpackRequire ) ;
} else if ( IS_DEV ) {
logger . error ( "WebpackRequire was not initialized, running modules without patches instead." ) ;
}
}
if ( IS_DEV ) {
2024-06-21 05:01:35 -03:00
return wrappedFactory . $ $vencordOriginal ! . apply ( this , args ) ;
2024-06-16 19:53:31 -03:00
}
}
let factoryReturn : unknown ;
try {
// Call the patched factory
factoryReturn = patchedFactory . apply ( this , args ) ;
} catch ( err ) {
// Just re-throw Discord errors
if ( patchedFactory === originalFactory ) {
throw err ;
}
2024-06-16 20:07:48 -03:00
logger . error ( "Error in patched module factory:\n" , err ) ;
2024-06-21 05:01:35 -03:00
return wrappedFactory . $ $vencordOriginal ! . apply ( this , args ) ;
2024-06-16 19:53:31 -03:00
}
2023-10-26 21:21:21 +02:00
exports = module . exports ;
2024-06-21 02:23:04 -03:00
if ( exports == null ) return ;
2024-06-16 19:53:31 -03:00
// There are (at the time of writing) 11 modules exporting the window
// Make these non enumerable to improve webpack search performance
2024-06-21 04:38:27 -03:00
if ( typeof require === "function" && require . c != null ) {
2024-06-21 02:23:04 -03:00
let foundWindow = false ;
if ( exports === window ) {
foundWindow = true ;
} else if ( typeof exports === "object" ) {
if ( exports . default === window ) {
foundWindow = true ;
} else {
for ( const exportKey in exports ) if ( exportKey . length <= 3 ) {
if ( exports [ exportKey ] === window ) {
foundWindow = true ;
}
}
}
}
if ( foundWindow ) {
Object . defineProperty ( require . c , id , {
value : require.c [ id ] ,
enumerable : false ,
configurable : true ,
writable : true
} ) ;
2024-06-21 04:38:27 -03:00
return factoryReturn ;
2024-06-21 02:23:04 -03:00
}
2024-06-16 19:53:31 -03:00
}
for ( const callback of moduleListeners ) {
try {
2024-06-21 04:58:43 -03:00
callback ( exports , { id , factory : wrappedFactory.$$vencordOriginal ! } ) ;
2024-06-16 19:53:31 -03:00
} catch ( err ) {
logger . error ( "Error in Webpack module listener:\n" , err , callback ) ;
}
}
2023-10-26 21:21:21 +02:00
2024-06-16 19:57:31 -03:00
for ( const [ filter , callback ] of waitForSubscriptions ) {
2024-06-16 19:53:31 -03:00
try {
2024-06-21 04:58:43 -03:00
if ( filter . $ $vencordIsFactoryFilter && filter ( wrappedFactory . $ $vencordOriginal ! ) ) {
2024-06-21 02:23:04 -03:00
waitForSubscriptions . delete ( filter ) ;
2024-06-21 04:58:43 -03:00
callback ( exports , { id , exportKey : null , factory : wrappedFactory.$$vencordOriginal ! } ) ;
2024-06-21 02:23:04 -03:00
continue ;
}
2024-06-16 19:57:31 -03:00
if ( filter ( exports ) ) {
waitForSubscriptions . delete ( filter ) ;
2024-06-21 04:58:43 -03:00
callback ( exports , { id , exportKey : null , factory : wrappedFactory.$$vencordOriginal ! } ) ;
2024-06-21 02:23:04 -03:00
continue ;
}
if ( typeof exports !== "object" ) {
continue ;
}
if ( exports . default != null && filter ( exports . default ) ) {
2024-06-16 19:57:31 -03:00
waitForSubscriptions . delete ( filter ) ;
2024-06-21 04:58:43 -03:00
callback ( exports . default , { id , exportKey : "default" , factory : wrappedFactory.$$vencordOriginal ! } ) ;
2024-06-21 02:23:04 -03:00
continue ;
}
for ( const exportKey in exports ) if ( exportKey . length <= 3 ) {
const exportValue = exports [ exportKey ] ;
if ( exportValue != null && filter ( exportValue ) ) {
waitForSubscriptions . delete ( filter ) ;
2024-06-21 04:58:43 -03:00
callback ( exportValue , { id , exportKey , factory : wrappedFactory.$$vencordOriginal ! } ) ;
2024-06-21 02:23:04 -03:00
break ;
}
2024-06-16 19:53:31 -03:00
}
} catch ( err ) {
2024-06-16 19:57:31 -03:00
logger . error ( "Error while firing callback for Webpack waitFor subscription:\n" , err , filter , callback ) ;
2024-06-16 19:53:31 -03:00
}
}
return factoryReturn ;
}
} . PatchedFactory ;
wrappedFactory . toString = originalFactory . toString . bind ( originalFactory ) ;
wrappedFactory . $ $vencordOriginal = originalFactory ;
return wrappedFactory ;
}
/ * *
* Patches a module factory .
*
* @param id The id of the module
* @param factory The original module factory
* @returns The patched module factory
* /
function patchFactory ( id : PropertyKey , factory : AnyModuleFactory ) {
2024-05-31 00:21:15 -03:00
// 0, prefix to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
let code : string = "0," + String ( factory ) ;
2024-06-16 19:53:31 -03:00
let patchedFactory = factory ;
const patchedBy = new Set < string > ( ) ;
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
for ( let i = 0 ; i < patches . length ; i ++ ) {
const patch = patches [ i ] ;
if ( patch . predicate && ! patch . predicate ( ) ) continue ;
2024-05-02 18:52:41 -03:00
2024-05-19 22:49:58 -03:00
const moduleMatches = typeof patch . find === "string"
2024-05-31 17:34:32 -03:00
? code . includes ( patch . find )
2024-05-31 05:19:34 -03:00
: ( patch . find . global && ( patch . find . lastIndex = 0 ) , patch . find . test ( code ) ) ;
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
if ( ! moduleMatches ) continue ;
2024-05-02 18:52:41 -03:00
2024-05-19 22:49:58 -03:00
patchedBy . add ( patch . plugin ) ;
2024-05-02 18:52:41 -03:00
2024-05-19 22:49:58 -03:00
const executePatch = traceFunction ( ` patch by ${ patch . plugin } ` , ( match : string | RegExp , replace : string ) = > code . replace ( match , replace ) ) ;
const previousCode = code ;
2024-05-26 00:27:13 -03:00
const previousFactory = factory ;
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
// We change all patch.replacement to array in plugins/index
for ( const replacement of patch . replacement as PatchReplacement [ ] ) {
if ( replacement . predicate && ! replacement . predicate ( ) ) continue ;
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
const lastCode = code ;
2024-05-26 00:27:13 -03:00
const lastFactory = factory ;
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
canonicalizeReplacement ( replacement , patch . plugin ) ;
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
try {
const newCode = executePatch ( replacement . match , replacement . replace as string ) ;
if ( newCode === code ) {
if ( ! patch . noWarn ) {
2024-05-23 03:36:53 -03:00
logger . warn ( ` Patch by ${ patch . plugin } had no effect (Module id is ${ String ( id ) } ): ${ replacement . match } ` ) ;
2024-05-19 22:49:58 -03:00
if ( IS_DEV ) {
logger . debug ( "Function Source:\n" , code ) ;
}
}
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
if ( patch . group ) {
logger . warn ( ` Undoing patch group ${ patch . find } by ${ patch . plugin } because replacement ${ replacement . match } had no effect ` ) ;
code = previousCode ;
2024-06-16 19:53:31 -03:00
patchedFactory = previousFactory ;
2024-05-19 22:49:58 -03:00
patchedBy . delete ( patch . plugin ) ;
break ;
}
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
continue ;
2023-10-26 21:21:21 +02:00
}
2024-05-19 22:49:58 -03:00
code = newCode ;
2024-06-16 19:53:31 -03:00
patchedFactory = ( 0 , eval ) ( ` // Webpack Module ${ String ( id ) } - Patched by ${ [ . . . patchedBy ] . join ( ", " ) } \ n ${ newCode } \ n//# sourceURL=WebpackModule ${ String ( id ) } ` ) ;
2024-05-19 22:49:58 -03:00
} catch ( err ) {
2024-05-23 03:36:53 -03:00
logger . error ( ` Patch by ${ patch . plugin } errored (Module id is ${ String ( id ) } ): ${ replacement . match } \ n ` , err ) ;
2024-05-19 22:49:58 -03:00
if ( IS_DEV ) {
const changeSize = code . length - lastCode . length ;
const match = lastCode . match ( replacement . match ) ! ;
// 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 = [ ] as string [ ] ;
for ( const d of diff ) {
const color = d . removed
? "red"
: d . added
? "lime"
: "grey" ;
fmt += "%c%s" ;
elements . push ( "color:" + color , d . value ) ;
2023-10-26 21:21:21 +02:00
}
2024-05-19 22:49:58 -03:00
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
}
2024-05-19 22:49:58 -03:00
patchedBy . delete ( patch . plugin ) ;
2023-10-26 21:21:21 +02:00
2024-05-19 22:49:58 -03:00
if ( patch . group ) {
logger . warn ( ` Undoing patch group ${ patch . find } by ${ patch . plugin } because replacement ${ replacement . match } errored ` ) ;
code = previousCode ;
2024-06-16 19:53:31 -03:00
patchedFactory = previousFactory ;
2024-05-19 22:49:58 -03:00
break ;
}
code = lastCode ;
2024-06-16 19:53:31 -03:00
patchedFactory = lastFactory ;
2024-05-02 18:52:41 -03:00
}
}
2024-05-19 22:49:58 -03:00
if ( ! patch . all ) patches . splice ( i -- , 1 ) ;
}
2023-11-22 01:23:21 -05:00
2024-05-19 22:49:58 -03:00
return patchedFactory ;
2023-10-26 21:21:21 +02:00
}