import os, json, tkinter as tk, threading, time, traceback, shutil, subprocess, platform
from tkinter import ttk, filedialog, messagebox, scrolledtext
from datetime import datetime
from pathlib import Path
try: import UnityPy; from PIL import Image, ImageTk
except ImportError as e: print(f"缺少必要的库: {e}"); print("请使用以下命令安装所需库:"); print("pip install UnityPy Pillow"); exit(1)

class ConfigManager:
    def __init__(self, config_file="config.json"): self.config_file = config_file; self.config = self.load_config()
    def load_config(self):
        default_config = {"index_folder": "","replace_folder": "","export_texture": True,"export_textasset": True,
                         "replace_texture": True,"replace_textasset": True,"search_mode": "fuzzy","index_mode": "normal",
                         "fast_mode_preview": False}
        try:
            if os.path.exists(self.config_file):
                with open(self.config_file, 'r', encoding='utf-8') as f:
                    loaded_config = json.load(f)
                    for key in default_config:
                        if key not in loaded_config: loaded_config[key] = default_config[key]
                    return loaded_config
        except Exception as e: print(f"加载配置文件失败: {e}")
        return default_config
    def save_config(self):
        try:
            with open(self.config_file, 'w', encoding='utf-8') as f:
                json.dump(self.config, f, ensure_ascii=False, indent=2)
            return True
        except Exception as e: print(f"保存配置文件失败: {e}"); return False
    def get(self, key, default=None): return self.config.get(key, default)
    def set(self, key, value): self.config[key] = value; self.save_config()

class UnityResourceManager:
    def __init__(self):
        self.root = tk.Tk(); self.root.title("Unity资源管理工具"); self.root.geometry("1400x900")
        self.config = ConfigManager()
        self.main_frame = ttk.Frame(self.root); self.main_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
        self.notebook = ttk.Notebook(self.main_frame); self.notebook.pack(fill=tk.BOTH, expand=True)
        self.index_tab = ttk.Frame(self.notebook); self.notebook.add(self.index_tab, text="索引生成")
        self.replace_tab = ttk.Frame(self.notebook); self.notebook.add(self.replace_tab, text="资源替换")
        self.init_variables(); self.init_index_tab(); self.init_replace_tab()
        for dir_name in ["indices", "output", "logs", "previews"]: os.makedirs(dir_name, exist_ok=True)
    
    def init_variables(self):
        self.selected_folder = tk.StringVar(value=self.config.get("index_folder", ""))
        self.export_texture = tk.BooleanVar(value=self.config.get("export_texture", True))
        self.export_textasset = tk.BooleanVar(value=self.config.get("export_textasset", True))
        self.index_mode = tk.StringVar(value=self.config.get("index_mode", "normal"))
        self.fast_mode_preview = tk.BooleanVar(value=self.config.get("fast_mode_preview", False))
        self.replace_folder = tk.StringVar(value=self.config.get("replace_folder", ""))
        self.resource_name = tk.StringVar()
        self.search_mode = tk.StringVar(value=self.config.get("search_mode", "fuzzy"))
        self.replace_texture = tk.BooleanVar(value=self.config.get("replace_texture", True))
        self.replace_textasset = tk.BooleanVar(value=self.config.get("replace_textasset", True))
        self.search_results = []; self.selected_file = None; self.new_resource_file = None
        self.new_resource_data = None; self.new_resource_type = None; self.edited_text_content = None
        self.processing = False; self.cancel_flag = False; self.original_photo = None; self.new_photo = None
        self.sort_column = "resource_name"; self.sort_reverse = False
    
    def init_index_tab(self):
        folder_frame = ttk.LabelFrame(self.index_tab, text="Unity资源文件夹选择", padding=8)
        folder_frame.pack(fill=tk.X, padx=8, pady=8)
        ttk.Label(folder_frame, text="选择文件夹:").grid(row=0, column=0, sticky=tk.W, pady=4)
        ttk.Entry(folder_frame, textvariable=self.selected_folder, width=60).grid(row=0, column=1, padx=5)
        ttk.Button(folder_frame, text="浏览", command=self.browse_folder).grid(row=0, column=2, padx=5)
        export_frame = ttk.LabelFrame(self.index_tab, text="导出设置", padding=8)
        export_frame.pack(fill=tk.X, padx=8, pady=8)
        ttk.Checkbutton(export_frame, text="导出Texture2D", variable=self.export_texture, command=lambda: self.config.set("export_texture", self.export_texture.get())).grid(row=0, column=0, sticky=tk.W, padx=8)
        ttk.Checkbutton(export_frame, text="导出TextAsset(Json/Atlas)", variable=self.export_textasset, command=lambda: self.config.set("export_textasset", self.export_textasset.get())).grid(row=0, column=1, sticky=tk.W, padx=8)
        mode_frame = ttk.LabelFrame(self.index_tab, text="索引模式设置", padding=8)
        mode_frame.pack(fill=tk.X, padx=8, pady=8)
        mode_select_frame = ttk.Frame(mode_frame); mode_select_frame.pack(fill=tk.X, pady=5)
        ttk.Label(mode_select_frame, text="索引模式:").pack(side=tk.LEFT, padx=(0, 10))
        ttk.Radiobutton(mode_select_frame, text="普通模式", variable=self.index_mode, value="normal", command=lambda: self.config.set("index_mode", self.index_mode.get())).pack(side=tk.LEFT, padx=5)
        ttk.Radiobutton(mode_select_frame, text="快速模式", variable=self.index_mode, value="fast", command=lambda: self.config.set("index_mode", self.index_mode.get())).pack(side=tk.LEFT, padx=5)
        self.preview_frame = ttk.Frame(mode_frame); self.preview_frame.pack(fill=tk.X, pady=5)
        ttk.Checkbutton(self.preview_frame, text="快速模式下预览新增文件", variable=self.fast_mode_preview, command=lambda: self.config.set("fast_mode_preview", self.fast_mode_preview.get())).pack(side=tk.LEFT)
        progress_frame = ttk.LabelFrame(self.index_tab, text="进度", padding=8)
        progress_frame.pack(fill=tk.X, padx=8, pady=8)
        self.index_progress = ttk.Progressbar(progress_frame, mode='indeterminate', length=280)
        self.index_progress.pack(fill=tk.X, pady=4)
        log_frame = ttk.LabelFrame(self.index_tab, text="日志", padding=8)
        log_frame.pack(fill=tk.BOTH, expand=True, padx=8, pady=8)
        self.index_log = scrolledtext.ScrolledText(log_frame, height=12, wrap=tk.WORD, font=('Consolas', 9))
        self.index_log.pack(fill=tk.BOTH, expand=True)
        button_frame = ttk.Frame(self.index_tab); button_frame.pack(fill=tk.X, padx=8, pady=8)
        ttk.Button(button_frame, text="开始生成索引", command=self.start_index_generation).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="取消生成", command=self.cancel_index_generation).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="清空日志", command=self.clear_index_log).pack(side=tk.LEFT, padx=5)
        ttk.Button(button_frame, text="保存配置", command=self.save_current_config).pack(side=tk.LEFT, padx=5)
    
    def init_replace_tab(self):
        main_paned = ttk.PanedWindow(self.replace_tab, orient=tk.HORIZONTAL)
        main_paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        left_panel = ttk.Frame(main_paned); main_paned.add(left_panel, weight=3)
        folder_frame = ttk.LabelFrame(left_panel, text="文件夹设置", padding=6)
        folder_frame.pack(fill=tk.X, padx=5, pady=5)
        ttk.Label(folder_frame, text="游戏资源文件夹:").pack(anchor=tk.W, pady=2)
        folder_entry_frame = ttk.Frame(folder_frame); folder_entry_frame.pack(fill=tk.X, pady=2)
        ttk.Entry(folder_entry_frame, textvariable=self.replace_folder).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        ttk.Button(folder_entry_frame, text="浏览", command=self.browse_replace_folder, width=8).pack(side=tk.RIGHT)
        search_frame = ttk.LabelFrame(left_panel, text="搜索设置", padding=6)
        search_frame.pack(fill=tk.X, padx=5, pady=5)
        ttk.Label(search_frame, text="资源名称(纹理Name):").pack(anchor=tk.W, pady=2)
        name_entry_frame = ttk.Frame(search_frame); name_entry_frame.pack(fill=tk.X, pady=2)
        ttk.Entry(name_entry_frame, textvariable=self.resource_name).pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))
        ttk.Button(name_entry_frame, text="查询", command=self.search_resources, width=8).pack(side=tk.RIGHT)
        mode_frame = ttk.Frame(search_frame); mode_frame.pack(fill=tk.X, pady=5)
        ttk.Label(mode_frame, text="搜索模式:").pack(side=tk.LEFT, padx=(0, 5))
        ttk.Radiobutton(mode_frame, text="模糊搜索", variable=self.search_mode, value="fuzzy", command=lambda: self.config.set("search_mode", self.search_mode.get())).pack(side=tk.LEFT, padx=5)
        ttk.Radiobutton(mode_frame, text="精确搜索", variable=self.search_mode, value="exact", command=lambda: self.config.set("search_mode", self.search_mode.get())).pack(side=tk.LEFT, padx=5)
        type_frame = ttk.LabelFrame(left_panel, text="资源类型", padding=6)
        type_frame.pack(fill=tk.X, padx=5, pady=5)
        type_check_frame = ttk.Frame(type_frame); type_check_frame.pack(fill=tk.X, pady=2)
        ttk.Checkbutton(type_check_frame, text="Texture2D", variable=self.replace_texture, command=lambda: self.config.set("replace_texture", self.replace_texture.get())).pack(side=tk.LEFT, padx=10)
        ttk.Checkbutton(type_check_frame, text="TextAsset", variable=self.replace_textasset, command=lambda: self.config.set("replace_textasset", self.replace_textasset.get())).pack(side=tk.LEFT, padx=10)
        results_label_frame = ttk.Frame(left_panel); results_label_frame.pack(fill=tk.X, padx=5, pady=(8, 0))
        ttk.Label(results_label_frame, text="搜索结果", font=('Arial', 10, 'bold')).pack(side=tk.LEFT)
        results_frame = ttk.LabelFrame(left_panel, padding=5); results_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.results_tree = ttk.Treeview(results_frame, columns=("resource_name", "file_name", "resource_type"), show="headings", height=15)
        self.results_tree.heading("resource_name", text="资源名", command=lambda: self.sort_results("resource_name"))
        self.results_tree.heading("file_name", text="文件名", command=lambda: self.sort_results("file_name"))
        self.results_tree.heading("resource_type", text="类型", command=lambda: self.sort_results("resource_type"))
        self.results_tree.column("resource_name", width=150, anchor=tk.W)
        self.results_tree.column("file_name", width=200, anchor=tk.W)
        self.results_tree.column("resource_type", width=80, anchor=tk.CENTER)
        scrollbar = ttk.Scrollbar(results_frame, orient=tk.VERTICAL, command=self.results_tree.yview)
        self.results_tree.configure(yscrollcommand=scrollbar.set)
        self.results_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True); scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.replace_status = ttk.Label(left_panel, text="就绪", relief=tk.SUNKEN, padding=3)
        self.replace_status.pack(fill=tk.X, padx=5, pady=5)
        right_panel = ttk.Frame(main_paned); main_paned.add(right_panel, weight=2)
        preview_paned = ttk.PanedWindow(right_panel, orient=tk.VERTICAL)
        preview_paned.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        self.original_preview_frame = ttk.LabelFrame(preview_paned, text="原始文件", padding=6)
        preview_paned.add(self.original_preview_frame, weight=1)
        self.new_preview_frame = ttk.LabelFrame(preview_paned, text="替换图片", padding=6)
        preview_paned.add(self.new_preview_frame, weight=1)
        self.init_preview_areas()
        bottom_frame = ttk.Frame(right_panel); bottom_frame.pack(fill=tk.X, padx=5, pady=5)
        left_btn_frame = ttk.Frame(bottom_frame); left_btn_frame.pack(side=tk.LEFT, fill=tk.X, expand=True)
        ttk.Button(left_btn_frame, text="导入修改文件", command=self.select_new_resource).pack(side=tk.LEFT, padx=5)
        ttk.Button(left_btn_frame, text="清除", command=self.clear_preview).pack(side=tk.LEFT, padx=5)
        right_btn_frame = ttk.Frame(bottom_frame); right_btn_frame.pack(side=tk.RIGHT)
        ttk.Button(right_btn_frame, text="保存文本编辑", command=self.save_text_edit).pack(side=tk.LEFT, padx=5)
        ttk.Button(right_btn_frame, text="生成Mod", command=self.generate_mod).pack(side=tk.LEFT, padx=5)
        self.results_tree.bind('<<TreeviewSelect>>', self.on_tree_select)
        self.update_sort_indicators()
    
    def sort_results(self, column):
        items = [(self.results_tree.set(child, column), child) for child in self.results_tree.get_children('')]
        if self.sort_column == column: self.sort_reverse = not self.sort_reverse
        else: self.sort_column = column; self.sort_reverse = False
        items.sort(key=lambda x: x[0], reverse=self.sort_reverse)
        for index, (_, child) in enumerate(items): self.results_tree.move(child, '', index)
        self.update_sort_indicators()

    def update_sort_indicators(self):
        columns = ["resource_name", "file_name", "resource_type"]
        for col in columns:
            heading = self.results_tree.heading(col)
            base_text = heading['text'].rstrip(' ▾▾▴▴')
            if col == self.sort_column:
                if self.sort_reverse: heading["text"] = base_text + " ▾▾"
                else: heading["text"] = base_text + " ▴▴"
            else: heading["text"] = base_text
        
    def init_preview_areas(self):
        original_content = ttk.Frame(self.original_preview_frame)
        original_content.pack(fill=tk.BOTH, expand=True)
        info_left_frame = ttk.LabelFrame(original_content, text="文件信息", padding=6)
        info_left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=False, padx=(0, 5))
        info_left_frame.config(width=180)
        self.original_info_text = scrolledtext.ScrolledText(info_left_frame, wrap=tk.WORD, height=8, width=20, font=('Consolas', 9))
        self.original_info_text.pack(fill=tk.BOTH, expand=True)
        img_right_frame = ttk.LabelFrame(original_content, text="预览", padding=6)
        img_right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        self.original_image_canvas = tk.Canvas(img_right_frame, width=250, height=250, bg='#f5f5f5')
        self.original_image_canvas.pack(fill=tk.BOTH, expand=True, pady=5)
        self.original_resolution_label = ttk.Label(img_right_frame, text="尺寸: 未加载")
        self.original_resolution_label.pack(pady=3)
        self.original_text_preview = scrolledtext.ScrolledText(img_right_frame, wrap=tk.WORD, height=10)
        new_content = ttk.Frame(self.new_preview_frame)
        new_content.pack(fill=tk.BOTH, expand=True)
        new_info_left_frame = ttk.LabelFrame(new_content, text="替换信息", padding=6)
        new_info_left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=False, padx=(0, 5))
        new_info_left_frame.config(width=180)
        self.new_info_text = scrolledtext.ScrolledText(new_info_left_frame, wrap=tk.WORD, height=8, width=20, font=('Consolas', 9))
        self.new_info_text.pack(fill=tk.BOTH, expand=True)
        new_img_right_frame = ttk.LabelFrame(new_content, text="预览", padding=6)
        new_img_right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
        self.new_image_canvas = tk.Canvas(new_img_right_frame, width=250, height=250, bg='#f5f5f5')
        self.new_image_canvas.pack(fill=tk.BOTH, expand=True, pady=5)
        self.new_resolution_label = ttk.Label(new_img_right_frame, text="尺寸: 未加载")
        self.new_resolution_label.pack(pady=3)
        self.new_text_preview = scrolledtext.ScrolledText(new_img_right_frame, wrap=tk.WORD, height=10)
    
    def browse_folder(self):
        folder = filedialog.askdirectory(title="选择Unity资源文件夹")
        if folder: self.selected_folder.set(folder); self.log_index(f"已选择文件夹: {folder}"); self.config.set("index_folder", folder)
    
    def browse_replace_folder(self):
        folder = filedialog.askdirectory(title="选择Unity资源文件夹")
        if folder: self.replace_folder.set(folder); self.config.set("replace_folder", folder)
    
    def save_current_config(self):
        self.config.set("index_folder", self.selected_folder.get())
        self.config.set("replace_folder", self.replace_folder.get())
        self.config.set("export_texture", self.export_texture.get())
        self.config.set("export_textasset", self.export_textasset.get())
        self.config.set("replace_texture", self.replace_texture.get())
        self.config.set("replace_textasset", self.replace_textasset.get())
        self.config.set("search_mode", self.search_mode.get())
        self.config.set("index_mode", self.index_mode.get())
        self.config.set("fast_mode_preview", self.fast_mode_preview.get())
        messagebox.showinfo("提示", "配置已保存！")
    
    def open_folder(self, folder_path):
        try:
            if os.path.exists(folder_path):
                if platform.system() == "Windows": os.startfile(folder_path)
                elif platform.system() == "Darwin": subprocess.run(["open", folder_path])
                else: subprocess.run(["xdg-open", folder_path])
            else: messagebox.showwarning("警告", f"文件夹不存在: {folder_path}")
        except Exception as e: messagebox.showerror("错误", f"打开文件夹失败: {str(e)}")
    
    def log_index(self, message):
        timestamp = datetime.now().strftime("%H:%M:%S")
        self.index_log.insert(tk.END, f"[{timestamp}] {message}\n")
        self.index_log.see(tk.END); self.root.update()
    
    def clear_index_log(self): self.index_log.delete(1.0, tk.END)
    
    def load_json_file(self, filename):
        try:
            if os.path.exists(filename):
                with open(filename, 'r', encoding='utf-8') as f: return json.load(f)
        except Exception as e: self.log_index(f"加载JSON文件失败: {filename}, 错误: {str(e)}")
        return []
    
    def save_json_file(self, filename, data):
        try:
            with open(filename, 'w', encoding='utf-8') as f:
                json.dump(data, f, ensure_ascii=False, indent=2)
            return True
        except Exception as e: self.log_index(f"保存JSON文件失败: {filename}, 错误: {str(e)}"); return False
    
    def start_index_generation(self):
        if not self.selected_folder.get(): messagebox.showerror("错误", "请先选择Unity资源文件夹"); return
        if not any([self.export_texture.get(), self.export_textasset.get()]): messagebox.showerror("错误", "请至少选择一种资源类型进行导出"); return
        self.processing = True; self.cancel_flag = False; self.index_progress.start()
        thread = threading.Thread(target=self.generate_index_thread); thread.daemon = True; thread.start()
    
    def cancel_index_generation(self):
        if self.processing: self.cancel_flag = True; self.log_index("正在取消索引生成，请稍候...")
        else: messagebox.showinfo("提示", "当前没有正在进行的索引生成任务")
    
    def generate_index_thread(self):
        try: self.generate_index()
        except Exception as e: self.log_index(f"索引生成过程中出现错误: {str(e)}"); self.log_index(traceback.format_exc())
        finally: self.processing = False; self.index_progress.stop(); self.log_index("索引生成完成！")
    
    def generate_index(self):
        folder = self.selected_folder.get(); is_fast_mode = self.index_mode.get() == "fast"; preview_new = self.fast_mode_preview.get()
        self.log_index(f"开始遍历文件夹: {folder}"); self.log_index(f"索引模式: {'快速模式' if is_fast_mode else '普通模式'}")
        if is_fast_mode: self.log_index(f"快速模式预览: {'开启' if preview_new else '关闭'}")
        texture_data = self.load_json_file("indices/texture2d.json")
        textasset_data = self.load_json_file("indices/textasset.json")
        texture_dict = {}; textasset_dict = {}
        for item in texture_data: key = (item.get("original_file"), item.get("resource_name")); texture_dict[key] = item
        for item in textasset_data: key = (item.get("original_file"), item.get("resource_name")); textasset_dict[key] = item
        fast_texture_files = set(); fast_textasset_files = set()
        if is_fast_mode:
            for item in texture_data: fast_texture_files.add(item.get("original_file"))
            for item in textasset_data: fast_textasset_files.add(item.get("original_file"))
            self.log_index(f"快速模式: 已索引 {len(fast_texture_files)} 个纹理文件, {len(fast_textasset_files)} 个文本资源文件")
        preview_dir = None
        if is_fast_mode and preview_new:
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            preview_dir = os.path.join("previews", f"preview_{timestamp}")
            os.makedirs(preview_dir, exist_ok=True)
            self.log_index(f"预览文件将保存到: {preview_dir}")
        total_files = 0; processed_files = 0; skipped_files = 0; found_textures = 0; found_textassets = 0; updated_textures = 0; updated_textassets = 0
        for root, dirs, files in os.walk(folder): total_files += len(files)
        self.log_index(f"总共发现 {total_files} 个文件")
        for root, dirs, files in os.walk(folder):
            for file in files:
                if self.cancel_flag: self.log_index(f"索引生成已取消，已处理 {processed_files} 个文件"); self.save_index_files(texture_dict, textasset_dict); return
                processed_files += 1
                if processed_files % 100 == 0: self.log_index(f"已处理 {processed_files}/{total_files} 个文件")
                file_path = os.path.join(root, file); rel_path = os.path.relpath(file_path, folder)
                filename_no_ext = os.path.splitext(os.path.basename(file))[0]
                if is_fast_mode:
                    texture_exists = filename_no_ext in fast_texture_files
                    textasset_exists = filename_no_ext in fast_textasset_files
                    if texture_exists or textasset_exists: skipped_files += 1; continue
                try:
                    env = UnityPy.load(file_path)
                    for obj in env.objects:
                        if obj.type.name == "Texture2D" and self.export_texture.get():
                            data = obj.read(); resource_name = getattr(data, "name", getattr(data, "m_Name", "未知名称"))
                            record = {"original_file": filename_no_ext, "resource_name": str(resource_name), "resource_type": "Texture2D", "source_file": rel_path, "index_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
                            key = (filename_no_ext, str(resource_name))
                            if key in texture_dict:
                                if not is_fast_mode: texture_dict[key] = record; updated_textures += 1
                            else:
                                texture_dict[key] = record; found_textures += 1
                                if is_fast_mode and preview_new and hasattr(data, "image"): self.save_texture_preview(data, resource_name, preview_dir)
                        elif obj.type.name in ["TextAsset", "SpriteAtlas"] and self.export_textasset.get():
                            data = obj.read(); resource_name = getattr(data, "name", getattr(data, "m_Name", "未知名称"))
                            record = {"original_file": filename_no_ext, "resource_name": str(resource_name), "resource_type": obj.type.name, "source_file": rel_path, "index_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
                            key = (filename_no_ext, str(resource_name))
                            if key in textasset_dict:
                                if not is_fast_mode: textasset_dict[key] = record; updated_textassets += 1
                            else:
                                textasset_dict[key] = record; found_textassets += 1
                                if is_fast_mode and preview_new and hasattr(data, "m_Script"): self.save_textasset_preview(data, resource_name, preview_dir)
                except Exception as e: self.log_index(f"处理文件失败: {rel_path}, 错误: {str(e)}"); continue
        self.save_index_files(texture_dict, textasset_dict)
        self.log_index(f"索引生成完成！总计处理 {processed_files} 个文件")
        if is_fast_mode:
            self.log_index(f"快速模式: 跳过 {skipped_files} 个已索引文件")
            self.log_index(f"新增Texture2D: {found_textures} 个")
            self.log_index(f"新增TextAsset: {found_textassets} 个")
        else:
            self.log_index(f"普通模式: 新增Texture2D: {found_textures} 个, 更新: {updated_textures} 个")
            self.log_index(f"普通模式: 新增TextAsset: {found_textassets} 个, 更新: {updated_textassets} 个")
        if preview_dir and (found_textures > 0 or found_textassets > 0): self.log_index(f"预览文件已保存到: {preview_dir}")
    
    def save_index_files(self, texture_dict, textasset_dict):
        if self.export_texture.get(): self.save_json_file("indices/texture2d.json", list(texture_dict.values()))
        if self.export_textasset.get(): self.save_json_file("indices/textasset.json", list(textasset_dict.values()))
    
    def save_texture_preview(self, texture_data, resource_name, preview_dir):
        try:
            if hasattr(texture_data, "image"):
                image = texture_data.image
                if image:
                    safe_name = "".join(c for c in resource_name if c.isalnum() or c in (' ', '_', '-')).rstrip()
                    if not safe_name: safe_name = "texture"
                    file_name = f"{safe_name}.png"; file_path = os.path.join(preview_dir, file_name)
                    counter = 1
                    while os.path.exists(file_path): file_name = f"{safe_name}_{counter}.png"; file_path = os.path.join(preview_dir, file_name); counter += 1
                    image.save(file_path, "PNG")
        except Exception as e: self.log_index(f"保存纹理预览失败 {resource_name}: {e}")
    
    def save_textasset_preview(self, textasset_data, resource_name, preview_dir):
        try:
            if hasattr(textasset_data, "m_Script"):
                content = textasset_data.m_Script
                if isinstance(content, bytes):
                    try: text = content.decode('utf-8')
                    except: text = str(content)
                else: text = str(content)
                safe_name = "".join(c for c in resource_name if c.isalnum() or c in (' ', '_', '-')).rstrip()
                if not safe_name: safe_name = "textasset"
                file_name = f"{safe_name}.txt"; file_path = os.path.join(preview_dir, file_name)
                counter = 1
                while os.path.exists(file_path): file_name = f"{safe_name}_{counter}.txt"; file_path = os.path.join(preview_dir, file_name); counter += 1
                with open(file_path, 'w', encoding='utf-8') as f: f.write(text)
        except Exception as e: self.log_index(f"保存TextAsset预览失败 {resource_name}: {e}")
    
    def search_resources(self):
        resource_name = self.resource_name.get().strip()
        if not resource_name: messagebox.showerror("错误", "请输入资源名"); return
        if not self.replace_folder.get(): messagebox.showerror("错误", "请先选择Unity资源文件夹"); return
        for item in self.results_tree.get_children(): self.results_tree.delete(item)
        self.search_results = []
        search_types = []
        if self.replace_texture.get(): search_types.append("Texture2D")
        if self.replace_textasset.get(): search_types.append("TextAsset"); search_types.append("SpriteAtlas")
        if not search_types: messagebox.showerror("错误", "请至少选择一种资源类型"); return
        all_results = []
        for resource_type in search_types:
            json_file = "indices/texture2d.json" if resource_type == "Texture2D" else "indices/textasset.json"
            if os.path.exists(json_file):
                data = self.load_json_file(json_file)
                for item in data:
                    item_name = item.get("resource_name", ""); item_type = item.get("resource_type", ""); search_mode = self.search_mode.get()
                    type_match = False
                    if resource_type == "Texture2D" and item_type == "Texture2D": type_match = True
                    elif resource_type in ["TextAsset", "SpriteAtlas"] and item_type in ["TextAsset", "SpriteAtlas"]: type_match = True
                    if not type_match: continue
                    match = False
                    if search_mode == "exact":
                        if item_name.lower() == resource_name.lower(): match = True
                    else:
                        if resource_name.lower() in item_name.lower(): match = True
                    if match:
                        original_file = item.get("original_file")
                        # 修改：不依赖索引中的路径，按文件名在用户文件夹中搜索
                        found_files = self.find_files_by_name(self.replace_folder.get(), original_file)
                        for file_path in found_files:
                            result = {
                                "file_path": file_path,
                                "resource_name": item_name,
                                "resource_type": item_type,
                                "original_file": original_file,
                                "found_by_name": True  # 标记是通过文件名搜索找到的
                            }
                            all_results.append(result)
        self.search_results = all_results; t2d_count = 0; textasset_count = 0
        for result in all_results:
            file_name = os.path.basename(result['file_path']); resource_type = result['resource_type']; resource_name = result['resource_name']
            if resource_type == "Texture2D": t2d_count += 1
            else: textasset_count += 1
            self.results_tree.insert("", tk.END, values=(resource_name, file_name, resource_type))
        status_text = f"找到 {len(all_results)} 个匹配的资源"
        if t2d_count > 0 or textasset_count > 0: status_text += f" (T2D: {t2d_count}, TextAsset: {textasset_count})"
        self.replace_status.config(text=status_text)
        self.sort_results(self.sort_column)
    
    def find_files_by_name(self, base_folder, filename_no_ext):
        """按文件名在文件夹中递归搜索，不依赖索引中的路径"""
        matching_files = []
        if not os.path.exists(base_folder): return matching_files
        for root, dirs, files in os.walk(base_folder):
            for file in files:
                name, ext = os.path.splitext(file)
                if name.lower() == filename_no_ext.lower():  # 不区分大小写匹配
                    full_path = os.path.join(root, file)
                    matching_files.append(full_path)
        return matching_files
    
    def on_tree_select(self, event):
        selected_items = self.results_tree.selection()
        if selected_items:
            item = selected_items[0]; values = self.results_tree.item(item, 'values')
            if values and len(values) >= 3:
                resource_name, file_name, resource_type = values
                for result in self.search_results:
                    if (os.path.basename(result['file_path']) == file_name and result['resource_type'] == resource_type and result['resource_name'] == resource_name):
                        self.selected_file = result; self.preview_resource(result); break
    
    def preview_resource(self, result):
        try:
            file_path = result["file_path"]; resource_type = result["resource_type"]; resource_name = result["resource_name"]
            self.original_info_text.delete(1.0, tk.END); self.original_resolution_label.config(text="尺寸: 未加载"); self.original_image_canvas.delete("all")
            self.original_text_preview.pack_forget(); self.original_image_canvas.pack(fill=tk.BOTH, expand=True); self.original_resolution_label.pack(pady=3)
            env = UnityPy.load(file_path); found_resource = False
            for obj in env.objects:
                if obj.type.name == resource_type:
                    data = obj.read(); current_name = getattr(data, "name", getattr(data, "m_Name", ""))
                    if str(current_name) == resource_name:
                        info = f"资源名称: {resource_name}\n文件路径: {file_path}\n资源类型: {resource_type}\n"
                        if resource_type == "Texture2D":
                            width = getattr(data, "m_Width", "未知"); height = getattr(data, "m_Height", "未知")
                            if hasattr(data, "image"):
                                image = data.image
                                if image:
                                    canvas_width = 250; canvas_height = 250; img_width, img_height = image.size
                                    scale = min(canvas_width/img_width, canvas_height/img_height)
                                    new_width = int(img_width * scale); new_height = int(img_height * scale)
                                    x_offset = (canvas_width - new_width) // 2; y_offset = (canvas_height - new_height) // 2
                                    image_resized = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
                                    self.original_photo = ImageTk.PhotoImage(image_resized)
                                    self.original_image_canvas.create_image(x_offset, y_offset, anchor=tk.NW, image=self.original_photo)
                                    info += f"原尺寸: {width}x{height}\n预览尺寸: {new_width}x{new_height}\n"
                                    self.original_resolution_label.config(text=f"尺寸: {width}x{height} (预览: {new_width}x{new_height})")
                        elif resource_type in ["TextAsset", "SpriteAtlas"]:
                            if hasattr(data, "m_Script"):
                                try: text = data.m_Script.decode('utf-8', errors='ignore')
                                except: text = str(data.m_Script)
                                self.original_image_canvas.pack_forget(); self.original_resolution_label.pack_forget()
                                self.original_text_preview.config(state=tk.NORMAL); self.original_text_preview.delete(1.0, tk.END)
                                self.original_text_preview.insert(tk.END, text); self.original_text_preview.config(state=tk.DISABLED)
                                self.original_text_preview.pack(fill=tk.BOTH, expand=True); info += f"文本长度: {len(text)} 字符\n"
                        self.original_info_text.insert(tk.END, info); found_resource = True; break
            if not found_resource: self.original_info_text.insert(tk.END, f"未找到匹配的资源: {resource_name}")
            self.original_text_preview.config(state=tk.DISABLED)
        except Exception as e: self.original_info_text.insert(tk.END, f"预览失败: {str(e)}\n\n{traceback.format_exc()}")
    
    def select_new_resource(self):
        filetypes = [("所有文件", "*.*"), ("图片文件", "*.png *.jpg *.jpeg *.bmp *.tga"), ("文本文件", "*.json *.txt *.xml")]
        file_path = filedialog.askopenfilename(title="选择新资源文件", filetypes=filetypes)
        if file_path: self.new_resource_file = file_path; self.preview_new_resource(file_path)
    
    def preview_new_resource(self, file_path):
        try:
            self.new_info_text.delete(1.0, tk.END); self.new_resolution_label.config(text="尺寸: 未加载"); self.new_image_canvas.delete("all")
            self.new_text_preview.pack_forget(); self.new_image_canvas.pack(fill=tk.BOTH, expand=True); self.new_resolution_label.pack(pady=3)
            ext = os.path.splitext(file_path)[1].lower()
            if ext in ['.png', '.jpg', '.jpeg', '.bmp', '.tga']:
                self.new_resource_type = "Texture2D"; image = Image.open(file_path)
                canvas_width = 250; canvas_height = 250; img_width, img_height = image.size
                scale = min(canvas_width/img_width, canvas_height/img_height)
                new_width = int(img_width * scale); new_height = int(img_height * scale)
                x_offset = (canvas_width - new_width) // 2; y_offset = (canvas_height - new_height) // 2
                image_resized = image.resize((new_width, new_height), Image.Resampling.LANCZOS)
                self.new_photo = ImageTk.PhotoImage(image_resized)
                self.new_image_canvas.create_image(x_offset, y_offset, anchor=tk.NW, image=self.new_photo)
                info = f"文件路径: {file_path}\n类型: 图片 ({ext})\n原尺寸: {img_width}x{img_height}\n预览尺寸: {new_width}x{new_height}\n模式: {image.mode}\n"
                self.new_info_text.insert(tk.END, info)
                self.new_resolution_label.config(text=f"尺寸: {img_width}x{img_height} (预览: {new_width}x{new_height})")
                self.new_resource_data = image
            elif ext in ['.json', '.txt', '.xml']:
                self.new_resource_type = "TextAsset"
                with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read()
                self.new_image_canvas.pack_forget(); self.new_resolution_label.pack_forget()
                self.new_text_preview.pack(fill=tk.BOTH, expand=True); self.new_text_preview.delete(1.0, tk.END)
                self.new_text_preview.insert(tk.END, content)
                info = f"文件路径: {file_path}\n类型: 文本 ({ext})\n大小: {len(content)} 字符\n"
                self.new_info_text.insert(tk.END, info)
                self.new_resource_data = content; self.edited_text_content = content
            else:
                self.new_resource_type = "Unknown"
                info = f"文件路径: {file_path}\n类型: 未知 ({ext})\n"; self.new_info_text.insert(tk.END, info)
                self.new_resource_data = None
            self.new_text_preview.config(state=tk.NORMAL)
        except Exception as e: self.new_info_text.insert(tk.END, f"预览失败: {str(e)}\n\n{traceback.format_exc()}")
    
    def save_text_edit(self):
        if self.new_resource_type == "TextAsset": self.edited_text_content = self.new_text_preview.get(1.0, tk.END); messagebox.showinfo("保存成功", "文本编辑已保存！")
        else: messagebox.showinfo("提示", "当前资源不是文本类型，无需保存编辑")
    
    def clear_preview(self):
        self.original_info_text.delete(1.0, tk.END); self.original_resolution_label.config(text="尺寸: 未加载"); self.original_image_canvas.delete("all")
        self.original_text_preview.pack_forget(); self.original_text_preview.config(state=tk.NORMAL); self.original_text_preview.delete(1.0, tk.END)
        self.original_image_canvas.pack(fill=tk.BOTH, expand=True); self.original_resolution_label.pack(pady=3); self.original_text_preview.config(state=tk.DISABLED)
        self.new_info_text.delete(1.0, tk.END); self.new_resolution_label.config(text="尺寸: 未加载"); self.new_image_canvas.delete("all")
        self.new_text_preview.pack_forget(); self.new_text_preview.config(state=tk.NORMAL); self.new_text_preview.delete(1.0, tk.END)
        self.new_image_canvas.pack(fill=tk.BOTH, expand=True); self.new_resolution_label.pack(pady=3)
        self.new_resource_file = None; self.new_resource_data = None; self.new_resource_type = None; self.edited_text_content = None
        self.results_tree.selection_remove(self.results_tree.selection()); self.selected_file = None
        self.replace_status.config(text="就绪")
    
    def generate_mod(self):
        if not self.selected_file: messagebox.showerror("错误", "请先选择要替换的资源"); return
        if not self.new_resource_file and not self.edited_text_content: messagebox.showerror("错误", "请先选择新资源文件或编辑文本"); return
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        resource_name = self.resource_name.get() or "resource"
        mod_name = f"{timestamp}_{resource_name}_mod"
        output_dir = os.path.join("output", mod_name)
        original_dir = os.path.join(output_dir, "original")
        modified_dir = os.path.join(output_dir, "modified")
        os.makedirs(original_dir, exist_ok=True); os.makedirs(modified_dir, exist_ok=True)
        try:
            self.replace_status.config(text=f"处理文件中..."); self.root.update()
            result = self.selected_file
            file_path = result["file_path"]; target_resource_name = result["resource_name"]; target_resource_type = result["resource_type"]
            # 使用实际找到的文件路径，不依赖索引中的相对路径
            rel_path = os.path.basename(file_path)  # 只使用文件名，不依赖路径结构
            original_file_path = os.path.join(original_dir, rel_path); modified_file_path = os.path.join(modified_dir, rel_path)
            os.makedirs(os.path.dirname(original_file_path), exist_ok=True); os.makedirs(os.path.dirname(modified_file_path), exist_ok=True)
            shutil.copy2(file_path, original_file_path)
            env = UnityPy.load(file_path); modified = False; found_resource = False
            for obj in env.objects:
                current_type = obj.type.name
                type_match = False
                if target_resource_type == "Texture2D" and current_type == "Texture2D": type_match = True
                elif target_resource_type in ["TextAsset", "SpriteAtlas"] and current_type in ["TextAsset", "SpriteAtlas"]: type_match = True
                if not type_match: continue
                try:
                    data = obj.read()
                    if not data: print(f"警告：无法读取对象 {current_type}"); continue
                    current_name = getattr(data, "name", getattr(data, "m_Name", ""))
                    if str(current_name) == target_resource_name:
                        print(f"找到匹配的资源: {current_name} (类型: {current_type})"); found_resource = True
                        if target_resource_type == "Texture2D" and self.new_resource_type == "Texture2D":
                            if self.new_resource_data:
                                print("开始替换Texture2D...")
                                data.image = self.new_resource_data; data.m_Width = self.new_resource_data.width; data.m_Height = self.new_resource_data.height
                                data.save(); modified = True; print("Texture2D替换完成")
                        elif target_resource_type in ["TextAsset", "SpriteAtlas"] and self.new_resource_type == "TextAsset":
                            if self.edited_text_content:
                                print("开始替换TextAsset...")
                                data.m_Script = self.edited_text_content.encode('utf-8'); data.save(); modified = True; print("TextAsset替换完成")
                        break
                except Exception as e: print(f"处理对象时出错: {e}"); continue
            if not found_resource:
                print(f"未找到匹配的资源: {target_resource_name} (类型: {target_resource_type})")
                print(f"文件中的对象类型: {[obj.type.name for obj in env.objects]}")
                self.replace_status.config(text="未找到匹配的资源")
                messagebox.showerror("错误", f"未找到匹配的资源: {target_resource_name} (类型: {target_resource_type})")
                return
            if modified:
                with open(modified_file_path, 'wb') as f: f.write(env.file.save())
                print("文件保存成功"); self.replace_status.config(text=f"Mod生成完成！保存到: {output_dir}")
                self.show_success_dialog("Mod生成", output_dir)
            else:
                print(f"未进行修改，直接复制文件")
                shutil.copy2(file_path, modified_file_path)
                self.replace_status.config(text=f"文件已复制但未修改，保存到: {output_dir}")
                messagebox.showinfo("提示", f"文件已复制但未修改\n保存到: {output_dir}")
        except Exception as e:
            self.replace_status.config(text="生成Mod时出错")
            error_msg = f"生成Mod时出错:\n{str(e)}\n\n{traceback.format_exc()}"
            print(error_msg); messagebox.showerror("错误", error_msg)
    
    def show_success_dialog(self, operation_type, folder_name):
        folder_path = os.path.abspath(folder_name)
        message = f"{operation_type}完成！\n保存到: {folder_path}"
        dialog = tk.Toplevel(self.root); dialog.title("成功"); dialog.geometry("400x150"); dialog.resizable(False, False)
        dialog.transient(self.root); dialog.grab_set()
        dialog.update_idletasks(); width = dialog.winfo_width(); height = dialog.winfo_height()
        x = (dialog.winfo_screenwidth() // 2) - (width // 2); y = (dialog.winfo_screenheight() // 2) - (height // 2)
        dialog.geometry(f'{width}x{height}+{x}+{y}')
        msg_label = ttk.Label(dialog, text=message, wraplength=350, justify=tk.CENTER); msg_label.pack(pady=20)
        button_frame = ttk.Frame(dialog); button_frame.pack(pady=10)
        ok_button = ttk.Button(button_frame, text="确定", width=10, command=dialog.destroy); ok_button.pack(side=tk.LEFT, padx=10)
        open_button = ttk.Button(button_frame, text="打开文件夹", width=12, command=lambda: [self.open_folder(folder_path), dialog.destroy()]); open_button.pack(side=tk.LEFT, padx=10)
    
    def run(self): self.root.mainloop()

def main(): app = UnityResourceManager(); app.run()
if __name__ == "__main__": main()