From cf0e54ea1a7f572b2478ce8130e51b87b096638e Mon Sep 17 00:00:00 2001 From: Andrew Skowronski Date: Wed, 18 Jun 2025 17:24:05 -0400 Subject: [PATCH 1/3] Add documentation and scripts for comparing builds using UnityDataTool --- Documentation/analyze-examples.md | 4 + Documentation/comparing-builds.md | 290 ++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+) create mode 100644 Documentation/comparing-builds.md diff --git a/Documentation/analyze-examples.md b/Documentation/analyze-examples.md index adee2ab..3b4657e 100644 --- a/Documentation/analyze-examples.md +++ b/Documentation/analyze-examples.md @@ -214,6 +214,10 @@ object_id type name pretty_size crc32 3866367853307903194 Sprite red 460.0 B 1811343945 ``` +## Example: Finding differences between two builds + +This is a larger topics, see [Comparing Builds](comparing-builds.md). + ## Example: Matching content back to the source asset UnityDataTool works on the output of a Unity build, which, by its very nature, only contains the crucial data needed to efficiently load built content in the Player. So it does not include any information about the assets and scenes in the project that was used to create that build. However you may want to match content back to the original source asset or scene. For example if the size of an AssetBundle has unexpectedly changed between builds then you may want to track down which source assets could be responsible for that change. Or you may want to confirm that some particular image has been included in the build. diff --git a/Documentation/comparing-builds.md b/Documentation/comparing-builds.md new file mode 100644 index 0000000..7505906 --- /dev/null +++ b/Documentation/comparing-builds.md @@ -0,0 +1,290 @@ +# Comparing Builds + +Each time a build is performed the output can change, based on changes in the Unity project. + +A common question is "what changed", especially if more files changed that were expected. This question arises most frequently in the area of AssetBundles, rather than the player build, so the examples here focus on the AssetBundle case. But the same principals can apply to Player builds. + +For the purpose of these examples, suppose that two builds of the same project are located side-by-side in two directories, /build1 and /build2. + +## File-level comparison + +A quick way to compare two builds is to do a file-level comparison, e.g. using a diff tool to compare build1 and build2. + +This will quickly narrow down which AssetBundle files have changed. But AssetBundles files are binary archive files, so this won't show what changed inside the files. + +## UnityDataTool object comparison + +UnityDataTools does not natively support comparing two builds, but it can be done by analyzing each build individually into separate SQLite databases, then running queries to compare the contents of the two databases. + +For example suppose two database are generated as follows: + + +``` +UnityDataTool.exe analyze -o build1.db .\Build1\ +UnityDataTool.exe analyze -o build2.db .\Build2\ +``` + +Running this PowerShell script would print all the objects, with info about whether they match between the two builds. Objects are matched primarily based on the AssetBundle and local file ID (object_id) matching. If there is a change in content then the CRC should change, and the size is also shown. + + +``` +param ( + [Parameter(Mandatory=$true, HelpMessage="Path to the first UnityDataTool database")] + [string]$db1, + + [Parameter(Mandatory=$true, HelpMessage="Path to the second UnityDataTool database")] + [string]$db2 +) + +# Check if the database file exists +if (-not (Test-Path $db1)) { + Write-Error "Database file '$db1' not found." + exit 1 +} + +if (-not (Test-Path $db2)) { + Write-Error "Database file '$db2' not found." + exit 1 +} + +# SQL query to compare the content of two builds. +# Note: when the ID of an object changes then it will not be matched as the same. +$query = @" +ATTACH DATABASE '$db2' AS db2; + +SELECT + COALESCE(o1.asset_bundle, o2.asset_bundle) AS asset_bundle, + COALESCE(o1.object_id, o2.object_id) AS object_id, + COALESCE(o1.type, o2.type) AS type, + COALESCE(o1.name, o2.name) AS name, + CASE + WHEN o1.asset_bundle IS NULL THEN 'Only in Build 1' + WHEN o2.asset_bundle IS NULL THEN 'Only in Build 2' + WHEN o1.crc32 != o2.crc32 OR o1.size != o2.size THEN 'Different' + ELSE 'Same' + END AS status, + o1.size AS size_build1, + o2.size AS size_build2, + o1.crc32 AS crc32_build1, + o2.crc32 AS crc32_build2 +FROM ( + SELECT + ab.name AS asset_bundle, + o.object_id, + t.name AS type, + o.name, + o.size, + o.crc32, + sf.name AS serialized_file + FROM + objects o + INNER JOIN + types t ON o.type = t.id + INNER JOIN + serialized_files sf ON o.serialized_file = sf.id + LEFT JOIN + asset_bundles ab ON sf.asset_bundle = ab.id +) AS o1 +FULL OUTER JOIN ( + SELECT + ab.name AS asset_bundle, + o.object_id, + t.name AS type, + o.name, + o.size, + o.crc32, + sf.name AS serialized_file + FROM + db2.objects o + INNER JOIN + db2.types t ON o.type = t.id + INNER JOIN + db2.serialized_files sf ON o.serialized_file = sf.id + LEFT JOIN + db2.asset_bundles ab ON sf.asset_bundle = ab.id +) AS o2 ON o1.asset_bundle = o2.asset_bundle + AND o1.object_id = o2.object_id + AND o1.type = o2.type + AND o1.name = o2.name + AND o1.serialized_file = o2.serialized_file; + +DETACH DATABASE db2; +"@ + +# Execute the query +Write-Host "Objects with differences, only in one DB, or the same:" +$results = sqlite3 $db1 ".mode column" $query +$results | ForEach-Object { Write-Output $_ } +``` + +This is a partial example output, where the "red.png" file has been edited between builds: + + +``` +asset_bundle object_id type name status size_build1 size_build2 crc32_build1 crc32_build2 +-------------- -------------------- ------------------- ------------------- --------- ----------- ----------- ------------ ------------ +AssetBundles 1 AssetBundle Same 104 104 241569179 241569179 +AssetBundles 2 AssetBundleManifest AssetBundleManifest Different 184 184 4124235088 3102991602 +audio.bundle -1630896013228033972 AudioClip audio Same 18656 18656 883020518 883020518 +audio.bundle 1 AssetBundle audio.bundle Same 144 144 2644028121 2644028121 +sprites.bundle -4266742476527514910 Sprite Snow 1 Same 464 464 2360191667 2360191667 +sprites.bundle -39415655269619539 Texture2D Snow 1 Same 524496 524496 3893000759 3893000759 +sprites.bundle -3600607445234681765 Texture2D red Different 152079 152079 3533099562 3115177070 +sprites.bundle -1350043613627603771 Texture2D Snow Same 524492 524492 3894005184 3894005184 +sprites.bundle 1 AssetBundle sprites.bundle Same 460 460 245831303 245831303 +``` + +The output pinpoints that "red" has changed. The AssetBundleManifest object also changes, because it lists AssetBundle hashes. + +This script is intentionally verbose for clarity. For very large builds you probably would want to filter down to only show the objects that have changed, and perhaps only compare individual AssetBundles rather than the whole build. + + +### Comparing Individual AssetBundles + +A variation of the PowerShell script above could be used to more directly compare AssetBundles. This uses temporary sqlite databases so that it becomes a one step process, for example it could be invoked like this: + +``` +comparebundles.ps1 .\Build1\sprites.bundle .\Build2\sprites.bundle +``` + +The script is as follows: + +``` +# PowerShell Script to compare the objects inside two versions of an AssetBundle +param( + [Parameter(Mandatory=$true, HelpMessage="Path to the first AssetBundle")] + [string]$FileName1, + + [Parameter(Mandatory=$true, HelpMessage="Path to the second AssetBundle")] + [string]$FileName2 +) + +if (-not (Test-Path -Path $FileName1)) { + Write-Error "File '$FileName1' does not exist." + exit 1 +} + +if (-not (Test-Path -Path $FileName2)) { + Write-Error "File '$FileName2' does not exist." + exit 1 +} + +# Separate the directory and file name +$FileDir1 = Split-Path -Path $FileName1 -Parent +$FileLeaf1 = Split-Path -Path $FileName1 -Leaf + +# If no directory is detected (relative file name), use the current working directory +if (-not $FileDir1) { + $FileDir1 = "." +} + +$FileDir2 = Split-Path -Path $FileName2 -Parent +$FileLeaf2 = Split-Path -Path $FileName2 -Leaf +if (-not $FileDir2) { + $FileDir2 = "." +} + +# Retrieve the system's temp folder and create the temporary database file names +$tempFolder = $env:TEMP +$dbName1 = Join-Path -Path $tempFolder -ChildPath ("1_$FileLeaf1.db") +$dbName2 = Join-Path -Path $tempFolder -ChildPath ("2_$FileLeaf2.db") + +#Analyze each AssetBundle into temporary databases +UnityDataTool analyze $FileDir1 -o $dbName1 -p $FileLeaf1 +UnityDataTool analyze $FileDir2 -o $dbName2 -p $FileLeaf2 + +$query = @" +ATTACH DATABASE '$dbName2' AS db2; + +SELECT + COALESCE(o1.serialized_file, o2.serialized_file) AS serialized_file, + COALESCE(o1.object_id, o2.object_id) AS object_id, + COALESCE(o1.type, o2.type) AS type, + COALESCE(o1.name, o2.name) AS name, + CASE + WHEN o1.asset_bundle IS NULL THEN 'Only in Build 1' + WHEN o2.asset_bundle IS NULL THEN 'Only in Build 2' + ELSE 'Different' + END AS status, + o1.size AS size_build1, + o2.size AS size_build2, + o1.crc32 AS crc32_build1, + o2.crc32 AS crc32_build2 +FROM ( + SELECT + ab.name AS asset_bundle, + o.object_id, + t.name AS type, + o.name, + o.size, + o.crc32, + sf.name AS serialized_file + FROM + objects o + INNER JOIN + types t ON o.type = t.id + INNER JOIN + serialized_files sf ON o.serialized_file = sf.id + LEFT JOIN + asset_bundles ab ON sf.asset_bundle = ab.id +) AS o1 +FULL OUTER JOIN ( + SELECT + ab.name AS asset_bundle, + o.object_id, + t.name AS type, + o.name, + o.size, + o.crc32, + sf.name AS serialized_file + FROM + db2.objects o + INNER JOIN + db2.types t ON o.type = t.id + INNER JOIN + db2.serialized_files sf ON o.serialized_file = sf.id + LEFT JOIN + db2.asset_bundles ab ON sf.asset_bundle = ab.id +) AS o2 ON o1.asset_bundle = o2.asset_bundle + AND o1.object_id = o2.object_id + AND o1.serialized_file = o2.serialized_file +WHERE NOT (o1.asset_bundle IS NOT NULL AND o2.asset_bundle IS NOT NULL AND o1.crc32 = o2.crc32 AND o1.size = o2.size); + +DETACH DATABASE db2; +"@ + +# Query the database using sqlite3 +sqlite3 $dbName1 ".mode column" $query + +# Delete the temporary database files +Remove-Item $dbName1 -Force +Remove-Item $dbName2 -Force +``` + +The query above does not show objects that are the same in both AssetBundles, so that only the differences are emphasized. And it shows the SerializedFile name instead of the AssetBundle name. For example: + + +``` +serialized_file object_id type name status size_build1 size_build2 crc32_build1 crc32_build2 +------------------------------------ -------------------- --------- ---- --------- ----------- ----------- ------------ ------------ +CAB-6b49068aebcf9d3b05692c8efd933167 -3600607445234681765 Texture2D red Different 152079 152079 3533099562 3115177070 +``` + +### In depth AssetBundle comparison + +The tips show above help pinpoint individual AssetBundles and objects, but do not determine "what" has changed at the object level. To dig deeper it can be necessary to look directly into the content of the AssetBundles and the Serialized values for Unity Objects. + +Although this level of comparison is quite low level, it does often give insight into exactly why an object is different, and hence why an AssetBundle has new content. This technique is also useful in pinpointing "non-determinism" bugs, e.g. where building a project more than once produces different results, even when nothing has changed at all. + +For this the **WebExtract** tool that is shipped with Unity is useful because it lets you compare the entire content of an AssetBundle. + +Use the **WebExtract** tool to expand the content of the AssetBundle from one build into a directory. Repeat the operation on the equivalent AssetBundle from the second build. Then compare the content of those output directories used a Diff tool. + +Some of the files in the output are Serialized Files, e.g. names like "CAB-6b49068aebcf9d3b05692c8efd933167" or "BuildPlayer-SampleScene.sharedAssets". These are files containing Unity object serialized in binary format. If they show up as different between the builds then you should convert them to text so you can compare the serialized forms of the objects. + +For that you can use **binary2text** or **UnityDataTools dump**. For example the output could be a file like "CAB-6b49068aebcf9d3b05692c8efd933167.txt". Different versions of these files can be compared using a diff tool. + +Warning: for Shaders the text dump output is quite huge and difficult to understand. And some objects may contain large arrays, where the change may just be some bytes changing mysteriously inside a very large list of numbers. + +If the difference in an AssetBundle turns out to be a change inside a ".resS" or ".resource" file then the change has happened in the raw data for a Mesh, Texture, Audio Clip or Video Clip. You can cross reference back to Serialized file with the same name (in text format) to find which objects own the data in those resource files. + From 1af342bd94cfdd3054c8b3472eb12e532cd1a12d Mon Sep 17 00:00:00 2001 From: Andrew Skowronski Date: Wed, 25 Jun 2025 17:07:43 -0400 Subject: [PATCH 2/3] Add detailed example for comparing content of AssetBundle .resS files Add images and more specific text, based on the example where an AssetBundle has changed due to a pixel change within a texture. --- Documentation/AssetBundleBuildComparison.png | Bin 0 -> 23969 bytes .../AssetBundleContentComparison.png | Bin 0 -> 13001 bytes Documentation/ResFileBinaryDiff.png | Bin 0 -> 61223 bytes Documentation/SpritesBundleContent.png | Bin 0 -> 12080 bytes .../SpritesBundleDetailedContent.png | Bin 0 -> 26916 bytes Documentation/comparing-builds.md | 108 ++++++++++++++---- 6 files changed, 85 insertions(+), 23 deletions(-) create mode 100644 Documentation/AssetBundleBuildComparison.png create mode 100644 Documentation/AssetBundleContentComparison.png create mode 100644 Documentation/ResFileBinaryDiff.png create mode 100644 Documentation/SpritesBundleContent.png create mode 100644 Documentation/SpritesBundleDetailedContent.png diff --git a/Documentation/AssetBundleBuildComparison.png b/Documentation/AssetBundleBuildComparison.png new file mode 100644 index 0000000000000000000000000000000000000000..fa2d1924ade1af2ab71cda73b1fbba542f0cb1da GIT binary patch literal 23969 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 A+5i9m literal 0 HcmV?d00001 diff --git a/Documentation/AssetBundleContentComparison.png b/Documentation/AssetBundleContentComparison.png new file mode 100644 index 0000000000000000000000000000000000000000..fbd78ef633370ba43104041ee3ee0e70a6af466b GIT binary patch literal 13001 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 literal 0 HcmV?d00001 diff --git a/Documentation/ResFileBinaryDiff.png b/Documentation/ResFileBinaryDiff.png new file mode 100644 index 0000000000000000000000000000000000000000..5c34ff7b0995224661d9d49fea284aefe0609ffa GIT binary patch literal 61223 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 YubRGk9MfV1zBE!-<*#I1zTx$M041`3>Hq)$ literal 0 HcmV?d00001 diff --git a/Documentation/SpritesBundleContent.png b/Documentation/SpritesBundleContent.png new file mode 100644 index 0000000000000000000000000000000000000000..949773d31126618dc6b78138c3b9f90384bb7f32 GIT binary patch literal 12080 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≷(}Md5e) zdJ!y|!5+jEOnLUwfr6f#%8P#-e#^-tsUcEcr)n?x<-tL^|2As&tj#@su*`<*nXoSD zZw$$bDTkmDa&4Er+0FWzkB>)Jee@iqeqvC#|y_u-M~akdhnGOa5rUgKn7Zb?ay@Fjd*zPwhL}v+k^ho`?~9r#HNR z>bi8r*BzcNT=izMPegI>cAnmt`?J;PAi9~CvzO}n3B^BxwDwJ!{;(XpnTB}6i!~yz+(hbdPIn95JQN67cHMKKCDT$lAq}yer8avS) zQ4>2gDvGF|A@Y~KSpNp-yRG-S!B=d4v8QtNNORKnR(qtm&2r}ct;`oy%1U!iBLx4c zYCG9$;IubCUL`2K<#R=$_)Ha4^2y~h`ltRs*>*8NxEKLB9;oFMymKf&^16?fT=G11 zehGGE{r(x9J)0bsL~2E?avQcH9v-$*uqW|a^D1fsn(qG@h+Y&wSan&cxTD-P`?|G? zl0?H!C2y4SAbIaB=&Cv}T8}zWMDP7|8L~ZUv(IPrq~V`h@WT4W(tY%klhC)SPu~T+ zTVCH+*@)i$-Qk(v?P2kLAEqcE-%gWwT<4m$^;G3~85dE&LpFTS~9!7;NV>Aw%( zP#`|5uU6pwZH*TR^MUJkwr2M4jc49sMRMcR0F4J6w}7paV7WT%>2|JqHWy}lbNIJE z7r|e#X{R6Yo~HS_nXCbinw~9iTe9F{mRyuEe_CeQeupmk+DNRA`1MaGn)2$azbo|gSH+Rl?guj9{R>Ag-M;_;XzQ} z2}ghamLBK!YkxhzA@(&VMsF|5vGZI%vhtPen_~swNn?Vvp5bQ}uV1c(B8^s-{)H8> z2^jZUk$9VM*vxq->^|U+fM6x2<&!lrbjOReNxY9P6QF>EF@`a}Rywly2|u zLioVxo3Orl1q3>R)T}K#UHVt9#WUWu9y*_ve0nae_WoS_>hIjNb%|Yl?$#BP*OKGC zox3{!LXtFP7_S!`0G{nOd-t7=cYU0nSBITjirHKpY!vqMpPp=Ig3SYI!i9HUwl9Wf z4VGLY?X;u&{xEc!C?$h$3)f2Ekd7t#sA)hf*ru;K-@?&Z)0=S_n z*mtA7XqK@ec4+R~M1c4fz()x?zaxF(4kZX;Cse~*9WhUdle2bEj|yMd12nk<-^3R(4PVL>_e571E@5SvcI5^V3r$v(1k zSAL6MI2&_+>Ttuy8GXh{^L|F^tWh5mqp#)C^>)- z8r9y*Ij#Te)i+*H`QAfwIC1IN{0SDLbryz-So@=CZ=0_GSh68@X0Xh6a|#v65buqA zQi=sUn{P{ce^KId%7djY|GUagl{Nc9?B0FK+czn#AeLo%!}0npS4-Va>q1fay!B#0t5eimwrHw@cjN}n+$gO4 z$So&ho|c@Xb+iOFAvKfffyIX^`oVEL-o*JPM#p|G$la?l#Ks#*97xRF2XeHJZti?P z@hDF)Q$Ao5yUKg2!!8khY2j3`@X1hIem|#Xjukh-edsQwcg%aEhFWHH5Iv(Eb_oUKmJO|wsq<~G8V?hs z_-I3WY$&B7r%&>foM9re?3k(QQ=E9D)05|(YdjMU{){OgrqW`AXNpCV^lxUsTIsUr zp)ggc&T)^j=95_iC5QNk1$0?5*(?Kd*CQ1cF8Jh%8I|UfV{)lYl#1UI+a^6nP_r<3 zehrWARTK-;d=dqqtOASUApWQ7m8hOB7o3J9;Bp$c}KW; zqfsed##I#tea7yv=FHaciGKHi7AHzqIJWEg#;S3kk%Fur6KH}lF|hk#V?#j7E8ECN z>E-h0f~GpQFW6V@2%z$xBI{p0eY~Jg`BaNI$7%XnAUrEIQ>kPmC1y{RPZe}v+SEF| z1WBdS5zoud!hGuc@kKUi_Eh9e@^qtftYlA)UmOOXHglFM#y;{4Uf|~_-4(stWEkAU z7a^Ud&KP#t1%;9-vm)e4h?@wv$|lNmnlj(Zg==BQ*M`Z8h>@F%u>Q^V_ry;UEnNT@ zXhc!Sd0%hM&Ld!H468Iv(iLYq3=6U5y9?!LLsA8eOFvC7d=)0hXhLujjoI1sZLIf+ zLICY}0t_+&5R@q@adsckqR=Qv!@Y$V&feu=$G!2!*{*E1s!OT|b*}r6t#^O5@-V#6 zw<8qFbxWIuI_SI1I>c2O=R?LR;;P7rx6WMul^8fC1wx2aqG0k1DHRj2%)W63JWxj4 zMQ=h}&@6&;yry!(d_6P*nyIXjiYMe6foj-lYqucw3<)U6u1L`SFMu^K*bcm zd+lS9rmYlVpaDIO@GdqSfTpAv99=@EHBut$qjEgBWoG5hDn^W>^!LFcCvU@uY`6wY zmdb4;!n_@k%?7ewYhD`J%A~tcHQd!{)-*gJmoxp8G zw)B$gXpt`2Fq4^?*DoTcG8&!0Gyc@|i@7p4%zB(AYDc?>7Wq(6L=Ay@y+*X`9MdGUy4tq z#EaY!^pMvz6D6E{;#%1VN#~lnN_8xK4b$;$9BzPK9LUX;auhnG*MtF3RmHs)@L|l2 z;Aeg*Jb3I@+)J__`(B}!uyLY6m?A~MMhNp{L!LNH+{S^sb=z9ghMYj@tg}0+1J?s= z6wSi!&y0pMEz(Igr8mN&&-&}z{f;OCjNU6tF|gC>*XMHui<743#?;GhP2H6# zzQ{B}8L!HBBg{K3?302k*V}1ijs8f4GEa{!Y{SMbNRWCop5Ry6mwcLjBfOeiIN%$$ zNq#=l{JAmP8RKZ|=nAMM%_t26xkGz}L*N@drLB!l_0N-qda&TqTdYj6?gTMacwdKe zTV(lK6q4Tb1kXW3TONwbp76>GoE%IY4bfhDEJiaYhYZ5P>uIS{Pry+Gc6UOC3IYwv z4b$xj!|&&B2`!AOhsmOmyQ_2hFi+W8N@EU~`}TYE>leGwc_3nw-sTbYjse-NnkO%MoZM-% z6Uv0~V89&$7|Gmy`k@UY)RxmmzTBEVGZaFyDmbaQD-LGv%yollN@qN!rZB!sJ)=?` zt>-i9?g_|w3n!MKl?vm(xg$PGE-cEjZIFw@cHmy3p`u|?rV}7zzF15xiUgr-(4BAs zI=|99%{0u{RRx`UNUGKpmB`OTAECw!*$T{?ED`T@<5B|-!F-(xBd|Cxkuh6@N}?9Q zitY#z?=xH7^}jpDQHXm$%d#j!j0Kr@j~`cv#^_7Va`-skO|Wif5ltdRDoJExj5@%% z=(+55o~gtH?dZ?%pqu45Q{u{QZ0Z#B!z?|+wE$*XMilU+fn8jgZmFpb!!f%`tHk-48XJ{A^)=ePz@98+N;;%mgh zS;Va-#dAA%Yz)`Pv7e+>W;CMFBN(#7qr#Hu3vmTzVVZ-5?W z>{h=ru$g}|85LE?qK?$JlLf|DhzUFG9b4L+t#mc*wS-(om{tFjx&x;o^oxZVCUDP>6sBiLKh9?F<8MG8JDg%dY&pE)!Lv?vkS(1trUF$j?@%tAui5n2)z%P) z7Gwb`Yve=;HcfHYXxio*fX{IaG8*oXoM{j~TiE>bG85^?apS_Q30~DL^4w-(C$uQ7 zv(EGc?6gz0BLk9>?<9fIh9NyuWfzZUUwqE%TMC2i6}UQyRmMlACC1 zl!5)dUW%PywrKq-^_mO%`R`ky=kAYMFgaE4LBTeZqf}`*TU5lh^s=0V=uLctPkYpR zp%CyR7<6`fy2f)`+mQM_heS~zbUDqH*m|Y_mTl07s-L+niHWT)w9w$Y4ffLe;YDnW zce==e;}o<>MZB|=FCCbE)K%!rCDQDiHAkk$OvX1#K-o>Q_F#GYVA(CS7hu=}-Cmr# zk0npXx};yk44{TC4r9!q80hH$U~90}48P6{3YQrM-%h9UiX>bfe@$Ge-}Y2X-sw9; zNkC9sF66>@KJ7hi1FoThlEF+5iPJ zOb_=qY(lqEC}E#4T;h{G0FoQlfbxJ9q|S_#_X#AxucF6DB66w0F85dicvpa1wO$ z?ywr}!^d}ovt3i8iw=YZeW9piHwCavv~X`bw(9J|w3I&12n-hUAM}|Hh<%;|t;U<8 zqF}5KNu_NdXC07hx=tzi_<<|aO>=*$NG~Db{>X67yPvXtX!R{puhYoAjCCDl9>sMt zPY;;?Zfwa*M1^Qd@J6M#@onKLFouSmsFE?LRhE$SP5<;oat*Sk zj2_&>;{^EZkAkTCh9EE?vfB_mbi^QjF!g8_xPNH0qWA~-#`sQ^@BW*iAKL%SuRv-a z81NaBLSo*MKnR2|FWdP8jGoBk<fJ-EYR$@z$S*-g?di%#h4 zgtRozgk-qDs?WkKg{G2iYQPmnZj`c6d2*kq`KVvT0AiZmX{4`1|SOIyuFp z7l7w-Be=+8b0I) znki&cuBU_=Mr)hwIR9RU_7Kbtb1=kV3W9#?^Nqb;bmqRm56H_FiopMq#JTNM={nZh zZy56RkTG}5V-*Pz6H{cvWT9N;1*zhfiSl2^NR`+pAX#^obQ% z*-6cMOzdcx?YCM?Wc5y!Hv}Nvh;W0~EYt+zg$vm&nUbaW_V@=k&C)inewA;f>i-e> z_x$ct$S4gI+2U#(aHA&gDY3WGMiz&?MeqmD%4l(NA^mHcV~u>F4{9U_{X=X1Gezse zCT1;Hpfwq-IHSwa%dNG(bz{wN_BQm%l{-}kK;BT22N2n2~o`+`wh zWsreGd~|Q<4ybdt&Ob8lKhmf(R`s^px-jE248ho{TV)xFtvWU9{;2~Qir>=2!7=Wo zg`H&XR2j0b3^kxm@qli||48wVWTw;wqI(h?lxHsYY-n?ce_N*VC@luL=m({QZ<#`; z?mTk(Zrt$vnbR&06ETSZldeY&AIg%aF8>$KEDz2xk3Belrs{Jj4DuH0J{r6PIi|NZ zL-8MVlXI0oy`tk#hT_Z$mydIGGv*XQ*@l1RUGn7cwh5QrDulT*Fd-0JFjaaPmI+)Q z`?qOBr>o|3;z?hB>6IXodua#$mDQW7KI=`}tprH;l)ZGCxi==c;C2oD;y(=T4`v5n zB|RnXR4GheJvF`IQb{~&r`Er60{BI)ELL&(2x0G3@|^ydpjn-j7x*fE$l!H#*clBo#fBULVbaoS_$L~E&4dY zjlp)s^Yxm8d1*A7UWVeF*}m`$;%$fJ;_GBT{$a+XEa3-6v0!fErr#sCw)IM!+3L}_DR(E4$5t8QUj$YO)ar)U=hmVs zib*?>+w_f6B!t1z*=tPP8A#(xz zN68U>c=gedbE9T1dpQ(KOvNY&iZ*rjF^AcnshMc8m~Z7JHD5JI{y8Vq92zUzt-kn2 zCPA|{X9W!0;Fmx{%~S-_g0d`3Pcx#WKJO0&g-3 zOP-L<S`VN+~V(r>{#* zts)3H?)^-nqsqK={p@40Y<>fDpRg~b^Qj8kq3o$(rOMMPbVV`Jrj+wYw{GE!aA zXO@-i6Vp5xy`c$_3GMEI_m>uVtBAu=Ti|zl3m}Cam|yN_xEbG(L<>jTrIBy~>l5aN z2*BQS4%j(+4DWeH2BmiO1kD!o&e~fG0(F0UgrH0egBg}KF46G2V)C8`<%Tp5*=E+8 zriWoW(rGXVUf&Yh?L-LB>q$uSvubhTvH>{Zh9)n`Z(+8^Y{@=n4ZXC9Fnw*ncaP-w;=PI1U)O?A7ArBTdDk9TG|C7lB^3! z5x{Vm)B^$WI;Xqn&#O>g3ZHI9+5ble5>g*+8}q^OD!HU!S|LsUxZRP}m%t;Z|A zqGh**VVc&Y`zmOo+qAt|pWWo;$yg?En~>)|8osAAON`ekgVo$=x}TF?{G{E{F|pXc zH$cQF0h4PUo=dGEo&%cIKRDq2i>`)cm-KOaZDo~>A1mP1t|*5ncs=AG!3sGTogkgW z)<5IL;?NRHoUtogD2^)F#jj_P+l23D^4#@&Uagx_PaD(2bU7PRnCob^HWT4?A_yZm zXrplV+;;x2_2bq%*jYCgjB1CAnquhmH2@#?BUNgt_>U?PD1qM7zusyp-ks*UnlBoq zCnnr|vW)V)0UZ!KD!4uDG)y?_BkUUI?bV}ch#>$1$ImO)&AH&oIIQ___0kBkXi#8Mzxnc ztbh|l<$(@14t;-7BMk(o`3Psb7iU8 zXLY}xDGao_)Tcnw3$k=YBni(8$Sp~ph}ZwIv^xMX|4c9zLPbHOmsdy%=bZ+B_nybi zx0JwGD-j7;?xSa})~%xxovz&=Ho5S7k~8US8>bE-QawMK_tG^*+^ z8Y!O#0}d2Z7}L#l2W@KdjCGYh7+=686Yz0|s3ZR0M3l-3Uhzx3?LXsbAZ}^O#v&Iy zbcZkt;zkW8VWGoUQV4F#vQ_g~BbXHV0k*gc*6h+}Sptv)F>(r_#iXDCW$vth7HKQ9 zu)(1$&G@8BZm4~T){Y1jlRL~_5Vzbf3F`@~!L~pVBXrg-mQ^0&F+{0q56;dOF_rYL zP(3tlhOpp|?T^G=T)Y1x=ZB}~sJ5UZ3>}j@UZE`-pxSe%e%rRAdcc-hp8>u=z1N9; zzyb(+jJY`x(_f|4qelv-L48E$yV??sWx`4k@xrGD5^LEUj#QmqK?l2 zb1>*6?m!!Zg0;&EE~|&TwVJZJR25Bss4=@)D0bO`q3igxIoH3s0XQ+o+e4^IacJ!D zb@bB<-8E;sV;&jwS2*M6hpT6X^!9(uBVwm#^0xnQPqom0Lcd|@yKVWP z^e-_J#0@w&zuZ+3E4ZBW4L0Vd?iTl8XAG{@D*P}A0=5FYxnc9nsd`HQedFfU9KO8; zGe=)Gmr_gop_KyBF#II=U6G?J9=PHkO9!|zJrXw=S_3pWt^S2I0I7No@q5M_64Ag7 zYZ?$06!^|dA1GO56{JD8(@41QFae4rh`iGGO5G4Q4^Y`Nh_x5-m zxBO898GwQT`PF|3&n8m>bnexDDg3{#po6|&^W@jNFT=k<1OBHu{5@6uFHQHf;#Y<4 zIQ(x{&3>V z7)vb&C{+MpqJWgdjMegn9fP?mz*GQ$(-({ zeV-Uva$%AafVcIP@@wW~68_sB!eWINm=oX!wkuptY68`Xu@KRO0x7Yn779RJ77(vQ zAG>wTpZBi_?Esg3^^H{7cc!(h2kXUbS^gop z1UtB8C4lW;20XVoRcQn4mwlf+T7C0zv>C@$2QQkp&d?KdtEm7Y83HuwH|NKc=lcPR z-LAg9##h>^)8l+gLBRY{rmUX?+yFo@DoHQqt&$0kHREPSD9Mg0SJp(OjF#O!-%jdH zTmB)q8Ed}U3XpmCb=;}MxTA|A4Y@UiKfTyBD()|sj9H>!v4BTf+>r2#nY99E z`@DqgGq13d(+ceACtwNSoh=p_#IXg7gQG_>Rp^-sWAJ-xjIS zY&^O6gGV3VqQ|*hyW?HsZ`ZR!_kk;>BiI(_g1 zer#RpNTQ&eTpcx1C80%5OYHdKiL`>ldfJq%9-jP6cj#Ic-QW9wL2ykU!8SG)vgq4l z%T`wtR(u7K zcNHFw`C`=yjvzW8V8CrU?N*ATz*GkLc#tDCnkJ^&gcaPFaCIJ1oanwIg$||{#w*Gm zg3bj4;Q3=8KDDd{?x)Pjw0(N`b=mKD+7)yZ=Sd9LU7kOg&}$orrbsSml$!xYqjSsLO5t4G=*jE8(dCAM<7H;gbD};bM?UFdPk~!B zeiGE?#o>#dSg*3__U%E_ZVEa)(1)EjiN~S>srTQiLi1wQrqY>xBg{F-HL}tKaVGzq zhWOe4Ywt_rp=|s9Pg7A6mmAr+N>{p7m}Cv@C{(hBC{shmG^CN;lsjbY>bkN-ToRQr zgBV*gOj!omH8VnXSth$N_MYRE`u|@%Z=V;>=kpxzX3q0G&f~Wo$M;x%-&4hkoMOXS z&aHY~EZpgR!q2*RI^NQ@jWWi2An9+8$f-KT2PF zlzYpG&90GUva;H_q(;n{WVVwkd&F__^n>Fa90x&`Xs_Jt_3UCF>VDP$rTy4Z3M;|e)v@8y7lHh};6!VB?*h zmp1cvbT8s8iT+~~-@tHwB_MfG1H<4^+ZvwDU4KIfju}MH9ZX{vWRB2+;}-C9>>$hJOd# z0sPfx`#Y4%7m_j)?3?jf?~tF8eu3x&Hph5vJJS@0o`QXDZq5fialnYXEImw7x4);s zIj^I7oyT6O<;h2_u?~!V*V%x-c`FwMdeHImIqv$@eB+G66*!v;K zrlc2Sy>S3hMK?Z3A7m~O{;JJF2#69^{&Z%nc2*ip8+k{?xZy01jT=X6Rpa_zf{!y=WK8>bA8wR`UM%h&?(_Ev-~)wCK&VI!{g zcD^+BmtD6weu~@XP*>A!(fP5KwrmQJ7uJ6V_ar00)k7_0gG z?Rs>fKsu&^x4s8W#$}aj^CRp3^lD<21YJE$&9a|Rnk2rkRVJ4krUD)>`lD&=Hp0q> zT!U^&+)?6|Kc~Zd(Uo0MBw>`O7PV{5t46cerz5nrQwHhXAyeI;FsfgZgd`f0Ee+Df zGVaM|C15_qF@l-wmQkmY;Ja!8b2??~tx{D7jBlWujDy}9royL1AAu8>-D2xHgKIPB zqd513+o$IE4z~k^b{am zAHh0XX2j$>(T{J?(KxDZ`F!bENEX9MB=ouZ&%1_~DQhX!Zma&O7D*h}+Mv;;*KrYDv6mXm4F6o=mAHHs zVp7iD37S-9R2l+HI1;F>d<4v8dAICJq3)fyQ?A;j5d^&ZZdiZ3QoLX|3;T zJEJ-n9>>$Fnv32f)*;2bZ^lY}ml$!4;sA;;u`Mfb9ip@RJ0S8|!-i>$2*j9s1E)!K zg~AsWix6~5UGL?--?qRg1%lY%6m;LIA#=c&80xV+b9fuU8>V_IQ2-YuAK(r`j&b9^ zRr2w}C<^es!46toEBi+@xUWA>1K}tD^85%KI3AIU&ot@yS^+7iIA{dU%;=#KRefrs^|( z%_&j$Yp=>Z36HPw2!Y)M3=nd&dNYdO2Ro=kviy&&o>%{06Z@wQLUP7j|G_+jr~I$K zchw5W1Avxiz`XvDEkmdLl2m&S?jN0Rkca8|F+plgPqkXb>g+pzovcjJ0*!RLE_$c8Rq0gPwTHlisAI=*Xe6cz>S9xty_TYZUqKE?cCKwkjZ{cbk)<1bxznti8ZFzDok$qY76)Ex1mkKlLD+`*Y1cMog8E&kfFlHm1 z#YRfYDzfe!x1X@b`eQQi-~CZhRfYjmNK*c=7D?yLJ$cVvK!dmWzrPU7lV|ch#6h&s zlKV^)F3(|cn$&T{Me+9Wg1TI-(I#dW)fl}jmR&3!f+xGF_>wbQmtpXv`OgpV#JT8B z+6IHCp#@c9r^)oiS(+P3j4|dK-5OZbSv2lK2B{pxxYjjq4-D;D$3?YQbHh3YjtnB@Ica>TC;!WOg zy0KsOin{l4rSf9;5ekukapSI?YtrryNKY%5q*2<+7&ECx_B`XNa&w%4)r^GuLEZB4 zu0fgMBP7o5dZqVxVC|AU%q&3^-!IqM?M^k$rLN)tpWPxsQ;*UJ%A z3nWJxz96hMu`du-u^78=1P^VQGqGAE+EIfah>PLRrIN9*^bH2gfjAZ}sf5*$6GFsEY$rXGg zH!;n(eNo1s&84b~CX?%faN@;TaW<0*G-PQLYP$LJ4E3oF&eS5go3-T6v*A9e!)RG@ zQJ=IHhC5OY^3rvPOk7l}Z@&yYsHG6Gc@SAB3{lvUW$m863)?O3&@;WzDN|jw%kSZe z*9Fs?8YS3+<KCmV$1741^O_iSwZTv$ZJ%e_asOl>HoS-L+94?VjgzvJ;5F*! ztD)Mgj2IkFvqssiT-K`w6ABzPB={=+gIb z+vA7hT+wE@Gv0;h+xofgieBYCH13*u6Y~_xGDr@E>5gq294CdURCpb?sAs-2Ea3+o zo(La`@1I>^i0Em4pIsWV0m#T1sagK3^I8Mwqry*%XWvbZJ2I_af3+$pH=Y91_~6d) z@c@jPMnR8#$%LC3u8QYfNA(Y~!lvZj!_Cp8Xe?qt?|5atA9kVUkd<=GC8uAu4W-*b ziyBPMgX8XkNuSMcMGJ_^ES{&jV_xU znuMf@_D->`9~&`HshZTC%p4ARc&r*C1Y ziw1XmcRo!gSs^`KAT73cQ2uE4kbKCwh^ zP0N#2p^20ChrkXB3}futjjOQ64dg$n&&iyYyC^5ND&6?kSED&A(UE3vM?e{p)V1hT zk~98sO{WLjU7Xt7r)9nR#84bn-656B*cdpSPT=W!0b=}l0;DYhy)E}edukUvqqV6S zi_u-N5kU>}8p*|s-MX=Tky#HS!qZ-}PS(!oooT$xMHU!VnK{>Mh$-8%4^qg|>$(h* zZd>=rQjQfRw{v0gEMDX(s&0kgUD#tF0%*s7Itybn~465td@)JDx{+kq5nWu#`+lf`$Jr^_BRLv`djQM*z(3bS!G#$4 zKt%5ahyY`@4L}Fkw63skBnitQuJ0y2?eTXx%t!bo_OvLi@}GU7_Zt-5# z^9>{jy*q+ZHIWi(M%~z7q5xgn{p&(F5~beZcrDb|`G)xXqy`!RO}{WVe~_;y)MeP#mu7A$TYW7rhnI5u~NrPMxMUZ{E}`C@W+tOpEWjesuNzS9;h9 zO=pAa`yje>RWsp0Pdgt3_f*jBs75~Q0b=M>^g!5rUGn9tUG`hve=q?s93lvTSLyT1 z3Z*3qJ@Ga%H0ioq@?xgX%P)tud#=wBz6Fv1jh?m}xW3Z9#m7`jae%LT?e-rdT)Z9A zm!%S?2;XuItlUAq5;OmlGx}GBKb(O7BamP(UX<06^*E|u?Lq(XrvYqvscV%dXZhC5 z=JqU1%yb;3>ulVw1YhcSTu)d$gWRZ<&V*~&DE|$~eP7Y)>RfXLW(PpWAE&VMVi05q zOAE^FkAMe!?p^%vr%z(vN~e#&YlOXNd=Om-);}V%oX>HXF{$wD7;pwy zA&sMi$+a9ZTP-R zfh5;`ML=r=c&-MQs4@B14@IU51YAcx&KRcfmLKoE-9FxhjejO6p>VoEvkUEW8_b(v zql0oN>n7rizW^sP1&39Pw7qikOhok{4QvO+|h+rw759;2wDMdrW`x1N!o6qr1 zkX3_rdwAE`?6)R2w?<@taTBs8+pL?YDMR$90Vmhs zw&qDGMrTHj_kF7@A3s70^~n{nV8#h^6^zNbcR(-hXzgr$+poTtf`|Eujk@ket5U3n0NJ*8mr%$G8d9q6ksUPO%cuXQ&tE$N94&tE%AHv z9rKap_?B2e6#4iF()ruRo6B!T{!~d=Y0j_F^`?Ey?5GfD@I#*W;0_A}0MgK7rDZbo zZDwUby~a$QJnG>cD}f1>ilNXl1zRhuzUuKa#wR|QP7iY?*bsp9f2u!PxK1vb{l1%z4zEpsLa6pspZu#L>9^LD zTa*4sAUEF+-`^ejrp0(}?-=fY8+o5WQ97N%r%vH;4|^x?UW$8*yf+si_U%N zaU+jP5d`H83KO{c|B>%Y#PtET|D_3d2D_#V_>%s`cG5!fS_y zn&p;nI*Mh?;4oj4W~|F|k>QM0X36u6hn&Ip&J)iM#@pUJHIsi)Lq-ks?mu4_=$o3Z zm*N_Dttouwk8n3e@$%)#lxEb&!eyUz!I9?u_g|0O2k}O^B9JEx{Xxl-lJ{^GSuxbN z6$r{mOu>=fv_#p`ZrB^0z%-Nwy=2x56-_!!_? zoG`B;<{WdjInxBwV4dUj#YEKk-AhC75a~D&(kj1Nwmmf+e?qc#d&}VKRkfwdBFwlN z>zL}b(O09x7Nu7Gdc$M?&REx1CE`iUMKDakgjYY_G$lj}%@h9Iq?w^$ph36t>uwTQ z@9`xrWZ7z)ro`0~raM3fhS==D&~8mtA%uG ze$I%T;Of5^nUwwi^|2TeKD1kJt8Ms@C|m*i;_=uvy8zU*X3X*&*G|{SPxz-AZ{r}-A^N1@v>(|bbp`|O_r2>y}f^BOHo9tfVz55(kr2=`nIM6B Date: Thu, 26 Jun 2025 11:43:08 -0400 Subject: [PATCH 3/3] Rework comparison topic, move scripts into dedicated files --- Documentation/comparing-builds.md | 254 ++++-------------------------- Scripts/comparebuilds.ps1 | 101 ++++++++++++ Scripts/comparebundles.ps1 | 121 ++++++++++++++ 3 files changed, 256 insertions(+), 220 deletions(-) create mode 100644 Scripts/comparebuilds.ps1 create mode 100644 Scripts/comparebundles.ps1 diff --git a/Documentation/comparing-builds.md b/Documentation/comparing-builds.md index 6ab6d73..6193f02 100644 --- a/Documentation/comparing-builds.md +++ b/Documentation/comparing-builds.md @@ -1,8 +1,22 @@ # Comparing Builds -The output can change each time a build is performed. Normally the change should be predictable, based on changes in the Unity project. But in other cases there may be a bug of "non-determinism", where the build changes each time it is run, or has different output from different identical build machines. So a common question is "what changed" between builds? This question arises most frequently in the area of AssetBundles, so the examples here focus on that type of build. But the same principals can also apply to Player builds. +When working with Unity typically many builds will be performed, so that the content can be tested in the player or released. Each time a build is performed it is likely that some content will changed. Normally the change should be predictable, based on changes made to assets, scenes, scripts, packages or any upgrades to the Unity Editor. But in other cases there may be a problem of "non-determinism", where the build changes each time it is run, or has different output from different identical build machines. So a common question is "what changed" between builds? This question arises most frequently in the area of AssetBundles, so the examples here focus on that type of build. But the same principals can also apply to Player builds. -For the purpose of these examples, suppose that two builds of the same project are located side-by-side in two directories, /build1 and /build2. This build includes an AssetBundle called "sprites.bundle" that contains 3 textures ("red.png", "Snow.png" and "Snow 1.png"). +This topic gives examples using several tools and techniques to compare build output files. + +* [comparebuilds.ps1](../scripts/comparebuilds.ps1) An example script using UnityDataTool to compare two builds at the object level +* [comparebundles.ps1](../scripts/comparebundles.ps1) An example script using UnityDatatTool to compare two versions of a build file. +* A diff tool for comparing directories, binary files and text files. These tools are readily available on Windows, Mac and Linux. For example Beyond Compare, WinMerge, and kdiff3. +* WebExtract for extracting contents of an AssetBundle (WebExtract is shipped as part of the Unity Editor installation). +* `UnityDataTool dump` to create a text representation of the content of a Unity Serialized File. + +The [Overview of Unity Content](./unity-content-format.md) topic gives useful background for the file formats and concepts that are discussed in this topic. + +# Example 1 - Changes in a texture + +As an example suppose that two builds of the same project are located side-by-side in two directories, /build1 and /build2. + +This build includes an AssetBundle called "sprites.bundle" that contains 3 textures ("red.png", "Snow.png" and "Snow 1.png"). ## File-level comparison @@ -23,99 +37,11 @@ UnityDataTool.exe analyze -o build1.db .\Build1\ UnityDataTool.exe analyze -o build2.db .\Build2\ ``` -Running the following PowerShell script would print all the objects, with info about whether they match between the two builds. Objects are matched primarily based on the AssetBundle and local file ID (object_id) matching. If there is a change in content then the CRC should change. The object size is also shown (which includes the actual texture data). +Running the following PowerShell script would print all the objects, with info about whether they match between the two builds. Objects are matched primarily based on the AssetBundle and local file ID (object_id) matching. If there is a change in content then the CRC should change. The object size is also shown (which includes the size of data that is stored in the side-car .resS file). -``` -param ( - [Parameter(Mandatory=$true, HelpMessage="Path to the first UnityDataTool database")] - [string]$db1, - - [Parameter(Mandatory=$true, HelpMessage="Path to the second UnityDataTool database")] - [string]$db2 -) - -# Check if the database file exists -if (-not (Test-Path $db1)) { - Write-Error "Database file '$db1' not found." - exit 1 -} - -if (-not (Test-Path $db2)) { - Write-Error "Database file '$db2' not found." - exit 1 -} - -# SQL query to compare the content of two builds. -# Note: when the ID of an object changes then it will not be matched as the same. -$query = @" -ATTACH DATABASE '$db2' AS db2; - -SELECT - COALESCE(o1.asset_bundle, o2.asset_bundle) AS asset_bundle, - COALESCE(o1.object_id, o2.object_id) AS object_id, - COALESCE(o1.type, o2.type) AS type, - COALESCE(o1.name, o2.name) AS name, - CASE - WHEN o1.asset_bundle IS NULL THEN 'Only in Build 1' - WHEN o2.asset_bundle IS NULL THEN 'Only in Build 2' - WHEN o1.crc32 != o2.crc32 OR o1.size != o2.size THEN 'Different' - ELSE 'Same' - END AS status, - o1.size AS size_build1, - o2.size AS size_build2, - o1.crc32 AS crc32_build1, - o2.crc32 AS crc32_build2 -FROM ( - SELECT - ab.name AS asset_bundle, - o.object_id, - t.name AS type, - o.name, - o.size, - o.crc32, - sf.name AS serialized_file - FROM - objects o - INNER JOIN - types t ON o.type = t.id - INNER JOIN - serialized_files sf ON o.serialized_file = sf.id - LEFT JOIN - asset_bundles ab ON sf.asset_bundle = ab.id -) AS o1 -FULL OUTER JOIN ( - SELECT - ab.name AS asset_bundle, - o.object_id, - t.name AS type, - o.name, - o.size, - o.crc32, - sf.name AS serialized_file - FROM - db2.objects o - INNER JOIN - db2.types t ON o.type = t.id - INNER JOIN - db2.serialized_files sf ON o.serialized_file = sf.id - LEFT JOIN - db2.asset_bundles ab ON sf.asset_bundle = ab.id -) AS o2 ON o1.asset_bundle = o2.asset_bundle - AND o1.object_id = o2.object_id - AND o1.type = o2.type - AND o1.name = o2.name - AND o1.serialized_file = o2.serialized_file; - -DETACH DATABASE db2; -"@ - -# Execute the query -Write-Host "Objects with differences, only in one DB, or the same:" -$results = sqlite3 $db1 ".mode column" $query -$results | ForEach-Object { Write-Output $_ } -``` +[comparebuilds.ps1](../scripts/comparebuilds.ps1) -This is a partial example output, where the "red.png" file has been edited between builds: +This is a truncated example output, where the "red.png" image has been changed between builds: ``` @@ -132,13 +58,13 @@ sprites.bundle -1350043613627603771 Texture2D Snow sprites.bundle 1 AssetBundle sprites.bundle Same 460 460 245831303 245831303 ``` -The output pinpoints that "red" has changed. The AssetBundleManifest object also changes, which is expected because it lists AssetBundle hashes. - -Note: This script is intentionally verbose for the sake of demonstration. For very large builds you probably would want to filter down to only show the objects that have changed, and perhaps focus only on certain files or object types. It should just be considered as a starting point for your own comparison scripts. +The output pinpoints that "red" has changed. The AssetBundleManifest object also changes, which is expected because it lists AssetBundle content hashes. ### Comparing Individual AssetBundles -A variation of the PowerShell script above could be used to compare two versions of a single AssetBundle. It creates temporary sqlite databases so that the comparison is a simple one-step process. +A variation of comparing entire builds is to compare two versions of an individual AssetBundle. + +The script [comparebundles.ps1](../scripts/comparebundles.ps1) is an example of this approach. It creates temporary sqlite databases, so that the comparison is a convenient one-step process. For example, to analyze the two versions of sprites.bundle it could be invoked like this: @@ -146,121 +72,7 @@ For example, to analyze the two versions of sprites.bundle it could be invoked l comparebundles.ps1 .\Build1\sprites.bundle .\Build2\sprites.bundle ``` -The script is as follows: - -``` -# PowerShell Script to compare the objects inside two versions of an AssetBundle -param( - [Parameter(Mandatory=$true, HelpMessage="Path to the first AssetBundle")] - [string]$FileName1, - - [Parameter(Mandatory=$true, HelpMessage="Path to the second AssetBundle")] - [string]$FileName2 -) - -if (-not (Test-Path -Path $FileName1)) { - Write-Error "File '$FileName1' does not exist." - exit 1 -} - -if (-not (Test-Path -Path $FileName2)) { - Write-Error "File '$FileName2' does not exist." - exit 1 -} - -# Separate the directory and file name -$FileDir1 = Split-Path -Path $FileName1 -Parent -$FileLeaf1 = Split-Path -Path $FileName1 -Leaf - -# If no directory is detected (relative file name), use the current working directory -if (-not $FileDir1) { - $FileDir1 = "." -} - -$FileDir2 = Split-Path -Path $FileName2 -Parent -$FileLeaf2 = Split-Path -Path $FileName2 -Leaf -if (-not $FileDir2) { - $FileDir2 = "." -} - -# Retrieve the system's temp folder and create the temporary database file names -$tempFolder = $env:TEMP -$dbName1 = Join-Path -Path $tempFolder -ChildPath ("1_$FileLeaf1.db") -$dbName2 = Join-Path -Path $tempFolder -ChildPath ("2_$FileLeaf2.db") - -#Analyze each AssetBundle into temporary databases -UnityDataTool analyze $FileDir1 -o $dbName1 -p $FileLeaf1 -UnityDataTool analyze $FileDir2 -o $dbName2 -p $FileLeaf2 - -$query = @" -ATTACH DATABASE '$dbName2' AS db2; - -SELECT - COALESCE(o1.serialized_file, o2.serialized_file) AS serialized_file, - COALESCE(o1.object_id, o2.object_id) AS object_id, - COALESCE(o1.type, o2.type) AS type, - COALESCE(o1.name, o2.name) AS name, - CASE - WHEN o1.asset_bundle IS NULL THEN 'Only in Build 1' - WHEN o2.asset_bundle IS NULL THEN 'Only in Build 2' - ELSE 'Different' - END AS status, - o1.size AS size_build1, - o2.size AS size_build2, - o1.crc32 AS crc32_build1, - o2.crc32 AS crc32_build2 -FROM ( - SELECT - ab.name AS asset_bundle, - o.object_id, - t.name AS type, - o.name, - o.size, - o.crc32, - sf.name AS serialized_file - FROM - objects o - INNER JOIN - types t ON o.type = t.id - INNER JOIN - serialized_files sf ON o.serialized_file = sf.id - LEFT JOIN - asset_bundles ab ON sf.asset_bundle = ab.id -) AS o1 -FULL OUTER JOIN ( - SELECT - ab.name AS asset_bundle, - o.object_id, - t.name AS type, - o.name, - o.size, - o.crc32, - sf.name AS serialized_file - FROM - db2.objects o - INNER JOIN - db2.types t ON o.type = t.id - INNER JOIN - db2.serialized_files sf ON o.serialized_file = sf.id - LEFT JOIN - db2.asset_bundles ab ON sf.asset_bundle = ab.id -) AS o2 ON o1.asset_bundle = o2.asset_bundle - AND o1.object_id = o2.object_id - AND o1.serialized_file = o2.serialized_file -WHERE NOT (o1.asset_bundle IS NOT NULL AND o2.asset_bundle IS NOT NULL AND o1.crc32 = o2.crc32 AND o1.size = o2.size); - -DETACH DATABASE db2; -"@ - -# Query the database using sqlite3 -sqlite3 $dbName1 ".mode column" $query - -# Delete the temporary database files -Remove-Item $dbName1 -Force -Remove-Item $dbName2 -Force -``` - -For the sake of brevity, the query above does not list objects when they are the same in both AssetBundles. It also shows the SerializedFile name instead of the AssetBundle name. The output from this example would be: +The output from this example would be: ``` @@ -273,7 +85,7 @@ CAB-6b49068aebcf9d3b05692c8efd933167 -3600607445234681765 Texture2D red Dif UnityDataTool helps pinpoint which AssetBundle objects have changed between builds. But to actually understand "what" has changed it is necessary to look deeper into the content of the AssetBundles and how Unity serializes data. -We already know that sprites.bundle has changed between builds, and the script pinpoints "red" as the object that changed, whereas "Snow" and "Snow 1" are unchanged. So how can we determine more information about what has changed about "red.png"? +We already know that sprites.bundle has changed between builds, and the script pinpoints "red" as the object that changed, whereas "Snow" and "Snow 1" are unchanged. So how can we determine more information about what has changed in the build of "red.png"? To go deeper we can extract the content of each build of sprites.bundle. The **WebExtract** tool that is shipped with Unity can be used to do this. When run on an AssetBundle it creates a subdirectory with all the contents of the AssetBundle expanded as individual files. @@ -284,15 +96,15 @@ cd ..\Build2 WebExtract.exe sprites.bundle ``` -When WebExtract has been run on both copies of sprites.bundle the directory comparison shows this result: +When WebExtract has been run on both copies of sprites.bundle the diff tool can be used to compare the contents of the AssetBundle: ![](./AssetBundleContentComparison.png) -So we see that the AssetBundle contains the following content: +In this case we see that the AssetBundle contained the following content: ![](./SpritesBundleContent.png) -Based on the diff we see that the SerializedFile is the same, but the .resS file is different. This means that the Texture2D object has the exact same properties (including dimensions, format etc), but the pixel data is different. That information may already be sufficient to fully understand why the AssetBundle has changed. +And, based on the diff, we see that the SerializedFile is unchanged between builds, but the .resS file is different. This means that the Texture2D object has the exact same properties (including dimensions, format etc), but the pixel data is different. For the sake of further illustration, we can go deeper and look at how the .resS file relates to the 3 textures in sprites.bundle. @@ -300,11 +112,11 @@ When a binary diff is performed on the two verions of the .resS file we can see ![](./ResFileBinaryDiff.png) -We can surmise that the "red" texture is at the start of the .resS file, because we know that from our UnityDataTool queries that "red" is the only texture that changed. But we could also determine this by further analysis of the AssetBundle contents. +We know from our UnityDataTool queries that "red" is the only texture that changed, so we can surmise that the "red" texture is at the start of the .resS file. Its possible to confirm this by further analysis of the AssetBundle contents. To understand the content of a resS file we have to look at the associated SerializedFile. E.g. to understand what is contained inside `CAB-6b49068aebcf9d3b05692c8efd933167.resS` we need to look inside `CAB-6b49068aebcf9d3b05692c8efd933167`. -Because the SerializedFile is a binary format, we first need to convert it to text. We can do this using the `dump` feature of UnityDataTools. We can run this on either build1 or build2 (because the file is identical from both builds). +Because the SerializedFile is a binary format, we first need to convert it to text. We can do this using the `dump` feature of UnityDataTools. We can run this on the WebExtract output from either build1 or build2 (because the file is identical from both builds). ``` UnityDataTool dump CAB-6b49068aebcf9d3b05692c8efd933167 @@ -343,10 +155,12 @@ ID: -39415655269619539 (ClassID: 28) Texture2D ``` -The resS file is a simple format with no header. It is literally just the binary data of textures or meshes, concatenated together, sometimes with padding in between. The m_StreamData describes each range of bytes inside the .resS file. The total file size on disk is 1200463 bytes so every byte of the file is accounted for based on those three objects. +The resS file is a simple format with no header. It is literally just the binary data of textures or meshes, concatenated together (sometimes with extra padding bytes between entries). The m_StreamData describes each range of bytes inside the .resS file. The total file size on disk is 1200463 bytes, so every byte of the file is accounted for based on the three objects. + +This diagram shows the structure and relation ship between the objects inside the Serialized file and the content of the .resS file. ![](./SpritesBundleDetailedContent.png) -In this case the range information for "red" exactly matches the changes we observed in the binary diff. So this matches our understanding that pixel data inside "red.png" caused the AssetBundle content to change. +Based on this analysis we have confirmed that the range information for "red" exactly matches the changes we observed in the binary diff. So this confirms our understanding that pixel data inside "red.png" is what caused the AssetBundle content to change. This same approach can be used to analyze mesh data inside .resS files. And also for Audio and Video inside .resource files. diff --git a/Scripts/comparebuilds.ps1 b/Scripts/comparebuilds.ps1 new file mode 100644 index 0000000..595358b --- /dev/null +++ b/Scripts/comparebuilds.ps1 @@ -0,0 +1,101 @@ +# Example Power Shell script that compare two builds (at the object level). +# It requires that you first run "UnityDataTool analyze" on each build, then pass the resulting databases to this script. +# It requires that sqlite3 is installed, in a location that is available in the PATH environmental variable. + +# Note: This script is intentionally verbose for the sake of demonstration. For very large builds you probably +# would want to hide unchanged objects, which can be achieved with a small change in the embedded SQL statement (see comparebundles.ps1). + +# DISCLAIMER: +# This script is provided "as-is," without any warranty of any kind, express or implied. +# By using this script, you agree that you understand its purpose and that you use it entirely at your own risk. +# The author assumes no liability for any damages resulting from its use, misuse, or inability to use. +# +# Always review and test this script in a safe environment before applying it to a production system. + +param ( + [Parameter(Mandatory=$true, HelpMessage="Path to the first UnityDataTool database")] + [string]$db1, + + [Parameter(Mandatory=$true, HelpMessage="Path to the second UnityDataTool database")] + [string]$db2 +) + +# Check if the database file exists +if (-not (Test-Path $db1)) { + Write-Error "Database file '$db1' not found." + exit 1 +} + +if (-not (Test-Path $db2)) { + Write-Error "Database file '$db2' not found." + exit 1 +} + +# SQL query to compare the content of two builds. +# Note: when the ID of an object changes then it will not be matched as the same. +$query = @" +ATTACH DATABASE '$db2' AS db2; + +SELECT + COALESCE(o1.asset_bundle, o2.asset_bundle) AS asset_bundle, + COALESCE(o1.object_id, o2.object_id) AS object_id, + COALESCE(o1.type, o2.type) AS type, + COALESCE(o1.name, o2.name) AS name, + CASE + WHEN o1.asset_bundle IS NULL THEN 'Only in Build 1' + WHEN o2.asset_bundle IS NULL THEN 'Only in Build 2' + WHEN o1.crc32 != o2.crc32 OR o1.size != o2.size THEN 'Different' + ELSE 'Same' + END AS status, + o1.size AS size_build1, + o2.size AS size_build2, + o1.crc32 AS crc32_build1, + o2.crc32 AS crc32_build2 +FROM ( + SELECT + ab.name AS asset_bundle, + o.object_id, + t.name AS type, + o.name, + o.size, + o.crc32, + sf.name AS serialized_file + FROM + objects o + INNER JOIN + types t ON o.type = t.id + INNER JOIN + serialized_files sf ON o.serialized_file = sf.id + LEFT JOIN + asset_bundles ab ON sf.asset_bundle = ab.id +) AS o1 +FULL OUTER JOIN ( + SELECT + ab.name AS asset_bundle, + o.object_id, + t.name AS type, + o.name, + o.size, + o.crc32, + sf.name AS serialized_file + FROM + db2.objects o + INNER JOIN + db2.types t ON o.type = t.id + INNER JOIN + db2.serialized_files sf ON o.serialized_file = sf.id + LEFT JOIN + db2.asset_bundles ab ON sf.asset_bundle = ab.id +) AS o2 ON o1.asset_bundle = o2.asset_bundle + AND o1.object_id = o2.object_id + AND o1.type = o2.type + AND o1.name = o2.name + AND o1.serialized_file = o2.serialized_file; + +DETACH DATABASE db2; +"@ + +# Execute the query +Write-Host "Objects with differences, only in one DB, or the same:" +$results = sqlite3 $db1 ".mode column" $query +$results | ForEach-Object { Write-Output $_ } diff --git a/Scripts/comparebundles.ps1 b/Scripts/comparebundles.ps1 new file mode 100644 index 0000000..45d533b --- /dev/null +++ b/Scripts/comparebundles.ps1 @@ -0,0 +1,121 @@ +# PowerShell Script to compare the objects inside two versions of an AssetBundle +# It requires that sqlite3 is installed, in a location that is available in the PATH environmental variable. + +# For the sake of brevity, the query does not list objects when they are the same in both AssetBundles. + +# DISCLAIMER: +# This script is provided "as-is," without any warranty of any kind, express or implied. +# By using this script, you agree that you understand its purpose and that you use it entirely at your own risk. +# The author assumes no liability for any damages resulting from its use, misuse, or inability to use. +# +# Always review and test this script in a safe environment before applying it to a production system. + + +param( + [Parameter(Mandatory=$true, HelpMessage="Path to the first AssetBundle")] + [string]$FileName1, + + [Parameter(Mandatory=$true, HelpMessage="Path to the second AssetBundle")] + [string]$FileName2 +) + +if (-not (Test-Path -Path $FileName1)) { + Write-Error "File '$FileName1' does not exist." + exit 1 +} + +if (-not (Test-Path -Path $FileName2)) { + Write-Error "File '$FileName2' does not exist." + exit 1 +} + +# Separate the directory and file name +$FileDir1 = Split-Path -Path $FileName1 -Parent +$FileLeaf1 = Split-Path -Path $FileName1 -Leaf + +# If no directory is detected (relative file name), use the current working directory +if (-not $FileDir1) { + $FileDir1 = "." +} + +$FileDir2 = Split-Path -Path $FileName2 -Parent +$FileLeaf2 = Split-Path -Path $FileName2 -Leaf +if (-not $FileDir2) { + $FileDir2 = "." +} + +# Retrieve the system's temp folder and create the temporary database file names +$tempFolder = $env:TEMP +$dbName1 = Join-Path -Path $tempFolder -ChildPath ("1_$FileLeaf1.db") +$dbName2 = Join-Path -Path $tempFolder -ChildPath ("2_$FileLeaf2.db") + +#Analyze each AssetBundle into temporary databases +UnityDataTool analyze $FileDir1 -o $dbName1 -p $FileLeaf1 +UnityDataTool analyze $FileDir2 -o $dbName2 -p $FileLeaf2 + +$query = @" +ATTACH DATABASE '$dbName2' AS db2; + +SELECT + COALESCE(o1.serialized_file, o2.serialized_file) AS serialized_file, + COALESCE(o1.object_id, o2.object_id) AS object_id, + COALESCE(o1.type, o2.type) AS type, + COALESCE(o1.name, o2.name) AS name, + CASE + WHEN o1.asset_bundle IS NULL THEN 'Only in Build 1' + WHEN o2.asset_bundle IS NULL THEN 'Only in Build 2' + ELSE 'Different' + END AS status, + o1.size AS size_build1, + o2.size AS size_build2, + o1.crc32 AS crc32_build1, + o2.crc32 AS crc32_build2 +FROM ( + SELECT + ab.name AS asset_bundle, + o.object_id, + t.name AS type, + o.name, + o.size, + o.crc32, + sf.name AS serialized_file + FROM + objects o + INNER JOIN + types t ON o.type = t.id + INNER JOIN + serialized_files sf ON o.serialized_file = sf.id + LEFT JOIN + asset_bundles ab ON sf.asset_bundle = ab.id +) AS o1 +FULL OUTER JOIN ( + SELECT + ab.name AS asset_bundle, + o.object_id, + t.name AS type, + o.name, + o.size, + o.crc32, + sf.name AS serialized_file + FROM + db2.objects o + INNER JOIN + db2.types t ON o.type = t.id + INNER JOIN + db2.serialized_files sf ON o.serialized_file = sf.id + LEFT JOIN + db2.asset_bundles ab ON sf.asset_bundle = ab.id +) AS o2 ON o1.asset_bundle = o2.asset_bundle + AND o1.object_id = o2.object_id + AND o1.serialized_file = o2.serialized_file +WHERE NOT (o1.asset_bundle IS NOT NULL AND o2.asset_bundle IS NOT NULL AND o1.crc32 = o2.crc32 AND o1.size = o2.size); + +DETACH DATABASE db2; +"@ + +# Query the database using sqlite3 +sqlite3 $dbName1 ".mode column" $query + +# Delete the temporary database files +Remove-Item $dbName1 -Force +Remove-Item $dbName2 -Force