229 lines
8.0 KiB
Python
229 lines
8.0 KiB
Python
import os
|
||
import json
|
||
import re
|
||
import argparse
|
||
from uuid import UUID
|
||
|
||
def is_valid_uuid(uuid_str):
|
||
"""验证UUID格式有效性"""
|
||
try:
|
||
UUID(uuid_str)
|
||
return True
|
||
except ValueError:
|
||
return False
|
||
|
||
def get_uuid_variants(uuid_str):
|
||
"""生成UUID的两种形式(带连字符和不带)"""
|
||
if not is_valid_uuid(uuid_str):
|
||
return [uuid_str]
|
||
|
||
uuid_obj = UUID(uuid_str)
|
||
return [str(uuid_obj), uuid_obj.hex]
|
||
|
||
def replace_uuid_variants(content, old_variants, new_variants):
|
||
"""替换所有UUID变体"""
|
||
total = 0
|
||
for old, new in zip(old_variants, new_variants):
|
||
pattern = re.compile(r'\b' + re.escape(old) + r'\b')
|
||
content, count = pattern.subn(new, content)
|
||
total += count
|
||
return content, total
|
||
|
||
def process_snbt_file(file_path, old_uuid, new_uuid):
|
||
"""处理SNBT文件"""
|
||
old_forms = get_uuid_variants(old_uuid)
|
||
new_forms = get_uuid_variants(new_uuid)
|
||
|
||
try:
|
||
with open(file_path, 'r+', encoding='utf-8') as f:
|
||
content = f.read()
|
||
new_content, count = replace_uuid_variants(content, old_forms, new_forms)
|
||
|
||
if count > 0:
|
||
print(f"在 {os.path.basename(file_path)} 中替换了 {count} 处UUID")
|
||
f.seek(0)
|
||
f.write(new_content)
|
||
f.truncate()
|
||
else:
|
||
print(f"在 {os.path.basename(file_path)} 中未找到目标UUID")
|
||
except Exception as e:
|
||
print(f"处理 {file_path} 时出错: {str(e)}")
|
||
|
||
def replace_json_uuid(file_path, old_uuid, new_uuid):
|
||
"""处理JSON文件"""
|
||
try:
|
||
with open(file_path, 'r+', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
changes = 0
|
||
|
||
if isinstance(data, list):
|
||
for item in data:
|
||
if item.get('uuid') == old_uuid:
|
||
item['uuid'] = new_uuid
|
||
changes += 1
|
||
elif isinstance(data, dict):
|
||
if old_uuid in data:
|
||
data[new_uuid] = data.pop(old_uuid)
|
||
changes += 1
|
||
|
||
if changes > 0:
|
||
print(f"在 {os.path.basename(file_path)} 中替换了 {changes} 处UUID")
|
||
f.seek(0)
|
||
f.truncate()
|
||
json.dump(data, f, indent=4, ensure_ascii=False)
|
||
else:
|
||
print(f"在 {os.path.basename(file_path)} 中未找到目标UUID")
|
||
except Exception as e:
|
||
print(f"处理 {file_path} 时出错: {str(e)}")
|
||
|
||
def rename_uuid_files(root_dir, old_uuid, new_uuid):
|
||
"""重命名文件和目录"""
|
||
renamed_count = 0
|
||
|
||
for root, dirs, files in os.walk(root_dir, topdown=False):
|
||
# 先处理文件
|
||
for name in files:
|
||
if old_uuid in name:
|
||
new_name = name.replace(old_uuid, new_uuid)
|
||
src = os.path.join(root, name)
|
||
dst = os.path.join(root, new_name)
|
||
try:
|
||
os.rename(src, dst)
|
||
renamed_count += 1
|
||
print(f"已重命名文件:{name} → {new_name}")
|
||
except Exception as e:
|
||
print(f"文件重命名失败 [{src}]: {str(e)}")
|
||
|
||
# 处理目录
|
||
for name in dirs:
|
||
if old_uuid in name:
|
||
new_name = name.replace(old_uuid, new_uuid)
|
||
src = os.path.join(root, name)
|
||
dst = os.path.join(root, new_name)
|
||
try:
|
||
os.rename(src, dst)
|
||
print(f"已重命名目录:{name} → {new_name}")
|
||
except Exception as e:
|
||
print(f"目录重命名失败 [{src}]: {str(e)}")
|
||
|
||
print(f"完成重命名操作,共处理 {renamed_count} 个文件")
|
||
|
||
def process_directory(base_path, sub_path, old_uuid, new_uuid):
|
||
"""处理指定目录下的所有相关文件"""
|
||
target_dir = os.path.join(base_path, "world", sub_path)
|
||
if not os.path.exists(target_dir):
|
||
print(f"未找到目录:{target_dir}")
|
||
return
|
||
|
||
for root, _, files in os.walk(target_dir):
|
||
for file in files:
|
||
if file.endswith((".json", ".snbt")):
|
||
process_snbt_file(os.path.join(root, file), old_uuid, new_uuid)
|
||
|
||
def process_server(server_path, old_uuid, new_uuid):
|
||
"""主处理流程"""
|
||
# 处理核心JSON文件
|
||
for json_file in ["usercache.json", "usernamecache.json"]:
|
||
file_path = os.path.join(server_path, json_file)
|
||
if os.path.exists(file_path):
|
||
replace_json_uuid(file_path, old_uuid, new_uuid)
|
||
|
||
# 处理各模块数据
|
||
modules = [
|
||
("ftbteams/party", "FTB Teams队伍数据"),
|
||
("ftbteams/player", "FTB Teams玩家数据"),
|
||
("ftbchunks", "FTB Chunks数据"),
|
||
("ftbquests", "FTB Quests数据")
|
||
]
|
||
|
||
for path, name in modules:
|
||
print(f"\n正在处理{name}...")
|
||
process_directory(server_path, path, old_uuid, new_uuid)
|
||
|
||
# 处理world目录文件重命名
|
||
world_dir = os.path.join(server_path, "world")
|
||
if os.path.exists(world_dir):
|
||
print("\n正在处理文件重命名...")
|
||
rename_uuid_files(world_dir, old_uuid, new_uuid)
|
||
else:
|
||
print(f"未找到world目录:{world_dir}")
|
||
|
||
def load_config(config_path):
|
||
"""加载配置文件"""
|
||
try:
|
||
with open(config_path, 'r', encoding='utf-8') as f:
|
||
config = json.load(f)
|
||
|
||
if not all(k in config for k in ("path", "mappings")):
|
||
raise ValueError("配置文件缺少必要字段")
|
||
|
||
if not isinstance(config["mappings"], dict):
|
||
raise TypeError("mappings字段必须为字典")
|
||
|
||
return config
|
||
except Exception as e:
|
||
print(f"配置文件错误: {str(e)}")
|
||
exit(1)
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(description="Minecraft UUID迁移工具")
|
||
parser.add_argument("--config", help="配置文件路径")
|
||
args = parser.parse_args()
|
||
|
||
print("=" * 50)
|
||
print("Minecraft UUID迁移工具 v4.0")
|
||
print("=" * 50)
|
||
|
||
if args.config:
|
||
if not os.path.exists(args.config):
|
||
print(f"错误:配置文件 {args.config} 不存在")
|
||
exit(1)
|
||
|
||
config = load_config(args.config)
|
||
server_path = config["path"]
|
||
|
||
if not os.path.isdir(server_path):
|
||
print(f"错误:无效的服务端路径 {server_path}")
|
||
exit(1)
|
||
|
||
print(f"\n开始批量处理 {server_path}")
|
||
for old_uuid, new_uuid in config["mappings"].items():
|
||
print(f"\n{'='*30} 正在处理 {old_uuid[:8]}... {'='*30}")
|
||
process_server(server_path, old_uuid, new_uuid)
|
||
exit(0)
|
||
|
||
# 交互模式
|
||
while True:
|
||
server_path = input("\n请输入服务端路径(输入q退出): ").strip()
|
||
if server_path.lower() == "q":
|
||
break
|
||
|
||
if not os.path.isdir(server_path):
|
||
print("错误:路径无效或不存在")
|
||
continue
|
||
|
||
while True:
|
||
print("\n" + "=" * 50)
|
||
old_uuid = input("请输入原UUID(输入q返回上级): ").strip()
|
||
if old_uuid.lower() == "q":
|
||
break
|
||
|
||
if not is_valid_uuid(old_uuid):
|
||
print("错误:无效的UUID格式")
|
||
continue
|
||
|
||
new_uuid = input("请输入新UUID: ").strip()
|
||
if not is_valid_uuid(new_uuid):
|
||
print("错误:无效的新UUID格式")
|
||
continue
|
||
|
||
process_server(server_path, old_uuid, new_uuid)
|
||
|
||
choice = input("\n是否继续处理其他UUID?(y/n): ").lower()
|
||
if choice != "y":
|
||
break
|
||
|
||
print("\n感谢使用!")
|
||
|
||
if __name__ == "__main__":
|
||
main() |