blackhack

Cheat for brofist.io

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name           blackhack
// @version        20.9
// @description    Cheat for brofist.io
// @author         CiNoP
// @match          *://*.brofist.io/*

// @require        https://update.greasyfork.org/scripts/571913/1787411/blackhack-utils.js
// @require        https://update.greasyfork.org/scripts/571914/1789017/blackhack-map.js
// @require        https://update.greasyfork.org/scripts/571915/1796552/blackhack-ui.js
// @require        https://update.greasyfork.org/scripts/573606/1796986/blackhack-logic.js

// @icon           https://www.google.com/s2/favicons?sz=64&domain=brofist.io
// @grant          none
// @license        GPL-3.0-only
// @namespace      brofist.io 1st-cheat (FOR ALL MODES)
// ==/UserScript==
/* jshint esversion: 11 */
/* jshint asi: true */

(() => {
	'use strict';
	const BH = window.BH;

	console.log(`utilsVer: ${BH.utilsVer}, uiVer: ${BH.uiVer}, mapVer: ${BH.mapVer}, logicVer: ${BH.logicVer}`);

	const pathname = location.pathname.toLowerCase();
	const isTwoPlayer = pathname.includes("twoplayer");

	window.hack = {
		mode: null, modeController: null, gp: null, networkHandler: null, client: null,
		originalMakeMeGhost: null, originalSocketEmit: null,
		_isMapReloading: false, _mapSocket: null
	};

	const hackReady = new Promise(resolve => {
		if (isTwoPlayer) {
			const c = () => { if (window.hack.mode) resolve(window.hack); else requestAnimationFrame(c); };
			requestAnimationFrame(c);
		} else {
			const traps = { pingCheckCount: ['networkHandler'], othersPlayerNetworkData: ['mode', 'modeController'] };
			let cap = 0; const tot = Object.keys(traps).length;
			for (const[p, tgts] of Object.entries(traps))
				Object.defineProperty(Object.prototype, p, {
					set(v) {
						delete Object.prototype[p]; this[p] = v;
						for (const t of tgts) window.hack[t] = this;
						if (++cap >= tot) { window.hack.gp = window.gp; window.hack.client = window.client; resolve(window.hack); }
					}, configurable: true
				});
		}
	});

	Promise.all([hackReady, BH.lzmaReady]).then(() => main(isTwoPlayer));

	function main(is2pa) {
		const hack = window.hack;
		Object.defineProperty(hack.client, 'runPhysics', { get: () => true, set: () => {} });
		const st = document.createElement('style'); st.textContent = '#startTime{display:none!important}'; document.head.appendChild(st);

		hack.getLP = () => is2pa ? hack.modeController.localPlayer : hack.modeController.player?.gpData ?? null;
		hack.getGPD = () => is2pa ? hack.mode.player?.gpData ?? null : hack.getLP();

		BH.Logic.initHooks(hack, is2pa);

		function patchSocket(sock) {
			if (!sock || sock.__patched) return; sock.__patched = true;
			hack.originalSocketEmit = sock.emit;
			sock.emit = function (ev, ...a) { if (ev === "rGho" && hack.vars.imm) return; return hack.originalSocketEmit.apply(this, [ev, ...a]); };
			const oo = sock.onevent;
			sock.onevent = function (pkt) {
				if (pkt?.data?.[0] === 'changeMap') {
					const raw = pkt.data[1];
					if (!hack._isMapReloading) hack.vars.mapDataBackup = Array.isArray(raw) ? raw.slice() : raw;
					if (hack.vars.xrayGates) {
						hack.vars.xrayGates.forEach(item => {
							if (item.gfx && item.gfx.parent) item.gfx.parent.removeChild(item.gfx);
							if (item.gfx && typeof item.gfx.destroy === 'function') item.gfx.destroy();
						});
						hack.vars.xrayGates =[];
					}
					try {
						let d = raw;
						if (typeof d === 'string') d = JSON.parse(d);
						if (Array.isArray(d) && typeof d[0] === 'number' && window.LZMA) d = JSON.parse(window.LZMA.decompress(d));
						window.mapData = d;
					} catch (_) { window.mapData = raw; }
					if (hack.vars.layoutMode) {
						try {
							const cl = Array.isArray(raw) ? raw.slice() : raw;
							const clean = BH.parseMapToLayout(cl);
							const j = JSON.stringify(clean);
							pkt.data[1] = window.LZMA?.compress ? LZMA.compress(j, 1) : j;
						} catch (_) {}
					}
				}
				return oo.call(this, pkt);
			};
			hack._mapSocket = sock;
		}

		function reloadMap() {
			if (!hack.vars.mapDataBackup || !hack._mapSocket?.onevent) return false;
			hack._isMapReloading = true;
			const bk = Array.isArray(hack.vars.mapDataBackup) ? hack.vars.mapDataBackup.slice() : hack.vars.mapDataBackup;
			hack._mapSocket.onevent({ type: 2, data: ['changeMap', bk] });
			hack._isMapReloading = false; return true;
		}

		function applyNoclipState(en) {
			const lp = hack.getLP();
			if (lp?.p) { lp.p.collisionResponse = !en; lp.p.massMultiplier[1] = en ? 0 : hack.vars.playerMass; }
		}
		function applyGravNoclipState(en) {
			const g = hack.getGPD(), lp = hack.getLP();
			if (lp?.p) { lp.p.collisionResponse = !en; lp.p.gravityScale = en ? 0 : 1; if (!en) lp.p.massMultiplier[1] = hack.vars.playerMass; }
			if (g?.p) g.p.gravityScale = en ? 0 : 1;
			if (!en) { g?.setAngle(0); if (lp && lp !== g) lp.setAngle(0); }
		}

		if (hack.networkHandler) {
			patchSocket(hack.networkHandler.gsSocket);
			const oc = hack.networkHandler.connectToGs;
			if (oc) hack.networkHandler.connectToGs = function (...a) { oc.apply(this, a); patchSocket(this.gsSocket); };
		}

		const cfg = BH.loadSettings();
		hack.vars = {
			imm: false, noclip: false, gravNoclip: false,
			mult: { enabled: false, value: 1, uiValue: cfg.mult },
			gravNoclipGravScale: cfg.gravScale, jumpHeight: cfg.jumpHeight,
			playerMass: cfg.mass, playerDamping: cfg.damping,
			blacklisted: { names: [], data:[] },
			shiftUsedForBlacklist: false, arrowFirstPressed: null,
			layoutMode: false, mapDataBackup: null, layoutSavedPos: null,
			layoutAlpha: { collision: cfg.alphaCollision, poison: cfg.alphaPoison, functional: cfg.alphaFunctional, background: 0xCCCCCC },
			numpadMode: false,
			xrayGates:[],
			tpTargetIndex: null,
			tpToPlayerEnabled: false,
			tpTargetName: "Никто",
			lastDoorSavedPos: null,
			homeCycleIndex: 0,
			// Кеш игрового чекпоинта (массив [x, y] из логики игры)
			lastCheckpointPos: null
		};

		hack.keyBinds = { F1:false,F2:false,F3:false,F5:false,SHIFT:false,HOME:false,END:false,ARROW_UP:false,ARROW_DOWN:false,NUMPAD_MINUS:false,DIGIT_1:false,DIGIT_2:false,DIGIT_3:false,DIGIT_5:false };
		hack.prevKeys = {};
		const keyMap = { F1:'F1',F2:'F2',F3:'F3',F5:'F5',Shift:'SHIFT',Home:'HOME',End:'END',ArrowUp:'ARROW_UP',ArrowDown:'ARROW_DOWN' };
		const codeMap = { NumpadSubtract:'NUMPAD_MINUS',Digit1:'DIGIT_1',Numpad1:'DIGIT_1',Digit2:'DIGIT_2',Numpad2:'DIGIT_2',Digit3:'DIGIT_3',Numpad3:'DIGIT_3',Digit5:'DIGIT_5',Numpad5:'DIGIT_5' };

		document.addEventListener('keydown', e => {
			const cm = codeMap[e.code];
			if (cm) { if (cm.startsWith('DIGIT_') && hack.vars.numpadMode) { const t = (e.target.tagName || '').toLowerCase(); if (t !== 'input' && t !== 'textarea' && !e.target.isContentEditable) e.preventDefault(); } if (cm === 'NUMPAD_MINUS') e.preventDefault(); hack.keyBinds[cm] = true; }
			const m = keyMap[e.key]; if (!m) return; e.preventDefault();
			if (m === 'ARROW_UP' && !hack.keyBinds.ARROW_UP) hack.vars.arrowFirstPressed = 'up';
			if (m === 'ARROW_DOWN' && !hack.keyBinds.ARROW_DOWN) hack.vars.arrowFirstPressed = 'down';
			hack.keyBinds[m] = true;
		});
		document.addEventListener('keyup', e => {
			const cm = codeMap[e.code]; if (cm) hack.keyBinds[cm] = false;
			const m = keyMap[e.key]; if (!m) return; hack.keyBinds[m] = false;
			if (m === 'ARROW_UP' || m === 'ARROW_DOWN') {
				if (!hack.keyBinds.ARROW_UP && !hack.keyBinds.ARROW_DOWN) hack.vars.arrowFirstPressed = null;
				else hack.vars.arrowFirstPressed = hack.keyBinds.ARROW_UP ? 'up' : 'down';
			}
		});
		window.addEventListener('blur', () => { for (const k in hack.keyBinds) hack.keyBinds[k] = false; hack.vars.arrowFirstPressed = null; });

		function isPlayerOnline(n) { const o = hack.mode.otherPlayers; if (!o) return false; for (let i = 0; i < o.length; i++) if (o[i]?.myName === n) return true; return false; }

		if (hack.mode.createKickGui) {
			const _o = hack.mode.createKickGui;
			hack.mode.createKickGui = function (e) {
				const _on = e.on.bind(e);
				e.on = function (ev, fn) {
					return _on(ev, function (...a) {
						if (hack.keyBinds.SHIFT) {
							const nm = this.ref.refP.name.getText();
							if (!hack.vars.blacklisted.names.includes(nm)) {
								hack.vars.blacklisted.names.push(nm);
								hack.vars.blacklisted.data.push([this.ref.refP.g.myIndex, nm]);
								this.ref.refP.g.visible = false;
								if (this.ref.refP.p) this.ref.refP.p.collisionResponse = false;
							}
							hack.vars.shiftUsedForBlacklist = true;
							if (hack._refreshBL) hack._refreshBL();
							return;
						}
						if (this.ref && this.ref.refP && this.ref.refP.g) {
							hack.vars.tpTargetIndex = this.ref.refP.g.myIndex;
							hack.vars.tpTargetName = this.ref.refP.name.getText();
						}
						return fn.apply(this, a);
					});
				};
				_o.call(this, e);
				e.on = _on;
			};
		}

		function restoreAfterReload() {
			let f = 0;
			const r = () => {
				if (++f < 8) { requestAnimationFrame(r); return; } // Увеличиваем задержку для гарантии инициализации объекта lp игрой
				const lp = hack.getLP();
				if (!lp) return;

				// 1. Принудительно восстанавливаем IMM, если он был включен
				if (hack.vars.imm) {
					const g = hack.getGPD();
					if (g) g.me = void 0;
					if (is2pa && hack.mode) hack.mode.makeMeGhost = function() {};
				}

				// 2. Восстанавливаем физические состояния
				if (hack.vars.noclip) applyNoclipState(true);
				if (hack.vars.gravNoclip) applyGravNoclipState(true);

				// 3. Сначала телепортируем игрока в точку, где он стоял до перезагрузки
				const pos = hack.vars.layoutSavedPos;
				if (pos) {
					lp.setX(pos.x);
					lp.setY(pos.y);
					if (lp.p) { lp.p.velocity[0] = 0; lp.p.velocity[1] = 0; }
				}

				// 4. Восстанавливаем игровой чекпоинт ПОСЛЕ телепортации
				// Это предотвращает перезапись чекпоинта координатами спавна
				if (hack.vars.lastCheckpointPos) {
					lp.lastCheckPoint = [...hack.vars.lastCheckpointPos];
				}
			};
			requestAnimationFrame(r);
		}

		hack.funcs = { handlers: {
			toggleImm: en => {
				hack.vars.imm = en; const g = hack.getGPD(); if (g) g.me = en ? void 0 : true;
				if (en) { if (hack.mode.makeMeGhost && !hack.originalMakeMeGhost) hack.originalMakeMeGhost = hack.mode.makeMeGhost; if (hack.mode.makeMeGhost) hack.mode.makeMeGhost = function () {}; }
				else if (hack.originalMakeMeGhost) hack.mode.makeMeGhost = hack.originalMakeMeGhost;
			},
			toggleNoclip: en => {
				hack.vars.noclip = en; applyNoclipState(en);
				if (hack.vars.mult.enabled) hack.vars.mult.value = en ? hack.vars.mult.uiValue : 1;
				if (en && hack.vars.gravNoclip) { hack.vars.gravNoclip = false; applyGravNoclipState(false); }
			},
			toggleGravNoclip: en => {
				if (en && hack.vars.noclip) hack.funcs.handlers.toggleNoclip(false);
				hack.vars.gravNoclip = en; applyGravNoclipState(en);
				if (hack.vars.mult.enabled) hack.vars.mult.value = en ? hack.vars.mult.uiValue : 1;
			},
			toggleMult: en => {
				if (!hack.vars.noclip && !hack.vars.gravNoclip) return;
				hack.vars.mult.enabled = en; hack.vars.mult.value = en ? hack.vars.mult.uiValue : 1;
			},
			toggleLayout: en => {
				// Включаем IMM принудительно на время перезагрузки
				const wasImm = hack.vars.imm;
				hack.funcs.handlers.toggleImm(true);

				hack.vars.layoutMode = en;
				const lp = hack.getLP();
				if (lp) {
					hack.vars.layoutSavedPos = { x: lp.getX(), y: lp.getY() };
					// Перед перезагрузкой сохраняем текущий реальный чекпоинт
					if (lp.lastCheckPoint) hack.vars.lastCheckpointPos = [...lp.lastCheckPoint];
				}

				if (reloadMap()) {
					restoreAfterReload();
				}

				try {
					const r = hack.gp.renderer || hack.gp.app?.renderer;
					if (r) { if (en) { if (hack._origBg === undefined) hack._origBg = r.backgroundColor; r.backgroundColor = hack.vars.layoutAlpha.background; } else if (hack._origBg !== undefined) r.backgroundColor = hack._origBg; }
				} catch (_) {}

				// Возвращаем IMM в пользовательское состояние (restoreAfterReload применит его)
				hack.vars.imm = wasImm;
			},
			tpPlayer: (x, y) => { const lp = hack.getLP(); if (lp) { lp.setX(x); lp.setY(y); } },
			toggleNumpadMode: en => { hack.vars.numpadMode = en; },
			blacklistAdd: n => {
				n = (n || '').trim(); if (!n || hack.vars.blacklisted.names.includes(n)) return;
				hack.vars.blacklisted.names.push(n);
				for (const i of BH.Logic.getIdxByName(hack, n)) { hack.vars.blacklisted.data.push([i, n]); const p = hack.mode.otherPlayers[i]; if (p?.gpData) { p.gpData.g.visible = false; if (p.gpData.p) p.gpData.p.collisionResponse = false; } }
				if (hack._refreshBL) hack._refreshBL();
			},
			blacklistRemove: n => {
				const ni = hack.vars.blacklisted.names.indexOf(n); if (ni === -1) return;
				hack.vars.blacklisted.names.splice(ni, 1);
				for (const [i] of hack.vars.blacklisted.data.filter(e => e?.[1] === n)) { const p = hack.mode.otherPlayers[i]; if (p?.gpData) { p.gpData.g.visible = true; if (p.gpData.p) p.gpData.p.collisionResponse = true; } }
				hack.vars.blacklisted.data = hack.vars.blacklisted.data.filter(e => e[1] !== n);
				if (hack._refreshBL) hack._refreshBL();
			}
		}};
		window.hackFunctions = hack.funcs.handlers;

		hack.reloadMap = reloadMap;
		hack.restoreAfterReload = restoreAfterReload;
		hack.isPlayerOnline = isPlayerOnline;

		function updateLoop() {
			const binds = hack.keyBinds, prev = hack.prevKeys, fn = hack.funcs.handlers, v = hack.vars;
			const lp = hack.getLP();
			const jp = k => binds[k] && !prev[k], jr = k => !binds[k] && prev[k];

			// Постоянно кешируем актуальный чекпоинт из свойств игрока
			// Важно: если lp.lastCheckPoint содержит [null, null], значит чекпоинт не взят
			if (lp?.lastCheckPoint && lp.lastCheckPoint[0] !== null) {
				v.lastCheckpointPos = [...lp.lastCheckPoint];
			}

			if (jp('NUMPAD_MINUS')) fn.toggleNumpadMode(!v.numpadMode);
			if (is2pa) {
				if (jp('HOME')) {
					const spawn = hack.gp.list.find(i => i?.id?.includes('spawn'));
					if (v.lastDoorSavedPos && v.homeCycleIndex === 0) {
						fn.tpPlayer(v.lastDoorSavedPos.x, v.lastDoorSavedPos.y);
						v.homeCycleIndex = 1;
					} else if (spawn) {
						fn.tpPlayer(spawn.getX(), spawn.getY());
						v.homeCycleIndex = 0;
					}
				}
				if (jp('END')) {
					const door = hack.gp.list.find(i => i?.id?.includes('door'));
					if (door && lp) {
						v.lastDoorSavedPos = { x: lp.getX(), y: lp.getY() };
						v.homeCycleIndex = 0;
						fn.tpPlayer(door.getX(), door.getY());
					}
				}
			}
			if (jp('F1') || (v.numpadMode && jp('DIGIT_1'))) fn.toggleNoclip(!v.noclip);
			if (jp('F2') || (v.numpadMode && jp('DIGIT_2'))) fn.toggleImm(!v.imm);
			if (jp('F3') || (v.numpadMode && jp('DIGIT_3'))) fn.toggleGravNoclip(!v.gravNoclip);
			if (jp('F5') || (v.numpadMode && jp('DIGIT_5'))) fn.toggleLayout(!v.layoutMode);
			if (jr('SHIFT')) { if (v.shiftUsedForBlacklist) v.shiftUsedForBlacklist = false; else fn.toggleMult(!v.mult.enabled); }

			if (v.noclip) applyNoclipState(true);
			if (v.gravNoclip) { if (lp?.p) lp.p.collisionResponse = false; }
			if (is2pa && v.imm) { const g = hack.getGPD(); if (g?.me !== undefined) g.me = void 0; }
			if (!v.noclip && !v.gravNoclip) { if (lp?.p) { lp.p.massMultiplier[1] = v.playerMass; lp.p.damping = v.playerDamping; } }

			try {
				BH.Logic.enforceBL(hack);
				BH.Logic.updateTP(hack);
				BH.Logic.updateXray(hack);
			} catch (e) { console.error("[BH] Loop Logic Error: ", e); }

			const dNc = document.getElementById('hk-d-nc'), dIm = document.getElementById('hk-d-im');
			const dGn = document.getElementById('hk-d-gn'), dLm = document.getElementById('hk-d-lm');
			if (dNc) dNc.style.background = v.noclip ? '#0f0' : '#f44';
			if (dIm) dIm.style.background = v.imm ? '#0f0' : '#f44';
			if (dGn) dGn.style.background = v.gravNoclip ? '#0f0' : '#f44';
			if (dLm) dLm.style.background = v.layoutMode ? '#0f0' : '#f44';

			for (const k in binds) prev[k] = binds[k];
			requestAnimationFrame(updateLoop);
		}
		requestAnimationFrame(updateLoop);

		const cSS = () => { const lf = hack.client.loopFunctions; for (let i = 0; i < lf.length; i++) if (lf[i] && (lf[i].timeOut === 200 || lf[i].timeOut === 150)) { lf[i].timeOut = 10; return; } requestAnimationFrame(cSS); };
		cSS();

		BH.createZoom();
		BH.createUI();
		console.log("blackhack v20.9 loaded");
	}
})();