284 lines
12 KiB
Python
284 lines
12 KiB
Python
"""Сборка src/main/java из vf-plain + jadx-дополнений + нобэкенд-патча."""
|
|
import os, re, shutil, zipfile, sys
|
|
|
|
EVO = '/storage/project/jvm/LoVisual/ref/evo'
|
|
VF = f'{EVO}/vf-plain'
|
|
JADX = f'{EVO}/mod/jar-src-plain/sources/defpackage'
|
|
DEST = '/storage/aboba/evovis-client'
|
|
JAVA = f'{DEST}/src/main/java'
|
|
CONSTS = 'GENERAL|MODULE|RENDER|SHADER|GUI|CONFIG|STORAGE|COSMETIC|MEDIA|NETWORK|AUTH|SOCIAL|UPDATE|COMPAT|SECURITY'
|
|
RULES = [
|
|
(re.compile(r'\bif\.(' + CONSTS + r')\b'), r'Cif.\1'),
|
|
(re.compile(r'(?<![\w.$])if\.(?=[a-z])'), 'Cif.'),
|
|
(re.compile(r'(?<![\w.$])do\.(?=[A-Za-z_$])'), 'Cdo.'),
|
|
(re.compile(r'\(if\)'), '(Cif)'),
|
|
(re.compile(r'\binstanceof if\b'), 'instanceof Cif'),
|
|
(re.compile(r'\bif\.class\b'), 'Cif.class'),
|
|
(re.compile(r'\bif\s*\[\]'), 'Cif[]'),
|
|
(re.compile(r'(?<![\w.$])if(?=\s+[A-Za-z_$][\w$]*\s*[=,);:])'), 'Cif'),
|
|
(re.compile(r'(?<=[<,\s])if(?=\s*>)'), 'Cif'),
|
|
]
|
|
def token_fix(text):
|
|
for rx, rep in RULES:
|
|
text = rx.sub(rep, text)
|
|
return text
|
|
|
|
def fix_strings(text):
|
|
out = []; in_s = False; in_c = False; esc = False; n = 0
|
|
for ch in text:
|
|
if in_s:
|
|
if esc: out.append(ch); esc = False; continue
|
|
if ch == '\\': out.append(ch); esc = True; continue
|
|
if ch == '"': out.append(ch); in_s = False; continue
|
|
if ch == '\n': out.append('\\n'); n += 1; continue
|
|
if ch == '\r': out.append('\\r'); n += 1; continue
|
|
if ord(ch) < 0x20 and ch != '\t': out.append('\\u%04x' % ord(ch)); n += 1; continue
|
|
out.append(ch); continue
|
|
if in_c:
|
|
if esc: out.append(ch); esc = False; continue
|
|
if ch == '\\': out.append(ch); esc = True; continue
|
|
if ch == "'": out.append(ch); in_c = False; continue
|
|
out.append(ch); continue
|
|
if ch == '"': in_s = True; out.append(ch); continue
|
|
if ch == "'": in_c = True; out.append(ch); continue
|
|
out.append(ch)
|
|
return ''.join(out), n
|
|
|
|
TOKEN = re.compile(r'\d+L?|>>>|<<|>>|[+\-*/%&|^~()]')
|
|
def toks(s):
|
|
out = []; i = 0
|
|
while i < len(s):
|
|
if s[i].isspace(): i += 1; continue
|
|
m = TOKEN.match(s, i)
|
|
if not m: return None
|
|
out.append(m.group()); i = m.end()
|
|
return out
|
|
|
|
def wrap(v, long_):
|
|
bits = 64 if long_ else 32
|
|
v &= (1 << bits) - 1
|
|
if v >= (1 << (bits - 1)): v -= (1 << bits)
|
|
return v
|
|
|
|
def evaluate(ts):
|
|
prec = {'|':1,'^':2,'&':3,'<<':4,'>>':4,'>>>':4,'+':5,'-':5,'*':6,'/':6,'%':6}
|
|
right = {'<<','>>','>>>','-','/','%'}
|
|
is_op = lambda x: x in prec
|
|
out = []; ops = []; prev = None
|
|
for t in ts:
|
|
if t == '(':
|
|
ops.append(t); prev = '('; continue
|
|
if t == ')':
|
|
while ops and ops[-1] != '(': out.append(ops.pop())
|
|
if not ops: return None
|
|
ops.pop()
|
|
prev = ')'; continue
|
|
if t == '~':
|
|
ops.append('u~'); prev = t; continue
|
|
if is_op(t):
|
|
if t in '+-' and prev in (None, '(', '+', '-', '*', '/', '%', '&', '|', '^', '<<', '>>', '>>>'):
|
|
if t == '-': ops.append('u-')
|
|
prev = t; continue
|
|
while ops and ops[-1] != '(':
|
|
top = ops[-1]
|
|
pt = 7 if top in ('u-', 'u~') else prec[top]
|
|
if pt > prec[t] or (pt == prec[t] and t not in right):
|
|
out.append(ops.pop())
|
|
else: break
|
|
ops.append(t); prev = t; continue
|
|
out.append(('n', t)); prev = 'n'
|
|
while ops:
|
|
o = ops.pop()
|
|
if o == '(': return None
|
|
out.append(o)
|
|
st = []
|
|
long_ = any(isinstance(x, tuple) and x[1].endswith('L') for x in out)
|
|
for x in out:
|
|
if isinstance(x, tuple):
|
|
st.append(int(x[1][:-1] if x[1].endswith('L') else x[1])); continue
|
|
if x == 'u-':
|
|
if not st: return None
|
|
st.append(wrap(-st.pop(), long_)); continue
|
|
if x == 'u~':
|
|
if not st: return None
|
|
st.append(wrap(~st.pop(), long_)); continue
|
|
if len(st) < 2: return None
|
|
b = st.pop(); a = st.pop()
|
|
try:
|
|
if x == '+': r = a + b
|
|
elif x == '-': r = a - b
|
|
elif x == '*': r = a * b
|
|
elif x == '/':
|
|
if b == 0: return None
|
|
r = int(a / b)
|
|
elif x == '%':
|
|
if b == 0: return None
|
|
r = a - int(a / b) * b
|
|
elif x == '&': r = a & b
|
|
elif x == '|': r = a | b
|
|
elif x == '^': r = a ^ b
|
|
elif x == '<<': r = a << (b & 63)
|
|
elif x == '>>': r = a >> (b & 63)
|
|
elif x == '>>>':
|
|
bits = 64 if long_ else 32
|
|
r = (a & ((1 << bits) - 1)) >> (b & 63)
|
|
else: return None
|
|
except Exception:
|
|
return None
|
|
st.append(wrap(r, long_))
|
|
if len(st) != 1: return None
|
|
return st[0]
|
|
|
|
CODE = re.compile(r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])\'|//[^\n]*|/\*.*?\*/', re.S)
|
|
NUMRUN = re.compile(r'(?<![\w.])(?:[0-9(][0-9+\-*/%&|^~()<>\s]*)(?![\w.])')
|
|
|
|
def flatten_file(text):
|
|
parts = []; last = 0
|
|
for m in CODE.finditer(text):
|
|
parts.append(('code', text[last:m.start()])); parts.append(('skip', m.group())); last = m.end()
|
|
parts.append(('code', text[last:]))
|
|
total = 0; new_parts = []
|
|
for kind, seg in parts:
|
|
if kind == 'skip':
|
|
new_parts.append(seg); continue
|
|
def repl(m):
|
|
nonlocal total
|
|
s = m.group()
|
|
if not re.search(r'[+\-*/%&|^~]', s): return s
|
|
if re.search(r'[^0-9+\-*/%&|^~()<>\s]', s): return s
|
|
core = s; k = 0
|
|
while core.startswith('(') and core.endswith(')'):
|
|
inner = core[1:-1]
|
|
bal = 0; ok = True
|
|
for ch in inner:
|
|
if ch == '(': bal += 1
|
|
elif ch == ')':
|
|
bal -= 1
|
|
if bal < 0: ok = False; break
|
|
if not ok or bal != 0: break
|
|
core = inner; k += 1
|
|
ts = toks(core)
|
|
if not ts: return s
|
|
if not any(t in ('+','-','*','/','%','&','|','^','<<','>>','>>>') for t in ts): return s
|
|
if any(t in ('<','>','==','!=') for t in ts): return s
|
|
v = evaluate(ts)
|
|
if v is None: return s
|
|
total += 1
|
|
return '(' * k + str(v) + ')' * k
|
|
new_parts.append(NUMRUN.sub(repl, seg))
|
|
return ''.join(new_parts), total
|
|
|
|
TYPE_SW = re.compile(r'SwitchBootstraps\.typeSwitch<\s*"typeSwitch"\s*,([^>]*)>\s*\(\s*([^,]+),\s*([^)]+?)\s*\)', re.S)
|
|
def fix_typeswitch(t):
|
|
def rep(m):
|
|
labels = [x.strip() for x in m.group(1).split(',')]
|
|
return 'typeSwitch(%s, %s, %s)' % (m.group(2).strip(), m.group(3).strip(),
|
|
', '.join(l + '.class' for l in labels))
|
|
t, n = TYPE_SW.subn(rep, t)
|
|
if n:
|
|
helper = '''
|
|
private static int typeSwitch(Object selector, int state, Class<?>... labels) {
|
|
if (state < 0 || state > labels.length) {
|
|
throw new IndexOutOfBoundsException("Index " + state + " out of bounds for length " + (labels.length + 1));
|
|
}
|
|
for (int i = state; i < labels.length; i++) {
|
|
if (labels[i].isInstance(selector)) {
|
|
return i;
|
|
}
|
|
}
|
|
return labels.length;
|
|
}
|
|
}
|
|
'''
|
|
t = t.rstrip()[:-1].rstrip() + '\n' + helper
|
|
return t, n
|
|
|
|
def fix_this0(text):
|
|
changed = False
|
|
if 'this$0' in text:
|
|
m = re.search(r'\n\s*(?:private|public|protected|final|static|\s)*\s*(?:class|enum|interface)\s+\w+[^{\n]*\{', text)
|
|
if m and not re.search(r'\b(this\$0|private\s+[\w.\[\]]+\s+this\$0)\s*;', text.replace('this.this$0', '')):
|
|
am = re.search(r'this\.this\$0 = (\w+);', text)
|
|
if am:
|
|
pname = am.group(1); ctor = None
|
|
for cm in re.finditer(r'(?:private|public|protected)?\s*(\w+)\s*\(([^)]*)\)\s*\{', text):
|
|
for p in [x.strip() for x in cm.group(2).split(',')]:
|
|
parts = p.split()
|
|
if len(parts) >= 2 and parts[-1] == pname:
|
|
ctor = parts[-2]; break
|
|
if ctor: break
|
|
if ctor:
|
|
text = text[:m.end()] + f'\n private {ctor} this$0;\n' + text[m.end():]
|
|
changed = True
|
|
text, n = re.subn(r';\n(\s*)super\(\);\n', ';\n', text)
|
|
if n: changed = True
|
|
return text, changed
|
|
|
|
def main():
|
|
z = zipfile.ZipFile(f'{EVO}/mod/evovis-dec.jar')
|
|
jar = {n[:-6] for n in z.namelist() if n.endswith('.class') and not n.startswith('META-INF/')}
|
|
vfset = set()
|
|
for dp, _, fns in os.walk(VF):
|
|
for fn in fns:
|
|
if fn.endswith('.java'):
|
|
vfset.add(os.path.relpath(os.path.join(dp, fn), VF)[:-5].replace(os.sep, '/'))
|
|
missing = sorted(jar - vfset)
|
|
if os.path.isdir(JAVA): shutil.rmtree(JAVA)
|
|
os.makedirs(f'{JAVA}/defpackage', exist_ok=True)
|
|
st = {'flat': 0, 'this0': 0, 'tw': 0, 'str': 0}
|
|
for fn in sorted(os.listdir(VF)):
|
|
if not fn.endswith('.java') or fn == 'if.java': continue
|
|
t = token_fix(open(os.path.join(VF, fn), encoding='utf-8').read())
|
|
if not t.lstrip().startswith('package '): t = 'package defpackage;\n\n' + t.lstrip('\n')
|
|
open(f'{JAVA}/defpackage/{fn}', 'w', encoding='utf-8').write(t)
|
|
for pkg in ['a', 'b', 'evo']:
|
|
s = os.path.join(VF, pkg); d = os.path.join(JAVA, pkg)
|
|
for dp, _, fns in os.walk(s):
|
|
rel = os.path.relpath(dp, s); t = os.path.join(d, rel) if rel != '.' else d
|
|
os.makedirs(t, exist_ok=True)
|
|
for fn in fns:
|
|
if not fn.endswith('.java'): continue
|
|
t_ = token_fix(open(os.path.join(dp, fn), encoding='utf-8').read())
|
|
m = re.search(r'^package [\w.]+;\s*\n', t_)
|
|
if m and 'import defpackage.' not in t_:
|
|
t_ = t_[:m.end()] + 'import defpackage.*;\n' + t_[m.end():]
|
|
elif not m:
|
|
t_ = 'import defpackage.*;\n' + t_
|
|
open(os.path.join(t, fn), 'w', encoding='utf-8').write(t_)
|
|
n_add = 0
|
|
for name in list(missing) + ['Cif', 'Cdo']:
|
|
src = os.path.join(JADX, name + '.java')
|
|
if not os.path.exists(src): continue
|
|
shutil.copy(src, os.path.join(JAVA, 'defpackage', name + '.java')); n_add += 1
|
|
for cls in ['cp', 'akj', 'alr']:
|
|
body = open(f'{EVO}/patch/src/defpackage/{cls}.java', encoding='utf-8').read()
|
|
if not body.lstrip().startswith('package '): body = 'package defpackage;\n\n' + body.lstrip('\n')
|
|
open(f'{JAVA}/defpackage/{cls}.java', 'w', encoding='utf-8').write(body)
|
|
for dp, _, fns in os.walk(JAVA):
|
|
for fn in fns:
|
|
if not fn.endswith('.java'): continue
|
|
p = os.path.join(dp, fn); t = open(p, encoding='utf-8').read()
|
|
t = re.sub(r'^import lombok\.Generated;\n', '', t, flags=re.M)
|
|
t = re.sub(r'[ \t]*@Generated\s*\n', '', t)
|
|
if fn == 'wb.java':
|
|
t, n = fix_typeswitch(t); st['tw'] += n
|
|
if fn == 'bz.java':
|
|
t = t.replace('\n@b\n', '\n')
|
|
if fn == 'zm.java':
|
|
t = re.sub(r'\?\? IsEnabled = [^;]+;', 'boolean IsEnabled = false;', t)
|
|
t = re.sub(r'\?\? flag = [^;]+;', 'boolean flag = false;', t)
|
|
if 'this$0' in t:
|
|
t, ch = fix_this0(t)
|
|
if ch: st['this0'] += 1
|
|
t, n1 = flatten_file(t); st['flat'] += n1
|
|
if fn == 'wq.java':
|
|
t = re.sub(r'(?<![\w.])rx(?![\w])', 'defpackage.rx', t)
|
|
t, n2 = fix_strings(t); st['str'] += n2
|
|
if t != open(p, encoding='utf-8').read():
|
|
open(p, 'w', encoding='utf-8').write(t)
|
|
if os.path.exists(f'{JAVA}/defpackage/b.java'): os.remove(f'{JAVA}/defpackage/b.java')
|
|
cnt = sum(len(f) for _, _, f in os.walk(JAVA))
|
|
print('missing-джадж:', n_add, 'файлов:', cnt, st)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|