support UnityWebData

This commit is contained in:
Perfare
2018-03-05 02:35:53 +08:00
parent c8393e165f
commit a9f1bd320e
9 changed files with 186 additions and 53 deletions

View File

@ -162,7 +162,7 @@ namespace Unity_Studio
int dataEnd = assetsFileReader.ReadInt32();
fileGen = assetsFileReader.ReadInt32();
uint dataOffset = assetsFileReader.ReadUInt32();
sharedAssetsList[0].fileName = Path.GetFileName(fullName); //reference itself because sharedFileIDs start from 1
sharedAssetsList[0].fileName = fileName; //reference itself because sharedFileIDs start from 1
switch (fileGen)
{

View File

@ -5,19 +5,18 @@ using SevenZip.Compression.LZMA;
namespace Unity_Studio
{
public class MemoryFile
{
public string fileName;
public MemoryStream stream;
}
public class BundleFile
{
public int format;
public string versionPlayer;
public string versionEngine;
public List<MemoryAssetsFile> MemoryAssetsFileList = new List<MemoryAssetsFile>();
public class MemoryAssetsFile
{
public string fileName;
public MemoryStream memStream;
}
public List<MemoryFile> fileList = new List<MemoryFile>();
public BundleFile(EndianBinaryReader bundleReader)
{
@ -92,16 +91,16 @@ namespace Unity_Studio
int fileCount = reader.ReadInt32();
for (int i = 0; i < fileCount; i++)
{
var memFile = new MemoryAssetsFile();
memFile.fileName = reader.ReadStringToNull();
var file = new MemoryFile();
file.fileName = reader.ReadStringToNull();
int fileOffset = reader.ReadInt32();
fileOffset += offset;
int fileSize = reader.ReadInt32();
long nextFile = reader.Position;
reader.Position = fileOffset;
var buffer = reader.ReadBytes(fileSize);
memFile.memStream = new MemoryStream(buffer);
MemoryAssetsFileList.Add(memFile);
file.stream = new MemoryStream(buffer);
fileList.Add(file);
reader.Position = nextFile;
}
}
@ -143,11 +142,9 @@ namespace Unity_Studio
case 3://LZ4HC
{
byte[] uncompressedBytes = new byte[uncompressedSize];
using (var mstream = new MemoryStream(blocksInfoBytes))
using (var decoder = new Lz4DecoderStream(new MemoryStream(blocksInfoBytes)))
{
var decoder = new Lz4DecoderStream(mstream);
decoder.Read(uncompressedBytes, 0, uncompressedSize);
decoder.Dispose();
}
blocksInfoStream = new MemoryStream(uncompressedBytes);
break;
@ -188,11 +185,9 @@ namespace Unity_Studio
case 3://LZ4HC
{
var uncompressedBytes = new byte[uncompressedSize];
using (var mstream = new MemoryStream(compressedBytes))
using (var decoder = new Lz4DecoderStream(new MemoryStream(compressedBytes)))
{
var decoder = new Lz4DecoderStream(mstream);
decoder.Read(uncompressedBytes, 0, uncompressedSize);
decoder.Dispose();
}
assetsDataStream.Write(uncompressedBytes, 0, uncompressedSize);
break;
@ -205,15 +200,15 @@ namespace Unity_Studio
var entryinfo_count = blocksInfo.ReadInt32();
for (int i = 0; i < entryinfo_count; i++)
{
var memFile = new MemoryAssetsFile();
var file = new MemoryFile();
var entryinfo_offset = blocksInfo.ReadInt64();
var entryinfo_size = blocksInfo.ReadInt64();
flag = blocksInfo.ReadInt32();
memFile.fileName = blocksInfo.ReadStringToNull();
file.fileName = Path.GetFileName(blocksInfo.ReadStringToNull());
assetsDataReader.Position = entryinfo_offset;
var buffer = assetsDataReader.ReadBytes((int)entryinfo_size);
memFile.memStream = new MemoryStream(buffer);
MemoryAssetsFileList.Add(memFile);
file.stream = new MemoryStream(buffer);
fileList.Add(file);
}
}
}

View File

@ -1,531 +0,0 @@
#define CHECK_ARGS
#define CHECK_EOF
//#define LOCAL_SHADOW
using System;
using System.IO;
namespace Lz4
{
public class Lz4DecoderStream : Stream
{
public Lz4DecoderStream(Stream input, long inputLength = long.MaxValue)
{
Reset(input, inputLength);
}
public void Reset(Stream input, long inputLength = long.MaxValue)
{
this.inputLength = inputLength;
this.input = input;
phase = DecodePhase.ReadToken;
decodeBufferPos = 0;
litLen = 0;
matLen = 0;
matDst = 0;
inBufPos = DecBufLen;
inBufEnd = DecBufLen;
}
public override void Close()
{
input = null;
}
private long inputLength;
private Stream input;
//because we might not be able to match back across invocations,
//we have to keep the last window's worth of bytes around for reuse
//we use a circular buffer for this - every time we write into this
//buffer, we also write the same into our output buffer
private const int DecBufLen = 0x10000;
private const int DecBufMask = 0xFFFF;
private const int InBufLen = 128;
private byte[] decodeBuffer = new byte[DecBufLen + InBufLen];
private int decodeBufferPos, inBufPos, inBufEnd;
//we keep track of which phase we're in so that we can jump right back
//into the correct part of decoding
private DecodePhase phase;
private enum DecodePhase
{
ReadToken,
ReadExLiteralLength,
CopyLiteral,
ReadOffset,
ReadExMatchLength,
CopyMatch,
}
//state within interruptable phases and across phase boundaries is
//kept here - again, so that we can punt out and restart freely
private int litLen, matLen, matDst;
public override int Read(byte[] buffer, int offset, int count)
{
#if CHECK_ARGS
if (buffer == null)
throw new ArgumentNullException("buffer");
if (offset < 0 || count < 0 || buffer.Length - count < offset)
throw new ArgumentOutOfRangeException();
if (input == null)
throw new InvalidOperationException();
#endif
int nRead, nToRead = count;
var decBuf = decodeBuffer;
//the stringy gotos are obnoxious, but their purpose is to
//make it *blindingly* obvious how the state machine transitions
//back and forth as it reads - remember, we can yield out of
//this routine in several places, and we must be able to re-enter
//and pick up where we left off!
#if LOCAL_SHADOW
var phase = this.phase;
var inBufPos = this.inBufPos;
var inBufEnd = this.inBufEnd;
#endif
switch (phase)
{
case DecodePhase.ReadToken:
goto readToken;
case DecodePhase.ReadExLiteralLength:
goto readExLiteralLength;
case DecodePhase.CopyLiteral:
goto copyLiteral;
case DecodePhase.ReadOffset:
goto readOffset;
case DecodePhase.ReadExMatchLength:
goto readExMatchLength;
case DecodePhase.CopyMatch:
goto copyMatch;
}
readToken:
int tok;
if (inBufPos < inBufEnd)
{
tok = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
tok = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (tok == -1)
goto finish;
#endif
}
litLen = tok >> 4;
matLen = (tok & 0xF) + 4;
switch (litLen)
{
case 0:
phase = DecodePhase.ReadOffset;
goto readOffset;
case 0xF:
phase = DecodePhase.ReadExLiteralLength;
goto readExLiteralLength;
default:
phase = DecodePhase.CopyLiteral;
goto copyLiteral;
}
readExLiteralLength:
int exLitLen;
if (inBufPos < inBufEnd)
{
exLitLen = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
exLitLen = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (exLitLen == -1)
goto finish;
#endif
}
litLen += exLitLen;
if (exLitLen == 255)
goto readExLiteralLength;
phase = DecodePhase.CopyLiteral;
goto copyLiteral;
copyLiteral:
int nReadLit = litLen < nToRead ? litLen : nToRead;
if (nReadLit != 0)
{
if (inBufPos + nReadLit <= inBufEnd)
{
int ofs = offset;
for (int c = nReadLit; c-- != 0;)
buffer[ofs++] = decBuf[inBufPos++];
nRead = nReadLit;
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
nRead = ReadCore(buffer, offset, nReadLit);
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (nRead == 0)
goto finish;
#endif
}
offset += nRead;
nToRead -= nRead;
litLen -= nRead;
if (litLen != 0)
goto copyLiteral;
}
if (nToRead == 0)
goto finish;
phase = DecodePhase.ReadOffset;
goto readOffset;
readOffset:
if (inBufPos + 1 < inBufEnd)
{
matDst = (decBuf[inBufPos + 1] << 8) | decBuf[inBufPos];
inBufPos += 2;
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
matDst = ReadOffsetCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (matDst == -1)
goto finish;
#endif
}
if (matLen == 15 + 4)
{
phase = DecodePhase.ReadExMatchLength;
goto readExMatchLength;
}
else
{
phase = DecodePhase.CopyMatch;
goto copyMatch;
}
readExMatchLength:
int exMatLen;
if (inBufPos < inBufEnd)
{
exMatLen = decBuf[inBufPos++];
}
else
{
#if LOCAL_SHADOW
this.inBufPos = inBufPos;
#endif
exMatLen = ReadByteCore();
#if LOCAL_SHADOW
inBufPos = this.inBufPos;
inBufEnd = this.inBufEnd;
#endif
#if CHECK_EOF
if (exMatLen == -1)
goto finish;
#endif
}
matLen += exMatLen;
if (exMatLen == 255)
goto readExMatchLength;
phase = DecodePhase.CopyMatch;
goto copyMatch;
copyMatch:
int nCpyMat = matLen < nToRead ? matLen : nToRead;
if (nCpyMat != 0)
{
nRead = count - nToRead;
int bufDst = matDst - nRead;
if (bufDst > 0)
{
//offset is fairly far back, we need to pull from the buffer
int bufSrc = decodeBufferPos - bufDst;
if (bufSrc < 0)
bufSrc += DecBufLen;
int bufCnt = bufDst < nCpyMat ? bufDst : nCpyMat;
for (int c = bufCnt; c-- != 0;)
buffer[offset++] = decBuf[bufSrc++ & DecBufMask];
}
else
{
bufDst = 0;
}
int sOfs = offset - matDst;
for (int i = bufDst; i < nCpyMat; i++)
buffer[offset++] = buffer[sOfs++];
nToRead -= nCpyMat;
matLen -= nCpyMat;
}
if (nToRead == 0)
goto finish;
phase = DecodePhase.ReadToken;
goto readToken;
finish:
nRead = count - nToRead;
int nToBuf = nRead < DecBufLen ? nRead : DecBufLen;
int repPos = offset - nToBuf;
if (nToBuf == DecBufLen)
{
Buffer.BlockCopy(buffer, repPos, decBuf, 0, DecBufLen);
decodeBufferPos = 0;
}
else
{
int decPos = decodeBufferPos;
while (nToBuf-- != 0)
decBuf[decPos++ & DecBufMask] = buffer[repPos++];
decodeBufferPos = decPos & DecBufMask;
}
#if LOCAL_SHADOW
this.phase = phase;
this.inBufPos = inBufPos;
#endif
return nRead;
}
private int ReadByteCore()
{
var buf = decodeBuffer;
if (inBufPos == inBufEnd)
{
int nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
return -1;
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
}
return buf[inBufPos++];
}
private int ReadOffsetCore()
{
var buf = decodeBuffer;
if (inBufPos == inBufEnd)
{
int nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
return -1;
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
}
if (inBufEnd - inBufPos == 1)
{
buf[DecBufLen] = buf[inBufPos];
int nRead = input.Read(buf, DecBufLen + 1,
InBufLen - 1 < inputLength ? InBufLen - 1 : (int)inputLength);
#if CHECK_EOF
if (nRead == 0)
{
inBufPos = DecBufLen;
inBufEnd = DecBufLen + 1;
return -1;
}
#endif
inputLength -= nRead;
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead + 1;
}
int ret = (buf[inBufPos + 1] << 8) | buf[inBufPos];
inBufPos += 2;
return ret;
}
private int ReadCore(byte[] buffer, int offset, int count)
{
int nToRead = count;
var buf = decodeBuffer;
int inBufLen = inBufEnd - inBufPos;
int fromBuf = nToRead < inBufLen ? nToRead : inBufLen;
if (fromBuf != 0)
{
var bufPos = inBufPos;
for (int c = fromBuf; c-- != 0;)
buffer[offset++] = buf[bufPos++];
inBufPos = bufPos;
nToRead -= fromBuf;
}
if (nToRead != 0)
{
int nRead;
if (nToRead >= InBufLen)
{
nRead = input.Read(buffer, offset,
nToRead < inputLength ? nToRead : (int)inputLength);
nToRead -= nRead;
}
else
{
nRead = input.Read(buf, DecBufLen,
InBufLen < inputLength ? InBufLen : (int)inputLength);
inBufPos = DecBufLen;
inBufEnd = DecBufLen + nRead;
fromBuf = nToRead < nRead ? nToRead : nRead;
var bufPos = inBufPos;
for (int c = fromBuf; c-- != 0;)
buffer[offset++] = buf[bufPos++];
inBufPos = bufPos;
nToRead -= fromBuf;
}
inputLength -= nRead;
}
return count - nToRead;
}
#region Stream internals
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override void Flush()
{
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
#endregion
}
}

View File

@ -16,13 +16,17 @@ namespace Unity_Studio
public static void LoadFile(string fullName)
{
if (CheckBundleFile(fullName, out var reader))
switch (CheckFileType(fullName, out var reader))
{
LoadBundleFile(fullName, reader);
}
else
{
LoadAssetsFile(fullName, reader);
case FileType.AssetsFile:
LoadAssetsFile(fullName, reader);
break;
case FileType.BundleFile:
LoadBundleFile(fullName, reader);
break;
case FileType.WebFile:
LoadWebFile(fullName, reader);
break;
}
}
@ -94,12 +98,12 @@ namespace Unity_Studio
var fileName = Path.GetFileName(fullName);
StatusStripUpdate("Decompressing " + fileName);
var bundleFile = new BundleFile(reader);
foreach (var memFile in bundleFile.MemoryAssetsFileList)
foreach (var file in bundleFile.fileList)
{
if (!assetsfileListHash.Contains(memFile.fileName.ToUpper()))
if (!assetsfileListHash.Contains(file.fileName.ToUpper()))
{
StatusStripUpdate("Loading " + memFile.fileName);
var assetsFile = new AssetsFile(Path.GetDirectoryName(fullName) + "\\" + memFile.fileName, new EndianBinaryReader(memFile.memStream));
StatusStripUpdate("Loading " + file.fileName);
var assetsFile = new AssetsFile(Path.GetDirectoryName(fullName) + "\\" + file.fileName, new EndianBinaryReader(file.stream));
if (assetsFile.valid)
{
assetsFile.bundlePath = fullName;
@ -124,6 +128,30 @@ namespace Unity_Studio
reader.Dispose();
}
private static void LoadWebFile(string fullName, EndianBinaryReader reader)
{
var fileName = Path.GetFileName(fullName);
StatusStripUpdate("Loading " + fileName);
var bundleFile = new WebFile(reader);
reader.Dispose();
foreach (var file in bundleFile.fileList)
{
var dummyName = Path.GetDirectoryName(fullName) + "\\" + file.fileName;
switch (CheckFileType(file.stream, out reader))
{
case FileType.AssetsFile:
LoadAssetsFile(dummyName, reader);
break;
case FileType.BundleFile:
LoadBundleFile(dummyName, reader);
break;
case FileType.WebFile:
LoadWebFile(dummyName, reader);
break;
}
}
}
public static void MergeSplitAssets(string dirPath)
{
string[] splitFiles = Directory.GetFiles(dirPath, "*.split0");

View File

@ -29,17 +29,23 @@ namespace Unity_Studio
public static Action<string> StatusStripUpdate;
public static Action<int> ProgressBarMaximumAdd;
public enum FileType
{
AssetsFile,
BundleFile,
WebFile
}
public static int ExtractBundleFile(string bundleFileName)
{
int extractedCount = 0;
if (CheckBundleFile(bundleFileName, out var reader))
if (CheckFileType(bundleFileName, out var reader) == FileType.BundleFile)
{
StatusStripUpdate($"Decompressing {Path.GetFileName(bundleFileName)} ...");
var extractPath = bundleFileName + "_unpacked\\";
Directory.CreateDirectory(extractPath);
var bundleFile = new BundleFile(reader);
foreach (var memFile in bundleFile.MemoryAssetsFileList)
foreach (var memFile in bundleFile.fileList)
{
var filePath = extractPath + memFile.fileName.Replace('/', '\\');
if (!Directory.Exists(Path.GetDirectoryName(filePath)))
@ -52,8 +58,8 @@ namespace Unity_Studio
extractedCount += 1;
using (var file = File.Create(filePath))
{
memFile.memStream.WriteTo(file);
memFile.memStream.Close();
memFile.stream.WriteTo(file);
memFile.stream.Close();
}
}
}
@ -355,9 +361,20 @@ namespace Unity_Studio
return Path.GetInvalidFileNameChars().Aggregate(str, (current, c) => current.Replace(c, '_'));
}
public static bool CheckBundleFile(string fileName, out EndianBinaryReader reader)
public static FileType CheckFileType(MemoryStream stream, out EndianBinaryReader reader)
{
reader = new EndianBinaryReader(stream);
return CheckFileType(reader);
}
public static FileType CheckFileType(string fileName, out EndianBinaryReader reader)
{
reader = new EndianBinaryReader(File.OpenRead(fileName));
return CheckFileType(reader);
}
public static FileType CheckFileType(EndianBinaryReader reader)
{
var signature = reader.ReadStringToNull();
reader.Position = 0;
switch (signature)
@ -366,9 +383,20 @@ namespace Unity_Studio
case "UnityRaw":
case "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA":
case "UnityFS":
return true;
return FileType.BundleFile;
case "UnityWebData1.0":
return FileType.WebFile;
default:
return false;
{
var magic = reader.ReadBytes(2);
reader.Position = 0;
if (WebFile.gzipMagic.SequenceEqual(magic))
{
return FileType.WebFile;
}
return FileType.AssetsFile;
}
}
}

View File

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
namespace Unity_Studio
{
public class WebFile
{
public static byte[] gzipMagic = { 0x1f, 0x8b };
public List<MemoryFile> fileList = new List<MemoryFile>();
public class WebData
{
public int dataOffset;
public int dataLength;
public string path;
}
public WebFile(EndianBinaryReader reader)
{
var magic = reader.ReadBytes(2);
reader.Position = 0;
if (gzipMagic.SequenceEqual(magic))
{
var stream = new MemoryStream();
using (var gstream = new GZipStream(reader.BaseStream, CompressionMode.Decompress))
{
gstream.CopyTo(stream);
}
stream.Position = 0;
using (reader = new EndianBinaryReader(stream, EndianType.LittleEndian))
{
ReadUnityWebData(reader);
}
}
else
{
ReadUnityWebData(reader);
}
}
private void ReadUnityWebData(EndianBinaryReader reader)
{
var signature = reader.ReadStringToNull();
if (signature != "UnityWebData1.0")
return;
var headLength = reader.ReadInt32();
var dataList = new List<WebData>();
while (reader.Position < headLength)
{
var data = new WebData();
data.dataOffset = reader.ReadInt32();
data.dataLength = reader.ReadInt32();
var pathLength = reader.ReadInt32();
data.path = Encoding.UTF8.GetString(reader.ReadBytes(pathLength));
dataList.Add(data);
}
foreach (var data in dataList)
{
var file = new MemoryFile();
file.fileName = Path.GetFileName(data.path);
reader.Position = data.dataOffset;
file.stream = new MemoryStream(reader.ReadBytes(data.dataLength));
fileList.Add(file);
}
}
}
}