Skip to content

Commit

Permalink
rename wpTools (the require helper) to spacepackLite.
Browse files Browse the repository at this point in the history
  • Loading branch information
adryd325 committed Dec 6, 2023
1 parent c14e1ac commit 8791f3a
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 128 deletions.
45 changes: 33 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,47 @@
Hi, while this "Works" right now, its not well documented; and a lot is subject to change.
# webpackTools

⚠️ All (including the name of the project) is subject to change, I still haven't settled entirely on the structure of everything ⚠️

A userscript for reverse engineering, debugging, and modifying sites using webpack.

## Installing
The runtime with an example config for twitter can be installed from https://moonlight-mod.github.io/webpackTools/webpackTools.user.js

## Updating
To update to the latest webpackTools runtime while maintaining your existing config, edit your userscript and replace the string after `const runtime = ` with https://moonlight-mod.github.io/webpackTools/webpackTools.runtime.json.
## Usage

An example config exists in the userscript with all fields documented.

### Spacepack Everywhere

By enabling `spacepackEverywhere` in your config, spacepack will automatically be injected into any webpack instances found.

spacepack will then be accessible from `window.spacepack` or window.spacepack_<webpackChunkObject> if there happens to be multiple webpack instances

## How to determine if something is Webpack 4 or Webpack 5
TODO: actually explain this
This version of spacepack is *slightly* different to the one in moonlight, however usage should be nearly the same. `__namedRequire`, `chunkObject`, and `name` are new to this version however.

Webpack 5 typically names its jsonp array webpackChunk\<projectName\>
Webpack 4 typically names its jsonp array webpackJsonp
TODO: Either write own docs about spacepack or link to moonlight docs

to double check you can find the first array with 3 entries
if the 3rd entry is an array with strings, its webpack 4
if the 3rd entry is a function it's webpack 5
### Full patching support

rspack seems to compile to webpack 5 runtime
TODO

## Updating
To update to the latest webpackTools runtime while maintaining your existing config, edit your userscript and replace the string after `const runtime = ` with https://moonlight-mod.github.io/webpackTools/webpackTools.runtime.json.

## Caveats

Some sites, namely Discord, will start multiple webpack runtimes running on the same webpackChunk object. Duplicate runtimes can be found in `window.wpTools.runtimes`. Injected modules will run multiple times, one for each runtime.

Some sites don't expose their export cache, making `spacepack.findModulesByExports` unusable and `spacepack.exports` undefined.

## Credits
A lot of this is based on research by [Mary](https://github.com/mstrodl) and [Cynthia](https://github.com/cynosphere) (HH3), and [Twilight Sparkle](https://github.com/twilight-sparkle-irl/) (webcrack, crispr)

A lot of this is based on research by [Mary](https://github.com/mstrodl) and [Cynthia](https://github.com/cynosphere) (HH3), and [Twilight Sparkle](https://github.com/twilight-sparkle-irl/) (webcrack, crispr, Endpwn)

## Terminology

We use our own names for a lot of things as we feel they make more sense, however here's some mappings to webpack's "official" namings and it can be slightly confusing

- modules, or module cache: `moduleFactories`
- exports, or export cache: `moduleCache`
- chunkObject (typically webpackJsonp or webpackChunk): `chunkCallback` or `chunkLoadingGlobal`
16 changes: 5 additions & 11 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
# TODO

- [x] injecting wpTools module on all webpack sites (like magicrequire everywhere). with webpack 3 support
- [ ] webpack 3 support for everything?
- [ ] glob matching for site names (for examle: \*.twitter.com) in userscript part
- [x] config validation with descriptive errors
- [x] rework configs
- [ ] check if Function.prototype.toString() is faster than checking for \_\_wpt_funcStr
- [ ] wpTools/findByExports: recurse into objects when searching? getters could pose a problem however
- [ ] add obfuscated code helpers and swc helpers in wpTools
- [ ] find a better way to do userscripts, ideally not fetching remotely
- [ ] log errors while patching (parse, patches that dont fire, etc)
- [ ] actually good documentation and tutorials
- [ ] Support for Webpack versions 3 or lower
- [ ] Glob matching for site names (for examle: \*.twitter.com) in userscript part
- [ ] Benchmark: Check if Function.prototype.toString() is faster than checking for \_\_wpt_funcStr
- [ ] Log errors while patching (parse, patches that dont fire, etc)
- [ ] Possibly create a browser extension with a devtools support
36 changes: 28 additions & 8 deletions src/Patcher.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
import matchModule from "./matchModule";
import { validateProperty } from "./validate";
import { getWpToolsFunc } from "./wpTools";
import { getWpToolsFunc } from "./spacepackLite";

class ConfigValidationError extends Error {}

function validateProperty(name, object, key, required, validationCallback) {
if (!Object.prototype.hasOwnProperty.call(object, [key])) {
if (required) {
throw new ConfigValidationError(`Required property not found, missing ${key} in ${name}`);
} else {
return;
}
} else {
if (!validationCallback(object[key])) {
throw new ConfigValidationError(
`Failed to validate ${key} in ${name}. The following check failed: \n${validationCallback.toString()}`,
);
}
}
}

export default class Patcher {
constructor(config) {
this._validateConfig(config);
this.name = config.name;
this.chunkObject = config.chunkObject;
this.webpackVersion = config.webpackVersion.toString();
this.inspectAll = config.inspectAll;
this.patchAll = config.patchAll;

this.modules = new Set(config.modules ?? []);
for (const module of this.modules) {
Expand Down Expand Up @@ -38,9 +55,9 @@ export default class Patcher {
}
}

if (config.injectWpTools !== false) {
if (config.injectSpacepack !== false) {
this.modulesToInject.add({
name: "wpTools",
name: "spacepack",
// This is sorta a scope hack.
// If we rewrap this function, it will lose its scope (in this case the match module import and the chunk object name)
run: getWpToolsFunc(this.chunkObject),
Expand Down Expand Up @@ -119,7 +136,7 @@ export default class Patcher {
funcStr = funcStr.replace(patch.replace.match, patch.replace.replacement);
}

if (matchingPatches.length > 0 || this.inspectAll) {
if (matchingPatches.length > 0 || this.patchAll) {
let debugString = "";
if (matchingPatches.length > 0) {
debugString += "Patched by: " + matchingPatches.map((patch) => patch.name).join(", ");
Expand Down Expand Up @@ -226,7 +243,7 @@ export default class Patcher {
return ["4", "5"].includes(value.toString());
});

validateProperty(`siteConfigs[${name}]`, config, "inspectAll", false, (value) => {
validateProperty(`siteConfigs[${name}]`, config, "patchAll", false, (value) => {
return typeof value === "boolean";
});

Expand All @@ -238,7 +255,7 @@ export default class Patcher {
return value instanceof Array;
});

validateProperty(`siteConfigs[${name}]`, config, "injectWpTools", false, (value) => {
validateProperty(`siteConfigs[${name}]`, config, "injectSpacepack", false, (value) => {
return typeof value === "boolean";
});
}
Expand Down Expand Up @@ -303,6 +320,9 @@ export default class Patcher {
validateProperty(`siteConfigs[${this.name}].modules[${name}]`, config, "entry", false, (value) => {
return typeof value === "boolean";
});
if (config.entry === undefined) {
config.entry = false;
}

// Possible future thing
// validateProperty(`siteConfigs[${this.name}].modules[${name}]`, config, "rewrap", false, (value) => {
Expand Down
14 changes: 9 additions & 5 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Patcher from "./Patcher";
import { injectEverywhere } from "./injectEverywhere";
import { spacepackEverywhere } from "./spacepackEverywhere";

export const globalConfig = window.__webpackTools_config;
delete window.__webpackTools_config;
Expand All @@ -15,16 +15,20 @@ for (let siteConfig of globalConfig.siteConfigs) {
window.wpTools = {
globalConfig,
activeSiteConfigs: siteConfigs,

spacepackEverywhereDetect: () => {
spacepackEverywhere(globalConfig.spacepackEverywhere);
},

runtimes: {},
};

// todo: magicrequire everywhere impl
if (siteConfigs.size > 0) {
for (const siteConfig of siteConfigs) {
const patcher = new Patcher(siteConfig);
patcher.run();
}
} else if (globalConfig.wpToolsEverywhere) {
window.addEventListener("load", injectEverywhere);
} else if (globalConfig?.spacepackEverywhere?.enabled !== false) {
window.addEventListener("load", () => {
spacepackEverywhere(globalConfig.spacepackEverywhere);
});
}
56 changes: 0 additions & 56 deletions src/injectEverywhere.js

This file was deleted.

58 changes: 58 additions & 0 deletions src/spacepackEverywhere.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { getWpToolsFunc } from "./spacepackLite";

function getWebpackVersion(chunkObject) {
if (chunkObject instanceof Array) {
return "modern";
} else {
return "legacy";
}
}

// Gross Hack to support both webpack 4, webpack 5
const onChunkLoaded = function (webpackRequire) {
webpackRequire("spacepack");
};
onChunkLoaded[0] = ["spacepack"];
onChunkLoaded[Symbol.iterator] = function () {
return {
read: false,
next() {
if (!this.read) {
this.read = true;
return { done: false, value: 0 };
} else {
return { done: true };
}
},
};
};

function pushSpacepack(chunkObjectName) {
const chunkObject = window[chunkObjectName];
if (chunkObject.__spacepack_everywhere_injected) {
return;
}
const version = getWebpackVersion(chunkObject);
console.log("[wpTools] Detected " + chunkObjectName + " using webpack " + version);
switch (version) {
case "modern":
chunkObject.__spacepack_everywhere_injected = true;
chunkObject.push([["spacepack"], { spacepack: getWpToolsFunc(chunkObjectName, true) }, onChunkLoaded]);
break;
}
}

export function spacepackEverywhere(config) {
if (config?.ignoreSites?.includes(window.location.host)) {
return;
}

for (const key of Object.getOwnPropertyNames(window)) {
if (
(key.includes("webpackJsonp") || key.includes("webpackChunk") || key.includes("__LOADABLE_LOADED_CHUNKS__")) &&
!key.startsWith("spacepack") && !config?.ignoreChunkObjects?.includes(key)
) {
pushSpacepack(key);
}
}
}
Loading

0 comments on commit 8791f3a

Please sign in to comment.