mirror of
https://github.com/aelurum/AssetStudio.git
synced 2025-06-03 00:58:13 -04:00
Add multiBundle support
including fake headers
This commit is contained in:
parent
db4eb30a27
commit
bc0e32efec
@ -201,7 +201,7 @@ namespace AssetStudio
|
||||
{
|
||||
if (!assetsFileListHash.Contains(reader.FileName))
|
||||
{
|
||||
Logger.Info($"Loading {reader.FullPath}");
|
||||
Logger.Info($"Loading \"{reader.FullPath}\"");
|
||||
try
|
||||
{
|
||||
var assetsFile = new SerializedFile(reader, this);
|
||||
@ -248,13 +248,13 @@ namespace AssetStudio
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning($"Failed to read assets file {reader.FullPath}\r\n{e}");
|
||||
Logger.Warning($"Failed to read assets file \"{reader.FullPath}\"\n{e}");
|
||||
reader.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info($"Skipping {reader.FullPath}");
|
||||
Logger.Info($"Skipping \"{reader.FullPath}\"");
|
||||
reader.Dispose();
|
||||
}
|
||||
return true;
|
||||
@ -284,38 +284,46 @@ namespace AssetStudio
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning($"Failed to read assets file {reader.FullPath} from {Path.GetFileName(originalPath)}\r\n{e}");
|
||||
Logger.Warning($"Failed to read assets file \"{reader.FullPath}\" from {Path.GetFileName(originalPath)}\n{e}");
|
||||
resourceFileReaders.TryAdd(reader.FileName, reader);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Info($"Skipping {originalPath} ({reader.FileName})");
|
||||
Logger.Info($"Skipping \"{originalPath}\" ({reader.FileName})");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool LoadBundleFile(FileReader reader, string originalPath = null)
|
||||
{
|
||||
Logger.Info("Loading " + reader.FullPath);
|
||||
Logger.Info($"Loading \"{reader.FullPath}\"");
|
||||
Logger.Debug($"Bundle offset: {reader.Position}");
|
||||
var bundleStream = new OffsetStream(reader);
|
||||
var bundleReader = new FileReader(reader.FullPath, bundleStream);
|
||||
|
||||
try
|
||||
{
|
||||
var bundleFile = new BundleFile(reader, ZstdEnabled, specifiedUnityVersion);
|
||||
foreach (var file in bundleFile.fileList)
|
||||
var bundleFile = new BundleFile(bundleReader, ZstdEnabled, specifiedUnityVersion);
|
||||
var isLoaded = LoadBundleFiles(bundleReader, bundleFile, originalPath);
|
||||
if (!isLoaded)
|
||||
return false;
|
||||
|
||||
while (bundleFile.IsMultiBundle && isLoaded)
|
||||
{
|
||||
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName);
|
||||
var subReader = new FileReader(dummyPath, file.stream);
|
||||
if (subReader.FileType == FileType.AssetsFile)
|
||||
bundleStream.Offset = reader.Position;
|
||||
bundleReader = new FileReader($"{reader.FullPath}_0x{bundleStream.Offset:X}", bundleStream);
|
||||
if (bundleReader.Position > 0)
|
||||
{
|
||||
if (!LoadAssetsFromMemory(subReader, originalPath ?? reader.FullPath, bundleFile.m_Header.unityRevision))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceFileReaders.TryAdd(file.fileName, subReader);
|
||||
bundleStream.Offset += bundleReader.Position;
|
||||
bundleReader.FullPath = $"{reader.FullPath}_0x{bundleStream.Offset:X}";
|
||||
bundleReader.FileName = $"{reader.FileName}_0x{bundleStream.Offset:X}";
|
||||
}
|
||||
Logger.Info($"[MultiBundle] Loading \"{reader.FileName}\" from offset: 0x{bundleStream.Offset:X}");
|
||||
bundleFile = new BundleFile(bundleReader, ZstdEnabled, specifiedUnityVersion);
|
||||
isLoaded = LoadBundleFiles(bundleReader, bundleFile, originalPath ?? reader.FullPath);
|
||||
}
|
||||
return true;
|
||||
return isLoaded;
|
||||
}
|
||||
catch (NotSupportedException e)
|
||||
{
|
||||
@ -324,23 +332,42 @@ namespace AssetStudio
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
var str = $"Error while reading bundle file {reader.FullPath}";
|
||||
var str = $"Error while reading bundle file \"{bundleReader.FullPath}\"";
|
||||
if (originalPath != null)
|
||||
{
|
||||
str += $" from {Path.GetFileName(originalPath)}";
|
||||
}
|
||||
Logger.Warning($"{str}\r\n{e}");
|
||||
Logger.Warning($"{str}\n{e}");
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
reader.Dispose();
|
||||
bundleReader.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private bool LoadBundleFiles(FileReader reader, BundleFile bundleFile, string originalPath = null)
|
||||
{
|
||||
foreach (var file in bundleFile.fileList)
|
||||
{
|
||||
var dummyPath = Path.Combine(Path.GetDirectoryName(reader.FullPath), file.fileName);
|
||||
var subReader = new FileReader(dummyPath, file.stream);
|
||||
if (subReader.FileType == FileType.AssetsFile)
|
||||
{
|
||||
if (!LoadAssetsFromMemory(subReader, originalPath ?? reader.FullPath, bundleFile.m_Header.unityRevision))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
resourceFileReaders.TryAdd(file.fileName, subReader);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void LoadWebFile(FileReader reader)
|
||||
{
|
||||
Logger.Info("Loading " + reader.FullPath);
|
||||
Logger.Info($"Loading \"{reader.FullPath}\"");
|
||||
try
|
||||
{
|
||||
var webFile = new WebFile(reader);
|
||||
@ -367,7 +394,7 @@ namespace AssetStudio
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Error($"Error while reading web file {reader.FullPath}", e);
|
||||
Logger.Error($"Error while reading web file \"{reader.FullPath}\"", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@ -427,7 +454,7 @@ namespace AssetStudio
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning($"Error while reading zip split file {basePath}\r\n{e}");
|
||||
Logger.Warning($"Error while reading zip split file \"{basePath}\"\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
@ -461,7 +488,7 @@ namespace AssetStudio
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.Warning($"Error while reading zip entry {entry.FullName}\r\n{e}");
|
||||
Logger.Warning($"Error while reading zip entry \"{entry.FullName}\"\n{e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ namespace AssetStudio
|
||||
|
||||
public class BundleFile
|
||||
{
|
||||
public readonly bool IsMultiBundle;
|
||||
|
||||
public class Header
|
||||
{
|
||||
public string signature;
|
||||
@ -102,6 +104,17 @@ namespace AssetStudio
|
||||
case "UnityFS":
|
||||
ReadHeader(reader);
|
||||
|
||||
var bundleSize = m_Header.size;
|
||||
var streamSize = reader.BaseStream.Length;
|
||||
if (bundleSize > streamSize)
|
||||
{
|
||||
Logger.Warning("Bundle size is incorrect.");
|
||||
}
|
||||
else if (streamSize - bundleSize > 200)
|
||||
{
|
||||
IsMultiBundle = true;
|
||||
}
|
||||
|
||||
var isUnityCnEnc = false;
|
||||
var unityVer = m_Header.unityRevision;
|
||||
if (specUnityVer != null)
|
||||
|
@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using static AssetStudio.EndianSpanReader;
|
||||
|
||||
namespace AssetStudio
|
||||
{
|
||||
@ -59,24 +57,25 @@ namespace AssetStudio
|
||||
}
|
||||
isBigEndian = BitConverter.ToBoolean(modelData, 5);
|
||||
|
||||
var modelDataSpan = modelData.AsSpan();
|
||||
//offsets
|
||||
var countInfoTableOffset = (int)SpanToUint32(modelData, 64, isBigEndian);
|
||||
var canvasInfoOffset = (int)SpanToUint32(modelData, 68, isBigEndian);
|
||||
var partIdsOffset = SpanToUint32(modelData, 76, isBigEndian);
|
||||
var parameterIdsOffset = SpanToUint32(modelData, 264, isBigEndian);
|
||||
var countInfoTableOffset = (int)modelDataSpan.ReadUInt32(64, isBigEndian);
|
||||
var canvasInfoOffset = (int)modelDataSpan.ReadUInt32(68, isBigEndian);
|
||||
var partIdsOffset = modelDataSpan.ReadUInt32(76, isBigEndian);
|
||||
var parameterIdsOffset = modelDataSpan.ReadUInt32(264, isBigEndian);
|
||||
|
||||
//canvas
|
||||
PixelPerUnit = ToSingle(modelData, canvasInfoOffset, isBigEndian);
|
||||
CentralPosX = ToSingle(modelData, canvasInfoOffset + 4, isBigEndian);
|
||||
CentralPosY = ToSingle(modelData, canvasInfoOffset + 8, isBigEndian);
|
||||
CanvasWidth = ToSingle(modelData, canvasInfoOffset + 12, isBigEndian);
|
||||
CanvasHeight = ToSingle(modelData, canvasInfoOffset + 16, isBigEndian);
|
||||
PixelPerUnit = modelDataSpan.ReadSingle(canvasInfoOffset, isBigEndian);
|
||||
CentralPosX = modelDataSpan.ReadSingle(canvasInfoOffset + 4, isBigEndian);
|
||||
CentralPosY = modelDataSpan.ReadSingle(canvasInfoOffset + 8, isBigEndian);
|
||||
CanvasWidth = modelDataSpan.ReadSingle(canvasInfoOffset + 12, isBigEndian);
|
||||
CanvasHeight = modelDataSpan.ReadSingle(canvasInfoOffset + 16, isBigEndian);
|
||||
|
||||
//model
|
||||
PartCount = SpanToUint32(modelData, countInfoTableOffset, isBigEndian);
|
||||
ParamCount = SpanToUint32(modelData, countInfoTableOffset + 20, isBigEndian);
|
||||
PartNames = ReadMocStringHashSet(modelData, (int)partIdsOffset, (int)PartCount);
|
||||
ParamNames = ReadMocStringHashSet(modelData, (int)parameterIdsOffset, (int)ParamCount);
|
||||
PartCount = modelDataSpan.ReadUInt32(countInfoTableOffset, isBigEndian);
|
||||
ParamCount = modelDataSpan.ReadUInt32(countInfoTableOffset + 20, isBigEndian);
|
||||
PartNames = ReadMocStrings(modelData, (int)partIdsOffset, (int)PartCount);
|
||||
ParamNames = ReadMocStrings(modelData, (int)parameterIdsOffset, (int)ParamCount);
|
||||
}
|
||||
|
||||
public void SaveMoc3(string savePath)
|
||||
@ -103,16 +102,7 @@ namespace AssetStudio
|
||||
}
|
||||
}
|
||||
|
||||
private static float ToSingle(ReadOnlySpan<byte> data, int index, bool isBigEndian) //net framework ver
|
||||
{
|
||||
var bytes = data.Slice(index, index + 4).ToArray();
|
||||
if ((isBigEndian && BitConverter.IsLittleEndian) || (!isBigEndian && !BitConverter.IsLittleEndian))
|
||||
(bytes[0], bytes[1], bytes[2], bytes[3]) = (bytes[3], bytes[2], bytes[1], bytes[0]);
|
||||
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
|
||||
private static HashSet<string> ReadMocStringHashSet(ReadOnlySpan<byte> data, int index, int count)
|
||||
private static HashSet<string> ReadMocStrings(Span<byte> data, int index, int count)
|
||||
{
|
||||
const int strLen = 64;
|
||||
var strHashSet = new HashSet<string>();
|
||||
@ -120,8 +110,8 @@ namespace AssetStudio
|
||||
{
|
||||
if (index + i * strLen <= data.Length)
|
||||
{
|
||||
var buff = data.Slice(index + i * strLen, strLen);
|
||||
strHashSet.Add(Encoding.UTF8.GetString(buff.ToArray()).TrimEnd('\0'));
|
||||
var str = data.Slice(index + i * strLen, strLen).ReadStringToNull();
|
||||
strHashSet.Add(str);
|
||||
}
|
||||
}
|
||||
return strHashSet;
|
||||
|
@ -82,6 +82,7 @@ namespace AssetStudio
|
||||
return base.ReadUInt64();
|
||||
}
|
||||
|
||||
#if NETFRAMEWORK
|
||||
public override float ReadSingle()
|
||||
{
|
||||
if (Endian == EndianType.BigEndian)
|
||||
@ -103,5 +104,26 @@ namespace AssetStudio
|
||||
}
|
||||
return base.ReadDouble();
|
||||
}
|
||||
#else
|
||||
public override float ReadSingle()
|
||||
{
|
||||
if (Endian == EndianType.BigEndian)
|
||||
{
|
||||
Read(buffer, 0, 4);
|
||||
return BinaryPrimitives.ReadSingleBigEndian(buffer);
|
||||
}
|
||||
return base.ReadSingle();
|
||||
}
|
||||
|
||||
public override double ReadDouble()
|
||||
{
|
||||
if (Endian == EndianType.BigEndian)
|
||||
{
|
||||
Read(buffer, 0, 8);
|
||||
return BinaryPrimitives.ReadDoubleBigEndian(buffer);
|
||||
}
|
||||
return base.ReadDouble();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,90 @@
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using System.Text;
|
||||
|
||||
namespace AssetStudio
|
||||
{
|
||||
public static class EndianSpanReader
|
||||
{
|
||||
public static uint SpanToUint32(Span<byte> data, int start, bool isBigEndian)
|
||||
public static uint ReadUInt32(this Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
return SpanToUInt32(data, start, isBigEndian);
|
||||
}
|
||||
|
||||
public static uint SpanToUInt32(Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
return isBigEndian
|
||||
? BinaryPrimitives.ReadUInt32BigEndian(data.Slice(start))
|
||||
: BinaryPrimitives.ReadUInt32LittleEndian(data.Slice(start));
|
||||
}
|
||||
|
||||
public static uint SpanToUint16(Span<byte> data, int start, bool isBigEndian)
|
||||
public static long ReadUInt16(this Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
return SpanToUInt16(data, start, isBigEndian);
|
||||
}
|
||||
|
||||
public static uint SpanToUInt16(Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
return isBigEndian
|
||||
? BinaryPrimitives.ReadUInt16BigEndian(data.Slice(start))
|
||||
: BinaryPrimitives.ReadUInt16LittleEndian(data.Slice(start));
|
||||
}
|
||||
|
||||
public static long ReadInt64(this Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
return SpanToInt64(data, start, isBigEndian);
|
||||
}
|
||||
|
||||
public static long SpanToInt64(Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
return isBigEndian
|
||||
? BinaryPrimitives.ReadInt64BigEndian(data.Slice(start))
|
||||
: BinaryPrimitives.ReadInt64LittleEndian(data.Slice(start));
|
||||
}
|
||||
|
||||
public static float ReadSingle(this Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
return SpanToSingle(data, start, isBigEndian);
|
||||
}
|
||||
|
||||
#if NETFRAMEWORK
|
||||
public static float SpanToSingle(Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
var bytes = data.Slice(start, 4);
|
||||
if ((isBigEndian && BitConverter.IsLittleEndian) || (!isBigEndian && !BitConverter.IsLittleEndian))
|
||||
bytes.Reverse();
|
||||
|
||||
return BitConverter.ToSingle(bytes.ToArray(), 0);
|
||||
}
|
||||
#else
|
||||
public static float SpanToSingle(Span<byte> data, int start, bool isBigEndian)
|
||||
{
|
||||
return isBigEndian
|
||||
? BinaryPrimitives.ReadSingleBigEndian(data[start..])
|
||||
: BinaryPrimitives.ReadSingleLittleEndian(data[start..]);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static string ReadStringToNull(this Span<byte> data, int maxLength = 32767)
|
||||
{
|
||||
Span<byte> bytes = stackalloc byte[maxLength];
|
||||
var count = 0;
|
||||
while (count != data.Length && count < maxLength)
|
||||
{
|
||||
var b = data[count];
|
||||
if (b == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
bytes[count] = b;
|
||||
count++;
|
||||
}
|
||||
bytes = bytes.Slice(0, count);
|
||||
#if NETFRAMEWORK
|
||||
return Encoding.UTF8.GetString(bytes.ToArray());
|
||||
#else
|
||||
return Encoding.UTF8.GetString(bytes);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,11 @@ namespace AssetStudio
|
||||
count++;
|
||||
}
|
||||
bytes = bytes.Slice(0, count);
|
||||
#if NETFRAMEWORK
|
||||
return encoding?.GetString(bytes.ToArray()) ?? Encoding.UTF8.GetString(bytes.ToArray());
|
||||
#else
|
||||
return encoding?.GetString(bytes) ?? Encoding.UTF8.GetString(bytes);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static string ReadUnicodeStringToNull(this BinaryReader reader, int maxLength)
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using static AssetStudio.EndianSpanReader;
|
||||
|
||||
namespace AssetStudio
|
||||
{
|
||||
@ -14,6 +13,9 @@ namespace AssetStudio
|
||||
private static readonly byte[] brotliMagic = { 0x62, 0x72, 0x6F, 0x74, 0x6C, 0x69 };
|
||||
private static readonly byte[] zipMagic = { 0x50, 0x4B, 0x03, 0x04 };
|
||||
private static readonly byte[] zipSpannedMagic = { 0x50, 0x4B, 0x07, 0x08 };
|
||||
private static readonly byte[] unityFsMagic = {0x55, 0x6E, 0x69, 0x74, 0x79, 0x46, 0x53};
|
||||
private static readonly int headerBuffLen = 1152;
|
||||
private static byte[] headerBuff = new byte[headerBuffLen];
|
||||
|
||||
public FileReader(string path) : this(path, File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { }
|
||||
|
||||
@ -26,14 +28,19 @@ namespace AssetStudio
|
||||
|
||||
private FileType CheckFileType()
|
||||
{
|
||||
var signature = this.ReadStringToNull(20);
|
||||
var buff = headerBuff.AsSpan();
|
||||
buff.Clear();
|
||||
var dataLen = Read(headerBuff, 0, headerBuffLen);
|
||||
Position = 0;
|
||||
|
||||
var signature = buff.ReadStringToNull(20);
|
||||
switch (signature)
|
||||
{
|
||||
case "UnityWeb":
|
||||
case "UnityRaw":
|
||||
case "UnityArchive":
|
||||
case "UnityFS":
|
||||
CheckBundleDataOffset(buff);
|
||||
return FileType.BundleFile;
|
||||
case "UnityWebData1.0":
|
||||
return FileType.WebFile;
|
||||
@ -41,17 +48,15 @@ namespace AssetStudio
|
||||
return FileType.WebFile;
|
||||
default:
|
||||
{
|
||||
var buff = ReadBytes(40).AsSpan();
|
||||
var magic = Span<byte>.Empty;
|
||||
Position = 0;
|
||||
|
||||
magic = buff.Length > 2 ? buff.Slice(0, 2) : magic;
|
||||
magic = dataLen > 2 ? buff.Slice(0, 2) : magic;
|
||||
if (magic.SequenceEqual(gzipMagic))
|
||||
{
|
||||
return FileType.GZipFile;
|
||||
}
|
||||
|
||||
magic = buff.Length > 38 ? buff.Slice(32, 6) : magic;
|
||||
magic = dataLen > 38 ? buff.Slice(32, 6) : magic;
|
||||
if (magic.SequenceEqual(brotliMagic))
|
||||
{
|
||||
return FileType.BrotliFile;
|
||||
@ -62,12 +67,17 @@ namespace AssetStudio
|
||||
return FileType.AssetsFile;
|
||||
}
|
||||
|
||||
magic = buff.Length > 4 ? buff.Slice(0, 4): magic;
|
||||
magic = dataLen > 4 ? buff.Slice(0, 4): magic;
|
||||
if (magic.SequenceEqual(zipMagic) || magic.SequenceEqual(zipSpannedMagic))
|
||||
{
|
||||
return FileType.ZipFile;
|
||||
}
|
||||
|
||||
if (CheckBundleDataOffset(buff))
|
||||
{
|
||||
return FileType.BundleFile;
|
||||
}
|
||||
|
||||
return FileType.ResourceFile;
|
||||
}
|
||||
}
|
||||
@ -82,10 +92,10 @@ namespace AssetStudio
|
||||
}
|
||||
var isBigEndian = Endian == EndianType.BigEndian;
|
||||
|
||||
//var m_MetadataSize = SpanToUint32(buff, 0, isBigEndian);
|
||||
long m_FileSize = SpanToUint32(buff, 4, isBigEndian);
|
||||
var m_Version = SpanToUint32(buff, 8, isBigEndian);
|
||||
long m_DataOffset = SpanToUint32(buff, 12, isBigEndian);
|
||||
//var m_MetadataSize = buff.ReadUInt32(0, isBigEndian);
|
||||
long m_FileSize = buff.ReadUInt32(4, isBigEndian);
|
||||
var m_Version = buff.ReadUInt32(8, isBigEndian);
|
||||
long m_DataOffset = buff.ReadUInt32(12, isBigEndian);
|
||||
//var m_Endianess = buff[16];
|
||||
//var m_Reserved = buff.Slice(17, 3);
|
||||
if (m_Version >= 22)
|
||||
@ -94,15 +104,44 @@ namespace AssetStudio
|
||||
{
|
||||
return false;
|
||||
}
|
||||
//m_MetadataSize = SpanToUint32(buff, 20, isBigEndian);
|
||||
m_FileSize = SpanToInt64(buff, 24, isBigEndian);
|
||||
m_DataOffset = SpanToInt64(buff, 32, isBigEndian);
|
||||
//m_MetadataSize = buff.ReadUInt32(20, isBigEndian);
|
||||
m_FileSize = buff.ReadInt64(24, isBigEndian);
|
||||
m_DataOffset = buff.ReadInt64(32, isBigEndian);
|
||||
}
|
||||
if (m_FileSize != fileSize || m_DataOffset > fileSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool CheckBundleDataOffset(ReadOnlySpan<byte> buff)
|
||||
{
|
||||
var lastOffset = buff.LastIndexOf(unityFsMagic);
|
||||
if (lastOffset <= 0)
|
||||
return false;
|
||||
|
||||
var firstOffset = buff.IndexOf(unityFsMagic);
|
||||
if (firstOffset == lastOffset || lastOffset - firstOffset < 200)
|
||||
{
|
||||
Position = lastOffset;
|
||||
return true;
|
||||
}
|
||||
|
||||
Position = firstOffset;
|
||||
_ = this.ReadStringToNull();
|
||||
_ = this.ReadUInt32();
|
||||
_ = this.ReadStringToNull();
|
||||
_ = this.ReadStringToNull();
|
||||
var bundleSize = this.ReadInt64();
|
||||
if (bundleSize > 200 && firstOffset + bundleSize < lastOffset)
|
||||
{
|
||||
Position = firstOffset;
|
||||
return true;
|
||||
}
|
||||
|
||||
Position = lastOffset;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
92
AssetStudio/OffsetStream.cs
Normal file
92
AssetStudio/OffsetStream.cs
Normal file
@ -0,0 +1,92 @@
|
||||
using System.IO;
|
||||
|
||||
namespace AssetStudio
|
||||
{
|
||||
public class OffsetStream : Stream
|
||||
{
|
||||
private readonly Stream _baseStream;
|
||||
private long _offset;
|
||||
|
||||
public override bool CanRead => _baseStream.CanRead;
|
||||
public override bool CanSeek => _baseStream.CanSeek;
|
||||
public override bool CanWrite => false;
|
||||
public override long Length => _baseStream.Length - _offset;
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => _baseStream.Position - _offset;
|
||||
set => Seek(value, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
public long BasePosition => _baseStream.Position;
|
||||
|
||||
public long Offset
|
||||
{
|
||||
get => _offset;
|
||||
set
|
||||
{
|
||||
if (value < 0 || value > _baseStream.Length)
|
||||
{
|
||||
throw new IOException($"{nameof(Offset)} is out of stream bound");
|
||||
}
|
||||
_offset = value;
|
||||
Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
public OffsetStream(FileReader reader)
|
||||
{
|
||||
_baseStream = reader.BaseStream;
|
||||
Offset = reader.Position;
|
||||
}
|
||||
|
||||
public override void Flush() { }
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return _baseStream.Read(buffer, offset, count);
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
if (offset > _baseStream.Length)
|
||||
{
|
||||
throw new IOException("Unable to seek beyond stream bound");
|
||||
}
|
||||
|
||||
switch (origin)
|
||||
{
|
||||
case SeekOrigin.Begin:
|
||||
_baseStream.Seek(offset + _offset, SeekOrigin.Begin);
|
||||
break;
|
||||
case SeekOrigin.Current:
|
||||
_baseStream.Seek(offset + Position, SeekOrigin.Begin);
|
||||
break;
|
||||
case SeekOrigin.End:
|
||||
_baseStream.Seek(offset + _baseStream.Length, SeekOrigin.Begin);
|
||||
break;
|
||||
}
|
||||
return Position;
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
public override void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_baseStream.Dispose();
|
||||
}
|
||||
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user