Skip to content

Instantly share code, notes, and snippets.

@BarelyAliveMau5
Last active September 28, 2023 12:17
Show Gist options
  • Save BarelyAliveMau5/6c3ae780b7e943ee9fb0ef96c1646b1d to your computer and use it in GitHub Desktop.
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)
// ==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