import os import zipfile import tempfile import json import re import subprocess import time import uuid import UnityPy from UnityPy.enums import CompressionFlags from fastapi import FastAPI, UploadFile, File, BackgroundTasks from fastapi.responses import FileResponse, JSONResponse from fastapi.middleware.cors import CORSMiddleware from typing import Optional app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) jobs = {} def process_repack(job_id: str, asset_path: str, zip_path: str, original_filename: str): try: jobs[job_id] = {"status": "processing", "progress": 5, "message": "Oyun dosyaları çözümleniyor..."} env = UnityPy.load(asset_path) filename_to_translations = {} with zipfile.ZipFile(zip_path, 'r') as zipf: for file_info in zipf.infolist(): filename = file_info.filename if "__MACOSX" in filename or filename.split("/")[-1].startswith("._"): continue if filename.endswith(".json"): try: content = zipf.read(filename) filename_to_translations[filename.split("/")[-1]] = json.loads(content.decode('utf-8')) except json.JSONDecodeError: pass total_objects = len(env.objects) processed = 0 start_time = time.time() for obj in env.objects: processed += 1 if processed % 50 == 0: elapsed = time.time() - start_time rate = processed / elapsed if elapsed > 0 else 1 remaining_seconds = int((total_objects - processed) / rate) prog = 5 + int((processed / total_objects) * 45) jobs[job_id].update({ "progress": prog, "message": f"Diyaloglar yamalanıyor... (Kalan Süre: ~{remaining_seconds} sn)" }) if obj.type.name in ["TextAsset", "MonoBehaviour"]: asset_name = getattr(obj.assets_file, 'name', 'assets') asset_name = "".join([c if c.isalnum() else "_" for c in asset_name]) obj_name = f"{asset_name}_{obj.type.name}_{obj.path_id}" target_filename = f"{obj_name}_raw.json" if target_filename in filename_to_translations: translations = filename_to_translations[target_filename] if obj.type.name == "MonoBehaviour": raw_data = obj.get_raw_data() patched_data = bytearray(raw_data) # ... (process_repack içindeki for döngüsünde) ... for orig_str, trans_str in translations.items(): if orig_str == trans_str: continue orig_bytes = orig_str.encode('utf-8') trans_bytes = trans_str.encode('utf-8') # KRİTİK DEĞİŞİKLİK: 4 baytlık hizalamayı (alignment) zorla yapıyoruz # Unity, dosyaları 4'ün katı olan bloklarda okur. # Metni değiştirdiğimizde bu bloğu bozarız. # Aşağıdaki kod, metni 4 bayta tamamlaya(padding) zorlar. orig_pad = (4 - (len(orig_bytes) % 4)) % 4 orig_block = len(orig_bytes).to_bytes(4, 'little') + orig_bytes + (b'\x00' * orig_pad) trans_pad = (4 - (len(trans_bytes) % 4)) % 4 trans_block = len(trans_bytes).to_bytes(4, 'little') + trans_bytes + (b'\x00' * trans_pad) # Bloğu sadece ve sadece %100 eşleşen bayt dizisinde değiştir patched_data = patched_data.replace(orig_block, trans_block) obj.set_raw_data(bytes(patched_data)) jobs[job_id].update({ "progress": 60, "message": "Sıkıştırma algoritmaları deneniyor... (Sunucu hızına göre 1-3 dakika sürebilir)" }) output_path = os.path.join(tempfile.gettempdir(), f"repacked_{job_id}_{original_filename}") compression_error = "" try: # 1. Deneme: Modern UnityPy String Formatı (LZMA) packed_data = env.file.save(packer="lzma") except Exception as e1: try: # 2. Deneme: Daha hızlı olan LZ4 Formatı packed_data = env.file.save(packer="lz4") except Exception as e2: try: # 3. Deneme: Eski Sürüm UnityPy Enum Formatı packed_data = env.file.save(packer=CompressionFlags.LZMA) except Exception as e3: # HİÇBİRİ İŞE YARAMAZSA HATAYI EKRANA YANSIT compression_error = f"LZMA_Str: {str(e1)} | LZ4_Str: {str(e2)} | Enum: {str(e3)}" packed_data = env.file.save() with open(output_path, "wb") as f: f.write(packed_data) if compression_error: final_msg = f"Dosya hazırlandı AMA Sıkıştırılamadı! Hata Logu: {compression_error}" else: final_msg = "İşlem %100 Tamamlandı ve Orijinal Boyuta Sıkıştırıldı! Dosya İndiriliyor..." jobs[job_id].update({ "status": "completed", "progress": 100, "message": final_msg, "file_path": output_path, "filename": original_filename }) except Exception as e: jobs[job_id].update({"status": "error", "message": f"Kritik Hata: {str(e)}"}) # --------- API UÇ NOKTALARI (ENDPOINTS) --------- @app.post("/generate_dll") async def generate_dll(binary_file: UploadFile = File(...), metadata_file: UploadFile = File(...)): temp_dir = tempfile.mkdtemp() binary_path = os.path.join(temp_dir, binary_file.filename) metadata_path = os.path.join(temp_dir, metadata_file.filename) with open(binary_path, "wb") as f: f.write(await binary_file.read()) with open(metadata_path, "wb") as f: f.write(await metadata_file.read()) output_dir = os.path.join(temp_dir, "output") os.makedirs(output_dir, exist_ok=True) subprocess.run(["/app/dumper/Il2CppDumper", binary_path, metadata_path, output_dir], capture_output=True) zip_path = os.path.join(temp_dir, "DummyDlls.zip") with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf: for root, _, files in os.walk(output_dir): for file in files: if file.endswith(".dll"): zipf.write(os.path.join(root, file), arcname=file) return FileResponse(zip_path, media_type="application/zip", filename="DummyDlls.zip") @app.post("/extract") async def extract_dialogues(file: UploadFile = File(...), assembly_file: Optional[UploadFile] = File(None)): temp_dir = tempfile.mkdtemp() asset_path = os.path.join(temp_dir, file.filename) with open(asset_path, "wb") as f: f.write(await file.read()) env = UnityPy.load(asset_path) zip_filename = os.path.join(temp_dir, f"{file.filename}_dialogues.zip") with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf: for obj in env.objects: if obj.type.name in ["TextAsset", "MonoBehaviour"]: asset_name = getattr(obj.assets_file, 'name', 'assets') asset_name = "".join([c if c.isalnum() else "_" for c in asset_name]) obj_name = f"{asset_name}_{obj.type.name}_{obj.path_id}" if obj.type.name == "MonoBehaviour": raw_data = obj.get_raw_data() found_strings = {} i = 28 while i <= len(raw_data) - 4: length = int.from_bytes(raw_data[i:i+4], 'little') if 2 <= length <= 5000 and (i + 4 + length) <= len(raw_data): str_bytes = raw_data[i+4 : i+4+length] pad_len = (4 - (length % 4)) % 4 if (i + 4 + length + pad_len) <= len(raw_data): padding = raw_data[i+4+length : i+4+length+pad_len] if all(b == 0 for b in padding): try: text = str_bytes.decode('utf-8') if len(text.strip()) > 1 and any(c.isalpha() for c in text): found_strings[text] = text i += 4 + length + pad_len continue except UnicodeDecodeError: pass i += 4 if found_strings: zipf.writestr(f"{obj_name}_raw.json", json.dumps(found_strings, indent=4, ensure_ascii=False).encode('utf-8')) elif obj.type.name == "TextAsset": try: content = obj.read().text.encode('utf-8') zipf.writestr(f"{obj_name}.txt", content) except: pass return FileResponse(zip_filename, media_type="application/zip", filename=f"{file.filename}_dialogues.zip") @app.post("/repack_async") async def repack_async(background_tasks: BackgroundTasks, asset_file: UploadFile = File(...), mod_zip: UploadFile = File(...)): job_id = str(uuid.uuid4()) temp_dir = tempfile.mkdtemp() asset_path = os.path.join(temp_dir, asset_file.filename) zip_path = os.path.join(temp_dir, mod_zip.filename) with open(asset_path, "wb") as f: f.write(await asset_file.read()) with open(zip_path, "wb") as f: f.write(await mod_zip.read()) background_tasks.add_task(process_repack, job_id, asset_path, zip_path, asset_file.filename) jobs[job_id] = {"status": "queued", "progress": 0, "message": "İşlem sıraya alındı, başlıyor..."} return {"job_id": job_id} @app.get("/status/{job_id}") async def get_status(job_id: str): if job_id not in jobs: return JSONResponse(status_code=404, content={"error": "Görev bulunamadı"}) return jobs[job_id] @app.get("/download/{job_id}") async def download_file(job_id: str): job = jobs.get(job_id) if not job or job.get("status") != "completed": return JSONResponse(status_code=400, content={"error": "Dosya hazır değil"}) return FileResponse(job["file_path"], media_type="application/octet-stream", filename=job["filename"]) @app.post("/check_errors") async def check_errors(mod_zip: UploadFile = File(...)): temp_dir = tempfile.mkdtemp() zip_path = os.path.join(temp_dir, mod_zip.filename) with open(zip_path, "wb") as f: f.write(await mod_zip.read()) report_lines = ["=== SİSTEM KODU ÇEVİRİSİ HATA TARAMA RAPORU ===\n"] report_lines.append("Aşağıdaki listede bulunan çeviriler, oyunun çökmesine sebep olan 'Sistem Kodları' olabilir.") report_lines.append("Lütfen bu dosyalara girip, bu kelimeleri tekrar orijinal (İngilizce) haline geri döndürün.\n") error_count = 0 with zipfile.ZipFile(zip_path, 'r') as zipf: for file_info in zipf.infolist(): filename = file_info.filename if "__MACOSX" in filename or filename.split("/")[-1].startswith("._"): continue if filename.endswith(".json"): try: content = zipf.read(filename) translations = json.loads(content.decode('utf-8')) file_suspects = [] for orig, trans in translations.items(): if orig != trans: # Sadece çevrilmiş olanları kontrol et is_system = False # 1. Kural: Cümlede hiç boşluk yoksa büyük ihtimalle koddur (Örn: PlayerSpawnPoint) if " " not in orig and len(orig) > 2: is_system = True # 2. Kural: Alt tire içeriyorsa koddur (Örn: bg_sky_01) if "_" in orig: is_system = True # 3. Kural: Dosya yolu içeriyorsa koddur (Örn: Assets/Sprites/hero) if "/" in orig or "\\" in orig: is_system = True if is_system: file_suspects.append(f" [RİSKLİ] Orijinal: '{orig}'\n Çevirin: '{trans}'\n") if file_suspects: report_lines.append(f"--------------------------------------------------") report_lines.append(f"DOSYA: {filename}") report_lines.append(f"--------------------------------------------------") report_lines.extend(file_suspects) error_count += len(file_suspects) except: pass if error_count == 0: report_lines.append("\nHarika! Görünürde hiçbir sistem kodunu çevirmemişsin. Çökme sebebi başka olabilir.") else: report_lines.append(f"\n==================================================") report_lines.append(f"TOPLAM BULUNAN RİSKLİ ÇEVİRİ: {error_count} ADET") report_lines.append(f"==================================================") report_path = os.path.join(temp_dir, "hata_raporu.txt") with open(report_path, "w", encoding="utf-8") as f: f.write("\n".join(report_lines)) return FileResponse(report_path, media_type="text/plain", filename="hata_raporu.txt")