Greasy Fork is available in English.

Google Snake Mod Loader (Standard)

Allows you to run multiple different google snake mods

  1. // ==UserScript==
  2. // @name Google Snake Mod Loader (Standard)
  3. // @namespace https://github.com/DarkSnakeGang
  4. // @version 1.0.0
  5. // @description Allows you to run multiple different google snake mods
  6. // @author DarkSnakeGang (https://github.com/DarkSnakeGang)
  7. // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
  8. // @run-at document-start
  9. // @grant none
  10. // @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).*$/
  11. // @match https://*.google.com/fbx?fbx=snake_arcade
  12. // @match https://*.google.ad/fbx?fbx=snake_arcade
  13. // @match https://*.google.ae/fbx?fbx=snake_arcade
  14. // @match https://*.google.com.af/fbx?fbx=snake_arcade
  15. // @match https://*.google.com.ag/fbx?fbx=snake_arcade
  16. // @match https://*.google.com.ai/fbx?fbx=snake_arcade
  17. // @match https://*.google.al/fbx?fbx=snake_arcade
  18. // @match https://*.google.am/fbx?fbx=snake_arcade
  19. // @match https://*.google.co.ao/fbx?fbx=snake_arcade
  20. // @match https://*.google.com.ar/fbx?fbx=snake_arcade
  21. // @match https://*.google.as/fbx?fbx=snake_arcade
  22. // @match https://*.google.at/fbx?fbx=snake_arcade
  23. // @match https://*.google.com.au/fbx?fbx=snake_arcade
  24. // @match https://*.google.az/fbx?fbx=snake_arcade
  25. // @match https://*.google.ba/fbx?fbx=snake_arcade
  26. // @match https://*.google.com.bd/fbx?fbx=snake_arcade
  27. // @match https://*.google.be/fbx?fbx=snake_arcade
  28. // @match https://*.google.bf/fbx?fbx=snake_arcade
  29. // @match https://*.google.bg/fbx?fbx=snake_arcade
  30. // @match https://*.google.com.bh/fbx?fbx=snake_arcade
  31. // @match https://*.google.bi/fbx?fbx=snake_arcade
  32. // @match https://*.google.bj/fbx?fbx=snake_arcade
  33. // @match https://*.google.com.bn/fbx?fbx=snake_arcade
  34. // @match https://*.google.com.bo/fbx?fbx=snake_arcade
  35. // @match https://*.google.com.br/fbx?fbx=snake_arcade
  36. // @match https://*.google.bs/fbx?fbx=snake_arcade
  37. // @match https://*.google.bt/fbx?fbx=snake_arcade
  38. // @match https://*.google.co.bw/fbx?fbx=snake_arcade
  39. // @match https://*.google.by/fbx?fbx=snake_arcade
  40. // @match https://*.google.com.bz/fbx?fbx=snake_arcade
  41. // @match https://*.google.ca/fbx?fbx=snake_arcade
  42. // @match https://*.google.cd/fbx?fbx=snake_arcade
  43. // @match https://*.google.cf/fbx?fbx=snake_arcade
  44. // @match https://*.google.cg/fbx?fbx=snake_arcade
  45. // @match https://*.google.ch/fbx?fbx=snake_arcade
  46. // @match https://*.google.ci/fbx?fbx=snake_arcade
  47. // @match https://*.google.co.ck/fbx?fbx=snake_arcade
  48. // @match https://*.google.cl/fbx?fbx=snake_arcade
  49. // @match https://*.google.cm/fbx?fbx=snake_arcade
  50. // @match https://*.google.cn/fbx?fbx=snake_arcade
  51. // @match https://*.google.com.co/fbx?fbx=snake_arcade
  52. // @match https://*.google.co.cr/fbx?fbx=snake_arcade
  53. // @match https://*.google.com.cu/fbx?fbx=snake_arcade
  54. // @match https://*.google.cv/fbx?fbx=snake_arcade
  55. // @match https://*.google.com.cy/fbx?fbx=snake_arcade
  56. // @match https://*.google.cz/fbx?fbx=snake_arcade
  57. // @match https://*.google.de/fbx?fbx=snake_arcade
  58. // @match https://*.google.dj/fbx?fbx=snake_arcade
  59. // @match https://*.google.dk/fbx?fbx=snake_arcade
  60. // @match https://*.google.dm/fbx?fbx=snake_arcade
  61. // @match https://*.google.com.do/fbx?fbx=snake_arcade
  62. // @match https://*.google.dz/fbx?fbx=snake_arcade
  63. // @match https://*.google.com.ec/fbx?fbx=snake_arcade
  64. // @match https://*.google.ee/fbx?fbx=snake_arcade
  65. // @match https://*.google.com.eg/fbx?fbx=snake_arcade
  66. // @match https://*.google.es/fbx?fbx=snake_arcade
  67. // @match https://*.google.com.et/fbx?fbx=snake_arcade
  68. // @match https://*.google.fi/fbx?fbx=snake_arcade
  69. // @match https://*.google.com.fj/fbx?fbx=snake_arcade
  70. // @match https://*.google.fm/fbx?fbx=snake_arcade
  71. // @match https://*.google.fr/fbx?fbx=snake_arcade
  72. // @match https://*.google.ga/fbx?fbx=snake_arcade
  73. // @match https://*.google.ge/fbx?fbx=snake_arcade
  74. // @match https://*.google.gg/fbx?fbx=snake_arcade
  75. // @match https://*.google.com.gh/fbx?fbx=snake_arcade
  76. // @match https://*.google.com.gi/fbx?fbx=snake_arcade
  77. // @match https://*.google.gl/fbx?fbx=snake_arcade
  78. // @match https://*.google.gm/fbx?fbx=snake_arcade
  79. // @match https://*.google.gr/fbx?fbx=snake_arcade
  80. // @match https://*.google.com.gt/fbx?fbx=snake_arcade
  81. // @match https://*.google.gy/fbx?fbx=snake_arcade
  82. // @match https://*.google.com.hk/fbx?fbx=snake_arcade
  83. // @match https://*.google.hn/fbx?fbx=snake_arcade
  84. // @match https://*.google.hr/fbx?fbx=snake_arcade
  85. // @match https://*.google.ht/fbx?fbx=snake_arcade
  86. // @match https://*.google.hu/fbx?fbx=snake_arcade
  87. // @match https://*.google.co.id/fbx?fbx=snake_arcade
  88. // @match https://*.google.ie/fbx?fbx=snake_arcade
  89. // @match https://*.google.co.il/fbx?fbx=snake_arcade
  90. // @match https://*.google.im/fbx?fbx=snake_arcade
  91. // @match https://*.google.co.in/fbx?fbx=snake_arcade
  92. // @match https://*.google.iq/fbx?fbx=snake_arcade
  93. // @match https://*.google.is/fbx?fbx=snake_arcade
  94. // @match https://*.google.it/fbx?fbx=snake_arcade
  95. // @match https://*.google.je/fbx?fbx=snake_arcade
  96. // @match https://*.google.com.jm/fbx?fbx=snake_arcade
  97. // @match https://*.google.jo/fbx?fbx=snake_arcade
  98. // @match https://*.google.co.jp/fbx?fbx=snake_arcade
  99. // @match https://*.google.co.ke/fbx?fbx=snake_arcade
  100. // @match https://*.google.com.kh/fbx?fbx=snake_arcade
  101. // @match https://*.google.ki/fbx?fbx=snake_arcade
  102. // @match https://*.google.kg/fbx?fbx=snake_arcade
  103. // @match https://*.google.co.kr/fbx?fbx=snake_arcade
  104. // @match https://*.google.com.kw/fbx?fbx=snake_arcade
  105. // @match https://*.google.kz/fbx?fbx=snake_arcade
  106. // @match https://*.google.la/fbx?fbx=snake_arcade
  107. // @match https://*.google.com.lb/fbx?fbx=snake_arcade
  108. // @match https://*.google.li/fbx?fbx=snake_arcade
  109. // @match https://*.google.lk/fbx?fbx=snake_arcade
  110. // @match https://*.google.co.ls/fbx?fbx=snake_arcade
  111. // @match https://*.google.lt/fbx?fbx=snake_arcade
  112. // @match https://*.google.lu/fbx?fbx=snake_arcade
  113. // @match https://*.google.lv/fbx?fbx=snake_arcade
  114. // @match https://*.google.com.ly/fbx?fbx=snake_arcade
  115. // @match https://*.google.co.ma/fbx?fbx=snake_arcade
  116. // @match https://*.google.md/fbx?fbx=snake_arcade
  117. // @match https://*.google.me/fbx?fbx=snake_arcade
  118. // @match https://*.google.mg/fbx?fbx=snake_arcade
  119. // @match https://*.google.mk/fbx?fbx=snake_arcade
  120. // @match https://*.google.ml/fbx?fbx=snake_arcade
  121. // @match https://*.google.com.mm/fbx?fbx=snake_arcade
  122. // @match https://*.google.mn/fbx?fbx=snake_arcade
  123. // @match https://*.google.ms/fbx?fbx=snake_arcade
  124. // @match https://*.google.com.mt/fbx?fbx=snake_arcade
  125. // @match https://*.google.mu/fbx?fbx=snake_arcade
  126. // @match https://*.google.mv/fbx?fbx=snake_arcade
  127. // @match https://*.google.mw/fbx?fbx=snake_arcade
  128. // @match https://*.google.com.mx/fbx?fbx=snake_arcade
  129. // @match https://*.google.com.my/fbx?fbx=snake_arcade
  130. // @match https://*.google.co.mz/fbx?fbx=snake_arcade
  131. // @match https://*.google.com.na/fbx?fbx=snake_arcade
  132. // @match https://*.google.com.ng/fbx?fbx=snake_arcade
  133. // @match https://*.google.com.ni/fbx?fbx=snake_arcade
  134. // @match https://*.google.ne/fbx?fbx=snake_arcade
  135. // @match https://*.google.nl/fbx?fbx=snake_arcade
  136. // @match https://*.google.no/fbx?fbx=snake_arcade
  137. // @match https://*.google.com.np/fbx?fbx=snake_arcade
  138. // @match https://*.google.nr/fbx?fbx=snake_arcade
  139. // @match https://*.google.nu/fbx?fbx=snake_arcade
  140. // @match https://*.google.co.nz/fbx?fbx=snake_arcade
  141. // @match https://*.google.com.om/fbx?fbx=snake_arcade
  142. // @match https://*.google.com.pa/fbx?fbx=snake_arcade
  143. // @match https://*.google.com.pe/fbx?fbx=snake_arcade
  144. // @match https://*.google.com.pg/fbx?fbx=snake_arcade
  145. // @match https://*.google.com.ph/fbx?fbx=snake_arcade
  146. // @match https://*.google.com.pk/fbx?fbx=snake_arcade
  147. // @match https://*.google.pl/fbx?fbx=snake_arcade
  148. // @match https://*.google.pn/fbx?fbx=snake_arcade
  149. // @match https://*.google.com.pr/fbx?fbx=snake_arcade
  150. // @match https://*.google.ps/fbx?fbx=snake_arcade
  151. // @match https://*.google.pt/fbx?fbx=snake_arcade
  152. // @match https://*.google.com.py/fbx?fbx=snake_arcade
  153. // @match https://*.google.com.qa/fbx?fbx=snake_arcade
  154. // @match https://*.google.ro/fbx?fbx=snake_arcade
  155. // @match https://*.google.ru/fbx?fbx=snake_arcade
  156. // @match https://*.google.rw/fbx?fbx=snake_arcade
  157. // @match https://*.google.com.sa/fbx?fbx=snake_arcade
  158. // @match https://*.google.com.sb/fbx?fbx=snake_arcade
  159. // @match https://*.google.sc/fbx?fbx=snake_arcade
  160. // @match https://*.google.se/fbx?fbx=snake_arcade
  161. // @match https://*.google.com.sg/fbx?fbx=snake_arcade
  162. // @match https://*.google.sh/fbx?fbx=snake_arcade
  163. // @match https://*.google.si/fbx?fbx=snake_arcade
  164. // @match https://*.google.sk/fbx?fbx=snake_arcade
  165. // @match https://*.google.com.sl/fbx?fbx=snake_arcade
  166. // @match https://*.google.sn/fbx?fbx=snake_arcade
  167. // @match https://*.google.so/fbx?fbx=snake_arcade
  168. // @match https://*.google.sm/fbx?fbx=snake_arcade
  169. // @match https://*.google.sr/fbx?fbx=snake_arcade
  170. // @match https://*.google.st/fbx?fbx=snake_arcade
  171. // @match https://*.google.com.sv/fbx?fbx=snake_arcade
  172. // @match https://*.google.td/fbx?fbx=snake_arcade
  173. // @match https://*.google.tg/fbx?fbx=snake_arcade
  174. // @match https://*.google.co.th/fbx?fbx=snake_arcade
  175. // @match https://*.google.com.tj/fbx?fbx=snake_arcade
  176. // @match https://*.google.tl/fbx?fbx=snake_arcade
  177. // @match https://*.google.tm/fbx?fbx=snake_arcade
  178. // @match https://*.google.tn/fbx?fbx=snake_arcade
  179. // @match https://*.google.to/fbx?fbx=snake_arcade
  180. // @match https://*.google.com.tr/fbx?fbx=snake_arcade
  181. // @match https://*.google.tt/fbx?fbx=snake_arcade
  182. // @match https://*.google.com.tw/fbx?fbx=snake_arcade
  183. // @match https://*.google.co.tz/fbx?fbx=snake_arcade
  184. // @match https://*.google.com.ua/fbx?fbx=snake_arcade
  185. // @match https://*.google.co.ug/fbx?fbx=snake_arcade
  186. // @match https://*.google.co.uk/fbx?fbx=snake_arcade
  187. // @match https://*.google.com.uy/fbx?fbx=snake_arcade
  188. // @match https://*.google.co.uz/fbx?fbx=snake_arcade
  189. // @match https://*.google.com.vc/fbx?fbx=snake_arcade
  190. // @match https://*.google.co.ve/fbx?fbx=snake_arcade
  191. // @match https://*.google.vg/fbx?fbx=snake_arcade
  192. // @match https://*.google.co.vi/fbx?fbx=snake_arcade
  193. // @match https://*.google.com.vn/fbx?fbx=snake_arcade
  194. // @match https://*.google.vu/fbx?fbx=snake_arcade
  195. // @match https://*.google.ws/fbx?fbx=snake_arcade
  196. // @match https://*.google.rs/fbx?fbx=snake_arcade
  197. // @match https://*.google.co.za/fbx?fbx=snake_arcade
  198. // @match https://*.google.co.zm/fbx?fbx=snake_arcade
  199. // @match https://*.google.co.zw/fbx?fbx=snake_arcade
  200. // @match https://*.google.cat/fbx?fbx=snake_arcade
  201. // @match https://*.google.com/search*snake*
  202. // @match https://*.google.ad/search*snake*
  203. // @match https://*.google.ae/search*snake*
  204. // @match https://*.google.com.af/search*snake*
  205. // @match https://*.google.com.ag/search*snake*
  206. // @match https://*.google.com.ai/search*snake*
  207. // @match https://*.google.al/search*snake*
  208. // @match https://*.google.am/search*snake*
  209. // @match https://*.google.co.ao/search*snake*
  210. // @match https://*.google.com.ar/search*snake*
  211. // @match https://*.google.as/search*snake*
  212. // @match https://*.google.at/search*snake*
  213. // @match https://*.google.com.au/search*snake*
  214. // @match https://*.google.az/search*snake*
  215. // @match https://*.google.ba/search*snake*
  216. // @match https://*.google.com.bd/search*snake*
  217. // @match https://*.google.be/search*snake*
  218. // @match https://*.google.bf/search*snake*
  219. // @match https://*.google.bg/search*snake*
  220. // @match https://*.google.com.bh/search*snake*
  221. // @match https://*.google.bi/search*snake*
  222. // @match https://*.google.bj/search*snake*
  223. // @match https://*.google.com.bn/search*snake*
  224. // @match https://*.google.com.bo/search*snake*
  225. // @match https://*.google.com.br/search*snake*
  226. // @match https://*.google.bs/search*snake*
  227. // @match https://*.google.bt/search*snake*
  228. // @match https://*.google.co.bw/search*snake*
  229. // @match https://*.google.by/search*snake*
  230. // @match https://*.google.com.bz/search*snake*
  231. // @match https://*.google.ca/search*snake*
  232. // @match https://*.google.cd/search*snake*
  233. // @match https://*.google.cf/search*snake*
  234. // @match https://*.google.cg/search*snake*
  235. // @match https://*.google.ch/search*snake*
  236. // @match https://*.google.ci/search*snake*
  237. // @match https://*.google.co.ck/search*snake*
  238. // @match https://*.google.cl/search*snake*
  239. // @match https://*.google.cm/search*snake*
  240. // @match https://*.google.cn/search*snake*
  241. // @match https://*.google.com.co/search*snake*
  242. // @match https://*.google.co.cr/search*snake*
  243. // @match https://*.google.com.cu/search*snake*
  244. // @match https://*.google.cv/search*snake*
  245. // @match https://*.google.com.cy/search*snake*
  246. // @match https://*.google.cz/search*snake*
  247. // @match https://*.google.de/search*snake*
  248. // @match https://*.google.dj/search*snake*
  249. // @match https://*.google.dk/search*snake*
  250. // @match https://*.google.dm/search*snake*
  251. // @match https://*.google.com.do/search*snake*
  252. // @match https://*.google.dz/search*snake*
  253. // @match https://*.google.com.ec/search*snake*
  254. // @match https://*.google.ee/search*snake*
  255. // @match https://*.google.com.eg/search*snake*
  256. // @match https://*.google.es/search*snake*
  257. // @match https://*.google.com.et/search*snake*
  258. // @match https://*.google.fi/search*snake*
  259. // @match https://*.google.com.fj/search*snake*
  260. // @match https://*.google.fm/search*snake*
  261. // @match https://*.google.fr/search*snake*
  262. // @match https://*.google.ga/search*snake*
  263. // @match https://*.google.ge/search*snake*
  264. // @match https://*.google.gg/search*snake*
  265. // @match https://*.google.com.gh/search*snake*
  266. // @match https://*.google.com.gi/search*snake*
  267. // @match https://*.google.gl/search*snake*
  268. // @match https://*.google.gm/search*snake*
  269. // @match https://*.google.gr/search*snake*
  270. // @match https://*.google.com.gt/search*snake*
  271. // @match https://*.google.gy/search*snake*
  272. // @match https://*.google.com.hk/search*snake*
  273. // @match https://*.google.hn/search*snake*
  274. // @match https://*.google.hr/search*snake*
  275. // @match https://*.google.ht/search*snake*
  276. // @match https://*.google.hu/search*snake*
  277. // @match https://*.google.co.id/search*snake*
  278. // @match https://*.google.ie/search*snake*
  279. // @match https://*.google.co.il/search*snake*
  280. // @match https://*.google.im/search*snake*
  281. // @match https://*.google.co.in/search*snake*
  282. // @match https://*.google.iq/search*snake*
  283. // @match https://*.google.is/search*snake*
  284. // @match https://*.google.it/search*snake*
  285. // @match https://*.google.je/search*snake*
  286. // @match https://*.google.com.jm/search*snake*
  287. // @match https://*.google.jo/search*snake*
  288. // @match https://*.google.co.jp/search*snake*
  289. // @match https://*.google.co.ke/search*snake*
  290. // @match https://*.google.com.kh/search*snake*
  291. // @match https://*.google.ki/search*snake*
  292. // @match https://*.google.kg/search*snake*
  293. // @match https://*.google.co.kr/search*snake*
  294. // @match https://*.google.com.kw/search*snake*
  295. // @match https://*.google.kz/search*snake*
  296. // @match https://*.google.la/search*snake*
  297. // @match https://*.google.com.lb/search*snake*
  298. // @match https://*.google.li/search*snake*
  299. // @match https://*.google.lk/search*snake*
  300. // @match https://*.google.co.ls/search*snake*
  301. // @match https://*.google.lt/search*snake*
  302. // @match https://*.google.lu/search*snake*
  303. // @match https://*.google.lv/search*snake*
  304. // @match https://*.google.com.ly/search*snake*
  305. // @match https://*.google.co.ma/search*snake*
  306. // @match https://*.google.md/search*snake*
  307. // @match https://*.google.me/search*snake*
  308. // @match https://*.google.mg/search*snake*
  309. // @match https://*.google.mk/search*snake*
  310. // @match https://*.google.ml/search*snake*
  311. // @match https://*.google.com.mm/search*snake*
  312. // @match https://*.google.mn/search*snake*
  313. // @match https://*.google.ms/search*snake*
  314. // @match https://*.google.com.mt/search*snake*
  315. // @match https://*.google.mu/search*snake*
  316. // @match https://*.google.mv/search*snake*
  317. // @match https://*.google.mw/search*snake*
  318. // @match https://*.google.com.mx/search*snake*
  319. // @match https://*.google.com.my/search*snake*
  320. // @match https://*.google.co.mz/search*snake*
  321. // @match https://*.google.com.na/search*snake*
  322. // @match https://*.google.com.ng/search*snake*
  323. // @match https://*.google.com.ni/search*snake*
  324. // @match https://*.google.ne/search*snake*
  325. // @match https://*.google.nl/search*snake*
  326. // @match https://*.google.no/search*snake*
  327. // @match https://*.google.com.np/search*snake*
  328. // @match https://*.google.nr/search*snake*
  329. // @match https://*.google.nu/search*snake*
  330. // @match https://*.google.co.nz/search*snake*
  331. // @match https://*.google.com.om/search*snake*
  332. // @match https://*.google.com.pa/search*snake*
  333. // @match https://*.google.com.pe/search*snake*
  334. // @match https://*.google.com.pg/search*snake*
  335. // @match https://*.google.com.ph/search*snake*
  336. // @match https://*.google.com.pk/search*snake*
  337. // @match https://*.google.pl/search*snake*
  338. // @match https://*.google.pn/search*snake*
  339. // @match https://*.google.com.pr/search*snake*
  340. // @match https://*.google.ps/search*snake*
  341. // @match https://*.google.pt/search*snake*
  342. // @match https://*.google.com.py/search*snake*
  343. // @match https://*.google.com.qa/search*snake*
  344. // @match https://*.google.ro/search*snake*
  345. // @match https://*.google.ru/search*snake*
  346. // @match https://*.google.rw/search*snake*
  347. // @match https://*.google.com.sa/search*snake*
  348. // @match https://*.google.com.sb/search*snake*
  349. // @match https://*.google.sc/search*snake*
  350. // @match https://*.google.se/search*snake*
  351. // @match https://*.google.com.sg/search*snake*
  352. // @match https://*.google.sh/search*snake*
  353. // @match https://*.google.si/search*snake*
  354. // @match https://*.google.sk/search*snake*
  355. // @match https://*.google.com.sl/search*snake*
  356. // @match https://*.google.sn/search*snake*
  357. // @match https://*.google.so/search*snake*
  358. // @match https://*.google.sm/search*snake*
  359. // @match https://*.google.sr/search*snake*
  360. // @match https://*.google.st/search*snake*
  361. // @match https://*.google.com.sv/search*snake*
  362. // @match https://*.google.td/search*snake*
  363. // @match https://*.google.tg/search*snake*
  364. // @match https://*.google.co.th/search*snake*
  365. // @match https://*.google.com.tj/search*snake*
  366. // @match https://*.google.tl/search*snake*
  367. // @match https://*.google.tm/search*snake*
  368. // @match https://*.google.tn/search*snake*
  369. // @match https://*.google.to/search*snake*
  370. // @match https://*.google.com.tr/search*snake*
  371. // @match https://*.google.tt/search*snake*
  372. // @match https://*.google.com.tw/search*snake*
  373. // @match https://*.google.co.tz/search*snake*
  374. // @match https://*.google.com.ua/search*snake*
  375. // @match https://*.google.co.ug/search*snake*
  376. // @match https://*.google.co.uk/search*snake*
  377. // @match https://*.google.com.uy/search*snake*
  378. // @match https://*.google.co.uz/search*snake*
  379. // @match https://*.google.com.vc/search*snake*
  380. // @match https://*.google.co.ve/search*snake*
  381. // @match https://*.google.vg/search*snake*
  382. // @match https://*.google.co.vi/search*snake*
  383. // @match https://*.google.com.vn/search*snake*
  384. // @match https://*.google.vu/search*snake*
  385. // @match https://*.google.ws/search*snake*
  386. // @match https://*.google.rs/search*snake*
  387. // @match https://*.google.co.za/search*snake*
  388. // @match https://*.google.co.zm/search*snake*
  389. // @match https://*.google.co.zw/search*snake*
  390. // @match https://*.google.cat/search*snake*
  391. // ==/UserScript==
  392.  
  393. const IS_DEVELOPER_MODE = false;
  394.  
  395. let modsConfig = {
  396. moreMenu: {
  397. displayName: 'More Menu Mod',
  398. hasUrl: true,
  399. url: 'https://raw.githubusercontent.com/DarkSnakeGang/GoogleSnakeCustomMenuStuff/main/modloadercode.js'
  400. },
  401. levelEditorMod: {
  402. displayName: 'Level Editor Mod',
  403. hasUrl: true,
  404. url: 'https://raw.githubusercontent.com/DarkSnakeGang/GoogleSnakeLevelEditor/main/modloadercode.js'
  405. },
  406. mouseMode: {
  407. displayName: 'Mouse Mode',
  408. hasUrl: true,
  409. url: 'https://raw.githubusercontent.com/DarkSnakeGang/GoogleSnakeMouseMode/main/modloadercode.js'
  410. },
  411. }
  412.  
  413. if(IS_DEVELOPER_MODE) {
  414. modsConfig.testMod = {
  415. displayName: 'Test Mod',
  416. hasUrl: false
  417. };
  418. modsConfig.customUrl = {
  419. displayName: 'Load from url (see advanced options)',
  420. customModName: JSON.parse(localStorage.getItem('snakeAdvancedSettings'))?.customModName ?? 'PLEASE_CHOOSE_CUSTOM_NAME_FROM_ADVANCED_SETTINGS',
  421. url: JSON.parse(localStorage.getItem('snakeAdvancedSettings'))?.customUrl ?? 'PLEASE_CHOOSE_URL_FROM_ADVANCED_SETTINGS',
  422. hasUrl: true
  423. }
  424. }
  425.  
  426. //Replace appendChild so we can intercept it
  427. document.body.appendChildOld = document.body.appendChild;
  428.  
  429. document.body.appendChild = function(el) {
  430. if(el.tagName !== 'SCRIPT') return document.body.appendChildOld(el);
  431. if(el.src === '' || el.src.includes('apis.google.com')) return document.body.appendChildOld(el);
  432.  
  433. const currentlySelectedMod = localStorage.getItem('snakeChosenMod');
  434.  
  435. //Just do default behaviour if it isn't the snake script or we don't have a mod to run
  436. const regexForScriptSrc = window.location.href.includes('fbx?fbx=snake_arcade') ? /xjs=s1$/ : /xjs=s2$/;
  437. if(!(regexForScriptSrc.test(el.src)) || currentlySelectedMod === null || currentlySelectedMod === 'none') return document.body.appendChildOld(el);
  438.  
  439. //default behaviour if we can't find any snake images on the webpage
  440. if(document.body.querySelector('img[src^="//www.google.com/logos/fnbx/snake_arcade"]') === null) return document.body.appendChildOld(el);
  441.  
  442. //Log which script(s) go through the ajax process
  443. console.log(el);
  444.  
  445. //Make sure to return the correct thing that appendChild would normally return
  446. let returnVal = el instanceof DocumentFragment ? new DocumentFragment : el;
  447.  
  448. /*
  449. Run some code depending on what google snake mod was chosen
  450. Request the text contents of the google snake code.
  451. Alter the contents of the google snake code (depending on the selected mod) and then eval
  452. Run some code afterwards depending on what google snake mod was chosen
  453. */
  454. const req = new XMLHttpRequest();
  455. req.open('GET', el.src, false);
  456.  
  457. req.onload = function() {
  458. //Do default behaviour if the source code doesn't look like the google snake code.
  459. //Set returnVal so it returns the correct thing for document.body.appendChild.
  460. if(this.responseText.indexOf('trophy') === -1 || this.responseText.indexOf('apple') === -1 || this.responseText.indexOf('snake_arcade') === -1) {
  461. returnVal = document.body.appendChildOld(el);
  462. return;
  463. }
  464.  
  465. console.log(`Selected mod: ${currentlySelectedMod}`);
  466. //Make sure currentlySelectedMod is an allowed value
  467. if(!(modsConfig.hasOwnProperty(currentlySelectedMod) || currentlySelectedMod === 'none' || currentlySelectedMod === null)) {
  468. const errMessage = `Bad value of snakeChosenMod: ${currentlySelectedMod}. The current allowed values are ${Object.keys(modsConfig)}. Changing this to the "None" setting for next time.`;
  469. localStorage.setItem('snakeChosenMod', 'none');
  470. throw new Error(errMessage);
  471. }
  472.  
  473. if(modsConfig[currentlySelectedMod].hasUrl) {
  474. const modUrl = modsConfig[currentlySelectedMod].url;
  475.  
  476. //Load and run the code for this mod.
  477. console.log(`Retrieving code for this mod from ${modUrl}`);
  478.  
  479. loadAndRunCodeSynchronous(modUrl);
  480. }
  481.  
  482. //Name of the object that contains runCodeBefore/alterSnakeCode/runCodeAfter methods
  483. let modObjectName = currentlySelectedMod;
  484. if(IS_DEVELOPER_MODE && modsConfig[currentlySelectedMod].customModName) {
  485. modObjectName = modsConfig[currentlySelectedMod].customModName;
  486. }
  487.  
  488. //Skip below if either the code didn't load, or it has the wrong variable
  489. if(!window[modObjectName]) {
  490. (0,eval)(this.responseText);
  491. throw new Error(`We were expecting to find a global variable called window.${modObjectName} but this is missing. Running snake without the mod.`);
  492. }
  493.  
  494. //Mod specific code to run before running google snake script
  495. if(window[modObjectName].runCodeBefore) window[modObjectName].runCodeBefore();
  496.  
  497. let newSnakeCode;
  498. //Alter google snake code and then run it
  499. if(window[modObjectName].alterSnakeCode) {
  500. try {
  501. newSnakeCode = window[modObjectName].alterSnakeCode(this.responseText);
  502. } catch (err) {
  503. let snakeErrEl = document.getElementById('snake-error-message')
  504. if(snakeErrEl) {snakeErrEl.style.display = 'block';}
  505. window.showSnakeErrMessage = true;
  506. console.log('Displaying message to user and then running the google snake script and then throwing the original error that occurred in "alterSnakeCode"');
  507. (0,eval)(this.responseText);
  508. throw err;
  509. }
  510. }
  511.  
  512. (0,eval)(newSnakeCode);
  513.  
  514. //Mod specific code to run after running google snake script
  515. if(window[modObjectName].runCodeAfter) window[modObjectName].runCodeAfter();
  516. };
  517.  
  518. req.send();
  519.  
  520. return returnVal;
  521. }
  522.  
  523. //Setup Modal box that lets the user choose which mod to run
  524. let addModSelectorPopup = function() {
  525. if(document.body.querySelector('img[src^="//www.google.com/logos/fnbx/snake_arcade"]') === null) {
  526. //We aren't on a page with google snake. Don't show the mod selector dialogue. Exit early.
  527. return;
  528. }
  529.  
  530. const modCornerIndicatorHTML = `
  531. Current mod: <span id="mod-name-span" style="background-color: #eeeaca;padding: 2px;border-radius: 3px;font-family: consolas, monospace;"></span>
  532. <div id="change-mod-button" style="text-align: center;font-size: 0.84em;font-family: arial, sans-serif;color: #069;text-decoration: underline;cursor: pointer;margin-top: 3px;">Change mod</div>
  533. <div id="snake-error-message" style="font-family: helvetica, sans-serif;color: #f44336;margin-top: 2px;display: ${window.showSnakeErrMessage ? 'block' : 'none'};">
  534. Error changing snake code.
  535. <br>
  536. <a href="https://github.com/DarkSnakeGang/GoogleSnakeModLoader/blob/main/docs/mod_errors.md" target="_blank">Why does this happen?</a>
  537. </div>
  538. `;
  539.  
  540. let modIndicatorEl = document.createElement('div');
  541. modIndicatorEl.id = 'mod-indicator';
  542. 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;';
  543. modIndicatorEl.innerHTML = modCornerIndicatorHTML;
  544. document.body.appendChild(modIndicatorEl);
  545.  
  546. document.getElementById('change-mod-button').addEventListener('click', ()=>{
  547. document.getElementById('mod-selector-dialogue-container').style.display = document.getElementById('mod-selector-dialogue-container').style.display === 'none' ? 'block' : 'none';
  548. });
  549.  
  550. let modSelectorRadioOptions = '';
  551. for(const [key, value] of Object.entries(modsConfig)) {
  552. modSelectorRadioOptions += `<label><input type="radio" name="mod-selector" value="${key}">${value.displayName}</label><br>`;
  553. }
  554. modSelectorRadioOptions += `<label><input type="radio" name="mod-selector" value="none">None</label>`;
  555.  
  556. let customUrlOptions = '';
  557. if(IS_DEVELOPER_MODE) {
  558. customUrlOptions = `<label><input id="custom-mod-name" type="text"> Custom Mod Name</label><br>
  559. <label><input id="custom-url" type="text"> Custom Mod Url</label><br>`;
  560. }
  561.  
  562. const modSelectorModal = `
  563. <div id="mod-selector-dialogue" style="display: block;margin:40px auto;padding:10px;border: 1px solid rgb(204 204 204);width:316.4px;background-color: #fffce0;border-radius:5px;-webkit-box-shadow: 0px 0px 10px 1px rgba(0,0,0,0.24);box-shadow: 0px 0px 10px 1px rgb(0 0 0 / 20%);font-family: helvetica, sans-serif;">
  564. <!-- <h1 style="font-size: 2em;font-weight: bold;font-family: &quot;Century Gothic&quot;, sans-serif;margin: 7px 0px 15px 0px;text-align: center;text-shadow: 2px 2px 3px #a2a2a2;">Snake Mod Loader</h1> -->
  565. <!-- <h1 style="font-size: 2em;font-weight: bold;font-family: &quot;Century Gothic&quot;, sans-serif;margin: 7px 0px 15px 0px;text-align: center;text-shadow: 1px 1px 1px #000000, 1px 1px 1px #000000, 1px 1px 5px #000000;color: #4674e9;">Snake Mod Loader</h1> -->
  566. <!-- <div style="background-color: #aad751;height: 17px;position: relative;top: -31px;transform: skewX(-21deg);z-index: 5;"></div> -->
  567. <h1 style="font-size: 2em;font-weight: bold;font-family: &quot;Century Gothic&quot;, sans-serif;margin: 7px 0px 15px 0px;text-align: center;text-shadow: 1px 1px 1px #000000, 1px 1px 1px #000000, 1px 1px 5px #000000;color: #4674e9;border: 5px inset #e4e0be;background-color: #ece9d4;">Snake Mod Loader</h1>
  568. ${modSelectorRadioOptions}
  569. <div id="advanced-options" style="display:none">
  570. <hr>
  571. <label><input id="fbx-centered-checkbox" type="checkbox">Make fbx centered</label><br>
  572. <label><input id="timer-starts-on" type="checkbox">Timer starts on</label><br>
  573. <label><input id="background-color-picker" type="color" value="#FFFFFF"> Background color on fbx</label><br>
  574. <label><input id="use-custom-theme" type="checkbox">Use custom theme</label><br>
  575. <div id="custom-theme-pickers">
  576. <label><input id="custom-theme-col1" type="color" value="#aad751"> Light Tiles</label><br>
  577. <label><input id="custom-theme-col2" type="color" value="#a2d149"> Dark Tiles</label><br>
  578. <label><input id="custom-theme-col3" type="color" value="#94bd46"> Shadow</label><br>
  579. <label><input id="custom-theme-col4" type="color" value="#578a34"> Border</label><br>
  580. <label><input id="custom-theme-col5" type="color" value="#38630d"> Lock Sign</label><br>
  581. <label><input id="custom-theme-col6" type="color" value="#4a752c"> Top Bar</label><br>
  582. <label><input id="custom-theme-col7" type="color" value="#4dc1f9"> Endscreen background</label><br>
  583. </div>
  584. ${customUrlOptions}
  585. </div>
  586. <br>
  587. <div style="display:inline-block;padding-top: 15px;margin-bottom: 4px;text-align: center;font-family: arial, sans-serif;color: #069;text-decoration: underline;cursor: pointer;user-select:none" id="advanced-options-toggle">Advanced options</div>
  588. <div id="apply-mod" class="mod-sel-btn" style="display:inline-block;background-color: hsl(24deg 64% 97%);padding: 4px;margin-top: 7px;border-radius: 3px;border: 2px solid #4caf50;color: #4caf50;font-weight: bold;user-select: none;float:right;cursor: pointer;">Apply</div>
  589. <div id="close-mod-selector" class="mod-sel-btn" style="display:inline-block;background-color: hsl(24deg 64% 97%);padding: 4px;margin-top: 7px;margin-right:10px;border-radius: 3px;border: 2px solid #606060;color: #606060;font-weight: bold;user-select: none;cursor: pointer;float:right">Close</div>
  590. </div>
  591. `;
  592.  
  593. //Insert html for custom preset export dialogue box.
  594. let modSelectorModalContainer = document.createElement('div');
  595. modSelectorModalContainer.innerHTML = modSelectorModal;
  596. modSelectorModalContainer.id = 'mod-selector-dialogue-container';
  597. modSelectorModalContainer.style = 'display:none; position:fixed; width:100%; height:100%; z-index: 999999; left:0; top:0';
  598. document.body.appendChild(modSelectorModalContainer);
  599.  
  600. //Tick the currently selected mod choice according to localStorage. Also, set the mod name in the indicator
  601. const currentlySelectedMod = localStorage.getItem('snakeChosenMod');
  602. let newlySelectedMod = currentlySelectedMod;
  603.  
  604. if(modsConfig.hasOwnProperty(currentlySelectedMod) && currentlySelectedMod !== null && currentlySelectedMod !== 'none') {
  605. document.querySelector(`input[name="mod-selector"][value="${currentlySelectedMod}"]`).checked = true;
  606. document.getElementById('mod-name-span').textContent = modsConfig[currentlySelectedMod].displayName;
  607. } else {
  608. document.querySelector('input[name="mod-selector"][value="none"]').checked = true;
  609. document.getElementById('mod-name-span').textContent = 'None';
  610. }
  611.  
  612. //save the mod selected when clicking on any of the radio buttons
  613. [...document.querySelectorAll('input[type="radio"][name="mod-selector"]')].forEach(radioEl=>{
  614. radioEl.addEventListener('click', function(){
  615. newlySelectedMod = this.value;
  616. });
  617. });
  618.  
  619. //Load advanced settings
  620. let advancedSettings = JSON.parse(localStorage.getItem('snakeAdvancedSettings')) ?? {};
  621. let advancedSettingsOriginal = {...advancedSettings};//Shallow copy, but it's ok as nothing is nested.
  622.  
  623. //Make sure the inputs are set the right values when starting up the mod
  624. updateAdvancedSettingInputs();
  625.  
  626. //Event listeners for advanced settings
  627. document.getElementById('advanced-options-toggle').addEventListener('click', (event)=>{
  628. document.getElementById('advanced-options').style.display = (document.getElementById('advanced-options').style.display === 'none' ? 'block':'none');
  629. event.preventDefault();
  630. });
  631. document.getElementById('use-custom-theme').addEventListener('change', function() {
  632. document.getElementById('custom-theme-pickers').style.display = (this.checked ? 'block' : 'none');
  633. updateAdvancedSetting('useCustomTheme', this.checked);
  634. });
  635. document.getElementById('fbx-centered-checkbox').addEventListener('change', function() {
  636. updateAdvancedSetting('fbxCentered', this.checked);
  637. });
  638. document.getElementById('timer-starts-on').addEventListener('change', function() {
  639. updateAdvancedSetting('timerStartsOn', this.checked);
  640. });
  641. document.getElementById('background-color-picker').addEventListener('input', function() {
  642. updateAdvancedSetting('backgroundColor', this.value);
  643. });
  644. document.getElementById('custom-theme-col1').addEventListener('input', function() {
  645. updateAdvancedSetting('themeCol1', this.value);
  646. });
  647. document.getElementById('custom-theme-col2').addEventListener('input', function() {
  648. updateAdvancedSetting('themeCol2', this.value);
  649. });
  650. document.getElementById('custom-theme-col3').addEventListener('input', function() {
  651. updateAdvancedSetting('themeCol3', this.value);
  652. });
  653. document.getElementById('custom-theme-col4').addEventListener('input', function() {
  654. updateAdvancedSetting('themeCol4', this.value);
  655. });
  656. document.getElementById('custom-theme-col5').addEventListener('input', function() {
  657. updateAdvancedSetting('themeCol5', this.value);
  658. });
  659. document.getElementById('custom-theme-col6').addEventListener('input', function() {
  660. updateAdvancedSetting('themeCol6', this.value);
  661. });
  662. document.getElementById('custom-theme-col7').addEventListener('input', function() {
  663. updateAdvancedSetting('themeCol7', this.value);
  664. });
  665.  
  666. if(IS_DEVELOPER_MODE) {
  667. document.getElementById('custom-mod-name').addEventListener('input', function() {
  668. updateAdvancedSetting('customModName', this.value);
  669. });
  670. document.getElementById('custom-url').addEventListener('input', function() {
  671. updateAdvancedSetting('customUrl', this.value);
  672. });
  673. }
  674.  
  675. //Hide mod selector dialogue when clicking close button
  676. document.getElementById('close-mod-selector').addEventListener('click', function() {
  677. document.getElementById('mod-selector-dialogue-container').style.display = 'none';
  678. });
  679.  
  680. //Apply button should save settings and refresh page
  681. document.getElementById('apply-mod').addEventListener('click', function(event) {
  682. //Figure out if advanced settings have been changed.
  683. let shallowEquality = true;
  684. for(let setting in advancedSettings) {
  685. if(advancedSettings[setting] !== advancedSettingsOriginal?.[setting]) {
  686. shallowEquality = false;
  687. break;
  688. }
  689. }
  690.  
  691. //Skip if settings/mod chosen are the same as before.
  692. if(shallowEquality && newlySelectedMod === currentlySelectedMod) {
  693. alert('Settings are the same as before!')
  694. return;
  695. }
  696.  
  697. //Save new settings and refresh to run with the new settings
  698. localStorage.setItem('snakeChosenMod', newlySelectedMod);
  699. localStorage.setItem('snakeAdvancedSettings',JSON.stringify(advancedSettings));
  700. location.reload();
  701. });
  702.  
  703. //Set up CSS
  704. const css = `
  705. .mod-sel-btn:hover {
  706. background-color: #fefcfb !important;
  707. }
  708.  
  709. .mod-sel-btn:active {
  710. background-color: #f0e9e5 !important;
  711. }
  712. `;
  713. document.getElementsByTagName('style')[0].innerHTML = document.getElementsByTagName('style')[0].innerHTML + css;
  714.  
  715. let attemptsApplyingAdvancedSettings = 0;
  716. setTimeout(applyAdvancedSettingsToGame, 300);//Small delay to give the game more time to load.
  717.  
  718. function updateAdvancedSettingInputs() {
  719. if(advancedSettings.hasOwnProperty('fbxCentered')) {
  720. document.getElementById('fbx-centered-checkbox').checked = advancedSettings.fbxCentered;
  721. }
  722. if(advancedSettings.hasOwnProperty('timerStartsOn')) {
  723. document.getElementById('timer-starts-on').checked = advancedSettings.timerStartsOn;
  724. }
  725. if(advancedSettings.hasOwnProperty('useCustomTheme')) {
  726. document.getElementById('use-custom-theme').checked = advancedSettings.useCustomTheme;
  727. document.getElementById('custom-theme-pickers').style.display = (advancedSettings.useCustomTheme ? 'block' : 'none');
  728. } else {
  729. document.getElementById('custom-theme-pickers').style.display = 'none';
  730. }
  731. if(advancedSettings.hasOwnProperty('backgroundColor')) {
  732. document.getElementById('background-color-picker').value = advancedSettings.backgroundColor;
  733. }
  734. if(advancedSettings.hasOwnProperty('themeCol1')) {
  735. document.getElementById('custom-theme-col1').value = advancedSettings.themeCol1;
  736. }
  737. if(advancedSettings.hasOwnProperty('themeCol2')) {
  738. document.getElementById('custom-theme-col2').value = advancedSettings.themeCol2;
  739. }
  740. if(advancedSettings.hasOwnProperty('themeCol3')) {
  741. document.getElementById('custom-theme-col3').value = advancedSettings.themeCol3;
  742. }
  743. if(advancedSettings.hasOwnProperty('themeCol4')) {
  744. document.getElementById('custom-theme-col4').value = advancedSettings.themeCol4;
  745. }
  746. if(advancedSettings.hasOwnProperty('themeCol5')) {
  747. document.getElementById('custom-theme-col5').value = advancedSettings.themeCol5;
  748. }
  749. if(advancedSettings.hasOwnProperty('themeCol6')) {
  750. document.getElementById('custom-theme-col6').value = advancedSettings.themeCol6;
  751. }
  752. if(advancedSettings.hasOwnProperty('themeCol7')) {
  753. document.getElementById('custom-theme-col7').value = advancedSettings.themeCol7;
  754. }
  755.  
  756. if(IS_DEVELOPER_MODE) {
  757. if(advancedSettings.hasOwnProperty('customModName')) {
  758. document.getElementById('custom-mod-name').value = advancedSettings.customModName;
  759. }
  760. if(advancedSettings.hasOwnProperty('customUrl')) {
  761. document.getElementById('custom-url').value = advancedSettings.customUrl;
  762. }
  763. }
  764. }
  765.  
  766. function updateAdvancedSetting(settingName, settingValue) {
  767. advancedSettings[settingName] = settingValue;
  768. }
  769.  
  770. function applyAdvancedSettingsToGame() {
  771. if(attemptsApplyingAdvancedSettings > 10) {
  772. //Stop trying to apply advanced setting if we've tried this much and the game still isn't ready
  773. console.log('window.snake is still not available after retrying many times. Skipping applying advanced settings');
  774. } else if(!window.snake) {
  775. //Game not ready. Wait a bit and then try again.
  776. console.log('window.snake not ready for when we apply advanced settings. Will retry again after waiting.');
  777. attemptsApplyingAdvancedSettings++;
  778. setTimeout(applyAdvancedSettingsToGame, 300);
  779. } else {
  780. if(advancedSettings.fbxCentered && window.location.href.includes('fbx?fbx=snake_arcade')) {
  781. //Copied from GoogleSnakeCenteredFBX mod
  782. document.getElementsByTagName('div')[0].style = 'position:relative;top:50%;transform:translate(0%,-50%);margin:auto;';
  783. }
  784. if(advancedSettings.timerStartsOn) {
  785. window.snake.speedrun();
  786. }
  787. if(advancedSettings.useCustomTheme) {
  788. window.snake.setCustomTheme(
  789. advancedSettings.themeCol1 ?? '#aad751',
  790. advancedSettings.themeCol2 ?? '#a2d149',
  791. advancedSettings.themeCol3 ?? '#94bd46',
  792. advancedSettings.themeCol4 ?? '#578a34',
  793. advancedSettings.themeCol5 ?? '#38630d',
  794. advancedSettings.themeCol6 ?? '#4a752c',
  795. advancedSettings.themeCol7 ?? '#4dc1f9'
  796. );
  797. }
  798. if(window.location.href.includes('fbx?fbx=snake_arcade')) {
  799. document.body.style.backgroundColor = advancedSettings.backgroundColor;
  800. }
  801. }
  802. }
  803. }
  804.  
  805. window.testMod = {};
  806. window.testMod.runCodeBefore = function() {
  807. }
  808. window.testMod.alterSnakeCode = function(code) {
  809. code = code.replaceAll('.66','.36')
  810.  
  811. return code;
  812. }
  813. window.testMod.runCodeAfter = function() {
  814.  
  815. }
  816.  
  817. function loadAndRunCodeSynchronous(url) {
  818. let req = new XMLHttpRequest();
  819. req.open('GET', url, false);
  820. req.onload = function() {
  821. if(this.status === 200) {
  822. (1,eval)(this.responseText);
  823. } else {
  824. console.log(`Loading selected mod returned non-200 status. Received: ${this.status}`);
  825. }
  826. };
  827. req.error = function(event) {
  828. console.error(`Error when attempting to retrieve mod code from ${url}`);
  829. console.log(event);
  830. };
  831. req.send();
  832. }
  833.  
  834. window.addEventListener('load', addModSelectorPopup);
  835.  
  836. ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  837. //Utility functions below
  838. ////////////////////////////////////////////////////////////////////////////////////////////////////////////
  839.  
  840. window.swapInMainClassPrototype = function(mainClass, functionText) {
  841. if(/^[$a-zA-Z0-9_]{0,6}=function/.test(functionText)) {
  842. throw new Error("Error, function is of form abc=function(), but this only works for stuff like s_.abc=function()");
  843. }
  844. functionText = assertReplace(functionText, /^[$a-zA-Z0-9_]{0,6}/,`${mainClass}.prototype`);
  845. return functionText;
  846. }
  847.  
  848. /*
  849. This function will search for a function/method in some code and return this function as a string
  850.  
  851. code will usually be the snake source code
  852.  
  853. functionSignature will be regex matching the beginning of the function/method (must end in $),
  854. for example if we are trying to find a function like s_xD = function(a, b, c, d, e) {......}
  855. then put functionSignature = /[$a-zA-Z0-9_]{0,6}=function\(a,b,c,d,e\)$/
  856.  
  857. somethingInsideFunction will be regex matching something in the function
  858. for example if we are trying to find a function like s_xD = function(a, b, c, d, e) {...a.Xa&&10!==a.Qb...}
  859. then put somethingInsideFunction = /a\.[$a-zA-Z0-9_]{0,6}&&10!==a\.[$a-zA-Z0-9_]{0,6}/
  860. */
  861. window.findFunctionInCode = function(code, functionSignature, somethingInsideFunction, logging = false) {
  862. let functionSignatureSource = functionSignature.source;
  863. let functionSignatureFlags = functionSignature.flags;//Probably empty string
  864.  
  865. /*Check functionSignature ends in $*/
  866. if (functionSignatureSource[functionSignatureSource.length - 1] !== "$") {
  867. throw new Error("functionSignature regex should end in $");
  868. }
  869.  
  870. /*Allow line breaks after commas or =. This is bit sketchy, but should be ok as findFunctionInCode is used in a quite limited way*/
  871. functionSignatureSource.replaceAll(/,|=/g,'$&\\n?');
  872. functionSignature = new RegExp(functionSignatureSource, functionSignatureFlags);
  873.  
  874. /*get the position of somethingInsideFunction*/
  875. let indexWithinFunction = code.search(somethingInsideFunction);
  876. if (indexWithinFunction == -1) {
  877. console.log("%cCouldn't find a match for somethingInsideFunction", "color:red;");
  878. diagnoseRegexError(code, somethingInsideFunction);
  879. }
  880.  
  881. /*expand outwards from somethingInsideFunction until we get to the function signature, then count brackets
  882. to find the end of the function*/
  883. let startIndex = 0;
  884. for (let i = indexWithinFunction; i >= 0; i--) {
  885. let startOfCode = code.substring(0, i);
  886. startIndex = startOfCode.search(functionSignature);
  887. if (startIndex !== -1) {
  888. break;
  889. }
  890. if (i == 0) {
  891. throw new Error("Couldn't find function signature");
  892. }
  893. }
  894.  
  895. let bracketCount = 0;
  896. let foundFirstBracket = false;
  897. let endIndex = 0;
  898. /*Use bracket counting to find the whole function*/
  899. let codeLength = code.length;
  900. for (let i = startIndex; i <= codeLength; i++) {
  901. if (!foundFirstBracket && code[i] == "{") {
  902. foundFirstBracket = true;
  903. }
  904.  
  905. if (code[i] == "{") {
  906. bracketCount++;
  907. }
  908. if (code[i] == "}") {
  909. bracketCount--;
  910. }
  911. if (foundFirstBracket && bracketCount == 0) {
  912. endIndex = i;
  913. break;
  914. }
  915.  
  916. if (i == codeLength) {
  917. throw new Error("Couldn't pair up brackets");
  918. }
  919. }
  920.  
  921. let fullFunction = code.substring(startIndex, endIndex + 1);
  922.  
  923. /*throw error if fullFunction doesn't contain something inside function - i.e. function signature was wrong*/
  924. if (fullFunction.search(somethingInsideFunction) === -1) {
  925. throw new Error("Function signature does not belong to the same function as somethingInsideFunction");
  926. }
  927.  
  928. if (logging) {
  929. console.log(fullFunction);
  930. }
  931.  
  932. return fullFunction;
  933. }
  934.  
  935. /*
  936. Same as replace, but throws an error if nothing is changed
  937. */
  938. window.assertReplace = function(baseText, regex, replacement) {
  939. if (typeof baseText !== 'string') {
  940. throw new Error('String argument expected for assertReplace');
  941. }
  942. let outputText = baseText.replace(regex, replacement);
  943.  
  944. //Throw warning if nothing is replaced
  945. if (baseText === outputText) {
  946. diagnoseRegexError(baseText, regex);
  947. }
  948.  
  949. return outputText;
  950. }
  951.  
  952. /*
  953. Same as replaceAll, but throws an error if nothing is changed
  954. */
  955. window.assertReplaceAll = function(baseText, regex, replacement) {
  956. if (typeof baseText !== 'string') {
  957. throw new Error('String argument expected for assertReplace');
  958. }
  959. let outputText = baseText.replaceAll(regex, replacement);
  960.  
  961. //Throw warning if nothing is replaced
  962. if (baseText === outputText) {
  963. diagnoseRegexError(baseText, regex);
  964. }
  965.  
  966. return outputText;
  967. }
  968.  
  969. //Alternate way to use assertReplace. Example: code = code.assertReplace('Thing to change', 'New thing');
  970. String.prototype.assertReplace = function(regex, replacement) {
  971. return assertReplace(this.toString(), regex, replacement);
  972. };
  973.  
  974. //Same as above for assertReplaceAll.
  975. String.prototype.assertReplaceAll = function(regex, replacement) {
  976. return assertReplaceAll(this.toString(), regex, replacement);
  977. };
  978.  
  979. window.diagnoseRegexError = function(baseText, regex) {
  980. if(!(regex instanceof RegExp)) {
  981. throw new Error('Failed to find match using string argument. No more details available');
  982. }
  983.  
  984. //see if removing line breaks works - in that case we can give a more useful error message
  985. let oneLineText = baseText.replaceAll(/\n/g,'');
  986. let res = regex.test(oneLineText);
  987.  
  988. //If line breaks don't solve the issue then throw a general error
  989. if (!res) {
  990. throw new Error('Failed to find match for regex.');
  991. }
  992.  
  993. //Try to suggest correct regex to use for searching
  994. let regexSource = regex.source;
  995. let regexFlags = regex.flags;
  996.  
  997. //Look at all the spots where line breaks might occur and try adding \n? there to see if it makes a difference
  998. //It might be easier to just crudely brute force putting \n? at each possible index?
  999. for(let breakableChar of ["%","&","\\*","\\+",",","-","\\/",":",";","<","=",">","\\?","{","\\|","}"]) {
  1000. for(let pos = regexSource.indexOf(breakableChar); pos !== -1; pos = regexSource.indexOf(breakableChar, pos + 1)) {
  1001. //Remake the regex with a new line at the candidate position
  1002. let candidateRegexSource = `${regexSource.slice(0,pos + breakableChar.length)}\\n?${regexSource.slice(pos + breakableChar.length)}`;
  1003. let candidateRegex;
  1004. try{
  1005. candidateRegex = new RegExp(candidateRegexSource, regexFlags);
  1006. } catch(err) {
  1007. continue;
  1008. }
  1009.  
  1010. //See if the new regex works
  1011. let testReplaceResult = candidateRegex.test(baseText);
  1012. if(testReplaceResult) {
  1013. //Success we found the working regex! Give descriptive error message to user and log suggested regex with new line in correct place
  1014. console.log(`Suggested regex improvement:
  1015. ${candidateRegex}`);
  1016. 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.');
  1017. }
  1018. }
  1019. }
  1020.  
  1021. 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');
  1022. }
  1023.  
  1024. window.appendCodeWithinSnakeModule = function(snakeCode, codeToAdd, addSemicolonAfter) {
  1025. if(addSemicolonAfter) {
  1026. codeToAdd += ';';
  1027. }
  1028. var newSnakeCode = snakeCode.replace(/}\)\(this\._s\);\n\/\/ Google Inc\./, codeToAdd + '$&');
  1029. return newSnakeCode;
  1030. }
  1031.  
  1032. //Turns _.abc into _s.abc
  1033. window.swapInSnakeGlobal = function(text) {
  1034. return assertReplace(text, /^_\./, '_s.');
  1035. }