Download app.py from Cnass/gamemaker: direct link, hf CLI and curl.
- Browser
- Download file 13.3 kB
-
https://huggingface.co/spaces/Cnass/gamemaker/resolve/main/app.py
- Command line
-
hf download hf://spaces/Cnass/gamemaker/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Cnass/gamemaker/resolve/main/app.py
13.3 kB
| import os | |
| import zipfile | |
| import tempfile | |
| import json | |
| import subprocess | |
| import time | |
| import uuid | |
| import glob | |
| import struct | |
| from fastapi import FastAPI, UploadFile, File, BackgroundTasks | |
| from fastapi.responses import FileResponse, JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| # ========================================== | |
| # C# SCRIPT FABRİKASI (KUSURSUZ X-RAY GÖRSEL ÇIKARICI) | |
| # ========================================== | |
| def generate_csx_scripts(): | |
| extract_code = """using System.IO; | |
| using System.Text.Json; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Reflection; | |
| using UndertaleModLib; | |
| // YENİLENMİŞ X-RAY: Hem obje içindeki hem de doğrudan çıplak (byte[]) olan resmi bulur. | |
| byte[] GetImg(UndertaleEmbeddedTexture emb) { | |
| if (emb == null || emb.TextureData == null) return null; | |
| object td = emb.TextureData; | |
| if (td is byte[] b) return b; // Eğer resim doğrudan çıplak haldeyse direkt al! | |
| var t = td.GetType(); | |
| var p = t.GetProperties().FirstOrDefault(x => x.PropertyType == typeof(byte[])); | |
| if (p != null) return p.GetValue(td) as byte[]; | |
| var f = t.GetFields().FirstOrDefault(x => x.FieldType == typeof(byte[])); | |
| if (f != null) return f.GetValue(td) as byte[]; | |
| return null; | |
| } | |
| var dict = new Dictionary<string, string>(); | |
| foreach (var str in Data.Strings) { | |
| if (!string.IsNullOrWhiteSpace(str.Content)) { | |
| if(!dict.ContainsKey(str.Content)) dict.Add(str.Content, str.Content); | |
| } | |
| } | |
| var opt = new JsonSerializerOptions { WriteIndented = true, Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping }; | |
| File.WriteAllText("strings.json", JsonSerializer.Serialize(dict, opt)); | |
| // FONT VE RESİMLERİ (ATLAS) ÇIKAR | |
| Directory.CreateDirectory("Fonts"); | |
| var fontData = new Dictionary<string, object>(); | |
| foreach (var font in Data.Fonts) { | |
| if (font.Texture != null && font.Texture.TexturePage != null) { | |
| var emb = font.Texture.TexturePage; | |
| // İsmi yoksa bile özel isim uydurarak o PNG'yi zorla kaydeder | |
| string texName = emb.Name?.Content; | |
| if (string.IsNullOrEmpty(texName)) texName = "Texture_" + font.Name.Content; | |
| string texPath = "Fonts/" + texName + ".png"; | |
| if (!File.Exists(texPath)) { | |
| byte[] img = GetImg(emb); | |
| if (img != null) { | |
| File.WriteAllBytes(texPath, img); | |
| } | |
| } | |
| var glyphs = new Dictionary<string, object>(); | |
| foreach (var g in font.Glyphs) { | |
| glyphs[g.Character.ToString()] = new { x = g.SourceX, y = g.SourceY, w = g.SourceWidth, h = g.SourceHeight, shift = g.Shift, offset = g.Offset }; | |
| } | |
| fontData[font.Name.Content] = new { Texture = texName, Glyphs = glyphs }; | |
| } | |
| } | |
| File.WriteAllText("Fonts/font_data.json", JsonSerializer.Serialize(fontData, opt)); | |
| """ | |
| repack_code = """using System; | |
| using System.IO; | |
| using System.Text.Json; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Reflection; | |
| using UndertaleModLib; | |
| // YENİLENMİŞ X-RAY: Değiştirilen PNG resmini oyun dosyasına kusursuzca gömer | |
| void SetImg(UndertaleEmbeddedTexture emb, byte[] d) { | |
| if (emb == null || emb.TextureData == null) return; | |
| object td = emb.TextureData; | |
| if (td is byte[]) { | |
| // Bazı UMT sürümlerinde TextureData sadece bir byte dizisidir | |
| var prop = emb.GetType().GetProperty("TextureData"); | |
| if (prop != null && prop.CanWrite) prop.SetValue(emb, d); | |
| return; | |
| } | |
| var t = td.GetType(); | |
| var p = t.GetProperties().FirstOrDefault(x => x.PropertyType == typeof(byte[]) && x.CanWrite); | |
| if (p != null) { p.SetValue(td, d); return; } | |
| var f = t.GetFields().FirstOrDefault(x => x.FieldType == typeof(byte[])); | |
| if (f != null) { f.SetValue(td, d); return; } | |
| } | |
| string json = File.ReadAllText("strings.json"); | |
| var dict = JsonSerializer.Deserialize<Dictionary<string, string>>(json); | |
| foreach (var str in Data.Strings) { | |
| if (dict.TryGetValue(str.Content, out string newStr)) { | |
| str.Content = newStr; | |
| } | |
| } | |
| // FONT RESİMLERİNİ VE KOORDİNAT HARİTASINI GERİ YÜKLE | |
| if (Directory.Exists("Fonts")) { | |
| string fontJsonPath = "Fonts/font_data.json"; | |
| if (File.Exists(fontJsonPath)) { | |
| string fJson = File.ReadAllText(fontJsonPath); | |
| var fDict = JsonSerializer.Deserialize<Dictionary<string, JsonElement>>(fJson); | |
| foreach (var font in Data.Fonts) { | |
| if (fDict.TryGetValue(font.Name.Content, out JsonElement fData)) { | |
| // Görseli Güncelle | |
| if (fData.TryGetProperty("Texture", out JsonElement texElement)) { | |
| string texName = texElement.GetString(); | |
| string texPath = "Fonts/" + texName + ".png"; | |
| if (File.Exists(texPath) && font.Texture != null && font.Texture.TexturePage != null) { | |
| SetImg(font.Texture.TexturePage, File.ReadAllBytes(texPath)); | |
| } | |
| } | |
| // Koordinatları Güncelle | |
| if (fData.TryGetProperty("Glyphs", out JsonElement glyphsJson)) { | |
| foreach (var glyphProp in glyphsJson.EnumerateObject()) { | |
| ushort charCode; | |
| if (!ushort.TryParse(glyphProp.Name, out charCode)) continue; | |
| var gData = glyphProp.Value; | |
| var existingGlyph = font.Glyphs.FirstOrDefault(g => g.Character == charCode); | |
| if (existingGlyph == null) { | |
| existingGlyph = new UndertaleFont.Glyph { Character = charCode }; | |
| font.Glyphs.Add(existingGlyph); | |
| } | |
| existingGlyph.SourceX = gData.GetProperty("x").GetUInt16(); | |
| existingGlyph.SourceY = gData.GetProperty("y").GetUInt16(); | |
| existingGlyph.SourceWidth = gData.GetProperty("w").GetUInt16(); | |
| existingGlyph.SourceHeight = gData.GetProperty("h").GetUInt16(); | |
| existingGlyph.Shift = gData.GetProperty("shift").GetInt16(); | |
| existingGlyph.Offset = gData.GetProperty("offset").GetInt16(); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // Reflection ile Dinamik Kaydetme | |
| using (var stream = new FileStream("repacked_game.droid", FileMode.Create, FileAccess.Write)) { | |
| var m2 = typeof(UndertaleIO).GetMethod("Write", new[] { typeof(Stream), typeof(UndertaleData) }); | |
| if (m2 != null) m2.Invoke(null, new object[] { stream, Data }); | |
| else { | |
| var m3 = typeof(UndertaleIO).GetMethods().FirstOrDefault(m => m.Name == "Write" && m.GetParameters().Length == 3); | |
| if (m3 != null) m3.Invoke(null, new object[] { stream, Data, null }); | |
| else { | |
| var w3 = typeof(UndertaleWriter).GetMethods().FirstOrDefault(m => m.Name == "Write" && m.GetParameters().Length == 3); | |
| w3?.Invoke(null, new object[] { stream, Data, null }); | |
| } | |
| } | |
| } | |
| """ | |
| with open("/app/extract.csx", "w", encoding="utf-8") as f: f.write(extract_code) | |
| with open("/app/repack.csx", "w", encoding="utf-8") as f: f.write(repack_code) | |
| generate_csx_scripts() | |
| app = FastAPI() | |
| app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"]) | |
| jobs = {} | |
| def get_cli_command(): | |
| paths = glob.glob("/app/umt/**/UndertaleModCli*", recursive=True) + glob.glob("/app/umt/**/UTMT*", recursive=True) | |
| for p in paths: | |
| if os.path.isfile(p): | |
| if p.endswith(".dll"): return ["dotnet", p] | |
| elif not p.endswith(".exe"): return [p] | |
| return ["/app/umt/UTMT_CLI"] | |
| def deep_heal_droid(filepath): | |
| try: | |
| with open(filepath, "rb") as f: data = bytearray(f.read()) | |
| original_header = data[0:4] | |
| if original_header != b'FORM': data[0:4] = b'FORM' | |
| offset = 8 | |
| file_len = len(data) | |
| log = [] | |
| while offset < file_len: | |
| if offset + 8 > file_len: | |
| data = data[:offset] | |
| break | |
| chunk_name = data[offset:offset+4] | |
| chunk_size = struct.unpack("<I", data[offset+4:offset+8])[0] | |
| is_valid = all(32 <= b <= 126 for b in chunk_name) | |
| if not is_valid or chunk_size > file_len: | |
| log.append(f"TUZAK KESİLDİ: Bozuk Blok '{chunk_name}' | Konum: {offset}") | |
| data = data[:offset] | |
| break | |
| offset += 8 + chunk_size | |
| data[4:8] = struct.pack("<I", len(data) - 8) | |
| with open(filepath, "wb") as f: f.write(data) | |
| return original_header, "\n".join(log) | |
| except Exception as e: | |
| return None, f"İyileştirme Hatası: {str(e)}" | |
| def revert_header(filepath, original_header): | |
| if not original_header or original_header == b'FORM': return | |
| try: | |
| with open(filepath, "rb") as f: data = bytearray(f.read()) | |
| data[0:4] = original_header | |
| with open(filepath, "wb") as f: f.write(data) | |
| except: pass | |
| async def extract_dialogues(file: UploadFile = File(...)): | |
| 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()) | |
| fake_header, heal_log = deep_heal_droid(asset_path) | |
| cmd = get_cli_command() + ["load", file.filename, "-s", "/app/extract.csx"] | |
| process = subprocess.run(cmd, cwd=temp_dir, capture_output=True, text=True) | |
| json_path = os.path.join(temp_dir, "strings.json") | |
| zip_filename = os.path.join(temp_dir, f"{file.filename}_dialogues.zip") | |
| with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zipf: | |
| if os.path.exists(json_path): | |
| zipf.write(json_path, arcname="strings.json") | |
| # FONT KLASÖRÜNÜ ZİPE EKLE (GARANTİLİ) | |
| font_dir = os.path.join(temp_dir, "Fonts") | |
| if os.path.exists(font_dir): | |
| for root, dirs, files in os.walk(font_dir): | |
| for f in files: | |
| file_path = os.path.join(root, f) | |
| arcname = os.path.relpath(file_path, temp_dir) | |
| zipf.write(file_path, arcname=arcname) | |
| if heal_log: zipf.writestr("IYILESME_RAPORU.txt", f"--- OTO-SIFACI RAPORU ---\n\n{heal_log}".encode('utf-8')) | |
| else: | |
| log_msg = f"=== UMT MOTORU ÇÖKME LOGU ===\nBASLIK: {fake_header}\nSTDOUT:\n{process.stdout}\nSTDERR:\n{process.stderr}\n" | |
| zipf.writestr("UMT_Hata_Logu.txt", log_msg.encode('utf-8')) | |
| return FileResponse(zip_filename, media_type="application/zip", filename=f"{file.filename}_dialogues.zip") | |
| def process_repack(job_id: str, asset_path: str, zip_path: str, original_filename: str): | |
| try: | |
| temp_dir = os.path.dirname(asset_path) | |
| jobs[job_id] = {"status": "processing", "progress": 20, "message": "Oyun korumaları kırılıyor..."} | |
| original_header, heal_log = deep_heal_droid(asset_path) | |
| with zipfile.ZipFile(zip_path, 'r') as zipf: | |
| zipf.extractall(temp_dir) | |
| jobs[job_id].update({"progress": 50, "message": "UMT C# Motoru çalıştırılıyor... Lütfen bekleyin."}) | |
| cmd = get_cli_command() + ["load", original_filename, "-s", "/app/repack.csx"] | |
| process = subprocess.run(cmd, cwd=temp_dir, capture_output=True, text=True) | |
| output_path = os.path.join(temp_dir, "repacked_game.droid") | |
| if os.path.exists(output_path): | |
| revert_header(output_path, original_header) | |
| jobs[job_id].update({"status": "completed", "progress": 100, "message": "UMT İşlemi Başarılı! İndiriliyor...", "file_path": output_path, "filename": original_filename}) | |
| else: | |
| jobs[job_id].update({"status": "error", "message": f"Paketleme başarısız! Log: {process.stderr}\n{process.stdout}"}) | |
| except Exception as e: | |
| jobs[job_id].update({"status": "error", "message": f"Kritik Hata: {str(e)}"}) | |
| 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": "GameMaker işleme sırasına alındı..."} | |
| return {"job_id": job_id} | |
| async def get_status(job_id: str): return jobs.get(job_id, JSONResponse(status_code=404, content={"error": "Bulunamadı"})) | |
| async def download_file(job_id: str): return FileResponse(jobs[job_id]["file_path"], media_type="application/octet-stream", filename=jobs[job_id]["filename"]) | |