chore: merge dev to main (#195)

This commit is contained in:
Palm 2025-07-21 22:49:55 +07:00 • committed by GitHub
commit 934ca93c82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 3508 additions and 9762 deletions

View file

@ -14,22 +14,24 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
- name: Restore Bun cache
uses: actions/cache@v4
with:
version: latest
path: ~/.bun/install/cache
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install Node.js
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
run: bun install --frozen-lockfile
- name: Release
run: pnpx semantic-release
run: bunx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_BRANCH: ${{ github.ref_name }}

View file

@ -28,7 +28,7 @@ export default {
[
"@semantic-release/exec",
{
prepareCmd: `pnpm build --release-branch ${process.env.RELEASE_BRANCH} --build-minify`,
prepareCmd: `bun run build --release-branch ${process.env.RELEASE_BRANCH} --build-minify`,
},
],
[

View file

@ -1,3 +1,47 @@
# [1.8.0-dev.2](https://github.com/revenge-mod/revenge-bundle/compare/v1.8.0-dev.1...v1.8.0-dev.2) (2025-07-21)
### Bug Fixes
* Restore original behavior broken by b92d2734b33d2f0e719c364ed5aaefd43c7ae457 ([ec2809f](https://github.com/revenge-mod/revenge-bundle/commit/ec2809fe4c835c84101a8b8525cfc3863d1bc4d8))
* Restore original behavior broken for old architecture ([#194](https://github.com/revenge-mod/revenge-bundle/issues/194)) ([acb85ba](https://github.com/revenge-mod/revenge-bundle/commit/acb85bacbab8b661c797f3b1609e64f2c724382f))
# [1.8.0-dev.1](https://github.com/revenge-mod/revenge-bundle/compare/v1.7.5-dev.1...v1.8.0-dev.1) (2025-07-21)
### Features
* compile to bytecode ([18f21fe](https://github.com/revenge-mod/revenge-bundle/commit/18f21feaaea0a4330e2b098c549b2a516cc8d352))
# [1.8.0-dev.1](https://github.com/revenge-mod/revenge-bundle/compare/v1.7.5-dev.1...v1.8.0-dev.1) (2025-07-21)
### Features
* compile to bytecode ([18f21fe](https://github.com/revenge-mod/revenge-bundle/commit/18f21feaaea0a4330e2b098c549b2a516cc8d352))
# [1.8.0-dev.1](https://github.com/revenge-mod/revenge-bundle/compare/v1.7.5-dev.1...v1.8.0-dev.1) (2025-07-21)
### Features
* compile to bytecode ([18f21fe](https://github.com/revenge-mod/revenge-bundle/commit/18f21feaaea0a4330e2b098c549b2a516cc8d352))
# [1.8.0-dev.1](https://github.com/revenge-mod/revenge-bundle/compare/v1.7.5-dev.1...v1.8.0-dev.1) (2025-07-21)
### Features
* compile to bytecode ([18f21fe](https://github.com/revenge-mod/revenge-bundle/commit/18f21feaaea0a4330e2b098c549b2a516cc8d352))
## [1.7.5-dev.1](https://github.com/revenge-mod/revenge-bundle/compare/v1.7.4...v1.7.5-dev.1) (2025-07-21)
### Bug Fixes
* support New Architecture ([b92d273](https://github.com/revenge-mod/revenge-bundle/commit/b92d2734b33d2f0e719c364ed5aaefd43c7ae457))
* support versions with New Architecture ([#193](https://github.com/revenge-mod/revenge-bundle/issues/193)) ([1b96867](https://github.com/revenge-mod/revenge-bundle/commit/1b96867ac76e522616186e96120c940b9311d659))
## [1.7.4](https://github.com/revenge-mod/revenge-bundle/compare/v1.7.3...v1.7.4) (2025-05-19)

3229
bun.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -10,13 +10,13 @@
4. Install dependencies
```
pnpm i
bun i
```
5. Build Revenge's code
```
pnpm build
bun run build
```
6. In the newly created `dist` directory, run a HTTP server. I recommend [http-server](https://www.npmjs.com/package/http-server).
@ -24,6 +24,6 @@
8. Restart Discord. Upon reload, you should notice that your device will download Revenge's bundled code from your server, rather than GitHub.
9. Make your changes, rebuild, reload, go wild!
Alternatively, you can directly *serve* the bundled code by running `pnpm serve`. `revenge.js` will be served on your local address under the port 4040. You will then insert `http://<local ip address>:4040/revenge.js` as a custom URL and reload. Whenever you restart your mobile client, the script will rebuild the bundle as your client fetches it.
Alternatively, you can directly *serve* the bundled code by running `bun serve`. `revenge.js` will be served on your local address under the port 4040. You will then insert `http://<local ip address>:4040/revenge.js` as a custom URL and reload. Whenever you restart your mobile client, the script will rebuild the bundle as your client fetches it.
If the bundle keeps being cached and not updated, you can instead tap the **Settings** > **Developer** > **Clear JS bundle** option which will prompt you to reload.

View file

@ -10,7 +10,7 @@
"scripts": {
"build": "node scripts/build.mjs",
"serve": "node scripts/serve.mjs",
"serve:adb": "pnpm run serve --adb",
"serve:adb": "bun run serve --adb",
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx"
},
"license": "BSD-3-Clause",
@ -19,47 +19,39 @@
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"semantic-release": "^24.1.3",
"@swc/core": "^1.7.0",
"@swc/helpers": "^0.5.12",
"@types/node": "^20.14.11",
"semantic-release": "^24.2.7",
"@swc/core": "^1.13.1",
"@swc/helpers": "^0.5.17",
"@types/node": "^20.19.9",
"@types/yargs-parser": "^21.0.3",
"@typescript-eslint/eslint-plugin": "^7.16.1",
"@typescript-eslint/parser": "^7.16.1",
"@typescript-eslint/typescript-estree": "^7.16.1",
"chalk": "^5.3.0",
"@typescript-eslint/eslint-plugin": "^7.18.0",
"@typescript-eslint/parser": "^7.18.0",
"@typescript-eslint/typescript-estree": "^7.18.0",
"chalk": "^5.4.1",
"esbuild": "^0.20.2",
"esbuild-plugin-globals": "^0.2.0",
"eslint": "^8.57.0",
"eslint": "^8.57.1",
"eslint-plugin-import-alias": "^1.2.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^3.2.0",
"typescript": "^5.5.3",
"typescript": "^5.8.3",
"yargs-parser": "^21.1.1"
},
"dependencies": {
"@gullerya/object-observer": "^6.1.3",
"@shopify/react-native-skia": "^1.3.8",
"@tanstack/react-query": "^5.51.16",
"@types/chroma-js": "~2.4.4",
"@types/lodash": "~4.17.7",
"@gullerya/object-observer": "^6.1.4",
"@shopify/react-native-skia": "^1.12.4",
"@tanstack/react-query": "^5.83.0",
"@types/chroma-js": "~2.4.5",
"@types/lodash": "~4.17.20",
"@types/react": "18.2.60",
"@types/react-native": "0.72.3",
"es-toolkit": "^1.13.1",
"fuzzysort": "^3.0.2",
"intl-messageformat": "^10.5.14",
"es-toolkit": "^1.39.7",
"fuzzysort": "^3.1.0",
"intl-messageformat": "^10.7.16",
"moment": "2.22.2",
"react-native-reanimated": "^3.6.2",
"react-native-reanimated": "^3.18.0",
"spitroast": "^1.4.4",
"type-fest": "^4.22.1"
},
"pnpm": {
"peerDependencyRules": {
"ignoreMissing": [
"react",
"react-native"
]
}
"type-fest": "^4.41.0"
}
}

9677
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
// @ts-nocheck
/* eslint-disable no-restricted-syntax */
import swc from "@swc/core";
import { execSync } from "child_process";
import { execFileSync, execSync } from "child_process";
import crypto from "crypto";
import { build } from "esbuild";
import globalPlugin from "esbuild-plugin-globals";
@ -10,6 +10,7 @@ import { fileURLToPath } from "url";
import yargs from "yargs-parser";
import { printBuildSuccess } from "./util.mjs";
import { readFile } from "fs/promises";
/** @type string[] */
const metroDeps = await (async () => {
@ -46,6 +47,7 @@ const config = {
".png": "dataurl"
},
define: {
window: "globalThis",
__DEV__: dev ?? JSON.stringify(releaseBranch !== "main")
},
inject: ["./shims/asyncIteratorSymbol.js", "./shims/promiseAllSettled.js"],
@ -120,6 +122,25 @@ export async function buildBundle(overrideConfig = {}) {
const initialStartTime = performance.now();
await build({ ...config, ...overrideConfig });
const paths = {
win32: 'win64-bin/hermesc.exe',
darwin: 'osx-bin/hermesc',
linux: 'linux64-bin/hermesc',
}
if (!(process.platform in paths))
throw new Error(`Unsupported platform: ${process.platform}`)
const sdksDir = './node_modules/react-native/sdks'
const binPath = `${sdksDir}/hermesc/${paths[process.platform]}`
const actualFile = overrideConfig.outfile ?? config.outfile;
execFileSync(binPath, ['-finline', '-strict', '-O', '-g1', '-reuse-prop-cache', '-optimized-eval', '-emit-binary', '-Wno-undefined-variable', '-out', actualFile], {
input: await readFile(actualFile),
stdio: 'pipe'
});
return {
config,
context,
@ -142,7 +163,7 @@ if (isThisFileBeingRunViaCLI) {
if (buildMinify) {
const { timeTook } = await buildBundle({
minify: true,
minifyIdentifiers: true,
outfile: config.outfile.replace(/\.js$/, ".min.js")
});

View file

@ -27,8 +27,8 @@ export function serve(options) {
timeTook
);
res.writeHead(200, { "Content-Type": "application/javascript" });
res.end(await readFile(config.outfile, "utf-8"));
res.writeHead(200);
res.end(await readFile(config.outfile));
} catch {
res.writeHead(500);
res.end();

View file

@ -1,10 +1,11 @@
import type { Metro } from "@metro/types";
import { version } from "bunny-build-info";
const { instead } = require("spitroast");
// @ts-ignore - shut up fr
// @ts-ignore - window is defined later in the bundle, so we assign it early
globalThis.window = globalThis;
async function initializeBunny() {
async function initializeRevenge() {
try {
// Make 'freeze' and 'seal' do nothing
Object.freeze = Object.seal = Object;
@ -18,51 +19,107 @@ async function initializeBunny() {
console.log(stack ?? e?.toString?.() ?? e);
alert([
"Failed to load Revenge!\n",
`Build Number: ${ClientInfoManager.Build}`,
`Build Number: ${ClientInfoManager.getConstants().Build}`,
`Revenge: ${version}`,
stack || e?.toString?.(),
].join("\n"));
}
}
// @ts-ignore
if (typeof globalThis.__r !== "undefined") {
initializeBunny();
} else {
// We hold calls from the native side
function onceIndexRequired(originalRequire: any) {
const batchedBridge = window.__fbBatchedBridge;
if (typeof window.__r === "undefined") {
// Used for storing the current require function for the global.__r getter defined below
var _requireFunc: any;
const callQueue = new Array<any>;
const unpatchHook = instead("callFunctionReturnFlushedQueue", batchedBridge, (args: any, orig: any) => {
if (args[0] === "AppRegistry" || !batchedBridge.getCallableModule(args[0])) {
callQueue.push(args);
return batchedBridge.flushedQueue();
// Calls from the native side are deferred until the index.ts(x) is loaded
// Revenge delays the execution of index.ts(x) because Revenge's initialization is asynchronous
interface DeferredQueue {
object: any;
method: string;
resume?: (queue: DeferredQueue) => void;
args: any[];
}
const deferredCalls: Array<DeferredQueue> = [];
const unpatches: Array<() => void> = [];
const deferMethodExecution = (
object: any,
method: string,
condition?: (...args: any[]) => boolean,
resume?: (queue: DeferredQueue) => void,
returnWith?: (queue: DeferredQueue) => any
) => {
const restore = instead(method, object, function (this: any, args: any[], original: any) {
if (!condition || condition(...args)) {
const queue: DeferredQueue = { object, method, args, resume };
deferredCalls.push(queue);
return returnWith ? returnWith(queue) : undefined;
}
return orig.apply(batchedBridge, args);
// If the condition is not met, we execute the original method immediately
return original.apply(this, args);
});
const startDiscord = async () => {
await initializeBunny();
unpatchHook();
originalRequire(0);
unpatches.push(restore);
}
callQueue.forEach(arg =>
batchedBridge.getCallableModule(arg[0])
&& batchedBridge.__callFunction(...arg));
const resumeDeferred = () => {
for (const queue of deferredCalls) {
const { object, method, args, resume } = queue;
if (resume) {
resume(queue);
} else {
object[method](...args);
}
}
deferredCalls.length = 0;
}
const onceIndexRequired = (originalRequire: Metro.RequireFn) => {
// We hold calls from the native side
if (window.__fbBatchedBridge) {
const batchedBridge = window.__fbBatchedBridge;
deferMethodExecution(
batchedBridge,
"callFunctionReturnFlushedQueue",
// If the call is to AppRegistry, we want to defer it because it is not yet registered (Revenge delays it)
// Same goes to the non-callable modules, which are not registered yet, so we ensure that only registered ones can get through
(...args) => args[0] === "AppRegistry" || !batchedBridge.getCallableModule(args[0]),
({ args }) => {
if (batchedBridge.getCallableModule(args[0])) {
batchedBridge.__callFunction(...args);
}
},
() => batchedBridge.flushedQueue()
);
}
// Introduced since RN New Architecture
if (window.RN$AppRegistry) {
deferMethodExecution(window.RN$AppRegistry, "runApplication");
}
const startDiscord = async () => {
await initializeRevenge();
for (const unpatch of unpatches) unpatch();
unpatches.length = 0;
originalRequire(0);
resumeDeferred();
};
startDiscord();
}
var _requireFunc: any; // We can't set properties to 'this' during __r set for some reason
Object.defineProperties(globalThis, {
__r: {
configurable: true,
get: () => _requireFunc,
set(v) {
// _requireFunc is required here, because using 'this' here errors for some unknown reason
_requireFunc = function patchedRequire(a: number) {
// Initializing index.ts(x)
if (a === 0) {
@ -85,4 +142,8 @@ if (typeof globalThis.__r !== "undefined") {
set(v) { this.value = v; }
}
});
}
} else {
// It is too late to late to hook __r, so we just initialize Revenge here
// Likely because of using the legacy loader (from Vendetta)
initializeRevenge();
}

1
src/global.d.ts vendored
View file

@ -6,6 +6,7 @@ declare global {
var globalEvalWithSourceUrl: (script: string, sourceURL: string) => any;
var nativePerformanceNow: typeof performance.now;
var nativeModuleProxy: Record<string, any>;
var __turboModuleProxy: (name: string) => any;
interface Window {
[key: string]: any;

View file

@ -113,8 +113,8 @@ export function getDebugInfo() {
}
},
discord: {
version: NativeClientInfoModule.Version,
build: NativeClientInfoModule.Build,
version: NativeClientInfoModule.getConstants().Version,
build: NativeClientInfoModule.getConstants().Build,
},
react: {
version: React.version,

View file

@ -2,10 +2,34 @@ import { RNModules } from "./types";
const nmp = window.nativeModuleProxy;
export const NativeCacheModule = (nmp.NativeCacheModule ?? nmp.MMKVManager) as RNModules.MMKVManager;
export const NativeFileModule = (nmp.NativeFileModule ?? nmp.RTNFileManager ?? nmp.DCDFileManager) as RNModules.FileManager;
export const NativeClientInfoModule = nmp.NativeClientInfoModule ?? nmp.RTNClientInfoManager ?? nmp.InfoDictionaryManager;
export const NativeDeviceModule = nmp.NativeDeviceModule ?? nmp.RTNDeviceManager ?? nmp.DCDDeviceManager;
export const NativeThemeModule = nmp.NativeThemeModule ?? nmp.RTNThemeManager ?? nmp.DCDTheme;
function getNativeModule<T = any>(...names: string[]): T | undefined {
for (const name of names) {
if (globalThis.__turboModuleProxy) {
const module = globalThis.__turboModuleProxy(name);
if (module) return module as T;
}
export const { BundleUpdaterManager } = nmp;
if (nmp[name]) return nmp[name] as T;
}
return undefined;
}
export const NativeCacheModule = getNativeModule<RNModules.MMKVManager>(
"NativeCacheModule", "MMKVManager"
)!;
export const NativeFileModule = getNativeModule<RNModules.FileManager>(
"NativeFileModule", "RTNFileManager", "DCDFileManager"
)!;
export const NativeClientInfoModule = getNativeModule<RNModules.ClientInfoModule>(
"NativeClientInfoModule", "RTNClientInfoManager", "InfoDictionaryManager"
)!;
export const NativeDeviceModule = getNativeModule(
"NativeDeviceModule", "RTNDeviceManager", "DCDDeviceManager"
)!;
export const NativeThemeModule = getNativeModule(
"NativeThemeModule", "RTNThemeManager", "DCDTheme"
)!;
export const BundleUpdaterManager = getNativeModule(
"BundleUpdaterManager"
)!;

View file

@ -86,4 +86,53 @@ export namespace RNModules {
*/
DocumentsDirPath: string;
}
export interface ClientInfoModule {
getConstants(): {
/**
* Sentry ingestion DSN URL for alpha/beta builds
*/
SentryAlphaBetaDsn: string
/**
* Sentry ingestion DSN URL for staff builds (?)
*/
SentryStaffDsn: string
/**
* Sentry ingestion DSN URL for stable builds
*/
SentryDsn: string
DeviceVendorID: string
Manifest: string
/**
* Version code
*
* Follows the format of `{MINOR}{CHANNEL}{PATCH}` for `{MINOR}.{PATCH} ({CHANNEL})`
* - `248200` for `248.0 (alpha)`
* - `247105` for `247.5 (beta)`
* - `246011` for `246.11 (stable)`
*/
Build: string
/**
* Version string
*
* Eg. `248.0`
*/
Version: string
/**
* Release channel
*/
ReleaseChannel: string
/**
* Matches `Version`
*/
OTABuild: string
/**
* Identifier for the installed client
*
* - **Android**: Package name
* - **iOS**: Bundle ID
*/
Identifier: string
}
}
}

View file

@ -18,7 +18,7 @@ export const getMetroCache = () => _metroCache;
function buildInitCache() {
const cache = {
_v: CACHE_VERSION,
_buildNumber: NativeClientInfoModule.Build as number,
_buildNumber: NativeClientInfoModule.getConstants().Build,
_modulesCount: Object.keys(window.modules).length,
flagsIndex: {} as Record<string, number>,
findIndex: {} as Record<string, ModulesMap | undefined>,
@ -48,7 +48,7 @@ export async function initMetroCache() {
_metroCache = null!;
throw "cache invalidated; cache version outdated";
}
if (_metroCache._buildNumber !== NativeClientInfoModule.Build) {
if (_metroCache._buildNumber !== NativeClientInfoModule.getConstants().Build) {
_metroCache = null!;
throw "cache invalidated; version mismatch";
}