|
| 1 | +import os |
| 2 | +import tkinter as tk |
| 3 | +from tkinter import messagebox, simpledialog, ttk |
| 4 | +import requests |
| 5 | +import json |
| 6 | +import subprocess |
| 7 | +import platform |
| 8 | +from PIL import Image, ImageTk |
| 9 | +import threading |
| 10 | + |
| 11 | +LAUNCHER_DIR = "launcherData" |
| 12 | +LOGIN_FILE = os.path.join(LAUNCHER_DIR, "login.dat") |
| 13 | +FILE_DOWNLOADS = { |
| 14 | + "./client.jar": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/client.jar", |
| 15 | + "launcherData/bg.png": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/launcherData/bg.png", |
| 16 | + "launcherData/logo.png": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/launcherData/bg.png", |
| 17 | + "libraries/lwjgl_util.jar": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/libraries/lwjgl_util.jar", |
| 18 | + "libraries/lwjgl.jar": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/libraries/lwjgl.jar", |
| 19 | + "libraries/jinput.jar": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/libraries/jinput.jar", |
| 20 | + "libraries/json-20210307.jar": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/libraries/json-20210307.jar", |
| 21 | + "natives/OpenAL64.dll": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/OpenAL64.dll", |
| 22 | + "natives/OpenAL32.dll": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/OpenAL32.dll", |
| 23 | + "natives/lwjgl64.dll": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/lwjgl64.dll", |
| 24 | + "natives/lwjgl.dll": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/lwjgl.dll", |
| 25 | + "natives/libopenal64.so": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/libopenal64.so", |
| 26 | + "natives/libopenal.so": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/libopenal.so", |
| 27 | + "natives/liblwjgl64.so": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/liblwjgl64.so", |
| 28 | + "natives/liblwjgl.so": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/liblwjgl.so", |
| 29 | + "natives/libjinput-linux64.so": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/libjinput-linux64.so", |
| 30 | + "natives/libjinput-linux.so": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/libjinput-linux.so", |
| 31 | + "natives/jinput-raw_64.dll": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/jinput-raw_64.dll", |
| 32 | + "natives/jinput-raw.dll": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/jinput-raw.dll", |
| 33 | + "natives/jinput-dx8_64.dll": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/jinput-dx8_64.dll", |
| 34 | + "natives/jinput-dx8.dll": "https://github.com/goldenboys2011/EndlessLauncher/raw/refs/heads/latest/natives/jinput-dx8.dll" |
| 35 | +} |
| 36 | + |
| 37 | + |
| 38 | +class LauncherApp: |
| 39 | + def __init__(self, root): |
| 40 | + self.root = root |
| 41 | + self.root.title("Endless Beta Launcher") |
| 42 | + self.ram_mb = "1024" |
| 43 | + self.login_method = tk.StringVar(value="OSMC") |
| 44 | + self.username = tk.StringVar() |
| 45 | + self.password = tk.StringVar() |
| 46 | + |
| 47 | + |
| 48 | + |
| 49 | + def build_ui(self): |
| 50 | + # Load bg tile image |
| 51 | + self.bg_tile = Image.open("launcherData/bg.png").resize((32, 32)) |
| 52 | + self.bg_tile_tk = ImageTk.PhotoImage(self.bg_tile) |
| 53 | + |
| 54 | + # Optional: Load logo |
| 55 | + # self.logo = Image.open("launcherData/logo.png").resize((200, 100), Image.ANTIALIAS) |
| 56 | + # self.logo_tk = ImageTk.PhotoImage(self.logo) |
| 57 | + |
| 58 | + canvas = tk.Canvas(self.root, width=300, height=400) |
| 59 | + canvas.pack(fill="both", expand=True) |
| 60 | + |
| 61 | + for x in range(0, 800, self.bg_tile.width): |
| 62 | + for y in range(0, 600, self.bg_tile.height): |
| 63 | + canvas.create_image(x, y, image=self.bg_tile_tk, anchor="nw") |
| 64 | + |
| 65 | + frame = tk.Frame(canvas, bg="#ffffff", bd=0) |
| 66 | + frame.place(relx=0.5, rely=0.5, anchor="center") |
| 67 | + |
| 68 | + tk.Label(frame, text="Login Method:", bg="white").pack() |
| 69 | + login_menu = ttk.Combobox(frame, textvariable=self.login_method, values=["OSMC", "Microsoft"], state="readonly") |
| 70 | + login_menu.pack() |
| 71 | + |
| 72 | + tk.Label(frame, text="Username:", bg="white").pack() |
| 73 | + self.username_entry = tk.Entry(frame, textvariable=self.username) |
| 74 | + self.username_entry.pack() |
| 75 | + |
| 76 | + tk.Label(frame, text="Password:", bg="white").pack() |
| 77 | + self.password_entry = tk.Entry(frame, textvariable=self.password, show="*") |
| 78 | + self.password_entry.pack() |
| 79 | + |
| 80 | + launch_button = tk.Button(frame, text="Launch", command=self.launch) |
| 81 | + launch_button.pack(pady=(10, 5)) |
| 82 | + |
| 83 | + settings_button = tk.Button(frame, text="Settings", command=self.open_settings) |
| 84 | + settings_button.pack() |
| 85 | + |
| 86 | + creds = self.load_credentials() |
| 87 | + if creds: |
| 88 | + self.login_method.set(creds[0]) |
| 89 | + self.username_entry.insert(0, creds[1]) |
| 90 | + self.password_entry.insert(0, creds[2]) |
| 91 | + |
| 92 | + def open_settings(self): |
| 93 | + new_ram = simpledialog.askstring("Settings", "Enter RAM in MB:", initialvalue=self.ram_mb) |
| 94 | + if new_ram: |
| 95 | + self.ram_mb = new_ram.strip() |
| 96 | + |
| 97 | + def show_alert(self, message): |
| 98 | + messagebox.showinfo("Info", message) |
| 99 | + |
| 100 | + def save_credentials(self): |
| 101 | + os.makedirs(LAUNCHER_DIR, exist_ok=True) |
| 102 | + with open(LOGIN_FILE, "w") as f: |
| 103 | + f.write(f"{self.login_method.get()}\n{self.username.get()}\n{self.password.get()}") |
| 104 | + |
| 105 | + def load_credentials(self): |
| 106 | + if not os.path.exists(LOGIN_FILE): |
| 107 | + return |
| 108 | + with open(LOGIN_FILE) as f: |
| 109 | + lines = f.read().splitlines() |
| 110 | + if len(lines) == 3: |
| 111 | + self.login_method.set(lines[0]) |
| 112 | + self.username.set(lines[1]) |
| 113 | + self.password.set(lines[2]) |
| 114 | + |
| 115 | + def authenticate_with_osmc(self, username, password): |
| 116 | + url = "https://os-mc.net/api/v1/authenticate" |
| 117 | + data = {"username": username, "password": password} |
| 118 | + headers = {"Content-Type": "application/json"} |
| 119 | + response = requests.post(url, json=data, headers=headers) |
| 120 | + return response.json() |
| 121 | + |
| 122 | + def launch(self): |
| 123 | + method = self.login_method.get() |
| 124 | + username = self.username.get() |
| 125 | + password = self.password.get() |
| 126 | + |
| 127 | + if method == "OSMC": |
| 128 | + try: |
| 129 | + response = self.authenticate_with_osmc(username, password) |
| 130 | + if response.get("success"): |
| 131 | + profile = response["selectedProfile"] |
| 132 | + player_name = profile["name"] |
| 133 | + uuid = profile["id"] |
| 134 | + self.save_credentials() |
| 135 | + self.launch_game(player_name, uuid, "osmc") |
| 136 | + else: |
| 137 | + self.show_alert("Authentication failed.") |
| 138 | + except Exception as e: |
| 139 | + print(e) |
| 140 | + self.show_alert("Login failed.") |
| 141 | + else: |
| 142 | + self.show_alert("Microsoft login not implemented.") |
| 143 | + |
| 144 | + def launch_game(self, username, uuid, launch_type): |
| 145 | + print(f"Launching Minecraft for: Username: {username}, UUID: {uuid}, RAM: {self.ram_mb}MB, Type: {launch_type}") |
| 146 | + java_exec = "javaw" if platform.system() == "Windows" else "java" |
| 147 | + |
| 148 | + classpath = os.pathsep.join([ |
| 149 | + "client.jar", |
| 150 | + "libraries/json.jar", |
| 151 | + "libraries/lwjgl.jar", |
| 152 | + "libraries/lwjgl_util.jar" |
| 153 | + ]) |
| 154 | + |
| 155 | + params = [ |
| 156 | + java_exec, |
| 157 | + f"-Xmx{self.ram_mb}M", |
| 158 | + "-Djava.library.path=natives", |
| 159 | + "-classpath", classpath, |
| 160 | + "net.minecraft.client.Minecraft", |
| 161 | + username, |
| 162 | + uuid, |
| 163 | + launch_type |
| 164 | + ] |
| 165 | + |
| 166 | + try: |
| 167 | + root.withdraw() # Hide the main window for now |
| 168 | + |
| 169 | + threading.Thread(target=subprocess.run(params, check=True)).start() |
| 170 | + |
| 171 | + root.deiconify() |
| 172 | + print("Game exited successfully.") |
| 173 | + except subprocess.CalledProcessError as e: |
| 174 | + print("Game process failed:", e) |
| 175 | + self.show_alert("Game launch failed.") |
| 176 | + |
| 177 | + |
| 178 | +if __name__ == "__main__": |
| 179 | + root = tk.Tk() |
| 180 | + root.withdraw() # Hide the main window for now |
| 181 | + download_window = tk.Toplevel() |
| 182 | + download_window.title("Preparing Launcher") |
| 183 | + download_window.geometry("300x100") |
| 184 | + download_window.resizable(False, False) |
| 185 | + label = tk.Label(download_window, text="Downloading required files...") |
| 186 | + label.pack(pady=10) |
| 187 | + progress = ttk.Progressbar(download_window, mode="determinate", maximum=len(FILE_DOWNLOADS)) |
| 188 | + progress.pack(pady=10, padx=20, fill="x") |
| 189 | + |
| 190 | + def download_and_continue(): |
| 191 | + total = len(FILE_DOWNLOADS) |
| 192 | + for i, (local_path, url) in enumerate(FILE_DOWNLOADS.items()): |
| 193 | + try: |
| 194 | + os.makedirs(os.path.dirname(local_path), exist_ok=True) |
| 195 | + if not os.path.exists(local_path): |
| 196 | + response = requests.get(url) |
| 197 | + with open(local_path, "wb") as f: |
| 198 | + f.write(response.content) |
| 199 | + progress["value"] = i + 1 |
| 200 | + label.config(text=f"Downloading {os.path.basename(local_path)}") |
| 201 | + download_window.update_idletasks() |
| 202 | + except Exception as e: |
| 203 | + print(f"Error downloading {local_path}: {e}") |
| 204 | + |
| 205 | + # Once done, close the popup and show main window |
| 206 | + download_window.destroy() |
| 207 | + root.deiconify() |
| 208 | + app = LauncherApp(root) |
| 209 | + app.build_ui() |
| 210 | + |
| 211 | + |
| 212 | + threading.Thread(target=download_and_continue).start() |
| 213 | + root.mainloop() |
0 commit comments