Redux -> Wabbajack main code

This commit is contained in:
Timothy Baldridge 2021-09-27 06:42:46 -06:00
parent fed371a6f1
commit 2e6c756d6f
1169 changed files with 412328 additions and 68813 deletions

View File

@ -1,136 +0,0 @@
# To learn more about .editorconfig see https://aka.ms/editorconfigdocs
###############################
# Core EditorConfig Options #
###############################
# All files
[*]
indent_style = space
# Code files
[*.{cs,csx}]
indent_size = 4
insert_final_newline = true
charset = utf-8-bom
###############################
# .NET Coding Conventions #
###############################
[*.{cs}]
# Organize usings
dotnet_sort_system_directives_first = true
# this. preferences
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_readonly_field = true:suggestion
# Expression-level preferences
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_prefer_inferred_tuple_names = true:suggestion
dotnet_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent
###############################
# Naming Conventions #
###############################
# Style Definitions
dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Use PascalCase for constant fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.applicable_accessibilities = *
dotnet_naming_symbols.constant_fields.required_modifiers = const
###############################
# C# Coding Conventions #
###############################
[*.cs]
# var preferences
csharp_style_var_for_built_in_types = true:silent
csharp_style_var_when_type_is_apparent = true:silent
csharp_style_var_elsewhere = true:silent
# Expression-bodied members
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_throw_expression = true:suggestion
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Expression-level preferences
csharp_prefer_braces = true:silent
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_prefer_simple_default_expression = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
###############################
# C# Formatting Rules #
###############################
# New line preferences
csharp_new_line_before_open_brace = all
csharp_new_line_before_else = true
csharp_new_line_before_catch = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_case_contents = true
csharp_indent_switch_labels = true
csharp_indent_labels = flush_left
# Space preferences
csharp_space_after_cast = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_around_binary_operators = before_and_after
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
# Wrapping preferences
csharp_preserve_single_line_statements = true
csharp_preserve_single_line_blocks = true
###############################
# C# Async Rules #
###############################
# CS4014: Task not awaited
dotnet_diagnostic.CS4014.severity = error
# CS1998: Async function does not contain await
dotnet_diagnostic.CS1998.severity = silent
###############################
# Other #
###############################
# CS1591: Missing XML comment for publicly visible type or member
dotnet_diagnostic.CS1591.severity = silent
dotnet_diagnostic.CS1701.severity = silent
dotnet_diagnostic.CS1702.severity = silent

107
.github/workflows/tests.yaml vendored Normal file
View File

@ -0,0 +1,107 @@
name: Main Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
env:
VERSION: 3.0.0.0-alpha5
jobs:
build:
name: Test ${{ matrix.project }} (${{ matrix.os }})
runs-on: ${{ matrix.os }}
env:
NEXUS_API_KEY: ${{ secrets.NEXUS_API_KEY }}
NEXUS_LOGIN: ${{ secrets.NEXUS_LOGIN }}
VECTOR_PLEXUS: ${{ secrets.VECTOR_PLEXUS }}
LOVERS_LAB: ${{ secrets.LOVERS_LAB }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
project:
- Wabbajack.Compiler.Test
- Wabbajack.Compression.BSA.Test
- Wabbajack.Downloaders.Dispatcher.Test
- Wabbajack.DTOs.Test
- Wabbajack.FileExtractor.Test
- Wabbajack.Hashing.PHash.Test
- Wabbajack.Hashing.xxHash64.Test
- Wabbajack.Installer.Test
- Wabbajack.Networking.NexusApi.Test
- Wabbajack.Paths.Test
- Wabbajack.Paths.IO.Test
- Wabbajack.VFS.Test
steps:
- uses: actions/checkout@v2
- name: Set Permissions
if: runner.os != 'Windows'
run: chmod -R +x Wabbajack.FileExtractor/Extractors
- name: Setup .NET Core SDK 6.0.x
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
include-prerelease: true
- name: Install dependencies
run: dotnet restore ${{ matrix.project }}/${{ matrix.project }}.csproj
- name: Build
run: dotnet build ${{ matrix.project }}/${{ matrix.project }}.csproj --configuration Release --no-restore
- name: Test
run: dotnet test ${{ matrix.project }}/${{ matrix.project }}.csproj --no-restore
publish:
name: Publish ${{ matrix.project }}
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' }}
strategy:
matrix:
project:
- Wabbajack.Common
- Wabbajack.Compiler
- Wabbajack.Compression.BSA
- Wabbajack.Downloaders.Interfaces
- Wabbajack.Downloaders.GoogleDrive
- Wabbajack.Downloaders.Http
- Wabbajack.Downloaders.Nexus
- Wabbajack.Downloaders.WabbajackCDN
- Wabbajack.DTOs
- Wabbajack.FileExtractor
- Wabbajack.Hashing.PHash
- Wabbajack.Hashing.xxHash64
- Wabbajack.Installer
- Wabbajack.Networking.Http
- Wabbajack.Networking.Http.Interfaces
- Wabbajack.Networking.NexusApi
- Wabbajack.Networking.WabbajackClientApi
- Wabbajack.Paths
- Wabbajack.Paths.IO
- Wabbajack.RateLimiter
- Wabbajack.VFS
needs: build
steps:
- uses: actions/checkout@v2
- name: Setup .NET Core SDK 6.0.x
uses: actions/setup-dotnet@v1
with:
dotnet-version: '6.0.x'
include-prerelease: true
- name: Publish ${{ matrix.project}} NuGet
uses: brandedoutcast/publish-nuget@v2.5.5
with:
PROJECT_FILE_PATH: ${{ matrix.project }}/${{ matrix.project }}.csproj
VERSION_STATIC: ${{ env.VERSION }}
NUGET_KEY: ${{secrets.NUGET_KEY}}

378
.gitignore vendored
View File

@ -1,374 +1,4 @@
################################################################################ bin
# This .gitignore file was automatically created by Microsoft(R) Visual Studio. obj
################################################################################ .idea
*.user
/.vs/Wabbajack
/BSA.Tools/bin/Debug/netstandard2.0
/BSA.Tools/obj
/packages
/SevenZipExtractor/bin/Debug
/SevenZipExtractor/obj/Debug
/Wabbajack/bin/Debug
/Wabbajack/obj/Debug
/Wabbajack.Common/bin/Debug
/Wabbajack.Common/obj/Debug
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
/.idea
Compression.BSA/Compression.BSA.xml
Wabbajack.Common/Wabbajack.Common.xml
Wabbajack.VirtualFileSystem/Wabbajack.VirtualFileSystem.xml

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<MSTest>
<Parallelize>
<Workers>0</Workers>
<Scope>MethodLevel</Scope>
</Parallelize>
<DisableAppDomain>True</DisableAppDomain>
</MSTest>
</RunSettings>

12
.vscode/settings.json vendored
View File

@ -1,12 +0,0 @@
{
"cSpell.words": [
"Modlists",
"Morrowind",
"Patreon",
"READMEs",
"SKSE",
"Stardew",
"Witcher",
"halgari"
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 262 43" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-128.835,-90.7199)">
<g transform="matrix(1.03627,0,0,1.1194,0,-120.896)">
<g id="Letter" transform="matrix(1,0,0,1,-6.85674e-14,5.36)">
<g transform="matrix(2.12799,0,0,1.96995,130.765,184)">
<path d="M0,18.744L-3.026,0L-1.821,0L0.991,17.084L3.722,0.08L5.034,0.08L7.658,17.03L10.363,0L11.46,0L8.462,18.744L6.855,18.744L4.338,3.401L1.714,18.744L0,18.744Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.12799,0,0,1.96995,179.882,194.235)">
<path d="M0,8.327L-2.758,-3.803L-5.462,8.327L0,8.327ZM-6.587,13.549L-7.712,13.549L-3.615,-5.222L-1.821,-5.222L2.356,13.549L1.125,13.549L0.187,9.398L-5.65,9.398L-6.587,13.549Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.12799,0,0,1.96995,207.774,187.823)">
<path d="M0,14.862C0.455,14.318 0.683,13.51 0.683,12.439L0.683,10.752C0.683,9.556 0.415,8.708 -0.121,8.208C-0.656,7.708 -1.495,7.458 -2.637,7.458L-4.967,7.458L-4.967,15.679L-2.182,15.679C-1.183,15.679 -0.455,15.407 0,14.862M-0.415,5.704C0.084,5.285 0.335,4.504 0.335,3.361L0.335,2.156C0.335,1.139 0.138,0.389 -0.254,-0.093C-0.647,-0.575 -1.326,-0.816 -2.29,-0.816L-4.967,-0.816L-4.967,6.334L-2.879,6.334C-1.736,6.334 -0.915,6.124 -0.415,5.704M0.683,-0.95C1.272,-0.289 1.566,0.71 1.566,2.049L1.566,3.12C1.566,4.191 1.383,5.026 1.017,5.624C0.651,6.222 0.013,6.619 -0.897,6.816C0.977,7.19 1.914,8.529 1.914,10.832L1.914,12.465C1.914,13.858 1.58,14.929 0.91,15.679C0.241,16.428 -0.79,16.803 -2.182,16.803L-6.199,16.803L-6.199,-1.941L-2.263,-1.941C-0.888,-1.941 0.094,-1.61 0.683,-0.95" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.12799,0,0,1.96995,235.011,187.823)">
<path d="M0,14.862C0.455,14.318 0.683,13.51 0.683,12.439L0.683,10.752C0.683,9.556 0.415,8.708 -0.121,8.208C-0.656,7.708 -1.495,7.458 -2.638,7.458L-4.967,7.458L-4.967,15.679L-2.182,15.679C-1.183,15.679 -0.455,15.407 0,14.862M-0.415,5.704C0.085,5.285 0.335,4.504 0.335,3.361L0.335,2.156C0.335,1.139 0.138,0.389 -0.254,-0.093C-0.647,-0.575 -1.326,-0.816 -2.289,-0.816L-4.967,-0.816L-4.967,6.334L-2.879,6.334C-1.736,6.334 -0.915,6.124 -0.415,5.704M0.683,-0.95C1.272,-0.289 1.566,0.71 1.566,2.049L1.566,3.12C1.566,4.191 1.383,5.026 1.017,5.624C0.651,6.222 0.013,6.619 -0.897,6.816C0.977,7.19 1.914,8.529 1.914,10.832L1.914,12.465C1.914,13.858 1.58,14.929 0.91,15.679C0.241,16.428 -0.79,16.803 -2.182,16.803L-6.199,16.803L-6.199,-1.941L-2.263,-1.941C-0.888,-1.941 0.094,-1.61 0.683,-0.95" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.12799,0,0,1.96995,264.897,194.235)">
<path d="M0,8.327L-2.758,-3.803L-5.463,8.327L0,8.327ZM-6.587,13.549L-7.712,13.549L-3.615,-5.222L-1.821,-5.222L2.356,13.549L1.125,13.549L0.187,9.398L-5.65,9.398L-6.587,13.549Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.12799,0,0,1.96995,279.654,186.268)">
<path d="M0,16.468C0.357,16.468 0.616,16.459 0.777,16.441C1.687,16.37 2.356,16.071 2.785,15.544C3.213,15.018 3.427,14.192 3.427,13.067L3.427,-1.151L4.659,-1.151L4.659,12.987C4.659,14.558 4.338,15.705 3.695,16.428C3.053,17.151 2.124,17.539 0.91,17.593C0.785,17.61 0.58,17.619 0.295,17.619C0.027,17.619 -0.179,17.61 -0.321,17.593L-0.321,16.468L0,16.468Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.12799,0,0,1.96995,315.552,194.235)">
<path d="M0,8.327L-2.758,-3.803L-5.463,8.327L0,8.327ZM-6.587,13.549L-7.712,13.549L-3.615,-5.222L-1.821,-5.222L2.356,13.549L1.125,13.549L0.187,9.398L-5.65,9.398L-6.587,13.549Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.12799,0,0,1.96995,345.181,218.681)">
<path d="M0,-16.467C0.66,-15.601 0.991,-14.454 0.991,-13.026L0.991,-11.473L-0.187,-11.473L-0.187,-13.106C-0.187,-14.178 -0.415,-15.034 -0.87,-15.677C-1.325,-16.32 -2.044,-16.641 -3.026,-16.641C-4.008,-16.641 -4.726,-16.32 -5.181,-15.677C-5.637,-15.034 -5.864,-14.178 -5.864,-13.106L-5.864,-3.36C-5.864,-2.289 -5.637,-1.436 -5.181,-0.802C-4.726,-0.169 -4.008,0.148 -3.026,0.148C-2.044,0.148 -1.325,-0.169 -0.87,-0.802C-0.415,-1.436 -0.187,-2.289 -0.187,-3.36L-0.187,-5.582L0.991,-5.582L0.991,-3.44C0.991,-2.012 0.66,-0.865 0,0.001C-0.661,0.867 -1.678,1.299 -3.053,1.299C-4.427,1.299 -5.445,0.867 -6.105,0.001C-6.766,-0.865 -7.096,-2.012 -7.096,-3.44L-7.096,-13.026C-7.096,-14.454 -6.766,-15.601 -6.105,-16.467C-5.445,-17.333 -4.427,-17.766 -3.053,-17.766C-1.678,-17.766 -0.661,-17.333 0,-16.467" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
<g transform="matrix(2.12799,0,0,1.96995,359.882,197.347)">
<path d="M0,5.194L0,11.969L-1.232,11.969L-1.232,-6.775L0,-6.775L0,3.132L6.105,-6.775L7.39,-6.775L2.035,1.954L7.739,11.969L6.453,11.969L1.285,3.159L0,5.194Z" style="fill:rgb(30,64,119);fill-rule:nonzero;"/>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 5.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 76 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -1 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 1000 1000" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><rect id="Artboard1" x="0" y="0" width="1000" height="1000" style="fill:none;"/><g id="Artboard11" serif:id="Artboard1"><circle cx="500" cy="500" r="425" style="fill:#fff;fill-opacity:0;stroke:#fff;stroke-width:40px;"/><path d="M468.763,410.75l-58.013,0l89.25,-267.75l89.25,267.75l-58.013,0l0,178.5l58.013,0l-89.25,267.75l-89.25,-267.75l58.013,0l0,-178.5Z" style="fill:#fff;fill-opacity:0;stroke:#fff;stroke-width:40px;"/></g></svg>

Before

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 782 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

View File

@ -1,821 +0,0 @@
### Changelog
#### Version - 2.5.2.2 - 8/11/2021
* Hotfix for compilation errors with TW3 modlists
#### Version - 2.5.2.1 - 7/30/2021
* Update deps to solve Steam game issue
#### Version - 2.5.2.0 - 7/20/2021
* Added compilation step that allows for automatic uploading to CDN
* Added CLI command to upload MO2 mods to CDN (and add them to the MO2 downloads)
* Upped BSA Compression to MAX to hopefully solve some >2GB errors
* Launcher now creates a `wabbajack-cli.bat` file that points to the most recent version of the cli
#### Version - 2.5.1.5 - 7/17/2021
* HOTFIX - Don't attempt to analyze files that have .dds extensions but are not DDS files internally
#### Version - 2.5.1.4 - 7/17/2021
* Fix some broken VFS caching (that's existed for awhile)
* Enable perceptual hashing for files inside BSA file
#### Version - 2.5.1.3 - 7/15/2021
* Fix textconv issue caused by a bug in ProcessHelper
* Add version info to gallery view (thanks JanuarySnow)
#### Version - 2.5.1.2 - 7/11/2021
* Fix issue with LL file upgrading
* Update GameFinder library to latest version
* Switch texture recompression to use TextConv for better compatability
#### Version - 2.5.1.1 - 7/9/2021
* Fix a bug with INI path remapping, was using case sensitive replace
#### Version - 2.5.1.0 - 7/6/2021
* Drastically improve compilation times by the removal of several bugs in the compiler
#### Version - 2.5.0.9 - 7/4/2021
* Use DX11 GPUs for compression when possible
* Don't threadbomb the system by creating O(N*M) threads when compressing textures
* Warn users that are running on old builds of Windows 10 (10.0.18363.0 or older) that some things (like LL logins) may not work
* Re-enable Nexus downloads for archived/hidden mods
#### Version - 2.5.0.8 - 7/3/2021
* Fix broken LL attachments
* Detect expired IPS4 accounts and prompt users to re-login
#### Version - 2.5.0.7 - 6/30/2021
* Make IPS4 downloader threadsafe during logins
#### Version - 2.5.0.6 - 6/29/2021
* Add Logging for IPS4 scheme handler should help with debugging login issues
#### Version - 2.5.0.5 - 6/28/2021
* Fix another NPE on texture matching
* Better logging on bad calls to IPS4 sites
#### Version - 2.5.0.4 - 6/27/2021
* Fix for NPE in Texture analysis compiler step
* Implemented attachment support in IPS4 sites
* Fix JSON response error for IPS4Sites
#### Version - 2.5.0.3 - 6/26/2021
* HOTFIX: Fix "argument out of range" error when installing modlists
#### Version - 2.5.0.2 - 6/25/2021
* HOTFIX: Fix for Nexus files that are downloadable but some API endpoints are unreachable
#### Version - 2.5.0.1 - 6/25/2021
* HOTFIX: Fix NPE with LL downloader
#### Version - 2.5.0.0 - 6/24/2021
* LoversLab downloader switched to OAuth2
* VectorPlexus downloader switched to OAuth2
* Wabbajack can now detect and compile modlists that contain resized/recompressed textures without binary
patching the textures resulting in massively reduced sizes for `.wabbajack` files
#### Version - 2.4.4.5 - 6/12/2021
* HOTFIX: Fix a game location detection error caused by `\\` in game paths
#### Version - 2.4.4.4 - 6/9/2021
* Use Erri's Game finder library so we can centralize game detection
* Fix to a compilation error mis-detecting Skyrim folders
* Several other dependency updates
#### Version - 2.4.4.3 - 5/28/2021
* Several quality of life fixes with install paths, logging and null pointer errors
#### Version - 2.4.4.2 - 5/17/2021
* Modlists are now exported as X.wabbajack where X is the name chosen in the Compiler UI
* Added a new `Network Workaround` mode to the WJ settings panel. Enabling this will bypass Cloudflare and
a lot of caching/DNS layers. Give this a try if you're getting strange SSL errors instead of using a VPN
Should also help get around problems with Telekom and their interaction with Cloudflare.
#### Version - 2.4.4.1 - 5/1/2021
* HOTFIX: downgrade cefsharp to fix the in-app browser (fixes Nexus login issue)
#### Version - 2.4.4.0 - 4/30/2021
* Search by tags in the gallery view
* Moved tags to a place where they won't break the UI so much if we have a lot of them
* Added a modlist contents viewer (does not require downloading the modlist)
#### Version - 2.4.3.3 - 4/13/2021
* Default to a "Wabbajack" user agent when making HTTP calls
* Some niceties for Mod authors uploading to our CDN
* upgrade several external dependencies
#### Version - 2.4.3.2 - 4/1/2021
* Fix for crashing when WJ is installed in the root drive
* Vortex is now the only option for WJ modlists, down with MO2, purge the xenos, praise to the Emperor
* Slightly more stable GoogleDrive link verification
* Updated several dependency libraries
#### Version - 2.4.3.1 - 3/24/2021
* HOTFIX : Go back to the non-core version of CEF, .NET Core version was crashing
* Folders prefixed with `[NoDelete]` in the name will be ignored when WJ cleans a installed modlist
* Resolution detection and setting is now supported for `SSEDisplayTweaks.ini` and `Oblivion.ini`
#### Version - 2.4.3.0 - 3/20/2021
* CDN part uploads are now retried
* New compiler options for including saves/splashscreens
* Save the location of modlists when installing
* Update several deps that were still based on .NET Framework
#### Version - 2.4.2.7 - 3/11/2021
* Several fixes for working off the new CDN
* Better detect failures in the launcher
* The app now cleans up older versions (leaving a total of 2 previous versions)
* The app now updates the launcher
#### Version - 2.4.2.6 - 2/26/2021
* Cache Modlist images (based on the URL)
* Load Gallery images off the GUI thread improving UI performance
#### Version - 2.4.2.5 - 2/24/2021
* HOTFIX: Fix a O(n*m) performance bug in compilation
* Add support for Enderal SSE
#### Version - 2.4.2.4 - 2/23/2021
* Reworked GDrive downloader for better compatability
* Ignore .cache files for realz
* Add support for folder tagging in Native Compiler mode (thanks Luca!)
* Fixed a "file in use" bug with .BA2s during installation
* Several fixes for Origin game detection
#### Version - 2.4.2.3 - 2/9/2021
* Remove unused file watcher from ManualDownloader
* Lower minimum number of threads to 1
* Added support for Kerbal Space Program
* Add Origin support for DA:O
* Stop vaccuming the patch cache (resulting in overuse of resources on some system)
* Fix reading flags in comments for DeconstructBSAs
* Update the "Overwrite folder" text to explain that saves are *not* deleted
* .cache files are now ignored by the compiler
#### Version - 2.4.2.2 - 2/6/2021
* Better Origin game detection
* Don't check the download whitelist for files that are already downloaded
#### Version - 2.4.2.1 - 2/4/2021
* HOTFIX - fix for the download path sometimes being empty
* HOTFIX - fix for some drive types not being detected (e.g. RAID drives)
#### Version - 2.4.2.0 - 2/3/2021
* Rework the Nexus Manual Downloading process now with less jank
* Manual Nexus Downloading now explains why it's so painful, and how to fix it (get Premium)
* Manual Nexus Downloading no longer opens a ton of CEF processes
* Manual Nexus Downloading no longer prompts users to download files that don't exist
* Disabled CloudFlare DDOS mitigation for LoversLab downloading, the site is back to normal now
#### Version - 2.4.1.2 - 1/30/2021
* Don't install .meta files for files sourced from the game folder
* Fix bug MO2 archive name detection in .meta files (rare bug with FO4VR and other like games)
* Catch exceptions when ECS downloads manifest data
* Don't double-index game files in some situations (duplicate game names in config files)
* Update all deps
* Reduce memory usage of open files (may help with memory errors during BSA creation)
#### Version - 2.4.1.1 - 1/13/2021
* HOTFIX: Fix game file sources that don't have MO2 specific names
#### Version - 2.4.1.0 - 1/12/2021
* Fix errors with broken SQL DBs crashing the system
* Fix errors with bad SQL clean commands
* Warn when the user doesn't have enough swap space
* Better OS version detection
* Use case-insensitive comparisons in Game File Downloader's PrimaryKeyString
#### Version - 2.4.0.0 - 1/9/2021
* Wabbajack is now based on .NET 5.0 (does not require a runtime download by users)
* Origin is now supported as a game source
* Basic (mostly untested) support for Dragon Age : Origins, Dragon Age 2, and Dragon Age: Inquisition
* Replace RocksDB with SQLite should result in less process contention when running the UI and the CLI at the same time
* Fixed Regression with CloudFront IPS4 sites not requesting logins before installation
* Fixed regression that caused us to spam the Nexus with verify calls
* Further fixes for IPS4 sites
* Optimized download folder hashing by only hashing files that match a specific size (thanks Unnoen!)
#### Version - 2.3.6.2 - 12/31/2020
* HOTFIX: Also apply the IPS4 changes to LL Meta lookups
#### Version - 2.3.6.1 - 12/31/2020
* When IPS4 (e.g. LL) sites based on CEF fail to validate, they no longer hang the app
* If a IPS4 CEF site throws a 503, or 400 error, retry
* Clean out the cookies during IPS4 CEF downloads so that they don't cause 400 errors
* Limit the number of connections to IPS4 sites to 20 per minute (one per 6 seconds)
* If a site *does* timeout, throw a log of the CEF state into `CEFStates` for easier debugging by the WJ team
* Wrote a new CLI utility to stress test the Verification routines.
* Ignore files that have `\Edit Scripts\Export\` in their path
* Added info/support for GoG's version of Kingdom Come : Deliverance
#### Version - 2.3.6.0 - 12/29/2020
* Move the LoversLab downloader to a CEF based backed making it interact with CloudFlare a bit better
* Add support for No Man's Sky
#### Version - 2.3.5.1 - 12/23/2020
* HOTFIX : Recover from errors in the EGS location detector
#### Version - 2.3.5.0 - 12/16/2020
* Fix tesall.ru download support
* Implement MechWarrior 5 support as a native compiler game
* Make the title in the WJ gallery (in app) optional for games that want the title to be in the splash screen
* Worked a few kinks out of the native game compiler
#### Version - 2.3.4.3 - 12/6/2020
* Disable the back button during install/compilation
#### Version - 2.3.4.2 - 11/24/2020
* Add Support for Kingdom Come : Deliverance (via MO2)
* Several other small bug fixes and deps updates
#### Version - 2.3.4.1 - 11/15/2020
* Tell the mod updater to use the existing Nexus Client instead of creating a new one
#### Version - 2.3.4.0 - 11/15/2020
* Removed the internal Manifest Viewer, you can still view the Manifest of any Modlist on the website
* Improved Nexus warnings about being low on API calls
* Added marker for "utility modlists" we will expand on this feature further in later releases
#### Version - 2.3.3.0 - 11/5/2020
* Game file hashes are now stored on Github instead of on the build server
* Added CLI Verb to produce these hash files for the Github repo
* When a user runs out of Nexus API calls we no longer bombard the Nexus with download attempts
* Check API limits before attempting a modlist download
* Logger is less chatty about recoverable download errors
* Display integer progress values during install so users know how far along in the process they are #issue-1156
#### Version - 2.3.2.0 - 11/2/2020
* 7Zip errors now re-hash the extracted file to check for file corruption issues. Should provide
better feedback in cases that a file is modified after being downloaded (perhaps by a disk failure)
* Fixed a file extraction issue with nested archives, most often seen whith the `Lucian` mod
* Fixed several small bugs and typeos with how open permission mirrored files are handled by Wabbajack
* Fixed a bug in the `download-url` cli command. It can now download from any Wabbajack CDN domain.
#### Version - 2.3.1.0 - 10/24/2020
* Fixed a long standing issue with path remapping, lots of edge cases were resolved here
* Implemented a basic compiler for non MO2 games, will expand as we get feedback
* Removed unused CPU percentage slider, we now have two settings, so please use the settings panel instead
#### Version - 2.3.0.4 - 10/13/2020
* Fix FOMOD extraction (for FNV)
#### Version - 2.3.0.3 - 10/12/2020
* Revert some 7zip changes due to 7zip crashing the app
#### Version - 2.3.0.2 - 10/5/2020
* Fixed a situation where 7zip would refuse to extract very large archives
#### Version - 2.3.0.1 - 10/4/2020
* Rewrote the file extraction routines. New code uses less memory, less disk space, and performs less thrashing on HDDs
* Reworked IPS4 integration to reduce download failures
* Profiles can now contain an (optional) file `compiler_settings.json` that includes options for other games to be used during install.
This is now the only way to include extra games in the install process, implicit inclusion has been removed.
* Number of download/install threads is now manually set (defaults to CPU cores) and is independently configurable
* Includes a "Favor performance over RAM" optional mode (defaults to off) that will use excessive amounts of RAM in exchange
for almost 1GB/sec install speed on the correct hardware. Don't enable this unless you have a fast SSD and at least 2.5GB of RAM for every
install thread.
* Fixed Extraction so that zip files no longer cause WJ to CTD
* Better path logging during install and compilation
* Fix the "this was created with a newer version of Wabbajack" issue
* If a downloaded file doesn't match the expected hash, try alternative download locations, if allowed
#### Version - 2.2.2.0 - 8/31/2020
* Route CDN requests through a reverse proxy to improve reliability
#### Version - 2.2.1.7 - 8/25/2020
* HOTFIX - Fix 404 errors with mirrors
#### Version - 2.2.1.6 - 8/24/2020
* HOTFIX - Make LoversLab auto-update work again
* HOTFIX - Fix for "End of Stream before End of Limit" symptom on BSA extraction
#### Version - 2.2.1.5 - 8/21/2020
* HOTFIX - Fix for broken patching in RGE and other lists
#### Version - 2.2.1.4 - 8/21/2020
* HOTFIX - No really...stop doing that
#### Version - 2.2.1.3 - 8/20/2020
* HOTFIX - We broke installation of existing lists...let's stop doing that
#### Version - 2.2.1.2 - 8/20/2020
* Added `WABBAJACK_ALWAYS_DISABLE` flag (see Readme for more info)
* Modlist can't be installed if the current Wabbajack Version is smaller than the Version used during Compilation of the Modlist
* Updates to use the latest version of the Wabbajack Common libs
* Don't require more than one game to be installed, unless absolutely required (this was a compiler bug)
#### Version - 2.2.0.0 - 8/7/2020
* Can now use NTFS XPRESS16 compression to reduce install sizes (optional in the settings panel)
* Better valid directory detection during install
* Prime the Hash cache during install so that we don't have to re-hash during a modlist update
* Better detection and handling of midden files
* Reworked the installer to use less temporary storage during install, keeps fewer archives open at once
* Launcher now passes arguments to the main Wabbajack.exe application
#### Version - 2.1.3.4 - 7/28/2020
* Fixes for Tar Files (for realz this time)
* Watch disk usage, throw an error if disk usage gets too high
* Added error icon triangle under install play button if there are blocking problems.
* Added tooltip styling to limit width to 500.
* Adjusted error text for MO2Installer unexpected files.
* Added filepicker error glow
* Disable WASM in the in-app browser so we can log into the Nexus again
#### Version - 2.1.3.3 - 7/22/2020
* Relax the RAR signature so it works with RAR 5 and RAR 4 formats
#### Version - 2.1.3.2 - 7/21/2020
* Fixed regression with 7zip and bad archive files (.tar files with Nemesis)
#### Version - 2.1.3.1 - 7/20/2020
* Fixed Mediafire Downloader not handling direct links
* Support for backup mirrors when a given CDN edge node isn't available
* Several help message improvements
* List ingestion now supports compression and processes on a background threaded
* Support for validation of unlisted modlists
* Abort installation/compilation on 7zip read errors
#### Version - 2.1.3.0 - 7/16/2020
* Filters from the FilePicker are now being used
* Wabbajack will continue working even if the build server is down
* Fixed an issue where the main window does not appear after the splash screen
* Patched executable files (dlls, exes, etc.) are now virus scanned both during compilation and install
* Fixed a VFS cache load issue with compilation
#### Version - 2.1.2.0 - 7/13/2020
* Can heal hand selected MEGA files
* Several backend fixes
* Reworked the ChangeDownload CLI command
* Fix for a VFS cache error when compiling lists that extract BSAs.
#### Version - 2.1.1.0 - 7/10/2020
* New CLI option for clearing nexus cache entries (authors only)
* Retry failed Move commands
* Don't re-hash files during compilation
* Can extract BSAs via wabbajack-cli.exe
#### Version - 2.1.0.2 - 7/7/2020
* Don't scan the game folder during compilation. If you are compiling and want `Game Folder Files` create it in your MO2 folder and manually place files into it
* Don't throw a hard error on a post-patch hash failure.
* Don't save the VFS cache to disk or load it during compilation. We have other caches that make this mostly worthless
#### Version - 2.1.0.1 - 7/6/2020
* Don't include saves in .wabbajack files
* Don't delete saves from any MO2 profile during installation
* Don't try to hash .wabbajack files *in the middle of downloading them*
* Print the PrimaryKeyString in logs when an archive is missing (in case the archive name is blank)
#### Version - 2.1.0.0 - 7/2/2020
* Game files are available as downloads automatically during compilation/installation
* Game files are patched/copied/used in BSA creation automatically
* CleanedESM support removed from the compiler stack (still usable during installation for backwards compatibility)
* VR games automatically pull from base games if they are required and are installed during compilation
* New `wabbajack-cli.exe` command `inlined-file-report` which will print statistics on the patches/included files in a `.wabbajack` file
#### Version - 2.0.9.4 - 6/16/2020
* Improve interactions between server and client code
#### Version - 2.0.9.3 - 6/14/2020
* Retry 503s not all 500s
#### Version - 2.0.9.2 - 6/13/2020
* Several bug fixes
* Added Darkest Dungeon as a game to alpha test non Bethesda MO2 game integration
#### Version - 2.0.9.1 - 6/8/2020
* Add crash handling and crash logging to the application startup
* Use string distance comparisons to find Nexus mod upgrades
#### Version - 2.0.9.0 - 6/5/2020
* Added support for Fallout 4 VR
#### Version - 2.0.8.0 - 6/2/2020
* Make sure the MEGA client is logged out before logging in (#881)
* Removed final references to `nexus_link_cache` (#865)
* Run disk benchmarks for 10 seconds and use a tiered approach to disk queue size calculation of disks
* Move the patch cache into RocksDB to get rid of the occasional race conditions while creating patches
* Added downloader support for tesall.ru
* Added downloader support for Yandex Disk
* Numerous bug fixes
#### Version - 2.0.7.0 - 5/28/2020
* Code is now robust when dealing with invasive Anti-virus software. We'll retry deletes/opens if the file is in use
* Rework HTTP retries for all sites to reduce the amount of 503 errors we get from LL
* Temp files now use more robust deletion code
#### Version - 2.0.6.1 - 5/22/2020
* Fix regression with manual archive downloading
#### Version - 2.0.6.0 - 5/21/2020
* Add auto-healing features back into the client/server code
* Put in the code about "Hashing Archives" that somehow missed the last release
#### Version - 2.0.5.1 - 5/16/2020
* Close Wabbajack when opening the CLI, resolves the "RocksDB is in use" errors
* Print some helpful messages about "Hashing Archives" to let users know the app isn't dead
#### Version - 2.0.5.0 - 5/14/2020
* Make the CDN downloads multi-threaded
* Optimize installation of included files
* Reinstate a broken feature with disabled mods
* Fix how JSON serializers handle dates (UTC all the things!)
* Fix for Absolute Paths in Steam files
#### Version - 2.0.4.4 - 5/11/2020
* BA2s store file names as UTF8 instead of UTF7
* Check for a BSA file by header magic not by extension (allows .bsa.bak files to be extracted)
* Exclude the game `Data` directory from compilation
#### Version - 2.0.4.3 - 5/10/2020
* Hotfix: tell the WJ CDN downloader to create the parent folder if it doesn't exist
#### Version - 2.0.4.2 - 5/10/2020
* Hotfix: allow the WJ CDN to be used for gallery modlists
* Fix a bug with the CDN downloader and modlist compilation
#### Version - 2.0.4.1 - 5/10/2020
* Hotfix: don't throw a compilation exceptions when metas can't be inferred
#### Version - 2.0.4.0 - 5/9/2020
* Several visual improvements to the gallery thanks to the hard work of Khamûl
* Rewrote most of the server-side code for better stability and performance
* Wabbajack CDN uploads/downloads now use a much more stable segmented interface
* Fixed support for Fallout 3
* Added support for Fallout 3 via GoG
* Several fixes to MEGA support
* Mediafire no longer uses Cef should stop popups (since we no longer run the JS)
* Fallback to the normal Nexus APIs if the WJ cache server is dead
#### Version - 2.0.3.0 - 4/29/2020
* Updated the MEGA Credentials Login form with more UI elements
* Switch LZ4 compression to L8 (vs L12) for much faster SSE BSA creation
* Several other internal code tweaks to improve performance and code quality
* Fixed Mediafire pop-up ads, they are no longer shown
* Updated 3rd party libraries to latest versions
#### Version - 2.0.2.0 - 4/27/2020
* Fixed mediafire links not getting resolved
* Fixed new mega links not being accepted
* Fixed cannot delete readonly file issue
* Fixed WABBAJACK_NOMATCH_INCLUDE with files inside BSAs
* Removed software rendering mode in the GUI...that should never had made it into master
#### Version - 2.0.1.0 - 4/27/2020
* Fixed "FileNotFound" and "File is open by another process" bugs during installation
* Raised the BSA limit from 2,000,000,000 bytes to 2 ^ 31 bytes
* Added NSFW flags for modlists/gallery
* Fixed zEdit settings integration
#### Version - 2.0.0.0 - 4/25/2020
* Reworked all internal routines to use Relative/Absolute path values instead of strings
* Reworked all internal routines to use Hash values instead of strings
* Reworked all internal routines to use Game values instead of strings
* Vortex support has been removed, it wasn't well tested, and wasn't used by enough people to justify further support
* Modlists are no longer saved in a binary format, everything uses Json
* Json type names are now a bit more human friendly
* All server-side code that used MongoDB now uses SQL (unifying the database)
* All Nexus validation code has been reworked to leverage RSS feeds for faster response times to updates
* All non-Nexus validation code has been reworked for better performance
* Feeds are now validated on demand, this is possible due to having a SQL backend and improved Nexus support
* Jobs in the job queue no long clobber each other so much
* BSA routines are now mostly async
* During installation, only the bare minimum number of files are extracted from a 7zip
* During indexing/extraction BSA files are not extracted, instead they are opened and files are read on-demand
* File extraction is now mostly async
* Modlists now only support website readmes (file readmes weren't used much and were a pain to read)
* Modlists now require a machine-readable version field
* Added support for games installed via the Bethesda Launcher
* Cache disk benchmarking results to save startup time of compilation/install
* Added VectorPlexus mods to the slideshow
#### Version - 1.1.5.0 - 4/6/2020
* Included LOOT configs are no longer Base64 encoded
* Reworked Wabbajack-cli
* Can use a MEGA login (if you have it, not required)
* Don't use the buggy Nexus SSO server, instead use the in-browser API key generator
* Several fixes for zEdit merge integration, handles several side-cases of improper configuration
#### Version - 1.1.4.0 - 3/30/2020
* Added support for Morrowind on GOG
* Fix a bug in the Author file uploader (Sync Error)
* Include symbols in the launcher
* Fix a small race condition/deadlock in the worker queue code
#### Version - 1.1.3.0 - 3/23/2020
* Fix for a lack of VC++ Redist dlls on newly installed Windows machines.
#### Version - 1.1.2.0 - 3/20/2020
* We now set VRAM settings for Skyrim LE ENBs
* Fixes for Morrowind Game metadata
* We now provide suggestions for users who try to install modlists for games they don't have installed
* We now warn users if they aren't running a modern version of Windows
#### Version - 1.1.1.0 - 3/9/2020
* Hotfix for Virtual Memory errors while creating BSAs
#### Version - 1.1.0.0 - 3/5/2020
* Binary Patching stores temporary and patch data on disk instead of memory (reducing memory usage)
* Fix a memory leak with diffing progress reporting
* Fix a bug with bad data in inferred game INI files.
* Added download support for YouTube
* Slideshow can now display mods from non-Nexus sites
* Building BSAs now leverage Virtual Memory resulting in a 32x reduction in memory usage during installation (#609)
#### Verison - 1.0.0.0 - 2/29/2020
* 1.0, first non-beta release
#### Version - 0.9.23.0 - 2/27/2020
* Several bugfixes and tweaks
* This is most likely the last version before the 1.0 release
#### Version - 0.9.22.1 - 2/25/2020
* Fix NaN error during installation
#### Version - 0.9.22.0 - 2/24/2020
* Server side fixes for CORS support and FTP uploads
* Print the assembly version in the log (#565)
* Don't thrash the VFS cache name quite so much
* Use OctoDiff instead of BSDiff for better performance during diff generation
#### Version - 0.9.21.0 - 2/23/2020
* Fix never ending hash issue
#### Version - 0.9.20.0 - 2/21/2020
* Don't reuse HTTP request objects (#532)
* Block popups in the in-app browser (#535)
* Don't print API keys in logs (#533)
* Store xxHash caches in binary format (#530)
* Added support for Morrowind BSA creation/unpacking
* Calculate screen size using DPI aware routines (#545)
* Only retain the most recent 50 log files
#### Version - 0.9.19.0 - 2/14/2020
* Disable server-side indexing of all mods from the Nexus
* Accept download states from clients and index the mods we haven't seen
* Fixes for Skyrin VR USSEP patch
* Remember the download states that we index on the server
* Only print remaining nexus quotas when they change
* Reworked the HTTP backend for Nexus/Http downloads performance and stability is much improved
* Fixed key errors with compilation and installation
* Improvements to the new manifest report
#### Version - 0.9.18.0 - 2/11/2020
* Auto update functionality added client-side.
* Slideshow now moves to next slide when users clicks, even if paused
* Installer now prints to log what modlist it is installing
* Adding `matchAll=<archive-name>` to a *mods's* `meta.ini` file will result in unconditional patching for all unmatching files or BSAs in
that mod (issue #465)
* Added support for non-premium Nexus downloads via manual downloading through the in-app browser.
* Downloads from Bethesda.NET are now supported. Can login via SkyrimSE or Fallout 4.
* Manual URL downloads are streamlined
* AFKMods.com download support is improved
#### Version - 1.0 beta 17 - 1/22/2020
* Build server now indexes CDN files after they are uploaded
* Build server actively looks for DynDOLOD updates
* Fix for the null key exception during compilation
* Added support for tesalliance, and afkmods
* Fix for queue size recommendation of 0GB RAM on low-end machines
* Fix for website readme compilation
* Fix for compiler downloads folder specification (was always standard path)
#### Version - 1.0 beta 16 - 1/19/2020
* Progress ring displays when downloading modlist images
* GUI releases memory of download modlists better when navigating around
* Fixed phrasing after failed installations to say "failed".
* Fixed download bug that was marking some modlists as corrupted if they were replacing older versions.
* While compiling Wabbajack will attempt to download VFS and .meta data from the build server
#### Version - 1.0 beta 15 - 1/6/2020
* Don't delete the download folder when deleting empty folders during an update
* If `Game Folder Files` exists in the MO2 folder during compilation the Game folder will be ignored as a file source
#### Version - 1.0 beta 14 - 1/6/2020
* Updating a list twice without starting WJ no longer deletes your modlist
* .mohidden files will now be correctly detected during binary patching
* Added support for MO2's new path format
* Added support for MO2 2.2.2's `portable.txt` feature
* Added support for VectorPlexus downloads
* Added a new CLI interface for providing Nexus API key overrides
* Several UI backend improvements
#### Version - 1.0 beta 13 - 1/4/22020
* Several fixes for steam game handling
* Fixes for metrics reporting
#### Version - 1.0 beta 12 - 1/3/22020
* Breaking change: the internal serialization format has changed, this will make existing lists inoperable on the latest version of WJ
* Added a change to serialization to make it backwards-compatible in the future
* Added an anonymous key to the metrics
* Fixed INI errors (again)
#### Version - 1.0 beta 11 - 1/3/2020
* Rewrote the ModDB downloader to retry with other mirrors after failure
* INI parse errors are now soft errors
* Fixed several backend stability bugs
* Changed application version scheme to better match the actual app version
#### Version - 1.0 beta 10 - 12/23/2019
* Many internal bug fixes releated to deadlocking
* Take the system RAM into account when configuring queue sizes
* Fixed the "This shouldn't happen" bug during patching. Thanks Noggog for spending countless hours on tracking down the problems.
#### Version - 1.0 beta 9 - 12/18/2019
* Create output folders before trying to download a file
#### Version - 1.0 beta 8 - 12/17/2019
* Fixed parsing of buggy ini files (Bethesda supports them so we must as well)
* Disable invalid modlists instead of hiding them
* Several Vortex improvements
* Implemented HTTP resuming for file downloads
#### Version - 1.0 beta 7 - 12/15/2019
* Fixed a regression with HTTP downloading introduced in beta 5
* No longer show broken modlists in the gallery
* Add Stardew Valley support
* Add support for .dat extraction
* Several UI fixes
#### Version - 1.0 beta 6 - 12/14/2019
* Fixes for some strange steam library setups
* Implemented download/install counts
#### Version - 1.0 beta 5 - 12/14/2019
* Added LoversLab download support
* Nexus and LL logins now happen via a in-ap browser
* Several UI enhancements
#### Version - 1.0 beta 4 - 12/3/2019
* Several crash and bug fixes
#### Version - 1.0 beta 3 - 11/30/2019
* Reworked much of the UI into a single window
* Can download modlists directly through the single-window UI
* Removed hard error on lack of disk space. We need to think about how we calculate required space
#### Version - 1.0 beta 2 - 11/23/2019
* Optimized install process, if you install on a directory that already contains an install
the minimal amount of work will be done to update the install, instead of doing a complete
from-scratch install
* Vortex Support for some non-Bethesda games.
* Reworked several internal systems (VFS and workqueues) for better reliability and stability
* Patches are cached during compilation, and source files are no longer extracted every compile
#### Version 1.0 beta 1 - 11/6/2019
* New Installation GUI
* Files are now moved during installation instead of copied
* Many other internal/non-user-facing improvements and optimizations
#### Version 1.0 alpha 5 - 11/2/2019
* Fix a NPE exception with game ESM verification
#### Version 1.0 alpha 4 - 11/2/2019
* Reorganize steps so that we run zEdit merges before NOMATCH_INCLUDE
* Look for hidden/optional ESMs when building zEdit plugins
* Check for modified ESMs before starting the long install process
#### Version 1.0 alpha 3 - 11/2/2019
* Slideshow more responsive on pressing next
* Slideshow timer resets when next is pressed
* Changed modlist extension to `.wabbajack`
* You can now open modlists directly (after initial launch)
* Wabbajack will exit if MO2 is running
* Added support for zEdit merges. We detect the zEdit install location by scanning the tool list in
Mod Organizer's .ini files, then we use the merges.json file to figure out the contents of each merge.
#### Version 1.0 alpha 2 - 10/15/2019
* Fix installer running in wrong mode
#### Version 1.0 alpha 1 - 10/14/2019
* Several internal bug fixes
#### Version 0.9.5 - 10/12/2019
* New Property system for chaning Modlist Name, Author, Description, Website, custom Banner and custom Readme
* Slideshow can now be disabled
* NSFW mods can be toggled to not appear in the Slideshow
* Set Oblivion's MO2 names to `Oblivion` not `oblivion`
* Fix validation tests to run in CI
* Add `check for broken archives` batch functionality
* Remove nexus timeout for login, it's pointless.
* Force slides to load before displaying
* Supress slide load failures
* UI is now resizeable
* Setup Crash handling at the very start of the app
* Add BA2 support
* Fix Downloads folder being incorrectly detected in some cases
* Fix validation error on selecting an installation directory in Install mode
* Reworked download code to be more extensible and stable
#### Version 0.9.4 - 10/2/2019
* Point github icon to https://github.com/wabbajack-tools/wabbajack
* Add game registry entry for Skyrim VR
* Modlists are now .zip files.
* Modlists now end with `.modlist_v1` to enable better version control
* If `readme.md` is found in the profile directory, inline it into the install report.
* Fix bug with null uri in slideshow images
* Fix bug in CleanedESM generation
#### Version 0.9.3 - 9/30/2019
* Add WABBAJACK_NOMATCH_INCLUDE works like WABBAJACK_INCLUDE but only includes files that are found to be missing at the end of compilation
* Add a list of all inlined data blobs to the install report, useful for reducing installer sizes
* Increased dummy EPS detection size to 250 bytes and added .esm files to the filter logic
* Only sync the VFS cache when it changes.
* Fix a crash in GroupedByArchive()
* Detect and zEdit Merges and include binary patches for merges (no install support yet)
* Add unit/integration tests.
* Don't assume *everyone* has LOOT
* Added support for `.exe` installers
* Rework UI to support a slideshow of used mods during installation and compilation
* Remove support for extracting `.exe` installers
* Added support for `.omod` files
* Stop emitting `.exe` modlist installers
* Reworked Nexus HTTP API - Thanks Cyclonit
* Added permissions system
* Auto detect game folders
#### Version 0.9.2 - 9/18/2013
* Fixed a bug with BSA string encoding
* Fixed another profile issue confirmed that they are properly included now
* Log when the executable is being generated
* Fixed a integer overflow resulting in a crash in very large BSA reading
* Fix a bug in BSA string encoding
* Add human friendly filesizes to the download header and file info sections in the Install Report
* Improve compilation times by caching BSDiff patches
* Detect when VFS root folders don't exist
* Only reauth against the Nexus every 3 days (instead of 12 hours)
* Optimize executable patching by switching to .NET serialization and LZ4 compression
* Ignore some files Wabbajack creates
* Improve compilation times by reworking file indexing algorithm
* Store patch files in byte format instead of base64 strings
* Verify SHA of patched files after install
* Treat .fomod files as archives
* Include WABBAJACK_INCLUDE files before including patches
* Ignore .bin and .refcache files (DynDOLOD temp files)
* Shell out to cmd.exe for VFS cleaning should fix "ReadOnlyFile" errors once and for all
* Switch out folder selection routines for Win32 APIs, should fix issue #27
* Disable the UI while working on things, so users don't accidentally mis-click during installation/loading
* Disabled "ignore missing files", it didn't work anyways
* Properly delete BSA temp folder after install
* Include size and hash for installed files
#### Version 0.9.1 - 9/5/2019
* Fixed a bug where having only one profile selected would result in no profiles being selected
#### Version 0.9 - 9/5/2019
* Added log information for when modlists start parsing during installation
* Check all links during mod list creation
* Generate a installation report during compilation
* Show the report after compiling
* Added a button to view the report before installing
* Added support for non-archive files in downloads and installation. You can now provide a link directly to a file
that is copied directly into a modfile (commonly used for `SSE Terrain Tamriel.esm`)
* Fix crash caused by multiple downloads with the same SHA256
* Putting `WABBAJACK_ALWAYS_ENABLE` on a mod's notes/comments will cause it to always be included in the modlist, even if disabled
* All `.json`, `.ini`, and `.yaml` files that contain remappable paths are now inlined and remapped.
* If Wabbajack finds a file called `otherprofiles.txt` inside the compiled profile's folder. Then that file is assumed
to be a list of other profiles to be included in the install. This list should be the name of a profile, one name per line.
* Can now set the download folder both during compilation and installation.
* Any config files pointing to the download folder are remapped.
* Refuse to run inside `downloads` folders (anti-virus watches these files too closely and it can cause VFS issues)
* Refuse to run if MO2 is on the system installed in non-portable mode (otherwise broken installs may result)
* Config files that don't otherwise match a rule are inlined into the modlist
* Warn users before installing into an existing MO2 install folder (prevents unintentional data loss from overwriting existing data #24)
* Fix for read only folder deletion bug (#23)
* Include version numbers and SHAs in the install report
* Removed option to endorse mods, Nexus devs mentioned it was of questionable worth, I (halgari) agree
#### Version 0.8.1 - 8/29/2019
* Fixed a bug that was causing VFS temp folders not to be cleaned
* 7zip Extraction code now shows a progress bar
* Told 7zip not to ask for permission before overwriting a file (should fix the hanging installer problem)
* Fixed several places where we were using long-path incompatible file routines
* Changed the work queue from FIFO to LIFO which results in depth-first work instead of breadth-first
TLDR: We now fully analyze a single archive before moving on to the next.
#### Version 0.8 - 8/26/2019
* Mod folders that contain ESMs with names matching the Skyrim core ESMs are assumed to be cleaned versions of the core
game mods, and will be patched from the ESMs found in the game folder. **Note:** if you have also cleaned the files in the Skyrim
folder, this will result in a broken install. The ESMs in the game folder should be the original ESMs, the cleaned
ESMs should go into their own mod. These files currently only include:
* `Update.esm`
* `Dragonborn.esm`
* `HearthFires.esm`
* `Dawnguard.esm`
* `ModOrganizer.ini` is now interpreted and included as part of the install. As part of the install users will be asked
to point to their game folder, and then all the references to the MO2 folder or Game folder in `ModOrganizer.ini` will
be remapped to the new locations.
* Progress bars were added to several install/hashing and compilation instructions
* 7zip routines were rewritten to use subprocesses instead of a C# library. This will result in slower indexing and installation
but should have full compatability with all usable archive formats. It should also reduce total memory usage during extraction.
* Added the ability to endorse all used mods at the completion of an install
* Custom LOOT rules are now included in a special folder in MO2 after install. Users can use this to quickly import new LOOT rules.
* Moved the VFS cache from using BSON to a custom binary encoding. Much faster read/write times and greatly reduced memory usage.
**Note:** This means that modlist authors will have to re-index their archives with this version. This is automatic but the first
compilation will take quite some time while the cache reindexes.

View File

@ -1,80 +0,0 @@
# Contributing to Wabbajack
The following is a set of guidelines for contributing to the `wabbajack-tools/wabbajack` repo on GitHub. These are guidelines but not rules, so be free to propose changes.
## How Can I Contribute?
You don't have to be a programmer to contribute to this project.
### Reporting Bugs
When you encounter problems with the application, go to our [Discord](https://discord.gg/zgbrkmA) server first and ask for help there. Before creating a new Issue, take a look at the others to avoid getting the [Duplicate](https://github.com/wabbajack-tools/wabbajack/labels/duplicate) label.
Creating a bug report is as easy as navigating to the [Issues](https://github.com/wabbajack-tools/wabbajack/issues) page and clicking the [New Issue](https://github.com/wabbajack-tools/wabbajack/issues/new/choose) button.
#### Submitting A Good Bug Report
* Select the Bug Report template to get started.
* **Use a clear and descriptive title** for the issue to identify the problem.
* **Describe the exact steps which reproduce the problem** in as many details as possible. Trace the steps you took and **don't just say what you did, but explain how you did it**.
* **Include additional data** in the issue. This encompasses your operating system, the version of Wabbajack that was used and your log file.
* **Upload the stacktrace or your entire log file** to the issue using the [Code Highlighting](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#code) feature of Markdown.
### Suggesting Enhancements
Enhancements can be everything from fixing typos to a complete revamp of documents in the repo. You can just use GitHub for making changes by clicking the pencil icon in the top right corner of a file.
### Code Contribution
This is where the fun begins. Wabbajack is programmed in C# so having a decent amount of knowledge in that language or in C/C++ is good to have. You also want to make sure that you have a basic understanding of the Git workflow.
#### Visual Studio 2019
You can download it [here](https://visualstudio.microsoft.com/vs/) but make sure to select the Community Edition as the other ones come at a cost. When installing Visual Studio you will be prompted to select a Workload and components. You will need the following:
* **.NET desktop development** from the Workload tab
* **[.NET Core 3.x](https://dotnet.microsoft.com/download/dotnet-core/3.1)**
* **NuGet package manager** from the Code Tools section
* **C# and Visual Basic** from the Development activities
The installer may have selected other options as well but these are the most important ones.
### Starting Development
1) **Fork and clone the project:** go to the GitHub repo page, click the fork button, copy the url from the forked repo, navigate to your project folder, open Git Bash or normal command prompt and type `git clone url name` and replace url with the copied URL and name with the folder name.
2) **Open Wabbajack.sln** in Visual Studio 2019.
3) **Download NuGet Packages** by selecting the solution and *Right Click* -> *Restore NuGet Packages*.
4) Set the **Wabbajack** project as the **Startup Project**
It may take a while for Visual Studio to download all packages and update all References so be patient. Once all packages are downloaded go and try building Wabbajack. If the build is successful then good job. If not, head over to the *#wabbajack-development* channel on the discord and talk about your build error.
#### Coding Style
As a C# project, you should follow the C# Coding Style. Further more you should never submit commits to your *master* branch, even if it's just a fork. Create a new branch with a meaningful name or the name of your issue/request and commit to that.
Your commits should also be elegant. Check out [this](https://github.com/git-for-windows/git/wiki/Good-commits) post for good practices.
Updating your fork is important and easy. Open your terminal of choice inside the project folder and add the original repository as a new remote:
`git remote add upstream https://github.com/wabbajack-tools/wabbajack.git`
Make sure that you're on your master branch:
`git checkout master`
Fetch all the branches of that remote into remote-tracking branches, such as upstream/master:
`git fetch upstream`
Rewrite your master branch so that any commits of yours that
aren't already in upstream/master are replayed on top of that
other branch:
`git rebase upstream/master`
#### Submitting Code Changes
Before you go and open a pull request, make sure that your code actually runs. Build the project with your changes and test the application with its new features against your testing modlist. This testing modlist should be an MO2 installation with some mods installed that worked on the version without your changes and was not modified since then.
Running the Unit Tests should also give you a good overview of what currently works.
If everything works as intended and you found no bugs in testing, go ahead and open a pull request. Your request should contain information about why you want to change something, what you changed and how you did it.

View File

@ -1,180 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Wabbajack.Common;
using Wabbajack.Lib.Downloaders;
using Wabbajack.Lib.NexusApi;
using Wabbajack.VirtualFileSystem;
using Xunit;
using Xunit.Abstractions;
namespace Compression.BSA.Test
{
public class BSATests : IAsyncLifetime
{
private static AbsolutePath _stagingFolder = ((RelativePath)"NexusDownloads").RelativeToEntryPoint();
private static AbsolutePath _bsaFolder = ((RelativePath)"BSAs").RelativeToEntryPoint();
private static AbsolutePath _testDir = ((RelativePath)"BSA Test Dir").RelativeToEntryPoint();
private static AbsolutePath _tempDir = ((RelativePath)"BSA Temp Dir").RelativeToEntryPoint();
private IDisposable _unsub;
public ITestOutputHelper TestContext { get; }
private static WorkQueue Queue { get; set; }
public BSATests(ITestOutputHelper helper)
{
TestContext = helper;
}
public async Task InitializeAsync()
{
Queue = new WorkQueue();
_unsub = Utils.LogMessages.Subscribe(f => TestContext.WriteLine(f.ShortDescription));
_stagingFolder.CreateDirectory();
await _bsaFolder.DeleteDirectory();
_bsaFolder.CreateDirectory();
}
public async Task DisposeAsync()
{
await _bsaFolder.DeleteDirectory();
Queue.Dispose();
_unsub.Dispose();
}
private static async Task<AbsolutePath> DownloadMod(Game game, int mod)
{
var client = DownloadDispatcher.GetInstance<NexusDownloader>();
await client.Prepare();
var results = await client.Client!.GetModFiles(game, mod);
var file = results.files.FirstOrDefault(f => f.is_primary) ??
results.files.OrderByDescending(f => f.uploaded_timestamp).First();
var src = _stagingFolder.Combine(file.file_name);
if (src.Exists) return src;
var state = new NexusDownloader.State
{
ModID = mod,
Game = game,
FileID = file.file_id
};
await state.Download(src);
return src;
}
[Theory]
//[InlineData(Game.SkyrimSpecialEdition, 29194)] // 3D NPCS
[InlineData(Game.SkyrimSpecialEdition, 12604)] // SkyUI
[InlineData(Game.Skyrim, 3863)] // SkyUI
[InlineData(Game.Skyrim, 51473)] // INeed
[InlineData(Game.Skyrim, 41634)] // DVA
[InlineData(Game.Fallout4, 22223)] // 10mm SMG
[InlineData(Game.Fallout4, 4472)] // True Storms
[InlineData(Game.Morrowind, 44537)]
[InlineData(Game.Fallout4, 43474)] // EM 2 Rifle
public async Task BSACompressionRecompression(Game game, int modid)
{
var filename = await DownloadMod(game, modid);
var folder = _bsaFolder.Combine(game.ToString(), modid.ToString());
await folder.DeleteDirectory();
folder.CreateDirectory();
await FileExtractor2.ExtractAll(Queue, filename, folder);
foreach (var bsa in folder.EnumerateFiles().Where(f => Consts.SupportedBSAs.Contains(f.Extension)))
{
TestContext.WriteLine($"From {bsa}");
TestContext.WriteLine("Cleaning Output Dir");
await _tempDir.DeleteDirectory();
_tempDir.CreateDirectory();
TestContext.WriteLine($"Reading {bsa}");
await using var tempFolder = await TempFolder.Create();
var tempFile = tempFolder.Dir.Combine("test.bsa");
var size = bsa.Size;
var a = await BSADispatch.OpenRead(bsa);
await a.Files.PMap(Queue, async file =>
{
var absName = _tempDir.Combine(file.Path);
ViaJson(file.State);
absName.Parent.CreateDirectory();
await using (var fs = await absName.Create())
{
await file.CopyDataTo(fs);
}
Assert.Equal(file.Size, absName.Size);
});
// Check Files should be case insensitive
Assert.Equal(a.Files.Count(), a.Files.Select(f => f.Path).ToHashSet().Count);
Assert.Equal(a.Files.Count(), a.Files.Select(f => f.Path.ToString().ToLowerInvariant()).ToHashSet().Count);
TestContext.WriteLine($"Building {bsa}");
await using (var w = await ViaJson(a.State).MakeBuilder(size))
{
var streams = await a.Files.PMap(Queue, async file =>
{
var absPath = _tempDir.Combine(file.Path);
var str = await absPath.OpenRead();
await w.AddFile(ViaJson(file.State), str);
return str;
});
await w.Build(tempFile);
streams.Do(s => s.Dispose());
}
TestContext.WriteLine($"Verifying {bsa}");
var b = await BSADispatch.OpenRead(tempFile);
TestContext.WriteLine($"Performing A/B tests on {bsa} and {tempFile}");
Assert.Equal(a.State.ToJson(), b.State.ToJson());
// Check same number of files
Assert.Equal(a.Files.Count(), b.Files.Count());
await a.Files.Zip(b.Files, (ai, bi) => (ai, bi))
.PMap(Queue, async pair =>
{
Assert.Equal(pair.ai.State.ToJson(), pair.bi.State.ToJson());
//Console.WriteLine($" - {pair.ai.Path}");
Assert.Equal(pair.ai.Path, pair.bi.Path);
//Equal(pair.ai.Compressed, pair.bi.Compressed);
Assert.Equal(pair.ai.Size, pair.bi.Size);
Utils.Log($"Comparing {pair.ai.Path} to {pair.bi.Path}");
try
{
Assert.Equal(await GetData(pair.ai), await GetData(pair.bi));
}
catch (Exception)
{
}
});
}
}
private static async ValueTask<byte[]> GetData(IFile pairAi)
{
await using var ms = new MemoryStream();
await pairAi.CopyDataTo(ms);
return ms.ToArray();
}
private static T ViaJson<T>(T i)
{
return i.ToJson().FromJsonString<T>();
}
}
}

View File

@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.10.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.console" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<PlatformTarget>x64</PlatformTarget>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Compression.BSA\Compression.BSA.csproj" />
<ProjectReference Include="..\Wabbajack.Common.CSP\Wabbajack.Common.CSP.csproj" />
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
<ProjectReference Include="..\Wabbajack.Lib\Wabbajack.Lib.csproj" />
</ItemGroup>
</Project>

View File

@ -1,19 +0,0 @@
using Wabbajack.Common;
using Xunit;
namespace Compression.BSA.Test
{
public class UnitTests
{
[Fact]
public void HashesRespectFolderExtensions()
{
Assert.Equal((ulong)0x085B31F63008E2B6, BSAUtils.GetBSAHash("005930b6.dds"));
// Old code has a bug where we were stripping the `.esp` from the folder which we shoudn't do when creating folder paths
Assert.Equal((ulong)0x38C7A858743A7370, BSAUtils.GetFolderBSAHash((RelativePath)@"textures\actors\character\facegendata\facetint\darkend.esp"));
}
}
}

View File

@ -1,280 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip.Compression;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using Wabbajack.Common;
#nullable disable
namespace Compression.BSA
{
interface IFileBuilder
{
uint FileHash { get; }
uint DirHash { get; }
string FullName { get; }
int Index { get; }
Task WriteData(BinaryWriter wtr);
void WriteHeader(BinaryWriter wtr);
}
public class BA2Builder : IBSABuilder
{
private BA2StateObject _state;
private List<IFileBuilder> _entries = new List<IFileBuilder>();
private DiskSlabAllocator _slab;
public static async Task<BA2Builder> Create(BA2StateObject state, long size)
{
var self = new BA2Builder {_state = state, _slab = await DiskSlabAllocator.Create(size)};
return self;
}
public async ValueTask DisposeAsync()
{
await _slab.DisposeAsync();
}
public async Task AddFile(FileStateObject state, Stream src)
{
switch (_state.Type)
{
case EntryType.GNRL:
var result = await BA2FileEntryBuilder.Create((BA2FileEntryState)state, src, _slab);
lock(_entries) _entries.Add(result);
break;
case EntryType.DX10:
var resultdx10 = await BA2DX10FileEntryBuilder.Create((BA2DX10EntryState)state, src, _slab);
lock(_entries) _entries.Add(resultdx10);
break;
}
}
public async Task Build(AbsolutePath filename)
{
SortEntries();
await using var fs = await filename.Create();
await using var bw = new BinaryWriter(fs);
bw.Write(Encoding.ASCII.GetBytes(_state.HeaderMagic));
bw.Write(_state.Version);
bw.Write(Encoding.ASCII.GetBytes(Enum.GetName(typeof(EntryType), _state.Type)));
bw.Write((uint)_entries.Count);
var table_offset_loc = bw.BaseStream.Position;
bw.Write((ulong)0);
foreach (var entry in _entries)
{
entry.WriteHeader(bw);
}
await _entries.DoProgress("Writing BSA Files", async entry =>
{
await entry.WriteData(bw);
});
if (!_state.HasNameTable) return;
var pos = bw.BaseStream.Position;
bw.BaseStream.Seek(table_offset_loc, SeekOrigin.Begin);
bw.Write((ulong)pos);
bw.BaseStream.Seek(pos, SeekOrigin.Begin);
foreach (var entry in _entries)
{
var bytes = Encoding.UTF8.GetBytes(entry.FullName);
bw.Write((ushort)bytes.Length);
await bw.BaseStream.WriteAsync(bytes, 0, bytes.Length);
}
}
private void SortEntries()
{
_entries = _entries.OrderBy(e => e.Index).ToList();
}
}
public class BA2DX10FileEntryBuilder : IFileBuilder
{
private BA2DX10EntryState _state;
private List<ChunkBuilder> _chunks;
public static async Task<BA2DX10FileEntryBuilder> Create(BA2DX10EntryState state, Stream src, DiskSlabAllocator slab)
{
var builder = new BA2DX10FileEntryBuilder {_state = state};
var headerSize = DDS.HeaderSizeForFormat((DXGI_FORMAT) state.PixelFormat) + 4;
new BinaryReader(src).ReadBytes((int)headerSize);
// This can't be parallel because it all runs off the same base IO stream.
builder._chunks = new List<ChunkBuilder>();
foreach (var chunk in state.Chunks)
builder._chunks.Add(await ChunkBuilder.Create(state, chunk, src, slab));
return builder;
}
public uint FileHash => _state.NameHash;
public uint DirHash => _state.DirHash;
public string FullName => (string)_state.Path;
public int Index => _state.Index;
public void WriteHeader(BinaryWriter bw)
{
bw.Write(_state.NameHash);
bw.Write(Encoding.UTF8.GetBytes(_state.Extension));
bw.Write(_state.DirHash);
bw.Write(_state.Unk8);
bw.Write((byte)_chunks.Count);
bw.Write(_state.ChunkHdrLen);
bw.Write(_state.Height);
bw.Write(_state.Width);
bw.Write(_state.NumMips);
bw.Write(_state.PixelFormat);
bw.Write(_state.Unk16);
foreach (var chunk in _chunks)
chunk.WriteHeader(bw);
}
public async Task WriteData(BinaryWriter wtr)
{
foreach (var chunk in _chunks)
await chunk.WriteData(wtr);
}
}
public class ChunkBuilder
{
private ChunkState _chunk;
private uint _packSize;
private long _offsetOffset;
private Stream _dataSlab;
public static async Task<ChunkBuilder> Create(BA2DX10EntryState state, ChunkState chunk, Stream src, DiskSlabAllocator slab)
{
var builder = new ChunkBuilder {_chunk = chunk};
if (!chunk.Compressed)
{
builder._dataSlab = slab.Allocate(chunk.FullSz);
await src.CopyToWithStatusAsync((int)chunk.FullSz, builder._dataSlab, $"Writing {state.Path} {chunk.StartMip}:{chunk.EndMip}");
}
else
{
var deflater = new Deflater(Deflater.BEST_COMPRESSION);
await using var ms = new MemoryStream();
await using (var ds = new DeflaterOutputStream(ms, deflater))
{
ds.IsStreamOwner = false;
await src.CopyToWithStatusAsync((int)chunk.FullSz, ds, $"Compressing {state.Path} {chunk.StartMip}:{chunk.EndMip}");
}
builder._dataSlab = slab.Allocate(ms.Length);
ms.Position = 0;
await ms.CopyToWithStatusAsync(ms.Length, builder._dataSlab, $"Writing {state.Path} {chunk.StartMip}:{chunk.EndMip}");
builder._packSize = (uint)ms.Length;
}
builder._dataSlab.Position = 0;
return builder;
}
public void WriteHeader(BinaryWriter bw)
{
_offsetOffset = bw.BaseStream.Position;
bw.Write((ulong)0);
bw.Write(_packSize);
bw.Write(_chunk.FullSz);
bw.Write(_chunk.StartMip);
bw.Write(_chunk.EndMip);
bw.Write(_chunk.Align);
}
public async Task WriteData(BinaryWriter bw)
{
var pos = bw.BaseStream.Position;
bw.BaseStream.Position = _offsetOffset;
bw.Write((ulong)pos);
bw.BaseStream.Position = pos;
await _dataSlab.CopyToLimitAsync(bw.BaseStream, (int)_dataSlab.Length);
await _dataSlab.DisposeAsync();
}
}
public class BA2FileEntryBuilder : IFileBuilder
{
private int _rawSize;
private int _size;
private BA2FileEntryState _state;
private long _offsetOffset;
private Stream _dataSrc;
public static async Task<BA2FileEntryBuilder> Create(BA2FileEntryState state, Stream src, DiskSlabAllocator slab)
{
var builder = new BA2FileEntryBuilder
{
_state = state,
_rawSize = (int)src.Length,
_dataSrc = src
};
if (!state.Compressed)
return builder;
await using (var ms = new MemoryStream())
{
await using (var ds = new DeflaterOutputStream(ms))
{
ds.IsStreamOwner = false;
await builder._dataSrc.CopyToAsync(ds);
}
await builder._dataSrc.DisposeAsync();
builder._dataSrc = slab.Allocate(ms.Length);
ms.Position = 0;
await ms.CopyToAsync(builder._dataSrc);
builder._dataSrc.Position = 0;
builder._size = (int)ms.Length;
}
return builder;
}
public uint FileHash => _state.NameHash;
public uint DirHash => _state.DirHash;
public string FullName => (string)_state.Path;
public int Index => _state.Index;
public void WriteHeader(BinaryWriter wtr)
{
wtr.Write(_state.NameHash);
wtr.Write(Encoding.UTF8.GetBytes(_state.Extension));
wtr.Write(_state.DirHash);
wtr.Write(_state.Flags);
_offsetOffset = wtr.BaseStream.Position;
wtr.Write((ulong)0);
wtr.Write(_size);
wtr.Write(_rawSize);
wtr.Write(_state.Align);
}
public async Task WriteData(BinaryWriter wtr)
{
var pos = wtr.BaseStream.Position;
wtr.BaseStream.Position = _offsetOffset;
wtr.Write((ulong)pos);
wtr.BaseStream.Position = pos;
_dataSrc.Position = 0;
await _dataSrc.CopyToLimitAsync(wtr.BaseStream, (int)_dataSrc.Length);
await _dataSrc.DisposeAsync();
}
}
}

View File

@ -1,544 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip.Compression;
using Wabbajack.Common;
using Wabbajack.Common.Serialization.Json;
using File = Alphaleonis.Win32.Filesystem.File;
#nullable disable
namespace Compression.BSA
{
public enum EntryType
{
GNRL,
DX10,
GNMF
}
interface IFileEntry : IFile
{
string FullPath { get; set; }
}
public class BA2Reader : IBSAReader
{
private Stream _stream;
internal BinaryReader _rdr;
internal uint _version;
internal string _headerMagic;
internal EntryType _type;
internal uint _numFiles;
internal ulong _nameTableOffset;
public IStreamFactory _streamFactory;
public bool UseATIFourCC { get; set; } = false;
public bool HasNameTable => _nameTableOffset > 0;
public static async Task<BA2Reader> Load(IStreamFactory streamFactory)
{
var rdr = new BA2Reader(await streamFactory.GetStream()) {_streamFactory = streamFactory};
await rdr.LoadHeaders();
return rdr;
}
private BA2Reader(Stream stream)
{
_stream = stream;
_rdr = new BinaryReader(_stream, Encoding.UTF8);
}
private async Task LoadHeaders()
{
_headerMagic = Encoding.ASCII.GetString(_rdr.ReadBytes(4));
if (_headerMagic != "BTDX")
throw new InvalidDataException("Unknown header type: " + _headerMagic);
_version = _rdr.ReadUInt32();
string fourcc = Encoding.ASCII.GetString(_rdr.ReadBytes(4));
if (Enum.TryParse(fourcc, out EntryType entryType))
{
_type = entryType;
}
else
{
throw new InvalidDataException($"Can't parse entry types of {fourcc}");
}
_numFiles = _rdr.ReadUInt32();
_nameTableOffset = _rdr.ReadUInt64();
var files = new List<IFileEntry>();
for (var idx = 0; idx < _numFiles; idx += 1)
{
switch (_type)
{
case EntryType.GNRL:
files.Add(new BA2FileEntry(this, idx));
break;
case EntryType.DX10:
files.Add(new BA2DX10Entry(this, idx));
break;
case EntryType.GNMF:
break;
}
}
if (HasNameTable)
{
_rdr.BaseStream.Seek((long) _nameTableOffset, SeekOrigin.Begin);
foreach (var file in files)
file.FullPath = Encoding.UTF8.GetString(_rdr.ReadBytes(_rdr.ReadInt16()));
}
Files = files;
_stream?.Dispose();
_rdr.Dispose();
}
public IEnumerable<IFile> Files { get; private set; }
public ArchiveStateObject State => new BA2StateObject(this);
public void Dump(Action<string> print)
{
print($"HeaderMagic: {_headerMagic}");
print($"Number of Files: {_numFiles}");
print($"----------------------------");
foreach (var file in Files)
{
print("\n");
file.Dump(print);
}
}
}
[JsonName("BA2State")]
public class BA2StateObject : ArchiveStateObject
{
public BA2StateObject()
{
}
public BA2StateObject(BA2Reader ba2Reader)
{
Version = ba2Reader._version;
HeaderMagic = ba2Reader._headerMagic;
Type = ba2Reader._type;
HasNameTable = ba2Reader.HasNameTable;
}
public bool HasNameTable { get; set; }
public EntryType Type { get; set; }
public string HeaderMagic { get; set; }
public uint Version { get; set; }
public override async Task<IBSABuilder> MakeBuilder(long size)
{
return await BA2Builder.Create(this, size);
}
}
public class BA2DX10Entry : IFileEntry
{
internal uint _nameHash;
internal string _extension;
internal uint _dirHash;
internal byte _unk8;
internal byte _numChunks;
internal ushort _chunkHdrLen;
internal ushort _height;
internal ushort _width;
internal byte _numMips;
internal byte _format;
internal ushort _unk16;
internal List<BA2TextureChunk> _chunks;
private BA2Reader _bsa;
internal int _index;
public BA2DX10Entry(BA2Reader ba2Reader, int idx)
{
_bsa = ba2Reader;
var _rdr = ba2Reader._rdr;
_nameHash = _rdr.ReadUInt32();
FullPath = _nameHash.ToString("X");
_extension = Encoding.UTF8.GetString(_rdr.ReadBytes(4));
_dirHash = _rdr.ReadUInt32();
_unk8 = _rdr.ReadByte();
_numChunks = _rdr.ReadByte();
_chunkHdrLen = _rdr.ReadUInt16();
_height = _rdr.ReadUInt16();
_width = _rdr.ReadUInt16();
_numMips = _rdr.ReadByte();
_format = _rdr.ReadByte();
_unk16 = _rdr.ReadUInt16();
_index = idx;
_chunks = Enumerable.Range(0, _numChunks)
.Select(_ => new BA2TextureChunk(_rdr))
.ToList();
}
public string FullPath { get; set; }
public RelativePath Path => new RelativePath(FullPath);
public uint Size => (uint)_chunks.Sum(f => f._fullSz) + HeaderSize + sizeof(uint);
public FileStateObject State => new BA2DX10EntryState(this);
public uint HeaderSize => DDS.HeaderSizeForFormat((DXGI_FORMAT)_format);
public async ValueTask CopyDataTo(Stream output)
{
var bw = new BinaryWriter(output);
WriteHeader(bw);
await using var fs = await _bsa._streamFactory.GetStream();
using var br = new BinaryReader(fs);
foreach (var chunk in _chunks)
{
var full = new byte[chunk._fullSz];
var isCompressed = chunk._packSz != 0;
br.BaseStream.Seek((long)chunk._offset, SeekOrigin.Begin);
if (!isCompressed)
{
await br.BaseStream.ReadAsync(full, 0, full.Length);
}
else
{
byte[] compressed = new byte[chunk._packSz];
await br.BaseStream.ReadAsync(compressed, 0, compressed.Length);
var inflater = new Inflater();
inflater.SetInput(compressed);
inflater.Inflate(full);
}
await bw.BaseStream.WriteAsync(full, 0, full.Length);
}
}
public void Dump(Action<string> print)
{
throw new NotImplementedException();
}
private void WriteHeader(BinaryWriter bw)
{
var ddsHeader = new DDS_HEADER();
ddsHeader.dwSize = ddsHeader.GetSize();
ddsHeader.dwHeaderFlags = DDS.DDS_HEADER_FLAGS_TEXTURE | DDS.DDS_HEADER_FLAGS_LINEARSIZE | DDS.DDS_HEADER_FLAGS_MIPMAP;
ddsHeader.dwHeight = _height;
ddsHeader.dwWidth = _width;
ddsHeader.dwMipMapCount = _numMips;
ddsHeader.PixelFormat.dwSize = ddsHeader.PixelFormat.GetSize();
ddsHeader.dwDepth = 1;
ddsHeader.dwSurfaceFlags = DDS.DDS_SURFACE_FLAGS_TEXTURE | DDS.DDS_SURFACE_FLAGS_MIPMAP;
switch ((DXGI_FORMAT)_format)
{
case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '1');
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height / 2); // 4bpp
break;
case DXGI_FORMAT.DXGI_FORMAT_BC2_UNORM:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '3');
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height); // 8bpp
break;
case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', 'T', '5');
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height); // 8bpp
break;
case DXGI_FORMAT.DXGI_FORMAT_BC5_UNORM:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
if (_bsa.UseATIFourCC)
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('A', 'T', 'I', '2'); // this is more correct but the only thing I have found that supports it is the nvidia photoshop plugin
else
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('B', 'C', '5', 'U');
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height); // 8bpp
break;
case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM_SRGB:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', '1', '0');
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height / 2); // 4bpp
break;
case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT.DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM_SRGB:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_FOURCC;
ddsHeader.PixelFormat.dwFourCC = DDS.MAKEFOURCC('D', 'X', '1', '0');
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height); // 8bpp
break;
case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM:
case DXGI_FORMAT.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGBA;
ddsHeader.PixelFormat.dwRGBBitCount = 32;
ddsHeader.PixelFormat.dwRBitMask = 0x000000FF;
ddsHeader.PixelFormat.dwGBitMask = 0x0000FF00;
ddsHeader.PixelFormat.dwBBitMask = 0x00FF0000;
ddsHeader.PixelFormat.dwABitMask = 0xFF000000;
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height * 4); // 32bpp
break;
case DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM:
case DXGI_FORMAT.DXGI_FORMAT_B8G8R8X8_UNORM:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGBA;
ddsHeader.PixelFormat.dwRGBBitCount = 32;
ddsHeader.PixelFormat.dwRBitMask = 0x00FF0000;
ddsHeader.PixelFormat.dwGBitMask = 0x0000FF00;
ddsHeader.PixelFormat.dwBBitMask = 0x000000FF;
ddsHeader.PixelFormat.dwABitMask = 0xFF000000;
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height * 4); // 32bpp
break;
case DXGI_FORMAT.DXGI_FORMAT_R8_UNORM:
ddsHeader.PixelFormat.dwFlags = DDS.DDS_RGB;
ddsHeader.PixelFormat.dwRGBBitCount = 8;
ddsHeader.PixelFormat.dwRBitMask = 0xFF;
ddsHeader.dwPitchOrLinearSize = (uint)(_width * _height); // 8bpp
break;
default:
throw new Exception("Unsupported DDS header format. File: " + FullPath);
}
bw.Write((uint)DDS.DDS_MAGIC);
ddsHeader.Write(bw);
switch ((DXGI_FORMAT)_format)
{
case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM_SRGB:
var dxt10 = new DDS_HEADER_DXT10()
{
dxgiFormat = _format,
resourceDimension = (uint)DXT10_RESOURCE_DIMENSION.DIMENSION_TEXTURE2D,
miscFlag = 0,
arraySize = 1,
miscFlags2 = DDS.DDS_ALPHA_MODE_UNKNOWN
};
dxt10.Write(bw);
break;
}
}
public async ValueTask<IStreamFactory> GetStreamFactory()
{
var ms = new MemoryStream();
await CopyDataTo(ms);
ms.Position = 0;
return new MemoryStreamFactory(ms, Path);
}
}
[JsonName("BA2DX10Entry")]
public class BA2DX10EntryState : FileStateObject
{
public BA2DX10EntryState() { }
public BA2DX10EntryState(BA2DX10Entry ba2Dx10Entry)
{
Path = ba2Dx10Entry.Path;
NameHash = ba2Dx10Entry._nameHash;
Extension = ba2Dx10Entry._extension;
DirHash = ba2Dx10Entry._dirHash;
Unk8 = ba2Dx10Entry._unk8;
ChunkHdrLen = ba2Dx10Entry._chunkHdrLen;
Height = ba2Dx10Entry._height;
Width = ba2Dx10Entry._width;
NumMips = ba2Dx10Entry._numMips;
PixelFormat = ba2Dx10Entry._format;
Unk16 = ba2Dx10Entry._unk16;
Index = ba2Dx10Entry._index;
Chunks = ba2Dx10Entry._chunks.Select(ch => new ChunkState(ch)).ToList();
}
public List<ChunkState> Chunks { get; set; }
public ushort Unk16 { get; set; }
public byte PixelFormat { get; set; }
public byte NumMips { get; set; }
public ushort Width { get; set; }
public ushort Height { get; set; }
public ushort ChunkHdrLen { get; set; }
public byte Unk8 { get; set; }
public uint DirHash { get; set; }
public string Extension { get; set; }
public uint NameHash { get; set; }
}
[JsonName("Chunk")]
public class ChunkState
{
public ChunkState() {}
public ChunkState(BA2TextureChunk ch)
{
FullSz = ch._fullSz;
StartMip = ch._startMip;
EndMip = ch._endMip;
Align = ch._align;
Compressed = ch._packSz != 0;
}
public bool Compressed { get; set; }
public uint Align { get; set; }
public ushort EndMip { get; set; }
public ushort StartMip { get; set; }
public uint FullSz { get; set; }
}
[JsonName("BA2TextureChunk")]
public class BA2TextureChunk
{
internal ulong _offset;
internal uint _packSz;
internal uint _fullSz;
internal ushort _startMip;
internal ushort _endMip;
internal uint _align;
public BA2TextureChunk(BinaryReader rdr)
{
_offset = rdr.ReadUInt64();
_packSz = rdr.ReadUInt32();
_fullSz = rdr.ReadUInt32();
_startMip = rdr.ReadUInt16();
_endMip = rdr.ReadUInt16();
_align = rdr.ReadUInt32();
}
}
public class BA2FileEntry : IFileEntry
{
internal uint _nameHash;
internal string _extension;
internal uint _dirHash;
internal uint _flags;
internal ulong _offset;
internal uint _size;
internal uint _realSize;
internal uint _align;
internal BA2Reader _bsa;
internal int _index;
public bool Compressed => _size != 0;
public void Dump(Action<string> print)
{
print($"FullPath: {FullPath}");
print($"Name Hash: {_nameHash}");
print($"Offset: {_offset}");
print($"Flags: {_flags:x}");
print($"Real Size: {_realSize}");
print($"Index: {_index}");
}
public BA2FileEntry(BA2Reader ba2Reader, int index)
{
_index = index;
_bsa = ba2Reader;
var _rdr = ba2Reader._rdr;
_nameHash = _rdr.ReadUInt32();
FullPath = _nameHash.ToString("X");
_extension = Encoding.UTF8.GetString(_rdr.ReadBytes(4));
_dirHash = _rdr.ReadUInt32();
_flags = _rdr.ReadUInt32();
_offset = _rdr.ReadUInt64();
_size = _rdr.ReadUInt32();
_realSize = _rdr.ReadUInt32();
_align = _rdr.ReadUInt32();
}
public string FullPath { get; set; }
public RelativePath Path => new RelativePath(FullPath);
public uint Size => _realSize;
public FileStateObject State => new BA2FileEntryState(this);
public async ValueTask CopyDataTo(Stream output)
{
await using var fs = await _bsa._streamFactory.GetStream();
fs.Seek((long) _offset, SeekOrigin.Begin);
uint len = Compressed ? _size : _realSize;
var bytes = new byte[len];
fs.Read(bytes, 0, (int) len);
if (!Compressed)
{
await output.WriteAsync(bytes, 0, bytes.Length);
}
else
{
var uncompressed = new byte[_realSize];
var inflater = new Inflater();
inflater.SetInput(bytes);
inflater.Inflate(uncompressed);
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, Path);
}
}
[JsonName("BA2FileEntryState")]
public class BA2FileEntryState : FileStateObject
{
public BA2FileEntryState() { }
public BA2FileEntryState(BA2FileEntry ba2FileEntry)
{
NameHash = ba2FileEntry._nameHash;
DirHash = ba2FileEntry._dirHash;
Flags = ba2FileEntry._flags;
Align = ba2FileEntry._align;
Compressed = ba2FileEntry.Compressed;
Path = ba2FileEntry.Path;
Extension = ba2FileEntry._extension;
Index = ba2FileEntry._index;
}
public string Extension { get; set; }
public bool Compressed { get; set; }
public uint Align { get; set; }
public uint Flags { get; set; }
public uint DirHash { get; set; }
public uint NameHash { get; set; }
}
}

View File

@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Compression.BSA
{
[Flags]
public enum ArchiveFlags : uint
{
HasFolderNames = 0x1,
HasFileNames = 0x2,
Compressed = 0x4,
Unk4 = 0x8,
Unk5 = 0x10,
Unk6 = 0x20,
XBox360Archive = 0x40,
Unk8 = 0x80,
HasFileNameBlobs = 0x100,
Unk10 = 0x200,
Unk11 = 0x400
}
}

View File

@ -1,349 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using K4os.Compression.LZ4;
using K4os.Compression.LZ4.Streams;
using Wabbajack.Common;
using File = Alphaleonis.Win32.Filesystem.File;
using Path = Alphaleonis.Win32.Filesystem.Path;
#nullable disable
namespace Compression.BSA
{
public class BSABuilder : IBSABuilder
{
internal byte[] _fileId;
private List<FileEntry> _files = new List<FileEntry>();
internal List<FolderRecordBuilder> _folders = new List<FolderRecordBuilder>();
internal uint _offset;
internal uint _totalFileNameLength;
internal DiskSlabAllocator _slab;
public static async Task<BSABuilder> Create(long size)
{
var self = new BSABuilder
{
_fileId = Encoding.ASCII.GetBytes("BSA\0"),
_offset = 0x24,
_slab = await DiskSlabAllocator.Create(size)
};
return self;
}
public static async Task<BSABuilder> Create(BSAStateObject bsaStateObject, long size)
{
var self = await Create(size).ConfigureAwait(false);
self.HeaderType = (VersionType)bsaStateObject.Version;
self.FileFlags = (FileFlags)bsaStateObject.FileFlags;
self.ArchiveFlags = (ArchiveFlags)bsaStateObject.ArchiveFlags;
return self;
}
public IEnumerable<FileEntry> Files => _files;
public ArchiveFlags ArchiveFlags { get; set; }
public FileFlags FileFlags { get; set; }
public VersionType HeaderType { get; set; }
public IEnumerable<RelativePath> FolderNames
{
get
{
return _files.Select(f => f.Path.Parent).Distinct();
}
}
public bool HasFolderNames => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNames);
public bool HasFileNames => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNames);
public bool CompressedByDefault => ArchiveFlags.HasFlag(ArchiveFlags.Compressed);
public bool HasNameBlobs => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNameBlobs);
public async ValueTask DisposeAsync()
{
await _slab.DisposeAsync();
}
public async Task AddFile(FileStateObject state, Stream src)
{
var ostate = (BSAFileStateObject) state;
var r = await FileEntry.Create(this, ostate.Path, src, ostate.FlipCompression);
lock (this)
{
_files.Add(r);
}
}
public async Task Build(AbsolutePath outputName)
{
RegenFolderRecords();
await using var fs = await outputName.Create();
await using var wtr = new BinaryWriter(fs);
wtr.Write(_fileId);
wtr.Write((uint)HeaderType);
wtr.Write(_offset);
wtr.Write((uint)ArchiveFlags);
var folders = FolderNames.ToList();
wtr.Write((uint) folders.Count);
wtr.Write((uint) _files.Count);
wtr.Write((uint) _folders.Select(f => f._nameBytes.Count() - 1).Sum()); // totalFolderNameLength
var s = _files.Select(f => f._pathBytes.Count()).Sum();
_totalFileNameLength = (uint) _files.Select(f => f._nameBytes.Count()).Sum();
wtr.Write(_totalFileNameLength); // totalFileNameLength
wtr.Write((uint)FileFlags);
foreach (var folder in _folders) folder.WriteFolderRecord(wtr);
foreach (var folder in _folders)
{
if (HasFolderNames)
wtr.Write(folder._nameBytes);
foreach (var file in folder._files) file.WriteFileRecord(wtr);
}
foreach (var file in _files) wtr.Write(file._nameBytes);
await _files.DoProgress("Writing BSA Body", async file =>
await file.WriteData(wtr));
}
public void RegenFolderRecords()
{
_folders = _files.GroupBy(f => f.Path.Parent)
.Select(f => new FolderRecordBuilder(this, f.Key, f.ToList()))
.OrderBy(f => f._hash)
.ToList();
foreach (var folder in _folders)
foreach (var file in folder._files)
file._folder = folder;
_files = (from folder in _folders
from file in folder._files
orderby folder._hash, file._hash
select file).ToList();
}
}
public class FolderRecordBuilder
{
internal BSABuilder _bsa;
internal uint _fileCount;
internal IEnumerable<FileEntry> _files;
internal ulong _hash;
internal byte[] _nameBytes;
internal ulong _offset;
internal uint _recordSize;
public FolderRecordBuilder(BSABuilder bsa, RelativePath folderName, IEnumerable<FileEntry> files)
{
_files = files.OrderBy(f => f._hash);
Name = folderName;
_bsa = bsa;
// Folders don't have extensions, so let's make sure we cut it out
_hash = Name.GetFolderBSAHash();
_fileCount = (uint) files.Count();
_nameBytes = folderName.ToBZString(_bsa.HeaderType);
_recordSize = sizeof(ulong) + sizeof(uint) + sizeof(uint);
}
public ulong Hash => _hash;
public RelativePath Name { get; }
public ulong SelfSize
{
get
{
if (_bsa.HeaderType == VersionType.SSE)
return sizeof(ulong) + sizeof(uint) + sizeof(uint) + sizeof(ulong);
return sizeof(ulong) + sizeof(uint) + sizeof(uint);
}
}
public ulong FileRecordSize
{
get
{
ulong size = 0;
if (_bsa.HasFolderNames)
size += (ulong) _nameBytes.Length;
size += (ulong) _files.Select(f => sizeof(ulong) + sizeof(uint) + sizeof(uint)).Sum();
return size;
}
}
public void WriteFolderRecord(BinaryWriter wtr)
{
var idx = _bsa._folders.IndexOf(this);
_offset = (ulong) wtr.BaseStream.Position;
_offset += (ulong) _bsa._folders.Skip(idx).Select(f => (long) f.SelfSize).Sum();
_offset += _bsa._totalFileNameLength;
_offset += (ulong) _bsa._folders.Take(idx).Select(f => (long) f.FileRecordSize).Sum();
var sp = wtr.BaseStream.Position;
wtr.Write(_hash);
wtr.Write(_fileCount);
if (_bsa.HeaderType == VersionType.SSE)
{
wtr.Write((uint) 0); // unk
wtr.Write(_offset); // offset
}
else if (_bsa.HeaderType == VersionType.FO3 || _bsa.HeaderType == VersionType.TES4)
{
wtr.Write((uint) _offset);
}
else
{
throw new NotImplementedException($"Cannot write to BSAs of type {_bsa.HeaderType}");
}
}
}
public class FileEntry
{
internal BSABuilder _bsa;
internal bool _flipCompression;
internal FolderRecordBuilder _folder;
internal ulong _hash;
internal string _name;
internal byte[] _nameBytes;
private long _offsetOffset;
internal int _originalSize;
internal RelativePath _path;
private byte[] _pathBSBytes;
internal byte[] _pathBytes;
private Stream _srcData;
public static async Task<FileEntry> Create(BSABuilder bsa, RelativePath path, Stream src, bool flipCompression)
{
var entry = new FileEntry();
entry._bsa = bsa;
entry._path = path;
entry._name = (string)entry._path.FileName;
entry._hash = entry._name.GetBSAHash();
entry._nameBytes = entry._name.ToTermString(bsa.HeaderType);
entry._pathBytes = entry._path.ToTermString(bsa.HeaderType);
entry._pathBSBytes = entry._path.ToBSString();
entry._flipCompression = flipCompression;
entry._srcData = src;
entry._originalSize = (int)entry._srcData.Length;
if (entry.Compressed)
await entry.CompressData();
return entry;
}
public bool Compressed
{
get
{
if (_flipCompression)
return !_bsa.CompressedByDefault;
return _bsa.CompressedByDefault;
}
}
public RelativePath Path => _path;
public bool FlipCompression => _flipCompression;
public ulong Hash => _hash;
public FolderRecordBuilder Folder => _folder;
private async Task CompressData()
{
switch (_bsa.HeaderType)
{
case VersionType.SSE:
{
var r = new MemoryStream();
await using (var w = LZ4Stream.Encode(r, new LZ4EncoderSettings {CompressionLevel = LZ4Level.L12_MAX}, true))
{
await _srcData.CopyToWithStatusAsync(_srcData.Length, w, $"Compressing {_path}");
}
await _srcData.DisposeAsync();
_srcData = _bsa._slab.Allocate(r.Length);
r.Position = 0;
await r.CopyToWithStatusAsync(r.Length, _srcData, $"Writing {_path}");
_srcData.Position = 0;
break;
}
case VersionType.FO3:
case VersionType.TES4:
{
var r = new MemoryStream();
using (var w = new DeflaterOutputStream(r))
{
w.IsStreamOwner = false;
await _srcData.CopyToWithStatusAsync(_srcData.Length, w, $"Compressing {_path}");
}
await _srcData.DisposeAsync();
_srcData = _bsa._slab.Allocate(r.Length);
r.Position = 0;
await r.CopyToWithStatusAsync(r.Length, _srcData, $"Writing {_path}");
_srcData.Position = 0;
break;
}
default:
throw new NotImplementedException($"Can't compress data for {_bsa.HeaderType} BSAs.");
}
}
internal void WriteFileRecord(BinaryWriter wtr)
{
wtr.Write(_hash);
var size = _srcData.Length;
if (_bsa.HasNameBlobs) size += _pathBSBytes.Length;
if (Compressed) size += 4;
if (_flipCompression)
wtr.Write((uint) size | (0x1 << 30));
else
wtr.Write((uint) size);
_offsetOffset = wtr.BaseStream.Position;
wtr.Write(0xDEADBEEF);
}
internal async Task WriteData(BinaryWriter wtr)
{
var offset = (uint) wtr.BaseStream.Position;
wtr.BaseStream.Position = _offsetOffset;
wtr.Write(offset);
wtr.BaseStream.Position = offset;
if (_bsa.HasNameBlobs) wtr.Write(_pathBSBytes);
if (Compressed)
{
wtr.Write((uint) _originalSize);
_srcData.Position = 0;
await _srcData.CopyToLimitAsync(wtr.BaseStream, (int)_srcData.Length);
await _srcData.DisposeAsync();
}
else
{
_srcData.Position = 0;
await _srcData.CopyToLimitAsync(wtr.BaseStream, (int)_srcData.Length);
await _srcData.DisposeAsync();
}
}
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Compression.BSA
{
[Flags]
public enum FileFlags : uint
{
Meshes = 0x1,
Textures = 0x2,
Menus = 0x4,
Sounds = 0x8,
Voices = 0x10,
Shaders = 0x20,
Trees = 0x40,
Fonts = 0x80,
Miscellaneous = 0x100
}
}

View File

@ -1,23 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Wabbajack.Common.Serialization.Json;
using File = Alphaleonis.Win32.Filesystem.File;
namespace Compression.BSA
{
[JsonName("BSAFileState")]
public class BSAFileStateObject : FileStateObject
{
public bool FlipCompression { get; set; }
public BSAFileStateObject() { }
public BSAFileStateObject(FileRecord fileRecord)
{
FlipCompression = fileRecord.FlipCompression;
Path = fileRecord.Path;
Index = fileRecord._index;
}
}
}

View File

@ -1,175 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Common.Serialization.Json;
using File = Alphaleonis.Win32.Filesystem.File;
namespace Compression.BSA
{
public class BSAReader : IBSAReader
{
public const int HeaderLength = 0x24;
internal uint _fileCount;
internal uint _folderCount;
internal uint _folderRecordOffset;
private Lazy<FolderRecord[]> _folders = null!;
private Lazy<Dictionary<string, FolderRecord>> _foldersByName = null!;
internal string _magic = string.Empty;
internal uint _totalFileNameLength;
internal uint _totalFolderNameLength;
public IStreamFactory _streamFactory = new NativeFileStreamFactory(default);
public VersionType HeaderType { get; private set; }
public ArchiveFlags ArchiveFlags { get; private set; }
public FileFlags FileFlags { get; private set; }
public IEnumerable<IFile> Files => _folders.Value.SelectMany(f => f.Files);
public IEnumerable<IFolder> Folders => _folders.Value;
public ArchiveStateObject State => new BSAStateObject(this);
public bool HasFolderNames => ArchiveFlags.HasFlag(ArchiveFlags.HasFolderNames);
public bool HasFileNames => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNames);
public bool CompressedByDefault => ArchiveFlags.HasFlag(ArchiveFlags.Compressed);
public bool Bit9Set => ArchiveFlags.HasFlag(ArchiveFlags.HasFileNameBlobs);
public bool HasNameBlobs
{
get
{
if (HeaderType == VersionType.FO3 || HeaderType == VersionType.SSE) return Bit9Set;
return false;
}
}
public void Dump(Action<string> print)
{
print($"File Name: {_streamFactory.Name}");
print($"File Count: {_fileCount}");
print($"Magic: {_magic}");
foreach (var file in Files)
{
print("\n");
file.Dump(print);
}
}
public static async ValueTask<BSAReader> LoadAsync(IStreamFactory factory)
{
await using var stream = await factory.GetStream().ConfigureAwait(false);
using var br = new BinaryReader(stream);
var bsa = new BSAReader { _streamFactory = factory };
bsa.LoadHeaders(br);
return bsa;
}
public static BSAReader Load(AbsolutePath filename)
{
var bsa = new BSAReader { _streamFactory = new NativeFileStreamFactory(filename)};
using var rdr = bsa.GetStream();
bsa.LoadHeaders(rdr);
return bsa;
}
internal BinaryReader GetStream()
{
return new BinaryReader(_streamFactory.GetStream().Result);
}
private void LoadHeaders(BinaryReader rdr)
{
var fourcc = Encoding.ASCII.GetString(rdr.ReadBytes(4));
if (fourcc != "BSA\0")
throw new InvalidDataException("Archive is not a BSA");
_magic = fourcc;
HeaderType = (VersionType)rdr.ReadUInt32();
_folderRecordOffset = rdr.ReadUInt32();
ArchiveFlags = (ArchiveFlags)rdr.ReadUInt32();
_folderCount = rdr.ReadUInt32();
_fileCount = rdr.ReadUInt32();
_totalFolderNameLength = rdr.ReadUInt32();
_totalFileNameLength = rdr.ReadUInt32();
FileFlags = (FileFlags)rdr.ReadUInt32();
_folders = new Lazy<FolderRecord[]>(
isThreadSafe: true,
valueFactory: () => LoadFolderRecords());
_foldersByName = new Lazy<Dictionary<string, FolderRecord>>(
isThreadSafe: true,
valueFactory: GetFolderDictionary);
}
private FolderRecord[] LoadFolderRecords()
{
using var rdr = GetStream();
rdr.BaseStream.Position = _folderRecordOffset;
var folderHeaderLength = FolderRecord.HeaderLength(HeaderType);
ReadOnlyMemorySlice<byte> folderHeaderData = rdr.ReadBytes(checked((int)(folderHeaderLength * _folderCount)));
var ret = new FolderRecord[_folderCount];
for (var idx = 0; idx < _folderCount; idx += 1)
ret[idx] = new FolderRecord(this, folderHeaderData.Slice(idx * folderHeaderLength, folderHeaderLength), idx);
// Slice off appropriate file header data per folder
int fileCountTally = 0;
foreach (var folder in ret)
{
folder.ProcessFileRecordHeadersBlock(rdr, fileCountTally);
fileCountTally = checked((int)(fileCountTally + folder.FileCount));
}
if (HasFileNames)
{
var filenameBlock = new FileNameBlock(this, rdr.BaseStream.Position);
foreach (var folder in ret)
{
folder.FileNameBlock = filenameBlock;
}
}
return ret;
}
private Dictionary<string, FolderRecord> GetFolderDictionary()
{
if (!HasFolderNames)
{
throw new ArgumentException("Cannot get folders by name if the BSA does not have folder names.");
}
var ret = new Dictionary<string, FolderRecord>();
foreach (var folder in _folders.Value)
{
ret.Add(folder.Name!, folder);
}
return ret;
}
public bool TryGetFolder(string path, [MaybeNullWhen(false)] out IFolder folder)
{
if (!HasFolderNames
|| !_foldersByName.Value.TryGetValue(path, out var folderRec))
{
folder = default;
return false;
}
folder = folderRec;
return true;
}
}
}

View File

@ -1,35 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common.Serialization.Json;
using File = Alphaleonis.Win32.Filesystem.File;
namespace Compression.BSA
{
[JsonName("BSAState")]
public class BSAStateObject : ArchiveStateObject
{
public string Magic { get; set; } = string.Empty;
public uint Version { get; set; }
public uint ArchiveFlags { get; set; }
public uint FileFlags { get; set; }
public BSAStateObject()
{
}
public BSAStateObject(BSAReader bsaReader)
{
Magic = bsaReader._magic;
Version = (uint)bsaReader.HeaderType;
ArchiveFlags = (uint)bsaReader.ArchiveFlags;
FileFlags = (uint)bsaReader.FileFlags;
}
public override async Task<IBSABuilder> MakeBuilder(long size)
{
return await BSABuilder.Create(this, size).ConfigureAwait(false);
}
}
}

View File

@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Wabbajack.Common;
namespace Compression.BSA
{
internal class FileNameBlock
{
public readonly Lazy<ReadOnlyMemorySlice<byte>[]> Names;
public FileNameBlock(BSAReader bsa, long position)
{
Names = new Lazy<ReadOnlyMemorySlice<byte>[]>(
mode: System.Threading.LazyThreadSafetyMode.ExecutionAndPublication,
valueFactory: () =>
{
using var stream = bsa.GetStream();
stream.BaseStream.Position = position;
ReadOnlyMemorySlice<byte> data = stream.ReadBytes(checked((int)bsa._totalFileNameLength));
ReadOnlyMemorySlice<byte>[] names = new ReadOnlyMemorySlice<byte>[bsa._fileCount];
for (int i = 0; i < bsa._fileCount; i++)
{
var index = data.Span.IndexOf(default(byte));
if (index == -1)
{
throw new InvalidDataException("Did not end all of its strings in null bytes");
}
names[i] = data.Slice(0, index + 1);
var str = names[i].ReadStringTerm(bsa.HeaderType);
data = data.Slice(index + 1);
}
// Data doesn't seem to need to be fully consumed.
// Official BSAs have overflow of zeros
return names;
});
}
}
}

View File

@ -1,177 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Versioning;
using System.Text;
using System.Threading.Tasks;
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
using K4os.Compression.LZ4.Streams;
using Wabbajack.Common;
using File = Alphaleonis.Win32.Filesystem.File;
namespace Compression.BSA
{
public class FileRecord : IFile
{
public const int HeaderLength = 0x10;
private readonly ReadOnlyMemorySlice<byte> _headerData;
internal readonly int _index;
internal readonly int _overallIndex;
internal readonly FileNameBlock _nameBlock;
internal readonly Lazy<string> _name;
internal Lazy<(uint Size, uint OnDisk, uint Original)> _size;
public ulong Hash => BinaryPrimitives.ReadUInt64LittleEndian(_headerData);
protected uint RawSize => BinaryPrimitives.ReadUInt32LittleEndian(_headerData.Slice(0x8));
public uint Offset => BinaryPrimitives.ReadUInt32LittleEndian(_headerData.Slice(0xC));
public string Name => _name.Value;
public uint Size => _size.Value.Size;
public bool FlipCompression => (RawSize & (0x1 << 30)) > 0;
internal FolderRecord Folder { get; }
internal BSAReader BSA => Folder.BSA;
internal FileRecord(
FolderRecord folderRecord,
ReadOnlyMemorySlice<byte> data,
int index,
int overallIndex,
FileNameBlock nameBlock)
{
_index = index;
_overallIndex = overallIndex;
_headerData = data;
_nameBlock = nameBlock;
Folder = folderRecord;
_name = new Lazy<string>(GetName, System.Threading.LazyThreadSafetyMode.PublicationOnly);
// Will be replaced if CopyDataTo is called before value is created
_size = new Lazy<(uint Size, uint OnDisk, uint Original)>(
mode: System.Threading.LazyThreadSafetyMode.ExecutionAndPublication,
valueFactory: () =>
{
using var rdr = BSA.GetStream();
rdr.BaseStream.Position = Offset;
return ReadSize(rdr);
});
}
public RelativePath Path => new RelativePath(string.IsNullOrEmpty(Folder.Name) ? Name : Folder.Name + "\\" + Name, skipValidation: true);
public bool Compressed
{
get
{
if (FlipCompression) return !BSA.CompressedByDefault;
return BSA.CompressedByDefault;
}
}
public FileStateObject State => new BSAFileStateObject(this);
public async ValueTask CopyDataTo(Stream output)
{
await using var in_file = await BSA._streamFactory.GetStream().ConfigureAwait(false);
using var rdr = new BinaryReader(in_file);
rdr.BaseStream.Position = Offset;
(uint Size, uint OnDisk, uint Original) size = ReadSize(rdr);
if (!_size.IsValueCreated)
{
_size = new Lazy<(uint Size, uint OnDisk, uint Original)>(value: size);
}
if (BSA.HeaderType == VersionType.SSE)
{
if (Compressed && size.Size != size.OnDisk)
{
await using var r = LZ4Stream.Decode(rdr.BaseStream);
await r.CopyToLimitAsync(output, size.Original).ConfigureAwait(false);
}
else
{
await rdr.BaseStream.CopyToLimitAsync(output, size.OnDisk).ConfigureAwait(false);
}
}
else
{
if (Compressed)
{
await using var z = new InflaterInputStream(rdr.BaseStream);
await z.CopyToLimitAsync(output, size.Original).ConfigureAwait(false);
}
else
await rdr.BaseStream.CopyToLimitAsync(output, size.OnDisk).ConfigureAwait(false);
}
}
private string GetName()
{
var names = _nameBlock.Names.Value;
return names[_overallIndex].ReadStringTerm(BSA.HeaderType);
}
private (uint Size, uint OnDisk, uint Original) ReadSize(BinaryReader rdr)
{
uint size = RawSize;
if (FlipCompression)
size = size ^ (0x1 << 30);
if (Compressed)
size -= 4;
byte nameBlobOffset;
if (BSA.HasNameBlobs)
{
nameBlobOffset = rdr.ReadByte();
// Just skip, not using
rdr.BaseStream.Position += nameBlobOffset;
// Minus one more for the size of the name blob offset size
nameBlobOffset++;
}
else
{
nameBlobOffset = 0;
}
uint originalSize;
if (Compressed)
{
originalSize = rdr.ReadUInt32();
}
else
{
originalSize = 0;
}
uint onDiskSize = size - nameBlobOffset;
if (Compressed)
{
return (Size: originalSize, OnDisk: onDiskSize, Original: originalSize);
}
else
{
return (Size: onDiskSize, OnDisk: onDiskSize, Original: originalSize);
}
}
public void Dump(Action<string> print)
{
print($"Name: {Name}");
print($"Offset: {Offset}");
print($"Raw Size: {RawSize}");
print($"Index: {_index}");
}
public async ValueTask<IStreamFactory> GetStreamFactory()
{
var ms = new MemoryStream();
await CopyDataTo(ms);
ms.Position = 0;
return new MemoryStreamFactory(ms, Path);
}
}
}

View File

@ -1,86 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
using System.IO;
using Wabbajack.Common;
namespace Compression.BSA
{
public class FolderRecord : IFolder
{
internal readonly BSAReader BSA;
private readonly ReadOnlyMemorySlice<byte> _data;
internal Lazy<FileRecord[]> _files = null!;
private int _prevFileCount;
internal FileNameBlock FileNameBlock = null!;
internal int Index { get; }
public string? Name { get; private set; }
public IEnumerable<IFile> Files => _files.Value;
internal FolderRecord(BSAReader bsa, ReadOnlyMemorySlice<byte> data, int index)
{
BSA = bsa;
_data = data;
Index = index;
}
private bool IsLongform => BSA.HeaderType == VersionType.SSE;
public ulong Hash => BinaryPrimitives.ReadUInt64LittleEndian(_data);
public int FileCount => checked((int)BinaryPrimitives.ReadUInt32LittleEndian(_data.Slice(0x8)));
public uint Unknown => IsLongform ?
BinaryPrimitives.ReadUInt32LittleEndian(_data.Slice(0xC)) :
0;
public ulong Offset => IsLongform ?
BinaryPrimitives.ReadUInt64LittleEndian(_data.Slice(0x10)) :
BinaryPrimitives.ReadUInt32LittleEndian(_data.Slice(0xC));
public static int HeaderLength(VersionType version)
{
return version switch
{
VersionType.SSE => 0x18,
_ => 0x10,
};
}
internal void ProcessFileRecordHeadersBlock(BinaryReader rdr, int fileCountTally)
{
_prevFileCount = fileCountTally;
var totalFileLen = checked((int)(FileCount * FileRecord.HeaderLength));
ReadOnlyMemorySlice<byte> data;
if (BSA.HasFolderNames)
{
var len = rdr.ReadByte();
data = rdr.ReadBytes(len + totalFileLen);
Name = data.Slice(0, len).ReadStringTerm(BSA.HeaderType);
data = data.Slice(len);
}
else
{
data = rdr.ReadBytes(totalFileLen);
}
_files = new Lazy<FileRecord[]>(
isThreadSafe: true,
valueFactory: () => ParseFileRecords(data));
}
private FileRecord[] ParseFileRecords(ReadOnlyMemorySlice<byte> data)
{
var fileCount = FileCount;
var ret = new FileRecord[fileCount];
for (var idx = 0; idx < fileCount; idx += 1)
{
var fileData = data.Slice(idx * FileRecord.HeaderLength, FileRecord.HeaderLength);
ret[idx] = new FileRecord(this, fileData, idx, idx + _prevFileCount, FileNameBlock);
}
return ret;
}
}
}

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Compression.BSA
{
public enum VersionType : uint
{
TES4 = 0x67,
FO3 = 0x68, // FO3, FNV, TES5
SSE = 0x69,
FO4 = 0x01,
TES3 = 0xFF // Not a real Bethesda version number
}
}

View File

@ -1,47 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
using Wabbajack.Common.FileSignatures;
namespace Compression.BSA
{
public static class BSADispatch
{
public static async ValueTask<IBSAReader> OpenRead(AbsolutePath filename)
{
return await BSASignatures.MatchesAsync(filename) switch
{
Definitions.FileType.TES3 => await TES3Reader.Load(new NativeFileStreamFactory(filename)),
Definitions.FileType.BSA => await BSAReader.LoadAsync(new NativeFileStreamFactory(filename)),
Definitions.FileType.BA2 => await BA2Reader.Load(new NativeFileStreamFactory(filename)),
_ => throw new InvalidDataException("Filename is not a .bsa or .ba2")
};
}
private static SignatureChecker BSASignatures = new SignatureChecker(Definitions.FileType.BSA, Definitions.FileType.BA2, Definitions.FileType.TES3);
public static async ValueTask<bool> MightBeBSA(AbsolutePath filename)
{
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}");
}
}
}
}

View File

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0-windows</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<Version>3.0</Version>
<Nullable>enable</Nullable>
<WarningsAsErrors>nullable</WarningsAsErrors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<PackageVersion>2.4.1.2</PackageVersion>
<Version>2.4.1.2</Version>
<AssemblyVersion>2.4.1.2</AssemblyVersion>
<FileVersion>2.4.1.2</FileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<DocumentationFile>Compression.BSA.xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Genbox.AlphaFS" Version="2.2.2.1" />
<PackageReference Include="K4os.Compression.LZ4.Streams" Version="1.2.6" />
<PackageReference Include="SharpZipLib" Version="1.3.2" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Wabbajack.Common\Wabbajack.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -1,217 +0,0 @@
using System.Runtime.InteropServices;
namespace Compression.BSA
{
/*
* Copied from https://raw.githubusercontent.com/AlexxEG/BSA_Browser/master/Sharp.BSA.BA2/BA2Util/DDS.cs
* which is also GPL3 code. Modified slightly for Wabbajack
*
*/
/*
* Copied from dds.h. Includes (almost) only stuff I need in this project.
*
* Link: https://github.com/digitalutopia1/BA2Lib/blob/master/BA2Lib/dds.h
*
*/
public class DDS
{
public static uint HeaderSizeForFormat(DXGI_FORMAT fmt)
{
switch (fmt)
{
case DXGI_FORMAT.DXGI_FORMAT_BC1_UNORM_SRGB:
case DXGI_FORMAT.DXGI_FORMAT_BC3_UNORM_SRGB:
case DXGI_FORMAT.DXGI_FORMAT_BC4_UNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC5_SNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC6H_UF16:
case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM:
case DXGI_FORMAT.DXGI_FORMAT_BC7_UNORM_SRGB:
return DDS_HEADER_DXT10.Size + DDS_HEADER.Size;
default:
return DDS_HEADER.Size;
}
}
public const int DDS_MAGIC = 0x20534444; // "DDS "
public static uint MAKEFOURCC(char ch0, char ch1, char ch2, char ch3)
{
// This is alien to me...
return ((uint)(byte)(ch0) | ((uint)(byte)(ch1) << 8) | ((uint)(byte)(ch2) << 16 | ((uint)(byte)(ch3) << 24)));
}
public const int DDS_FOURCC = 0x00000004; // DDPF_FOURCC
public const int DDS_RGB = 0x00000040; // DDPF_RGB
public const int DDS_RGBA = 0x00000041; // DDPF_RGB | DDPF_ALPHAPIXELS
public const int DDS_HEADER_FLAGS_TEXTURE = 0x00001007; // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT
public const int DDS_HEADER_FLAGS_MIPMAP = 0x00020000; // DDSD_MIPMAPCOUNT
public const int DDS_HEADER_FLAGS_LINEARSIZE = 0x00080000; // DDSD_LINEARSIZE
public const int DDS_SURFACE_FLAGS_TEXTURE = 0x00001000; // DDSCAPS_TEXTURE
public const int DDS_SURFACE_FLAGS_MIPMAP = 0x00400008; // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP
public const int DDS_ALPHA_MODE_UNKNOWN = 0x0;
}
#region dxgiformat.h
public enum DXGI_FORMAT
{
DXGI_FORMAT_R8G8B8A8_UNORM = 28,
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29,
DXGI_FORMAT_R8_UNORM = 61,
DXGI_FORMAT_BC1_UNORM = 71,
DXGI_FORMAT_BC1_UNORM_SRGB = 72,
DXGI_FORMAT_BC2_UNORM = 74,
DXGI_FORMAT_BC3_UNORM = 77,
DXGI_FORMAT_BC3_UNORM_SRGB = 78,
DXGI_FORMAT_BC4_UNORM = 80,
DXGI_FORMAT_BC5_UNORM = 83,
DXGI_FORMAT_BC5_SNORM = 84,
DXGI_FORMAT_B8G8R8A8_UNORM = 87,
DXGI_FORMAT_B8G8R8X8_UNORM = 88,
DXGI_FORMAT_BC6H_UF16 = 95,
DXGI_FORMAT_BC7_UNORM = 98,
DXGI_FORMAT_BC7_UNORM_SRGB = 99
}
#endregion
public enum DXT10_RESOURCE_DIMENSION
{
DIMENSION_TEXTURE1D = 2,
DIMENSION_TEXTURE2D = 3,
DIMENSION_TEXTURE3D = 4,
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DDS_HEADER
{
public uint dwSize;
public uint dwHeaderFlags;
public uint dwHeight;
public uint dwWidth;
public uint dwPitchOrLinearSize;
public uint dwDepth; // only if DDS_HEADER_FLAGS_VOLUME is set in dwHeaderFlags
public uint dwMipMapCount;
public uint dwReserved1; // [11]
public DDS_PIXELFORMAT PixelFormat; // ddspf
public uint dwSurfaceFlags;
public uint dwCubemapFlags;
public uint dwReserved2; // [3]
public uint GetSize()
{
// 9 uint + DDS_PIXELFORMAT uints + 2 uint arrays with 14 uints total
// each uint 4 bytes each
return (9 * 4) + PixelFormat.GetSize() + (14 * 4);
}
public void Write(System.IO.BinaryWriter bw)
{
bw.Write(dwSize);
bw.Write(dwHeaderFlags);
bw.Write(dwHeight);
bw.Write(dwWidth);
bw.Write(dwPitchOrLinearSize);
bw.Write(dwDepth);
bw.Write(dwMipMapCount);
// Just write it multiple times, since it's never assigned a value anyway
for (int i = 0; i < 11; i++)
bw.Write(dwReserved1);
// DDS_PIXELFORMAT
bw.Write(PixelFormat.dwSize);
bw.Write(PixelFormat.dwFlags);
bw.Write(PixelFormat.dwFourCC);
bw.Write(PixelFormat.dwRGBBitCount);
bw.Write(PixelFormat.dwRBitMask);
bw.Write(PixelFormat.dwGBitMask);
bw.Write(PixelFormat.dwBBitMask);
bw.Write(PixelFormat.dwABitMask);
bw.Write(dwSurfaceFlags);
bw.Write(dwCubemapFlags);
// Just write it multiple times, since it's never assigned a value anyway
for (int i = 0; i < 3; i++)
bw.Write(dwReserved2);
}
public static uint Size
{
get
{
unsafe
{
return (uint)(sizeof(DDS_HEADER) + (sizeof(int) * 10) + (sizeof(int) * 2));
};
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct DDS_HEADER_DXT10
{
public uint dxgiFormat;
public uint resourceDimension;
public uint miscFlag;
public uint arraySize;
public uint miscFlags2;
public void Write(System.IO.BinaryWriter bw)
{
bw.Write(dxgiFormat);
bw.Write(resourceDimension);
bw.Write(miscFlag);
bw.Write(arraySize);
bw.Write(miscFlags2);
}
public static uint Size
{
get
{
unsafe
{
return (uint)sizeof(DDS_HEADER_DXT10);
};
}
}
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct DDS_PIXELFORMAT
{
public uint dwSize;
public uint dwFlags;
public uint dwFourCC;
public uint dwRGBBitCount;
public uint dwRBitMask;
public uint dwGBitMask;
public uint dwBBitMask;
public uint dwABitMask;
public DDS_PIXELFORMAT(uint size, uint flags, uint fourCC, uint rgbBitCount, uint rBitMask, uint gBitMask, uint bBitMask, uint aBitMask)
{
dwSize = size;
dwFlags = flags;
dwFourCC = fourCC;
dwRGBBitCount = rgbBitCount;
dwRBitMask = rBitMask;
dwGBitMask = gBitMask;
dwBBitMask = bBitMask;
dwABitMask = aBitMask;
}
public uint GetSize()
{
// 8 uints, each 4 bytes each
return 8 * 4;
}
}
}

View File

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
namespace Compression.BSA
{
public interface IBSABuilder : IAsyncDisposable
{
Task AddFile(FileStateObject state, Stream src);
Task Build(AbsolutePath filename);
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Wabbajack.Common;
namespace Compression.BSA
{
public interface IBSAReader
{
/// <summary>
/// The files defined by the archive
/// </summary>
IEnumerable<IFile> Files { get; }
ArchiveStateObject State { get; }
void Dump(Action<string> print);
}
}

View File

@ -1,37 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Wabbajack.Common;
namespace Compression.BSA
{
public interface IFile
{
/// <summary>
/// The path of the file inside the archive
/// </summary>
RelativePath Path { get; }
/// <summary>
/// The uncompressed file size
/// </summary>
uint Size { get; }
/// <summary>
/// Get the metadata for the file.
/// </summary>
FileStateObject State { get; }
/// <summary>
/// Copies this entry to the given stream. 100% thread safe, the .bsa will be opened multiple times
/// in order to maintain thread-safe access.
/// </summary>
/// <param name="output"></param>
ValueTask CopyDataTo(Stream output);
void Dump(Action<string> print);
ValueTask<IStreamFactory> GetStreamFactory();
}
}

View File

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Compression.BSA
{
public interface IFolder
{
string? Name { get; }
IEnumerable<IFile> Files { get; }
int FileCount { get; }
}
}

View File

@ -1,45 +0,0 @@
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, IPath path)
{
_data = data;
Name = path;
}
public async ValueTask<Stream> GetStream()
{
return new MemoryStream(_data.GetBuffer(), 0, (int)_data.Length);
}
public DateTime LastModifiedUtc => DateTime.UtcNow;
public IPath Name { get; }
}
public class MemoryBufferFactory : IStreamFactory
{
private readonly byte[] _data;
private int _size;
public MemoryBufferFactory(byte[] data, int size, IPath path)
{
_data = data;
_size = size;
Name = path;
}
public async ValueTask<Stream> GetStream()
{
return new MemoryStream(_data, 0, _size);
}
public DateTime LastModifiedUtc => DateTime.UtcNow;
public IPath Name { get; }
}
}

View File

@ -1,3 +0,0 @@
# Compression.BSA
BSA Compression gets it's own project to remove cluttering.

Some files were not shown because too many files have changed in this diff Show More