diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c0acd85 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +root = true + +[*] +end_of_line = crlf +insert_final_newline = true +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..539c826 --- /dev/null +++ b/.gitignore @@ -0,0 +1,444 @@ +# Created by https://www.gitignore.io/api/vim,delphi,visualstudio,visualstudiocode +# Edit at https://www.gitignore.io/?templates=vim,delphi,visualstudio,visualstudiocode + +### 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) +*.exe +*.dll +*.bpl +*.bpi +*.dcp +*.so +*.apk +*.drc +*.map +*.dres +*.rsm +*.tds +*.dcu +*.lib +*.a +*.o +*.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/ + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history + +### 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 + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# 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 +*- Backup*.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/vim,delphi,visualstudio,visualstudiocode diff --git a/DDS.h b/DDS.h new file mode 100644 index 0000000..59a274c --- /dev/null +++ b/DDS.h @@ -0,0 +1,241 @@ +//-------------------------------------------------------------------------------------- +// dds.h +// +// This header defines constants and structures that are useful when parsing +// DDS files. DDS files were originally designed to use several structures +// and constants that are native to DirectDraw and are defined in ddraw.h, +// such as DDSURFACEDESC2 and DDSCAPS2. This file defines similar +// (compatible) constants and structures so that one can use DDS files +// without needing to include ddraw.h. +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// +// http://go.microsoft.com/fwlink/?LinkId=248926 +// http://go.microsoft.com/fwlink/?LinkId=248929 +// http://go.microsoft.com/fwlink/?LinkID=615561 +//-------------------------------------------------------------------------------------- + +#pragma once + + +namespace DirectX +{ + +#pragma pack(push,1) + +const uint32_t DDS_MAGIC = 0x20534444; // "DDS " + +struct DDS_PIXELFORMAT +{ + uint32_t size; + uint32_t flags; + uint32_t fourCC; + uint32_t RGBBitCount; + uint32_t RBitMask; + uint32_t GBitMask; + uint32_t BBitMask; + uint32_t ABitMask; +}; + +#define DDS_FOURCC 0x00000004 // DDPF_FOURCC +#define DDS_RGB 0x00000040 // DDPF_RGB +#define DDS_RGBA 0x00000041 // DDPF_RGB | DDPF_ALPHAPIXELS +#define DDS_LUMINANCE 0x00020000 // DDPF_LUMINANCE +#define DDS_LUMINANCEA 0x00020001 // DDPF_LUMINANCE | DDPF_ALPHAPIXELS +#define DDS_ALPHAPIXELS 0x00000001 // DDPF_ALPHAPIXELS +#define DDS_ALPHA 0x00000002 // DDPF_ALPHA +#define DDS_PAL8 0x00000020 // DDPF_PALETTEINDEXED8 +#define DDS_PAL8A 0x00000021 // DDPF_PALETTEINDEXED8 | DDPF_ALPHAPIXELS +#define DDS_BUMPDUDV 0x00080000 // DDPF_BUMPDUDV + +#ifndef MAKEFOURCC + #define MAKEFOURCC(ch0, ch1, ch2, ch3) \ + (static_cast(static_cast(ch0)) \ + | (static_cast(static_cast(ch1)) << 8) \ + | (static_cast(static_cast(ch2)) << 16) \ + | (static_cast(static_cast(ch3)) << 24)) +#endif /* defined(MAKEFOURCC) */ + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT1 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','1'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT2 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','2'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT3 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','3'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT4 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','4'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DXT5 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','T','5'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_BC4_UNORM = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B','C','4','U'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_BC4_SNORM = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B','C','4','S'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_BC5_UNORM = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B','C','5','U'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_BC5_SNORM = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('B','C','5','S'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_R8G8_B8G8 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('R','G','B','G'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_G8R8_G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('G','R','G','B'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_YUY2 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('Y','U','Y','2'), 0, 0, 0, 0, 0 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_X8R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8B8G8R8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_X8B8G8R8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_G16R16 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_R5G6B5 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 16, 0x0000f800, 0x000007e0, 0x0000001f, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A1R5G5B5 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00007c00, 0x000003e0, 0x0000001f, 0x00008000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A4R4G4B4 = + { sizeof(DDS_PIXELFORMAT), DDS_RGBA, 0, 16, 0x00000f00, 0x000000f0, 0x0000000f, 0x0000f000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_R8G8B8 = + { sizeof(DDS_PIXELFORMAT), DDS_RGB, 0, 24, 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_L8 = + { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCE, 0, 8, 0xff, 0x00, 0x00, 0x00 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_L16 = + { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCE, 0, 16, 0xffff, 0x0000, 0x0000, 0x0000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8L8 = + { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCEA, 0, 16, 0x00ff, 0x0000, 0x0000, 0xff00 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8L8_ALT = + { sizeof(DDS_PIXELFORMAT), DDS_LUMINANCEA, 0, 8, 0x00ff, 0x0000, 0x0000, 0xff00 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_A8 = + { sizeof(DDS_PIXELFORMAT), DDS_ALPHA, 0, 8, 0x00, 0x00, 0x00, 0xff }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_V8U8 = + { sizeof(DDS_PIXELFORMAT), DDS_BUMPDUDV, 0, 16, 0x00ff, 0xff00, 0x0000, 0x0000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_Q8W8V8U8 = + { sizeof(DDS_PIXELFORMAT), DDS_BUMPDUDV, 0, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 }; + +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_V16U16 = + { sizeof(DDS_PIXELFORMAT), DDS_BUMPDUDV, 0, 32, 0x0000ffff, 0xffff0000, 0x00000000, 0x00000000 }; + +// D3DFMT_A2R10G10B10/D3DFMT_A2B10G10R10 should be written using DX10 extension to avoid D3DX 10:10:10:2 reversal issue + +// This indicates the DDS_HEADER_DXT10 extension is present (the format is in dxgiFormat) +extern __declspec(selectany) const DDS_PIXELFORMAT DDSPF_DX10 = + { sizeof(DDS_PIXELFORMAT), DDS_FOURCC, MAKEFOURCC('D','X','1','0'), 0, 0, 0, 0, 0 }; + +#define DDS_HEADER_FLAGS_TEXTURE 0x00001007 // DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT +#define DDS_HEADER_FLAGS_MIPMAP 0x00020000 // DDSD_MIPMAPCOUNT +#define DDS_HEADER_FLAGS_VOLUME 0x00800000 // DDSD_DEPTH +#define DDS_HEADER_FLAGS_PITCH 0x00000008 // DDSD_PITCH +#define DDS_HEADER_FLAGS_LINEARSIZE 0x00080000 // DDSD_LINEARSIZE + +#define DDS_HEIGHT 0x00000002 // DDSD_HEIGHT +#define DDS_WIDTH 0x00000004 // DDSD_WIDTH + +#define DDS_SURFACE_FLAGS_TEXTURE 0x00001000 // DDSCAPS_TEXTURE +#define DDS_SURFACE_FLAGS_MIPMAP 0x00400008 // DDSCAPS_COMPLEX | DDSCAPS_MIPMAP +#define DDS_SURFACE_FLAGS_CUBEMAP 0x00000008 // DDSCAPS_COMPLEX + +#define DDS_CUBEMAP_POSITIVEX 0x00000600 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX +#define DDS_CUBEMAP_NEGATIVEX 0x00000a00 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX +#define DDS_CUBEMAP_POSITIVEY 0x00001200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY +#define DDS_CUBEMAP_NEGATIVEY 0x00002200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY +#define DDS_CUBEMAP_POSITIVEZ 0x00004200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ +#define DDS_CUBEMAP_NEGATIVEZ 0x00008200 // DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ + +#define DDS_CUBEMAP_ALLFACES ( DDS_CUBEMAP_POSITIVEX | DDS_CUBEMAP_NEGATIVEX |\ + DDS_CUBEMAP_POSITIVEY | DDS_CUBEMAP_NEGATIVEY |\ + DDS_CUBEMAP_POSITIVEZ | DDS_CUBEMAP_NEGATIVEZ ) + +#define DDS_CUBEMAP 0x00000200 // DDSCAPS2_CUBEMAP + +#define DDS_FLAGS_VOLUME 0x00200000 // DDSCAPS2_VOLUME + +// Subset here matches D3D10_RESOURCE_DIMENSION and D3D11_RESOURCE_DIMENSION +enum DDS_RESOURCE_DIMENSION +{ + DDS_DIMENSION_TEXTURE1D = 2, + DDS_DIMENSION_TEXTURE2D = 3, + DDS_DIMENSION_TEXTURE3D = 4, +}; + +// Subset here matches D3D10_RESOURCE_MISC_FLAG and D3D11_RESOURCE_MISC_FLAG +enum DDS_RESOURCE_MISC_FLAG +{ + DDS_RESOURCE_MISC_TEXTURECUBE = 0x4L, +}; + +enum DDS_MISC_FLAGS2 +{ + DDS_MISC_FLAGS2_ALPHA_MODE_MASK = 0x7L, +}; + +enum DDS_ALPHA_MODE +{ + DDS_ALPHA_MODE_UNKNOWN = 0, + DDS_ALPHA_MODE_STRAIGHT = 1, + DDS_ALPHA_MODE_PREMULTIPLIED = 2, + DDS_ALPHA_MODE_OPAQUE = 3, + DDS_ALPHA_MODE_CUSTOM = 4, +}; + +struct DDS_HEADER +{ + uint32_t size; + uint32_t flags; + uint32_t height; + uint32_t width; + uint32_t pitchOrLinearSize; + uint32_t depth; // only if DDS_HEADER_FLAGS_VOLUME is set in flags + uint32_t mipMapCount; + uint32_t reserved1[11]; + DDS_PIXELFORMAT ddspf; + uint32_t caps; + uint32_t caps2; + uint32_t caps3; + uint32_t caps4; + uint32_t reserved2; +}; + +struct DDS_HEADER_DXT10 +{ + DXGI_FORMAT dxgiFormat; + uint32_t resourceDimension; + uint32_t miscFlag; // see D3D11_RESOURCE_MISC_FLAG + uint32_t arraySize; + uint32_t miscFlags2; // see DDS_MISC_FLAGS2 +}; + +#pragma pack(pop) + +static_assert( sizeof(DDS_HEADER) == 124, "DDS Header size mismatch" ); +static_assert( sizeof(DDS_HEADER_DXT10) == 20, "DDS DX10 Extended Header size mismatch"); + +} // namespace diff --git a/libbsarch.cpp b/libbsarch.cpp new file mode 100644 index 0000000..5f16d1c --- /dev/null +++ b/libbsarch.cpp @@ -0,0 +1,149 @@ +#ifndef BSARCH_DLL_EXPORT +#define BSARCH_DLL_EXPORT 1 +#endif + +#include "libbsarch.h" + +BSARCH_DLL_API(bsa_entry_list_t) bsa_entry_list_create() { + return NULL; +} + +BSARCH_DLL_API(void) bsa_entry_list_free(bsa_entry_list_t *entry_list) { + return; +} + +BSARCH_DLL_API(uint32_t) bsa_entry_list_count(bsa_entry_list_t *entry_list) { + return 0; +} + +BSARCH_DLL_API(void) bsa_entry_list_add(bsa_entry_list_t *entry_list, char *entry_string) { + return; +} + +BSARCH_DLL_API(uint32_t) bsa_entry_list_get(bsa_entry_list_t *entry_list, uint32_t index, uint32_t string_buffer_size, char *string_buffer) { + return 0; +} + +BSARCH_DLL_API(bsa_archive_t) bsa_create() { + return NULL; +} + +BSARCH_DLL_API(void) bsa_free(bsa_archive_t archive) { + return; +} + +BSARCH_DLL_API(void) bsa_load_from_file(bsa_archive_t archive, char *filename) { + return; +} + +BSARCH_DLL_API(void) bsa_create_archive(bsa_archive_t archive, char *filename, bsa_archive_type_t archive_type, bsa_entry_list_t entry_list) { + return; +} + +BSARCH_DLL_API(void) bsa_save(bsa_archive_t archive) { + return; +} + +BSARCH_DLL_API(void) bsa_add_file_from_disk(bsa_archive_t archive, char *root_dir, char *filename) { + return; +} + +BSARCH_DLL_API(void) bsa_add_file_from_memory(bsa_archive_t archive, char *filename, uint32_t size, bsa_file_data_t data) { + return; +} + +BSARCH_DLL_API(bsa_file_record_t) bsa_find_file_record(bsa_archive_t archive, char *filename) { + return NULL; +} + +BSARCH_DLL_API(bsa_file_data_result_t) bsa_extract_file_data_by_record(bsa_archive_t archive, bsa_file_record_t file_record) { + return { 0 }; +} + +BSARCH_DLL_API(bsa_file_data_result_t) bsa_extract_file_data_by_filename(bsa_archive_t archive, char *filename) { + return { 0 }; +} + +BSARCH_DLL_API(void) bsa_file_data_free(bsa_archive_t archive, bsa_file_data_result_t file_data_result) { + return; +} + +BSARCH_DLL_API(void) bsa_extract_file(bsa_archive_t archive, char *filename, char *save_as) { + return; +} + +BSARCH_DLL_API(void) bsa_iterate_files(bsa_archive_t archive, bsa_file_iteration_proc_t file_iteration_proc, void *context) { + return; +} + +BSARCH_DLL_API(bool) bsa_file_exists(bsa_archive_t archive, char *filename) { + return false; +} + +BSARCH_DLL_API(void) bsa_get_resource_list(bsa_archive_t archive, bsa_entry_list_t entry_result_list, char *folder) { + return; +} + +BSARCH_DLL_API(void) bsa_resolve_hash(bsa_archive_t archive, uint64_t hash, bsa_entry_list_t entry_result_list) { + return; +} + +BSARCH_DLL_API(void) bsa_close(bsa_archive_t archive) { + return; +} + +BSARCH_DLL_API(uint32_t) bsa_filename_get(bsa_archive_t *archive, uint32_t string_buffer_size, char *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, char *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) { + return; +} diff --git a/libbsarch.def b/libbsarch.def new file mode 100644 index 0000000..a232da4 --- /dev/null +++ b/libbsarch.def @@ -0,0 +1,39 @@ +LIBRARY LIBBSARCH_DLL +;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_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_resolve_hash + 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/libbsarch.dpr b/libbsarch.dpr new file mode 100644 index 0000000..e9e4ef6 --- /dev/null +++ b/libbsarch.dpr @@ -0,0 +1,250 @@ +library libbsarch; + +uses + Math, + Classes, + Types, + SysUtils, + wbStreams in 'wbStreams.pas', + wbBSArchive in './wbBSArchive.pas'; + +{$IFDEF FPC} +{$mode delphi}{$H+} +{$ENDIF} + +{Shared} + +function string_to_cstring(aString: String; const aStringBufferSize: Cardinal; + const aStringBuffer: PChar): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := Min(aStringBufferSize, Length(AString)); + if (aStringBuffer <> nil) and (Result > 0) then + Move(AString[1], aStringBuffer^, Result * SizeOf(Char)); +end; + +{BSA File List} + +function bsa_entry_list_create:Pointer; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSEntryList.Create; +end; + +procedure bsa_entry_list_free(obj: Pointer); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSEntryList(obj).Clear(); + TwbBSEntryList(obj).free; +end; + +function bsa_entry_list_count(obj: Pointer): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSEntryList(obj).Count; +end; + +procedure bsa_entry_list_add(obj: Pointer; const aEntryString: PChar); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSEntryList(obj).Add(String(aEntryString)); +end; + +function bsa_entry_list_get(obj: Pointer; const aIndex: Cardinal; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := string_to_cstring(TwbBSEntryList(obj).Strings[aIndex], aStringBufferSize, aStringBuffer); +end; + +{BSArchive} + +function bsa_create:Pointer; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive.Create; +end; + +procedure bsa_free(obj: Pointer); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).free; +end; + +procedure bsa_load_from_file(obj: Pointer; const aFileName: PChar); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).LoadFromFile(String(aFileName)); +end; + +procedure bsa_create_archive(obj: Pointer; const aFileName: PChar; aType: TBSArchiveType; aFileList: TwbBSEntryList); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).CreateArchiveCompat(String(aFileName), aType, aFileList); +end; + +procedure bsa_save(obj: Pointer); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).Save; +end; + +procedure bsa_add_file_from_disk(obj: Pointer; const aRootDir, aFileName: PChar); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).AddFile(String(aRootDir), String(aFileName)); +end; + +procedure bsa_add_file_from_memory(obj: Pointer; const aFileName: PChar; const aSize: Cardinal; const aData: PByte); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).AddFileCompat(String(aFileName), aSize, aData); +end; + +function bsa_find_file_record(obj: Pointer; const aFileName: PChar): Pointer; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).FindFileRecord(String(aFileName)); +end; + +function bsa_extract_file_data_by_record(obj: Pointer; aFileRecord: Pointer): TwbBSFileDataResult; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).ExtractFileDataCompat(aFileRecord); +end; + +function bsa_extract_file_data_by_filename(obj: Pointer; const aFileName: PChar): TwbBSFileDataResult; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).ExtractFileDataCompat(String(aFileName)); +end; + +procedure bsa_file_data_free(obj: Pointer; fileDataResult: TwbBSFileDataResult); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).ReleaseFileDataCompat(fileDataResult); +end; + +procedure bsa_extract_file(obj: Pointer; const aFileName, aSaveAs: PChar); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).ExtractFile(String(aFileName), String(aSaveAs)); +end; + +procedure bsa_iterate_files(obj: Pointer; aProc: TBSFileIterationProcCompat; aContext: Pointer); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).IterateFilesCompat(aProc, aContext); +end; + +function bsa_file_exists(obj: Pointer; const aFileName: string): Boolean; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).FileExists(aFileName); +end; + +procedure bsa_get_resource_list(obj: Pointer; const aEntryResultList: TwbBSEntryList; aFolder: PChar); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).ResourceListCompat(aEntryResultList, String(aFolder)); +end; + +procedure bsa_resolve_hash(obj: Pointer; const aHash: UInt64; const aEntryResultList: TwbBSEntryList); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).ResolveHashCompat(aHash, aEntryResultList); +end; + +procedure bsa_close(obj: Pointer); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).Close; +end; + +function bsa_filename_get(obj: Pointer; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := string_to_cstring(TwbBSArchive(obj).FileName, aStringBufferSize, aStringBuffer); +end; + +function bsa_archive_type_get(obj: Pointer): TBSArchiveType; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).ArchiveType; +end; + +function bsa_version_get(obj: Pointer): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).Version; +end; + +function bsa_format_name_get(obj: Pointer; const aStringBufferSize: Cardinal; const aStringBuffer: PChar): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := string_to_cstring(TwbBSArchive(obj).FormatName, aStringBufferSize, aStringBuffer); +end; + +function bsa_file_count_get(obj: Pointer): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).FileCount; +end; + +function bsa_archive_flags_get(obj: Pointer): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).ArchiveFlags; +end; + +procedure bsa_archive_flags_set(obj: Pointer; flags: Cardinal); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).ArchiveFlags := flags; +end; + +function bsa_file_flags_get(obj: Pointer): Cardinal; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).FileFlags; +end; + +procedure bsa_file_flags_set(obj: Pointer; flags: Cardinal); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).FileFlags := flags; +end; + +function bsa_compress_get(obj: Pointer): Boolean; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).Compress; +end; + +procedure bsa_compress_set(obj: Pointer; compress: Boolean); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).Compress := compress; +end; + +function bsa_share_data_get(obj: Pointer): Boolean; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + Result := TwbBSArchive(obj).ShareData; +end; + +procedure bsa_share_data_set(obj: Pointer; shareData: Boolean); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).ShareData := shareData; +end; + +procedure bsa_file_dds_info_callback_set(obj: Pointer; aProc: TBSFileDDSInfoProcCompat); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + TwbBSArchive(obj).DDSInfoProc := aProc; +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_resolve_hash, + 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, + bsa_save, + bsa_create_archive, + bsa_load_from_file, + bsa_create, + bsa_free + ; +end. diff --git a/libbsarch.dproj b/libbsarch.dproj new file mode 100644 index 0000000..4df1cf1 --- /dev/null +++ b/libbsarch.dproj @@ -0,0 +1,125 @@ + + + {381E4D59-4D34-46B3-B039-1B3C3ABEC58F} + libbsarch.dpr + True + Debug + 3 + Library + None + 18.6 + Win64 + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + false + false + false + false + false + 00400000 + true + libbsarch + 1033 + CompanyName=;FileDescription=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=;ProductVersion=1.0.0.0;Comments=;CFBundleName= + System;Xml;Data;Datasnap;Web;Soap;Winapi;$(DCC_Namespace) + $(XEditPath);$(XEditPath)\lz4;$(XEditPath)\zlib;$(XEditPath)\Tools\BSArchive\TForge;$(DCC_UnitSearchPath) + + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName) + 1033 + + + System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + + + RELEASE;$(DCC_Define) + 0 + false + 0 + + + DEBUG;$(DCC_Define) + false + true + + + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + (None) + + + + MainSource + + + + + Cfg_2 + Base + + + Base + + + Cfg_1 + Base + + + + Delphi.Personality.12 + + + + + libbsarch.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + True + True + + + 12 + + + + diff --git a/libbsarch.h b/libbsarch.h new file mode 100644 index 0000000..106ae10 --- /dev/null +++ b/libbsarch.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +#include + +#include "DDS.h" + +#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 + +typedef void* bsa_archive_t; +typedef void* bsa_file_data_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; + +typedef struct bsa_dds_header_s { + uint32_t magic; // DDS_MAGIC + struct DirectX::DDS_HEADER header; +} bsa_dds_header_t; + +typedef struct bsa_file_data_result_s { + uint32_t size; + bsa_file_data_t data; +} bsa_file_data_result_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 +} bsa_archive_type_t; + +typedef void (*bsa_file_dds_info_proc_t)(bsa_archive_t bsa_archive, char **filename, bsa_dds_info_t dds_info); +typedef bool (*bsa_file_iteration_proc_t)(bsa_archive_t bsa_archive, char **filename, 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(void) 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(void) bsa_entry_list_add(bsa_entry_list_t *entry_list, char *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, char *string_buffer); + +BSARCH_DLL_API(bsa_archive_t) bsa_create(); +BSARCH_DLL_API(void) bsa_free(bsa_archive_t archive); +BSARCH_DLL_API(void) bsa_load_from_file(bsa_archive_t archive, char *filename); +BSARCH_DLL_API(void) bsa_create_archive(bsa_archive_t archive, char *filename, bsa_archive_type_t archive_type, bsa_entry_list_t entry_list); +BSARCH_DLL_API(void) bsa_save(bsa_archive_t archive); +BSARCH_DLL_API(void) bsa_add_file_from_disk(bsa_archive_t archive, char *root_dir, char *filename); +BSARCH_DLL_API(void) bsa_add_file_from_memory(bsa_archive_t archive, char *filename, uint32_t size, bsa_file_data_t data); +BSARCH_DLL_API(bsa_file_record_t) bsa_find_file_record(bsa_archive_t archive, char *filename); +BSARCH_DLL_API(bsa_file_data_result_t) bsa_extract_file_data_by_record(bsa_archive_t archive, bsa_file_record_t file_record); +BSARCH_DLL_API(bsa_file_data_result_t) bsa_extract_file_data_by_filename(bsa_archive_t archive, char *filename); +BSARCH_DLL_API(void) bsa_file_data_free(bsa_archive_t archive, bsa_file_data_result_t file_data_result); +BSARCH_DLL_API(void) bsa_extract_file(bsa_archive_t archive, char *filename, char *save_as); +BSARCH_DLL_API(void) 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, char *filename); +BSARCH_DLL_API(void) bsa_get_resource_list(bsa_archive_t archive, bsa_entry_list_t entry_result_list, char *folder); +BSARCH_DLL_API(void) bsa_resolve_hash(bsa_archive_t archive, uint64_t hash, bsa_entry_list_t entry_result_list); +BSARCH_DLL_API(void) bsa_close(bsa_archive_t archive); + +BSARCH_DLL_API(uint32_t) bsa_filename_get(bsa_archive_t *archive, uint32_t string_buffer_size, char *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, char *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); diff --git a/libbsarch.sln b/libbsarch.sln new file mode 100644 index 0000000..59cc737 --- /dev/null +++ b/libbsarch.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.572 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libbsarch", "libbsarch.vcxproj", "{537F42AD-5B15-4671-B99E-2D1818999A98}" +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 + {537F42AD-5B15-4671-B99E-2D1818999A98}.Debug|x64.ActiveCfg = Debug|x64 + {537F42AD-5B15-4671-B99E-2D1818999A98}.Debug|x64.Build.0 = Debug|x64 + {537F42AD-5B15-4671-B99E-2D1818999A98}.Debug|x86.ActiveCfg = Debug|Win32 + {537F42AD-5B15-4671-B99E-2D1818999A98}.Debug|x86.Build.0 = Debug|Win32 + {537F42AD-5B15-4671-B99E-2D1818999A98}.Release|x64.ActiveCfg = Release|x64 + {537F42AD-5B15-4671-B99E-2D1818999A98}.Release|x64.Build.0 = Release|x64 + {537F42AD-5B15-4671-B99E-2D1818999A98}.Release|x86.ActiveCfg = Release|Win32 + {537F42AD-5B15-4671-B99E-2D1818999A98}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {274A40A7-F42F-40A4-A8BD-2DA20258DB0A} + EndGlobalSection +EndGlobal diff --git a/libbsarch.vcxproj b/libbsarch.vcxproj new file mode 100644 index 0000000..b3069a6 --- /dev/null +++ b/libbsarch.vcxproj @@ -0,0 +1,128 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {537F42AD-5B15-4671-B99E-2D1818999A98} + Win32Proj + 10.0.17763.0 + + + + DynamicLibrary + true + v141 + + + DynamicLibrary + false + v141 + + + DynamicLibrary + true + v141 + + + DynamicLibrary + false + v141 + + + + + + + + + + + + + + + + + + + + + true + $(DirectXTexPath)\DirectXTex;$(IncludePath) + $(ProjectName)_dummy + + + true + $(DirectXTexPath)\DirectXTex;$(IncludePath) + $(ProjectName)_dummy + + + $(DirectXTexPath)\DirectXTex;$(IncludePath) + $(ProjectName)_dummy + + + $(DirectXTexPath)\DirectXTex;$(IncludePath) + $(ProjectName)_dummy + + + + WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBBSARCH_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + + + MachineX86 + true + Windows + + + + + WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBBSARCH_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + + + MachineX86 + true + Windows + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libbsarch.vcxproj.filters b/libbsarch.vcxproj.filters new file mode 100644 index 0000000..502ec7f --- /dev/null +++ b/libbsarch.vcxproj.filters @@ -0,0 +1,41 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Header Files + + + Resource Files + + + + + Header Files + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/wbBSArchive.pas b/wbBSArchive.pas new file mode 100644 index 0000000..8984f89 --- /dev/null +++ b/wbBSArchive.pas @@ -0,0 +1,2383 @@ +{******************************************************************************* + + The contents of this file are subject to the Mozilla Public License + Version 1.1 (the "License"); you may not use this file except in + compliance with the License. You may obtain a copy of the License at + http://www.mozilla.org/MPL/ + + Software distributed under the License is distributed on an "AS IS" + basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + License for the specific language governing rights and limitations + under the License. + +*******************************************************************************} + +unit wbBSArchive; + +interface + +uses + SysUtils, + Classes, + Windows, + Threading, + wbStreams, + tfTypes, + tfMD5; + +type + 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; + + TwbBSArchive = class; + + TBSArchiveType = (baNone, baTES3, baTES4, baFO3, baSSE, baFO4, baFO4dds); + TBSArchiveState = (stReading, stWriting); + TBSArchiveStates = set of TBSArchiveState; + TBSFileIterationProcCompat = function(aArchive: Pointer; const aFileName: PChar; + aFileRecord: Pointer; aFolderRecord: Pointer; aContext: Pointer): Boolean; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; + + TDDSInfo = record Width, Height, MipMaps: Integer; end; + TBSFileDDSInfoProcCompat = procedure(aArchive: Pointer; const aFileName: PChar; + var aInfo: TDDSInfo); {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; + + 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; + function Compressed(bsa: TwbBSArchive): Boolean; + 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; + + 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; + function DXGIFormatName: string; + end; + PwbBSFileFO4 = ^TwbBSFileFO4; + + TPackedDataHash = TMD5Digest; + TPackedDataInfo = record + Size: Cardinal; + Hash: TPackedDataHash; + FileRecord: Pointer; + end; + PPackedDataInfo = ^TPackedDataInfo; + + TwbBSFileDataResult = record + Size: Cardinal; + Data: PByte; + end; + + TwbBSEntryList = class(TStringList); + + TwbBSArchive = class + private + fStream: TwbBaseCachedFileStream; + fStates: TBSArchiveStates; + fType: TBSArchiveType; + fFileName: string; + fMagic: TMagic4; + fVersion: Cardinal; + fCompress: Boolean; + fShareData: Boolean; + fDDSInfoProc: TBSFileDDSInfoProcCompat; + + fHeaderTES3: TwbBSHeaderTES3; + fFilesTES3: array of TwbBSFileTES3; + + fHeaderTES4: TwbBSHeaderTES4; + fFoldersTES4: array of TwbBSFolderTES4; + + fHeaderFO4: TwbBSHeaderFO4; + fFilesFO4: array of TwbBSFileFO4; + fMaxChunkCount: Integer; + fSingleMipChunkX: Integer; + fSingleMipChunkY: Integer; + + fDataOffset: Int64; + + fPackedData: array of TPackedDataInfo; + fPackedDataCount: Integer; + + function GetArchiveFormatName: string; + function GetFileCount: Cardinal; + procedure SetArchiveFlags(aFlags: Cardinal); + 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); + + public + Sync: IReadWriteSync; + constructor Create; + destructor Destroy; override; + procedure LoadFromFile(const aFileName: string); + procedure CreateArchiveCompat(const aFileName: string; aType: TBSArchiveType; + aFilesList: TwbBSEntryList = nil); + procedure Save; + procedure AddFile(const aRootDir, aFileName: string); overload; + procedure AddFileCompat(const aFileName: string; const aSize: Cardinal; const aData: PByte); + function FindFileRecord(const aFileName: string): Pointer; + function ExtractFileDataCompat(aFileRecord: Pointer): TwbBSFileDataResult; overload; + function ExtractFileDataCompat(const aFileName: string): TwbBSFileDataResult; overload; + procedure ReleaseFileDataCompat(fileDataResult: TwbBSFileDataResult); + procedure ExtractFile(const aFileName, aSaveAs: string); + procedure IterateFilesCompat(aProc: TBSFileIterationProcCompat; aContext: Pointer = nil); + function FileExists(const aFileName: string): Boolean; + procedure ResourceListCompat(const aEntryResultList: TwbBSEntryList; aFolder: string = ''); + procedure ResolveHashCompat(const aHash: UInt64; const aEntryResultList: TwbBSEntryList); + procedure Close; + + 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 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 DDSInfoProc: TBSFileDDSInfoProcCompat read fDDSInfoProc write fDDSInfoProc; + end; + +function SplitDirName(const aFileName: string; var Dir, Name: string): Integer; +function SplitNameExt(const aFileName: string; var Name, Ext: string): Integer; +function CreateHashTES3(const aFileName: string): UInt64; +function CreateHashTES4(const aFileName: string): UInt64; overload; +function CreateHashFO4(const aFileName: string): Cardinal; + +implementation + +uses + TypInfo, + zlibEx, + lz4io; + +const + TBSArchiveFormatName: array[TBSArchiveType] of string = ( + 'None', + 'Morrowind', + 'Oblivion', + 'Fallout 3, New Vegas, Skyrim LE', + 'Skyrim Special Edition', + 'Fallout 4 General', + 'Fallout 4 DDS' + ); + + 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_FO4 = $01; // FO4 + + // 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 + + 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; + + 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, '/'); + Dir := Copy(aFileName, 1, Pred(Result)); + Name := Copy(aFileName, Succ(Result), Length(aFileName) - Result); +end; + +function SplitNameExt(const aFileName: string; var Name, Ext: string): Integer; +begin + Result := LastCharPos(aFileName, '.'); + if Result <> 0 then begin + Name := Copy(aFileName, 1, Pred(Result)); + Ext := Copy(aFileName, Result, Length(aFileName) - Result + 2); + 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.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; + + +{ 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 := TBSArchiveFormatName[fType]; +end; + +function TwbBSArchive.GetFileCount: Cardinal; +begin + case fType of + baTES3: + Result := fHeaderTES3.FileCount; + baTES4, baFO3, baSSE: + Result := fHeaderTES4.FileCount; + baFO4, baFO4dds: + // because we use fHeaderFO4.FileCount as the current index in new archives + Result := Length(fFilesFO4); + 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; + +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 + if SplitDirName(aFileName, fdir, fname) = 0 then + Exit(False); + + 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 + if SplitDirName(aFileName, fdir, fname) = 0 then + Exit(False); + + SplitNameExt(fname, name, ext); + hdir := CreateHashFO4(fdir); + hfile := CreateHashFO4(name); + if Copy(ext, 1, 1) = '.' then + Delete(ext, 1, 1); + 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: + 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: 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: 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; + case fVersion of + HEADER_VERSION_TES4: fType := baTES4; + HEADER_VERSION_FO3 : fType := baFO3; + HEADER_VERSION_SSE : fType := baSSE; + HEADER_VERSION_FO4 : fType := baFO4; + 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 + baFO4: begin + // read header + fStream.ReadBuffer(fHeaderFO4, SizeOf(fHeaderFO4)); + + // 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 + fType := baFO4dds; + 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 + // 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; 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.CreateArchiveCompat(const aFileName: string; aType: TBSArchiveType; + aFilesList: TwbBSEntryList = nil); +var + HashPairs: array of THashPair; + h: PHashPair; + hdir: UInt64; + s, fdir, fname, fext: 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); + 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); + 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); + end; + baFO4: begin + fMagic := MAGIC_BTDX; + fHeaderFO4.Magic := MAGIC_GNRL; + fVersion := HEADER_VERSION_FO4; + end; + baFO4dds: begin + fMagic := MAGIC_BTDX; + fHeaderFO4.Magic := MAGIC_DX10; + fVersion := HEADER_VERSION_FO4; + 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); + aFilesList.Objects[i] := Pointer(h); + + // determine file flags + fext := LowerCase(fext); + with fHeaderTES4 do + if fext = '.nif' 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 = '.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; + 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); + 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 + if fType = baSSE then + fHeaderTES4.FileFlags := fHeaderTES4.FileFlags and not FILE_MISC; + + // embedded names in texture only archives + if fHeaderTES4.FileFlags = FILE_DDS 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] then begin + if not Assigned(aFilesList) or (aFilesList.Count = 0) then + raise Exception.Create('Archive requires predefined files list'); + + SetLength(fFilesFO4, aFilesList.Count); + fDataOffset := SizeOf(fMagic) + SizeOf(fVersion) + SizeOf(fHeaderFO4); + + // file records have fixed length in general archive + if fType = baFO4 then + fDataOffset := fDataOffset + 36 * Length(fFilesFO4) + + // variable file record length depending on DDS chunks number + else if fType = baFO4dds 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, PChar(aFilesList[i]), ddsinfo); + 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: 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)); + // 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: 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)); + // 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; + +procedure TwbBSArchive.AddFile(const aRootDir, aFileName: string); +var + fname: string; + i: integer; + Buffer: PByte; +begin + if not (stWriting in fStates) then + raise Exception.Create('Archive is not in writing mode'); + + i := Length(aRootDir); + if (i > 1) and (aRootDir[Length(aRootDir)] <> '\') then + Inc(i); + + fname := Copy(aFileName, i + 1, Length(aFileName)); + + with TFileStream.Create(aFileName, fmOpenRead + fmShareDenyNone) do try + GetMem(Buffer, Size); + Read(Buffer[0], Size); + AddFileCompat(fname, Size, Buffer); + finally + FreeAndNil(Buffer); + Free; + end; +end; + +// Modified: Version for use in non-Borland C/C++ +procedure TwbBSArchive.AddFileCompat(const aFileName: string; const aSize: Cardinal; const aData: PByte); +var + i, j, Off, MipSize, BitsPerPixel: integer; + fdir, fname, name, ext: string; + msData: TPreallocatedMemoryStream; + MemoryStream: TBytesStream; + wasCompressed: Boolean; + DataHash: TPackedDataHash; + DDSHeader: PDDSHeader; + DDSHeaderDX10: PDDSHeaderDX10; + DDSInfo: TDDSInfo; +begin + if not (stWriting in fStates) then + raise Exception.Create('Archive is not in writing mode'); + + // FO4 dds mipmaps have their own partial hash calculation down below + if fShareData and (fType <> baFO4dds) then + DataHash := CalcDataHash(@aData[0], aSize); + + MemoryStream := nil; + if Assigned(Sync) then + Sync.BeginWrite; + try + case fType of + baTES3: begin + if not FindFileRecordTES3(aFileName, i) then + raise Exception.Create('File not found in files table: ' + aFileName); + + if FindPackedData(aSize, DataHash, @fFilesTES3[i]) then + Exit; + + fFilesTES3[i].Size := aSize; + fFilesTES3[i].Offset := fStream.Position; + fStream.Write(aData[0], aSize); + AddPackedData(aSize, DataHash, @fFilesTES3[i]); + end; + + baTES4, baFO3, baSSE: begin + if not FindFileRecordTES4(aFileName, i, j) then + raise Exception.Create('File not found in files table: ' + aFileName); + + if FindPackedData(aSize, DataHash, @fFoldersTES4[i].Files[j]) then + Exit; + + if Assigned(Sync) and fCompress and (aSize >= 32) then begin + Sync.EndWrite; + try + MemoryStream := TBytesStream.Create; + msData := TPreallocatedMemoryStream.Create(@aData[0], aSize); + try + // store compressed data + if fType = baSSE then + lz4CompressStream(msData, MemoryStream) + else + ZCompressStream(msData, MemoryStream); + finally + msData.Free; + end; + finally + Sync.BeginWrite; + end; + if FindPackedData(aSize, DataHash, @fFoldersTES4[i].Files[j]) then + Exit; + end; + + fFoldersTES4[i].Files[j].Offset := fStream.Position; + + if (fType in [baFO3, baSSE]) and (fHeaderTES4.Flags and ARCHIVE_EMBEDNAME <> 0) then + fStream.WriteStringLen(fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name, False); + + if Assigned(MemoryStream) then begin + fStream.WriteCardinal(aSize); + fStream.Write(MemoryStream.Bytes[0], MemoryStream.Size); + wasCompressed := True; + end else if fCompress and (aSize >= 32) then begin + msData := TPreallocatedMemoryStream.Create(@aData[0], aSize); + try + // store uncompressed length + fStream.WriteCardinal(aSize); + // store compressed data + if fType = baSSE then + lz4CompressStream(msData, fStream) + else + ZCompressStream(msData, fStream); + finally + msData.Free; + end; + wasCompressed := True; + end + else begin + fStream.Write(aData[0], aSize); + wasCompressed := False; + end; + + fFoldersTES4[i].Files[j].Size := fStream.Position - fFoldersTES4[i].Files[j].Offset; + + // compress flag in Size inverts compression status from the header + // set it if archive is flagged as compressed but file was not compressed + if fCompress and not WasCompressed then + fFoldersTES4[i].Files[j].Size := fFoldersTES4[i].Files[j].Size or FILE_SIZE_COMPRESS; + + AddPackedData(aSize, DataHash, @fFoldersTES4[i].Files[j]); + end; + + baFO4: begin + if SplitDirName(aFileName, fdir, fname) = 0 then + raise Exception.Create('File is missing the folder part: ' + aFileName); + + SplitNameExt(fname, name, ext); + if Length(ext) > 1 then Delete(ext, 1, 1); // no dot in extension + + i := fHeaderFO4.FileCount; + Inc(fHeaderFO4.FileCount); + // archive2.exe uses / + fFilesFO4[i].Name := StringReplace(aFileName, '\', '/', [rfReplaceAll]); + fFilesFO4[i].DirHash := CreateHashFO4(fdir); + fFilesFO4[i].NameHash := CreateHashFO4(name); + fFilesFO4[i].Ext := Int2Magic(Str2MagicInt(ext)); + fFilesFO4[i].Unknown := iFileFO4Unknown; + fFilesFO4[i].Offset := fStream.Position; + fFilesFO4[i].Size := aSize; + + if FindPackedData(aSize, DataHash, @fFilesFO4[i]) then + Exit; + + if fCompress and (aSize >= 32) then begin + msData := TPreallocatedMemoryStream.Create(@aData[0], aSize); + try + ZCompressStream(msData, fStream); + fFilesFO4[i].PackedSize := fStream.Position - fFilesFO4[i].Offset; + // rare case: if packed size matches original, then rewrite with original data + if fFilesFO4[i].PackedSize = fFilesFO4[i].Size then begin + fFilesFO4[i].PackedSize := 0; + fStream.Position := fFilesFO4[i].Offset; + fStream.Write(aData[0], aSize); + end; + finally + msData.Free; + end; + end + else + fStream.Write(aData[0], aSize); + + AddPackedData(aSize, DataHash, @fFilesFO4[i]); + end; + + baFO4dds: begin + if SplitDirName(aFileName, fdir, fname) = 0 then + raise Exception.Create('File is missing the folder part: ' + aFileName); + + SplitNameExt(fname, name, ext); + if Length(ext) > 1 then Delete(ext, 1, 1); // no dot in extension + + // filling common part + i := fHeaderFO4.FileCount; + Inc(fHeaderFO4.FileCount); + // archive2.exe uses / + fFilesFO4[i].Name := StringReplace(aFileName, '\', '/', [rfReplaceAll]); + fFilesFO4[i].DirHash := CreateHashFO4(fdir); + fFilesFO4[i].NameHash := CreateHashFO4(name); + fFilesFO4[i].Ext := Int2Magic(Str2MagicInt(ext)); + 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); + if not FindPackedData(MipSize, DataHash, @fFilesFO4[i].TexChunks[j]) then begin + fFilesFO4[i].TexChunks[j].Offset := fStream.Position; + fFilesFO4[i].TexChunks[j].Size := MipSize; + if fCompress then begin + msData := TPreallocatedMemoryStream.Create(@aData[Off], MipSize); + try + ZCompressStream(msData, fStream); + fFilesFO4[i].TexChunks[j].PackedSize := fStream.Position - fFilesFO4[i].TexChunks[j].Offset; + finally + msData.Free; + end; + end + else + fStream.Write(aData[Off], MipSize); + + AddPackedData(MipSize, DataHash, @fFilesFO4[i].TexChunks[j]); + end; + Inc(Off, MipSize); + MipSize := MipSize div 4; + end; + end; + + end; + finally + if Assigned(Sync) then + Sync.EndWrite; + FreeAndNil(MemoryStream); + end; +end; + +// Modified: Version for use in non-Borland C/C++ (Result not automatically freed) +function TwbBSArchive.ExtractFileDataCompat(aFileRecord: Pointer): TwbBSFileDataResult; +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; + + if Assigned(Sync) then + Sync.BeginWrite; + try + case fType of + baTES3: begin + FileTES3 := aFileRecord; + fStream.Position := fDataOffset + FileTES3.Offset; + Result.Size := FileTES3.Size; + GetMem(Result.Data, FileTES3.Size); + fStream.ReadBuffer(Result.Data[0], Result.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; + GetMem(Result.Data, Result.Size); + dec(size, SizeOf(Cardinal)); + if (Result.Size > 0) and (size > 0) then begin + SetLength(Buffer, size); + fStream.ReadBuffer(Buffer[0], Length(Buffer)); + if Assigned(Sync) then + Sync.EndWrite; + try + if fType = baSSE then + lz4DecompressToUserBuf(@Buffer[0], Length(Buffer), @Result.Data[0], Result.Size) + else try + DecompressToUserBuf(@Buffer[0], Length(Buffer), @Result.Data[0], Result.Size); + 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; + finally + if Assigned(Sync) then + Sync.BeginWrite; + end; + end; + end + else begin + Result.Size := size; + GetMem(Result.Data, Result.Size); + if size > 0 then + fStream.ReadBuffer(Result.Data[0], Result.Size); + end; + end; + + baFO4: 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; + GetMem(Result.Data, Result.Size); + if Assigned(Sync) then + Sync.EndWrite; + try + DecompressToUserBuf(@Buffer[0], Length(Buffer), @Result.Data[0], Result.Size); + finally + if Assigned(Sync) then + Sync.BeginWrite; + end; + end + else begin + Result.Size := FileFO4.Size; + GetMem(Result.Data, Result.Size); + fStream.ReadBuffer(Result.Data[0], Result.Size); + end; + end; + + baFO4dds: 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; + GetMem(Result.Data, 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 + 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; + 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)); + if Assigned(Sync) then + Sync.EndWrite; + try + DecompressToUserBuf(@Buffer[0], Length(Buffer), @Result.Data[TexSize], Size); + finally + if Assigned(Sync) then + Sync.BeginWrite; + 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 + if Assigned(Sync) then + Sync.EndWrite; + end; +end; + +// Modified: Version for use in non-Borland C/C++ +function TwbBSArchive.ExtractFileDataCompat(const aFileName: string): TwbBSFileDataResult; +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 := ExtractFileDataCompat(FileRecord); +end; + +// Addded: For use in non-Borland C/C++ +procedure TwbBSArchive.ReleaseFileDataCompat(fileDataResult: TwbBSFileDataResult); +begin + FreeAndNil(fileDataResult.Data); + fileDataResult.Size := 0; +end; + +procedure TwbBSArchive.ExtractFile(const aFileName, aSaveAs: string); +var + fs: TFileStream; + fileData: TwbBSFileDataResult; +begin + if not (stReading in fStates) then + raise Exception.Create('Archive is not loaded'); + + fs := TFileStream.Create(aSaveAs, fmCreate); + try + fileData := ExtractFileDataCompat(aFileName); + fs.Write(fileData.Data[0], fileData.Size); + finally + ReleaseFileDataCompat(fileData); + fs.Free; + end; +end; + +// Modified: Version for use in non-Borland C/C++ +procedure TwbBSArchive.IterateFilesCompat(aProc: TBSFileIterationProcCompat; aContext: Pointer = nil); +var + i, j: Integer; +begin + if not Assigned(aProc) then + Exit; + + if Assigned(Sync) then + case fType of + baTES3: + TParallel.&For(Low(fFilesTES3), High(fFilesTES3), procedure(i: Integer; LoopState: TParallel.TLoopState) begin + if aProc(Self, PChar(fFilesTES3[i].Name), @fFilesTES3[i], nil, aContext) 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, PChar(fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name), @fFoldersTES4[i].Files[j], @fFoldersTES4[i], aContext) then begin + OuterLoopState.Stop; + InnerLoopState.Stop; + end; + end); + end); + baFO4, baFO4dds: + TParallel.&For(Low(fFilesFO4), High(fFilesFO4), procedure(i: Integer; LoopState: TParallel.TLoopState) begin + if aProc(Self, PChar(fFilesFO4[i].Name), @fFilesFO4[i], nil, aContext) then + LoopState.Stop; + end); + end + else + case fType of + baTES3: + for i := Low(fFilesTES3) to High(fFilesTES3) do + if aProc(Self, PChar(fFilesTES3[i].Name), @fFilesTES3[i], nil, aContext) 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, PChar(fFoldersTES4[i].Name + '\' + fFoldersTES4[i].Files[j].Name), @fFoldersTES4[i].Files[j], @fFoldersTES4[i], aContext) then + Break; + baFO4, baFO4dds: + for i := Low(fFilesFO4) to High(fFilesFO4) do + if aProc(Self, PChar(fFilesFO4[i].Name), @fFilesFO4[i], nil, aContext) then + Break; + end; +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 + 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.ResourceListCompat(const aEntryResultList: TwbBSEntryList; aFolder: string = ''); +var + Folder : string; + i, j : Integer; +begin + if not Assigned(aEntryResultList) 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 + aEntryResultList.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 + aEntryResultList.Add(Name + '\' + Files[j].Name); + end; + baFO4, baFO4dds: + for i := Low(fFilesFO4) to High(fFilesFO4) do + with fFilesFO4[i] do + if (Folder = '') or Name.StartsWith(Folder, True) then + aEntryResultList.Add(Name); + end; + +end; + +procedure TwbBSArchive.ResolveHashCompat(const aHash: UInt64; const aEntryResultList: TwbBSEntryList); +var + Len : Integer; + i, j : Integer; +begin + case fType of + baTES3: + for i := Low(fFilesTES3) to High(fFilesTES3) do + with fFilesTES3[i] do + if aHash = Hash then begin + aEntryResultList.Add(Name); + end; + baTES4, baFO3, baSSE: + for i := Low(fFoldersTES4) to High(fFoldersTES4) do + with fFoldersTES4[i] do begin + if aHash = Hash then begin + aEntryResultList.Add(Name); + end; + for j := Low(Files) to High(Files) do + with Files[j] do + if aHash = Hash then begin + aEntryResultList.Add(Name); + end; + end; + { + baFO4, baFO4dds: + for i := Low(fFilesFO4) to High(fFilesFO4) do + with fFilesFO4[i] do begin + if aHash = NameHash then begin + SetLength(Results, Succ(Len)); + Results[Len] := Name; + Inc(Len); + end; + end; + } + end; +end; + + + +end.