mirror of
https://github.com/4sval/FModel.git
synced 2026-09-25 18:48:50 -05:00
io store reading
This commit is contained in:
@@ -7,6 +7,7 @@ namespace PakReader
|
||||
{
|
||||
static class AESDecryptor
|
||||
{
|
||||
public const int ALIGN = 16;
|
||||
public const int BLOCK_SIZE = 16 * 8; // 128
|
||||
static readonly Rijndael Cipher;
|
||||
static readonly Dictionary<byte[], ICryptoTransform> CachedTransforms = new Dictionary<byte[], ICryptoTransform>();
|
||||
|
||||
20
FModel/PakReader/Pak/IO/EIoChunkType.cs
Normal file
20
FModel/PakReader/Pak/IO/EIoChunkType.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
/// <summary>
|
||||
/// Addressable chunk types.
|
||||
/// </summary>
|
||||
public enum EIoChunkType : byte
|
||||
{
|
||||
Invalid,
|
||||
InstallManifest,
|
||||
ExportBundleData,
|
||||
BulkData,
|
||||
OptionalBulkData,
|
||||
MemoryMappedBulkData,
|
||||
LoaderGlobalMeta,
|
||||
LoaderInitialLoadMeta,
|
||||
LoaderGlobalNames,
|
||||
LoaderGlobalNameHashes,
|
||||
ContainerHeader
|
||||
};
|
||||
}
|
||||
11
FModel/PakReader/Pak/IO/EIoContainerFlags.cs
Normal file
11
FModel/PakReader/Pak/IO/EIoContainerFlags.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public enum EIoContainerFlags : byte
|
||||
{
|
||||
None,
|
||||
Compressed = (1 << 0),
|
||||
Encrypted = (1 << 1),
|
||||
Signed = (1 << 2),
|
||||
Indexed = (1 << 3),
|
||||
};
|
||||
}
|
||||
20
FModel/PakReader/Pak/IO/FFileIoStoreContainerFile.cs
Normal file
20
FModel/PakReader/Pak/IO/FFileIoStoreContainerFile.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.IO;
|
||||
using PakReader.Parsers.Objects;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public struct FFileIoStoreContainerFile
|
||||
{
|
||||
public Stream FileHandle;
|
||||
public string FileName;
|
||||
public long CompressionBlockSize;
|
||||
public string[] CompressionMethods;
|
||||
public FIoStoreTocCompressedBlockEntry[] CompressionBlocks;
|
||||
public FGuid EncryptionKeyGuid;
|
||||
public byte[] EncryptionKey;
|
||||
public EIoContainerFlags ContainerFlags;
|
||||
public FSHAHash[] BlockSignatureHashes;
|
||||
|
||||
public long FileSize => FileHandle.Length;
|
||||
}
|
||||
}
|
||||
253
FModel/PakReader/Pak/IO/FFileIoStoreReader.cs
Normal file
253
FModel/PakReader/Pak/IO/FFileIoStoreReader.cs
Normal file
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
using FModel.Utils;
|
||||
using Ionic.Zlib;
|
||||
using PakReader.Parsers;
|
||||
using PakReader.Parsers.Objects;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public class FFileIoStoreReader
|
||||
{
|
||||
public readonly FIoStoreTocResource TocResource;
|
||||
public readonly Dictionary<FIoChunkId, FIoOffsetAndLength> Toc;
|
||||
public readonly FFileIoStoreContainerFile ContainerFile;
|
||||
public readonly FIoContainerId ContainerId;
|
||||
|
||||
private byte[] _aesKey;
|
||||
public byte[] AesKey
|
||||
{
|
||||
get => _aesKey;
|
||||
set
|
||||
{
|
||||
if (value != null && !TestAesKey(value)) //if value not null, test but fail, throw not working
|
||||
throw new ArgumentException(string.Format(FModel.Properties.Resources.AesNotWorking, value.ToStringKey(), ContainerFile.FileName));
|
||||
_aesKey = value; // else, even if value is null, set it
|
||||
// setting _aesKey to null will disable the corresponding menu item
|
||||
}
|
||||
}
|
||||
|
||||
public FGuid EncryptionKeyGuid => ContainerFile.EncryptionKeyGuid;
|
||||
public bool IsEncrypted => ContainerFile.ContainerFlags.HasAnyFlags(EIoContainerFlags.Encrypted);
|
||||
|
||||
|
||||
public FIoDirectoryIndexResource _directoryIndex;
|
||||
private byte[] _directoryIndexBuffer;
|
||||
|
||||
public FFileIoStoreReader(Stream tocStream, Stream containerStream, EIoStoreTocReadOptions tocReadOptions = EIoStoreTocReadOptions.ReadDirectoryIndex)
|
||||
{
|
||||
ContainerFile.FileHandle = containerStream;
|
||||
var tocResource = new FIoStoreTocResource(tocStream, tocReadOptions);
|
||||
TocResource = tocResource;
|
||||
|
||||
var containerUncompressedSize = tocResource.Header.TocCompressedBlockEntryCount > 0
|
||||
? tocResource.Header.TocCompressedBlockEntryCount * tocResource.Header.CompressionBlockSize
|
||||
: containerStream.Length;
|
||||
|
||||
Toc = new Dictionary<FIoChunkId, FIoOffsetAndLength>((int) tocResource.Header.TocEntryCount);
|
||||
|
||||
for (var chunkIndex = 0; chunkIndex < tocResource.Header.TocEntryCount; chunkIndex++)
|
||||
{
|
||||
ref var chunkOffsetLength = ref tocResource.ChunkOffsetLengths[chunkIndex];
|
||||
if (chunkOffsetLength.Offset + chunkOffsetLength.Length > containerUncompressedSize)
|
||||
{
|
||||
throw new FileLoadException("TocEntry out of container bounds");
|
||||
}
|
||||
Toc[tocResource.ChunkIds[chunkIndex]] = chunkOffsetLength;
|
||||
}
|
||||
|
||||
for (var compressedBlockIndex = 0; compressedBlockIndex < tocResource.CompressionBlocks.Length; compressedBlockIndex++)
|
||||
{
|
||||
ref var compressedBlockEntry = ref tocResource.CompressionBlocks[compressedBlockIndex];
|
||||
if (compressedBlockEntry.Offset + compressedBlockEntry.CompressedSize > ContainerFile.FileSize)
|
||||
{
|
||||
throw new FileLoadException("TocCompressedBlockEntry out of container bounds");
|
||||
}
|
||||
}
|
||||
|
||||
ContainerFile.CompressionMethods = tocResource.CompressionMethods;
|
||||
ContainerFile.CompressionBlockSize = tocResource.Header.CompressionBlockSize;
|
||||
ContainerFile.CompressionBlocks = tocResource.CompressionBlocks;
|
||||
ContainerFile.ContainerFlags = tocResource.Header.ContainerFlags;
|
||||
ContainerFile.EncryptionKeyGuid = tocResource.Header.EncryptionKeyGuid;
|
||||
ContainerFile.BlockSignatureHashes = tocResource.ChunkBlockSignatures;
|
||||
|
||||
ContainerId = tocResource.Header.ContainerId;
|
||||
|
||||
_directoryIndexBuffer = tocResource.DirectoryIndexBuffer;
|
||||
}
|
||||
|
||||
public void ReadIndex()
|
||||
{
|
||||
using Stream indexStream = IsEncrypted
|
||||
? new MemoryStream(AESDecryptor.DecryptAES(_directoryIndexBuffer, _aesKey))
|
||||
: new MemoryStream(_directoryIndexBuffer);
|
||||
_directoryIndex = new FIoDirectoryIndexResource(indexStream);
|
||||
}
|
||||
|
||||
public string MountPoint => _directoryIndex.MountPoint;
|
||||
|
||||
public bool TestAesKey(byte[] key)
|
||||
{
|
||||
if (!IsEncrypted)
|
||||
return true;
|
||||
return TestAesKey(_directoryIndexBuffer, key);
|
||||
}
|
||||
|
||||
public static bool TestAesKey(byte[] bytes, byte[] key)
|
||||
{
|
||||
using BinaryReader indexReader = new BinaryReader(new MemoryStream(AESDecryptor.DecryptAES(bytes, key)));
|
||||
var stringLen = indexReader.ReadInt32();
|
||||
if (stringLen > 128 || stringLen < -128)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (stringLen == 0)
|
||||
{
|
||||
return indexReader.ReadUInt16() == 0;
|
||||
}
|
||||
if (stringLen < 0)
|
||||
{
|
||||
var nullTerminatedPos = 4 - (stringLen - 1) * 2;
|
||||
indexReader.BaseStream.Seek(nullTerminatedPos, SeekOrigin.Begin);
|
||||
return indexReader.ReadInt16() == 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
var nullTerminatedPos = 4 + stringLen - 1;
|
||||
indexReader.BaseStream.Seek(nullTerminatedPos, SeekOrigin.Begin);
|
||||
return indexReader.ReadSByte() == 0;
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] Read(FIoChunkId chunkId)
|
||||
{
|
||||
var offsetAndLength = Toc[chunkId];
|
||||
var tocResource = TocResource;
|
||||
var compressionBlockSize = tocResource.Header.CompressionBlockSize;
|
||||
var dst = new byte[offsetAndLength.Length];
|
||||
var firstBlockIndex = (int) (offsetAndLength.Offset / compressionBlockSize);
|
||||
var lastBlockIndex = (int) ((BinaryHelper.Align(offsetAndLength.Offset + dst.Length, compressionBlockSize) - 1) / compressionBlockSize);
|
||||
var offsetInBlock = offsetAndLength.Offset % compressionBlockSize;
|
||||
|
||||
byte[] src;
|
||||
var remainingSize = dst.Length;
|
||||
var dstOffset = 0;
|
||||
for (int blockIndex = firstBlockIndex; blockIndex < lastBlockIndex; blockIndex++)
|
||||
{
|
||||
var compressionBlock = tocResource.CompressionBlocks[blockIndex];
|
||||
|
||||
var rawSize = BinaryHelper.Align(compressionBlock.CompressedSize, AESDecryptor.ALIGN);
|
||||
var compressedBuffer = new byte[rawSize];
|
||||
|
||||
var uncompressedSize = compressionBlock.UncompressedSize;
|
||||
var uncompressedBuffer = new byte[uncompressedSize];
|
||||
|
||||
var containerStream = ContainerFile.FileHandle;
|
||||
containerStream.Position = compressionBlock.Offset;
|
||||
containerStream.Read(compressedBuffer, 0, (int) rawSize);
|
||||
if (TocResource.Header.ContainerFlags.HasAnyFlags(EIoContainerFlags.Encrypted))
|
||||
{
|
||||
compressedBuffer = AESDecryptor.DecryptAES(compressedBuffer, _aesKey);
|
||||
}
|
||||
|
||||
if (compressionBlock.CompressionMethodIndex == 0)
|
||||
{
|
||||
src = compressedBuffer;
|
||||
}
|
||||
else
|
||||
{
|
||||
var compressionMethod = tocResource.CompressionMethods[compressionBlock.CompressionMethodIndex - 1];
|
||||
Decompress(compressedBuffer, uncompressedBuffer, compressionMethod);
|
||||
src = uncompressedBuffer;
|
||||
}
|
||||
|
||||
var sizeInBlock = (int) Math.Min(compressionBlockSize - offsetInBlock, remainingSize);
|
||||
Buffer.BlockCopy(src, (int) offsetInBlock, dst, dstOffset, sizeInBlock);
|
||||
offsetInBlock = 0;
|
||||
remainingSize -= sizeInBlock;
|
||||
dstOffset += sizeInBlock;
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FIoDirectoryIndexHandle GetChildDirectory(FIoDirectoryIndexHandle directory) =>
|
||||
directory.IsValid() && IsValidIndex()
|
||||
? FIoDirectoryIndexHandle.FromIndex(GetDirectoryEntry(directory).FirstChildEntry)
|
||||
: FIoDirectoryIndexHandle.InvalidHandle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FIoDirectoryIndexHandle GetNextDirectory(FIoDirectoryIndexHandle directory) =>
|
||||
directory.IsValid() && IsValidIndex()
|
||||
? FIoDirectoryIndexHandle.FromIndex(GetDirectoryEntry(directory).NextSiblingEntry)
|
||||
: FIoDirectoryIndexHandle.InvalidHandle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FIoDirectoryIndexHandle GetFile(FIoDirectoryIndexHandle directory) =>
|
||||
directory.IsValid() && IsValidIndex()
|
||||
? FIoDirectoryIndexHandle.FromIndex(GetDirectoryEntry(directory).FirstFileEntry)
|
||||
: FIoDirectoryIndexHandle.InvalidHandle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FIoDirectoryIndexHandle GetNextFile(FIoDirectoryIndexHandle file) => file.IsValid() && IsValidIndex()
|
||||
? FIoDirectoryIndexHandle.FromIndex(GetFileEntry(file).NextFileEntry)
|
||||
: FIoDirectoryIndexHandle.InvalidHandle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string GetDirectoryName(FIoDirectoryIndexHandle directory)
|
||||
{
|
||||
if (directory.IsValid() && IsValidIndex())
|
||||
{
|
||||
var nameIndex = GetDirectoryEntry(directory).Name;
|
||||
return _directoryIndex.StringTable[nameIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public string GetFileName(FIoDirectoryIndexHandle file)
|
||||
{
|
||||
if (file.IsValid() && IsValidIndex())
|
||||
{
|
||||
var nameIndex = GetFileEntry(file).Name;
|
||||
return _directoryIndex.StringTable[nameIndex];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint GetFileData(FIoDirectoryIndexHandle file) => file.IsValid() && IsValidIndex()
|
||||
? _directoryIndex.FileEntries[file.ToIndex()].UserData
|
||||
: 0;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ref FIoDirectoryIndexEntry GetDirectoryEntry(FIoDirectoryIndexHandle directory) =>
|
||||
ref _directoryIndex.DirectoryEntries[directory.ToIndex()];
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ref FIoFileIndexEntry GetFileEntry(FIoDirectoryIndexHandle file) =>
|
||||
ref _directoryIndex.FileEntries[file.ToIndex()];
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private bool IsValidIndex() => _directoryIndex.DirectoryEntries.Length > 0;
|
||||
|
||||
|
||||
private void Decompress(byte[] src, byte[] outData, string compressionMethod)
|
||||
{
|
||||
using var blockMs = new MemoryStream(src, false);
|
||||
using Stream compressionStream = compressionMethod switch
|
||||
{
|
||||
"Zlib" => new ZlibStream(blockMs, CompressionMode.Decompress),
|
||||
"Gzip" => new GZipStream(blockMs, CompressionMode.Decompress),
|
||||
"Oodle" => new OodleStream(src, outData.Length),
|
||||
_ => throw new NotImplementedException($"Decompression not yet implemented ({compressionMethod})")
|
||||
};
|
||||
compressionStream.Read(outData, 0, outData.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
14
FModel/PakReader/Pak/IO/FIoChunkHash.cs
Normal file
14
FModel/PakReader/Pak/IO/FIoChunkHash.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public struct FIoChunkHash
|
||||
{
|
||||
public byte[] Hash;
|
||||
|
||||
public FIoChunkHash(BinaryReader reader)
|
||||
{
|
||||
Hash = reader.ReadBytes(32);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
FModel/PakReader/Pak/IO/FIoChunkId.cs
Normal file
47
FModel/PakReader/Pak/IO/FIoChunkId.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public readonly struct FIoChunkId
|
||||
{
|
||||
public readonly byte[] Id;
|
||||
|
||||
public ulong ChunkId => BitConverter.ToUInt64(Id);
|
||||
public ushort ChunkIndex => BitConverter.ToUInt16(Id, 8);
|
||||
public EIoChunkType ChunkType => (EIoChunkType) Id[11];
|
||||
|
||||
public FIoChunkId(BinaryReader reader)
|
||||
{
|
||||
Id = reader.ReadBytes(12);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = 5381;
|
||||
for (int i = 0; i < 12; i++)
|
||||
{
|
||||
hash = hash * 33 + Id[i];
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (!(obj is FIoChunkId cast)) return false;
|
||||
return Id.SequenceEqual(cast.Id);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(FIoChunkId a, FIoChunkId b) => a.Id.SequenceEqual(b.Id);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(FIoChunkId a, FIoChunkId b) => !a.Id.SequenceEqual(b.Id);
|
||||
|
||||
public override string ToString() => BitConverter.ToString(Id).Replace("-","");
|
||||
}
|
||||
}
|
||||
14
FModel/PakReader/Pak/IO/FIoContainerId.cs
Normal file
14
FModel/PakReader/Pak/IO/FIoContainerId.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public struct FIoContainerId
|
||||
{
|
||||
public ulong Id;
|
||||
|
||||
public FIoContainerId(BinaryReader reader)
|
||||
{
|
||||
Id = reader.ReadUInt64();
|
||||
}
|
||||
}
|
||||
}
|
||||
20
FModel/PakReader/Pak/IO/FIoDirectoryIndexEntry.cs
Normal file
20
FModel/PakReader/Pak/IO/FIoDirectoryIndexEntry.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public readonly struct FIoDirectoryIndexEntry
|
||||
{
|
||||
public readonly uint Name;
|
||||
public readonly uint FirstChildEntry;
|
||||
public readonly uint NextSiblingEntry;
|
||||
public readonly uint FirstFileEntry;
|
||||
|
||||
public FIoDirectoryIndexEntry(BinaryReader reader)
|
||||
{
|
||||
Name = reader.ReadUInt32();
|
||||
FirstChildEntry = reader.ReadUInt32();
|
||||
NextSiblingEntry = reader.ReadUInt32();
|
||||
FirstFileEntry = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
33
FModel/PakReader/Pak/IO/FIoDirectoryIndexHandle.cs
Normal file
33
FModel/PakReader/Pak/IO/FIoDirectoryIndexHandle.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public readonly struct FIoDirectoryIndexHandle
|
||||
{
|
||||
public static FIoDirectoryIndexHandle InvalidHandle = new FIoDirectoryIndexHandle(uint.MaxValue);
|
||||
private readonly uint _handle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public uint ToIndex() => _handle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public FIoDirectoryIndexHandle(uint handle)
|
||||
{
|
||||
_handle = handle;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool IsValid() => this != InvalidHandle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => (int) _handle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator ==(FIoDirectoryIndexHandle a, FIoDirectoryIndexHandle b) => a._handle == b._handle;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool operator !=(FIoDirectoryIndexHandle a, FIoDirectoryIndexHandle b) => a._handle != b._handle;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static FIoDirectoryIndexHandle FromIndex(uint index) => new FIoDirectoryIndexHandle(index);
|
||||
}
|
||||
}
|
||||
30
FModel/PakReader/Pak/IO/FIoDirectoryIndexResource.cs
Normal file
30
FModel/PakReader/Pak/IO/FIoDirectoryIndexResource.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public class FIoDirectoryIndexResource
|
||||
{
|
||||
public readonly string MountPoint;
|
||||
public readonly FIoDirectoryIndexEntry[] DirectoryEntries;
|
||||
public readonly FIoFileIndexEntry[] FileEntries;
|
||||
public readonly string[] StringTable;
|
||||
|
||||
public FIoDirectoryIndexResource(Stream directoryIndexStream)
|
||||
{
|
||||
using var reader = new BinaryReader(directoryIndexStream);
|
||||
MountPoint = reader.ReadFString();
|
||||
if (MountPoint.StartsWith("../../.."))
|
||||
{
|
||||
MountPoint = MountPoint[9..];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Weird mount point location...
|
||||
MountPoint = "";
|
||||
}
|
||||
DirectoryEntries = reader.ReadTArray(() => new FIoDirectoryIndexEntry(reader));
|
||||
FileEntries = reader.ReadTArray(() => new FIoFileIndexEntry(reader));
|
||||
StringTable = reader.ReadTArray(reader.ReadFString);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
FModel/PakReader/Pak/IO/FIoFileIndexEntry.cs
Normal file
18
FModel/PakReader/Pak/IO/FIoFileIndexEntry.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public readonly struct FIoFileIndexEntry
|
||||
{
|
||||
public readonly uint Name;
|
||||
public readonly uint NextFileEntry;
|
||||
public readonly uint UserData;
|
||||
|
||||
public FIoFileIndexEntry(BinaryReader reader)
|
||||
{
|
||||
Name = reader.ReadUInt32();
|
||||
NextFileEntry = reader.ReadUInt32();
|
||||
UserData = reader.ReadUInt32();
|
||||
}
|
||||
}
|
||||
}
|
||||
26
FModel/PakReader/Pak/IO/FIoOffsetAndLength.cs
Normal file
26
FModel/PakReader/Pak/IO/FIoOffsetAndLength.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public readonly struct FIoOffsetAndLength
|
||||
{
|
||||
// We use 5 bytes for offset and size, this is enough to represent
|
||||
// an offset and size of 1PB
|
||||
public readonly byte[] OffsetAndLength;
|
||||
public long Offset => OffsetAndLength[4]
|
||||
| ((long) OffsetAndLength[3] << 8)
|
||||
| ((long) OffsetAndLength[2] << 16)
|
||||
| ((long) OffsetAndLength[1] << 24)
|
||||
| ((long) OffsetAndLength[0] << 32);
|
||||
public long Length => OffsetAndLength[9]
|
||||
| ((long) OffsetAndLength[8] << 8)
|
||||
| ((long) OffsetAndLength[7] << 16)
|
||||
| ((long) OffsetAndLength[6] << 24)
|
||||
| ((long) OffsetAndLength[5] << 32);
|
||||
|
||||
public FIoOffsetAndLength(BinaryReader reader)
|
||||
{
|
||||
OffsetAndLength = reader.ReadBytes(5 + 5);
|
||||
}
|
||||
}
|
||||
}
|
||||
69
FModel/PakReader/Pak/IO/FIoStoreTocCompressedBlockEntry.cs
Normal file
69
FModel/PakReader/Pak/IO/FIoStoreTocCompressedBlockEntry.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.IO;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public readonly struct FIoStoreTocCompressedBlockEntry
|
||||
{
|
||||
private const int OffsetBits = 40;
|
||||
private const ulong OffsetMask = (1ul << OffsetBits) - 1ul;
|
||||
private const int SizeBits = 24;
|
||||
private const uint SizeMask = (1 << SizeBits) - 1;
|
||||
private const int SizeShift = 8;
|
||||
|
||||
|
||||
/* 5 bytes offset, 3 bytes for size / uncompressed size and 1 byte for compresseion method. */
|
||||
public readonly byte[] Data;
|
||||
|
||||
public long Offset
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
unsafe { fixed (byte* ptr = Data) {
|
||||
var offset = (ulong*) ptr;
|
||||
return (long) (*offset & OffsetMask);
|
||||
} }
|
||||
}
|
||||
}
|
||||
public uint CompressedSize
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
unsafe { fixed (byte* ptr = Data) {
|
||||
var size = ((uint*) ptr) + 1;
|
||||
return (*size >> SizeShift) & SizeMask;
|
||||
} }
|
||||
}
|
||||
}
|
||||
public uint UncompressedSize
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
unsafe { fixed (byte* ptr = Data) {
|
||||
var uncompressedSize = ((uint*) ptr) + 2;
|
||||
return *uncompressedSize & SizeMask;
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
public byte CompressionMethodIndex
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
unsafe { fixed (byte* ptr = Data) {
|
||||
var index = ((uint*) ptr) + 2;
|
||||
return (byte) (*index >> SizeBits);
|
||||
} }
|
||||
}
|
||||
}
|
||||
|
||||
public FIoStoreTocCompressedBlockEntry(BinaryReader reader)
|
||||
{
|
||||
Data = reader.ReadBytes(5 + 3 + 3 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
FModel/PakReader/Pak/IO/FIoStoreTocEntryMeta.cs
Normal file
18
FModel/PakReader/Pak/IO/FIoStoreTocEntryMeta.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.IO;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public readonly struct FIoStoreTocEntryMeta
|
||||
{
|
||||
public const int SIZE = 32 + 4;
|
||||
|
||||
public readonly FIoChunkHash ChunkHash;
|
||||
public readonly FIoStoreTocEntryMetaFlags Flags;
|
||||
|
||||
public FIoStoreTocEntryMeta(BinaryReader reader)
|
||||
{
|
||||
ChunkHash = new FIoChunkHash(reader);
|
||||
Flags = (FIoStoreTocEntryMetaFlags) reader.ReadByte();
|
||||
}
|
||||
}
|
||||
}
|
||||
9
FModel/PakReader/Pak/IO/FIoStoreTocEntryMetaFlags.cs
Normal file
9
FModel/PakReader/Pak/IO/FIoStoreTocEntryMetaFlags.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public enum FIoStoreTocEntryMetaFlags : byte
|
||||
{
|
||||
None,
|
||||
Compressed = (1 << 0),
|
||||
MemoryMapped = (1 << 1)
|
||||
}
|
||||
}
|
||||
56
FModel/PakReader/Pak/IO/FIoStoreTocHeader.cs
Normal file
56
FModel/PakReader/Pak/IO/FIoStoreTocHeader.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using PakReader.Parsers.Objects;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public enum EIoStoreTocVersion : byte
|
||||
{
|
||||
Invalid = 0,
|
||||
Initial,
|
||||
DirectoryIndex,
|
||||
LatestPlusOne,
|
||||
Latest = LatestPlusOne - 1
|
||||
}
|
||||
|
||||
public class FIoStoreTocHeader
|
||||
{
|
||||
public const int SIZE = 144;
|
||||
public static byte[] TOC_MAGIC = new byte[]
|
||||
{0x2D, 0x3D, 0x3D, 0x2D, 0x2D, 0x3D, 0x3D, 0x2D, 0x2D, 0x3D, 0x3D, 0x2D, 0x2D, 0x3D, 0x3D, 0x2D};
|
||||
|
||||
public byte[] TocMagic;
|
||||
public EIoStoreTocVersion Version;
|
||||
public uint TocHeaderSize;
|
||||
public uint TocEntryCount;
|
||||
public uint TocCompressedBlockEntryCount;
|
||||
public uint TocCompressedBlockEntrySize; // For sanity checking
|
||||
public uint CompressionMethodNameCount;
|
||||
public uint CompressionMethodNameLength;
|
||||
public uint CompressionBlockSize;
|
||||
public long DirectoryIndexSize;
|
||||
public FIoContainerId ContainerId;
|
||||
public FGuid EncryptionKeyGuid;
|
||||
public EIoContainerFlags ContainerFlags;
|
||||
|
||||
public FIoStoreTocHeader(BinaryReader reader)
|
||||
{
|
||||
TocMagic = reader.ReadBytes(16);
|
||||
if (!TOC_MAGIC.SequenceEqual(TocMagic))
|
||||
throw new FileLoadException("Invalid utoc magic");
|
||||
Version = (EIoStoreTocVersion) reader.ReadInt32();
|
||||
TocHeaderSize = reader.ReadUInt32();
|
||||
TocEntryCount = reader.ReadUInt32();
|
||||
TocCompressedBlockEntryCount = reader.ReadUInt32();
|
||||
TocCompressedBlockEntrySize = reader.ReadUInt32();
|
||||
CompressionMethodNameCount = reader.ReadUInt32();
|
||||
CompressionMethodNameLength = reader.ReadUInt32();
|
||||
CompressionBlockSize = reader.ReadUInt32();
|
||||
DirectoryIndexSize = reader.ReadInt64();
|
||||
ContainerId = new FIoContainerId(reader);
|
||||
EncryptionKeyGuid = new FGuid(reader);
|
||||
ContainerFlags = (EIoContainerFlags) reader.ReadInt32();
|
||||
reader.BaseStream.Position += 60; // Padding
|
||||
}
|
||||
}
|
||||
}
|
||||
113
FModel/PakReader/Pak/IO/FIoStoreTocResource.cs
Normal file
113
FModel/PakReader/Pak/IO/FIoStoreTocResource.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using FModel.Utils;
|
||||
using PakReader.Parsers.Objects;
|
||||
|
||||
namespace PakReader.Pak.IO
|
||||
{
|
||||
public enum EIoStoreTocReadOptions
|
||||
{
|
||||
Default,
|
||||
ReadDirectoryIndex = (1 << 0),
|
||||
ReadTocMeta = (1 << 1),
|
||||
ReadAll = ReadDirectoryIndex | ReadTocMeta
|
||||
}
|
||||
|
||||
public class FIoStoreTocResource
|
||||
{
|
||||
public readonly FIoStoreTocHeader Header;
|
||||
public readonly FIoChunkId[] ChunkIds;
|
||||
public readonly FIoOffsetAndLength[] ChunkOffsetLengths;
|
||||
public readonly FIoStoreTocCompressedBlockEntry[] CompressionBlocks;
|
||||
public readonly string[] CompressionMethods;
|
||||
public readonly FSHAHash[] ChunkBlockSignatures;
|
||||
public readonly byte[] DirectoryIndexBuffer;
|
||||
public readonly FIoStoreTocEntryMeta[] ChunkMetas;
|
||||
|
||||
public FIoStoreTocResource(Stream tocStream, EIoStoreTocReadOptions readOptions = EIoStoreTocReadOptions.Default)
|
||||
{
|
||||
using var reader = new BinaryReader(tocStream);
|
||||
Header = new FIoStoreTocHeader(reader);
|
||||
|
||||
var totalTocSize = tocStream.Length - FIoStoreTocHeader.SIZE;
|
||||
var tocMetaSize = Header.TocEntryCount * FIoStoreTocEntryMeta.SIZE;
|
||||
var defaultTocSize = totalTocSize - Header.DirectoryIndexSize - tocMetaSize;
|
||||
|
||||
var tocSize = defaultTocSize;
|
||||
if (readOptions.HasAnyFlags(EIoStoreTocReadOptions.ReadTocMeta))
|
||||
{
|
||||
tocSize = totalTocSize; // Meta data is at the end of the TOC file
|
||||
}
|
||||
|
||||
if (readOptions.HasAnyFlags(EIoStoreTocReadOptions.ReadDirectoryIndex))
|
||||
{
|
||||
tocSize = defaultTocSize + Header.DirectoryIndexSize;
|
||||
}
|
||||
|
||||
// Chunk IDs
|
||||
ChunkIds = new FIoChunkId[Header.TocEntryCount];
|
||||
for (var i = 0; i < Header.TocEntryCount; i++)
|
||||
{
|
||||
ChunkIds[i] = new FIoChunkId(reader);
|
||||
}
|
||||
|
||||
// Chunk offsets
|
||||
ChunkOffsetLengths = new FIoOffsetAndLength[Header.TocEntryCount];
|
||||
for (var i = 0; i < Header.TocEntryCount; i++)
|
||||
{
|
||||
ChunkOffsetLengths[i] = new FIoOffsetAndLength(reader);
|
||||
}
|
||||
|
||||
// Compression blocks
|
||||
CompressionBlocks = new FIoStoreTocCompressedBlockEntry[Header.TocCompressedBlockEntryCount];
|
||||
for (var i = 0; i < Header.TocCompressedBlockEntryCount; i++)
|
||||
{
|
||||
CompressionBlocks[i] = new FIoStoreTocCompressedBlockEntry(reader);
|
||||
}
|
||||
|
||||
// Compression methods
|
||||
CompressionMethods = new string[Header.CompressionMethodNameCount]; // Not doing +1 nor adding CompressionMethod none here since the FPakInfo implementation doesn't as well
|
||||
for (var i = 0; i < Header.CompressionMethodNameCount; i++)
|
||||
{
|
||||
CompressionMethods[i] = Encoding.ASCII.GetString(reader.ReadBytes((int) Header.CompressionMethodNameLength)).TrimEnd('\0');
|
||||
}
|
||||
|
||||
// Chunk block signatures
|
||||
if (Header.ContainerFlags.HasAnyFlags(EIoContainerFlags.Signed))
|
||||
{
|
||||
var hashSize = reader.ReadInt32();
|
||||
reader.BaseStream.Position += hashSize; // actually: var tocSignature = reader.ReadBytes(hashSize);
|
||||
reader.BaseStream.Position += hashSize; // actually: var blockSignature = reader.ReadBytes(hashSize);
|
||||
|
||||
ChunkBlockSignatures = new FSHAHash[Header.TocCompressedBlockEntryCount];
|
||||
for (var i = 0; i < Header.TocCompressedBlockEntryCount; i++)
|
||||
{
|
||||
ChunkBlockSignatures[i] = new FSHAHash(reader);
|
||||
}
|
||||
|
||||
// You could very hashes here but nah
|
||||
}
|
||||
|
||||
// Directory index
|
||||
if (Header.Version >= EIoStoreTocVersion.DirectoryIndex &&
|
||||
readOptions.HasAnyFlags(EIoStoreTocReadOptions.ReadDirectoryIndex) &&
|
||||
Header.ContainerFlags.HasAnyFlags(EIoContainerFlags.Indexed) &&
|
||||
Header.DirectoryIndexSize > 0)
|
||||
{
|
||||
DirectoryIndexBuffer = reader.ReadBytes((int) Header.DirectoryIndexSize);
|
||||
}
|
||||
|
||||
// Meta
|
||||
if (readOptions.HasAnyFlags(EIoStoreTocReadOptions.ReadTocMeta))
|
||||
{
|
||||
ChunkMetas = new FIoStoreTocEntryMeta[Header.TocEntryCount];
|
||||
for (var i = 0; i < Header.TocEntryCount; i++)
|
||||
{
|
||||
ChunkMetas[i] = new FIoStoreTocEntryMeta(reader);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
FModel/Utils/Enums.cs
Normal file
14
FModel/Utils/Enums.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FModel.Utils
|
||||
{
|
||||
public static class Enums
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool HasAnyFlags<T>(this T flags, T contains) where T : System.Enum, IConvertible
|
||||
{
|
||||
return (flags.ToInt32(null) & contains.ToInt32(null)) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user