├── Extra ├── classdata.tpk ├── unity_default_resources └── empty.ttf ├── .gitmodules ├── .gitignore ├── CLI.Unity.Trimmer ├── .gitignore ├── CLI.Unity.Trimmer.sln.DotSettings ├── CLI.Unity.Trimmer.csproj.DotSettings ├── CLI.Unity.Trimmer.sln ├── Source │ ├── Program.cs │ └── Commands │ │ ├── Version.cs │ │ └── Trim.cs └── CLI.Unity.Trimmer.csproj ├── LICENSE ├── README.md └── .gitattributes /Extra/classdata.tpk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starburst997/Unity.Trimmer/HEAD/Extra/classdata.tpk -------------------------------------------------------------------------------- /Extra/unity_default_resources: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starburst997/Unity.Trimmer/HEAD/Extra/unity_default_resources -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Dependencies/AssetsTools.NET"] 2 | path = Dependencies/AssetsTools.NET 3 | url = git@github.com:starburst997/AssetsTools.NET.git 4 | -------------------------------------------------------------------------------- /Extra/empty.ttf: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:0083416ffb54c21dee698d5b098c2060b50b85636a04026d40e21a95dc427357 3 | size 1380 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | logs/ 3 | .vs/ 4 | .gradle/ 5 | .DS_Store 6 | DevAuthToken.txt* 7 | *.user 8 | 9 | [Tt]emp/ 10 | Logs/ 11 | [Oo]bj/ 12 | [Bb]in/ 13 | [Pp]ublish/ 14 | [Bb]inMono/ -------------------------------------------------------------------------------- /CLI.Unity.Trimmer/.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | logs/ 3 | .idea/ 4 | .vs/ 5 | .gradle/ 6 | .DS_Store 7 | DevAuthToken.txt* 8 | 9 | [Tt]emp/ 10 | Logs/ 11 | [Oo]bj/ 12 | [Bb]in/ 13 | [Pp]ublish*/ 14 | 15 | settings -------------------------------------------------------------------------------- /CLI.Unity.Trimmer/CLI.Unity.Trimmer.sln.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /CLI.Unity.Trimmer/CLI.Unity.Trimmer.csproj.DotSettings: -------------------------------------------------------------------------------- 1 | 2 | True -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jean-Denis Boivin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity.Trimmer 2 | Reduce size of unity asset bundled by default 3 | 4 | Unity include a file by default called `unity_default_resources` which includes a bunch of unnecessary assets. 5 | 6 | There is no easy way to remove it and it doesn't seems like the build process attempts to remove unnecessary assets from it either, so the file is simply copied as is... All 3.36MB (WebGL / 2021.1.28f1) of it... Which is kind of a lot for web games. 7 | 8 | For example, a 2048x1024 2MB Texture called "UnitySplash-cube" is include in it, the default unity splash screen, even if you overwrite it in Project Setting. 9 | 10 | It is very disappointing to see this, especially since the WebGL team at Unity is making great progress attempting to reduce the size of the code and yet they leave an unused 2MB Texture by default with no way to remove it. 11 | 12 | This tools use [AssetsTools.NET](https://github.com/nesrak1/AssetsTools.NET) to read that file, replace all `Texture2D` inside of it and write a new trimmed copy of the file. 13 | 14 | Replace that file in Unity Editor folder: `C:\Program Files\Unity\Hub\Editor\XXXXXXX\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\data\unity_default_resources` 15 | 16 | Build your project and notice how you reduce the output size by 3MB! 17 | 18 | The file is also included in the "Extra" folder for those that don't want to build the program. -------------------------------------------------------------------------------- /CLI.Unity.Trimmer/CLI.Unity.Trimmer.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBD}") = "CLI.Unity.Trimmer", "CLI.Unity.Trimmer.csproj", "{CCACCC96-9AF6-4048-AE42-7FE602C12EE4}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AssetsTools.NET", "..\Dependencies\AssetsTools.NET\AssetTools.NET\AssetsTools.NET.csproj", "{98998C85-C405-4F11-A458-538B2C7F4596}" 6 | EndProject 7 | Global 8 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 9 | Debug|Any CPU = Debug|Any CPU 10 | Release|Any CPU = Release|Any CPU 11 | EndGlobalSection 12 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 13 | {CCACCC96-9AF6-4048-AE42-7FE602C12EE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 14 | {CCACCC96-9AF6-4048-AE42-7FE602C12EE4}.Debug|Any CPU.Build.0 = Debug|Any CPU 15 | {CCACCC96-9AF6-4048-AE42-7FE602C12EE4}.Release|Any CPU.ActiveCfg = Release|Any CPU 16 | {CCACCC96-9AF6-4048-AE42-7FE602C12EE4}.Release|Any CPU.Build.0 = Release|Any CPU 17 | {98998C85-C405-4F11-A458-538B2C7F4596}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 18 | {98998C85-C405-4F11-A458-538B2C7F4596}.Debug|Any CPU.Build.0 = Debug|Any CPU 19 | {98998C85-C405-4F11-A458-538B2C7F4596}.Release|Any CPU.ActiveCfg = Release|Any CPU 20 | {98998C85-C405-4F11-A458-538B2C7F4596}.Release|Any CPU.Build.0 = Release|Any CPU 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /CLI.Unity.Trimmer/Source/Program.cs: -------------------------------------------------------------------------------- 1 | using System.CommandLine; 2 | using System.CommandLine.Invocation; 3 | using System.Threading.Tasks; 4 | using Unity.Trimmer.Cli.Commands; 5 | using Version = Unity.Trimmer.Cli.Commands.Version; 6 | 7 | namespace unity.trimmer.cli 8 | { 9 | internal static class Program 10 | { 11 | private static async Task Main(params string[] args) 12 | { 13 | // Parse arguments 14 | RootCommand rootCommand = new RootCommand("Unity Trimmer CLI Tools."); 15 | 16 | // Trim 17 | var trimCommand = new Command("trim", "Trim an asset bundle (ex: 'unity_default_resources')"); 18 | trimCommand.AddAlias("n"); 19 | trimCommand.AddArgument(new Argument("input")); 20 | trimCommand.AddArgument(new Argument("output")); 21 | trimCommand.AddOption(new Option(new []{"--classdata", "--c"}, () => "", "'classdata.tpk' file path")); 22 | trimCommand.AddOption(new Option(new []{"--font", "--f"}, () => "", "Empty font file path")); 23 | trimCommand.Handler = CommandHandler.Create(Trim.Execute); 24 | rootCommand.AddCommand(trimCommand); 25 | 26 | // Version 27 | var versionCommand = new Command("version", "Print version information"); 28 | versionCommand.AddOption(new Option(new []{"--json", "--j"}, () => false, "JSON Output")); 29 | versionCommand.AddOption(new Option(new []{"--show-system", "--s"}, () => false, "Show system assemblies as well")); 30 | versionCommand.Handler = CommandHandler.Create(Version.Execute); 31 | rootCommand.AddCommand(versionCommand); 32 | 33 | return await rootCommand.InvokeAsync(args); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /CLI.Unity.Trimmer/CLI.Unity.Trimmer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | Unity.Trimmer.Cli 6 | false 7 | win-x64;linux-x64;osx-x64 8 | Debug;Release 9 | AnyCPU 10 | 2022.0 11 | 2022.0 12 | 2022.0 13 | 2022.0 14 | 2022.0 15 | net6.0 16 | 17 | 18 | 19 | true 20 | 21 | 22 | 23 | true 24 | portable 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 2.0.0-beta1.20574.7 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /CLI.Unity.Trimmer/Source/Commands/Version.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace Unity.Trimmer.Cli.Commands 6 | { 7 | public static class Version 8 | { 9 | public static void Execute(bool json, bool showSystem) 10 | { 11 | AppDomain currentDomain = AppDomain.CurrentDomain; 12 | Assembly[] assemblies = currentDomain.GetAssemblies(); 13 | 14 | var assembly = Assembly.GetEntryAssembly() ?? Assembly.GetExecutingAssembly(); 15 | var dict = new Dictionary(); 16 | 17 | var info = assembly.GetName(); 18 | if (info.Name != null) dict[info.Name] = info; 19 | 20 | GetAssemblies(assembly, dict, showSystem); 21 | 22 | if (json) 23 | { 24 | var assemblyVersionAttribute = assembly.GetCustomAttribute(); 25 | Console.WriteLine($"{{\"success\": true, \"version\": \"{assemblyVersionAttribute?.InformationalVersion ?? assembly.GetName().Version.ToString()}\", \"versions\": ["); 26 | } 27 | 28 | var first = true; 29 | foreach (var pair in dict) 30 | { 31 | var comma = first ? ' ' : ','; 32 | 33 | if (json) 34 | Console.WriteLine($"{comma}{{\"name\": \"{pair.Key}\", \"version\": \"{pair.Value.Version}\"}}"); 35 | else 36 | Console.WriteLine($"{pair.Key} : {pair.Value.Version}"); 37 | 38 | first = false; 39 | } 40 | 41 | if (json) 42 | Console.WriteLine("]}"); 43 | } 44 | 45 | private static bool IsSystem(string name) 46 | { 47 | return name.StartsWith("Microsoft") || 48 | name.StartsWith("System") || 49 | name.StartsWith("netstandard") || 50 | name.StartsWith("mscorlib"); 51 | } 52 | 53 | private static void GetAssemblies(Assembly assembly, Dictionary dict, bool allowSystem = false) 54 | { 55 | foreach (var child in assembly.GetReferencedAssemblies()) 56 | { 57 | if (child.Name != null && (allowSystem || !IsSystem(child.Name)) && !dict.ContainsKey(child.Name)) 58 | { 59 | dict[child.Name] = child; 60 | 61 | try 62 | { 63 | var childAssembly = Assembly.Load(child); 64 | GetAssemblies(childAssembly, dict, allowSystem); 65 | } 66 | catch (Exception _) 67 | { 68 | // Nothing 69 | } 70 | } 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | ## Text files ## 3 | *.mat text 4 | *.shader text 5 | *.meta text 6 | *.md5 text 7 | *.compute text 8 | *.md text 9 | *.prefab text 10 | *.mixer text 11 | *.ShaderGraph text 12 | *.anim text 13 | *.asmdef text 14 | *.json text 15 | *.overrideController text 16 | *.cginc text 17 | *.hlsl text 18 | *.giparams text 19 | *.controller text 20 | *.mask text 21 | *.playable text 22 | *.asset text 23 | ## Binaries ## 24 | # Image 25 | *.jpg filter=lfs diff=lfs merge=lfs -text 26 | *.jpeg filter=lfs diff=lfs merge=lfs -text 27 | *.png filter=lfs diff=lfs merge=lfs -text 28 | *.gif filter=lfs diff=lfs merge=lfs -text 29 | *.psd filter=lfs diff=lfs merge=lfs -text 30 | *.ai filter=lfs diff=lfs merge=lfs -text 31 | *.tga filter=lfs diff=lfs merge=lfs -text 32 | *.TGA filter=lfs diff=lfs merge=lfs -text 33 | *.tif filter=lfs diff=lfs merge=lfs -text 34 | *.TIF filter=lfs diff=lfs merge=lfs -text 35 | *.bmp filter=lfs diff=lfs merge=lfs -text 36 | *.tiff filter=lfs diff=lfs merge=lfs -text 37 | # Audio 38 | *.mp3 filter=lfs diff=lfs merge=lfs -text 39 | *.wav filter=lfs diff=lfs merge=lfs -text 40 | *.ogg filter=lfs diff=lfs merge=lfs -text 41 | *.mod filter=lfs diff=lfs merge=lfs -text 42 | *.s3m filter=lfs diff=lfs merge=lfs -text 43 | *.xm filter=lfs diff=lfs merge=lfs -text 44 | *.mo3 filter=lfs diff=lfs merge=lfs -text 45 | *.it filter=lfs diff=lfs merge=lfs -text 46 | *.note filter=lfs diff=lfs merge=lfs -text 47 | # Video 48 | *.mp4 filter=lfs diff=lfs merge=lfs -text 49 | *.mov filter=lfs diff=lfs merge=lfs -text 50 | *.ogv filter=lfs diff=lfs merge=lfs -text 51 | # 3D Object 52 | *.FBX filter=lfs diff=lfs merge=lfs -text 53 | *.fbx filter=lfs diff=lfs merge=lfs -text 54 | *.blend filter=lfs diff=lfs merge=lfs -text 55 | *.obj filter=lfs diff=lfs merge=lfs -text 56 | *.OBJ filter=lfs diff=lfs merge=lfs -text 57 | *.gltf filter=lfs diff=lfs merge=lfs -text 58 | # ETC 59 | *.a filter=lfs diff=lfs merge=lfs -text 60 | *.ma filter=lfs diff=lfs merge=lfs -text 61 | *.hdr filter=lfs diff=lfs merge=lfs -text 62 | *.exr filter=lfs diff=lfs merge=lfs -text 63 | *.tga filter=lfs diff=lfs merge=lfs -text 64 | *.pdf filter=lfs diff=lfs merge=lfs -text 65 | *.zip filter=lfs diff=lfs merge=lfs -text 66 | *.dll filter=lfs diff=lfs merge=lfs -text 67 | *.pdb filter=lfs diff=lfs merge=lfs -text 68 | *.exe filter=lfs diff=lfs merge=lfs -text 69 | *.unitypackage filter=lfs diff=lfs merge=lfs -text 70 | *.aif filter=lfs diff=lfs merge=lfs -text 71 | *.ttf filter=lfs diff=lfs merge=lfs -text 72 | *.rns filter=lfs diff=lfs merge=lfs -text 73 | *.reason filter=lfs diff=lfs merge=lfs -text 74 | *.lxo filter=lfs diff=lfs merge=lfs -text 75 | *.so filter=lfs diff=lfs merge=lfs -text 76 | *.bundle filter=lfs diff=lfs merge=lfs -text 77 | *.mdb filter=lfs diff=lfs merge=lfs -text 78 | *.pdt filter=lfs diff=lfs merge=lfs -text 79 | *.bytes filter=lfs diff=lfs merge=lfs -text 80 | *.sbsar filter=lfs diff=lfs merge=lfs -text 81 | *.unity filter=lfs diff=lfs merge=lfs -text 82 | *.entities filter=lfs diff=lfs merge=lfs -text 83 | *.bin filter=lfs diff=lfs merge=lfs -text 84 | *.tpsheet filter=lfs diff=lfs merge=lfs -text 85 | *.wasm filter=lfs diff=lfs merge=lfs -text 86 | *.unityweb filter=lfs diff=lfs merge=lfs -text 87 | *.note filter=lfs diff=lfs merge=lfs -text 88 | *.s3m filter=lfs diff=lfs merge=lfs -text 89 | *.mod filter=lfs diff=lfs merge=lfs -text 90 | *.xm filter=lfs diff=lfs merge=lfs -text 91 | *.it filter=lfs diff=lfs merge=lfs -text 92 | *.mo3 filter=lfs diff=lfs merge=lfs -text 93 | *.fla filter=lfs diff=lfs merge=lfs -text 94 | *.br filter=lfs diff=lfs merge=lfs -text 95 | *.gz filter=lfs diff=lfs merge=lfs -text 96 | 97 | # Line endings 98 | *.txt text eol=lf 99 | *.cs diff=csharp text eol=lf 100 | *.tt text eol=lf 101 | *.sh text eol=lf 102 | *.md text eol=lf 103 | *.json text eol=lf -------------------------------------------------------------------------------- /CLI.Unity.Trimmer/Source/Commands/Trim.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using AssetsTools.NET; 5 | using AssetsTools.NET.Extra; 6 | 7 | namespace Unity.Trimmer.Cli.Commands 8 | { 9 | public static class Trim 10 | { 11 | public static void Execute(string input, string output, string classdata, string font) 12 | { 13 | var manager = new AssetsManager(); 14 | var asset = manager.LoadAssetsFile(input, false); 15 | 16 | if (!string.IsNullOrEmpty(classdata)) 17 | manager.LoadClassPackage(classdata); // TODO: Is this necessary? 18 | 19 | manager.LoadClassDatabaseFromPackage(asset.file.typeTree.unityVersion); 20 | 21 | Console.WriteLine($"Asset found: {asset.name} ({asset.file.typeTree.unityVersion})"); 22 | 23 | byte[] fontBytes = null; 24 | if (!string.IsNullOrEmpty(font)) 25 | fontBytes = File.ReadAllBytes(font); 26 | 27 | var replacers = new List(); 28 | var empty = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; // 2x2 32bit 29 | 30 | // Loop all assets to find all Texture2D and replace them with 2x2 empty one 31 | // TODO: Still left with ~350kb of crap, seems like there is a compute shader worth 200kb, not sure if worth trying to slim the rest, when compressed with brotli we're left with less than 66kb which is a huge gain compared to before 32 | foreach (var info in asset.table.assetFileInfo) 33 | { 34 | var baseField = manager.GetTypeInstance(asset, info).GetBaseField(); 35 | var name = baseField.Get("m_Name").GetValue().AsString(); 36 | 37 | Console.WriteLine($"Found asset: {name} / {(AssetClassID) info.curFileType}"); 38 | 39 | // We've got a `Texture2D` so replace it 40 | if (info.curFileType == (int) AssetClassID.Texture2D) 41 | { 42 | var texture = TextureFile.ReadTextureFile(baseField); 43 | if (texture.m_Width < 2 && texture.m_Height < 2) continue; 44 | 45 | Console.WriteLine($"{texture.m_Width} / {texture.m_Height} / {(TextureFormat) texture.m_TextureFormat}"); 46 | 47 | // Create the new texture 48 | texture.m_TextureFormat = (int) TextureFormat.RGBA32; 49 | var data = TextureFile.Encode(empty, (TextureFormat) texture.m_TextureFormat, 2, 2); 50 | 51 | texture.SetTextureDataRaw(data, 2, 2); 52 | texture.WriteTo(baseField); 53 | 54 | var bytes = baseField.WriteToByteArray(); 55 | var replacer = new AssetsReplacerFromMemory(0, info.index, (int) info.curFileType, 0xffff, bytes); 56 | replacers.Add(replacer); 57 | 58 | Console.WriteLine($"*** Texture replaced!"); 59 | } 60 | 61 | // Also replace "Arial" font since we don't need it 62 | if (fontBytes != null && info.curFileType == (int) AssetClassID.Font) 63 | { 64 | Console.WriteLine($"Found Font!"); 65 | foreach (var child in baseField.children) 66 | { 67 | Console.WriteLine($"Child: {child.GetName()} / {child.GetFieldType()}"); 68 | 69 | if (child.GetName() == "m_FontData") 70 | { 71 | Console.WriteLine($"Data:"); 72 | foreach (var child2 in child.children) 73 | { 74 | Console.WriteLine($"Child2: {child2.GetName()} / {child2.GetFieldType()}"); 75 | 76 | if (child2.GetName() == "Array") 77 | { 78 | // Extract font data 79 | /*var size = child2.GetValue().AsArray().size; 80 | var bytes = new byte[size]; 81 | for (int i = 0; i < size; i++) 82 | { 83 | bytes[i] = (byte) child2[i].GetValue().AsInt(); 84 | } 85 | 86 | File.WriteAllBytes($"{font}.copy", bytes);*/ 87 | 88 | // Overwrite data 89 | child2.GetValue().Set(new AssetTypeArray(fontBytes.Length)); 90 | 91 | AssetTypeValueField[] children = new AssetTypeValueField[fontBytes.Length]; 92 | for (int i = 0; i < fontBytes.Length; i++) 93 | { 94 | AssetTypeValueField c = ValueBuilder.DefaultValueFieldFromArrayTemplate(child2); 95 | c.GetValue().Set(fontBytes[i]); 96 | children[i] = c; 97 | } 98 | 99 | child2.SetChildrenList(children); 100 | 101 | // Replace 102 | var bytes = baseField.WriteToByteArray(); 103 | var replacer = new AssetsReplacerFromMemory(0, info.index, (int) info.curFileType, 0xffff, bytes); 104 | replacers.Add(replacer); 105 | } 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | Console.WriteLine($"Writing output to: \"{output}\""); 113 | 114 | File.Delete(output); 115 | using (var stream = File.OpenWrite(output)) 116 | using (var writer = new AssetsFileWriter(stream)) 117 | asset.file.Write(writer, 0, replacers, 0); 118 | 119 | Console.WriteLine($"Done!"); 120 | 121 | manager.UnloadAllAssetsFiles(); 122 | } 123 | } 124 | } --------------------------------------------------------------------------------