From e605f763816171051c3298c500cff7275b7663f8 Mon Sep 17 00:00:00 2001 From: ljz Date: Sun, 26 Apr 2026 20:42:30 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E6=96=87=E4=BB=B6=E8=87=B3?= =?UTF-8?q?=E3=80=8C/=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yclgui.py | 329 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 晕.ico | Bin 0 -> 519 bytes 2 files changed, 329 insertions(+) create mode 100644 yclgui.py create mode 100644 晕.ico diff --git a/yclgui.py b/yclgui.py new file mode 100644 index 0000000..6cdd216 --- /dev/null +++ b/yclgui.py @@ -0,0 +1,329 @@ +# -*- coding: utf-8 -*- +""" +Created on Thu Aug 21 21:55:21 2025 +Modified to include GUI on Fri Sep 12 19:00:00 2025 + +@author: ljz +@modifier: Qwen (Alibaba Cloud) +""" + +import minecraft_launcher_lib +import subprocess +import os +import uuid +import shutil +import requests +import zipfile +import tkinter as tk +from tkinter import messagebox, scrolledtext, simpledialog +import threading + + +class LauncherGUI: + def __init__(self, root): + self.root = root + self.root.title("DizzyCraftLauncher2 GUI") + self.root.geometry("600x500") + + # --- 配置路径 --- + self.launcher_directory = "C:\\DizzyCraftLauncher2" + self.minecraft_directory = os.path.join(self.launcher_directory, ".minecraft") + self.java_dir = os.path.join(self.launcher_directory, "jdk-21.0.8") + self.java_path = os.path.join(self.java_dir, "bin", "java.exe") + self.java_zip_path = os.path.join(self.launcher_directory, "jdk-21_windows-x64_bin.zip") + self.java_download_url = "https://download.oracle.com/java/21/latest/jdk-21_windows-x64_bin.zip" + + # --- 创建 GUI 元素 --- + self.create_widgets() + + # --- 初始化 --- + self.username = "" + self.refresh_versions() + + def create_widgets(self): + # 欢迎标签 + self.label_welcome = tk.Label(self.root, text="欢迎使用 DizzyCraftLauncher2 GUI", font=("Arial", 14)) + self.label_welcome.pack(pady=10) + + # 用户名输入 + frame_username = tk.Frame(self.root) + frame_username.pack(pady=5) + tk.Label(frame_username, text="用户名:").pack(side=tk.LEFT) + self.entry_username = tk.Entry(frame_username) + self.entry_username.pack(side=tk.LEFT, padx=(5, 0)) + # 默认用户名可以从系统获取,这里简化处理 + self.entry_username.insert(0, os.getlogin()) + + # 版本列表 + frame_versions = tk.Frame(self.root) + frame_versions.pack(pady=5, fill=tk.BOTH, expand=True) + tk.Label(frame_versions, text="已下载版本:").pack(anchor=tk.W) + self.listbox_versions = tk.Listbox(frame_versions) + self.listbox_versions.pack(fill=tk.BOTH, expand=True, pady=(0, 5)) + self.listbox_versions.bind('<>', self.on_version_select) + + # 版本输入和按钮 + frame_version_input = tk.Frame(self.root) + frame_version_input.pack(pady=5) + tk.Label(frame_version_input, text="版本:").pack(side=tk.LEFT) + self.entry_version = tk.Entry(frame_version_input, width=20) + self.entry_version.pack(side=tk.LEFT, padx=(5, 10)) + + self.button_launch = tk.Button(frame_version_input, text="启动游戏", command=self.launch_game) + self.button_launch.pack(side=tk.LEFT, padx=(0, 5)) + + self.button_download_version = tk.Button(frame_version_input, text="下载版本", command=self.download_version_gui) + self.button_download_version.pack(side=tk.LEFT) + + # 下载 Java 按钮 + self.button_download_java = tk.Button(self.root, text="下载 Java (JDK 21)", command=self.start_download_java) + self.button_download_java.pack(pady=5) + + # 日志输出 + frame_log = tk.Frame(self.root) + frame_log.pack(pady=5, fill=tk.BOTH, expand=True) + tk.Label(frame_log, text="日志:").pack(anchor=tk.W) + self.text_log = scrolledtext.ScrolledText(frame_log, state='disabled', height=8) + self.text_log.pack(fill=tk.BOTH, expand=True) + + def log(self, message): + """在日志区域添加信息""" + self.text_log.config(state='normal') + self.text_log.insert(tk.END, message + "\n") + self.text_log.config(state='disabled') + self.text_log.see(tk.END) # 自动滚动到底部 + + def refresh_versions(self): + """刷新已下载版本列表""" + self.listbox_versions.delete(0, tk.END) + versions_path = os.path.join(self.minecraft_directory, "versions") + if os.path.exists(versions_path): + try: + for item in os.scandir(versions_path): + if item.is_dir(): + self.listbox_versions.insert(tk.END, item.name) + except Exception as e: + self.log(f"刷新版本列表时出错: {e}") + + def on_version_select(self, event): + """当在列表框中选择版本时,填充到输入框""" + selection = self.listbox_versions.curselection() + if selection: + version = self.listbox_versions.get(selection[0]) + self.entry_version.delete(0, tk.END) + self.entry_version.insert(0, version) + + def get_username(self): + """获取用户名""" + username = self.entry_username.get().strip() + if not username: + messagebox.showerror("错误", "请输入用户名!") + return None + if ' ' in username: + messagebox.showwarning("警告", "用户名包含空格,Minecraft 可能不支持。已移除空格。") + username = username.replace(' ', '_') + self.entry_username.delete(0, tk.END) + self.entry_username.insert(0, username) + return username + + def get_version(self): + """获取版本""" + version = self.entry_version.get().strip() + if not version: + messagebox.showerror("错误", "请输入或选择版本!") + return None + return version + + def is_version_installed(self, version): + """检查版本是否已安装""" + version_json_path = os.path.join(self.minecraft_directory, "versions", version, f"{version}.json") + return os.path.exists(version_json_path) + + def download_minecraft(self, version_name): + """下载 Minecraft 版本 (后台线程执行)""" + try: + self.log(f"开始下载版本 {version_name}...") + # 确保 .minecraft 目录存在 + os.makedirs(self.minecraft_directory, exist_ok=True) + + if self.is_version_installed(version_name): + self.log(f"版本 {version_name} 已经存在!") + return True + + # 使用新版本 API + if hasattr(minecraft_launcher_lib, "install"): + minecraft_launcher_lib.install.install_minecraft_version(version_name, self.minecraft_directory) + else: + # 旧版本 API (不太可能在新库中) + minecraft_launcher_lib.utils.download_minecraft(version_name, self.minecraft_directory) + + if self.is_version_installed(version_name): + self.log(f"版本 {version_name} 下载并验证成功!") + # 在主线程刷新 UI + self.root.after(0, self.refresh_versions) + return True + else: + self.log(f"版本 {version_name} 下载失败或验证失败!") + return False + except Exception as e: + self.log(f"下载版本 {version_name} 时出错: {e}") + return False + + def download_version_gui(self): + """下载版本按钮的回调""" + version = self.get_version() + if version: + # 在新线程中执行下载,避免阻塞 GUI + thread = threading.Thread(target=self.download_minecraft, args=(version,)) + thread.daemon = True # 主程序关闭时,线程也关闭 + thread.start() + + def download_java(self): + """下载并解压 Java (后台线程执行)""" + try: + self.log("开始下载 Java (JDK 21)...") + if os.path.exists(self.java_path): + self.log("Java 已存在,无需下载。") + return True + + # 确保 launcher 目录存在 + os.makedirs(self.launcher_directory, exist_ok=True) + + # 下载 JDK + self.log(f"正在从 {self.java_download_url} 下载 Java (约 180MB)...") + with requests.get(self.java_download_url, stream=True) as r: + r.raise_for_status() + with open(self.java_zip_path, "wb") as f: + for chunk in r.iter_content(chunk_size=8192): + f.write(chunk) + self.log("Java 下载完成。") + + # 解压 ZIP 文件 + self.log("正在解压 Java... (可能需要几分钟)") + with zipfile.ZipFile(self.java_zip_path, "r") as zip_ref: + zip_ref.extractall(self.launcher_directory) + self.log("Java 解压完成。") + + # 清理 ZIP 文件 + os.remove(self.java_zip_path) + self.log("已删除 Java ZIP 压缩包。") + + # 验证 Java 是否可用 + if os.path.exists(self.java_path): + self.log("Java 安装成功!") + # 在主线程更新按钮状态或提示 (如果需要) + # self.root.after(0, lambda: self.button_download_java.config(state='disabled', text="Java 已下载")) + return True + else: + self.log("Java 安装失败:解压后未找到 java.exe") + return False + + except Exception as e: + self.log(f"下载/安装 Java 时出错: {e}") + # 清理失败的下载 + try: + if os.path.exists(self.java_zip_path): + os.remove(self.java_zip_path) + # 注意:解压失败的目录清理可能比较复杂,这里简化处理 + # if os.path.exists(self.java_dir) and not os.listdir(self.java_dir): + # os.rmdir(self.java_dir) + except: + pass + return False + + def start_download_java(self): + """下载 Java 按钮的回调""" + # 简单确认 + if messagebox.askyesno("确认", "即将下载约 180MB 的 Java (JDK 21) 文件。确定继续吗?"): + self.log("--- 开始 Java 下载任务 ---") + # 在新线程中执行下载,避免阻塞 GUI + thread = threading.Thread(target=self.download_java) + thread.daemon = True + thread.start() + + + def launch_game(self): + """启动游戏按钮的回调""" + username = self.get_username() + version = self.get_version() + if not username or not version: + return + + if not self.is_version_installed(version): + messagebox.showerror("错误", f"版本 {version} 未安装!请先下载。") + return + + # 检查 Java + if not os.path.exists(self.java_path): + messagebox.showwarning("警告", "未检测到 Java。请先点击 '下载 Java' 按钮。") + return + + self.log(f"准备启动游戏: {version} 作为用户: {username}") + + try: + user_uuid = str(uuid.uuid3(uuid.NAMESPACE_DNS, username)) + options = { + "username": username, + "uuid": user_uuid, + "token": "", # 离线模式,token 为空 + "launcherName": "DizzyCraftLauncher2_GUI", + "versionName": version, + "gameDirectory": self.minecraft_directory, + } + + command = minecraft_launcher_lib.command.get_minecraft_command( + version=version, minecraft_directory=self.minecraft_directory, options=options + ) + # 替换命令中的 Java 路径 + command[0] = self.java_path + + self.log("启动命令: " + " ".join(command)) + self.log("--- 游戏启动中,请稍候... ---\n请耐心等待,由于不会写多线程互通,游戏启动时启动器可能会出现未响应,等等就好了") + + # 启动游戏进程 (阻塞当前线程) + # 注意:subprocess.run 会阻塞调用它的线程。 + # 如果希望 GUI 在游戏运行时仍然响应,可以考虑使用 subprocess.Popen, + # 但这会使得管理游戏进程生命周期变得复杂。 + # 这里为了简化,直接在调用线程运行。如果在主线程调用会导致 GUI 冻结。 + # 更好的做法是在新线程中启动,但需要处理进程间通信等问题。 + # 作为示例,我们暂时在调用线程(即当前按钮点击事件的线程,它本身就在主线程之外) + # 执行 run,但这意味着 GUI 会在此期间无响应。 + # --- 改进:在新线程中启动游戏 --- + def run_game(): + try: + # 重新获取用户名和版本,以防用户在点击后修改 + # 但为了简化,我们使用点击时的值 + result = subprocess.run(command) + self.root.after(0, lambda: self.log(f"--- 游戏已退出 (返回码: {result.returncode}) ---")) + except Exception as e: + self.root.after(0, lambda: self.log(f"启动游戏进程时出错: {e}")) + self.root.after(0, lambda: messagebox.showerror("启动错误", f"无法启动游戏: {e}")) + + game_thread = threading.Thread(target=run_game) + game_thread.daemon = True # 主程序关闭时,游戏进程也可能被终止 (取决于系统) + game_thread.start() + self.log("游戏启动命令已发送到新线程。") + + except Exception as e: + self.log(f"构建启动命令时出错: {e}") + messagebox.showerror("启动错误", f"无法构建启动命令: {e}") + + +if __name__ == "__main__": + # --- 确保必要的目录存在 --- + # (虽然类内部也会创建,但提前确保更安全) + launcher_dir = "C:\\DizzyCraftLauncher2" + os.makedirs(launcher_dir, exist_ok=True) + os.makedirs(os.path.join(launcher_dir, ".minecraft"), exist_ok=True) + + + root = tk.Tk() + app = LauncherGUI(root) + # 显示初始欢迎信息 + app.log("DizzyCraftLauncher2 GUI 已启动。") + + root.mainloop() + + + + diff --git a/晕.ico b/晕.ico new file mode 100644 index 0000000000000000000000000000000000000000..aa0fba08230d30448f16b680e43f71bcbcee8bc4 GIT binary patch literal 519 zcmV+i0{Hy^0096203aX$0096X0Pz6;02TlM0EtjeM-2)Z3IG5A4M|8uQUCw|AOHXW zAP5Ek0047(dh`GQ00DDSM?wIu&K&6g00FB>L_t(oN9~u-D}!+y$6sIMSCN@$htLi#Rmkk zNFw7py*n=ERR;|q&&gAu8idefSGbbFH_TSxQ~~Bt@EaCXCXm1{yo<^n&@1C-pzXd2 zaA#n*%d$mO07cfzNc#f7|5LzYhY>fFmwfGny)Y~X7(zQ3gkv=M^!i_BG!DnW-0X8PbUs>|=F zflvgVVtP+B*23y3teoTsKn>eo55sO~tB0c-a9Z@*-#^1+3=Xd|Pp`T~fhba$K zmqD?B(qf1vAgMs`7G}3{2;gb}Uk_L-^(3DG)E-zn%W(FRv!Dd)EhMiwd6w)P- z6ZmKA0zg55KQWS-Jd~O~mYcnpn7*2&-l{zBhaZ>IcL)>K^H=bczaIisGeQ6W002ov JPDHLkV1l}~+Gqd( literal 0 HcmV?d00001