// ==UserScript== // @name Google Snake Mod Loader (Standard) // @namespace https://github.com/DarkSnakeGang // @version 1.0.0 // @description Allows you to run multiple different google snake mods // @author DarkSnakeGang (https://github.com/DarkSnakeGang) // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com // @run-at document-start // @grant none // @include /^https:\/\/(www\.)?google\.(com|ad|ae|com\.af|com\.ag|com\.ai|al|am|co\.ao|com\.ar|as|at|com\.au|az|ba|com\.bd|be|bf|bg|com\.bh|bi|bj|com\.bn|com\.bo|com\.br|bs|bt|co\.bw|by|com\.bz|ca|cd|cf|cg|ch|ci|co\.ck|cl|cm|cn|com\.co|co\.cr|com\.cu|cv|com\.cy|cz|de|dj|dk|dm|com\.do|dz|com\.ec|ee|com\.eg|es|com\.et|fi|com\.fj|fm|fr|ga|ge|gg|com\.gh|com\.gi|gl|gm|gr|com\.gt|gy|com\.hk|hn|hr|ht|hu|co\.id|ie|co\.il|im|co\.in|iq|is|it|je|com\.jm|jo|co\.jp|co\.ke|com\.kh|ki|kg|co\.kr|com\.kw|kz|la|com\.lb|li|lk|co\.ls|lt|lu|lv|com\.ly|co\.ma|md|me|mg|mk|ml|com\.mm|mn|ms|com\.mt|mu|mv|mw|com\.mx|com\.my|co\.mz|com\.na|com\.ng|com\.ni|ne|nl|no|com\.np|nr|nu|co\.nz|com\.om|com\.pa|com\.pe|com\.pg|com\.ph|com\.pk|pl|pn|com\.pr|ps|pt|com\.py|com\.qa|ro|ru|rw|com\.sa|com\.sb|sc|se|com\.sg|sh|si|sk|com\.sl|sn|so|sm|sr|st|com\.sv|td|tg|co\.th|com\.tj|tl|tm|tn|to|com\.tr|tt|com\.tw|co\.tz|com\.ua|co\.ug|co\.uk|com\.uy|co\.uz|com\.vc|co\.ve|vg|co\.vi|com\.vn|vu|ws|rs|co\.za|co\.zm|co\.zw|cat)\/search.*(snake|serpiente|serpent|serpente|%E8%B4%AA%E5%90%83%E8%9B%87|%E0%A4%B8%E0%A4%BE%E0%A4%81%E0%A4%AA|%D8%AB%D8%B9%D8%A8%D8%A7%D9%86|%D0%B7%D0%BC%D0%B5%D0%B9%D0%BA%D0%B0|%E3%83%98%E3%83%93%E3%82%B2%E3%83%BC%E3%83%A0|%E0%A4%B8%E0%A4%BE%E0%A4%AA|y%C4%B1lan|%E0%AE%AA%E0%AE%BE%E0%AE%AE%E0%AF%8D%E0%AE%AA%E0%AF%81|r%E1%BA%AFn\+s%C4%83n\+m%E1%BB%93i|%EC%8A%A4%EB%84%A4%EC%9D%B4%ED%81%AC|%E0%B9%80%E0%B8%81%E0%B8%A1%E0%B8%87%E0%B8%B9|%E1%8B%A8%E1%8A%A5%E1%89%A3%E1%89%A5\+%E1%8C%A8%E1%8B%8B%E1%89%B3|%E0%A4%B8%E0%A4%BE%E0%A4%81%E0%A4%AA|%E1%80%99%E1%80%BC%E1%80%BD%E1%80%B1|w%C4%85%C5%BC|hra\+had|had%C3%AD\+hra|slange|ular).*$/ // @match https://*.google.com/fbx?fbx=snake_arcade // @match https://*.google.ad/fbx?fbx=snake_arcade // @match https://*.google.ae/fbx?fbx=snake_arcade // @match https://*.google.com.af/fbx?fbx=snake_arcade // @match https://*.google.com.ag/fbx?fbx=snake_arcade // @match https://*.google.com.ai/fbx?fbx=snake_arcade // @match https://*.google.al/fbx?fbx=snake_arcade // @match https://*.google.am/fbx?fbx=snake_arcade // @match https://*.google.co.ao/fbx?fbx=snake_arcade // @match https://*.google.com.ar/fbx?fbx=snake_arcade // @match https://*.google.as/fbx?fbx=snake_arcade // @match https://*.google.at/fbx?fbx=snake_arcade // @match https://*.google.com.au/fbx?fbx=snake_arcade // @match https://*.google.az/fbx?fbx=snake_arcade // @match https://*.google.ba/fbx?fbx=snake_arcade // @match https://*.google.com.bd/fbx?fbx=snake_arcade // @match https://*.google.be/fbx?fbx=snake_arcade // @match https://*.google.bf/fbx?fbx=snake_arcade // @match https://*.google.bg/fbx?fbx=snake_arcade // @match https://*.google.com.bh/fbx?fbx=snake_arcade // @match https://*.google.bi/fbx?fbx=snake_arcade // @match https://*.google.bj/fbx?fbx=snake_arcade // @match https://*.google.com.bn/fbx?fbx=snake_arcade // @match https://*.google.com.bo/fbx?fbx=snake_arcade // @match https://*.google.com.br/fbx?fbx=snake_arcade // @match https://*.google.bs/fbx?fbx=snake_arcade // @match https://*.google.bt/fbx?fbx=snake_arcade // @match https://*.google.co.bw/fbx?fbx=snake_arcade // @match https://*.google.by/fbx?fbx=snake_arcade // @match https://*.google.com.bz/fbx?fbx=snake_arcade // @match https://*.google.ca/fbx?fbx=snake_arcade // @match https://*.google.cd/fbx?fbx=snake_arcade // @match https://*.google.cf/fbx?fbx=snake_arcade // @match https://*.google.cg/fbx?fbx=snake_arcade // @match https://*.google.ch/fbx?fbx=snake_arcade // @match https://*.google.ci/fbx?fbx=snake_arcade // @match https://*.google.co.ck/fbx?fbx=snake_arcade // @match https://*.google.cl/fbx?fbx=snake_arcade // @match https://*.google.cm/fbx?fbx=snake_arcade // @match https://*.google.cn/fbx?fbx=snake_arcade // @match https://*.google.com.co/fbx?fbx=snake_arcade // @match https://*.google.co.cr/fbx?fbx=snake_arcade // @match https://*.google.com.cu/fbx?fbx=snake_arcade // @match https://*.google.cv/fbx?fbx=snake_arcade // @match https://*.google.com.cy/fbx?fbx=snake_arcade // @match https://*.google.cz/fbx?fbx=snake_arcade // @match https://*.google.de/fbx?fbx=snake_arcade // @match https://*.google.dj/fbx?fbx=snake_arcade // @match https://*.google.dk/fbx?fbx=snake_arcade // @match https://*.google.dm/fbx?fbx=snake_arcade // @match https://*.google.com.do/fbx?fbx=snake_arcade // @match https://*.google.dz/fbx?fbx=snake_arcade // @match https://*.google.com.ec/fbx?fbx=snake_arcade // @match https://*.google.ee/fbx?fbx=snake_arcade // @match https://*.google.com.eg/fbx?fbx=snake_arcade // @match https://*.google.es/fbx?fbx=snake_arcade // @match https://*.google.com.et/fbx?fbx=snake_arcade // @match https://*.google.fi/fbx?fbx=snake_arcade // @match https://*.google.com.fj/fbx?fbx=snake_arcade // @match https://*.google.fm/fbx?fbx=snake_arcade // @match https://*.google.fr/fbx?fbx=snake_arcade // @match https://*.google.ga/fbx?fbx=snake_arcade // @match https://*.google.ge/fbx?fbx=snake_arcade // @match https://*.google.gg/fbx?fbx=snake_arcade // @match https://*.google.com.gh/fbx?fbx=snake_arcade // @match https://*.google.com.gi/fbx?fbx=snake_arcade // @match https://*.google.gl/fbx?fbx=snake_arcade // @match https://*.google.gm/fbx?fbx=snake_arcade // @match https://*.google.gr/fbx?fbx=snake_arcade // @match https://*.google.com.gt/fbx?fbx=snake_arcade // @match https://*.google.gy/fbx?fbx=snake_arcade // @match https://*.google.com.hk/fbx?fbx=snake_arcade // @match https://*.google.hn/fbx?fbx=snake_arcade // @match https://*.google.hr/fbx?fbx=snake_arcade // @match https://*.google.ht/fbx?fbx=snake_arcade // @match https://*.google.hu/fbx?fbx=snake_arcade // @match https://*.google.co.id/fbx?fbx=snake_arcade // @match https://*.google.ie/fbx?fbx=snake_arcade // @match https://*.google.co.il/fbx?fbx=snake_arcade // @match https://*.google.im/fbx?fbx=snake_arcade // @match https://*.google.co.in/fbx?fbx=snake_arcade // @match https://*.google.iq/fbx?fbx=snake_arcade // @match https://*.google.is/fbx?fbx=snake_arcade // @match https://*.google.it/fbx?fbx=snake_arcade // @match https://*.google.je/fbx?fbx=snake_arcade // @match https://*.google.com.jm/fbx?fbx=snake_arcade // @match https://*.google.jo/fbx?fbx=snake_arcade // @match https://*.google.co.jp/fbx?fbx=snake_arcade // @match https://*.google.co.ke/fbx?fbx=snake_arcade // @match https://*.google.com.kh/fbx?fbx=snake_arcade // @match https://*.google.ki/fbx?fbx=snake_arcade // @match https://*.google.kg/fbx?fbx=snake_arcade // @match https://*.google.co.kr/fbx?fbx=snake_arcade // @match https://*.google.com.kw/fbx?fbx=snake_arcade // @match https://*.google.kz/fbx?fbx=snake_arcade // @match https://*.google.la/fbx?fbx=snake_arcade // @match https://*.google.com.lb/fbx?fbx=snake_arcade // @match https://*.google.li/fbx?fbx=snake_arcade // @match https://*.google.lk/fbx?fbx=snake_arcade // @match https://*.google.co.ls/fbx?fbx=snake_arcade // @match https://*.google.lt/fbx?fbx=snake_arcade // @match https://*.google.lu/fbx?fbx=snake_arcade // @match https://*.google.lv/fbx?fbx=snake_arcade // @match https://*.google.com.ly/fbx?fbx=snake_arcade // @match https://*.google.co.ma/fbx?fbx=snake_arcade // @match https://*.google.md/fbx?fbx=snake_arcade // @match https://*.google.me/fbx?fbx=snake_arcade // @match https://*.google.mg/fbx?fbx=snake_arcade // @match https://*.google.mk/fbx?fbx=snake_arcade // @match https://*.google.ml/fbx?fbx=snake_arcade // @match https://*.google.com.mm/fbx?fbx=snake_arcade // @match https://*.google.mn/fbx?fbx=snake_arcade // @match https://*.google.ms/fbx?fbx=snake_arcade // @match https://*.google.com.mt/fbx?fbx=snake_arcade // @match https://*.google.mu/fbx?fbx=snake_arcade // @match https://*.google.mv/fbx?fbx=snake_arcade // @match https://*.google.mw/fbx?fbx=snake_arcade // @match https://*.google.com.mx/fbx?fbx=snake_arcade // @match https://*.google.com.my/fbx?fbx=snake_arcade // @match https://*.google.co.mz/fbx?fbx=snake_arcade // @match https://*.google.com.na/fbx?fbx=snake_arcade // @match https://*.google.com.ng/fbx?fbx=snake_arcade // @match https://*.google.com.ni/fbx?fbx=snake_arcade // @match https://*.google.ne/fbx?fbx=snake_arcade // @match https://*.google.nl/fbx?fbx=snake_arcade // @match https://*.google.no/fbx?fbx=snake_arcade // @match https://*.google.com.np/fbx?fbx=snake_arcade // @match https://*.google.nr/fbx?fbx=snake_arcade // @match https://*.google.nu/fbx?fbx=snake_arcade // @match https://*.google.co.nz/fbx?fbx=snake_arcade // @match https://*.google.com.om/fbx?fbx=snake_arcade // @match https://*.google.com.pa/fbx?fbx=snake_arcade // @match https://*.google.com.pe/fbx?fbx=snake_arcade // @match https://*.google.com.pg/fbx?fbx=snake_arcade // @match https://*.google.com.ph/fbx?fbx=snake_arcade // @match https://*.google.com.pk/fbx?fbx=snake_arcade // @match https://*.google.pl/fbx?fbx=snake_arcade // @match https://*.google.pn/fbx?fbx=snake_arcade // @match https://*.google.com.pr/fbx?fbx=snake_arcade // @match https://*.google.ps/fbx?fbx=snake_arcade // @match https://*.google.pt/fbx?fbx=snake_arcade // @match https://*.google.com.py/fbx?fbx=snake_arcade // @match https://*.google.com.qa/fbx?fbx=snake_arcade // @match https://*.google.ro/fbx?fbx=snake_arcade // @match https://*.google.ru/fbx?fbx=snake_arcade // @match https://*.google.rw/fbx?fbx=snake_arcade // @match https://*.google.com.sa/fbx?fbx=snake_arcade // @match https://*.google.com.sb/fbx?fbx=snake_arcade // @match https://*.google.sc/fbx?fbx=snake_arcade // @match https://*.google.se/fbx?fbx=snake_arcade // @match https://*.google.com.sg/fbx?fbx=snake_arcade // @match https://*.google.sh/fbx?fbx=snake_arcade // @match https://*.google.si/fbx?fbx=snake_arcade // @match https://*.google.sk/fbx?fbx=snake_arcade // @match https://*.google.com.sl/fbx?fbx=snake_arcade // @match https://*.google.sn/fbx?fbx=snake_arcade // @match https://*.google.so/fbx?fbx=snake_arcade // @match https://*.google.sm/fbx?fbx=snake_arcade // @match https://*.google.sr/fbx?fbx=snake_arcade // @match https://*.google.st/fbx?fbx=snake_arcade // @match https://*.google.com.sv/fbx?fbx=snake_arcade // @match https://*.google.td/fbx?fbx=snake_arcade // @match https://*.google.tg/fbx?fbx=snake_arcade // @match https://*.google.co.th/fbx?fbx=snake_arcade // @match https://*.google.com.tj/fbx?fbx=snake_arcade // @match https://*.google.tl/fbx?fbx=snake_arcade // @match https://*.google.tm/fbx?fbx=snake_arcade // @match https://*.google.tn/fbx?fbx=snake_arcade // @match https://*.google.to/fbx?fbx=snake_arcade // @match https://*.google.com.tr/fbx?fbx=snake_arcade // @match https://*.google.tt/fbx?fbx=snake_arcade // @match https://*.google.com.tw/fbx?fbx=snake_arcade // @match https://*.google.co.tz/fbx?fbx=snake_arcade // @match https://*.google.com.ua/fbx?fbx=snake_arcade // @match https://*.google.co.ug/fbx?fbx=snake_arcade // @match https://*.google.co.uk/fbx?fbx=snake_arcade // @match https://*.google.com.uy/fbx?fbx=snake_arcade // @match https://*.google.co.uz/fbx?fbx=snake_arcade // @match https://*.google.com.vc/fbx?fbx=snake_arcade // @match https://*.google.co.ve/fbx?fbx=snake_arcade // @match https://*.google.vg/fbx?fbx=snake_arcade // @match https://*.google.co.vi/fbx?fbx=snake_arcade // @match https://*.google.com.vn/fbx?fbx=snake_arcade // @match https://*.google.vu/fbx?fbx=snake_arcade // @match https://*.google.ws/fbx?fbx=snake_arcade // @match https://*.google.rs/fbx?fbx=snake_arcade // @match https://*.google.co.za/fbx?fbx=snake_arcade // @match https://*.google.co.zm/fbx?fbx=snake_arcade // @match https://*.google.co.zw/fbx?fbx=snake_arcade // @match https://*.google.cat/fbx?fbx=snake_arcade // @match https://*.google.com/search*snake* // @match https://*.google.ad/search*snake* // @match https://*.google.ae/search*snake* // @match https://*.google.com.af/search*snake* // @match https://*.google.com.ag/search*snake* // @match https://*.google.com.ai/search*snake* // @match https://*.google.al/search*snake* // @match https://*.google.am/search*snake* // @match https://*.google.co.ao/search*snake* // @match https://*.google.com.ar/search*snake* // @match https://*.google.as/search*snake* // @match https://*.google.at/search*snake* // @match https://*.google.com.au/search*snake* // @match https://*.google.az/search*snake* // @match https://*.google.ba/search*snake* // @match https://*.google.com.bd/search*snake* // @match https://*.google.be/search*snake* // @match https://*.google.bf/search*snake* // @match https://*.google.bg/search*snake* // @match https://*.google.com.bh/search*snake* // @match https://*.google.bi/search*snake* // @match https://*.google.bj/search*snake* // @match https://*.google.com.bn/search*snake* // @match https://*.google.com.bo/search*snake* // @match https://*.google.com.br/search*snake* // @match https://*.google.bs/search*snake* // @match https://*.google.bt/search*snake* // @match https://*.google.co.bw/search*snake* // @match https://*.google.by/search*snake* // @match https://*.google.com.bz/search*snake* // @match https://*.google.ca/search*snake* // @match https://*.google.cd/search*snake* // @match https://*.google.cf/search*snake* // @match https://*.google.cg/search*snake* // @match https://*.google.ch/search*snake* // @match https://*.google.ci/search*snake* // @match https://*.google.co.ck/search*snake* // @match https://*.google.cl/search*snake* // @match https://*.google.cm/search*snake* // @match https://*.google.cn/search*snake* // @match https://*.google.com.co/search*snake* // @match https://*.google.co.cr/search*snake* // @match https://*.google.com.cu/search*snake* // @match https://*.google.cv/search*snake* // @match https://*.google.com.cy/search*snake* // @match https://*.google.cz/search*snake* // @match https://*.google.de/search*snake* // @match https://*.google.dj/search*snake* // @match https://*.google.dk/search*snake* // @match https://*.google.dm/search*snake* // @match https://*.google.com.do/search*snake* // @match https://*.google.dz/search*snake* // @match https://*.google.com.ec/search*snake* // @match https://*.google.ee/search*snake* // @match https://*.google.com.eg/search*snake* // @match https://*.google.es/search*snake* // @match https://*.google.com.et/search*snake* // @match https://*.google.fi/search*snake* // @match https://*.google.com.fj/search*snake* // @match https://*.google.fm/search*snake* // @match https://*.google.fr/search*snake* // @match https://*.google.ga/search*snake* // @match https://*.google.ge/search*snake* // @match https://*.google.gg/search*snake* // @match https://*.google.com.gh/search*snake* // @match https://*.google.com.gi/search*snake* // @match https://*.google.gl/search*snake* // @match https://*.google.gm/search*snake* // @match https://*.google.gr/search*snake* // @match https://*.google.com.gt/search*snake* // @match https://*.google.gy/search*snake* // @match https://*.google.com.hk/search*snake* // @match https://*.google.hn/search*snake* // @match https://*.google.hr/search*snake* // @match https://*.google.ht/search*snake* // @match https://*.google.hu/search*snake* // @match https://*.google.co.id/search*snake* // @match https://*.google.ie/search*snake* // @match https://*.google.co.il/search*snake* // @match https://*.google.im/search*snake* // @match https://*.google.co.in/search*snake* // @match https://*.google.iq/search*snake* // @match https://*.google.is/search*snake* // @match https://*.google.it/search*snake* // @match https://*.google.je/search*snake* // @match https://*.google.com.jm/search*snake* // @match https://*.google.jo/search*snake* // @match https://*.google.co.jp/search*snake* // @match https://*.google.co.ke/search*snake* // @match https://*.google.com.kh/search*snake* // @match https://*.google.ki/search*snake* // @match https://*.google.kg/search*snake* // @match https://*.google.co.kr/search*snake* // @match https://*.google.com.kw/search*snake* // @match https://*.google.kz/search*snake* // @match https://*.google.la/search*snake* // @match https://*.google.com.lb/search*snake* // @match https://*.google.li/search*snake* // @match https://*.google.lk/search*snake* // @match https://*.google.co.ls/search*snake* // @match https://*.google.lt/search*snake* // @match https://*.google.lu/search*snake* // @match https://*.google.lv/search*snake* // @match https://*.google.com.ly/search*snake* // @match https://*.google.co.ma/search*snake* // @match https://*.google.md/search*snake* // @match https://*.google.me/search*snake* // @match https://*.google.mg/search*snake* // @match https://*.google.mk/search*snake* // @match https://*.google.ml/search*snake* // @match https://*.google.com.mm/search*snake* // @match https://*.google.mn/search*snake* // @match https://*.google.ms/search*snake* // @match https://*.google.com.mt/search*snake* // @match https://*.google.mu/search*snake* // @match https://*.google.mv/search*snake* // @match https://*.google.mw/search*snake* // @match https://*.google.com.mx/search*snake* // @match https://*.google.com.my/search*snake* // @match https://*.google.co.mz/search*snake* // @match https://*.google.com.na/search*snake* // @match https://*.google.com.ng/search*snake* // @match https://*.google.com.ni/search*snake* // @match https://*.google.ne/search*snake* // @match https://*.google.nl/search*snake* // @match https://*.google.no/search*snake* // @match https://*.google.com.np/search*snake* // @match https://*.google.nr/search*snake* // @match https://*.google.nu/search*snake* // @match https://*.google.co.nz/search*snake* // @match https://*.google.com.om/search*snake* // @match https://*.google.com.pa/search*snake* // @match https://*.google.com.pe/search*snake* // @match https://*.google.com.pg/search*snake* // @match https://*.google.com.ph/search*snake* // @match https://*.google.com.pk/search*snake* // @match https://*.google.pl/search*snake* // @match https://*.google.pn/search*snake* // @match https://*.google.com.pr/search*snake* // @match https://*.google.ps/search*snake* // @match https://*.google.pt/search*snake* // @match https://*.google.com.py/search*snake* // @match https://*.google.com.qa/search*snake* // @match https://*.google.ro/search*snake* // @match https://*.google.ru/search*snake* // @match https://*.google.rw/search*snake* // @match https://*.google.com.sa/search*snake* // @match https://*.google.com.sb/search*snake* // @match https://*.google.sc/search*snake* // @match https://*.google.se/search*snake* // @match https://*.google.com.sg/search*snake* // @match https://*.google.sh/search*snake* // @match https://*.google.si/search*snake* // @match https://*.google.sk/search*snake* // @match https://*.google.com.sl/search*snake* // @match https://*.google.sn/search*snake* // @match https://*.google.so/search*snake* // @match https://*.google.sm/search*snake* // @match https://*.google.sr/search*snake* // @match https://*.google.st/search*snake* // @match https://*.google.com.sv/search*snake* // @match https://*.google.td/search*snake* // @match https://*.google.tg/search*snake* // @match https://*.google.co.th/search*snake* // @match https://*.google.com.tj/search*snake* // @match https://*.google.tl/search*snake* // @match https://*.google.tm/search*snake* // @match https://*.google.tn/search*snake* // @match https://*.google.to/search*snake* // @match https://*.google.com.tr/search*snake* // @match https://*.google.tt/search*snake* // @match https://*.google.com.tw/search*snake* // @match https://*.google.co.tz/search*snake* // @match https://*.google.com.ua/search*snake* // @match https://*.google.co.ug/search*snake* // @match https://*.google.co.uk/search*snake* // @match https://*.google.com.uy/search*snake* // @match https://*.google.co.uz/search*snake* // @match https://*.google.com.vc/search*snake* // @match https://*.google.co.ve/search*snake* // @match https://*.google.vg/search*snake* // @match https://*.google.co.vi/search*snake* // @match https://*.google.com.vn/search*snake* // @match https://*.google.vu/search*snake* // @match https://*.google.ws/search*snake* // @match https://*.google.rs/search*snake* // @match https://*.google.co.za/search*snake* // @match https://*.google.co.zm/search*snake* // @match https://*.google.co.zw/search*snake* // @match https://*.google.cat/search*snake* // @downloadURL https://update.greasyfork.icu/scripts/462873/Google%20Snake%20Mod%20Loader%20%28Standard%29.user.js // @updateURL https://update.greasyfork.icu/scripts/462873/Google%20Snake%20Mod%20Loader%20%28Standard%29.meta.js // ==/UserScript== const IS_DEVELOPER_MODE = false; let modsConfig = { moreMenu: { displayName: 'More Menu Mod', hasUrl: true, url: 'https://raw.githubusercontent.com/DarkSnakeGang/GoogleSnakeCustomMenuStuff/main/modloadercode.js' }, levelEditorMod: { displayName: 'Level Editor Mod', hasUrl: true, url: 'https://raw.githubusercontent.com/DarkSnakeGang/GoogleSnakeLevelEditor/main/modloadercode.js' }, mouseMode: { displayName: 'Mouse Mode', hasUrl: true, url: 'https://raw.githubusercontent.com/DarkSnakeGang/GoogleSnakeMouseMode/main/modloadercode.js' }, } if(IS_DEVELOPER_MODE) { modsConfig.testMod = { displayName: 'Test Mod', hasUrl: false }; modsConfig.customUrl = { displayName: 'Load from url (see advanced options)', customModName: JSON.parse(localStorage.getItem('snakeAdvancedSettings'))?.customModName ?? 'PLEASE_CHOOSE_CUSTOM_NAME_FROM_ADVANCED_SETTINGS', url: JSON.parse(localStorage.getItem('snakeAdvancedSettings'))?.customUrl ?? 'PLEASE_CHOOSE_URL_FROM_ADVANCED_SETTINGS', hasUrl: true } } //Replace appendChild so we can intercept it document.body.appendChildOld = document.body.appendChild; document.body.appendChild = function(el) { if(el.tagName !== 'SCRIPT') return document.body.appendChildOld(el); if(el.src === '' || el.src.includes('apis.google.com')) return document.body.appendChildOld(el); const currentlySelectedMod = localStorage.getItem('snakeChosenMod'); //Just do default behaviour if it isn't the snake script or we don't have a mod to run const regexForScriptSrc = window.location.href.includes('fbx?fbx=snake_arcade') ? /xjs=s1$/ : /xjs=s2$/; if(!(regexForScriptSrc.test(el.src)) || currentlySelectedMod === null || currentlySelectedMod === 'none') return document.body.appendChildOld(el); //default behaviour if we can't find any snake images on the webpage if(document.body.querySelector('img[src^="//www.google.com/logos/fnbx/snake_arcade"]') === null) return document.body.appendChildOld(el); //Log which script(s) go through the ajax process console.log(el); //Make sure to return the correct thing that appendChild would normally return let returnVal = el instanceof DocumentFragment ? new DocumentFragment : el; /* Run some code depending on what google snake mod was chosen Request the text contents of the google snake code. Alter the contents of the google snake code (depending on the selected mod) and then eval Run some code afterwards depending on what google snake mod was chosen */ const req = new XMLHttpRequest(); req.open('GET', el.src, false); req.onload = function() { //Do default behaviour if the source code doesn't look like the google snake code. //Set returnVal so it returns the correct thing for document.body.appendChild. if(this.responseText.indexOf('trophy') === -1 || this.responseText.indexOf('apple') === -1 || this.responseText.indexOf('snake_arcade') === -1) { returnVal = document.body.appendChildOld(el); return; } console.log(`Selected mod: ${currentlySelectedMod}`); //Make sure currentlySelectedMod is an allowed value if(!(modsConfig.hasOwnProperty(currentlySelectedMod) || currentlySelectedMod === 'none' || currentlySelectedMod === null)) { const errMessage = `Bad value of snakeChosenMod: ${currentlySelectedMod}. The current allowed values are ${Object.keys(modsConfig)}. Changing this to the "None" setting for next time.`; localStorage.setItem('snakeChosenMod', 'none'); throw new Error(errMessage); } if(modsConfig[currentlySelectedMod].hasUrl) { const modUrl = modsConfig[currentlySelectedMod].url; //Load and run the code for this mod. console.log(`Retrieving code for this mod from ${modUrl}`); loadAndRunCodeSynchronous(modUrl); } //Name of the object that contains runCodeBefore/alterSnakeCode/runCodeAfter methods let modObjectName = currentlySelectedMod; if(IS_DEVELOPER_MODE && modsConfig[currentlySelectedMod].customModName) { modObjectName = modsConfig[currentlySelectedMod].customModName; } //Skip below if either the code didn't load, or it has the wrong variable if(!window[modObjectName]) { (0,eval)(this.responseText); throw new Error(`We were expecting to find a global variable called window.${modObjectName} but this is missing. Running snake without the mod.`); } //Mod specific code to run before running google snake script if(window[modObjectName].runCodeBefore) window[modObjectName].runCodeBefore(); let newSnakeCode; //Alter google snake code and then run it if(window[modObjectName].alterSnakeCode) { try { newSnakeCode = window[modObjectName].alterSnakeCode(this.responseText); } catch (err) { let snakeErrEl = document.getElementById('snake-error-message') if(snakeErrEl) {snakeErrEl.style.display = 'block';} window.showSnakeErrMessage = true; console.log('Displaying message to user and then running the google snake script and then throwing the original error that occurred in "alterSnakeCode"'); (0,eval)(this.responseText); throw err; } } (0,eval)(newSnakeCode); //Mod specific code to run after running google snake script if(window[modObjectName].runCodeAfter) window[modObjectName].runCodeAfter(); }; req.send(); return returnVal; } //Setup Modal box that lets the user choose which mod to run let addModSelectorPopup = function() { if(document.body.querySelector('img[src^="//www.google.com/logos/fnbx/snake_arcade"]') === null) { //We aren't on a page with google snake. Don't show the mod selector dialogue. Exit early. return; } const modCornerIndicatorHTML = ` Current mod:
Change mod
Error changing snake code.
Why does this happen?
`; let modIndicatorEl = document.createElement('div'); modIndicatorEl.id = 'mod-indicator'; modIndicatorEl.style = 'z-index: 9999999;background-color: #fffce0;position: fixed;bottom: 0;right: 0;border-top: 1px solid #cccccc;border-left: 1px solid #cccccc;font-size: 1.2em;border-top-left-radius: 5px;padding: 5px;-webkit-box-shadow: 0px 0px 7px 1px hwb(0deg 0% 100% / 12%);box-shadow: 0px 0px 7px 1px rgb(0 0 0 / 12%);font-family: helvetica, sans-serif;height: initial;user-select:none;'; modIndicatorEl.innerHTML = modCornerIndicatorHTML; document.body.appendChild(modIndicatorEl); document.getElementById('change-mod-button').addEventListener('click', ()=>{ document.getElementById('mod-selector-dialogue-container').style.display = document.getElementById('mod-selector-dialogue-container').style.display === 'none' ? 'block' : 'none'; }); let modSelectorRadioOptions = ''; for(const [key, value] of Object.entries(modsConfig)) { modSelectorRadioOptions += `
`; } modSelectorRadioOptions += ``; let customUrlOptions = ''; if(IS_DEVELOPER_MODE) { customUrlOptions = `

`; } const modSelectorModal = `

Snake Mod Loader

${modSelectorRadioOptions}
Advanced options
Apply
Close
`; //Insert html for custom preset export dialogue box. let modSelectorModalContainer = document.createElement('div'); modSelectorModalContainer.innerHTML = modSelectorModal; modSelectorModalContainer.id = 'mod-selector-dialogue-container'; modSelectorModalContainer.style = 'display:none; position:fixed; width:100%; height:100%; z-index: 999999; left:0; top:0'; document.body.appendChild(modSelectorModalContainer); //Tick the currently selected mod choice according to localStorage. Also, set the mod name in the indicator const currentlySelectedMod = localStorage.getItem('snakeChosenMod'); let newlySelectedMod = currentlySelectedMod; if(modsConfig.hasOwnProperty(currentlySelectedMod) && currentlySelectedMod !== null && currentlySelectedMod !== 'none') { document.querySelector(`input[name="mod-selector"][value="${currentlySelectedMod}"]`).checked = true; document.getElementById('mod-name-span').textContent = modsConfig[currentlySelectedMod].displayName; } else { document.querySelector('input[name="mod-selector"][value="none"]').checked = true; document.getElementById('mod-name-span').textContent = 'None'; } //save the mod selected when clicking on any of the radio buttons [...document.querySelectorAll('input[type="radio"][name="mod-selector"]')].forEach(radioEl=>{ radioEl.addEventListener('click', function(){ newlySelectedMod = this.value; }); }); //Load advanced settings let advancedSettings = JSON.parse(localStorage.getItem('snakeAdvancedSettings')) ?? {}; let advancedSettingsOriginal = {...advancedSettings};//Shallow copy, but it's ok as nothing is nested. //Make sure the inputs are set the right values when starting up the mod updateAdvancedSettingInputs(); //Event listeners for advanced settings document.getElementById('advanced-options-toggle').addEventListener('click', (event)=>{ document.getElementById('advanced-options').style.display = (document.getElementById('advanced-options').style.display === 'none' ? 'block':'none'); event.preventDefault(); }); document.getElementById('use-custom-theme').addEventListener('change', function() { document.getElementById('custom-theme-pickers').style.display = (this.checked ? 'block' : 'none'); updateAdvancedSetting('useCustomTheme', this.checked); }); document.getElementById('fbx-centered-checkbox').addEventListener('change', function() { updateAdvancedSetting('fbxCentered', this.checked); }); document.getElementById('timer-starts-on').addEventListener('change', function() { updateAdvancedSetting('timerStartsOn', this.checked); }); document.getElementById('background-color-picker').addEventListener('input', function() { updateAdvancedSetting('backgroundColor', this.value); }); document.getElementById('custom-theme-col1').addEventListener('input', function() { updateAdvancedSetting('themeCol1', this.value); }); document.getElementById('custom-theme-col2').addEventListener('input', function() { updateAdvancedSetting('themeCol2', this.value); }); document.getElementById('custom-theme-col3').addEventListener('input', function() { updateAdvancedSetting('themeCol3', this.value); }); document.getElementById('custom-theme-col4').addEventListener('input', function() { updateAdvancedSetting('themeCol4', this.value); }); document.getElementById('custom-theme-col5').addEventListener('input', function() { updateAdvancedSetting('themeCol5', this.value); }); document.getElementById('custom-theme-col6').addEventListener('input', function() { updateAdvancedSetting('themeCol6', this.value); }); document.getElementById('custom-theme-col7').addEventListener('input', function() { updateAdvancedSetting('themeCol7', this.value); }); if(IS_DEVELOPER_MODE) { document.getElementById('custom-mod-name').addEventListener('input', function() { updateAdvancedSetting('customModName', this.value); }); document.getElementById('custom-url').addEventListener('input', function() { updateAdvancedSetting('customUrl', this.value); }); } //Hide mod selector dialogue when clicking close button document.getElementById('close-mod-selector').addEventListener('click', function() { document.getElementById('mod-selector-dialogue-container').style.display = 'none'; }); //Apply button should save settings and refresh page document.getElementById('apply-mod').addEventListener('click', function(event) { //Figure out if advanced settings have been changed. let shallowEquality = true; for(let setting in advancedSettings) { if(advancedSettings[setting] !== advancedSettingsOriginal?.[setting]) { shallowEquality = false; break; } } //Skip if settings/mod chosen are the same as before. if(shallowEquality && newlySelectedMod === currentlySelectedMod) { alert('Settings are the same as before!') return; } //Save new settings and refresh to run with the new settings localStorage.setItem('snakeChosenMod', newlySelectedMod); localStorage.setItem('snakeAdvancedSettings',JSON.stringify(advancedSettings)); location.reload(); }); //Set up CSS const css = ` .mod-sel-btn:hover { background-color: #fefcfb !important; } .mod-sel-btn:active { background-color: #f0e9e5 !important; } `; document.getElementsByTagName('style')[0].innerHTML = document.getElementsByTagName('style')[0].innerHTML + css; let attemptsApplyingAdvancedSettings = 0; setTimeout(applyAdvancedSettingsToGame, 300);//Small delay to give the game more time to load. function updateAdvancedSettingInputs() { if(advancedSettings.hasOwnProperty('fbxCentered')) { document.getElementById('fbx-centered-checkbox').checked = advancedSettings.fbxCentered; } if(advancedSettings.hasOwnProperty('timerStartsOn')) { document.getElementById('timer-starts-on').checked = advancedSettings.timerStartsOn; } if(advancedSettings.hasOwnProperty('useCustomTheme')) { document.getElementById('use-custom-theme').checked = advancedSettings.useCustomTheme; document.getElementById('custom-theme-pickers').style.display = (advancedSettings.useCustomTheme ? 'block' : 'none'); } else { document.getElementById('custom-theme-pickers').style.display = 'none'; } if(advancedSettings.hasOwnProperty('backgroundColor')) { document.getElementById('background-color-picker').value = advancedSettings.backgroundColor; } if(advancedSettings.hasOwnProperty('themeCol1')) { document.getElementById('custom-theme-col1').value = advancedSettings.themeCol1; } if(advancedSettings.hasOwnProperty('themeCol2')) { document.getElementById('custom-theme-col2').value = advancedSettings.themeCol2; } if(advancedSettings.hasOwnProperty('themeCol3')) { document.getElementById('custom-theme-col3').value = advancedSettings.themeCol3; } if(advancedSettings.hasOwnProperty('themeCol4')) { document.getElementById('custom-theme-col4').value = advancedSettings.themeCol4; } if(advancedSettings.hasOwnProperty('themeCol5')) { document.getElementById('custom-theme-col5').value = advancedSettings.themeCol5; } if(advancedSettings.hasOwnProperty('themeCol6')) { document.getElementById('custom-theme-col6').value = advancedSettings.themeCol6; } if(advancedSettings.hasOwnProperty('themeCol7')) { document.getElementById('custom-theme-col7').value = advancedSettings.themeCol7; } if(IS_DEVELOPER_MODE) { if(advancedSettings.hasOwnProperty('customModName')) { document.getElementById('custom-mod-name').value = advancedSettings.customModName; } if(advancedSettings.hasOwnProperty('customUrl')) { document.getElementById('custom-url').value = advancedSettings.customUrl; } } } function updateAdvancedSetting(settingName, settingValue) { advancedSettings[settingName] = settingValue; } function applyAdvancedSettingsToGame() { if(attemptsApplyingAdvancedSettings > 10) { //Stop trying to apply advanced setting if we've tried this much and the game still isn't ready console.log('window.snake is still not available after retrying many times. Skipping applying advanced settings'); } else if(!window.snake) { //Game not ready. Wait a bit and then try again. console.log('window.snake not ready for when we apply advanced settings. Will retry again after waiting.'); attemptsApplyingAdvancedSettings++; setTimeout(applyAdvancedSettingsToGame, 300); } else { if(advancedSettings.fbxCentered && window.location.href.includes('fbx?fbx=snake_arcade')) { //Copied from GoogleSnakeCenteredFBX mod document.getElementsByTagName('div')[0].style = 'position:relative;top:50%;transform:translate(0%,-50%);margin:auto;'; } if(advancedSettings.timerStartsOn) { window.snake.speedrun(); } if(advancedSettings.useCustomTheme) { window.snake.setCustomTheme( advancedSettings.themeCol1 ?? '#aad751', advancedSettings.themeCol2 ?? '#a2d149', advancedSettings.themeCol3 ?? '#94bd46', advancedSettings.themeCol4 ?? '#578a34', advancedSettings.themeCol5 ?? '#38630d', advancedSettings.themeCol6 ?? '#4a752c', advancedSettings.themeCol7 ?? '#4dc1f9' ); } if(window.location.href.includes('fbx?fbx=snake_arcade')) { document.body.style.backgroundColor = advancedSettings.backgroundColor; } } } } window.testMod = {}; window.testMod.runCodeBefore = function() { } window.testMod.alterSnakeCode = function(code) { code = code.replaceAll('.66','.36') return code; } window.testMod.runCodeAfter = function() { } function loadAndRunCodeSynchronous(url) { let req = new XMLHttpRequest(); req.open('GET', url, false); req.onload = function() { if(this.status === 200) { (1,eval)(this.responseText); } else { console.log(`Loading selected mod returned non-200 status. Received: ${this.status}`); } }; req.error = function(event) { console.error(`Error when attempting to retrieve mod code from ${url}`); console.log(event); }; req.send(); } window.addEventListener('load', addModSelectorPopup); //////////////////////////////////////////////////////////////////////////////////////////////////////////// //Utility functions below //////////////////////////////////////////////////////////////////////////////////////////////////////////// window.swapInMainClassPrototype = function(mainClass, functionText) { if(/^[$a-zA-Z0-9_]{0,6}=function/.test(functionText)) { throw new Error("Error, function is of form abc=function(), but this only works for stuff like s_.abc=function()"); } functionText = assertReplace(functionText, /^[$a-zA-Z0-9_]{0,6}/,`${mainClass}.prototype`); return functionText; } /* This function will search for a function/method in some code and return this function as a string code will usually be the snake source code functionSignature will be regex matching the beginning of the function/method (must end in $), for example if we are trying to find a function like s_xD = function(a, b, c, d, e) {......} then put functionSignature = /[$a-zA-Z0-9_]{0,6}=function\(a,b,c,d,e\)$/ somethingInsideFunction will be regex matching something in the function for example if we are trying to find a function like s_xD = function(a, b, c, d, e) {...a.Xa&&10!==a.Qb...} then put somethingInsideFunction = /a\.[$a-zA-Z0-9_]{0,6}&&10!==a\.[$a-zA-Z0-9_]{0,6}/ */ window.findFunctionInCode = function(code, functionSignature, somethingInsideFunction, logging = false) { let functionSignatureSource = functionSignature.source; let functionSignatureFlags = functionSignature.flags;//Probably empty string /*Check functionSignature ends in $*/ if (functionSignatureSource[functionSignatureSource.length - 1] !== "$") { throw new Error("functionSignature regex should end in $"); } /*Allow line breaks after commas or =. This is bit sketchy, but should be ok as findFunctionInCode is used in a quite limited way*/ functionSignatureSource.replaceAll(/,|=/g,'$&\\n?'); functionSignature = new RegExp(functionSignatureSource, functionSignatureFlags); /*get the position of somethingInsideFunction*/ let indexWithinFunction = code.search(somethingInsideFunction); if (indexWithinFunction == -1) { console.log("%cCouldn't find a match for somethingInsideFunction", "color:red;"); diagnoseRegexError(code, somethingInsideFunction); } /*expand outwards from somethingInsideFunction until we get to the function signature, then count brackets to find the end of the function*/ let startIndex = 0; for (let i = indexWithinFunction; i >= 0; i--) { let startOfCode = code.substring(0, i); startIndex = startOfCode.search(functionSignature); if (startIndex !== -1) { break; } if (i == 0) { throw new Error("Couldn't find function signature"); } } let bracketCount = 0; let foundFirstBracket = false; let endIndex = 0; /*Use bracket counting to find the whole function*/ let codeLength = code.length; for (let i = startIndex; i <= codeLength; i++) { if (!foundFirstBracket && code[i] == "{") { foundFirstBracket = true; } if (code[i] == "{") { bracketCount++; } if (code[i] == "}") { bracketCount--; } if (foundFirstBracket && bracketCount == 0) { endIndex = i; break; } if (i == codeLength) { throw new Error("Couldn't pair up brackets"); } } let fullFunction = code.substring(startIndex, endIndex + 1); /*throw error if fullFunction doesn't contain something inside function - i.e. function signature was wrong*/ if (fullFunction.search(somethingInsideFunction) === -1) { throw new Error("Function signature does not belong to the same function as somethingInsideFunction"); } if (logging) { console.log(fullFunction); } return fullFunction; } /* Same as replace, but throws an error if nothing is changed */ window.assertReplace = function(baseText, regex, replacement) { if (typeof baseText !== 'string') { throw new Error('String argument expected for assertReplace'); } let outputText = baseText.replace(regex, replacement); //Throw warning if nothing is replaced if (baseText === outputText) { diagnoseRegexError(baseText, regex); } return outputText; } /* Same as replaceAll, but throws an error if nothing is changed */ window.assertReplaceAll = function(baseText, regex, replacement) { if (typeof baseText !== 'string') { throw new Error('String argument expected for assertReplace'); } let outputText = baseText.replaceAll(regex, replacement); //Throw warning if nothing is replaced if (baseText === outputText) { diagnoseRegexError(baseText, regex); } return outputText; } //Alternate way to use assertReplace. Example: code = code.assertReplace('Thing to change', 'New thing'); String.prototype.assertReplace = function(regex, replacement) { return assertReplace(this.toString(), regex, replacement); }; //Same as above for assertReplaceAll. String.prototype.assertReplaceAll = function(regex, replacement) { return assertReplaceAll(this.toString(), regex, replacement); }; window.diagnoseRegexError = function(baseText, regex) { if(!(regex instanceof RegExp)) { throw new Error('Failed to find match using string argument. No more details available'); } //see if removing line breaks works - in that case we can give a more useful error message let oneLineText = baseText.replaceAll(/\n/g,''); let res = regex.test(oneLineText); //If line breaks don't solve the issue then throw a general error if (!res) { throw new Error('Failed to find match for regex.'); } //Try to suggest correct regex to use for searching let regexSource = regex.source; let regexFlags = regex.flags; //Look at all the spots where line breaks might occur and try adding \n? there to see if it makes a difference //It might be easier to just crudely brute force putting \n? at each possible index? for(let breakableChar of ["%","&","\\*","\\+",",","-","\\/",":",";","<","=",">","\\?","{","\\|","}"]) { for(let pos = regexSource.indexOf(breakableChar); pos !== -1; pos = regexSource.indexOf(breakableChar, pos + 1)) { //Remake the regex with a new line at the candidate position let candidateRegexSource = `${regexSource.slice(0,pos + breakableChar.length)}\\n?${regexSource.slice(pos + breakableChar.length)}`; let candidateRegex; try{ candidateRegex = new RegExp(candidateRegexSource, regexFlags); } catch(err) { continue; } //See if the new regex works let testReplaceResult = candidateRegex.test(baseText); if(testReplaceResult) { //Success we found the working regex! Give descriptive error message to user and log suggested regex with new line in correct place console.log(`Suggested regex improvement: ${candidateRegex}`); throw new Error('Suggested improvement found! Error with line break, failed to find match for regex. See logged output for regex to use instead that should hopefully fix this.'); } } } throw new Error('Line break error! Failed to failed to find match for regex - most likely caused by a new line break. No suggestions provided'); } window.appendCodeWithinSnakeModule = function(snakeCode, codeToAdd, addSemicolonAfter) { if(addSemicolonAfter) { codeToAdd += ';'; } var newSnakeCode = snakeCode.replace(/}\)\(this\._s\);\n\/\/ Google Inc\./, codeToAdd + '$&'); return newSnakeCode; } //Turns _.abc into _s.abc window.swapInSnakeGlobal = function(text) { return assertReplace(text, /^_\./, '_s.'); }