mirror of
https://github.com/ModOrganizer2/libbsarch.git
synced 2026-07-27 14:06:31 -07:00
Added CMake support Added clang format configuration Reorganized the file structure, removed VS files
2411 lines
83 KiB
ObjectPascal
2411 lines
83 KiB
ObjectPascal
{*******************************************************************************
|
|
|
|
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 aFilePath: PChar;
|
|
aFileRecord: Pointer; aFolderRecord: Pointer; aContext: Pointer): Boolean; stdcall;
|
|
|
|
TDDSInfo = record Width, Height, MipMaps: Integer; end;
|
|
TBSFileDDSInfoProcCompat = procedure(aArchive: Pointer; const aFilePath: PChar;
|
|
var aInfo: TDDSInfo; aContext: Pointer); stdcall;
|
|
|
|
TwbBSHeaderTES3 = packed record
|
|
HashOffset: Cardinal;
|
|
FileCount: Cardinal;
|
|
end;
|
|
|
|
TwbBSFileTES3 = record
|
|
Hash: UInt64;
|
|
Size: Cardinal;
|
|
Offset: Cardinal;
|
|
Name: string;
|
|
end;
|
|
PwbBSFileTES3 = ^TwbBSFileTES3;
|
|
|
|
TwbBSHeaderTES4 = packed record
|
|
FoldersOffset: Cardinal;
|
|
Flags: Cardinal;
|
|
FolderCount: Cardinal;
|
|
FileCount: Cardinal;
|
|
FolderNamesLength: Cardinal;
|
|
FileNamesLength: Cardinal;
|
|
FileFlags: Cardinal;
|
|
end;
|
|
|
|
TwbBSFileTES4 = record
|
|
Hash: UInt64;
|
|
Size: Cardinal;
|
|
Offset: Int64;
|
|
Name: string;
|
|
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;
|
|
|
|
TwbBSResultBuffer = packed 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;
|
|
fDDSInfoProcContext: Pointer;
|
|
|
|
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 aFilePath: string; var aFileIdx: Integer): Boolean;
|
|
function FindFileRecordTES4(const aFilePath: string; var aFolderIdx, aFileIdx: Integer): Boolean;
|
|
function FindFileRecordFO4(const aFilePath: 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 aFilePath: string);
|
|
procedure CreateArchiveCompat(const aFilePath: string; aType: TBSArchiveType;
|
|
aFilesList: TwbBSEntryList = nil);
|
|
procedure Save;
|
|
procedure AddFileDisk(const aFilePath, aSourcePath: string);
|
|
procedure AddFileDiskRoot(const aRootDir, aSourcePath: string);
|
|
procedure AddFileDataCompat(const aFilePath: string; const aSize: Cardinal; const aData: PByte);
|
|
function FindFileRecord(const aFilePath: string): Pointer;
|
|
function ExtractFileDataCompat(aFileRecord: Pointer): TwbBSResultBuffer; overload;
|
|
function ExtractFileDataCompat(const aFilePath: string): TwbBSResultBuffer; overload;
|
|
procedure ReleaseFileDataCompat(fileDataResult: TwbBSResultBuffer);
|
|
procedure ExtractFile(const aFilePath, aSaveAs: string);
|
|
procedure IterateFilesCompat(aProc: TBSFileIterationProcCompat; aContext: Pointer = nil);
|
|
function FileExists(const aFilePath: 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;
|
|
property DDSInfoProcContext: Pointer read fDDSInfoProcContext write fDDSInfoProcContext;
|
|
end;
|
|
|
|
function SplitDirName(const aFilePath: string; var Dir, Name: string): Integer;
|
|
function SplitNameExt(const aFilePath: string; var Name, Ext: string): Integer;
|
|
function CreateHashTES3(const aFilePath: string): UInt64;
|
|
function CreateHashTES4(const aFilePath: string): UInt64; overload;
|
|
function CreateHashFO4(const aFilePath: 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 aFilePath: string; var Dir, Name: string): Integer;
|
|
begin
|
|
Result := LastCharPos(aFilePath, '\');
|
|
if Result = 0 then
|
|
Result := LastCharPos(aFilePath, '/');
|
|
Dir := Copy(aFilePath, 1, Pred(Result));
|
|
Name := Copy(aFilePath, Succ(Result), Length(aFilePath) - Result);
|
|
end;
|
|
|
|
function SplitNameExt(const aFilePath: string; var Name, Ext: string): Integer;
|
|
begin
|
|
Result := LastCharPos(aFilePath, '.');
|
|
if Result <> 0 then begin
|
|
Name := Copy(aFilePath, 1, Pred(Result));
|
|
Ext := Copy(aFilePath, Result, Length(aFilePath) - Result + 2);
|
|
end
|
|
else begin
|
|
Name := aFilePath;
|
|
Ext := '';
|
|
end;
|
|
end;
|
|
|
|
function CreateHashTES3(const aFilePath: string): UInt64;
|
|
var
|
|
s: AnsiString;
|
|
i, l: integer;
|
|
sum, off, temp, n: Cardinal;
|
|
begin
|
|
s := AnsiString(aFilePath);
|
|
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 aFilePath: string): UInt64; overload;
|
|
var
|
|
fname, fext: string;
|
|
begin
|
|
SplitNameExt(aFilePath, fname, fext);
|
|
Result := CreateHashTES4(fname, fext);
|
|
end;
|
|
|
|
function CreateHashFO4(const aFilePath: string): Cardinal;
|
|
var
|
|
i: Integer;
|
|
s: AnsiString;
|
|
c: AnsiChar;
|
|
begin
|
|
Result := 0;
|
|
s := AnsiString(aFilePath);
|
|
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 aFilePath: string; var aFileIdx: Integer): Boolean;
|
|
var
|
|
h: UInt64;
|
|
i: integer;
|
|
begin
|
|
h := CreateHashTES3(aFilePath);
|
|
|
|
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 aFilePath: string; var aFolderIdx, aFileIdx: Integer): Boolean;
|
|
var
|
|
fdir, fname, name, ext: string;
|
|
h: UInt64;
|
|
i, j: integer;
|
|
begin
|
|
if SplitDirName(aFilePath, 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 aFilePath: string; var aFileIdx: Integer): Boolean;
|
|
var
|
|
fdir, fname, name, ext: string;
|
|
hdir, hfile: Cardinal;
|
|
hext: TMagic4;
|
|
i: integer;
|
|
begin
|
|
if SplitDirName(aFilePath, 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 aFilePath: string): Pointer;
|
|
var
|
|
i, j: integer;
|
|
begin
|
|
Result := nil;
|
|
|
|
case fType of
|
|
baTES3:
|
|
if FindFileRecordTES3(aFilePath, i) then Result := @fFilesTES3[i];
|
|
baTES4, baFO3, baSSE:
|
|
if FindFileRecordTES4(aFilePath, i, j) then Result := @fFoldersTES4[i].Files[j];
|
|
baFO4, baFO4dds:
|
|
if FindFileRecordFO4(aFilePath, 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 aFilePath: string);
|
|
var
|
|
i, j: Integer;
|
|
begin
|
|
if fStates * [stReading, stWriting] <> [] then
|
|
Close;
|
|
|
|
fStream := TwbReadOnlyCachedFileStream.Create(aFilePath, 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 := aFilePath;
|
|
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 aFilePath: 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, Self.fDDSInfoProcContext);
|
|
fDataOffset := fDataOffset + 24 {size of file record} + 24 {size of each texchunk} * GetDDSMipChunkNum(ddsinfo);
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
fStream := TwbWriteCachedFileStream.Create(aFilePath, fmCreate);
|
|
fFileName := aFilePath;
|
|
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.AddFileDisk(const aFilePath, aSourcePath: string);
|
|
var
|
|
fname: string;
|
|
i: integer;
|
|
buffer: PByte;
|
|
stream: TFileStream;
|
|
begin
|
|
if not (stWriting in fStates) then
|
|
raise Exception.Create('Archive is not in writing mode');
|
|
|
|
stream := TFileStream.Create(aSourcePath, fmOpenRead + fmShareDenyNone);
|
|
try
|
|
// Modified: Make sure memory is zeroed when allocated
|
|
buffer := AllocMem(stream.Size);
|
|
try
|
|
stream.Read(buffer^, stream.Size);
|
|
AddFileDataCompat(aFilePath, stream.Size, buffer);
|
|
finally
|
|
if Assigned(buffer) then
|
|
FreeMem(buffer);
|
|
end;
|
|
finally
|
|
stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TwbBSArchive.AddFileDiskRoot(const aRootDir, aSourcePath: string);
|
|
var
|
|
fname: string;
|
|
i: integer;
|
|
begin
|
|
i := Length(aRootDir);
|
|
if (i > 1) and (aRootDir[Length(aRootDir)] <> '\') then
|
|
Inc(i);
|
|
|
|
fname := Copy(aSourcePath, i + 1, Length(aSourcePath));
|
|
|
|
AddFileDisk(fname, aSourcePath);
|
|
end;
|
|
|
|
// Modified: Version for use in non-Borland C/C++
|
|
procedure TwbBSArchive.AddFileDataCompat(const aFilePath: 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(aFilePath, i) then
|
|
raise Exception.Create('File not found in files table: ' + aFilePath);
|
|
|
|
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(aFilePath, i, j) then
|
|
raise Exception.Create('File not found in files table: ' + aFilePath);
|
|
|
|
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(aFilePath, fdir, fname) = 0 then
|
|
raise Exception.Create('File is missing the folder part: ' + aFilePath);
|
|
|
|
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(aFilePath, '\', '/', [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(aFilePath, fdir, fname) = 0 then
|
|
raise Exception.Create('File is missing the folder part: ' + aFilePath);
|
|
|
|
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(aFilePath, '\', '/', [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): TwbBSResultBuffer;
|
|
var
|
|
FileTES3: PwbBSFileTES3;
|
|
FileTES4: PwbBSFileTES4;
|
|
FileFO4: PwbBSFileFO4;
|
|
DDSHeader: PDDSHeader;
|
|
DDSHeaderDX10: PDDSHeaderDX10;
|
|
i, size, TexSize: integer;
|
|
bCompressed: Boolean;
|
|
Buffer: TBytes;
|
|
begin
|
|
if not (stReading in fStates) then
|
|
raise Exception.Create('Archive is not loaded');
|
|
|
|
if aFileRecord = nil then
|
|
Exit;
|
|
|
|
if Assigned(Sync) then
|
|
Sync.BeginWrite;
|
|
try
|
|
case fType of
|
|
baTES3: begin
|
|
FileTES3 := aFileRecord;
|
|
fStream.Position := fDataOffset + FileTES3.Offset;
|
|
Result.size := FileTES3.Size;
|
|
// Modified: Make sure memory is zeroed when allocated
|
|
Result.data := AllocMem(FileTES3.Size);
|
|
end;
|
|
|
|
baTES4, baFO3, baSSE: begin
|
|
FileTES4 := aFileRecord;
|
|
fStream.Position := FileTES4.Offset;
|
|
size := FileTES4.Size;
|
|
|
|
bCompressed := size and FILE_SIZE_COMPRESS <> 0;
|
|
if bCompressed then
|
|
size := size and not FILE_SIZE_COMPRESS;
|
|
if fHeaderTES4.Flags and ARCHIVE_COMPRESS <> 0 then
|
|
bCompressed := not bCompressed;
|
|
|
|
// skip embedded file name + length prefix
|
|
if (fType in [baFO3, baSSE]) and (fHeaderTES4.Flags and ARCHIVE_EMBEDNAME <> 0) then
|
|
size := size - (Length(fStream.ReadStringLen(False)) + 1);
|
|
|
|
if bCompressed then begin
|
|
// reading uncompressed size
|
|
Result.size := fStream.ReadCardinal;
|
|
// Modified: Make sure memory is zeroed when allocated
|
|
Result.data := AllocMem(Result.size);
|
|
dec(size, SizeOf(Cardinal));
|
|
if (Result.size > 0) and (size > 0) then begin
|
|
SetLength(Buffer, size);
|
|
fStream.ReadBuffer(Buffer[0], Length(Buffer));
|
|
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;
|
|
// Modified: Make sure memory is zeroed when allocated
|
|
Result.data := AllocMem(Result.size);
|
|
if size > 0 then
|
|
fStream.ReadBuffer(Result.data[0], Result.size);
|
|
end;
|
|
end;
|
|
|
|
baFO4: begin
|
|
FileFO4 := aFileRecord;
|
|
fStream.Position := FileFO4.Offset;
|
|
if FileFO4.PackedSize <> 0 then begin
|
|
SetLength(Buffer, FileFO4.PackedSize);
|
|
fStream.ReadBuffer(Buffer[0], Length(Buffer));
|
|
Result.size := FileFO4.Size;
|
|
// Modified: Make sure memory is zeroed when allocated
|
|
Result.data := AllocMem(Result.size);
|
|
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;
|
|
// Modified: Make sure memory is zeroed when allocated
|
|
Result.data := AllocMem(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;
|
|
|
|
// Modified: Make sure memory is zeroed when allocated
|
|
Result.data := AllocMem(Result.size);
|
|
|
|
DDSHeader := @Result.data[0];
|
|
DDSHeader.Magic := MAGIC_DDS;
|
|
DDSHeader.dwSize := SizeOf(TDDSHeader) - SizeOf(TMagic4);
|
|
DDSHeader.dwWidth := FileFO4.Width;
|
|
DDSHeader.dwHeight := FileFO4.Height;
|
|
DDSHeader.dwFlags := DDSD_CAPS or DDSD_PIXELFORMAT or
|
|
DDSD_WIDTH or DDSD_HEIGHT or DDSD_MIPMAPCOUNT;
|
|
DDSHeader.dwCaps := DDSCAPS_TEXTURE;
|
|
DDSHeader.dwMipMapCount := FileFO4.NumMips;
|
|
if DDSHeader.dwMipMapCount > 1 then
|
|
DDSHeader.dwCaps := DDSHeader.dwCaps or DDSCAPS_MIPMAP or DDSCAPS_COMPLEX;
|
|
DDSHeader.dwDepth := 1;
|
|
|
|
DDSHeaderDX10 := @Result.data[SizeOf(TDDSHeader)];
|
|
DDSHeaderDX10.resourceDimension := DDS_DIMENSION_TEXTURE2D;
|
|
DDSHeaderDX10.arraySize := 1;
|
|
|
|
if FileFO4.CubeMaps = 2049 then begin
|
|
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 aFilePath: string): TwbBSResultBuffer;
|
|
var
|
|
FileRecord: Pointer;
|
|
begin
|
|
if not (stReading in fStates) then
|
|
raise Exception.Create('Archive is not loaded');
|
|
|
|
FileRecord := FindFileRecord(aFilePath);
|
|
|
|
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: TwbBSResultBuffer);
|
|
begin
|
|
FreeMem(fileDataResult.data);
|
|
fileDataResult.size := 0;
|
|
end;
|
|
|
|
procedure TwbBSArchive.ExtractFile(const aFilePath, aSaveAs: string);
|
|
var
|
|
fs: TFileStream;
|
|
fileData: TwbBSResultBuffer;
|
|
begin
|
|
if not (stReading in fStates) then
|
|
raise Exception.Create('Archive is not loaded');
|
|
|
|
fs := TFileStream.Create(aSaveAs, fmCreate);
|
|
try
|
|
fileData := ExtractFileDataCompat(aFilePath);
|
|
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 aFilePath: string): Boolean;
|
|
begin
|
|
Result := Assigned(FindFileRecord(aFilePath));
|
|
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.
|