forked from Unity-Technologies/UnityDataTools
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWebBundleHelper.cs
More file actions
151 lines (135 loc) · 5.14 KB
/
Copy pathWebBundleHelper.cs
File metadata and controls
151 lines (135 loc) · 5.14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Text.Json;
namespace UnityDataTools.UnityDataTool;
public static class WebBundleHelper
{
private static readonly byte[] WebBundlePrefix = Encoding.UTF8.GetBytes("UnityWebData1.0\0");
public static bool IsWebBundle(string path)
{
return (
path.EndsWith(".data")
|| path.EndsWith(".data.gz")
|| path.EndsWith(".data.br")
);
}
public static void Extract(FileInfo filename, DirectoryInfo outputFolder, string filter = null)
{
Console.WriteLine($"Extracting web bundle: {filename}");
using var fileStream = File.Open(filename.ToString(), FileMode.Open);
using var stream = GetStream(filename, fileStream);
using var reader = new BinaryReader(stream, Encoding.UTF8);
var fileDescriptions = ParseWebBundleHeader(reader);
int total = fileDescriptions.Count;
int extracted = 0;
foreach (var description in fileDescriptions)
{
// Always read the bytes to advance the stream position.
var data = ReadBytes(reader, (int)description.Size);
if (filter != null && !description.Path.Contains(filter, StringComparison.OrdinalIgnoreCase))
continue;
Console.WriteLine($"... Extracting {description.Path}");
var path = Path.Combine(outputFolder.ToString(), description.Path);
Directory.CreateDirectory(Path.GetDirectoryName(path));
File.WriteAllBytes(path, data);
extracted++;
}
Console.WriteLine($"Extracted {extracted} out of {total} files.");
}
public static void List(FileInfo filename, OutputFormat format)
{
using var fileStream = File.Open(filename.ToString(), FileMode.Open);
using var stream = GetStream(filename, fileStream);
using var reader = new BinaryReader(stream, Encoding.UTF8);
var fileDescriptions = ParseWebBundleHeader(reader);
if (format == OutputFormat.Json)
{
var jsonArray = new object[fileDescriptions.Count];
for (int i = 0; i < fileDescriptions.Count; i++)
{
var desc = fileDescriptions[i];
jsonArray[i] = new { path = desc.Path, size = desc.Size };
}
var json = JsonSerializer.Serialize(jsonArray, new JsonSerializerOptions { WriteIndented = true });
Console.WriteLine(json);
}
else
{
foreach (var description in fileDescriptions)
{
Console.WriteLine($"{description.Path}");
Console.WriteLine($" Size: {description.Size}");
Console.WriteLine();
}
}
}
struct FileDescription
{
public uint ByteOffset;
public uint Size;
public string Path;
}
static Stream GetStream(FileInfo filename, FileStream fileStream)
{
var fileExtension = Path.GetExtension(filename.ToString());
return fileExtension switch
{
".data" => fileStream,
".gz" => new GZipStream(fileStream, CompressionMode.Decompress),
".br" => new BrotliStream(fileStream, CompressionMode.Decompress),
_ => throw new FileFormatException("Incorrect file extension for web bundle"),
};
}
static List<FileDescription> ParseWebBundleHeader(BinaryReader reader)
{
var result = new List<FileDescription>();
var prefix = ReadBytes(reader, WebBundlePrefix.Length);
if (!prefix.SequenceEqual(WebBundlePrefix))
{
throw new FileFormatException("File is not a valid web bundle.");
}
uint headerSize = ReadUInt32(reader);
// Advance offset past prefix string and header size uint.
var currentByteOffset = WebBundlePrefix.Length + sizeof(uint);
while (currentByteOffset < headerSize)
{
var fileByteOffset = ReadUInt32(reader);
var fileSize = ReadUInt32(reader);
var filePathLength = ReadUInt32(reader);
var filePath = Encoding.UTF8.GetString(ReadBytes(reader, (int)filePathLength));
result.Add(new FileDescription()
{
ByteOffset = fileByteOffset,
Size = fileSize,
Path = filePath,
});
// Advance byte offset, so we keep track of the position (to know when we're done reading the header).
currentByteOffset += 3 * sizeof(uint) + (int)filePathLength;
}
return result;
}
static uint ReadUInt32(BinaryReader reader)
{
try
{
return reader.ReadUInt32();
}
catch (EndOfStreamException)
{
throw new FileFormatException("File data is corrupt.");
}
}
static byte[] ReadBytes(BinaryReader reader, int count)
{
var result = reader.ReadBytes(count);
if (result.Length != count)
{
throw new FileFormatException("File data is corrupt.");
}
return result;
}
}