Download app.py from Cnass/Renpy: direct link, hf CLI and curl.
- Browser
- Download file 9.91 kB
-
https://huggingface.co/spaces/Cnass/Renpy/resolve/main/app.py
- Command line
-
hf download hf://spaces/Cnass/Renpy/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Cnass/Renpy/resolve/main/app.py
9.91 kB
| import os | |
| import subprocess | |
| import shutil | |
| import base64 | |
| import struct | |
| import zlib | |
| import pickle | |
| import sys | |
| import traceback | |
| from flask import Flask, request, jsonify | |
| from decompiler import magic, renpycompat | |
| sys.setrecursionlimit(50000) | |
| app = Flask(__name__) | |
| # --- 1. YARDIMCI VE ZIRHLI ÇEVİRİ FONKSİYONLARI --- | |
| def unescape_wp_string(s): | |
| if not isinstance(s, str): return s | |
| s = s.replace('\\n', '\n') | |
| s = s.replace('\\"', '"') | |
| s = s.replace('\\ ', ' ') | |
| s = s.replace('\\\\', '\\') | |
| return s.replace('\r', '') | |
| def apply_translation(text, translations): | |
| """AST Zırhı: Sadece birebir eşleşen metinleri değiştirir.""" | |
| if not isinstance(text, (str, bytes)): return text | |
| is_bytes = isinstance(text, bytes) | |
| text_str = text.decode('utf-8', 'ignore') if is_bytes else text | |
| for k, v in translations.items(): | |
| clean_k = k[1:-1] if len(k) >= 2 and k[0] in ('"', "'") and k[-1] == k[0] else k | |
| clean_v = v[1:-1] if len(v) >= 2 and v[0] in ('"', "'") and v[-1] == v[0] else v | |
| # Görünmez \r karakterlerini yoksayarak eşleşme oranını artır | |
| if text_str.replace('\r', '') == clean_k.replace('\r', ''): | |
| return clean_v.encode('utf-8') if is_bytes else clean_v | |
| return text | |
| # --- 2. AST MOTORU --- | |
| def patch_ast(obj, translations, visited=None): | |
| if visited is None: visited = set() | |
| # FIX: str ve bytes'ı burada durdurmuyoruz; çağıran zaten kontrol ediyor | |
| if obj is None or isinstance(obj, (int, float, bool)): return | |
| obj_id = id(obj) | |
| if obj_id in visited: return | |
| visited.add(obj_id) | |
| if isinstance(obj, (str, bytes)): | |
| # Tek başına gelen string'lere yapacak bir şey yok (referansı değiştiremeyiz) | |
| return | |
| if isinstance(obj, list): | |
| for i in range(len(obj)): | |
| if isinstance(obj[i], (str, bytes)): | |
| obj[i] = apply_translation(obj[i], translations) | |
| else: | |
| patch_ast(obj[i], translations, visited) | |
| elif isinstance(obj, tuple): | |
| for item in obj: | |
| patch_ast(item, translations, visited) | |
| elif isinstance(obj, dict): | |
| for k, v in list(obj.items()): | |
| if isinstance(v, (str, bytes)): | |
| obj[k] = apply_translation(v, translations) | |
| else: | |
| patch_ast(v, translations, visited) | |
| elif hasattr(obj, '__dict__'): | |
| class_name = type(obj).__name__ | |
| # --- Ren'Py Sınıflarına Göre Hedefli Yama --- | |
| # FIX: 'Translate' bloğu (translate turkish xxx: ... ) işleniyor | |
| # Bu sınıf, 'block' listesi içinde Say objelerini barındırır | |
| if class_name == 'Translate' and hasattr(obj, 'block'): | |
| patch_ast(obj.block, translations, visited) | |
| # identifier, language gibi meta alanları atla, devam et | |
| TRANSLATE_SKIP = {'old', 'language', 'identifier', 'filename', 'name', 'label', 'parameters', 'block'} | |
| for k, v in list(obj.__dict__.items()): | |
| if k in TRANSLATE_SKIP: continue | |
| if isinstance(v, (str, bytes)): | |
| if len(v if isinstance(v, str) else v.decode('utf-8', 'ignore')) >= 2: | |
| setattr(obj, k, apply_translation(v, translations)) | |
| else: | |
| patch_ast(v, translations, visited) | |
| return # Translate için işlem tamam | |
| if class_name in ('Say', 'TranslateSay') and hasattr(obj, 'what'): | |
| obj.what = apply_translation(obj.what, translations) | |
| elif class_name in ('Menu', 'TranslateMenu') and hasattr(obj, 'items'): | |
| new_items = [] | |
| for item in obj.items: | |
| label = apply_translation(item[0], translations) | |
| new_items.append((label, item[1], item[2]) if len(item) >= 3 else (label,) + item[1:]) | |
| obj.items = new_items | |
| elif class_name == 'TranslateString' and hasattr(obj, 'new'): | |
| obj.new = apply_translation(obj.new, translations) | |
| FORBIDDEN_KEYS = {'old', 'language', 'identifier', 'filename', 'name', 'label', 'parameters'} | |
| for k, v in list(obj.__dict__.items()): | |
| if k in FORBIDDEN_KEYS: continue | |
| if class_name in ('Say', 'TranslateSay') and k == 'what': continue | |
| if class_name in ('Menu', 'TranslateMenu') and k == 'items': continue | |
| if class_name == 'TranslateString' and k == 'new': continue | |
| if isinstance(v, (str, bytes)): | |
| text_len = len(v) if isinstance(v, str) else len(v.decode('utf-8', 'ignore')) | |
| if text_len >= 2: | |
| setattr(obj, k, apply_translation(v, translations)) | |
| else: | |
| patch_ast(v, translations, visited) | |
| # --- 3. ENDPOINT'LER --- | |
| def decompile_rpyc(): | |
| req_data = request.json | |
| if not req_data or 'filedata' not in req_data: | |
| return jsonify({"error": "Dosya verisi bulunamadı."}), 400 | |
| try: | |
| file_data = base64.b64decode(req_data['filedata']) | |
| filename = req_data.get('filename', 'temp.rpyc') | |
| rpy_filename = filename.replace('.rpyc', '.rpy') | |
| with open(filename, 'wb') as f: | |
| f.write(file_data) | |
| subprocess.run(['python', 'unrpyc.py', filename], check=True) | |
| with open(rpy_filename, 'r', encoding='utf-8') as f: | |
| rpy_content = f.read() | |
| if os.path.exists(filename): os.remove(filename) | |
| if os.path.exists(rpy_filename): os.remove(rpy_filename) | |
| return jsonify({"success": True, "rpy_content": rpy_content}) | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| def patch_endpoint(): | |
| # FIX: Değişken adı 'req_data' olarak değiştirildi — loop içindeki 'data' ile çakışmayı önler | |
| req_data = request.get_json() | |
| if not req_data or 'filedata' not in req_data or 'translations' not in req_data: | |
| return jsonify({"error": "Eksik veri gönderildi."}), 400 | |
| try: | |
| file_bytes = base64.b64decode(req_data['filedata']) | |
| raw_translations = req_data['translations'] | |
| clean_translations = {} | |
| for k, v in raw_translations.items(): | |
| clean_k = unescape_wp_string(k) | |
| clean_v = unescape_wp_string(v) | |
| if clean_k.strip() and clean_k != clean_v: | |
| clean_translations[clean_k] = clean_v | |
| if file_bytes.startswith(b"RENPY RPC2"): | |
| position = 10 | |
| chunks = [] | |
| while True: | |
| slot, start, length = struct.unpack("III", file_bytes[position:position+12]) | |
| position += 12 | |
| if slot == 0: break | |
| chunks.append({"slot": slot, "start": start, "length": length}) | |
| payloads = {} | |
| for c in chunks: | |
| chunk_data = file_bytes[c["start"] : c["start"] + c["length"]] | |
| if c["slot"] == 1: | |
| raw_pickle = zlib.decompress(chunk_data) | |
| orig_proto = 2 | |
| if len(raw_pickle) >= 2 and raw_pickle[0] == 0x80: | |
| orig_proto = raw_pickle[1] | |
| ast_tree = renpycompat.pickle_loads(raw_pickle) | |
| patch_ast(ast_tree, clean_translations) | |
| payloads[1] = zlib.compress(pickle.dumps(ast_tree, protocol=orig_proto)) | |
| elif c["slot"] == 2: | |
| try: | |
| raw_source = zlib.decompress(chunk_data).decode('utf-8') | |
| for k, v in clean_translations.items(): | |
| ck = k[1:-1] if len(k) >= 2 and k[0] in ('"', "'") and k[-1] == k[0] else k | |
| cv = v[1:-1] if len(v) >= 2 and v[0] in ('"', "'") and v[-1] == v[0] else v | |
| raw_source = raw_source.replace(f'"{ck}"', f'"{cv}"') | |
| raw_source = raw_source.replace(f"'{ck}'", f"'{cv}'") | |
| # FIX: Kaçış karakterli tırnak varyantlarını da yakala | |
| raw_source = raw_source.replace(f'\\"{ ck}\\"', f'\\"{cv}\\"') | |
| payloads[2] = zlib.compress(raw_source.encode('utf-8')) | |
| except Exception: | |
| payloads[2] = chunk_data | |
| else: | |
| payloads[c["slot"]] = chunk_data | |
| new_dir = bytearray() | |
| current_offset = 10 + (len(chunks) + 1) * 12 | |
| for c in chunks: | |
| # FIX: 'payload_data' kullanıldı — üstteki req_data ile karışmaz | |
| payload_data = payloads[c["slot"]] | |
| new_dir.extend(struct.pack("III", c["slot"], current_offset, len(payload_data))) | |
| current_offset += len(payload_data) | |
| new_dir.extend(struct.pack("III", 0, 0, 0)) | |
| new_file = bytearray(b"RENPY RPC2") | |
| new_file.extend(new_dir) | |
| for c in chunks: | |
| new_file.extend(payloads[c["slot"]]) | |
| new_rpyc_bytes = bytes(new_file) | |
| else: | |
| raw_pickle = zlib.decompress(file_bytes) | |
| orig_proto = 2 | |
| if len(raw_pickle) >= 2 and raw_pickle[0] == 0x80: | |
| orig_proto = raw_pickle[1] | |
| ast_tree = renpycompat.pickle_loads(raw_pickle) | |
| patch_ast(ast_tree, clean_translations) | |
| new_rpyc_bytes = zlib.compress(pickle.dumps(ast_tree, protocol=orig_proto)) | |
| return jsonify({ | |
| 'success': True, | |
| 'patched_file': base64.b64encode(new_rpyc_bytes).decode('utf-8') | |
| }) | |
| except Exception as e: | |
| error_msg = traceback.format_exc() | |
| print("PATCH ERROR:", error_msg) | |
| return jsonify({'error': str(e)}), 500 | |
| if __name__ == '__main__': | |
| port = int(os.environ.get("PORT", 7860)) | |
| app.run(host='0.0.0.0', port=port) |