Skip to content

Commit c8f8221

Browse files
committed
refactor: restructure project into modular architecture
1 parent e44fe79 commit c8f8221

File tree

530 files changed

+4583
-4141
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

530 files changed

+4583
-4141
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ riderModule.iml
55
/_ReSharper.Caches/
66
.idea/
77
.vs
8-
UnityAsset.NET.sln.DotSettings.user
8+
UnityAsset.NET.sln.DotSettings.user
9+
artifacts/

Directory.Build.props

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<Project>
2+
<PropertyGroup>
3+
<Authors>AXiX</Authors>
4+
<Copyright>AXiX</Copyright>
5+
<PackageProjectUrl>https://github.com/AXiX-official/UnityAsset.NET</PackageProjectUrl>
6+
<RepositoryUrl>https://github.com/AXiX-official/UnityAsset.NET</RepositoryUrl>
7+
<PackageTags>Unity</PackageTags>
8+
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
9+
<PackageLicenseFile>LICENSE</PackageLicenseFile>
10+
<PackageReadmeFile>README.md</PackageReadmeFile>
11+
</PropertyGroup>
12+
13+
<PropertyGroup>
14+
<LangVersion>latest</LangVersion>
15+
<Nullable>enable</Nullable>
16+
<WarningsAsErrors>nullable</WarningsAsErrors>
17+
<ImplicitUsings>enable</ImplicitUsings>
18+
</PropertyGroup>
19+
20+
<PropertyGroup>
21+
<PackageOutputPath>$(SolutionDir)artifacts\</PackageOutputPath>
22+
</PropertyGroup>
23+
24+
<ItemGroup>
25+
<None Include="$(SolutionDir)LICENSE" Pack="true" PackagePath="\" />
26+
<None Include="$(SolutionDir)README.md" Pack="true" PackagePath="\" />
27+
</ItemGroup>
28+
</Project>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace UnityAsset.NET.BundleFiles
2+
{
3+
[Flags]
4+
public enum ArchiveFlags : UInt32
5+
{
6+
CompressionTypeMask = 0x3f,
7+
BlocksAndDirectoryInfoCombined = 0x40,
8+
BlocksInfoAtTheEnd = 0x80,
9+
OldWebPluginCompatibility = 0x100,
10+
BlockInfoNeedPaddingAtStart = 0x200,
11+
UnityCNEncryption = 0x400,
12+
UnityCNEncryptionNew = 0x1000
13+
}
14+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System.Text;
2+
using UnityAsset.NET.IO;
3+
4+
namespace UnityAsset.NET.BundleFiles
5+
{
6+
public class BlocksAndDirectoryInfo
7+
{
8+
public byte[] UncompressedDataHash;
9+
public StorageBlockInfo[] BlocksInfo;
10+
public FileEntry[] DirectoryInfo;
11+
12+
public BlocksAndDirectoryInfo(
13+
byte[] uncompressedDataHash,
14+
StorageBlockInfo[] blocksInfo,
15+
FileEntry[] directoryInfo)
16+
{
17+
UncompressedDataHash = uncompressedDataHash;
18+
BlocksInfo = blocksInfo;
19+
DirectoryInfo = directoryInfo;
20+
}
21+
22+
public static BlocksAndDirectoryInfo Parse(IReader reader) => new BlocksAndDirectoryInfo(
23+
reader.ReadBytes(16),
24+
reader.ReadArray(StorageBlockInfo.Parse),
25+
reader.ReadArray(FileEntry.Parse)
26+
);
27+
28+
public void Serialize(IWriter writer)
29+
{
30+
writer.WriteBytes(UncompressedDataHash);
31+
writer.WriteArrayWithCount(BlocksInfo, (w, info) => info.Serialize(w));
32+
writer.WriteArrayWithCount(DirectoryInfo, (w, info) => info.Serialize(w));
33+
}
34+
35+
public override string ToString()
36+
{
37+
StringBuilder sb = new StringBuilder();
38+
sb.AppendLine($"UncompressedDataHash: {BitConverter.ToString(UncompressedDataHash)}");
39+
for (int i = 0; i < BlocksInfo.Length; ++i)
40+
sb.AppendLine($"Block {i}: {BlocksInfo[i]}");
41+
var directoryInfoSpan = DirectoryInfo.AsSpan();
42+
for (int i = 0; i < directoryInfoSpan.Length; ++i)
43+
sb.AppendLine($"Directory {i}: {directoryInfoSpan[i]}");
44+
return sb.ToString();
45+
}
46+
}
47+
}
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
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

Comments
 (0)