Neler yeni

Foruma hoş geldin, Ziyaretçi

Metin2Lobby.com Metin2 Private Server Tanıtım Advertising Ve Geliştirme Forumudur.Metin2 pvp serverler,1-99,1-105,1-120,55-120 global serverları paylaş yada ara.
Forum içeriğine ve tüm hizmetlerimize erişim sağlamak için foruma kayıt olmalı ya da giriş yapmalısınız. Foruma üye olmak tamamen ücretsizdir.

[Araç] Lib Builder Pro - Otomatik Kütüphane Derleme Aracı (libjpeg, DevIL, Crypto++)

Admin

Metin2Lobby
Yönetici
Founder
Katılım
6 Mayıs 2022
Konular
48,268
Mesajlar
48,578
Tepkime puanı
74
M2 Yaşı
3 yıl 11 ay 10 gün
Trophy Puan
48
Konum
Web sitesi
M2 Yang
488,649
Ticaret : 1 / 0 / 0
Ticaret Oranı : 100%
GÜNCELLENDİ
270972_2f3d3919c83e4d9af56b8024153a45ed.png
Selamlar Turkmmo ailesi,
Metin2 source derleme sürecinin en baş ağrıtan kısımlarından birinin kütüphaneleri (library) derlemek olduğunu hepimiz biliyoruz. libjpeg, DevIL, Crypto++ gibi temel bağımlılıkları doğru ayarlarla, doğru mimaride (32-bit client için!) derlemek, özellikle bu işe yeni başlayanlar için tam bir kabusa dönüşebiliyor.

Forumda "kütüphane hatası alıyorum", "build edemiyorum" gibi onlarca konu gördükten sonra bu süreci tamamen otomatikleştiren bir araç geliştirdim! Artık tek bir programla, hiçbir ayarla boğuşmadan, tüm kütüphanelerinizi dakikalar içinde hazır hale getirebileceksiniz.




💣 Bu Araç Ne İşe Yarıyor? Neden Kullanmalısınız? 💣



Bu program, Metin2 client ve server derlemek için gereken en temel 3 kütüphaneyi sizin için sıfırdan oluşturur.

  • <li data-xf-list-type="ul">Zahmetsiz Kurulum: Derleme için gereken tüm programları (Git, CMake, Ninja vb.) tek tıkla kendisi indirir. Size sadece Visual Studio kurmak kalır. <li data-xf-list-type="ul">Metin2 Uyumlu: Client için zorunlu olan 32-bit (x86) mimarisini ve isteğe bağlı olarak 64-bit (x64) mimarisini destekler. <li data-xf-list-type="ul">Tam Otomasyon: "Tüm Kütüphaneleri Derle" butonu ile bağımlılıkları (DevIL için libjpeg gibi) kendi gözeterek tüm kütüphaneleri sırayla derler. <li data-xf-list-type="ul">Düzenli ve Hazır Çıktı: Derlenen tüm dosyaları (.lib, .h, .dll) alıp doğrudan source'unuzun extern klasörüne atabileceğiniz şekilde hazırlar. Artık extern/include ve extern/lib klasörlerini aramakla uğraşmak yok!



⚠️ Kurulum Öncesi GEREKSİNİMLER (MUTLAKA OKUYUN!) ⚠️



Programı çalıştırmadan önce sisteminizde iki şeyin kurulu ve ayarlı olması ŞARTTIR!



<h4>1. Visual Studio 2022 (C++ İş Yükü)</h4>

Bu adım olmadan program KESİNLİKLE ÇALIŞMAZ!

  • <li data-xf-list-type="ul">Visual Studio 2022 kurulu olmalı. <li data-xf-list-type="ul">Visual Studio Installer üzerinden "Masaüstü geliştirme (C++)" (Desktop development with C++) iş yükünün seçili olduğundan emin olun. Bu, derleyicinin kendisidir.


<h4>2. Python ve Gerekli Paketler</h4>

Bu araç Python ile yazılmıştır. Bu yüzden sisteminizde Python kurulu olmalıdır.

  • <li data-xf-list-type="ul">Python Sürümü: Python 3.14 veya daha yenibir sürüm gereklidir.
    • <li data-xf-list-type="ul">PowerShell'i açıp python --version komutuyla sürümünüzü kontrol edebilirsiniz. <li data-xf-list-type="ul">Kurulu değilse, indirip kurarken "Add Python to PATH" seçeneğini işaretlemeyi UNUTMAYIN!
    <li data-xf-list-type="ul">Gerekli Python Paketleri: Programın çalışması için birkaç ek pakete ihtiyacı var. PowerShell'i açıp aşağıdaki komutu çalıştırarak hepsini tek seferde kurun:

    Kod:
    pip install requests psutil ttkbootstrap



🚀 Kurulum ve Çalıştırma (SIFIRDAN ANLATIM) 🚀



İndirme linki yok! Kodu doğrudan buraya yapıştıracağım. Aşağıdaki adımları sırayla takip edin:

1. Boş Bir Klasör Oluşturun:Masaüstünüzde veya istediğiniz bir yerde LibBuilder gibi bir isimle yeni bir klasör oluşturun.

2. Metin Belgesi Oluşturun:Oluşturduğunuz bu LibBuilder klasörünün içine girin. Sağ tıklayıp Yeni -&gt; Metin Belgesi deyin.

3. Dosyanın Adını ve Uzantısını Değiştirin:Oluşan Yeni Metin Belgesi.txt dosyasının adını LibBuilder.py olarak değiştirin.

  • <li data-xf-list-type="ul">DİKKAT: Dosya uzantılarının görünür olduğundan emin olun! Eğer .txt kısmını göremiyorsanız, klasör seçeneklerinden "Bilinen dosya türleri için uzantıları gizle" seçeneğinin tikini kaldırın. Dosyanın adı tam olarak derleyici.py olmalıdır.
4. Kodu Yapıştırın:Aşağıda --- KOD BLOĞU --- başlığı altında verdiğim kodun tamamını kopyalayın. Oluşturduğunuz derleyici.py dosyasına sağ tıklayıp "Düzenle" deyin (veya Notepad++ ile açın), kopyaladığınız kodu içine yapıştırın ve dosyayı kaydedip kapatın.

5. Programı Çalıştırın:

  • <li data-xf-list-type="ul">LibBuilder klasörünün içindeyken, üstteki adres çubuğuna powershell yazıp Enter'a basın. Bu, o klasörde bir PowerShell penceresi açacaktır. <li data-xf-list-type="ul">Açılan PowerShell ekranına aşağıdaki komutu yazın ve Enter'a basın:
Kod:
python .\LibBuilder.py
Tebrikler! Programın arayüzü karşınıza gelecek.




Kod:
# -*- coding: utf-8 -*- import os import re import sys import time import shutil import uuid import zipfile import threading import queue import subprocess import tarfile import contextlib import json import datetime import traceback from pathlib import Path from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass, field from enum import Enum from typing import Callable, Any, cast try:     import requests     import psutil     import ttkbootstrap as ttk     import tkinter as tk     from ttkbootstrap.constants import *     from tkinter import font as tkFont, messagebox, scrolledtext, filedialog except ImportError:     raise SystemExit("Gerekli paketler eksik. Lütfen çalıştırın: pip install requests ttkbootstrap psutil") class C:     class Arch(str, Enum):         X86 = "x86"         X64 = "x64"     class Runtime(str, Enum):         MT = "MT"         MD = "MD"     class BuildType(str, Enum):         RELEASE = "Release"         DEBUG = "Debug"     class ToolMode(str, Enum):         BUNDLED = "bundled"         SYSTEM = "system"           class LogLevel(str, Enum):         STAGE = "stage"; CMD = "cmd"; WARN = "warn"; ERR = "err"; INFO = "info"     ROOT = Path.cwd()     LOGS = ROOT / "logs"; TOOLS = ROOT / ".tools"; DL = ROOT / "downloads"; SRC = ROOT / "src"     BUILD = ROOT / "build"; INST = ROOT / "install"; DEPS = ROOT / ".deps"; TRASH = ROOT / ".trash"     CONFIG_FILE = ROOT / "config.json"       INST_LIBJPEG_TURBO = INST / "libjpeg-turbo"     INST_DEVIL = INST / "devil"     INST_CRYPTOPP = INST / "cryptopp"     INST_LZO = INST / "lzo"     INST_SPDLOG = INST / "spdlog"     INST_NLOHMANN_JSON = INST / "nlohmann-json"     INST_PYTHON = INST / "python"     LIB_META = {         "libjpeg-turbo": {"name": "libjpeg-turbo", "type": "git", "repo": "https://github.com/libjpeg-turbo/libjpeg-turbo", "tag_prefix": "", "tag_filter": re.compile(r"^\d+\.\d+\.\d+$")},         "devil": {"name": "DevIL", "type": "git", "repo": "https://github.com/DentonW/DevIL", "tag_prefix": "v", "tag_filter": re.compile(r"^v\d+\.\d+\.\d+$")},         "cryptopp": {"name": "Crypto++", "type": "git", "repo": "https://github.com/weidai11/cryptopp", "tag_prefix": "CRYPTOPP_", "tag_filter": re.compile(r"^CRYPTOPP_\d+_\d+(_\d+)?$")},         "lzo": {"name": "LZO", "type": "url", "url": "https://www.oberhumer.com/opensource/lzo/download/lzo-2.10.tar.gz", "version": "2.10"},         "spdlog": {"name": "spdlog", "type": "git", "repo": "https://github.com/gabime/spdlog", "tag_prefix": "v", "tag_filter": re.compile(r"^v\d+\.\d+\.\d+$")},         "nlohmann-json": {"name": "nlohmann/json", "type": "git", "repo": "https://github.com/nlohmann/json", "tag_prefix": "v", "tag_filter": re.compile(r"^v\d+\.\d+\.\d+$")},         "python": {"name": "Python", "type": "git", "repo": "https://github.com/python/cpython", "tag_prefix": "v", "tag_filter": re.compile(r"^v\d+\.\d+(\.\d+)?$")},     }     BUILD_ORDER = ["libjpeg-turbo", "devil", "cryptopp", "lzo", "spdlog", "nlohmann-json", "python"]     DEFAULT_JOBS = max(1, (os.cpu_count() or 4) - 2)     DISK_MIN_GB = 2.0     MAX_LOG_LINES = 5000     _RE_NINJA = re.compile(r"\[(\d+)\s*/\s*(\d+)\]")     URLS = {         "7zr": "https://www.7-zip.org/a/7zr.exe", "7zextra": "https://www.7-zip.org/a/7z2408-extra.7z",         "mingit": "https://github.com/git-for-windows/git/releases/download/v2.46.0.windows.1/MinGit-2.46.0-64-bit.zip",         "cmake": "https://github.com/Kitware/CMake/releases/download/v3.30.3/cmake-3.30.3-windows-x86_64.zip",         "ninja": "https://github.com/ninja-build/ninja/releases/download/v1.12.1/ninja-win.zip",         "nasm": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.03/win64/nasm-2.16.03-win64.zip",     }     BUNDLED_PATHS = [TOOLS, TOOLS / "7zip", TOOLS / "cmake" / "bin", TOOLS / "mingit" / "cmd", TOOLS / "nasm"] @dataclass class BuildConfig:     lib_key: str     tag: str     runtime: C.Runtime     tool_mode: C.ToolMode     jobs: int     prefer_clean: bool     architectures: list[C.Arch]     build_configs: list[C.BuildType]     extra_opts: dict = field(default_factory=dict)     @property     def source_dir(self) -&gt; Path: return C.SRC / self.lib_key / self.tag     def build_dir_for(self, arch: C.Arch, build_type: C.BuildType) -&gt; Path: return C.BUILD / self.lib_key / self.tag / arch.value / self.runtime.value / build_type.value.lower()     def install_dir_for(self, arch: C.Arch, build_type: C.BuildType) -&gt; Path: return getattr(C, f"INST_{self.lib_key.upper().replace('-', '_')}") / arch.value / self.runtime.value / build_type.value.lower() class Bus:     def __init__(self, app: 'App'): self.app = app; self.q = app.q     def log(self, message: str, level: C.LogLevel = C.LogLevel.INFO) -&gt; None: self.q.put(("log", message, level.value))     def progress(self, kind: str, pc: int, text: str = "") -&gt; None: self.q.put((kind, pc, text))     def status(self, text: str) -&gt; None: self.q.put(("status", text))     def update_task_progress(self, task_id: str, progress_text: str) -&gt; None: self.q.put(("task_progress", task_id, progress_text))     def work_mode(self, mode: str): self.q.put(("work_mode", mode))     def work_control(self, action: str): self.q.put(("work_control", action))     def work_text(self, text: str): self.q.put(("work_text", text)) @contextlib.contextmanager def temp_env_path(extra_paths: list[Path]):     old_path = os.environ.get("PATH", "")     new_path = ";".join([str(p) for p in extra_paths if p.exists()] + [old_path])     try: os.environ["PATH"] = new_path; yield     finally: os.environ["PATH"] = old_path def run_out(cmd: list[str] | str, cwd: Path | None = None) -&gt; str:     try:         return subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True, errors="replace",                                      shell=isinstance(cmd, str), creationflags=subprocess.CREATE_NO_WINDOW,                                      cwd=cwd, timeout=30).strip()     except subprocess.TimeoutExpired: return "ERR:Timeout"     except Exception as e: return f"ERR:{e}" def stream_proc(cmd: list[str] | str, env: dict, on_line: Callable[[str], None], log_file_path: Path | None = None,                 low_pri: bool = True, stop_event: threading.Event | None = None, cwd: str | Path | None = None,                 on_start: Callable[[subprocess.Popen], None] | None = None) -&gt; bool:     log_file = None     try:         if log_file_path:             log_file_path.parent.mkdir(parents=True, exist_ok=True)             log_file = open(log_file_path, "w", encoding="utf-8", errors="replace")         creation_flags = subprocess.CREATE_NO_WINDOW | (subprocess.BELOW_NORMAL_PRIORITY_CLASS if low_pri else 0)         p = subprocess.Popen(cmd, shell=isinstance(cmd, str), stdout=subprocess.PIPE, stderr=subprocess.STDOUT,                              universal_newlines=True, encoding="utf-8", errors="replace", bufsize=1,                              env=env, creationflags=creation_flags, cwd=cwd)         if on_start: on_start(p)         for line in iter(p.stdout.readline, ''):             if stop_event and stop_event.is_set():                 p.terminate(); on_line("--- Process terminated by user request. ---"); break             if line:                 clean_line = line.rstrip()                 on_line(clean_line)                 if log_file: log_file.write(clean_line + '\n')         return p.wait() == 0     finally:         if log_file: log_file.close() class SevenZipProgressParser:     _RE_PERCENT = re.compile(r"(\d+)\s*\%")     def __init__(self, bus: Bus): self.bus = bus; self.last_progress_update = -1     def parse_line(self, line: str) -&gt; None:         match = self._RE_PERCENT.search(line)         if match:             pc = int(match.group(1))             if pc &gt; self.last_progress_update:                 self.bus.progress("work", pc, f"%{pc} çıkarılıyor..."); self.last_progress_update = pc                 if pc % 10 == 0: self.bus.log(line, C.LogLevel.CMD)         else: self.bus.log(line, C.LogLevel.CMD) class TaskManager:     def __init__(self, app: 'App'):         self.app = app         self.bus = app.bus         self.task_registry: dict[str, dict[str, Any]] = {}     def register(self, name: str, target: Callable, args: tuple = (), kwargs: dict | None = None,                  stop_event: threading.Event | None = None, is_build_task: bool = True,                  task_id: str | None = None) -&gt; threading.Thread:         task_id = task_id or f"task_{uuid.uuid4().hex[:8]}"         kwargs = kwargs or {}         if "task_id" in target.__code__.co_varnames: kwargs['task_id'] = task_id         thread = threading.Thread(target=target, args=args, kwargs=kwargs, daemon=True, name=task_id)         self.task_registry[task_id] = {'thread': thread, 'name': name, 'start_time': time.time(),                                        'stop_event': stop_event, 'is_build_task': is_build_task,                                        'progress': 'Başlatılıyor...', 'status': 'Başlatılıyor', 'success': None}         thread.start()         self.bus.log(f"Yeni görev başlatıldı: {name} (ID: {task_id})", C.LogLevel.INFO)         return thread     def update_monitor(self) -&gt; None:         task_tree = self.app.ui_manager.task_tree         finished_tasks = {tid: info for tid, info in self.task_registry.items() if not info['thread'].is_alive()}         running_tasks = {tid: info for tid, info in self.task_registry.items() if info['thread'].is_alive()}         for task_id, info in finished_tasks.items():             if info.get('status') == 'Bitti': continue             self.task_registry[task_id]['status'] = 'Bitti'             if task_tree.exists(task_id):                 final_status = "Başarısız" if info.get('success') is False else "Bitti"                 task_tree.set(task_id, "status", final_status)                 task_tree.set(task_id, "progress", "Tamamlandı")                 if final_status == "Başarısız": task_tree.item(task_id, tags=("failed",))                 if not info.get('is_build_task'):                     self.app.after(5000, lambda tid=task_id: task_tree.delete(tid) if task_tree.exists(tid) else None)               for task_id, info in running_tasks.items():             elapsed = time.time() - info['start_time']             runtime_str = str(datetime.timedelta(seconds=int(elapsed)))             status = "Durduruluyor" if info.get('stop_event') and info['stop_event'].is_set() else "Çalışıyor"             values = (info['name'], status, info.get('progress', '...'), runtime_str)             if task_tree.exists(task_id): task_tree.item(task_id, values=values)             else: task_tree.insert("", "end", iid=task_id, values=values)         task_tree.tag_configure("failed", background="#f8d7da", foreground="#842029")         self.app.after(1000, self.update_monitor)     def terminate_selected_task(self) -&gt; None:         task_tree = self.app.ui_manager.task_tree         selected_items = task_tree.selection()         if not selected_items: messagebox.showwarning("Uyarı", "Lütfen durdurmak için bir görev seçin."); return         task_id = selected_items[0]         if task_id in self.task_registry:             task_info = self.task_registry[task_id]             if task_info['thread'].is_alive():                 if task_info.get('stop_event'):                     task_info['stop_event'].set()                     self.bus.log(f"Durdurma sinyali gönderildi: {task_info['name']}", C.LogLevel.WARN)                 else: messagebox.showerror("Hata", "Bu görev, güvenli bir şekilde durdurulmayı desteklemiyor.")             else: messagebox.showwarning("Bilgi", "Seçilen görev artık çalışmıyor veya tamamlanmış.")     def stop_all(self) -&gt; None:         self.bus.log("DURDURMA KOMUTU GÖNDERİLDİ! Tüm aktif görevler durduruluyor.", C.LogLevel.ERR)         for task_info in self.task_registry.values():             if task_info['thread'].is_alive() and task_info.get('stop_event'):                 task_info['stop_event'].set()               active_pids = self.app.build_manager.get_active_pids()         if active_pids:             for pid in active_pids:                 try: psutil.Process(pid).kill()                 except psutil.Error: pass         self.app.build_manager.clear_active_pids()         self.app.set_busy(False) class UIManager:     def __init__(self, app: 'App'):         self.app = app; self.root = app; self.bus = app.bus         self.control_widgets: list[tk.Widget | ttk.Variable] = []         self.log_filter_vars: dict[str, tk.BooleanVar] = {}         self.lib_tabs: dict[str, dict[str, Any]] = {}         self._create_main_layout()         self._create_config_widgets(self.top_frame)         self._create_log_widgets(self.log_frame)         self._create_task_panel(self.task_frame)         self.load_settings()         self.last_disk_io = psutil.disk_io_counters(); self.last_update_time = time.time()           def _create_main_layout(self) -&gt; None:         main_pane = ttk.Panedwindow(self.root, orient=VERTICAL); main_pane.pack(fill=BOTH, expand=True, padx=10, pady=10)         self.top_frame = ttk.Frame(main_pane, padding=5); main_pane.add(self.top_frame, weight=0)         bottom_pane = ttk.Panedwindow(main_pane, orient=HORIZONTAL); main_pane.add(bottom_pane, weight=1)         self.log_frame = ttk.Labelframe(bottom_pane, text="Log", padding=5); bottom_pane.add(self.log_frame, weight=1)         self.task_frame = ttk.Labelframe(bottom_pane, text="Arka Plan Görev Yöneticisi", padding=5); bottom_pane.add(self.task_frame, weight=0)       def _register_control(self, widget: tk.Widget | ttk.Variable) -&gt; Any:         self.control_widgets.append(widget); return widget           def _create_config_widgets(self, parent: ttk.Frame) -&gt; None:         parent.grid_columnconfigure(0, weight=1)         common_panel = ttk.Labelframe(parent, text="Ortak Derleme Ayarları", padding=10); common_panel.grid(row=0, column=0, sticky="ew")         self.notebook = ttk.Notebook(parent, padding=0); self.notebook.grid(row=1, column=0, sticky="nsew", pady=(5,0))         self._create_common_panel_content(common_panel)         for lib_key, meta in C.LIB_META.items():             tab = ttk.Frame(self.notebook, padding=10)             self.notebook.add(tab, text=meta["name"])             self.lib_tabs[lib_key] = {"_tab_id": tab}             self._create_library_tab(tab, lib_key)         tools_panel = ttk.Labelframe(parent, text="Araç Durumu", padding=10); tools_panel.grid(row=2, column=0, sticky="ew", pady=(10,0))         dash = ttk.Labelframe(parent, text="İşlem Durumu", padding=10); dash.grid(row=3, column=0, sticky="ew", pady=(10, 0))         self._create_tools_panel(tools_panel)         self._create_status_panel_content(dash)     def _create_library_tab(self, parent: ttk.Frame, lib_key: str):         parent.grid_columnconfigure(1, weight=1)         f_ver = ttk.Frame(parent); f_ver.grid(row=0, column=0, columnspan=3, sticky="ew", pady=2)         meta = C.LIB_META[lib_key]         ttk.Label(f_ver, text=f"{meta['name']} Sürümü:", width=20).pack(side=LEFT)         if meta.get('type') == 'url':             version_str = meta.get('version', 'Sabit Sürüm')             ttk.Label(f_ver, text=version_str, font="-weight bold").pack(side=LEFT, padx=5, fill=X, expand=True)             self.lib_tabs[lib_key]['fixed_version'] = version_str         else:             cmb_tag = self._register_control(ttk.Combobox(f_ver, width=25, state="readonly"))             cmb_tag.pack(side=LEFT, padx=5, fill=X, expand=True)             self.lib_tabs[lib_key]['cmb_tag'] = cmb_tag             btn_fetch = ttk.Button(f_ver, text="Listele", command=lambda k=lib_key, c=cmb_tag: self.app.build_manager.fetch_tags_async(k, c), width=8, bootstyle=(SECONDARY, OUTLINE))             btn_fetch.pack(side=LEFT)                 f_opts = ttk.Labelframe(parent, text="Özel Ayarlar", padding=5); f_opts.grid(row=1, column=0, columnspan=3, sticky="nsew", pady=(10,0))               opts_dict = {}         if lib_key in ['libjpeg-turbo', 'devil', 'lzo', 'python']:             var_static = self._register_control(ttk.BooleanVar(value=True))             var_shared = self._register_control(ttk.BooleanVar(value=True))             self._register_control(ttk.Checkbutton(f_opts, text="Statik Kütüphane (.lib) Derle", variable=var_static)).pack(anchor=W)             self._register_control(ttk.Checkbutton(f_opts, text="Dinamik Kütüphane (.dll) Derle", variable=var_shared)).pack(anchor=W)             opts_dict = {'static': var_static, 'shared': var_shared}         else:             ttk.Label(f_opts, text="Bu kütüphane için özel bir ayar bulunmuyor.").pack(padx=5, pady=5)               self.lib_tabs[lib_key]['opts'] = opts_dict     def _create_common_panel_content(self, parent: ttk.Frame):         parent.grid_columnconfigure(1, weight=1); parent.grid_columnconfigure(3, weight=1)               ttk.Label(parent, text="Runtime:").grid(row=0, column=0, sticky="w", padx=5, pady=2)         self.runtime = self._register_control(ttk.StringVar(value=C.Runtime.MT.value))         self._register_control(ttk.Combobox(parent, textvariable=self.runtime, values=[r.value for r in C.Runtime], width=10, state="readonly")).grid(row=0, column=1, sticky="ew", padx=5)               ttk.Label(parent, text="Paralel İş:").grid(row=0, column=2, sticky="w", padx=(15, 5), pady=2)         self.jobs = self._register_control(ttk.IntVar(value=C.DEFAULT_JOBS))         self._register_control(ttk.Spinbox(parent, from_=1, to=128, textvariable=self.jobs, width=6)).grid(row=0, column=3, sticky="ew", padx=5)               ttk.Label(parent, text="Araç Modu:").grid(row=1, column=0, sticky="w", padx=5, pady=2)         f_tools = ttk.Frame(parent); f_tools.grid(row=1, column=1, sticky="w")         self.tool_mode = self._register_control(ttk.StringVar(value=C.ToolMode.BUNDLED.value))         self._register_control(ttk.Radiobutton(f_tools, text=".tools", variable=self.tool_mode, value=C.ToolMode.BUNDLED.value, command=self.app.tool_manager.refresh_tools)).pack(side=LEFT)         self._register_control(ttk.Radiobutton(f_tools, text="Sistem", variable=self.tool_mode, value=C.ToolMode.SYSTEM.value, command=self.app.tool_manager.refresh_tools)).pack(side=LEFT, padx=5)               ttk.Label(parent, text="Mimari:").grid(row=1, column=2, sticky="w", padx=(15, 5), pady=2)         f_arch = ttk.Frame(parent); f_arch.grid(row=1, column=3, sticky="w")         self.build_x64 = self._register_control(ttk.BooleanVar(value=True))         self._register_control(ttk.Checkbutton(f_arch, text="x64 (64-bit)", variable=self.build_x64, bootstyle=PRIMARY)).pack(side=LEFT)         self.build_x86 = self._register_control(ttk.BooleanVar(value=True))         self._register_control(ttk.Checkbutton(f_arch, text="x86 (32-bit)", variable=self.build_x86, bootstyle=PRIMARY)).pack(side=LEFT, padx=10)         f_opts = ttk.Frame(parent); f_opts.grid(row=0, column=4, rowspan=2, sticky="e", padx=(20,0))         self.build_release = self._register_control(ttk.BooleanVar(value=True))         self._register_control(ttk.Checkbutton(f_opts, text="Release Derle", variable=self.build_release, bootstyle=PRIMARY)).pack(anchor="w", pady=1)         self.build_debug = self._register_control(ttk.BooleanVar(value=True))         self._register_control(ttk.Checkbutton(f_opts, text="Debug Derle", variable=self.build_debug, bootstyle=PRIMARY)).pack(anchor="w", pady=1)         self.prefer_clean = self._register_control(ttk.BooleanVar(value=False))         self._register_control(ttk.Checkbutton(f_opts, text="Temiz başlangıç", variable=self.prefer_clean, bootstyle=PRIMARY)).pack(anchor="w", pady=1)     def _create_tools_panel(self, parent: ttk.Frame) -&gt; None:         parent.grid_columnconfigure(0, weight=1)         self.tree = ttk.Treeview(parent, columns=("tool", "status", "ver", "src", "path"), show="headings", height=4, bootstyle=INFO)         for c, t, w in (("tool","Araç",120), ("status","Durum",80), ("ver","Sürüm",100), ("src","Kaynak",70), ("path","Yol",300)):             self.tree.heading(c, text=t); self.tree.column(c, width=w, anchor=W)         self.tree.grid(row=0, column=0, sticky="nsew")         btn_frame = ttk.Frame(parent); btn_frame.grid(row=0, column=2, sticky="ns", padx=(10, 0))         ttk.Button(btn_frame, text="Yenile", command=self.app.tool_manager.refresh_tools, width=20, bootstyle=SECONDARY).pack(fill=X, pady=2)         ttk.Button(btn_frame, text="Eksikleri Kur", command=lambda: self.app.tool_manager.install_tools_async(missing=True), width=20).pack(fill=X, pady=2)         ttk.Button(btn_frame, text="Hepsini Yeniden Kur", command=self.app.tool_manager.confirm_reinstall_all, width=20, bootstyle=OUTLINE).pack(fill=X, pady=2)           def _create_status_panel_content(self, dash: ttk.Labelframe) -&gt; None:         dash.grid_columnconfigure(0, weight=1)         self.lbl_status_main = ttk.Label(dash, text="Beklemede...", font="-weight bold -size 10"); self.lbl_status_main.grid(row=0, column=0, columnspan=2, sticky="ew", pady=(0, 5))         pb_frame = ttk.Frame(dash); pb_frame.grid(row=1, column=0, sticky="ew", pady=2); pb_frame.grid_columnconfigure(1, weight=3); pb_frame.grid_columnconfigure(3, weight=2)         ttk.Label(pb_frame, text="İndirme:", width=10).grid(row=0, column=0, sticky="e", padx=5)         self.pb_dl = ttk.Progressbar(pb_frame, length=100); self.pb_dl.grid(row=0, column=1, sticky="ew")         self.lbl_dl_pct = ttk.Label(pb_frame, text="0%", width=5); self.lbl_dl_pct.grid(row=0, column=2, sticky="w", padx=5)         self.lbl_dl_txt = ttk.Label(pb_frame, text="—", width=30); self.lbl_dl_txt.grid(row=0, column=3, sticky="ew", padx=5)         ttk.Label(pb_frame, text="Genel İşlem:", width=10).grid(row=1, column=0, sticky="e", padx=5)         self.pb_work = ttk.Progressbar(pb_frame, length=100); self.pb_work.grid(row=1, column=1, sticky="ew")         self.lbl_work_pct = ttk.Label(pb_frame, text="0%", width=5); self.lbl_work_pct.grid(row=1, column=2, sticky="w", padx=5)         self.lbl_work_txt = ttk.Label(pb_frame, text="—", width=30); self.lbl_work_txt.grid(row=1, column=3, sticky="ew", padx=5)         metrics_frame = ttk.Frame(dash); metrics_frame.grid(row=2, column=0, sticky="ew", pady=5)         ttk.Label(metrics_frame, text="CPU:", font="-weight bold").pack(side=LEFT, padx=(15, 5)); self.lbl_cpu = ttk.Label(metrics_frame, text="0%"); self.lbl_cpu.pack(side=LEFT)         ttk.Label(metrics_frame, text="RAM:", font="-weight bold").pack(side=LEFT, padx=(15, 5)); self.lbl_ram = ttk.Label(metrics_frame, text="0%"); self.lbl_ram.pack(side=LEFT)         ttk.Label(metrics_frame, text="Disk:", font="-weight bold").pack(side=LEFT, padx=(15, 5)); self.lbl_disk = ttk.Label(metrics_frame, text="0 MB/s"); self.lbl_disk.pack(side=LEFT)         ttk.Label(metrics_frame, text=".trash:", font="-weight bold").pack(side=LEFT, padx=(15, 5)); self.lbl_trash_size = ttk.Label(metrics_frame, text="0 B"); self.lbl_trash_size.pack(side=LEFT)         self.btn_empty_trash = ttk.Button(metrics_frame, text="Boşalt", command=self.app.build_manager.empty_trash_folder, bootstyle=(SECONDARY, OUTLINE), width=6); self.btn_empty_trash.pack(side=LEFT, padx=5)               btns = ttk.Frame(dash); btns.grid(row=0, column=1, rowspan=3, sticky="nse", padx=(20, 0))         self.btn_start = ttk.Button(btns, text="Seçili Kütüphaneyi Derle", bootstyle=SUCCESS, width=22, command=self.app.build_manager.start_build); self.btn_start.pack(pady=2, fill=X)         self.btn_build_all = ttk.Button(btns, text="Tüm Kütüphaneleri Derle", bootstyle=(SUCCESS, OUTLINE), width=22, command=self.app.build_manager.start_build_all); self.btn_build_all.pack(pady=2, fill=X)         self.btn_stop = ttk.Button(btns, text="Durdur (Acil)", bootstyle=(DANGER, OUTLINE), width=22, command=self.app.task_manager.stop_all, state=DISABLED); self.btn_stop.pack(pady=2, fill=X)         self.btn_clear_cache = ttk.Button(btns, text="Tüm 'build' Klasörünü Temizle", bootstyle=(WARNING, OUTLINE), width=22, command=self.app.build_manager.clear_build_folder); self.btn_clear_cache.pack(pady=2, fill=X)         self.btn_open_install = ttk.Button(btns, text="Install Klasörünü Aç", bootstyle=(INFO, OUTLINE), width=22, command=self._open_install_folder); self.btn_open_install.pack(pady=2, fill=X)         self.btn_info = ttk.Button(btns, text="ℹ️ Bilgi &amp; Yardım", command=self._show_info_modal, bootstyle=(SECONDARY, OUTLINE)); self.btn_info.pack(fill=X, pady=(8, 0))     def _create_log_widgets(self, parent: ttk.Labelframe) -&gt; None:         parent.rowconfigure(1, weight=1); parent.columnconfigure(0, weight=1)         filter_frame = ttk.Frame(parent, padding=(0, 5)); filter_frame.grid(row=0, column=0, sticky="ew")         ttk.Label(filter_frame, text="Filtrele:").pack(side=LEFT, padx=(0, 5))         for level in C.LogLevel:             var = tk.BooleanVar(value=True)             chk = ttk.Checkbutton(filter_frame, text=level.name.capitalize(), variable=var, command=lambda l=level.value, v=var: self.toggle_log_filter(l, v.get()), bootstyle="outline-toolbutton")             chk.pack(side=LEFT, padx=2); self.log_filter_vars[level.value] = var         log_text_frame = ttk.Frame(parent); log_text_frame.grid(row=1, column=0, sticky="nsew"); log_text_frame.rowconfigure(0, weight=1); log_text_frame.columnconfigure(0, weight=1)         self.log = scrolledtext.ScrolledText(log_text_frame, font=("Consolas", 10), wrap=WORD, undo=True); self.log.grid(row=0, column=0, sticky="nsew")         colors = { C.LogLevel.STAGE: ("#198754", None), C.LogLevel.CMD: ("#0d6efd", None), C.LogLevel.WARN: ("#664d03", "#fff3cd"), C.LogLevel.ERR: ("#842029", "#f8d7da"), C.LogLevel.INFO: ("#6c757d", None) }         for level, (fg, bg) in colors.items(): self.log.tag_config(level.value, foreground=fg, background=bg)     def _create_task_panel(self, parent: ttk.Labelframe) -&gt; None:         parent.rowconfigure(0, weight=1); parent.columnconfigure(0, weight=1)         self.task_tree = ttk.Treeview(parent, columns=("name", "status", "progress", "runtime"), show="headings", height=4)         for c, t, w in (("name","Görev",200), ("status","Durum",100), ("progress","İlerleme",120), ("runtime","Süre",100)):             self.task_tree.heading(c, text=t); self.task_tree.column(c, width=w, anchor=W)         self.task_tree.grid(row=0, column=0, sticky="nsew")         btn_frame = ttk.Frame(parent); btn_frame.grid(row=1, column=0, sticky="ew", pady=(5,0))         ttk.Button(btn_frame, text="Seçili Görevi Durdur", command=self.app.task_manager.terminate_selected_task, bootstyle=(WARNING, OUTLINE)).pack(fill=X)     def pump_queue(self) -&gt; None:         try:             while not self.app.q.empty():                 kind, *rest = self.app.q.get_nowait()                 if kind == "log":                     timestamp = datetime.datetime.now().strftime('%H:%M:%S')                     message, level = cast(str, rest[0]), cast(str, rest[1])                     is_scrolled_to_bottom = self.log.yview()[1] &gt;= 1.0                     for ln in message.splitlines():                         start_index = self.log.index("end-1c")                         self.log.insert(END, f"[{timestamp}] {ln}\n")                         end_index = self.log.index("end-1c")                         self.log.tag_add(level, start_index, end_index)                     if is_scrolled_to_bottom: self.log.see(END)                 elif kind == "status": self.lbl_status_main.config(text=rest[0])                 elif kind == "dl":                     val, txt = int(rest[0]), str(rest[1])                     self.pb_dl['value'] = val; self.lbl_dl_pct.config(text=f"{val}%"); self.lbl_dl_txt.config(text=txt)                 elif kind == "work":                     val, txt = int(rest[0]), str(rest[1])                     self.pb_work['value'] = val; self.lbl_work_pct.config(text=f"{val}%"); self.lbl_work_txt.config(text=txt)                 elif kind == "task_progress":                     task_id, progress_text = cast(str, rest[0]), cast(str, rest[1])                     if task_id in self.app.task_manager.task_registry: self.app.task_manager.task_registry[task_id]['progress'] = progress_text                 elif kind == "work_mode": self.pb_work.config(mode=rest[0])                 elif kind == "work_control" and rest[0] == "start": self.pb_work.start()                 elif kind == "work_control" and rest[0] == "stop": self.pb_work.stop()                 elif kind == "work_text": self.lbl_work_txt.config(text=rest[0])         except queue.Empty: pass         finally: self.app.after(100, self.pump_queue)     def set_controls_state(self, state: str) -&gt; None:         self.btn_start.config(state=state)         self.btn_build_all.config(state=state)         self.btn_stop.config(state='normal' if state == 'disabled' else 'disabled')         self.btn_clear_cache.config(state=state); self.btn_open_install.config(state=state)         for widget in self.control_widgets:             try:                 if isinstance(widget, ttk.Variable): continue                 widget.config(state="readonly" if isinstance(widget, ttk.Combobox) and state == 'normal' else state)             except tk.TclError: pass         self.tree.config(selectmode='browse' if state == 'normal' else 'none')           def update_build_stats(self, last_line: str, task_id: str) -&gt; None:         m = C._RE_NINJA.search(last_line)         if m:             d, t = int(m.group(1)), int(m.group(2))             pct = int(100.0 * d / max(1, t))             progress_text = f"[{d}/{t}] {pct}%"             self.bus.update_task_progress(task_id, progress_text)             self.bus.progress("work", pct, progress_text)         else: self.bus.log(last_line, C.LogLevel.CMD)     def update_resource_monitor(self) -&gt; None:         try:             self.lbl_cpu.config(text=f"{psutil.cpu_percent(interval=None):.1f}%")             self.lbl_ram.config(text=f"{psutil.virtual_memory().percent:.1f}%")             current_time, current_io = time.time(), psutil.disk_io_counters()             delta_time = current_time - self.last_update_time             if delta_time &gt; 0:                 total_mb_s = (current_io.read_bytes - self.last_disk_io.read_bytes + current_io.write_bytes - self.last_disk_io.write_bytes) / (1024 * 1024) / delta_time                 self.lbl_disk.config(text=f"{total_mb_s:.1f} MB/s")             self.last_update_time, self.last_disk_io = current_time, current_io         except (psutil.Error, ZeroDivisionError): pass         finally: self.app.after(1000, self.update_resource_monitor)       def update_trash_monitor(self) -&gt; None:         try:             if C.TRASH.exists():                 total_size = sum(f.stat().st_size for f in C.TRASH.glob('**/*') if f.is_file())                 if total_size &lt; 1024**2: size_str = f"{total_size/1024:.1f} KB"                 elif total_size &lt; 1024**3: size_str = f"{total_size/1024**2:.1f} MB"                 else: size_str = f"{total_size/1024**3:.1f} GB"                 self.lbl_trash_size.config(text=size_str)             else: self.lbl_trash_size.config(text="0 B")         except Exception: self.lbl_trash_size.config(text="N/A")         finally: self.app.after(5000, self.update_trash_monitor)               def load_settings(self) -&gt; None:         try:             if C.CONFIG_FILE.exists():                 with open(C.CONFIG_FILE, 'r', encoding='utf-8') as f: settings = json.load(f)                 self.runtime.set(settings.get("runtime", C.Runtime.MT.value))                 self.jobs.set(settings.get("jobs", C.DEFAULT_JOBS))                 self.tool_mode.set(settings.get("tool_mode", C.ToolMode.BUNDLED.value))                 self.build_release.set(settings.get("build_release", True))                 self.build_debug.set(settings.get("build_debug", True))                 self.build_x64.set(settings.get("build_x64", True))                 self.build_x86.set(settings.get("build_x86", True))                 self.prefer_clean.set(settings.get("prefer_clean", False))                 for lib_key, tab_widgets in self.lib_tabs.items():                     meta = C.LIB_META.get(lib_key, {})                     if meta.get('type') != 'url':                         last_tag = settings.get(f"last_tag_{lib_key}")                         if last_tag: tab_widgets['last_tag_from_config'] = last_tag                 self.bus.log("Ayarlar 'config.json' dosyasından yüklendi.", C.LogLevel.INFO)         except (json.JSONDecodeError, TypeError, KeyError) as e: self.bus.log(f"'config.json' dosyası okunurken hata: {e}", C.LogLevel.WARN)     def save_settings(self) -&gt; None:         settings = {             "runtime": self.runtime.get(), "jobs": self.jobs.get(), "tool_mode": self.tool_mode.get(),             "build_release": self.build_release.get(), "build_debug": self.build_debug.get(),             "build_x64": self.build_x64.get(), "build_x86": self.build_x86.get(),             "prefer_clean": self.prefer_clean.get()         }         for lib_key, tab_widgets in self.lib_tabs.items():             if 'cmb_tag' in tab_widgets:                 tag = tab_widgets['cmb_tag'].get()                 if tag: settings[f"last_tag_{lib_key}"] = tag         try:             with open(C.CONFIG_FILE, 'w', encoding='utf-8') as f: json.dump(settings, f, indent=4)         except OSError as e: self.bus.log(f"Ayarlar kaydedilemedi: {e}", C.LogLevel.ERR)     def _open_install_folder(self) -&gt; None:         C.INST.mkdir(parents=True, exist_ok=True)         try: os.startfile(C.INST)         except Exception as e: messagebox.showerror("Hata", f"'{C.INST}' klasörü açılamadı:\n{e}")     def _show_info_modal(self) -&gt; None:         info_window = ttk.Toplevel(title="Uygulama Kullanım Rehberi"); info_window.geometry("850x750")         info_window.transient(self.root); info_window.grab_set()         text_area = scrolledtext.ScrolledText(info_window, wrap=WORD, font=("Segoe UI", 10), relief="flat", padx=10, pady=10)         text_area.pack(fill=BOTH, expand=True)         info_text = """Lib Builder Pro v7.4 (Final) Kullanım Rehberi ============================================ Bu araç libjpeg-turbo, DevIL, Crypto++, LZO, spdlog ve nlohmann/json kütüphanelerini Windows ortamında 32-bit ve 64-bit olarak derleme/kurma sürecini otomatikleştirmek için tasarlanmıştır. --- ÖN GEREKSİNİMLER --- - **Windows 10/11 (64-bit)** - **Visual Studio 2022:** "Masaüstü geliştirme (C++)" iş yükünün kurulu olması ZORUNLUDUR. --- ADIM ADIM KULLANIM --- 1.  **Araç Kurulumu:** Uygulamayı ilk açtığınızda "Araç Durumu" panelinden **"Eksikleri Kur"** butonuna tıklayarak gerekli araçları (CMake, Ninja vb.) kurun. 2.  **Derleme Ayarları:**     -   Sürüm seçimi gerektiren kütüphaneler için derlemek istediğiniz sürümü **"Listele"** butonu ile seçin.     -   **Ortak Ayarlar** bölümünden `Runtime`, `Mimari` ve `Release/Debug` gibi seçenekleri belirleyin. 3.  **Derlemeyi Başlatma:**     -   **"Seçili Kütüphaneyi Derle":** Aktif olan sekmeyi derler.     -   **"Tüm Kütüphaneleri Derle":** Tüm kütüphaneleri sırayla (bağımlılıkları gözeterek) derler. 4.  **Sonuçlar:** Derleme bittiğinde, dosyalar `install/&lt;kütüphane_adı&gt;/&lt;mimari&gt;/...` klasörüne kaydedilir. --- KÜTÜPHANEYE ÖZEL NOTLAR --- -   **DevIL:** Bu kütüphane, libjpeg-turbo'ya bağımlıdır. "Tümünü Derle" seçeneği bu sırayı otomatik yönetir. Tek tek derliyorsanız, **önce libjpeg-turbo'yu derlemeniz GEREKİR.** -   **Crypto++, spdlog, Python:** MSBuild/CMake ile statik kütüphane (.lib) olarak derlenirler. -   **LZO:** CMake ile derlenir. -   **nlohmann/json:** Bu kütüphane sadece başlık dosyalarından oluşur (header-only). Derleme işlemi yerine sadece ilgili dosyalar `install` klasörüne kopyalanır. """         text_area.insert(END, info_text.strip()); text_area.config(state=DISABLED)         ttk.Button(info_window, text="Kapat", command=info_window.destroy, bootstyle=SECONDARY).pack(pady=10)           def toggle_log_filter(self, tag_name: str, is_visible: bool) -&gt; None: self.log.tag_config(tag_name, elide=not is_visible) class ToolManager:     def __init__(self, app: 'App'):         self.app = app; self.bus = app.bus         self.TOOLS_META = [             {"key":"7zip", "name":"7-Zip", "exe":"7z", "altexe": ["7zr"], "ensure":self._ensure_7zip, "vercmd":["7z"]},             {"key":"git", "name":"Git (MinGit)", "exe":"git", "ensure":lambda bus, se: self._ensure_zip_tool("mingit", C.URLS["mingit"], "mingit", bus, se), "vercmd":["git", "--version"]},             {"key":"cmake", "name":"CMake", "exe":"cmake", "ensure":lambda bus, se: self._ensure_zip_tool("cmake", C.URLS["cmake"], "cmake", bus, se), "vercmd":["cmake", "--version"]},             {"key":"ninja", "name":"Ninja", "exe":"ninja", "ensure":lambda bus, se: self._ensure_zip_tool("ninja", C.URLS["ninja"], None, bus, se), "vercmd":["ninja", "--version"]},             {"key":"nasm", "name":"NASM", "exe":"nasm", "ensure":lambda bus, se: self._ensure_zip_tool("nasm", C.URLS["nasm"], "nasm", bus, se), "vercmd":["nasm", "-v"]},         ]           def vs_env_path(self) -&gt; str | None:         vswhere = Path(r"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe")         if vswhere.exists():             cmd = [str(vswhere), "-latest", "-requires", "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath"]             base_path = run_out(subprocess.list2cmdline(cmd)).splitlines()             base = Path((base_path[0] if base_path else "").strip())             if base.is_dir():                 candidate = base / "VC/Auxiliary/Build/vcvarsall.bat"                 if candidate.exists(): return str(candidate)         for ver in ("2022", "2019"):             for ed in ("Community", "Professional", "Enterprise", "BuildTools"):                 p = Path(rf"C:\Program Files\Microsoft Visual Studio\{ver}\{ed}\VC\Auxiliary\Build\vcvarsall.bat")                 if p.exists(): return str(p)         return None     def which(self, name: str, bundled_only: bool) -&gt; str | None:         paths = C.BUNDLED_PATHS if bundled_only else (os.environ.get("PATH", "").split(os.pathsep) + [str(p) for p in C.BUNDLED_PATHS])         for folder in paths:             if not folder: continue             p = Path(folder)             if not p.is_dir(): continue             for ext in ("", ".exe"):                 fp = p / (name + ext)                 if fp.exists() and fp.is_file(): return str(fp)         return None     def detect_tool(self, meta: dict, mode: C.ToolMode) -&gt; tuple[str | None, str | None, str | None]:         executables = [meta['exe']] + meta.get('altexe', [])         p = next((path for exe in executables if (path := self.which(exe, mode == C.ToolMode.BUNDLED))), None)         if not p and mode == C.ToolMode.SYSTEM:             p = next((path for exe in executables if (path := shutil.which(exe))), None)         if not p: return None, None, None               src = "Bundled" if Path(p).resolve().is_relative_to(C.TOOLS.resolve()) else "System"         vercmd = [p] + meta.get("vercmd", ["--version"])[1:]         out = run_out(vercmd, cwd=Path(p).parent)         if out.startswith("ERR:"): return p, "Hata", src         ver = next((m.group(1) for pat in [r"v?(\d+\.\d+(?:\.\d+)?)", r"version[\s:]*(\d+\.\d+(?:\.\d+)?)"] if (m := re.search(pat, out, re.I))), out.splitlines()[0][:40])         return p, ver, src     def refresh_tools(self) -&gt; None:         ui = self.app.ui_manager         ui.tree.delete(*ui.tree.get_children())         mode = C.ToolMode(ui.tool_mode.get())         for meta in self.TOOLS_META:             path, ver, src = self.detect_tool(meta, mode)             ui.tree.insert("", END, iid=meta["key"], values=(meta["name"], "✔️ Yüklü" if path else "❌ Yok", ver or "-", src or "-", path or "-"), tags=() if path else ("missing",))         ui.tree.tag_configure("missing", background="#f8d7da", foreground="#842029")     def confirm_reinstall_all(self) -&gt; None:         if messagebox.askyesno("Onay", "Tüm araçlar .tools klasöründen silinip yeniden indirilecektir. Devam etmek istiyor musunuz?"):             self.install_tools_async(reinstall=True)     def install_tools_async(self, missing: bool = False, reinstall: bool = False) -&gt; None:         if self.app._is_busy: return         self.app.set_busy(True)         stop_event = threading.Event()         self.app.task_manager.register(name="Araç Kurulum Yöneticisi", target=self._install_tools_worker,                                        args=(missing, reinstall, stop_event), stop_event=stop_event, is_build_task=False)     def _install_tools_worker(self, missing: bool, reinstall: bool, stop_event: threading.Event, task_id: str) -&gt; None:         try:             metas_to_install = [m for m in self.TOOLS_META if not self.detect_tool(m, C.ToolMode.BUNDLED)[0]] if missing else self.TOOLS_META             if not metas_to_install and missing:                 self.bus.log("Tüm araçlar zaten .tools klasöründe yüklü.", C.LogLevel.INFO)                 messagebox.showinfo("Araçlar", "Eksik araç bulunamadı."); return             with ThreadPoolExecutor() as executor:                 futures = [executor.submit(m["ensure"], self.bus, stop_event) for m in metas_to_install if "ensure" in m]                 for future in futures: future.result()             if not stop_event.is_set(): messagebox.showinfo("Araçlar", "Seçilen araçların kurulumu tamamlandı.")             self.app.task_manager.task_registry[task_id]['success'] = True         except Exception as e:             if not stop_event.is_set(): messagebox.showerror("Hata", f"Araç kurulumu başarısız oldu: {e}")             self.app.task_manager.task_registry[task_id]['success'] = False         finally:             self.app.after(0, self.refresh_tools); self.app.set_busy(False)     def _http_get(self, url: str, dest: Path, bus: Bus) -&gt; bool:         bus.log(f"[DL] Başlıyor: {Path(url).name}", C.LogLevel.STAGE)         dest.parent.mkdir(parents=True, exist_ok=True)         try:             with requests.get(url, stream=True, timeout=60, verify=True) as r:                 r.raise_for_status()                 total_size = int(r.headers.get("Content-Length", 0))                 done, last_pc = 0, -1                 with open(dest, "wb") as f:                     for chunk in r.iter_content(256 * 1024):                         f.write(chunk); done += len(chunk)                         if total_size:                             pc = int(done * 100 / total_size)                             if pc != last_pc: bus.progress("dl", pc, f"{done/1e6:.1f}/{total_size/1e6:.1f} MB"); last_pc = pc             bus.progress("dl", 100, "OK"); return True         except requests.exceptions.SSLError as e:              bus.log(f"[DL] SSL Hatası: {e}. Sunucu sertifikasıyla ilgili bir sorun olabilir.", C.LogLevel.ERR)              return False         except requests.RequestException as e:             bus.log(f"[DL] Hata: {e}", C.LogLevel.ERR)             return False     def _seven_extract(self, arc: Path, out: Path, bus: Bus) -&gt; bool:         seven = self.which("7z", False) or self.which("7zr", False)         if not seven:             try:                 self._ensure_7zip(bus, None)                 seven = self.which("7z", False) or self.which("7zr", False)             except Exception as e:                 bus.log(f"7-Zip otomatik olarak kurulamadı: {e}", C.LogLevel.ERR)         if not seven:             bus.log("7-Zip (7z.exe or 7zr.exe) bulunamadı.", C.LogLevel.ERR)             return False         parser = SevenZipProgressParser(bus)         cmd = f'"{seven}" x "{arc}" -o"{out}" -y -bsp1'         return stream_proc(cmd, os.environ.copy(), on_line=parser.parse_line)     def _python_unzip(self, z: Path, out: Path, bus: Bus) -&gt; bool:         with zipfile.ZipFile(z) as zp:             infolist = zp.infolist(); n = len(infolist); last_pc = -1             for i, info in enumerate(infolist, 1):                 zp.extract(info, out)                 pc = int(i * 100 / n) if n else 0                 if pc != last_pc: bus.progress("work", pc, f"{i}/{n}"); last_pc = pc         return True     def _ensure_zip_tool(self, name: str, url: str, subfolder: str | None, bus: Bus, stop_event: threading.Event | None):         if stop_event and stop_event.is_set(): return         zip_path = C.DL / f"{name}.zip"         if not zip_path.exists() and not self._http_get(url, zip_path, bus): raise RuntimeError(f"{name} indirilemedi")         out = C.TOOLS / subfolder if subfolder else C.TOOLS         out.mkdir(parents=True, exist_ok=True)         if not (self._seven_extract(zip_path, out, bus) or self._python_unzip(zip_path, out, bus)): raise RuntimeError(f"{name} arşivi açılamadı")         if subfolder:             kids = list(out.glob("*"))             if len(kids) == 1 and kids[0].is_dir():                 tmp = out.parent / f"_{subfolder}"; shutil.move(str(kids[0]), str(tmp))                 shutil.rmtree(out, ignore_errors=True); tmp.rename(out)     def _ensure_7zip(self, bus: Bus, stop_event: threading.Event | None) -&gt; None:         zdir = C.TOOLS / "7zip"; zdir.mkdir(exist_ok=True)         if (zdir / "7z.exe").exists(): return         zr, ze = zdir / "7zr.exe", C.DL / "7zextra.7z"         if not zr.exists(): self._http_get(C.URLS["7zr"], zr, bus)         if not ze.exists(): self._http_get(C.URLS["7zextra"], ze, bus)               subprocess.call(f'"{zr}" x "{ze}" -o"{zdir}" -y', shell=True, creationflags=subprocess.CREATE_NO_WINDOW)         if not (zdir / "7z.exe").exists():             raise RuntimeError("7-Zip extraction failed. 7z.exe was not found after extraction.")           class BuildManager:     def __init__(self, app: 'App'):         self.app = app; self.bus = app.bus         self.active_pids = set(); self.pids_lock = threading.Lock()       def add_pid(self, pid: int) -&gt; None: self.active_pids.add(pid)     def remove_pid(self, pid: int) -&gt; None: self.active_pids.discard(pid)     def get_active_pids(self) -&gt; set[int]: return self.active_pids.copy()     def clear_active_pids(self) -&gt; None: self.active_pids.clear()     def _get_common_build_config_parts(self) -&gt; dict | None:         ui = self.app.ui_manager         architectures = [arch for arch, var in [(C.Arch.X64, ui.build_x64), (C.Arch.X86, ui.build_x86)] if var.get()]         if not architectures: messagebox.showwarning("Seçim Gerekli", "En az bir mimari (x86/x64) seçin."); return None               build_configs = [btype for btype, var in [(C.BuildType.RELEASE, ui.build_release), (C.BuildType.DEBUG, ui.build_debug)] if var.get()]         if not build_configs: messagebox.showwarning("Seçim Gerekli", "En az bir derleme konfigürasyonu (Release/Debug) seçin."); return None               return {             "runtime": C.Runtime(ui.runtime.get()), "tool_mode": C.ToolMode(ui.tool_mode.get()),             "jobs": ui.jobs.get(), "prefer_clean": ui.prefer_clean.get(),             "architectures": architectures, "build_configs": build_configs         }     def start_build(self) -&gt; None:         if self.app._is_busy: return         ui = self.app.ui_manager               selected_tab_id = ui.notebook.index(ui.notebook.select())         if selected_tab_id is None: messagebox.showwarning("Seçim Gerekli", "Lütfen derlemek için bir kütüphane sekmesi seçin."); return         lib_key = list(C.LIB_META.keys())[selected_tab_id]         meta = C.LIB_META[lib_key]         common_parts = self._get_common_build_config_parts()         if not common_parts: return         tab_widgets = ui.lib_tabs.get(lib_key)                 tag = ""         if meta.get('type') == 'url':             tag = meta.get('version')         else:             tag = tab_widgets['cmb_tag'].get()         if not tag: messagebox.showwarning("Seçim Gerekli", f"{meta['name']} için bir sürüm seçin."); return               extra_opts = {key: var.get() for key, var in tab_widgets.get('opts', {}).items()}         config = BuildConfig(lib_key=lib_key, tag=tag, **common_parts, extra_opts=extra_opts)               stop_event = threading.Event()         self.app.task_manager.register(             name=f"Ana Derleme Yöneticisi ({meta['name']})",             target=self._build_manager_worker, args=([config], stop_event),             stop_event=stop_event, is_build_task=False         )     def start_build_all(self) -&gt; None:         if self.app._is_busy: return         ui = self.app.ui_manager               common_parts = self._get_common_build_config_parts()         if not common_parts: return         configs_to_build = []         for lib_key in C.BUILD_ORDER:             meta = C.LIB_META[lib_key]             tab_widgets = ui.lib_tabs.get(lib_key)                         tag = ""             if meta.get('type') == 'url':                 tag = meta.get('version')             else:                 tag = tab_widgets['cmb_tag'].get()                         if not tag: messagebox.showwarning("Seçim Gerekli", f"Tümünü derlemek için önce {meta['name']} sekmesinden bir sürüm seçmelisiniz."); return                         extra_opts = {key: var.get() for key, var in tab_widgets.get('opts', {}).items()}             configs_to_build.append(BuildConfig(lib_key=lib_key, tag=tag, **common_parts, extra_opts=extra_opts))               stop_event = threading.Event()         self.app.task_manager.register(             name="Tüm Kütüphaneleri Derleme Yöneticisi",             target=self._build_manager_worker, args=(configs_to_build, stop_event),             stop_event=stop_event, is_build_task=False         )     def _build_manager_worker(self, configs: list[BuildConfig], stop_event: threading.Event, task_id: str) -&gt; None:         self.app.set_busy(True)         self.app.ui_manager.log.delete("1.0", END)         overall_success = True               try:             if configs and configs[0].prefer_clean:                 self._do_clean_start()             for config in configs:                 if stop_event.is_set(): overall_success = False; break                 self.bus.log(f"====== {C.LIB_META[config.lib_key]['name'].upper()} İÇİN DERLEME SÜRECİ BAŞLIYOR ======", C.LogLevel.STAGE)                 if not self._run_preflight_checks(config):                     overall_success = False; break                               success = self._build_single_library(config, stop_event)                 if not success:                     overall_success = False                     self.bus.log(f"{C.LIB_META[config.lib_key]['name']} derlemesi başarısız oldu. Tüm işlemler durduruluyor.", C.LogLevel.ERR)                     break                   except Exception as e:             if not stop_event.is_set():                 self.bus.log(f"ANA YÖNETİCİDE KRİTİK HATA: {e}\n{traceback.format_exc()}", C.LogLevel.ERR)                 messagebox.showerror("Kritik Hata", f"Beklenmedik hata:\n\n{e}")             overall_success = False         finally:             self._finalize_build_process(overall_success)             self.app.task_manager.task_registry[task_id]['success'] = overall_success             self.app.set_busy(False)               def _build_single_library(self, config: BuildConfig, stop_event: threading.Event) -&gt; bool:         source_dir = self._fetch_source(config.lib_key, config.tag, stop_event)         if not source_dir: return False         build_actions = {             "libjpeg-turbo": self._build_libjpeg, "devil": self._build_devil,             "cryptopp": self._build_cryptopp, "lzo": self._build_lzo,             "spdlog": self._build_spdlog, "nlohmann-json": self._build_nlohmann_json,             "python": self._build_python         }         action = build_actions.get(config.lib_key)         if not action: return False         for arch in config.architectures:             if stop_event.is_set(): return False             self.bus.log(f"===== {arch.value.upper()} MİMARİSİ İÇİN DERLEME BAŞLIYOR =====", C.LogLevel.STAGE)             if not action(config, source_dir, arch, stop_event):                 return False         return True     def _run_command_in_vs_env(self, cmd_list: list[str], cwd: Path, log_name_prefix: str, arch: C.Arch, stop_event: threading.Event, on_line: Callable[[str],None]=None) -&gt; bool:         vsbat = self.app.tool_manager.vs_env_path()         if not vsbat: self.bus.log("VS Geliştirici Ortamı (vcvarsall.bat) bulunamadı.", C.LogLevel.ERR); return False         arch_param = "amd64" if arch == C.Arch.X64 else "x86"         log_path = C.LOGS / f"{log_name_prefix}_{arch.value}_{datetime.datetime.now():%Y%m%d-%H%M%S}.log"         self.bus.log(f"Çıktı şu dosyaya loglanıyor: {log_path.name}", C.LogLevel.STAGE)         bat_path = C.DEPS / f"_{log_name_prefix}_runner.bat"         with open(bat_path, "w", encoding="utf-8", newline="\r\n") as f:             f.write(f'@echo off\r\nchcp 65001 &gt; nul\r\n')             f.write(f'call "{vsbat}" {arch_param}\r\n')             f.write(f'{" ".join(cmd_list)}\r\n')         env = os.environ.copy()         if self.app.ui_manager.tool_mode.get() == C.ToolMode.BUNDLED:             env["PATH"] = ";".join([str(p) for p in C.BUNDLED_PATHS if p.exists()] + [env.get('PATH', '')])         ok = stream_proc(f'cmd /d /c "{bat_path}"', env, on_line=on_line if on_line else lambda s: self.bus.log(s, C.LogLevel.CMD),                          log_file_path=log_path, stop_event=stop_event, cwd=cwd, on_start=lambda p: self.add_pid(p.pid))               bat_path.unlink(missing_ok=True)         if not ok: self.bus.log(f"Komut başarısız oldu. Detaylar için logu inceleyin: {log_path}", C.LogLevel.ERR)         return ok     def _build_libjpeg(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -&gt; bool:         for build_type in config.build_configs:             if stop_event.is_set(): return False             self.bus.log(f"--- libjpeg-turbo ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE)             build_dir = config.build_dir_for(arch, build_type); install_dir = config.install_dir_for(arch, build_type)                       cmake_exe = self.app.tool_manager.which("cmake", config.tool_mode == C.ToolMode.BUNDLED)             if not cmake_exe: return False                       cmake_args = [f'"{cmake_exe}"', "-S", f'"{source_dir}"', "-B", f'"{build_dir}"', "-G", "Ninja",                           f"-DCMAKE_INSTALL_PREFIX={install_dir}", f"-DCMAKE_BUILD_TYPE={build_type.value}",                           f"-DENABLE_STATIC={'ON' if config.extra_opts.get('static') else 'OFF'}",                           f"-DENABLE_SHARED={'ON' if config.extra_opts.get('shared') else 'OFF'}",                           f"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded{'Debug' if build_type == C.BuildType.DEBUG else ''}{'DLL' if config.runtime == C.Runtime.MD else ''}"]             if not self._run_command_in_vs_env(cmake_args, source_dir, f"ljt_config_{build_type.value.lower()}", arch, stop_event): return False             build_args = [f'"{cmake_exe}"', "--build", f'"{build_dir}"', "--config", build_type.value, "--target", "install", "--parallel", str(config.jobs)]             if not self._run_command_in_vs_env(build_args, source_dir, f"ljt_build_{build_type.value.lower()}", arch, stop_event, on_line=lambda s: self.app.ui_manager.update_build_stats(s, 'build_task')): return False         return True     def _patch_devil_rc_file(self, source_dir: Path) -&gt; bool:         rc_file = source_dir / "DevIL" / "src-IL" / "msvc" / "IL.rc"         if not rc_file.exists():             self.bus.log(f"DevIL RC dosyası bulunamadı, patch atlanıyor: {rc_file}", C.LogLevel.WARN)             return True               try:             self.bus.log(f"DevIL RC dosyası ({rc_file.name}) MFC bağımlılığını kaldırmak için yamalanıyor...", C.LogLevel.STAGE)             content = rc_file.read_text(encoding='cp1252', errors='ignore')             if '#include "afxres.h"' in content:                 content = content.replace('#include "afxres.h"', '// #include "afxres.h" -- Patched by LibBuilder')                 rc_file.write_text(content, encoding='cp1252')                 self.bus.log("Yama başarıyla uygulandı.", C.LogLevel.INFO)             else:                 self.bus.log("afxres.h bağımlılığı bulunamadı, yama gerekli değil.", C.LogLevel.INFO)             return True         except Exception as e:             self.bus.log(f"DevIL RC dosyası yamalanırken hata: {e}", C.LogLevel.ERR)             return False     def _build_devil(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -&gt; bool:         if not self._patch_devil_rc_file(source_dir):             return False         for build_type in config.build_configs:             if stop_event.is_set(): return False             self.bus.log(f"--- DevIL ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE)             build_dir = config.build_dir_for(arch, build_type); install_dir = config.install_dir_for(arch, build_type)                       ljt_install_dir = C.INST_LIBJPEG_TURBO / arch.value / config.runtime.value / build_type.value.lower()             ljt_lib = ljt_install_dir / "lib" / "jpeg-static.lib"; ljt_inc = ljt_install_dir / "include"             if config.extra_opts.get('static') and not (ljt_lib.exists() and ljt_inc.exists()):                 msg = f"DevIL bağımlılığı (libjpeg-turbo statik) bulunamadı: {ljt_install_dir}. Lütfen önce libjpeg-turbo'yu aynı ayarlarla ({arch.value}/{config.runtime.value}/{build_type.value}) statik olarak derleyin."                 self.bus.log(msg, C.LogLevel.ERR); messagebox.showerror("Bağımlılık Hatası", msg); return False             cmake_exe = self.app.tool_manager.which("cmake", config.tool_mode == C.ToolMode.BUNDLED)             if not cmake_exe: return False             cmake_source_dir = source_dir / "DevIL"             if not (cmake_source_dir / "CMakeLists.txt").exists():                 self.bus.log(f"DevIL için CMakeLists.txt beklenen yolda bulunamadı: {cmake_source_dir}", C.LogLevel.ERR)                 return False             cmake_args = [f'"{cmake_exe}"', "-S", f'"{cmake_source_dir}"', "-B", f'"{build_dir}"', "-G", "Ninja",                           f"-DCMAKE_INSTALL_PREFIX={install_dir}", f"-DCMAKE_BUILD_TYPE={build_type.value}",                           f"-DBUILD_STATIC_LIBS={'ON' if config.extra_opts.get('static') else 'OFF'}",                           f"-DBUILD_SHARED_LIBS={'ON' if config.extra_opts.get('shared') else 'OFF'}",                           f"-DJPEG_INCLUDE_DIR={ljt_inc}", f"-DJPEG_LIBRARY={ljt_lib}", "-DIL_USE_JPEG=ON",                           "-DIL_USE_PNG=OFF", "-DIL_USE_TIFF=OFF", "-DIL_USE_LCMS=OFF", "-DIL_USE_JASPER=OFF",                           f"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded{'Debug' if build_type == C.BuildType.DEBUG else ''}{'DLL' if config.runtime == C.Runtime.MD else ''}"]             if not self._run_command_in_vs_env(cmake_args, cmake_source_dir, f"devil_config_{build_type.value.lower()}", arch, stop_event): return False             build_args = [f'"{cmake_exe}"', "--build", f'"{build_dir}"', "--config", build_type.value, "--target", "install", "--parallel", str(config.jobs)]             if not self._run_command_in_vs_env(build_args, cmake_source_dir, f"devil_build_{build_type.value.lower()}", arch, stop_event, on_line=lambda s: self.app.ui_manager.update_build_stats(s, 'build_task')): return False         return True         def _build_cryptopp(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -&gt; bool:         vs_env = self.app.tool_manager.vs_env_path()         if not vs_env: return False         msbuild_exe = Path(vs_env).parent.parent.parent.parent / "MSBuild/Current/Bin/MSBuild.exe"         if not msbuild_exe.exists(): self.bus.log(f"MSBuild.exe bulunamadı: {msbuild_exe}", C.LogLevel.ERR); return False               sln_file = source_dir / "cryptest.sln"         if not sln_file.exists(): self.bus.log(f"Crypto++ solution dosyası bulunamadı: {sln_file}", C.LogLevel.ERR); return False               for build_type in config.build_configs:             if stop_event.is_set(): return False             self.bus.log(f"--- Crypto++ ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE)             install_dir = config.install_dir_for(arch, build_type)                       runtime_flag = "MultiThreaded" if config.runtime == C.Runtime.MT else "MultiThreadedDLL"             if build_type == C.BuildType.DEBUG: runtime_flag += "Debug"             platform = "x64" if arch == C.Arch.X64 else "Win32"             build_args = [f'"{msbuild_exe}"', f'"{sln_file}"', "/t:cryptlib", f"/p:Configuration={build_type.value}",                           f"/p:Platform={platform}", f"/p:RuntimeLibrary={runtime_flag}", f"/p:UseMultiToolTask=true",                           f"/p:BuildInParallel=true", f"/m:{config.jobs}"]                       self.bus.work_mode("indeterminate")             self.bus.work_control("start")             def on_line_msbuild(line: str):                 self.bus.log(line, C.LogLevel.CMD)                 if ".cpp" in line or ".asm" in line:                     self.bus.work_text(Path(line.split(' ')[0]).name)             build_ok = self._run_command_in_vs_env(build_args, source_dir, f"cryptopp_build_{build_type.value.lower()}", arch, stop_event, on_line=on_line_msbuild)                       self.bus.work_control("stop")             self.bus.work_mode("determinate")             self.bus.progress("work", 100 if build_ok else 0, "Tamamlandı" if build_ok else "Hata")             if not build_ok: return False                       self.bus.log("Derleme sonrası kopyalama işlemi...", C.LogLevel.STAGE)             try:                 output_dir = source_dir / ("x64" if arch == C.Arch.X64 else "Win32") / "Output" / build_type.value                 install_dir.mkdir(parents=True, exist_ok=True)                 (install_dir / "lib").mkdir(exist_ok=True); (install_dir / "include").mkdir(exist_ok=True)                 for f in output_dir.glob("*.lib"): shutil.copy2(f, install_dir / "lib")                 for header in source_dir.glob('*.h'): shutil.copy2(header, install_dir / "include")             except Exception as e: self.bus.log(f"Crypto++ dosyaları kopyalanırken hata: {e}", C.LogLevel.ERR); return False         return True         def _build_lzo(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -&gt; bool:         for build_type in config.build_configs:             if stop_event.is_set(): return False             self.bus.log(f"--- LZO ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE)             build_dir = config.build_dir_for(arch, build_type)             install_dir = config.install_dir_for(arch, build_type)                         cmake_exe = self.app.tool_manager.which("cmake", config.tool_mode == C.ToolMode.BUNDLED)             if not cmake_exe: return False                         cmake_args = [                 f'"{cmake_exe}"', "-S", f'"{source_dir}"', "-B", f'"{build_dir}"', "-G", "Ninja",                 f"-DCMAKE_INSTALL_PREFIX={install_dir}",                 f"-DCMAKE_BUILD_TYPE={build_type.value}",                 f"-DBUILD_SHARED_LIBS={'ON' if config.extra_opts.get('shared') else 'OFF'}",                 f"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded{'Debug' if build_type == C.BuildType.DEBUG else ''}{'DLL' if config.runtime == C.Runtime.MD else ''}"             ]             if not self._run_command_in_vs_env(cmake_args, source_dir, f"lzo_config_{build_type.value.lower()}", arch, stop_event):                 return False             build_args = [f'"{cmake_exe}"', "--build", f'"{build_dir}"', "--config", build_type.value, "--target", "install", "--parallel", str(config.jobs)]             if not self._run_command_in_vs_env(build_args, source_dir, f"lzo_build_{build_type.value.lower()}", arch, stop_event, on_line=lambda s: self.app.ui_manager.update_build_stats(s, 'build_task')):                 return False         return True     def _build_spdlog(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -&gt; bool:         for build_type in config.build_configs:             if stop_event.is_set(): return False             self.bus.log(f"--- spdlog ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE)                         build_dir = config.build_dir_for(arch, build_type)             install_dir = config.install_dir_for(arch, build_type)             cmake_exe = self.app.tool_manager.which("cmake", config.tool_mode == C.ToolMode.BUNDLED)             if not cmake_exe: return False             cmake_args = [                 f'"{cmake_exe}"', "-S", f'"{source_dir}"', "-B", f'"{build_dir}"', "-G", "Ninja",                 f"-DCMAKE_INSTALL_PREFIX={install_dir}",                 f"-DCMAKE_BUILD_TYPE={build_type.value}",                 f"-DSPDLOG_BUILD_STATIC=ON",                 f"-DSPDLOG_BUILD_SHARED=OFF",                 f"-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded{'Debug' if build_type == C.BuildType.DEBUG else ''}{'DLL' if config.runtime == C.Runtime.MD else ''}",                 "-DSPDLOG_BUILD_EXAMPLES=OFF",                 "-DSPDLOG_BUILD_TESTS=OFF",                 "-DSPDLOG_BUILD_BENCH=OFF"             ]                         if not self._run_command_in_vs_env(cmake_args, source_dir, f"spdlog_config_{build_type.value.lower()}", arch, stop_event):                 return False             build_args = [f'"{cmake_exe}"', "--build", f'"{build_dir}"', "--config", build_type.value, "--target", "install", "--parallel", str(config.jobs)]             if not self._run_command_in_vs_env(build_args, source_dir, f"spdlog_build_{build_type.value.lower()}", arch, stop_event, on_line=lambda s: self.app.ui_manager.update_build_stats(s, 'build_task')):                 return False         return True     def _build_nlohmann_json(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -&gt; bool:         for build_type in config.build_configs:             if stop_event.is_set(): return False             self.bus.log(f"--- nlohmann/json ({arch.value} / {build_type.value} / {config.runtime.value}) Kurulumu Başlıyor ---", C.LogLevel.STAGE)             install_dir = config.install_dir_for(arch, build_type)                         try:                 src_include_path = source_dir / "include"                 if not src_include_path.exists():                     self.bus.log(f"Kaynak 'include' klasörü bulunamadı: {src_include_path}", C.LogLevel.ERR)                     return False                                 dest_include_path = install_dir / "include"                 self.bus.log(f"'{src_include_path}' kopyalanıyor -&gt; '{dest_include_path}'", C.LogLevel.CMD)                                 if dest_include_path.exists():                     shutil.rmtree(dest_include_path)                 shutil.copytree(src_include_path, dest_include_path)                                 self.bus.log("nlohmann/json başlık dosyaları başarıyla kopyalandı.", C.LogLevel.INFO)             except Exception as e:                 self.bus.log(f"nlohmann/json dosyaları kopyalanırken hata: {e}", C.LogLevel.ERR)                 return False         return True           def _build_python(self, config: BuildConfig, source_dir: Path, arch: C.Arch, stop_event: threading.Event) -&gt; bool:         vs_env = self.app.tool_manager.vs_env_path()         if not vs_env: return False         msbuild_exe = Path(vs_env).parent.parent.parent.parent / "MSBuild/Current/Bin/MSBuild.exe"         if not msbuild_exe.exists(): self.bus.log(f"MSBuild.exe bulunamadı: {msbuild_exe}", C.LogLevel.ERR); return False               sln_file = source_dir / "PCbuild" / "pcbuild.sln"         if not sln_file.exists(): self.bus.log(f"Python solution dosyası bulunamadı: {sln_file}", C.LogLevel.ERR); return False               for build_type in config.build_configs:             if stop_event.is_set(): return False             self.bus.log(f"--- Python ({arch.value} / {build_type.value} / {config.runtime.value}) Derlemesi Başlıyor ---", C.LogLevel.STAGE)             install_dir = config.install_dir_for(arch, build_type)                       runtime_flag = "MultiThreaded" if config.runtime == C.Runtime.MT else "MultiThreadedDLL"             if build_type == C.BuildType.DEBUG: runtime_flag += "Debug"             platform = "x64" if arch == C.Arch.X64 else "Win32"             targets = []             if config.extra_opts.get('shared'): targets.append("pythoncore")             if config.extra_opts.get('static'): targets.append("pythoncore_static")                         if not targets:                 self.bus.log("Python için 'static' veya 'shared' seçilmedi, bu konfigürasyon atlanıyor.", C.LogLevel.WARN)                 continue             # CPython'un kendi build sistemi, çıktıları $(SolutionDir)$(Platform)\$(Configuration) içine koyar             output_dir = source_dir / "PCbuild" / platform / build_type.value             build_args = [f'"{msbuild_exe}"', f'"{sln_file}"', f"/t:{';'.join(targets)}", f"/p:Configuration={build_type.value}",                           f"/p:Platform={platform}", f"/p:RuntimeLibrary={runtime_flag}", f"/p:UseMultiToolTask=true",                           f"/p:BuildInParallel=true", f"/m:{config.jobs}", f"/p:OutDir={output_dir}\\"]                       self.bus.work_mode("indeterminate")             self.bus.work_control("start")             def on_line_msbuild(line: str):                 self.bus.log(line, C.LogLevel.CMD)                 if ".cpp" in line or ".c" in line:                     self.bus.work_text(Path(line.split(' ')[0]).name)             build_ok = self._run_command_in_vs_env(build_args, source_dir / "PCbuild", f"python_build_{build_type.value.lower()}", arch, stop_event, on_line=on_line_msbuild)                       self.bus.work_control("stop")             self.bus.work_mode("determinate")             self.bus.progress("work", 100 if build_ok else 0, "Tamamlandı" if build_ok else "Hata")             if not build_ok: return False                       self.bus.log("Derleme sonrası kopyalama işlemi...", C.LogLevel.STAGE)             try:                 lib_dir = (install_dir / "lib"); lib_dir.mkdir(parents=True, exist_ok=True)                 inc_dir = (install_dir / "include"); inc_dir.mkdir(parents=True, exist_ok=True)                 dll_dir = (install_dir / "bin"); dll_dir.mkdir(parents=True, exist_ok=True)                 self.bus.log("Kopyalanıyor: Başlık Dosyaları (Include, PC)", C.LogLevel.CMD)                 # Dirs_exist_ok=True, var olan bir hedefe kopyalamaya izin verir                 shutil.copytree(source_dir / "Include", inc_dir / "Include", dirs_exist_ok=True)                 shutil.copytree(source_dir / "PC", inc_dir / "PC", dirs_exist_ok=True)                 if config.extra_opts.get('shared'):                     self.bus.log("Kopyalanıyor: Paylaşımlı Kütüphane (.lib, .dll)", C.LogLevel.CMD)                     for f in output_dir.glob("python*.lib"): shutil.copy2(f, lib_dir)                     for f in output_dir.glob("python*.dll"): shutil.copy2(f, dll_dir)                                 if config.extra_opts.get('static'):                     self.bus.log("Kopyalanıyor: Statik Kütüphane (_s.lib)", C.LogLevel.CMD)                     for f in output_dir.glob("python*_s.lib"): shutil.copy2(f, lib_dir)             except Exception as e: self.bus.log(f"Python dosyaları kopyalanırken hata: {e}", C.LogLevel.ERR); return False         return True     def _finalize_build_process(self, all_success: bool) -&gt; None:         if all_success:             self.bus.status("Tüm işlemler başarıyla tamamlandı!")             messagebox.showinfo("Lib Builder", "Derleme işlemi başarıyla tamamlandı.")         else:             self.bus.status("Derleme bir veya daha fazla hatayla tamamlandı.")             messagebox.showwarning("Derleme Başarısız", "Derleme görevi başarısız oldu. Detaylar için logları inceleyin.")     def _run_preflight_checks(self, config: BuildConfig) -&gt; bool:         self.bus.log("--- Uçuş Öncesi Kontroller Başlatıldı ---", C.LogLevel.STAGE)         if not self.app.tool_manager.vs_env_path():             messagebox.showerror("Ortam Hatası", "Visual Studio 'Desktop development with C++' bileşeni kurulu değil."); return False               required = ["cmake", "ninja"]         if config.lib_key == "libjpeg-turbo": required.append("nasm")               missing = []         for tool_key in required:             meta = next((m for m in self.app.tool_manager.TOOLS_META if m["key"] == tool_key), None)             if not meta or not self.app.tool_manager.detect_tool(meta, config.tool_mode)[0]:                 missing.append(meta['name'] if meta else tool_key)         if missing:             msg = f"Eksik araçlar: {', '.join(missing)}. Lütfen 'Araç Durumu' sekmesinden kurun."             messagebox.showwarning("Eksik Araçlar", msg); return False                   try:             free_gb = shutil.disk_usage(C.ROOT).free / (1024**3)             if free_gb &lt; C.DISK_MIN_GB:                 if not messagebox.askyesno("Disk Alanı Uyarısı", f"Yetersiz disk alanı ({free_gb:.1f} GB). En az {C.DISK_MIN_GB} GB önerilir.\nYine de devam edilsin mi?"):                     return False         except OSError: pass         self.bus.log("--- Uçuş Öncesi Kontroller Başarıyla Tamamlandı ---", C.LogLevel.STAGE)         return True     def clear_build_folder(self) -&gt; None:         if self.app._is_busy: return         if not C.BUILD.exists() or not any(C.BUILD.iterdir()): messagebox.showinfo("Bilgi", "'build' klasörü zaten boş."); return         if messagebox.askyesno("Onay", "Tüm 'build' klasörünün içeriği silinecektir. Devam etmek istiyor musunuz?"):             try: shutil.rmtree(C.BUILD); C.BUILD.mkdir(); messagebox.showinfo("Başarılı", "'build' klasörü temizlendi.")             except OSError as e: messagebox.showerror("Hata", f"Silinemedi:\n{e}")     def fetch_tags_async(self, lib_key: str, combobox: ttk.Combobox) -&gt; None:         if self.app._is_busy: return         meta = C.LIB_META[lib_key]         self.app.task_manager.register(name=f"{meta['name']} Sürüm Listeleme",                                        target=self._fetch_tags_worker, args=(meta, combobox), is_build_task=False)           def _fetch_tags_worker(self, meta: dict, combobox: ttk.Combobox, task_id: str) -&gt; None:         self.bus.log(f"{meta['name']} GitHub etiketleri çekiliyor...", C.LogLevel.INFO)         tags = []         try:             git = self.app.tool_manager.which("git", False)             if git:                 out = run_out([git, "ls-remote", "--tags", meta['repo']])                 versions = []                 for ln in out.splitlines():                     m = re.search(r"refs/tags/(" + re.escape(meta['tag_prefix']) + r".*)$", ln)                     if m and meta['tag_filter'].match(m.group(1)):                         versions.append(m.group(1))                 tags = sorted(versions, key=lambda v: list(map(int, re.findall(r'\d+', v))), reverse=True)[:50]         except Exception as e: self.bus.log(f"Git ile etiket çekme hatası: {e}", C.LogLevel.WARN)                   if not tags: messagebox.showerror("Hata", "Sürümler listelenemedi. İnternet bağlantınızı veya Git kurulumunu kontrol edin.")               lib_key_from_meta = next(key for key, m in C.LIB_META.items() if m['name'] == meta['name'])         last_tag = self.app.ui_manager.lib_tabs.get(lib_key_from_meta, {}).get('last_tag_from_config')         selected_tag = (last_tag if last_tag in tags else tags[0]) if tags else ""         self.app.after(0, lambda: (combobox.config(values=tags), combobox.set(selected_tag)))         self.app.task_manager.task_registry[task_id]['success'] = True     def _fetch_source(self, lib_key: str, tag: str, stop_event: threading.Event) -&gt; Path | None:         meta = C.LIB_META[lib_key]         dst = C.SRC / lib_key / tag         if stop_event.is_set(): return None         if dst.exists() and any(dst.iterdir()):             self.bus.log("Kaynaklar zaten çıkarılmış, atlandı.", C.LogLevel.STAGE); return dst                   if meta.get('type') == 'url':             url = meta['url']             archive_name = Path(url).name             zip_path = C.DL / archive_name.replace(":", "_")             if not zip_path.exists():                 if not self.app.tool_manager._http_get(url, zip_path, self.bus):                     raise RuntimeError("Kaynak arşivi indirilemedi.")         else: # git type             zip_name = tag.replace('/', '_')             zip_path = C.DL / f"{lib_key}-{zip_name}.zip"             if not zip_path.exists():                 url = f"{meta['repo']}/archive/refs/tags/{tag}.zip"                 if not self.app.tool_manager._http_get(url, zip_path, self.bus):                     raise RuntimeError("Kaynak ZIP alınamadı.")         if stop_event.is_set(): return None               extract_dir = C.SRC / lib_key / "_tmp"         if extract_dir.exists(): shutil.rmtree(extract_dir)         extract_dir.mkdir(parents=True)               try:             self.bus.log(f"'{zip_path.name}' arşivi çıkarılıyor...", C.LogLevel.STAGE)             shutil.unpack_archive(zip_path, extract_dir)             self.bus.log("Arşiv başarıyla çıkarıldı.", C.LogLevel.INFO)         except Exception as e:             self.bus.log(f"Arşiv çıkarılırken hata oluştu: {e}", C.LogLevel.ERR)             self.bus.log(traceback.format_exc(), C.LogLevel.ERR)             raise RuntimeError("Arşiv dosyası çıkarılamadı.")         items_in_extract_dir = list(extract_dir.iterdir())         if len(items_in_extract_dir) == 1 and items_in_extract_dir[0].is_dir():             self.bus.log("Tek bir kök dizin algılandı, taşınıyor...", C.LogLevel.INFO)             source_root = items_in_extract_dir[0]             if dst.exists():                 shutil.rmtree(dst)             shutil.move(str(source_root), str(dst))             shutil.rmtree(extract_dir)         else:             self.bus.log("Kök dizin bulunamadı, geçici klasörün kendisi kaynak olarak kullanılıyor.", C.LogLevel.INFO)             if dst.exists():                 shutil.rmtree(dst)             shutil.move(str(extract_dir), str(dst))         if not dst.exists():              raise RuntimeError("Kaynak çıkarma sonrası klasör bulunamadı.")         return dst     def initial_trash_cleanup(self) -&gt; None:         if C.TRASH.exists() and any(C.TRASH.iterdir()):             self.bus.log("Başlangıç: Dolu .trash klasörü bulundu. Arka planda temizleniyor...", C.LogLevel.WARN)             for item in C.TRASH.iterdir(): self._cleanup_trash_folder_async(item)     def _cleanup_trash_folder_async(self, folder: Path) -&gt; None:         self.app.task_manager.register(name=f".trash Temizliği ({folder.name})", target=shutil.rmtree, args=(folder,), kwargs={'ignore_errors': True}, is_build_task=False)     def _do_clean_start(self) -&gt; None:         self.bus.log("Temiz başlangıç: Klasörler .trash'e taşınıyor...", C.LogLevel.STAGE)         for p in (C.SRC, C.BUILD, C.INST):             if p.exists() and any(p.iterdir()):                 trash_dest = C.TRASH / f"{p.name}-{int(time.time())}"                 try: shutil.move(str(p), str(trash_dest)); p.mkdir(exist_ok=True)                 except OSError as e: self.bus.log(f"'{p}' taşınamadı: {e}.", C.LogLevel.ERR); raise                 self._cleanup_trash_folder_async(trash_dest)       def empty_trash_folder(self):         if not C.TRASH.exists() or not any(C.TRASH.iterdir()): messagebox.showinfo("Bilgi", ".trash klasörü zaten boş."); return         if messagebox.askyesno("Onay", ".trash klasörünün tüm içeriği kalıcı olarak silinecektir. Devam etmek istiyor musunuz?"):             for item in C.TRASH.iterdir(): self._cleanup_trash_folder_async(item)             self.bus.log(".trash temizleme görevi başlatıldı.", C.LogLevel.INFO) class App(ttk.Window):     def __init__(self):         super().__init__(themename="litera", title="Lib Builder Pro v7.4 (Final) - turkmmo.com - dormammu", size=(1320, 960), minsize=(1200, 860))         self.process = psutil.Process(); self.q: queue.Queue = queue.Queue(); self._is_busy = False               self.bus = Bus(self)         self.task_manager = TaskManager(self)         self.tool_manager = ToolManager(self)         self.build_manager = BuildManager(self)         self.ui_manager = UIManager(self)         self.after(100, self.ui_manager.pump_queue)         self.after(10, self.tool_manager.refresh_tools)         for lib_key, widgets in self.ui_manager.lib_tabs.items():              if 'cmb_tag' in widgets:                 self.after(200, lambda k=lib_key, c=widgets['cmb_tag']: self.build_manager.fetch_tags_async(k, c))         self.after(500, self.build_manager.initial_trash_cleanup)         self.after(1000, self.ui_manager.update_resource_monitor)         self.after(1000, self.task_manager.update_monitor)         self.after(1100, self.ui_manager.update_trash_monitor)         self.protocol("WM_DELETE_WINDOW", self.on_closing)     def on_closing(self) -&gt; None:         if self._is_busy and messagebox.askyesno("Çıkış Onayı", "Bir işlem çalışıyor. Çıkmak istediğinizden emin misiniz?"):             self.task_manager.stop_all()         self.ui_manager.save_settings(); self.destroy()     def set_busy(self, busy: bool) -&gt; None:         self._is_busy = busy         self.ui_manager.set_controls_state('disabled' if busy else 'normal')         try:             priority = psutil.IDLE_PRIORITY_CLASS if busy else psutil.NORMAL_PRIORITY_CLASS             self.process.nice(priority)         except psutil.Error: pass if __name__ == "__main__":     for p in (C.LOGS, C.TOOLS, C.DL, C.SRC, C.BUILD, C.INST, C.DEPS, C.TRASH,               C.INST_LIBJPEG_TURBO, C.INST_DEVIL, C.INST_CRYPTOPP, C.INST_LZO,               C.INST_SPDLOG, C.INST_NLOHMANN_JSON, C.INST_PYTHON):         p.mkdir(parents=True, exist_ok=True)     app = App()     app.mainloop()




🛠️ Program Nasıl Kullanılır? 🛠️



  1. <li data-xf-list-type="ol">Araçları Kur: İlk olarak, "Araç Durumu" panelindeki eksikleri "Eksikleri Kur" butonuyla tamamlayın. <li data-xf-list-type="ol">Ayarları Yap:
    • <li data-xf-list-type="ul">"Ortak Ayarlar" kısmından Metin2 client için zorunlu olan x86 (32-bit) mimarisinin seçili olduğundan emin olun. <li data-xf-list-type="ul">Runtime olarak genellikle MT seçilir. <li data-xf-list-type="ul">Her kütüphane sekmesine gidip "Listele" butonuna basarak en güncel sürümü seçin (veya istediğiniz başka bir sürümü).
    <li data-xf-list-type="ol">Derlemeyi Başlat: Sağdaki "Tüm Kütüphaneleri Derle" butonuna basın. Bu en kolay yöntemdir, çünkü bağımlılık sırasını kendi ayarlar. <li data-xf-list-type="ol">Kullanıma Hazır: İşlem bitince "Install Klasörünü Aç" butonuna basın. Açılan klasörde, her kütüphane için ayrı ayrı klasörler göreceksiniz. Örneğin install\devil\x86\MT\Release klasörünün içindeki include ve lib klasörlerini, kendi source'unuzun extern klasöründeki ilgili yerlere kopyalayın.
Artık kütüphane derleme hataları olmadan, keyifle source'unuzu derleyebilirsiniz!

Tüm soru, sorun ve önerilerinizi konu altından bekliyorum.

Saygılarımla,dormammu
Lib Builder Pro: Otomatik Kütüphane Derleme Aracı
Metin2 geliştiricileri için önemli bir araçtan bahsetmek istiyoruz; Lib Builder Pro. Bu araç, libjpeg, DevIL, Crypto++ gibi popüler kütüphaneleri otomatik olarak derlemek ve projelerinize entegre etmek için tasarlanmıştır. Özellikle C++ tabanlı Metin2 sunucu kaynak kodları üzerinde çalışan geliştiriciler için büyük kolaylık sağlar.

Lib Builder Pro Nedir?
Lib Builder Pro, geliştiricilerin zaman kazanmasını sağlayan otomatik derleme çözümlerinden biridir. Yazılım projelerinde kullanılan harici kütüphaneleri derlemek genellikle uzun süren ve hataya açık bir işlemdir. Bu aracın temel amacı, bu işlemi hızlandırmak ve otomatikleştirmektir. Özellikle Metin2 server src[/COORD] geliştirenler için bu, büyük bir avantajdır.

Desteklenen Kütüphaneler
Aracın desteklediği başlıca kütüphaneler şunlardır:
- libjpeg: JPEG görüntü işleme kütüphanesi
- DevIL: Geniş platform desteği sunan görüntü işleme kütüphanesi
- Crypto++: Şifreleme ve güvenlik odaklı kriptografi kütüphanesi
Bu kütüphaneler, özellikle game core ve client src geliştirme süreçlerinde sıklıkla kullanılır. Lib Builder Pro sayesinde bu kütüphaneleri derlemek için manuel adımlar atmak yerine, tek bir komutla süreci tamamlayabilirsiniz.

Neden Lib Builder Pro Kullanmalısınız?
Otomatik Derleme: Manuel derleme işlemlerinde oluşabilecek hataları ortadan kaldırır.
Zaman Tasarrufu: Geliştirme sürecini hızlandırır.
Kolay Entegrasyon: Hazırlanan kütüphaneler doğrudan projenize dahil edilebilir.
Platform Uyumluluğu: Windows, Linux ve diğer platformlarda çalışabilir.

Metin2 Geliştirme Sürecine Entegrasyonu
Lib Builder Pro, Metin2 development sürecinde önemli bir yer tutar. Özellikle source edit veya server src üzerinde çalışırken, harici bağımlılıkların doğru şekilde derlenmiş olması gerekir. Bu araç sayesinde, örneğin Crypto++ kütüphanesini kullanarak güçlü güvenlik katmanları oluşturabilirsiniz. Aynı zamanda libjpeg sayesinde görsel içeriklerinizi optimize edebilirsiniz.

Lib Builder Pro ile Metin2 PVP Sistemleri Geliştirme
Metin2 PVP sistem geliştirme sürecinde, bazı güvenlik ve performans kütüphaneleri mutlaka gereklidir. Lib Builder Pro, bu kütüphaneleri hızlıca derleyip projeye entegre etmenizi sağlar. Böylece game server programming sırasında daha güvenli ve optimize çözümler elde edebilirsiniz. Örneğin, DevIL ile sunucu tarafında dinamik olarak resim yüklemeleri yapabilirsiniz.

Sonuç
Lib Builder Pro, Metin2 özel sunucuları geliştiren herkes için oldukça değerli bir araçtır. Özellikle C++ sistem odaklı çalışmalarda, derleme sürecini büyük ölçüde kolaylaştırır. Metin2Lobby olarak, bu tarz gelişmiş araçların geliştiriciler tarafından kullanılmasını teşvik ediyoruz. Lib Builder Pro ile pack dosyalarınıza daha hızlı ve güvenli bir şekilde erişim sağlayabilir, auth ve db süreçlerini optimize edebilirsiniz.


Lib Builder Pro: Automatic Library Building Tool
We would like to discuss an important tool for Metin2 developers; Lib Builder Pro. This tool is designed to automatically build popular libraries such as libjpeg, DevIL, Crypto++ and integrate them into your projects. It provides great convenience especially for developers working on C++ based Metin2 server sources.

What is Lib Builder Pro?
Lib Builder Pro is one of the automated building solutions that help developers save time. Compiling external libraries used in software projects is often a lengthy and error-prone process. The main purpose of this tool is to speed up and automate this process. Especially for those developing Metin2 server src, this is a major advantage.

Supported Libraries
The main libraries supported by the tool are:
- libjpeg: JPEG image processing library
- DevIL: Cross-platform supporting image processing library
- Crypto++: Cryptography library focused on encryption and security
These libraries are frequently used during game core and client src development processes. With Lib Builder Pro, you can complete the process with a single command instead of manually taking steps to build these libraries.

Why Should You Use Lib Builder Pro?
Automatic Building: Eliminates potential errors in manual building processes.
Time Saving: Accelerates the development process.
Easy Integration: Built libraries can be directly included in your project.
Platform Compatibility: Works on Windows, Linux, and other platforms.

Integration into Metin2 Development Process
Lib Builder Pro holds a significant place in the Metin2 development process. Especially when working on source edit or server src, it's essential that external dependencies are correctly built. With this tool, you can, for example, create robust security layers using the Crypto++ library. Additionally, with libjpeg, you can optimize your visual content.

Developing Metin2 PVP Systems with Lib Builder Pro
In the process of developing Metin2 PVP systems, certain security and performance libraries are essential. Lib Builder Pro allows you to quickly build and integrate these libraries into your project. This way, during game server programming, you can achieve more secure and optimized solutions. For instance, with DevIL, you can dynamically load images on the server side.

Conclusion
Lib Builder Pro is an invaluable tool for anyone developing Metin2 private servers. Especially in C++ system focused work, it significantly simplifies the building process. At Metin2Lobby, we encourage developers to make use of such advanced tools. With Lib Builder Pro, you can access your pack files faster and more securely, and optimize auth and db processes.
 

Forumdan daha fazla yararlanmak için giriş yapın yada üye olun!

Forumdan daha fazla yararlanmak için giriş yapın veya kayıt olun!

Kaydol

Forumda bir hesap oluşturmak tamamen ücretsizdir.

Üye ol
Giriş Yap

Eğer bir hesabınız var ise lütfen giriş yapın

Giriş Yap

Tema düzenleyici

Tema özelletirmeleri