1+ using System . Text ;
2+ using UnityAsset . NET . Files ;
3+ using UnityAsset . NET . FileSystem ;
4+ using UnityAsset . NET . IO ;
5+ using UnityAsset . NET . IO . Reader ;
6+ using UnityAsset . NET . IO . Writer ;
7+
8+ namespace UnityAsset . NET . BundleFiles
9+ {
10+ public class BundleFile : IFile
11+ {
12+ /// <summary>
13+ /// BundleFile's Header
14+ /// </summary>
15+ public readonly Header ? Header ;
16+
17+ /// <summary>
18+ /// Blocks and Cab info
19+ /// </summary>
20+ public readonly BlocksAndDirectoryInfo ? DataInfo ;
21+
22+ /// <summary>
23+ /// SerializedFiles and BinaryData
24+ /// </summary>
25+ public readonly List < FileWrapper > Files ;
26+
27+ /// <summary>
28+ /// Optional UnityCN encryption data
29+ /// </summary>
30+ public readonly UnityCN ? UnityCnInfo ;
31+
32+ /// <summary>
33+ /// Optional key for UnityCN encryption
34+ /// </summary>
35+ public readonly string ? UnityCnKey ;
36+
37+ public IVirtualFileInfo ? SourceVirtualFile { get ; private set ; }
38+
39+ public static UnityCN ? ParseUnityCnInfo ( IReader reader , Header header , string ? key )
40+ {
41+ var unityCnMask = UnityCNVersionJudge ( header . UnityRevision )
42+ ? ArchiveFlags . BlockInfoNeedPaddingAtStart
43+ : ArchiveFlags . UnityCNEncryption | ArchiveFlags . UnityCNEncryptionNew ;
44+
45+ if ( ( header . Flags & unityCnMask ) != 0 )
46+ {
47+ key ??= Setting . DefaultUnityCNKey ;
48+ if ( key == null )
49+ throw new Exception ( $ "UnityCN key is required for decryption. UnityCN Flag Mask: { unityCnMask } ") ;
50+ return new UnityCN ( reader , key ) ;
51+ }
52+
53+ return null ;
54+ }
55+
56+ public static void AlignAfterHeader ( IReader reader , Header header )
57+ {
58+ if ( header . NeedAlign ( ) )
59+ {
60+ reader . Align ( 16 ) ;
61+ }
62+ }
63+
64+ public static BlocksAndDirectoryInfo ParseDataInfo ( IReader reader , Header header )
65+ {
66+ byte [ ] blocksInfoBytes ;
67+ if ( ( header . Flags & ArchiveFlags . BlocksInfoAtTheEnd ) != 0 )
68+ {
69+ var pos = reader . Position ;
70+ reader . Seek ( ( int ) ( header . Size - header . CompressedBlocksInfoSize ) ) ;
71+ blocksInfoBytes = reader . ReadBytes ( ( int ) header . CompressedBlocksInfoSize ) ;
72+ reader . Seek ( pos ) ;
73+ }
74+ else
75+ {
76+ blocksInfoBytes = reader . ReadBytes ( ( int ) header . CompressedBlocksInfoSize ) ;
77+ }
78+
79+ var compressionType = ( CompressionType ) ( header . Flags & ArchiveFlags . CompressionTypeMask ) ;
80+ MemoryReader blocksInfoUncompressedData = new MemoryReader ( ( int ) header . UncompressedBlocksInfoSize ) ;
81+ Compression . DecompressToBytes ( blocksInfoBytes , blocksInfoUncompressedData . AsWritableSpan , compressionType ) ;
82+ var dataInfo = BlocksAndDirectoryInfo . Parse ( blocksInfoUncompressedData ) ;
83+ if ( dataInfo . BlocksInfo . Any ( blockInfo => blockInfo . UncompressedSize > int . MaxValue ) )
84+ {
85+ throw new Exception ( "Block size too large." ) ;
86+ }
87+
88+ if ( ! UnityCNVersionJudge ( header . UnityRevision ) &&
89+ ( header . Flags & ArchiveFlags . BlockInfoNeedPaddingAtStart ) != 0 )
90+ reader . Align ( 16 ) ;
91+ return dataInfo ;
92+ }
93+
94+ public BundleFile ( Header header , BlocksAndDirectoryInfo dataInfo , List < FileWrapper > files , string ? key = null )
95+ {
96+ UnityCnKey = key ;
97+ Header = header ;
98+ DataInfo = dataInfo ;
99+ Files = files ;
100+ }
101+
102+ public BundleFile ( IVirtualFileInfo fileInfo , string ? key = null )
103+ {
104+ var cfrProvider = new CustomFileReaderProvider ( fileInfo ) ;
105+ var reader = cfrProvider . CreateReader ( ) ;
106+ UnityCnKey = key ;
107+ Header = Header . Parse ( reader ) ;
108+ if ( Header . UnityRevision == "0.0.0" )
109+ {
110+ Header . UnityRevision = Setting . DefaultUnityVerion ;
111+ }
112+
113+ UnityCnInfo = ParseUnityCnInfo ( reader , Header , UnityCnKey ) ;
114+ UnityCnKey = UnityCnInfo ? . Key ;
115+ AlignAfterHeader ( reader , Header ) ;
116+ DataInfo = ParseDataInfo ( reader , Header ) ;
117+
118+ var blockReaderProvider = new BlockReaderProvider ( DataInfo . BlocksInfo , new SliceFile ( fileInfo . GetFile ( ) ,
119+ ( ulong ) reader . Position ,
120+ ( ulong ) ( reader . Length - reader . Position ) ) , UnityCnInfo ) ;
121+ Files = new List < FileWrapper > ( ) ;
122+ foreach ( var dir in DataInfo . DirectoryInfo )
123+ {
124+ Files . Add ( new FileWrapper ( new SlicedReaderProvider ( blockReaderProvider , dir . Offset , dir . Size ) , dir ) ) ;
125+ }
126+
127+ SourceVirtualFile = fileInfo ;
128+ }
129+
130+ public void Serialize ( string path , CompressionType infoPacker = CompressionType . Lz4HC ,
131+ CompressionType dataPacker = CompressionType . Lz4HC , string ? unityCnKey = null )
132+ {
133+ using var output = new FileStream ( path , FileMode . Create , FileAccess . Write , FileShare . None ) ;
134+ var writer = new CustomStreamWriter ( output , leaveOpen : false ) ;
135+ Serialize ( writer , infoPacker , dataPacker , unityCnKey ) ;
136+ }
137+
138+ public void Serialize ( IWriter writer , CompressionType infoPacker = CompressionType . Lz4HC ,
139+ CompressionType dataPacker = CompressionType . Lz4HC , string ? unityCnKey = null )
140+ {
141+ if ( Header == null || DataInfo == null || Files == null )
142+ throw new NullReferenceException ( "BundleFile has not read completely." ) ;
143+
144+ if ( ! ( unityCnKey is null ) &&
145+ ( ! ( dataPacker is CompressionType . Lz4 ) && ! ( dataPacker is CompressionType . Lz4HC ) ) )
146+ throw new Exception (
147+ $ "UnityCN Encryption only support packing data with Lz4/Lz4HC, but { dataPacker } was set.") ;
148+
149+ using var blockWriter = BlockStreamWriter . GetBlockWriter ( dataPacker ) ;
150+
151+ var directoryInfo = new List < FileEntry > ( ) ;
152+ ulong offset = 0 ;
153+ foreach ( var file in Files )
154+ {
155+ blockWriter . SetAlignBegin ( ) ;
156+ ulong cabSize = file . File . WriteBytes ( blockWriter ) ;
157+ directoryInfo . Add ( new FileEntry ( offset , cabSize , file . Info . Flags , file . Info . Path ) ) ;
158+ offset += cabSize ;
159+ }
160+
161+ blockWriter . Finish ( ) ;
162+ using var blockStream = blockWriter . GetDataStream ( ) ;
163+
164+ var dataInfo = new BlocksAndDirectoryInfo ( DataInfo . UncompressedDataHash , blockWriter . BlockInfos . ToArray ( ) ,
165+ directoryInfo . ToArray ( ) ) ;
166+ using var dataInfoStream = new MemoryStream ( ) ;
167+ var dataInfoWriter = new CustomStreamWriter ( dataInfoStream ) ;
168+ dataInfo . Serialize ( dataInfoWriter ) ;
169+ dataInfoWriter . Finish ( ) ;
170+ var uncompressedBlocksInfoSize = dataInfoStream . Length ;
171+ var compressedDataInfoStream = Compression . CompressToStream ( dataInfoStream . ToArray ( ) , infoPacker ) ;
172+ var header = new Header ( Header . Signature , Header . Version , Header . UnityVersion ,
173+ Header . UnityRevision . ToString ( ) ,
174+ 0 , ( uint ) compressedDataInfoStream . Length , ( uint ) uncompressedBlocksInfoSize ,
175+ ( Header . Flags & ~ ArchiveFlags . CompressionTypeMask ) | ( ArchiveFlags ) infoPacker ) ;
176+ if ( unityCnKey == null && UnityCnKey != null )
177+ {
178+ var unityCnMask = UnityCNVersionJudge ( header . UnityRevision )
179+ ? ArchiveFlags . BlockInfoNeedPaddingAtStart
180+ : ArchiveFlags . UnityCNEncryption | ArchiveFlags . UnityCNEncryptionNew ;
181+ if ( unityCnMask == ( ArchiveFlags . UnityCNEncryption | ArchiveFlags . UnityCNEncryptionNew ) )
182+ unityCnMask = ( Header . Flags & ArchiveFlags . UnityCNEncryptionNew ) != 0
183+ ? ArchiveFlags . UnityCNEncryptionNew
184+ : ArchiveFlags . UnityCNEncryption ;
185+ header . Flags &= ~ unityCnMask ;
186+ }
187+
188+ bool needAlignAfterHeader = false ;
189+ bool needAlignAfterInfo = false ;
190+ bool infoAtEnd = ( header . Flags & ArchiveFlags . BlocksInfoAtTheEnd ) != 0 ;
191+ if ( ! UnityCNVersionJudge ( header . UnityRevision ) &&
192+ ( header . Flags & ArchiveFlags . BlockInfoNeedPaddingAtStart ) != 0 )
193+ needAlignAfterInfo = true ;
194+ if ( Header . NeedAlign ( )
195+ || ! UnityCNVersionJudge ( Header . UnityRevision ) &&
196+ ( Header . Flags & ArchiveFlags . BlockInfoNeedPaddingAtStart ) != 0 )
197+ needAlignAfterHeader = true ;
198+
199+ header . Size = header . SerializeSize ;
200+ if ( unityCnKey != null && UnityCnInfo != null )
201+ header . Size += UnityCnInfo . SerializeSize ;
202+ if ( needAlignAfterHeader )
203+ header . Size = Align ( header . Size , 16 ) ;
204+ if ( ! infoAtEnd )
205+ {
206+ header . Size += compressedDataInfoStream . Length ;
207+ if ( needAlignAfterInfo ) header . Size = Align ( header . Size , 16 ) ;
208+ }
209+
210+ header . Size += blockStream . Length ;
211+ if ( infoAtEnd )
212+ {
213+ header . Size += compressedDataInfoStream . Length ;
214+ if ( needAlignAfterInfo ) header . Size = Align ( header . Size , 16 ) ;
215+ }
216+
217+ header . Serialize ( writer ) ;
218+ if ( unityCnKey != null && UnityCnInfo != null )
219+ UnityCnInfo . Serialize ( writer ) ;
220+ if ( needAlignAfterHeader )
221+ writer . Align ( 16 ) ;
222+ if ( ! infoAtEnd )
223+ {
224+ writer . WriteStream ( compressedDataInfoStream ) ;
225+ if ( needAlignAfterInfo ) writer . Align ( 16 ) ;
226+ }
227+
228+ writer . WriteStream ( blockStream ) ;
229+ if ( infoAtEnd )
230+ {
231+ writer . WriteStream ( compressedDataInfoStream ) ;
232+ if ( needAlignAfterInfo ) writer . Align ( 16 ) ;
233+ }
234+ }
235+
236+ public ulong WriteBytes ( IWriter writer )
237+ {
238+ var pos = writer . Position ;
239+ Serialize ( writer ) ;
240+ return ( ulong ) ( writer . Position - pos ) ;
241+ }
242+
243+ private static long Align ( long size , int alignment )
244+ {
245+ var offset = size % alignment ;
246+ if ( offset == 0 )
247+ return size ;
248+ return size + alignment - offset ;
249+ }
250+
251+ public override string ToString ( )
252+ {
253+ StringBuilder sb = new StringBuilder ( ) ;
254+ sb . AppendLine ( $ "Header: { Header } ") ;
255+ sb . AppendLine ( $ "DataInfo: { DataInfo } ") ;
256+ for ( int i = 0 ; i < Files . Count ; ++ i )
257+ sb . AppendLine ( $ "File { i } : { Files [ i ] } ") ;
258+ return sb . ToString ( ) ;
259+ }
260+
261+ public static bool UnityCNVersionJudge ( UnityRevision version )
262+ {
263+ return version < "2020" || //2020 and earlier
264+ ( version >= "2020.3" && version <= "2020.3.34" ) || //2020.3.34 and earlier
265+ ( version >= "2021.3" && version <= "2021.3.2" ) || //2021.3.2 and earlier
266+ ( version >= "2022.3" && version <= "2022.3.1" ) ; //2022.3.1 and earlier
267+ }
268+ }
269+ }
0 commit comments