上传文件至「/」

This commit is contained in:
ljz
2026-04-26 20:42:30 +08:00
parent 047433d8c2
commit e605f76381
2 changed files with 329 additions and 0 deletions
+329
View File
@@ -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('<<ListboxSelect>>', 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()
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 519 B