Last active
September 29, 2023 13:15
-
-
Save xBZZZZ/acbbae6659000b02b320c06b85b49dac to your computer and use it in GitHub Desktop.
download and modify scenexe.io js into chromium overrides folder
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/python3 | |
| import requests,os,sys,re | |
| stdoutbuf=sys.stdout.buffer | |
| def bprint(data:bytes): | |
| stdoutbuf.write(data) | |
| stdoutbuf.flush() | |
| def find1(pattern,string,flags): | |
| i=re.finditer(pattern,string,flags) | |
| try: | |
| m=i.__next__() | |
| except StopIteration: | |
| raise Exception('matched 0 times (need 1)') | |
| try: | |
| i.__next__() | |
| except StopIteration: | |
| return m | |
| raise Exception('matched 2 or more times (need 1)') | |
| def download_and_patch(domain:bytes,overrides_path:bytes,patch_functions:list): | |
| bprint(b'domain = %b\n'%domain) | |
| with requests.Session() as s: | |
| jsname=s.get(b'https://'+domain) | |
| jsname.raise_for_status() | |
| jsname=find1( | |
| br'<script defer="defer" src="([0-9A-Za-z-_]{21}\.js)"></script>', | |
| jsname.content,0 | |
| ).group(1) | |
| bprint(b'js name = %b\n'%jsname) | |
| jsdata=s.get(b'https://%b/%b'%(domain,jsname)) | |
| jsdata.raise_for_status() | |
| jsdata=jsdata.content | |
| jsdata=bytearray(jsdata) | |
| bprint(b'js length in bytes = %d\n'%jsdata.__len__()) | |
| try: | |
| os.mkdir(os.path.join(overrides_path,domain)) | |
| except FileExistsError: | |
| pass | |
| with open(os.path.join(overrides_path,domain,b'vanilla_'+jsname),'wb') as s: | |
| s.write(jsdata) | |
| before2=bytearray() | |
| before=bytearray() | |
| for s in patch_functions: | |
| s(before2,before,jsdata) | |
| with open(os.path.join(overrides_path,domain,jsname),'wb') as s: | |
| s.write(before2) | |
| s.write(before) | |
| s.write(b'\n;\n') | |
| s.write(jsdata) | |
| def add_deps(patch_functions:list): | |
| a=[] | |
| for p in patch_functions: | |
| try: | |
| a.__init__(f for f in p.deps if f not in patch_functions) | |
| except AttributeError: | |
| continue | |
| patch_functions.extend(a) | |
| if frozenset(patch_functions).__len__()<patch_functions.__len__(): | |
| raise Exception('duplicates in patch_functions') | |
| def patch_log_patched(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| before.extend(b''' | |
| //patch: log patched | |
| console.log('patched') | |
| //end patch | |
| ''') | |
| def patch_game_canvas_alpha_false(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| before.extend(b''' | |
| //patch: game canvas alpha false | |
| { | |
| const c = document.getElementById('game-canvas').getContext('2d', {'alpha': false}) | |
| c.canvas.getContext = () => c | |
| } | |
| //end patch | |
| ''') | |
| jsres={b'v':b'[A-Za-z$_][0-9A-Za-z$_]*',b'n':b'0x[0-9a-f]+'} | |
| jsres[b'p']=br"\[(?:%(v)b\(%(n)b\)|'%(v)b')]"%jsres | |
| def patch_expose_game_updates(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| 'exposes array of game updates as window.gameupdates' | |
| before2.extend(b''' | |
| //patch: expose game updates (part 1) | |
| var gameupdatelisteners = 0 | |
| //end patch | |
| ''') | |
| s=find1(br''' | |
| function\ %(v)b\(\) { | |
| (%(v)b) = \[], | |
| %(v)b = {}, | |
| %(v)b = !0x1, | |
| %(v)b\(\), | |
| %(v)b\(\); | |
| } | |
| '''%jsres,jsdata,re.VERBOSE) | |
| b=s.start(0) | |
| g=s.group(1) | |
| jsdata[s.start(1):s.end(1)+4]=b''' | |
| //patch (replacing code): expose game updates (part 3) | |
| %b.length = 0, | |
| //end patch | |
| '''%g | |
| jsdata[b:b]=b''' | |
| //patch: expose game updates (part 2) | |
| %(g)b.realpush = Array.prototype.push | |
| %(g)b.push = g => { | |
| %(g)b.realpush(g) | |
| if (gameupdatelisteners) | |
| dispatchEvent(new Event('gameupdate')) | |
| } | |
| window.gameupdates = %(g)b; | |
| //end patch | |
| '''%{b'g':g} | |
| def patch_zoom(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| before.extend(b''' | |
| //patch: zoom (part 1) | |
| var scenexezoom = 1 | |
| document.getElementById('game-canvas').addEventListener('wheel', event => { | |
| if (event.deltaY > 0) | |
| scenexezoom *= 1.0625 | |
| else if ((scenexezoom *= 0.9375) < 1) | |
| scenexezoom = 1 | |
| }, {'passive': true}) | |
| //end patch | |
| ''') | |
| s=find1(br''' | |
| if \(%(v)b\((%(v)b), %(v)b\), null != %(v)b\) (%(v)b) = (%(v)b)\(\2, 0x12c0, 0\.05, \1\), | |
| '''%jsres,jsdata,re.VERBOSE).groups() | |
| p=br'%b=%b\(%b,[^,]+(),0\.05,%b\)'%(s[1],s[2],s[1],s[0]) | |
| s=tuple(m.start(1) for m in re.finditer(p,jsdata,0)) | |
| if 4!=s.__len__(): | |
| raise Exception('expected 4 but found %d matches of %r'%(s.__len__(),p)) | |
| f=0 | |
| for i,p in enumerate(s,1): | |
| p+=f | |
| jsdata[p:p]=p=b''' | |
| //patch: zoom (part 2 %d/4) | |
| * scenexezoom | |
| //end patch | |
| '''%i | |
| f+=p.__len__() | |
| def patch_no_darkness(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| before.extend(b''' | |
| //patch: no darkness (part 1) | |
| document.getElementById('darkness-canvas').getContext=Element.prototype.remove | |
| //end patch | |
| ''') | |
| s=find1(br''' | |
| ,(%(v)b) = %(v)b, | |
| (%(v)b)(?:%(p)b){2}\) { | |
| var\ %(v)b = (%(v)b)%(p)b / 0x2 - %(v)b / (%(v)b), | |
| %(v)b = \3%(p)b / 0x2 - %(v)b / \4; | |
| \1%(p)b = \2(?:%(p)b){3} \|\| 0\.9, | |
| \1%(p)b = \2(?:%(p)b){3}, | |
| \1%(p)b\(0x0, 0x0, (%(v)b)%(p)b, \5%(p)b\), | |
| \1%(p)b\(0x0, 0x0(?:, \5%(p)b){2}\), | |
| \1%(p)b = 0x1; | |
| .+? | |
| \1%(p)b=%(v)b, | |
| \1%(p)b\(0x0, 0x0(?:, \5%(p)b){2}\)\), | |
| () | |
| \1=%(v)b, | |
| !%(v)b%(p)b && %(v)b \|\| | |
| '''%jsres,jsdata,re.VERBOSE) | |
| jsdata[s.start(0):s.start(6)]=b''' | |
| //patch (replacing code): no darkness (part 2) | |
| ) { | |
| //empty if body | |
| } | |
| //end patch | |
| ''' | |
| def patch_grid_render_pattern(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| 'render grid using CanvasPattern instead of stroking lines' | |
| before.extend(b''' | |
| //patch: grid render pattern (part 1) | |
| { | |
| let olddimvisual = null, pattern = null, numerator = 1 | |
| const patterncanvas = 'function' === typeof OffscreenCanvas ? new OffscreenCanvas(0,0) : document.createElement('canvas'), | |
| patterncontext = patterncanvas.getContext('2d', {'alpha': false}), | |
| makepattern = dimvisual => { | |
| if (olddimvisual === dimvisual) | |
| return true | |
| let gs = Math.round(dimvisual.gridSize), s | |
| if (gs % 5) { | |
| s = 5 | |
| numerator = 1 | |
| } else { | |
| s = 1 | |
| numerator = 5 | |
| gs /= 5 | |
| } | |
| if (gs > 1024) | |
| return false | |
| olddimvisual = dimvisual | |
| patterncanvas.width = patterncanvas.height = gs | |
| patterncontext.fillStyle = dimvisual.gridColor | |
| patterncontext.fillRect(0, 0, gs, gs) | |
| patterncontext.fillStyle = dimvisual.backgroundColor | |
| gs -= s | |
| patterncontext.fillRect(0, 0, gs, gs) | |
| pattern = patterncontext.createPattern(patterncanvas, null) | |
| return true | |
| }, | |
| matrix = {'__proto__': null, 'a': NaN, 'd': NaN, 'e': NaN, 'f': NaN} | |
| var rendergrid = (context, dimvisual, denominator, gridx, gridy) => { | |
| context.imageSmoothingEnabled = false | |
| if (denominator < 16.666666666666668 && makepattern(dimvisual)) { | |
| const p = 2.5 * (matrix.a = matrix.d = numerator / denominator) | |
| matrix.e = p + gridx | |
| matrix.f = p + gridy | |
| pattern.setTransform(matrix) | |
| context.fillStyle = pattern | |
| } else | |
| context.fillStyle = dimvisual.backgroundColor | |
| const c = context.canvas | |
| context.fillRect(0, 0, c.width, c.height) | |
| } | |
| } | |
| //end patch | |
| ''') | |
| s=find1(br''' | |
| (%(v)b)%(p)b = (%(v)b)%(p)b, | |
| \1%(p)b\(0x0, 0x0(?:, \1(?:%(p)b){2}){2}\); | |
| var\ (%(v)b) = 0x5 / (%(v)b); | |
| if \(\3 > 0\.3\) { | |
| var\ (%(v)b) = \2%(p)b / \4, | |
| (%(v)b) = Math%(p)b\((%(v)b) / \5\) \* \5, | |
| (%(v)b) = Math%(p)b\((%(v)b) / \5\) \* \5; | |
| \1%(p)b = \3, | |
| \1%(p)b = \2%(p)b, | |
| \1%(p)b\(\); | |
| for \(var\ (%(v)b) = \7; \10 < \1(?:%(p)b){2} \+ \7; \10 \+= \5\) | |
| \1%(p)b\(\10 - \6, 0x0\), | |
| \1%(p)b\(\10 - \6, \1(?:%(p)b){2}\); | |
| for \(var\ (%(v)b) = \9; \11 < \1(?:%(p)b){2} \+ \9; \11 \+= \5\) | |
| \1%(p)b\(0x0, \11 - \8\), | |
| \1%(p)b\(\1(?:%(p)b){2}, \11 - \8\); | |
| \1%(p)b\(\), | |
| \1%(p)b\(\); | |
| } | |
| '''%jsres,jsdata,re.VERBOSE) | |
| g=s.groups() | |
| jsdata[s.start(0):s.end(0)]=b''' | |
| //patch (replacing code): grid render pattern (part 2) | |
| rendergrid(%b, %b, %b, %b, %b); | |
| //end patch | |
| '''%(g[0],g[1],g[3],g[6],g[8]) | |
| def patch_rewrite1(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| 'trying to optimize game update receiving and interpolating' | |
| before.extend(b''' | |
| //patch: rewrite1 (part 1) | |
| var emptymap = new Map() | |
| //end patch | |
| ''') | |
| s=find1(br''' | |
| if \((%(v)b) = !0x0, 0x0 != (%(v)b)%(p)b\) { | |
| for \(var\ (%(v)b) = {}, (%(v)b) = 0x0; \4 < (%(v)b)%(p)b%(p)b; \4 \+= 0x3\) | |
| \3\[\5%(p)b\[\4]] = \[\5%(p)b\[\4 \+ 0x1], \5%(p)b\[\4 \+ 0x2]]; | |
| \5%(p)b = \3; | |
| var\ (%(v)b) = \2\[\2%(p)b - 0x1]; | |
| \5%(p)b && \(\5%(p)b = (%(v)b)\(\6%(p)b, \5%(p)b\)\) | |
| ,\5%(p)b = (%(v)b)\(\6%(p)b, \5%(p)b, \3\) | |
| (?:,\5%(p)b = \8\(\6%(p)b, \5%(p)b, \3\)){3}; | |
| } | |
| '''%jsres,jsdata,re.VERBOSE) | |
| g=s.groups() | |
| jsdata[s.start(0):s.end(0)]=b''' | |
| //patch (replacing code): rewrite1 (part 2) | |
| %(bool1)b = true | |
| if (%(gameupdatearr)b.length) { | |
| const idmap = emptymap, prevgameupdate = %(gameupdatearr)b.at(-1) | |
| %(currgameupdate)b.me &&= %(inherit)b(prevgameupdate.me, %(currgameupdate)b.me) | |
| let i, arr, preventity | |
| i = (arr = prevgameupdate.tanks).length | |
| while (i) | |
| idmap.set(arr[--i].id, arr[i]) | |
| i = (arr = %(currgameupdate)b.tanks).length | |
| while (i) | |
| if (preventity = idmap.get(arr[--i].id)) | |
| arr[i] = %(inherit)b(preventity, arr[i]) | |
| idmap.clear() | |
| i = (arr = prevgameupdate.bullets).length | |
| while (i) | |
| idmap.set(arr[--i].id, arr[i]) | |
| i = (arr = %(currgameupdate)b.bullets).length | |
| while (i) | |
| if (preventity = idmap.get(arr[--i].id)) | |
| arr[i] = %(inherit)b(preventity, arr[i]) | |
| idmap.clear() | |
| i = (arr = prevgameupdate.rifts).length | |
| while (i) | |
| idmap.set(arr[--i].id, arr[i]) | |
| i = (arr = %(currgameupdate)b.rifts).length | |
| while (i) | |
| if (preventity = idmap.get(arr[--i].id)) | |
| arr[i] = %(inherit)b(preventity, arr[i]) | |
| idmap.clear() | |
| i = (arr = prevgameupdate.polygons).length | |
| while (i) | |
| idmap.set(arr[--i].id, arr[i]) | |
| i = (arr = %(currgameupdate)b.polygons).length | |
| while (i) | |
| if (preventity = idmap.get(arr[--i].id)) | |
| arr[i] = %(inherit)b(preventity, arr[i]) | |
| idmap.clear() | |
| if (i = (arr = %(currgameupdate)b.removedEntities).length) { | |
| emptymap = new Map() | |
| let j = 0 | |
| while (j < i) | |
| idmap.set(arr[j], arr.slice(++j, j += 2)) | |
| } | |
| %(currgameupdate)b.removedEntities = idmap | |
| } else | |
| %(currgameupdate)b.removedEntities = emptymap; | |
| //end patch | |
| '''%{b'bool1':g[0],b'gameupdatearr':g[1],b'currgameupdate':g[4],b'inherit':g[6]} | |
| s=find1(br''' | |
| function\ (%(v)b)\((%(v)b), (%(v)b), (?:%(v)b, ){2}(%(v)b)\) { | |
| var\ (?:%(v)b = %(v)b,)? | |
| (%(v)b) = arguments%(p)b > 0x5 && void\ 0x0 !== arguments\[0x5] && arguments\[0x5]; | |
| if \(\2\) { | |
| if \(\3\) { | |
| var\ %(v)b, %(v)b = {}, (%(v)b) = {}, (%(v)b) = {}, %(v)b = (%(v)b)\(\2\); | |
| try { | |
| .+? | |
| return\ %(v)b%(p)b\(function\((%(v)b)\) { | |
| (?:var\ %(v)b = %(v)b;)? | |
| return\ (%(v)b)\(\9, \6\[\9%(p)b] \|\| \9, \7\[\9%(p)b] \|\| \9, \4, \5, !0x0\); | |
| }\)%(p)b\(function\((%(v)b)\) { | |
| (?:var\ %(v)b = %(v)b;)? | |
| return !!\(\6\[\11%(p)b] \|\| \11%(p)b && \11%(p)b > 0x0\); | |
| }\); | |
| } | |
| return\ \2; | |
| } | |
| } | |
| function\ (%(v)b)\((%(v)b), (%(v)b), %(v)b, (%(v)b)\) { | |
| var\ (?:%(v)b = %(v)b,)? | |
| %(v)b, (%(v)b) = {}, (%(v)b) = {}, %(v)b = \8\(\13\); | |
| try { | |
| .+? | |
| } | |
| if \(\13\) { | |
| if \(\14\) { | |
| for \(var\ (%(v)b) = new\ Array\(\14%(p)b\), (%(v)b) = 0x0; \19 < \14%(p)b; \+\+\19\) { | |
| var\ (%(v)b) = \14\[\19]; | |
| \18\[\19] = \10\(\16\[\20%(p)b] \|\| \20, \20, \17\[\20%(p)b] \|\| \20, \15, !0x1\); | |
| } | |
| return\ \18; | |
| } | |
| return\ \13; | |
| } | |
| } | |
| '''%jsres,jsdata,re.VERBOSE) | |
| jsdata[s.start(0):s.end(0)]=b''' | |
| //patch (replacing code): rewrite1 (part 3) | |
| { | |
| const id_arr1_set = new Set(), id_arr2_map = new Map(), id_arr3_map = new Map() | |
| var %(interpolate_entities)b = (arr1, arr2, arr3, removedentities, num1, bool1 = false) => { | |
| //don't know why original code has ifs | |
| /*if (!arr1) | |
| return | |
| if (!arr2) | |
| return arr1*/ | |
| let i = arr1.length | |
| while (i) { | |
| const entity = arr1[--i] | |
| id_arr1_set.add(entity.id) | |
| const fadetype = removedentities.get(entity.id) | |
| if (fadetype) | |
| entity.fadeType = fadetype | |
| } | |
| i = arr2.length | |
| while (i) | |
| id_arr2_map.set(arr2[--i].id, arr2[i]) | |
| i = arr3.length | |
| while (i) | |
| id_arr3_map.set(arr3[--i].id, arr3[i]) | |
| const outarr = [] | |
| let l = arr1.length | |
| //don't need i = 0 here because i is already 0 | |
| while (i < l) { | |
| let entity = arr1[i++] | |
| const entity_from_arr2 = id_arr2_map.get(entity.id) | |
| entity = %(interpolate)b( | |
| entity, | |
| entity_from_arr2 || entity, | |
| id_arr3_map.get(entity.id) || entity, | |
| num1, bool1, true | |
| ) | |
| if (entity_from_arr2 || entity.fadeType && entity.fadeTime > 0) | |
| outarr.push(entity) | |
| } | |
| l = arr2.length | |
| i = 0 | |
| while (i < l) { | |
| const entity = arr2[i++] | |
| if (!id_arr1_set.has(entity.id)) | |
| outarr.push(%(interpolate)b( | |
| entity, | |
| entity, | |
| id_arr3_map.get(entity.id) || entity, | |
| num1, bool1, true | |
| )) | |
| } | |
| id_arr1_set.clear() | |
| id_arr2_map.clear() | |
| id_arr3_map.clear() | |
| return outarr | |
| }, %(interpolate_rifts)b = (arr2, arr1, arr3, num1) => { | |
| //don't know why original code has ifs | |
| /*if (!arr2) | |
| return | |
| if (!arr1) | |
| return arr2*/ | |
| let i = arr2.length | |
| while (i) | |
| id_arr2_map.set(arr2[--i].id, arr2[i]) | |
| i = arr3.length | |
| while (i) | |
| id_arr3_map.set(arr3[--i].id, arr3[i]) | |
| const outarr = [] | |
| let l = arr1.length | |
| //don't need i = 0 here because i is already 0 | |
| while (i < l) { | |
| const rift = arr1[i++] | |
| outarr.push(%(interpolate)b( | |
| id_arr2_map.get(rift.id) || rift, | |
| rift, | |
| id_arr3_map.get(rift.id) || rift, | |
| num1, false | |
| )) | |
| } | |
| id_arr2_map.clear() | |
| id_arr3_map.clear() | |
| return outarr | |
| } | |
| } | |
| //end patch | |
| '''%{b'interpolate_entities':s.group(1),b'interpolate':s.group(10),b'interpolate_rifts':s.group(12)} | |
| def patch_rewrite2(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| 'rewrite very inefficient function that adds number to each channel of #RRGGBB hex code' | |
| s=find1(br''' | |
| function\ (%(v)b)\((%(v)b), (%(v)b)\) { | |
| (?:var\ %(v)b = %(v)b;)? | |
| return '\#' \+ \2%(p)b\(/\^\#/, ''\)%(p)b\(/\.\./g, function\((%(v)b)\) { | |
| (?:var\ %(v)b = %(v)b;)? | |
| return \('0' \+ Math%(p)b\(0xff, Math%(p)b\(0x0, parseInt\(\4, 0x10\) \+ \3\)\)%(p)b\(0x10\)\)%(p)b\(-0x2\); | |
| }\); | |
| } | |
| '''%jsres,jsdata,re.VERBOSE) | |
| jsdata[s.start(0):s.end(0)]=b''' | |
| //patch: rewrite2 (replacing code) | |
| { | |
| const i32arr = Int32Array.of(1), u8arr = new Uint8ClampedArray(i32arr.buffer, new Int8Array(i32arr.buffer)[3], 3) | |
| var %b = (hexcodewithhash, increment) => { | |
| if (!increment) | |
| return hexcodewithhash | |
| i32arr[0] = parseInt(hexcodewithhash.slice(1), 16) | |
| u8arr[0] += increment | |
| u8arr[1] += increment | |
| u8arr[2] += increment | |
| return i32arr[0].toString(16).padStart(7, '#000000') | |
| } | |
| } | |
| //end patch | |
| '''%s.group(1) | |
| def patch_rewrite3(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| 'rewrite array structure to object function and array of array structure to array of objects function' | |
| s=find1(br''' | |
| function\ (%(v)b)\((%(v)b)\) { | |
| (?:var\ %(v)b = %(v)b;)? | |
| for \(var\ (%(v)b) = {}, (%(v)b) = (%(v)b%(p)b)\[\2\[0x1]], (%(v)b) = 0x0; \6 < \2%(p)b; \6\+\+\) { | |
| var\ (%(v)b) = \2\[\6]; | |
| null != \7 && \(\3\[\4\[\6]] = \7\); | |
| } | |
| return\ \3; | |
| } | |
| function\ (%(v)b)\((%(v)b)\) { | |
| (?:var\ %(v)b = %(v)b;)? | |
| for \(var\ (%(v)b) = 0x0; \10 < \9%(p)b; \10\+\+\) | |
| \9\[\10] = \1\(\9\[\10]\); | |
| return\ \9; | |
| } | |
| '''%jsres,jsdata,re.VERBOSE) | |
| jsdata[s.start(0):s.end(0)]=br''' | |
| //patch (replacing code): rewrite3 | |
| { | |
| const constructor = Function('a', (arr => { | |
| if (!arr.length) | |
| throw Error('[patch: rewrite3] empty structures array') | |
| if (arr.some(s => s.length < 6 || s[0] !== 'id' || s[1] !== 'sType' || s[2] !== 'x' || s[3] !== 'y' || s[4] !== 'd' || s[5] !== 'size')) | |
| throw Error("[patch: rewrite3] found structure not like ['id', 'sType', 'x', 'y', 'd', 'size'...") | |
| let code = `this.id=a[0] | |
| if(a[2]!=null)this.x=a[2] | |
| if(a[3]!=null)this.y=a[3] | |
| if(a[4]!=null)this.d=a[4] | |
| if(a[5]!=null)this.size=a[5] | |
| switch(this.sType=a[1]){ | |
| `, l = arr.length, i = 0, i2, l2, s | |
| for (;;) | |
| if ((l2 = (s = arr[i]).length) > 6) { | |
| code += `case ${i}:\n` | |
| i2 = 6 | |
| while (i2 < l2) | |
| code += `if(a[${i2}]!=null)this.${s[i2]}=a[${i2++}]\n` | |
| if (++i === l) | |
| break | |
| code += 'break\n' | |
| } else if (++i === l) | |
| break | |
| return code + '}' | |
| })(%b)) | |
| var %b = arr => { | |
| let i = arr.length | |
| while (i) | |
| arr[--i] = new constructor(arr[i]) | |
| }, %b = s => new constructor(s) | |
| } | |
| //end patch | |
| '''%(s.group(5),s.group(8),s.group(1)) | |
| def patch_expose_msgpackr_and_conn(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| ''' | |
| window.msgpackr.pack(object) | |
| window.msgpackr.unpack(uint8array) | |
| window.conn().sendMessage(packet_type_id, object) | |
| all packet type ids except ROTATION_INPUT (always 0) change on client update | |
| ''' | |
| s=find1(br''' | |
| var\ (%(v)s) = new\ %(v)s\(\); | |
| \1%(p)s\(\); | |
| var\ %(v)s(?:, %(v)s = \1%(p)s){2}, %(v)s = '', %(v)s = !0x1, %(v)s = '', (%(v)s) = { | |
| 'closed': !0x0 | |
| }; | |
| '''%jsres,jsdata,re.VERBOSE) | |
| jsdata[s.end(0):s.end(0)]=b''' | |
| //patch: expose msgpackr and conn | |
| window.msgpackr = %b | |
| window.conn = () => %b; | |
| //end patch | |
| '''%s.groups() | |
| def patch_no_auto_join(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| "don't automatically join when getting sent to server" | |
| s=find1(br",''==%(v)b\|\|%(v)b\|\|\(%(v)b\(0x0\)(?:,%(v)b\(\)){5}\);"%jsres,jsdata,0) | |
| jsdata[s.start(0):s.end(0)-1]=b''' | |
| //patch (deleting code): no auto join | |
| //end patch | |
| ''' | |
| def patch_expose_upgrade_packets_ids(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| s=find1(br'%(v)b\?(%(v)b)%(p)b\((%(n)b),(%(v)b)\):\1%(p)b\((%(n)b),\3\);'%jsres,jsdata,0) | |
| before2.extend(b''' | |
| //patch: expose upgrade packets ids | |
| var C2S_UPGRADE_WEAPON = %b, C2S_UPGRADE_BODY = %b | |
| //end patch | |
| '''%(s.group(2),s.group(4))) | |
| def patch_debug_mode_div(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| before2.extend(b''' | |
| //patch: debug mode div | |
| var d_add_btn = (value, onclick) => { | |
| const b = document.createElement('input') | |
| b.type = 'button' | |
| b.tabIndex = -1 | |
| b.value = value | |
| b.addEventListener('click', onclick, {'passive': true}) | |
| return d_buttons_div.appendChild(b) | |
| }, d_buttons_div = document.createElement('div'), d_shadow_div = document.createElement('div') | |
| { | |
| const e = e => { | |
| if ('INPUT' === e.target.tagName) | |
| e.stopPropagation() | |
| } | |
| d_shadow_div.addEventListener('mousedown', e, {'passive': true, 'capture': true}) | |
| d_shadow_div.addEventListener('mouseup', e, {'passive': true, 'capture': true}) | |
| } | |
| d_buttons_div.setAttribute('style', 'display:contents') | |
| d_shadow_div.setAttribute('style', 'all:initial;user-select:none;display:block') | |
| d_shadow_div.appendChild(d_buttons_div) | |
| { | |
| const d = document.createElement('div') | |
| d.setAttribute('style', 'display:contents') | |
| d.attachShadow({'mode': 'closed'}).appendChild(d_shadow_div) | |
| document.getElementById('debug-mode').appendChild(d) | |
| } | |
| //end patch | |
| ''') | |
| def patch_force_upgrade(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| "adds force upgrade button in debug mode to upgrade even when server doesn't tell client about upgrades (like test.scenexe.io)" | |
| before.extend(b''' | |
| //patch: force upgrade | |
| { | |
| const upgrades = { | |
| '__proto__': null, | |
| 'node': ['mono', 'commander', 'trapper'], | |
| 'mono': ['duo', 'flank', 'split', 'single', 'alloy', 'guard', 'sniper'], | |
| 'commander': ['alloy', 'overseer', 'director', 'fusion'], | |
| 'trapper': ['guard', 'fusion', 'gamma', 'blockade', 'rubble'], | |
| 'duo': ['trio', 'quad', 'arc', 'gunner'], | |
| 'flank': ['quad', 'overlord', 'conglomerate', 'wake'], | |
| 'split': ['trio', 'quad', 'spread', 'arc', 'gunner'], | |
| 'single': ['destroyer', 'assassin', 'compound'], | |
| 'alloy': ['conglomerate', 'compound'], | |
| 'guard': ['conglomerate', 'wake'], | |
| 'sniper': ['assassin', 'destroyer', 'wake'], | |
| 'overseer': ['overlord', 'factory'], | |
| 'director': ['manager', 'factory', 'compound'], | |
| 'fusion': ['conglomerate'], | |
| 'gamma': ['beta', 'engineer'], | |
| 'blockade': ['barricade', 'conglomerate', 'stockade'], | |
| 'rubble': ['scrap'], | |
| 'trio': ['quadro', 'penta', 'streamliner', 'industry'], | |
| 'quad': ['octo', 'quadro', 'horizon'], | |
| 'arc': ['octo', 'horizon', 'disperse', 'penta'], | |
| 'gunner': ['quadro', 'minigun', 'horizon', 'streamliner'], | |
| 'overlord': ['emperor', 'hatcher'], | |
| 'conglomerate': ['amalgam'], | |
| 'wake': ['wave'], | |
| 'spread': ['horizon', 'disperse', 'penta'], | |
| 'destroyer': ['annihilator', 'manufacturer', 'hybrid'], | |
| 'assassin': ['marksman', 'streamliner'], | |
| 'compound': ['hybrid'], | |
| 'factory': ['industry', 'hatcher', 'manufacturer'], | |
| 'manager': ['executive', 'manufacturer', 'hybrid'], | |
| 'beta': ['alpha', 'raider'], | |
| 'engineer': ['arsenal', 'raider', 'mechanic'], | |
| 'barricade': ['riot', 'wave', 'arsenal'], | |
| 'stockade': ['palisade', 'mechanic'], | |
| 'scrap': ['shrapnel', 'mechanic'], | |
| 'nova': ['pulsar', 'satellite', 'debris'], | |
| 'pulsar': ['quasar', 'blazar'], | |
| 'satellite': ['moon', 'synope'], | |
| 'debris': ['asteroid', 'charon'], | |
| 'base': ['smasher', 'wall', 'sentry', 'hangar', 'hearth'], | |
| 'smasher': ['fortress', 'spike', 'armory'], | |
| 'wall': ['fortress', 'stronghold', 'citadel'], | |
| 'sentry': ['armory', 'turret', 'citadel'], | |
| 'hangar': ['warship'], | |
| 'hearth': ['mender', 'bonfire'], | |
| 'fortress': ['castle', 'palace'], | |
| 'spike': ['castle', 'thorn', 'forge', 'brigade'], | |
| 'armory': ['castle', 'brigade'], | |
| 'stronghold': ['castle', 'palace'], | |
| 'citadel': ['castle', 'palace'], | |
| 'turret': ['castle', 'artillery', 'triplet', 'brigade'], | |
| 'warship': ['battleship'], | |
| 'mender': ['remedy'], | |
| 'bonfire': ['flare', 'forge'], | |
| 'castle': ['ziggurat', 'bombard', 'saw', 'bastion', 'battalion'], | |
| 'palace': ['ziggurat', 'bastion'], | |
| 'thorn': ['bastion', 'saw', 'foundry', 'battalion'], | |
| 'forge': ['foundry'], | |
| 'brigade': ['bastion', 'battalion'], | |
| 'artillery': ['bombard', 'bastion'], | |
| 'triplet': ['quadruplet', 'battalion'], | |
| 'battleship': ['mothership'], | |
| 'remedy': ['fabricator'], | |
| 'flare': ['inferno', 'foundry'], | |
| 'celestial': ['nebula', 'chasm', 'exosphere', 'corvus', 'triton'], | |
| 'nebula': ['naos', 'galaxy', 'pollux', 'oberon'], | |
| 'chasm': ['void', 'meteor', 'comet'], | |
| 'exosphere': ['heliosphere', 'meteor'], | |
| 'corvus': ['naos', 'cygnus'], | |
| 'triton': ['hyperion', 'oberon'] | |
| }, makebtn = value => { | |
| const b = document.createElement('input') | |
| b.type = 'button' | |
| b.tabIndex = -1 | |
| b.value = value | |
| return b | |
| }, div = document.createElement('div'), upgrbtn = makebtn('force upgrade'), | |
| wf = document.createElement('fieldset'), bf = document.createElement('fieldset'), | |
| wl = document.createElement('legend'), bl = document.createElement('legend'), | |
| wt = document.createTextNode(''), bt = document.createTextNode(''), arr = [], | |
| replaceChildren = Element.prototype.replaceChildren, el = e => { | |
| const t = e.target | |
| if (t.tagName !== 'INPUT') | |
| return | |
| e.stopPropagation() | |
| switch (t.parentNode) { | |
| case div: | |
| if (menu) { | |
| div.replaceChildren(upgrbtn) | |
| menu = false | |
| break | |
| } | |
| const me = gameupdates.at(-1).me | |
| wt.nodeValue = 'weapon: ' + me.weaponUpgrade | |
| let u, l, i | |
| if (u = upgrades[me.weaponUpgrade]) { | |
| l = u.length | |
| arr[i = 0] = wl | |
| while (i < l) | |
| arr.push(makebtn(u[i++])) | |
| replaceChildren.apply(wf,arr) | |
| arr.length = 1 | |
| } else | |
| wf.replaceChildren(wl) | |
| bt.nodeValue = 'body: ' + me.bodyUpgrade | |
| if (u = upgrades[me.bodyUpgrade]) { | |
| l = u.length | |
| arr[i = 0] = bl | |
| while (i < l) | |
| arr.push(makebtn(u[i++])) | |
| replaceChildren.apply(bf,arr) | |
| arr.length = 1 | |
| } else | |
| bf.replaceChildren(bl) | |
| div.replaceChildren(upgrbtn, wf, bf) | |
| menu = true | |
| break | |
| case wf: | |
| conn().sendMessage(C2S_UPGRADE_WEAPON, t.value) | |
| div.replaceChildren(upgrbtn) | |
| menu = false | |
| break | |
| case bf: | |
| conn().sendMessage(C2S_UPGRADE_BODY, t.value) | |
| div.replaceChildren(upgrbtn) | |
| menu = false | |
| } | |
| } | |
| let menu = false | |
| div.setAttribute('style', 'display:contents') | |
| { | |
| const s = 'background-color:buttonface;color:buttontext' | |
| wl.setAttribute('style',s) | |
| bl.setAttribute('style',s) | |
| } | |
| wl.appendChild(wt) | |
| bl.appendChild(bt) | |
| div.addEventListener('click', el, {'passive': true, 'capture': true}) | |
| div.appendChild(upgrbtn) | |
| d_shadow_div.appendChild(div) | |
| } | |
| //end patch | |
| ''') | |
| patch_force_upgrade.deps=patch_expose_upgrade_packets_ids,patch_expose_msgpackr_and_conn,patch_debug_mode_div | |
| def patch_expose_reconnect(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| ''' | |
| window.reconnect(url) | |
| url - https url string without end slash like 'https://2teams-nyc-g3iyhr.scnx.cc' | |
| ''' | |
| s=find1(br''' | |
| function\ (%(v)b)\((%(v)b)\) { | |
| (?:var\ %(v)b = %(v)b;)? | |
| (%(v)b)%(p)b \|\| \(\3%(p)b\(\), %(v)b\(%(v)b, !0x1\)\), | |
| %(v)b\(\2\); | |
| } | |
| '''%jsres,jsdata,re.VERBOSE) | |
| jsdata[s.end(0):s.end(0)]=b''' | |
| //patch: expose reconnect | |
| window.reconnect = %b; | |
| //end patch | |
| '''%s.group(1) | |
| def patch_send_storage(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| before.extend(b''' | |
| //patch: send storage | |
| { | |
| let replace_c = '' | |
| fetch = new Proxy(fetch, { | |
| '__proto__': null, | |
| 'apply': (realfetch, this2, args) => { | |
| if (2 !== args.length || 'string' !== typeof args[0] || !args[0].endsWith('/api/joinserver')) | |
| return realfetch.apply(this2, args) | |
| const o = JSON.parse(args[1].body) | |
| if (replace_c) { | |
| o.c = replace_c | |
| replace_c = '' | |
| args[1].body = JSON.stringify(o) | |
| return realfetch(args[0], args[1]) | |
| } | |
| return o.c ? new Promise((res, rej) => { | |
| const div = document.createElement('div') | |
| div.setAttribute('style', 'all:initial;display:contents') | |
| const s = div.attachShadow({'mode': 'closed'}) | |
| s.innerHTML = `<dialog style=padding:.5em;gap:.5em;display:grid><strong style=grid-column:1/3>send storage (store)</strong><input value=":${location.hostname}:${encodeURIComponent(o.a)}:${encodeURIComponent(o.c)}:" style=grid-column:1/3 readonly size=60><input value=copy type=button><input value=join type=button></dialog>` | |
| const dialog = s.firstChild, {1: input, 2: copybtn, 3: joinbtn} = dialog.childNodes | |
| dialog.addEventListener('cancel', Function.prototype.call.bind(Event.prototype.preventDefault)) | |
| dialog.addEventListener('click', ({target}) => { | |
| switch (target) { | |
| case copybtn: | |
| input.select() | |
| document.execCommand('copy') | |
| break | |
| case joinbtn: | |
| realfetch(args[0], args[1]).then(res, rej) | |
| dialog.close() | |
| document.body.removeChild(div) | |
| } | |
| }, {'passive': true}) | |
| document.body.appendChild(div) | |
| dialog.showModal() | |
| }) : realfetch(args[0], args[1]) | |
| } | |
| }) | |
| const ssbtn = document.createElement('button') | |
| ssbtn.setAttribute('style', 'font-size:2vmin;background-image:linear-gradient(to top,#808,#E0E)') | |
| ssbtn.addEventListener('click', () => { | |
| const div = document.createElement('div') | |
| div.setAttribute('style', 'all:initial;display:contents') | |
| const s = div.attachShadow({'mode': 'closed'}) | |
| s.innerHTML = '<dialog style=padding:.5em;gap:.5em;display:grid><strong style=grid-column:1/3>send storage (join)</strong><input style=grid-column:1/3 size=60><input value=join type=button><input value=cancel type=button></dialog>' | |
| const dialog = s.firstChild, {0: {firstChild: status}, 1: input, 2: joinbtn, 3: cancelbtn} = dialog.childNodes | |
| dialog.addEventListener('close', Element.prototype.removeChild.bind(document.body, div), {'passive': true}) | |
| dialog.addEventListener('click', ({target}) => { | |
| switch (target) { | |
| case joinbtn: | |
| const s = input.value.split(':', 6) | |
| if (5 !== s.length || s[0] || s[4]) { | |
| status.nodeValue = 'error: bad format' | |
| break | |
| } | |
| if (location.hostname !== s[1]) { | |
| status.nodeValue = 'error: bad hostname' | |
| break | |
| } | |
| let a, c | |
| try { | |
| a = decodeURIComponent(s[2]) | |
| c = decodeURIComponent(s[3]) | |
| } catch { | |
| status.nodeValue = 'error: bad uri encoding' | |
| break | |
| } | |
| replace_c = c | |
| reconnect(a) | |
| case cancelbtn: | |
| dialog.close() | |
| } | |
| }, {'passive': true}) | |
| document.body.appendChild(div) | |
| dialog.showModal() | |
| }, {'passive': true}) | |
| ssbtn.textContent='send storage (join)' | |
| document.getElementById('right-buttons').appendChild(ssbtn) | |
| } | |
| //end patch | |
| ''') | |
| patch_send_storage.deps=patch_expose_reconnect, | |
| def patch_expose_movement_packet_id(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| before2.extend(b''' | |
| //patch: expose movement packet id | |
| var C2S_MOVEMENT_INPUT = %b | |
| //end patch | |
| '''%find1(br''' | |
| %(v)b = function\((%(v)b, %(v)b)\) { | |
| (?:var\ %(v)b = %(v)b;)? | |
| %(v)b%(p)b\((%(n)b), \[\1]\); | |
| } | |
| '''%jsres,jsdata,re.VERBOSE).group(2)) | |
| def patch_afk(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| before.extend(b''' | |
| //patch: afk | |
| var AFK_MAX_DIST_SQ = 100 * 100, afk_on_game_update = () => { | |
| const g = gameupdates.at(-1) | |
| if (!(g && g.me && conn() === afk_obj.conn)) { | |
| afk_off() | |
| return | |
| } | |
| const dx = afk_obj.x - g.me.x, dy = g.me.y - afk_obj.y | |
| if (dx * dx + dy * dy > AFK_MAX_DIST_SQ) { | |
| afk_obj.conn.sendMessage(C2S_MOVEMENT_INPUT, [dx, dy]) | |
| afk_obj.moving = true | |
| } else if (afk_obj.moving) { | |
| afk_obj.conn.sendMessage(C2S_MOVEMENT_INPUT, [0, 0]) | |
| afk_obj.moving = false | |
| } | |
| }, afk_off = () => { | |
| if (!afk_obj) { | |
| console.log('afk already off') | |
| return | |
| } | |
| removeEventListener('gameupdate', afk_on_game_update) | |
| --gameupdatelisteners | |
| afk_obj = null | |
| afk_btn.value = 'afk:off' | |
| console.log('afk off') | |
| }, afk_on = () => { | |
| if (afk_obj) { | |
| console.log('afk already on') | |
| return | |
| } | |
| const g = gameupdates.at(-1) | |
| if (!(g && g.me)) { | |
| console.log("can't turn on afk because player doesn't exist") | |
| return | |
| } | |
| afk_obj = { | |
| 'conn': conn(), | |
| 'moving': false, | |
| 'x': g.me.x, | |
| 'y': g.me.y | |
| } | |
| addEventListener('gameupdate', afk_on_game_update) | |
| ++gameupdatelisteners | |
| afk_btn.value = 'afk:on' | |
| console.log('afk on') | |
| }, afk_btn = d_add_btn('afk:off', () => { | |
| if (afk_obj) | |
| afk_off() | |
| else | |
| afk_on() | |
| }), afk_obj = null | |
| //end patch | |
| ''') | |
| patch_afk.deps=patch_expose_game_updates,patch_expose_msgpackr_and_conn,patch_expose_movement_packet_id,patch_debug_mode_div | |
| def patch_rewrite4(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| ''' | |
| make sendMessage and message listener create less Uint8Arrays and expose C2S_XOR and S2C_XOR | |
| WARNING: breaks https://greasyfork.org/en/scripts/450531-scenexe-io-afk | |
| ''' | |
| s=find1(br'var (%(v)b)=%(v)b\(%(v)b\(%(v)b\((%(v)b)\),new Uint8Array\(\[(%(v)b)]\)\),(%(n)b)\);'%jsres,jsdata,0) | |
| v_packet_u8arr,v_unpacked_data,v_packet_id,C2S_XOR=s.groups() | |
| jsdata[s.start(0):s.end(0)]=b''' | |
| //patch (replacing code): rewrite4 (part 2) | |
| var %(packet_u8arr)b = (%(packet_u8arr)b = msgpackr.pack([%(unpacked_data)b, null], 1536)).subarray(1 + %(packet_u8arr)b.start, %(packet_u8arr)b.end) | |
| { | |
| let packet_u8arr_pos = %(packet_u8arr)b.length - 1 | |
| %(packet_u8arr)b[packet_u8arr_pos] = %(C2S_XOR)b ^ %(packet_id)b | |
| do | |
| %(packet_u8arr)b[--packet_u8arr_pos] ^= %(C2S_XOR)b | |
| while (packet_u8arr_pos) | |
| } | |
| //end patch | |
| '''%{b'packet_u8arr':v_packet_u8arr,b'unpacked_data':v_unpacked_data,b'packet_id':v_packet_id,b'C2S_XOR':C2S_XOR} | |
| s=find1(br''' | |
| var\ (%(v)b) = %(v)b\(new\ Uint8Array\((%(v)b)%(p)b\), (%(n)b)\), | |
| (%(v)b) = \1\[\1%(p)b - 0x1], | |
| (%(v)b) = %(v)b\(\1%(p)b\(0x0, \1%(p)b - 0x1\)\); | |
| '''%jsres,jsdata,re.VERBOSE) | |
| v_packet_u8arr,v_event,S2C_XOR,v_packet_id,v_unpacked_data=s.groups() | |
| jsdata[s.start(0):s.end(0)]=b''' | |
| //patch (replacing code): rewrite4 (part 3) | |
| { | |
| const packet_u8arr = new Uint8Array(%(event)b.data), | |
| packet_u8arr_len = packet_u8arr.length - 1 | |
| let packet_u8arr_pos = 0 | |
| do | |
| packet_u8arr[packet_u8arr_pos] ^= %(S2C_XOR)b | |
| while (++packet_u8arr_pos < packet_u8arr_len) | |
| var %(packet_id)b = %(S2C_XOR)b ^ packet_u8arr[packet_u8arr_len], | |
| %(unpacked_data)b = msgpackr.unpack(packet_u8arr, packet_u8arr_len) | |
| } | |
| //end patch | |
| '''%{b'event':v_event,b'S2C_XOR':S2C_XOR,b'packet_id':v_packet_id,b'unpacked_data':v_unpacked_data} | |
| before2.extend(b''' | |
| //patch: rewrite4 (part 1) | |
| var C2S_XOR = %b, S2C_XOR = %b | |
| //end patch | |
| '''%(C2S_XOR,S2C_XOR)) | |
| patch_rewrite4.deps=patch_expose_msgpackr_and_conn, | |
| def patch_use_date_now(before2:bytearray,before:bytearray,jsdata:bytearray): | |
| ''' | |
| use Date.now() instead of performance.now() | |
| this needs to run after patch_no_darkness if patch_no_darkness is used | |
| ''' | |
| s=tuple((m.start(0),m.end(0)) for m in re.finditer(br'performance%(p)b\(\)'%jsres,jsdata,0)) | |
| ps=b'''Date.now( | |
| //patch (including 'Date.now(' before and ')' after) (replacing code) (part %%d/%d): use date now | |
| //end patch | |
| )'''%s.__len__() | |
| f=0 | |
| for i,(s,e) in enumerate(s,1): | |
| jsdata[f+s:f+e]=i=ps%i | |
| f+=i.__len__()+s-e | |
| patches=[ | |
| patch_log_patched, | |
| patch_game_canvas_alpha_false, | |
| patch_no_darkness, | |
| patch_zoom, | |
| patch_grid_render_pattern, | |
| patch_rewrite1, | |
| patch_rewrite2, | |
| patch_rewrite3, | |
| patch_rewrite4, | |
| patch_no_auto_join, | |
| patch_force_upgrade, | |
| patch_send_storage, | |
| patch_afk, | |
| # patch_use_date_now | |
| ] | |
| add_deps(patches) | |
| for domain in (b'scenexe.io',b'test.scenexe.io'): | |
| download_and_patch( | |
| domain, | |
| br'/path/to/overrides/folder',#<- edit this!!! | |
| patches | |
| ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment