Last active
September 28, 2023 12:17
-
-
Save BarelyAliveMau5/6c3ae780b7e943ee9fb0ef96c1646b1d to your computer and use it in GitHub Desktop.
Custom userscript for a specific game that I can't name publicly (the "match" param has a sha256 of the matching website)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name ZoomControl | |
// @namespace http://tampermonkey.net/ | |
// @version 0.2 | |
// @description Use mouse wheel to adjust the viewport zoom | |
// @author BarelyAliveMau5 | |
// @match https://5891d91f6481d61777db401c5b7861d0e6ad4d67356fcbd61d83b00131d6a814 | |
// @icon https://5891d91f6481d61777db401c5b7861d0e6ad4d67356fcbd61d83b00131d6a814/static/img/icon64.png | |
// @grant none | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
var DEBUG = false; | |
Window.__game_socket = undefined; | |
Window.__zoom_ctrl_factor = 0.05; // changes how much zoom will be increased/decreased | |
/** | |
* Sends a websocket message to the connected game socket telling it to join a specific team. | |
* Works even for grayed out teams for some reason, you can join the winning team if you want with this. | |
* @param {WebSocket} socket - socket object used to send the message | |
* @param {Number} team_id - The team to be joined | |
*/ | |
Window.__join_team = async function (team_id = 0, socket=Window.__game_socket) { | |
if (!(socket instanceof WebSocket)) throw new Error("socket is not a WebSocket"); | |
if (socket.readyState != WebSocket.OPEN) return; | |
/****** subject to change if the game updates ******/ | |
socket.send(JSON.stringify({"name":"enter","data":{"team":team_id,"spectate":false}})); | |
// sleep a bit, supposedly waiting the confirmation | |
await new Promise(r => setTimeout(() => r(), 100)); | |
// spawn into the team | |
socket.send(JSON.stringify({ "name": "respawn" })); | |
// tryfix | |
await new Promise(r => setTimeout(() => r(), 100)); | |
socket.send(JSON.stringify({"name":"get_name","data":{"id":1}})); | |
} | |
/** | |
* ensures that the right socket object is found | |
* @param {Object} obj - object to be validated | |
* @param {String} propertyToFind - name of the property to find | |
* @returns true if the validation was ok, false otherwise | |
*/ | |
Window.__validate_socket_object = function (obj, propertyToFind) { | |
let socket = obj[propertyToFind]; | |
if (typeof (socket) === 'object' | |
&& socket instanceof WebSocket | |
&& socket.url.includes("starblast.io")) { | |
return true; | |
} | |
return false; | |
} | |
/** | |
* recursive function to deep find a specific property inside objects | |
* @param obj - initial object | |
* @param propertyToFind - name of the property to find | |
* @param cur_depth - used to keep track of current depth, should be 0 at the beginning | |
* @param history - used for debugging, shows where the path of objects where the property was found | |
* @param validator - function used to validate specific criteria to ensure the right property path is found. This is important, | |
* it should be used to verify other variables in the same scope. | |
* @param visited - used internally to avoid circular references | |
* @returns the found object, if found. null if not found. | |
*/ | |
Window.__find_recursively = function (obj, propertyToFind, cur_depth = 0, history = [], validator = null, visited = []) { | |
if (cur_depth > 8 || obj == null) { | |
return null; | |
} | |
if (Object.keys(obj).includes(propertyToFind) // main validation, find the desired property in subitems | |
&& ((validator && validator(obj, propertyToFind)) || validator == null)) { // additional validations, filter-game-specific stuff | |
return obj[propertyToFind]; | |
} | |
// avoid circular references | |
for (var vobj of visited) { | |
if (vobj === obj) | |
return null; | |
} | |
visited.push(obj); | |
if (typeof (obj) === 'object') { | |
for (let i of Object.keys(obj)) { | |
let ret = Window.__find_recursively(obj[i], propertyToFind, cur_depth + 1, history, validator, visited); | |
if (ret) { | |
history.push(i); | |
return ret; | |
} | |
} | |
} | |
return null; | |
} | |
/** | |
* non-recursive version of __find_recursively | |
* @returns the found object, if found. null if not found. | |
*/ | |
Window.__find_iteractively = function (obj, propertyToFind, validator = null, history = [], deepFirst=false, maxDepth=8) { | |
let visited = []; | |
let stack = Object.keys(obj).map(obj => [{o: obj, depth: 1, parent: undefined}][0]); | |
let pushMethod = deepFirst ? stack.push : stack.unshift; | |
while (stack.length > 0){ | |
let current = stack.pop(); | |
let curObj = current.o, | |
curDepth = current.depth; | |
if (curDepth > maxDepth || curObj == null) { | |
continue; | |
} | |
if (Object.keys(curObj).includes(propertyToFind) // main validation, find the desired property in subitems | |
&& ((validator && validator(curObj, propertyToFind)) || validator == null)) // additional validations, filter-game-specific stuff | |
{ | |
let objParent = parent; | |
while (objParent != null) { | |
if (objParent) { | |
history.push(objParent); | |
} | |
objParent = objParent.parent; | |
} | |
history.push(curObj); | |
history.push(curObj[propertyToFind]); | |
return curObj[propertyToFind]; | |
} | |
// avoid circular references | |
for (var vobj of visited) { | |
if (vobj === curObj) | |
return null; | |
} | |
visited.push(curObj); | |
if (typeof (curObj) === 'object') { | |
for (let i of Object.keys(curObj)) { | |
pushMethod({o: curObj[i], depth: curDepth+1, parent: curObj}); | |
} | |
} | |
} | |
} | |
/** | |
* ensures that the right ship list was found, there is one with json strings in it which is useless for us | |
* @param {Object} obj - object to be validated | |
* @param {String} propertyToFind - name of the property to find | |
* @returns true if the validation was ok, false otherwise | |
*/ | |
Window.__validate_ships_object = function (obj, propertyToFind) { | |
let ships = obj[propertyToFind]; | |
if (typeof (ships) === 'object' | |
&& ships.length > 0 | |
&& typeof (ships[0]) !== 'string') { // cant edit ship properties if they are json strings | |
return true; | |
} | |
return false; | |
} | |
/** | |
* callback used to set the game zoom | |
* @param event - mousewheel event | |
* @param event - list of all game's ships and their respective properties | |
*/ | |
Window.__zoom_ctrl_fn = function (event, ships) { | |
// scroll down = decrease zoom | |
// scroll up = increase zoom | |
let y = event.deltaY > 0 ? -Window.__zoom_ctrl_factor : Window.__zoom_ctrl_factor; | |
ships.forEach((s) => { | |
// by default some game modes dont have a zoom property, but it still works if added | |
if (s.zoom == null) { | |
s.zoom = 1.0 | |
} | |
// prevent zero and negative values that makes the camera zoom in and out like crazy | |
s.zoom = Math.max(s.zoom + y, Window.__zoom_ctrl_factor) | |
}) | |
} | |
Window.__patch_function_zoom = function (thisArg, args) { | |
let ships = null, | |
objectPath = []; | |
if (args != null | |
&& args.length > 1 | |
&& args[1].length > 0 | |
&& args[1][0]) | |
{ | |
ships = Window.__find_recursively(args[1][0], "ships", 0, objectPath, Window.__validate_ships_object); | |
} | |
if (ships) { | |
console.log(objectPath.reverse().join(".")) | |
if (Window.__e == undefined) { | |
Window.__e = args[1][0] | |
window.addEventListener('mousewheel', (event) => Window.__zoom_ctrl_fn(event, ships)); | |
ships.forEach((s) => { | |
if (s.typespec != null | |
&& s.typespec.specs != null | |
&& s.typespec.specs.ship != null) | |
{ | |
s.typespec.specs.ship.rotation = [200, 220]; | |
s.typespec.specs.ship.mass = 20; | |
} | |
// capture the flag mode | |
if (s.specs != null | |
&& s.specs.ship != null) | |
{ | |
s.specs.ship.rotation = [200, 220]; | |
s.specs.ship.mass = 20; | |
} | |
}) | |
} | |
return true; | |
}; | |
return false; | |
} | |
/** | |
* Tries to apply a patch function inside a private scope | |
* @param {Function} patch_function - callback which receives the params of the proxied call | |
*/ | |
Window.__apply_patch = function (patch_function) { | |
// intercept every function call until we find the right scope | |
var Function_prototype_call_orig = Function.prototype.call | |
Function.prototype.call = new Proxy(Function.prototype.call, { | |
apply(thisArg, ...args) { | |
// remove patch because this may slowdown a bit the page | |
// since this whole block of code is executed on every function call | |
Window.__c = Window.__c == undefined ? 0 : Window.__c + 1; | |
if (Window.__c > 10000) { | |
console.log("unable to patch. giving up") | |
Function.prototype.call = Function_prototype_call_orig | |
} | |
if (patch_function(thisArg, args)) { | |
console.log("patch applied successfully") | |
Function.prototype.call = Function_prototype_call_orig | |
} | |
return thisArg.apply(...args); | |
} | |
}) | |
} | |
Window.patch_get_socket = function () { | |
let objectPath = []; | |
let socket = Window.__find_recursively(window, "socket", 0, objectPath, Window.__validate_socket_object); | |
if (socket) { | |
Window.__game_socket = socket; | |
return Window.__game_socket; | |
} | |
} | |
window.addEventListener('keypress', function (e) { | |
console.log(e.key) | |
if (e.key == '.') { | |
Window.__apply_patch(Window.__patch_function_zoom); | |
} | |
}) | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment