"""Сборка 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'(?)'), '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'(?\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'(?