Cnass commited on
Commit
c2895e2
·
verified ·
1 Parent(s): 954e4c2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -67
app.py CHANGED
@@ -4,10 +4,12 @@ import tempfile
4
  import json
5
  import re
6
  import subprocess
 
 
7
  import UnityPy
8
  from UnityPy.enums import CompressionFlags
9
- from fastapi import FastAPI, UploadFile, File
10
- from fastapi.responses import FileResponse
11
  from fastapi.middleware.cors import CORSMiddleware
12
  from typing import Optional
13
 
@@ -21,8 +23,96 @@ app.add_middleware(
21
  allow_headers=["*"],
22
  )
23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  @app.post("/generate_dll")
25
  async def generate_dll(binary_file: UploadFile = File(...), metadata_file: UploadFile = File(...)):
 
26
  temp_dir = tempfile.mkdtemp()
27
  binary_path = os.path.join(temp_dir, binary_file.filename)
28
  metadata_path = os.path.join(temp_dir, metadata_file.filename)
@@ -40,109 +130,74 @@ async def generate_dll(binary_file: UploadFile = File(...), metadata_file: Uploa
40
 
41
  @app.post("/extract")
42
  async def extract_dialogues(file: UploadFile = File(...), assembly_file: Optional[UploadFile] = File(None)):
 
43
  temp_dir = tempfile.mkdtemp()
44
  asset_path = os.path.join(temp_dir, file.filename)
45
  with open(asset_path, "wb") as f: f.write(await file.read())
46
-
47
  env = UnityPy.load(asset_path)
48
  zip_filename = os.path.join(temp_dir, f"{file.filename}_dialogues.zip")
49
- extracted_count = 0
50
-
51
  with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
52
  for obj in env.objects:
53
  if obj.type.name in ["TextAsset", "MonoBehaviour"]:
54
  asset_name = getattr(obj.assets_file, 'name', 'assets')
55
  asset_name = "".join([c if c.isalnum() else "_" for c in asset_name])
56
  obj_name = f"{asset_name}_{obj.type.name}_{obj.path_id}"
57
-
58
  if obj.type.name == "MonoBehaviour":
59
  raw_data = obj.get_raw_data()
60
  found_strings = {}
61
  i = 28
62
-
63
  while i <= len(raw_data) - 4:
64
  length = int.from_bytes(raw_data[i:i+4], 'little')
65
  if 2 <= length <= 5000 and (i + 4 + length) <= len(raw_data):
66
  str_bytes = raw_data[i+4 : i+4+length]
67
  pad_len = (4 - (length % 4)) % 4
68
-
69
  if (i + 4 + length + pad_len) <= len(raw_data):
70
  padding = raw_data[i+4+length : i+4+length+pad_len]
71
  if all(b == 0 for b in padding):
72
  try:
73
  text = str_bytes.decode('utf-8')
74
- if len(text.strip()) > 1 and any(c.isalpha() for c in text):
75
- found_strings[text] = text
76
  i += 4 + length + pad_len
77
  continue
78
- except UnicodeDecodeError:
79
- pass
80
  i += 4
81
-
82
- if found_strings:
83
- zipf.writestr(f"{obj_name}_raw.json", json.dumps(found_strings, indent=4, ensure_ascii=False).encode('utf-8'))
84
- extracted_count += 1
85
-
86
  elif obj.type.name == "TextAsset":
87
  try:
88
  content = obj.read().text.encode('utf-8')
89
  zipf.writestr(f"{obj_name}.txt", content)
90
- extracted_count += 1
91
  except: pass
92
-
93
  return FileResponse(zip_filename, media_type="application/zip", filename=f"{file.filename}_dialogues.zip")
94
 
95
- @app.post("/repack")
96
- async def repack_dialogues(asset_file: UploadFile = File(...), mod_zip: UploadFile = File(...)):
 
 
97
  temp_dir = tempfile.mkdtemp()
98
  asset_path = os.path.join(temp_dir, asset_file.filename)
99
  zip_path = os.path.join(temp_dir, mod_zip.filename)
 
 
100
  with open(asset_path, "wb") as f: f.write(await asset_file.read())
101
  with open(zip_path, "wb") as f: f.write(await mod_zip.read())
102
-
103
- env = UnityPy.load(asset_path)
104
 
105
- filename_to_translations = {}
106
- with zipfile.ZipFile(zip_path, 'r') as zipf:
107
- for file_info in zipf.infolist():
108
- filename = file_info.filename
109
- if "__MACOSX" in filename or filename.split("/")[-1].startswith("._"):
110
- continue
111
- if filename.endswith(".json"):
112
- try:
113
- content = zipf.read(filename)
114
- filename_to_translations[filename.split("/")[-1]] = json.loads(content.decode('utf-8'))
115
- except json.JSONDecodeError: pass
116
-
117
- for obj in env.objects:
118
- if obj.type.name in ["TextAsset", "MonoBehaviour"]:
119
- asset_name = getattr(obj.assets_file, 'name', 'assets')
120
- asset_name = "".join([c if c.isalnum() else "_" for c in asset_name])
121
- obj_name = f"{asset_name}_{obj.type.name}_{obj.path_id}"
122
- target_filename = f"{obj_name}_raw.json"
123
-
124
- if target_filename in filename_to_translations:
125
- translations = filename_to_translations[target_filename]
126
- if obj.type.name == "MonoBehaviour":
127
- raw_data = obj.get_raw_data()
128
- patched_data = bytearray(raw_data)
129
- for orig_str, trans_str in translations.items():
130
- if orig_str == trans_str: continue
131
- orig_bytes = orig_str.encode('utf-8')
132
- orig_pad = (4 - (len(orig_bytes) % 4)) % 4
133
- orig_block = len(orig_bytes).to_bytes(4, 'little') + orig_bytes + (b'\x00' * orig_pad)
134
- trans_bytes = trans_str.encode('utf-8')
135
- trans_pad = (4 - (len(trans_bytes) % 4)) % 4
136
- trans_block = len(trans_bytes).to_bytes(4, 'little') + trans_bytes + (b'\x00' * trans_pad)
137
- patched_data = patched_data.replace(orig_block, trans_block)
138
- obj.set_raw_data(bytes(patched_data))
139
-
140
- output_path = os.path.join(temp_dir, f"repacked_{asset_file.filename}")
141
- with open(output_path, "wb") as f:
142
- try:
143
- # YENİLİK: Dosyayı ışık hızında LZ4 ile sıkıştırarak RAM dostu hale getir
144
- f.write(env.file.save(packer=CompressionFlags.LZ4))
145
- except Exception:
146
- f.write(env.file.save())
147
-
148
- return FileResponse(output_path, media_type="application/octet-stream", filename=asset_file.filename)
 
4
  import json
5
  import re
6
  import subprocess
7
+ import time
8
+ import uuid
9
  import UnityPy
10
  from UnityPy.enums import CompressionFlags
11
+ from fastapi import FastAPI, UploadFile, File, BackgroundTasks
12
+ from fastapi.responses import FileResponse, JSONResponse
13
  from fastapi.middleware.cors import CORSMiddleware
14
  from typing import Optional
15
 
 
23
  allow_headers=["*"],
24
  )
25
 
26
+ # Arka plandaki görevleri takip edeceğimiz hafıza sözlüğü
27
+ jobs = {}
28
+
29
+ def process_repack(job_id: str, asset_path: str, zip_path: str, original_filename: str):
30
+ try:
31
+ jobs[job_id] = {"status": "processing", "progress": 5, "message": "Oyun dosyaları çözümleniyor..."}
32
+ env = UnityPy.load(asset_path)
33
+
34
+ filename_to_translations = {}
35
+ with zipfile.ZipFile(zip_path, 'r') as zipf:
36
+ for file_info in zipf.infolist():
37
+ filename = file_info.filename
38
+ if "__MACOSX" in filename or filename.split("/")[-1].startswith("._"): continue
39
+ if filename.endswith(".json"):
40
+ try:
41
+ content = zipf.read(filename)
42
+ filename_to_translations[filename.split("/")[-1]] = json.loads(content.decode('utf-8'))
43
+ except json.JSONDecodeError: pass
44
+
45
+ total_objects = len(env.objects)
46
+ processed = 0
47
+ start_time = time.time()
48
+
49
+ for obj in env.objects:
50
+ processed += 1
51
+ # Her 50 objede bir hızı ölç ve ETA (Kalan Süre) hesapla
52
+ if processed % 50 == 0:
53
+ elapsed = time.time() - start_time
54
+ rate = processed / elapsed if elapsed > 0 else 1
55
+ remaining_seconds = int((total_objects - processed) / rate)
56
+ prog = 5 + int((processed / total_objects) * 45) # İlk aşama %5 ile %50 arası sürer
57
+
58
+ jobs[job_id].update({
59
+ "progress": prog,
60
+ "message": f"Diyaloglar yamalanıyor... (Kalan Süre: ~{remaining_seconds} saniye)"
61
+ })
62
+
63
+ if obj.type.name in ["TextAsset", "MonoBehaviour"]:
64
+ asset_name = getattr(obj.assets_file, 'name', 'assets')
65
+ asset_name = "".join([c if c.isalnum() else "_" for c in asset_name])
66
+ obj_name = f"{asset_name}_{obj.type.name}_{obj.path_id}"
67
+ target_filename = f"{obj_name}_raw.json"
68
+
69
+ if target_filename in filename_to_translations:
70
+ translations = filename_to_translations[target_filename]
71
+ if obj.type.name == "MonoBehaviour":
72
+ raw_data = obj.get_raw_data()
73
+ patched_data = bytearray(raw_data)
74
+ for orig_str, trans_str in translations.items():
75
+ if orig_str == trans_str: continue
76
+ orig_bytes = orig_str.encode('utf-8')
77
+ orig_pad = (4 - (len(orig_bytes) % 4)) % 4
78
+ orig_block = len(orig_bytes).to_bytes(4, 'little') + orig_bytes + (b'\x00' * orig_pad)
79
+ trans_bytes = trans_str.encode('utf-8')
80
+ trans_pad = (4 - (len(trans_bytes) % 4)) % 4
81
+ trans_block = len(trans_bytes).to_bytes(4, 'little') + trans_bytes + (b'\x00' * trans_pad)
82
+ patched_data = patched_data.replace(orig_block, trans_block)
83
+ obj.set_raw_data(bytes(patched_data))
84
+
85
+ # En ağır kısma geçiliyor: Orijinal boyuta sıkıştırma (LZMA)
86
+ jobs[job_id].update({
87
+ "progress": 60,
88
+ "message": "Orijinal boyuta (LZMA) sıkıştırılıyor. Sunucu hızına göre 1-3 dakika sürebilir, bağlantınız kopmayacak..."
89
+ })
90
+
91
+ output_path = os.path.join(tempfile.gettempdir(), f"repacked_{job_id}_{original_filename}")
92
+ with open(output_path, "wb") as f:
93
+ try:
94
+ # 300 MB'ı 20 MB'a düşüren asıl kod:
95
+ f.write(env.file.save(packer=CompressionFlags.LZMA))
96
+ except Exception:
97
+ f.write(env.file.save())
98
+
99
+ jobs[job_id].update({
100
+ "status": "completed",
101
+ "progress": 100,
102
+ "message": "İşlem %100 Tamamlandı! Dosya İndiriliyor...",
103
+ "file_path": output_path,
104
+ "filename": original_filename
105
+ })
106
+
107
+ except Exception as e:
108
+ jobs[job_id].update({"status": "error", "message": f"Kritik Hata: {str(e)}"})
109
+
110
+
111
+ # --------- YENİ API UÇ NOKTALARI (ENDPOINTS) ---------
112
+
113
  @app.post("/generate_dll")
114
  async def generate_dll(binary_file: UploadFile = File(...), metadata_file: UploadFile = File(...)):
115
+ # Bu kısmı dokunmadık, aynı duruyor
116
  temp_dir = tempfile.mkdtemp()
117
  binary_path = os.path.join(temp_dir, binary_file.filename)
118
  metadata_path = os.path.join(temp_dir, metadata_file.filename)
 
130
 
131
  @app.post("/extract")
132
  async def extract_dialogues(file: UploadFile = File(...), assembly_file: Optional[UploadFile] = File(None)):
133
+ # Bu kısmı dokunmadık, aynı duruyor
134
  temp_dir = tempfile.mkdtemp()
135
  asset_path = os.path.join(temp_dir, file.filename)
136
  with open(asset_path, "wb") as f: f.write(await file.read())
 
137
  env = UnityPy.load(asset_path)
138
  zip_filename = os.path.join(temp_dir, f"{file.filename}_dialogues.zip")
 
 
139
  with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf:
140
  for obj in env.objects:
141
  if obj.type.name in ["TextAsset", "MonoBehaviour"]:
142
  asset_name = getattr(obj.assets_file, 'name', 'assets')
143
  asset_name = "".join([c if c.isalnum() else "_" for c in asset_name])
144
  obj_name = f"{asset_name}_{obj.type.name}_{obj.path_id}"
 
145
  if obj.type.name == "MonoBehaviour":
146
  raw_data = obj.get_raw_data()
147
  found_strings = {}
148
  i = 28
 
149
  while i <= len(raw_data) - 4:
150
  length = int.from_bytes(raw_data[i:i+4], 'little')
151
  if 2 <= length <= 5000 and (i + 4 + length) <= len(raw_data):
152
  str_bytes = raw_data[i+4 : i+4+length]
153
  pad_len = (4 - (length % 4)) % 4
 
154
  if (i + 4 + length + pad_len) <= len(raw_data):
155
  padding = raw_data[i+4+length : i+4+length+pad_len]
156
  if all(b == 0 for b in padding):
157
  try:
158
  text = str_bytes.decode('utf-8')
159
+ if len(text.strip()) > 1 and any(c.isalpha() for c in text): found_strings[text] = text
 
160
  i += 4 + length + pad_len
161
  continue
162
+ except UnicodeDecodeError: pass
 
163
  i += 4
164
+ if found_strings: zipf.writestr(f"{obj_name}_raw.json", json.dumps(found_strings, indent=4, ensure_ascii=False).encode('utf-8'))
 
 
 
 
165
  elif obj.type.name == "TextAsset":
166
  try:
167
  content = obj.read().text.encode('utf-8')
168
  zipf.writestr(f"{obj_name}.txt", content)
 
169
  except: pass
 
170
  return FileResponse(zip_filename, media_type="application/zip", filename=f"{file.filename}_dialogues.zip")
171
 
172
+ # ARKA PLAN GÖREVİ BAŞLATICISI
173
+ @app.post("/repack_async")
174
+ async def repack_async(background_tasks: BackgroundTasks, asset_file: UploadFile = File(...), mod_zip: UploadFile = File(...)):
175
+ job_id = str(uuid.uuid4())
176
  temp_dir = tempfile.mkdtemp()
177
  asset_path = os.path.join(temp_dir, asset_file.filename)
178
  zip_path = os.path.join(temp_dir, mod_zip.filename)
179
+
180
+ # Dosyaları hızlıca diske al ve isteği hemen geri döndür ki 60 saniye sorunu yaşanmasın
181
  with open(asset_path, "wb") as f: f.write(await asset_file.read())
182
  with open(zip_path, "wb") as f: f.write(await mod_zip.read())
 
 
183
 
184
+ # Gerçek işi arka plandaki isimsiz bir hayalete devret
185
+ background_tasks.add_task(process_repack, job_id, asset_path, zip_path, asset_file.filename)
186
+ jobs[job_id] = {"status": "queued", "progress": 0, "message": "İşlem sıraya alındı, başlıyor..."}
187
+
188
+ return {"job_id": job_id}
189
+
190
+ # DURUM KONTROLCÜSÜ (Yüzde ve Süre Buradan Okunur)
191
+ @app.get("/status/{job_id}")
192
+ async def get_status(job_id: str):
193
+ if job_id not in jobs:
194
+ return JSONResponse(status_code=404, content={"error": "Görev bulunamadı"})
195
+ return jobs[job_id]
196
+
197
+ # İNDİRME UÇ NOKTASI
198
+ @app.get("/download/{job_id}")
199
+ async def download_file(job_id: str):
200
+ job = jobs.get(job_id)
201
+ if not job or job.get("status") != "completed":
202
+ return JSONResponse(status_code=400, content={"error": "Dosya henüz hazır değil"})
203
+ return FileResponse(job["file_path"], media_type="application/octet-stream", filename=job["filename"])