2.4.1. Epic游戏备份和还原工具
2.4.1.1. 第一步 Python环境安装
到Python官网下载安装包(自行安装)
https://www.python.org/downloads/windows/将下列文本保存为
requirements.txtPySide6==6.10.2 PySide6_Addons==6.10.2 PySide6_Essentials==6.10.2 shiboken6==6.10.2
使用下面命令安装python模块
pip install -r requirements.txt
如果感觉下载速度慢可以使用国内的源
pip install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ 其它镜像源 https://pypi.tuna.tsinghua.edu.cn/simple/ https://pypi.mirrors.ustc.edu.cn/simple/ https://mirrors.cloud.tencent.com/pypi/simple/
2.4.1.2. 第二步 源码保存为py文件
将下列源码保存为 EpicGameBackupTool.py
"""
================================================================================
EPIC GAME BACKUP TOOL - 免责声明
================================================================================
【软件性质声明】
本工具为第三方开源辅助工具,非 Epic Games 官方产品,与 Epic Games, Inc.
及其关联公司无任何关联、认可、赞助或合作关系。
【风险提示】
1. 数据安全风险
- 备份/还原操作可能导致游戏文件损坏、存档丢失或游戏无法启动
- 涉及系统目录(C:\\ProgramData\\Epic\\...)写入,操作不当可能影响系统稳定性
- 强烈建议在操作前备份重要数据
2. 账号安全风险
- 修改游戏文件可能导致 Epic Games 账号异常或封禁
- 请确保仅用于个人正版游戏的合法备份
3. 权限要求
- 还原脚本需要管理员权限运行
- 请以管理员身份启动本程序及还原脚本
【责任限制】
作者(-_u友歌u_-)不对以下情况承担任何责任:
- 因使用本工具导致的任何数据丢失、损坏或业务中断
- 游戏无法识别、Epic 启动器异常或系统故障
- 违反 Epic Games 用户协议导致的账号处罚
- 因操作失误、硬件故障或不可抗力导致的损失
【使用条款】
1. 本工具仅供拥有正版游戏授权的用户个人使用
2. 禁止用于盗版游戏、商业用途或非法目的
3. 禁止反编译、修改后重新分发
4. 使用本工具即视为您已阅读、理解并同意承担全部操作风险
【技术支持】
电子邮件:[email protected]
联系页面:https://luxiaoyou.com/contact.html
社交媒体:抖音/哔哩哔哩 @-_u友歌u_-
作者不保证实时技术支持响应,也不承诺解决所有兼容性问题。
【最终条款】
使用本工具即表示您同意上述所有条款。如您不同意,请立即停止使用并删除本软件。
================================================================================
软件版本:v1.0
最后更新:2026年
================================================================================
"""
import sys
import os
import json
import subprocess
import re
from pathlib import Path
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
QHBoxLayout, QTableWidget, QTableWidgetItem,
QPushButton, QFileDialog, QMessageBox, QHeaderView,QToolBar,QDialog,QLabel, QTextEdit)
from PySide6.QtCore import Qt, QThread, Signal
class ExportWorker(QThread):
# 信号定义:进度文本,是否成功,成功消息,存储路径,脚本路径
progress_signal = Signal(str)
finished_signal = Signal(bool, str, str, str)
def __init__(self, game_info, target_root):
super().__init__()
self.game = game_info
self.target_root = Path(target_root)
def run(self):
try:
# 1. 获取核心数据
manifest_data = self.game['raw_data']
guid = manifest_data.get("InstallationGuid")
if not guid:
raise Exception("无法在 Manifest 中找到 InstallationGuid")
# 2. 准备导出目录
clean_name = "".join([c for c in self.game['name'] if c not in r'<>:"/\|?*']).strip()
export_path = self.target_root / clean_name
os.makedirs(export_path, exist_ok=True)
source_dir = self.game['path']
dest_dir = export_path / "GameFiles"
manifest_filename = f"{guid}.item"
epic_manifest_dir = "C:\\ProgramData\\Epic\\EpicGamesLauncher\\Data\\Manifests"
restore_bat_path = export_path / "restore_game.bat"
self.progress_signal.emit(f"🚀 准备开始镜像同步: {clean_name}")
# 3. 执行 Robocopy 并监控进度
# 移除了 /NP 以便获取进度,添加 /NJH /NJS 简化输出
cmd = f'robocopy "{source_dir}" "{dest_dir}" /MIR /MT:16 /R:2 /W:5 /NJH /NJS'
# 使用 Popen 实时捕获输出
process = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True,
text=True,
encoding='gbk',
errors='ignore'
)
last_percent = ""
while True:
line = process.stdout.readline()
if not line and process.poll() is not None:
break
if line:
# 正则匹配百分比(例如 10.5%)
match = re.search(r'(\d+(\.\d+)?%)', line)
if match:
percent = match.group(1)
if percent != last_percent:
self.progress_signal.emit(f"同步进度: {percent}")
last_percent = percent
# 如果行内包含文件名,且不是纯百分比,也可以输出(可选)
elif "100%" not in line and "GameFiles" not in line and line.strip():
# 只输出包含具体文件路径的行,避免刷屏
if "\\" in line:
self.progress_signal.emit(f"正在处理: {line.strip()}")
# 4. 导出 metadata.json
with open(export_path / "metadata.json", 'w', encoding='utf-8') as f:
json.dump(manifest_data, f, indent=4, ensure_ascii=False)
# 5. 生成 BAT 还原脚本
restore_bat = f"""@echo off
setlocal enabledelayedexpansion
title EpicGameBackup Restore - {clean_name}
echo ======================================================
echo EpicGameBackup 自动还原工具
echo ======================================================
echo 游戏名称: {clean_name}
echo 还原 Guid: {guid}
echo ======================================================
echo.
:: 检查权限
net session >nul 2>&1
if %errorLevel% neq 0 (
echo [错误] 请右键点击此脚本,选择“以管理员身份运行”!
pause
exit /b
)
echo [1/2] 正在同步游戏源码至: "{source_dir}"...
robocopy "%~dp0GameFiles" "{source_dir}" /MIR /MT:16 /R:2 /W:5
echo.
echo [2/2] 正在处理 Manifest 配置文件...
set "DEST_DIR={epic_manifest_dir}"
set "TARGET_ITEM=%DEST_DIR%\\{manifest_filename}"
if exist "!TARGET_ITEM!" (
echo [提示] 目标目录已存在 {manifest_filename}
choice /M "是否覆盖现有的 Manifest 文件?"
if errorlevel 2 (
echo [跳过] 已取消 Manifest 还原。
goto END
)
)
copy /Y "%~dp0metadata.json" "!TARGET_ITEM!"
echo [成功] Manifest 已还原为 {manifest_filename}
:END
echo.
echo 还原完成!请重启 Epic Launcher。
pause
"""
with open(restore_bat_path, 'w', encoding='gbk') as f:
f.write(restore_bat)
self.finished_signal.emit(True, "✅ 文件备份已全部完成!", str(export_path), str(restore_bat_path))
except Exception as e:
self.finished_signal.emit(False, f"❌ 导出发生异常: {str(e)}", "", "")
class EpicGameBackup(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Epic Game Backup Tool - Epic 游戏备份与恢复工具 抖音或哔哩哔哩 关注@-_u友歌u_-")
self.resize(1000, 700)
self.manifest_dir = Path("C:/ProgramData/Epic/EpicGamesLauncher/Data/Manifests")
self.games_data = []
self.init_ui()
self.refresh_list()
def init_ui(self):
widget = QWidget()
self.setCentralWidget(widget)
layout = QVBoxLayout(widget)
# 工具栏
bar = QToolBar()
self.addToolBar(bar)
bar.addAction("联系作者", self.show_about_me_dialog)
# 游戏表格
self.table = QTableWidget(0, 3)
self.table.setHorizontalHeaderLabels(["游戏名称", "版本", "安装路径"])
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
self.table.setSelectionBehavior(QTableWidget.SelectRows)
self.table.setAlternatingRowColors(False)
layout.addWidget(self.table)
# 日志监控区
self.log_area = QTextEdit()
self.log_area.setReadOnly(True)
self.log_area.setFixedHeight(200)
self.log_area.setStyleSheet("""
QTextEdit {
background-color: #1e1e1e;
color: #00ff00;
font-family: 'Consolas', 'Monaco', monospace;
font-size: 11pt;
border: 1px solid #333;
}
""")
layout.addWidget(self.log_area)
# 控制按钮
btns = QHBoxLayout()
self.btn_refresh = QPushButton("刷新游戏列表")
self.btn_refresh.setMinimumWidth(120)
self.btn_refresh.clicked.connect(self.refresh_list)
self.btn_export = QPushButton("开始备份")
self.btn_export.setFixedHeight(40)
self.btn_export.setStyleSheet("""
QPushButton {
background-color: #27ae60;
color: white;
font-weight: bold;
}
QPushButton:hover { background-color: #2ecc71; }
QPushButton:disabled { background-color: #7f8c8d; }
""")
self.btn_export.clicked.connect(self.start_export)
btns.addWidget(self.btn_refresh)
btns.addStretch()
btns.addWidget(self.btn_export)
layout.addLayout(btns)
def refresh_list(self):
self.table.setRowCount(0)
self.games_data = []
if not self.manifest_dir.exists():
self.log_area.append("⚠️ 系统中未检测到 Epic Manifests 文件夹。")
return
for file in self.manifest_dir.glob("*.item"):
try:
with open(file, 'r', encoding='utf-8') as f:
d = json.load(f)
info = {
"name": d.get("DisplayName", "未知项目"),
"path": d.get("InstallLocation", ""),
"raw_data": d
}
if info["path"]: # 只添加有安装路径的游戏
self.games_data.append(info)
row = self.table.rowCount()
self.table.insertRow(row)
self.table.setItem(row, 0, QTableWidgetItem(info["name"]))
self.table.setItem(row, 1, QTableWidgetItem(d.get("AppVersionString", "N/A")))
self.table.setItem(row, 2, QTableWidgetItem(info["path"]))
except: pass
self.log_area.append(f"🔄 列表更新成功:发现 {len(self.games_data)} 个游戏。请选择您要备份的游戏!")
def start_export(self):
row = self.table.currentRow()
if row < 0:
QMessageBox.warning(self, "提示", "请在列表中选中一个游戏后再操作。")
return
target = QFileDialog.getExistingDirectory(self, "选择保存游戏的根目录")
if not target: return
self.btn_export.setEnabled(False)
self.btn_refresh.setEnabled(False)
self.log_area.clear()
self.worker = ExportWorker(self.games_data[row], target)
self.worker.progress_signal.connect(self.log_area.append)
self.worker.finished_signal.connect(self.handle_finished)
self.worker.start()
def handle_finished(self, success, msg, export_dir, bat_path):
self.btn_export.setEnabled(True)
self.btn_refresh.setEnabled(True)
self.log_area.append("-" * 50)
self.log_area.append(msg)
if success:
self.log_area.append(f"📂 存储路径: {export_dir}")
self.log_area.append(f"📄 恢复脚本: {bat_path}")
self.log_area.append("-" * 50)
QMessageBox.information(self, "任务完成", f"游戏备份成功:\n{export_dir}")
else:
QMessageBox.critical(self, "错误", msg)
def show_about_me_dialog(self):
"""显示一个关于我的对话框"""
# 创建一个对话框
dialog = QDialog()
dialog.setWindowTitle("联系作者")
dialog.resize(300, 100)
# 创建布局
layout = QVBoxLayout()
# 添加一些个人信息
email_label = QLabel('电子邮件:<a href="mailto:[email protected]">[email protected]</a>')
email_label.setOpenExternalLinks(True) # 允许打开外部链接
home_page_label = QLabel('联系作者:<a href="https://luxiaoyou.com/contact.html">https://luxiaoyou.com/contact</a>')
home_page_label.setOpenExternalLinks(True)
# 设置文本对齐方式
email_label.setAlignment(Qt.AlignLeft)
home_page_label.setAlignment(Qt.AlignLeft)
# 添加到布局中
layout.addWidget(email_label)
layout.addWidget(home_page_label)
# 添加一个关闭按钮
close_button = QPushButton("关闭")
close_button.clicked.connect(dialog.close)
layout.addWidget(close_button)
# 设置布局到对话框
dialog.setLayout(layout)
# 显示对话框
dialog.exec()
if __name__ == "__main__":
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = EpicGameBackup()
window.show()
sys.exit(app.exec())
2.4.1.3. 第三步 运行脚本
python EpicGameBackupTool.py