import os import subprocess import threading import time import customtkinter as ctk import minecraft_launcher_lib from tkinter import messagebox from PIL import Image, ImageTk, ImageFilter import urllib.request import zipfile import sys import platform # ---------------------------- # Paths # ---------------------------- CUSTOM_DIR = os.path.join(os.getcwd(), "blockbreak") JAVA_DIR = os.path.join(CUSTOM_DIR, "java") os.makedirs(CUSTOM_DIR, exist_ok=True) os.makedirs(JAVA_DIR, exist_ok=True) BG_PATH = os.path.join(os.getcwd(), "bg.png") # ---------------------------- # GUI Setup # ---------------------------- ctk.set_appearance_mode("dark") ctk.set_default_color_theme("dark-blue") root = ctk.CTk() root.title("BlockBreak") root.geometry("800x600") root.minsize(600, 450) # ---------------------------- # Background Canvas # ---------------------------- bg_canvas = ctk.CTkCanvas(root, width=800, height=600, highlightthickness=0) bg_canvas.pack(fill="both", expand=True) bg_image = None # keep reference def load_and_blur_bg(): global bg_image if os.path.exists(BG_PATH): pil_img = Image.open(BG_PATH).convert("RGB") w, h = root.winfo_width(), root.winfo_height() pil_img = pil_img.resize((w, h)) pil_img = pil_img.filter(ImageFilter.GaussianBlur(4)) bg_image = ImageTk.PhotoImage(pil_img) bg_canvas.delete("bg") bg_canvas.create_image(0, 0, anchor="nw", image=bg_image, tags="bg") root.bind("", lambda e: load_and_blur_bg()) # ---------------------------- # Loading Overlay for Fade-in # ---------------------------- loading_overlay = ctk.CTkFrame(root, fg_color="#1f1f1f", corner_radius=0) loading_overlay.place(relx=0, rely=0, relwidth=1, relheight=1) loading_label = ctk.CTkLabel(loading_overlay, text="Loading...", font=("Arial", 36)) loading_label.place(relx=0.5, rely=0.5, anchor="center") # ---------------------------- # Main Panel with curves + padding # ---------------------------- panel = ctk.CTkFrame(root, width=600, height=500, corner_radius=25, fg_color="#1f1f1f") panel.place(relx=0.5, rely=0.5, anchor="center") inner_panel = ctk.CTkFrame(panel, fg_color=None, corner_radius=0) inner_panel.pack(padx=30, pady=20, fill="both", expand=True) ctk.CTkLabel(inner_panel, text="BlockBreak", font=("Arial", 28)).pack(pady=(0, 15)) username_entry = ctk.CTkEntry(inner_panel, placeholder_text="Username", width=500) username_entry.pack(pady=10) version_var = ctk.StringVar() version_dropdown = ctk.CTkComboBox(inner_panel, values=[], variable=version_var, width=500) version_dropdown.pack(pady=10) # RAM slider ram_var = ctk.StringVar(value="2G") ctk.CTkLabel(inner_panel, text="RAM Allocation").pack(pady=(10,0)) ram_slider = ctk.CTkSegmentedButton(inner_panel, values=["2G - Default (fast + safe)","4G - More mods","6G - Maximum sensible"], command=lambda v: ram_var.set(v.split(" ")[0])) ram_slider.pack(pady=5) progress = ctk.CTkProgressBar(inner_panel, width=500) progress.set(0) progress.pack(pady=10) log_box = ctk.CTkTextbox(inner_panel, width=500, height=150) log_box.pack(pady=(10, 0)) log_box.configure(state="disabled") def log(msg): log_box.configure(state="normal") log_box.insert("end", msg + "\n") log_box.see("end") log_box.configure(state="disabled") # ---------------------------- # Java installation (simple bundled version) # ---------------------------- def ensure_java(): java_exe = os.path.join(JAVA_DIR, "bin", "java.exe" if os.name == "nt" else "java") if os.path.exists(java_exe): log("Java found.") return java_exe log("Downloading Java...") # We'll use a lightweight OpenJDK zip for Windows only here for simplicity # For cross-platform, you'd need proper handling java_url = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8%2B7/OpenJDK17U-jre_x64_windows_hotspot_17.0.8_7.zip" zip_path = os.path.join(JAVA_DIR, "java.zip") urllib.request.urlretrieve(java_url, zip_path) with zipfile.ZipFile(zip_path, "r") as zip_ref: zip_ref.extractall(JAVA_DIR) os.remove(zip_path) # Find java.exe in extracted folder for root_dir, dirs, files in os.walk(JAVA_DIR): if "java.exe" in files: java_exe = os.path.join(root_dir, "java.exe") log("Java installed successfully.") return java_exe raise FileNotFoundError("Java installation failed") # ---------------------------- # Launcher Functions # ---------------------------- def get_versions_with_status(): try: versions = minecraft_launcher_lib.utils.get_available_versions(CUSTOM_DIR) except Exception: versions = [] items = [] for v in versions: v_id = v["id"] path = os.path.join(CUSTOM_DIR, "versions", v_id) if os.path.exists(path): items.append(f"{v_id} [Installed]") else: items.append(v_id) return items def update_progress(current, total): if total > 0: progress.set(current / total) def download_and_launch(username, version): try: java_path = ensure_java() actual_version = version.replace(" [Installed]", "") version_path = os.path.join(CUSTOM_DIR, "versions", actual_version) if not os.path.exists(version_path): log(f"Installing version {actual_version}...") minecraft_launcher_lib.install.install_minecraft_version(actual_version, CUSTOM_DIR, progress_callback=update_progress) log("Installation complete.") else: log(f"Version {actual_version} already installed.") log("Generating launch command...") options = minecraft_launcher_lib.utils.generate_test_options() options["username"] = username options["uuid"] = options.get("uuid", "") options["token"] = "" options["java_path"] = java_path options["max_memory"] = ram_var.get() cmd = minecraft_launcher_lib.command.get_minecraft_command(actual_version, CUSTOM_DIR, options) log("Launching Minecraft...") subprocess.Popen(cmd, cwd=CUSTOM_DIR) log("Minecraft launched!") progress.set(0) except Exception as e: log(f"Error: {e}") messagebox.showerror("Launch Failed", str(e)) progress.set(0) def launch_threaded(): username = username_entry.get().strip() version = version_var.get().strip() if not username or not version: messagebox.showerror("Error", "Username and version required!") return progress.set(0) threading.Thread(target=download_and_launch, args=(username, version), daemon=True).start() play_button = ctk.CTkButton(inner_panel, text="Play", command=launch_threaded, width=500, height=50) play_button.pack(pady=10) # ---------------------------- # Initialization + fade-in # ---------------------------- def initialize_launcher(): version_dropdown.configure(values=get_versions_with_status()) load_and_blur_bg() # Fade-in simulation using overlay for i in range(20, -1, -1): gray = int(i * 12.75) hex_gray = f"{gray:02x}" loading_overlay.configure(fg_color=f"#{hex_gray}{hex_gray}{hex_gray}") time.sleep(0.03) loading_overlay.destroy() threading.Thread(target=initialize_launcher, daemon=True).start() root.mainloop()