mirror of
https://github.com/wabbajack-tools/wabbajack.git
synced 2024-08-30 18:42:17 +00:00
working on BSA compression support
This commit is contained in:
parent
61862ae7dd
commit
e63e3be3b7
@ -1,409 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
using static BSA.Tools.libbsarch;
|
|
||||||
|
|
||||||
namespace BSA.Tools
|
|
||||||
{
|
|
||||||
// Represents a BSA archive on disk (in READ mode)
|
|
||||||
public class Archive : IDisposable
|
|
||||||
{
|
|
||||||
protected unsafe libbsarch.bsa_archive_t* _archive;
|
|
||||||
|
|
||||||
public UInt32 Version
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock(this) {
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return libbsarch.bsa_version_get(_archive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bsa_archive_type_t Type
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock(this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return libbsarch.bsa_archive_type_get(_archive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt32 FileCount
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return libbsarch.bsa_file_count_get(_archive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt32 ArchiveFlags
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return libbsarch.bsa_archive_flags_get(_archive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
libbsarch.bsa_archive_flags_set(_archive, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt32 FileFlags
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return libbsarch.bsa_file_flags_get(_archive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
libbsarch.bsa_file_flags_set(_archive, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Compress
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return libbsarch.bsa_compress_get(_archive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
libbsarch.bsa_compress_set(_archive, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ShareData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return libbsarch.bsa_share_data_get(_archive);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
set
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
libbsarch.bsa_share_data_set(_archive, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void Save()
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
check_err(libbsarch.bsa_save(_archive));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<ArchiveEntry> _entries = null;
|
|
||||||
public IEnumerable<ArchiveEntry> Entries {
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_entries != null)
|
|
||||||
return _entries;
|
|
||||||
|
|
||||||
return GetAndCacheEntries();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<ArchiveEntry> GetAndCacheEntries()
|
|
||||||
{
|
|
||||||
var entries = new List<ArchiveEntry>();
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
foreach (var filename in GetFileNames())
|
|
||||||
{
|
|
||||||
entries.Add(new ArchiveEntry(this, _archive, filename));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_entries = entries;
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Archive()
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
_archive = libbsarch.bsa_create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Create(string filename, bsa_archive_type_t type, EntryList entries)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
check_err(libbsarch.bsa_create_archive(_archive, filename, type, entries._list));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Archive(string filename)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
_archive = libbsarch.bsa_create();
|
|
||||||
check_err(libbsarch.bsa_load_from_file(_archive, filename));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void AddFile(string filename, byte[] data)
|
|
||||||
{
|
|
||||||
lock(this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
var ptr = Marshal.AllocHGlobal(data.Length);
|
|
||||||
Marshal.Copy(data, 0, ptr, data.Length);
|
|
||||||
libbsarch.bsa_add_file_from_memory(_archive, filename, (UInt32)data.Length, (byte*)ptr);
|
|
||||||
Marshal.FreeHGlobal(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
check_err(libbsarch.bsa_free(_archive));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void check_err(libbsarch.bsa_result_message_t bsa_result_message_t)
|
|
||||||
{
|
|
||||||
if (bsa_result_message_t.code != 0)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
for (i = 0; i < 1024 * 2; i += 2)
|
|
||||||
if (bsa_result_message_t.text[i] == 0) break;
|
|
||||||
|
|
||||||
var msg = new String((sbyte*)bsa_result_message_t.text, 0, i, Encoding.Unicode);
|
|
||||||
throw new Exception(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<string> GetFileNames()
|
|
||||||
{
|
|
||||||
List<string> filenames = new List<string>();
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
check_err(libbsarch.bsa_iterate_files(_archive, (archive, filename, file, folder, context) =>
|
|
||||||
{
|
|
||||||
lock (filenames)
|
|
||||||
{
|
|
||||||
filenames.Add(filename);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}, null));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filenames;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class ArchiveEntry
|
|
||||||
{
|
|
||||||
private Archive _archive;
|
|
||||||
private unsafe libbsarch.bsa_archive_t* _archivep;
|
|
||||||
private string _filename;
|
|
||||||
|
|
||||||
public string Filename {
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _filename;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe ArchiveEntry(Archive archive, libbsarch.bsa_archive_t* archivep, string filename)
|
|
||||||
{
|
|
||||||
_archive = archive;
|
|
||||||
_archivep = archivep;
|
|
||||||
_filename = filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FileData GetFileData()
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
var result = libbsarch.bsa_extract_file_data_by_filename(_archivep, _filename);
|
|
||||||
Archive.check_err(result.message);
|
|
||||||
return new FileData(_archive, _archivep, result.buffer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExtractTo(Stream stream)
|
|
||||||
{
|
|
||||||
using (var data = GetFileData())
|
|
||||||
{
|
|
||||||
data.WriteTo(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ExtractTo(string filename)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
libbsarch.bsa_extract_file(_archivep, _filename, filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class FileData : IDisposable
|
|
||||||
{
|
|
||||||
private Archive archive;
|
|
||||||
private unsafe libbsarch.bsa_archive_t* archivep;
|
|
||||||
private libbsarch.bsa_result_buffer_t result;
|
|
||||||
|
|
||||||
public unsafe FileData(Archive archive, libbsarch.bsa_archive_t* archivep, libbsarch.bsa_result_buffer_t result)
|
|
||||||
{
|
|
||||||
this.archive = archive;
|
|
||||||
this.archivep = archivep;
|
|
||||||
this.result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void WriteTo(Stream stream)
|
|
||||||
{
|
|
||||||
var memory = ToByteArray();
|
|
||||||
stream.Write(memory, 0, (int)result.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] ToByteArray()
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
byte[] memory = new byte[result.size];
|
|
||||||
Marshal.Copy((IntPtr)result.data, memory, 0, (int)result.size);
|
|
||||||
return memory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
lock(archive)
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
Archive.check_err(libbsarch.bsa_file_data_free(archivep, result));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class EntryList : IDisposable
|
|
||||||
{
|
|
||||||
public unsafe bsa_entry_list_t* _list;
|
|
||||||
|
|
||||||
public EntryList()
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
_list = libbsarch.bsa_entry_list_create();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public UInt32 Count
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
return libbsarch.bsa_entry_list_count(_list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(string entry)
|
|
||||||
{
|
|
||||||
lock(this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
libbsarch.bsa_entry_list_add(_list, entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
lock (this)
|
|
||||||
{
|
|
||||||
unsafe
|
|
||||||
{
|
|
||||||
libbsarch.bsa_entry_list_free(_list);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -31,6 +31,11 @@
|
|||||||
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||||
POSSIBILITY OF SUCH DAMAGE.
|
POSSIBILITY OF SUCH DAMAGE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Includes fixes from https://github.com/mendsley/bsdiff/pull/12/files
|
||||||
|
* translated from C to C# to prevent stack overflows.
|
||||||
|
*/
|
||||||
public class BSDiff
|
public class BSDiff
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -444,7 +449,23 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int x = v[I[start + len / 2] + h];
|
int y, z, j, k, x, tmp;
|
||||||
|
j = start + len / 2;
|
||||||
|
k = start + len - 1;
|
||||||
|
x = v[I[j] + h];
|
||||||
|
y = v[I[start] + h];
|
||||||
|
z = v[I[k] + h];
|
||||||
|
if (len > 40)
|
||||||
|
{ /* Big array: Pseudomedian of 9 */
|
||||||
|
tmp = len / 8;
|
||||||
|
x = median3(x, v[I[j - tmp] + h], v[I[j + tmp] + h]);
|
||||||
|
y = median3(y, v[I[start + tmp] + h], v[I[start + tmp + tmp] + h]);
|
||||||
|
z = median3(z, v[I[k - tmp] + h], v[I[k - tmp - tmp] + h]);
|
||||||
|
}; /* Else medium array: Pseudomedian of 3 */
|
||||||
|
x = median3(x, y, z);
|
||||||
|
|
||||||
|
//int x = v[I[start + len / 2] + h];
|
||||||
|
|
||||||
int jj = 0;
|
int jj = 0;
|
||||||
int kk = 0;
|
int kk = 0;
|
||||||
for (int i2 = start; i2 < start + len; i2++)
|
for (int i2 = start; i2 < start + len; i2++)
|
||||||
@ -458,8 +479,8 @@
|
|||||||
kk += jj;
|
kk += jj;
|
||||||
|
|
||||||
int i = start;
|
int i = start;
|
||||||
int j = 0;
|
j = 0;
|
||||||
int k = 0;
|
k = 0;
|
||||||
while (i < jj)
|
while (i < jj)
|
||||||
{
|
{
|
||||||
if (v[I[i] + h] < x)
|
if (v[I[i] + h] < x)
|
||||||
@ -652,5 +673,12 @@
|
|||||||
|
|
||||||
const long c_fileSignature = 0x3034464649445342L;
|
const long c_fileSignature = 0x3034464649445342L;
|
||||||
const int c_headerSize = 32;
|
const int c_headerSize = 32;
|
||||||
|
|
||||||
|
|
||||||
|
private static int median3(int a, int b, int c)
|
||||||
|
{
|
||||||
|
return (((a) < (b)) ? ((b) < (c) ? (b) : ((a) < (c) ? (c) : (a))) : ((b) > (c) ? (b) : ((a) > (c) ? (c) : (a))));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,10 @@ namespace Wabbajack.Common
|
|||||||
{
|
{
|
||||||
public static string GameFolderFilesDir = "Game Folder Files";
|
public static string GameFolderFilesDir = "Game Folder Files";
|
||||||
public static string ModPackMagic = "Celebration!, Cheese for Everyone!";
|
public static string ModPackMagic = "Celebration!, Cheese for Everyone!";
|
||||||
|
public static string BSACreationDir = "TEMP_BSA_FILES";
|
||||||
|
|
||||||
public static HashSet<string> SupportedArchives = new HashSet<string>() { ".zip", ".rar", ".7z", ".7zip" };
|
public static HashSet<string> SupportedArchives = new HashSet<string>() { ".zip", ".rar", ".7z", ".7zip" };
|
||||||
|
public static HashSet<string> SupportedBSAs = new HashSet<string>() { ".bsa", ".ba2" };
|
||||||
|
|
||||||
public static String UserAgent {
|
public static String UserAgent {
|
||||||
get
|
get
|
||||||
|
@ -20,6 +20,10 @@ namespace Wabbajack.Common
|
|||||||
_hash = AbsolutePath.FileSHA256();
|
_hash = AbsolutePath.FileSHA256();
|
||||||
return _hash;
|
return _hash;
|
||||||
}
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_hash = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public T EvolveTo<T>() where T : Directive, new()
|
public T EvolveTo<T>() where T : Directive, new()
|
||||||
@ -94,6 +98,17 @@ namespace Wabbajack.Common
|
|||||||
public string From;
|
public string From;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class CreateBSA : Directive
|
||||||
|
{
|
||||||
|
public string TempID;
|
||||||
|
public string IsCompressed;
|
||||||
|
public uint Version;
|
||||||
|
public Int32 Type;
|
||||||
|
|
||||||
|
public uint FileFlags { get; set; }
|
||||||
|
public bool Compress { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class PatchedFromArchive : FromArchive
|
public class PatchedFromArchive : FromArchive
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -113,7 +128,6 @@ namespace Wabbajack.Common
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name;
|
public string Name;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Meta INI for the downloaded archive
|
/// Meta INI for the downloaded archive
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Meta;
|
public string Meta;
|
||||||
@ -126,6 +140,11 @@ namespace Wabbajack.Common
|
|||||||
public string FileID;
|
public string FileID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class GoogleDriveMod : Archive
|
||||||
|
{
|
||||||
|
public string Id;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// URL that can be downloaded directly without any additional options
|
/// URL that can be downloaded directly without any additional options
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -34,6 +34,12 @@ namespace Wabbajack.Common
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string SHA256(this byte[] data)
|
||||||
|
{
|
||||||
|
return new SHA256Managed().ComputeHash(data).ToBase64();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a Base64 encoding of these bytes
|
/// Returns a Base64 encoding of these bytes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -167,6 +173,8 @@ namespace Wabbajack.Common
|
|||||||
return tasks.Select(t =>
|
return tasks.Select(t =>
|
||||||
{
|
{
|
||||||
t.Wait();
|
t.Wait();
|
||||||
|
if (t.IsFaulted)
|
||||||
|
throw t.Exception;
|
||||||
return t.Result;
|
return t.Result;
|
||||||
}).ToList();
|
}).ToList();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="utf-8" ?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<configuration>
|
<configuration>
|
||||||
<startup>
|
<startup>
|
||||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||||
</startup>
|
</startup>
|
||||||
|
<runtime>
|
||||||
|
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||||
|
<dependentAssembly>
|
||||||
|
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||||
|
<bindingRedirect oldVersion="0.0.0.0-12.0.0.0" newVersion="12.0.0.0" />
|
||||||
|
</dependentAssembly>
|
||||||
|
</assemblyBinding>
|
||||||
|
</runtime>
|
||||||
</configuration>
|
</configuration>
|
@ -1,6 +1,8 @@
|
|||||||
using Newtonsoft.Json;
|
using BSA.Tools;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using SevenZipExtractor;
|
using SevenZipExtractor;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -49,6 +51,7 @@ namespace Wabbajack
|
|||||||
public List<Archive> SelectedArchives { get; private set; }
|
public List<Archive> SelectedArchives { get; private set; }
|
||||||
public List<RawSourceFile> AllFiles { get; private set; }
|
public List<RawSourceFile> AllFiles { get; private set; }
|
||||||
public ModList ModList { get; private set; }
|
public ModList ModList { get; private set; }
|
||||||
|
public ConcurrentBag<Directive> ExtraFiles { get; private set; }
|
||||||
|
|
||||||
public List<IndexedArchive> IndexedArchives;
|
public List<IndexedArchive> IndexedArchives;
|
||||||
|
|
||||||
@ -159,15 +162,23 @@ namespace Wabbajack
|
|||||||
|
|
||||||
Info("Found {0} files to build into mod list", AllFiles.Count);
|
Info("Found {0} files to build into mod list", AllFiles.Count);
|
||||||
|
|
||||||
|
ExtraFiles = new ConcurrentBag<Directive>();
|
||||||
|
|
||||||
var stack = MakeStack();
|
var stack = MakeStack();
|
||||||
|
|
||||||
Info("Running Compilation Stack");
|
Info("Running Compilation Stack");
|
||||||
var results = AllFiles.PMap(f => RunStack(stack, f)).ToList();
|
var results = AllFiles.PMap(f => RunStack(stack, f)).ToList();
|
||||||
|
|
||||||
|
// Add the extra files that were generated by the stack
|
||||||
|
Info($"Adding {ExtraFiles.Count} that were generated by the stack");
|
||||||
|
results = results.Concat(ExtraFiles).ToList();
|
||||||
|
|
||||||
var nomatch = results.OfType<NoMatch>();
|
var nomatch = results.OfType<NoMatch>();
|
||||||
Info("No match for {0} files", nomatch.Count());
|
Info("No match for {0} files", nomatch.Count());
|
||||||
foreach (var file in nomatch)
|
foreach (var file in nomatch)
|
||||||
Info(" {0}", file.To);
|
Info(" {0}", file.To);
|
||||||
|
if (nomatch.Count() > 0)
|
||||||
|
Error("Exiting due to no way to compile these files");
|
||||||
|
|
||||||
InstallDirectives = results.Where(i => !(i is IgnoredDirectly)).ToList();
|
InstallDirectives = results.Where(i => !(i is IgnoredDirectly)).ToList();
|
||||||
|
|
||||||
@ -196,6 +207,7 @@ namespace Wabbajack
|
|||||||
IndexedArchives = null;
|
IndexedArchives = null;
|
||||||
InstallDirectives = null;
|
InstallDirectives = null;
|
||||||
SelectedArchives = null;
|
SelectedArchives = null;
|
||||||
|
ExtraFiles = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -224,7 +236,7 @@ namespace Wabbajack
|
|||||||
var archive = IndexedArchives.First(a => a.Hash == archive_sha);
|
var archive = IndexedArchives.First(a => a.Hash == archive_sha);
|
||||||
var paths = group.Select(g => g.From).ToHashSet();
|
var paths = group.Select(g => g.From).ToHashSet();
|
||||||
var streams = new Dictionary<string, MemoryStream>();
|
var streams = new Dictionary<string, MemoryStream>();
|
||||||
Status("Etracting Patch Files from {0}", archive.Name);
|
Status($"Extracting {paths.Count} patch files from {archive.Name}");
|
||||||
// First we fetch the source files from the input archive
|
// First we fetch the source files from the input archive
|
||||||
using (var a = new ArchiveFile(archive.AbsolutePath))
|
using (var a = new ArchiveFile(archive.AbsolutePath))
|
||||||
{
|
{
|
||||||
@ -246,11 +258,10 @@ namespace Wabbajack
|
|||||||
Info("Patching {0}", entry.To);
|
Info("Patching {0}", entry.To);
|
||||||
var ss = extracted[entry.From];
|
var ss = extracted[entry.From];
|
||||||
using (var origin = new MemoryStream(ss))
|
using (var origin = new MemoryStream(ss))
|
||||||
using (var dest = File.OpenRead(absolute_paths[entry.To]))
|
|
||||||
using (var output = new MemoryStream())
|
using (var output = new MemoryStream())
|
||||||
{
|
{
|
||||||
var a = origin.ReadAll();
|
var a = origin.ReadAll();
|
||||||
var b = dest.ReadAll();
|
var b = LoadDataForTo(entry.To, absolute_paths);
|
||||||
BSDiff.Create(a, b, output);
|
BSDiff.Create(a, b, output);
|
||||||
entry.Patch = output.ToArray().ToBase64();
|
entry.Patch = output.ToArray().ToBase64();
|
||||||
}
|
}
|
||||||
@ -258,6 +269,30 @@ namespace Wabbajack
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private byte[] LoadDataForTo(string to, Dictionary<string, string> absolute_paths)
|
||||||
|
{
|
||||||
|
if (absolute_paths.TryGetValue(to, out var absolute))
|
||||||
|
return File.ReadAllBytes(absolute);
|
||||||
|
|
||||||
|
if (to.StartsWith(Consts.BSACreationDir))
|
||||||
|
{
|
||||||
|
var bsa_id = to.Split('\\')[1];
|
||||||
|
var bsa = InstallDirectives.OfType<CreateBSA>().First(b => b.TempID == bsa_id);
|
||||||
|
|
||||||
|
using (var a = new BSAFile(Path.Combine(MO2Folder, bsa.To)))
|
||||||
|
{
|
||||||
|
var file = a.Entries.First(e => e.Filename == Path.Combine(to.Split('\\').Skip(2).ToArray()));
|
||||||
|
using (var data = file.GetFileData())
|
||||||
|
{
|
||||||
|
return data.ToByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Error($"Couldn't load data for {to}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void GatherArchives()
|
private void GatherArchives()
|
||||||
{
|
{
|
||||||
var archives = IndexedArchives.GroupBy(a => a.Hash).ToDictionary(k => k.Key, k => k.First());
|
var archives = IndexedArchives.GroupBy(a => a.Hash).ToDictionary(k => k.Key, k => k.First());
|
||||||
@ -291,6 +326,15 @@ namespace Wabbajack
|
|||||||
ModID = general.modID
|
ModID = general.modID
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
else if (general.directURL != null && general.directURL.StartsWith("https://drive.google.com"))
|
||||||
|
{
|
||||||
|
var regex = new Regex("((?<=id=)[a-zA-Z0-9_-]*)|(?<=\\/file\\/d\\/)[a-zA-Z0-9_-]*");
|
||||||
|
var match = regex.Match(general.directURL);
|
||||||
|
result = new GoogleDriveMod()
|
||||||
|
{
|
||||||
|
Id = match.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
else if (general.directURL != null && general.directURL.StartsWith("https://www.dropbox.com/"))
|
else if (general.directURL != null && general.directURL.StartsWith("https://www.dropbox.com/"))
|
||||||
{
|
{
|
||||||
var uri = new UriBuilder((string)general.directURL);
|
var uri = new UriBuilder((string)general.directURL);
|
||||||
@ -363,6 +407,7 @@ namespace Wabbajack
|
|||||||
IgnoreStartsWith("logs\\"),
|
IgnoreStartsWith("logs\\"),
|
||||||
IgnoreStartsWith("downloads\\"),
|
IgnoreStartsWith("downloads\\"),
|
||||||
IgnoreStartsWith("webcache\\"),
|
IgnoreStartsWith("webcache\\"),
|
||||||
|
IgnoreStartsWith("overwrite\\"),
|
||||||
IgnoreEndsWith(".pyc"),
|
IgnoreEndsWith(".pyc"),
|
||||||
IgnoreOtherProfiles(),
|
IgnoreOtherProfiles(),
|
||||||
IgnoreDisabledMods(),
|
IgnoreDisabledMods(),
|
||||||
@ -377,12 +422,108 @@ namespace Wabbajack
|
|||||||
DirectMatch(),
|
DirectMatch(),
|
||||||
IncludePatches(),
|
IncludePatches(),
|
||||||
|
|
||||||
|
DeconstructBSAs(),
|
||||||
|
|
||||||
// If we have no match at this point for a game folder file, skip them, we can't do anything about them
|
// If we have no match at this point for a game folder file, skip them, we can't do anything about them
|
||||||
IgnoreGameFiles(),
|
IgnoreGameFiles(),
|
||||||
|
|
||||||
|
// There are some types of files that will error the compilation, because tehy're created on-the-fly via tools
|
||||||
|
// so if we don't have a match by this point, just drop them.
|
||||||
|
IgnoreEndsWith(".ini"),
|
||||||
|
IgnoreEndsWith(".html"),
|
||||||
|
IgnoreEndsWith(".txt"),
|
||||||
|
// Don't know why, but this seems to get copied around a bit
|
||||||
|
IgnoreEndsWith("HavokBehaviorPostProcess.exe"),
|
||||||
DropAll()
|
DropAll()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This function will search for a way to create a BSA in the installed mod list by assembling it from files
|
||||||
|
/// found in archives. To do this we hash all the files in side the BSA then try to find matches and patches for
|
||||||
|
/// all of the files.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private Func<RawSourceFile, Directive> DeconstructBSAs()
|
||||||
|
{
|
||||||
|
var microstack = new List<Func<RawSourceFile, Directive>>()
|
||||||
|
{
|
||||||
|
DirectMatch(),
|
||||||
|
IncludePatches(),
|
||||||
|
DropAll()
|
||||||
|
};
|
||||||
|
|
||||||
|
return source =>
|
||||||
|
{
|
||||||
|
if (!Consts.SupportedBSAs.Contains(Path.GetExtension(source.Path))) return null;
|
||||||
|
|
||||||
|
var hashed = HashBSA(source.AbsolutePath);
|
||||||
|
|
||||||
|
var source_files = hashed.Select(e => new RawSourceFile() {
|
||||||
|
Hash = e.Item2,
|
||||||
|
Path = e.Item1,
|
||||||
|
AbsolutePath = e.Item1
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var matches = source_files.Select(e => RunStack(microstack, e));
|
||||||
|
|
||||||
|
var id = Guid.NewGuid().ToString();
|
||||||
|
|
||||||
|
foreach (var match in matches)
|
||||||
|
{
|
||||||
|
if (match is IgnoredDirectly)
|
||||||
|
{
|
||||||
|
Error($"File required for BSA creation doesn't exist: {match.To}");
|
||||||
|
}
|
||||||
|
match.To = Path.Combine(Consts.BSACreationDir, id, match.To);
|
||||||
|
ExtraFiles.Add(match);
|
||||||
|
};
|
||||||
|
|
||||||
|
CreateBSA directive;
|
||||||
|
using (var bsa = new BSAFile(source.AbsolutePath))
|
||||||
|
{
|
||||||
|
directive = new CreateBSA()
|
||||||
|
{
|
||||||
|
To = source.Path,
|
||||||
|
TempID = id,
|
||||||
|
Version = bsa.Version,
|
||||||
|
Type = (int)bsa.Type,
|
||||||
|
FileFlags = bsa.FileFlags,
|
||||||
|
Compress = bsa.Compress
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return directive;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given a BSA on disk, index it and return a dictionary of SHA256 -> filename
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="absolutePath"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private List<(string, string)> HashBSA(string absolutePath)
|
||||||
|
{
|
||||||
|
Status($"Hashing BSA: {absolutePath}");
|
||||||
|
var results = new List<(string, string)>();
|
||||||
|
using (var a = new BSAFile(absolutePath))
|
||||||
|
{
|
||||||
|
foreach (var entry in a.Entries)
|
||||||
|
{
|
||||||
|
Status($"Hashing BSA: {absolutePath} - {entry.Filename}");
|
||||||
|
|
||||||
|
using (var data = entry.GetFileData())
|
||||||
|
{
|
||||||
|
results.Add((entry.Filename, data.ToByteArray().SHA256()));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
private Func<RawSourceFile, Directive> IgnoreDisabledMods()
|
private Func<RawSourceFile, Directive> IgnoreDisabledMods()
|
||||||
{
|
{
|
||||||
var disabled_mods = File.ReadAllLines(Path.Combine(MO2ProfileDir, "modlist.txt"))
|
var disabled_mods = File.ReadAllLines(Path.Combine(MO2ProfileDir, "modlist.txt"))
|
||||||
@ -406,12 +547,12 @@ namespace Wabbajack
|
|||||||
var indexed = (from archive in IndexedArchives
|
var indexed = (from archive in IndexedArchives
|
||||||
from entry in archive.Entries
|
from entry in archive.Entries
|
||||||
select new { archive = archive, entry = entry })
|
select new { archive = archive, entry = entry })
|
||||||
.GroupBy(e => Path.GetFileName(e.entry.Path))
|
.GroupBy(e => Path.GetFileName(e.entry.Path).ToLower())
|
||||||
.ToDictionary(e => e.Key);
|
.ToDictionary(e => e.Key);
|
||||||
|
|
||||||
return source =>
|
return source =>
|
||||||
{
|
{
|
||||||
if (indexed.TryGetValue(Path.GetFileName(source.Path), out var value))
|
if (indexed.TryGetValue(Path.GetFileName(source.Path.ToLower()), out var value))
|
||||||
{
|
{
|
||||||
var found = value.First();
|
var found = value.First();
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ namespace Wabbajack
|
|||||||
public ModList ModList { get; }
|
public ModList ModList { get; }
|
||||||
public Action<string> Log_Fn { get; }
|
public Action<string> Log_Fn { get; }
|
||||||
public Dictionary<string, string> HashedArchives { get; private set; }
|
public Dictionary<string, string> HashedArchives { get; private set; }
|
||||||
|
|
||||||
public string NexusAPIKey { get; private set; }
|
public string NexusAPIKey { get; private set; }
|
||||||
|
|
||||||
public void Info(string msg, params object[] args)
|
public void Info(string msg, params object[] args)
|
||||||
@ -199,26 +200,37 @@ namespace Wabbajack
|
|||||||
{
|
{
|
||||||
missing.PMap(archive =>
|
missing.PMap(archive =>
|
||||||
{
|
{
|
||||||
if (archive is NexusMod)
|
switch (archive) {
|
||||||
{
|
case NexusMod a:
|
||||||
var url = NexusAPI.GetNexusDownloadLink(archive as NexusMod, NexusAPIKey);
|
var url = NexusAPI.GetNexusDownloadLink(a as NexusMod, NexusAPIKey);
|
||||||
DownloadURLDirect(archive, url);
|
DownloadURLDirect(archive, url);
|
||||||
}
|
break;
|
||||||
else if (archive is MODDBArchive)
|
case GoogleDriveMod a:
|
||||||
{
|
DownloadGoogleDriveArchive(a);
|
||||||
|
break;
|
||||||
|
case MODDBArchive a:
|
||||||
DownloadModDBArchive(archive, (archive as MODDBArchive).URL);
|
DownloadModDBArchive(archive, (archive as MODDBArchive).URL);
|
||||||
}
|
break;
|
||||||
else if (archive is DirectURLArchive)
|
case DirectURLArchive a:
|
||||||
{
|
|
||||||
DownloadURLDirect(archive, (archive as DirectURLArchive).URL);
|
DownloadURLDirect(archive, (archive as DirectURLArchive).URL);
|
||||||
}
|
break;
|
||||||
else
|
default:
|
||||||
{
|
break;
|
||||||
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DownloadGoogleDriveArchive(GoogleDriveMod a)
|
||||||
|
{
|
||||||
|
var initial_url = $"https://drive.google.com/uc?id={a.Id}&export=download";
|
||||||
|
var client = new HttpClient();
|
||||||
|
var result = client.GetStringSync(initial_url);
|
||||||
|
var regex = new Regex("(?<=/uc\\?export=download&confirm=).*(?=;id=)");
|
||||||
|
var confirm = regex.Match(result);
|
||||||
|
DownloadURLDirect(a, $"https://drive.google.com/uc?export=download&confirm={confirm}&id={a.Id}", client);
|
||||||
|
}
|
||||||
|
|
||||||
private void DownloadModDBArchive(Archive archive, string url)
|
private void DownloadModDBArchive(Archive archive, string url)
|
||||||
{
|
{
|
||||||
var client = new HttpClient();
|
var client = new HttpClient();
|
||||||
@ -228,10 +240,12 @@ namespace Wabbajack
|
|||||||
DownloadURLDirect(archive, match.Value);
|
DownloadURLDirect(archive, match.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DownloadURLDirect(Archive archive, string url)
|
private void DownloadURLDirect(Archive archive, string url, HttpClient client = null)
|
||||||
{
|
{
|
||||||
HttpClient client = new HttpClient();
|
if (client == null) {
|
||||||
|
client = new HttpClient();
|
||||||
client.DefaultRequestHeaders.Add("User-Agent", Consts.UserAgent);
|
client.DefaultRequestHeaders.Add("User-Agent", Consts.UserAgent);
|
||||||
|
}
|
||||||
long total_read = 0;
|
long total_read = 0;
|
||||||
int buffer_size = 1024 * 32;
|
int buffer_size = 1024 * 32;
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
@ -25,6 +26,7 @@ namespace Wabbajack
|
|||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
var context = new AppState(Dispatcher, "Building");
|
var context = new AppState(Dispatcher, "Building");
|
||||||
WorkQueue.Init((id, msg, progress) => context.SetProgress(id, msg, progress));
|
WorkQueue.Init((id, msg, progress) => context.SetProgress(id, msg, progress));
|
||||||
|
|
||||||
@ -32,12 +34,13 @@ namespace Wabbajack
|
|||||||
new Thread(() =>
|
new Thread(() =>
|
||||||
{
|
{
|
||||||
compiler.LoadArchives();
|
compiler.LoadArchives();
|
||||||
compiler.MO2Profile = "Basic Graphics and Fixes";
|
compiler.MO2Profile = "DEV"; //"Basic Graphics and Fixes";
|
||||||
compiler.Compile();
|
compiler.Compile();
|
||||||
|
|
||||||
compiler.ModList.ToJSON("C:\\tmp\\modpack.json");
|
compiler.ModList.ToJSON("C:\\tmp\\modpack.json");
|
||||||
|
var modlist = compiler.ModList;
|
||||||
var installer = new Installer(compiler.ModList, "c:\\tmp\\install\\", msg => context.LogMsg(msg));
|
compiler = null;
|
||||||
|
var installer = new Installer(modlist, "c:\\tmp\\install\\", msg => context.LogMsg(msg));
|
||||||
installer.Install();
|
installer.Install();
|
||||||
|
|
||||||
}).Start();
|
}).Start();
|
||||||
|
Loading…
Reference in New Issue
Block a user