上传文件至「/」
This commit is contained in:
@@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user