Cnass/UnityPy
0
1import os2import zipfile3import tempfile4import json5import re6import subprocess7import time8import uuid9import UnityPy10from UnityPy.enums import CompressionFlags11from fastapi import FastAPI, UploadFile, File, BackgroundTasks12from fastapi.responses import FileResponse, JSONResponse13from fastapi.middleware.cors import CORSMiddleware14from typing import Optional15 16app = FastAPI()17 18app.add_middleware(19 CORSMiddleware,20 allow_origins=["*"], 21 allow_credentials=True,22 allow_methods=["*"],23 allow_headers=["*"],24)25 26jobs = {}27 28def process_repack(job_id: str, asset_path: str, zip_path: str, original_filename: str):29 try:30 jobs[job_id] = {"status": "processing", "progress": 5, "message": "Oyun dosyaları çözümleniyor..."}31 env = UnityPy.load(asset_path)32 33 filename_to_translations = {}34 with zipfile.ZipFile(zip_path, 'r') as zipf:35 for file_info in zipf.infolist():36 filename = file_info.filename37 if "__MACOSX" in filename or filename.split("/")[-1].startswith("._"): continue38 if filename.endswith(".json"):39 try:40 content = zipf.read(filename)41 filename_to_translations[filename.split("/")[-1]] = json.loads(content.decode('utf-8'))42 except json.JSONDecodeError: pass43 44 total_objects = len(env.objects)45 processed = 046 start_time = time.time()47 48 for obj in env.objects:49 processed += 150 if processed % 50 == 0:51 elapsed = time.time() - start_time52 rate = processed / elapsed if elapsed > 0 else 153 remaining_seconds = int((total_objects - processed) / rate)54 prog = 5 + int((processed / total_objects) * 45)55 56 jobs[job_id].update({57 "progress": prog,58 "message": f"Diyaloglar yamalanıyor... (Kalan Süre: ~{remaining_seconds} sn)"59 })60 61 if obj.type.name in ["TextAsset", "MonoBehaviour"]:62 asset_name = getattr(obj.assets_file, 'name', 'assets')63 asset_name = "".join([c if c.isalnum() else "_" for c in asset_name])64 obj_name = f"{asset_name}_{obj.type.name}_{obj.path_id}"65 target_filename = f"{obj_name}_raw.json"66 67 if target_filename in filename_to_translations:68 translations = filename_to_translations[target_filename]69 if obj.type.name == "MonoBehaviour":70 raw_data = obj.get_raw_data()71 patched_data = bytearray(raw_data)72 # ... (process_repack içindeki for döngüsünde) ...73 for orig_str, trans_str in translations.items():74 if orig_str == trans_str: continue75 76 orig_bytes = orig_str.encode('utf-8')77 trans_bytes = trans_str.encode('utf-8')78 79 # KRİTİK DEĞİŞİKLİK: 4 baytlık hizalamayı (alignment) zorla yapıyoruz80 # Unity, dosyaları 4'ün katı olan bloklarda okur. 81 # Metni değiştirdiğimizde bu bloğu bozarız. 82 # Aşağıdaki kod, metni 4 bayta tamamlaya(padding) zorlar.83 84 orig_pad = (4 - (len(orig_bytes) % 4)) % 485 orig_block = len(orig_bytes).to_bytes(4, 'little') + orig_bytes + (b'\x00' * orig_pad)86 87 trans_pad = (4 - (len(trans_bytes) % 4)) % 488 trans_block = len(trans_bytes).to_bytes(4, 'little') + trans_bytes + (b'\x00' * trans_pad)89 90 # Bloğu sadece ve sadece %100 eşleşen bayt dizisinde değiştir91 patched_data = patched_data.replace(orig_block, trans_block)92 obj.set_raw_data(bytes(patched_data))93 94 jobs[job_id].update({95 "progress": 60, 96 "message": "Sıkıştırma algoritmaları deneniyor... (Sunucu hızına göre 1-3 dakika sürebilir)"97 })98 99 output_path = os.path.join(tempfile.gettempdir(), f"repacked_{job_id}_{original_filename}")100 101 compression_error = ""102 try:103 # 1. Deneme: Modern UnityPy String Formatı (LZMA)104 packed_data = env.file.save(packer="lzma")105 except Exception as e1:106 try:107 # 2. Deneme: Daha hızlı olan LZ4 Formatı108 packed_data = env.file.save(packer="lz4")109 except Exception as e2:110 try:111 # 3. Deneme: Eski Sürüm UnityPy Enum Formatı112 packed_data = env.file.save(packer=CompressionFlags.LZMA)113 except Exception as e3:114 # HİÇBİRİ İŞE YARAMAZSA HATAYI EKRANA YANSIT115 compression_error = f"LZMA_Str: {str(e1)} | LZ4_Str: {str(e2)} | Enum: {str(e3)}"116 packed_data = env.file.save()117 118 with open(output_path, "wb") as f:119 f.write(packed_data)120 121 if compression_error:122 final_msg = f"Dosya hazırlandı AMA Sıkıştırılamadı! Hata Logu: {compression_error}"123 else:124 final_msg = "İşlem %100 Tamamlandı ve Orijinal Boyuta Sıkıştırıldı! Dosya İndiriliyor..."125 126 jobs[job_id].update({127 "status": "completed",128 "progress": 100,129 "message": final_msg,130 "file_path": output_path,131 "filename": original_filename132 })133 134 except Exception as e:135 jobs[job_id].update({"status": "error", "message": f"Kritik Hata: {str(e)}"})136 137 138# --------- API UÇ NOKTALARI (ENDPOINTS) ---------139 140@app.post("/generate_dll")141async def generate_dll(binary_file: UploadFile = File(...), metadata_file: UploadFile = File(...)):142 temp_dir = tempfile.mkdtemp()143 binary_path = os.path.join(temp_dir, binary_file.filename)144 metadata_path = os.path.join(temp_dir, metadata_file.filename)145 with open(binary_path, "wb") as f: f.write(await binary_file.read())146 with open(metadata_path, "wb") as f: f.write(await metadata_file.read())147 output_dir = os.path.join(temp_dir, "output")148 os.makedirs(output_dir, exist_ok=True)149 subprocess.run(["/app/dumper/Il2CppDumper", binary_path, metadata_path, output_dir], capture_output=True)150 zip_path = os.path.join(temp_dir, "DummyDlls.zip")151 with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:152 for root, _, files in os.walk(output_dir):153 for file in files:154 if file.endswith(".dll"): zipf.write(os.path.join(root, file), arcname=file)155 return FileResponse(zip_path, media_type="application/zip", filename="DummyDlls.zip")156 157@app.post("/extract")158async def extract_dialogues(file: UploadFile = File(...), assembly_file: Optional[UploadFile] = File(None)):159 temp_dir = tempfile.mkdtemp()160 asset_path = os.path.join(temp_dir, file.filename)161 with open(asset_path, "wb") as f: f.write(await file.read())162 env = UnityPy.load(asset_path)163 zip_filename = os.path.join(temp_dir, f"{file.filename}_dialogues.zip")164 with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:165 for obj in env.objects:166 if obj.type.name in ["TextAsset", "MonoBehaviour"]:167 asset_name = getattr(obj.assets_file, 'name', 'assets')168 asset_name = "".join([c if c.isalnum() else "_" for c in asset_name])169 obj_name = f"{asset_name}_{obj.type.name}_{obj.path_id}"170 if obj.type.name == "MonoBehaviour":171 raw_data = obj.get_raw_data()172 found_strings = {}173 i = 28174 while i <= len(raw_data) - 4:175 length = int.from_bytes(raw_data[i:i+4], 'little')176 if 2 <= length <= 5000 and (i + 4 + length) <= len(raw_data):177 str_bytes = raw_data[i+4 : i+4+length]178 pad_len = (4 - (length % 4)) % 4179 if (i + 4 + length + pad_len) <= len(raw_data):180 padding = raw_data[i+4+length : i+4+length+pad_len]181 if all(b == 0 for b in padding):182 try:183 text = str_bytes.decode('utf-8')184 if len(text.strip()) > 1 and any(c.isalpha() for c in text): found_strings[text] = text185 i += 4 + length + pad_len186 continue187 except UnicodeDecodeError: pass188 i += 4189 if found_strings: zipf.writestr(f"{obj_name}_raw.json", json.dumps(found_strings, indent=4, ensure_ascii=False).encode('utf-8'))190 elif obj.type.name == "TextAsset":191 try:192 content = obj.read().text.encode('utf-8')193 zipf.writestr(f"{obj_name}.txt", content)194 except: pass195 return FileResponse(zip_filename, media_type="application/zip", filename=f"{file.filename}_dialogues.zip")196 197@app.post("/repack_async")198async def repack_async(background_tasks: BackgroundTasks, asset_file: UploadFile = File(...), mod_zip: UploadFile = File(...)):199 job_id = str(uuid.uuid4())200 temp_dir = tempfile.mkdtemp()201 asset_path = os.path.join(temp_dir, asset_file.filename)202 zip_path = os.path.join(temp_dir, mod_zip.filename)203 204 with open(asset_path, "wb") as f: f.write(await asset_file.read())205 with open(zip_path, "wb") as f: f.write(await mod_zip.read())206 207 background_tasks.add_task(process_repack, job_id, asset_path, zip_path, asset_file.filename)208 jobs[job_id] = {"status": "queued", "progress": 0, "message": "İşlem sıraya alındı, başlıyor..."}209 return {"job_id": job_id}210 211@app.get("/status/{job_id}")212async def get_status(job_id: str):213 if job_id not in jobs: return JSONResponse(status_code=404, content={"error": "Görev bulunamadı"})214 return jobs[job_id]215 216@app.get("/download/{job_id}")217async def download_file(job_id: str):218 job = jobs.get(job_id)219 if not job or job.get("status") != "completed": return JSONResponse(status_code=400, content={"error": "Dosya hazır değil"})220 return FileResponse(job["file_path"], media_type="application/octet-stream", filename=job["filename"])221 222@app.post("/check_errors")223async def check_errors(mod_zip: UploadFile = File(...)):224 temp_dir = tempfile.mkdtemp()225 zip_path = os.path.join(temp_dir, mod_zip.filename)226 with open(zip_path, "wb") as f: f.write(await mod_zip.read())227 228 report_lines = ["=== SİSTEM KODU ÇEVİRİSİ HATA TARAMA RAPORU ===\n"]229 report_lines.append("Aşağıdaki listede bulunan çeviriler, oyunun çökmesine sebep olan 'Sistem Kodları' olabilir.")230 report_lines.append("Lütfen bu dosyalara girip, bu kelimeleri tekrar orijinal (İngilizce) haline geri döndürün.\n")231 232 error_count = 0233 234 with zipfile.ZipFile(zip_path, 'r') as zipf:235 for file_info in zipf.infolist():236 filename = file_info.filename237 if "__MACOSX" in filename or filename.split("/")[-1].startswith("._"): continue238 239 if filename.endswith(".json"):240 try:241 content = zipf.read(filename)242 translations = json.loads(content.decode('utf-8'))243 file_suspects = []244 245 for orig, trans in translations.items():246 if orig != trans: # Sadece çevrilmiş olanları kontrol et247 is_system = False248 249 # 1. Kural: Cümlede hiç boşluk yoksa büyük ihtimalle koddur (Örn: PlayerSpawnPoint)250 if " " not in orig and len(orig) > 2: is_system = True251 252 # 2. Kural: Alt tire içeriyorsa koddur (Örn: bg_sky_01)253 if "_" in orig: is_system = True254 255 # 3. Kural: Dosya yolu içeriyorsa koddur (Örn: Assets/Sprites/hero)256 if "/" in orig or "\\" in orig: is_system = True257 258 if is_system:259 file_suspects.append(f" [RİSKLİ] Orijinal: '{orig}'\n Çevirin: '{trans}'\n")260 261 if file_suspects:262 report_lines.append(f"--------------------------------------------------")263 report_lines.append(f"DOSYA: {filename}")264 report_lines.append(f"--------------------------------------------------")265 report_lines.extend(file_suspects)266 error_count += len(file_suspects)267 except:268 pass269 270 if error_count == 0:271 report_lines.append("\nHarika! Görünürde hiçbir sistem kodunu çevirmemişsin. Çökme sebebi başka olabilir.")272 else:273 report_lines.append(f"\n==================================================")274 report_lines.append(f"TOPLAM BULUNAN RİSKLİ ÇEVİRİ: {error_count} ADET")275 report_lines.append(f"==================================================")276 277 report_path = os.path.join(temp_dir, "hata_raporu.txt")278 with open(report_path, "w", encoding="utf-8") as f:279 f.write("\n".join(report_lines))280 281 return FileResponse(report_path, media_type="text/plain", filename="hata_raporu.txt")282 