UnityPy / app.py
Cnass's picture
Update app.py
f9c7630 verified
Raw History Blame
10.1 kB
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)
for orig_str, trans_str in translations.items():
if orig_str == trans_str: continue
orig_bytes = orig_str.encode('utf-8')
orig_pad = (4 - (len(orig_bytes) % 4)) % 4
orig_block = len(orig_bytes).to_bytes(4, 'little') + orig_bytes + (b'\x00' * orig_pad)
trans_bytes = trans_str.encode('utf-8')
trans_pad = (4 - (len(trans_bytes) % 4)) % 4
trans_block = len(trans_bytes).to_bytes(4, 'little') + trans_bytes + (b'\x00' * trans_pad)
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"])