mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
Patching and extraction works, fewer failing tests
This commit is contained in:
parent
6602f1a895
commit
a847d69851
@ -27,7 +27,6 @@ namespace Compression.BSA
|
|||||||
|
|
||||||
public class BA2Reader : IBSAReader
|
public class BA2Reader : IBSAReader
|
||||||
{
|
{
|
||||||
internal AbsolutePath _filename;
|
|
||||||
private Stream _stream;
|
private Stream _stream;
|
||||||
internal BinaryReader _rdr;
|
internal BinaryReader _rdr;
|
||||||
internal uint _version;
|
internal uint _version;
|
||||||
@ -35,15 +34,16 @@ namespace Compression.BSA
|
|||||||
internal EntryType _type;
|
internal EntryType _type;
|
||||||
internal uint _numFiles;
|
internal uint _numFiles;
|
||||||
internal ulong _nameTableOffset;
|
internal ulong _nameTableOffset;
|
||||||
|
public IStreamFactory _streamFactory;
|
||||||
public bool UseATIFourCC { get; set; } = false;
|
public bool UseATIFourCC { get; set; } = false;
|
||||||
|
|
||||||
public bool HasNameTable => _nameTableOffset > 0;
|
public bool HasNameTable => _nameTableOffset > 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static async Task<BA2Reader> Load(AbsolutePath filename)
|
public static async Task<BA2Reader> Load(IStreamFactory streamFactory)
|
||||||
{
|
{
|
||||||
var rdr = new BA2Reader(await filename.OpenShared()) {_filename = filename};
|
var rdr = new BA2Reader(await streamFactory.GetStream()) {_streamFactory = streamFactory};
|
||||||
await rdr.LoadHeaders();
|
await rdr.LoadHeaders();
|
||||||
return rdr;
|
return rdr;
|
||||||
}
|
}
|
||||||
@ -206,7 +206,7 @@ namespace Compression.BSA
|
|||||||
|
|
||||||
WriteHeader(bw);
|
WriteHeader(bw);
|
||||||
|
|
||||||
await using var fs = await _bsa._filename.OpenRead();
|
await using var fs = await _bsa._streamFactory.GetStream();
|
||||||
using var br = new BinaryReader(fs);
|
using var br = new BinaryReader(fs);
|
||||||
foreach (var chunk in _chunks)
|
foreach (var chunk in _chunks)
|
||||||
{
|
{
|
||||||
@ -344,6 +344,14 @@ namespace Compression.BSA
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<IStreamFactory> GetStreamFactory()
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
await CopyDataTo(ms);
|
||||||
|
ms.Position = 0;
|
||||||
|
return new MemoryStreamFactory(ms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonName("BA2DX10Entry")]
|
[JsonName("BA2DX10Entry")]
|
||||||
@ -483,7 +491,7 @@ namespace Compression.BSA
|
|||||||
|
|
||||||
public async ValueTask CopyDataTo(Stream output)
|
public async ValueTask CopyDataTo(Stream output)
|
||||||
{
|
{
|
||||||
await using var fs = await _bsa._filename.OpenRead();
|
await using var fs = await _bsa._streamFactory.GetStream();
|
||||||
fs.Seek((long) _offset, SeekOrigin.Begin);
|
fs.Seek((long) _offset, SeekOrigin.Begin);
|
||||||
uint len = Compressed ? _size : _realSize;
|
uint len = Compressed ? _size : _realSize;
|
||||||
|
|
||||||
@ -503,6 +511,14 @@ namespace Compression.BSA
|
|||||||
await output.WriteAsync(uncompressed, 0, uncompressed.Length);
|
await output.WriteAsync(uncompressed, 0, uncompressed.Length);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<IStreamFactory> GetStreamFactory()
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
await CopyDataTo(ms);
|
||||||
|
ms.Position = 0;
|
||||||
|
return new MemoryStreamFactory(ms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[JsonName("BA2FileEntryState")]
|
[JsonName("BA2FileEntryState")]
|
||||||
|
@ -16,7 +16,6 @@ namespace Compression.BSA
|
|||||||
public const int HeaderLength = 0x24;
|
public const int HeaderLength = 0x24;
|
||||||
|
|
||||||
internal uint _fileCount;
|
internal uint _fileCount;
|
||||||
internal AbsolutePath _fileName;
|
|
||||||
internal uint _folderCount;
|
internal uint _folderCount;
|
||||||
internal uint _folderRecordOffset;
|
internal uint _folderRecordOffset;
|
||||||
private Lazy<FolderRecord[]> _folders = null!;
|
private Lazy<FolderRecord[]> _folders = null!;
|
||||||
@ -24,6 +23,7 @@ namespace Compression.BSA
|
|||||||
internal string _magic = string.Empty;
|
internal string _magic = string.Empty;
|
||||||
internal uint _totalFileNameLength;
|
internal uint _totalFileNameLength;
|
||||||
internal uint _totalFolderNameLength;
|
internal uint _totalFolderNameLength;
|
||||||
|
public IStreamFactory _streamFactory = new NativeFileStreamFactory(default);
|
||||||
|
|
||||||
public VersionType HeaderType { get; private set; }
|
public VersionType HeaderType { get; private set; }
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ namespace Compression.BSA
|
|||||||
|
|
||||||
public void Dump(Action<string> print)
|
public void Dump(Action<string> print)
|
||||||
{
|
{
|
||||||
print($"File Name: {_fileName}");
|
print($"File Name: {_streamFactory.Name}");
|
||||||
print($"File Count: {_fileCount}");
|
print($"File Count: {_fileCount}");
|
||||||
print($"Magic: {_magic}");
|
print($"Magic: {_magic}");
|
||||||
|
|
||||||
@ -67,11 +67,11 @@ namespace Compression.BSA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async ValueTask<BSAReader> LoadAsync(AbsolutePath filename)
|
public static async ValueTask<BSAReader> LoadAsync(IStreamFactory factory)
|
||||||
{
|
{
|
||||||
using var stream = await filename.OpenRead().ConfigureAwait(false);
|
await using var stream = await factory.GetStream().ConfigureAwait(false);
|
||||||
using var br = new BinaryReader(stream);
|
using var br = new BinaryReader(stream);
|
||||||
var bsa = new BSAReader { _fileName = filename };
|
var bsa = new BSAReader { _streamFactory = factory };
|
||||||
bsa.LoadHeaders(br);
|
bsa.LoadHeaders(br);
|
||||||
return bsa;
|
return bsa;
|
||||||
}
|
}
|
||||||
@ -79,7 +79,7 @@ namespace Compression.BSA
|
|||||||
|
|
||||||
public static BSAReader Load(AbsolutePath filename)
|
public static BSAReader Load(AbsolutePath filename)
|
||||||
{
|
{
|
||||||
var bsa = new BSAReader { _fileName = filename };
|
var bsa = new BSAReader { _streamFactory = new NativeFileStreamFactory(filename)};
|
||||||
using var rdr = bsa.GetStream();
|
using var rdr = bsa.GetStream();
|
||||||
bsa.LoadHeaders(rdr);
|
bsa.LoadHeaders(rdr);
|
||||||
return bsa;
|
return bsa;
|
||||||
@ -87,7 +87,7 @@ namespace Compression.BSA
|
|||||||
|
|
||||||
internal BinaryReader GetStream()
|
internal BinaryReader GetStream()
|
||||||
{
|
{
|
||||||
return new BinaryReader(File.Open(_fileName.ToString(), FileMode.Open, FileAccess.Read, FileShare.Read));
|
return new BinaryReader(_streamFactory.GetStream().Result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadHeaders(BinaryReader rdr)
|
private void LoadHeaders(BinaryReader rdr)
|
||||||
|
@ -74,7 +74,7 @@ namespace Compression.BSA
|
|||||||
|
|
||||||
public async ValueTask CopyDataTo(Stream output)
|
public async ValueTask CopyDataTo(Stream output)
|
||||||
{
|
{
|
||||||
await using var in_file = await BSA._fileName.OpenRead().ConfigureAwait(false);
|
await using var in_file = await BSA._streamFactory.GetStream().ConfigureAwait(false);
|
||||||
using var rdr = new BinaryReader(in_file);
|
using var rdr = new BinaryReader(in_file);
|
||||||
rdr.BaseStream.Position = Offset;
|
rdr.BaseStream.Position = Offset;
|
||||||
|
|
||||||
@ -165,5 +165,13 @@ namespace Compression.BSA
|
|||||||
print($"Raw Size: {RawSize}");
|
print($"Raw Size: {RawSize}");
|
||||||
print($"Index: {_index}");
|
print($"Index: {_index}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<IStreamFactory> GetStreamFactory()
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
await CopyDataTo(ms);
|
||||||
|
ms.Position = 0;
|
||||||
|
return new MemoryStreamFactory(ms);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -13,9 +15,9 @@ namespace Compression.BSA
|
|||||||
{
|
{
|
||||||
return await BSASignatures.MatchesAsync(filename) switch
|
return await BSASignatures.MatchesAsync(filename) switch
|
||||||
{
|
{
|
||||||
Definitions.FileType.TES3 => await TES3Reader.Load(filename),
|
Definitions.FileType.TES3 => await TES3Reader.Load(new NativeFileStreamFactory(filename)),
|
||||||
Definitions.FileType.BSA => await BSAReader.LoadAsync(filename),
|
Definitions.FileType.BSA => await BSAReader.LoadAsync(new NativeFileStreamFactory(filename)),
|
||||||
Definitions.FileType.BA2 => await BA2Reader.Load(filename),
|
Definitions.FileType.BA2 => await BA2Reader.Load(new NativeFileStreamFactory(filename)),
|
||||||
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -25,5 +27,21 @@ namespace Compression.BSA
|
|||||||
{
|
{
|
||||||
return await BSASignatures.MatchesAsync(filename) != null;
|
return await BSASignatures.MatchesAsync(filename) != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async ValueTask<IBSAReader> OpenRead(IStreamFactory sFn, Definitions.FileType sig)
|
||||||
|
{
|
||||||
|
switch(sig)
|
||||||
|
{
|
||||||
|
case Definitions.FileType.TES3:
|
||||||
|
return await TES3Reader.Load(sFn);
|
||||||
|
case Definitions.FileType.BSA:
|
||||||
|
return await BSAReader.LoadAsync(sFn);
|
||||||
|
case Definitions.FileType.BA2:
|
||||||
|
return await BA2Reader.Load(sFn);
|
||||||
|
default:
|
||||||
|
throw new Exception($"Bad archive format for {sFn.Name}");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,5 +32,6 @@ namespace Compression.BSA
|
|||||||
ValueTask CopyDataTo(Stream output);
|
ValueTask CopyDataTo(Stream output);
|
||||||
|
|
||||||
void Dump(Action<string> print);
|
void Dump(Action<string> print);
|
||||||
|
ValueTask<IStreamFactory> GetStreamFactory();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
Compression.BSA/MemoryStreamFactory.cs
Normal file
24
Compression.BSA/MemoryStreamFactory.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
public class MemoryStreamFactory : IStreamFactory
|
||||||
|
{
|
||||||
|
private readonly MemoryStream _data;
|
||||||
|
|
||||||
|
public MemoryStreamFactory(MemoryStream data)
|
||||||
|
{
|
||||||
|
_data = data;
|
||||||
|
}
|
||||||
|
public async ValueTask<Stream> GetStream()
|
||||||
|
{
|
||||||
|
return new MemoryStream(_data.GetBuffer(), 0, (int)_data.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime LastModifiedUtc => DateTime.UtcNow;
|
||||||
|
public IPath Name => (RelativePath)"BSA Memory Stream";
|
||||||
|
}
|
||||||
|
}
|
82
Compression.BSA/StreamView.cs
Normal file
82
Compression.BSA/StreamView.cs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
|
||||||
|
namespace Compression.BSA
|
||||||
|
{
|
||||||
|
public class StreamView : Stream
|
||||||
|
{
|
||||||
|
private Stream _base;
|
||||||
|
private long _startPos;
|
||||||
|
private long _length;
|
||||||
|
|
||||||
|
public StreamView(Stream baseStream, long startPos, long length)
|
||||||
|
{
|
||||||
|
_base = baseStream;
|
||||||
|
_startPos = startPos;
|
||||||
|
_length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush()
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
var realCount = Math.Min(count, Length - Position);
|
||||||
|
return _base.Read(buffer, offset, (int)realCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin)
|
||||||
|
{
|
||||||
|
switch (origin)
|
||||||
|
{
|
||||||
|
case SeekOrigin.Begin:
|
||||||
|
Position = offset;
|
||||||
|
return Position;
|
||||||
|
case SeekOrigin.End:
|
||||||
|
Position = _length - offset;
|
||||||
|
return Position;
|
||||||
|
case SeekOrigin.Current:
|
||||||
|
Position += offset;
|
||||||
|
return Position;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetLength(long value)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
throw new System.NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool CanRead => true;
|
||||||
|
public override bool CanSeek => true;
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
public override long Length => _length;
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _base.Position - _startPos;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_base.Position = _startPos + value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async ValueTask DisposeAsync()
|
||||||
|
{
|
||||||
|
await _base.DisposeAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -17,15 +17,15 @@ namespace Compression.BSA
|
|||||||
private uint _fileCount;
|
private uint _fileCount;
|
||||||
private TES3FileEntry[] _files;
|
private TES3FileEntry[] _files;
|
||||||
internal long _dataOffset;
|
internal long _dataOffset;
|
||||||
internal AbsolutePath _filename;
|
public IStreamFactory _streamFactory;
|
||||||
|
|
||||||
public static async ValueTask<TES3Reader> Load(AbsolutePath filename)
|
public static async ValueTask<TES3Reader> Load(IStreamFactory factory)
|
||||||
{
|
{
|
||||||
await using var fs = await filename.OpenRead();
|
await using var fs = await factory.GetStream();
|
||||||
using var br = new BinaryReader(fs);
|
using var br = new BinaryReader(fs);
|
||||||
var rdr = new TES3Reader
|
var rdr = new TES3Reader
|
||||||
{
|
{
|
||||||
_filename = filename,
|
_streamFactory = factory,
|
||||||
_versionNumber = br.ReadUInt32(),
|
_versionNumber = br.ReadUInt32(),
|
||||||
_hashTableOffset = br.ReadUInt32(),
|
_hashTableOffset = br.ReadUInt32(),
|
||||||
_fileCount = br.ReadUInt32()
|
_fileCount = br.ReadUInt32()
|
||||||
@ -125,16 +125,27 @@ namespace Compression.BSA
|
|||||||
|
|
||||||
public async ValueTask CopyDataTo(Stream output)
|
public async ValueTask CopyDataTo(Stream output)
|
||||||
{
|
{
|
||||||
await using var fs = await Archive._filename.OpenRead();
|
await using var fs = await Archive._streamFactory.GetStream();
|
||||||
fs.Position = Archive._dataOffset + Offset;
|
fs.Position = Archive._dataOffset + Offset;
|
||||||
await fs.CopyToLimitAsync(output, (int)Size);
|
await fs.CopyToLimitAsync(output, (int)Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async ValueTask<IStreamFactory> GetStreamFactory()
|
||||||
|
{
|
||||||
|
var ms = new MemoryStream();
|
||||||
|
await CopyDataTo(ms);
|
||||||
|
ms.Position = 0;
|
||||||
|
return new MemoryStreamFactory(ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void Dump(Action<string> print)
|
public void Dump(Action<string> print)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public uint Offset { get; set; }
|
public uint Offset { get; set; }
|
||||||
public uint NameOffset { get; set; }
|
public uint NameOffset { get; set; }
|
||||||
public uint Hash1 { get; set; }
|
public uint Hash1 { get; set; }
|
||||||
|
34
Wabbajack.Common/IStreamFactory.cs
Normal file
34
Wabbajack.Common/IStreamFactory.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Wabbajack.Common;
|
||||||
|
|
||||||
|
namespace Wabbajack.Common
|
||||||
|
{
|
||||||
|
public interface IStreamFactory
|
||||||
|
{
|
||||||
|
ValueTask<Stream> GetStream();
|
||||||
|
|
||||||
|
DateTime LastModifiedUtc { get; }
|
||||||
|
|
||||||
|
IPath Name { get; }
|
||||||
|
|
||||||
|
}
|
||||||
|
public class NativeFileStreamFactory : IStreamFactory
|
||||||
|
{
|
||||||
|
private AbsolutePath _file;
|
||||||
|
|
||||||
|
public NativeFileStreamFactory(AbsolutePath file)
|
||||||
|
{
|
||||||
|
_file = file;
|
||||||
|
}
|
||||||
|
public async ValueTask<Stream> GetStream()
|
||||||
|
{
|
||||||
|
return await _file.OpenRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime LastModifiedUtc => _file.LastModifiedUtc;
|
||||||
|
public IPath Name => _file;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -50,7 +50,7 @@ namespace Wabbajack.Common
|
|||||||
await patch.CopyToAsync(output);
|
await patch.CopyToAsync(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<long> CreatePatchCached(Stream srcStream, Hash srcHash, FileStream destStream, Hash destHash,
|
public static async Task<long> CreatePatchCached(Stream srcStream, Hash srcHash, Stream destStream, Hash destHash,
|
||||||
Stream? patchOutStream = null)
|
Stream? patchOutStream = null)
|
||||||
{
|
{
|
||||||
var key = PatchKey(srcHash, destHash);
|
var key = PatchKey(srcHash, destHash);
|
||||||
@ -128,7 +128,7 @@ namespace Wabbajack.Common
|
|||||||
public static Task CreatePatchCached(byte[] a, byte[] b, Stream output) =>
|
public static Task CreatePatchCached(byte[] a, byte[] b, Stream output) =>
|
||||||
PatchCache.CreatePatchCached(a, b, output);
|
PatchCache.CreatePatchCached(a, b, output);
|
||||||
|
|
||||||
public static Task<long> CreatePatchCached(Stream srcStream, Hash srcHash, FileStream destStream, Hash destHash, Stream? patchOutStream = null) =>
|
public static Task<long> CreatePatchCached(Stream srcStream, Hash srcHash, Stream destStream, Hash destHash, Stream? patchOutStream = null) =>
|
||||||
PatchCache.CreatePatchCached(srcStream, srcHash, destStream, destHash, patchOutStream);
|
PatchCache.CreatePatchCached(srcStream, srcHash, destStream, destHash, patchOutStream);
|
||||||
|
|
||||||
public static bool TryGetPatch(Hash foundHash, Hash fileHash, [MaybeNullWhen(false)] out byte[] ePatch) =>
|
public static bool TryGetPatch(Hash foundHash, Hash fileHash, [MaybeNullWhen(false)] out byte[] ePatch) =>
|
||||||
|
@ -9,6 +9,7 @@ using Alphaleonis.Win32.Filesystem;
|
|||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Lib.Downloaders;
|
using Wabbajack.Lib.Downloaders;
|
||||||
using Wabbajack.VirtualFileSystem;
|
using Wabbajack.VirtualFileSystem;
|
||||||
|
using Wabbajack.VirtualFileSystem.SevenZipExtractor;
|
||||||
using Directory = Alphaleonis.Win32.Filesystem.Directory;
|
using Directory = Alphaleonis.Win32.Filesystem.Directory;
|
||||||
using File = Alphaleonis.Win32.Filesystem.File;
|
using File = Alphaleonis.Win32.Filesystem.File;
|
||||||
using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo;
|
using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo;
|
||||||
@ -35,6 +36,8 @@ namespace Wabbajack.Lib
|
|||||||
|
|
||||||
public bool UseCompression { get; set; }
|
public bool UseCompression { get; set; }
|
||||||
|
|
||||||
|
public TempFolder? ExtractedModlistFolder { get; set; } = null;
|
||||||
|
|
||||||
|
|
||||||
public AInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters? parameters, int steps, Game game)
|
public AInstaller(AbsolutePath archive, ModList modList, AbsolutePath outputFolder, AbsolutePath downloadFolder, SystemParameters? parameters, int steps, Game game)
|
||||||
: base(steps)
|
: base(steps)
|
||||||
@ -45,12 +48,23 @@ namespace Wabbajack.Lib
|
|||||||
DownloadFolder = downloadFolder;
|
DownloadFolder = downloadFolder;
|
||||||
SystemParameters = parameters;
|
SystemParameters = parameters;
|
||||||
Game = game.MetaData();
|
Game = game.MetaData();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private ExtractedFiles? ExtractedModListFiles { get; set; } = null;
|
private ExtractedFiles? ExtractedModListFiles { get; set; } = null;
|
||||||
public async Task ExtractModlist()
|
public async Task ExtractModlist()
|
||||||
{
|
{
|
||||||
ExtractedModListFiles = await FileExtractor.ExtractAll(Queue, ModListArchive);
|
ExtractedModlistFolder = await TempFolder.Create();
|
||||||
|
await FileExtractor2.GatheringExtract(new NativeFileStreamFactory(ModListArchive), _ => true,
|
||||||
|
async (path, sfn) =>
|
||||||
|
{
|
||||||
|
await using var s = await sfn.GetStream();
|
||||||
|
var fp = ExtractedModlistFolder.Dir.Combine(path);
|
||||||
|
fp.Parent.CreateDirectory();
|
||||||
|
await fp.WriteAllAsync(s);
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -73,8 +87,7 @@ namespace Wabbajack.Lib
|
|||||||
|
|
||||||
public async Task<byte[]> LoadBytesFromPath(RelativePath path)
|
public async Task<byte[]> LoadBytesFromPath(RelativePath path)
|
||||||
{
|
{
|
||||||
await using var e = await ExtractedModListFiles![path].OpenRead();
|
return await ExtractedModlistFolder!.Dir.Combine(path).ReadAllBytesAsync();
|
||||||
return await e.ReadAllAsync();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ModList LoadFromFile(AbsolutePath path)
|
public static ModList LoadFromFile(AbsolutePath path)
|
||||||
@ -113,23 +126,23 @@ namespace Wabbajack.Lib
|
|||||||
|
|
||||||
public async Task InstallArchives()
|
public async Task InstallArchives()
|
||||||
{
|
{
|
||||||
await VFS.CopyTo(Queue, ModList.Directives
|
|
||||||
.OfType<FromArchive>()
|
|
||||||
.Select(a => (VFS.Index.FileForArchiveHashPath(a.ArchiveHashPath), a.To.RelativeTo(OutputFolder))));
|
|
||||||
/*
|
|
||||||
Info("Installing Archives");
|
|
||||||
Info("Grouping Install Files");
|
|
||||||
var grouped = ModList.Directives
|
var grouped = ModList.Directives
|
||||||
.OfType<FromArchive>()
|
.OfType<FromArchive>()
|
||||||
.GroupBy(e => e.ArchiveHashPath.BaseHash)
|
.Select(a => new {VF = VFS.Index.FileForArchiveHashPath(a.ArchiveHashPath), Directive = a})
|
||||||
.ToDictionary(k => k.Key);
|
.GroupBy(a => a.VF)
|
||||||
var archives = ModList.Archives
|
.ToDictionary(a => a.Key);
|
||||||
.Select(a => new { Archive = a, AbsolutePath = HashedArchives.GetOrDefault(a.Hash) })
|
|
||||||
.Where(a => a.AbsolutePath != null)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Info("Installing Archives");
|
if (grouped.Count == 0) return;
|
||||||
await archives.PMap(Queue, UpdateTracker,a => InstallArchive(Queue, a.Archive, a.AbsolutePath, grouped[a.Archive.Hash]));*/
|
|
||||||
|
await VFS.Extract(Queue, grouped.Keys.ToHashSet(), async (vf, sf) =>
|
||||||
|
{
|
||||||
|
await using var s = await sf.GetStream();
|
||||||
|
foreach (var directive in grouped[vf])
|
||||||
|
{
|
||||||
|
s.Position = 0;
|
||||||
|
await directive.Directive.To.RelativeTo(OutputFolder).WriteAllAsync(s, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task InstallArchive(WorkQueue queue, Archive archive, AbsolutePath absolutePath, IGrouping<Hash, FromArchive> grouping)
|
private async Task InstallArchive(WorkQueue queue, Archive archive, AbsolutePath absolutePath, IGrouping<Hash, FromArchive> grouping)
|
||||||
|
@ -6,10 +6,12 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using AngleSharp.Common;
|
||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Lib.CompilationSteps;
|
using Wabbajack.Lib.CompilationSteps;
|
||||||
using Wabbajack.Lib.Downloaders;
|
using Wabbajack.Lib.Downloaders;
|
||||||
using Wabbajack.Lib.Validation;
|
using Wabbajack.Lib.Validation;
|
||||||
|
using Wabbajack.VirtualFileSystem;
|
||||||
using Path = Alphaleonis.Win32.Filesystem.Path;
|
using Path = Alphaleonis.Win32.Filesystem.Path;
|
||||||
|
|
||||||
namespace Wabbajack.Lib
|
namespace Wabbajack.Lib
|
||||||
@ -495,17 +497,35 @@ namespace Wabbajack.Lib
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
if (toBuild.Length == 0) return;
|
if (toBuild.Length == 0) return;
|
||||||
|
|
||||||
|
// Extract all the source files
|
||||||
|
var indexed = toBuild.GroupBy(f => (VFS.Index.FileForArchiveHashPath(f.ArchiveHashPath)))
|
||||||
|
.ToDictionary(f => f.Key);
|
||||||
|
await VFS.Extract(Queue, indexed.Keys.ToHashSet(),
|
||||||
|
async (vf, sf) =>
|
||||||
|
{
|
||||||
|
// For each, extract the destination
|
||||||
|
var matches = indexed[vf];
|
||||||
|
using var iqueue = new WorkQueue(1);
|
||||||
|
foreach (var match in matches)
|
||||||
|
{
|
||||||
|
var destFile = FindDestFile(match.To);
|
||||||
|
// Build the patch
|
||||||
|
await VFS.Extract(iqueue, new[] {destFile}.ToHashSet(),
|
||||||
|
async (destvf, destsfn) =>
|
||||||
|
{
|
||||||
|
Info($"Patching {match.To}");
|
||||||
|
Status($"Patching {match.To}");
|
||||||
|
await using var srcStream = await sf.GetStream();
|
||||||
|
await using var destStream = await destsfn.GetStream();
|
||||||
|
var patchSize = await Utils.CreatePatchCached(srcStream, vf.Hash, destStream, destvf.Hash);
|
||||||
|
Info($"Patch size {patchSize} for {match.To}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
var groups = toBuild
|
// Load in the patches
|
||||||
.Where(p => p.PatchID == default)
|
|
||||||
.GroupBy(p => p.ArchiveHashPath.BaseHash)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
Info($"Patching building patches from {groups.Count} archives");
|
|
||||||
var absolutePaths = AllFiles.ToDictionary(e => e.Path, e => e.AbsolutePath);
|
|
||||||
await groups.PMap(Queue, group => BuildArchivePatches(group.Key, group, absolutePaths));
|
|
||||||
|
|
||||||
|
|
||||||
await InstallDirectives.OfType<PatchedFromArchive>()
|
await InstallDirectives.OfType<PatchedFromArchive>()
|
||||||
.Where(p => p.PatchID == default)
|
.Where(p => p.PatchID == default)
|
||||||
.PMap(Queue, async pfa =>
|
.PMap(Queue, async pfa =>
|
||||||
@ -514,6 +534,7 @@ namespace Wabbajack.Lib
|
|||||||
.Select(c => (Utils.TryGetPatch(c.Hash, pfa.Hash, out var data), data, c))
|
.Select(c => (Utils.TryGetPatch(c.Hash, pfa.Hash, out var data), data, c))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
// Pick the best patch
|
||||||
if (patches.All(p => p.Item1))
|
if (patches.All(p => p.Item1))
|
||||||
{
|
{
|
||||||
var (_, bytes, file) = IncludePatches.PickPatch(this, patches);
|
var (_, bytes, file) = IncludePatches.PickPatch(this, patches);
|
||||||
@ -529,42 +550,19 @@ namespace Wabbajack.Lib
|
|||||||
Error($"Missing patches after generation, this should not happen. First failure: {firstFailedPatch.FullPath}");
|
Error($"Missing patches after generation, this should not happen. First failure: {firstFailedPatch.FullPath}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task BuildArchivePatches(Hash archiveSha, IEnumerable<PatchedFromArchive> group,
|
private VirtualFile FindDestFile(RelativePath to)
|
||||||
Dictionary<RelativePath, AbsolutePath> absolutePaths)
|
|
||||||
{
|
{
|
||||||
await using var files = await VFS.StageWith(@group.Select(g => VFS.Index.FileForArchiveHashPath(g.ArchiveHashPath)));
|
var abs = to.RelativeTo(MO2Folder);
|
||||||
var byPath = files.GroupBy(f => string.Join("|", f.FilesInFullPath.Skip(1).Select(i => i.Name)))
|
if (abs.Exists)
|
||||||
.ToDictionary(f => f.Key, f => f.First());
|
return VFS.Index.ByRootPath[abs];
|
||||||
// Now Create the patches
|
|
||||||
await @group.PMap(Queue, async entry =>
|
|
||||||
{
|
|
||||||
Info($"Patching {entry.To}");
|
|
||||||
Status($"Patching {entry.To}");
|
|
||||||
var srcFile = byPath[string.Join("|", entry.ArchiveHashPath.Paths)];
|
|
||||||
await using var srcStream = await srcFile.OpenRead();
|
|
||||||
await using var destStream = await LoadDataForTo(entry.To, absolutePaths);
|
|
||||||
var patchSize = await Utils.CreatePatchCached(srcStream, srcFile.Hash, destStream, entry.Hash);
|
|
||||||
Info($"Patch size {patchSize} for {entry.To}");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<FileStream> LoadDataForTo(RelativePath to, Dictionary<RelativePath, AbsolutePath> absolutePaths)
|
|
||||||
{
|
|
||||||
if (absolutePaths.TryGetValue(to, out var absolute))
|
|
||||||
return await absolute.OpenRead();
|
|
||||||
|
|
||||||
if (to.StartsWith(Consts.BSACreationDir))
|
if (to.StartsWith(Consts.BSACreationDir))
|
||||||
{
|
{
|
||||||
var bsaId = (RelativePath)((string)to).Split('\\')[1];
|
var bsaId = (RelativePath)((string)to).Split('\\')[1];
|
||||||
var bsa = InstallDirectives.OfType<CreateBSA>().First(b => b.TempID == bsaId);
|
var bsa = InstallDirectives.OfType<CreateBSA>().First(b => b.TempID == bsaId);
|
||||||
|
|
||||||
var a = await BSADispatch.OpenRead(MO2Folder.Combine(bsa.To));
|
|
||||||
var find = (RelativePath)Path.Combine(((string)to).Split('\\').Skip(2).ToArray());
|
var find = (RelativePath)Path.Combine(((string)to).Split('\\').Skip(2).ToArray());
|
||||||
var file = a.Files.First(e => e.Path == find);
|
|
||||||
var returnStream = new TempStream();
|
return VFS.Index.ByRootPath[MO2Folder.Combine(bsa.To)].Children.First(c => c.RelativeName == find);
|
||||||
await file.CopyDataTo(returnStream);
|
|
||||||
returnStream.Position = 0;
|
|
||||||
return returnStream;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new ArgumentException($"Couldn't load data for {to}");
|
throw new ArgumentException($"Couldn't load data for {to}");
|
||||||
|
@ -180,6 +180,7 @@ namespace Wabbajack.Lib
|
|||||||
SetScreenSizeInPrefs();
|
SetScreenSizeInPrefs();
|
||||||
|
|
||||||
UpdateTracker.NextStep("Installation complete! You may exit the program.");
|
UpdateTracker.NextStep("Installation complete! You may exit the program.");
|
||||||
|
await ExtractedModlistFolder!.DisposeAsync();
|
||||||
await Metrics.Send(Metrics.FinishInstall, ModList.Name);
|
await Metrics.Send(Metrics.FinishInstall, ModList.Name);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -243,7 +244,8 @@ namespace Wabbajack.Lib
|
|||||||
Status($"Writing included .meta file {directive.To}");
|
Status($"Writing included .meta file {directive.To}");
|
||||||
var outPath = DownloadFolder.Combine(directive.To);
|
var outPath = DownloadFolder.Combine(directive.To);
|
||||||
if (outPath.IsFile) await outPath.DeleteAsync();
|
if (outPath.IsFile) await outPath.DeleteAsync();
|
||||||
await outPath.WriteAllBytesAsync(await LoadBytesFromPath(directive.SourceDataID));
|
var bytes = await LoadBytesFromPath(directive.SourceDataID);
|
||||||
|
await outPath.WriteAllBytesAsync(bytes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
r => fileNames.ContainsKey(r),
|
r => fileNames.ContainsKey(r),
|
||||||
async (rel, csf) =>
|
async (rel, csf) =>
|
||||||
{
|
{
|
||||||
await HandleFile(fileNames[rel], sfn);
|
await HandleFile(fileNames[rel], csf);
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -261,12 +261,6 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CopyTo(WorkQueue queue, IEnumerable<(VirtualFile src, AbsolutePath dest)> directives)
|
|
||||||
{
|
|
||||||
var plans = StagingPlan.StagingPlan.CreatePlan(directives).ToArray();
|
|
||||||
await StagingPlan.StagingPlan.ExecutePlans(queue, plans);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<AsyncDisposableList<VirtualFile>> StageWith(IEnumerable<VirtualFile> files)
|
public async Task<AsyncDisposableList<VirtualFile>> StageWith(IEnumerable<VirtualFile> files)
|
||||||
{
|
{
|
||||||
return new AsyncDisposableList<VirtualFile>(await Stage(files), files);
|
return new AsyncDisposableList<VirtualFile>(await Stage(files), files);
|
||||||
|
@ -30,6 +30,7 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
|
|
||||||
public static async Task<ExtractedFiles> ExtractAll(WorkQueue queue, AbsolutePath source, IEnumerable<RelativePath> OnlyFiles = null, bool throwOnError = true)
|
public static async Task<ExtractedFiles> ExtractAll(WorkQueue queue, AbsolutePath source, IEnumerable<RelativePath> OnlyFiles = null, bool throwOnError = true)
|
||||||
{
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
OnlyFiles ??= new RelativePath[0];
|
OnlyFiles ??= new RelativePath[0];
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Compression.BSA;
|
||||||
using Wabbajack.Common;
|
using Wabbajack.Common;
|
||||||
using Wabbajack.Common.FileSignatures;
|
using Wabbajack.Common.FileSignatures;
|
||||||
using Wabbajack.VirtualFileSystem.SevenZipExtractor;
|
using Wabbajack.VirtualFileSystem.SevenZipExtractor;
|
||||||
@ -14,7 +15,7 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
Definitions.FileType.BSA,
|
Definitions.FileType.BSA,
|
||||||
Definitions.FileType.BA2,
|
Definitions.FileType.BA2,
|
||||||
Definitions.FileType.ZIP,
|
Definitions.FileType.ZIP,
|
||||||
Definitions.FileType.EXE,
|
//Definitions.FileType.EXE,
|
||||||
Definitions.FileType.RAR,
|
Definitions.FileType.RAR,
|
||||||
Definitions.FileType._7Z);
|
Definitions.FileType._7Z);
|
||||||
|
|
||||||
@ -31,11 +32,33 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
case Definitions.FileType.ZIP:
|
case Definitions.FileType.ZIP:
|
||||||
return await GatheringExtractWith7Zip<T>(archive, (Definitions.FileType)sig, shouldExtract, mapfn);
|
return await GatheringExtractWith7Zip<T>(archive, (Definitions.FileType)sig, shouldExtract, mapfn);
|
||||||
|
|
||||||
|
case Definitions.FileType.TES3:
|
||||||
|
case Definitions.FileType.BSA:
|
||||||
|
case Definitions.FileType.BA2:
|
||||||
|
return await GatheringExtractWithBSA(sFn, (Definitions.FileType)sig, shouldExtract, mapfn);
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Exception("Invalid file format");
|
throw new Exception($"Invalid file format {sFn.Name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task<Dictionary<RelativePath,T>> GatheringExtractWithBSA<T>(IStreamFactory sFn, Definitions.FileType sig, Predicate<RelativePath> shouldExtract, Func<RelativePath,IStreamFactory,ValueTask<T>> mapfn)
|
||||||
|
{
|
||||||
|
var archive = await BSADispatch.OpenRead(sFn, sig);
|
||||||
|
var results = new Dictionary<RelativePath, T>();
|
||||||
|
foreach (var entry in archive.Files)
|
||||||
|
{
|
||||||
|
if (!shouldExtract(entry.Path))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var result = await mapfn(entry.Path, await entry.GetStreamFactory());
|
||||||
|
results.Add(entry.Path, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<Dictionary<RelativePath,T>> GatheringExtractWith7Zip<T>(Stream stream, Definitions.FileType sig, Predicate<RelativePath> shouldExtract, Func<RelativePath,IStreamFactory,ValueTask<T>> mapfn)
|
private static async Task<Dictionary<RelativePath,T>> GatheringExtractWith7Zip<T>(Stream stream, Definitions.FileType sig, Predicate<RelativePath> shouldExtract, Func<RelativePath,IStreamFactory,ValueTask<T>> mapfn)
|
||||||
{
|
{
|
||||||
return await new GatheringExtractor<T>(stream, sig, shouldExtract, mapfn).Extract();
|
return await new GatheringExtractor<T>(stream, sig, shouldExtract, mapfn).Extract();
|
||||||
|
@ -5,13 +5,6 @@ using Wabbajack.Common;
|
|||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem
|
namespace Wabbajack.VirtualFileSystem
|
||||||
{
|
{
|
||||||
public interface IStreamFactory
|
|
||||||
{
|
|
||||||
Task<Stream> GetStream();
|
|
||||||
|
|
||||||
DateTime LastModifiedUtc { get; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public class UnmanagedStreamFactory : IStreamFactory
|
public class UnmanagedStreamFactory : IStreamFactory
|
||||||
{
|
{
|
||||||
@ -23,7 +16,7 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
_data = data;
|
_data = data;
|
||||||
_size = size;
|
_size = size;
|
||||||
}
|
}
|
||||||
public async Task<Stream> GetStream()
|
public async ValueTask<Stream> GetStream()
|
||||||
{
|
{
|
||||||
unsafe
|
unsafe
|
||||||
{
|
{
|
||||||
@ -32,21 +25,8 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DateTime LastModifiedUtc => DateTime.UtcNow;
|
public DateTime LastModifiedUtc => DateTime.UtcNow;
|
||||||
|
public IPath Name => (RelativePath)"Unmanaged Memory Stream";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NativeFileStreamFactory : IStreamFactory
|
|
||||||
{
|
|
||||||
private AbsolutePath _file;
|
|
||||||
|
|
||||||
public NativeFileStreamFactory(AbsolutePath file)
|
|
||||||
{
|
|
||||||
_file = file;
|
|
||||||
}
|
|
||||||
public async Task<Stream> GetStream()
|
|
||||||
{
|
|
||||||
return await _file.OpenRead();
|
|
||||||
}
|
|
||||||
|
|
||||||
public DateTime LastModifiedUtc => _file.LastModifiedUtc;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,25 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public abstract class ASubStage : ISubStage
|
|
||||||
{
|
|
||||||
private IEnumerable<IStagingPlan> _plans;
|
|
||||||
|
|
||||||
public ASubStage(IEnumerable<IStagingPlan> plans)
|
|
||||||
{
|
|
||||||
_plans = plans;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract ValueTask DisposeAsync();
|
|
||||||
public abstract AbsolutePath Destination { get; }
|
|
||||||
public abstract IPath Source { get; }
|
|
||||||
|
|
||||||
public async Task Execute(WorkQueue queue)
|
|
||||||
{
|
|
||||||
await StagingPlan.ExecutePlan(queue, async () => await Destination.OpenWrite(), _plans);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public class CopyTo : IStagingSrc
|
|
||||||
{
|
|
||||||
public CopyTo(IPath src, AbsolutePath destination)
|
|
||||||
{
|
|
||||||
Destination = destination;
|
|
||||||
Source = src;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AbsolutePath Destination { get; }
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public IPath Source { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public class DuplicateTo : IStagingPlan
|
|
||||||
{
|
|
||||||
private readonly IStagingPlan _src;
|
|
||||||
|
|
||||||
public DuplicateTo(IStagingPlan src, AbsolutePath destination)
|
|
||||||
{
|
|
||||||
_src = src;
|
|
||||||
Destination = destination;
|
|
||||||
}
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task Execute()
|
|
||||||
{
|
|
||||||
await _src.Destination.CopyToAsync(Destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AbsolutePath Destination { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
using Wabbajack.Common.FileSignatures;
|
|
||||||
using Wabbajack.VirtualFileSystem.SevenZipExtractor;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public interface IStagingPlan : IAsyncDisposable
|
|
||||||
{
|
|
||||||
AbsolutePath Destination { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class StagingPlan
|
|
||||||
{
|
|
||||||
|
|
||||||
public static IEnumerable<IStagingPlan> CreatePlan(IEnumerable<(VirtualFile src, AbsolutePath dest)> directives)
|
|
||||||
{
|
|
||||||
var top = new VirtualFile();
|
|
||||||
var original = directives.ToHashSet();
|
|
||||||
var files = directives.GroupBy(f => f.src).ToDictionary(f => f.Key);
|
|
||||||
|
|
||||||
var childrenForParent = files.Keys.GroupBy(f => f.Parent ?? top).ToDictionary(f => f.Key);
|
|
||||||
|
|
||||||
var allFilesForParent = files.Select(f => f.Key)
|
|
||||||
.SelectMany(f => f.FilesInFullPath)
|
|
||||||
.Distinct()
|
|
||||||
.GroupBy(f => f.Parent ?? top)
|
|
||||||
.ToDictionary(f => f.Key, f => f.ToArray());
|
|
||||||
|
|
||||||
var baseDirectives = new List<IStagingPlan>();
|
|
||||||
|
|
||||||
// Recursive internal function to get the plans for a given parent, we'll then start at the
|
|
||||||
// null parent (the top) and recurse our way down into all the children files.
|
|
||||||
IEnumerable<IStagingPlan> GetPlans(VirtualFile parent)
|
|
||||||
{
|
|
||||||
foreach (var forParent in allFilesForParent[parent])
|
|
||||||
{
|
|
||||||
// Do we need files inside this file?
|
|
||||||
if (childrenForParent.TryGetValue(forParent, out var children))
|
|
||||||
{
|
|
||||||
if (files.TryGetValue(forParent, out var copies))
|
|
||||||
{
|
|
||||||
ASubStage subStage;
|
|
||||||
if (parent == top)
|
|
||||||
{
|
|
||||||
subStage = new NativeArchive(forParent.AbsoluteName, GetPlans(forParent));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
subStage = new SubStage(forParent.RelativeName, copies.First().dest, GetPlans(forParent));
|
|
||||||
}
|
|
||||||
yield return subStage;
|
|
||||||
foreach (var copy in copies)
|
|
||||||
{
|
|
||||||
yield return new DuplicateTo(subStage, copy.dest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (parent == top)
|
|
||||||
{
|
|
||||||
yield return new NativeArchive(forParent.AbsoluteName, GetPlans(forParent));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
yield return new TempSubStage(forParent.RelativeName, GetPlans(forParent));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// If not then we need to copy this file around
|
|
||||||
var copies = files[forParent];
|
|
||||||
var firstCopy = new CopyTo(copies.Key.Name, copies.First().dest);
|
|
||||||
yield return firstCopy;
|
|
||||||
|
|
||||||
foreach (var duplicate in copies.Skip(1))
|
|
||||||
{
|
|
||||||
yield return new DuplicateTo(firstCopy, duplicate.dest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetPlans(top);
|
|
||||||
}
|
|
||||||
public static async ValueTask ExecutePlan(WorkQueue queue, Func<ValueTask<Stream>> src, IEnumerable<IStagingPlan> plans)
|
|
||||||
{
|
|
||||||
// Extract these files
|
|
||||||
|
|
||||||
await using var stream = await src();
|
|
||||||
var sig = await FileExtractor.ArchiveSigs.MatchesAsync(stream);
|
|
||||||
stream.Position = 0;
|
|
||||||
|
|
||||||
switch (sig)
|
|
||||||
{
|
|
||||||
case Definitions.FileType.ZIP:
|
|
||||||
await ExtractWith7Zip((Definitions.FileType)sig, stream, plans);
|
|
||||||
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Exception($"Invalid archive for extraction");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Copy around the duplicates
|
|
||||||
foreach (var file in plans.OfType<DuplicateTo>())
|
|
||||||
{
|
|
||||||
await file.Execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the sub-stages
|
|
||||||
foreach (var subStage in plans.OfType<ISubStage>())
|
|
||||||
{
|
|
||||||
await subStage.Execute(queue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispose of all plans
|
|
||||||
foreach (var file in plans)
|
|
||||||
{
|
|
||||||
await file.DisposeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async ValueTask ExtractWith7Zip(Definitions.FileType sig, Stream stream, IEnumerable<IStagingPlan> plans)
|
|
||||||
{
|
|
||||||
using var archive = await ArchiveFile.Open(stream, sig);
|
|
||||||
|
|
||||||
void HandleFile(RelativePath path, IStagingPlan src, Func<Stream> sf)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var extractor = new TypedExtractor<IStagingPlan>(plans.OfType<IStagingSrc>().ToDictionary(s => s.Source.FileName, s => (IStagingPlan)s),
|
|
||||||
HandleFile);
|
|
||||||
extractor.Extract(archive);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async Task ExecutePlans(WorkQueue queue, IEnumerable<IStagingPlan> plans)
|
|
||||||
{
|
|
||||||
foreach (var file in plans.OfType<NativeArchive>())
|
|
||||||
{
|
|
||||||
await file.Execute(queue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public interface IStagingSrc : IStagingPlan
|
|
||||||
{
|
|
||||||
public IPath Source { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public interface ISubStage : IStagingSrc
|
|
||||||
{
|
|
||||||
Task Execute(WorkQueue queue);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public class NativeArchive : ASubStage
|
|
||||||
{
|
|
||||||
public NativeArchive(AbsolutePath src, IEnumerable<IStagingPlan> plans) : base(plans)
|
|
||||||
{
|
|
||||||
Source = src;
|
|
||||||
Destination = src;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public override AbsolutePath Destination { get; }
|
|
||||||
public override IPath Source { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public class SubStage : ASubStage
|
|
||||||
{
|
|
||||||
public SubStage(RelativePath src, AbsolutePath destination, IEnumerable<IStagingPlan> plans) : base(plans)
|
|
||||||
{
|
|
||||||
Source = src;
|
|
||||||
Destination = destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
public override AbsolutePath Destination { get; }
|
|
||||||
public override IPath Source { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Wabbajack.Common;
|
|
||||||
|
|
||||||
namespace Wabbajack.VirtualFileSystem.StagingPlan
|
|
||||||
{
|
|
||||||
public class TempSubStage : ASubStage
|
|
||||||
{
|
|
||||||
private readonly TempFile _temp;
|
|
||||||
|
|
||||||
public TempSubStage(RelativePath src, IEnumerable<IStagingPlan> plans) : base(plans)
|
|
||||||
{
|
|
||||||
Source = src;
|
|
||||||
_temp = new TempFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
await _temp.DisposeAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override AbsolutePath Destination => _temp.Path;
|
|
||||||
public override IPath Source { get; }
|
|
||||||
}
|
|
||||||
}
|
|
@ -141,7 +141,7 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
itm.ThisAndAllChildrenReduced(fn);
|
itm.ThisAndAllChildrenReduced(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static VirtualFile ConvertFromIndexedFile(Context context, IndexedVirtualFile file, IPath path, VirtualFile vparent, IExtractedFile extractedFile)
|
private static VirtualFile ConvertFromIndexedFile(Context context, IndexedVirtualFile file, IPath path, VirtualFile vparent, IStreamFactory extractedFile)
|
||||||
{
|
{
|
||||||
var vself = new VirtualFile
|
var vself = new VirtualFile
|
||||||
{
|
{
|
||||||
@ -161,7 +161,7 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
return vself;
|
return vself;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGetFromCache(Context context, VirtualFile parent, IPath path, IExtractedFile extractedFile, Hash hash, out VirtualFile found)
|
private static bool TryGetFromCache(Context context, VirtualFile parent, IPath path, IStreamFactory extractedFile, Hash hash, out VirtualFile found)
|
||||||
{
|
{
|
||||||
var result = _vfsCache.Get(hash.ToArray());
|
var result = _vfsCache.Get(hash.ToArray());
|
||||||
if (result == null)
|
if (result == null)
|
||||||
@ -199,11 +199,9 @@ namespace Wabbajack.VirtualFileSystem
|
|||||||
|
|
||||||
var sig = await FileExtractor2.ArchiveSigs.MatchesAsync(stream);
|
var sig = await FileExtractor2.ArchiveSigs.MatchesAsync(stream);
|
||||||
|
|
||||||
/* TODO
|
|
||||||
if (TryGetFromCache(context, parent, relPath, extractedFile, hash, out var vself))
|
if (TryGetFromCache(context, parent, relPath, extractedFile, hash, out var vself))
|
||||||
return vself;
|
return vself;
|
||||||
*/
|
|
||||||
|
|
||||||
var self = new VirtualFile
|
var self = new VirtualFile
|
||||||
{
|
{
|
||||||
Context = context,
|
Context = context,
|
||||||
|
Loading…
Reference in New Issue
Block a user