From 40fcdce26587a6a91efec1bb44633db56f483f26 Mon Sep 17 00:00:00 2001 From: nexpid <60316309+nexpid@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:36:10 +0100 Subject: [PATCH 1/5] feat(scripts): add debugger script --- bun.lockb | Bin 318428 -> 325292 bytes package.json | 5 +- scripts/debugger.mjs | 138 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 scripts/debugger.mjs diff --git a/bun.lockb b/bun.lockb index 478a04e62df32c269f277121eccf2e76c4eacee2..80b4400fa67fd2a870796131c250017f1c6d199f 100644 GIT binary patch delta 52947 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 delta 49829 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.p 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 mOExX; (isPrompting ? '\n' : '') + chalk.bold.blue('[Debugger] ') + message + +const clientColorify = (style, message) => + (isPrompting ? '\n' : '') + + (style === 'error' + ? chalk.bold.red('[Revenge] ERR! ') + chalk.red(message) + : style === 'warn' + ? chalk.bold.yellow('[Revenge] ') + chalk.yellow(message) + : chalk.bold.green('[Revenge] ') + message) + +const logAsDebugger = message => console.info(debuggerColorify(message)) + +const logAsClient = message => console.info(clientColorify(null, message)) +const logAsClientWarn = message => console.warn(clientColorify('warn', message)) +const logAsClientError = message => console.error(clientColorify('error', message)) + +const copyPrompt = ' --copy' +const clearHistoryPrompt = '--ch' + +export function serve() { + let websocketOpen = false + let awaitingReply + + const wss = new WebSocketServer({ + port: 9090, + }) + wss.on('connection', ws => { + if (websocketOpen) return + websocketOpen = true + + logAsDebugger('Starting debugger session') + + ws.on('message', data => { + try { + /** @type {{ level: "info" | "warn" | "error", message: string, nonce?: string }} */ + const json = JSON.parse(data.toString()) + + if (awaitingReply?.cb && awaitingReply?.nonce && awaitingReply.nonce === json.nonce) { + if (json.level === 'info' && awaitingReply.toCopy) { + clipboardy.write(json.message) + awaitingReply.cb(null, debuggerColorify('Copied result to clipboard')) + } else + awaitingReply.cb( + null, + json.level === 'error' + ? clientColorify('error', json.message) + : clientColorify(null, json.message), + ) + awaitingReply = null + isPrompting = true + } else { + if (json.level === 'error') logAsClientError(json.message) + else if (json.level === 'warn') logAsClientWarn(json.message) + else logAsClient(json.message) + + if (isPrompting) rl.displayPrompt(true) + } + } catch {} + }) + + isPrompting = true + const rl = repl.start({ + eval(input, _, __, cb) { + if (!isPrompting) return + if (!input.trim()) return cb() + + try { + isPrompting = false + + const code = input.trim() + if (code === clearHistoryPrompt) { + writeFile(join(debuggerHistoryPath, 'history.txt'), '') + logAsDebugger('Cleared repl history') + return cb() + } + + awaitingReply = { + nonce: crypto.randomUUID(), + cb, + toCopy: code.endsWith(copyPrompt), + } + ws.send( + JSON.stringify({ + code: code.endsWith(copyPrompt) ? code.slice(0, -copyPrompt.length) : code, + nonce: awaitingReply.nonce, + }), + ) + } catch (e) { + cb(e) + } + }, + writer(msg) { + return msg + }, + }) + rl.setupHistory(join(debuggerHistoryPath, 'history.txt'), () => void 0) + + rl.on('close', () => { + isPrompting = false + ws.close() + logAsDebugger('Closing debugger, press Ctrl+C to exit') + }) + + ws.on('close', () => { + logAsDebugger('Websocket was closed') + rl.close() + websocketOpen = false + }) + }) + + logAsDebugger('Debugger ready at :9090') + logAsDebugger(`Add${chalk.bold(copyPrompt)} to your prompt to copy the result to clipboard`) + logAsDebugger(`Type ${chalk.bold(clearHistoryPrompt)} to clear your repl history `) + + return wss +} + +if (!existsSync(debuggerHistoryPath)) await mkdir(debuggerHistoryPath, { recursive: true }) + +serve() From a22136cdb77db3b7452cb27314ed9d5c56bc792c Mon Sep 17 00:00:00 2001 From: nexpid <60316309+nexpid@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:36:55 +0100 Subject: [PATCH 2/5] feat(src/plugins/debugger): add Debugger plugin --- src/plugins/debugger/debugger.ts | 116 ++++++++++++++++++++++++ src/plugins/debugger/index.tsx | 115 +++++++++++++++++++++++ src/plugins/debugger/pages/Debugger.tsx | 95 +++++++++++++++++++ src/plugins/index.ts | 1 + 4 files changed, 327 insertions(+) create mode 100644 src/plugins/debugger/debugger.ts create mode 100644 src/plugins/debugger/index.tsx create mode 100644 src/plugins/debugger/pages/Debugger.tsx diff --git a/src/plugins/debugger/debugger.ts b/src/plugins/debugger/debugger.ts new file mode 100644 index 0000000..802c9a7 --- /dev/null +++ b/src/plugins/debugger/debugger.ts @@ -0,0 +1,116 @@ +import { toasts } from '@revenge-mod/modules/common' +import { EventEmitter } from 'events' +import type { DebuggerContextType } from '.' + +export const DebuggerEvents = new EventEmitter() + +export type DebuggerEventsListeners = { + connect: () => void + disconnect: () => void + // biome-ignore lint/suspicious/noExplicitAny: Anything can be thrown + error: (err: any) => void + // biome-ignore lint/suspicious/noExplicitAny: Anything can be thrown + '*': (event: keyof DebuggerEventsListeners, err?: any) => void +} + +export const DebuggerContext = { + ws: undefined, + connected: false, +} as { + ws: WebSocket | undefined + connected: boolean +} + +export function disconnectFromDebugger() { + DebuggerContext.ws!.close() + DebuggerContext.connected = false +} + +export function connectToDebugger(addr: string, context: DebuggerContextType) { + const ws = (DebuggerContext.ws = new WebSocket(`ws://${addr}`)) + + ws.addEventListener('open', () => { + DebuggerContext.connected = true + DebuggerEvents.emit('connect') + DebuggerEvents.emit('*', 'connect') + + toasts.open({ + key: 'revenge.debugger.connected', + content: 'Connected to debugger!', + }) + }) + + ws.addEventListener('close', () => { + DebuggerContext.connected = false + DebuggerEvents.emit('disconnect') + DebuggerEvents.emit('*', 'disconnect') + + toasts.open({ + key: 'revenge.debugger.disconnected', + content: 'Disconnected from debugger!', + }) + }) + + ws.addEventListener('error', e => { + DebuggerContext.connected = false + DebuggerEvents.emit('error', e) + DebuggerEvents.emit('*', 'error', e) + + toasts.open({ + key: 'revenge.debugger.errored', + content: 'Debugger errored!', + }) + }) + + ws.addEventListener('message', e => { + try { + const json = JSON.parse(e.data) as { + code: string + nonce: string + } + + if (typeof json.code === 'string' && typeof json.nonce === 'string') { + let res: unknown + try { + // biome-ignore lint/security/noGlobalEval: This is intentional + res = globalThis.eval(json.code) + } catch (e) { + res = e + } + + const inspect = + context.revenge.modules.findProp< + (val: unknown, opts?: { depth?: number; showHidden?: boolean; color?: boolean }) => string + >('inspect')! + + try { + if (res instanceof Error) + ws.send( + JSON.stringify({ + level: 'error', + message: String(res), + nonce: json.nonce, + }), + ) + else { + ws.send( + JSON.stringify({ + level: 'info', + message: inspect(res, { showHidden: true }), + nonce: json.nonce, + }), + ) + } + } catch (e) { + ws.send( + JSON.stringify({ + level: 'error', + message: `DebuggerInternalError: ${String(e)}`, + nonce: json.nonce, + }), + ) + } + } + } catch {} + }) +} diff --git a/src/plugins/debugger/index.tsx b/src/plugins/debugger/index.tsx new file mode 100644 index 0000000..6291bb0 --- /dev/null +++ b/src/plugins/debugger/index.tsx @@ -0,0 +1,115 @@ +import { getAssetIndexByName } from '@revenge-mod/assets' +import type { PluginContextFor } from '@revenge-mod/plugins' +import { sleep } from '@revenge-mod/utils/functions' +import { registerPlugin } from 'libraries/plugins/src/internals' +import DebuggerSettingsPage from './pages/Debugger' +import { connectToDebugger, DebuggerContext } from './debugger' +import { BundleUpdaterManager } from '@revenge-mod/modules/native' + +const plugin = registerPlugin<{ + connectOnStartup: boolean + debuggerUrl: string +}>( + { + name: 'Debugger', + author: 'Revenge', + description: 'A simple WebSocket debugger for Revenge to make development easier', + id: 'revenge.debugger', + version: '1.0.0', + icon: 'LinkIcon', + async afterAppRender(context) { + const { + revenge: { + ui: { settings: sui }, + }, + patcher, + cleanup, + storage: { connectOnStartup, debuggerUrl }, + } = context + + if (connectOnStartup) connectToDebugger(debuggerUrl, context) + + // Wait for the section to be added by the Settings plugin + await sleep(0) + + // biome-ignore lint/suspicious/noExplicitAny: globalThis can be anything + const win = globalThis as any + + const doCleanup = new Set<() => void>() + + cleanup( + sui.addRowsToSection('Revenge', { + Debugger: { + type: 'route', + label: 'Debugger', + icon: getAssetIndexByName('LinkIcon'), + component: () => ( + + + + ), + }, + }), + + (() => { + win.debgr = { + reload: () => BundleUpdaterManager.reload(), + patcher: { + // biome-ignore lint/suspicious/noExplicitAny: These arguments can be anything lol + snipe: (object: any, key: any, callback?: (args: unknown) => void) => { + doCleanup.add( + patcher.after( + object, + key, + callback ?? ((args, ret) => console.log('[SNIPER]', args, ret)), + 'debgr.patcher.snipe', + ), + ) + }, + // biome-ignore lint/suspicious/noExplicitAny: These arguments can be anything lol 2 + noop: (object: any, key: any) => { + doCleanup.add(patcher.instead(object, key, () => void 0, 'debgr.patcher.noop')) + }, + wipe: () => { + for (const c of doCleanup) c() + doCleanup.clear() + }, + }, + } + + return () => (win.debgr = undefined) + })(), + + () => { + for (const c of doCleanup) c() + }, + + patcher.before( + win, + 'nativeLoggingHook', + ([message, level]) => { + if (DebuggerContext.ws?.readyState === WebSocket.OPEN) + DebuggerContext.ws.send( + JSON.stringify({ + level: level === 3 ? 'error' : level === 2 ? 'warn' : 'info', + message, + }), + ) + }, + 'loggerPatch', + ), + ) + }, + initializeStorage() { + return { + connectOnStartup: false, + debuggerUrl: 'localhost:9090', + } + }, + }, + true, + true, +) + +export type DebuggerContextType = PluginContextFor +export const PluginContext = React.createContext(null!) diff --git a/src/plugins/debugger/pages/Debugger.tsx b/src/plugins/debugger/pages/Debugger.tsx new file mode 100644 index 0000000..8a00a9b --- /dev/null +++ b/src/plugins/debugger/pages/Debugger.tsx @@ -0,0 +1,95 @@ +import { useContext, useEffect, useRef, useState } from 'react' +import { PluginContext } from '..' +import { useObservable } from '@revenge-mod/storage' +import { ScrollView } from 'react-native' +import PageWrapper from 'src/plugins/settings/pages/(Wrapper)' +import { + Stack, + TableRow, + TableRowGroup, + TableRowIcon, + TableSwitchRow, + TextInput, +} from '@revenge-mod/modules/common/components' +import { toasts } from '@revenge-mod/modules/common' +import { + connectToDebugger, + DebuggerContext, + DebuggerEvents, + disconnectFromDebugger, + type DebuggerEventsListeners, +} from '../debugger' + +export default function DebuggerSettingsPage() { + const context = useContext(PluginContext) + const { + storage, + revenge: { assets }, + } = context + useObservable([storage]) + + const tempDebuggerUrl = useRef(storage.debuggerUrl || 'localhost:9090') + const [connected, setConnected] = useState(DebuggerContext.connected) + + useEffect(() => { + const listener: DebuggerEventsListeners['*'] = evt => { + if (evt === 'connect') setConnected(true) + else setConnected(false) + } + + DebuggerEvents.on('*', listener) + + return () => void DebuggerEvents.off('*', listener) + }, []) + + return ( + + + + (tempDebuggerUrl.current = text)} + onBlur={() => { + if (tempDebuggerUrl.current === storage.debuggerUrl) return + storage.debuggerUrl = tempDebuggerUrl.current + + toasts.open({ + key: 'revenge.debugger.savedurl', + content: 'Saved debugger URL!', + }) + }} + returnKeyType="done" + /> + {/* Rerender when connected changes */} + + {connected ? ( + } + onPress={() => disconnectFromDebugger()} + /> + ) : ( + } + onPress={() => connectToDebugger(storage.debuggerUrl, context)} + /> + )} + } + value={storage.connectOnStartup} + onValueChange={v => (storage.connectOnStartup = v)} + /> + + + + + ) +} diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 4710715..17a774f 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -3,3 +3,4 @@ import './settings' import './staff-settings' import './developer-settings' import './warnings' +import './debugger' From 1e80190f13fcfe3ef282846d8c45b7c94ca97244 Mon Sep 17 00:00:00 2001 From: nexpid <60316309+nexpid@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:37:16 +0100 Subject: [PATCH 3/5] chore(libraries/modules/types): add Slider type --- libraries/modules/src/types.d.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/modules/src/types.d.ts b/libraries/modules/src/types.d.ts index 98388d6..9bd235e 100644 --- a/libraries/modules/src/types.d.ts +++ b/libraries/modules/src/types.d.ts @@ -599,7 +599,17 @@ export namespace DiscordModules { } // Other - export type Slider = FC + export type Slider = FC<{ + value: number + step: number + minimumValue: number + maximumValue: number + onValueChange?: (value: number) => void + onSlidingStart?: () => void + onSlidingComplete?: () => void + startIcon?: ReactNode + endIcon?: ReactNode + }> export type FlashList = FC export type Text = FC< TextProps & { From 7b32d54f807d215f1d4a778f914ce4b6d48c910b Mon Sep 17 00:00:00 2001 From: nexpid <60316309+nexpid@users.noreply.github.com> Date: Mon, 30 Dec 2024 00:41:25 +0100 Subject: [PATCH 4/5] chore(bun): run `bun i` --- bun.lockb | Bin 325292 -> 325292 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index 80b4400fa67fd2a870796131c250017f1c6d199f..3c12cc6361e6f0130d5ada2c798a628322714d57 100644 GIT binary patch delta 33 pcmZ4UR(Q=@;f5B*7N#xCp{DGNaVB~OdIs$=rp()8Oj!aB0szis3iAK} delta 33 lcmZ4UR(Q=@;f5B*7N#xCp{DFi3}Db6W6Hce#*`)CAOO5x38nx5 From b7729c0d3b18e65df5b8bb64a48c5ef9bf949e85 Mon Sep 17 00:00:00 2001 From: PalmDevs Date: Wed, 1 Jan 2025 04:14:02 +0700 Subject: [PATCH 5/5] feat(debugger): merge with `developer-settings` plugin, improve server logging --- bun.lockb | Bin 325292 -> 325580 bytes scripts/debugger.mjs | 49 ++-- src/plugins/debugger/index.tsx | 115 ---------- src/plugins/debugger/pages/Debugger.tsx | 95 -------- src/plugins/developer-settings/debugger.d.ts | 14 ++ .../debugger.ts | 212 ++++++++---------- src/plugins/developer-settings/index.tsx | 63 ++++++ .../developer-settings/pages/Developer.tsx | 167 +++++++++----- src/plugins/index.ts | 1 - types.d.ts | 2 + 10 files changed, 320 insertions(+), 398 deletions(-) delete mode 100644 src/plugins/debugger/index.tsx delete mode 100644 src/plugins/debugger/pages/Debugger.tsx create mode 100644 src/plugins/developer-settings/debugger.d.ts rename src/plugins/{debugger => developer-settings}/debugger.ts (60%) diff --git a/bun.lockb b/bun.lockb index 3c12cc6361e6f0130d5ada2c798a628322714d57..17cabe7ce8c8a363135bd0fca89348a7342af0d7 100644 GIT binary patch delta 50832 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 Wc5(CU6(Do3k?LKP$rN console.info(clientColorify(null, message)) const logAsClientWarn = message => console.warn(clientColorify('warn', message)) const logAsClientError = message => console.error(clientColorify('error', message)) -const copyPrompt = ' --copy' -const clearHistoryPrompt = '--ch' +const copyPrompt = '--copy' +const clearHistoryPrompt = '--clear' export function serve() { let websocketOpen = false - let awaitingReply + let nextReply const wss = new WebSocketServer({ port: 9090, @@ -46,25 +47,25 @@ export function serve() { if (websocketOpen) return websocketOpen = true - logAsDebugger('Starting debugger session') + logAsDebugger('Client connected') ws.on('message', data => { try { /** @type {{ level: "info" | "warn" | "error", message: string, nonce?: string }} */ const json = JSON.parse(data.toString()) - if (awaitingReply?.cb && awaitingReply?.nonce && awaitingReply.nonce === json.nonce) { - if (json.level === 'info' && awaitingReply.toCopy) { + if (nextReply?.cb && nextReply?.nonce && nextReply.nonce === json.nonce) { + if (json.level === 'info' && nextReply.toCopy) { clipboardy.write(json.message) - awaitingReply.cb(null, debuggerColorify('Copied result to clipboard')) + nextReply.cb(null, debuggerColorify('Copied result to clipboard')) } else - awaitingReply.cb( + nextReply.cb( null, json.level === 'error' ? clientColorify('error', json.message) : clientColorify(null, json.message), ) - awaitingReply = null + nextReply = null isPrompting = true } else { if (json.level === 'error') logAsClientError(json.message) @@ -88,11 +89,11 @@ export function serve() { const code = input.trim() if (code === clearHistoryPrompt) { writeFile(join(debuggerHistoryPath, 'history.txt'), '') - logAsDebugger('Cleared repl history') + logAsDebugger('Cleared REPL history') return cb() } - awaitingReply = { + nextReply = { nonce: crypto.randomUUID(), cb, toCopy: code.endsWith(copyPrompt), @@ -100,7 +101,7 @@ export function serve() { ws.send( JSON.stringify({ code: code.endsWith(copyPrompt) ? code.slice(0, -copyPrompt.length) : code, - nonce: awaitingReply.nonce, + nonce: nextReply.nonce, }), ) } catch (e) { @@ -116,19 +117,27 @@ export function serve() { rl.on('close', () => { isPrompting = false ws.close() - logAsDebugger('Closing debugger, press Ctrl+C to exit') }) ws.on('close', () => { - logAsDebugger('Websocket was closed') + logAsDebugger('Client disconnected') rl.close() websocketOpen = false }) }) - logAsDebugger('Debugger ready at :9090') - logAsDebugger(`Add${chalk.bold(copyPrompt)} to your prompt to copy the result to clipboard`) - logAsDebugger(`Type ${chalk.bold(clearHistoryPrompt)} to clear your repl history `) + console.info(chalk.red('\nDebugger ready, available on:\n')) + const netInterfaces = os.networkInterfaces() + for (const netinterfaces of Object.values(netInterfaces)) { + for (const details of netinterfaces || []) { + if (details.family !== 'IPv4') continue + const port = chalk.yellowBright(wss.address()?.port.toString()) + console.info(` ${chalk.gray('http://')}${details.address}:${port}`) + } + } + + console.log(chalk.gray.underline(`\nRun with ${chalk.bold.white(copyPrompt)} to your prompt to copy the result to clipboard`)) + console.log(chalk.gray.underline(`Run with ${chalk.bold.white(clearHistoryPrompt)} to clear your REPL history\n`)) return wss } diff --git a/src/plugins/debugger/index.tsx b/src/plugins/debugger/index.tsx deleted file mode 100644 index 6291bb0..0000000 --- a/src/plugins/debugger/index.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { getAssetIndexByName } from '@revenge-mod/assets' -import type { PluginContextFor } from '@revenge-mod/plugins' -import { sleep } from '@revenge-mod/utils/functions' -import { registerPlugin } from 'libraries/plugins/src/internals' -import DebuggerSettingsPage from './pages/Debugger' -import { connectToDebugger, DebuggerContext } from './debugger' -import { BundleUpdaterManager } from '@revenge-mod/modules/native' - -const plugin = registerPlugin<{ - connectOnStartup: boolean - debuggerUrl: string -}>( - { - name: 'Debugger', - author: 'Revenge', - description: 'A simple WebSocket debugger for Revenge to make development easier', - id: 'revenge.debugger', - version: '1.0.0', - icon: 'LinkIcon', - async afterAppRender(context) { - const { - revenge: { - ui: { settings: sui }, - }, - patcher, - cleanup, - storage: { connectOnStartup, debuggerUrl }, - } = context - - if (connectOnStartup) connectToDebugger(debuggerUrl, context) - - // Wait for the section to be added by the Settings plugin - await sleep(0) - - // biome-ignore lint/suspicious/noExplicitAny: globalThis can be anything - const win = globalThis as any - - const doCleanup = new Set<() => void>() - - cleanup( - sui.addRowsToSection('Revenge', { - Debugger: { - type: 'route', - label: 'Debugger', - icon: getAssetIndexByName('LinkIcon'), - component: () => ( - - - - ), - }, - }), - - (() => { - win.debgr = { - reload: () => BundleUpdaterManager.reload(), - patcher: { - // biome-ignore lint/suspicious/noExplicitAny: These arguments can be anything lol - snipe: (object: any, key: any, callback?: (args: unknown) => void) => { - doCleanup.add( - patcher.after( - object, - key, - callback ?? ((args, ret) => console.log('[SNIPER]', args, ret)), - 'debgr.patcher.snipe', - ), - ) - }, - // biome-ignore lint/suspicious/noExplicitAny: These arguments can be anything lol 2 - noop: (object: any, key: any) => { - doCleanup.add(patcher.instead(object, key, () => void 0, 'debgr.patcher.noop')) - }, - wipe: () => { - for (const c of doCleanup) c() - doCleanup.clear() - }, - }, - } - - return () => (win.debgr = undefined) - })(), - - () => { - for (const c of doCleanup) c() - }, - - patcher.before( - win, - 'nativeLoggingHook', - ([message, level]) => { - if (DebuggerContext.ws?.readyState === WebSocket.OPEN) - DebuggerContext.ws.send( - JSON.stringify({ - level: level === 3 ? 'error' : level === 2 ? 'warn' : 'info', - message, - }), - ) - }, - 'loggerPatch', - ), - ) - }, - initializeStorage() { - return { - connectOnStartup: false, - debuggerUrl: 'localhost:9090', - } - }, - }, - true, - true, -) - -export type DebuggerContextType = PluginContextFor -export const PluginContext = React.createContext(null!) diff --git a/src/plugins/debugger/pages/Debugger.tsx b/src/plugins/debugger/pages/Debugger.tsx deleted file mode 100644 index 8a00a9b..0000000 --- a/src/plugins/debugger/pages/Debugger.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { useContext, useEffect, useRef, useState } from 'react' -import { PluginContext } from '..' -import { useObservable } from '@revenge-mod/storage' -import { ScrollView } from 'react-native' -import PageWrapper from 'src/plugins/settings/pages/(Wrapper)' -import { - Stack, - TableRow, - TableRowGroup, - TableRowIcon, - TableSwitchRow, - TextInput, -} from '@revenge-mod/modules/common/components' -import { toasts } from '@revenge-mod/modules/common' -import { - connectToDebugger, - DebuggerContext, - DebuggerEvents, - disconnectFromDebugger, - type DebuggerEventsListeners, -} from '../debugger' - -export default function DebuggerSettingsPage() { - const context = useContext(PluginContext) - const { - storage, - revenge: { assets }, - } = context - useObservable([storage]) - - const tempDebuggerUrl = useRef(storage.debuggerUrl || 'localhost:9090') - const [connected, setConnected] = useState(DebuggerContext.connected) - - useEffect(() => { - const listener: DebuggerEventsListeners['*'] = evt => { - if (evt === 'connect') setConnected(true) - else setConnected(false) - } - - DebuggerEvents.on('*', listener) - - return () => void DebuggerEvents.off('*', listener) - }, []) - - return ( - - - - (tempDebuggerUrl.current = text)} - onBlur={() => { - if (tempDebuggerUrl.current === storage.debuggerUrl) return - storage.debuggerUrl = tempDebuggerUrl.current - - toasts.open({ - key: 'revenge.debugger.savedurl', - content: 'Saved debugger URL!', - }) - }} - returnKeyType="done" - /> - {/* Rerender when connected changes */} - - {connected ? ( - } - onPress={() => disconnectFromDebugger()} - /> - ) : ( - } - onPress={() => connectToDebugger(storage.debuggerUrl, context)} - /> - )} - } - value={storage.connectOnStartup} - onValueChange={v => (storage.connectOnStartup = v)} - /> - - - - - ) -} diff --git a/src/plugins/developer-settings/debugger.d.ts b/src/plugins/developer-settings/debugger.d.ts new file mode 100644 index 0000000..a3ea97d --- /dev/null +++ b/src/plugins/developer-settings/debugger.d.ts @@ -0,0 +1,14 @@ +declare global { + var dbgr: { + reload(): void + patcher: { + // biome-ignore lint/suspicious/noExplicitAny: The object can be anything + snipe(object: any, key: string, callback?: (args: unknown) => void): void + // biome-ignore lint/suspicious/noExplicitAny: The object can be anything + noop(object: any, key: string): void + wipe(): void + } + } | undefined +} + +export {} \ No newline at end of file diff --git a/src/plugins/debugger/debugger.ts b/src/plugins/developer-settings/debugger.ts similarity index 60% rename from src/plugins/debugger/debugger.ts rename to src/plugins/developer-settings/debugger.ts index 802c9a7..734d117 100644 --- a/src/plugins/debugger/debugger.ts +++ b/src/plugins/developer-settings/debugger.ts @@ -1,116 +1,96 @@ -import { toasts } from '@revenge-mod/modules/common' -import { EventEmitter } from 'events' -import type { DebuggerContextType } from '.' - -export const DebuggerEvents = new EventEmitter() - -export type DebuggerEventsListeners = { - connect: () => void - disconnect: () => void - // biome-ignore lint/suspicious/noExplicitAny: Anything can be thrown - error: (err: any) => void - // biome-ignore lint/suspicious/noExplicitAny: Anything can be thrown - '*': (event: keyof DebuggerEventsListeners, err?: any) => void -} - -export const DebuggerContext = { - ws: undefined, - connected: false, -} as { - ws: WebSocket | undefined - connected: boolean -} - -export function disconnectFromDebugger() { - DebuggerContext.ws!.close() - DebuggerContext.connected = false -} - -export function connectToDebugger(addr: string, context: DebuggerContextType) { - const ws = (DebuggerContext.ws = new WebSocket(`ws://${addr}`)) - - ws.addEventListener('open', () => { - DebuggerContext.connected = true - DebuggerEvents.emit('connect') - DebuggerEvents.emit('*', 'connect') - - toasts.open({ - key: 'revenge.debugger.connected', - content: 'Connected to debugger!', - }) - }) - - ws.addEventListener('close', () => { - DebuggerContext.connected = false - DebuggerEvents.emit('disconnect') - DebuggerEvents.emit('*', 'disconnect') - - toasts.open({ - key: 'revenge.debugger.disconnected', - content: 'Disconnected from debugger!', - }) - }) - - ws.addEventListener('error', e => { - DebuggerContext.connected = false - DebuggerEvents.emit('error', e) - DebuggerEvents.emit('*', 'error', e) - - toasts.open({ - key: 'revenge.debugger.errored', - content: 'Debugger errored!', - }) - }) - - ws.addEventListener('message', e => { - try { - const json = JSON.parse(e.data) as { - code: string - nonce: string - } - - if (typeof json.code === 'string' && typeof json.nonce === 'string') { - let res: unknown - try { - // biome-ignore lint/security/noGlobalEval: This is intentional - res = globalThis.eval(json.code) - } catch (e) { - res = e - } - - const inspect = - context.revenge.modules.findProp< - (val: unknown, opts?: { depth?: number; showHidden?: boolean; color?: boolean }) => string - >('inspect')! - - try { - if (res instanceof Error) - ws.send( - JSON.stringify({ - level: 'error', - message: String(res), - nonce: json.nonce, - }), - ) - else { - ws.send( - JSON.stringify({ - level: 'info', - message: inspect(res, { showHidden: true }), - nonce: json.nonce, - }), - ) - } - } catch (e) { - ws.send( - JSON.stringify({ - level: 'error', - message: `DebuggerInternalError: ${String(e)}`, - nonce: json.nonce, - }), - ) - } - } - } catch {} - }) -} +import { EventEmitter } from 'events' +import type { RevengeLibrary } from '@revenge-mod/revenge' + +export const DebuggerEvents = new EventEmitter() + +export type DebuggerEventsListeners = { + connect: () => void + disconnect: () => void + // biome-ignore lint/suspicious/noExplicitAny: Anything can be thrown + error: (err: any) => void + // biome-ignore lint/suspicious/noExplicitAny: Anything can be thrown + '*': (event: keyof DebuggerEventsListeners, err?: any) => void +} + +export const DebuggerContext = { + ws: undefined, + connected: false, +} as { + ws: WebSocket | undefined + connected: boolean +} + +export function disconnectFromDebugger() { + DebuggerContext.ws!.close() + DebuggerContext.connected = false +} + +export function connectToDebugger(addr: string, revenge: RevengeLibrary) { + const ws = (DebuggerContext.ws = new WebSocket(`ws://${addr}`)) + + ws.addEventListener('open', () => { + DebuggerContext.connected = true + DebuggerEvents.emit('connect') + DebuggerEvents.emit('*', 'connect') + }) + + ws.addEventListener('close', () => { + DebuggerContext.connected = false + DebuggerEvents.emit('disconnect') + DebuggerEvents.emit('*', 'disconnect') + }) + + ws.addEventListener('error', e => { + DebuggerContext.connected = false + DebuggerEvents.emit('error', e) + DebuggerEvents.emit('*', 'error', e) + }) + + ws.addEventListener('message', e => { + try { + const json = JSON.parse(e.data) as { + code: string + nonce: string + } + + if (typeof json.code === 'string' && typeof json.nonce === 'string') { + let res: unknown + try { + // biome-ignore lint/security/noGlobalEval: This is intentional + res = globalThis.eval(json.code) + } catch (e) { + res = e + } + + const inspect = + revenge.modules.findProp< + (val: unknown, opts?: { depth?: number; showHidden?: boolean; color?: boolean }) => string + >('inspect')! + + try { + ws.send( + res instanceof Error + ? JSON.stringify({ + level: 'error', + message: String(res), + nonce: json.nonce, + }) + : JSON.stringify({ + level: 'info', + message: inspect(res, { showHidden: true }), + nonce: json.nonce, + }), + ) + } catch (e) { + ws.send( + JSON.stringify({ + level: 'error', + message: `DebuggerError: ${String(e)}`, + nonce: json.nonce, + }), + ) + } + } + } catch {} + }) +} diff --git a/src/plugins/developer-settings/index.tsx b/src/plugins/developer-settings/index.tsx index c3d3393..bdbf8a1 100644 --- a/src/plugins/developer-settings/index.tsx +++ b/src/plugins/developer-settings/index.tsx @@ -1,3 +1,5 @@ +/// + import { toasts } from '@revenge-mod/modules/common' import { registerPlugin } from '@revenge-mod/plugins/internals' import { sleep } from '@revenge-mod/utils/functions' @@ -6,16 +8,22 @@ import AssetBrowserSettingsPage from './pages/AssetBrowser' import DebugPerformanceTimesSettingsPage from './pages/DebugPerformanceTimes' import DeveloperSettingsPage from './pages/Developer' +import { connectToDebugger, DebuggerContext } from './debugger' import { DevToolsEvents, connectToDevTools } from './devtools' import type { PluginContextFor } from '@revenge-mod/plugins' import type { FunctionComponent } from 'react' +import { BundleUpdaterManager } from '@revenge-mod/modules/native' const plugin = registerPlugin<{ reactDevTools: { address: string autoConnect: boolean } + debugger: { + address: string + autoConnect: boolean + } }>( { name: 'Developer Settings', @@ -58,6 +66,9 @@ const plugin = registerPlugin<{ if (storage.reactDevTools.autoConnect && globalThis.__reactDevTools) connectToDevTools(storage.reactDevTools.address) + if (storage.debugger.autoConnect) connectToDebugger(storage.debugger.address, context.revenge) + + setupDebugger(context) // Wait for the section to be added by the Settings plugin await sleep(0) @@ -92,10 +103,62 @@ const plugin = registerPlugin<{ address: 'localhost:8097', autoConnect: false, }, + debugger: { + address: 'localhost:9090', + autoConnect: false, + }, }), }, true, true, ) +function setupDebugger({ patcher, cleanup }: PluginContextFor) { + const debuggerCleanups = new Set<() => unknown>() + + patcher.before( + globalThis, + 'nativeLoggingHook', + ([message, level]) => { + if (DebuggerContext.ws?.readyState === WebSocket.OPEN) + DebuggerContext.ws.send( + JSON.stringify({ + level: level === 3 ? 'error' : level === 2 ? 'warn' : 'info', + message, + }), + ) + }, + 'loggerPatch', + ) + + globalThis.dbgr = { + reload: () => BundleUpdaterManager.reload(), + patcher: { + snipe: (object, key, callback) => + debuggerCleanups.add( + patcher.after( + object, + key, + callback ?? ((args, ret) => console.log('[SNIPER]', args, ret)), + 'revenge.plugins.developer-settings.debugger.patcher.snipe', + ), + ), + noop: (object, key) => + debuggerCleanups.add(patcher.instead(object, key, () => void 0, 'revenge.plugins.developer-settings.debugger.patcher.noop')), + wipe: () => { + for (const c of debuggerCleanups) c() + debuggerCleanups.clear() + }, + }, + } + + cleanup( + // biome-ignore lint/performance/noDelete: This happens once + () => delete globalThis.dbgr, + () => { + for (const c of debuggerCleanups) c() + }, + ) +} + export const PluginContext = React.createContext>(null!) diff --git a/src/plugins/developer-settings/pages/Developer.tsx b/src/plugins/developer-settings/pages/Developer.tsx index 8ba3a7a..c15fc90 100644 --- a/src/plugins/developer-settings/pages/Developer.tsx +++ b/src/plugins/developer-settings/pages/Developer.tsx @@ -22,6 +22,13 @@ import { connectToDevTools, disconnectFromDevTools, } from '../devtools' +import { + connectToDebugger, + DebuggerContext, + DebuggerEvents, + disconnectFromDebugger, + type DebuggerEventsListeners, +} from '../debugger' import { settings } from '@revenge-mod/preferences' import { ScrollView } from 'react-native' @@ -41,12 +48,15 @@ export default function DeveloperSettingsPage() { const navigation = NavigationNative.useNavigation() const refDevToolsAddr = useRef(storage.reactDevTools.address || 'localhost:8097') - const [connected, setConnected] = useState(DevToolsContext.connected) + const [rdtConnected, setRdtConnected] = useState(DevToolsContext.connected) + + const refDebuggerAddr = useRef(storage.debugger.address || 'localhost:9090') + const [dbgConnected, setDbgConnected] = useState(DebuggerContext.connected) useEffect(() => { const listener: DevToolsEventsListeners['*'] = evt => { - if (evt === 'connect') setConnected(true) - else setConnected(false) + if (evt === 'connect') setRdtConnected(true) + else setRdtConnected(false) } DevToolsEvents.on('*', listener) @@ -54,60 +64,115 @@ export default function DeveloperSettingsPage() { return () => void DevToolsEvents.off('*', listener) }, []) + useEffect(() => { + const listener: DebuggerEventsListeners['*'] = evt => { + if (evt === 'connect') setDbgConnected(true) + else setDbgConnected(false) + } + + DebuggerEvents.on('*', listener) + + return () => void DebuggerEvents.off('*', listener) + }, []) + return ( - {typeof __reactDevTools !== 'undefined' && ( - - (refDevToolsAddr.current = text)} - onBlur={() => { - if (refDevToolsAddr.current === storage.reactDevTools.address) return - storage.reactDevTools.address = refDevToolsAddr.current - - toasts.open({ - key: 'revenge.plugins.settings.react-devtools.saved', - content: 'Saved DevTools address!', - }) - }} - returnKeyType="done" - /> - {/* Rerender when connected changes */} - - {connected ? ( - - } - onPress={() => disconnectFromDevTools()} - /> - ) : ( - + {typeof __reactDevTools !== 'undefined' && ( + <> + (refDevToolsAddr.current = text)} + onBlur={() => { + if (refDevToolsAddr.current === storage.reactDevTools.address) return + storage.reactDevTools.address = refDevToolsAddr.current + + toasts.open({ + key: 'revenge.plugins.settings.react-devtools.saved', + content: 'Saved DevTools address!', + }) + }} + returnKeyType="done" + /> + {/* Rerender when connected changes */} + + {rdtConnected ? ( + + } + onPress={() => disconnectFromDevTools()} + /> + ) : ( + } + onPress={() => connectToDevTools(refDevToolsAddr.current)} + /> + )} + } - onPress={() => connectToDevTools(refDevToolsAddr.current)} + value={storage.reactDevTools.autoConnect} + onValueChange={v => (storage.reactDevTools.autoConnect = v)} /> - )} - } - value={storage.reactDevTools.autoConnect} - onValueChange={v => (storage.reactDevTools.autoConnect = v)} + + + )} + (refDebuggerAddr.current = text)} + onBlur={() => { + if (refDebuggerAddr.current === storage.debugger.address) return + storage.debugger.address = refDebuggerAddr.current + + toasts.open({ + key: 'revenge.plugins.developer-settings.debugger.saved', + content: 'Saved debugger address!', + }) + }} + returnKeyType="done" + /> + {/* Rerender when connected changes */} + + {dbgConnected ? ( + } + onPress={() => disconnectFromDebugger()} /> - - - )} + ) : ( + } + onPress={() => connectToDebugger(storage.debugger.address, context.revenge)} + /> + )} + } + value={storage.debugger.autoConnect} + onValueChange={v => (storage.debugger.autoConnect = v)} + /> + + unknown, timeout?: number): number /** * Calls the garbage collector