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()