├── .gitignore ├── AssetDataUtil.cs ├── AssetStudio ├── BigArrayPool.cs ├── Math │ ├── Color.cs │ ├── Half.cs │ ├── HalfHelper.cs │ ├── Matrix4x4.cs │ ├── Quaternion.cs │ ├── Vector2.cs │ ├── Vector3.cs │ └── Vector4.cs └── Texture2DDecoder │ ├── T2DDll.cs │ ├── TextureDecoder.PInvoke.cs │ └── TextureDecoder.cs ├── AssetStudioExporter.csproj ├── AssetStudioExporter.sln ├── AssetTypeManager.cs ├── AssetTypes ├── AssetBundle.cs ├── AudioClip.cs ├── Component.cs ├── Feature │ ├── IAssetTypeExporter.cs │ ├── IAssetTypeReader.cs │ └── INamedObject.cs ├── Font.cs ├── GameObject.cs ├── IAssetType.cs ├── MonoBehaviour.cs ├── MonoBehaviourCollection.cs ├── MonoScript.cs ├── Object.cs ├── PPtr.cs ├── ResourceManager.cs ├── Sprite.cs ├── SpriteAtlas.cs ├── TextAsset.cs ├── Texture2D.cs └── ValueObject │ ├── AABB.cs │ ├── MeshHelper.cs │ ├── MeshTypes.cs │ ├── Rectf.cs │ └── SpriteTypes.cs ├── Export ├── AudioClipConverter.cs ├── SpriteHelper.cs └── Texture2DConverter.cs ├── ExporterSetting.cs ├── Formats.cs ├── Internal └── VersionCache.cs ├── Native ├── DllLoader.cs ├── DllLoaderNative.cs └── FMOD │ ├── fmod.cs │ ├── fmod_dsp.cs │ └── fmod_errors.cs ├── README.md ├── TextureDecoderType.cs ├── Util ├── AssetTypeValueFieldExtensions.cs └── UnityVersionExtensions.cs └── lib ├── x64 ├── Texture2DDecoderNative.dll ├── fmod.dll ├── libfmod.dylib └── libfmod.so └── x86 ├── Texture2DDecoderNative.dll ├── fmod.dll └── libfmod.so /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.pch 68 | *.pdb 69 | *.pgc 70 | *.pgd 71 | *.rsp 72 | *.sbr 73 | *.tlb 74 | *.tli 75 | *.tlh 76 | *.tmp 77 | *.tmp_proj 78 | *.log 79 | *.vspscc 80 | *.vssscc 81 | .builds 82 | *.pidb 83 | *.svclog 84 | *.scc 85 | 86 | # Chutzpah Test files 87 | _Chutzpah* 88 | 89 | # Visual C++ cache files 90 | ipch/ 91 | *.aps 92 | *.ncb 93 | *.opendb 94 | *.opensdf 95 | *.sdf 96 | *.cachefile 97 | *.VC.db 98 | *.VC.VC.opendb 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | *.sap 105 | 106 | # Visual Studio Trace Files 107 | *.e2e 108 | 109 | # TFS 2012 Local Workspace 110 | $tf/ 111 | 112 | # Guidance Automation Toolkit 113 | *.gpState 114 | 115 | # ReSharper is a .NET coding add-in 116 | _ReSharper*/ 117 | *.[Rr]e[Ss]harper 118 | *.DotSettings.user 119 | 120 | # JustCode is a .NET coding add-in 121 | .JustCode 122 | 123 | # TeamCity is a build add-in 124 | _TeamCity* 125 | 126 | # DotCover is a Code Coverage Tool 127 | *.dotCover 128 | 129 | # AxoCover is a Code Coverage Tool 130 | .axoCover/* 131 | !.axoCover/settings.json 132 | 133 | # Visual Studio code coverage results 134 | *.coverage 135 | *.coveragexml 136 | 137 | # NCrunch 138 | _NCrunch_* 139 | .*crunch*.local.xml 140 | nCrunchTemp_* 141 | 142 | # MightyMoose 143 | *.mm.* 144 | AutoTest.Net/ 145 | 146 | # Web workbench (sass) 147 | .sass-cache/ 148 | 149 | # Installshield output folder 150 | [Ee]xpress/ 151 | 152 | # DocProject is a documentation generator add-in 153 | DocProject/buildhelp/ 154 | DocProject/Help/*.HxT 155 | DocProject/Help/*.HxC 156 | DocProject/Help/*.hhc 157 | DocProject/Help/*.hhk 158 | DocProject/Help/*.hhp 159 | DocProject/Help/Html2 160 | DocProject/Help/html 161 | 162 | # Click-Once directory 163 | publish/ 164 | 165 | # Publish Web Output 166 | *.[Pp]ublish.xml 167 | *.azurePubxml 168 | # Note: Comment the next line if you want to checkin your web deploy settings, 169 | # but database connection strings (with potential passwords) will be unencrypted 170 | *.pubxml 171 | *.publishproj 172 | 173 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 174 | # checkin your Azure Web App publish settings, but sensitive information contained 175 | # in these scripts will be unencrypted 176 | PublishScripts/ 177 | 178 | # NuGet Packages 179 | *.nupkg 180 | # The packages folder can be ignored because of Package Restore 181 | **/[Pp]ackages/* 182 | # except build/, which is used as an MSBuild target. 183 | !**/[Pp]ackages/build/ 184 | # Uncomment if necessary however generally it will be regenerated when needed 185 | #!**/[Pp]ackages/repositories.config 186 | # NuGet v3's project.json files produces more ignorable files 187 | *.nuget.props 188 | *.nuget.targets 189 | 190 | # Microsoft Azure Build Output 191 | csx/ 192 | *.build.csdef 193 | 194 | # Microsoft Azure Emulator 195 | ecf/ 196 | rcf/ 197 | 198 | # Windows Store app package directories and files 199 | AppPackages/ 200 | BundleArtifacts/ 201 | Package.StoreAssociation.xml 202 | _pkginfo.txt 203 | *.appx 204 | 205 | # Visual Studio cache files 206 | # files ending in .cache can be ignored 207 | *.[Cc]ache 208 | # but keep track of directories ending in .cache 209 | !*.[Cc]ache/ 210 | 211 | # Others 212 | ClientBin/ 213 | ~$* 214 | *~ 215 | *.dbmdl 216 | *.dbproj.schemaview 217 | *.jfm 218 | *.pfx 219 | *.publishsettings 220 | orleans.codegen.cs 221 | 222 | # Including strong name files can present a security risk 223 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 224 | #*.snk 225 | 226 | # Since there are multiple workflows, uncomment next line to ignore bower_components 227 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 228 | #bower_components/ 229 | 230 | # RIA/Silverlight projects 231 | Generated_Code/ 232 | 233 | # Backup & report files from converting an old project file 234 | # to a newer Visual Studio version. Backup files are not needed, 235 | # because we have git ;-) 236 | _UpgradeReport_Files/ 237 | Backup*/ 238 | UpgradeLog*.XML 239 | UpgradeLog*.htm 240 | ServiceFabricBackup/ 241 | 242 | # SQL Server files 243 | *.mdf 244 | *.ldf 245 | *.ndf 246 | 247 | # Business Intelligence projects 248 | *.rdl.data 249 | *.bim.layout 250 | *.bim_*.settings 251 | *.rptproj.rsuser 252 | 253 | # Microsoft Fakes 254 | FakesAssemblies/ 255 | 256 | # GhostDoc plugin setting file 257 | *.GhostDoc.xml 258 | 259 | # Node.js Tools for Visual Studio 260 | .ntvs_analysis.dat 261 | node_modules/ 262 | 263 | # Visual Studio 6 build log 264 | *.plg 265 | 266 | # Visual Studio 6 workspace options file 267 | *.opt 268 | 269 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 270 | *.vbw 271 | 272 | # Visual Studio LightSwitch build output 273 | **/*.HTMLClient/GeneratedArtifacts 274 | **/*.DesktopClient/GeneratedArtifacts 275 | **/*.DesktopClient/ModelManifest.xml 276 | **/*.Server/GeneratedArtifacts 277 | **/*.Server/ModelManifest.xml 278 | _Pvt_Extensions 279 | 280 | # Paket dependency manager 281 | .paket/paket.exe 282 | paket-files/ 283 | 284 | # FAKE - F# Make 285 | .fake/ 286 | 287 | # JetBrains Rider 288 | .idea/ 289 | *.sln.iml 290 | 291 | # CodeRush 292 | .cr/ 293 | 294 | # Python Tools for Visual Studio (PTVS) 295 | __pycache__/ 296 | *.pyc 297 | 298 | # Cake - Uncomment if you are using it 299 | # tools/** 300 | # !tools/packages.config 301 | 302 | # Tabs Studio 303 | *.tss 304 | 305 | # Telerik's JustMock configuration file 306 | *.jmconfig 307 | 308 | # BizTalk build output 309 | *.btp.cs 310 | *.btm.cs 311 | *.odx.cs 312 | *.xsd.cs 313 | 314 | # OpenCover UI analysis results 315 | OpenCover/ 316 | 317 | # Azure Stream Analytics local run output 318 | ASALocalRun/ 319 | 320 | # MSBuild Binary and Structured Log 321 | *.binlog 322 | 323 | # NVidia Nsight GPU debugger configuration file 324 | *.nvuser 325 | 326 | # Zip releases 327 | *.zip 328 | 329 | !lib/x86 330 | !lib/x64 -------------------------------------------------------------------------------- /AssetDataUtil.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudio; 4 | using AssetStudioExporter.AssetTypes; 5 | using AssetStudioExporter.Internal; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace AssetStudio; 13 | 14 | public static class AssetDataUtil 15 | { 16 | /// 17 | /// 根据资源路径、大小和偏移量读取AssetBundle中的数据 18 | /// 19 | /// AssetsFileInstance 20 | /// 资源路径 21 | /// 大小 22 | /// 偏移量 23 | /// 资源的原始二进制数据 24 | /// 找不到对应的资源 25 | public static byte[] GetAssetData(this AssetsFileInstance inst, string path, uint size, long offset = 0) 26 | { 27 | byte[] data = Array.Empty(); 28 | if (path != null && inst.parentBundle != null) 29 | { 30 | string text = path; 31 | if (path.StartsWith("archive:/")) 32 | { 33 | text = text[9..]; 34 | } 35 | text = Path.GetFileName(text); 36 | AssetBundleFile file = inst.parentBundle.file; 37 | AssetsFileReader dataReader = file.DataReader; 38 | var directoryInfos = file.BlockAndDirInfo.DirectoryInfos; 39 | bool foundFile = false; 40 | foreach (AssetBundleDirectoryInfo assetBundleDirectoryInfo in directoryInfos) 41 | { 42 | if (assetBundleDirectoryInfo.Name == text) 43 | { 44 | dataReader.Position = assetBundleDirectoryInfo.Offset + offset; 45 | data = dataReader.ReadBytes((int)size); 46 | foundFile = true; 47 | break; 48 | } 49 | } 50 | if (!foundFile) 51 | { 52 | throw new FileNotFoundException("resS was detected but no file was found in bundle"); 53 | } 54 | } 55 | return data; 56 | } 57 | 58 | public static UnityVersion GetVersion(this AssetsFileInstance inst) 59 | { 60 | //return VersionCache.GetVersion(inst); 61 | return new UnityVersion(inst.file.Metadata.UnityVersion); 62 | } 63 | 64 | /// 65 | /// 获取AssetBundle或ResourceManager的Container 66 | /// 67 | /// AssetsFileInstance 68 | /// Container字典 69 | public static Dictionary GetContainers(this AssetsFileInstance inst, AssetsManager am) 70 | { 71 | var isAB = true; 72 | var ab = inst.file.AssetInfos.FirstOrDefault(f => f.TypeId == (uint)AssetClassID.AssetBundle); 73 | if (ab is null) 74 | { 75 | ab = inst.file.AssetInfos.FirstOrDefault(f => f.TypeId == (uint)AssetClassID.ResourceManager); 76 | isAB = false; 77 | if (ab is null) 78 | { 79 | return new Dictionary(); 80 | } 81 | } 82 | var bf = am.GetBaseField(inst, ab); 83 | var version = VersionCache.GetVersion(inst); 84 | if (isAB) 85 | { 86 | var assetBundle = AssetBundle.Read(bf, version); 87 | return assetBundle.m_Container 88 | .Select(p => new KeyValuePair(p.Key, p.Value.asset)) 89 | .ToDictionary(p => p.Key, p => p.Value); 90 | } else 91 | { 92 | var resourceManager = ResourceManager.Read(bf, version); 93 | return resourceManager.m_Container 94 | .Select(p => new KeyValuePair(p.Key, p.Value)) 95 | .ToDictionary(p => p.Key, p => p.Value); 96 | } 97 | } 98 | 99 | public static object? DeserializeMonoBehaviour(AssetTypeValueField monoBehaviour) => 100 | monoBehaviour.Value switch 101 | { 102 | AssetTypeValue v => v.ValueType switch 103 | { 104 | AssetValueType.Bool => v.AsBool, 105 | AssetValueType.Int8 => v.AsByte, 106 | AssetValueType.UInt8 => v.AsSByte, 107 | AssetValueType.Int16 => v.AsShort, 108 | AssetValueType.UInt16 => v.AsUShort, 109 | AssetValueType.Int32 => v.AsInt, 110 | AssetValueType.UInt32 => v.AsUInt, 111 | AssetValueType.Int64 => v.AsLong, 112 | AssetValueType.UInt64 => v.AsULong, 113 | AssetValueType.Float => v.AsFloat, 114 | AssetValueType.Double => v.AsDouble, 115 | AssetValueType.String => v.AsString, 116 | AssetValueType.Array => monoBehaviour.Children 117 | .Select(DeserializeMonoBehaviour) 118 | .ToArray(), 119 | AssetValueType.ByteArray => v.AsByteArray, 120 | _ => null 121 | }, 122 | null => monoBehaviour.Children 123 | .Aggregate(new Dictionary(), (obj, c) => 124 | { 125 | var key = c.TemplateField.Name; 126 | var value = DeserializeMonoBehaviour(c); 127 | obj[key] = value; 128 | return obj; 129 | }) 130 | }; 131 | 132 | public static AssetTypeValueField SerializeMonoBehaviour(object? obj, AssetTypeTemplateField templateField) 133 | { 134 | var ret = new AssetTypeValueField(); 135 | ret.TemplateField = templateField; 136 | 137 | ret.Value = obj switch 138 | { 139 | null => new AssetTypeValue(AssetValueType.None, null), 140 | bool b => new AssetTypeValue(AssetValueType.Bool, b), 141 | sbyte int8 => new AssetTypeValue(AssetValueType.Int8, int8), 142 | byte uint8 => new AssetTypeValue(AssetValueType.UInt8, uint8), 143 | short int16 => new AssetTypeValue(AssetValueType.Int16, int16), 144 | ushort uint16 => new AssetTypeValue(AssetValueType.UInt16, uint16), 145 | int int32 => new AssetTypeValue(AssetValueType.Int32, int32), 146 | uint uint32 => new AssetTypeValue(AssetValueType.UInt32, uint32), 147 | long int64 => new AssetTypeValue(AssetValueType.Int64, int64), 148 | ulong uint64 => new AssetTypeValue(AssetValueType.UInt64, uint64), 149 | float f => new AssetTypeValue(AssetValueType.Float, f), 150 | double d => new AssetTypeValue(AssetValueType.Double, d), 151 | string s => new AssetTypeValue(AssetValueType.String, s), 152 | IEnumerable barr => new AssetTypeValue( 153 | AssetValueType.ByteArray, 154 | barr.ToArray()), 155 | _ => null 156 | }; 157 | 158 | if (ret.Value is not null) 159 | { 160 | return ret; 161 | } 162 | 163 | 164 | if (obj is IDictionary dict) 165 | { 166 | ret.Value = null; 167 | ret.Children = dict.Select(p => 168 | { 169 | var temp = templateField.Children.First(f => f.Name == p.Key); 170 | AssetTypeValueField child = SerializeMonoBehaviour(p.Value, temp); 171 | return child; 172 | }).ToList(); 173 | } 174 | else if (obj is IList arr) 175 | { 176 | var array = new AssetTypeArrayInfo(arr.Count); 177 | ret.Children =arr 178 | .Select(x => 179 | { 180 | var temp = templateField.Children[1];// [0]为size 181 | return (AssetTypeValueField)SerializeMonoBehaviour(x, temp); 182 | }) 183 | .ToList(); 184 | ret.Value = new AssetTypeValue(AssetValueType.Array, array); 185 | } 186 | else 187 | { 188 | ret.Value = null; 189 | Type type = obj!.GetType(); 190 | ret.Children = type.GetFields().Select(f => 191 | { 192 | var key = f.Name; 193 | var temp = templateField.Children.First(f => f.Name == key); 194 | AssetTypeValueField child = SerializeMonoBehaviour(f.GetValue(obj), temp); 195 | return child; 196 | }).ToList(); 197 | } 198 | 199 | 200 | return ret; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /AssetStudio/BigArrayPool.cs: -------------------------------------------------------------------------------- 1 | using System.Buffers; 2 | 3 | namespace AssetStudio 4 | { 5 | public static class BigArrayPool 6 | { 7 | private static readonly ArrayPool s_shared = ArrayPool.Create(64 * 1024 * 1024, 3); 8 | public static ArrayPool Shared => s_shared; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /AssetStudio/Math/Color.cs: -------------------------------------------------------------------------------- 1 | 2 | using System; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace AssetStudio 6 | { 7 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 8 | public struct Color : IEquatable 9 | { 10 | public float R; 11 | public float G; 12 | public float B; 13 | public float A; 14 | 15 | public Color(float r, float g, float b, float a) 16 | { 17 | R = r; 18 | G = g; 19 | B = b; 20 | A = a; 21 | } 22 | 23 | public override int GetHashCode() 24 | { 25 | return ((Vector4)this).GetHashCode(); 26 | } 27 | 28 | public override bool Equals(object other) 29 | { 30 | if (!(other is Color)) 31 | return false; 32 | return Equals((Color)other); 33 | } 34 | 35 | public bool Equals(Color other) 36 | { 37 | return R.Equals(other.R) && G.Equals(other.G) && B.Equals(other.B) && A.Equals(other.A); 38 | } 39 | 40 | public static Color operator +(Color a, Color b) 41 | { 42 | return new Color(a.R + b.R, a.G + b.G, a.B + b.B, a.A + b.A); 43 | } 44 | 45 | public static Color operator -(Color a, Color b) 46 | { 47 | return new Color(a.R - b.R, a.G - b.G, a.B - b.B, a.A - b.A); 48 | } 49 | 50 | public static Color operator *(Color a, Color b) 51 | { 52 | return new Color(a.R * b.R, a.G * b.G, a.B * b.B, a.A * b.A); 53 | } 54 | 55 | public static Color operator *(Color a, float b) 56 | { 57 | return new Color(a.R * b, a.G * b, a.B * b, a.A * b); 58 | } 59 | 60 | public static Color operator *(float b, Color a) 61 | { 62 | return new Color(a.R * b, a.G * b, a.B * b, a.A * b); 63 | } 64 | 65 | public static Color operator /(Color a, float b) 66 | { 67 | return new Color(a.R / b, a.G / b, a.B / b, a.A / b); 68 | } 69 | 70 | public static bool operator ==(Color lhs, Color rhs) 71 | { 72 | return (Vector4)lhs == (Vector4)rhs; 73 | } 74 | 75 | public static bool operator !=(Color lhs, Color rhs) 76 | { 77 | return !(lhs == rhs); 78 | } 79 | 80 | public static implicit operator Vector4(Color c) 81 | { 82 | return new Vector4(c.R, c.G, c.B, c.A); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /AssetStudio/Math/HalfHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AssetStudio 5 | { 6 | /// 7 | /// Helper class for Half conversions and some low level operations. 8 | /// This class is internally used in the Half class. 9 | /// 10 | /// 11 | /// References: 12 | /// - Fast Half Float Conversions, Jeroen van der Zijp, link: http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf 13 | /// 14 | [ComVisible(false)] 15 | internal static class HalfHelper 16 | { 17 | private static uint[] mantissaTable = GenerateMantissaTable(); 18 | private static uint[] exponentTable = GenerateExponentTable(); 19 | private static ushort[] offsetTable = GenerateOffsetTable(); 20 | private static ushort[] baseTable = GenerateBaseTable(); 21 | private static sbyte[] shiftTable = GenerateShiftTable(); 22 | 23 | // Transforms the subnormal representation to a normalized one. 24 | private static uint ConvertMantissa(int i) 25 | { 26 | uint m = (uint)(i << 13); // Zero pad mantissa bits 27 | uint e = 0; // Zero exponent 28 | 29 | // While not normalized 30 | while ((m & 0x00800000) == 0) 31 | { 32 | e -= 0x00800000; // Decrement exponent (1<<23) 33 | m <<= 1; // Shift mantissa 34 | } 35 | m &= unchecked((uint)~0x00800000); // Clear leading 1 bit 36 | e += 0x38800000; // Adjust bias ((127-14)<<23) 37 | return m | e; // Return combined number 38 | } 39 | 40 | private static uint[] GenerateMantissaTable() 41 | { 42 | uint[] mantissaTable = new uint[2048]; 43 | mantissaTable[0] = 0; 44 | for (int i = 1; i < 1024; i++) 45 | { 46 | mantissaTable[i] = ConvertMantissa(i); 47 | } 48 | for (int i = 1024; i < 2048; i++) 49 | { 50 | mantissaTable[i] = (uint)(0x38000000 + ((i - 1024) << 13)); 51 | } 52 | 53 | return mantissaTable; 54 | } 55 | private static uint[] GenerateExponentTable() 56 | { 57 | uint[] exponentTable = new uint[64]; 58 | exponentTable[0] = 0; 59 | for (int i = 1; i < 31; i++) 60 | { 61 | exponentTable[i] = (uint)(i << 23); 62 | } 63 | exponentTable[31] = 0x47800000; 64 | exponentTable[32] = 0x80000000; 65 | for (int i = 33; i < 63; i++) 66 | { 67 | exponentTable[i] = (uint)(0x80000000 + ((i - 32) << 23)); 68 | } 69 | exponentTable[63] = 0xc7800000; 70 | 71 | return exponentTable; 72 | } 73 | private static ushort[] GenerateOffsetTable() 74 | { 75 | ushort[] offsetTable = new ushort[64]; 76 | offsetTable[0] = 0; 77 | for (int i = 1; i < 32; i++) 78 | { 79 | offsetTable[i] = 1024; 80 | } 81 | offsetTable[32] = 0; 82 | for (int i = 33; i < 64; i++) 83 | { 84 | offsetTable[i] = 1024; 85 | } 86 | 87 | return offsetTable; 88 | } 89 | private static ushort[] GenerateBaseTable() 90 | { 91 | ushort[] baseTable = new ushort[512]; 92 | for (int i = 0; i < 256; ++i) 93 | { 94 | sbyte e = (sbyte)(127 - i); 95 | if (e > 24) 96 | { // Very small numbers map to zero 97 | baseTable[i | 0x000] = 0x0000; 98 | baseTable[i | 0x100] = 0x8000; 99 | } 100 | else if (e > 14) 101 | { // Small numbers map to denorms 102 | baseTable[i | 0x000] = (ushort)(0x0400 >> (18 + e)); 103 | baseTable[i | 0x100] = (ushort)((0x0400 >> (18 + e)) | 0x8000); 104 | } 105 | else if (e >= -15) 106 | { // Normal numbers just lose precision 107 | baseTable[i | 0x000] = (ushort)((15 - e) << 10); 108 | baseTable[i | 0x100] = (ushort)(((15 - e) << 10) | 0x8000); 109 | } 110 | else if (e > -128) 111 | { // Large numbers map to Infinity 112 | baseTable[i | 0x000] = 0x7c00; 113 | baseTable[i | 0x100] = 0xfc00; 114 | } 115 | else 116 | { // Infinity and NaN's stay Infinity and NaN's 117 | baseTable[i | 0x000] = 0x7c00; 118 | baseTable[i | 0x100] = 0xfc00; 119 | } 120 | } 121 | 122 | return baseTable; 123 | } 124 | private static sbyte[] GenerateShiftTable() 125 | { 126 | sbyte[] shiftTable = new sbyte[512]; 127 | for (int i = 0; i < 256; ++i) 128 | { 129 | sbyte e = (sbyte)(127 - i); 130 | if (e > 24) 131 | { // Very small numbers map to zero 132 | shiftTable[i | 0x000] = 24; 133 | shiftTable[i | 0x100] = 24; 134 | } 135 | else if (e > 14) 136 | { // Small numbers map to denorms 137 | shiftTable[i | 0x000] = (sbyte)(e - 1); 138 | shiftTable[i | 0x100] = (sbyte)(e - 1); 139 | } 140 | else if (e >= -15) 141 | { // Normal numbers just lose precision 142 | shiftTable[i | 0x000] = 13; 143 | shiftTable[i | 0x100] = 13; 144 | } 145 | else if (e > -128) 146 | { // Large numbers map to Infinity 147 | shiftTable[i | 0x000] = 24; 148 | shiftTable[i | 0x100] = 24; 149 | } 150 | else 151 | { // Infinity and NaN's stay Infinity and NaN's 152 | shiftTable[i | 0x000] = 13; 153 | shiftTable[i | 0x100] = 13; 154 | } 155 | } 156 | 157 | return shiftTable; 158 | } 159 | 160 | /*public static unsafe float HalfToSingle(Half half) 161 | { 162 | uint result = mantissaTable[offsetTable[half.value >> 10] + (half.value & 0x3ff)] + exponentTable[half.value >> 10]; 163 | return *((float*)&result); 164 | } 165 | public static unsafe Half SingleToHalf(float single) 166 | { 167 | uint value = *((uint*)&single); 168 | 169 | ushort result = (ushort)(baseTable[(value >> 23) & 0x1ff] + ((value & 0x007fffff) >> shiftTable[value >> 23])); 170 | return Half.ToHalf(result); 171 | }*/ 172 | public static float HalfToSingle(Half half) 173 | { 174 | uint result = mantissaTable[offsetTable[half.value >> 10] + (half.value & 0x3ff)] + exponentTable[half.value >> 10]; 175 | byte[] uintBytes = BitConverter.GetBytes(result); 176 | return BitConverter.ToSingle(uintBytes, 0); 177 | } 178 | public static Half SingleToHalf(float single) 179 | { 180 | byte[] singleBytes = BitConverter.GetBytes(single); 181 | uint value = BitConverter.ToUInt32(singleBytes, 0); 182 | ushort result = (ushort)(baseTable[(value >> 23) & 0x1ff] + ((value & 0x007fffff) >> shiftTable[value >> 23])); 183 | return Half.ToHalf(result); 184 | } 185 | 186 | public static Half Negate(Half half) 187 | { 188 | return Half.ToHalf((ushort)(half.value ^ 0x8000)); 189 | } 190 | public static Half Abs(Half half) 191 | { 192 | return Half.ToHalf((ushort)(half.value & 0x7fff)); 193 | } 194 | 195 | public static bool IsNaN(Half half) 196 | { 197 | return ((half.value & 0x7fff) > 0x7c00); 198 | } 199 | public static bool IsInfinity(Half half) 200 | { 201 | return ((half.value & 0x7fff) == 0x7c00); 202 | } 203 | public static bool IsPositiveInfinity(Half half) 204 | { 205 | return (half.value == 0x7c00); 206 | } 207 | public static bool IsNegativeInfinity(Half half) 208 | { 209 | return (half.value == 0xfc00); 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /AssetStudio/Math/Matrix4x4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AssetStudio 5 | { 6 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 7 | public struct Matrix4x4 : IEquatable 8 | { 9 | public float M00; 10 | public float M10; 11 | public float M20; 12 | public float M30; 13 | 14 | public float M01; 15 | public float M11; 16 | public float M21; 17 | public float M31; 18 | 19 | public float M02; 20 | public float M12; 21 | public float M22; 22 | public float M32; 23 | 24 | public float M03; 25 | public float M13; 26 | public float M23; 27 | public float M33; 28 | 29 | public Matrix4x4(float[] values) 30 | { 31 | if (values == null) 32 | throw new ArgumentNullException(nameof(values)); 33 | if (values.Length != 16) 34 | throw new ArgumentOutOfRangeException(nameof(values), "There must be sixteen and only sixteen input values for Matrix."); 35 | 36 | M00 = values[0]; 37 | M10 = values[1]; 38 | M20 = values[2]; 39 | M30 = values[3]; 40 | 41 | M01 = values[4]; 42 | M11 = values[5]; 43 | M21 = values[6]; 44 | M31 = values[7]; 45 | 46 | M02 = values[8]; 47 | M12 = values[9]; 48 | M22 = values[10]; 49 | M32 = values[11]; 50 | 51 | M03 = values[12]; 52 | M13 = values[13]; 53 | M23 = values[14]; 54 | M33 = values[15]; 55 | } 56 | 57 | public float this[int row, int column] 58 | { 59 | get => this[row + column * 4]; 60 | 61 | set => this[row + column * 4] = value; 62 | } 63 | 64 | public float this[int index] 65 | { 66 | get 67 | { 68 | switch (index) 69 | { 70 | case 0: return M00; 71 | case 1: return M10; 72 | case 2: return M20; 73 | case 3: return M30; 74 | case 4: return M01; 75 | case 5: return M11; 76 | case 6: return M21; 77 | case 7: return M31; 78 | case 8: return M02; 79 | case 9: return M12; 80 | case 10: return M22; 81 | case 11: return M32; 82 | case 12: return M03; 83 | case 13: return M13; 84 | case 14: return M23; 85 | case 15: return M33; 86 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Matrix4x4 index!"); 87 | } 88 | } 89 | 90 | set 91 | { 92 | switch (index) 93 | { 94 | case 0: M00 = value; break; 95 | case 1: M10 = value; break; 96 | case 2: M20 = value; break; 97 | case 3: M30 = value; break; 98 | case 4: M01 = value; break; 99 | case 5: M11 = value; break; 100 | case 6: M21 = value; break; 101 | case 7: M31 = value; break; 102 | case 8: M02 = value; break; 103 | case 9: M12 = value; break; 104 | case 10: M22 = value; break; 105 | case 11: M32 = value; break; 106 | case 12: M03 = value; break; 107 | case 13: M13 = value; break; 108 | case 14: M23 = value; break; 109 | case 15: M33 = value; break; 110 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Matrix4x4 index!"); 111 | } 112 | } 113 | } 114 | 115 | public override int GetHashCode() 116 | { 117 | return GetColumn(0).GetHashCode() ^ (GetColumn(1).GetHashCode() << 2) ^ (GetColumn(2).GetHashCode() >> 2) ^ (GetColumn(3).GetHashCode() >> 1); 118 | } 119 | 120 | public override bool Equals(object other) 121 | { 122 | if (!(other is Matrix4x4)) 123 | return false; 124 | return Equals((Matrix4x4)other); 125 | } 126 | 127 | public bool Equals(Matrix4x4 other) 128 | { 129 | return GetColumn(0).Equals(other.GetColumn(0)) 130 | && GetColumn(1).Equals(other.GetColumn(1)) 131 | && GetColumn(2).Equals(other.GetColumn(2)) 132 | && GetColumn(3).Equals(other.GetColumn(3)); 133 | } 134 | 135 | public Vector4 GetColumn(int index) 136 | { 137 | switch (index) 138 | { 139 | case 0: return new Vector4(M00, M10, M20, M30); 140 | case 1: return new Vector4(M01, M11, M21, M31); 141 | case 2: return new Vector4(M02, M12, M22, M32); 142 | case 3: return new Vector4(M03, M13, M23, M33); 143 | default: throw new IndexOutOfRangeException("Invalid column index!"); 144 | } 145 | } 146 | 147 | public Vector4 GetRow(int index) 148 | { 149 | switch (index) 150 | { 151 | case 0: return new Vector4(M00, M01, M02, M03); 152 | case 1: return new Vector4(M10, M11, M12, M13); 153 | case 2: return new Vector4(M20, M21, M22, M23); 154 | case 3: return new Vector4(M30, M31, M32, M33); 155 | default: throw new IndexOutOfRangeException("Invalid row index!"); 156 | } 157 | } 158 | 159 | public static Matrix4x4 operator *(Matrix4x4 lhs, Matrix4x4 rhs) 160 | { 161 | Matrix4x4 res; 162 | res.M00 = lhs.M00 * rhs.M00 + lhs.M01 * rhs.M10 + lhs.M02 * rhs.M20 + lhs.M03 * rhs.M30; 163 | res.M01 = lhs.M00 * rhs.M01 + lhs.M01 * rhs.M11 + lhs.M02 * rhs.M21 + lhs.M03 * rhs.M31; 164 | res.M02 = lhs.M00 * rhs.M02 + lhs.M01 * rhs.M12 + lhs.M02 * rhs.M22 + lhs.M03 * rhs.M32; 165 | res.M03 = lhs.M00 * rhs.M03 + lhs.M01 * rhs.M13 + lhs.M02 * rhs.M23 + lhs.M03 * rhs.M33; 166 | 167 | res.M10 = lhs.M10 * rhs.M00 + lhs.M11 * rhs.M10 + lhs.M12 * rhs.M20 + lhs.M13 * rhs.M30; 168 | res.M11 = lhs.M10 * rhs.M01 + lhs.M11 * rhs.M11 + lhs.M12 * rhs.M21 + lhs.M13 * rhs.M31; 169 | res.M12 = lhs.M10 * rhs.M02 + lhs.M11 * rhs.M12 + lhs.M12 * rhs.M22 + lhs.M13 * rhs.M32; 170 | res.M13 = lhs.M10 * rhs.M03 + lhs.M11 * rhs.M13 + lhs.M12 * rhs.M23 + lhs.M13 * rhs.M33; 171 | 172 | res.M20 = lhs.M20 * rhs.M00 + lhs.M21 * rhs.M10 + lhs.M22 * rhs.M20 + lhs.M23 * rhs.M30; 173 | res.M21 = lhs.M20 * rhs.M01 + lhs.M21 * rhs.M11 + lhs.M22 * rhs.M21 + lhs.M23 * rhs.M31; 174 | res.M22 = lhs.M20 * rhs.M02 + lhs.M21 * rhs.M12 + lhs.M22 * rhs.M22 + lhs.M23 * rhs.M32; 175 | res.M23 = lhs.M20 * rhs.M03 + lhs.M21 * rhs.M13 + lhs.M22 * rhs.M23 + lhs.M23 * rhs.M33; 176 | 177 | res.M30 = lhs.M30 * rhs.M00 + lhs.M31 * rhs.M10 + lhs.M32 * rhs.M20 + lhs.M33 * rhs.M30; 178 | res.M31 = lhs.M30 * rhs.M01 + lhs.M31 * rhs.M11 + lhs.M32 * rhs.M21 + lhs.M33 * rhs.M31; 179 | res.M32 = lhs.M30 * rhs.M02 + lhs.M31 * rhs.M12 + lhs.M32 * rhs.M22 + lhs.M33 * rhs.M32; 180 | res.M33 = lhs.M30 * rhs.M03 + lhs.M31 * rhs.M13 + lhs.M32 * rhs.M23 + lhs.M33 * rhs.M33; 181 | 182 | return res; 183 | } 184 | 185 | public static bool operator ==(Matrix4x4 lhs, Matrix4x4 rhs) 186 | { 187 | return lhs.GetColumn(0) == rhs.GetColumn(0) 188 | && lhs.GetColumn(1) == rhs.GetColumn(1) 189 | && lhs.GetColumn(2) == rhs.GetColumn(2) 190 | && lhs.GetColumn(3) == rhs.GetColumn(3); 191 | } 192 | 193 | public static bool operator !=(Matrix4x4 lhs, Matrix4x4 rhs) 194 | { 195 | return !(lhs == rhs); 196 | } 197 | 198 | public static Matrix4x4 Scale(Vector3 vector) 199 | { 200 | Matrix4x4 m; 201 | m.M00 = vector.X; m.M01 = 0F; m.M02 = 0F; m.M03 = 0F; 202 | m.M10 = 0F; m.M11 = vector.Y; m.M12 = 0F; m.M13 = 0F; 203 | m.M20 = 0F; m.M21 = 0F; m.M22 = vector.Z; m.M23 = 0F; 204 | m.M30 = 0F; m.M31 = 0F; m.M32 = 0F; m.M33 = 1F; 205 | return m; 206 | } 207 | 208 | public static Matrix4x4 Translate(Vector3 vector) 209 | { 210 | Matrix4x4 m; 211 | m.M00 = 1F; m.M01 = 0F; m.M02 = 0F; m.M03 = vector.X; 212 | m.M10 = 0F; m.M11 = 1F; m.M12 = 0F; m.M13 = vector.Y; 213 | m.M20 = 0F; m.M21 = 0F; m.M22 = 1F; m.M23 = vector.Z; 214 | m.M30 = 0F; m.M31 = 0F; m.M32 = 0F; m.M33 = 1F; 215 | return m; 216 | } 217 | 218 | public static Matrix4x4 Rotate(Quaternion q) 219 | { 220 | float x = q.X * 2.0F; 221 | float y = q.Y * 2.0F; 222 | float z = q.Z * 2.0F; 223 | float xx = q.X * x; 224 | float yy = q.Y * y; 225 | float zz = q.Z * z; 226 | float xy = q.X * y; 227 | float xz = q.X * z; 228 | float yz = q.Y * z; 229 | float wx = q.W * x; 230 | float wy = q.W * y; 231 | float wz = q.W * z; 232 | 233 | Matrix4x4 m; 234 | m.M00 = 1.0f - (yy + zz); m.M10 = xy + wz; m.M20 = xz - wy; m.M30 = 0.0F; 235 | m.M01 = xy - wz; m.M11 = 1.0f - (xx + zz); m.M21 = yz + wx; m.M31 = 0.0F; 236 | m.M02 = xz + wy; m.M12 = yz - wx; m.M22 = 1.0f - (xx + yy); m.M32 = 0.0F; 237 | m.M03 = 0.0F; m.M13 = 0.0F; m.M23 = 0.0F; m.M33 = 1.0F; 238 | return m; 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /AssetStudio/Math/Quaternion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AssetStudio 5 | { 6 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 7 | public struct Quaternion : IEquatable 8 | { 9 | public float X; 10 | public float Y; 11 | public float Z; 12 | public float W; 13 | 14 | public Quaternion(float x, float y, float z, float w) 15 | { 16 | X = x; 17 | Y = y; 18 | Z = z; 19 | W = w; 20 | } 21 | 22 | public float this[int index] 23 | { 24 | get 25 | { 26 | switch (index) 27 | { 28 | case 0: return X; 29 | case 1: return Y; 30 | case 2: return Z; 31 | case 3: return W; 32 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Quaternion index!"); 33 | } 34 | } 35 | 36 | set 37 | { 38 | switch (index) 39 | { 40 | case 0: X = value; break; 41 | case 1: Y = value; break; 42 | case 2: Z = value; break; 43 | case 3: W = value; break; 44 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Quaternion index!"); 45 | } 46 | } 47 | } 48 | 49 | public override int GetHashCode() 50 | { 51 | return X.GetHashCode() ^ (Y.GetHashCode() << 2) ^ (Z.GetHashCode() >> 2) ^ (W.GetHashCode() >> 1); 52 | } 53 | 54 | public override bool Equals(object other) 55 | { 56 | if (!(other is Quaternion)) 57 | return false; 58 | return Equals((Quaternion)other); 59 | } 60 | 61 | public bool Equals(Quaternion other) 62 | { 63 | return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z) && W.Equals(other.W); 64 | } 65 | 66 | public static Quaternion Zero => new Quaternion(0, 0, 0, 1); 67 | public static float Dot(Quaternion a, Quaternion b) 68 | { 69 | return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W; 70 | } 71 | 72 | private static bool IsEqualUsingDot(float dot) 73 | { 74 | return dot > 1.0f - kEpsilon; 75 | } 76 | 77 | public static bool operator ==(Quaternion lhs, Quaternion rhs) 78 | { 79 | return IsEqualUsingDot(Dot(lhs, rhs)); 80 | } 81 | 82 | public static bool operator !=(Quaternion lhs, Quaternion rhs) 83 | { 84 | return !(lhs == rhs); 85 | } 86 | 87 | private const float kEpsilon = 0.000001F; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /AssetStudio/Math/Vector2.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetStudioExporter.AssetTypes; 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace AssetStudio 7 | { 8 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 9 | public struct Vector2 : IEquatable 10 | { 11 | public float X; 12 | public float Y; 13 | 14 | public Vector2(float x, float y) 15 | { 16 | X = x; 17 | Y = y; 18 | } 19 | 20 | public float this[int index] 21 | { 22 | get 23 | { 24 | switch (index) 25 | { 26 | case 0: return X; 27 | case 1: return Y; 28 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector2 index!"); 29 | } 30 | } 31 | 32 | set 33 | { 34 | switch (index) 35 | { 36 | case 0: X = value; break; 37 | case 1: Y = value; break; 38 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector2 index!"); 39 | } 40 | } 41 | } 42 | 43 | public override int GetHashCode() 44 | { 45 | return X.GetHashCode() ^ (Y.GetHashCode() << 2); 46 | } 47 | 48 | public override bool Equals(object other) 49 | { 50 | if (!(other is Vector2)) 51 | return false; 52 | return Equals((Vector2)other); 53 | } 54 | 55 | public bool Equals(Vector2 other) 56 | { 57 | return X.Equals(other.X) && Y.Equals(other.Y); 58 | } 59 | 60 | public void Normalize() 61 | { 62 | var length = Length(); 63 | if (length > kEpsilon) 64 | { 65 | var invNorm = 1.0f / length; 66 | X *= invNorm; 67 | Y *= invNorm; 68 | } 69 | else 70 | { 71 | X = 0; 72 | Y = 0; 73 | } 74 | } 75 | 76 | public float Length() 77 | { 78 | return (float)Math.Sqrt(LengthSquared()); 79 | } 80 | 81 | public float LengthSquared() 82 | { 83 | return X * X + Y * Y; 84 | } 85 | 86 | public static Vector2 Zero => new Vector2(); 87 | 88 | public static Vector2 operator +(Vector2 a, Vector2 b) 89 | { 90 | return new Vector2(a.X + b.X, a.Y + b.Y); 91 | } 92 | 93 | public static Vector2 operator -(Vector2 a, Vector2 b) 94 | { 95 | return new Vector2(a.X - b.X, a.Y - b.Y); 96 | } 97 | 98 | public static Vector2 operator *(Vector2 a, Vector2 b) 99 | { 100 | return new Vector2(a.X * b.X, a.Y * b.Y); 101 | } 102 | 103 | public static Vector2 operator /(Vector2 a, Vector2 b) 104 | { 105 | return new Vector2(a.X / b.X, a.Y / b.Y); 106 | } 107 | 108 | public static Vector2 operator -(Vector2 a) 109 | { 110 | return new Vector2(-a.X, -a.Y); 111 | } 112 | 113 | public static Vector2 operator *(Vector2 a, float d) 114 | { 115 | return new Vector2(a.X * d, a.Y * d); 116 | } 117 | 118 | public static Vector2 operator *(float d, Vector2 a) 119 | { 120 | return new Vector2(a.X * d, a.Y * d); 121 | } 122 | 123 | public static Vector2 operator /(Vector2 a, float d) 124 | { 125 | return new Vector2(a.X / d, a.Y / d); 126 | } 127 | 128 | public static bool operator ==(Vector2 lhs, Vector2 rhs) 129 | { 130 | return (lhs - rhs).LengthSquared() < kEpsilon * kEpsilon; 131 | } 132 | 133 | public static bool operator !=(Vector2 lhs, Vector2 rhs) 134 | { 135 | return !(lhs == rhs); 136 | } 137 | 138 | public static implicit operator Vector3(Vector2 v) 139 | { 140 | return new Vector3(v.X, v.Y, 0); 141 | } 142 | 143 | public static implicit operator Vector4(Vector2 v) 144 | { 145 | return new Vector4(v.X, v.Y, 0.0F, 0.0F); 146 | } 147 | 148 | private const float kEpsilon = 0.00001F; 149 | 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /AssetStudio/Math/Vector3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AssetStudio 5 | { 6 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 7 | public struct Vector3 : IEquatable 8 | { 9 | public float X; 10 | public float Y; 11 | public float Z; 12 | 13 | public Vector3(float x, float y, float z) 14 | { 15 | X = x; 16 | Y = y; 17 | Z = z; 18 | } 19 | 20 | public float this[int index] 21 | { 22 | get 23 | { 24 | switch (index) 25 | { 26 | case 0: return X; 27 | case 1: return Y; 28 | case 2: return Z; 29 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector3 index!"); 30 | } 31 | } 32 | 33 | set 34 | { 35 | switch (index) 36 | { 37 | case 0: X = value; break; 38 | case 1: Y = value; break; 39 | case 2: Z = value; break; 40 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector3 index!"); 41 | } 42 | } 43 | } 44 | 45 | public override int GetHashCode() 46 | { 47 | return X.GetHashCode() ^ (Y.GetHashCode() << 2) ^ (Z.GetHashCode() >> 2); 48 | } 49 | 50 | public override bool Equals(object other) 51 | { 52 | if (!(other is Vector3)) 53 | return false; 54 | return Equals((Vector3)other); 55 | } 56 | 57 | public bool Equals(Vector3 other) 58 | { 59 | return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z); 60 | } 61 | 62 | public void Normalize() 63 | { 64 | var length = Length(); 65 | if (length > kEpsilon) 66 | { 67 | var invNorm = 1.0f / length; 68 | X *= invNorm; 69 | Y *= invNorm; 70 | Z *= invNorm; 71 | } 72 | else 73 | { 74 | X = 0; 75 | Y = 0; 76 | Z = 0; 77 | } 78 | } 79 | 80 | public float Length() 81 | { 82 | return (float)Math.Sqrt(LengthSquared()); 83 | } 84 | 85 | public float LengthSquared() 86 | { 87 | return X * X + Y * Y + Z * Z; 88 | } 89 | 90 | 91 | public static Vector3 Zero => new Vector3(); 92 | 93 | public static Vector3 One => new Vector3(1.0f, 1.0f, 1.0f); 94 | 95 | public static Vector3 operator +(Vector3 a, Vector3 b) 96 | { 97 | return new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); 98 | } 99 | 100 | public static Vector3 operator -(Vector3 a, Vector3 b) 101 | { 102 | return new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); 103 | } 104 | 105 | public static Vector3 operator -(Vector3 a) 106 | { 107 | return new Vector3(-a.X, -a.Y, -a.Z); 108 | } 109 | 110 | public static Vector3 operator *(Vector3 a, float d) 111 | { 112 | return new Vector3(a.X * d, a.Y * d, a.Z * d); 113 | } 114 | 115 | public static Vector3 operator *(float d, Vector3 a) 116 | { 117 | return new Vector3(a.X * d, a.Y * d, a.Z * d); 118 | } 119 | 120 | public static Vector3 operator /(Vector3 a, float d) 121 | { 122 | return new Vector3(a.X / d, a.Y / d, a.Z / d); 123 | } 124 | 125 | public static bool operator ==(Vector3 lhs, Vector3 rhs) 126 | { 127 | return (lhs - rhs).LengthSquared() < kEpsilon * kEpsilon; 128 | } 129 | 130 | public static bool operator !=(Vector3 lhs, Vector3 rhs) 131 | { 132 | return !(lhs == rhs); 133 | } 134 | 135 | public static implicit operator Vector2(Vector3 v) 136 | { 137 | return new Vector2(v.X, v.Y); 138 | } 139 | 140 | public static implicit operator Vector4(Vector3 v) 141 | { 142 | return new Vector4(v.X, v.Y, v.Z, 0.0F); 143 | } 144 | 145 | private const float kEpsilon = 0.00001F; 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /AssetStudio/Math/Vector4.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetStudioExporter.AssetTypes; 3 | using System; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace AssetStudio 7 | { 8 | [StructLayout(LayoutKind.Sequential, Pack = 4)] 9 | public struct Vector4 : IEquatable 10 | { 11 | public float X; 12 | public float Y; 13 | public float Z; 14 | public float W; 15 | 16 | public Vector4(float x, float y, float z, float w) 17 | { 18 | X = x; 19 | Y = y; 20 | Z = z; 21 | W = w; 22 | } 23 | 24 | public Vector4(Vector3 value, float w) 25 | { 26 | X = value.X; 27 | Y = value.Y; 28 | Z = value.Z; 29 | W = w; 30 | } 31 | 32 | public float this[int index] 33 | { 34 | get 35 | { 36 | switch (index) 37 | { 38 | case 0: return X; 39 | case 1: return Y; 40 | case 2: return Z; 41 | case 3: return W; 42 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector4 index!"); 43 | } 44 | } 45 | 46 | set 47 | { 48 | switch (index) 49 | { 50 | case 0: X = value; break; 51 | case 1: Y = value; break; 52 | case 2: Z = value; break; 53 | case 3: W = value; break; 54 | default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector4 index!"); 55 | } 56 | } 57 | } 58 | 59 | public override int GetHashCode() 60 | { 61 | return X.GetHashCode() ^ (Y.GetHashCode() << 2) ^ (Z.GetHashCode() >> 2) ^ (W.GetHashCode() >> 1); 62 | } 63 | 64 | public override bool Equals(object other) 65 | { 66 | if (!(other is Vector4)) 67 | return false; 68 | return Equals((Vector4)other); 69 | } 70 | 71 | public bool Equals(Vector4 other) 72 | { 73 | return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z) && W.Equals(other.W); 74 | } 75 | 76 | public void Normalize() 77 | { 78 | var length = Length(); 79 | if (length > kEpsilon) 80 | { 81 | var invNorm = 1.0f / length; 82 | X *= invNorm; 83 | Y *= invNorm; 84 | Z *= invNorm; 85 | W *= invNorm; 86 | } 87 | else 88 | { 89 | X = 0; 90 | Y = 0; 91 | Z = 0; 92 | W = 0; 93 | } 94 | } 95 | 96 | public float Length() 97 | { 98 | return (float)Math.Sqrt(LengthSquared()); 99 | } 100 | 101 | public float LengthSquared() 102 | { 103 | return X * X + Y * Y + Z * Z + W * W; 104 | } 105 | 106 | public static Vector4 Zero => new Vector4(); 107 | 108 | public static Vector4 operator +(Vector4 a, Vector4 b) 109 | { 110 | return new Vector4(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); 111 | } 112 | 113 | public static Vector4 operator -(Vector4 a, Vector4 b) 114 | { 115 | return new Vector4(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W); 116 | } 117 | 118 | public static Vector4 operator -(Vector4 a) 119 | { 120 | return new Vector4(-a.X, -a.Y, -a.Z, -a.W); 121 | } 122 | 123 | public static Vector4 operator *(Vector4 a, float d) 124 | { 125 | return new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); 126 | } 127 | 128 | public static Vector4 operator *(float d, Vector4 a) 129 | { 130 | return new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); 131 | } 132 | 133 | public static Vector4 operator /(Vector4 a, float d) 134 | { 135 | return new Vector4(a.X / d, a.Y / d, a.Z / d, a.W / d); 136 | } 137 | 138 | public static bool operator ==(Vector4 lhs, Vector4 rhs) 139 | { 140 | return (lhs - rhs).LengthSquared() < kEpsilon * kEpsilon; 141 | } 142 | 143 | public static bool operator !=(Vector4 lhs, Vector4 rhs) 144 | { 145 | return !(lhs == rhs); 146 | } 147 | 148 | public static implicit operator Vector2(Vector4 v) 149 | { 150 | return new Vector2(v.X, v.Y); 151 | } 152 | 153 | public static implicit operator Vector3(Vector4 v) 154 | { 155 | return new Vector3(v.X, v.Y, v.Z); 156 | } 157 | 158 | public static implicit operator Color(Vector4 v) 159 | { 160 | return new Color(v.X, v.Y, v.Z, v.W); 161 | } 162 | 163 | private const float kEpsilon = 0.00001F; 164 | 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /AssetStudio/Texture2DDecoder/T2DDll.cs: -------------------------------------------------------------------------------- 1 | namespace Texture2DDecoder 2 | { 3 | internal static class T2DDll 4 | { 5 | 6 | internal const string DllName = "Texture2DDecoderNative"; 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AssetStudio/Texture2DDecoder/TextureDecoder.PInvoke.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using AssetStudioExporter.Native; 3 | 4 | namespace Texture2DDecoder 5 | { 6 | unsafe partial class TextureDecoder 7 | { 8 | 9 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 10 | [return: MarshalAs(UnmanagedType.Bool)] 11 | private static extern bool DecodeDXT1(void* data, int width, int height, void* image); 12 | 13 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 14 | [return: MarshalAs(UnmanagedType.Bool)] 15 | private static extern bool DecodeDXT5(void* data, int width, int height, void* image); 16 | 17 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 18 | [return: MarshalAs(UnmanagedType.Bool)] 19 | private static extern bool DecodePVRTC(void* data, int width, int height, void* image, [MarshalAs(UnmanagedType.Bool)] bool is2bpp); 20 | 21 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 22 | [return: MarshalAs(UnmanagedType.Bool)] 23 | private static extern bool DecodeETC1(void* data, int width, int height, void* image); 24 | 25 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 26 | [return: MarshalAs(UnmanagedType.Bool)] 27 | private static extern bool DecodeETC2(void* data, int width, int height, void* image); 28 | 29 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 30 | [return: MarshalAs(UnmanagedType.Bool)] 31 | private static extern bool DecodeETC2A1(void* data, int width, int height, void* image); 32 | 33 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 34 | [return: MarshalAs(UnmanagedType.Bool)] 35 | private static extern bool DecodeETC2A8(void* data, int width, int height, void* image); 36 | 37 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 38 | [return: MarshalAs(UnmanagedType.Bool)] 39 | private static extern bool DecodeEACR(void* data, int width, int height, void* image); 40 | 41 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 42 | [return: MarshalAs(UnmanagedType.Bool)] 43 | private static extern bool DecodeEACRSigned(void* data, int width, int height, void* image); 44 | 45 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 46 | [return: MarshalAs(UnmanagedType.Bool)] 47 | private static extern bool DecodeEACRG(void* data, int width, int height, void* image); 48 | 49 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 50 | [return: MarshalAs(UnmanagedType.Bool)] 51 | private static extern bool DecodeEACRGSigned(void* data, int width, int height, void* image); 52 | 53 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 54 | [return: MarshalAs(UnmanagedType.Bool)] 55 | private static extern bool DecodeBC4(void* data, int width, int height, void* image); 56 | 57 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 58 | [return: MarshalAs(UnmanagedType.Bool)] 59 | private static extern bool DecodeBC5(void* data, int width, int height, void* image); 60 | 61 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 62 | [return: MarshalAs(UnmanagedType.Bool)] 63 | private static extern bool DecodeBC6(void* data, int width, int height, void* image); 64 | 65 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 66 | [return: MarshalAs(UnmanagedType.Bool)] 67 | private static extern bool DecodeBC7(void* data, int width, int height, void* image); 68 | 69 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 70 | [return: MarshalAs(UnmanagedType.Bool)] 71 | private static extern bool DecodeATCRGB4(void* data, int width, int height, void* image); 72 | 73 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 74 | [return: MarshalAs(UnmanagedType.Bool)] 75 | private static extern bool DecodeATCRGBA8(void* data, int width, int height, void* image); 76 | 77 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 78 | [return: MarshalAs(UnmanagedType.Bool)] 79 | private static extern bool DecodeASTC(void* data, int width, int height, int blockWidth, int blockHeight, void* image); 80 | 81 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 82 | private static extern void DisposeBuffer(ref void* ppBuffer); 83 | 84 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 85 | private static extern void UnpackCrunch(void* data, uint dataSize, out void* result, out uint resultSize); 86 | 87 | [DllImport(T2DDll.DllName, CallingConvention = CallingConvention.Winapi)] 88 | private static extern void UnpackUnityCrunch(void* data, uint dataSize, out void* result, out uint resultSize); 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /AssetStudio/Texture2DDecoder/TextureDecoder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using AssetStudioExporter.Native; 4 | 5 | namespace Texture2DDecoder 6 | { 7 | public static unsafe partial class TextureDecoder 8 | { 9 | 10 | static TextureDecoder() 11 | { 12 | DllLoaderNative.PreloadDll(T2DDll.DllName); 13 | } 14 | 15 | public static bool DecodeDXT1(byte[] data, int width, int height, byte[] image) 16 | { 17 | fixed (byte* pData = data) 18 | { 19 | fixed (byte* pImage = image) 20 | { 21 | return DecodeDXT1(pData, width, height, pImage); 22 | } 23 | } 24 | } 25 | 26 | public static bool DecodeDXT5(byte[] data, int width, int height, byte[] image) 27 | { 28 | fixed (byte* pData = data) 29 | { 30 | fixed (byte* pImage = image) 31 | { 32 | return DecodeDXT5(pData, width, height, pImage); 33 | } 34 | } 35 | } 36 | 37 | public static bool DecodePVRTC(byte[] data, int width, int height, byte[] image, bool is2bpp) 38 | { 39 | fixed (byte* pData = data) 40 | { 41 | fixed (byte* pImage = image) 42 | { 43 | return DecodePVRTC(pData, width, height, pImage, is2bpp); 44 | } 45 | } 46 | } 47 | 48 | public static bool DecodeETC1(byte[] data, int width, int height, byte[] image) 49 | { 50 | fixed (byte* pData = data) 51 | { 52 | fixed (byte* pImage = image) 53 | { 54 | return DecodeETC1(pData, width, height, pImage); 55 | } 56 | } 57 | } 58 | 59 | public static bool DecodeETC2(byte[] data, int width, int height, byte[] image) 60 | { 61 | fixed (byte* pData = data) 62 | { 63 | fixed (byte* pImage = image) 64 | { 65 | return DecodeETC2(pData, width, height, pImage); 66 | } 67 | } 68 | } 69 | 70 | public static bool DecodeETC2A1(byte[] data, int width, int height, byte[] image) 71 | { 72 | fixed (byte* pData = data) 73 | { 74 | fixed (byte* pImage = image) 75 | { 76 | return DecodeETC2A1(pData, width, height, pImage); 77 | } 78 | } 79 | } 80 | 81 | public static bool DecodeETC2A8(byte[] data, int width, int height, byte[] image) 82 | { 83 | fixed (byte* pData = data) 84 | { 85 | fixed (byte* pImage = image) 86 | { 87 | return DecodeETC2A8(pData, width, height, pImage); 88 | } 89 | } 90 | } 91 | 92 | public static bool DecodeEACR(byte[] data, int width, int height, byte[] image) 93 | { 94 | fixed (byte* pData = data) 95 | { 96 | fixed (byte* pImage = image) 97 | { 98 | return DecodeEACR(pData, width, height, pImage); 99 | } 100 | } 101 | } 102 | 103 | public static bool DecodeEACRSigned(byte[] data, int width, int height, byte[] image) 104 | { 105 | fixed (byte* pData = data) 106 | { 107 | fixed (byte* pImage = image) 108 | { 109 | return DecodeEACRSigned(pData, width, height, pImage); 110 | } 111 | } 112 | } 113 | 114 | public static bool DecodeEACRG(byte[] data, int width, int height, byte[] image) 115 | { 116 | fixed (byte* pData = data) 117 | { 118 | fixed (byte* pImage = image) 119 | { 120 | return DecodeEACRG(pData, width, height, pImage); 121 | } 122 | } 123 | } 124 | 125 | public static bool DecodeEACRGSigned(byte[] data, int width, int height, byte[] image) 126 | { 127 | fixed (byte* pData = data) 128 | { 129 | fixed (byte* pImage = image) 130 | { 131 | return DecodeEACRGSigned(pData, width, height, pImage); 132 | } 133 | } 134 | } 135 | 136 | public static bool DecodeBC4(byte[] data, int width, int height, byte[] image) 137 | { 138 | fixed (byte* pData = data) 139 | { 140 | fixed (byte* pImage = image) 141 | { 142 | return DecodeBC4(pData, width, height, pImage); 143 | } 144 | } 145 | } 146 | 147 | public static bool DecodeBC5(byte[] data, int width, int height, byte[] image) 148 | { 149 | fixed (byte* pData = data) 150 | { 151 | fixed (byte* pImage = image) 152 | { 153 | return DecodeBC5(pData, width, height, pImage); 154 | } 155 | } 156 | } 157 | 158 | public static bool DecodeBC6(byte[] data, int width, int height, byte[] image) 159 | { 160 | fixed (byte* pData = data) 161 | { 162 | fixed (byte* pImage = image) 163 | { 164 | return DecodeBC6(pData, width, height, pImage); 165 | } 166 | } 167 | } 168 | 169 | public static bool DecodeBC7(byte[] data, int width, int height, byte[] image) 170 | { 171 | fixed (byte* pData = data) 172 | { 173 | fixed (byte* pImage = image) 174 | { 175 | return DecodeBC7(pData, width, height, pImage); 176 | } 177 | } 178 | } 179 | 180 | public static bool DecodeATCRGB4(byte[] data, int width, int height, byte[] image) 181 | { 182 | fixed (byte* pData = data) 183 | { 184 | fixed (byte* pImage = image) 185 | { 186 | return DecodeATCRGB4(pData, width, height, pImage); 187 | } 188 | } 189 | } 190 | 191 | public static bool DecodeATCRGBA8(byte[] data, int width, int height, byte[] image) 192 | { 193 | fixed (byte* pData = data) 194 | { 195 | fixed (byte* pImage = image) 196 | { 197 | return DecodeATCRGBA8(pData, width, height, pImage); 198 | } 199 | } 200 | } 201 | 202 | public static bool DecodeASTC(byte[] data, int width, int height, int blockWidth, int blockHeight, byte[] image) 203 | { 204 | fixed (byte* pData = data) 205 | { 206 | fixed (byte* pImage = image) 207 | { 208 | return DecodeASTC(pData, width, height, blockWidth, blockHeight, pImage); 209 | } 210 | } 211 | } 212 | 213 | public static byte[] UnpackCrunch(byte[] data) 214 | { 215 | void* pBuffer; 216 | uint bufferSize; 217 | 218 | fixed (byte* pData = data) 219 | { 220 | UnpackCrunch(pData, (uint)data.Length, out pBuffer, out bufferSize); 221 | } 222 | 223 | if (pBuffer == null) 224 | { 225 | return null; 226 | } 227 | 228 | var result = new byte[bufferSize]; 229 | 230 | Marshal.Copy(new IntPtr(pBuffer), result, 0, (int)bufferSize); 231 | 232 | DisposeBuffer(ref pBuffer); 233 | 234 | return result; 235 | } 236 | 237 | public static byte[] UnpackUnityCrunch(byte[] data) 238 | { 239 | void* pBuffer; 240 | uint bufferSize; 241 | 242 | fixed (byte* pData = data) 243 | { 244 | UnpackUnityCrunch(pData, (uint)data.Length, out pBuffer, out bufferSize); 245 | } 246 | 247 | if (pBuffer == null) 248 | { 249 | return null; 250 | } 251 | 252 | var result = new byte[bufferSize]; 253 | 254 | Marshal.Copy(new IntPtr(pBuffer), result, 0, (int)bufferSize); 255 | 256 | DisposeBuffer(ref pBuffer); 257 | 258 | return result; 259 | } 260 | 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /AssetStudioExporter.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 0.23.2.0 8 | AnyCPU 9 | x64;x86 10 | true 11 | 12.0 12 | True 13 | True 14 | 15 | 16 | 17 | 18 | PreserveNewest 19 | x64/fmod.dll 20 | 21 | 22 | PreserveNewest 23 | x86/fmod.dll 24 | 25 | 26 | PreserveNewest 27 | x64/libfmod.so 28 | 29 | 30 | PreserveNewest 31 | x86/libfmod.so 32 | 33 | 34 | PreserveNewest 35 | x64/Texture2DDecoderNative.dll 36 | 37 | 38 | PreserveNewest 39 | x86/Texture2DDecoderNative.dll 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /AssetStudioExporter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33801.468 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AssetStudioExporter", "AssetStudioExporter.csproj", "{C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}.Debug|x64.ActiveCfg = Debug|x64 17 | {C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}.Debug|x64.Build.0 = Debug|x64 18 | {C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}.Debug|x86.ActiveCfg = Debug|x86 19 | {C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}.Debug|x86.Build.0 = Debug|x86 20 | {C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}.Release|x64.ActiveCfg = Release|x64 21 | {C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}.Release|x64.Build.0 = Release|x64 22 | {C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}.Release|x86.ActiveCfg = Release|x86 23 | {C08A03AC-E4A4-44B8-8213-736FF3BBE2ED}.Release|x86.Build.0 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {D1D98A9A-CE49-478A-B70B-06FEC46E4A6D} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /AssetTypeManager.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudio; 4 | using AssetStudioExporter.AssetTypes; 5 | using AssetStudioExporter.AssetTypes.Feature; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics.CodeAnalysis; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Text.RegularExpressions; 12 | using System.Threading.Tasks; 13 | using Object = AssetStudioExporter.AssetTypes.Object; 14 | 15 | namespace AssetStudioExporter; 16 | public class AssetTypeManager 17 | { 18 | public string VersionString => Version.ToString(); 19 | public UnityVersion Version { get; } 20 | 21 | readonly AssetsManager am; 22 | public AssetTypeManager(string version, AssetsManager manager) 23 | { 24 | Version = new UnityVersion(version); 25 | am = manager; 26 | } 27 | 28 | public T ReadAsset(AssetTypeValueField valueField) where T : IAssetType 29 | { 30 | var type = T.AssetClassID; 31 | return (T)ReadAsset(type, valueField); 32 | } 33 | 34 | public IAssetType ReadAsset(AssetClassID type, AssetTypeValueField valueField) 35 | { 36 | return type switch 37 | { 38 | AssetClassID.Object => Object.EmptyObject.Read(valueField, Version),//0 39 | AssetClassID.GameObject => GameObject.Read(valueField, Version),//1 40 | AssetClassID.Component => Component.Read(valueField, Version),//2 41 | 42 | AssetClassID.Texture2D => Texture2D.Read(valueField, Version),//28 43 | AssetClassID.TextAsset => TextAsset.Read(valueField, Version),//49 44 | AssetClassID.AudioClip => AudioClip.Read(valueField, Version),//83 45 | AssetClassID.MonoBehaviour => MonoBehaviour.Read(valueField, Version),//114 46 | AssetClassID.MonoScript => MonoScript.Read(valueField, Version),//115 47 | AssetClassID.Font => Font.Read(valueField, Version),//128 48 | 49 | AssetClassID.AssetBundle => AssetBundle.Read(valueField, Version),//142 50 | AssetClassID.ResourceManager => ResourceManager.Read(valueField, Version),//147 51 | 52 | AssetClassID.Sprite => Sprite.Read(valueField, Version),//213 53 | 54 | AssetClassID.SpriteAtlas => SpriteAtlas.Read(valueField, Version),//687078895 55 | 56 | _ => Object.EmptyObject.Read(valueField, Version), 57 | }; 58 | } 59 | 60 | public T ReadObject(AssetTypeValueField valueField) where T : IAssetTypeReader 61 | { 62 | return T.Read(valueField, Version); 63 | } 64 | 65 | public bool TryGetExporter(IAssetType asset, [NotNullWhen(true)]out IAssetTypeExporter? exporter) 66 | { 67 | exporter = null; 68 | if (asset is IAssetTypeExporter exporterSelf) 69 | exporter = exporterSelf; 70 | else if (asset is Sprite sprite) 71 | exporter = sprite.CreateExporter(am); 72 | else 73 | return false; 74 | 75 | return true; 76 | } 77 | 78 | public static bool CanExport() 79 | { 80 | return typeof(T).IsAssignableTo(typeof(IAssetTypeExporter)); 81 | } 82 | 83 | public static bool CanExport(IAssetType asset) 84 | { 85 | return asset.GetType().IsAssignableTo(typeof(IAssetTypeExporter)); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /AssetTypes/AssetBundle.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudioExporter.AssetTypes.Feature; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace AssetStudioExporter.AssetTypes; 11 | 12 | 13 | public class AssetInfo 14 | { 15 | public int preloadIndex; 16 | public int preloadSize; 17 | public PPtr asset; 18 | 19 | public AssetInfo(int preloadIndex, int preloadSize, PPtr asset) 20 | { 21 | this.preloadIndex = preloadIndex; 22 | this.preloadSize = preloadSize; 23 | this.asset = asset; 24 | } 25 | } 26 | 27 | public class AssetBundle : Object, INamedObject, IAssetType, IAssetTypeReader 28 | { 29 | public static AssetClassID AssetClassID { get; } = AssetClassID.AssetBundle; 30 | public string? Name 31 | { 32 | get => m_Name; 33 | set => m_Name = value; 34 | } 35 | 36 | public string? m_Name; 37 | public Dictionary m_Container = new(); 38 | 39 | public static AssetBundle Read(AssetTypeValueField value, UnityVersion version) 40 | { 41 | var ab = new AssetBundle(); 42 | ab.m_Name = value["m_Name"].AsString; 43 | 44 | var m_Container = value.Get("m_Container").Get("Array"); 45 | foreach (var container in m_Container.Children) 46 | { 47 | 48 | var path = container.Get("first").AsString; 49 | var assetInfo = container.Get("second"); 50 | 51 | var preloadIndex = assetInfo.Get("preloadIndex").AsInt; 52 | var preloadSize = assetInfo.Get("preloadSize").AsInt; 53 | var pptr = new PPtr(assetInfo.Get("asset")); 54 | 55 | ab.m_Container[path] = new AssetInfo(preloadIndex, preloadSize, pptr); 56 | } 57 | 58 | return ab; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AssetTypes/AudioClip.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudioExporter.AssetTypes; 4 | using AssetStudioExporter.AssetTypes.Feature; 5 | using AssetStudioExporter.Export; 6 | using AssetStudioExporter.Util; 7 | using System; 8 | using System.Collections.Generic; 9 | using System.IO; 10 | using System.Linq; 11 | using System.Text; 12 | 13 | namespace AssetStudio 14 | { 15 | public sealed class AudioClip : INamedObject, IAssetType, IAssetTypeReader, IAssetTypeExporter 16 | { 17 | 18 | public static AssetClassID AssetClassID { get; } = AssetClassID.AudioClip; 19 | 20 | public string Name 21 | { 22 | get => m_Name; 23 | set => m_Name = value; 24 | } 25 | 26 | public string m_Name; 27 | 28 | 29 | public int m_Format; 30 | public AudioType m_Type; 31 | public bool m_3D; 32 | public bool m_UseHardware; 33 | 34 | //version 5 35 | public int m_LoadType; 36 | public int m_Channels; 37 | public int m_Frequency; 38 | public int m_BitsPerSample; 39 | public float m_Length; 40 | public bool m_IsTrackerFormat; 41 | public int m_SubsoundIndex; 42 | public bool m_PreloadAudioData; 43 | public bool m_LoadInBackground; 44 | public bool m_Legacy3D; 45 | public AudioCompressionFormat m_CompressionFormat; 46 | 47 | public string m_Source; 48 | public long m_Offset; //ulong 49 | public long m_Size; //ulong 50 | 51 | [Obsolete("无法直接读取m_AudioData")] 52 | internal readonly byte[] m_AudioData = Array.Empty(); 53 | 54 | 55 | UnityVersion version; 56 | public byte[] GetAudioData(AssetsFileInstance assetsFile) 57 | { 58 | return assetsFile.GetAssetData(m_Source, (uint)m_Size, m_Offset); 59 | } 60 | 61 | public AudioClip() 62 | { 63 | 64 | } 65 | 66 | public static AudioClip Read(AssetTypeValueField value, UnityVersion version) 67 | { 68 | var ac = new AudioClip(); 69 | ac.version = version; 70 | 71 | ac.m_Name = value["m_Name"].AsString; 72 | ac.m_LoadType = value["m_LoadType"].AsInt; 73 | ac.m_Channels = value["m_Channels"].AsInt; 74 | ac.m_Frequency = value["m_Frequency"].AsInt; 75 | ac.m_BitsPerSample = value["m_BitsPerSample"].AsInt; 76 | ac.m_Length = value["m_Length"].AsFloat; 77 | ac.m_IsTrackerFormat = value["m_IsTrackerFormat"].AsBool; 78 | //ac.m_Ambisonic = bf["m_Ambisonic"].AsBool; 79 | ac.m_SubsoundIndex = value["m_SubsoundIndex"].AsInt; 80 | ac.m_PreloadAudioData = value["m_PreloadAudioData"].AsBool; 81 | ac.m_LoadInBackground = value["m_LoadInBackground"].AsBool; 82 | ac.m_Legacy3D = value["m_Legacy3D"].AsBool; 83 | ac.m_CompressionFormat = (AudioCompressionFormat)value["m_CompressionFormat"].AsInt; 84 | 85 | var m_Resource = value["m_Resource"]; 86 | if (!m_Resource.IsDummy) 87 | { 88 | ac.m_Source = m_Resource["m_Source"].AsString; 89 | ac.m_Offset = (long)m_Resource["m_Offset"].AsULong; 90 | ac.m_Size = (long)m_Resource["m_Size"].AsULong; 91 | } 92 | return ac; 93 | } 94 | 95 | public bool Export(AssetsFileInstance assetsFile, Stream stream) 96 | { 97 | var m_AudioData = GetAudioData(assetsFile); 98 | var converter = new AudioClipConverter(this, version.ToIntArray()); 99 | var wav = converter.ConvertToWav(m_AudioData); 100 | if (wav is null) 101 | { 102 | Console.WriteLine($"Export audio clip '{m_Name}' failed"); 103 | return false; 104 | } 105 | stream.Write(wav); 106 | return true; 107 | } 108 | 109 | public string GetFileExtension(string fileName) 110 | { 111 | return ".wav"; 112 | } 113 | } 114 | 115 | public enum AudioType 116 | { 117 | UNKNOWN, 118 | ACC, 119 | AIFF, 120 | IT = 10, 121 | MOD = 12, 122 | MPEG, 123 | OGGVORBIS, 124 | S3M = 17, 125 | WAV = 20, 126 | XM, 127 | XMA, 128 | VAG, 129 | AUDIOQUEUE 130 | } 131 | 132 | public enum AudioCompressionFormat 133 | { 134 | PCM, 135 | Vorbis, 136 | ADPCM, 137 | MP3, 138 | VAG, 139 | HEVAG, 140 | XMA, 141 | AAC, 142 | GCADPCM, 143 | ATRAC9 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /AssetTypes/Component.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | 4 | namespace AssetStudioExporter.AssetTypes 5 | { 6 | public class Component : Object, IAssetType, IAssetTypeReader 7 | { 8 | public static AssetClassID AssetClassID { get; } = AssetClassID.Component; 9 | 10 | public PPtr m_GameObject; 11 | 12 | public Component(PPtr gameObject) 13 | { 14 | m_GameObject = gameObject; 15 | } 16 | 17 | public static Component Read(AssetTypeValueField value, UnityVersion version) 18 | { 19 | var pptr = new PPtr(value); 20 | return new Component(pptr); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AssetTypes/Feature/IAssetTypeExporter.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET.Extra; 2 | 3 | namespace AssetStudioExporter.AssetTypes.Feature; 4 | 5 | public interface IAssetTypeExporter where TSetting : class 6 | { 7 | bool Export(AssetsFileInstance assetsFile, Stream stream, TSetting setting); 8 | } 9 | 10 | public interface IAssetTypeExporter 11 | { 12 | /// 13 | /// 导出当前AssetType 14 | /// 15 | /// AssetsFile 16 | /// 要输出的流 17 | /// 是否导出成功 18 | bool Export(AssetsFileInstance assetsFile, Stream stream); 19 | 20 | 21 | void Export(AssetsFileInstance assetsFile, string fileName) 22 | { 23 | using (var fs = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.Write)) 24 | { 25 | if (Export(assetsFile, fs)) 26 | { 27 | return; 28 | } 29 | } 30 | // 未能导出,删掉空文件 31 | File.Delete(fileName); 32 | } 33 | 34 | string GetFileExtension(string fileName); 35 | } 36 | 37 | 38 | public interface IFormatAssetTypeExporter 39 | { 40 | bool Export(AssetsFileInstance assetsFile, Stream stream, TFormat format); 41 | } 42 | 43 | public interface IMultipleFileAssetTypeExporter 44 | { 45 | bool Export(AssetsFileInstance assetsFile, DirectoryInfo outputDir); 46 | } 47 | -------------------------------------------------------------------------------- /AssetTypes/Feature/IAssetTypeReader.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AssetStudioExporter.AssetTypes 10 | { 11 | public interface IAssetTypeReader 12 | { 13 | /// 14 | /// 根据创建类型的AssetType 15 | /// 16 | /// 序列化的 17 | /// 类型的AssetType 18 | static abstract T Read(AssetTypeValueField value, UnityVersion version); 19 | 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /AssetTypes/Feature/INamedObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace AssetStudioExporter.AssetTypes.Feature; 4 | 5 | 6 | /// 7 | /// 有名称的对象 8 | /// 9 | public interface INamedObject 10 | { 11 | /// 12 | /// 对象的名称 13 | /// 14 | public string? Name { get; set; } 15 | 16 | } 17 | 18 | 19 | public interface IStructureExportable 20 | { 21 | /// 22 | /// 返回一个可以用于序列化JSON的动态对象 23 | /// 24 | dynamic? DeserializeStructure(); 25 | 26 | /// 27 | /// 序列化为指定的对象 28 | /// 29 | /// 对象的类型 30 | object DeserializeObject(Type type); 31 | } -------------------------------------------------------------------------------- /AssetTypes/Font.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudio; 4 | using AssetStudioExporter.AssetTypes.Feature; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace AssetStudioExporter.AssetTypes 12 | { 13 | public class Font : INamedObject, IAssetType, IAssetTypeReader, IAssetTypeExporter 14 | { 15 | public static AssetClassID AssetClassID { get; } = AssetClassID.Font; 16 | public string Name 17 | { 18 | get => m_Name; 19 | set => m_Name = value; 20 | } 21 | 22 | public string m_Name; 23 | public byte[] m_FontData = Array.Empty(); 24 | 25 | public static Font Read(AssetTypeValueField value, UnityVersion Version) 26 | { 27 | var f = new Font(); 28 | 29 | f.m_Name = value["m_Name"].AsString; 30 | // 其它字段省略,因为整个字体文件都在m_FontData里面 31 | 32 | // WARN: 高内存占用警告!因为Unity的蜜汁类型定义不能直接返回byte[],产生巨量包装类 33 | var arr = value["m_FontData"].Get("Array"); 34 | var length = arr.Children.Count; 35 | if (length > 0) 36 | { 37 | f.m_FontData = new byte[length]; 38 | for (int i = 0; i < length; i++) 39 | { 40 | f.m_FontData[i] = arr.Children[i].AsByte; 41 | } 42 | } 43 | return f; 44 | } 45 | 46 | public string GetFileExtension(string name) 47 | { 48 | if (m_FontData[0..4].SequenceEqual(Encoding.ASCII.GetBytes("OTTO"))) 49 | { 50 | return ".otf"; 51 | } else 52 | { 53 | return ".ttf"; 54 | } 55 | } 56 | 57 | public bool Export(AssetsFileInstance assetsFile, Stream stream) 58 | { 59 | if (m_FontData.Length == 0) 60 | { 61 | Console.WriteLine($"[WARN] Font '{m_Name}' doesn't have any data, skipped"); 62 | return false; 63 | } 64 | stream.Write(m_FontData); 65 | return true; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /AssetTypes/GameObject.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Diagnostics.Metrics; 5 | 6 | namespace AssetStudioExporter.AssetTypes 7 | { 8 | public class GameObject : Object, IAssetType, IAssetTypeReader 9 | { 10 | public static AssetClassID AssetClassID { get; } = AssetClassID.GameObject; 11 | 12 | public string m_Name; 13 | public IList> m_Components; 14 | 15 | 16 | 17 | 18 | public static GameObject Read(AssetTypeValueField value, UnityVersion version) 19 | { 20 | var gameObject = new GameObject(); 21 | 22 | gameObject.m_Name = value["m_Name"].AsString; 23 | gameObject.m_Components = new List>(); 24 | 25 | var components = value["m_Component"]["Array"]; 26 | foreach (var comp in components.Children) 27 | { 28 | var childPtr = comp["component"]; 29 | gameObject.m_Components.Add(new PPtr(childPtr)); 30 | } 31 | 32 | return gameObject; 33 | } 34 | 35 | public MonoBehaviourCollection GetAllMonoBehaviours(AssetsFileInstance inst, AssetsManager am) 36 | { 37 | var ret = new MonoBehaviourCollection(); 38 | foreach (var componentPtr in m_Components) 39 | { 40 | var componentRef = componentPtr.GetAssetReference(inst, am, true); 41 | var childInfo = componentRef.info; 42 | 43 | if (childInfo.TypeId != (int)AssetClassID.MonoBehaviour) continue; 44 | 45 | var monoBehaviourPtr = componentPtr.Cast(); 46 | monoBehaviourPtr.FindInstance(inst, am); 47 | 48 | var mb = monoBehaviourPtr.Instance; 49 | mb.AssetInfo = childInfo; 50 | 51 | mb.m_Script.FindInstance(inst, am); 52 | 53 | ret.Add(mb); 54 | 55 | } 56 | return ret; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /AssetTypes/IAssetType.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET.Extra; 2 | 3 | namespace AssetStudioExporter.AssetTypes 4 | { 5 | public interface IAssetType 6 | { 7 | static abstract AssetClassID AssetClassID { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AssetTypes/MonoBehaviour.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudio; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace AssetStudioExporter.AssetTypes 11 | { 12 | 13 | public class MonoBehaviour : IAssetType, IAssetTypeReader 14 | { 15 | public static AssetClassID AssetClassID { get; } = AssetClassID.MonoBehaviour; 16 | 17 | public PPtr m_Script; 18 | 19 | public string m_Name; 20 | 21 | public AssetFileInfo? AssetInfo { get; internal set; } 22 | 23 | public MonoBehaviour(string name, PPtr monoScript) 24 | { 25 | m_Name = name; 26 | m_Script = monoScript; 27 | } 28 | 29 | public static MonoBehaviour Read(AssetTypeValueField value, UnityVersion Version) 30 | { 31 | var name = value["m_Name"].AsString; 32 | var script = new PPtr(value["m_Script"]); 33 | return new MonoBehaviour(name, script); 34 | } 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /AssetTypes/MonoBehaviourCollection.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | 3 | namespace AssetStudioExporter.AssetTypes 4 | { 5 | public class MonoBehaviourCollection : List 6 | { 7 | public MonoBehaviour? GetMonoBehaviourByClassName(string fullClassName) 8 | { 9 | return this.FirstOrDefault(m => 10 | { 11 | var script = m.m_Script.Instance ?? 12 | throw new InvalidOperationException("The PPtr doesn't have an instance"); 13 | 14 | return script.FullName == fullClassName; 15 | }); 16 | } 17 | 18 | public MonoBehaviour? GetMonoBehaviourByClassName(string @namespace, string className) 19 | { 20 | return GetMonoBehaviourByClassName(@namespace + "." + className); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /AssetTypes/MonoScript.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using System.Globalization; 4 | 5 | namespace AssetStudioExporter.AssetTypes 6 | { 7 | public class MonoScript : IAssetType, IAssetTypeReader 8 | { 9 | public static AssetClassID AssetClassID { get; } = AssetClassID.MonoScript; 10 | 11 | public string m_ClassName; 12 | 13 | public string m_Namespace; 14 | 15 | public string m_AssemblyName; 16 | 17 | public string FullName 18 | { 19 | get 20 | { 21 | var ns = string.IsNullOrEmpty(m_Namespace) ? "" : (m_Namespace + "."); 22 | return ns + m_ClassName; 23 | } 24 | } 25 | 26 | public MonoScript(string assembly, string className, string @namespace = "") 27 | { 28 | m_AssemblyName = assembly; 29 | m_ClassName = className; 30 | m_Namespace = @namespace; 31 | } 32 | 33 | public static MonoScript Read(AssetTypeValueField value, UnityVersion Version) 34 | { 35 | var className = value["m_ClassName"].AsString; 36 | var ns = value["m_Namespace"].AsString ?? ""; 37 | var asm = value["m_AssemblyName"].AsString; 38 | return new MonoScript(asm, className, ns); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /AssetTypes/Object.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AssetStudioExporter.AssetTypes 10 | { 11 | public abstract class Object 12 | { 13 | public static Object Create() => new EmptyObject(); 14 | 15 | internal class EmptyObject : Object, IAssetType, IAssetTypeReader 16 | { 17 | public static AssetClassID AssetClassID { get; } = AssetClassID.Object; 18 | public static EmptyObject Read(AssetTypeValueField value, UnityVersion version) 19 | { 20 | return new EmptyObject(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /AssetTypes/PPtr.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudio; 4 | using AssetStudioExporter.Internal; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Diagnostics.CodeAnalysis; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace AssetStudioExporter.AssetTypes; 13 | 14 | public class PPtr : IAssetTypeReader> where T : class 15 | { 16 | 17 | public int m_FileID; 18 | public long m_PathID; 19 | 20 | public T? Instance { get; set; } 21 | 22 | public PPtr(int fileID, long pathID) 23 | { 24 | m_FileID = fileID; 25 | m_PathID = pathID; 26 | } 27 | 28 | public PPtr(AssetTypeValueField value) 29 | { 30 | m_FileID = value["m_FileID"].AsInt; 31 | m_PathID = value["m_PathID"].AsLong; 32 | } 33 | 34 | public static implicit operator AssetPPtr(PPtr self) 35 | { 36 | return new AssetPPtr(self.m_FileID, self.m_PathID); 37 | } 38 | 39 | public static explicit operator PPtr(AssetPPtr pptr) 40 | { 41 | return new PPtr(pptr.FileId, pptr.PathId); 42 | } 43 | 44 | /// 45 | /// 追随的引用,并返回目标类型的 46 | /// 47 | /// 相对的AssetsFile 48 | /// 所用的AssetsManager 49 | /// 只获取信息 50 | public AssetTypeValueField FollowReference(AssetsFileInstance relativeFile, AssetsManager am, bool onlyGetInfo = false) 51 | { 52 | return GetAssetReference(relativeFile, am).baseField; 53 | } 54 | 55 | /// 56 | /// 查找的引用并将设置为实例 57 | /// 58 | /// 类型的读取器 59 | /// 相对的AssetsFile 60 | /// 所用的AssetsManager 61 | [MemberNotNull(nameof(Instance))] 62 | public void FindInstance(AssetsFileInstance relativeFile, AssetsManager am) 63 | where R : class, T, IAssetTypeReader 64 | { 65 | 66 | try 67 | { 68 | var bf = FollowReference(relativeFile, am, false); 69 | if (bf is null) 70 | { 71 | throw new InvalidDataException($"PPtr {this} doesn't exists"); 72 | } 73 | var version = relativeFile.GetVersion(); 74 | Instance = R.Read(bf, version); 75 | return; 76 | } 77 | catch (Exception ex) 78 | { 79 | throw new InvalidDataException($"Cannot read PPtr {this}", ex); 80 | } 81 | 82 | } 83 | 84 | public bool TryFindInstance([NotNullWhen(true)]out T? instance, AssetsFileInstance relativeFile, AssetsManager am) 85 | where R : class, T, IAssetTypeReader 86 | { 87 | try 88 | { 89 | FindInstance(relativeFile, am); 90 | instance = Instance; 91 | return instance is not null; 92 | } 93 | catch (Exception) 94 | { 95 | instance = null; 96 | return false; 97 | } 98 | } 99 | 100 | /// 101 | /// 类似,但返回 102 | /// 103 | public AssetExternal GetAssetReference(AssetsFileInstance relativeFile, AssetsManager am, bool onlyGetInfo = false) 104 | { 105 | return am.GetExtAsset(relativeFile, m_FileID, m_PathID, onlyGetInfo); 106 | } 107 | 108 | /// 109 | /// 将引用的类型重新解读为类型
110 | /// 如果有值会丢弃 111 | ///
112 | /// 新的类型 113 | public PPtr Cast() where U : class, IAssetTypeReader 114 | { 115 | return new PPtr(m_FileID, m_PathID); 116 | } 117 | 118 | public override string ToString() 119 | { 120 | return $"{{ FileID = {m_FileID}, PathID = {m_PathID} }}"; 121 | } 122 | 123 | public static PPtr Read(AssetTypeValueField value, UnityVersion version) 124 | { 125 | return Read(value); 126 | } 127 | public static PPtr Read(AssetTypeValueField value) 128 | { 129 | return new PPtr(value); 130 | } 131 | } 132 | 133 | 134 | public class PPtr : PPtr 135 | { 136 | public PPtr(AssetTypeValueField value) : base(value) 137 | { 138 | } 139 | 140 | public PPtr(int fileID, long pathID) : base(fileID, pathID) 141 | { 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /AssetTypes/ResourceManager.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AssetStudioExporter.AssetTypes; 10 | 11 | 12 | public class ResourceManager : IAssetType, IAssetTypeReader 13 | { 14 | public static AssetClassID AssetClassID { get; } = AssetClassID.ResourceManager; 15 | 16 | 17 | public Dictionary> m_Container = new(); 18 | 19 | public static ResourceManager Read(AssetTypeValueField value, UnityVersion version) 20 | { 21 | var rm = new ResourceManager(); 22 | 23 | var m_Container = value.Get("m_Container").Get("Array"); 24 | foreach (var container in m_Container.Children) 25 | { 26 | 27 | var path = container.Get("first").AsString; 28 | var assetInfo = container.Get("second"); 29 | 30 | rm.m_Container[path] = new PPtr(assetInfo); 31 | } 32 | 33 | return rm; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /AssetTypes/Sprite.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudio; 4 | using AssetStudioExporter.AssetTypes.Feature; 5 | using AssetStudioExporter.AssetTypes.ValueObject; 6 | using AssetStudioExporter.Export; 7 | using AssetStudioExporter.Util; 8 | using SixLabors.ImageSharp; 9 | using System.Reflection.PortableExecutable; 10 | 11 | namespace AssetStudioExporter.AssetTypes; 12 | 13 | public class Sprite : INamedObject, IAssetType, IAssetTypeReader 14 | { 15 | public static AssetClassID AssetClassID { get; } = AssetClassID.Sprite; 16 | 17 | public string Name 18 | { 19 | get => m_Name; 20 | set => m_Name = value; 21 | } 22 | 23 | public string m_Name; 24 | public Rectf m_Rect; 25 | public Vector2 m_Offset; 26 | public Vector4 m_Border; 27 | public float m_PixelsToUnits; 28 | public Vector2 m_Pivot = new Vector2(0.5f, 0.5f); 29 | public uint m_Extrude; 30 | public bool m_IsPolygon; 31 | public KeyValuePair m_RenderDataKey; 32 | public string[] m_AtlasTags; 33 | public PPtr m_SpriteAtlas; 34 | public SpriteRenderData m_RD; 35 | public List m_PhysicsShape; 36 | /// 37 | /// 不知道是什么 38 | /// 39 | public List m_Bones; 40 | 41 | 42 | public static Sprite Read(AssetTypeValueField value, UnityVersion version) 43 | { 44 | var s = new Sprite(); 45 | s.m_Name = value["m_Name"].AsString; 46 | s.m_Rect = Rectf.Read(value["m_Rect"]); 47 | s.m_Border = value["m_Border"].AsVector4(); 48 | s.m_PixelsToUnits = value["m_PixelsToUnits"].AsFloat; 49 | s.m_Pivot = value["m_Pivot"].AsVector2(); 50 | s.m_Extrude = value["m_Extrude"].AsUInt; 51 | s.m_IsPolygon = value["m_IsPolygon"].AsBool; 52 | 53 | if (version.major >= 2017) {//2017 and up 54 | var m_RenderDataKey = value["m_RenderDataKey"]; 55 | var first = m_RenderDataKey["first"].AsUnityGUID(); 56 | var second = m_RenderDataKey["second"].AsLong; 57 | s.m_RenderDataKey = new KeyValuePair(first, second); 58 | 59 | s.m_AtlasTags = value["m_AtlasTags"] 60 | .AsArray(t => t.AsString); 61 | 62 | s.m_SpriteAtlas = PPtr.Read(value["m_SpriteAtlas"]); 63 | } 64 | 65 | s.m_RD = SpriteRenderData.Read(value["m_RD"], version); 66 | 67 | if (version.major >= 2017) { 68 | s.m_PhysicsShape = value["m_PhysicsShape"] 69 | .AsList(s => s.AsArray(v => v.AsVector2())); 70 | } 71 | 72 | if (version.major >= 2018) { //2018 and up 73 | s.m_Bones = value["m_Bones"].AsList(b => (object)b.ToString()); 74 | } 75 | 76 | return s; 77 | } 78 | 79 | 80 | 81 | private class SpriteExporter(Sprite sprite, AssetsManager am) : IAssetTypeExporter 82 | { 83 | public bool Export(AssetsFileInstance assetsFile, Stream stream) 84 | { 85 | var format = ExporterSetting.Default.ImageExportFormat; 86 | var helper = new SpriteHelper(assetsFile, am); 87 | 88 | var image = helper.GetImage(sprite); 89 | if (image is null) 90 | { 91 | return false; 92 | } 93 | 94 | using (image) 95 | { 96 | switch (format) 97 | { 98 | case ImageFormat.Jpeg: 99 | image.SaveAsJpeg(stream); 100 | break; 101 | case ImageFormat.Webp: 102 | image.SaveAsWebp(stream); 103 | break; 104 | case ImageFormat.Tga: 105 | image.SaveAsTga(stream); 106 | break; 107 | case ImageFormat.Tiff: 108 | image.SaveAsTiff(stream); 109 | break; 110 | case ImageFormat.Bmp: 111 | image.SaveAsBmp(stream); 112 | break; 113 | case ImageFormat.Gif: 114 | image.SaveAsGif(stream); 115 | break; 116 | default: 117 | image.SaveAsPng(stream); 118 | break; 119 | } 120 | } 121 | return true; 122 | } 123 | 124 | public string GetFileExtension(string fileName) 125 | { 126 | if (ExporterSetting.Default.ImageExportFormat == ImageFormat.Auto) 127 | return ".png"; 128 | return "." + ExporterSetting.Default.ImageExportFormat.ToString().ToLower(); 129 | } 130 | } 131 | 132 | public IAssetTypeExporter CreateExporter(AssetsManager am) 133 | { 134 | return new SpriteExporter(this, am); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /AssetTypes/SpriteAtlas.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudioExporter.AssetTypes.Feature; 4 | using AssetStudioExporter.AssetTypes.ValueObject; 5 | using AssetStudioExporter.Util; 6 | using System.Runtime.InteropServices; 7 | 8 | namespace AssetStudioExporter.AssetTypes; 9 | 10 | public class SpriteAtlas : INamedObject, IAssetType, IAssetTypeReader 11 | { 12 | public static AssetClassID AssetClassID { get; } = AssetClassID.SpriteAtlas; 13 | 14 | public string Name 15 | { 16 | get => m_Name; 17 | set => m_Name = value; 18 | } 19 | 20 | public string m_Name; 21 | public List> m_PackedSprites; 22 | public List m_PackedSpriteNamesToIndex; 23 | public Dictionary, SpriteAtlasData> m_RenderDataMap; 24 | public string m_Tag; 25 | public bool m_IsVariant; 26 | 27 | public static SpriteAtlas Read(AssetTypeValueField value, UnityVersion version) 28 | { 29 | var sa = new SpriteAtlas(); 30 | 31 | sa.m_Name = value["m_Name"].AsString; 32 | sa.m_PackedSprites = value["m_PackedSprites"] 33 | .AsList(sprite => new PPtr(sprite)); 34 | sa.m_PackedSpriteNamesToIndex = value["m_PackedSpriteNamesToIndex"] 35 | .AsList(name => name.AsString); 36 | 37 | sa.m_RenderDataMap = new(); 38 | var renderDataMap = value["m_RenderDataMap"]["Array"]; 39 | foreach (var pair in renderDataMap.Children) 40 | { 41 | var key = pair["first"]; 42 | var guid = key["first"].AsUnityGUID(); 43 | var id = key["second"].AsLong; 44 | 45 | var v = SpriteAtlasData.Read(pair["second"], version); 46 | 47 | sa.m_RenderDataMap[new(guid, id)] = v; 48 | } 49 | 50 | sa.m_Tag = value["m_Tag"].AsString; 51 | sa.m_IsVariant = value["m_IsVariant"].AsBool; 52 | 53 | return sa; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /AssetTypes/TextAsset.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudioExporter.AssetTypes.Feature; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace AssetStudioExporter.AssetTypes 11 | { 12 | public class TextAsset : IAssetType, IAssetTypeReader, IAssetTypeExporter 13 | { 14 | public static AssetClassID AssetClassID { get; } = AssetClassID.TextAsset; 15 | 16 | public byte[] m_Script; 17 | 18 | public static TextAsset Read(AssetTypeValueField value, UnityVersion version) 19 | { 20 | var textAsset = new TextAsset(); 21 | 22 | var str = value.Get("m_Script"); 23 | textAsset.m_Script = str.AsByteArray; 24 | 25 | return textAsset; 26 | } 27 | 28 | public string GetFileExtension(string name) 29 | { 30 | var ext = Path.GetExtension(name); 31 | if (!string.IsNullOrEmpty(ext)) 32 | { 33 | return ext; 34 | } 35 | return ".txt"; 36 | } 37 | 38 | public bool Export(AssetsFileInstance assetsFile, Stream stream) 39 | { 40 | stream.Write(m_Script); 41 | return true; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AssetTypes/Texture2D.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetsTools.NET.Texture; 4 | using AssetStudio; 5 | using AssetStudioExporter.AssetTypes.Feature; 6 | using AssetStudioExporter.Export; 7 | using AssetStudioExporter.Util; 8 | using SixLabors.ImageSharp; 9 | using SixLabors.ImageSharp.PixelFormats; 10 | using SixLabors.ImageSharp.Processing; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Text; 15 | using System.Threading.Tasks; 16 | 17 | namespace AssetStudioExporter.AssetTypes 18 | { 19 | public class Texture2D : TextureFile, IAssetType, IAssetTypeReader, IAssetTypeExporter 20 | { 21 | public static AssetClassID AssetClassID { get; } = AssetClassID.Texture2D; 22 | 23 | public string Name 24 | { 25 | get => m_Name; 26 | set => m_Name = value; 27 | } 28 | 29 | new TextureFormat m_TextureFormat; 30 | 31 | public Texture2D() { } 32 | public Texture2D(TextureFile textureFile) 33 | { 34 | // 自动生成,复制构造函数 35 | m_Name = textureFile.m_Name; 36 | m_ForcedFallbackFormat = textureFile.m_ForcedFallbackFormat; 37 | m_DownscaleFallback = textureFile.m_DownscaleFallback; 38 | m_Width = textureFile.m_Width; 39 | m_Height = textureFile.m_Height; 40 | m_CompleteImageSize = textureFile.m_CompleteImageSize; 41 | m_TextureFormat = (TextureFormat)textureFile.m_TextureFormat; 42 | m_MipCount = textureFile.m_MipCount; 43 | m_MipMap = textureFile.m_MipMap; 44 | m_IsReadable = textureFile.m_IsReadable; 45 | m_ReadAllowed = textureFile.m_ReadAllowed; 46 | m_StreamingMipmaps = textureFile.m_StreamingMipmaps; 47 | m_StreamingMipmapsPriority = textureFile.m_StreamingMipmapsPriority; 48 | m_ImageCount = textureFile.m_ImageCount; 49 | m_TextureDimension = textureFile.m_TextureDimension; 50 | m_TextureSettings = textureFile.m_TextureSettings; 51 | m_LightmapFormat = textureFile.m_LightmapFormat; 52 | m_ColorSpace = textureFile.m_ColorSpace; 53 | m_StreamData = textureFile.m_StreamData; 54 | 55 | pictureData = textureFile.pictureData; 56 | } 57 | 58 | private UnityVersion version; 59 | public static Texture2D Read(AssetTypeValueField value, UnityVersion version) 60 | { 61 | var t = new Texture2D(ReadTextureFile(value)) 62 | { 63 | version = version 64 | }; 65 | return t; 66 | } 67 | 68 | public bool Export(AssetsFileInstance assetsFile, Stream stream) 69 | { 70 | return Export(assetsFile, stream, ExporterSetting.Default.ImageExportFormat); 71 | } 72 | 73 | public byte[]? GetAssetData(AssetsFileInstance assetsFile) 74 | { 75 | byte[] rawdata; 76 | if (pictureData?.Length > 0) 77 | { 78 | rawdata = pictureData; 79 | } 80 | else 81 | { 82 | var path = m_StreamData.path; 83 | var size = m_StreamData.size; 84 | var offset = (long)m_StreamData.offset; 85 | 86 | if (size == 0 || string.IsNullOrEmpty(path)) 87 | { 88 | Console.WriteLine($"[WARN] Texture2D '{m_Name}' doesn't have any data, skipped"); 89 | return null; 90 | } 91 | 92 | rawdata = assetsFile.GetAssetData(path, size, offset); 93 | } 94 | return rawdata; 95 | } 96 | 97 | public bool Export(AssetsFileInstance assetsFile, Stream stream, ImageFormat format) 98 | { 99 | var image = GetImage(assetsFile); 100 | if (image is null) 101 | { 102 | return false; 103 | } 104 | 105 | using(image) 106 | { 107 | switch (format) 108 | { 109 | case ImageFormat.Jpeg: 110 | image.SaveAsJpeg(stream); 111 | break; 112 | case ImageFormat.Webp: 113 | image.SaveAsWebp(stream); 114 | break; 115 | case ImageFormat.Tga: 116 | image.SaveAsTga(stream); 117 | break; 118 | case ImageFormat.Tiff: 119 | image.SaveAsTiff(stream); 120 | break; 121 | case ImageFormat.Bmp: 122 | image.SaveAsBmp(stream); 123 | break; 124 | case ImageFormat.Gif: 125 | image.SaveAsGif(stream); 126 | break; 127 | default: 128 | image.SaveAsPng(stream); 129 | break; 130 | } 131 | } 132 | return true; 133 | } 134 | 135 | 136 | public Image? GetImage(AssetsFileInstance assetsFile, bool flip = true) 137 | { 138 | var rawdata = GetAssetData(assetsFile); 139 | if (rawdata is null || rawdata.Length == 0) 140 | { 141 | return null; 142 | } 143 | 144 | if (ExporterSetting.Default.TextureDecoder == TextureDecoderType.AssetStudio) 145 | { 146 | return ConvertToImage(rawdata, flip); 147 | } 148 | else 149 | { 150 | var decoded = DecodeManaged(rawdata, m_TextureFormat, m_Width, m_Height); 151 | return ConvertToImageFromDecoded(decoded, flip); 152 | } 153 | } 154 | 155 | Image? ConvertToImage(byte[] data, bool flip = true) 156 | { 157 | var converter = new Texture2DConverter(this, version); 158 | var buff = BigArrayPool.Shared.Rent(m_Width * m_Height * 4); 159 | try 160 | { 161 | if (converter.DecodeTexture2D(data, buff)) 162 | { 163 | var image = Image.LoadPixelData(buff, m_Width, m_Height); 164 | if (flip) 165 | { 166 | image.Mutate(x => x.Flip(FlipMode.Vertical)); 167 | } 168 | return image; 169 | } 170 | return null; 171 | } 172 | finally 173 | { 174 | BigArrayPool.Shared.Return(buff); 175 | } 176 | } 177 | 178 | Image ConvertToImageFromDecoded(byte[] decoded, bool flip = true) 179 | { 180 | var image = Image.LoadPixelData(decoded, m_Width, m_Height); 181 | if (flip) 182 | { 183 | image.Mutate(x => x.Flip(FlipMode.Vertical)); 184 | } 185 | return image; 186 | } 187 | 188 | public string GetFileExtension(string fileName) 189 | { 190 | if (ExporterSetting.Default.ImageExportFormat == ImageFormat.Auto) 191 | return ".png"; 192 | return "." + ExporterSetting.Default.ImageExportFormat.ToString().ToLower(); 193 | } 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /AssetTypes/ValueObject/AABB.cs: -------------------------------------------------------------------------------- 1 | namespace AssetStudio; 2 | 3 | // 该类型从AnimationClip.cs中剥离并扩充 4 | public struct AABB 5 | { 6 | public Vector3 m_Center; 7 | public Vector3 m_Extent; 8 | 9 | public AABB(Vector3 center, Vector3 extent) 10 | { 11 | m_Center = center; 12 | m_Extent = extent; 13 | } 14 | } -------------------------------------------------------------------------------- /AssetTypes/ValueObject/MeshHelper.cs: -------------------------------------------------------------------------------- 1 | using AssetStudio; 2 | using System.Buffers.Binary; 3 | using Half = AssetStudio.Half; 4 | 5 | namespace AssetStudioExporter.AssetTypes.ValueObject; 6 | 7 | public static class MeshHelper 8 | { 9 | public enum VertexChannelFormat 10 | { 11 | Float, 12 | Float16, 13 | Color, 14 | Byte, 15 | UInt32 16 | } 17 | 18 | public enum VertexFormat2017 19 | { 20 | Float, 21 | Float16, 22 | Color, 23 | UNorm8, 24 | SNorm8, 25 | UNorm16, 26 | SNorm16, 27 | UInt8, 28 | SInt8, 29 | UInt16, 30 | SInt16, 31 | UInt32, 32 | SInt32 33 | } 34 | 35 | public enum VertexFormat 36 | { 37 | Float, 38 | Float16, 39 | UNorm8, 40 | SNorm8, 41 | UNorm16, 42 | SNorm16, 43 | UInt8, 44 | SInt8, 45 | UInt16, 46 | SInt16, 47 | UInt32, 48 | SInt32 49 | } 50 | 51 | public static VertexFormat ToVertexFormat(int format, int[] version) 52 | { 53 | if (version[0] < 2017) 54 | { 55 | switch ((VertexChannelFormat)format) 56 | { 57 | case VertexChannelFormat.Float: 58 | return VertexFormat.Float; 59 | case VertexChannelFormat.Float16: 60 | return VertexFormat.Float16; 61 | case VertexChannelFormat.Color: //in 4.x is size 4 62 | return VertexFormat.UNorm8; 63 | case VertexChannelFormat.Byte: 64 | return VertexFormat.UInt8; 65 | case VertexChannelFormat.UInt32: //in 5.x 66 | return VertexFormat.UInt32; 67 | default: 68 | throw new ArgumentOutOfRangeException(nameof(format), format, null); 69 | } 70 | } 71 | else if (version[0] < 2019) 72 | { 73 | switch ((VertexFormat2017)format) 74 | { 75 | case VertexFormat2017.Float: 76 | return VertexFormat.Float; 77 | case VertexFormat2017.Float16: 78 | return VertexFormat.Float16; 79 | case VertexFormat2017.Color: 80 | case VertexFormat2017.UNorm8: 81 | return VertexFormat.UNorm8; 82 | case VertexFormat2017.SNorm8: 83 | return VertexFormat.SNorm8; 84 | case VertexFormat2017.UNorm16: 85 | return VertexFormat.UNorm16; 86 | case VertexFormat2017.SNorm16: 87 | return VertexFormat.SNorm16; 88 | case VertexFormat2017.UInt8: 89 | return VertexFormat.UInt8; 90 | case VertexFormat2017.SInt8: 91 | return VertexFormat.SInt8; 92 | case VertexFormat2017.UInt16: 93 | return VertexFormat.UInt16; 94 | case VertexFormat2017.SInt16: 95 | return VertexFormat.SInt16; 96 | case VertexFormat2017.UInt32: 97 | return VertexFormat.UInt32; 98 | case VertexFormat2017.SInt32: 99 | return VertexFormat.SInt32; 100 | default: 101 | throw new ArgumentOutOfRangeException(nameof(format), format, null); 102 | } 103 | } 104 | else 105 | { 106 | return (VertexFormat)format; 107 | } 108 | } 109 | 110 | 111 | public static uint GetFormatSize(VertexFormat format) 112 | { 113 | switch (format) 114 | { 115 | case VertexFormat.Float: 116 | case VertexFormat.UInt32: 117 | case VertexFormat.SInt32: 118 | return 4u; 119 | case VertexFormat.Float16: 120 | case VertexFormat.UNorm16: 121 | case VertexFormat.SNorm16: 122 | case VertexFormat.UInt16: 123 | case VertexFormat.SInt16: 124 | return 2u; 125 | case VertexFormat.UNorm8: 126 | case VertexFormat.SNorm8: 127 | case VertexFormat.UInt8: 128 | case VertexFormat.SInt8: 129 | return 1u; 130 | default: 131 | throw new ArgumentOutOfRangeException(nameof(format), format, null); 132 | } 133 | } 134 | 135 | public static bool IsIntFormat(VertexFormat format) 136 | { 137 | return format >= VertexFormat.UInt8; 138 | } 139 | 140 | public static float[] BytesToFloatArray(byte[] inputBytes, VertexFormat format) 141 | { 142 | var size = GetFormatSize(format); 143 | var len = inputBytes.Length / size; 144 | var result = new float[len]; 145 | for (int i = 0; i < len; i++) 146 | { 147 | switch (format) 148 | { 149 | case VertexFormat.Float: 150 | result[i] = BinaryPrimitives.ReadSingleLittleEndian(inputBytes.AsSpan(i * 4)); 151 | break; 152 | case VertexFormat.Float16: 153 | result[i] = Half.ToHalf(inputBytes, i * 2); 154 | break; 155 | case VertexFormat.UNorm8: 156 | result[i] = inputBytes[i] / 255f; 157 | break; 158 | case VertexFormat.SNorm8: 159 | result[i] = Math.Max((sbyte)inputBytes[i] / 127f, -1f); 160 | break; 161 | case VertexFormat.UNorm16: 162 | result[i] = BinaryPrimitives.ReadUInt16LittleEndian(inputBytes.AsSpan(i * 2)) / 65535f; 163 | break; 164 | case VertexFormat.SNorm16: 165 | result[i] = Math.Max(BinaryPrimitives.ReadInt16LittleEndian(inputBytes.AsSpan(i * 2)) / 32767f, -1f); 166 | break; 167 | } 168 | } 169 | return result; 170 | } 171 | 172 | public static int[] BytesToIntArray(byte[] inputBytes, VertexFormat format) 173 | { 174 | var size = GetFormatSize(format); 175 | var len = inputBytes.Length / size; 176 | var result = new int[len]; 177 | for (int i = 0; i < len; i++) 178 | { 179 | switch (format) 180 | { 181 | case VertexFormat.UInt8: 182 | case VertexFormat.SInt8: 183 | result[i] = inputBytes[i]; 184 | break; 185 | case VertexFormat.UInt16: 186 | case VertexFormat.SInt16: 187 | result[i] = BinaryPrimitives.ReadInt16LittleEndian(inputBytes.AsSpan(i * 2)); 188 | break; 189 | case VertexFormat.UInt32: 190 | case VertexFormat.SInt32: 191 | result[i] = BinaryPrimitives.ReadInt32LittleEndian(inputBytes.AsSpan(i * 4)); 192 | break; 193 | } 194 | } 195 | return result; 196 | } 197 | } -------------------------------------------------------------------------------- /AssetTypes/ValueObject/MeshTypes.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudio; 4 | using AssetStudioExporter.Util; 5 | using FMOD; 6 | using System; 7 | using System.Collections; 8 | using System.Collections.Generic; 9 | using System.ComponentModel.Design; 10 | using System.Linq; 11 | using System.Reflection.PortableExecutable; 12 | using System.Text; 13 | using System.Threading.Tasks; 14 | 15 | namespace AssetStudioExporter.AssetTypes.ValueObject; 16 | 17 | public struct MinMaxAABB 18 | { 19 | public Vector3 m_Min; 20 | public Vector3 m_Max; 21 | 22 | public MinMaxAABB(Vector3 min, Vector3 max) 23 | { 24 | m_Min = min; 25 | m_Max = max; 26 | } 27 | } 28 | /* 29 | public class CompressedMesh 30 | { 31 | public PackedFloatVector m_Vertices; 32 | public PackedFloatVector m_UV; 33 | public PackedFloatVector m_BindPoses; 34 | public PackedFloatVector m_Normals; 35 | public PackedFloatVector m_Tangents; 36 | public PackedIntVector m_Weights; 37 | public PackedIntVector m_NormalSigns; 38 | public PackedIntVector m_TangentSigns; 39 | public PackedFloatVector m_FloatColors; 40 | public PackedIntVector m_BoneIndices; 41 | public PackedIntVector m_Triangles; 42 | public PackedIntVector m_Colors; 43 | public uint m_UVInfo; 44 | 45 | public CompressedMesh(ObjectReader reader) 46 | { 47 | var version = reader.version; 48 | 49 | m_Vertices = new PackedFloatVector(reader); 50 | m_UV = new PackedFloatVector(reader); 51 | if (version[0] < 5) 52 | { 53 | m_BindPoses = new PackedFloatVector(reader); 54 | } 55 | m_Normals = new PackedFloatVector(reader); 56 | m_Tangents = new PackedFloatVector(reader); 57 | m_Weights = new PackedIntVector(reader); 58 | m_NormalSigns = new PackedIntVector(reader); 59 | m_TangentSigns = new PackedIntVector(reader); 60 | if (version[0] >= 5) 61 | { 62 | m_FloatColors = new PackedFloatVector(reader); 63 | } 64 | m_BoneIndices = new PackedIntVector(reader); 65 | m_Triangles = new PackedIntVector(reader); 66 | if (version[0] > 3 || (version[0] == 3 && version[1] >= 5)) //3.5 and up 67 | { 68 | if (version[0] < 5) 69 | { 70 | m_Colors = new PackedIntVector(reader); 71 | } 72 | else 73 | { 74 | m_UVInfo = reader.ReadUInt32(); 75 | } 76 | } 77 | } 78 | } 79 | */ 80 | public class StreamInfo : IAssetTypeReader 81 | { 82 | public uint channelMask; 83 | public uint offset; 84 | public uint stride; 85 | public uint align; 86 | public byte dividerOp; 87 | public ushort frequency; 88 | 89 | public StreamInfo() { } 90 | 91 | public static StreamInfo Read(AssetTypeValueField value, UnityVersion version) 92 | { 93 | var stream = new StreamInfo(); 94 | 95 | stream.channelMask = value["channelMask"].AsUInt; 96 | stream.offset = value["offset"].AsUInt; 97 | //if (version[0] < 4) //4.0 down 98 | 99 | //} 100 | //else 101 | stream.stride = value["stride"].AsByte; 102 | stream.dividerOp = value["dividerOp"].AsByte; 103 | stream.frequency = value["frequency"].AsUShort; 104 | 105 | return stream; 106 | } 107 | } 108 | 109 | public class ChannelInfo : IAssetTypeReader 110 | { 111 | public byte stream; 112 | public byte offset; 113 | public byte format; 114 | public byte dimension; 115 | 116 | public static ChannelInfo Read(AssetTypeValueField value, UnityVersion version) 117 | { 118 | var channel = new ChannelInfo(); 119 | 120 | channel.stream = value["stream"].AsByte; 121 | channel.offset = value["offset"].AsByte; 122 | channel.format = value["format"].AsByte; 123 | channel.dimension = (byte)(value["dimension"].AsByte & 0xF); 124 | return channel; 125 | } 126 | } 127 | 128 | public class VertexData : IAssetTypeReader 129 | { 130 | public uint m_CurrentChannels; 131 | public uint m_VertexCount; 132 | public List m_Channels; 133 | public List m_Streams; 134 | public byte[] m_DataSize; 135 | 136 | 137 | public static VertexData Read(AssetTypeValueField value, UnityVersion version) 138 | { 139 | var vertex = new VertexData(); 140 | 141 | if (version.major < 2018)//2018 down 142 | { 143 | vertex.m_CurrentChannels = value["m_CurrentChannels"].AsUInt; 144 | } 145 | 146 | vertex.m_VertexCount = value["m_VertexCount"].AsUInt; 147 | vertex.m_Channels = value["m_Channels"] 148 | .AsList(c => ChannelInfo.Read(c, version)); 149 | 150 | //if (version[0] < 5) //5.0 down 151 | //{ 152 | 153 | //} 154 | //else //5.0 and up 155 | //{ 156 | vertex.GetStreams(version.ToIntArray()); 157 | //} 158 | 159 | vertex.m_DataSize = value["m_DataSize"].AsByteArray; 160 | 161 | return vertex; 162 | } 163 | 164 | private void GetStreams(int[] version) 165 | { 166 | var streamCount = m_Channels.Max(x => x.stream) + 1; 167 | m_Streams = new List(); 168 | uint offset = 0; 169 | for (int s = 0; s < streamCount; s++) 170 | { 171 | uint chnMask = 0; 172 | uint stride = 0; 173 | for (int chn = 0; chn < m_Channels.Count; chn++) 174 | { 175 | var m_Channel = m_Channels[chn]; 176 | if (m_Channel.stream == s) 177 | { 178 | if (m_Channel.dimension > 0) 179 | { 180 | chnMask |= 1u << chn; 181 | stride += m_Channel.dimension * MeshHelper.GetFormatSize(MeshHelper.ToVertexFormat(m_Channel.format, version)); 182 | } 183 | } 184 | } 185 | m_Streams.Add(new StreamInfo 186 | { 187 | channelMask = chnMask, 188 | offset = offset, 189 | stride = stride, 190 | dividerOp = 0, 191 | frequency = 0 192 | }); 193 | offset += m_VertexCount * stride; 194 | //static size_t AlignStreamSize (size_t size) { return (size + (kVertexStreamAlign-1)) & ~(kVertexStreamAlign-1); } 195 | offset = (offset + (16u - 1u)) & ~(16u - 1u); 196 | } 197 | } 198 | 199 | private void GetChannels(int[] version) 200 | { 201 | m_Channels = new List(6); 202 | for (int i = 0; i < 6; i++) 203 | { 204 | m_Channels.Add(new ChannelInfo()); 205 | } 206 | for (var s = 0; s < m_Streams.Count; s++) 207 | { 208 | var m_Stream = m_Streams[s]; 209 | var channelMask = new BitArray(new[] { (int)m_Stream.channelMask }); 210 | byte offset = 0; 211 | for (int i = 0; i < 6; i++) 212 | { 213 | if (channelMask.Get(i)) 214 | { 215 | var m_Channel = m_Channels[i]; 216 | m_Channel.stream = (byte)s; 217 | m_Channel.offset = offset; 218 | switch (i) 219 | { 220 | case 0: //kShaderChannelVertex 221 | case 1: //kShaderChannelNormal 222 | m_Channel.format = 0; //kChannelFormatFloat 223 | m_Channel.dimension = 3; 224 | break; 225 | case 2: //kShaderChannelColor 226 | m_Channel.format = 2; //kChannelFormatColor 227 | m_Channel.dimension = 4; 228 | break; 229 | case 3: //kShaderChannelTexCoord0 230 | case 4: //kShaderChannelTexCoord1 231 | m_Channel.format = 0; //kChannelFormatFloat 232 | m_Channel.dimension = 2; 233 | break; 234 | case 5: //kShaderChannelTangent 235 | m_Channel.format = 0; //kChannelFormatFloat 236 | m_Channel.dimension = 4; 237 | break; 238 | } 239 | offset += (byte)(m_Channel.dimension * MeshHelper.GetFormatSize(MeshHelper.ToVertexFormat(m_Channel.format, version))); 240 | } 241 | } 242 | } 243 | } 244 | 245 | } 246 | 247 | 248 | public class BoneWeights4 : IAssetTypeReader 249 | { 250 | public float[] weight; 251 | public int[] boneIndex; 252 | 253 | public BoneWeights4() 254 | { 255 | weight = new float[4]; 256 | boneIndex = new int[4]; 257 | } 258 | 259 | 260 | public static BoneWeights4 Read(AssetTypeValueField value, UnityVersion version) 261 | { 262 | return Read(value); 263 | } 264 | 265 | public static BoneWeights4 Read(AssetTypeValueField value) 266 | { 267 | var weights = new BoneWeights4(); 268 | 269 | weights.weight = value["weight"].AsArray(w => w.AsFloat); 270 | weights.boneIndex = value["boneIndex"].AsArray(i => i.AsInt); 271 | 272 | return weights; 273 | } 274 | } 275 | 276 | //public class BlendShapeVertex 277 | //{ 278 | // public Vector3 vertex; 279 | // public Vector3 normal; 280 | // public Vector3 tangent; 281 | // public uint index; 282 | 283 | // public BlendShapeVertex(ObjectReader reader) 284 | // { 285 | // vertex = reader.ReadVector3(); 286 | // normal = reader.ReadVector3(); 287 | // tangent = reader.ReadVector3(); 288 | // index = reader.ReadUInt32(); 289 | // } 290 | //} 291 | 292 | //public class MeshBlendShape 293 | //{ 294 | // public string name; 295 | // public uint firstVertex; 296 | // public uint vertexCount; 297 | // public bool hasNormals; 298 | // public bool hasTangents; 299 | 300 | // public MeshBlendShape(ObjectReader reader) 301 | // { 302 | // var version = reader.version; 303 | 304 | // if (version[0] == 4 && version[1] < 3) //4.3 down 305 | // { 306 | // name = reader.ReadAlignedString(); 307 | // } 308 | // firstVertex = reader.ReadUInt32(); 309 | // vertexCount = reader.ReadUInt32(); 310 | // if (version[0] == 4 && version[1] < 3) //4.3 down 311 | // { 312 | // var aabbMinDelta = reader.ReadVector3(); 313 | // var aabbMaxDelta = reader.ReadVector3(); 314 | // } 315 | // hasNormals = reader.ReadBoolean(); 316 | // hasTangents = reader.ReadBoolean(); 317 | // if (version[0] > 4 || (version[0] == 4 && version[1] >= 3)) //4.3 and up 318 | // { 319 | // reader.AlignStream(); 320 | // } 321 | // } 322 | //} 323 | 324 | //public class MeshBlendShapeChannel 325 | //{ 326 | // public string name; 327 | // public uint nameHash; 328 | // public int frameIndex; 329 | // public int frameCount; 330 | 331 | // public MeshBlendShapeChannel(ObjectReader reader) 332 | // { 333 | // name = reader.ReadAlignedString(); 334 | // nameHash = reader.ReadUInt32(); 335 | // frameIndex = reader.ReadInt32(); 336 | // frameCount = reader.ReadInt32(); 337 | // } 338 | //} 339 | 340 | //public class BlendShapeData 341 | //{ 342 | // public List vertices; 343 | // public List shapes; 344 | // public List channels; 345 | // public float[] fullWeights; 346 | 347 | // public BlendShapeData(ObjectReader reader) 348 | // { 349 | // var version = reader.version; 350 | 351 | // if (version[0] > 4 || (version[0] == 4 && version[1] >= 3)) //4.3 and up 352 | // { 353 | // int numVerts = reader.ReadInt32(); 354 | // vertices = new List(); 355 | // for (int i = 0; i < numVerts; i++) 356 | // { 357 | // vertices.Add(new BlendShapeVertex(reader)); 358 | // } 359 | 360 | // int numShapes = reader.ReadInt32(); 361 | // shapes = new List(); 362 | // for (int i = 0; i < numShapes; i++) 363 | // { 364 | // shapes.Add(new MeshBlendShape(reader)); 365 | // } 366 | 367 | // int numChannels = reader.ReadInt32(); 368 | // channels = new List(); 369 | // for (int i = 0; i < numChannels; i++) 370 | // { 371 | // channels.Add(new MeshBlendShapeChannel(reader)); 372 | // } 373 | 374 | // fullWeights = reader.ReadSingleArray(); 375 | // } 376 | // else 377 | // { 378 | // var m_ShapesSize = reader.ReadInt32(); 379 | // var m_Shapes = new List(); 380 | // for (int i = 0; i < m_ShapesSize; i++) 381 | // { 382 | // m_Shapes.Add(new MeshBlendShape(reader)); 383 | // } 384 | // reader.AlignStream(); 385 | // var m_ShapeVerticesSize = reader.ReadInt32(); 386 | // var m_ShapeVertices = new List(); //MeshBlendShapeVertex 387 | // for (int i = 0; i < m_ShapeVerticesSize; i++) 388 | // { 389 | // m_ShapeVertices.Add(new BlendShapeVertex(reader)); 390 | // } 391 | // } 392 | // } 393 | //} 394 | 395 | public enum GfxPrimitiveType 396 | { 397 | Triangles = 0, 398 | TriangleStrip = 1, 399 | Quads = 2, 400 | Lines = 3, 401 | LineStrip = 4, 402 | Points = 5 403 | }; 404 | 405 | public class SubMesh : IAssetTypeReader 406 | { 407 | public uint firstByte; 408 | public uint indexCount; 409 | public GfxPrimitiveType topology; 410 | public uint triangleCount; 411 | public uint baseVertex; 412 | public uint firstVertex; 413 | public uint vertexCount; 414 | public AABB localAABB; 415 | 416 | public static SubMesh Read(AssetTypeValueField value, UnityVersion version) 417 | { 418 | var mesh = new SubMesh(); 419 | 420 | mesh.firstByte = value["firstByte"].AsUInt; 421 | mesh.indexCount = value["indexCount"].AsUInt; 422 | mesh.topology = (GfxPrimitiveType)value["indexCount"].AsInt; 423 | 424 | //if (version[0] < 4) //4.0 down 425 | //{ 426 | // triangleCount = reader.ReadUInt32(); 427 | //} 428 | 429 | var baseVertexValue = value["baseVertex"]; 430 | if (!baseVertexValue.IsDummy) 431 | { 432 | mesh.baseVertex = baseVertexValue.AsUInt; 433 | } 434 | 435 | 436 | //if (version[0] >= 3) //3.0 and up 437 | //{ 438 | mesh.firstVertex = value["firstVertex"].AsUInt; 439 | mesh.vertexCount = value["vertexCount"].AsUInt; 440 | mesh.localAABB = value["localAABB"].AsAABB(); 441 | //} 442 | 443 | return mesh; 444 | } 445 | } -------------------------------------------------------------------------------- /AssetTypes/ValueObject/Rectf.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudioExporter.AssetTypes; 4 | using System.Runtime.InteropServices; 5 | using System.Xml; 6 | 7 | namespace AssetStudio; 8 | 9 | // 该类型从Sprite.cs中剥离并扩充 10 | 11 | [StructLayout(LayoutKind.Sequential)] 12 | public struct Rectf : IEquatable, IAssetTypeReader 13 | { 14 | public float x; 15 | public float y; 16 | public float width; 17 | public float height; 18 | 19 | public bool Equals(Rectf other) 20 | { 21 | return this == other; 22 | } 23 | 24 | public override int GetHashCode() 25 | { 26 | return HashCode.Combine(x, y, width, height); 27 | } 28 | 29 | public override bool Equals(object obj) 30 | { 31 | return obj is Rectf rectf && Equals(rectf); 32 | } 33 | 34 | public static bool operator==(Rectf left, Rectf right) 35 | { 36 | return left.x == right.x 37 | && left.y == right.y 38 | && left.width == right.width 39 | && left.height == right.height; 40 | } 41 | 42 | public static bool operator!=(Rectf left, Rectf right) 43 | { 44 | return !(left == right); 45 | } 46 | 47 | 48 | public static Rectf Read(AssetTypeValueField value, UnityVersion version) 49 | { 50 | return Read(value); 51 | } 52 | 53 | public static Rectf Read(AssetTypeValueField value) 54 | { 55 | return new Rectf() 56 | { 57 | x = value["x"].AsFloat, 58 | y = value["y"].AsFloat, 59 | width = value["width"].AsFloat, 60 | height = value["height"].AsFloat, 61 | }; 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /AssetTypes/ValueObject/SpriteTypes.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetsTools.NET.Extra; 3 | using AssetStudio; 4 | using AssetStudioExporter.Util; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Reflection.PortableExecutable; 7 | 8 | namespace AssetStudioExporter.AssetTypes.ValueObject; 9 | 10 | public class SecondarySpriteTexture 11 | { 12 | public PPtr texture; 13 | public string name; 14 | 15 | public SecondarySpriteTexture(AssetTypeValueField value) 16 | { 17 | texture = PPtr.Read(value["texture"]); 18 | name = value["name"].AsString; 19 | } 20 | } 21 | 22 | public enum SpritePackingRotation 23 | { 24 | None = 0, 25 | FlipHorizontal = 1, 26 | FlipVertical = 2, 27 | Rotate180 = 3, 28 | Rotate90 = 4 29 | }; 30 | 31 | public enum SpritePackingMode 32 | { 33 | Tight = 0, 34 | Rectangle 35 | }; 36 | 37 | public enum SpriteMeshType 38 | { 39 | FullRect, 40 | Tight 41 | }; 42 | 43 | public struct SpriteSettings : IEquatable 44 | { 45 | public uint settingsRaw; 46 | 47 | public uint packed => settingsRaw & 1; //1 48 | public SpritePackingMode packingMode => (SpritePackingMode)(settingsRaw >> 1 & 1); //1 49 | public SpritePackingRotation packingRotation => (SpritePackingRotation)(settingsRaw >> 2 & 0xf); //4 50 | public SpriteMeshType meshType => (SpriteMeshType)(settingsRaw >> 6 & 1); //1 51 | 52 | //reserved 53 | 54 | public SpriteSettings(uint settingsRaw) 55 | { 56 | this.settingsRaw = settingsRaw; 57 | } 58 | 59 | public static implicit operator uint(SpriteSettings spriteSettings) 60 | { 61 | return spriteSettings.settingsRaw; 62 | } 63 | 64 | public static explicit operator SpriteSettings(uint settingsRaw) 65 | { 66 | return new SpriteSettings(settingsRaw); 67 | } 68 | 69 | public bool Equals(SpriteSettings other) 70 | { 71 | return this == other; 72 | } 73 | 74 | public override bool Equals([NotNullWhen(true)] object? obj) 75 | { 76 | return obj is SpriteSettings settings && Equals(settings); 77 | } 78 | 79 | public override int GetHashCode() 80 | { 81 | return settingsRaw.GetHashCode(); 82 | } 83 | 84 | public static bool operator ==(SpriteSettings left, SpriteSettings right) 85 | { 86 | return left.settingsRaw == right.settingsRaw; 87 | } 88 | 89 | public static bool operator !=(SpriteSettings left, SpriteSettings right) 90 | { 91 | return !(left == right); 92 | } 93 | } 94 | 95 | public class SpriteVertex : IAssetTypeReader 96 | { 97 | public Vector3 pos; 98 | public Vector2 uv; 99 | 100 | public static SpriteVertex Read(AssetTypeValueField value, UnityVersion version) 101 | { 102 | var vertex = new SpriteVertex(); 103 | vertex.pos = value["pos"].AsVector3(); 104 | //if (version[0] < 4 || (version[0] == 4 && version[1] <= 3)) //4.3 and down 105 | //{ 106 | // uv = reader.ReadVector2(); 107 | //} 108 | 109 | return vertex; 110 | } 111 | } 112 | 113 | public class SpriteAtlasData : IAssetTypeReader 114 | { 115 | public PPtr texture = null!; 116 | public PPtr alphaTexture = null!; 117 | public Rectf textureRect; 118 | public Vector2 textureRectOffset; 119 | public Vector2 atlasRectOffset; 120 | public Vector4 uvTransform; 121 | public float downscaleMultiplier; 122 | public SpriteSettings settingsRaw; 123 | public List? secondaryTextures; 124 | 125 | 126 | public static SpriteAtlasData Read(AssetTypeValueField value, UnityVersion version) 127 | { 128 | var data = new SpriteAtlasData(); 129 | 130 | data.texture = new PPtr(value["texture"]); 131 | data.alphaTexture = new PPtr(value["alphaTexture"]); 132 | data.textureRect = Rectf.Read(value["textureRect"]); 133 | data.textureRectOffset = value["textureRectOffset"].AsVector2(); 134 | 135 | var atlasRectOffsetValue = value["atlasRectOffset"]; 136 | if (!atlasRectOffsetValue.IsDummy) 137 | { 138 | data.atlasRectOffset = atlasRectOffsetValue.AsVector2(); 139 | } 140 | 141 | data.uvTransform = value["uvTransform"].AsVector4(); 142 | data.downscaleMultiplier = value["downscaleMultiplier"].AsFloat; 143 | data.settingsRaw = new SpriteSettings(value["settingsRaw"].AsUInt); 144 | 145 | var secondaryTexturesValue = value["secondaryTextures"]; 146 | if (!secondaryTexturesValue.IsDummy) 147 | { 148 | data.secondaryTextures = secondaryTexturesValue 149 | .AsList(texture => new SecondarySpriteTexture(texture)); 150 | } 151 | 152 | return data; 153 | } 154 | } 155 | 156 | public class SpriteRenderData : IAssetTypeReader 157 | { 158 | public PPtr texture; 159 | public PPtr alphaTexture; 160 | public List secondaryTextures; 161 | public List m_SubMeshes; 162 | public byte[] m_IndexBuffer; 163 | public VertexData m_VertexData; 164 | public List vertices; 165 | public ushort[] indices; 166 | public Matrix4x4[] m_Bindpose; 167 | public List m_SourceSkin; 168 | public Rectf textureRect; 169 | public Vector2 textureRectOffset; 170 | public Vector2 atlasRectOffset; 171 | public SpriteSettings settingsRaw; 172 | public Vector4 uvTransform; 173 | public float downscaleMultiplier; 174 | 175 | public static SpriteRenderData Read(AssetTypeValueField value, UnityVersion version) 176 | { 177 | var rd = new SpriteRenderData(); 178 | 179 | rd.texture = PPtr.Read(value["texture"]); 180 | rd.alphaTexture = PPtr.Read(value["alphaTexture"]); 181 | 182 | 183 | if (version.major >= 2019) //2019 and up 184 | { 185 | var secondaryTextures = value["secondaryTextures"]; 186 | if (!secondaryTextures.IsDummy) 187 | { 188 | rd.secondaryTextures = secondaryTextures 189 | .AsList(t => new SecondarySpriteTexture(t)); 190 | } 191 | } 192 | 193 | //if (version[0] > 5 || (version[0] == 5 && version[1] >= 6)) //5.6 and up 194 | rd.m_SubMeshes = value["m_SubMeshes"].AsList(x => SubMesh.Read(x, version)); 195 | rd.m_IndexBuffer = value["m_IndexBuffer"]["Array"].AsByteArray; 196 | rd.m_VertexData = VertexData.Read(value["m_VertexData"], version); 197 | //} else { 198 | // rd.vertices = value["vertices"].AsList(SpriteVertex.Read); 199 | //} 200 | 201 | if (version.major >= 2018) //2018 and up 202 | { 203 | rd.m_Bindpose = value["m_Bindpose"].AsArray(p => p.AsMatrix4x4()); 204 | if (version.major == 2018 && version.minor < 2) //2018.2 down 205 | { 206 | rd.m_SourceSkin = value["m_SourceSkin"].AsList(BoneWeights4.Read); 207 | } 208 | } 209 | 210 | rd.textureRect = Rectf.Read(value["textureRect"]); 211 | rd.textureRectOffset = value["textureRectOffset"].AsVector2(); 212 | 213 | rd.atlasRectOffset = value["atlasRectOffset"].AsVector2(); 214 | rd.settingsRaw = new SpriteSettings(value["settingsRaw"].AsUInt); 215 | 216 | rd.uvTransform = value["uvTransform"].AsVector4(); 217 | 218 | if (version.major >= 2017) //2017 and up 219 | { 220 | rd.downscaleMultiplier = value["downscaleMultiplier"].AsFloat; 221 | } 222 | 223 | return rd; 224 | } 225 | } -------------------------------------------------------------------------------- /Export/AudioClipConverter.cs: -------------------------------------------------------------------------------- 1 | using AssetStudio; 2 | using FMOD; 3 | using System; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | namespace AssetStudioExporter.Export 8 | { 9 | public class AudioClipConverter 10 | { 11 | private AudioClip m_AudioClip; 12 | readonly int[] version; 13 | 14 | public AudioClipConverter(AudioClip audioClip, int[] version) 15 | { 16 | m_AudioClip = audioClip; 17 | this.version = version; 18 | } 19 | 20 | public byte[]? ConvertToWav(byte[] m_AudioData) 21 | { 22 | if (m_AudioData == null || m_AudioData.Length == 0) 23 | return null; 24 | var exinfo = new CREATESOUNDEXINFO(); 25 | var result = Factory.System_Create(out var system); 26 | if (result != RESULT.OK) 27 | return null; 28 | result = system.init(1, INITFLAGS.NORMAL, IntPtr.Zero); 29 | if (result != RESULT.OK) 30 | return null; 31 | exinfo.cbsize = Marshal.SizeOf(exinfo); 32 | exinfo.length = (uint)m_AudioClip.m_Size; 33 | result = system.createSound(m_AudioData, MODE.OPENMEMORY, ref exinfo, out var sound); 34 | if (result != RESULT.OK) 35 | return null; 36 | result = sound.getNumSubSounds(out var numsubsounds); 37 | if (result != RESULT.OK) 38 | return null; 39 | byte[] buff; 40 | if (numsubsounds > 0) 41 | { 42 | result = sound.getSubSound(0, out var subsound); 43 | if (result != RESULT.OK) 44 | return null; 45 | buff = SoundToWav(subsound); 46 | subsound.release(); 47 | } 48 | else 49 | { 50 | buff = SoundToWav(sound); 51 | } 52 | sound.release(); 53 | system.release(); 54 | return buff; 55 | } 56 | 57 | public static byte[]? SoundToWav(Sound sound) 58 | { 59 | var result = sound.getFormat(out _, out _, out int channels, out int bits); 60 | if (result != RESULT.OK) 61 | return null; 62 | result = sound.getDefaults(out var frequency, out _); 63 | if (result != RESULT.OK) 64 | return null; 65 | var sampleRate = (int)frequency; 66 | result = sound.getLength(out var length, TIMEUNIT.PCMBYTES); 67 | if (result != RESULT.OK) 68 | return null; 69 | result = sound.@lock(0, length, out var ptr1, out var ptr2, out var len1, out var len2); 70 | if (result != RESULT.OK) 71 | return null; 72 | byte[] buffer = new byte[len1 + 44]; 73 | //添加wav头 74 | Encoding.UTF8.GetBytes("RIFF").CopyTo(buffer, 0); 75 | BitConverter.GetBytes(len1 + 36).CopyTo(buffer, 4); 76 | Encoding.UTF8.GetBytes("WAVEfmt ").CopyTo(buffer, 8); 77 | BitConverter.GetBytes(16).CopyTo(buffer, 16); 78 | BitConverter.GetBytes((short)1).CopyTo(buffer, 20); 79 | BitConverter.GetBytes((short)channels).CopyTo(buffer, 22); 80 | BitConverter.GetBytes(sampleRate).CopyTo(buffer, 24); 81 | BitConverter.GetBytes(sampleRate * channels * bits / 8).CopyTo(buffer, 28); 82 | BitConverter.GetBytes((short)(channels * bits / 8)).CopyTo(buffer, 32); 83 | BitConverter.GetBytes((short)bits).CopyTo(buffer, 34); 84 | Encoding.UTF8.GetBytes("data").CopyTo(buffer, 36); 85 | BitConverter.GetBytes(len1).CopyTo(buffer, 40); 86 | Marshal.Copy(ptr1, buffer, 44, (int)len1); 87 | result = sound.unlock(ptr1, ptr2, len1, len2); 88 | if (result != RESULT.OK) 89 | return null; 90 | return buffer; 91 | } 92 | 93 | public string GetExtensionName() 94 | { 95 | if (/*m_AudioClip.*/version[0] < 5) 96 | { 97 | switch (m_AudioClip.m_Type) 98 | { 99 | case AudioType.ACC: 100 | return ".m4a"; 101 | case AudioType.AIFF: 102 | return ".aif"; 103 | case AudioType.IT: 104 | return ".it"; 105 | case AudioType.MOD: 106 | return ".mod"; 107 | case AudioType.MPEG: 108 | return ".mp3"; 109 | case AudioType.OGGVORBIS: 110 | return ".ogg"; 111 | case AudioType.S3M: 112 | return ".s3m"; 113 | case AudioType.WAV: 114 | return ".wav"; 115 | case AudioType.XM: 116 | return ".xm"; 117 | case AudioType.XMA: 118 | return ".wav"; 119 | case AudioType.VAG: 120 | return ".vag"; 121 | case AudioType.AUDIOQUEUE: 122 | return ".fsb"; 123 | } 124 | 125 | } 126 | else 127 | { 128 | switch (m_AudioClip.m_CompressionFormat) 129 | { 130 | case AudioCompressionFormat.PCM: 131 | return ".fsb"; 132 | case AudioCompressionFormat.Vorbis: 133 | return ".fsb"; 134 | case AudioCompressionFormat.ADPCM: 135 | return ".fsb"; 136 | case AudioCompressionFormat.MP3: 137 | return ".fsb"; 138 | case AudioCompressionFormat.VAG: 139 | return ".fsb"; 140 | case AudioCompressionFormat.HEVAG: 141 | return ".fsb"; 142 | case AudioCompressionFormat.XMA: 143 | return ".fsb"; 144 | case AudioCompressionFormat.AAC: 145 | return ".m4a"; 146 | case AudioCompressionFormat.GCADPCM: 147 | return ".fsb"; 148 | case AudioCompressionFormat.ATRAC9: 149 | return ".fsb"; 150 | } 151 | } 152 | 153 | return ".AudioClip"; 154 | } 155 | 156 | public bool IsSupport 157 | { 158 | get 159 | { 160 | if (/*m_AudioClip.*/version[0] < 5) 161 | { 162 | switch (m_AudioClip.m_Type) 163 | { 164 | case AudioType.AIFF: 165 | case AudioType.IT: 166 | case AudioType.MOD: 167 | case AudioType.S3M: 168 | case AudioType.XM: 169 | case AudioType.XMA: 170 | case AudioType.AUDIOQUEUE: 171 | return true; 172 | default: 173 | return false; 174 | } 175 | } 176 | else 177 | { 178 | switch (m_AudioClip.m_CompressionFormat) 179 | { 180 | case AudioCompressionFormat.PCM: 181 | case AudioCompressionFormat.Vorbis: 182 | case AudioCompressionFormat.ADPCM: 183 | case AudioCompressionFormat.MP3: 184 | case AudioCompressionFormat.XMA: 185 | return true; 186 | default: 187 | return false; 188 | } 189 | } 190 | } 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /Export/SpriteHelper.cs: -------------------------------------------------------------------------------- 1 | using AssetStudioExporter.AssetTypes; 2 | using AssetStudioExporter.AssetTypes.ValueObject; 3 | using SixLabors.ImageSharp; 4 | using SixLabors.ImageSharp.Drawing; 5 | using SixLabors.ImageSharp.Drawing.Processing; 6 | using SixLabors.ImageSharp.PixelFormats; 7 | using SixLabors.ImageSharp.Processing; 8 | using AssetsTools.NET; 9 | using AssetsTools.NET.Extra; 10 | using AssetStudio; 11 | 12 | namespace AssetStudioExporter.Export 13 | { 14 | public class SpriteHelper(AssetsFileInstance inst, AssetsManager am) 15 | { 16 | public Image? GetImage(Sprite m_Sprite) 17 | { 18 | if (m_Sprite.m_SpriteAtlas != null && m_Sprite.m_SpriteAtlas.TryFindInstance(out var m_SpriteAtlas, inst, am)) 19 | { 20 | if (m_SpriteAtlas.m_RenderDataMap.TryGetValue(m_Sprite.m_RenderDataKey, out var spriteAtlasData) && spriteAtlasData.texture.TryFindInstance(out var m_Texture2D, inst, am)) 21 | { 22 | return CutImage(m_Sprite, m_Texture2D, spriteAtlasData!.textureRect, spriteAtlasData.textureRectOffset, spriteAtlasData.downscaleMultiplier, spriteAtlasData.settingsRaw); 23 | } 24 | } 25 | else 26 | { 27 | if (m_Sprite.m_RD.texture.TryFindInstance(out var m_Texture2D, inst, am)) 28 | { 29 | return CutImage(m_Sprite, m_Texture2D, m_Sprite.m_RD.textureRect, m_Sprite.m_RD.textureRectOffset, m_Sprite.m_RD.downscaleMultiplier, m_Sprite.m_RD.settingsRaw); 30 | } 31 | } 32 | return null; 33 | } 34 | 35 | private Image? CutImage(Sprite m_Sprite, Texture2D m_Texture2D, Rectf textureRect, Vector2 textureRectOffset, float downscaleMultiplier, SpriteSettings settingsRaw) 36 | { 37 | var originalImage = m_Texture2D.GetImage(inst, false); 38 | if (originalImage is null) 39 | { 40 | return null; 41 | } 42 | 43 | using (originalImage) 44 | { 45 | if (downscaleMultiplier > 0f && downscaleMultiplier != 1f) 46 | { 47 | var width = (int)(m_Texture2D.m_Width / downscaleMultiplier); 48 | var height = (int)(m_Texture2D.m_Height / downscaleMultiplier); 49 | originalImage.Mutate(x => x.Resize(width, height)); 50 | } 51 | var rectX = (int)Math.Floor(textureRect.x); 52 | var rectY = (int)Math.Floor(textureRect.y); 53 | var rectRight = (int)Math.Ceiling(textureRect.x + textureRect.width); 54 | var rectBottom = (int)Math.Ceiling(textureRect.y + textureRect.height); 55 | rectRight = Math.Min(rectRight, originalImage.Width); 56 | rectBottom = Math.Min(rectBottom, originalImage.Height); 57 | var rect = new Rectangle(rectX, rectY, rectRight - rectX, rectBottom - rectY); 58 | var spriteImage = originalImage.Clone(x => x.Crop(rect)); 59 | if (settingsRaw.packed == 1) 60 | { 61 | //RotateAndFlip 62 | switch (settingsRaw.packingRotation) 63 | { 64 | case SpritePackingRotation.FlipHorizontal: 65 | spriteImage.Mutate(x => x.Flip(FlipMode.Horizontal)); 66 | break; 67 | case SpritePackingRotation.FlipVertical: 68 | spriteImage.Mutate(x => x.Flip(FlipMode.Vertical)); 69 | break; 70 | case SpritePackingRotation.Rotate180: 71 | spriteImage.Mutate(x => x.Rotate(180)); 72 | break; 73 | case SpritePackingRotation.Rotate90: 74 | spriteImage.Mutate(x => x.Rotate(270)); 75 | break; 76 | } 77 | } 78 | 79 | //Tight 80 | if (settingsRaw.packingMode == SpritePackingMode.Tight) 81 | { 82 | try 83 | { 84 | var triangles = GetTriangles(m_Sprite.m_RD); 85 | var polygons = triangles.Select(x => new Polygon(new LinearLineSegment(x.Select(y => new PointF(y.X, y.Y)).ToArray()))).ToArray(); 86 | IPathCollection path = new PathCollection(polygons); 87 | var matrix = System.Numerics.Matrix3x2.CreateScale(m_Sprite.m_PixelsToUnits); 88 | matrix *= System.Numerics.Matrix3x2.CreateTranslation(m_Sprite.m_Rect.width * m_Sprite.m_Pivot.X - textureRectOffset.X, m_Sprite.m_Rect.height * m_Sprite.m_Pivot.Y - textureRectOffset.Y); 89 | path = path.Transform(matrix); 90 | var options = new DrawingOptions 91 | { 92 | GraphicsOptions = new GraphicsOptions 93 | { 94 | Antialias = false, 95 | AlphaCompositionMode = PixelAlphaCompositionMode.DestOut 96 | } 97 | }; 98 | using (var mask = new Image(rect.Width, rect.Height, SixLabors.ImageSharp.Color.Black)) 99 | { 100 | mask.Mutate(x => x.Fill(options, SixLabors.ImageSharp.Color.Red, path)); 101 | var bursh = new ImageBrush(mask); 102 | spriteImage.Mutate(x => x.Fill(options, bursh)); 103 | spriteImage.Mutate(x => x.Flip(FlipMode.Vertical)); 104 | return spriteImage; 105 | } 106 | } 107 | catch 108 | { 109 | // ignored 110 | } 111 | } 112 | 113 | //Rectangle 114 | spriteImage.Mutate(x => x.Flip(FlipMode.Vertical)); 115 | return spriteImage; 116 | } 117 | } 118 | 119 | private static Vector2[][] GetTriangles(SpriteRenderData m_RD) 120 | { 121 | if (m_RD.vertices != null) //5.6 down 122 | { 123 | var vertices = m_RD.vertices.Select(x => (Vector2)x.pos).ToArray(); 124 | var triangleCount = m_RD.indices.Length / 3; 125 | var triangles = new Vector2[triangleCount][]; 126 | for (int i = 0; i < triangleCount; i++) 127 | { 128 | var first = m_RD.indices[i * 3]; 129 | var second = m_RD.indices[i * 3 + 1]; 130 | var third = m_RD.indices[i * 3 + 2]; 131 | var triangle = new[] { vertices[first], vertices[second], vertices[third] }; 132 | triangles[i] = triangle; 133 | } 134 | return triangles; 135 | } 136 | else //5.6 and up 137 | { 138 | var triangles = new List(); 139 | var m_VertexData = m_RD.m_VertexData; 140 | var m_Channel = m_VertexData.m_Channels[0]; //kShaderChannelVertex 141 | var m_Stream = m_VertexData.m_Streams[m_Channel.stream]; 142 | // 均为LittleEndian 143 | using (var vertexReader = new BinaryReader(new MemoryStream(m_VertexData.m_DataSize))) 144 | { 145 | using (var indexReader = new BinaryReader(new MemoryStream(m_RD.m_IndexBuffer))) 146 | { 147 | foreach (var subMesh in m_RD.m_SubMeshes) 148 | { 149 | vertexReader.BaseStream.Position = m_Stream.offset + subMesh.firstVertex * m_Stream.stride + m_Channel.offset; 150 | 151 | var vertices = new Vector2[subMesh.vertexCount]; 152 | for (int v = 0; v < subMesh.vertexCount; v++) 153 | { 154 | vertices[v] = new Vector3(vertexReader.ReadSingle(), vertexReader.ReadSingle(), vertexReader.ReadSingle()); 155 | vertexReader.BaseStream.Position += m_Stream.stride - 12; 156 | } 157 | 158 | indexReader.BaseStream.Position = subMesh.firstByte; 159 | 160 | var triangleCount = subMesh.indexCount / 3u; 161 | for (int i = 0; i < triangleCount; i++) 162 | { 163 | var first = indexReader.ReadUInt16() - subMesh.firstVertex; 164 | var second = indexReader.ReadUInt16() - subMesh.firstVertex; 165 | var third = indexReader.ReadUInt16() - subMesh.firstVertex; 166 | var triangle = new[] { vertices[first], vertices[second], vertices[third] }; 167 | triangles.Add(triangle); 168 | } 169 | } 170 | } 171 | } 172 | return triangles.ToArray(); 173 | } 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /Export/Texture2DConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | using Texture2DDecoder; 3 | using AssetsTools.NET; 4 | using AssetStudioExporter.AssetTypes; 5 | using AssetsTools.NET.Extra; 6 | using SixLabors.ImageSharp; 7 | using AssetStudio; 8 | using AssetsTools.NET.Texture; 9 | using Half = AssetStudio.Half; 10 | 11 | namespace AssetStudioExporter.Export 12 | { 13 | /// 14 | /// 魔改的AssetStudio.Texture2DConverter,适配AssetsTools.NET并注释了暂需要额外库的格式 15 | /// 16 | public class Texture2DConverter 17 | { 18 | //private ResourceReader reader; 19 | private int m_Width; 20 | private int m_Height; 21 | private TextureFormat m_TextureFormat; 22 | private UnityVersion version; 23 | //private BuildTarget platform; 24 | private int outPutSize; 25 | private TextureFile texture2D; 26 | 27 | public Texture2DConverter(TextureFile m_Texture2D, UnityVersion version) 28 | { 29 | m_Width = m_Texture2D.m_Width; 30 | m_Height = m_Texture2D.m_Height; 31 | m_TextureFormat = (TextureFormat)m_Texture2D.m_TextureFormat; 32 | this.version = version; 33 | //platform = m_Texture2D.platform; 34 | outPutSize = m_Width * m_Height * 4; 35 | texture2D = m_Texture2D; 36 | } 37 | 38 | public bool DecodeTexture2D(byte[] buff, byte[] bytes) 39 | { 40 | // if (reader.Size == 0 || m_Width == 0 || m_Height == 0) 41 | // { 42 | // return false; 43 | // } 44 | var flag = false; 45 | // var buff = BigArrayPool.Shared.Rent(reader.Size); 46 | // reader.GetData(buff); 47 | switch (m_TextureFormat) 48 | { 49 | case TextureFormat.Alpha8: //test pass 50 | flag = DecodeAlpha8(buff, bytes); 51 | break; 52 | case TextureFormat.ARGB4444: //test pass 53 | SwapBytesForXbox(buff); 54 | flag = DecodeARGB4444(buff, bytes); 55 | break; 56 | case TextureFormat.RGB24: //test pass 57 | flag = DecodeRGB24(buff, bytes); 58 | break; 59 | case TextureFormat.RGBA32: //test pass 60 | flag = DecodeRGBA32(buff, bytes); 61 | break; 62 | case TextureFormat.ARGB32: //test pass 63 | flag = DecodeARGB32(buff, bytes); 64 | break; 65 | case TextureFormat.RGB565: //test pass 66 | SwapBytesForXbox(buff); 67 | flag = DecodeRGB565(buff, bytes); 68 | break; 69 | case TextureFormat.R16: //test pass 70 | flag = DecodeR16(buff, bytes); 71 | break; 72 | case TextureFormat.DXT1: //test pass 73 | SwapBytesForXbox(buff); 74 | flag = DecodeDXT1(buff, bytes); 75 | break; 76 | case TextureFormat.DXT3: 77 | break; 78 | case TextureFormat.DXT5: //test pass 79 | SwapBytesForXbox(buff); 80 | flag = DecodeDXT5(buff, bytes); 81 | break; 82 | case TextureFormat.RGBA4444: //test pass 83 | flag = DecodeRGBA4444(buff, bytes); 84 | break; 85 | case TextureFormat.BGRA32: //test pass 86 | flag = DecodeBGRA32(buff, bytes); 87 | break; 88 | case TextureFormat.RHalf: 89 | flag = DecodeRHalf(buff, bytes); 90 | break; 91 | case TextureFormat.RGHalf: 92 | flag = DecodeRGHalf(buff, bytes); 93 | break; 94 | case TextureFormat.RGBAHalf: //test pass 95 | flag = DecodeRGBAHalf(buff, bytes); 96 | break; 97 | case TextureFormat.RFloat: 98 | flag = DecodeRFloat(buff, bytes); 99 | break; 100 | case TextureFormat.RGFloat: 101 | flag = DecodeRGFloat(buff, bytes); 102 | break; 103 | case TextureFormat.RGBAFloat: 104 | flag = DecodeRGBAFloat(buff, bytes); 105 | break; 106 | case TextureFormat.YUY2: //test pass 107 | flag = DecodeYUY2(buff, bytes); 108 | break; 109 | case TextureFormat.RGB9e5Float: //test pass 110 | flag = DecodeRGB9e5Float(buff, bytes); 111 | break; 112 | case TextureFormat.BC6H: //test pass 113 | flag = DecodeBC6H(buff, bytes); 114 | break; 115 | case TextureFormat.BC7: //test pass 116 | flag = DecodeBC7(buff, bytes); 117 | break; 118 | case TextureFormat.BC4: //test pass 119 | flag = DecodeBC4(buff, bytes); 120 | break; 121 | case TextureFormat.BC5: //test pass 122 | flag = DecodeBC5(buff, bytes); 123 | break; 124 | case TextureFormat.DXT1Crunched: //test pass 125 | flag = DecodeDXT1Crunched(buff, bytes); 126 | break; 127 | case TextureFormat.DXT5Crunched: //test pass 128 | flag = DecodeDXT5Crunched(buff, bytes); 129 | break; 130 | case TextureFormat.PVRTC_RGB2: //test pass 131 | case TextureFormat.PVRTC_RGBA2: //test pass 132 | flag = DecodePVRTC(buff, bytes, true); 133 | break; 134 | case TextureFormat.PVRTC_RGB4: //test pass 135 | case TextureFormat.PVRTC_RGBA4: //test pass 136 | flag = DecodePVRTC(buff, bytes, false); 137 | break; 138 | case TextureFormat.ETC_RGB4: //test pass 139 | case TextureFormat.ETC_RGB4_3DS: 140 | flag = DecodeETC1(buff, bytes); 141 | break; 142 | case TextureFormat.ATC_RGB4: //test pass 143 | flag = DecodeATCRGB4(buff, bytes); 144 | break; 145 | case TextureFormat.ATC_RGBA8: //test pass 146 | flag = DecodeATCRGBA8(buff, bytes); 147 | break; 148 | case TextureFormat.EAC_R: //test pass 149 | flag = DecodeEACR(buff, bytes); 150 | break; 151 | case TextureFormat.EAC_R_SIGNED: 152 | flag = DecodeEACRSigned(buff, bytes); 153 | break; 154 | case TextureFormat.EAC_RG: //test pass 155 | flag = DecodeEACRG(buff, bytes); 156 | break; 157 | case TextureFormat.EAC_RG_SIGNED: 158 | flag = DecodeEACRGSigned(buff, bytes); 159 | break; 160 | case TextureFormat.ETC2_RGB4: //test pass 161 | flag = DecodeETC2(buff, bytes); 162 | break; 163 | case TextureFormat.ETC2_RGBA1: //test pass 164 | flag = DecodeETC2A1(buff, bytes); 165 | break; 166 | case TextureFormat.ETC2_RGBA8: //test pass 167 | case TextureFormat.ETC_RGBA8_3DS: 168 | flag = DecodeETC2A8(buff, bytes); 169 | break; 170 | case TextureFormat.ASTC_RGB_4x4: //test pass 171 | case TextureFormat.ASTC_RGBA_4x4: //test pass 172 | case TextureFormat.ASTC_HDR_4x4: //test pass 173 | flag = DecodeASTC(buff, bytes, 4); 174 | break; 175 | case TextureFormat.ASTC_RGB_5x5: //test pass 176 | case TextureFormat.ASTC_RGBA_5x5: //test pass 177 | case TextureFormat.ASTC_HDR_5x5: //test pass 178 | flag = DecodeASTC(buff, bytes, 5); 179 | break; 180 | case TextureFormat.ASTC_RGB_6x6: //test pass 181 | case TextureFormat.ASTC_RGBA_6x6: //test pass 182 | case TextureFormat.ASTC_HDR_6x6: //test pass 183 | flag = DecodeASTC(buff, bytes, 6); 184 | break; 185 | case TextureFormat.ASTC_RGB_8x8: //test pass 186 | case TextureFormat.ASTC_RGBA_8x8: //test pass 187 | case TextureFormat.ASTC_HDR_8x8: //test pass 188 | flag = DecodeASTC(buff, bytes, 8); 189 | break; 190 | case TextureFormat.ASTC_RGB_10x10: //test pass 191 | case TextureFormat.ASTC_RGBA_10x10: //test pass 192 | case TextureFormat.ASTC_HDR_10x10: //test pass 193 | flag = DecodeASTC(buff, bytes, 10); 194 | break; 195 | case TextureFormat.ASTC_RGB_12x12: //test pass 196 | case TextureFormat.ASTC_RGBA_12x12: //test pass 197 | case TextureFormat.ASTC_HDR_12x12: //test pass 198 | flag = DecodeASTC(buff, bytes, 12); 199 | break; 200 | case TextureFormat.RG16: //test pass 201 | flag = DecodeRG16(buff, bytes); 202 | break; 203 | case TextureFormat.R8: //test pass 204 | flag = DecodeR8(buff, bytes); 205 | break; 206 | case TextureFormat.ETC_RGB4Crunched: //test pass 207 | flag = DecodeETC1Crunched(buff, bytes); 208 | break; 209 | case TextureFormat.ETC2_RGBA8Crunched: //test pass 210 | flag = DecodeETC2A8Crunched(buff, bytes); 211 | break; 212 | case TextureFormat.RG32: //test pass 213 | flag = DecodeRG32(buff, bytes); 214 | break; 215 | case TextureFormat.RGB48: //test pass 216 | flag = DecodeRGB48(buff, bytes); 217 | break; 218 | case TextureFormat.RGBA64: //test pass 219 | flag = DecodeRGBA64(buff, bytes); 220 | break; 221 | default: 222 | throw new NotSupportedException($"不支持的图片格式 {m_TextureFormat}"); 223 | } 224 | // BigArrayPool.Shared.Return(buff); 225 | return flag; 226 | } 227 | 228 | private void SwapBytesForXbox(byte[] image_data) 229 | { 230 | // if (platform == BuildTarget.XBOX360) 231 | // { 232 | // for (var i = 0; i < reader.Size / 2; i++) 233 | // { 234 | // var b = image_data[i * 2]; 235 | // image_data[i * 2] = image_data[i * 2 + 1]; 236 | // image_data[i * 2 + 1] = b; 237 | // } 238 | // } 239 | } 240 | 241 | private bool DecodeAlpha8(byte[] image_data, byte[] buff) 242 | { 243 | var size = m_Width * m_Height; 244 | var span = new Span(buff); 245 | span.Fill(0xFF); 246 | for (var i = 0; i < size; i++) 247 | { 248 | buff[i * 4 + 3] = image_data[i]; 249 | } 250 | return true; 251 | } 252 | 253 | private bool DecodeARGB4444(byte[] image_data, byte[] buff) 254 | { 255 | var size = m_Width * m_Height; 256 | var pixelNew = new byte[4]; 257 | for (var i = 0; i < size; i++) 258 | { 259 | var pixelOldShort = BitConverter.ToUInt16(image_data, i * 2); 260 | pixelNew[0] = (byte)(pixelOldShort & 0x000f); 261 | pixelNew[1] = (byte)((pixelOldShort & 0x00f0) >> 4); 262 | pixelNew[2] = (byte)((pixelOldShort & 0x0f00) >> 8); 263 | pixelNew[3] = (byte)((pixelOldShort & 0xf000) >> 12); 264 | for (var j = 0; j < 4; j++) 265 | pixelNew[j] = (byte)(pixelNew[j] << 4 | pixelNew[j]); 266 | pixelNew.CopyTo(buff, i * 4); 267 | } 268 | return true; 269 | } 270 | 271 | private bool DecodeRGB24(byte[] image_data, byte[] buff) 272 | { 273 | var size = m_Width * m_Height; 274 | for (var i = 0; i < size; i++) 275 | { 276 | buff[i * 4] = image_data[i * 3 + 2]; 277 | buff[i * 4 + 1] = image_data[i * 3 + 1]; 278 | buff[i * 4 + 2] = image_data[i * 3 + 0]; 279 | buff[i * 4 + 3] = 255; 280 | } 281 | return true; 282 | } 283 | 284 | private bool DecodeRGBA32(byte[] image_data, byte[] buff) 285 | { 286 | for (var i = 0; i < outPutSize; i += 4) 287 | { 288 | buff[i] = image_data[i + 2]; 289 | buff[i + 1] = image_data[i + 1]; 290 | buff[i + 2] = image_data[i + 0]; 291 | buff[i + 3] = image_data[i + 3]; 292 | } 293 | return true; 294 | } 295 | 296 | private bool DecodeARGB32(byte[] image_data, byte[] buff) 297 | { 298 | for (var i = 0; i < outPutSize; i += 4) 299 | { 300 | buff[i] = image_data[i + 3]; 301 | buff[i + 1] = image_data[i + 2]; 302 | buff[i + 2] = image_data[i + 1]; 303 | buff[i + 3] = image_data[i + 0]; 304 | } 305 | return true; 306 | } 307 | 308 | private bool DecodeRGB565(byte[] image_data, byte[] buff) 309 | { 310 | var size = m_Width * m_Height; 311 | for (var i = 0; i < size; i++) 312 | { 313 | var p = BitConverter.ToUInt16(image_data, i * 2); 314 | buff[i * 4] = (byte)(p << 3 | p >> 2 & 7); 315 | buff[i * 4 + 1] = (byte)(p >> 3 & 0xfc | p >> 9 & 3); 316 | buff[i * 4 + 2] = (byte)(p >> 8 & 0xf8 | p >> 13); 317 | buff[i * 4 + 3] = 255; 318 | } 319 | return true; 320 | } 321 | 322 | private bool DecodeR16(byte[] image_data, byte[] buff) 323 | { 324 | var size = m_Width * m_Height; 325 | for (var i = 0; i < size; i++) 326 | { 327 | buff[i * 4] = 0; //b 328 | buff[i * 4 + 1] = 0; //g 329 | buff[i * 4 + 2] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i * 2)); //r 330 | buff[i * 4 + 3] = 255; //a 331 | } 332 | return true; 333 | } 334 | 335 | private bool DecodeDXT1(byte[] image_data, byte[] buff) 336 | { 337 | return TextureDecoder.DecodeDXT1(image_data, m_Width, m_Height, buff); 338 | } 339 | 340 | private bool DecodeDXT5(byte[] image_data, byte[] buff) 341 | { 342 | return TextureDecoder.DecodeDXT5(image_data, m_Width, m_Height, buff); 343 | } 344 | 345 | private bool DecodeRGBA4444(byte[] image_data, byte[] buff) 346 | { 347 | var size = m_Width * m_Height; 348 | var pixelNew = new byte[4]; 349 | for (var i = 0; i < size; i++) 350 | { 351 | var pixelOldShort = BitConverter.ToUInt16(image_data, i * 2); 352 | pixelNew[0] = (byte)((pixelOldShort & 0x00f0) >> 4); 353 | pixelNew[1] = (byte)((pixelOldShort & 0x0f00) >> 8); 354 | pixelNew[2] = (byte)((pixelOldShort & 0xf000) >> 12); 355 | pixelNew[3] = (byte)(pixelOldShort & 0x000f); 356 | for (var j = 0; j < 4; j++) 357 | pixelNew[j] = (byte)(pixelNew[j] << 4 | pixelNew[j]); 358 | pixelNew.CopyTo(buff, i * 4); 359 | } 360 | return true; 361 | } 362 | 363 | private bool DecodeBGRA32(byte[] image_data, byte[] buff) 364 | { 365 | for (var i = 0; i < outPutSize; i += 4) 366 | { 367 | buff[i] = image_data[i]; 368 | buff[i + 1] = image_data[i + 1]; 369 | buff[i + 2] = image_data[i + 2]; 370 | buff[i + 3] = image_data[i + 3]; 371 | } 372 | return true; 373 | } 374 | 375 | private bool DecodeRHalf(byte[] image_data, byte[] buff) 376 | { 377 | for (var i = 0; i < outPutSize; i += 4) 378 | { 379 | buff[i] = 0; 380 | buff[i + 1] = 0; 381 | buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i / 2) * 255f); 382 | buff[i + 3] = 255; 383 | } 384 | return true; 385 | } 386 | 387 | private bool DecodeRGHalf(byte[] image_data, byte[] buff) 388 | { 389 | for (var i = 0; i < outPutSize; i += 4) 390 | { 391 | buff[i] = 0; 392 | buff[i + 1] = (byte)Math.Round(Half.ToHalf(image_data, i + 2) * 255f); 393 | buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i) * 255f); 394 | buff[i + 3] = 255; 395 | } 396 | return true; 397 | } 398 | 399 | private bool DecodeRGBAHalf(byte[] image_data, byte[] buff) 400 | { 401 | for (var i = 0; i < outPutSize; i += 4) 402 | { 403 | buff[i] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 4) * 255f); 404 | buff[i + 1] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 2) * 255f); 405 | buff[i + 2] = (byte)Math.Round(Half.ToHalf(image_data, i * 2) * 255f); 406 | buff[i + 3] = (byte)Math.Round(Half.ToHalf(image_data, i * 2 + 6) * 255f); 407 | } 408 | return true; 409 | } 410 | 411 | private bool DecodeRFloat(byte[] image_data, byte[] buff) 412 | { 413 | for (var i = 0; i < outPutSize; i += 4) 414 | { 415 | buff[i] = 0; 416 | buff[i + 1] = 0; 417 | buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i) * 255f); 418 | buff[i + 3] = 255; 419 | } 420 | return true; 421 | } 422 | 423 | private bool DecodeRGFloat(byte[] image_data, byte[] buff) 424 | { 425 | for (var i = 0; i < outPutSize; i += 4) 426 | { 427 | buff[i] = 0; 428 | buff[i + 1] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 2 + 4) * 255f); 429 | buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 2) * 255f); 430 | buff[i + 3] = 255; 431 | } 432 | return true; 433 | } 434 | 435 | private bool DecodeRGBAFloat(byte[] image_data, byte[] buff) 436 | { 437 | for (var i = 0; i < outPutSize; i += 4) 438 | { 439 | buff[i] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 8) * 255f); 440 | buff[i + 1] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 4) * 255f); 441 | buff[i + 2] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4) * 255f); 442 | buff[i + 3] = (byte)Math.Round(BitConverter.ToSingle(image_data, i * 4 + 12) * 255f); 443 | } 444 | return true; 445 | } 446 | 447 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 448 | private static byte ClampByte(int x) 449 | { 450 | return (byte)(byte.MaxValue < x ? byte.MaxValue : x > byte.MinValue ? x : byte.MinValue); 451 | } 452 | 453 | private bool DecodeYUY2(byte[] image_data, byte[] buff) 454 | { 455 | int p = 0; 456 | int o = 0; 457 | int halfWidth = m_Width / 2; 458 | for (int j = 0; j < m_Height; j++) 459 | { 460 | for (int i = 0; i < halfWidth; ++i) 461 | { 462 | int y0 = image_data[p++]; 463 | int u0 = image_data[p++]; 464 | int y1 = image_data[p++]; 465 | int v0 = image_data[p++]; 466 | int c = y0 - 16; 467 | int d = u0 - 128; 468 | int e = v0 - 128; 469 | buff[o++] = ClampByte(298 * c + 516 * d + 128 >> 8); // b 470 | buff[o++] = ClampByte(298 * c - 100 * d - 208 * e + 128 >> 8); // g 471 | buff[o++] = ClampByte(298 * c + 409 * e + 128 >> 8); // r 472 | buff[o++] = 255; 473 | c = y1 - 16; 474 | buff[o++] = ClampByte(298 * c + 516 * d + 128 >> 8); // b 475 | buff[o++] = ClampByte(298 * c - 100 * d - 208 * e + 128 >> 8); // g 476 | buff[o++] = ClampByte(298 * c + 409 * e + 128 >> 8); // r 477 | buff[o++] = 255; 478 | } 479 | } 480 | return true; 481 | } 482 | 483 | private bool DecodeRGB9e5Float(byte[] image_data, byte[] buff) 484 | { 485 | for (var i = 0; i < outPutSize; i += 4) 486 | { 487 | var n = BitConverter.ToInt32(image_data, i); 488 | var scale = n >> 27 & 0x1f; 489 | var scalef = Math.Pow(2, scale - 24); 490 | var b = n >> 18 & 0x1ff; 491 | var g = n >> 9 & 0x1ff; 492 | var r = n & 0x1ff; 493 | buff[i] = (byte)Math.Round(b * scalef * 255f); 494 | buff[i + 1] = (byte)Math.Round(g * scalef * 255f); 495 | buff[i + 2] = (byte)Math.Round(r * scalef * 255f); 496 | buff[i + 3] = 255; 497 | } 498 | return true; 499 | } 500 | 501 | private bool DecodeBC4(byte[] image_data, byte[] buff) 502 | { 503 | return TextureDecoder.DecodeBC4(image_data, m_Width, m_Height, buff); 504 | } 505 | 506 | private bool DecodeBC5(byte[] image_data, byte[] buff) 507 | { 508 | return TextureDecoder.DecodeBC5(image_data, m_Width, m_Height, buff); 509 | } 510 | 511 | private bool DecodeBC6H(byte[] image_data, byte[] buff) 512 | { 513 | return TextureDecoder.DecodeBC6(image_data, m_Width, m_Height, buff); 514 | } 515 | 516 | private bool DecodeBC7(byte[] image_data, byte[] buff) 517 | { 518 | return TextureDecoder.DecodeBC7(image_data, m_Width, m_Height, buff); 519 | } 520 | 521 | private bool DecodeDXT1Crunched(byte[] image_data, byte[] buff) 522 | { 523 | if (UnpackCrunch(image_data, out var result)) 524 | { 525 | if (DecodeDXT1(result, buff)) 526 | { 527 | return true; 528 | } 529 | } 530 | return false; 531 | } 532 | 533 | private bool DecodeDXT5Crunched(byte[] image_data, byte[] buff) 534 | { 535 | if (UnpackCrunch(image_data, out var result)) 536 | { 537 | if (DecodeDXT5(result, buff)) 538 | { 539 | return true; 540 | } 541 | } 542 | return false; 543 | } 544 | 545 | private bool DecodePVRTC(byte[] image_data, byte[] buff, bool is2bpp) 546 | { 547 | return TextureDecoder.DecodePVRTC(image_data, m_Width, m_Height, buff, is2bpp); 548 | } 549 | 550 | private bool DecodeETC1(byte[] image_data, byte[] buff) 551 | { 552 | return TextureDecoder.DecodeETC1(image_data, m_Width, m_Height, buff); 553 | } 554 | 555 | private bool DecodeATCRGB4(byte[] image_data, byte[] buff) 556 | { 557 | return TextureDecoder.DecodeATCRGB4(image_data, m_Width, m_Height, buff); 558 | } 559 | 560 | private bool DecodeATCRGBA8(byte[] image_data, byte[] buff) 561 | { 562 | return TextureDecoder.DecodeATCRGBA8(image_data, m_Width, m_Height, buff); 563 | } 564 | 565 | private bool DecodeEACR(byte[] image_data, byte[] buff) 566 | { 567 | return TextureDecoder.DecodeEACR(image_data, m_Width, m_Height, buff); 568 | } 569 | 570 | private bool DecodeEACRSigned(byte[] image_data, byte[] buff) 571 | { 572 | return TextureDecoder.DecodeEACRSigned(image_data, m_Width, m_Height, buff); 573 | } 574 | 575 | private bool DecodeEACRG(byte[] image_data, byte[] buff) 576 | { 577 | return TextureDecoder.DecodeEACRG(image_data, m_Width, m_Height, buff); 578 | } 579 | 580 | private bool DecodeEACRGSigned(byte[] image_data, byte[] buff) 581 | { 582 | return TextureDecoder.DecodeEACRGSigned(image_data, m_Width, m_Height, buff); 583 | } 584 | 585 | private bool DecodeETC2(byte[] image_data, byte[] buff) 586 | { 587 | return TextureDecoder.DecodeETC2(image_data, m_Width, m_Height, buff); 588 | } 589 | 590 | private bool DecodeETC2A1(byte[] image_data, byte[] buff) 591 | { 592 | return TextureDecoder.DecodeETC2A1(image_data, m_Width, m_Height, buff); 593 | } 594 | 595 | private bool DecodeETC2A8(byte[] image_data, byte[] buff) 596 | { 597 | return TextureDecoder.DecodeETC2A8(image_data, m_Width, m_Height, buff); 598 | } 599 | 600 | private bool DecodeASTC(byte[] image_data, byte[] buff, int blocksize) 601 | { 602 | return TextureDecoder.DecodeASTC(image_data, m_Width, m_Height, blocksize, blocksize, buff); 603 | } 604 | 605 | private bool DecodeRG16(byte[] image_data, byte[] buff) 606 | { 607 | var size = m_Width * m_Height; 608 | for (var i = 0; i < size; i++) 609 | { 610 | buff[i * 4] = 0; //B 611 | buff[i * 4 + 1] = image_data[i * 2 + 1];//G 612 | buff[i * 4 + 2] = image_data[i * 2];//R 613 | buff[i * 4 + 3] = 255;//A 614 | } 615 | return true; 616 | } 617 | 618 | private bool DecodeR8(byte[] image_data, byte[] buff) 619 | { 620 | var size = m_Width * m_Height; 621 | for (var i = 0; i < size; i++) 622 | { 623 | buff[i * 4] = 0; //B 624 | buff[i * 4 + 1] = 0; //G 625 | buff[i * 4 + 2] = image_data[i];//R 626 | buff[i * 4 + 3] = 255;//A 627 | } 628 | return true; 629 | } 630 | 631 | private bool DecodeETC1Crunched(byte[] image_data, byte[] buff) 632 | { 633 | if (UnpackCrunch(image_data, out var result)) 634 | { 635 | if (DecodeETC1(result, buff)) 636 | { 637 | return true; 638 | } 639 | } 640 | return false; 641 | } 642 | 643 | private bool DecodeETC2A8Crunched(byte[] image_data, byte[] buff) 644 | { 645 | if (UnpackCrunch(image_data, out var result)) 646 | { 647 | if (DecodeETC2A8(result, buff)) 648 | { 649 | return true; 650 | } 651 | } 652 | return false; 653 | } 654 | 655 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 656 | public static byte DownScaleFrom16BitTo8Bit(ushort component) 657 | { 658 | return (byte)(component * 255 + 32895 >> 16); 659 | } 660 | 661 | private bool DecodeRG32(byte[] image_data, byte[] buff) 662 | { 663 | for (var i = 0; i < outPutSize; i += 4) 664 | { 665 | buff[i] = 0; //b 666 | buff[i + 1] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i + 2)); //g 667 | buff[i + 2] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i)); //r 668 | buff[i + 3] = byte.MaxValue; //a 669 | } 670 | return true; 671 | } 672 | 673 | private bool DecodeRGB48(byte[] image_data, byte[] buff) 674 | { 675 | var size = m_Width * m_Height; 676 | for (var i = 0; i < size; i++) 677 | { 678 | buff[i * 4] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i * 6 + 4)); //b 679 | buff[i * 4 + 1] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i * 6 + 2)); //g 680 | buff[i * 4 + 2] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i * 6)); //r 681 | buff[i * 4 + 3] = byte.MaxValue; //a 682 | } 683 | return true; 684 | } 685 | 686 | private bool DecodeRGBA64(byte[] image_data, byte[] buff) 687 | { 688 | for (var i = 0; i < outPutSize; i += 4) 689 | { 690 | buff[i] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i * 2 + 4)); //b 691 | buff[i + 1] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i * 2 + 2)); //g 692 | buff[i + 2] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i * 2)); //r 693 | buff[i + 3] = DownScaleFrom16BitTo8Bit(BitConverter.ToUInt16(image_data, i * 2 + 6)); //a 694 | } 695 | return true; 696 | } 697 | 698 | private bool UnpackCrunch(byte[] image_data, out byte[] result) 699 | { 700 | if (version.major > 2017 || version.major == 2017 && version.minor >= 3 //2017.3 and up 701 | || m_TextureFormat == TextureFormat.ETC_RGB4Crunched 702 | || m_TextureFormat == TextureFormat.ETC2_RGBA8Crunched) 703 | { 704 | result = TextureDecoder.UnpackUnityCrunch(image_data); 705 | } 706 | else 707 | { 708 | result = TextureDecoder.UnpackCrunch(image_data); 709 | } 710 | if (result != null) 711 | { 712 | return true; 713 | } 714 | return false; 715 | } 716 | 717 | } 718 | } 719 | -------------------------------------------------------------------------------- /ExporterSetting.cs: -------------------------------------------------------------------------------- 1 | using AssetStudioExporter.AssetTypes; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace AssetStudioExporter 9 | { 10 | public class ExporterSetting 11 | { 12 | public static ExporterSetting Default { get; set; } = new ExporterSetting() 13 | { 14 | 15 | }; 16 | 17 | /// 18 | /// 可导出为图片的资源(如, Sprite等)的文件格式
19 | /// 部分生成多个文件的组合资源可能不会遵循这个值 20 | ///
21 | public ImageFormat ImageExportFormat { get; set; } = ImageFormat.Png; 22 | 23 | /// 24 | /// 指定图片纹理的解码器
25 | /// 26 | /// : 格式全面,但需要加载Native Dll 27 | /// : 托管实现,格式较少,并且有大量指针算法并不安全 28 | /// 29 | ///
30 | public TextureDecoderType TextureDecoder { get; set; } = TextureDecoderType.AssetRipper; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Formats.cs: -------------------------------------------------------------------------------- 1 | namespace AssetStudioExporter 2 | { 3 | public enum ImageFormat 4 | { 5 | Auto = 0, 6 | 7 | Png = 1,// 默认 8 | Jpeg, 9 | Webp, 10 | Tga, 11 | Tiff, 12 | Bmp, 13 | Gif 14 | } 15 | 16 | public enum AudioFormat 17 | { 18 | Auto = 0, 19 | 20 | Wav = 1,// 默认 21 | Ogg, 22 | Mp3 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Internal/VersionCache.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET.Extra; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace AssetStudioExporter.Internal; 5 | 6 | [Obsolete("没必要空间换时间")] 7 | internal static class VersionCache 8 | { 9 | static readonly ConditionalWeakTable cache = new(); 10 | 11 | public static UnityVersion GetVersion(AssetsFileInstance instance) 12 | { 13 | if (!cache.TryGetValue(instance, out var version)) 14 | { 15 | version = new UnityVersion(instance.file.Metadata.UnityVersion); 16 | cache.AddOrUpdate(instance, version); 17 | } 18 | return version; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Native/DllLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | 7 | namespace AssetStudioExporter.Native 8 | { 9 | /// 10 | /// AssetStudio原版的loader,在Posix平上会报找不到libdl 11 | /// 12 | [Obsolete("Use DllLoaderNative instead.")] 13 | public static class DllLoader 14 | { 15 | 16 | public static void PreloadDll(string dllName) 17 | { 18 | var dllDir = GetDirectedDllDirectory(); 19 | 20 | // Not using OperatingSystem.Platform. 21 | // See: https://www.mono-project.com/docs/faq/technical/#how-to-detect-the-execution-platform 22 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 23 | { 24 | Win32.LoadDll(dllDir, dllName); 25 | } 26 | else 27 | { 28 | Posix.LoadDll(dllDir, dllName); 29 | } 30 | } 31 | 32 | private static string GetDirectedDllDirectory() 33 | { 34 | //var localPath = Process.GetCurrentProcess().MainModule.FileName; 35 | //var localDir = Path.GetDirectoryName(localPath); 36 | var localDir = AppContext.BaseDirectory; 37 | 38 | var subDir = Environment.Is64BitProcess ? "x64" : "x86"; 39 | 40 | var directedDllDir = Path.Combine(localDir, subDir); 41 | 42 | return directedDllDir; 43 | } 44 | 45 | private static class Win32 46 | { 47 | 48 | internal static void LoadDll(string dllDir, string dllName) 49 | { 50 | var dllFileName = $"{dllName}.dll"; 51 | var directedDllPath = Path.Combine(dllDir, dllFileName); 52 | 53 | // Specify SEARCH_DLL_LOAD_DIR to load dependent libraries located in the same platform-specific directory. 54 | var hLibrary = LoadLibraryEx(directedDllPath, IntPtr.Zero, LOAD_LIBRARY_SEARCH_DEFAULT_DIRS | LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR); 55 | 56 | if (hLibrary == IntPtr.Zero) 57 | { 58 | var errorCode = Marshal.GetLastWin32Error(); 59 | var exception = new Win32Exception(errorCode); 60 | 61 | throw new DllNotFoundException(exception.Message, exception); 62 | } 63 | } 64 | 65 | // HMODULE LoadLibraryExA(LPCSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); 66 | // HMODULE LoadLibraryExW(LPCWSTR lpLibFileName, HANDLE hFile, DWORD dwFlags); 67 | [DllImport("kernel32.dll", SetLastError = true)] 68 | private static extern IntPtr LoadLibraryEx(string lpLibFileName, IntPtr hFile, uint dwFlags); 69 | 70 | private const uint LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x1000; 71 | private const uint LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x100; 72 | 73 | } 74 | 75 | private static class Posix 76 | { 77 | 78 | internal static void LoadDll(string dllDir, string dllName) 79 | { 80 | string dllExtension; 81 | 82 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 83 | { 84 | dllExtension = ".so"; 85 | } 86 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 87 | { 88 | dllExtension = ".dylib"; 89 | } 90 | else 91 | { 92 | throw new NotSupportedException(); 93 | } 94 | 95 | var dllFileName = $"lib{dllName}{dllExtension}"; 96 | var directedDllPath = Path.Combine(dllDir, dllFileName); 97 | 98 | const int ldFlags = RTLD_NOW | RTLD_GLOBAL; 99 | var hLibrary = DlOpen(directedDllPath, ldFlags); 100 | 101 | if (hLibrary == IntPtr.Zero) 102 | { 103 | var pErrStr = DlError(); 104 | // `PtrToStringAnsi` always uses the specific constructor of `String` (see dotnet/core#2325), 105 | // which in turn interprets the byte sequence with system default codepage. On OSX and Linux 106 | // the codepage is UTF-8 so the error message should be handled correctly. 107 | var errorMessage = Marshal.PtrToStringAnsi(pErrStr); 108 | 109 | throw new DllNotFoundException(errorMessage); 110 | } 111 | } 112 | 113 | // OSX and most Linux OS use LP64 so `int` is still 32-bit even on 64-bit platforms. 114 | // void *dlopen(const char *filename, int flag); 115 | [DllImport("libdl", EntryPoint = "dlopen")] 116 | private static extern IntPtr DlOpen([MarshalAs(UnmanagedType.LPStr)] string fileName, int flags); 117 | 118 | // char *dlerror(void); 119 | [DllImport("libdl", EntryPoint = "dlerror")] 120 | private static extern IntPtr DlError(); 121 | 122 | private const int RTLD_LAZY = 0x1; 123 | private const int RTLD_NOW = 0x2; 124 | private const int RTLD_GLOBAL = 0x100; 125 | 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /Native/DllLoaderNative.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace AssetStudioExporter.Native 5 | { 6 | /// 7 | /// 使用原生方法加载的loader 8 | /// 9 | public static class DllLoaderNative 10 | { 11 | private static string GetDirectedDllDirectory() 12 | { 13 | var localPath = Assembly.GetExecutingAssembly().Location; 14 | var localDir = Path.GetDirectoryName(localPath); 15 | 16 | var subDir = Environment.Is64BitProcess ? "x64" : "x86"; 17 | 18 | var directedDllDir = Path.Combine(localDir, subDir); 19 | 20 | return directedDllDir; 21 | } 22 | 23 | private static string GetFileName(string dllName) 24 | { 25 | if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) 26 | { 27 | return $"{dllName}.dll"; 28 | } 29 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) 30 | { 31 | return $"lib{dllName}.so"; 32 | } 33 | else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) 34 | { 35 | return $"lib{dllName}.dylib"; 36 | } 37 | else 38 | { 39 | throw new PlatformNotSupportedException(); 40 | } 41 | } 42 | 43 | public static void PreloadDll(string dllName) 44 | { 45 | var name = Path.Combine(GetDirectedDllDirectory(), GetFileName(dllName)); 46 | Console.WriteLine($"dll位置:{name}"); 47 | NativeLibrary.Load(name); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Native/FMOD/fmod_errors.cs: -------------------------------------------------------------------------------- 1 | /* =================================================================================================== */ 2 | /* FMOD Studio - Error string header file. Copyright (c), Firelight Technologies Pty, Ltd. 2004-2016. */ 3 | /* */ 4 | /* Use this header if you want to store or display a string version / english explanation of */ 5 | /* the FMOD error codes. */ 6 | /* */ 7 | /* =================================================================================================== */ 8 | 9 | namespace FMOD 10 | { 11 | public class Error 12 | { 13 | public static string String(FMOD.RESULT errcode) 14 | { 15 | switch (errcode) 16 | { 17 | case FMOD.RESULT.OK: return "No errors."; 18 | case FMOD.RESULT.ERR_BADCOMMAND: return "Tried to call a function on a data type that does not allow this type of functionality (ie calling Sound::lock on a streaming sound)."; 19 | case FMOD.RESULT.ERR_CHANNEL_ALLOC: return "Error trying to allocate a channel."; 20 | case FMOD.RESULT.ERR_CHANNEL_STOLEN: return "The specified channel has been reused to play another sound."; 21 | case FMOD.RESULT.ERR_DMA: return "DMA Failure. See debug output for more information."; 22 | case FMOD.RESULT.ERR_DSP_CONNECTION: return "DSP connection error. Connection possibly caused a cyclic dependency or connected dsps with incompatible buffer counts."; 23 | case FMOD.RESULT.ERR_DSP_DONTPROCESS: return "DSP return code from a DSP process query callback. Tells mixer not to call the process callback and therefore not consume CPU. Use this to optimize the DSP graph."; 24 | case FMOD.RESULT.ERR_DSP_FORMAT: return "DSP Format error. A DSP unit may have attempted to connect to this network with the wrong format, or a matrix may have been set with the wrong size if the target unit has a specified channel map."; 25 | case FMOD.RESULT.ERR_DSP_INUSE: return "DSP is already in the mixer's DSP network. It must be removed before being reinserted or released."; 26 | case FMOD.RESULT.ERR_DSP_NOTFOUND: return "DSP connection error. Couldn't find the DSP unit specified."; 27 | case FMOD.RESULT.ERR_DSP_RESERVED: return "DSP operation error. Cannot perform operation on this DSP as it is reserved by the system."; 28 | case FMOD.RESULT.ERR_DSP_SILENCE: return "DSP return code from a DSP process query callback. Tells mixer silence would be produced from read, so go idle and not consume CPU. Use this to optimize the DSP graph."; 29 | case FMOD.RESULT.ERR_DSP_TYPE: return "DSP operation cannot be performed on a DSP of this type."; 30 | case FMOD.RESULT.ERR_FILE_BAD: return "Error loading file."; 31 | case FMOD.RESULT.ERR_FILE_COULDNOTSEEK: return "Couldn't perform seek operation. This is a limitation of the medium (ie netstreams) or the file format."; 32 | case FMOD.RESULT.ERR_FILE_DISKEJECTED: return "Media was ejected while reading."; 33 | case FMOD.RESULT.ERR_FILE_EOF: return "End of file unexpectedly reached while trying to read essential data (truncated?)."; 34 | case FMOD.RESULT.ERR_FILE_ENDOFDATA: return "End of current chunk reached while trying to read data."; 35 | case FMOD.RESULT.ERR_FILE_NOTFOUND: return "File not found."; 36 | case FMOD.RESULT.ERR_FORMAT: return "Unsupported file or audio format."; 37 | case FMOD.RESULT.ERR_HEADER_MISMATCH: return "There is a version mismatch between the FMOD header and either the FMOD Studio library or the FMOD Low Level library."; 38 | case FMOD.RESULT.ERR_HTTP: return "A HTTP error occurred. This is a catch-all for HTTP errors not listed elsewhere."; 39 | case FMOD.RESULT.ERR_HTTP_ACCESS: return "The specified resource requires authentication or is forbidden."; 40 | case FMOD.RESULT.ERR_HTTP_PROXY_AUTH: return "Proxy authentication is required to access the specified resource."; 41 | case FMOD.RESULT.ERR_HTTP_SERVER_ERROR: return "A HTTP server error occurred."; 42 | case FMOD.RESULT.ERR_HTTP_TIMEOUT: return "The HTTP request timed out."; 43 | case FMOD.RESULT.ERR_INITIALIZATION: return "FMOD was not initialized correctly to support this function."; 44 | case FMOD.RESULT.ERR_INITIALIZED: return "Cannot call this command after System::init."; 45 | case FMOD.RESULT.ERR_INTERNAL: return "An error occurred that wasn't supposed to. Contact support."; 46 | case FMOD.RESULT.ERR_INVALID_FLOAT: return "Value passed in was a NaN, Inf or denormalized float."; 47 | case FMOD.RESULT.ERR_INVALID_HANDLE: return "An invalid object handle was used."; 48 | case FMOD.RESULT.ERR_INVALID_PARAM: return "An invalid parameter was passed to this function."; 49 | case FMOD.RESULT.ERR_INVALID_POSITION: return "An invalid seek position was passed to this function."; 50 | case FMOD.RESULT.ERR_INVALID_SPEAKER: return "An invalid speaker was passed to this function based on the current speaker mode."; 51 | case FMOD.RESULT.ERR_INVALID_SYNCPOINT: return "The syncpoint did not come from this sound handle."; 52 | case FMOD.RESULT.ERR_INVALID_THREAD: return "Tried to call a function on a thread that is not supported."; 53 | case FMOD.RESULT.ERR_INVALID_VECTOR: return "The vectors passed in are not unit length, or perpendicular."; 54 | case FMOD.RESULT.ERR_MAXAUDIBLE: return "Reached maximum audible playback count for this sound's soundgroup."; 55 | case FMOD.RESULT.ERR_MEMORY: return "Not enough memory or resources."; 56 | case FMOD.RESULT.ERR_MEMORY_CANTPOINT: return "Can't use FMOD_OPENMEMORY_POINT on non PCM source data, or non mp3/xma/adpcm data if FMOD_CREATECOMPRESSEDSAMPLE was used."; 57 | case FMOD.RESULT.ERR_NEEDS3D: return "Tried to call a command on a 2d sound when the command was meant for 3d sound."; 58 | case FMOD.RESULT.ERR_NEEDSHARDWARE: return "Tried to use a feature that requires hardware support."; 59 | case FMOD.RESULT.ERR_NET_CONNECT: return "Couldn't connect to the specified host."; 60 | case FMOD.RESULT.ERR_NET_SOCKET_ERROR: return "A socket error occurred. This is a catch-all for socket-related errors not listed elsewhere."; 61 | case FMOD.RESULT.ERR_NET_URL: return "The specified URL couldn't be resolved."; 62 | case FMOD.RESULT.ERR_NET_WOULD_BLOCK: return "Operation on a non-blocking socket could not complete immediately."; 63 | case FMOD.RESULT.ERR_NOTREADY: return "Operation could not be performed because specified sound/DSP connection is not ready."; 64 | case FMOD.RESULT.ERR_OUTPUT_ALLOCATED: return "Error initializing output device, but more specifically, the output device is already in use and cannot be reused."; 65 | case FMOD.RESULT.ERR_OUTPUT_CREATEBUFFER: return "Error creating hardware sound buffer."; 66 | case FMOD.RESULT.ERR_OUTPUT_DRIVERCALL: return "A call to a standard soundcard driver failed, which could possibly mean a bug in the driver or resources were missing or exhausted."; 67 | case FMOD.RESULT.ERR_OUTPUT_FORMAT: return "Soundcard does not support the specified format."; 68 | case FMOD.RESULT.ERR_OUTPUT_INIT: return "Error initializing output device."; 69 | case FMOD.RESULT.ERR_OUTPUT_NODRIVERS: return "The output device has no drivers installed. If pre-init, FMOD_OUTPUT_NOSOUND is selected as the output mode. If post-init, the function just fails."; 70 | case FMOD.RESULT.ERR_PLUGIN: return "An unspecified error has been returned from a plugin."; 71 | case FMOD.RESULT.ERR_PLUGIN_MISSING: return "A requested output, dsp unit type or codec was not available."; 72 | case FMOD.RESULT.ERR_PLUGIN_RESOURCE: return "A resource that the plugin requires cannot be found. (ie the DLS file for MIDI playback)"; 73 | case FMOD.RESULT.ERR_PLUGIN_VERSION: return "A plugin was built with an unsupported SDK version."; 74 | case FMOD.RESULT.ERR_RECORD: return "An error occurred trying to initialize the recording device."; 75 | case FMOD.RESULT.ERR_REVERB_CHANNELGROUP: return "Reverb properties cannot be set on this channel because a parent channelgroup owns the reverb connection."; 76 | case FMOD.RESULT.ERR_REVERB_INSTANCE: return "Specified instance in FMOD_REVERB_PROPERTIES couldn't be set. Most likely because it is an invalid instance number or the reverb doesn't exist."; 77 | case FMOD.RESULT.ERR_SUBSOUNDS: return "The error occurred because the sound referenced contains subsounds when it shouldn't have, or it doesn't contain subsounds when it should have. The operation may also not be able to be performed on a parent sound."; 78 | case FMOD.RESULT.ERR_SUBSOUND_ALLOCATED: return "This subsound is already being used by another sound, you cannot have more than one parent to a sound. Null out the other parent's entry first."; 79 | case FMOD.RESULT.ERR_SUBSOUND_CANTMOVE: return "Shared subsounds cannot be replaced or moved from their parent stream, such as when the parent stream is an FSB file."; 80 | case FMOD.RESULT.ERR_TAGNOTFOUND: return "The specified tag could not be found or there are no tags."; 81 | case FMOD.RESULT.ERR_TOOMANYCHANNELS: return "The sound created exceeds the allowable input channel count. This can be increased using the 'maxinputchannels' parameter in System::setSoftwareFormat."; 82 | case FMOD.RESULT.ERR_TRUNCATED: return "The retrieved string is too long to fit in the supplied buffer and has been truncated."; 83 | case FMOD.RESULT.ERR_UNIMPLEMENTED: return "Something in FMOD hasn't been implemented when it should be! contact support!"; 84 | case FMOD.RESULT.ERR_UNINITIALIZED: return "This command failed because System::init or System::setDriver was not called."; 85 | case FMOD.RESULT.ERR_UNSUPPORTED: return "A command issued was not supported by this object. Possibly a plugin without certain callbacks specified."; 86 | case FMOD.RESULT.ERR_VERSION: return "The version number of this file format is not supported."; 87 | case FMOD.RESULT.ERR_EVENT_ALREADY_LOADED: return "The specified bank has already been loaded."; 88 | case FMOD.RESULT.ERR_EVENT_LIVEUPDATE_BUSY: return "The live update connection failed due to the game already being connected."; 89 | case FMOD.RESULT.ERR_EVENT_LIVEUPDATE_MISMATCH: return "The live update connection failed due to the game data being out of sync with the tool."; 90 | case FMOD.RESULT.ERR_EVENT_LIVEUPDATE_TIMEOUT: return "The live update connection timed out."; 91 | case FMOD.RESULT.ERR_EVENT_NOTFOUND: return "The requested event, bus or vca could not be found."; 92 | case FMOD.RESULT.ERR_STUDIO_UNINITIALIZED: return "The Studio::System object is not yet initialized."; 93 | case FMOD.RESULT.ERR_STUDIO_NOT_LOADED: return "The specified resource is not loaded, so it can't be unloaded."; 94 | case FMOD.RESULT.ERR_INVALID_STRING: return "An invalid string was passed to this function."; 95 | case FMOD.RESULT.ERR_ALREADY_LOCKED: return "The specified resource is already locked."; 96 | case FMOD.RESULT.ERR_NOT_LOCKED: return "The specified resource is not locked, so it can't be unlocked."; 97 | case FMOD.RESULT.ERR_RECORD_DISCONNECTED: return "The specified recording driver has been disconnected."; 98 | case FMOD.RESULT.ERR_TOOMANYSAMPLES: return "The length provided exceed the allowable limit."; 99 | default: return "Unknown error."; 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AssetStudioExporter 2 | 3 | 适配 [AssetsTools.NET](https://github.com/nesrak1/AssetsTools.NET) V3(AT3) 的AssetBundle导出工具,基于AssetStudio进行修改 4 | 5 | > 本仓库是生成的是.NET库,用于简化使用AssetsTools.NET编写解包代码的逻辑,并不是一个独立的可执行文件,请先使用其他工具探索AssetBundle(AB)的结构并定位所需内容 6 | 7 | --- 8 | 9 | ## 特性 10 | 11 | * 同时支持AssetFile和AssetBundle文件 12 | * 经过简单的封装,以简单的方式导出`Texture2D`, `AudioClip`, `Font`等各类资源 13 | * 更好的`Texture2D` 14 | * 全面的图片纹理格式支持,能导出几乎所有种类的`Texture2D`。 15 | * 同时带有AssetRipper的解码器和AssetStudio自己的解码器,两者有各自的特点,可根据实际情况自由切换 16 | * 支持反序列化`MonoBehaviour`,只需要提供由Il2CppDumper/Cpp2Il生成的dummy DLL,调一个简单方法即可导出JSON 17 | * 直接的资源类型抽象,以及简单的导出参数配置,可以方便地实现新的导出方法,并灵活控制封装程度,不会对编写解包代码产生较大的侵入性和限制 18 | * 简单的跨平台 19 | * 同时带有Windows/Mac/Linux的x86/x64第三方库二进制,无分平台包,无需判断平台和重新编译,编译一次到处运行 20 | 21 | ## 开始使用 22 | 23 | 本仓库包含了对AssetsTools.NET V3(AT3)的封装,使用前先阅读AT3的开发文档 24 | 25 | ### 添加引用 26 | 27 | 1. 添加对`AssetsTools.NET`和`AssetsTools.NET.Texture`(如果需要处理图片)的nuget包 28 | 2. 克隆本仓库并作为项目依赖添加 29 | 30 | ### 定位Asset 31 | 32 | 具体参考AT3的文档 33 | 34 | ```csharp 35 | var am = new AssetsManager(); 36 | am.LoadClassPackage(Path.Combine(AppContext.BaseDirectory, "classdata.tpk")); 37 | 38 | var root = new DirectoryInfo("path/to/ab"); 39 | foreach (var file in root.EnumerateFiles("*.bundle", SearchOption.AllDirectories)) 40 | { 41 | var bf = am.LoadBundleFile(file.FullName, true); 42 | var inst = am.LoadAssetsFileFromBundle(bf, 0, true); 43 | 44 | foreach (var info in inst.file.AssetInfos) { 45 | var needExport = false; 46 | // 尝试定位到所需的资源 47 | // ... 48 | if(needExport) { 49 | // 使用Exporter进行导出 50 | } 51 | } 52 | } 53 | 54 | 55 | ``` 56 | 57 | ### 导出资源 58 | 59 | * 如果只是想简单的导出符合要求的资源,可以根据asset的`AssetClassID`来使用不同的导出类。可以使用`ExporterSetting.Default`来设置全局默认导出设置。 60 | 61 | ```csharp 62 | IAssetTypeExporter? exporter = null; 63 | switch (info.TypeId) 64 | { 65 | case (int)AssetClassID.Texture2D: 66 | exporter = Texture2D.Read(baseField); 67 | break; 68 | case (int)AssetClassID.AudioClip: 69 | exporter = AudioClip.Read(baseField); 70 | break; 71 | case (int)AssetClassID.TextAsset: 72 | exporter = TextAsset.Read(baseField); 73 | break; 74 | case (int)AssetClassID.Font: 75 | exporter = Font.Read(baseField); 76 | break; 77 | // 检查其他类型 78 | // ... 79 | default: 80 | break; 81 | } 82 | 83 | if (exporter is not null) 84 | { 85 | exporter.Export(inst, "path/to/exported/file"); 86 | } 87 | ``` 88 | 89 | * 对于需要更精细控制的asset,也可以手动传入参数 90 | 91 | ```csharp 92 | if (info.TypeId == (int)AssetClassID.Texture2D) 93 | { 94 | var texture2D = Texture2D.Read(baseField); 95 | var fileName = Path.Combind("path/to/export", texture2D.m_Name + ".webp"); 96 | using (var fs = File.Open(fileName, FileMode.Create, FileAccess.ReadWrite)) 97 | { 98 | // 使用webp导出并写入流 99 | texture2D.Export(inst, fs, ImageFormat.Webp); 100 | } 101 | } 102 | 103 | ``` 104 | 105 | ### 反序列化MonoBehavoiur 106 | 107 | 读取MonoBehavoiur是一件十分复杂的操作,但如果只需要简单地读取指定`GameObject`中存储的数据的结构(例如反序列化并生成JSON),使用提供的方法可以进行很大程度的简化 108 | 109 | ```csharp 110 | 111 | if (info.TypeId == (int)AssetClassID.GameObject) 112 | { 113 | // 确保加载了GameObject的依赖 114 | // ... 115 | 116 | // 读取该GameObject上包含的所有MonoBehaviour并筛选出需要的那个 117 | var assetFile = info 118 | .ReadAllMonoBehaviours(inst, am) 119 | .First(m => m.m_Script.m_ClassName == "YourClass" && m.m_Script.m_Namespace == "YourNamespace") 120 | .assetFile!; 121 | 122 | // dump出的dummy DLL所在目录 123 | var managedDir = "path/to/bin/Data/Managed"; 124 | var monoBehaviour = MonoDeserializer.GetMonoBaseField(am, inst, assetFile, managedDir); 125 | 126 | // 将monoBehaviour的字段信息转换为对象, 127 | // 该对象和原始的类结构类似,且生成JSON的内容一致, 128 | // 但并不是原来的类的实例,而是由字典、数组和基本类型等组成的复杂结构 129 | dynamic? obj = AssetDataUtil.DeserializeMonoBehaviour(monoBehaviour); 130 | var json = JsonSerializer.Serialize(obj); 131 | Console.WriteLine(json); 132 | } 133 | 134 | ``` 135 | 136 | ## 一些工具方法 137 | 138 | 139 | ### 读取资产路径 140 | 141 | 部分资产在打包时包含路径信息,在AssetStudio中会显示为 "Container" 列,可以通过扩展方法`AssetsFileInstance.GetContainers`获取当前文件里的所有路径信息。 142 | 143 | ```csharp 144 | Dictionary containers = inst.GetContainers(am); 145 | foreach (var (name, pptr) in containers) 146 | { 147 | Console.WriteLine($"{name}: FileId={pptr.FileId}, PathId={pptr.PathId}"); 148 | // 根据PPtr找到对应的资产对象 149 | } 150 | 151 | ``` 152 | -------------------------------------------------------------------------------- /TextureDecoderType.cs: -------------------------------------------------------------------------------- 1 | namespace AssetStudioExporter 2 | { 3 | public enum TextureDecoderType 4 | { 5 | AssetRipper = 0, 6 | AssetStudio = 1, 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /Util/AssetTypeValueFieldExtensions.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET; 2 | using AssetStudio; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace AssetStudioExporter.Util; 12 | 13 | public static class AssetTypeValueFieldExtensions 14 | { 15 | /// 16 | /// Author: ChatGPT 17 | /// 18 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 19 | public static byte ReverseBits(byte b) 20 | { 21 | // 获取高4位和低4位 22 | byte highNibble = (byte)(b >> 4); 23 | byte lowNibble = (byte)(b & 0x0F); 24 | 25 | // 交换高4位和低4位 26 | byte swappedByte = (byte)((lowNibble << 4) | highNibble); 27 | 28 | return swappedByte; 29 | } 30 | 31 | public static List AsList(this AssetTypeValueField value, Func selector) 32 | { 33 | return value["Array"] 34 | .Children 35 | .Select(selector) 36 | .ToList(); 37 | } 38 | 39 | public static T[] AsArray(this AssetTypeValueField value, Func selector) 40 | { 41 | return value["Array"] 42 | .Children 43 | .Select(selector) 44 | .ToArray(); 45 | } 46 | 47 | /// 48 | /// 将按照UnityEditor.GUID进行读取 49 | /// 50 | /// 要转换的值 51 | /// 52 | public static Guid AsUnityGUID(this AssetTypeValueField value) 53 | { 54 | // 将uint[4]重新解释为byte[16] 55 | var uintArray = value.Children 56 | .Select(n => n.AsUInt) 57 | .ToArray(); 58 | byte[] bytes = MemoryMarshal.AsBytes(uintArray.AsSpan()).ToArray(); 59 | // 翻转每个字节的高低位 0x42ABCDEF => 0x24BADCFE 60 | for (int i = 0; i < bytes.Length; i++) 61 | { 62 | bytes[i] = ReverseBits(bytes[i]); 63 | } 64 | 65 | return new Guid(bytes); 66 | } 67 | 68 | public static Vector2 AsVector2(this AssetTypeValueField value) 69 | { 70 | return new Vector2 71 | ( 72 | value["x"].AsFloat, 73 | value["y"].AsFloat 74 | ); 75 | } 76 | 77 | public static Vector3 AsVector3(this AssetTypeValueField value) 78 | { 79 | return new Vector3 80 | ( 81 | value["x"].AsFloat, 82 | value["y"].AsFloat, 83 | value["z"].AsFloat 84 | ); 85 | } 86 | 87 | public static Vector4 AsVector4(this AssetTypeValueField value) 88 | { 89 | return new Vector4 90 | ( 91 | value["x"].AsFloat, 92 | value["y"].AsFloat, 93 | value["z"].AsFloat, 94 | value["w"].AsFloat 95 | ); 96 | } 97 | 98 | public static AABB AsAABB(this AssetTypeValueField value) 99 | { 100 | return new AABB 101 | ( 102 | value["m_Center"].AsVector3(), 103 | value["m_Extent"].AsVector3() 104 | ); 105 | } 106 | 107 | public static Matrix4x4 AsMatrix4x4(this AssetTypeValueField value) 108 | { 109 | var values = value.AsArray(v => v.AsFloat); 110 | return new Matrix4x4(values); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /Util/UnityVersionExtensions.cs: -------------------------------------------------------------------------------- 1 | using AssetsTools.NET.Extra; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace AssetStudioExporter.Util; 9 | 10 | public static class UnityVersionExtensions 11 | { 12 | public static int[] ToIntArray(this UnityVersion version) 13 | { 14 | return new[] 15 | { 16 | version.major, 17 | version.minor, 18 | version.patch 19 | }; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/x64/Texture2DDecoderNative.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwingCosmic/AssetStudioExporter/52fd580bd362aa27171fd9b7cf7b52c6213a83fb/lib/x64/Texture2DDecoderNative.dll -------------------------------------------------------------------------------- /lib/x64/fmod.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwingCosmic/AssetStudioExporter/52fd580bd362aa27171fd9b7cf7b52c6213a83fb/lib/x64/fmod.dll -------------------------------------------------------------------------------- /lib/x64/libfmod.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwingCosmic/AssetStudioExporter/52fd580bd362aa27171fd9b7cf7b52c6213a83fb/lib/x64/libfmod.dylib -------------------------------------------------------------------------------- /lib/x64/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwingCosmic/AssetStudioExporter/52fd580bd362aa27171fd9b7cf7b52c6213a83fb/lib/x64/libfmod.so -------------------------------------------------------------------------------- /lib/x86/Texture2DDecoderNative.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwingCosmic/AssetStudioExporter/52fd580bd362aa27171fd9b7cf7b52c6213a83fb/lib/x86/Texture2DDecoderNative.dll -------------------------------------------------------------------------------- /lib/x86/fmod.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwingCosmic/AssetStudioExporter/52fd580bd362aa27171fd9b7cf7b52c6213a83fb/lib/x86/fmod.dll -------------------------------------------------------------------------------- /lib/x86/libfmod.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SwingCosmic/AssetStudioExporter/52fd580bd362aa27171fd9b7cf7b52c6213a83fb/lib/x86/libfmod.so --------------------------------------------------------------------------------