Skip to content

Instantly share code, notes, and snippets.

@xBZZZZ
Last active September 29, 2023 13:15
Show Gist options
  • Select an option

  • Save xBZZZZ/acbbae6659000b02b320c06b85b49dac to your computer and use it in GitHub Desktop.

Select an option

Save xBZZZZ/acbbae6659000b02b320c06b85b49dac to your computer and use it in GitHub Desktop.
download and modify scenexe.io js into chromium overrides folder
#!/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