Add project files.
This commit is contained in:
164
AssetStudio/Game/GI/Mhy0File.cs
Normal file
164
AssetStudio/Game/GI/Mhy0File.cs
Normal file
@@ -0,0 +1,164 @@
|
||||
using K4os.Compression.LZ4;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace AssetStudio
|
||||
{
|
||||
public partial class Mhy0File
|
||||
{
|
||||
public class Header
|
||||
{
|
||||
public int Size;
|
||||
public int BundleCount;
|
||||
public int BlockCount;
|
||||
}
|
||||
|
||||
public class StorageBlock
|
||||
{
|
||||
public int CompressedSize;
|
||||
public int UncompressedSize;
|
||||
}
|
||||
|
||||
public class Node
|
||||
{
|
||||
public long Offset;
|
||||
public long Size;
|
||||
public string Path;
|
||||
public bool IsAssetFile;
|
||||
}
|
||||
|
||||
private Header m_Header;
|
||||
private StorageBlock[] m_BlocksInfo;
|
||||
private Node[] m_DirectoryInfo;
|
||||
|
||||
public StreamFile[] FileList;
|
||||
|
||||
private byte[] DecompressHeader(byte[] header)
|
||||
{
|
||||
using (var ms = new MemoryStream(header))
|
||||
using (var reader = new EndianBinaryReader(ms))
|
||||
{
|
||||
reader.Position += 0x20;
|
||||
var decompressedSize = reader.ReadMhy0Int1();
|
||||
var decompressed = new byte[decompressedSize];
|
||||
|
||||
var compressed = reader.ReadBytes((int)(reader.BaseStream.Length - reader.Position));
|
||||
|
||||
var numWrite = LZ4Codec.Decode(compressed, decompressed);
|
||||
if (numWrite != decompressedSize)
|
||||
throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {decompressedSize} bytes");
|
||||
|
||||
return decompressed;
|
||||
}
|
||||
}
|
||||
|
||||
private void ReadBlocksInfoAndDirectory(byte[] header)
|
||||
{
|
||||
using (var ms = new MemoryStream(header))
|
||||
using (var reader = new EndianBinaryReader(ms))
|
||||
{
|
||||
m_Header.BundleCount = reader.ReadMhy0Int2();
|
||||
m_DirectoryInfo = new Node[m_Header.BundleCount];
|
||||
for (int i = 0; i < m_Header.BundleCount; i++)
|
||||
{
|
||||
m_DirectoryInfo[i] = new Node
|
||||
{
|
||||
Path = reader.ReadMhy0String(),
|
||||
IsAssetFile = reader.ReadMhy0Bool(),
|
||||
Offset = reader.ReadMhy0Int2(),
|
||||
Size = reader.ReadMhy0Int1(),
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
m_Header.BlockCount = reader.ReadMhy0Int2();
|
||||
m_BlocksInfo = new StorageBlock[m_Header.BlockCount];
|
||||
for (int i = 0; i < m_Header.BlockCount; i++)
|
||||
{
|
||||
m_BlocksInfo[i] = new StorageBlock
|
||||
{
|
||||
CompressedSize = reader.ReadMhy0Int2(),
|
||||
UncompressedSize = reader.ReadMhy0Int1()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Stream CreateBlocksStream(string path)
|
||||
{
|
||||
Stream blocksStream;
|
||||
var uncompressedSizeSum = m_BlocksInfo.Sum(x => x.UncompressedSize);
|
||||
if (uncompressedSizeSum >= int.MaxValue)
|
||||
blocksStream = new FileStream(path + ".temp", FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, FileOptions.DeleteOnClose);
|
||||
else
|
||||
blocksStream = new MemoryStream(uncompressedSizeSum);
|
||||
return blocksStream;
|
||||
}
|
||||
|
||||
private void ReadBlocks(EndianBinaryReader reader, Stream blocksStream)
|
||||
{
|
||||
var compressedBytes = BigArrayPool<byte>.Shared.Rent(m_BlocksInfo.Max(x => x.CompressedSize));
|
||||
var uncompressedBytes = BigArrayPool<byte>.Shared.Rent(m_BlocksInfo.Max(x => x.UncompressedSize));
|
||||
foreach (var blockInfo in m_BlocksInfo)
|
||||
{
|
||||
var compressedSize = blockInfo.CompressedSize;
|
||||
reader.Read(compressedBytes, 0, compressedSize);
|
||||
if (compressedSize < 0x10)
|
||||
throw new Exception($"Wrong compressed length: {compressedSize}");
|
||||
compressedBytes = Crypto.DescrambleEntry(compressedBytes);
|
||||
var uncompressedSize = blockInfo.UncompressedSize;
|
||||
var numWrite = LZ4Codec.Decode(compressedBytes.AsSpan(0xC, compressedSize - 0xC), uncompressedBytes.AsSpan(0, uncompressedSize));
|
||||
if (numWrite != uncompressedSize)
|
||||
throw new IOException($"Lz4 decompression error, write {numWrite} bytes but expected {uncompressedSize} bytes");
|
||||
blocksStream.Write(uncompressedBytes, 0, uncompressedSize);
|
||||
}
|
||||
BigArrayPool<byte>.Shared.Return(compressedBytes);
|
||||
BigArrayPool<byte>.Shared.Return(uncompressedBytes);
|
||||
}
|
||||
|
||||
private void ReadFiles(Stream blocksStream, string path)
|
||||
{
|
||||
FileList = new StreamFile[m_DirectoryInfo.Length];
|
||||
for (int i = 0; i < m_DirectoryInfo.Length; i++)
|
||||
{
|
||||
var node = m_DirectoryInfo[i];
|
||||
var file = new StreamFile();
|
||||
FileList[i] = file;
|
||||
file.path = node.Path;
|
||||
file.fileName = Path.GetFileName(node.Path);
|
||||
if (node.Size >= int.MaxValue)
|
||||
{
|
||||
var extractPath = path + "_unpacked" + Path.DirectorySeparatorChar;
|
||||
Directory.CreateDirectory(extractPath);
|
||||
file.stream = new FileStream(extractPath + file.fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
|
||||
}
|
||||
else
|
||||
file.stream = new MemoryStream((int)node.Size);
|
||||
blocksStream.Position = node.Offset;
|
||||
blocksStream.CopyTo(file.stream, node.Size);
|
||||
file.stream.Position = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Mhy0File(EndianBinaryReader reader, string path)
|
||||
{
|
||||
var magic = reader.ReadStringToNull(4);
|
||||
if (magic != "mhy0")
|
||||
throw new Exception("not a mhy0");
|
||||
|
||||
m_Header = new Header();
|
||||
m_Header.Size = reader.ReadInt32();
|
||||
var header = reader.ReadBytes(m_Header.Size);
|
||||
|
||||
header = Crypto.DescrambleHeader(header);
|
||||
header = DecompressHeader(header);
|
||||
ReadBlocksInfoAndDirectory(header);
|
||||
using (var blocksStream = CreateBlocksStream(path))
|
||||
{
|
||||
ReadBlocks(reader, blocksStream);
|
||||
ReadFiles(blocksStream, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user