using UnityEngine; using UnityEditor; using System.IO; using System.Text; using System.Collections.Generic; using System.Linq; /// /// SpriteMetaRestorer Editor 窗口 /// /// 功能:将独立 .asset Sprite 文件的分割信息还原到 PNG.meta 的 spriteSheet.sprites 中。 /// 使用方法: /// 1. 把 PNG 图片拖到"目标 PNG"槽位 /// 2. 把对应的 .asset Sprite 文件拖到列表区域(支持多选拖入) /// 3. 点击"写入 Meta"完成还原 /// 4. 可选:勾选"写入后删除已融合的 .asset 文件" /// /// 作者:AI Assistant /// public class SpriteMetaRestorer : EditorWindow { #region 字段 /// 目标 PNG 纹理 private Texture2D targetTexture; /// 待处理的 Sprite .asset 文件列表 private List assetList = new List(); /// 拖入区域的滚动位置 private Vector2 scrollPos; /// 处理结果日志 private string logText = ""; private Vector2 logScrollPos; /// 是否显示日志区域 private bool showLog = false; /// 写入完成后是否删除已融合的 .asset 文件及其 .meta private bool deleteAssetsAfterWrite = true; /// 是否允许写入与 PNG 不匹配的 Sprite(textureGuid 不同) private bool allowMismatchedSprites = false; #endregion #region 数据结构 /// 单个 Sprite .asset 的解析信息 private class SpriteAssetInfo { /// .asset 文件路径(Assets/...) public string assetPath; /// Sprite 名称(m_Name) public string name; /// 裁切矩形 public Rect rect; /// 轴心点 public Vector2 pivot; /// 边框 public Vector4 border; /// spriteID(来自 .asset.meta 的 guid) public string spriteID; /// 该 .asset 引用的纹理 guid,用于校验是否与目标 PNG 匹配 public string textureGuid; /// 解析是否成功 public bool valid; public SpriteAssetInfo() { valid = false; spriteID = ""; textureGuid = ""; rect = new Rect(0, 0, 0, 0); pivot = new Vector2(0.5f, 0.5f); border = new Vector4(0, 0, 0, 0); } } #endregion #region 菜单入口 [MenuItem("Tools/Sprite Meta Restorer")] private static void ShowWindow() { // 获取已有窗口或创建新窗口 var window = GetWindow("Sprite Meta Restorer"); window.minSize = new Vector2(420, 500); window.Show(); } #endregion #region GUI 绘制 private void OnGUI() { DrawHeader(); DrawTargetPNGSlot(); DrawAssetDropZone(); DrawActionBar(); DrawLogArea(); } /// /// 顶部标题说明 /// private void DrawHeader() { EditorGUILayout.Space(8); EditorGUILayout.HelpBox( "将 Sprite .asset 文件的分割信息还原到 PNG.meta 的 spriteSheet.sprites 中。\n" + "1. 拖入目标 PNG 图片\n" + "2. 拖入对应的 .asset 文件(支持多选)\n" + "3. 点击「写入 Meta」", MessageType.Info ); EditorGUILayout.Space(4); } /// /// 目标 PNG 拖拽槽位 /// private void DrawTargetPNGSlot() { EditorGUILayout.LabelField("目标 PNG", EditorStyles.boldLabel); // 使用 ObjectField 支持拖拽和点击选择 Rect dropRect = EditorGUILayout.GetControlRect(GUILayout.Height(60)); GUI.Box(dropRect, "", GUI.skin.box); var centeredStyle = new GUIStyle(GUI.skin.label) { alignment = TextAnchor.MiddleCenter, fontSize = 12 }; if (targetTexture != null) { // 显示已选择的 PNG 信息 string pngPath = AssetDatabase.GetAssetPath(targetTexture); int texW = targetTexture.width; int texH = targetTexture.height; GUI.Label(dropRect, $"{Path.GetFileName(pngPath)}\n{texW} x {texH} px", centeredStyle); } else { GUI.Label(dropRect, "拖入 PNG 图片 或 点击选择", centeredStyle); } // 处理拖拽事件 if (Event.current.type == EventType.DragUpdated && dropRect.Contains(Event.current.mousePosition)) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; Event.current.Use(); } else if (Event.current.type == EventType.DragPerform && dropRect.Contains(Event.current.mousePosition)) { DragAndDrop.AcceptDrag(); if (DragAndDrop.objectReferences.Length > 0) { var obj = DragAndDrop.objectReferences[0] as Texture2D; if (obj != null) { string path = AssetDatabase.GetAssetPath(obj); // 确保是 PNG 文件而非内部纹理 if (path.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase)) { targetTexture = obj; assetList.Clear(); logText = ""; } else { Debug.LogWarning("[SpriteMetaRestorer] 请拖入 PNG 文件,不是内部纹理"); } } } Event.current.Use(); } // 点击选择 targetTexture = (Texture2D)EditorGUI.ObjectField( new Rect(dropRect.x, dropRect.y, dropRect.width, dropRect.height), targetTexture, typeof(Texture2D), false ); // 检查选择后是否为 PNG if (targetTexture != null) { string p = AssetDatabase.GetAssetPath(targetTexture); if (!p.EndsWith(".png", System.StringComparison.OrdinalIgnoreCase)) { targetTexture = null; } } EditorGUILayout.Space(8); } /// /// .asset 文件拖拽列表区域 /// private void DrawAssetDropZone() { EditorGUILayout.LabelField($"Sprite Assets ({assetList.Count} 个)", EditorStyles.boldLabel); // 列表区域 Rect listRect = EditorGUILayout.GetControlRect(false, GUILayout.Height(200)); GUI.Box(listRect, ""); // 空状态提示 if (assetList.Count == 0 && targetTexture != null) { // 创建带灰色的 GUIStyle var hintStyle = new GUIStyle(GUI.skin.label); hintStyle.alignment = TextAnchor.MiddleCenter; hintStyle.fontSize = 12; hintStyle.normal.textColor = Color.gray; GUI.Label(listRect, "拖入 .asset Sprite 文件到这里\n(支持 Project 窗口多选拖入)", hintStyle); } // 处理拖拽 if (Event.current.type == EventType.DragUpdated && listRect.Contains(Event.current.mousePosition)) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; Event.current.Use(); } else if (Event.current.type == EventType.DragPerform && listRect.Contains(Event.current.mousePosition)) { DragAndDrop.AcceptDrag(); AddDraggedAssets(DragAndDrop.objectReferences); Event.current.Use(); } // 绘制列表(带滚动) if (assetList.Count > 0) { var innerRect = new Rect(listRect.x + 4, listRect.y + 4, listRect.width - 8, listRect.height - 8); scrollPos = GUI.BeginScrollView(innerRect, scrollPos, new Rect(0, 0, innerRect.width - 16, assetList.Count * 22)); for (int i = 0; i < assetList.Count; i++) { var info = assetList[i]; Rect rowRect = new Rect(0, i * 22, innerRect.width, 20); // 背景色:有效=正常,无效=红底 Color origBg = GUI.backgroundColor; GUI.backgroundColor = info.valid ? Color.green : Color.red; // 名称 float nameW = rowRect.width * 0.35f; GUI.Label(new Rect(rowRect.x, rowRect.y, nameW, 20), info.name); // 坐标 float coordW = rowRect.width * 0.35f; string coordText = $"({info.rect.x:F0},{info.rect.y:F0},{info.rect.width:F0}x{info.rect.height:F0})"; GUI.Label(new Rect(rowRect.x + nameW, rowRect.y, coordW, 20), coordText); // 匹配状态 float statusW = rowRect.width * 0.15f; if (targetTexture != null) { string pngGuid = GetAssetGuid(AssetDatabase.GetAssetPath(targetTexture) + ".meta"); bool matched = info.textureGuid == pngGuid; // 正确方式:通过 GUIStyle.normal.textColor 设置颜色 var statusStyle = new GUIStyle(GUI.skin.label); statusStyle.fontSize = 10; statusStyle.normal.textColor = matched ? Color.green : Color.red; GUI.Label(new Rect(rowRect.x + nameW + coordW, rowRect.y, statusW, 20), matched ? "✓ 匹配" : "✗ 不匹配", statusStyle); } // 删除按钮 float btnW = rowRect.width * 0.12f; if (GUI.Button(new Rect(rowRect.x + rowRect.width - btnW, rowRect.y, btnW, 20), "删除")) { assetList.RemoveAt(i); i--; } GUI.backgroundColor = origBg; } GUI.EndScrollView(); } EditorGUILayout.Space(6); } /// /// 操作按钮区 /// private void DrawActionBar() { EditorGUILayout.BeginHorizontal(); // 写入按钮 GUI.enabled = targetTexture != null && assetList.Count > 0; if (GUILayout.Button("写入 Meta", GUILayout.Height(32))) { WriteSpritesToMeta(); } GUI.enabled = true; // 清空按钮 if (GUILayout.Button("清空列表", GUILayout.Height(32))) { assetList.Clear(); logText = ""; } // 清空全部 if (GUILayout.Button("全部重置", GUILayout.Height(32))) { targetTexture = null; assetList.Clear(); logText = ""; } // 新增:删除所有与 PNG 不匹配的 Sprite if (targetTexture != null && assetList.Count > 0) { if (GUILayout.Button("删除不匹配的 Sprite", GUILayout.Height(32))) { RemoveMismatchedSprites(); } } EditorGUILayout.EndHorizontal(); // 自动检测按钮 EditorGUILayout.Space(4); if (GUILayout.Button("自动检测同目录 .asset 文件", GUILayout.Height(28))) { AutoDetectAssets(); } // 删除 asset 选项 EditorGUILayout.Space(6); deleteAssetsAfterWrite = EditorGUILayout.Toggle( new GUIContent("写入后删除已融合的 .asset 文件", "勾选后,写入 Meta 成功会自动删除列表中的 .asset 及其 .meta 文件"), deleteAssetsAfterWrite ); // 新增:是否允许写入与 PNG 不匹配的 Sprite allowMismatchedSprites = EditorGUILayout.Toggle( new GUIContent("允许写入与 PNG 不匹配的 Sprite", "勾选:即使 Sprite 引用的纹理 guid 与当前 PNG 不同,也照样按其 rect 写入。\n不勾选:写入时会跳过这些不匹配的 Sprite。"), allowMismatchedSprites ); EditorGUILayout.Space(4); } /// /// 日志区域 /// private void DrawLogArea() { if (string.IsNullOrEmpty(logText)) return; showLog = EditorGUILayout.Foldout(showLog, "处理日志"); if (showLog) { EditorGUILayout.BeginVertical(GUI.skin.box); logScrollPos = EditorGUILayout.BeginScrollView(logScrollPos, GUILayout.Height(150)); EditorGUILayout.TextArea(logText, EditorStyles.wordWrappedLabel, GUILayout.ExpandHeight(true)); EditorGUILayout.EndScrollView(); EditorGUILayout.EndVertical(); } } #endregion #region 拖拽处理 /// /// 处理从 Project 窗口拖入的对象 /// private void AddDraggedAssets(Object[] objects) { int added = 0; int skipped = 0; foreach (var obj in objects) { string path = AssetDatabase.GetAssetPath(obj); // 只处理 .asset 文件 if (string.IsNullOrEmpty(path) || !path.EndsWith(".asset", System.StringComparison.OrdinalIgnoreCase)) { skipped++; continue; } // 检查是否已存在 if (assetList.Any(a => a.assetPath == path)) { skipped++; continue; } // 解析 .asset 文件 var info = ParseSpriteAsset(path); if (info != null && info.valid) { assetList.Add(info); added++; } else { Debug.LogWarning($"[SpriteMetaRestorer] 无法解析: {path}"); skipped++; } } if (added > 0) Debug.Log($"[SpriteMetaRestorer] 添加 {added} 个 .asset,跳过 {skipped} 个"); } #endregion #region 自动检测 /// /// 自动检测目标 PNG 同目录下的所有 Sprite 类型 .asset 文件 /// private void AutoDetectAssets() { if (targetTexture == null) { EditorUtility.DisplayDialog("提示", "请先选择目标 PNG", "确定"); return; } string pngPath = AssetDatabase.GetAssetPath(targetTexture); string pngDir = Path.GetDirectoryName(pngPath).Replace("\\", "/"); string pngGuid = GetAssetGuid(pngPath + ".meta"); if (string.IsNullOrEmpty(pngGuid)) { EditorUtility.DisplayDialog("错误", "无法获取 PNG 的 guid", "确定"); return; } // 查找同目录下所有 .asset 文件 string[] assetFiles = Directory.GetFiles(pngDir, "*.asset"); int added = 0; foreach (string file in assetFiles) { string assetPath = file.Replace("\\", "/"); // 跳过已存在的 if (assetList.Any(a => a.assetPath == assetPath)) continue; // 解析并检查是否为 Sprite 类型且匹配当前 PNG var info = ParseSpriteAsset(assetPath); if (info != null && info.valid) { // 检查 textureGuid 是否匹配 if (info.textureGuid == pngGuid) { assetList.Add(info); added++; } } } Debug.Log($"[SpriteMetaRestorer] 自动检测到 {added} 个匹配的 .asset 文件"); Repaint(); } #endregion #region .asset 解析 /// /// 解析单个 Sprite .asset 文件,提取名称、rect、pivot、border、textureGuid /// private SpriteAssetInfo ParseSpriteAsset(string assetPath) { string fullPath = Path.GetFullPath(assetPath); if (!File.Exists(fullPath)) return null; string text = File.ReadAllText(fullPath, Encoding.UTF8); // 检查是否是 Sprite 类型(!u!213),而非 MonoBehaviour(!u!114) // Split by --- and check second document string[] docs = text.Split(new[] { "---" }, System.StringSplitOptions.None); if (docs.Length < 2 || !docs[1].Contains("!u!213")) return null; var info = new SpriteAssetInfo(); info.assetPath = assetPath; // 提取 m_Name info.name = ExtractField(text, "m_Name"); if (string.IsNullOrEmpty(info.name)) info.name = Path.GetFileNameWithoutExtension(assetPath); // 提取 m_Rect info.rect = ExtractRect(text); // 提取 m_Pivot info.pivot = ExtractVec2(text, "m_Pivot"); // 提取 m_Border info.border = ExtractVec4(text, "m_Border"); // 提取 texture guid info.textureGuid = ExtractTextureGuid(text); // 提取 .asset.meta 的 guid 作为 spriteID string metaPath = assetPath + ".meta"; if (File.Exists(metaPath)) { string metaText = File.ReadAllText(metaPath, Encoding.UTF8); info.spriteID = GetGuidFromText(metaText); } // 基本校验 info.valid = info.rect.width > 0 && info.rect.height > 0; return info; } /// 提取 m_Name 字段值 private string ExtractField(string text, string fieldName) { int idx = text.IndexOf(fieldName + ":"); if (idx < 0) return ""; int start = idx + fieldName.Length; // 跳过冒号后的空格 while (start < text.Length && (text[start] == ':' || text[start] == ' ' || text[start] == '\t')) start++; int end = start; while (end < text.Length && text[end] != '\n' && text[end] != '\r') end++; return text.Substring(start, end - start).Trim(); } /// 提取 m_Rect 块 private Rect ExtractRect(string text) { string marker = "m_Rect:"; int idx = text.IndexOf(marker); if (idx < 0) return new Rect(0, 0, 0, 0); // 找到 marker 之后的 x/y/width/height string block = text.Substring(idx); float x = ExtractFloatAfter(block, "x:"); float y = ExtractFloatAfter(block, "y:"); float w = ExtractFloatAfter(block, "width:"); float h = ExtractFloatAfter(block, "height:"); return new Rect(x, y, w, h); } /// 提取 m_Pivot 二维向量 private Vector2 ExtractVec2(string text, string fieldName) { string marker = fieldName + ":"; int idx = text.IndexOf(marker); if (idx < 0) return new Vector2(0.5f, 0.5f); string block = text.Substring(idx); float x = ExtractFloatAfter(block, "x:"); float y = ExtractFloatAfter(block, "y:"); return new Vector2(x, y); } /// 提取 m_Border 四维向量 private Vector4 ExtractVec4(string text, string fieldName) { string marker = fieldName + ":"; int idx = text.IndexOf(marker); if (idx < 0) return new Vector4(0, 0, 0, 0); string block = text.Substring(idx); float x = ExtractFloatAfter(block, "x:"); float y = ExtractFloatAfter(block, "y:"); float z = ExtractFloatAfter(block, "z:"); float w = ExtractFloatAfter(block, "w:"); return new Vector4(x, y, z, w); } /// 提取 m_RD.texture 中的 guid private string ExtractTextureGuid(string text) { int idx = text.IndexOf("texture:"); if (idx < 0) return ""; string block = text.Substring(idx); int guidIdx = block.IndexOf("guid:"); if (guidIdx < 0) return ""; int start = guidIdx + 5; int end = start; while (end < block.Length && block[end] != ',' && block[end] != '}' && block[end] != '\n') end++; return block.Substring(start, end - start).Trim(); } /// 在文本中提取指定字段后的浮点数 private float ExtractFloatAfter(string text, string fieldName) { int idx = text.IndexOf(fieldName); if (idx < 0) return 0f; int start = idx + fieldName.Length; // 跳过到数字 while (start < text.Length && !char.IsDigit(text[start]) && text[start] != '-' && text[start] != '.') start++; int end = start; while (end < text.Length && (char.IsDigit(text[end]) || text[end] == '.' || text[end] == '-' || text[end] == 'e' || text[end] == 'E' || text[end] == '+')) end++; float result; if (float.TryParse(text.Substring(start, end - start), System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out result)) return result; return 0f; } #endregion #region Meta 文件操作 /// /// 删除所有与当前 PNG 不匹配的 Sprite(textureGuid 不同) /// private void RemoveMismatchedSprites() { if (targetTexture == null || assetList.Count == 0) return; string pngGuid = GetAssetGuid(AssetDatabase.GetAssetPath(targetTexture) + ".meta"); if (string.IsNullOrEmpty(pngGuid)) return; int before = assetList.Count; assetList.RemoveAll(a => a.textureGuid != pngGuid); int removed = before - assetList.Count; if (removed > 0) { Debug.Log($"[SpriteMetaRestorer] 已删除 {removed} 个与目标 PNG 不匹配的 Sprite"); Repaint(); } } /// /// 将解析到的 sprite 列表写入 .png.meta 的 spriteSheet.sprites 块 /// private void WriteSpritesToMeta() { if (targetTexture == null || assetList.Count == 0) return; string pngPath = AssetDatabase.GetAssetPath(targetTexture); string metaPath = pngPath + ".meta"; if (!File.Exists(metaPath)) { EditorUtility.DisplayDialog("错误", $"找不到 meta 文件:\n{metaPath}", "确定"); return; } string metaText = File.ReadAllText(metaPath, Encoding.UTF8); StringBuilder sb = new StringBuilder(); string pngGuid = GetAssetGuid(metaPath); // 构建 sprites YAML 文本 StringBuilder spritesYaml = new StringBuilder(); List nameFileIdEntries = new List(); foreach (var info in assetList) { if (!info.valid) continue; // ★★★ 新增:不匹配 PNG 时的处理逻辑 ★★★ bool matched = info.textureGuid == pngGuid; if (!matched && !allowMismatchedSprites) { sb.AppendLine($" [跳过] {info.name}(textureGuid 不匹配当前 PNG)"); continue; } // ★★★ 原有写入逻辑保持不变 ★★★ spritesYaml.AppendLine(" - serializedVersion: 2"); spritesYaml.AppendLine($" name: {info.name}"); spritesYaml.AppendLine(" rect:"); spritesYaml.AppendLine(" serializedVersion: 2"); spritesYaml.AppendLine($" x: {FormatFloat(info.rect.x)}"); spritesYaml.AppendLine($" y: {FormatFloat(info.rect.y)}"); spritesYaml.AppendLine($" width: {FormatFloat(info.rect.width)}"); spritesYaml.AppendLine($" height: {FormatFloat(info.rect.height)}"); spritesYaml.AppendLine(" alignment: 0"); spritesYaml.AppendLine($" pivot: {{x: {FormatFloat(info.pivot.x)}, y: {FormatFloat(info.pivot.y)}}}"); spritesYaml.AppendLine($" border: {{x: {FormatFloat(info.border.x)}, y: {FormatFloat(info.border.y)}, z: {FormatFloat(info.border.z)}, w: {FormatFloat(info.border.w)}}}"); spritesYaml.AppendLine(" outline: []"); spritesYaml.AppendLine(" physicsShape: []"); spritesYaml.AppendLine(" tessellationDetail: -1"); spritesYaml.AppendLine(" bones: []"); spritesYaml.AppendLine($" spriteID: {info.spriteID}"); spritesYaml.AppendLine(" internalID: 0"); spritesYaml.AppendLine(" vertices: []"); spritesYaml.AppendLine(" indices: "); spritesYaml.AppendLine(" edges: []"); spritesYaml.AppendLine(" weights: []"); nameFileIdEntries.Add($" {info.name}: 0"); sb.AppendLine($" [+] {info.name} rect=({info.rect.x:F0},{info.rect.y:F0},{info.rect.width:F0}x{info.rect.height:F0})"); } string spritesBlock = spritesYaml.ToString().TrimEnd('\n', '\r'); string spritesContent = " sprites:\n" + spritesBlock + "\n"; // 替换 meta 中的 sprites 块(保持你原来的逻辑) string result; if (metaText.Contains("sprites: []")) { result = ReplaceFirst(metaText, " sprites: []", spritesContent); } else { int idx = metaText.IndexOf(" sprites:"); if (idx >= 0) { int endIdx = FindBlockEnd(metaText, idx); result = metaText.Substring(0, idx) + spritesContent + metaText.Substring(endIdx); } else { result = metaText; } } // 更新 nameFileIdTable(保持你原来的逻辑) if (nameFileIdEntries.Count > 0) { string tableBlock = " nameFileIdTable:\n" + string.Join("\n", nameFileIdEntries) + "\n"; int tblIdx = result.IndexOf(" nameFileIdTable:"); if (tblIdx >= 0) { int tblEndIdx = FindBlockEnd(result, tblIdx); result = result.Substring(0, tblIdx) + tableBlock + result.Substring(tblEndIdx); } } File.WriteAllText(metaPath, result, Encoding.UTF8); AssetDatabase.ImportAsset(pngPath); int validCount = assetList.Count(a => a.valid); sb.AppendLine($"\n[完成] 已写入 {validCount} 个 Sprite 到:"); sb.AppendLine($" {metaPath}"); sb.AppendLine("\n→ Unity 已自动 Reimport,请在 Inspector 中确认。"); logText = sb.ToString(); showLog = true; } /// /// 替换字符串中第一次出现的 oldText /// private string ReplaceFirst(string text, string oldText, string newText) { int idx = text.IndexOf(oldText); if (idx < 0) return text; return text.Substring(0, idx) + newText + text.Substring(idx + oldText.Length); } /// /// 从 blockStartIndex 开始,找到当前 YAML 块的结束位置。 /// 块以 " keyName:" 开头,后续行要么以 " - " 继续列表项, /// 要么以更多缩进(6+ 空格或 " ")作为子属性。 /// 遇到同级缩进(4空格+非"-"非空格)的行时结束。 /// private int FindBlockEnd(string text, int blockStartIndex) { int i = blockStartIndex; // 跳过 " sprites:" 这一行 int lineEnd = text.IndexOf('\n', i); if (lineEnd < 0) return text.Length; i = lineEnd + 1; while (i < text.Length) { // 找到当前行开头 int nextNewline = text.IndexOf('\n', i); if (nextNewline < 0) nextNewline = text.Length; string line = text.Substring(i, nextNewline - i); // 空行不结束块(跳过) if (line.Trim().Length == 0) { i = nextNewline + 1; continue; } // 以 " - " 开头 → 仍是列表项,继续 if (line.StartsWith(" - ")) { i = nextNewline + 1; continue; } // 以 " "(6空格)开头 → 子属性,继续 if (line.StartsWith(" ")) { i = nextNewline + 1; continue; } // 以 " "(4空格)开头但不是 "- " → 下一个同级字段,块结束 if (line.StartsWith(" ") && !line.StartsWith(" - ") && !line.StartsWith(" ")) { return i; } // 其他情况(缩进更少),块结束 return i; } return text.Length; } /// /// 格式化浮点数:整数值不带小数点 /// private string FormatFloat(float v) { if (v == Mathf.RoundToInt(v)) return Mathf.RoundToInt(v).ToString(); return v.ToString("G7", System.Globalization.CultureInfo.InvariantCulture); } #endregion #region 工具方法 /// /// 从 meta 文本提取 guid /// private string GetAssetGuid(string metaPath) { if (!File.Exists(metaPath)) return ""; string text = File.ReadAllText(metaPath, Encoding.UTF8); return GetGuidFromText(text); } private string GetGuidFromText(string text) { int idx = text.IndexOf("guid:"); if (idx < 0) return ""; int start = idx + 5; int end = start; while (end < text.Length && text[end] != '\n' && text[end] != '\r') end++; return text.Substring(start, end - start).Trim(); } #endregion }