using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Web.Script.Serialization; namespace Unity_Studio { internal static class UnityStudio { public static List assetsfileList = new List(); //loaded files public static Dictionary sharedFileIndex = new Dictionary(); //to improve the loading speed public static Dictionary resourceFileReaders = new Dictionary(); //use for read res files public static List exportableAssets = new List(); //used to hold all assets while the ListView is filtered private static HashSet exportableAssetsHash = new HashSet(); //avoid the same name asset public static List visibleAssets = new List(); //used to build the ListView from all or filtered assets public static string productName = ""; public static string mainPath = ""; public static List fileNodes = new List(); public static Dictionary> jsonMats; public static Dictionary> AllClassStructures = new Dictionary>(); //UI public static Action SetProgressBarValue; public static Action SetProgressBarMaximum; public static Action ProgressBarPerformStep; public static Action StatusStripUpdate; public static Action ProgressBarMaximumAdd; public static int ExtractBundleFile(string bundleFileName) { int extractedCount = 0; if (CheckBundleFile(bundleFileName, out var reader)) { StatusStripUpdate($"Decompressing {Path.GetFileName(bundleFileName)} ..."); var extractPath = bundleFileName + "_unpacked\\"; Directory.CreateDirectory(extractPath); var bundleFile = new BundleFile(reader); foreach (var memFile in bundleFile.MemoryAssetsFileList) { var filePath = extractPath + memFile.fileName.Replace('/', '\\'); if (!Directory.Exists(Path.GetDirectoryName(filePath))) { Directory.CreateDirectory(Path.GetDirectoryName(filePath)); } if (!File.Exists(filePath)) { StatusStripUpdate($"Extracting {Path.GetFileName(memFile.fileName)}"); extractedCount += 1; using (var file = File.Create(filePath)) { memFile.memStream.WriteTo(file); memFile.memStream.Close(); } } } } reader.Dispose(); return extractedCount; } public static void BuildAssetStructures(bool loadAssetsMenuItem, bool displayAll, bool buildHierarchyMenuItem, bool buildClassStructuresMenuItem, bool displayOriginalName) { #region first loop - read asset data & create list if (loadAssetsMenuItem) { SetProgressBarValue(0); SetProgressBarMaximum(assetsfileList.Sum(x => x.preloadTable.Values.Count)); string fileIDfmt = "D" + assetsfileList.Count.ToString().Length; for (var i = 0; i < assetsfileList.Count; i++) { var assetsFile = assetsfileList[i]; StatusStripUpdate("Building asset list from " + Path.GetFileName(assetsFile.filePath)); string fileID = i.ToString(fileIDfmt); AssetBundle ab = null; foreach (var asset in assetsFile.preloadTable.Values) { asset.uniqueID = fileID + asset.uniqueID; var exportable = false; switch (asset.Type2) { case 1: //GameObject { GameObject m_GameObject = new GameObject(asset); assetsFile.GameObjectList.Add(asset.m_PathID, m_GameObject); //totalTreeNodes++; break; } case 4: //Transform { Transform m_Transform = new Transform(asset); assetsFile.TransformList.Add(asset.m_PathID, m_Transform); break; } case 224: //RectTransform { RectTransform m_Rect = new RectTransform(asset); assetsFile.TransformList.Add(asset.m_PathID, m_Rect.m_Transform); break; } case 28: //Texture2D { Texture2D m_Texture2D = new Texture2D(asset, false); exportable = true; break; } case 48: //Shader { Shader m_Shader = new Shader(asset, false); exportable = true; break; } case 49: //TextAsset { TextAsset m_TextAsset = new TextAsset(asset, false); exportable = true; break; } case 83: //AudioClip { AudioClip m_AudioClip = new AudioClip(asset, false); exportable = true; break; } case 114: //MonoBehaviour { var m_MonoBehaviour = new MonoBehaviour(asset, false); if (asset.Type1 != asset.Type2 && assetsFile.ClassStructures.ContainsKey(asset.Type1)) exportable = true; break; } case 128: //Font { UnityFont m_Font = new UnityFont(asset, false); exportable = true; break; } case 129: //PlayerSettings { var plSet = new PlayerSettings(asset); productName = plSet.productName; break; } case 43: //Mesh { Mesh m_Mesh = new Mesh(asset, false); exportable = true; break; } case 142: //AssetBundle { ab = new AssetBundle(asset); break; } case 329: //VideoClip { var m_VideoClip = new VideoClip(asset, false); exportable = true; break; } case 152: //MovieTexture { var m_MovieTexture = new MovieTexture(asset, false); exportable = true; break; } case 213: //Sprite { var m_Sprite = new Sprite(asset, false); exportable = true; break; } /*case 21: //Material case 74: //AnimationClip case 90: //Avatar case 91: //AnimatorController case 115: //MonoScript case 687078895: //SpriteAtlas { if (asset.Offset + 4 > asset.sourceFile.a_Stream.BaseStream.Length) break; asset.sourceFile.a_Stream.Position = asset.Offset; var len = asset.sourceFile.a_Stream.ReadInt32(); if (len > 0 && len < asset.Size - 4) { var bytes = asset.sourceFile.a_Stream.ReadBytes(len); asset.Text = Encoding.UTF8.GetString(bytes); } break; }*/ } if (!exportable && displayAll) { asset.extension = ".dat"; exportable = true; } if (exportable) { if (asset.Text == "") { asset.Text = asset.TypeString + " #" + asset.uniqueID; } asset.SubItems.AddRange(new[] { asset.TypeString, asset.fullSize.ToString() }); //处理同名文件 if (!exportableAssetsHash.Add((asset.TypeString + asset.Text).ToUpper())) { asset.Text += " #" + asset.uniqueID; } //处理非法文件名 asset.Text = FixFileName(asset.Text); assetsFile.exportableAssets.Add(asset); } ProgressBarPerformStep(); } if (displayOriginalName) { assetsFile.exportableAssets.ForEach(x => { var replacename = ab?.m_Container.Find(y => y.second.asset.m_PathID == x.m_PathID)?.first; if (!string.IsNullOrEmpty(replacename)) { var ex = Path.GetExtension(replacename); x.Text = !string.IsNullOrEmpty(ex) ? replacename.Replace(ex, "") : replacename; } }); } exportableAssets.AddRange(assetsFile.exportableAssets); } visibleAssets = exportableAssets; exportableAssetsHash.Clear(); } #endregion #region second loop - build tree structure fileNodes = new List(); if (buildHierarchyMenuItem) { SetProgressBarMaximum(1); SetProgressBarValue(1); SetProgressBarMaximum(assetsfileList.Sum(x => x.GameObjectList.Values.Count) + 1); foreach (var assetsFile in assetsfileList) { StatusStripUpdate("Building tree structure from " + Path.GetFileName(assetsFile.filePath)); GameObject fileNode = new GameObject(null); fileNode.Text = Path.GetFileName(assetsFile.filePath); fileNode.m_Name = "RootNode"; foreach (var m_GameObject in assetsFile.GameObjectList.Values) { //ParseGameObject foreach (var m_Component in m_GameObject.m_Components) { if (m_Component.m_FileID >= 0 && m_Component.m_FileID < assetsfileList.Count) { var sourceFile = assetsfileList[m_Component.m_FileID]; if (sourceFile.preloadTable.TryGetValue(m_Component.m_PathID, out var asset)) { switch (asset.Type2) { case 4: //Transform { m_GameObject.m_Transform = m_Component; break; } case 23: //MeshRenderer { m_GameObject.m_MeshRenderer = m_Component; break; } case 33: //MeshFilter { m_GameObject.m_MeshFilter = m_Component; break; } case 137: //SkinnedMeshRenderer { m_GameObject.m_SkinnedMeshRenderer = m_Component; break; } } } } } // var parentNode = fileNode; if (assetsfileList.TryGetTransform(m_GameObject.m_Transform, out var m_Transform)) { if (assetsfileList.TryGetTransform(m_Transform.m_Father, out var m_Father)) { //GameObject Parent; if (assetsfileList.TryGetGameObject(m_Father.m_GameObject, out parentNode)) { //parentNode = Parent; } } } parentNode.Nodes.Add(m_GameObject); ProgressBarPerformStep(); } if (fileNode.Nodes.Count == 0) { fileNode.Text += " (no children)"; } fileNodes.Add(fileNode); } if (File.Exists(mainPath + "\\materials.json")) { string matLine; using (StreamReader reader = File.OpenText(mainPath + "\\materials.json")) { matLine = reader.ReadToEnd(); } jsonMats = new JavaScriptSerializer().Deserialize>>(matLine); //var jsonMats = new JavaScriptSerializer().DeserializeObject(matLine); } } #endregion #region build list of class strucutres if (buildClassStructuresMenuItem) { //group class structures by versionv foreach (var assetsFile in assetsfileList) { if (AllClassStructures.TryGetValue(assetsFile.m_Version, out var curVer)) { foreach (var uClass in assetsFile.ClassStructures) { curVer[uClass.Key] = uClass.Value; } } else { AllClassStructures.Add(assetsFile.m_Version, assetsFile.ClassStructures); } } } #endregion } public static string FixFileName(string str) { if (str.Length >= 260) return Path.GetRandomFileName(); return Path.GetInvalidFileNameChars().Aggregate(str, (current, c) => current.Replace(c, '_')); } public static bool CheckBundleFile(string fileName, out EndianBinaryReader reader) { reader = new EndianBinaryReader(File.OpenRead(fileName)); var signature = reader.ReadStringToNull(); reader.Position = 0; switch (signature) { case "UnityWeb": case "UnityRaw": case "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA": case "UnityFS": return true; default: return false; } } public static string[] ProcessingSplitFiles(List selectFile) { var splitFiles = selectFile.Where(x => x.Contains(".split")) .Select(x => Path.GetDirectoryName(x) + "\\" + Path.GetFileNameWithoutExtension(x)) .Distinct() .ToList(); selectFile.RemoveAll(x => x.Contains(".split")); foreach (var file in splitFiles) { if (File.Exists(file)) { selectFile.Add(file); } } return selectFile.Distinct().ToArray(); } } }