aboutsummaryrefslogtreecommitdiff
path: root/libs/libbsarch
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/libbsarch
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/libbsarch')
-rw-r--r--libs/libbsarch/.clang-format117
-rw-r--r--libs/libbsarch/.gitattributes7
-rw-r--r--libs/libbsarch/.github/workflows/build.yml64
-rw-r--r--libs/libbsarch/.github/workflows/linting.yml17
-rw-r--r--libs/libbsarch/.gitignore539
-rw-r--r--libs/libbsarch/.pre-commit-config.yaml20
-rw-r--r--libs/libbsarch/CMakeLists.txt144
-rw-r--r--libs/libbsarch/CMakePresets.json17
-rw-r--r--libs/libbsarch/LICENSE21
-rw-r--r--libs/libbsarch/README.md25
-rw-r--r--libs/libbsarch/Version.cmake3
-rw-r--r--libs/libbsarch/cmake/config.cmake.in37
-rw-r--r--libs/libbsarch/delphi/libbsarch.dpr412
-rw-r--r--libs/libbsarch/delphi/libbsarch.dproj916
-rw-r--r--libs/libbsarch/delphi/wbBSArchive.pas2637
-rw-r--r--libs/libbsarch/examples/libbsarch-visualstudio-test.cpp190
-rw-r--r--libs/libbsarch/examples/libbsarch-visualstudio-test.sln31
-rw-r--r--libs/libbsarch/examples/test_read.ba2bin0 -> 5592601 bytes
-rw-r--r--libs/libbsarch/examples/test_read.bsabin0 -> 5592685 bytes
-rw-r--r--libs/libbsarch/include/libbsarch/base_types.hpp66
-rw-r--r--libs/libbsarch/include/libbsarch/bs_archive.h100
-rw-r--r--libs/libbsarch/include/libbsarch/bs_archive_auto.hpp39
-rw-r--r--libs/libbsarch/include/libbsarch/bs_archive_entries.h24
-rw-r--r--libs/libbsarch/include/libbsarch/libbsarch.h157
-rw-r--r--libs/libbsarch/include/libbsarch/libbsarch.hpp37
-rw-r--r--libs/libbsarch/include/libbsarch/utils/convertible_ostream.hpp37
-rw-r--r--libs/libbsarch/include/libbsarch/utils/convertible_string.hpp50
-rw-r--r--libs/libbsarch/include/libbsarch/utils/string_convert.hpp18
-rw-r--r--libs/libbsarch/src/bs_archive.cpp185
-rw-r--r--libs/libbsarch/src/bs_archive_auto.cpp87
-rw-r--r--libs/libbsarch/src/bs_archive_entries.cpp52
-rw-r--r--libs/libbsarch/src/libbsarch.cpp206
-rw-r--r--libs/libbsarch/src/libbsarch.def38
-rw-r--r--libs/libbsarch/src/utils/convertible_ostream.cpp58
-rw-r--r--libs/libbsarch/src/utils/convertible_string.cpp108
-rw-r--r--libs/libbsarch/src/utils/string_convert.cpp39
-rw-r--r--libs/libbsarch/vcpkg.json18
37 files changed, 6516 insertions, 0 deletions
diff --git a/libs/libbsarch/.clang-format b/libs/libbsarch/.clang-format
new file mode 100644
index 0000000..42a3a23
--- /dev/null
+++ b/libs/libbsarch/.clang-format
@@ -0,0 +1,117 @@
+---
+Language: Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlines: DontAlign
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: true
+AllowShortCaseLabelsOnASingleLine: true
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: false
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ AfterExternBlock: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+ SplitEmptyFunction: false
+ SplitEmptyRecord: true
+ SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: All
+BreakBeforeBraces: Custom
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: true
+ColumnLimit: 120
+CommentPragmas: '^ IWYU pragma:'
+CompactNamespaces: false
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+FixNamespaceComments: true
+ForEachMacros:
+ - forever
+ - foreach
+ - Q_FOREACH
+ - BOOST_FOREACH
+IncludeBlocks: Preserve
+IncludeCategories:
+ - Regex: '^<Q.*'
+ Priority: 200
+ - Regex: '^(<|"(gtest|gmock|isl|json)/)'
+ Priority: 3
+ - Regex: '.*'
+ Priority: 1
+IncludeIsMainRegex: '(Test)?$'
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 150
+PenaltyBreakBeforeFirstCallParameter: 300
+PenaltyBreakComment: 500
+PenaltyBreakFirstLessLess: 400
+PenaltyBreakString: 600
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 50
+PenaltyReturnTypeOnItsOwnLine: 300
+PointerAlignment: Right
+ReflowComments: false
+SortIncludes: true
+SortUsingDeclarations: true
+SpaceAfterCStyleCast: true
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Cpp11
+TabWidth: 4
+UseTab: Never
+...
diff --git a/libs/libbsarch/.gitattributes b/libs/libbsarch/.gitattributes
new file mode 100644
index 0000000..f869712
--- /dev/null
+++ b/libs/libbsarch/.gitattributes
@@ -0,0 +1,7 @@
+# Set the default behavior, in case people don't have core.autocrlf set.
+* text=auto
+
+# Explicitly declare text files you want to always be normalized and converted
+# to native line endings on checkout.
+*.cpp text eol=crlf
+*.h text eol=crlf
diff --git a/libs/libbsarch/.github/workflows/build.yml b/libs/libbsarch/.github/workflows/build.yml
new file mode 100644
index 0000000..f20a40c
--- /dev/null
+++ b/libs/libbsarch/.github/workflows/build.yml
@@ -0,0 +1,64 @@
+name: Build libbsarch
+
+on:
+ push:
+ branches: [master]
+ tags:
+ - "*"
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+env:
+ VCPKG_BINARY_SOURCES: clear;x-azblob,${{ vars.AZ_BLOB_VCPKG_URL }},${{ secrets.AZ_BLOB_SAS }},readwrite
+
+jobs:
+ build:
+ runs-on: windows-2022
+ steps:
+ - name: "Set environmental variables"
+ shell: bash
+ run: |
+ echo "VCPKG_ROOT=$VCPKG_INSTALLATION_ROOT" >> $GITHUB_ENV
+
+ - uses: actions/checkout@v4
+
+ - run: cmake --preset vs2022-windows "-DCMAKE_INSTALL_PREFIX=install"
+
+ # build both Debug and Release for package
+ - name: Build libbsarch
+ run: |
+ cmake --build vsbuild --config Debug --verbose
+ cmake --build vsbuild --config Release --verbose
+
+ - name: Install libbsarch
+ run: |
+ cmake --install vsbuild --config Debug
+ cmake --install vsbuild --config Release
+
+ - name: Upload libbsarch artifact
+ uses: actions/upload-artifact@master
+ with:
+ name: libbsarch
+ path: ./install
+
+ publish:
+ if: github.ref_type == 'tag'
+ needs: build
+ runs-on: windows-2022
+ permissions:
+ contents: write
+ steps:
+ - name: Download Artifact
+ uses: actions/download-artifact@master
+ with:
+ name: libbsarch
+ path: ./install
+
+ - name: Create libbsarch archive
+ run: 7z a libbsarch_${{ github.ref_name }}.7z ./install/*
+
+ - name: Publish Release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ GH_REPO: ${{ github.repository }}
+ run: gh release create --draft=false --notes="${{ github.ref_name }}" "${{ github.ref_name }}" ./libbsarch_${{ github.ref_name }}.7z
diff --git a/libs/libbsarch/.github/workflows/linting.yml b/libs/libbsarch/.github/workflows/linting.yml
new file mode 100644
index 0000000..12681a8
--- /dev/null
+++ b/libs/libbsarch/.github/workflows/linting.yml
@@ -0,0 +1,17 @@
+name: Lint libbsarch
+
+on:
+ push:
+ pull_request:
+ types: [opened, synchronize, reopened]
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - name: Check format
+ uses: ModOrganizer2/check-formatting-action@master
+ with:
+ check-path: "."
+ exclude-regex: "third-party"
diff --git a/libs/libbsarch/.gitignore b/libs/libbsarch/.gitignore
new file mode 100644
index 0000000..4c54425
--- /dev/null
+++ b/libs/libbsarch/.gitignore
@@ -0,0 +1,539 @@
+
+# Created by https://www.gitignore.io/api/qt,c++,cmake,delphi,qtcreator,visualstudio
+# Edit at https://www.gitignore.io/?templates=qt,c++,cmake,delphi,qtcreator,visualstudio
+
+### C++ ###
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+### CMake ###
+CMakeLists.txt.user
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Testing
+Makefile
+cmake_install.cmake
+install_manifest.txt
+compile_commands.json
+CTestTestfile.cmake
+_deps
+
+
+### CMake Patch ###
+# External projects
+*-prefix/
+
+### Delphi ###
+# Uncomment these types if you want even more clean repository. But be careful.
+# It can make harm to an existing project source. Read explanations below.
+#
+# Resource files are binaries containing manifest, project icon and version info.
+# They can not be viewed as text or compared by diff-tools. Consider replacing them with .rc files.
+#*.res
+# Type library file (binary). In old Delphi versions it should be stored.
+# Since Delphi 2009 it is produced from .ridl file and can safely be ignored.
+#*.tlb
+# Diagram Portfolio file. Used by the diagram editor up to Delphi 7.
+# Uncomment this if you are not using diagrams or use newer Delphi version.
+#*.ddp
+# Visual LiveBindings file. Added in Delphi XE2.
+# Uncomment this if you are not using LiveBindings Designer.
+#*.vlb
+# Deployment Manager configuration file for your project. Added in Delphi XE2.
+# Uncomment this if it is not mobile development and you do not use remote debug feature.
+#*.deployproj
+# C++ object files produced when C/C++ Output file generation is configured.
+# Uncomment this if you are not using external objects (zlib library for example).
+#*.obj
+
+# Delphi compiler-generated binaries (safe to delete)
+*.bpl
+*.bpi
+*.dcp
+*.apk
+*.drc
+*.map
+*.dres
+*.rsm
+*.tds
+*.dcu
+*.ocx
+
+# Delphi autogenerated files (duplicated info)
+*.cfg
+# *.hpp
+*Resource.rc
+
+# Delphi local files (user-specific info)
+*.local
+*.identcache
+*.projdata
+*.tvsconfig
+*.dsk
+
+# Delphi history and backups
+__history/
+__recovery/
+*.~*
+
+# Castalia statistics file (since XE7 Castalia is distributed with Delphi)
+*.stat
+
+# Boss dependency manager vendor folder https://github.com/HashLoad/boss
+modules/
+
+### Qt ###
+# C++ objects and libs
+
+# Qt-es
+object_script.*.Release
+object_script.*.Debug
+*_plugin_import.cpp
+/.qmake.cache
+/.qmake.stash
+*.pro.user
+*.pro.user.*
+*.qbs.user
+*.qbs.user.*
+*.moc
+moc_*.cpp
+moc_*.h
+qrc_*.cpp
+ui_*.h
+*.qmlc
+*.jsc
+Makefile*
+*build-*
+vsbuild
+
+# Qt unit tests
+target_wrapper.*
+
+# QtCreator
+*.autosave
+
+# QtCreator Qml
+*.qmlproject.user
+*.qmlproject.user.*
+
+# QtCreator CMake
+CMakeLists.txt.user*
+
+# QtCreator 4.8< compilation database
+
+# QtCreator local machine specific files for imported projects
+*creator.user*
+
+### QtCreator ###
+# gitignore for Qt Creator like IDE for pure C/C++ project without Qt
+# Reference: http://doc.qt.io/qtcreator/creator-project-generic.html
+
+
+
+# Qt Creator autogenerated files
+
+
+# A listing of all the files included in the project
+*.files
+
+# Include directories
+*.includes
+
+# Project configuration settings like predefined Macros
+*.config
+
+# Qt Creator settings
+*.creator
+
+# User project settings
+*.creator.user*
+
+# Qt Creator backups
+
+### VisualStudio ###
+## 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
+*.iobj
+*.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/
+
+# End of https://www.gitignore.io/api/qt,c++,cmake,delphi,qtcreator,visualstudio
+
+install
+examples/build
+examples/vsbuild
+examples/test_ba1
+examples/test_ba2
diff --git a/libs/libbsarch/.pre-commit-config.yaml b/libs/libbsarch/.pre-commit-config.yaml
new file mode 100644
index 0000000..3103a1f
--- /dev/null
+++ b/libs/libbsarch/.pre-commit-config.yaml
@@ -0,0 +1,20 @@
+repos:
+ - repo: https://github.com/pre-commit/pre-commit-hooks
+ rev: v5.0.0
+ hooks:
+ - id: trailing-whitespace
+ - id: end-of-file-fixer
+ - id: check-merge-conflict
+ - id: check-case-conflict
+ - repo: https://github.com/pre-commit/mirrors-clang-format
+ rev: v19.1.5
+ hooks:
+ - id: clang-format
+ 'types_or': [c++, c]
+
+ci:
+ autofix_commit_msg: "[pre-commit.ci] Auto fixes from pre-commit.com hooks."
+ autofix_prs: true
+ autoupdate_commit_msg: "[pre-commit.ci] Pre-commit autoupdate."
+ autoupdate_schedule: quarterly
+ submodules: false
diff --git a/libs/libbsarch/CMakeLists.txt b/libs/libbsarch/CMakeLists.txt
new file mode 100644
index 0000000..d3e994e
--- /dev/null
+++ b/libs/libbsarch/CMakeLists.txt
@@ -0,0 +1,144 @@
+cmake_minimum_required(VERSION 3.16)
+
+include(CMakePackageConfigHelpers)
+
+project(libbsarch CXX)
+
+find_package(mo2-dds-header CONFIG REQUIRED)
+
+get_target_property(SRCS mo2::dds-header SOURCES)
+
+# libbsarch
+add_library(libbsarch SHARED)
+if(WIN32)
+ target_sources(libbsarch
+ PRIVATE
+ src/libbsarch.cpp
+ src/libbsarch.def
+ PUBLIC
+ FILE_SET HEADERS
+ BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include
+ FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/libbsarch.h
+ )
+else()
+ target_sources(libbsarch
+ PRIVATE
+ src/libbsarch.cpp
+ PUBLIC
+ FILE_SET HEADERS
+ BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include
+ FILES ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/libbsarch.h
+ )
+ # Set default symbol visibility to hidden, export only marked symbols
+ target_compile_options(libbsarch PRIVATE -fvisibility=hidden)
+endif()
+target_include_directories(libbsarch PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch)
+set_target_properties(libbsarch PROPERTIES DEBUG_POSTFIX "d" CXX_STANDARD 11)
+target_link_libraries(libbsarch PUBLIC mo2::dds-header)
+target_compile_definitions(libbsarch PRIVATE BSARCH_DLL_EXPORT)
+
+# preprocessor definitions
+if(MSVC)
+ target_compile_options(libbsarch PRIVATE
+ /W3 /EHsc /MP
+ $<$<CONFIG:DEBUG>:/MDd /Od>
+ $<$<CONFIG:RELEASE>:/Zi>
+ )
+endif()
+
+
+# libbsarch_OOP
+add_library(libbsarch_OOP STATIC)
+
+target_sources(libbsarch_OOP
+ PRIVATE
+ src/bs_archive_entries.cpp
+ src/utils/string_convert.cpp
+ src/utils/convertible_ostream.cpp
+ src/utils/convertible_string.cpp
+ src/bs_archive_auto.cpp
+ src/bs_archive.cpp
+ PUBLIC
+ FILE_SET HEADERS
+ BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/include
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/base_types.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/bs_archive_auto.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/bs_archive.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/bs_archive_entries.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/libbsarch.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/libbsarch.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/utils/convertible_string.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/utils/convertible_ostream.hpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/utils/string_convert.hpp
+)
+target_include_directories(libbsarch_OOP PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch
+ ${CMAKE_CURRENT_SOURCE_DIR}/include/libbsarch/utils)
+target_link_libraries(libbsarch_OOP PUBLIC mo2::dds-header)
+
+set_target_properties(libbsarch_OOP PROPERTIES
+ DEBUG_POSTFIX "d"
+ CXX_STANDARD 17
+)
+
+if (MSVC)
+ target_compile_options(libbsarch_OOP PRIVATE /MP)
+endif()
+
+# target_link_libraries(libbsarch_OOP PUBLIC libbsarch)
+
+# install
+
+# finding 32 or 64bits
+if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ # 64 bits
+ set(lib_delphi "${CMAKE_CURRENT_SOURCE_DIR}/delphi/lib64")
+ message("libbsarch: using 64bits DLL")
+elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
+ # 32 bits
+ set(lib_delphi "${CMAKE_CURRENT_SOURCE_DIR}/delphi/lib")
+ message("libbsarch: using 32bits DLL")
+endif()
+
+install(TARGETS libbsarch libbsarch_OOP EXPORT libbsarchTargets FILE_SET HEADERS)
+if(WIN32)
+ install(FILES $<TARGET_PDB_FILE:libbsarch> DESTINATION pdb OPTIONAL)
+endif()
+install(EXPORT libbsarchTargets
+ FILE mo2-libbsarch-targets.cmake
+ NAMESPACE mo2::
+ DESTINATION lib/cmake/mo2-libbsarch
+)
+
+if(WIN32)
+ # install the true DLLs (Windows only - Delphi-built BSArch)
+ install(FILES ${lib_delphi}/libbsarch.dll DESTINATION bin CONFIGURATIONS Release RelWithDebInfo MinSizeRel)
+ install(FILES ${lib_delphi}/libbsarchd.dll DESTINATION bin CONFIGURATIONS Debug)
+endif()
+
+configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/cmake/config.cmake.in
+ "${CMAKE_CURRENT_BINARY_DIR}/mo2-libbsarch-config.cmake"
+ INSTALL_DESTINATION "lib/cmake/mo2-libbsarch"
+ NO_SET_AND_CHECK_MACRO
+ NO_CHECK_REQUIRED_COMPONENTS_MACRO
+)
+
+
+include(${CMAKE_CURRENT_SOURCE_DIR}/Version.cmake)
+set(LIBBSARCH_VERSION
+ ${LIBBSARCH_MAJOR_VERSION}.${LIBBSARCH_MINOR_VERSION}.${LIBBSARCH_PATCH_VERSION})
+
+
+write_basic_package_version_file(
+ "${CMAKE_CURRENT_BINARY_DIR}/mo2-libbsarch-config-version.cmake"
+ VERSION "${LIBBSARCH_VERSION}"
+ COMPATIBILITY AnyNewerVersion
+ ARCH_INDEPENDENT
+)
+
+install(FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/mo2-libbsarch-config.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/mo2-libbsarch-config-version.cmake
+ DESTINATION lib/cmake/mo2-libbsarch
+)
diff --git a/libs/libbsarch/CMakePresets.json b/libs/libbsarch/CMakePresets.json
new file mode 100644
index 0000000..acc6d97
--- /dev/null
+++ b/libs/libbsarch/CMakePresets.json
@@ -0,0 +1,17 @@
+{
+ "configurePresets": [
+ {
+ "binaryDir": "${sourceDir}/vsbuild",
+ "toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
+ "generator": "Visual Studio 17 2022",
+ "name": "vs2022-windows"
+ }
+ ],
+ "buildPresets": [
+ {
+ "name": "vs2022-windows",
+ "configurePreset": "vs2022-windows"
+ }
+ ],
+ "version": 4
+}
diff --git a/libs/libbsarch/LICENSE b/libs/libbsarch/LICENSE
new file mode 100644
index 0000000..c2799e0
--- /dev/null
+++ b/libs/libbsarch/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Deorder
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/libs/libbsarch/README.md b/libs/libbsarch/README.md
new file mode 100644
index 0000000..671b6ff
--- /dev/null
+++ b/libs/libbsarch/README.md
@@ -0,0 +1,25 @@
+# BSArchive DLL and C++ bindings
+
+BSArchive Dynamic Link Library and C++ bindings
+
+## How to build
+
+1) Create a `XEditPath` environment variable and set it to the directory containing the XEdit code
+2) Build the DLL in Delphi by opening the `libbsarch.dproj` project
+3) Build the LIB in Visual Studio by opening the `libbsarch.sln` project
+
+## How to use
+
+With CMake:
+Use ``add_subdirectory`` and ``target_link_library``
+Then ``#include "libbsarch.h"`` for the C wrapper, or ``#include "bs_archive_auto"`` for the C++ wrapper.
+See the example for code use.
+
+Without CMake:
+Use `libbsarch.dll` (provided in delphi/lib), `libbsarch.lib` and `libbsarch.h` in your project.
+
+## Credits
+
+The original BSArchive can be found at: https://github.com/TES5Edit/TES5Edit/tree/dev/Tools/BSArchive
+The version in this project had been modified for better compatibility with C/C++ and
+allow the users to allocate their own memory in some cases.
diff --git a/libs/libbsarch/Version.cmake b/libs/libbsarch/Version.cmake
new file mode 100644
index 0000000..59f33b6
--- /dev/null
+++ b/libs/libbsarch/Version.cmake
@@ -0,0 +1,3 @@
+set(LIBBSARCH_MAJOR_VERSION 0)
+set(LIBBSARCH_MINOR_VERSION 1)
+set(LIBBSARCH_PATCH_VERSION 2)
diff --git a/libs/libbsarch/cmake/config.cmake.in b/libs/libbsarch/cmake/config.cmake.in
new file mode 100644
index 0000000..8bb228f
--- /dev/null
+++ b/libs/libbsarch/cmake/config.cmake.in
@@ -0,0 +1,37 @@
+@PACKAGE_INIT@
+
+include ( "${CMAKE_CURRENT_LIST_DIR}/mo2-libbsarch-targets.cmake" )
+
+target_link_libraries(mo2::libbsarch INTERFACE mo2::libbsarch_OOP)
+
+# libbsarch is usually be build in Debug or Release/RelWithDebInfo, so we need to map
+# missing configurations to avoid issue with CMP0111
+get_target_property(_LIBBSARCH_CONFIGURATIONS mo2::libbsarch IMPORTED_CONFIGURATIONS)
+list(FIND _LIBBSARCH_CONFIGURATIONS "RELEASE" _LIBBSARCH_HAS_RELEASE)
+if (_LIBBSARCH_HAS_RELEASE EQUAL -1)
+ set_target_properties(mo2::libbsarch PROPERTIES
+ MAP_IMPORTED_CONFIG_MINSIZEREL RelWithDebInfo
+ MAP_IMPORTED_CONFIG_RELEASE RelWithDebInfo
+ )
+ set_property(TARGET mo2::libbsarch APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
+ set_property(TARGET mo2::libbsarch APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
+ set_target_properties(mo2::libbsarch_OOP PROPERTIES
+ MAP_IMPORTED_CONFIG_MINSIZEREL RelWithDebInfo
+ MAP_IMPORTED_CONFIG_RELEASE RelWithDebInfo
+ )
+ set_property(TARGET mo2::libbsarch_OOP APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
+ set_property(TARGET mo2::libbsarch_OOP APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
+else()
+ set_target_properties(mo2::libbsarch PROPERTIES
+ MAP_IMPORTED_CONFIG_MINSIZEREL Release
+ MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release
+ )
+ set_property(TARGET mo2::libbsarch APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
+ set_property(TARGET mo2::libbsarch APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
+ set_target_properties(mo2::libbsarch_OOP PROPERTIES
+ MAP_IMPORTED_CONFIG_MINSIZEREL Release
+ MAP_IMPORTED_CONFIG_RELWITHDEBINFO Release
+ )
+ set_property(TARGET mo2::libbsarch_OOP APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
+ set_property(TARGET mo2::libbsarch_OOP APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
+endif()
diff --git a/libs/libbsarch/delphi/libbsarch.dpr b/libs/libbsarch/delphi/libbsarch.dpr
new file mode 100644
index 0000000..34c0e35
--- /dev/null
+++ b/libs/libbsarch/delphi/libbsarch.dpr
@@ -0,0 +1,412 @@
+library libbsarch;
+
+uses
+ Math,
+ Classes,
+ Types,
+ SysUtils,
+ wbStreams,
+ wbBSArchive;
+
+type
+ TwbBSResultCode = (
+ BSA_RESULT_NONE = 0,
+ BSA_RESULT_EXCEPTION = -1
+ );
+ TwbBSResultMessage = packed record
+ code: ShortInt;
+ text: array[0..1023] of WideChar;
+ end;
+ TwbBSResultMessageBuffer = packed record
+ buffer: TwbBSResultBuffer;
+ message: TwbBSResultMessage;
+ end;
+
+{Shared}
+
+function string_to_cstring(
+ aString: String;
+ const aStringBufferSize: Cardinal;
+ const aStringBuffer: PChar
+): Integer; stdcall;
+begin
+ Result := Min(aStringBufferSize, Length(AString));
+ if (aStringBuffer <> nil) and (Result > 0) then
+ Move(AString[1], aStringBuffer^, Result * SizeOf(WideChar));
+end;
+
+function string_to_cstring_end(
+ aString: String;
+ const aStringBufferSize: Cardinal;
+ const aStringBuffer: PChar
+): Integer; stdcall;
+begin
+ Result := Min(aStringBufferSize - 1, Length(AString));
+ if (aStringBuffer <> nil) and (Result > 0) then
+ begin
+ Move(AString[1], aStringBuffer^, Result * SizeOf(WideChar));
+ aStringBuffer[Result] := #0;
+ end;
+end;
+
+procedure exception_handler(E: Exception; var Result: TwbBSResultMessage); stdcall;
+begin
+ Result.code := ShortInt(BSA_RESULT_EXCEPTION);
+ string_to_cstring_end(E.Message, Length(Result.text), Result.text);
+end;
+
+procedure buffer_exception_handler(E: Exception; var Result: TwbBSResultMessageBuffer); stdcall;
+begin
+ Result.buffer.size := 0;
+ Result.buffer.data := nil;
+ Result.message.code := ShortInt(BSA_RESULT_EXCEPTION);
+ string_to_cstring_end(E.Message, Length(Result.message.text), Result.message.text);
+end;
+
+{BSA File List}
+
+function bsa_entry_list_create:Pointer; stdcall;
+begin
+ try
+ Result := TStringList.Create;
+ except
+ Result := nil;
+ end;
+end;
+
+function bsa_entry_list_free(obj: Pointer): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TStringList(obj).Clear();
+ TStringList(obj).free;
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_entry_list_count(obj: Pointer): Integer; stdcall;
+begin
+ try
+ Result := TStringList(obj).Count;
+ except
+ Result := Ord(BSA_RESULT_EXCEPTION);
+ end;
+end;
+
+function bsa_entry_list_add(obj: Pointer; const aEntryString: PChar): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TStringList(obj).Add(String(aEntryString));
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_entry_list_get(obj: Pointer; const aIndex: Cardinal; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Integer; stdcall;
+begin
+ try
+ Result := string_to_cstring_end(TStringList(obj).Strings[aIndex], aStringBufferSize, aStringBuffer);
+ except
+ Result := Integer(BSA_RESULT_EXCEPTION);
+ end;
+end;
+
+{BSArchive}
+
+function bsa_create:Pointer; stdcall;
+begin
+ try
+ Result := TwbBSArchive.Create;
+ except
+ Result := nil;
+ end;
+end;
+
+function bsa_free(obj: Pointer): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).free;
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_load_from_file(obj: Pointer; const aFilePath: PChar): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).LoadFromFile(String(aFilePath));
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_create_archive(obj: Pointer; const aFilePath: PChar; aType: TBSArchiveType; aFileList: TStringList): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).CreateArchive(String(aFilePath), aType, aFileList);
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_save(obj: Pointer): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).Save;
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_add_file_from_disk_root(obj: Pointer; const aRootDir, aSourcePath: PChar): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).AddFileDiskRoot(String(aRootDir), String(aSourcePath));
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_add_file_from_disk(obj: Pointer; const aFilePath, aSourcePath: PChar): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).AddFileDisk(String(aFilePath), String(aSourcePath));
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_add_file_from_memory(obj: Pointer; const aFilePath: PChar; const aSize: Cardinal; const aData: PByte): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).AddFileData(String(aFilePath), aSize, aData);
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_find_file_record(obj: Pointer; const aFilePath: PChar): Pointer; stdcall;
+begin
+ try
+ Result := TwbBSArchive(obj).FindFileRecord(String(aFilePath));
+ except
+ Result := nil
+ end;
+end;
+
+function bsa_extract_file_data_by_record(obj: Pointer; aFileRecord: Pointer): TwbBSResultMessageBuffer; stdcall;
+begin
+ Result.message.code := Ord(BSA_RESULT_NONE);
+ try
+ Result.buffer := TwbBSArchive(obj).ExtractFileData(aFileRecord);
+ except
+ on E: Exception do
+ buffer_exception_handler(E, Result);
+ end;
+end;
+
+function bsa_extract_file_data_by_filename(obj: Pointer; const aFilePath: PChar): TwbBSResultMessageBuffer; stdcall;
+begin
+ Result.message.code := Ord(BSA_RESULT_NONE);
+ try
+ Result.buffer := TwbBSArchive(obj).ExtractFileData(String(aFilePath));
+ except
+ on E: Exception do
+ buffer_exception_handler(E, Result);
+ end;
+end;
+
+function bsa_file_data_free(obj: Pointer; fileDataResult: TwbBSResultMessageBuffer): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).ReleaseFileData(fileDataResult.buffer);
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_extract_file(obj: Pointer; const aFilePath, aSaveAs: PChar): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).ExtractFile(String(aFilePath), String(aSaveAs));
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_iterate_files(obj: Pointer; aProc: TBSFileIterationProc; aContext: Pointer): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).IterateFiles(aProc, aContext);
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_file_exists(obj: Pointer; const aFilePath: string): Boolean; stdcall;
+begin
+ try
+ Result := TwbBSArchive(obj).FileExists(aFilePath);
+ except
+ Result := False;
+ end;
+end;
+
+function bsa_get_resource_list(obj: Pointer; const aEntryResultList: TStringList; aFolder: PChar): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).ResourceList(aEntryResultList, String(aFolder));
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_close(obj: Pointer): TwbBSResultMessage; stdcall;
+begin
+ Result.code := Ord(BSA_RESULT_NONE);
+ try
+ TwbBSArchive(obj).Close;
+ except
+ on E: Exception do
+ exception_handler(E, Result);
+ end;
+end;
+
+function bsa_filename_get(obj: Pointer; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Cardinal; stdcall;
+begin
+ Result := string_to_cstring_end(TwbBSArchive(obj).FileName, aStringBufferSize, aStringBuffer);
+end;
+
+function bsa_archive_type_get(obj: Pointer): TBSArchiveType; stdcall;
+begin
+ Result := TwbBSArchive(obj).ArchiveType;
+end;
+
+function bsa_version_get(obj: Pointer): Cardinal; stdcall;
+begin
+ Result := TwbBSArchive(obj).Version;
+end;
+
+function bsa_format_name_get(obj: Pointer; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Cardinal; stdcall;
+begin
+ Result := string_to_cstring_end(TwbBSArchive(obj).FormatName, aStringBufferSize, aStringBuffer);
+end;
+
+function bsa_file_count_get(obj: Pointer): Cardinal; stdcall;
+begin
+ Result := TwbBSArchive(obj).FileCount;
+end;
+
+function bsa_archive_flags_get(obj: Pointer): Cardinal; stdcall;
+begin
+ Result := TwbBSArchive(obj).ArchiveFlags;
+end;
+
+procedure bsa_archive_flags_set(obj: Pointer; flags: Cardinal); stdcall;
+begin
+ TwbBSArchive(obj).ArchiveFlags := flags;
+end;
+
+function bsa_file_flags_get(obj: Pointer): Cardinal; stdcall;
+begin
+ Result := TwbBSArchive(obj).FileFlags;
+end;
+
+procedure bsa_file_flags_set(obj: Pointer; flags: Cardinal); stdcall;
+begin
+ TwbBSArchive(obj).FileFlags := flags;
+end;
+
+function bsa_compress_get(obj: Pointer): Boolean; stdcall;
+begin
+ Result := TwbBSArchive(obj).Compress;
+end;
+
+procedure bsa_compress_set(obj: Pointer; compress: Boolean); stdcall;
+begin
+ TwbBSArchive(obj).Compress := compress;
+end;
+
+function bsa_share_data_get(obj: Pointer): Boolean; stdcall;
+begin
+ Result := TwbBSArchive(obj).ShareData;
+end;
+
+procedure bsa_share_data_set(obj: Pointer; shareData: Boolean); stdcall;
+begin
+ TwbBSArchive(obj).ShareData := shareData;
+end;
+
+procedure bsa_file_dds_info_callback_set(obj: Pointer; aProc: TBSFileDDSInfoProc; aContext: Pointer); stdcall;
+begin
+ TwbBSArchive(obj).DDSInfoProc := aProc;
+ TwbBSArchive(obj).DDSInfoProcContext := aContext;
+end;
+
+exports
+ bsa_entry_list_create,
+ bsa_entry_list_free,
+ bsa_entry_list_count,
+ bsa_entry_list_add,
+ bsa_entry_list_get,
+
+ bsa_filename_get,
+ bsa_archive_type_get,
+ bsa_version_get,
+ bsa_format_name_get,
+ bsa_file_count_get,
+ bsa_archive_flags_get,
+ bsa_archive_flags_set,
+ bsa_file_flags_get,
+ bsa_file_flags_set,
+ bsa_compress_get,
+ bsa_compress_set,
+ bsa_share_data_get,
+ bsa_share_data_set,
+ bsa_file_dds_info_callback_set,
+
+ bsa_close,
+ bsa_get_resource_list,
+ bsa_file_exists,
+ bsa_iterate_files,
+ bsa_extract_file,
+ bsa_file_data_free,
+ bsa_extract_file_data_by_filename,
+ bsa_extract_file_data_by_record,
+ bsa_find_file_record,
+ bsa_add_file_from_memory,
+ bsa_add_file_from_disk_root,
+ bsa_add_file_from_disk,
+ bsa_save,
+ bsa_create_archive,
+ bsa_load_from_file,
+ bsa_create,
+ bsa_free
+ ;
+end.
diff --git a/libs/libbsarch/delphi/libbsarch.dproj b/libs/libbsarch/delphi/libbsarch.dproj
new file mode 100644
index 0000000..1e928c6
--- /dev/null
+++ b/libs/libbsarch/delphi/libbsarch.dproj
@@ -0,0 +1,916 @@
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <ProjectGuid>{381E4D59-4D34-46B3-B039-1B3C3ABEC58F}</ProjectGuid>
+ <MainSource>libbsarch.dpr</MainSource>
+ <Base>True</Base>
+ <Config Condition="'$(Config)'==''">Release</Config>
+ <TargetedPlatforms>3</TargetedPlatforms>
+ <AppType>Library</AppType>
+ <FrameworkType>None</FrameworkType>
+ <ProjectVersion>19.5</ProjectVersion>
+ <Platform Condition="'$(Platform)'==''">Win64</Platform>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Config)'=='Base' or '$(Base)'!=''">
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Base)'=='true') or '$(Base_Win32)'!=''">
+ <Base_Win32>true</Base_Win32>
+ <CfgParent>Base</CfgParent>
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Base)'=='true') or '$(Base_Win64)'!=''">
+ <Base_Win64>true</Base_Win64>
+ <CfgParent>Base</CfgParent>
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Config)'=='Release' or '$(Cfg_1)'!=''">
+ <Cfg_1>true</Cfg_1>
+ <CfgParent>Base</CfgParent>
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win32)'!=''">
+ <Cfg_1_Win32>true</Cfg_1_Win32>
+ <CfgParent>Cfg_1</CfgParent>
+ <Cfg_1>true</Cfg_1>
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_1)'=='true') or '$(Cfg_1_Win64)'!=''">
+ <Cfg_1_Win64>true</Cfg_1_Win64>
+ <CfgParent>Cfg_1</CfgParent>
+ <Cfg_1>true</Cfg_1>
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Config)'=='Debug' or '$(Cfg_2)'!=''">
+ <Cfg_2>true</Cfg_2>
+ <CfgParent>Base</CfgParent>
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="('$(Platform)'=='Win32' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win32)'!=''">
+ <Cfg_2_Win32>true</Cfg_2_Win32>
+ <CfgParent>Cfg_2</CfgParent>
+ <Cfg_2>true</Cfg_2>
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="('$(Platform)'=='Win64' and '$(Cfg_2)'=='true') or '$(Cfg_2_Win64)'!=''">
+ <Cfg_2_Win64>true</Cfg_2_Win64>
+ <CfgParent>Cfg_2</CfgParent>
+ <Cfg_2>true</Cfg_2>
+ <Base>true</Base>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Base)'!=''">
+ <DCC_E>false</DCC_E>
+ <DCC_F>false</DCC_F>
+ <DCC_K>false</DCC_K>
+ <DCC_N>false</DCC_N>
+ <DCC_S>false</DCC_S>
+ <DCC_ImageBase>00400000</DCC_ImageBase>
+ <GenDll>true</GenDll>
+ <SanitizedProjectName>libbsarch</SanitizedProjectName>
+ <VerInfo_Locale>1033</VerInfo_Locale>
+ <VerInfo_Keys>CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName=</VerInfo_Keys>
+ <DCC_Namespace>System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace)</DCC_Namespace>
+ <XEditPath>C:\dev\TES5Edit</XEditPath>
+ <DCC_UnitSearchPath>$(XEditPath)\Core;$(XEditPath)\External\lz4;$(XEditPath)\External\TForge;$(XEditPath)\External\TForge\Source\Include;$(XEditPath)\External\TForge\Source\Shared;$(XEditPath)\External\TForge\Source\Engine\Hashes;$(XEditPath)\External\TForge\Source\Engine\Forge;$(XEditPath)\External\ZLibEx;$(XEditPath)\External\lz4\lib\delphi;$(XEditPath)\External\lz4\common;$(DCC_UnitSearchPath)</DCC_UnitSearchPath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Base_Win32)'!=''">
+ <DCC_Namespace>System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)</DCC_Namespace>
+ <BT_BuildType>Debug</BT_BuildType>
+ <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
+ <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys>
+ <VerInfo_Locale>1033</VerInfo_Locale>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Base_Win64)'!=''">
+ <DCC_Namespace>System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)</DCC_Namespace>
+ <BT_BuildType>Debug</BT_BuildType>
+ <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
+ <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
+ <Manifest_File>(None)</Manifest_File>
+ <AppDPIAwarenessMode>none</AppDPIAwarenessMode>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Cfg_1)'!=''">
+ <DCC_Define>RELEASE;$(DCC_Define)</DCC_Define>
+ <DCC_DebugInformation>0</DCC_DebugInformation>
+ <DCC_LocalDebugSymbols>false</DCC_LocalDebugSymbols>
+ <DCC_SymbolReferenceInfo>0</DCC_SymbolReferenceInfo>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Cfg_1_Win32)'!=''">
+ <DCC_ExeOutput>Win32\Release\</DCC_ExeOutput>
+ <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
+ <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys>
+ <Manifest_File>(None)</Manifest_File>
+ <Debugger_HostApplication>Win32\Release\libbsarch-visualstudio-test.exe</Debugger_HostApplication>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Cfg_1_Win64)'!=''">
+ <DCC_ExeOutput>X64\Release\</DCC_ExeOutput>
+ <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
+ <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
+ <Manifest_File>(None)</Manifest_File>
+ <Debugger_HostApplication>X64\Release\libbsarch-visualstudio-test.exe</Debugger_HostApplication>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Cfg_2)'!=''">
+ <DCC_Define>DEBUG;$(DCC_Define)</DCC_Define>
+ <DCC_Optimize>false</DCC_Optimize>
+ <DCC_GenerateStackFrames>true</DCC_GenerateStackFrames>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Cfg_2_Win32)'!=''">
+ <Debugger_HostApplication>Win32\Debug\libbsarch-visualstudio-test.exe</Debugger_HostApplication>
+ <DCC_ExeOutput>Win32\Debug\</DCC_ExeOutput>
+ <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
+ <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName)</VerInfo_Keys>
+ <Manifest_File>(None)</Manifest_File>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Cfg_2_Win64)'!=''">
+ <VerInfo_IncludeVerInfo>true</VerInfo_IncludeVerInfo>
+ <VerInfo_Keys>CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=</VerInfo_Keys>
+ <Manifest_File>(None)</Manifest_File>
+ <DCC_ExeOutput>X64\Debug\</DCC_ExeOutput>
+ <Debugger_HostApplication>D:\Projects\libbsarch\x64\Debug\libbsarch-visualstudio-test.exe</Debugger_HostApplication>
+ <Debugger_CWD>D:\Projects\libbsarch\x64\Debug</Debugger_CWD>
+ </PropertyGroup>
+ <ItemGroup>
+ <DelphiCompile Include="$(MainSource)">
+ <MainSource>MainSource</MainSource>
+ </DelphiCompile>
+ <BuildConfiguration Include="Base">
+ <Key>Base</Key>
+ </BuildConfiguration>
+ <BuildConfiguration Include="Release">
+ <Key>Cfg_1</Key>
+ <CfgParent>Base</CfgParent>
+ </BuildConfiguration>
+ <BuildConfiguration Include="Debug">
+ <Key>Cfg_2</Key>
+ <CfgParent>Base</CfgParent>
+ </BuildConfiguration>
+ </ItemGroup>
+ <ProjectExtensions>
+ <Borland.Personality>Delphi.Personality.12</Borland.Personality>
+ <Borland.ProjectType/>
+ <BorlandProject>
+ <Delphi.Personality>
+ <Source>
+ <Source Name="MainSource">libbsarch.dpr</Source>
+ </Source>
+ <Excluded_Packages>
+ <Excluded_Packages Name="$(BDSBIN)\dcloffice2k280.bpl">Microsoft Office 2000 Sample Automation Server Wrapper Components</Excluded_Packages>
+ <Excluded_Packages Name="$(BDSBIN)\dclofficexp280.bpl">Microsoft Office XP Sample Automation Server Wrapper Components</Excluded_Packages>
+ </Excluded_Packages>
+ </Delphi.Personality>
+ <Platforms>
+ <Platform value="Win32">True</Platform>
+ <Platform value="Win64">True</Platform>
+ </Platforms>
+ <ModelSupport>False</ModelSupport>
+ <Deployment Version="4">
+ <DeployFile LocalName="$(BDS)\Redist\iossimulator\libcgunwind.1.0.dylib" Class="DependencyModule">
+ <Platform Name="iOSSimulator">
+ <Overwrite>true</Overwrite>
+ </Platform>
+ </DeployFile>
+ <DeployFile LocalName="$(BDS)\Redist\iossimulator\libpcre.dylib" Class="DependencyModule">
+ <Platform Name="iOSSimulator">
+ <Overwrite>true</Overwrite>
+ </Platform>
+ </DeployFile>
+ <DeployFile LocalName="$(BDS)\Redist\osx32\libcgunwind.1.0.dylib" Class="DependencyModule">
+ <Platform Name="OSX32">
+ <Overwrite>true</Overwrite>
+ </Platform>
+ </DeployFile>
+ <DeployFile LocalName="X64\Release\libbsarch.dll" Configuration="Release" Class="ProjectOutput">
+ <Platform Name="Win64">
+ <RemoteName>libbsarch.dll</RemoteName>
+ <Overwrite>true</Overwrite>
+ </Platform>
+ </DeployFile>
+ <DeployClass Name="AdditionalDebugSymbols">
+ <Platform Name="OSX32">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Win32">
+ <Operation>0</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidClasses">
+ <Platform Name="Android">
+ <RemoteDir>classes</RemoteDir>
+ <Operation>64</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>classes</RemoteDir>
+ <Operation>64</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidFileProvider">
+ <Platform Name="Android">
+ <RemoteDir>res\xml</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\xml</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidGDBServer">
+ <Platform Name="Android">
+ <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidLibnativeArmeabiFile">
+ <Platform Name="Android">
+ <RemoteDir>library\lib\armeabi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>library\lib\armeabi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidLibnativeArmeabiv7aFile">
+ <Platform Name="Android64">
+ <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidLibnativeMipsFile">
+ <Platform Name="Android">
+ <RemoteDir>library\lib\mips</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>library\lib\mips</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidServiceOutput">
+ <Platform Name="Android">
+ <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>library\lib\arm64-v8a</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidServiceOutput_Android32">
+ <Platform Name="Android64">
+ <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidSplashImageDef">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidSplashStyles">
+ <Platform Name="Android">
+ <RemoteDir>res\values</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\values</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="AndroidSplashStylesV21">
+ <Platform Name="Android">
+ <RemoteDir>res\values-v21</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\values-v21</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_Colors">
+ <Platform Name="Android">
+ <RemoteDir>res\values</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\values</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_DefaultAppIcon">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_LauncherIcon144">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-xxhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-xxhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_LauncherIcon192">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-xxxhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-xxxhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_LauncherIcon36">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-ldpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-ldpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_LauncherIcon48">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-mdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-mdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_LauncherIcon72">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-hdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-hdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_LauncherIcon96">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-xhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-xhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_NotificationIcon24">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-mdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-mdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_NotificationIcon36">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-hdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-hdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_NotificationIcon48">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-xhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-xhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_NotificationIcon72">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-xxhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-xxhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_NotificationIcon96">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-xxxhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-xxxhdpi</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_SplashImage426">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-small</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-small</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_SplashImage470">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-normal</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-normal</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_SplashImage640">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-large</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-large</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_SplashImage960">
+ <Platform Name="Android">
+ <RemoteDir>res\drawable-xlarge</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\drawable-xlarge</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="Android_Strings">
+ <Platform Name="Android">
+ <RemoteDir>res\values</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>res\values</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="DebugSymbols">
+ <Platform Name="iOSSimulator">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="OSX32">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Win32">
+ <Operation>0</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="DependencyFramework">
+ <Platform Name="OSX32">
+ <Operation>1</Operation>
+ <Extensions>.framework</Extensions>
+ </Platform>
+ <Platform Name="OSX64">
+ <Operation>1</Operation>
+ <Extensions>.framework</Extensions>
+ </Platform>
+ <Platform Name="OSXARM64">
+ <Operation>1</Operation>
+ <Extensions>.framework</Extensions>
+ </Platform>
+ <Platform Name="Win32">
+ <Operation>0</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="DependencyModule">
+ <Platform Name="OSX32">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="OSX64">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="OSXARM64">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="Win32">
+ <Operation>0</Operation>
+ <Extensions>.dll;.bpl</Extensions>
+ </Platform>
+ </DeployClass>
+ <DeployClass Required="true" Name="DependencyPackage">
+ <Platform Name="iOSDevice32">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="iOSDevice64">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="OSX32">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="OSX64">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="OSXARM64">
+ <Operation>1</Operation>
+ <Extensions>.dylib</Extensions>
+ </Platform>
+ <Platform Name="Win32">
+ <Operation>0</Operation>
+ <Extensions>.bpl</Extensions>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="File">
+ <Platform Name="Android">
+ <Operation>0</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <Operation>0</Operation>
+ </Platform>
+ <Platform Name="iOSDevice32">
+ <Operation>0</Operation>
+ </Platform>
+ <Platform Name="iOSDevice64">
+ <Operation>0</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <Operation>0</Operation>
+ </Platform>
+ <Platform Name="OSX32">
+ <Operation>0</Operation>
+ </Platform>
+ <Platform Name="OSX64">
+ <Operation>0</Operation>
+ </Platform>
+ <Platform Name="OSXARM64">
+ <Operation>0</Operation>
+ </Platform>
+ <Platform Name="Win32">
+ <Operation>0</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="ProjectAndroidManifest">
+ <Platform Name="Android">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="ProjectOSXDebug"/>
+ <DeployClass Name="ProjectOSXEntitlements"/>
+ <DeployClass Name="ProjectOSXInfoPList"/>
+ <DeployClass Name="ProjectOSXResource">
+ <Platform Name="OSX32">
+ <RemoteDir>Contents\Resources</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="OSX64">
+ <RemoteDir>Contents\Resources</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="OSXARM64">
+ <RemoteDir>Contents\Resources</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Required="true" Name="ProjectOutput">
+ <Platform Name="Android">
+ <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Android64">
+ <RemoteDir>library\lib\arm64-v8a</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSDevice32">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSDevice64">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Linux64">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="OSX32">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="OSX64">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="OSXARM64">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Win32">
+ <Operation>0</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="ProjectOutput_Android32">
+ <Platform Name="Android64">
+ <RemoteDir>library\lib\armeabi-v7a</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="ProjectUWPManifest">
+ <Platform Name="Win32">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Win64">
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="ProjectiOSDeviceDebug">
+ <Platform Name="iOSDevice32">
+ <RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="ProjectiOSEntitlements"/>
+ <DeployClass Name="ProjectiOSInfoPList"/>
+ <DeployClass Name="ProjectiOSLaunchScreen"/>
+ <DeployClass Name="ProjectiOSResource">
+ <Platform Name="iOSDevice32">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSDevice64">
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="UWP_DelphiLogo150">
+ <Platform Name="Win32">
+ <RemoteDir>Assets</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Win64">
+ <RemoteDir>Assets</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="UWP_DelphiLogo44">
+ <Platform Name="Win32">
+ <RemoteDir>Assets</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="Win64">
+ <RemoteDir>Assets</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iOS_AppStore1024">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPad_AppIcon152">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPad_AppIcon167">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPad_Launch2x">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPad_LaunchDark2x">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPad_Notification40">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPad_Setting58">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPad_SpotLight80">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_AppIcon120">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_AppIcon180">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_Launch2x">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_Launch3x">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_LaunchDark2x">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_LaunchDark3x">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_Notification40">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_Notification60">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_Setting58">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_Setting87">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_Spotlight120">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <DeployClass Name="iPhone_Spotlight80">
+ <Platform Name="iOSDevice64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ <Platform Name="iOSSimARM64">
+ <RemoteDir>..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset</RemoteDir>
+ <Operation>1</Operation>
+ </Platform>
+ </DeployClass>
+ <ProjectRoot Platform="Android" Name="$(PROJECTNAME)"/>
+ <ProjectRoot Platform="Android64" Name="$(PROJECTNAME)"/>
+ <ProjectRoot Platform="iOSDevice32" Name="$(PROJECTNAME).app"/>
+ <ProjectRoot Platform="iOSDevice64" Name="$(PROJECTNAME).app"/>
+ <ProjectRoot Platform="iOSSimARM64" Name="$(PROJECTNAME).app"/>
+ <ProjectRoot Platform="Linux64" Name="$(PROJECTNAME)"/>
+ <ProjectRoot Platform="OSX32" Name="$(PROJECTNAME)"/>
+ <ProjectRoot Platform="OSX64" Name="$(PROJECTNAME)"/>
+ <ProjectRoot Platform="OSXARM64" Name="$(PROJECTNAME)"/>
+ <ProjectRoot Platform="Win32" Name="$(PROJECTNAME)"/>
+ <ProjectRoot Platform="Win64" Name="$(PROJECTNAME)"/>
+ </Deployment>
+ </BorlandProject>
+ <ProjectFileVersion>12</ProjectFileVersion>
+ </ProjectExtensions>
+ <Import Project="$(BDS)\Bin\CodeGear.Delphi.Targets" Condition="Exists('$(BDS)\Bin\CodeGear.Delphi.Targets')"/>
+ <Import Project="$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj" Condition="Exists('$(APPDATA)\Embarcadero\$(BDSAPPDATABASEDIR)\$(PRODUCTVERSION)\UserTools.proj')"/>
+ <Import Project="$(MSBuildProjectName).deployproj" Condition="Exists('$(MSBuildProjectName).deployproj')"/>
+</Project>
diff --git a/libs/libbsarch/delphi/wbBSArchive.pas b/libs/libbsarch/delphi/wbBSArchive.pas
new file mode 100644
index 0000000..533ee1c
--- /dev/null
+++ b/libs/libbsarch/delphi/wbBSArchive.pas
@@ -0,0 +1,2637 @@
+{******************************************************************************
+
+ This Source Code Form is subject to the terms of the Mozilla Public License,
+ v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain
+ one at https://mozilla.org/MPL/2.0/.
+
+*******************************************************************************}
+
+unit wbBSArchive;
+
+interface
+
+uses
+ System.SysUtils,
+ System.Classes,
+ Winapi.Windows,
+ System.Threading,
+ System.SyncObjs,
+ System.Generics.Defaults,
+ System.Generics.Collections,
+ wbStreams,
+ tfTypes,
+ tfMD5;
+
+const
+ csBSAVersion = '0.9d';
+
+type
+ // per file compression options
+ TPackingCompression = (pcGlobal, pcCompress, pcUncompress);
+
+ TBGSCompressionType = (ctZlib, ctLZ4Frame, ctLZ4Block);
+
+ TMagic4 = array [0..3] of AnsiChar;
+ PMagic4 = ^TMagic4;
+
+ TDXGI = (
+ DXGI_FORMAT_UNKNOWN,
+ DXGI_FORMAT_R32G32B32A32_TYPELESS,
+ DXGI_FORMAT_R32G32B32A32_FLOAT,
+ DXGI_FORMAT_R32G32B32A32_UINT,
+ DXGI_FORMAT_R32G32B32A32_SINT,
+ DXGI_FORMAT_R32G32B32_TYPELESS,
+ DXGI_FORMAT_R32G32B32_FLOAT,
+ DXGI_FORMAT_R32G32B32_UINT,
+ DXGI_FORMAT_R32G32B32_SINT,
+ DXGI_FORMAT_R16G16B16A16_TYPELESS,
+ DXGI_FORMAT_R16G16B16A16_FLOAT,
+ DXGI_FORMAT_R16G16B16A16_UNORM,
+ DXGI_FORMAT_R16G16B16A16_UINT,
+ DXGI_FORMAT_R16G16B16A16_SNORM,
+ DXGI_FORMAT_R16G16B16A16_SINT,
+ DXGI_FORMAT_R32G32_TYPELESS,
+ DXGI_FORMAT_R32G32_FLOAT,
+ DXGI_FORMAT_R32G32_UINT,
+ DXGI_FORMAT_R32G32_SINT,
+ DXGI_FORMAT_R32G8X24_TYPELESS,
+ DXGI_FORMAT_D32_FLOAT_S8X24_UINT,
+ DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS,
+ DXGI_FORMAT_X32_TYPELESS_G8X24_UINT,
+ DXGI_FORMAT_R10G10B10A2_TYPELESS,
+ DXGI_FORMAT_R10G10B10A2_UNORM,
+ DXGI_FORMAT_R10G10B10A2_UINT,
+ DXGI_FORMAT_R11G11B10_FLOAT,
+ DXGI_FORMAT_R8G8B8A8_TYPELESS,
+ DXGI_FORMAT_R8G8B8A8_UNORM,
+ DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
+ DXGI_FORMAT_R8G8B8A8_UINT,
+ DXGI_FORMAT_R8G8B8A8_SNORM,
+ DXGI_FORMAT_R8G8B8A8_SINT,
+ DXGI_FORMAT_R16G16_TYPELESS,
+ DXGI_FORMAT_R16G16_FLOAT,
+ DXGI_FORMAT_R16G16_UNORM,
+ DXGI_FORMAT_R16G16_UINT,
+ DXGI_FORMAT_R16G16_SNORM,
+ DXGI_FORMAT_R16G16_SINT,
+ DXGI_FORMAT_R32_TYPELESS,
+ DXGI_FORMAT_D32_FLOAT,
+ DXGI_FORMAT_R32_FLOAT,
+ DXGI_FORMAT_R32_UINT,
+ DXGI_FORMAT_R32_SINT,
+ DXGI_FORMAT_R24G8_TYPELESS,
+ DXGI_FORMAT_D24_UNORM_S8_UINT,
+ DXGI_FORMAT_R24_UNORM_X8_TYPELESS,
+ DXGI_FORMAT_X24_TYPELESS_G8_UINT,
+ DXGI_FORMAT_R8G8_TYPELESS,
+ DXGI_FORMAT_R8G8_UNORM,
+ DXGI_FORMAT_R8G8_UINT,
+ DXGI_FORMAT_R8G8_SNORM,
+ DXGI_FORMAT_R8G8_SINT,
+ DXGI_FORMAT_R16_TYPELESS,
+ DXGI_FORMAT_R16_FLOAT,
+ DXGI_FORMAT_D16_UNORM,
+ DXGI_FORMAT_R16_UNORM,
+ DXGI_FORMAT_R16_UINT,
+ DXGI_FORMAT_R16_SNORM,
+ DXGI_FORMAT_R16_SINT,
+ DXGI_FORMAT_R8_TYPELESS,
+ DXGI_FORMAT_R8_UNORM,
+ DXGI_FORMAT_R8_UINT,
+ DXGI_FORMAT_R8_SNORM,
+ DXGI_FORMAT_R8_SINT,
+ DXGI_FORMAT_A8_UNORM,
+ DXGI_FORMAT_R1_UNORM,
+ DXGI_FORMAT_R9G9B9E5_SHAREDEXP,
+ DXGI_FORMAT_R8G8_B8G8_UNORM,
+ DXGI_FORMAT_G8R8_G8B8_UNORM,
+ DXGI_FORMAT_BC1_TYPELESS,
+ DXGI_FORMAT_BC1_UNORM,
+ DXGI_FORMAT_BC1_UNORM_SRGB,
+ DXGI_FORMAT_BC2_TYPELESS,
+ DXGI_FORMAT_BC2_UNORM,
+ DXGI_FORMAT_BC2_UNORM_SRGB,
+ DXGI_FORMAT_BC3_TYPELESS,
+ DXGI_FORMAT_BC3_UNORM,
+ DXGI_FORMAT_BC3_UNORM_SRGB,
+ DXGI_FORMAT_BC4_TYPELESS,
+ DXGI_FORMAT_BC4_UNORM,
+ DXGI_FORMAT_BC4_SNORM,
+ DXGI_FORMAT_BC5_TYPELESS,
+ DXGI_FORMAT_BC5_UNORM,
+ DXGI_FORMAT_BC5_SNORM,
+ DXGI_FORMAT_B5G6R5_UNORM,
+ DXGI_FORMAT_B5G5R5A1_UNORM,
+ DXGI_FORMAT_B8G8R8A8_UNORM,
+ DXGI_FORMAT_B8G8R8X8_UNORM,
+ DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM,
+ DXGI_FORMAT_B8G8R8A8_TYPELESS,
+ DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
+ DXGI_FORMAT_B8G8R8X8_TYPELESS,
+ DXGI_FORMAT_B8G8R8X8_UNORM_SRGB,
+ DXGI_FORMAT_BC6H_TYPELESS,
+ DXGI_FORMAT_BC6H_UF16,
+ DXGI_FORMAT_BC6H_SF16,
+ DXGI_FORMAT_BC7_TYPELESS,
+ DXGI_FORMAT_BC7_UNORM,
+ DXGI_FORMAT_BC7_UNORM_SRGB,
+ DXGI_FORMAT_AYUV,
+ DXGI_FORMAT_Y410,
+ DXGI_FORMAT_Y416,
+ DXGI_FORMAT_NV12,
+ DXGI_FORMAT_P010,
+ DXGI_FORMAT_P016,
+ DXGI_FORMAT_420_OPAQUE,
+ DXGI_FORMAT_YUY2,
+ DXGI_FORMAT_Y210,
+ DXGI_FORMAT_Y216,
+ DXGI_FORMAT_NV11,
+ DXGI_FORMAT_AI44,
+ DXGI_FORMAT_IA44,
+ DXGI_FORMAT_P8,
+ DXGI_FORMAT_A8P8,
+ DXGI_FORMAT_B4G4R4A4_UNORM,
+ DXGI_FORMAT_P208,
+ DXGI_FORMAT_V208,
+ DXGI_FORMAT_V408
+ );
+
+ TDDSHeader = packed record
+ Magic: TMagic4;
+ dwSize: Cardinal;
+ dwFlags: Cardinal;
+ dwHeight: Cardinal;
+ dwWidth: Cardinal;
+ dwPitchOrLinearSize: Cardinal;
+ dwDepth: Cardinal;
+ dwMipMapCount: Cardinal;
+ dwReserved1: array [0..10] of Cardinal;
+ ddspf: packed record
+ dwSize: Cardinal;
+ dwFlags: Cardinal;
+ dwFourCC: TMagic4;
+ dwRGBBitCount: Cardinal;
+ dwRBitMask: Cardinal;
+ dwGBitMask: Cardinal;
+ dwBBitMask: Cardinal;
+ dwABitMask: Cardinal;
+ end;
+ dwCaps: Cardinal;
+ dwCaps2: Cardinal;
+ dwCaps3: Cardinal;
+ dwCaps4: Cardinal;
+ dwReserved2: Cardinal;
+ end;
+ PDDSHeader = ^TDDSHeader;
+
+ TDDSHeaderDX10 = packed record
+ dxgiFormat: Integer;
+ resourceDimension: Cardinal;
+ miscFlags: Cardinal;
+ arraySize: Cardinal;
+ miscFlags2: Cardinal;
+ end;
+ PDDSHeaderDX10 = ^TDDSHeaderDX10;
+
+const
+ DDSD_CAPS = $00000001;
+ DDSD_HEIGHT = $00000002;
+ DDSD_WIDTH = $00000004;
+ DDSD_PITCH = $00000008;
+ DDSD_PIXELFORMAT = $00001000;
+ DDSD_MIPMAPCOUNT = $00020000;
+ DDSD_LINEARSIZE = $00080000;
+ DDSD_DEPTH = $00800000;
+ DDSCAPS_COMPLEX = $00000008;
+ DDSCAPS_TEXTURE = $00001000;
+ DDSCAPS_MIPMAP = $00400000;
+ DDSCAPS2_CUBEMAP = $00000200;
+ DDSCAPS2_POSITIVEX = $00000400;
+ DDSCAPS2_NEGATIVEX = $00000800;
+ DDSCAPS2_POSITIVEY = $00001000;
+ DDSCAPS2_NEGATIVEY = $00002000;
+ DDSCAPS2_POSITIVEZ = $00004000;
+ DDSCAPS2_NEGATIVEZ = $00008000;
+ DDSCAPS2_VOLUME = $00200000;
+ DDPF_ALPHAPIXELS = $00000001;
+ DDPF_ALPHA = $00000002;
+ DDPF_FOURCC = $00000004;
+ DDPF_RGB = $00000040;
+ DDPF_YUV = $00000200;
+ DDPF_LUMINANCE = $00020000;
+
+ // DX10
+ DDS_DIMENSION_TEXTURE2D = $00000003;
+ DDS_RESOURCE_MISC_TEXTURECUBE = $00000004;
+
+type
+ TwbBSArchive = class;
+
+ TwbResourceDict = TDictionary<string, TwbNothing>;
+
+ TBSArchiveType = (baNone, baTES3, baTES4, baFO3, baSSE, baFO4, baFO4dds, baSF, baSFdds);
+ TBSArchiveState = (stReading, stWriting);
+ TBSArchiveStates = set of TBSArchiveState;
+ TBSFileIterationProc = function(aArchive: Pointer; const aFileName: string;
+ aFileRecord: Pointer; aFolderRecord: Pointer; aData: Pointer): Boolean; stdcall;
+
+ TDDSInfo = record Width, Height, MipMaps: Integer; end;
+ TBSFileDDSInfoProc = procedure(aArchive: Pointer; const aFileName: string;
+ var aInfo: TDDSInfo; aContext: Pointer); stdcall;
+
+ TwbBSHeaderTES3 = packed record
+ HashOffset: Cardinal;
+ FileCount: Cardinal;
+ end;
+
+ TwbBSFileTES3 = record
+ Hash: UInt64;
+ Size: Cardinal;
+ Offset: Cardinal;
+ Name: string;
+ end;
+ PwbBSFileTES3 = ^TwbBSFileTES3;
+
+ TwbBSHeaderTES4 = packed record
+ FoldersOffset: Cardinal;
+ Flags: Cardinal;
+ FolderCount: Cardinal;
+ FileCount: Cardinal;
+ FolderNamesLength: Cardinal;
+ FileNamesLength: Cardinal;
+ FileFlags: Cardinal;
+ end;
+
+ TwbBSFileTES4 = record
+ Hash: UInt64;
+ Size: Cardinal;
+ Offset: Int64;
+ Name: string;
+ PackingCompression: TPackingCompression;
+ function Compress(bsa: TwbBSArchive): Boolean; // compress when packing into a new archive
+ function Compressed(bsa: TwbBSArchive): Boolean; // compressed in existing archive
+ function RawSize: Cardinal;
+ end;
+ PwbBSFileTES4 = ^TwbBSFileTES4;
+
+ TwbBSFolderTES4 = record
+ Hash: UInt64;
+ FileCount: Cardinal;
+ Unk32: Cardinal;
+ Offset: Int64;
+ Name: string;
+ Files: array of TwbBSFileTES4;
+ end;
+ PwbBSFolderTES4 = ^TwbBSFolderTES4;
+
+ TwbBSHeaderFO4 = packed record
+ Magic: TMagic4;
+ FileCount: Cardinal;
+ FileTableOffset: Int64;
+ end;
+
+ TwbBSHeaderSFv2 = packed record
+ Unknown1: Cardinal;
+ Unknown2: Cardinal;
+ end;
+
+ TwbBSHeaderSFv3 = packed record
+ Unknown1: Cardinal;
+ Unknown2: Cardinal;
+ CompressionMethod: Cardinal;
+ end;
+
+ TwbBSTexChunkRec = record
+ Size : Cardinal;
+ PackedSize : Cardinal;
+ Offset : Int64;
+ StartMip : Word;
+ EndMip : Word;
+ end;
+ PwbBSTexChunkRec = ^TwbBSTexChunkRec;
+
+ TwbBSFileFO4 = record
+ NameHash: Cardinal;
+ Ext: TMagic4;
+ DirHash: Cardinal;
+ // GNRL archive format
+ Unknown: Cardinal;
+ Offset: Int64;
+ PackedSize: Cardinal;
+ Size: Cardinal;
+ //
+ // DX10 archive format
+ UnknownTex : Byte;
+ //ChunkHeaderSize: Word;
+ Height : Word;
+ Width : Word;
+ NumMips : Byte;
+ DXGIFormat : Byte;
+ CubeMaps : Word;
+ TexChunks : array of TwbBSTexChunkRec;
+ //
+ Name: string;
+ PackingCompression: TPackingCompression;
+ function DXGIFormatName: string;
+ function Compress(bsa: TwbBSArchive): Boolean; // compress when packing into a new archive
+ function Compressed(bsa: TwbBSArchive): Boolean; // compressed in existing archive
+ end;
+ PwbBSFileFO4 = ^TwbBSFileFO4;
+
+ TPackedDataHash = TMD5Digest;
+ TPackedDataInfo = record
+ Size: Cardinal;
+ Hash: TPackedDataHash;
+ FileRecord: Pointer;
+ end;
+ PPackedDataInfo = ^TPackedDataInfo;
+
+ TwbBSResultBuffer = packed record
+ size: Cardinal;
+ data: PByte;
+ end;
+
+ TwbBSArchive = class
+ private
+ fStream: TwbBaseCachedFileStream;
+ fStates: TBSArchiveStates;
+ fType: TBSArchiveType;
+ fFileName: string;
+ fMagic: TMagic4;
+ fVersion: Cardinal;
+ fCompress: Boolean;
+ fCompressionType: TBGSCompressionType;
+ fShareData: Boolean;
+ fMultiThreaded: Boolean;
+ fDDSInfoProc: TBSFileDDSInfoProc;
+ fDDSInfoProcContext: Pointer;
+
+ fHeaderTES3: TwbBSHeaderTES3;
+ fFilesTES3: array of TwbBSFileTES3;
+
+ fHeaderTES4: TwbBSHeaderTES4;
+ fFoldersTES4: array of TwbBSFolderTES4;
+
+ fHeaderFO4: TwbBSHeaderFO4;
+ fHeaderSFv2: TwbBSHeaderSFv2;
+ fHeaderSFv3: TwbBSHeaderSFv3;
+ fFilesFO4: array of TwbBSFileFO4;
+ fMaxChunkCount: Integer;
+ fSingleMipChunkX: Integer;
+ fSingleMipChunkY: Integer;
+
+ fDataOffset: Int64;
+
+ fPackedData: array of TPackedDataInfo;
+ fPackedDataCount: Integer;
+
+ {$IF CompilerVersion >= 34.0} { Delphi 10.4 }
+ Sync: TLightweightMREW;
+ {$ELSE}
+ Sync: IReadWriteSync;
+ {$IFEND}
+
+ function GetArchiveFormatName: string;
+ function GetFileCount: Cardinal;
+ function GetCreatedArchiveSize: Int64;
+ procedure SetArchiveFlags(aFlags: Cardinal);
+ procedure SetMultiThreaded(aValue: Boolean);
+ function FindFileRecordTES3(const aFileName: string; var aFileIdx: Integer): Boolean;
+ function FindFileRecordTES4(const aFileName: string; var aFolderIdx, aFileIdx: Integer): Boolean;
+ function FindFileRecordFO4(const aFileName: string; var aFileIdx: Integer): Boolean;
+ function GetDDSMipChunkNum(var aDDSInfo: TDDSInfo): Integer;
+ function CalcDataHash(aData: Pointer; aLen: Cardinal): TPackedDataHash;
+ function FindPackedData(aSize: Cardinal; aHash: TPackedDataHash; aFileRecord: Pointer): Boolean;
+ procedure AddPackedData(aSize: Cardinal; aHash: TPackedDataHash; aFileRecord: Pointer);
+ procedure PackData(aFileRecord: Pointer; const aFileName: string;
+ aDataHash: TPackedDataHash; aData: PByte; aSize: Integer;
+ aCompress: Boolean; aDoCompress: Boolean = False);
+ procedure CompressStream(aSrc, aDst: TStream);
+ procedure DecompressBuf(aSrc: Pointer; aSrcSize: Integer; aDst: Pointer; aDstSize: Integer);
+
+ public
+ constructor Create;
+ destructor Destroy; override;
+ procedure LoadFromFile(const aFileName: string);
+ procedure CreateArchive(const aFileName: string; aType: TBSArchiveType;
+ aFilesList: TStringList = nil);
+ procedure Save;
+ procedure AddFileDisk(const aFilePath, aSourcePath: string);
+ procedure AddFileDiskRoot(const aRootDir, aSourcePath: string);
+ procedure AddFileData(const aFileName: string; const aSize: Cardinal; const aData: PByte); overload;
+ function FindFileRecord(const aFileName: string): Pointer;
+ function ExtractFileData(aFileRecord: Pointer): TwbBSResultBuffer; overload;
+ function ExtractFileData(const aFileName: string): TwbBSResultBuffer; overload;
+ procedure ReleaseFileData(fileDataResult: TwbBSResultBuffer);
+ procedure ExtractFile(const aFileName, aSaveAs: string);
+ procedure IterateFiles(aProc: TBSFileIterationProc; aData: Pointer = nil;
+ aSingleThreaded: Boolean = False);
+ function FileExists(const aFileName: string): Boolean;
+ procedure ResourceList(const aList: TStrings; aFolder: string = '');
+ procedure ResourceDict(const aDict: TwbResourceDict; aFolder: string = '');
+ //procedure IterateFolders(aProc: TBSFileIterationProc);
+ procedure Close;
+
+ procedure SyncBeginWrite;
+ procedure SyncEndWrite;
+
+ property FileName: string read fFileName;
+ property ArchiveType: TBSArchiveType read fType;
+ property Version: Cardinal read fVersion;
+ property FormatName: string read GetArchiveFormatName;
+ property FileCount: Cardinal read GetFileCount;
+ property CreatedArchiveSize: Int64 read GetCreatedArchiveSize;
+ property ArchiveFlags: Cardinal read fHeaderTES4.Flags write SetArchiveFlags;
+ property FileFlags: Cardinal read fHeaderTES4.FileFlags write fHeaderTES4.FileFlags;
+ property Compress: Boolean read fCompress write fCompress;
+ property ShareData: Boolean read fShareData write fShareData;
+ property MultiThreaded: Boolean read fMultiThreaded write SetMultiThreaded;
+ property DDSInfoProc: TBSFileDDSInfoProc read fDDSInfoProc write fDDSInfoProc;
+ property DDSInfoProcContext: Pointer read fDDSInfoProcContext write fDDSInfoProcContext;
+ end;
+
+const
+ cArchiveFormatNames: array[TBSArchiveType] of string = (
+ 'None',
+ 'Morrowind',
+ 'Oblivion',
+ 'Skyrim LE, New Vegas, Fallout 3',
+ 'Skyrim SE, Skyrim AE',
+ 'Fallout 4',
+ 'Fallout 4 DDS',
+ 'Starfield',
+ 'Starfield DDS'
+ );
+
+ cArchiveTypeExtensions: array[TBSArchiveType] of string = (
+ '.bsa',
+ '.bsa',
+ '.bsa',
+ '.bsa',
+ '.bsa',
+ '.ba2',
+ '.ba2',
+ '.ba2',
+ '.ba2'
+ );
+
+ cArchiveFlagNames: array [0..31] of string = (
+ 'Include Directory Names', 'Include File Names', 'Compressed',
+ 'Retain Directory Names', 'Retain File Names',
+ 'Retain File Name Offsets', 'XBox 360 Archive',
+ 'Retain Strings During Startup',
+ 'Embed File Names', 'XMem Codec', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''
+ );
+
+ cFileFlagNames: array [0..31] of string = (
+ 'Meshes', 'Textures', 'Menus', 'Sounds',
+ 'Voices', 'Shaders', 'Trees', 'Fonts',
+ 'Misc', '', '', '',
+ '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''
+ );
+
+function SplitDirName(const aFileName: string; var Dir, Name: string): Integer;
+function SplitNameExt(const aFileName: string; var Name, Ext: string; aNoExtDot: Boolean = False): Integer;
+function CreateHashTES3(const aFileName: string): UInt64;
+function CreateHashTES4(const aFileName: string): UInt64; overload;
+function CreateHashTES4(const aName, aExt: string): UInt64; overload;
+function CreateHashFO4(const aFileName: string): Cardinal;
+
+implementation
+
+uses
+ TypInfo,
+ zlibEx,
+ lz4io;
+
+const
+ MAGIC_TES3: TMagic4 = #0#1#0#0;
+ MAGIC_BSA : TMagic4 = 'BSA'#0;
+ MAGIC_BTDX: TMagic4 = 'BTDX';
+ MAGIC_GNRL: TMagic4 = 'GNRL';
+ MAGIC_DX10: TMagic4 = 'DX10';
+ MAGIC_DDS : TMagic4 = 'DDS ';
+ MAGIC_DXT1: TMagic4 = 'DXT1';
+ MAGIC_DXT3: TMagic4 = 'DXT3';
+ MAGIC_DXT5: TMagic4 = 'DXT5';
+ MAGIC_ATI1: TMagic4 = 'ATI1';
+ MAGIC_ATI2: TMagic4 = 'ATI2';
+ MAGIC_BC4S: TMagic4 = 'BC4S';
+ MAGIC_BC4U: TMagic4 = 'BC4U';
+ MAGIC_BC5S: TMagic4 = 'BC5S';
+ MAGIC_BC5U: TMagic4 = 'BC5U';
+
+ iFileFO4Unknown = $00100100;
+ iFileFO4Tail = $BAADF00D;
+
+ { https://github.com/jonwd7/bae/blob/master/src/bsa.h }
+
+ // header versions
+ HEADER_VERSION_TES4 = $67; // Oblivion
+ HEADER_VERSION_FO3 = $68; // FO3, FNV, TES5
+ HEADER_VERSION_SSE = $69; // SSE
+ HEADER_VERSION_FO4v1 = $01; // FO4
+ HEADER_VERSION_SF2 = $02; // SF
+ HEADER_VERSION_SF3 = $03; // SF
+ HEADER_VERSION_FO4NGv7 = $07; // FO4NG
+ HEADER_VERSION_FO4NGv8 = $08; // FO4NG2
+
+
+ // archive flags
+ ARCHIVE_PATHNAMES = $0001; // Whether the BSA has names for paths
+ ARCHIVE_FILENAMES = $0002; // Whether the BSA has names for files
+ ARCHIVE_COMPRESS = $0004; // Whether the files are compressed in archive (invert file's compression flag)
+ ARCHIVE_RETAINDIR = $0008;
+ ARCHIVE_RETAINNAME = $0010;
+ ARCHIVE_RETAINFOFF = $0020;
+ ARCHIVE_XBOX360 = $0040;
+ ARCHIVE_STARTUPSTR = $0080;
+ ARCHIVE_EMBEDNAME = $0100; // Whether the name is prefixed to the data
+ ARCHIVE_XMEM = $0200;
+ ARCHIVE_UNKNOWN10 = $0400;
+
+ // file flags
+ FILE_NIF = $0001;
+ FILE_DDS = $0002;
+ FILE_XML = $0004;
+ FILE_WAV = $0008;
+ FILE_MP3 = $0010;
+ FILE_TXT = $0020; // TXT, HTML, BAT, SCC
+ FILE_SPT = $0040;
+ FILE_FNT = $0080; // TEX, FNT
+ FILE_MISC = $0100; // CTL and others
+
+ FILE_SIZE_COMPRESS = $40000000; // Whether the file is compressed
+
+ crc32table : array [0..255] of Cardinal = (
+ $00000000, $77073096, $ee0e612c, $990951ba, $076dc419, $706af48f,
+ $e963a535, $9e6495a3, $0edb8832, $79dcb8a4, $e0d5e91e, $97d2d988,
+ $09b64c2b, $7eb17cbd, $e7b82d07, $90bf1d91, $1db71064, $6ab020f2,
+ $f3b97148, $84be41de, $1adad47d, $6ddde4eb, $f4d4b551, $83d385c7,
+ $136c9856, $646ba8c0, $fd62f97a, $8a65c9ec, $14015c4f, $63066cd9,
+ $fa0f3d63, $8d080df5, $3b6e20c8, $4c69105e, $d56041e4, $a2677172,
+ $3c03e4d1, $4b04d447, $d20d85fd, $a50ab56b, $35b5a8fa, $42b2986c,
+ $dbbbc9d6, $acbcf940, $32d86ce3, $45df5c75, $dcd60dcf, $abd13d59,
+ $26d930ac, $51de003a, $c8d75180, $bfd06116, $21b4f4b5, $56b3c423,
+ $cfba9599, $b8bda50f, $2802b89e, $5f058808, $c60cd9b2, $b10be924,
+ $2f6f7c87, $58684c11, $c1611dab, $b6662d3d, $76dc4190, $01db7106,
+ $98d220bc, $efd5102a, $71b18589, $06b6b51f, $9fbfe4a5, $e8b8d433,
+ $7807c9a2, $0f00f934, $9609a88e, $e10e9818, $7f6a0dbb, $086d3d2d,
+ $91646c97, $e6635c01, $6b6b51f4, $1c6c6162, $856530d8, $f262004e,
+ $6c0695ed, $1b01a57b, $8208f4c1, $f50fc457, $65b0d9c6, $12b7e950,
+ $8bbeb8ea, $fcb9887c, $62dd1ddf, $15da2d49, $8cd37cf3, $fbd44c65,
+ $4db26158, $3ab551ce, $a3bc0074, $d4bb30e2, $4adfa541, $3dd895d7,
+ $a4d1c46d, $d3d6f4fb, $4369e96a, $346ed9fc, $ad678846, $da60b8d0,
+ $44042d73, $33031de5, $aa0a4c5f, $dd0d7cc9, $5005713c, $270241aa,
+ $be0b1010, $c90c2086, $5768b525, $206f85b3, $b966d409, $ce61e49f,
+ $5edef90e, $29d9c998, $b0d09822, $c7d7a8b4, $59b33d17, $2eb40d81,
+ $b7bd5c3b, $c0ba6cad, $edb88320, $9abfb3b6, $03b6e20c, $74b1d29a,
+ $ead54739, $9dd277af, $04db2615, $73dc1683, $e3630b12, $94643b84,
+ $0d6d6a3e, $7a6a5aa8, $e40ecf0b, $9309ff9d, $0a00ae27, $7d079eb1,
+ $f00f9344, $8708a3d2, $1e01f268, $6906c2fe, $f762575d, $806567cb,
+ $196c3671, $6e6b06e7, $fed41b76, $89d32be0, $10da7a5a, $67dd4acc,
+ $f9b9df6f, $8ebeeff9, $17b7be43, $60b08ed5, $d6d6a3e8, $a1d1937e,
+ $38d8c2c4, $4fdff252, $d1bb67f1, $a6bc5767, $3fb506dd, $48b2364b,
+ $d80d2bda, $af0a1b4c, $36034af6, $41047a60, $df60efc3, $a867df55,
+ $316e8eef, $4669be79, $cb61b38c, $bc66831a, $256fd2a0, $5268e236,
+ $cc0c7795, $bb0b4703, $220216b9, $5505262f, $c5ba3bbe, $b2bd0b28,
+ $2bb45a92, $5cb36a04, $c2d7ffa7, $b5d0cf31, $2cd99e8b, $5bdeae1d,
+ $9b64c2b0, $ec63f226, $756aa39c, $026d930a, $9c0906a9, $eb0e363f,
+ $72076785, $05005713, $95bf4a82, $e2b87a14, $7bb12bae, $0cb61b38,
+ $92d28e9b, $e5d5be0d, $7cdcefb7, $0bdbdf21, $86d3d2d4, $f1d4e242,
+ $68ddb3f8, $1fda836e, $81be16cd, $f6b9265b, $6fb077e1, $18b74777,
+ $88085ae6, $ff0f6a70, $66063bca, $11010b5c, $8f659eff, $f862ae69,
+ $616bffd3, $166ccf45, $a00ae278, $d70dd2ee, $4e048354, $3903b3c2,
+ $a7672661, $d06016f7, $4969474d, $3e6e77db, $aed16a4a, $d9d65adc,
+ $40df0b66, $37d83bf0, $a9bcae53, $debb9ec5, $47b2cf7f, $30b5ffe9,
+ $bdbdf21c, $cabac28a, $53b39330, $24b4a3a6, $bad03605, $cdd70693,
+ $54de5729, $23d967bf, $b3667a2e, $c4614ab8, $5d681b02, $2a6f2b94,
+ $b40bbe37, $c30c8ea1, $5a05df1b, $2d02ef8d
+ );
+
+type
+ TPreallocatedMemoryStream = class(TCustomMemoryStream)
+ public
+ constructor Create(Ptr: Pointer; Size: Int64);
+ function Write(const Buffer; Count: Longint): Longint; override;
+ end;
+
+constructor TPreallocatedMemoryStream.Create(Ptr: Pointer; Size: Int64);
+begin
+ inherited Create;
+ SetPointer(Ptr, Size);
+end;
+
+function TPreallocatedMemoryStream.Write(const Buffer; Count: Integer): Longint;
+begin
+ Result := Size-Position;
+ if Result > Count then
+ Result := Count;
+ System.Move(Buffer, Pointer(PByte(Memory) + Position)^, Result);
+ Seek(Result, soCurrent);
+end;
+
+function Magic2Int(aMagic: TMagic4): Cardinal; inline;
+begin
+ Result := PCardinal(@aMagic)^;
+end;
+
+function Int2Magic(aInt: Cardinal): TMagic4; inline;
+begin
+ Result := PMagic4(@aInt)^;
+end;
+
+function String2Magic(const aStr: string): TMagic4;
+begin
+ Result := #0#0#0#0;
+ if Length(aStr) > 0 then Result[0] := AnsiChar(aStr[1]);
+ if Length(aStr) > 1 then Result[1] := AnsiChar(aStr[2]);
+ if Length(aStr) > 2 then Result[2] := AnsiChar(aStr[3]);
+ if Length(aStr) > 3 then Result[3] := AnsiChar(aStr[4]);
+end;
+
+function LowerByte(ch: AnsiChar): Byte; inline;
+begin
+ case ch of
+ 'A'..'Z':
+ Result := Byte(Ord(ch) + Ord('a')-Ord('A'));
+ else
+ Result := Byte(ch);
+ end;
+end;
+
+function LastCharPos(const s: string; const Chr: char): Integer; inline;
+begin
+ for Result := Length(s) downto 1 do
+ if s[Result] = Chr then
+ Exit;
+ Result := 0;
+end;
+
+function Str2MagicInt(const s: string): Cardinal;
+var
+ i: integer;
+begin
+ Result := 0;
+ for i := 1 to Length(s) do begin
+ PByte(PByte(@Result) + i-1)^ := LowerByte(AnsiChar(s[i]));
+ if i = 4 then Break;
+ end;
+end;
+
+function SplitDirName(const aFileName: string; var Dir, Name: string): Integer;
+begin
+ Result := LastCharPos(aFileName, '\');
+ if Result = 0 then
+ Result := LastCharPos(aFileName, '/');
+ if Result <> 0 then begin
+ Dir := Copy(aFileName, 1, Pred(Result));
+ Name := Copy(aFileName, Succ(Result), Length(aFileName) - Result);
+ end
+ else begin
+ Dir := '';
+ Name := aFileName;
+ end;
+end;
+
+function SplitNameExt(const aFileName: string; var Name, Ext: string; aNoExtDot: Boolean = False): Integer;
+begin
+ Result := LastCharPos(aFileName, '.');
+ if Result <> 0 then begin
+ Name := Copy(aFileName, 1, Pred(Result));
+ if aNoExtDot then
+ Inc(Result);
+ Ext := Copy(aFileName, Result, Length(aFileName) - Result + 1);
+ end
+ else begin
+ Name := aFileName;
+ Ext := '';
+ end;
+end;
+
+function CreateHashTES3(const aFileName: string): UInt64;
+var
+ s: AnsiString;
+ i, l: integer;
+ sum, off, temp, n: Cardinal;
+begin
+ s := AnsiString(aFileName);
+ l := Length(s) shr 1;
+
+ sum := 0; off := 0;
+ for i := 1 to l do begin
+ temp := Cardinal(LowerByte(s[i])) shl (off and $1F);
+ sum := sum xor temp;
+ off := off + 8;
+ end;
+ Result := UInt64(sum) shl 32;
+
+ sum := 0; off := 0;
+ for i := l + 1 to Length(s) do begin
+ temp := Cardinal(LowerByte(s[i])) shl (off and $1F);
+ sum := sum xor temp;
+ n := temp and $1F;
+ sum := (sum shr n) or (sum shl (32 - n));
+ off := off + 8;
+ end;
+ Result := Result or sum;
+end;
+
+function CreateHashTES4(const aName, aExt: string): UInt64; overload;
+var
+ i, l: integer;
+ hash: Cardinal;
+ ext: array [0..3] of Byte;
+ s, e: AnsiString;
+begin
+ Result := 0;
+ s := AnsiString(aName);
+ e := AnsiString(aExt);
+
+ l := Length(s);
+ if l = 0 then
+ Exit;
+
+ Result := LowerByte(s[l]);
+ if l > 2 then
+ Result := Result or (Cardinal(LowerByte(s[l-1])) shl 8);
+ Result := Result or (l shl 16);
+ Result := Result or (Cardinal(LowerByte(s[1])) shl 24);
+
+ PCardinal(@ext)^ := 0;
+ for i := 1 to Length(e) do begin
+ ext[i-1] := LowerByte(e[i]);
+ if i = 4 then Break;
+ end;
+
+ case PCardinal(@ext)^ of
+ $00666B2E: Result := Result or $80; // .kf
+ $66696E2E: Result := Result or $8000; // .nif
+ $7364642E: Result := Result or $8080; // .dds
+ $7661772E: Result := Result or $80000000; // .wav
+ end;
+
+ hash := 0;
+ for i := 2 to l-2 do
+ hash := LowerByte(s[i]) + (hash shl 6) + (hash shl 16) - hash;
+ Result := Result + UInt64(hash) shl 32;
+
+ hash := 0;
+ for i := 1 to Length(e) do
+ hash := LowerByte(e[i]) + (hash shl 6) + (hash shl 16) - hash;
+ Result := Result + UInt64(hash) shl 32;
+end;
+
+function CreateHashTES4(const aFileName: string): UInt64; overload;
+var
+ fname, fext: string;
+begin
+ SplitNameExt(aFileName, fname, fext);
+ Result := CreateHashTES4(fname, fext);
+end;
+
+function CreateHashFO4(const aFileName: string): Cardinal;
+var
+ i: Integer;
+ s: AnsiString;
+ c: AnsiChar;
+begin
+ Result := 0;
+ s := AnsiString(aFileName);
+ for i := 1 to Length(s) do begin
+ c := s[i];
+ if Byte(c) > 127 then Continue;
+ if c = '/' then c := '\';
+ Result := (Result shr 8) xor crc32table[(Result xor LowerByte(c)) and $FF];
+ end;
+end;
+
+function TwbBSFileTES4.Compress(bsa: TwbBSArchive): Boolean;
+begin
+ case PackingCompression of
+ pcCompress : Result := True;
+ pcUncompress: Result := False;
+ else
+ Result := bsa.Compress;
+ end;
+end;
+
+function TwbBSFileTES4.Compressed(bsa: TwbBSArchive): Boolean;
+begin
+ Result := (bsa.ArchiveFlags and ARCHIVE_COMPRESS <> 0) xor (Size and FILE_SIZE_COMPRESS <> 0);
+end;
+
+function TwbBSFileTES4.RawSize: Cardinal;
+begin
+ Result := Size and not FILE_SIZE_COMPRESS;
+end;
+
+function TwbBSFileFO4.DXGIFormatName: string;
+begin
+ Result := GetEnumName(TypeInfo(TDXGI), Integer(DXGIFormat));
+end;
+
+function TwbBSFileFO4.Compress(bsa: TwbBSArchive): Boolean;
+begin
+ case PackingCompression of
+ pcCompress : Result := True;
+ pcUncompress: Result := False;
+ else
+ Result := bsa.Compress;
+ end;
+end;
+
+function TwbBSFileFO4.Compressed(bsa: TwbBSArchive): Boolean;
+begin
+ if bsa.ArchiveType = baFO4 then
+ Result := PackedSize <> 0
+ else
+ Result := (Length(TexChunks) <> 0) and (TexChunks[0].PackedSize <> 0);
+end;
+
+
+{ TwbBSArchive }
+
+constructor TwbBSArchive.Create;
+begin
+ fType := baNone;
+ fMaxChunkCount := 4;
+ fSingleMipChunkX := 512;
+ fSingleMipChunkY := 512;
+end;
+
+destructor TwbBSArchive.Destroy;
+begin
+ if fStates * [stReading, stWriting] <> [] then
+ Close;
+end;
+
+function TwbBSArchive.GetArchiveFormatName: string;
+begin
+ Result := cArchiveFormatNames[fType];
+end;
+
+function TwbBSArchive.GetFileCount: Cardinal;
+begin
+ case fType of
+ baTES3:
+ Result := fHeaderTES3.FileCount;
+ baTES4, baFO3, baSSE:
+ Result := fHeaderTES4.FileCount;
+ baFO4, baFO4dds, baSF, baSFdds:
+ Result := fHeaderFO4.FileCount;
+ else
+ Result := 0;
+ end;
+end;
+
+procedure TwbBSArchive.SetArchiveFlags(aFlags: Cardinal);
+begin
+ if not (fType in [baTES4, baFO3, baSSE]) then
+ raise Exception.Create('Archive flags are not supported for this archive type');
+
+ fHeaderTES4.Flags := aFlags;
+
+ // force compression flag if needed
+ if fCompress then
+ fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_COMPRESS;
+end;
+
+procedure TwbBSArchive.SetMultiThreaded(aValue: Boolean);
+begin
+ fMultiThreaded := aValue;
+ {$IF CompilerVersion < 34.0}
+ if aValue and not Assigned(Sync) then
+ Sync := TReadWriteSync.Create;
+ {$IFEND}
+end;
+
+function TwbBSArchive.FindFileRecordTES3(const aFileName: string; var aFileIdx: Integer): Boolean;
+var
+ h: UInt64;
+ i: integer;
+begin
+ h := CreateHashTES3(aFileName);
+
+ Result := False;
+ for i := Low(fFilesTES3) to High(fFilesTES3) do
+ if fFilesTES3[i].Hash = h then begin
+ aFileIdx := i;
+ Result := True;
+ Exit;
+ end;
+end;
+
+function TwbBSArchive.FindFileRecordTES4(const aFileName: string; var aFolderIdx, aFileIdx: Integer): Boolean;
+var
+ fdir, fname, name, ext: string;
+ h: UInt64;
+ i, j: integer;
+begin
+ SplitDirName(aFileName, fdir, fname);
+ Result := False;
+ h := CreateHashTES4(fdir, '');
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin
+ if h <> fFoldersTES4[i].Hash then
+ // since table is sorted by hash, we can abort when our hash is lesser
+ if h < fFoldersTES4[i].Hash then
+ Exit
+ else
+ Continue;
+
+ SplitNameExt(fname, name, ext);
+ h := CreateHashTES4(name, ext);
+ for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do begin
+ if h <> fFoldersTES4[i].Files[j].Hash then
+ if h < fFoldersTES4[i].Files[j].Hash then
+ Exit
+ else
+ Continue;
+
+ Result := True;
+ aFolderIdx := i;
+ aFileIdx := j;
+ Exit;
+ end;
+ end;
+end;
+
+function TwbBSArchive.FindFileRecordFO4(const aFileName: string; var aFileIdx: Integer): Boolean;
+var
+ fdir, fname, name, ext: string;
+ hdir, hfile: Cardinal;
+ hext: TMagic4;
+ i: integer;
+begin
+ SplitDirName(aFileName, fdir, fname);
+ SplitNameExt(fname, name, ext, True);
+ hdir := CreateHashFO4(fdir);
+ hfile := CreateHashFO4(name);
+ hext := String2Magic(LowerCase(ext));
+
+ Result := False;
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ if (fFilesFO4[i].DirHash = hdir) and (fFilesFO4[i].NameHash = hfile) and (fFilesFO4[i].Ext = hext) then begin
+ aFileIdx := i;
+ Result := True;
+ Exit;
+ end;
+end;
+
+function TwbBSArchive.FindFileRecord(const aFileName: string): Pointer;
+var
+ i, j: integer;
+begin
+ Result := nil;
+
+ case fType of
+ baTES3:
+ if FindFileRecordTES3(aFileName, i) then Result := @fFilesTES3[i];
+ baTES4, baFO3, baSSE:
+ if FindFileRecordTES4(aFileName, i, j) then Result := @fFoldersTES4[i].Files[j];
+ baFO4, baFO4dds, baSF, baSFdds:
+ if FindFileRecordFO4(aFileName, i) then Result := @fFilesFO4[i];
+ end;
+end;
+
+function TwbBSArchive.GetDDSMipChunkNum(var aDDSInfo: TDDSInfo): Integer;
+var
+ w, h: Integer;
+begin
+ w := aDDSInfo.Width;
+ h := aDDSInfo.Height;
+ Result := 1;
+ while (Result < aDDSInfo.MipMaps) and
+ (Result < fMaxChunkCount) and
+ (w >= fSingleMipChunkX) and
+ (h >= fSingleMipChunkY)
+ do begin
+ Inc(Result);
+ w := w div 2;
+ h := h div 2;
+ end;
+end;
+
+function TwbBSArchive.CalcDataHash(aData: Pointer; aLen: Cardinal): TPackedDataHash;
+var
+ fMD5: TMD5Alg;
+begin
+ fMD5.Init(@fMD5);
+ fMD5.Update(@fMD5, aData, aLen);
+ fMD5.Done(@fMD5, @Result);
+end;
+
+function TwbBSArchive.FindPackedData(aSize: Cardinal; aHash: TPackedDataHash; aFileRecord: Pointer): Boolean;
+var
+ i: Integer;
+begin
+ Result := False;
+
+ if not fShareData then
+ Exit;
+
+ for i := 0 to Pred(fPackedDataCount) do
+ if (aSize = fPackedData[i].Size) and CompareMem(@aHash, @fPackedData[i].Hash, SizeOf(aHash)) then begin
+ case fType of
+ baTES3: begin
+ PwbBSFileTES3(aFileRecord).Size := PwbBSFileTES3(fPackedData[i].FileRecord).Size;
+ PwbBSFileTES3(aFileRecord).Offset := PwbBSFileTES3(fPackedData[i].FileRecord).Offset;
+ end;
+ baTES4, baFO3, baSSE: begin
+ PwbBSFileTES4(aFileRecord).Size := PwbBSFileTES4(fPackedData[i].FileRecord).Size;
+ PwbBSFileTES4(aFileRecord).Offset := PwbBSFileTES4(fPackedData[i].FileRecord).Offset;
+ end;
+ baFO4, baSF: begin
+ PwbBSFileFO4(aFileRecord).Size := PwbBSFileFO4(fPackedData[i].FileRecord).Size;
+ PwbBSFileFO4(aFileRecord).PackedSize := PwbBSFileFO4(fPackedData[i].FileRecord).PackedSize;
+ PwbBSFileFO4(aFileRecord).Offset := PwbBSFileFO4(fPackedData[i].FileRecord).Offset;
+ end;
+ baFO4dds, baSFdds: begin
+ PwbBSTexChunkRec(aFileRecord).Size := PwbBSTexChunkRec(fPackedData[i].FileRecord).Size;
+ PwbBSTexChunkRec(aFileRecord).PackedSize := PwbBSTexChunkRec(fPackedData[i].FileRecord).PackedSize;
+ PwbBSTexChunkRec(aFileRecord).Offset := PwbBSTexChunkRec(fPackedData[i].FileRecord).Offset;
+ end;
+ end;
+ Result := True;
+ Exit;
+ end;
+end;
+
+procedure TwbBSArchive.AddPackedData(aSize: Cardinal; aHash: TPackedDataHash; aFileRecord: Pointer);
+begin
+ if not fShareData then
+ Exit;
+
+ if fPackedDataCount = Length(fPackedData) then
+ if Length(fPackedData) = 0 then
+ SetLength(fPackedData, 2048)
+ else
+ SetLength(fPackedData, Length(fPackedData) * 2);
+
+ fPackedData[fPackedDataCount].Size := aSize;
+ fPackedData[fPackedDataCount].Hash := aHash;
+ fPackedData[fPackedDataCount].FileRecord := aFileRecord;
+ Inc(fPackedDataCount);
+end;
+
+procedure TwbBSArchive.LoadFromFile(const aFileName: string);
+var
+ i, j: Integer;
+begin
+ if fStates * [stReading, stWriting] <> [] then
+ Close;
+
+ fStream := TwbReadOnlyCachedFileStream.Create(aFileName, fmOpenRead or fmShareDenyWrite);
+
+ // magic
+ fMagic := Int2Magic(fStream.ReadCardinal);
+ if fMagic = MAGIC_TES3 then fType := baTES3 else
+ if fMagic = MAGIC_BSA then fType := baTES4 else
+ if fMagic = MAGIC_BTDX then fType := baFO4 else
+ raise Exception.Create('Unknown archive format');
+
+ // archive version except Morrowind
+ if fType <> baTES3 then begin
+ fVersion := fStream.ReadCardinal;
+ fCompressionType := ctZlib; // default compression type
+ case fVersion of
+ HEADER_VERSION_TES4:
+ fType := baTES4;
+ HEADER_VERSION_FO3 :
+ fType := baFO3;
+ HEADER_VERSION_SSE :
+ fType := baSSE;
+ HEADER_VERSION_FO4v1,
+ HEADER_VERSION_FO4NGv7,
+ HEADER_VERSION_FO4NGv8 :
+ fType := baFO4;
+ HEADER_VERSION_SF2,
+ HEADER_VERSION_SF3:
+ fType := baSF;
+ else
+ raise Exception.Create('Unknown archive version 0x' + IntToHex(fVersion, 8));
+ end;
+ end;
+
+ case fType of
+ //--------------------------------------------------
+ // Morrowind
+ baTES3: begin
+ // read header
+ fStream.ReadBuffer(fHeaderTES3, SizeOf(fHeaderTES3));
+ SetLength(fFilesTES3, fHeaderTES3.FileCount);
+ for i := Low(fFilesTES3) to High(fFilesTES3) do begin
+ fFilesTES3[i].Size := fStream.ReadCardinal;
+ fFilesTES3[i].Offset := fStream.ReadCardinal;
+ end;
+ // skip name offsets
+ fStream.Position := fStream.Position + 4 * fHeaderTES3.FileCount;
+ // read names
+ for i := Low(fFilesTES3) to High(fFilesTES3) do
+ fFilesTES3[i].Name := fStream.ReadStringTerm;
+ // read hashes
+ for i := Low(fFilesTES3) to High(fFilesTES3) do
+ fFilesTES3[i].Hash := fStream.ReadUInt64;
+ // remember binary data offset since stored files offsets are relative
+ fDataOffset := fStream.Position;
+ end;
+
+ //--------------------------------------------------
+ // Fallout 4, Starfield
+ baFO4, baSF: begin
+ // read header
+ fStream.ReadBuffer(fHeaderFO4, SizeOf(fHeaderFO4));
+ // SF header
+ if fType = baSF then
+ if fVersion = HEADER_VERSION_SF2 then
+ fStream.ReadBuffer(fHeaderSFv2, SizeOf(fHeaderSFv2))
+ else begin
+ fStream.ReadBuffer(fHeaderSFv3, SizeOf(fHeaderSFv3));
+ if fHeaderSFv3.CompressionMethod = 3 then
+ fCompressionType := ctLZ4Block;
+ end;
+
+ // read GNRL files
+ if fHeaderFO4.Magic = MAGIC_GNRL then begin
+ SetLength(fFilesFO4, fHeaderFO4.FileCount);
+ for i := Low(fFilesFO4) to High(fFilesFO4) do begin
+ fFilesFO4[i].NameHash := fStream.ReadCardinal;
+ fFilesFO4[i].Ext := Int2Magic(fStream.ReadCardinal);
+ fFilesFO4[i].DirHash := fStream.ReadCardinal;
+ fFilesFO4[i].Unknown := fStream.ReadCardinal;
+ fFilesFO4[i].Offset := fStream.ReadInt64;
+ fFilesFO4[i].PackedSize := fStream.ReadCardinal;
+ fFilesFO4[i].Size := fStream.ReadCardinal;
+ fStream.ReadCardinal; // BAADF00D
+ end;
+ end
+ // read DX10 textures
+ else if fHeaderFO4.Magic = MAGIC_DX10 then begin
+ if fType = baFO4 then
+ fType := baFO4dds
+ else if fType = baSF then
+ fType := baSFdds;
+ SetLength(fFilesFO4, fHeaderFO4.FileCount);
+ for i := Low(fFilesFO4) to High(fFilesFO4) do begin
+ fFilesFO4[i].NameHash := fStream.ReadCardinal;
+ fFilesFO4[i].Ext := Int2Magic(fStream.ReadCardinal);
+ fFilesFO4[i].DirHash := fStream.ReadCardinal;
+ fFilesFO4[i].UnknownTex := fStream.ReadByte;
+ SetLength(fFilesFO4[i].TexChunks, fStream.ReadByte);
+ fStream.ReadWord; // skip chunkHeaderSize, always 24
+ //fFilesFO4[i].ChunkHeaderSize := fStream.ReadWord;
+ fFilesFO4[i].Height := fStream.ReadWord;
+ fFilesFO4[i].Width := fStream.ReadWord;
+ fFilesFO4[i].NumMips := fStream.ReadByte;
+ fFilesFO4[i].DXGIFormat := fStream.ReadByte;
+ fFilesFO4[i].CubeMaps := fStream.ReadWord;
+ for j := Low(fFilesFO4[i].TexChunks) to High(fFilesFO4[i].TexChunks) do
+ with fFilesFO4[i].TexChunks[j] do begin
+ Offset := fStream.ReadInt64;
+ PackedSize := fStream.ReadCardinal;
+ Size := fStream.ReadCardinal;
+ StartMip := fStream.ReadWord;
+ EndMip := fStream.ReadWord;
+ fStream.ReadCardinal; // skip BAADF00D
+ end;
+ end;
+ end
+ else
+ raise Exception.Create('Unknown BA2 archive type');
+
+ // read file names
+ fStream.Position := fHeaderFO4.FileTableOffset;
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ fFilesFO4[i].Name := StringReplace(fStream.ReadStringLen16, '/', '\', [rfReplaceAll]);
+ end;
+
+ //--------------------------------------------------
+ // Oblivion, Fallout 3, New Vegas, Skyrim, Skyrim SE
+ baTES4, baFO3, baSSE: begin
+ if fType = baSSE then
+ fCompressionType := ctLZ4Frame;
+
+ // read header
+ fStream.ReadBuffer(fHeaderTES4, SizeOf(fHeaderTES4));
+ fStream.Position := fHeaderTES4.FoldersOffset;
+
+ // read folder records
+ SetLength(fFoldersTES4, fHeaderTES4.FolderCount);
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin
+ fFoldersTES4[i].Hash := fStream.ReadUInt64;
+ fFoldersTES4[i].FileCount := fStream.ReadCardinal;
+ if fType = baSSE then begin
+ fFoldersTES4[i].Unk32 := fStream.ReadCardinal;
+ fFoldersTES4[i].Offset := fStream.ReadInt64;
+ end else
+ fFoldersTES4[i].Offset := fStream.ReadCardinal;
+ end;
+
+ // read folder names and file records
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin
+ fFoldersTES4[i].Name := fStream.ReadStringLen;
+ SetLength(fFoldersTES4[i].Files, fFoldersTES4[i].FileCount);
+ for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do begin
+ fFoldersTES4[i].Files[j].Hash := fStream.ReadUInt64;
+ fFoldersTES4[i].Files[j].Size := fStream.ReadCardinal;
+ fFoldersTES4[i].Files[j].Offset := fStream.ReadCardinal;
+ end;
+ end;
+
+ // read file names
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do
+ for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do
+ fFoldersTES4[i].Files[j].Name := fStream.ReadStringTerm;
+ end;
+
+ end;
+
+ fFileName := aFileName;
+ Include(fStates, stReading);
+end;
+
+
+type
+ THashPair = record DirHash, FileHash: UInt64; pc: TPackingCompression end;
+ PHashPair = ^THashPair;
+
+function HashPairSort(List: TStringList; Index1, Index2: Integer): Integer;
+var
+ h1, h2: PHashPair;
+begin
+ h1 := PHashPair(List.Objects[Index1]);
+ h2 := PHashPair(List.Objects[Index2]);
+ if h1.DirHash < h2.DirHash then
+ Result := -1
+ else if h1.DirHash > h2.DirHash then
+ Result := 1
+ else if h1.FileHash < h2.FileHash then
+ Result := -1
+ else if h1.FileHash > h2.FileHash then
+ Result := 1
+ else
+ Result := 0;
+end;
+
+function AlphabeticalSort(List: TStringList; Index1, Index2: Integer): Integer;
+begin
+ Result := CompareStr(List[Index1], List[Index2]);
+end;
+
+procedure TwbBSArchive.CreateArchive(const aFileName: string; aType: TBSArchiveType;
+ aFilesList: TStringList = nil);
+var
+ HashPairs: array of THashPair;
+ h: PHashPair;
+ hdir: UInt64;
+ s, fdir, fname, fext, name: string;
+ i, len, folderidx, fileidx: Integer;
+ Buffer: TBytes;
+ ddsinfo: TDDSInfo;
+begin
+ if stReading in fStates then
+ Close;
+
+ if stWriting in fStates then
+ raise Exception.Create('Archive is already being created');
+
+ case aType of
+ baTES3: begin
+ fMagic := MAGIC_TES3;
+ end;
+ baTES4: begin
+ fVersion := HEADER_VERSION_TES4;
+ fMagic := MAGIC_BSA;
+ fHeaderTES4.Flags := ARCHIVE_PATHNAMES or ARCHIVE_FILENAMES or ARCHIVE_EMBEDNAME or ARCHIVE_XMEM or ARCHIVE_UNKNOWN10;
+ fHeaderTES4.FileFlags := 0;
+ fHeaderTES4.FoldersOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderTES4);
+ fCompressionType := ctZlib;
+ end;
+ baFO3: begin
+ fVersion := HEADER_VERSION_FO3;
+ fMagic := MAGIC_BSA;
+ fHeaderTES4.Flags := ARCHIVE_PATHNAMES or ARCHIVE_FILENAMES;
+ fHeaderTES4.FileFlags := 0;
+ fHeaderTES4.FoldersOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderTES4);
+ fCompressionType := ctZlib;
+ end;
+ baSSE: begin
+ fVersion := HEADER_VERSION_SSE;
+ fMagic := MAGIC_BSA;
+ fHeaderTES4.Flags := ARCHIVE_PATHNAMES or ARCHIVE_FILENAMES;
+ fHeaderTES4.FileFlags := 0;
+ fHeaderTES4.FoldersOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderTES4);
+ fCompressionType := ctLZ4Frame;
+ end;
+ baFO4: begin
+ fMagic := MAGIC_BTDX;
+ fHeaderFO4.Magic := MAGIC_GNRL;
+ fVersion := HEADER_VERSION_FO4v1;
+ fCompressionType := ctZlib;
+ end;
+ baFO4dds: begin
+ fMagic := MAGIC_BTDX;
+ fHeaderFO4.Magic := MAGIC_DX10;
+ fVersion := HEADER_VERSION_FO4v1;
+ fCompressionType := ctZlib;
+ end;
+ baSF: begin
+ fMagic := MAGIC_BTDX;
+ fHeaderFO4.Magic := MAGIC_GNRL;
+ fVersion := HEADER_VERSION_SF2;
+ fCompressionType := ctZlib;
+ end;
+ baSFdds: begin
+ fMagic := MAGIC_BTDX;
+ fHeaderFO4.Magic := MAGIC_DX10;
+ fVersion := HEADER_VERSION_SF3;
+ fCompressionType := ctLZ4Block;
+ end;
+ else
+ raise Exception.Create('Unsupported archive type');
+ end;
+
+ fType := aType;
+
+ if fType in [baTES3] then begin
+ if not Assigned(aFilesList) or (aFilesList.Count = 0) then
+ raise Exception.Create('Archive requires predefined files list');
+
+ // sort files by hashes
+ SetLength(HashPairs, aFilesList.Count);
+ for i := 0 to Pred(aFilesList.Count) do begin
+ h := @HashPairs[i];
+ h.FileHash := CreateHashTES3(aFilesList[i]);
+ aFilesList.Objects[i] := Pointer(h);
+ end;
+ aFilesList.CustomSort(HashPairSort);
+
+ // create file records and calculate total names length
+ SetLength(fFilesTES3, aFilesList.Count);
+ len := 0;
+ for i := 0 to Pred(aFilesList.Count) do begin
+ fFilesTES3[i].Hash := PHashPair(aFilesList.Objects[i]).FileHash;
+ fFilesTES3[i].Name := LowerCase(aFilesList[i]);
+ Inc(len, Length(fFilesTES3[i].Name) + 1); // include terminator
+ end;
+
+ // offset to hash table
+ fDataOffset := SizeOf(fMagic) + SizeOf(fHeaderTES3) +
+ 8 * Length(fFilesTES3) + // File sizes/offsets
+ 4 * Length(fFilesTES3) + // Archive directory/name offsets
+ len; // Filename records
+
+ // stored as minus 12 (for header size)
+ fHeaderTES3.HashOffset := fDataOffset - 12;
+ fHeaderTES3.FileCount := aFilesList.Count;
+
+ // offset to files data
+ fDataOffset := fDataOffset + 8 * Length(fFilesTES3); // Hash table
+
+ // files are stored alphabetically in the data section in vanilla archives
+ // not really needed but whatever
+ aFilesList.CustomSort(AlphabeticalSort);
+ end
+
+ else if fType in [baTES4, baFO3, baSSE] then begin
+ if not Assigned(aFilesList) or (aFilesList.Count = 0) then
+ raise Exception.Create('Archive requires predefined files list');
+
+ fHeaderTES4.FolderNamesLength := 0;
+ fHeaderTES4.FileNamesLength := 0;
+
+ // dirs and files must be sorted by their hashes
+ SetLength(HashPairs, aFilesList.Count);
+ for i := 0 to Pred(aFilesList.Count) do begin
+ h := @HashPairs[i];
+ if SplitDirName(aFilesList[i], fdir, fname) = 0 then
+ raise Exception.Create('File is missing the folder part: ' + aFilesList[i]);
+
+ // calculate hashes
+ h.DirHash := CreateHashTES4(fdir, '');
+ SplitNameExt(fname, s, fext);
+ h.FileHash := CreateHashTES4(s, fext);
+ h.pc := TPackingCompression(aFilesList.Objects[i]);
+ aFilesList.Objects[i] := Pointer(h);
+
+ fext := LowerCase(fext);
+ with fHeaderTES4 do begin
+ // determine file flags
+ if fdir.StartsWith('meshes\', True) then FileFlags := FileFlags or FILE_NIF else
+ if fdir.StartsWith('textures\', True) then FileFlags := FileFlags or FILE_DDS else
+ if fdir.StartsWith('sound\', True) then FileFlags := FileFlags or FILE_WAV or FILE_MP3 else
+ if fext = '.nif' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.lod' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.bto' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.btr' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.btt' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.dtl' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.kf' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.kfm' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.hkx' then FileFlags := FileFlags or FILE_NIF else
+ if fext = '.dds' then FileFlags := FileFlags or FILE_DDS else
+ if fext = '.xml' then FileFlags := FileFlags or FILE_XML or FILE_MISC else
+ if fext = '.wav' then FileFlags := FileFlags or FILE_WAV else
+ if fext = '.fuz' then FileFlags := FileFlags or FILE_WAV else
+ if fext = '.lip' then FileFlags := FileFlags or FILE_MP3 else
+ if fext = '.mp3' then FileFlags := FileFlags or FILE_MP3 else
+ if fext = '.ogg' then FileFlags := FileFlags or FILE_MP3 else
+ if fext = '.txt' then FileFlags := FileFlags or FILE_TXT else
+ if fext = '.htm' then FileFlags := FileFlags or FILE_TXT else
+ if fext = '.bat' then FileFlags := FileFlags or FILE_TXT else
+ if fext = '.scc' then FileFlags := FileFlags or FILE_TXT else
+ if fext = '.spt' then FileFlags := FileFlags or FILE_SPT else
+ if fext = '.fnt' then FileFlags := FileFlags or FILE_FNT else
+ if fext = '.tex' then FileFlags := FileFlags or FILE_FNT else
+ FileFlags := FileFlags or FILE_MISC;
+
+ // determine archive flags
+
+ // packed scripts can't be added to objects in the SSE CK if the archive was packed
+ // without the "RetainNames" flag (the scripts aren't shown in the script adding window)
+ if fext = '.pex' then ArchiveFlags := ArchiveFlags or ARCHIVE_RETAINNAME;
+ end;
+ end;
+
+ // sort by hashes
+ aFilesList.CustomSort(HashPairSort);
+
+ // create folder and file records
+ fHeaderTES4.FileCount := 0;
+ hdir := 0;
+ folderidx := -1;
+ fileidx := 0;
+ for i := 0 to Pred(aFilesList.Count) do begin
+ SplitDirName(aFilesList[i], fdir, fname);
+ h := Pointer(aFilesList.Objects[i]);
+ // new folder
+ if h.DirHash <> hdir then begin
+ Inc(folderidx);
+ fileidx := 0;
+ hdir := h.DirHash;
+ SetLength(fFoldersTES4, folderidx + 1);
+ fFoldersTES4[folderidx].Hash := h.DirHash;
+ fFoldersTES4[folderidx].Name := LowerCase(fdir);
+ // calc folder names length
+ Inc(fHeaderTES4.FolderNamesLength, Length(fdir) + 1); // + terminator only, length prefix is not counted
+ end;
+ SetLength(fFoldersTES4[folderidx].Files, fileidx + 1);
+ fFoldersTES4[folderidx].Files[fileidx].Hash := h.FileHash;
+ fFoldersTES4[folderidx].Files[fileidx].Name := LowerCase(fname);
+ fFoldersTES4[folderidx].Files[fileidx].PackingCompression := h.pc;
+ Inc(fileidx);
+ Inc(fFoldersTES4[folderidx].FileCount);
+ Inc(fHeaderTES4.FileCount);
+ // calculate file names length
+ Inc(fHeaderTES4.FileNamesLength, Length(fname) + 1); // + terminator
+ end;
+ fHeaderTES4.FolderCount := Length(fFoldersTES4);
+
+ // calculate folders offsets
+ // at the end fDataOffset will hold the total size of header, folder and file records
+ // in other words the start of files data
+ fDataOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderTES4) + 16 * Length(fFoldersTES4);
+ // SSE folder record is 8 bytes larger
+ if fType = baSSE then
+ Inc(fDataOffset, 8 * Length(fFoldersTES4));
+ // Offsets are stored including this value
+ Inc(fDataOffset, fHeaderTES4.FileNamesLength);
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin
+ fFoldersTES4[i].Offset := fDataOffset;
+ // add folder name length
+ Inc(fDataOffset, Length(fFoldersTES4[i].Name) + 2); // + length prefix + terminator
+ // add file records length
+ Inc(fDataOffset, 16 * Length(fFoldersTES4[i].Files));
+ end;
+
+ // final flags detection
+
+ // misc file flag is not in Skyrim SE
+ if fType = baSSE then
+ fHeaderTES4.FileFlags := fHeaderTES4.FileFlags and not FILE_MISC;
+
+ // embedded names in texture only archives
+ // except Skyrim SE: crashing engine bug if texture is uncompressed and file name is embedded
+ if (fHeaderTES4.FileFlags = FILE_DDS) and (fType <> baSSE) then
+ fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_EMBEDNAME;
+
+ // startupstr flag in archives with meshes
+ if fHeaderTES4.FileFlags and FILE_NIF <> 0 then
+ fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_STARTUPSTR;
+
+ // retainname flag in archives with sounds
+ if fHeaderTES4.FileFlags and FILE_WAV <> 0 then
+ fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_RETAINNAME;
+
+ // txt, xml and fnt file flags are exclusive for Oblivion
+ if fType <> baTES4 then
+ fHeaderTES4.FileFlags := fHeaderTES4.FileFlags and not (FILE_XML or FILE_TXT or FILE_FNT);
+
+ // set compression flag if needed
+ if fCompress then
+ fHeaderTES4.Flags := fHeaderTES4.Flags or ARCHIVE_COMPRESS;
+ end
+
+ else if fType in [baFO4, baFO4dds, baSF, baSFdds] then begin
+ if not Assigned(aFilesList) or (aFilesList.Count = 0) then
+ raise Exception.Create('Archive requires predefined files list');
+
+ fHeaderFO4.FileCount := aFilesList.Count;
+ SetLength(fFilesFO4, aFilesList.Count);
+ for i := 0 to Pred(aFilesList.Count) do begin
+ if SplitDirName(aFilesList[i], fdir, fname) = 0 then
+ raise Exception.Create('File is missing the folder part: ' + aFileName);
+
+ SplitNameExt(fname, name, fext, True);
+ // archive2.exe uses /
+ fFilesFO4[i].Name := StringReplace(aFilesList[i], '\', '/', [rfReplaceAll]);
+ fFilesFO4[i].DirHash := CreateHashFO4(fdir);
+ fFilesFO4[i].NameHash := CreateHashFO4(name);
+ fFilesFO4[i].Ext := String2Magic(LowerCase(fext));
+ fFilesFO4[i].Unknown := iFileFO4Unknown;
+ fFilesFO4[i].PackingCompression := TPackingCompression(aFilesList.Objects[i]);
+ end;
+
+ fDataOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderFO4);
+ if fType = baSF then
+ Inc(fDataOffset, SizeOf(fHeaderSFv2))
+ else if fType = baSFdds then
+ Inc(fDataOffset, SizeOf(fHeaderSFv3));
+
+ // file records have fixed length in general archive
+ if fType in [baFO4, baSF] then
+ fDataOffset := fDataOffset + 36 * Length(fFilesFO4)
+
+ // variable file record length depending on DDS chunks number
+ else if fType in [baFO4dds, baSFdds] then begin
+ if not Assigned(fDDSInfoProc) then
+ raise Exception.Create('DDS archive requires DDS file information callback');
+
+ for i := 0 to Pred(aFilesList.Count) do begin
+ fDDSInfoProc(Self, aFilesList[i], ddsinfo, Self.fDDSInfoProcContext);
+ fDataOffset := fDataOffset + 24 {size of file record} + 24 {size of each texchunk} * GetDDSMipChunkNum(ddsinfo);
+ end;
+ end;
+
+ end;
+
+ fStream := TwbWriteCachedFileStream.Create(aFileName, fmCreate);
+ fFileName := aFileName;
+ Include(fStates, stWriting);
+
+ // reserve space for the header
+ SetLength(Buffer, fDataOffset);
+ fStream.Write(Buffer[0], Length(Buffer));
+end;
+
+procedure TwbBSArchive.Save;
+var
+ i, j: integer;
+begin
+ if not (stWriting in fStates) then
+ raise Exception.Create('Archive is not in writing mode');
+
+ case fType of
+ baTES3: begin
+ for i := Low(fFilesTES3) to High(fFilesTES3) do
+ if fFilesTES3[i].Offset = 0 then
+ raise Exception.Create('Archived file has no data: ' + fFilesTES3[i].Name);
+
+ // write header
+ fStream.Position := 0;
+ // magic, header record
+ fStream.Write(fMagic, SizeOf(fMagic));
+ fStream.Write(fHeaderTES3, SizeOf(fHeaderTES3));
+ // file sizes/offsets
+ for i := Low(fFilesTES3) to High(fFilesTES3) do begin
+ fStream.WriteCardinal(fFilesTES3[i].Size);
+ fStream.WriteCardinal(fFilesTES3[i].Offset - fDataOffset); // offsets are relative
+ end;
+ // Archive directory/name offsets
+ j := 0;
+ for i := Low(fFilesTES3) to High(fFilesTES3) do begin
+ fStream.WriteCardinal(j);
+ Inc(j, Length(fFilesTES3[i].Name) + 1); // including terminator
+ end;
+ // Filename records
+ for i := Low(fFilesTES3) to High(fFilesTES3) do
+ fStream.WriteStringTerm(fFilesTES3[i].Name);
+ // Hash table
+ for i := Low(fFilesTES3) to High(fFilesTES3) do begin
+ fStream.WriteCardinal(fFilesTES3[i].Hash shr 32);
+ fStream.WriteCardinal(fFilesTES3[i].Hash and $FFFFFFFF);
+ end;
+ end;
+
+ baTES4, baFO3, baSSE: begin
+ // check that all files from files table have saved data
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do
+ for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do
+ if fFoldersTES4[i].Files[j].Offset = 0 then
+ raise Exception.Create('Archived file has no data: ' + fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name);
+
+ // write header
+ fStream.Position := 0;
+ // magic, version, header record
+ fStream.Write(fMagic, SizeOf(fMagic));
+ fStream.Write(fVersion, SizeOf(fVersion));
+ fStream.Write(fHeaderTES4, SizeOf(fHeaderTES4));
+ // folder records
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin
+ fStream.WriteUInt64(fFoldersTES4[i].Hash);
+ fStream.WriteCardinal(fFoldersTES4[i].FileCount);
+ if fType = baSSE then begin
+ fStream.WriteCardinal(fFoldersTES4[i].Unk32);
+ fStream.WriteInt64(fFoldersTES4[i].Offset);
+ end else
+ fStream.WriteCardinal(fFoldersTES4[i].Offset);
+ end;
+ // file records
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do begin
+ fStream.WriteStringLen(fFoldersTES4[i].Name);
+ for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do begin
+ fStream.WriteUInt64(fFoldersTES4[i].Files[j].Hash);
+ fStream.WriteCardinal(fFoldersTES4[i].Files[j].Size);
+ fStream.WriteCardinal(fFoldersTES4[i].Files[j].Offset);
+ end;
+ end;
+ // file names
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do
+ for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do
+ fStream.WriteStringTerm(fFoldersTES4[i].Files[j].Name);
+ end;
+
+ baFO4, baSF: begin
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ if fFilesFO4[i].Offset = 0 then
+ raise Exception.Create('Archived file has no data: ' + fFilesFO4[i].Name);
+
+ // file names table
+ fHeaderFO4.FileTableOffset := fStream.Position;
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ fStream.WriteStringLen16(fFilesFO4[i].Name);
+ // write header
+ fStream.Position := 0;
+ // magic, version, header record
+ fStream.Write(fMagic, SizeOf(fMagic));
+ fStream.Write(fVersion, SizeOf(fVersion));
+ fStream.Write(fHeaderFO4, SizeOf(fHeaderFO4));
+ // additional SF header
+ if fType = baSF then begin
+ fHeaderSFv2.Unknown1 := 1;
+ fHeaderSFv2.Unknown2 := 0;
+ fStream.Write(fHeaderSFv2, SizeOf(fHeaderSFv2));
+ end;
+ // file records
+ for i := Low(fFilesFO4) to High(fFilesFO4) do begin
+ fStream.WriteCardinal(fFilesFO4[i].NameHash);
+ fStream.WriteBuffer(fFilesFO4[i].Ext[0], SizeOf(fFilesFO4[i].Ext));
+ fStream.WriteCardinal(fFilesFO4[i].DirHash);
+ fStream.WriteCardinal(fFilesFO4[i].Unknown);
+ fStream.WriteInt64(fFilesFO4[i].Offset);
+ fStream.WriteCardinal(fFilesFO4[i].PackedSize);
+ fStream.WriteCardinal(fFilesFO4[i].Size);
+ fStream.WriteCardinal(iFileFO4Tail);
+ end;
+ end;
+
+ baFO4dds, baSFdds: begin
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ if Length(fFilesFO4[i].TexChunks) = 0 then
+ raise Exception.Create('Archived file has no data: ' + fFilesFO4[i].Name);
+
+ // file names table
+ fHeaderFO4.FileTableOffset := fStream.Position;
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ fStream.WriteStringLen16(fFilesFO4[i].Name);
+ // write header
+ fStream.Position := 0;
+ // magic, version, header record
+ fStream.Write(fMagic, SizeOf(fMagic));
+ fStream.Write(fVersion, SizeOf(fVersion));
+ fStream.Write(fHeaderFO4, SizeOf(fHeaderFO4));
+ // additional SF header
+ if fType = baSFdds then begin
+ fHeaderSFv3.Unknown1 := 1;
+ fHeaderSFv3.Unknown2 := 0;
+ fHeaderSFv3.CompressionMethod := 3; // lz4
+ fStream.Write(fHeaderSFv3, SizeOf(fHeaderSFv3));
+ end;
+ // file records
+ for i := Low(fFilesFO4) to High(fFilesFO4) do begin
+ fStream.WriteCardinal(fFilesFO4[i].NameHash);
+ fStream.WriteBuffer(fFilesFO4[i].Ext[0], SizeOf(fFilesFO4[i].Ext));
+ fStream.WriteCardinal(fFilesFO4[i].DirHash);
+ fStream.WriteByte(fFilesFO4[i].UnknownTex);
+ fStream.WriteByte(Length(fFilesFO4[i].TexChunks));
+ fStream.WriteWord(24); // fixed chunk header size
+ fStream.WriteWord(fFilesFO4[i].Height);
+ fStream.WriteWord(fFilesFO4[i].Width);
+ fStream.WriteByte(fFilesFO4[i].NumMips);
+ fStream.WriteByte(fFilesFO4[i].DXGIFormat);
+ fStream.WriteWord(fFilesFO4[i].CubeMaps);
+ for j := Low(fFilesFO4[i].TexChunks) to High(fFilesFO4[i].TexChunks) do
+ with fFilesFO4[i].TexChunks[j] do begin
+ fStream.WriteUInt64(Offset);
+ fStream.WriteCardinal(PackedSize);
+ fStream.WriteCardinal(Size);
+ fStream.WriteWord(StartMip);
+ fStream.WriteWord(EndMip);
+ fStream.WriteCardinal(iFileFO4Tail);
+ end;
+ end;
+ end;
+
+ end;
+
+ FreeAndNil(fStream);
+ Exclude(fStates, stWriting);
+ Close;
+end;
+
+function TwbBSArchive.GetCreatedArchiveSize: Int64;
+begin
+ if (stWriting in fStates) and Assigned(fStream) then
+ Result := fStream.Position
+ else
+ Result := 0;
+end;
+
+procedure TwbBSArchive.AddFileDisk(const aFilePath, aSourcePath: string);
+var
+ fname: string;
+ i: integer;
+ buffer: PByte;
+ stream: TFileStream;
+begin
+ if not (stWriting in fStates) then
+ raise Exception.Create('Archive is not in writing mode');
+
+ stream := TFileStream.Create(aSourcePath, fmOpenRead + fmShareDenyNone);
+ try
+ // Modified: Make sure memory is zeroed when allocated
+ buffer := AllocMem(stream.Size);
+ try
+ stream.Read(buffer^, stream.Size);
+ AddFileData(aFilePath, stream.Size, buffer);
+ finally
+ if Assigned(buffer) then
+ FreeMem(buffer);
+ end;
+ finally
+ stream.Free;
+ end;
+end;
+
+procedure TwbBSArchive.AddFileDiskRoot(const aRootDir, aSourcePath: string);
+var
+ fname: string;
+ i: integer;
+begin
+ i := Length(aRootDir);
+ if (i > 1) and (aRootDir[Length(aRootDir)] <> '\') then
+ Inc(i);
+
+ fname := Copy(aSourcePath, i + 1, Length(aSourcePath));
+
+ AddFileDisk(fname, aSourcePath);
+end;
+
+procedure TwbBSArchive.SyncBeginWrite;
+begin
+ if fMultiThreaded then
+ Sync.BeginWrite;
+end;
+
+procedure TwbBSArchive.SyncEndWrite;
+begin
+ if fMultiThreaded then
+ Sync.EndWrite;
+end;
+
+procedure TwbBSArchive.CompressStream(aSrc, aDst: TStream);
+begin
+ case fCompressionType of
+ ctZlib: ZCompressStream(aSrc, aDst);
+ ctLZ4Frame: lz4CompressStream(aSrc, aDst);
+ ctLZ4Block: lz4BlockCompressStream(aSrc, aDst);
+ else
+ raise Exception.Create('Archive compression type is undefined');
+ end;
+end;
+
+procedure TwbBSArchive.DecompressBuf(aSrc: Pointer; aSrcSize: Integer; aDst: Pointer; aDstSize: Integer);
+begin
+ case fCompressionType of
+ ctZlib: try
+ DecompressToUserBuf(aSrc, aSrcSize, aDst, aDstSize);
+ except
+ // ignore zlib's Buffer error since it happens in vanilla "Fallout - Misc.bsa"
+ // Bethesda probably used old buggy zlib version when packing it
+ on E: Exception do if E.Message <> 'Buffer error' then raise;
+ end;
+ ctLZ4Frame: lz4DecompressToUserBuf(aSrc, aSrcSize, aDst, aDstSize);
+ ctLZ4Block: lz4BlockDecompressToUserBuf(aSrc, aSrcSize, aDst, aDstSize);
+ end;
+end;
+
+procedure TwbBSArchive.PackData(aFileRecord: Pointer; const aFileName: string;
+ aDataHash: TPackedDataHash; aData: PByte; aSize: Integer;
+ aCompress: Boolean; aDoCompress: Boolean = False);
+var
+ zStream: TBytesStream;
+ msData: TPreallocatedMemoryStream;
+ DataSize: Integer;
+ Position: Int64;
+begin
+ DataSize := aSize;
+ zStream := nil;
+ msData := nil;
+
+ if FindPackedData(DataSize, aDataHash, aFileRecord) then
+ Exit;
+
+ try
+ if aCompress then begin
+ // compressing in parallel when multithreaded
+ SyncEndWrite;
+ try
+ zStream := TBytesStream.Create;
+ msData := TPreallocatedMemoryStream.Create(aData, aSize);
+ CompressStream(msData, zStream);
+
+ // leave as compressed if compression reduced the size
+ // by at least let's say 32 bytes
+ // or data is forced to be compressed
+ if aDoCompress or (zStream.Size + 32 < aSize) then begin
+ aData := @zStream.Bytes[0];
+ aSize := zStream.Size;
+ end else
+ aCompress := False;
+ finally
+ SyncBeginWrite;
+ end;
+ end;
+
+ // let's try to find existing data again if multithreaded
+ // maybe some other thread has written the same data while we've been busy compressing
+ // zStream exists if we've really spent time compressing
+ if fMultiThreaded and Assigned(zStream) then
+ if FindPackedData(DataSize, aDataHash, aFileRecord) then
+ Exit;
+
+ Position := fStream.Position;
+
+ // embedded name for Fallout 3/NV/Skyrim/Skyrim SE
+ if (fType in [baFO3, baSSE]) and (fHeaderTES4.Flags and ARCHIVE_EMBEDNAME <> 0) then
+ fStream.WriteStringLen(aFileName, False);
+
+ // if compressed then write uncompressed size first for Oblivion/Fallout 3/NV/Skyrim/Skyrim SE
+ if (fType in [baTES4, baFO3, baSSE]) and aCompress then
+ fStream.WriteCardinal(DataSize);
+
+ fStream.Write(aData^, aSize);
+
+ // updating file record
+ case fType of
+ baTES3: with PwbBSFileTES3(aFileRecord)^ do begin
+ Offset := Position;
+ Size := DataSize;
+ end;
+ baTES4, baFO3, baSSE: with PwbBSFileTES4(aFileRecord)^ do begin
+ Offset := Position;
+ Size := fStream.Position - Offset;
+ // compress flag in Size inverts compression status from the header
+ // set it if archive's compression doesn't match file's compression
+ if Self.fCompress xor aCompress then
+ Size := Size or FILE_SIZE_COMPRESS;
+ end;
+ baFO4, baSF: with PwbBSFileFO4(aFileRecord)^ do begin
+ Offset := Position;
+ Size := DataSize;
+ if aCompress then
+ PackedSize := aSize;
+ end;
+ baFO4dds, baSFdds: with PwbBSTexChunkRec(aFileRecord)^ do begin
+ Offset := Position;
+ Size := DataSize;
+ if aCompress then
+ PackedSize := aSize;
+ end;
+ end;
+
+ AddPackedData(DataSize, aDataHash, aFileRecord);
+
+ finally
+ if Assigned(zStream) then
+ zStream.Free;
+ if Assigned(msData) then
+ msData.Free;
+ end;
+end;
+
+procedure TwbBSArchive.AddFileData(const aFileName: string; const aSize: Cardinal; const aData: PByte);
+var
+ i, j, Off, MipSize, BitsPerPixel: integer;
+ DataHash: TPackedDataHash;
+ DDSHeader: PDDSHeader;
+ DDSHeaderDX10: PDDSHeaderDX10;
+ DDSInfo: TDDSInfo;
+begin
+ if not (stWriting in fStates) then
+ raise Exception.Create('Archive is not in writing mode');
+
+ // dds mipmaps have their own partial hash calculation down below
+ if fShareData and not (fType in [baFO4dds, baSFdds]) then
+ DataHash := CalcDataHash(@aData[0], aSize);
+
+ SyncBeginWrite;
+ try
+ case fType of
+ baTES3: begin
+ if not FindFileRecordTES3(aFileName, i) then
+ raise Exception.Create('File not found in files table: ' + aFileName);
+
+ PackData(@fFilesTES3[i], aFileName, DataHash, @aData[0], aSize, False);
+ end;
+
+ baTES4, baFO3, baSSE: begin
+ if not FindFileRecordTES4(aFileName, i, j) then
+ raise Exception.Create('File not found in files table: ' + aFileName);
+
+ PackData(
+ @fFoldersTES4[i].Files[j], fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name,
+ DataHash, @aData[0], aSize, fFoldersTES4[i].Files[j].Compress(Self)
+ );
+ end;
+
+ baFO4, baSF: begin
+ if not FindFileRecordFO4(aFileName, i) then
+ raise Exception.Create('File not found in files table: ' + aFileName);
+
+ fFilesFO4[i].Offset := fStream.Position;
+ fFilesFO4[i].Size := aSize;
+
+ PackData(
+ @fFilesFO4[i], fFilesFO4[i].Name,
+ DataHash, @aData[0], aSize, fFilesFO4[i].Compress(Self)
+ );
+ end;
+
+ baFO4dds, baSFdds: begin
+ if not FindFileRecordFO4(aFileName, i) then
+ raise Exception.Create('File not found in files table: ' + aFileName);
+
+ fFilesFO4[i].UnknownTex := 0;
+ // DDS file parameters
+ DDSHeader := @aData[0];
+ Off := SizeOf(DDSHeader^); // offset to image data
+ fFilesFO4[i].Width := DDSHeader.dwWidth;
+ fFilesFO4[i].Height := DDSHeader.dwHeight;
+ fFilesFO4[i].NumMips := DDSHeader.dwMipMapCount;
+ // no mipmaps is equal to a single one
+ if fFilesFO4[i].NumMips = 0 then
+ fFilesFO4[i].NumMips := 1;
+
+ // DXGI detection
+ if DDSHeader.ddspf.dwFourCC = MAGIC_DXT1 then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC1_UNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_DXT3 then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC2_UNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_DXT5 then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC3_UNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_ATI1 then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC4_UNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_BC4U then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC4_UNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_BC4S then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC4_SNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_ATI2 then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC5_UNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_BC5U then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC5_UNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_BC5S then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_BC5_SNORM)
+ else if DDSHeader.ddspf.dwFourCC = MAGIC_DX10 then begin
+ DDSHeaderDX10 := @aData[Off];
+ Off := Off + SizeOf(DDSHeaderDX10^);
+ fFilesFO4[i].DXGIFormat := Byte(DDSHeaderDX10.dxgiFormat);
+ end
+ else begin
+ if DDSHeader.ddspf.dwRGBBitCount = 32 then
+ if DDSHeader.ddspf.dwFlags and DDPF_ALPHAPIXELS = 0 then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_B8G8R8X8_UNORM)
+ else if DDSHeader.ddspf.dwRBitMask = $000000FF then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_R8G8B8A8_UNORM)
+ else
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_B8G8R8A8_UNORM)
+
+ else if DDSHeader.ddspf.dwRGBBitCount = 16 then
+ if (DDSHeader.ddspf.dwRBitMask = $F800) and (DDSHeader.ddspf.dwGBitMask = $07E0) and
+ (DDSHeader.ddspf.dwBBitMask = $001F) and (DDSHeader.ddspf.dwABitMask = $0000) then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_B5G6R5_UNORM)
+ else if (DDSHeader.ddspf.dwRBitMask = $7C00) and (DDSHeader.ddspf.dwGBitMask = $03E0) and
+ (DDSHeader.ddspf.dwBBitMask = $001F) and (DDSHeader.ddspf.dwABitMask = $8000) then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_B5G5R5A1_UNORM)
+ else
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_R8G8_UNORM)
+
+ else if DDSHeader.ddspf.dwRGBBitCount = 8 then
+ if DDSHeader.ddspf.dwFlags and DDPF_ALPHA <> 0 then
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_A8_UNORM)
+ else
+ fFilesFO4[i].DXGIFormat := Byte(DXGI_FORMAT_R8_UNORM)
+
+ else
+ raise Exception.Create('Unsupported uncompressed DDS format');
+ end;
+
+ // MipMap size detection
+ case TDXGI(fFilesFO4[i].DXGIFormat) of
+ DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM_SRGB,
+ DXGI_FORMAT_BC4_UNORM, DXGI_FORMAT_BC4_SNORM:
+ BitsPerPixel := 4;
+ DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM_SRGB,
+ DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM_SRGB,
+ DXGI_FORMAT_BC5_UNORM, DXGI_FORMAT_BC5_SNORM,
+ DXGI_FORMAT_BC6H_SF16, DXGI_FORMAT_BC6H_UF16,
+ DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM_SRGB,
+ DXGI_FORMAT_A8_UNORM,
+ DXGI_FORMAT_R8_SINT, DXGI_FORMAT_R8_SNORM,
+ DXGI_FORMAT_R8_UINT, DXGI_FORMAT_R8_UNORM:
+ BitsPerPixel := 8;
+ DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM,
+ DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_R8G8_UINT, DXGI_FORMAT_R8G8_UNORM:
+ BitsPerPixel := 16;
+ DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
+ DXGI_FORMAT_B8G8R8X8_UNORM, DXGI_FORMAT_B8G8R8X8_UNORM_SRGB,
+ DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R8G8B8A8_SINT,
+ DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+ BitsPerPixel := 32;
+ else
+ raise Exception.Create('Unsupported DDS format');
+ end;
+ MipSize := (fFilesFO4[i].Width * fFilesFO4[i].Height * BitsPerPixel) shr 3;
+
+ // cubemaps detection
+ fFilesFO4[i].CubeMaps := $800;
+ if DDSHeader.dwCaps2 and DDSCAPS2_CUBEMAP <> 0 then
+ fFilesFO4[i].CubeMaps := fFilesFO4[i].CubeMaps or 1;
+
+ // number of chunks to store in file record
+ DDSInfo.Width := fFilesFO4[i].Width;
+ DDSInfo.Height := fFilesFO4[i].Height;
+ DDSInfo.MipMaps := fFilesFO4[i].NumMips;
+ SetLength(fFilesFO4[i].TexChunks, GetDDSMipChunkNum(DDSInfo));
+
+ // storing chunks
+ for j := Low(fFilesFO4[i].TexChunks) to High(fFilesFO4[i].TexChunks) do begin
+ fFilesFO4[i].TexChunks[j].StartMip := j;
+ if j < High(fFilesFO4[i].TexChunks) then
+ fFilesFO4[i].TexChunks[j].EndMip := j
+ else begin
+ // last chunk stores all remaining mipmaps
+ fFilesFO4[i].TexChunks[j].EndMip := Pred(fFilesFO4[i].NumMips);
+ MipSize := Integer(aSize) - Off;
+ end;
+
+ DataHash := CalcDataHash(@aData[Off], MipSize);
+
+ PackData(
+ @fFilesFO4[i].TexChunks[j], fFilesFO4[i].Name,
+ DataHash, @aData[Off], MipSize,
+ fFilesFO4[i].Compress(Self), fFilesFO4[i].Compress(Self) // force compression
+ );
+ Inc(Off, MipSize);
+ MipSize := MipSize div 4;
+ end;
+ end;
+
+ end;
+ finally
+ SyncEndWrite;
+ end;
+end;
+
+function TwbBSArchive.ExtractFileData(aFileRecord: Pointer): TwbBSResultBuffer;
+var
+ FileTES3: PwbBSFileTES3;
+ FileTES4: PwbBSFileTES4;
+ FileFO4: PwbBSFileFO4;
+ DDSHeader: PDDSHeader;
+ DDSHeaderDX10: PDDSHeaderDX10;
+ i, size, TexSize: integer;
+ bCompressed: Boolean;
+ Buffer: TBytes;
+begin
+ if not (stReading in fStates) then
+ raise Exception.Create('Archive is not loaded');
+
+ if aFileRecord = nil then
+ Exit;
+
+ SyncBeginWrite;
+ try
+ case fType of
+ baTES3: begin
+ FileTES3 := aFileRecord;
+ fStream.Position := fDataOffset + FileTES3.Offset;
+ Result.size := FileTES3.Size;
+ // Modified: Make sure memory is zeroed when allocated
+ Result.data := AllocMem(FileTES3.Size);
+ end;
+
+ baTES4, baFO3, baSSE: begin
+ FileTES4 := aFileRecord;
+ fStream.Position := FileTES4.Offset;
+ size := FileTES4.Size;
+ bCompressed := size and FILE_SIZE_COMPRESS <> 0;
+ if bCompressed then
+ size := size and not FILE_SIZE_COMPRESS;
+ if fHeaderTES4.Flags and ARCHIVE_COMPRESS <> 0 then
+ bCompressed := not bCompressed;
+
+ // skip embedded file name + length prefix
+ if (fType in [baFO3, baSSE]) and (fHeaderTES4.Flags and ARCHIVE_EMBEDNAME <> 0) then
+ size := size - (Length(fStream.ReadStringLen(False)) + 1);
+
+ if bCompressed then begin
+ // reading uncompressed size
+ Result.size := fStream.ReadCardinal;
+ // Modified: Make sure memory is zeroed when allocated
+ Result.data := AllocMem(Result.size);
+ dec(size, SizeOf(Cardinal));
+ if (Result.size > 0) and (size > 0) then begin
+ SetLength(Buffer, size);
+ fStream.ReadBuffer(Buffer[0], Length(Buffer));
+ SyncEndWrite;
+ try
+ DecompressBuf(@Buffer[0], Length(Buffer), @Result.data[0], Result.size);
+ finally
+ SyncBeginWrite;
+ end;
+ end;
+ end
+ else begin
+ Result.size := size;
+ // Modified: Make sure memory is zeroed when allocated
+ Result.data := AllocMem(Result.size);
+ if size > 0 then
+ fStream.ReadBuffer(Result.data[0], Result.size);
+ end;
+ end;
+
+ baFO4, baSF: begin
+ FileFO4 := aFileRecord;
+ fStream.Position := FileFO4.Offset;
+ if FileFO4.PackedSize <> 0 then begin
+ SetLength(Buffer, FileFO4.PackedSize);
+ fStream.ReadBuffer(Buffer[0], Length(Buffer));
+ Result.size := FileFO4.Size;
+ // Modified: Make sure memory is zeroed when allocated
+ Result.data := AllocMem(Result.size);
+ SyncEndWrite;
+ try
+ DecompressBuf(@Buffer[0], Length(Buffer), @Result.data[0], Result.size);
+ finally
+ SyncBeginWrite;
+ end;
+ end
+ else begin
+ Result.size := FileFO4.Size;
+ // Modified: Make sure memory is zeroed when allocated
+ Result.data := AllocMem(Result.size);
+ fStream.ReadBuffer(Result.data[0], Result.size);
+ end;
+ end;
+
+ baFO4dds, baSFdds: begin
+ FileFO4 := aFileRecord;
+
+ TexSize := SizeOf(TDDSHeader);
+ for i := Low(FileFO4.TexChunks) to High(FileFO4.TexChunks) do
+ Inc(TexSize, FileFO4.TexChunks[i].Size);
+ Result.size := Texsize;
+
+ // Modified: Make sure memory is zeroed when allocated
+ Result.data := AllocMem(Result.size);
+
+ DDSHeader := @Result.data[0];
+ DDSHeader.Magic := MAGIC_DDS;
+ DDSHeader.dwSize := SizeOf(TDDSHeader) - SizeOf(TMagic4);
+ DDSHeader.dwWidth := FileFO4.Width;
+ DDSHeader.dwHeight := FileFO4.Height;
+ DDSHeader.dwFlags := DDSD_CAPS or DDSD_PIXELFORMAT or
+ DDSD_WIDTH or DDSD_HEIGHT or DDSD_MIPMAPCOUNT;
+ DDSHeader.dwCaps := DDSCAPS_TEXTURE;
+ DDSHeader.dwMipMapCount := FileFO4.NumMips;
+ if DDSHeader.dwMipMapCount > 1 then
+ DDSHeader.dwCaps := DDSHeader.dwCaps or DDSCAPS_MIPMAP or DDSCAPS_COMPLEX;
+ DDSHeader.dwDepth := 1;
+
+ DDSHeaderDX10 := @Result.data[SizeOf(TDDSHeader)];
+ DDSHeaderDX10.resourceDimension := DDS_DIMENSION_TEXTURE2D;
+ DDSHeaderDX10.arraySize := 1;
+
+ if FileFO4.CubeMaps = 2049 then begin
+ // Archive2.exe creates invalid textures like this
+ //DDSHeader.dwCaps := DDSHeader.dwCaps or DDSCAPS2_CUBEMAP or DDSCAPS_COMPLEX
+ // or DDSCAPS2_POSITIVEX or DDSCAPS2_NEGATIVEX
+ // or DDSCAPS2_POSITIVEY or DDSCAPS2_NEGATIVEY
+ // or DDSCAPS2_POSITIVEZ or DDSCAPS2_NEGATIVEZ;
+ // This is the correct way
+ DDSHeader.dwCaps := DDSHeader.dwCaps or DDSCAPS_COMPLEX;
+ DDSHeader.dwCaps2 := DDSCAPS2_CUBEMAP
+ or DDSCAPS2_POSITIVEX or DDSCAPS2_NEGATIVEX
+ or DDSCAPS2_POSITIVEY or DDSCAPS2_NEGATIVEY
+ or DDSCAPS2_POSITIVEZ or DDSCAPS2_NEGATIVEZ;
+ DDSHeaderDX10.miscFlags := DDS_RESOURCE_MISC_TEXTURECUBE;
+ end;
+ DDSHeader.ddspf.dwSize := SizeOf(DDSHeader.ddspf);
+
+ case TDXGI(FileFO4.DXGIFormat) of
+ DXGI_FORMAT_BC1_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_DXT1;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height div 2;
+ end;
+ DXGI_FORMAT_BC2_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_DXT3;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height;
+ end;
+ DXGI_FORMAT_BC3_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_DXT5;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height;
+ end;
+ DXGI_FORMAT_BC4_SNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_BC4S;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height div 2;
+ end;
+ DXGI_FORMAT_BC4_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_BC4U;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height div 2;
+ end;
+ DXGI_FORMAT_BC5_SNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_BC5S;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height;
+ end;
+ DXGI_FORMAT_BC5_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_BC5U;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height;
+ end;
+ DXGI_FORMAT_BC1_UNORM_SRGB: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_DX10;
+ DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat);
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height div 2;
+ end;
+ DXGI_FORMAT_BC2_UNORM_SRGB, DXGI_FORMAT_BC3_UNORM_SRGB,
+ DXGI_FORMAT_BC6H_UF16, DXGI_FORMAT_BC6H_SF16,
+ DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM_SRGB: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_LINEARSIZE;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_DX10;
+ DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat);
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * FileFO4.Height;
+ end;
+ DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, DXGI_FORMAT_B8G8R8X8_UNORM_SRGB,
+ DXGI_FORMAT_R8G8B8A8_SINT, DXGI_FORMAT_R8G8B8A8_UINT, DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_DX10;
+ DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat);
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 4;
+ end;
+ DXGI_FORMAT_R8G8_SINT, DXGI_FORMAT_R8G8_UINT: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_DX10;
+ DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat);
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 2;
+ end;
+ DXGI_FORMAT_R8_SINT, DXGI_FORMAT_R8_SNORM, DXGI_FORMAT_R8_UINT: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_FOURCC;
+ DDSHeader.ddspf.dwFourCC := MAGIC_DX10;
+ DDSHeaderDX10.dxgiFormat := Integer(FileFO4.DXGIFormat);
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width;
+ end;
+ DXGI_FORMAT_R8G8B8A8_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_RGB or DDPF_ALPHAPIXELS;
+ DDSHeader.ddspf.dwRGBBitCount := 32;
+ DDSHeader.ddspf.dwRBitMask := $000000FF;
+ DDSHeader.ddspf.dwGBitMask := $0000FF00;
+ DDSHeader.ddspf.dwBBitMask := $00FF0000;
+ DDSHeader.ddspf.dwABitMask := $FF000000;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 4;
+ end;
+ DXGI_FORMAT_B8G8R8A8_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_RGB or DDPF_ALPHAPIXELS;
+ DDSHeader.ddspf.dwRGBBitCount := 32;
+ DDSHeader.ddspf.dwRBitMask := $00FF0000;
+ DDSHeader.ddspf.dwGBitMask := $0000FF00;
+ DDSHeader.ddspf.dwBBitMask := $000000FF;
+ DDSHeader.ddspf.dwABitMask := $FF000000;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 4;
+ end;
+ DXGI_FORMAT_B8G8R8X8_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_RGB;
+ DDSHeader.ddspf.dwRGBBitCount := 32;
+ DDSHeader.ddspf.dwRBitMask := $00FF0000;
+ DDSHeader.ddspf.dwGBitMask := $0000FF00;
+ DDSHeader.ddspf.dwBBitMask := $000000FF;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 4;
+ end;
+ DXGI_FORMAT_B5G6R5_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_RGB;
+ DDSHeader.ddspf.dwRGBBitCount := 16;
+ DDSHeader.ddspf.dwRBitMask := $0000F800;
+ DDSHeader.ddspf.dwGBitMask := $000007E0;
+ DDSHeader.ddspf.dwBBitMask := $0000001F;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 2;
+ end;
+ DXGI_FORMAT_B5G5R5A1_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_RGB or DDPF_ALPHAPIXELS;
+ DDSHeader.ddspf.dwRGBBitCount := 16;
+ DDSHeader.ddspf.dwRBitMask := $00007C00;
+ DDSHeader.ddspf.dwGBitMask := $000003E0;
+ DDSHeader.ddspf.dwBBitMask := $0000001F;
+ DDSHeader.ddspf.dwABitMask := $00008000;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 2;
+ end;
+ DXGI_FORMAT_R8G8_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_LUMINANCE OR DDPF_ALPHAPIXELS;
+ DDSHeader.ddspf.dwRGBBitCount := 16;
+ DDSHeader.ddspf.dwRBitMask := $000000FF;
+ DDSHeader.ddspf.dwABitMask := $0000FF00;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width * 2;
+ end;
+ DXGI_FORMAT_A8_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_ALPHA;
+ DDSHeader.ddspf.dwRGBBitCount := 8;
+ DDSHeader.ddspf.dwABitMask := $000000FF;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width;
+ end;
+ DXGI_FORMAT_R8_UNORM: begin
+ DDSHeader.dwFlags := DDSHeader.dwFlags or DDSD_PITCH;
+ DDSHeader.ddspf.dwFlags := DDPF_LUMINANCE;
+ DDSHeader.ddspf.dwRGBBitCount := 8;
+ DDSHeader.ddspf.dwRBitMask := $000000FF;
+ DDSHeader.dwPitchOrLinearSize := FileFO4.Width;
+ end;
+ end;
+
+ TexSize := SizeOf(TDDSHeader);
+ if DDSHeader.ddspf.dwFourCC = MAGIC_DX10 then begin
+ ReallocMem(Result.data, Result.size + SizeOf(TDDSHeaderDX10));
+ Inc(TexSize, SizeOf(TDDSHeaderDX10));
+ Result.size := TexSize;
+ end;
+
+ // append chunks
+ for i := Low(FileFO4.TexChunks) to High(FileFO4.TexChunks) do with FileFO4.TexChunks[i] do begin
+ fStream.Position := Offset;
+ // compressed chunk
+ if PackedSize <> 0 then begin
+ SetLength(Buffer, PackedSize);
+ fStream.ReadBuffer(Buffer[0], Length(Buffer));
+ SyncEndWrite;
+ try
+ DecompressBuf(@Buffer[0], Length(Buffer), @Result.data[TexSize], Size);
+ finally
+ SyncBeginWrite;
+ end;
+ end
+ // uncompressed chunk
+ else
+ fStream.ReadBuffer(Result.data[TexSize], Size);
+
+ Inc(TexSize, Size);
+ Result.size := TexSize;
+ end;
+ end
+
+ else
+ raise Exception.Create('Extraction is not supported for this archive');
+ end;
+ finally
+ SyncEndWrite;
+ end;
+end;
+
+function TwbBSArchive.ExtractFileData(const aFileName: string): TwbBSResultBuffer;
+var
+ FileRecord: Pointer;
+begin
+ if not (stReading in fStates) then
+ raise Exception.Create('Archive is not loaded');
+
+ FileRecord := FindFileRecord(aFileName);
+
+ if not Assigned(FileRecord) then
+ raise Exception.Create('File not found in archive');
+
+ Result := ExtractFileData(FileRecord);
+end;
+
+// Addded: For use in non-Borland C/C++
+procedure TwbBSArchive.ReleaseFileData(fileDataResult: TwbBSResultBuffer);
+begin
+ FreeMem(fileDataResult.data);
+ fileDataResult.size := 0;
+end;
+
+procedure TwbBSArchive.ExtractFile(const aFileName, aSaveAs: string);
+var
+ fs: TFileStream;
+ fileData: TwbBSResultBuffer;
+begin
+ if not (stReading in fStates) then
+ raise Exception.Create('Archive is not loaded');
+
+ fs := TFileStream.Create(aSaveAs, fmCreate);
+ try
+ fileData := ExtractFileData(aFileName);
+ fs.Write(fileData.data[0], fileData.size);
+ finally
+ ReleaseFileData(fileData);
+ fs.Free;
+ end;
+end;
+
+procedure TwbBSArchive.IterateFiles(aProc: TBSFileIterationProc; aData: Pointer = nil;
+ aSingleThreaded: Boolean = False);
+var
+ i, j: Integer;
+begin
+ if not Assigned(aProc) then
+ Exit;
+
+ if fMultiThreaded and not aSingleThreaded then
+ case fType of
+ baTES3:
+ TParallel.&For(Low(fFilesTES3), High(fFilesTES3), procedure(i: Integer; LoopState: TParallel.TLoopState) begin
+ if aProc(Self, fFilesTES3[i].Name, @fFilesTES3[i], nil, aData) then
+ LoopState.Stop;
+ end);
+ baTES4, baFO3, baSSE:
+ TParallel.&For(Low(fFoldersTES4), High(fFoldersTES4), procedure(i: Integer; OuterLoopState: TParallel.TLoopState) begin
+ TParallel.&For(Low(fFoldersTES4[i].Files), High(fFoldersTES4[i].Files), procedure(j: Integer; InnerLoopState: TParallel.TLoopState) begin
+ if aProc(Self, fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name, @fFoldersTES4[i].Files[j], @fFoldersTES4[i], aData) then begin
+ OuterLoopState.Stop;
+ InnerLoopState.Stop;
+ end;
+ end);
+ end);
+ baFO4, baFO4dds, baSF, baSFdds:
+ TParallel.&For(Low(fFilesFO4), High(fFilesFO4), procedure(i: Integer; LoopState: TParallel.TLoopState) begin
+ if aProc(Self, fFilesFO4[i].Name, @fFilesFO4[i], nil, aData) then
+ LoopState.Stop;
+ end);
+ end
+ else
+ case fType of
+ baTES3:
+ for i := Low(fFilesTES3) to High(fFilesTES3) do
+ if aProc(Self, fFilesTES3[i].Name, @fFilesTES3[i], nil, aData) then
+ Break;
+ baTES4, baFO3, baSSE:
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do
+ for j := Low(fFoldersTES4[i].Files) to High(fFoldersTES4[i].Files) do
+ if aProc(Self, fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name, @fFoldersTES4[i].Files[j], @fFoldersTES4[i], aData) then
+ Break;
+ baFO4, baFO4dds, baSF, baSFdds:
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ if aProc(Self, fFilesFO4[i].Name, @fFilesFO4[i], nil, aData) then
+ Break;
+ end;
+end;
+
+{procedure TwbBSArchive.IterateFolders(aProc: TBSFileIterationProc);
+var
+ i: Integer;
+begin
+ if not Assigned(aProc) then
+ Exit;
+
+ if fType in [baTES4, baFO3, baSSE] then
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do
+ aProc(Self, fFoldersTES4[i].Name, nil, @fFoldersTES4[i]);
+end;}
+
+function TwbBSArchive.FileExists(const aFileName: string): Boolean;
+begin
+ Result := Assigned(FindFileRecord(aFileName));
+end;
+
+procedure TwbBSArchive.Close;
+begin
+ if Assigned(fStream) then
+ FreeAndNil(fStream);
+
+ if stWriting in fStates then
+ System.SysUtils.DeleteFile(fFileName);
+
+ fStates := [];
+ fType := baNone;
+ fFileName := '';
+ fDataOffset := 0;
+ FillChar(fHeaderTES3, SizeOf(fHeaderTES3), 0);
+ SetLength(fFilesTES3, 0);
+ FillChar(fHeaderTES4, SizeOf(fHeaderTES4), 0);
+ SetLength(fFoldersTES4, 0);
+ FillChar(fHeaderFO4, SizeOf(fHeaderFO4), 0);
+ SetLength(fFilesFO4, 0);
+
+ if fShareData then begin
+ SetLength(fPackedData, 0);
+ fPackedDataCount := 0;
+ end;
+end;
+
+procedure TwbBSArchive.ResourceDict(const aDict: TwbResourceDict; aFolder: string);
+var
+ Folder : string;
+ i, j : Integer;
+begin
+ if not Assigned(aDict) then
+ Exit;
+
+ Folder := ExcludeTrailingPathDelimiter(aFolder);
+ case fType of
+ baTES3:
+ for i := Low(fFilesTES3) to High(fFilesTES3) do
+ with fFilesTES3[i] do
+ if (Folder = '') or Name.StartsWith(Folder, True) then
+ aDict.TryAdd(Name, wbNothing);
+ baTES4, baFO3, baSSE:
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do
+ with fFoldersTES4[i] do begin
+ if (Folder = '') or Name.StartsWith(Folder, True) then
+ for j := Low(Files) to High(Files) do begin
+ var lName := Name + '\' + Files[j].Name;
+ aDict.TryAdd(lName, wbNothing);
+ end;
+ end;
+ baFO4, baFO4dds, baSF, baSFdds:
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ with fFilesFO4[i] do
+ if (Folder = '') or Name.StartsWith(Folder, True) then
+ aDict.TryAdd(Name, wbNothing);
+ end;
+end;
+
+procedure TwbBSArchive.ResourceList(const aList: TStrings; aFolder: string = '');
+var
+ Folder : string;
+ i, j : Integer;
+begin
+ if not Assigned(aList) then
+ Exit;
+
+ Folder := ExcludeTrailingPathDelimiter(aFolder);
+ case fType of
+ baTES3:
+ for i := Low(fFilesTES3) to High(fFilesTES3) do
+ with fFilesTES3[i] do
+ if (Folder = '') or Name.StartsWith(Folder, True) then
+ aList.Add(Name);
+ baTES4, baFO3, baSSE:
+ for i := Low(fFoldersTES4) to High(fFoldersTES4) do
+ with fFoldersTES4[i] do begin
+ if (Folder = '') or Name.StartsWith(Folder, True) then
+ for j := Low(Files) to High(Files) do
+ aList.Add(Name + '\' + Files[j].Name);
+ end;
+ baFO4, baFO4dds, baSF, baSFdds:
+ for i := Low(fFilesFO4) to High(fFilesFO4) do
+ with fFilesFO4[i] do
+ if (Folder = '') or Name.StartsWith(Folder, True) then
+ aList.Add(Name);
+ end;
+
+end;
+
+end.
diff --git a/libs/libbsarch/examples/libbsarch-visualstudio-test.cpp b/libs/libbsarch/examples/libbsarch-visualstudio-test.cpp
new file mode 100644
index 0000000..1183ee9
--- /dev/null
+++ b/libs/libbsarch/examples/libbsarch-visualstudio-test.cpp
@@ -0,0 +1,190 @@
+#include <iostream>
+#include <windows.h>
+
+#include <libbsarch/libbsarch.h>
+
+// #pragma comment(lib, "libbsarch")
+
+static const std::wstring separators(L"\\/");
+
+std::wstring dirname(const std::wstring &path)
+{
+ size_t slash_pos = path.find_last_of(separators);
+ return path.substr(0, slash_pos);
+}
+
+std::wstring basename(const std::wstring &path)
+{
+ size_t slash_pos = path.find_last_of(separators);
+ return path.substr(slash_pos + 1);
+}
+
+bool mkdirp(const std::wstring &directory, bool basedir = true)
+{
+ DWORD attributes = ::GetFileAttributesW(directory.c_str());
+ if (attributes == INVALID_FILE_ATTRIBUTES)
+ {
+ std::size_t slash_pos = directory.find_last_of(separators);
+ if (slash_pos != std::wstring::npos)
+ {
+ mkdirp(directory.substr(0, slash_pos));
+ }
+ return ::CreateDirectoryW(directory.c_str(), nullptr);
+ }
+ else
+ {
+ return true;
+ }
+}
+
+void dds_info(bsa_archive_t archive, const wchar_t *file_path, bsa_dds_info_t *dds_info, void *context)
+{
+ // Mock result
+ dds_info->width = 2048;
+ dds_info->height = 2048;
+ dds_info->mipmaps = 12;
+
+ printf("ba2 dds_info callback file: %ls, context: %s\n", file_path, (char *) context);
+}
+
+int main()
+{
+ bsa_result_message_t result = {0};
+ bsa_archive_t archive = bsa_create();
+
+ {
+ result = bsa_load_from_file(archive, L"test_read.bsa");
+ if (result.code < 0)
+ printf("ba1 %ls\n", result.text);
+
+ bsa_entry_list_t entries = bsa_entry_list_create();
+ bsa_get_resource_list(archive, entries, L"");
+
+ for (size_t index = 0; index < bsa_entry_list_count(entries); index++)
+ {
+ wchar_t filename[2048];
+ wchar_t dest_filename[2048] = L"test_ba1\\";
+
+ bsa_entry_list_get(entries, index, 2048, filename);
+ wcscat_s(dest_filename, 2048, filename);
+
+ printf("ba1 file: %ls %ls\n", filename, dest_filename);
+
+ if (mkdirp(dirname(dest_filename)))
+ {
+ result = bsa_extract_file(archive, filename, dest_filename);
+ if (result.code < 0)
+ printf("ba1 %ls\n", result.text);
+ }
+ else
+ {
+ printf("ba1 could not create: %ls\n", dirname(dest_filename).c_str());
+ }
+ }
+
+ bsa_entry_list_free(entries);
+
+ bsa_close(archive);
+ }
+
+ {
+ bsa_entry_list_t entries = bsa_entry_list_create();
+ bsa_entry_list_add(entries, L"textures\\grass\\test.dds");
+ bsa_entry_list_add(entries, L"textures\\grass\\test2.dds");
+
+ bsa_create_archive(archive, L"test_write.bsa", baSSE, entries);
+ bsa_add_file_from_disk_root(archive, L"test_ba1\\", L"test_ba1\\textures\\grass\\test.dds");
+ result = bsa_add_file_from_disk(archive, L"textures\\grass\\test2.dds", L"test_ba1\\textures\\grass\\test.dds");
+ if (result.code < 0)
+ printf("ba1 %ls\n", result.text);
+ bsa_save(archive);
+ bsa_close(archive);
+
+ bsa_entry_list_free(entries);
+ }
+
+ bsa_file_dds_info_callback_set(archive, dds_info, (void *) "I was shared");
+
+ {
+ result = bsa_load_from_file(archive, L"test_read.ba2");
+ if (result.code < 0)
+ printf("ba2 %ls\n", result.text);
+
+ bsa_entry_list_t entries = bsa_entry_list_create();
+ bsa_get_resource_list(archive, entries, L"");
+
+ for (size_t index = 0; index < bsa_entry_list_count(entries); index++)
+ {
+ wchar_t filename[2048];
+ wchar_t dest_filename[2048] = L"test_ba2\\";
+
+ bsa_entry_list_get(entries, index, 2048, filename);
+ wcscat_s(dest_filename, 2048, filename);
+
+ printf("ba2 file: %ls %ls\n", filename, dest_filename);
+
+ if (mkdirp(dirname(dest_filename)))
+ {
+ result = bsa_extract_file(archive, filename, dest_filename);
+ if (result.code < 0)
+ printf("ba2 %ls\n", result.text);
+ }
+ else
+ {
+ printf("ba2 could not create: %ls\n", dirname(dest_filename).c_str());
+ }
+ }
+
+ bsa_entry_list_free(entries);
+
+ bsa_close(archive);
+ }
+
+ {
+ bsa_entry_list_t entries = bsa_entry_list_create();
+ bsa_entry_list_add(entries, L"textures\\grass\\test.dds");
+ bsa_entry_list_add(entries, L"textures\\grass\\test2.dds");
+
+ bsa_create_archive(archive, L"test_write.ba2", baFO4dds, entries);
+ bsa_add_file_from_disk_root(archive, L"test_ba2\\", L"test_ba2\\textures\\grass\\test.dds");
+ result = bsa_add_file_from_disk(archive, L"textures\\grass\\test2.dds", L"test_ba2\\textures\\grass\\test.dds");
+ if (result.code < 0)
+ printf("ba2 %ls\n", result.text);
+ bsa_save(archive);
+ bsa_close(archive);
+
+ bsa_entry_list_free(entries);
+ }
+
+ /*
+ {
+ result = bsa_load_from_file(archive, L"test_large.ba2");
+ if (result.code < 0)
+ printf("ba2 large %ls\n", result.text);
+
+ bsa_entry_list_t entries = bsa_entry_list_create();
+ bsa_get_resource_list(archive, entries, L"");
+
+ for (size_t index = 0; index < bsa_entry_list_count(entries); index++) {
+ wchar_t filename[2048];
+ wchar_t dest_filename[2048] = L"test_ba2_large\\";
+
+ bsa_entry_list_get(entries, index, 2048, filename);
+ wcscat_s(dest_filename, 2048, filename);
+
+ printf("ba2 file large: %ls %ls\n", filename, dest_filename);
+
+ if (mkdirp(dirname(dest_filename))) {
+ result = bsa_extract_file(archive, filename, dest_filename);
+ if (result.code < 0)
+ printf("ba2 large %ls\n", result.text);
+ }
+ else {
+ printf("ba2 large could not create: %ls\n", dirname(dest_filename).c_str());
+ }
+ }
+ }
+ */
+
+ bsa_free(archive);
+}
diff --git a/libs/libbsarch/examples/libbsarch-visualstudio-test.sln b/libs/libbsarch/examples/libbsarch-visualstudio-test.sln
new file mode 100644
index 0000000..8f105e4
--- /dev/null
+++ b/libs/libbsarch/examples/libbsarch-visualstudio-test.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.28803.156
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbsarch-visualstudio-test", "libbsarch-visualstudio-test.vcxproj", "{8F73F848-939D-4862-B58B-26572CF20A5F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {8F73F848-939D-4862-B58B-26572CF20A5F}.Debug|x64.ActiveCfg = Debug|x64
+ {8F73F848-939D-4862-B58B-26572CF20A5F}.Debug|x64.Build.0 = Debug|x64
+ {8F73F848-939D-4862-B58B-26572CF20A5F}.Debug|x86.ActiveCfg = Debug|Win32
+ {8F73F848-939D-4862-B58B-26572CF20A5F}.Debug|x86.Build.0 = Debug|Win32
+ {8F73F848-939D-4862-B58B-26572CF20A5F}.Release|x64.ActiveCfg = Release|x64
+ {8F73F848-939D-4862-B58B-26572CF20A5F}.Release|x64.Build.0 = Release|x64
+ {8F73F848-939D-4862-B58B-26572CF20A5F}.Release|x86.ActiveCfg = Release|Win32
+ {8F73F848-939D-4862-B58B-26572CF20A5F}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {F05F0FBF-790B-4443-B2EC-E7033074D9E0}
+ EndGlobalSection
+EndGlobal
diff --git a/libs/libbsarch/examples/test_read.ba2 b/libs/libbsarch/examples/test_read.ba2
new file mode 100644
index 0000000..16162ca
--- /dev/null
+++ b/libs/libbsarch/examples/test_read.ba2
Binary files differ
diff --git a/libs/libbsarch/examples/test_read.bsa b/libs/libbsarch/examples/test_read.bsa
new file mode 100644
index 0000000..e493003
--- /dev/null
+++ b/libs/libbsarch/examples/test_read.bsa
Binary files differ
diff --git a/libs/libbsarch/include/libbsarch/base_types.hpp b/libs/libbsarch/include/libbsarch/base_types.hpp
new file mode 100644
index 0000000..2baf4be
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/base_types.hpp
@@ -0,0 +1,66 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#pragma once
+
+#include "libbsarch.h"
+#include "utils/convertible_string.hpp"
+#include <variant>
+
+namespace libbsarch {
+struct memory_blob : public bsa_result_buffer_t
+{
+ memory_blob(uint32_t buffer_size, bsa_buffer_t buffer_data, bsa_archive_t parent)
+ : bsa_result_buffer_t{buffer_size, buffer_data}
+ , parent_(parent)
+ {}
+
+ memory_blob(bsa_result_buffer_t buffer, bsa_archive_t parent)
+ : bsa_result_buffer_t(buffer)
+ , parent_(parent)
+ {}
+
+ bsa_archive_t parent_;
+
+ memory_blob(memory_blob const &) = default;
+ ~memory_blob() { bsa_file_data_free(parent_, *this); }
+};
+
+struct disk_blob
+{
+ disk_blob(const convertible_string &root_dir, const convertible_string &absolute_path)
+ : path_in_archive(absolute_path)
+ , path_on_disk(absolute_path)
+ {
+ path_in_archive.remove_substring(root_dir);
+ }
+
+ disk_blob(const convertible_string &path_in_archive,
+ const convertible_string &absolute_path,
+ [[maybe_unused]] bool decoy_parameter)
+ : path_in_archive(path_in_archive)
+ , path_on_disk(absolute_path)
+ {}
+
+ convertible_string path_in_archive;
+ convertible_string path_on_disk;
+};
+
+struct bs_packed_file
+{
+ bs_packed_file(const disk_blob &blob)
+ : path_in_archive(blob.path_in_archive)
+ , data(blob.path_on_disk)
+ {}
+
+ bs_packed_file(convertible_string path_in_archive, memory_blob blob)
+ : path_in_archive(std::move(path_in_archive))
+ , data(blob)
+ {}
+
+ convertible_string path_in_archive;
+ std::variant<memory_blob, convertible_string> data;
+};
+
+} // namespace libbsarch
diff --git a/libs/libbsarch/include/libbsarch/bs_archive.h b/libs/libbsarch/include/libbsarch/bs_archive.h
new file mode 100644
index 0000000..1954e99
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/bs_archive.h
@@ -0,0 +1,100 @@
+#pragma once
+
+#include "bs_archive_entries.h"
+#include "libbsarch.hpp"
+
+namespace libbsarch {
+class bs_archive
+{
+public:
+ bs_archive();
+ bs_archive(const bsa_archive_type_t &type);
+ virtual ~bs_archive();
+
+ /* Properties */
+ convertible_string get_filename();
+ bsa_archive_type_t get_type();
+ uint32_t get_version();
+ convertible_string get_format_name();
+ uint32_t get_file_count();
+
+ uint32_t get_archive_flags();
+ void set_archive_flags(uint32_t flags);
+
+ uint32_t get_file_flags();
+ void set_file_flags(uint32_t flags);
+
+ void set_compressed(bool value);
+ bool get_compressed();
+
+ void set_share_data(bool value);
+ bool get_share_data();
+
+ /* Input-Output */
+ virtual void load_from_disk(const convertible_string &archive_path);
+ virtual void create(const convertible_string &archiveName, const bs_archive_entries &entries);
+ virtual void create(const convertible_string &archiveName,
+ const bs_archive_entries &entries,
+ const bsa_archive_type_t type);
+ void save() const;
+
+ /* Add to archive */
+ virtual void add_file_from_disk(const disk_blob &blob);
+ virtual void add_file_from_memory(const convertible_string &path_in_archive, const memory_blob &memory_data);
+
+ /* Extract */
+ memory_blob extract_to_memory(bsa_file_record_t record);
+ memory_blob extract_to_memory(const convertible_string &filename);
+ void extract_to_disk(const convertible_string &filename, const convertible_string &save_as) const;
+
+ /* Selectors */
+ bsa_file_record_t find_file_record(const convertible_string &filename);
+ std::vector<convertible_string> list_files() const;
+
+ /* Other */
+
+ /*! \brief Sets the DDS callback. It is required to use Fallout 4 DDS archives. You do not have
+ * to manage the lifetime of the provided context
+ * \example
+ void dds_callback([[maybe_unused]] bsa_archive_t archive,
+ const wchar_t *file_path,
+ bsa_dds_info_t *dds_info,
+ void *context)
+ {
+ const auto &path = *static_cast<convertible_string *>(context) + '/' + convertible_string(file_path);
+
+ auto image = std::make_unique<DirectX::ScratchImage>();
+ DirectX::TexMetadata info;
+
+ const auto hr = LoadFromDDSFile(PREPARE_PATH_LIBBSARCH(path), DirectX::DDS_FLAGS_BAD_DXTN_TAILS, &info, *image);
+ if (FAILED(hr))
+ {
+ //Do not throw here. Exceptions will be ignored
+ return;
+ }
+
+ dds_info->width = info.width;
+ dds_info->height = info.height;
+ dds_info->mipmaps = info.mipLevels;
+ }
+ */
+ template<typename context_type>
+ void set_dds_callback(bsa_file_dds_info_proc_t dds_function, const context_type &context)
+ {
+ debug_log() << "Setting DDS callback for archive: " << archive_ << "\nCallback adress: " << &dds_function;
+
+ auto heap_context = new context_type(context);
+ callback_contexts_.emplace_back(heap_context);
+ bsa_file_dds_info_callback_set(archive_, dds_function, callback_contexts_.back());
+ }
+
+ bsa_archive_t get_archive() const;
+
+protected:
+ bsa_archive_t archive_;
+ bsa_archive_type_t type_;
+
+private:
+ std::vector<void *> callback_contexts_;
+};
+} // namespace libbsarch
diff --git a/libs/libbsarch/include/libbsarch/bs_archive_auto.hpp b/libs/libbsarch/include/libbsarch/bs_archive_auto.hpp
new file mode 100644
index 0000000..c288f68
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/bs_archive_auto.hpp
@@ -0,0 +1,39 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#pragma once
+
+#include "bs_archive.h"
+
+namespace libbsarch {
+/*!
+ * \brief The bs_archive_auto class is a convenience class. It manages both bs_archive and bs_archive_entries.
+ * It is recommended when packing from disk/extracting to disk
+ * Otherwise, if using memory, the use of these two separate classes is recommended
+ */
+class bs_archive_auto : public bs_archive
+{
+protected:
+ bs_archive_entries entries_;
+
+public:
+ bs_archive_auto() = default;
+ bs_archive_auto(const bsa_archive_type_t &type);
+
+ void add_file_from_disk(const disk_blob &blob) override;
+ void add_file_from_disk(const std::vector<disk_blob> &blobs);
+ void add_file_from_memory(const convertible_string &path_in_archive, const memory_blob &memory_data) override;
+ void add_file(const bs_packed_file &file);
+
+ void extract_all(const convertible_string &directory, bool overwrite_current_files);
+
+ void load_from_disk(const convertible_string &archive_path) override;
+ void save_to_disk(const convertible_string &archive_path);
+
+private:
+ using bs_archive::create; //Use save_to_disk instead
+ using bs_archive::save; //Use save_to_disk instead
+ std::vector<bs_packed_file> files_;
+};
+} // namespace libbsarch
diff --git a/libs/libbsarch/include/libbsarch/bs_archive_entries.h b/libs/libbsarch/include/libbsarch/bs_archive_entries.h
new file mode 100644
index 0000000..7df2f40
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/bs_archive_entries.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "libbsarch.hpp"
+#include <vector>
+
+namespace libbsarch {
+class bs_archive_entries
+{
+public:
+ bs_archive_entries();
+ explicit bs_archive_entries(const std::vector<convertible_string> &entries);
+ explicit bs_archive_entries(const bsa_entry_list_t &entries);
+ ~bs_archive_entries();
+
+ void add(const convertible_string &filepath);
+ uint32_t count();
+ std::vector<convertible_string> list();
+
+ bsa_entry_list_t getEntries() const;
+
+protected:
+ bsa_entry_list_t _entries;
+};
+} // namespace libbsarch
diff --git a/libs/libbsarch/include/libbsarch/libbsarch.h b/libs/libbsarch/include/libbsarch/libbsarch.h
new file mode 100644
index 0000000..bda7ca0
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/libbsarch.h
@@ -0,0 +1,157 @@
+#pragma once
+
+#include <dxgiformat.h>
+
+#include <DDS.h>
+
+#include <stdint.h>
+
+#ifdef _WIN32
+ #ifdef BSARCH_DLL_EXPORT
+ #define BSARCH_DLL_API(ReturnType) extern "C" __declspec(dllexport) ReturnType __stdcall
+ #else
+ #define BSARCH_DLL_API(ReturnType) extern "C" __declspec(dllimport) ReturnType __stdcall
+ #endif
+#else
+ #ifdef BSARCH_DLL_EXPORT
+ #define BSARCH_DLL_API(ReturnType) extern "C" __attribute__((visibility("default"))) ReturnType
+ #else
+ #define BSARCH_DLL_API(ReturnType) extern "C" ReturnType
+ #endif
+#endif
+
+#ifdef __GNUC__
+#define PACKED(datastructure) datastructure __attribute__((__packed__))
+#else
+#define PACKED(datastructure) __pragma(pack(push, 1)) datastructure __pragma(pack(pop))
+#endif
+
+typedef enum bsa_result_code_e
+{
+ BSA_RESULT_NONE = 0,
+ BSA_RESULT_EXCEPTION = -1
+} bsa_result_code_t;
+
+typedef void *bsa_buffer_t;
+typedef void *bsa_archive_t;
+typedef void *bsa_entry_list_t;
+typedef void *bsa_file_record_t;
+typedef void *bsa_folder_record_t;
+
+typedef struct bsa_dds_info_s
+{
+ uint32_t width, height, mipmaps;
+} bsa_dds_info_t;
+
+PACKED(struct bsa_dds_header_s {
+ uint32_t magic; // DDS_MAGIC
+ DirectX::DDS_HEADER header;
+});
+
+typedef struct bsa_dds_header_s bsa_dds_header_t;
+
+PACKED(struct bsa_result_message_s {
+ int8_t code; // bsa_result_code_t
+ wchar_t text[1024];
+});
+
+typedef struct bsa_result_message_s bsa_result_message_t;
+
+PACKED(struct bsa_result_buffer_s {
+ uint32_t size;
+ bsa_buffer_t data;
+});
+
+typedef struct bsa_result_buffer_s bsa_result_buffer_t;
+
+PACKED(struct bsa_result_message_buffer_s {
+ bsa_result_buffer_t buffer;
+ bsa_result_message_t message;
+});
+
+typedef struct bsa_result_message_buffer_s bsa_result_message_buffer_t;
+
+typedef enum bsa_archive_state_e
+{
+ stReading,
+ stWriting
+} bsa_archive_state_t;
+
+typedef enum bsa_archive_type_e
+{
+ baNone,
+ baTES3,
+ baTES4,
+ baFO3,
+ baSSE,
+ baFO4,
+ baFO4dds,
+ baSF,
+ baSFdds
+} bsa_archive_type_t;
+
+typedef void (*bsa_file_dds_info_proc_t)(bsa_archive_t archive,
+ const wchar_t *file_path,
+ bsa_dds_info_t *dds_info,
+ void *context);
+typedef bool (*bsa_file_iteration_proc_t)(bsa_archive_t archive,
+ const wchar_t *file_path,
+ bsa_file_record_t file_record,
+ bsa_folder_record_t folder_record,
+ void *context);
+
+BSARCH_DLL_API(bsa_entry_list_t) bsa_entry_list_create();
+BSARCH_DLL_API(bsa_result_message_t) bsa_entry_list_free(bsa_entry_list_t entry_list);
+BSARCH_DLL_API(uint32_t) bsa_entry_list_count(bsa_entry_list_t entry_list);
+BSARCH_DLL_API(bsa_result_message_t) bsa_entry_list_add(bsa_entry_list_t entry_list, const wchar_t *entry_string);
+BSARCH_DLL_API(uint32_t)
+bsa_entry_list_get(bsa_entry_list_t entry_list, uint32_t index, uint32_t string_buffer_size, wchar_t *string_buffer);
+
+BSARCH_DLL_API(bsa_archive_t) bsa_create();
+BSARCH_DLL_API(bsa_result_message_t) bsa_free(bsa_archive_t archive);
+BSARCH_DLL_API(bsa_result_message_t) bsa_load_from_file(bsa_archive_t archive, const wchar_t *file_path);
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_create_archive(bsa_archive_t archive,
+ const wchar_t *file_path,
+ bsa_archive_type_t _archivetype,
+ bsa_entry_list_t entry_list);
+BSARCH_DLL_API(bsa_result_message_t) bsa_save(bsa_archive_t archive);
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_add_file_from_disk(bsa_archive_t archive, const wchar_t *file_path, const wchar_t *source_path);
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_add_file_from_disk_root(bsa_archive_t archive, const wchar_t *root_dir, const wchar_t *source_path);
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_add_file_from_memory(bsa_archive_t archive, const wchar_t *file_path, uint32_t size, bsa_buffer_t data);
+BSARCH_DLL_API(bsa_file_record_t) bsa_find_file_record(bsa_archive_t archive, const wchar_t *file_path);
+BSARCH_DLL_API(bsa_result_message_buffer_t)
+bsa_extract_file_data_by_record(bsa_archive_t archive, bsa_file_record_t file_record);
+BSARCH_DLL_API(bsa_result_message_buffer_t)
+bsa_extract_file_data_by_filename(bsa_archive_t archive, const wchar_t *file_path);
+BSARCH_DLL_API(bsa_result_message_t) bsa_file_data_free(bsa_archive_t archive, bsa_result_buffer_t file_data_result);
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_extract_file(bsa_archive_t archive, const wchar_t *file_path, const wchar_t *save_as);
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_iterate_files(bsa_archive_t archive, bsa_file_iteration_proc_t file_iteration_proc, void *context);
+BSARCH_DLL_API(bool) bsa_file_exists(bsa_archive_t archive, const wchar_t *file_path);
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_get_resource_list(bsa_archive_t archive, bsa_entry_list_t entry_result_list, const wchar_t *folder);
+BSARCH_DLL_API(bsa_result_message_t) bsa_close(bsa_archive_t archive);
+
+BSARCH_DLL_API(uint32_t)
+bsa_filename_get(bsa_archive_t archive, uint32_t string_buffer_size, wchar_t *string_buffer);
+BSARCH_DLL_API(bsa_archive_type_t) bsa_archive_type_get(bsa_archive_t archive);
+BSARCH_DLL_API(uint32_t) bsa_version_get(bsa_archive_t archive);
+BSARCH_DLL_API(uint32_t)
+bsa_format_name_get(bsa_archive_t archive, uint32_t string_buffer_size, wchar_t *string_buffer);
+BSARCH_DLL_API(uint32_t) bsa_file_count_get(bsa_archive_t archive);
+BSARCH_DLL_API(uint32_t) bsa_archive_flags_get(bsa_archive_t archive);
+BSARCH_DLL_API(void) bsa_archive_flags_set(bsa_archive_t archive, uint32_t flags);
+BSARCH_DLL_API(uint32_t) bsa_file_flags_get(bsa_archive_t archive);
+BSARCH_DLL_API(void) bsa_file_flags_set(bsa_archive_t archive, uint32_t flags);
+BSARCH_DLL_API(bool) bsa_compress_get(bsa_archive_t archive);
+BSARCH_DLL_API(void) bsa_compress_set(bsa_archive_t archive, bool flags);
+BSARCH_DLL_API(bool) bsa_share_data_get(bsa_archive_t archive);
+BSARCH_DLL_API(void) bsa_share_data_set(bsa_archive_t archive, bool flags);
+
+BSARCH_DLL_API(void)
+bsa_file_dds_info_callback_set(bsa_archive_t archive, bsa_file_dds_info_proc_t file_dds_info_proc, void *context);
diff --git a/libs/libbsarch/include/libbsarch/libbsarch.hpp b/libs/libbsarch/include/libbsarch/libbsarch.hpp
new file mode 100644
index 0000000..2a6acce
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/libbsarch.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "base_types.hpp"
+#include "utils/convertible_ostream.hpp"
+#include "utils/convertible_string.hpp"
+
+#include <cstring>
+
+namespace libbsarch {
+constexpr bool enable_debug_log = true;
+[[maybe_unused]] constexpr int max_string_buffer_size = 1024;
+
+inline convertible_ostream &debug_log()
+{
+ static convertible_ostream ostr;
+ if constexpr (enable_debug_log)
+ return ostr << "[libbsarch] " << __FUNCTION__ << ' ';
+}
+
+inline void checkResult(const bsa_result_message_s &result)
+{
+ if (result.code == BSA_RESULT_EXCEPTION)
+ {
+ // Copy from packed struct to aligned local buffer to avoid UB
+ wchar_t aligned_text[1024];
+ std::memcpy(aligned_text, result.text, sizeof(aligned_text));
+ const std::string error = to_string(std::wstring(aligned_text));
+ debug_log() << error;
+ throw std::runtime_error(error);
+ }
+}
+
+inline void checkResult(const bsa_result_message_buffer_s &result)
+{
+ checkResult(result.message);
+}
+} // namespace libbsarch
diff --git a/libs/libbsarch/include/libbsarch/utils/convertible_ostream.hpp b/libs/libbsarch/include/libbsarch/utils/convertible_ostream.hpp
new file mode 100644
index 0000000..80babb0
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/utils/convertible_ostream.hpp
@@ -0,0 +1,37 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#pragma once
+
+#include "convertible_string.hpp"
+#include <iostream>
+
+namespace libbsarch {
+class convertible_ostream
+{
+public:
+ convertible_ostream &operator<<(const std::string &rh);
+ convertible_ostream &operator<<(const char *rh);
+ convertible_ostream &operator<<(const char rh);
+ convertible_ostream &operator<<(const std::wstring &rh);
+ convertible_ostream &operator<<(const void *rh);
+ convertible_ostream &operator<<(const convertible_string &rh);
+
+ template<typename number, //real type
+ typename = typename std::enable_if<std::is_arithmetic<number>::value, number>::type>
+ convertible_ostream &operator<<(const number &num)
+ {
+ std::cout << num;
+ return *this;
+ }
+
+ operator std::ostream &();
+ operator std::wostream &();
+
+#ifdef LIBBSARCH_QT_SUPPORT
+ operator QDebug();
+ convertible_ostream &operator<<(const QString &rh);
+#endif
+};
+} // namespace libbsarch
diff --git a/libs/libbsarch/include/libbsarch/utils/convertible_string.hpp b/libs/libbsarch/include/libbsarch/utils/convertible_string.hpp
new file mode 100644
index 0000000..e6b996d
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/utils/convertible_string.hpp
@@ -0,0 +1,50 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#pragma once
+
+//See https://stackoverflow.com/questions/4804298/how-to-convert-wstring-into-string
+
+#ifdef LIBBSARCH_QT_SUPPORT
+#include <QString>
+#endif
+
+#include "string_convert.hpp"
+
+namespace libbsarch {
+
+template<typename String>
+class convertible_string_converter;
+
+class convertible_string
+{
+public:
+ // default ctor
+ convertible_string() = default;
+
+ /* conversion ctors */
+ convertible_string(std::string value, bool to_native_path = true);
+ convertible_string(const char *const val_array, bool to_native_path = true);
+ convertible_string(const std::wstring &wvalue, bool to_native_path = true);
+ convertible_string(const wchar_t *const wval_array, bool to_native_path = true);
+
+ /* assignment operators */
+ convertible_string &operator=(const std::string &value);
+ convertible_string &operator=(const std::wstring &wvalue);
+ convertible_string &operator=(const wchar_t *wvalue);
+
+ /* implicit conversion operators */
+ operator std::string() const;
+ operator std::wstring() const;
+ operator const wchar_t *() const;
+
+ /* Util */
+ bool remove_substring(const convertible_string &sub_str);
+ convertible_string &to_native_path();
+
+private:
+ std::string str_;
+ bool auto_convert_to_native_path = true;
+};
+} // namespace libbsarch
diff --git a/libs/libbsarch/include/libbsarch/utils/string_convert.hpp b/libs/libbsarch/include/libbsarch/utils/string_convert.hpp
new file mode 100644
index 0000000..6086fe1
--- /dev/null
+++ b/libs/libbsarch/include/libbsarch/utils/string_convert.hpp
@@ -0,0 +1,18 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#pragma once
+
+#ifdef LIBBSARCH_QT_SUPPORT
+#include <QDebug>
+#endif
+
+#include <codecvt>
+#include <locale>
+#include <string>
+
+namespace libbsarch {
+std::string to_string(const std::wstring &str);
+std::wstring to_wstring(const std::string &str);
+} // namespace libbsarch
diff --git a/libs/libbsarch/src/bs_archive.cpp b/libs/libbsarch/src/bs_archive.cpp
new file mode 100644
index 0000000..b50ef5b
--- /dev/null
+++ b/libs/libbsarch/src/bs_archive.cpp
@@ -0,0 +1,185 @@
+#include "bs_archive.h"
+
+namespace libbsarch {
+
+bs_archive::bs_archive()
+ : archive_(bsa_create())
+{}
+
+bs_archive::bs_archive(const bsa_archive_type_t &type)
+ : archive_(bsa_create())
+ , type_(type)
+{}
+
+bs_archive::~bs_archive()
+{
+ bsa_close(archive_);
+ bsa_free(archive_);
+}
+
+/* Properties */
+convertible_string bs_archive::get_filename()
+{
+ wchar_t name[max_string_buffer_size];
+ bsa_filename_get(archive_, max_string_buffer_size, name);
+ return name;
+}
+
+bsa_archive_type_t bs_archive::get_type()
+{
+ return bsa_archive_type_get(archive_);
+}
+
+uint32_t bs_archive::get_version()
+{
+ return bsa_version_get(archive_);
+}
+
+convertible_string bs_archive::get_format_name()
+{
+ wchar_t format_name[max_string_buffer_size];
+ bsa_format_name_get(archive_, max_string_buffer_size, format_name);
+ return format_name;
+}
+
+uint32_t bs_archive::get_file_count()
+{
+ return bsa_file_count_get(archive_);
+}
+
+uint32_t bs_archive::get_archive_flags()
+{
+ return bsa_archive_flags_get(archive_);
+}
+
+void bs_archive::set_archive_flags(uint32_t flags)
+{
+ bsa_archive_flags_set(archive_, flags);
+}
+
+uint32_t bs_archive::get_file_flags()
+{
+ return bsa_file_flags_get(archive_);
+}
+
+void bs_archive::set_file_flags(uint32_t flags)
+{
+ bsa_file_flags_set(archive_, flags);
+}
+
+void bs_archive::set_compressed(bool value)
+{
+ bsa_compress_set(archive_, value);
+}
+
+void bs_archive::set_share_data(bool value)
+{
+ bsa_share_data_set(archive_, value);
+}
+
+bool bs_archive::get_compressed()
+{
+ return bsa_compress_get(archive_);
+}
+
+bool bs_archive::get_share_data()
+{
+ return bsa_share_data_get(archive_);
+}
+
+void bs_archive::load_from_disk(const convertible_string &archive_path)
+{
+ debug_log() << "Opening archive: " << archive_path;
+ const std::wstring str = archive_path;
+ const auto &result = bsa_load_from_file(archive_, str.c_str());
+ libbsarch::checkResult(result);
+}
+
+void bs_archive::create(const convertible_string &archiveName, const bs_archive_entries &entries)
+{
+ create(archiveName, entries, type_);
+}
+
+void bs_archive::create(const convertible_string &archiveName,
+ const bs_archive_entries &entries,
+ const bsa_archive_type_t type)
+{
+ debug_log() << "Creating archive. Archive name: " << archiveName << '\n'
+ << "type: " << type_ << '\n'
+ << "entries: " << entries.getEntries();
+ bsa_create_archive(archive_, archiveName, type, entries.getEntries());
+}
+
+void bs_archive::save() const
+{
+ debug_log() << "Saving archive: " << archive_;
+
+ const auto &result = bsa_save(archive_);
+
+ libbsarch::checkResult(result);
+}
+
+void bs_archive::add_file_from_disk(const disk_blob &blob)
+{
+ debug_log() << "Adding file from disk root. Path in archive: " << blob.path_in_archive << '\n'
+ << "Filepath: " << blob.path_on_disk << '\n'
+ << "Archive: " << archive_;
+
+ const auto &result = bsa_add_file_from_disk(archive_, blob.path_in_archive, blob.path_on_disk);
+
+ libbsarch::checkResult(result);
+}
+
+void bs_archive::add_file_from_memory(const convertible_string &path_in_archive,
+ const memory_blob &memory_data) //NOTE UNTESTED
+{
+ debug_log() << "Adding file from memory. Filename: " << path_in_archive << '\n'
+ << "Data size: " << memory_data.size;
+ const auto &result = bsa_add_file_from_memory(archive_, path_in_archive, memory_data.size, memory_data.data);
+ libbsarch::checkResult(result);
+}
+
+bsa_file_record_t bs_archive::find_file_record(const convertible_string &filename)
+{
+ debug_log() << "Finding file record of: " << filename;
+ const auto &result = bsa_find_file_record(archive_, filename);
+ return result;
+}
+
+memory_blob bs_archive::extract_to_memory(bsa_file_record_t record)
+{
+ const auto &result = bsa_extract_file_data_by_record(archive_, record);
+ libbsarch::checkResult(result);
+ return memory_blob(result.buffer, archive_);
+}
+
+memory_blob bs_archive::extract_to_memory(const convertible_string &filename)
+{
+ const auto &result = bsa_extract_file_data_by_filename(archive_, filename);
+ libbsarch::checkResult(result);
+ return memory_blob(result.buffer, archive_);
+}
+
+void bs_archive::extract_to_disk(const convertible_string &filename, const convertible_string &save_as) const
+{
+ debug_log() << "Extracting: " << filename << " saved as " << save_as;
+ const auto &result = bsa_extract_file(archive_, filename, save_as);
+
+ libbsarch::checkResult(result);
+}
+
+std::vector<convertible_string> bs_archive::list_files() const
+{
+ bsa_entry_list_t list = bsa_entry_list_create();
+ const auto &result = bsa_get_resource_list(archive_, list, L"");
+
+ libbsarch::checkResult(result);
+
+ return bs_archive_entries(list).list();
+}
+
+bsa_archive_t bs_archive::get_archive() const
+{
+ return archive_;
+}
+} // namespace libbsarch
diff --git a/libs/libbsarch/src/bs_archive_auto.cpp b/libs/libbsarch/src/bs_archive_auto.cpp
new file mode 100644
index 0000000..1ad8e3e
--- /dev/null
+++ b/libs/libbsarch/src/bs_archive_auto.cpp
@@ -0,0 +1,87 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "bs_archive_auto.hpp"
+#include <filesystem>
+namespace libbsarch {
+bs_archive_auto::bs_archive_auto(const bsa_archive_type_t &type)
+ : bs_archive(type)
+{}
+
+void bs_archive_auto::add_file_from_disk(const disk_blob &blob)
+{
+ files_.emplace_back(blob);
+}
+
+void bs_archive_auto::add_file_from_disk(const std::vector<disk_blob> &blobs)
+{
+ files_.reserve(blobs.size());
+ files_.insert(files_.end(), blobs.cbegin(), blobs.cend());
+}
+
+void bs_archive_auto::add_file_from_memory(const convertible_string &path_in_archive, const memory_blob &memory_data)
+{
+ files_.emplace_back(bs_packed_file(path_in_archive, memory_data));
+}
+
+void bs_archive_auto::add_file(const bs_packed_file &file)
+{
+ files_.emplace_back(file);
+}
+
+void bs_archive_auto::extract_all(const convertible_string &directory, bool overwrite_current_files)
+{
+ namespace fs = std::filesystem;
+ for (const auto &file : list_files())
+ {
+ convertible_string path_string = std::string(directory) + "/" + std::string(file);
+ fs::path current_file(path_string);
+ fs::path current_path = fs::path(current_file).remove_filename();
+
+ fs::create_directories(current_path);
+ if (fs::exists(current_file) && overwrite_current_files)
+ {
+ fs::remove(current_file);
+ extract_to_disk(file, current_file.string());
+ }
+ else if (!fs::exists(current_file))
+ extract_to_disk(file, current_file.string());
+ }
+}
+
+void bs_archive_auto::load_from_disk(const convertible_string &archive_path)
+{
+ bs_archive::load_from_disk(archive_path);
+ for (const auto &file : list_files())
+ entries_.add(file);
+}
+
+void bs_archive_auto::save_to_disk(const convertible_string &archive_path)
+{
+ for (const auto &file : files_)
+ entries_.add(file.path_in_archive);
+
+ bs_archive::create(archive_path, entries_);
+
+ for (const auto &file : files_)
+ {
+ switch (file.data.index())
+ {
+ case 0: {
+ const auto &blob = std::get<memory_blob>(file.data);
+ bs_archive::add_file_from_memory(file.path_in_archive, blob);
+ break;
+ }
+ case 1: {
+ const auto &file_path = std::get<convertible_string>(file.data);
+ const auto &blob = disk_blob(file.path_in_archive, file_path, bool());
+ bs_archive::add_file_from_disk(blob);
+ }
+ }
+ }
+ bs_archive::save();
+}
+
+} // namespace libbsarch
diff --git a/libs/libbsarch/src/bs_archive_entries.cpp b/libs/libbsarch/src/bs_archive_entries.cpp
new file mode 100644
index 0000000..4dc9c6d
--- /dev/null
+++ b/libs/libbsarch/src/bs_archive_entries.cpp
@@ -0,0 +1,52 @@
+#include "bs_archive_entries.h"
+
+namespace libbsarch {
+bs_archive_entries::bs_archive_entries()
+ : _entries(bsa_entry_list_create())
+{}
+
+bs_archive_entries::bs_archive_entries(const std::vector<convertible_string> &entries)
+ : _entries(bsa_entry_list_create())
+{
+ for (const auto &entry : entries)
+ add(entry);
+}
+
+bs_archive_entries::bs_archive_entries(const bsa_entry_list_t &entries)
+ : _entries(entries)
+{}
+
+bs_archive_entries::~bs_archive_entries()
+{
+ bsa_entry_list_free(_entries);
+}
+
+void bs_archive_entries::add(const convertible_string &filepath)
+{
+ debug_log() << "Adding to entries: " << filepath;
+ const auto &result = bsa_entry_list_add(_entries, filepath);
+ libbsarch::checkResult(result);
+}
+
+uint32_t bs_archive_entries::count()
+{
+ return bsa_entry_list_count(_entries);
+}
+
+std::vector<convertible_string> bs_archive_entries::list()
+{
+ std::vector<convertible_string> list;
+ for (uint32_t i = 0; i < count(); ++i)
+ {
+ wchar_t buffer[max_string_buffer_size];
+ bsa_entry_list_get(_entries, i, max_string_buffer_size, buffer);
+ list.push_back(buffer);
+ }
+ return list;
+}
+
+bsa_entry_list_t bs_archive_entries::getEntries() const
+{
+ return _entries;
+}
+} // namespace libbsarch
diff --git a/libs/libbsarch/src/libbsarch.cpp b/libs/libbsarch/src/libbsarch.cpp
new file mode 100644
index 0000000..52ded9f
--- /dev/null
+++ b/libs/libbsarch/src/libbsarch.cpp
@@ -0,0 +1,206 @@
+#ifdef BSARCH_DLL_EXPORT
+#include "libbsarch.h"
+
+namespace libbsarch {
+
+BSARCH_DLL_API(bsa_entry_list_t) bsa_entry_list_create()
+{
+ return nullptr;
+}
+
+BSARCH_DLL_API(bsa_result_message_t) bsa_entry_list_free(bsa_entry_list_t entry_list)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(uint32_t) bsa_entry_list_count(bsa_entry_list_t entry_list)
+{
+ return 0;
+}
+
+BSARCH_DLL_API(bsa_result_message_t) bsa_entry_list_add(bsa_entry_list_t entry_list, const wchar_t *entry_string)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(uint32_t)
+bsa_entry_list_get(bsa_entry_list_t entry_list, uint32_t index, uint32_t string_buffer_size, wchar_t *string_buffer)
+{
+ return 0;
+}
+
+BSARCH_DLL_API(bsa_archive_t) bsa_create()
+{
+ return nullptr;
+}
+
+BSARCH_DLL_API(bsa_result_message_t) bsa_free(bsa_archive_t archive)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t) bsa_load_from_file(bsa_archive_t archive, const wchar_t *file_path)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_create_archive(bsa_archive_t archive,
+ const wchar_t *file_path,
+ bsa_archive_type_t archive_type,
+ bsa_entry_list_t entry_list)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t) bsa_save(bsa_archive_t archive)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_add_file_from_disk(bsa_archive_t archive, const wchar_t *file_path, const wchar_t *source_path)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_add_file_from_disk_root(bsa_archive_t archive, const wchar_t *root_dir, const wchar_t *source_path)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_add_file_from_memory(bsa_archive_t archive, const wchar_t *file_path, uint32_t size, bsa_buffer_t data)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_file_record_t) bsa_find_file_record(bsa_archive_t archive, const wchar_t *file_path)
+{
+ return nullptr;
+}
+
+BSARCH_DLL_API(bsa_result_message_buffer_t)
+bsa_extract_file_data_by_record(bsa_archive_t archive, bsa_file_record_t file_record)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_buffer_t)
+bsa_extract_file_data_by_filename(bsa_archive_t archive, const wchar_t *file_path)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t) bsa_file_data_free(bsa_archive_t archive, bsa_result_buffer_t file_data_result)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_extract_file(bsa_archive_t archive, const wchar_t *file_path, const wchar_t *save_as)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_iterate_files(bsa_archive_t archive, bsa_file_iteration_proc_t file_iteration_proc, void *context)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bool) bsa_file_exists(bsa_archive_t archive, const wchar_t *file_path)
+{
+ return false;
+}
+
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_get_resource_list(bsa_archive_t archive, bsa_entry_list_t entry_result_list, const wchar_t *folder)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t)
+bsa_resolve_hash(bsa_archive_t archive, uint64_t hash, bsa_entry_list_t entry_result_list)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(bsa_result_message_t) bsa_close(bsa_archive_t archive)
+{
+ return {0};
+}
+
+BSARCH_DLL_API(uint32_t) bsa_filename_get(bsa_archive_t archive, uint32_t string_buffer_size, wchar_t *string_buffer)
+{
+ return 0;
+}
+
+BSARCH_DLL_API(bsa_archive_type_t) bsa_archive_type_get(bsa_archive_t archive)
+{
+ return bsa_archive_type_t::baNone;
+}
+
+BSARCH_DLL_API(uint32_t) bsa_version_get(bsa_archive_t archive)
+{
+ return 0;
+}
+
+BSARCH_DLL_API(uint32_t) bsa_format_name_get(bsa_archive_t archive, uint32_t string_buffer_size, wchar_t *string_buffer)
+{
+ return 0;
+}
+
+BSARCH_DLL_API(uint32_t) bsa_file_count_get(bsa_archive_t archive)
+{
+ return 0;
+}
+
+BSARCH_DLL_API(uint32_t) bsa_archive_flags_get(bsa_archive_t archive)
+{
+ return 0;
+}
+
+BSARCH_DLL_API(void) bsa_archive_flags_set(bsa_archive_t archive, uint32_t flags)
+{
+ return;
+}
+
+BSARCH_DLL_API(uint32_t) bsa_file_flags_get(bsa_archive_t archive)
+{
+ return 0;
+}
+
+BSARCH_DLL_API(void) bsa_file_flags_set(bsa_archive_t archive, uint32_t flags)
+{
+ return;
+}
+
+BSARCH_DLL_API(bool) bsa_compress_get(bsa_archive_t archive)
+{
+ return false;
+}
+
+BSARCH_DLL_API(void) bsa_compress_set(bsa_archive_t archive, bool flags)
+{
+ return;
+}
+
+BSARCH_DLL_API(bool) bsa_share_data_get(bsa_archive_t archive)
+{
+ return false;
+}
+
+BSARCH_DLL_API(void) bsa_share_data_set(bsa_archive_t archive, bool flags)
+{
+ return;
+}
+
+BSARCH_DLL_API(void)
+bsa_file_dds_info_callback_set(bsa_archive_t archive, bsa_file_dds_info_proc_t file_dds_info_proc, void *context)
+{
+ return;
+}
+} // namespace libbsarch
+#endif
diff --git a/libs/libbsarch/src/libbsarch.def b/libs/libbsarch/src/libbsarch.def
new file mode 100644
index 0000000..d6386b0
--- /dev/null
+++ b/libs/libbsarch/src/libbsarch.def
@@ -0,0 +1,38 @@
+;DESCRIPTION "BSArch DLL"
+EXPORTS
+ bsa_entry_list_create
+ bsa_entry_list_free
+ bsa_entry_list_count
+ bsa_entry_list_add
+ bsa_entry_list_get
+ bsa_create
+ bsa_free
+ bsa_load_from_file
+ bsa_create_archive
+ bsa_save
+ bsa_add_file_from_disk
+ bsa_add_file_from_disk_root
+ bsa_add_file_from_memory
+ bsa_find_file_record
+ bsa_extract_file_data_by_record
+ bsa_extract_file_data_by_filename
+ bsa_file_data_free
+ bsa_extract_file
+ bsa_iterate_files
+ bsa_file_exists
+ bsa_get_resource_list
+ bsa_close
+ bsa_filename_get
+ bsa_archive_type_get
+ bsa_version_get
+ bsa_format_name_get
+ bsa_file_count_get
+ bsa_archive_flags_get
+ bsa_archive_flags_set
+ bsa_file_flags_get
+ bsa_file_flags_set
+ bsa_compress_get
+ bsa_compress_set
+ bsa_share_data_get
+ bsa_share_data_set
+ bsa_file_dds_info_callback_set
diff --git a/libs/libbsarch/src/utils/convertible_ostream.cpp b/libs/libbsarch/src/utils/convertible_ostream.cpp
new file mode 100644
index 0000000..d5ac238
--- /dev/null
+++ b/libs/libbsarch/src/utils/convertible_ostream.cpp
@@ -0,0 +1,58 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#include "convertible_ostream.hpp"
+
+namespace libbsarch {
+convertible_ostream &convertible_ostream::operator<<(const std::string &rh)
+{
+ std::cout << rh;
+ return *this;
+}
+convertible_ostream &convertible_ostream::operator<<(const char *rh)
+{
+ std::cout << rh;
+ return *this;
+}
+convertible_ostream &convertible_ostream::operator<<(const char rh)
+{
+ std::cout << rh;
+ return *this;
+}
+convertible_ostream &convertible_ostream::operator<<(const std::wstring &rh)
+{
+ std::wcout << rh;
+ return *this;
+}
+convertible_ostream &convertible_ostream::operator<<(const void *rh)
+{
+ std::cout << rh;
+ return *this;
+}
+convertible_ostream &convertible_ostream::operator<<(const convertible_string &rh)
+{
+ return *this << std::string(rh);
+}
+
+convertible_ostream::operator std::ostream &()
+{
+ return std::cout;
+}
+convertible_ostream::operator std::wostream &()
+{
+ return std::wcout;
+}
+
+#ifdef LIBBSARCH_QT_SUPPORT
+convertible_ostream::operator QDebug()
+{
+ return qDebug();
+}
+convertible_ostream &convertible_ostream::operator<<(const QString &rh)
+{
+ qDebug() << rh;
+ return *this;
+}
+#endif
+} // namespace libbsarch
diff --git a/libs/libbsarch/src/utils/convertible_string.cpp b/libs/libbsarch/src/utils/convertible_string.cpp
new file mode 100644
index 0000000..c965202
--- /dev/null
+++ b/libs/libbsarch/src/utils/convertible_string.cpp
@@ -0,0 +1,108 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "convertible_string.hpp"
+#include <cwchar>
+#include <filesystem>
+
+namespace libbsarch {
+/* conversion ctors */
+convertible_string::convertible_string(std::string value, bool to_native_path)
+ : str_(std::move(value))
+ , auto_convert_to_native_path(to_native_path)
+{
+ if (auto_convert_to_native_path)
+ this->to_native_path();
+}
+
+convertible_string::convertible_string(const char *const val_array, bool to_native_path)
+ : str_(std::string(val_array))
+ , auto_convert_to_native_path(to_native_path)
+
+{
+ if (auto_convert_to_native_path)
+ this->to_native_path();
+}
+convertible_string::convertible_string(const std::wstring &wvalue, bool to_native_path)
+ : str_(to_string(wvalue))
+ , auto_convert_to_native_path(to_native_path)
+
+{
+ if (auto_convert_to_native_path)
+ this->to_native_path();
+}
+convertible_string::convertible_string(wchar_t const *wval_array, bool to_native_path)
+ : str_(to_string(std::wstring(wval_array)))
+ , auto_convert_to_native_path(to_native_path)
+{
+ if (auto_convert_to_native_path)
+ this->to_native_path();
+}
+
+/* assignment operators */
+convertible_string &convertible_string::operator=(const std::string &value)
+{
+ str_ = value;
+ if (auto_convert_to_native_path)
+ this->to_native_path();
+ return *this;
+}
+convertible_string &convertible_string::operator=(const std::wstring &wvalue)
+{
+ str_ = to_string(wvalue);
+ if (auto_convert_to_native_path)
+ this->to_native_path();
+ return *this;
+}
+
+convertible_string &convertible_string::operator=(const wchar_t *wvalue)
+{
+ str_ = to_string(std::wstring(wvalue));
+ if (auto_convert_to_native_path)
+ this->to_native_path();
+ return *this;
+}
+
+/* implicit conversion operators */
+convertible_string::operator std::string() const
+{
+ return str_;
+}
+convertible_string::operator std::wstring() const
+{
+ return to_wstring(str_);
+}
+convertible_string::operator const wchar_t *() const
+{
+ const std::wstring wstr = to_wstring(str_);
+ wchar_t *wchar_array = new wchar_t[wstr.length() + 1];
+#ifdef _WIN32
+ wcscpy_s(wchar_array, wstr.length() + 1, wstr.c_str());
+#else
+ std::wmemcpy(wchar_array, wstr.c_str(), wstr.length() + 1);
+#endif
+ return wchar_array;
+}
+
+/* Util */
+bool convertible_string::remove_substring(const convertible_string &sub_str)
+{
+ size_t pos = str_.find(sub_str);
+ if (pos != std::string::npos)
+ {
+ str_.erase(pos, std::string(sub_str).length());
+ return true;
+ }
+ return false;
+}
+
+convertible_string &convertible_string::to_native_path()
+{
+ std::filesystem::path path(str_);
+ str_ = path.make_preferred().string();
+ return *this;
+}
+
+} // namespace libbsarch
diff --git a/libs/libbsarch/src/utils/string_convert.cpp b/libs/libbsarch/src/utils/string_convert.cpp
new file mode 100644
index 0000000..1e8ce9e
--- /dev/null
+++ b/libs/libbsarch/src/utils/string_convert.cpp
@@ -0,0 +1,39 @@
+/* Copyright (C) 2019 G'k
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+#include "string_convert.hpp"
+
+#include <cstdlib>
+#include <cstring>
+
+namespace libbsarch {
+
+std::string to_string(const std::wstring &str)
+{
+ if (str.empty()) return {};
+ std::mbstate_t state{};
+ const wchar_t* src = str.data();
+ std::size_t len = std::wcsrtombs(nullptr, &src, 0, &state);
+ if (len == static_cast<std::size_t>(-1)) return {};
+ std::string result(len, '\0');
+ src = str.data();
+ state = std::mbstate_t{};
+ std::wcsrtombs(result.data(), &src, len + 1, &state);
+ return result;
+}
+
+std::wstring to_wstring(const std::string &str)
+{
+ if (str.empty()) return {};
+ std::mbstate_t state{};
+ const char* src = str.data();
+ std::size_t len = std::mbsrtowcs(nullptr, &src, 0, &state);
+ if (len == static_cast<std::size_t>(-1)) return {};
+ std::wstring result(len, L'\0');
+ src = str.data();
+ state = std::mbstate_t{};
+ std::mbsrtowcs(result.data(), &src, len + 1, &state);
+ return result;
+}
+} // namespace libbsarch
diff --git a/libs/libbsarch/vcpkg.json b/libs/libbsarch/vcpkg.json
new file mode 100644
index 0000000..3cfe0a0
--- /dev/null
+++ b/libs/libbsarch/vcpkg.json
@@ -0,0 +1,18 @@
+{
+ "dependencies": ["mo2-dds-header"],
+ "vcpkg-configuration": {
+ "default-registry": {
+ "kind": "git",
+ "repository": "https://github.com/Microsoft/vcpkg",
+ "baseline": "f61a294e765b257926ae9e9d85f96468a0af74e7"
+ },
+ "registries": [
+ {
+ "kind": "git",
+ "repository": "https://github.com/ModOrganizer2/vcpkg-registry",
+ "baseline": "69db61b22147b14ad66fd05cf798d855f6d68c12",
+ "packages": ["mo2-*"]
+ }
+ ]
+ }
+}